-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #114 from golang-fips/ed25519
Support Ed25519
- Loading branch information
Showing
5 changed files
with
381 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
//go:build !cmd_go_bootstrap | ||
|
||
package openssl | ||
|
||
// #include "goopenssl.h" | ||
import "C" | ||
import ( | ||
"errors" | ||
"runtime" | ||
"strconv" | ||
"sync" | ||
"unsafe" | ||
) | ||
|
||
const ( | ||
// publicKeySizeEd25519 is the size, in bytes, of public keys as used in crypto/ed25519. | ||
publicKeySizeEd25519 = 32 | ||
// privateKeySizeEd25519 is the size, in bytes, of private keys as used in crypto/ed25519. | ||
privateKeySizeEd25519 = 64 | ||
// signatureSizeEd25519 is the size, in bytes, of signatures generated and verified by crypto/ed25519. | ||
signatureSizeEd25519 = 64 | ||
// seedSizeEd25519 is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032. | ||
seedSizeEd25519 = 32 | ||
) | ||
|
||
// TODO: Add support for Ed25519ph and Ed25519ctx when OpenSSL supports them, | ||
// which will probably be in 3.2.0 (https://github.com/openssl/openssl/issues/20418). | ||
|
||
var ( | ||
onceSupportsEd25519 sync.Once | ||
supportsEd25519 bool | ||
) | ||
|
||
// SupportsEd25519 returns true if the current OpenSSL version supports | ||
// GenerateKeyEd25519, NewKeyFromSeedEd25519, SignEd25519 and VerifyEd25519. | ||
func SupportsEd25519() bool { | ||
onceSupportsEd25519.Do(func() { | ||
switch vMajor { | ||
case 1: | ||
supportsEd25519 = version1_1_1_or_above() | ||
case 3: | ||
name := C.CString("ED25519") | ||
defer C.free(unsafe.Pointer(name)) | ||
sig := C.go_openssl_EVP_SIGNATURE_fetch(nil, name, nil) | ||
if sig != nil { | ||
C.go_openssl_EVP_SIGNATURE_free(sig) | ||
supportsEd25519 = true | ||
} | ||
} | ||
}) | ||
return supportsEd25519 | ||
} | ||
|
||
type PublicKeyEd25519 struct { | ||
_pkey C.GO_EVP_PKEY_PTR | ||
} | ||
|
||
func (k *PublicKeyEd25519) finalize() { | ||
C.go_openssl_EVP_PKEY_free(k._pkey) | ||
} | ||
|
||
func (k *PublicKeyEd25519) Bytes() ([]byte, error) { | ||
defer runtime.KeepAlive(k) | ||
pub := make([]byte, publicKeySizeEd25519) | ||
if err := extractPKEYPubEd25519(k._pkey, pub); err != nil { | ||
return nil, err | ||
} | ||
return pub, nil | ||
} | ||
|
||
type PrivateKeyEd25519 struct { | ||
_pkey C.GO_EVP_PKEY_PTR | ||
} | ||
|
||
func (k *PrivateKeyEd25519) finalize() { | ||
C.go_openssl_EVP_PKEY_free(k._pkey) | ||
} | ||
|
||
func (k *PrivateKeyEd25519) Bytes() ([]byte, error) { | ||
defer runtime.KeepAlive(k) | ||
priv := make([]byte, privateKeySizeEd25519) | ||
if err := extractPKEYPrivEd25519(k._pkey, priv); err != nil { | ||
return nil, err | ||
} | ||
return priv, nil | ||
} | ||
|
||
// GenerateKeyEd25519 generates a public/private key pair. | ||
func GenerateKeyEd25519() (*PublicKeyEd25519, *PrivateKeyEd25519, error) { | ||
pkeyPriv, err := generateEVPPKey(C.GO_EVP_PKEY_ED25519, 0, "") | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
pub := make([]byte, publicKeySizeEd25519) | ||
if err := extractPKEYPubEd25519(pkeyPriv, pub); err != nil { | ||
C.go_openssl_EVP_PKEY_free(pkeyPriv) | ||
return nil, nil, err | ||
} | ||
pubk, err := NewPublicKeyEd25119(pub) | ||
if err != nil { | ||
C.go_openssl_EVP_PKEY_free(pkeyPriv) | ||
return nil, nil, err | ||
} | ||
privk := &PrivateKeyEd25519{_pkey: pkeyPriv} | ||
runtime.SetFinalizer(privk, (*PrivateKeyEd25519).finalize) | ||
return pubk, privk, nil | ||
} | ||
|
||
func NewPrivateKeyEd25119(priv []byte) (*PrivateKeyEd25519, error) { | ||
if len(priv) != privateKeySizeEd25519 { | ||
panic("ed25519: bad private key length: " + strconv.Itoa(len(priv))) | ||
} | ||
return NewPrivateKeyEd25519FromSeed(priv[:seedSizeEd25519]) | ||
} | ||
|
||
func NewPublicKeyEd25119(pub []byte) (*PublicKeyEd25519, error) { | ||
if len(pub) != publicKeySizeEd25519 { | ||
panic("ed25519: bad public key length: " + strconv.Itoa(len(pub))) | ||
} | ||
pkey := C.go_openssl_EVP_PKEY_new_raw_public_key(C.GO_EVP_PKEY_ED25519, nil, base(pub), C.size_t(len(pub))) | ||
if pkey == nil { | ||
return nil, newOpenSSLError("EVP_PKEY_new_raw_public_key") | ||
} | ||
pubk := &PublicKeyEd25519{_pkey: pkey} | ||
runtime.SetFinalizer(pubk, (*PublicKeyEd25519).finalize) | ||
return pubk, nil | ||
} | ||
|
||
// NewPrivateKeyEd25519FromSeed calculates a private key from a seed. It will panic if | ||
// len(seed) is not [SeedSize]. RFC 8032's private keys correspond to seeds in this | ||
// package. | ||
func NewPrivateKeyEd25519FromSeed(seed []byte) (*PrivateKeyEd25519, error) { | ||
if len(seed) != seedSizeEd25519 { | ||
panic("ed25519: bad seed length: " + strconv.Itoa(len(seed))) | ||
} | ||
pkey := C.go_openssl_EVP_PKEY_new_raw_private_key(C.GO_EVP_PKEY_ED25519, nil, base(seed), C.size_t(len(seed))) | ||
if pkey == nil { | ||
return nil, newOpenSSLError("EVP_PKEY_new_raw_private_key") | ||
} | ||
priv := &PrivateKeyEd25519{_pkey: pkey} | ||
runtime.SetFinalizer(priv, (*PrivateKeyEd25519).finalize) | ||
return priv, nil | ||
} | ||
|
||
func extractPKEYPubEd25519(pkey C.GO_EVP_PKEY_PTR, pub []byte) error { | ||
pubSize := C.size_t(publicKeySizeEd25519) | ||
if C.go_openssl_EVP_PKEY_get_raw_public_key(pkey, base(pub), &pubSize) != 1 { | ||
return newOpenSSLError("EVP_PKEY_get_raw_public_key") | ||
} | ||
if pubSize != publicKeySizeEd25519 { | ||
return errors.New("ed25519: bad public key length: " + strconv.Itoa(int(pubSize))) | ||
} | ||
return nil | ||
} | ||
|
||
func extractPKEYPrivEd25519(pkey C.GO_EVP_PKEY_PTR, priv []byte) error { | ||
if err := extractPKEYPubEd25519(pkey, priv[seedSizeEd25519:]); err != nil { | ||
return err | ||
} | ||
privSize := C.size_t(seedSizeEd25519) | ||
if C.go_openssl_EVP_PKEY_get_raw_private_key(pkey, base(priv), &privSize) != 1 { | ||
return newOpenSSLError("EVP_PKEY_get_raw_private_key") | ||
} | ||
if privSize != seedSizeEd25519 { | ||
return errors.New("ed25519: bad private key length: " + strconv.Itoa(int(privSize))) | ||
} | ||
return nil | ||
} | ||
|
||
// SignEd25519 signs the message with priv and returns a signature. | ||
func SignEd25519(priv *PrivateKeyEd25519, message []byte) (sig []byte, err error) { | ||
// Outline the function body so that the returned key can be stack-allocated. | ||
sig = make([]byte, signatureSizeEd25519) | ||
err = signEd25519(priv, sig, message) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return sig, err | ||
} | ||
|
||
func signEd25519(priv *PrivateKeyEd25519, sig, message []byte) error { | ||
defer runtime.KeepAlive(priv) | ||
ctx := C.go_openssl_EVP_MD_CTX_new() | ||
if ctx == nil { | ||
return newOpenSSLError("EVP_MD_CTX_new") | ||
} | ||
defer C.go_openssl_EVP_MD_CTX_free(ctx) | ||
if C.go_openssl_EVP_DigestSignInit(ctx, nil, nil, nil, priv._pkey) != 1 { | ||
return newOpenSSLError("EVP_DigestSignInit") | ||
} | ||
siglen := C.size_t(signatureSizeEd25519) | ||
if C.go_openssl_EVP_DigestSign(ctx, base(sig), &siglen, base(message), C.size_t(len(message))) != 1 { | ||
return newOpenSSLError("EVP_DigestSign") | ||
} | ||
if siglen != signatureSizeEd25519 { | ||
return errors.New("ed25519: bad signature length: " + strconv.Itoa(int(siglen))) | ||
} | ||
return nil | ||
} | ||
|
||
// VerifyEd25519 reports whether sig is a valid signature of message by pub. | ||
func VerifyEd25519(pub *PublicKeyEd25519, message, sig []byte) error { | ||
defer runtime.KeepAlive(pub) | ||
ctx := C.go_openssl_EVP_MD_CTX_new() | ||
if ctx == nil { | ||
return newOpenSSLError("EVP_MD_CTX_new") | ||
} | ||
defer C.go_openssl_EVP_MD_CTX_free(ctx) | ||
if C.go_openssl_EVP_DigestVerifyInit(ctx, nil, nil, nil, pub._pkey) != 1 { | ||
return newOpenSSLError("EVP_DigestVerifyInit") | ||
} | ||
if C.go_openssl_EVP_DigestVerify(ctx, base(sig), C.size_t(len(sig)), base(message), C.size_t(len(message))) != 1 { | ||
return errors.New("ed25519: invalid signature") | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package openssl_test | ||
|
||
import ( | ||
"bytes" | ||
"crypto/ed25519" | ||
"testing" | ||
|
||
"github.com/golang-fips/openssl/v2" | ||
) | ||
|
||
func TestNewKeyFromSeedEd25519(t *testing.T) { | ||
if !openssl.SupportsEd25519() { | ||
t.Skip("Ed25519 not supported") | ||
} | ||
seed := bytes.Repeat([]byte{0x01}, ed25519.SeedSize) | ||
priv, err := openssl.NewPrivateKeyEd25519FromSeed(seed) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
data, err := priv.Bytes() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
priv2 := ed25519.NewKeyFromSeed(seed) | ||
if !bytes.Equal(data, []byte(priv2)) { | ||
t.Errorf("private key mismatch") | ||
} | ||
} | ||
|
||
func TestEd25519SignVerify(t *testing.T) { | ||
if !openssl.SupportsEd25519() { | ||
t.Skip("Ed25519 not supported") | ||
} | ||
public, private, err := openssl.GenerateKeyEd25519() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
message := []byte("test message") | ||
sig, err := openssl.SignEd25519(private, message) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
privData, err := private.Bytes() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if sig2 := ed25519.Sign(privData, message); !bytes.Equal(sig, sig2) { | ||
t.Errorf("signature mismatch") | ||
} | ||
if openssl.VerifyEd25519(public, message, sig) != nil { | ||
t.Errorf("valid signature rejected") | ||
} | ||
wrongMessage := []byte("wrong message") | ||
if openssl.VerifyEd25519(public, wrongMessage, sig) == nil { | ||
t.Errorf("signature of different message accepted") | ||
} | ||
} | ||
|
||
func TestEd25519Malleability(t *testing.T) { | ||
if !openssl.SupportsEd25519() { | ||
t.Skip("Ed25519 not supported") | ||
} | ||
// https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test | ||
// that s be in [0, order). This prevents someone from adding a multiple of | ||
// order to s and obtaining a second valid signature for the same message. | ||
msg := []byte{0x54, 0x65, 0x73, 0x74} | ||
sig := []byte{ | ||
0x7c, 0x38, 0xe0, 0x26, 0xf2, 0x9e, 0x14, 0xaa, 0xbd, 0x05, 0x9a, | ||
0x0f, 0x2d, 0xb8, 0xb0, 0xcd, 0x78, 0x30, 0x40, 0x60, 0x9a, 0x8b, | ||
0xe6, 0x84, 0xdb, 0x12, 0xf8, 0x2a, 0x27, 0x77, 0x4a, 0xb0, 0x67, | ||
0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f, 0x5d, | ||
0xaf, 0xc0, 0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33, | ||
0x36, 0xa5, 0xc5, 0x1e, 0xb6, 0xf9, 0x46, 0xb3, 0x1d, | ||
} | ||
publicKey := []byte{ | ||
0x7d, 0x4d, 0x0e, 0x7f, 0x61, 0x53, 0xa6, 0x9b, 0x62, 0x42, 0xb5, | ||
0x22, 0xab, 0xbe, 0xe6, 0x85, 0xfd, 0xa4, 0x42, 0x0f, 0x88, 0x34, | ||
0xb1, 0x08, 0xc3, 0xbd, 0xae, 0x36, 0x9e, 0xf5, 0x49, 0xfa, | ||
} | ||
|
||
pub, err := openssl.NewPublicKeyEd25119(publicKey) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if openssl.VerifyEd25519(pub, msg, sig) == nil { | ||
t.Fatal("non-canonical signature accepted") | ||
} | ||
} | ||
|
||
func BenchmarkEd25519GenerateKey(b *testing.B) { | ||
if !openssl.SupportsEd25519() { | ||
b.Skip("Ed25519 not supported") | ||
} | ||
for i := 0; i < b.N; i++ { | ||
_, _, err := openssl.GenerateKeyEd25519() | ||
if err != nil { | ||
b.Fatal(err) | ||
} | ||
} | ||
} | ||
|
||
func BenchmarkEd25519NewKeyFromSeed(b *testing.B) { | ||
if !openssl.SupportsEd25519() { | ||
b.Skip("Ed25519 not supported") | ||
} | ||
seed := make([]byte, ed25519.SeedSize) | ||
for i := 0; i < b.N; i++ { | ||
_, err := openssl.NewPrivateKeyEd25519FromSeed(seed) | ||
if err != nil { | ||
b.Fatal(err) | ||
} | ||
} | ||
} | ||
|
||
func BenchmarkEd25519Signing(b *testing.B) { | ||
if !openssl.SupportsEd25519() { | ||
b.Skip("Ed25519 not supported") | ||
} | ||
_, priv, err := openssl.GenerateKeyEd25519() | ||
if err != nil { | ||
b.Fatal(err) | ||
} | ||
message := []byte("Hello, world!") | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
openssl.SignEd25519(priv, message) | ||
} | ||
} | ||
|
||
func BenchmarkEd25519Verification(b *testing.B) { | ||
if !openssl.SupportsEd25519() { | ||
b.Skip("Ed25519 not supported") | ||
} | ||
pub, priv, err := openssl.GenerateKeyEd25519() | ||
if err != nil { | ||
b.Fatal(err) | ||
} | ||
message := []byte("Hello, world!") | ||
signature, err := openssl.SignEd25519(priv, message) | ||
if err != nil { | ||
b.Fatal(err) | ||
} | ||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
openssl.VerifyEd25519(pub, message, signature) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.