@ -12,6 +12,7 @@ import (
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"net/url"
"testing"
@ -63,6 +64,7 @@ func generateKeyID(t *testing.T, pub crypto.PublicKey) []byte {
func mustAttestTPM ( t * testing . T , keyAuthorization string ) ( [ ] byte , crypto . Signer , * x509 . Certificate ) {
t . Helper ( )
aca , err := minica . New (
minica . WithName ( "TPM Testing" ) ,
minica . WithGetSignerFunc (
func ( ) ( crypto . Signer , error ) {
return keyutil . GenerateSigner ( "RSA" , "" , 2048 )
@ -133,7 +135,7 @@ func mustAttestTPM(t *testing.T, keyAuthorization string) ([]byte, crypto.Signer
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS A
"alg" : int64 ( - 257 ) , // RS 256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
@ -292,3 +294,448 @@ func Test_deviceAttest01ValidateWithTPMSimulator(t *testing.T) {
} )
}
}
func newBadAttestationStatementError ( msg string ) * Error {
return & Error {
Type : "urn:ietf:params:acme:error:badAttestationStatement" ,
Status : 400 ,
Err : errors . New ( msg ) ,
}
}
func newInternalServerError ( msg string ) * Error {
return & Error {
Type : "urn:ietf:params:acme:error:serverInternal" ,
Status : 500 ,
Err : errors . New ( msg ) ,
}
}
func Test_doTPMAttestationFormat ( t * testing . T ) {
ctx := context . Background ( )
aca , err := minica . New (
minica . WithName ( "TPM Testing" ) ,
minica . WithGetSignerFunc (
func ( ) ( crypto . Signer , error ) {
return keyutil . GenerateSigner ( "RSA" , "" , 2048 )
} ,
) ,
)
require . NoError ( t , err )
acaRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : aca . Root . Raw } )
// prepare simulated TPM and create an AK
stpm := newSimulatedTPM ( t )
eks , err := stpm . GetEKs ( context . Background ( ) )
require . NoError ( t , err )
ak , err := stpm . CreateAK ( context . Background ( ) , "first-ak" )
require . NoError ( t , err )
require . NotNil ( t , ak )
// extract the AK public key // TODO(hs): replace this when there's a simpler method to get the AK public key (e.g. ak.Public())
ap , err := ak . AttestationParameters ( context . Background ( ) )
require . NoError ( t , err )
akp , err := attest . ParseAKPublic ( attest . TPMVersion20 , ap . Public )
require . NoError ( t , err )
// create template and sign certificate for the AK public key
keyID := generateKeyID ( t , eks [ 0 ] . Public ( ) )
template := & x509 . Certificate {
Subject : pkix . Name {
CommonName : "testakcert" ,
} ,
PublicKey : akp . Public ,
URIs : [ ] * url . URL {
{ Scheme : "urn" , Opaque : "ek:sha256:" + base64 . StdEncoding . EncodeToString ( keyID ) } ,
} ,
}
akCert , err := aca . Sign ( template )
require . NoError ( t , err )
require . NotNil ( t , akCert )
templateMissingSAN := & x509 . Certificate {
Subject : pkix . Name {
CommonName : "testakcertmissingsan" ,
} ,
PublicKey : akp . Public ,
}
akCertMissingSAN , err := aca . Sign ( templateMissingSAN )
require . NoError ( t , err )
require . NotNil ( t , akCert )
// generate a JWK and the key authorization value
jwk , err := jose . GenerateJWK ( "EC" , "P-256" , "ES256" , "sig" , "" , 0 )
require . NoError ( t , err )
keyAuthorization , err := KeyAuthorization ( "token" , jwk )
require . NoError ( t , err )
// create a new key attested by the AK, while including
// the key authorization bytes as qualifying data.
keyAuthSum := sha256 . Sum256 ( [ ] byte ( keyAuthorization ) )
config := tpm . AttestKeyConfig {
Algorithm : "RSA" ,
Size : 2048 ,
QualifyingData : keyAuthSum [ : ] ,
}
key , err := stpm . AttestKey ( context . Background ( ) , "first-ak" , "first-key" , config )
require . NoError ( t , err )
require . NotNil ( t , key )
params , err := key . CertificationParameters ( context . Background ( ) )
require . NoError ( t , err )
signer , err := key . Signer ( context . Background ( ) )
require . NoError ( t , err )
fingerprint , err := keyutil . Fingerprint ( signer . Public ( ) )
require . NoError ( t , err )
// attest another key and get its certification parameters
anotherKey , err := stpm . AttestKey ( context . Background ( ) , "first-ak" , "another-key" , config )
require . NoError ( t , err )
require . NotNil ( t , key )
anotherKeyParams , err := anotherKey . CertificationParameters ( context . Background ( ) )
require . NoError ( t , err )
type args struct {
ctx context . Context
prov Provisioner
ch * Challenge
jwk * jose . JSONWebKey
att * attestationObject
}
tests := [ ] struct {
name string
args args
want * tpmAttestationData
expErr * Error
} {
{ "ok" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , //
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , nil } ,
{ "fail ver not present" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "ver not present" ) } ,
{ "fail ver type" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : [ ] interface { } { } ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "ver not present" ) } ,
{ "fail bogus ver" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "bogus" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( ` version "bogus" is not supported ` ) } ,
{ "fail x5c not present" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "x5c not present" ) } ,
{ "fail x5c type" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] [ ] byte { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "x5c not present" ) } ,
{ "fail x5c empty" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "x5c is empty" ) } ,
{ "fail leaf type" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "step" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { "leaf" , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "x5c is malformed" ) } ,
{ "fail leaf parse" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "step" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw [ : 100 ] , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "x5c is malformed: x509: malformed certificate" ) } ,
{ "fail intermediate type" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "step" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , "intermediate" } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "x5c is malformed" ) } ,
{ "fail intermediate parse" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "step" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw [ : 100 ] } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "x5c is malformed: x509: malformed certificate" ) } ,
{ "fail roots" , args { ctx , mustAttestationProvisioner ( t , nil ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newInternalServerError ( "failed getting TPM attestation root CAs" ) } ,
{ "fail verify" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "step" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "x5c is not valid: x509: certificate signed by unknown authority" ) } ,
{ "fail missing SAN extension" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCertMissingSAN . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "AK certificate is missing Subject Alternative Name extension" ) } ,
{ "fail pubArea not present" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
} ,
} } , nil , newBadAttestationStatementError ( "invalid pubArea in attestation statement" ) } ,
{ "fail pubArea type" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : [ ] interface { } { } ,
} ,
} } , nil , newBadAttestationStatementError ( "invalid pubArea in attestation statement" ) } ,
{ "fail pubArea empty" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : [ ] byte { } ,
} ,
} } , nil , newBadAttestationStatementError ( "pubArea is empty" ) } ,
{ "fail sig not present" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "invalid sig in attestation statement" ) } ,
{ "fail sig type" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : [ ] interface { } { } ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "invalid sig in attestation statement" ) } ,
{ "fail sig empty" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : [ ] byte { } ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "sig is empty" ) } ,
{ "fail certInfo not present" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "invalid certInfo in attestation statement" ) } ,
{ "fail certInfo type" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : [ ] interface { } { } ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "invalid certInfo in attestation statement" ) } ,
{ "fail certInfo empty" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : [ ] byte { } ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "certInfo is empty" ) } ,
{ "fail alg not present" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "invalid alg in attestation statement" ) } ,
{ "fail alg type" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( 0 ) , // invalid alg
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "invalid alg 0 in attestation statement" ) } ,
{ "fail attestation verification" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : anotherKeyParams . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "invalid certification parameters: certification refers to a different key" ) } ,
{ "fail keyAuthorization" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "token" } , & jose . JSONWebKey { Key : [ ] byte ( "not an asymmetric key" ) } , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , // RS256
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newInternalServerError ( "failed creating key auth digest: error generating JWK thumbprint: square/go-jose: unknown key type '[]uint8'" ) } ,
{ "fail different keyAuthorization" , args { ctx , mustAttestationProvisioner ( t , acaRoot ) , & Challenge { Token : "aDifferentToken" } , jwk , & attestationObject {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
"alg" : int64 ( - 257 ) , //
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} } , nil , newBadAttestationStatementError ( "key authorization does not match" ) } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
got , err := doTPMAttestationFormat ( tt . args . ctx , tt . args . prov , tt . args . ch , tt . args . jwk , tt . args . att )
if tt . expErr != nil {
var ae * Error
if assert . True ( t , errors . As ( err , & ae ) ) {
assert . EqualError ( t , err , tt . expErr . Error ( ) )
assert . Equal ( t , ae . StatusCode ( ) , tt . expErr . StatusCode ( ) )
assert . Equal ( t , ae . Type , tt . expErr . Type )
}
assert . Nil ( t , got )
return
}
assert . NoError ( t , err )
if assert . NotNil ( t , got ) {
assert . Equal ( t , akCert , got . Certificate )
assert . Equal ( t , [ ] [ ] * x509 . Certificate {
{
akCert , aca . Intermediate , aca . Root ,
} ,
} , got . VerifiedChains )
assert . Equal ( t , fingerprint , got . Fingerprint )
assert . Empty ( t , got . PermanentIdentifiers ) // currently expected to be always empty
}
} )
}
}