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

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

このコミットは、Go言語の標準ライブラリ crypto/x509 パッケージに、PKCS #10 (Certificate Signing Request: CSR) のサポートを追加するものです。具体的には、CSRのパース(解析)とシリアライズ(直列化)の機能が導入され、これによりGoアプリケーション内で証明書署名要求の生成と処理が可能になります。

コミット

Author: Kyle Isom kyle@gokyle.net Date: Thu Feb 13 12:54:04 2014 -0500

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

https://github.com/golang/go/commit/fc8e77ca65ab7d4dfd6fd58ad67145f253aab829

元コミット内容

crypto/x509: Add certificate signature request (CSR) support.

This change adds support for parsing and serialisation of PKCS #10,
certificate signature requests.

LGTM=agl
R=golang-codereviews, agl
CC=agl, golang-codereviews, nick
https://golang.org/cl/49830048

変更の背景

この変更が導入される以前のGo言語の crypto/x509 パッケージは、X.509証明書の生成、パース、検証の機能を提供していましたが、PKCS #10形式の証明書署名要求 (CSR) を直接扱う機能がありませんでした。CSRは、公開鍵インフラストラクチャ (PKI) において、証明書発行局 (CA) に証明書の発行を要求する際に用いられる標準的なフォーマットです。

CSRのサポートがない場合、Goアプリケーションから証明書を要求するには、外部ツール(例: OpenSSL)を使用するか、手動でCSRを作成する必要がありました。これは、自動化された証明書管理システムや、Go言語でセキュアな通信を必要とするサービスを構築する上で大きな制約となっていました。

このコミットは、Go言語エコシステム内でCSRの生成と解析を完結させることで、開発者がより簡単にセキュアなアプリケーションを構築できるようにすることを目的としています。これにより、Go言語の crypto/x509 パッケージが提供する機能が拡張され、より包括的なPKI操作が可能になりました。

前提知識の解説

PKCS #10 (Certificate Signing Request: CSR)

PKCS #10は、公開鍵暗号標準 (PKCS) の一つで、証明書署名要求 (CSR) のフォーマットを定義しています。CSRは、公開鍵と、その公開鍵の所有者に関する情報(識別名、Subject Alternative Nameなど)をCAに提出し、デジタル証明書の発行を要求するために使用されます。CSRには、要求者の秘密鍵で署名が施されており、これにより要求の正当性が保証されます。

X.509 証明書

X.509は、公開鍵証明書のフォーマットを定義するITU-Tの標準です。デジタル証明書は、公開鍵と、その公開鍵が属するエンティティ(人、組織、デバイスなど)の識別情報を結びつけ、CAによって署名されたものです。これにより、公開鍵の信頼性を保証し、安全な通信を可能にします。

ASN.1 (Abstract Syntax Notation One)

ASN.1は、データ構造を記述するための標準的な記法です。通信プロトコルやデータストレージにおいて、異なるシステム間でデータを交換する際に、データの表現方法を統一するために使用されます。X.509証明書やPKCS #10 CSRなど、多くの暗号関連の標準がASN.1で定義されています。

DER (Distinguished Encoding Rules)

DERは、ASN.1で定義されたデータ構造をバイト列にエンコードするための規則の一つです。DERは、特定のASN.1構造に対して一意のバイト列表現を保証するため、デジタル署名やハッシュ計算など、データの同一性が厳密に要求される場面で広く利用されます。

Subject Alternative Name (SAN)

X.509証明書において、Subject Alternative Name (SAN) 拡張は、証明書が保護する追加の識別子を指定するために使用されます。従来の証明書では「Subject Common Name (CN)」フィールドが主要な識別子でしたが、SANは複数のDNS名(ウェブサイトのドメイン名)、IPアドレス、メールアドレスなどを含めることができ、より柔軟な識別子を提供します。特に、単一の証明書で複数のドメインを保護する(例: example.comwww.example.com)場合や、IPアドレスで識別されるサーバーの証明書を発行する場合に不可欠です。

暗号学的ハッシュ関数

暗号学的ハッシュ関数は、任意の長さの入力データ(メッセージ)を受け取り、固定長の短いバイト列(ハッシュ値またはメッセージダイジェスト)を出力する関数です。以下の特性を持ちます。

  • 一方向性: ハッシュ値から元のメッセージを効率的に復元することは困難です。
  • 衝突耐性: 異なるメッセージから同じハッシュ値が生成されること(衝突)は、計算上非常に困難です。
  • 改ざん検出: メッセージが少しでも変更されると、ハッシュ値が大きく変化するため、データの改ざんを検出できます。 デジタル署名では、メッセージ全体ではなく、そのハッシュ値に署名することで、効率的にデータの完全性と認証性を保証します。

デジタル署名

デジタル署名は、公開鍵暗号技術を用いて、電子文書の作成者の身元を証明し、文書が改ざんされていないことを保証する仕組みです。署名者は自身の秘密鍵で文書のハッシュ値を暗号化し、その結果を署名として添付します。検証者は署名者の公開鍵を用いて署名を復号し、得られたハッシュ値と、受信した文書から計算したハッシュ値を比較することで、署名の正当性と文書の完全性を確認します。

RSA と ECDSA

  • RSA (Rivest–Shamir–Adleman): 最も広く使用されている公開鍵暗号アルゴリズムの一つで、素因数分解の困難性に基づいています。デジタル署名、鍵交換、暗号化に利用されます。
  • ECDSA (Elliptic Curve Digital Signature Algorithm): 楕円曲線暗号 (ECC) に基づくデジタル署名アルゴリズムです。RSAと比較して、同等のセキュリティレベルをより短い鍵長で実現できるため、計算リソースが限られた環境や、より高いパフォーマンスが求められる場面で有利です。

技術的詳細

このコミットは、crypto/x509 パッケージにPKCS #10 CSRの生成と解析のための新しいデータ構造と関数を導入しています。

新しいデータ構造

  • type CertificateRequest struct:

    • CSRの主要な情報を保持する構造体です。
    • Raw: 完全なDERエンコードされたCSRバイト列。
    • RawTBSCertificateRequest: TBSCertificateRequest(署名される部分)のDERバイト列。
    • RawSubjectPublicKeyInfo: 公開鍵情報のDERバイト列。
    • RawSubject: Subject(識別名)のDERバイト列。
    • Version: CSRのバージョン(通常は0)。
    • Signature: 署名値。
    • SignatureAlgorithm: 署名アルゴリズム。
    • PublicKeyAlgorithm: 公開鍵アルゴリズム。
    • PublicKey: パースされた公開鍵(*rsa.PublicKey または *ecdsa.PublicKey)。
    • Subject: pkix.Name 型で、要求者の識別名。
    • Attributes: PKCS #10で定義される属性のコレクション。これには、要求された拡張(例: SAN)が含まれることがあります。
    • Extensions: パース時に抽出されるX.509拡張。
    • ExtraExtensions: CSRを生成する際に、追加で含める拡張。
    • DNSNames, EmailAddresses, IPAddresses: Subject Alternative Name (SAN) 拡張から抽出された、またはCSR生成時に指定されるDNS名、メールアドレス、IPアドレス。
  • type tbsCertificateRequest struct:

    • CSRの「To Be Signed Certificate Request」部分を表す内部構造体。この部分が秘密鍵で署名されます。
    • Version, Subject, PublicKey, Attributes を含みます。
  • type certificateRequest struct:

    • DERエンコードされたCSR全体の構造を表す内部構造体。
    • TBSCSR (tbsCertificateRequest), SignatureAlgorithm, SignatureValue を含みます。
  • type AttributeTypeAndValueSET struct (in src/pkg/crypto/x509/pkix/pkix.go):

    • RFC 2986 (PKCS #10) で定義される AttributeTypeAndValue のセットを表す新しい構造体。CSRの属性に使用されます。

新しい関数

  • func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv interface{}) (csr []byte, err error):

    • CertificateRequest テンプレートと秘密鍵を使用して、新しいCSRをDERエンコードされたバイト列として生成します。
    • 内部で signingParamsForPrivateKey を呼び出して署名パラメータを決定し、公開鍵をマーシャルし、tbsCertificateRequest を構築して署名します。
    • SANs (DNSNames, EmailAddresses, IPAddresses) が指定されている場合、それらを SubjectAltName 拡張としてCSRに含めます。
    • ExtraExtensionsAttributes の処理ロジックが含まれており、特に oidExtensionRequest (PKCS #9 Extension Request) 属性を通じて拡張がどのように組み込まれるかが実装されています。
  • func ParseCertificateRequest(asn1Data []byte) (*CertificateRequest, error):

    • DERエンコードされたCSRバイト列をパースし、CertificateRequest 構造体を返します。
    • 内部で parseCertificateRequest を呼び出し、ASN.1のデコード、公開鍵のパース、Subjectの抽出、および拡張(特にSAN)の処理を行います。
  • func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP) (derBytes []byte, err error):

    • DNS名、メールアドレス、IPアドレスのリストを、X.509 SubjectAlternativeName 拡張のDERエンコードされた内容にマーシャルするヘルパー関数。
    • IPv4アドレスは可能な限り4バイトでエンコードされます。
  • func signingParamsForPrivateKey(priv interface{}, requestedSigAlgo SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error):

    • 与えられた秘密鍵と要求された署名アルゴリズムに基づいて、ハッシュ関数と署名アルゴリズムの識別子を決定するヘルパー関数。
    • RSAおよびECDSA鍵に対応し、適切なハッシュ関数とOID (Object Identifier) を選択します。

既存関数の変更

  • func parseCertificate(in *certificate) (*Certificate, error) (in src/pkg/crypto/x509/x509.go):

    • SubjectAltName 拡張のパースロジックが parseSANExtension 関数に切り出され、コードの重複が解消されました。
  • func buildExtensions(template *Certificate) (ret []pkix.Extension, err error) (in src/pkg/crypto/x509/x509.go):

    • SubjectAltName 拡張の構築に marshalSANs ヘルパー関数が使用されるようになり、コードが簡素化されました。
  • func CreateCertificate(...) (in src/pkg/crypto/x509/x509.go):

    • 署名パラメータの決定ロジックが signingParamsForPrivateKey 関数に切り出され、コードの再利用性が向上しました。

src/pkg/encoding/asn1/asn1.go の変更

  • ObjectIdentifier 型に String() メソッドが追加され、OIDをドット区切りの文字列形式で表現できるようになりました。これはデバッグやログ出力に便利です。

テストの追加 (src/pkg/crypto/x509/x509_test.go)

  • TestCreateCertificateRequest: さまざまな鍵タイプ(RSA, ECDSA)と署名アルゴリズムを使用してCSRを生成し、その結果をパースして、元のテンプレートと一致するかどうかを検証するテストが追加されました。
  • TestCertificateRequestOverrides: ExtraExtensionsAttributes を使用して、CSRの拡張がどのようにオーバーライドされるかを検証するテストが追加されました。
  • TestParseCertificateRequest: 既存のBase64エンコードされたCSRをパースし、その内容(メールアドレス、DNS名、Subjectなど)が正しく抽出されるかを検証するテストが追加されました。

これらの変更により、Go言語の crypto/x509 パッケージは、証明書ライフサイクル管理においてより完全な機能セットを提供するようになりました。

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

このコミットにおける主要な変更は、以下のファイルとコードブロックに集中しています。

  1. src/pkg/crypto/x509/x509.go:

    • CertificateRequest 構造体の追加 (約1559行目から)
    • tbsCertificateRequest および certificateRequest 内部構造体の追加
    • oidExtensionRequest OIDの定義
    • CreateCertificateRequest 関数の追加 (約1600行目から)
    • ParseCertificateRequest 関数の追加 (約1720行目から)
    • parseCertificateRequest ヘルパー関数の追加
    • marshalSANs ヘルパー関数の追加 (約1157行目から)
    • signingParamsForPrivateKey ヘルパー関数の追加 (約1354行目から)
    • 既存の parseCertificate 関数における SubjectAltName 拡張のパースロジックの parseSANExtension 関数への切り出し (約790行目から)
    • 既存の buildExtensions 関数における marshalSANs の利用 (約1279行目)
    • 既存の CreateCertificate 関数における signingParamsForPrivateKey の利用 (約1420行目)
  2. src/pkg/crypto/x509/pkix/pkix.go:

    • AttributeTypeAndValueSET 構造体の追加 (約30行目から)
  3. src/pkg/crypto/x509/x509_test.go:

    • TestCreateCertificateRequest 関数の追加 (約735行目から)
    • TestCertificateRequestOverrides 関数の追加 (約800行目から)
    • TestParseCertificateRequest 関数の追加 (約880行目から)
    • テスト用のBase64エンコードされたCSRデータ csrBase64 の追加
  4. src/pkg/encoding/asn1/asn1.go:

    • ObjectIdentifier 型への String() メソッドの追加 (約197行目から)

コアとなるコードの解説

CertificateRequest 構造体

type CertificateRequest struct {
	Raw                      []byte // Complete ASN.1 DER content (CSR, signature algorithm and signature).
	RawTBSCertificateRequest []byte // Certificate request info part of raw ASN.1 DER content.
	RawSubjectPublicKeyInfo  []byte // DER encoded SubjectPublicKeyInfo.
	RawSubject               []byte // DER encoded Subject.

	Version            int
	Signature          []byte
	SignatureAlgorithm SignatureAlgorithm

	PublicKeyAlgorithm PublicKeyAlgorithm
	PublicKey          interface{}

	Subject pkix.Name

	// Attributes is a collection of attributes providing
	// additional information about the subject of the certificate.
	// See RFC 2986 section 4.1.
	Attributes []pkix.AttributeTypeAndValueSET

	// Extensions contains raw X.509 extensions. When parsing CSRs, this
	// can be used to extract extensions that are not parsed by this
	// package.
	Extensions []pkix.Extension

	// ExtraExtensions contains extensions to be copied, raw, into any
	// marshaled CSR. Values override any extensions that would otherwise
	// be produced based on the other fields but are overridden by any
	// extensions specified in Attributes.
	//
	// The ExtraExtensions field is not populated when parsing CSRs, see
	// Extensions.
	ExtraExtensions []pkix.Extension

	// Subject Alternate Name values.
	DNSNames       []string
	EmailAddresses []string
	IPAddresses    []net.IP
}

この構造体は、PKCS #10 CSRのすべての関連情報をカプセル化します。CSRの生成時には、SubjectDNSNamesEmailAddressesIPAddressesAttributesExtraExtensions などのフィールドを設定してテンプレートとして使用します。パース時には、これらのフィールドにCSRから抽出されたデータが格納されます。Raw フィールドは、完全なDERエンコードされたCSRバイト列を保持し、署名検証などに利用されます。

CreateCertificateRequest 関数

func CreateCertificateRequest(rand io.Reader, template *CertificateRequest, priv interface{}) (csr []byte, err error) {
	hashFunc, sigAlgo, err := signingParamsForPrivateKey(priv, template.SignatureAlgorithm)
	if err != nil {
		return nil, err
	}

	// ... (公開鍵のマーシャル、SANsの処理、拡張の処理) ...

	tbsCSR := tbsCertificateRequest{
		Version: 0, // PKCS #10, RFC 2986
		Subject: asn1.RawValue{FullBytes: asn1Subject},
		PublicKey: publicKeyInfo{
			Algorithm: publicKeyAlgorithm,
			PublicKey: asn1.BitString{
				Bytes:     publicKeyBytes,
				BitLength: len(publicKeyBytes) * 8,
			},
		},
		Attributes: attributes,
	}

	tbsCSRContents, err := asn1.Marshal(tbsCSR)
	if err != nil {
		return
	}
	tbsCSR.Raw = tbsCSRContents

	h := hashFunc.New()
	h.Write(tbsCSRContents)
	digest := h.Sum(nil)

	var signature []byte
	switch priv := priv.(type) {
	case *rsa.PrivateKey:
		signature, err = rsa.SignPKCS1v15(rand, priv, hashFunc, digest)
	case *ecdsa.PrivateKey:
		var r, s *big.Int
		if r, s, err = ecdsa.Sign(rand, priv, digest); err == nil {
			signature, err = asn1.Marshal(ecdsaSignature{r, s})
		}
	default:
		panic("internal error")
	}

	if err != nil {
		return
	}

	return asn1.Marshal(certificateRequest{
		TBSCSR:             tbsCSR,
		SignatureAlgorithm: sigAlgo,
		SignatureValue: asn1.BitString{
			Bytes:     signature,
			BitLength: len(signature) * 8,
		},
	})
}

この関数はCSR生成の中心的なロジックを担います。

  1. 署名パラメータの決定: signingParamsForPrivateKey を呼び出し、使用するハッシュ関数と署名アルゴリズムを決定します。
  2. 公開鍵のマーシャル: テンプレートから公開鍵情報を抽出し、DERエンコードします。
  3. 拡張の処理: DNSNames, EmailAddresses, IPAddresses が指定されていれば、marshalSANs を使って SubjectAltName 拡張を生成します。また、ExtraExtensionsAttributes に含まれる拡張も適切に処理し、PKCS #9 ExtensionRequest 属性としてCSRに含めます。
  4. tbsCertificateRequest の構築と署名: CSRの署名対象となる部分 (tbsCSR) を構築し、そのDERエンコードされたバイト列をハッシュ化します。その後、提供された秘密鍵 (priv) を使用してハッシュ値にデジタル署名を行います(RSAまたはECDSA)。
  5. 最終的なCSRのエンコード: 署名された tbsCSR と署名情報 (SignatureAlgorithm, SignatureValue) を含む certificateRequest 構造体をASN.1 DER形式でエンコードし、完成したCSRバイト列を返します。

ParseCertificateRequest 関数

func ParseCertificateRequest(asn1Data []byte) (*CertificateRequest, error) {
	var csr certificateRequest

	rest, err := asn1.Unmarshal(asn1Data, &csr)
	if err != nil {
		return nil, err
	} else if len(rest) != 0 {
		return nil, asn1.SyntaxError{Msg: "trailing data"}
	}

	return parseCertificateRequest(&csr)
}

func parseCertificateRequest(in *certificateRequest) (*CertificateRequest, error) {
	out := &CertificateRequest{
		Raw: in.Raw,
		RawTBSCertificateRequest: in.TBSCSR.Raw,
		RawSubjectPublicKeyInfo:  in.TBSCSR.PublicKey.Raw,
		RawSubject:               in.TBSCSR.Subject.FullBytes,

		Signature:          in.SignatureValue.RightAlign(),
		SignatureAlgorithm: getSignatureAlgorithmFromOID(in.SignatureAlgorithm.Algorithm),

		PublicKeyAlgorithm: getPublicKeyAlgorithmFromOID(in.TBSCSR.PublicKey.Algorithm.Algorithm),

		Version:    in.TBSCSR.Version,
		Attributes: in.TBSCSR.Attributes,
	}

	// ... (公開鍵のパース、Subjectのパース、拡張の抽出とSANsのパース) ...

	return out, nil
}

この関数は、DERエンコードされたCSRバイト列を受け取り、それを CertificateRequest 構造体にデコードします。

  1. ASN.1デコード: まず、入力されたバイト列を certificateRequest 内部構造体にASN.1デコードします。
  2. 基本情報の抽出: certificateRequest から RawRawTBSCertificateRequestRawSubjectPublicKeyInfoRawSubjectSignatureSignatureAlgorithmPublicKeyAlgorithmVersionAttributes などの基本情報を CertificateRequest 構造体にコピーします。
  3. 公開鍵のパース: parsePublicKey を使用して、CSRに含まれる公開鍵を適切なGoの公開鍵型(*rsa.PublicKey または *ecdsa.PublicKey)にパースします。
  4. Subjectのパース: in.TBSCSR.Subject.FullBytes から pkix.Name 構造体を構築し、要求者の識別名を抽出します。
  5. 拡張の抽出とSANsのパース: CSRの Attributes フィールドから oidExtensionRequest 属性を探し、その中に含まれるX.509拡張を抽出します。特に、SubjectAltName 拡張が見つかった場合は、parseSANExtension を呼び出して DNSNames, EmailAddresses, IPAddresses をパースし、CertificateRequest 構造体の対応するフィールドに格納します。

これらの関数と構造体の導入により、Go言語の crypto/x509 パッケージは、PKCS #10 CSRの生成と解析という重要な機能を提供できるようになり、PKI関連のアプリケーション開発がより容易になりました。

関連リンク

参考にした情報源リンク