77 "crypto/x509"
88 "encoding/asn1"
99 "encoding/hex"
10+ "encoding/pem"
1011 "fmt"
12+ "os"
1113 "reflect"
1214 "slices"
1315 "time"
@@ -21,10 +23,18 @@ const (
2123 Format = "apple-appattest"
2224)
2325
24- type VerifyAttestationInputStateless struct {
26+ var (
27+ NonceOID = asn1.ObjectIdentifier {1 , 2 , 840 , 113635 , 100 , 8 , 2 }
28+ AAGUIDProd = Environment ("appattest\x00 \x00 \x00 \x00 \x00 \x00 \x00 " )
29+ AAGUIDDev = Environment ("appattestdevelop" )
30+ )
31+
32+ type Environment = []byte
33+
34+ type VerifyAttestationInputPure struct {
2535 AttestationInput * VerifyAttestationInput
2636 Time time.Time
27- AARoots * x509.CertPool
37+ AARoots [] * x509.Certificate
2838}
2939
3040type VerifyAttestationOutput struct {
@@ -33,15 +43,16 @@ type VerifyAttestationOutput struct {
3343
3444 EnvironmentGUID Environment
3545 BundleDigest []byte
46+ KeyID []byte
3647}
3748
3849// AttestedPubkey returns the key from the leaf certificate
3950func (o * VerifyAttestationOutput ) AttestedPubkey () * ecdsa.PublicKey {
4051 return o .LeafCert .PublicKey .(* ecdsa.PublicKey )
4152}
4253
43- // VerifyAttestationStateless performs attestation without the guardrails provided by AppAttestImpl.
44- func VerifyAttestationStateless (in * VerifyAttestationInputStateless ) (VerifyAttestationOutput , error ) {
54+ // VerifyAttestationPure performs attestation without the guardrails provided by AppAttestImpl.
55+ func VerifyAttestationPure (in * VerifyAttestationInputPure ) (VerifyAttestationOutput , error ) {
4556 // unmarshal the attestation object
4657 attestObj := AttestationObject {}
4758 err := cbor .Unmarshal (in .AttestationInput .AttestationCBOR , & attestObj )
@@ -54,24 +65,30 @@ func VerifyAttestationStateless(in *VerifyAttestationInputStateless) (VerifyAtte
5465 return VerifyAttestationOutput {}, fmt .Errorf ("attestation object format mismatch: expected '%s', got '%s'" , Format , attestObj .Format )
5566 }
5667
57- // create a new cert verifier using the intermediates provided in the attestation object
58- verifyOpts := x509.VerifyOptions {}
59- if err := populateVerifyOpts (& verifyOpts , & attestObj , in .AARoots ); err != nil {
60- return VerifyAttestationOutput {}, fmt .Errorf ("populating verify opts: %w" , err )
68+ // Parse certificate chain from bytes to certificates
69+ chain := make ([]* x509.Certificate , len (attestObj .AttestationStatement .X509CertChain ))
70+ for i , certBytes := range attestObj .AttestationStatement .X509CertChain {
71+ cert , err := x509 .ParseCertificate (certBytes )
72+ if err != nil {
73+ return VerifyAttestationOutput {}, fmt .Errorf ("parsing certificate at index %d: %w" , i , err )
74+ }
75+ chain [i ] = cert
6176 }
62- verifyOpts .CurrentTime = in .Time
6377
64- // parse the leaf certificate
65- leafCert , err := x509 .ParseCertificate (attestObj .AttestationStatement .X509CertChain [0 ])
66- if err != nil {
67- return VerifyAttestationOutput {}, fmt .Errorf ("parsing leaf certificate: %w" , err )
78+ if err := VerifyChain (chain , in .AARoots ); err != nil {
79+ return VerifyAttestationOutput {}, fmt .Errorf ("verifying certificate chain: %w" , err )
6880 }
6981
70- // verify the leaf certificate
71- _ , err = leafCert .Verify (verifyOpts )
72- if err != nil {
73- return VerifyAttestationOutput {}, fmt .Errorf ("verifying leaf certificate: %w" , err )
82+ // get the leaf certificate (first in chain)
83+ leafCert := chain [0 ]
84+ pblock := pem.Block {
85+ Type : "CERTIFICATE" ,
86+ Bytes : leafCert .Raw ,
7487 }
88+ if err := pem .Encode (os .Stdout , & pblock ); err != nil {
89+ panic (err )
90+ }
91+ fmt .Println ()
7592
7693 // > 2. Create clientDataHash as the SHA256 hash of the one-time challenge your server sends
7794 // > to your app before performing the attestation,
@@ -102,11 +119,6 @@ func VerifyAttestationStateless(in *VerifyAttestationInputStateless) (VerifyAtte
102119
103120 computedPubkeyHash := ComputeKeyHash (certPubKey )
104121
105- // assert that the public key of the leaf certificate matches the key handle returned by the app
106- if ! bytes .Equal (in .AttestationInput .KeyIdentifier , computedPubkeyHash [:]) {
107- return VerifyAttestationOutput {}, fmt .Errorf ("key identifier did not match public key of leaf certificate: %s != %s" , hex .EncodeToString (computedPubkeyHash [:]), hex .EncodeToString (in .AttestationInput .KeyIdentifier ))
108- }
109-
110122 authenticatorData := in .AttestationInput .OutAuthenticatorData
111123 if authenticatorData == nil {
112124 authenticatorData = & authenticatordata.T {}
@@ -117,7 +129,7 @@ func VerifyAttestationStateless(in *VerifyAttestationInputStateless) (VerifyAtte
117129 }
118130
119131 // > 9. Verify that the authenticator data’s credentialId field is the same as the key identifier.
120- if ! bytes .Equal (in . AttestationInput . KeyIdentifier , authenticatorData .AttestedCredentialData .CredentialID ) {
132+ if ! bytes .Equal (computedPubkeyHash [:] , authenticatorData .AttestedCredentialData .CredentialID ) {
121133 return VerifyAttestationOutput {}, fmt .Errorf ("key identifier did not match attested credential id of authenticator data" )
122134 }
123135
@@ -127,29 +139,10 @@ func VerifyAttestationStateless(in *VerifyAttestationInputStateless) (VerifyAtte
127139
128140 EnvironmentGUID : authenticatorData .AttestedCredentialData .AAGUID ,
129141 BundleDigest : authenticatorData .RelayingPartyHash ,
142+ KeyID : computedPubkeyHash [:],
130143 }, nil
131144}
132145
133- func populateVerifyOpts (dst * x509.VerifyOptions , attObj * AttestationObject , aaroots * x509.CertPool ) (err error ) {
134- if len (attObj .AttestationStatement .X509CertChain ) < 1 {
135- return errors .New ("expected at least one certificate in x509 cert chain" )
136- }
137-
138- // set the intermediates
139- dst .Intermediates = x509 .NewCertPool ()
140- // skip the first element, it's the leaf certificate
141- for _ , inter := range attObj .AttestationStatement .X509CertChain [1 :] {
142- cert , err := x509 .ParseCertificate (inter )
143- if err != nil {
144- return errors .Wrap (err , "parsing intermediate" )
145- }
146- dst .Intermediates .AddCert (cert )
147- dst .Roots = aaroots
148- }
149-
150- return nil
151- }
152-
153146func extractNonceFromCert (c * x509.Certificate ) ([]byte , error ) {
154147 var oidValue []byte
155148 for _ , ext := range c .Extensions {
@@ -216,11 +209,3 @@ func ComputeNonce(authData, clientDataHash []byte) (res [sha256.Size]byte, err e
216209func ComputeKeyHash (key * ecdsa.PublicKey ) [sha256 .Size ]byte {
217210 return sha256 .Sum256 (ellipticPointToX962Uncompressed (key ))
218211}
219-
220- type Environment = []byte
221-
222- var (
223- NonceOID = asn1.ObjectIdentifier {1 , 2 , 840 , 113635 , 100 , 8 , 2 }
224- AAGUIDProd = Environment ("appattest\x00 \x00 \x00 \x00 \x00 \x00 \x00 " )
225- AAGUIDDev = Environment ("appattestdevelop" )
226- )
0 commit comments