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

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

このコミットは、Go言語の標準ライブラリである crypto/x509 および crypto/tls パッケージに、PKCS#8形式の秘密鍵のサポートを追加するものです。これにより、OpenSSL 1.0.0以降でデフォルトで生成されるようになったPKCS#8形式の秘密鍵を、Goの http.ListenAndServeTLS 関数が適切に処理できるようになります。

コミット

commit 7c161b05aaa18cb5a49ab0566c7e26e264117eca
Author: Adam Langley <agl@golang.org>
Date:   Mon Nov 21 14:18:42 2011 -0500

    crypto/x509, crypto/tls: support PKCS#8 private keys.
    
    OpenSSL 1.0.0 has switched to generating PKCS#8 format private keys by
    default. This change allows http.ListenAndServeTLS to work with either
    types of keys.
    
    See http://groups.google.com/group/golang-nuts/browse_thread/thread/84715b5f0c9e3c30/63a8a27b53e102a6
    
    R=bradfitz
    CC=golang-dev
    https://golang.org/cl/5416059

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

https://github.com/golang/go/commit/7c161b05aaa18cb5a49ab0566c7e26e264117eca

元コミット内容

このコミットは、Go言語の crypto/x509 および crypto/tls パッケージにPKCS#8形式の秘密鍵のサポートを追加します。OpenSSL 1.0.0がデフォルトでPKCS#8形式の秘密鍵を生成するようになったため、Goの http.ListenAndServeTLS がこれらの新しい形式の鍵でも動作するようにするための変更です。

変更の背景

この変更の主な背景は、広く利用されている暗号化ライブラリであるOpenSSLのバージョンアップです。OpenSSL 0.9.8までは、RSA秘密鍵をPKCS#1形式でデフォルトで生成していました。しかし、OpenSSL 1.0.0からは、より汎用的なPKCS#8形式がデフォルトの秘密鍵フォーマットとして採用されました。

Go言語の http.ListenAndServeTLS 関数は、HTTPSサーバーを起動する際に、証明書と秘密鍵のペアを必要とします。この関数は内部的に crypto/tls パッケージの X509KeyPair 関数を使用して鍵ペアをパースします。OpenSSLの変更により、GoアプリケーションがOpenSSL 1.0.0以降で生成された秘密鍵を使用しようとすると、Goがその形式を認識できず、エラーが発生する可能性がありました。

このコミットは、このような互換性の問題を解決し、GoのTLS機能がOpenSSLの新しいデフォルト設定とシームレスに連携できるようにするために導入されました。これにより、開発者はOpenSSLのバージョンに依存せず、GoのTLS機能を利用できるようになります。

前提知識の解説

PKCS (Public-Key Cryptography Standards)

PKCSは、RSA Security社が策定した公開鍵暗号に関する一連の標準規格です。これらは、公開鍵暗号システムの実装における相互運用性を確保するために設計されています。

PKCS#1 (RSA Cryptography Standard)

PKCS#1は、RSA公開鍵暗号アルゴリズムの仕様を定義する標準です。これには、RSA公開鍵と秘密鍵のフォーマット、署名スキーム、暗号化スキームなどが含まれます。特に、秘密鍵のフォーマットは、通常 -----BEGIN RSA PRIVATE KEY----- で始まり -----END RSA PRIVATE KEY----- で終わるPEMブロックとしてエンコードされます。これは、特定のアルゴリズム(この場合はRSA)に特化した秘密鍵の表現形式です。

PKCS#8 (Private-Key Information Syntax Standard)

PKCS#8は、秘密鍵情報を格納するための汎用的な構文を定義する標準です。PKCS#1が特定のアルゴリズム(RSA)に特化しているのに対し、PKCS#8は任意の秘密鍵アルゴリズム(RSA、DSA、ECCなど)に対応できます。秘密鍵自体はPKCS#8構造の内部にカプセル化され、そのアルゴリズム識別子も含まれます。PEMブロックとしては、通常 -----BEGIN PRIVATE KEY----- で始まり -----END PRIVATE KEY----- で終わります。PKCS#8は、秘密鍵を暗号化して格納する機能もサポートしていますが、このコミットで扱われるのは「暗号化されていないPKCS#8秘密鍵」です。

ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造を記述するための標準的な記法です。通信プロトコルや暗号化システムにおいて、異なるシステム間でデータを交換する際に、そのデータの構造を明確に定義するために使用されます。PKCSの各標準も、内部のデータ構造をASN.1で定義しています。

DER (Distinguished Encoding Rules)

DERは、ASN.1で定義されたデータ構造をバイト列にエンコードするための規則の一つです。DERは、特定のASN.1値に対して常に一意のバイト列表現を生成することを保証します。これにより、暗号化における署名やハッシュ計算の際に、データの表現が一意であることが保証され、セキュリティが向上します。秘密鍵ファイルは、通常PEM形式(Base64エンコードされたDER形式)で保存されます。

Go言語の crypto/x509 パッケージ

Goの crypto/x509 パッケージは、X.509証明書とPKIX(Public Key Infrastructure X.509)関連の機能を実装しています。これには、証明書のパース、検証、証明書署名要求(CSR)の生成、そして秘密鍵のパースなどが含まれます。このコミットでは、特に秘密鍵のパース機能が拡張されています。

Go言語の crypto/tls パッケージ

Goの crypto/tls パッケージは、TLS(Transport Layer Security)プロトコルを実装しています。これにより、Goアプリケーションは安全な通信チャネルを確立できます。HTTPSサーバーやクライアントの実装に利用され、証明書と秘密鍵の管理もこのパッケージの重要な機能の一部です。

http.ListenAndServeTLS 関数

Goの標準ライブラリ net/http パッケージに含まれる ListenAndServeTLS 関数は、指定された証明書と秘密鍵を使用してHTTPSサーバーを起動します。この関数は、ウェブアプリケーションで安全な通信を提供するために広く利用されています。

技術的詳細

このコミットの核心は、Goの crypto/tls パッケージが秘密鍵をパースする際に、PKCS#1形式だけでなくPKCS#8形式も試行するように変更された点です。

具体的には、crypto/tls パッケージの X509KeyPair 関数が修正されました。この関数は、PEMエンコードされた証明書と秘密鍵のバイト列を受け取り、それらをパースして tls.Certificate 構造体を返します。秘密鍵のパース部分で、まず既存の x509.ParsePKCS1PrivateKey を呼び出してPKCS#1形式としてパースを試みます。もしこれが失敗した場合(つまり、鍵がPKCS#1形式ではない場合)、新しく追加された x509.ParsePKCS8PrivateKey 関数を呼び出してPKCS#8形式としてパースを試みます。

x509.ParsePKCS8PrivateKey 関数は、DERエンコードされたPKCS#8秘密鍵のバイト列を受け取ります。この関数は以下のステップを実行します。

  1. 入力されたバイト列をASN.1の pkcs8 構造体としてアンマーシャルします。この pkcs8 構造体は、PKCS#8標準で定義されている秘密鍵のラッパー構造をGoの構造体として表現したものです。これには、バージョン、秘密鍵のアルゴリズム識別子、そして実際の秘密鍵データが含まれます。
  2. アンマーシャルが成功した後、pkcs8 構造体内のアルゴリズム識別子をチェックします。このコミットでは、特にRSA秘密鍵のサポートに焦点を当てているため、アルゴリズム識別子がRSAのOID(Object Identifier)と一致するかどうかを確認します。
  3. アルゴリズムがRSAであると確認された場合、pkcs8 構造体から抽出された実際の秘密鍵データ(これはPKCS#1形式のRSA秘密鍵データです)を、既存の x509.ParsePKCS1PrivateKey 関数に渡してパースします。
  4. パースが成功すれば、*rsa.PrivateKey 型の秘密鍵オブジェクトが返されます。もしアルゴリズムがRSA以外であったり、PKCS#1としてのパースに失敗したりした場合は、適切なエラーが返されます。

この二段階のパース試行により、GoのTLS機能は、OpenSSL 0.9.8以前のPKCS#1形式の鍵と、OpenSSL 1.0.0以降のPKCS#8形式の鍵の両方に対応できるようになります。

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

src/pkg/crypto/tls/tls.go

X509KeyPair 関数内で、秘密鍵のパースロジックが変更されました。

--- a/src/pkg/crypto/tls/tls.go
+++ b/src/pkg/crypto/tls/tls.go
@@ -157,10 +157,21 @@ func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (cert Certificate, err error)\n 		return\n 	}\n 
-	key, err := x509.ParsePKCS1PrivateKey(keyDERBlock.Bytes)\n-	if err != nil {\n-		err = errors.New("crypto/tls: failed to parse key: " + err.Error())\n-		return\n+	// OpenSSL 0.9.8 generates PKCS#1 private keys by default, while\n+	// OpenSSL 1.0.0 generates PKCS#8 keys. We try both.\n+	var key *rsa.PrivateKey\n+	if key, err = x509.ParsePKCS1PrivateKey(keyDERBlock.Bytes); err != nil {\n+		var privKey interface{}\n+		if privKey, err = x509.ParsePKCS8PrivateKey(keyDERBlock.Bytes); err != nil {\n+			err = errors.New("crypto/tls: failed to parse key: " + err.Error())\n+			return\n+		}\n+\n+		var ok bool\n+		if key, ok = privKey.(*rsa.PrivateKey); !ok {\n+			err = errors.New("crypto/tls: found non-RSA private key in PKCS#8 wrapping")\n+			return\n+		}\n 	}\n 
 	cert.PrivateKey = key

src/pkg/crypto/x509/Makefile

pkcs8.go がビルド対象ファイルに追加されました。

--- a/src/pkg/crypto/x509/Makefile
+++ b/src/pkg/crypto/x509/Makefile
@@ -8,6 +8,7 @@ TARG=crypto/x509\n GOFILES=\\\n 	cert_pool.go\\\n 	pkcs1.go\\\n+\tpkcs8.go\\\n 	verify.go\\\n 	x509.go\\\n 

src/pkg/crypto/x509/pkcs8.go (新規ファイル)

PKCS#8秘密鍵のパースロジックを実装する新しいファイルです。

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package x509

import (
	"crypto/x509/pkix"
	"encoding/asn1"
	"errors"
	"fmt"
)

// pkcs8 reflects an ASN.1, PKCS#8 PrivateKey. See 
// ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-8/pkcs-8v1_2.asn.
type pkcs8 struct {
	Version    int
	Algo       pkix.AlgorithmIdentifier
	PrivateKey []byte
	// optional attributes omitted.
}

// ParsePKCS8PrivateKey parses an unencrypted, PKCS#8 private key. See
// http://www.rsa.com/rsalabs/node.asp?id=2130
func ParsePKCS8PrivateKey(der []byte) (key interface{}, err error) {
	var privKey pkcs8
	if _, err := asn1.Unmarshal(der, &privKey); err != nil {
		return nil, err
	}
	switch {
	case privKey.Algo.Algorithm.Equal(oidRSA):
		key, err = ParsePKCS1PrivateKey(privKey.PrivateKey)
		if err != nil {
			return nil, errors.New("crypto/x509: failed to parse RSA private key embedded in PKCS#8: " + err.Error())
		}
		return key, nil
	default:
		return nil, fmt.Errorf("crypto/x509: PKCS#8 wrapping contained private key with unknown algorithm: %v", privKey.Algo.Algorithm)
	}

	panic("unreachable")
}

src/pkg/crypto/x509/pkcs8_test.go (新規ファイル)

pkcs8.go で実装された ParsePKCS8PrivateKey 関数のテストケースです。

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package x509

import (
	"encoding/hex"
	"testing"
)

var pkcs8PrivateKeyHex = `30820278020100300d06092a864886f70d0101010500048202623082025e02010002818100cfb1b5bf9685ffa97b4f99df4ff122b70e59ac9b992f3bc2b3dde17d53c1a34928719b02e8fd17839499bfbd515bd6ef99c7a1c47a239718fe36bfd824c0d96060084b5f67f0273443007a24dfaf5634f7772c9346e10eb294c2306671a5a5e719ae24b4de467291bc571014b0e02dec04534d66a9bb171d644b66b091780e8d020301000102818100b595778383c4afdbab95d2bfed12b3f93bb0a73a7ad952f44d7185fd9ec6c34de8f03a48770f2009c8580bcd275e9632714e9a5e3f32f29dc55474b2329ff0ebc08b3ffcb35bc96e6516b483df80a4a59cceb71918cbabf91564e64a39d7e35dce21cb3031824fdbc845dba6458852ec16af5dddf51a8397a8797ae0337b1439024100ea0eb1b914158c70db39031dd8904d6f18f408c85fbbc592d7d20dee7986969efbda081fdf8bc40e1b1336d6b638110c836bfdc3f314560d2e49cd4fbde1e20b024100e32a4e793b574c9c4a94c8803db5152141e72d03de64e54ef2c8ed104988ca780cd11397bc359630d01b97ebd87067c5451ba777cf045ca23f5912f1031308c702406dfcdbbd5a57c9f85abc4edf9e9e29153507b07ce0a7ef6f52e60dcfebe1b8341babd8b789a837485da6c8d55b29bbb142ace3c24a1f5b54b454d01b51e2ad03024100bd6a2b60dee01e1b3bfcef6a2f09ed027c273cdbbaf6ba55a80f6dcc64e4509ee560f84b4f3e076bd03b11e42fe71a3fdd2dffe7e0902c8584f8cad877cdc945024100aa512fa4ada69881f1d8bb8ad6614f192b83200aef5edf4811313d5ef30a86cbd0a90f7b025c71ea06ec6b34db6306c86b1040670fd8654ad7291d066d06d031`

func TestPKCS8(t *testing.T) {
	derBytes, _ := hex.DecodeString(pkcs8PrivateKeyHex)
	_, err := ParsePKCS8PrivateKey(derBytes)
	if err != nil {
		t.Errorf("failed to decode PKCS8 key: %s", err)
	}
}

コアとなるコードの解説

src/pkg/crypto/tls/tls.go の変更点

X509KeyPair 関数は、証明書と秘密鍵のPEMブロックを受け取り、それらをパースして tls.Certificate 構造体を構築します。変更前は、秘密鍵のパースに x509.ParsePKCS1PrivateKey のみを使用していました。

変更後、この関数は以下のように動作します。

  1. keyDERBlock.Bytes を使って x509.ParsePKCS1PrivateKey を呼び出し、PKCS#1形式の秘密鍵としてパースを試みます。
  2. もし x509.ParsePKCS1PrivateKey がエラーを返した場合(つまり、鍵がPKCS#1形式ではない可能性が高い場合)、次に x509.ParsePKCS8PrivateKey を呼び出してPKCS#8形式の秘密鍵としてパースを試みます。
  3. x509.ParsePKCS8PrivateKey が成功した場合、返される privKeyinterface{} 型です。これは、PKCS#8が様々なアルゴリズムの鍵をラップできるためです。このコミットではRSA鍵を想定しているため、privKey*rsa.PrivateKey 型に型アサーション可能かどうかを確認します。
  4. 型アサーションが成功し、*rsa.PrivateKey が得られれば、それが最終的な秘密鍵として cert.PrivateKey に設定されます。
  5. いずれのパース試行も失敗した場合、またはPKCS#8から抽出された鍵がRSA秘密鍵でなかった場合は、適切なエラーが返されます。

このロジックにより、GoのTLSスタックは、OpenSSLのバージョンによって異なるデフォルトの秘密鍵フォーマットに透過的に対応できるようになりました。

src/pkg/crypto/x509/pkcs8.go の新規実装

このファイルは、PKCS#8秘密鍵のパースを専門に行う ParsePKCS8PrivateKey 関数を定義しています。

  • pkcs8 構造体: これは、PKCS#8標準で定義されている秘密鍵のASN.1構造をGoの構造体として表現したものです。

    • Version: PKCS#8のバージョン番号。
    • Algo: pkix.AlgorithmIdentifier 型で、秘密鍵のアルゴリズム(例: RSA、DSA、ECC)とそのパラメータを識別します。
    • PrivateKey: 実際の秘密鍵データがDERエンコードされたバイト列として格納されます。このデータは、通常、そのアルゴリズム固有のフォーマット(例: RSAの場合はPKCS#1形式)でエンコードされています。
  • ParsePKCS8PrivateKey 関数: この関数は、DERエンコードされたPKCS#8秘密鍵のバイト列 (der []byte) を受け取り、パースされた秘密鍵オブジェクト (key interface{}) とエラーを返します。

    1. asn1.Unmarshal(der, &privKey): 入力されたDERバイト列を pkcs8 構造体にアンマーシャルします。これにより、PKCS#8ラッパーからバージョン、アルゴリズム識別子、および内部の秘密鍵データが抽出されます。
    2. switch { case privKey.Algo.Algorithm.Equal(oidRSA)}: 抽出されたアルゴリズム識別子 (privKey.Algo.Algorithm) がRSAのOID (oidRSA) と一致するかどうかをチェックします。oidRSAcrypto/x509 パッケージ内で定義されているRSAアルゴリズムのオブジェクト識別子です。
    3. key, err = ParsePKCS1PrivateKey(privKey.PrivateKey): もしアルゴリズムがRSAであれば、pkcs8 構造体から抽出された PrivateKey フィールド(これはPKCS#1形式のRSA秘密鍵データ)を、既存の ParsePKCS1PrivateKey 関数に渡してパースします。これにより、最終的に *rsa.PrivateKey 型のオブジェクトが得られます。
    4. エラーハンドリング: パース中にエラーが発生した場合(例: ASN.1のアンマーシャル失敗、RSA秘密鍵のパース失敗、未知のアルゴリズム)、適切なエラーメッセージと共にエラーが返されます。

この新しい pkcs8.go ファイルと ParsePKCS8PrivateKey 関数の追加により、Goの crypto/x509 パッケージは、PKCS#8形式でラップされた秘密鍵を解釈し、その内部に含まれるアルゴリズム固有の秘密鍵(この場合はPKCS#1形式のRSA秘密鍵)を抽出できるようになりました。

関連リンク

参考にした情報源リンク