Skip to content

Commit a9f40ba

Browse files
authored
Merge pull request #1960 from smallstep/herman/wire-db-interface
Change `Wire` DB operations into using a runtime type assertion
2 parents 9fd4dff + 25f674c commit a9f40ba

File tree

5 files changed

+478
-183
lines changed

5 files changed

+478
-183
lines changed

acme/challenge.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,17 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey,
117117
case DEVICEATTEST01:
118118
return deviceAttest01Validate(ctx, ch, db, jwk, payload)
119119
case WIREOIDC01:
120-
return wireOIDC01Validate(ctx, ch, db, jwk, payload)
120+
wireDB, ok := db.(WireDB)
121+
if !ok {
122+
return NewErrorISE("db %T is not a WireDB", db)
123+
}
124+
return wireOIDC01Validate(ctx, ch, wireDB, jwk, payload)
121125
case WIREDPOP01:
122-
return wireDPOP01Validate(ctx, ch, db, jwk, payload)
126+
wireDB, ok := db.(WireDB)
127+
if !ok {
128+
return NewErrorISE("db %T is not a WireDB", db)
129+
}
130+
return wireDPOP01Validate(ctx, ch, wireDB, jwk, payload)
123131
default:
124132
return NewErrorISE("unexpected challenge type %q", ch.Type)
125133
}
@@ -392,7 +400,7 @@ type wireOidcPayload struct {
392400
IDToken string `json:"id_token"`
393401
}
394402

395-
func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, payload []byte) error {
403+
func wireOIDC01Validate(ctx context.Context, ch *Challenge, db WireDB, jwk *jose.JSONWebKey, payload []byte) error {
396404
prov, ok := ProvisionerFromContext(ctx)
397405
if !ok {
398406
return NewErrorISE("missing provisioner")
@@ -522,7 +530,7 @@ type wireDpopPayload struct {
522530
AccessToken string `json:"access_token"`
523531
}
524532

525-
func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *jose.JSONWebKey, payload []byte) error {
533+
func wireDPOP01Validate(ctx context.Context, ch *Challenge, db WireDB, accountJWK *jose.JSONWebKey, payload []byte) error {
526534
prov, ok := ProvisionerFromContext(ctx)
527535
if !ok {
528536
return NewErrorISE("missing provisioner")

acme/challenge_test.go

Lines changed: 249 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -962,14 +962,16 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
962962
payload: payload,
963963
ctx: ctx,
964964
jwk: jwk,
965-
db: &MockDB{
966-
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
967-
assert.Equal(t, "chID", updch.ID)
968-
assert.Equal(t, "token", updch.Token)
969-
assert.Equal(t, StatusValid, updch.Status)
970-
assert.Equal(t, ChallengeType("wire-oidc-01"), updch.Type)
971-
assert.Equal(t, string(valueBytes), updch.Value)
972-
return nil
965+
db: &MockWireDB{
966+
MockDB: MockDB{
967+
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
968+
assert.Equal(t, "chID", updch.ID)
969+
assert.Equal(t, "token", updch.Token)
970+
assert.Equal(t, StatusValid, updch.Status)
971+
assert.Equal(t, ChallengeType("wire-oidc-01"), updch.Type)
972+
assert.Equal(t, string(valueBytes), updch.Value)
973+
return nil
974+
},
973975
},
974976
MockGetAllOrdersByAccountID: func(ctx context.Context, accountID string) ([]string, error) {
975977
assert.Equal(t, "accID", accountID)
@@ -984,6 +986,100 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
984986
},
985987
}
986988
},
989+
"fail/wire-oidc-01-no-wire-db": func(t *testing.T) test {
990+
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
991+
signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
992+
require.NoError(t, err)
993+
signer, err := jose.NewSigner(jose.SigningKey{
994+
Algorithm: jose.SignatureAlgorithm(signerJWK.Algorithm),
995+
Key: signerJWK,
996+
}, new(jose.SignerOptions))
997+
require.NoError(t, err)
998+
srv := mustJWKServer(t, signerJWK.Public())
999+
tokenBytes, err := json.Marshal(struct {
1000+
jose.Claims
1001+
Name string `json:"name,omitempty"`
1002+
PreferredUsername string `json:"preferred_username,omitempty"`
1003+
KeyAuth string `json:"keyauth"`
1004+
ACMEAudience string `json:"acme_aud"`
1005+
}{
1006+
Claims: jose.Claims{
1007+
Issuer: srv.URL,
1008+
Audience: []string{"test"},
1009+
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
1010+
},
1011+
Name: "Alice Smith",
1012+
PreferredUsername: "wireapp://%[email protected]",
1013+
KeyAuth: keyAuth,
1014+
ACMEAudience: "https://ca.example.com/acme/wire/challenge/azID/chID",
1015+
})
1016+
require.NoError(t, err)
1017+
signed, err := signer.Sign(tokenBytes)
1018+
require.NoError(t, err)
1019+
idToken, err := signed.CompactSerialize()
1020+
require.NoError(t, err)
1021+
payload, err := json.Marshal(struct {
1022+
IDToken string `json:"id_token"`
1023+
}{
1024+
IDToken: idToken,
1025+
})
1026+
require.NoError(t, err)
1027+
valueBytes, err := json.Marshal(struct {
1028+
Name string `json:"name,omitempty"`
1029+
Domain string `json:"domain,omitempty"`
1030+
ClientID string `json:"client-id,omitempty"`
1031+
Handle string `json:"handle,omitempty"`
1032+
}{
1033+
Name: "Alice Smith",
1034+
Domain: "wire.com",
1035+
ClientID: "wireapp://[email protected]",
1036+
Handle: "wireapp://%[email protected]",
1037+
})
1038+
require.NoError(t, err)
1039+
ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{
1040+
Wire: &wireprovisioner.Options{
1041+
OIDC: &wireprovisioner.OIDCOptions{
1042+
Provider: &wireprovisioner.Provider{
1043+
IssuerURL: srv.URL,
1044+
JWKSURL: srv.URL + "/keys",
1045+
Algorithms: []string{"ES256"},
1046+
},
1047+
Config: &wireprovisioner.Config{
1048+
ClientID: "test",
1049+
SignatureAlgorithms: []string{"ES256"},
1050+
Now: time.Now,
1051+
},
1052+
TransformTemplate: "",
1053+
},
1054+
DPOP: &wireprovisioner.DPOPOptions{
1055+
SigningKey: []byte(fakeKey),
1056+
},
1057+
},
1058+
}))
1059+
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
1060+
return test{
1061+
ch: &Challenge{
1062+
ID: "chID",
1063+
AuthorizationID: "azID",
1064+
AccountID: "accID",
1065+
Token: "token",
1066+
Type: "wire-oidc-01",
1067+
Status: StatusPending,
1068+
Value: string(valueBytes),
1069+
},
1070+
srv: srv,
1071+
payload: payload,
1072+
ctx: ctx,
1073+
jwk: jwk,
1074+
db: &MockDB{},
1075+
err: &Error{
1076+
Type: "urn:ietf:params:acme:error:serverInternal",
1077+
Detail: "The server experienced an internal error",
1078+
Status: 500,
1079+
Err: errors.New("db *acme.MockDB is not a WireDB"),
1080+
},
1081+
}
1082+
},
9871083
"ok/wire-dpop-01": func(t *testing.T) test {
9881084
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
9891085
_ = keyAuth // TODO(hs): keyAuth (not) required for DPoP? Or needs to be added to validation?
@@ -1111,14 +1207,16 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
11111207
payload: payload,
11121208
ctx: ctx,
11131209
jwk: jwk,
1114-
db: &MockDB{
1115-
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
1116-
assert.Equal(t, "chID", updch.ID)
1117-
assert.Equal(t, "token", updch.Token)
1118-
assert.Equal(t, StatusValid, updch.Status)
1119-
assert.Equal(t, ChallengeType("wire-dpop-01"), updch.Type)
1120-
assert.Equal(t, string(valueBytes), updch.Value)
1121-
return nil
1210+
db: &MockWireDB{
1211+
MockDB: MockDB{
1212+
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
1213+
assert.Equal(t, "chID", updch.ID)
1214+
assert.Equal(t, "token", updch.Token)
1215+
assert.Equal(t, StatusValid, updch.Status)
1216+
assert.Equal(t, ChallengeType("wire-dpop-01"), updch.Type)
1217+
assert.Equal(t, string(valueBytes), updch.Value)
1218+
return nil
1219+
},
11221220
},
11231221
MockGetAllOrdersByAccountID: func(ctx context.Context, accountID string) ([]string, error) {
11241222
assert.Equal(t, "accID", accountID)
@@ -1134,6 +1232,141 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
11341232
},
11351233
}
11361234
},
1235+
"fail/wire-dpop-01-no-wire-db": func(t *testing.T) test {
1236+
jwk, _ := mustAccountAndKeyAuthorization(t, "token")
1237+
dpopSigner, err := jose.NewSigner(jose.SigningKey{
1238+
Algorithm: jose.SignatureAlgorithm(jwk.Algorithm),
1239+
Key: jwk,
1240+
}, new(jose.SignerOptions))
1241+
require.NoError(t, err)
1242+
signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
1243+
require.NoError(t, err)
1244+
signer, err := jose.NewSigner(jose.SigningKey{
1245+
Algorithm: jose.SignatureAlgorithm(signerJWK.Algorithm),
1246+
Key: signerJWK,
1247+
}, new(jose.SignerOptions))
1248+
require.NoError(t, err)
1249+
signerPEMBlock, err := pemutil.Serialize(signerJWK.Public().Key)
1250+
require.NoError(t, err)
1251+
signerPEMBytes := pem.EncodeToMemory(signerPEMBlock)
1252+
dpopBytes, err := json.Marshal(struct {
1253+
jose.Claims
1254+
Challenge string `json:"chal,omitempty"`
1255+
Handle string `json:"handle,omitempty"`
1256+
Nonce string `json:"nonce,omitempty"`
1257+
HTU string `json:"htu,omitempty"`
1258+
Name string `json:"name,omitempty"`
1259+
}{
1260+
Claims: jose.Claims{
1261+
Subject: "wireapp://[email protected]",
1262+
Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"},
1263+
},
1264+
Challenge: "token",
1265+
Handle: "wireapp://%[email protected]",
1266+
Nonce: "nonce",
1267+
HTU: "http://issuer.example.com",
1268+
Name: "Alice Smith",
1269+
})
1270+
require.NoError(t, err)
1271+
dpop, err := dpopSigner.Sign(dpopBytes)
1272+
require.NoError(t, err)
1273+
proof, err := dpop.CompactSerialize()
1274+
require.NoError(t, err)
1275+
tokenBytes, err := json.Marshal(struct {
1276+
jose.Claims
1277+
Challenge string `json:"chal,omitempty"`
1278+
Nonce string `json:"nonce,omitempty"`
1279+
Cnf struct {
1280+
Kid string `json:"kid,omitempty"`
1281+
} `json:"cnf"`
1282+
Proof string `json:"proof,omitempty"`
1283+
ClientID string `json:"client_id"`
1284+
APIVersion int `json:"api_version"`
1285+
Scope string `json:"scope"`
1286+
}{
1287+
Claims: jose.Claims{
1288+
Issuer: "http://issuer.example.com",
1289+
Audience: jose.Audience{"https://ca.example.com/acme/wire/challenge/azID/chID"},
1290+
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
1291+
},
1292+
Challenge: "token",
1293+
Nonce: "nonce",
1294+
Cnf: struct {
1295+
Kid string `json:"kid,omitempty"`
1296+
}{
1297+
Kid: jwk.KeyID,
1298+
},
1299+
Proof: proof,
1300+
ClientID: "wireapp://[email protected]",
1301+
APIVersion: 5,
1302+
Scope: "wire_client_id",
1303+
})
1304+
require.NoError(t, err)
1305+
signed, err := signer.Sign(tokenBytes)
1306+
require.NoError(t, err)
1307+
accessToken, err := signed.CompactSerialize()
1308+
require.NoError(t, err)
1309+
payload, err := json.Marshal(struct {
1310+
AccessToken string `json:"access_token"`
1311+
}{
1312+
AccessToken: accessToken,
1313+
})
1314+
require.NoError(t, err)
1315+
valueBytes, err := json.Marshal(struct {
1316+
Name string `json:"name,omitempty"`
1317+
Domain string `json:"domain,omitempty"`
1318+
ClientID string `json:"client-id,omitempty"`
1319+
Handle string `json:"handle,omitempty"`
1320+
}{
1321+
Name: "Alice Smith",
1322+
Domain: "wire.com",
1323+
ClientID: "wireapp://[email protected]",
1324+
Handle: "wireapp://%[email protected]",
1325+
})
1326+
require.NoError(t, err)
1327+
ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{
1328+
Wire: &wireprovisioner.Options{
1329+
OIDC: &wireprovisioner.OIDCOptions{
1330+
Provider: &wireprovisioner.Provider{
1331+
IssuerURL: "http://issuerexample.com",
1332+
Algorithms: []string{"ES256"},
1333+
},
1334+
Config: &wireprovisioner.Config{
1335+
ClientID: "test",
1336+
SignatureAlgorithms: []string{"ES256"},
1337+
Now: time.Now,
1338+
},
1339+
TransformTemplate: "",
1340+
},
1341+
DPOP: &wireprovisioner.DPOPOptions{
1342+
Target: "http://issuer.example.com",
1343+
SigningKey: signerPEMBytes,
1344+
},
1345+
},
1346+
}))
1347+
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
1348+
return test{
1349+
ch: &Challenge{
1350+
ID: "chID",
1351+
AuthorizationID: "azID",
1352+
AccountID: "accID",
1353+
Token: "token",
1354+
Type: "wire-dpop-01",
1355+
Status: StatusPending,
1356+
Value: string(valueBytes),
1357+
},
1358+
payload: payload,
1359+
ctx: ctx,
1360+
jwk: jwk,
1361+
db: &MockDB{},
1362+
err: &Error{
1363+
Type: "urn:ietf:params:acme:error:serverInternal",
1364+
Detail: "The server experienced an internal error",
1365+
Status: 500,
1366+
Err: errors.New("db *acme.MockDB is not a WireDB"),
1367+
},
1368+
}
1369+
},
11371370
}
11381371
for name, run := range tests {
11391372
t.Run(name, func(t *testing.T) {

0 commit comments

Comments
 (0)