So-net無料ブログ作成

HTTP/2.0はTLSを使うのでそこから [プログラミング]

TLSは使ったことがあるのですが、正直覚えてない。OpenSSLでやったのでそもそものやり方も違うだろうし、そもそもサーバ側を作ったことがなかったのでした。Webはクライアントサイドを作るよりかサーバを作る方が簡単なので、クライアントはWindows側のありもののブラウザで、サーバは普通にLinux上のGolangで実装してみましょう、という事になった。

まぁそもそもHTTP/1.1のサーバをGolangで作ろうとしていたんだけど、標準パッケージに簡単に実現できてしまったので、そこを深堀するかHTTP/2.0のサーバを作ってみようと思っていた。普通なら標準パッケージを深堀してHTTP/1.1をきちんと学んだ方がいいのだろうが、無理して2.0に。でも、もうすでに作ってあるので車輪の再発明は馬鹿らしいと言えばそうなのだが、やっぱり自分で実装してみたいというのがあるのでやってみます。

まずTLS1.2なのですが、サンプルソースが少々怪しい。

https://github.com/nareix/tls-example

実行結果がChina, Fuck, FuckUとか出てきていて穏やかでない。GitHubの名前がピンインっぽいんだけどな。とにかく、TLSの証明書をでっち上げて、通信を通すところまではできるようだ。


 
原理的には元々のプロトコルがあって、それをSSLで暗号化する形だと思う。少なくともOpenSSLでGmailのPOP3Sを使った時は、暗号以外の手順はまんまPOP3であった。そんなわけでHTTPSもSSLでHTTPをラップ下だけのものなのでしょうね。まぁ標準的なポートは80と443と違うわけだが、基本一緒だと思う。

ともあれHTTP/2.0はHTTP/1.1とはほとんど別のプロトコルなので(送る実質的な内容は一緒でも)、あんまり関係ないんだけどね。2.0でSSLを使わないバージョンはほとんどないみたいなので、とりあえずはSSLを使えないと話にならない。

TLSという名前になっとるけど、SSLも脆弱性があってその記事も読まないとなぁ。

ソフトウェアデザイン 2015年 08 月号 [雑誌]

ソフトウェアデザイン 2015年 08 月号 [雑誌]

  • 作者:
  • 出版社/メーカー: 技術評論社
  • 発売日: 2015/07/18
  • メディア: 雑誌


脆弱性は一番新しいそうなものを使って回避するとして、後できっちり読んでおこう。

動いているところは、証明書を作って、サーバを稼働して、クライアントをつないで反応してたから問題なさそう。とりあえず証明書を作っているところを見てみたい。前に作った時は適当にどこかから取ってきた気がした。作ったのはしばらく前なので忘れたが。




せっかく自分で一部分インプリしたものがあるので、手続きを関数名で類推するぐらいの事は出来るだろう。OpenSSLはヤバいと言われていたけど、基本的にクライアントで使っているからあんまり気にしてない。というかGPLにしているからOSSにした上に責任を放棄している。使いたければ自分でやってよ的な。

簡単に関数を並べてみるとこんな感じ。

socket() ソケット作って
connect() つなぐ(ここまで普通にソケット)
SSL_load_error_strings() エラーメッセージを文字列で
SSL_library_init() SSL/TLSの初期化
SSL_CTX_new(TLSv1_client_method()) プロトコル選択。GmailはTLS
SSL_new() SSL_CTX構造体を生成
SSL_set_fd() ソケットとSSLの構造体を結びつけ
RAND_poll() 乱数の種生成
RAND_status() 乱数の種の過不足チェック
RAND_seed() 乱数の大きさが小さい場合追加
SSL_connect() サーバとハンドシェイク
SSL_write() 送信
SSL_read() 受信
SSL_free() SSL_new()で確保した領域解放
SSL_CTX_free() SSL_CTX_new()で確保した領域解放
ERR_free_strings() SSL_load_error_strings()で確保した領域解放

ん~クライアント側だから、いまいち参考にならないかもな。基本、SSLの証明書はサーバ側で必要だから、あんまり意味がないのかも。




とりあえず、例のソースを見てみる。
表面上としては
 ca.pem, ca.key, cert2.key cert2.pem
が生成される。そもそもこれらが何なのか知らない。

どっちかがサーバで、他のはクライアント用のもので、拡張子ごとにルート証明書か秘密鍵なんでしょう。呼び方が色々だし、何やってるんだかわかんねーな。具体的にどうやっているのかと、その仕組みを一度に説明できているところがあまりない。でっち上げているにしても、その方法を確かめないと、何やってんだかわからないし今後に生かせない。


package main

import (
	"crypto/x509"
	"crypto/x509/pkix"
	"crypto/rsa"
	"crypto/rand"
	"math/big"
	"io/ioutil"
	"log"
	"time"
)

func main() {
	//こっちはCA(認証局)証明書?
	ca := &x509.Certificate{	//認証関係の構造体
		SerialNumber: big.NewInt(1653),
		Subject: pkix.Name{
			Country: []string{"China"},
			Organization: []string{"Yjwt"},
			OrganizationalUnit: []string{"YjwtU"},
		},
		NotBefore: time.Now(),
		NotAfter: time.Now().AddDate(10,0,0),
		SubjectKeyId: []byte{1,2,3,4,5},
		BasicConstraintsValid: true,
		IsCA: true,
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage: x509.KeyUsageDigitalSignature|x509.KeyUsageCertSign,
	}

	priv, _ := rsa.GenerateKey(rand.Reader, 1024)	//RSAキーのペアを乱数で作る
	pub := &priv.PublicKey
	
	//上の構造体、RSAの秘密鍵から証明書を作る(テンプレートも親も同じくでっち上げ?)
	ca_b, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv)
	if err != nil {
		log.Println("create ca failed", err)
		return
	}
	ca_f := "ca.pem"	//証明書のファイル名
	log.Println("write to", ca_f)
	ioutil.WriteFile(ca_f, ca_b, 0777)	//ca.pemに証明書を書き込み

	priv_f := "ca.key"
	priv_b := x509.MarshalPKCS1PrivateKey(priv)	//RSAな秘密鍵からASN.1 DER形式に変換
	log.Println("write to", priv_f)
	ioutil.WriteFile(priv_f, priv_b, 0777)	//ca.keyはRSAの秘密鍵

	//自分のところの証明書?をCA証明書から作る。
	cert2 := &x509.Certificate{
		SerialNumber: big.NewInt(1658),
		Subject: pkix.Name{
			Country: []string{"China"},
			Organization: []string{"Fuck"},	//ここが上と違う
			OrganizationalUnit: []string{"FuckU"},	//ここが上と違う
		},
		NotBefore: time.Now(),
		NotAfter: time.Now().AddDate(10,0,0),
		SubjectKeyId: []byte{1,2,3,4,6},	//上のと違う
		ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage: x509.KeyUsageDigitalSignature|x509.KeyUsageCertSign,
	}
	priv2, _ := rsa.GenerateKey(rand.Reader, 1024)	//RSAキーのペアを乱数で作る
	pub2 := &priv2.PublicKey
	
	//新しい証明書の設定をテンプレートで、前の証明書の設定をCA証明書(親?)で、証明書を作成
	cert2_b, err2 := x509.CreateCertificate(rand.Reader, cert2, ca, pub2, priv)
	if err2 != nil {
		log.Println("create cert2 failed", err2)
		return
	}

	cert2_f := "cert2.pem"
	log.Println("write to", cert2_f)
	ioutil.WriteFile(cert2_f, cert2_b, 0777)	//cert2.pemに証明書を書き込み

	priv2_f := "cert2.key"
	priv2_b := x509.MarshalPKCS1PrivateKey(priv2)	//RSAな秘密鍵からASN.1 DER形式に変換
	log.Println("write to", priv2_f)
	ioutil.WriteFile(priv2_f, priv2_b, 0777)	//cert2.keyにRSA秘密鍵を書き込み

	ca_c, _ := x509.ParseCertificate(ca_b)	//ASN.1 DERなCA証明書を解析する
	cert2_c, _ := x509.ParseCertificate(cert2_b)	//同上で自前の証明書の解析

	err3 := cert2_c.CheckSignatureFrom(ca_c)	//CA証明書から自前の証明書を署名が大丈夫か調べる
	log.Println("check signature", err3 == nil)
}


細かいところはコメントで打ち込みましたが、要するにCA証明書をでっち上げて、更にそれを元に自分の証明書を作ってしまうって事みたいですね。いろいろやっちゃいるけど、証明書を作っていることに違いはない。たぶん下のような認識でいいと思う。

ca.pem 認証局の証明書
ca.key 認証局のRSA鍵(ASN.1 DER)
cert2.pem 自前の証明書
cert2.key 自前のRSA鍵(ASN.1 DER)


ファイル名でcert2って書いているのは自分のところの証明書で、caってのはCA証明書の事だろう。んで、CA証明書の方はでっち上げているので、普通に使おうとするとクライアント側から怒られること間違いなし。でも使う時に差し替えて動けば問題ない。その場合はCAをでっち上げるコードはいらない。そこいらはそんなに問題となることはないだろう。ソースを読めれば自分で書き換えることはそれほど難しくないはずだ。ソースを直すのが面倒であれば、OpenSSLなどの既存のツールを使えば済むことだ。

あとクライアントとサーバですね。どちらかというとこっちが主目的なんだけど見ていきます。

・クライアント側
https://github.com/nareix/tls-example/blob/master/client.go
package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io"
	"io/ioutil"
	"log"
)

func main() {
	cert2_b, _ := ioutil.ReadFile("cert2.pem")
	priv2_b, _ := ioutil.ReadFile("cert2.key")
	priv2, _ := x509.ParsePKCS1PrivateKey(priv2_b)

	cert := tls.Certificate{
		Certificate: [][]byte{ cert2_b },
		PrivateKey: priv2,
	}

	config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
	conn, err := tls.Dial("tcp", "127.0.0.1:443", &config)
	if err != nil {
		log.Fatalf("client: dial: %s", err)
	}
	defer conn.Close()
	log.Println("client: connected to: ", conn.RemoteAddr())

	state := conn.ConnectionState()
	for _, v := range state.PeerCertificates {
		fmt.Println(x509.MarshalPKIXPublicKey(v.PublicKey))
		fmt.Println(v.Subject)
	}
	log.Println("client: handshake: ", state.HandshakeComplete)
	log.Println("client: mutual: ", state.NegotiatedProtocolIsMutual)

	message := "Hello\n"
	n, err := io.WriteString(conn, message)
	if err != nil {
		log.Fatalf("client: write: %s", err)
	}
	log.Printf("client: wrote %q (%d bytes)", message, n)

	reply := make([]byte, 256)
	n, err = conn.Read(reply)
	log.Printf("client: read %q (%d bytes)", string(reply[:n]), n)
	log.Print("client: exiting")
}


クライアント側と言っているけれど、サーバの証明書やRSA鍵とかも直接使ってる気がするので、例として適切じゃないかもしれない。あ~認証局とのやり取りをしたかったみたい。サーバのソースの方はでっち上げたCA証明書とかを直で読み込んでいたりするので、普通のサーバとブラウザなどのクライアントとはちょっと違うやり取りになっているっぽい。

https://github.com/nareix/tls-example/blob/master/server.go

ともあれ、サーバ側で証明書とかRSAのファイルを読んで、tls.Listen()で待ち受けているのは同じなので、TLSを使うのはそこそこ簡単であろうと思う。何か標準パッケージの中に使えそうなソースがあるので今度はそっちを見てみたいと思う。

タグ:Golang
コメント(0) 
共通テーマ:パソコン・インターネット

コメント 0