Skip to content

Commit 8c14b8b

Browse files
authored
Merge pull request smallstep#1666 from smallstep/wire-acme-extensions
Wire ACME extensions
2 parents 0a89da0 + 8e956cc commit 8c14b8b

29 files changed

+6707
-205
lines changed

acme/api/account_test.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ import (
2525
)
2626

2727
var (
28-
defaultDisableRenewal = false
29-
globalProvisionerClaims = provisioner.Claims{
30-
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
31-
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
32-
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
33-
DisableRenewal: &defaultDisableRenewal,
28+
defaultDisableRenewal = false
29+
defaultDisableSmallstepExtensions = false
30+
globalProvisionerClaims = provisioner.Claims{
31+
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
32+
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
33+
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
34+
DisableRenewal: &defaultDisableRenewal,
35+
DisableSmallstepExtensions: &defaultDisableSmallstepExtensions,
3436
}
3537
)
3638

acme/api/order.go

+109-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/x509"
66
"encoding/base64"
77
"encoding/json"
8+
"fmt"
89
"net"
910
"net/http"
1011
"strings"
@@ -16,6 +17,7 @@ import (
1617
"go.step.sm/crypto/x509util"
1718

1819
"github.com/smallstep/certificates/acme"
20+
"github.com/smallstep/certificates/acme/wire"
1921
"github.com/smallstep/certificates/api/render"
2022
"github.com/smallstep/certificates/authority/policy"
2123
"github.com/smallstep/certificates/authority/provisioner"
@@ -48,16 +50,86 @@ func (n *NewOrderRequest) Validate() error {
4850
if id.Value == "" {
4951
return acme.NewError(acme.ErrorMalformedType, "permanent identifier cannot be empty")
5052
}
53+
case acme.WireUser, acme.WireDevice:
54+
// validation of Wire identifiers is performed in `validateWireIdentifiers`, but
55+
// marked here as known and supported types.
56+
continue
5157
default:
5258
return acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: %s", id.Type)
5359
}
60+
}
5461

55-
// TODO(hs): add some validations for DNS domains?
56-
// TODO(hs): combine the errors from this with allow/deny policy, like example error in https://datatracker.ietf.org/doc/html/rfc8555#section-6.7.1
62+
if err := n.validateWireIdentifiers(); err != nil {
63+
return acme.WrapError(acme.ErrorMalformedType, err, "failed validating Wire identifiers")
5764
}
65+
66+
// TODO(hs): add some validations for DNS domains?
67+
// TODO(hs): combine the errors from this with allow/deny policy, like example error in https://datatracker.ietf.org/doc/html/rfc8555#section-6.7.1
68+
5869
return nil
5970
}
6071

72+
func (n *NewOrderRequest) validateWireIdentifiers() error {
73+
if !n.hasWireIdentifiers() {
74+
return nil
75+
}
76+
77+
userIdentifiers := identifiersOfType(acme.WireUser, n.Identifiers)
78+
deviceIdentifiers := identifiersOfType(acme.WireDevice, n.Identifiers)
79+
80+
if len(userIdentifiers) != 1 {
81+
return fmt.Errorf("expected exactly one Wire UserID identifier; got %d", len(userIdentifiers))
82+
}
83+
if len(deviceIdentifiers) != 1 {
84+
return fmt.Errorf("expected exactly one Wire DeviceID identifier, got %d", len(deviceIdentifiers))
85+
}
86+
87+
wireUserID, err := wire.ParseUserID(userIdentifiers[0].Value)
88+
if err != nil {
89+
return fmt.Errorf("failed parsing Wire UserID: %w", err)
90+
}
91+
92+
wireDeviceID, err := wire.ParseDeviceID(deviceIdentifiers[0].Value)
93+
if err != nil {
94+
return fmt.Errorf("failed parsing Wire DeviceID: %w", err)
95+
}
96+
if _, err := wire.ParseClientID(wireDeviceID.ClientID); err != nil {
97+
return fmt.Errorf("invalid Wire client ID %q: %w", wireDeviceID.ClientID, err)
98+
}
99+
100+
switch {
101+
case wireUserID.Domain != wireDeviceID.Domain:
102+
return fmt.Errorf("UserID domain %q does not match DeviceID domain %q", wireUserID.Domain, wireDeviceID.Domain)
103+
case wireUserID.Name != wireDeviceID.Name:
104+
return fmt.Errorf("UserID name %q does not match DeviceID name %q", wireUserID.Name, wireDeviceID.Name)
105+
case wireUserID.Handle != wireDeviceID.Handle:
106+
return fmt.Errorf("UserID handle %q does not match DeviceID handle %q", wireUserID.Handle, wireDeviceID.Handle)
107+
}
108+
109+
return nil
110+
}
111+
112+
// hasWireIdentifiers returns whether the [NewOrderRequest] contains
113+
// Wire identifiers.
114+
func (n *NewOrderRequest) hasWireIdentifiers() bool {
115+
for _, i := range n.Identifiers {
116+
if i.Type == acme.WireUser || i.Type == acme.WireDevice {
117+
return true
118+
}
119+
}
120+
return false
121+
}
122+
123+
// identifiersOfType returns the Identifiers that are of type typ.
124+
func identifiersOfType(typ acme.IdentifierType, ids []acme.Identifier) (result []acme.Identifier) {
125+
for _, id := range ids {
126+
if id.Type == typ {
127+
result = append(result, id)
128+
}
129+
}
130+
return
131+
}
132+
61133
// FinalizeRequest captures the body for a Finalize order request.
62134
type FinalizeRequest struct {
63135
CSR string `json:"csr"`
@@ -263,12 +335,43 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
263335
continue
264336
}
265337

338+
var target string
339+
switch az.Identifier.Type {
340+
case acme.WireUser:
341+
wireOptions, err := prov.GetOptions().GetWireOptions()
342+
if err != nil {
343+
return acme.WrapErrorISE(err, "failed getting Wire options")
344+
}
345+
target, err = wireOptions.GetOIDCOptions().EvaluateTarget("") // TODO(hs): determine if required by Wire
346+
if err != nil {
347+
return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'")
348+
}
349+
case acme.WireDevice:
350+
wireID, err := wire.ParseDeviceID(az.Identifier.Value)
351+
if err != nil {
352+
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireDevice")
353+
}
354+
clientID, err := wire.ParseClientID(wireID.ClientID)
355+
if err != nil {
356+
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing ClientID")
357+
}
358+
wireOptions, err := prov.GetOptions().GetWireOptions()
359+
if err != nil {
360+
return acme.WrapErrorISE(err, "failed getting Wire options")
361+
}
362+
target, err = wireOptions.GetDPOPOptions().EvaluateTarget(clientID.DeviceID)
363+
if err != nil {
364+
return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'")
365+
}
366+
}
367+
266368
ch := &acme.Challenge{
267369
AccountID: az.AccountID,
268370
Value: az.Identifier.Value,
269371
Type: typ,
270372
Token: az.Token,
271373
Status: acme.StatusPending,
374+
Target: target,
272375
}
273376
if err := db.CreateChallenge(ctx, ch); err != nil {
274377
return acme.WrapErrorISE(err, "error creating challenge")
@@ -400,6 +503,10 @@ func challengeTypes(az *acme.Authorization) []acme.ChallengeType {
400503
}
401504
case acme.PermanentIdentifier:
402505
chTypes = []acme.ChallengeType{acme.DEVICEATTEST01}
506+
case acme.WireUser:
507+
chTypes = []acme.ChallengeType{acme.WIREOIDC01}
508+
case acme.WireDevice:
509+
chTypes = []acme.ChallengeType{acme.WIREDPOP01}
403510
default:
404511
chTypes = []acme.ChallengeType{}
405512
}

0 commit comments

Comments
 (0)