Support precomputed ed25519 public keys
This commit is contained in:
parent
c11e3bb90d
commit
00909b144c
|
@ -99,22 +99,22 @@ func (r *RSAKey) Blob() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ED25519Key is a Key for AddOnion that is a ed25519 key (i.e. v3).
|
// ED25519Key is a Key for AddOnion that is a ed25519 key (i.e. v3).
|
||||||
type ED25519Key ed25519.PrivateKey
|
type ED25519Key struct{ ed25519.KeyPair }
|
||||||
|
|
||||||
// ED25519KeyFromBlob creates a ED25519Key for the given response blob.
|
// ED25519KeyFromBlob creates a ED25519Key for the given response blob.
|
||||||
func ED25519KeyFromBlob(blob string) (ED25519Key, error) {
|
func ED25519KeyFromBlob(blob string) (*ED25519Key, error) {
|
||||||
byts, err := base64.StdEncoding.DecodeString(blob)
|
byts, err := base64.StdEncoding.DecodeString(blob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ED25519Key(ed25519.PrivateKey(byts)), nil
|
return &ED25519Key{ed25519.PrivateKey(byts).KeyPair()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type implements Key.Type.
|
// Type implements Key.Type.
|
||||||
func (ED25519Key) Type() KeyType { return KeyTypeED25519V3 }
|
func (*ED25519Key) Type() KeyType { return KeyTypeED25519V3 }
|
||||||
|
|
||||||
// Blob implements Key.Blob.
|
// Blob implements Key.Blob.
|
||||||
func (e ED25519Key) Blob() string { return base64.StdEncoding.EncodeToString(e) }
|
func (e *ED25519Key) Blob() string { return base64.StdEncoding.EncodeToString(e.PrivateKey()) }
|
||||||
|
|
||||||
// AddOnionRequest is a set of request params for AddOnion.
|
// AddOnionRequest is a set of request params for AddOnion.
|
||||||
type AddOnionRequest struct {
|
type AddOnionRequest struct {
|
||||||
|
|
|
@ -11,12 +11,14 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var torEnabled bool
|
||||||
var torExePath string
|
var torExePath string
|
||||||
var torVerbose bool
|
var torVerbose bool
|
||||||
var torIncludeNetworkTests bool
|
var torIncludeNetworkTests bool
|
||||||
var globalEnabledNetworkContext *TestContext
|
var globalEnabledNetworkContext *TestContext
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
|
flag.BoolVar(&torEnabled, "tor", false, "Whether any of the integration tests are enabled")
|
||||||
flag.StringVar(&torExePath, "tor.path", "tor", "The Tor exe path")
|
flag.StringVar(&torExePath, "tor.path", "tor", "The Tor exe path")
|
||||||
flag.BoolVar(&torVerbose, "tor.verbose", false, "Show verbose test info")
|
flag.BoolVar(&torVerbose, "tor.verbose", false, "Show verbose test info")
|
||||||
flag.BoolVar(&torIncludeNetworkTests, "tor.network", false, "Include network tests")
|
flag.BoolVar(&torIncludeNetworkTests, "tor.network", false, "Include network tests")
|
||||||
|
@ -29,14 +31,10 @@ func TestMain(m *testing.M) {
|
||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SkipIfExcludingNetworkTests(t *testing.T) {
|
|
||||||
if !torIncludeNetworkTests {
|
|
||||||
t.Skip("Only runs if -tor.network is set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GlobalEnabledNetworkContext(t *testing.T) *TestContext {
|
func GlobalEnabledNetworkContext(t *testing.T) *TestContext {
|
||||||
SkipIfExcludingNetworkTests(t)
|
if !torEnabled || !torIncludeNetworkTests {
|
||||||
|
t.Skip("Only runs if -tor and -tor.network is set")
|
||||||
|
}
|
||||||
if globalEnabledNetworkContext == nil {
|
if globalEnabledNetworkContext == nil {
|
||||||
ctx := NewTestContext(t, nil)
|
ctx := NewTestContext(t, nil)
|
||||||
ctx.CloseTorOnClose = false
|
ctx.CloseTorOnClose = false
|
||||||
|
@ -61,6 +59,9 @@ type TestContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestContext(t *testing.T, conf *tor.StartConf) *TestContext {
|
func NewTestContext(t *testing.T, conf *tor.StartConf) *TestContext {
|
||||||
|
if !torEnabled {
|
||||||
|
t.Skip("Only runs if -tor is set")
|
||||||
|
}
|
||||||
// Build start conf
|
// Build start conf
|
||||||
if conf == nil {
|
if conf == nil {
|
||||||
conf = &tor.StartConf{}
|
conf = &tor.StartConf{}
|
||||||
|
|
|
@ -21,7 +21,7 @@ type OnionService struct {
|
||||||
// Key is the private key for this service. It is either the set key, the
|
// Key is the private key for this service. It is either the set key, the
|
||||||
// generated key, or nil if asked to discard the key. If present, it is
|
// generated key, or nil if asked to discard the key. If present, it is
|
||||||
// *crypto/rsa.PrivateKey (1024 bit) when Version3 is false or
|
// *crypto/rsa.PrivateKey (1024 bit) when Version3 is false or
|
||||||
// github.com/cretz/bine/torutil/ed25519.PrivateKey when Version3 is true.
|
// github.com/cretz/bine/torutil/ed25519.KeyPair when Version3 is true.
|
||||||
Key crypto.PrivateKey
|
Key crypto.PrivateKey
|
||||||
|
|
||||||
// Version3 says whether or not this service is a V3 service.
|
// Version3 says whether or not this service is a V3 service.
|
||||||
|
@ -66,7 +66,7 @@ type ListenConf struct {
|
||||||
// Key is the private key to use. If not present, a key is generated based
|
// Key is the private key to use. If not present, a key is generated based
|
||||||
// on whether Version3 is true or false. If present, it must be a
|
// on whether Version3 is true or false. If present, it must be a
|
||||||
// *crypto/rsa.PrivateKey (1024 bit), a
|
// *crypto/rsa.PrivateKey (1024 bit), a
|
||||||
// github.com/cretz/bine/torutil/ed25519.PrivateKey, a
|
// github.com/cretz/bine/torutil/ed25519.KeyPair, a
|
||||||
// golang.org/x/crypto/ed25519.PrivateKey, or a
|
// golang.org/x/crypto/ed25519.PrivateKey, or a
|
||||||
// github.com/cretz/bine/control.Key.
|
// github.com/cretz/bine/control.Key.
|
||||||
Key crypto.PrivateKey
|
Key crypto.PrivateKey
|
||||||
|
@ -179,17 +179,17 @@ func (t *Tor) Listen(ctx context.Context, conf *ListenConf) (*OnionService, erro
|
||||||
} else {
|
} else {
|
||||||
req.Key = key
|
req.Key = key
|
||||||
}
|
}
|
||||||
case ed25519.PrivateKey:
|
case ed25519.KeyPair:
|
||||||
svc.Key = key
|
svc.Key = key
|
||||||
svc.Version3 = true
|
svc.Version3 = true
|
||||||
req.Key = control.ED25519Key(key)
|
req.Key = &control.ED25519Key{key}
|
||||||
case othered25519.PrivateKey:
|
case othered25519.PrivateKey:
|
||||||
properKey := ed25519.FromCryptoPrivateKey(key)
|
properKey := ed25519.FromCryptoPrivateKey(key)
|
||||||
svc.Key = properKey
|
svc.Key = properKey
|
||||||
svc.Version3 = true
|
svc.Version3 = true
|
||||||
req.Key = control.ED25519Key(properKey)
|
req.Key = &control.ED25519Key{properKey}
|
||||||
case control.ED25519Key:
|
case *control.ED25519Key:
|
||||||
svc.Key = ed25519.PrivateKey(key)
|
svc.Key = key.KeyPair
|
||||||
svc.Version3 = true
|
svc.Version3 = true
|
||||||
req.Key = key
|
req.Key = key
|
||||||
default:
|
default:
|
||||||
|
@ -235,8 +235,8 @@ func (t *Tor) Listen(ctx context.Context, conf *ListenConf) (*OnionService, erro
|
||||||
// Do nothing
|
// Do nothing
|
||||||
case *control.RSAKey:
|
case *control.RSAKey:
|
||||||
svc.Key = key.PrivateKey
|
svc.Key = key.PrivateKey
|
||||||
case control.ED25519Key:
|
case *control.ED25519Key:
|
||||||
svc.Key = ed25519.PrivateKey(key)
|
svc.Key = key.KeyPair
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("Unrecognized result key type: %T", key)
|
err = fmt.Errorf("Unrecognized result key type: %T", key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,21 +16,31 @@ import (
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ref: https://stackoverflow.com/questions/44810708/ed25519-public-result-is-different
|
const (
|
||||||
|
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||||
|
PublicKeySize = 32
|
||||||
|
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||||
|
PrivateKeySize = 64
|
||||||
|
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||||
|
SignatureSize = 64
|
||||||
|
)
|
||||||
|
|
||||||
// PrivateKey is a 64-byte Ed25519 private key.
|
// PrivateKey is a 64-byte Ed25519 private key. Unlike
|
||||||
|
// golang.org/x/crypto/ed25519, this is just the digest and does not contain
|
||||||
|
// the public key within it. Instead call PublicKey() or better, call KeyPair()
|
||||||
|
// which stores the precomputed public key.
|
||||||
type PrivateKey []byte
|
type PrivateKey []byte
|
||||||
|
|
||||||
// PublicKey is a 32-byte Ed25519 public key.
|
// PublicKey is a 32-byte Ed25519 public key.
|
||||||
type PublicKey []byte
|
type PublicKey []byte
|
||||||
|
|
||||||
// FromCryptoPrivateKey converts a Go private key to the one in this package.
|
// FromCryptoPrivateKey converts a Go private key to the one in this package.
|
||||||
func FromCryptoPrivateKey(key ed25519.PrivateKey) PrivateKey {
|
func FromCryptoPrivateKey(key ed25519.PrivateKey) KeyPair {
|
||||||
digest := sha512.Sum512(key[:32])
|
digest := sha512.Sum512(key[:32])
|
||||||
digest[0] &= 248
|
digest[0] &= 248
|
||||||
digest[31] &= 127
|
digest[31] &= 127
|
||||||
digest[31] |= 64
|
digest[31] |= 64
|
||||||
return digest[:]
|
return &precomputedKeyPair{PrivateKeyBytes: digest[:], PublicKeyBytes: PublicKey(key[32:])}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromCryptoPublicKey converts a Go public key to the one in this package.
|
// FromCryptoPublicKey converts a Go public key to the one in this package.
|
||||||
|
@ -38,16 +48,24 @@ func FromCryptoPublicKey(key ed25519.PublicKey) PublicKey {
|
||||||
return PublicKey(key)
|
return PublicKey(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyPair returns a new key pair with the public key precomputed.
|
||||||
|
func (p PrivateKey) KeyPair() KeyPair {
|
||||||
|
return &precomputedKeyPair{PrivateKeyBytes: p, PublicKeyBytes: p.PublicKey()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivateKey simply returns itself. Implements KeyPair.PrivateKey.
|
||||||
|
func (p PrivateKey) PrivateKey() PrivateKey { return p }
|
||||||
|
|
||||||
// Public simply delegates to PublicKey() to satisfy crypto.Signer. This method
|
// Public simply delegates to PublicKey() to satisfy crypto.Signer. This method
|
||||||
// does a bit more work than the traditional Go ed25519's private key's Public()
|
// does a bit more work than the traditional Go ed25519's private key's Public()
|
||||||
// method so developers are encouraged to reuse the result.
|
// method so developers are encouraged to reuse the result or use KeyPair()
|
||||||
func (p PrivateKey) Public() crypto.PublicKey {
|
// which stores this value.
|
||||||
return p.PublicKey()
|
func (p PrivateKey) Public() crypto.PublicKey { return p.PublicKey() }
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKey generates a public key for this private key. This method does a bit
|
// PublicKey generates a public key for this private key. This method does a bit
|
||||||
// more work than the traditional Go ed25519's private key's Public() method so
|
// more work than the traditional Go ed25519's private key's Public() method so
|
||||||
// developers are encouraged to reuse the result.
|
// developers are encouraged to reuse the result or use KeyPair() which stores
|
||||||
|
// this value. Implements KeyPair.PublicKey.
|
||||||
func (p PrivateKey) PublicKey() PublicKey {
|
func (p PrivateKey) PublicKey() PublicKey {
|
||||||
var A edwards25519.ExtendedGroupElement
|
var A edwards25519.ExtendedGroupElement
|
||||||
var hBytes [32]byte
|
var hBytes [32]byte
|
||||||
|
@ -58,28 +76,114 @@ func (p PrivateKey) PublicKey() PublicKey {
|
||||||
return publicKeyBytes[:]
|
return publicKeyBytes[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign is not yet implemented.
|
// Sign signs the given message with priv. Ed25519 performs two passes over
|
||||||
func (p PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
// messages to be signed and therefore cannot handle pre-hashed messages. Thus
|
||||||
|
// opts.HashFunc() must return zero to indicate the message hasn't been hashed.
|
||||||
|
// This can be achieved by passing crypto.Hash(0) as the value for opts.
|
||||||
|
func (p PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||||
if opts.HashFunc() != crypto.Hash(0) {
|
if opts.HashFunc() != crypto.Hash(0) {
|
||||||
return nil, errors.New("ed25519: cannot sign hashed message")
|
return nil, errors.New("ed25519: cannot sign hashed message")
|
||||||
}
|
}
|
||||||
panic("TODO")
|
return Sign(p, message), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify simply calls PublicKey().Verify(). Callers are encouraged to instead
|
||||||
|
// store a precomputed KeyPair (via KeyPair() or GenerateKey()) and call Verify
|
||||||
|
// on that.
|
||||||
|
func (p PrivateKey) Verify(message []byte, sig []byte) bool {
|
||||||
|
return p.PublicKey().Verify(message, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify simply calls the package-level function Verify().
|
||||||
|
func (p PublicKey) Verify(message []byte, sig []byte) bool {
|
||||||
|
return Verify(p, message, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyPair is an interface for types with both keys. While PrivateKey does
|
||||||
|
// implement this, it generates the PublicKey on demand. For better performance,
|
||||||
|
// use the result of GenerateKey directly or call PrivateKey.KeyPair().
|
||||||
|
type KeyPair interface {
|
||||||
|
crypto.Signer
|
||||||
|
PrivateKey() PrivateKey
|
||||||
|
PublicKey() PublicKey
|
||||||
|
Verify(message []byte, sig []byte) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type precomputedKeyPair struct {
|
||||||
|
PrivateKeyBytes PrivateKey
|
||||||
|
PublicKeyBytes PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *precomputedKeyPair) PrivateKey() PrivateKey { return p.PrivateKeyBytes }
|
||||||
|
func (p *precomputedKeyPair) PublicKey() PublicKey { return p.PublicKeyBytes }
|
||||||
|
func (p *precomputedKeyPair) Public() crypto.PublicKey { return p.PublicKey() }
|
||||||
|
func (p *precomputedKeyPair) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||||
|
if opts.HashFunc() != crypto.Hash(0) {
|
||||||
|
return nil, errors.New("ed25519: cannot sign hashed message")
|
||||||
|
}
|
||||||
|
return Sign(p, message), nil
|
||||||
|
}
|
||||||
|
func (p *precomputedKeyPair) Verify(message []byte, sig []byte) bool {
|
||||||
|
return p.PublicKeyBytes.Verify(message, sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateKey generates a public/private key pair using entropy from rand.
|
// GenerateKey generates a public/private key pair using entropy from rand.
|
||||||
// If rand is nil, crypto/rand.Reader will be used.
|
// If rand is nil, crypto/rand.Reader will be used.
|
||||||
func GenerateKey(rnd io.Reader) (publicKey PublicKey, privateKey PrivateKey, err error) {
|
func GenerateKey(rnd io.Reader) (KeyPair, error) {
|
||||||
if rnd == nil {
|
if rnd == nil {
|
||||||
rnd = rand.Reader
|
rnd = rand.Reader
|
||||||
}
|
}
|
||||||
_, err = io.ReadFull(rnd, privateKey[:32])
|
rndByts := make([]byte, 32)
|
||||||
if err == nil {
|
if _, err := io.ReadFull(rnd, rndByts); err != nil {
|
||||||
digest := sha512.Sum512(privateKey[:32])
|
return nil, err
|
||||||
digest[0] &= 248
|
|
||||||
digest[31] &= 127
|
|
||||||
digest[31] |= 64
|
|
||||||
privateKey = digest[:]
|
|
||||||
publicKey = privateKey.PublicKey()
|
|
||||||
}
|
}
|
||||||
return
|
digest := sha512.Sum512(rndByts)
|
||||||
|
digest[0] &= 248
|
||||||
|
digest[31] &= 127
|
||||||
|
digest[31] |= 64
|
||||||
|
return PrivateKey(digest[:]).KeyPair(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the message with the given key pair.
|
||||||
|
func Sign(keyPair KeyPair, message []byte) []byte {
|
||||||
|
// Ref: https://stackoverflow.com/questions/44810708/ed25519-public-result-is-different
|
||||||
|
|
||||||
|
var privateKeyA [32]byte
|
||||||
|
copy(privateKeyA[:], keyPair.PrivateKey()) // we need this in an array later
|
||||||
|
var messageDigest, hramDigest [64]byte
|
||||||
|
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write(keyPair.PrivateKey()[32:])
|
||||||
|
h.Write(message)
|
||||||
|
h.Sum(messageDigest[:0])
|
||||||
|
|
||||||
|
var messageDigestReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
|
||||||
|
var R edwards25519.ExtendedGroupElement
|
||||||
|
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
|
||||||
|
|
||||||
|
var encodedR [32]byte
|
||||||
|
R.ToBytes(&encodedR)
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(encodedR[:])
|
||||||
|
h.Write(keyPair.PublicKey())
|
||||||
|
h.Write(message)
|
||||||
|
h.Sum(hramDigest[:0])
|
||||||
|
var hramDigestReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
|
||||||
|
|
||||||
|
var s [32]byte
|
||||||
|
edwards25519.ScMulAdd(&s, &hramDigestReduced, &privateKeyA, &messageDigestReduced)
|
||||||
|
|
||||||
|
signature := make([]byte, 64)
|
||||||
|
copy(signature[:], encodedR[:])
|
||||||
|
copy(signature[32:], s[:])
|
||||||
|
|
||||||
|
return signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify verifies a signed message.
|
||||||
|
func Verify(p PublicKey, message []byte, sig []byte) bool {
|
||||||
|
return ed25519.Verify(ed25519.PublicKey(p), message, sig)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
package ed25519
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cretz/bine/torutil/ed25519/internal/edwards25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Taken from https://github.com/golang/crypto/blob/1a580b3eff7814fc9b40602fd35256c63b50f491/ed25519/ed25519_test.go
|
||||||
|
|
||||||
|
type zeroReader struct{}
|
||||||
|
|
||||||
|
func (zeroReader) Read(buf []byte) (int, error) {
|
||||||
|
for i := range buf {
|
||||||
|
buf[i] = 0
|
||||||
|
}
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalMarshal(t *testing.T) {
|
||||||
|
pair, _ := GenerateKey(rand.Reader)
|
||||||
|
|
||||||
|
var A edwards25519.ExtendedGroupElement
|
||||||
|
var pubBytes [32]byte
|
||||||
|
copy(pubBytes[:], pair.PublicKey())
|
||||||
|
if !A.FromBytes(&pubBytes) {
|
||||||
|
t.Fatalf("ExtendedGroupElement.FromBytes failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var pub2 [32]byte
|
||||||
|
A.ToBytes(&pub2)
|
||||||
|
|
||||||
|
if pubBytes != pub2 {
|
||||||
|
t.Errorf("FromBytes(%v)->ToBytes does not round-trip, got %x\n", pubBytes, pub2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignVerify(t *testing.T) {
|
||||||
|
var zero zeroReader
|
||||||
|
pair, _ := GenerateKey(zero)
|
||||||
|
|
||||||
|
message := []byte("test message")
|
||||||
|
sig := Sign(pair, message)
|
||||||
|
if !Verify(pair.PublicKey(), message, sig) {
|
||||||
|
t.Errorf("valid signature rejected")
|
||||||
|
}
|
||||||
|
|
||||||
|
wrongMessage := []byte("wrong message")
|
||||||
|
if Verify(pair.PublicKey(), wrongMessage, sig) {
|
||||||
|
t.Errorf("signature of different message accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCryptoSigner(t *testing.T) {
|
||||||
|
var zero zeroReader
|
||||||
|
pair, _ := GenerateKey(zero)
|
||||||
|
|
||||||
|
signer := crypto.Signer(pair)
|
||||||
|
|
||||||
|
publicInterface := signer.Public()
|
||||||
|
public2, ok := publicInterface.(PublicKey)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected PublicKey from Public() but got %T", publicInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(pair.PublicKey(), public2) {
|
||||||
|
t.Errorf("public keys do not match: original:%x vs Public():%x", pair.PublicKey(), public2)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := []byte("message")
|
||||||
|
var noHash crypto.Hash
|
||||||
|
signature, err := signer.Sign(zero, message, noHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error from Sign(): %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Verify(pair.PublicKey(), message, signature) {
|
||||||
|
t.Errorf("Verify failed on signature from Sign()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGolden(t *testing.T) {
|
||||||
|
// sign.input.gz is a selection of test cases from
|
||||||
|
// https://ed25519.cr.yp.to/python/sign.input
|
||||||
|
testDataZ, err := os.Open("testdata/sign.input.gz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer testDataZ.Close()
|
||||||
|
testData, err := gzip.NewReader(testDataZ)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer testData.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(testData)
|
||||||
|
lineNo := 0
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
lineNo++
|
||||||
|
|
||||||
|
line := scanner.Text()
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
if len(parts) != 5 {
|
||||||
|
t.Fatalf("bad number of parts on line %d", lineNo)
|
||||||
|
}
|
||||||
|
|
||||||
|
privBytes, _ := hex.DecodeString(parts[0])
|
||||||
|
pubKey, _ := hex.DecodeString(parts[1])
|
||||||
|
msg, _ := hex.DecodeString(parts[2])
|
||||||
|
sig, _ := hex.DecodeString(parts[3])
|
||||||
|
// The signatures in the test vectors also include the message
|
||||||
|
// at the end, but we just want R and S.
|
||||||
|
sig = sig[:SignatureSize]
|
||||||
|
|
||||||
|
if l := len(pubKey); l != PublicKeySize {
|
||||||
|
t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
var otherPriv [PrivateKeySize]byte
|
||||||
|
copy(otherPriv[:], privBytes)
|
||||||
|
copy(otherPriv[32:], pubKey)
|
||||||
|
priv := FromCryptoPrivateKey(otherPriv[:])
|
||||||
|
|
||||||
|
sig2 := Sign(priv, msg)
|
||||||
|
if !bytes.Equal(sig, sig2[:]) {
|
||||||
|
t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Verify(pubKey, msg, sig2) {
|
||||||
|
t.Errorf("signature failed to verify on line %d", lineNo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
t.Fatalf("error reading test data: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMalleability(t *testing.T) {
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
if Verify(publicKey, msg, sig) {
|
||||||
|
t.Fatal("non-canonical signature accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkKeyGeneration(b *testing.B) {
|
||||||
|
var zero zeroReader
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if _, err := GenerateKey(zero); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSigning(b *testing.B) {
|
||||||
|
var zero zeroReader
|
||||||
|
pair, err := GenerateKey(zero)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
message := []byte("Hello, world!")
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Sign(pair, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkVerification(b *testing.B) {
|
||||||
|
var zero zeroReader
|
||||||
|
pair, err := GenerateKey(zero)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
message := []byte("Hello, world!")
|
||||||
|
signature := Sign(pair, message)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Verify(pair.PublicKey(), message, signature)
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
|
@ -17,12 +17,12 @@ var serviceIDEncoding = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||||
|
|
||||||
// OnionServiceIDFromPrivateKey generates the onion service ID from the given
|
// OnionServiceIDFromPrivateKey generates the onion service ID from the given
|
||||||
// private key. This panics if the private key is not a crypto/*rsa.PrivateKey
|
// private key. This panics if the private key is not a crypto/*rsa.PrivateKey
|
||||||
// or github.com/cretz/bine/torutil/ed25519.PrivateKey.
|
// or github.com/cretz/bine/torutil/ed25519.KeyPair.
|
||||||
func OnionServiceIDFromPrivateKey(key crypto.PrivateKey) string {
|
func OnionServiceIDFromPrivateKey(key crypto.PrivateKey) string {
|
||||||
switch k := key.(type) {
|
switch k := key.(type) {
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
return OnionServiceIDFromV2PublicKey(&k.PublicKey)
|
return OnionServiceIDFromV2PublicKey(&k.PublicKey)
|
||||||
case ed25519.PrivateKey:
|
case ed25519.KeyPair:
|
||||||
return OnionServiceIDFromV3PublicKey(k.PublicKey())
|
return OnionServiceIDFromV3PublicKey(k.PublicKey())
|
||||||
}
|
}
|
||||||
panic(fmt.Sprintf("Unrecognized private key type: %T", key))
|
panic(fmt.Sprintf("Unrecognized private key type: %T", key))
|
||||||
|
|
Loading…
Reference in New Issue