pull/1666/merge
Herman Slatman 4 weeks ago committed by GitHub
commit 1fe0831130
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -24,12 +24,14 @@ import (
)
var (
defaultDisableRenewal = false
globalProvisionerClaims = provisioner.Claims{
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
DisableRenewal: &defaultDisableRenewal,
defaultDisableRenewal = false
defaultDisableSmallstepExtensions = false
globalProvisionerClaims = provisioner.Claims{
MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute},
MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
DefaultTLSDur: &provisioner.Duration{Duration: 24 * time.Hour},
DisableRenewal: &defaultDisableRenewal,
DisableSmallstepExtensions: &defaultDisableSmallstepExtensions,
}
)

@ -5,6 +5,7 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"net"
"net/http"
"strings"
@ -16,6 +17,7 @@ import (
"go.step.sm/crypto/x509util"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/acme/wire"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/authority/provisioner"
@ -48,16 +50,86 @@ func (n *NewOrderRequest) Validate() error {
if id.Value == "" {
return acme.NewError(acme.ErrorMalformedType, "permanent identifier cannot be empty")
}
case acme.WireUser, acme.WireDevice:
// validation of Wire identifiers is performed in `validateWireIdentifiers`, but
// marked here as known and supported types.
continue
default:
return acme.NewError(acme.ErrorMalformedType, "identifier type unsupported: %s", id.Type)
}
}
// TODO(hs): add some validations for DNS domains?
// 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
if err := n.validateWireIdentifiers(); err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "failed validating Wire identifiers")
}
// TODO(hs): add some validations for DNS domains?
// 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
return nil
}
func (n *NewOrderRequest) validateWireIdentifiers() error {
if !n.hasWireIdentifiers() {
return nil
}
userIdentifiers := identifiersOfType(acme.WireUser, n.Identifiers)
deviceIdentifiers := identifiersOfType(acme.WireDevice, n.Identifiers)
if len(userIdentifiers) != 1 {
return fmt.Errorf("expected exactly one Wire UserID identifier; got %d", len(userIdentifiers))
}
if len(deviceIdentifiers) != 1 {
return fmt.Errorf("expected exactly one Wire DeviceID identifier, got %d", len(deviceIdentifiers))
}
wireUserID, err := wire.ParseUserID(userIdentifiers[0].Value)
if err != nil {
return fmt.Errorf("failed parsing Wire UserID: %w", err)
}
wireDeviceID, err := wire.ParseDeviceID(deviceIdentifiers[0].Value)
if err != nil {
return fmt.Errorf("failed parsing Wire DeviceID: %w", err)
}
if _, err := wire.ParseClientID(wireDeviceID.ClientID); err != nil {
return fmt.Errorf("invalid Wire client ID %q: %w", wireDeviceID.ClientID, err)
}
switch {
case wireUserID.Domain != wireDeviceID.Domain:
return fmt.Errorf("UserID domain %q does not match DeviceID domain %q", wireUserID.Domain, wireDeviceID.Domain)
case wireUserID.Name != wireDeviceID.Name:
return fmt.Errorf("UserID name %q does not match DeviceID name %q", wireUserID.Name, wireDeviceID.Name)
case wireUserID.Handle != wireDeviceID.Handle:
return fmt.Errorf("UserID handle %q does not match DeviceID handle %q", wireUserID.Handle, wireDeviceID.Handle)
}
return nil
}
// hasWireIdentifiers returns whether the [NewOrderRequest] contains
// Wire identifiers.
func (n *NewOrderRequest) hasWireIdentifiers() bool {
for _, i := range n.Identifiers {
if i.Type == acme.WireUser || i.Type == acme.WireDevice {
return true
}
}
return false
}
// identifiersOfType returns the Identifiers that are of type typ.
func identifiersOfType(typ acme.IdentifierType, ids []acme.Identifier) (result []acme.Identifier) {
for _, id := range ids {
if id.Type == typ {
result = append(result, id)
}
}
return
}
// FinalizeRequest captures the body for a Finalize order request.
type FinalizeRequest struct {
CSR string `json:"csr"`
@ -262,12 +334,43 @@ func newAuthorization(ctx context.Context, az *acme.Authorization) error {
continue
}
var target string
switch az.Identifier.Type {
case acme.WireUser:
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return acme.WrapErrorISE(err, "failed getting Wire options")
}
target, err = wireOptions.GetOIDCOptions().EvaluateTarget("") // TODO(hs): determine if required by Wire
if err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'")
}
case acme.WireDevice:
wireID, err := wire.ParseDeviceID(az.Identifier.Value)
if err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing WireDevice")
}
clientID, err := wire.ParseClientID(wireID.ClientID)
if err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "failed parsing ClientID")
}
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return acme.WrapErrorISE(err, "failed getting Wire options")
}
target, err = wireOptions.GetDPOPOptions().EvaluateTarget(clientID.DeviceID)
if err != nil {
return acme.WrapError(acme.ErrorMalformedType, err, "invalid Go template registered for 'target'")
}
}
ch := &acme.Challenge{
AccountID: az.AccountID,
Value: az.Identifier.Value,
Type: typ,
Token: az.Token,
Status: acme.StatusPending,
Target: target,
}
if err := db.CreateChallenge(ctx, ch); err != nil {
return acme.WrapErrorISE(err, "error creating challenge")
@ -399,6 +502,10 @@ func challengeTypes(az *acme.Authorization) []acme.ChallengeType {
}
case acme.PermanentIdentifier:
chTypes = []acme.ChallengeType{acme.DEVICEATTEST01}
case acme.WireUser:
chTypes = []acme.ChallengeType{acme.WIREOIDC01}
case acme.WireDevice:
chTypes = []acme.ChallengeType{acme.WIREDPOP01}
default:
chTypes = []acme.ChallengeType{}
}

@ -24,6 +24,10 @@ import (
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/authority/provisioner/wire"
sassert "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewOrderRequest_Validate(t *testing.T) {
@ -80,7 +84,7 @@ func TestNewOrderRequest_Validate(t *testing.T) {
err: acme.NewError(acme.ErrorMalformedType, "invalid DNS name: *.example.com:8080"),
}
},
"fail/bad-ip": func(t *testing.T) test {
"fail/bad-identifier/ip": func(t *testing.T) test {
nbf := time.Now().UTC().Add(time.Minute)
naf := time.Now().UTC().Add(5 * time.Minute)
return test{
@ -96,6 +100,39 @@ func TestNewOrderRequest_Validate(t *testing.T) {
err: acme.NewError(acme.ErrorMalformedType, "invalid IP address: %s", "192.168.42.1000"),
}
},
"fail/bad-identifier/wireapp-invalid-uri": func(t *testing.T) test {
return test{
nor: &NewOrderRequest{
Identifiers: []acme.Identifier{
{Type: "wireapp-user", Value: `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`},
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "example.com", "handle": "wireapp://%40alice.smith.qa@example.com"}`},
},
},
err: acme.NewError(acme.ErrorMalformedType, `failed validating Wire identifiers: invalid Wire client ID "example.com": invalid Wire client ID scheme ""; expected "wireapp"`),
}
},
"fail/bad-identifier/wireapp-wrong-scheme": func(t *testing.T) test {
return test{
nor: &NewOrderRequest{
Identifiers: []acme.Identifier{
{Type: "wireapp-user", Value: `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.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, `failed validating Wire identifiers: invalid Wire client ID "nowireapp://example.com": invalid Wire client ID scheme "nowireapp"; expected "wireapp"`),
}
},
"fail/bad-identifier/wireapp-invalid-user-parts": func(t *testing.T) test {
return test{
nor: &NewOrderRequest{
Identifiers: []acme.Identifier{
{Type: "wireapp-user", Value: `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.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, `failed validating Wire identifiers: invalid Wire client ID "wireapp://user-device@example.com": invalid Wire client ID username "user-device"`),
}
},
"ok": func(t *testing.T) test {
nbf := time.Now().UTC().Add(time.Minute)
naf := time.Now().UTC().Add(5 * time.Minute)
@ -174,34 +211,50 @@ func TestNewOrderRequest_Validate(t *testing.T) {
naf: naf,
}
},
"ok/wireapp": 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-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"}`},
},
NotAfter: naf,
NotBefore: nbf,
},
nbf: nbf,
naf: naf,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
if err := tc.nor.Validate(); err != nil {
if assert.NotNil(t, err) {
var ae *acme.Error
if assert.True(t, errors.As(err, &ae)) {
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
err := tc.nor.Validate()
if tc.err != nil {
assert.Error(t, err)
var ae *acme.Error
if assert.True(t, errors.As(err, &ae)) {
assert.HasPrefix(t, ae.Error(), tc.err.Error())
assert.Equals(t, ae.StatusCode(), tc.err.StatusCode())
assert.Equals(t, ae.Type, tc.err.Type)
}
return
}
assert.NoError(t, err)
if tc.nbf.IsZero() {
assert.True(t, tc.nor.NotBefore.Before(time.Now().Add(time.Minute)))
assert.True(t, tc.nor.NotBefore.After(time.Now().Add(-time.Minute)))
} else {
if assert.Nil(t, tc.err) {
if tc.nbf.IsZero() {
assert.True(t, tc.nor.NotBefore.Before(time.Now().Add(time.Minute)))
assert.True(t, tc.nor.NotBefore.After(time.Now().Add(-time.Minute)))
} else {
assert.Equals(t, tc.nor.NotBefore, tc.nbf)
}
if tc.naf.IsZero() {
assert.True(t, tc.nor.NotAfter.Before(time.Now().Add(24*time.Hour)))
assert.True(t, tc.nor.NotAfter.After(time.Now().Add(24*time.Hour-time.Minute)))
} else {
assert.Equals(t, tc.nor.NotAfter, tc.naf)
}
}
assert.Equals(t, tc.nor.NotBefore, tc.nbf)
}
if tc.naf.IsZero() {
assert.True(t, tc.nor.NotAfter.Before(time.Now().Add(24*time.Hour)))
assert.True(t, tc.nor.NotAfter.After(time.Now().Add(24*time.Hour-time.Minute)))
} else {
assert.Equals(t, tc.nor.NotAfter, tc.naf)
}
})
}
@ -503,6 +556,37 @@ func TestHandler_GetOrder(t *testing.T) {
func TestHandler_newAuthorization(t *testing.T) {
defaultProvisioner := newProv()
fakeKey := `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`
wireProvisioner := newWireProvisionerWithOptions(t, &provisioner.Options{
Wire: &wire.Options{
OIDC: &wire.OIDCOptions{
Provider: &wire.Provider{
IssuerURL: "https://issuer.example.com",
Algorithms: []string{"ES256"},
},
Config: &wire.Config{
ClientID: "test",
SignatureAlgorithms: []string{"ES256"},
Now: time.Now,
},
TransformTemplate: "",
},
DPOP: &wire.DPOPOptions{
SigningKey: []byte(fakeKey),
},
},
})
wireProvisionerFailOptions := &provisioner.ACME{
Type: "ACME",
Name: "test@acme-<test>provisioner.com",
Options: &provisioner.Options{},
Challenges: []provisioner.ACMEChallenge{
provisioner.WIREOIDC_01,
provisioner.WIREDPOP_01,
},
}
type test struct {
az *acme.Authorization
prov acme.Provisioner
@ -530,8 +614,13 @@ func TestHandler_newAuthorization(t *testing.T) {
return errors.New("force")
},
},
az: az,
err: acme.NewErrorISE("error creating challenge: force"),
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:serverInternal",
Err: errors.New("error creating challenge: force"),
Detail: "The server experienced an internal error",
Status: 500,
},
}
},
"fail/error-db.CreateAuthorization": func(t *testing.T) test {
@ -585,8 +674,101 @@ func TestHandler_newAuthorization(t *testing.T) {
return errors.New("force")
},
},
az: az,
err: acme.NewErrorISE("error creating authorization: force"),
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:serverInternal",
Err: errors.New("error creating authorization: force"),
Detail: "The server experienced an internal error",
Status: 500,
},
}
},
"fail/wireapp-user-options": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-user",
Value: "wireapp://%40alice.smith.qa@example.com",
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
return test{
prov: wireProvisionerFailOptions,
db: &acme.MockDB{},
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:serverInternal",
Err: errors.New("failed getting Wire options: no Wire options available"),
Detail: "The server experienced an internal error",
Status: 500,
},
}
},
"fail/wireapp-device-parse-id": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-device",
Value: `{"name}`,
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
return test{
prov: wireProvisioner,
db: &acme.MockDB{},
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:malformed",
Err: errors.New("failed parsing WireDevice: unexpected end of JSON input"),
Detail: "The request message was malformed",
Status: 400,
},
}
},
"fail/wireapp-device-parse-client-id": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-device",
Value: `{"name": "device", "domain": "wire.com", "client-id": "CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`,
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
return test{
prov: wireProvisioner,
db: &acme.MockDB{},
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:malformed",
Err: errors.New(`failed parsing ClientID: invalid Wire client ID scheme ""; expected "wireapp"`),
Detail: "The request message was malformed",
Status: 400,
},
}
},
"fail/wireapp-device-options": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-device",
Value: `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`,
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
return test{
prov: wireProvisionerFailOptions,
db: &acme.MockDB{},
az: az,
err: &acme.Error{
Type: "urn:ietf:params:acme:error:serverInternal",
Err: errors.New("failed getting Wire options: no Wire options available"),
Detail: "The server experienced an internal error",
Status: 500,
},
}
},
"ok/no-wildcard": func(t *testing.T) test {
@ -755,33 +937,121 @@ func TestHandler_newAuthorization(t *testing.T) {
az: az,
}
},
"ok/wireapp-user": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-user",
Value: "wireapp://%40alice.smith.qa@example.com",
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
count := 0
var ch1 **acme.Challenge
return test{
prov: wireProvisioner,
db: &acme.MockDB{
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
switch count {
case 0:
ch.ID = "wireapp-user"
assert.Equals(t, ch.Type, acme.WIREOIDC01)
ch1 = &ch
default:
assert.FatalError(t, errors.New("test logic error"))
return errors.New("force")
}
count++
assert.Equals(t, ch.AccountID, az.AccountID)
assert.Equals(t, ch.Token, az.Token)
assert.Equals(t, ch.Status, acme.StatusPending)
assert.Equals(t, ch.Value, az.Identifier.Value)
return nil
},
MockCreateAuthorization: func(ctx context.Context, _az *acme.Authorization) error {
assert.Equals(t, _az.AccountID, az.AccountID)
assert.Equals(t, _az.Token, az.Token)
assert.Equals(t, _az.Status, acme.StatusPending)
assert.Equals(t, _az.Identifier, az.Identifier)
assert.Equals(t, _az.ExpiresAt, az.ExpiresAt)
_ = ch1
// assert.Equals(t, _az.Challenges, []*acme.Challenge{*ch1})
assert.Equals(t, _az.Wildcard, false)
return nil
},
},
az: az,
}
},
"ok/wireapp-device": func(t *testing.T) test {
az := &acme.Authorization{
AccountID: "accID",
Identifier: acme.Identifier{
Type: "wireapp-device",
Value: `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`,
},
Status: acme.StatusPending,
ExpiresAt: clock.Now(),
}
count := 0
var ch1 **acme.Challenge
return test{
prov: wireProvisioner,
db: &acme.MockDB{
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
switch count {
case 0:
ch.ID = "wireapp-device"
assert.Equals(t, ch.Type, acme.WIREDPOP01)
ch1 = &ch
default:
assert.FatalError(t, errors.New("test logic error"))
return errors.New("force")
}
count++
assert.Equals(t, ch.AccountID, az.AccountID)
assert.Equals(t, ch.Token, az.Token)
assert.Equals(t, ch.Status, acme.StatusPending)
assert.Equals(t, ch.Value, az.Identifier.Value)
return nil
},
MockCreateAuthorization: func(ctx context.Context, _az *acme.Authorization) error {
assert.Equals(t, _az.AccountID, az.AccountID)
assert.Equals(t, _az.Token, az.Token)
assert.Equals(t, _az.Status, acme.StatusPending)
assert.Equals(t, _az.Identifier, az.Identifier)
assert.Equals(t, _az.ExpiresAt, az.ExpiresAt)
_ = ch1
// assert.Equals(t, _az.Challenges, []*acme.Challenge{*ch1})
assert.Equals(t, _az.Wildcard, false)
return nil
},
},
az: az,
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
if name == "ok/permanent-identifier-enabled" {
println(1)
}
tc := run(t)
ctx := newBaseContext(context.Background(), tc.db)
ctx = acme.NewProvisionerContext(ctx, tc.prov)
if err := newAuthorization(ctx, tc.az); err != nil {
if assert.NotNil(t, tc.err) {
var k *acme.Error
if assert.True(t, errors.As(err, &k)) {
assert.Equals(t, k.Type, tc.err.Type)
assert.Equals(t, k.Detail, tc.err.Detail)
assert.Equals(t, k.Status, tc.err.Status)
assert.Equals(t, k.Err.Error(), tc.err.Err.Error())
assert.Equals(t, k.Detail, tc.err.Detail)
} else {
assert.FatalError(t, errors.New("unexpected error type"))
}
err := newAuthorization(ctx, tc.az)
if tc.err != nil {
sassert.Error(t, err)
var k *acme.Error
if sassert.True(t, errors.As(err, &k)) {
sassert.Equal(t, tc.err.Type, k.Type)
sassert.Equal(t, tc.err.Detail, k.Detail)
sassert.Equal(t, tc.err.Status, k.Status)
sassert.EqualError(t, k.Err, tc.err.Error())
}
} else {
assert.Nil(t, tc.err)
return
}
})
sassert.NoError(t, err)
})
}
}
@ -793,6 +1063,10 @@ func TestHandler_NewOrder(t *testing.T) {
u := fmt.Sprintf("%s/acme/%s/order/ordID",
baseURL.String(), escProvName)
fakeWireSigningKey := `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`
type test struct {
ca acme.CertificateAuthority
db acme.DB
@ -1623,6 +1897,141 @@ func TestHandler_NewOrder(t *testing.T) {
},
}
},
"ok/default-naf-nbf-wireapp": 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-user", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice_wire@wire.com"}`},
{Type: "wireapp-device", Value: `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice_wire@wire.com"}`},
},
}
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, ch2 **acme.Challenge
az1ID, az2ID *string
chCount, azCount = 0, 0
)
return test{
ctx: ctx,
statusCode: 201,
nor: nor,
ca: &mockCA{},
db: &acme.MockDB{
MockCreateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
switch chCount {
case 0:
assert.Equals(t, ch.Type, acme.WIREOIDC01)
assert.Equals(t, ch.Value, `{"name": "Smith, Alice M (QA)", "domain": "example.com", "handle": "wireapp://%40alice_wire@wire.com"}`)
ch.ID = "wireapp-oidc"
ch1 = &ch
case 1:
assert.Equals(t, ch.Type, acme.WIREDPOP01)
assert.Equals(t, ch.Value, `{"name": "Smith, Alice M (QA)", "domain": "example.com", "client-id": "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com", "handle": "wireapp://%40alice_wire@wire.com"}`)
ch.ID = "wireapp-dpop"
ch2 = &ch
default:
require.Fail(t, "test logic error")
}
chCount++
assert.Equals(t, ch.AccountID, "accID")
assert.NotEquals(t, ch.Token, "")
assert.Equals(t, ch.Status, acme.StatusPending)
_, _ = ch1, ch2
return nil
},
MockCreateAuthorization: func(ctx context.Context, az *acme.Authorization) error {
switch azCount {
case 0:
az.ID = "az1ID"
az1ID = &az.ID
assert.Equals(t, az.Identifier, nor.Identifiers[0])
assert.Equals(t, az.Challenges, []*acme.Challenge{*ch1})
case 1:
az.ID = "az2ID"
az2ID = &az.ID
assert.Equals(t, az.Identifier, nor.Identifiers[1])
assert.Equals(t, az.Challenges, []*acme.Challenge{*ch2})
default:
require.Fail(t, "test logic error")
}
azCount++
assert.Equals(t, az.AccountID, "accID")
assert.NotEquals(t, az.Token, "")
assert.Equals(t, az.Status, acme.StatusPending)
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, *az2ID})
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),
fmt.Sprintf("%s/acme/%s/authz/az2ID", 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/naf-nbf": func(t *testing.T) test {
now := clock.Now()
expNbf := now.Add(5 * time.Minute)

@ -0,0 +1,615 @@
package api
import (
"bytes"
"context"
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
"time"
"github.com/go-chi/chi/v5"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/certificates/acme/db/nosql"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/authority/provisioner/wire"
nosqlDB "github.com/smallstep/nosql"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
)
const (
baseURL = "test.ca.smallstep.com"
linkerPrefix = "acme"
)
func newWireProvisionerWithOptions(t *testing.T, options *provisioner.Options) *provisioner.ACME {
t.Helper()
prov := &provisioner.ACME{
Type: "ACME",
Name: "test@acme-<test>provisioner.com",
Options: options,
Challenges: []provisioner.ACMEChallenge{
provisioner.WIREOIDC_01,
provisioner.WIREDPOP_01,
},
}
err := prov.Init(provisioner.Config{
Claims: config.GlobalProvisionerClaims,
})
require.NoError(t, err)
return prov
}
// TODO(hs): replace with test CA server + acmez based test client for
// more realistic integration test?
func TestWireIntegration(t *testing.T) {
accessTokenSignerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
accessTokenSignerPEMBlock, err := pemutil.Serialize(accessTokenSignerJWK.Public().Key)
require.NoError(t, err)
accessTokenSignerPEMBytes := pem.EncodeToMemory(accessTokenSignerPEMBlock)
accessTokenSigner, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(accessTokenSignerJWK.Algorithm),
Key: accessTokenSignerJWK,
}, new(jose.SignerOptions))
require.NoError(t, err)
oidcTokenSignerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
oidcTokenSigner, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(oidcTokenSignerJWK.Algorithm),
Key: oidcTokenSignerJWK,
}, new(jose.SignerOptions))
require.NoError(t, err)
prov := newWireProvisionerWithOptions(t, &provisioner.Options{
X509: &provisioner.X509Options{
Template: `{
"subject": {
"organization": "WireTest",
"commonName": {{ toJson .Oidc.name }}
},
"uris": [{{ toJson .Oidc.preferred_username }}, {{ toJson .Dpop.sub }}],
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["clientAuth"]
}`,
},
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, // NOTE: this skips actual token verification
Now: time.Now,
},
TransformTemplate: "",
},
DPOP: &wire.DPOPOptions{
SigningKey: accessTokenSignerPEMBytes,
},
},
})
// mock provisioner and linker
ctx := context.Background()
ctx = acme.NewProvisionerContext(ctx, prov)
ctx = acme.NewLinkerContext(ctx, acme.NewLinker(baseURL, linkerPrefix))
// create temporary BoltDB file
file, err := os.CreateTemp(os.TempDir(), "integration-db-")
require.NoError(t, err)
t.Log("database file name:", file.Name())
dbFn := file.Name()
err = file.Close()
require.NoError(t, err)
// open BoltDB
rawDB, err := nosqlDB.New(nosqlDB.BBoltDriver, dbFn)
require.NoError(t, err)
// create tables
db, err := nosql.New(rawDB)
require.NoError(t, err)
// make DB available to handlers
ctx = acme.NewDatabaseContext(ctx, db)
// simulate signed payloads by making the signing key available in ctx
jwk, err := jose.GenerateJWK("OKP", "", "EdDSA", "sig", "", 0)
require.NoError(t, err)
ed25519PrivKey, ok := jwk.Key.(ed25519.PrivateKey)
require.True(t, ok)
dpopSigner, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(jwk.Algorithm),
Key: jwk,
}, new(jose.SignerOptions))
require.NoError(t, err)
ed25519PubKey, ok := ed25519PrivKey.Public().(ed25519.PublicKey)
require.True(t, ok)
jwk.Key = ed25519PubKey
ctx = context.WithValue(ctx, jwkContextKey, jwk)
// get directory
dir := func(ctx context.Context) (dir Directory) {
req := httptest.NewRequest(http.MethodGet, "/foo/bar", http.NoBody)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
GetDirectory(w, req)
res := w.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &dir)
require.NoError(t, err)
return
}(ctx)
t.Log("directory:", dir)
// get nonce
nonce := func(ctx context.Context) (nonce string) {
req := httptest.NewRequest(http.MethodGet, dir.NewNonce, http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
addNonce(GetNonce)(w, req)
res := w.Result()
require.Equal(t, http.StatusNoContent, res.StatusCode)
nonce = res.Header["Replay-Nonce"][0]
return
}(ctx)
t.Log("nonce:", nonce)
// create new account
acc := func(ctx context.Context) (acc *acme.Account) {
// create payload
nar := &NewAccountRequest{
Contact: []string{"foo", "bar"},
}
rawNar, err := json.Marshal(nar)
require.NoError(t, err)
// create account
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: rawNar})
req := httptest.NewRequest(http.MethodGet, dir.NewAccount, http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
NewAccount(w, req)
res := w.Result()
require.Equal(t, http.StatusCreated, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &acc)
require.NoError(t, err)
locationParts := strings.Split(res.Header["Location"][0], "/")
acc, err = db.GetAccount(ctx, locationParts[len(locationParts)-1])
require.NoError(t, err)
return
}(ctx)
ctx = context.WithValue(ctx, accContextKey, acc)
t.Log("account ID:", acc.ID)
// new order
order := func(ctx context.Context) (order *acme.Order) {
mockMustAuthority(t, &mockCA{})
nor := &NewOrderRequest{
Identifiers: []acme.Identifier{
{
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"}`,
},
},
}
b, err := json.Marshal(nor)
require.NoError(t, err)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
req := httptest.NewRequest("POST", "https://random.local/", http.NoBody)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
NewOrder(w, req)
res := w.Result()
require.Equal(t, http.StatusCreated, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &order)
require.NoError(t, err)
order, err = db.GetOrder(ctx, order.ID)
require.NoError(t, err)
return
}(ctx)
t.Log("authzs IDs:", order.AuthorizationIDs)
// get authorization
getAuthz := func(ctx context.Context, authzID string) (az *acme.Authorization) {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("authzID", authzID)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
GetAuthorization(w, req)
res := w.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &az)
require.NoError(t, err)
az, err = db.GetAuthorization(ctx, authzID)
require.NoError(t, err)
return
}
var azs []*acme.Authorization
for _, azID := range order.AuthorizationIDs {
az := getAuthz(ctx, azID)
azs = append(azs, az)
for _, challenge := range az.Challenges {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("chID", challenge.ID)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
var payload []byte
switch challenge.Type {
case acme.WIREDPOP01:
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://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)
require.NoError(t, err)
proof, err := dpop.CompactSerialize()
require.NoError(t, err)
tokenBytes, err := json.Marshal(struct {
jose.Claims
Challenge string `json:"chal,omitempty"`
Cnf struct {
Kid string `json:"kid,omitempty"`
} `json:"cnf"`
Proof string `json:"proof,omitempty"`
ClientID string `json:"client_id"`
APIVersion int `json:"api_version"`
Scope string `json:"scope"`
}{
Claims: jose.Claims{
Issuer: "http://issuer.example.com",
Audience: []string{"test"},
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
},
Challenge: "token",
Cnf: struct {
Kid string `json:"kid,omitempty"`
}{
Kid: jwk.KeyID,
},
Proof: proof,
ClientID: "wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com",
APIVersion: 5,
Scope: "wire_client_id",
})
require.NoError(t, err)
signed, err := accessTokenSigner.Sign(tokenBytes)
require.NoError(t, err)
accessToken, err := signed.CompactSerialize()
require.NoError(t, err)
p, err := json.Marshal(struct {
AccessToken string `json:"access_token"`
}{
AccessToken: accessToken,
})
require.NoError(t, err)
payload = p
case acme.WIREOIDC01:
keyAuth, err := acme.KeyAuthorization("token", jwk)
require.NoError(t, err)
tokenBytes, err := json.Marshal(struct {
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",
Audience: []string{"test"},
Expiry: jose.NewNumericDate(time.Now().Add(1 * time.Minute)),
},
Name: "Alice Smith",
PreferredUsername: "wireapp://%40alice_wire@wire.com",
KeyAuth: keyAuth,
})
require.NoError(t, err)
signed, err := oidcTokenSigner.Sign(tokenBytes)
require.NoError(t, err)
idToken, err := signed.CompactSerialize()
require.NoError(t, err)
p, err := json.Marshal(struct {
IDToken string `json:"id_token"`
}{
IDToken: idToken,
})
require.NoError(t, err)
payload = p
default:
require.Fail(t, "unexpected challenge payload type")
}
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: payload})
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
GetChallenge(w, req)
res := w.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close() //nolint:gocritic // close the body
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &challenge)
require.NoError(t, err)
t.Log("challenge:", challenge.ID, challenge.Status)
}
}
// get/validate challenge simulation
updateAz := func(ctx context.Context, az *acme.Authorization) (updatedAz *acme.Authorization) {
now := clock.Now().Format(time.RFC3339)
for _, challenge := range az.Challenges {
challenge.Status = acme.StatusValid
challenge.ValidatedAt = now
err := db.UpdateChallenge(ctx, challenge)
if err != nil {
t.Error("updating challenge", challenge.ID, ":", err)
}
}
updatedAz, err = db.GetAuthorization(ctx, az.ID)
require.NoError(t, err)
return
}
for _, az := range azs {
updatedAz := updateAz(ctx, az)
for _, challenge := range updatedAz.Challenges {
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)", "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"})
require.NoError(t, err)
default:
require.Fail(t, "unexpected challenge type")
}
}
}
// get order
updatedOrder := func(ctx context.Context) (updatedOrder *acme.Order) {
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("ordID", order.ID)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
GetOrder(w, req)
res := w.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &updatedOrder)
require.NoError(t, err)
require.Equal(t, acme.StatusReady, updatedOrder.Status)
return
}(ctx)
t.Log("updated order status:", updatedOrder.Status)
// finalize order
finalizedOrder := func(ctx context.Context) (finalizedOrder *acme.Order) {
ca, err := minica.New(minica.WithName("WireTestCA"))
require.NoError(t, err)
mockMustAuthority(t, &mockCASigner{
signer: func(csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
var (
certOptions []x509util.Option
)
for _, op := range extraOpts {
if k, ok := op.(provisioner.CertificateOptions); ok {
certOptions = append(certOptions, k.Options(signOpts)...)
}
}
x509utilTemplate, err := x509util.NewCertificate(csr, certOptions...)
require.NoError(t, err)
template := x509utilTemplate.GetCertificate()
require.NotNil(t, template)
cert, err := ca.Sign(template)
require.NoError(t, err)
u1, err := url.Parse("wireapp://%40alice.smith.qa@example.com")
require.NoError(t, err)
u2, err := url.Parse("wireapp://lJGYPz0ZRq2kvc_XpdaDlA%21ed416ce8ecdd9fad@example.com")
require.NoError(t, err)
assert.Equal(t, []*url.URL{u1, u2}, cert.URIs)
assert.Equal(t, "Smith, Alice M (QA)", cert.Subject.CommonName)
return []*x509.Certificate{cert, ca.Intermediate}, nil
},
})
qUserID, err := url.Parse("wireapp://lJGYPz0ZRq2kvc_XpdaDlA!ed416ce8ecdd9fad@example.com")
require.NoError(t, err)
qUserName, err := url.Parse("wireapp://%40alice.smith.qa@example.com")
require.NoError(t, err)
_, priv, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)
csrTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{"example.com"},
ExtraNames: []pkix.AttributeTypeAndValue{
{
Type: asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 3, 1, 241},
Value: "Smith, Alice M (QA)",
},
},
},
URIs: []*url.URL{
qUserName,
qUserID,
},
SignatureAlgorithm: x509.PureEd25519,
}
csr, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, priv)
require.NoError(t, err)
fr := FinalizeRequest{CSR: base64.RawURLEncoding.EncodeToString(csr)}
frRaw, err := json.Marshal(fr)
require.NoError(t, err)
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: frRaw})
chiCtx := chi.NewRouteContext()
chiCtx.URLParams.Add("ordID", order.ID)
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
req := httptest.NewRequest(http.MethodGet, "https://random.local/", http.NoBody).WithContext(ctx)
w := httptest.NewRecorder()
FinalizeOrder(w, req)
res := w.Result()
require.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
require.NoError(t, err)
err = json.Unmarshal(bytes.TrimSpace(body), &finalizedOrder)
require.NoError(t, err)
require.Equal(t, acme.StatusValid, finalizedOrder.Status)
finalizedOrder, err = db.GetOrder(ctx, order.ID)
require.NoError(t, err)
return
}(ctx)
t.Log("finalized order status:", finalizedOrder.Status)
}
type mockCASigner struct {
signer func(*x509.CertificateRequest, provisioner.SignOptions, ...provisioner.SignOption) ([]*x509.Certificate, error)
}
func (m *mockCASigner) SignWithContext(_ context.Context, cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error) {
if m.signer == nil {
return nil, errors.New("unimplemented")
}
return m.signer(cr, opts, signOpts...)
}
func (m *mockCASigner) AreSANsAllowed(ctx context.Context, sans []string) error {
return nil
}
func (m *mockCASigner) IsRevoked(sn string) (bool, error) {
return false, nil
}
func (m *mockCASigner) Revoke(ctx context.Context, opts *authority.RevokeOptions) error {
return nil
}
func (m *mockCASigner) LoadProvisionerByName(string) (provisioner.Interface, error) {
return nil, nil
}

@ -25,18 +25,19 @@ import (
"strings"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/fxamacker/cbor/v2"
"github.com/google/go-tpm/legacy/tpm2"
"golang.org/x/exp/slices"
"github.com/smallstep/go-attestation/attest"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
"golang.org/x/exp/slices"
"github.com/smallstep/certificates/acme/wire"
"github.com/smallstep/certificates/authority/provisioner"
wireprovisioner "github.com/smallstep/certificates/authority/provisioner/wire"
)
type ChallengeType string
@ -50,6 +51,10 @@ const (
TLSALPN01 ChallengeType = "tls-alpn-01"
// DEVICEATTEST01 is the device-attest-01 ACME challenge type
DEVICEATTEST01 ChallengeType = "device-attest-01"
// WIREOIDC01 is the Wire OIDC challenge type
WIREOIDC01 ChallengeType = "wire-oidc-01"
// WIREDPOP01 is the Wire DPoP challenge type
WIREDPOP01 ChallengeType = "wire-dpop-01"
)
var (
@ -75,6 +80,7 @@ type Challenge struct {
Token string `json:"token"`
ValidatedAt string `json:"validated,omitempty"`
URL string `json:"url"`
Target string `json:"target,omitempty"`
Error *Error `json:"error,omitempty"`
}
@ -104,8 +110,12 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey,
return tlsalpn01Validate(ctx, ch, db, jwk)
case DEVICEATTEST01:
return deviceAttest01Validate(ctx, ch, db, jwk, payload)
case WIREOIDC01:
return wireOIDC01Validate(ctx, ch, db, jwk, payload)
case WIREDPOP01:
return wireDPOP01Validate(ctx, ch, db, jwk, payload)
default:
return NewErrorISE("unexpected challenge type '%s'", ch.Type)
return NewErrorISE("unexpected challenge type %q", ch.Type)
}
}
@ -342,6 +352,387 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK
return nil
}
type wireOidcPayload struct {
// IDToken contains the OIDC identity token
IDToken string `json:"id_token"`
}
func wireOIDC01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, payload []byte) error {
prov, ok := ProvisionerFromContext(ctx)
if !ok {
return NewErrorISE("missing provisioner")
}
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return WrapErrorISE(err, "failed getting Wire options")
}
linker, ok := LinkerFromContext(ctx)
if !ok {
return NewErrorISE("missing linker")
}
var oidcPayload wireOidcPayload
if err := json.Unmarshal(payload, &oidcPayload); err != nil {
return WrapError(ErrorMalformedType, err, "error unmarshalling Wire OIDC challenge payload")
}
wireID, err := wire.ParseUserID(ch.Value)
if err != nil {
return WrapErrorISE(err, "error unmarshalling challenge data")
}
oidcOptions := wireOptions.GetOIDCOptions()
verifier, err := oidcOptions.GetVerifier(ctx)
if err != nil {
return WrapErrorISE(err, "no OIDC verifier available")
}
idToken, err := verifier.Verify(ctx, oidcPayload.IDToken)
if err != nil {
return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err,
"error verifying ID token signature"))
}
var claims struct {
Name string `json:"preferred_username,omitempty"`
Handle string `json:"name"`
Issuer string `json:"iss,omitempty"`
GivenName string `json:"given_name,omitempty"`
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"))
}
// Update and store the challenge.
ch.Status = StatusValid
ch.Error = nil
ch.ValidatedAt = clock.Now().Format(time.RFC3339)
if err = db.UpdateChallenge(ctx, ch); err != nil {
return WrapErrorISE(err, "error updating challenge")
}
orders, err := db.GetAllOrdersByAccountID(ctx, ch.AccountID)
if err != nil {
return WrapErrorISE(err, "could not retrieve current order by account id")
}
if len(orders) == 0 {
return NewErrorISE("there are not enough orders for this account for this custom OIDC challenge")
}
order := orders[len(orders)-1]
if err := db.CreateOidcToken(ctx, order, transformedIDToken); err != nil {
return WrapErrorISE(err, "failed storing OIDC id token")
}
return nil
}
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)
}
transformed, err := o.Transform(m)
if err != nil {
return nil, fmt.Errorf("failed transforming OIDC ID token: %w", err)
}
name, ok := transformed["name"]
if !ok {
return nil, fmt.Errorf("transformed OIDC ID token does not contain 'name'")
}
if wireID.Name != name {
return nil, fmt.Errorf("invalid 'name' %q after transformation", name)
}
preferredUsername, ok := transformed["preferred_username"]
if !ok {
return nil, fmt.Errorf("transformed OIDC ID token does not contain 'preferred_username'")
}
if wireID.Handle != preferredUsername {
return nil, fmt.Errorf("invalid 'preferred_username' %q after transformation", preferredUsername)
}
return transformed, nil
}
type wireDpopPayload struct {
// AccessToken is the token generated by wire-server
AccessToken string `json:"access_token"`
}
func wireDPOP01Validate(ctx context.Context, ch *Challenge, db DB, accountJWK *jose.JSONWebKey, payload []byte) error {
prov, ok := ProvisionerFromContext(ctx)
if !ok {
return NewErrorISE("missing provisioner")
}
wireOptions, err := prov.GetOptions().GetWireOptions()
if err != nil {
return WrapErrorISE(err, "failed getting Wire options")
}
linker, ok := LinkerFromContext(ctx)
if !ok {
return NewErrorISE("missing linker")
}
var dpopPayload wireDpopPayload
if err := json.Unmarshal(payload, &dpopPayload); err != nil {
return WrapError(ErrorMalformedType, err, "error unmarshalling Wire DPoP challenge payload")
}
wireID, err := wire.ParseDeviceID(ch.Value)
if err != nil {
return WrapErrorISE(err, "error unmarshalling challenge data")
}
clientID, err := wire.ParseClientID(wireID.ClientID)
if err != nil {
return WrapErrorISE(err, "error parsing device id")
}
dpopOptions := wireOptions.GetDPOPOptions()
issuer, err := dpopOptions.EvaluateTarget(clientID.DeviceID)
if err != nil {
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(),
}
_, dpop, err := parseAndVerifyWireAccessToken(params)
if err != nil {
return storeError(ctx, db, ch, true, WrapError(ErrorRejectedIdentifierType, err,
"failed validating Wire access token"))
}
// Update and store the challenge.
ch.Status = StatusValid
ch.Error = nil
ch.ValidatedAt = clock.Now().Format(time.RFC3339)
if err = db.UpdateChallenge(ctx, ch); err != nil {
return WrapErrorISE(err, "error updating challenge")
}
orders, err := db.GetAllOrdersByAccountID(ctx, ch.AccountID)
if err != nil {
return WrapErrorISE(err, "could not find current order by account id")
}
if len(orders) == 0 {
return NewErrorISE("there are not enough orders for this account for this custom OIDC challenge")
}
order := orders[len(orders)-1]
if err := db.CreateDpopToken(ctx, order, map[string]any(*dpop)); err != nil {
return WrapErrorISE(err, "failed storing DPoP token")
}
return nil
}
type wireCnf struct {
Kid string `json:"kid"`
}
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"`
APIVersion int `json:"api_version"`
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 {
token string
tokenKey crypto.PublicKey
dpopKey crypto.PublicKey
dpopKeyID string
issuer string
audience string
wireID wire.DeviceID
chToken string
t time.Time
}
func parseAndVerifyWireAccessToken(v wireVerifyParams) (*wireAccessToken, *wireDpopToken, error) {
jwt, err := jose.ParseSigned(v.token)
if err != nil {
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)
}
if err := accessToken.ValidateWithLeeway(jose.Expected{
Time: v.t,
Issuer: v.issuer,
Audience: jose.Audience{v.audience},
}, 1*time.Minute); err != nil {
return nil, nil, fmt.Errorf("failed validation: %w", err)
}
if accessToken.Challenge == "" {
return nil, nil, errors.New("access token challenge 'chal' 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)) {
return nil, nil, fmt.Errorf("token expiry '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 'chal' %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)
}
challenge, ok := dpopToken["chal"].(string)
if !ok {
return nil, nil, fmt.Errorf("invalid challenge 'chal' in Wire DPoP token")
}
if challenge == "" || challenge != v.chToken {
return nil, nil, fmt.Errorf("invalid Wire DPoP challenge 'chal' %q", challenge)
}
handle, ok := dpopToken["handle"].(string)
if !ok {
return nil, nil, fmt.Errorf("invalid 'handle' in Wire DPoP token")
}
if handle == "" || handle != v.wireID.Handle {
return nil, nil, fmt.Errorf("invalid Wire client 'handle' %q", handle)
}
name, ok := dpopToken["name"].(string)
if !ok {
return nil, nil, fmt.Errorf("invalid display 'name' in Wire DPoP token")
}
if name == "" || name != v.wireID.Name {
return nil, nil, fmt.Errorf("invalid Wire client display 'name' %q", name)
}
return &accessToken, &dpopToken, nil
}
type payloadType struct {
AttObj string `json:"attObj"`
Error string `json:"error"`

@ -33,11 +33,13 @@ import (
"github.com/fxamacker/cbor/v2"
"github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner"
wireprovisioner "github.com/smallstep/certificates/authority/provisioner/wire"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/minica"
"go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
)
@ -196,6 +198,25 @@ func mustAttestYubikey(t *testing.T, _, keyAuthorization string, serial int) ([]
return payload, leaf, ca.Root
}
func newWireProvisionerWithOptions(t *testing.T, options *provisioner.Options) *provisioner.ACME {
t.Helper()
prov := &provisioner.ACME{
Type: "ACME",
Name: "wire",
Options: options,
Challenges: []provisioner.ACMEChallenge{
provisioner.WIREOIDC_01,
provisioner.WIREDPOP_01,
},
}
if err := prov.Init(provisioner.Config{
Claims: config.GlobalProvisionerClaims,
}); err != nil {
t.Fatal(err)
}
return prov
}
func Test_storeError(t *testing.T) {
type test struct {
ch *Challenge
@ -396,6 +417,9 @@ func TestKeyAuthorization(t *testing.T) {
}
func TestChallenge_Validate(t *testing.T) {
fakeKey := `-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`
type test struct {
ch *Challenge
vc Client
@ -430,7 +454,7 @@ func TestChallenge_Validate(t *testing.T) {
}
return test{
ch: ch,
err: NewErrorISE("unexpected challenge type 'foo'"),
err: NewErrorISE(`unexpected challenge type "foo"`),
}
},
"fail/http-01": func(t *testing.T) test {
@ -853,6 +877,263 @@ func TestChallenge_Validate(t *testing.T) {
},
}
},
"ok/wire-oidc-01": 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)
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,
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"`
}{
IDToken: idToken,
})
require.NoError(t, err)
valueBytes, err := json.Marshal(struct {
Name string `json:"name,omitempty"`
Domain string `json:"domain,omitempty"`
ClientID string `json:"client-id,omitempty"`
Handle string `json:"handle,omitempty"`
}{
Name: "Alice Smith",
Domain: "wire.com",
ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
Handle: "wireapp://%40alice_wire@wire.com",
})
require.NoError(t, err)
ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{
Wire: &wireprovisioner.Options{
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: srv.URL,
JWKSURL: srv.URL + "/keys",
Algorithms: []string{"ES256"},
},
Config: &wireprovisioner.Config{
ClientID: "test",
SignatureAlgorithms: []string{"ES256"},
Now: time.Now,
},
TransformTemplate: "",
},
DPOP: &wireprovisioner.DPOPOptions{
SigningKey: []byte(fakeKey),
},
},
}))
ctx = NewLinkerContext(ctx, NewLinker("ca.example.com", "acme"))
return test{
ch: &Challenge{
ID: "chID",
AuthorizationID: "azID",
AccountID: "accID",
Token: "token",
Type: "wire-oidc-01",
Status: StatusPending,
Value: string(valueBytes),
},
srv: srv,
payload: payload,
ctx: ctx,
jwk: jwk,
db: &MockDB{
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token)
assert.Equal(t, StatusValid, updch.Status)
assert.Equal(t, ChallengeType("wire-oidc-01"), updch.Type)
assert.Equal(t, string(valueBytes), updch.Value)
return nil
},
MockGetAllOrdersByAccountID: func(ctx context.Context, accountID string) ([]string, error) {
assert.Equal(t, "accID", accountID)
return []string{"orderID"}, nil
},
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["preferred_username"].(string))
return nil
},
},
}
},
"ok/wire-dpop-01": func(t *testing.T) test {
jwk, keyAuth := mustAccountAndKeyAuthorization(t, "token")
_ = keyAuth // TODO(hs): keyAuth (not) required for DPoP? Or needs to be added to validation?
dpopSigner, err := jose.NewSigner(jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(jwk.Algorithm),
Key: jwk,
}, new(jose.SignerOptions))
require.NoError(t, err)
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)
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"`
Name string `json:"name,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",
Name: "Alice Smith",
})
require.NoError(t, err)
dpop, err := dpopSigner.Sign(dpopBytes)
require.NoError(t, err)
proof, err := dpop.CompactSerialize()
require.NoError(t, err)
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"`
Proof string `json:"proof,omitempty"`
ClientID string `json:"client_id"`
APIVersion int `json:"api_version"`
Scope string `json:"scope"`
}{
Claims: jose.Claims{
Issuer: "http://issuer.example.com",
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"`
}{
Kid: jwk.KeyID,
},
Proof: proof,
ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
APIVersion: 5,
Scope: "wire_client_id",
})
require.NoError(t, err)
signed, err := signer.Sign(tokenBytes)
require.NoError(t, err)
accessToken, err := signed.CompactSerialize()
require.NoError(t, err)
payload, err := json.Marshal(struct {
AccessToken string `json:"access_token"`
}{
AccessToken: accessToken,
})
require.NoError(t, err)
valueBytes, err := json.Marshal(struct {
Name string `json:"name,omitempty"`
Domain string `json:"domain,omitempty"`
ClientID string `json:"client-id,omitempty"`
Handle string `json:"handle,omitempty"`
}{
Name: "Alice Smith",
Domain: "wire.com",
ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
Handle: "wireapp://%40alice_wire@wire.com",
})
require.NoError(t, err)
ctx := NewProvisionerContext(context.Background(), newWireProvisionerWithOptions(t, &provisioner.Options{
Wire: &wireprovisioner.Options{
OIDC: &wireprovisioner.OIDCOptions{
Provider: &wireprovisioner.Provider{
IssuerURL: "http://issuerexample.com",
Algorithms: []string{"ES256"},
},
Config: &wireprovisioner.Config{
ClientID: "test",
SignatureAlgorithms: []string{"ES256"},
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",
AuthorizationID: "azID",
AccountID: "accID",
Token: "token",
Type: "wire-dpop-01",
Status: StatusPending,
Value: string(valueBytes),
},
payload: payload,
ctx: ctx,
jwk: jwk,
db: &MockDB{
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
assert.Equal(t, "chID", updch.ID)
assert.Equal(t, "token", updch.Token)
assert.Equal(t, StatusValid, updch.Status)
assert.Equal(t, ChallengeType("wire-dpop-01"), updch.Type)
assert.Equal(t, string(valueBytes), updch.Value)
return nil
},
MockGetAllOrdersByAccountID: func(ctx context.Context, accountID string) ([]string, error) {
assert.Equal(t, "accID", accountID)
return []string{"orderID"}, nil
},
MockCreateDpopToken: func(ctx context.Context, orderID string, dpop map[string]interface{}) error {
assert.Equal(t, "orderID", orderID)
assert.Equal(t, "token", dpop["chal"].(string))
assert.Equal(t, "wireapp://%40alice_wire@wire.com", dpop["handle"].(string))
assert.Equal(t, "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", dpop["sub"].(string))
return nil
},
},
}
},
}
for name, run := range tests {
t.Run(name, func(t *testing.T) {
@ -867,25 +1148,63 @@ func TestChallenge_Validate(t *testing.T) {
ctx = context.Background()
}
ctx = NewClientContext(ctx, tc.vc)
if err := tc.ch.Validate(ctx, tc.db, tc.jwk, tc.payload); err != nil {
if assert.Error(t, tc.err) {
var k *Error
if errors.As(err, &k) {
assert.Equal(t, tc.err.Type, k.Type)
assert.Equal(t, tc.err.Detail, k.Detail)
assert.Equal(t, tc.err.Status, k.Status)
assert.Equal(t, tc.err.Err.Error(), k.Err.Error())
} else {
assert.Fail(t, "unexpected error type")
}
err := tc.ch.Validate(ctx, tc.db, tc.jwk, tc.payload)
if tc.err != nil {
var k *Error
if errors.As(err, &k) {
assert.Equal(t, tc.err.Type, k.Type)
assert.Equal(t, tc.err.Detail, k.Detail)
assert.Equal(t, tc.err.Status, k.Status)
assert.Equal(t, tc.err.Err.Error(), k.Err.Error())
} else {
assert.Fail(t, "unexpected error type")
}
} else {
assert.Nil(t, tc.err)
return
}
assert.NoError(t, err)
})
}
}
func mustJWKServer(t *testing.T, pub jose.JSONWebKey) *httptest.Server {
t.Helper()
mux := http.NewServeMux()
server := httptest.NewServer(mux)
b, err := json.Marshal(struct {
Keys []jose.JSONWebKey `json:"keys,omitempty"`
}{
Keys: []jose.JSONWebKey{pub},
})
require.NoError(t, err)
jwks := string(b)
wellKnown := fmt.Sprintf(`{
"issuer": "%[1]s",
"authorization_endpoint": "%[1]s/auth",
"token_endpoint": "%[1]s/token",
"jwks_uri": "%[1]s/keys",
"userinfo_endpoint": "%[1]s/userinfo",
"id_token_signing_alg_values_supported": ["ES256"]
}`, server.URL)
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, req *http.Request) {
_, err := io.WriteString(w, wellKnown)
if err != nil {
w.WriteHeader(500)
}
})
mux.HandleFunc("/keys", func(w http.ResponseWriter, req *http.Request) {
_, err := io.WriteString(w, jwks)
if err != nil {
w.WriteHeader(500)
}
})
t.Cleanup(server.Close)
return server
}
type errReader int
func (errReader) Read([]byte) (int, error) {

File diff suppressed because it is too large Load Diff

@ -53,6 +53,13 @@ type DB interface {
GetOrder(ctx context.Context, id string) (*Order, error)
GetOrdersByAccountID(ctx context.Context, accountID string) ([]string, error)
UpdateOrder(ctx context.Context, o *Order) error
// TODO(hs): put in a different interface
GetAllOrdersByAccountID(ctx context.Context, accountID string) ([]string, error)
CreateDpopToken(ctx context.Context, orderID string, dpop map[string]interface{}) error
GetDpopToken(ctx context.Context, orderID string) (map[string]interface{}, error)
CreateOidcToken(ctx context.Context, orderID string, idToken map[string]interface{}) error
GetOidcToken(ctx context.Context, orderID string) (map[string]interface{}, error)
}
type dbKey struct{}
@ -118,6 +125,12 @@ type MockDB struct {
MockGetOrdersByAccountID func(ctx context.Context, accountID string) ([]string, error)
MockUpdateOrder func(ctx context.Context, o *Order) error
MockGetAllOrdersByAccountID func(ctx context.Context, accountID string) ([]string, error)
MockGetDpopToken func(ctx context.Context, orderID string) (map[string]interface{}, error)
MockCreateDpopToken func(ctx context.Context, orderID string, dpop map[string]interface{}) error
MockGetOidcToken func(ctx context.Context, orderID string) (map[string]interface{}, error)
MockCreateOidcToken func(ctx context.Context, orderID string, idToken map[string]interface{}) error
MockRet1 interface{}
MockError error
}
@ -391,3 +404,49 @@ func (m *MockDB) GetOrdersByAccountID(ctx context.Context, accID string) ([]stri
}
return m.MockRet1.([]string), m.MockError
}
// GetAllOrdersByAccountID returns a list of any order IDs owned by the account.
func (m *MockDB) GetAllOrdersByAccountID(ctx context.Context, accountID string) ([]string, error) {
if m.MockGetAllOrdersByAccountID != nil {
return m.MockGetAllOrdersByAccountID(ctx, accountID)
} else if m.MockError != nil {
return nil, m.MockError
}
return m.MockRet1.([]string), m.MockError
}
// GetDpop retrieves a DPoP from the database.
func (m *MockDB) GetDpopToken(ctx context.Context, orderID string) (map[string]any, error) {
if m.MockGetDpopToken != nil {
return m.MockGetDpopToken(ctx, orderID)
} else if m.MockError != nil {
return nil, m.MockError
}
return m.MockRet1.(map[string]any), m.MockError
}
// CreateDpop creates DPoP resources and saves them to the DB.
func (m *MockDB) CreateDpopToken(ctx context.Context, orderID string, dpop map[string]any) error {
if m.MockCreateDpopToken != nil {
return m.MockCreateDpopToken(ctx, orderID, dpop)
}
return m.MockError
}
// GetOidcToken retrieves an oidc token from the database.
func (m *MockDB) GetOidcToken(ctx context.Context, orderID string) (map[string]any, error) {
if m.MockGetOidcToken != nil {
return m.MockGetOidcToken(ctx, orderID)
} else if m.MockError != nil {
return nil, m.MockError
}
return m.MockRet1.(map[string]any), m.MockError
}
// CreateOidcToken creates oidc token resources and saves them to the DB.
func (m *MockDB) CreateOidcToken(ctx context.Context, orderID string, idToken map[string]any) error {
if m.MockCreateOidcToken != nil {
return m.MockCreateOidcToken(ctx, orderID, idToken)
}
return m.MockError
}

@ -19,6 +19,7 @@ type dbChallenge struct {
Status acme.Status `json:"status"`
Token string `json:"token"`
Value string `json:"value"`
Target string `json:"target,omitempty"`
ValidatedAt string `json:"validatedAt"`
CreatedAt time.Time `json:"createdAt"`
Error *acme.Error `json:"error"` // TODO(hs): a bit dangerous; should become db-specific type
@ -61,6 +62,7 @@ func (db *DB) CreateChallenge(ctx context.Context, ch *acme.Challenge) error {
Token: ch.Token,
CreatedAt: clock.Now(),
Type: ch.Type,
Target: ch.Target,
}
return db.save(ctx, ch.ID, dbch, nil, "challenge", challengeTable)
@ -84,6 +86,7 @@ func (db *DB) GetChallenge(ctx context.Context, id, authzID string) (*acme.Chall
Token: dbch.Token,
Error: dbch.Error,
ValidatedAt: dbch.ValidatedAt,
Target: dbch.Target,
}
return ch, nil
}

@ -23,6 +23,8 @@ var (
externalAccountKeyTable = []byte("acme_external_account_keys")
externalAccountKeyIDsByReferenceTable = []byte("acme_external_account_keyID_reference_index")
externalAccountKeyIDsByProvisionerIDTable = []byte("acme_external_account_keyID_provisionerID_index")
wireDpopTokenTable = []byte("wire_acme_dpop_token")
wireOidcTokenTable = []byte("wire_acme_oidc_token")
)
// DB is a struct that implements the AcmeDB interface.
@ -36,11 +38,11 @@ func New(db nosqlDB.DB) (*DB, error) {
challengeTable, nonceTable, orderTable, ordersByAccountIDTable,
certTable, certBySerialTable, externalAccountKeyTable,
externalAccountKeyIDsByReferenceTable, externalAccountKeyIDsByProvisionerIDTable,
wireDpopTokenTable, wireOidcTokenTable,
}
for _, b := range tables {
if err := db.CreateTable(b); err != nil {
return nil, errors.Wrapf(err, "error creating table %s",
string(b))
return nil, errors.Wrapf(err, "error creating table %s", string(b))
}
}
return &DB{db}, nil

@ -98,7 +98,7 @@ func (db *DB) CreateOrder(ctx context.Context, o *acme.Order) error {
return err
}
_, err = db.updateAddOrderIDs(ctx, o.AccountID, o.ID)
_, err = db.updateAddOrderIDs(ctx, o.AccountID, false, o.ID)
if err != nil {
return err
}
@ -117,10 +117,11 @@ func (db *DB) UpdateOrder(ctx context.Context, o *acme.Order) error {
nu.Status = o.Status
nu.Error = o.Error
nu.CertificateID = o.CertificateID
return db.save(ctx, old.ID, nu, old, "order", orderTable)
}
func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, addOids ...string) ([]string, error) {
func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, includeReadyOrders bool, addOids ...string) ([]string, error) {
ordersByAccountMux.Lock()
defer ordersByAccountMux.Unlock()
@ -151,7 +152,8 @@ func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, addOids ...st
if err = o.UpdateStatus(ctx, db); err != nil {
return nil, acme.WrapErrorISE(err, "error updating order %s for account %s", oid, accID)
}
if o.Status == acme.StatusPending {
if o.Status == acme.StatusPending || (o.Status == acme.StatusReady && includeReadyOrders) {
pendOids = append(pendOids, oid)
}
}
@ -183,5 +185,10 @@ func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, addOids ...st
// GetOrdersByAccountID returns a list of order IDs owned by the account.
func (db *DB) GetOrdersByAccountID(ctx context.Context, accID string) ([]string, error) {
return db.updateAddOrderIDs(ctx, accID)
return db.updateAddOrderIDs(ctx, accID, false)
}
// GetAllOrdersByAccountID returns a list of any order IDs owned by the account.
func (db *DB) GetAllOrdersByAccountID(ctx context.Context, accID string) ([]string, error) {
return db.updateAddOrderIDs(ctx, accID, true)
}

@ -997,9 +997,9 @@ func TestDB_updateAddOrderIDs(t *testing.T) {
err error
)
if tc.addOids == nil {
res, err = d.updateAddOrderIDs(context.Background(), accID)
res, err = d.updateAddOrderIDs(context.Background(), accID, false)
} else {
res, err = d.updateAddOrderIDs(context.Background(), accID, tc.addOids...)
res, err = d.updateAddOrderIDs(context.Background(), accID, false, tc.addOids...)
}
if err != nil {

@ -0,0 +1,121 @@
package nosql
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/smallstep/certificates/acme"
"github.com/smallstep/nosql"
)
type dbDpopToken struct {
ID string `json:"id"`
Content []byte `json:"content"`
CreatedAt time.Time `json:"createdAt"`
}
// 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)
}
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, fmt.Errorf("failed unmarshaling dpop token %q into dbDpopToken: %w", orderID, err)
}
return d, nil
}
// GetDpopToken retrieves an DPoP from the database.
func (db *DB) GetDpopToken(ctx context.Context, orderID string) (map[string]any, error) {
dbDpop, err := db.getDBDpopToken(ctx, orderID)
if err != nil {
return nil, err
}
dpop := make(map[string]any)
err = json.Unmarshal(dbDpop.Content, &dpop)
return dpop, err
}
// CreateDpopToken creates DPoP resources and saves them to the DB.
func (db *DB) CreateDpopToken(ctx context.Context, orderID string, dpop map[string]any) error {
content, err := json.Marshal(dpop)
if err != nil {
return fmt.Errorf("failed marshaling dpop token: %w", err)
}
now := clock.Now()
dbDpop := &dbDpopToken{
ID: orderID,
Content: content,
CreatedAt: now,
}
if err := db.save(ctx, orderID, dbDpop, nil, "dpop", wireDpopTokenTable); err != nil {
return fmt.Errorf("failed saving dpop token: %w", err)
}
return nil
}
type dbOidcToken struct {
ID string `json:"id"`
Content []byte `json:"content"`
CreatedAt time.Time `json:"createdAt"`
}
// 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)
}
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, fmt.Errorf("failed unmarshaling oidc token %q into dbOidcToken: %w", orderID, err)
}
return o, nil
}
// GetOidcToken retrieves an oidc token from the database.
func (db *DB) GetOidcToken(ctx context.Context, orderID string) (map[string]any, error) {
dbOidc, err := db.getDBOidcToken(ctx, orderID)
if err != nil {
return nil, err
}
idToken := make(map[string]any)
err = json.Unmarshal(dbOidc.Content, &idToken)
return idToken, err
}
// CreateOidcToken creates oidc token resources and saves them to the DB.
func (db *DB) CreateOidcToken(ctx context.Context, orderID string, idToken map[string]any) error {
content, err := json.Marshal(idToken)
if err != nil {
return fmt.Errorf("failed marshaling oidc token: %w", err)
}
now := clock.Now()
dbOidc := &dbOidcToken{
ID: orderID,
Content: content,
CreatedAt: now,
}
if err := db.save(ctx, orderID, dbOidc, nil, "oidc", wireOidcTokenTable); err != nil {
return fmt.Errorf("failed saving oidc token: %w", err)
}
return nil
}

@ -0,0 +1,394 @@
package nosql
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
"github.com/smallstep/certificates/acme"
certificatesdb "github.com/smallstep/certificates/db"
"github.com/smallstep/nosql"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDB_GetDpopToken(t *testing.T) {
type test struct {
db *DB
orderID string
expected map[string]any
expectedErr error
}
var tests = map[string]func(t *testing.T) test{
"fail/acme-not-found": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: &acme.Error{
Type: "urn:ietf:params:acme:error:malformed",
Status: 400,
Detail: "The request message was malformed",
Err: errors.New(`dpop token "orderID" not found`),
},
}
},
"fail/unmarshal-error": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
token := dbDpopToken{
ID: "orderID",
Content: []byte("{}"),
CreatedAt: time.Now(),
}
b, err := json.Marshal(token)
require.NoError(t, err)
err = db.Set(wireDpopTokenTable, []byte("orderID"), b[1:]) // start at index 1; corrupt JSON data
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: errors.New(`failed unmarshaling dpop token "orderID" into dbDpopToken: invalid character ':' after top-level value`),
}
},
"fail/db.Get": func(t *testing.T) test {
db := &certificatesdb.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equal(t, wireDpopTokenTable, bucket)
assert.Equal(t, []byte("orderID"), key)
return nil, errors.New("fail")
},
}
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: errors.New(`failed loading dpop token "orderID": fail`),
}
},
"ok": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
token := dbDpopToken{
ID: "orderID",
Content: []byte(`{"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com"}`),
CreatedAt: time.Now(),
}
b, err := json.Marshal(token)
require.NoError(t, err)
err = db.Set(wireDpopTokenTable, []byte("orderID"), b)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expected: map[string]any{
"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
got, err := tc.db.GetDpopToken(context.Background(), tc.orderID)
if tc.expectedErr != nil {
assert.EqualError(t, err, tc.expectedErr.Error())
ae := &acme.Error{}
if errors.As(err, &ae) {
ee := &acme.Error{}
require.True(t, errors.As(tc.expectedErr, &ee))
assert.Equal(t, ee.Detail, ae.Detail)
assert.Equal(t, ee.Type, ae.Type)
assert.Equal(t, ee.Status, ae.Status)
}
assert.Nil(t, got)
return
}
assert.NoError(t, err)
assert.Equal(t, tc.expected, got)
})
}
}
func TestDB_CreateDpopToken(t *testing.T) {
type test struct {
db *DB
orderID string
dpop map[string]any
expectedErr error
}
var tests = map[string]func(t *testing.T) test{
"fail/db.Save": func(t *testing.T) test {
db := &certificatesdb.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
assert.Equal(t, wireDpopTokenTable, bucket)
assert.Equal(t, []byte("orderID"), key)
return nil, false, errors.New("fail")
},
}
return test{
db: &DB{
db: db,
},
orderID: "orderID",
dpop: map[string]any{
"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
},
expectedErr: errors.New("failed saving dpop token: error saving acme dpop: fail"),
}
},
"ok": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
dpop: map[string]any{
"sub": "wireapp://guVX5xeFS3eTatmXBIyA4A!7a41cf5b79683410@wire.com",
},
}
},
"ok/nil": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
dpop: nil,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
err := tc.db.CreateDpopToken(context.Background(), tc.orderID, tc.dpop)
if tc.expectedErr != nil {
assert.EqualError(t, err, tc.expectedErr.Error())
return
}
assert.NoError(t, err)
dpop, err := tc.db.getDBDpopToken(context.Background(), tc.orderID)
require.NoError(t, err)
assert.Equal(t, tc.orderID, dpop.ID)
var m map[string]any
err = json.Unmarshal(dpop.Content, &m)
require.NoError(t, err)
assert.Equal(t, tc.dpop, m)
})
}
}
func TestDB_GetOidcToken(t *testing.T) {
type test struct {
db *DB
orderID string
expected map[string]any
expectedErr error
}
var tests = map[string]func(t *testing.T) test{
"fail/acme-not-found": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: &acme.Error{
Type: "urn:ietf:params:acme:error:malformed",
Status: 400,
Detail: "The request message was malformed",
Err: errors.New(`oidc token "orderID" not found`),
},
}
},
"fail/unmarshal-error": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
token := dbOidcToken{
ID: "orderID",
Content: []byte("{}"),
CreatedAt: time.Now(),
}
b, err := json.Marshal(token)
require.NoError(t, err)
err = db.Set(wireOidcTokenTable, []byte("orderID"), b[1:]) // start at index 1; corrupt JSON data
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: errors.New(`failed unmarshaling oidc token "orderID" into dbOidcToken: invalid character ':' after top-level value`),
}
},
"fail/db.Get": func(t *testing.T) test {
db := &certificatesdb.MockNoSQLDB{
MGet: func(bucket, key []byte) ([]byte, error) {
assert.Equal(t, wireOidcTokenTable, bucket)
assert.Equal(t, []byte("orderID"), key)
return nil, errors.New("fail")
},
}
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expectedErr: errors.New(`failed loading oidc token "orderID": fail`),
}
},
"ok": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
token := dbOidcToken{
ID: "orderID",
Content: []byte(`{"name": "Alice Smith", "preferred_username": "@alice.smith"}`),
CreatedAt: time.Now(),
}
b, err := json.Marshal(token)
require.NoError(t, err)
err = db.Set(wireOidcTokenTable, []byte("orderID"), b)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
expected: map[string]any{
"name": "Alice Smith",
"preferred_username": "@alice.smith",
},
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
got, err := tc.db.GetOidcToken(context.Background(), tc.orderID)
if tc.expectedErr != nil {
assert.EqualError(t, err, tc.expectedErr.Error())
ae := &acme.Error{}
if errors.As(err, &ae) {
ee := &acme.Error{}
require.True(t, errors.As(tc.expectedErr, &ee))
assert.Equal(t, ee.Detail, ae.Detail)
assert.Equal(t, ee.Type, ae.Type)
assert.Equal(t, ee.Status, ae.Status)
}
assert.Nil(t, got)
return
}
assert.NoError(t, err)
assert.Equal(t, tc.expected, got)
})
}
}
func TestDB_CreateOidcToken(t *testing.T) {
type test struct {
db *DB
orderID string
oidc map[string]any
expectedErr error
}
var tests = map[string]func(t *testing.T) test{
"fail/db.Save": func(t *testing.T) test {
db := &certificatesdb.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
assert.Equal(t, wireOidcTokenTable, bucket)
assert.Equal(t, []byte("orderID"), key)
return nil, false, errors.New("fail")
},
}
return test{
db: &DB{
db: db,
},
orderID: "orderID",
oidc: map[string]any{
"name": "Alice Smith",
"preferred_username": "@alice.smith",
},
expectedErr: errors.New("failed saving oidc token: error saving acme oidc: fail"),
}
},
"ok": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
oidc: map[string]any{
"name": "Alice Smith",
"preferred_username": "@alice.smith",
},
}
},
"ok/nil": func(t *testing.T) test {
dir := t.TempDir()
db, err := nosql.New("badgerv2", dir)
require.NoError(t, err)
return test{
db: &DB{
db: db,
},
orderID: "orderID",
oidc: nil,
}
},
}
for name, run := range tests {
tc := run(t)
t.Run(name, func(t *testing.T) {
err := tc.db.CreateOidcToken(context.Background(), tc.orderID, tc.oidc)
if tc.expectedErr != nil {
assert.EqualError(t, err, tc.expectedErr.Error())
return
}
assert.NoError(t, err)
oidc, err := tc.db.getDBOidcToken(context.Background(), tc.orderID)
require.NoError(t, err)
assert.Equal(t, tc.orderID, oidc.ID)
var m map[string]any
err = json.Unmarshal(oidc.Content, &m)
require.NoError(t, err)
assert.Equal(t, tc.oidc, m)
})
}
}

@ -5,15 +5,20 @@ import (
"context"
"crypto/subtle"
"crypto/x509"
"encoding/asn1"
"encoding/json"
"fmt"
"net"
"net/url"
"sort"
"strings"
"time"
"github.com/smallstep/certificates/authority/provisioner"
"go.step.sm/crypto/keyutil"
"go.step.sm/crypto/x509util"
"github.com/smallstep/certificates/acme/wire"
"github.com/smallstep/certificates/authority/provisioner"
)
type IdentifierType string
@ -26,6 +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"
// 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.
@ -121,9 +130,11 @@ func (o *Order) UpdateStatus(ctx context.Context, db DB) error {
default:
return NewErrorISE("unrecognized order status: %s", o.Status)
}
if err := db.UpdateOrder(ctx, o); err != nil {
return WrapErrorISE(err, "error updating order")
}
return nil
}
@ -196,7 +207,28 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
// Template data
data := x509util.NewTemplateData()
data.SetCommonName(csr.Subject.CommonName)
if o.containsWireIdentifiers() {
subject, err := createWireSubject(o, csr)
if err != nil {
return fmt.Errorf("failed creating Wire subject: %w", err)
}
data.SetSubject(subject)
// Inject Wire's custom challenges into the template once they have been validated
dpop, err := db.GetDpopToken(ctx, o.ID)
if err != nil {
return fmt.Errorf("failed getting Wire DPoP token: %w", err)
}
data.Set("Dpop", dpop)
oidc, err := db.GetOidcToken(ctx, o.ID)
if err != nil {
return fmt.Errorf("failed getting Wire OIDC token: %w", err)
}
data.Set("Oidc", oidc)
} else {
data.SetCommonName(csr.Subject.CommonName)
}
// Custom sign options passed to authority.Sign
var extraOptions []provisioner.SignOption
@ -283,15 +315,76 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques
o.CertificateID = cert.ID
o.Status = StatusValid
if err = db.UpdateOrder(ctx, o); err != nil {
return WrapErrorISE(err, "error updating order %s", o.ID)
}
return nil
}
// containsWireIdentifiers checks if [Order] contains ACME
// identifiers for the WireUser or WireDevice types.
func (o *Order) containsWireIdentifiers() bool {
for _, i := range o.Identifiers {
if i.Type == WireUser || i.Type == WireDevice {
return true
}
}
return false
}
// createWireSubject creates the subject for an [Order] with WireUser identifiers.
func createWireSubject(o *Order, csr *x509.CertificateRequest) (subject x509util.Subject, err error) {
wireUserIDs, wireDeviceIDs, otherIDs := 0, 0, 0
for _, identifier := range o.Identifiers {
switch identifier.Type {
case WireUser:
wireID, err := wire.ParseUserID(identifier.Value)
if err != nil {
return subject, NewErrorISE("unmarshal wireID: %s", err)
}
// TODO: temporarily using a custom OIDC for carrying the display name without having it listed as a DNS SAN.
// reusing LDAP's OID for diplay name see http://oid-info.com/get/2.16.840.1.113730.3.1.241
displayNameOid := asn1.ObjectIdentifier{2, 16, 840, 1, 113730, 3, 1, 241}
var foundDisplayName = false
for _, entry := range csr.Subject.Names {
if entry.Type.Equal(displayNameOid) {
foundDisplayName = true
displayName := entry.Value.(string)
if displayName != wireID.Name {
return subject, NewErrorISE("expected displayName %v, found %v", wireID.Name, displayName)
}
}
}
if !foundDisplayName {
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) {
return subject, NewErrorISE("expected Organization [%s], found %v", wireID.Domain, csr.Subject.Organization)
}
subject.CommonName = wireID.Name
subject.Organization = []string{wireID.Domain}
wireUserIDs++
case WireDevice:
wireDeviceIDs++
default:
otherIDs++
}
}
if otherIDs > 0 || wireUserIDs != 1 && wireDeviceIDs != 1 {
return subject, NewErrorISE("order must have exactly one WireUser and WireDevice identifier")
}
return
}
func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativeName, error) {
var sans []x509util.SubjectAlternativeName
if len(csr.EmailAddresses) > 0 || len(csr.URIs) > 0 {
if len(csr.EmailAddresses) > 0 {
return sans, NewError(ErrorBadCSRType, "Only DNS names and IP addresses are allowed")
}
@ -299,7 +392,8 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
orderNames := make([]string, numberOfIdentifierType(DNS, o.Identifiers))
orderIPs := make([]net.IP, numberOfIdentifierType(IP, o.Identifiers))
orderPIDs := make([]string, numberOfIdentifierType(PermanentIdentifier, o.Identifiers))
indexDNS, indexIP, indexPID := 0, 0, 0
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 {
case DNS:
@ -311,14 +405,37 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
case PermanentIdentifier:
orderPIDs[indexPID] = n.Value
indexPID++
case WireUser:
wireID, err := wire.ParseUserID(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(n.Value)
if err != nil {
return sans, NewErrorISE("unsupported identifier value in order: %s", n.Value)
}
clientID, err := url.Parse(wireID.ClientID)
if err != nil {
return sans, NewErrorISE("clientId must be a URI: %s", wireID.ClientID)
}
tmpOrderURIs[indexURI] = clientID
indexURI++
default:
return sans, NewErrorISE("unsupported identifier type in order: %s", n.Type)
}
}
orderNames = uniqueSortedLowerNames(orderNames)
orderIPs = uniqueSortedIPs(orderIPs)
orderURIs := uniqueSortedURIStrings(tmpOrderURIs)
totalNumberOfSANs := len(csr.DNSNames) + len(csr.IPAddresses)
totalNumberOfSANs := len(csr.DNSNames) + len(csr.IPAddresses) + len(csr.URIs)
sans = make([]x509util.SubjectAlternativeName, totalNumberOfSANs)
index := 0
@ -361,6 +478,26 @@ func (o *Order) sans(csr *x509.CertificateRequest) ([]x509util.SubjectAlternativ
index++
}
if len(csr.URIs) != len(tmpOrderURIs) {
return sans, NewError(ErrorBadCSRType, "CSR URIs do not match identifiers exactly: "+
"CSR URIs = %v, Order URIs = %v", csr.URIs, tmpOrderURIs)
}
// sort URI list
csrURIs := uniqueSortedURIStrings(csr.URIs)
for i := range csrURIs {
if csrURIs[i] != orderURIs[i] {
return sans, NewError(ErrorBadCSRType, "CSR URIs do not match identifiers exactly: "+
"CSR URIs = %v, Order URIs = %v", csr.URIs, tmpOrderURIs)
}
sans[index] = x509util.SubjectAlternativeName{
Type: x509util.URIType,
Value: orderURIs[i],
}
index++
}
return sans, nil
}
@ -430,6 +567,21 @@ func uniqueSortedLowerNames(names []string) (unique []string) {
}
unique = make([]string, 0, len(nameMap))
for name := range nameMap {
if len(name) > 0 {
unique = append(unique, name)
}
}
sort.Strings(unique)
return
}
func uniqueSortedURIStrings(uris []*url.URL) (unique []string) {
uriMap := make(map[string]struct{}, len(uris))
for _, name := range uris {
uriMap[name.String()] = struct{}{}
}
unique = make([]string, 0, len(uriMap))
for name := range uriMap {
unique = append(unique, name)
}
sort.Strings(unique)

@ -9,7 +9,6 @@ import (
"encoding/json"
"fmt"
"net"
"net/url"
"reflect"
"testing"
"time"
@ -1702,25 +1701,6 @@ func TestOrder_sans(t *testing.T) {
want: []x509util.SubjectAlternativeName{},
err: NewError(ErrorBadCSRType, "Only DNS names and IP addresses are allowed"),
},
{
name: "fail/invalid-alternative-name-uri",
fields: fields{
Identifiers: []Identifier{},
},
csr: &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: "foo.internal",
},
URIs: []*url.URL{
{
Scheme: "https://",
Host: "smallstep.com",
},
},
},
want: []x509util.SubjectAlternativeName{},
err: NewError(ErrorBadCSRType, "Only DNS names and IP addresses are allowed"),
},
{
name: "fail/error-names-length-mismatch",
fields: fields{

@ -0,0 +1,91 @@
package wire
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strings"
)
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 ParseUserID(value string) (id UserID, err error) {
if err = json.Unmarshal([]byte(value), &id); err != nil {
return
}
switch {
case id.Handle == "":
err = errors.New("handle must not be empty")
case id.Name == "":
err = errors.New("name must not be empty")
case id.Domain == "":
err = errors.New("domain must not be empty")
}
return
}
func ParseDeviceID(value string) (id DeviceID, err error) {
if err = json.Unmarshal([]byte(value), &id); err != nil {
return
}
switch {
case id.Handle == "":
err = errors.New("handle must not be empty")
case id.Name == "":
err = errors.New("name must not be empty")
case id.Domain == "":
err = errors.New("domain must not be empty")
case id.ClientID == "":
err = errors.New("client-id must not be empty")
}
return
}
type ClientID struct {
Scheme string
Username string
DeviceID string
Domain string
}
// ParseClientID parses a Wire clientID. The ClientID format is as follows:
//
// "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com",
//
// where '!' is used as a separator between the user id & device id.
func ParseClientID(clientID string) (ClientID, error) {
clientIDURI, err := url.Parse(clientID)
if err != nil {
return ClientID{}, fmt.Errorf("invalid Wire client ID URI %q: %w", clientID, err)
}
if clientIDURI.Scheme != "wireapp" {
return ClientID{}, fmt.Errorf("invalid Wire client ID scheme %q; expected \"wireapp\"", clientIDURI.Scheme)
}
fullUsername := clientIDURI.User.Username()
parts := strings.SplitN(fullUsername, "!", 2)
if len(parts) != 2 {
return ClientID{}, fmt.Errorf("invalid Wire client ID username %q", fullUsername)
}
return ClientID{
Scheme: clientIDURI.Scheme,
Username: parts[0],
DeviceID: parts[1],
Domain: clientIDURI.Host,
}, nil
}

@ -0,0 +1,100 @@
package wire
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseUserID(t *testing.T) {
ok := `{"name": "Alice Smith", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
failJSON := `{"name": }`
emptyHandle := `{"name": "Alice Smith", "domain": "wire.com", "handle": ""}`
emptyName := `{"name": "", "domain": "wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
emptyDomain := `{"name": "Alice Smith", "domain": "", "handle": "wireapp://%40alice_wire@wire.com"}`
tests := []struct {
name string
value string
wantWireID UserID
wantErr bool
}{
{name: "ok", value: ok, wantWireID: UserID{Name: "Alice Smith", Domain: "wire.com", Handle: "wireapp://%40alice_wire@wire.com"}},
{name: "fail/json", value: failJSON, wantErr: true},
{name: "fail/empty-handle", value: emptyHandle, wantErr: true},
{name: "fail/empty-name", value: emptyName, wantErr: true},
{name: "fail/empty-domain", value: emptyDomain, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotWireID, err := ParseUserID(tt.value)
if tt.wantErr {
assert.Error(t, err)
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"}`
failJSON := `{"name": }`
emptyHandle := `{"name": "device", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": ""}`
emptyName := `{"name": "", "domain": "wire.com", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
emptyDomain := `{"name": "device", "domain": "", "client-id": "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", "handle": "wireapp://%40alice_wire@wire.com"}`
emptyClientID := `{"name": "device", "domain": "wire.com", "client-id": "", "handle": "wireapp://%40alice_wire@wire.com"}`
tests := []struct {
name string
value string
wantWireID DeviceID
wantErr bool
}{
{name: "ok", value: ok, wantWireID: DeviceID{Name: "device", Domain: "wire.com", ClientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", Handle: "wireapp://%40alice_wire@wire.com"}},
{name: "fail/json", value: failJSON, wantErr: true},
{name: "fail/empty-handle", value: emptyHandle, wantErr: true},
{name: "fail/empty-name", value: emptyName, wantErr: true},
{name: "fail/empty-domain", value: emptyDomain, wantErr: true},
{name: "fail/empty-client-id", value: emptyClientID, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotWireID, err := ParseDeviceID(tt.value)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.wantWireID, gotWireID)
})
}
}
func TestParseClientID(t *testing.T) {
tests := []struct {
name string
clientID string
want ClientID
expectedErr error
}{
{name: "ok", clientID: "wireapp://CzbfFjDOQrenCbDxVmgnFw!594930e9d50bb175@wire.com", want: ClientID{Scheme: "wireapp", Username: "CzbfFjDOQrenCbDxVmgnFw", DeviceID: "594930e9d50bb175", Domain: "wire.com"}},
{name: "fail/uri", clientID: "bla", expectedErr: errors.New(`invalid Wire client ID scheme ""; expected "wireapp"`)},
{name: "fail/scheme", clientID: "not-wireapp://bla.com", expectedErr: errors.New(`invalid Wire client ID scheme "not-wireapp"; expected "wireapp"`)},
{name: "fail/username", clientID: "wireapp://user@wire.com", expectedErr: errors.New(`invalid Wire client ID username "user"`)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseClientID(tt.clientID)
if tt.expectedErr != nil {
assert.EqualError(t, err, tt.expectedErr.Error())
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}

@ -10,6 +10,7 @@ import (
"time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/acme/wire"
"go.step.sm/linkedca"
)
@ -26,6 +27,10 @@ const (
TLS_ALPN_01 ACMEChallenge = "tls-alpn-01"
// DEVICE_ATTEST_01 is the device-attest-01 ACME challenge.
DEVICE_ATTEST_01 ACMEChallenge = "device-attest-01"
// WIREOIDC_01 is the Wire OIDC challenge.
WIREOIDC_01 ACMEChallenge = "wire-oidc-01"
// WIREDPOP_01 is the Wire DPoP challenge.
WIREDPOP_01 ACMEChallenge = "wire-dpop-01"
)
// String returns a normalized version of the challenge.
@ -36,7 +41,7 @@ func (c ACMEChallenge) String() string {
// Validate returns an error if the acme challenge is not a valid one.
func (c ACMEChallenge) Validate() error {
switch ACMEChallenge(c.String()) {
case HTTP_01, DNS_01, TLS_ALPN_01, DEVICE_ATTEST_01:
case HTTP_01, DNS_01, TLS_ALPN_01, DEVICE_ATTEST_01, WIREOIDC_01, WIREDPOP_01:
return nil
default:
return fmt.Errorf("acme challenge %q is not supported", c)
@ -102,7 +107,8 @@ type ACME struct {
RequireEAB bool `json:"requireEAB,omitempty"`
// Challenges contains the enabled challenges for this provisioner. If this
// value is not set the default http-01, dns-01 and tls-alpn-01 challenges
// will be enabled, device-attest-01 will be disabled.
// will be enabled, device-attest-01, wire-oidc-01 and wire-dpop-01 will be
// disabled.
Challenges []ACMEChallenge `json:"challenges,omitempty"`
// AttestationFormats contains the enabled attestation formats for this
// provisioner. If this value is not set the default apple, step and tpm
@ -206,10 +212,50 @@ func (p *ACME) Init(config Config) (err error) {
}
}
if err := p.initializeWireOptions(); err != nil {
return fmt.Errorf("failed initializing Wire options: %w", err)
}
p.ctl, err = NewController(p, p.Claims, config, p.Options)
return
}
// initializeWireOptions initializes the options for the ACME Wire
// integration. It'll return early if no Wire challenge types are
// enabled.
func (p *ACME) initializeWireOptions() error {
hasWireChallenges := false
for _, c := range p.Challenges {
if c == WIREOIDC_01 || c == WIREDPOP_01 {
hasWireChallenges = true
break
}
}
if !hasWireChallenges {
return nil
}
w, err := p.GetOptions().GetWireOptions()
if err != nil {
return fmt.Errorf("failed getting Wire options: %w", err)
}
if err := w.Validate(); err != nil {
return fmt.Errorf("failed validating Wire options: %w", err)
}
// at this point the Wire options have been validated, and (mostly)
// initialized. Remote keys will be loaded upon the first verification,
// currently.
// TODO(hs): can/should we "prime" the underlying remote keyset, to verify
// auto discovery works as expected? Because of the current way provisioners
// are initialized, doing that as part of the initialization isn't the best
// time to do it, because it could result in operations not resulting in the
// expected result in all cases.
return nil
}
// ACMEIdentifierType encodes ACME Identifier types
type ACMEIdentifierType string
@ -218,6 +264,10 @@ const (
IP ACMEIdentifierType = "ip"
// DNS is the ACME dns identifier type
DNS ACMEIdentifierType = "dns"
// 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
@ -243,6 +293,18 @@ func (p *ACME) AuthorizeOrderIdentifier(_ context.Context, identifier ACMEIdenti
err = x509Policy.IsIPAllowed(net.ParseIP(identifier.Value))
case DNS:
err = x509Policy.IsDNSAllowed(identifier.Value)
case WireUser:
var wireID wire.UserID
if wireID, err = wire.ParseUserID(identifier.Value); err != nil {
return fmt.Errorf("failed parsing Wire SANs: %w", err)
}
err = x509Policy.AreSANsAllowed([]string{wireID.Handle})
case WireDevice:
var wireID wire.DeviceID
if wireID, err = wire.ParseDeviceID(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)
}

@ -1,6 +1,3 @@
//go:build !go1.18
// +build !go1.18
package provisioner
import (
@ -14,8 +11,10 @@ import (
"testing"
"time"
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api/render"
"github.com/smallstep/certificates/authority/provisioner/wire"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestACMEChallenge_Validate(t *testing.T) {
@ -28,14 +27,20 @@ func TestACMEChallenge_Validate(t *testing.T) {
{"dns-01", DNS_01, false},
{"tls-alpn-01", TLS_ALPN_01, false},
{"device-attest-01", DEVICE_ATTEST_01, false},
{"wire-oidc-01", DEVICE_ATTEST_01, false},
{"wire-dpop-01", DEVICE_ATTEST_01, false},
{"uppercase", "HTTP-01", false},
{"fail", "http-02", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.c.Validate(); (err != nil) != tt.wantErr {
t.Errorf("ACMEChallenge.Validate() error = %v, wantErr %v", err, tt.wantErr)
err := tt.c.Validate()
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
})
}
}
@ -54,26 +59,24 @@ func TestACMEAttestationFormat_Validate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.f.Validate(); (err != nil) != tt.wantErr {
t.Errorf("ACMEAttestationFormat.Validate() error = %v, wantErr %v", err, tt.wantErr)
err := tt.f.Validate()
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
})
}
}
func TestACME_Getters(t *testing.T) {
p, err := generateACME()
assert.FatalError(t, err)
id := "acme/" + p.Name
if got := p.GetID(); got != id {
t.Errorf("ACME.GetID() = %v, want %v", got, id)
}
if got := p.GetName(); got != p.Name {
t.Errorf("ACME.GetName() = %v, want %v", got, p.Name)
}
if got := p.GetType(); got != TypeACME {
t.Errorf("ACME.GetType() = %v, want %v", got, TypeACME)
}
require.NoError(t, err)
id := "acme/test@acme-provisioner.com"
assert.Equal(t, id, p.GetID())
assert.Equal(t, "test@acme-provisioner.com", p.GetName())
assert.Equal(t, TypeACME, p.GetType())
kid, key, ok := p.GetEncryptedKey()
if kid != "" || key != "" || ok == true {
t.Errorf("ACME.GetEncryptedKey() = (%v, %v, %v), want (%v, %v, %v)",
@ -83,26 +86,25 @@ func TestACME_Getters(t *testing.T) {
func TestACME_Init(t *testing.T) {
appleCA, err := os.ReadFile("testdata/certs/apple-att-ca.crt")
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
yubicoCA, err := os.ReadFile("testdata/certs/yubico-piv-ca.crt")
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
fakeWireDPoPKey := []byte(`-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`)
type ProvisionerValidateTest struct {
p *ACME
err error
}
tests := map[string]func(*testing.T) ProvisionerValidateTest{
"fail-empty": func(t *testing.T) ProvisionerValidateTest {
"fail/empty": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{},
err: errors.New("provisioner type cannot be empty"),
}
},
"fail-empty-name": func(t *testing.T) ProvisionerValidateTest {
"fail/empty-name": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Type: "ACME",
@ -110,60 +112,119 @@ func TestACME_Init(t *testing.T) {
err: errors.New("provisioner name cannot be empty"),
}
},
"fail-empty-type": func(t *testing.T) ProvisionerValidateTest {
"fail/empty-type": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo"},
err: errors.New("provisioner type cannot be empty"),
}
},
"fail-bad-claims": func(t *testing.T) ProvisionerValidateTest {
"fail/bad-claims": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar", Claims: &Claims{DefaultTLSDur: &Duration{0}}},
p: &ACME{Name: "foo", Type: "ACME", Claims: &Claims{DefaultTLSDur: &Duration{0}}},
err: errors.New("claims: MinTLSCertDuration must be greater than 0"),
}
},
"fail-bad-challenge": func(t *testing.T) ProvisionerValidateTest {
"fail/bad-challenge": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar", Challenges: []ACMEChallenge{HTTP_01, "zar"}},
p: &ACME{Name: "foo", Type: "ACME", Challenges: []ACMEChallenge{HTTP_01, "zar"}},
err: errors.New("acme challenge \"zar\" is not supported"),
}
},
"fail-bad-attestation-format": func(t *testing.T) ProvisionerValidateTest {
"fail/bad-attestation-format": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar", AttestationFormats: []ACMEAttestationFormat{APPLE, "zar"}},
p: &ACME{Name: "foo", Type: "ACME", AttestationFormats: []ACMEAttestationFormat{APPLE, "zar"}},
err: errors.New("acme attestation format \"zar\" is not supported"),
}
},
"fail-parse-attestation-roots": func(t *testing.T) ProvisionerValidateTest {
"fail/parse-attestation-roots": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar", AttestationRoots: []byte("-----BEGIN CERTIFICATE-----\nZm9v\n-----END CERTIFICATE-----")},
p: &ACME{Name: "foo", Type: "ACME", AttestationRoots: []byte("-----BEGIN CERTIFICATE-----\nZm9v\n-----END CERTIFICATE-----")},
err: errors.New("error parsing attestationRoots: malformed certificate"),
}
},
"fail-empty-attestation-roots": func(t *testing.T) ProvisionerValidateTest {
"fail/empty-attestation-roots": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar", AttestationRoots: []byte("\n")},
p: &ACME{Name: "foo", Type: "ACME", AttestationRoots: []byte("\n")},
err: errors.New("error parsing attestationRoots: no certificates found"),
}
},
"fail/wire-missing-options": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "ACME",
Challenges: []ACMEChallenge{WIREOIDC_01, WIREDPOP_01},
},
err: errors.New("failed initializing Wire options: failed getting Wire options: no options available"),
}
},
"fail/wire-missing-wire-options": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "ACME",
Challenges: []ACMEChallenge{WIREOIDC_01, WIREDPOP_01},
Options: &Options{},
},
err: errors.New("failed initializing Wire options: failed getting Wire options: no Wire options available"),
}
},
"fail/wire-validate-options": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "ACME",
Challenges: []ACMEChallenge{WIREOIDC_01, WIREDPOP_01},
Options: &Options{
Wire: &wire.Options{
OIDC: &wire.OIDCOptions{},
DPOP: &wire.DPOPOptions{
SigningKey: fakeWireDPoPKey,
},
},
},
},
err: errors.New("failed initializing Wire options: failed validating Wire options: failed initializing OIDC options: provider not set"),
}
},
"ok": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{Name: "foo", Type: "bar"},
p: &ACME{Name: "foo", Type: "ACME"},
}
},
"ok attestation": func(t *testing.T) ProvisionerValidateTest {
"ok/attestation": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "bar",
Type: "ACME",
Challenges: []ACMEChallenge{DNS_01, DEVICE_ATTEST_01},
AttestationFormats: []ACMEAttestationFormat{APPLE, STEP},
AttestationRoots: bytes.Join([][]byte{appleCA, yubicoCA}, []byte("\n")),
},
}
},
"ok/wire": func(t *testing.T) ProvisionerValidateTest {
return ProvisionerValidateTest{
p: &ACME{
Name: "foo",
Type: "ACME",
Challenges: []ACMEChallenge{WIREOIDC_01, WIREDPOP_01},
Options: &Options{
Wire: &wire.Options{
OIDC: &wire.OIDCOptions{
Provider: &wire.Provider{
IssuerURL: "https://issuer.example.com",
},
},
DPOP: &wire.DPOPOptions{
SigningKey: fakeWireDPoPKey,
},
},
},
},
}
},
}
config := Config{
Claims: globalProvisionerClaims,
Audiences: testAudiences,
@ -173,13 +234,12 @@ func TestACME_Init(t *testing.T) {
tc := get(t)
t.Log(string(tc.p.AttestationRoots))
err := tc.p.Init(config)
if err != nil {
if assert.NotNil(t, tc.err) {
assert.Equals(t, tc.err.Error(), err.Error())
}
} else {
assert.Nil(t, tc.err)
if tc.err != nil {
assert.EqualError(t, err, tc.err.Error())
return
}
assert.NoError(t, err)
})
}
}
@ -195,12 +255,12 @@ func TestACME_AuthorizeRenew(t *testing.T) {
tests := map[string]func(*testing.T) test{
"fail/renew-disabled": func(t *testing.T) test {
p, err := generateACME()
assert.FatalError(t, err)
require.NoError(t, err)
// disable renewal
disable := true
p.Claims = &Claims{DisableRenewal: &disable}
p.ctl.Claimer, err = NewClaimer(p.Claims, globalProvisionerClaims)
assert.FatalError(t, err)
require.NoError(t, err)
return test{
p: p,
cert: &x509.Certificate{
@ -213,7 +273,7 @@ func TestACME_AuthorizeRenew(t *testing.T) {
},
"ok": func(t *testing.T) test {
p, err := generateACME()
assert.FatalError(t, err)
require.NoError(t, err)
return test{
p: p,
cert: &x509.Certificate{
@ -226,16 +286,19 @@ func TestACME_AuthorizeRenew(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
tc := tt(t)
if err := tc.p.AuthorizeRenew(context.Background(), tc.cert); err != nil {
sc, ok := err.(render.StatusCodedError)
assert.Fatal(t, ok, "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code)
if assert.NotNil(t, tc.err) {
assert.HasPrefix(t, err.Error(), tc.err.Error())
err := tc.p.AuthorizeRenew(context.Background(), tc.cert)
if tc.err != nil {
if assert.Implements(t, (*render.StatusCodedError)(nil), err) {
var sc render.StatusCodedError
if errors.As(err, &sc) {
assert.Equal(t, tc.code, sc.StatusCode())
}
}
} else {
assert.Nil(t, tc.err)
assert.EqualError(t, err, tc.err.Error())
return
}
assert.NoError(t, err)
})
}
}
@ -250,7 +313,7 @@ func TestACME_AuthorizeSign(t *testing.T) {
tests := map[string]func(*testing.T) test{
"ok": func(t *testing.T) test {
p, err := generateACME()
assert.FatalError(t, err)
require.NoError(t, err)
return test{
p: p,
token: "foo",
@ -260,39 +323,43 @@ func TestACME_AuthorizeSign(t *testing.T) {
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
tc := tt(t)
if opts, err := tc.p.AuthorizeSign(context.Background(), tc.token); err != nil {
if assert.NotNil(t, tc.err) {
sc, ok := err.(render.StatusCodedError)
assert.Fatal(t, ok, "error does not implement StatusCodedError interface")
assert.Equals(t, sc.StatusCode(), tc.code)
assert.HasPrefix(t, err.Error(), tc.err.Error())
opts, err := tc.p.AuthorizeSign(context.Background(), tc.token)
if tc.err != nil {
if assert.Implements(t, (*render.StatusCodedError)(nil), err) {
var sc render.StatusCodedError
if errors.As(err, &sc) {
assert.Equal(t, tc.code, sc.StatusCode())
}
}
} else {
if assert.Nil(t, tc.err) && assert.NotNil(t, opts) {
assert.Equals(t, 8, len(opts)) // number of SignOptions returned
for _, o := range opts {
switch v := o.(type) {
case *ACME:
case *provisionerExtensionOption:
assert.Equals(t, v.Type, TypeACME)
assert.Equals(t, v.Name, tc.p.GetName())
assert.Equals(t, v.CredentialID, "")
assert.Len(t, 0, v.KeyValuePairs)
case *forceCNOption:
assert.Equals(t, v.ForceCN, tc.p.ForceCN)
case profileDefaultDuration:
assert.Equals(t, time.Duration(v), tc.p.ctl.Claimer.DefaultTLSCertDuration())
case defaultPublicKeyValidator:
case *validityValidator:
assert.Equals(t, v.min, tc.p.ctl.Claimer.MinTLSCertDuration())
assert.Equals(t, v.max, tc.p.ctl.Claimer.MaxTLSCertDuration())
case *x509NamePolicyValidator:
assert.Equals(t, nil, v.policyEngine)
case *WebhookController:
assert.Len(t, 0, v.webhooks)
default:
assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v))
}
assert.EqualError(t, err, tc.err.Error())
return
}
assert.NoError(t, err)
if assert.NotNil(t, opts) {
assert.Len(t, opts, 8) // number of SignOptions returned
for _, o := range opts {
switch v := o.(type) {
case *ACME:
case *provisionerExtensionOption:
assert.Equal(t, v.Type, TypeACME)
assert.Equal(t, v.Name, tc.p.GetName())
assert.Equal(t, v.CredentialID, "")
assert.Len(t, v.KeyValuePairs, 0)
case *forceCNOption:
assert.Equal(t, v.ForceCN, tc.p.ForceCN)
case profileDefaultDuration:
assert.Equal(t, time.Duration(v), tc.p.ctl.Claimer.DefaultTLSCertDuration())
case defaultPublicKeyValidator:
case *validityValidator:
assert.Equal(t, v.min, tc.p.ctl.Claimer.MinTLSCertDuration())
assert.Equal(t, v.max, tc.p.ctl.Claimer.MaxTLSCertDuration())
case *x509NamePolicyValidator:
assert.Equal(t, nil, v.policyEngine)
case *WebhookController:
assert.Len(t, v.webhooks, 0)
default:
require.NoError(t, fmt.Errorf("unexpected sign option of type %T", v))
}
}
}
@ -323,10 +390,14 @@ func TestACME_IsChallengeEnabled(t *testing.T) {
{"ok dns-01 enabled", fields{[]ACMEChallenge{"http-01", "dns-01"}}, args{ctx, DNS_01}, true},
{"ok tls-alpn-01 enabled", fields{[]ACMEChallenge{"http-01", "dns-01", "tls-alpn-01"}}, args{ctx, TLS_ALPN_01}, true},
{"ok device-attest-01 enabled", fields{[]ACMEChallenge{"device-attest-01", "dns-01"}}, args{ctx, DEVICE_ATTEST_01}, true},
{"ok wire-oidc-01 enabled", fields{[]ACMEChallenge{"wire-oidc-01"}}, args{ctx, WIREOIDC_01}, true},
{"ok wire-dpop-01 enabled", fields{[]ACMEChallenge{"wire-dpop-01"}}, args{ctx, WIREDPOP_01}, true},
{"fail http-01", fields{[]ACMEChallenge{"dns-01"}}, args{ctx, "http-01"}, false},
{"fail dns-01", fields{[]ACMEChallenge{"http-01", "tls-alpn-01"}}, args{ctx, "dns-01"}, false},
{"fail tls-alpn-01", fields{[]ACMEChallenge{"http-01", "dns-01", "device-attest-01"}}, args{ctx, "tls-alpn-01"}, false},
{"fail device-attest-01", fields{[]ACMEChallenge{"http-01", "dns-01"}}, args{ctx, "device-attest-01"}, false},
{"fail wire-oidc-01", fields{[]ACMEChallenge{"http-01", "dns-01"}}, args{ctx, "wire-oidc-01"}, false},
{"fail wire-dpop-01", fields{[]ACMEChallenge{"http-01", "dns-01"}}, args{ctx, "wire-dpop-01"}, false},
{"fail unknown", fields{[]ACMEChallenge{"http-01", "dns-01", "tls-alpn-01", "device-attest-01"}}, args{ctx, "unknown"}, false},
}
for _, tt := range tests {
@ -334,9 +405,8 @@ func TestACME_IsChallengeEnabled(t *testing.T) {
p := &ACME{
Challenges: tt.fields.Challenges,
}
if got := p.IsChallengeEnabled(tt.args.ctx, tt.args.challenge); got != tt.want {
t.Errorf("ACME.AuthorizeChallenge() = %v, want %v", got, tt.want)
}
got := p.IsChallengeEnabled(tt.args.ctx, tt.args.challenge)
assert.Equal(t, tt.want, got)
})
}
}
@ -370,9 +440,8 @@ func TestACME_IsAttestationFormatEnabled(t *testing.T) {
p := &ACME{
AttestationFormats: tt.fields.AttestationFormats,
}
if got := p.IsAttestationFormatEnabled(tt.args.ctx, tt.args.format); got != tt.want {
t.Errorf("ACME.IsAttestationFormatEnabled() = %v, want %v", got, tt.want)
}
got := p.IsAttestationFormatEnabled(tt.args.ctx, tt.args.format)
assert.Equal(t, tt.want, got)
})
}
}

@ -11,6 +11,7 @@ import (
"go.step.sm/crypto/x509util"
"github.com/smallstep/certificates/authority/policy"
"github.com/smallstep/certificates/authority/provisioner/wire"
)
// CertificateOptions is an interface that returns a list of options passed when
@ -30,9 +31,10 @@ func (fn certificateOptionsFunc) Options(so SignOptions) []x509util.Option {
type Options struct {
X509 *X509Options `json:"x509,omitempty"`
SSH *SSHOptions `json:"ssh,omitempty"`
// Webhooks is a list of webhooks that can augment template data
Webhooks []*Webhook `json:"webhooks,omitempty"`
// Wire holds the options used for the ACME Wire integration
Wire *wire.Options `json:"wire,omitempty"`
}
// GetX509Options returns the X.509 options.
@ -51,6 +53,18 @@ func (o *Options) GetSSHOptions() *SSHOptions {
return o.SSH
}
// GetWireOptions returns the Wire options if available. It
// returns an error if they're not available.
func (o *Options) GetWireOptions() (*wire.Options, error) {
if o == nil {
return nil, errors.New("no options available")
}
if o.Wire == nil {
return nil, errors.New("no Wire options available")
}
return o.Wire, nil
}
// GetWebhooks returns the webhooks options.
func (o *Options) GetWebhooks() []*Webhook {
if o == nil {

@ -0,0 +1,49 @@
package wire
import (
"bytes"
"crypto"
"errors"
"fmt"
"text/template"
"go.step.sm/crypto/pemutil"
)
type DPOPOptions struct {
// 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"`
signingKey crypto.PublicKey
target *template.Template
}
func (o *DPOPOptions) GetSigningKey() crypto.PublicKey {
return o.signingKey
}
func (o *DPOPOptions) EvaluateTarget(deviceID string) (string, error) {
if deviceID == "" {
return "", errors.New("deviceID must not be empty")
}
buf := new(bytes.Buffer)
if err := o.target.Execute(buf, struct{ DeviceID string }{DeviceID: deviceID}); err != nil {
return "", fmt.Errorf("failed executing DPoP template: %w", err)
}
return buf.String(), nil
}
func (o *DPOPOptions) validateAndInitialize() (err error) {
o.signingKey, err = pemutil.Parse(o.SigningKey)
if err != nil {
return fmt.Errorf("failed parsing key: %w", err)
}
o.target, err = template.New("DeviceID").Parse(o.Target)
if err != nil {
return fmt.Errorf("failed parsing DPoP template: %w", err)
}
return nil
}

@ -0,0 +1,58 @@
package wire
import (
"errors"
"testing"
"text/template"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDPOPOptions_EvaluateTarget(t *testing.T) {
tu := "http://wire.com:15958/clients/{{.DeviceID}}/access-token"
target, err := template.New("DeviceID").Parse(tu)
require.NoError(t, err)
fail := "https:/wire.com:15958/clients/{{.DeviceId}}/access-token"
failTarget, err := template.New("DeviceID").Parse(fail)
require.NoError(t, err)
type fields struct {
target *template.Template
}
type args struct {
deviceID string
}
tests := []struct {
name string
fields fields
args args
want string
expectedErr error
}{
{
name: "ok", fields: fields{target: target}, args: args{deviceID: "deviceID"}, want: "http://wire.com:15958/clients/deviceID/access-token",
},
{
name: "fail/empty", fields: fields{target: target}, args: args{deviceID: ""}, expectedErr: errors.New("deviceID must not be empty"),
},
{
name: "fail/template", fields: fields{target: failTarget}, args: args{deviceID: "bla"}, expectedErr: errors.New(`failed executing DPoP template: template: DeviceID:1:32: executing "DeviceID" at <.DeviceId>: can't evaluate field DeviceId in type struct { DeviceID string }`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &DPOPOptions{
target: tt.fields.target,
}
got, err := o.EvaluateTarget(tt.args.deviceID)
if tt.expectedErr != nil {
assert.EqualError(t, err, tt.expectedErr.Error())
assert.Empty(t, got)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}

@ -0,0 +1,179 @@
package wire
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"text/template"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"go.step.sm/crypto/x509util"
)
type Provider struct {
DiscoveryBaseURL string `json:"discoveryBaseUrl,omitempty"`
IssuerURL string `json:"issuerUrl,omitempty"`
AuthURL string `json:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty"`
JWKSURL string `json:"jwksUrl,omitempty"`
UserInfoURL string `json:"userInfoUrl,omitempty"`
Algorithms []string `json:"signatureAlgorithms,omitempty"`
}
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:"-"`
InsecureSkipSignatureCheck bool `json:"-"`
Now func() time.Time `json:"-"`
}
type OIDCOptions struct {
Provider *Provider `json:"provider,omitempty"`
Config *Config `json:"config,omitempty"`
TransformTemplate string `json:"transform,omitempty"`
target *template.Template
transform *template.Template
oidcProviderConfig *oidc.ProviderConfig
provider *oidc.Provider
verifier *oidc.IDTokenVerifier
}
func (o *OIDCOptions) GetVerifier(ctx context.Context) (*oidc.IDTokenVerifier, error) {
if o.verifier == nil {
switch {
case o.Provider.DiscoveryBaseURL != "":
// creates a new OIDC provider using automatic discovery and the default HTTP client
provider, err := oidc.NewProvider(ctx, o.Provider.DiscoveryBaseURL)
if err != nil {
return nil, fmt.Errorf("failed creating new OIDC provider using discovery: %w", err)
}
o.provider = provider
default:
o.provider = o.oidcProviderConfig.NewProvider(ctx)
}
if o.provider == nil {
return nil, errors.New("no OIDC provider available")
}
o.verifier = o.provider.Verifier(o.getConfig())
}
return o.verifier, nil
}
func (o *OIDCOptions) getConfig() *oidc.Config {
if o == nil || o.Config == nil {
return &oidc.Config{}
}
return &oidc.Config{
ClientID: o.Config.ClientID,
SupportedSigningAlgs: o.Config.SignatureAlgorithms,
SkipClientIDCheck: o.Config.SkipClientIDCheck,
SkipExpiryCheck: o.Config.SkipExpiryCheck,
SkipIssuerCheck: o.Config.SkipIssuerCheck,
Now: o.Config.Now,
InsecureSkipSignatureCheck: o.Config.InsecureSkipSignatureCheck,
}
}
const defaultTemplate = `{"name": "{{ .name }}", "preferred_username": "{{ .preferred_username }}"}`
func (o *OIDCOptions) validateAndInitialize() (err error) {
if o.Provider == nil {
return errors.New("provider not set")
}
if o.Provider.IssuerURL == "" && o.Provider.DiscoveryBaseURL == "" {
return errors.New("either OIDC discovery or issuer URL must be set")
}
if o.Provider.DiscoveryBaseURL == "" {
o.oidcProviderConfig, err = toOIDCProviderConfig(o.Provider)
if err != nil {
return fmt.Errorf("failed creationg OIDC provider config: %w", err)
}
}
o.target, err = template.New("DeviceID").Parse(o.Provider.IssuerURL)
if err != nil {
return fmt.Errorf("failed parsing OIDC template: %w", err)
}
o.transform, err = parseTransform(o.TransformTemplate)
if err != nil {
return fmt.Errorf("failed parsing OIDC transformation template: %w", err)
}
return nil
}
func parseTransform(transformTemplate string) (*template.Template, error) {
if transformTemplate == "" {
transformTemplate = defaultTemplate
}
return template.New("transform").Funcs(x509util.GetFuncMap()).Parse(transformTemplate)
}
func (o *OIDCOptions) EvaluateTarget(deviceID string) (string, error) {
buf := new(bytes.Buffer)
if err := o.target.Execute(buf, struct{ DeviceID string }{DeviceID: deviceID}); err != nil {
return "", fmt.Errorf("failed executing OIDC template: %w", err)
}
return buf.String(), nil
}
func (o *OIDCOptions) Transform(v map[string]any) (map[string]any, error) {
if o.transform == nil || v == nil {
return v, nil
}
// TODO(hs): add support for extracting error message from template "fail" function?
buf := new(bytes.Buffer)
if err := o.transform.Execute(buf, v); err != nil {
return nil, fmt.Errorf("failed executing OIDC transformation: %w", err)
}
var r map[string]any
if err := json.Unmarshal(buf.Bytes(), &r); err != nil {
return nil, fmt.Errorf("failed unmarshaling transformed OIDC token: %w", err)
}
// add original claims if not yet in the transformed result
for key, value := range v {
if _, ok := r[key]; !ok {
r[key] = value
}
}
return r, nil
}
func toOIDCProviderConfig(in *Provider) (*oidc.ProviderConfig, error) {
issuerURL, err := url.Parse(in.IssuerURL)
if err != nil {
return nil, fmt.Errorf("failed parsing issuer URL: %w", err)
}
// Removes query params from the URL because we use it as a way to notify client about the actual OAuth ClientId
// for this provisioner.
// This URL is going to look like: "https://idp:5556/dex?clientid=foo"
// If we don't trim the query params here i.e. 'clientid' then the idToken verification is going to fail because
// the 'iss' claim of the idToken will be "https://idp:5556/dex"
issuerURL.RawQuery = ""
issuerURL.Fragment = ""
return &oidc.ProviderConfig{
IssuerURL: issuerURL.String(),
AuthURL: in.AuthURL,
TokenURL: in.TokenURL,
UserInfoURL: in.UserInfoURL,
JWKSURL: in.JWKSURL,
Algorithms: in.Algorithms,
}, nil
}

@ -0,0 +1,305 @@
package wire
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"text/template"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.step.sm/crypto/jose"
)
func TestOIDCOptions_Transform(t *testing.T) {
defaultTransform, err := parseTransform(``)
require.NoError(t, err)
swapTransform, err := parseTransform(`{"name": "{{ .preferred_username }}", "preferred_username": "{{ .name }}"}`)
require.NoError(t, err)
funcTransform, err := parseTransform(`{"name": "{{ .name }}", "preferred_username": "{{ first .usernames }}"}`)
require.NoError(t, err)
type fields struct {
transform *template.Template
}
type args struct {
v map[string]any
}
tests := []struct {
name string
fields fields
args args
want map[string]any
expectedErr error
}{
{
name: "ok/no-transform",
fields: fields{
transform: nil,
},
args: args{
v: map[string]any{
"name": "Example",
"preferred_username": "Preferred",
},
},
want: map[string]any{
"name": "Example",
"preferred_username": "Preferred",
},
},
{
name: "ok/empty-data",
fields: fields{
transform: nil,
},
args: args{
v: map[string]any{},
},
want: map[string]any{},
},
{
name: "ok/default-transform",
fields: fields{
transform: defaultTransform,
},
args: args{
v: map[string]any{
"name": "Example",
"preferred_username": "Preferred",
},
},
want: map[string]any{
"name": "Example",
"preferred_username": "Preferred",
},
},
{
name: "ok/swap-transform",
fields: fields{
transform: swapTransform,
},
args: args{
v: map[string]any{
"name": "Example",
"preferred_username": "Preferred",
},
},
want: map[string]any{
"name": "Preferred",
"preferred_username": "Example",
},
},
{
name: "ok/transform-with-functions",
fields: fields{
transform: funcTransform,
},
args: args{
v: map[string]any{
"name": "Example",
"usernames": []string{"name-1", "name-2", "name-3"},
},
},
want: map[string]any{
"name": "Example",
"preferred_username": "name-1",
"usernames": []string{"name-1", "name-2", "name-3"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &OIDCOptions{
transform: tt.fields.transform,
}
got, err := o.Transform(tt.args.v)
if tt.expectedErr != nil {
assert.Error(t, err)
return
}
assert.Equal(t, tt.want, got)
})
}
}
func TestOIDCOptions_EvaluateTarget(t *testing.T) {
tu := "http://target.example.com/{{.DeviceID}}"
target, err := template.New("DeviceID").Parse(tu)
require.NoError(t, err)
empty := "http://target.example.com"
emptyTarget, err := template.New("DeviceID").Parse(empty)
require.NoError(t, err)
fail := "https:/wire.com:15958/clients/{{.DeviceId}}/access-token"
failTarget, err := template.New("DeviceID").Parse(fail)
require.NoError(t, err)
type fields struct {
target *template.Template
}
type args struct {
deviceID string
}
tests := []struct {
name string
fields fields
args args
want string
expectedErr error
}{
{
name: "ok", fields: fields{target: target}, args: args{deviceID: "deviceID"}, want: "http://target.example.com/deviceID",
},
{
name: "ok/empty", fields: fields{target: emptyTarget}, args: args{deviceID: ""}, want: "http://target.example.com",
},
{
name: "fail/template", fields: fields{target: failTarget}, args: args{deviceID: "bla"}, expectedErr: errors.New(`failed executing OIDC template: template: DeviceID:1:32: executing "DeviceID" at <.DeviceId>: can't evaluate field DeviceId in type struct { DeviceID string }`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &OIDCOptions{
target: tt.fields.target,
}
got, err := o.EvaluateTarget(tt.args.deviceID)
if tt.expectedErr != nil {
assert.EqualError(t, err, tt.expectedErr.Error())
assert.Empty(t, got)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
func TestOIDCOptions_GetVerifier(t *testing.T) {
signerJWK, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
require.NoError(t, err)
require.NoError(t, err)
srv := mustDiscoveryServer(t, signerJWK.Public())
defer srv.Close()
type fields struct {
Provider *Provider
Config *Config
TransformTemplate string
}
tests := []struct {
name string
fields fields
ctx context.Context
want *oidc.IDTokenVerifier
wantErr bool
}{
{
name: "fail/invalid-discovery-url",
fields: fields{
Provider: &Provider{
DiscoveryBaseURL: "http://invalid.example.com",
},
Config: &Config{
ClientID: "client-id",
},
TransformTemplate: "http://target.example.com/{{.DeviceID}}",
},
ctx: context.Background(),
wantErr: true,
},
{
name: "ok/auto",
fields: fields{
Provider: &Provider{
DiscoveryBaseURL: srv.URL,
},
Config: &Config{
ClientID: "client-id",
},
TransformTemplate: "http://target.example.com/{{.DeviceID}}",
},
ctx: context.Background(),
},
{
name: "ok/fixed",
fields: fields{
Provider: &Provider{
IssuerURL: "http://issuer.example.com",
},
Config: &Config{
ClientID: "client-id",
},
TransformTemplate: "http://target.example.com/{{.DeviceID}}",
},
ctx: context.Background(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &OIDCOptions{
Provider: tt.fields.Provider,
Config: tt.fields.Config,
TransformTemplate: tt.fields.TransformTemplate,
}
err := o.validateAndInitialize()
require.NoError(t, err)
verifier, err := o.GetVerifier(tt.ctx)
if tt.wantErr {
assert.Error(t, err)
assert.Nil(t, verifier)
return
}
assert.NoError(t, err)
assert.NotNil(t, verifier)
if assert.NotNil(t, o.provider) {
assert.NotNil(t, o.provider.Endpoint())
}
})
}
}
func mustDiscoveryServer(t *testing.T, pub jose.JSONWebKey) *httptest.Server {
t.Helper()
mux := http.NewServeMux()
server := httptest.NewServer(mux)
b, err := json.Marshal(struct {
Keys []jose.JSONWebKey `json:"keys,omitempty"`
}{
Keys: []jose.JSONWebKey{pub},
})
require.NoError(t, err)
jwks := string(b)
wellKnown := fmt.Sprintf(`{
"issuer": "%[1]s",
"authorization_endpoint": "%[1]s/auth",
"token_endpoint": "%[1]s/token",
"jwks_uri": "%[1]s/keys",
"userinfo_endpoint": "%[1]s/userinfo",
"id_token_signing_alg_values_supported": ["ES256"]
}`, server.URL)
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, req *http.Request) {
_, err := io.WriteString(w, wellKnown)
if err != nil {
w.WriteHeader(500)
}
})
mux.HandleFunc("/keys", func(w http.ResponseWriter, req *http.Request) {
_, err := io.WriteString(w, jwks)
if err != nil {
w.WriteHeader(500)
}
})
t.Cleanup(server.Close)
return server
}

@ -0,0 +1,51 @@
package wire
import (
"errors"
"fmt"
)
// Options holds the Wire ACME extension options
type Options struct {
OIDC *OIDCOptions `json:"oidc,omitempty"`
DPOP *DPOPOptions `json:"dpop,omitempty"`
}
// GetOIDCOptions returns the OIDC options.
func (o *Options) GetOIDCOptions() *OIDCOptions {
if o == nil {
return nil
}
return o.OIDC
}
// GetDPOPOptions returns the DPoP options.
func (o *Options) GetDPOPOptions() *DPOPOptions {
if o == nil {
return nil
}
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 {
if oidc := o.GetOIDCOptions(); oidc != nil {
if err := oidc.validateAndInitialize(); err != nil {
return fmt.Errorf("failed initializing OIDC options: %w", err)
}
} else {
return errors.New("no OIDC options available")
}
if dpop := o.GetDPOPOptions(); dpop != nil {
if err := dpop.validateAndInitialize(); err != nil {
return fmt.Errorf("failed initializing DPoP options: %w", err)
}
} else {
return errors.New("no DPoP options available")
}
return nil
}

@ -0,0 +1,163 @@
package wire
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestOptions_Validate(t *testing.T) {
key := []byte(`-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k=
-----END PUBLIC KEY-----`)
type fields struct {
OIDC *OIDCOptions
DPOP *DPOPOptions
}
tests := []struct {
name string
fields fields
expectedErr error
}{
{
name: "ok",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://example.com",
},
Config: &Config{},
},
DPOP: &DPOPOptions{
SigningKey: key,
},
},
expectedErr: nil,
},
{
name: "fail/no-oidc-options",
fields: fields{
OIDC: nil,
DPOP: &DPOPOptions{},
},
expectedErr: errors.New("no OIDC options available"),
},
{
name: "fail/empty-issuer-url",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "",
},
Config: &Config{},
},
DPOP: &DPOPOptions{},
},
expectedErr: errors.New("failed initializing OIDC options: either OIDC discovery or issuer URL must be set"),
},
{
name: "fail/invalid-issuer-url",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "\x00",
},
Config: &Config{},
},
DPOP: &DPOPOptions{},
},
expectedErr: errors.New(`failed initializing OIDC options: failed creationg OIDC provider config: failed parsing issuer URL: parse "\x00": net/url: invalid control character in URL`),
},
{
name: "fail/issuer-url-template",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://issuer.example.com/{{}",
},
Config: &Config{},
},
DPOP: &DPOPOptions{},
},
expectedErr: errors.New(`failed initializing OIDC options: failed parsing OIDC template: template: DeviceID:1: unexpected "}" in command`),
},
{
name: "fail/invalid-transform-template",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://example.com",
},
Config: &Config{},
TransformTemplate: "{{}",
},
DPOP: &DPOPOptions{
SigningKey: key,
},
},
expectedErr: errors.New(`failed initializing OIDC options: failed parsing OIDC transformation template: template: transform:1: unexpected "}" in command`),
},
{
name: "fail/no-dpop-options",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://example.com",
},
Config: &Config{},
},
DPOP: nil,
},
expectedErr: errors.New("no DPoP options available"),
},
{
name: "fail/invalid-key",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://example.com",
},
Config: &Config{},
},
DPOP: &DPOPOptions{
SigningKey: []byte{0x00},
Target: "",
},
},
expectedErr: errors.New(`failed initializing DPoP options: failed parsing key: error decoding PEM: not a valid PEM encoded block`),
},
{
name: "fail/target-template",
fields: fields{
OIDC: &OIDCOptions{
Provider: &Provider{
IssuerURL: "https://example.com",
},
Config: &Config{},
},
DPOP: &DPOPOptions{
SigningKey: key,
Target: "{{}",
},
},
expectedErr: errors.New(`failed initializing DPoP options: failed parsing DPoP template: template: DeviceID:1: unexpected "}" in command`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
o := &Options{
OIDC: tt.fields.OIDC,
DPOP: tt.fields.DPOP,
}
err := o.Validate()
if tt.expectedErr != nil {
assert.EqualError(t, err, tt.expectedErr.Error())
return
}
assert.NoError(t, err)
})
}
}

@ -6,6 +6,7 @@ require (
cloud.google.com/go/longrunning v0.5.6
cloud.google.com/go/security v1.16.0
github.com/Masterminds/sprig/v3 v3.2.3
github.com/coreos/go-oidc/v3 v3.4.0
github.com/dgraph-io/badger v1.6.2
github.com/dgraph-io/badger/v2 v2.2007.4
github.com/fxamacker/cbor/v2 v2.6.0
@ -164,5 +165,6 @@ require (
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

500
go.sum

@ -1,20 +1,75 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
cloud.google.com/go/auth v0.2.2 h1:gmxNJs4YZYcw6YvKRtVBaF2fyUE6UrWPyzU8jHvYfmI=
cloud.google.com/go/auth v0.2.2/go.mod h1:2bDNJWtWziDT3Pu1URxHHbkHE/BbOCuyUiKIGcNvafo=
cloud.google.com/go/auth/oauth2adapt v0.2.1 h1:VSPmMmUlT8CkIZ2PzD9AlLN+R3+D1clXMWHHa6vG/Ag=
cloud.google.com/go/auth/oauth2adapt v0.2.1/go.mod h1:tOdK/k+D2e4GEwfBRA48dKNQiDsqIXxLh7VU319eV0g=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
cloud.google.com/go/kms v1.15.8 h1:szIeDCowID8th2i8XE4uRev5PMxQFqW+JjwYxL9h6xs=
cloud.google.com/go/kms v1.15.8/go.mod h1:WoUHcDjD9pluCg7pNds131awnH429QGvRM3N/4MyoVs=
cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXmjuEaE=
cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/security v1.16.0 h1:dzc3oYxFG/9+uMwmxpnG+te4ZEEkbzvBoAFR1Va36N4=
cloud.google.com/go/security v1.16.0/go.mod h1:e1GsICfB1nLCRXOq0yaRlKE/6RUAlBqmalTYQH4J2Xo=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
@ -33,6 +88,7 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mx
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
@ -44,6 +100,7 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E=
github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
@ -96,10 +153,20 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g=
github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
@ -129,6 +196,12 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
@ -138,8 +211,12 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
@ -175,27 +252,44 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
@ -206,9 +300,16 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -226,6 +327,24 @@ github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
@ -233,10 +352,20 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -261,6 +390,8 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/vault/api v1.12.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck=
@ -272,6 +403,8 @@ github.com/hashicorp/vault/api/auth/kubernetes v0.6.0 h1:K8sKGhtTAqGKfzaaYvUSIOA
github.com/hashicorp/vault/api/auth/kubernetes v0.6.0/go.mod h1:Htwcjez5J9PwAHaZ1EYMBlgGq3/in5ajUV4+WCPihPE=
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
@ -323,6 +456,8 @@ github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
@ -407,6 +542,7 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
@ -482,11 +618,22 @@ github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOH
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
@ -501,6 +648,7 @@ go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8=
go.step.sm/crypto v0.44.6 h1:vQg8ujce7fNXDO8EWdriSz+ZSJpYnNh22QrFtRjdyoY=
@ -524,6 +672,7 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -539,28 +688,89 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc=
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
@ -568,12 +778,40 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
@ -585,24 +823,73 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -625,10 +912,13 @@ golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
@ -637,6 +927,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
@ -644,15 +937,58 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -661,13 +997,135 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
google.golang.org/api v0.176.0 h1:dHj1/yv5Dm/eQTXiP9hNCRT3xzJHWXeNdRq29XbMxoE=
google.golang.org/api v0.176.0/go.mod h1:Rra+ltKu14pps/4xTycZfobMgLpbosoaaL7c+SEMrO8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38=
@ -675,12 +1133,39 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -689,7 +1174,12 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -699,7 +1189,10 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@ -707,5 +1200,12 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

Loading…
Cancel
Save