[インデックス 15269] ファイルの概要
このコミットは、Go言語のcrypto/x509
パッケージにおいて、X.509証明書のSubject Alternative Name (SAN) にIPアドレスを含める機能(IP SANs)をサポートするための変更を導入しています。具体的には、IPアドレス形式のSANのマーシャリング(エンコード)、アンマーシャリング(デコード)、および検証の機能が追加されました。また、RFC 6125に準拠し、IPアドレスはIP SANsに対してのみ検証されるようになり、以前のようにホスト名に対して検証されることはなくなりました。
コミット
commit 5b20a18f3b985cdf116a0ea6dd589d26056b16ad
Author: Adam Langley <agl@golang.org>
Date: Fri Feb 15 10:40:17 2013 -0500
crypto/x509: support IP SANs.
Subject Alternative Names in X.509 certificates may include IP
addresses. This change adds support for marshaling, unmarshaling and
verifying this form of SAN.
It also causes IP addresses to only be checked against IP SANs,
rather than against hostnames as was previously the case. This
reflects RFC 6125.
Fixes #4658.
R=golang-dev, mikioh.mikioh, bradfitz
CC=golang-dev
https://golang.org/cl/7336046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5b20a18f3b985cdf116a0ea6dd589d26056b16ad
元コミット内容
crypto/x509: support IP SANs.
Subject Alternative Names in X.509 certificates may include IP
addresses. This change adds support for marshaling, unmarshaling and
verifying this form of SAN.
It also causes IP addresses to only be checked against IP SANs,
rather than against hostnames as was previously the case. This
reflects RFC 6125.
Fixes #4658.
R=golang-dev, mikioh.mikioh, bradfitz
CC=golang-dev
https://golang.org/cl/7336046
変更の背景
X.509証明書は、ウェブサイトやその他のエンティティの身元を検証するために広く使用されるデジタル証明書です。これらの証明書は、通常、Common Name (CN)
フィールドにホスト名を含みますが、より柔軟な識別子をサポートするためにSubject Alternative Name (SAN)
拡張が導入されました。SANは、DNS名、URI、電子メールアドレスなど、さまざまな形式の識別子を格納できます。
このコミット以前のGoのcrypto/x509
パッケージでは、IPアドレスがSANとして適切に扱われていませんでした。特に、IPアドレスを検証する際に、証明書内のDNS名(DNSNames
フィールド)やCommon Name
に対して照合しようとする問題がありました。これは、IPアドレスとホスト名が異なる種類の識別子であり、セキュリティ上の懸念や誤った検証結果につながる可能性がありました。
この問題に対処するため、RFC 6125「Representation and Verification of Domain Names in the Internet Public Key Infrastructure」が重要な指針となります。RFC 6125は、ドメイン名(およびIPアドレス)がX.509証明書に対してどのように検証されるべきかについて明確なルールを定めています。このRFCの重要な点は、IPアドレスはIPアドレスとしてSANに記述され、IPアドレスの検証はIP SANに対してのみ行われるべきであるという点です。
このコミットは、Goのcrypto/x509
パッケージをRFC 6125に準拠させ、IPアドレスのSANサポートを完全に実装することで、より堅牢で安全な証明書検証メカニズムを提供することを目的としています。これにより、IPアドレスを直接指定してアクセスするサービスや、IPアドレスベースの仮想ホスティングなどにおいて、証明書の検証が正しく行われるようになります。
前提知識の解説
X.509 証明書
X.509は、公開鍵証明書の標準フォーマットを定義するITU-Tの標準です。インターネット上で広く使用されており、TLS/SSL通信においてサーバーやクライアントの身元を証明するために利用されます。X.509証明書には、公開鍵、所有者の識別情報(Common Name、Organizationなど)、発行者の情報、有効期間、署名などが含まれます。
Subject Alternative Name (SAN)
Subject Alternative Name (SAN) は、X.509証明書の拡張フィールドの一つです。これは、証明書が有効である追加の識別子を指定するために使用されます。従来の証明書ではCommon Name (CN)
フィールドが主要な識別子でしたが、CNは単一のホスト名しか指定できませんでした。SANは、以下のような複数の種類の識別子をサポートします。
- DNS Name (dNSName): ホスト名(例:
www.example.com
)。ワイルドカード(例:*.example.com
)も使用できます。 - IP Address (iPAddress): IPアドレス(IPv4またはIPv6)。
- Email Address (rfc822Name): 電子メールアドレス。
- URI (uniformResourceIdentifier): Uniform Resource Identifier。
- Directory Name (directoryName): X.500形式の識別名。
- Registered ID (registeredID): 登録されたオブジェクト識別子。
SANは、単一の証明書で複数のドメイン名やIPアドレスをカバーできるため、特にバーチャルホスティング環境や、複数のサービスが同じ証明書を共有する場合に非常に有用です。
IP SANs
IP SANsは、SAN拡張の中でも特にIPアドレスを指定する形式です。これにより、証明書が特定のIPアドレスに対して有効であることを明示的に示すことができます。例えば、https://192.168.1.1
のようなIPアドレスで直接アクセスされる内部サービスや、ロードバランサーのIPアドレスに対して証明書を発行する場合に利用されます。
RFC 6125
RFC 6125は、「Representation and Verification of Domain Names in the Internet Public Key Infrastructure」というタイトルのIETF標準です。このRFCは、TLS/SSLなどのプロトコルにおいて、X.509証明書内の識別子(特にドメイン名)がどのように表現され、検証されるべきかについて詳細なガイドラインを提供します。
RFC 6125の重要なポイントの一つは、証明書の検証において、クライアントが接続しようとしている識別子(ホスト名またはIPアドレス)と証明書内の識別子との照合ルールを明確にすることです。特にIPアドレスの検証に関して、RFC 6125は以下の点を強調しています。
- IPアドレスはIP SANsに対してのみ検証されるべきである。 証明書にIPアドレスがSANとして含まれている場合、そのIPアドレスはIP SANフィールドと照合されるべきです。
- IPアドレスをDNS名やCommon Nameとして扱うべきではない。 IPアドレスをDNS名としてSANに含めたり、Common Nameとして使用したりすることは推奨されません。また、IPアドレスを検証する際に、DNS名やCommon Nameフィールドと照合することは誤りであるとされています。
このコミットは、Goのcrypto/x509
パッケージがこのRFC 6125の原則に準拠するように変更を加えるものです。
マーシャリングとアンマーシャリング
- マーシャリング (Marshaling): プログラム内のデータ構造(この場合はGoの
x509.Certificate
構造体)を、外部形式(この場合はX.509証明書のDERエンコードされたバイナリ形式)に変換するプロセスです。これにより、データはファイルに保存されたり、ネットワーク経由で送信されたりすることができます。 - アンマーシャリング (Unmarshaling): 外部形式のデータを、プログラム内のデータ構造に変換し直すプロセスです。これにより、受信した証明書データをGoのプログラムで扱えるようになります。
X.509証明書はASN.1 (Abstract Syntax Notation One) とDER (Distinguished Encoding Rules) を用いてエンコードされます。SAN拡張内の各識別子(DNS名、IPアドレスなど)は、ASN.1のGeneralName構造体として表現され、それぞれ異なるタグ(例: DNS名はタグ2、IPアドレスはタグ7)を持ちます。
技術的詳細
このコミットは、Goのcrypto/x509
パッケージとその関連ツールにおいて、IP SANsのサポートを全面的に統合するために複数のファイルにわたる変更を加えています。
src/pkg/crypto/x509/x509.go
このファイルは、X.509証明書のコア構造と、証明書のパース(アンマーシャリング)および構築(マーシャリング)ロジックを定義しています。
-
Certificate
構造体へのIPAddresses
フィールドの追加:Certificate
構造体にIPAddresses []net.IP
という新しいフィールドが追加されました。これにより、パースされた証明書からIP SANsを直接取得できるようになります。type Certificate struct { // ... // Subject Alternate Name values DNSNames []string EmailAddresses []string IPAddresses []net.IP // 新しく追加されたフィールド // ... }
-
parseCertificate
関数でのIP SANsのパース:parseCertificate
関数は、DERエンコードされた証明書をパースし、Certificate
構造体にデータを格納します。この関数内で、SAN拡張のGeneralNameを処理するロジックが変更されました。 ASN.1のGeneralName構造体において、IPアドレスはtag 7
としてエンコードされます。この変更により、tag 7
を持つRawValue
が検出された場合、そのバイト列がnet.IP
に変換され、Certificate.IPAddresses
スライスに追加されるようになりました。IPv4とIPv6の両方に対応し、不正な長さのIPアドレスが検出された場合はエラーを返します。// parseCertificate関数内 case 7: // iPAddress switch len(v.Bytes) { case net.IPv4len, net.IPv6len: out.IPAddresses = append(out.IPAddresses, v.Bytes) default: return nil, errors.New("x509: certificate contained IP address of length " + strconv.Itoa(len(v.Bytes))) }
-
buildExtensions
関数でのIP SANsのエンコード:buildExtensions
関数は、Certificate
構造体からX.509証明書の拡張(SANを含む)を構築し、DERエンコードします。この関数も変更され、Certificate.IPAddresses
フィールドにIPアドレスが存在する場合、それらがASN.1のtag 7
として適切にエンコードされ、SAN拡張に追加されるようになりました。IPv4アドレスは、可能であれば4バイト形式でエンコードされます。// buildExtensions関数内 if len(template.DNSNames) > 0 || len(template.EmailAddresses) > 0 || len(template.IPAddresses) > 0 { ret[n].Id = oidExtensionSubjectAltName var rawValues []asn1.RawValue // ... (DNSNamesとEmailAddressesの処理) for _, rawIP := range template.IPAddresses { // If possible, we always want to encode IPv4 addresses in 4 bytes. ip := rawIP.To4() if ip == nil { ip = rawIP } rawValues = append(rawValues, asn1.RawValue{Tag: 7, Class: 2, Bytes: ip}) } ret[n].Value, err = asn1.Marshal(rawValues) // ... }
src/pkg/crypto/x509/verify.go
このファイルは、X.509証明書の検証ロジック、特にホスト名検証を担当しています。
-
VerifyHostname
関数の変更:VerifyHostname
関数は、与えられたホスト名が証明書によって有効であるかを検証します。この関数は、RFC 6125の原則に従い、IPアドレスの検証ロジックが大幅に変更されました。- 検証対象のホスト名がIPアドレス(IPv4またはIPv6、
[]
で囲まれた形式も含む)である場合、証明書のIPAddresses
フィールド(IP SANs)のみが照合対象となります。 - IP SANsの中に一致するIPアドレスが見つかれば検証は成功します。
- IPアドレスであるにもかかわらず、証明書にIP SANsが含まれていない場合、または一致するIP SANsがない場合は、
HostnameError
が返されます。 - これにより、IPアドレスがDNS名やCommon Nameと照合されるという以前の誤った挙動が修正されました。
// VerifyHostname関数内 candidateIP := h if len(h) >= 3 && h[0] == '[' && h[len(h)-1] == ']' { candidateIP = h[1 : len(h)-1] } if ip := net.ParseIP(candidateIP); ip != nil { // We only match IP addresses against IP SANs. // https://tools.ietf.org/html/rfc6125#appendix-B.2 for _, candidate := range c.IPAddresses { if ip.Equal(candidate) { return nil } } return HostnameError{c, candidateIP} } // ... (IPアドレスでない場合は既存のDNSName/CommonName検証ロジック)
- 検証対象のホスト名がIPアドレス(IPv4またはIPv6、
-
HostnameError
の変更:HostnameError
構造体のError()
メソッドが、IPアドレス検証の失敗時に、より適切なエラーメッセージを生成するように変更されました。IPアドレスがIP SANsに対して検証されなかった場合に、その旨を明確に伝えるメッセージが生成されます。
src/pkg/crypto/tls/generate_cert.go
このファイルは、テスト目的などで自己署名証明書を生成するためのユーティリティです。
- コマンドライン引数の拡張:
--host
フラグが、カンマ区切りのホスト名とIPアドレスを受け入れるように変更されました。--start-date
、--duration
、--ca
、--rsa-bits
などの新しいフラグが追加され、証明書生成の柔軟性が向上しました。
- IP SANsの生成:
--host
で指定された値がIPアドレスとしてパースできる場合、そのIPアドレスは生成される証明書のtemplate.IPAddresses
スライスに追加されます。これにより、このツールでIP SANsを含む証明書を簡単に生成できるようになりました。
src/pkg/crypto/x509/x509_test.go
このファイルは、crypto/x509
パッケージのテストケースを含んでいます。
TestMatchIP
関数の追加: IPアドレスの検証ロジックをテストするための新しい関数TestMatchIP
が追加されました。このテストは、IPアドレスがIP SANsに対して正しく照合されること、およびDNS名やCommon Nameに対してIPアドレスが照合されないことを確認します。TestCreateSelfSignedCertificate
の更新: 自己署名証明書生成のテストが更新され、生成される証明書にIP SANsとEmail SANsが含まれるように変更されました。また、パースされた証明書のDNSNames
、EmailAddresses
、IPAddresses
フィールドがテンプレートと一致するかを検証するアサーションが追加されました。
src/pkg/go/build/deps_test.go
このファイルは、Goパッケージのビルド依存関係を定義しています。
crypto/x509
の依存関係にnet
パッケージを追加:crypto/x509
パッケージがIPアドレスを扱うためにnet
パッケージの機能(net.IP
、net.ParseIP
など)を使用するようになったため、その依存関係が明示的に追加されました。
src/pkg/net/http/httptest/server.go
このファイルは、HTTPテストサーバーのヘルパー関数を含んでいます。
localhostCert
の更新: テスト用のlocalhostCert
が、IP SANs(127.0.0.1
と::1
)を含むように更新されました。これは、generate_cert.go
ツールを使用して新しい証明書が生成されたことを示しています。これにより、HTTPテスト環境でIP SANsの検証をテストできるようになります。
これらの変更により、Goのcrypto/x509
パッケージはIPアドレスをX.509証明書のSANとして完全にサポートし、RFC 6125に準拠した堅牢な検証メカニズムを提供します。
コアとなるコードの変更箇所
src/pkg/crypto/x509/x509.go
- Certificate
構造体へのIPAddresses
フィールド追加
type Certificate struct {
// ...
// Subject Alternate Name values
DNSNames []string
EmailAddresses []string
IPAddresses []net.IP // 新しく追加されたフィールド
// ...
}
src/pkg/crypto/x509/x509.go
- parseCertificate
関数でのIP SANsのパース
// parseCertificate関数内、GeneralNameのswitch文の一部
case 7: // iPAddress
switch len(v.Bytes) {
case net.IPv4len, net.IPv6len:
out.IPAddresses = append(out.IPAddresses, v.Bytes)
default:
return nil, errors.New("x509: certificate contained IP address of length " + strconv.Itoa(len(v.Bytes)))
}
src/pkg/crypto/x509/x509.go
- buildExtensions
関数でのIP SANsのエンコード
// buildExtensions関数内、SAN拡張の構築部分
if len(template.DNSNames) > 0 || len(template.EmailAddresses) > 0 || len(template.IPAddresses) > 0 {
ret[n].Id = oidExtensionSubjectAltName
var rawValues []asn1.RawValue
for _, name := range template.DNSNames {
rawValues = append(rawValues, asn1.RawValue{Tag: 2, Class: 2, Bytes: []byte(name)})
}
for _, email := range template.EmailAddresses {
rawValues = append(rawValues, asn1.RawValue{Tag: 1, Class: 2, Bytes: []byte(email)})
}
for _, rawIP := range template.IPAddresses {
// If possible, we always want to encode IPv4 addresses in 4 bytes.
ip := rawIP.To4()
if ip == nil {
ip = rawIP
}
rawValues = append(rawValues, asn1.RawValue{Tag: 7, Class: 2, Bytes: ip})
}
ret[n].Value, err = asn1.Marshal(rawValues)
if err != nil {
return
}
n++
}
src/pkg/crypto/x509/verify.go
- VerifyHostname
関数でのIPアドレス検証ロジック
// VerifyHostname関数内
func (c *Certificate) VerifyHostname(h string) error {
// IP addresses may be written in [ ].
candidateIP := h
if len(h) >= 3 && h[0] == '[' && h[len(h)-1] == ']' {
candidateIP = h[1 : len(h)-1]
}
if ip := net.ParseIP(candidateIP); ip != nil {
// We only match IP addresses against IP SANs.
// https://tools.ietf.org/html/rfc6125#appendix-B.2
for _, candidate := range c.IPAddresses {
if ip.Equal(candidate) {
return nil
}
// IPv4-mapped IPv6 addresses are not considered equal to IPv4 addresses.
// RFC 6125, Section 6.4.2, bullet 3.
}
return HostnameError{c, candidateIP}
}
lowered := toLowerCaseASCII(h)
if len(c.DNSNames) > 0 {
// ... 既存のDNSName検証ロジック ...
}
// ... 既存のCommonName検証ロジック ...
}
コアとなるコードの解説
Certificate
構造体へのIPAddresses
フィールド追加
これは、X.509証明書からパースされたIPアドレス形式のSANを保持するための基本的な変更です。net.IP
型はGoの標準ライブラリでIPアドレスを表すために使用され、IPv4とIPv6の両方に対応できます。このフィールドの追加により、証明書内のIP SANsにプログラムから直接アクセスできるようになります。
parseCertificate
関数でのIP SANsのパース
X.509証明書はASN.1 (Abstract Syntax Notation One) を使用してエンコードされます。SAN拡張内の各エントリはGeneralName構造体として表現され、その種類(DNS名、IPアドレスなど)はASN.1のタグによって識別されます。IPアドレスの場合、ASN.1のGeneralNameではtag 7
が割り当てられています。
このコードは、証明書をパースする際に、SAN拡張からtag 7
を持つRawValue
(生のASN.1値)を検出します。検出されたRawValue
のバイト列がIPアドレスの有効な長さ(IPv4の場合は4バイト、IPv6の場合は16バイト)であるかを確認し、net.IP
型に変換してCertificate.IPAddresses
スライスに追加します。これにより、証明書内のIP SANsがGoのデータ構造に正しくデコードされるようになります。
buildExtensions
関数でのIP SANsのエンコード
このコードは、GoのCertificate
構造体からX.509証明書を構築する際に、IPAddresses
フィールドに格納されているIPアドレスをSAN拡張としてエンコードする役割を担います。
template.IPAddresses
スライスにIPアドレスが存在する場合、それぞれのIPアドレスはASN.1のRawValue
として準備されます。ここでもtag 7
がIPアドレスを示すために使用されます。特に、IPv4アドレスは、可能であれば4バイト形式でエンコードされるようにrawIP.To4()
が呼び出されます。これは、IPv4アドレスがIPv6アドレスとしてマッピングされた形式(例: ::ffff:192.0.2.1
)でエンコードされるのを防ぎ、よりコンパクトで標準的な表現を保証するためです。これらのRawValue
はASN.1のシーケンスとしてマーシャリングされ、SAN拡張として証明書に追加されます。
VerifyHostname
関数でのIPアドレス検証ロジック
この変更は、RFC 6125の主要な要件を実装するものです。VerifyHostname
関数が呼び出された際に、検証対象のホスト名h
がIPアドレス形式であるかをnet.ParseIP
を使って最初にチェックします。
- もし
h
がIPアドレスとしてパースできる場合([::1]
のようなブラケット形式も考慮)、検証ロジックは証明書のIPAddresses
フィールド(IP SANs)のみを対象とします。 - 証明書の
IPAddresses
スライスをイテレートし、いずれかのIP SANが検証対象のIPアドレスと一致するかを確認します。ip.Equal(candidate)
は、IPアドレスの比較を正確に行うためのメソッドです。 - 一致するIP SANが見つかれば、検証は成功し
nil
が返されます。 - 一致するIP SANが見つからない場合、または証明書にIP SANsが全く含まれていない場合は、
HostnameError
が返され、IPアドレスがIP SANsに対して検証されなかったことを示すエラーメッセージが生成されます。
この変更により、IPアドレスはDNS名やCommon Nameとは独立して、IP SANsに対してのみ検証されるようになり、RFC 6125に準拠したより安全で正確な証明書検証が実現されます。
関連リンク
- Go Issue #4658: https://code.google.com/p/go/issues/detail?id=4658 (このコミットによって修正されたGoのIssue)
- Gerrit Change 7336046: https://golang.org/cl/7336046 (このコミットのGerritレビューページ)
参考にした情報源リンク
- RFC 6125: https://datatracker.ietf.org/doc/html/rfc6125 - Representation and Verification of Domain Names in the Internet Public Key Infrastructure
- X.509 Subject Alternative Name (SAN): https://en.wikipedia.org/wiki/Subject_Alternative_Name
- ASN.1 GeneralName: https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.6 (RFC 5280, Section 4.2.1.6 - Subject Alternative Nameの定義)
- Go
crypto/x509
package documentation: https://pkg.go.dev/crypto/x509 - Go
net
package documentation: https://pkg.go.dev/net