Merge pull request #1681 from smallstep/herman/fix-wire-extensions

Improve access and dpop token validation
pull/1670/head v0.25.3-rc.1
Herman Slatman 5 months ago committed by GitHub
commit 51d1270541
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

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

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

@ -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 {
@ -364,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)
@ -381,16 +383,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 +396,31 @@ 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"`
ACMEAudience string `json:"acme_aud,omitempty"`
}
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))
}
// 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"))
@ -459,12 +469,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
@ -480,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 {
@ -507,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(),
@ -555,6 +573,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"`
@ -562,6 +581,14 @@ type wireAccessToken struct {
Scope string `json:"scope"`
}
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
type wireVerifyParams struct {
@ -570,6 +597,7 @@ type wireVerifyParams struct {
dpopKey crypto.PublicKey
dpopKeyID string
issuer string
audience string
wireID wire.ID
chToken string
t time.Time
@ -581,6 +609,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,24 +634,72 @@ func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireD
if err := accessToken.ValidateWithLeeway(jose.Expected{
Time: v.t,
Issuer: v.issuer,
}, 360*time.Second); err != nil {
Audience: jose.Audience{v.audience},
}, 1*time.Minute); err != nil {
return nil, nil, fmt.Errorf("failed validation: %w", err)
}
if accessToken.Cnf.Kid != v.dpopKeyID {
if accessToken.Challenge == "" {
return nil, nil, errors.New("access token challenge must not be empty")
}
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 {
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" {
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,
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)) {
return nil, nil, fmt.Errorf("'exp' %s is too far into the future", wireDpop.Expiry.Time().String())
}
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)
}
// 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 +709,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 +717,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)
}

@ -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,
@ -891,6 +891,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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,
@ -899,6 +901,8 @@ 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)
@ -907,10 +911,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 {
@ -931,14 +933,11 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Provider: &wireprovisioner.Provider{
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,
},
TransformTemplate: "",
@ -948,6 +947,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -978,7 +978,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
},
},
@ -1002,17 +1002,21 @@ 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"`
Handle string `json:"handle,omitempty"`
Nonce string `json:"nonce,omitempty"`
HTU string `json:"htu,omitempty"`
}{
Claims: jose.Claims{
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",
Nonce: "nonce",
HTU: "http://issuer.example.com",
})
require.NoError(t, err)
dpop, err := dpopSigner.Sign(dpopBytes)
@ -1022,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"`
@ -1032,10 +1037,11 @@ 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",
Nonce: "nonce",
Cnf: struct {
Kid string `json:"kid,omitempty"`
}{
@ -1074,23 +1080,22 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
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,
},
TransformTemplate: "",
},
DPOP: &wireprovisioner.DPOPOptions{
Target: "http://issuer.example.com",
SigningKey: signerPEMBytes,
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",

@ -3,6 +3,7 @@ package acme
import (
"context"
"crypto"
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"encoding/pem"
@ -45,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("?!"),
@ -69,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("{}"),
@ -91,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"`
@ -125,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"`
@ -162,7 +179,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Wire: &wireprovisioner.Options{
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: "http://issuerexample.com",
IssuerURL: "http://issuer.example.com",
Algorithms: []string{"ES256"},
},
Config: &wireprovisioner.Config{
ClientID: "test",
@ -177,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"`
@ -248,12 +267,17 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"},
},
Challenge: "token",
Handle: "wireapp://%40alice_wire@wire.com",
Nonce: "nonce",
HTU: "http://issuer.example.com",
})
require.NoError(t, err)
dpop, err := dpopSigner.Sign(dpopBytes)
@ -263,6 +287,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"`
@ -273,10 +298,11 @@ 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",
Nonce: "nonce",
Cnf: struct {
Kid string `json:"kid,omitempty"`
}{
@ -315,7 +341,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Wire: &wireprovisioner.Options{
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: "http://issuerexample.com",
IssuerURL: "http://issuer.example.com",
Algorithms: []string{"ES256"},
},
Config: &wireprovisioner.Config{
ClientID: "test",
@ -325,10 +352,12 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
TransformTemplate: "",
},
DPOP: &wireprovisioner.DPOPOptions{
Target: "http://issuer.example.com",
SigningKey: signerPEMBytes,
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -382,12 +411,17 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"},
},
Challenge: "token",
Handle: "wireapp://%40alice_wire@wire.com",
Nonce: "nonce",
HTU: "http://issuer.example.com",
})
require.NoError(t, err)
dpop, err := dpopSigner.Sign(dpopBytes)
@ -397,6 +431,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"`
@ -407,10 +442,11 @@ 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",
Nonce: "nonce",
Cnf: struct {
Kid string `json:"kid,omitempty"`
}{
@ -449,24 +485,23 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Wire: &wireprovisioner.Options{
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: "http://issuerexample.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,
},
TransformTemplate: "",
},
DPOP: &wireprovisioner.DPOPOptions{
Target: "http://issuer.example.com",
SigningKey: signerPEMBytes,
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -524,12 +559,17 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"},
},
Challenge: "token",
Handle: "wireapp://%40alice_wire@wire.com",
Nonce: "nonce",
HTU: "http://issuer.example.com",
})
require.NoError(t, err)
dpop, err := dpopSigner.Sign(dpopBytes)
@ -539,6 +579,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"`
@ -549,10 +590,11 @@ 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",
Nonce: "nonce",
Cnf: struct {
Kid string `json:"kid,omitempty"`
}{
@ -591,24 +633,23 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Wire: &wireprovisioner.Options{
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: "http://issuerexample.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,
},
TransformTemplate: "",
},
DPOP: &wireprovisioner.DPOPOptions{
Target: "http://issuer.example.com",
SigningKey: signerPEMBytes,
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -666,12 +707,17 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"},
},
Challenge: "token",
Handle: "wireapp://%40alice_wire@wire.com",
Nonce: "nonce",
HTU: "http://issuer.example.com",
})
require.NoError(t, err)
dpop, err := dpopSigner.Sign(dpopBytes)
@ -681,6 +727,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"`
@ -691,10 +738,11 @@ 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",
Nonce: "nonce",
Cnf: struct {
Kid string `json:"kid,omitempty"`
}{
@ -733,24 +781,23 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Wire: &wireprovisioner.Options{
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: "http://issuerexample.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,
},
TransformTemplate: "",
},
DPOP: &wireprovisioner.DPOPOptions{
Target: "http://issuer.example.com",
SigningKey: signerPEMBytes,
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -815,12 +862,17 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"},
},
Challenge: "token",
Handle: "wireapp://%40alice_wire@wire.com",
Nonce: "nonce",
HTU: "http://issuer.example.com",
})
require.NoError(t, err)
dpop, err := dpopSigner.Sign(dpopBytes)
@ -830,6 +882,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"`
@ -840,10 +893,11 @@ 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",
Nonce: "nonce",
Cnf: struct {
Kid string `json:"kid,omitempty"`
}{
@ -854,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)
@ -882,24 +935,23 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Wire: &wireprovisioner.Options{
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: "http://issuerexample.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,
},
TransformTemplate: "",
},
DPOP: &wireprovisioner.DPOPOptions{
Target: "http://issuer.example.com",
SigningKey: signerPEMBytes,
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -984,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("?!"),
@ -1014,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("{}"),
@ -1036,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"`
@ -1068,14 +1135,44 @@ 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{
Algorithm: jose.SignatureAlgorithm(signerJWK.Algorithm),
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())
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,
Audience: []string{"test"},
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
},
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)
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 {
@ -1094,7 +1191,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",
@ -1108,6 +1207,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -1118,6 +1218,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Status: StatusPending,
Value: string(valueBytes),
},
srv: srv,
payload: payload,
ctx: ctx,
jwk: jwk,
@ -1134,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; expected`)
assert.Equal(t, `error verifying ID token signature: failed to verify signature: failed to verify id token signature`, k.Err.Error())
}
}
return nil
@ -1142,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{
@ -1151,13 +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,
@ -1166,6 +1267,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
Name: "Alice Smith",
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)
@ -1174,10 +1277,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 {
@ -1198,14 +1299,11 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Provider: &wireprovisioner.Provider{
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,
},
TransformTemplate: "",
@ -1215,6 +1313,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -1242,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
@ -1264,6 +1363,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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,
@ -1272,6 +1373,8 @@ 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)
@ -1280,10 +1383,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 {
@ -1304,14 +1405,11 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Provider: &wireprovisioner.Provider{
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,
},
TransformTemplate: "",
@ -1321,6 +1419,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -1348,7 +1447,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
@ -1370,6 +1469,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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,
@ -1378,6 +1479,8 @@ 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)
@ -1386,10 +1489,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 {
@ -1410,14 +1511,11 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Provider: &wireprovisioner.Provider{
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,
},
TransformTemplate: "",
@ -1427,6 +1525,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -1473,6 +1572,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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,
@ -1481,6 +1582,8 @@ 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)
@ -1489,10 +1592,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 {
@ -1513,14 +1614,11 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Provider: &wireprovisioner.Provider{
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,
},
TransformTemplate: "",
@ -1530,6 +1628,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -1580,6 +1679,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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,
@ -1588,6 +1689,8 @@ 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)
@ -1596,10 +1699,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 {
@ -1620,14 +1721,11 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Provider: &wireprovisioner.Provider{
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,
},
TransformTemplate: "",
@ -1637,6 +1735,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -1687,6 +1786,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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,
@ -1695,6 +1796,8 @@ 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)
@ -1703,10 +1806,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 {
@ -1727,14 +1828,11 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Provider: &wireprovisioner.Provider{
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,
},
TransformTemplate: "",
@ -1744,6 +1842,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -1774,7 +1873,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")
},
},
@ -1800,6 +1899,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
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,
@ -1808,6 +1909,8 @@ 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)
@ -1816,10 +1919,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 {
@ -1840,14 +1941,11 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
Provider: &wireprovisioner.Provider{
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,
},
TransformTemplate: "",
@ -1857,6 +1955,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
@ -1887,7 +1986,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
},
},
@ -1920,19 +2019,23 @@ 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=
-----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",
Handle: "wireapp://%40alice_wire@wire.com",
}
token := `eyJhbGciOiJFZERTQSIsInR5cCI6ImF0K2p3dCIsImp3ayI6eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6IkIySVlxQldYQW91RHQzV2NDWmdDTTN0OWd1bU1FS01sZ01zR2VuU3UtZkEifX0.eyJpYXQiOjE3MDQ5ODUyMDUsImV4cCI6MTcwNDk4OTE2NSwibmJmIjoxNzA0OTg1MjA1LCJpc3MiOiJodHRwOi8vd2lyZS5jb206MTk5ODMvY2xpZW50cy83YTQxY2Y1Yjc5NjgzNDEwL2FjY2Vzcy10b2tlbiIsInN1YiI6IndpcmVhcHA6Ly9ndVZYNXhlRlMzZVRhdG1YQkl5QTRBITdhNDFjZjViNzk2ODM0MTBAd2lyZS5jb20iLCJhdWQiOiJodHRwOi8vd2lyZS5jb206MTk5ODMvY2xpZW50cy83YTQxY2Y1Yjc5NjgzNDEwL2FjY2Vzcy10b2tlbiIsImp0aSI6IjQyYzQ2ZDRjLWU1MTAtNDE3NS05ZmI1LWQwNTVlMTI1YTQ5ZCIsIm5vbmNlIjoiVUVKeVIyZHFPRWh6WkZKRVlXSkJhVGt5T0RORVlURTJhRXMwZEhJeGNFYyIsImNoYWwiOiJiWFVHTnBVZmNSeDNFaEIzNHhQM3k2MmFRWm9HWlM2aiIsImNuZiI6eyJraWQiOiJvTVdmTkRKUXNJNWNQbFhONVVvQk5uY0t0YzRmMmRxMnZ3Q2pqWHNxdzdRIn0sInByb29mIjoiZXlKaGJHY2lPaUpGWkVSVFFTSXNJblI1Y0NJNkltUndiM0FyYW5kMElpd2lhbmRySWpwN0ltdDBlU0k2SWs5TFVDSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSjRJam9pTVV3eFpVZ3lZVFpCWjFaMmVsUndOVnBoYkV0U1puRTJjRlpRVDNSRmFrazNhRGhVVUhwQ1dVWm5UU0o5ZlEuZXlKcFlYUWlPakUzTURRNU9EVXlNRFVzSW1WNGNDSTZNVGN3TkRrNU1qUXdOU3dpYm1KbUlqb3hOekEwT1RnMU1qQTFMQ0p6ZFdJaU9pSjNhWEpsWVhCd09pOHZaM1ZXV0RWNFpVWlRNMlZVWVhSdFdFSkplVUUwUVNFM1lUUXhZMlkxWWpjNU5qZ3pOREV3UUhkcGNtVXVZMjl0SWl3aWFuUnBJam9pTldVMk5qZzBZMkl0Tm1JME9DMDBOamhrTFdJd09URXRabVl3TkdKbFpEWmxZekpsSWl3aWJtOXVZMlVpT2lKVlJVcDVVakprY1U5RmFIcGFSa3BGV1ZkS1FtRlVhM2xQUkU1RldWUkZNbUZGY3pCa1NFbDRZMFZqSWl3aWFIUnRJam9pVUU5VFZDSXNJbWgwZFNJNkltaDBkSEE2THk5M2FYSmxMbU52YlRveE9UazRNeTlqYkdsbGJuUnpMemRoTkRGalpqVmlOemsyT0RNME1UQXZZV05qWlhOekxYUnZhMlZ1SWl3aVkyaGhiQ0k2SW1KWVZVZE9jRlZtWTFKNE0wVm9Rak0wZUZBemVUWXlZVkZhYjBkYVV6WnFJaXdpYUdGdVpHeGxJam9pZDJseVpXRndjRG92THlVME1HRnNhV05sWDNkcGNtVkFkMmx5WlM1amIyMGlMQ0owWldGdElqb2lkMmx5WlNKOS52bkN1T2JURFRLVFhCYXpyX3Z2X0xyZDBZT1Rac2xteHQtM2xKNWZKSU9iRVRidUVCTGlEaS1JVWZHcFJHTm1Dbm9IZjVocHNsWW5HeFMzSjloUmVDZyIsImNsaWVudF9pZCI6IndpcmVhcHA6Ly9ndVZYNXhlRlMzZVRhdG1YQkl5QTRBITdhNDFjZjViNzk2ODM0MTBAd2lyZS5jb20iLCJhcGlfdmVyc2lvbiI6NSwic2NvcGUiOiJ3aXJlX2NsaWVudF9pZCJ9.uCVYhmvCJm7nM1NxJQKl_XZJcSqm9eFmNmbRJkA5Wpsw70ZF1YANYC9nQ91QgsnuAbaRZMJiJt3P8ZntR2ozDQ`
token := `eyJhbGciOiJFZERTQSIsInR5cCI6ImF0K2p3dCIsImp3ayI6eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6Im8zcWZhQ045a2FzSnZJRlhPdFNMTGhlYW0wTE5jcVF5MHdBMk9PeFRRNW8ifX0.eyJpYXQiOjE3MDU0OTc3MzksImV4cCI6MTcwNTUwMTY5OSwibmJmIjoxNzA1NDk3NzM5LCJpc3MiOiJodHRwOi8vd2lyZS5jb206MTY4MjQvY2xpZW50cy8zN2ZlOThiZDQwZDBkZmUvYWNjZXNzLXRva2VuIiwic3ViIjoid2lyZWFwcDovLzE4NXdIUmtRVHdTOTVGODhaZTQ1SlEhMzdmZTk4YmQ0MGQwZGZlQHdpcmUuY29tIiwiYXVkIjoiaHR0cHM6Ly9zdGVwY2E6NTUwMjMvYWNtZS93aXJlL2NoYWxsZW5nZS9SeEdSWGVoRGxCcHcxNTJQTVUzem0xY2M0cEtGcHVWRi9RWnRFazdQNUVFRXhadHBSYngydjVoYlc3QXB1S2NOSSIsImp0aSI6ImU1MzllODYzLTRkNTgtNGMwMS1iYjk3LTYwODdiNTEzOWIyMCIsIm5vbmNlIjoiUzJKYWVWcExkV28wUkZKaFFrWndXR0ZKY0VoVlFrNUxXVGd4WkhkRFVqQSIsImNoYWwiOiIyaDFPdUdxbTBKUXd6bHVsWGtLSTJEMGZiRDgzRUIxdyIsImNuZiI6eyJraWQiOiJhSEY3MVhYeG0tTWE5Q05zSjNaU1RKTjlYS0ZxOFFmOGh2UTJLN3NLQmQ4In0sInByb29mIjoiZXlKaGJHY2lPaUpGWkVSVFFTSXNJblI1Y0NJNkltUndiM0FyYW5kMElpd2lhbmRySWpwN0ltdDBlU0k2SWs5TFVDSXNJbU55ZGlJNklrVmtNalUxTVRraUxDSjRJam9pWVVsaVMwcFBha0poWXpZeVF6TnRhVmhHVjAxb09ITTJkRXQzUkROaGNHRnVSMHBQZURaVVFYVklRU0o5ZlEuZXlKcFlYUWlPakUzTURVME9UYzNNemtzSW1WNGNDSTZNVGN3TlRVd05Ea3pPU3dpYm1KbUlqb3hOekExTkRrM056TTVMQ0p6ZFdJaU9pSjNhWEpsWVhCd09pOHZNVGcxZDBoU2ExRlVkMU01TlVZNE9GcGxORFZLVVNFek4yWmxPVGhpWkRRd1pEQmtabVZBZDJseVpTNWpiMjBpTENKaGRXUWlPaUpvZEhSd2N6b3ZMM04wWlhCallUbzFOVEF5TXk5aFkyMWxMM2RwY21VdlkyaGhiR3hsYm1kbEwxSjRSMUpZWldoRWJFSndkekUxTWxCTlZUTjZiVEZqWXpSd1MwWndkVlpHTDFGYWRFVnJOMUExUlVWRmVGcDBjRkppZURKMk5XaGlWemRCY0hWTFkwNUpJaXdpYW5ScElqb2lNV1kxTUdRM1lUQXRaamt6WmkwME5XWXdMV0V3TWpBdE1ETm1NREJpTlRreVlUUmtJaXdpYm05dVkyVWlPaUpUTWtwaFpWWndUR1JYYnpCU1JrcG9VV3RhZDFkSFJrcGpSV2hXVVdzMVRGZFVaM2hhU0dSRVZXcEJJaXdpYUhSdElqb2lVRTlUVkNJc0ltaDBkU0k2SW1oMGRIQTZMeTkzYVhKbExtTnZiVG94TmpneU5DOWpiR2xsYm5Sekx6TTNabVU1T0dKa05EQmtNR1JtWlM5aFkyTmxjM010ZEc5clpXNGlMQ0pqYUdGc0lqb2lNbWd4VDNWSGNXMHdTbEYzZW14MWJGaHJTMGt5UkRCbVlrUTRNMFZDTVhjaUxDSm9ZVzVrYkdVaU9pSjNhWEpsWVhCd09pOHZKVFF3WVd4cFkyVmZkMmx5WlVCM2FYSmxMbU52YlNJc0luUmxZVzBpT2lKM2FYSmxJbjAuZlNmQnFuWWlfMTRhZEc5MDAyZ0RJdEgybXNyYW55eXVnR0g5bHpFcmprdmRGbkRPOFRVWWRYUXJKUzdlX3BlU0lzcGxlRUVkaGhzc0gwM3FBWHY2QXciLCJjbGllbnRfaWQiOiJ3aXJlYXBwOi8vMTg1d0hSa1FUd1M5NUY4OFplNDVKUSEzN2ZlOThiZDQwZDBkZmVAd2lyZS5jb20iLCJhcGlfdmVyc2lvbiI6NSwic2NvcGUiOiJ3aXJlX2NsaWVudF9pZCJ9.GKK7ZsJ8EWJjeaHqf8P48H9mluJhxyXUmI0FO3xstda3XDJIK7Z5Ur4hi1OIJB0ZsS5BqRVT2q5whL4KP9hZCA`
ch := &Challenge{
Token: "bXUGNpUfcRx3EhB34xP3y62aQZoGZS6j",
}
@ -1951,7 +2054,7 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA=
at, dpop, err := parseAndVerifyWireAccessToken(wireVerifyParams{
token: token,
tokenKey: publicKey,
tokenKey: pk,
dpopKey: accountJWK.Public(),
dpopKeyID: accountJWK.KeyID,
issuer: issuer,
@ -1959,6 +2062,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)
@ -1995,15 +2099,17 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: "http://dex:15818/dex",
Algorithms: []string{"ES256"},
},
Config: &wireprovisioner.Config{
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),
@ -2029,7 +2135,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))
}
@ -2044,9 +2150,11 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: "https://issuer.example.com",
Algorithms: []string{"ES256"},
},
Config: &wireprovisioner.Config{
ClientID: "unit test",
SignatureAlgorithms: []string{"ES256"},
Now: time.Now,
},
TransformTemplate: transformTemplate,
@ -2090,18 +2198,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))
}

@ -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 err != nil {
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)
}
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 err != nil {
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)
}
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
}

@ -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 {
@ -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)
@ -284,7 +284,7 @@ func TestDB_GetOidcToken(t *testing.T) {
orderID: "orderID",
expected: map[string]any{
"name": "Alice Smith",
"handle": "@alice.smith",
"preferred_username": "@alice.smith",
},
}
},
@ -336,7 +336,7 @@ func TestDB_CreateOidcToken(t *testing.T) {
orderID: "orderID",
oidc: map[string]any{
"name": "Alice Smith",
"handle": "@alice.smith",
"preferred_username": "@alice.smith",
},
expectedErr: errors.New("failed saving oidc token: error saving acme oidc: fail"),
}
@ -352,7 +352,7 @@ func TestDB_CreateOidcToken(t *testing.T) {
orderID: "orderID",
oidc: map[string]any{
"name": "Alice Smith",
"handle": "@alice.smith",
"preferred_username": "@alice.smith",
},
}
},

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

@ -26,6 +26,8 @@ type Provider struct {
type Config struct {
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:"-"`
@ -66,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 {

@ -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",
},
},
{
@ -101,7 +99,7 @@ func TestOIDCOptions_Transform(t *testing.T) {
},
want: map[string]any{
"name": "Example",
"handle": "name-1",
"preferred_username": "name-1",
"usernames": []string{"name-1", "name-2", "name-3"},
},
},

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

Loading…
Cancel
Save