go-webauthnを使っていたところ以下のエラーに遭遇した。
Unable to validate attestation signature statement during attestation validation: invalid certificate chain from MDS: x509: unhandled critical extension
ここでエラーを追跡してみた。
前半の文面からここがエラーだとわかる。どうやら証明書の検証エラーのようだ https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation.go#L257-L259
ここで検証に使っているx5c
の出どころはここ
https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation.go#L240-L250
x5cs
の出どころはここ
https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation.go#L169
formatHandler
の出どころであるattestationRegistry
に登録されているのはRegisterAttestationFormat
関数
https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation.go#L87-L89
今回は Windows Hello の tpm 形式であるため
tpm のハンドラーがRegisterAttestationFormat
で登録されているのはここで、formatHandler
の実態はverifyTPMFormat
だとわかる
https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation_tpm.go#L18-L20
verifyTPMFormat 内では証明書(x5cs)がある場合、独自に extension を解析しているようだ Subject Alternative Name extension https://github.com/go-webauthn/webauthn/blob/9ca2faef6e4bbc88bfbaaccca846ee420b142e17/protocol/attestation_tpm.go#L175-L182 Extended Key Usage https://github.com/go-webauthn/webauthn/blob/9ca2faef6e4bbc88bfbaaccca846ee420b142e17/protocol/attestation_tpm.go#L199 Basic Constraints extension https://github.com/go-webauthn/webauthn/blob/9ca2faef6e4bbc88bfbaaccca846ee420b142e17/protocol/attestation_tpm.go#L222
なお標準では以下の processExtensions 関数でハンドリングされる https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/parser.go#L664
ここでデバッグ用のサーバーを立ち上げ tpm 形式の attestation object を取得してその中からx5c
にあたる証明書をダンプして、
python でをパースして x509 証明書の extension を確認したところ以下のようであった。
Signature Algorithm: <ObjectIdentifier(oid=1.2.840.113549.1.1.11, name=sha256WithRSAEncryption)>
Extensions:
Type: <ObjectIdentifier(oid=2.5.29.15, name=keyUsage)>
Value: <KeyUsage(digital_signature=True, content_commitment=False, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False, decipher_only=False)>
Critical: True
Type: <ObjectIdentifier(oid=2.5.29.19, name=basicConstraints)>
Value: <BasicConstraints(ca=False, path_length=None)>
Critical: True
Type: <ObjectIdentifier(oid=2.5.29.32, name=certificatePolicies)>
Value: <CertificatePolicies([<PolicyInformation(policy_identifier=<ObjectIdentifier(oid=1.3.6.1.4.1.311.21.31, name=Unknown OID)>, policy_qualifiers=[<UserNotice(notice_reference=None, explicit_text='TCPA Trusted Platform Identity')>])>])>
Critical: True
Type: <ObjectIdentifier(oid=2.5.29.37, name=extendedKeyUsage)>
Value: <ExtendedKeyUsage([<ObjectIdentifier(oid=2.23.133.8.3, name=Unknown OID)>])>
Critical: False
Type: <ObjectIdentifier(oid=2.5.29.17, name=subjectAltName)>
Value: <SubjectAlternativeName(<GeneralNames([<DirectoryName(value=<Name(2.23.133.2.1=id:494E5443,2.23.133.2.2=ADL,2.23.133.2.3=id:02580012)>)>])>)>
Critical: True
Type: <ObjectIdentifier(oid=2.5.29.35, name=authorityKeyIdentifier)>
Value: <AuthorityKeyIdentifier(key_identifier=b'0\xa3\x12\xbc\x94$q\xc8\xcb#\x9c\xf3\xc2,@N\x19\x1e\x8f\xa9', authority_cert_issuer=None, authority_cert_serial_number=None)>
Critical: False
Type: <ObjectIdentifier(oid=2.5.29.14, name=subjectKeyIdentifier)>
Value: <SubjectKeyIdentifier(digest=b'\xab#\xb6\xd1z\xb92\x16\xd3\xcf\x92%\xa1\xdct\xd2\x96\x961t')>
Critical: False
Type: <ObjectIdentifier(oid=1.3.6.1.5.5.7.1.1, name=authorityInfoAccess)>
Value: <AuthorityInformationAccess([<AccessDescription(access_method=<ObjectIdentifier(oid=1.3.6.1.5.5.7.48.2, name=caIssuers)>, access_location=<UniformResourceIdentifier(value='http://azcsprodncuaikpublish.blob.core.windows.net/ncu-intc-keyid-134d03d6581dabea3bf82eb2e34bf98192826962/e069c79b-9b7d-4ae6-afc6-23d396df1a4c.cer')>)>])>
Critical: False
比較してみるとわかるのだが実は標準でハンドリングされるものばかりなのである。 では何が原因かとデバッガを使って調べてみると以下のコードに到達した。
if len(out.DNSNames) == 0 && len(out.EmailAddresses) == 0 && len(out.IPAddresses) == 0 && len(out.URIs) == 0 {
// If we didn't parse anything then we do the critical check, below.
unhandled = true
}
つまり証明書がドメイン名にもメールアドレスにも IP アドレスにも URI にも関連していないのでハンドリングされない扱いになっているのであった。 ちなみに tpm 用の独自のロジックのほうでは tpm 用のメタデータ(manufacturer,model,version)などが解析されていた https://github.com/go-webauthn/webauthn/blob/9ca2faef6e4bbc88bfbaaccca846ee420b142e17/protocol/attestation_tpm.go#L320-L328
そしてこの unhandled フラグにより以下で UnhandledCriticalExtensions が設定されえる。 https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/parser.go#L819-L821
そしてこの箇所で UnhandledCriticalExtensions との比較が行われエラーが発生する。 https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/verify.go#L565
なお UnhandledCriticalExtension エラー型の定義は以下でありこれはx509: unhandled critical extension
の文面に一致する。
https://github.com/golang/go/blob/72735094660a475a69050b7368c56b25346f5406/src/crypto/x509/x509.go#L969-L973
なおこの一連の検証を行うかは GetValidateTrustAnchor によって判定されている。 https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/protocol/attestation.go#L229
GetValidateTrustAnchor の実態は anchors である。 https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/metadata/providers/memory/provider.go#L71-72
anchors はここでデフォルトの true に設定され https://github.com/go-webauthn/webauthn/blob/cf1758ab00a77bbe97e9315946f78e6874a8d708/metadata/providers/memory/provider.go#L17
ここのオプションで変更可能であると思われ https://github.com/go-webauthn/webauthn/blob/master/metadata/providers/memory/provider.go#L23
そしてこの関数をオプションとして与えることで検証をスキップさせとりあえずの成功にはできそうである。 https://github.com/go-webauthn/webauthn/blob/master/metadata/providers/memory/options.go#L44
またもうひとつの手段としてはブラウザの publicKey オブジェクトに渡す attestation プロパティの値を none に設定することで tpm 形式の証明書ではないものにするという手もある。
https://developer.mozilla.org/ja/docs/Web/API/CredentialsContainer/create#attestation
だが検証できるのに検証できないのはもどかしいので Issue を立てた。どうなるかは知らない。
https://github.com/go-webauthn/webauthn/issues/275