Skip to content
Draft
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
39 changes: 39 additions & 0 deletions internal/mldsa/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package mldsa

import (
"crypto/sha3"
"errors"
)

var (
errContextTooLong = errors.New("mldsa: context too long")
)

func PublicKeyHash(pub *PublicKey) [64]byte {
H := sha3.NewSHAKE256()
H.Write(pub.Bytes())
var tr [64]byte
H.Read(tr[:])
return tr
}

func MessageHash(pub *PublicKey, msg []byte, opts *Options) ([64]byte, error) {
if opts == nil {
opts = &Options{}
}

tr := PublicKeyHash(pub)
if len(opts.Context) > 255 {
return [64]byte{}, errContextTooLong
}

H := sha3.NewSHAKE256()
H.Write(tr[:])
H.Write([]byte{0}) // ML-DSA / HashML-DSA domain separator
H.Write([]byte{byte(len(opts.Context))})
H.Write([]byte(opts.Context))
H.Write(msg)
var μ [64]byte
H.Read(μ[:])
return μ, nil
}
27 changes: 27 additions & 0 deletions internal/mldsa/mldsa_go127.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//go:build go1.27

package mldsa

import (
"crypto/mldsa"
)

// Supported reports whether ML-DSA is available in the current build. It is
// true when compiled with Go 1.27 or later.
const Supported = true

type Options = mldsa.Options

type Parameters = mldsa.Parameters

type PrivateKey = mldsa.PrivateKey

type PublicKey = mldsa.PublicKey

var (
GenerateKey = mldsa.GenerateKey
Verify = mldsa.Verify
MLDSA44 = mldsa.MLDSA44
MLDSA65 = mldsa.MLDSA65
MLDSA87 = mldsa.MLDSA87
)
81 changes: 81 additions & 0 deletions internal/mldsa/mldsa_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//go:build !go1.27

package mldsa

import (
"crypto"
"errors"
"io"
)

var errNotSupported = errors.New("mldsa is not supported")

// Supported reports whether ML-DSA is available in the current build. It is
// false when compiled with a Go toolchain older than 1.27.
const Supported = false

type Parameters struct{}

func MLDSA44() Parameters {
return Parameters{}
}

func MLDSA65() Parameters {
return Parameters{}
}

func MLDSA87() Parameters {
return Parameters{}
}

type Options struct {
Context string
}

func (o *Options) HashFunc() crypto.Hash {
return 0
}

type PrivateKey struct{}

func (sk *PrivateKey) Bytes() []byte {
return nil
}

func (sk *PrivateKey) Equal(x crypto.PrivateKey) bool {
return false
}

func (sk *PrivateKey) Public() crypto.PublicKey {
return (*PublicKey)(nil)
}

func (sk *PrivateKey) PublicKey() *PublicKey {
return (*PublicKey)(nil)
}

func (sk *PrivateKey) Sign(_ io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
return nil, errNotSupported
}

type PublicKey struct{}

func (pk *PublicKey) Bytes() []byte {
return nil
}

func (pk *PublicKey) Equal(x crypto.PublicKey) bool {
return false
}

func (pk *PublicKey) Parameters() Parameters {
return Parameters{}
}

func GenerateKey(params Parameters) (*PrivateKey, error) {
return nil, errNotSupported
}

func Verify(pk *PublicKey, message, signature []byte, opts *Options) error {
return errNotSupported
}
87 changes: 51 additions & 36 deletions keyutil/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"fmt"
"math/big"
"sync/atomic"

"github.com/pkg/errors"
"golang.org/x/crypto/ssh"

"go.step.sm/crypto/internal/mldsa"
"go.step.sm/crypto/x25519"
)

Expand All @@ -26,6 +28,8 @@ var (
DefaultKeySize = 2048
// DefaultKeyCurve is the default curve of a private key.
DefaultKeyCurve = "P-256"
// DefaultKeyAlgorithm is the default algorithm for AKP (ML-DSA) keys.
DefaultKeyAlgorithm = mldsa.MLDSA44
// DefaultSignatureAlgorithm is the default signature algorithm used on a
// certificate with the default key type.
DefaultSignatureAlgorithm = x509.ECDSAWithSHA256
Expand Down Expand Up @@ -55,15 +59,7 @@ func Insecure() (revert func()) {
// PublicKey extracts a public key from a private key.
func PublicKey(priv interface{}) (crypto.PublicKey, error) {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey, nil
case *ecdsa.PrivateKey:
return &k.PublicKey, nil
case ed25519.PrivateKey:
return k.Public(), nil
case x25519.PrivateKey:
return k.Public(), nil
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey, x25519.PublicKey:
case *rsa.PublicKey, *ecdsa.PublicKey, *mldsa.PublicKey, ed25519.PublicKey, x25519.PublicKey:
return k, nil
case crypto.Signer:
return k.Public(), nil
Expand All @@ -87,7 +83,7 @@ func GenerateDefaultKeyPair() (crypto.PublicKey, crypto.PrivateKey, error) {
// GenerateKey generates a key of the given type (kty).
func GenerateKey(kty, crv string, size int) (crypto.PrivateKey, error) {
switch kty {
case "EC", "RSA", "OKP":
case "EC", "RSA", "OKP", "AKP":
return GenerateSigner(kty, crv, size)
case "oct":
return generateOctKey(size)
Expand All @@ -113,7 +109,8 @@ func GenerateDefaultSigner() (crypto.Signer, error) {
}

// GenerateSigner creates an asymmetric crypto key that implements
// crypto.Signer.
// crypto.Signer. For ML-DSA keys, the crv parameters indicates the algorithm to
// use.
func GenerateSigner(kty, crv string, size int) (crypto.Signer, error) {
switch kty {
case "EC":
Expand All @@ -122,6 +119,8 @@ func GenerateSigner(kty, crv string, size int) (crypto.Signer, error) {
return generateRSAKey(size)
case "OKP":
return generateOKPKey(crv)
case "AKP":
return generateAKPKey(crv)
default:
return nil, errors.Errorf("unrecognized key type: %s", kty)
}
Expand All @@ -133,6 +132,7 @@ func ExtractKey(in interface{}) (interface{}, error) {
switch k := in.(type) {
case *rsa.PublicKey, *rsa.PrivateKey,
*ecdsa.PublicKey, *ecdsa.PrivateKey,
*mldsa.PublicKey, *mldsa.PrivateKey,
ed25519.PublicKey, ed25519.PrivateKey,
x25519.PublicKey, x25519.PrivateKey:
return in, nil
Expand All @@ -147,7 +147,7 @@ func ExtractKey(in interface{}) (interface{}, error) {
case *ssh.Certificate:
return ExtractKey(k.Key)
default:
return nil, errors.Errorf("cannot extract the key from type '%T'", k)
return nil, errors.Errorf("cannot extract the key from type '%T'", in)
}
}

Expand All @@ -165,31 +165,14 @@ func VerifyPair(pub crypto.PublicKey, priv crypto.PrivateKey) error {

// Equal reports if x and y are the same key.
func Equal(x, y any) bool {
if eq, ok := x.(interface{ Equal(crypto.PublicKey) bool }); ok {
return eq.Equal(y)
}
if eq, ok := x.(interface{ Equal(crypto.PrivateKey) bool }); ok {
return eq.Equal(y)
}

switch xx := x.(type) {
case *ecdsa.PublicKey:
yy, ok := y.(*ecdsa.PublicKey)
return ok && xx.Equal(yy)
case *ecdsa.PrivateKey:
yy, ok := y.(*ecdsa.PrivateKey)
return ok && xx.Equal(yy)
case *rsa.PublicKey:
yy, ok := y.(*rsa.PublicKey)
return ok && xx.Equal(yy)
case *rsa.PrivateKey:
yy, ok := y.(*rsa.PrivateKey)
return ok && xx.Equal(yy)
case ed25519.PublicKey:
yy, ok := y.(ed25519.PublicKey)
return ok && xx.Equal(yy)
case ed25519.PrivateKey:
yy, ok := y.(ed25519.PrivateKey)
return ok && xx.Equal(yy)
case x25519.PublicKey:
yy, ok := y.(x25519.PublicKey)
return ok && xx.Equal(yy)
case x25519.PrivateKey:
yy, ok := y.(x25519.PrivateKey)
return ok && xx.Equal(yy)
case []byte: // special case for symmetric keys
yy, ok := y.([]byte)
return ok && bytes.Equal(xx, yy)
Expand Down Expand Up @@ -252,6 +235,38 @@ func generateOKPKey(crv string) (crypto.Signer, error) {
}
}

func generateAKPKey(alg string) (crypto.Signer, error) {
switch alg {
case "":
key, err := mldsa.GenerateKey(DefaultKeyAlgorithm())
if err != nil {
return nil, fmt.Errorf("error generating ML-DSA key: %w", err)
}
return key, nil
case "ML-DSA-44":
key, err := mldsa.GenerateKey(mldsa.MLDSA44())
if err != nil {
return nil, fmt.Errorf("error generating ML-DSA-44 key: %w", err)
}
return key, nil
case "ML-DSA-65":
key, err := mldsa.GenerateKey(mldsa.MLDSA65())
if err != nil {
return nil, fmt.Errorf("error generating ML-DSA-65 key: %w", err)
}
return key, nil
case "ML-DSA-87":
key, err := mldsa.GenerateKey(mldsa.MLDSA87())
if err != nil {
return nil, fmt.Errorf("error generating ML-DSA-87 key: %w", err)
}
return key, nil
default:
return nil, errors.Errorf("missing or invalid value for argument 'alg'. "+
"expected 'ML-DSA-44', 'ML-DSA-65', or 'ML-DSA-87', but got '%s'", alg)
}
}

func generateOctKey(size int) (interface{}, error) {
const chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
result := make([]byte, size)
Expand Down
Loading
Loading