[インデックス 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」に関連するさらなる修正として提出されました。主な変更点は以下の通りです。
crypto/aes
およびcrypto/des
パッケージからReset()
メソッドを削除し、その変更をドキュメントに反映。- いくつかの空のエラー構造体を変数に変換。これにより、エラーの定義がより簡潔になります。
- TLSソケットに対して
SetWriteDeadline
を実装。これにより、TLS接続における書き込み操作のタイムアウト設定が可能になります。 crypto/aes
およびcrypto/des
パッケージのNewCipher
関数が、具体的な暗号器の型ではなく、cipher.Block
インターフェースを返すように変更。
変更の背景
このコミットは、Go言語がバージョン1.0のリリースに向けて、標準ライブラリのAPIを安定させ、堅牢性を高めるための広範な取り組みの一環として行われました。特に暗号化ライブラリにおいては、以下の点が背景にあります。
- APIの統一と抽象化:
crypto/aes
やcrypto/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
パッケージで定義されているネットワーク接続の共通インターフェースです。Read
、Write
、Close
、LocalAddr
、RemoteAddr
、SetDeadline
、SetReadDeadline
、SetWriteDeadline
などのメソッドを持ちます。- エラーハンドリング: Go言語では、エラーは戻り値として明示的に返され、呼び出し元で適切に処理されることが期待されます。このコミットでは、エラーの定義方法を改善し、よりGoらしいエラーハンドリングを促進しています。
技術的詳細
-
Reset()
メソッドの削除:crypto/aes
のCipher
型とcrypto/des
のCipher
型およびTripleDESCipher
型からReset()
メソッドが削除されました。- このメソッドは、暗号鍵が格納されているメモリ領域をゼロクリアすることを意図していましたが、Goのガベージコレクタはメモリのコピーを自由に行うため、
Reset()
を呼び出しても、コピーされた鍵データがメモリ上に残る可能性がありました。 - この変更は、開発者が
Reset()
を呼び出すことで鍵データが完全に消去されると誤解するのを防ぎ、より安全なプログラミングを促すものです。鍵の機密性を確保するためには、鍵を使い終わったら参照をなくし、ガベージコレクタに任せるのがGoの推奨するアプローチです。
-
空のエラー構造体の変数化:
crypto/dsa
のErrInvalidPublicKey
、crypto/rsa
のErrMessageTooLong
,ErrDecryption
,ErrVerification
、crypto/x509
のErrUnsupportedAlgorithm
といったエラーが、以前は空の構造体として定義され、そのインスタンスが返されていました。- このコミットでは、これらのエラーが
errors.New()
関数を使ってvar
として定義されるようになりました。 - 例:
type MessageTooLongError struct{}
からvar ErrMessageTooLong = errors.New("crypto/rsa: message too long for RSA public key size")
- これにより、エラーの比較が
err == ErrMessageTooLong
のように直接行えるようになり、コードの簡潔さと効率性が向上します。
-
TLSソケットにおける
SetWriteDeadline
の実装:crypto/tls/conn.go
において、Conn
型のSetWriteDeadline
メソッドが実装されました。以前は「TLSはSetWriteDeadline
をサポートしていません」というエラーを常に返していました。- この変更により、TLS接続の書き込み操作にタイムアウトを設定できるようになりました。
- 重要な注意点として、コミットメッセージにもあるように「Writeエラーが発生した後、TLSの状態は破損し、将来のすべての書き込みは同じエラーを返す」という挙動が明記されています。これは、TLSプロトコルの性質上、書き込みエラーが発生するとセッションの状態が不整合になるためです。
-
crypto/aes
とcrypto/des
がcipher.Block
を返すように変更:crypto/aes.NewCipher
は、以前は*aes.Cipher
を返していましたが、cipher.Block
インターフェースを返すように変更されました。同様に、crypto/des.NewCipher
とcrypto/des.NewTripleDESCipher
もcipher.Block
を返すようになりました。- これに伴い、
aes.Cipher
、des.Cipher
、des.TripleDESCipher
といった具体的な型は、それぞれaesCipher
、desCipher
、tripleDESCipher
という非公開の型(小文字で始まる)に変更されました。 - これにより、これらのパッケージを利用する側は、具体的な実装の詳細を知ることなく、
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
型がそれぞれdesCipher
、tripleDESCipher
にリネームされました。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/aes
とcrypto/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リリースに向けて、標準ライブラリ全体でエラーハンドリングの一貫性を高めるための重要な変更でした。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go 1リリースノート: https://golang.org/doc/go1 (このコミットで更新された内容も含まれます)
crypto/cipher
パッケージのドキュメント: https://golang.org/pkg/crypto/cipher/
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/5625045
はGerritのチェンジリストへのリンクです) - Go言語のバグトラッカー: https://golang.org/issue/ (コミットメッセージに記載されている「bug 2841」は、このバグトラッカーのIDを指します)
- Go言語の
errors
パッケージのドキュメント: https://golang.org/pkg/errors/