forked from TrueCloudLab/certificates
Refactor renew after expiry token authorization
This changes adds a new authority method that authorizes the renew after expiry tokens.
This commit is contained in:
parent
41ea67ce10
commit
616490a9c6
12 changed files with 527 additions and 57 deletions
|
@ -33,6 +33,7 @@ type Authority interface {
|
||||||
// context specifies the Authorize[Sign|Revoke|etc.] method.
|
// context specifies the Authorize[Sign|Revoke|etc.] method.
|
||||||
Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error)
|
Authorize(ctx context.Context, ott string) ([]provisioner.SignOption, error)
|
||||||
AuthorizeSign(ott string) ([]provisioner.SignOption, error)
|
AuthorizeSign(ott string) ([]provisioner.SignOption, error)
|
||||||
|
AuthorizeRenewToken(ctx context.Context, token string) (*x509.Certificate, error)
|
||||||
GetTLSOptions() *config.TLSOptions
|
GetTLSOptions() *config.TLSOptions
|
||||||
Root(shasum string) (*x509.Certificate, error)
|
Root(shasum string) (*x509.Certificate, error)
|
||||||
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
Sign(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||||
|
|
|
@ -173,6 +173,7 @@ type mockAuthority struct {
|
||||||
ret1, ret2 interface{}
|
ret1, ret2 interface{}
|
||||||
err error
|
err error
|
||||||
authorizeSign func(ott string) ([]provisioner.SignOption, error)
|
authorizeSign func(ott string) ([]provisioner.SignOption, error)
|
||||||
|
authorizeRenewToken func(ctx context.Context, ott string) (*x509.Certificate, error)
|
||||||
getTLSOptions func() *authority.TLSOptions
|
getTLSOptions func() *authority.TLSOptions
|
||||||
root func(shasum string) (*x509.Certificate, error)
|
root func(shasum string) (*x509.Certificate, error)
|
||||||
sign func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
sign func(cr *x509.CertificateRequest, opts provisioner.SignOptions, signOpts ...provisioner.SignOption) ([]*x509.Certificate, error)
|
||||||
|
@ -210,6 +211,13 @@ func (m *mockAuthority) AuthorizeSign(ott string) ([]provisioner.SignOption, err
|
||||||
return m.ret1.([]provisioner.SignOption), m.err
|
return m.ret1.([]provisioner.SignOption), m.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockAuthority) AuthorizeRenewToken(ctx context.Context, ott string) (*x509.Certificate, error) {
|
||||||
|
if m.authorizeRenewToken != nil {
|
||||||
|
return m.authorizeRenewToken(ctx, ott)
|
||||||
|
}
|
||||||
|
return m.ret1.(*x509.Certificate), m.err
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockAuthority) GetTLSOptions() *authority.TLSOptions {
|
func (m *mockAuthority) GetTLSOptions() *authority.TLSOptions {
|
||||||
if m.getTLSOptions != nil {
|
if m.getTLSOptions != nil {
|
||||||
return m.getTLSOptions()
|
return m.getTLSOptions()
|
||||||
|
@ -1010,8 +1018,21 @@ func Test_caHandler_Renew(t *testing.T) {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
h := New(&mockAuthority{
|
h := New(&mockAuthority{
|
||||||
ret1: tt.cert, ret2: tt.root, err: tt.err,
|
ret1: tt.cert, ret2: tt.root, err: tt.err,
|
||||||
getRoots: func() ([]*x509.Certificate, error) {
|
authorizeRenewToken: func(ctx context.Context, ott string) (*x509.Certificate, error) {
|
||||||
return []*x509.Certificate{tt.root}, nil
|
jwt, chain, err := jose.ParseX5cInsecure(ott, []*x509.Certificate{tt.root})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Unauthorized(err.Error())
|
||||||
|
}
|
||||||
|
var claims jose.Claims
|
||||||
|
if err := jwt.Claims(chain[0][0].PublicKey, &claims); err != nil {
|
||||||
|
return nil, errs.Unauthorized(err.Error())
|
||||||
|
}
|
||||||
|
if err := claims.ValidateWithLeeway(jose.Expected{
|
||||||
|
Time: now,
|
||||||
|
}, time.Minute); err != nil {
|
||||||
|
return nil, errs.Unauthorized(err.Error())
|
||||||
|
}
|
||||||
|
return chain[0][0], nil
|
||||||
},
|
},
|
||||||
getTLSOptions: func() *authority.TLSOptions {
|
getTLSOptions: func() *authority.TLSOptions {
|
||||||
return nil
|
return nil
|
||||||
|
@ -1022,17 +1043,19 @@ func Test_caHandler_Renew(t *testing.T) {
|
||||||
req.Header = tt.header
|
req.Header = tt.header
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
h.Renew(logging.NewResponseLogger(w), req)
|
h.Renew(logging.NewResponseLogger(w), req)
|
||||||
res := w.Result()
|
|
||||||
|
|
||||||
if res.StatusCode != tt.statusCode {
|
res := w.Result()
|
||||||
t.Errorf("caHandler.Renew StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
|
defer res.Body.Close()
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
res.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("caHandler.Renew unexpected error = %v", err)
|
t.Errorf("caHandler.Renew unexpected error = %v", err)
|
||||||
}
|
}
|
||||||
|
if res.StatusCode != tt.statusCode {
|
||||||
|
t.Errorf("caHandler.Renew StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
|
||||||
|
t.Errorf("%s", body)
|
||||||
|
}
|
||||||
|
|
||||||
if tt.statusCode < http.StatusBadRequest {
|
if tt.statusCode < http.StatusBadRequest {
|
||||||
expected := []byte(`{"crt":"` + strings.ReplaceAll(string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: tt.cert.Raw})), "\n", `\n`) + `",` +
|
expected := []byte(`{"crt":"` + strings.ReplaceAll(string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: tt.cert.Raw})), "\n", `\n`) + `",` +
|
||||||
`"ca":"` + strings.ReplaceAll(string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: tt.root.Raw})), "\n", `\n`) + `",` +
|
`"ca":"` + strings.ReplaceAll(string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: tt.root.Raw})), "\n", `\n`) + `",` +
|
||||||
|
|
29
api/renew.go
29
api/renew.go
|
@ -4,10 +4,8 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/smallstep/certificates/errs"
|
"github.com/smallstep/certificates/errs"
|
||||||
"go.step.sm/crypto/jose"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -48,35 +46,10 @@ func (h *caHandler) getPeerCertificate(r *http.Request) (*x509.Certificate, erro
|
||||||
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
|
||||||
return r.TLS.PeerCertificates[0], nil
|
return r.TLS.PeerCertificates[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if s := r.Header.Get(authorizationHeader); s != "" {
|
if s := r.Header.Get(authorizationHeader); s != "" {
|
||||||
if parts := strings.SplitN(s, bearerScheme+" ", 2); len(parts) == 2 {
|
if parts := strings.SplitN(s, bearerScheme+" ", 2); len(parts) == 2 {
|
||||||
roots, err := h.Authority.GetRoots()
|
return h.Authority.AuthorizeRenewToken(r.Context(), parts[1])
|
||||||
if err != nil {
|
|
||||||
return nil, errs.BadRequestErr(err, "missing client certificate")
|
|
||||||
}
|
|
||||||
jwt, chain, err := jose.ParseX5cInsecure(parts[1], roots)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating client certificate"))
|
|
||||||
}
|
|
||||||
|
|
||||||
var claims jose.Claims
|
|
||||||
leaf := chain[0][0]
|
|
||||||
if err := jwt.Claims(leaf.PublicKey, &claims); err != nil {
|
|
||||||
return nil, errs.InternalServerErr(err, errs.WithMessage("error validating client certificate"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// According to "rfc7519 JSON Web Token" acceptable skew should be no
|
|
||||||
// more than a few minutes.
|
|
||||||
if err = claims.ValidateWithLeeway(jose.Expected{
|
|
||||||
Time: time.Now().UTC(),
|
|
||||||
}, time.Minute); err != nil {
|
|
||||||
return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating client certificate"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return leaf, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errs.BadRequest("missing client certificate")
|
return nil, errs.BadRequest("missing client certificate")
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -371,3 +372,80 @@ func (a *Authority) authorizeSSHRevoke(ctx context.Context, token string) error
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AuthorizeRenewToken validates the renew token and returns the leaf
|
||||||
|
// certificate in the x5cInsecure header.
|
||||||
|
func (a *Authority) AuthorizeRenewToken(ctx context.Context, ott string) (*x509.Certificate, error) {
|
||||||
|
var claims jose.Claims
|
||||||
|
jwt, chain, err := jose.ParseX5cInsecure(ott, a.rootX509Certs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token"))
|
||||||
|
}
|
||||||
|
leaf := chain[0][0]
|
||||||
|
if err := jwt.Claims(leaf.PublicKey, &claims); err != nil {
|
||||||
|
return nil, errs.InternalServerErr(err, errs.WithMessage("error validating renew token"))
|
||||||
|
}
|
||||||
|
|
||||||
|
p, ok := a.provisioners.LoadByCertificate(leaf)
|
||||||
|
if !ok {
|
||||||
|
return nil, errs.Unauthorized("error validating renew token: cannot get provisioner from certificate")
|
||||||
|
}
|
||||||
|
if err := a.UseToken(ott, p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := claims.ValidateWithLeeway(jose.Expected{
|
||||||
|
Issuer: p.GetName(),
|
||||||
|
Subject: leaf.Subject.CommonName,
|
||||||
|
Time: time.Now().UTC(),
|
||||||
|
}, time.Minute); err != nil {
|
||||||
|
switch err {
|
||||||
|
case jose.ErrInvalidIssuer:
|
||||||
|
return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token: invalid issuer claim (iss)"))
|
||||||
|
case jose.ErrInvalidSubject:
|
||||||
|
return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token: invalid subject claim (sub)"))
|
||||||
|
case jose.ErrNotValidYet:
|
||||||
|
return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token: token not valid yet (nbf)"))
|
||||||
|
case jose.ErrExpired:
|
||||||
|
return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token: token is expired (exp)"))
|
||||||
|
case jose.ErrIssuedInTheFuture:
|
||||||
|
return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token: token issued in the future (iat)"))
|
||||||
|
default:
|
||||||
|
return nil, errs.UnauthorizedErr(err, errs.WithMessage("error validating renew token"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audiences := a.config.GetAudiences().Renew
|
||||||
|
if !matchesAudience(claims.Audience, audiences) {
|
||||||
|
return nil, errs.InternalServerErr(err, errs.WithMessage("error validating renew token: invalid audience claim (aud)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return leaf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchesAudience returns true if A and B share at least one element.
|
||||||
|
func matchesAudience(as, bs []string) bool {
|
||||||
|
if len(bs) == 0 || len(as) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, b := range bs {
|
||||||
|
for _, a := range as {
|
||||||
|
if b == a || stripPort(a) == stripPort(b) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripPort attempts to strip the port from the given url. If parsing the url
|
||||||
|
// produces errors it will just return the passed argument.
|
||||||
|
func stripPort(rawurl string) string {
|
||||||
|
u, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return rawurl
|
||||||
|
}
|
||||||
|
u.Host = u.Hostname()
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
|
@ -3,11 +3,15 @@ package authority
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -20,6 +24,7 @@ import (
|
||||||
"go.step.sm/crypto/jose"
|
"go.step.sm/crypto/jose"
|
||||||
"go.step.sm/crypto/pemutil"
|
"go.step.sm/crypto/pemutil"
|
||||||
"go.step.sm/crypto/randutil"
|
"go.step.sm/crypto/randutil"
|
||||||
|
"go.step.sm/crypto/x509util"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1320,3 +1325,283 @@ func TestAuthority_authorizeSSHRekey(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthority_AuthorizeRenewToken(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
type stepProvisionerASN1 struct {
|
||||||
|
Type int
|
||||||
|
Name []byte
|
||||||
|
CredentialID []byte
|
||||||
|
KeyValuePairs []string `asn1:"optional,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
_, signer, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
csr, err := x509util.CreateCertificateRequest("test.example.com", []string{"test.example.com"}, signer)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, otherSigner, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
generateX5cToken := func(a *Authority, key crypto.Signer, claims jose.Claims, opts ...provisioner.SignOption) (string, *x509.Certificate) {
|
||||||
|
chain, err := a.Sign(csr, provisioner.SignOptions{}, opts...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var x5c []string
|
||||||
|
for _, c := range chain {
|
||||||
|
x5c = append(x5c, base64.StdEncoding.EncodeToString(c.Raw))
|
||||||
|
}
|
||||||
|
|
||||||
|
so := new(jose.SignerOptions)
|
||||||
|
so.WithType("JWT")
|
||||||
|
so.WithHeader("x5cInsecure", x5c)
|
||||||
|
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.EdDSA, Key: key}, so)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s, err := jose.Signed(sig).Claims(claims).CompactSerialize()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return s, chain[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
a1 := testAuthority(t)
|
||||||
|
t1, c1 := generateX5cToken(a1, signer, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
|
Subject: "test.example.com",
|
||||||
|
Issuer: "step-cli",
|
||||||
|
NotBefore: jose.NewNumericDate(now),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now
|
||||||
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("step-cli"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
t2, c2 := generateX5cToken(a1, signer, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
|
Subject: "test.example.com",
|
||||||
|
Issuer: "step-cli",
|
||||||
|
NotBefore: jose.NewNumericDate(now),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
IssuedAt: jose.NewNumericDate(now),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now.Add(-time.Hour)
|
||||||
|
cert.NotAfter = now.Add(-time.Minute)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("step-cli"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
badSigner, _ := generateX5cToken(a1, otherSigner, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
|
Subject: "test.example.com",
|
||||||
|
Issuer: "step-cli",
|
||||||
|
NotBefore: jose.NewNumericDate(now),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now
|
||||||
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("foobar"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
badProvisioner, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
|
Subject: "test.example.com",
|
||||||
|
Issuer: "step-cli",
|
||||||
|
NotBefore: jose.NewNumericDate(now),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now
|
||||||
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("foobar"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
badIssuer, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
|
Subject: "test.example.com",
|
||||||
|
Issuer: "bad-issuer",
|
||||||
|
NotBefore: jose.NewNumericDate(now),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now
|
||||||
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("step-cli"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
badSubject, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/renew"},
|
||||||
|
Subject: "bad-subject",
|
||||||
|
Issuer: "step-cli",
|
||||||
|
NotBefore: jose.NewNumericDate(now),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now
|
||||||
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("step-cli"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
badNotBefore, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/sign"},
|
||||||
|
Subject: "test.example.com",
|
||||||
|
Issuer: "step-cli",
|
||||||
|
NotBefore: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(10 * time.Minute)),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now
|
||||||
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("step-cli"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
badExpiry, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/sign"},
|
||||||
|
Subject: "test.example.com",
|
||||||
|
Issuer: "step-cli",
|
||||||
|
NotBefore: jose.NewNumericDate(now.Add(-5 * time.Minute)),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(-time.Minute)),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now
|
||||||
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("step-cli"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
badIssuedAt, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/sign"},
|
||||||
|
Subject: "test.example.com",
|
||||||
|
Issuer: "step-cli",
|
||||||
|
NotBefore: jose.NewNumericDate(now),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
IssuedAt: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now
|
||||||
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("step-cli"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
badAudience, _ := generateX5cToken(a1, signer, jose.Claims{
|
||||||
|
Audience: []string{"https://example.com/1.0/sign"},
|
||||||
|
Subject: "test.example.com",
|
||||||
|
Issuer: "step-cli",
|
||||||
|
NotBefore: jose.NewNumericDate(now),
|
||||||
|
Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)),
|
||||||
|
}, provisioner.CertificateEnforcerFunc(func(cert *x509.Certificate) error {
|
||||||
|
cert.NotBefore = now
|
||||||
|
cert.NotAfter = now.Add(time.Hour)
|
||||||
|
b, err := asn1.Marshal(stepProvisionerASN1{int(provisioner.TypeJWK), []byte("step-cli"), nil, nil})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{
|
||||||
|
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 1},
|
||||||
|
Value: b,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
ott string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
authority *Authority
|
||||||
|
args args
|
||||||
|
want *x509.Certificate
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"ok", a1, args{ctx, t1}, c1, false},
|
||||||
|
{"ok expired cert", a1, args{ctx, t2}, c2, false},
|
||||||
|
{"fail token", a1, args{ctx, "not.a.token"}, nil, true},
|
||||||
|
{"fail token reuse", a1, args{ctx, t1}, nil, true},
|
||||||
|
{"fail token signature", a1, args{ctx, badSigner}, nil, true},
|
||||||
|
{"fail token provisioner", a1, args{ctx, badProvisioner}, nil, true},
|
||||||
|
{"fail token iss", a1, args{ctx, badIssuer}, nil, true},
|
||||||
|
{"fail token sub", a1, args{ctx, badSubject}, nil, true},
|
||||||
|
{"fail token iat", a1, args{ctx, badNotBefore}, nil, true},
|
||||||
|
{"fail token iat", a1, args{ctx, badExpiry}, nil, true},
|
||||||
|
{"fail token iat", a1, args{ctx, badIssuedAt}, nil, true},
|
||||||
|
{"fail token aud", a1, args{ctx, badAudience}, nil, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := tt.authority.AuthorizeRenewToken(tt.args.ctx, tt.args.ott)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Authority.AuthorizeRenewToken() error = %+v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Authority.AuthorizeRenewToken() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -272,28 +272,32 @@ func (c *Config) GetAudiences() provisioner.Audiences {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range c.DNSNames {
|
for _, name := range c.DNSNames {
|
||||||
|
hostname := toHostname(name)
|
||||||
audiences.Sign = append(audiences.Sign,
|
audiences.Sign = append(audiences.Sign,
|
||||||
fmt.Sprintf("https://%s/1.0/sign", toHostname(name)),
|
fmt.Sprintf("https://%s/1.0/sign", hostname),
|
||||||
fmt.Sprintf("https://%s/sign", toHostname(name)),
|
fmt.Sprintf("https://%s/sign", hostname),
|
||||||
fmt.Sprintf("https://%s/1.0/ssh/sign", toHostname(name)),
|
fmt.Sprintf("https://%s/1.0/ssh/sign", hostname),
|
||||||
fmt.Sprintf("https://%s/ssh/sign", toHostname(name)))
|
fmt.Sprintf("https://%s/ssh/sign", hostname))
|
||||||
|
audiences.Renew = append(audiences.Renew,
|
||||||
|
fmt.Sprintf("https://%s/1.0/renew", hostname),
|
||||||
|
fmt.Sprintf("https://%s/renew", hostname))
|
||||||
audiences.Revoke = append(audiences.Revoke,
|
audiences.Revoke = append(audiences.Revoke,
|
||||||
fmt.Sprintf("https://%s/1.0/revoke", toHostname(name)),
|
fmt.Sprintf("https://%s/1.0/revoke", hostname),
|
||||||
fmt.Sprintf("https://%s/revoke", toHostname(name)))
|
fmt.Sprintf("https://%s/revoke", hostname))
|
||||||
audiences.SSHSign = append(audiences.SSHSign,
|
audiences.SSHSign = append(audiences.SSHSign,
|
||||||
fmt.Sprintf("https://%s/1.0/ssh/sign", toHostname(name)),
|
fmt.Sprintf("https://%s/1.0/ssh/sign", hostname),
|
||||||
fmt.Sprintf("https://%s/ssh/sign", toHostname(name)),
|
fmt.Sprintf("https://%s/ssh/sign", hostname),
|
||||||
fmt.Sprintf("https://%s/1.0/sign", toHostname(name)),
|
fmt.Sprintf("https://%s/1.0/sign", hostname),
|
||||||
fmt.Sprintf("https://%s/sign", toHostname(name)))
|
fmt.Sprintf("https://%s/sign", hostname))
|
||||||
audiences.SSHRevoke = append(audiences.SSHRevoke,
|
audiences.SSHRevoke = append(audiences.SSHRevoke,
|
||||||
fmt.Sprintf("https://%s/1.0/ssh/revoke", toHostname(name)),
|
fmt.Sprintf("https://%s/1.0/ssh/revoke", hostname),
|
||||||
fmt.Sprintf("https://%s/ssh/revoke", toHostname(name)))
|
fmt.Sprintf("https://%s/ssh/revoke", hostname))
|
||||||
audiences.SSHRenew = append(audiences.SSHRenew,
|
audiences.SSHRenew = append(audiences.SSHRenew,
|
||||||
fmt.Sprintf("https://%s/1.0/ssh/renew", toHostname(name)),
|
fmt.Sprintf("https://%s/1.0/ssh/renew", hostname),
|
||||||
fmt.Sprintf("https://%s/ssh/renew", toHostname(name)))
|
fmt.Sprintf("https://%s/ssh/renew", hostname))
|
||||||
audiences.SSHRekey = append(audiences.SSHRekey,
|
audiences.SSHRekey = append(audiences.SSHRekey,
|
||||||
fmt.Sprintf("https://%s/1.0/ssh/rekey", toHostname(name)),
|
fmt.Sprintf("https://%s/1.0/ssh/rekey", hostname),
|
||||||
fmt.Sprintf("https://%s/ssh/rekey", toHostname(name)))
|
fmt.Sprintf("https://%s/ssh/rekey", hostname))
|
||||||
}
|
}
|
||||||
|
|
||||||
return audiences
|
return audiences
|
||||||
|
|
|
@ -35,9 +35,7 @@ type JWK struct {
|
||||||
EncryptedKey string `json:"encryptedKey,omitempty"`
|
EncryptedKey string `json:"encryptedKey,omitempty"`
|
||||||
Claims *Claims `json:"claims,omitempty"`
|
Claims *Claims `json:"claims,omitempty"`
|
||||||
Options *Options `json:"options,omitempty"`
|
Options *Options `json:"options,omitempty"`
|
||||||
// claimer *Claimer
|
ctl *Controller
|
||||||
// audiences Audiences
|
|
||||||
ctl *Controller
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetID returns the provisioner unique identifier. The name and credential id
|
// GetID returns the provisioner unique identifier. The name and credential id
|
||||||
|
|
|
@ -46,6 +46,7 @@ var ErrAllowTokenReuse = stderrors.New("allow token reuse")
|
||||||
// Audiences stores all supported audiences by request type.
|
// Audiences stores all supported audiences by request type.
|
||||||
type Audiences struct {
|
type Audiences struct {
|
||||||
Sign []string
|
Sign []string
|
||||||
|
Renew []string
|
||||||
Revoke []string
|
Revoke []string
|
||||||
SSHSign []string
|
SSHSign []string
|
||||||
SSHRevoke []string
|
SSHRevoke []string
|
||||||
|
@ -56,6 +57,7 @@ type Audiences struct {
|
||||||
// All returns all supported audiences across all request types in one list.
|
// All returns all supported audiences across all request types in one list.
|
||||||
func (a Audiences) All() (auds []string) {
|
func (a Audiences) All() (auds []string) {
|
||||||
auds = a.Sign
|
auds = a.Sign
|
||||||
|
auds = append(auds, a.Renew...)
|
||||||
auds = append(auds, a.Revoke...)
|
auds = append(auds, a.Revoke...)
|
||||||
auds = append(auds, a.SSHSign...)
|
auds = append(auds, a.SSHSign...)
|
||||||
auds = append(auds, a.SSHRevoke...)
|
auds = append(auds, a.SSHRevoke...)
|
||||||
|
@ -69,6 +71,7 @@ func (a Audiences) All() (auds []string) {
|
||||||
func (a Audiences) WithFragment(fragment string) Audiences {
|
func (a Audiences) WithFragment(fragment string) Audiences {
|
||||||
ret := Audiences{
|
ret := Audiences{
|
||||||
Sign: make([]string, len(a.Sign)),
|
Sign: make([]string, len(a.Sign)),
|
||||||
|
Renew: make([]string, len(a.Renew)),
|
||||||
Revoke: make([]string, len(a.Revoke)),
|
Revoke: make([]string, len(a.Revoke)),
|
||||||
SSHSign: make([]string, len(a.SSHSign)),
|
SSHSign: make([]string, len(a.SSHSign)),
|
||||||
SSHRevoke: make([]string, len(a.SSHRevoke)),
|
SSHRevoke: make([]string, len(a.SSHRevoke)),
|
||||||
|
@ -82,6 +85,13 @@ func (a Audiences) WithFragment(fragment string) Audiences {
|
||||||
ret.Sign[i] = s
|
ret.Sign[i] = s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for i, s := range a.Renew {
|
||||||
|
if u, err := url.Parse(s); err == nil {
|
||||||
|
ret.Renew[i] = u.ResolveReference(&url.URL{Fragment: fragment}).String()
|
||||||
|
} else {
|
||||||
|
ret.Renew[i] = s
|
||||||
|
}
|
||||||
|
}
|
||||||
for i, s := range a.Revoke {
|
for i, s := range a.Revoke {
|
||||||
if u, err := url.Parse(s); err == nil {
|
if u, err := url.Parse(s); err == nil {
|
||||||
ret.Revoke[i] = u.ResolveReference(&url.URL{Fragment: fragment}).String()
|
ret.Revoke[i] = u.ResolveReference(&url.URL{Fragment: fragment}).String()
|
||||||
|
|
30
ca/client.go
30
ca/client.go
|
@ -723,6 +723,36 @@ retry:
|
||||||
return &sign, nil
|
return &sign, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenewWithToken performs the renew request to the CA with the given
|
||||||
|
// authorization token and returns the api.SignResponse struct. This method is
|
||||||
|
// generally used to renew an expired certificate.
|
||||||
|
func (c *Client) RenewWithToken(token string) (*api.SignResponse, error) {
|
||||||
|
var retried bool
|
||||||
|
u := c.endpoint.ResolveReference(&url.URL{Path: "/renew"})
|
||||||
|
req, err := http.NewRequest("POST", u.String(), http.NoBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.RenewWithToken; error creating request")
|
||||||
|
}
|
||||||
|
req.Header.Add("Authorization", "Bearer "+token)
|
||||||
|
retry:
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.RenewWithToken; client POST %s failed", u)
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
if !retried && c.retryOnError(resp) {
|
||||||
|
retried = true
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
return nil, readError(resp.Body)
|
||||||
|
}
|
||||||
|
var sign api.SignResponse
|
||||||
|
if err := readJSON(resp.Body, &sign); err != nil {
|
||||||
|
return nil, errs.Wrapf(http.StatusInternalServerError, err, "client.RenewWithToken; error reading %s", u)
|
||||||
|
}
|
||||||
|
return &sign, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Rekey performs the rekey request to the CA and returns the api.SignResponse
|
// Rekey performs the rekey request to the CA and returns the api.SignResponse
|
||||||
// struct.
|
// struct.
|
||||||
func (c *Client) Rekey(req *api.RekeyRequest, tr http.RoundTripper) (*api.SignResponse, error) {
|
func (c *Client) Rekey(req *api.RekeyRequest, tr http.RoundTripper) (*api.SignResponse, error) {
|
||||||
|
|
|
@ -529,6 +529,74 @@ func TestClient_Renew(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClient_RenewWithToken(t *testing.T) {
|
||||||
|
ok := &api.SignResponse{
|
||||||
|
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
|
||||||
|
CaPEM: api.Certificate{Certificate: parseCertificate(rootPEM)},
|
||||||
|
CertChainPEM: []api.Certificate{
|
||||||
|
{Certificate: parseCertificate(certPEM)},
|
||||||
|
{Certificate: parseCertificate(rootPEM)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
response interface{}
|
||||||
|
responseCode int
|
||||||
|
wantErr bool
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{"ok", ok, 200, false, nil},
|
||||||
|
{"unauthorized", errs.Unauthorized("force"), 401, true, errors.New(errs.UnauthorizedDefaultMsg)},
|
||||||
|
{"empty request", errs.BadRequest("force"), 400, true, errors.New(errs.BadRequestPrefix)},
|
||||||
|
{"nil request", errs.BadRequest("force"), 400, true, errors.New(errs.BadRequestPrefix)},
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := httptest.NewServer(nil)
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c, err := NewClient(srv.URL, WithTransport(http.DefaultTransport))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("NewClient() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.Header.Get("Authorization") != "Bearer token" {
|
||||||
|
api.JSONStatus(w, errs.InternalServer("force"), 500)
|
||||||
|
} else {
|
||||||
|
api.JSONStatus(w, tt.response, tt.responseCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
got, err := c.RenewWithToken("token")
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
fmt.Printf("%+v", err)
|
||||||
|
t.Errorf("Client.RenewWithToken() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("Client.RenewWithToken() = %v, want nil", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
sc, ok := err.(errs.StatusCoder)
|
||||||
|
assert.Fatal(t, ok, "error does not implement StatusCoder interface")
|
||||||
|
assert.Equals(t, sc.StatusCode(), tt.responseCode)
|
||||||
|
assert.HasPrefix(t, err.Error(), tt.err.Error())
|
||||||
|
default:
|
||||||
|
if !reflect.DeepEqual(got, tt.response) {
|
||||||
|
t.Errorf("Client.RenewWithToken() = %v, want %v", got, tt.response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClient_Rekey(t *testing.T) {
|
func TestClient_Rekey(t *testing.T) {
|
||||||
ok := &api.SignResponse{
|
ok := &api.SignResponse{
|
||||||
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
|
ServerPEM: api.Certificate{Certificate: parseCertificate(certPEM)},
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -34,7 +34,7 @@ require (
|
||||||
github.com/urfave/cli v1.22.4
|
github.com/urfave/cli v1.22.4
|
||||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
|
||||||
go.step.sm/cli-utils v0.7.0
|
go.step.sm/cli-utils v0.7.0
|
||||||
go.step.sm/crypto v0.15.0
|
go.step.sm/crypto v0.15.3
|
||||||
go.step.sm/linkedca v0.10.0
|
go.step.sm/linkedca v0.10.0
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||||
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d
|
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -683,8 +683,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
|
||||||
go.step.sm/cli-utils v0.7.0 h1:2GvY5Muid1yzp7YQbfCCS+gK3q7zlHjjLL5Z0DXz8ds=
|
go.step.sm/cli-utils v0.7.0 h1:2GvY5Muid1yzp7YQbfCCS+gK3q7zlHjjLL5Z0DXz8ds=
|
||||||
go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/E=
|
go.step.sm/cli-utils v0.7.0/go.mod h1:Ur6bqA/yl636kCUJbp30J7Unv5JJ226eW2KqXPDwF/E=
|
||||||
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
go.step.sm/crypto v0.9.0/go.mod h1:+CYG05Mek1YDqi5WK0ERc6cOpKly2i/a5aZmU1sfGj0=
|
||||||
go.step.sm/crypto v0.15.0 h1:VioBln+x3+RoejgeBhvxkLGVYdWRy6PFiAaUUN29/E0=
|
go.step.sm/crypto v0.15.3 h1:f3GMl+aCydt294BZRjTYwpaXRqwwndvoTY2NLN4wu10=
|
||||||
go.step.sm/crypto v0.15.0/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g=
|
go.step.sm/crypto v0.15.3/go.mod h1:3G0yQr5lQqfEG0CMYz8apC/qMtjLRQlzflL2AxkcN+g=
|
||||||
go.step.sm/linkedca v0.10.0 h1:+bqymMRulHYkVde4l16FnqFVskoS6HCWJN5Z5cxAqF8=
|
go.step.sm/linkedca v0.10.0 h1:+bqymMRulHYkVde4l16FnqFVskoS6HCWJN5Z5cxAqF8=
|
||||||
go.step.sm/linkedca v0.10.0/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
go.step.sm/linkedca v0.10.0/go.mod h1:5uTRjozEGSPAZal9xJqlaD38cvJcLe3o1VAFVjqcORo=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
|
Loading…
Reference in a new issue