From 8bef5ea4c57af438a43879ce8de2b992b248b88b Mon Sep 17 00:00:00 2001 From: Moreno Ambrosin Date: Wed, 11 Sep 2024 01:54:08 -0700 Subject: [PATCH] Add ED25519 private key #tinkApiChange PiperOrigin-RevId: 673293972 Change-Id: I48f63bc30ba40c249b4da75897b37d810e950d37 --- signature/ed25519/BUILD.bazel | 4 + signature/ed25519/key.go | 75 ++++++- signature/ed25519/key_test.go | 379 +++++++++++++++++++++++++++++++++- 3 files changed, 454 insertions(+), 4 deletions(-) diff --git a/signature/ed25519/BUILD.bazel b/signature/ed25519/BUILD.bazel index b67fb0c..ddd7605 100644 --- a/signature/ed25519/BUILD.bazel +++ b/signature/ed25519/BUILD.bazel @@ -12,12 +12,14 @@ go_library( visibility = ["//visibility:public"], deps = [ "//core/registry", + "//insecuresecretdataaccess", "//internal/internalregistry", "//internal/outputprefix", "//key", "//keyset", "//proto/ed25519_go_proto", "//proto/tink_go_proto", + "//secretdata", "//signature/subtle", "@org_golang_google_protobuf//proto", ], @@ -40,9 +42,11 @@ go_test( ":ed25519", "//core/cryptofmt", "//core/registry", + "//insecuresecretdataaccess", "//internal/internalregistry", "//proto/ed25519_go_proto", "//proto/tink_go_proto", + "//secretdata", "//signature/subtle", "//subtle/random", "//testutil", diff --git a/signature/ed25519/key.go b/signature/ed25519/key.go index 277573a..0a7fc29 100644 --- a/signature/ed25519/key.go +++ b/signature/ed25519/key.go @@ -19,8 +19,10 @@ import ( "crypto/ed25519" "fmt" + "github.com/tink-crypto/tink-go/v2/insecuresecretdataaccess" "github.com/tink-crypto/tink-go/v2/internal/outputprefix" "github.com/tink-crypto/tink-go/v2/key" + "github.com/tink-crypto/tink-go/v2/secretdata" ) // Variant is the prefix variant of an ED25519 key. @@ -124,7 +126,7 @@ func NewPublicKey(keyBytes []byte, idRequirement uint32, params Parameters) (*Pu if !params.HasIDRequirement() && idRequirement != 0 { return nil, fmt.Errorf("ed25519.NewPublicKey: idRequirement must be zero if params doesn't have an ID requirement") } - if len(keyBytes) != ed25519.PublicKeySize { + if len(keyBytes) != 32 { return nil, fmt.Errorf("ed25519.NewPublicKey: keyBytes must be 32 bytes") } outputPrefix, err := calculateOutputPrefix(params.variant, idRequirement) @@ -164,3 +166,74 @@ func (k *PublicKey) Equals(other key.Key) bool { bytes.Equal(k.keyBytes, that.keyBytes) && k.idRequirement == that.idRequirement } + +// PrivateKey represents an ED25519 private key. +type PrivateKey struct { + publicKey *PublicKey + keyBytes secretdata.Bytes +} + +var _ key.Key = (*PrivateKey)(nil) + +// NewPrivateKey creates a new ED25519 private key from privateKeyBytes, with +// idRequirement and params. +func NewPrivateKey(privateKeyBytes secretdata.Bytes, idRequirement uint32, params Parameters) (*PrivateKey, error) { + if privateKeyBytes.Len() != ed25519.SeedSize { + return nil, fmt.Errorf("ed25519.NewPrivateKey: privateKeyBytes must be 32 bytes") + } + privKey := ed25519.NewKeyFromSeed(privateKeyBytes.Data(insecuresecretdataaccess.Token{})) + pubKeyBytes := privKey.Public().(ed25519.PublicKey) + pubKey, err := NewPublicKey(pubKeyBytes, idRequirement, params) + if err != nil { + return nil, fmt.Errorf("ed25519.NewPrivateKey: %w", err) + } + return &PrivateKey{ + publicKey: pubKey, + keyBytes: privateKeyBytes, + }, nil +} + +// NewPrivateKeyWithPublicKey creates a new ED25519 private key from +// privateKeyBytes and a [PublicKey]. +func NewPrivateKeyWithPublicKey(privateKeyBytes secretdata.Bytes, pubKey *PublicKey) (*PrivateKey, error) { + if pubKey == nil { + return nil, fmt.Errorf("ed25519.NewPrivateKeyWithPublicKey: pubKey must not be nil") + } + if privateKeyBytes.Len() != ed25519.SeedSize { + return nil, fmt.Errorf("ed25519.NewPrivateKey: seed must be 32 bytes") + } + // Make sure the public key is correct. + privKey := ed25519.NewKeyFromSeed(privateKeyBytes.Data(insecuresecretdataaccess.Token{})) + if !bytes.Equal(privKey.Public().(ed25519.PublicKey), pubKey.KeyBytes()) { + return nil, fmt.Errorf("ed25519.NewPrivateKeyWithPublicKey: public key does not match private key") + } + return &PrivateKey{ + publicKey: pubKey, + keyBytes: privateKeyBytes, + }, nil +} + +// PrivateKeyBytes returns the private key bytes. +func (k *PrivateKey) PrivateKeyBytes() secretdata.Bytes { return k.keyBytes } + +// PublicKey returns the public key of the key. +func (k *PrivateKey) PublicKey() *PublicKey { return k.publicKey } + +// Parameters returns the parameters of the key. +func (k *PrivateKey) Parameters() key.Parameters { return &k.publicKey.params } + +// IDRequirement returns the ID requirement of the key, and whether it is +// required. +func (k *PrivateKey) IDRequirement() (uint32, bool) { return k.publicKey.IDRequirement() } + +// OutputPrefix returns the output prefix of this key. +func (k *PrivateKey) OutputPrefix() []byte { return bytes.Clone(k.publicKey.outputPrefix) } + +// Equals returns true if this key is equal to other. +func (k *PrivateKey) Equals(other key.Key) bool { + if k == other { + return true + } + that, ok := other.(*PrivateKey) + return ok && k.publicKey.Equals(that.PublicKey()) && k.keyBytes.Equals(that.keyBytes) +} diff --git a/signature/ed25519/key_test.go b/signature/ed25519/key_test.go index c73186b..26867a9 100644 --- a/signature/ed25519/key_test.go +++ b/signature/ed25519/key_test.go @@ -16,10 +16,12 @@ package ed25519_test import ( "bytes" - "fmt" + "encoding/hex" "testing" "github.com/tink-crypto/tink-go/v2/core/cryptofmt" + "github.com/tink-crypto/tink-go/v2/insecuresecretdataaccess" + "github.com/tink-crypto/tink-go/v2/secretdata" "github.com/tink-crypto/tink-go/v2/signature/ed25519" ) @@ -364,7 +366,6 @@ func TestPublicKeyEqualsFalse(t *testing.T) { if err != nil { t.Fatalf("ed25519.NewPublicKey(%v, %v, %v) err = %v, want nil", tc.firstKey.keyBytes, tc.firstKey.idRequirement, firstParams, err) } - secondParams, err := ed25519.NewParameters(tc.secondKey.variant) if err != nil { t.Fatalf("ed25519.NewParameters(%v) err = %v, want nil", tc.secondKey.variant, err) @@ -396,7 +397,6 @@ func TestPublicKeyKeyBytes(t *testing.T) { } // Make sure a copy is made when creating the public key. keyBytes[0] = 0x99 - fmt.Println(keyBytes) if bytes.Equal(pubKey.KeyBytes(), keyBytes) { t.Errorf("bytes.Equal(pubKey.KeyBytes(), keyBytes) = true, want false") } @@ -406,3 +406,376 @@ func TestPublicKeyKeyBytes(t *testing.T) { t.Errorf("bytes.Equal((pubKey.KeyBytes(), gotPubKeyBytes) = true, want false") } } + +const ( + // Taken from + // https://github.com/google/boringssl/blob/f10c1dc37174843c504a80e94c252e35b7b1eb61/crypto/evp/evp_tests.txt#L178 + privKeyHex = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + pubKeyHex = "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a" +) + +var testCases = []struct { + name string + variant ed25519.Variant + privKeyBytesHex string + pubKeyBytesHex string + idRequirement uint32 + wantOutputPrefix []byte +}{ + { + name: "tink", + variant: ed25519.VariantTink, + privKeyBytesHex: privKeyHex, + pubKeyBytesHex: pubKeyHex, + idRequirement: uint32(0x01020304), + wantOutputPrefix: []byte{cryptofmt.TinkStartByte, 0x01, 0x02, 0x03, 0x04}, + }, + { + name: "crunchy", + variant: ed25519.VariantCrunchy, + privKeyBytesHex: privKeyHex, + pubKeyBytesHex: pubKeyHex, + idRequirement: uint32(0x01020304), + wantOutputPrefix: []byte{cryptofmt.LegacyStartByte, 0x01, 0x02, 0x03, 0x04}, + }, + { + name: "legacy", + variant: ed25519.VariantLegacy, + privKeyBytesHex: privKeyHex, + pubKeyBytesHex: pubKeyHex, + idRequirement: uint32(0x01020304), + wantOutputPrefix: []byte{cryptofmt.LegacyStartByte, 0x01, 0x02, 0x03, 0x04}, + }, + { + name: "no prefix", + variant: ed25519.VariantNoPrefix, + privKeyBytesHex: privKeyHex, + pubKeyBytesHex: pubKeyHex, + idRequirement: 0, + wantOutputPrefix: nil, + }, +} + +func TestPrivateKeyNewPrivateKeyWithPublicKey(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + params, err := ed25519.NewParameters(tc.variant) + if err != nil { + t.Fatalf("ed25519.NewParameters(%v) err = %v, want nil", tc.variant, err) + } + pubKeyBytes, privKeyBytes := getTestKeyPair(t) + pubKey, err := ed25519.NewPublicKey(pubKeyBytes, tc.idRequirement, params) + if err != nil { + t.Fatalf("ed25519.NewPublicKey(%v, %v, %v) err = %v, want nil", pubKeyBytes, tc.idRequirement, params, err) + } + secretSeed := secretdata.NewBytesFromData(privKeyBytes, insecuresecretdataaccess.Token{}) + privKey, err := ed25519.NewPrivateKeyWithPublicKey(secretSeed, pubKey) + if err != nil { + t.Fatalf("ed25519.NewPrivateKeyWithPublicKey(%v, %v) err = %v, want nil", secretSeed, pubKey, err) + } + + // Test IDRequirement. + gotIDRequrement, gotRequired := privKey.IDRequirement() + if got, want := gotRequired, params.HasIDRequirement(); got != want { + t.Errorf("params.HasIDRequirement() = %v, want %v", got, want) + } + if got, want := gotIDRequrement, tc.idRequirement; got != want { + t.Errorf("params.IDRequirement() = %v, want %v", got, want) + } + + // Test OutputPrefix. + if got := privKey.OutputPrefix(); !bytes.Equal(got, tc.wantOutputPrefix) { + t.Errorf("params.OutputPrefix() = %v, want %v", got, tc.wantOutputPrefix) + } + + // Test Equals. + otherPubKey, err := ed25519.NewPublicKey(pubKeyBytes, tc.idRequirement, params) + if err != nil { + t.Fatalf("ed25519.NewPublicKey(%v, %v, %v) err = %v, want nil", pubKeyBytes, tc.idRequirement, params, err) + } + otherPrivKey, err := ed25519.NewPrivateKeyWithPublicKey(secretSeed, otherPubKey) + if err != nil { + t.Fatalf("ed25519.NewPrivateKeyWithPublicKey(%v, %v) err = %v, want nil", secretSeed, pubKey, err) + } + if !otherPrivKey.Equals(privKey) { + t.Errorf("otherPrivKey.Equals(privKey) = false, want true") + } + + // Test PublicKey. + if got := privKey.PublicKey(); !got.Equals(pubKey) { + t.Errorf("privKey.PublicKey().Equals(pubKey) = false, want true") + } + + // Test Parameters. + if got := privKey.Parameters(); !got.Equals(¶ms) { + t.Errorf("privKey.Parameters().Equals(¶ms) = false, want true") + } + }) + } +} + +func TestPrivateKeyNewPrivateKey(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + params, err := ed25519.NewParameters(tc.variant) + if err != nil { + t.Fatalf("ed25519.NewParameters(%v) err = %v, want nil", tc.variant, err) + } + pubKeyBytes, privKeyBytes := getTestKeyPair(t) + secretSeed := secretdata.NewBytesFromData(privKeyBytes, insecuresecretdataaccess.Token{}) + privKey, err := ed25519.NewPrivateKey(secretSeed, tc.idRequirement, params) + if err != nil { + t.Fatalf("ed25519.NewPrivateKey(%v, %v, %v) err = %v, want nil", secretSeed, tc.idRequirement, params, err) + } + + // Test IDRequirement. + gotIDRequrement, gotRequired := privKey.IDRequirement() + if got, want := gotRequired, params.HasIDRequirement(); got != want { + t.Errorf("params.HasIDRequirement() = %v, want %v", got, want) + } + if got, want := gotIDRequrement, tc.idRequirement; got != want { + t.Errorf("params.IDRequirement() = %v, want %v", got, want) + } + + // Test OutputPrefix. + if got := privKey.OutputPrefix(); !bytes.Equal(got, tc.wantOutputPrefix) { + t.Errorf("params.OutputPrefix() = %v, want %v", got, tc.wantOutputPrefix) + } + + // Test Equals. + otherPrivKey, err := ed25519.NewPrivateKey(secretSeed, tc.idRequirement, params) + if err != nil { + t.Fatalf("ed25519.NewPrivateKey(%v, %v, %v) err = %v, want nil", secretSeed, tc.idRequirement, params, err) + } + if !otherPrivKey.Equals(privKey) { + t.Errorf("otherPrivKey.Equals(privKey) = false, want true") + } + + // Test PublicKey. + pubKey, err := ed25519.NewPublicKey(pubKeyBytes, tc.idRequirement, params) + if err != nil { + t.Fatalf("ed25519.NewPublicKey(%v, %v, %v) err = %v, want nil", pubKeyBytes, tc.idRequirement, params, err) + } + if got := privKey.PublicKey(); !got.Equals(pubKey) { + t.Errorf("privKey.PublicKey().Equals(pubKey) = false, want true") + } + + // Test Parameters. + if got := privKey.Parameters(); !got.Equals(¶ms) { + t.Errorf("privKey.Parameters().Equals(¶ms) = false, want true") + } + }) + } +} + +func TestNewPrivateKeyFails(t *testing.T) { + paramsTink, err := ed25519.NewParameters(ed25519.VariantTink) + if err != nil { + t.Fatalf("ed25519.NewParameters(%v) err = %v, want nil", ed25519.VariantTink, err) + } + paramsNoPrefix, err := ed25519.NewParameters(ed25519.VariantNoPrefix) + if err != nil { + t.Fatalf("ed25519.NewParameters(%v) err = %v, want nil", ed25519.VariantNoPrefix, err) + } + for _, tc := range []struct { + name string + params ed25519.Parameters + idRequrement uint32 + privKeyBytes secretdata.Bytes + }{ + { + name: "nil private key bytes", + params: paramsTink, + idRequrement: 123, + privKeyBytes: secretdata.NewBytesFromData(nil, insecuresecretdataaccess.Token{}), + }, + { + name: "invalid private key bytes size", + params: paramsTink, + idRequrement: 123, + privKeyBytes: secretdata.NewBytesFromData([]byte("123"), insecuresecretdataaccess.Token{}), + }, + { + name: "empty params", + params: ed25519.Parameters{}, + idRequrement: 123, + privKeyBytes: secretdata.NewBytesFromData([]byte("12345678123456781234567812345678"), insecuresecretdataaccess.Token{}), + }, + { + name: "invalid ID requiremet", + idRequrement: 123, + params: paramsNoPrefix, + privKeyBytes: secretdata.NewBytesFromData([]byte("12345678123456781234567812345678"), insecuresecretdataaccess.Token{}), + }, + } { + t.Run(tc.name, func(t *testing.T) { + if _, err := ed25519.NewPrivateKey(tc.privKeyBytes, tc.idRequrement, tc.params); err == nil { + t.Errorf("ed25519.NewPrivateKey(%v, %v, %v) err = nil, want error", tc.privKeyBytes, tc.idRequrement, tc.params) + } + }) + } +} + +func getTestKeyPair(t *testing.T) ([]byte, []byte) { + t.Helper() + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + if err != nil { + t.Fatalf("hex.DecodeString(pubKeyHex) err = %v, want nil", err) + } + privKeyBytes, err := hex.DecodeString(privKeyHex) + if err != nil { + t.Fatalf("hex.DecodeString(privKeyHex) err = %v, want nil", err) + } + return pubKeyBytes, privKeyBytes +} + +func TestNewPrivateKeyWithPublicKeyFails(t *testing.T) { + params, err := ed25519.NewParameters(ed25519.VariantTink) + if err != nil { + t.Fatalf("ed25519.NewParameters(%v) err = %v, want nil", ed25519.VariantTink, err) + } + pubKeyBytes, privKeyBytes := getTestKeyPair(t) + pubKey, err := ed25519.NewPublicKey(pubKeyBytes, 123, params) + if err != nil { + t.Fatalf("ed25519.NewPublicKey(%v, %v, %v) err = %v, want nil", pubKeyBytes, 123, params, err) + } + for _, tc := range []struct { + name string + pubKey *ed25519.PublicKey + privateKeyBytes secretdata.Bytes + }{ + { + name: "nil private key bytes", + pubKey: pubKey, + privateKeyBytes: secretdata.NewBytesFromData(nil, insecuresecretdataaccess.Token{}), + }, + { + name: "invalid private key bytes size", + pubKey: pubKey, + privateKeyBytes: secretdata.NewBytesFromData([]byte("123"), insecuresecretdataaccess.Token{}), + }, + { + name: "empty public key", + pubKey: &ed25519.PublicKey{}, + privateKeyBytes: secretdata.NewBytesFromData(privKeyBytes, insecuresecretdataaccess.Token{}), + }, + { + name: "nil public key", + pubKey: nil, + privateKeyBytes: secretdata.NewBytesFromData(privKeyBytes, insecuresecretdataaccess.Token{}), + }, + { + name: "invalid public key", + pubKey: pubKey, + privateKeyBytes: secretdata.NewBytesFromData([]byte("12345678123456781234567812345678"), insecuresecretdataaccess.Token{}), + }, + } { + t.Run(tc.name, func(t *testing.T) { + if _, err := ed25519.NewPrivateKeyWithPublicKey(tc.privateKeyBytes, tc.pubKey); err == nil { + t.Errorf("ed25519.NewPrivateKeyWithPublicKey(%v, %v) err = nil, want error", tc.privateKeyBytes, tc.pubKey) + } + }) + } +} + +func TestPrivateKeyEqualsSelf(t *testing.T) { + params, err := ed25519.NewParameters(ed25519.VariantTink) + if err != nil { + t.Fatalf("ed25519.NewParameters(%v) err = %v, want nil", ed25519.VariantTink, err) + } + pubKeyBytes, privKeyBytes := getTestKeyPair(t) + pubKey, err := ed25519.NewPublicKey(pubKeyBytes, 123, params) + if err != nil { + t.Fatalf("ed25519.NewPublicKey(%v, %v, %v) err = %v", pubKeyBytes, 123, params, err) + } + secretSeed := secretdata.NewBytesFromData(privKeyBytes, insecuresecretdataaccess.Token{}) + privKey, err := ed25519.NewPrivateKeyWithPublicKey(secretSeed, pubKey) + if err != nil { + t.Fatalf("ed25519.NewPrivateKeyWithPublicKey(%v, %v) err = %v", secretSeed, pubKey, err) + } + if !privKey.Equals(privKey) { + t.Errorf("privKey.Equals(privKey) = false, want true") + } +} + +func TestPrivateKeyEqualsFalse(t *testing.T) { + paramsTink, err := ed25519.NewParameters(ed25519.VariantTink) + if err != nil { + t.Fatalf("ed25519.NewParameters(%v) err = %v, want nil", ed25519.VariantTink, err) + } + paramsCrunchy, err := ed25519.NewParameters(ed25519.VariantCrunchy) + if err != nil { + t.Fatalf("ed25519.NewParameters(%v) err = %v, want nil", ed25519.VariantCrunchy, err) + } + for _, tc := range []struct { + name string + privKeyBytes1 secretdata.Bytes + params1 ed25519.Parameters + idRequirement1 uint32 + privKeyBytes2 secretdata.Bytes + params2 ed25519.Parameters + idRequirement2 uint32 + }{ + { + name: "different private key bytes", + privKeyBytes1: secretdata.NewBytesFromData([]byte("12345678123456781234567812345678"), insecuresecretdataaccess.Token{}), + params1: paramsTink, + idRequirement1: 123, + privKeyBytes2: secretdata.NewBytesFromData([]byte("12345678123456781234567812345679"), insecuresecretdataaccess.Token{}), + params2: paramsTink, + idRequirement2: 123, + }, + { + name: "different ID requirement", + privKeyBytes1: secretdata.NewBytesFromData([]byte("12345678123456781234567812345678"), insecuresecretdataaccess.Token{}), + params1: paramsTink, + idRequirement1: 123, + privKeyBytes2: secretdata.NewBytesFromData([]byte("12345678123456781234567812345678"), insecuresecretdataaccess.Token{}), + params2: paramsTink, + idRequirement2: 456, + }, + { + name: "different params", + privKeyBytes1: secretdata.NewBytesFromData([]byte("12345678123456781234567812345678"), insecuresecretdataaccess.Token{}), + params1: paramsTink, + idRequirement1: 123, + privKeyBytes2: secretdata.NewBytesFromData([]byte("12345678123456781234567812345678"), insecuresecretdataaccess.Token{}), + params2: paramsCrunchy, + idRequirement2: 123, + }, + } { + t.Run(tc.name, func(t *testing.T) { + firstPrivKey, err := ed25519.NewPrivateKey(tc.privKeyBytes1, tc.idRequirement1, tc.params1) + if err != nil { + t.Fatalf("ed25519.NewPrivateKey(%v, %v, %v) err = %v", tc.privKeyBytes1, tc.idRequirement1, tc.params1, err) + } + secondPrivKey, err := ed25519.NewPrivateKey(tc.privKeyBytes2, tc.idRequirement2, tc.params2) + if err != nil { + t.Fatalf("ed25519.NewPrivateKey(%v, %v, %v) err = %v", tc.privKeyBytes2, tc.idRequirement2, tc.params2, err) + } + if firstPrivKey.Equals(secondPrivKey) { + t.Errorf("firstPrivKey.Equals(secondPrivKey) = true, want false") + } + }) + } +} + +func TestPrivateKeyKeyBytes(t *testing.T) { + pubKeyBytes, privKeyBytes := getTestKeyPair(t) + params, err := ed25519.NewParameters(ed25519.VariantTink) + if err != nil { + t.Fatalf("ed25519.NewParameters(%v) err = %v, want nil", ed25519.VariantTink, err) + } + pubKey, err := ed25519.NewPublicKey([]byte(pubKeyBytes), 123, params) + if err != nil { + t.Fatalf("ed25519.NewPublicKey(%v, %v, %v) err = %v, want nil", []byte(pubKeyBytes), 123, params, err) + } + secretSeed := secretdata.NewBytesFromData([]byte(privKeyBytes), insecuresecretdataaccess.Token{}) + privKey, err := ed25519.NewPrivateKeyWithPublicKey(secretSeed, pubKey) + if err != nil { + t.Fatalf("ed25519.NewPrivateKeyWithPublicKey(%v, %v) err = %v, want nil", secretSeed, pubKey, err) + } + if got, want := privKey.PrivateKeyBytes().Data(insecuresecretdataaccess.Token{}), []byte(privKeyBytes); !bytes.Equal(got, want) { + t.Errorf("bytes.Equal(got, want) = false, want true") + } +}