From 0f0f06014933f2ce7095ad7f5f47211a2d41d989 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 17 Jan 2024 00:09:24 +0100 Subject: [PATCH 1/8] Improve access and dpop token validation --- acme/challenge.go | 70 +++++++++++++++++++++++++++++++++++-- acme/challenge_test.go | 2 ++ acme/challenge_wire_test.go | 20 +++++++++-- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index 44939f21..450b0cd4 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -562,6 +562,12 @@ type wireAccessToken struct { Scope string `json:"scope"` } +type wireDpopJwt struct { + jose.Claims + ClientID string `json:"client_id"` + Challenge string `json:"chal"` +} + type wireDpopToken map[string]any type wireVerifyParams struct { @@ -581,6 +587,23 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD return nil, nil, fmt.Errorf("failed parsing token: %w", err) } + if len(jwt.Headers) != 1 { + return nil, nil, fmt.Errorf("token has wrong number of headers %d", len(jwt.Headers)) + } + keyID, err := KeyToID(&jose.JSONWebKey{Key: v.tokenKey}) + if err != nil { + return nil, nil, fmt.Errorf("failed calculating token key ID: %w", err) + } + jwtKeyID := jwt.Headers[0].KeyID + if jwtKeyID == "" { + if jwtKeyID, err = KeyToID(jwt.Headers[0].JSONWebKey); err != nil { + return nil, nil, fmt.Errorf("failed extracting token key ID: %w", err) + } + } + if jwtKeyID != keyID { + return nil, nil, fmt.Errorf("invalid token key ID %q", jwtKeyID) + } + var accessToken wireAccessToken if err = jwt.Claims(v.tokenKey, &accessToken); err != nil { return nil, nil, fmt.Errorf("failed validating Wire DPoP token claims: %w", err) @@ -589,10 +612,13 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD if err := accessToken.ValidateWithLeeway(jose.Expected{ Time: v.t, Issuer: v.issuer, - }, 360*time.Second); err != nil { + }, 1*time.Minute); err != nil { return nil, nil, fmt.Errorf("failed validation: %w", err) } + if accessToken.Challenge == "" { + return nil, nil, errors.New("access token challenge must not be empty") + } if accessToken.Cnf.Kid != v.dpopKeyID { return nil, nil, fmt.Errorf("expected kid %q; got %q", v.dpopKeyID, accessToken.Cnf.Kid) } @@ -602,11 +628,49 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD if accessToken.Expiry.Time().After(v.t.Add(time.Hour * 24 * 365)) { return nil, nil, fmt.Errorf("'exp' %s is too far into the future", accessToken.Expiry.Time().String()) } + if accessToken.Scope != "wire_client_id" { + return nil, nil, fmt.Errorf("invalid Wire scope %q", accessToken.Scope) + } dpopJWT, err := jose.ParseSigned(accessToken.Proof) if err != nil { return nil, nil, fmt.Errorf("invalid Wire DPoP token: %w", err) } + if len(dpopJWT.Headers) != 1 { + return nil, nil, fmt.Errorf("DPoP token has wrong number of headers %d", len(jwt.Headers)) + } + dpopJwtKeyID := dpopJWT.Headers[0].KeyID + if dpopJwtKeyID == "" { + if dpopJwtKeyID, err = KeyToID(dpopJWT.Headers[0].JSONWebKey); err != nil { + return nil, nil, fmt.Errorf("failed extracting DPoP token key ID: %w", err) + } + } + if dpopJwtKeyID != v.dpopKeyID { + return nil, nil, fmt.Errorf("invalid DPoP token key ID %q", dpopJWT.Headers[0].KeyID) + } + + var wireDpop wireDpopJwt + if err := dpopJWT.Claims(v.dpopKey, &wireDpop); err != nil { + return nil, nil, fmt.Errorf("failed validating Wire DPoP token claims: %w", err) + } + + if err := wireDpop.ValidateWithLeeway(jose.Expected{ + Time: v.t, + Issuer: v.issuer, + }, 1*time.Minute); err != nil { + return nil, nil, fmt.Errorf("failed DPoP validation: %w", err) + } + if wireDpop.Expiry.Time().After(v.t.Add(time.Hour * 24 * 365)) { + return nil, nil, fmt.Errorf("'exp' %s is too far into the future", wireDpop.Expiry.Time().String()) + } + if wireDpop.ClientID != v.wireID.ClientID { + return nil, nil, fmt.Errorf("DPoP contains invalid Wire client ID %q", wireDpop.ClientID) + } + if wireDpop.Challenge != accessToken.Challenge { + return nil, nil, fmt.Errorf("DPoP contains invalid challenge %q", wireDpop.Challenge) + } + + // TODO(hs): can we use the wireDpopJwt and map that instead of doing Claims() twice? 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) @@ -616,7 +680,7 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD if !ok { return nil, nil, fmt.Errorf("invalid challenge in Wire DPoP token") } - if challenge != v.chToken { + if challenge == "" || challenge != v.chToken { return nil, nil, fmt.Errorf("invalid Wire DPoP challenge %q", challenge) } @@ -624,7 +688,7 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD if !ok { return nil, nil, fmt.Errorf("invalid handle in Wire DPoP token") } - if handle != v.wireID.Handle { + if handle == "" || handle != v.wireID.Handle { return nil, nil, fmt.Errorf("invalid Wire client handle %q", handle) } diff --git a/acme/challenge_test.go b/acme/challenge_test.go index efe04c47..db737758 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -1007,12 +1007,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` + ClientID string `json:"client_id,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", + ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) diff --git a/acme/challenge_wire_test.go b/acme/challenge_wire_test.go index 55582d02..5f915d9a 100644 --- a/acme/challenge_wire_test.go +++ b/acme/challenge_wire_test.go @@ -3,6 +3,7 @@ package acme import ( "context" "crypto" + "crypto/ed25519" "encoding/base64" "encoding/json" "encoding/pem" @@ -248,12 +249,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` + ClientID string `json:"client_id,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", + ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -382,12 +385,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` + ClientID string `json:"client_id,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", + ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -449,7 +454,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuerexample.com", + IssuerURL: "http://issuer.example.com", }, Config: &wireprovisioner.Config{ ClientID: "test", @@ -524,12 +529,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` + ClientID string `json:"client_id,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", + ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -666,12 +673,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` + ClientID string `json:"client_id,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", + ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -815,12 +824,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` + ClientID string `json:"client_id,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", + ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -1920,12 +1931,16 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } func Test_parseAndVerifyWireAccessToken(t *testing.T) { + t.Skip("skip this until capturing a new e2e flow with proper values") key := ` -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA= -----END PUBLIC KEY-----` publicKey, err := pemutil.Parse([]byte(key)) require.NoError(t, err) + pk, ok := publicKey.(ed25519.PublicKey) + require.True(t, ok) + issuer := "http://wire.com:19983/clients/7a41cf5b79683410/access-token" wireID := wire.ID{ ClientID: "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com", @@ -1951,7 +1966,7 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA= at, dpop, err := parseAndVerifyWireAccessToken(wireVerifyParams{ token: token, - tokenKey: publicKey, + tokenKey: pk, dpopKey: accountJWK.Public(), dpopKeyID: accountJWK.KeyID, issuer: issuer, @@ -1959,6 +1974,7 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA= chToken: ch.Token, t: issuedAt.Add(1 * time.Minute), // set validation time to be one minute after issuance }) + if assert.NoError(t, err) { // token assertions assert.Equal(t, "42c46d4c-e510-4175-9fb5-d055e125a49d", at.ID) From 0a7fe6ebe9b3c88c74aaac9a1076485d87dae37d Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 17 Jan 2024 00:47:34 +0100 Subject: [PATCH 2/8] Comment DPoP token checks that fail e2e test (currently) --- acme/challenge.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index 450b0cd4..aa310288 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -655,17 +655,18 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD } if err := wireDpop.ValidateWithLeeway(jose.Expected{ - Time: v.t, - Issuer: v.issuer, + Time: v.t, + //Issuer: v.issuer, // TODO(hs): doesn't seem to be set as claim in e2e test? }, 1*time.Minute); err != nil { return nil, nil, fmt.Errorf("failed DPoP validation: %w", err) } if wireDpop.Expiry.Time().After(v.t.Add(time.Hour * 24 * 365)) { return nil, nil, fmt.Errorf("'exp' %s is too far into the future", wireDpop.Expiry.Time().String()) } - if wireDpop.ClientID != v.wireID.ClientID { - return nil, nil, fmt.Errorf("DPoP contains invalid Wire client ID %q", wireDpop.ClientID) - } + // TODO(hs): doesn't seem to be set as claim in e2e test? + // if wireDpop.ClientID != v.wireID.ClientID { + // return nil, nil, fmt.Errorf("DPoP contains invalid Wire client ID %q", wireDpop.ClientID) + // } if wireDpop.Challenge != accessToken.Challenge { return nil, nil, fmt.Errorf("DPoP contains invalid challenge %q", wireDpop.Challenge) } From b9254744a2ae2a47c5a66e390410354cf0f0c7a3 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 17 Jan 2024 11:33:50 +0100 Subject: [PATCH 3/8] Fix validations for DPoP client ID, nonce and issuer --- acme/challenge.go | 21 +++++++++----- acme/challenge_wire_test.go | 56 ++++++++++++++++++++++++++----------- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index aa310288..7b8c7254 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -555,6 +555,7 @@ type wireCnf struct { type wireAccessToken struct { jose.Claims Challenge string `json:"chal"` + Nonce string `json:"nonce"` Cnf wireCnf `json:"cnf"` Proof string `json:"proof"` ClientID string `json:"client_id"` @@ -566,6 +567,8 @@ type wireDpopJwt struct { jose.Claims ClientID string `json:"client_id"` Challenge string `json:"chal"` + Nonce string `json:"nonce"` + HTU string `json:"htu"` } type wireDpopToken map[string]any @@ -619,7 +622,7 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD if accessToken.Challenge == "" { return nil, nil, errors.New("access token challenge must not be empty") } - if accessToken.Cnf.Kid != v.dpopKeyID { + if accessToken.Cnf.Kid == "" || accessToken.Cnf.Kid != v.dpopKeyID { return nil, nil, fmt.Errorf("expected kid %q; got %q", v.dpopKeyID, accessToken.Cnf.Kid) } if accessToken.ClientID != v.wireID.ClientID { @@ -656,18 +659,22 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD if err := wireDpop.ValidateWithLeeway(jose.Expected{ Time: v.t, - //Issuer: v.issuer, // TODO(hs): doesn't seem to be set as claim in e2e test? }, 1*time.Minute); err != nil { return nil, nil, fmt.Errorf("failed DPoP validation: %w", err) } + if wireDpop.HTU == "" || wireDpop.HTU != v.issuer { // DPoP doesn't contains "iss" claim, but has it in the "htu" claim + return nil, nil, fmt.Errorf("DPoP contains invalid issuer (htu) %q", wireDpop.HTU) + } if wireDpop.Expiry.Time().After(v.t.Add(time.Hour * 24 * 365)) { return nil, nil, fmt.Errorf("'exp' %s is too far into the future", wireDpop.Expiry.Time().String()) } - // TODO(hs): doesn't seem to be set as claim in e2e test? - // if wireDpop.ClientID != v.wireID.ClientID { - // return nil, nil, fmt.Errorf("DPoP contains invalid Wire client ID %q", wireDpop.ClientID) - // } - if wireDpop.Challenge != accessToken.Challenge { + if wireDpop.Subject != v.wireID.ClientID { + return nil, nil, fmt.Errorf("DPoP contains invalid Wire client ID %q", wireDpop.ClientID) + } + if wireDpop.Nonce == "" || wireDpop.Nonce != accessToken.Nonce { + return nil, nil, fmt.Errorf("DPoP contains invalid nonce %q", wireDpop.Nonce) + } + if wireDpop.Challenge == "" || wireDpop.Challenge != accessToken.Challenge { return nil, nil, fmt.Errorf("DPoP contains invalid challenge %q", wireDpop.Challenge) } diff --git a/acme/challenge_wire_test.go b/acme/challenge_wire_test.go index 5f915d9a..2ed33b38 100644 --- a/acme/challenge_wire_test.go +++ b/acme/challenge_wire_test.go @@ -163,7 +163,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuerexample.com", + IssuerURL: "http://issuer.example.com", }, Config: &wireprovisioner.Config{ ClientID: "test", @@ -249,14 +249,16 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` - ClientID string `json:"client_id,omitempty"` + Nonce string `json:"nonce,omitempty"` + HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", - ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Nonce: "nonce", + HTU: "http://issuer.example.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -266,6 +268,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= tokenBytes, err := json.Marshal(struct { jose.Claims Challenge string `json:"chal,omitempty"` + Nonce string `json:"nonce,omitempty"` Cnf struct { Kid string `json:"kid,omitempty"` } `json:"cnf"` @@ -280,6 +283,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", + Nonce: "nonce", Cnf: struct { Kid string `json:"kid,omitempty"` }{ @@ -318,7 +322,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuerexample.com", + IssuerURL: "http://issuer.example.com", }, Config: &wireprovisioner.Config{ ClientID: "test", @@ -328,6 +332,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= TransformTemplate: "", }, DPOP: &wireprovisioner.DPOPOptions{ + Target: "http://issuer.example.com", SigningKey: signerPEMBytes, }, }, @@ -385,14 +390,16 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` - ClientID string `json:"client_id,omitempty"` + Nonce string `json:"nonce,omitempty"` + HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", - ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Nonce: "nonce", + HTU: "http://issuer.example.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -402,6 +409,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= tokenBytes, err := json.Marshal(struct { jose.Claims Challenge string `json:"chal,omitempty"` + Nonce string `json:"nonce,omitempty"` Cnf struct { Kid string `json:"kid,omitempty"` } `json:"cnf"` @@ -416,6 +424,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", + Nonce: "nonce", Cnf: struct { Kid string `json:"kid,omitempty"` }{ @@ -468,6 +477,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= TransformTemplate: "", }, DPOP: &wireprovisioner.DPOPOptions{ + Target: "http://issuer.example.com", SigningKey: signerPEMBytes, }, }, @@ -529,14 +539,16 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` - ClientID string `json:"client_id,omitempty"` + Nonce string `json:"nonce,omitempty"` + HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", - ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Nonce: "nonce", + HTU: "http://issuer.example.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -546,6 +558,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= tokenBytes, err := json.Marshal(struct { jose.Claims Challenge string `json:"chal,omitempty"` + Nonce string `json:"nonce,omitempty"` Cnf struct { Kid string `json:"kid,omitempty"` } `json:"cnf"` @@ -560,6 +573,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", + Nonce: "nonce", Cnf: struct { Kid string `json:"kid,omitempty"` }{ @@ -598,7 +612,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuerexample.com", + IssuerURL: "http://issuer.example.com", }, Config: &wireprovisioner.Config{ ClientID: "test", @@ -612,6 +626,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= TransformTemplate: "", }, DPOP: &wireprovisioner.DPOPOptions{ + Target: "http://issuer.example.com", SigningKey: signerPEMBytes, }, }, @@ -673,14 +688,16 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` - ClientID string `json:"client_id,omitempty"` + Nonce string `json:"nonce,omitempty"` + HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", - ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Nonce: "nonce", + HTU: "http://issuer.example.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -690,6 +707,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= tokenBytes, err := json.Marshal(struct { jose.Claims Challenge string `json:"chal,omitempty"` + Nonce string `json:"nonce,omitempty"` Cnf struct { Kid string `json:"kid,omitempty"` } `json:"cnf"` @@ -704,6 +722,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", + Nonce: "nonce", Cnf: struct { Kid string `json:"kid,omitempty"` }{ @@ -742,7 +761,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuerexample.com", + IssuerURL: "http://issuer.example.com", }, Config: &wireprovisioner.Config{ ClientID: "test", @@ -756,6 +775,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= TransformTemplate: "", }, DPOP: &wireprovisioner.DPOPOptions{ + Target: "http://issuer.example.com", SigningKey: signerPEMBytes, }, }, @@ -824,14 +844,16 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` - ClientID string `json:"client_id,omitempty"` + Nonce string `json:"nonce,omitempty"` + HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", - ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Nonce: "nonce", + HTU: "http://issuer.example.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -841,6 +863,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= tokenBytes, err := json.Marshal(struct { jose.Claims Challenge string `json:"chal,omitempty"` + Nonce string `json:"nonce,omitempty"` Cnf struct { Kid string `json:"kid,omitempty"` } `json:"cnf"` @@ -855,6 +878,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", + Nonce: "nonce", Cnf: struct { Kid string `json:"kid,omitempty"` }{ @@ -893,7 +917,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuerexample.com", + IssuerURL: "http://issuer.example.com", }, Config: &wireprovisioner.Config{ ClientID: "test", @@ -907,6 +931,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= TransformTemplate: "", }, DPOP: &wireprovisioner.DPOPOptions{ + Target: "http://issuer.example.com", SigningKey: signerPEMBytes, }, }, @@ -1931,7 +1956,6 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } func Test_parseAndVerifyWireAccessToken(t *testing.T) { - t.Skip("skip this until capturing a new e2e flow with proper values") key := ` -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA= From f221232a80c8616f565b8a9c6e5920c1cecc0375 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 17 Jan 2024 11:38:54 +0100 Subject: [PATCH 4/8] Fix ACME `Validate` test for Wire DPoP challenge --- acme/challenge_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/acme/challenge_test.go b/acme/challenge_test.go index db737758..5bddfa13 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -1007,14 +1007,16 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` - ClientID string `json:"client_id,omitempty"` + Nonce string `json:"nonce,omitempty"` + HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", - ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Nonce: "nonce", + HTU: "http://issuer.example.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -1024,6 +1026,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= tokenBytes, err := json.Marshal(struct { jose.Claims Challenge string `json:"chal,omitempty"` + Nonce string `json:"nonce,omitempty"` Cnf struct { Kid string `json:"kid,omitempty"` } `json:"cnf"` @@ -1038,6 +1041,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", + Nonce: "nonce", Cnf: struct { Kid string `json:"kid,omitempty"` }{ @@ -1089,6 +1093,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= TransformTemplate: "", }, DPOP: &wireprovisioner.DPOPOptions{ + Target: "http://issuer.example.com", SigningKey: signerPEMBytes, }, }, From f150a4f850e08dc50cbfffcdb59e733262bd8309 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 17 Jan 2024 12:35:16 +0100 Subject: [PATCH 5/8] Remove `sync.Once` for Wire configuration validation --- authority/provisioner/wire/dpop_options.go | 2 +- authority/provisioner/wire/oidc_options.go | 6 ++++-- authority/provisioner/wire/wire_options.go | 17 +++-------------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/authority/provisioner/wire/dpop_options.go b/authority/provisioner/wire/dpop_options.go index c4172a65..721eab01 100644 --- a/authority/provisioner/wire/dpop_options.go +++ b/authority/provisioner/wire/dpop_options.go @@ -10,7 +10,7 @@ import ( ) type DPOPOptions struct { - // Public part of the signing key for DPoP access token + // Public part of the signing key for DPoP access token in PEM format SigningKey []byte `json:"key"` // URI template for the URI the ACME client must call to fetch the DPoP challenge proof (an access token from wire-server) Target string `json:"target"` diff --git a/authority/provisioner/wire/oidc_options.go b/authority/provisioner/wire/oidc_options.go index 67ead41c..5040fa07 100644 --- a/authority/provisioner/wire/oidc_options.go +++ b/authority/provisioner/wire/oidc_options.go @@ -24,8 +24,10 @@ type Provider struct { } type Config struct { - ClientID string `json:"clientId,omitempty"` - SignatureAlgorithms []string `json:"signatureAlgorithms,omitempty"` + ClientID string `json:"clientId,omitempty"` + SignatureAlgorithms []string `json:"signatureAlgorithms,omitempty"` + + // the properties below are only used for testing SkipClientIDCheck bool `json:"-"` SkipExpiryCheck bool `json:"-"` SkipIssuerCheck bool `json:"-"` diff --git a/authority/provisioner/wire/wire_options.go b/authority/provisioner/wire/wire_options.go index f143c287..2ae5543f 100644 --- a/authority/provisioner/wire/wire_options.go +++ b/authority/provisioner/wire/wire_options.go @@ -3,16 +3,12 @@ package wire import ( "errors" "fmt" - "sync" ) // Options holds the Wire ACME extension options type Options struct { OIDC *OIDCOptions `json:"oidc,omitempty"` DPOP *DPOPOptions `json:"dpop,omitempty"` - - validateOnce sync.Once - validationErr error } // GetOIDCOptions returns the OIDC options. @@ -31,17 +27,10 @@ func (o *Options) GetDPOPOptions() *DPOPOptions { return o.DPOP } +// Validate validates and initializes the Wire OIDC and DPoP options. +// +// TODO(hs): find a good way to perform this only once. func (o *Options) Validate() error { - o.validateOnce.Do( - func() { - o.validationErr = validate(o) - }, - ) - - return o.validationErr -} - -func validate(o *Options) error { if oidc := o.GetOIDCOptions(); oidc != nil { if err := oidc.validateAndInitialize(); err != nil { return fmt.Errorf("failed initializing OIDC options: %w", err) From 36e14de88251ed94596809617b2a22f9c52df11e Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 17 Jan 2024 13:02:12 +0100 Subject: [PATCH 6/8] Improve Wire persistence errors --- acme/db/nosql/wire.go | 24 +++++++++++++----------- acme/db/nosql/wire_test.go | 8 ++++---- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/acme/db/nosql/wire.go b/acme/db/nosql/wire.go index 1fa072cf..03b93505 100644 --- a/acme/db/nosql/wire.go +++ b/acme/db/nosql/wire.go @@ -6,7 +6,6 @@ import ( "fmt" "time" - "github.com/pkg/errors" "github.com/smallstep/certificates/acme" "github.com/smallstep/nosql" ) @@ -20,15 +19,16 @@ type dbDpopToken struct { // getDBDpopToken retrieves and unmarshals an DPoP type from the database. func (db *DB) getDBDpopToken(_ context.Context, orderID string) (*dbDpopToken, error) { b, err := db.db.Get(wireDpopTokenTable, []byte(orderID)) - if nosql.IsErrNotFound(err) { - return nil, acme.NewError(acme.ErrorMalformedType, "dpop token %q not found", orderID) - } else if err != nil { - return nil, errors.Wrapf(err, "error loading dpop %q", orderID) + if err != nil { + if nosql.IsErrNotFound(err) { + return nil, acme.NewError(acme.ErrorMalformedType, "dpop token %q not found", orderID) + } + return nil, fmt.Errorf("failed loading dpop token %q: %w", orderID, err) } d := new(dbDpopToken) if err := json.Unmarshal(b, d); err != nil { - return nil, errors.Wrapf(err, "error unmarshaling dpop %q into dbDpopToken", orderID) + return nil, fmt.Errorf("failed unmarshaling dpop token %q into dbDpopToken: %w", orderID, err) } return d, nil } @@ -74,14 +74,16 @@ type dbOidcToken struct { // getDBOidcToken retrieves and unmarshals an OIDC id token type from the database. func (db *DB) getDBOidcToken(_ context.Context, orderID string) (*dbOidcToken, error) { b, err := db.db.Get(wireOidcTokenTable, []byte(orderID)) - if nosql.IsErrNotFound(err) { - return nil, acme.NewError(acme.ErrorMalformedType, "oidc token %q not found", orderID) - } else if err != nil { - return nil, errors.Wrapf(err, "error loading oidc token %q", orderID) + if err != nil { + if nosql.IsErrNotFound(err) { + return nil, acme.NewError(acme.ErrorMalformedType, "oidc token %q not found", orderID) + } + return nil, fmt.Errorf("failed loading oidc token %q: %w", orderID, err) } + o := new(dbOidcToken) if err := json.Unmarshal(b, o); err != nil { - return nil, errors.Wrapf(err, "error unmarshaling oidc token %q into dbOidcToken", orderID) + return nil, fmt.Errorf("failed unmarshaling oidc token %q into dbOidcToken: %w", orderID, err) } return o, nil } diff --git a/acme/db/nosql/wire_test.go b/acme/db/nosql/wire_test.go index 1f820fa5..136db3a0 100644 --- a/acme/db/nosql/wire_test.go +++ b/acme/db/nosql/wire_test.go @@ -57,7 +57,7 @@ func TestDB_GetDpopToken(t *testing.T) { db: db, }, orderID: "orderID", - expectedErr: errors.New(`error unmarshaling dpop "orderID" into dbDpopToken: invalid character ':' after top-level value`), + expectedErr: errors.New(`failed unmarshaling dpop token "orderID" into dbDpopToken: invalid character ':' after top-level value`), } }, "fail/db.Get": func(t *testing.T) test { @@ -73,7 +73,7 @@ func TestDB_GetDpopToken(t *testing.T) { db: db, }, orderID: "orderID", - expectedErr: errors.New(`error loading dpop "orderID": fail`), + expectedErr: errors.New(`failed loading dpop token "orderID": fail`), } }, "ok": func(t *testing.T) test { @@ -245,7 +245,7 @@ func TestDB_GetOidcToken(t *testing.T) { db: db, }, orderID: "orderID", - expectedErr: errors.New(`error unmarshaling oidc token "orderID" into dbOidcToken: invalid character ':' after top-level value`), + expectedErr: errors.New(`failed unmarshaling oidc token "orderID" into dbOidcToken: invalid character ':' after top-level value`), } }, "fail/db.Get": func(t *testing.T) test { @@ -261,7 +261,7 @@ func TestDB_GetOidcToken(t *testing.T) { db: db, }, orderID: "orderID", - expectedErr: errors.New(`error loading oidc token "orderID": fail`), + expectedErr: errors.New(`failed loading oidc token "orderID": fail`), } }, "ok": func(t *testing.T) test { From 2f3819aa4e99dcb36203015f9a0666628a97c45d Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 17 Jan 2024 14:12:09 +0100 Subject: [PATCH 7/8] Use key authorization from ID token and `handle` -> `preferred_username` --- acme/api/order_test.go | 4 +- acme/api/wire_integration_test.go | 16 +- acme/challenge.go | 32 +-- acme/challenge_test.go | 34 +-- acme/challenge_wire_test.go | 264 +++++++++--------- acme/db/nosql/wire_test.go | 14 +- authority/provisioner/wire/oidc_options.go | 2 +- .../provisioner/wire/oidc_options_test.go | 14 +- 8 files changed, 188 insertions(+), 192 deletions(-) diff --git a/acme/api/order_test.go b/acme/api/order_test.go index 1a3c5df4..4948ea94 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -1729,11 +1729,11 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= TokenURL: "", JWKSURL: "", UserInfoURL: "", - Algorithms: []string{}, + Algorithms: []string{"ES256"}, }, Config: &wire.Config{ ClientID: "integration test", - SignatureAlgorithms: []string{}, + SignatureAlgorithms: []string{"ES256"}, SkipClientIDCheck: true, SkipExpiryCheck: true, SkipIssuerCheck: true, diff --git a/acme/api/wire_integration_test.go b/acme/api/wire_integration_test.go index 2fa72797..3bb62a9a 100644 --- a/acme/api/wire_integration_test.go +++ b/acme/api/wire_integration_test.go @@ -85,7 +85,7 @@ func TestWireIntegration(t *testing.T) { "organization": "WireTest", "commonName": {{ toJson .Oidc.name }} }, - "uris": [{{ toJson .Oidc.handle }}, {{ toJson .Dpop.sub }}], + "uris": [{{ toJson .Oidc.preferred_username }}, {{ toJson .Dpop.sub }}], "keyUsage": ["digitalSignature"], "extKeyUsage": ["clientAuth"] }`, @@ -98,11 +98,11 @@ func TestWireIntegration(t *testing.T) { TokenURL: "", JWKSURL: "", UserInfoURL: "", - Algorithms: []string{}, + Algorithms: []string{"ES256"}, }, Config: &wire.Config{ ClientID: "integration test", - SignatureAlgorithms: []string{}, + SignatureAlgorithms: []string{"ES256"}, SkipClientIDCheck: true, SkipExpiryCheck: true, SkipIssuerCheck: true, @@ -306,12 +306,16 @@ func TestWireIntegration(t *testing.T) { jose.Claims Challenge string `json:"chal,omitempty"` Handle string `json:"handle,omitempty"` + Nonce string `json:"nonce,omitempty"` + HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ Subject: "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", }, Challenge: "token", Handle: "wireapp://%40alice.smith.qa@example.com", + Nonce: "nonce", + HTU: "http://issuer.example.com", }) require.NoError(t, err) dpop, err := dpopSigner.Sign(dpopBytes) @@ -366,6 +370,7 @@ func TestWireIntegration(t *testing.T) { jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` + KeyAuth string `json:"keyauth"` }{ Claims: jose.Claims{ Issuer: "https://issuer.example.com", @@ -374,6 +379,7 @@ func TestWireIntegration(t *testing.T) { }, Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", + KeyAuth: keyAuth, }) require.NoError(t, err) signed, err := oidcTokenSigner.Sign(tokenBytes) @@ -382,10 +388,8 @@ func TestWireIntegration(t *testing.T) { require.NoError(t, err) p, err := json.Marshal(struct { IDToken string `json:"id_token"` - KeyAuth string `json:"keyauth"` }{ IDToken: idToken, - KeyAuth: keyAuth, }) require.NoError(t, err) payload = p @@ -436,7 +440,7 @@ func TestWireIntegration(t *testing.T) { t.Log("updated challenge:", challenge.ID, challenge.Status) switch challenge.Type { case acme.WIREOIDC01: - err = db.CreateOidcToken(ctx, order.ID, map[string]any{"name": "Smith, Alice M (QA)", "handle": "wireapp://%40alice.smith.qa@example.com"}) + err = db.CreateOidcToken(ctx, order.ID, map[string]any{"name": "Smith, Alice M (QA)", "preferred_username": "wireapp://%40alice.smith.qa@example.com"}) require.NoError(t, err) case acme.WIREDPOP01: err = db.CreateDpopToken(ctx, order.ID, map[string]any{"sub": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com"}) diff --git a/acme/challenge.go b/acme/challenge.go index 7b8c7254..7d67a4d2 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -355,8 +355,6 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK type wireOidcPayload struct { // IDToken contains the OIDC identity token IDToken string `json:"id_token"` - // KeyAuth ({challenge-token}.{jwk-thumbprint}) - KeyAuth string `json:"keyauth"` } func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, payload []byte) error { @@ -381,16 +379,6 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO return WrapErrorISE(err, "failed getting Wire options") } - // TODO(hs): move this into validation below? - expectedKeyAuth, err := KeyAuthorization(ch.Token, jwk) - if err != nil { - return WrapErrorISE(err, "error determining key authorization") - } - if expectedKeyAuth != oidcPayload.KeyAuth { - return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, - "keyAuthorization does not match; expected %q, but got %q", expectedKeyAuth, oidcPayload.KeyAuth)) - } - oidcOptions := wireOptions.GetOIDCOptions() verifier := oidcOptions.GetProvider(ctx).Verifier(oidcOptions.GetConfig()) idToken, err := verifier.Verify(ctx, oidcPayload.IDToken) @@ -404,13 +392,23 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO Handle string `json:"name"` Issuer string `json:"iss,omitempty"` GivenName string `json:"given_name,omitempty"` - KeyAuth string `json:"keyauth"` // TODO(hs): use this property instead of the one in the payload after https://github.com/wireapp/rusty-jwt-tools/tree/fix/keyauth is done + KeyAuth string `json:"keyauth"` } if err := idToken.Claims(&claims); err != nil { return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err, "error retrieving claims from ID token")) } + // TODO(hs): move this into validation below? + expectedKeyAuth, err := KeyAuthorization(ch.Token, jwk) + if err != nil { + return WrapErrorISE(err, "error determining key authorization") + } + if expectedKeyAuth != claims.KeyAuth { + return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, + "keyAuthorization does not match; expected %q, but got %q", expectedKeyAuth, claims.KeyAuth)) + } + transformedIDToken, err := validateWireOIDCClaims(oidcOptions, idToken, wireID) if err != nil { return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err, "claims in OIDC ID token don't match")) @@ -459,12 +457,12 @@ func validateWireOIDCClaims(o *wireprovisioner.OIDCOptions, token *oidc.IDToken, return nil, fmt.Errorf("invalid 'name' %q after transformation", name) } - handle, ok := transformed["handle"] + preferredUsername, ok := transformed["preferred_username"] if !ok { - return nil, fmt.Errorf("transformed OIDC ID token does not contain 'handle'") + return nil, fmt.Errorf("transformed OIDC ID token does not contain 'preferred_username'") } - if wireID.Handle != handle { - return nil, fmt.Errorf("invalid 'handle' %q after transformation", handle) + if wireID.Handle != preferredUsername { + return nil, fmt.Errorf("invalid 'preferred_username' %q after transformation", preferredUsername) } return transformed, nil diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 5bddfa13..4d63928e 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -891,6 +891,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` + KeyAuth string `json:"keyauth"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -899,6 +900,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", + KeyAuth: keyAuth, }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -907,10 +909,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= require.NoError(t, err) payload, err := json.Marshal(struct { IDToken string `json:"id_token"` - KeyAuth string `json:"keyauth"` }{ IDToken: idToken, - KeyAuth: keyAuth, }) require.NoError(t, err) valueBytes, err := json.Marshal(struct { @@ -929,17 +929,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: srv.URL, - JWKSURL: srv.URL + "/keys", + IssuerURL: srv.URL, + JWKSURL: srv.URL + "/keys", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -978,7 +975,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= MockCreateOidcToken: func(ctx context.Context, orderID string, idToken map[string]interface{}) error { assert.Equal(t, "orderID", orderID) assert.Equal(t, "Alice Smith", idToken["name"].(string)) - assert.Equal(t, "wireapp://%40alice_wire@wire.com", idToken["handle"].(string)) + assert.Equal(t, "wireapp://%40alice_wire@wire.com", idToken["preferred_username"].(string)) return nil }, }, @@ -1079,16 +1076,13 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuerexample.com", + IssuerURL: "http://issuerexample.com", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, diff --git a/acme/challenge_wire_test.go b/acme/challenge_wire_test.go index 2ed33b38..65f7be51 100644 --- a/acme/challenge_wire_test.go +++ b/acme/challenge_wire_test.go @@ -163,7 +163,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuer.example.com", + IssuerURL: "http://issuer.example.com", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ ClientID: "test", @@ -322,7 +323,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuer.example.com", + IssuerURL: "http://issuer.example.com", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ ClientID: "test", @@ -463,16 +465,13 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuer.example.com", + IssuerURL: "http://issuer.example.com", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -612,16 +611,13 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuer.example.com", + IssuerURL: "http://issuer.example.com", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -761,16 +757,13 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuer.example.com", + IssuerURL: "http://issuer.example.com", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -917,16 +910,13 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuer.example.com", + IssuerURL: "http://issuer.example.com", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -1106,12 +1096,38 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, "fail/keyauth-mismatch": func(t *testing.T) test { jwk, _ := mustAccountAndKeyAuthorization(t, "token") + signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + require.NoError(t, err) + signer, err := jose.NewSigner(jose.SigningKey{ + Algorithm: jose.SignatureAlgorithm(signerJWK.Algorithm), + Key: signerJWK, + }, new(jose.SignerOptions)) + require.NoError(t, err) + srv := mustJWKServer(t, signerJWK.Public()) + tokenBytes, err := json.Marshal(struct { + jose.Claims + Name string `json:"name,omitempty"` + PreferredUsername string `json:"preferred_username,omitempty"` + KeyAuth string `json:"keyauth"` + }{ + Claims: jose.Claims{ + Issuer: srv.URL, + Audience: []string{"test"}, + Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), + }, + Name: "Alice Smith", + PreferredUsername: "wireapp://%40bob@wire.com", + KeyAuth: "wrong-keyauth", + }) + require.NoError(t, err) + signed, err := signer.Sign(tokenBytes) + require.NoError(t, err) + idToken, err := signed.CompactSerialize() + require.NoError(t, err) payload, err := json.Marshal(struct { IDToken string `json:"id_token"` - KeyAuth string `json:"keyauth"` }{ - IDToken: "some-token", - KeyAuth: "wrong-key-authorization", + IDToken: idToken, }) require.NoError(t, err) valueBytes, err := json.Marshal(struct { @@ -1130,7 +1146,9 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://issuer.example.com", + IssuerURL: srv.URL, + JWKSURL: srv.URL + "/keys", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ ClientID: "test", @@ -1154,6 +1172,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Status: StatusPending, Value: string(valueBytes), }, + srv: srv, payload: payload, ctx: ctx, jwk: jwk, @@ -1170,7 +1189,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, "urn:ietf:params:acme:error:rejectedIdentifier", k.Type) assert.Equal(t, "The server will not issue certificates for the identifier", k.Detail) assert.Equal(t, 400, k.Status) - assert.Contains(t, k.Err.Error(), `keyAuthorization does not match; expected`) + assert.Contains(t, k.Err.Error(), "keyAuthorization does not match") } } return nil @@ -1194,6 +1213,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` + KeyAuth string `json:"keyauth"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1201,7 +1221,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Name: "Alice Smith", - PreferredUsername: "wireapp://%40alice_wire@wire.com", + PreferredUsername: "wireapp://%40bob@wire.com", + KeyAuth: keyAuth, }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1210,10 +1231,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= require.NoError(t, err) payload, err := json.Marshal(struct { IDToken string `json:"id_token"` - KeyAuth string `json:"keyauth"` }{ IDToken: idToken, - KeyAuth: keyAuth, }) require.NoError(t, err) valueBytes, err := json.Marshal(struct { @@ -1232,17 +1251,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: srv.URL, - JWKSURL: srv.URL + "/keys", + IssuerURL: srv.URL, + JWKSURL: srv.URL + "/keys", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -1300,6 +1316,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` + KeyAuth string `json:"keyauth"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1308,6 +1325,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, Name: "Alice Smith", PreferredUsername: "wireapp://%40bob@wire.com", + KeyAuth: keyAuth, }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1316,10 +1334,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= require.NoError(t, err) payload, err := json.Marshal(struct { IDToken string `json:"id_token"` - KeyAuth string `json:"keyauth"` }{ IDToken: idToken, - KeyAuth: keyAuth, }) require.NoError(t, err) valueBytes, err := json.Marshal(struct { @@ -1338,17 +1354,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: srv.URL, - JWKSURL: srv.URL + "/keys", + IssuerURL: srv.URL, + JWKSURL: srv.URL + "/keys", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -1384,7 +1397,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, "urn:ietf:params:acme:error:rejectedIdentifier", k.Type) assert.Equal(t, "The server will not issue certificates for the identifier", k.Detail) assert.Equal(t, 400, k.Status) - assert.Equal(t, `claims in OIDC ID token don't match: invalid 'handle' "wireapp://%40bob@wire.com" after transformation`, k.Err.Error()) + assert.Equal(t, `claims in OIDC ID token don't match: invalid 'preferred_username' "wireapp://%40bob@wire.com" after transformation`, k.Err.Error()) } } return nil @@ -1406,6 +1419,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` + KeyAuth string `json:"keyauth"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1414,6 +1428,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", + KeyAuth: keyAuth, }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1422,10 +1437,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= require.NoError(t, err) payload, err := json.Marshal(struct { IDToken string `json:"id_token"` - KeyAuth string `json:"keyauth"` }{ IDToken: idToken, - KeyAuth: keyAuth, }) require.NoError(t, err) valueBytes, err := json.Marshal(struct { @@ -1444,17 +1457,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: srv.URL, - JWKSURL: srv.URL + "/keys", + IssuerURL: srv.URL, + JWKSURL: srv.URL + "/keys", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -1509,6 +1519,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` + KeyAuth string `json:"keyauth"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1517,6 +1528,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", + KeyAuth: keyAuth, }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1525,10 +1537,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= require.NoError(t, err) payload, err := json.Marshal(struct { IDToken string `json:"id_token"` - KeyAuth string `json:"keyauth"` }{ IDToken: idToken, - KeyAuth: keyAuth, }) require.NoError(t, err) valueBytes, err := json.Marshal(struct { @@ -1547,17 +1557,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: srv.URL, - JWKSURL: srv.URL + "/keys", + IssuerURL: srv.URL, + JWKSURL: srv.URL + "/keys", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -1616,6 +1623,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` + KeyAuth string `json:"keyauth"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1624,6 +1632,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", + KeyAuth: keyAuth, }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1632,10 +1641,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= require.NoError(t, err) payload, err := json.Marshal(struct { IDToken string `json:"id_token"` - KeyAuth string `json:"keyauth"` }{ IDToken: idToken, - KeyAuth: keyAuth, }) require.NoError(t, err) valueBytes, err := json.Marshal(struct { @@ -1654,17 +1661,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: srv.URL, - JWKSURL: srv.URL + "/keys", + IssuerURL: srv.URL, + JWKSURL: srv.URL + "/keys", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -1723,6 +1727,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` + KeyAuth string `json:"keyauth"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1731,6 +1736,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", + KeyAuth: keyAuth, }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1739,10 +1745,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= require.NoError(t, err) payload, err := json.Marshal(struct { IDToken string `json:"id_token"` - KeyAuth string `json:"keyauth"` }{ IDToken: idToken, - KeyAuth: keyAuth, }) require.NoError(t, err) valueBytes, err := json.Marshal(struct { @@ -1761,17 +1765,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: srv.URL, - JWKSURL: srv.URL + "/keys", + IssuerURL: srv.URL, + JWKSURL: srv.URL + "/keys", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -1810,7 +1811,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= MockCreateOidcToken: func(ctx context.Context, orderID string, idToken map[string]interface{}) error { assert.Equal(t, "orderID", orderID) assert.Equal(t, "Alice Smith", idToken["name"].(string)) - assert.Equal(t, "wireapp://%40alice_wire@wire.com", idToken["handle"].(string)) + assert.Equal(t, "wireapp://%40alice_wire@wire.com", idToken["preferred_username"].(string)) return errors.New("fail") }, }, @@ -1836,6 +1837,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` + KeyAuth string `json:"keyauth"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1844,6 +1846,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", + KeyAuth: keyAuth, }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1852,10 +1855,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= require.NoError(t, err) payload, err := json.Marshal(struct { IDToken string `json:"id_token"` - KeyAuth string `json:"keyauth"` }{ IDToken: idToken, - KeyAuth: keyAuth, }) require.NoError(t, err) valueBytes, err := json.Marshal(struct { @@ -1874,17 +1875,14 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Wire: &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: srv.URL, - JWKSURL: srv.URL + "/keys", + IssuerURL: srv.URL, + JWKSURL: srv.URL + "/keys", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "test", - SignatureAlgorithms: []string{"ES256"}, - SkipClientIDCheck: false, - SkipExpiryCheck: false, - SkipIssuerCheck: false, - InsecureSkipSignatureCheck: false, - Now: time.Now, + ClientID: "test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: "", }, @@ -1923,7 +1921,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= MockCreateOidcToken: func(ctx context.Context, orderID string, idToken map[string]interface{}) error { assert.Equal(t, "orderID", orderID) assert.Equal(t, "Alice Smith", idToken["name"].(string)) - assert.Equal(t, "wireapp://%40alice_wire@wire.com", idToken["handle"].(string)) + assert.Equal(t, "wireapp://%40alice_wire@wire.com", idToken["preferred_username"].(string)) return nil }, }, @@ -2034,16 +2032,18 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= opts := &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "http://dex:15818/dex", + IssuerURL: "http://dex:15818/dex", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "wireapp", + ClientID: "wireapp", + SignatureAlgorithms: []string{"ES256"}, Now: func() time.Time { return time.Date(2024, 1, 12, 18, 32, 41, 0, time.UTC) // (Token Expiry: 2024-01-12 21:32:42 +0100 CET) }, - InsecureSkipSignatureCheck: true, + InsecureSkipSignatureCheck: true, // skipping signature check for this specific test }, - TransformTemplate: `{"name": "{{ .preferred_username }}", "handle": "{{ .name }}"}`, + TransformTemplate: `{"name": "{{ .preferred_username }}", "preferred_username": "{{ .name }}"}`, }, DPOP: &wireprovisioner.DPOPOptions{ SigningKey: []byte(fakeKey), @@ -2069,7 +2069,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= got, err := validateWireOIDCClaims(o, idToken, wireID) assert.NoError(t, err) - assert.Equal(t, "wireapp://%40alice_wire@wire.com", got["handle"].(string)) + assert.Equal(t, "wireapp://%40alice_wire@wire.com", got["preferred_username"].(string)) assert.Equal(t, "Alice Smith", got["name"].(string)) assert.Equal(t, "http://dex:15818/dex", got["iss"].(string)) } @@ -2083,11 +2083,13 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= opts := &wireprovisioner.Options{ OIDC: &wireprovisioner.OIDCOptions{ Provider: &wireprovisioner.Provider{ - IssuerURL: "https://issuer.example.com", + IssuerURL: "https://issuer.example.com", + Algorithms: []string{"ES256"}, }, Config: &wireprovisioner.Config{ - ClientID: "unit test", - Now: time.Now, + ClientID: "unit test", + SignatureAlgorithms: []string{"ES256"}, + Now: time.Now, }, TransformTemplate: transformTemplate, }, @@ -2130,18 +2132,18 @@ func Test_idTokenTransformation(t *testing.T) { require.NoError(t, err) // default transformation sets preferred username to handle; name as name - assert.Equal(t, "Alice Smith", result["handle"].(string)) + assert.Equal(t, "Alice Smith", result["preferred_username"].(string)) assert.Equal(t, "wireapp://%40alice_wire@wire.com", result["name"].(string)) assert.Equal(t, "http://dex:15818/dex", result["iss"].(string)) // swap the preferred_name and the name - swap := `{"name": "{{ .preferred_username }}", "handle": "{{ .name }}"}` + swap := `{"name": "{{ .preferred_username }}", "preferred_username": "{{ .name }}"}` opts = createWireOptions(t, swap) result, err = opts.GetOIDCOptions().Transform(m) require.NoError(t, err) // with the transformation, handle now contains wireapp://%40alice_wire@wire.com, name contains Alice Smith - assert.Equal(t, "wireapp://%40alice_wire@wire.com", result["handle"].(string)) + assert.Equal(t, "wireapp://%40alice_wire@wire.com", result["preferred_username"].(string)) assert.Equal(t, "Alice Smith", result["name"].(string)) assert.Equal(t, "http://dex:15818/dex", result["iss"].(string)) } diff --git a/acme/db/nosql/wire_test.go b/acme/db/nosql/wire_test.go index 136db3a0..6759f420 100644 --- a/acme/db/nosql/wire_test.go +++ b/acme/db/nosql/wire_test.go @@ -270,7 +270,7 @@ func TestDB_GetOidcToken(t *testing.T) { require.NoError(t, err) token := dbOidcToken{ ID: "orderID", - Content: []byte(`{"name": "Alice Smith", "handle": "@alice.smith"}`), + Content: []byte(`{"name": "Alice Smith", "preferred_username": "@alice.smith"}`), CreatedAt: time.Now(), } b, err := json.Marshal(token) @@ -283,8 +283,8 @@ func TestDB_GetOidcToken(t *testing.T) { }, orderID: "orderID", expected: map[string]any{ - "name": "Alice Smith", - "handle": "@alice.smith", + "name": "Alice Smith", + "preferred_username": "@alice.smith", }, } }, @@ -335,8 +335,8 @@ func TestDB_CreateOidcToken(t *testing.T) { }, orderID: "orderID", oidc: map[string]any{ - "name": "Alice Smith", - "handle": "@alice.smith", + "name": "Alice Smith", + "preferred_username": "@alice.smith", }, expectedErr: errors.New("failed saving oidc token: error saving acme oidc: fail"), } @@ -351,8 +351,8 @@ func TestDB_CreateOidcToken(t *testing.T) { }, orderID: "orderID", oidc: map[string]any{ - "name": "Alice Smith", - "handle": "@alice.smith", + "name": "Alice Smith", + "preferred_username": "@alice.smith", }, } }, diff --git a/authority/provisioner/wire/oidc_options.go b/authority/provisioner/wire/oidc_options.go index 5040fa07..5bbcbc7a 100644 --- a/authority/provisioner/wire/oidc_options.go +++ b/authority/provisioner/wire/oidc_options.go @@ -68,7 +68,7 @@ func (o *OIDCOptions) GetConfig() *oidc.Config { } } -const defaultTemplate = `{"name": "{{ .name }}", "handle": "{{ .preferred_username }}"}` +const defaultTemplate = `{"name": "{{ .name }}", "preferred_username": "{{ .preferred_username }}"}` func (o *OIDCOptions) validateAndInitialize() (err error) { if o.Provider == nil { diff --git a/authority/provisioner/wire/oidc_options_test.go b/authority/provisioner/wire/oidc_options_test.go index 6346bcd8..8b3eaa75 100644 --- a/authority/provisioner/wire/oidc_options_test.go +++ b/authority/provisioner/wire/oidc_options_test.go @@ -11,9 +11,9 @@ import ( func TestOIDCOptions_Transform(t *testing.T) { defaultTransform, err := parseTransform(``) require.NoError(t, err) - swapTransform, err := parseTransform(`{"name": "{{ .preferred_username }}", "handle": "{{ .name }}"}`) + swapTransform, err := parseTransform(`{"name": "{{ .preferred_username }}", "preferred_username": "{{ .name }}"}`) require.NoError(t, err) - funcTransform, err := parseTransform(`{"name": "{{ .name }}", "handle": "{{ first .usernames }}"}`) + funcTransform, err := parseTransform(`{"name": "{{ .name }}", "preferred_username": "{{ first .usernames }}"}`) require.NoError(t, err) type fields struct { transform *template.Template @@ -67,7 +67,6 @@ func TestOIDCOptions_Transform(t *testing.T) { }, want: map[string]any{ "name": "Example", - "handle": "Preferred", "preferred_username": "Preferred", }, }, @@ -84,8 +83,7 @@ func TestOIDCOptions_Transform(t *testing.T) { }, want: map[string]any{ "name": "Preferred", - "handle": "Example", - "preferred_username": "Preferred", + "preferred_username": "Example", }, }, { @@ -100,9 +98,9 @@ func TestOIDCOptions_Transform(t *testing.T) { }, }, want: map[string]any{ - "name": "Example", - "handle": "name-1", - "usernames": []string{"name-1", "name-2", "name-3"}, + "name": "Example", + "preferred_username": "name-1", + "usernames": []string{"name-1", "name-2", "name-3"}, }, }, } From 19dbd02451da40767028b044582f4af2793be867 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Wed, 17 Jan 2024 16:04:58 +0100 Subject: [PATCH 8/8] Add audience validation to access, dpop and id token --- acme/challenge.go | 43 ++++++++++--- acme/challenge_test.go | 12 ++-- acme/challenge_wire_test.go | 118 ++++++++++++++++++++++++++++-------- 3 files changed, 133 insertions(+), 40 deletions(-) diff --git a/acme/challenge.go b/acme/challenge.go index 7d67a4d2..3a53ed3a 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -362,6 +362,10 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO if !ok { return NewErrorISE("missing provisioner") } + linker, ok := LinkerFromContext(ctx) + if !ok { + return NewErrorISE("missing linker") + } var oidcPayload wireOidcPayload err := json.Unmarshal(payload, &oidcPayload) @@ -388,11 +392,12 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO } var claims struct { - Name string `json:"preferred_username,omitempty"` - Handle string `json:"name"` - Issuer string `json:"iss,omitempty"` - GivenName string `json:"given_name,omitempty"` - KeyAuth string `json:"keyauth"` + Name string `json:"preferred_username,omitempty"` + Handle string `json:"name"` + Issuer string `json:"iss,omitempty"` + GivenName string `json:"given_name,omitempty"` + KeyAuth string `json:"keyauth"` + ACMEAudience string `json:"acme_aud,omitempty"` } if err := idToken.Claims(&claims); err != nil { return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err, @@ -409,6 +414,13 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO "keyAuthorization does not match; expected %q, but got %q", expectedKeyAuth, claims.KeyAuth)) } + // audience is the full URL to the challenge + acmeAudience := linker.GetLink(ctx, ChallengeLinkType, ch.AuthorizationID, ch.ID) + if claims.ACMEAudience != acmeAudience { + return storeError(ctx, db, ch, true, NewError(ErrorRejectedIdentifierType, + "invalid 'acme_aud' %q", claims.ACMEAudience)) + } + transformedIDToken, err := validateWireOIDCClaims(oidcOptions, idToken, wireID) if err != nil { return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err, "claims in OIDC ID token don't match")) @@ -478,6 +490,10 @@ func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *j if !ok { return NewErrorISE("missing provisioner") } + linker, ok := LinkerFromContext(ctx) + if !ok { + return NewErrorISE("missing linker") + } var dpopPayload wireDpopPayload if err := json.Unmarshal(payload, &dpopPayload); err != nil { @@ -505,12 +521,16 @@ func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *j return WrapErrorISE(err, "invalid Go template registered for 'target'") } + // audience is the full URL to the challenge + audience := linker.GetLink(ctx, ChallengeLinkType, ch.AuthorizationID, ch.ID) + params := wireVerifyParams{ token: dpopPayload.AccessToken, tokenKey: dpopOptions.GetSigningKey(), dpopKey: accountJWK.Public(), dpopKeyID: accountJWK.KeyID, issuer: issuer, + audience: audience, wireID: wireID, chToken: ch.Token, t: clock.Now().UTC(), @@ -577,6 +597,7 @@ type wireVerifyParams struct { dpopKey crypto.PublicKey dpopKeyID string issuer string + audience string wireID wire.ID chToken string t time.Time @@ -611,8 +632,9 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD } if err := accessToken.ValidateWithLeeway(jose.Expected{ - Time: v.t, - Issuer: v.issuer, + Time: v.t, + Issuer: v.issuer, + Audience: jose.Audience{v.audience}, }, 1*time.Minute); err != nil { return nil, nil, fmt.Errorf("failed validation: %w", err) } @@ -626,7 +648,7 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD if accessToken.ClientID != v.wireID.ClientID { return nil, nil, fmt.Errorf("invalid Wire client ID %q", accessToken.ClientID) } - if accessToken.Expiry.Time().After(v.t.Add(time.Hour * 24 * 365)) { + if accessToken.Expiry.Time().After(v.t.Add(time.Hour)) { return nil, nil, fmt.Errorf("'exp' %s is too far into the future", accessToken.Expiry.Time().String()) } if accessToken.Scope != "wire_client_id" { @@ -656,14 +678,15 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD } if err := wireDpop.ValidateWithLeeway(jose.Expected{ - Time: v.t, + Time: v.t, + Audience: jose.Audience{v.audience}, }, 1*time.Minute); err != nil { return nil, nil, fmt.Errorf("failed DPoP validation: %w", err) } if wireDpop.HTU == "" || wireDpop.HTU != v.issuer { // DPoP doesn't contains "iss" claim, but has it in the "htu" claim return nil, nil, fmt.Errorf("DPoP contains invalid issuer (htu) %q", wireDpop.HTU) } - if wireDpop.Expiry.Time().After(v.t.Add(time.Hour * 24 * 365)) { + if wireDpop.Expiry.Time().After(v.t.Add(time.Hour)) { return nil, nil, fmt.Errorf("'exp' %s is too far into the future", wireDpop.Expiry.Time().String()) } if wireDpop.Subject != v.wireID.ClientID { diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 4d63928e..35d94376 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -202,7 +202,7 @@ func newWireProvisionerWithOptions(t *testing.T, options *provisioner.Options) * t.Helper() prov := &provisioner.ACME{ Type: "ACME", - Name: "acme", + Name: "wire", Options: options, Challenges: []provisioner.ACMEChallenge{ provisioner.WIREOIDC_01, @@ -892,6 +892,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` KeyAuth string `json:"keyauth"` + ACMEAudience string `json:"acme_aud"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -901,6 +902,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", KeyAuth: keyAuth, + ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID", }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -945,6 +947,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -999,7 +1002,6 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= signerPEMBlock, err := pemutil.Serialize(signerJWK.Public().Key) require.NoError(t, err) signerPEMBytes := pem.EncodeToMemory(signerPEMBlock) - dpopBytes, err := json.Marshal(struct { jose.Claims Challenge string `json:"chal,omitempty"` @@ -1008,7 +1010,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ - Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", @@ -1034,7 +1037,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }{ Claims: jose.Claims{ Issuer: "http://issuer.example.com", - Audience: []string{"test"}, + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", @@ -1092,6 +1095,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", diff --git a/acme/challenge_wire_test.go b/acme/challenge_wire_test.go index 65f7be51..5a471a0f 100644 --- a/acme/challenge_wire_test.go +++ b/acme/challenge_wire_test.go @@ -46,8 +46,21 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, } }, + "fail/no-linker": func(t *testing.T) test { + ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + return test{ + ctx: ctx, + expectedErr: &Error{ + Type: "urn:ietf:params:acme:error:serverInternal", + Detail: "The server experienced an internal error", + Status: 500, + Err: errors.New("missing linker"), + }, + } + }, "fail/unmarshal": func(t *testing.T) test { ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ctx: ctx, payload: []byte("?!"), @@ -70,6 +83,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, "fail/wire-parse-id": func(t *testing.T) test { ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ctx: ctx, payload: []byte("{}"), @@ -92,6 +106,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, "fail/wire-parse-client-id": func(t *testing.T) test { ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) valueBytes, err := json.Marshal(struct { Name string `json:"name,omitempty"` Domain string `json:"domain,omitempty"` @@ -126,6 +141,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, "fail/no-wire-options": func(t *testing.T) test { ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) valueBytes, err := json.Marshal(struct { Name string `json:"name,omitempty"` Domain string `json:"domain,omitempty"` @@ -179,6 +195,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) valueBytes, err := json.Marshal(struct { Name string `json:"name,omitempty"` Domain string `json:"domain,omitempty"` @@ -254,7 +271,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ - Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", @@ -280,7 +298,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }{ Claims: jose.Claims{ Issuer: "http://issuer.example.com", - Audience: []string{"test"}, + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", @@ -339,6 +357,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -396,7 +415,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ - Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", @@ -422,7 +442,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }{ Claims: jose.Claims{ Issuer: "http://issuer.example.com", - Audience: []string{"test"}, + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", @@ -481,6 +501,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -542,7 +563,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ - Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", @@ -568,7 +590,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }{ Claims: jose.Claims{ Issuer: "http://issuer.example.com", - Audience: []string{"test"}, + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", @@ -627,6 +649,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -688,7 +711,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ - Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", @@ -714,7 +738,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }{ Claims: jose.Claims{ Issuer: "http://issuer.example.com", - Audience: []string{"test"}, + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", @@ -773,6 +797,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -841,7 +866,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= HTU string `json:"htu,omitempty"` }{ Claims: jose.Claims{ - Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Subject: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, }, Challenge: "token", Handle: "wireapp://%40alice_wire@wire.com", @@ -867,7 +893,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }{ Claims: jose.Claims{ Issuer: "http://issuer.example.com", - Audience: []string{"test"}, + Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"}, Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Challenge: "token", @@ -882,7 +908,6 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= APIVersion: 5, Scope: "wire_client_id", }) - require.NoError(t, err) signed, err := signer.Sign(tokenBytes) require.NoError(t, err) @@ -926,6 +951,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -1010,8 +1036,21 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, } }, + "fail/no-linker": func(t *testing.T) test { + ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + return test{ + ctx: ctx, + expectedErr: &Error{ + Type: "urn:ietf:params:acme:error:serverInternal", + Detail: "The server experienced an internal error", + Status: 500, + Err: errors.New("missing linker"), + }, + } + }, "fail/unmarshal": func(t *testing.T) test { ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ctx: ctx, payload: []byte("?!"), @@ -1040,6 +1079,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, "fail/wire-parse-id": func(t *testing.T) test { ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ctx: ctx, payload: []byte("{}"), @@ -1062,6 +1102,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, "fail/no-wire-options": func(t *testing.T) test { ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{})) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) valueBytes, err := json.Marshal(struct { Name string `json:"name,omitempty"` Domain string `json:"domain,omitempty"` @@ -1094,8 +1135,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, } }, - "fail/keyauth-mismatch": func(t *testing.T) test { - jwk, _ := mustAccountAndKeyAuthorization(t, "token") + "fail/verify": func(t *testing.T) test { + jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token") signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) require.NoError(t, err) signer, err := jose.NewSigner(jose.SigningKey{ @@ -1103,12 +1144,15 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Key: signerJWK, }, new(jose.SignerOptions)) require.NoError(t, err) - srv := mustJWKServer(t, signerJWK.Public()) + anotherSignerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) + require.NoError(t, err) + srv := mustJWKServer(t, anotherSignerJWK.Public()) tokenBytes, err := json.Marshal(struct { jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` KeyAuth string `json:"keyauth"` + ACMEAudience string `json:"acme_aud"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1116,8 +1160,9 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Name: "Alice Smith", - PreferredUsername: "wireapp://%40bob@wire.com", - KeyAuth: "wrong-keyauth", + PreferredUsername: "wireapp://%40alice_wire@wire.com", + KeyAuth: keyAuth, + ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID", }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1162,6 +1207,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -1189,7 +1235,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, "urn:ietf:params:acme:error:rejectedIdentifier", k.Type) assert.Equal(t, "The server will not issue certificates for the identifier", k.Detail) assert.Equal(t, 400, k.Status) - assert.Contains(t, k.Err.Error(), "keyAuthorization does not match") + assert.Equal(t, `error verifying ID token signature: failed to verify signature: failed to verify id token signature`, k.Err.Error()) } } return nil @@ -1197,8 +1243,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, } }, - "fail/verify": func(t *testing.T) test { - jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token") + "fail/keyauth-mismatch": func(t *testing.T) test { + jwk, _ := mustAccountAndKeyAuthorization(t, "token") signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) require.NoError(t, err) signer, err := jose.NewSigner(jose.SigningKey{ @@ -1206,14 +1252,13 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Key: signerJWK, }, new(jose.SignerOptions)) require.NoError(t, err) - anotherSignerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - require.NoError(t, err) - srv := mustJWKServer(t, anotherSignerJWK.Public()) + srv := mustJWKServer(t, signerJWK.Public()) tokenBytes, err := json.Marshal(struct { jose.Claims Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` KeyAuth string `json:"keyauth"` + ACMEAudience string `json:"acme_aud"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1221,8 +1266,9 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)), }, Name: "Alice Smith", - PreferredUsername: "wireapp://%40bob@wire.com", - KeyAuth: keyAuth, + PreferredUsername: "wireapp://%40alice_wire@wire.com", + KeyAuth: "wrong-keyauth", + ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID", }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1267,6 +1313,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -1294,7 +1341,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, "urn:ietf:params:acme:error:rejectedIdentifier", k.Type) assert.Equal(t, "The server will not issue certificates for the identifier", k.Detail) assert.Equal(t, 400, k.Status) - assert.Equal(t, `error verifying ID token signature: failed to verify signature: failed to verify id token signature`, k.Err.Error()) + assert.Contains(t, k.Err.Error(), "keyAuthorization does not match") } } return nil @@ -1317,6 +1364,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` KeyAuth string `json:"keyauth"` + ACMEAudience string `json:"acme_aud"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1326,6 +1374,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name: "Alice Smith", PreferredUsername: "wireapp://%40bob@wire.com", KeyAuth: keyAuth, + ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID", }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1370,6 +1419,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -1420,6 +1470,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` KeyAuth string `json:"keyauth"` + ACMEAudience string `json:"acme_aud"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1429,6 +1480,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", KeyAuth: keyAuth, + ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID", }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1473,6 +1525,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -1520,6 +1573,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` KeyAuth string `json:"keyauth"` + ACMEAudience string `json:"acme_aud"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1529,6 +1583,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", KeyAuth: keyAuth, + ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID", }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1573,6 +1628,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -1624,6 +1680,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` KeyAuth string `json:"keyauth"` + ACMEAudience string `json:"acme_aud"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1633,6 +1690,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", KeyAuth: keyAuth, + ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID", }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1677,6 +1735,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -1728,6 +1787,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` KeyAuth string `json:"keyauth"` + ACMEAudience string `json:"acme_aud"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1737,6 +1797,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", KeyAuth: keyAuth, + ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID", }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1781,6 +1842,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -1838,6 +1900,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name string `json:"name,omitempty"` PreferredUsername string `json:"preferred_username,omitempty"` KeyAuth string `json:"keyauth"` + ACMEAudience string `json:"acme_aud"` }{ Claims: jose.Claims{ Issuer: srv.URL, @@ -1847,6 +1910,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= Name: "Alice Smith", PreferredUsername: "wireapp://%40alice_wire@wire.com", KeyAuth: keyAuth, + ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID", }) require.NoError(t, err) signed, err := signer.Sign(tokenBytes) @@ -1891,6 +1955,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= }, }, })) + ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme")) return test{ ch: &Challenge{ ID: "chID", @@ -1954,6 +2019,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } func Test_parseAndVerifyWireAccessToken(t *testing.T) { + t.Skip("skip until we can retrieve public key from e2e test, so that we can actually verify the token") key := ` -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA= @@ -1969,7 +2035,7 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA= Handle: "wireapp://%40alice_wire@wire.com", } - token := `eyJhbGciOiJFZERTQSIsInR5cCI6ImF0K2p3dCIsImp3ayI6eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6IkIySVlxQldYQW91RHQzV2NDWmdDTTN0OWd1bU1FS01sZ01zR2VuU3UtZkEifX0.eyJpYXQiOjE3MDQ5ODUyMDUsImV4cCI6MTcwNDk4OTE2NSwibmJmIjoxNzA0OTg1MjA1LCJpc3MiOiJodHRwOi8vd2lyZS5jb206MTk5ODMvY2xpZW50cy83YTQxY2Y1Yjc5NjgzNDEwL2FjY2Vzcy10b2tlbiIsInN1YiI6IndpcmVhcHA6Ly9ndVZYNXhlRlMzZVRhdG1YQkl5QTRBITdhNDFjZjViNzk2ODM0MTBAd2lyZS5jb20iLCJhdWQiOiJodHRwOi8vd2lyZS5jb206MTk5ODMvY2xpZW50cy83YTQxY2Y1Yjc5NjgzNDEwL2FjY2Vzcy10b2tlbiIsImp0aSI6IjQyYzQ2ZDRjLWU1MTAtNDE3NS05ZmI1LWQwNTVlMTI1YTQ5ZCIsIm5vbmNlIjoiVUVKeVIyZHFPRWh6WkZKRVlXSkJhVGt5T0RORVlURTJhRXMwZEhJeGNFYyIsImNoYWwiOiJiWFVHTnBVZmNSeDNFaEIzNHhQM3k2MmFRWm9HWlM2aiIsImNuZiI6eyJraWQiOiJvTVdmTkRKUXNJNWNQbFhONVVvQk5uY0t0YzRmMmRxMnZ3Q2pqWHNxdzdRIn0sInByb29mIjoiZXlKaGJHY2lPaUpGWkVSVFFTSXNJblI1Y0NJNkltUndiM0FyYW5kMElpd2lhbmRySWpwN0ltdDBlU0k2SWs5TFVDSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSjRJam9pTVV3eFpVZ3lZVFpCWjFaMmVsUndOVnBoYkV0U1puRTJjRlpRVDNSRmFrazNhRGhVVUhwQ1dVWm5UU0o5ZlEuZXlKcFlYUWlPakUzTURRNU9EVXlNRFVzSW1WNGNDSTZNVGN3TkRrNU1qUXdOU3dpYm1KbUlqb3hOekEwT1RnMU1qQTFMQ0p6ZFdJaU9pSjNhWEpsWVhCd09pOHZaM1ZXV0RWNFpVWlRNMlZVWVhSdFdFSkplVUUwUVNFM1lUUXhZMlkxWWpjNU5qZ3pOREV3UUhkcGNtVXVZMjl0SWl3aWFuUnBJam9pTldVMk5qZzBZMkl0Tm1JME9DMDBOamhrTFdJd09URXRabVl3TkdKbFpEWmxZekpsSWl3aWJtOXVZMlVpT2lKVlJVcDVVakprY1U5RmFIcGFSa3BGV1ZkS1FtRlVhM2xQUkU1RldWUkZNbUZGY3pCa1NFbDRZMFZqSWl3aWFIUnRJam9pVUU5VFZDSXNJbWgwZFNJNkltaDBkSEE2THk5M2FYSmxMbU52YlRveE9UazRNeTlqYkdsbGJuUnpMemRoTkRGalpqVmlOemsyT0RNME1UQXZZV05qWlhOekxYUnZhMlZ1SWl3aVkyaGhiQ0k2SW1KWVZVZE9jRlZtWTFKNE0wVm9Rak0wZUZBemVUWXlZVkZhYjBkYVV6WnFJaXdpYUdGdVpHeGxJam9pZDJseVpXRndjRG92THlVME1HRnNhV05sWDNkcGNtVkFkMmx5WlM1amIyMGlMQ0owWldGdElqb2lkMmx5WlNKOS52bkN1T2JURFRLVFhCYXpyX3Z2X0xyZDBZT1Rac2xteHQtM2xKNWZKSU9iRVRidUVCTGlEaS1JVWZHcFJHTm1Dbm9IZjVocHNsWW5HeFMzSjloUmVDZyIsImNsaWVudF9pZCI6IndpcmVhcHA6Ly9ndVZYNXhlRlMzZVRhdG1YQkl5QTRBITdhNDFjZjViNzk2ODM0MTBAd2lyZS5jb20iLCJhcGlfdmVyc2lvbiI6NSwic2NvcGUiOiJ3aXJlX2NsaWVudF9pZCJ9.uCVYhmvCJm7nM1NxJQKl_XZJcSqm9eFmNmbRJkA5Wpsw70ZF1YANYC9nQ91QgsnuAbaRZMJiJt3P8ZntR2ozDQ` + token := `eyJhbGciOiJFZERTQSIsInR5cCI6ImF0K2p3dCIsImp3ayI6eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6Im8zcWZhQ045a2FzSnZJRlhPdFNMTGhlYW0wTE5jcVF5MHdBMk9PeFRRNW8ifX0.eyJpYXQiOjE3MDU0OTc3MzksImV4cCI6MTcwNTUwMTY5OSwibmJmIjoxNzA1NDk3NzM5LCJpc3MiOiJodHRwOi8vd2lyZS5jb206MTY4MjQvY2xpZW50cy8zN2ZlOThiZDQwZDBkZmUvYWNjZXNzLXRva2VuIiwic3ViIjoid2lyZWFwcDovLzE4NXdIUmtRVHdTOTVGODhaZTQ1SlEhMzdmZTk4YmQ0MGQwZGZlQHdpcmUuY29tIiwiYXVkIjoiaHR0cHM6Ly9zdGVwY2E6NTUwMjMvYWNtZS93aXJlL2NoYWxsZW5nZS9SeEdSWGVoRGxCcHcxNTJQTVUzem0xY2M0cEtGcHVWRi9RWnRFazdQNUVFRXhadHBSYngydjVoYlc3QXB1S2NOSSIsImp0aSI6ImU1MzllODYzLTRkNTgtNGMwMS1iYjk3LTYwODdiNTEzOWIyMCIsIm5vbmNlIjoiUzJKYWVWcExkV28wUkZKaFFrWndXR0ZKY0VoVlFrNUxXVGd4WkhkRFVqQSIsImNoYWwiOiIyaDFPdUdxbTBKUXd6bHVsWGtLSTJEMGZiRDgzRUIxdyIsImNuZiI6eyJraWQiOiJhSEY3MVhYeG0tTWE5Q05zSjNaU1RKTjlYS0ZxOFFmOGh2UTJLN3NLQmQ4In0sInByb29mIjoiZXlKaGJHY2lPaUpGWkVSVFFTSXNJblI1Y0NJNkltUndiM0FyYW5kMElpd2lhbmRySWpwN0ltdDBlU0k2SWs5TFVDSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSjRJam9pWVVsaVMwcFBha0poWXpZeVF6TnRhVmhHVjAxb09ITTJkRXQzUkROaGNHRnVSMHBQZURaVVFYVklRU0o5ZlEuZXlKcFlYUWlPakUzTURVME9UYzNNemtzSW1WNGNDSTZNVGN3TlRVd05Ea3pPU3dpYm1KbUlqb3hOekExTkRrM056TTVMQ0p6ZFdJaU9pSjNhWEpsWVhCd09pOHZNVGcxZDBoU2ExRlVkMU01TlVZNE9GcGxORFZLVVNFek4yWmxPVGhpWkRRd1pEQmtabVZBZDJseVpTNWpiMjBpTENKaGRXUWlPaUpvZEhSd2N6b3ZMM04wWlhCallUbzFOVEF5TXk5aFkyMWxMM2RwY21VdlkyaGhiR3hsYm1kbEwxSjRSMUpZWldoRWJFSndkekUxTWxCTlZUTjZiVEZqWXpSd1MwWndkVlpHTDFGYWRFVnJOMUExUlVWRmVGcDBjRkppZURKMk5XaGlWemRCY0hWTFkwNUpJaXdpYW5ScElqb2lNV1kxTUdRM1lUQXRaamt6WmkwME5XWXdMV0V3TWpBdE1ETm1NREJpTlRreVlUUmtJaXdpYm05dVkyVWlPaUpUTWtwaFpWWndUR1JYYnpCU1JrcG9VV3RhZDFkSFJrcGpSV2hXVVdzMVRGZFVaM2hhU0dSRVZXcEJJaXdpYUhSdElqb2lVRTlUVkNJc0ltaDBkU0k2SW1oMGRIQTZMeTkzYVhKbExtTnZiVG94TmpneU5DOWpiR2xsYm5Sekx6TTNabVU1T0dKa05EQmtNR1JtWlM5aFkyTmxjM010ZEc5clpXNGlMQ0pqYUdGc0lqb2lNbWd4VDNWSGNXMHdTbEYzZW14MWJGaHJTMGt5UkRCbVlrUTRNMFZDTVhjaUxDSm9ZVzVrYkdVaU9pSjNhWEpsWVhCd09pOHZKVFF3WVd4cFkyVmZkMmx5WlVCM2FYSmxMbU52YlNJc0luUmxZVzBpT2lKM2FYSmxJbjAuZlNmQnFuWWlfMTRhZEc5MDAyZ0RJdEgybXNyYW55eXVnR0g5bHpFcmprdmRGbkRPOFRVWWRYUXJKUzdlX3BlU0lzcGxlRUVkaGhzc0gwM3FBWHY2QXciLCJjbGllbnRfaWQiOiJ3aXJlYXBwOi8vMTg1d0hSa1FUd1M5NUY4OFplNDVKUSEzN2ZlOThiZDQwZDBkZmVAd2lyZS5jb20iLCJhcGlfdmVyc2lvbiI6NSwic2NvcGUiOiJ3aXJlX2NsaWVudF9pZCJ9.GKK7ZsJ8EWJjeaHqf8P48H9mluJhxyXUmI0FO3xstda3XDJIK7Z5Ur4hi1OIJB0ZsS5BqRVT2q5whL4KP9hZCA` ch := &Challenge{ Token: "bXUGNpUfcRx3EhB34xP3y62aQZoGZS6j", }