[インデックス 10870] ファイルの概要
このコミットでは、src/pkg/crypto/crypto.go、src/pkg/crypto/tls/common.go、src/pkg/crypto/tls/handshake_client.go、src/pkg/crypto/tls/key_agreement.go の4つのファイルが変更されています。
コミット
- コミットハッシュ:
2ca4a61658b5561cc807fc1cebe177169ff28034 - Author: Adam Langley agl@golang.org
- Date: Mon Dec 19 10:39:30 2011 -0500
- コミットメッセージ:
crypto/tls: don't assume an RSA private key in the API. We still very much assume it in the code, but with this change in place we can implement other things later without changing and users of the package. Fixes #2319. R=golang-dev, bradfitz, r CC=golang-dev https://golang.org/cl/5489073
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2ca4a61658b5561cc807fc1cebe177169ff28034
元コミット内容
crypto/tls: don't assume an RSA private key in the API.
We still very much assume it in the code, but with this change in
place we can implement other things later without changing and users
of the package.
Fixes #2319.
R=golang-dev, bradfitz, r
CC=golang-dev
https://golang.org/cl/5489073
変更の背景
このコミットの背景には、Go言語の crypto/tls パッケージが、TLS (Transport Layer Security) プロトコルにおける秘密鍵の型として、RSA秘密鍵のみを前提としていたという問題があります。これは、Issue #2319「TLS interface only supports RSA keys」として報告されていました。
TLSプロトコルでは、サーバー認証や鍵交換のために秘密鍵が使用されますが、RSA鍵以外にもECDSA (Elliptic Curve Digital Signature Algorithm) やEd25519などの異なるアルゴリズムに基づく秘密鍵が存在します。しかし、当時の crypto/tls パッケージのAPI設計では、Certificate 構造体の PrivateKey フィールドが *rsa.PrivateKey 型に固定されており、これにより他の種類の秘密鍵を扱うことができませんでした。
この制限は、将来的にRSA以外の鍵アルゴリズムをサポートしようとした際に、APIの破壊的変更を伴う可能性がありました。そのため、APIレベルでRSA秘密鍵への依存を解消し、より汎用的な秘密鍵の型を許容するように変更することが求められました。これにより、コード内部では引き続きRSA秘密鍵を前提とする部分が残るものの、APIの利用者にとっては将来的な拡張性を持たせることが可能になります。
前提知識の解説
crypto/tls パッケージ
crypto/tls はGo言語の標準ライブラリの一部で、TLS (Transport Layer Security) プロトコルを実装するためのパッケージです。TLSは、インターネット上での安全な通信を確立するために広く使用されており、ウェブブラウザとサーバー間のHTTPS通信などで利用されています。このパッケージは、TLSクライアントとサーバーの実装、証明書の管理、鍵交換、暗号化などの機能を提供します。
RSA秘密鍵
RSA (Rivest–Shamir–Adleman) は、公開鍵暗号方式の一つで、デジタル署名や鍵交換に広く用いられています。RSA秘密鍵は、RSA公開鍵とペアになる鍵で、暗号化されたデータの復号やデジタル署名の生成に使用されます。Go言語の crypto/rsa パッケージで扱われます。
Go言語の interface
Go言語の interface は、メソッドのシグネチャの集合を定義する型です。ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを満たすとみなされます。これにより、具体的な型に依存しない汎用的なコードを書くことが可能になります。例えば、io.Reader インターフェースは Read メソッドを持つ任意の型を受け入れることができます。
型アサーション (Type Assertion)
Go言語の型アサーションは、インターフェース型の値が、特定の具象型であるかどうかをチェックし、もしそうであればその具象型の値として取り出すための構文です。
value.(Type) の形式で記述され、value が Type 型であれば、その値が返されます。もし value が Type 型でなければ、パニックが発生します。安全な形式として value, ok := value.(Type) があり、この場合 ok は型アサーションが成功したかどうかを示すブール値になります。
このコミットでは、crypto.PrivateKey インターフェース型として受け取った秘密鍵を、内部処理で *rsa.PrivateKey 型として扱うために型アサーションが使用されています。
技術的詳細
このコミットの主要な技術的変更は、crypto/tls パッケージのAPIがRSA秘密鍵に強く依存している状態を緩和することです。具体的には、以下の点が変更されています。
-
crypto.PrivateKeyインターフェースの導入:src/pkg/crypto/crypto.goに、PrivateKey interface{}という空のインターフェースが導入されました。これは、任意の型を秘密鍵として表現できる汎用的な型となります。 -
Certificate構造体のPrivateKeyフィールドの型変更:src/pkg/crypto/tls/common.go内のCertificate構造体において、PrivateKeyフィールドの型が*rsa.PrivateKeyから新しく定義されたcrypto.PrivateKeyインターフェース型に変更されました。これにより、Certificate構造体はRSA秘密鍵だけでなく、将来的に他のアルゴリズムの秘密鍵も保持できるようになります。コメントには// supported types: *rsa.PrivateKeyと追記され、現時点ではRSA秘密鍵のみがサポートされていることが明示されています。 -
内部コードでの型アサーションの利用:
src/pkg/crypto/tls/handshake_client.goとsrc/pkg/crypto/tls/key_agreement.goの内部処理では、Certificate.PrivateKeyがcrypto.PrivateKey型になったため、RSA秘密鍵に特化した関数 (rsa.SignPKCS1v15,rsa.DecryptPKCS1v15SessionKey) に渡す際に、.(*rsa.PrivateKey)という型アサーションを使用して、crypto.PrivateKey型の値を*rsa.PrivateKey型に変換しています。これは、APIは汎用化されたものの、実際の暗号処理のコードはまだRSA秘密鍵に依存しているためです。
この変更により、crypto/tls パッケージの利用者は、将来的にRSA以外の秘密鍵を扱う際に、Certificate 構造体の定義自体が変わることを心配する必要がなくなりました。パッケージの内部実装はまだRSAに特化していますが、APIの柔軟性が向上し、将来の拡張が容易になっています。
コアとなるコードの変更箇所
src/pkg/crypto/crypto.go
--- a/src/pkg/crypto/crypto.go
+++ b/src/pkg/crypto/crypto.go
@@ -71,3 +71,6 @@ func RegisterHash(h Hash, f func() hash.Hash) {
}\n \thashes[h] = f\n }\n+\n+// PrivateKey represents a private key using an unspecified algorithm.\n+type PrivateKey interface{}\n```
### `src/pkg/crypto/tls/common.go`
```diff
--- a/src/pkg/crypto/tls/common.go
+++ b/src/pkg/crypto/tls/common.go
@@ -5,8 +5,8 @@
package tls
import (
+\t"crypto"
\t"crypto/rand"
-\t"crypto/rsa"
\t"crypto/x509"
\t"io"
\t"strings"
@@ -255,7 +255,7 @@ func (c *Config) BuildNameToCertificate() {
// A Certificate is a chain of one or more certificates, leaf first.
type Certificate struct {
Certificate [][]byte
-\tPrivateKey *rsa.PrivateKey
+\tPrivateKey crypto.PrivateKey // supported types: *rsa.PrivateKey
// OCSPStaple contains an optional OCSP response which will be served
// to clients that request it.
OCSPStaple []byte
src/pkg/crypto/tls/handshake_client.go
--- a/src/pkg/crypto/tls/handshake_client.go
+++ b/src/pkg/crypto/tls/handshake_client.go
@@ -234,7 +234,7 @@ func (c *Conn) clientHandshake() error {
digest := make([]byte, 0, 36)
digest = finishedHash.serverMD5.Sum(digest)
digest = finishedHash.serverSHA1.Sum(digest)
-\t\tsigned, err := rsa.SignPKCS1v15(c.config.rand(), c.config.Certificates[0].PrivateKey, crypto.MD5SHA1, digest)
+\t\tsigned, err := rsa.SignPKCS1v15(c.config.rand(), c.config.Certificates[0].PrivateKey.(*rsa.PrivateKey), crypto.MD5SHA1, digest)
if err != nil {
return c.sendAlert(alertInternalError)
}
src/pkg/crypto/tls/key_agreement.go
--- a/src/pkg/crypto/tls/key_agreement.go
+++ b/src/pkg/crypto/tls/key_agreement.go
@@ -44,7 +44,7 @@ func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, ckx *clientKe
ciphertext = ckx.ciphertext[2:]
}
-\terr = rsa.DecryptPKCS1v15SessionKey(config.rand(), config.Certificates[0].PrivateKey, ciphertext, preMasterSecret)
+\terr = rsa.DecryptPKCS1v15SessionKey(config.rand(), config.Certificates[0].PrivateKey.(*rsa.PrivateKey), ciphertext, preMasterSecret)
if err != nil {
return nil, err
}
@@ -147,7 +147,7 @@ Curve:
copy(serverECDHParams[4:], ecdhePublic)
md5sha1 := md5SHA1Hash(clientHello.random, hello.random, serverECDHParams)
-\tsig, err := rsa.SignPKCS1v15(config.rand(), config.Certificates[0].PrivateKey, crypto.MD5SHA1, md5sha1)
+\tsig, err := rsa.SignPKCS1v15(config.rand(), config.Certificates[0].PrivateKey.(*rsa.PrivateKey), crypto.MD5SHA1, md5sha1)
if err != nil {
return nil, errors.New("failed to sign ECDHE parameters: " + err.Error())
}
コアとなるコードの解説
src/pkg/crypto/crypto.go の変更
type PrivateKey interface{}が追加されました。これは、Go言語のcryptoパッケージ全体で利用可能な、秘密鍵を表すための汎用的なインターフェースです。このインターフェースはメソッドを持たないため、任意の型がこのインターフェースを満たすことができます。これにより、秘密鍵の具体的な型に依存しないAPI設計が可能になります。
src/pkg/crypto/tls/common.go の変更
import "crypto/rsa"が削除され、代わりにimport "crypto"が追加されました。これは、Certificate構造体のPrivateKeyフィールドの型が*rsa.PrivateKeyからcrypto.PrivateKeyインターフェースに変更されたためです。Certificate構造体のPrivateKeyフィールドの型が*rsa.PrivateKeyからcrypto.PrivateKeyに変更されました。これにより、Certificate構造体はRSA秘密鍵だけでなく、将来的に他のアルゴリズムの秘密鍵も保持できるようになります。// supported types: *rsa.PrivateKeyというコメントが追加され、現時点ではRSA秘密鍵のみがサポートされていることが明示されています。これは、APIは汎用化されたものの、内部実装はまだRSAに依存していることを示唆しています。
src/pkg/crypto/tls/handshake_client.go および src/pkg/crypto/tls/key_agreement.go の変更
- これらのファイルでは、
Certificate構造体のPrivateKeyフィールドがcrypto.PrivateKeyインターフェース型になったことに伴い、既存のRSA秘密鍵を扱う関数 (rsa.SignPKCS1v15やrsa.DecryptPKCS1v15SessionKey) に渡す際に、型アサーション.(*rsa.PrivateKey)が追加されました。 - 例えば、
c.config.Certificates[0].PrivateKeyはcrypto.PrivateKey型ですが、rsa.SignPKCS1v15関数は*rsa.PrivateKey型を期待するため、c.config.Certificates[0].PrivateKey.(*rsa.PrivateKey)と記述することで、インターフェース型の値を具象型である*rsa.PrivateKeyに変換しています。 - この変更は、APIの柔軟性を高めつつも、既存のRSAに特化した暗号処理ロジックを維持するための過渡的な措置です。将来的に他の鍵アルゴリズムがサポートされる際には、これらの型アサーションを含むコードも、より汎用的なインターフェースベースの処理に置き換えられる可能性があります。
関連リンク
- Go Issue #2319: https://github.com/golang/go/issues/2319
参考にした情報源リンク
- Go
crypto/tlsパッケージのCertificate構造体に関するドキュメント: https://pkg.go.dev/crypto/tls#Certificate (現在のドキュメントでは、PrivateKeyフィールドがcrypto.Signerインターフェースを実装する任意の型を受け入れることが明記されています。これは、このコミット以降のさらなる進化を示しています。)