Skip to content

WIP: Add support for x448 / ed448 #179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
62 changes: 62 additions & 0 deletions crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import (
"math/big"
"time"

"git.schwanenlied.me/yawning/x448.git"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/ed25519"

// Blank includes to ensure hash support
_ "crypto/sha1"
Expand All @@ -44,6 +46,7 @@ const (
signatureAlgorithmRSA_PKCS1
signatureAlgorithmRSA_PSS
signatureAlgorithmECDSA
signatureAlgorithmEd25519
)

var (
Expand Down Expand Up @@ -71,6 +74,7 @@ var (
RSA_PSS_SHA256: signatureAlgorithmRSA_PSS,
RSA_PSS_SHA384: signatureAlgorithmRSA_PSS,
RSA_PSS_SHA512: signatureAlgorithmRSA_PSS,
Ed25519: signatureAlgorithmEd25519,
}

curveMap = map[SignatureScheme]NamedGroup{
Expand Down Expand Up @@ -114,6 +118,8 @@ var (
ECDSA_P256_SHA256: x509.ECDSAWithSHA256,
ECDSA_P384_SHA384: x509.ECDSAWithSHA384,
ECDSA_P521_SHA512: x509.ECDSAWithSHA512,
// XXX([email protected]): Ed25519 not supported by crypto/x509
// XXX([email protected]): Ed448 not supported by crypto/x509
}

defaultRSAKeySize = 2048
Expand Down Expand Up @@ -148,6 +154,8 @@ func keyExchangeSizeFromNamedGroup(group NamedGroup) (size int) {
switch group {
case X25519:
size = 32
case X448:
size = 56
case P256:
size = 65
case P384:
Expand Down Expand Up @@ -191,6 +199,8 @@ func schemeValidForKey(alg SignatureScheme, key crypto.Signer) bool {
return sigType == signatureAlgorithmRSA_PKCS1 || sigType == signatureAlgorithmRSA_PSS
case *ecdsa.PrivateKey:
return sigType == signatureAlgorithmECDSA
case *ed25519.PrivateKey:
return sigType == signatureAlgorithmEd25519
default:
return false
}
Expand Down Expand Up @@ -257,6 +267,22 @@ func newKeyShare(group NamedGroup) (pub []byte, priv []byte, err error) {
pub = public[:]
return

case X448:
var private, public [56]byte
_, err = prng.Read(private[:])
if err != nil {
return
}

rv := x448.ScalarBaseMult(&public, &private)
if rv != 0 {
return nil, nil, fmt.Errorf("tls.newkeyshare: X448 failed")
}

priv = private[:]
pub = public[:]
return

default:
return nil, nil, fmt.Errorf("tls.newkeyshare: Unsupported group %v", group)
}
Expand Down Expand Up @@ -308,6 +334,21 @@ func keyAgreement(group NamedGroup, pub []byte, priv []byte) ([]byte, error) {

return ret[:], nil

case X448:
if len(pub) != keyExchangeSizeFromNamedGroup(group) {
return nil, fmt.Errorf("tls.keyagreement: Wrong public key size")
}

var private, public, ret [56]byte
copy(private[:], priv)
copy(public[:], pub)
rv := x448.ScalarMult(&ret, &private, &public)
if rv != 0 {
return nil, fmt.Errorf("tls.keyagreement: Error in X448")
}

return ret[:], nil

default:
return nil, fmt.Errorf("tls.keyagreement: Unsupported group %v", group)
}
Expand All @@ -326,6 +367,9 @@ func newSigningKey(sig SignatureScheme) (crypto.Signer, error) {
return ecdsa.GenerateKey(elliptic.P384(), prng)
case ECDSA_P521_SHA512:
return ecdsa.GenerateKey(elliptic.P521(), prng)
case Ed25519:
_, priv, err := ed25519.GenerateKey(prng)
return priv, err
default:
return nil, fmt.Errorf("tls.newsigningkey: Unsupported signature algorithm [%04x]", sig)
}
Expand Down Expand Up @@ -378,6 +422,13 @@ func sign(alg SignatureScheme, privateKey crypto.Signer, sigInput []byte) ([]byt
h := hash.New()
h.Write(sigInput)
realInput = h.Sum(nil)
case ed25519.PrivateKey:
if sigType != signatureAlgorithmEd25519 {
return nil, fmt.Errorf("tls.crypto.sign: Unsupported algorithm for ECDSA key")
}

realInput = sigInput
opts = crypto.Hash(0)
default:
return nil, fmt.Errorf("tls.crypto.sign: Unsupported private key type")
}
Expand Down Expand Up @@ -445,6 +496,17 @@ func verify(alg SignatureScheme, publicKey crypto.PublicKey, sigInput []byte, si
return fmt.Errorf("tls.verify: ECDSA verification failure")
}
return nil

case ed25519.PublicKey:
if sigType != signatureAlgorithmEd25519 {
return fmt.Errorf("tls.verify: Unsupported algorithm for ECDSA key")
}

if !ed25519.Verify(pub, sigInput, sig) {
return fmt.Errorf("tls.verify: Ed25519 verification failure")
}
return nil

default:
return fmt.Errorf("tls.verify: Unsupported key type")
}
Expand Down
41 changes: 36 additions & 5 deletions crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import (
"io"
"math/big"
"testing"

"golang.org/x/crypto/ed25519"
)

var (
ecGroups = []NamedGroup{P256, P384, P521}
nonECGroups = []NamedGroup{FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192, X25519}
nonECGroups = []NamedGroup{FFDHE2048, FFDHE3072, FFDHE4096, FFDHE6144, FFDHE8192, X25519, X448}
dhGroups = append(ecGroups, nonECGroups...)

shortKeyPubHex = "04e9f6076620ddf6a24e4398162057eccd3077892f046b412" +
Expand Down Expand Up @@ -92,6 +94,15 @@ func TestNewKeyShare(t *testing.T) {
assertError(t, err, "Generated an X25519 key with no entropy")
prng = originalPRNG

// Test failure case for an X448 key generation failure
originalPRNG = prng
prng = bytes.NewReader(nil)
_, _, err = newKeyShare(X448)
assertError(t, err, "Generated an X448 key with no entropy")
prng = originalPRNG

// TODO([email protected]): Test failure for X448 when the zero key is derived

// Test failure case for an unknown group
_, _, err = newKeyShare(NamedGroup(0))
assertError(t, err, "Generated a key for an unsupported group")
Expand Down Expand Up @@ -135,6 +146,10 @@ func TestKeyAgreement(t *testing.T) {
_, err = keyAgreement(X25519, shortKeyPub[:5], shortKeyPriv)
assertError(t, err, "Performed key agreement with a truncated public key")

// Test failure for a too-short X448 public key
_, err = keyAgreement(X448, shortKeyPub[:5], shortKeyPriv)
assertError(t, err, "Performed key agreement with a truncated public key")

// Test failure case for an unknown group
_, err = keyAgreement(NamedGroup(0), shortKeyPub, shortKeyPriv)
assertError(t, err, "Performed key agreement with an unsupported group")
Expand All @@ -149,30 +164,36 @@ func TestNewSigningKey(t *testing.T) {

// Test ECDSA success (P-256)
privECDSA, err := newSigningKey(ECDSA_P256_SHA256)
assertNotError(t, err, "failed to generate RSA private key")
assertNotError(t, err, "failed to generate ECDSA private key")
_, ok = privECDSA.(*ecdsa.PrivateKey)
assert(t, ok, "New ECDSA key was not actually an ECDSA key")
pub := privECDSA.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey)
assertEquals(t, P256, namedGroupFromECDSAKey(pub))

// Test ECDSA success (P-384)
privECDSA, err = newSigningKey(ECDSA_P384_SHA384)
assertNotError(t, err, "failed to generate RSA private key")
assertNotError(t, err, "failed to generate ECDSA private key")
_, ok = privECDSA.(*ecdsa.PrivateKey)
assert(t, ok, "New ECDSA key was not actually an ECDSA key")
pub = privECDSA.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey)
assertEquals(t, P384, namedGroupFromECDSAKey(pub))

// Test ECDSA success (P-521)
privECDSA, err = newSigningKey(ECDSA_P521_SHA512)
assertNotError(t, err, "failed to generate RSA private key")
assertNotError(t, err, "failed to generate ECDSA private key")
_, ok = privECDSA.(*ecdsa.PrivateKey)
assert(t, ok, "New ECDSA key was not actually an ECDSA key")
pub = privECDSA.(*ecdsa.PrivateKey).Public().(*ecdsa.PublicKey)
assertEquals(t, P521, namedGroupFromECDSAKey(pub))

// Test Ed25519 success
privEd25519, err := newSigningKey(Ed25519)
assertNotError(t, err, "failed to generate Ed25519 private key")
_, ok = privEd25519.(ed25519.PrivateKey)
assert(t, ok, "New Ed25519 key was not actually an Ed25519 key")

// Test unsupported algorithm
_, err = newSigningKey(Ed25519)
_, err = newSigningKey(Ed448 + 1)
assertError(t, err, "Created a private key for an unsupported algorithm")
}

Expand Down Expand Up @@ -208,6 +229,8 @@ func TestSignVerify(t *testing.T) {
assertNotError(t, err, "failed to generate RSA private key")
privECDSA, err := newSigningKey(ECDSA_P256_SHA256)
assertNotError(t, err, "failed to generate ECDSA private key")
privEd25519, err := newSigningKey(Ed25519)
assertNotError(t, err, "failed to generate Ed25519 private key")

// Test successful signing with PKCS#1 when it is allowed
originalAllowPKCS1 := allowPKCS1
Expand Down Expand Up @@ -235,6 +258,10 @@ func TestSignVerify(t *testing.T) {
sigECDSA, err := sign(ECDSA_P256_SHA256, privECDSA, data)
assertNotError(t, err, "Failed to generate ECDSA signature")

// Test successful signing with Ed25519
sigEd25519, err := sign(Ed25519, privEd25519, data)
assertNotError(t, err, "Failed to generate Ed25519 signature")

// Test signature failure on use of SHA-1
_, err = sign(RSA_PKCS1_SHA1, privRSA, data)
assertError(t, err, "Allowed a SHA-1 signature")
Expand Down Expand Up @@ -277,6 +304,10 @@ func TestSignVerify(t *testing.T) {
err = verify(ECDSA_P256_SHA256, privECDSA.Public(), data, sigECDSA)
assertNotError(t, err, "Failed to verify a valid ECDSA signature")

// Test successful verification with Ed25519
err = verify(Ed25519, privEd25519.Public(), data, sigEd25519)
assertNotError(t, err, "Failed to verify a valid Ed25519 signature")

// Test that SHA-1 is forbidden
err = verify(RSA_PKCS1_SHA1, privECDSA.Public(), data, sigECDSA)
assertError(t, err, "Allowed verification of a SHA-1 signature")
Expand Down