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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.24.1
require (
cloud.google.com/go/compute/metadata v0.7.0
cloud.google.com/go/storage v1.54.0
github.com/cloudflare/circl v1.6.1
github.com/fsnotify/fsnotify v1.9.0
github.com/golangci/golangci-lint v1.64.8
github.com/google/addlicense v1.1.1
Expand Down Expand Up @@ -164,7 +165,6 @@ require (
github.com/ckaznocha/intrange v0.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudevents/sdk-go/v2 v2.15.2 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,8 @@ github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc=
github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6tjmaQBSvxcv4uE=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
Expand Down
141 changes: 141 additions & 0 deletions pkg/chains/signing/mldsa/mldsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
Copyright 2024 The Tekton 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 mldsa

import (
"crypto"
"errors"
"io"

"github.com/cloudflare/circl/sign/mldsa/mldsa65"
"github.com/sigstore/sigstore/pkg/signature"
)

// SignerVerifier implements signature.SignerVerifier and crypto.Signer for MLDSA
type SignerVerifier struct {
priv *mldsa65.PrivateKey
pub *mldsa65.PublicKey
}

// LoadSignerVerifier creates a new SignerVerifier from a private key
func LoadSignerVerifier(priv *mldsa65.PrivateKey) (*SignerVerifier, error) {
if priv == nil {
return nil, errors.New("private key cannot be nil")
}

// Get the public key from the private key
pub := priv.Public().(*mldsa65.PublicKey)

return &SignerVerifier{
priv: priv,
pub: pub,
}, nil
}

// LoadVerifier creates a new SignerVerifier from a public key
func LoadVerifier(pub *mldsa65.PublicKey) (*SignerVerifier, error) {
if pub == nil {
return nil, errors.New("public key cannot be nil")
}

return &SignerVerifier{
pub: pub,
}, nil
}

// Public implements crypto.Signer interface
func (s *SignerVerifier) Public() crypto.PublicKey {
return s.pub
}

// Sign signs the given data
func (s *SignerVerifier) Sign(data []byte) ([]byte, error) {
if s.priv == nil {
return nil, errors.New("private key not available for signing")
}

sig := make([]byte, mldsa65.SignatureSize)
err := mldsa65.SignTo(s.priv, data, nil, false, sig)
if err != nil {
return nil, err
}
return sig, nil
}

// SignWithOpts implements crypto.Signer interface
func (s *SignerVerifier) SignWithOpts(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {

Check failure on line 77 in pkg/chains/signing/mldsa/mldsa.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 'opts' seems to be unused, consider removing or renaming it as _ (revive)

Check failure on line 77 in pkg/chains/signing/mldsa/mldsa.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 'rand' seems to be unused, consider removing or renaming it as _ (revive)
// MLDSA doesn't use pre-hashing, so we use the input directly
return s.Sign(digest)
}

// SignMessage signs a message from a reader
func (s *SignerVerifier) SignMessage(message io.Reader, opts ...signature.SignOption) ([]byte, error) {

Check failure on line 83 in pkg/chains/signing/mldsa/mldsa.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 'opts' seems to be unused, consider removing or renaming it as _ (revive)
data, err := io.ReadAll(message)
if err != nil {
return nil, err
}

return s.Sign(data)
}

// Verify verifies the signature against the data
func (s *SignerVerifier) Verify(data, sig []byte) error {
if s.pub == nil {
return errors.New("public key not available for verification")
}

if len(sig) != mldsa65.SignatureSize {
return errors.New("invalid signature size")
}

if !mldsa65.Verify(s.pub, data, nil, sig) {
return errors.New("invalid signature")
}

return nil
}

// VerifySignature verifies a signature from readers
func (s *SignerVerifier) VerifySignature(signature, message io.Reader, opts ...signature.VerifyOption) error {

Check failure on line 110 in pkg/chains/signing/mldsa/mldsa.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 'opts' seems to be unused, consider removing or renaming it as _ (revive)
sig, err := io.ReadAll(signature)
if err != nil {
return err
}

data, err := io.ReadAll(message)
if err != nil {
return err
}

return s.Verify(data, sig)
}

// PublicKey returns the public key with optional parameters
func (s *SignerVerifier) PublicKey(opts ...signature.PublicKeyOption) (crypto.PublicKey, error) {

Check failure on line 125 in pkg/chains/signing/mldsa/mldsa.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 'opts' seems to be unused, consider removing or renaming it as _ (revive)
return s.pub, nil
}

// Type returns the key type for SSH and other uses
func (s *SignerVerifier) Type() string {
return "mldsa65-sha256"
}

// CreateKey generates a new key pair
func (s *SignerVerifier) CreateKey(rand io.Reader) (crypto.PublicKey, crypto.PrivateKey, error) {
pub, priv, err := mldsa65.GenerateKey(rand)
if err != nil {
return nil, nil, err
}
return pub, priv, nil
}
32 changes: 26 additions & 6 deletions pkg/chains/signing/wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ import (
"bytes"
"context"
"crypto"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"

"github.com/cloudflare/circl/sign/mldsa/mldsa65"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
"github.com/sigstore/sigstore/pkg/signature"
Expand All @@ -34,23 +37,40 @@ func Wrap(s Signer) (Signer, error) {
return nil, err
}

// Generate public key fingerprint
sshpk, err := ssh.NewPublicKey(pub)
if err != nil {
return nil, err
var fingerprint string
var pk crypto.PublicKey

// Handle MLDSA keys differently
if mldsaPub, ok := pub.(*mldsa65.PublicKey); ok {
// Generate fingerprint from MLDSA public key bytes
pkBytes, err := mldsaPub.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal MLDSA public key: %w", err)
}
hash := sha256.Sum256(pkBytes)
fingerprint = "SHA256:" + base64.StdEncoding.EncodeToString(hash[:])
pk = pub
} else {
// For other key types, use SSH public key
sshpk, err := ssh.NewPublicKey(pub)
if err != nil {
return nil, err
}
fingerprint = ssh.FingerprintSHA256(sshpk)
pk = sshpk
}
fingerprint := ssh.FingerprintSHA256(sshpk)

adapter := sslAdapter{
wrapped: s,
keyID: fingerprint,
pk: sshpk,
pk: pk,
}

envelope, err := dsse.NewEnvelopeSigner(&adapter)
if err != nil {
return nil, err
}

return &sslSigner{
wrapper: envelope,
typ: s.Type(),
Expand Down
96 changes: 86 additions & 10 deletions pkg/chains/signing/x509/x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"crypto"
"crypto/ecdsa"
cx509 "crypto/x509"
"encoding/asn1"
"encoding/json"
"encoding/pem"
"fmt"
Expand All @@ -34,16 +35,32 @@
"github.com/sigstore/cosign/v2/pkg/providers"
"knative.dev/pkg/logging"

"github.com/cloudflare/circl/sign/mldsa/mldsa65"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/tuf"
"github.com/tektoncd/chains/pkg/chains/signing"
"github.com/tektoncd/chains/pkg/chains/signing/mldsa"
"github.com/tektoncd/chains/pkg/config"
)

const (
defaultOIDCClientID = "sigstore"
)

// MLDSA65 OID: 2.16.840.1.101.3.4.3.18
var mldsaOID = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 18}

Check failure on line 51 in pkg/chains/signing/x509/x509.go

View workflow job for this annotation

GitHub Actions / lint

var `mldsaOID` is unused (unused)

type pkcs8 struct {

Check failure on line 53 in pkg/chains/signing/x509/x509.go

View workflow job for this annotation

GitHub Actions / lint

type `pkcs8` is unused (unused)
Version int
Algorithm pkcs8Algorithm
PrivateKey []byte
}

type pkcs8Algorithm struct {

Check failure on line 59 in pkg/chains/signing/x509/x509.go

View workflow job for this annotation

GitHub Actions / lint

type `pkcs8Algorithm` is unused (unused)
Algorithm asn1.ObjectIdentifier
Parameters asn1.RawValue `asn1:"optional"`
}

// Signer exposes methods to sign payloads.
type Signer struct {
cert string
Expand Down Expand Up @@ -175,23 +192,82 @@
return io.ReadAll(resp.Body)
}

func extractMLDSAFromPKCS8(der []byte) (*mldsa65.PrivateKey, error) {
// PKCS#8 structure typically has the raw key at the end
// For MLDSA65, we need exactly mldsa65.PrivateKeySize bytes

if len(der) < mldsa65.PrivateKeySize {
return nil, fmt.Errorf("PKCS#8 data too short: %d bytes, need at least %d",
len(der), mldsa65.PrivateKeySize)
}

// Strategy 1: Try the last PrivateKeySize bytes (most common case)
if len(der) >= mldsa65.PrivateKeySize {
rawKey := der[len(der)-mldsa65.PrivateKeySize:]
var mldsaKey mldsa65.PrivateKey
if err := mldsaKey.UnmarshalBinary(rawKey); err == nil {
return &mldsaKey, nil
}
}
return nil, fmt.Errorf("no valid MLDSA key found in PKCS#8 data")
}

func tryLoadMLDSA(data []byte) (*mldsa65.PrivateKey, error) {
// First try direct raw format
var mldsaKey mldsa65.PrivateKey
if err := mldsaKey.UnmarshalBinary(data); err == nil {
return &mldsaKey, nil
}

// Then try to extract from PKCS#8
if key, err := extractMLDSAFromPKCS8(data); err == nil {
return key, nil
}

return nil, fmt.Errorf("data is neither raw MLDSA key nor PKCS#8 wrapped MLDSA key")
}

func x509Signer(ctx context.Context, privateKey []byte) (*Signer, error) {
logger := logging.FromContext(ctx)
logger.Info("Found x509 key...")

p, _ := pem.Decode(privateKey)
if p.Type != "PRIVATE KEY" {
return nil, fmt.Errorf("expected private key, found object of type %s", p.Type)
}
pk, err := cx509.ParsePKCS8PrivateKey(p.Bytes)
if err != nil {
return nil, err
if p == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
signer, err := signature.LoadECDSASignerVerifier(pk.(*ecdsa.PrivateKey), crypto.SHA256)
if err != nil {
return nil, err

logger.Infof("Attempting to parse private key of type: %s", p.Type)

switch p.Type {
case "PRIVATE KEY":
// Try PKCS#8 first for ECDSA keys
if pk, err := cx509.ParsePKCS8PrivateKey(p.Bytes); err == nil {
if ecKey, ok := pk.(*ecdsa.PrivateKey); ok {
logger.Info("Using ECDSA private key...")
signer, err := signature.LoadECDSASignerVerifier(ecKey, crypto.SHA256)
if err != nil {
return nil, fmt.Errorf("failed to load ECDSA signer: %w", err)
}
return &Signer{SignerVerifier: signer}, nil
}
}

// Try MLDSA formats
if mldsaKey, err := tryLoadMLDSA(p.Bytes); err == nil {
logger.Info("Using MLDSA private key...")
signer, err := mldsa.LoadSignerVerifier(mldsaKey)
if err != nil {
return nil, fmt.Errorf("failed to load MLDSA signer: %w", err)
}
return &Signer{SignerVerifier: signer}, nil
} else {

Check failure on line 263 in pkg/chains/signing/x509/x509.go

View workflow job for this annotation

GitHub Actions / lint

indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)
logger.Infof("Failed to load MLDSA key: %v", err)
}

return nil, fmt.Errorf("unsupported private key format - key could not be parsed as PKCS#8 ECDSA or MLDSA")
default:
return nil, fmt.Errorf("expected private key, found object of type %s", p.Type)
}
return &Signer{SignerVerifier: signer}, nil
}

func cosignSigner(ctx context.Context, secretPath string, privateKey []byte) (*Signer, error) {
Expand Down
3 changes: 2 additions & 1 deletion vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.s

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.s

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading