1193 lines
47 KiB
Go
Vendored
1193 lines
47 KiB
Go
Vendored
package protocol
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/ed25519"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/binary"
|
|
"encoding/pem"
|
|
"errors"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/google/go-tpm/tpm2"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/go-webauthn/webauthn/protocol/webauthncbor"
|
|
"github.com/go-webauthn/webauthn/protocol/webauthncose"
|
|
)
|
|
|
|
func TestForEachSAN(t *testing.T) {
|
|
t.Run("ShouldReturnErrorWhenAsn1UnmarshalFails", func(t *testing.T) {
|
|
err := forEachSAN([]byte{0x01, 0x02, 0x03}, func(tag int, data []byte) error {
|
|
return nil
|
|
})
|
|
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("ShouldReturnErrorWhenTrailingDataExists", func(t *testing.T) {
|
|
extension := []byte{0x30, 0x00, 0x00}
|
|
|
|
err := forEachSAN(extension, func(tag int, data []byte) error {
|
|
return nil
|
|
})
|
|
|
|
assert.EqualError(t, err, "x509: trailing data after X.509 extension")
|
|
})
|
|
|
|
t.Run("ShouldReturnStructuralErrorWhenNotACompoundSequence", func(t *testing.T) {
|
|
extension, marshalErr := asn1.Marshal([]byte{0xAA, 0xBB})
|
|
require.NoError(t, marshalErr)
|
|
|
|
err := forEachSAN(extension, func(tag int, data []byte) error {
|
|
return nil
|
|
})
|
|
|
|
require.EqualError(t, err, "asn1: structure error: bad SAN sequence")
|
|
|
|
var structuralErr asn1.StructuralError
|
|
|
|
require.True(t, errors.As(err, &structuralErr))
|
|
})
|
|
|
|
t.Run("ShouldReturnErrorWhenInnerAsn1UnmarshalFails", func(t *testing.T) {
|
|
extension := []byte{0x30, 0x01, 0xFF}
|
|
|
|
err := forEachSAN(extension, func(tag int, data []byte) error {
|
|
return nil
|
|
})
|
|
|
|
assert.EqualError(t, err, "asn1: syntax error: truncated base 128 integer")
|
|
})
|
|
}
|
|
|
|
func TestParseSANExtension(t *testing.T) {
|
|
makeSANWithDN := func(t *testing.T, dnDER []byte) []byte {
|
|
t.Helper()
|
|
|
|
gn := asn1.RawValue{
|
|
Class: asn1.ClassContextSpecific,
|
|
Tag: nameTypeDN,
|
|
IsCompound: true,
|
|
Bytes: dnDER,
|
|
}
|
|
|
|
sanDER, err := asn1.Marshal([]asn1.RawValue{gn})
|
|
require.NoError(t, err)
|
|
|
|
return sanDER
|
|
}
|
|
|
|
t.Run("ShouldReturnErrorWhenAsn1UnmarshalOfRdnSequenceFails", func(t *testing.T) {
|
|
san := makeSANWithDN(t, []byte{0x01, 0x02, 0x03})
|
|
|
|
manufacturer, model, version, err := parseSANExtension(san)
|
|
assert.EqualError(t, err, "asn1: structure error: tags don't match (16 vs {class:0 tag:1 length:2 isCompound:false}) {optional:false explicit:false application:false private:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} RDNSequence @2")
|
|
assert.Empty(t, manufacturer)
|
|
assert.Empty(t, model)
|
|
assert.Empty(t, version)
|
|
})
|
|
|
|
t.Run("ShouldSkipEmptyRdnSets", func(t *testing.T) {
|
|
rdnSeq := pkix.RDNSequence{
|
|
{},
|
|
{
|
|
{
|
|
Type: oidTCGAtTpmManufacturer,
|
|
Value: "id:414D4400",
|
|
},
|
|
{
|
|
Type: oidTCGAtTpmModel,
|
|
Value: "ModelX",
|
|
},
|
|
{
|
|
Type: oidTCGAtTPMVersion,
|
|
Value: "id:00070002",
|
|
},
|
|
},
|
|
}
|
|
|
|
dnDER, err := asn1.Marshal(rdnSeq)
|
|
require.NoError(t, err)
|
|
|
|
san := makeSANWithDN(t, dnDER)
|
|
|
|
manufacturer, model, version, err := parseSANExtension(san)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "414D4400", manufacturer)
|
|
assert.Equal(t, "ModelX", model)
|
|
assert.Equal(t, "00070002", version)
|
|
})
|
|
|
|
t.Run("ShouldSkipNonStringAttributeValues", func(t *testing.T) {
|
|
rdnSeq := pkix.RDNSequence{
|
|
{
|
|
{
|
|
Type: oidTCGAtTpmManufacturer,
|
|
Value: asn1.RawValue{
|
|
Class: asn1.ClassUniversal,
|
|
Tag: asn1.TagOctetString,
|
|
Bytes: []byte{0xAA, 0xBB},
|
|
},
|
|
},
|
|
{
|
|
Type: oidTCGAtTpmModel,
|
|
Value: "ModelX",
|
|
},
|
|
{
|
|
Type: oidTCGAtTPMVersion,
|
|
Value: "id:00070002",
|
|
},
|
|
},
|
|
}
|
|
|
|
dnDER, err := asn1.Marshal(rdnSeq)
|
|
require.NoError(t, err)
|
|
|
|
san := makeSANWithDN(t, dnDER)
|
|
|
|
manufacturer, model, version, err := parseSANExtension(san)
|
|
require.NoError(t, err)
|
|
|
|
assert.Empty(t, manufacturer)
|
|
assert.Equal(t, "ModelX", model)
|
|
assert.Equal(t, "00070002", version)
|
|
})
|
|
}
|
|
|
|
func TestIsValidTPMManufacturer(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
id string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "ShouldReturnTrueForKnownManufacturer",
|
|
id: "414D4400",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "ShouldReturnFalseForUnknownManufacturer",
|
|
id: "DEADBEEF",
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert.Equal(t, tc.want, isValidTPMManufacturer(tc.id))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTpmRemoveEKU(t *testing.T) {
|
|
t.Run("ShouldFailWhenAikEkuMissing", func(t *testing.T) {
|
|
cert := &x509.Certificate{
|
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{
|
|
{1, 2, 3, 4},
|
|
},
|
|
}
|
|
|
|
assert.EqualError(t, tpmRemoveEKU(cert), "Attestation Identity Key certificate missing required Extended Key Usage.")
|
|
})
|
|
|
|
t.Run("ShouldRemoveAikAndMicrosoftEkuButKeepOtherUnknownEku", func(t *testing.T) {
|
|
other := asn1.ObjectIdentifier{1, 2, 3, 4}
|
|
|
|
cert := &x509.Certificate{
|
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{
|
|
oidMicrosoftKpPrivacyCA,
|
|
other,
|
|
oidTCGKpAIKCertificate,
|
|
},
|
|
}
|
|
|
|
require.Nil(t, tpmRemoveEKU(cert))
|
|
|
|
require.Len(t, cert.UnknownExtKeyUsage, 1)
|
|
assert.True(t, cert.UnknownExtKeyUsage[0].Equal(other))
|
|
})
|
|
}
|
|
|
|
func TestTpmParseSANExtension(t *testing.T) {
|
|
makeSAN := func(t *testing.T, manufacturer, model, version string) []byte {
|
|
t.Helper()
|
|
|
|
rdnSeq := pkix.RDNSequence{
|
|
{
|
|
{
|
|
Type: oidTCGAtTpmManufacturer,
|
|
Value: manufacturer,
|
|
},
|
|
{
|
|
Type: oidTCGAtTpmModel,
|
|
Value: model,
|
|
},
|
|
{
|
|
Type: oidTCGAtTPMVersion,
|
|
Value: version,
|
|
},
|
|
},
|
|
}
|
|
|
|
nameDER, err := asn1.Marshal(rdnSeq)
|
|
require.NoError(t, err)
|
|
|
|
gn := asn1.RawValue{
|
|
Class: asn1.ClassContextSpecific,
|
|
Tag: nameTypeDN,
|
|
IsCompound: true,
|
|
Bytes: nameDER,
|
|
}
|
|
|
|
sanDER, err := asn1.Marshal([]asn1.RawValue{gn})
|
|
require.NoError(t, err)
|
|
|
|
return sanDER
|
|
}
|
|
|
|
t.Run("ShouldFailWhenSanExtensionMalformed", func(t *testing.T) {
|
|
cert := &x509.Certificate{
|
|
Extensions: []pkix.Extension{
|
|
{Id: oidExtensionSubjectAltName, Value: []byte{0x01, 0x02, 0x03}},
|
|
},
|
|
}
|
|
|
|
protoErr := tpmParseSANExtension(cert)
|
|
require.EqualError(t, protoErr, "Authenticator with invalid Authenticator Identity Key SAN data encountered during attestation validation.")
|
|
|
|
assert.Equal(t, ErrInvalidAttestation.Type, protoErr.Type)
|
|
})
|
|
|
|
t.Run("ShouldFailWhenSanDataMissing", func(t *testing.T) {
|
|
cert := &x509.Certificate{
|
|
Extensions: []pkix.Extension{},
|
|
}
|
|
|
|
assert.EqualError(t, tpmParseSANExtension(cert), "Invalid SAN data in AIK certificate.")
|
|
})
|
|
|
|
t.Run("ShouldRemoveUnhandledCriticalSanExtensionWhenPresent", func(t *testing.T) {
|
|
san := makeSAN(t, "id:414D4400", "ModelX", "id:00070002")
|
|
other := asn1.ObjectIdentifier{1, 2, 3, 4}
|
|
|
|
cert := &x509.Certificate{
|
|
Extensions: []pkix.Extension{
|
|
{Id: oidExtensionSubjectAltName, Value: san},
|
|
},
|
|
UnhandledCriticalExtensions: []asn1.ObjectIdentifier{
|
|
oidExtensionSubjectAltName,
|
|
other,
|
|
},
|
|
}
|
|
|
|
protoErr := tpmParseSANExtension(cert)
|
|
require.Nil(t, protoErr)
|
|
|
|
require.Len(t, cert.UnhandledCriticalExtensions, 1)
|
|
assert.True(t, cert.UnhandledCriticalExtensions[0].Equal(other))
|
|
})
|
|
}
|
|
|
|
func TestTpmParseAIKAttCA(t *testing.T) {
|
|
makeSAN := func(t *testing.T, manufacturer, model, version string) []byte {
|
|
t.Helper()
|
|
|
|
rdnSeq := pkix.RDNSequence{
|
|
{
|
|
{
|
|
Type: oidTCGAtTpmManufacturer,
|
|
Value: manufacturer,
|
|
},
|
|
{
|
|
Type: oidTCGAtTpmModel,
|
|
Value: model,
|
|
},
|
|
{
|
|
Type: oidTCGAtTPMVersion,
|
|
Value: version,
|
|
},
|
|
},
|
|
}
|
|
|
|
nameDER, err := asn1.Marshal(rdnSeq)
|
|
require.NoError(t, err)
|
|
|
|
gn := asn1.RawValue{
|
|
Class: asn1.ClassContextSpecific,
|
|
Tag: nameTypeDN,
|
|
IsCompound: true,
|
|
Bytes: nameDER,
|
|
}
|
|
|
|
sanDER, err := asn1.Marshal([]asn1.RawValue{gn})
|
|
require.NoError(t, err)
|
|
|
|
return sanDER
|
|
}
|
|
|
|
t.Run("ShouldFailWhenSanParsingFails", func(t *testing.T) {
|
|
leaf := &x509.Certificate{
|
|
Extensions: []pkix.Extension{
|
|
{Id: oidExtensionSubjectAltName, Value: []byte{0x01, 0x02, 0x03}},
|
|
},
|
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{
|
|
oidTCGKpAIKCertificate,
|
|
},
|
|
}
|
|
|
|
err := tpmParseAIKAttCA(leaf, nil)
|
|
require.Error(t, err)
|
|
|
|
assert.Equal(t, ErrInvalidAttestation.Type, err.Type)
|
|
assert.Equal(t, "Authenticator with invalid Authenticator Identity Key SAN data encountered during attestation validation.", err.Details)
|
|
assert.Equal(t, "Error occurred parsing SAN extension: asn1: syntax error: data truncated", err.DevInfo)
|
|
})
|
|
|
|
t.Run("ShouldFailWhenParentMissingAikEku", func(t *testing.T) {
|
|
otherEku := asn1.ObjectIdentifier{1, 2, 3, 4}
|
|
|
|
leaf := &x509.Certificate{
|
|
Extensions: []pkix.Extension{
|
|
{Id: oidExtensionSubjectAltName, Value: makeSAN(t, "id:414D4400", "ModelX", "id:00070002")},
|
|
},
|
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{
|
|
oidTCGKpAIKCertificate,
|
|
oidMicrosoftKpPrivacyCA,
|
|
otherEku,
|
|
},
|
|
}
|
|
|
|
parent := &x509.Certificate{
|
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{
|
|
oidMicrosoftKpPrivacyCA,
|
|
otherEku,
|
|
},
|
|
}
|
|
|
|
err := tpmParseAIKAttCA(leaf, []*x509.Certificate{parent})
|
|
assert.EqualError(t, err, "Attestation Identity Key certificate missing required Extended Key Usage.")
|
|
})
|
|
|
|
t.Run("ShouldSucceedAndMutateCertificates", func(t *testing.T) {
|
|
otherEku := asn1.ObjectIdentifier{1, 2, 3, 4}
|
|
otherCritical := asn1.ObjectIdentifier{2, 999, 1}
|
|
|
|
leaf := &x509.Certificate{
|
|
Extensions: []pkix.Extension{
|
|
{Id: oidExtensionSubjectAltName, Value: makeSAN(t, "id:414D4400", "ModelX", "id:00070002")},
|
|
},
|
|
UnhandledCriticalExtensions: []asn1.ObjectIdentifier{
|
|
oidExtensionSubjectAltName,
|
|
otherCritical,
|
|
},
|
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{
|
|
oidTCGKpAIKCertificate,
|
|
oidMicrosoftKpPrivacyCA,
|
|
otherEku,
|
|
},
|
|
}
|
|
|
|
parent := &x509.Certificate{
|
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{
|
|
oidTCGKpAIKCertificate,
|
|
oidMicrosoftKpPrivacyCA,
|
|
otherEku,
|
|
},
|
|
}
|
|
|
|
err := tpmParseAIKAttCA(leaf, []*x509.Certificate{parent})
|
|
require.Nil(t, err)
|
|
|
|
require.Len(t, leaf.UnhandledCriticalExtensions, 1)
|
|
assert.True(t, leaf.UnhandledCriticalExtensions[0].Equal(otherCritical))
|
|
|
|
require.Len(t, leaf.UnknownExtKeyUsage, 1)
|
|
assert.True(t, leaf.UnknownExtKeyUsage[0].Equal(otherEku))
|
|
|
|
require.Len(t, parent.UnknownExtKeyUsage, 1)
|
|
assert.True(t, parent.UnknownExtKeyUsage[0].Equal(otherEku))
|
|
})
|
|
|
|
t.Run("ShouldFailWhenLeafMissingAikEku", func(t *testing.T) {
|
|
leaf := &x509.Certificate{
|
|
Extensions: []pkix.Extension{
|
|
{Id: oidExtensionSubjectAltName, Value: makeSAN(t, "id:414D4400", "ModelX", "id:00070002")},
|
|
},
|
|
UnknownExtKeyUsage: []asn1.ObjectIdentifier{
|
|
oidMicrosoftKpPrivacyCA,
|
|
},
|
|
}
|
|
|
|
err := tpmParseAIKAttCA(leaf, nil)
|
|
assert.EqualError(t, err, "Attestation Identity Key certificate missing required Extended Key Usage.")
|
|
})
|
|
}
|
|
|
|
func TestTpm2Exponent(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
params *tpm2.TPMSRSAParms
|
|
want uint32
|
|
}{
|
|
{
|
|
name: "ShouldReturnDefaultWhenExponentIsZero",
|
|
params: &tpm2.TPMSRSAParms{Exponent: 0},
|
|
want: 65537,
|
|
},
|
|
{
|
|
name: "ShouldReturnExponentWhenNonZero",
|
|
params: &tpm2.TPMSRSAParms{Exponent: 3},
|
|
want: 3,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
assert.Equal(t, tc.want, tpm2Exponent(tc.params))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTpm2NameDigest(t *testing.T) {
|
|
t.Run("ShouldRejectNameTooShort", func(t *testing.T) {
|
|
_, _, err := tpm2NameDigest(tpm2.TPM2BName{Buffer: []byte{0x00, 0x0B}})
|
|
assert.EqualError(t, err, "name too short")
|
|
})
|
|
|
|
t.Run("ShouldRejectInvalidHashAlgorithm", func(t *testing.T) {
|
|
buf := []byte{0xFF, 0xFF, 0x01}
|
|
_, _, err := tpm2NameDigest(tpm2.TPM2BName{Buffer: buf})
|
|
assert.EqualError(t, err, "invalid hash algorithm: unsupported hash algorithm: 65535")
|
|
})
|
|
|
|
t.Run("ShouldRejectDigestLengthMismatch", func(t *testing.T) {
|
|
buf := []byte{0x00, 0x0B, 0xAA}
|
|
_, _, err := tpm2NameDigest(tpm2.TPM2BName{Buffer: buf})
|
|
assert.EqualError(t, err, "invalid name digest length: 1")
|
|
})
|
|
|
|
t.Run("ShouldReturnAlgAndDigestWhenValid", func(t *testing.T) {
|
|
digest := make([]byte, sha256.Size)
|
|
for i := range digest {
|
|
digest[i] = byte(i)
|
|
}
|
|
|
|
buf := append([]byte{0x00, 0x0B}, digest...)
|
|
alg, gotDigest, err := tpm2NameDigest(tpm2.TPM2BName{Buffer: buf})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tpm2.TPMAlgSHA256, alg)
|
|
assert.Equal(t, digest, gotDigest)
|
|
})
|
|
}
|
|
|
|
func TestTPM2NameMatch(t *testing.T) {
|
|
t.Run("ShouldReturnFalseWhenInputsNil", func(t *testing.T) {
|
|
match, err := tpm2NameMatch(nil, nil)
|
|
require.NoError(t, err)
|
|
assert.False(t, match)
|
|
})
|
|
|
|
t.Run("ShouldReturnFalseWhenAttestedNameDoesNotMatchPubArea", func(t *testing.T) {
|
|
pubArea := makeTPMTPublicRSA(nil)
|
|
|
|
qualifiedSigner := tpm2.TPM2BName{
|
|
Buffer: append([]byte{0x00, 0x0B}, make([]byte, sha256.Size)...),
|
|
}
|
|
|
|
certInfo := &tpm2.TPMSAttest{
|
|
Attested: tpm2.NewTPMUAttest[*tpm2.TPMSCertifyInfo](
|
|
tpm2.TPMSTAttestCertify,
|
|
&tpm2.TPMSCertifyInfo{
|
|
Name: tpm2.TPM2BName{Buffer: []byte{0xDE, 0xAD, 0xBE, 0xEF}},
|
|
QualifiedName: tpm2.TPM2BName{Buffer: []byte{0x00}},
|
|
},
|
|
),
|
|
QualifiedSigner: qualifiedSigner,
|
|
}
|
|
|
|
match, err := tpm2NameMatch(certInfo, &pubArea)
|
|
require.NoError(t, err)
|
|
assert.False(t, match)
|
|
})
|
|
}
|
|
|
|
func TestTPMAttestationVerificationSuccess(t *testing.T) {
|
|
for i := range testAttestationTPMResponses {
|
|
t.Run("ShouldPassStandardScenario", func(t *testing.T) {
|
|
pcc := attestationTestUnpackResponse(t, testAttestationTPMResponses[i])
|
|
clientDataHash := sha256.Sum256(pcc.Raw.AttestationResponse.ClientDataJSON)
|
|
|
|
attestationType, _, err := attestationFormatValidationHandlerTPM(pcc.Response.AttestationObject, clientDataHash[:], nil)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "attca", attestationType)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTPMAttestationVerificationFailAttStatement(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
att AttestationObject
|
|
attestationType string
|
|
x5cs []any
|
|
err string
|
|
}{
|
|
{
|
|
"ShouldNotParseAttStatementMissingVer",
|
|
AttestationObject{},
|
|
"",
|
|
nil,
|
|
"Error retrieving ver value",
|
|
},
|
|
{
|
|
"ShouldNotParseAttStatementWithVersionNot2Point0",
|
|
AttestationObject{AttStatement: map[string]any{stmtVersion: "foo.bar", stmtAlgorithm: int64(0), stmtX5C: []any{}, stmtSignature: []byte{}, stmtCertInfo: []byte{}, stmtPubArea: []byte{}}},
|
|
"",
|
|
nil,
|
|
"WebAuthn only supports TPM 2.0 currently",
|
|
},
|
|
{
|
|
"ShouldNotParseAttStatementWithNoAlg",
|
|
AttestationObject{AttStatement: map[string]any{stmtVersion: "2.0"}},
|
|
"",
|
|
nil,
|
|
"Error retrieving alg value",
|
|
},
|
|
{
|
|
"ShouldNotParseAttStatementWithoutX5C",
|
|
AttestationObject{AttStatement: map[string]any{stmtVersion: "2.0", stmtAlgorithm: int64(0), stmtSignature: []byte{}, stmtCertInfo: []byte{}, stmtPubArea: []byte{}}},
|
|
"",
|
|
nil,
|
|
ErrNotImplemented.Details,
|
|
},
|
|
{
|
|
"ShouldNotParseAttStatementWithECDAAKID",
|
|
AttestationObject{AttStatement: map[string]any{stmtVersion: "2.0", stmtAlgorithm: int64(0), stmtX5C: []any{}, stmtECDAAKID: []byte{}, stmtSignature: []byte{}, stmtCertInfo: []byte{}, stmtPubArea: []byte{}}},
|
|
"",
|
|
nil,
|
|
ErrNotImplemented.Details,
|
|
},
|
|
{
|
|
"ShouldNotParseAttStatementWithNoSig",
|
|
AttestationObject{AttStatement: map[string]any{stmtVersion: "2.0", stmtAlgorithm: int64(0), stmtX5C: []any{}}},
|
|
"",
|
|
nil,
|
|
"Error retrieving sig value",
|
|
},
|
|
{
|
|
"ShouldNotParseAttStatementCertInfoNotPresent",
|
|
AttestationObject{AttStatement: map[string]any{stmtVersion: "2.0", stmtAlgorithm: int64(0), stmtX5C: []any{}, stmtSignature: []byte{}}},
|
|
"",
|
|
nil,
|
|
"Error retrieving certInfo value",
|
|
},
|
|
{
|
|
"ShouldNotParseAttStatementPubAreaNotPresent",
|
|
AttestationObject{AttStatement: map[string]any{stmtVersion: "2.0", stmtAlgorithm: int64(0), stmtX5C: []any{}, stmtSignature: []byte{}, stmtCertInfo: []byte{}}},
|
|
"",
|
|
nil,
|
|
"Error retrieving pubArea value",
|
|
},
|
|
{
|
|
"ShouldNotParseAttStatementPubAreaNotCorrectValue",
|
|
AttestationObject{AttStatement: defaultAttStatement},
|
|
"",
|
|
nil,
|
|
"Unable to decode TPMT_PUBLIC in attestation statement",
|
|
},
|
|
{
|
|
"ShouldNotParseAttStatementUnsupportedPublicKeyType",
|
|
AttestationObject{AttStatement: map[string]any{stmtVersion: "2.0", stmtAlgorithm: int64(0), stmtX5C: []any{}, stmtSignature: []byte{}, stmtCertInfo: []byte{}, stmtPubArea: tpm2.Marshal(makeTPMTPublicRSA(nil))}, AuthData: AuthenticatorData{AttData: AttestedCredentialData{CredentialPublicKey: []byte{}}}},
|
|
"",
|
|
nil,
|
|
"Unsupported Public Key Type",
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
attestationType, x5cs, err := attestationFormatValidationHandlerTPM(tc.att, nil, nil)
|
|
|
|
assert.Equal(t, tc.attestationType, attestationType)
|
|
assert.Equal(t, tc.x5cs, x5cs)
|
|
|
|
if tc.err != "" {
|
|
assert.EqualError(t, err, tc.err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTPMAttestationVerificationFailPubArea(t *testing.T) {
|
|
epk, rpk, opk, rsaKey, eccKey, err := getTPMAttestionKeys()
|
|
require.NoError(t, err)
|
|
|
|
require.GreaterOrEqual(t, rsaKey.E, 0)
|
|
require.LessOrEqual(t, int64(rsaKey.E), int64(4294967295))
|
|
|
|
e := uint32(rsaKey.E) //nolint:gosec
|
|
|
|
testCases := []struct {
|
|
name string
|
|
keyType webauthncose.COSEKeyType
|
|
rsaParams *TPMRSATestParameters
|
|
eccParams *TPMECCTestParameters
|
|
cpk []byte
|
|
err string
|
|
}{
|
|
{
|
|
"CurveMismatch",
|
|
webauthncose.EllipticKey,
|
|
nil,
|
|
&TPMECCTestParameters{Curve: tpm2.TPMECCNistP224, X: eccKey.X.Bytes(), Y: eccKey.Y.Bytes()},
|
|
epk,
|
|
"Mismatch between ECCParameters in pubArea and credentialPublicKey",
|
|
},
|
|
{
|
|
"XMismatch",
|
|
webauthncose.EllipticKey,
|
|
nil,
|
|
&TPMECCTestParameters{X: corruptBytes(eccKey.X.Bytes()), Y: eccKey.Y.Bytes()},
|
|
epk,
|
|
"Mismatch between ECCParameters in pubArea and credentialPublicKey",
|
|
},
|
|
{
|
|
"YMismatch",
|
|
webauthncose.EllipticKey,
|
|
nil,
|
|
&TPMECCTestParameters{X: eccKey.X.Bytes(), Y: corruptBytes(eccKey.Y.Bytes())},
|
|
epk,
|
|
"Mismatch between ECCParameters in pubArea and credentialPublicKey",
|
|
},
|
|
{
|
|
"NMismatch",
|
|
webauthncose.RSAKey,
|
|
&TPMRSATestParameters{Modulus: corruptBytes(rsaKey.N.Bytes()), Exponent: e},
|
|
nil,
|
|
rpk,
|
|
"Mismatch between RSAParameters in pubArea and credentialPublicKey",
|
|
},
|
|
{
|
|
"EMismatch",
|
|
webauthncose.RSAKey,
|
|
&TPMRSATestParameters{Modulus: rsaKey.N.Bytes(), Exponent: e + 1},
|
|
nil,
|
|
rpk,
|
|
"Mismatch between RSAParameters in pubArea and credentialPublicKey",
|
|
},
|
|
{
|
|
"UnsupportedKeyType",
|
|
webauthncose.OctetKey,
|
|
nil,
|
|
nil,
|
|
opk,
|
|
"Unsupported Public Key Type",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
attStmt := make(map[string]any, len(defaultAttStatement))
|
|
for id, v := range defaultAttStatement {
|
|
attStmt[id] = v
|
|
}
|
|
|
|
var public tpm2.TPMTPublic
|
|
|
|
switch tc.keyType {
|
|
case webauthncose.EllipticKey:
|
|
public = makeTPMTPublicECDSA(tc.eccParams)
|
|
case webauthncose.RSAKey:
|
|
public = makeTPMTPublicRSA(tc.rsaParams)
|
|
case webauthncose.OctetKey:
|
|
public = makeTPMTPublicECDSA(nil)
|
|
default:
|
|
t.Fatal("invalid key type")
|
|
}
|
|
|
|
attStmt[stmtPubArea] = tpm2.Marshal(public)
|
|
att := AttestationObject{
|
|
AttStatement: attStmt,
|
|
AuthData: AuthenticatorData{
|
|
AttData: AttestedCredentialData{
|
|
CredentialPublicKey: tc.cpk,
|
|
},
|
|
},
|
|
}
|
|
|
|
attestationType, _, err := attestationFormatValidationHandlerTPM(att, nil, nil)
|
|
if tc.err != "" {
|
|
assert.EqualError(t, err, tc.err)
|
|
} else {
|
|
assert.Equal(t, "attca", attestationType)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTPMAttestationVerificationFailCertInfo(t *testing.T) {
|
|
h := webauthncose.HasherFromCOSEAlg(webauthncose.AlgRS256)
|
|
extraData := h.Sum(nil)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
certInfo tpm2.TPMSAttest
|
|
err string
|
|
}{
|
|
{
|
|
"CertInfoMagicValueNoCorrect",
|
|
tpm2.TPMSAttest{Magic: 42, Type: tpm2.TPMSTAttestCreation, Attested: tpm2.NewTPMUAttest[*tpm2.TPMSCreationInfo](tpm2.TPMSTAttestCreation, &tpm2.TPMSCreationInfo{})},
|
|
"Magic is not set to TPM_GENERATED_VALUE",
|
|
},
|
|
{
|
|
"CertInfoTypeNotTagAttestCreation",
|
|
tpm2.TPMSAttest{Magic: tpm2.TPMGeneratedValue, Type: tpm2.TPMSTAttestCreation, Attested: tpm2.NewTPMUAttest[*tpm2.TPMSCreationInfo](tpm2.TPMSTAttestCreation, &tpm2.TPMSCreationInfo{})},
|
|
"Type is not set to TPM_ST_ATTEST_CERTIFY",
|
|
},
|
|
{
|
|
"CertInfoTypeNotTagAttestCertify",
|
|
tpm2.TPMSAttest{Magic: tpm2.TPMGeneratedValue, Type: tpm2.TPMSTAttestCertify, Attested: tpm2.NewTPMUAttest[*tpm2.TPMSCertifyInfo](tpm2.TPMSTAttestCertify, &tpm2.TPMSCertifyInfo{})},
|
|
"ExtraData is not set to hash of attToBeSigned",
|
|
},
|
|
{
|
|
"CertInfoPubAreaNameMismatch",
|
|
tpm2.TPMSAttest{Magic: tpm2.TPMGeneratedValue, Type: tpm2.TPMSTAttestCertify, Attested: tpm2.NewTPMUAttest[*tpm2.TPMSCertifyInfo](tpm2.TPMSTAttestCertify, &tpm2.TPMSCertifyInfo{Name: tpm2.TPM2BName{Buffer: nil}, QualifiedName: tpm2.TPM2BName{Buffer: nil}}), ExtraData: tpm2.TPM2BData{Buffer: extraData}},
|
|
"Signature validation error: crypto/rsa: verification error",
|
|
},
|
|
{
|
|
"CertInfoPubAreaHashMismatch",
|
|
tpm2.TPMSAttest{Magic: tpm2.TPMGeneratedValue, Type: tpm2.TPMSTAttestCertify, Attested: tpm2.NewTPMUAttest[*tpm2.TPMSCertifyInfo](tpm2.TPMSTAttestCertify, &tpm2.TPMSCertifyInfo{Name: tpm2.TPM2BName{Buffer: tpm2.Marshal(tpm2.TPMTHA{HashAlg: tpm2.TPMAlgSHA256, Digest: extraData})}, QualifiedName: tpm2.TPM2BName{Buffer: nil}}), ExtraData: tpm2.TPM2BData{Buffer: h.Sum(nil)}},
|
|
"Signature validation error: crypto/rsa: verification error",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
attStmt := make(map[string]any, len(defaultAttStatement))
|
|
|
|
for id, v := range defaultAttStatement {
|
|
attStmt[id] = v
|
|
}
|
|
|
|
attStmt[stmtX5C] = []any{mustConvertPEMToPEMBytes(t, certificateAndroidKeyRoot1)}
|
|
|
|
rsaKey, _ := rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
require.GreaterOrEqual(t, rsaKey.E, 0)
|
|
require.LessOrEqual(t, int64(rsaKey.E), int64(4294967295))
|
|
|
|
e := uint32(rsaKey.E) //nolint:gosec
|
|
|
|
r := webauthncose.RSAPublicKeyData{
|
|
PublicKeyData: webauthncose.PublicKeyData{
|
|
KeyType: int64(webauthncose.RSAKey),
|
|
Algorithm: int64(webauthncose.AlgRS256),
|
|
},
|
|
Modulus: rsaKey.N.Bytes(),
|
|
Exponent: uint32ToBytes(e),
|
|
}
|
|
|
|
attStmt[stmtPubArea] = tpm2.Marshal(makeTPMTPublicRSA(&TPMRSATestParameters{Modulus: rsaKey.N.Bytes(), Exponent: e}))
|
|
rpk, _ := webauthncbor.Marshal(r)
|
|
att := AttestationObject{
|
|
AttStatement: attStmt,
|
|
AuthData: AuthenticatorData{
|
|
AttData: AttestedCredentialData{
|
|
CredentialPublicKey: rpk,
|
|
},
|
|
},
|
|
}
|
|
|
|
att.AttStatement[stmtCertInfo] = tpm2.Marshal(tc.certInfo)
|
|
attestationType, _, err := attestationFormatValidationHandlerTPM(att, nil, nil)
|
|
|
|
if tc.err != "" {
|
|
assert.EqualError(t, err, tc.err)
|
|
} else {
|
|
assert.Equal(t, "attca", attestationType)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTPMAttestationVerificationFailX5c(t *testing.T) {
|
|
attStmt := make(map[string]any, len(defaultAttStatement))
|
|
|
|
for id, v := range defaultAttStatement {
|
|
attStmt[id] = v
|
|
}
|
|
|
|
rsaKey, _ := rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
require.GreaterOrEqual(t, rsaKey.E, 0)
|
|
require.LessOrEqual(t, int64(rsaKey.E), int64(4294967295))
|
|
|
|
e := uint32(rsaKey.E) //nolint:gosec
|
|
|
|
r := webauthncose.RSAPublicKeyData{
|
|
PublicKeyData: webauthncose.PublicKeyData{
|
|
KeyType: int64(webauthncose.RSAKey),
|
|
Algorithm: int64(webauthncose.AlgRS256),
|
|
},
|
|
Modulus: rsaKey.N.Bytes(),
|
|
Exponent: uint32ToBytes(e),
|
|
}
|
|
|
|
public := makeTPMTPublicRSA(&TPMRSATestParameters{Modulus: rsaKey.N.Bytes(), Exponent: e})
|
|
|
|
pubBytes := tpm2.Marshal(public)
|
|
|
|
attStmt[stmtPubArea] = pubBytes
|
|
rpk, _ := webauthncbor.Marshal(r)
|
|
att := AttestationObject{
|
|
AttStatement: attStmt,
|
|
AuthData: AuthenticatorData{
|
|
AttData: AttestedCredentialData{
|
|
CredentialPublicKey: rpk,
|
|
},
|
|
},
|
|
}
|
|
|
|
h := webauthncose.HasherFromCOSEAlg(webauthncose.AlgRS256)
|
|
|
|
h.Write(att.RawAuthData)
|
|
extraData := h.Sum(nil)
|
|
|
|
h.Reset()
|
|
|
|
h.Write(pubBytes)
|
|
pubName := h.Sum(nil)
|
|
|
|
certInfo := tpm2.TPMSAttest{
|
|
Magic: tpm2.TPMGeneratedValue,
|
|
Type: tpm2.TPMSTAttestCertify,
|
|
Attested: tpm2.NewTPMUAttest[*tpm2.TPMSCertifyInfo](tpm2.TPMSTAttestCertify, &tpm2.TPMSCertifyInfo{
|
|
Name: tpm2.TPM2BName{
|
|
Buffer: tpm2.Marshal(tpm2.TPMTHA{
|
|
HashAlg: tpm2.TPMAlgSHA256,
|
|
Digest: pubName,
|
|
}),
|
|
},
|
|
}),
|
|
ExtraData: tpm2.TPM2BData{
|
|
Buffer: extraData,
|
|
},
|
|
}
|
|
|
|
attStmt[stmtCertInfo] = tpm2.Marshal(certInfo)
|
|
|
|
makeX5c := func(b []byte) []any {
|
|
q := make([]any, 1)
|
|
q[0] = b
|
|
|
|
return q
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
x5c []any
|
|
err string
|
|
}{
|
|
{
|
|
"TPMNegativeTestX5CEmpty",
|
|
make([]any, 1),
|
|
"Error getting certificate from x5c cert chain",
|
|
},
|
|
{
|
|
"TPMNegativeTestX5CCanNotParse",
|
|
makeX5c(make([]byte, 1)),
|
|
"Error parsing certificate from ASN.1",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
att.AttStatement[stmtX5C] = tc.x5c
|
|
attestationType, _, err := attestationFormatValidationHandlerTPM(att, nil, nil)
|
|
|
|
if tc.err != "" {
|
|
assert.EqualError(t, err, tc.err)
|
|
} else {
|
|
assert.Equal(t, "attca", attestationType)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type TPMECCTestParameters struct {
|
|
Curve tpm2.TPMECCCurve
|
|
X []byte
|
|
Y []byte
|
|
}
|
|
|
|
type TPMRSATestParameters struct {
|
|
KeyBits tpm2.TPMIRSAKeyBits
|
|
Exponent uint32
|
|
Modulus []byte
|
|
}
|
|
|
|
var testAttestationTPMResponses = []string{
|
|
// TPM attestation with ECC P256.
|
|
`{
|
|
"id": "hsS2ywFz_LWf9-lC35vC9uJTVD3ZCVdweZvESUbjXnQ",
|
|
"rawId": "hsS2ywFz_LWf9-lC35vC9uJTVD3ZCVdweZvESUbjXnQ",
|
|
"type": "public-key",
|
|
"response": {
|
|
"attestationObject": "o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzn__mNzaWdZAQCqAcGoi2IFXCF5xxokjR5yOAwK_11iCOqt8hCkpHE9rW602J3KjhcRQzoFf1UxZvadwmYcHHMxDQDmVuOhH-yW-DfARVT7O3MzlhhzrGTNO_-jhGFsGeEdz0RgNsviDdaVP5lNsV6Pe4bMhgBv1aTkk0zx1T8sxK8B7gKT6x80RIWg89_aYY4gHR4n65SRDp2gOGI2IHDvqTwidyeaAHVPbDrF8iDbQ88O-GH_fheAtFtgjbIq-XQbwVdzQhYdWyL0XVUwGLSSuABuB4seRPkyZCKoOU6VuuQzfWNpH2Nl05ybdXi27HysUexgfPxihB3PbR8LJdi1j04tRg3JvBUvY3ZlcmMyLjBjeDVjglkFuzCCBbcwggOfoAMCAQICEGEZiaSlAkKpqaQOKDYmWPkwDQYJKoZIhvcNAQELBQAwQTE_MD0GA1UEAxM2RVVTLU5UQy1LRVlJRC1FNEE4NjY2RjhGNEM2RDlDMzkzMkE5NDg4NDc3ODBBNjgxMEM0MjEzMB4XDTIyMDExMjIyMTUxOFoXDTI3MDYxMDE4NTQzNlowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKo-7DHdiipZTzfA9fpTaIMVK887zM0nXAVIvU0kmGAsPpTYbf7dn1DAl6BhcDkXs2WrwYP02K8RxXWOF4jf7esMAIkr65zPWqLys8WRNM60d7g9GOADwbN8qrY0hepSsaJwjhswbNJI6L8vJwnnrQ6UWVCm3xHqn8CB2iSWNSUnshgTQTkJ1ZEdToeD51sFXUE0fSxXjyIiSAAD4tCIZkmHFVqchzfqUgiiM_mbbKzUnxEZ6c6r39ccHzbm4Ir-u62repQnVXKTpzFBbJ-Eg15REvw6xuYaGtpItk27AXVcEodfAylf7pgQPfExWkoMZfb8faqbQAj5x29mBJvlzj0CAwEAAaOCAeowggHmMA4GA1UdDwEB_wQEAwIHgDAMBgNVHRMBAf8EAjAAMG0GA1UdIAEB_wRjMGEwXwYJKwYBBAGCNxUfMFIwUAYIKwYBBQUHAgIwRB5CAFQAQwBQAEEAIAAgAFQAcgB1AHMAdABlAGQAIAAgAFAAbABhAHQAZgBvAHIAbQAgACAASQBkAGUAbgB0AGkAdAB5MBAGA1UdJQQJMAcGBWeBBQgDMFAGA1UdEQEB_wRGMESkQjBAMT4wEAYFZ4EFAgIMB05QQ1Q3NXgwFAYFZ4EFAgEMC2lkOjRFNTQ0MzAwMBQGBWeBBQIDDAtpZDowMDA3MDAwMjAfBgNVHSMEGDAWgBQ3yjAtSXrnaSNOtzy1PEXxOO1ZUDAdBgNVHQ4EFgQU1ml3H5Tzrs0Nev69tFNhPZnhaV0wgbIGCCsGAQUFBwEBBIGlMIGiMIGfBggrBgEFBQcwAoaBkmh0dHA6Ly9hemNzcHJvZGV1c2Fpa3B1Ymxpc2guYmxvYi5jb3JlLndpbmRvd3MubmV0L2V1cy1udGMta2V5aWQtZTRhODY2NmY4ZjRjNmQ5YzM5MzJhOTQ4ODQ3NzgwYTY4MTBjNDIxMy9lMDFjMjA2Mi1mYmRjLTQwYTUtYTQwZi1jMzc3YzBmNzY1MWMuY2VyMA0GCSqGSIb3DQEBCwUAA4ICAQAz-YGrj0S841gyMZuit-qsKpKNdxbkaEhyB1baexHGcMzC2y1O1kpTrpaH3I80hrIZFtYoA2xKQ1j67uoC6vm1PhsJB6qhs9T7zmWZ1VtleJTYGNZ_bYY2wo65qJHFB5TXkevJUVe2G39kB_W1TKB6g_GSwb4a5e4D_Sjp7b7RZpyIKHT1_UE1H4RXgR9Qi68K4WVaJXJUS6T4PHrRc4PeGUoJLQFUGxYokWIf456G32GwGgvUSX76K77pVv4Y-kT3v5eEJdYxlS4EVT13a17KWd0DdLje0Ae69q_DQSlrHVLUrADvuZMeM8jxyPQvDb7ETKLsSUeHm73KOCGLStcGQ3pB49nt3d9XdWCcUwUrmbBF2G7HsRgTNbj16G6QUcWroQEqNrBG49aO9mMZ0NwSn5d3oNuXSXjLdGBXM1ukLZ-GNrZDYw5KXU102_5VpHpjIHrZh0dXg3Q9eucKe6EkFbH65-O5VaQWUnR5WJpt6-fl_l0iHqHnKXbgL6tjeerCqZWDvFsOak05R-hosAoQs_Ni0EsgZqHwR_VlG86fsSwCVU3_sDKTNs_Je08ewJ_bbMB5Tq6k1Sxs8Aw8R96EwjQLp3z-Zva1myU-KerYYVDl5BdvgPqbD8Xmst-z6vrP3CJbtr8jgqVS7RWy_cJOA8KCZ6IS_75QT7Gblq6UGFkG7zCCBuswggTToAMCAQICEzMAAAbTtnznKsOrB-gAAAAABtMwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNDAeFw0yMTA2MTAxODU0MzZaFw0yNzA2MTAxODU0MzZaMEExPzA9BgNVBAMTNkVVUy1OVEMtS0VZSUQtRTRBODY2NkY4RjRDNkQ5QzM5MzJBOTQ4ODQ3NzgwQTY4MTBDNDIxMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJA7GLwHWWbn2H8DRppxQfre4zll1sgE3Wxt9DTYWt5-v-xKwCQb6z_7F1py7LMe58qLqglAgVhS6nEvN2puZ1GzejdsFFxz2gyEfH1y-X3RGp0dxS6UKwEtmksaMEKIRQn2GgKdUkiuvkaxaoznuExoTPyu0aXk6yFsX5KEDu9UZCgt66bRy6m3KIRnn1VK2frZfqGYi8C8x9Q69oGG316tUwAIm3ypDtv3pREXsDLYE1U5Irdv32hzJ4CqqPyau-qJS18b8CsjvgOppwXRSwpOmU7S3xqo-F7h1eeFw2tgHc7PEPt8MSSKeba8Fz6QyiLhgFr8jFUvKRzk4B41HFUMqXYawbhAtfIBiGGsGrrdNKb7MxISnH1E6yLVCQGGhXiN9U7V0h8Gn56eKzopGlubw7yMmgu8Cu2wBX_a_jFmIBHnn8YgwcRm6NvT96KclDHnFqPVm3On12bG31F7EYkIRGLbaTT6avEu9rL6AJn7Xr245Sa6dC_OSMRKqLSufxp6O6f2TH2g4kvT0Go9SeyM2_acBjIiQ0rFeBOm49H4E4VcJepf79FkljovD68imeZ5MXjxepcCzS138374Jeh7k28JePwJnjDxS8n9Dr6xOU3_wxS1gN5cW6cXSoiPGe0JM4CEyAcUtKrvpUWoTajxxnylZuvS8ou2thfH2PQlAgMBAAGjggGOMIIBijAOBgNVHQ8BAf8EBAMCAoQwGwYDVR0lBBQwEgYJKwYBBAGCNxUkBgVngQUIAzAWBgNVHSAEDzANMAsGCSsGAQQBgjcVHzASBgNVHRMBAf8ECDAGAQH_AgEAMB0GA1UdDgQWBBQ3yjAtSXrnaSNOtzy1PEXxOO1ZUDAfBgNVHSMEGDAWgBR6jArOL0hiF-KU0a5VwVLscXSkVjBwBgNVHR8EaTBnMGWgY6Bhhl9odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUUE0lMjBSb290JTIwQ2VydGlmaWNhdGUlMjBBdXRob3JpdHklMjAyMDE0LmNybDB9BggrBgEFBQcBAQRxMG8wbQYIKwYBBQUHMAKGYWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVFBNJTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAxNC5jcnQwDQYJKoZIhvcNAQELBQADggIBAFZTSitCISvll6i6rPUPd8Wt2mogRw6I_c-dWQzdc9-SY9iaIGXqVSPKKOlAYU2ju7nvN6AvrIba6sngHeU0AUTeg1UZ5-bDFOWdSgPaGyH_EN_l-vbV6SJPzOmZHJOHfw2WT8hjlFaTaKYRXxzFH7PUR4nxGRbWtdIGgQhUlWg5oo_FO4bvLKfssPSONn684qkAVierq-ly1WeqJzOYhd4EylgVJ9NL3YUhg8dYcHAieptDzF7OcDqffbuZLZUx6xcyibhWQcntAh7a3xPwqXxENsHhme_bqw_kqa-NVk-Wz4zdoiNNLRvUmCSL1WLc4JPsFJ08Ekn1kW7f9ZKnie5aw-29jEf6KIBt4lGDD3tXTfaOVvWcDbu92jMOO1dhEIj63AwQiDJgZhqnrpjlyWU_X0IVQlaPBg80AE0Y3sw1oMrY0XwdeQUjSpH6e5fTYKrNB6NMT1jXGjKIzVg8XbPWlnebP2wEhq8rYiDR31b9B9Sw_naK7Xb-Cqi-VQdUtknSjeljusrBpxGUx-EIJci0-dzeXRT5_376vyKSuYxA1Xd2jd4EknJLIAVLT3rb10DCuKGLDgafbsfTBxVoEa9hSjYOZUr_m3WV6t6I9WPYjVyhyi7fCEIG4JE7YbM4na4jg5q3DM8ibE8jyufAq0PfJZTJyi7c2Q2N_9NgnCNwZ3B1YkFyZWFYdgAjAAsABAByACCd_8vzbDg65pn7mGjcbcuJ1xU4hL4oA5IsEkFYv60irgAQABAAAwAQACAek7g2C8TeORRoKxuN7HrJ5OinVGuHzEgYODyUsF9D1wAggXPPXn-Pm_4IF0c4XVaJjmHO3EB2KBwdg_L60N0IL9xoY2VydEluZm9Yof9UQ0eAFwAiAAvQNGTLa2wT6u8SKDDdwkgaq5Cmh6jcD_6ULvM9ZmvdbwAUtMInD3WtGSdWHPWijMrW_TfYo-gAAAABPuBems3Sywu4aQsGAe85iOosjtXIACIAC5FPRiZSJzjYMNnAz9zFtM62o57FJwv8F5gNEcioqhHwACIACyVXxq1wZhDsqTqdYr7vQUUJ3vwWVrlN0ZQv5HFnHqWdaGF1dGhEYXRhWKR0puqSE8mcL3SyJJKzIM9AJiqUwalQoDl_KSULYIQe8EUAAAAACJhwWMrcS4G24TDeUNy-lgAghsS2ywFz_LWf9-lC35vC9uJTVD3ZCVdweZvESUbjXnSlAQIDJiABIVggHpO4NgvE3jkUaCsbjex6yeTop1Rrh8xIGDg8lLBfQ9ciWCCBc89ef4-b_ggXRzhdVomOYc7cQHYoHB2D8vrQ3Qgv3A",
|
|
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidXpuOXUwVHgtTEJkdEdnRVJzYmtIUkJqaVV0NWkycnZtMkJCVFpyV3FFbyIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViYXV0aG4uaW8iLCJjcm9zc09yaWdpbiI6ZmFsc2V9"
|
|
}
|
|
}`,
|
|
// TPM attestation with RSA SHA1.
|
|
`{
|
|
"rawId": "UJDoUJoGiDQF_EEZ3G_z9Lfq16_KFaXtMTjwTUrrRlc",
|
|
"id": "UJDoUJoGiDQF_EEZ3G_z9Lfq16_KFaXtMTjwTUrrRlc",
|
|
"response": {
|
|
"clientDataJSON": "eyJvcmlnaW4iOiJodHRwczovL2xvY2FsaG9zdDo0NDMyOSIsImNoYWxsZW5nZSI6IjlKeVVmSmtnOFBxb0tadUQ3Rkh6T0U5ZGJ5Y3VsQzl1ckdUcEdxQm5Fd25oS21uaTRyR1JYeG0zLVpCSEs4eDZyaUpRcUlwQzhxRWEtVDBxSUZUS1RRIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9",
|
|
"attestationObject": "o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzn__mNzaWdZAQBIwu9LPAl-LgxlRzPlvn7L-0yuMnFFn1XALxXtGnmC5-oMIIqfUJWFbgBbkN2l2zPsqOCRT5GQU8ucKNI6HrlbuDAUIq7wjcxG5TzgQt3YtGMWtgEcrZn2ecUlQFKjY67_wZIuHLy443Ki1SjErNPrMrkIPe9lyFhIalMgrWLCol40gYIVr_9xLfgyX55c7XiB-XbUKhDLUv5uPA3CSAiWeWwWx26K2BTV85vHsaG6f2YFTfcQTFs1cTSwMm7A9C2SiQ7N01ENwM1urVxlCvuEsBgiXapR70Oyq_cfiENYY0ti7_w2fvikmfv0z0O1cJOAyUlYWjnWhT707chrVmkFY3ZlcmMyLjBjeDVjglkEXzCCBFswggNDoAMCAQICDwRsOt2imXnV5Z4BftcqfzANBgkqhkiG9w0BAQsFADBBMT8wPQYDVQQDEzZOQ1UtTlRDLUtFWUlELTM2MTA0Q0U0MEJCQ0MxRjQwRDg0QTRCQkQ1MEJFOTkwMjREOTU3RDQwHhcNMTgwMjAxMDAwMDAwWhcNMjUwMTMxMjM1OTU5WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmw-4ficURR_sgVfW7cs1iRoDGdxjBpCczF233ba_5WTP-RrsYZPlzWgSN9WXptuywzjZoDlbid7NlduSR1ZFsds4bW71LyKDL62eyqaiAc645gocXAyxdDIDJAeo-3N9Dm4vsw-Gy_0sd2v1UEkBhWjuE1gL5hcaB9EtXSDvHPwmrf0eYn_4cWu9AxqSxpn79JIPYEOUrURr2H8zyG4_P0j1a3MVBmtAymhpXBn9ila-bW7K_k0JYXBh5yAYZDsmHgFsXbUauDWdja3HYzkep9jXkFcegXOMjPr_QSqWRjawEvzoprnJ-QqoWNbaRhuD-UnfgCNbwseU8kZ0aQNjBQIDAQABo4IBjzCCAYswDgYDVR0PAQH_BAQDAgeAMAwGA1UdEwEB_wQCMAAwUwYDVR0gAQH_BEkwRzBFBgkrBgEEAYI3FR8wODA2BggrBgEFBQcCAjAqEyhGQUtFIEZJRE8gVENQQSBUcnVzdGVkIFBsYXRmb3JtIElkZW50aXR5MBAGA1UdJQQJMAcGBWeBBQgDMEoGA1UdEQEB_wRAMD6kPDA6MTgwDgYFZ4EFAgMMBWlkOjEzMBAGBWeBBQICDAdOUENUNnh4MBQGBWeBBQIBDAtpZDpGRkZGRjFEMDAfBgNVHSMEGDAWoBRRfyLI5lOlfNVM3TBYfjD_ZzaMXTAdBgNVHQ4EFgQUO6SUmiOhCHVZcq-88acg2uQkQz8weAYIKwYBBQUHAQEEbDBqMGgGCCsGAQUFBzAChlxodHRwczovL2ZpZG9hbGxpYW5jZS5jby5uei90cG1wa2kvTkNVLU5UQy1LRVlJRC0zNjEwNENFNDBCQkNDMUY0MEQ4NEE0QkJENTBCRTk5MDI0RDk1N0Q0LmNydDANBgkqhkiG9w0BAQsFAAOCAQEAIIyVBkck_SD2nbj4KOwUI6cYZHrjwrcULoEiOSXn9TjTIiB5MdBMvqqNyAXiyWoWd1GEc_MI3mKOzu4g5UTVQQqfiOTrqfuZrpoU0tAeojKnZLj2wYj5GpyOfEkPK3m9qVaDxiYrh6aS8a3w_Iog878EiIaoVALbBt5uAfh0TAHHwSdxHtU8DRJrC43yIqcP9byRqssJmgSNcpMAjw_hcKJxDMD2UurvsMasqyWvK533yNA0-VwXvk3HI0ItSOw_g352D-qOTHI82lJIjc3yKoaNeYKn7RzgcLAF7AesTiiJReY2kU_vLyf-wH54-08T3oyBBJpBCHc1y_Lt5d2qWFkGCDCCBgQwggPsoAMCAQICENBTpEeEh5lpTgeR7VT9oQcwDQYJKoZIhvcNAQELBQAwgb8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMQwwCgYDVQQLDANDV0cxNjA0BgNVBAMMLUZJRE8gRmFrZSBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxODExMC8GCSqGSIb3DQEJARYiY29uZm9ybWFuY2UtdG9vbHNAZmlkb2FsbGlhbmNlLm9yZzAeFw0xNzAyMDEwMDAwMDBaFw0zNTAxMzEyMzU5NTlaMEExPzA9BgNVBAMTNk5DVS1OVEMtS0VZSUQtMzYxMDRDRTQwQkJDQzFGNDBEODRBNEJCRDUwQkU5OTAyNEQ5NTdENDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANc-c30RpQd-_LCoiLJbXz3t_vqciOIovwjez79_DtVgi8G9Ph-tPL-lC0ueFGBMSPcKd_RDdSFe2QCYQd9e0DtiFxra-uWGa0olI1hHI7bK2GzNAZSTKEbwgqpf8vXMQ-7SPajg6PfxSOLH_Nj2yd6tkNkUSdlGtWfY8XGB3n-q--nt3UHdUQWEtgUoTe5abBXsG7MQSuTNoad3v6vk-tLd0W44ivM6pbFqFUHchx8mGLApCpjlVXrfROaCoc9E91hG9B-WNvekJ0dM6kJ658Hy7yscQ6JdqIEolYojCtWaWNmwcfv--OE1Ax_4Ub24gl3hpB9EOcBCzpb4UFmLYUECAwEAAaOCAXcwggFzMAsGA1UdDwQEAwIBhjAWBgNVHSAEDzANMAsGCSsGAQQBgjcVHzAbBgNVHSUEFDASBgkrBgEEAYI3FSQGBWeBBQgDMBIGA1UdEwEB_wQIMAYBAf8CAQAwHwYDVR0OBBgEFsIUUX8iyOZTpXzVTN0wWH4w_2c2jF0wHwYDVR0jBBgwFqAUXH82LZCtWry6jnXa3jqg7cFOAoswaAYDVR0fBGEwXzBdoFugWYZXaHR0cHM6Ly9maWRvYWxsaWFuY2UuY28ubnovdHBtcGtpL2NybC9GSURPIEZha2UgVFBNIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTguY3JsMG8GCCsGAQUFBwEBBGMwYTBfBggrBgEFBQcwAoZTaHR0cHM6Ly9maWRvYWxsaWFuY2UuY28ubnovdHBtcGtpL0ZJRE8gRmFrZSBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxOC5jcnQwDQYJKoZIhvcNAQELBQADggIBAG138t55DF9nPJbvbPQZOypmyTPpNne0A5fh69P1fHZ5qdE2PDz3cf5Tl-8OPI4xQniEFNPcXMb7KlhMM6zCl4GkZtNN4MxygdFjQ1gTZOBDpt7Dwziij0MakmwyC0RYTNtbSyVhHUevgw9rnu13EzqxPyL5JD-UqADh2Y51MS0qy7IOgegLQv-eJzSNUgHxFJreUzz4PU6yzSsTyyYDW-H4ZjAQKienVp8ewZf8oHGWHGQFGa5E9m1P8vxCMZ7pIzeQweCVYrs3q7unu4nzBAIXLPI092kYFUgyz3lIaSB3XEiPBokpupX6Zmgrfphb-XX3tbenH5hkxfumueA5RMHTMu5TVjhJXiV0yM3q5W5xrQHdJlF5nOdJDEE-Kb7nm6xaT1DDpafqBc5vEDMkJmBA4AXHUY7JPGqEEzEenT7k6Wn5IQLZg4qc8Irnj__yM7xUhJWJam47KVbLA4WFu-IKvJrkP5GSglZ9qASOCxBHaOL2UcTAg50uvhUSwur2KSak2vlENdmAijwdAL4LLQWrkFd-9NBwcNwTdfK4ekEHP1l4BwJtkNwW6etUgeA5rkW2JLocXoBq5v7GSk4_CBoKhyiahQGQQ9SZFGeBJhzzkK9yN-yKskcVjjjInSHPl-ZpeOK3sI08sEyTH0gxlTtRoX0MKDsMAHEVToe5o1u9Z3B1YkFyZWFZATYAAQALAAYEcgAgnf_L82w4OuaZ-5ho3G3LidcVOIS-KAOSLBJBWL-tIq4AEAAQCAAAAAAAAQCl9siJwqoHJ2pCwEKyLQ_u6zGcZDKZtA0jtvtn1aPlIe7wFAvQNgjI6KDiQsDPTCVeJj_RA441VbV0Z4oX2b68quDY0Gf4VpF4KWfNPdKH6H4E882m8OnBb10mhaNbPxTmDVDZLQZjh3ubX1Z56FNg6cQmz4bEnHF-7X1l7AcNORhzdzgM7uRXhwo9UsAzpu4Io1OCTsb5DaDnng3f3Y9qDn8OG3MI_5IYtm1qGgmY72nSEiIhhPCk2lvmajN6A4tWgUstc7QtdlKEPBd-ITtGdKYTSwqihaHzBQd8D-d_HDqgcOWECLKo51_YqyaEiuGlv6sPon1LMsEL6PlVw47PaGNlcnRJbmZvWKH_VENHgBcAIgALEeaO1E21Ny4UKW4vhKzHg5h1GIGSHjD8IqBvi3PHlFMAFF6MXAvgUX_Rbc04fmdB2TyLG-mdAAAAAUdwF0hVaXtLxoVgpQFzfvmNNFZV-wAiAAuYlrm-5Jg3251TsEdZ8NV11xd4X5O3q0AFLmammw658QAiAAtuzX-04mcxAHq9kO70Ew3vJCOmCS0UvQzZB2CNCeGXpWhhdXRoRGF0YVkBZ0mWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAHXyRLZ-U2RP1Z-Qw5YicxfbACBQkOhQmgaINAX8QRncb_P0t-rXr8oVpe0xOPBNSutGV6QBAwM5__4gWQEApfbIicKqBydqQsBCsi0P7usxnGQymbQNI7b7Z9Wj5SHu8BQL0DYIyOig4kLAz0wlXiY_0QOONVW1dGeKF9m-vKrg2NBn-FaReClnzT3Sh-h-BPPNpvDpwW9dJoWjWz8U5g1Q2S0GY4d7m19WeehTYOnEJs-GxJxxfu19ZewHDTkYc3c4DO7kV4cKPVLAM6buCKNTgk7G-Q2g554N392Pag5_DhtzCP-SGLZtahoJmO9p0hIiIYTwpNpb5mozegOLVoFLLXO0LXZShDwXfiE7RnSmE0sKooWh8wUHfA_nfxw6oHDlhAiyqOdf2KsmhIrhpb-rD6J9SzLBC-j5VcOOzyFDAQAB"
|
|
},
|
|
"type": "public-key"
|
|
}`,
|
|
// TPM attestation with RSA SHA256.
|
|
`{
|
|
"rawId": "h9XMhkVePN1Prq9Ks_VfwIsVZvt-jmSRTEnevTc-KB8",
|
|
"id": "h9XMhkVePN1Prq9Ks_VfwIsVZvt-jmSRTEnevTc-KB8",
|
|
"response": {
|
|
"clientDataJSON": "eyJvcmlnaW4iOiJodHRwczovL2xvY2FsaG9zdDo0NDMyOSIsImNoYWxsZW5nZSI6ImdIckFrNHBOZTJWbEIwSExlS2NsSTJQNlFFYTgzUHVHZWlqVEhNdHBiaFk5S2x5YnlobHdGX1Z6UmU3eWhhYlhhZ1d1WTZya0RXZnZ2aE5xZ2gybzdBIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9",
|
|
"attestationObject": "o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzkBAGNzaWdZAQA6Gh1Oa3-8vCY8bTrpUHA4zp4UCsbuh36tH09G-qWlvQdoqEQsJJQu1Rz61_mFes9CXE2cxiJV8pEwxtUUTSZQWnamVU1x9bBk07qcHqAuamP_NDAahHhZ9D46q9JklT3aVdhbaZVh0y5b8NZB2eUfKqcUmM0JCxLP9ZfSe7XcVguhQVEduM6Qnl9R1zRh7cquOa8UOEpdXkt1-drsOtrA9c0UJPYzkI8qscCDc-xfzo2xv12tLXjRq395JnynHhjzJIz8Ch2IYQUiMSM6TQDcnvzDEvRgril9NC0aIkHd79omIZNnBjEDfjyqOZbBffjGyvt1Eikz4M0EE8e7N4uRY3ZlcmMyLjBjeDVjglkEXzCCBFswggNDoAMCAQICDwQ_ozlil_l5hh6NlMsLzzANBgkqhkiG9w0BAQsFADBBMT8wPQYDVQQDEzZOQ1UtTlRDLUtFWUlELTM2MTA0Q0U0MEJCQ0MxRjQwRDg0QTRCQkQ1MEJFOTkwMjREOTU3RDQwHhcNMTgwMjAxMDAwMDAwWhcNMjUwMTMxMjM1OTU5WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAor_6-4WYizZdOQ9Ia_offaIdL2BVGtGDq8jQxo16ymBSOWCP15gZt9QAkqowS3ayqEh48Pg5SdA7F5kcjD_FqKaZDBOqkjvJivdo7FKv7EaUI2al9B7h0pXIRb97jn2z0zPlXz6RV_RmBe3CCljyxrhav7bTkCXEJUnkNgxsWgLGBIW6VSVct0z42xBB6_6mYekWIej5vXLqB8AuzsqnLbU5jOohfJiI5urFso12j6YCWZ_kXK4j8e4IoHUOjWgtHXdb3kP8PvI948hcJpIEpuuLDZDDOCOPI1wAlryGwz_tJLarODZzD1XhG3BMlXi1TG7x1s-AriC3A7B89wuSpwIDAQABo4IBjzCCAYswDgYDVR0PAQH_BAQDAgeAMAwGA1UdEwEB_wQCMAAwUwYDVR0gAQH_BEkwRzBFBgkrBgEEAYI3FR8wODA2BggrBgEFBQcCAjAqEyhGQUtFIEZJRE8gVENQQSBUcnVzdGVkIFBsYXRmb3JtIElkZW50aXR5MBAGA1UdJQQJMAcGBWeBBQgDMEoGA1UdEQEB_wRAMD6kPDA6MTgwDgYFZ4EFAgMMBWlkOjEzMBAGBWeBBQICDAdOUENUNnh4MBQGBWeBBQIBDAtpZDpGRkZGRjFEMDAfBgNVHSMEGDAWoBRRfyLI5lOlfNVM3TBYfjD_ZzaMXTAdBgNVHQ4EFgQUS1ZtGu6ZoewTH3mq04Ytxa4kOQcweAYIKwYBBQUHAQEEbDBqMGgGCCsGAQUFBzAChlxodHRwczovL2ZpZG9hbGxpYW5jZS5jby5uei90cG1wa2kvTkNVLU5UQy1LRVlJRC0zNjEwNENFNDBCQkNDMUY0MEQ4NEE0QkJENTBCRTk5MDI0RDk1N0Q0LmNydDANBgkqhkiG9w0BAQsFAAOCAQEAbp-Xp9W0vyY08YUHxerc6FnFdXZ6KFuQTZ4hze60BWexCSQOee25gqOoQaQr9ufS3ImLAoV4Ifc3vKVBQvBRwMjG3pJINoWr0p2McI0F2SNclH4M0sXFYHRlmHQ2phZB6Ddd-XL8PsGyiXRI6gVacVw5ZiVEBsRrekLH-Zy25EeqS3SxaBVnEd-HZ6BGGgbflgFtyGP9fQ5YSORC-Btno_uJbmRiZm4iHiEULp9wWEWOJIOXv9tVQKsYpPg58L1_Dgc8oml1YG5a8qK3jaR77tcUgZyYy5GOk1zIsXv36f0SkmLcNTiTjrhdGVcKs2KpW5fQgm_llQ5cvhR1jlY6dFkGCDCCBgQwggPsoAMCAQICENBTpEeEh5lpTgeR7VT9oQcwDQYJKoZIhvcNAQELBQAwgb8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNWTESMBAGA1UEBwwJV2FrZWZpZWxkMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMQwwCgYDVQQLDANDV0cxNjA0BgNVBAMMLUZJRE8gRmFrZSBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxODExMC8GCSqGSIb3DQEJARYiY29uZm9ybWFuY2UtdG9vbHNAZmlkb2FsbGlhbmNlLm9yZzAeFw0xNzAyMDEwMDAwMDBaFw0zNTAxMzEyMzU5NTlaMEExPzA9BgNVBAMTNk5DVS1OVEMtS0VZSUQtMzYxMDRDRTQwQkJDQzFGNDBEODRBNEJCRDUwQkU5OTAyNEQ5NTdENDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANc-c30RpQd-_LCoiLJbXz3t_vqciOIovwjez79_DtVgi8G9Ph-tPL-lC0ueFGBMSPcKd_RDdSFe2QCYQd9e0DtiFxra-uWGa0olI1hHI7bK2GzNAZSTKEbwgqpf8vXMQ-7SPajg6PfxSOLH_Nj2yd6tkNkUSdlGtWfY8XGB3n-q--nt3UHdUQWEtgUoTe5abBXsG7MQSuTNoad3v6vk-tLd0W44ivM6pbFqFUHchx8mGLApCpjlVXrfROaCoc9E91hG9B-WNvekJ0dM6kJ658Hy7yscQ6JdqIEolYojCtWaWNmwcfv--OE1Ax_4Ub24gl3hpB9EOcBCzpb4UFmLYUECAwEAAaOCAXcwggFzMAsGA1UdDwQEAwIBhjAWBgNVHSAEDzANMAsGCSsGAQQBgjcVHzAbBgNVHSUEFDASBgkrBgEEAYI3FSQGBWeBBQgDMBIGA1UdEwEB_wQIMAYBAf8CAQAwHwYDVR0OBBgEFsIUUX8iyOZTpXzVTN0wWH4w_2c2jF0wHwYDVR0jBBgwFqAUXH82LZCtWry6jnXa3jqg7cFOAoswaAYDVR0fBGEwXzBdoFugWYZXaHR0cHM6Ly9maWRvYWxsaWFuY2UuY28ubnovdHBtcGtpL2NybC9GSURPIEZha2UgVFBNIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTguY3JsMG8GCCsGAQUFBwEBBGMwYTBfBggrBgEFBQcwAoZTaHR0cHM6Ly9maWRvYWxsaWFuY2UuY28ubnovdHBtcGtpL0ZJRE8gRmFrZSBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxOC5jcnQwDQYJKoZIhvcNAQELBQADggIBAG138t55DF9nPJbvbPQZOypmyTPpNne0A5fh69P1fHZ5qdE2PDz3cf5Tl-8OPI4xQniEFNPcXMb7KlhMM6zCl4GkZtNN4MxygdFjQ1gTZOBDpt7Dwziij0MakmwyC0RYTNtbSyVhHUevgw9rnu13EzqxPyL5JD-UqADh2Y51MS0qy7IOgegLQv-eJzSNUgHxFJreUzz4PU6yzSsTyyYDW-H4ZjAQKienVp8ewZf8oHGWHGQFGa5E9m1P8vxCMZ7pIzeQweCVYrs3q7unu4nzBAIXLPI092kYFUgyz3lIaSB3XEiPBokpupX6Zmgrfphb-XX3tbenH5hkxfumueA5RMHTMu5TVjhJXiV0yM3q5W5xrQHdJlF5nOdJDEE-Kb7nm6xaT1DDpafqBc5vEDMkJmBA4AXHUY7JPGqEEzEenT7k6Wn5IQLZg4qc8Irnj__yM7xUhJWJam47KVbLA4WFu-IKvJrkP5GSglZ9qASOCxBHaOL2UcTAg50uvhUSwur2KSak2vlENdmAijwdAL4LLQWrkFd-9NBwcNwTdfK4ekEHP1l4BwJtkNwW6etUgeA5rkW2JLocXoBq5v7GSk4_CBoKhyiahQGQQ9SZFGeBJhzzkK9yN-yKskcVjjjInSHPl-ZpeOK3sI08sEyTH0gxlTtRoX0MKDsMAHEVToe5o1u9Z3B1YkFyZWFZATYAAQALAAYEcgAgnf_L82w4OuaZ-5ho3G3LidcVOIS-KAOSLBJBWL-tIq4AEAAQCAAAAAAAAQDPtSggWlsjcFiQO61-hUF8i-3FPcyvuARcy3p1seZ-_B4ClhNh5U-T0v0flMU5p6nsNDWj4f6-soe-2vVJMTm2d26uKYD2zwdrkrYYXRu5IFqUXqF-kY99v8RcrAF7DQKDo-E4XhiMz6uECvnjEloGfTYZrVuQ1mdjQ8Qki7U-9SQHMW_IsaI8ZKHtupXNhM5YPQyFbDHHXSE_iyPGh2mY4SR466ouesIuG0NccCUk5UDIvS__OUmNaX7aBrKTlnkMFjkCA1ZDFC99ZQoLFCJQHqnOU7m8zSvTJpUyG2feWgAL2Gl05V3I_lb_v5yELXcihFoA33QIOSpDmKqKV3SXaGNlcnRJbmZvWK3_VENHgBcAIgALEeaO1E21Ny4UKW4vhKzHg5h1GIGSHjD8IqBvi3PHlFMAIBo8rAwJFDGsmQjauX_FCBQenvBa2ApBcR_gOx2qW2QAAAAAAUdwF0hVaXtLxoVgpQFzfvmNNFZV-wAiAAsXPoJSq0uhvU6VLf0uIelHBNFHEanasKAoTp-lQ2dRGAAiAAuO1HPzTRRabZhwPvHQh0b1MnLIG8EVGNfpshASWSfjQWhhdXRoRGF0YVkBZ0mWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAEOn1tk6ig0R6JqUps9xBy9zACCH1cyGRV483U-ur0qz9V_AixVm-36OZJFMSd69Nz4oH6QBAwM5AQAgWQEAz7UoIFpbI3BYkDutfoVBfIvtxT3Mr7gEXMt6dbHmfvweApYTYeVPk9L9H5TFOaep7DQ1o-H-vrKHvtr1STE5tndurimA9s8Ha5K2GF0buSBalF6hfpGPfb_EXKwBew0Cg6PhOF4YjM-rhAr54xJaBn02Ga1bkNZnY0PEJIu1PvUkBzFvyLGiPGSh7bqVzYTOWD0MhWwxx10hP4sjxodpmOEkeOuqLnrCLhtDXHAlJOVAyL0v_zlJjWl-2gayk5Z5DBY5AgNWQxQvfWUKCxQiUB6pzlO5vM0r0yaVMhtn3loAC9hpdOVdyP5W_7-chC13IoRaAN90CDkqQ5iqild0lyFDAQAB"
|
|
},
|
|
"type": "public-key"
|
|
}`,
|
|
}
|
|
|
|
func makeTPMTPublicRSA(params *TPMRSATestParameters) (public tpm2.TPMTPublic) {
|
|
var (
|
|
bits tpm2.TPMIRSAKeyBits
|
|
unique tpm2.TPMUPublicID
|
|
exp uint32
|
|
)
|
|
|
|
if params != nil {
|
|
exp = params.Exponent
|
|
|
|
if params.KeyBits != 0 {
|
|
bits = params.KeyBits
|
|
} else {
|
|
bits = tpm2.TPMIRSAKeyBits(2048)
|
|
}
|
|
|
|
if len(params.Modulus) > 0 {
|
|
unique = tpm2.NewTPMUPublicID[*tpm2.TPM2BPublicKeyRSA](tpm2.TPMAlgRSA, &tpm2.TPM2BPublicKeyRSA{Buffer: params.Modulus})
|
|
}
|
|
} else {
|
|
bits = tpm2.TPMIRSAKeyBits(2048)
|
|
}
|
|
|
|
public = tpm2.TPMTPublic{
|
|
Type: tpm2.TPMAlgRSA,
|
|
NameAlg: tpm2.TPMAlgSHA256,
|
|
ObjectAttributes: tpm2.TPMAObject{
|
|
SignEncrypt: true,
|
|
Restricted: true,
|
|
FixedTPM: true,
|
|
FixedParent: true,
|
|
SensitiveDataOrigin: true,
|
|
UserWithAuth: true,
|
|
},
|
|
AuthPolicy: tpm2.TPM2BDigest{},
|
|
Parameters: tpm2.NewTPMUPublicParms[*tpm2.TPMSRSAParms](tpm2.TPMAlgRSA, &tpm2.TPMSRSAParms{
|
|
Symmetric: tpm2.TPMTSymDefObject{
|
|
Algorithm: tpm2.TPMAlgNull,
|
|
},
|
|
Scheme: tpm2.TPMTRSAScheme{
|
|
Scheme: tpm2.TPMAlgRSASSA,
|
|
Details: tpm2.NewTPMUAsymScheme[*tpm2.TPMSSigSchemeRSASSA](tpm2.TPMAlgRSASSA, &tpm2.TPMSSigSchemeRSASSA{
|
|
HashAlg: tpm2.TPMAlgSHA256,
|
|
}),
|
|
},
|
|
Exponent: exp,
|
|
KeyBits: bits,
|
|
}),
|
|
Unique: unique,
|
|
}
|
|
|
|
return public
|
|
}
|
|
|
|
func makeTPMTPublicECDSA(params *TPMECCTestParameters) tpm2.TPMTPublic {
|
|
var (
|
|
curve tpm2.TPMECCCurve
|
|
x, y []byte
|
|
)
|
|
|
|
if params != nil {
|
|
if params.Curve != tpm2.TPMECCNone {
|
|
curve = params.Curve
|
|
}
|
|
|
|
x, y = params.X, params.Y
|
|
}
|
|
|
|
if curve == tpm2.TPMECCNone {
|
|
curve = tpm2.TPMECCNistP256
|
|
}
|
|
|
|
return tpm2.TPMTPublic{
|
|
Type: tpm2.TPMAlgECC,
|
|
NameAlg: tpm2.TPMAlgSHA256,
|
|
ObjectAttributes: tpm2.TPMAObject{
|
|
SignEncrypt: true,
|
|
Restricted: true,
|
|
FixedTPM: true,
|
|
FixedParent: true,
|
|
SensitiveDataOrigin: true,
|
|
UserWithAuth: true,
|
|
},
|
|
AuthPolicy: tpm2.TPM2BDigest{},
|
|
Parameters: tpm2.NewTPMUPublicParms[*tpm2.TPMSECCParms](tpm2.TPMAlgECC, &tpm2.TPMSECCParms{
|
|
Symmetric: tpm2.TPMTSymDefObject{
|
|
Algorithm: tpm2.TPMAlgNull,
|
|
},
|
|
Scheme: tpm2.TPMTECCScheme{
|
|
Scheme: tpm2.TPMAlgECDSA,
|
|
Details: tpm2.NewTPMUAsymScheme[*tpm2.TPMSSigSchemeECDSA](tpm2.TPMAlgECDSA, &tpm2.TPMSSigSchemeECDSA{
|
|
HashAlg: tpm2.TPMAlgSHA256,
|
|
}),
|
|
},
|
|
CurveID: curve,
|
|
KDF: tpm2.TPMTKDFScheme{Scheme: tpm2.TPMAlgNull},
|
|
}),
|
|
Unique: tpm2.NewTPMUPublicID(
|
|
tpm2.TPMAlgECC,
|
|
&tpm2.TPMSECCPoint{
|
|
X: tpm2.TPM2BECCParameter{Buffer: x},
|
|
Y: tpm2.TPM2BECCParameter{Buffer: y},
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
var defaultAttStatement = map[string]any{stmtVersion: "2.0", stmtAlgorithm: int64(-257), stmtX5C: []any{}, stmtSignature: []byte{}, stmtCertInfo: []byte{}, stmtPubArea: []byte{}}
|
|
|
|
type CredentialPublicKey struct {
|
|
KeyType int64 `cbor:"1,keyasint" json:"kty"`
|
|
Algorithm int64 `cbor:"3,keyasint" json:"alg"`
|
|
Curve int64 `cbor:"-1,keyasint,omitempty" json:"crv"`
|
|
XCoord []byte `cbor:"-2,keyasint,omitempty" json:"x"`
|
|
YCoord []byte `cbor:"-3,keyasint,omitempty" json:"y"`
|
|
Modulus []byte `cbor:"-1,keyasint,omitempty" json:"n"`
|
|
Exponent []byte `cbor:"-2,keyasint,omitempty" json:"e"`
|
|
}
|
|
|
|
func corruptBytes(in []byte) []byte {
|
|
out := make([]byte, len(in))
|
|
|
|
copy(out, in)
|
|
|
|
out[len(in)-1] ^= 0xff
|
|
|
|
return out
|
|
}
|
|
|
|
func uint32ToBytes(i uint32) []byte {
|
|
t, o := make([]byte, 4), make([]byte, 3)
|
|
|
|
binary.LittleEndian.PutUint32(t, i)
|
|
|
|
copy(o, t)
|
|
|
|
return o
|
|
}
|
|
|
|
func getTPMAttestionKeys() ([]byte, []byte, []byte, rsa.PrivateKey, ecdsa.PrivateKey, error) {
|
|
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return nil, nil, nil, rsa.PrivateKey{}, ecdsa.PrivateKey{}, err
|
|
}
|
|
|
|
if rsaKey.E < 0 || int64(rsaKey.E) > int64(math.MaxUint32) {
|
|
return nil, nil, nil, rsa.PrivateKey{}, ecdsa.PrivateKey{}, errors.New("invalid E value")
|
|
}
|
|
|
|
exp := uint32(rsaKey.E)
|
|
|
|
r := webauthncose.RSAPublicKeyData{
|
|
PublicKeyData: webauthncose.PublicKeyData{
|
|
KeyType: int64(webauthncose.RSAKey),
|
|
Algorithm: int64(webauthncose.AlgRS256),
|
|
},
|
|
Modulus: rsaKey.N.Bytes(),
|
|
Exponent: uint32ToBytes(exp),
|
|
}
|
|
|
|
eccKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return nil, nil, nil, rsa.PrivateKey{}, ecdsa.PrivateKey{}, err
|
|
}
|
|
|
|
c := CredentialPublicKey{
|
|
KeyType: int64(webauthncose.EllipticKey),
|
|
Algorithm: int64(webauthncose.AlgES256),
|
|
Curve: int64(webauthncose.P256),
|
|
XCoord: eccKey.X.Bytes(),
|
|
YCoord: eccKey.Y.Bytes(),
|
|
}
|
|
|
|
e := webauthncose.EC2PublicKeyData{
|
|
PublicKeyData: webauthncose.PublicKeyData{
|
|
KeyType: c.KeyType,
|
|
Algorithm: c.Algorithm,
|
|
},
|
|
Curve: c.Curve,
|
|
XCoord: c.XCoord,
|
|
YCoord: c.YCoord,
|
|
}
|
|
|
|
okpKey, _, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
return nil, nil, nil, rsa.PrivateKey{}, ecdsa.PrivateKey{}, err
|
|
}
|
|
|
|
o := webauthncose.OKPPublicKeyData{
|
|
PublicKeyData: webauthncose.PublicKeyData{
|
|
KeyType: int64(webauthncose.OctetKey),
|
|
Algorithm: int64(webauthncose.AlgEdDSA),
|
|
},
|
|
Curve: int64(webauthncose.Ed25519),
|
|
XCoord: okpKey,
|
|
}
|
|
|
|
epk, err := webauthncbor.Marshal(e)
|
|
if err != nil {
|
|
return nil, nil, nil, rsa.PrivateKey{}, ecdsa.PrivateKey{}, err
|
|
}
|
|
|
|
rpk, err := webauthncbor.Marshal(r)
|
|
if err != nil {
|
|
return nil, nil, nil, rsa.PrivateKey{}, ecdsa.PrivateKey{}, err
|
|
}
|
|
|
|
opk, err := webauthncbor.Marshal(o)
|
|
|
|
return epk, rpk, opk, *rsaKey, *eccKey, err
|
|
}
|
|
|
|
func mustConvertPEMToPEMBytes(t *testing.T, in string) []byte {
|
|
data, rest := pem.Decode([]byte(in))
|
|
require.NotNil(t, data)
|
|
require.Len(t, rest, 0)
|
|
|
|
return data.Bytes
|
|
}
|