Use a struct for the Wire DPoP token

pull/1672/head
Herman Slatman 4 months ago
parent 9bb1b24bf1
commit 231f03ae28
No known key found for this signature in database
GPG Key ID: F4D8A44EA0A75A4F

@ -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)

@ -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

@ -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)
}
}

@ -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)
}

@ -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 {

Loading…
Cancel
Save