20220626 Go言語でTLSにチャレンジ/Go言語でVNCサーバを作ってみよう!
# 前半
Go言語でTLSにチャレンジ さとうさん
## 資料
テキスト
「golangで作るTLS1.2プロトコル」
https://zenn.dev/satoken/articles/golang-tls1_2
6ページ目のネゴシエーションの手順図を参照
「SSL を理解するための基礎ネゴシエーション」
https://www.digicert.co.jp/welcome/pdf/wp_ssl_negotiation.pdf
## 環境
環境として、Go 言語環境と 証明書が必要です。
また、今回のサンプルコードは Linux 環境用です。以下の環境で対応できます。
- Linux ( PC, RaspberryPi )
- Windows ( WSL )
- Cloud Shell Editor
Macは対応できていないです。
Macの方や、WindowsでWSLを使わない方は Cloud Shell Editor を使いましょう → https://shell.cloud.google.com/?pli=1&show=ide,terminal
Cloud Shell Editor は Go 言語もインストール不要でそのまま使用できます。
ローカルでの環境構築が必要な方や、前回の復習についてはこちらを参照してください。
「20220529 低レベル勉強会 Go言語でTCP/IP をみんなでチャレンジ!」
https://hackmd.io/BCGiB2WMQdyeTgNN43aGfg
### 証明書
mkcert コマンドを使いましょう。
https://github.com/FiloSottile/mkcert
```
$ curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
$ chmod 555 mkcert-v1.4.4-linux-amd64
$ ./mkcert-v1.4.4-linux-amd64 -install
$ ./mkcert-v1.4.4-linux-amd64 my-tls localhost 127.0.0.1
```
これで、以下の 2 つが生成されます。
```
The certificate is at "./my-tls+2.pem" and the key at "./my-tls+2-key.pem"
```
### コード
https://github.com/sat0ken/go-tcpip にあります。
```
$ git clone https://github.com/sat0ken/go-tcpip.git
$ cd go-tcpip
```
先程作成した pem ファイルを debug/https-server.go の 36行目に記述します。
また、31行目のMaxVersionをtls12からtls13に書き換えます。
書き換え完了したら、
```
$ cd debug
$ go run https-server.go
```
としてサーバが起動するので
exampleのtls13_handshake.goプログラムと debug プログラムを動かしてみましょう
tls13_handshake.go の 24 行目の LOCALPORT=8443 をhttps-serer.go に合わせて 10443 に書き換えます
```
$ go run tls13_handshake.go
ServerHello : {HandshakeType:[2] Length:[70 0 0] Version:[0 0] Random:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] SessionIDLength:[0] SessionID:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] CipherSuites:[0 0] CompressionMethod:[0] ExtensionLength:[0 0] TLSExtensions:[{Type:[0 0] Length:[0 0] Value:[0 0]} {Type:[0 0] Length:[0 0] Value:map[Group:[0 0] KeyExchange:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] KeyExchangeLength:[0 0]]}]}
server key share is 0000000000000000000000000000000000000000000000000000000000000000
sharedkey is
derivedSecretForhs 6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba
handshake_secret is : c3ca2291d7991eb40aa4562aea7b5463ba923a29e63baae1095150de7bbbb696
hashed messages is 4c3c49ddddb013853da41aa5caa5c310eaadf81252e0180291c71065477fcee3
CLIENT_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 d98cacac3647c8d4ebae26b4694b93e2e28847fc9012c237503dc32b0228ff6f
SERVER_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 a68907925d31034f548a3a55cb885a383c5f419b72f782ebfcf5d55b17794d49
serverfinkey is : a2760d69a3c6d4be4efcf56ff07958649089a1b438b9b62b13cd58f76fbe65b8
derivedSecretFormaster is : 5eae5ae9b127a860a8e35e3ea985289dbcb7ad08fa96e8d4a9faa6f7294aa189
extractSecretMaster is : cd6d71f2cfbdb144241be8baed070d9b8e7ab1068be6cdcf496984d7dd3e44c1
client traffic key is : d7142cf85e0d7044b0babd5dea610f3fefd908bbbbbea09ac05f12747f1a9f7e
client traffic iv is : 8c07385fd280cc95708e9634
server traffic key is : 9eed2183c7192ad4af453dd6f43c9889d7978bb59720c48c62f9df7cc124dde7
server traffic iv is : 84f871ed3b77f5c06944cb57
read ChangeCipherSpec is 150301000202, これから暗号化するんやでー
panic: runtime error: slice bounds out of range [:17928] with capacity 16
goroutine 1 [running]:
main.main()
/home/nanbuwks_office/go-tcpip/example/tls13_handshake.go:89 +0x174b
exit status 2
```
# 後半
Go言語でVNCサーバを作ってみよう! Tamさん
## 環境
環境と以下の環境で対応できます。
- Linux ( PC, RaspberryPi )
- Windows ( WSL )
- Mac OSX
(Cloud Shell Editor では難しい)
ローカルでの環境構築が必要な方や、前回の復習についてはこちらを参照してください。
「20220529 低レベル勉強会 Go言語でTCP/IP をみんなでチャレンジ!」
https://hackmd.io/BCGiB2WMQdyeTgNN43aGfg
## 試してみよう!
https://github.com/tamx/EasyVNC
こちらをクローンして実行。
```
$ go run main.go
```
適当なマシンで VNCViewer を起動します。 main.go を実行しているマシンと同じでも違うマシンでも大丈夫ですが、同じLANに接続しているのが望ましいです。
VNCViewer で、main.go にアクセスしたら画面が出てきます。
main.go のアドレスは、VNCViewer 同じ論理マシンだった場合は 127.0.0.1 あるいは localhost ):port5900
### VNCビューワインストール例
#### Windows
UltraVNC で成功しています。
WindowsでUltraVNCのビューアでローカル接続すると「The Server has been setup without authentication, do you trust this server?」の警告が出ましたが、そのままOKで進めたら遠隔画面が表示されました。
#### Mac OSX
本家VNCを使ってみましょう。
https://www.realvnc.com/en/connect/download/viewer/macos/
#### Ubuntu Linux 20.04
VNCビューワは以下のようにしてインストールできます。
ex.,
```
$ sudo apt install gvncviewer
```
以下のようにしてアクセスしましょう。
```
$ gvncviewer localhost
```
## トラブルシュート!!
既に5900を使っている場合は、main.go の
```
vnc, err := easyvnc.NewEasyVNC(5900, 800, 600)
```
の 5900 を書き換えてください。
## 資料
「VNCプロトコルとRDPについて」
https://gist.github.com/261shimizu/75e49beb2a6451561bdd46d612cb764b
「RFC 6143 - The Remote Framebuffer Protocol 日本語訳」
https://tex2e.github.io/rfc-translater/html/rfc6143.html
## プロとコルの解説
ネゴジエーションして描画するだけです
### ネゴシエーション
148行目からのコードについて説明していきます。
```
func (vnc *EasyVNC) doNegotiation(conn *net.TCPConn) error {
defer conn.Close()
buf := make([]byte, 1024)
_, err := conn.Write([]byte("RFB 003.008\n"))
if err != nil {
return err
}
_, err = conn.Read(buf) // "RFB 003.008\n"
if err != nil {
return err
}
fmt.Println("Read RFB")
_, err = conn.Write([]byte{1, /* Length */
1 /* Security None */})
if err != nil {
return err
}
auth, err := ReadByte(conn, nil) // Read auth method
if err != nil {
return err
}
fmt.Println("Read auth method: " + strconv.Itoa(auth))
if auth != 1 {
return nil
}
err = WriteInt(conn, nil, 0) // Success
if err != nil {
return err
}
_, err = ReadByte(conn, nil) // Read shared flag
if err != nil {
return err
}
fmt.Println("Read shared flag")
err = WriteShort(conn, nil, vnc.frame_width) // Frame Width
err = WriteShort(conn, err, vnc.frame_height) // Frame Height
err = WriteByte(conn, err, 32) // Bits Per
err = WriteByte(conn, err, 24) // Depth
err = WriteByte(conn, err, 0) // Big Endian Flag
err = WriteByte(conn, err, 1) // True Color Flag
err = WriteShort(conn, err, 0xff) // Red MAX
err = WriteShort(conn, err, 0xff) // Green MAX
err = WriteShort(conn, err, 0xff) // Blue MAX
err = WriteByte(conn, err, 16) // Red Shift
err = WriteByte(conn, err, 8) // Green Shift
err = WriteByte(conn, err, 0) // Blue Shift
err = WriteByte(conn, err, 0) // Padding
err = WriteByte(conn, err, 0) // Padding
err = WriteByte(conn, err, 0) // Padding
err = WriteInt(conn, err, len(vnc.name))
if err == nil {
_, err = conn.Write([]byte(vnc.name))
}
if err != nil {
return err
}
ch = conn
fmt.Println("Start.")
vnc.sendDummyFrameData()
vnc.SendAllFrameData()
return vnc.loop(conn)
}
```
#### 認証
7.1. Handshake Messages
7.1.2. Security Handshake
について、
```
_, err = conn.Write([]byte{1, /* Length */
1 /* Security None */})
if err != nil {
```
これがセキュリティの選択肢の長さと セキュリティ無しを表します。
```
fmt.Println("Read auth method: " + strconv.Itoa(auth))
if auth != 1 {
return nil
}
```
ここで、対応したセキュリティが帰ってこなかったらエラーとします。
```
err = WriteInt(conn, nil, 0) // Success
```
成功を返します
```
_, err = ReadByte(conn, nil) // Read shared flag
```
複数のクライアントからの同時接続を許可するかどうかがクライアント側から通知される
#### SERVER INIT
7.3.2. ServerInit に対応する箇所が以下になります
```
fmt.Println("Read shared flag")
err = WriteShort(conn, nil, vnc.frame_width) // Frame Width
err = WriteShort(conn, err, vnc.frame_height) // Frame Height
err = WriteByte(conn, err, 32) // Bits Per Pixel
err = WriteByte(conn, err, 24) // Depth
err = WriteByte(conn, err, 0) // Big Endian Flag
err = WriteByte(conn, err, 1) // True Color Flag
err = WriteShort(conn, err, 0xff) // Red MAX
err = WriteShort(conn, err, 0xff) // Green MAX
err = WriteShort(conn, err, 0xff) // Blue MAX
err = WriteByte(conn, err, 16) // Red Shift
err = WriteByte(conn, err, 8) // Green Shift
err = WriteByte(conn, err, 0) // Blue Shift
err = WriteByte(conn, err, 0) // Padding
err = WriteByte(conn, err, 0) // Padding
err = WriteByte(conn, err, 0) // Padding
err = WriteInt(conn, err, len(vnc.name))
```
トコルを読み解く
### 描画
描画は簡単。
easyvnc.go の 308行目
```
func (vnc *EasyVNC) SendFrameData(x, y, width, height int) {
// fmt.Printf("frame %d, %d : %d x %d.\n", x, y, width, height)
if width == 0 || height == 0 {
return
buf := make([]byte, 0)
buf = append(buf, byte(0)) // Type
buf = append(buf, byte(0)) // Padding
buf = append(buf, byte(0)) // Len
buf = append(buf, byte(1))
buf = append(buf, byte(0xff&(x>>8)))
buf = append(buf, byte(0xff&(x>>0)))
buf = append(buf, byte(0xff&(y>>8)))
buf = append(buf, byte(0xff&(y>>0)))
buf = append(buf, byte(0xff&(width>>8)))
buf = append(buf, byte(0xff&(width>>0)))
buf = append(buf, byte(0xff&(height>>8)))
buf = append(buf, byte(0xff&(height>>0)))
buf = append(buf, byte(0)) // Encoding Type
buf = append(buf, byte(0))
buf = append(buf, byte(0))
buf = append(buf, byte(0))
// fmt.Printf("x: %d\n", x)
// fmt.Printf("Width: %d\n", width)
// fmt.Printf("F Width: %d\n", vnc.frame_width)
buf2 := make([]byte, width*height*4)
for y1 := 0; y1 < height; y1++ {
for x1 := 0; x1 < width; x1++ {
color := vnc.PGET(x+x1, y+y1)
r := 0xff & (color >> 16)
g := 0xff & (color >> 8)
b := 0xff & (color >> 0)
buf2[(y1*width+x1)*4+0] = byte(b)
buf2[(y1*width+x1)*4+1] = byte(g)
buf2[(y1*width+x1)*4+2] = byte(r)
// fmt.Printf("Color: %d %d %d\n", r, g, b)
}
}
buf = append(buf, buf2...)
sendVncMessage(buf)
}
```
が資料の以下のプロトコルに対応しています。
- 7.6.1. FramebufferUpdate (矩形描画)
- 7.7.1. 生のエンコーディング (ビットマップ描画)