Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11846] ファイルの概要

このコミットは、Go言語の標準ライブラリにおける暗号化関連パッケージ(crypto/aes, crypto/des, crypto/tls, crypto/rsa, crypto/dsa, crypto/x509, crypto/rand)に対する複数の修正と改善を目的としています。特に、Go 1リリースに向けたAPIの整理と、セキュリティおよび堅牢性の向上に焦点を当てています。

コミット

commit cdd7e02583325b05024cc1366a59eaafc09e1dc3
Author: Adam Langley <agl@golang.org>
Date:   Mon Feb 13 12:38:45 2012 -0500

    crypto/...: more fixes for bug 2841
    
    1) Remove the Reset() member in crypto/aes and crypto/des (and
       document the change).
    2) Turn several empty error structures into vars. Any remaining error
       structures are either non-empty, or will probably become so in the
       future.
    3) Implement SetWriteDeadline for TLS sockets. At the moment, the TLS
       status cannot be reused after a Write error, which is probably fine
       for most uses.
    4) Make crypto/aes and crypto/des return a cipher.Block.
    
    R=rsc, r
    CC=golang-dev
    https://golang.org/cl/5625045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/cdd7e02583325b05024cc1366a59eaafc09e1dc3

元コミット内容

このコミットは、Go言語のバグトラッカーにおける「bug 2841」に関連するさらなる修正として提出されました。主な変更点は以下の通りです。

  1. crypto/aes および crypto/des パッケージから Reset() メソッドを削除し、その変更をドキュメントに反映。
  2. いくつかの空のエラー構造体を変数に変換。これにより、エラーの定義がより簡潔になります。
  3. TLSソケットに対して SetWriteDeadline を実装。これにより、TLS接続における書き込み操作のタイムアウト設定が可能になります。
  4. crypto/aes および crypto/des パッケージの NewCipher 関数が、具体的な暗号器の型ではなく、cipher.Block インターフェースを返すように変更。

変更の背景

このコミットは、Go言語がバージョン1.0のリリースに向けて、標準ライブラリのAPIを安定させ、堅牢性を高めるための広範な取り組みの一環として行われました。特に暗号化ライブラリにおいては、以下の点が背景にあります。

  • APIの統一と抽象化: crypto/aescrypto/des のような具体的な暗号アルゴリズムの実装が、より汎用的な cipher.Block インターフェースを返すようにすることで、利用者は特定の暗号アルゴリズムに依存することなく、ブロック暗号の共通インターフェースを通じて操作できるようになります。これにより、コードの再利用性、柔軟性、および将来的な拡張性が向上します。
  • メモリ管理とセキュリティ: Reset() メソッドの削除は、暗号鍵などの機密情報がメモリ上に残る可能性を減らすためのセキュリティ上の配慮です。Goのガベージコレクタはメモリのコピーを保証しないため、Reset() のようなメソッドが存在すると、開発者が誤解して機密情報が安全に消去されたと考えるリスクがありました。
  • エラーハンドリングの改善: 空のエラー構造体を変数にすることで、エラーの比較がより効率的になり、コードの可読性も向上します。
  • ネットワーク通信の堅牢性: TLS接続における SetWriteDeadline の実装は、ネットワークの不安定性や相手側の応答遅延によって書き込み操作がブロックされることを防ぎ、アプリケーションの応答性を向上させるために重要です。

前提知識の解説

  • Go言語のcryptoパッケージ: Go言語の標準ライブラリには、暗号化およびハッシュ関数を提供するcryptoパッケージ群が含まれています。これらは、データの機密性、完全性、認証性を確保するために使用されます。
  • ブロック暗号: データを固定長のブロックに分割して暗号化する方式です。AES (Advanced Encryption Standard) や DES (Data Encryption Standard) が代表的なブロック暗号です。
  • cipher.Blockインターフェース: Go言語のcrypto/cipherパッケージで定義されているインターフェースで、ブロック暗号が満たすべき共通の振る舞いを定義しています。具体的には、BlockSize()(ブロックサイズを返す)とEncrypt()Decrypt()(ブロックの暗号化・復号化を行う)メソッドを持ちます。このインターフェースを導入することで、特定の暗号アルゴリズムに依存しない汎用的なコードを書くことが可能になります。
  • TLS (Transport Layer Security): インターネット上で安全な通信を行うためのプロトコルです。ウェブサイトのHTTPS接続などで広く利用されています。
  • net.Connインターフェース: Go言語のnetパッケージで定義されているネットワーク接続の共通インターフェースです。ReadWriteCloseLocalAddrRemoteAddrSetDeadlineSetReadDeadlineSetWriteDeadlineなどのメソッドを持ちます。
  • エラーハンドリング: Go言語では、エラーは戻り値として明示的に返され、呼び出し元で適切に処理されることが期待されます。このコミットでは、エラーの定義方法を改善し、よりGoらしいエラーハンドリングを促進しています。

技術的詳細

  1. Reset() メソッドの削除:

    • crypto/aesCipher型とcrypto/desCipher型およびTripleDESCipher型からReset()メソッドが削除されました。
    • このメソッドは、暗号鍵が格納されているメモリ領域をゼロクリアすることを意図していましたが、Goのガベージコレクタはメモリのコピーを自由に行うため、Reset()を呼び出しても、コピーされた鍵データがメモリ上に残る可能性がありました。
    • この変更は、開発者がReset()を呼び出すことで鍵データが完全に消去されると誤解するのを防ぎ、より安全なプログラミングを促すものです。鍵の機密性を確保するためには、鍵を使い終わったら参照をなくし、ガベージコレクタに任せるのがGoの推奨するアプローチです。
  2. 空のエラー構造体の変数化:

    • crypto/dsaErrInvalidPublicKeycrypto/rsaErrMessageTooLong, ErrDecryption, ErrVerificationcrypto/x509ErrUnsupportedAlgorithmといったエラーが、以前は空の構造体として定義され、そのインスタンスが返されていました。
    • このコミットでは、これらのエラーがerrors.New()関数を使ってvarとして定義されるようになりました。
    • 例: type MessageTooLongError struct{} から var ErrMessageTooLong = errors.New("crypto/rsa: message too long for RSA public key size")
    • これにより、エラーの比較がerr == ErrMessageTooLongのように直接行えるようになり、コードの簡潔さと効率性が向上します。
  3. TLSソケットにおけるSetWriteDeadlineの実装:

    • crypto/tls/conn.goにおいて、Conn型のSetWriteDeadlineメソッドが実装されました。以前は「TLSはSetWriteDeadlineをサポートしていません」というエラーを常に返していました。
    • この変更により、TLS接続の書き込み操作にタイムアウトを設定できるようになりました。
    • 重要な注意点として、コミットメッセージにもあるように「Writeエラーが発生した後、TLSの状態は破損し、将来のすべての書き込みは同じエラーを返す」という挙動が明記されています。これは、TLSプロトコルの性質上、書き込みエラーが発生するとセッションの状態が不整合になるためです。
  4. crypto/aescrypto/descipher.Blockを返すように変更:

    • crypto/aes.NewCipherは、以前は*aes.Cipherを返していましたが、cipher.Blockインターフェースを返すように変更されました。同様に、crypto/des.NewCiphercrypto/des.NewTripleDESCiphercipher.Blockを返すようになりました。
    • これに伴い、aes.Cipherdes.Cipherdes.TripleDESCipherといった具体的な型は、それぞれaesCipherdesCiphertripleDESCipherという非公開の型(小文字で始まる)に変更されました。
    • これにより、これらのパッケージを利用する側は、具体的な実装の詳細を知ることなく、cipher.Blockインターフェースを通じてブロック暗号の操作を行うことができます。これは、Goのインターフェースの強力な活用例であり、APIの抽象化と柔軟性を高めます。

コアとなるコードの変更箇所

このコミットは広範囲にわたる変更を含んでいますが、特に重要な変更は以下のファイルに見られます。

  • src/pkg/crypto/aes/cipher.go: NewCipher関数の戻り値の型が*Cipherからcipher.Blockに変更され、Cipher型がaesCipherにリネームされました。Reset()メソッドが削除されました。
  • src/pkg/crypto/des/cipher.go: NewCipherおよびNewTripleDESCipher関数の戻り値の型がそれぞれ*Cipher*TripleDESCipherからcipher.Blockに変更され、Cipher型とTripleDESCipher型がそれぞれdesCiphertripleDESCipherにリネームされました。Reset()メソッドが削除されました。
  • src/pkg/crypto/tls/conn.go: SetWriteDeadlineメソッドの実装が追加されました。また、Writeメソッド内でエラーハンドリングが改善され、c.errフィールドを使用してTLS接続の状態を管理するようになりました。
  • src/pkg/crypto/dsa/dsa.go, src/pkg/crypto/rsa/pkcs1v15.go, src/pkg/crypto/rsa/rsa.go, src/pkg/crypto/x509/x509.go: これらのファイルでは、空のエラー構造体の定義がerrors.New()を用いたvar定義に置き換えられました。
  • src/pkg/crypto/rand/rand_unix.go: reader構造体のcipherフィールドの型が*aes.Cipherからcipher.Blockに変更されました。
  • doc/go1.html および doc/go1.tmpl: crypto/aescrypto/desパッケージに関するGo 1の変更点として、Resetメソッドの削除と、具体的な暗号器の型がcipher.Blockに置き換えられたことが追記されました。

コアとなるコードの解説

crypto/aes/cipher.go および crypto/des/cipher.go の変更

// src/pkg/crypto/aes/cipher.go (抜粋)
-type Cipher struct {
+type aesCipher struct {
 	enc []uint32
 	dec []uint32
 }

-func NewCipher(key []byte) (*Cipher, error) {
+func NewCipher(key []byte) (cipher.Block, error) {
 	// ...
-	c := &Cipher{make([]uint32, n), make([]uint32, n)}
+	c := &aesCipher{make([]uint32, n), make([]uint32, n)}
 	// ...
 	return c, nil
 }

// Reset() メソッドは削除された

この変更により、NewCipher関数は、具体的な実装型である*aesCipher(または*desCipher*tripleDESCipher)を直接返すのではなく、crypto/cipherパッケージで定義されているcipher.Blockインターフェースを返すようになりました。これにより、AESやDESの具体的な実装に依存せず、より抽象的なレベルでブロック暗号を扱うことができるようになります。

Reset()メソッドの削除は、Goのメモリ管理モデル(ガベージコレクション)と整合性を取るための重要な変更です。Goでは、メモリのコピーが頻繁に行われるため、Reset()を呼び出しても、以前の鍵データがメモリの別の場所にコピーされて残ってしまう可能性がありました。このため、Reset()は誤解を招く可能性があり、セキュリティ上の保証を提供できないと判断されました。

crypto/tls/conn.go の変更

// src/pkg/crypto/tls/conn.go (抜粋)
// SetWriteDeadline sets the write deadline on the underlying conneciton.
// A zero value for t means Write will not time out.
// After a Write has timed out, the TLS state is corrupt and all future writes will return the same error.
func (c *Conn) SetWriteDeadline(t time.Time) error {
	return c.conn.SetWriteDeadline(t)
}

func (c *Conn) Write(b []byte) (int, error) {
	if c.err != nil { // 以前のWriteエラーの状態をチェック
		return 0, c.err
	}
	if c.err = c.Handshake(); c.err != nil { // ハンドシェイクエラーもc.errに保存
		return 0, c.err
	}
	// ...
	var n int
	n, c.err = c.writeRecord(recordTypeApplicationData, b) // writeRecordの結果もc.errに保存
	return n, c.err
}

SetWriteDeadlineの実装は、基盤となるネットワーク接続(c.conn)のSetWriteDeadlineを呼び出すことで実現されています。これにより、TLS接続の書き込み操作にタイムアウトを設定できるようになりました。

Writeメソッドの変更は、TLS接続の堅牢性を高めるためのものです。以前は、Write中にエラーが発生した場合、そのエラーがc.errに保存されず、後続のWrite呼び出しで同じエラーが返される保証がありませんでした。この変更により、Handshake()writeRecord()で発生したエラーがc.errフィールドに確実に保存され、一度書き込みエラーが発生すると、その後のすべての書き込み操作が同じエラーを返すようになります。これは、TLSセッションが一度破損すると再利用できないというプロトコルの特性を反映したものです。

エラー定義の変更例 (crypto/rsa/rsa.go 抜粋)

// src/pkg/crypto/rsa/rsa.go (抜粋)
-type MessageTooLongError struct{}
-func (MessageTooLongError) Error() string {
-	return "message too long for RSA public key size"
-}
+// ErrMessageTooLong is returned when attempting to encrypt a message which is
+// too large for the size of the public key.
+var ErrMessageTooLong = errors.New("crypto/rsa: message too long for RSA public key size")

この変更は、Goのエラーハンドリングのベストプラクティスに沿ったものです。以前は、エラーを表現するために空の構造体が定義され、そのインスタンスが返されていました。この方法では、エラーの比較は_, ok := err.(MessageTooLongError)のように型アサーションを使って行う必要がありました。

新しい方法では、errors.New()関数を使ってエラーメッセージを持つerror型の変数を定義します。これにより、エラーの比較がerr == ErrMessageTooLongのように直接行えるようになり、コードの可読性と保守性が向上します。これは、Go 1リリースに向けて、標準ライブラリ全体でエラーハンドリングの一貫性を高めるための重要な変更でした。

関連リンク

参考にした情報源リンク