2023-03-29 16:58:50 +00:00
//go:build tpmsimulator
// +build tpmsimulator
package acme
import (
"context"
"crypto"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
2023-03-31 15:39:18 +00:00
"encoding/asn1"
2023-03-29 16:58:50 +00:00
"encoding/base64"
"encoding/json"
"encoding/pem"
2023-03-31 12:57:25 +00:00
"errors"
2023-03-31 15:39:18 +00:00
"fmt"
2023-03-29 16:58:50 +00:00
"net/url"
"testing"
"github.com/fxamacker/cbor/v2"
"github.com/google/go-attestation/attest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/tpm"
"go.step.sm/crypto/tpm/simulator"
tpmstorage "go.step.sm/crypto/tpm/storage"
2023-03-31 15:39:18 +00:00
"go.step.sm/crypto/x509util"
2023-03-29 16:58:50 +00:00
)
func newSimulatedTPM ( t * testing . T ) * tpm . TPM {
t . Helper ( )
tmpDir := t . TempDir ( )
tpm , err := tpm . New ( withSimulator ( t ) , tpm . WithStore ( tpmstorage . NewDirstore ( tmpDir ) ) ) // TODO: provide in-memory storage implementation instead
require . NoError ( t , err )
return tpm
}
func withSimulator ( t * testing . T ) tpm . NewTPMOption {
t . Helper ( )
var sim simulator . Simulator
t . Cleanup ( func ( ) {
if sim == nil {
return
}
err := sim . Close ( )
require . NoError ( t , err )
} )
sim = simulator . New ( )
err := sim . Open ( )
require . NoError ( t , err )
return tpm . WithSimulator ( sim )
}
func generateKeyID ( t * testing . T , pub crypto . PublicKey ) [ ] byte {
t . Helper ( )
b , err := x509 . MarshalPKIXPublicKey ( pub )
require . NoError ( t , err )
hash := sha256 . Sum256 ( b )
return hash [ : ]
}
2023-03-31 15:39:18 +00:00
func mustAttestTPM ( t * testing . T , keyAuthorization string , permanentIdentifiers [ ] string ) ( [ ] byte , crypto . Signer , * x509 . Certificate ) {
2023-03-29 16:58:50 +00:00
t . Helper ( )
aca , err := minica . New (
2023-03-31 12:57:25 +00:00
minica . WithName ( "TPM Testing" ) ,
2023-03-29 16:58:50 +00:00
minica . WithGetSignerFunc (
func ( ) ( crypto . Signer , error ) {
return keyutil . GenerateSigner ( "RSA" , "" , 2048 )
} ,
) ,
)
require . NoError ( t , err )
// prepare simulated TPM and create an AK
2023-03-30 11:02:04 +00:00
stpm := newSimulatedTPM ( t )
eks , err := stpm . GetEKs ( context . Background ( ) )
2023-03-29 16:58:50 +00:00
require . NoError ( t , err )
2023-03-30 11:02:04 +00:00
ak , err := stpm . CreateAK ( context . Background ( ) , "first-ak" )
2023-03-29 16:58:50 +00:00
require . NoError ( t , err )
require . NotNil ( t , ak )
2023-03-30 11:02:04 +00:00
// 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())
2023-03-29 16:58:50 +00:00
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 {
PublicKey : akp . Public ,
2023-03-31 15:39:18 +00:00
}
if len ( permanentIdentifiers ) == 0 {
template . URIs = [ ] * url . URL {
2023-03-29 16:58:50 +00:00
{ Scheme : "urn" , Opaque : "ek:sha256:" + base64 . StdEncoding . EncodeToString ( keyID ) } ,
2023-03-31 15:39:18 +00:00
}
} else {
san := x509util . SubjectAlternativeName {
Type : x509util . PermanentIdentifierType ,
Value : permanentIdentifiers [ 0 ] , // TODO(hs): multiple?
}
ext , err := createSubjectAltNameExtension ( nil , nil , nil , nil , [ ] x509util . SubjectAlternativeName { san } , true )
require . NoError ( t , err )
template . ExtraExtensions = append ( template . ExtraExtensions ,
pkix . Extension {
Id : asn1 . ObjectIdentifier ( ext . ID ) ,
Critical : ext . Critical ,
Value : ext . Value ,
} ,
)
2023-03-29 16:58:50 +00:00
}
akCert , err := aca . Sign ( template )
require . NoError ( t , err )
require . NotNil ( t , akCert )
// 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 [ : ] ,
}
2023-03-30 11:02:04 +00:00
key , err := stpm . AttestKey ( context . Background ( ) , "first-ak" , "first-key" , config )
2023-03-29 16:58:50 +00:00
require . NoError ( t , err )
require . NotNil ( t , key )
require . Equal ( t , "first-key" , key . Name ( ) )
require . NotEqual ( t , 0 , len ( key . Data ( ) ) )
require . Equal ( t , "first-ak" , key . AttestedBy ( ) )
require . True ( t , key . WasAttested ( ) )
require . True ( t , key . WasAttestedBy ( ak ) )
signer , err := key . Signer ( context . Background ( ) )
require . NoError ( t , err )
// prepare the attestation object with the AK certificate chain,
// the attested key, its metadata and the signature signed by the
// AK.
params , err := key . CertificationParameters ( context . Background ( ) )
require . NoError ( t , err )
attObj , err := cbor . Marshal ( struct {
Format string ` json:"fmt" `
AttStatement map [ string ] interface { } ` json:"attStmt,omitempty" `
} {
Format : "tpm" ,
AttStatement : map [ string ] interface { } {
"ver" : "2.0" ,
"x5c" : [ ] interface { } { akCert . Raw , aca . Intermediate . Raw } ,
2023-03-31 12:57:25 +00:00
"alg" : int64 ( - 257 ) , // RS256
2023-03-29 16:58:50 +00:00
"sig" : params . CreateSignature ,
"certInfo" : params . CreateAttestation ,
"pubArea" : params . Public ,
} ,
} )
require . NoError ( t , err )
// marshal the ACME payload
payload , err := json . Marshal ( struct {
AttObj string ` json:"attObj" `
} {
AttObj : base64 . RawURLEncoding . EncodeToString ( attObj ) ,
} )
require . NoError ( t , err )
return payload , signer , aca . Root
}
func Test_deviceAttest01ValidateWithTPMSimulator ( t * testing . T ) {
type args struct {
ctx context . Context
ch * Challenge
db DB
jwk * jose . JSONWebKey
payload [ ] byte
}
type test struct {
args args
wantErr * Error
}
tests := map [ string ] func ( t * testing . T ) test {
2023-03-30 11:02:04 +00:00
"ok/doTPMAttestationFormat-storeError" : func ( t * testing . T ) test {
2023-03-31 15:39:18 +00:00
jwk , keyAuth := mustAccountAndKeyAuthorization ( t , "token" )
payload , _ , root := mustAttestTPM ( t , keyAuth , nil ) // TODO: value(s) for AK cert?
2023-03-30 11:02:04 +00:00
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
// parse payload, set invalid "ver", remarshal
var p payloadType
err := json . Unmarshal ( payload , & p )
require . NoError ( t , err )
attObj , err := base64 . RawURLEncoding . DecodeString ( p . AttObj )
require . NoError ( t , err )
att := attestationObject { }
err = cbor . Unmarshal ( attObj , & att )
require . NoError ( t , err )
att . AttStatement [ "ver" ] = "bogus"
attObj , err = cbor . Marshal ( struct {
Format string ` json:"fmt" `
AttStatement map [ string ] interface { } ` json:"attStmt,omitempty" `
} {
Format : "tpm" ,
AttStatement : att . AttStatement ,
} )
require . NoError ( t , err )
payload , err = json . Marshal ( struct {
AttObj string ` json:"attObj" `
} {
AttObj : base64 . RawURLEncoding . EncodeToString ( attObj ) ,
} )
require . NoError ( t , err )
return test {
args : args {
ctx : ctx ,
2023-03-31 15:39:18 +00:00
jwk : jwk ,
2023-03-30 11:02:04 +00:00
ch : & Challenge {
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "device.id.12345678" ,
} ,
payload : payload ,
db : & MockDB {
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "device.id.12345678" , updch . Value )
err := NewError ( ErrorBadAttestationStatementType , ` version "bogus" is not supported ` )
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
2023-03-31 15:39:18 +00:00
"ok with invalid PermanentIdentifier SAN" : func ( t * testing . T ) test {
jwk , keyAuth := mustAccountAndKeyAuthorization ( t , "token" )
payload , _ , root := mustAttestTPM ( t , keyAuth , [ ] string { "device.id.12345678" } ) // TODO: value(s) for AK cert?
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
return test {
args : args {
ctx : ctx ,
jwk : jwk ,
ch : & Challenge {
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "device.id.99999999" ,
} ,
payload : payload ,
db : & MockDB {
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusInvalid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "device.id.99999999" , updch . Value )
err := NewError ( ErrorRejectedIdentifierType , ` permanent identifier does not match ` ) .
AddSubproblems ( NewSubproblemWithIdentifier (
ErrorMalformedType ,
Identifier { Type : "permanent-identifier" , Value : "device.id.99999999" } ,
` challenge identifier "device.id.99999999" doesn't match any of the attested hardware identifiers ["device.id.12345678"] ` ,
) )
assert . EqualError ( t , updch . Error . Err , err . Err . Error ( ) )
assert . Equal ( t , err . Type , updch . Error . Type )
assert . Equal ( t , err . Detail , updch . Error . Detail )
assert . Equal ( t , err . Status , updch . Error . Status )
assert . Equal ( t , err . Subproblems , updch . Error . Subproblems )
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
2023-03-29 16:58:50 +00:00
"ok" : func ( t * testing . T ) test {
jwk , keyAuth := mustAccountAndKeyAuthorization ( t , "token" )
2023-03-31 15:39:18 +00:00
payload , signer , root := mustAttestTPM ( t , keyAuth , nil ) // TODO: value(s) for AK cert?
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
return test {
args : args {
ctx : ctx ,
jwk : jwk ,
ch : & Challenge {
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "device.id.12345678" ,
} ,
payload : payload ,
db : & MockDB {
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
MockUpdateAuthorization : func ( ctx context . Context , az * Authorization ) error {
fingerprint , err := keyutil . Fingerprint ( signer . Public ( ) )
assert . NoError ( t , err )
assert . Equal ( t , "azID" , az . ID )
assert . Equal ( t , fingerprint , az . Fingerprint )
return nil
} ,
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusValid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "device.id.12345678" , updch . Value )
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
"ok with PermanentIdentifier SAN" : func ( t * testing . T ) test {
jwk , keyAuth := mustAccountAndKeyAuthorization ( t , "token" )
payload , signer , root := mustAttestTPM ( t , keyAuth , [ ] string { "device.id.12345678" } ) // TODO: value(s) for AK cert?
2023-03-29 16:58:50 +00:00
caRoot := pem . EncodeToMemory ( & pem . Block { Type : "CERTIFICATE" , Bytes : root . Raw } )
ctx := NewProvisionerContext ( context . Background ( ) , mustAttestationProvisioner ( t , caRoot ) )
return test {
args : args {
ctx : ctx ,
jwk : jwk ,
ch : & Challenge {
ID : "chID" ,
AuthorizationID : "azID" ,
Token : "token" ,
Type : "device-attest-01" ,
Status : StatusPending ,
Value : "device.id.12345678" ,
} ,
payload : payload ,
db : & MockDB {
MockGetAuthorization : func ( ctx context . Context , id string ) ( * Authorization , error ) {
assert . Equal ( t , "azID" , id )
return & Authorization { ID : "azID" } , nil
} ,
MockUpdateAuthorization : func ( ctx context . Context , az * Authorization ) error {
fingerprint , err := keyutil . Fingerprint ( signer . Public ( ) )
assert . NoError ( t , err )
assert . Equal ( t , "azID" , az . ID )
assert . Equal ( t , fingerprint , az . Fingerprint )
return nil
} ,
MockUpdateChallenge : func ( ctx context . Context , updch * Challenge ) error {
assert . Equal ( t , "chID" , updch . ID )
assert . Equal ( t , "token" , updch . Token )
assert . Equal ( t , StatusValid , updch . Status )
assert . Equal ( t , ChallengeType ( "device-attest-01" ) , updch . Type )
assert . Equal ( t , "device.id.12345678" , updch . Value )
return nil
} ,
} ,
} ,
wantErr : nil ,
}
} ,
}
for name , run := range tests {
t . Run ( name , func ( t * testing . T ) {
tc := run ( t )
if err := deviceAttest01Validate ( tc . args . ctx , tc . args . ch , tc . args . db , tc . args . jwk , tc . args . payload ) ; err != nil {
assert . Error ( t , tc . wantErr )
assert . EqualError ( t , err , tc . wantErr . Error ( ) )
return
}
assert . Nil ( t , tc . wantErr )
} )
}
}
2023-03-31 12:57:25 +00:00
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 {
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 ,
} ,
2023-04-03 09:54:22 +00:00
} } , nil , newInternalServerError ( "no root CA bundle available to verify the attestation certificate" ) } ,
2023-03-31 12:57:25 +00:00
{ "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
}
} )
}
}
2023-03-31 15:39:18 +00:00
// createSubjectAltNameExtension will construct an Extension containing all
// SubjectAlternativeNames held in a Certificate. It implements more types than
// the golang x509 library, so it is used whenever OtherName or RegisteredID
// type SANs are present in the certificate.
//
// See also https://datatracker.ietf.org/doc/html/rfc5280.html#section-4.2.1.6
//
// TODO(hs): this was copied from go.step.sm/crypto/x509util. Should it be
// exposed instead?
func createSubjectAltNameExtension ( dnsNames , emailAddresses x509util . MultiString , ipAddresses x509util . MultiIP , uris x509util . MultiURL , sans [ ] x509util . SubjectAlternativeName , subjectIsEmpty bool ) ( x509util . Extension , error ) {
var zero x509util . Extension
var rawValues [ ] asn1 . RawValue
for _ , dnsName := range dnsNames {
rawValue , err := x509util . SubjectAlternativeName {
Type : x509util . DNSType , Value : dnsName ,
} . RawValue ( )
if err != nil {
return zero , err
}
rawValues = append ( rawValues , rawValue )
}
for _ , emailAddress := range emailAddresses {
rawValue , err := x509util . SubjectAlternativeName {
Type : x509util . EmailType , Value : emailAddress ,
} . RawValue ( )
if err != nil {
return zero , err
}
rawValues = append ( rawValues , rawValue )
}
for _ , ip := range ipAddresses {
rawValue , err := x509util . SubjectAlternativeName {
Type : x509util . IPType , Value : ip . String ( ) ,
} . RawValue ( )
if err != nil {
return zero , err
}
rawValues = append ( rawValues , rawValue )
}
for _ , uri := range uris {
rawValue , err := x509util . SubjectAlternativeName {
Type : x509util . URIType , Value : uri . String ( ) ,
} . RawValue ( )
if err != nil {
return zero , err
}
rawValues = append ( rawValues , rawValue )
}
for _ , san := range sans {
rawValue , err := san . RawValue ( )
if err != nil {
return zero , err
}
rawValues = append ( rawValues , rawValue )
}
// Now marshal the rawValues into the ASN1 sequence, and create an Extension object to hold the extension
rawBytes , err := asn1 . Marshal ( rawValues )
if err != nil {
return zero , fmt . Errorf ( "error marshaling SubjectAlternativeName extension to ASN1: %w" , err )
}
return x509util . Extension {
ID : x509util . ObjectIdentifier ( oidSubjectAlternativeName ) ,
Critical : subjectIsEmpty ,
Value : rawBytes ,
} , nil
}