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

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

このコミットは、Go言語の標準ライブラリ crypto/tls パッケージにおけるTLSクライアント認証の機能強化とバグ修正に焦点を当てています。具体的には、certificateRequest メッセージのマーシャリング/アンマーシャリングの不正確さの修正、サーバーサイドでのクライアント認証設定のサポート追加、およびクライアントサイドでの証明書選択ロジックの改善が行われました。これにより、TLSクライアント認証の堅牢性と柔軟性が向上しています。

コミット

commit c581ec4918ba7fc92e991afdb6f7dd4ccfb31124
Author: Jeff R. Allen <jra@nella.org>
Date:   Thu Jan 5 12:05:38 2012 -0500

    crypto/tls: Improve TLS Client Authentication
    
    Fix incorrect marshal/unmarshal of certificateRequest.
    Add support for configuring client-auth on the server side.
    Fix the certificate selection in the client side.
    Update generate_cert.go to new time package
    
    Fixes #2521.
    
    R=krautz, agl, bradfitz
    CC=golang-dev, mikkel
    https://golang.org/cl/5448093

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

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

元コミット内容

crypto/tls: Improve TLS Client Authentication

Fix incorrect marshal/unmarshal of certificateRequest.
Add support for configuring client-auth on the server side.
Fix the certificate selection in the client side.
Update generate_cert.go to new time package

Fixes #2521.

R=krautz, agl, bradfitz
CC=golang-dev, mikkel
https://golang.org/cl/5448093

変更の背景

このコミットの主な背景には、Go言語の crypto/tls パッケージにおけるTLSクライアント認証機能の既存の課題と制限がありました。具体的には、以下の点が挙げられます。

  1. certificateRequest メッセージの不正確な処理: TLSハンドシェイク中にサーバーがクライアントに証明書を要求する際に使用される certificateRequest メッセージのマーシャリング(構造体をバイト列に変換)およびアンマーシャリング(バイト列を構造体に戻す)処理にバグが存在していました。これにより、クライアント認証が正しく機能しない、または予期せぬエラーが発生する可能性がありました。
  2. サーバーサイドでのクライアント認証設定の不足: サーバーがクライアント認証を要求する際のポリシー設定が限定的でした。以前は AuthenticateClient という単純なブーリアン値で制御されていましたが、より詳細な認証要件(例:証明書を要求するが必須ではない、特定のCAによって署名された証明書のみを受け入れるなど)を設定する柔軟性が求められていました。
  3. クライアントサイドでの証明書選択ロジックの不備: クライアントがサーバーからの証明書要求に対して、自身の利用可能な証明書の中から適切なものを選択するロジックが不十分でした。特に、サーバーが信頼する認証局(CA)のリストを提示した場合に、クライアントがそのリストに基づいて適切な証明書を提示する機能が不足していました。

これらの問題は、TLSクライアント認証の信頼性と実用性を低下させていました。このコミットは、これらの課題を解決し、より堅牢で柔軟なクライアント認証メカニズムを提供することを目的としています。コミットメッセージに Fixes #2521. とあるように、特定のバグトラッキングシステム上の問題に対応するものでもありますが、その詳細な内容は公開情報からは確認できませんでした。

前提知識の解説

このコミットの技術的詳細を理解するためには、以下の概念について基本的な知識があると役立ちます。

  • TLS (Transport Layer Security): インターネット上でデータを安全にやり取りするための暗号化プロトコルです。ウェブブラウザとサーバー間のHTTPS通信などで広く利用されています。TLSハンドシェイクは、クライアントとサーバーが安全な通信チャネルを確立するために行う一連のメッセージ交換です。
  • クライアント認証 (Client Authentication): 通常のTLS通信では、サーバーが自身の身元を証明するために証明書をクライアントに提示します(サーバー認証)。クライアント認証は、これに加えてクライアントも自身の身元を証明するために証明書をサーバーに提示するプロセスです。これにより、相互認証が実現され、より高いセキュリティレベルが提供されます。
  • X.509 証明書: 公開鍵証明書の標準フォーマットです。TLS通信において、エンティティ(サーバーやクライアント)の公開鍵と身元情報を関連付けるために使用されます。証明書には、発行者(CA)、サブジェクト(証明書の所有者)、公開鍵、有効期間などの情報が含まれます。
  • 認証局 (Certificate Authority, CA): デジタル証明書を発行し、その有効性を保証する信頼された第三者機関です。CAは、証明書にデジタル署名を行うことで、その証明書が本物であることを保証します。
  • certificateRequest メッセージ: TLSハンドシェイクプロトコルの一部として、サーバーがクライアントに証明書を要求する際に送信するメッセージです。このメッセージには、サーバーが受け入れる証明書の種類(例: RSA署名)や、サーバーが信頼する認証局の識別名(Distinguished Name, DN)のリストが含まれることがあります。
  • マーシャリング (Marshalling) / アンマーシャリング (Unmarshalling): プログラム内のデータ構造(オブジェクト)を、ネットワーク経由で送信したり、ファイルに保存したりできる形式(通常はバイト列)に変換するプロセスをマーシャリングと呼びます。その逆のプロセス(バイト列からデータ構造に戻す)をアンマーシャリングと呼びます。TLSハンドシェイクメッセージも、送信前にマーシャリングされ、受信後にアンマーシャリングされます。
  • x509.CertPool: Go言語の crypto/x509 パッケージで提供される構造体で、信頼されたルート証明書や中間証明書の集合を管理するために使用されます。証明書の検証プロセスにおいて、提示された証明書チェーンがこのプール内のいずれかの証明書によって信頼されているかを確認するために利用されます。
  • RFC 4346: TLS 1.1プロトコルの仕様を定義するIETFの文書です。このコミットの変更点、特に certificateRequest メッセージの構造に関する修正は、このRFCの規定に準拠するためのものです。

技術的詳細

このコミットは、Go言語の crypto/tls パッケージにおけるクライアント認証の複数の側面を改善しています。

  1. ClientAuthType 列挙型の導入 (src/pkg/crypto/tls/common.go):

    • 以前は Config 構造体内の AuthenticateClient という単純なブーリアン値でクライアント認証の有無を制御していました。
    • このコミットでは、より詳細なクライアント認証ポリシーを定義するために ClientAuthType という新しい列挙型が導入されました。
    • NoClientCert: クライアント証明書を要求しない(デフォルト)。
    • RequestClientCert: クライアント証明書を要求するが、クライアントが提示しなくても接続を継続する。
    • RequireAnyClientCert: クライアント証明書を要求し、クライアントが何らかの証明書を提示することを必須とする(検証は行わない)。
    • VerifyClientCertIfGiven: クライアント証明書を要求するが、クライアントが提示した場合にのみ検証を行う。
    • RequireAndVerifyClientCert: クライアント証明書を要求し、クライアントが提示した証明書を必須かつ検証することを必須とする。
    • これにより、サーバーはクライアント認証の挙動をより細かく制御できるようになりました。
  2. Config 構造体の変更 (src/pkg/crypto/tls/common.go):

    • AuthenticateClient bool フィールドが ClientAuth ClientAuthType に置き換えられました。
    • ClientCAs *x509.CertPool フィールドが追加されました。これは、サーバーがクライアント証明書を検証する際に信頼するルート認証局のセットを定義するために使用されます。サーバーは、この ClientCAs に含まれるCAによって署名されたクライアント証明書のみを信頼します。
  3. certificateRequestMsg のマーシャリング/アンマーシャリングの修正 (src/pkg/crypto/tls/handshake_messages.go):

    • certificateRequestMsgmarshal() および unmarshal() メソッドにおける certificateAuthorities フィールドの長さの計算と処理にバグがありました。
    • 以前の実装では、certificateAuthorities のリストの要素数 (numCA) を長さとして扱っていましたが、RFC 4346の仕様では、これはリスト内のすべての認証局の識別名(DERエンコードされたバイト列)の合計バイト長 (casLength) であるべきでした。
    • このコミットでは、この計算ロジックが修正され、正しい casLength が使用されるようになりました。これにより、異なるTLS実装間での certificateRequest メッセージの互換性が向上しました。
  4. サーバーサイドでのクライアント認証要求の改善 (src/pkg/crypto/tls/handshake_server.go):

    • サーバーが certificateRequestMsg を送信する際に、config.ClientCAs が設定されている場合、その CertPool 内のすべての証明書のサブジェクト(識別名)を certificateAuthorities フィールドに含めるようになりました。
    • これにより、クライアントはサーバーが信頼する認証局のリストを受け取り、自身の証明書の中から適切なものを選択する際のヒントとして利用できるようになります。
    • クライアントが証明書を提示しなかった場合のサーバーの挙動が、新しい ClientAuthType に基づいて適切に処理されるようになりました(例: RequireAnyClientCertRequireAndVerifyClientCert の場合に証明書がないとエラーを返す)。
  5. サーバーサイドでのクライアント証明書検証の強化 (src/pkg/crypto/tls/handshake_server.go):

    • サーバーは、クライアントから提示された証明書チェーンに対して、x509.VerifyOptions を使用したより厳密な検証を行うようになりました。
    • 検証には、config.ClientCAs をルートとして使用し、証明書の有効期限、署名チェーンの検証、および拡張鍵用途(Extended Key Usage, EKU)のチェック(ExtKeyUsageClientAuth が含まれているか)が含まれます。
    • これにより、サーバーは不正なクライアント証明書や、クライアント認証目的ではない証明書を拒否できるようになり、セキュリティが向上しました。
  6. クライアントサイドでの証明書選択ロジックの改善 (src/pkg/crypto/tls/handshake_client.go):

    • クライアントは、サーバーから受け取った certificateRequestMsgcertificateAuthorities リストを考慮して、自身の config.Certificates から適切な証明書を選択するようになりました。
    • 具体的には、クライアントは自身の証明書のIssuer(発行者)が、サーバーが提示した certificateAuthorities リスト内のいずれかの識別名と一致するかどうかを確認します。
    • これにより、クライアントはサーバーの要求に合致する証明書をよりインテリジェントに選択し、不要な証明書を送信することを避けることができます。
  7. x509.CertPool.Subjects() メソッドの追加 (src/pkg/crypto/x509/cert_pool.go):

    • CertPool 内のすべての証明書のDERエンコードされたサブジェクト(識別名)のリストを返すヘルパーメソッドが追加されました。これは、サーバーが certificateRequestMsgcertificateAuthorities フィールドを構築する際に利用されます。
  8. テストの拡充 (src/pkg/crypto/tls/handshake_server_test.go):

    • ClientAuthType の各設定を網羅する新しいテストケース clientauthTests が追加され、様々なクライアント認証シナリオ(証明書なし、証明書あり、検証あり/なしなど)がテストされるようになりました。
    • これにより、クライアント認証機能の信頼性と正確性が大幅に向上しました。

これらの変更は、Go言語の crypto/tls パッケージにおけるクライアント認証の機能とセキュリティを大幅に強化し、より複雑な認証要件を持つアプリケーションでの利用を可能にしました。

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

このコミットにおける主要なコード変更箇所は以下の通りです。

  1. src/pkg/crypto/tls/common.go:

    • ClientAuthType 列挙型の追加:
      type ClientAuthType int
      
      const (
      	NoClientCert ClientAuthType = iota
      	RequestClientCert
      	RequireAnyClientCert
      	VerifyClientCertIfGiven
      	RequireAndVerifyClientCert
      )
      
    • Config 構造体の変更:
      --- a/src/pkg/crypto/tls/common.go
      +++ b/src/pkg/crypto/tls/common.go
      @@ -148,11 +160,14 @@ type Config struct {
       	// hosting.
       	ServerName string
       
      -	// AuthenticateClient controls whether a server will request a certificate
      -	// from the client. It does not require that the client send a
      -	// certificate nor does it require that the certificate sent be
      -	// anything more than self-signed.
      -	AuthenticateClient bool
      +	// ClientAuth determines the server's policy for
      +	// TLS Client Authentication. The default is NoClientCert.
      +	ClientAuth ClientAuthType
      +
      +	// ClientCAs defines the set of root certificate authorities
      +	// that servers use if required to verify a client certificate
      +	// by the policy in ClientAuth.
      +	ClientCAs *x509.CertPool
       
       	// InsecureSkipVerify controls whether a client verifies the
       	// server's certificate chain and host name.
      
  2. src/pkg/crypto/tls/handshake_messages.go:

    • certificateRequestMsgmarshal() メソッドにおける長さ計算の修正:
      --- a/src/pkg/crypto/tls/handshake_messages.go
      +++ b/src/pkg/crypto/tls/handshake_messages.go
      @@ -881,9 +881,11 @@ func (m *certificateRequestMsg) marshal() (x []byte) {
       
       	// See http://tools.ietf.org/html/rfc4346#section-7.4.4
       	length := 1 + len(m.certificateTypes) + 2
      +	casLength := 0
       	for _, ca := range m.certificateAuthorities {
      -		length += 2 + len(ca)
      +		casLength += 2 + len(ca)
       	}
      +	length += casLength
       
       	x = make([]byte, 4+length)
       	x[0] = typeCertificateRequest
      @@ -895,10 +897,8 @@ func (m *certificateRequestMsg) marshal() (x []byte) {
       
       	copy(x[5:], m.certificateTypes)
       	y := x[5+len(m.certificateTypes):]
      -\n-	numCA := len(m.certificateAuthorities)\n-\ty[0] = uint8(numCA >> 8)\n-\ty[1] = uint8(numCA)\n+\ty[0] = uint8(casLength >> 8)
      +\ty[1] = uint8(casLength)
       	y = y[2:]
       	for _, ca := range m.certificateAuthorities {
       		y[0] = uint8(len(ca) >> 8)
      
    • certificateRequestMsgunmarshal() メソッドにおける解析ロジックの修正:
      --- a/src/pkg/crypto/tls/handshake_messages.go
      +++ b/src/pkg/crypto/tls/handshake_messages.go
      @@ -937,31 +936,34 @@ func (m *certificateRequestMsg) unmarshal(data []byte) bool {
       	}
       
       	data = data[numCertTypes:]
      +\n         	if len(data) < 2 {
       		return false
       	}
      -\n-	numCAs := uint16(data[0])<<16 | uint16(data[1])
      +	casLength := uint16(data[0])<<8 | uint16(data[1])
       	data = data[2:]
      +	if len(data) < int(casLength) {
      +		return false
      +	}
      +	cas := make([]byte, casLength)
      +	copy(cas, data)
      +	data = data[casLength:]
       
      -	m.certificateAuthorities = make([][]byte, numCAs)
      -	for i := uint16(0); i < numCAs; i++ {
      -		if len(data) < 2 {
      +	m.certificateAuthorities = nil
      +	for len(cas) > 0 {
      +		if len(cas) < 2 {
       			return false
       		}
      -		caLen := uint16(data[0])<<16 | uint16(data[1])
      -
      -		data = data[2:]
      -		if len(data) < int(caLen) {
      +		caLen := uint16(cas[0])<<8 | uint16(cas[1])
      +		cas = cas[2:]
      +
      +		if len(cas) < int(caLen) {
       			return false
       		}
       
      -		ca := make([]byte, caLen)
      -		copy(ca, data)
      -		m.certificateAuthorities[i] = ca
      -		data = data[caLen:]
      +		m.certificateAuthorities = append(m.certificateAuthorities, cas[:caLen])
      +		cas = cas[caLen:]
       	}
      -\n         	if len(data) > 0 {
       		return false
       	}
      
  3. src/pkg/crypto/tls/handshake_server.go:

    • サーバーがクライアント認証を要求するロジックの変更と ClientCAs の利用:
      --- a/src/pkg/crypto/tls/handshake_server.go
      +++ b/src/pkg/crypto/tls/handshake_server.go
      @@ -150,14 +150,19 @@ FindCipherSuite:
       		c.writeRecord(recordTypeHandshake, skx.marshal())
       	}
       
      -	if config.AuthenticateClient {
      +	if config.ClientAuth >= RequestClientCert {
       		// Request a client certificate
       		certReq := new(certificateRequestMsg)
       		certReq.certificateTypes = []byte{certTypeRSASign}
      +\n         		// An empty list of certificateAuthorities signals to
       		// the client that it may send any certificate in response
      -		// to our request.\n-\n+		// to our request. When we know the CAs we trust, then
      +		// we can send them down, so that the client can choose
      +		// an appropriate certificate to give to us.
      +		if config.ClientCAs != nil {
      +			certReq.certificateAuthorities = config.ClientCAs.Subjects()
      +		}
       		finishedHash.Write(certReq.marshal())
       		c.writeRecord(recordTypeHandshake, certReq.marshal())
       	}
      @@ -166,52 +171,87 @@ FindCipherSuite:
       	finishedHash.Write(helloDone.marshal())
       	c.writeRecord(recordTypeHandshake, helloDone.marshal())
       
      -	var pub *rsa.PublicKey
      -	if config.AuthenticateClient {
      -		// Get client certificate
      -		msg, err = c.readHandshake()\n-		if err != nil {\n-			return err
      -		}
      -		certMsg, ok = msg.(*certificateMsg)
      -		if !ok {\n-			return c.sendAlert(alertUnexpectedMessage)
      +	var pub *rsa.PublicKey // public key for client auth, if any
      +
      +	msg, err = c.readHandshake()
      +	if err != nil {
      +		return err
      +	}
      +
      +	// If we requested a client certificate, then the client must send a
      +	// certificate message, even if it's empty.
      +	if config.ClientAuth >= RequestClientCert {
      +		if certMsg, ok = msg.(*certificateMsg); !ok {
      +			return c.sendAlert(alertHandshakeFailure)
       		}
       		finishedHash.Write(certMsg.marshal())
       
      +		if len(certMsg.certificates) == 0 {
      +			// The client didn't actually send a certificate
      +			switch config.ClientAuth {
      +			case RequireAnyClientCert, RequireAndVerifyClientCert:
      +				c.sendAlert(alertBadCertificate)
      +				return errors.New("tls: client didn't provide a certificate")
      +			}
      +		}
      +
       		certs := make([]*x509.Certificate, len(certMsg.certificates))
       		for i, asn1Data := range certMsg.certificates {
      -			cert, err := x509.ParseCertificate(asn1Data)
      -			if err != nil {\n-				c.sendAlert(alertBadCertificate)
      -				return errors.New("could not parse client's certificate: " + err.Error())
      +			if certs[i], err = x509.ParseCertificate(asn1Data); err != nil {
      +				c.sendAlert(alertBadCertificate)
      +				return errors.New("tls: failed to parse client certificate: " + err.Error())
       			}
      -			certs[i] = cert
       		}
       
      -		// TODO(agl): do better validation of certs: max path length, name restrictions etc.\n-		for i := 1; i < len(certs); i++ {\n-			if err := certs[i-1].CheckSignatureFrom(certs[i]); err != nil {\n-				c.sendAlert(alertBadCertificate)
      -				return errors.New("could not validate certificate signature: " + err.Error())
      +		if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 {
      +			opts := x509.VerifyOptions{
      +				Roots:         c.config.ClientCAs,
      +				CurrentTime:   c.config.time(),
      +				Intermediates: x509.NewCertPool(),
      +			}
      +
      +			for i, cert := range certs {
      +				if i == 0 {
      +					continue
      +				}
      +				opts.Intermediates.AddCert(cert)
      
  •   	}
    
  •   	chains, err := certs[0].Verify(opts)
    
  •   	if err != nil {
    
  •   		c.sendAlert(alertBadCertificate)
    
  •   		return errors.New("tls: failed to verify client's certificate: " + err.Error())
    
  •   	}
    
  •   	ok := false
    
  •   	for _, ku := range certs[0].ExtKeyUsage {
    
  •   		if ku == x509.ExtKeyUsageClientAuth {
    
  •   			ok = true
    
  •   			break
    
  •   		}
    
  •   	}
    
  •   	if !ok {
    
  •   		c.sendAlert(alertHandshakeFailure)
    
  •   		return errors.New("tls: client's certificate's extended key usage doesn't permit it to be used for client authentication")
    
  •   	}
    
  •   	c.verifiedChains = chains
       		}
       
       		if len(certs) > 0 {
      -			key, ok := certs[0].PublicKey.(*rsa.PublicKey)
      -			if !ok {\n-				return c.sendAlert(alertUnsupportedCertificate)
      +			if pub, ok = certs[0].PublicKey.(*rsa.PublicKey); !ok {
      +				return c.sendAlert(alertUnsupportedCertificate)
       			}
      -			pub = key
       			c.peerCertificates = certs
       		}
      +\n+		msg, err = c.readHandshake()
      +		if err != nil {
      +			return err
      +		}
       	}
       
       	// Get client key exchange
      -	msg, err = c.readHandshake()\n-	if err != nil {\n-		return err
      -	}
       	ckx, ok := msg.(*clientKeyExchangeMsg)
       	if !ok {
       		return c.sendAlert(alertUnexpectedMessage)
      ```
    
  1. src/pkg/crypto/tls/handshake_client.go:

    • クライアントの証明書選択ロジックの改善:
      --- a/src/pkg/crypto/tls/handshake_client.go
      +++ b/src/pkg/crypto/tls/handshake_client.go
      @@ -162,10 +164,23 @@ func (c *Conn) clientHandshake() error {
       		}
       	}\n         
      -	transmitCert := false
      +	var certToSend *Certificate
       	certReq, ok := msg.(*certificateRequestMsg)
       	if ok {
      -		// We only accept certificates with RSA keys.\n+		// RFC 4346 on the certificateAuthorities field:
      +		// A list of the distinguished names of acceptable certificate
      +		// authorities. These distinguished names may specify a desired
      +		// distinguished name for a root CA or for a subordinate CA;
      +		// thus, this message can be used to describe both known roots
      +		// and a desired authorization space. If the
      +		// certificate_authorities list is empty then the client MAY
      +		// send any certificate of the appropriate
      +		// ClientCertificateType, unless there is some external
      +		// arrangement to the contrary.
      +
      +		finishedHash.Write(certReq.marshal())
      +
      +		// For now, we only know how to sign challenges with RSA
       		rsaAvail := false
       		for _, certType := range certReq.certificateTypes {
       			if certType == certTypeRSASign {
      @@ -174,23 +189,41 @@ func (c *Conn) clientHandshake() error {
       			}
       		}
       
      -		// For now, only send a certificate back if the server gives us an
      -		// empty list of certificateAuthorities.\n-		//\n-		// RFC 4346 on the certificateAuthorities field:
      -		// A list of the distinguished names of acceptable certificate
      -		// authorities.  These distinguished names may specify a desired
      -		// distinguished name for a root CA or for a subordinate CA; thus,\n-		// this message can be used to describe both known roots and a\n-		// desired authorization space.  If the certificate_authorities\n-		// list is empty then the client MAY send any certificate of the\n-		// appropriate ClientCertificateType, unless there is some\n-		// external arrangement to the contrary.\n-		if rsaAvail && len(certReq.certificateAuthorities) == 0 {\n-			transmitCert = true
      -		}\n+\n+		// We need to search our list of client certs for one
      +		// where SignatureAlgorithm is RSA and the Issuer is in
      +		// certReq.certificateAuthorities
      +	findCert:
      +		for i, cert := range c.config.Certificates {
      +			if !rsaAvail {
      +				continue
      +			}
      +
      +			leaf := cert.Leaf
      +			if leaf == nil {
      +				if leaf, err = x509.ParseCertificate(cert.Certificate[0]); err != nil {
      +					c.sendAlert(alertInternalError)
      +					return errors.New("tls: failed to parse client certificate #" + strconv.Itoa(i) + ": " + err.Error())
      +				}
      +			}
      +
      +			if leaf.PublicKeyAlgorithm != x509.RSA {
      +				continue
      +			}
      +
      +			if len(certReq.certificateAuthorities) == 0 {
      +				// they gave us an empty list, so just take the
      +				// first RSA cert from c.config.Certificates
      +				certToSend = &cert
      +				break
      +			}
      +
      +			for _, ca := range certReq.certificateAuthorities {
      +				if bytes.Equal(leaf.RawIssuer, ca) {
      +					certToSend = &cert
      +					break findCert
      +				}
      +			}
      +		}
       
      -		finishedHash.Write(certReq.marshal())
       
       		msg, err = c.readHandshake()
       		if err != nil {
      @@ -204,17 +237,9 @@ func (c *Conn) clientHandshake() error {
       	}
       	finishedHash.Write(shd.marshal())
       
      -	var cert *x509.Certificate
      -	if transmitCert {\n+	if certToSend != nil {
       		certMsg = new(certificateMsg)
      -		if len(c.config.Certificates) > 0 {\n-			cert, err = x509.ParseCertificate(c.config.Certificates[0].Certificate[0])
      -			if err == nil && cert.PublicKeyAlgorithm == x509.RSA {\n-				certMsg.certificates = c.config.Certificates[0].Certificate
      -			} else {\n-				cert = nil
      -			}
      -		}\n+		certMsg.certificates = certToSend.Certificate
       		finishedHash.Write(certMsg.marshal())
       		c.writeRecord(recordTypeHandshake, certMsg.marshal())
       	}
      @@ -229,7 +254,7 @@ func (c *Conn) clientHandshake() error {
       		c.writeRecord(recordTypeHandshake, ckx.marshal())
       	}
       
      -	if cert != nil {\n+	if certToSend != nil {
       		certVerify := new(certificateVerifyMsg)
       		digest := make([]byte, 0, 36)
       		digest = finishedHash.serverMD5.Sum(digest)
      
  2. src/pkg/crypto/x509/cert_pool.go:

    • Subjects() メソッドの追加:
      --- a/src/pkg/crypto/x509/cert_pool.go
      +++ b/src/pkg/crypto/x509/cert_pool.go
      @@ -101,3 +101,13 @@ func (s *CertPool) AppendCertsFromPEM(pemCerts []byte) (ok bool) {
       
       	return
       }\n+\n+// Subjects returns a list of the DER-encoded subjects of
      

+// all of the certificates in the pool. +func (s *CertPool) Subjects() (res [][]byte) {

  • res = make([][]byte, len(s.certs))
  • for i, c := range s.certs {
  •   res[i] = c.RawSubject
    
  • }
  • return +} ```

コアとなるコードの解説

上記のコード変更は、TLSクライアント認証の機能性、セキュリティ、および柔軟性を向上させるために連携して機能します。

  1. ClientAuthType 列挙型と Config 構造体の変更:

    • common.go で導入された ClientAuthType は、サーバーがクライアント証明書をどのように扱うべきかについて、以前の単純なオン/オフスイッチ (AuthenticateClient) よりもはるかに詳細な制御を提供します。これにより、開発者はアプリケーションのセキュリティ要件に合わせて、クライアント認証の厳格さを正確に指定できるようになります。
    • ClientCAs *x509.CertPool の追加は、サーバーがクライアント証明書を検証する際に、どの認証局を信頼するかを明示的に指定できるようになったことを意味します。これは、特定の組織内のクライアントのみを許可するなどのシナリオで非常に重要です。
  2. certificateRequestMsg のマーシャリング/アンマーシャリングの修正:

    • handshake_messages.go でのこの修正は、TLSプロトコルの仕様(RFC 4346)に厳密に準拠するためのものです。以前のバグは、certificateAuthorities フィールドの長さの解釈を誤っていたため、異なるTLS実装間で互換性の問題を引き起こす可能性がありました。この修正により、GoのTLS実装が他のTLS実装と正しく相互運用できるようになり、クライアント認証の信頼性が向上します。
  3. サーバーサイドでのクライアント認証要求と検証の改善:

    • handshake_server.go の変更により、サーバーは ClientAuthType の設定に基づいて、クライアント証明書を要求するかどうか、そしてその要求の厳格さを決定します。
    • 特に重要なのは、サーバーが ClientCAs.Subjects() を使用して、信頼するCAのリストを certificateRequestMsg に含めるようになった点です。これにより、クライアントはサーバーがどの証明書を期待しているかを事前に知ることができ、適切な証明書を提示する可能性が高まります。
    • クライアント証明書の検証ロジックが x509.VerifyOptions を使用して強化されたことで、サーバーは証明書の有効性(有効期限、署名チェーン)だけでなく、その証明書がクライアント認証の目的で発行されたものであるか(ExtKeyUsageClientAuth)も確認できるようになりました。これにより、セキュリティが大幅に向上し、誤ってサーバー証明書がクライアント認証に使用されるなどの問題を防止できます。
  4. クライアントサイドでの証明書選択ロジックの改善:

    • handshake_client.go の変更は、クライアントがサーバーの要求に「賢く」応答できるようにします。以前は、サーバーがCAリストを提示しない場合にのみ証明書を送信するような単純なロジックでしたが、新しいロジックでは、サーバーが提示したCAリスト(certReq.certificateAuthorities)と自身の証明書の発行者(leaf.RawIssuer)を比較して、最適な証明書を選択します。
    • この findCert ループは、クライアントが複数の証明書を持っている場合に、サーバーが信頼するCAに合致する証明書を優先的に選択することを可能にし、クライアント認証の成功率を高めます。
  5. x509.CertPool.Subjects() メソッドの追加:

    • cert_pool.go に追加されたこのユーティリティメソッドは、ClientCAs からサーバーがクライアントに送信するCAリストを効率的に抽出するために使用されます。これは、コードの簡潔性と再利用性を向上させます。

これらの変更は全体として、Goの crypto/tls パッケージが提供するクライアント認証機能の堅牢性、設定の柔軟性、および相互運用性を大幅に向上させ、より多様なセキュリティ要件を持つアプリケーションでの利用を可能にしています。

関連リンク

参考にした情報源リンク

  • Go言語のコミットデータ (./commit_data/11031.txt)
  • GitHub上のコミットページ: https://github.com/golang/go/commit/c581ec4918ba7fc92e991afdb6f7dd4ccfb31124
  • TLS 1.1 仕様 (RFC 4346) - 特に certificateRequest メッセージの構造に関するセクション
  • Go言語の crypto/tls および crypto/x509 パッケージのドキュメント (一般的な概念理解のため)
  • Google検索 (golang/go issue 2521) - ただし、直接関連する情報は得られませんでした。