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
7 changes: 6 additions & 1 deletion cmd/signatory-tools/genkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"text/template"

"github.com/cloudflare/circl/sign/mldsa/mldsa44"
"github.com/ecadlabs/goblst/minpk"
"github.com/ecadlabs/gotez/v2/crypt"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -65,6 +66,10 @@ func NewGenKeyCommand() *cobra.Command {
var k *minpk.PrivateKey
k, err = minpk.GenerateKey(rand.Reader)
priv = (*crypt.BLSPrivateKey)(k)
case "mldsa44":
var k *mldsa44.PrivateKey
_, k, err = mldsa44.GenerateKey(rand.Reader)
priv = (*crypt.MLDSA44PrivateKey)(k)
default:
err = fmt.Errorf("unknown key type: %s", keyType)
}
Expand All @@ -88,7 +93,7 @@ func NewGenKeyCommand() *cobra.Command {
}

cmd.Flags().IntVarP(&num, "num", "n", 1, "Number of key pairs to generate")
cmd.Flags().StringVarP(&keyType, "type", "t", "ed25519", "Key type [ed25519, secp256k1, p256, bls]")
cmd.Flags().StringVarP(&keyType, "type", "t", "ed25519", "Key type [ed25519, secp256k1, p256, bls, mldsa44]")

return cmd
}
16 changes: 8 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/ecadlabs/signatory

go 1.25.0
go 1.26

require (
cloud.google.com/go/firestore v1.21.0
Expand All @@ -12,9 +12,10 @@ require (
github.com/aws/aws-sdk-go-v2/service/kms v1.50.1
github.com/aws/smithy-go v1.24.2
github.com/certusone/yubihsm-go v0.3.0
github.com/cloudflare/circl v1.6.3
github.com/ecadlabs/go-pkcs11 v0.3.0
github.com/ecadlabs/goblst v1.1.0
github.com/ecadlabs/gotez/v2 v2.4.3
github.com/ecadlabs/goblst v1.1.1
github.com/ecadlabs/gotez/v2 v2.4.7
github.com/fxamacker/cbor/v2 v2.9.0
github.com/go-playground/validator/v10 v10.30.1
github.com/google/uuid v1.6.0
Expand All @@ -28,7 +29,7 @@ require (
github.com/sirupsen/logrus v1.9.4
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.48.0
golang.org/x/crypto v0.49.0
golang.org/x/exp v0.0.0-20231127185646-65229373498e
golang.org/x/oauth2 v0.36.0
google.golang.org/api v0.270.0
Expand Down Expand Up @@ -57,7 +58,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/ecadlabs/pretty v0.0.0-20230412124801-f948fc689a04 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
Expand Down Expand Up @@ -91,7 +91,7 @@ require (
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
Expand All @@ -115,8 +115,8 @@ require (
github.com/spf13/pflag v1.0.10 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.42.0
golang.org/x/term v0.40.0
golang.org/x/text v0.34.0 // indirect
golang.org/x/term v0.41.0
golang.org/x/text v0.35.0 // indirect
google.golang.org/grpc v1.79.2
google.golang.org/protobuf v1.36.11 // indirect
)
Expand Down
24 changes: 12 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/ecadlabs/go-pkcs11 v0.3.0 h1:AsLURdNoZn0YocumJFloWXIlx1f2SDw4eTx4nPMa7II=
github.com/ecadlabs/go-pkcs11 v0.3.0/go.mod h1:PwAVBY0muwp6quQFmSDcB5Ekl4TjGG7cEQQwY9KpNVc=
github.com/ecadlabs/goblst v1.1.0 h1:U0JzRxIbtv1OsefUqfXNJ1HLzPMSaHY8aOeIQggUe9U=
github.com/ecadlabs/goblst v1.1.0/go.mod h1:s67gqaOol9o6fguh+evH75X5uQniOhv1HG/EU8xPLPY=
github.com/ecadlabs/gotez/v2 v2.4.3 h1:1541+i6qR5gD06zbnWhRqeb6/1HBL9rIWOAnmYn7MT0=
github.com/ecadlabs/gotez/v2 v2.4.3/go.mod h1:7nikOJohXAyRk0AQDzMKfa/bRrRrA4DybgUb2edwkF0=
github.com/ecadlabs/goblst v1.1.1 h1:uQbL38RDAWEaBD4IGxTpTZFwyiDe8KjUU+pmom1WSYQ=
github.com/ecadlabs/goblst v1.1.1/go.mod h1:4YORXA9M6mVf0QhwLnrjiPTnOb2xxnGSt+PuaEgqCFY=
github.com/ecadlabs/gotez/v2 v2.4.7 h1:YSvy6e69znWusXGn1kmbUcTKc+qtU8gUnPJHAnyuEMo=
github.com/ecadlabs/gotez/v2 v2.4.7/go.mod h1:dYSUbR9zMydTHlyqJNG5FpmIdzP8wOg3+nhi7ZvdVvg=
github.com/ecadlabs/pretty v0.0.0-20230412124801-f948fc689a04 h1:7WdblGykGxtGGtchW4kzTaJJO8Fm+JKhLzhttOOWr9k=
github.com/ecadlabs/pretty v0.0.0-20230412124801-f948fc689a04/go.mod h1:VApUlocsLMpp4hUXHxTTIlosebnwo0BM6e1hy78qTPM=
github.com/enceve/crypto v0.0.0-20160707101852-34d48bb93815 h1:D22EM5TeYZJp43hGDx6dUng8mvtyYbB9BnE3+BmJR1Q=
Expand Down Expand Up @@ -229,26 +229,26 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
159 changes: 151 additions & 8 deletions pkg/cryptoutils/x509/pkcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,21 @@ import (
"fmt"
"math/big"

"github.com/cloudflare/circl/sign/mldsa/mldsa44"
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
"github.com/cloudflare/circl/sign/mldsa/mldsa87"
"github.com/ecadlabs/gotez/v2/crypt"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)

var (
oidPublicKeyECDSA = encoding_asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
oidPublicKeyEd25519 = encoding_asn1.ObjectIdentifier{1, 3, 101, 112}
oidECDSA = encoding_asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
oidEd25519 = encoding_asn1.ObjectIdentifier{1, 3, 101, 112}

oidMLDSA44 = encoding_asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 17}
oidMLDSA65 = encoding_asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 18}
oidMLDSA87 = encoding_asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 19}

oidNamedCurveP224 = encoding_asn1.ObjectIdentifier{1, 3, 132, 0, 33}
oidNamedCurveP256 = encoding_asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7}
Expand Down Expand Up @@ -111,6 +118,34 @@ func parseECDSAPrivateKey(curveOid encoding_asn1.ObjectIdentifier, data []byte)
return &out, nil
}

func parseMLDSA44PrivateKey(obj cryptobyte.String, seedSize, privateSize int, newFromSeed func(seed []byte) any, newFromExpanded func(expanded []byte) any) (key any, err error) {
var inner cryptobyte.String
if obj.PeekASN1Tag(asn1.Tag(0).ContextSpecific()) && obj.ReadASN1(&inner, asn1.Tag(0).ContextSpecific()) {
if len(inner) != seedSize {
return nil, fmt.Errorf("x509: invalid MLDSA private key length: %d", len(inner))
}
return newFromSeed(inner), nil
}
if obj.PeekASN1Tag(asn1.OCTET_STRING) &&
obj.ReadASN1(&inner, asn1.OCTET_STRING) {
// expanded
if len(inner) != privateSize {
return nil, fmt.Errorf("x509: invalid MLDSA private key length: %d", len(inner))
}
return newFromExpanded(inner), nil
}
var seed cryptobyte.String
if obj.PeekASN1Tag(asn1.SEQUENCE) &&
obj.ReadASN1(&inner, asn1.SEQUENCE) &&
inner.ReadASN1(&seed, asn1.OCTET_STRING) {
if len(seed) != seedSize {
return nil, fmt.Errorf("x509: invalid MLDSA private key length: %d", len(inner))
}
return newFromSeed(seed), nil
}
return nil, errors.New("x509: invalid MLDSA44 private key format")
}

func ParsePKCS8PrivateKey(der []byte) (key any, err error) {
src := cryptobyte.String(der)
var (
Expand All @@ -128,7 +163,7 @@ func ParsePKCS8PrivateKey(der []byte) (key any, err error) {
}

switch {
case algoOid.Equal(oidPublicKeyECDSA):
case algoOid.Equal(oidECDSA):
var curve encoding_asn1.ObjectIdentifier
if algo.PeekASN1Tag(asn1.OBJECT_IDENTIFIER) {
if !algo.ReadASN1ObjectIdentifier(&curve) {
Expand All @@ -137,7 +172,7 @@ func ParsePKCS8PrivateKey(der []byte) (key any, err error) {
}
return parseECDSAPrivateKey(curve, keyData)

case algoOid.Equal(oidPublicKeyEd25519):
case algoOid.Equal(oidEd25519):
der := cryptobyte.String(keyData)
var priv cryptobyte.String
if !der.ReadASN1(&priv, asn1.OCTET_STRING) {
Expand All @@ -148,6 +183,53 @@ func ParsePKCS8PrivateKey(der []byte) (key any, err error) {
}
return ed25519.NewKeyFromSeed(priv), nil

case algoOid.Equal(oidMLDSA44):
return parseMLDSA44PrivateKey(keyData, mldsa44.SeedSize, mldsa44.PrivateKeySize, func(seed []byte) any {
var tmp [mldsa44.SeedSize]byte
copy(tmp[:], seed)
_, priv := mldsa44.NewKeyFromSeed(&tmp)
return priv
}, func(expanded []byte) any {
var (
tmp [mldsa44.PrivateKeySize]byte
priv mldsa44.PrivateKey
)
copy(tmp[:], expanded)
priv.Unpack(&tmp)
return &priv
})

case algoOid.Equal(oidMLDSA65):
return parseMLDSA44PrivateKey(keyData, mldsa65.SeedSize, mldsa65.PrivateKeySize, func(seed []byte) any {
var tmp [mldsa65.SeedSize]byte
copy(tmp[:], seed)
_, priv := mldsa65.NewKeyFromSeed(&tmp)
return priv
}, func(expanded []byte) any {
var (
tmp [mldsa65.PrivateKeySize]byte
priv mldsa65.PrivateKey
)
copy(tmp[:], expanded)
priv.Unpack(&tmp)
return &priv
})

case algoOid.Equal(oidMLDSA87):
return parseMLDSA44PrivateKey(keyData, mldsa87.SeedSize, mldsa87.PrivateKeySize, func(seed []byte) any {
var tmp [mldsa87.SeedSize]byte
copy(tmp[:], seed)
_, priv := mldsa87.NewKeyFromSeed(&tmp)
return priv
}, func(expanded []byte) any {
var (
tmp [mldsa87.PrivateKeySize]byte
priv mldsa87.PrivateKey
)
copy(tmp[:], expanded)
priv.Unpack(&tmp)
return &priv
})
default:
return nil, fmt.Errorf("x509: unsupported algorithm: %v", algo)
}
Expand All @@ -170,7 +252,7 @@ func ParsePKIXPublicKey(der []byte) (pub any, err error) {

keyBytes := keyData.RightAlign()
switch {
case algoOid.Equal(oidPublicKeyECDSA):
case algoOid.Equal(oidECDSA):
var curveOid encoding_asn1.ObjectIdentifier
if algo.PeekASN1Tag(asn1.OBJECT_IDENTIFIER) {
if !algo.ReadASN1ObjectIdentifier(&curveOid) {
Expand All @@ -191,17 +273,66 @@ func ParsePKIXPublicKey(der []byte) (pub any, err error) {
Y: y,
}, nil

case algoOid.Equal(oidPublicKeyEd25519):
case algoOid.Equal(oidEd25519):
if len(keyBytes) != ed25519.PublicKeySize {
return nil, fmt.Errorf("x509: invalid Ed25519 public key length: %d", len(keyBytes))
}
return ed25519.PublicKey(keyBytes), nil

case algoOid.Equal(oidMLDSA44):
if len(keyBytes) != mldsa44.PublicKeySize {
return nil, fmt.Errorf("x509: invalid MLDSA44 public key length: %d", len(keyBytes))
}
var (
out mldsa44.PublicKey
buf [mldsa44.PublicKeySize]byte
)
copy(buf[:], keyBytes)
out.Unpack(&buf)
return &out, nil

case algoOid.Equal(oidMLDSA65):
if len(keyBytes) != mldsa65.PublicKeySize {
return nil, fmt.Errorf("x509: invalid MLDSA65 public key length: %d", len(keyBytes))
}
var (
out mldsa65.PublicKey
buf [mldsa65.PublicKeySize]byte
)
copy(buf[:], keyBytes)
out.Unpack(&buf)
return &out, nil

case algoOid.Equal(oidMLDSA87):
if len(keyBytes) != mldsa87.PublicKeySize {
return nil, fmt.Errorf("x509: invalid MLDSA87 public key length: %d", len(keyBytes))
}
var (
out mldsa87.PublicKey
buf [mldsa87.PublicKeySize]byte
)
copy(buf[:], keyBytes)
out.Unpack(&buf)
return &out, nil

default:
return nil, fmt.Errorf("x509: unsupported algorithm: %v", algo)
}
}

type mldsaPrivateKey interface {
Bytes() []byte
Seed() []byte
}

func marshalMLDSAPrivateKey(key mldsaPrivateKey, b *cryptobyte.Builder) {
if seed := key.Seed(); seed != nil {
b.AddASN1(asn1.Tag(0).ContextSpecific(), func(child *cryptobyte.Builder) { child.AddBytes(seed) })
} else {
b.AddASN1OctetString(key.Bytes())
}
}

func MarshalPKCS8PrivateKey(key any) (res []byte, err error) {
var (
b cryptobyte.Builder
Expand All @@ -212,15 +343,24 @@ func MarshalPKCS8PrivateKey(key any) (res []byte, err error) {
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
switch k := key.(type) {
case *ecdsa.PrivateKey:
b.AddASN1ObjectIdentifier(oidPublicKeyECDSA)
b.AddASN1ObjectIdentifier(oidECDSA)
if curveOid = oidFromNamedCurve(k.Curve); curveOid == nil {
b.SetError(fmt.Errorf("x509: unknown curve: %T", k.Curve))
return
}
b.AddASN1ObjectIdentifier(curveOid)

case ed25519.PrivateKey:
b.AddASN1ObjectIdentifier(oidPublicKeyEd25519)
b.AddASN1ObjectIdentifier(oidEd25519)

case *mldsa44.PrivateKey:
b.AddASN1ObjectIdentifier(oidMLDSA44)

case *mldsa65.PrivateKey:
b.AddASN1ObjectIdentifier(oidMLDSA65)

case *mldsa87.PrivateKey:
b.AddASN1ObjectIdentifier(oidMLDSA87)

default:
b.SetError(fmt.Errorf("x509: unsupported private key type %T", k))
Expand All @@ -246,6 +386,9 @@ func MarshalPKCS8PrivateKey(key any) (res []byte, err error) {
case ed25519.PrivateKey:
keyData.AddASN1OctetString(k.Seed())

case *mldsa44.PrivateKey, *mldsa65.PrivateKey, *mldsa87.PrivateKey:
marshalMLDSAPrivateKey(k.(mldsaPrivateKey), &keyData)

default:
b.SetError(fmt.Errorf("x509: unsupported private key type %T", k))
return
Expand Down
Loading
Loading