Skip to content

Commit

Permalink
Add support for full primitives in the signature factory.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 696473119
Change-Id: Icf89afb72dcba9481aab9f237e1f129a9ebaebdc
  • Loading branch information
morambro authored and copybara-github committed Nov 14, 2024
1 parent 8b68265 commit 1bbf2cc
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 32 deletions.
208 changes: 208 additions & 0 deletions signature/signature_factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ package signature_test
import (
"bytes"
"fmt"
"slices"
"testing"

"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/proto"
"github.com/tink-crypto/tink-go/v2/core/cryptofmt"
"github.com/tink-crypto/tink-go/v2/core/registry"
"github.com/tink-crypto/tink-go/v2/insecurecleartextkeyset"
"github.com/tink-crypto/tink-go/v2/internal/internalregistry"
"github.com/tink-crypto/tink-go/v2/internal/protoserialization"
"github.com/tink-crypto/tink-go/v2/internal/registryconfig"
"github.com/tink-crypto/tink-go/v2/internal/testing/stubkeymanager"
"github.com/tink-crypto/tink-go/v2/key"
"github.com/tink-crypto/tink-go/v2/keyset"
"github.com/tink-crypto/tink-go/v2/mac"
"github.com/tink-crypto/tink-go/v2/monitoring"
Expand Down Expand Up @@ -480,3 +485,206 @@ func TestVerifyWithLegacyKeyDoesNotHaveSideEffectOnMessage(t *testing.T) {
t.Errorf("data = %q, want: %q", data, wantData)
}
}

const stubKeyURL = "type.googleapis.com/google.crypto.tink.SomeKey"

type stubFullSigner struct{}

func (s *stubFullSigner) Sign(data []byte) ([]byte, error) {
return slices.Concat([]byte("full_signer_prefix"), data), nil
}

type stubParams struct{}

var _ key.Parameters = (*stubParams)(nil)

func (p *stubParams) Equals(_ key.Parameters) bool { return true }
func (p *stubParams) HasIDRequirement() bool { return true }

type stubPublicKey struct{}

var _ key.Key = (*stubPublicKey)(nil)

func (p *stubPublicKey) Equals(_ key.Key) bool { return true }
func (p *stubPublicKey) Parameters() key.Parameters { return &stubParams{} }
func (p *stubPublicKey) IDRequirement() (uint32, bool) { return 0, false }
func (p *stubPublicKey) HasIDRequirement() bool { return true }

type stubPrivateKey struct {
prefixType tinkpb.OutputPrefixType
idRequrement uint32
}

var _ key.Key = (*stubPrivateKey)(nil)

func (p *stubPrivateKey) Equals(_ key.Key) bool { return true }
func (p *stubPrivateKey) Parameters() key.Parameters { return &stubParams{} }
func (p *stubPrivateKey) IDRequirement() (uint32, bool) { return p.idRequrement, p.HasIDRequirement() }
func (p *stubPrivateKey) HasIDRequirement() bool { return p.prefixType != tinkpb.OutputPrefixType_RAW }
func (p *stubPrivateKey) PublicKey() (key.Key, error) { return &stubPublicKey{}, nil }

type stubPrivateKeySerialization struct{}

var _ protoserialization.KeySerializer = (*stubPrivateKeySerialization)(nil)

func (s *stubPrivateKeySerialization) SerializeKey(key key.Key) (*protoserialization.KeySerialization, error) {
return protoserialization.NewKeySerialization(
&tinkpb.KeyData{
TypeUrl: stubKeyURL,
Value: []byte("serialized_key"),
KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PRIVATE,
},
key.(*stubPrivateKey).prefixType,
key.(*stubPrivateKey).idRequrement,
)
}

type stubPrivateKeyParser struct{}

var _ protoserialization.KeyParser = (*stubPrivateKeyParser)(nil)

func (s *stubPrivateKeyParser) ParseKey(serialization *protoserialization.KeySerialization) (key.Key, error) {
idRequirement, _ := serialization.IDRequirement()
return &stubPrivateKey{serialization.OutputPrefixType(), idRequirement}, nil
}

func TestPrimitiveFactoryUsesFullPrimitiveIfRegistered(t *testing.T) {
defer registryconfig.ClearPrimitiveConstructors()
defer protoserialization.ClearKeyParsers()
defer protoserialization.UnregisterKeySerializer[*stubPrivateKey]()

if err := protoserialization.RegisterKeyParser(stubKeyURL, &stubPrivateKeyParser{}); err != nil {
t.Fatalf("protoserialization.RegisterKeyParser() err = %v, want nil", err)
}
if err := protoserialization.RegisterKeySerializer[*stubPrivateKey](&stubPrivateKeySerialization{}); err != nil {
t.Fatalf("protoserialization.RegisterKeySerializer() err = %v, want nil", err)
}
// Register a primitive constructor to make sure that the factory uses the
// full primitive.
primitiveConstructor := func(key key.Key) (any, error) { return &stubFullSigner{}, nil }
if err := registryconfig.RegisterPrimitiveConstructor[*stubPrivateKey](primitiveConstructor); err != nil {
t.Fatalf("registryconfig.RegisterPrimitiveConstructor() err = %v, want nil", err)
}

km := keyset.NewManager()
keyID, err := km.AddKey(&stubPrivateKey{
tinkpb.OutputPrefixType_TINK,
0x1234,
})
if err != nil {
t.Fatalf("km.AddKey() err = %v, want nil", err)
}
if err := km.SetPrimary(keyID); err != nil {
t.Fatalf("km.SetPrimary() err = %v, want nil", err)
}
handle, err := km.Handle()
if err != nil {
t.Fatalf("km.Handle() err = %v, want nil", err)
}

signer, err := signature.NewSigner(handle)
if err != nil {
t.Fatalf("signature.NewSigner() err = %v, want nil", err)
}
data := []byte("data")
signature, err := signer.Sign(data)
if err != nil {
t.Fatalf("signer.Sign() err = %v, want nil", err)
}
if !bytes.Equal(signature, slices.Concat([]byte("full_signer_prefix"), data)) {
t.Errorf("signature = %q, want: %q", signature, data)
}
}

type stubLegacySigner struct{}

func (s *stubLegacySigner) Sign(data []byte) ([]byte, error) {
return slices.Concat([]byte("legacy_signer_prefix"), data), nil
}

type stubKeyManager struct{}

var _ registry.KeyManager = (*stubKeyManager)(nil)

func (km *stubKeyManager) NewKey(_ []byte) (proto.Message, error) {
return nil, fmt.Errorf("not implemented")
}
func (km *stubKeyManager) NewKeyData(_ []byte) (*tinkpb.KeyData, error) {
return nil, fmt.Errorf("not implemented")
}
func (km *stubKeyManager) DoesSupport(keyURL string) bool { return keyURL == stubKeyURL }
func (km *stubKeyManager) TypeURL() string { return stubKeyURL }
func (km *stubKeyManager) Primitive(_ []byte) (any, error) { return &stubLegacySigner{}, nil }

func TestPrimitiveFactoryUsesLegacyPrimitive(t *testing.T) {
defer protoserialization.ClearKeyParsers()
defer protoserialization.UnregisterKeySerializer[*stubPrivateKey]()

if err := protoserialization.RegisterKeyParser(stubKeyURL, &stubPrivateKeyParser{}); err != nil {
t.Fatalf("protoserialization.RegisterKeyParser() err = %v, want nil", err)
}
if err := protoserialization.RegisterKeySerializer[*stubPrivateKey](&stubPrivateKeySerialization{}); err != nil {
t.Fatalf("protoserialization.RegisterKeySerializer() err = %v, want nil", err)
}

if err := registry.RegisterKeyManager(&stubKeyManager{}); err != nil {
t.Fatalf("registry.RegisterKeyManager() err = %v, want nil", err)
}

data := []byte("data")
legacyPrefix := []byte("legacy_signer_prefix")
for _, tc := range []struct {
name string
key *stubPrivateKey
wantSigature []byte
}{
{
name: "TINK",
key: &stubPrivateKey{tinkpb.OutputPrefixType_TINK, 0x1234},
wantSigature: slices.Concat([]byte{cryptofmt.TinkStartByte, 0x00, 0x00, 0x12, 0x34}, legacyPrefix, data),
},
{
name: "LEGACY",
key: &stubPrivateKey{tinkpb.OutputPrefixType_LEGACY, 0x1234},
wantSigature: slices.Concat([]byte{cryptofmt.LegacyStartByte, 0x00, 0x00, 0x12, 0x34}, legacyPrefix, data, []byte{0}),
},
{
name: "CRUNCHY",
key: &stubPrivateKey{tinkpb.OutputPrefixType_CRUNCHY, 0x1234},
wantSigature: slices.Concat([]byte{cryptofmt.LegacyStartByte, 0x00, 0x00, 0x12, 0x34}, legacyPrefix, data),
},
{
name: "RAW",
key: &stubPrivateKey{tinkpb.OutputPrefixType_RAW, 0},
wantSigature: slices.Concat(legacyPrefix, data),
},
} {
t.Run(tc.name, func(t *testing.T) {
// Create a keyset with a single key.
km := keyset.NewManager()
keyID, err := km.AddKey(tc.key)
if err != nil {
t.Fatalf("km.AddKey() err = %v, want nil", err)
}
if err := km.SetPrimary(keyID); err != nil {
t.Fatalf("km.SetPrimary() err = %v, want nil", err)
}
handle, err := km.Handle()
if err != nil {
t.Fatalf("km.Handle() err = %v, want nil", err)
}

signer, err := signature.NewSigner(handle)
if err != nil {
t.Fatalf("signature.NewSigner() err = %v, want nil", err)
}
signature, err := signer.Sign(data)
if err != nil {
t.Fatalf("signer.Sign() err = %v, want nil", err)
}
if !bytes.Equal(signature, tc.wantSigature) {
t.Errorf("signature = %q, want: %q", signature, data)
}
})
}
}
89 changes: 57 additions & 32 deletions signature/signer_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package signature

import (
"fmt"
"slices"

"github.com/tink-crypto/tink-go/v2/internal/internalapi"
"github.com/tink-crypto/tink-go/v2/internal/internalregistry"
Expand Down Expand Up @@ -45,15 +46,57 @@ type wrappedSigner struct {
// 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 {
type fullSignerAdapter struct {
primitive tink.Signer
prefix []byte
prefixType tinkpb.OutputPrefixType
}

var _ tink.Signer = (*fullSignerAdapter)(nil)

func (a *fullSignerAdapter) Sign(data []byte) ([]byte, error) {
toSign := data
if a.prefixType == tinkpb.OutputPrefixType_LEGACY {
toSign = slices.Concat(data, []byte{0})
}
s, err := a.primitive.Sign(toSign)
if err != nil {
return nil, err
}
return slices.Concat(a.prefix, s), nil
}

// extractFullSigner returns a [tink.Signer] from the given entry as a "full"
// primitive.
//
// It wraps legacy primitives in a full primitive adapter.
func extractFullSigner(entry *primitiveset.Entry) (tink.Signer, error) {
if entry.FullPrimitive != nil {
p, ok := (entry.FullPrimitive).(tink.Signer)
if !ok {
return nil, fmt.Errorf("public_key_sign_factory: not a Signer full primitive")
}
return p, nil
}
p, ok := (entry.Primitive).(tink.Signer)
if !ok {
return nil, fmt.Errorf("public_key_sign_factory: not a Signer primitive")
}
return &fullSignerAdapter{
primitive: p,
prefix: []byte(entry.Prefix),
prefixType: entry.PrefixType,
}, nil
}

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")
func newWrappedSigner(ps *primitiveset.PrimitiveSet) (*wrappedSigner, error) {
if _, err := extractFullSigner(ps.Primary); err != nil {
return nil, err
}
for _, entries := range ps.Entries {
for _, entry := range entries {
if _, err := extractFullSigner(entry); err != nil {
return nil, err
}
}
}
Expand All @@ -68,7 +111,7 @@ func newWrappedSigner(ps *primitiveset.PrimitiveSet) (*wrappedSigner, error) {
}

func createSignerLogger(ps *primitiveset.PrimitiveSet) (monitoring.Logger, error) {
// only keysets which contain annotations are monitored.
// Only keysets which contain annotations are monitored.
if len(ps.Annotations) == 0 {
return &monitoringutil.DoNothingLogger{}, nil
}
Expand All @@ -83,35 +126,17 @@ func createSignerLogger(ps *primitiveset.PrimitiveSet) (monitoring.Logger, error
})
}

// Sign signs the given data and returns the signature concatenated with the identifier of the
// primary primitive.
// Sign signs the given data using the primary key.
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 = make([]byte, 0, len(data)+1)
signedData = append(signedData, data...)
signedData = append(signedData, byte(0))
} else {
signedData = data
signer, err := extractFullSigner(s.ps.Primary)
if err != nil {
return nil, err
}

signature, err := signer.Sign(signedData)
signature, err := signer.Sign(data)
if err != nil {
s.logger.LogFailure()
return nil, err
}
s.logger.Log(primary.KeyID, len(data))
if len(primary.Prefix) == 0 {
return signature, nil
}
output := make([]byte, 0, len(primary.Prefix)+len(signature))
output = append(output, primary.Prefix...)
output = append(output, signature...)
return output, nil
s.logger.Log(s.ps.Primary.KeyID, len(data))
return signature, nil
}

0 comments on commit 1bbf2cc

Please sign in to comment.