1# Copyright 2020 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Python implementation of a KeysetBuilder.""" 15 16import io 17import random 18from tink.proto import tink_pb2 19import tink 20from tink import cleartext_keyset_handle 21 22_MAX_INT32 = 4294967295 # 2^32-1 23 24 25def _new_key_data(key_template: tink_pb2.KeyTemplate) -> tink_pb2.KeyData: 26 return tink.core.Registry.new_key_data(key_template) 27 28 29def _generate_unused_key_id(keyset: tink_pb2.Keyset) -> int: 30 while True: 31 key_id = random.randint(1, _MAX_INT32) 32 if key_id not in {key.key_id for key in keyset.key}: 33 return key_id 34 35 36def raw_template(template: tink_pb2.KeyTemplate) -> tink_pb2.KeyTemplate: 37 template_copy = tink_pb2.KeyTemplate() 38 template_copy.CopyFrom(template) 39 template_copy.output_prefix_type = tink_pb2.RAW 40 return template_copy 41 42 43def legacy_template(template: tink_pb2.KeyTemplate) -> tink_pb2.KeyTemplate: 44 template_copy = tink_pb2.KeyTemplate() 45 template_copy.CopyFrom(template) 46 template_copy.output_prefix_type = tink_pb2.LEGACY 47 return template_copy 48 49 50class KeysetBuilder: 51 """A KeysetBuilder provides convenience methods for managing Keysets. 52 53 It provides methods for adding, disabling, enabling, or deleting keys. 54 The validity of the keyset is checked when creating a keyset_handle. 55 """ 56 57 def __init__(self, keyset_proto: tink_pb2.Keyset): 58 self._keyset = keyset_proto 59 60 def keyset_handle(self) -> tink.KeysetHandle: 61 keyset_copy = tink_pb2.Keyset() 62 keyset_copy.CopyFrom(self._keyset) 63 return cleartext_keyset_handle.from_keyset(keyset_copy) 64 65 def keyset(self) -> bytes: 66 return self._keyset.SerializeToString() 67 68 def public_keyset(self) -> bytes: 69 public_handle = self.keyset_handle().public_keyset_handle() 70 public_keyset = io.BytesIO() 71 writer = tink.BinaryKeysetWriter(public_keyset) 72 cleartext_keyset_handle.write(writer, public_handle) 73 return public_keyset.getvalue() 74 75 def add_new_key(self, key_template: tink_pb2.KeyTemplate) -> int: 76 """Generates a new key, adds it to the keyset, and returns its ID.""" 77 new_key = self._keyset.key.add() 78 new_key.key_data.CopyFrom(_new_key_data(key_template)) 79 new_key.status = tink_pb2.ENABLED 80 new_key_id = _generate_unused_key_id(self._keyset) 81 new_key.key_id = new_key_id 82 new_key.output_prefix_type = key_template.output_prefix_type 83 return new_key_id 84 85 def set_primary_key(self, key_id: int) -> None: 86 """Sets a key as primary.""" 87 for key in self._keyset.key: 88 if key.key_id == key_id: 89 self._keyset.primary_key_id = key_id 90 return 91 raise tink.TinkError('key not found: %d' % key_id) 92 93 def enable_key(self, key_id: int) -> None: 94 """Enables a key.""" 95 for key in self._keyset.key: 96 if key.key_id == key_id: 97 key.status = tink_pb2.ENABLED 98 return 99 raise tink.TinkError('key not found: %d' % key_id) 100 101 def disable_key(self, key_id: int) -> None: 102 """Disables a key.""" 103 for key in self._keyset.key: 104 if key.key_id == key_id: 105 key.status = tink_pb2.DISABLED 106 return 107 raise tink.TinkError('key not found: %d' % key_id) 108 109 def delete_key(self, key_id: int) -> None: 110 """Deletes a key.""" 111 for key in self._keyset.key: 112 if key.key_id == key_id: 113 self._keyset.key.remove(key) 114 return 115 raise tink.TinkError('key not found: %d' % key_id) 116 117 118def from_keyset(keyset: bytes) -> KeysetBuilder: 119 """Return a KeysetBuilder for a Keyset copied from a KeysetHandle.""" 120 keyset_proto = tink_pb2.Keyset.FromString(keyset) 121 return KeysetBuilder(keyset_proto) 122 123 124def from_keyset_handle(keyset_handle: tink.KeysetHandle) -> KeysetBuilder: 125 """Return a KeysetBuilder for a Keyset copied from a KeysetHandle.""" 126 keyset_buffer = io.BytesIO() 127 cleartext_keyset_handle.write( 128 tink.BinaryKeysetWriter(keyset_buffer), keyset_handle) 129 return from_keyset(keyset_buffer.getvalue()) 130 131 132def new_keyset_builder() -> KeysetBuilder: 133 return KeysetBuilder(tink_pb2.Keyset()) 134