diff --git a/acme/api/order_test.go b/acme/api/order_test.go index aaa356f9..d22a4911 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -885,6 +885,10 @@ func TestHandler_NewOrder(t *testing.T) { u := fmt.Sprintf("%s/acme/%s/order/ordID", baseURL.String(), escProvName) + fakeWireSigningKey := `-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= +-----END PUBLIC KEY-----` + type test struct { ca acme.CertificateAuthority db acme.DB @@ -1737,7 +1741,9 @@ func TestHandler_NewOrder(t *testing.T) { Now: time.Now, }, }, - DPOP: &wire.DPOPOptions{}, + DPOP: &wire.DPOPOptions{ + SigningKey: []byte(fakeWireSigningKey), + }, }, }) acc := &acme.Account{ID: "accID"} diff --git a/acme/api/wire_integration_test.go b/acme/api/wire_integration_test.go index de4c54c7..35a49c6f 100644 --- a/acme/api/wire_integration_test.go +++ b/acme/api/wire_integration_test.go @@ -51,6 +51,9 @@ func newWireProvisionerWithOptions(t *testing.T, options *provisioner.Options) * } func TestWireIntegration(t *testing.T) { + fakeKey := `-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= +-----END PUBLIC KEY-----` prov := newWireProvisionerWithOptions(t, &provisioner.Options{ Wire: &wire.Options{ OIDC: &wire.OIDCOptions{ @@ -72,7 +75,9 @@ func TestWireIntegration(t *testing.T) { Now: time.Now, }, }, - DPOP: &wire.DPOPOptions{}, + DPOP: &wire.DPOPOptions{ + SigningKey: []byte(fakeKey), + }, }, }) diff --git a/acme/challenge.go b/acme/challenge.go index 934aa361..69e20083 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -490,10 +490,9 @@ func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO return WrapErrorISE(err, "invalid Go template registered for 'target'") } - key := dpopOptions.GetSigningKey() params := verifyParams{ token: dpopPayload.AccessToken, - key: key, + key: dpopOptions.GetSigningKey(), kid: kid, issuer: issuer, wireID: wireID, @@ -548,7 +547,7 @@ type wireDpopToken map[string]any type verifyParams struct { token string - key string + key crypto.PublicKey issuer string kid string wireID wire.ID @@ -557,23 +556,13 @@ type verifyParams struct { } func parseAndVerifyWireAccessToken(v verifyParams) (*wireAccessToken, *wireDpopToken, error) { - k, err := pemutil.Parse([]byte(v.key)) // TODO(hs): move this to earlier in the configuration process? Do it once? - if err != nil { - return nil, nil, fmt.Errorf("failed parsing public key: %w", err) - } - - pk, ok := k.(ed25519.PublicKey) // TODO(hs): allow more key types - if !ok { - return nil, nil, fmt.Errorf("unexpected type: %T", k) - } - jwt, err := jose.ParseSigned(v.token) if err != nil { return nil, nil, fmt.Errorf("failed parsing token: %w", err) } var accessToken wireAccessToken - if err = jwt.Claims(pk, &accessToken); err != nil { + if err = jwt.Claims(v.key, &accessToken); err != nil { return nil, nil, fmt.Errorf("failed getting token claims: %w", err) } diff --git a/acme/challenge_test.go b/acme/challenge_test.go index d02a838a..0208f583 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -34,6 +34,7 @@ import ( "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" + "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" "github.com/smallstep/certificates/acme/wire" @@ -4305,10 +4306,11 @@ func createSubjectAltNameExtension(dnsNames, emailAddresses x509util.MultiString } func Test_parseAndVerifyWireAccessToken(t *testing.T) { - key := ` ------BEGIN PUBLIC KEY----- + key := `-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= ------END PUBLIC KEY-----` // TODO(hs): different format? +-----END PUBLIC KEY-----` + publicKey, err := pemutil.Parse([]byte(key)) + require.NoError(t, err) issuer := "https://wire.example.com/clients/314845990100130665/access-token" kid := "QAv6C9q47Cyfd1u9z6uX3V_o-t11S8p81wLH-oTRlh0" wireID := wire.ID{ @@ -4327,7 +4329,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= at, dpop, err := parseAndVerifyWireAccessToken(verifyParams{ token: token, - key: key, + key: publicKey, kid: kid, issuer: issuer, wireID: wireID, diff --git a/authority/provisioner/wire/dpop_options.go b/authority/provisioner/wire/dpop_options.go index 8f39ec7d..3beb23c1 100644 --- a/authority/provisioner/wire/dpop_options.go +++ b/authority/provisioner/wire/dpop_options.go @@ -2,23 +2,31 @@ package wire import ( "bytes" + "crypto" "errors" "fmt" "text/template" + + "go.step.sm/crypto/pemutil" ) type DPOPOptions struct { // Public part of the signing key for DPoP access token - SigningKey string `json:"key"` + SigningKey []byte `json:"key"` // URI template acme client must call to fetch the DPoP challenge proof (an access token from wire-server) Target string `json:"target"` } -func (o *DPOPOptions) GetSigningKey() string { +func (o *DPOPOptions) GetSigningKey() crypto.PublicKey { if o == nil { - return "" + return nil + } + k, err := pemutil.Parse(o.SigningKey) // TODO(hs): do this once? + if err != nil { + return nil } - return o.SigningKey + + return k } func (o *DPOPOptions) GetTarget() string { @@ -44,6 +52,9 @@ func (o *DPOPOptions) EvaluateTarget(deviceID string) (string, error) { } func (o *DPOPOptions) validate() error { + if _, err := pemutil.Parse(o.SigningKey); err != nil { + return fmt.Errorf("failed parsing key: %w", err) + } if _, err := template.New("DeviceID").Parse(o.GetTarget()); err != nil { return fmt.Errorf("failed parsing template: %w", err) } diff --git a/authority/provisioner/wire/wire_options_test.go b/authority/provisioner/wire/wire_options_test.go index cc28b142..6831aca5 100644 --- a/authority/provisioner/wire/wire_options_test.go +++ b/authority/provisioner/wire/wire_options_test.go @@ -8,6 +8,10 @@ import ( ) func TestOptions_Validate(t *testing.T) { + key := []byte(`-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= +-----END PUBLIC KEY-----`) + type fields struct { OIDC *OIDCOptions DPOP *DPOPOptions @@ -26,7 +30,9 @@ func TestOptions_Validate(t *testing.T) { }, Config: &Config{}, }, - DPOP: &DPOPOptions{}, + DPOP: &DPOPOptions{ + SigningKey: key, + }, }, expectedErr: nil, }, @@ -90,6 +96,22 @@ func TestOptions_Validate(t *testing.T) { }, expectedErr: errors.New("no DPoP options available"), }, + { + name: "fail/invalid-key", + fields: fields{ + OIDC: &OIDCOptions{ + Provider: &Provider{ + IssuerURL: "https://example.com", + }, + Config: &Config{}, + }, + DPOP: &DPOPOptions{ + SigningKey: []byte{0x00}, + Target: "", + }, + }, + expectedErr: errors.New(`failed validating DPoP options: failed parsing key: error decoding PEM: not a valid PEM encoded block`), + }, { name: "fail/target-template", fields: fields{ @@ -100,7 +122,8 @@ func TestOptions_Validate(t *testing.T) { Config: &Config{}, }, DPOP: &DPOPOptions{ - Target: "{{}", + SigningKey: key, + Target: "{{}", }, }, expectedErr: errors.New(`failed validating DPoP options: failed parsing template: template: DeviceID:1: unexpected "}" in command`),