mirror of
https://github.com/smallstep/certificates.git
synced 2024-11-15 18:12:59 +00:00
Merge pull request #1689 from smallstep/beltram/wire-acme-extensions
Use two separate Wire identifier types
This commit is contained in:
commit
502334fd82
@ -49,8 +49,13 @@ func (n *NewOrderRequest) Validate() error {
|
||||
if id.Value == "" {
|
||||
return acme.NewError(acme.ErrorMalformedType, "permanent identifier cannot be empty")
|
||||
}
|
||||
case acme.WireID:
|
||||
wireID, err := wire.ParseID([]byte(id.Value))
|
||||
case acme.WireUser:
|
||||
_, err := wire.ParseUserID([]byte(id.Value))
|
||||
if err != nil {
|
||||
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing Wire ID")
|
||||
}
|
||||
case acme.WireDevice:
|
||||
wireID, err := wire.ParseDeviceID([]byte(id.Value))
|
||||
if err != nil {
|
||||
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing Wire ID")
|
||||
}
|
||||
@ -273,10 +278,28 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
|
||||
}
|
||||
|
||||
var target string
|
||||
if az.Identifier.Type == acme.WireID {
|
||||
wireID, err := wire.ParseID([]byte(az.Identifier.Value))
|
||||
switch az.Identifier.Type {
|
||||
case acme.WireUser:
|
||||
wireOptions, err := prov.GetOptions().GetWireOptions()
|
||||
if err != nil {
|
||||
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireID")
|
||||
return acme.WrapErrorISE(err, "failed getting Wire options")
|
||||
}
|
||||
var targetProvider interface{ EvaluateTarget(string) (string, error) }
|
||||
switch typ {
|
||||
case acme.WIREOIDC01:
|
||||
targetProvider = wireOptions.GetOIDCOptions()
|
||||
default:
|
||||
return acme.NewError(acme.ErrorMalformedType, "unsupported type %q", typ)
|
||||
}
|
||||
|
||||
target, err = targetProvider.EvaluateTarget("")
|
||||
if err != nil {
|
||||
return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'")
|
||||
}
|
||||
case acme.WireDevice:
|
||||
wireID, err := wire.ParseDeviceID([]byte(az.Identifier.Value))
|
||||
if err != nil {
|
||||
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireUser")
|
||||
}
|
||||
clientID, err := wire.ParseClientID(wireID.ClientID)
|
||||
if err != nil {
|
||||
@ -288,8 +311,6 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
|
||||
}
|
||||
var targetProvider interface{ EvaluateTarget(string) (string, error) }
|
||||
switch typ {
|
||||
case acme.WIREOIDC01:
|
||||
targetProvider = wireOptions.GetOIDCOptions()
|
||||
case acme.WIREDPOP01:
|
||||
targetProvider = wireOptions.GetDPOPOptions()
|
||||
default:
|
||||
@ -440,8 +461,10 @@ func challengeTypes(az *acme.Authorization) []acme.ChallengeType {
|
||||
}
|
||||
case acme.PermanentIdentifier:
|
||||
chTypes = []acme.ChallengeType{acme.DEVICEATTEST01}
|
||||
case acme.WireID:
|
||||
chTypes = []acme.ChallengeType{acme.WIREOIDC01, acme.WIREDPOP01}
|
||||
case acme.WireUser:
|
||||
chTypes = []acme.ChallengeType{acme.WIREOIDC01}
|
||||
case acme.WireDevice:
|
||||
chTypes = []acme.ChallengeType{acme.WIREDPOP01}
|
||||
default:
|
||||
chTypes = []acme.ChallengeType{}
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ func TestNewOrderRequest_Validate(t *testing.T) {
|
||||
return test{
|
||||
nor: &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "wireapp-id", Value: "{}"},
|
||||
{Type: "wireapp-device", Value: "{}"},
|
||||
},
|
||||
},
|
||||
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) {
|
||||
return test{
|
||||
nor: &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "nowireapp://example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
|
||||
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "nowireapp://example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
|
||||
},
|
||||
},
|
||||
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) {
|
||||
return test{
|
||||
nor: &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://user-device@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
|
||||
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://user-device@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
|
||||
},
|
||||
},
|
||||
err: acme.NewError(acme.ErrorMalformedType, `invalid Wire client ID "wireapp://user-device@example.com": invalid Wire client ID username "user-device"`),
|
||||
@ -205,13 +205,28 @@ func TestNewOrderRequest_Validate(t *testing.T) {
|
||||
naf: naf,
|
||||
}
|
||||
},
|
||||
"ok/wireapp-idd": func(t *testing.T) test {
|
||||
"ok/wireapp-user": func(t *testing.T) test {
|
||||
nbf := time.Now().UTC().Add(time.Minute)
|
||||
naf := time.Now().UTC().Add(5 * time.Minute)
|
||||
return test{
|
||||
nor: &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "wireapp-id", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
|
||||
{Type: "wireapp-user", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
|
||||
},
|
||||
NotAfter: naf,
|
||||
NotBefore: nbf,
|
||||
},
|
||||
nbf: nbf,
|
||||
naf: naf,
|
||||
}
|
||||
},
|
||||
"ok/wireapp-device": func(t *testing.T) test {
|
||||
nbf := time.Now().UTC().Add(time.Minute)
|
||||
naf := time.Now().UTC().Add(5 * time.Minute)
|
||||
return test{
|
||||
nor: &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
|
||||
},
|
||||
NotAfter: naf,
|
||||
NotBefore: nbf,
|
||||
@ -1719,7 +1734,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/default-naf-nbf-wireapp": func(t *testing.T) test {
|
||||
"ok/default-naf-nbf-wireapp-user": func(t *testing.T) test {
|
||||
acmeWireProv := newWireProvisionerWithOptions(t, &provisioner.Options{
|
||||
Wire: &wire.Options{
|
||||
OIDC: &wire.OIDCOptions{
|
||||
@ -1749,7 +1764,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
|
||||
acc := &acme.Account{ID: "accID"}
|
||||
nor := &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "wireapp-id", Value: `{"client-id": "wireapp://user!client@domain"}`},
|
||||
{Type: "wireapp-user", Value: `{"name": "Alice Smith", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
|
||||
},
|
||||
}
|
||||
b, err := json.Marshal(nor)
|
||||
@ -1758,9 +1773,8 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
|
||||
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
|
||||
var (
|
||||
ch1, ch2 **acme.Challenge
|
||||
az1ID *string
|
||||
count = 0
|
||||
ch1 **acme.Challenge
|
||||
az1ID *string
|
||||
)
|
||||
return test{
|
||||
ctx: ctx,
|
||||
@ -1769,20 +1783,113 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
|
||||
ca: &mockCA{},
|
||||
db: &acme.MockDB{
|
||||
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
|
||||
switch count {
|
||||
case 0:
|
||||
ch.ID = "wireapp-oidc"
|
||||
assert.Equals(t, ch.Type, acme.WIREOIDC01)
|
||||
ch1 = &ch
|
||||
case 1:
|
||||
ch.ID = "wireapp-dpop"
|
||||
assert.Equals(t, ch.Type, acme.WIREDPOP01)
|
||||
ch2 = &ch
|
||||
default:
|
||||
assert.FatalError(t, errors.New("test logic error"))
|
||||
return errors.New("force")
|
||||
}
|
||||
count++
|
||||
ch.ID = "wireapp-oidc"
|
||||
assert.Equals(t, ch.Type, acme.WIREOIDC01)
|
||||
ch1 = &ch
|
||||
assert.Equals(t, ch.AccountID, "accID")
|
||||
assert.NotEquals(t, ch.Token, "")
|
||||
assert.Equals(t, ch.Status, acme.StatusPending)
|
||||
assert.Equals(t, ch.Value, `{"name": "Alice Smith", "handle": "wireapp://%40alice.smith.qa@example.com"}`)
|
||||
return nil
|
||||
},
|
||||
MockCreateAuthorization: func(ctx context.Context, az *acme.Authorization) error {
|
||||
az.ID = "az1ID"
|
||||
az1ID = &az.ID
|
||||
assert.Equals(t, az.AccountID, "accID")
|
||||
assert.NotEquals(t, az.Token, "")
|
||||
assert.Equals(t, az.Status, acme.StatusPending)
|
||||
assert.Equals(t, az.Identifier, nor.Identifiers[0])
|
||||
assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1})
|
||||
assert.Equals(t, az.Wildcard, false)
|
||||
return nil
|
||||
},
|
||||
MockCreateOrder: func(ctx context.Context, o *acme.Order) error {
|
||||
o.ID = "ordID"
|
||||
assert.Equals(t, o.AccountID, "accID")
|
||||
assert.Equals(t, o.ProvisionerID, prov.GetID())
|
||||
assert.Equals(t, o.Status, acme.StatusPending)
|
||||
assert.Equals(t, o.Identifiers, nor.Identifiers)
|
||||
assert.Equals(t, o.AuthorizationIDs, []string{*az1ID})
|
||||
return nil
|
||||
},
|
||||
MockGetExternalAccountKeyByAccountID: func(ctx context.Context, provisionerID, accountID string) (*acme.ExternalAccountKey, error) {
|
||||
assert.Equals(t, prov.GetID(), provisionerID)
|
||||
assert.Equals(t, "accID", accountID)
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
vr: func(t *testing.T, o *acme.Order) {
|
||||
now := clock.Now()
|
||||
testBufferDur := 5 * time.Second
|
||||
orderExpiry := now.Add(defaultOrderExpiry)
|
||||
expNbf := now.Add(-defaultOrderBackdate)
|
||||
expNaf := now.Add(prov.DefaultTLSCertDuration())
|
||||
|
||||
assert.Equals(t, o.ID, "ordID")
|
||||
assert.Equals(t, o.Status, acme.StatusPending)
|
||||
assert.Equals(t, o.Identifiers, nor.Identifiers)
|
||||
assert.Equals(t, o.AuthorizationURLs, []string{fmt.Sprintf("%s/acme/%s/authz/az1ID", baseURL.String(), escProvName)})
|
||||
assert.True(t, o.NotBefore.Add(-testBufferDur).Before(expNbf))
|
||||
assert.True(t, o.NotBefore.Add(testBufferDur).After(expNbf))
|
||||
assert.True(t, o.NotAfter.Add(-testBufferDur).Before(expNaf))
|
||||
assert.True(t, o.NotAfter.Add(testBufferDur).After(expNaf))
|
||||
assert.True(t, o.ExpiresAt.Add(-testBufferDur).Before(orderExpiry))
|
||||
assert.True(t, o.ExpiresAt.Add(testBufferDur).After(orderExpiry))
|
||||
},
|
||||
}
|
||||
},
|
||||
"ok/default-naf-nbf-wireapp-device": func(t *testing.T) test {
|
||||
acmeWireProv := newWireProvisionerWithOptions(t, &provisioner.Options{
|
||||
Wire: &wire.Options{
|
||||
OIDC: &wire.OIDCOptions{
|
||||
Provider: &wire.Provider{
|
||||
IssuerURL: "https://issuer.example.com",
|
||||
AuthURL: "",
|
||||
TokenURL: "",
|
||||
JWKSURL: "",
|
||||
UserInfoURL: "",
|
||||
Algorithms: []string{"ES256"},
|
||||
},
|
||||
Config: &wire.Config{
|
||||
ClientID: "integration test",
|
||||
SignatureAlgorithms: []string{"ES256"},
|
||||
SkipClientIDCheck: true,
|
||||
SkipExpiryCheck: true,
|
||||
SkipIssuerCheck: true,
|
||||
InsecureSkipSignatureCheck: true,
|
||||
Now: time.Now,
|
||||
},
|
||||
},
|
||||
DPOP: &wire.DPOPOptions{
|
||||
SigningKey: []byte(fakeWireSigningKey),
|
||||
},
|
||||
},
|
||||
})
|
||||
acc := &acme.Account{ID: "accID"}
|
||||
nor := &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{Type: "wireapp-device", Value: `{"client-id": "wireapp://user!client@domain"}`},
|
||||
},
|
||||
}
|
||||
b, err := json.Marshal(nor)
|
||||
assert.FatalError(t, err)
|
||||
ctx := acme.NewProvisionerContext(context.Background(), acmeWireProv)
|
||||
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
|
||||
var (
|
||||
ch1 **acme.Challenge
|
||||
az1ID *string
|
||||
)
|
||||
return test{
|
||||
ctx: ctx,
|
||||
statusCode: 201,
|
||||
nor: nor,
|
||||
ca: &mockCA{},
|
||||
db: &acme.MockDB{
|
||||
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
|
||||
ch.ID = "wireapp-dpop"
|
||||
assert.Equals(t, ch.Type, acme.WIREDPOP01)
|
||||
ch1 = &ch
|
||||
assert.Equals(t, ch.AccountID, "accID")
|
||||
assert.NotEquals(t, ch.Token, "")
|
||||
assert.Equals(t, ch.Status, acme.StatusPending)
|
||||
@ -1796,7 +1903,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
|
||||
assert.NotEquals(t, az.Token, "")
|
||||
assert.Equals(t, az.Status, acme.StatusPending)
|
||||
assert.Equals(t, az.Identifier, nor.Identifiers[0])
|
||||
assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1, *ch2})
|
||||
assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1})
|
||||
assert.Equals(t, az.Wildcard, false)
|
||||
return nil
|
||||
},
|
||||
|
@ -234,7 +234,11 @@ func TestWireIntegration(t *testing.T) {
|
||||
nor := &NewOrderRequest{
|
||||
Identifiers: []acme.Identifier{
|
||||
{
|
||||
Type: "wireapp-id",
|
||||
Type: "wireapp-user",
|
||||
Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`,
|
||||
},
|
||||
{
|
||||
Type: "wireapp-device",
|
||||
Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`,
|
||||
},
|
||||
},
|
||||
|
@ -373,7 +373,7 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO
|
||||
return WrapError(ErrorMalformedType, err, "error unmarshalling Wire OIDC challenge payload")
|
||||
}
|
||||
|
||||
wireID, err := wire.ParseID([]byte(ch.Value))
|
||||
wireID, err := wire.ParseUserID([]byte(ch.Value))
|
||||
if err != nil {
|
||||
return WrapErrorISE(err, "error unmarshalling challenge data")
|
||||
}
|
||||
@ -451,7 +451,7 @@ func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSO
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateWireOIDCClaims(o *wireprovisioner.OIDCOptions, token *oidc.IDToken, wireID wire.ID) (map[string]any, error) {
|
||||
func validateWireOIDCClaims(o *wireprovisioner.OIDCOptions, token *oidc.IDToken, wireID wire.UserID) (map[string]any, error) {
|
||||
var m map[string]any
|
||||
if err := token.Claims(&m); err != nil {
|
||||
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
|
||||
return WrapError(ErrorMalformedType, err, "error unmarshalling Wire DPoP challenge payload")
|
||||
}
|
||||
|
||||
wireID, err := wire.ParseID([]byte(ch.Value))
|
||||
wireID, err := wire.ParseDeviceID([]byte(ch.Value))
|
||||
if err != nil {
|
||||
return WrapErrorISE(err, "error unmarshalling challenge data")
|
||||
}
|
||||
@ -598,7 +598,7 @@ type wireVerifyParams struct {
|
||||
dpopKeyID string
|
||||
issuer string
|
||||
audience string
|
||||
wireID wire.ID
|
||||
wireID wire.DeviceID
|
||||
chToken string
|
||||
t time.Time
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
|
||||
Type: "urn:ietf:params:acme:error:serverInternal",
|
||||
Detail: "The server experienced an internal error",
|
||||
Status: 500,
|
||||
Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.ID`),
|
||||
Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.DeviceID`),
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -1096,7 +1096,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
|
||||
Type: "urn:ietf:params:acme:error:serverInternal",
|
||||
Detail: "The server experienced an internal error",
|
||||
Status: 500,
|
||||
Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.ID`),
|
||||
Err: errors.New(`error unmarshalling challenge data: json: cannot unmarshal number into Go value of type wire.UserID`),
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -2030,7 +2030,7 @@ MCowBQYDK2VwAyEAB2IYqBWXAouDt3WcCZgCM3t9gumMEKMlgMsGenSu+fA=
|
||||
require.True(t, ok)
|
||||
|
||||
issuer := "http://wire.com:19983/clients/7a41cf5b79683410/access-token"
|
||||
wireID := wire.ID{
|
||||
wireID := wire.DeviceID{
|
||||
ClientID: "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
|
||||
Handle: "wireapp://%40alice_wire@wire.com",
|
||||
}
|
||||
@ -2127,7 +2127,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
|
||||
idToken, err := verifier.Verify(ctx, idTokenString)
|
||||
require.NoError(t, err)
|
||||
|
||||
wireID := wire.ID{
|
||||
wireID := wire.UserID{
|
||||
Name: "Alice Smith",
|
||||
Handle: "wireapp://%40alice_wire@wire.com",
|
||||
}
|
||||
|
@ -31,8 +31,10 @@ const (
|
||||
// PermanentIdentifier is the ACME permanent-identifier identifier type
|
||||
// defined in https://datatracker.ietf.org/doc/html/draft-bweeks-acme-device-attest-00
|
||||
PermanentIdentifier IdentifierType = "permanent-identifier"
|
||||
// WireID is the Wire user identifier type
|
||||
WireID IdentifierType = "wireapp-id"
|
||||
// WireUser is the Wire user identifier type
|
||||
WireUser IdentifierType = "wireapp-user"
|
||||
// WireDevice is the Wire device identifier type
|
||||
WireDevice IdentifierType = "wireapp-device"
|
||||
)
|
||||
|
||||
// Identifier encodes the type that an order pertains to.
|
||||
@ -322,23 +324,23 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
|
||||
}
|
||||
|
||||
// containsWireIdentifiers checks if [Order] contains ACME
|
||||
// identifiers for the WireID type.
|
||||
// identifiers for the WireUser or WireDevice types.
|
||||
func (o *Order) containsWireIdentifiers() bool {
|
||||
for _, i := range o.Identifiers {
|
||||
if i.Type == WireID {
|
||||
if i.Type == WireUser || i.Type == WireDevice {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// createWireSubject creates the subject for an [Order] with WireID identifiers.
|
||||
// createWireSubject creates the subject for an [Order] with WireUser identifiers.
|
||||
func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util.Subject, err error) {
|
||||
wireIDs, otherIDs := 0, 0
|
||||
wireUserIDs, wireDeviceIDs, otherIDs := 0, 0, 0
|
||||
for _, identifier := range o.Identifiers {
|
||||
switch identifier.Type {
|
||||
case WireID:
|
||||
wireID, err := wire.ParseID([]byte(identifier.Value))
|
||||
case WireUser:
|
||||
wireID, err := wire.ParseUserID([]byte(identifier.Value))
|
||||
if err != nil {
|
||||
return subject, NewErrorISE("unmarshal wireID: %s", err)
|
||||
}
|
||||
@ -357,7 +359,7 @@ func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util
|
||||
}
|
||||
}
|
||||
if !foundDisplayName {
|
||||
return subject, NewErrorISE("CSR must contain the display name in 2.16.840.1.113730.3.1.241 OID")
|
||||
return subject, NewErrorISE("CSR must contain the display name in '2.16.840.1.113730.3.1.241' OID")
|
||||
}
|
||||
|
||||
if len(csr.Subject.Organization) == 0 || !strings.EqualFold(csr.Subject.Organization[0], wireID.Domain) {
|
||||
@ -365,14 +367,16 @@ func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util
|
||||
}
|
||||
subject.CommonName = wireID.Name
|
||||
subject.Organization = []string{wireID.Domain}
|
||||
wireIDs++
|
||||
wireUserIDs++
|
||||
case WireDevice:
|
||||
wireDeviceIDs++
|
||||
default:
|
||||
otherIDs++
|
||||
}
|
||||
}
|
||||
|
||||
if wireIDs > 0 && otherIDs > 0 || wireIDs > 1 {
|
||||
return subject, NewErrorISE("at most one WireID can be signed along with no other ID, found %d WireIDs and %d other IDs", wireIDs, otherIDs)
|
||||
if otherIDs > 0 || wireUserIDs != 1 && wireDeviceIDs != 1 {
|
||||
return subject, NewErrorISE("order must have exactly one WireUser and WireDevice identifier")
|
||||
}
|
||||
|
||||
return
|
||||
@ -385,10 +389,10 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
|
||||
}
|
||||
|
||||
// order the DNS names and IP addresses, so that they can be compared against the canonicalized CSR
|
||||
orderNames := make([]string, numberOfIdentifierType(DNS, o.Identifiers)+2*numberOfIdentifierType(WireID, o.Identifiers))
|
||||
orderNames := make([]string, numberOfIdentifierType(DNS, o.Identifiers))
|
||||
orderIPs := make([]net.IP, numberOfIdentifierType(IP, o.Identifiers))
|
||||
orderPIDs := make([]string, numberOfIdentifierType(PermanentIdentifier, o.Identifiers))
|
||||
tmpOrderURIs := make([]*url.URL, 2*numberOfIdentifierType(WireID, o.Identifiers))
|
||||
tmpOrderURIs := make([]*url.URL, numberOfIdentifierType(WireUser, o.Identifiers)+numberOfIdentifierType(WireDevice, o.Identifiers))
|
||||
indexDNS, indexIP, indexPID, indexURI := 0, 0, 0, 0
|
||||
for _, n := range o.Identifiers {
|
||||
switch n.Type {
|
||||
@ -401,8 +405,19 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
|
||||
case PermanentIdentifier:
|
||||
orderPIDs[indexPID] = n.Value
|
||||
indexPID++
|
||||
case WireID:
|
||||
wireID, err := wire.ParseID([]byte(n.Value))
|
||||
case WireUser:
|
||||
wireID, err := wire.ParseUserID([]byte(n.Value))
|
||||
if err != nil {
|
||||
return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value)
|
||||
}
|
||||
handle, err := url.Parse(wireID.Handle)
|
||||
if err != nil {
|
||||
return sans, NewErrorISE("handle must be a URI: %s", wireID.Handle)
|
||||
}
|
||||
tmpOrderURIs[indexURI] = handle
|
||||
indexURI++
|
||||
case WireDevice:
|
||||
wireID, err := wire.ParseDeviceID([]byte(n.Value))
|
||||
if err != nil {
|
||||
return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value)
|
||||
}
|
||||
@ -412,12 +427,6 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
|
||||
}
|
||||
tmpOrderURIs[indexURI] = clientID
|
||||
indexURI++
|
||||
handle, err := url.Parse(wireID.Handle)
|
||||
if err != nil {
|
||||
return sans, NewErrorISE("handle must be a URI: %s", wireID.Handle)
|
||||
}
|
||||
tmpOrderURIs[indexURI] = handle
|
||||
indexURI++
|
||||
default:
|
||||
return sans, NewErrorISE("unsupported identifier type in order: %s", n.Type)
|
||||
}
|
||||
|
@ -8,15 +8,26 @@ import (
|
||||
"go.step.sm/crypto/kms/uri"
|
||||
)
|
||||
|
||||
type ID struct {
|
||||
type UserID struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
Handle string `json:"handle,omitempty"`
|
||||
}
|
||||
|
||||
type DeviceID struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
ClientID string `json:"client-id,omitempty"`
|
||||
Handle string `json:"handle,omitempty"`
|
||||
}
|
||||
|
||||
func ParseID(data []byte) (wireID ID, err error) {
|
||||
err = json.Unmarshal(data, &wireID)
|
||||
func ParseUserID(data []byte) (id UserID, err error) {
|
||||
err = json.Unmarshal(data, &id)
|
||||
return
|
||||
}
|
||||
|
||||
func ParseDeviceID(data []byte) (id DeviceID, err error) {
|
||||
err = json.Unmarshal(data, &id)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -7,19 +7,43 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseID(t *testing.T) {
|
||||
ok := `{"name": "Alice Smith", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
|
||||
func TestParseUserID(t *testing.T) {
|
||||
ok := `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantWireID ID
|
||||
wantWireID UserID
|
||||
expectedErr error
|
||||
}{
|
||||
{name: "ok", data: []byte(ok), wantWireID: ID{Name: "Alice Smith", Domain: "wire.com", ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", Handle: "wireapp://%40alice_wire@wire.com"}},
|
||||
{name: "ok", data: []byte(ok), wantWireID: UserID{Name: "Alice Smith", Domain: "wire.com", Handle: "wireapp://%40alice_wire@wire.com"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotWireID, err := ParseID(tt.data)
|
||||
gotWireID, err := ParseUserID(tt.data)
|
||||
if tt.expectedErr != nil {
|
||||
assert.EqualError(t, err, tt.expectedErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantWireID, gotWireID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDeviceID(t *testing.T) {
|
||||
ok := `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantWireID DeviceID
|
||||
expectedErr error
|
||||
}{
|
||||
{name: "ok", data: []byte(ok), wantWireID: DeviceID{Name: "device", Domain: "wire.com", ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", Handle: "wireapp://%40alice_wire@wire.com"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotWireID, err := ParseDeviceID(tt.data)
|
||||
if tt.expectedErr != nil {
|
||||
assert.EqualError(t, err, tt.expectedErr.Error())
|
||||
return
|
||||
|
@ -10,9 +10,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.step.sm/linkedca"
|
||||
|
||||
"github.com/smallstep/certificates/acme/wire"
|
||||
"go.step.sm/linkedca"
|
||||
)
|
||||
|
||||
// ACMEChallenge represents the supported acme challenges.
|
||||
@ -224,8 +223,10 @@ const (
|
||||
IP ACMEIdentifierType = "ip"
|
||||
// DNS is the ACME dns identifier type
|
||||
DNS ACMEIdentifierType = "dns"
|
||||
// WireID is the Wire user identifier type
|
||||
WireID ACMEIdentifierType = "wireapp-id"
|
||||
// WireUser is the Wire user identifier type
|
||||
WireUser ACMEIdentifierType = "wireapp-user"
|
||||
// WireDevice is the Wire device identifier type
|
||||
WireDevice ACMEIdentifierType = "wireapp-device"
|
||||
)
|
||||
|
||||
// ACMEIdentifier encodes ACME Order Identifiers
|
||||
@ -251,12 +252,18 @@ func (p *ACME) AuthorizeOrderIdentifier(_ context.Context, identifier ACMEIdenti
|
||||
err = x509Policy.IsIPAllowed(net.ParseIP(identifier.Value))
|
||||
case DNS:
|
||||
err = x509Policy.IsDNSAllowed(identifier.Value)
|
||||
case WireID:
|
||||
var wireID wire.ID
|
||||
if wireID, err = wire.ParseID([]byte(identifier.Value)); err != nil {
|
||||
case WireUser:
|
||||
var wireID wire.UserID
|
||||
if wireID, err = wire.ParseUserID([]byte(identifier.Value)); err != nil {
|
||||
return fmt.Errorf("failed parsing Wire SANs: %w", err)
|
||||
}
|
||||
err = x509Policy.AreSANsAllowed([]string{wireID.ClientID, wireID.Handle})
|
||||
err = x509Policy.AreSANsAllowed([]string{wireID.Handle})
|
||||
case WireDevice:
|
||||
var wireID wire.DeviceID
|
||||
if wireID, err = wire.ParseDeviceID([]byte(identifier.Value)); err != nil {
|
||||
return fmt.Errorf("failed parsing Wire SANs: %w", err)
|
||||
}
|
||||
err = x509Policy.AreSANsAllowed([]string{wireID.ClientID})
|
||||
default:
|
||||
err = fmt.Errorf("invalid ACME identifier type '%s' provided", identifier.Type)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user