diff --git a/aead/aesgcm/aesgcm.go b/aead/aesgcm/aesgcm.go index 6d5bc5b..73dedc0 100644 --- a/aead/aesgcm/aesgcm.go +++ b/aead/aesgcm/aesgcm.go @@ -31,13 +31,16 @@ func init() { if err := internalregistry.AllowKeyDerivation(typeURL); err != nil { panic(fmt.Sprintf("aesgcm.init() failed: %v", err)) } - if err := protoserialization.RegisterKeySerializer[*Key](&serializer{}); err != nil { + if err := protoserialization.RegisterKeySerializer[*Key](&keySerializer{}); err != nil { panic(fmt.Sprintf("aesgcm.init() failed: %v", err)) } - if err := protoserialization.RegisterKeyParser(typeURL, &parser{}); err != nil { + if err := protoserialization.RegisterKeyParser(typeURL, &keyParser{}); err != nil { panic(fmt.Sprintf("aesgcm.init() failed: %v", err)) } if err := registryconfig.RegisterPrimitiveConstructor[*Key](primitiveConstructor); err != nil { panic(fmt.Sprintf("aesgcm.init() failed: %v", err)) } + if err := protoserialization.RegisterParametersSerializer[*Parameters](¶metersSerializer{}); err != nil { + panic(fmt.Sprintf("aesgcm.init() failed: %v", err)) + } } diff --git a/aead/aesgcm/aesgcm_test.go b/aead/aesgcm/aesgcm_test.go index da9d263..179067e 100644 --- a/aead/aesgcm/aesgcm_test.go +++ b/aead/aesgcm/aesgcm_test.go @@ -23,7 +23,7 @@ import ( "github.com/tink-crypto/tink-go/v2/keyset" ) -func TestGetAESGCMKeyFromHandle(t *testing.T) { +func TestGetKeyFromHandle(t *testing.T) { keysetHandle, err := keyset.NewHandle(aead.AES128GCMKeyTemplate()) if err != nil { t.Fatalf("keyset.NewHandle(aead.AES128GCMKeyTemplate()) err = %v, want nil", err) @@ -59,7 +59,7 @@ func TestGetAESGCMKeyFromHandle(t *testing.T) { } } -func TestCreateKeysetHandleFromAESGCMKey(t *testing.T) { +func TestCreateKeysetHandleFromKey(t *testing.T) { keysetHandle, err := keyset.NewHandle(aead.AES128GCMKeyTemplate()) if err != nil { t.Fatalf("keyset.NewHandle(aead.AES128GCMKeyTemplate()) err = %v, want nil", err) @@ -111,3 +111,42 @@ func TestCreateKeysetHandleFromAESGCMKey(t *testing.T) { t.Errorf("decrypt = %v, want %v", decrypt, plaintext) } } + +func TestCreateKeysetHandleFromParameters(t *testing.T) { + params, err := aesgcm.NewParameters(aesgcm.ParametersOpts{ + KeySizeInBytes: 32, + IVSizeInBytes: 12, + TagSizeInBytes: 16, + Variant: aesgcm.VariantTink, + }) + if err != nil { + t.Fatalf("aesgcm.NewParameters(%v) err = %v, want nil", params, err) + } + manager := keyset.NewManager() + keyID, err := manager.AddNewKeyFromParameters(params) + if err != nil { + t.Fatalf("manager.AddNewKeyFromParameters(%v) err = %v, want nil", params, err) + } + manager.SetPrimary(keyID) + handle, err := manager.Handle() + if err != nil { + t.Fatalf("manager.Handle() err = %v, want nil", err) + } + aeadPrimitive, err := aead.New(handle) + if err != nil { + t.Fatalf("aead.New(handle) err = %v, want nil", err) + } + plaintext := []byte("plaintext") + additionalData := []byte("additionalData") + ciphertext, err := aeadPrimitive.Encrypt(plaintext, additionalData) + if err != nil { + t.Fatalf("aeadPrimitive.Encrypt(%v, %v) err = %v, want nil", plaintext, additionalData, err) + } + decrypted, err := aeadPrimitive.Decrypt(ciphertext, additionalData) + if err != nil { + t.Fatalf("aeadPrimitive.Decrypt(%v, %v) err = %v, want nil", ciphertext, additionalData, err) + } + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("decrypted = %v, want %v", decrypted, plaintext) + } +} diff --git a/aead/aesgcm/protoserialization.go b/aead/aesgcm/protoserialization.go index 1a7b84d..ec5df4c 100644 --- a/aead/aesgcm/protoserialization.go +++ b/aead/aesgcm/protoserialization.go @@ -33,9 +33,9 @@ const ( protoVersion = 0 ) -type serializer struct{} +type keySerializer struct{} -var _ protoserialization.KeySerializer = (*serializer)(nil) +var _ protoserialization.KeySerializer = (*keySerializer)(nil) func protoOutputPrefixTypeFromVariant(variant Variant) (tinkpb.OutputPrefixType, error) { switch variant { @@ -50,7 +50,7 @@ func protoOutputPrefixTypeFromVariant(variant Variant) (tinkpb.OutputPrefixType, } } -func (s *serializer) SerializeKey(key key.Key) (*protoserialization.KeySerialization, error) { +func (s *keySerializer) SerializeKey(key key.Key) (*protoserialization.KeySerialization, error) { actualKey, ok := key.(*Key) if !ok { return nil, fmt.Errorf("key is not a Key") @@ -82,9 +82,9 @@ func (s *serializer) SerializeKey(key key.Key) (*protoserialization.KeySerializa return protoserialization.NewKeySerialization(keyData, outputPrefixType, idRequirement) } -type parser struct{} +type keyParser struct{} -var _ protoserialization.KeyParser = (*parser)(nil) +var _ protoserialization.KeyParser = (*keyParser)(nil) func variantFromProto(prefixType tinkpb.OutputPrefixType) (Variant, error) { switch prefixType { @@ -99,7 +99,7 @@ func variantFromProto(prefixType tinkpb.OutputPrefixType) (Variant, error) { } } -func (s *parser) ParseKey(keySerialization *protoserialization.KeySerialization) (key.Key, error) { +func (s *keyParser) ParseKey(keySerialization *protoserialization.KeySerialization) (key.Key, error) { if keySerialization == nil { return nil, fmt.Errorf("key serialization is nil") } @@ -132,7 +132,35 @@ func (s *parser) ParseKey(keySerialization *protoserialization.KeySerialization) return nil, err } keyMaterial := secretdata.NewBytesFromData(protoKey.GetKeyValue(), insecuresecretdataaccess.Token{}) - // keySerialization.IDRequirement() returns zero if the key doesn't have a key requirement. + // keySerialization.IDRequirement() returns zero if the key doesn't have a + // key requirement. keyID, _ := keySerialization.IDRequirement() return NewKey(keyMaterial, keyID, params) } + +type parametersSerializer struct{} + +var _ protoserialization.ParametersSerializer = (*parametersSerializer)(nil) + +func (s *parametersSerializer) Serialize(parameters key.Parameters) (*tinkpb.KeyTemplate, error) { + actualParameters, ok := parameters.(*Parameters) + if !ok { + return nil, fmt.Errorf("invalid parameters type: got %T, want *aesgcm.Parameters", parameters) + } + outputPrefixType, err := protoOutputPrefixTypeFromVariant(actualParameters.Variant()) + if err != nil { + return nil, err + } + format := &gcmpb.AesGcmKeyFormat{ + KeySize: uint32(actualParameters.KeySizeInBytes()), + } + serializedFormat, err := proto.Marshal(format) + if err != nil { + return nil, err + } + return &tinkpb.KeyTemplate{ + TypeUrl: typeURL, + OutputPrefixType: outputPrefixType, + Value: serializedFormat, + }, nil +} diff --git a/aead/aesgcm/protoserialization_test.go b/aead/aesgcm/protoserialization_test.go index c314460..4a9cc15 100644 --- a/aead/aesgcm/protoserialization_test.go +++ b/aead/aesgcm/protoserialization_test.go @@ -17,7 +17,9 @@ package aesgcm import ( "testing" + "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" "github.com/tink-crypto/tink-go/v2/insecuresecretdataaccess" "github.com/tink-crypto/tink-go/v2/internal/protoserialization" "github.com/tink-crypto/tink-go/v2/key" @@ -76,7 +78,7 @@ func TestParseKeyFails(t *testing.T) { { name: "invalid AES GCM key size", keyData: &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: serializedKeyWithInvalidSize, KeyMaterialType: tinkpb.KeyData_SYMMETRIC, }, @@ -86,7 +88,7 @@ func TestParseKeyFails(t *testing.T) { { name: "invalid AES GCM key proto serialization", keyData: &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: []byte("invalid proto"), KeyMaterialType: tinkpb.KeyData_SYMMETRIC, }, @@ -96,7 +98,7 @@ func TestParseKeyFails(t *testing.T) { { name: "invalid AES GCM key version", keyData: &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: serializedKeyWithInvalidVersion, KeyMaterialType: tinkpb.KeyData_SYMMETRIC, }, @@ -106,7 +108,7 @@ func TestParseKeyFails(t *testing.T) { { name: "invalid key material type", keyData: &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: serializedKey, KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE, }, @@ -116,7 +118,7 @@ func TestParseKeyFails(t *testing.T) { { name: "invalid output prefix type", keyData: &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: serializedKey, KeyMaterialType: tinkpb.KeyData_SYMMETRIC, }, @@ -125,7 +127,7 @@ func TestParseKeyFails(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - p := &parser{} + p := &keyParser{} keySerialization, err := protoserialization.NewKeySerialization(tc.keyData, tc.outputPrefixType, tc.keyID) if err != nil { t.Fatalf("protoserialization.NewKeySerialization(%v, %v, %v) err = %v, want nil", tc.keyData, tc.outputPrefixType, tc.keyID, err) @@ -164,7 +166,7 @@ func TestParseKey(t *testing.T) { { name: "key with TINK output prefix type", keySerialization: newKeySerialization(t, &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: serializedKey, KeyMaterialType: tinkpb.KeyData_SYMMETRIC, }, tinkpb.OutputPrefixType_TINK, 12345), @@ -173,7 +175,7 @@ func TestParseKey(t *testing.T) { { name: "key with CRUNCHY output prefix type", keySerialization: newKeySerialization(t, &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: serializedKey, KeyMaterialType: tinkpb.KeyData_SYMMETRIC, }, tinkpb.OutputPrefixType_CRUNCHY, 12345), @@ -182,7 +184,7 @@ func TestParseKey(t *testing.T) { { name: "key with RAW output prefix type", keySerialization: newKeySerialization(t, &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: serializedKey, KeyMaterialType: tinkpb.KeyData_SYMMETRIC, }, tinkpb.OutputPrefixType_RAW, 0), @@ -209,7 +211,7 @@ func TestParseKey(t *testing.T) { if err != nil { t.Fatalf("NewKey(keyMaterial, %v, wantParams) err = %v, want nil", keyID, err) } - p := &parser{} + p := &keyParser{} gotKey, err := p.ParseKey(tc.keySerialization) if err != nil { t.Errorf("protoserialization.ParseKey(%v) err = %v, want nil", tc.keySerialization, err) @@ -265,7 +267,7 @@ func TestSerializeKeyFails(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - aesGCMSerializer := &serializer{} + aesGCMSerializer := &keySerializer{} _, err := aesGCMSerializer.SerializeKey(tc.key) if err == nil { t.Errorf("protoserialization.SerializeKey(&testKey{}) err = nil, want non-nil") @@ -292,7 +294,7 @@ func TestSerializeKey(t *testing.T) { name: "key with TINK output prefix type", variant: VariantTink, wantKeySerialization: newKeySerialization(t, &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: serializedProtoKey, KeyMaterialType: tinkpb.KeyData_SYMMETRIC, }, tinkpb.OutputPrefixType_TINK, 12345), @@ -301,7 +303,7 @@ func TestSerializeKey(t *testing.T) { name: "key with CRUNCHY output prefix type", variant: VariantCrunchy, wantKeySerialization: newKeySerialization(t, &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: serializedProtoKey, KeyMaterialType: tinkpb.KeyData_SYMMETRIC, }, tinkpb.OutputPrefixType_CRUNCHY, 12345), @@ -311,7 +313,7 @@ func TestSerializeKey(t *testing.T) { name: "key with RAW output prefix type", variant: VariantNoPrefix, wantKeySerialization: newKeySerialization(t, &tinkpb.KeyData{ - TypeUrl: typeURL, + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", Value: serializedProtoKey, KeyMaterialType: tinkpb.KeyData_SYMMETRIC, }, tinkpb.OutputPrefixType_RAW, 0), @@ -337,7 +339,7 @@ func TestSerializeKey(t *testing.T) { if err != nil { t.Fatalf("NewKey(secretKey, %v, params) err = %v, want nil", keyID, err) } - aesGCMSerializer := &serializer{} + aesGCMSerializer := &keySerializer{} got, err := aesGCMSerializer.SerializeKey(key) if err != nil { t.Errorf("protoserialization.SerializeKey(&testKey{}) err = %v, want nil", err) @@ -348,3 +350,126 @@ func TestSerializeKey(t *testing.T) { }) } } + +func TestSerializeParametersFailsWithWrongParameters(t *testing.T) { + for _, tc := range []struct { + name string + parameters key.Parameters + }{ + { + name: "struct literal", + parameters: &Parameters{}, + }, + { + name: "nil", + parameters: nil, + }, + { + name: "wrong type", + parameters: &testParams{}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + serializer := ¶metersSerializer{} + if _, err := serializer.Serialize(tc.parameters); err == nil { + t.Errorf("serializer.Serialize(%v) err = nil, want error", tc.parameters) + } + }) + } +} + +func newKeyTemplate(t *testing.T, outputPrefixType tinkpb.OutputPrefixType, keySizeInBytes uint32) *tinkpb.KeyTemplate { + t.Helper() + format := &aesgcmpb.AesGcmKeyFormat{ + KeySize: keySizeInBytes, + } + serializedFormat, err := proto.Marshal(format) + if err != nil { + t.Fatalf("proto.Marshal(%v) err = %v, want nil", format, err) + } + return &tinkpb.KeyTemplate{ + TypeUrl: "type.googleapis.com/google.crypto.tink.AesGcmKey", + OutputPrefixType: outputPrefixType, + Value: serializedFormat, + } +} + +func TestSerializeParameters(t *testing.T) { + for _, tc := range []struct { + name string + parameters key.Parameters + wantKeyTemplate *tinkpb.KeyTemplate + }{ + { + name: "AES128-GCM TINK output prefix type", + parameters: &Parameters{ + keySizeInBytes: 16, + ivSizeInBytes: 12, + tagSizeInBytes: 16, + variant: VariantTink, + }, + wantKeyTemplate: newKeyTemplate(t, tinkpb.OutputPrefixType_TINK, 16), + }, + { + name: "AES256-GCM TINK output prefix type", + parameters: &Parameters{ + keySizeInBytes: 32, + ivSizeInBytes: 12, + tagSizeInBytes: 16, + variant: VariantTink, + }, + wantKeyTemplate: newKeyTemplate(t, tinkpb.OutputPrefixType_TINK, 32), + }, + { + name: "AES128-GCM RAW output prefix type", + parameters: &Parameters{ + keySizeInBytes: 16, + ivSizeInBytes: 12, + tagSizeInBytes: 16, + variant: VariantNoPrefix, + }, + wantKeyTemplate: newKeyTemplate(t, tinkpb.OutputPrefixType_RAW, 16), + }, + { + name: "AES256-GCM RAW output prefix type", + parameters: &Parameters{ + keySizeInBytes: 32, + ivSizeInBytes: 12, + tagSizeInBytes: 16, + variant: VariantNoPrefix, + }, + wantKeyTemplate: newKeyTemplate(t, tinkpb.OutputPrefixType_RAW, 32), + }, + { + name: "AES128-GCM CRUNCHY output prefix type", + parameters: &Parameters{ + keySizeInBytes: 16, + ivSizeInBytes: 12, + tagSizeInBytes: 16, + variant: VariantCrunchy, + }, + wantKeyTemplate: newKeyTemplate(t, tinkpb.OutputPrefixType_CRUNCHY, 16), + }, + { + name: "AES256-GCM CRUNCHY output prefix type", + parameters: &Parameters{ + keySizeInBytes: 32, + ivSizeInBytes: 12, + tagSizeInBytes: 16, + variant: VariantCrunchy, + }, + wantKeyTemplate: newKeyTemplate(t, tinkpb.OutputPrefixType_CRUNCHY, 32), + }, + } { + t.Run(tc.name, func(t *testing.T) { + serializer := ¶metersSerializer{} + gotKeyTemplate, err := serializer.Serialize(tc.parameters) + if err != nil { + t.Errorf("serializer.Serialize(%v) err = %v, want nil", tc.parameters, err) + } + if diff := cmp.Diff(tc.wantKeyTemplate, gotKeyTemplate, protocmp.Transform()); diff != "" { + t.Errorf("serializer.Serialize(%v) returned unexpected diff (-want +got):\n%s", tc.parameters, diff) + } + }) + } +} diff --git a/internal/protoserialization/protoserialization.go b/internal/protoserialization/protoserialization.go index 848971e..38eabbd 100644 --- a/internal/protoserialization/protoserialization.go +++ b/internal/protoserialization/protoserialization.go @@ -201,7 +201,8 @@ type KeySerializer interface { SerializeKey(key key.Key) (*KeySerialization, error) } -// ParametersSerializer is an interface for serializing parameters into a proto key template. +// ParametersSerializer is an interface for serializing parameters into a proto +// key template. type ParametersSerializer interface { // Serialize serializes the given parameters into a proto key template. Serialize(parameters key.Parameters) (*tinkpb.KeyTemplate, error) @@ -221,7 +222,8 @@ func RegisterKeySerializer[K key.Key](keySerializer KeySerializer) error { return nil } -// RegisterParametersSerializer registers the given parameter serializer for parameters of type P. +// RegisterParametersSerializer registers the given parameter serializer for +// parameters of type P. // // It doesn't allow replacing existing serializers. func RegisterParametersSerializer[P key.Parameters](parameterSerializer ParametersSerializer) error {