mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-11-06 05:17:08 +00:00
Merge pull request #142397 from everettraven/upstream/mldsa/sa-token-gen
ML-DSA support for tokens
This commit is contained in:
40 files changed
+864
-84
No files matched your search
@@ -28,7 +28,7 @@ require (
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/emicklei/go-restful/v3 v3.14.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/go-jose/go-jose/v4 v4.1.7
|
||||
github.com/go-jose/go-jose/v4 v4.1.9
|
||||
github.com/go-logr/logr v1.4.4
|
||||
github.com/go-openapi/jsonreference v1.0.2
|
||||
github.com/godbus/dbus/v5 v5.2.4
|
||||
|
||||
@@ -109,8 +109,8 @@ github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01
|
||||
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
|
||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7 h1:Nac/98uT5LPkfbiFuYa8ktf62a2VxwQ4xbq1GK8pV5E=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9 h1:pKfeQkig1NmgK4hJrZYXLEYURXfoJB61Larw0uIW4gY=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
||||
@@ -1033,6 +1033,12 @@ const (
|
||||
// kubelet can request a service account token for.
|
||||
ServiceAccountNodeAudienceRestriction featuregate.Feature = "ServiceAccountNodeAudienceRestriction"
|
||||
|
||||
// owner: @everettraven
|
||||
// issue: https://github.com/kubernetes/kubernetes/issues/141838
|
||||
//
|
||||
// Adds support for using ML-DSA private keys for generating service account tokens and adding support for ML-DSA signing algorithms for service account external JWT.
|
||||
ServiceAccountTokenMLDSA featuregate.Feature = "ServiceAccountTokenMLDSA"
|
||||
|
||||
// owner: @jpbetz
|
||||
//
|
||||
// When enabled, the apiserver ignores changes to status fields in writes to the ServiceCIDR root resource.
|
||||
@@ -1950,6 +1956,11 @@ var defaultVersionedKubernetesFeatureGates = map[featuregate.Feature]featuregate
|
||||
{Version: version.MustParse("1.33"), Default: true, PreRelease: featuregate.Beta},
|
||||
},
|
||||
|
||||
ServiceAccountTokenMLDSA: {
|
||||
{Version: version.MustParse("1.38"), Default: false, PreRelease: featuregate.Beta},
|
||||
{Version: version.MustParse("1.38"), Default: true, PreRelease: featuregate.Beta, MinCompatibilityVersion: version.MustParse("1.38")},
|
||||
},
|
||||
|
||||
ServiceCIDRStatusFieldWiping: {
|
||||
{Version: version.MustParse("1.0"), Default: false, PreRelease: featuregate.GA},
|
||||
{Version: version.MustParse("1.36"), Default: true, PreRelease: featuregate.Deprecated},
|
||||
@@ -2614,6 +2625,8 @@ var defaultKubernetesFeatureGateDependencies = map[featuregate.Feature][]feature
|
||||
|
||||
ServiceAccountNodeAudienceRestriction: {},
|
||||
|
||||
ServiceAccountTokenMLDSA: {},
|
||||
|
||||
ServiceCIDRStatusFieldWiping: {},
|
||||
|
||||
StaleControllerConsistencyDaemonSet: {featuregate.Feature(clientfeatures.AtomicFIFO)},
|
||||
|
||||
@@ -595,7 +595,7 @@ kind: AuthenticationConfiguration
|
||||
anonymous:
|
||||
enabled: false
|
||||
`,
|
||||
OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"},
|
||||
OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -620,7 +620,7 @@ kind: AuthenticationConfiguration
|
||||
anonymous:
|
||||
enabled: true
|
||||
`,
|
||||
OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"},
|
||||
OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -690,7 +690,7 @@ anonymous:
|
||||
conditions:
|
||||
- path: "/livez"
|
||||
`,
|
||||
OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"},
|
||||
OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -752,7 +752,7 @@ jwt:
|
||||
claim: sub
|
||||
prefix: ""
|
||||
`,
|
||||
OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"},
|
||||
OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1034,7 +1034,7 @@ jwt:
|
||||
claim: sub
|
||||
prefix: ""
|
||||
`,
|
||||
OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"},
|
||||
OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -30,7 +30,9 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
externaljwtv1 "k8s.io/externaljwt/apis/v1"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
externaljwtmetrics "k8s.io/kubernetes/pkg/serviceaccount/externaljwt/metrics"
|
||||
)
|
||||
@@ -178,10 +180,15 @@ func (p *Plugin) validateJWTHeader(ctx context.Context, response *externaljwtv1.
|
||||
// - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader
|
||||
// - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go signerFromMLDSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms
|
||||
// - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs
|
||||
case "RS256", "ES256", "ES384", "ES512":
|
||||
// OK
|
||||
case "ML-DSA-44", "ML-DSA-65", "ML-DSA-87":
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenMLDSA) {
|
||||
return fmt.Errorf("bad signing algorithm %q", header.Algorithm)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("bad signing algorithm %q", header.Algorithm)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/mldsa"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
@@ -37,6 +38,8 @@ import (
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// ServiceAccountTokenGetter defines functions to retrieve a named service account and secret
|
||||
@@ -75,6 +78,14 @@ func JWTTokenGenerator(iss string, privateKey interface{}) (TokenGenerator, erro
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate signer for ECDSA keypair: %v", err)
|
||||
}
|
||||
case *mldsa.PrivateKey:
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenMLDSA) {
|
||||
return nil, fmt.Errorf("*mldsa.PrivateKey support is not enabled, must be *rsa.PrivateKey, *ecdsa.PrivateKey, or jose.OpaqueSigner")
|
||||
}
|
||||
signer, err = signerFromMLDSAPrivateKey(pk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not generate signer for ML-DSA keypair: %w", err)
|
||||
}
|
||||
case jose.OpaqueSigner:
|
||||
signer, err = signerFromOpaqueSigner(pk)
|
||||
if err != nil {
|
||||
@@ -90,6 +101,52 @@ func JWTTokenGenerator(iss string, privateKey interface{}) (TokenGenerator, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
func signerFromMLDSAPrivateKey(pk *mldsa.PrivateKey) (jose.Signer, error) {
|
||||
keyID, err := keyIDFromPublicKey(pk.PublicKey())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting key ID from public key: %w", err)
|
||||
}
|
||||
|
||||
alg := ""
|
||||
var joseAlg jose.SignatureAlgorithm
|
||||
|
||||
switch pk.PublicKey().Parameters() {
|
||||
case mldsa.MLDSA44():
|
||||
alg = string(jose.MLDSA44)
|
||||
joseAlg = jose.MLDSA44
|
||||
case mldsa.MLDSA65():
|
||||
alg = string(jose.MLDSA65)
|
||||
joseAlg = jose.MLDSA65
|
||||
case mldsa.MLDSA87():
|
||||
alg = string(jose.MLDSA87)
|
||||
joseAlg = jose.MLDSA87
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown ML-DSA key parameter: %v", pk.PublicKey().Parameters().String())
|
||||
}
|
||||
|
||||
privateJWK := &jose.JSONWebKey{
|
||||
Algorithm: alg,
|
||||
Key: pk,
|
||||
KeyID: keyID,
|
||||
Use: "sig",
|
||||
}
|
||||
|
||||
signer, err := jose.NewSigner(
|
||||
jose.SigningKey{
|
||||
Algorithm: joseAlg,
|
||||
Key: privateJWK,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create signer: %w", err)
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
|
||||
}
|
||||
|
||||
// keyIDFromPublicKey derives a key ID non-reversibly from a public key.
|
||||
//
|
||||
// The Key ID is field on a given on JWTs and JWKs that help relying parties
|
||||
@@ -124,6 +181,7 @@ func signerFromRSAPrivateKey(keyPair *rsa.PrivateKey) (jose.Signer, error) {
|
||||
// - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader
|
||||
// - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go signerFromMLDSAPrivateKey
|
||||
// - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs
|
||||
|
||||
// Wrap the RSA keypair in a JOSE JWK with the designated key ID.
|
||||
@@ -156,6 +214,7 @@ func signerFromECDSAPrivateKey(keyPair *ecdsa.PrivateKey) (jose.Signer, error) {
|
||||
// - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader
|
||||
// - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go signerFromMLDSAPrivateKey
|
||||
// - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs
|
||||
|
||||
switch keyPair.Curve {
|
||||
@@ -341,6 +400,7 @@ type Validator[PrivateClaims any] interface {
|
||||
// - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader
|
||||
// - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go signerFromMLDSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms
|
||||
// - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs
|
||||
var AcceptableServiceAccountSignatureAlgorithms = []jose.SignatureAlgorithm{
|
||||
@@ -348,6 +408,9 @@ var AcceptableServiceAccountSignatureAlgorithms = []jose.SignatureAlgorithm{
|
||||
jose.ES384,
|
||||
jose.ES512,
|
||||
jose.RS256,
|
||||
jose.MLDSA44,
|
||||
jose.MLDSA65,
|
||||
jose.MLDSA87,
|
||||
}
|
||||
|
||||
func (j *jwtTokenAuthenticator[PrivateClaims]) AuthenticateToken(ctx context.Context, tokenData string) (*authenticator.Response, bool, error) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
typedv1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
@@ -37,7 +38,9 @@ import (
|
||||
v1listers "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/keyutil"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
)
|
||||
|
||||
@@ -120,6 +123,51 @@ X2i8uIp/C/ASqiIGUeeKQtX0/IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg==
|
||||
// 3. URLSafe Base64-encoding the sha bytes
|
||||
const ecdsaKeyID = "SoABiieYuNx4UdqYvZRVeuC6SihxgLrhLy9peHMHpTc"
|
||||
|
||||
// openssl genpkey -algorithm ML-DSA-44 -provparam ml-dsa.output_formats=seed-only -out mldsa44-priv.pem
|
||||
const mldsa44PrivateKey = `-----BEGIN PRIVATE KEY-----
|
||||
MDQCAQAwCwYJYIZIAWUDBAMRBCKAIERz6p69gPaVMOBq4wlLM7B/ZKvGjkR3TPXR
|
||||
vh+K2OoE
|
||||
-----END PRIVATE KEY-----`
|
||||
|
||||
// openssl pkey -in mldsa44-priv.pem -pubout -out mldsa44-public.pem
|
||||
const mldsa44PublicKey = `-----BEGIN PUBLIC KEY-----
|
||||
MIIFMjALBglghkgBZQMEAxEDggUhACd2RrSgGNuFoOXNXT3D7fZxUj+ks1cVspRx
|
||||
a1e4xXDX9Ki/nJ6AfGoqC2idkuVFa/ZjTYgXxXvCdNgLHFGpHZ2EvUhEI3qoudiC
|
||||
lRaT++szVlGeJePJZohgJ5sH8tl2bNg8ltKL0pDkA0HZvDxlTxgv/zBrlDx7merW
|
||||
OqX+zLKXxHtOZEY4yM9dKfgZY/d5M5T7EVpPldR5QLzhvlwXmQl5i+sYrFtoJXRd
|
||||
OTibV1AtfwgoHpL6CI0B7/pkmhKtMCL40lCh/j9bqilgrnfp1vI3d5Y2JHcvNLGQ
|
||||
zGJhdbJo2ddZaUzQCha1uLm8vkapfSV5i4Co1l9grqpUAERRskZLggzOjciCcGtM
|
||||
LysX+jHfTVxmQ2qVV0hbPo7IMdhVVVmK7Lc1mr1Q0Wq4nUBr/nd3HpO6/fcQcYK7
|
||||
04kXUmFSIIpGWEOi/vq5XSLBQpXpFtZBrY6o0MkqkXNjFTCcQ0H2x+N36bX6IolO
|
||||
kCYlbbcuFLKplDIW0bx6YIJiKSiAj5eugLPZLPwwNEiafG1Pn6SaNvd+gXo7iBOW
|
||||
HomUsh5auE6iWlIOvOepyDxWUECu1/pRxRqj1crTV7Kvz5q4oawVLzCHHg04pymW
|
||||
ZIQPFgVQN/X4y8lHhX8/9x8+W2yS4uaSzWTL+YfgjLEjGH5EMbVi/KP4zwyUG1Sa
|
||||
NE9pBcXN2e2EXEK/gE/zNOkAJMNi6N/5gocJ2KhT996h7XI/+Jnzc7IMTOoT9BHS
|
||||
9VnwLjPPx/qQ6oOSn+Pgdr+kknQ1XJE3rPn5haEnTKwwe8t5VdqNb82pypX+VHhk
|
||||
FK/tk79+5hnJzj+avD7dCVvbsjR0BafZv+fSZWH8wkmZYitBoN0wlc7F6n8bwKSV
|
||||
VJKE5RD6xMIjs5n+DA0HVDfLYGM+1Qn05V7AaAP0FXuABEP+v65rAqzB3OpfIsow
|
||||
EIrDbgloNz5nenrKEXFJYknONzn8M1eDBIej7RyWRgRtJOg1mNFP8WnbjBFqHfSA
|
||||
mdOSWgVPqA6Wx6NZBLdLTGPK//MVayH2q+ixcemp5LFwYyqMIKQRf3F81uzH9gW9
|
||||
3u5vANCVtCmZT/PIYCCRGy4RzluJHs4tEv3AwgKXMsFhkEU3r0QujAkbqiWgX7rh
|
||||
XDGMoEp/XVOaH+DAiEij3aDvmwfYeaXfOBLlhQgA8QEtv6Rl6Thaj2Cb3h2I2Kct
|
||||
wmqga71FHDUcF2PFmv+g3LIgD7nY0SdYxe7kDIQ5PJgEdzlZRhRvEgeoPCpjdcP6
|
||||
6P0+QHyKEOktwtT1atWlblDq/lrfFvcRgJBxq+CydL4CaHp0Dr9rxF8Y0PGvTEQp
|
||||
Z1987A7LJ2YcGIPsJZ28FSDUJ+rT2gNgv8Ja9+kqbW3pP2z4OVVlVog1TLU96Xbf
|
||||
VZefwnDzYWn9I4y5QMG1gbmN7SRpcYfpiSbKYf5Lsm4sIT6+JmKQ0pTie5BPOhmZ
|
||||
yc6j/aG3Aicq76O/BN75oRg+gSm7rPG6b5A5uTII9lI96ISXdIQ6MsHWEZbB/7ej
|
||||
T3tZ/s0sRQxzPOA9YzbdRh3+g6Hz2LKafI8ob0VgroJ+O0Ev7SCaudHYK3fnkJ/X
|
||||
YHAJEYoBiUxaoLpx3TeNRr8oiciSql7042vMi8cwdspnQ/bnyKrrJRtzMFvr4CCU
|
||||
6BXhb3ZRWBopzR1HW7sJWr5nzjcF6WpPzERogNUmZIcMCrsp3h01VGNGhZD7qQWB
|
||||
AqBiW3lpSxcS4S4TtrM/wCLB9cgtXXW7iYzJljI/VnJ7jN5NbJU=
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
// Obtained by:
|
||||
//
|
||||
// 1. Serializing ecdsaPublicKey as DER
|
||||
// 2. Taking the SHA256 of the DER bytes
|
||||
// 3. URLSafe Base64-encoding the sha bytes
|
||||
const mldsa44KeyID = "ujqYahCmg9pxpxdLGbGbhQyizN2vtd-lMTixkaKHggo"
|
||||
|
||||
func getPrivateKey(data string) interface{} {
|
||||
key, err := keyutil.ParsePrivateKeyPEM([]byte(data))
|
||||
if err != nil {
|
||||
@@ -169,6 +217,12 @@ func TestTokenGenerateAndValidate(t *testing.T) {
|
||||
Namespace: "test",
|
||||
},
|
||||
}
|
||||
mldsa44Secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-mldsa44-secret",
|
||||
Namespace: "test",
|
||||
},
|
||||
}
|
||||
|
||||
// Generate the RSA token
|
||||
rsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(rsaPrivateKey))
|
||||
@@ -237,6 +291,17 @@ func TestTokenGenerateAndValidate(t *testing.T) {
|
||||
t.Fatalf("error generating token: %v", err)
|
||||
}
|
||||
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenMLDSA, true)
|
||||
|
||||
// Generate ML-DSA-44 token
|
||||
mldsa44Token := generateMLDSA44Token(t, serviceaccount.LegacyIssuer, serviceAccount, mldsa44Secret)
|
||||
|
||||
mldsa44Secret.Data = map[string][]byte{
|
||||
"token": []byte(mldsa44Token),
|
||||
}
|
||||
|
||||
checkJSONWebSignatureHasKeyID(t, mldsa44Token, mldsa44KeyID)
|
||||
|
||||
testCases := map[string]struct {
|
||||
Client clientset.Interface
|
||||
Keys []interface{}
|
||||
@@ -269,6 +334,13 @@ func TestTokenGenerateAndValidate(t *testing.T) {
|
||||
ExpectedErr: true,
|
||||
ExpectedOK: false,
|
||||
},
|
||||
"invalid keys (mldsa)": {
|
||||
Token: mldsa44Token,
|
||||
Client: nil,
|
||||
Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey)},
|
||||
ExpectedErr: true,
|
||||
ExpectedOK: false,
|
||||
},
|
||||
"valid key (rsa)": {
|
||||
Token: rsaToken,
|
||||
Client: nil,
|
||||
@@ -306,6 +378,16 @@ func TestTokenGenerateAndValidate(t *testing.T) {
|
||||
ExpectedUserUID: expectedUserUID,
|
||||
ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
|
||||
},
|
||||
"valid key (mldsa)": {
|
||||
Token: mldsa44Token,
|
||||
Client: nil,
|
||||
Keys: []interface{}{getPublicKey(mldsa44PublicKey)},
|
||||
ExpectedErr: false,
|
||||
ExpectedOK: true,
|
||||
ExpectedUserName: expectedUserName,
|
||||
ExpectedUserUID: expectedUserUID,
|
||||
ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
|
||||
},
|
||||
"rotated keys (rsa)": {
|
||||
Token: rsaToken,
|
||||
Client: nil,
|
||||
@@ -326,6 +408,16 @@ func TestTokenGenerateAndValidate(t *testing.T) {
|
||||
ExpectedUserUID: expectedUserUID,
|
||||
ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
|
||||
},
|
||||
"rotated keys (mldsa)": {
|
||||
Token: mldsa44Token,
|
||||
Client: nil,
|
||||
Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey), getPublicKey(mldsa44PublicKey)},
|
||||
ExpectedErr: false,
|
||||
ExpectedOK: true,
|
||||
ExpectedUserName: expectedUserName,
|
||||
ExpectedUserUID: expectedUserUID,
|
||||
ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"},
|
||||
},
|
||||
"valid lookup": {
|
||||
Token: rsaToken,
|
||||
Client: fake.NewSimpleClientset(serviceAccount, rsaSecret, ecdsaSecret),
|
||||
@@ -551,6 +643,25 @@ func generateECDSATokenWithMalformedIss(t *testing.T, serviceAccount *v1.Service
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func generateMLDSA44Token(t *testing.T, iss string, serviceAccount *v1.ServiceAccount, mldsa44Secret *v1.Secret) string {
|
||||
t.Helper()
|
||||
|
||||
mldsaGenerator, err := serviceaccount.JWTTokenGenerator(iss, getPrivateKey(mldsa44PrivateKey))
|
||||
if err != nil {
|
||||
t.Fatalf("error making generator: %v", err)
|
||||
}
|
||||
c, pc := serviceaccount.LegacyClaims(*serviceAccount, *mldsa44Secret)
|
||||
mldsaToken, err := mldsaGenerator.GenerateToken(context.TODO(), c, pc)
|
||||
if err != nil {
|
||||
t.Fatalf("error generating token: %v", err)
|
||||
}
|
||||
if len(mldsaToken) == 0 {
|
||||
t.Fatalf("no token generated")
|
||||
}
|
||||
|
||||
return mldsaToken
|
||||
}
|
||||
|
||||
func TestStaticPublicKeysGetter(t *testing.T) {
|
||||
ecPrivate := getPrivateKey(ecdsaPrivateKey)
|
||||
ecPublic := getPublicKey(ecdsaPublicKey)
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/mldsa"
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -31,7 +32,9 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -66,15 +69,18 @@ type openidConfig struct {
|
||||
func (p *openidConfigProvider) GetConfigJSON() ([]byte, int) {
|
||||
return p.config.Load().configJSON, p.pubKeyGetter.GetCacheAgeMaxSeconds()
|
||||
}
|
||||
|
||||
func (p *openidConfigProvider) GetKeysetJSON() ([]byte, int) {
|
||||
return p.config.Load().keysetJSON, p.pubKeyGetter.GetCacheAgeMaxSeconds()
|
||||
}
|
||||
|
||||
func (p *openidConfigProvider) Enqueue() {
|
||||
err := p.Update()
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to update openid config metadata")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *openidConfigProvider) Update() error {
|
||||
pubKeys := []PublicKey{}
|
||||
|
||||
@@ -326,6 +332,21 @@ func algorithmFromPublicKey(publicKey crypto.PublicKey) (jose.SignatureAlgorithm
|
||||
default:
|
||||
return "", fmt.Errorf("unknown private key curve, must be 256, 384, or 521")
|
||||
}
|
||||
case *mldsa.PublicKey:
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.ServiceAccountTokenMLDSA) {
|
||||
return "", fmt.Errorf("*mldsa.PrivateKey support is not enabled, must be *rsa.PrivateKey, *ecdsa.PrivateKey, or jose.OpaqueSigner")
|
||||
}
|
||||
|
||||
switch pk.Parameters() {
|
||||
case mldsa.MLDSA44():
|
||||
return jose.MLDSA44, nil
|
||||
case mldsa.MLDSA65():
|
||||
return jose.MLDSA65, nil
|
||||
case mldsa.MLDSA87():
|
||||
return jose.MLDSA87, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unknown ml-dsa key parameters, must be ML-DSA-44, ML-DSA-65, or ML-DSA-87")
|
||||
}
|
||||
case jose.OpaqueSigner:
|
||||
return jose.SignatureAlgorithm(pk.Public().Algorithm), nil
|
||||
default:
|
||||
|
||||
@@ -32,6 +32,9 @@ import (
|
||||
jose "github.com/go-jose/go-jose/v4"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/routes"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
)
|
||||
@@ -97,6 +100,10 @@ func TestServeConfiguration(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mldsaKeysGetter, err := serviceaccount.StaticPublicKeysGetter([]interface{}{getPublicKey(mldsa44PublicKey)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keysGetter := &proxyKeyGetter{PublicKeysGetter: ecKeysGetter}
|
||||
s, jwksURI := setupServer(t, exampleIssuer, keysGetter)
|
||||
defer s.Close()
|
||||
@@ -115,6 +122,13 @@ func TestServeConfiguration(t *testing.T) {
|
||||
SubjectTypes: []string{"public"},
|
||||
SigningAlgs: []string{"RS256"},
|
||||
}
|
||||
wantMLDSA := Configuration{
|
||||
Issuer: exampleIssuer,
|
||||
JWKSURI: jwksURI,
|
||||
ResponseTypes: []string{"id_token"},
|
||||
SubjectTypes: []string{"public"},
|
||||
SigningAlgs: []string{"ML-DSA-44"},
|
||||
}
|
||||
reqURL := s.URL + "/.well-known/openid-configuration"
|
||||
|
||||
expectConfiguration(t, reqURL, wantEC)
|
||||
@@ -128,6 +142,16 @@ func TestServeConfiguration(t *testing.T) {
|
||||
listener.Enqueue()
|
||||
}
|
||||
expectConfiguration(t, reqURL, wantRSA)
|
||||
|
||||
keysGetter.PublicKeysGetter = mldsaKeysGetter
|
||||
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAccountTokenMLDSA, true)
|
||||
|
||||
// notify the metadata the keys changed, expected a modified response
|
||||
for _, listener := range keysGetter.listeners {
|
||||
listener.Enqueue()
|
||||
}
|
||||
expectConfiguration(t, reqURL, wantMLDSA)
|
||||
}
|
||||
|
||||
func expectConfiguration(t *testing.T, reqURL string, want Configuration) {
|
||||
@@ -168,7 +192,7 @@ func TestServeKeys(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var serveKeysTests = []struct {
|
||||
serveKeysTests := []struct {
|
||||
Name string
|
||||
Keys []interface{}
|
||||
WantKeys []jose.JSONWebKey
|
||||
@@ -349,6 +373,7 @@ func TestServeKeys(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func expectKeys(t *testing.T, reqURL string, wantKeys []jose.JSONWebKey) {
|
||||
resp, err := http.Get(reqURL)
|
||||
if err != nil {
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
||||
@@ -13,7 +13,7 @@ require (
|
||||
github.com/coreos/go-systemd/v22 v22.7.0
|
||||
github.com/emicklei/go-restful/v3 v3.14.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/go-jose/go-jose/v4 v4.1.7
|
||||
github.com/go-jose/go-jose/v4 v4.1.9
|
||||
github.com/go-logr/logr v1.4.4
|
||||
github.com/go-openapi/jsonreference v1.0.2
|
||||
github.com/google/gnostic-models v0.7.1
|
||||
|
||||
Generated
+2
-2
@@ -52,8 +52,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7 h1:Nac/98uT5LPkfbiFuYa8ktf62a2VxwQ4xbq1GK8pV5E=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9 h1:pKfeQkig1NmgK4hJrZYXLEYURXfoJB61Larw0uIW4gY=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
||||
@@ -255,16 +255,22 @@ func AllValidSigningAlgorithms() []string {
|
||||
}
|
||||
|
||||
// allowlist of signing algorithms to ensure users don't mistakenly pass something goofy.
|
||||
//
|
||||
// IMPORTANT: The algorithms in this list must support at least the algorithms
|
||||
// that we support for serviceaccount tokens (pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms).
|
||||
var allowedSigningAlgs = map[string]bool{
|
||||
oidc.RS256: true,
|
||||
oidc.RS384: true,
|
||||
oidc.RS512: true,
|
||||
oidc.ES256: true,
|
||||
oidc.ES384: true,
|
||||
oidc.ES512: true,
|
||||
oidc.PS256: true,
|
||||
oidc.PS384: true,
|
||||
oidc.PS512: true,
|
||||
oidc.RS256: true,
|
||||
oidc.RS384: true,
|
||||
oidc.RS512: true,
|
||||
oidc.ES256: true,
|
||||
oidc.ES384: true,
|
||||
oidc.ES512: true,
|
||||
oidc.PS256: true,
|
||||
oidc.PS384: true,
|
||||
oidc.PS512: true,
|
||||
string(jose.MLDSA44): true,
|
||||
string(jose.MLDSA65): true,
|
||||
string(jose.MLDSA87): true,
|
||||
}
|
||||
|
||||
type AuthenticatorTokenWithHealthCheck interface {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/mldsa"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -88,6 +89,38 @@ func loadECDSAPrivKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm
|
||||
})
|
||||
}
|
||||
|
||||
func loadMLDSAKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey {
|
||||
return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) {
|
||||
key, err := x509.ParsePKCS8PrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mldsaKey, ok := key.(*mldsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("loaded key is not an ML-DSA private key, got %T", key)
|
||||
}
|
||||
|
||||
return mldsaKey.PublicKey(), nil
|
||||
})
|
||||
}
|
||||
|
||||
func loadMLDSAPrivKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm) *jose.JSONWebKey {
|
||||
return loadKey(t, filepath, alg, func(b []byte) (interface{}, error) {
|
||||
key, err := x509.ParsePKCS8PrivateKey(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mldsaKey, ok := key.(*mldsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("loaded key is not an ML-DSA private key, got %T", key)
|
||||
}
|
||||
|
||||
return mldsaKey, nil
|
||||
})
|
||||
}
|
||||
|
||||
func loadKey(t *testing.T, filepath string, alg jose.SignatureAlgorithm, unmarshal func([]byte) (interface{}, error)) *jose.JSONWebKey {
|
||||
data, err := os.ReadFile(filepath)
|
||||
if err != nil {
|
||||
@@ -2018,6 +2051,39 @@ func TestToken(t *testing.T) {
|
||||
Name: "jane",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mldsa-44",
|
||||
options: Options{
|
||||
JWTAuthenticator: apiserver.JWTAuthenticator{
|
||||
Issuer: apiserver.Issuer{
|
||||
URL: "https://auth.example.com",
|
||||
Audiences: []string{"my-client"},
|
||||
},
|
||||
ClaimMappings: apiserver.ClaimMappings{
|
||||
Username: apiserver.PrefixedClaimOrExpression{
|
||||
Claim: "username",
|
||||
Prefix: new(""),
|
||||
},
|
||||
},
|
||||
},
|
||||
SupportedSigningAlgs: []string{"ML-DSA-44"},
|
||||
now: func() time.Time { return now },
|
||||
},
|
||||
signingKey: loadMLDSAPrivKey(t, "testdata/mldsa_2.pem", jose.MLDSA44),
|
||||
pubKeys: []*jose.JSONWebKey{
|
||||
loadMLDSAKey(t, "testdata/mldsa_1.pem", jose.MLDSA44),
|
||||
loadMLDSAKey(t, "testdata/mldsa_2.pem", jose.MLDSA44),
|
||||
},
|
||||
claims: fmt.Sprintf(`{
|
||||
"iss": "https://auth.example.com",
|
||||
"aud": "my-client",
|
||||
"username": "jane",
|
||||
"exp": %d
|
||||
}`, valid.Unix()),
|
||||
want: &user.DefaultInfo{
|
||||
Name: "jane",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not-https",
|
||||
options: Options{
|
||||
|
||||
+4
@@ -26,4 +26,8 @@ for N in $(seq 1 3); do
|
||||
ssh-keygen -t ecdsa -b 521 -f ecdsa_"$N".pem -N ''
|
||||
done
|
||||
|
||||
for N in $(seq 1 3); do
|
||||
openssl genpkey -algorithm ML-DSA-44 -provparam ml-dsa.output_formats=seed-only -out mldsa_"$N".pem
|
||||
done
|
||||
|
||||
rm ./*.pub
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MDQCAQAwCwYJYIZIAWUDBAMRBCKAILcgxkjA2tFxP8V/KWAYGWa9lJRHSsGD/ea7
|
||||
06Ho3ATv
|
||||
-----END PRIVATE KEY-----
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MDQCAQAwCwYJYIZIAWUDBAMRBCKAIJL22V98+3MTtacNF4D/fcu5yyOgpPjTr/C3
|
||||
Ue3eGfBH
|
||||
-----END PRIVATE KEY-----
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MDQCAQAwCwYJYIZIAWUDBAMRBCKAIInkcsgPS8SuzcIcPsJVbLs+z9nsa0KTDV6W
|
||||
LWlyRijt
|
||||
-----END PRIVATE KEY-----
|
||||
Generated
+1
-1
@@ -47,7 +47,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
||||
@@ -7,7 +7,7 @@ go 1.27.0
|
||||
godebug default=go1.27
|
||||
|
||||
require (
|
||||
github.com/go-jose/go-jose/v4 v4.1.7
|
||||
github.com/go-jose/go-jose/v4 v4.1.9
|
||||
github.com/stretchr/testify v1.12.1
|
||||
k8s.io/api v0.0.0
|
||||
k8s.io/apimachinery v0.0.0
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7 h1:Nac/98uT5LPkfbiFuYa8ktf62a2VxwQ4xbq1GK8pV5E=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9 h1:pKfeQkig1NmgK4hJrZYXLEYURXfoJB61Larw0uIW4gY=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-openapi/jsonpointer v1.0.2/go.mod h1:N0rI4MbNKD7xAFTiHtB+5Nhrg2j/wwYg7m7Fmb4/jk8=
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
|
||||
@@ -106,7 +106,7 @@ type SignJWTResponse struct {
|
||||
// header must contain only alg, kid, typ claims.
|
||||
// typ must be “JWT”.
|
||||
// kid must be non-empty, <=1024 characters, and its corresponding public key should not be excluded from OIDC discovery.
|
||||
// alg must be one of the algorithms supported by kube-apiserver (currently RS256, ES256, ES384, ES512).
|
||||
// alg must be one of the algorithms supported by kube-apiserver (currently RS256, ES256, ES384, ES512, ML-DSA-44, ML-DSA-65, ML-DSA-87).
|
||||
// header cannot have any additional data that kube-apiserver does not recognize.
|
||||
// Already wrapped in URL-safe base64, exactly as it appears in the first segment of the JWT.
|
||||
Header string `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
|
||||
|
||||
@@ -55,7 +55,7 @@ service ExternalJWTSigner {
|
||||
// header must contain only alg, kid, typ claims.
|
||||
// typ must be “JWT”.
|
||||
// kid must be non-empty, <=1024 characters, and its corresponding public key should not be excluded from OIDC discovery.
|
||||
// alg must be one of the algorithms supported by kube-apiserver (currently RS256, ES256, ES384, ES512).
|
||||
// alg must be one of the algorithms supported by kube-apiserver (currently RS256, ES256, ES384, ES512, ML-DSA-44, ML-DSA-65, ML-DSA-87).
|
||||
// header cannot have any additional data that kube-apiserver does not recognize.
|
||||
// Already wrapped in URL-safe base64, exactly as it appears in the first segment of the JWT.
|
||||
string header = 1;
|
||||
@@ -109,4 +109,4 @@ service ExternalJWTSigner {
|
||||
//
|
||||
// `max_token_expiration_seconds` must be at least 600s.
|
||||
int64 max_token_expiration_seconds = 1;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -45,7 +45,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.7/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.9/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
||||
@@ -197,6 +197,7 @@
|
||||
| SecurityProfileOCI | | | 1.38– | | | | | [code](https://cs.k8s.io/?q=%5CbSecurityProfileOCI%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbSecurityProfileOCI%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| SeparateCacheWatchRPC | :ballot_box_with_check: 1.28+ | :closed_lock_with_key: 1.36+ | | 1.28–1.32 | | 1.33– | | [code](https://cs.k8s.io/?q=%5CbSeparateCacheWatchRPC%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbSeparateCacheWatchRPC%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| ServiceAccountNodeAudienceRestriction | :ballot_box_with_check: 1.33+ | | | 1.32– | | | | [code](https://cs.k8s.io/?q=%5CbServiceAccountNodeAudienceRestriction%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceAccountNodeAudienceRestriction%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| ServiceAccountTokenMLDSA | :ballot_box_with_check: 1.38+ | | | 1.38– | | | | [code](https://cs.k8s.io/?q=%5CbServiceAccountTokenMLDSA%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceAccountTokenMLDSA%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| ServiceCIDRStatusFieldWiping | :ballot_box_with_check: 1.36+ | | | | 1.0–1.35 | 1.36– | | [code](https://cs.k8s.io/?q=%5CbServiceCIDRStatusFieldWiping%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbServiceCIDRStatusFieldWiping%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| ShardedListAndWatch | :ballot_box_with_check: 1.38+ | | 1.36–1.37 | 1.38– | | | | [code](https://cs.k8s.io/?q=%5CbShardedListAndWatch%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbShardedListAndWatch%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
| SizeBasedListCostEstimate | :ballot_box_with_check: 1.34+ | | | 1.34– | | | | [code](https://cs.k8s.io/?q=%5CbSizeBasedListCostEstimate%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/kubernetes) [KEPs](https://cs.k8s.io/?q=%5CbSizeBasedListCostEstimate%5Cb&i=nope&files=&excludeFiles=CHANGELOG&repos=kubernetes/enhancements) |
|
||||
|
||||
@@ -1806,6 +1806,17 @@
|
||||
lockToDefault: false
|
||||
preRelease: Beta
|
||||
version: "1.33"
|
||||
- name: ServiceAccountTokenMLDSA
|
||||
versionedSpecs:
|
||||
- default: false
|
||||
lockToDefault: false
|
||||
preRelease: Beta
|
||||
version: "1.38"
|
||||
- default: true
|
||||
lockToDefault: false
|
||||
preRelease: Beta
|
||||
version: "1.38"
|
||||
minCompatibilityVersion: "1.38"
|
||||
- name: ServiceCIDRStatusFieldWiping
|
||||
versionedSpecs:
|
||||
- default: false
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/go-jose/go-jose/v4/jwt"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/oauth2"
|
||||
@@ -128,12 +129,13 @@ func validate(ctx context.Context, raw string) error {
|
||||
// - pkg/serviceaccount/externaljwt/plugin/plugin.go validateJWTHeader
|
||||
// - pkg/serviceaccount/jwt.go signerFromRSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go signerFromECDSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go signerFromMLDSAPrivateKey
|
||||
// - pkg/serviceaccount/jwt.go AcceptableServiceAccountSignatureAlgorithms
|
||||
// - test/images/agnhost/openidmetadata/openidmetadata.go validate SupportedSigningAlgs
|
||||
|
||||
validTok, err := iss.Verifier(&oidc.Config{
|
||||
ClientID: audience,
|
||||
SupportedSigningAlgs: []string{oidc.RS256, oidc.ES256, oidc.ES384, oidc.ES512},
|
||||
SupportedSigningAlgs: []string{oidc.RS256, oidc.ES256, oidc.ES384, oidc.ES512, string(jose.MLDSA44), string(jose.MLDSA65), string(jose.MLDSA87)},
|
||||
}).Verify(ctx, raw)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+19
-13
@@ -61,16 +61,19 @@ standard where possible. The Godoc reference has a list of constants.
|
||||
|
||||
<sup>1. Not supported in multi-recipient mode</sup>
|
||||
|
||||
| Signing / MAC | Algorithm identifier(s) |
|
||||
|:------------------|:------------------------|
|
||||
| RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 |
|
||||
| RSASSA-PSS | PS256, PS384, PS512 |
|
||||
| HMAC | HS256, HS384, HS512 |
|
||||
| ECDSA | ES256, ES384, ES512 |
|
||||
| Ed25519 | EdDSA<sup>2</sup> |
|
||||
| Signing / MAC | Algorithm identifier(s) |
|
||||
|:------------------|:--------------------------------------------|
|
||||
| RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 |
|
||||
| RSASSA-PSS | PS256, PS384, PS512 |
|
||||
| HMAC | HS256, HS384, HS512 |
|
||||
| ECDSA | ES256, ES384, ES512 |
|
||||
| Ed25519 | EdDSA<sup>2</sup> |
|
||||
| ML-DSA | ML-DSA-44, ML-DSA-65, ML-DSA-87<sup>3</sup> |
|
||||
|
||||
<sup>2. Only available in version 2 of the package</sup>
|
||||
|
||||
<sup>3. Requires Go 1.27 or later</sup>
|
||||
|
||||
| Content encryption | Algorithm identifier(s) |
|
||||
|:-------------------|:--------------------------------------------|
|
||||
| AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 |
|
||||
@@ -87,15 +90,18 @@ library, and can be passed to corresponding functions such as `NewEncrypter` or
|
||||
`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which
|
||||
allows attaching a key id.
|
||||
|
||||
| Algorithm(s) | Corresponding types |
|
||||
|:------------------|--------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey) |
|
||||
| ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey) |
|
||||
| EdDSA<sup>1</sup> | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey) |
|
||||
| AES, HMAC | []byte |
|
||||
| Algorithm(s) | Corresponding types |
|
||||
|:-------------------|--------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey) |
|
||||
| ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey) |
|
||||
| EdDSA<sup>1</sup> | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey) |
|
||||
| ML-DSA<sup>2</sup> | *[mldsa.PublicKey](https://pkg.go.dev/crypto/mldsa#PublicKey), *[mldsa.PrivateKey](https://pkg.go.dev/crypto/mldsa#PrivateKey) |
|
||||
| AES, HMAC | []byte |
|
||||
|
||||
<sup>1. Only available in version 2 or later of the package</sup>
|
||||
|
||||
<sup>2. Requires Go 1.27 or later</sup>
|
||||
|
||||
## Examples
|
||||
|
||||
[](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
|
||||
|
||||
+5
@@ -21,5 +21,10 @@ the JSON Web Encryption and JSON Web Signature standards, with optional JSON Web
|
||||
Token support available in a sub-package. The library supports both the compact
|
||||
and JWS/JWE JSON Serialization formats, and has optional support for multiple
|
||||
recipients.
|
||||
|
||||
ML-DSA (FIPS 204) signing and verification, and the AKP JSON Web Key type, are
|
||||
defined by RFC 9964 and require Go 1.27 or later. On earlier toolchains the
|
||||
ML-DSA algorithms are recognized but unusable, and AKP keys fail to parse with
|
||||
ErrUnsupportedKeyType.
|
||||
*/
|
||||
package jose
|
||||
+46
-11
@@ -51,6 +51,11 @@ type rawJSONWebKey struct {
|
||||
Y *byteBuffer `json:"y,omitempty"`
|
||||
N *byteBuffer `json:"n,omitempty"`
|
||||
E *byteBuffer `json:"e,omitempty"`
|
||||
// AKP (RFC 9964). "pub" is the encoded public key; "priv" is the 32-byte
|
||||
// seed. An AKP key has no "crv" / "alg" identifies the parameter set, and is
|
||||
// REQUIRED.
|
||||
Pub *byteBuffer `json:"pub,omitempty"`
|
||||
Priv *byteBuffer `json:"priv,omitempty"`
|
||||
// -- Following fields are only used for private keys --
|
||||
// RSA uses D, P and Q, while ECDSA uses only D. Fields Dp, Dq, and Qi are
|
||||
// completely optional. Therefore for RSA/ECDSA, D != nil is a contract that
|
||||
@@ -122,7 +127,11 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
||||
case []byte:
|
||||
raw, err = fromSymmetricKey(key)
|
||||
default:
|
||||
return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||
var ok bool
|
||||
raw, ok, err = mldsaRawJWK(k.Key)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -130,9 +139,19 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
raw.Kid = k.KeyID
|
||||
raw.Alg = k.Algorithm
|
||||
raw.Use = k.Use
|
||||
|
||||
if raw.Kty == "AKP" {
|
||||
// RFC 9964 requires "alg" on every AKP key, and mldsaRawJWK already
|
||||
// derived it from the key's parameter set. Rather than let a caller's
|
||||
// Algorithm silently overwrite it, treat a disagreement as an error.
|
||||
if k.Algorithm != "" && k.Algorithm != raw.Alg {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: JWK algorithm %q does not match ML-DSA key parameter set %q", k.Algorithm, raw.Alg)
|
||||
}
|
||||
} else {
|
||||
raw.Alg = k.Algorithm
|
||||
}
|
||||
|
||||
for _, cert := range k.Certificates {
|
||||
raw.X5c = append(raw.X5c, base64.StdEncoding.EncodeToString(cert.Raw))
|
||||
}
|
||||
@@ -177,7 +196,7 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// UnmarshalJSON reads a key from its JSON representation.
|
||||
//
|
||||
// Returns ErrUnsupportedKeyType for unrecognized or unsupported "kty" header values.
|
||||
// Returns ErrUnsupportedKeyType for unsupported key types or parameters.
|
||||
//
|
||||
// Directly unmarshaling a JWK is not safe for untrusted input.
|
||||
func (k *JSONWebKey) UnmarshalJSON(data []byte) error {
|
||||
@@ -197,7 +216,7 @@ func (k *JSONWebKey) unmarshalJSON(data []byte, allowPrivate bool) (err error) {
|
||||
|
||||
if !allowPrivate &&
|
||||
(raw.K != nil || raw.D != nil || raw.P != nil || raw.Q != nil ||
|
||||
raw.Dp != nil || raw.Dq != nil || raw.Qi != nil) {
|
||||
raw.Dp != nil || raw.Dq != nil || raw.Qi != nil || raw.Priv != nil) {
|
||||
return errors.New("go-jose/go-jose: JWK contains private parameters")
|
||||
}
|
||||
|
||||
@@ -273,6 +292,14 @@ func (k *JSONWebKey) unmarshalJSON(data []byte, allowPrivate bool) (err error) {
|
||||
keyPub = key
|
||||
}
|
||||
}
|
||||
case "AKP":
|
||||
key, err = mldsaParseJWK(&raw, certPub)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// mldsaParseJWK compared certPub using (*mldsa.PublicKey).Equal, so the
|
||||
// generic comparison below only needs to confirm that a leaf key exists.
|
||||
keyPub = certPub
|
||||
case "":
|
||||
// kty MUST be present
|
||||
return fmt.Errorf("go-jose/go-jose: missing json web key type")
|
||||
@@ -446,7 +473,11 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
|
||||
case OpaqueSigner:
|
||||
return key.Public().Thumbprint(hash)
|
||||
default:
|
||||
return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||
var ok bool
|
||||
input, ok, err = mldsaThumbprintInput(k.Key)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -464,7 +495,7 @@ func (k *JSONWebKey) IsPublic() bool {
|
||||
case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
return mldsaIsPublic(k.Key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,7 +513,11 @@ func (k *JSONWebKey) Public() JSONWebKey {
|
||||
case ed25519.PrivateKey:
|
||||
ret.Key = key.Public()
|
||||
default:
|
||||
return JSONWebKey{} // returning invalid key
|
||||
pub, ok := mldsaPublicOf(k.Key)
|
||||
if !ok {
|
||||
return JSONWebKey{} // returning invalid key
|
||||
}
|
||||
ret.Key = pub
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -518,7 +553,7 @@ func (k *JSONWebKey) Valid() bool {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
return mldsaValid(k.Key)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -542,7 +577,7 @@ func rsaPublicKey(N, E *byteBuffer) (*rsa.PublicKey, error) {
|
||||
|
||||
n := N.bigInt()
|
||||
if n.BitLen() > MaxRSAModulusBits {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, modulus too large")
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
e := new(big.Int).SetBytes(E.bytes())
|
||||
@@ -591,7 +626,7 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
|
||||
case "P-521":
|
||||
curve = elliptic.P521()
|
||||
default:
|
||||
return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
if key.X == nil || key.Y == nil {
|
||||
@@ -798,7 +833,7 @@ func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
|
||||
case "P-521":
|
||||
curve = elliptic.P521()
|
||||
default:
|
||||
return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
var missing []string
|
||||
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
//go:build go1.27
|
||||
|
||||
/*-
|
||||
* Copyright 2026 The Go JOSE Authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/mldsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// mldsaParamsFor maps a JOSE signature algorithm to its ML-DSA parameter set.
|
||||
//
|
||||
// See RFC 9964, which registers ML-DSA-44, ML-DSA-65 and ML-DSA-87 as JWS "alg"
|
||||
// values for the FIPS 204 parameter sets of the same names.
|
||||
func mldsaParamsFor(alg SignatureAlgorithm) (mldsa.Parameters, bool) {
|
||||
switch alg {
|
||||
case MLDSA44:
|
||||
return mldsa.MLDSA44(), true
|
||||
case MLDSA65:
|
||||
return mldsa.MLDSA65(), true
|
||||
case MLDSA87:
|
||||
return mldsa.MLDSA87(), true
|
||||
default:
|
||||
return mldsa.Parameters{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// mldsaAlgFor maps an ML-DSA parameter set to its JOSE signature algorithm. An
|
||||
// AKP JWK carries no "crv", so the parameter set is the only thing that
|
||||
// identifies which algorithm a key belongs to.
|
||||
func mldsaAlgFor(params mldsa.Parameters) (SignatureAlgorithm, bool) {
|
||||
switch params {
|
||||
case mldsa.MLDSA44():
|
||||
return MLDSA44, true
|
||||
case mldsa.MLDSA65():
|
||||
return MLDSA65, true
|
||||
case mldsa.MLDSA87():
|
||||
return MLDSA87, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// mldsaIsPublic reports whether key is an ML-DSA public key.
|
||||
func mldsaIsPublic(key interface{}) bool {
|
||||
_, ok := key.(*mldsa.PublicKey)
|
||||
return ok
|
||||
}
|
||||
|
||||
// mldsaValid reports whether key is an initialized ML-DSA key. It backs
|
||||
// JSONWebKey.Valid, which is how a caller asks this question. mldsaVerifier
|
||||
// rejects a nil or zero-value key and mldsaSigner rejects a nil key. The other
|
||||
// hooks treat an uninitialized key as a programmer error, and crypto/mldsa
|
||||
// panics on one.
|
||||
func mldsaValid(key interface{}) bool {
|
||||
switch k := key.(type) {
|
||||
case *mldsa.PublicKey:
|
||||
return k != nil && *k != (mldsa.PublicKey{})
|
||||
case *mldsa.PrivateKey:
|
||||
return k != nil && *k != (mldsa.PrivateKey{})
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// mldsaPublicOf returns the public half of an ML-DSA private key.
|
||||
func mldsaPublicOf(key interface{}) (interface{}, bool) {
|
||||
if k, ok := key.(*mldsa.PrivateKey); ok {
|
||||
return k.PublicKey(), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// mldsaPrivateKeySigner signs payloads with an ML-DSA private key. The algorithm
|
||||
// is resolved from the key's parameter set once at construction and cached.
|
||||
type mldsaPrivateKeySigner struct {
|
||||
privateKey *mldsa.PrivateKey
|
||||
alg SignatureAlgorithm
|
||||
}
|
||||
|
||||
// mldsaPublicKeyVerifier verifies payloads with an ML-DSA public key.
|
||||
type mldsaPublicKeyVerifier struct {
|
||||
publicKey *mldsa.PublicKey
|
||||
alg SignatureAlgorithm
|
||||
}
|
||||
|
||||
// mldsaSigner creates a recipientSigInfo for an ML-DSA private key. The second
|
||||
// return value reports whether key was an ML-DSA key at all.
|
||||
func mldsaSigner(sigAlg SignatureAlgorithm, key interface{}) (recipientSigInfo, bool, error) {
|
||||
privateKey, ok := key.(*mldsa.PrivateKey)
|
||||
if !ok {
|
||||
return recipientSigInfo{}, false, nil
|
||||
}
|
||||
if privateKey == nil {
|
||||
return recipientSigInfo{}, true, errors.New("invalid private key")
|
||||
}
|
||||
|
||||
alg, ok := mldsaAlgFor(privateKey.PublicKey().Parameters())
|
||||
if !ok || alg != sigAlg {
|
||||
return recipientSigInfo{}, true, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
return recipientSigInfo{
|
||||
sigAlg: sigAlg,
|
||||
publicKey: staticPublicKey(&JSONWebKey{
|
||||
Key: privateKey.PublicKey(),
|
||||
}),
|
||||
signer: &mldsaPrivateKeySigner{
|
||||
privateKey: privateKey,
|
||||
alg: alg,
|
||||
},
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
// mldsaVerifier creates a payloadVerifier for an ML-DSA public key.
|
||||
func mldsaVerifier(key interface{}) (payloadVerifier, bool, error) {
|
||||
publicKey, ok := key.(*mldsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
if publicKey == nil || *publicKey == (mldsa.PublicKey{}) {
|
||||
return nil, true, errInvalidVerificationKey
|
||||
}
|
||||
|
||||
alg, ok := mldsaAlgFor(publicKey.Parameters())
|
||||
if !ok {
|
||||
return nil, true, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
return &mldsaPublicKeyVerifier{
|
||||
publicKey: publicKey,
|
||||
alg: alg,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
// mldsaRawJWK converts an ML-DSA key to its AKP JWK representation. The "alg"
|
||||
// member is derived from the key's own parameter set, because RFC 9964 requires
|
||||
// it on every AKP key and it is the only member identifying the parameter set.
|
||||
func mldsaRawJWK(key interface{}) (*rawJSONWebKey, bool, error) {
|
||||
var (
|
||||
pub *mldsa.PublicKey
|
||||
priv *mldsa.PrivateKey
|
||||
)
|
||||
|
||||
switch k := key.(type) {
|
||||
case *mldsa.PublicKey:
|
||||
pub = k
|
||||
case *mldsa.PrivateKey:
|
||||
priv, pub = k, k.PublicKey()
|
||||
default:
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
alg, ok := mldsaAlgFor(pub.Parameters())
|
||||
if !ok {
|
||||
return nil, true, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
raw := &rawJSONWebKey{
|
||||
Kty: "AKP",
|
||||
Alg: string(alg),
|
||||
Pub: newBuffer(pub.Bytes()),
|
||||
}
|
||||
if priv != nil {
|
||||
// RFC 9964: priv MUST be the seed, and MUST be 32 bytes. Bytes() returns
|
||||
// exactly that.
|
||||
raw.Priv = newBuffer(priv.Bytes())
|
||||
}
|
||||
|
||||
return raw, true, nil
|
||||
}
|
||||
|
||||
// mldsaParseJWK builds an ML-DSA key from an AKP JWK.
|
||||
//
|
||||
// RFC 9964 makes "alg" REQUIRED for AKP keys: unlike EC and OKP there is no
|
||||
// "crv", so "alg" is the only member identifying the parameter set. A JWK
|
||||
// without a recognized "alg" is therefore not merely unusual but unusable, and
|
||||
// is rejected with ErrUnsupportedKeyType.
|
||||
//
|
||||
// A parsed key can never carry an alg/material disagreement: params is derived
|
||||
// from raw.Alg below, and mldsa.NewPrivateKey/NewPublicKey both reject key
|
||||
// material whose length disagrees with those params.
|
||||
//
|
||||
// When certPub is non-nil it is checked against the key's public half here,
|
||||
// using (*mldsa.PublicKey).Equal, so that the caller does not have to compare
|
||||
// unexported crypto/mldsa internals with reflect.DeepEqual.
|
||||
func mldsaParseJWK(raw *rawJSONWebKey, certPub interface{}) (interface{}, error) {
|
||||
params, ok := mldsaParamsFor(SignatureAlgorithm(raw.Alg))
|
||||
if !ok {
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
// RFC 9964 makes "pub" REQUIRED on every AKP JWK.
|
||||
if raw.Pub == nil {
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
var (
|
||||
key interface{}
|
||||
pub *mldsa.PublicKey
|
||||
)
|
||||
|
||||
if raw.Priv != nil {
|
||||
// RFC 9964: priv MUST be the seed, and MUST be 32 bytes. NewPrivateKey
|
||||
// enforces the length.
|
||||
privateKey, err := mldsa.NewPrivateKey(params, raw.Priv.bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: invalid ML-DSA private key: %w", err)
|
||||
}
|
||||
pub = privateKey.PublicKey()
|
||||
if !bytes.Equal(raw.Pub.bytes(), pub.Bytes()) {
|
||||
return nil, errors.New("go-jose/go-jose: invalid ML-DSA key, pub does not match the public key derived from priv")
|
||||
}
|
||||
key = privateKey
|
||||
} else {
|
||||
// NewPublicKey rejects an encoding whose length disagrees with params,
|
||||
// which is what catches an alg/pub mismatch.
|
||||
publicKey, err := mldsa.NewPublicKey(params, raw.Pub.bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("go-jose/go-jose: invalid ML-DSA public key: %w", err)
|
||||
}
|
||||
pub = publicKey
|
||||
key = publicKey
|
||||
}
|
||||
|
||||
if certPub != nil && !pub.Equal(certPub) {
|
||||
return nil, errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match")
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (ctx mldsaPrivateKeySigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||
if alg != ctx.alg {
|
||||
return Signature{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// RFC 9964 specifies empty context string and doesn't specify pre-hashing.
|
||||
// Passing a `nil` crypto.SignerOpts gets us that.
|
||||
sig, err := ctx.privateKey.Sign(nil, payload, nil)
|
||||
if err != nil {
|
||||
return Signature{}, err
|
||||
}
|
||||
|
||||
return Signature{
|
||||
Signature: sig,
|
||||
protected: &rawHeader{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctx mldsaPublicKeyVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
|
||||
// Reject a header algorithm that disagrees with the key's parameter set, so
|
||||
// an ML-DSA-44 key can never be used under an ML-DSA-87 header.
|
||||
if alg != ctx.alg {
|
||||
return ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
// A nil Options means an empty context string, matching RFC 9964.
|
||||
if err := mldsa.Verify(ctx.publicKey, payload, signature, nil); err != nil {
|
||||
return fmt.Errorf("go-jose/go-jose: %s signature failed to verify: %w", ctx.alg, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mldsaThumbprintTemplate is the JWK Thumbprint input for AKP keys. RFC 9964
|
||||
// gives the required members as "alg", "kty" and "pub"; RFC 7638 requires them
|
||||
// in lexicographic order with no whitespace. Note that AKP is the only key type
|
||||
// in this package whose thumbprint includes "alg". The verbs take the algorithm
|
||||
// name and the unpadded base64url encoding of the public key's FIPS 204 encoding.
|
||||
const mldsaThumbprintTemplate = `{"alg":"%s","kty":"AKP","pub":"%s"}`
|
||||
|
||||
// mldsaThumbprintInput builds the JWK Thumbprint input for an ML-DSA key. A
|
||||
// private key thumbprints as its public half.
|
||||
//
|
||||
// key is the value of JSONWebKey.Key and may be of any type; only
|
||||
// *mldsa.PublicKey and *mldsa.PrivateKey are ML-DSA keys.
|
||||
//
|
||||
// The second return value reports whether key was an ML-DSA key at all. When it
|
||||
// is false the string is empty and the error is nil, leaving the caller to
|
||||
// report the unknown key type. When it is true the error is non-nil if the key's
|
||||
// parameter set has no JOSE algorithm.
|
||||
func mldsaThumbprintInput(key interface{}) (string, bool, error) {
|
||||
var pub *mldsa.PublicKey
|
||||
|
||||
switch k := key.(type) {
|
||||
case *mldsa.PublicKey:
|
||||
pub = k
|
||||
case *mldsa.PrivateKey:
|
||||
pub = k.PublicKey()
|
||||
default:
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
alg, ok := mldsaAlgFor(pub.Parameters())
|
||||
if !ok {
|
||||
return "", true, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
return fmt.Sprintf(mldsaThumbprintTemplate, alg, newBuffer(pub.Bytes()).base64()), true, nil
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
//go:build !go1.27
|
||||
|
||||
/*-
|
||||
* Copyright 2026 The Go JOSE Authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This file provides the ML-DSA seam for toolchains without crypto/mldsa, which
|
||||
// was added in Go 1.27. Every hook reports that it did not recognize the key, so
|
||||
// callers fall through to their existing unsupported-key/algorithm error.
|
||||
//
|
||||
// Each identifier here must have a counterpart in mldsa.go.
|
||||
|
||||
package jose
|
||||
|
||||
func mldsaIsPublic(_ interface{}) bool { return false }
|
||||
|
||||
func mldsaValid(_ interface{}) bool { return false }
|
||||
|
||||
func mldsaPublicOf(_ interface{}) (interface{}, bool) { return nil, false }
|
||||
|
||||
func mldsaSigner(_ SignatureAlgorithm, _ interface{}) (recipientSigInfo, bool, error) {
|
||||
return recipientSigInfo{}, false, nil
|
||||
}
|
||||
|
||||
func mldsaVerifier(_ interface{}) (payloadVerifier, bool, error) { return nil, false, nil }
|
||||
|
||||
func mldsaRawJWK(_ interface{}) (*rawJSONWebKey, bool, error) { return nil, false, nil }
|
||||
|
||||
func mldsaParseJWK(_ *rawJSONWebKey, _ interface{}) (interface{}, error) {
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
func mldsaThumbprintInput(_ interface{}) (string, bool, error) { return "", false, nil }
|
||||
+20
-2
@@ -16,6 +16,12 @@
|
||||
|
||||
package jose
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
// OpaqueSigner is an interface that supports signing payloads with opaque
|
||||
// private key(s). Private key operations performed by implementers may, for
|
||||
// example, occur in a hardware module. An OpaqueSigner may rotate signing keys
|
||||
@@ -46,8 +52,7 @@ func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigI
|
||||
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
pk := signer.Public()
|
||||
if pk != nil && !pk.IsPublic() {
|
||||
if pk := signer.Public(); pk != nil && isKnownPrivateKeyType(pk.Key) {
|
||||
return recipientSigInfo{}, ErrNotPublic
|
||||
}
|
||||
|
||||
@@ -60,6 +65,19 @@ func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigI
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isKnownPrivateKeyType reports whether key is a private key of one of the
|
||||
// supported signing algorithms. Other key types are allowed, since an
|
||||
// OpaqueSigner may use a key type that go-jose does not know about.
|
||||
func isKnownPrivateKeyType(key any) bool {
|
||||
switch key.(type) {
|
||||
case *ecdsa.PrivateKey, *rsa.PrivateKey, ed25519.PrivateKey:
|
||||
return true
|
||||
default:
|
||||
_, ok := mldsaPublicOf(key)
|
||||
return ok
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opaqueSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||
out, err := o.signer.SignPayload(payload, alg)
|
||||
if err != nil {
|
||||
|
||||
+19
-22
@@ -53,11 +53,8 @@ var (
|
||||
// algorithm that is not yet implemented.
|
||||
ErrUnsupportedAlgorithm = errors.New("go-jose/go-jose: unknown/unsupported algorithm")
|
||||
|
||||
// ErrUnsupportedKeyType indicates that the given key type/format is not
|
||||
// supported. This occurs when trying to instantiate an encrypter and passing
|
||||
// it a key of an unrecognized type or with unsupported parameters, such as
|
||||
// an RSA private key with more than two primes.
|
||||
ErrUnsupportedKeyType = errors.New("go-jose/go-jose: unsupported key type/format")
|
||||
// ErrUnsupportedKeyType indicates that the given key is not supported.
|
||||
ErrUnsupportedKeyType = errors.New("go-jose/go-jose: unsupported key type, format, curve, or RSA modulus size")
|
||||
|
||||
// ErrInvalidKeySize indicates that the given key is not the correct size
|
||||
// for the selected algorithm. This can occur, for example, when trying to
|
||||
@@ -76,9 +73,6 @@ var (
|
||||
// ErrMissingX5cHeader indicates that the JWT header is missing x5c headers.
|
||||
ErrMissingX5cHeader = errors.New("go-jose/go-jose: no x5c header present in message")
|
||||
|
||||
// ErrUnsupportedEllipticCurve indicates unsupported or unknown elliptic curve has been found.
|
||||
ErrUnsupportedEllipticCurve = errors.New("go-jose/go-jose: unsupported/unknown elliptic curve")
|
||||
|
||||
// ErrUnsupportedCriticalHeader is returned when a header is marked critical but not supported by go-jose.
|
||||
ErrUnsupportedCriticalHeader = errors.New("go-jose/go-jose: unsupported critical header")
|
||||
|
||||
@@ -113,19 +107,22 @@ const (
|
||||
|
||||
// Signature algorithms
|
||||
const (
|
||||
EdDSA = SignatureAlgorithm("EdDSA")
|
||||
HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256
|
||||
HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384
|
||||
HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512
|
||||
RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256
|
||||
RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384
|
||||
RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512
|
||||
ES256 = SignatureAlgorithm("ES256") // ECDSA using P-256 and SHA-256
|
||||
ES384 = SignatureAlgorithm("ES384") // ECDSA using P-384 and SHA-384
|
||||
ES512 = SignatureAlgorithm("ES512") // ECDSA using P-521 and SHA-512
|
||||
PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256
|
||||
PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384
|
||||
PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512
|
||||
EdDSA = SignatureAlgorithm("EdDSA")
|
||||
HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256
|
||||
HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384
|
||||
HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512
|
||||
RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256
|
||||
RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384
|
||||
RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512
|
||||
ES256 = SignatureAlgorithm("ES256") // ECDSA using P-256 and SHA-256
|
||||
ES384 = SignatureAlgorithm("ES384") // ECDSA using P-384 and SHA-384
|
||||
ES512 = SignatureAlgorithm("ES512") // ECDSA using P-521 and SHA-512
|
||||
PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256
|
||||
PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384
|
||||
PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512
|
||||
MLDSA44 = SignatureAlgorithm("ML-DSA-44") // ML-DSA-44 (FIPS 204, RFC 9964), requires Go 1.27 or later
|
||||
MLDSA65 = SignatureAlgorithm("ML-DSA-65") // ML-DSA-65 (FIPS 204, RFC 9964), requires Go 1.27 or later
|
||||
MLDSA87 = SignatureAlgorithm("ML-DSA-87") // ML-DSA-87 (FIPS 204, RFC 9964), requires Go 1.27 or later
|
||||
)
|
||||
|
||||
// Content encryption algorithms
|
||||
@@ -527,7 +524,7 @@ func curveName(crv elliptic.Curve) (string, error) {
|
||||
case elliptic.P521():
|
||||
return "P-521", nil
|
||||
default:
|
||||
return "", ErrUnsupportedEllipticCurve
|
||||
return "", ErrUnsupportedKeyType
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
@@ -43,6 +43,7 @@ type Signer interface {
|
||||
//
|
||||
// Key must have one of these types:
|
||||
// - ed25519.PrivateKey
|
||||
// - *mldsa.PrivateKey (requires Go 1.27 or later)
|
||||
// - *ecdsa.PrivateKey
|
||||
// - *rsa.PrivateKey
|
||||
// - *JSONWebKey
|
||||
@@ -224,6 +225,9 @@ func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
|
||||
case OpaqueVerifier:
|
||||
return &opaqueVerifier{verifier: verificationKey}, nil
|
||||
default:
|
||||
if verifier, ok, err := mldsaVerifier(verificationKey); ok {
|
||||
return verifier, err
|
||||
}
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
}
|
||||
@@ -255,6 +259,9 @@ func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipient
|
||||
case OpaqueSigner:
|
||||
return newOpaqueSigner(alg, signingKey)
|
||||
default:
|
||||
if recipient, ok, err := mldsaSigner(alg, signingKey); ok {
|
||||
return recipient, err
|
||||
}
|
||||
return recipientSigInfo{}, ErrUnsupportedKeyType
|
||||
}
|
||||
}
|
||||
@@ -300,6 +307,12 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
||||
//
|
||||
// See https://github.com/square/go-jose/issues/157 for more context.
|
||||
if ctx.embedJWK {
|
||||
// MarshalJSON can fail for a semantically inconsistent key (an AKP
|
||||
// key whose Algorithm contradicts its parameter set). Surface that
|
||||
// as an error rather than letting mustSerializeJSON panic below.
|
||||
if _, err := recipientPubKey.MarshalJSON(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protected[headerJWK] = recipientPubKey
|
||||
} else {
|
||||
keyID := recipientPubKey.KeyID
|
||||
@@ -381,6 +394,7 @@ func (ctx *genericSigner) Options() SignerOptions {
|
||||
//
|
||||
// The verificationKey argument must have one of these types:
|
||||
// - ed25519.PublicKey
|
||||
// - *mldsa.PublicKey (requires Go 1.27 or later)
|
||||
// - *ecdsa.PublicKey
|
||||
// - *rsa.PublicKey
|
||||
// - *JSONWebKey
|
||||
|
||||
Vendored
+1
-1
@@ -182,7 +182,7 @@ github.com/fxamacker/cbor/v2
|
||||
# github.com/go-errors/errors v1.5.1
|
||||
## explicit; go 1.14
|
||||
github.com/go-errors/errors
|
||||
# github.com/go-jose/go-jose/v4 v4.1.7
|
||||
# github.com/go-jose/go-jose/v4 v4.1.9
|
||||
## explicit; go 1.24.0
|
||||
github.com/go-jose/go-jose/v4
|
||||
github.com/go-jose/go-jose/v4/cipher
|
||||
|
||||
Reference in new issue
Block a user