// Copyright 2019 Google LLC // // 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 keyset import ( "errors" "fmt" "google.golang.org/protobuf/encoding/prototext" "google.golang.org/protobuf/proto" "github.com/google/tink/go/core/primitiveset" "github.com/google/tink/go/core/registry" "github.com/google/tink/go/tink" tinkpb "github.com/google/tink/go/proto/tink_go_proto" ) var errInvalidKeyset = fmt.Errorf("keyset.Handle: invalid keyset") // Handle provides access to a Keyset protobuf, to limit the exposure of actual protocol // buffers that hold sensitive key material. type Handle struct { ks *tinkpb.Keyset annotations map[string]string } func newWithOptions(ks *tinkpb.Keyset, opts ...Option) (*Handle, error) { h := &Handle{ks: ks} if err := applyOptions(h, opts...); err != nil { return nil, err } return h, nil } // NewHandle creates a keyset handle that contains a single fresh key generated according // to the given KeyTemplate. func NewHandle(kt *tinkpb.KeyTemplate) (*Handle, error) { manager := NewManager() keyID, err := manager.Add(kt) if err != nil { return nil, fmt.Errorf("keyset.Handle: cannot generate new keyset: %s", err) } err = manager.SetPrimary(keyID) if err != nil { return nil, fmt.Errorf("keyset.Handle: cannot set primary: %s", err) } handle, err := manager.Handle() if err != nil { return nil, fmt.Errorf("keyset.Handle: cannot get keyset handle: %s", err) } return handle, nil } // NewHandleWithNoSecrets creates a new instance of KeysetHandle using the given keyset which does // not contain any secret key material. func NewHandleWithNoSecrets(ks *tinkpb.Keyset) (*Handle, error) { if ks == nil { return nil, errors.New("keyset.Handle: nil keyset") } h := &Handle{ks: ks} if h.hasSecrets() { // If you need to do this, you have to use func insecurecleartextkeyset.Read() instead. return nil, errors.New("importing unencrypted secret key material is forbidden") } return h, nil } // Read tries to create a Handle from an encrypted keyset obtained via reader. func Read(reader Reader, masterKey tink.AEAD) (*Handle, error) { return ReadWithAssociatedData(reader, masterKey, []byte{}) } // ReadWithAssociatedData tries to create a Handle from an encrypted keyset obtained via reader using the provided associated data. func ReadWithAssociatedData(reader Reader, masterKey tink.AEAD, associatedData []byte) (*Handle, error) { encryptedKeyset, err := reader.ReadEncrypted() if err != nil { return nil, err } ks, err := decrypt(encryptedKeyset, masterKey, associatedData) if err != nil { return nil, err } return &Handle{ks: ks}, nil } // ReadWithNoSecrets tries to create a keyset.Handle from a keyset obtained via reader. func ReadWithNoSecrets(reader Reader) (*Handle, error) { ks, err := reader.Read() if err != nil { return nil, err } return NewHandleWithNoSecrets(ks) } // Public returns a Handle of the public keys if the managed keyset contains private keys. func (h *Handle) Public() (*Handle, error) { privKeys := h.ks.Key pubKeys := make([]*tinkpb.Keyset_Key, len(privKeys)) for i := 0; i < len(privKeys); i++ { if privKeys[i] == nil || privKeys[i].KeyData == nil { return nil, errInvalidKeyset } privKeyData := privKeys[i].KeyData pubKeyData, err := publicKeyData(privKeyData) if err != nil { return nil, fmt.Errorf("keyset.Handle: %s", err) } pubKeys[i] = &tinkpb.Keyset_Key{ KeyData: pubKeyData, Status: privKeys[i].Status, KeyId: privKeys[i].KeyId, OutputPrefixType: privKeys[i].OutputPrefixType, } } ks := &tinkpb.Keyset{ PrimaryKeyId: h.ks.PrimaryKeyId, Key: pubKeys, } return &Handle{ks: ks}, nil } // String returns a string representation of the managed keyset. // The result does not contain any sensitive key material. func (h *Handle) String() string { c, err := prototext.MarshalOptions{}.Marshal(getKeysetInfo(h.ks)) if err != nil { return "" } return string(c) } // KeysetInfo returns KeysetInfo representation of the managed keyset. // The result does not contain any sensitive key material. func (h *Handle) KeysetInfo() *tinkpb.KeysetInfo { return getKeysetInfo(h.ks) } // Write encrypts and writes the enclosing keyset. func (h *Handle) Write(writer Writer, masterKey tink.AEAD) error { return h.WriteWithAssociatedData(writer, masterKey, []byte{}) } // WriteWithAssociatedData encrypts and writes the enclosing keyset using the provided associated data. func (h *Handle) WriteWithAssociatedData(writer Writer, masterKey tink.AEAD, associatedData []byte) error { encrypted, err := encrypt(h.ks, masterKey, associatedData) if err != nil { return err } return writer.WriteEncrypted(encrypted) } // WriteWithNoSecrets exports the keyset in h to the given Writer w returning an error if the keyset // contains secret key material. func (h *Handle) WriteWithNoSecrets(w Writer) error { if h.hasSecrets() { return errors.New("exporting unencrypted secret key material is forbidden") } return w.Write(h.ks) } // Primitives creates a set of primitives corresponding to the keys with // status=ENABLED in the keyset of the given keyset handle, assuming all the // corresponding key managers are present (keys with status!=ENABLED are skipped). // // The returned set is usually later "wrapped" into a class that implements // the corresponding Primitive-interface. func (h *Handle) Primitives() (*primitiveset.PrimitiveSet, error) { return h.PrimitivesWithKeyManager(nil) } // PrimitivesWithKeyManager creates a set of primitives corresponding to // the keys with status=ENABLED in the keyset of the given keysetHandle, using // the given key manager (instead of registered key managers) for keys supported // by it. Keys not supported by the key manager are handled by matching registered // key managers (if present), and keys with status!=ENABLED are skipped. // // This enables custom treatment of keys, for example providing extra context // (e.g. credentials for accessing keys managed by a KMS), or gathering custom // monitoring/profiling information. // // The returned set is usually later "wrapped" into a class that implements // the corresponding Primitive-interface. func (h *Handle) PrimitivesWithKeyManager(km registry.KeyManager) (*primitiveset.PrimitiveSet, error) { if err := Validate(h.ks); err != nil { return nil, fmt.Errorf("registry.PrimitivesWithKeyManager: invalid keyset: %s", err) } primitiveSet := primitiveset.New() primitiveSet.Annotations = h.annotations for _, key := range h.ks.Key { if key.Status != tinkpb.KeyStatusType_ENABLED { continue } var primitive interface{} var err error if km != nil && km.DoesSupport(key.KeyData.TypeUrl) { primitive, err = km.Primitive(key.KeyData.Value) } else { primitive, err = registry.PrimitiveFromKeyData(key.KeyData) } if err != nil { return nil, fmt.Errorf("registry.PrimitivesWithKeyManager: cannot get primitive from key: %s", err) } entry, err := primitiveSet.Add(primitive, key) if err != nil { return nil, fmt.Errorf("registry.PrimitivesWithKeyManager: cannot add primitive: %s", err) } if key.KeyId == h.ks.PrimaryKeyId { primitiveSet.Primary = entry } } return primitiveSet, nil } // hasSecrets returns true if the keyset handle contains key material considered secret. This // includes symmetric keys, private keys of asymmetric crypto systems, and keys of an unknown type. func (h *Handle) hasSecrets() bool { for _, k := range h.ks.Key { if k == nil || k.KeyData == nil { continue } if k.KeyData.KeyMaterialType == tinkpb.KeyData_UNKNOWN_KEYMATERIAL { return true } if k.KeyData.KeyMaterialType == tinkpb.KeyData_ASYMMETRIC_PRIVATE { return true } if k.KeyData.KeyMaterialType == tinkpb.KeyData_SYMMETRIC { return true } } return false } func publicKeyData(privKeyData *tinkpb.KeyData) (*tinkpb.KeyData, error) { if privKeyData.KeyMaterialType != tinkpb.KeyData_ASYMMETRIC_PRIVATE { return nil, fmt.Errorf("keyset.Handle: keyset contains a non-private key") } km, err := registry.GetKeyManager(privKeyData.TypeUrl) if err != nil { return nil, err } pkm, ok := km.(registry.PrivateKeyManager) if !ok { return nil, fmt.Errorf("keyset.Handle: %s does not belong to a PrivateKeyManager", privKeyData.TypeUrl) } return pkm.PublicKeyData(privKeyData.Value) } func decrypt(encryptedKeyset *tinkpb.EncryptedKeyset, masterKey tink.AEAD, associatedData []byte) (*tinkpb.Keyset, error) { if encryptedKeyset == nil || masterKey == nil { return nil, fmt.Errorf("keyset.Handle: invalid encrypted keyset") } decrypted, err := masterKey.Decrypt(encryptedKeyset.EncryptedKeyset, associatedData) if err != nil { return nil, fmt.Errorf("keyset.Handle: decryption failed: %s", err) } keyset := new(tinkpb.Keyset) if err := proto.Unmarshal(decrypted, keyset); err != nil { return nil, errInvalidKeyset } return keyset, nil } func encrypt(keyset *tinkpb.Keyset, masterKey tink.AEAD, associatedData []byte) (*tinkpb.EncryptedKeyset, error) { serializedKeyset, err := proto.Marshal(keyset) if err != nil { return nil, errInvalidKeyset } encrypted, err := masterKey.Encrypt(serializedKeyset, associatedData) if err != nil { return nil, fmt.Errorf("keyset.Handle: encrypted failed: %s", err) } // get keyset info encryptedKeyset := &tinkpb.EncryptedKeyset{ EncryptedKeyset: encrypted, KeysetInfo: getKeysetInfo(keyset), } return encryptedKeyset, nil } // getKeysetInfo returns a KeysetInfo from a Keyset protobuf. func getKeysetInfo(keyset *tinkpb.Keyset) *tinkpb.KeysetInfo { if keyset == nil { panic("keyset.Handle: keyset must be non nil") } nKey := len(keyset.Key) keyInfos := make([]*tinkpb.KeysetInfo_KeyInfo, nKey) for i, key := range keyset.Key { keyInfos[i] = getKeyInfo(key) } return &tinkpb.KeysetInfo{ PrimaryKeyId: keyset.PrimaryKeyId, KeyInfo: keyInfos, } } // getKeyInfo returns a KeyInfo from a Key protobuf. func getKeyInfo(key *tinkpb.Keyset_Key) *tinkpb.KeysetInfo_KeyInfo { return &tinkpb.KeysetInfo_KeyInfo{ TypeUrl: key.KeyData.TypeUrl, Status: key.Status, KeyId: key.KeyId, OutputPrefixType: key.OutputPrefixType, } }