diff --git a/cmd/rekor-server/app/root.go b/cmd/rekor-server/app/root.go index 0506db5a7..c617e69f2 100644 --- a/cmd/rekor-server/app/root.go +++ b/cmd/rekor-server/app/root.go @@ -98,9 +98,11 @@ func init() { rootCmd.PersistentFlags().String("rekor_server.address", "127.0.0.1", "Address to bind to") rootCmd.PersistentFlags().String("rekor_server.signer", "memory", - `Rekor signer to use. Valid options are: [awskms://keyname, azurekms://keyname, gcpkms://keyname, hashivault://keyname, memory, ]. + `Rekor signer to use. Valid options are: [awskms://keyname, azurekms://keyname, gcpkms://keyname, hashivault://keyname, memory, tink, ]. Memory and file-based signers should only be used for testing.`) rootCmd.PersistentFlags().String("rekor_server.signer-passwd", "", "Password to decrypt signer private key") + rootCmd.PersistentFlags().String("rekor_server.tink_kek_uri", "", "Key encryption key for decrypting Tink keyset. Valid options are [aws-kms://keyname, gcp-kms://keyname]") + rootCmd.PersistentFlags().String("rekor_server.tink_keyset_path", "", "Path to encrypted Tink keyset, containing private key to sign log checkpoints") rootCmd.PersistentFlags().String("rekor_server.new_entry_publisher", "", "URL for pub/sub queue to send messages to when new entries are added to the log. Ignored if not set. Supported providers: [gcppubsub]") rootCmd.PersistentFlags().Bool("rekor_server.publish_events_protobuf", false, "Whether to publish events in Protobuf wire format. Applies to all enabled event types.") diff --git a/go.mod b/go.mod index 827fcb2ac..7bcb2af97 100644 --- a/go.mod +++ b/go.mod @@ -68,6 +68,9 @@ require ( github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.8 github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.8 github.com/sigstore/sigstore/pkg/signature/kms/hashivault v1.8.8 + github.com/tink-crypto/tink-go-awskms/v2 v2.1.0 + github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 + github.com/tink-crypto/tink-go/v2 v2.2.0 golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c ) diff --git a/go.sum b/go.sum index d0357cbca..29a176544 100644 --- a/go.sum +++ b/go.sum @@ -418,6 +418,12 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= github.com/theupdateframework/go-tuf v0.7.0/go.mod h1:uEB7WSY+7ZIugK6R1hiBMBjQftaFzn7ZCDJcp1tCUug= +github.com/tink-crypto/tink-go-awskms/v2 v2.1.0 h1:N9UxlsOzu5mttdjhxkDLbzwtEecuXmlxZVo/ds7JKJI= +github.com/tink-crypto/tink-go-awskms/v2 v2.1.0/go.mod h1:PxSp9GlOkKL9rlybW804uspnHuO9nbD98V/fDX4uSis= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0 h1:3B9i6XBXNTRspfkTC0asN5W0K6GhOSgcujNiECNRNb0= +github.com/tink-crypto/tink-go-gcpkms/v2 v2.2.0/go.mod h1:jY5YN2BqD/KSCHM9SqZPIpJNG/u3zwfLXHgws4x2IRw= +github.com/tink-crypto/tink-go/v2 v2.2.0 h1:L2Da0F2Udh2agtKztdr69mV/KpnY3/lGTkMgLTVIXlA= +github.com/tink-crypto/tink-go/v2 v2.2.0/go.mod h1:JJ6PomeNPF3cJpfWC0lgyTES6zpJILkAX0cJNwlS3xU= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= diff --git a/pkg/api/api.go b/pkg/api/api.go index d6aa774e2..ab85437ae 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -136,7 +136,10 @@ func NewAPI(treeID uint) (*API, error) { ranges.SetActive(tid) rekorSigner, err := signer.New(ctx, viper.GetString("rekor_server.signer"), - viper.GetString("rekor_server.signer-passwd")) + viper.GetString("rekor_server.signer-passwd"), + viper.GetString("rekor_server.tink_kek_uri"), + viper.GetString("rekor_server.tink_keyset_path"), + ) if err != nil { return nil, fmt.Errorf("getting new signer: %w", err) } diff --git a/pkg/signer/memory_test.go b/pkg/signer/memory_test.go index 60d257947..68517d6e2 100644 --- a/pkg/signer/memory_test.go +++ b/pkg/signer/memory_test.go @@ -24,7 +24,7 @@ import ( func TestMemory(t *testing.T) { ctx := context.Background() - m, err := New(ctx, "memory", "") + m, err := New(ctx, "memory", "", "", "") if err != nil { t.Fatalf("new memory: %v", err) } diff --git a/pkg/signer/signer.go b/pkg/signer/signer.go index 6701e8ce3..93d868d4e 100644 --- a/pkg/signer/signer.go +++ b/pkg/signer/signer.go @@ -32,7 +32,7 @@ import ( _ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault" ) -func New(ctx context.Context, signer string, pass string) (signature.Signer, error) { +func New(ctx context.Context, signer, pass, tinkKEKURI, tinkKeysetPath string) (signature.Signer, error) { switch { case slices.ContainsFunc(kms.SupportedProviders(), func(s string) bool { @@ -41,6 +41,8 @@ func New(ctx context.Context, signer string, pass string) (signature.Signer, err return kms.Get(ctx, signer, crypto.SHA256) case signer == MemoryScheme: return NewMemory() + case signer == TinkScheme: + return NewTinkSigner(ctx, tinkKEKURI, tinkKeysetPath) default: return NewFile(signer, pass) } diff --git a/pkg/signer/tink.go b/pkg/signer/tink.go new file mode 100644 index 000000000..b135db591 --- /dev/null +++ b/pkg/signer/tink.go @@ -0,0 +1,87 @@ +// Copyright 2024 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signer + +import ( + "context" + "errors" + "os" + "path/filepath" + "strings" + + tinkUtils "github.com/sigstore/rekor/pkg/signer/tink" + "github.com/tink-crypto/tink-go-awskms/v2/integration/awskms" + "github.com/tink-crypto/tink-go-gcpkms/v2/integration/gcpkms" + "github.com/tink-crypto/tink-go/v2/core/registry" + "github.com/tink-crypto/tink-go/v2/keyset" + "github.com/tink-crypto/tink-go/v2/tink" + + "github.com/sigstore/sigstore/pkg/signature" +) + +const TinkScheme = "tink" + +// NewTinkSignerWithHandle returns a signature.SignerVerifier that wraps crypto.Signer and a hash function. +// Provide a path to the encrypted keyset and cloud KMS key URI for decryption +func NewTinkSigner(ctx context.Context, kekURI, keysetPath string) (signature.Signer, error) { + kek, err := getKeyEncryptionKey(ctx, kekURI) + if err != nil { + return nil, err + } + return NewTinkSignerWithHandle(kek, keysetPath) +} + +// NewTinkSignerWithHandle returns a signature.SignerVerifier that wraps crypto.Signer and a hash function. +// Provide a path to the encrypted keyset and a key handle for decrypting the keyset +func NewTinkSignerWithHandle(kek tink.AEAD, keysetPath string) (signature.Signer, error) { + f, err := os.Open(filepath.Clean(keysetPath)) + if err != nil { + return nil, err + } + defer f.Close() + + kh, err := keyset.Read(keyset.NewJSONReader(f), kek) + if err != nil { + return nil, err + } + signer, hash, err := tinkUtils.KeyHandleToSigner(kh) + if err != nil { + return nil, err + } + return signature.LoadSignerVerifier(signer, hash) +} + +// getKeyEncryptionKey returns a Tink AEAD encryption key from KMS +// Supports GCP and AWS +func getKeyEncryptionKey(ctx context.Context, kmsKey string) (tink.AEAD, error) { + switch { + case strings.HasPrefix(kmsKey, "gcp-kms://"): + gcpClient, err := gcpkms.NewClientWithOptions(ctx, kmsKey) + if err != nil { + return nil, err + } + registry.RegisterKMSClient(gcpClient) + return gcpClient.GetAEAD(kmsKey) + case strings.HasPrefix(kmsKey, "aws-kms://"): + awsClient, err := awskms.NewClientWithOptions(kmsKey) + if err != nil { + return nil, err + } + registry.RegisterKMSClient(awsClient) + return awsClient.GetAEAD(kmsKey) + default: + return nil, errors.New("unsupported KMS key type") + } +} diff --git a/pkg/signer/tink/tink.go b/pkg/signer/tink/tink.go new file mode 100644 index 000000000..bb698b939 --- /dev/null +++ b/pkg/signer/tink/tink.go @@ -0,0 +1,157 @@ +// Copyright 2024 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tink + +// Copy of https://github.com/sigstore/fulcio/blob/a781da9903c63a2cd2c0f0c1c2bfc763196db44f/pkg/ca/tinkca/signer.go +// with a modification for including the hash function when creating the signer + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "errors" + "fmt" + "math/big" + + "github.com/tink-crypto/tink-go/v2/insecurecleartextkeyset" + "github.com/tink-crypto/tink-go/v2/keyset" + commonpb "github.com/tink-crypto/tink-go/v2/proto/common_go_proto" + ecdsapb "github.com/tink-crypto/tink-go/v2/proto/ecdsa_go_proto" + ed25519pb "github.com/tink-crypto/tink-go/v2/proto/ed25519_go_proto" + tinkpb "github.com/tink-crypto/tink-go/v2/proto/tink_go_proto" + signatureSubtle "github.com/tink-crypto/tink-go/v2/signature/subtle" + "github.com/tink-crypto/tink-go/v2/subtle" + "google.golang.org/protobuf/proto" +) + +var ( + ecdsaSignerKeyVersion = 0 + ecdsaSignerTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey" + ed25519SignerKeyVersion = 0 + ed25519SignerTypeURL = "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey" +) + +// KeyHandleToSigner converts a key handle to the crypto.Signer interface. +// Heavily pulls from Tink's signature and subtle packages. +func KeyHandleToSigner(kh *keyset.Handle) (crypto.Signer, crypto.Hash, error) { + // extract the key material from the key handle + ks := insecurecleartextkeyset.KeysetMaterial(kh) + + k := getPrimaryKey(ks) + if k == nil { + return nil, 0, errors.New("no enabled key found in keyset") + } + + switch k.GetTypeUrl() { + case ecdsaSignerTypeURL: + // https://github.com/tink-crypto/tink-go/blob/0aadc94a816408c4bdf95885b3c9860ecfd55fc0/signature/ecdsa/signer_key_manager.go#L48 + privKey := new(ecdsapb.EcdsaPrivateKey) + if err := proto.Unmarshal(k.GetValue(), privKey); err != nil { + return nil, 0, fmt.Errorf("error unmarshalling ecdsa private key: %w", err) + } + if err := validateEcdsaPrivKey(privKey); err != nil { + return nil, 0, fmt.Errorf("error validating ecdsa private key: %w", err) + } + // https://github.com/tink-crypto/tink-go/blob/0aadc94a816408c4bdf95885b3c9860ecfd55fc0/signature/subtle/ecdsa_signer.go#L37 + hashAlg, curve, _ := getECDSAParamNames(privKey.PublicKey.Params) + p := new(ecdsa.PrivateKey) + c := subtle.GetCurve(curve) + if c == nil { + return nil, 0, errors.New("tink ecdsa signer: invalid curve") + } + p.PublicKey.Curve = c + p.D = new(big.Int).SetBytes(privKey.GetKeyValue()) + p.PublicKey.X, p.PublicKey.Y = c.ScalarBaseMult(privKey.GetKeyValue()) + hash := getHashFunc(hashAlg) + return p, hash, nil + case ed25519SignerTypeURL: + // https://github.com/tink-crypto/tink-go/blob/0aadc94a816408c4bdf95885b3c9860ecfd55fc0/signature/ed25519/signer_key_manager.go#L47 + privKey := new(ed25519pb.Ed25519PrivateKey) + if err := proto.Unmarshal(k.GetValue(), privKey); err != nil { + return nil, 0, fmt.Errorf("error unmarshalling ed25519 private key: %w", err) + } + if err := validateEd25519PrivKey(privKey); err != nil { + return nil, 0, fmt.Errorf("error validating ed25519 private key: %w", err) + } + // https://github.com/tink-crypto/tink-go/blob/0aadc94a816408c4bdf95885b3c9860ecfd55fc0/signature/subtle/ed25519_signer.go#L27 + p := ed25519.NewKeyFromSeed(privKey.GetKeyValue()) + return p, crypto.SHA512, nil + default: + return nil, 0, fmt.Errorf("unsupported key type: %s", k.GetTypeUrl()) + } +} + +// getPrimaryKey returns the first enabled key from a keyset. +func getPrimaryKey(ks *tinkpb.Keyset) *tinkpb.KeyData { + for _, k := range ks.GetKey() { + if k.GetKeyId() == ks.GetPrimaryKeyId() && k.GetStatus() == tinkpb.KeyStatusType_ENABLED { + return k.GetKeyData() + } + } + return nil +} + +// validateEcdsaPrivKey validates the given ECDSAPrivateKey. +// https://github.com/tink-crypto/tink-go/blob/0aadc94a816408c4bdf95885b3c9860ecfd55fc0/signature/ecdsa/signer_key_manager.go#L151 +func validateEcdsaPrivKey(key *ecdsapb.EcdsaPrivateKey) error { + if err := keyset.ValidateKeyVersion(key.Version, uint32(ecdsaSignerKeyVersion)); err != nil { + return fmt.Errorf("ecdsa: invalid key version in key: %s", err) + } + if err := keyset.ValidateKeyVersion(key.GetPublicKey().GetVersion(), uint32(ecdsaSignerKeyVersion)); err != nil { + return fmt.Errorf("ecdsa: invalid public version in key: %s", err) + } + hash, curve, encoding := getECDSAParamNames(key.PublicKey.Params) + return signatureSubtle.ValidateECDSAParams(hash, curve, encoding) +} + +// getECDSAParamNames returns the string representations of each parameter in +// the given ECDSAParams. +// https://github.com/tink-crypto/tink-go/blob/0aadc94a816408c4bdf95885b3c9860ecfd55fc0/signature/ecdsa/proto.go#L24 +func getECDSAParamNames(params *ecdsapb.EcdsaParams) (string, string, string) { + hashName := commonpb.HashType_name[int32(params.GetHashType())] + curveName := commonpb.EllipticCurveType_name[int32(params.GetCurve())] + encodingName := ecdsapb.EcdsaSignatureEncoding_name[int32(params.GetEncoding())] + return hashName, curveName, encodingName +} + +// validateEd25519PrivKey validates the given ED25519PrivateKey. +// https://github.com/tink-crypto/tink-go/blob/0aadc94a816408c4bdf95885b3c9860ecfd55fc0/signature/ed25519/signer_key_manager.go#L157 +func validateEd25519PrivKey(key *ed25519pb.Ed25519PrivateKey) error { + if err := keyset.ValidateKeyVersion(key.Version, uint32(ed25519SignerKeyVersion)); err != nil { + return fmt.Errorf("ed25519: invalid key: %w", err) + } + if len(key.KeyValue) != ed25519.SeedSize { + return fmt.Errorf("ed25519: invalid key length, got %d", len(key.KeyValue)) + } + return nil +} + +// getHashFunc returns the hash function for a given hash name +func getHashFunc(hash string) crypto.Hash { + switch hash { + case "SHA1": + return crypto.SHA1 + case "SHA224": + return crypto.SHA224 + case "SHA256": + return crypto.SHA256 + case "SHA384": + return crypto.SHA384 + case "SHA512": + return crypto.SHA512 + default: + return crypto.SHA256 + } +} diff --git a/pkg/signer/tink/tink_test.go b/pkg/signer/tink/tink_test.go new file mode 100644 index 000000000..f2e2e17bf --- /dev/null +++ b/pkg/signer/tink/tink_test.go @@ -0,0 +1,143 @@ +// Copyright 2024 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tink + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rand" + "testing" + + "github.com/tink-crypto/tink-go/v2/keyset" + "github.com/tink-crypto/tink-go/v2/proto/tink_go_proto" + "github.com/tink-crypto/tink-go/v2/signature" +) + +type TestStruct struct { + keyTemplate *tink_go_proto.KeyTemplate + h crypto.Hash +} + +func TestKeyHandleToSignerECDSA(t *testing.T) { + supportedKeyTypes := []TestStruct{ + { + keyTemplate: signature.ECDSAP256KeyWithoutPrefixTemplate(), + h: crypto.SHA256, + }, + { + keyTemplate: signature.ECDSAP384KeyWithoutPrefixTemplate(), + h: crypto.SHA512, + }, + { + keyTemplate: signature.ECDSAP521KeyWithoutPrefixTemplate(), + h: crypto.SHA512, + }, + } + for _, kt := range supportedKeyTypes { + kh, err := keyset.NewHandle(kt.keyTemplate) + if err != nil { + t.Fatalf("error creating ECDSA key handle: %v", err) + } + // convert to crypto.Signer interface + signer, hash, err := KeyHandleToSigner(kh) + if err != nil { + t.Fatalf("error converting ECDSA key handle to signer: %v", err) + } + if hash != kt.h { + t.Fatalf("unexpected hash function, expected %s, got %s", kt.h, hash) + } + msg := []byte("hello there") + + // sign with key handle, verify with signer public key + tinkSigner, err := signature.NewSigner(kh) + if err != nil { + t.Fatalf("error creating tink signer: %v", err) + } + sig, err := tinkSigner.Sign(msg) + if err != nil { + t.Fatalf("error signing with tink signer: %v", err) + } + h := kt.h.New() + h.Write(msg) + digest := h.Sum(nil) + if !ecdsa.VerifyASN1(signer.Public().(*ecdsa.PublicKey), digest, sig) { + t.Fatalf("signature from tink signer did not match") + } + + // sign with signer, verify with key handle + sig, err = ecdsa.SignASN1(rand.Reader, signer.(*ecdsa.PrivateKey), digest) + if err != nil { + t.Fatalf("error signing with crypto signer: %v", err) + } + pubkh, err := kh.Public() + if err != nil { + t.Fatalf("error fetching public key handle: %v", err) + } + v, err := signature.NewVerifier(pubkh) + if err != nil { + t.Fatalf("error creating tink verifier: %v", err) + } + if err := v.Verify(sig, msg); err != nil { + t.Fatalf("error verifying with tink verifier: %v", err) + } + } +} + +func TestKeyHandleToSignerED25519(t *testing.T) { + kh, err := keyset.NewHandle(signature.ED25519KeyWithoutPrefixTemplate()) + if err != nil { + t.Fatalf("error creating ED25519 key handle: %v", err) + } + // convert to crypto.Signer interface + signer, hash, err := KeyHandleToSigner(kh) + if err != nil { + t.Fatalf("error converting ED25519 key handle to signer: %v", err) + } + if hash != crypto.SHA512 { + t.Fatalf("unexpected hash function, expected SHA512, got %s", hash) + } + msg := []byte("hello there") + + // sign with key handle, verify with signer public key + tinkSigner, err := signature.NewSigner(kh) + if err != nil { + t.Fatalf("error creating tink signer: %v", err) + } + sig, err := tinkSigner.Sign(msg) + if err != nil { + t.Fatalf("error signing with tink signer: %v", err) + } + if !ed25519.Verify(signer.Public().(ed25519.PublicKey), msg, sig) { + t.Fatalf("signature from tink signer did not match") + } + + // sign with signer, verify with key handle + sig = ed25519.Sign(signer.(ed25519.PrivateKey), msg) + if err != nil { + t.Fatalf("error signing with crypto signer: %v", err) + } + pubkh, err := kh.Public() + if err != nil { + t.Fatalf("error fetching public key handle: %v", err) + } + v, err := signature.NewVerifier(pubkh) + if err != nil { + t.Fatalf("error creating tink verifier: %v", err) + } + if err := v.Verify(sig, msg); err != nil { + t.Fatalf("error verifying with tink verifier: %v", err) + } +} diff --git a/pkg/signer/tink_test.go b/pkg/signer/tink_test.go new file mode 100644 index 000000000..4d7c9ee60 --- /dev/null +++ b/pkg/signer/tink_test.go @@ -0,0 +1,89 @@ +// Copyright 2024 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signer + +import ( + "os" + "path/filepath" + "strings" + "testing" + + tinkUtils "github.com/sigstore/rekor/pkg/signer/tink" + "github.com/sigstore/sigstore/pkg/cryptoutils" + _ "github.com/sigstore/sigstore/pkg/signature/kms/fake" + "github.com/tink-crypto/tink-go/v2/aead" + "github.com/tink-crypto/tink-go/v2/keyset" + "github.com/tink-crypto/tink-go/v2/signature" +) + +func TestNewTinkCA(t *testing.T) { + aeskh, err := keyset.NewHandle(aead.AES256GCMKeyTemplate()) + if err != nil { + t.Fatalf("error creating AEAD key handle: %v", err) + } + a, err := aead.New(aeskh) + if err != nil { + t.Fatalf("error creating AEAD key: %v", err) + } + + kh, err := keyset.NewHandle(signature.ECDSAP256KeyTemplate()) + if err != nil { + t.Fatalf("error creating ECDSA key handle: %v", err) + } + khsigner, _, err := tinkUtils.KeyHandleToSigner(kh) + if err != nil { + t.Fatalf("error converting ECDSA key handle to signer: %v", err) + } + + dir := t.TempDir() + keysetPath := filepath.Join(dir, "keyset.json.enc") + f, err := os.Create(keysetPath) + if err != nil { + t.Fatalf("error creating file: %v", err) + } + defer f.Close() + jsonWriter := keyset.NewJSONWriter(f) + if err := kh.Write(jsonWriter, a); err != nil { + t.Fatalf("error writing enc keyset: %v", err) + } + + signer, err := NewTinkSignerWithHandle(a, keysetPath) + if err != nil { + t.Fatalf("unexpected error creating signer: %v", err) + } + + // Expect keyset signer and created signer public key match + pubKey, err := signer.PublicKey() + if err != nil { + t.Fatalf("unexpected error creating signer public key: %v", err) + } + if err := cryptoutils.EqualKeys(pubKey, khsigner.Public()); err != nil { + t.Fatalf("keys between CA and signer do not match: %v", err) + } + + // Failure: Unable to decrypt keyset + aeskhAlt, err := keyset.NewHandle(aead.AES256GCMKeyTemplate()) + if err != nil { + t.Fatalf("error creating AEAD key handle: %v", err) + } + aeadAlt, err := aead.New(aeskhAlt) + if err != nil { + t.Fatalf("error creating AEAD key: %v", err) + } + _, err = NewTinkSignerWithHandle(aeadAlt, keysetPath) + if err == nil || !strings.Contains(err.Error(), "decryption failed") { + t.Fatalf("expected error decrypting keyset, got %v", err) + } +}