From a7b0c1f4cff6ebd4f8c0ff643fbf20ca5ceedc84 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 20 Dec 2024 16:37:29 -0800 Subject: [PATCH 1/2] Attestation payload on webhooks This commit allows passing the attestation payload to webhooks with a custom database implementation. --- acme/challenge.go | 2 ++ acme/order.go | 12 +++++++++--- authority/provisioner/sign_options.go | 1 + authority/tls.go | 1 + webhook/types.go | 1 + 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index cf658cf7b..1dc39bf27 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -88,6 +88,7 @@ type Challenge struct { URL string `json:"url"` Target string `json:"target,omitempty"` Error *Error `json:"error,omitempty"` + Payload []byte `json:"-"` } // ToLog enables response logging. @@ -942,6 +943,7 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose ch.Status = StatusValid ch.Error = nil ch.ValidatedAt = clock.Now().Format(time.RFC3339) + ch.Payload = payload // Store the fingerprint in the authorization. // diff --git a/acme/order.go b/acme/order.go index c23befd91..43bcc51bd 100644 --- a/acme/order.go +++ b/acme/order.go @@ -41,8 +41,9 @@ const ( // Identifier encodes the type that an order pertains to. type Identifier struct { - Type IdentifierType `json:"type"` - Value string `json:"value"` + Type IdentifierType `json:"type"` + Value string `json:"value"` + Payload []byte `json:"-"` } // Order contains order metadata for the ACME protocol order type. @@ -240,10 +241,14 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques var extraOptions []provisioner.SignOption // TODO: support for multiple identifiers? - var permanentIdentifier string + var ( + permanentIdentifier string + attestationPayload []byte + ) for i := range o.Identifiers { if o.Identifiers[i].Type == PermanentIdentifier { permanentIdentifier = o.Identifiers[i].Value + attestationPayload = o.Identifiers[i].Payload // the first (and only) Permanent Identifier that gets added to the certificate // should be equal to the Subject Common Name if it's set. If not equal, the CSR // is rejected, because the Common Name hasn't been challenged in that case. This @@ -266,6 +271,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques }) extraOptions = append(extraOptions, provisioner.AttestationData{ PermanentIdentifier: permanentIdentifier, + Payload: attestationPayload, }) } else { defaultTemplate = x509util.DefaultLeafTemplate diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index fc842c43a..5c9dc0643 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -85,6 +85,7 @@ func (fn CertificateEnforcerFunc) Enforce(cert *x509.Certificate) error { // sign methods. type AttestationData struct { PermanentIdentifier string + Payload []byte } // defaultPublicKeyValidator validates the public key of a certificate request. diff --git a/authority/tls.go b/authority/tls.go index 320eb5961..f099f1bc0 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -1029,6 +1029,7 @@ func (a *Authority) callEnrichingWebhooksX509(ctx context.Context, prov provisio if attData != nil { attested = &webhook.AttestationData{ PermanentIdentifier: attData.PermanentIdentifier, + Payload: attData.Payload, } } diff --git a/webhook/types.go b/webhook/types.go index c60de7099..e5695dcc5 100644 --- a/webhook/types.go +++ b/webhook/types.go @@ -67,6 +67,7 @@ type SSHCertificate struct { // AttestationData is data validated by acme device-attest-01 challenge type AttestationData struct { PermanentIdentifier string `json:"permanentIdentifier"` + Payload []byte `json:"payload,omitempty"` } // X5CCertificate is the authorization certificate sent to webhook servers for From e209addb1236de177ed3b8c521f4b0e40c02ba9e Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 26 Dec 2024 11:11:29 -0800 Subject: [PATCH 2/2] Remove payload from webhooks This commit removes the unnecessary payload from webhooks. --- acme/challenge_test.go | 17 +++++++++++++++++ acme/order.go | 12 +++--------- authority/provisioner/sign_options.go | 1 - authority/tls.go | 1 - webhook/types.go | 1 - 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 9db451934..d17bcb216 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -820,6 +820,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewError(ErrorRejectedIdentifierType, "payload contained error: an error") @@ -871,6 +872,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, StatusValid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "1234", updch.Value) + assert.Equal(t, payload, updch.Payload) return nil }, @@ -4004,6 +4006,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewError(ErrorRejectedIdentifierType, "payload contained error: an error") @@ -4042,6 +4045,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewDetailedError(ErrorBadAttestationStatementType, "failed base64 decoding attObj %q", "?!") @@ -4080,6 +4084,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewDetailedError(ErrorBadAttestationStatementType, "attObj must not be empty") @@ -4118,6 +4123,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewDetailedError(ErrorBadAttestationStatementType, "attObj must not be empty") @@ -4156,6 +4162,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewDetailedError(ErrorBadAttestationStatementType, "attObj is not well formed CBOR: unexpected EOF") @@ -4196,6 +4203,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewDetailedError(ErrorBadAttestationStatementType, "unsupported attestation object format %q", "unsupported-format") @@ -4241,6 +4249,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewError(ErrorBadAttestationStatementType, "attestation format %q is not enabled", "step") @@ -4296,6 +4305,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewDetailedError(ErrorBadAttestationStatementType, "x5c not present") @@ -4343,6 +4353,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "serial-number", updch.Value) + assert.Nil(t, updch.Payload) err := NewDetailedError(ErrorBadAttestationStatementType, "challenge token does not match") @@ -4389,6 +4400,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "non-matching-value", updch.Value) + assert.Nil(t, updch.Payload) subproblem := NewSubproblemWithIdentifier( ErrorRejectedIdentifierType, @@ -4467,6 +4479,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewDetailedError(ErrorBadAttestationStatementType, "x5c not present") @@ -4521,6 +4534,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewDetailedError(ErrorBadAttestationStatementType, "permanent identifier does not match"). AddSubproblems(NewSubproblemWithIdentifier( @@ -4616,6 +4630,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusInvalid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Nil(t, updch.Payload) err := NewDetailedError(ErrorBadAttestationStatementType, `unsupported attestation object format "bogus-format"`) @@ -4708,6 +4723,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusValid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Equal(t, payload, updch.Payload) return errors.New("force") }, @@ -4754,6 +4770,7 @@ func Test_deviceAttest01Validate(t *testing.T) { assert.Equal(t, StatusValid, updch.Status) assert.Equal(t, ChallengeType("device-attest-01"), updch.Type) assert.Equal(t, "12345678", updch.Value) + assert.Equal(t, payload, updch.Payload) return nil }, diff --git a/acme/order.go b/acme/order.go index 43bcc51bd..c23befd91 100644 --- a/acme/order.go +++ b/acme/order.go @@ -41,9 +41,8 @@ const ( // Identifier encodes the type that an order pertains to. type Identifier struct { - Type IdentifierType `json:"type"` - Value string `json:"value"` - Payload []byte `json:"-"` + Type IdentifierType `json:"type"` + Value string `json:"value"` } // Order contains order metadata for the ACME protocol order type. @@ -241,14 +240,10 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques var extraOptions []provisioner.SignOption // TODO: support for multiple identifiers? - var ( - permanentIdentifier string - attestationPayload []byte - ) + var permanentIdentifier string for i := range o.Identifiers { if o.Identifiers[i].Type == PermanentIdentifier { permanentIdentifier = o.Identifiers[i].Value - attestationPayload = o.Identifiers[i].Payload // the first (and only) Permanent Identifier that gets added to the certificate // should be equal to the Subject Common Name if it's set. If not equal, the CSR // is rejected, because the Common Name hasn't been challenged in that case. This @@ -271,7 +266,6 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques }) extraOptions = append(extraOptions, provisioner.AttestationData{ PermanentIdentifier: permanentIdentifier, - Payload: attestationPayload, }) } else { defaultTemplate = x509util.DefaultLeafTemplate diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 5c9dc0643..fc842c43a 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -85,7 +85,6 @@ func (fn CertificateEnforcerFunc) Enforce(cert *x509.Certificate) error { // sign methods. type AttestationData struct { PermanentIdentifier string - Payload []byte } // defaultPublicKeyValidator validates the public key of a certificate request. diff --git a/authority/tls.go b/authority/tls.go index f099f1bc0..320eb5961 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -1029,7 +1029,6 @@ func (a *Authority) callEnrichingWebhooksX509(ctx context.Context, prov provisio if attData != nil { attested = &webhook.AttestationData{ PermanentIdentifier: attData.PermanentIdentifier, - Payload: attData.Payload, } } diff --git a/webhook/types.go b/webhook/types.go index e5695dcc5..c60de7099 100644 --- a/webhook/types.go +++ b/webhook/types.go @@ -67,7 +67,6 @@ type SSHCertificate struct { // AttestationData is data validated by acme device-attest-01 challenge type AttestationData struct { PermanentIdentifier string `json:"permanentIdentifier"` - Payload []byte `json:"payload,omitempty"` } // X5CCertificate is the authorization certificate sent to webhook servers for