Skip to content

Commit 502334f

Browse files
authored
Merge pull request #1689 from smallstep/beltram/wire-acme-extensions
Use two separate Wire identifier types
2 parents 7e6356e + a38132a commit 502334f

File tree

9 files changed

+266
-81
lines changed

9 files changed

+266
-81
lines changed

acme/api/order.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ func (n *NewOrderRequest) Validate() error {
4949
if id.Value == "" {
5050
return acme.NewError(acme.ErrorMalformedType, "permanent identifier cannot be empty")
5151
}
52-
case acme.WireID:
53-
wireID, err := wire.ParseID([]byte(id.Value))
52+
case acme.WireUser:
53+
_, err := wire.ParseUserID([]byte(id.Value))
54+
if err != nil {
55+
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing Wire ID")
56+
}
57+
case acme.WireDevice:
58+
wireID, err := wire.ParseDeviceID([]byte(id.Value))
5459
if err != nil {
5560
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing Wire ID")
5661
}
@@ -273,10 +278,28 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
273278
}
274279

275280
var target string
276-
if az.Identifier.Type == acme.WireID {
277-
wireID, err := wire.ParseID([]byte(az.Identifier.Value))
281+
switch az.Identifier.Type {
282+
case acme.WireUser:
283+
wireOptions, err := prov.GetOptions().GetWireOptions()
284+
if err != nil {
285+
return acme.WrapErrorISE(err, "failed getting Wire options")
286+
}
287+
var targetProvider interface{ EvaluateTarget(string) (string, error) }
288+
switch typ {
289+
case acme.WIREOIDC01:
290+
targetProvider = wireOptions.GetOIDCOptions()
291+
default:
292+
return acme.NewError(acme.ErrorMalformedType, "unsupported type %q", typ)
293+
}
294+
295+
target, err = targetProvider.EvaluateTarget("")
278296
if err != nil {
279-
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireID")
297+
return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'")
298+
}
299+
case acme.WireDevice:
300+
wireID, err := wire.ParseDeviceID([]byte(az.Identifier.Value))
301+
if err != nil {
302+
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireUser")
280303
}
281304
clientID, err := wire.ParseClientID(wireID.ClientID)
282305
if err != nil {
@@ -288,8 +311,6 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
288311
}
289312
var targetProvider interface{ EvaluateTarget(string) (string, error) }
290313
switch typ {
291-
case acme.WIREOIDC01:
292-
targetProvider = wireOptions.GetOIDCOptions()
293314
case acme.WIREDPOP01:
294315
targetProvider = wireOptions.GetDPOPOptions()
295316
default:
@@ -440,8 +461,10 @@ func challengeTypes(az *acme.Authorization) []acme.ChallengeType {
440461
}
441462
case acme.PermanentIdentifier:
442463
chTypes = []acme.ChallengeType{acme.DEVICEATTEST01}
443-
case acme.WireID:
444-
chTypes = []acme.ChallengeType{acme.WIREOIDC01, acme.WIREDPOP01}
464+
case acme.WireUser:
465+
chTypes = []acme.ChallengeType{acme.WIREOIDC01}
466+
case acme.WireDevice:
467+
chTypes = []acme.ChallengeType{acme.WIREDPOP01}
445468
default:
446469
chTypes = []acme.ChallengeType{}
447470
}

acme/api/order_test.go

Lines changed: 132 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func TestNewOrderRequest_Validate(t *testing.T) {
101101
return test{
102102
nor: &NewOrderRequest{
103103
Identifiers: []acme.Identifier{
104-
{Type: "wireapp-id", Value: "{}"},
104+
{Type: "wireapp-device", Value: "{}"},
105105
},
106106
},
107107
err: acme.NewError(acme.ErrorMalformedType, `invalid Wire client ID "": invalid Wire client ID URI "": error parsing : scheme is missing`),
@@ -111,7 +111,7 @@ func TestNewOrderRequest_Validate(t *testing.T) {
111111
return test{
112112
nor: &NewOrderRequest{
113113
Identifiers: []acme.Identifier{
114-
{Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "nowireapp://example.com", "handle": "wireapp://%[email protected]"}`},
114+
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "nowireapp://example.com", "handle": "wireapp://%[email protected]"}`},
115115
},
116116
},
117117
err: acme.NewError(acme.ErrorMalformedType, `invalid Wire client ID "nowireapp://example.com": invalid Wire client ID scheme "nowireapp"; expected "wireapp"`),
@@ -121,7 +121,7 @@ func TestNewOrderRequest_Validate(t *testing.T) {
121121
return test{
122122
nor: &NewOrderRequest{
123123
Identifiers: []acme.Identifier{
124-
{Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://[email protected]", "handle": "wireapp://%[email protected]"}`},
124+
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://[email protected]", "handle": "wireapp://%[email protected]"}`},
125125
},
126126
},
127127
err: acme.NewError(acme.ErrorMalformedType, `invalid Wire client ID "wireapp://[email protected]": invalid Wire client ID username "user-device"`),
@@ -205,13 +205,28 @@ func TestNewOrderRequest_Validate(t *testing.T) {
205205
naf: naf,
206206
}
207207
},
208-
"ok/wireapp-idd": func(t *testing.T) test {
208+
"ok/wireapp-user": func(t *testing.T) test {
209209
nbf := time.Now().UTC().Add(time.Minute)
210210
naf := time.Now().UTC().Add(5 * time.Minute)
211211
return test{
212212
nor: &NewOrderRequest{
213213
Identifiers: []acme.Identifier{
214-
{Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://[email protected]", "handle": "wireapp://%[email protected]"}`},
214+
{Type: "wireapp-user", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%[email protected]"}`},
215+
},
216+
NotAfter: naf,
217+
NotBefore: nbf,
218+
},
219+
nbf: nbf,
220+
naf: naf,
221+
}
222+
},
223+
"ok/wireapp-device": func(t *testing.T) test {
224+
nbf := time.Now().UTC().Add(time.Minute)
225+
naf := time.Now().UTC().Add(5 * time.Minute)
226+
return test{
227+
nor: &NewOrderRequest{
228+
Identifiers: []acme.Identifier{
229+
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://[email protected]", "handle": "wireapp://%[email protected]"}`},
215230
},
216231
NotAfter: naf,
217232
NotBefore: nbf,
@@ -1719,7 +1734,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
17191734
},
17201735
}
17211736
},
1722-
"ok/default-naf-nbf-wireapp": func(t *testing.T) test {
1737+
"ok/default-naf-nbf-wireapp-user": func(t *testing.T) test {
17231738
acmeWireProv := newWireProvisionerWithOptions(t, &provisioner.Options{
17241739
Wire: &wire.Options{
17251740
OIDC: &wire.OIDCOptions{
@@ -1749,7 +1764,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
17491764
acc := &acme.Account{ID: "accID"}
17501765
nor := &NewOrderRequest{
17511766
Identifiers: []acme.Identifier{
1752-
{Type: "wireapp-id", Value: `{"client-id": "wireapp://user!client@domain"}`},
1767+
{Type: "wireapp-user", Value: `{"name": "Alice Smith", "handle": "wireapp://%[email protected]"}`},
17531768
},
17541769
}
17551770
b, err := json.Marshal(nor)
@@ -1758,9 +1773,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
17581773
ctx = context.WithValue(ctx, accContextKey, acc)
17591774
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
17601775
var (
1761-
ch1, ch2 **acme.Challenge
1762-
az1ID *string
1763-
count = 0
1776+
ch1 **acme.Challenge
1777+
az1ID *string
17641778
)
17651779
return test{
17661780
ctx: ctx,
@@ -1769,20 +1783,113 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
17691783
ca: &mockCA{},
17701784
db: &acme.MockDB{
17711785
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
1772-
switch count {
1773-
case 0:
1774-
ch.ID = "wireapp-oidc"
1775-
assert.Equals(t, ch.Type, acme.WIREOIDC01)
1776-
ch1 = &ch
1777-
case 1:
1778-
ch.ID = "wireapp-dpop"
1779-
assert.Equals(t, ch.Type, acme.WIREDPOP01)
1780-
ch2 = &ch
1781-
default:
1782-
assert.FatalError(t, errors.New("test logic error"))
1783-
return errors.New("force")
1784-
}
1785-
count++
1786+
ch.ID = "wireapp-oidc"
1787+
assert.Equals(t, ch.Type, acme.WIREOIDC01)
1788+
ch1 = &ch
1789+
assert.Equals(t, ch.AccountID, "accID")
1790+
assert.NotEquals(t, ch.Token, "")
1791+
assert.Equals(t, ch.Status, acme.StatusPending)
1792+
assert.Equals(t, ch.Value, `{"name": "Alice Smith", "handle": "wireapp://%[email protected]"}`)
1793+
return nil
1794+
},
1795+
MockCreateAuthorization: func(ctx context.Context, az *acme.Authorization) error {
1796+
az.ID = "az1ID"
1797+
az1ID = &az.ID
1798+
assert.Equals(t, az.AccountID, "accID")
1799+
assert.NotEquals(t, az.Token, "")
1800+
assert.Equals(t, az.Status, acme.StatusPending)
1801+
assert.Equals(t, az.Identifier, nor.Identifiers[0])
1802+
assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1})
1803+
assert.Equals(t, az.Wildcard, false)
1804+
return nil
1805+
},
1806+
MockCreateOrder: func(ctx context.Context, o *acme.Order) error {
1807+
o.ID = "ordID"
1808+
assert.Equals(t, o.AccountID, "accID")
1809+
assert.Equals(t, o.ProvisionerID, prov.GetID())
1810+
assert.Equals(t, o.Status, acme.StatusPending)
1811+
assert.Equals(t, o.Identifiers, nor.Identifiers)
1812+
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
1813+
return nil
1814+
},
1815+
MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
1816+
assert.Equals(t, prov.GetID(), provisionerID)
1817+
assert.Equals(t, "accID", accountID)
1818+
return nil, nil
1819+
},
1820+
},
1821+
vr: func(t *testing.T, o *acme.Order) {
1822+
now := clock.Now()
1823+
testBufferDur := 5 * time.Second
1824+
orderExpiry := now.Add(defaultOrderExpiry)
1825+
expNbf := now.Add(-defaultOrderBackdate)
1826+
expNaf := now.Add(prov.DefaultTLSCertDuration())
1827+
1828+
assert.Equals(t, o.ID, "ordID")
1829+
assert.Equals(t, o.Status, acme.StatusPending)
1830+
assert.Equals(t, o.Identifiers, nor.Identifiers)
1831+
assert.Equals(t, o.AuthorizationURLs, []string{fmt.Sprintf("%s/acme/%s/authz/az1ID", baseURL.String(), escProvName)})
1832+
assert.True(t, o.NotBefore.Add(-testBufferDur).Before(expNbf))
1833+
assert.True(t, o.NotBefore.Add(testBufferDur).After(expNbf))
1834+
assert.True(t, o.NotAfter.Add(-testBufferDur).Before(expNaf))
1835+
assert.True(t, o.NotAfter.Add(testBufferDur).After(expNaf))
1836+
assert.True(t, o.ExpiresAt.Add(-testBufferDur).Before(orderExpiry))
1837+
assert.True(t, o.ExpiresAt.Add(testBufferDur).After(orderExpiry))
1838+
},
1839+
}
1840+
},
1841+
"ok/default-naf-nbf-wireapp-device": func(t *testing.T) test {
1842+
acmeWireProv := newWireProvisionerWithOptions(t, &provisioner.Options{
1843+
Wire: &wire.Options{
1844+
OIDC: &wire.OIDCOptions{
1845+
Provider: &wire.Provider{
1846+
IssuerURL: "https://issuer.example.com",
1847+
AuthURL: "",
1848+
TokenURL: "",
1849+
JWKSURL: "",
1850+
UserInfoURL: "",
1851+
Algorithms: []string{"ES256"},
1852+
},
1853+
Config: &wire.Config{
1854+
ClientID: "integration test",
1855+
SignatureAlgorithms: []string{"ES256"},
1856+
SkipClientIDCheck: true,
1857+
SkipExpiryCheck: true,
1858+
SkipIssuerCheck: true,
1859+
InsecureSkipSignatureCheck: true,
1860+
Now: time.Now,
1861+
},
1862+
},
1863+
DPOP: &wire.DPOPOptions{
1864+
SigningKey: []byte(fakeWireSigningKey),
1865+
},
1866+
},
1867+
})
1868+
acc := &acme.Account{ID: "accID"}
1869+
nor := &NewOrderRequest{
1870+
Identifiers: []acme.Identifier{
1871+
{Type: "wireapp-device", Value: `{"client-id": "wireapp://user!client@domain"}`},
1872+
},
1873+
}
1874+
b, err := json.Marshal(nor)
1875+
assert.FatalError(t, err)
1876+
ctx := acme.NewProvisionerContext(context.Background(), acmeWireProv)
1877+
ctx = context.WithValue(ctx, accContextKey, acc)
1878+
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
1879+
var (
1880+
ch1 **acme.Challenge
1881+
az1ID *string
1882+
)
1883+
return test{
1884+
ctx: ctx,
1885+
statusCode: 201,
1886+
nor: nor,
1887+
ca: &mockCA{},
1888+
db: &acme.MockDB{
1889+
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
1890+
ch.ID = "wireapp-dpop"
1891+
assert.Equals(t, ch.Type, acme.WIREDPOP01)
1892+
ch1 = &ch
17861893
assert.Equals(t, ch.AccountID, "accID")
17871894
assert.NotEquals(t, ch.Token, "")
17881895
assert.Equals(t, ch.Status, acme.StatusPending)
@@ -1796,7 +1903,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
17961903
assert.NotEquals(t, az.Token, "")
17971904
assert.Equals(t, az.Status, acme.StatusPending)
17981905
assert.Equals(t, az.Identifier, nor.Identifiers[0])
1799-
assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1, *ch2})
1906+
assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1})
18001907
assert.Equals(t, az.Wildcard, false)
18011908
return nil
18021909
},

acme/api/wire_integration_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,11 @@ func TestWireIntegration(t *testing.T) {
234234
nor := &NewOrderRequest{
235235
Identifiers: []acme.Identifier{
236236
{
237-
Type: "wireapp-id",
237+
Type: "wireapp-user",
238+
Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%[email protected]"}`,
239+
},
240+
{
241+
Type: "wireapp-device",
238242
Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://[email protected]", "handle": "wireapp://%[email protected]"}`,
239243
},
240244
},

acme/challenge.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO
373373
return WrapError(ErrorMalformedType, err, "error unmarshalling Wire OIDC challenge payload")
374374
}
375375

376-
wireID, err := wire.ParseID([]byte(ch.Value))
376+
wireID, err := wire.ParseUserID([]byte(ch.Value))
377377
if err != nil {
378378
return WrapErrorISE(err, "error unmarshalling challenge data")
379379
}
@@ -451,7 +451,7 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO
451451
return nil
452452
}
453453

454-
func validateWireOIDCClaims(o *wireprovisioner.OIDCOptions, token *oidc.IDToken, wireID wire.ID) (map[string]any, error) {
454+
func validateWireOIDCClaims(o *wireprovisioner.OIDCOptions, token *oidc.IDToken, wireID wire.UserID) (map[string]any, error) {
455455
var m map[string]any
456456
if err := token.Claims(&m); err != nil {
457457
return nil, fmt.Errorf("failed extracting OIDC ID token claims: %w", err)
@@ -500,7 +500,7 @@ func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *j
500500
return WrapError(ErrorMalformedType, err, "error unmarshalling Wire DPoP challenge payload")
501501
}
502502

503-
wireID, err := wire.ParseID([]byte(ch.Value))
503+
wireID, err := wire.ParseDeviceID([]byte(ch.Value))
504504
if err != nil {
505505
return WrapErrorISE(err, "error unmarshalling challenge data")
506506
}
@@ -598,7 +598,7 @@ type wireVerifyParams struct {
598598
dpopKeyID string
599599
issuer string
600600
audience string
601-
wireID wire.ID
601+
wireID wire.DeviceID
602602
chToken string
603603
t time.Time
604604
}

acme/challenge_wire_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
100100
Type: "urn:ietf:params:acme:error:serverInternal",
101101
Detail: "The server experienced an internal error",
102102
Status: 500,
103-
Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.ID`),
103+
Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.DeviceID`),
104104
},
105105
}
106106
},
@@ -1096,7 +1096,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
10961096
Type: "urn:ietf:params:acme:error:serverInternal",
10971097
Detail: "The server experienced an internal error",
10981098
Status: 500,
1099-
Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.ID`),
1099+
Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.UserID`),
11001100
},
11011101
}
11021102
},
@@ -2030,7 +2030,7 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA=
20302030
require.True(t, ok)
20312031

20322032
issuer := "http://wire.com:19983/clients/7a41cf5b79683410/access-token"
2033-
wireID := wire.ID{
2033+
wireID := wire.DeviceID{
20342034
ClientID: "wireapp://[email protected]",
20352035
Handle: "wireapp://%[email protected]",
20362036
}
@@ -2127,7 +2127,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
21272127
idToken, err := verifier.Verify(ctx, idTokenString)
21282128
require.NoError(t, err)
21292129

2130-
wireID := wire.ID{
2130+
wireID := wire.UserID{
21312131
Name: "Alice Smith",
21322132
Handle: "wireapp://%[email protected]",
21332133
}

0 commit comments

Comments
 (0)