Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions acme/api/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,18 +282,21 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
if err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing ClientID")
}

var targetProvider interface{ GetTarget(string) (string, error) }
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return acme.WrapErrorISE(err, "failed getting Wire options")
}
var targetProvider interface{ EvaluateTarget(string) (string, error) }
switch typ {
case acme.WIREOIDC01:
targetProvider = prov.GetOptions().GetWireOptions().GetOIDCOptions()
targetProvider = wireOptions.GetOIDCOptions()
case acme.WIREDPOP01:
targetProvider = prov.GetOptions().GetWireOptions().GetDPOPOptions()
targetProvider = wireOptions.GetDPOPOptions()
default:
return acme.NewError(acme.ErrorMalformedType, "unsupported type %q", typ)
}

target, err = targetProvider.GetTarget(clientID.DeviceID)
target, err = targetProvider.EvaluateTarget(clientID.DeviceID)
if err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'")
}
Expand Down
14 changes: 10 additions & 4 deletions acme/api/order_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1719,15 +1723,15 @@ func TestHandler_NewOrder(t *testing.T) {
acmeWireProv := newWireProvisionerWithOptions(t, &provisioner.Options{
Wire: &wire.Options{
OIDC: &wire.OIDCOptions{
Provider: wire.ProviderJSON{
IssuerURL: "",
Provider: &wire.Provider{
IssuerURL: "https://issuer.example.com",
AuthURL: "",
TokenURL: "",
JWKSURL: "",
UserInfoURL: "",
Algorithms: []string{},
},
Config: wire.ConfigJSON{
Config: &wire.Config{
ClientID: "integration test",
SupportedSigningAlgs: []string{},
SkipClientIDCheck: true,
Expand All @@ -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"}
Expand Down
13 changes: 9 additions & 4 deletions acme/api/wire_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,21 @@ 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{
Provider: wire.ProviderJSON{
IssuerURL: "",
Provider: &wire.Provider{
IssuerURL: "https://issuer.example.com",
AuthURL: "",
TokenURL: "",
JWKSURL: "",
UserInfoURL: "",
Algorithms: []string{},
},
Config: wire.ConfigJSON{
Config: &wire.Config{
ClientID: "integration test",
SupportedSigningAlgs: []string{},
SkipClientIDCheck: true,
Expand All @@ -72,7 +75,9 @@ func TestWireIntegration(t *testing.T) {
Now: time.Now,
},
},
DPOP: &wire.DPOPOptions{},
DPOP: &wire.DPOPOptions{
SigningKey: []byte(fakeKey),
},
},
})

Expand Down
33 changes: 16 additions & 17 deletions acme/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,12 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO
"error unmarshalling Wire challenge payload"))
}

oidcOptions := prov.GetOptions().GetWireOptions().GetOIDCOptions()
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return WrapErrorISE(err, "failed getting Wire options")
}

oidcOptions := wireOptions.GetOIDCOptions()
idToken, err := oidcOptions.GetProvider(ctx).Verifier(oidcOptions.GetConfig()).Verify(ctx, oidcPayload.IDToken)
if err != nil {
return storeError(ctx, db, ch, false, WrapError(ErrorRejectedIdentifierType, err,
Expand Down Expand Up @@ -468,8 +473,13 @@ func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *j
return WrapErrorISE(err, "error parsing device id")
}

dpopOptions := prov.GetOptions().GetWireOptions().GetDPOPOptions()
issuer, err := dpopOptions.GetTarget(clientID.DeviceID)
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return WrapErrorISE(err, "failed getting Wire options")
}

dpopOptions := wireOptions.GetDPOPOptions()
issuer, err := dpopOptions.EvaluateTarget(clientID.DeviceID)
if err != nil {
return WrapErrorISE(err, "invalid Go template registered for 'target'")
}
Expand Down Expand Up @@ -531,32 +541,22 @@ type wireDpopToken map[string]any

type verifyParams struct {
token string
key string
issuer string
key crypto.PublicKey
accountJWK *jose.JSONWebKey
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify the name of these variables? Looking at the code below, I think they can be something like tokenKey and dpopKey.

It's also quite weird to see two types here. I assume these keys come from some configuration, and you should be able to create the verifyParams with two crypto.PublicKey

issuer string
wireID wire.ID
challenge *Challenge
t time.Time
}

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 validating Wire DPoP token claims: %w", err)
}

Expand Down Expand Up @@ -590,7 +590,6 @@ func parseAndVerifyWireAccessToken(v verifyParams) (*wireAccessToken, *wireDpopT
if err != nil {
return nil, nil, fmt.Errorf("invalid Wire DPoP token: %w", err)
}

var dpopToken wireDpopToken
if err := dpopJWT.Claims(v.accountJWK.Key, &dpopToken); err != nil {
return nil, nil, fmt.Errorf("failed validating Wire DPoP token claims: %w", err)
Expand Down
5 changes: 4 additions & 1 deletion acme/challenge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -4309,6 +4310,8 @@ func Test_parseAndVerifyWireAccessToken(t *testing.T) {
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA=
-----END PUBLIC KEY-----` // TODO(hs): different format?
publicKey, err := pemutil.Parse([]byte(key))
require.NoError(t, err)
issuer := "http://wire.com:19983/clients/7a41cf5b79683410/access-token"
wireID := wire.ID{
ClientID: "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
Expand All @@ -4331,7 +4334,7 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA=

at, dpop, err := parseAndVerifyWireAccessToken(verifyParams{
token: token,
key: key,
key: publicKey,
accountJWK: &accountJWK,
issuer: issuer,
wireID: wireID,
Expand Down
10 changes: 7 additions & 3 deletions authority/provisioner/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package provisioner

import (
"encoding/json"
"fmt"
"strings"

"github.com/pkg/errors"
Expand Down Expand Up @@ -54,11 +55,14 @@ func (o *Options) GetSSHOptions() *SSHOptions {
}

// GetWireOptions returns the SSH options.
func (o *Options) GetWireOptions() *wire.Options {
func (o *Options) GetWireOptions() (*wire.Options, error) {
if o == nil {
return nil
return nil, errors.New("no Wire options available")
}
if err := o.Wire.Validate(); err != nil {
return nil, fmt.Errorf("failed validating Wire options: %w", err)
}
return o.Wire
return o.Wire, nil
}

// GetWebhooks returns the webhooks options.
Expand Down
41 changes: 29 additions & 12 deletions authority/provisioner/wire/dpop_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,61 @@ package wire

import (
"bytes"
"crypto"
"errors"
"fmt"
"text/template"

"go.step.sm/crypto/pemutil"
)

type DPOPOptions struct {
// Backend signing key for DPoP access token
SigningKey string `json:"key"`
// Public part of the signing key for DPoP access token
SigningKey []byte `json:"key"`
// URI template acme client must call to fetch the DPoP challenge proof (an access token from wire-server)
DpopTarget string `json:"dpop-target"`
Target string `json:"target"`
}

func (o *DPOPOptions) GetSigningKey() string {
func (o *DPOPOptions) GetSigningKey() crypto.PublicKey {
if o == nil {
return ""
return nil
}
return o.SigningKey
k, err := pemutil.Parse(o.SigningKey) // TODO(hs): do this once?
if err != nil {
return nil
}

return k
}

func (o *DPOPOptions) GetDPOPTarget() string {
func (o *DPOPOptions) GetTarget() string {
if o == nil {
return ""
}
return o.DpopTarget
return o.Target
}

func (o *DPOPOptions) GetTarget(deviceID string) (string, error) {
func (o *DPOPOptions) EvaluateTarget(deviceID string) (string, error) {
if o == nil {
return "", errors.New("misconfigured target template configuration")
}
targetTemplate := o.GetDPOPTarget()
tmpl, err := template.New("DeviceId").Parse(targetTemplate)
tmpl, err := template.New("DeviceID").Parse(o.GetTarget())
if err != nil {
return "", fmt.Errorf("failed parsing dpop template: %w", err)
}
buf := new(bytes.Buffer)
if err = tmpl.Execute(buf, struct{ DeviceId string }{DeviceId: deviceID}); err != nil { //nolint:revive,stylecheck // TODO(hs): this requires changes in configuration
if err = tmpl.Execute(buf, struct{ DeviceID string }{DeviceID: deviceID}); err != nil {
return "", fmt.Errorf("failed executing dpop template: %w", err)
}
return buf.String(), nil
}

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)
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation can also be part of the // TODO(hs): do this once?. We can validate and set hidden fields in the DPOPOptions struct.

I'm ok if you want to do this later.

Loading