メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#74424active

x/crypto/ssh: refactor signers API

新規提案

要約

AIによる要約であり、誤りを含む場合があります。

概要

このproposalは、x/crypto/sshパッケージの標準ライブラリ移行に向けて、署名者(Signer)APIを大幅に再設計するものです。現在3つに分かれているSigner関連インターフェース(Signer、AlgorithmSigner、MultiAlgorithmSigner)を統合し、アルゴリズム選択とコンテキスト伝播に対応したSignerV2インターフェースに一本化します。

ステータス変更

(未設定)active
2026年1月28日のProposal Review Meetingにおいて、本proposalが「active」ステータスに昇格しました。議事録には「added to minutes」とのみ記載されており、積極的な議論を継続するための活性化措置と見られます。この決定は、x/crypto/sshの標準ライブラリ移行(#68723)という大きな文脈の中で、重要なAPI改善として位置づけられています。

技術的背景

現状の問題点

x/crypto/sshパッケージは後方互換性を保つために、RSA鍵の複数の署名アルゴリズム対応を段階的に追加してきた結果、以下の3つのインターフェースが乱立しています。

  • Signer: 基本的な署名インターフェース
  • AlgorithmSigner: アルゴリズムを指定して署名できるインターフェース
  • MultiAlgorithmSigner: 対応アルゴリズムを列挙できるインターフェース
    また、Signer作成のための関数も4つに分かれています。
NewSignerFromKey(key interface{}) (Signer, error)
NewSignerFromSigner(signer crypto.Signer) (Signer, error)
NewCertSigner(cert *Certificate, signer Signer) (Signer, error)
NewSignerWithAlgorithms(signer AlgorithmSigner, algorithms []string) (MultiAlgorithmSigner, error)

NewSignerFromKeyinterface{}を受け取る理由は、DSA鍵(*dsa.PrivateKey)対応のためでしたが、DSAはOpenSSH 10.0(2025年4月リリース)で完全に削除されており、もはや不要な複雑さです。
さらに、秘密鍵パース関数も4つあり、冗長性が見られます。

ParsePrivateKey(pemBytes []byte) (Signer, error)
ParsePrivateKeyWithPassphrase(pemBytes, passphrase []byte) (Signer, error)
ParseRawPrivateKey(pemBytes []byte) (interface{}, error)
ParseRawPrivateKeyWithPassphrase(pemBytes, passphrase []byte) (interface{}, error)

提案された解決策

新しいSignerV2インターフェースは、これらの複雑性を解消し、以下の機能を提供します。

type SignerV2 interface {
    PublicKey() PublicKey
    // 後方互換性のための従来型メソッド
    Sign(rand io.Reader, data []byte) (*Signature, error)
    // コンテキストとアルゴリズム選択に対応した新メソッド
    SignContext(ctx context.Context, rand io.Reader, data []byte, algorithm string) (*Signature, error)
    // 対応アルゴリズムをリスト表示
    Algorithms() []string
    // 基礎となるcrypto.Signerへのアクセス(HSM等で有用)
    Signer() (crypto.Signer, error)
}

Signer作成関数も簡潔になります。

// crypto.SignerからSignerV2を作成(RSA鍵の場合はSHA-1を除外)
func NewSignerV2(signer crypto.Signer) (SignerV2, error)
// 対応アルゴリズムを制限したSignerV2を作成
func NewSignerV2WithAlgorithms(signer SignerV2, algorithms []string) (SignerV2, error)
// 証明書付きSignerV2を作成
func NewCertificateSignerV2(cert *Certificate, signer SignerV2) (SignerV2, error)

秘密鍵のパース・マーシャルは、オプション構造体を用いた統一的な設計になります。

type ParsePrivateKeyV2Options struct {
    Passphrase string
    SignatureAlgorithms []string  // デフォルトで安全なアルゴリズムのみ許可
}
func ParsePrivateKeyV2(pemBytes []byte, options *ParsePrivateKeyV2Options) (SignerV2, error)
type MarshalPrivateKeyV2Options struct {
    Comment string
    Passphrase string  // 設定時はOpenSSH形式で暗号化
    SaltRounds int     // 鍵導出の反復回数(デフォルト24)
}
func MarshalPrivateKeyV2(key SignerV2, options *MarshalPrivateKeyV2Options) (*pem.Block, error)

重要な変更点として、RFC 1423準拠の旧式PEM暗号化(DES-CBC、AES-CBC使用)は非対応となり、OpenSSH形式の暗号化のみをサポートします。

これによって何ができるようになるか

  1. 統一されたシンプルなAPI: 3つのインターフェースと4つのコンストラクタを、1つのインターフェースと3つのコンストラクタに集約。コードの見通しが大幅に改善されます。
  2. コンテキスト伝播のサポート: HSMやクラウドKMS(AWS KMS、Google Cloud KMS等)を使用する際に、タイムアウト、キャンセル、トレース情報をSigner実装に伝播できるようになります。これは標準ライブラリのcrypto.Signer向け提案(#56508)と整合性があります。
  3. セキュアなデフォルト設定: RSA鍵に対してSHA-1ベースのssh-rsaアルゴリズムをデフォルトで無効化し、安全なrsa-sha2-256rsa-sha2-512のみを許可。レガシーアルゴリズムが必要な場合は明示的に指定が必要です。
  4. 柔軟なアルゴリズム制限: SignatureAlgorithmsオプションにより、鍵の種類を問わず使用可能なアルゴリズムを事前に指定可能。以下のようなコードが実現できます。

コード例

// Before: 従来の書き方(複雑な型アサーションが必要)
signer, err := ssh.ParsePrivateKey(pemBytes)
if err != nil {
    return err
}
// MultiAlgorithmSignerでアルゴリズムを制限したい場合
if algSigner, ok := signer.(ssh.AlgorithmSigner); ok {
    signer, err = ssh.NewSignerWithAlgorithms(algSigner,
        []string{ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512})
}
// After: 新APIを使った書き方(オプションでアルゴリズム制限が可能)
signer, err := ssh.ParsePrivateKeyV2(pemBytes, &ssh.ParsePrivateKeyV2Options{
    SignatureAlgorithms: []string{
        ssh.KeyAlgoRSASHA256,
        ssh.KeyAlgoRSASHA512,
        ssh.KeyAlgoED25519,
        // ssh-rsaは含めない(セキュア設定)
    },
})
// 鍵種別に関わらず、対応するアルゴリズムのみがフィルタされる
// Before: コンテキストを伝播できない
signature, err := signer.Sign(rand.Reader, data)
// After: コンテキストを使ってタイムアウトやキャンセルを制御
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
signature, err := signer.SignContext(ctx, rand.Reader, data, "rsa-sha2-256")
// Before: 暗号化された鍵のマーシャル(複雑な手順)
block := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
// パスフレーズ付き暗号化は更に複雑
// After: オプション構造体でシンプルに
block, err := ssh.MarshalPrivateKeyV2(signer, &ssh.MarshalPrivateKeyV2Options{
    Comment: "my-key",
    Passphrase: "secret",
    SaltRounds: 32, // セキュリティ強化
})

議論のハイライト

  • コンテキスト対応の要請: @hslatmanより、KMS利用時にコンテキスト伝播が必須との指摘を受け、SignContextメソッドが提案に追加されました(標準ライブラリの#56508提案との整合性を考慮)。
  • 旧式PEM暗号化のサポート: @imirkinより、既存の暗号化PEM鍵の互換性喪失への懸念が示されましたが、RFC 1423の暗号化方式(DES-CBC等)は既に標準ライブラリで非推奨(x509.DecryptPEMBlock)であり、OpenSSH形式への移行が推奨されています。ただし、この点は他のメンテナーとの議論継続事項です。
  • レガシーアルゴリズムの扱い: ssh-rsa(SHA-1使用)をデフォルトで無効化する方針に対し、@imirkinより「安全でないアルゴリズムを有効化する際の簡便性」の要望があり、ParsePrivateKeyV2Options.SignatureAlgorithmsによる柔軟な制御が追加されました。これにより、鍵タイプを意識せずに許可アルゴリズムを指定可能です。
  • Signer()メソッドの命名: @hslatmanより、PrivateKey()よりもSigner()の方が返り値の型(crypto.Signer)に合致するとの提案があり、採用されました。
  • エージェントAPIのコンテキスト対応: @arianvpより、ServeAgentメソッドもSignerV2と整合的にコンテキスト対応すべきとの要望があり、@drakkanは別proposalで対応予定と回答しています。
  • V2接尾辞の理由: x/crypto/sshでの移行期間中の新旧API共存のためにV2接尾辞を使用。標準ライブラリ移行後は接尾辞を削除する計画です。

関連リンク