diff --git a/cmd/signatory-tools/genkey.go b/cmd/signatory-tools/genkey.go index 5b3b8cc6..b516a61b 100644 --- a/cmd/signatory-tools/genkey.go +++ b/cmd/signatory-tools/genkey.go @@ -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" @@ -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) } @@ -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 } diff --git a/go.mod b/go.mod index d6de6621..5b8a47ca 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 ) diff --git a/go.sum b/go.sum index 43f320b2..5752a6da 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -229,8 +229,8 @@ 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= @@ -238,17 +238,17 @@ 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= diff --git a/pkg/cryptoutils/x509/pkcs.go b/pkg/cryptoutils/x509/pkcs.go index 612de097..291a123d 100644 --- a/pkg/cryptoutils/x509/pkcs.go +++ b/pkg/cryptoutils/x509/pkcs.go @@ -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} @@ -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 ( @@ -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) { @@ -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) { @@ -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) } @@ -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) { @@ -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 @@ -212,7 +343,7 @@ 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 @@ -220,7 +351,16 @@ func MarshalPKCS8PrivateKey(key any) (res []byte, err error) { 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)) @@ -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 diff --git a/pkg/cryptoutils/x509/pkcs8_test.go b/pkg/cryptoutils/x509/pkcs8_test.go index 50d0515f..d11ebd42 100644 --- a/pkg/cryptoutils/x509/pkcs8_test.go +++ b/pkg/cryptoutils/x509/pkcs8_test.go @@ -14,7 +14,7 @@ type testData struct { pubKey string } -var tests = []testData{ +var roundtripTests = []testData{ { name: "Ed25519", privKey: `-----BEGIN PRIVATE KEY----- @@ -56,6 +56,176 @@ OCOWhQ199QDNidz8Q2stpm2rYgguVNJLUs3PjPfaon0x9AOuzhaDHNK8zQ== }, } +type parseTestData struct { + name string + pem string +} + +var parsePrivTests = []parseTestData{ + { + name: "MLDSA44 Seed", + pem: `-----BEGIN PRIVATE KEY----- +MDQCAQAwCwYJYIZIAWUDBAMRBCKAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ +GhscHR4f +-----END PRIVATE KEY-----`, + }, + { + name: "MLDSA44 Expanded", + pem: `-----BEGIN PRIVATE KEY----- +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 +-----END PRIVATE KEY-----`, + }, + { + name: "MLDSA44 Both", + pem: `-----BEGIN PRIVATE KEY----- +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 +-----END PRIVATE KEY-----`, + }, +} + +var parsePubTests = []parseTestData{ + { + name: "MLDSA44 Public", + pem: `-----BEGIN PUBLIC KEY----- +MIIFMjALBglghkgBZQMEAxEDggUhANeytHJUquDbReeTDUqY0sl9jxOX0Xidr6Fw +JLMW6b7JT8mUbULxm3mnQTu6oz5xSctC7VEVaTrAQfrLmIretf4OHYYxGEmVtZLD +l9IpTi4U+QqkFLo4JomaxD9MzKy8JumoMrlRGNXLQzy++WYLABOOCBf2HnYsonTD +atVU6yKqwRYuSrAay6HjjE79j4C2WzM9D3LlXf5xzpweu5iJ58VhBsD9c4A6Kuz+ +r97XqjyyztpU0SvYzTanjPl1lDtHq9JeiArEUuV0LtHo0agq+oblkMdYwVrk0oQN +kryhpQkPQElll/yn2LlRPxob2m6VCqqY3kZ1B9Sk9aTwWZIWWCw1cvYu2okFqzWB +ZwxKAnd6M+DKcpX9j0/20aCjp2g9ZfX19/xg2gI+gmxfkhRMAvfRuhB1mHVT6pNn +/NdtmQt/qZzUWv24g21D5Fn1GH3wWEeXCaAepoNZNfpwRgmQzT3BukAbqUurHd5B +rGerMxncrKBgSNTE7vJ+4TqcF9BTj0MPLWQtwkFWYN54h32NirxyUjl4wELkKF9D +GYRsRBJiQpdoRMEOVWuiFbWnGeWdDGsqltOYWQcf3MLN51JKe+2uVOhbMY6FTo/i +svPt+slxkSgnCq/R5QRMOk/a/Z/zH5B4S46ORZYUSg2vWGUR09mWK56pWvGXtOX8 +YPKx7RXeOlvvX4m9x52RBR2bKBbnT6VFMe/cHL501EiFf0drzVjyHAtlOzt2pOB2 +plWaMCcYVVzGP3SFmqurkl8COGHKjND3utsocfZ9VTJtdFETWtRfShumkRj7ssij +DuyTku8/l3Bmya3VxxDMZHsVFNIX2VjHAXw+kP0gwE5nS5BIbpNwoxoAHTL0c5ee +SQZ0nn5Hf6C3RQj4pfI3gxK4PCW9OIygsP/3R4uvQrcWZ+2qyXxGsSlkPlhuWwVa +DCEZRtTzbmdb7Vhg+gQqMV2YJhZNapI3w1pfv0lUkKW9TfJIuVxKrneEtgVnMWas +QkW1tLCCoJ6TI+YvIHjFt2eDRG3v1zatOjcC1JsImESQCmGDM5e8RBmzDXqXoLOH +wZEUdMTUG1PjKpd6y28Op122W7OeWecB52lX3vby1EVZwxp3EitSBOO1whnxaIsU +7QvAuAGz5ugtzUPpwOn0F0TNmBW9G8iCDYuxI/BPrNGxtoXdWisbjbvz7ZM2cPCV +oYC08ZLQixC4+rvfzCskUY4y7qCl4MkEyoRHgAg/OwzS0Li2r2e8NVuUlAJdx7Cn +j6gOOi2/61EyiFHWB4GY6Uk2Ua54fsAlH5Irow6fUd9iptcnhM890gU5MXbfoySl +Er2Ulwo23TSlFKhnkfDrNvAUWwmrZGUbSgMTsplhGiocSIkWJ1mHaKMRQGC6RENI +bfUVIqHOiLMJhcIW+ObtF43VZ7MEoNTK+6iCooNC8XqaomrljbYwCD0sNY/fVmw/ +XWKkKFZ7yeqM6VyqDzVHSwv6jzOaJQq0388gg76O77wQVeGP4VNw7ssmBWbYP/Br +IRquxDyim1TM0A+IFaJGXvC0ZRXMfkHzEk8J7/9zkwmrWLKaFFmgC85QOOk4yWeP +cusOTuX9quZtn4Vz/Jf8QrSVn0v4th14Qz6GsDNdbpGRxNi/SHs5BcEIz9asJLDO +t9y3z1H4TQ7Wh7lerrHFM8BvDZcCPZKnCCWDe1m6bLfU5WsKh8IDhiro8xW6WSXo +7e+meTaaIgJ2YVHxapZfn4Hs52zAcLVYaeTbl4TPBcgwsyQsgxI= +-----END PUBLIC KEY-----`, + }, +} + type Pub interface { Equal(x crypto.PublicKey) bool } @@ -65,8 +235,8 @@ type Priv interface { Equal(x crypto.PrivateKey) bool } -func TestParse(t *testing.T) { - for _, test := range tests { +func TestRoundtrip(t *testing.T) { + for _, test := range roundtripTests { t.Run(test.name, func(t *testing.T) { privBlock, _ := pem.Decode([]byte(test.privKey)) require.NotNil(t, privBlock) @@ -90,3 +260,31 @@ func TestParse(t *testing.T) { }) } } + +func TestParsePriv(t *testing.T) { + for _, test := range parsePrivTests { + t.Run(test.name, func(t *testing.T) { + privBlock, _ := pem.Decode([]byte(test.pem)) + require.NotNil(t, privBlock) + priv, err := ParsePKCS8PrivateKey(privBlock.Bytes) + require.NoError(t, err) + + privBytes, err := MarshalPKCS8PrivateKey(priv) + require.NoError(t, err) + priv2, err := ParsePKCS8PrivateKey(privBytes) + require.NoError(t, err) + require.True(t, priv2.(Priv).Equal(priv)) + }) + } +} + +func TestParsePub(t *testing.T) { + for _, test := range parsePubTests { + t.Run(test.name, func(t *testing.T) { + pubBlock, _ := pem.Decode([]byte(test.pem)) + require.NotNil(t, pubBlock) + _, err := ParsePKIXPublicKey(pubBlock.Bytes) + require.NoError(t, err) + }) + } +} diff --git a/pkg/signatory/signatory.go b/pkg/signatory/signatory.go index 3d15f792..020e8306 100644 --- a/pkg/signatory/signatory.go +++ b/pkg/signatory/signatory.go @@ -159,6 +159,19 @@ func strInSlice(slice []string, s string) bool { return false } +func validateOperations(req *SignRequest, msg core.SignRequest) error { + if ops, ok := getOperations(req, msg); ok { + for _, op := range ops { + if validator, ok := op.(core.Validator); ok { + if err := validator.Validate(); err != nil { + return err + } + } + } + } + return nil +} + func matchFilter(policy *PublicKeyPolicy, req *SignRequest, msg core.SignRequest) error { if policy.AllowedChains != nil { if m, ok := msg.(request.WithWatermark); ok { @@ -371,8 +384,11 @@ func (s *Signatory) Sign(ctx context.Context, req *SignRequest) (crypt.Signature l.Error(err) return nil, err } - l = l.WithField(logVault, p.key.Vault().Name()) + if err = validateOperations(req, msg); err != nil { + l.Error(err) + return nil, errors.Wrap(err, http.StatusBadRequest) + } if err = matchFilter(policy, req, msg); err != nil { l.Error(err) return nil, errors.Wrap(err, http.StatusForbidden) diff --git a/pkg/signatory/signatory_test.go b/pkg/signatory/signatory_test.go index b101e4c7..fd2d84b8 100644 --- a/pkg/signatory/signatory_test.go +++ b/pkg/signatory/signatory_test.go @@ -16,6 +16,7 @@ import ( "github.com/ecadlabs/gotez/v2/protocol/core" "github.com/ecadlabs/gotez/v2/protocol/core/expression" "github.com/ecadlabs/gotez/v2/protocol/latest" + "github.com/ecadlabs/gotez/v2/protocol/proto_023_PtSeouLo" "github.com/ecadlabs/signatory/pkg/config" "github.com/ecadlabs/signatory/pkg/hashmap" "github.com/ecadlabs/signatory/pkg/signatory" @@ -405,6 +406,31 @@ func TestPolicy(t *testing.T) { }, expected: "operation `ballot:yay' is not allowed", }, + { + title: "Non BLS companion keys are not allowed", + req: &latest.GenericOperationSignRequest{ + Branch: &tz.BlockHash{}, + Contents: []latest.GenericOperationSignRequestOperationContents{ + &latest.UpdateCompanionKey{ + ManagerOperation: latest.ManagerOperation{ + Source: &tz.Ed25519PublicKeyHash{1, 2, 3}, + Fee: tz.BigUint{0}, + Counter: tz.BigUint{0}, + GasLimit: tz.BigUint{0}, + StorageLimit: tz.BigUint{0}, + }, + PublicKey: &tz.Ed25519PublicKey{1, 2, 3}, + Proof: tz.None[proto_023_PtSeouLo.Proof](), + }, + }, + }, + policy: signatory.PublicKeyPolicy{ + AllowedRequests: []string{}, + AllowedOps: []string{}, + LogPayloads: true, + }, + expected: "companion key is not a BLS key", + }, } priv, err := crypt.ParsePrivateKey([]byte(privateKey))