|
|
@ -30,6 +30,7 @@ import (
|
|
|
|
"golang.org/x/exp/slices"
|
|
|
|
"golang.org/x/exp/slices"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/smallstep/go-attestation/attest"
|
|
|
|
"github.com/smallstep/go-attestation/attest"
|
|
|
|
|
|
|
|
|
|
|
|
"go.step.sm/crypto/jose"
|
|
|
|
"go.step.sm/crypto/jose"
|
|
|
|
"go.step.sm/crypto/keyutil"
|
|
|
|
"go.step.sm/crypto/keyutil"
|
|
|
|
"go.step.sm/crypto/pemutil"
|
|
|
|
"go.step.sm/crypto/pemutil"
|
|
|
@ -379,13 +380,18 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
|
|
|
|
return WrapErrorISE(err, "error unmarshalling CBOR")
|
|
|
|
return WrapErrorISE(err, "error unmarshalling CBOR")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
format := att.Format
|
|
|
|
prov := MustProvisionerFromContext(ctx)
|
|
|
|
prov := MustProvisionerFromContext(ctx)
|
|
|
|
if !prov.IsAttestationFormatEnabled(ctx, provisioner.ACMEAttestationFormat(att.Format)) {
|
|
|
|
if !prov.IsAttestationFormatEnabled(ctx, provisioner.ACMEAttestationFormat(format)) {
|
|
|
|
|
|
|
|
if format != "apple" && format != "step" && format != "tpm" {
|
|
|
|
|
|
|
|
return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "unsupported attestation object format %q", format))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return storeError(ctx, db, ch, true,
|
|
|
|
return storeError(ctx, db, ch, true,
|
|
|
|
NewError(ErrorBadAttestationStatementType, "attestation format %q is not enabled", att.Format))
|
|
|
|
NewError(ErrorBadAttestationStatementType, "attestation format %q is not enabled", format))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch att.Format {
|
|
|
|
switch format {
|
|
|
|
case "apple":
|
|
|
|
case "apple":
|
|
|
|
data, err := doAppleAttestationFormat(ctx, prov, ch, &att)
|
|
|
|
data, err := doAppleAttestationFormat(ctx, prov, ch, &att)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -398,11 +404,12 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return WrapErrorISE(err, "error validating attestation")
|
|
|
|
return WrapErrorISE(err, "error validating attestation")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Validate nonce with SHA-256 of the token.
|
|
|
|
// Validate nonce with SHA-256 of the token.
|
|
|
|
if len(data.Nonce) != 0 {
|
|
|
|
if len(data.Nonce) != 0 {
|
|
|
|
sum := sha256.Sum256([]byte(ch.Token))
|
|
|
|
sum := sha256.Sum256([]byte(ch.Token))
|
|
|
|
if subtle.ConstantTimeCompare(data.Nonce, sum[:]) != 1 {
|
|
|
|
if subtle.ConstantTimeCompare(data.Nonce, sum[:]) != 1 {
|
|
|
|
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "challenge token does not match"))
|
|
|
|
return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "challenge token does not match"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -411,7 +418,12 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Note: We might want to use an external service for this.
|
|
|
|
// Note: We might want to use an external service for this.
|
|
|
|
if data.UDID != ch.Value && data.SerialNumber != ch.Value {
|
|
|
|
if data.UDID != ch.Value && data.SerialNumber != ch.Value {
|
|
|
|
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "permanent identifier does not match"))
|
|
|
|
subproblem := NewSubproblemWithIdentifier(
|
|
|
|
|
|
|
|
ErrorRejectedIdentifierType,
|
|
|
|
|
|
|
|
Identifier{Type: "permanent-identifier", Value: ch.Value},
|
|
|
|
|
|
|
|
"challenge identifier %q doesn't match any of the attested hardware identifiers %q", ch.Value, []string{data.UDID, data.SerialNumber},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "permanent identifier does not match").AddSubproblems(subproblem))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update attestation key fingerprint to compare against the CSR
|
|
|
|
// Update attestation key fingerprint to compare against the CSR
|
|
|
@ -435,11 +447,11 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
|
|
|
|
// Note: We might want to use an external service for this.
|
|
|
|
// Note: We might want to use an external service for this.
|
|
|
|
if data.SerialNumber != ch.Value {
|
|
|
|
if data.SerialNumber != ch.Value {
|
|
|
|
subproblem := NewSubproblemWithIdentifier(
|
|
|
|
subproblem := NewSubproblemWithIdentifier(
|
|
|
|
ErrorMalformedType,
|
|
|
|
ErrorRejectedIdentifierType,
|
|
|
|
Identifier{Type: "permanent-identifier", Value: ch.Value},
|
|
|
|
Identifier{Type: "permanent-identifier", Value: ch.Value},
|
|
|
|
"challenge identifier %q doesn't match the attested hardware identifier %q", ch.Value, data.SerialNumber,
|
|
|
|
"challenge identifier %q doesn't match the attested hardware identifier %q", ch.Value, data.SerialNumber,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "permanent identifier does not match").AddSubproblems(subproblem))
|
|
|
|
return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "permanent identifier does not match").AddSubproblems(subproblem))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update attestation key fingerprint to compare against the CSR
|
|
|
|
// Update attestation key fingerprint to compare against the CSR
|
|
|
@ -448,8 +460,6 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
|
|
|
|
case "tpm":
|
|
|
|
case "tpm":
|
|
|
|
data, err := doTPMAttestationFormat(ctx, prov, ch, jwk, &att)
|
|
|
|
data, err := doTPMAttestationFormat(ctx, prov, ch, jwk, &att)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
// TODO(hs): we should provide more details in the error reported to the client;
|
|
|
|
|
|
|
|
// "Attestation statement cannot be verified" is VERY generic. Also holds true for the other formats.
|
|
|
|
|
|
|
|
var acmeError *Error
|
|
|
|
var acmeError *Error
|
|
|
|
if errors.As(err, &acmeError) {
|
|
|
|
if errors.As(err, &acmeError) {
|
|
|
|
if acmeError.Status == 500 {
|
|
|
|
if acmeError.Status == 500 {
|
|
|
@ -467,17 +477,17 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose
|
|
|
|
// still fail if the challenge value isn't equal to the CSR subject.
|
|
|
|
// still fail if the challenge value isn't equal to the CSR subject.
|
|
|
|
if len(data.PermanentIdentifiers) > 0 && !slices.Contains(data.PermanentIdentifiers, ch.Value) { // TODO(hs): add support for HardwareModuleName
|
|
|
|
if len(data.PermanentIdentifiers) > 0 && !slices.Contains(data.PermanentIdentifiers, ch.Value) { // TODO(hs): add support for HardwareModuleName
|
|
|
|
subproblem := NewSubproblemWithIdentifier(
|
|
|
|
subproblem := NewSubproblemWithIdentifier(
|
|
|
|
ErrorMalformedType,
|
|
|
|
ErrorRejectedIdentifierType,
|
|
|
|
Identifier{Type: "permanent-identifier", Value: ch.Value},
|
|
|
|
Identifier{Type: "permanent-identifier", Value: ch.Value},
|
|
|
|
"challenge identifier %q doesn't match any of the attested hardware identifiers %q", ch.Value, data.PermanentIdentifiers,
|
|
|
|
"challenge identifier %q doesn't match any of the attested hardware identifiers %q", ch.Value, data.PermanentIdentifiers,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, "permanent identifier does not match").AddSubproblems(subproblem))
|
|
|
|
return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "permanent identifier does not match").AddSubproblems(subproblem))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update attestation key fingerprint to compare against the CSR
|
|
|
|
// Update attestation key fingerprint to compare against the CSR
|
|
|
|
az.Fingerprint = data.Fingerprint
|
|
|
|
az.Fingerprint = data.Fingerprint
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
return storeError(ctx, db, ch, true, NewError(ErrorBadAttestationStatementType, "unexpected attestation object format"))
|
|
|
|
return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "unsupported attestation object format %q", format))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update and store the challenge.
|
|
|
|
// Update and store the challenge.
|
|
|
@ -523,38 +533,38 @@ const (
|
|
|
|
func doTPMAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, jwk *jose.JSONWebKey, att *attestationObject) (*tpmAttestationData, error) {
|
|
|
|
func doTPMAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, jwk *jose.JSONWebKey, att *attestationObject) (*tpmAttestationData, error) {
|
|
|
|
ver, ok := att.AttStatement["ver"].(string)
|
|
|
|
ver, ok := att.AttStatement["ver"].(string)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "ver not present")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "ver not present")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ver != "2.0" {
|
|
|
|
if ver != "2.0" {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "version %q is not supported", ver)
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "version %q is not supported", ver)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
x5c, ok := att.AttStatement["x5c"].([]interface{})
|
|
|
|
x5c, ok := att.AttStatement["x5c"].([]interface{})
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c not present")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c not present")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(x5c) == 0 {
|
|
|
|
if len(x5c) == 0 {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c is empty")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c is empty")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
akCertBytes, ok := x5c[0].([]byte)
|
|
|
|
akCertBytes, ok := x5c[0].([]byte)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
akCert, err := x509.ParseCertificate(akCertBytes)
|
|
|
|
akCert, err := x509.ParseCertificate(akCertBytes)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
intermediates := x509.NewCertPool()
|
|
|
|
intermediates := x509.NewCertPool()
|
|
|
|
for _, v := range x5c[1:] {
|
|
|
|
for _, v := range x5c[1:] {
|
|
|
|
intCertBytes, vok := v.([]byte)
|
|
|
|
intCertBytes, vok := v.([]byte)
|
|
|
|
if !vok {
|
|
|
|
if !vok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
intCert, err := x509.ParseCertificate(intCertBytes)
|
|
|
|
intCert, err := x509.ParseCertificate(intCertBytes)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
intermediates.AddCert(intCert)
|
|
|
|
intermediates.AddCert(intCert)
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -592,19 +602,19 @@ func doTPMAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge,
|
|
|
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
|
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is not valid")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "x5c is not valid")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// validate additional AK certificate requirements
|
|
|
|
// validate additional AK certificate requirements
|
|
|
|
if err := validateAKCertificate(akCert); err != nil {
|
|
|
|
if err := validateAKCertificate(akCert); err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "AK certificate is not valid")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "AK certificate is not valid")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO(hs): implement revocation check; Verify() doesn't perform CRL check nor OCSP lookup.
|
|
|
|
// TODO(hs): implement revocation check; Verify() doesn't perform CRL check nor OCSP lookup.
|
|
|
|
|
|
|
|
|
|
|
|
sans, err := x509util.ParseSubjectAlternativeNames(akCert)
|
|
|
|
sans, err := x509util.ParseSubjectAlternativeNames(akCert)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed parsing AK certificate Subject Alternative Names")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "failed parsing AK certificate Subject Alternative Names")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
permanentIdentifiers := make([]string, len(sans.PermanentIdentifiers))
|
|
|
|
permanentIdentifiers := make([]string, len(sans.PermanentIdentifiers))
|
|
|
@ -615,37 +625,37 @@ func doTPMAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge,
|
|
|
|
// extract and validate pubArea, sig, certInfo and alg properties from the request body
|
|
|
|
// extract and validate pubArea, sig, certInfo and alg properties from the request body
|
|
|
|
pubArea, ok := att.AttStatement["pubArea"].([]byte)
|
|
|
|
pubArea, ok := att.AttStatement["pubArea"].([]byte)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "invalid pubArea in attestation statement")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "invalid pubArea in attestation statement")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(pubArea) == 0 {
|
|
|
|
if len(pubArea) == 0 {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "pubArea is empty")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "pubArea is empty")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sig, ok := att.AttStatement["sig"].([]byte)
|
|
|
|
sig, ok := att.AttStatement["sig"].([]byte)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "invalid sig in attestation statement")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "invalid sig in attestation statement")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(sig) == 0 {
|
|
|
|
if len(sig) == 0 {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "sig is empty")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "sig is empty")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
certInfo, ok := att.AttStatement["certInfo"].([]byte)
|
|
|
|
certInfo, ok := att.AttStatement["certInfo"].([]byte)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "invalid certInfo in attestation statement")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "invalid certInfo in attestation statement")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(certInfo) == 0 {
|
|
|
|
if len(certInfo) == 0 {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "certInfo is empty")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "certInfo is empty")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
alg, ok := att.AttStatement["alg"].(int64)
|
|
|
|
alg, ok := att.AttStatement["alg"].(int64)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "invalid alg in attestation statement")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "invalid alg in attestation statement")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// only RS256 and ES256 are allowed
|
|
|
|
// only RS256 and ES256 are allowed
|
|
|
|
coseAlg := coseAlgorithmIdentifier(alg)
|
|
|
|
coseAlg := coseAlgorithmIdentifier(alg)
|
|
|
|
if coseAlg != coseAlgRS256 && coseAlg != coseAlgES256 {
|
|
|
|
if coseAlg != coseAlgRS256 && coseAlg != coseAlgES256 {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "invalid alg %d in attestation statement", alg)
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "invalid alg %d in attestation statement", alg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// set the hash algorithm to use to SHA256
|
|
|
|
// set the hash algorithm to use to SHA256
|
|
|
@ -663,36 +673,36 @@ func doTPMAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge,
|
|
|
|
Hash: hash,
|
|
|
|
Hash: hash,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err = certificationParameters.Verify(verifyOpts); err != nil {
|
|
|
|
if err = certificationParameters.Verify(verifyOpts); err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "invalid certification parameters")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "invalid certification parameters")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// decode the "certInfo" data. This won't fail, as it's also done as part of Verify().
|
|
|
|
// decode the "certInfo" data. This won't fail, as it's also done as part of Verify().
|
|
|
|
tpmCertInfo, err := tpm2.DecodeAttestationData(certInfo)
|
|
|
|
tpmCertInfo, err := tpm2.DecodeAttestationData(certInfo)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed decoding attestation data")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "failed decoding attestation data")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
keyAuth, err := KeyAuthorization(ch.Token, jwk)
|
|
|
|
keyAuth, err := KeyAuthorization(ch.Token, jwk)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed creating key auth digest")
|
|
|
|
return nil, WrapErrorISE(err, "failed creating key auth digest")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hashedKeyAuth := sha256.Sum256([]byte(keyAuth))
|
|
|
|
hashedKeyAuth := sha256.Sum256([]byte(keyAuth))
|
|
|
|
|
|
|
|
|
|
|
|
// verify the WebAuthn object contains the expect key authorization digest, which is carried
|
|
|
|
// verify the WebAuthn object contains the expect key authorization digest, which is carried
|
|
|
|
// within the encoded `certInfo` property of the attestation statement.
|
|
|
|
// within the encoded `certInfo` property of the attestation statement.
|
|
|
|
if subtle.ConstantTimeCompare(hashedKeyAuth[:], []byte(tpmCertInfo.ExtraData)) == 0 {
|
|
|
|
if subtle.ConstantTimeCompare(hashedKeyAuth[:], []byte(tpmCertInfo.ExtraData)) == 0 {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "key authorization does not match")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "key authorization invalid")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// decode the (attested) public key and determine its fingerprint. This won't fail, as it's also done as part of Verify().
|
|
|
|
// decode the (attested) public key and determine its fingerprint. This won't fail, as it's also done as part of Verify().
|
|
|
|
pub, err := tpm2.DecodePublic(pubArea)
|
|
|
|
pub, err := tpm2.DecodePublic(pubArea)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed decoding pubArea")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "failed decoding pubArea")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
publicKey, err := pub.Key()
|
|
|
|
publicKey, err := pub.Key()
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "failed getting public key")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "failed getting public key")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data := &tpmAttestationData{
|
|
|
|
data := &tpmAttestationData{
|
|
|
@ -838,30 +848,30 @@ func doAppleAttestationFormat(_ context.Context, prov Provisioner, _ *Challenge,
|
|
|
|
|
|
|
|
|
|
|
|
x5c, ok := att.AttStatement["x5c"].([]interface{})
|
|
|
|
x5c, ok := att.AttStatement["x5c"].([]interface{})
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c not present")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c not present")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(x5c) == 0 {
|
|
|
|
if len(x5c) == 0 {
|
|
|
|
return nil, NewError(ErrorRejectedIdentifierType, "x5c is empty")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c is empty")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
der, ok := x5c[0].([]byte)
|
|
|
|
der, ok := x5c[0].([]byte)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
leaf, err := x509.ParseCertificate(der)
|
|
|
|
leaf, err := x509.ParseCertificate(der)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
intermediates := x509.NewCertPool()
|
|
|
|
intermediates := x509.NewCertPool()
|
|
|
|
for _, v := range x5c[1:] {
|
|
|
|
for _, v := range x5c[1:] {
|
|
|
|
der, ok = v.([]byte)
|
|
|
|
der, ok = v.([]byte)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(der)
|
|
|
|
cert, err := x509.ParseCertificate(der)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
intermediates.AddCert(cert)
|
|
|
|
intermediates.AddCert(cert)
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -872,7 +882,7 @@ func doAppleAttestationFormat(_ context.Context, prov Provisioner, _ *Challenge,
|
|
|
|
CurrentTime: time.Now().Truncate(time.Second),
|
|
|
|
CurrentTime: time.Now().Truncate(time.Second),
|
|
|
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
|
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
|
|
}); err != nil {
|
|
|
|
}); err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is not valid")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "x5c is not valid")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data := &appleAttestationData{
|
|
|
|
data := &appleAttestationData{
|
|
|
@ -944,28 +954,28 @@ func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge,
|
|
|
|
// Extract x5c and verify certificate
|
|
|
|
// Extract x5c and verify certificate
|
|
|
|
x5c, ok := att.AttStatement["x5c"].([]interface{})
|
|
|
|
x5c, ok := att.AttStatement["x5c"].([]interface{})
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c not present")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c not present")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(x5c) == 0 {
|
|
|
|
if len(x5c) == 0 {
|
|
|
|
return nil, NewError(ErrorRejectedIdentifierType, "x5c is empty")
|
|
|
|
return nil, NewDetailedError(ErrorRejectedIdentifierType, "x5c is empty")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
der, ok := x5c[0].([]byte)
|
|
|
|
der, ok := x5c[0].([]byte)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
leaf, err := x509.ParseCertificate(der)
|
|
|
|
leaf, err := x509.ParseCertificate(der)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
intermediates := x509.NewCertPool()
|
|
|
|
intermediates := x509.NewCertPool()
|
|
|
|
for _, v := range x5c[1:] {
|
|
|
|
for _, v := range x5c[1:] {
|
|
|
|
der, ok = v.([]byte)
|
|
|
|
der, ok = v.([]byte)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(der)
|
|
|
|
cert, err := x509.ParseCertificate(der)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "x5c is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
intermediates.AddCert(cert)
|
|
|
|
intermediates.AddCert(cert)
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -975,7 +985,7 @@ func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge,
|
|
|
|
CurrentTime: time.Now().Truncate(time.Second),
|
|
|
|
CurrentTime: time.Now().Truncate(time.Second),
|
|
|
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
|
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
|
|
}); err != nil {
|
|
|
|
}); err != nil {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "x5c is not valid")
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "x5c is not valid")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Verify proof of possession of private key validating the key
|
|
|
|
// Verify proof of possession of private key validating the key
|
|
|
@ -985,10 +995,10 @@ func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge,
|
|
|
|
var sig []byte
|
|
|
|
var sig []byte
|
|
|
|
csig, ok := att.AttStatement["sig"].([]byte)
|
|
|
|
csig, ok := att.AttStatement["sig"].([]byte)
|
|
|
|
if !ok {
|
|
|
|
if !ok {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "sig not present")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "sig not present")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := cbor.Unmarshal(csig, &sig); err != nil {
|
|
|
|
if err := cbor.Unmarshal(csig, &sig); err != nil {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "sig is malformed")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "sig is malformed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
keyAuth, err := KeyAuthorization(ch.Token, jwk)
|
|
|
|
keyAuth, err := KeyAuthorization(ch.Token, jwk)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
@ -998,23 +1008,23 @@ func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge,
|
|
|
|
switch pub := leaf.PublicKey.(type) {
|
|
|
|
switch pub := leaf.PublicKey.(type) {
|
|
|
|
case *ecdsa.PublicKey:
|
|
|
|
case *ecdsa.PublicKey:
|
|
|
|
if pub.Curve != elliptic.P256() {
|
|
|
|
if pub.Curve != elliptic.P256() {
|
|
|
|
return nil, WrapError(ErrorBadAttestationStatementType, err, "unsupported elliptic curve %s", pub.Curve)
|
|
|
|
return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "unsupported elliptic curve %s", pub.Curve)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sum := sha256.Sum256([]byte(keyAuth))
|
|
|
|
sum := sha256.Sum256([]byte(keyAuth))
|
|
|
|
if !ecdsa.VerifyASN1(pub, sum[:], sig) {
|
|
|
|
if !ecdsa.VerifyASN1(pub, sum[:], sig) {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "failed to validate signature")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed to validate signature")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case *rsa.PublicKey:
|
|
|
|
case *rsa.PublicKey:
|
|
|
|
sum := sha256.Sum256([]byte(keyAuth))
|
|
|
|
sum := sha256.Sum256([]byte(keyAuth))
|
|
|
|
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, sum[:], sig); err != nil {
|
|
|
|
if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, sum[:], sig); err != nil {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "failed to validate signature")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed to validate signature")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case ed25519.PublicKey:
|
|
|
|
case ed25519.PublicKey:
|
|
|
|
if !ed25519.Verify(pub, []byte(keyAuth), sig) {
|
|
|
|
if !ed25519.Verify(pub, []byte(keyAuth), sig) {
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "failed to validate signature")
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed to validate signature")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
return nil, NewError(ErrorBadAttestationStatementType, "unsupported public key type %T", pub)
|
|
|
|
return nil, NewDetailedError(ErrorBadAttestationStatementType, "unsupported public key type %T", pub)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Parse attestation data:
|
|
|
|
// Parse attestation data:
|
|
|
|