You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

attestation_apple.go 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package protocol
  2. import (
  3. "bytes"
  4. "crypto/ecdsa"
  5. "crypto/elliptic"
  6. "crypto/sha256"
  7. "crypto/x509"
  8. "encoding/asn1"
  9. "fmt"
  10. "math/big"
  11. "github.com/duo-labs/webauthn/protocol/webauthncose"
  12. )
  13. var appleAttestationKey = "apple"
  14. func init() {
  15. RegisterAttestationFormat(appleAttestationKey, verifyAppleKeyFormat)
  16. }
  17. // From §8.8. https://www.w3.org/TR/webauthn-2/#sctn-apple-anonymous-attestation
  18. // The apple attestation statement looks like:
  19. // $$attStmtType //= (
  20. // fmt: "apple",
  21. // attStmt: appleStmtFormat
  22. // )
  23. // appleStmtFormat = {
  24. // x5c: [ credCert: bytes, * (caCert: bytes) ]
  25. // }
  26. func verifyAppleKeyFormat(att AttestationObject, clientDataHash []byte) (string, []interface{}, error) {
  27. // Step 1. Verify that attStmt is valid CBOR conforming to the syntax defined
  28. // above and perform CBOR decoding on it to extract the contained fields.
  29. // If x5c is not present, return an error
  30. x5c, x509present := att.AttStatement["x5c"].([]interface{})
  31. if !x509present {
  32. // Handle Basic Attestation steps for the x509 Certificate
  33. return appleAttestationKey, nil, ErrAttestationFormat.WithDetails("Error retreiving x5c value")
  34. }
  35. credCertBytes, valid := x5c[0].([]byte)
  36. if !valid {
  37. return appleAttestationKey, nil, ErrAttestation.WithDetails("Error getting certificate from x5c cert chain")
  38. }
  39. credCert, err := x509.ParseCertificate(credCertBytes)
  40. if err != nil {
  41. return appleAttestationKey, nil, ErrAttestationFormat.WithDetails(fmt.Sprintf("Error parsing certificate from ASN.1 data: %+v", err))
  42. }
  43. // Step 2. Concatenate authenticatorData and clientDataHash to form nonceToHash.
  44. nonceToHash := append(att.RawAuthData, clientDataHash...)
  45. // Step 3. Perform SHA-256 hash of nonceToHash to produce nonce.
  46. nonce := sha256.Sum256(nonceToHash)
  47. // Step 4. Verify that nonce equals the value of the extension with OID 1.2.840.113635.100.8.2 in credCert.
  48. var attExtBytes []byte
  49. for _, ext := range credCert.Extensions {
  50. if ext.Id.Equal([]int{1, 2, 840, 113635, 100, 8, 2}) {
  51. attExtBytes = ext.Value
  52. }
  53. }
  54. if len(attExtBytes) == 0 {
  55. return appleAttestationKey, nil, ErrAttestationFormat.WithDetails("Attestation certificate extensions missing 1.2.840.113635.100.8.2")
  56. }
  57. decoded := AppleAnonymousAttestation{}
  58. _, err = asn1.Unmarshal([]byte(attExtBytes), &decoded)
  59. if err != nil {
  60. return appleAttestationKey, nil, ErrAttestationFormat.WithDetails("Unable to parse apple attestation certificate extensions")
  61. }
  62. if !bytes.Equal(decoded.Nonce, nonce[:]) || err != nil {
  63. return appleAttestationKey, nil, ErrInvalidAttestation.WithDetails("Attestation certificate does not contain expected nonce")
  64. }
  65. // Step 5. Verify that the credential public key equals the Subject Public Key of credCert.
  66. // TODO: Probably move this part to webauthncose.go
  67. pubKey, err := webauthncose.ParsePublicKey(att.AuthData.AttData.CredentialPublicKey)
  68. if err != nil {
  69. return appleAttestationKey, nil, ErrInvalidAttestation.WithDetails(fmt.Sprintf("Error parsing public key: %+v\n", err))
  70. }
  71. credPK := pubKey.(webauthncose.EC2PublicKeyData)
  72. subjectPK := credCert.PublicKey.(*ecdsa.PublicKey)
  73. credPKInfo := &ecdsa.PublicKey{
  74. Curve: elliptic.P256(),
  75. X: big.NewInt(0).SetBytes(credPK.XCoord),
  76. Y: big.NewInt(0).SetBytes(credPK.YCoord),
  77. }
  78. if !credPKInfo.Equal(subjectPK) {
  79. return appleAttestationKey, nil, ErrInvalidAttestation.WithDetails("Certificate public key does not match public key in authData")
  80. }
  81. // Step 6. If successful, return implementation-specific values representing attestation type Anonymization CA and attestation trust path x5c.
  82. return appleAttestationKey, x5c, nil
  83. }
  84. // Apple has not yet publish schema for the extension(as of JULY 2021.)
  85. type AppleAnonymousAttestation struct {
  86. Nonce []byte `asn1:"tag:1,explicit"`
  87. }