From 368f53b380a4ac2d53f8f9ad418aa6ee7be247f1 Mon Sep 17 00:00:00 2001 From: Baha <29608896+Baha-sk@users.noreply.github.com> Date: Fri, 21 Oct 2022 18:42:15 -0400 Subject: [PATCH] feat: support secp256k1 curve in KMS (#3411) This change adds secp256k1 keys support in the kms for the IEEE-P1363 format only. DER format is not supported because the x509 package does not support this curve. The framework does support the creation of an secp256k1 key with DER format, however storing it in the KMS is not supported. closes #3410 Signed-off-by: Baha Shaaban Signed-off-by: Baha Shaaban --- pkg/crypto/tinkcrypto/crypto_test.go | 33 ++ .../proto/secp256k1_go_proto/secp256k1.pb.go | 541 ++++++++++++++++++ .../primitive/secp256k1/secp256k1.go | 29 + .../secp256k1/secp256k1_key_templates.go | 64 +++ .../secp256k1/secp256k1_key_templates_test.go | 108 ++++ .../secp256k1/secp256k1_signer_key_manager.go | 220 +++++++ .../secp256k1_signer_key_manager_test.go | 427 ++++++++++++++ .../secp256k1_verifier_key_manager.go | 95 +++ .../secp256k1_verifier_key_manager_test.go | 74 +++ .../primitive/secp256k1/signer_factory.go | 86 +++ .../primitive/secp256k1/subtle/encoding.go | 106 ++++ .../primitive/secp256k1/subtle/secp256k1.go | 112 ++++ .../secp256k1/subtle/secp256k1_signer.go | 94 +++ .../subtle/secp256k1_signer_verifier_test.go | 56 ++ .../secp256k1/subtle/secp256k1_verifier.go | 79 +++ .../primitive/secp256k1/subtle/subtle.go | 8 + .../primitive/secp256k1/verifier_factory.go | 116 ++++ pkg/crypto/webkms/remotecrypto.go | 65 ++- pkg/doc/jwt/verifier.go | 2 +- pkg/doc/util/jwkkid/kid_creator.go | 115 +++- pkg/doc/util/jwkkid/kid_creator_test.go | 22 + .../internal/signer/crypto_signer.go | 4 + pkg/doc/util/signature/signer.go | 2 +- pkg/kms/api.go | 4 + pkg/kms/localkms/keytemplate.go | 7 +- pkg/kms/localkms/kid_creator_test.go | 102 +++- pkg/kms/localkms/localkms.go | 36 +- pkg/kms/localkms/localkms_test.go | 28 +- pkg/kms/localkms/privkey_import.go | 50 +- pkg/kms/localkms/pubkey_reader.go | 86 +++ pkg/kms/localkms/pubkey_writer.go | 63 +- 31 files changed, 2746 insertions(+), 88 deletions(-) create mode 100755 pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto/secp256k1.pb.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_key_templates.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_key_templates_test.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_signer_key_manager.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_signer_key_manager_test.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_verifier_key_manager.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_verifier_key_manager_test.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/signer_factory.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/encoding.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_signer.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_signer_verifier_test.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_verifier.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/subtle.go create mode 100644 pkg/crypto/tinkcrypto/primitive/secp256k1/verifier_factory.go diff --git a/pkg/crypto/tinkcrypto/crypto_test.go b/pkg/crypto/tinkcrypto/crypto_test.go index 3054da56a..ebc09a648 100644 --- a/pkg/crypto/tinkcrypto/crypto_test.go +++ b/pkg/crypto/tinkcrypto/crypto_test.go @@ -31,6 +31,7 @@ import ( "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh" "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/keyio" ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/secp256k1" ) const testMessage = "test message" @@ -238,6 +239,38 @@ func TestCrypto_SignVerify(t *testing.T) { err = c.Verify(s, msg, badKH) require.Error(t, err) }) + + t.Run("test with secp256k1 signature", func(t *testing.T) { + derTemplate, err := secp256k1.DERKeyTemplate() + require.NoError(t, err) + + kh, err := keyset.NewHandle(derTemplate) + require.NoError(t, err) + + badKH, err := keyset.NewHandle(tinkaead.KMSEnvelopeAEADKeyTemplate("babdUrl", nil)) + require.NoError(t, err) + + c := Crypto{} + msg := []byte(testMessage) + s, err := c.Sign(msg, kh) + require.NoError(t, err) + + // get corresponding public key handle to verify + pubKH, err := kh.Public() + require.NoError(t, err) + + err = c.Verify(s, msg, pubKH) + require.NoError(t, err) + + // verify with nil key handle - should fail + err = c.Verify(s, msg, nil) + require.Error(t, err) + require.Equal(t, errBadKeyHandleFormat, err) + + // verify with bad key handle - should fail + err = c.Verify(s, msg, badKH) + require.Error(t, err) + }) } func TestCrypto_ComputeMAC(t *testing.T) { diff --git a/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto/secp256k1.pb.go b/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto/secp256k1.pb.go new file mode 100755 index 000000000..ffeef354c --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto/secp256k1.pb.go @@ -0,0 +1,541 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.14.0 +// source: proto/secp256k1.proto + +package secp256k1_go_proto + +import ( + proto "github.com/golang/protobuf/proto" + common_go_proto "github.com/google/tink/go/proto/common_go_proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type BitcoinCurveType int32 + +const ( + BitcoinCurveType_INVALID_BITCOIN_CURVE BitcoinCurveType = 0 + BitcoinCurveType_SECP256K1 BitcoinCurveType = 2 +) + +// Enum value maps for BitcoinCurveType. +var ( + BitcoinCurveType_name = map[int32]string{ + 0: "INVALID_BITCOIN_CURVE", + 2: "SECP256K1", + } + BitcoinCurveType_value = map[string]int32{ + "INVALID_BITCOIN_CURVE": 0, + "SECP256K1": 2, + } +) + +func (x BitcoinCurveType) Enum() *BitcoinCurveType { + p := new(BitcoinCurveType) + *p = x + return p +} + +func (x BitcoinCurveType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (BitcoinCurveType) Descriptor() protoreflect.EnumDescriptor { + return file_proto_secp256k1_proto_enumTypes[0].Descriptor() +} + +func (BitcoinCurveType) Type() protoreflect.EnumType { + return &file_proto_secp256k1_proto_enumTypes[0] +} + +func (x BitcoinCurveType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use BitcoinCurveType.Descriptor instead. +func (BitcoinCurveType) EnumDescriptor() ([]byte, []int) { + return file_proto_secp256k1_proto_rawDescGZIP(), []int{0} +} + +type Secp256K1SignatureEncoding int32 + +const ( + Secp256K1SignatureEncoding_UNKNOWN_BITCOIN_ENCODING Secp256K1SignatureEncoding = 0 + Secp256K1SignatureEncoding_Bitcoin_IEEE_P1363 Secp256K1SignatureEncoding = 1 + Secp256K1SignatureEncoding_Bitcoin_DER Secp256K1SignatureEncoding = 2 +) + +// Enum value maps for Secp256K1SignatureEncoding. +var ( + Secp256K1SignatureEncoding_name = map[int32]string{ + 0: "UNKNOWN_BITCOIN_ENCODING", + 1: "Bitcoin_IEEE_P1363", + 2: "Bitcoin_DER", + } + Secp256K1SignatureEncoding_value = map[string]int32{ + "UNKNOWN_BITCOIN_ENCODING": 0, + "Bitcoin_IEEE_P1363": 1, + "Bitcoin_DER": 2, + } +) + +func (x Secp256K1SignatureEncoding) Enum() *Secp256K1SignatureEncoding { + p := new(Secp256K1SignatureEncoding) + *p = x + return p +} + +func (x Secp256K1SignatureEncoding) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Secp256K1SignatureEncoding) Descriptor() protoreflect.EnumDescriptor { + return file_proto_secp256k1_proto_enumTypes[1].Descriptor() +} + +func (Secp256K1SignatureEncoding) Type() protoreflect.EnumType { + return &file_proto_secp256k1_proto_enumTypes[1] +} + +func (x Secp256K1SignatureEncoding) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Secp256K1SignatureEncoding.Descriptor instead. +func (Secp256K1SignatureEncoding) EnumDescriptor() ([]byte, []int) { + return file_proto_secp256k1_proto_rawDescGZIP(), []int{1} +} + +type Secp256K1Params struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + HashType common_go_proto.HashType `protobuf:"varint,1,opt,name=hash_type,json=hashType,proto3,enum=google.crypto.tink.HashType" json:"hash_type,omitempty"` + Curve BitcoinCurveType `protobuf:"varint,2,opt,name=curve,proto3,enum=google.crypto.tink.BitcoinCurveType" json:"curve,omitempty"` + Encoding Secp256K1SignatureEncoding `protobuf:"varint,3,opt,name=encoding,proto3,enum=google.crypto.tink.Secp256K1SignatureEncoding" json:"encoding,omitempty"` +} + +func (x *Secp256K1Params) Reset() { + *x = Secp256K1Params{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_secp256k1_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Secp256K1Params) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Secp256K1Params) ProtoMessage() {} + +func (x *Secp256K1Params) ProtoReflect() protoreflect.Message { + mi := &file_proto_secp256k1_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Secp256K1Params.ProtoReflect.Descriptor instead. +func (*Secp256K1Params) Descriptor() ([]byte, []int) { + return file_proto_secp256k1_proto_rawDescGZIP(), []int{0} +} + +func (x *Secp256K1Params) GetHashType() common_go_proto.HashType { + if x != nil { + return x.HashType + } + return common_go_proto.HashType_UNKNOWN_HASH +} + +func (x *Secp256K1Params) GetCurve() BitcoinCurveType { + if x != nil { + return x.Curve + } + return BitcoinCurveType_INVALID_BITCOIN_CURVE +} + +func (x *Secp256K1Params) GetEncoding() Secp256K1SignatureEncoding { + if x != nil { + return x.Encoding + } + return Secp256K1SignatureEncoding_UNKNOWN_BITCOIN_ENCODING +} + +type Secp256K1PublicKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + Params *Secp256K1Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"` + X []byte `protobuf:"bytes,3,opt,name=x,proto3" json:"x,omitempty"` + Y []byte `protobuf:"bytes,4,opt,name=y,proto3" json:"y,omitempty"` +} + +func (x *Secp256K1PublicKey) Reset() { + *x = Secp256K1PublicKey{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_secp256k1_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Secp256K1PublicKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Secp256K1PublicKey) ProtoMessage() {} + +func (x *Secp256K1PublicKey) ProtoReflect() protoreflect.Message { + mi := &file_proto_secp256k1_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Secp256K1PublicKey.ProtoReflect.Descriptor instead. +func (*Secp256K1PublicKey) Descriptor() ([]byte, []int) { + return file_proto_secp256k1_proto_rawDescGZIP(), []int{1} +} + +func (x *Secp256K1PublicKey) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Secp256K1PublicKey) GetParams() *Secp256K1Params { + if x != nil { + return x.Params + } + return nil +} + +func (x *Secp256K1PublicKey) GetX() []byte { + if x != nil { + return x.X + } + return nil +} + +func (x *Secp256K1PublicKey) GetY() []byte { + if x != nil { + return x.Y + } + return nil +} + +type Secp256K1PrivateKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + PublicKey *Secp256K1PublicKey `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + KeyValue []byte `protobuf:"bytes,3,opt,name=key_value,json=keyValue,proto3" json:"key_value,omitempty"` +} + +func (x *Secp256K1PrivateKey) Reset() { + *x = Secp256K1PrivateKey{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_secp256k1_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Secp256K1PrivateKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Secp256K1PrivateKey) ProtoMessage() {} + +func (x *Secp256K1PrivateKey) ProtoReflect() protoreflect.Message { + mi := &file_proto_secp256k1_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Secp256K1PrivateKey.ProtoReflect.Descriptor instead. +func (*Secp256K1PrivateKey) Descriptor() ([]byte, []int) { + return file_proto_secp256k1_proto_rawDescGZIP(), []int{2} +} + +func (x *Secp256K1PrivateKey) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Secp256K1PrivateKey) GetPublicKey() *Secp256K1PublicKey { + if x != nil { + return x.PublicKey + } + return nil +} + +func (x *Secp256K1PrivateKey) GetKeyValue() []byte { + if x != nil { + return x.KeyValue + } + return nil +} + +type Secp256K1KeyFormat struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Params *Secp256K1Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"` +} + +func (x *Secp256K1KeyFormat) Reset() { + *x = Secp256K1KeyFormat{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_secp256k1_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Secp256K1KeyFormat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Secp256K1KeyFormat) ProtoMessage() {} + +func (x *Secp256K1KeyFormat) ProtoReflect() protoreflect.Message { + mi := &file_proto_secp256k1_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Secp256K1KeyFormat.ProtoReflect.Descriptor instead. +func (*Secp256K1KeyFormat) Descriptor() ([]byte, []int) { + return file_proto_secp256k1_proto_rawDescGZIP(), []int{3} +} + +func (x *Secp256K1KeyFormat) GetParams() *Secp256K1Params { + if x != nil { + return x.Params + } + return nil +} + +var File_proto_secp256k1_proto protoreflect.FileDescriptor + +var file_proto_secp256k1_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x63, 0x70, 0x32, 0x35, 0x36, 0x6b, + 0x31, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x74, 0x69, 0x6e, 0x6b, 0x1a, 0x12, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0xd4, 0x01, 0x0a, 0x0f, 0x53, 0x65, 0x63, 0x70, 0x32, 0x35, 0x36, 0x4b, 0x31, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x12, 0x39, 0x0a, 0x09, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x74, 0x69, 0x6e, 0x6b, 0x2e, 0x48, 0x61, 0x73, 0x68, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x68, 0x61, 0x73, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3a, + 0x0a, 0x05, 0x63, 0x75, 0x72, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x74, 0x69, + 0x6e, 0x6b, 0x2e, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x43, 0x75, 0x72, 0x76, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x05, 0x63, 0x75, 0x72, 0x76, 0x65, 0x12, 0x4a, 0x0a, 0x08, 0x65, 0x6e, + 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x74, 0x69, 0x6e, + 0x6b, 0x2e, 0x53, 0x65, 0x63, 0x70, 0x32, 0x35, 0x36, 0x4b, 0x31, 0x53, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x65, 0x6e, + 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x87, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x63, 0x70, 0x32, + 0x35, 0x36, 0x6b, 0x31, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x74, 0x69, 0x6e, 0x6b, 0x2e, 0x53, 0x65, 0x63, + 0x70, 0x32, 0x35, 0x36, 0x4b, 0x31, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x06, 0x70, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x12, 0x0c, 0x0a, 0x01, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x01, 0x78, 0x12, 0x0c, 0x0a, 0x01, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x79, + 0x22, 0x93, 0x01, 0x0a, 0x13, 0x53, 0x65, 0x63, 0x70, 0x32, 0x35, 0x36, 0x6b, 0x31, 0x50, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x74, 0x69, 0x6e, 0x6b, 0x2e, 0x53, 0x65, 0x63, 0x70, + 0x32, 0x35, 0x36, 0x6b, 0x31, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x09, + 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6b, 0x65, 0x79, + 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6b, 0x65, + 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x51, 0x0a, 0x12, 0x53, 0x65, 0x63, 0x70, 0x32, 0x35, + 0x36, 0x4b, 0x31, 0x4b, 0x65, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x3b, 0x0a, 0x06, + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x74, 0x69, 0x6e, + 0x6b, 0x2e, 0x53, 0x65, 0x63, 0x70, 0x32, 0x35, 0x36, 0x4b, 0x31, 0x50, 0x61, 0x72, 0x61, 0x6d, + 0x73, 0x52, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2a, 0x3c, 0x0a, 0x10, 0x42, 0x69, 0x74, + 0x63, 0x6f, 0x69, 0x6e, 0x43, 0x75, 0x72, 0x76, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, + 0x15, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x42, 0x49, 0x54, 0x43, 0x4f, 0x49, 0x4e, + 0x5f, 0x43, 0x55, 0x52, 0x56, 0x45, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x45, 0x43, 0x50, + 0x32, 0x35, 0x36, 0x4b, 0x31, 0x10, 0x02, 0x2a, 0x63, 0x0a, 0x1a, 0x53, 0x65, 0x63, 0x70, 0x32, + 0x35, 0x36, 0x4b, 0x31, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x45, 0x6e, 0x63, + 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x5f, 0x42, 0x49, 0x54, 0x43, 0x4f, 0x49, 0x4e, 0x5f, 0x45, 0x4e, 0x43, 0x4f, 0x44, 0x49, 0x4e, + 0x47, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x42, 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x49, + 0x45, 0x45, 0x45, 0x5f, 0x50, 0x31, 0x33, 0x36, 0x33, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x42, + 0x69, 0x74, 0x63, 0x6f, 0x69, 0x6e, 0x5f, 0x44, 0x45, 0x52, 0x10, 0x02, 0x42, 0x51, 0x0a, 0x1c, + 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x6f, 0x2e, 0x74, 0x69, 0x6e, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x74, 0x69, 0x6e, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x63, + 0x70, 0x32, 0x35, 0x36, 0x6b, 0x31, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_secp256k1_proto_rawDescOnce sync.Once + file_proto_secp256k1_proto_rawDescData = file_proto_secp256k1_proto_rawDesc +) + +func file_proto_secp256k1_proto_rawDescGZIP() []byte { + file_proto_secp256k1_proto_rawDescOnce.Do(func() { + file_proto_secp256k1_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_secp256k1_proto_rawDescData) + }) + return file_proto_secp256k1_proto_rawDescData +} + +var file_proto_secp256k1_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_proto_secp256k1_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_proto_secp256k1_proto_goTypes = []interface{}{ + (BitcoinCurveType)(0), // 0: google.crypto.tink.BitcoinCurveType + (Secp256K1SignatureEncoding)(0), // 1: google.crypto.tink.Secp256K1SignatureEncoding + (*Secp256K1Params)(nil), // 2: google.crypto.tink.Secp256K1Params + (*Secp256K1PublicKey)(nil), // 3: google.crypto.tink.Secp256k1PublicKey + (*Secp256K1PrivateKey)(nil), // 4: google.crypto.tink.Secp256k1PrivateKey + (*Secp256K1KeyFormat)(nil), // 5: google.crypto.tink.Secp256K1KeyFormat + (common_go_proto.HashType)(0), // 6: google.crypto.tink.HashType +} +var file_proto_secp256k1_proto_depIdxs = []int32{ + 6, // 0: google.crypto.tink.Secp256K1Params.hash_type:type_name -> google.crypto.tink.HashType + 0, // 1: google.crypto.tink.Secp256K1Params.curve:type_name -> google.crypto.tink.BitcoinCurveType + 1, // 2: google.crypto.tink.Secp256K1Params.encoding:type_name -> google.crypto.tink.Secp256K1SignatureEncoding + 2, // 3: google.crypto.tink.Secp256k1PublicKey.params:type_name -> google.crypto.tink.Secp256K1Params + 3, // 4: google.crypto.tink.Secp256k1PrivateKey.public_key:type_name -> google.crypto.tink.Secp256k1PublicKey + 2, // 5: google.crypto.tink.Secp256K1KeyFormat.params:type_name -> google.crypto.tink.Secp256K1Params + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_proto_secp256k1_proto_init() } +func file_proto_secp256k1_proto_init() { + if File_proto_secp256k1_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_secp256k1_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Secp256K1Params); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_secp256k1_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Secp256K1PublicKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_secp256k1_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Secp256K1PrivateKey); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_secp256k1_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Secp256K1KeyFormat); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_secp256k1_proto_rawDesc, + NumEnums: 2, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto_secp256k1_proto_goTypes, + DependencyIndexes: file_proto_secp256k1_proto_depIdxs, + EnumInfos: file_proto_secp256k1_proto_enumTypes, + MessageInfos: file_proto_secp256k1_proto_msgTypes, + }.Build() + File_proto_secp256k1_proto = out.File + file_proto_secp256k1_proto_rawDesc = nil + file_proto_secp256k1_proto_goTypes = nil + file_proto_secp256k1_proto_depIdxs = nil +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1.go new file mode 100644 index 000000000..7cad94376 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1.go @@ -0,0 +1,29 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package secp256k1 + +import ( + "fmt" + + "github.com/google/tink/go/core/registry" +) + +// Package secp256k1 provides implementations of the Signer and Verifier +// primitives. +// +// To sign data using Tink you can use the Secp256k1 key templates. +// nolint:gochecknoinits +func init() { + // ECDSA Secp256K1 key managers. + if err := registry.RegisterKeyManager(newSecp256K2SignerKeyManager()); err != nil { + panic(fmt.Sprintf("signature.init() failed: %v", err)) + } + + if err := registry.RegisterKeyManager(newSecp256K1VerifierKeyManager()); err != nil { + panic(fmt.Sprintf("signature.init() failed: %v", err)) + } +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_key_templates.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_key_templates.go new file mode 100644 index 000000000..95a19f949 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_key_templates.go @@ -0,0 +1,64 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package secp256k1 + +import ( + "github.com/golang/protobuf/proto" + commonpb "github.com/google/tink/go/proto/common_go_proto" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + + secp256k1pb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto" +) + +// This file contains pre-generated KeyTemplates for Signer and Verifier. +// One can use these templates to generate new Keysets. + +// DERKeyTemplate is a KeyTemplate that generates a new ECDSA secp256k1 private key with the following parameters: +// - Hash function: SHA256 +// - Curve: secp256k1 +// - Signature encoding: DER +// - Output prefix type: TINK +func DERKeyTemplate() (*tinkpb.KeyTemplate, error) { + return createECDSAKeyTemplate(commonpb.HashType_SHA256, + secp256k1pb.BitcoinCurveType_SECP256K1, + secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER, + tinkpb.OutputPrefixType_TINK) +} + +// IEEEP1363KeyTemplate is a KeyTemplate that generates a new ECDSA secp256k1 private key with the following parameters: +// - Hash function: SHA256 +// - Curve: secp256k1 +// - Signature encoding: IEEE-P1363 +// - Output prefix type: TINK +func IEEEP1363KeyTemplate() (*tinkpb.KeyTemplate, error) { + return createECDSAKeyTemplate(commonpb.HashType_SHA256, + secp256k1pb.BitcoinCurveType_SECP256K1, + secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_IEEE_P1363, + tinkpb.OutputPrefixType_TINK) +} + +// createECDSAKeyTemplate creates a KeyTemplate containing a Secp256K1KeyFormat with the given parameters. +func createECDSAKeyTemplate(hashType commonpb.HashType, curve secp256k1pb.BitcoinCurveType, + encoding secp256k1pb.Secp256K1SignatureEncoding, prefixType tinkpb.OutputPrefixType) (*tinkpb.KeyTemplate, error) { + params := &secp256k1pb.Secp256K1Params{ + HashType: hashType, + Curve: curve, + Encoding: encoding, + } + format := &secp256k1pb.Secp256K1KeyFormat{Params: params} + + serializedFormat, err := proto.Marshal(format) + if err != nil { + return nil, err + } + + return &tinkpb.KeyTemplate{ + TypeUrl: secp256k1SignerTypeURL, + Value: serializedFormat, + OutputPrefixType: prefixType, + }, nil +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_key_templates_test.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_key_templates_test.go new file mode 100644 index 000000000..e3c632047 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_key_templates_test.go @@ -0,0 +1,108 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package secp256k1_test + +import ( + "fmt" + "testing" + + "github.com/google/tink/go/keyset" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + "github.com/stretchr/testify/require" + + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/secp256k1" +) + +func TestKeyTemplates(t *testing.T) { + derKeyTemplate, err := secp256k1.DERKeyTemplate() + require.NoError(t, err) + + ieeeKeyTempalte, err := secp256k1.IEEEP1363KeyTemplate() + require.NoError(t, err) + + testCases := []struct { + name string + template *tinkpb.KeyTemplate + }{ + { + name: "SECP256K1", + template: derKeyTemplate, + }, + { + name: "SECP256K1", + template: ieeeKeyTempalte, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var kh *keyset.Handle + + kh, err = keyset.NewHandle(tc.template) + require.NoError(t, err) + + err = testSignVerify(kh) + require.NoError(t, err) + }) + } +} + +func testSignVerify(privateHandle *keyset.Handle) error { + signer, err := secp256k1.NewSigner(privateHandle) + if err != nil { + return fmt.Errorf("signature.NewSigner(privateHandle) failed: %w", err) + } + + publicHandle, err := privateHandle.Public() + if err != nil { + return fmt.Errorf("privateHandle.Public() failed: %w", err) + } + + verifier, err := secp256k1.NewVerifier(publicHandle) + if err != nil { + return fmt.Errorf("signature.NewVerifier(publicHandle) failed: %w", err) + } + + testInputs := []struct { + message1 []byte + message2 []byte + }{ + { + message1: []byte("this data needs to be signed"), + message2: []byte("this data needs to be signed"), + }, + { + message1: []byte(""), + message2: []byte(""), + }, + { + message1: []byte(""), + message2: nil, + }, + { + message1: nil, + message2: []byte(""), + }, + { + message1: nil, + message2: nil, + }, + } + + for _, ti := range testInputs { + sig, e := signer.Sign(ti.message1) + if e != nil { + return fmt.Errorf("signer.Sign(ti.message1) failed: %w", e) + } + + if err = verifier.Verify(sig, ti.message2); err != nil { + return fmt.Errorf("verifier.Verify(sig, ti.message2) failed: %w", err) + } + } + + return nil +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_signer_key_manager.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_signer_key_manager.go new file mode 100644 index 000000000..6809382ca --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_signer_key_manager.go @@ -0,0 +1,220 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package secp256k1 + +import ( + "crypto/ecdsa" + "crypto/rand" + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcec" + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/keyset" + commonpb "github.com/google/tink/go/proto/common_go_proto" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + + secp256k1pb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto" + subtleSignature "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle" +) + +const ( + secp256k1SignerKeyVersion = 0 + secp256k1SignerTypeURL = "type.googleapis.com/google.crypto.tink.secp256k1PrivateKey" +) + +// common errors. +var ( + errInvalidSECP256K1SignKey = errors.New("secp256k1_signer_key_manager: invalid key") + errInvalidSECP256K1SignKeyFormat = errors.New("secp256k1_signer_key_manager: invalid key format") +) + +// secp256k1SignerKeyManager is an implementation of KeyManager interface. +// It generates new Secp256K1PrivateKeys and produces new instances of ECDSASign subtle. +type secp256k1SignerKeyManager struct{} + +// newSecp256K2SignerKeyManager creates a new secp256k1SignerKeyManager. +func newSecp256K2SignerKeyManager() *secp256k1SignerKeyManager { + return new(secp256k1SignerKeyManager) +} + +// Primitive creates an ECDSASign subtle for the given serialized ECDSAPrivateKey proto. +func (km *secp256k1SignerKeyManager) Primitive(serializedKey []byte) (interface{}, error) { + if len(serializedKey) == 0 { + return nil, errInvalidSECP256K1SignKey + } + + key := new(secp256k1pb.Secp256K1PrivateKey) + if err := proto.Unmarshal(serializedKey, key); err != nil { + return nil, errInvalidSECP256K1SignKey + } + + if err := km.validateKey(key); err != nil { + return nil, err + } + + hash, curve, encoding := getSecp256K1ParamNames(key.PublicKey.Params) + + ret, err := subtleSignature.NewSecp256K1Signer(hash, curve, encoding, key.KeyValue) + if err != nil { + return nil, fmt.Errorf("secp256k1_signer_key_manager: %w", err) + } + + return ret, nil +} + +// NewKey creates a new ECDSAPrivateKey according to specification the given serialized ECDSAKeyFormat. +func (km *secp256k1SignerKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { + if len(serializedKeyFormat) == 0 { + return nil, errInvalidSECP256K1SignKeyFormat + } + + keyFormat := new(secp256k1pb.Secp256K1KeyFormat) + if err := proto.Unmarshal(serializedKeyFormat, keyFormat); err != nil { + return nil, fmt.Errorf("secp256k1_signer_key_manager: invalid proto: %w", err) + } + + if err := km.validateKeyFormat(keyFormat); err != nil { + return nil, fmt.Errorf("secp256k1_signer_key_manager: invalid key format: %w", err) + } + + // generate key + params := keyFormat.Params + + tmpKey, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("secp256k1_signer_key_manager: cannot generate ECDSA key: %w", err) + } + + keyValue := tmpKey.D.Bytes() + pub := newSecp256K1PublicKey(secp256k1SignerKeyVersion, params, tmpKey.X.Bytes(), tmpKey.Y.Bytes()) + priv := newSecp256K1PrivateKey(secp256k1SignerKeyVersion, pub, keyValue) + + return priv, nil +} + +// NewKeyData creates a new KeyData according to specification in the given +// serialized ECDSAKeyFormat. It should be used solely by the key management API. +func (km *secp256k1SignerKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { + key, err := km.NewKey(serializedKeyFormat) + if err != nil { + return nil, err + } + + serializedKey, err := proto.Marshal(key) + if err != nil { + return nil, errInvalidSECP256K1SignKeyFormat + } + + return &tinkpb.KeyData{ + TypeUrl: secp256k1SignerTypeURL, + Value: serializedKey, + KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE, + }, nil +} + +// PublicKeyData extracts the public key data from the private key. +func (km *secp256k1SignerKeyManager) PublicKeyData(serializedPrivKey []byte) (*tinkpb.KeyData, error) { + privKey := new(secp256k1pb.Secp256K1PrivateKey) + if err := proto.Unmarshal(serializedPrivKey, privKey); err != nil { + return nil, errInvalidSECP256K1SignKey + } + + serializedPubKey, err := proto.Marshal(privKey.PublicKey) + if err != nil { + return nil, errInvalidSECP256K1SignKey + } + + return &tinkpb.KeyData{ + TypeUrl: secp256k1VerifierTypeURL, + Value: serializedPubKey, + KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PUBLIC, + }, nil +} + +// DoesSupport indicates if this key manager supports the given key type. +func (km *secp256k1SignerKeyManager) DoesSupport(typeURL string) bool { + return typeURL == secp256k1SignerTypeURL +} + +// TypeURL returns the key type of keys managed by this key manager. +func (km *secp256k1SignerKeyManager) TypeURL() string { + return secp256k1SignerTypeURL +} + +// validateKey validates the given ECDSAPrivateKey. +func (km *secp256k1SignerKeyManager) validateKey(key *secp256k1pb.Secp256K1PrivateKey) error { + if err := keyset.ValidateKeyVersion(key.Version, secp256k1SignerKeyVersion); err != nil { + return fmt.Errorf("secp256k1_signer_key_manager: invalid key: %w", err) + } + + hash, curve, encoding := getSecp256K1ParamNames(key.PublicKey.Params) + + return ValidateSecp256K1Params(hash, curve, encoding) +} + +// validateKeyFormat validates the given ECDSAKeyFormat. +func (km *secp256k1SignerKeyManager) validateKeyFormat(format *secp256k1pb.Secp256K1KeyFormat) error { + hash, curve, encoding := getSecp256K1ParamNames(format.Params) + return ValidateSecp256K1Params(hash, curve, encoding) +} + +// ValidateSecp256K1Params validates ECDSA parameters. +// The hash's strength must not be weaker than the curve's strength. +// DER and IEEE_P1363 encodings are supported. +func ValidateSecp256K1Params(hashAlg, curve, encoding string) error { + switch encoding { + case "Bitcoin_DER": + case "Bitcoin_IEEE_P1363": + default: + return errors.New("secp256k1: unsupported encoding") + } + + switch curve { + case "SECP256K1": + if hashAlg != "SHA256" { + return errors.New("invalid hash type for secp256k1 curve, expect SHA-256") + } + default: + return fmt.Errorf("unsupported curve: %s", curve) + } + + return nil +} + +// getSecp256K1ParamNames returns the string representations of each parameter in +// the given secp256k1Params. +func getSecp256K1ParamNames(params *secp256k1pb.Secp256K1Params) (string, string, string) { + hashName := commonpb.HashType_name[int32(params.HashType)] + curveName := secp256k1pb.BitcoinCurveType_name[int32(params.Curve)] + encodingName := secp256k1pb.Secp256K1SignatureEncoding_name[int32(params.Encoding)] + + return hashName, curveName, encodingName +} + +// newSecp256K1PublicKey creates a Secp256K1PublicKey with the specified parameters. +func newSecp256K1PublicKey(version uint32, + params *secp256k1pb.Secp256K1Params, + x []byte, y []byte) *secp256k1pb.Secp256K1PublicKey { + return &secp256k1pb.Secp256K1PublicKey{ + Version: version, + Params: params, + X: x, + Y: y, + } +} + +// newSecp256K1PrivateKey creates a Secp256K1PrivateKey with the specified parameters. +func newSecp256K1PrivateKey(version uint32, + publicKey *secp256k1pb.Secp256K1PublicKey, + keyValue []byte) *secp256k1pb.Secp256K1PrivateKey { + return &secp256k1pb.Secp256K1PrivateKey{ + Version: version, + PublicKey: publicKey, + KeyValue: keyValue, + } +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_signer_key_manager_test.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_signer_key_manager_test.go new file mode 100644 index 000000000..24af2cb68 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_signer_key_manager_test.go @@ -0,0 +1,427 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package secp256k1_test + +import ( + "crypto/ecdsa" + "crypto/rand" + "fmt" + "math/big" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/core/registry" + commonpb "github.com/google/tink/go/proto/common_go_proto" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + "github.com/google/tink/go/subtle/random" + "github.com/stretchr/testify/require" + + secp256k1pb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle" +) + +const ( + secp256k1SignerKeyVersion = uint32(0) + secp256k1SignerTypeURL = "type.googleapis.com/google.crypto.tink.secp256k1PrivateKey" +) + +type secp256k1Params struct { + hashType commonpb.HashType + curve secp256k1pb.BitcoinCurveType +} + +func TestSecp256k1SignerGetPrimitiveBasic(t *testing.T) { + testParams := genValidSecp256k1Params() + km, err := registry.GetKeyManager(secp256k1SignerTypeURL) + require.NoError(t, err) + + for i := 0; i < len(testParams); i++ { + serializedKey, e := proto.Marshal(NewRandomSecp256K1PrivateKey(testParams[i].hashType, testParams[i].curve)) + require.NoError(t, e) + + _, err = km.Primitive(serializedKey) + require.NoErrorf(t, err, "unexpect error in test case %d ", i) + } +} + +func TestECDSASecp256K1SignGetPrimitiveWithInvalidInput(t *testing.T) { + // invalid params + testParams := genInvalidSecp256k1Params() + km, err := registry.GetKeyManager(secp256k1SignerTypeURL) + require.NoError(t, err) + + for i := 0; i < len(testParams); i++ { + serializedKey, e := proto.Marshal(NewRandomSecp256K1PrivateKey(testParams[i].hashType, testParams[i].curve)) + if testParams[i].curve != secp256k1pb.BitcoinCurveType_INVALID_BITCOIN_CURVE { + require.NoError(t, e) + } + + _, err = km.Primitive(serializedKey) + require.Errorf(t, err, "expect an error in test case %d", i) + } + + // invalid version + key := NewRandomSecp256K1PrivateKey(commonpb.HashType_SHA256, + secp256k1pb.BitcoinCurveType_SECP256K1) + key.Version = secp256k1SignerKeyVersion + 1 + serializedKey, e := proto.Marshal(key) + require.NoError(t, e) + + _, err = km.Primitive(serializedKey) + require.Error(t, err, "expect an error when version is invalid") + + // nil input + _, err = km.Primitive(nil) + require.Error(t, err, "expect an error when input is nil") + + _, err = km.Primitive([]byte{}) + require.Error(t, err, "expect an error when input is empty slice") +} + +func TestECDSASecp256K1SignNewKeyBasic(t *testing.T) { + testParams := genValidSecp256k1Params() + + km, err := registry.GetKeyManager(secp256k1SignerTypeURL) + require.NoError(t, err) + + for i := 0; i < len(testParams); i++ { + params := NewSecp256K1Params(testParams[i].hashType, testParams[i].curve, + secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER) + + serializedFormat, e := proto.Marshal(newSecp256K1KeyFormat(params)) + require.NoError(t, e) + + tmp, e := km.NewKey(serializedFormat) + require.NoError(t, e) + + key, ok := tmp.(*secp256k1pb.Secp256K1PrivateKey) + require.True(t, ok) + + err = validateECDSASecp256K1PrivateKey(t, key, params) + require.NoErrorf(t, err, "invalid private key in test case %d", i) + } +} + +func TestECDSASecp256K1SignNewKeyWithInvalidInput(t *testing.T) { + km, err := registry.GetKeyManager(secp256k1SignerTypeURL) + require.NoError(t, err) + + // invalid hash and curve type + testParams := genInvalidSecp256k1Params() + for i := 0; i < len(testParams); i++ { + params := NewSecp256K1Params(testParams[i].hashType, testParams[i].curve, + secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER) + + serializedFormat, e := proto.Marshal(newSecp256K1KeyFormat(params)) + require.NoError(t, e) + + _, err = km.NewKey(serializedFormat) + require.Errorf(t, err, "expect an error in test case %d", i) + } + + // invalid encoding + testParams = genValidSecp256k1Params() + for i := 0; i < len(testParams); i++ { + params := NewSecp256K1Params(testParams[i].hashType, testParams[i].curve, + secp256k1pb.Secp256K1SignatureEncoding_UNKNOWN_BITCOIN_ENCODING) + + serializedFormat, e := proto.Marshal(newSecp256K1KeyFormat(params)) + require.NoError(t, e) + + _, err = km.NewKey(serializedFormat) + require.Errorf(t, err, "expect an error in test case %d", i) + } + + // nil input + _, err = km.NewKey(nil) + require.Error(t, err, "expect an error when input is nil") + + _, err = km.NewKey([]byte{}) + require.Error(t, err, "expect an error when input is empty slice") +} + +func TestECDSASecp256K1SignNewKeyMultipleTimes(t *testing.T) { + km, err := registry.GetKeyManager(secp256k1SignerTypeURL) + require.NoError(t, err) + + testParams := genValidSecp256k1Params() + + nTest := 27 + + for i := 0; i < len(testParams); i++ { + keys := make(map[string]bool) + params := NewSecp256K1Params(testParams[i].hashType, testParams[i].curve, + secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER) + format := newSecp256K1KeyFormat(params) + + serializedFormat, e := proto.Marshal(format) + require.NoError(t, e) + + for j := 0; j < nTest; j++ { + key, e := km.NewKey(serializedFormat) + require.NoError(t, e) + + serializedKey, e := proto.Marshal(key) + require.NoError(t, e) + + keys[string(serializedKey)] = true + + keyData, e := km.NewKeyData(serializedFormat) + require.NoError(t, e) + + serializedKey = keyData.Value + keys[string(serializedKey)] = true + } + + require.Equalf(t, len(keys), nTest*2, "key is repeated with params: %s", params) + } +} + +func TestECDSASecp256K1SignNewKeyDataBasic(t *testing.T) { + km, err := registry.GetKeyManager(secp256k1SignerTypeURL) + require.NoError(t, err) + + testParams := genValidSecp256k1Params() + for i := 0; i < len(testParams); i++ { + params := NewSecp256K1Params(testParams[i].hashType, testParams[i].curve, + secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER) + serializedFormat, e := proto.Marshal(newSecp256K1KeyFormat(params)) + require.NoError(t, e) + + keyData, e := km.NewKeyData(serializedFormat) + require.NoErrorf(t, e, "unexpected error in test case %d", i) + + require.Equalf(t, keyData.TypeUrl, secp256k1SignerTypeURL, + "incorrect type url in test case %d: expect %s, got %s", + i, secp256k1SignerTypeURL, keyData.TypeUrl) + + require.Equalf(t, keyData.KeyMaterialType, tinkpb.KeyData_ASYMMETRIC_PRIVATE, + "incorrect key material type in test case %d: expect %s, got %s", + i, tinkpb.KeyData_ASYMMETRIC_PRIVATE, keyData.KeyMaterialType) + + key := new(secp256k1pb.Secp256K1PrivateKey) + err = proto.Unmarshal(keyData.Value, key) + require.NoErrorf(t, err, "unexpect error in test case %d", i) + + err = validateECDSASecp256K1PrivateKey(t, key, params) + require.NoErrorf(t, err, "invalid private key in test case %d", i) + } +} + +func TestECDSASecp256K1SignNewKeyDataWithInvalidInput(t *testing.T) { + km, err := registry.GetKeyManager(secp256k1SignerTypeURL) + require.NoError(t, err) + + testParams := genInvalidSecp256k1Params() + for i := 0; i < len(testParams); i++ { + params := NewSecp256K1Params(testParams[i].hashType, testParams[i].curve, + secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER) + format := newSecp256K1KeyFormat(params) + + serializedFormat, e := proto.Marshal(format) + require.NoError(t, e) + + _, err = km.NewKeyData(serializedFormat) + require.Errorf(t, err, "expect an error in test case %d", i) + } + + // nil input + _, err = km.NewKeyData(nil) + require.Errorf(t, err, "expect an error when input is nil") +} + +func TestPublicKeyDataBasic(t *testing.T) { + testParams := genValidSecp256k1Params() + + km, err := registry.GetKeyManager(secp256k1SignerTypeURL) + require.NoError(t, err) + + pkm, ok := km.(registry.PrivateKeyManager) + require.True(t, ok, "cannot obtain private key manager") + + for i := 0; i < len(testParams); i++ { + key := NewRandomSecp256K1PrivateKey(testParams[i].hashType, testParams[i].curve) + serializedKey, e := proto.Marshal(key) + require.NoError(t, e) + + pubKeyData, e := pkm.PublicKeyData(serializedKey) + require.NoErrorf(t, e, "unexpect error in test case %d", i) + + require.Equalf(t, pubKeyData.TypeUrl, secp256k1VerifierTypeURL, "incorrect type url: %s", pubKeyData.TypeUrl) + + require.Equalf(t, pubKeyData.KeyMaterialType, tinkpb.KeyData_ASYMMETRIC_PUBLIC, + "incorrect key material type: %d", pubKeyData.KeyMaterialType) + + pubKey := new(secp256k1pb.Secp256K1PublicKey) + err = proto.Unmarshal(pubKeyData.Value, pubKey) + require.NoError(t, err) + } +} + +func TestPublicKeyDataWithInvalidInput(t *testing.T) { + km, err := registry.GetKeyManager(secp256k1SignerTypeURL) + require.NoError(t, err) + + pkm, ok := km.(registry.PrivateKeyManager) + require.True(t, ok, "cannot obtain private key manager") + + // modified key + key := NewRandomSecp256K1PrivateKey(commonpb.HashType_SHA256, secp256k1pb.BitcoinCurveType_SECP256K1) + serializedKey, err := proto.Marshal(key) + require.NoError(t, err) + + serializedKey[0] = 0 + + _, err = pkm.PublicKeyData(serializedKey) + require.Error(t, err, "expect an error when input is a modified serialized key") + + // nil + _, err = pkm.PublicKeyData(nil) + require.Errorf(t, err, "expect an error when input is nil") + + // empty slice + _, err = pkm.PublicKeyData([]byte{}) + require.Errorf(t, err, "expect an error when input is an empty slice") +} + +var errSmallKey = fmt.Errorf("private key doesn't have adequate size") + +func validateECDSASecp256K1PrivateKey(t *testing.T, key *secp256k1pb.Secp256K1PrivateKey, + params *secp256k1pb.Secp256K1Params) error { + require.Equalf(t, key.Version, secp256k1SignerKeyVersion, "incorrect private key's version: expect %d, got %d", + secp256k1SignerKeyVersion, key.Version) + + publicKey := key.PublicKey + require.Equalf(t, publicKey.Version, secp256k1SignerKeyVersion, "incorrect public key's version: expect %d, got %d", + secp256k1SignerKeyVersion, key.Version) + + if params.HashType != publicKey.Params.HashType || + params.Curve != publicKey.Params.Curve || + params.Encoding != publicKey.Params.Encoding { + return fmt.Errorf("incorrect params: expect %s, got %s", params, publicKey.Params) + } + + if len(publicKey.X) == 0 || len(publicKey.Y) == 0 { + return fmt.Errorf("public points are not initialized") + } + + // check private key's size + d := new(big.Int).SetBytes(key.KeyValue) + keySize := len(d.Bytes()) + + if params.Curve == secp256k1pb.BitcoinCurveType_SECP256K1 { + if keySize < 256/8-8 || keySize > 256/8+1 { + return errSmallKey + } + } + + // try to sign and verify with the key + hash, curve, encoding := getSecp256K1ParamNames(publicKey.Params) + signer, err := subtle.NewSecp256K1Signer(hash, curve, encoding, key.KeyValue) + require.NoError(t, err, "unexpected error when creating Secp256K1Sign") + + verifier, err := subtle.NewSecp256K1Verifier(hash, curve, encoding, publicKey.X, publicKey.Y) + require.NoError(t, err, "unexpected error when creating Secp256K1Verify") + + data := random.GetRandomBytes(1281) + + signature, err := signer.Sign(data) + require.NoError(t, err, "unexpected error when signing") + + err = verifier.Verify(signature, data) + require.NoError(t, err, "unexpected error when verifying signature") + + return nil +} + +func genValidSecp256k1Params() []secp256k1Params { + return []secp256k1Params{ + { + hashType: commonpb.HashType_SHA256, + curve: secp256k1pb.BitcoinCurveType_SECP256K1, + }, + } +} + +func genInvalidSecp256k1Params() []secp256k1Params { + return []secp256k1Params{ + { + hashType: commonpb.HashType_SHA1, + curve: secp256k1pb.BitcoinCurveType_SECP256K1, + }, + { + hashType: commonpb.HashType_SHA1, + curve: secp256k1pb.BitcoinCurveType_INVALID_BITCOIN_CURVE, + }, + } +} + +// NewRandomSecp256K1PrivateKey creates an ECDSAPrivateKey with randomly generated key material. +func NewRandomSecp256K1PrivateKey(hashType commonpb.HashType, + curve secp256k1pb.BitcoinCurveType) *secp256k1pb.Secp256K1PrivateKey { + curveName := secp256k1pb.BitcoinCurveType_name[int32(curve)] + if curveName == secp256k1pb.BitcoinCurveType_INVALID_BITCOIN_CURVE.String() { + return nil + } + + priv, e := ecdsa.GenerateKey(subtle.GetCurve(curveName), rand.Reader) + if e != nil { + return nil + } + + params := NewSecp256K1Params(hashType, curve, secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER) + publicKey := newSecp256K1PublicKey(secp256k1SignerKeyVersion, params, priv.X.Bytes(), priv.Y.Bytes()) + + return newSecp256K1APrivateKey(secp256k1SignerKeyVersion, publicKey, priv.D.Bytes()) +} + +// NewSecp256K1Params creates a ECDSAParams with the specified parameters. +func NewSecp256K1Params(hashType commonpb.HashType, + curve secp256k1pb.BitcoinCurveType, + encoding secp256k1pb.Secp256K1SignatureEncoding) *secp256k1pb.Secp256K1Params { + return &secp256k1pb.Secp256K1Params{ + HashType: hashType, + Curve: curve, + Encoding: encoding, + } +} + +// newSecp256K1KeyFormat creates a ECDSAKeyFormat with the specified parameters. +func newSecp256K1KeyFormat(params *secp256k1pb.Secp256K1Params) *secp256k1pb.Secp256K1KeyFormat { + return &secp256k1pb.Secp256K1KeyFormat{Params: params} +} + +// newSecp256K1APrivateKey creates a ECDSAPrivateKey with the specified parameters. +func newSecp256K1APrivateKey(version uint32, publicKey *secp256k1pb.Secp256K1PublicKey, + keyValue []byte) *secp256k1pb.Secp256K1PrivateKey { + return &secp256k1pb.Secp256K1PrivateKey{ + Version: version, + PublicKey: publicKey, + KeyValue: keyValue, + } +} + +// newSecp256K1PublicKey creates a ECDSAPublicKey with the specified parameters. +func newSecp256K1PublicKey(version uint32, params *secp256k1pb.Secp256K1Params, + x []byte, y []byte) *secp256k1pb.Secp256K1PublicKey { + return &secp256k1pb.Secp256K1PublicKey{ + Version: version, + Params: params, + X: x, + Y: y, + } +} + +// getSecp256K1ParamNames returns the string representations of each parameter in +// the given Secp256K1Params. +func getSecp256K1ParamNames(params *secp256k1pb.Secp256K1Params) (string, string, string) { + hashName := commonpb.HashType_name[int32(params.HashType)] + curveName := secp256k1pb.BitcoinCurveType_name[int32(params.Curve)] + encodingName := secp256k1pb.Secp256K1SignatureEncoding_name[int32(params.Encoding)] + + return hashName, curveName, encodingName +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_verifier_key_manager.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_verifier_key_manager.go new file mode 100644 index 000000000..97a8fd619 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_verifier_key_manager.go @@ -0,0 +1,95 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package secp256k1 + +import ( + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/keyset" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + + secp256k1pb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle" +) + +const ( + secp256k1VerifierKeyVersion = 0 + secp256k1VerifierTypeURL = "type.googleapis.com/google.crypto.tink.secp256k1PublicKey" +) + +// common errors. +var ( + errInvalidsecp256k1VerifierKey = fmt.Errorf("secp256k1_verifier_key_manager: invalid key") + errsecp256k1VerifierNotImplemented = fmt.Errorf("secp256k1_verifier_key_manager: not implemented") +) + +// secp256k1VerifierKeyManager is an implementation of KeyManager interface. +// It doesn't support key generation. +type secp256k1VerifierKeyManager struct{} + +// newSecp256K1VerifierKeyManager creates a new secp256k1VerifierKeyManager. +func newSecp256K1VerifierKeyManager() *secp256k1VerifierKeyManager { + return new(secp256k1VerifierKeyManager) +} + +// Primitive creates an secp256k1Verifier subtle for the given serialized secp256k1PublicKey proto. +func (km *secp256k1VerifierKeyManager) Primitive(serializedKey []byte) (interface{}, error) { + if len(serializedKey) == 0 { + return nil, errInvalidsecp256k1VerifierKey + } + + key := new(secp256k1pb.Secp256K1PublicKey) + if err := proto.Unmarshal(serializedKey, key); err != nil { + return nil, errInvalidsecp256k1VerifierKey + } + + if err := km.validateKey(key); err != nil { + return nil, fmt.Errorf("secp256k1_verifier_key_manager: %w", err) + } + + hash, curve, encoding := getSecp256K1ParamNames(key.Params) + + ret, err := subtle.NewSecp256K1Verifier(hash, curve, encoding, key.X, key.Y) + if err != nil { + return nil, fmt.Errorf("secp256k1_verifier_key_manager: invalid key: %w", err) + } + + return ret, nil +} + +// NewKey is not implemented. +func (km *secp256k1VerifierKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) { + return nil, errsecp256k1VerifierNotImplemented +} + +// NewKeyData creates a new KeyData according to specification in the given +// serialized secp256k1KeyFormat. It should be used solely by the key management API. +func (km *secp256k1VerifierKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) { + return nil, errsecp256k1VerifierNotImplemented +} + +// DoesSupport indicates if this key manager supports the given key type. +func (km *secp256k1VerifierKeyManager) DoesSupport(typeURL string) bool { + return typeURL == secp256k1VerifierTypeURL +} + +// TypeURL returns the key type of keys managed by this key manager. +func (km *secp256k1VerifierKeyManager) TypeURL() string { + return secp256k1VerifierTypeURL +} + +// validateKey validates the given secp256k1PublicKey. +func (km *secp256k1VerifierKeyManager) validateKey(key *secp256k1pb.Secp256K1PublicKey) error { + if err := keyset.ValidateKeyVersion(key.Version, secp256k1VerifierKeyVersion); err != nil { + return fmt.Errorf("secp256k1_verifier_key_manager: %w", err) + } + + hash, curve, encoding := getSecp256K1ParamNames(key.Params) + + return ValidateSecp256K1Params(hash, curve, encoding) +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_verifier_key_manager_test.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_verifier_key_manager_test.go new file mode 100644 index 000000000..65e96a2ee --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/secp256k1_verifier_key_manager_test.go @@ -0,0 +1,74 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package secp256k1_test + +import ( + "testing" + + "github.com/golang/protobuf/proto" + "github.com/google/tink/go/core/registry" + commonpb "github.com/google/tink/go/proto/common_go_proto" + "github.com/google/tink/go/testutil" + "github.com/stretchr/testify/require" + + secp256k1pb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto" +) + +const secp256k1VerifierTypeURL = "type.googleapis.com/google.crypto.tink.secp256k1PublicKey" + +func TestSecp256K1VerifyGetPrimitiveBasic(t *testing.T) { + testParams := genValidSecp256k1Params() + km, err := registry.GetKeyManager(secp256k1VerifierTypeURL) + require.NoError(t, err, "cannot obtain secp256K1Verifier key manager") + + for i := 0; i < len(testParams); i++ { + serializedKey, e := proto.Marshal(NewRandomSecp256K1PublicKey(testParams[i].hashType, testParams[i].curve)) + require.NoError(t, e) + + _, err = km.Primitive(serializedKey) + require.NoErrorf(t, err, "unexpect error in test case %d ", i) + } +} + +func TestECDSAVerifyGetPrimitiveWithInvalidInput(t *testing.T) { + testParams := genInvalidSecp256k1Params() + km, err := registry.GetKeyManager(secp256k1VerifierTypeURL) + require.NoError(t, err) + + for i := 0; i < len(testParams); i++ { + serializedKey, e := proto.Marshal(NewRandomSecp256K1PrivateKey(testParams[i].hashType, testParams[i].curve)) + if testParams[i].curve != secp256k1pb.BitcoinCurveType_INVALID_BITCOIN_CURVE { + require.NoError(t, e) + } + + _, err = km.Primitive(serializedKey) + require.Errorf(t, err, "expect an error in test case %d", i) + } + + // invalid version + key := NewRandomSecp256K1PublicKey(commonpb.HashType_SHA256, secp256k1pb.BitcoinCurveType_SECP256K1) + key.Version = testutil.ECDSAVerifierKeyVersion + 1 + + serializedKey, e := proto.Marshal(key) + require.NoError(t, e) + + _, err = km.Primitive(serializedKey) + require.Error(t, err, "expect an error when version is invalid") + + // nil input + _, err = km.Primitive(nil) + require.Error(t, err, "expect an error when input is nil") + + _, err = km.Primitive([]byte{}) + require.Error(t, err, "expect an error when input is empty slice") +} + +// NewRandomSecp256K1PublicKey creates an Secp256K1PublicKey with randomly generated key material. +func NewRandomSecp256K1PublicKey(hashType commonpb.HashType, + curve secp256k1pb.BitcoinCurveType) *secp256k1pb.Secp256K1PublicKey { + return NewRandomSecp256K1PrivateKey(hashType, curve).PublicKey +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/signer_factory.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/signer_factory.go new file mode 100644 index 000000000..e17a5c2b7 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/signer_factory.go @@ -0,0 +1,86 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package secp256k1 + +import ( + "fmt" + + "github.com/google/tink/go/core/primitiveset" + "github.com/google/tink/go/core/registry" + "github.com/google/tink/go/keyset" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + "github.com/google/tink/go/tink" +) + +// NewSigner returns a Signer primitive from the given keyset handle. +func NewSigner(h *keyset.Handle) (tink.Signer, error) { + return NewSignerWithKeyManager(h, nil /*keyManager*/) +} + +// NewSignerWithKeyManager returns a Signer primitive from the given keyset handle and custom key manager. +// Deprecated: register the KeyManager and use New above. +func NewSignerWithKeyManager(h *keyset.Handle, km registry.KeyManager) (tink.Signer, error) { + ps, err := h.PrimitivesWithKeyManager(km) + if err != nil { + return nil, fmt.Errorf("public_key_sign_factory: cannot obtain primitive set: %w", err) + } + + return newWrappedSigner(ps) +} + +// wrappedSigner is an Signer implementation that uses the underlying primitive set for signing. +type wrappedSigner struct { + ps *primitiveset.PrimitiveSet +} + +// Asserts that wrappedSigner implements the Signer interface. +var _ tink.Signer = (*wrappedSigner)(nil) + +func newWrappedSigner(ps *primitiveset.PrimitiveSet) (*wrappedSigner, error) { + if _, ok := (ps.Primary.Primitive).(tink.Signer); !ok { + return nil, fmt.Errorf("public_key_sign_factory: not a Signer primitive") + } + + for _, primitives := range ps.Entries { + for _, p := range primitives { + if _, ok := (p.Primitive).(tink.Signer); !ok { + return nil, fmt.Errorf("public_key_sign_factory: not an Signer primitive") + } + } + } + + ret := new(wrappedSigner) + ret.ps = ps + + return ret, nil +} + +// Sign signs the given data and returns the signature concatenated with the identifier of the +// primary primitive. +func (s *wrappedSigner) Sign(data []byte) ([]byte, error) { + primary := s.ps.Primary + + signer, ok := (primary.Primitive).(tink.Signer) + if !ok { + return nil, fmt.Errorf("public_key_sign_factory: not a Signer primitive") + } + + var signedData []byte + if primary.PrefixType == tinkpb.OutputPrefixType_LEGACY { + signedData = append(signedData, data...) + signedData = append(signedData, byte(0)) + } else { + signedData = data + } + + signature, err := signer.Sign(signedData) + if err != nil { + return nil, err + } + + return append([]byte(primary.Prefix), signature...), nil +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/encoding.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/encoding.go new file mode 100644 index 000000000..7d6b392e1 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/encoding.go @@ -0,0 +1,106 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package subtle + +import ( + "bytes" + "encoding/asn1" + "fmt" + "math/big" + + "github.com/btcsuite/btcd/btcec" +) + +// asn1encode encodes the given ECDSA signature using ASN.1 encoding. +func asn1encode(sig *Secp256k1Signature) ([]byte, error) { + ret, err := asn1.Marshal(*sig) + if err != nil { + return nil, fmt.Errorf("asn.1 encoding failed") + } + + return ret, nil +} + +var errAsn1Decoding = fmt.Errorf("asn.1 decoding failed") + +// asn1decode verifies the given ECDSA signature and decodes it if it is valid. +// Since asn1.Unmarshal() doesn't do a strict verification on its input, it will +// accept signatures with trailing data. Therefore, we add an additional check to make sure +// that the input follows strict DER encoding: after unmarshalling the signature bytes, +// we marshal the obtained signature object again. Since DER encoding is deterministic, +// we expect that the obtained bytes would be equal to the input. +func asn1decode(b []byte) (*Secp256k1Signature, error) { + sig := new(Secp256k1Signature) + + _, err := asn1.Unmarshal(b, sig) // der encoding does not work for Secp256k1 keys. + if err != nil { + return nil, errAsn1Decoding + } + + // encode the signature again + encoded, err := asn1.Marshal(*sig) + if err != nil { + return nil, errAsn1Decoding + } + + if !bytes.Equal(b, encoded) { + return nil, errAsn1Decoding + } + + return sig, nil +} + +func ieeeSignatureSize(curveName string) (int, error) { + switch curveName { + case btcec.S256().Params().Name: + return 64, nil //nolint:gomnd + default: + return 0, fmt.Errorf("ieeeP1363 unsupported curve name: %q", curveName) + } +} + +func ieeeP1363Encode(sig *Secp256k1Signature, curveName string) ([]byte, error) { + two := 2 + + sigSize, err := ieeeSignatureSize(curveName) + if err != nil { + return nil, err + } + + enc := make([]byte, sigSize) + + // sigR and sigS must be half the size of the signature. If not, we need to pad them with zeros. + offset := 0 + if len(sig.R.Bytes()) < (sigSize / two) { + offset += (sigSize / two) - len(sig.R.Bytes()) + } + + // Copy sigR after any zero-padding. + copy(enc[offset:], sig.R.Bytes()) + + // Skip the bytes of sigR. + offset = sigSize / two + if len(sig.S.Bytes()) < (sigSize / two) { + offset += (sigSize / two) - len(sig.S.Bytes()) + } + + // Copy sigS after sigR and any zero-padding. + copy(enc[offset:], sig.S.Bytes()) + + return enc, nil +} + +func ieeeP1363Decode(encodedBytes []byte) (*Secp256k1Signature, error) { + if len(encodedBytes) == 0 || len(encodedBytes) > 132 || len(encodedBytes)%2 != 0 { + return nil, fmt.Errorf("ecdsa: Invalid IEEE_P1363 encoded bytes") + } + + r := new(big.Int).SetBytes(encodedBytes[:len(encodedBytes)/2]) + s := new(big.Int).SetBytes(encodedBytes[len(encodedBytes)/2:]) + + return &Secp256k1Signature{R: r, S: s}, nil +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1.go new file mode 100644 index 000000000..5e5d4670e --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1.go @@ -0,0 +1,112 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package subtle + +import ( + "crypto/elliptic" + "errors" + "fmt" + "math/big" + + "github.com/btcsuite/btcd/btcec" + + secp256k1pb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto" +) + +var errUnsupportedEncoding = errors.New("secp256k1: unsupported encoding") + +// Secp256k1Signature is a struct holding the r and s values of an secp256k1 signature. +type Secp256k1Signature struct { + R, S *big.Int +} + +// NewSecp256K1Signature creates a new Secp256k1Signature instance. +func NewSecp256K1Signature(r, s *big.Int) *Secp256k1Signature { + return &Secp256k1Signature{R: r, S: s} +} + +// EncodeSecp256K1Signature converts the signature to the given encoding format. +func (sig *Secp256k1Signature) EncodeSecp256K1Signature(encoding, curveName string) ([]byte, error) { + var ( + enc []byte + err error + ) + + switch encoding { + case secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_IEEE_P1363.String(): + enc, err = ieeeP1363Encode(sig, curveName) + case secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER.String(): + enc, err = asn1encode(sig) + default: + err = errUnsupportedEncoding + } + + if err != nil { + return nil, fmt.Errorf("secp256k1: can't convert secp256k1 signature to %s encoding: %w", encoding, err) + } + + return enc, nil +} + +// DecodeSecp256K1Signature creates a new secp256k1 signature using the given byte slice. +// The function assumes that the byte slice is the concatenation of the BigEndian +// representation of two big integer r and s. +func DecodeSecp256K1Signature(encodedBytes []byte, encoding string) (*Secp256k1Signature, error) { + var ( + sig *Secp256k1Signature + err error + ) + + switch encoding { + case secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_IEEE_P1363.String(): + sig, err = ieeeP1363Decode(encodedBytes) + case secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER.String(): + sig, err = asn1decode(encodedBytes) + default: + err = errUnsupportedEncoding + } + + if err != nil { + return nil, fmt.Errorf("secp256k1: %w", err) + } + + return sig, nil +} + +// ValidateSecp256K1Params validates secp256k1 parameters. +// The hash's strength must not be weaker than the curve's strength. +// DER and IEEE_P1363 encodings are supported. +func ValidateSecp256K1Params(hashAlg, curve, encoding string) error { + switch encoding { + case secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER.String(): + case secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_IEEE_P1363.String(): + default: + return errUnsupportedEncoding + } + + switch curve { + case secp256k1pb.BitcoinCurveType_SECP256K1.String(): + if hashAlg != "SHA256" { + return errors.New("invalid hash type, expect SHA-256") + } + default: + return fmt.Errorf("unsupported curve: %s", curve) + } + + return nil +} + +// GetCurve returns the curve object that corresponds to the given curve type. +// It returns null if the curve type is not supported. +func GetCurve(curve string) elliptic.Curve { + switch curve { + case secp256k1pb.BitcoinCurveType_SECP256K1.String(): + return btcec.S256() + default: + return nil + } +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_signer.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_signer.go new file mode 100644 index 000000000..7be2065a6 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_signer.go @@ -0,0 +1,94 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package subtle + +import ( + "crypto/ecdsa" + "crypto/rand" + "errors" + "fmt" + "hash" + "math/big" + + "github.com/google/tink/go/subtle" +) + +// Secp256K1Signer is an implementation of Signer for secp256k1 Secp256k2 (Koblitz curve). +// At the moment, the implementation only accepts DER encoding. +type Secp256K1Signer struct { + privateKey *ecdsa.PrivateKey + hashFunc func() hash.Hash + encoding string +} + +// NewSecp256K1Signer creates a new instance of Secp256K1Signer. +func NewSecp256K1Signer(hashAlg string, + curve string, + encoding string, + keyValue []byte) (*Secp256K1Signer, error) { + privKey := new(ecdsa.PrivateKey) + c := GetCurve(curve) + privKey.PublicKey.Curve = c + privKey.D = new(big.Int).SetBytes(keyValue) + privKey.PublicKey.X, privKey.PublicKey.Y = c.ScalarBaseMult(keyValue) + + return NewSecp256K1SignerFromPrivateKey(hashAlg, encoding, privKey) +} + +// NewSecp256K1SignerFromPrivateKey creates a new instance of Secp256K1Signer. +func NewSecp256K1SignerFromPrivateKey(hashAlg string, encoding string, + privateKey *ecdsa.PrivateKey) (*Secp256K1Signer, error) { + if privateKey.Curve == nil { + return nil, errors.New("secp256k1_signer: privateKey.Curve can't be nil") + } + + curve := ConvertCurveName(privateKey.Curve.Params().Name) + if err := ValidateSecp256K1Params(hashAlg, curve, encoding); err != nil { + return nil, fmt.Errorf("secp256k1_signer: %w", err) + } + + hashFunc := subtle.GetHashFunc(hashAlg) + + return &Secp256K1Signer{ + privateKey: privateKey, + hashFunc: hashFunc, + encoding: encoding, + }, nil +} + +// Sign computes a signature for the given data. +func (e *Secp256K1Signer) Sign(data []byte) ([]byte, error) { + hashed, err := subtle.ComputeHash(e.hashFunc, data) + if err != nil { + return nil, err + } + + r, s, err := ecdsa.Sign(rand.Reader, e.privateKey, hashed) + if err != nil { + return nil, fmt.Errorf("secp256k1_signer: signing failed: %w", err) + } + + // format the signature + sig := NewSecp256K1Signature(r, s) + + ret, err := sig.EncodeSecp256K1Signature(e.encoding, e.privateKey.PublicKey.Curve.Params().Name) + if err != nil { + return nil, fmt.Errorf("secp256k1_signer: signing failed: %w", err) + } + + return ret, nil +} + +// ConvertCurveName converts different forms of a curve name to the name that tink recognizes. +func ConvertCurveName(name string) string { + switch name { + case "secp256k1", "secp256K1": + return "SECP256K1" + default: + return "" + } +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_signer_verifier_test.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_signer_verifier_test.go new file mode 100644 index 000000000..f5a5409a4 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_signer_verifier_test.go @@ -0,0 +1,56 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package subtle_test + +import ( + "crypto/ecdsa" + "crypto/rand" + "testing" + + "github.com/google/tink/go/subtle/random" + "github.com/stretchr/testify/require" + + subtleSignature "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle" +) + +func TestSignVerify(t *testing.T) { + data := random.GetRandomBytes(20) + hash := "SHA256" + curve := "SECP256K1" + encodings := []string{"Bitcoin_DER", "Bitcoin_IEEE_P1363"} + + for _, encoding := range encodings { + priv, err := ecdsa.GenerateKey(subtleSignature.GetCurve(curve), rand.Reader) + require.NoError(t, err) + + // Use the private key and public key directly to create new instances + signer, err := subtleSignature.NewSecp256K1SignerFromPrivateKey(hash, encoding, priv) + require.NoError(t, err, "unexpected error when creating Secp256K1Signer") + + verifier, err := subtleSignature.NewSecp256K1VerifierFromPublicKey(hash, encoding, &priv.PublicKey) + require.NoError(t, err, "unexpected error when creating ECDSAVerifier") + + signature, err := signer.Sign(data) + require.NoError(t, err, "unexpected error when signing") + + err = verifier.Verify(signature, data) + require.NoError(t, err, "unexpected error when verifying") + + // Use byte slices to create new instances + signer, err = subtleSignature.NewSecp256K1Signer(hash, curve, encoding, priv.D.Bytes()) + require.NoError(t, err, "unexpected error when creating Secp256K1Signer") + + verifier, err = subtleSignature.NewSecp256K1Verifier(hash, curve, encoding, priv.X.Bytes(), priv.Y.Bytes()) + require.NoError(t, err, "unexpected error when creating ECDSAVerifier") + + signature, err = signer.Sign(data) + require.NoError(t, err, "unexpected error when signing") + + err = verifier.Verify(signature, data) + require.NoError(t, err, "unexpected error when verifying") + } +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_verifier.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_verifier.go new file mode 100644 index 000000000..e72a807a8 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/secp256k1_verifier.go @@ -0,0 +1,79 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package subtle + +import ( + "crypto/ecdsa" + "errors" + "fmt" + "hash" + "math/big" + + "github.com/google/tink/go/subtle" +) + +var errInvalidSecp256K1Signature = errors.New("secp256k1_verifier: invalid signature") + +// ECDSAVerifier is an implementation of Verifier for ECDSA. +// At the moment, the implementation only accepts signatures with strict DER encoding. +type ECDSAVerifier struct { + publicKey *ecdsa.PublicKey + hashFunc func() hash.Hash + encoding string +} + +// NewSecp256K1Verifier creates a new instance of Secp256K1Verifier. +func NewSecp256K1Verifier(hashAlg, curve, encoding string, x, y []byte) (*ECDSAVerifier, error) { + publicKey := &ecdsa.PublicKey{ + Curve: GetCurve(curve), + X: new(big.Int).SetBytes(x), + Y: new(big.Int).SetBytes(y), + } + + return NewSecp256K1VerifierFromPublicKey(hashAlg, encoding, publicKey) +} + +// NewSecp256K1VerifierFromPublicKey creates a new instance of ECDSAVerifier. +func NewSecp256K1VerifierFromPublicKey(hashAlg, encoding string, publicKey *ecdsa.PublicKey) (*ECDSAVerifier, error) { + if publicKey.Curve == nil { + return nil, errors.New("ecdsa_verifier: invalid curve") + } + + curve := ConvertCurveName(publicKey.Curve.Params().Name) + if err := ValidateSecp256K1Params(hashAlg, curve, encoding); err != nil { + return nil, fmt.Errorf("ecdsa_verifier: %w", err) + } + + hashFunc := subtle.GetHashFunc(hashAlg) + + return &ECDSAVerifier{ + publicKey: publicKey, + hashFunc: hashFunc, + encoding: encoding, + }, nil +} + +// Verify verifies whether the given signature is valid for the given data. +// It returns an error if the signature is not valid; nil otherwise. +func (e *ECDSAVerifier) Verify(signatureBytes, data []byte) error { + signature, err := DecodeSecp256K1Signature(signatureBytes, e.encoding) + if err != nil { + return fmt.Errorf("secp256k1_verifier: %w", err) + } + + hashed, err := subtle.ComputeHash(e.hashFunc, data) + if err != nil { + return err + } + + valid := ecdsa.Verify(e.publicKey, hashed, signature.R, signature.S) + if !valid { + return errInvalidSecp256K1Signature + } + + return nil +} diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/subtle.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/subtle.go new file mode 100644 index 000000000..cbae39859 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle/subtle.go @@ -0,0 +1,8 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +// Package subtle provides subtle implementations of the digital signature primitive. +package subtle diff --git a/pkg/crypto/tinkcrypto/primitive/secp256k1/verifier_factory.go b/pkg/crypto/tinkcrypto/primitive/secp256k1/verifier_factory.go new file mode 100644 index 000000000..147a99446 --- /dev/null +++ b/pkg/crypto/tinkcrypto/primitive/secp256k1/verifier_factory.go @@ -0,0 +1,116 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package secp256k1 + +import ( + "errors" + "fmt" + + "github.com/google/tink/go/core/cryptofmt" + "github.com/google/tink/go/core/primitiveset" + "github.com/google/tink/go/core/registry" + "github.com/google/tink/go/keyset" + tinkpb "github.com/google/tink/go/proto/tink_go_proto" + "github.com/google/tink/go/tink" +) + +// NewVerifier returns a Verifier primitive from the given keyset handle. +func NewVerifier(h *keyset.Handle) (tink.Verifier, error) { + return NewVerifierWithKeyManager(h, nil /*keyManager*/) +} + +// NewVerifierWithKeyManager returns a Verifier primitive from the given keyset handle and custom key manager. +// Deprecated: register the KeyManager and use New above. +func NewVerifierWithKeyManager(h *keyset.Handle, km registry.KeyManager) (tink.Verifier, error) { + ps, err := h.PrimitivesWithKeyManager(km) + if err != nil { + return nil, fmt.Errorf("verifier_factory: cannot obtain primitive set: %w", err) + } + + return newWrappedVerifier(ps) +} + +// verifierSet is a Verifier implementation that uses the +// underlying primitive set for verifying. +type wrappedVerifier struct { + ps *primitiveset.PrimitiveSet +} + +// Asserts that verifierSet implements the Verifier interface. +var _ tink.Verifier = (*wrappedVerifier)(nil) + +func newWrappedVerifier(ps *primitiveset.PrimitiveSet) (*wrappedVerifier, error) { + if _, ok := (ps.Primary.Primitive).(tink.Verifier); !ok { + return nil, fmt.Errorf("verifier_factory: not a Verifier primitive") + } + + for _, primitives := range ps.Entries { + for _, p := range primitives { + if _, ok := (p.Primitive).(tink.Verifier); !ok { + return nil, fmt.Errorf("verifier_factory: not an Verifier primitive") + } + } + } + + ret := new(wrappedVerifier) + ret.ps = ps + + return ret, nil +} + +var errInvalidSignature = errors.New("verifier_factory: invalid signature") + +// Verify checks whether the given signature is a valid signature of the given data. +// nolint:gocyclo +func (v *wrappedVerifier) Verify(signature, data []byte) error { + prefixSize := cryptofmt.NonRawPrefixSize + if len(signature) < prefixSize { + return errInvalidSignature + } + + // try non-raw keys + prefix := signature[:prefixSize] + signatureNoPrefix := signature[prefixSize:] + + entries, err := v.ps.EntriesForPrefix(string(prefix)) + if err == nil { + for i := 0; i < len(entries); i++ { + var signedData []byte + if entries[i].PrefixType == tinkpb.OutputPrefixType_LEGACY { + signedData = append(data, byte(0)) //nolint:gocritic + } else { + signedData = data + } + + verifier, ok := (entries[i].Primitive).(tink.Verifier) + if !ok { + return fmt.Errorf("verifier_factory: not an Verifier primitive") + } + + if err = verifier.Verify(signatureNoPrefix, signedData); err == nil { + return nil + } + } + } + + // try raw keys + entries, err = v.ps.RawEntries() + if err == nil { + for i := 0; i < len(entries); i++ { + verifier, ok := (entries[i].Primitive).(tink.Verifier) + if !ok { + return fmt.Errorf("verifier_factory: not an Verifier primitive") + } + + if err = verifier.Verify(signature, data); err == nil { + return nil + } + } + } + + return errInvalidSignature +} diff --git a/pkg/crypto/webkms/remotecrypto.go b/pkg/crypto/webkms/remotecrypto.go index 5535f51db..f02e1e242 100644 --- a/pkg/crypto/webkms/remotecrypto.go +++ b/pkg/crypto/webkms/remotecrypto.go @@ -263,9 +263,10 @@ func (r *RemoteCrypto) doHTTPRequest(method, destination string, mReq []byte) (* // Encrypt will remotely encrypt msg and aad using a matching AEAD primitive in a remote key handle at keyURL of // a public key. // returns: -// cipherText in []byte -// nonce in []byte -// error in case of errors during encryption +// +// cipherText in []byte +// nonce in []byte +// error in case of errors during encryption func (r *RemoteCrypto) Encrypt(msg, aad []byte, keyURL interface{}) ([]byte, []byte, error) { startEncrypt := time.Now() destination := fmt.Sprintf("%s", keyURL) + encryptURI @@ -312,8 +313,9 @@ func (r *RemoteCrypto) Encrypt(msg, aad []byte, keyURL interface{}) ([]byte, []b // Decrypt will remotely decrypt cipher with aad and given nonce using a matching AEAD primitive in a remote key handle // at keyURL of a private key. // returns: -// plainText in []byte -// error in case of errors +// +// plainText in []byte +// error in case of errors func (r *RemoteCrypto) Decrypt(cipher, aad, nonce []byte, keyURL interface{}) ([]byte, error) { startDecrypt := time.Now() destination := fmt.Sprintf("%s", keyURL) + decryptURI @@ -360,8 +362,9 @@ func (r *RemoteCrypto) Decrypt(cipher, aad, nonce []byte, keyURL interface{}) ([ // Sign will remotely sign msg using a matching signature primitive in remote kh key handle at keyURL of a private key. // returns: -// signature in []byte -// error in case of errors +// +// signature in []byte +// error in case of errors func (r *RemoteCrypto) Sign(msg []byte, keyURL interface{}) ([]byte, error) { startSign := time.Now() destination := fmt.Sprintf("%s", keyURL) + signURI @@ -407,7 +410,8 @@ func (r *RemoteCrypto) Sign(msg []byte, keyURL interface{}) ([]byte, error) { // Verify will remotely verify a signature for the given msg using a matching signature primitive in a remote key // handle at keyURL of a public key. // returns: -// error in case of errors or nil if signature verification was successful +// +// error in case of errors or nil if signature verification was successful func (r *RemoteCrypto) Verify(signature, msg []byte, keyURL interface{}) error { startVerify := time.Now() destination := fmt.Sprintf("%s", keyURL) + verifyURI @@ -539,8 +543,9 @@ func (r *RemoteCrypto) VerifyMAC(mac, data []byte, keyURL interface{}) error { // 'opts' allows setting the option sender key handle using WithSender() option where the sender key handle consists // of a remote key located in the option as a keyURL. This option allows ECDH-1PU key wrapping (aka Authcrypt). // The absence of this option uses ECDH-ES key wrapping (aka Anoncrypt). -// RecipientWrappedKey containing the wrapped cek value -// error in case of errors +// +// RecipientWrappedKey containing the wrapped cek value +// error in case of errors func (r *RemoteCrypto) WrapKey(cek, apu, apv []byte, recPubKey *crypto.PublicKey, // nolint:funlen opts ...crypto.WrapKeyOpts) (*crypto.RecipientWrappedKey, error) { startWrapKey := time.Now() @@ -621,8 +626,9 @@ func (r *RemoteCrypto) buildWrappedKeyResponse(respBody []byte, dest string) (*c // of a remote key located in the option as a keyURL. This options allows ECDH-1PU key unwrapping (aka Authcrypt). // The absence of this option uses ECDH-ES key unwrapping (aka Anoncrypt). // returns: -// unwrapped key in raw bytes -// error in case of errors +// +// unwrapped key in raw bytes +// error in case of errors func (r *RemoteCrypto) UnwrapKey(recWK *crypto.RecipientWrappedKey, keyURL interface{}, opts ...crypto.WrapKeyOpts) ([]byte, error) { startUnwrapKey := time.Now() @@ -700,8 +706,9 @@ func ksToCryptoPublicKey(ks interface{}) (*crypto.PublicKey, error) { // SignMulti will create a BBS+ signature of messages using the signer's private key handle found at signerKeyURL. // returns: -// signature in []byte -// error in case of errors +// +// signature in []byte +// error in case of errors func (r *RemoteCrypto) SignMulti(messages [][]byte, signerKeyURL interface{}) ([]byte, error) { startSign := time.Now() destination := fmt.Sprintf("%s", signerKeyURL) + signMultiURI @@ -746,7 +753,8 @@ func (r *RemoteCrypto) SignMulti(messages [][]byte, signerKeyURL interface{}) ([ // VerifyMulti will BBS+ verify a signature of messages against the signer's public key handle found at signerKeyURL. // returns: -// error in case of errors or nil if signature verification was successful +// +// error in case of errors or nil if signature verification was successful func (r *RemoteCrypto) VerifyMulti(messages [][]byte, signature []byte, signerKeyURL interface{}) error { startVerify := time.Now() destination := fmt.Sprintf("%s", signerKeyURL) + verifyMultiURI @@ -781,7 +789,8 @@ func (r *RemoteCrypto) VerifyMulti(messages [][]byte, signature []byte, signerKe // VerifyProof will verify a BBS+ signature proof (generated e.g. by Verifier's DeriveProof() call) for revealedMessages // with the signer's public key handle found at signerKeyURL. // returns: -// error in case of errors or nil if signature proof verification was successful +// +// error in case of errors or nil if signature proof verification was successful func (r *RemoteCrypto) VerifyProof(revealedMessages [][]byte, proof, nonce []byte, signerKeyURL interface{}) error { startVerifyProof := time.Now() destination := fmt.Sprintf("%s", signerKeyURL) + verifyProofURI @@ -817,8 +826,9 @@ func (r *RemoteCrypto) VerifyProof(revealedMessages [][]byte, proof, nonce []byt // DeriveProof will create a BBS+ signature proof for a list of revealed messages using BBS signature (can be built // using a Signer's SignMulti() call) and the signer's public key handle found at signerKeyURL. // returns: -// signature proof in []byte -// error in case of errors +// +// signature proof in []byte +// error in case of errors func (r *RemoteCrypto) DeriveProof(messages [][]byte, bbsSignature, nonce []byte, revealedIndexes []int, signerKeyURL interface{}) ([]byte, error) { startDeriveProof := time.Now() @@ -867,8 +877,9 @@ func (r *RemoteCrypto) DeriveProof(messages [][]byte, bbsSignature, nonce []byte // Blind will blind provided values with MasterSecret provided in a kh // returns: -// blinded values in []byte -// error in case of errors +// +// blinded values in []byte +// error in case of errors func (r *RemoteCrypto) Blind(kh interface{}, values ...map[string]interface{}) ([][]byte, error) { startBlind := time.Now() destination := fmt.Sprintf("%s", kh) + blindURI @@ -913,8 +924,9 @@ func (r *RemoteCrypto) Blind(kh interface{}, values ...map[string]interface{}) ( // GetCorrectnessProof will return correctness proof for a public key handle // returns: -// correctness proof in []byte -// error in case of errors +// +// correctness proof in []byte +// error in case of errors func (r *RemoteCrypto) GetCorrectnessProof(kh interface{}) ([]byte, error) { startGet := time.Now() destination := fmt.Sprintf("%s", kh) + correctnessProofURI @@ -951,9 +963,10 @@ func (r *RemoteCrypto) GetCorrectnessProof(kh interface{}) ([]byte, error) { // SignWithSecrets will generate a signature and related correctness proof // for the provided values using secrets and related DID // returns: -// signature in []byte -// correctness proof in []byte -// error in case of errors +// +// signature in []byte +// correctness proof in []byte +// error in case of errors func (r *RemoteCrypto) SignWithSecrets(kh interface{}, values map[string]interface{}, secrets []byte, correctnessProof []byte, nonces [][]byte, did string) ([]byte, []byte, error) { startSign := time.Now() @@ -984,7 +997,7 @@ func (r *RemoteCrypto) SignWithSecrets(kh interface{}, values map[string]interfa return nil, nil, fmt.Errorf("posting CL SignWithSecrets returned http error: %s", resp.Status) } - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { return nil, nil, fmt.Errorf("read response for CL SignWithSecrets failed [%s, %w]", destination, err) } diff --git a/pkg/doc/jwt/verifier.go b/pkg/doc/jwt/verifier.go index 4b7c3e08a..7d2d3834d 100644 --- a/pkg/doc/jwt/verifier.go +++ b/pkg/doc/jwt/verifier.go @@ -98,7 +98,7 @@ func GetVerifier(publicKey *verifier.PublicKey) (*BasicVerifier, error) { v = verifier.NewECDSAES521SignatureVerifier() case kmsapi.ED25519Type: v = verifier.NewEd25519SignatureVerifier() - case kmsapi.ECDSASecp256k1TypeIEEEP1363: + case kmsapi.ECDSASecp256k1DER, kmsapi.ECDSASecp256k1TypeIEEEP1363: v = verifier.NewECDSASecp256k1SignatureVerifier() case kmsapi.RSAPS256Type: v = verifier.NewRSAPS256SignatureVerifier() diff --git a/pkg/doc/util/jwkkid/kid_creator.go b/pkg/doc/util/jwkkid/kid_creator.go index 57273fad3..78165ec19 100644 --- a/pkg/doc/util/jwkkid/kid_creator.go +++ b/pkg/doc/util/jwkkid/kid_creator.go @@ -17,7 +17,9 @@ import ( "errors" "fmt" "math/big" + "reflect" + "github.com/btcsuite/btcd/btcec" hybrid "github.com/google/tink/go/hybrid/subtle" cryptoapi "github.com/hyperledger/aries-framework-go/pkg/crypto" @@ -32,8 +34,10 @@ var errInvalidKeyType = errors.New("key type is not supported") // CreateKID creates a KID value based on the marshalled keyBytes of type kt. This function should be called for // asymmetric public keys only (ECDSA DER or IEEE-P1363, ED25519, X25519, BLS12381G2). // returns: -// - base64 raw (no padding) URL encoded KID -// - error in case of error +// - base64 raw (no padding) URL encoded KID +// - error in case of error +// +//nolint:gocyclo func CreateKID(keyBytes []byte, kt kms.KeyType) (string, error) { if len(keyBytes) == 0 { return "", errors.New("createKID: empty key") @@ -61,6 +65,13 @@ func CreateKID(keyBytes []byte, kt kms.KeyType) (string, error) { } return bbsKID, nil + case kms.ECDSASecp256k1TypeDER, kms.ECDSASecp256k1TypeIEEEP1363: + secp256k1KID, err := secp256k1Thumbprint(keyBytes, kt) + if err != nil { + return "", fmt.Errorf("createKID: %w", err) + } + + return secp256k1KID, nil } j, err := BuildJWK(keyBytes, kt) @@ -76,6 +87,100 @@ func CreateKID(keyBytes []byte, kt kms.KeyType) (string, error) { return base64.RawURLEncoding.EncodeToString(tp), nil } +func secp256k1Thumbprint(keyBytes []byte, kt kms.KeyType) (string, error) { + switch kt { + case kms.ECDSASecp256k1IEEEP1363: + case kms.ECDSASecp256k1DER: + default: + return "", fmt.Errorf("secp256k1Thumbprint: invalid key type: %s", kt) + } + + j, err := BuildJWK(keyBytes, kt) + if err != nil { + return "", fmt.Errorf("secp256k1Thumbprint: failed to build jwk: %w", err) + } + + var input string + + switch key := j.Key.(type) { + case *ecdsa.PublicKey: + input, err = secp256k1ThumbprintInput(key.Curve, key.X, key.Y) + if err != nil { + return "", fmt.Errorf("secp256k1Thumbprint: failed to get public key thumbprint input: %w", err) + } + case *ecdsa.PrivateKey: + input, err = secp256k1ThumbprintInput(key.Curve, key.X, key.Y) + if err != nil { + return "", fmt.Errorf("secp256k1Thumbprint: failed to get private key thumbprint input: %w", err) + } + default: + return "", fmt.Errorf("secp256k1Thumbprint: unknown key type '%s'", reflect.TypeOf(key)) + } + + h := crypto.SHA256.New() + _, _ = h.Write([]byte(input)) + + return base64.RawURLEncoding.EncodeToString(h.Sum(nil)), nil +} + +func secp256k1ThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) { + ecSecp256K1ThumbprintTemplate := `{"crv":"SECP256K1","kty":"EC","x":"%s","y":"%s"}` + + coordLength := curveSize(curve) + + if len(x.Bytes()) > coordLength || len(y.Bytes()) > coordLength { + return "", errors.New("invalid elliptic secp256k1 key (too large)") + } + + return fmt.Sprintf(ecSecp256K1ThumbprintTemplate, + newFixedSizeBuffer(x.Bytes(), coordLength).base64(), + newFixedSizeBuffer(y.Bytes(), coordLength).base64()), nil +} + +// byteBuffer represents a slice of bytes that can be serialized to url-safe base64. +type byteBuffer struct { + data []byte +} + +func (b *byteBuffer) base64() string { + return base64.RawURLEncoding.EncodeToString(b.data) +} + +func newBuffer(data []byte) *byteBuffer { + if data == nil { + return nil + } + + return &byteBuffer{ + data: data, + } +} + +func newFixedSizeBuffer(data []byte, length int) *byteBuffer { + if len(data) > length { + panic("newFixedSizeBuffer: invalid call to newFixedSizeBuffer (len(data) > length)") + } + + pad := make([]byte, length-len(data)) + + return newBuffer(append(pad, data...)) +} + +// Get size of curve in bytes. +func curveSize(crv elliptic.Curve) int { + bits := crv.Params().BitSize + byteSize := 8 + + div := bits / byteSize + mod := bits % byteSize + + if mod == 0 { + return div + } + + return div + 1 +} + // BuildJWK builds a go jose JWK from keyBytes with key type kt. func BuildJWK(keyBytes []byte, kt kms.KeyType) (*jwk.JWK, error) { var ( @@ -84,7 +189,7 @@ func BuildJWK(keyBytes []byte, kt kms.KeyType) (*jwk.JWK, error) { ) switch kt { - case kms.ECDSAP256TypeDER, kms.ECDSAP384TypeDER, kms.ECDSAP521TypeDER: + case kms.ECDSAP256TypeDER, kms.ECDSAP384TypeDER, kms.ECDSAP521TypeDER, kms.ECDSASecp256k1DER: j, err = generateJWKFromDERECDSA(keyBytes) if err != nil { return nil, fmt.Errorf("buildJWK: failed to build JWK from ecdsa DER key: %w", err) @@ -96,7 +201,7 @@ func BuildJWK(keyBytes []byte, kt kms.KeyType) (*jwk.JWK, error) { // if err != nil { // return nil, fmt.Errorf("buildJWK: failed to build JWK from ed25519 key: %w", err) // } - case kms.ECDSAP256TypeIEEEP1363, kms.ECDSAP384TypeIEEEP1363, kms.ECDSAP521TypeIEEEP1363: + case kms.ECDSAP256TypeIEEEP1363, kms.ECDSAP384TypeIEEEP1363, kms.ECDSAP521TypeIEEEP1363, kms.ECDSASecp256k1IEEEP1363: c := getCurveByKMSKeyType(kt) x, y := elliptic.Unmarshal(c, keyBytes) @@ -169,6 +274,8 @@ func getCurveByKMSKeyType(kt kms.KeyType) elliptic.Curve { return elliptic.P384() case kms.ECDSAP521TypeIEEEP1363: return elliptic.P521() + case kms.ECDSASecp256k1TypeIEEEP1363: + return btcec.S256() } // should never be called but added for linting diff --git a/pkg/doc/util/jwkkid/kid_creator_test.go b/pkg/doc/util/jwkkid/kid_creator_test.go index 4756e5b24..5ce142a96 100644 --- a/pkg/doc/util/jwkkid/kid_creator_test.go +++ b/pkg/doc/util/jwkkid/kid_creator_test.go @@ -20,6 +20,7 @@ import ( "strings" "testing" + "github.com/btcsuite/btcd/btcec" commonpb "github.com/google/tink/go/proto/common_go_proto" "github.com/stretchr/testify/require" @@ -272,3 +273,24 @@ func TestCreateBLS12381G2KID(t *testing.T) { _, err = CreateKID(append(pubKeyBytes, []byte("larger key")...), kms.BLS12381G2Type) require.EqualError(t, err, "createKID: invalid BBS+ key") } + +func TestCreateSecp256K1KID(t *testing.T) { + secp256k1Key, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + require.NoError(t, err) + + pubKeyBytes := elliptic.Marshal(secp256k1Key.Curve, secp256k1Key.X, secp256k1Key.Y) + + t.Run("create kid for secp256k1 in DER format", func(t *testing.T) { + t.Skipf("Secp256k1 keys are not DER compliant") + + kid, e := CreateKID(pubKeyBytes, kms.ECDSASecp256k1TypeDER) + require.NoError(t, e) + require.NotEmpty(t, kid) + }) + + t.Run("create kid for secp256k1 in IEEE-1363 format", func(t *testing.T) { + kid2, e := CreateKID(pubKeyBytes, kms.ECDSASecp256k1TypeIEEEP1363) + require.NoError(t, e) + require.NotEmpty(t, kid2) + }) +} diff --git a/pkg/doc/util/signature/internal/signer/crypto_signer.go b/pkg/doc/util/signature/internal/signer/crypto_signer.go index 67eeae80d..b60dcf5f2 100644 --- a/pkg/doc/util/signature/internal/signer/crypto_signer.go +++ b/pkg/doc/util/signature/internal/signer/crypto_signer.go @@ -14,6 +14,8 @@ import ( "errors" "fmt" + "github.com/btcsuite/btcd/btcec" + cryptoapi "github.com/hyperledger/aries-framework-go/pkg/crypto" kmsapi "github.com/hyperledger/aries-framework-go/pkg/kms" ) @@ -60,6 +62,8 @@ func (s *CryptoSigner) Alg() string { return p384Alg case elliptic.P521(): return p521Alg + case btcec.S256(): + return secp256Alg } case ed25519.PublicKey: return alg diff --git a/pkg/doc/util/signature/signer.go b/pkg/doc/util/signature/signer.go index 32ef5ffa5..0be06791a 100644 --- a/pkg/doc/util/signature/signer.go +++ b/pkg/doc/util/signature/signer.go @@ -27,7 +27,7 @@ func NewCryptoSigner(crypto cryptoapi.Crypto, kms kmsapi.KeyManager, keyType kms kmsapi.ED25519Type: return signer.NewCryptoSigner(crypto, kms, keyType) - case kmsapi.ECDSASecp256k1TypeIEEEP1363: + case kmsapi.ECDSASecp256k1DER, kmsapi.ECDSASecp256k1TypeIEEEP1363: // TODO use crypto signer when available (https://github.com/hyperledger/aries-framework-go/issues/1285) return signer.NewECDSASecp256k1Signer() diff --git a/pkg/kms/api.go b/pkg/kms/api.go index 68a02e18e..ba2d27797 100644 --- a/pkg/kms/api.go +++ b/pkg/kms/api.go @@ -115,6 +115,8 @@ const ( ECDSAP384DER = "ECDSAP384DER" // ECDSAP521DER key type value. ECDSAP521DER = "ECDSAP521DER" + // ECDSASecp256k1DER key type value. + ECDSASecp256k1DER = "ECDSASecp256k1DER" // ECDSAP256IEEEP1363 key type value. ECDSAP256IEEEP1363 = "ECDSAP256IEEEP1363" // ECDSAP384IEEEP1363 key type value. @@ -163,6 +165,8 @@ const ( XChaCha20Poly1305Type = KeyType(XChaCha20Poly1305) // ECDSAP256TypeDER key type value. ECDSAP256TypeDER = KeyType(ECDSAP256DER) + // ECDSASecp256k1TypeDER key type value. + ECDSASecp256k1TypeDER = KeyType(ECDSASecp256k1DER) // ECDSAP384TypeDER key type value. ECDSAP384TypeDER = KeyType(ECDSAP384DER) // ECDSAP521TypeDER key type value. diff --git a/pkg/kms/localkms/keytemplate.go b/pkg/kms/localkms/keytemplate.go index 599281e3f..52e1f3d75 100644 --- a/pkg/kms/localkms/keytemplate.go +++ b/pkg/kms/localkms/keytemplate.go @@ -19,10 +19,11 @@ import ( "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/bbs" "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdh" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/secp256k1" "github.com/hyperledger/aries-framework-go/pkg/kms" ) -// nolint:gocyclo +// nolint:gocyclo,funlen func keyTemplate(keyType kms.KeyType, _ ...kms.KeyOpts) (*tinkpb.KeyTemplate, error) { switch keyType { case kms.AES128GCMType: @@ -67,6 +68,10 @@ func keyTemplate(keyType kms.KeyType, _ ...kms.KeyOpts) (*tinkpb.KeyTemplate, er return ecdh.X25519ECDHKWKeyTemplate(), nil case kms.BLS12381G2Type: return bbs.BLS12381G2KeyTemplate(), nil + case kms.ECDSASecp256k1DER: + return secp256k1.DERKeyTemplate() + case kms.ECDSASecp256k1IEEEP1363: + return secp256k1.IEEEP1363KeyTemplate() default: return nil, fmt.Errorf("getKeyTemplate: key type '%s' unrecognized", keyType) } diff --git a/pkg/kms/localkms/kid_creator_test.go b/pkg/kms/localkms/kid_creator_test.go index 881c068ba..d293c66d5 100644 --- a/pkg/kms/localkms/kid_creator_test.go +++ b/pkg/kms/localkms/kid_creator_test.go @@ -7,11 +7,14 @@ SPDX-License-Identifier: Apache-2.0 package localkms import ( + "crypto/ecdsa" "crypto/ed25519" + "crypto/elliptic" "crypto/rand" "encoding/json" "testing" + "github.com/btcsuite/btcd/btcec" "github.com/stretchr/testify/require" cryptoapi "github.com/hyperledger/aries-framework-go/pkg/crypto" @@ -20,35 +23,72 @@ import ( ) func TestCreateKID(t *testing.T) { - pubKey, _, err := ed25519.GenerateKey(rand.Reader) - require.NoError(t, err) - - kid, err := CreateKID(pubKey, kms.ED25519Type) - require.NoError(t, err) - require.NotEmpty(t, kid) - - _, err = CreateKID(pubKey, "badType") - require.EqualError(t, err, "createKID: failed to build jwk: buildJWK: key type is not supported: 'badType'") - - badPubKey := ed25519.PublicKey("badKey") - _, err = CreateKID(badPubKey, kms.NISTP256ECDHKWType) - require.EqualError(t, err, "createKID: failed to build jwk: buildJWK: failed to build JWK from ecdh "+ - "key: generateJWKFromECDH: unmarshalECDHKey: failed to unmarshal ECDH key: invalid character 'b' looking for "+ - "beginning of value") - - randomKey := make([]byte, 32) - _, err = rand.Read(randomKey) - require.NoError(t, err) - - x25519Key := &cryptoapi.PublicKey{ - Curve: "X25519", - Type: ecdhpb.KeyType_OKP.String(), - X: randomKey, - } - mX25519Key, err := json.Marshal(x25519Key) - require.NoError(t, err) - - kid, err = CreateKID(mX25519Key, kms.X25519ECDHKWType) - require.NoError(t, err) - require.NotEmpty(t, kid) + t.Run("ED25519 KID", func(t *testing.T) { + pubKey, _, err := ed25519.GenerateKey(rand.Reader) + require.NoError(t, err) + + kid, err := CreateKID(pubKey, kms.ED25519Type) + require.NoError(t, err) + require.NotEmpty(t, kid) + + t.Run("KID for invalid keys", func(t *testing.T) { + _, err = CreateKID(pubKey, "badType") + require.EqualError(t, err, "createKID: failed to build jwk: buildJWK: key type is not supported: 'badType'") + + badPubKey := ed25519.PublicKey("badKey") + _, err = CreateKID(badPubKey, kms.NISTP256ECDHKWType) + require.EqualError(t, err, "createKID: failed to build jwk: buildJWK: failed to build JWK from ecdh "+ + "key: generateJWKFromECDH: unmarshalECDHKey: failed to unmarshal ECDH key: invalid character 'b' looking for "+ + "beginning of value") + }) + }) + + t.Run("X25519ECDH KID", func(t *testing.T) { + var kid string + + randomKey := make([]byte, 32) + _, err := rand.Read(randomKey) + require.NoError(t, err) + + x25519Key := &cryptoapi.PublicKey{ + Curve: "X25519", + Type: ecdhpb.KeyType_OKP.String(), + X: randomKey, + } + mX25519Key, err := json.Marshal(x25519Key) + require.NoError(t, err) + + kid, err = CreateKID(mX25519Key, kms.X25519ECDHKWType) + require.NoError(t, err) + require.NotEmpty(t, kid) + }) + + t.Run("ECDSA secp256k1 DER format KID", func(t *testing.T) { + t.Skip("DER format does not support secp256k1 curve") + var kid string + + secp256k1Key, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + require.NoError(t, err) + + pubECKeyBytes := elliptic.Marshal(secp256k1Key.Curve, secp256k1Key.X, secp256k1Key.Y) + require.NoError(t, err) + + kid, err = CreateKID(pubECKeyBytes, kms.ECDSASecp256k1DER) + require.NoError(t, err) + require.NotEmpty(t, kid) + }) + + t.Run("ECDSA secp256k1 IEEE-P1363 format KID", func(t *testing.T) { + var kid string + + secp256k1Key, err := ecdsa.GenerateKey(btcec.S256(), rand.Reader) + require.NoError(t, err) + + pubECKeyBytes := elliptic.Marshal(secp256k1Key.Curve, secp256k1Key.X, secp256k1Key.Y) + require.NoError(t, err) + + kid, err = CreateKID(pubECKeyBytes, kms.ECDSASecp256k1IEEEP1363) + require.NoError(t, err) + require.NotEmpty(t, kid) + }) } diff --git a/pkg/kms/localkms/localkms.go b/pkg/kms/localkms/localkms.go index 633313cbf..7ba0bdb2b 100644 --- a/pkg/kms/localkms/localkms.go +++ b/pkg/kms/localkms/localkms.go @@ -79,14 +79,18 @@ func (l *LocalKMS) HealthCheck() error { // Create a new key/keyset/key handle for the type kt // Returns: -// - keyID of the handle -// - handle instance (to private key) -// - error if failure +// - keyID of the handle +// - handle instance (to private key) +// - error if failure func (l *LocalKMS) Create(kt kms.KeyType, opts ...kms.KeyOpts) (string, interface{}, error) { if kt == "" { return "", nil, fmt.Errorf("failed to create new key, missing key type") } + if kt == kms.ECDSASecp256k1DER { + return "", nil, fmt.Errorf("create: Unable to create kms key: Secp256K1 is not supported by DER format") + } + keyTemplate, err := getKeyTemplate(kt, opts...) if err != nil { return "", nil, fmt.Errorf("create: failed to getKeyTemplate: %w", err) @@ -107,8 +111,8 @@ func (l *LocalKMS) Create(kt kms.KeyType, opts ...kms.KeyOpts) (string, interfac // Get key handle for the given keyID // Returns: -// - handle instance (to private key) -// - error if failure +// - handle instance (to private key) +// - error if failure func (l *LocalKMS) Get(keyID string) (interface{}, error) { return l.getKeySet(keyID) } @@ -116,9 +120,9 @@ func (l *LocalKMS) Get(keyID string) (interface{}, error) { // Rotate a key referenced by keyID and return a new handle of a keyset including old key and // new key with type kt. It also returns the updated keyID as the first return value // Returns: -// - new KeyID -// - handle instance (to private key) -// - error if failure +// - new KeyID +// - handle instance (to private key) +// - error if failure func (l *LocalKMS) Rotate(kt kms.KeyType, keyID string, opts ...kms.KeyOpts) (string, interface{}, error) { kh, err := l.getKeySet(keyID) if err != nil { @@ -222,8 +226,8 @@ func (l *LocalKMS) getKeySet(id string) (*keyset.Handle, error) { // ExportPubKeyBytes will fetch a key referenced by id then gets its public key in raw bytes and returns it. // The key must be an asymmetric key. // Returns: -// - marshalled public key []byte -// - error if it fails to export the public key bytes +// - marshalled public key []byte +// - error if it fails to export the public key bytes func (l *LocalKMS) ExportPubKeyBytes(id string) ([]byte, kms.KeyType, error) { kh, err := l.getKeySet(id) if err != nil { @@ -280,9 +284,9 @@ func (l *LocalKMS) exportPubKeyBytes(kh *keyset.Handle) ([]byte, kms.KeyType, er // CreateAndExportPubKeyBytes will create a key of type kt and export its public key in raw bytes and returns it. // The key must be an asymmetric key. // Returns: -// - keyID of the new handle created. -// - marshalled public key []byte -// - error if it fails to export the public key bytes +// - keyID of the new handle created. +// - marshalled public key []byte +// - error if it fails to export the public key bytes func (l *LocalKMS) CreateAndExportPubKeyBytes(kt kms.KeyType, opts ...kms.KeyOpts) (string, []byte, error) { kid, _, err := l.Create(kt, opts...) if err != nil { @@ -312,9 +316,9 @@ func (l *LocalKMS) PubKeyBytesToHandle(pubKey []byte, kt kms.KeyType, opts ...km // 'opts' allows setting the keysetID of the imported key using WithKeyID() option. If the ID is already used, // then an error is returned. // Returns: -// - keyID of the handle -// - handle instance (to private key) -// - error if import failure (key empty, invalid, doesn't match keyType, unsupported keyType or storing key failed) +// - keyID of the handle +// - handle instance (to private key) +// - error if import failure (key empty, invalid, doesn't match keyType, unsupported keyType or storing key failed) func (l *LocalKMS) ImportPrivateKey(privKey interface{}, kt kms.KeyType, opts ...kms.PrivateKeyOpts) (string, interface{}, error) { switch pk := privKey.(type) { diff --git a/pkg/kms/localkms/localkms_test.go b/pkg/kms/localkms/localkms_test.go index eb0ecc88a..35b84aa6a 100644 --- a/pkg/kms/localkms/localkms_test.go +++ b/pkg/kms/localkms/localkms_test.go @@ -22,6 +22,7 @@ import ( "strings" "testing" + "github.com/btcsuite/btcd/btcec" "github.com/google/tink/go/keyset" "github.com/google/tink/go/subtle/random" "github.com/stretchr/testify/require" @@ -332,9 +333,19 @@ func TestLocalKMS_Success(t *testing.T) { kms.NISTP521ECDHKWType, kms.X25519ECDHKWType, kms.BLS12381G2Type, + kms.ECDSASecp256k1DER, + kms.ECDSASecp256k1IEEEP1363, } for _, v := range keyTemplates { + if v == kms.ECDSASecp256k1DER { + t.Logf("testing create for %s", v) + _, _, e := kmsService.Create(v) + require.EqualError(t, e, "create: Unable to create kms key: Secp256K1 is not supported by DER format") + + continue + } + // test Create() a new key keyID, newKeyHandle, e := kmsService.Create(v) require.NoError(t, e, "failed on template %v", v) @@ -470,6 +481,16 @@ func TestLocalKMS_ImportPrivateKey(t *testing.T) { // nolint:gocyclo keyType: kms.ECDSAP521TypeIEEEP1363, curve: elliptic.P521(), }, + /*{ + tcName: "import private key using ECDSAP256DER type", + keyType: kms.ECDSASecp256k1DER, + curve: btcec.S256(), + },*/ + { + tcName: "import private key using ECDSAP256IEEEP1363 type", + keyType: kms.ECDSASecp256k1IEEEP1363, + curve: btcec.S256(), + }, { tcName: "import private key using ED25519Type type", keyType: kms.ED25519Type, @@ -570,10 +591,11 @@ func TestLocalKMS_ImportPrivateKey(t *testing.T) { // nolint:gocyclo var expectedPubKey []byte switch tt.keyType { - case kms.ECDSAP256TypeDER, kms.ECDSAP384TypeDER, kms.ECDSAP521TypeDER: + case kms.ECDSAP256TypeDER, kms.ECDSAP384TypeDER, kms.ECDSAP521TypeDER, kms.ECDSASecp256k1TypeDER: expectedPubKey, err = x509.MarshalPKIXPublicKey(privKey.Public()) require.NoError(t, err) - case kms.ECDSAP256TypeIEEEP1363, kms.ECDSAP384TypeIEEEP1363, kms.ECDSAP521TypeIEEEP1363: + case kms.ECDSAP256TypeIEEEP1363, kms.ECDSAP384TypeIEEEP1363, kms.ECDSAP521TypeIEEEP1363, + kms.ECDSASecp256k1TypeIEEEP1363: expectedPubKey = elliptic.Marshal(tt.curve, privKey.X, privKey.Y) case kms.NISTP256ECDHKWType, kms.NISTP384ECDHKWType, kms.NISTP521ECDHKWType: var curveName string @@ -585,6 +607,8 @@ func TestLocalKMS_ImportPrivateKey(t *testing.T) { // nolint:gocyclo curveName = "NIST_P384" case "P-521": curveName = "NIST_P521" + case "secp256k1": + curveName = "SECP256K1" } cryptoKey := &crypto.PublicKey{ diff --git a/pkg/kms/localkms/privkey_import.go b/pkg/kms/localkms/privkey_import.go index 9bd3e5f69..aa51734f0 100644 --- a/pkg/kms/localkms/privkey_import.go +++ b/pkg/kms/localkms/privkey_import.go @@ -24,17 +24,19 @@ import ( bbspb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/bbs_go_proto" clpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/cl_go_proto" ecdhpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto" + secp256k1pb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto" "github.com/hyperledger/aries-framework-go/pkg/kms" ) const ( - ecdsaSignerTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey" - ed25519SignerTypeURL = "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey" - bbsSignerKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.BBSPrivateKey" - + ecdsaSignerTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey" + ed25519SignerTypeURL = "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey" + bbsSignerKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.BBSPrivateKey" + secp256k1SignerTypeURL = "type.googleapis.com/google.crypto.tink.secp256k1PrivateKey" nistpECDHKWPrivateKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.NistPEcdhKwPrivateKey" ) +//nolint:funlen,gocyclo func (l *LocalKMS) importECDSAKey(privKey *ecdsa.PrivateKey, kt kms.KeyType, opts ...kms.PrivateKeyOpts) (string, *keyset.Handle, error) { var params *ecdsapb.EcdsaParams @@ -83,6 +85,18 @@ func (l *LocalKMS) importECDSAKey(privKey *ecdsa.PrivateKey, kt kms.KeyType, Encoding: ecdsapb.EcdsaSignatureEncoding_IEEE_P1363, HashType: commonpb.HashType_SHA512, } + case kms.ECDSASecp256k1DER: + return l.importSecp256K1Key(privKey, &secp256k1pb.Secp256K1Params{ + HashType: commonpb.HashType_SHA256, + Curve: secp256k1pb.BitcoinCurveType_SECP256K1, + Encoding: secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER, + }) + case kms.ECDSASecp256k1IEEEP1363: + return l.importSecp256K1Key(privKey, &secp256k1pb.Secp256K1Params{ + HashType: commonpb.HashType_SHA256, + Curve: secp256k1pb.BitcoinCurveType_SECP256K1, + Encoding: secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_IEEE_P1363, + }) default: return "", nil, fmt.Errorf("import private EC key failed: invalid ECDSA key type") } @@ -97,6 +111,18 @@ func (l *LocalKMS) importECDSAKey(privKey *ecdsa.PrivateKey, kt kms.KeyType, return l.importKeySet(ks, opts...) } +func (l *LocalKMS) importSecp256K1Key(privKey *ecdsa.PrivateKey, params *secp256k1pb.Secp256K1Params, + opts ...kms.PrivateKeyOpts) (string, *keyset.Handle, error) { + mKeyValue, err := getMarshalledECDSASecp256K1PrivateKey(privKey, params) + if err != nil { + return "", nil, fmt.Errorf("import private EC secp256k1 key failed: %w", err) + } + + ks := newKeySet(secp256k1SignerTypeURL, mKeyValue, tinkpb.KeyData_ASYMMETRIC_PRIVATE) + + return l.importKeySet(ks, opts...) +} + func (l *LocalKMS) buildAndImportECDSAPrivateKeyAsECDHKW(privKey *ecdsa.PrivateKey, kt kms.KeyType, opts ...kms.PrivateKeyOpts) (string, *keyset.Handle, error) { var keyTemplate *tinkpb.KeyTemplate @@ -157,6 +183,12 @@ func getMarshalledECDSAPrivateKey(privKey *ecdsa.PrivateKey, params *ecdsapb.Ecd return proto.Marshal(newProtoECDSAPrivateKey(pubKeyProto, privKey.D.Bytes())) } +func getMarshalledECDSASecp256K1PrivateKey(privKey *ecdsa.PrivateKey, + params *secp256k1pb.Secp256K1Params) ([]byte, error) { + pubKeyProto := newProtoSecp256K1PublicKey(&privKey.PublicKey, params) + return proto.Marshal(newProtoECDSASecp256K1PrivateKey(pubKeyProto, privKey.D.Bytes())) +} + func (l *LocalKMS) importEd25519Key(privKey ed25519.PrivateKey, kt kms.KeyType, opts ...kms.PrivateKeyOpts) (string, *keyset.Handle, error) { if privKey == nil { @@ -236,6 +268,16 @@ func newProtoECDSAPrivateKey(publicKey *ecdsapb.EcdsaPublicKey, keyValue []byte) } } +// newProtoECDSASecp256K1PrivateKey creates a Secp256K1PrivateKey with the specified parameters. +func newProtoECDSASecp256K1PrivateKey(publicKey *secp256k1pb.Secp256K1PublicKey, + keyValue []byte) *secp256k1pb.Secp256K1PrivateKey { + return &secp256k1pb.Secp256K1PrivateKey{ + Version: 0, + PublicKey: publicKey, + KeyValue: keyValue, + } +} + func newProtoEd25519PrivateKey(privateKey ed25519.PrivateKey) (*ed25519pb.Ed25519PrivateKey, error) { pubKey, ok := (privateKey.Public()).(ed25519.PublicKey) if !ok { diff --git a/pkg/kms/localkms/pubkey_reader.go b/pkg/kms/localkms/pubkey_reader.go index 55e1e280f..46db7efd9 100644 --- a/pkg/kms/localkms/pubkey_reader.go +++ b/pkg/kms/localkms/pubkey_reader.go @@ -23,6 +23,8 @@ import ( bbspb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/bbs_go_proto" clpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/cl_go_proto" + secp256k1pb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto" + secp256k1subtle "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle" "github.com/hyperledger/aries-framework-go/pkg/kms" ) @@ -180,6 +182,28 @@ func getMarshalledProtoKeyAndKeyURL(pubKey []byte, kt kms.KeyType, if err != nil { return nil, "", err } + case kms.ECDSASecp256k1DER: + tURL = secp256k1VerifierTypeURL + + keyValue, err = getMarshalledECDSASecp256K1DERKey( + pubKey, + "SECP256K1", + secp256k1pb.BitcoinCurveType_SECP256K1, + commonpb.HashType_SHA256) + if err != nil { + return nil, "", err + } + case kms.ECDSASecp256k1IEEEP1363: + tURL = secp256k1VerifierTypeURL + + keyValue, err = getMarshalledECDSASecp256K1IEEEP1363Key( + pubKey, + "SECP256K1", + secp256k1pb.BitcoinCurveType_SECP256K1, + commonpb.HashType_SHA256) + if err != nil { + return nil, "", err + } default: return nil, "", fmt.Errorf("invalid key type") } @@ -213,6 +237,32 @@ func getMarshalledECDSADERKey(marshaledPubKey []byte, curveName string, c common return getMarshalledECDSAKey(ecPubKey, params) } +func getMarshalledECDSASecp256K1DERKey(marshaledPubKey []byte, curveName string, c secp256k1pb.BitcoinCurveType, + h commonpb.HashType) ([]byte, error) { + curve := secp256k1subtle.GetCurve(curveName) + if curve == nil { + return nil, fmt.Errorf("undefined curve") + } + + pubKey, err := x509.ParsePKIXPublicKey(marshaledPubKey) + if err != nil { + return nil, err + } + + ecPubKey, ok := pubKey.(*ecdsa.PublicKey) + if !ok { + return nil, fmt.Errorf("public key reader: not an ecdsa public key") + } + + params := &secp256k1pb.Secp256K1Params{ + Curve: c, + Encoding: secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER, + HashType: h, + } + + return getMarshalledSecp256Key(ecPubKey, params) +} + func getMarshalledECDSAIEEEP1363Key(marshaledPubKey []byte, curveName string, c commonpb.EllipticCurveType, h commonpb.HashType) ([]byte, error) { curve := subtle.GetCurve(curveName) @@ -235,10 +285,36 @@ func getMarshalledECDSAIEEEP1363Key(marshaledPubKey []byte, curveName string, c return getMarshalledECDSAKey(&ecdsa.PublicKey{X: x, Y: y, Curve: curve}, params) } +func getMarshalledECDSASecp256K1IEEEP1363Key(marshaledPubKey []byte, curveName string, c secp256k1pb.BitcoinCurveType, + h commonpb.HashType) ([]byte, error) { + curve := secp256k1subtle.GetCurve(curveName) + if curve == nil { + return nil, fmt.Errorf("undefined curve") + } + + x, y := elliptic.Unmarshal(curve, marshaledPubKey) + + if x == nil || y == nil { + return nil, fmt.Errorf("failed to unamrshal public ecdsa key") + } + + params := &secp256k1pb.Secp256K1Params{ + Curve: c, + Encoding: secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_IEEE_P1363, + HashType: h, + } + + return getMarshalledSecp256Key(&ecdsa.PublicKey{X: x, Y: y, Curve: curve}, params) +} + func getMarshalledECDSAKey(ecPubKey *ecdsa.PublicKey, params *ecdsapb.EcdsaParams) ([]byte, error) { return proto.Marshal(newProtoECDSAPublicKey(ecPubKey, params)) } +func getMarshalledSecp256Key(ecPubKey *ecdsa.PublicKey, params *secp256k1pb.Secp256K1Params) ([]byte, error) { + return proto.Marshal(newProtoSecp256K1PublicKey(ecPubKey, params)) +} + func newProtoECDSAPublicKey(ecPubKey *ecdsa.PublicKey, params *ecdsapb.EcdsaParams) *ecdsapb.EcdsaPublicKey { return &ecdsapb.EcdsaPublicKey{ Version: 0, @@ -247,3 +323,13 @@ func newProtoECDSAPublicKey(ecPubKey *ecdsa.PublicKey, params *ecdsapb.EcdsaPara Params: params, } } + +func newProtoSecp256K1PublicKey(ecPubKey *ecdsa.PublicKey, + params *secp256k1pb.Secp256K1Params) *secp256k1pb.Secp256K1PublicKey { + return &secp256k1pb.Secp256K1PublicKey{ + Version: 0, + X: ecPubKey.X.Bytes(), + Y: ecPubKey.Y.Bytes(), + Params: params, + } +} diff --git a/pkg/kms/localkms/pubkey_writer.go b/pkg/kms/localkms/pubkey_writer.go index 66c6fe848..a44a6e620 100644 --- a/pkg/kms/localkms/pubkey_writer.go +++ b/pkg/kms/localkms/pubkey_writer.go @@ -24,6 +24,8 @@ import ( "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/keyio" bbspb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/bbs_go_proto" clpb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/cl_go_proto" + secp256k1pb "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/proto/secp256k1_go_proto" + secp256k1subtle "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/secp256k1/subtle" "github.com/hyperledger/aries-framework-go/pkg/kms" ) @@ -34,6 +36,7 @@ const ( x25519ECDHKWPublicKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.X25519EcdhKwPublicKey" bbsVerifierKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.BBSPublicKey" clCredDefKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.CLCredDefKey" + secp256k1VerifierTypeURL = "type.googleapis.com/google.crypto.tink.secp256k1PublicKey" derPrefix = "der-" p13163Prefix = "p1363-" ) @@ -43,9 +46,11 @@ var ecdsaKMSKeyTypes = map[string]kms.KeyType{ derPrefix + "NIST_P256": kms.ECDSAP256TypeDER, derPrefix + "NIST_P384": kms.ECDSAP384TypeDER, derPrefix + "NIST_P521": kms.ECDSAP521TypeDER, + derPrefix + "SECP256K1": kms.ECDSASecp256k1DER, p13163Prefix + "NIST_P256": kms.ECDSAP256TypeIEEEP1363, p13163Prefix + "NIST_P384": kms.ECDSAP384TypeIEEEP1363, p13163Prefix + "NIST_P521": kms.ECDSAP521TypeIEEEP1363, + p13163Prefix + "SECP256K1": kms.ECDSASecp256k1IEEEP1363, } // PubKeyWriter will write the raw bytes of a Tink KeySet's primary public key @@ -95,7 +100,8 @@ func write(w io.Writer, msg *tinkpb.Keyset) (kms.KeyType, error) { for _, key := range ks { if key.KeyId == primaryKID && key.Status == tinkpb.KeyStatusType_ENABLED { switch key.KeyData.TypeUrl { - case ecdsaVerifierTypeURL, ed25519VerifierTypeURL, bbsVerifierKeyTypeURL, clCredDefKeyTypeURL: + case ecdsaVerifierTypeURL, ed25519VerifierTypeURL, bbsVerifierKeyTypeURL, clCredDefKeyTypeURL, + secp256k1VerifierTypeURL: created, kt, err = writePubKey(w, key) if err != nil { return "", err @@ -183,6 +189,18 @@ func writePubKey(w io.Writer, key *tinkpb.Keyset_Key) (bool, kms.KeyType, error) copy(marshaledRawPubKey, pubKeyProto.KeyValue) kt = kms.CLCredDefType + case secp256k1VerifierTypeURL: + pubKeyProto := new(secp256k1pb.Secp256K1PublicKey) + + err := proto.Unmarshal(key.KeyData.Value, pubKeyProto) + if err != nil { + return false, "", err + } + + marshaledRawPubKey, kt, err = getMarshalledSecp256K1KeyValueFromProto(pubKeyProto) + if err != nil { + return false, "", err + } default: return false, "", fmt.Errorf("can't export key with keyURL:%s", key.KeyData.TypeUrl) } @@ -220,7 +238,7 @@ func getMarshalledECDSAKeyValueFromProto(pubKeyProto *ecdsapb.EcdsaPublicKey) ([ switch pubKeyProto.Params.Encoding { case ecdsapb.EcdsaSignatureEncoding_DER: - marshaledRawPubKey, err = x509.MarshalPKIXPublicKey(&pubKey) + marshaledRawPubKey, err = x509.MarshalPKIXPublicKey(&pubKey) // DER format not supported here. if err != nil { return nil, "", err } @@ -235,3 +253,44 @@ func getMarshalledECDSAKeyValueFromProto(pubKeyProto *ecdsapb.EcdsaPublicKey) ([ return marshaledRawPubKey, kt, nil } + +func getMarshalledSecp256K1KeyValueFromProto(pkPB *secp256k1pb.Secp256K1PublicKey) ([]byte, kms.KeyType, error) { + var ( + marshaledRawPubKey []byte + err error + kt kms.KeyType + ) + + curveName := secp256k1pb.BitcoinCurveType_name[int32(pkPB.Params.Curve)] + + curve := secp256k1subtle.GetCurve(curveName) + if curve == nil { + return nil, "", fmt.Errorf("undefined curve") + } + + pubKey := ecdsa.PublicKey{ + Curve: curve, + X: new(big.Int), + Y: new(big.Int), + } + + pubKey.X.SetBytes(pkPB.X) + pubKey.Y.SetBytes(pkPB.Y) + + switch pkPB.Params.Encoding { + case secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_DER: + marshaledRawPubKey, err = x509.MarshalPKIXPublicKey(&pubKey) + if err != nil { + return nil, "", err + } + + kt = ecdsaKMSKeyTypes[derPrefix+curveName] + case secp256k1pb.Secp256K1SignatureEncoding_Bitcoin_IEEE_P1363: + marshaledRawPubKey = elliptic.Marshal(curve, pubKey.X, pubKey.Y) + kt = ecdsaKMSKeyTypes[p13163Prefix+curveName] + default: + return nil, "", fmt.Errorf("can't export key with bad key encoding: '%s'", pkPB.Params.Encoding) + } + + return marshaledRawPubKey, kt, nil +}