diff --git a/CHANGELOG.md b/CHANGELOG.md index 0543eace5..bf7f2f54b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. --- +## [TPB] - TBD + +### Added + +- Support for ML-DSA (FIPS 204) keys, available when built with Go 1.27 or + newer. JOSE-based flows (JWK, JWS and JWT tokens) do not support ML-DSA yet. + +### Deprecated + +- Go 1.25 support. Go 1.26 or newer is now required. + ## [0.30.2] - 2026-03-22 - Update golang.org/grpc to patch security advisory diff --git a/acme/account.go b/acme/account.go index 246e031ba..e6301af0d 100644 --- a/acme/account.go +++ b/acme/account.go @@ -19,7 +19,7 @@ type Account struct { Contact []string `json:"contact,omitempty"` Status Status `json:"status"` OrdersURL string `json:"orders"` - ExternalAccountBinding interface{} `json:"externalAccountBinding,omitempty"` + ExternalAccountBinding any `json:"externalAccountBinding,omitempty"` LocationPrefix string `json:"-"` ProvisionerID string `json:"-"` ProvisionerName string `json:"-"` @@ -34,7 +34,7 @@ func (a *Account) GetLocation() string { } // ToLog enables response logging. -func (a *Account) ToLog() (interface{}, error) { +func (a *Account) ToLog() (any, error) { b, err := json.Marshal(a) if err != nil { return nil, WrapErrorISE(err, "error marshaling account for logging") @@ -112,7 +112,7 @@ type ExternalAccountKey struct { AccountID string `json:"-"` HmacKey []byte `json:"-"` CreatedAt time.Time `json:"createdAt"` - BoundAt time.Time `json:"boundAt,omitempty"` + BoundAt time.Time `json:"boundAt,omitzero"` Policy *Policy `json:"policy,omitempty"` } diff --git a/acme/api/account.go b/acme/api/account.go index 3114dcb35..aa3cfb435 100644 --- a/acme/api/account.go +++ b/acme/api/account.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "net/http" + "slices" "github.com/go-chi/chi/v5" @@ -22,10 +23,8 @@ type NewAccountRequest struct { } func validateContacts(cs []string) error { - for _, c := range cs { - if c == "" { - return acme.NewError(acme.ErrorMalformedType, "contact cannot be empty string") - } + if slices.Contains(cs, "") { + return acme.NewError(acme.ErrorMalformedType, "contact cannot be empty string") } return nil } @@ -218,7 +217,7 @@ func GetOrUpdateAccount(w http.ResponseWriter, r *http.Request) { func logOrdersByAccount(w http.ResponseWriter, oids []string) { if rl, ok := w.(logging.ResponseLogger); ok { - m := map[string]interface{}{ + m := map[string]any{ "orders": oids, } rl.WithFields(m) diff --git a/acme/api/account_test.go b/acme/api/account_test.go index b69830f92..140a15e72 100644 --- a/acme/api/account_test.go +++ b/acme/api/account_test.go @@ -120,7 +120,7 @@ func createEABJWS(jwk *jose.JSONWebKey, hmacKey []byte, keyID, u string) (*jose. Key: hmacKey, }, &jose.SignerOptions{ - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "kid": keyID, "url": u, }, diff --git a/acme/api/eab.go b/acme/api/eab.go index 3dce69472..595c67520 100644 --- a/acme/api/eab.go +++ b/acme/api/eab.go @@ -53,8 +53,7 @@ func validateExternalAccountBinding(ctx context.Context, nar *NewAccountRequest) db := acme.MustDatabaseFromContext(ctx) externalAccountKey, err := db.GetExternalAccountKey(ctx, acmeProv.ID, keyID) if err != nil { - var ae *acme.Error - if errors.As(err, &ae) { + if _, ok := errors.AsType[*acme.Error](err); ok { return nil, acme.WrapError(acme.ErrorUnauthorizedType, err, "the field 'kid' references an unknown key") } return nil, acme.WrapErrorISE(err, "error retrieving external account key") diff --git a/acme/api/handler.go b/acme/api/handler.go index 0722bd9b3..9ad6ae36d 100644 --- a/acme/api/handler.go +++ b/acme/api/handler.go @@ -209,7 +209,7 @@ type Directory struct { } // ToLog enables response logging for the Directory type. -func (d *Directory) ToLog() (interface{}, error) { +func (d *Directory) ToLog() (any, error) { b, err := json.Marshal(d) if err != nil { return nil, acme.WrapErrorISE(err, "error marshaling directory for logging") diff --git a/acme/api/middleware.go b/acme/api/middleware.go index aa59e25fa..b31d1a6d8 100644 --- a/acme/api/middleware.go +++ b/acme/api/middleware.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "path" + "slices" "strings" "go.step.sm/crypto/jose" @@ -23,7 +24,7 @@ type nextHTTP = func(http.ResponseWriter, *http.Request) func logNonce(w http.ResponseWriter, nonce string) { if rl, ok := w.(logging.ResponseLogger); ok { - m := map[string]interface{}{ + m := map[string]any{ "nonce": nonce, } rl.WithFields(m) @@ -82,11 +83,9 @@ func verifyContentType(next nextHTTP) nextHTTP { } ct := r.Header.Get("Content-Type") - for _, e := range expected { - if ct == e { - next(w, r) - return - } + if slices.Contains(expected, ct) { + next(w, r) + return } render.Error(w, r, acme.NewError(acme.ErrorMalformedType, "expected content-type to be in %s, but got %s", expected, ct)) diff --git a/acme/api/middleware_test.go b/acme/api/middleware_test.go index 7dcbb6440..454af3c35 100644 --- a/acme/api/middleware_test.go +++ b/acme/api/middleware_test.go @@ -30,7 +30,7 @@ func testNext(w http.ResponseWriter, _ *http.Request) { w.Write(testBody) } -func newBaseContext(ctx context.Context, args ...interface{}) context.Context { +func newBaseContext(ctx context.Context, args ...any) context.Context { for _, a := range args { switch v := a.(type) { case acme.DB: @@ -1269,7 +1269,7 @@ func TestHandler_validateJWS(t *testing.T) { Protected: jose.Header{ Algorithm: jose.RS256, JSONWebKey: &pub, - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "url": u, }, }, @@ -1299,7 +1299,7 @@ func TestHandler_validateJWS(t *testing.T) { Protected: jose.Header{ Algorithm: jose.RS256, JSONWebKey: &pub, - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "url": u, }, }, @@ -1357,7 +1357,7 @@ func TestHandler_validateJWS(t *testing.T) { { Protected: jose.Header{ Algorithm: jose.ES256, - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "url": "foo", }, }, @@ -1386,7 +1386,7 @@ func TestHandler_validateJWS(t *testing.T) { Algorithm: jose.ES256, KeyID: "bar", JSONWebKey: &pub, - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "url": u, }, }, @@ -1410,7 +1410,7 @@ func TestHandler_validateJWS(t *testing.T) { { Protected: jose.Header{ Algorithm: jose.ES256, - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "url": u, }, }, @@ -1435,7 +1435,7 @@ func TestHandler_validateJWS(t *testing.T) { Protected: jose.Header{ Algorithm: jose.ES256, KeyID: "bar", - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "url": u, }, }, @@ -1465,7 +1465,7 @@ func TestHandler_validateJWS(t *testing.T) { Protected: jose.Header{ Algorithm: jose.ES256, JSONWebKey: &pub, - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "url": u, }, }, @@ -1495,7 +1495,7 @@ func TestHandler_validateJWS(t *testing.T) { Protected: jose.Header{ Algorithm: jose.RS256, JSONWebKey: &pub, - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "url": u, }, }, diff --git a/acme/api/order.go b/acme/api/order.go index 4a2e5b334..03c8ab9a1 100644 --- a/acme/api/order.go +++ b/acme/api/order.go @@ -26,8 +26,8 @@ import ( // NewOrderRequest represents the body for a NewOrder request. type NewOrderRequest struct { Identifiers []acme.Identifier `json:"identifiers"` - NotBefore time.Time `json:"notBefore,omitempty,omitzero"` - NotAfter time.Time `json:"notAfter,omitempty,omitzero"` + NotBefore time.Time `json:"notBefore,omitzero"` + NotAfter time.Time `json:"notAfter,omitzero"` } // Validate validates a new-order request body. @@ -310,8 +310,8 @@ func newACMEPolicyEngine(eak *acme.ExternalAccountKey) (policy.X509Policy, error } func trimIfWildcard(value string) (string, bool) { - if strings.HasPrefix(value, "*.") { - return strings.TrimPrefix(value, "*."), true + if after, ok := strings.CutPrefix(value, "*."); ok { + return after, true } return value, false } diff --git a/acme/api/revoke.go b/acme/api/revoke.go index 46a48af37..edcc90574 100644 --- a/acme/api/revoke.go +++ b/acme/api/revoke.go @@ -212,7 +212,7 @@ func wrapUnauthorizedError(cert *x509.Certificate, unauthorizedIdentifiers []acm // logRevoke logs successful revocation of certificate func logRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) { if rl, ok := w.(logging.ResponseLogger); ok { - rl.WithFields(map[string]interface{}{ + rl.WithFields(map[string]any{ "serial": ri.Serial, "reasonCode": ri.ReasonCode, "reason": ri.Reason, diff --git a/acme/api/revoke_test.go b/acme/api/revoke_test.go index 207a4d0c7..0d9f48693 100644 --- a/acme/api/revoke_test.go +++ b/acme/api/revoke_test.go @@ -37,8 +37,10 @@ import ( ) // v is a utility function to return the pointer to an integer +// +//go:fix inline func v(v int) *int { - return &v + return new(v) } func generateSerial() (*big.Int, error) { @@ -47,7 +49,6 @@ func generateSerial() (*big.Int, error) { // generateCertKeyPair generates fresh x509 certificate/key pairs for testing func generateCertKeyPair() (*x509.Certificate, crypto.Signer, error) { - pub, priv, err := keyutil.GenerateKeyPair("EC", "P-256", 0) if err != nil { return nil, nil, err @@ -103,7 +104,7 @@ const noKeyID = keyID("") // If nonce is empty, it will not be encoded into the header. // Implementation taken from github.com/mholt/acmez, which seems to be based on // https://github.com/golang/crypto/blob/master/acme/jws.go. -func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid keyID, nonce, u string) ([]byte, error) { +func jwsEncodeJSON(claimset any, key crypto.Signer, kid keyID, nonce, u string) ([]byte, error) { alg, sha := jwsHasher(key.Public()) if alg == "" || !sha.Available() { return nil, errUnsupportedKey @@ -236,14 +237,12 @@ func jwkEncode(pub crypto.PublicKey) (string, error) { if p.BitSize%8 != 0 { n++ } - x := pub.X.Bytes() - if n > len(x) { - x = append(make([]byte, n-len(x)), x...) - } - y := pub.Y.Bytes() - if n > len(y) { - y = append(make([]byte, n-len(y)), y...) + b, err := pub.Bytes() + if err != nil { + return "", err } + x, y := b[1:n+1], b[n+1:] + // Field order is important. // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. return fmt.Sprintf(`{"crv":%q,"kty":"EC","x":%q,"y":%q}`, @@ -331,17 +330,17 @@ func Test_validateReasonCode(t *testing.T) { }, { name: "fail/too-low", - reasonCode: v(-1), + reasonCode: new(-1), want: acme.NewError(acme.ErrorBadRevocationReasonType, "reasonCode out of bounds"), }, { name: "fail/too-high", - reasonCode: v(11), + reasonCode: new(11), want: acme.NewError(acme.ErrorBadRevocationReasonType, "reasonCode out of bounds"), }, { name: "fail/missing-7", - reasonCode: v(7), + reasonCode: new(7), want: acme.NewError(acme.ErrorBadRevocationReasonType, "reasonCode out of bounds"), }, @@ -509,7 +508,7 @@ func TestHandler_RevokeCert(t *testing.T) { Protected: jose.Header{ Algorithm: jose.ES256, KeyID: "bar", - ExtraHeaders: map[jose.HeaderKey]interface{}{ + ExtraHeaders: map[jose.HeaderKey]any{ "url": revokeURL, }, }, @@ -788,7 +787,7 @@ func TestHandler_RevokeCert(t *testing.T) { assert.FatalError(t, err) jwsPayload := &revokePayload{ Certificate: base64.RawURLEncoding.EncodeToString(cert.Raw), - ReasonCode: v(2), + ReasonCode: new(2), } jwsBytes, err := jwsEncodeJSON(rp, unauthorizedKey, "", "nonce", revokeURL) assert.FatalError(t, err) @@ -888,7 +887,7 @@ func TestHandler_RevokeCert(t *testing.T) { "fail/invalid-reasoncode": func(t *testing.T) test { invalidReasonPayload := &revokePayload{ Certificate: base64.RawURLEncoding.EncodeToString(cert.Raw), - ReasonCode: v(7), + ReasonCode: new(7), } invalidReasonCodePayloadBytes, err := json.Marshal(invalidReasonPayload) assert.FatalError(t, err) diff --git a/acme/authorization.go b/acme/authorization.go index cb6290734..a4064a542 100644 --- a/acme/authorization.go +++ b/acme/authorization.go @@ -21,7 +21,7 @@ type Authorization struct { } // ToLog enables response logging. -func (az *Authorization) ToLog() (interface{}, error) { +func (az *Authorization) ToLog() (any, error) { b, err := json.Marshal(az) if err != nil { return nil, WrapErrorISE(err, "error marshaling authz for logging") diff --git a/acme/challenge.go b/acme/challenge.go index 5d7a4fbf6..4e1e3f53c 100644 --- a/acme/challenge.go +++ b/acme/challenge.go @@ -35,6 +35,7 @@ import ( "github.com/smallstep/go-attestation/attest" "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" + "go.step.sm/crypto/mldsa" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" @@ -96,7 +97,7 @@ type Challenge struct { } // ToLog enables response logging. -func (ch *Challenge) ToLog() (interface{}, error) { +func (ch *Challenge) ToLog() (any, error) { b, err := json.Marshal(ch) if err != nil { return nil, WrapErrorISE(err, "error marshaling challenge for logging") @@ -228,8 +229,7 @@ func dns01ChallengeHost(domain string) string { } func tlsAlert(err error) uint8 { - var opErr *net.OpError - if errors.As(err, &opErr) { + if opErr, ok := errors.AsType[*net.OpError](err); ok { v := reflect.ValueOf(opErr.Err) if v.Kind() == reflect.Uint8 { return cast.Uint8(v.Uint()) @@ -378,11 +378,8 @@ func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebK h := sha256.Sum256([]byte(expectedKeyAuth)) expected := base64.RawURLEncoding.EncodeToString(h[:]) var found bool - for _, r := range txtRecords { - if r == expected { - found = true - break - } + if slices.Contains(txtRecords, expected) { + found = true } if !found { return storeError(ctx, db, ch, false, NewError(ErrorRejectedIdentifierType, @@ -787,8 +784,8 @@ type payloadType struct { } type attestationObject struct { - Format string `json:"fmt"` - AttStatement map[string]interface{} `json:"attStmt,omitempty"` + Format string `json:"fmt"` + AttStatement map[string]any `json:"attStmt,omitempty"` } // TODO(bweeks): move attestation verification to a shared package. @@ -851,8 +848,7 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose case "android-key": data, err := doAndroidKeyAttestationFormat(ctx, prov, ch, jwk, &att) if err != nil { - var acmeError *Error - if errors.As(err, &acmeError) { + if acmeError, ok := errors.AsType[*Error](err); ok { if acmeError.Status == 500 { return acmeError } @@ -881,8 +877,7 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose case "apple": data, err := doAppleAttestationFormat(ctx, prov, ch, &att) if err != nil { - var acmeError *Error - if errors.As(err, &acmeError) { + if acmeError, ok := errors.AsType[*Error](err); ok { if acmeError.Status == 500 { return acmeError } @@ -917,8 +912,7 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose case "step": data, err := doStepAttestationFormat(ctx, prov, ch, jwk, &att) if err != nil { - var acmeError *Error - if errors.As(err, &acmeError) { + if acmeError, ok := errors.AsType[*Error](err); ok { if acmeError.Status == 500 { return acmeError } @@ -946,8 +940,7 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose case "tpm": data, err := doTPMAttestationFormat(ctx, prov, ch, jwk, &att) if err != nil { - var acmeError *Error - if errors.As(err, &acmeError) { + if acmeError, ok := errors.AsType[*Error](err); ok { if acmeError.Status == 500 { return acmeError } @@ -1027,7 +1020,7 @@ func doTPMAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, return nil, NewDetailedError(ErrorBadAttestationStatementType, "version %q is not supported", ver) } - x5c, ok := att.AttStatement["x5c"].([]interface{}) + x5c, ok := att.AttStatement["x5c"].([]any) if !ok { return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c not present") } @@ -1340,7 +1333,7 @@ func doAppleAttestationFormat(_ context.Context, prov Provisioner, _ *Challenge, roots.AddCert(root) } - x5c, ok := att.AttStatement["x5c"].([]interface{}) + x5c, ok := att.AttStatement["x5c"].([]any) if !ok { return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c not present") } @@ -1468,8 +1461,7 @@ type androidKeyAttestationData struct { // extension? That should immediately precede the cert with the key attestation // extension. func findAndroidAttestationCert(certs []*x509.Certificate) *x509.Certificate { - for i := len(certs) - 1; i >= 0; i-- { - cert := certs[i] + for _, cert := range slices.Backward(certs) { for _, ext := range cert.Extensions { if ext.Id.Equal(oidAndroidAttestation) { return cert @@ -1610,26 +1602,9 @@ func doAndroidKeyAttestationFormat(ctx context.Context, prov Provisioner, ch *Ch return nil, NewDetailedError(ErrorBadAttestationStatementType, "no attestation certificate with OID 1.3.6.1.4.1.11129.2.1.17 found in the cert chain") } - switch pub := attCert.PublicKey.(type) { - case *ecdsa.PublicKey: - if pub.Curve != elliptic.P256() { - return nil, NewDetailedError(ErrorBadAttestationStatementType, "unsupported elliptic curve %s", pub.Curve) - } - sum := sha256.Sum256([]byte(keyAuth)) - if !ecdsa.VerifyASN1(pub, sum[:], sig) { - return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed validating signature") - } - case *rsa.PublicKey: - sum := sha256.Sum256([]byte(keyAuth)) - if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, sum[:], sig); err != nil { - return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed validating signature") - } - case ed25519.PublicKey: - if !ed25519.Verify(pub, []byte(keyAuth), sig) { - return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed validating signature") - } - default: - return nil, NewDetailedError(ErrorBadAttestationStatementType, "unsupported public key type %T", pub) + // Verify attestation statement sig + if err := validateAttestationSignature(attCert, keyAuth, sig); err != nil { + return nil, err } data := &androidKeyAttestationData{ @@ -1737,7 +1712,7 @@ func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, } // Extract x5c and verify certificate - x5c, ok := att.AttStatement["x5c"].([]interface{}) + x5c, ok := att.AttStatement["x5c"].([]any) if !ok { return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c not present") } @@ -1785,31 +1760,15 @@ func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, if err := cbor.Unmarshal(csig, &sig); err != nil { return nil, NewDetailedError(ErrorBadAttestationStatementType, "sig is malformed") } + keyAuth, err := KeyAuthorization(ch.Token, jwk) if err != nil { return nil, err } - switch pub := leaf.PublicKey.(type) { - case *ecdsa.PublicKey: - if pub.Curve != elliptic.P256() { - return nil, NewDetailedError(ErrorBadAttestationStatementType, "unsupported elliptic curve %s", pub.Curve) - } - sum := sha256.Sum256([]byte(keyAuth)) - if !ecdsa.VerifyASN1(pub, sum[:], sig) { - return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed to validate signature") - } - case *rsa.PublicKey: - sum := sha256.Sum256([]byte(keyAuth)) - if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, sum[:], sig); err != nil { - return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed to validate signature") - } - case ed25519.PublicKey: - if !ed25519.Verify(pub, []byte(keyAuth), sig) { - return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed to validate signature") - } - default: - return nil, NewDetailedError(ErrorBadAttestationStatementType, "unsupported public key type %T", pub) + // Verify attestation statement sig + if err := validateAttestationSignature(leaf, keyAuth, sig); err != nil { + return nil, err } // Parse attestation data: @@ -1829,6 +1788,38 @@ func doStepAttestationFormat(_ context.Context, prov Provisioner, ch *Challenge, return data, nil } +// validateAttestationSignature verifies that sig is a signature of the key +// authorization made with the private key of the given attestation certificate. +func validateAttestationSignature(cert *x509.Certificate, keyAuth string, sig []byte) error { + switch pub := cert.PublicKey.(type) { + case *ecdsa.PublicKey: + if pub.Curve != elliptic.P256() { + return NewDetailedError(ErrorBadAttestationStatementType, "unsupported elliptic curve %s", pub.Curve) + } + sum := sha256.Sum256([]byte(keyAuth)) + if !ecdsa.VerifyASN1(pub, sum[:], sig) { + return NewDetailedError(ErrorBadAttestationStatementType, "failed validating signature") + } + case *rsa.PublicKey: + sum := sha256.Sum256([]byte(keyAuth)) + if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, sum[:], sig); err != nil { + return NewDetailedError(ErrorBadAttestationStatementType, "failed validating signature") + } + case ed25519.PublicKey: + if !ed25519.Verify(pub, []byte(keyAuth), sig) { + return NewDetailedError(ErrorBadAttestationStatementType, "failed validating signature") + } + case *mldsa.PublicKey: + if err := mldsa.Verify(pub, []byte(keyAuth), sig, nil); err != nil { + return NewDetailedError(ErrorBadAttestationStatementType, "failed validating signature") + } + default: + return NewDetailedError(ErrorBadAttestationStatementType, "unsupported public key type %T", pub) + } + + return nil +} + // searchSerialNumber searches the certificate extensions, looking for a serial // number encoded in one of them. It is not guaranteed that a certificate contains // an extension carrying a serial number, so the result can be empty. @@ -1880,8 +1871,7 @@ func reverseAddr(ip net.IP) (arpa string) { // Must be IPv6 buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) // Add it, in reverse, to the buffer - for i := len(ip) - 1; i >= 0; i-- { - v := ip[i] + for _, v := range slices.Backward(ip) { buf = append(buf, hexit[v&0xF], '.', hexit[v>>4], diff --git a/acme/challenge_test.go b/acme/challenge_test.go index 0cd29a463..6177eb926 100644 --- a/acme/challenge_test.go +++ b/acme/challenge_test.go @@ -217,12 +217,12 @@ func mustAttestApple(t *testing.T, nonce string) ([]byte, *x509.Certificate, *x5 fatalError(t, err) attObj, err := cbor.Marshal(struct { - Format string `json:"fmt"` - AttStatement map[string]interface{} `json:"attStmt,omitempty"` + Format string `json:"fmt"` + AttStatement map[string]any `json:"attStmt,omitempty"` }{ Format: "apple", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, }, }) fatalError(t, err) @@ -265,12 +265,12 @@ func mustAttestYubikey(t *testing.T, _, keyAuthorization string, serial int) ([] fatalError(t, err) attObj, err := cbor.Marshal(struct { - Format string `json:"fmt"` - AttStatement map[string]interface{} `json:"attStmt,omitempty"` + Format string `json:"fmt"` + AttStatement map[string]any `json:"attStmt,omitempty"` }{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, @@ -319,12 +319,12 @@ func mustAttestStepManagedDeviceID(t *testing.T, _, keyAuthorization, serialNumb require.NoError(t, err) attObj, err := cbor.Marshal(struct { - Format string `json:"fmt"` - AttStatement map[string]interface{} `json:"attStmt,omitempty"` + Format string `json:"fmt"` + AttStatement map[string]any `json:"attStmt,omitempty"` }{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, @@ -486,8 +486,7 @@ func Test_storeError(t *testing.T) { tc := run(t) if err := storeError(context.Background(), tc.db, tc.ch, tc.markInvalid, err); err != nil { if assert.Error(t, tc.err) { - var k *Error - if errors.As(err, &k) { + if k, ok := errors.AsType[*Error](err); ok { assert.Equal(t, tc.err.Type, k.Type) assert.Equal(t, tc.err.Detail, k.Detail) assert.Equal(t, tc.err.Status, k.Status) @@ -540,8 +539,7 @@ func TestKeyAuthorization(t *testing.T) { tc := run(t) if ka, err := KeyAuthorization(tc.token, tc.jwk); err != nil { if assert.Error(t, tc.err) { - var k *Error - if errors.As(err, &k) { + if k, ok := errors.AsType[*Error](err); ok { assert.Equal(t, tc.err.Type, k.Type) assert.Equal(t, tc.err.Detail, k.Detail) assert.Equal(t, tc.err.Status, k.Status) @@ -1124,7 +1122,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, "accID", accountID) return []string{"orderID"}, nil }, - MockCreateOidcToken: func(ctx context.Context, orderID string, idToken map[string]interface{}) error { + MockCreateOidcToken: func(ctx context.Context, orderID string, idToken map[string]any) 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)) @@ -1369,7 +1367,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, "accID", accountID) return []string{"orderID"}, nil }, - MockCreateDpopToken: func(ctx context.Context, orderID string, dpop map[string]interface{}) error { + MockCreateDpopToken: func(ctx context.Context, orderID string, dpop map[string]any) error { assert.Equal(t, "orderID", orderID) assert.Equal(t, "token", dpop["chal"].(string)) assert.Equal(t, "wireapp://%40alice_wire@wire.com", dpop["handle"].(string)) @@ -1530,8 +1528,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= ctx = NewClientContext(ctx, tc.vc) err := tc.ch.Validate(ctx, tc.db, tc.jwk, tc.payload) if tc.err != nil { - var k *Error - if errors.As(err, &k) { + if k, ok := errors.AsType[*Error](err); ok { assert.Equal(t, tc.err.Type, k.Type) assert.Equal(t, tc.err.Detail, k.Detail) assert.Equal(t, tc.err.Status, k.Status) @@ -1969,8 +1966,7 @@ func TestHTTP01Validate(t *testing.T) { ctx := NewClientContext(context.Background(), tc.vc) if err := http01Validate(ctx, tc.ch, tc.db, tc.jwk); err != nil { if assert.Error(t, tc.err) { - var k *Error - if errors.As(err, &k) { + if k, ok := errors.AsType[*Error](err); ok { assert.Equal(t, tc.err.Type, k.Type) assert.Equal(t, tc.err.Detail, k.Detail) assert.Equal(t, tc.err.Status, k.Status) @@ -2270,8 +2266,7 @@ func TestDNS01Validate(t *testing.T) { ctx := NewClientContext(context.Background(), tc.vc) if err := dns01Validate(ctx, tc.ch, tc.db, tc.jwk); err != nil { if assert.Error(t, tc.err) { - var k *Error - if errors.As(err, &k) { + if k, ok := errors.AsType[*Error](err); ok { assert.Equal(t, tc.err.Type, k.Type) assert.Equal(t, tc.err.Detail, k.Detail) assert.Equal(t, tc.err.Status, k.Status) @@ -3367,8 +3362,7 @@ func TestTLSALPN01Validate(t *testing.T) { ctx := NewClientContext(context.Background(), tc.vc) if err := tlsalpn01Validate(ctx, tc.ch, tc.db, tc.jwk); err != nil { if assert.Error(t, tc.err) { - var k *Error - if errors.As(err, &k) { + if k, ok := errors.AsType[*Error](err); ok { assert.Equal(t, tc.err.Type, k.Type) assert.Equal(t, tc.err.Detail, k.Detail) assert.Equal(t, tc.err.Status, k.Status) @@ -3563,8 +3557,8 @@ func Test_doAppleAttestationFormat(t *testing.T) { }{ {"ok", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &attestationObject{ Format: "apple", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, }, }}, &appleAttestationData{ Nonce: []byte("nonce"), @@ -3576,50 +3570,50 @@ func Test_doAppleAttestationFormat(t *testing.T) { }, false}, {"fail apple issuer", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{}, &attestationObject{ Format: "apple", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, }, }}, nil, true}, {"fail missing x5c", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &attestationObject{ Format: "apple", - AttStatement: map[string]interface{}{ + AttStatement: map[string]any{ "foo": "bar", }, }}, nil, true}, {"fail empty issuer", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &attestationObject{ Format: "apple", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{}, + AttStatement: map[string]any{ + "x5c": []any{}, }, }}, nil, true}, {"fail leaf type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &attestationObject{ Format: "apple", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{"leaf", ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{"leaf", ca.Intermediate.Raw}, }, }}, nil, true}, {"fail leaf parse", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &attestationObject{ Format: "apple", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw[:100], ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw[:100], ca.Intermediate.Raw}, }, }}, nil, true}, {"fail intermediate type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &attestationObject{ Format: "apple", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, "intermediate"}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, "intermediate"}, }, }}, nil, true}, {"fail intermediate parse", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &attestationObject{ Format: "apple", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw[:100]}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw[:100]}, }, }}, nil, true}, {"fail verify", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{}, &attestationObject{ Format: "apple", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw}, }, }}, nil, true}, } @@ -3725,8 +3719,8 @@ func Test_doStepAttestationFormat(t *testing.T) { }{ {"ok", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, @@ -3737,8 +3731,8 @@ func Test_doStepAttestationFormat(t *testing.T) { }, false}, {"ok/step-managed-device-id", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leafWithStepManagedDeviceID.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leafWithStepManagedDeviceID.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, @@ -3749,15 +3743,15 @@ func Test_doStepAttestationFormat(t *testing.T) { }, false}, {"fail yubico issuer", args{ctx, mustAttestationProvisioner(t, nil), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail x5c type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ + AttStatement: map[string]any{ "x5c": [][]byte{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, @@ -3765,112 +3759,112 @@ func Test_doStepAttestationFormat(t *testing.T) { }}, nil, true}, {"fail x5c empty", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{}, + AttStatement: map[string]any{ + "x5c": []any{}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail leaf type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{"leaf", ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{"leaf", ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail leaf parse", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw[:100], ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw[:100], ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail intermediate type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, "intermediate"}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, "intermediate"}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail intermediate parse", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw[:100]}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw[:100]}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail verify", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail sig type", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": string(cborSig), }, }}, nil, true}, {"fail sig unmarshal", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": []byte("bad-sig"), }, }}, nil, true}, {"fail keyAuthorization", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, &jose.JSONWebKey{Key: []byte("not an asymmetric key")}, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail sig verify P-256", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": otherCBORSig, }, }}, nil, true}, {"fail sig verify P-384", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{makeLeaf(mustSigner("EC", "P-384", 0), serialNumber).Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{makeLeaf(mustSigner("EC", "P-384", 0), serialNumber).Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail sig verify RSA", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{makeLeaf(mustSigner("RSA", "", 2048), serialNumber).Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{makeLeaf(mustSigner("RSA", "", 2048), serialNumber).Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail sig verify Ed25519", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{makeLeaf(mustSigner("OKP", "Ed25519", 0), serialNumber).Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{makeLeaf(mustSigner("OKP", "Ed25519", 0), serialNumber).Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, }}, nil, true}, {"fail unmarshal serial number", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{makeLeaf(signer, []byte("bad-serial")).Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{makeLeaf(signer, []byte("bad-serial")).Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, @@ -3963,8 +3957,8 @@ func Test_doStepAttestationFormat_noCAIntermediate(t *testing.T) { }{ {"fail no intermediate", args{ctx, mustAttestationProvisioner(t, caRoot), &Challenge{Token: "token"}, jwk, &attestationObject{ Format: "step", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, @@ -4011,11 +4005,11 @@ func Test_deviceAttest01Validate(t *testing.T) { }) require.NoError(t, err) attObj, err := cbor.Marshal(struct { - Format string `json:"fmt"` - AttStatement map[string]interface{} `json:"attStmt,omitempty"` + Format string `json:"fmt"` + AttStatement map[string]any `json:"attStmt,omitempty"` }{ Format: "step", - AttStatement: map[string]interface{}{ + AttStatement: map[string]any{ "alg": -7, "sig": "", }, @@ -4028,11 +4022,11 @@ func Test_deviceAttest01Validate(t *testing.T) { }) require.NoError(t, err) unsupportedFormatAttObj, err := cbor.Marshal(struct { - Format string `json:"fmt"` - AttStatement map[string]interface{} `json:"attStmt,omitempty"` + Format string `json:"fmt"` + AttStatement map[string]any `json:"attStmt,omitempty"` }{ Format: "unsupported-format", - AttStatement: map[string]interface{}{ + AttStatement: map[string]any{ "alg": -7, "sig": "", }, @@ -4432,11 +4426,11 @@ func Test_deviceAttest01Validate(t *testing.T) { "ok/doAppleAttestationFormat-storeError": func(t *testing.T) test { ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, nil)) attObj, err := cbor.Marshal(struct { - Format string `json:"fmt"` - AttStatement map[string]interface{} `json:"attStmt,omitempty"` + Format string `json:"fmt"` + AttStatement map[string]any `json:"attStmt,omitempty"` }{ Format: "apple", - AttStatement: map[string]interface{}{}, + AttStatement: map[string]any{}, }) require.NoError(t, err) payload, err := json.Marshal(struct { @@ -4695,11 +4689,11 @@ func Test_deviceAttest01Validate(t *testing.T) { require.NoError(t, err) ctx := NewProvisionerContext(context.Background(), mustAttestationProvisioner(t, caRoot)) attObj, err := cbor.Marshal(struct { - Format string `json:"fmt"` - AttStatement map[string]interface{} `json:"attStmt,omitempty"` + Format string `json:"fmt"` + AttStatement map[string]any `json:"attStmt,omitempty"` }{ Format: "step", - AttStatement: map[string]interface{}{ + AttStatement: map[string]any{ "alg": -7, "sig": cborSig, }, @@ -4847,12 +4841,12 @@ func Test_deviceAttest01Validate(t *testing.T) { require.NoError(t, err) leaf := makeLeaf(signer, serialNumber) attObj, err := cbor.Marshal(struct { - Format string `json:"fmt"` - AttStatement map[string]interface{} `json:"attStmt,omitempty"` + Format string `json:"fmt"` + AttStatement map[string]any `json:"attStmt,omitempty"` }{ Format: "bogus-format", - AttStatement: map[string]interface{}{ - "x5c": []interface{}{leaf.Raw, ca.Intermediate.Raw}, + AttStatement: map[string]any{ + "x5c": []any{leaf.Raw, ca.Intermediate.Raw}, "alg": -7, "sig": cborSig, }, diff --git a/acme/challenge_wire_test.go b/acme/challenge_wire_test.go index 147b58c98..f2efa4e8e 100644 --- a/acme/challenge_wire_test.go +++ b/acme/challenge_wire_test.go @@ -276,8 +276,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, StatusInvalid, ch.Status) assert.Equal(t, string(valueBytes), ch.Value) if assert.NotNil(t, ch.Error) { - var k *Error // NOTE: the error is not returned up, but stored with the challenge instead - if errors.As(ch.Error, &k) { + if k, ok := errors.AsType[*Error](ch.Error); ok { assert.Equal(t, "urn:ietf:params:acme:error:rejectedIdentifier", k.Type) assert.Equal(t, "The server will not issue certificates for the identifier", k.Detail) assert.Equal(t, 400, k.Status) @@ -885,7 +884,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, "accID", accountID) return []string{"orderID"}, nil }, - MockCreateDpopToken: func(ctx context.Context, orderID string, dpop map[string]interface{}) error { + MockCreateDpopToken: func(ctx context.Context, orderID string, dpop map[string]any) error { assert.Equal(t, "orderID", orderID) assert.Equal(t, "token", dpop["chal"].(string)) assert.Equal(t, "wireapp://%40alice_wire@wire.com", dpop["handle"].(string)) @@ -2646,7 +2645,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, "accID", accountID) return []string{"orderID"}, nil }, - MockCreateDpopToken: func(ctx context.Context, orderID string, dpop map[string]interface{}) error { + MockCreateDpopToken: func(ctx context.Context, orderID string, dpop map[string]any) error { assert.Equal(t, "orderID", orderID) assert.Equal(t, "token", dpop["chal"].(string)) assert.Equal(t, "wireapp://%40alice_wire@wire.com", dpop["handle"].(string)) @@ -2662,8 +2661,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= tc := run(t) err := wireDPOP01Validate(tc.ctx, tc.ch, tc.db, tc.jwk, tc.payload) if tc.expectedErr != nil { - var k *Error - if errors.As(err, &k) { + if k, ok := errors.AsType[*Error](err); ok { assert.Equal(t, tc.expectedErr.Type, k.Type) assert.Equal(t, tc.expectedErr.Detail, k.Detail) assert.Equal(t, tc.expectedErr.Status, k.Status) @@ -2923,8 +2921,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, ChallengeType("wire-oidc-01"), updch.Type) assert.Equal(t, string(valueBytes), updch.Value) if assert.NotNil(t, updch.Error) { - var k *Error // NOTE: the error is not returned up, but stored with the challenge instead - if errors.As(updch.Error, &k) { + if k, ok := errors.AsType[*Error](updch.Error); ok { assert.Equal(t, "urn:ietf:params:acme:error:rejectedIdentifier", k.Type) assert.Equal(t, "The server will not issue certificates for the identifier", k.Detail) assert.Equal(t, 400, k.Status) @@ -3031,8 +3028,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, ChallengeType("wire-oidc-01"), updch.Type) assert.Equal(t, string(valueBytes), updch.Value) if assert.NotNil(t, updch.Error) { - var k *Error // NOTE: the error is not returned up, but stored with the challenge instead - if errors.As(updch.Error, &k) { + if k, ok := errors.AsType[*Error](updch.Error); ok { assert.Equal(t, "urn:ietf:params:acme:error:rejectedIdentifier", k.Type) assert.Equal(t, "The server will not issue certificates for the identifier", k.Detail) assert.Equal(t, 400, k.Status) @@ -3242,8 +3238,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, ChallengeType("wire-oidc-01"), updch.Type) assert.Equal(t, string(valueBytes), updch.Value) if assert.NotNil(t, updch.Error) { - var k *Error // NOTE: the error is not returned up, but stored with the challenge instead - if errors.As(updch.Error, &k) { + if k, ok := errors.AsType[*Error](updch.Error); ok { assert.Equal(t, "urn:ietf:params:acme:error:rejectedIdentifier", k.Type) assert.Equal(t, "The server will not issue certificates for the identifier", k.Detail) assert.Equal(t, 400, k.Status) @@ -3679,7 +3674,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, "accID", accountID) return []string{"orderID"}, nil }, - MockCreateOidcToken: func(ctx context.Context, orderID string, idToken map[string]interface{}) error { + MockCreateOidcToken: func(ctx context.Context, orderID string, idToken map[string]any) 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)) @@ -3899,7 +3894,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= assert.Equal(t, "accID", accountID) return []string{"orderID"}, nil }, - MockCreateOidcToken: func(ctx context.Context, orderID string, idToken map[string]interface{}) error { + MockCreateOidcToken: func(ctx context.Context, orderID string, idToken map[string]any) 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)) @@ -3917,8 +3912,7 @@ MCowBQYDK2VwAyEA5c+4NKZSNQcR1T8qN6SjwgdPZQ0Ge12Ylx/YeGAJ35k= } err := wireOIDC01Validate(tc.ctx, tc.ch, tc.db, tc.jwk, tc.payload) if tc.expectedErr != nil { - var k *Error - if errors.As(err, &k) { + if k, ok := errors.AsType[*Error](err); ok { assert.Equal(t, tc.expectedErr.Type, k.Type) assert.Equal(t, tc.expectedErr.Detail, k.Detail) assert.Equal(t, tc.expectedErr.Status, k.Status) diff --git a/acme/common.go b/acme/common.go index 8e5586a84..400d4db04 100644 --- a/acme/common.go +++ b/acme/common.go @@ -109,7 +109,7 @@ func MustProvisionerFromContext(ctx context.Context) Provisioner { // MockProvisioner for testing type MockProvisioner struct { - Mret1 interface{} + Mret1 any Merr error MgetID func() string MgetName func() string diff --git a/acme/db.go b/acme/db.go index bcbed4176..be5939263 100644 --- a/acme/db.go +++ b/acme/db.go @@ -63,10 +63,10 @@ type DB interface { type WireDB interface { DB 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) + CreateDpopToken(ctx context.Context, orderID string, dpop map[string]any) error + GetDpopToken(ctx context.Context, orderID string) (map[string]any, error) + CreateOidcToken(ctx context.Context, orderID string, idToken map[string]any) error + GetOidcToken(ctx context.Context, orderID string) (map[string]any, error) } type dbKey struct{} @@ -132,7 +132,7 @@ type MockDB struct { MockGetOrdersByAccountID func(ctx context.Context, accountID string) ([]string, error) MockUpdateOrder func(ctx context.Context, o *Order) error - MockRet1 interface{} + MockRet1 any MockError error } @@ -142,10 +142,10 @@ type MockDB struct { type MockWireDB struct { MockDB 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 + MockGetDpopToken func(ctx context.Context, orderID string) (map[string]any, error) + MockCreateDpopToken func(ctx context.Context, orderID string, dpop map[string]any) error + MockGetOidcToken func(ctx context.Context, orderID string) (map[string]any, error) + MockCreateOidcToken func(ctx context.Context, orderID string, idToken map[string]any) error } // CreateAccount mock. diff --git a/acme/db/nosql/eab.go b/acme/db/nosql/eab.go index e2a437ddf..342a6e6a5 100644 --- a/acme/db/nosql/eab.go +++ b/acme/db/nosql/eab.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "encoding/json" + "slices" "sync" "time" @@ -289,11 +290,9 @@ func (db *DB) addEAKID(ctx context.Context, provisionerID, eakID string) error { } } - for _, id := range eakIDs { - if id == eakID { - // return an error when a duplicate ID is found - return errors.Errorf("eakID %s already exists for provisioner %s", eakID, provisionerID) - } + if slices.Contains(eakIDs, eakID) { + // return an error when a duplicate ID is found + return errors.Errorf("eakID %s already exists for provisioner %s", eakID, provisionerID) } var newEAKIDs []string @@ -301,8 +300,8 @@ func (db *DB) addEAKID(ctx context.Context, provisionerID, eakID string) error { newEAKIDs = append(newEAKIDs, eakID) var ( - _old interface{} = eakIDs - _new interface{} = newEAKIDs + _old any = eakIDs + _new any = newEAKIDs ) // ensure that the DB gets the expected value when the slice is empty; otherwise @@ -338,8 +337,8 @@ func (db *DB) deleteEAKID(ctx context.Context, provisionerID, eakID string) erro newEAKIDs := removeElement(eakIDs, eakID) var ( - _old interface{} = eakIDs - _new interface{} = newEAKIDs + _old any = eakIDs + _new any = newEAKIDs ) // ensure that the DB gets the expected value when the slice is empty; otherwise diff --git a/acme/db/nosql/nosql.go b/acme/db/nosql/nosql.go index b2921f55e..8bfb6ac5a 100644 --- a/acme/db/nosql/nosql.go +++ b/acme/db/nosql/nosql.go @@ -50,7 +50,7 @@ func New(db nosqlDB.DB) (*DB, error) { // save writes the new data to the database, overwriting the old data if it // existed. -func (db *DB) save(_ context.Context, id string, nu, old interface{}, typ string, table []byte) error { +func (db *DB) save(_ context.Context, id string, nu, old any, typ string, table []byte) error { var ( err error newB []byte diff --git a/acme/db/nosql/nosql_test.go b/acme/db/nosql/nosql_test.go index d9c0b484f..8a2c862cf 100644 --- a/acme/db/nosql/nosql_test.go +++ b/acme/db/nosql/nosql_test.go @@ -55,8 +55,8 @@ func (et errorThrower) MarshalJSON() ([]byte, error) { func TestDB_save(t *testing.T) { type test struct { db nosql.DB - nu interface{} - old interface{} + nu any + old any err error } var tests = map[string]test{ diff --git a/acme/db/nosql/order.go b/acme/db/nosql/order.go index 983fbe8d5..fcdb2d5ca 100644 --- a/acme/db/nosql/order.go +++ b/acme/db/nosql/order.go @@ -21,10 +21,10 @@ type dbOrder struct { Identifiers []acme.Identifier `json:"identifiers"` AuthorizationIDs []string `json:"authorizationIDs"` Status acme.Status `json:"status"` - NotBefore time.Time `json:"notBefore,omitempty"` - NotAfter time.Time `json:"notAfter,omitempty"` + NotBefore time.Time `json:"notBefore"` + NotAfter time.Time `json:"notAfter"` CreatedAt time.Time `json:"createdAt"` - ExpiresAt time.Time `json:"expiresAt,omitempty"` + ExpiresAt time.Time `json:"expiresAt"` CertificateID string `json:"certificate,omitempty"` Error *acme.Error `json:"error,omitempty"` } @@ -159,8 +159,8 @@ func (db *DB) updateAddOrderIDs(ctx context.Context, accID string, includeReadyO } pendOids = append(pendOids, addOids...) var ( - _old interface{} = oldOids - _new interface{} = pendOids + _old any = oldOids + _new any = pendOids ) switch { case len(oldOids) == 0 && len(pendOids) == 0: diff --git a/acme/errors_test.go b/acme/errors_test.go index 8e586a127..ed7f9f1d1 100644 --- a/acme/errors_test.go +++ b/acme/errors_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -func mustJSON(t *testing.T, m map[string]interface{}) string { +func mustJSON(t *testing.T, m map[string]any) string { t.Helper() b, err := json.Marshal(m) @@ -18,16 +18,16 @@ func mustJSON(t *testing.T, m map[string]interface{}) string { } func TestError_WithAdditionalErrorDetail(t *testing.T) { - internalJSON := mustJSON(t, map[string]interface{}{ + internalJSON := mustJSON(t, map[string]any{ "detail": "The server experienced an internal error", "type": "urn:ietf:params:acme:error:serverInternal", }) malformedErr := NewError(ErrorMalformedType, "malformed error") // will result in Err == nil behavior - malformedJSON := mustJSON(t, map[string]interface{}{ + malformedJSON := mustJSON(t, map[string]any{ "detail": "The request message was malformed", "type": "urn:ietf:params:acme:error:malformed", }) - withDetailJSON := mustJSON(t, map[string]interface{}{ + withDetailJSON := mustJSON(t, map[string]any{ "detail": "Attestation statement cannot be verified: invalid property", "type": "urn:ietf:params:acme:error:badAttestationStatement", }) diff --git a/acme/order.go b/acme/order.go index 78ac9a0d1..875d3aa5b 100644 --- a/acme/order.go +++ b/acme/order.go @@ -64,7 +64,7 @@ type Order struct { } // ToLog enables response logging. -func (o *Order) ToLog() (interface{}, error) { +func (o *Order) ToLog() (any, error) { b, err := json.Marshal(o) if err != nil { return nil, WrapErrorISE(err, "error marshaling order for logging") @@ -307,8 +307,7 @@ func (o *Order) Finalize(ctx context.Context, db DB, csr *x509.CertificateReques }, signOps...) if err != nil { // Add subproblem for webhook errors, others can be added later. - var webhookErr *webhook.Error - if errors.As(err, &webhookErr) { + if webhookErr, ok := errors.AsType[*webhook.Error](err); ok { acmeError := NewDetailedError(ErrorUnauthorizedType, "%s", webhookErr.Error()) acmeError.AddSubproblems(Subproblem{ Type: fmt.Sprintf("urn:smallstep:acme:error:%s", webhookErr.Code), diff --git a/acme/order_test.go b/acme/order_test.go index cdcbdb518..a89a118a2 100644 --- a/acme/order_test.go +++ b/acme/order_test.go @@ -276,7 +276,7 @@ type mockSignAuth struct { signWithContext func(ctx context.Context, csr *x509.CertificateRequest, signOpts provisioner.SignOptions, extraOpts ...provisioner.SignOption) ([]*x509.Certificate, error) areSANsAllowed func(ctx context.Context, sans []string) error loadProvisionerByName func(string) (provisioner.Interface, error) - ret1, ret2 interface{} + ret1, ret2 any err error } @@ -1133,16 +1133,16 @@ func TestOrder_Finalize(t *testing.T) { return &Authorization{ID: id, Status: StatusValid}, nil }, }, - MockGetDpopToken: func(ctx context.Context, orderID string) (map[string]interface{}, error) { + MockGetDpopToken: func(ctx context.Context, orderID string) (map[string]any, error) { assert.Equals(t, orderID, o.ID) - dpopMap := map[string]interface{}{ + dpopMap := map[string]any{ "dpop": "a-dpop-token", } return dpopMap, nil }, - MockGetOidcToken: func(ctx context.Context, orderID string) (map[string]interface{}, error) { + MockGetOidcToken: func(ctx context.Context, orderID string) (map[string]any, error) { assert.Equals(t, orderID, o.ID) - oidcMap := map[string]interface{}{ + oidcMap := map[string]any{ "oidc": "a-oidc-token", } return oidcMap, nil @@ -1226,16 +1226,16 @@ func TestOrder_Finalize(t *testing.T) { return &Authorization{ID: id, Status: StatusValid}, nil }, }, - MockGetDpopToken: func(ctx context.Context, orderID string) (map[string]interface{}, error) { + MockGetDpopToken: func(ctx context.Context, orderID string) (map[string]any, error) { assert.Equals(t, orderID, o.ID) - dpopMap := map[string]interface{}{ + dpopMap := map[string]any{ "dpop": "a-dpop-token", } return dpopMap, nil }, - MockGetOidcToken: func(ctx context.Context, orderID string) (map[string]interface{}, error) { + MockGetOidcToken: func(ctx context.Context, orderID string) (map[string]any, error) { assert.Equals(t, orderID, o.ID) - oidcMap := map[string]interface{}{ + oidcMap := map[string]any{ "oidc": "a-oidc-token", } return oidcMap, nil @@ -1614,16 +1614,16 @@ func TestOrder_Finalize(t *testing.T) { return &Authorization{ID: id, Status: StatusValid}, nil }, }, - MockGetDpopToken: func(ctx context.Context, orderID string) (map[string]interface{}, error) { + MockGetDpopToken: func(ctx context.Context, orderID string) (map[string]any, error) { assert.Equals(t, orderID, o.ID) - dpopMap := map[string]interface{}{ + dpopMap := map[string]any{ "dpop": "a-dpop-token", } return dpopMap, nil }, - MockGetOidcToken: func(ctx context.Context, orderID string) (map[string]interface{}, error) { + MockGetOidcToken: func(ctx context.Context, orderID string) (map[string]any, error) { assert.Equals(t, orderID, o.ID) - oidcMap := map[string]interface{}{ + oidcMap := map[string]any{ "oidc": "a-oidc-token", } return oidcMap, nil @@ -1706,16 +1706,16 @@ func TestOrder_Finalize(t *testing.T) { return &Authorization{ID: id, Status: StatusValid}, nil }, }, - MockGetDpopToken: func(ctx context.Context, orderID string) (map[string]interface{}, error) { + MockGetDpopToken: func(ctx context.Context, orderID string) (map[string]any, error) { assert.Equals(t, orderID, o.ID) - dpopMap := map[string]interface{}{ + dpopMap := map[string]any{ "dpop": "a-dpop-token", } return dpopMap, nil }, - MockGetOidcToken: func(ctx context.Context, orderID string) (map[string]interface{}, error) { + MockGetOidcToken: func(ctx context.Context, orderID string) (map[string]any, error) { assert.Equals(t, orderID, o.ID) - oidcMap := map[string]interface{}{ + oidcMap := map[string]any{ "oidc": "a-oidc-token", } return oidcMap, nil diff --git a/api/api.go b/api/api.go index 09d2c83fb..ae30d2d0b 100644 --- a/api/api.go +++ b/api/api.go @@ -21,6 +21,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/pkg/errors" + "go.step.sm/crypto/mldsa" "go.step.sm/crypto/sshutil" "golang.org/x/crypto/ssh" @@ -539,7 +540,7 @@ type stepProvisioner struct { func logOtt(w http.ResponseWriter, token string) { if rl, ok := w.(logging.ResponseLogger); ok { - rl.WithFields(map[string]interface{}{ + rl.WithFields(map[string]any{ "ott": token, }) } @@ -548,7 +549,7 @@ func logOtt(w http.ResponseWriter, token string) { // LogCertificate adds certificate fields to the log message. func LogCertificate(w http.ResponseWriter, cert *x509.Certificate) { if rl, ok := w.(logging.ResponseLogger); ok { - m := map[string]interface{}{ + m := map[string]any{ "serial": cert.SerialNumber.String(), "subject": cert.Subject.CommonName, "issuer": cert.Issuer.CommonName, @@ -594,7 +595,7 @@ func LogSSHCertificate(w http.ResponseWriter, cert *ssh.Certificate) { userOrHost = "user" } certificateType := fmt.Sprintf("%s %s certificate", parts[0], userOrHost) // e.g. ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate - m := map[string]interface{}{ + m := map[string]any{ "serial": cert.Serial, "principals": cert.ValidPrincipals, "valid-from": time.Unix(cast.Int64(cert.ValidAfter), 0).Format(time.RFC3339), @@ -660,6 +661,8 @@ func fmtPublicKey(cert *x509.Certificate) string { params = strconv.Itoa(pk.Size() * 8) case ed25519.PublicKey: return cert.PublicKeyAlgorithm.String() + case *mldsa.PublicKey: + return pk.Parameters().String() case *dsa.PublicKey: params = strconv.Itoa(pk.Q.BitLen() * 8) default: diff --git a/api/api_test.go b/api/api_test.go index 15794bc1b..5e5163e7a 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -191,7 +191,7 @@ func mockMustAuthority(t *testing.T, a Authority) { } type mockAuthority struct { - ret1, ret2 interface{} + ret1, ret2 any err error authorize func(ctx context.Context, ott string) ([]provisioner.SignOption, error) authorizeRenewToken func(ctx context.Context, ott string) (*x509.Certificate, error) @@ -681,7 +681,7 @@ func TestSignRequest_Validate(t *testing.T) { } type mockProvisioner struct { - ret1, ret2, ret3 interface{} + ret1, ret2, ret3 any err error getID func() string getIDForToken func() string @@ -1487,7 +1487,7 @@ func Test_fmtPublicKey(t *testing.T) { } type args struct { - pub, priv interface{} + pub, priv any cert *x509.Certificate } tests := []struct { @@ -1516,7 +1516,7 @@ func Test_fmtPublicKey(t *testing.T) { } } -func mustCertificate(t *testing.T, pub, priv interface{}) *x509.Certificate { +func mustCertificate(t *testing.T, pub, priv any) *x509.Certificate { template := x509.Certificate{ SerialNumber: big.NewInt(1), Subject: pkix.Name{ diff --git a/api/read/read.go b/api/read/read.go index 6f75c41a2..4394cc840 100644 --- a/api/read/read.go +++ b/api/read/read.go @@ -17,7 +17,7 @@ import ( // JSON reads JSON from the request body and stores it in the value // pointed to by v. -func JSON(r io.Reader, v interface{}) error { +func JSON(r io.Reader, v any) error { if err := json.NewDecoder(r).Decode(v); err != nil { return errs.BadRequestErr(err, "error decoding json") } diff --git a/api/read/read_test.go b/api/read/read_test.go index fe29903fa..3f5f9ed68 100644 --- a/api/read/read_test.go +++ b/api/read/read_test.go @@ -23,15 +23,15 @@ import ( func TestJSON(t *testing.T) { type args struct { r io.Reader - v interface{} + v any } tests := []struct { name string args args wantErr bool }{ - {"ok", args{strings.NewReader(`{"foo":"bar"}`), make(map[string]interface{})}, false}, - {"fail", args{strings.NewReader(`{"foo"}`), make(map[string]interface{})}, true}, + {"ok", args{strings.NewReader(`{"foo":"bar"}`), make(map[string]any)}, false}, + {"fail", args{strings.NewReader(`{"foo"}`), make(map[string]any)}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -41,16 +41,15 @@ func TestJSON(t *testing.T) { } if tt.wantErr { - var e *errs.Error - if errors.As(err, &e) { + if e, ok := errors.AsType[*errs.Error](err); ok { if code := e.StatusCode(); code != 400 { t.Errorf("error.StatusCode() = %v, wants 400", code) } } else { t.Errorf("error type = %T, wants *Error", err) } - } else if !reflect.DeepEqual(tt.args.v, map[string]interface{}{"foo": "bar"}) { - t.Errorf("JSON value = %v, wants %v", tt.args.v, map[string]interface{}{"foo": "bar"}) + } else if !reflect.DeepEqual(tt.args.v, map[string]any{"foo": "bar"}) { + t.Errorf("JSON value = %v, wants %v", tt.args.v, map[string]any{"foo": "bar"}) } }) } diff --git a/api/render/render.go b/api/render/render.go index 1c66280c4..8e089db1e 100644 --- a/api/render/render.go +++ b/api/render/render.go @@ -13,7 +13,7 @@ import ( ) // JSON is shorthand for JSONStatus(w, v, http.StatusOK). -func JSON(w http.ResponseWriter, r *http.Request, v interface{}) { +func JSON(w http.ResponseWriter, r *http.Request, v any) { JSONStatus(w, r, v, http.StatusOK) } @@ -22,23 +22,20 @@ func JSON(w http.ResponseWriter, r *http.Request, v interface{}) { // // JSONStatus sets the Content-Type of w to application/json unless one is // specified. -func JSONStatus(w http.ResponseWriter, r *http.Request, v interface{}, status int) { +func JSONStatus(w http.ResponseWriter, r *http.Request, v any, status int) { setContentTypeUnlessPresent(w, "application/json") w.WriteHeader(status) if err := json.NewEncoder(w).Encode(v); err != nil { - var errUnsupportedType *json.UnsupportedTypeError - if errors.As(err, &errUnsupportedType) { + if _, ok := errors.AsType[*json.UnsupportedTypeError](err); ok { panic(err) } - var errUnsupportedValue *json.UnsupportedValueError - if errors.As(err, &errUnsupportedValue) { + if _, ok := errors.AsType[*json.UnsupportedValueError](err); ok { panic(err) } - var errMarshalError *json.MarshalerError - if errors.As(err, &errMarshalError) { + if _, ok := errors.AsType[*json.MarshalerError](err); ok { panic(err) } } @@ -88,8 +85,7 @@ type RenderableError interface { func Error(rw http.ResponseWriter, r *http.Request, err error) { log.Error(rw, r, err) - var re RenderableError - if errors.As(err, &re) { + if re, ok := errors.AsType[RenderableError](err); ok { re.Render(rw, r) return @@ -117,8 +113,7 @@ func statusCodeFromError(err error) (code int) { } for err != nil { - var sc StatusCodedError - if errors.As(err, &sc) { + if sc, ok := errors.AsType[StatusCodedError](err); ok { code = sc.StatusCode() break diff --git a/api/render/render_test.go b/api/render/render_test.go index d7ee37fd8..2653abb9e 100644 --- a/api/render/render_test.go +++ b/api/render/render_test.go @@ -19,7 +19,7 @@ func TestJSON(t *testing.T) { rec := httptest.NewRecorder() rw := logging.NewResponseLogger(rec) r := httptest.NewRequest("POST", "/test", http.NoBody) - JSON(rw, r, map[string]interface{}{"foo": "bar"}) + JSON(rw, r, map[string]any{"foo": "bar"}) assert.Equal(t, http.StatusOK, rec.Result().StatusCode) assert.Equal(t, "application/json", rec.Header().Get("Content-Type")) diff --git a/api/revoke.go b/api/revoke.go index 7d87646bd..42b66082b 100644 --- a/api/revoke.go +++ b/api/revoke.go @@ -117,7 +117,7 @@ func Revoke(w http.ResponseWriter, r *http.Request) { func logRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) { if rl, ok := w.(logging.ResponseLogger); ok { - rl.WithFields(map[string]interface{}{ + rl.WithFields(map[string]any{ "serial": ri.Serial, "reasonCode": ri.ReasonCode, "reason": ri.Reason, diff --git a/api/sign.go b/api/sign.go index bff417638..56469e531 100644 --- a/api/sign.go +++ b/api/sign.go @@ -16,8 +16,8 @@ import ( type SignRequest struct { CsrPEM CertificateRequest `json:"csr"` OTT string `json:"ott"` - NotAfter TimeDuration `json:"notAfter,omitempty"` - NotBefore TimeDuration `json:"notBefore,omitempty"` + NotAfter TimeDuration `json:"notAfter"` + NotBefore TimeDuration `json:"notBefore"` TemplateData json.RawMessage `json:"templateData,omitempty"` } diff --git a/api/ssh.go b/api/ssh.go index dd70e5edb..546668e45 100644 --- a/api/ssh.go +++ b/api/ssh.go @@ -45,10 +45,10 @@ type SSHSignRequest struct { CertType string `json:"certType,omitempty"` KeyID string `json:"keyID,omitempty"` Principals []string `json:"principals,omitempty"` - ValidAfter TimeDuration `json:"validAfter,omitempty"` - ValidBefore TimeDuration `json:"validBefore,omitempty"` + ValidAfter TimeDuration `json:"validAfter"` + ValidBefore TimeDuration `json:"validBefore"` AddUserPublicKey []byte `json:"addUserPublicKey,omitempty"` - IdentityCSR CertificateRequest `json:"identityCSR,omitempty"` + IdentityCSR CertificateRequest `json:"identityCSR"` TemplateData json.RawMessage `json:"templateData,omitempty"` } diff --git a/api/sshRevoke.go b/api/sshRevoke.go index 68e9a2bea..d18e989d1 100644 --- a/api/sshRevoke.go +++ b/api/sshRevoke.go @@ -90,7 +90,7 @@ func SSHRevoke(w http.ResponseWriter, r *http.Request) { func logSSHRevoke(w http.ResponseWriter, ri *authority.RevokeOptions) { if rl, ok := w.(logging.ResponseLogger); ok { - rl.WithFields(map[string]interface{}{ + rl.WithFields(map[string]any{ "serial": ri.Serial, "reasonCode": ri.ReasonCode, "reason": ri.Reason, diff --git a/authority/admin/api/admin_test.go b/authority/admin/api/admin_test.go index 44857a2eb..eaab204d5 100644 --- a/authority/admin/api/admin_test.go +++ b/authority/admin/api/admin_test.go @@ -26,7 +26,7 @@ import ( type mockAdminAuthority struct { MockLoadProvisionerByName func(name string) (provisioner.Interface, error) MockGetProvisioners func(nextCursor string, limit int) (provisioner.List, string, error) - MockRet1, MockRet2 interface{} // TODO: refactor the ret1/ret2 into those two + MockRet1, MockRet2 any // TODO: refactor the ret1/ret2 into those two MockErr error MockIsAdminAPIEnabled func() bool MockLoadAdminByID func(id string) (*linkedca.Admin, bool) diff --git a/authority/admin/db.go b/authority/admin/db.go index 63940a8a3..c58278cd6 100644 --- a/authority/admin/db.go +++ b/authority/admin/db.go @@ -123,7 +123,7 @@ type MockDB struct { MockDeleteAuthorityPolicy func(ctx context.Context) error MockError error - MockRet1 interface{} + MockRet1 any } // CreateProvisioner mock. diff --git a/authority/admin/db/nosql/nosql.go b/authority/admin/db/nosql/nosql.go index 02acf72a2..9f5f0a727 100644 --- a/authority/admin/db/nosql/nosql.go +++ b/authority/admin/db/nosql/nosql.go @@ -36,7 +36,7 @@ func New(db nosqlDB.DB, authorityID string) (*DB, error) { // save writes the new data to the database, overwriting the old data if it // existed. -func (db *DB) save(_ context.Context, id string, nu, old interface{}, typ string, table []byte) error { +func (db *DB) save(_ context.Context, id string, nu, old any, typ string, table []byte) error { var ( err error newB []byte diff --git a/authority/admin/db/nosql/policy_test.go b/authority/admin/db/nosql/policy_test.go index 833fbc874..49190ae18 100644 --- a/authority/admin/db/nosql/policy_test.go +++ b/authority/admin/db/nosql/policy_test.go @@ -72,8 +72,7 @@ func TestDB_getDBAuthorityPolicyBytes(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db} if b, err := d.getDBAuthorityPolicyBytes(tc.ctx, tc.authorityID); err != nil { - var ae *admin.Error - if errors.As(err, &ae) { + if ae, ok := errors.AsType[*admin.Error](err); ok { if assert.NotNil(t, tc.adminErr) { assert.Equals(t, ae.Type, tc.adminErr.Type) assert.Equals(t, ae.Detail, tc.adminErr.Detail) @@ -208,8 +207,7 @@ func TestDB_getDBAuthorityPolicy(t *testing.T) { dbp, err := d.getDBAuthorityPolicy(tc.ctx, tc.authorityID) switch { case err != nil: - var ae *admin.Error - if errors.As(err, &ae) { + if ae, ok := errors.AsType[*admin.Error](err); ok { if assert.NotNil(t, tc.adminErr) { assert.Equals(t, ae.Type, tc.adminErr.Type) assert.Equals(t, ae.Detail, tc.adminErr.Detail) @@ -309,8 +307,7 @@ func TestDB_CreateAuthorityPolicy(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: tc.authorityID} if err := d.CreateAuthorityPolicy(tc.ctx, tc.policy); err != nil { - var ae *admin.Error - if errors.As(err, &ae) { + if ae, ok := errors.AsType[*admin.Error](err); ok { if assert.NotNil(t, tc.adminErr) { assert.Equals(t, ae.Type, tc.adminErr.Type) assert.Equals(t, ae.Detail, tc.adminErr.Detail) @@ -406,8 +403,7 @@ func TestDB_GetAuthorityPolicy(t *testing.T) { d := DB{db: tc.db, authorityID: tc.authorityID} got, err := d.GetAuthorityPolicy(tc.ctx) if err != nil { - var ae *admin.Error - if errors.As(err, &ae) { + if ae, ok := errors.AsType[*admin.Error](err); ok { if assert.NotNil(t, tc.adminErr) { assert.Equals(t, ae.Type, tc.adminErr.Type) assert.Equals(t, ae.Detail, tc.adminErr.Detail) @@ -578,8 +574,7 @@ func TestDB_UpdateAuthorityPolicy(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: tc.authorityID} if err := d.UpdateAuthorityPolicy(tc.ctx, tc.policy); err != nil { - var ae *admin.Error - if errors.As(err, &ae) { + if ae, ok := errors.AsType[*admin.Error](err); ok { if assert.NotNil(t, tc.adminErr) { assert.Equals(t, ae.Type, tc.adminErr.Type) assert.Equals(t, ae.Detail, tc.adminErr.Detail) @@ -718,8 +713,7 @@ func TestDB_DeleteAuthorityPolicy(t *testing.T) { t.Run(name, func(t *testing.T) { d := DB{db: tc.db, authorityID: tc.authorityID} if err := d.DeleteAuthorityPolicy(tc.ctx); err != nil { - var ae *admin.Error - if errors.As(err, &ae) { + if ae, ok := errors.AsType[*admin.Error](err); ok { if assert.NotNil(t, tc.adminErr) { assert.Equals(t, ae.Type, tc.adminErr.Type) assert.Equals(t, ae.Detail, tc.adminErr.Detail) diff --git a/authority/admin/errors.go b/authority/admin/errors.go index a14e2dee0..054644982 100644 --- a/authority/admin/errors.go +++ b/authority/admin/errors.go @@ -125,7 +125,7 @@ func (e *Error) IsType(pt ProblemType) bool { } // NewError creates a new Error type. -func NewError(pt ProblemType, msg string, args ...interface{}) *Error { +func NewError(pt ProblemType, msg string, args ...any) *Error { return newError(pt, errors.Errorf(msg, args...)) } @@ -150,12 +150,12 @@ func newError(pt ProblemType, err error) *Error { } // NewErrorISE creates a new ErrorServerInternalType Error. -func NewErrorISE(msg string, args ...interface{}) *Error { +func NewErrorISE(msg string, args ...any) *Error { return NewError(ErrorServerInternalType, msg, args...) } // WrapError attempts to wrap the internal error. -func WrapError(typ ProblemType, err error, msg string, args ...interface{}) *Error { +func WrapError(typ ProblemType, err error, msg string, args ...any) *Error { var ee *Error switch { case err == nil: @@ -173,7 +173,7 @@ func WrapError(typ ProblemType, err error, msg string, args ...interface{}) *Err } // WrapErrorISE shortcut to wrap an internal server error type. -func WrapErrorISE(err error, msg string, args ...interface{}) *Error { +func WrapErrorISE(err error, msg string, args ...any) *Error { return WrapError(ErrorServerInternalType, err, msg, args...) } @@ -196,7 +196,7 @@ func (e *Error) Cause() error { } // ToLog implements the EnableLogger interface. -func (e *Error) ToLog() (interface{}, error) { +func (e *Error) ToLog() (any, error) { b, err := json.Marshal(e) if err != nil { return nil, WrapErrorISE(err, "error marshaling authority.Error for logging") diff --git a/authority/administrator/collection.go b/authority/administrator/collection.go index 716877d15..f95c80818 100644 --- a/authority/administrator/collection.go +++ b/authority/administrator/collection.go @@ -230,7 +230,7 @@ func (c *Collection) Find(cursor string, limit int) ([]*linkedca.Admin, string) return slice, "" } -func loadAdmin(m *sync.Map, key interface{}) (*linkedca.Admin, bool) { +func loadAdmin(m *sync.Map, key any) (*linkedca.Admin, bool) { val, ok := m.Load(key) if !ok { return nil, false diff --git a/authority/authority.go b/authority/authority.go index 8dd6a0b8e..b91d88bb7 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -843,7 +843,7 @@ func (a *Authority) init() error { a.templates = templates.DefaultTemplates() } if a.templates.Data == nil { - a.templates.Data = make(map[string]interface{}) + a.templates.Data = make(map[string]any) } a.templates.Data["Step"] = tmplVars } diff --git a/authority/authorize.go b/authority/authorize.go index 74ed8936e..f65095ad4 100644 --- a/authority/authorize.go +++ b/authority/authorize.go @@ -228,7 +228,7 @@ func (a *Authority) UseToken(ctx context.Context, token string, prov provisioner // Authorize grabs the method from the context and authorizes the request by // validating the one-time-token. func (a *Authority) Authorize(ctx context.Context, token string) ([]provisioner.SignOption, error) { - var opts = []interface{}{errs.WithKeyVal("token", token)} + var opts = []any{errs.WithKeyVal("token", token)} switch m := provisioner.MethodFromContext(ctx); m { case provisioner.SignMethod, provisioner.SignIdentityMethod: @@ -257,7 +257,7 @@ func (a *Authority) Authorize(ctx context.Context, token string) ([]provisioner. _, signOpts, err := a.authorizeSSHRekey(ctx, token) return signOpts, errs.Wrap(http.StatusInternalServerError, err, "authority.Authorize", opts...) default: - return nil, errs.InternalServer("authority.Authorize; method %d is not supported", append([]interface{}{m}, opts...)...) + return nil, errs.InternalServer("authority.Authorize; method %d is not supported", append([]any{m}, opts...)...) } } @@ -305,7 +305,7 @@ func (a *Authority) authorizeRevoke(ctx context.Context, token string) error { // TODO(mariano): should we authorize by default? func (a *Authority) authorizeRenew(ctx context.Context, cert *x509.Certificate) (provisioner.Interface, error) { serial := cert.SerialNumber.String() - var opts = []interface{}{errs.WithKeyVal("serialNumber", serial)} + var opts = []any{errs.WithKeyVal("serialNumber", serial)} isRevoked, err := a.IsRevoked(serial) if err != nil { diff --git a/authority/config/types.go b/authority/config/types.go index 5ca3b15fb..a54d4b8a3 100644 --- a/authority/config/types.go +++ b/authority/config/types.go @@ -2,6 +2,7 @@ package config import ( "encoding/json" + "slices" "github.com/pkg/errors" ) @@ -24,12 +25,7 @@ func (s multiString) HasEmpties() bool { if len(s) == 0 { return true } - for _, ss := range s { - if ss == "" { - return true - } - } - return false + return slices.Contains(s, "") } // MarshalJSON marshals the multistring as a string or a slice of strings . With diff --git a/authority/export.go b/authority/export.go index 0380a9382..824a2e122 100644 --- a/authority/export.go +++ b/authority/export.go @@ -230,7 +230,7 @@ func mustDuration(d *provisioner.Duration) string { return d.String() } -func mustMarshalToStruct(v interface{}) *structpb.Struct { +func mustMarshalToStruct(v any) *structpb.Struct { b, err := json.Marshal(v) if err != nil { panic(errors.Wrapf(err, "error marshaling %T", v)) diff --git a/authority/linkedca.go b/authority/linkedca.go index a452f67cf..70eb8cc42 100644 --- a/authority/linkedca.go +++ b/authority/linkedca.go @@ -448,14 +448,14 @@ func serializeCertificate(crt *x509.Certificate) string { } func serializeCertificateChain(fullchain ...*x509.Certificate) string { - var chain string + var chain strings.Builder for _, crt := range fullchain { - chain += string(pem.EncodeToMemory(&pem.Block{ + chain.WriteString(string(pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: crt.Raw, - })) + }))) } - return chain + return chain.String() } func serializeSSHCertificate(crt *ssh.Certificate) string { @@ -599,7 +599,7 @@ func login(authority, token string, csr *x509.CertificateRequest, signer crypto. // Add intermediates to the tls.Certificate last := len(bundle) - 1 - for i := 0; i < last; i++ { + for i := range last { cert.Certificate = append(cert.Certificate, bundle[i].Raw) } diff --git a/authority/policy/engine.go b/authority/policy/engine.go index 475522d27..c1c76d59d 100644 --- a/authority/policy/engine.go +++ b/authority/policy/engine.go @@ -106,6 +106,6 @@ func (e *Engine) IsSSHCertificateAllowed(cert *ssh.Certificate) error { // return result of SSH user policy evaluation return e.sshUserPolicy.IsSSHCertificateAllowed(cert) default: - return fmt.Errorf("unexpected SSH certificate type %q", cert.CertType) + return fmt.Errorf("unexpected SSH certificate type %d", cert.CertType) } } diff --git a/authority/provisioner/acme_test.go b/authority/provisioner/acme_test.go index 292e87bc6..acac63c41 100644 --- a/authority/provisioner/acme_test.go +++ b/authority/provisioner/acme_test.go @@ -300,8 +300,7 @@ func TestACME_AuthorizeRenew(t *testing.T) { 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) { + if sc, ok := errors.AsType[render.StatusCodedError](err); ok { assert.Equal(t, tc.code, sc.StatusCode()) } } @@ -337,8 +336,7 @@ func TestACME_AuthorizeSign(t *testing.T) { 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) { + if sc, ok := errors.AsType[render.StatusCodedError](err); ok { assert.Equal(t, tc.code, sc.StatusCode()) } } diff --git a/authority/provisioner/aws.go b/authority/provisioner/aws.go index 9bbde2f7f..e3e1280d0 100644 --- a/authority/provisioner/aws.go +++ b/authority/provisioner/aws.go @@ -13,6 +13,7 @@ import ( "net" "net/http" "os" + "slices" "strings" "time" @@ -169,7 +170,7 @@ type AWS struct { DisableCustomSANs bool `json:"disableCustomSANs"` DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` IMDSVersions []string `json:"imdsVersions"` - InstanceAge Duration `json:"instanceAge,omitempty"` + InstanceAge Duration `json:"instanceAge"` IIDRoots string `json:"iidRoots,omitempty"` Claims *Claims `json:"claims,omitempty"` Options *Options `json:"options,omitempty"` @@ -590,11 +591,8 @@ func (p *AWS) authorizeToken(token string) (*awsPayload, error) { // validate accounts if len(p.Accounts) > 0 { var found bool - for _, sa := range p.Accounts { - if sa == doc.AccountID { - found = true - break - } + if slices.Contains(p.Accounts, doc.AccountID) { + found = true } if !found { return nil, errs.Unauthorized("aws.authorizeToken; invalid aws identity document - accountId is not valid") diff --git a/authority/provisioner/aws_test.go b/authority/provisioner/aws_test.go index eb57a57cb..cea44a1f7 100644 --- a/authority/provisioner/aws_test.go +++ b/authority/provisioner/aws_test.go @@ -776,7 +776,7 @@ func TestAWS_AuthorizeSSHSign(t *testing.T) { type args struct { token string sshOpts SignSSHOptions - key interface{} + key any } tests := []struct { name string diff --git a/authority/provisioner/azure.go b/authority/provisioner/azure.go index 4b4a7b7b4..eeac24d62 100644 --- a/authority/provisioner/azure.go +++ b/authority/provisioner/azure.go @@ -9,6 +9,7 @@ import ( "io" "net/http" "regexp" + "slices" "strings" "time" @@ -325,11 +326,8 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption, // Filter by resource group if len(p.ResourceGroups) > 0 { var found bool - for _, g := range p.ResourceGroups { - if g == group { - found = true - break - } + if slices.Contains(p.ResourceGroups, group) { + found = true } if !found { return nil, errs.Unauthorized("azure.AuthorizeSign; azure token validation failed - invalid resource group") @@ -339,11 +337,8 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption, // Filter by subscription id if len(p.SubscriptionIDs) > 0 { var found bool - for _, s := range p.SubscriptionIDs { - if s == subscription { - found = true - break - } + if slices.Contains(p.SubscriptionIDs, subscription) { + found = true } if !found { return nil, errs.Unauthorized("azure.AuthorizeSign; azure token validation failed - invalid subscription id") @@ -353,11 +348,8 @@ func (p *Azure) AuthorizeSign(ctx context.Context, token string) ([]SignOption, // Filter by Azure AD identity object id if len(p.ObjectIDs) > 0 { var found bool - for _, i := range p.ObjectIDs { - if i == identityObjectID { - found = true - break - } + if slices.Contains(p.ObjectIDs, identityObjectID) { + found = true } if !found { return nil, errs.Unauthorized("azure.AuthorizeSign; azure token validation failed - invalid identity object id") diff --git a/authority/provisioner/azure_test.go b/authority/provisioner/azure_test.go index c0438231f..a38e475a8 100644 --- a/authority/provisioner/azure_test.go +++ b/authority/provisioner/azure_test.go @@ -680,7 +680,7 @@ func TestAzure_AuthorizeSSHSign(t *testing.T) { type args struct { token string sshOpts SignSSHOptions - key interface{} + key any } tests := []struct { name string diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index b12016cfd..fa1e9f701 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -105,7 +105,7 @@ type GCP struct { DisableTrustOnFirstUse bool `json:"disableTrustOnFirstUse"` DisableSSHCAUser *bool `json:"disableSSHCAUser,omitempty"` DisableSSHCAHost *bool `json:"disableSSHCAHost,omitempty"` - InstanceAge Duration `json:"instanceAge,omitempty"` + InstanceAge Duration `json:"instanceAge"` Claims *Claims `json:"claims,omitempty"` Options *Options `json:"options,omitempty"` config *gcpConfig diff --git a/authority/provisioner/gcp/projectvalidator.go b/authority/provisioner/gcp/projectvalidator.go index a7dc5cf11..bc7f23e1e 100644 --- a/authority/provisioner/gcp/projectvalidator.go +++ b/authority/provisioner/gcp/projectvalidator.go @@ -3,6 +3,7 @@ package gcp import ( "context" "net/http" + "slices" "google.golang.org/api/cloudresourcemanager/v1" @@ -18,10 +19,8 @@ func (p *ProjectValidator) ValidateProject(_ context.Context, projectID string) return nil } - for _, pi := range p.ProjectIDs { - if pi == projectID { - return nil - } + if slices.Contains(p.ProjectIDs, projectID) { + return nil } return errs.Unauthorized("gcp.authorizeToken; invalid gcp token - invalid project id") diff --git a/authority/provisioner/gcp_test.go b/authority/provisioner/gcp_test.go index 37b031ec3..2f10ff1fc 100644 --- a/authority/provisioner/gcp_test.go +++ b/authority/provisioner/gcp_test.go @@ -674,7 +674,7 @@ func TestGCP_AuthorizeSSHSign(t *testing.T) { type args struct { token string sshOpts SignSSHOptions - key interface{} + key any } tests := []struct { name string diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 68fb7f47a..6ab78ffe7 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -470,7 +470,7 @@ func TestJWK_AuthorizeSSHSign(t *testing.T) { type args struct { token string sshOpts SignSSHOptions - key interface{} + key any } tests := []struct { name string diff --git a/authority/provisioner/k8sSA.go b/authority/provisioner/k8sSA.go index 54d098c62..2609d5a08 100644 --- a/authority/provisioner/k8sSA.go +++ b/authority/provisioner/k8sSA.go @@ -2,9 +2,6 @@ package provisioner import ( "context" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" "crypto/x509" "encoding/pem" "net/http" @@ -18,6 +15,7 @@ import ( "go.step.sm/crypto/x509util" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/internal/cryptoutil" ) // NOTE: There can be at most one kubernetes service account provisioner configured @@ -52,7 +50,7 @@ type K8sSA struct { Claims *Claims `json:"claims,omitempty"` Options *Options `json:"options,omitempty"` //kauthn kauthn.AuthenticationV1Interface - pubKeys []interface{} + pubKeys []any ctl *Controller } @@ -115,10 +113,8 @@ func (p *K8sSA) Init(config Config) (err error) { if err != nil { return errors.Wrapf(err, "error parsing public key in provisioner '%s'", p.GetName()) } - switch q := key.(type) { - case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: - default: - return errors.Errorf("Unexpected public key type %T in provisioner '%s'", q, p.GetName()) + if !cryptoutil.IsSupportedPublicKey(key) { + return errors.Errorf("Unexpected public key type %T in provisioner %q", key, p.GetName()) } p.pubKeys = append(p.pubKeys, key) } diff --git a/authority/provisioner/nebula.go b/authority/provisioner/nebula.go index a4ac4396a..c98e5546f 100644 --- a/authority/provisioner/nebula.go +++ b/authority/provisioner/nebula.go @@ -2,16 +2,15 @@ package provisioner import ( "context" - "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/x509" "encoding/base64" "encoding/pem" - "math/big" "net" "net/netip" + "slices" "time" "github.com/pkg/errors" @@ -343,16 +342,10 @@ func (p *Nebula) authorizeToken(token string, audiences []string) (nebula.Certif switch { case c.Curve() == nebula.Curve_P256: // When Nebula is used with ECDSA P-256 keys, both CAs and clients use the same type. - ecdhPub, err := ecdh.P256().NewPublicKey(c.PublicKey()) + pub, err = ecdsa.ParseUncompressedPublicKey(elliptic.P256(), c.PublicKey()) if err != nil { return nil, nil, errs.UnauthorizedErr(err, errs.WithMessage("failed to parse nebula public key")) } - publicKeyBytes := ecdhPub.Bytes() - pub = &ecdsa.PublicKey{ // convert back to *ecdsa.PublicKey, because our jose package nor go-jose supports *ecdh.PublicKey - Curve: elliptic.P256(), - X: big.NewInt(0).SetBytes(publicKeyBytes[1:33]), - Y: big.NewInt(0).SetBytes(publicKeyBytes[33:]), - } case c.IsCA(): pub = ed25519.PublicKey(c.PublicKey()) default: @@ -413,11 +406,8 @@ func (v nebulaSANsValidator) Valid(req *x509.CertificateRequest) error { for _, ip := range req.IPAddresses { var valid bool // Check ip in name - for _, ipInName := range ips { - if ip.Equal(ipInName) { - valid = true - break - } + if slices.ContainsFunc(ips, ip.Equal) { + valid = true } // Check ip network if !valid { diff --git a/authority/provisioner/nebula_test.go b/authority/provisioner/nebula_test.go index 57bb36b95..0a1c1e0ff 100644 --- a/authority/provisioner/nebula_test.go +++ b/authority/provisioner/nebula_test.go @@ -98,8 +98,12 @@ func mustNebulaP256CA(t *testing.T) (cert.Certificate, *ecdsa.PrivateKey) { IsCA: true, } + // d is the private scalar value. + d, err := key.Bytes() + require.NoError(t, err) + // For P256 CAs, Sign expects the raw 32-byte scalar as the key. - nc, err := tbs.Sign(nil, cert.Curve_P256, key.D.FillBytes(make([]byte, 32))) + nc, err := tbs.Sign(nil, cert.Curve_P256, d) require.NoError(t, err) return nc, key } diff --git a/authority/provisioner/oidc.go b/authority/provisioner/oidc.go index 044971bf6..8f85f04ed 100644 --- a/authority/provisioner/oidc.go +++ b/authority/provisioner/oidc.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "path" + "slices" "strings" "time" @@ -65,10 +66,8 @@ func (o *openIDPayload) IsAdmin(admins []string) bool { // The groups and emails can be in the same array for now, but consider // making a specialized option later. for _, name := range o.Groups { - for _, admin := range admins { - if name == admin { - return true - } + if slices.Contains(admins, name) { + return true } } @@ -246,11 +245,8 @@ func (o *OIDC) ValidatePayload(p openIDPayload) error { if len(o.Groups) > 0 { var found bool for _, group := range o.Groups { - for _, g := range p.Groups { - if g == group { - found = true - break - } + if slices.Contains(p.Groups, group) { + found = true } } if !found { @@ -485,7 +481,7 @@ func (o *OIDC) AuthorizeSSHRevoke(_ context.Context, token string) error { return errs.Unauthorized("oidc.AuthorizeSSHRevoke; cannot revoke with non-admin oidc token") } -func getAndDecode(client HTTPClient, uri string, v interface{}) error { +func getAndDecode(client HTTPClient, uri string, v any) error { resp, err := client.Get(uri) if err != nil { return errors.Wrapf(err, "failed to connect to %s", uri) diff --git a/authority/provisioner/oidc_test.go b/authority/provisioner/oidc_test.go index a26b87db5..2c3e060a4 100644 --- a/authority/provisioner/oidc_test.go +++ b/authority/provisioner/oidc_test.go @@ -577,7 +577,7 @@ func TestOIDC_AuthorizeSSHSign(t *testing.T) { type args struct { token string sshOpts SignSSHOptions - key interface{} + key any } tests := []struct { name string diff --git a/authority/provisioner/options.go b/authority/provisioner/options.go index f68e9daca..3a79106c6 100644 --- a/authority/provisioner/options.go +++ b/authority/provisioner/options.go @@ -164,9 +164,9 @@ func CustomTemplateOptions(o *Options, data x509util.TemplateData, defaultTempla // Add user provided data. if len(so.TemplateData) > 0 { - userObject := make(map[string]interface{}) + userObject := make(map[string]any) if err := json.Unmarshal(so.TemplateData, &userObject); err != nil { - data.SetUserData(map[string]interface{}{}) + data.SetUserData(map[string]any{}) } else { data.SetUserData(userObject) } @@ -196,12 +196,12 @@ func CustomTemplateOptions(o *Options, data x509util.TemplateData, defaultTempla // unsafeParseSigned parses the given token and returns all the claims without // verifying the signature of the token. -func unsafeParseSigned(s string) (map[string]interface{}, error) { +func unsafeParseSigned(s string) (map[string]any, error) { token, err := jose.ParseSigned(s) if err != nil { return nil, err } - claims := make(map[string]interface{}) + claims := make(map[string]any) if err := token.UnsafeClaimsWithoutVerification(&claims); err != nil { return nil, err } diff --git a/authority/provisioner/options_test.go b/authority/provisioner/options_test.go index d70e9345e..a5d4318b6 100644 --- a/authority/provisioner/options_test.go +++ b/authority/provisioner/options_test.go @@ -292,10 +292,10 @@ func Test_unsafeParseSigned(t *testing.T) { tests := []struct { name string args args - want map[string]interface{} + want map[string]any wantErr bool }{ - {"ok", args{okToken}, map[string]interface{}{ + {"ok", args{okToken}, map[string]any{ "sub": "jane@doe.com", "iss": "https://doe.com", "jti": "8ff32481-fd5f-4e2e-96df-908c127c85f7", diff --git a/authority/provisioner/provisioner.go b/authority/provisioner/provisioner.go index ae6e0f978..9da2ccb0c 100644 --- a/authority/provisioner/provisioner.go +++ b/authority/provisioner/provisioner.go @@ -427,7 +427,7 @@ func (p *raProvisioner) RAInfo() *RAInfo { // MockProvisioner for testing type MockProvisioner struct { - Mret1, Mret2, Mret3 interface{} + Mret1, Mret2, Mret3 any Merr error MgetID func() string MgetIDForToken func() string diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 1e84901b6..b030bb5f2 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -2,8 +2,6 @@ package provisioner import ( "context" - "crypto/ecdsa" - "crypto/ed25519" "crypto/rsa" "crypto/sha256" "crypto/subtle" @@ -14,6 +12,7 @@ import ( "net/http" "net/url" "reflect" + "slices" "time" "go.step.sm/crypto/keyutil" @@ -21,6 +20,7 @@ import ( "github.com/smallstep/certificates/authority/policy" "github.com/smallstep/certificates/errs" + "github.com/smallstep/certificates/internal/cryptoutil" ) // DefaultCertValidity is the default validity for a certificate if none is specified. @@ -37,7 +37,7 @@ type SignOptions struct { // SignOption is the interface used to collect all extra options used in the // Sign method. -type SignOption interface{} +type SignOption any // CertificateValidator is an interface used to validate a given X.509 certificate. type CertificateValidator interface { @@ -92,17 +92,7 @@ type defaultPublicKeyValidator struct{} // Valid checks that certificate request common name matches the one configured. func (v defaultPublicKeyValidator) Valid(req *x509.CertificateRequest) error { - switch k := req.PublicKey.(type) { - case *rsa.PublicKey: - if k.Size() < keyutil.MinRSAKeyBytes { - return errs.Forbidden("certificate request RSA key must be at least %d bits (%d bytes)", - 8*keyutil.MinRSAKeyBytes, keyutil.MinRSAKeyBytes) - } - case *ecdsa.PublicKey, ed25519.PublicKey: - default: - return errs.BadRequest("certificate request key of type '%T' is not supported", k) - } - return nil + return newPublicKeyMinimumLengthValidator(8 * keyutil.MinRSAKeyBytes).Valid(req) } // publicKeyMinimumLengthValidator validates the length (in bits) of the public key @@ -122,18 +112,20 @@ func newPublicKeyMinimumLengthValidator(length int) publicKeyMinimumLengthValida // Valid checks that certificate request common name matches the one configured. func (v publicKeyMinimumLengthValidator) Valid(req *x509.CertificateRequest) error { - switch k := req.PublicKey.(type) { - case *rsa.PublicKey: + if rsaKey, ok := req.PublicKey.(*rsa.PublicKey); ok { minimumLengthInBytes := v.length / 8 - if k.Size() < minimumLengthInBytes { + if rsaKey.Size() < minimumLengthInBytes { return errs.Forbidden("certificate request RSA key must be at least %d bits (%d bytes)", v.length, minimumLengthInBytes) } - case *ecdsa.PublicKey, ed25519.PublicKey: - default: - return errs.BadRequest("certificate request key of type '%T' is not supported", k) + return nil } - return nil + + if cryptoutil.IsSupportedPublicKey(req.PublicKey) { + return nil + } + + return errs.BadRequest("certificate request key of type '%T' is not supported", req.PublicKey) } // commonNameValidator validates the common name of a certificate request. @@ -159,10 +151,8 @@ func (v commonNameSliceValidator) Valid(req *x509.CertificateRequest) error { if req.Subject.CommonName == "" { return nil } - for _, cn := range v { - if req.Subject.CommonName == cn { - return nil - } + if slices.Contains(v, req.Subject.CommonName) { + return nil } return errs.Forbidden("certificate request does not contain the valid common name - got %s, want %s", req.Subject.CommonName, v) } diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index b4f7ffbe6..2b57f5ae8 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "math/big" + "slices" "strings" "time" @@ -59,8 +60,8 @@ type SignSSHOptions struct { CertType string `json:"certType"` KeyID string `json:"keyID"` Principals []string `json:"principals"` - ValidAfter TimeDuration `json:"validAfter,omitempty"` - ValidBefore TimeDuration `json:"validBefore,omitempty"` + ValidAfter TimeDuration `json:"validAfter"` + ValidBefore TimeDuration `json:"validBefore"` TemplateData json.RawMessage `json:"templateData,omitempty"` Backdate time.Duration `json:"-"` } @@ -70,10 +71,8 @@ func (o SignSSHOptions) Validate() error { if o.CertType != "" && o.CertType != SSHUserCert && o.CertType != SSHHostCert { return errs.BadRequest("certType '%s' is not valid", o.CertType) } - for _, p := range o.Principals { - if p == "" { - return errs.BadRequest("principals cannot contain empty values") - } + if slices.Contains(o.Principals, "") { + return errs.BadRequest("principals cannot contain empty values") } return nil } @@ -448,10 +447,10 @@ func containsAllMembers(group, subgroup []string) bool { return false } visit := make(map[string]struct{}, lg) - for i := 0; i < lg; i++ { + for i := range lg { visit[strings.ToLower(group[i])] = struct{}{} } - for i := 0; i < lsg; i++ { + for i := range lsg { if _, ok := visit[strings.ToLower(subgroup[i])]; !ok { return false } diff --git a/authority/provisioner/ssh_options.go b/authority/provisioner/ssh_options.go index def2ec724..33b1a1e5c 100644 --- a/authority/provisioner/ssh_options.go +++ b/authority/provisioner/ssh_options.go @@ -135,9 +135,9 @@ func CustomSSHTemplateOptions(o *Options, data sshutil.TemplateData, defaultTemp // Add user provided data. if len(so.TemplateData) > 0 { - userObject := make(map[string]interface{}) + userObject := make(map[string]any) if err := json.Unmarshal(so.TemplateData, &userObject); err != nil { - data.SetUserData(map[string]interface{}{}) + data.SetUserData(map[string]any{}) } else { data.SetUserData(userObject) } diff --git a/authority/provisioner/ssh_test.go b/authority/provisioner/ssh_test.go index 39bda0d45..31d4a4047 100644 --- a/authority/provisioner/ssh_test.go +++ b/authority/provisioner/ssh_test.go @@ -90,8 +90,7 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si // Create certificate from template. certificate, err := sshutil.NewCertificate(cr, certOptions...) if err != nil { - var templErr *sshutil.TemplateError - if errors.As(err, &templErr) { + if templErr, ok := errors.AsType[*sshutil.TemplateError](err); ok { return nil, errs.NewErr(http.StatusBadRequest, templErr, errs.WithMessage("%s", templErr.Error()), errs.WithKeyVal("signOptions", signOpts), diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index 88c11dd37..aac7ddc27 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -86,7 +86,7 @@ O4vZCKd4vzljH6eL+OECQHHxhYoTW7lFpKGnUDG9fPZ3eYzWpgka6w1vvBk10BAu 6fbwppM9pQ7DPMg7V6YGEjjT0gX9B9TttfHxGhvtZNQ= -----END RSA PRIVATE KEY-----` -func must(args ...interface{}) []interface{} { +func must(args ...any) []any { if l := len(args); l > 0 && args[l-1] != nil { if err, ok := args[l-1].(error); ok { panic(err) @@ -110,7 +110,7 @@ func generateJSONWebKey() (*jose.JSONWebKey, error) { func generateJSONWebKeySet(n int) (jose.JSONWebKeySet, error) { var keySet jose.JSONWebKeySet - for i := 0; i < n; i++ { + for range n { key, err := generateJSONWebKey() if err != nil { return jose.JSONWebKeySet{}, err @@ -192,7 +192,7 @@ func generateJWK() (*JWK, error) { return p, err } -func generateK8sSA(inputPubKey interface{}) (*K8sSA, error) { +func generateK8sSA(inputPubKey any) (*K8sSA, error) { fooPubB, err := os.ReadFile("./testdata/certs/foo.pub") if err != nil { return nil, err @@ -210,7 +210,7 @@ func generateK8sSA(inputPubKey interface{}) (*K8sSA, error) { return nil, err } - pubKeys := []interface{}{fooPub, barPub} + pubKeys := []any{fooPub, barPub} if inputPubKey != nil { pubKeys = append(pubKeys, inputPubKey) } @@ -625,7 +625,7 @@ func generateAzureWithServer() (*Azure, *httptest.Server, error) { if err != nil { return nil, nil, err } - writeJSON := func(w http.ResponseWriter, v interface{}) { + writeJSON := func(w http.ResponseWriter, v any) { b, err := json.Marshal(v) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -688,14 +688,14 @@ func generateAzureWithServer() (*Azure, *httptest.Server, error) { func generateCollection(nJWK, nOIDC int) (*Collection, error) { col := NewCollection(testAudiences) - for i := 0; i < nJWK; i++ { + for range nJWK { p, err := generateJWK() if err != nil { return nil, err } col.Store(p) } - for i := 0; i < nOIDC; i++ { + for range nOIDC { p, err := generateOIDC() if err != nil { return nil, err @@ -1108,7 +1108,7 @@ func generateJWKServerHandler(n int, srv *httptest.Server) http.Handler { hits := struct { Hits int `json:"hits"` }{} - writeJSON := func(w http.ResponseWriter, v interface{}) { + writeJSON := func(w http.ResponseWriter, v any) { b, err := json.Marshal(v) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/authority/provisioner/webhook_test.go b/authority/provisioner/webhook_test.go index a6cb37ecb..5e3f2d773 100644 --- a/authority/provisioner/webhook_test.go +++ b/authority/provisioner/webhook_test.go @@ -458,7 +458,7 @@ func TestWebhook_Do(t *testing.T) { }, requestID: "reqID", webhookResponse: webhook.ResponseBody{ - Data: map[string]interface{}{"role": "dba"}, + Data: map[string]any{"role": "dba"}, }, }, "ok/no-request-id": { @@ -467,7 +467,7 @@ func TestWebhook_Do(t *testing.T) { Secret: "c2VjcmV0Cg==", }, webhookResponse: webhook.ResponseBody{ - Data: map[string]interface{}{"role": "dba"}, + Data: map[string]any{"role": "dba"}, }, }, "ok/bearer": { @@ -478,7 +478,7 @@ func TestWebhook_Do(t *testing.T) { }, requestID: "reqID", webhookResponse: webhook.ResponseBody{ - Data: map[string]interface{}{"role": "dba"}, + Data: map[string]any{"role": "dba"}, }, }, "ok/basic": { @@ -495,7 +495,7 @@ func TestWebhook_Do(t *testing.T) { }, requestID: "reqID", webhookResponse: webhook.ResponseBody{ - Data: map[string]interface{}{"role": "dba"}, + Data: map[string]any{"role": "dba"}, }, }, "ok/templated-url": { @@ -506,9 +506,9 @@ func TestWebhook_Do(t *testing.T) { Secret: "c2VjcmV0Cg==", }, requestID: "reqID", - dataArg: map[string]interface{}{"username": "areed", "region": "central"}, + dataArg: map[string]any{"username": "areed", "region": "central"}, webhookResponse: webhook.ResponseBody{ - Data: map[string]interface{}{"role": "dba"}, + Data: map[string]any{"role": "dba"}, }, expectPath: "/users/areed?region=central", }, @@ -552,7 +552,7 @@ func TestWebhook_Do(t *testing.T) { Secret: "c2VjcmV0Cg==", }, webhookResponse: webhook.ResponseBody{ - Data: map[string]interface{}{"role": "dba"}, + Data: map[string]any{"role": "dba"}, }, requestID: "reqID", errStatusCode: 404, diff --git a/authority/root.go b/authority/root.go index 0a6ee639c..a682d97b1 100644 --- a/authority/root.go +++ b/authority/root.go @@ -45,7 +45,7 @@ func (a *Authority) GetRoots() ([]*x509.Certificate, error) { // GetFederation returns all the root certificates in the federation. // This method implements the Authority interface. func (a *Authority) GetFederation() (federation []*x509.Certificate, err error) { - a.certificates.Range(func(_, v interface{}) bool { + a.certificates.Range(func(_, v any) bool { crt, ok := v.(*x509.Certificate) if !ok { federation = nil diff --git a/authority/ssh.go b/authority/ssh.go index e29e1c318..691205c06 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "encoding/binary" "errors" + "maps" "net/http" "strings" "time" @@ -77,16 +78,14 @@ func (a *Authority) GetSSHConfig(_ context.Context, typ string, data map[string] } // Merge user and default data - var mergedData map[string]interface{} + var mergedData map[string]any if len(data) == 0 { mergedData = a.templates.Data } else { - mergedData = make(map[string]interface{}, len(a.templates.Data)+1) + mergedData = make(map[string]any, len(a.templates.Data)+1) mergedData["User"] = data - for k, v := range a.templates.Data { - mergedData[k] = v - } + maps.Copy(mergedData, a.templates.Data) } // Render templates @@ -293,8 +292,7 @@ func (a *Authority) signSSH(ctx context.Context, key ssh.PublicKey, opts provisi // Check if authority is allowed to sign the certificate if err := a.isAllowedToSignSSHCertificate(certTpl); err != nil { - var ee *errs.Error - if errors.As(err, &ee) { + if ee, ok := errors.AsType[*errs.Error](err); ok { return nil, prov, ee } return nil, prov, errs.InternalServerErr(err, diff --git a/authority/ssh_test.go b/authority/ssh_test.go index 9a5c0d095..df82b909c 100644 --- a/authority/ssh_test.go +++ b/authority/ssh_test.go @@ -519,7 +519,7 @@ func TestAuthority_GetSSHConfig(t *testing.T) { {Name: "ca.tpl", Type: templates.File, TemplatePath: "./testdata/templates/ca.tpl", Path: "/etc/ssh/ca.pub", Comment: "#"}, }, }, - Data: map[string]interface{}{ + Data: map[string]any{ "Step": &templates.Step{ SSH: templates.StepSSH{ UserKey: user, @@ -552,7 +552,7 @@ func TestAuthority_GetSSHConfig(t *testing.T) { }, }, }, - Data: map[string]interface{}{ + Data: map[string]any{ "Step": &templates.Step{ SSH: templates.StepSSH{ UserKey: user, @@ -575,7 +575,7 @@ func TestAuthority_GetSSHConfig(t *testing.T) { {Name: "step_includes.tpl", Type: templates.PrependLine, TemplatePath: "./testdata/templates/step_includes.tpl", Path: "${STEPPATH}/ssh/includes", Comment: "#"}, }, }, - Data: map[string]interface{}{ + Data: map[string]any{ "Step": &templates.Step{ SSH: templates.StepSSH{ UserKey: user, diff --git a/authority/tls.go b/authority/tls.go index 3900c446c..936ba612e 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -573,7 +573,7 @@ type RevokeOptions struct { // // TODO: Add OCSP and CRL support. func (a *Authority) Revoke(ctx context.Context, revokeOpts *RevokeOptions) error { - opts := []interface{}{ + opts := []any{ errs.WithKeyVal("serialNumber", revokeOpts.Serial), errs.WithKeyVal("reasonCode", revokeOpts.ReasonCode), errs.WithKeyVal("reason", revokeOpts.Reason), diff --git a/authority/tls_test.go b/authority/tls_test.go index 1e660dcb5..7d525b4d2 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -80,7 +80,7 @@ func getDefaultSigner(a *Authority) crypto.Signer { return a.x509CAService.(*softcas.SoftCAS).Signer } -func generateCertificate(t *testing.T, commonName string, sans []string, opts ...interface{}) *x509.Certificate { +func generateCertificate(t *testing.T, commonName string, sans []string, opts ...any) *x509.Certificate { t.Helper() priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) @@ -187,7 +187,7 @@ func withSigner(issuer *x509.Certificate, signer crypto.Signer) signerFunc { } } -func getCSR(t *testing.T, priv interface{}, opts ...func(*x509.CertificateRequest)) *x509.CertificateRequest { +func getCSR(t *testing.T, priv any, opts ...func(*x509.CertificateRequest)) *x509.CertificateRequest { _csr := &x509.CertificateRequest{ Subject: pkix.Name{CommonName: "smallstep test"}, DNSNames: []string{"test.smallstep.com"}, @@ -2013,7 +2013,7 @@ func TestAuthority_CRL(t *testing.T) { var ex []string - for i := 0; i < 100; i++ { + for i := range 100 { sn := fmt.Sprintf("%v", i) cl := jose.Claims{ @@ -2078,7 +2078,7 @@ func TestAuthority_CRL(t *testing.T) { var ex []string zeroReasonCode := 0 - for i := 0; i < 5; i++ { + for i := range 5 { sn := fmt.Sprintf("%v", i) cl := jose.Claims{ Subject: sn, diff --git a/ca/acmeClient_test.go b/ca/acmeClient_test.go index c909af198..f191a407b 100644 --- a/ca/acmeClient_test.go +++ b/ca/acmeClient_test.go @@ -25,7 +25,7 @@ import ( func TestNewACMEClient(t *testing.T) { type test struct { ops []ClientOption - r1, r2 interface{} + r1, r2 any rc1, rc2 int err error } @@ -159,7 +159,7 @@ func TestACMEClient_GetDirectory(t *testing.T) { func TestACMEClient_GetNonce(t *testing.T) { type test struct { - r1 interface{} + r1 any rc1 int err error } @@ -229,7 +229,7 @@ func TestACMEClient_post(t *testing.T) { payload []byte Key *jose.JSONWebKey ops []withHeaderOption - r1, r2 interface{} + r1, r2 any rc1, rc2 int jwkInJWS bool client *ACMEClient @@ -361,7 +361,7 @@ func TestACMEClient_post(t *testing.T) { func TestACMEClient_NewOrder(t *testing.T) { type test struct { ops []withHeaderOption - r1, r2 interface{} + r1, r2 any rc1, rc2 int err error } @@ -497,7 +497,7 @@ func TestACMEClient_NewOrder(t *testing.T) { func TestACMEClient_GetOrder(t *testing.T) { type test struct { - r1, r2 interface{} + r1, r2 any rc1, rc2 int err error } @@ -619,7 +619,7 @@ func TestACMEClient_GetOrder(t *testing.T) { func TestACMEClient_GetAuthz(t *testing.T) { type test struct { - r1, r2 interface{} + r1, r2 any rc1, rc2 int err error } @@ -741,7 +741,7 @@ func TestACMEClient_GetAuthz(t *testing.T) { func TestACMEClient_GetChallenge(t *testing.T) { type test struct { - r1, r2 interface{} + r1, r2 any rc1, rc2 int err error } @@ -864,7 +864,7 @@ func TestACMEClient_GetChallenge(t *testing.T) { func TestACMEClient_ValidateChallenge(t *testing.T) { type test struct { - r1, r2 interface{} + r1, r2 any rc1, rc2 int err error } @@ -1077,7 +1077,7 @@ func TestACMEClient_ValidateWithPayload(t *testing.T) { func TestACMEClient_FinalizeOrder(t *testing.T) { type test struct { - r1, r2 interface{} + r1, r2 any rc1, rc2 int err error } @@ -1203,7 +1203,7 @@ func TestACMEClient_FinalizeOrder(t *testing.T) { func TestACMEClient_GetAccountOrders(t *testing.T) { type test struct { - r1, r2 interface{} + r1, r2 any rc1, rc2 int err error client *ACMEClient @@ -1336,7 +1336,7 @@ func TestACMEClient_GetAccountOrders(t *testing.T) { func TestACMEClient_GetCertificate(t *testing.T) { type test struct { - r1, r2 interface{} + r1, r2 any certBytes []byte rc1, rc2 int err error diff --git a/ca/bootstrap_test.go b/ca/bootstrap_test.go index da37eee58..642ffa9a7 100644 --- a/ca/bootstrap_test.go +++ b/ca/bootstrap_test.go @@ -658,13 +658,11 @@ func TestBootstrapListener(t *testing.T) { return } wg := new(sync.WaitGroup) - wg.Add(1) - go func() { + wg.Go(func() { http.Serve(lis, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) })) - wg.Done() - }() + }) defer wg.Wait() defer lis.Close() diff --git a/ca/ca_test.go b/ca/ca_test.go index d61170551..0430e016b 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -41,7 +41,7 @@ func (cb *ClosingBuffer) Close() error { return nil } -func getCSR(priv interface{}) (*x509.CertificateRequest, error) { +func getCSR(priv any) (*x509.CertificateRequest, error) { _csr := &x509.CertificateRequest{ Subject: pkix.Name{CommonName: "test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"}, diff --git a/ca/client.go b/ca/client.go index 89acf267a..a8257b049 100644 --- a/ca/client.go +++ b/ca/client.go @@ -366,7 +366,7 @@ func WithCertificate(cert tls.Certificate) ClientOption { // WithAdminX5C will set the given file as the X5C certificate for use // by the client. -func WithAdminX5C(certs []*x509.Certificate, key interface{}, passwordFile string) ClientOption { +func WithAdminX5C(certs []*x509.Certificate, key any, passwordFile string) ClientOption { return func(o *clientOptions) error { // Get private key from given key file var ( @@ -1566,7 +1566,7 @@ func getRootCAPath() string { return filepath.Join(step.Path(), "certs", "root_ca.crt") } -func readJSON(r io.ReadCloser, v interface{}) error { +func readJSON(r io.ReadCloser, v any) error { defer r.Close() return json.NewDecoder(r).Decode(v) } diff --git a/ca/client_test.go b/ca/client_test.go index e3877e3fb..1fa33682d 100644 --- a/ca/client_test.go +++ b/ca/client_test.go @@ -139,7 +139,7 @@ func parseCertificateRequest(t *testing.T, csrPEM string) *x509.CertificateReque return csr } -func equalJSON(t *testing.T, a, b interface{}) bool { +func equalJSON(t *testing.T, a, b any) bool { t.Helper() if reflect.DeepEqual(a, b) { return true @@ -159,7 +159,7 @@ func TestClient_Version(t *testing.T) { tests := []struct { name string - response interface{} + response any responseCode int wantErr bool expectedErr error @@ -201,7 +201,7 @@ func TestClient_Health(t *testing.T) { tests := []struct { name string - response interface{} + response any responseCode int wantErr bool expectedErr error @@ -245,7 +245,7 @@ func TestClient_Root(t *testing.T) { tests := []struct { name string shasum string - response interface{} + response any responseCode int wantErr bool expectedErr error @@ -304,7 +304,7 @@ func TestClient_Sign(t *testing.T) { tests := []struct { name string request *api.SignRequest - response interface{} + response any responseCode int wantErr bool expectedErr error @@ -367,7 +367,7 @@ func TestClient_Revoke(t *testing.T) { tests := []struct { name string request *api.RevokeRequest - response interface{} + response any responseCode int wantErr bool expectedErr error @@ -431,7 +431,7 @@ func TestClient_Renew(t *testing.T) { tests := []struct { name string - response interface{} + response any responseCode int wantErr bool err error @@ -485,7 +485,7 @@ func TestClient_RenewWithToken(t *testing.T) { tests := []struct { name string - response interface{} + response any responseCode int wantErr bool err error @@ -548,7 +548,7 @@ func TestClient_Rekey(t *testing.T) { tests := []struct { name string request *api.RekeyRequest - response interface{} + response any responseCode int wantErr bool err error @@ -600,7 +600,7 @@ func TestClient_Provisioners(t *testing.T) { name string args []ProvisionerOption expectedURI string - response interface{} + response any responseCode int wantErr bool }{ @@ -649,7 +649,7 @@ func TestClient_ProvisionerKey(t *testing.T) { tests := []struct { name string kid string - response interface{} + response any responseCode int wantErr bool err error @@ -702,7 +702,7 @@ func TestClient_Roots(t *testing.T) { tests := []struct { name string - response interface{} + response any responseCode int wantErr bool err error @@ -752,7 +752,7 @@ func TestClient_Federation(t *testing.T) { tests := []struct { name string - response interface{} + response any responseCode int wantErr bool err error @@ -803,7 +803,7 @@ func TestClient_SSHRoots(t *testing.T) { tests := []struct { name string - response interface{} + response any responseCode int wantErr bool err error @@ -896,7 +896,7 @@ func TestClient_RootFingerprint(t *testing.T) { tests := []struct { name string server *httptest.Server - response interface{} + response any responseCode int want string wantErr bool @@ -952,7 +952,7 @@ func TestClient_SSHBastion(t *testing.T) { tests := []struct { name string request *api.SSHBastionRequest - response interface{} + response any responseCode int wantErr bool err error diff --git a/ca/provisioner_test.go b/ca/provisioner_test.go index 056673df5..259098cdc 100644 --- a/ca/provisioner_test.go +++ b/ca/provisioner_test.go @@ -213,7 +213,7 @@ func TestProvisioner_Token(t *testing.T) { if lifetime != tt.fields.tokenLifetime { t.Errorf("Claims token life time = %s, want %s", lifetime, tt.fields.tokenLifetime) } - allClaims := make(map[string]interface{}) + allClaims := make(map[string]any) if err := jwt.Claims(tt.fields.jwk.Public(), &allClaims); err != nil { t.Error(err) return @@ -222,15 +222,15 @@ func TestProvisioner_Token(t *testing.T) { t.Errorf("Claim sha = %s, want %s", v, sha) } if len(tt.args.sans) == 0 { - if v, ok := allClaims["sans"].([]interface{}); !ok || !reflect.DeepEqual(v, []interface{}{tt.args.subject}) { - t.Errorf("Claim sans = %s, want %s", v, []interface{}{tt.args.subject}) + if v, ok := allClaims["sans"].([]any); !ok || !reflect.DeepEqual(v, []any{tt.args.subject}) { + t.Errorf("Claim sans = %s, want %s", v, []any{tt.args.subject}) } } else { - want := []interface{}{} + want := []any{} for _, s := range tt.args.sans { want = append(want, s) } - if v, ok := allClaims["sans"].([]interface{}); !ok || !reflect.DeepEqual(v, want) { + if v, ok := allClaims["sans"].([]any); !ok || !reflect.DeepEqual(v, want) { t.Errorf("Claim sans = %s, want %s", v, want) } } @@ -309,7 +309,7 @@ func TestProvisioner_IPv6Token(t *testing.T) { if lifetime != tt.fields.tokenLifetime { t.Errorf("Claims token life time = %s, want %s", lifetime, tt.fields.tokenLifetime) } - allClaims := make(map[string]interface{}) + allClaims := make(map[string]any) if err := jwt.Claims(tt.fields.jwk.Public(), &allClaims); err != nil { t.Error(err) return @@ -318,15 +318,15 @@ func TestProvisioner_IPv6Token(t *testing.T) { t.Errorf("Claim sha = %s, want %s", v, sha) } if len(tt.args.sans) == 0 { - if v, ok := allClaims["sans"].([]interface{}); !ok || !reflect.DeepEqual(v, []interface{}{tt.args.subject}) { - t.Errorf("Claim sans = %s, want %s", v, []interface{}{tt.args.subject}) + if v, ok := allClaims["sans"].([]any); !ok || !reflect.DeepEqual(v, []any{tt.args.subject}) { + t.Errorf("Claim sans = %s, want %s", v, []any{tt.args.subject}) } } else { - want := []interface{}{} + want := []any{} for _, s := range tt.args.sans { want = append(want, s) } - if v, ok := allClaims["sans"].([]interface{}); !ok || !reflect.DeepEqual(v, want) { + if v, ok := allClaims["sans"].([]any); !ok || !reflect.DeepEqual(v, want) { t.Errorf("Claim sans = %s, want %s", v, want) } } @@ -407,7 +407,7 @@ func TestProvisioner_SSHToken(t *testing.T) { if lifetime != tt.fields.tokenLifetime { t.Errorf("Claims token life time = %s, want %s", lifetime, tt.fields.tokenLifetime) } - allClaims := make(map[string]interface{}) + allClaims := make(map[string]any) if err := jwt.Claims(tt.fields.jwk.Public(), &allClaims); err != nil { t.Error(err) return @@ -416,12 +416,12 @@ func TestProvisioner_SSHToken(t *testing.T) { t.Errorf("Claim sha = %s, want %s", v, sha) } - principals := make([]interface{}, len(tt.args.principals)) + principals := make([]any, len(tt.args.principals)) for i, p := range tt.args.principals { principals[i] = p } - want := map[string]interface{}{ - "ssh": map[string]interface{}{ + want := map[string]any{ + "ssh": map[string]any{ "certType": tt.args.certType, "keyID": tt.args.keyID, "principals": principals, diff --git a/ca/tls.go b/ca/tls.go index 49bdf0fc4..b28d4c94b 100644 --- a/ca/tls.go +++ b/ca/tls.go @@ -3,12 +3,8 @@ package ca import ( "context" "crypto" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" "crypto/tls" "crypto/x509" - "encoding/pem" "net" "net/http" "os" @@ -18,6 +14,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/ca/identity" + "github.com/smallstep/certificates/internal/cryptoutil" ) // mTLSDialContext will hold the dial context function to use in @@ -264,15 +261,15 @@ func RootCertificate(sign *api.SignResponse) (*x509.Certificate, error) { // TLSCertificate creates a new TLS certificate from the sign response and the // private key used. func TLSCertificate(sign *api.SignResponse, pk crypto.PrivateKey) (*tls.Certificate, error) { - certPEM, err := getPEM(sign.ServerPEM) + certPEM, err := cryptoutil.PEMEncode(sign.ServerPEM.Certificate) if err != nil { return nil, err } - caPEM, err := getPEM(sign.CaPEM) + caPEM, err := cryptoutil.PEMEncode(sign.CaPEM.Certificate) if err != nil { return nil, err } - keyPEM, err := getPEM(pk) + keyPEM, err := cryptoutil.PEMEncode(pk) if err != nil { return nil, err } @@ -342,38 +339,6 @@ func getDefaultTransport(tlsConfig *tls.Config) *http.Transport { } } -func getPEM(i interface{}) ([]byte, error) { - block := new(pem.Block) - switch i := i.(type) { - case api.Certificate: - block.Type = "CERTIFICATE" - block.Bytes = i.Raw - case *x509.Certificate: - block.Type = "CERTIFICATE" - block.Bytes = i.Raw - case *rsa.PrivateKey: - block.Type = "RSA PRIVATE KEY" - block.Bytes = x509.MarshalPKCS1PrivateKey(i) - case *ecdsa.PrivateKey: - var err error - block.Type = "EC PRIVATE KEY" - block.Bytes, err = x509.MarshalECPrivateKey(i) - if err != nil { - return nil, errors.Wrap(err, "error marshaling private key") - } - case ed25519.PrivateKey: - var err error - block.Type = "PRIVATE KEY" - block.Bytes, err = x509.MarshalPKCS8PrivateKey(i) - if err != nil { - return nil, errors.Wrap(err, "error marshaling private key") - } - default: - return nil, errors.Errorf("unsupported key type %T", i) - } - return pem.EncodeToMemory(block), nil -} - func getRenewFunc(ctx *TLSOptionCtx, client *Client, tr http.RoundTripper, pk crypto.PrivateKey) RenewFunc { return func() (*tls.Certificate, error) { // Close connections in keep-alive state diff --git a/cas/cloudcas/certificate_test.go b/cas/cloudcas/certificate_test.go index dfc3c2295..d6214259f 100644 --- a/cas/cloudcas/certificate_test.go +++ b/cas/cloudcas/certificate_test.go @@ -15,6 +15,7 @@ import ( "testing" pb "cloud.google.com/go/security/privateca/apiv1/privatecapb" + "github.com/stretchr/testify/require" kmsapi "go.step.sm/crypto/kms/apiv1" ) @@ -112,8 +113,20 @@ func Test_createPublicKey(t *testing.T) { t.Fatal(err) } ecCert := mustParseCertificate(t, testLeafCertificate) - ecCertPublicKey := ecCert.PublicKey.(*ecdsa.PublicKey) rsaCert := mustParseCertificate(t, testRSACertificate) + + badKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + badECKey := &badKey.PublicKey + badECKey.Curve = &elliptic.CurveParams{ + Name: "FOO", + BitSize: 256, + P: badECKey.Params().P, + B: badECKey.Params().B, + Gx: badECKey.Params().Gx, + Gy: badECKey.Params().Gy, + } + type args struct { key crypto.PublicKey } @@ -132,16 +145,7 @@ func Test_createPublicKey(t *testing.T) { Key: []byte(testRSAPublicKey), }, false}, {"fail ed25519", args{edpub}, nil, true}, - {"fail ec marshal", args{&ecdsa.PublicKey{ - Curve: &elliptic.CurveParams{ - Name: "FOO", - BitSize: 256, - P: ecCertPublicKey.Params().P, - B: ecCertPublicKey.Params().B, - }, - X: ecCertPublicKey.X, - Y: ecCertPublicKey.Y, - }}, nil, true}, + {"fail ec marshal", args{badECKey}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cas/softcas/softcas_test.go b/cas/softcas/softcas_test.go index 1c20d2776..544da44ff 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -199,7 +199,7 @@ func setTeeReader(t *testing.T, w *bytes.Buffer) { } func TestNew(t *testing.T) { - assertEqual := func(x, y interface{}) bool { + assertEqual := func(x, y any) bool { return reflect.DeepEqual(x, y) || fmt.Sprintf("%#v", x) == fmt.Sprintf("%#v", y) } diff --git a/cas/stepcas/jwk_issuer.go b/cas/stepcas/jwk_issuer.go index 2af4fceea..ab16db395 100644 --- a/cas/stepcas/jwk_issuer.go +++ b/cas/stepcas/jwk_issuer.go @@ -83,13 +83,13 @@ func (i *jwkIssuer) createToken(aud, sub string, sans []string, info *raInfo) (s claims := defaultClaims(i.issuer, sub, aud, id) builder := jose.Signed(i.signer).Claims(claims) if len(sans) > 0 { - builder = builder.Claims(map[string]interface{}{ + builder = builder.Claims(map[string]any{ "sans": sans, }) } if info != nil { - builder = builder.Claims(map[string]interface{}{ - "step": map[string]interface{}{ + builder = builder.Claims(map[string]any{ + "step": map[string]any{ "ra": info, }, }) diff --git a/cas/stepcas/stepcas_test.go b/cas/stepcas/stepcas_test.go index b4013792a..f55f193d9 100644 --- a/cas/stepcas/stepcas_test.go +++ b/cas/stepcas/stepcas_test.go @@ -123,10 +123,10 @@ func mustEncryptKey(filename string, key crypto.Signer) { func testCAHelper(t *testing.T) (*url.URL, *ca.Client) { t.Helper() - writeJSON := func(w http.ResponseWriter, v interface{}) { + writeJSON := func(w http.ResponseWriter, v any) { _ = json.NewEncoder(w).Encode(v) } - parseJSON := func(r *http.Request, v interface{}) { + parseJSON := func(r *http.Request, v any) { _ = json.NewDecoder(r.Body).Decode(v) } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/cas/stepcas/x5c_issuer.go b/cas/stepcas/x5c_issuer.go index a005e5016..4fb8f6cf8 100644 --- a/cas/stepcas/x5c_issuer.go +++ b/cas/stepcas/x5c_issuer.go @@ -90,13 +90,13 @@ func (i *x5cIssuer) createToken(aud, sub string, sans []string, info *raInfo) (s claims := defaultClaims(i.issuer, sub, aud, id) builder := jose.Signed(signer).Claims(claims) if len(sans) > 0 { - builder = builder.Claims(map[string]interface{}{ + builder = builder.Claims(map[string]any{ "sans": sans, }) } if info != nil { - builder = builder.Claims(map[string]interface{}{ - "step": map[string]interface{}{ + builder = builder.Claims(map[string]any{ + "step": map[string]any{ "ra": info, }, }) diff --git a/cas/stepcas/x5c_issuer_test.go b/cas/stepcas/x5c_issuer_test.go index df8bc71ca..0349d92ec 100644 --- a/cas/stepcas/x5c_issuer_test.go +++ b/cas/stepcas/x5c_issuer_test.go @@ -229,7 +229,7 @@ func Test_x5cIssuer_Lifetime(t *testing.T) { } func Test_newJoseSigner(t *testing.T) { - mustSigner := func(args ...interface{}) crypto.Signer { + mustSigner := func(args ...any) crypto.Signer { if err := args[len(args)-1]; err != nil { t.Fatal(err) } @@ -260,7 +260,7 @@ func Test_newJoseSigner(t *testing.T) { wantErr bool }{ {"p256", args{p256, nil}, []jose.Header{{Algorithm: "ES256"}}, false}, - {"p384", args{p384, new(jose.SignerOptions).WithType("JWT")}, []jose.Header{{Algorithm: "ES384", ExtraHeaders: map[jose.HeaderKey]interface{}{"typ": "JWT"}}}, false}, + {"p384", args{p384, new(jose.SignerOptions).WithType("JWT")}, []jose.Header{{Algorithm: "ES384", ExtraHeaders: map[jose.HeaderKey]any{"typ": "JWT"}}}, false}, {"p521", args{p521, new(jose.SignerOptions).WithHeader("kid", "the-kid")}, []jose.Header{{Algorithm: "ES512", KeyID: "the-kid"}}, false}, {"ed25519", args{edKey, nil}, []jose.Header{{Algorithm: "EdDSA"}}, false}, {"rsa", args{rsaKey, nil}, []jose.Header{{Algorithm: "RS256"}}, false}, diff --git a/cas/vaultcas/vaultcas.go b/cas/vaultcas/vaultcas.go index f53be6b31..28d745b80 100644 --- a/cas/vaultcas/vaultcas.go +++ b/cas/vaultcas/vaultcas.go @@ -195,7 +195,7 @@ func (v *VaultCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv sn = req.Certificate.SerialNumber } - vaultReq := map[string]interface{}{ + vaultReq := map[string]any{ "serial_number": formatSerialNumber(sn), } _, err := v.client.Logical().Write(v.config.PKIMountPath+"/revoke/", vaultReq) @@ -223,7 +223,7 @@ func (v *VaultCAS) createCertificate(cr *x509.CertificateRequest, lifetime time. return nil, nil, fmt.Errorf("unsupported public key algorithm %v", cr.PublicKeyAlgorithm) } - vaultReq := map[string]interface{}{ + vaultReq := map[string]any{ "csr": string(pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: cr.Raw, diff --git a/cas/vaultcas/vaultcas_test.go b/cas/vaultcas/vaultcas_test.go index 4b9931ba4..b04f47047 100644 --- a/cas/vaultcas/vaultcas_test.go +++ b/cas/vaultcas/vaultcas_test.go @@ -92,7 +92,7 @@ func mustParseCertificateRequest(t *testing.T, pemData string) *x509.Certificate func testCAHelper(t *testing.T) (*url.URL, *vault.Client) { t.Helper() - writeJSON := func(w http.ResponseWriter, v interface{}) { + writeJSON := func(w http.ResponseWriter, v any) { _ = json.NewEncoder(w).Encode(v) } @@ -107,22 +107,22 @@ func testCAHelper(t *testing.T) (*url.URL, *vault.Client) { }`) case "/v1/pki/sign/ec": w.WriteHeader(http.StatusOK) - cert := map[string]interface{}{"data": map[string]interface{}{"certificate": testCertificateSigned + "\n" + testRootCertificate}} + cert := map[string]any{"data": map[string]any{"certificate": testCertificateSigned + "\n" + testRootCertificate}} writeJSON(w, cert) return case "/v1/pki/sign/rsa": w.WriteHeader(http.StatusOK) - cert := map[string]interface{}{"data": map[string]interface{}{"certificate": testCertificateSigned + "\n" + testRootCertificate}} + cert := map[string]any{"data": map[string]any{"certificate": testCertificateSigned + "\n" + testRootCertificate}} writeJSON(w, cert) return case "/v1/pki/sign/ed25519": w.WriteHeader(http.StatusOK) - cert := map[string]interface{}{"data": map[string]interface{}{"certificate": testCertificateSigned + "\n" + testRootCertificate}} + cert := map[string]any{"data": map[string]any{"certificate": testCertificateSigned + "\n" + testRootCertificate}} writeJSON(w, cert) return case "/v1/pki/cert/ca_chain": w.WriteHeader(http.StatusOK) - cert := map[string]interface{}{"data": map[string]interface{}{"certificate": testCertificateSigned + "\n" + testRootCertificate}} + cert := map[string]any{"data": map[string]any{"certificate": testCertificateSigned + "\n" + testRootCertificate}} writeJSON(w, cert) return case "/v1/pki/revoke": diff --git a/cmd/step-ca/main.go b/cmd/step-ca/main.go index 46661e637..45af120c4 100644 --- a/cmd/step-ca/main.go +++ b/cmd/step-ca/main.go @@ -217,7 +217,7 @@ $ step-ca --context=mybiz --password-file ./password.txt func flagValue(f cli.Flag) reflect.Value { fv := reflect.ValueOf(f) - for fv.Kind() == reflect.Ptr { + for fv.Kind() == reflect.Pointer { fv = reflect.Indirect(fv) } return fv diff --git a/commands/onboard.go b/commands/onboard.go index ac0756021..14fd7b577 100644 --- a/commands/onboard.go +++ b/commands/onboard.go @@ -224,7 +224,7 @@ func onboardPKI(cfg onboardingConfiguration) (*config.Config, string, error) { return caConfig, p.GetRootFingerprint(), nil } -func readJSON(r io.ReadCloser, v interface{}) error { +func readJSON(r io.ReadCloser, v any) error { defer r.Close() return json.NewDecoder(r).Decode(v) } diff --git a/db/db.go b/db/db.go index 9afc3b2ed..7da33b0e8 100644 --- a/db/db.go +++ b/db/db.go @@ -489,7 +489,7 @@ func (db *DB) Shutdown() error { // MockAuthDB mocks the AuthDB interface. // type MockAuthDB struct { Err error - Ret1 interface{} + Ret1 any MIsRevoked func(string) (bool, error) MIsSSHRevoked func(string) (bool, error) MRevoke func(rci *RevokedCertificateInfo) error @@ -633,7 +633,7 @@ func (m *MockAuthDB) Shutdown() error { // MockNoSQLDB // type MockNoSQLDB struct { Err error - Ret1, Ret2 interface{} + Ret1, Ret2 any MGet func(bucket, key []byte) ([]byte, error) MSet func(bucket, key, value []byte) error MOpen func(dataSourceName string, opt ...database.Option) error diff --git a/errs/error.go b/errs/error.go index 04de21dd5..c8a4e4bf8 100644 --- a/errs/error.go +++ b/errs/error.go @@ -30,7 +30,7 @@ func withDefaultMessage(message string) Option { // withFormattedMessage returns an Option that modifies the error by overwriting // the formatted message only if it is empty. -func withFormattedMessage(format string, args ...interface{}) Option { +func withFormattedMessage(format string, args ...any) Option { return func(e *Error) error { if e.Msg != "" { return e @@ -42,7 +42,7 @@ func withFormattedMessage(format string, args ...interface{}) Option { // WithMessage returns an Option that modifies the error by overwriting the // message with the formatted string. -func WithMessage(format string, args ...interface{}) Option { +func WithMessage(format string, args ...any) Option { return func(e *Error) error { e.Msg = fmt.Sprintf(format, args...) return e @@ -60,10 +60,10 @@ func WithErrorMessage() Option { // WithKeyVal returns an Option that adds the given key-value pair to the // Error details. This is helpful for debugging errors. -func WithKeyVal(key string, val interface{}) Option { +func WithKeyVal(key string, val any) Option { return func(e *Error) error { if e.Details == nil { - e.Details = make(map[string]interface{}) + e.Details = make(map[string]any) } e.Details[key] = val return e @@ -75,7 +75,7 @@ type Error struct { Status int Err error Msg string - Details map[string]interface{} + Details map[string]any RequestID string `json:"-"` } @@ -116,7 +116,7 @@ func (e *Error) Message() string { // Wrap returns an error annotating err with a stack trace at the point Wrap is // called, and the supplied message. If err is nil, Wrap returns nil. -func Wrap(status int, e error, m string, args ...interface{}) error { +func Wrap(status int, e error, m string, args ...any) error { if e == nil { return nil } @@ -133,7 +133,7 @@ func Wrap(status int, e error, m string, args ...interface{}) error { // Wrapf returns an error annotating err with a stack trace at the point Wrap is // called, and the supplied message. If err is nil, Wrap returns nil. -func Wrapf(status int, e error, format string, args ...interface{}) error { +func Wrapf(status int, e error, format string, args ...any) error { if e == nil { return nil } @@ -264,7 +264,7 @@ func formatMessage(status int, msg string) string { // splitOptionArgs splits the variadic length args into string formatting args // and Option(s) to apply to an Error. -func splitOptionArgs(args []interface{}) ([]interface{}, []Option) { +func splitOptionArgs(args []any) ([]any, []Option) { indexOptionStart := -1 for i, a := range args { if _, ok := a.(Option); ok { @@ -287,7 +287,7 @@ func splitOptionArgs(args []interface{}) ([]interface{}, []Option) { } // New creates a new http error with the given status and message. -func New(status int, format string, args ...interface{}) error { +func New(status int, format string, args ...any) error { msg := fmt.Sprintf(format, args...) return &Error{ Status: status, @@ -297,7 +297,7 @@ func New(status int, format string, args ...interface{}) error { } // NewError creates a new http error with the given error and message. -func NewError(status int, err error, format string, args ...interface{}) error { +func NewError(status int, err error, format string, args ...any) error { var e *Error if errors.As(err, &e) { return err @@ -333,7 +333,7 @@ func NewErr(status int, err error, opts ...Option) error { } // Errorf creates a new error using the given format and status code. -func Errorf(code int, format string, args ...interface{}) error { +func Errorf(code int, format string, args ...any) error { as, opts := splitOptionArgs(args) opts = append(opts, withDefaultMessage(defaultMessage(code))) e := &Error{Status: code, Err: fmt.Errorf(format, as...)} @@ -345,7 +345,7 @@ func Errorf(code int, format string, args ...interface{}) error { // ApplyOptions applies the given options to the error if is the type *Error. // TODO(mariano): try to get rid of this. -func ApplyOptions(err error, opts ...interface{}) error { +func ApplyOptions(err error, opts ...any) error { var e *Error if errors.As(err, &e) { _, o := splitOptionArgs(opts) @@ -357,7 +357,7 @@ func ApplyOptions(err error, opts ...interface{}) error { } // InternalServer creates a 500 error with the given format and arguments. -func InternalServer(format string, args ...interface{}) error { +func InternalServer(format string, args ...any) error { args = append(args, withDefaultMessage(InternalServerErrorDefaultMsg)) return Errorf(http.StatusInternalServerError, format, args...) } @@ -369,7 +369,7 @@ func InternalServerErr(err error, opts ...Option) error { } // NotImplemented creates a 501 error with the given format and arguments. -func NotImplemented(format string, args ...interface{}) error { +func NotImplemented(format string, args ...any) error { args = append(args, withDefaultMessage(NotImplementedDefaultMsg)) return Errorf(http.StatusNotImplemented, format, args...) } @@ -381,17 +381,17 @@ func NotImplementedErr(err error, opts ...Option) error { } // BadRequest creates a 400 error with the given format and arguments. -func BadRequest(format string, args ...interface{}) error { +func BadRequest(format string, args ...any) error { return New(http.StatusBadRequest, format, args...) } // BadRequestErr returns an 400 error with the given error. -func BadRequestErr(err error, format string, args ...interface{}) error { +func BadRequestErr(err error, format string, args ...any) error { return NewError(http.StatusBadRequest, err, format, args...) } // Unauthorized creates a 401 error with the given format and arguments. -func Unauthorized(format string, args ...interface{}) error { +func Unauthorized(format string, args ...any) error { args = append(args, withDefaultMessage(UnauthorizedDefaultMsg)) return Errorf(http.StatusUnauthorized, format, args...) } @@ -403,17 +403,17 @@ func UnauthorizedErr(err error, opts ...Option) error { } // Forbidden creates a 403 error with the given format and arguments. -func Forbidden(format string, args ...interface{}) error { +func Forbidden(format string, args ...any) error { return New(http.StatusForbidden, format, args...) } // ForbiddenErr returns an 403 error with the given error. -func ForbiddenErr(err error, format string, args ...interface{}) error { +func ForbiddenErr(err error, format string, args ...any) error { return NewError(http.StatusForbidden, err, format, args...) } // NotFound creates a 404 error with the given format and arguments. -func NotFound(format string, args ...interface{}) error { +func NotFound(format string, args ...any) error { args = append(args, withDefaultMessage(NotFoundDefaultMsg)) return Errorf(http.StatusNotFound, format, args...) } diff --git a/errs/errors_test.go b/errs/errors_test.go index a5eb4af78..aac7c414e 100644 --- a/errs/errors_test.go +++ b/errs/errors_test.go @@ -190,7 +190,7 @@ func TestErrorf(t *testing.T) { Status: 500, Err: errors.New("test error string"), Msg: InternalServerErrorDefaultMsg, - Details: map[string]interface{}{"foo": 1, "bar": "zar"}, + Details: map[string]any{"foo": 1, "bar": "zar"}, }}, {"withDefaultMessage", 501, "test error string", []any{withDefaultMessage("some message")}, &Error{ Status: 501, diff --git a/examples/basic-client/client.go b/examples/basic-client/client.go index 8bae919cd..3ab62fffd 100644 --- a/examples/basic-client/client.go +++ b/examples/basic-client/client.go @@ -13,7 +13,7 @@ import ( "github.com/smallstep/certificates/ca" ) -func printResponse(name string, v interface{}) { +func printResponse(name string, v any) { b, err := json.MarshalIndent(v, "", " ") if err != nil { panic(err) diff --git a/go.mod b/go.mod index 28b9478b3..3be394ea8 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/smallstep/certificates -go 1.25.8 +go 1.26.0 require ( cloud.google.com/go/longrunning v1.2.0 @@ -38,19 +38,19 @@ require ( github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 github.com/stretchr/testify v1.12.1 github.com/urfave/cli v1.22.17 - go.step.sm/crypto v0.89.0 + go.step.sm/crypto v0.90.0 go.uber.org/mock v0.6.0 - golang.org/x/crypto v0.55.0 + golang.org/x/crypto v0.56.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 - google.golang.org/api v0.292.0 + google.golang.org/api v0.298.0 google.golang.org/grpc v1.83.2 google.golang.org/protobuf v1.36.12 ) require ( cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/auth v0.22.0 // indirect + cloud.google.com/go/auth v0.23.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/iam v1.12.0 // indirect @@ -59,31 +59,31 @@ require ( filippo.io/bigmod v0.1.0 // indirect filippo.io/edwards25519 v1.2.0 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/ThalesGroup/crypto11 v1.6.2 // indirect github.com/aws/aws-sdk-go v1.55.7 // indirect - github.com/aws/aws-sdk-go-v2 v1.43.4 // indirect - github.com/aws/aws-sdk-go-v2/config v1.32.35 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.34 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.55.4 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.5.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.33.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.45.4 // indirect - github.com/aws/smithy-go v1.27.6 // indirect + github.com/aws/aws-sdk-go-v2 v1.45.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.33.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.20.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.19.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.1 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.57.1 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.35.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.40.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.47.1 // indirect + github.com/aws/smithy-go v1.28.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect @@ -109,7 +109,7 @@ require ( github.com/google/go-tpm-tools v0.4.9 // indirect github.com/google/go-tspi v0.3.0 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.19 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.20 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect @@ -170,6 +170,6 @@ require ( golang.org/x/tools v0.48.0 // indirect google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect ) diff --git a/go.sum b/go.sum index 82385d576..a63f9d8c3 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/auth v0.22.0 h1:Xp9wAKkLoeaYb5pYZZoQGz4E9sdPxIbzS3gywZE3ciQ= -cloud.google.com/go/auth v0.22.0/go.mod h1:M9o2Oz+YI2jAfxewJgb1vyI3vceHF+eohmxyzmrl+9s= +cloud.google.com/go/auth v0.23.2 h1:pxSCpfiji41hpzpPdMCftEUCezpgpqmmDdYiAjCKXxo= +cloud.google.com/go/auth v0.23.2/go.mod h1:4DhBRcqvtljQN3dJ57qtqbib5ZGCYE5f2crfiiC2EM0= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= @@ -22,10 +22,10 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0 h1:aokoqcHvaGjiM3VpjKDfMMnF/8epJ+Q1HLJ7CudztqE= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0/go.mod h1:/WYEx9pcM9Y+Dd/APJaNlSvVSvzl54rrMdZT5+Oi2LM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 h1:CU4+EJeJi3TKYWEcYuSdWsjzw0nVsK/H0MSQOiPcymU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0/go.mod h1:q0+UTSRvShwUCrR/s5HtyInYphN7Wvxb7snFM3u+SLA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1/go.mod h1:iptorS+VYKFL2N6PnebpS91dubG35eAOEERnT4PJbQU= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 h1:u93s+zU2JD62im61Bm5CZIc1ZrOJaIAWEg0WOrMVkEo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1/go.mod h1:oXtinPO4OLj9d1DOTrqrL1oRwGhcqadvAmrl6wTeGlk= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0 h1:xFaZZ+IubdftrDHnGGwZ6QvQ3KHTtWl2MCK+GMt2vxs= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.4.0/go.mod h1:mCBhUhlMjLLJKr5aqw2TNS/VqJOie8MzWq3DAMJeKso= github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= @@ -36,8 +36,8 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfg github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 h1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/GoogleCloudPlatform/confidential-space/server v0.0.0-20260522213940-e5c6d01a3007 h1:DoeEFwEGBdqcawmpiWtSsSVVZ+wk3zpqvcvssO2JLmY= @@ -56,36 +56,36 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.43.4 h1:b9FTvbRwy+JCsfp2Wp6wV/KbOx3Aj7nkoFb2cRX0IhE= -github.com/aws/aws-sdk-go-v2 v1.43.4/go.mod h1:70vwSy16txshwG+g55WkpgPKDIByzHI8ccBsOteo3bQ= -github.com/aws/aws-sdk-go-v2/config v1.32.35 h1:UEzXuET8E42lxBPijuACu/tEK7v5lFPlk0Q+GT5WD9E= -github.com/aws/aws-sdk-go-v2/config v1.32.35/go.mod h1:KaMtJpFa2JlL2BStjjHQVwQpzZEmw+ND/EgVrfFoo2g= -github.com/aws/aws-sdk-go-v2/credentials v1.19.34 h1:y6GkSmcv5myd1ngrYbGmiLlwQqB6TQhOuN/tbSSuWDY= -github.com/aws/aws-sdk-go-v2/credentials v1.19.34/go.mod h1:w3dTcnDVoQIewjo7JG45hduAToikiIFLC4FIO7fndvw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35 h1:+S7kbJoLDDQ5tE+lHrUBgMkzC8NLgsaioS2F3dVoFAE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35/go.mod h1:Ak7xXviIARfFdNUJ9Etb0bdVDt/KAvKjMGJVLWXDzik= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35 h1:kzVuGlatQtYinwBJEEyLAbggepCoavosiaHHX9+fD+c= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35/go.mod h1:0yLx0yEI+SfqeJMPvOtIEFoZbiQYXMGszBueiutQyaI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35 h1:WK6CjihTuLisCjSKKbildJ79sGZZgbBz3iNa7VsKIhU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35/go.mod h1:KYleN57luLoe97R7vTnx8PMcVrr9gAcRECtOjl91DNg= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36 h1:jbGY4CXLzZElOXgGsexlC3Hi+3YM0rSmk4opFXKqg/k= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36/go.mod h1:uBu/9aKsS/UQGc72RAt3y54kjgYQxmhut8ZD2dXCDNE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15 h1:JJLBQxwY+AFwuPAi5ivGc1ChnTdUt4cXMv7e76m2c/Y= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15/go.mod h1:lQknBIe78MVL0cQOQDlag8KGflMbMEVFx9mB6O8ENvk= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35 h1:BBEElKh4a+rKshvjrfpajTe9CbpZvrbb4Jkg2PB7RzA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35/go.mod h1:zaZk983w//8beSruBVec/mr4CmDwgZitW/qzGhAAX0g= -github.com/aws/aws-sdk-go-v2/service/kms v1.55.4 h1:8T9CDPlcIUpXTKXXfMMFtD1eujGXbVysGiidx79bTkc= -github.com/aws/aws-sdk-go-v2/service/kms v1.55.4/go.mod h1:XlYycjMbh9zYnTPpjUropzSDngZd/x37jNa9vGHA7hE= -github.com/aws/aws-sdk-go-v2/service/signin v1.5.4 h1:cOJELVNrq5Q3Udry2GLuHUM7MhwpeaQRdYaoa6GI/yI= -github.com/aws/aws-sdk-go-v2/service/signin v1.5.4/go.mod h1:f4LxzKBtaTxD7xh3PiVg3CE1tchQemfmghaJr+NbK2c= -github.com/aws/aws-sdk-go-v2/service/sso v1.33.4 h1:AMW7a7S8iQaHjBYZdU3PCq4GKRPijTPRAc7e6XtEThY= -github.com/aws/aws-sdk-go-v2/service/sso v1.33.4/go.mod h1:QQNsFV1DVXoXcZt18FS8lI8rtUrlDyAuWZLQ5shunv4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4 h1:AsbZcJAQPRmHDJG8K1N0pof/1zPWjVT8TFlTWuGLSvo= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4/go.mod h1:6imqztH0//t0mKbl6yWl7swSEl7F/w32oAmqB3vP1ag= -github.com/aws/aws-sdk-go-v2/service/sts v1.45.4 h1:w/AryDYMjSUANSQ2uoZxJovUsMTwWJNTv3IMex30Y+4= -github.com/aws/aws-sdk-go-v2/service/sts v1.45.4/go.mod h1:WeBiAa67azG7Su9Vf+ChGDBLiAozJCXzdjXiPBUwtbc= -github.com/aws/smithy-go v1.27.6 h1:0zjT8jgK3jbrTT7JJ3EE6JsMhX8JTrZ+f1sEndYDXrA= -github.com/aws/smithy-go v1.27.6/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/aws-sdk-go-v2 v1.45.1 h1:iIoG3NaLhV6UZpPXyPXlDj2I9oS8tV/nMcMnITCC6Ks= +github.com/aws/aws-sdk-go-v2 v1.45.1/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU= +github.com/aws/aws-sdk-go-v2/config v1.33.1 h1:bq9jze1hQ5YTCLoVxNnbp0T7rglrlOE7N9YsHqjGkEw= +github.com/aws/aws-sdk-go-v2/config v1.33.1/go.mod h1:2A3HQwG4zaL5Tm80rc6RZj8LmWWv4WYT5v8raSz/L7A= +github.com/aws/aws-sdk-go-v2/credentials v1.20.1 h1:Z8GRNEx0u9sDkZOq4PUnN8mjGwbUQGRzMSXpvt3d8xQ= +github.com/aws/aws-sdk-go-v2/credentials v1.20.1/go.mod h1:uBIK00kFo95dnemqfFMTWx0X8YRqsh6ecIoCjjOkZqM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.19.1 h1:YIEBqcqRnpi4Pfv0YHImtgi6czGCwKHANC7SwmUAVD0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.19.1/go.mod h1:imEf0oufgAo8KAkCHhrOdqGEC0YWx1PPBQH82shSxGw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.1 h1:pc138gM1CW+XPc60rEwUlwwuwWFQK16CI1T7v1F9Oec= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.1/go.mod h1:1+koxpPIbfBdfzP6vojm5/zTpTQ/micYwlxIiNB3TxI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.1 h1:K0JsbZQj+1h208Ro1zHeA4l7bMp0NvRffHQ91q8Ol1s= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.1/go.mod h1:W3/vL6EtCIatICGy9ab29QhMuae+cOKPWcMxv02CO+Q= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.1 h1:yhw5KD1phVyP9vijxOUzDfEtJx+bt+L63k+VfuiYFAA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.1/go.mod h1:ZW2e0d7DYlRxlS9hEiMXE47gTdX5KRN4byUiNbUpG+Q= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.1 h1:RmmWQPREQdk9U+PfqeHW3MqZaBaNK7TpV9W3RY+b+7g= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.1/go.mod h1:0A3W4F+68ZnNk5XcNL/e9HFMwnP8RlEicFfy6eOEDyw= +github.com/aws/aws-sdk-go-v2/service/kms v1.57.1 h1:z0+ZRgFCZQzc5o4Ke9ni4zXGn/k7Hoy5JkbZPrXl9CI= +github.com/aws/aws-sdk-go-v2/service/kms v1.57.1/go.mod h1:EzyGQwPscu9Pwk4XJx5PrG0g8Wxtc2sv8ullQP1NIJA= +github.com/aws/aws-sdk-go-v2/service/signin v1.7.1 h1:mdMtSVKdQ3+mzBh+l0ogrFYZVQUCg6pJZOirA2ARsYE= +github.com/aws/aws-sdk-go-v2/service/signin v1.7.1/go.mod h1:9IqUlsJDbUPcg6cgx3WEzXdjrbWzLDQrak0aaSqlTcI= +github.com/aws/aws-sdk-go-v2/service/sso v1.35.1 h1:B6WFn91tobD6gG4724ONHaqrpKsoETGnv98LHe/yIGM= +github.com/aws/aws-sdk-go-v2/service/sso v1.35.1/go.mod h1:tWuiVBUtPBr8/rgRiYS8Uf85sHcAN+G7XS3D3CEoUh8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.40.1 h1:6yeYCWFvgbI2TI3K6jr9LtBNhXgJ7g4xqD+DEiaDDmM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.40.1/go.mod h1:naFe83jSMuYkH+QjQPX8n1MLhBkeCFM5Lsnh5m5wz3c= +github.com/aws/aws-sdk-go-v2/service/sts v1.47.1 h1:Sv2xPnRHlThSUtVujYuUBPI/Il8si6UPHXL8DMiB/F0= +github.com/aws/aws-sdk-go-v2/service/sts v1.47.1/go.mod h1:mKo/CzaCz8qytGW70NG4vIIGAx1HXTlb5lHNkC5k3lk= +github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ= +github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/ccoveille/go-safecast/v2 v2.0.1 h1:2+mIu3gXtwmWelBia2kkxfB8eP4orTHDH7ClSlWkd6I= @@ -211,8 +211,8 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= 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.3.19 h1:mMOE7DN2+p76/EdIrmAy9B9bH+yC4563vmnJ34QR8i4= -github.com/googleapis/enterprise-certificate-proxy v0.3.19/go.mod h1:rSEsBUemEBZEexP2y6jPp16LUmUbjmSbcPMQizR0o4k= +github.com/googleapis/enterprise-certificate-proxy v0.3.20 h1:t/xL64VUoN69MuMRQuJETqYGOw4Z9mSRJK9epIEtwFk= +github.com/googleapis/enterprise-certificate-proxy v0.3.20/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w= github.com/googleapis/gax-go/v2 v2.24.1 h1:AtqTN21IXMMWo99LiEVAiBfNNQmO40d8xUfZI640mc0= github.com/googleapis/gax-go/v2 v2.24.1/go.mod h1:bWeBei0NVwaNZKb2y1HUBS7gLXIF3/Tu3pq7j8D2Tb0= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -424,8 +424,8 @@ go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRk go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.step.sm/crypto v0.89.0 h1:NPxryE+cZ4XDwXOQY2U6Gu+mOzOihuRLPIsX7WtPzZM= -go.step.sm/crypto v0.89.0/go.mod h1:IEgE6DndnYojbDyHU7eYXpwWzpu4ftkouw7nyI6jsxg= +go.step.sm/crypto v0.90.0 h1:ZEWK0Ly0RyEC2S2OP1+N/SRbTRU+sW7go0tttHgyXkw= +go.step.sm/crypto v0.90.0/go.mod h1:dgT4uZ4cClpjCi6HZZAmLomgn5tOfpHTqb34lTFN2PQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -443,8 +443,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= -golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= 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/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -533,14 +533,14 @@ golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.292.0 h1:Ewiwo/GTtiaPZSNAZQUcWLh8AYDEoPmIXyJfeoTSMHU= -google.golang.org/api v0.292.0/go.mod h1:07kjmMnFGm2RQuCza2EZM/5N68G/fVvFb1xKjWqoFA0= +google.golang.org/api v0.298.0 h1:YW18RkHBMZBA1ergX0m4biagzgbiPTb2uTsRsDPWNRY= +google.golang.org/api v0.298.0/go.mod h1:02qB8+Ox1ZFzcaKFMguy1nQLJmSIyvV6Ff4txJEXtl4= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d h1:QwnJwPte4XXAkhPu26LTDIahnsMSUV0kK8HkxbC+Pc4= google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d/go.mod h1:WRrQ7/7N19PypuT0fxLOL5Lq0waoiRri4FbtHDEKrGE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= diff --git a/internal/cryptoutil/cryptoutil.go b/internal/cryptoutil/cryptoutil.go new file mode 100644 index 000000000..14099e740 --- /dev/null +++ b/internal/cryptoutil/cryptoutil.go @@ -0,0 +1,82 @@ +// Package cryptoutil provides small helpers for inspecting and encoding the +// key and certificate types used across the CA. +package cryptoutil + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + + "go.step.sm/crypto/mldsa" +) + +// IsSupportedPublicKey reports whether pub is a public key type that the CA +// knows how to work with. Anything else, including a nil key, is rejected. +func IsSupportedPublicKey(pub crypto.PublicKey) bool { + switch pub.(type) { + case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey, *mldsa.PublicKey: + return true + default: + return false + } +} + +// IsSupportedPrivateKey reports whether priv is a private key type that the CA +// knows how to work with. Anything else, including a nil key, is rejected. +// +// Note that keys living outside the process, like the ones backed by a KMS, +// are not reported as supported here; they only satisfy crypto.Signer. +func IsSupportedPrivateKey(priv crypto.PrivateKey) bool { + switch priv.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *mldsa.PrivateKey: + return true + default: + return false + } +} + +// PEMEncode encodes a key, a certificate or a certificate request using PEM. +// +// Public keys are marshaled using PKIX and private keys using PKCS #8, so the +// resulting blocks are always "PUBLIC KEY" and "PRIVATE KEY"; the legacy +// "RSA PRIVATE KEY" and "EC PRIVATE KEY" forms are never produced. Certificates +// and certificate requests are encoded from their raw DER bytes, which means +// they must have been parsed or created beforehand. +func PEMEncode(key any) ([]byte, error) { + switch k := key.(type) { + case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey, *mldsa.PublicKey: + b, err := x509.MarshalPKIXPublicKey(key) + if err != nil { + return nil, fmt.Errorf("error marshaling public key: %w", err) + } + return pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: b, + }), nil + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *mldsa.PrivateKey: + b, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return nil, fmt.Errorf("error marshaling private key: %w", err) + } + return pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: b, + }), nil + case *x509.Certificate: + return pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: k.Raw, + }), nil + case *x509.CertificateRequest: + return pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: k.Raw, + }), nil + default: + return nil, fmt.Errorf("error PEM encoding: unsupported type %T", key) + } +} diff --git a/internal/cryptoutil/cryptoutil_test.go b/internal/cryptoutil/cryptoutil_test.go new file mode 100644 index 000000000..8542e4905 --- /dev/null +++ b/internal/cryptoutil/cryptoutil_test.go @@ -0,0 +1,196 @@ +package cryptoutil + +import ( + "crypto" + "crypto/dsa" //nolint:staticcheck // DSA is only used to build an unsupported key + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.step.sm/crypto/mldsa" +) + +func TestIsSupportedPublicKey(t *testing.T) { + tests := []struct { + name string + pub crypto.PublicKey + want bool + }{ + {"rsa", &rsa.PublicKey{}, true}, + {"ecdsa", &ecdsa.PublicKey{}, true}, + {"ed25519", ed25519.PublicKey{}, true}, + {"mldsa", &mldsa.PublicKey{}, true}, + {"dsa", &dsa.PublicKey{}, false}, + {"rsa value", rsa.PublicKey{}, false}, + {"ecdsa value", ecdsa.PublicKey{}, false}, + {"ed25519 pointer", &ed25519.PublicKey{}, false}, + {"private key", &rsa.PrivateKey{}, false}, + {"bytes", []byte("not a key"), false}, + {"nil", nil, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, IsSupportedPublicKey(tt.pub)) + }) + } +} + +func TestIsSupportedPrivateKey(t *testing.T) { + tests := []struct { + name string + priv crypto.PrivateKey + want bool + }{ + {"rsa", &rsa.PrivateKey{}, true}, + {"ecdsa", &ecdsa.PrivateKey{}, true}, + {"ed25519", ed25519.PrivateKey{}, true}, + {"mldsa", &mldsa.PrivateKey{}, true}, + {"dsa", &dsa.PrivateKey{}, false}, + {"rsa value", rsa.PrivateKey{}, false}, + {"ecdsa value", ecdsa.PrivateKey{}, false}, + {"ed25519 pointer", &ed25519.PrivateKey{}, false}, + {"public key", &rsa.PublicKey{}, false}, + {"bytes", []byte("not a key"), false}, + {"nil", nil, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, IsSupportedPrivateKey(tt.priv)) + }) + } +} + +func TestPEMEncode(t *testing.T) { + rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + _, ed25519Key, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + cert := mustCertificate(t, ecdsaKey) + csr := mustCertificateRequest(t, ecdsaKey) + + type test struct { + name string + key any + want *pem.Block + wantErr string + } + tests := []test{ + {"rsa public key", rsaKey.Public(), &pem.Block{ + Type: "PUBLIC KEY", Bytes: mustPKIX(t, rsaKey.Public()), + }, ""}, + {"ecdsa public key", ecdsaKey.Public(), &pem.Block{ + Type: "PUBLIC KEY", Bytes: mustPKIX(t, ecdsaKey.Public()), + }, ""}, + {"ed25519 public key", ed25519Key.Public(), &pem.Block{ + Type: "PUBLIC KEY", Bytes: mustPKIX(t, ed25519Key.Public()), + }, ""}, + {"rsa private key", rsaKey, &pem.Block{ + Type: "PRIVATE KEY", Bytes: mustPKCS8(t, rsaKey), + }, ""}, + {"ecdsa private key", ecdsaKey, &pem.Block{ + Type: "PRIVATE KEY", Bytes: mustPKCS8(t, ecdsaKey), + }, ""}, + {"ed25519 private key", ed25519Key, &pem.Block{ + Type: "PRIVATE KEY", Bytes: mustPKCS8(t, ed25519Key), + }, ""}, + {"certificate", cert, &pem.Block{ + Type: "CERTIFICATE", Bytes: cert.Raw, + }, ""}, + {"certificate request", csr, &pem.Block{ + Type: "CERTIFICATE REQUEST", Bytes: csr.Raw, + }, ""}, + {"fail public key", &ecdsa.PublicKey{}, nil, "error marshaling public key"}, + {"fail private key", &ecdsa.PrivateKey{}, nil, "error marshaling private key"}, + {"fail dsa public key", &dsa.PublicKey{}, nil, "unsupported type *dsa.PublicKey"}, + {"fail bytes", []byte("not a key"), nil, "unsupported type []uint8"}, + {"fail nil", nil, nil, "unsupported type "}, + } + + // ML-DSA is only available on Go 1.27 and later. On older toolchains the + // mldsa package is a stub that cannot generate keys, so there is nothing + // to encode. + if mldsa.Supported { + mldsaKey, err := mldsa.GenerateKey(mldsa.MLDSA44()) + require.NoError(t, err) + tests = append(tests, + test{"mldsa public key", mldsaKey.Public(), &pem.Block{ + Type: "PUBLIC KEY", Bytes: mustPKIX(t, mldsaKey.Public()), + }, ""}, + test{"mldsa private key", mldsaKey, &pem.Block{ + Type: "PRIVATE KEY", Bytes: mustPKCS8(t, mldsaKey), + }, ""}, + ) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := PEMEncode(tt.key) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + assert.Nil(t, got) + return + } + + require.NoError(t, err) + block, rest := pem.Decode(got) + require.NotNil(t, block) + assert.Empty(t, rest) + assert.Equal(t, tt.want.Type, block.Type) + assert.Equal(t, tt.want.Bytes, block.Bytes) + assert.Empty(t, block.Headers) + }) + } +} + +func mustPKIX(t *testing.T, pub crypto.PublicKey) []byte { + t.Helper() + b, err := x509.MarshalPKIXPublicKey(pub) + require.NoError(t, err) + return b +} + +func mustPKCS8(t *testing.T, priv crypto.PrivateKey) []byte { + t.Helper() + b, err := x509.MarshalPKCS8PrivateKey(priv) + require.NoError(t, err) + return b +} + +func mustCertificate(t *testing.T, signer crypto.Signer) *x509.Certificate { + t.Helper() + template := &x509.Certificate{ + SerialNumber: big.NewInt(1234), + Subject: pkix.Name{CommonName: "test.smallstep.com"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + } + der, err := x509.CreateCertificate(rand.Reader, template, template, signer.Public(), signer) + require.NoError(t, err) + cert, err := x509.ParseCertificate(der) + require.NoError(t, err) + return cert +} + +func mustCertificateRequest(t *testing.T, signer crypto.Signer) *x509.CertificateRequest { + t.Helper() + der, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{ + Subject: pkix.Name{CommonName: "test.smallstep.com"}, + }, signer) + require.NoError(t, err) + csr, err := x509.ParseCertificateRequest(der) + require.NoError(t, err) + return csr +} diff --git a/logging/handler.go b/logging/handler.go index e9823c671..d8c85df64 100644 --- a/logging/handler.go +++ b/logging/handler.go @@ -1,6 +1,7 @@ package logging import ( + "maps" "net" "net/http" "os" @@ -121,9 +122,7 @@ func (l *LoggerHandler) writeEntry(w ResponseLogger, r *http.Request, t time.Tim "user-agent": sanitizeLogEntry(r.UserAgent()), } - for k, v := range w.Fields() { - fields[k] = v - } + maps.Copy(fields, w.Fields()) switch { case status < http.StatusBadRequest: diff --git a/logging/responselogger.go b/logging/responselogger.go index 6f992ac05..f88f7e0c9 100644 --- a/logging/responselogger.go +++ b/logging/responselogger.go @@ -2,6 +2,7 @@ package logging import ( "bufio" + "maps" "net" "net/http" ) @@ -13,8 +14,8 @@ type ResponseLogger interface { http.ResponseWriter Size() int StatusCode() int - Fields() map[string]interface{} - WithFields(map[string]interface{}) + Fields() map[string]any + WithFields(map[string]any) } // NewResponseLogger wraps the given response writer with methods to capture @@ -46,7 +47,7 @@ type rwDefault struct { http.ResponseWriter code int size int - fields map[string]interface{} + fields map[string]any } func (r *rwDefault) Header() http.Header { @@ -72,17 +73,15 @@ func (r *rwDefault) StatusCode() int { return r.code } -func (r *rwDefault) Fields() map[string]interface{} { +func (r *rwDefault) Fields() map[string]any { return r.fields } -func (r *rwDefault) WithFields(fields map[string]interface{}) { +func (r *rwDefault) WithFields(fields map[string]any) { if r.fields == nil { - r.fields = make(map[string]interface{}, len(fields)) - } - for k, v := range fields { - r.fields[k] = v + r.fields = make(map[string]any, len(fields)) } + maps.Copy(r.fields, fields) } type rwFlusher struct { diff --git a/pki/pki.go b/pki/pki.go index c908c0a8c..271ac97ef 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -557,7 +557,7 @@ func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) ( } // WriteRootCertificate writes to the buffer the given certificate and key if given. -func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error { +func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey any, pass []byte) error { p.Files[p.Root[0]] = encodeCertificate(rootCrt) if rootKey != nil { var err error diff --git a/pki/templates.go b/pki/templates.go index bae6db1dd..838061f1d 100644 --- a/pki/templates.go +++ b/pki/templates.go @@ -20,7 +20,7 @@ func (p *PKI) getTemplates() *templates.Templates { } return &templates.Templates{ SSH: &templates.DefaultSSHTemplates, - Data: map[string]interface{}{}, + Data: map[string]any{}, } } diff --git a/policy/validate.go b/policy/validate.go index b66eed567..257d01005 100644 --- a/policy/validate.go +++ b/policy/validate.go @@ -74,7 +74,7 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA } } if err := checkNameConstraints(DNSNameType, dns, parsedDNS, - func(parsedName, constraint interface{}) (bool, error) { + func(parsedName, constraint any) (bool, error) { return e.matchDomainConstraint(parsedName.(string), constraint.(string)) }, e.permittedDNSDomains, e.excludedDNSDomains); err != nil { return err @@ -91,7 +91,7 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA } } if err := checkNameConstraints(IPNameType, ip.String(), ip, - func(parsedName, constraint interface{}) (bool, error) { + func(parsedName, constraint any) (bool, error) { return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet)) }, e.permittedIPRanges, e.excludedIPRanges); err != nil { return err @@ -130,7 +130,7 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA } mailbox.domain = domainASCII if err := checkNameConstraints(EmailNameType, email, mailbox, - func(parsedName, constraint interface{}) (bool, error) { + func(parsedName, constraint any) (bool, error) { return e.matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string)) }, e.permittedEmailAddresses, e.excludedEmailAddresses); err != nil { return err @@ -151,7 +151,7 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA // TODO(hs): ideally we'd like the uri.String() to be the original contents; now // it's transformed into ASCII. Prevent that here? if err := checkNameConstraints(URINameType, uri.String(), uri, - func(parsedName, constraint interface{}) (bool, error) { + func(parsedName, constraint any) (bool, error) { return e.matchURIConstraint(parsedName.(*url.URL), constraint.(string)) }, e.permittedURIDomains, e.excludedURIDomains); err != nil { return err @@ -169,7 +169,7 @@ func (e *NamePolicyEngine) validateNames(dnsNames []string, ips []net.IP, emailA } // TODO: some validation? I.e. allowed characters? if err := checkNameConstraints(PrincipalNameType, principal, principal, - func(parsedName, constraint interface{}) (bool, error) { + func(parsedName, constraint any) (bool, error) { return matchPrincipalConstraint(parsedName.(string), constraint.(string)) }, e.permittedPrincipals, e.excludedPrincipals); err != nil { return err @@ -197,7 +197,7 @@ func (e *NamePolicyEngine) validateCommonName(commonName string) error { // configured. If no error is returned from matching, the Common Name was // explicitly allowed and nil is returned immediately. if err := checkNameConstraints(CNNameType, commonName, commonName, - func(parsedName, constraint interface{}) (bool, error) { + func(parsedName, constraint any) (bool, error) { return matchCommonNameConstraint(parsedName.(string), constraint.(string)) }, e.permittedCommonNames, e.excludedCommonNames); err == nil { return nil @@ -211,8 +211,7 @@ func (e *NamePolicyEngine) validateCommonName(commonName string) error { err := e.validateNames(dnsNames, ips, emails, uris, []string{}) - var pe *NamePolicyError - if errors.As(err, &pe) { + if pe, ok := errors.AsType[*NamePolicyError](err); ok { // override the name type with CN pe.NameType = CNNameType } @@ -226,9 +225,9 @@ func (e *NamePolicyEngine) validateCommonName(commonName string) error { func checkNameConstraints( nameType NameType, name string, - parsedName interface{}, - match func(parsedName, constraint interface{}) (match bool, err error), - permitted, excluded interface{}) error { + parsedName any, + match func(parsedName, constraint any) (match bool, err error), + permitted, excluded any) error { excludedValue := reflect.ValueOf(excluded) for i := 0; i < excludedValue.Len(); i++ { diff --git a/scep/api/api.go b/scep/api/api.go index 32dd754d6..5ea3180d1 100644 --- a/scep/api/api.go +++ b/scep/api/api.go @@ -211,8 +211,7 @@ func decodeMessage(message string, r *http.Request) ([]byte, error) { // only interested in corrupt input errors below this. This type of error is the // most likely to return, but better safe than sorry. - var cie base64.CorruptInputError - if !errors.As(err, &cie) { + if _, ok := errors.AsType[base64.CorruptInputError](err); !ok { return nil, fmt.Errorf("failed base64 decoding message: %w", err) } diff --git a/templates/templates.go b/templates/templates.go index a8cd1df26..0d5fa8ab7 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -48,8 +48,8 @@ func StepFuncMap() template.FuncMap { // Templates is a collection of templates and variables. type Templates struct { - SSH *SSHTemplates `json:"ssh,omitempty"` - Data map[string]interface{} `json:"data,omitempty"` + SSH *SSHTemplates `json:"ssh,omitempty"` + Data map[string]any `json:"data,omitempty"` } // Validate returns an error if a template is not valid. @@ -213,7 +213,7 @@ func (t *Template) LoadBytes(b []byte) error { // Render executes the template with the given data and returns the rendered // version. -func (t *Template) Render(data interface{}) ([]byte, error) { +func (t *Template) Render(data any) ([]byte, error) { if t.Type == Directory { return nil, nil } @@ -230,7 +230,7 @@ func (t *Template) Render(data interface{}) ([]byte, error) { } // Output renders the template and returns a template.Output struct or an error. -func (t *Template) Output(data interface{}) (Output, error) { +func (t *Template) Output(data any) (Output, error) { b, err := t.Render(data) if err != nil { return Output{}, err diff --git a/templates/templates_test.go b/templates/templates_test.go index 42687f701..2a4e41696 100644 --- a/templates/templates_test.go +++ b/templates/templates_test.go @@ -26,7 +26,7 @@ func TestTemplates_Validate(t *testing.T) { } type fields struct { SSH *SSHTemplates - Data map[string]interface{} + Data map[string]any } tests := []struct { name string @@ -34,10 +34,10 @@ func TestTemplates_Validate(t *testing.T) { wantErr bool }{ {"ok", fields{sshTemplates, nil}, false}, - {"okWithData", fields{sshTemplates, map[string]interface{}{"Foo": "Bar"}}, false}, + {"okWithData", fields{sshTemplates, map[string]any{"Foo": "Bar"}}, false}, {"badSSH", fields{&SSHTemplates{User: []Template{{}}}, nil}, true}, - {"badDataUser", fields{sshTemplates, map[string]interface{}{"User": "Bar"}}, true}, - {"badDataStep", fields{sshTemplates, map[string]interface{}{"Step": "Bar"}}, true}, + {"badDataUser", fields{sshTemplates, map[string]any{"User": "Bar"}}, true}, + {"badDataStep", fields{sshTemplates, map[string]any{"Step": "Bar"}}, true}, } var nilValue *Templates assert.NoError(t, nilValue.Validate()) @@ -229,7 +229,7 @@ func TestTemplate_Render(t *testing.T) { assert.FatalError(t, err) hostB64 := base64.StdEncoding.EncodeToString(host.Marshal()) - data := map[string]interface{}{ + data := map[string]any{ "Step": &Step{ SSH: StepSSH{ UserKey: user, @@ -251,7 +251,7 @@ func TestTemplate_Render(t *testing.T) { Comment string } type args struct { - data interface{} + data any } tests := []struct { name string @@ -300,7 +300,7 @@ func TestTemplate_Output(t *testing.T) { assert.FatalError(t, err) hostB64 := base64.StdEncoding.EncodeToString(host.Marshal()) - data := map[string]interface{}{ + data := map[string]any{ "Step": &Step{ SSH: StepSSH{ UserKey: user, @@ -320,7 +320,7 @@ func TestTemplate_Output(t *testing.T) { Comment string } type args struct { - data interface{} + data any } tests := []struct { name string diff --git a/templates/values.go b/templates/values.go index f02ba4766..8c19c17a3 100644 --- a/templates/values.go +++ b/templates/values.go @@ -149,6 +149,6 @@ func DefaultTemplates() *Templates { } return &Templates{ SSH: &sshTemplates, - Data: map[string]interface{}{}, + Data: map[string]any{}, } } diff --git a/templates/values_test.go b/templates/values_test.go index 4bbaa8d33..4fe85e6eb 100644 --- a/templates/values_test.go +++ b/templates/values_test.go @@ -39,7 +39,7 @@ func TestDefaultTemplates(t *testing.T) { {Name: "bar.tpl", Type: Snippet, Content: []byte("bar"), Path: "/tmp/bar", Comment: "#"}, }, }, - Data: map[string]interface{}{}, + Data: map[string]any{}, }}, } for _, tt := range tests { diff --git a/test/integration/requestid_test.go b/test/integration/requestid_test.go index 64300bc19..13f268331 100644 --- a/test/integration/requestid_test.go +++ b/test/integration/requestid_test.go @@ -129,13 +129,11 @@ func Test_reflectRequestID(t *testing.T) { require.NoError(t, err) var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { err = c.Run() require.ErrorIs(t, err, http.ErrServerClosed) - }() + }) // require the CA server to be available within 10 seconds, // failing the test if it doesn't. diff --git a/test/integration/scep/decrypter_cas_test.go b/test/integration/scep/decrypter_cas_test.go index e60dea787..1bd561934 100644 --- a/test/integration/scep/decrypter_cas_test.go +++ b/test/integration/scep/decrypter_cas_test.go @@ -123,13 +123,11 @@ func TestIssuesCertificateUsingSCEPWithDecrypterAndUpstreamCAS(t *testing.T) { require.NoError(t, err) var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { err = c.Run() require.ErrorIs(t, err, http.ErrServerClosed) - }() + }) // instantiate a client for the CA running at the random address caClient := newCAClient(t, fmt.Sprintf("https://localhost:%s", port), rootFilepath) diff --git a/test/integration/scep/decrypter_test.go b/test/integration/scep/decrypter_test.go index 2432f1ad3..69474a07e 100644 --- a/test/integration/scep/decrypter_test.go +++ b/test/integration/scep/decrypter_test.go @@ -113,13 +113,11 @@ func TestIssuesCertificateUsingSCEPWithDecrypter(t *testing.T) { require.NoError(t, err) var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { err = c.Run() require.ErrorIs(t, err, http.ErrServerClosed) - }() + }) // instantiate a client for the CA running at the random address caClient := newCAClient(t, fmt.Sprintf("https://localhost:%s", port), rootFilepath) diff --git a/test/integration/scep/internal/x509/oid.go b/test/integration/scep/internal/x509/oid.go index 2c5a26c44..589ad773b 100644 --- a/test/integration/scep/internal/x509/oid.go +++ b/test/integration/scep/internal/x509/oid.go @@ -133,8 +133,8 @@ func (o *OID) UnmarshalText(text []byte) error { // The found result reports whether sep appears in s. // If sep does not appear in s, cut returns s, "", false. func cutString(s, sep string) (before, after string, found bool) { - if i := strings.Index(s, sep); i >= 0 { - return s[:i], s[i+len(sep):], true + if before, after, ok := strings.Cut(s, sep); ok { + return before, after, true } return s, "", false } diff --git a/test/integration/scep/internal/x509/parser.go b/test/integration/scep/internal/x509/parser.go index c8944da8c..4aa2dcaee 100644 --- a/test/integration/scep/internal/x509/parser.go +++ b/test/integration/scep/internal/x509/parser.go @@ -231,7 +231,7 @@ func parseExtension(der cryptobyte.String) (pkix.Extension, error) { return ext, nil } -func parsePublicKey(keyData *publicKeyInfo) (interface{}, error) { +func parsePublicKey(keyData *publicKeyInfo) (any, error) { oid := keyData.Algorithm.Algorithm params := keyData.Algorithm.Parameters der := cryptobyte.String(keyData.PublicKey.RightAlign()) @@ -340,7 +340,7 @@ func parseKeyUsageExtension(der cryptobyte.String) (stdx509.KeyUsage, error) { } var usage int - for i := 0; i < 9; i++ { + for i := range 9 { if usageBits.At(i) != 0 { usage |= 1 << uint(i) } diff --git a/test/integration/scep/regular_cas_test.go b/test/integration/scep/regular_cas_test.go index 8a93e96d6..ed3b5d104 100644 --- a/test/integration/scep/regular_cas_test.go +++ b/test/integration/scep/regular_cas_test.go @@ -91,13 +91,11 @@ func TestFailsIssuingCertificateUsingRegularSCEPWithUpstreamCAS(t *testing.T) { require.NoError(t, err) var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { err = c.Run() require.ErrorIs(t, err, http.ErrServerClosed) - }() + }) // instantiate a client for the CA running at the random address caClient := newCAClient(t, fmt.Sprintf("https://localhost:%s", port), rootFilepath) diff --git a/test/integration/scep/regular_test.go b/test/integration/scep/regular_test.go index 321f4deb4..5e1d2d3b3 100644 --- a/test/integration/scep/regular_test.go +++ b/test/integration/scep/regular_test.go @@ -15,13 +15,11 @@ func TestIssuesCertificateUsingRegularSCEPConfiguration(t *testing.T) { c := newTestCA(t, "Step E2E | SCEP Regular") var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { err := c.run() require.ErrorIs(t, err, http.ErrServerClosed) - }() + }) // instantiate a client for the CA running at the random address caClient := newCAClient(t, c.caURL, c.rootFilepath) @@ -46,13 +44,11 @@ func TestBlocksCertificateRequestUsingInvalidChallenge(t *testing.T) { c := newTestCA(t, "Step E2E | SCEP Regular w/ invalid challenge") var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { err := c.run() require.ErrorIs(t, err, http.ErrServerClosed) - }() + }) // instantiate a client for the CA running at the random address caClient := newCAClient(t, c.caURL, c.rootFilepath) @@ -74,13 +70,11 @@ func TestBlocksUnsupportedMessageType(t *testing.T) { c := newTestCA(t, "Step E2E | SCEP Regular w/ unsupported message type") var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { err := c.run() require.ErrorIs(t, err, http.ErrServerClosed) - }() + }) // instantiate a client for the CA running at the random address caClient := newCAClient(t, c.caURL, c.rootFilepath)