diff --git a/acme/api/wire_integration_test.go b/acme/api/wire_integration_test.go index 8e2497cd..846c02fa 100644 --- a/acme/api/wire_integration_test.go +++ b/acme/api/wire_integration_test.go @@ -370,7 +370,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= require.NoError(t, err) // TODO(hs): move these to a more appropriate place and/or provide more realistic value - err = db.CreateDpopToken(ctx, order.ID, map[string]any{"fake-dpop": "dpop-value"}) + err = db.CreateDpopToken(ctx, order.ID, &acme.WireDpopToken{Challenge: "challenge", Handle: "handle", Team: "wire"}) require.NoError(t, err) err = db.CreateOidcToken(ctx, order.ID, map[string]any{"fake-oidc": "oidc-value"}) require.NoError(t, err) diff --git a/acme/challenge.go b/acme/challenge.go index f3c23d34..f4361d07 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -517,7 +517,7 @@ func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *j } order := orders[len(orders)-1] - if err := db.CreateDpopToken(ctx, order, map[string]any(*dpop)); err != nil { + if err := db.CreateDpopToken(ctx, order, dpop); err != nil { return WrapErrorISE(err, "failed storing DPoP token") } @@ -525,20 +525,28 @@ func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *j } type wireCnf struct { - Kid string `json:"kid"` + Kid string `json:"kid,omitempty"` } type wireAccessToken struct { jose.Claims - Challenge string `json:"chal"` - Cnf wireCnf `json:"cnf"` - Proof string `json:"proof"` - ClientID string `json:"client_id"` - APIVersion int `json:"api_version"` - Scope string `json:"scope"` + Challenge string `json:"chal,omitempty"` + Cnf wireCnf `json:"cnf,omitempty"` + Proof string `json:"proof,omitempty"` + ClientID string `json:"client_id,omitempty"` + APIVersion int `json:"api_version,omitempty"` + Scope string `json:"scope,omitempty"` } -type wireDpopToken map[string]any +type WireDpopToken struct { + jose.Claims + Nonce string `json:"nonce,omitempty"` + Method string `json:"htm,omitempty"` + URL string `json:"htu,omitempty"` + Challenge string `json:"chal,omitempty"` + Handle string `json:"handle,omitempty"` + Team string `json:"team,omitempty"` +} type wireVerifyParams struct { token string @@ -551,7 +559,7 @@ type wireVerifyParams struct { t time.Time } -func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireDpopToken, error) { +func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *WireDpopToken, error) { jwt, err := jose.ParseSigned(v.token) if err != nil { return nil, nil, fmt.Errorf("failed parsing token: %w", err) @@ -583,25 +591,17 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD if err != nil { return nil, nil, fmt.Errorf("invalid Wire DPoP token: %w", err) } - var dpopToken wireDpopToken + var dpopToken WireDpopToken if err := dpopJWT.Claims(v.dpopKey, &dpopToken); err != nil { return nil, nil, fmt.Errorf("failed validating Wire DPoP token claims: %w", err) } - challenge, ok := dpopToken["chal"].(string) - if !ok { - return nil, nil, fmt.Errorf("invalid challenge in Wire DPoP token") - } - if challenge != v.chToken { - return nil, nil, fmt.Errorf("invalid Wire DPoP challenge %q", challenge) + if dpopToken.Challenge != v.chToken { + return nil, nil, fmt.Errorf("invalid Wire DPoP challenge %q", dpopToken.Challenge) } - handle, ok := dpopToken["handle"].(string) - if !ok { - return nil, nil, fmt.Errorf("invalid handle in Wire DPoP token") - } - if handle != v.wireID.Handle { - return nil, nil, fmt.Errorf("invalid Wire client handle %q", handle) + if dpopToken.Handle != v.wireID.Handle { + return nil, nil, fmt.Errorf("invalid Wire client handle %q", dpopToken.Handle) } return &accessToken, &dpopToken, nil diff --git a/acme/challenge_test.go b/acme/challenge_test.go index f3f65a21..bbe6273d 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -4320,7 +4320,6 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA= token := `eyJhbGciOiJFZERTQSIsInR5cCI6ImF0K2p3dCIsImp3ayI6eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6IkIySVlxQldYQW91RHQzV2NDWmdDTTN0OWd1bU1FS01sZ01zR2VuU3UtZkEifX0.eyJpYXQiOjE3MDQ5ODUyMDUsImV4cCI6MTcwNDk4OTE2NSwibmJmIjoxNzA0OTg1MjA1LCJpc3MiOiJodHRwOi8vd2lyZS5jb206MTk5ODMvY2xpZW50cy83YTQxY2Y1Yjc5NjgzNDEwL2FjY2Vzcy10b2tlbiIsInN1YiI6IndpcmVhcHA6Ly9ndVZYNXhlRlMzZVRhdG1YQkl5QTRBITdhNDFjZjViNzk2ODM0MTBAd2lyZS5jb20iLCJhdWQiOiJodHRwOi8vd2lyZS5jb206MTk5ODMvY2xpZW50cy83YTQxY2Y1Yjc5NjgzNDEwL2FjY2Vzcy10b2tlbiIsImp0aSI6IjQyYzQ2ZDRjLWU1MTAtNDE3NS05ZmI1LWQwNTVlMTI1YTQ5ZCIsIm5vbmNlIjoiVUVKeVIyZHFPRWh6WkZKRVlXSkJhVGt5T0RORVlURTJhRXMwZEhJeGNFYyIsImNoYWwiOiJiWFVHTnBVZmNSeDNFaEIzNHhQM3k2MmFRWm9HWlM2aiIsImNuZiI6eyJraWQiOiJvTVdmTkRKUXNJNWNQbFhONVVvQk5uY0t0YzRmMmRxMnZ3Q2pqWHNxdzdRIn0sInByb29mIjoiZXlKaGJHY2lPaUpGWkVSVFFTSXNJblI1Y0NJNkltUndiM0FyYW5kMElpd2lhbmRySWpwN0ltdDBlU0k2SWs5TFVDSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSjRJam9pTVV3eFpVZ3lZVFpCWjFaMmVsUndOVnBoYkV0U1puRTJjRlpRVDNSRmFrazNhRGhVVUhwQ1dVWm5UU0o5ZlEuZXlKcFlYUWlPakUzTURRNU9EVXlNRFVzSW1WNGNDSTZNVGN3TkRrNU1qUXdOU3dpYm1KbUlqb3hOekEwT1RnMU1qQTFMQ0p6ZFdJaU9pSjNhWEpsWVhCd09pOHZaM1ZXV0RWNFpVWlRNMlZVWVhSdFdFSkplVUUwUVNFM1lUUXhZMlkxWWpjNU5qZ3pOREV3UUhkcGNtVXVZMjl0SWl3aWFuUnBJam9pTldVMk5qZzBZMkl0Tm1JME9DMDBOamhrTFdJd09URXRabVl3TkdKbFpEWmxZekpsSWl3aWJtOXVZMlVpT2lKVlJVcDVVakprY1U5RmFIcGFSa3BGV1ZkS1FtRlVhM2xQUkU1RldWUkZNbUZGY3pCa1NFbDRZMFZqSWl3aWFIUnRJam9pVUU5VFZDSXNJbWgwZFNJNkltaDBkSEE2THk5M2FYSmxMbU52YlRveE9UazRNeTlqYkdsbGJuUnpMemRoTkRGalpqVmlOemsyT0RNME1UQXZZV05qWlhOekxYUnZhMlZ1SWl3aVkyaGhiQ0k2SW1KWVZVZE9jRlZtWTFKNE0wVm9Rak0wZUZBemVUWXlZVkZhYjBkYVV6WnFJaXdpYUdGdVpHeGxJam9pZDJseVpXRndjRG92THlVME1HRnNhV05sWDNkcGNtVkFkMmx5WlM1amIyMGlMQ0owWldGdElqb2lkMmx5WlNKOS52bkN1T2JURFRLVFhCYXpyX3Z2X0xyZDBZT1Rac2xteHQtM2xKNWZKSU9iRVRidUVCTGlEaS1JVWZHcFJHTm1Dbm9IZjVocHNsWW5HeFMzSjloUmVDZyIsImNsaWVudF9pZCI6IndpcmVhcHA6Ly9ndVZYNXhlRlMzZVRhdG1YQkl5QTRBITdhNDFjZjViNzk2ODM0MTBAd2lyZS5jb20iLCJhcGlfdmVyc2lvbiI6NSwic2NvcGUiOiJ3aXJlX2NsaWVudF9pZCJ9.uCVYhmvCJm7nM1NxJQKl_XZJcSqm9eFmNmbRJkA5Wpsw70ZF1YANYC9nQ91QgsnuAbaRZMJiJt3P8ZntR2ozDQ` ch := &Challenge{ - Type: WIREDPOP01, Token: "bXUGNpUfcRx3EhB34xP3y62aQZoGZS6j", } @@ -4361,14 +4360,13 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA= } // dpop proof assertions - dt := *dpop - assert.Equal(t, "bXUGNpUfcRx3EhB34xP3y62aQZoGZS6j", dt["chal"].(string)) - assert.Equal(t, "wireapp://%40alice_wire@wire.com", dt["handle"].(string)) - assert.Equal(t, "POST", dt["htm"].(string)) - assert.Equal(t, "http://wire.com:19983/clients/7a41cf5b79683410/access-token", dt["htu"].(string)) - assert.Equal(t, "5e6684cb-6b48-468d-b091-ff04bed6ec2e", dt["jti"].(string)) - assert.Equal(t, "UEJyR2dqOEhzZFJEYWJBaTkyODNEYTE2aEs0dHIxcEc", dt["nonce"].(string)) - assert.Equal(t, "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com", dt["sub"].(string)) - assert.Equal(t, "wire", dt["team"].(string)) + assert.Equal(t, "bXUGNpUfcRx3EhB34xP3y62aQZoGZS6j", dpop.Challenge) + assert.Equal(t, "wireapp://%40alice_wire@wire.com", dpop.Handle) + assert.Equal(t, "POST", dpop.Method) + assert.Equal(t, "http://wire.com:19983/clients/7a41cf5b79683410/access-token", dpop.URL) + assert.Equal(t, "5e6684cb-6b48-468d-b091-ff04bed6ec2e", dpop.ID) + assert.Equal(t, "UEJyR2dqOEhzZFJEYWJBaTkyODNEYTE2aEs0dHIxcEc", dpop.Nonce) + assert.Equal(t, "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com", dpop.Subject) + assert.Equal(t, "wire", dpop.Team) } } diff --git a/acme/db.go b/acme/db.go index d98234a9..1089761e 100644 --- a/acme/db.go +++ b/acme/db.go @@ -56,8 +56,8 @@ type DB interface { // TODO(hs): put in a different interface GetAllOrdersByAccountID(ctx context.Context, accountID string) ([]string, error) - CreateDpopToken(ctx context.Context, orderID string, dpop map[string]interface{}) error - GetDpopToken(ctx context.Context, orderID string) (map[string]interface{}, error) + CreateDpopToken(ctx context.Context, orderID string, dpop *WireDpopToken) error + GetDpopToken(ctx context.Context, orderID string) (*WireDpopToken, error) CreateOidcToken(ctx context.Context, orderID string, idToken map[string]interface{}) error GetOidcToken(ctx context.Context, orderID string) (map[string]interface{}, error) } @@ -126,8 +126,8 @@ type MockDB struct { MockUpdateOrder func(ctx context.Context, o *Order) error MockGetAllOrdersByAccountID func(ctx context.Context, accountID string) ([]string, error) - MockGetDpopToken func(ctx context.Context, orderID string) (map[string]interface{}, error) - MockCreateDpopToken func(ctx context.Context, orderID string, dpop map[string]interface{}) error + MockGetDpopToken func(ctx context.Context, orderID string) (*WireDpopToken, error) + MockCreateDpopToken func(ctx context.Context, orderID string, dpop *WireDpopToken) error MockGetOidcToken func(ctx context.Context, orderID string) (map[string]interface{}, error) MockCreateOidcToken func(ctx context.Context, orderID string, idToken map[string]interface{}) error @@ -416,17 +416,17 @@ func (m *MockDB) GetAllOrdersByAccountID(ctx context.Context, accountID string) } // GetDpop retrieves a DPoP from the database. -func (m *MockDB) GetDpopToken(ctx context.Context, orderID string) (map[string]any, error) { +func (m *MockDB) GetDpopToken(ctx context.Context, orderID string) (*WireDpopToken, error) { if m.MockGetDpopToken != nil { return m.MockGetDpopToken(ctx, orderID) } else if m.MockError != nil { return nil, m.MockError } - return m.MockRet1.(map[string]any), m.MockError + return m.MockRet1.(*WireDpopToken), m.MockError } // CreateDpop creates DPoP resources and saves them to the DB. -func (m *MockDB) CreateDpopToken(ctx context.Context, orderID string, dpop map[string]any) error { +func (m *MockDB) CreateDpopToken(ctx context.Context, orderID string, dpop *WireDpopToken) error { if m.MockCreateDpopToken != nil { return m.MockCreateDpopToken(ctx, orderID, dpop) } diff --git a/acme/db/nosql/wire.go b/acme/db/nosql/wire.go index 9ceeb52d..5caba9a4 100644 --- a/acme/db/nosql/wire.go +++ b/acme/db/nosql/wire.go @@ -9,12 +9,24 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/acme" "github.com/smallstep/nosql" + "go.step.sm/crypto/jose" ) type dbDpopToken struct { - ID string `json:"id"` - Content []byte `json:"content"` - CreatedAt time.Time `json:"createdAt"` + ID string `json:"id"` // jti + Issuer string `json:"iss"` + Subject string `json:"sub"` + Audience jose.Audience `json:"aud,omitempty"` + Expiry *jose.NumericDate `json:"exp,omitempty"` + NotBefore *jose.NumericDate `json:"nbf,omitempty"` + IssuedAt *jose.NumericDate `json:"iat,omitempty"` + Nonce string `json:"nonce"` + Method string `json:"htm"` + URL string `json:"htu"` + Challenge string `json:"chal"` + Handle string `json:"handle"` + Team string `json:"team"` + CreatedAt time.Time `json:"createdAt"` } // getDBDpopToken retrieves and unmarshals an DPoP type from the database. @@ -33,30 +45,49 @@ func (db *DB) getDBDpopToken(_ context.Context, orderID string) (*dbDpopToken, e return d, nil } -// GetDpopToken retrieves an DPoP from the database. -func (db *DB) GetDpopToken(ctx context.Context, orderID string) (map[string]any, error) { +// GetDpopToken retrieves a DPoP from the database. +func (db *DB) GetDpopToken(ctx context.Context, orderID string) (*acme.WireDpopToken, error) { dbDpop, err := db.getDBDpopToken(ctx, orderID) if err != nil { return nil, err } - dpop := make(map[string]any) - err = json.Unmarshal(dbDpop.Content, &dpop) - - return dpop, err + return &acme.WireDpopToken{ + Claims: jose.Claims{ + ID: dbDpop.ID, + Issuer: dbDpop.Issuer, + Subject: dbDpop.Subject, + Audience: dbDpop.Audience, + Expiry: dbDpop.Expiry, + NotBefore: dbDpop.NotBefore, + IssuedAt: dbDpop.IssuedAt, + }, + Nonce: dbDpop.Nonce, + Method: dbDpop.Method, + URL: dbDpop.URL, + Challenge: dbDpop.Challenge, + Handle: dbDpop.Handle, + Team: dbDpop.Team, + }, nil } // CreateDpopToken creates DPoP resources and saves them to the DB. -func (db *DB) CreateDpopToken(ctx context.Context, orderID string, dpop map[string]any) error { - content, err := json.Marshal(dpop) - if err != nil { - return err - } - +func (db *DB) CreateDpopToken(ctx context.Context, orderID string, dpop *acme.WireDpopToken) error { now := clock.Now() dbDpop := &dbDpopToken{ - ID: orderID, - Content: content, + ID: dpop.ID, + Issuer: dpop.Issuer, + Subject: dpop.Subject, + Audience: dpop.Audience, + Expiry: dpop.Expiry, + NotBefore: dpop.NotBefore, + IssuedAt: dpop.IssuedAt, + Nonce: dpop.Nonce, + Method: dpop.Method, + URL: dpop.URL, + Challenge: dpop.Challenge, + Handle: dpop.Handle, + Team: dpop.Team, CreatedAt: now, } if err := db.save(ctx, orderID, dbDpop, nil, "dpop", wireDpopTokenTable); err != nil {