diff --git a/pkg/atls/ea/authenticator.go b/pkg/atls/ea/authenticator.go index 93551f66..105d787e 100644 --- a/pkg/atls/ea/authenticator.go +++ b/pkg/atls/ea/authenticator.go @@ -347,19 +347,22 @@ func validateAuthenticator(session *Session, st *tls.ConnectionState, role Role, Context: append([]byte(nil), certMsg.Context...), Chain: chain, } + var verifierPolicy eaattestation.VerificationPolicy + // A nil attestation policy is intentional: VerifyPayload then fails closed + // for any payload that carries evidence or attestation results without + // explicit verifiers being configured. + if attPolicy != nil { + verifierPolicy = *attPolicy + } + if !present && verifierPolicy.RequiresAttestation() { + return nil, eaattestation.ErrMissingAttestation + } if present { res.CMWAttestation = extracted parsed, err := eaattestation.ParsePayload(extracted) if err != nil { return nil, err } - var verifierPolicy eaattestation.VerificationPolicy - // A nil attestation policy is intentional: VerifyPayload then fails closed - // for any payload that carries evidence or attestation results without - // explicit verifiers being configured. - if attPolicy != nil { - verifierPolicy = *attPolicy - } verified, err := eaattestation.VerifyPayload(st, eaattestation.ExporterLabelAttestation, certMsg.Context, leaf, parsed, verifierPolicy) if err != nil { return nil, err diff --git a/pkg/atls/ea/authenticator_test.go b/pkg/atls/ea/authenticator_test.go index cd827142..2d10fbdc 100644 --- a/pkg/atls/ea/authenticator_test.go +++ b/pkg/atls/ea/authenticator_test.go @@ -64,6 +64,8 @@ type acceptEvidenceVerifier struct{} func (acceptEvidenceVerifier) VerifyEvidence(evidence []byte) error { return nil } +const alternateExporterLabel = "Attestation Binding" + func TestDummyAttestationRoundTrip(t *testing.T) { cert := selfSignedCert(t) srv, cli := tlsPair(t, cert) @@ -127,6 +129,63 @@ func TestDummyAttestationRoundTrip(t *testing.T) { } } +func TestDummyAttestationRoundTripRejectsAlternateExporterLabel(t *testing.T) { + cert := selfSignedCert(t) + srv, cli := tlsPair(t, cert) + defer srv.Close() + defer cli.Close() + + ctx, _ := NewRandomContext(16) + req := &AuthenticatorRequest{ + Type: HandshakeTypeClientCertificateRequest, + Context: ctx, + Extensions: []Extension{ + {Type: SignatureAlgorithmsExtensionType, Data: []byte{0x00, 0x02, 0x04, 0x03}}, + CMWAttestationOfferExtension(), + }, + } + + leaf, _ := x509.ParseCertificate(cert.Certificate[0]) + srvState := srv.ConnectionState() + _, aikPubHash, binding, err := attestation.ComputeBinding(&srvState, alternateExporterLabel, ctx, leaf) + if err != nil { + t.Fatal(err) + } + payloadBytes, err := attestation.MarshalPayload(attestation.Payload{ + Version: 1, + Evidence: []byte("dummy-attestation-report"), + MediaType: "application/eat+cwt", + Binder: attestation.AttestationBinder{ + ExporterLabel: alternateExporterLabel, + AIKPubHash: aikPubHash, + Binding: binding, + }, + }) + if err != nil { + t.Fatal(err) + } + ext, err := CMWAttestationDataExtension(payloadBytes) + if err != nil { + t.Fatal(err) + } + + auth, err := CreateAuthenticator(&srvState, RoleServer, req, cert, []Extension{ext}) + if err != nil { + t.Fatal(err) + } + + cliState := cli.ConnectionState() + roots := x509.NewCertPool() + roots.AddCert(leaf) + + _, err = ValidateAuthenticatorWithAttestation(&cliState, RoleServer, req, auth, &x509.VerifyOptions{Roots: roots}, attestation.VerificationPolicy{ + EvidenceVerifier: acceptEvidenceVerifier{}, + }) + if err != attestation.ErrUnexpectedExporterLabel { + t.Fatalf("got %v, want %v", err, attestation.ErrUnexpectedExporterLabel) + } +} + func TestRejectIfNotOffered(t *testing.T) { cert := selfSignedCert(t) srv, cli := tlsPair(t, cert) @@ -199,6 +258,44 @@ func TestAttestationFailsClosedWithoutVerifier(t *testing.T) { } } +func TestValidateAuthenticatorRejectsMissingOfferedAttestation(t *testing.T) { + cert := selfSignedCert(t) + srv, cli := tlsPair(t, cert) + defer srv.Close() + defer cli.Close() + + ctx, _ := NewRandomContext(16) + req := &AuthenticatorRequest{ + Type: HandshakeTypeClientCertificateRequest, + Context: ctx, + Extensions: []Extension{ + {Type: SignatureAlgorithmsExtensionType, Data: []byte{0x00, 0x02, 0x04, 0x03}}, + CMWAttestationOfferExtension(), + }, + } + + srvState := srv.ConnectionState() + auth, err := CreateAuthenticator(&srvState, RoleServer, req, cert, nil) + if err != nil { + t.Fatal(err) + } + + cliState := cli.ConnectionState() + roots := x509.NewCertPool() + leaf, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + t.Fatal(err) + } + roots.AddCert(leaf) + + _, err = ValidateAuthenticatorWithAttestation(&cliState, RoleServer, req, auth, &x509.VerifyOptions{Roots: roots}, attestation.VerificationPolicy{ + EvidenceVerifier: acceptEvidenceVerifier{}, + }) + if err != attestation.ErrMissingAttestation { + t.Fatalf("got %v, want %v", err, attestation.ErrMissingAttestation) + } +} + func TestRejectCMWAttestationOnIntermediateEntry(t *testing.T) { cert := selfSignedCert(t) srv, cli := tlsPair(t, cert) diff --git a/pkg/atls/ea/cmw_attestation.go b/pkg/atls/ea/cmw_attestation.go index b96f6e58..d581ba3c 100644 --- a/pkg/atls/ea/cmw_attestation.go +++ b/pkg/atls/ea/cmw_attestation.go @@ -3,19 +3,22 @@ package ea -const CMWAttestationExtensionType uint16 = 0xFF00 +const ( + CMWAttestationExtensionType uint16 = 0xFF00 + cmwAttestationLengthBytes = 2 +) func CMWAttestationOfferExtension() Extension { return Extension{Type: CMWAttestationExtensionType, Data: nil} } func CMWAttestationDataExtension(cmw []byte) (Extension, error) { - if len(cmw) == 0 || len(cmw) > 0xFFFF { + if len(cmw) == 0 || len(cmw)+cmwAttestationLengthBytes > 0xFFFF { return Extension{}, ErrInvalidLength } - data := make([]byte, 2+len(cmw)) + data := make([]byte, cmwAttestationLengthBytes+len(cmw)) putUint16(data[0:2], uint16(len(cmw))) - copy(data[2:], cmw) + copy(data[cmwAttestationLengthBytes:], cmw) return Extension{Type: CMWAttestationExtensionType, Data: data}, nil } diff --git a/pkg/atls/eaattestation/binding.go b/pkg/atls/eaattestation/binding.go index 96a86e5f..41a94e6f 100644 --- a/pkg/atls/eaattestation/binding.go +++ b/pkg/atls/eaattestation/binding.go @@ -12,12 +12,10 @@ import ( ) const ( - ExporterLabelAttestation = "Attestation" - ExporterLabelAttestationBinding = "Attestation Binding" + ExporterLabelAttestation = "Attestation" + ExportedAttestationValueLen = 32 ) -const ExportedAttestationValueLen = 32 - var errNotTLS13 = errors.New("attestation: not TLS 1.3") func ExportAttestationValue(st *tls.ConnectionState, label string, contextValue []byte) ([]byte, crypto.Hash, error) { diff --git a/pkg/atls/eaattestation/binding_test.go b/pkg/atls/eaattestation/binding_test.go index dcec1aea..2dbaeef5 100644 --- a/pkg/atls/eaattestation/binding_test.go +++ b/pkg/atls/eaattestation/binding_test.go @@ -17,6 +17,8 @@ import ( "time" ) +const alternateExporterLabel = "Attestation Binding" + type stubEvidenceVerifier struct { called bool err error @@ -175,6 +177,37 @@ func TestVerifyPayloadSuccess(t *testing.T) { } } +func TestVerifyPayloadRejectsAlternateExporterLabel(t *testing.T) { + cert, leaf := makeCert(t) + srv, cli := tls13Client(t, cert) + defer srv.Close() + defer cli.Close() + + st := cli.ConnectionState() + ctx := []byte{1, 2, 3, 4} + _, aik, binding, err := ComputeBinding(&st, alternateExporterLabel, ctx, leaf) + if err != nil { + t.Fatal(err) + } + + payload := &Payload{ + Version: 1, + Evidence: []byte("evidence"), + Binder: AttestationBinder{ + ExporterLabel: alternateExporterLabel, + AIKPubHash: aik, + Binding: binding, + }, + } + + _, err = VerifyPayload(&st, ExporterLabelAttestation, ctx, leaf, payload, VerificationPolicy{ + EvidenceVerifier: &stubEvidenceVerifier{}, + }) + if err != ErrUnexpectedExporterLabel { + t.Fatalf("got %v, want %v", err, ErrUnexpectedExporterLabel) + } +} + func TestVerifyBinderRejectsMismatch(t *testing.T) { cert, leaf := makeCert(t) srv, cli := tls13Client(t, cert) @@ -197,3 +230,39 @@ func TestVerifyBinderRejectsMismatch(t *testing.T) { t.Fatalf("got %v, want %v", err, ErrBindingMismatch) } } + +func TestVerifyPayloadRejectsBadBinderBeforeEvidenceVerification(t *testing.T) { + cert, leaf := makeCert(t) + srv, cli := tls13Client(t, cert) + defer srv.Close() + defer cli.Close() + + st := cli.ConnectionState() + ctx := []byte{1, 2, 3, 4} + _, aik, binding, err := ComputeBinding(&st, ExporterLabelAttestation, ctx, leaf) + if err != nil { + t.Fatal(err) + } + binding[0] ^= 0xff + + ev := &stubEvidenceVerifier{} + payload := &Payload{ + Version: 1, + Evidence: []byte("evidence"), + Binder: AttestationBinder{ + ExporterLabel: ExporterLabelAttestation, + AIKPubHash: aik, + Binding: binding, + }, + } + + _, err = VerifyPayload(&st, ExporterLabelAttestation, ctx, leaf, payload, VerificationPolicy{ + EvidenceVerifier: ev, + }) + if err != ErrBindingMismatch { + t.Fatalf("got %v, want %v", err, ErrBindingMismatch) + } + if ev.called { + t.Fatalf("evidence verifier should not be called before binder verification succeeds") + } +} diff --git a/pkg/atls/eaattestation/types.go b/pkg/atls/eaattestation/types.go index c1502a25..87592e7c 100644 --- a/pkg/atls/eaattestation/types.go +++ b/pkg/atls/eaattestation/types.go @@ -14,6 +14,8 @@ var ( ErrMissingBinder = errors.New("attestation: missing attestation binder") ErrAIKPubHashMismatch = errors.New("attestation: AIK public key hash mismatch") ErrBindingMismatch = errors.New("attestation: attestation binding mismatch") + ErrUnexpectedExporterLabel = errors.New("attestation: unexpected exporter label") + ErrMissingAttestation = errors.New("attestation: missing attestation payload") ErrEvidenceVerificationMissing = errors.New("attestation: evidence verifier not configured") ErrResultsVerificationMissing = errors.New("attestation: attestation results verifier not configured") ) @@ -62,6 +64,16 @@ func (p *Payload) NormalizedExporterLabel(defaultLabel string) string { return p.Binder.ExporterLabel } +func (p *Payload) VerifyExporterLabel(expectedLabel string) error { + if p == nil { + return ErrMalformedPayload + } + if p.Binder.ExporterLabel != "" && p.Binder.ExporterLabel != expectedLabel { + return ErrUnexpectedExporterLabel + } + return nil +} + func MarshalPayload(p Payload) ([]byte, error) { if err := p.Validate(); err != nil { return nil, err diff --git a/pkg/atls/eaattestation/verify.go b/pkg/atls/eaattestation/verify.go index cd27ffbc..934a382f 100644 --- a/pkg/atls/eaattestation/verify.go +++ b/pkg/atls/eaattestation/verify.go @@ -22,16 +22,28 @@ type VerificationPolicy struct { ResultsVerifier ResultsVerifier } +func (p VerificationPolicy) RequiresAttestation() bool { + return p.EvidenceVerifier != nil || p.ResultsVerifier != nil +} + func VerifyPayload(st *tls.ConnectionState, defaultLabel string, certificateRequestContext []byte, leaf *x509.Certificate, payload *Payload, policy VerificationPolicy) (*VerifiedPayload, error) { if err := payload.Validate(); err != nil { return nil, err } + if err := payload.VerifyExporterLabel(defaultLabel); err != nil { + return nil, err + } verified := &VerifiedPayload{ Payload: payload, - UsedExporterLabel: payload.NormalizedExporterLabel(defaultLabel), + UsedExporterLabel: defaultLabel, } + if err := VerifyBinder(st, verified.UsedExporterLabel, certificateRequestContext, leaf, payload.Binder); err != nil { + return nil, err + } + verified.BindingVerified = true + if len(payload.Evidence) > 0 && policy.EvidenceVerifier != nil { if err := policy.EvidenceVerifier.VerifyEvidence(payload.Evidence); err != nil { return nil, err @@ -48,10 +60,6 @@ func VerifyPayload(st *tls.ConnectionState, defaultLabel string, certificateRequ } else if len(payload.AttestationResults) > 0 { return nil, ErrResultsVerificationMissing } - if err := VerifyBinder(st, verified.UsedExporterLabel, certificateRequestContext, leaf, payload.Binder); err != nil { - return nil, err - } - verified.BindingVerified = true return verified, nil } diff --git a/pkg/atls/internal_transport/conn.go b/pkg/atls/internal_transport/conn.go index 00202056..c919257a 100644 --- a/pkg/atls/internal_transport/conn.go +++ b/pkg/atls/internal_transport/conn.go @@ -168,6 +168,9 @@ func Server(tlsConn *tls.Conn, cfg *ServerConfig) (*Conn, error) { if len(rest) != 0 { return nil, fmt.Errorf("atls: trailing request bytes") } + if cfg.BuildLeafExtensions != nil && !ea.RequestPermitsCertificateExtension(&req, ea.CMWAttestationExtensionType) { + return nil, fmt.Errorf("atls: cmw_attestation extension not offered") + } st := tlsConn.ConnectionState() identity, err := resolveIdentity(cfg)