Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions acme/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"-"`
Expand All @@ -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")
Expand Down Expand Up @@ -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"`
}

Expand Down
9 changes: 4 additions & 5 deletions acme/api/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"net/http"
"slices"

"github.com/go-chi/chi/v5"

Expand All @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion acme/api/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down
3 changes: 1 addition & 2 deletions acme/api/eab.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion acme/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
11 changes: 5 additions & 6 deletions acme/api/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"net/url"
"path"
"slices"
"strings"

"go.step.sm/crypto/jose"
Expand All @@ -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)
Expand Down Expand Up @@ -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))
Expand Down
18 changes: 9 additions & 9 deletions acme/api/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
},
},
Expand Down Expand Up @@ -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,
},
},
Expand Down Expand Up @@ -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",
},
},
Expand Down Expand Up @@ -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,
},
},
Expand All @@ -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,
},
},
Expand All @@ -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,
},
},
Expand Down Expand Up @@ -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,
},
},
Expand Down Expand Up @@ -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,
},
},
Expand Down
8 changes: 4 additions & 4 deletions acme/api/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion acme/api/revoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
31 changes: 15 additions & 16 deletions acme/api/revoke_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}`,
Expand Down Expand Up @@ -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"),
},
Expand Down Expand Up @@ -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,
},
},
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion acme/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading