From 4d4a931545fe9bc859b33f4a111d2f552934f93f Mon Sep 17 00:00:00 2001 From: Felix Phipps Date: Thu, 17 Sep 2026 15:17:33 +0100 Subject: [PATCH 1/2] VC-59682: Align the go directive with the vendored Go 1.27.1 toolchain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem go.mod declares go 1.26.4 while VENDORED_GO_VERSION is 1.27.1, the toolchain CI builds with. Richard's 0c29720 (#831) moved the toolchain and left the directive behind. This fixes no bug — the agent was never broken for ML-DSA, since it ships raw PEM unparsed and its one customer-certificate parse point reads only IsCA and ExtKeyUsage. But the mismatch has two costs: * The compiler gates standard library symbols on the go directive, so nothing in this repo can import crypto/mldsa and the agent's ML-DSA behaviour cannot be pinned by a test at all. * Nothing stops a Go 1.26 build. Once anyone reads cert.PublicKeyAlgorithm to report a key type, which is where PQC discovery is heading, that build silently reports 0 instead of failing to compile. Solution Move the directive to 1.27.1 and add ML-DSA-65 cases to TestIsExcludableSecret pinning that a post-quantum chain is classified exactly like the P-256 one. The helper imports crypto/mldsa, so the suite now also fails to build on an older toolchain, which gives the directive teeth. The bump activates modernize/embedlit, which would rewrite ~250 lines of unrelated Kubernetes object literals and whose --fix leaves a tree that does not compile; disabled in .golangci.yaml. Remaining go.mod churn is go mod tidy canonicalising three require blocks into two. Test plan * TestIsExcludableSecret — ML-DSA-65 client-auth leaf kept, server-auth leaf excluded, matching the existing P-256 cases. All 11 subtests pass. * The agent's parse path run against a real ML-DSA-65 chain on go1.26.5 and go1.27.1 gives identical classification, confirming this is a guard, not a fix. * make -j verify green — golangci-lint 0 issues at GOVERSION=1.27.1, tree clean. * make -j test-unit green — 465 tests, 4 skipped, 0 failures. * Rebased onto 2b69e98; go.mod require set identical to master. Co-Authored-By: Claude Opus 5 --- .golangci.yaml | 10 +++ go.mod | 63 ++++++++-------- ...lient_cyberark_convertdatareadings_test.go | 74 +++++++++++++++++++ 3 files changed, 114 insertions(+), 33 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index e312e398..419cef03 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -16,6 +16,16 @@ linters: paths: [third_party, builtin$, examples$] warn-unused: true settings: + modernize: + # embedlit activates at language version 1.27 and rewrites every + # Kubernetes object literal in the repo, turning + # corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "x"}} + # into + # corev1.Pod{Name: "x"} + # The flattened form hides which embedded struct a field came from, and + # mixes TypeMeta and ObjectMeta fields into one list. Keep it off. + disable: + - embedlit staticcheck: checks: ["all", "-ST1000", "-ST1001", "-ST1003", "-ST1005", "-ST1012", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1001", "-QF1003", "-QF1008"] enable: diff --git a/go.mod b/go.mod index 832eb347..e75daf02 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,13 @@ // TODO(wallrj): Rename the Go module to match the repository name module github.com/jetstack/preflight -go 1.26.4 +go 1.27.1 require ( github.com/cenkalti/backoff/v5 v5.0.3 github.com/fatih/color v1.19.0 + github.com/go-logr/logr v1.4.3 + github.com/golang-jwt/jwt/v4 v4.5.2 github.com/google/uuid v1.6.0 github.com/hashicorp/go-multierror v1.1.1 github.com/jetstack/venafi-connection-lib v0.6.1-0.20260528123542-443dd7e48a1a @@ -18,10 +20,12 @@ require ( github.com/stretchr/testify v1.11.1 golang.org/x/sync v0.23.0 gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.36.1 k8s.io/apimachinery v0.36.1 k8s.io/client-go v0.36.1 k8s.io/component-base v0.36.1 + k8s.io/klog/v2 v2.140.0 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/yaml v1.6.0 ) @@ -30,13 +34,20 @@ require ( cel.dev/expr v0.25.2 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect github.com/fxamacker/cbor/v2 v2.9.2 // indirect github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/jsonreference v0.21.6 // indirect + github.com/go-openapi/swag v0.26.0 // indirect github.com/go-openapi/swag/cmdutils v0.26.0 // indirect github.com/go-openapi/swag/conv v0.26.0 // indirect github.com/go-openapi/swag/fileutils v0.26.0 // indirect @@ -55,12 +66,22 @@ require ( github.com/google/cel-go v0.30.0 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/gorilla/css v1.0.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc/v3 v3.0.5 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.68.1 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/segmentio/asm v1.2.1 // indirect github.com/sosodev/duration v1.4.0 // indirect github.com/valyala/fastjson v1.6.10 // indirect @@ -74,46 +95,22 @@ require ( golang.org/x/crypto v0.57.0 // indirect golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect golang.org/x/net v0.58.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect - k8s.io/apiextensions-apiserver v0.36.1 // indirect - k8s.io/apiserver v0.36.1 // indirect - sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect -) - -require ( - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.13.0 // indirect - github.com/go-logr/logr v1.4.3 - github.com/go-openapi/jsonpointer v0.23.1 // indirect - github.com/go-openapi/jsonreference v0.21.6 // indirect - github.com/go-openapi/swag v0.26.0 // indirect - github.com/golang-jwt/jwt/v4 v4.5.2 - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/mattn/go-colorable v0.1.15 // indirect - github.com/mattn/go-isatty v0.0.22 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.68.1 // indirect - github.com/prometheus/procfs v0.20.1 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/term v0.46.0 // indirect golang.org/x/text v0.42.0 // indirect golang.org/x/time v0.15.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 - k8s.io/klog/v2 v2.140.0 + k8s.io/apiextensions-apiserver v0.36.1 // indirect + k8s.io/apiserver v0.36.1 // indirect k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 // indirect k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect ) diff --git a/pkg/client/client_cyberark_convertdatareadings_test.go b/pkg/client/client_cyberark_convertdatareadings_test.go index de1a598b..bdfd5042 100644 --- a/pkg/client/client_cyberark_convertdatareadings_test.go +++ b/pkg/client/client_cyberark_convertdatareadings_test.go @@ -3,6 +3,7 @@ package client import ( "crypto/ecdsa" "crypto/elliptic" + "crypto/mldsa" "crypto/rand" "crypto/x509" "crypto/x509/pkix" @@ -1477,6 +1478,16 @@ func TestIsExcludableSecret(t *testing.T) { secret: newTLSSecret("tls-secret-without-client", sampleCertificateChain(t, x509.ExtKeyUsageServerAuth)), exclude: true, }, + { + name: "TLS secret with ML-DSA client cert in tls.crt", + secret: newTLSSecret("tls-secret-with-mldsa-client", sampleMLDSACertificateChain(t, x509.ExtKeyUsageClientAuth)), + exclude: false, + }, + { + name: "TLS secret with ML-DSA non-client cert in tls.crt", + secret: newTLSSecret("tls-secret-without-mldsa-client", sampleMLDSACertificateChain(t, x509.ExtKeyUsageServerAuth)), + exclude: true, + }, { name: "Non-unstructured", secret: &corev1.Pod{ @@ -1634,3 +1645,66 @@ func sampleCertificateChain(t testing.TB, usages ...x509.ExtKeyUsage) string { return base64.StdEncoding.EncodeToString(append(clientCertPEM, caCertPEM...)) } + +// sampleMLDSACertificateChain is sampleCertificateChain with ML-DSA-65 +// (FIPS 204) keys instead of P-256 ones. +// +// isExcludableTLSSecret only reads the ExtKeyUsage extension, so it is +// algorithm-agnostic by construction and a post-quantum chain has to be +// classified exactly like the elliptic curve one. Nothing enforced that, so +// these cases pin it before the agent grows any code that does look at the +// key. crypto/mldsa exists only in Go 1.27+, so this also fails to build under +// an older toolchain, which is the requirement the go directive in go.mod +// states. +func sampleMLDSACertificateChain(t testing.TB, usages ...x509.ExtKeyUsage) string { + t.Helper() + + caPrivKey, err := mldsa.GenerateKey(mldsa.MLDSA65()) + require.NoError(t, err) + + caTemplate := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Test ML-DSA CA"}, + CommonName: "Test ML-DSA CA", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + ExtKeyUsage: []x509.ExtKeyUsage{}, + BasicConstraintsValid: true, + IsCA: true, + } + + caCertDER, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, caPrivKey.Public(), caPrivKey) + require.NoError(t, err) + + caCertPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: caCertDER, + }) + + clientPrivKey, err := mldsa.GenerateKey(mldsa.MLDSA65()) + require.NoError(t, err) + clientTemplate := x509.Certificate{ + SerialNumber: big.NewInt(2), + Subject: pkix.Name{ + Organization: []string{"Test Organization"}, + CommonName: "mldsa.example.com", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: usages, + } + + clientCertDER, err := x509.CreateCertificate(rand.Reader, &clientTemplate, &caTemplate, clientPrivKey.Public(), caPrivKey) + require.NoError(t, err) + + clientCertPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: clientCertDER, + }) + + return base64.StdEncoding.EncodeToString(append(clientCertPEM, caCertPEM...)) +} From 5453b8e5038e2fcf77b06d7b6e3ef64505d5318c Mon Sep 17 00:00:00 2001 From: Felix Phipps Date: Fri, 18 Sep 2026 14:01:23 +0100 Subject: [PATCH 2/2] =?UTF-8?q?VC-59682:=20Address=20review=20=E2=80=94=20?= =?UTF-8?q?exclusion=20rule,=20algorithm=20assertions,=20shared=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Richard's review on #841. Four points, all taken. * .golangci.yaml: move the embedlit suppression from settings.modernize.disable to exclusions.rules. golangci-lint v2.13.2 does not validate names under settings.modernize.disable — renaming embedlit to embedlitXXX there is silently accepted and the findings come back. Under exclusions.rules the same typo makes warn-unused print Skipped 0 issues by rules: [Text: "^embedlitXXX:", Linters: "modernize"] and the 46 embedlit findings resurface, so CI goes red. Verified that `golangci-lint run --fix` with the exclusion in place still leaves the tree untouched: exclusions are applied before fixes, so this keeps the property the disable list was there for. * sampleMLDSACertificateChain asserted nothing about the key, so it did not pin the claim in its own name. It now checks the parsed leaf is x509.MLDSA / x509.MLDSA65, and sampleCertificateChain checks x509.ECDSA. Note x509.MLDSA65 is a SignatureAlgorithm, not a PublicKeyAlgorithm — the PublicKeyAlgorithm constant is x509.MLDSA. Swapping the expectation to x509.ECDSA fails with `expected: 3, actual: 5`, so the assertion is live. * Both samples are now thin wrappers over certificateChain(t, newKey, usages...), parameterised on a crypto.Signer factory. *mldsa.PrivateKey and *ecdsa.PrivateKey both implement crypto.Signer, so one body serves both and the 60-line copy is gone. * Trimmed the sampleMLDSACertificateChain doc comment to Richard's wording. Test plan * make -j verify green, including verify-golangci-lint at 0 issues and generate-golangci-lint-config idempotent over two runs. * make test-unit green — 465 tests, 4 skipped. * TestIsExcludableSecret passes all 11 subtests. Co-Authored-By: Claude Opus 5 --- .golangci.yaml | 23 ++-- ...lient_cyberark_convertdatareadings_test.go | 100 ++++++------------ 2 files changed, 47 insertions(+), 76 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 419cef03..bf449906 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -5,6 +5,19 @@ linters: generated: lax presets: [comments, common-false-positives, legacy, std-error-handling] rules: + # embedlit activates at language version 1.27 and rewrites every + # Kubernetes object literal in the repo, turning + # corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "x"}} + # into + # corev1.Pod{Name: "x"} + # The flattened form hides which embedded struct a field came from, and + # mixes TypeMeta and ObjectMeta fields into one list. Keep it off here + # rather than in settings.modernize.disable, which silently ignores + # names it does not recognise. warn-unused reports this rule if it ever + # stops matching. + - linters: + - modernize + text: '^embedlit:' - linters: - errchkjson - forbidigo @@ -16,16 +29,6 @@ linters: paths: [third_party, builtin$, examples$] warn-unused: true settings: - modernize: - # embedlit activates at language version 1.27 and rewrites every - # Kubernetes object literal in the repo, turning - # corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "x"}} - # into - # corev1.Pod{Name: "x"} - # The flattened form hides which embedded struct a field came from, and - # mixes TypeMeta and ObjectMeta fields into one list. Keep it off. - disable: - - embedlit staticcheck: checks: ["all", "-ST1000", "-ST1001", "-ST1003", "-ST1005", "-ST1012", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1001", "-QF1003", "-QF1008"] enable: diff --git a/pkg/client/client_cyberark_convertdatareadings_test.go b/pkg/client/client_cyberark_convertdatareadings_test.go index bdfd5042..953373e6 100644 --- a/pkg/client/client_cyberark_convertdatareadings_test.go +++ b/pkg/client/client_cyberark_convertdatareadings_test.go @@ -1,6 +1,7 @@ package client import ( + "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/mldsa" @@ -1588,15 +1589,15 @@ func newOpaqueSecret(name string) *unstructured.Unstructured { } } -// sampleCertificateChain returns a PEM encoded sample certificate chain for testing purposes. -// The leaf certificate is signed by a self-signed CA certificate. -// Uses an elliptic curve key for the CA and leaf certificates for speed. -// The returned string is base64 encoded to match how TLS certificates -// are typically provided in Kubernetes secrets. -func sampleCertificateChain(t testing.TB, usages ...x509.ExtKeyUsage) string { +// certificateChain returns a PEM encoded sample certificate chain for testing +// purposes. The leaf certificate is signed by a self-signed CA certificate, +// both keyed by newKey. The returned string is base64 encoded to match how TLS +// certificates are typically provided in Kubernetes secrets. The parsed leaf is +// returned so that callers can assert they got the algorithm they asked for. +func certificateChain(t testing.TB, newKey func() (crypto.Signer, error), usages ...x509.ExtKeyUsage) (string, *x509.Certificate) { t.Helper() - caPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + caPrivKey, err := newKey() require.NoError(t, err) caTemplate := x509.Certificate{ @@ -1613,7 +1614,7 @@ func sampleCertificateChain(t testing.TB, usages ...x509.ExtKeyUsage) string { IsCA: true, } - caCertDER, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caPrivKey.PublicKey, caPrivKey) + caCertDER, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, caPrivKey.Public(), caPrivKey) require.NoError(t, err) caCertPEM := pem.EncodeToMemory(&pem.Block{ @@ -1621,7 +1622,7 @@ func sampleCertificateChain(t testing.TB, usages ...x509.ExtKeyUsage) string { Bytes: caCertDER, }) - clientPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + clientPrivKey, err := newKey() require.NoError(t, err) clientTemplate := x509.Certificate{ SerialNumber: big.NewInt(2), @@ -1635,7 +1636,7 @@ func sampleCertificateChain(t testing.TB, usages ...x509.ExtKeyUsage) string { ExtKeyUsage: usages, } - clientCertDER, err := x509.CreateCertificate(rand.Reader, &clientTemplate, &caTemplate, &clientPrivKey.PublicKey, caPrivKey) + clientCertDER, err := x509.CreateCertificate(rand.Reader, &clientTemplate, &caTemplate, clientPrivKey.Public(), caPrivKey) require.NoError(t, err) clientCertPEM := pem.EncodeToMemory(&pem.Block{ @@ -1643,68 +1644,35 @@ func sampleCertificateChain(t testing.TB, usages ...x509.ExtKeyUsage) string { Bytes: clientCertDER, }) - return base64.StdEncoding.EncodeToString(append(clientCertPEM, caCertPEM...)) -} - -// sampleMLDSACertificateChain is sampleCertificateChain with ML-DSA-65 -// (FIPS 204) keys instead of P-256 ones. -// -// isExcludableTLSSecret only reads the ExtKeyUsage extension, so it is -// algorithm-agnostic by construction and a post-quantum chain has to be -// classified exactly like the elliptic curve one. Nothing enforced that, so -// these cases pin it before the agent grows any code that does look at the -// key. crypto/mldsa exists only in Go 1.27+, so this also fails to build under -// an older toolchain, which is the requirement the go directive in go.mod -// states. -func sampleMLDSACertificateChain(t testing.TB, usages ...x509.ExtKeyUsage) string { - t.Helper() - - caPrivKey, err := mldsa.GenerateKey(mldsa.MLDSA65()) + leaf, err := x509.ParseCertificate(clientCertDER) require.NoError(t, err) - caTemplate := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"Test ML-DSA CA"}, - CommonName: "Test ML-DSA CA", - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(24 * time.Hour), - KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, - ExtKeyUsage: []x509.ExtKeyUsage{}, - BasicConstraintsValid: true, - IsCA: true, - } + return base64.StdEncoding.EncodeToString(append(clientCertPEM, caCertPEM...)), leaf +} - caCertDER, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, caPrivKey.Public(), caPrivKey) - require.NoError(t, err) +// sampleCertificateChain is certificateChain with P-256 keys, for speed. +func sampleCertificateChain(t testing.TB, usages ...x509.ExtKeyUsage) string { + t.Helper() - caCertPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: caCertDER, - }) + chain, leaf := certificateChain(t, func() (crypto.Signer, error) { + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + }, usages...) + require.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm) - clientPrivKey, err := mldsa.GenerateKey(mldsa.MLDSA65()) - require.NoError(t, err) - clientTemplate := x509.Certificate{ - SerialNumber: big.NewInt(2), - Subject: pkix.Name{ - Organization: []string{"Test Organization"}, - CommonName: "mldsa.example.com", - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(24 * time.Hour), - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: usages, - } + return chain +} - clientCertDER, err := x509.CreateCertificate(rand.Reader, &clientTemplate, &caTemplate, clientPrivKey.Public(), caPrivKey) - require.NoError(t, err) +// sampleMLDSACertificateChain is certificateChain with ML-DSA-65 keys. The +// crypto/mldsa import holds the Go 1.27 floor: this package will not build on +// an older toolchain. +func sampleMLDSACertificateChain(t testing.TB, usages ...x509.ExtKeyUsage) string { + t.Helper() - clientCertPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: clientCertDER, - }) + chain, leaf := certificateChain(t, func() (crypto.Signer, error) { + return mldsa.GenerateKey(mldsa.MLDSA65()) + }, usages...) + require.Equal(t, x509.MLDSA, leaf.PublicKeyAlgorithm) + require.Equal(t, x509.MLDSA65, leaf.SignatureAlgorithm) - return base64.StdEncoding.EncodeToString(append(clientCertPEM, caCertPEM...)) + return chain }