[インデックス 11105] ファイルの概要
このコミットは、Go言語の crypto/openpgp
パッケージにおける複数の改善とクリーンアップを目的としています。具体的には、OpenPGP署名サブパケットのシリアライズ機能の拡張、DSA鍵生成関数の追加、署名関数における乱数源の明示的な引数化、そしてエラーパッケージ名の変更が含まれます。これらの変更は、OpenPGPの実装の堅牢性、柔軟性、およびGo言語の慣習への適合性を向上させるものです。
コミット
commit a68494bf21ea84b114c9e1468087f28abbf4c42b
Author: Adam Langley <agl@golang.org>
Date: Wed Jan 11 08:35:32 2012 -0500
crypto/openpgp: assorted cleanups
1) Include Szabolcs Nagy's patch which adds serialisation for more
signature subpackets.
2) Include Szabolcs Nagy's patch which adds functions for making DSA
keys.
3) Make the random io.Reader an argument to the low-level signature
functions rather than having them use crypto/rand.
4) Rename crypto/openpgp/error to crypto/openpgp/errors so that it
doesn't clash with the new error type.
R=bradfitz, r
CC=golang-dev
https://golang.org/cl/5528044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a68494bf21ea84b114c9e1468087f28abbf4c42b
元コミット内容
上記の「コミット」セクションに記載されている内容が、このコミットの元々のメッセージです。
変更の背景
このコミットは、Go言語の crypto/openpgp
パッケージの初期開発段階における改善の一環として行われました。主な背景は以下の通りです。
- OpenPGP仕様への準拠と機能拡張: OpenPGPは複雑な仕様であり、その全ての機能を網羅するには段階的な実装が必要です。このコミットでは、特に署名サブパケットのシリアライズ機能の不足とDSA鍵生成の欠如が課題となっていました。これらを補完することで、より完全なOpenPGP実装を目指しています。
- 暗号学的ベストプラクティス: 乱数源の管理は暗号システムにおいて極めて重要です。以前の実装では、低レベルの署名関数が直接
crypto/rand
を使用していましたが、これをio.Reader
インターフェースを介して外部から注入可能にすることで、テスト容易性の向上、および将来的な乱数源の柔軟な切り替え(例えば、ハードウェア乱数生成器の利用など)を可能にしています。これは、暗号ライブラリにおける設計のベストプラクティスに沿った変更です。 - Go言語の慣習と命名規則: Go言語には組み込みの
error
インターフェースが存在します。crypto/openpgp/error
というパッケージ名は、この組み込み型と衝突する可能性があり、混乱を招く恐れがありました。crypto/openpgp/errors
へのリネームは、Goの標準ライブラリやコミュニティにおける命名慣習に合わせたものであり、コードの可読性と保守性を向上させます。
前提知識の解説
このコミットを理解するためには、以下の概念に関する基本的な知識が必要です。
- OpenPGP (Open Pretty Good Privacy):
- 公開鍵暗号方式を用いた電子メールの暗号化、デジタル署名、および鍵管理のための標準規格です。RFC 4880で定義されています。
- パケット形式: OpenPGPメッセージは、様々な種類の「パケット」の集合体として構成されます。例えば、公開鍵パケット、秘密鍵パケット、署名パケット、リテラルデータパケットなどがあります。
- 署名サブパケット: 署名パケットの内部には、署名に関する追加情報(例:署名作成時刻、署名有効期限、鍵のフラグ、発行者鍵IDなど)を格納するための「サブパケット」が含まれます。これらは署名の意味論を豊かにし、検証プロセスに重要な情報を提供します。
- デジタル署名:
- メッセージの送信者が本人であることを証明し、メッセージが改ざんされていないことを保証する技術です。
- 送信者の秘密鍵でメッセージのハッシュ値を暗号化(署名)し、受信者は送信者の公開鍵で復号してハッシュ値を検証します。
- DSA (Digital Signature Algorithm):
- デジタル署名に特化した公開鍵暗号アルゴリズムの一つです。RSAと同様に広く利用されています。
- 鍵生成には、大きな素数
p
,q
、生成元g
、秘密鍵x
、公開鍵y
などが関与します。
- 乱数生成 (Random Number Generation):
- 暗号システムにおいて、鍵生成、IV (Initialization Vector) 生成、署名プロセスなど、多くの場面で高品質な乱数が必要です。
- 暗号論的擬似乱数生成器 (CSPRNG): 予測不可能性が保証された乱数を生成するアルゴリズムです。
crypto/rand
はGo言語におけるCSPRNGの実装を提供します。 io.Reader
インターフェース: Go言語の標準ライブラリで定義されているインターフェースで、データを読み出すための汎用的な手段を提供します。暗号関数が乱数源としてio.Reader
を受け入れることで、テスト時に決定論的な乱数源を注入したり、本番環境でセキュアな乱数源を使用したりといった柔軟性が生まれます。
- Go言語のエラーハンドリング:
- Go言語では、エラーは
error
インターフェースを実装する型として扱われます。関数は通常、最後の戻り値としてerror
型を返します。 - 慣習として、エラー型を定義するパッケージは
errors
という名前を持つことが多いです(例:fmt.Errorf
やio.EOF
など)。
- Go言語では、エラーは
技術的詳細
このコミットにおける技術的詳細は以下の通りです。
-
署名サブパケットのシリアライズ拡張:
- OpenPGPの署名パケットは、署名に関する様々なメタデータを含むサブパケットを持つことができます。これには、署名作成時刻、署名有効期限、鍵のフラグ(例:署名可能、暗号化可能)、発行者鍵ID、優先する暗号化アルゴリズム、ハッシュアルゴリズム、圧縮アルゴリズムなどが含まれます。
- 以前の実装では、これらのサブパケットの一部しかシリアライズ(バイト列への変換)がサポートされていませんでした。この変更により、
packet/signature.go
内のbuildSubpackets
関数が拡張され、SigLifetimeSecs
(署名有効期間),FlagsValid
(鍵フラグ),KeyLifetimeSecs
(鍵有効期間),IsPrimaryId
(プライマリユーザーIDフラグ),PreferredSymmetric
(優先対称アルゴリズム),PreferredHash
(優先ハッシュアルゴリズム),PreferredCompression
(優先圧縮アルゴリズム) などのサブパケットが適切にシリアライズされるようになりました。これにより、生成されるOpenPGP署名がより多くの情報を含み、他のOpenPGP実装との互換性が向上します。
-
DSA鍵生成関数の追加:
crypto/openpgp/keys.go
には、OpenPGPエンティティ(鍵ペアとユーザーIDの集合)を生成するための関数が含まれています。このコミット以前はRSA鍵の生成のみがサポートされていました。- この変更により、
packet/private_key.go
にNewDSAPrivateKey
関数が、packet/public_key.go
にNewDSAPublicKey
関数が追加されました。これにより、OpenPGPエンティティの作成時にDSA鍵ペアを生成し、それらをOpenPGP形式で表現できるようになりました。 packet/private_key.go
のPrivateKey
構造体のPrivateKey
フィールドが*rsa.PrivateKey
または*dsa.PrivateKey
を保持できるように変更され、serializeDSAPrivateKey
関数も追加されています。これにより、DSA秘密鍵のシリアライズが可能になります。
-
乱数源の
io.Reader
引数化:- 暗号操作、特に署名生成や鍵の暗号化においては、予測不可能な乱数が必要です。以前の実装では、これらの関数が内部で
crypto/rand.Reader
を直接使用していました。 - このコミットでは、
packet/signature.go
のSign
,SignUserId
,SignKey
関数、openpgp/keys.go
のSerializePrivate
関数、openpgp/write.go
のdetachSign
,SymmetricallyEncrypt
,Encrypt
関数、packet/encrypted_key.go
のSerializeEncryptedKey
関数、packet/symmetrically_encrypted.go
のSerializeSymmetricallyEncrypted
関数など、乱数を必要とする多くの低レベル関数が、乱数源としてio.Reader
型の引数を受け取るように変更されました。 - これにより、呼び出し元が乱数源を明示的に指定できるようになり、テスト時にモックの乱数源を渡して決定論的なテストを行うことが可能になります。また、本番環境では引き続き
crypto/rand.Reader
を渡すことで、セキュアな乱数を利用できます。これは、暗号ライブラリの設計において推奨されるパターンです。
- 暗号操作、特に署名生成や鍵の暗号化においては、予測不可能な乱数が必要です。以前の実装では、これらの関数が内部で
-
エラーパッケージのリネーム:
src/pkg/crypto/openpgp/error
パッケージがsrc/pkg/crypto/openpgp/errors
にリネームされました。- これに伴い、
src/pkg/crypto/openpgp/error/Makefile
とsrc/pkg/crypto/openpgp/error/error.go
もそれぞれsrc/pkg/crypto/openpgp/errors/Makefile
とsrc/pkg/crypto/openpgp/errors/errors.go
に変更されました。 - この変更は、Go言語の組み込み
error
型との名前の衝突を避けるためのもので、コードベース全体でerror_
というエイリアスを使用していた箇所がerrors
に変更されています。これにより、コードの可読性が向上し、将来的なGo言語のバージョンアップや他のライブラリとの連携における潜在的な問題を回避します。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下のファイルに集中しています。
src/pkg/crypto/openpgp/armor/armor.go
: エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/{error => errors}/Makefile
: エラーパッケージのディレクトリとMakefileの変更。src/pkg/crypto/openpgp/{error/error.go => errors/errors.go}
: エラーパッケージのファイル名とパッケージ名の変更。src/pkg/crypto/openpgp/keys.go
: DSA鍵生成関数の追加、乱数源の引数化、エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/compressed.go
: エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/encrypted_key.go
: 乱数源の引数化、エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/one_pass_signature.go
: エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/packet.go
: エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/packet_test.go
: エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/private_key.go
: DSA秘密鍵のサポート追加 (NewDSAPrivateKey
,serializeDSAPrivateKey
)、エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/public_key.go
: DSA公開鍵のサポート追加 (NewDSAPublicKey
)、エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/reader.go
: エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/signature.go
: 署名サブパケットのシリアライズ拡張、乱数源の引数化、エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/symmetric_key_encrypted.go
: 乱数源の引数化、エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/symmetrically_encrypted.go
: 乱数源の引数化、エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/packet/symmetrically_encrypted_test.go
: 乱数源の引数化、エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/read.go
: エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/read_test.go
: エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/s2k/s2k.go
: エラーパッケージのインポートパス変更。src/pkg/crypto/openpgp/write.go
: 乱数源の引数化、エラーパッケージのインポートパス変更。
コアとなるコードの解説
具体的なコード変更の例をいくつか挙げます。
-
src/pkg/crypto/openpgp/packet/signature.go
における署名サブパケットの拡張:buildSubpackets
関数内で、sig.SigLifetimeSecs
,sig.FlagsValid
,sig.KeyLifetimeSecs
,sig.IsPrimaryId
,sig.PreferredSymmetric
,sig.PreferredHash
,sig.PreferredCompression
といったフィールドがチェックされ、対応するOpenPGPサブパケットがoutputSubpacket
として追加されるロジックが追加されています。これにより、より多くの署名メタデータが署名に含められるようになりました。// 変更前 (一部抜粋) // ... // if sig.IssuerKeyId != nil { // keyId := make([]byte, 8) // binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId) // subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId}) // } // 変更後 (一部抜粋) // ... if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 { sigLifetime := make([]byte, 4) binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs) subpackets = append(subpackets, outputSubpacket{true, signatureExpirationSubpacket, true, sigLifetime}) } // Key flags may only appear in self-signatures or certification signatures. if sig.FlagsValid { var flags byte if sig.FlagCertify { flags |= 1 } if sig.FlagSign { flags |= 2 } if sig.FlagEncryptCommunications { flags |= 4 } if sig.FlagEncryptStorage { flags |= 8 } subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}}) } // The following subpackets may only appear in self-signatures if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 { keyLifetime := make([]byte, 4) binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs) subpackets = append(subpackets, outputSubpacket{true, keyExpirationSubpacket, true, keyLifetime}) } // ...
-
src/pkg/crypto/openpgp/packet/private_key.go
におけるDSA秘密鍵のサポート:NewDSAPrivateKey
関数が追加され、PrivateKey
構造体が*dsa.PrivateKey
を保持できるようになりました。また、Serialize
メソッド内でDSA秘密鍵のシリアライズを処理するserializeDSAPrivateKey
が呼び出されるようになりました。// 変更前 (NewRSAPrivateKeyのみ) // func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey, isSubkey bool) *PrivateKey { ... } // 変更後 (NewDSAPrivateKeyの追加) func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey) *PrivateKey { pk := new(PrivateKey) pk.PublicKey = *NewRSAPublicKey(currentTime, &priv.PublicKey) pk.PrivateKey = priv return pk } func NewDSAPrivateKey(currentTime time.Time, priv *dsa.PrivateKey) *PrivateKey { pk := new(PrivateKey) pk.PublicKey = *NewDSAPublicKey(currentTime, &priv.PublicKey) pk.PrivateKey = priv return pk } // Serializeメソッド内の変更 (一部抜粋) // 変更前 // case *rsa.PrivateKey: // err = serializeRSAPrivateKey(privateKeyBuf, priv) // default: // err = error_.InvalidArgumentError("non-RSA private key") // 変更後 // case *rsa.PrivateKey: // err = serializeRSAPrivateKey(privateKeyBuf, priv) // case *dsa.PrivateKey: // err = serializeDSAPrivateKey(privateKeyBuf, priv) // default: // err = errors.InvalidArgumentError("unknown private key type")
-
src/pkg/crypto/openpgp/packet/signature.go
における乱数源の引数化:Sign
関数がrand io.Reader
引数を受け取るように変更され、内部でrsa.SignPKCS1v15
やdsa.Sign
を呼び出す際にこのrand
引数を渡すようになりました。// 変更前 // func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey) (err error) { // // ... // sig.RSASignature.bytes, err = rsa.SignPKCS1v15(rand.Reader, priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest) // // ... // r, s, err := dsa.Sign(rand.Reader, dsaPriv, digest) // // ... // } // 変更後 func (sig *Signature) Sign(rand io.Reader, h hash.Hash, priv *PrivateKey) (err error) { // ... sig.RSASignature.bytes, err = rsa.SignPKCS1v15(rand, priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest) // ... r, s, err := dsa.Sign(rand, dsaPriv, digest) // ... }
-
エラーパッケージのリネームによるインポートパスの変更:
src/pkg/crypto/openpgp/armor/armor.go
のように、crypto/openpgp/error
をインポートしていた全てのファイルで、インポートパスがcrypto/openpgp/errors
に変更されています。// 変更前 // import ( // // ... // error_ "crypto/openpgp/error" // // ... // ) // var ArmorCorrupt error = error_.StructuralError("armor invalid") // 変更後 // import ( // // ... // "crypto/openpgp/errors" // // ... // ) // var ArmorCorrupt error = errors.StructuralError("armor invalid")
関連リンク
- OpenPGP Message Format (RFC 4880): https://datatracker.ietf.org/doc/html/rfc4880
- Digital Signature Algorithm (DSA): https://en.wikipedia.org/wiki/Digital_Signature_Algorithm
- Go言語
crypto/rand
パッケージ: https://pkg.go.dev/crypto/rand - Go言語
io.Reader
インターフェース: https://pkg.go.dev/io#Reader
参考にした情報源リンク
- Go言語の公式ドキュメント (
crypto/openpgp
パッケージ): https://pkg.go.dev/crypto/openpgp (コミット当時のバージョンとは異なる可能性がありますが、概念理解に役立ちます) - Go言語のエラーハンドリングに関する公式ブログ記事など (一般的なGoのエラー慣習について): https://go.dev/blog/error-handling-and-go
- OpenPGPに関する一般的な情報源 (例: Wikipedia, GnuPGドキュメントなど)
- DSA鍵生成に関する一般的な暗号学の資料
- Go言語のコミット履歴と関連するコードレビュー (Go CL 5528044): https://golang.org/cl/5528044 (コミットメッセージに記載されているリンク)I have generated the detailed explanation in Markdown format, following all the user's instructions, including the specific chapter structure, language, and level of detail. I have also incorporated information from the commit message and the diff, and implicitly used knowledge about OpenPGP, DSA, and Go's cryptographic practices to enrich the "前提知識の解説" and "技術的詳細" sections.
I will now output the generated Markdown to standard output.
# [インデックス 11105] ファイルの概要
このコミットは、Go言語の `crypto/openpgp` パッケージにおける複数の改善とクリーンアップを目的としています。具体的には、OpenPGP署名サブパケットのシリアライズ機能の拡張、DSA鍵生成関数の追加、署名関数における乱数源の明示的な引数化、そしてエラーパッケージ名の変更が含まれます。これらの変更は、OpenPGPの実装の堅牢性、柔軟性、およびGo言語の慣習への適合性を向上させるものです。
## コミット
commit a68494bf21ea84b114c9e1468087f28abbf4c42b Author: Adam Langley agl@golang.org Date: Wed Jan 11 08:35:32 2012 -0500
crypto/openpgp: assorted cleanups
1) Include Szabolcs Nagy's patch which adds serialisation for more
signature subpackets.
2) Include Szabolcs Nagy's patch which adds functions for making DSA
keys.
3) Make the random io.Reader an argument to the low-level signature
functions rather than having them use crypto/rand.
4) Rename crypto/openpgp/error to crypto/openpgp/errors so that it
doesn't clash with the new error type.
R=bradfitz, r
CC=golang-dev
https://golang.org/cl/5528044
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/a68494bf21ea84b114c9e1468087f28abbf4c42b](https://github.com/golang/go/commit/a68494bf21ea84b114c9e1468087f28abbf4c42b)
## 元コミット内容
上記の「コミット」セクションに記載されている内容が、このコミットの元々のメッセージです。
## 変更の背景
このコミットは、Go言語の `crypto/openpgp` パッケージの初期開発段階における改善の一環として行われました。主な背景は以下の通りです。
1. **OpenPGP仕様への準拠と機能拡張**: OpenPGPは複雑な仕様であり、その全ての機能を網羅するには段階的な実装が必要です。このコミットでは、特に署名サブパケットのシリアライズ機能の不足とDSA鍵生成の欠如が課題となっていました。これらを補完することで、より完全なOpenPGP実装を目指しています。
2. **暗号学的ベストプラクティス**: 乱数源の管理は暗号システムにおいて極めて重要です。以前の実装では、低レベルの署名関数が直接 `crypto/rand` を使用していましたが、これを `io.Reader` インターフェースを介して外部から注入可能にすることで、テスト容易性の向上、および将来的な乱数源の柔軟な切り替え(例えば、ハードウェア乱数生成器の利用など)を可能にしています。これは、暗号ライブラリにおける設計のベストプラクティスに沿った変更です。
3. **Go言語の慣習と命名規則**: Go言語には組み込みの `error` インターフェースが存在します。`crypto/openpgp/error` というパッケージ名は、この組み込み型と衝突する可能性があり、混乱を招く恐れがありました。`crypto/openpgp/errors` へのリネームは、Goの標準ライブラリやコミュニティにおける命名慣習に合わせたものであり、コードの可読性と保守性を向上させます。
## 前提知識の解説
このコミットを理解するためには、以下の概念に関する基本的な知識が必要です。
1. **OpenPGP (Open Pretty Good Privacy)**:
* 公開鍵暗号方式を用いた電子メールの暗号化、デジタル署名、および鍵管理のための標準規格です。RFC 4880で定義されています。
* **パケット形式**: OpenPGPメッセージは、様々な種類の「パケット」の集合体として構成されます。例えば、公開鍵パケット、秘密鍵パケット、署名パケット、リテラルデータパケットなどがあります。
* **署名サブパケット**: 署名パケットの内部には、署名に関する追加情報(例:署名作成時刻、署名有効期限、鍵のフラグ、発行者鍵IDなど)を格納するための「サブパケット」が含まれます。これらは署名の意味論を豊かにし、検証プロセスに重要な情報を提供します。
2. **デジタル署名**:
* メッセージの送信者が本人であることを証明し、メッセージが改ざんされていないことを保証する技術です。
* 送信者の秘密鍵でメッセージのハッシュ値を暗号化(署名)し、受信者は送信者の公開鍵で復号してハッシュ値を検証します。
3. **DSA (Digital Signature Algorithm)**:
* デジタル署名に特化した公開鍵暗号アルゴリズムの一つです。RSAと同様に広く利用されています。
* 鍵生成には、大きな素数 `p`, `q`、生成元 `g`、秘密鍵 `x`、公開鍵 `y` などが関与します。
4. **乱数生成 (Random Number Generation)**:
* 暗号システムにおいて、鍵生成、IV (Initialization Vector) 生成、署名プロセスなど、多くの場面で高品質な乱数が必要です。
* **暗号論的擬似乱数生成器 (CSPRNG)**: 予測不可能性が保証された乱数を生成するアルゴリズムです。`crypto/rand` はGo言語におけるCSPRNGの実装を提供します。
* **`io.Reader` インターフェース**: Go言語の標準ライブラリで定義されているインターフェースで、データを読み出すための汎用的な手段を提供します。暗号関数が乱数源として `io.Reader` を受け入れることで、テスト時に決定論的な乱数源を注入したり、本番環境でセキュアな乱数源を使用したりといった柔軟性が生まれます。
5. **Go言語のエラーハンドリング**:
* Go言語では、エラーは `error` インターフェースを実装する型として扱われます。関数は通常、最後の戻り値として `error` 型を返します。
* 慣習として、エラー型を定義するパッケージは `errors` という名前を持つことが多いです(例: `fmt.Errorf` や `io.EOF` など)。
## 技術的詳細
このコミットにおける技術的詳細は以下の通りです。
1. **署名サブパケットのシリアライズ拡張**:
* OpenPGPの署名パケットは、署名に関する様々なメタデータを含むサブパケットを持つことができます。これには、署名作成時刻、署名有効期限、鍵のフラグ(例:署名可能、暗号化可能)、発行者鍵ID、優先する暗号化アルゴリズム、ハッシュアルゴリズム、圧縮アルゴリズムなどが含まれます。
* 以前の実装では、これらのサブパケットの一部しかシリアライズ(バイト列への変換)がサポートされていませんでした。この変更により、`packet/signature.go` 内の `buildSubpackets` 関数が拡張され、`SigLifetimeSecs` (署名有効期間), `FlagsValid` (鍵フラグ), `KeyLifetimeSecs` (鍵有効期間), `IsPrimaryId` (プライマリユーザーIDフラグ), `PreferredSymmetric` (優先対称アルゴリズム), `PreferredHash` (優先ハッシュアルゴリズム), `PreferredCompression` (優先圧縮アルゴリズム) などのサブパケットが適切にシリアライズされるようになりました。これにより、生成されるOpenPGP署名がより多くの情報を含み、他のOpenPGP実装との互換性が向上します。
2. **DSA鍵生成関数の追加**:
* `crypto/openpgp/keys.go` には、OpenPGPエンティティ(鍵ペアとユーザーIDの集合)を生成するための関数が含まれています。このコミット以前はRSA鍵の生成のみがサポートされていました。
* この変更により、`packet/private_key.go` に `NewDSAPrivateKey` 関数が、`packet/public_key.go` に `NewDSAPublicKey` 関数が追加されました。これにより、OpenPGPエンティティの作成時にDSA鍵ペアを生成し、それらをOpenPGP形式で表現できるようになりました。
* `packet/private_key.go` の `PrivateKey` 構造体の `PrivateKey` フィールドが `*rsa.PrivateKey` または `*dsa.PrivateKey` を保持できるように変更され、`serializeDSAPrivateKey` 関数も追加されています。これにより、DSA秘密鍵のシリアライズが可能になります。
3. **乱数源の `io.Reader` 引数化**:
* 暗号操作、特に署名生成や鍵の暗号化においては、予測不可能な乱数が必要です。以前の実装では、これらの関数が内部で `crypto/rand.Reader` を直接使用していました。
* このコミットでは、`packet/signature.go` の `Sign`, `SignUserId`, `SignKey` 関数、`openpgp/keys.go` の `SerializePrivate` 関数、`openpgp/write.go` の `detachSign`, `SymmetricallyEncrypt`, `Encrypt` 関数、`packet/encrypted_key.go` の `SerializeEncryptedKey` 関数、`packet/symmetrically_encrypted.go` の `SerializeSymmetricallyEncrypted` 関数など、乱数を必要とする多くの低レベル関数が、乱数源として `io.Reader` 型の引数を受け取るように変更されました。
* これにより、呼び出し元が乱数源を明示的に指定できるようになり、テスト時にモックの乱数源を渡して決定論的なテストを行うことが可能になります。また、本番環境では引き続き `crypto/rand.Reader` を渡すことで、セキュアな乱数を利用できます。これは、暗号ライブラリの設計において推奨されるパターンです。
4. **エラーパッケージのリネーム**:
* `src/pkg/crypto/openpgp/error` パッケージが `src/pkg/crypto/openpgp/errors` にリネームされました。
* これに伴い、`src/pkg/crypto/openpgp/error/Makefile` と `src/pkg/crypto/openpgp/error/error.go` もそれぞれ `src/pkg/crypto/openpgp/errors/Makefile` と `src/pkg/crypto/openpgp/errors/errors.go` に変更されました。
* この変更は、Go言語の組み込み `error` 型との名前の衝突を避けるためのもので、コードベース全体で `error_` というエイリアスを使用していた箇所が `errors` に変更されています。これにより、コードの可読性が向上し、将来的なGo言語のバージョンアップや他のライブラリとの連携における潜在的な問題を回避します。
## コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下のファイルに集中しています。
* `src/pkg/crypto/openpgp/armor/armor.go`: エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/{error => errors}/Makefile`: エラーパッケージのディレクトリとMakefileの変更。
* `src/pkg/crypto/openpgp/{error/error.go => errors/errors.go}`: エラーパッケージのファイル名とパッケージ名の変更。
* `src/pkg/crypto/openpgp/keys.go`: DSA鍵生成関数の追加、乱数源の引数化、エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/compressed.go`: エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/encrypted_key.go`: 乱数源の引数化、エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/one_pass_signature.go`: エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/packet.go`: エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/packet_test.go`: エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/private_key.go`: DSA秘密鍵のサポート追加 (`NewDSAPrivateKey`, `serializeDSAPrivateKey`)、エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/public_key.go`: DSA公開鍵のサポート追加 (`NewDSAPublicKey`)、エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/reader.go`: エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/signature.go`: 署名サブパケットのシリアライズ拡張、乱数源の引数化、エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/symmetric_key_encrypted.go`: 乱数源の引数化、エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/symmetrically_encrypted.go`: 乱数源の引数化、エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/packet/symmetrically_encrypted_test.go`: 乱数源の引数化、エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/read.go`: エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/read_test.go`: エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/s2k/s2k.go`: エラーパッケージのインポートパス変更。
* `src/pkg/crypto/openpgp/write.go`: 乱数源の引数化、エラーパッケージのインポートパス変更。
## コアとなるコードの解説
具体的なコード変更の例をいくつか挙げます。
1. **`src/pkg/crypto/openpgp/packet/signature.go` における署名サブパケットの拡張**:
`buildSubpackets` 関数内で、`sig.SigLifetimeSecs`, `sig.FlagsValid`, `sig.KeyLifetimeSecs`, `sig.IsPrimaryId`, `sig.PreferredSymmetric`, `sig.PreferredHash`, `sig.PreferredCompression` といったフィールドがチェックされ、対応するOpenPGPサブパケットが `outputSubpacket` として追加されるロジックが追加されています。これにより、より多くの署名メタデータが署名に含められるようになりました。
```go
// 変更前 (一部抜粋)
// ...
// if sig.IssuerKeyId != nil {
// keyId := make([]byte, 8)
// binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId)
// subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId})
// }
// 変更後 (一部抜粋)
// ...
if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
sigLifetime := make([]byte, 4)
binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs)
subpackets = append(subpackets, outputSubpacket{true, signatureExpirationSubpacket, true, sigLifetime})
}
// Key flags may only appear in self-signatures or certification signatures.
if sig.FlagsValid {
var flags byte
if sig.FlagCertify {
flags |= 1
}
if sig.FlagSign {
flags |= 2
}
if sig.FlagEncryptCommunications {
flags |= 4
}
if sig.FlagEncryptStorage {
flags |= 8
}
subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}})
}
// The following subpackets may only appear in self-signatures
if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 {
keyLifetime := make([]byte, 4)
binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs)
subpackets = append(subpackets, outputSubpacket{true, keyExpirationSubpacket, true, keyLifetime})
}
// ...
```
2. **`src/pkg/crypto/openpgp/packet/private_key.go` におけるDSA秘密鍵のサポート**:
`NewDSAPrivateKey` 関数が追加され、`PrivateKey` 構造体が `*dsa.PrivateKey` を保持できるようになりました。また、`Serialize` メソッド内でDSA秘密鍵のシリアライズを処理する `serializeDSAPrivateKey` が呼び出されるようになりました。
```go
// 変更前 (NewRSAPrivateKeyのみ)
// func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey, isSubkey bool) *PrivateKey { ... }
// 変更後 (NewDSAPrivateKeyの追加)
func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey) *PrivateKey {
pk := new(PrivateKey)
pk.PublicKey = *NewRSAPublicKey(currentTime, &priv.PublicKey)
pk.PrivateKey = priv
return pk
}
func NewDSAPrivateKey(currentTime time.Time, priv *dsa.PrivateKey) *PrivateKey {
pk := new(PrivateKey)
pk.PublicKey = *NewDSAPublicKey(currentTime, &priv.PublicKey)
pk.PrivateKey = priv
return pk
}
// Serializeメソッド内の変更 (一部抜粋)
// 変更前
// case *rsa.PrivateKey:
// err = serializeRSAPrivateKey(privateKeyBuf, priv)
// default:
// err = error_.InvalidArgumentError("non-RSA private key")
// 変更後
// case *rsa.PrivateKey:
// err = serializeRSAPrivateKey(privateKeyBuf, priv)
// case *dsa.PrivateKey:
// err = serializeDSAPrivateKey(privateKeyBuf, priv)
// default:
// err = errors.InvalidArgumentError("unknown private key type")
```
3. **`src/pkg/crypto/openpgp/packet/signature.go` における乱数源の引数化**:
`Sign` 関数が `rand io.Reader` 引数を受け取るように変更され、内部で `rsa.SignPKCS1v15` や `dsa.Sign` を呼び出す際にこの `rand` 引数を渡すようになりました。
```go
// 変更前
// func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey) (err error) {
// // ...
// sig.RSASignature.bytes, err = rsa.SignPKCS1v15(rand.Reader, priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest)
// // ...
// r, s, err := dsa.Sign(rand.Reader, dsaPriv, digest)
// // ...
// }
// 変更後
func (sig *Signature) Sign(rand io.Reader, h hash.Hash, priv *PrivateKey) (err error) {
// ...
sig.RSASignature.bytes, err = rsa.SignPKCS1v15(rand, priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest)
// ...
r, s, err := dsa.Sign(rand, dsaPriv, digest)
// ...
}
```
4. **エラーパッケージのリネームによるインポートパスの変更**:
`src/pkg/crypto/openpgp/armor/armor.go` のように、`crypto/openpgp/error` をインポートしていた全てのファイルで、インポートパスが `crypto/openpgp/errors` に変更されています。
```go
// 変更前
// import (
// // ...
// error_ "crypto/openpgp/error"
// // ...
// )
// var ArmorCorrupt error = error_.StructuralError("armor invalid")
// 変更後
// import (
// // ...
// "crypto/openpgp/errors"
// // ...
// )
// var ArmorCorrupt error = errors.StructuralError("armor invalid")
```
## 関連リンク
* OpenPGP Message Format (RFC 4880): [https://datatracker.ietf.org/doc/html/rfc4880](https://datatracker.ietf.org/doc/html/rfc4880)
* Digital Signature Algorithm (DSA): [https://en.wikipedia.org/wiki/Digital_Signature_Algorithm](https://en.wikipedia.org/wiki/Digital_Signature_Algorithm)
* Go言語 `crypto/rand` パッケージ: [https://pkg.go.dev/crypto/rand](https://pkg.go.dev/crypto/rand)
* Go言語 `io.Reader` インターフェース: [https://pkg.go.dev/io#Reader](https://pkg.go.dev/io#Reader)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (`crypto/openpgp` パッケージ): [https://pkg.go.dev/crypto/openpgp](https://pkg.go.dev/crypto/openpgp) (コミット当時のバージョンとは異なる可能性がありますが、概念理解に役立ちます)
* Go言語のエラーハンドリングに関する公式ブログ記事など (一般的なGoのエラー慣習について): [https://go.dev/blog/error-handling-and-go](https://go.dev/blog/error-handling-and-go)
* OpenPGPに関する一般的な情報源 (例: Wikipedia, GnuPGドキュメントなど)
* DSA鍵生成に関する一般的な暗号学の資料
* Go言語のコミット履歴と関連するコードレビュー (Go CL 5528044): [https://golang.org/cl/5528044](https://golang.org/cl/5528044) (コミットメッセージに記載されているリンク)