def decrypt(self, ciphertext: bytes, associated_data: bytes) -> bytes: data = ciphertext.split(b'|') if (len(data) < 3 or data[1] != associated_data or data[2] != self._name.encode()): raise core.TinkError('failed to decrypt ciphertext ' + ciphertext.decode()) return data[0]
def write_encrypted(self, encrypted_keyset: tink_pb2.EncryptedKeyset) -> None: if not isinstance(encrypted_keyset, tink_pb2.EncryptedKeyset): raise core.TinkError('invalid encrypted keyset.') json_keyset = json_format.MessageToJson(encrypted_keyset) self._io_stream.write(json_keyset) self._io_stream.flush()
def _validate_primitive_set(pset: core.PrimitiveSet): # TODO(juerg): also validate that there is a primary for entries in pset.all(): for entry in entries: if (entry.output_prefix_type != tink_pb2.RAW and entry.output_prefix_type != tink_pb2.TINK): raise core.TinkError('unsupported OutputPrefixType')
def verify_mac_and_decode( self, compact: Text, validator: _jwt_validator.JwtValidator ) -> _verified_jwt.VerifiedJwt: """Verifies, validates and decodes a MACed compact JWT token. Args: compact: A MACed token encoded in the JWS compact serialization format. validator: A JwtValidator that validates the token. Returns: A VerifiedJwt. Raises: tink.TinkError if the operation fails. """ interesting_error = None for entry in self._primitive_set.raw_primitives(): try: return entry.primitive.verify_mac_and_decode( compact, validator) except core.TinkError as e: if isinstance(e, _jwt_error.JwtInvalidError): interesting_error = e pass if interesting_error: raise interesting_error raise core.TinkError('invalid MAC')
def read(self, size: int = -1) -> Optional[bytes]: if self._error: raise self._error if not self._bytes_io: # read to EOF, and don't stop when None is returned. while True: try: d = self._ciphertext_source.read() if d is None: return None except BlockingIOError: # There is currently no data available. This error may be raised by a # BufferedIOBase source. For RawIOBase, we have to return None in this # case. return None if not d: # d == b'', which means EOF break self._data.extend(d) data = bytes(self._data).split(b'|') if (len(data) < 3 or data[1] != self._associated_data or data[0] != self._name.encode()): self._error = core.TinkError('error occured.') raise self._error self._bytes_io = io.BytesIO(data[2]) return self._bytes_io.read(size)
def _assert_no_secret_key_material(keyset: tink_pb2.Keyset): for key in keyset.key: if key.key_data.key_material_type in ( tink_pb2.KeyData.UNKNOWN_KEYMATERIAL, tink_pb2.KeyData.SYMMETRIC, tink_pb2.KeyData.ASYMMETRIC_PRIVATE): raise core.TinkError('keyset contains secret key material')
def _encrypt(keyset: tink_pb2.Keyset, master_key_primitive: aead.Aead) -> tink_pb2.EncryptedKeyset: """Encrypts a Keyset and returns an EncryptedKeyset.""" encrypted_keyset = master_key_primitive.encrypt(keyset.SerializeToString(), b'') # Check if we can decrypt, to detect errors try: keyset2 = tink_pb2.Keyset.FromString( master_key_primitive.decrypt(encrypted_keyset, b'')) if keyset != keyset2: raise core.TinkError('cannot encrypt keyset: %s != %s' % (keyset, keyset2)) except message.DecodeError: raise core.TinkError('invalid keyset, corrupted key material') return tink_pb2.EncryptedKeyset(encrypted_keyset=encrypted_keyset, keyset_info=_keyset_info(keyset))
def verify_mac_and_decode( self, compact: str, validator: _jwt_validator.JwtValidator ) -> _verified_jwt.VerifiedJwt: """Verifies, validates and decodes a MACed compact JWT token. Args: compact: A MACed token encoded in the JWS compact serialization format. validator: A JwtValidator that validates the token. Returns: A VerifiedJwt. Raises: tink.TinkError if the operation fails. """ interesting_error = None for entries in self._primitive_set.all(): for entry in entries: try: kid = _jwt_format.get_kid(entry.key_id, entry.output_prefix_type) return entry.primitive.verify_mac_and_decode_with_kid( compact, validator, kid) except core.TinkError as e: if isinstance(e, _jwt_error.JwtInvalidError): interesting_error = e pass if interesting_error: raise interesting_error raise core.TinkError('invalid MAC')
def decrypt(self, ciphertext: bytes, associated_data: bytes) -> bytes: try: response = self.client.decrypt(self.key_name, ciphertext, associated_data) except (GoogleAPICallError, RetryError, ValueError): raise core.TinkError("Decryption failed inside GCP client.") return response.plaintext
def write(self, keyset: tink_pb2.Keyset) -> None: if not isinstance(keyset, tink_pb2.Keyset): raise core.TinkError('invalid keyset.') json_keyset = json_format.MessageToJson(keyset) # TODO(b/141106504) Needed for python 2.7 compatibility. StringIO expects # unicode, but MessageToJson outputs UTF-8. if isinstance(json_keyset, bytes): json_keyset = json_keyset.decode('utf-8') self._io_stream.write(json_keyset) self._io_stream.flush()
def _validate_keyset(keyset: tink_pb2.Keyset): """Raises tink_error.TinkError if keyset is not valid.""" for key in keyset.key: if key.status != tink_pb2.DESTROYED: _validate_key(key) num_non_destroyed_keys = sum(1 for key in keyset.key if key.status != tink_pb2.DESTROYED) num_non_public_key_material = sum( 1 for key in keyset.key if key.key_data.key_material_type != tink_pb2.KeyData.ASYMMETRIC_PUBLIC) num_primary_keys = sum(1 for key in keyset.key if key.status == tink_pb2.ENABLED and key.key_id == keyset.primary_key_id) if num_non_destroyed_keys == 0: raise core.TinkError('empty keyset') if num_primary_keys > 1: raise core.TinkError('keyset contains multiple primary keys') if num_primary_keys == 0 and num_non_public_key_material > 0: raise core.TinkError('keyset does not contain a valid primary key')
def write(self, b: bytes) -> int: """Write the given buffer to the stream. May use multiple calls to the underlying file object's write() method. Returns: The number of bytes written, which will always be the length of b in bytes. Raises: BlockingIOError: if the write could not be fully completed, with characters_written set to the number of bytes successfully written. TinkError: if there was a permanent error. Args: b: The buffer to write. """ self._check_not_closed() if not isinstance(b, (bytes, memoryview, bytearray)): raise TypeError('a bytes-like object is required, not {}'.format( type(b).__name__)) # One call to OutputStreamAdapter.write() may call next() multiple times # on the C++ EncryptingStream, but will perform a partial write if there is # a temporary write error. Permanent write errors will bubble up as # exceptions. written = self._output_stream_adapter.write(b) if written < 0: raise core.TinkError('Number of written bytes was negative') self._bytes_written += written if written < len(b): raise io.BlockingIOError( errno.EAGAIN, 'Write could not complete without blocking.', written) elif written > len(b): raise core.TinkError( 'Number of written bytes was greater than length of bytes given' ) return written
def verify(self, signature: bytes, data: bytes): """Verifies that signature is a digital signature for data. Args: signature: The signature bytes to be checked. data: The data bytes to be checked. Raises: tink_error.TinkError if the verification fails. """ if len(signature) <= core.crypto_format.NON_RAW_PREFIX_SIZE: # This also rejects raw signatures with size of 4 bytes or fewer. # We're not aware of any schemes that output signatures that small. raise core.TinkError('signature too short') key_id = signature[:core.crypto_format.NON_RAW_PREFIX_SIZE] raw_sig = signature[core.crypto_format.NON_RAW_PREFIX_SIZE:] for entry in self._primitive_set.primitive_from_identifier(key_id): try: if entry.output_prefix_type == tink_pb2.LEGACY: entry.primitive.verify( raw_sig, data + core.crypto_format.LEGACY_START_BYTE) else: entry.primitive.verify(raw_sig, data) # Signature is valid, we can return return except core.TinkError as err: logging.info( 'signature prefix matches a key, but cannot verify: %s', err) # No matching key succeeded with verification, try all RAW keys for entry in self._primitive_set.raw_primitives(): try: entry.primitive.verify(signature, data) # Signature is valid, we can return return except core.TinkError: pass raise core.TinkError('invalid signature')
def _decrypt(encrypted_keyset: tink_pb2.EncryptedKeyset, master_key_aead: aead.Aead) -> tink_pb2.Keyset: """Decrypts an EncryptedKeyset and returns a Keyset.""" try: keyset = tink_pb2.Keyset.FromString( master_key_aead.decrypt(encrypted_keyset.encrypted_keyset, b'')) # Check emptiness here too, in case the encrypted keys unwrapped to nothing? _assert_enough_key_material(keyset) return keyset except message.DecodeError: raise core.TinkError('invalid keyset, corrupted key material')
def compute_mac_and_encode(self, raw_jwt: _raw_jwt.RawJwt) -> Text: """Computes a MAC and encodes the token. Args: raw_jwt: The RawJwt token to be MACed and encoded. Returns: The MACed token encoded in the JWS compact serialization format. Raises: tink.TinkError if the operation fails. """ primary = self._primitive_set.primary() if primary.output_prefix_type != tink_pb2.RAW: raise core.TinkError('unexpected output prefix type') return primary.primitive.compute_mac_and_encode(raw_jwt)
def decrypt(self, ciphertext: bytes, associated_data: bytes) -> bytes: if associated_data: ad = {'additionalData': associated_data.decode()} try: if associated_data: response = self.client.decrypt(KeyId=self.key_name, CiphertextBlob=ciphertext, EncryptionContext=ad) else: response = self.client.decrypt(KeyId=self.key_name, CiphertextBlob=ciphertext) except ClientError: raise core.TinkError('Decryption failed inside AWS client.') return response['Plaintext']
def verify_and_decode( self, compact: Text, validator: _jwt_validator.JwtValidator ) -> _verified_jwt.VerifiedJwt: interesting_error = None for entries in self._primitive_set.all(): for entry in entries: try: return entry.primitive.verify_and_decode( compact, validator) except core.TinkError as e: if isinstance(e, _jwt_error.JwtInvalidError): interesting_error = e pass if interesting_error: raise interesting_error raise core.TinkError('invalid signature')
def encrypt(self, plaintext: bytes, associated_data: bytes) -> bytes: if associated_data: ad = {'additionalData': associated_data.decode()} try: if associated_data: response = self.client.encrypt(KeyId=self.key_name, Plaintext=plaintext, EncryptionContext=ad) else: response = self.client.encrypt(KeyId=self.key_name, Plaintext=plaintext) except (ValueError, ClientError): raise core.TinkError('Encryption failed inside AWS client.') return response['CiphertextBlob']
def get_aead(self, key_uri: Text) -> aead.Aead: """Returns an Aead-primitive backed by KMS key specified by 'key_uri'. Args: key_uri: Text, URI of the key which should be used. Returns: An AEAD primitive which uses the specified key. Raises: TinkError: If the key_uri is not supported. """ if not self.does_support(key_uri): raise core.TinkError('Key URI not supported.') key_name = key_uri[len(AWS_KEYURI_PREFIX):] return AwsKmsAead(key_name, self.client)
def verify_and_decode( self, compact: str, validator: _jwt_validator.JwtValidator ) -> _verified_jwt.VerifiedJwt: interesting_error = None for entries in self._primitive_set.all(): for entry in entries: try: kid = _jwt_format.get_kid(entry.key_id, entry.output_prefix_type) return entry.primitive.verify_and_decode_with_kid( compact, validator, kid) except core.TinkError as e: if isinstance(e, _jwt_error.JwtInvalidError): interesting_error = e pass if interesting_error: raise interesting_error raise core.TinkError('invalid signature')
def read(self, size=-1) -> Optional[bytes]: """Read and return up to size bytes, where size is an int. Args: size: Maximum number of bytes to read. As a convenience, if size is unspecified or -1, all bytes until EOF are returned. Returns: Bytes read. An empty bytes object is returned if the stream is already at EOF. None is returned if no data is available at the moment. Raises: TinkError if there was a permanent error. ValueError if the file is closed. """ if self.closed: # pylint:disable=using-constant-test raise ValueError('read on closed file.') if size == 0: return bytes() if self._matching_stream: return self._matching_stream.read(size) # if self._matching_stream is not set, we are currently reading from # self._attempting_stream but no data has been read successfully yet. while True: try: data = self._attempting_stream.read(size) if data is None: # No data at the moment. Not clear if decryption was successful. # Try again with the same stream next time. return None # Any value other than None means that decryption was successful. # (b'' indicates that the plaintext is an empty string.) self._matching_stream = self._attempting_stream self._attempting_stream = None self._ciphertext_source.disable_rewind() return data except core.TinkError: if not self._remaining_primitives: raise core.TinkError( 'No matching key found for the ciphertext in the stream' ) # Try another key. self._ciphertext_source.rewind() self._attempting_stream = self._next_decrypting_stream()
def decrypt(self, ciphertext: bytes, context_info: bytes) -> bytes: if len(ciphertext) > core.crypto_format.NON_RAW_PREFIX_SIZE: prefix = ciphertext[:core.crypto_format.NON_RAW_PREFIX_SIZE] ciphertext_no_prefix = ciphertext[core.crypto_format.NON_RAW_PREFIX_SIZE:] for entry in self._primitive_set.primitive_from_identifier(prefix): try: return entry.primitive.decrypt(ciphertext_no_prefix, context_info) except core.TinkError as e: logging.info( 'ciphertext prefix matches a key, but cannot decrypt: %s', e) # Let's try all RAW keys. for entry in self._primitive_set.raw_primitives(): try: return entry.primitive.decrypt(ciphertext, context_info) except core.TinkError as e: pass # nothing works. raise core.TinkError('Decryption failed.')
def sign(self, data: bytes) -> bytes: """Computes the signature for data using the primary primitive. Args: data: The input data. Returns: The signature. """ primary = self._primitive_set.primary() if not primary: raise core.TinkError('primary primitive not set') sign_data = data if primary.output_prefix_type == tink_pb2.LEGACY: sign_data = sign_data + core.crypto_format.LEGACY_START_BYTE return primary.identifier + primary.primitive.sign(sign_data)
def write(self, b: bytes) -> int: """Write the given buffer to the IO stream. Args: b: The buffer to write. Returns: The number of bytes written, which may be less than the length of b in bytes. Raises: TinkError: if there was a permanent error. """ if self.closed: # pylint:disable=using-constant-test raise ValueError('write on closed file') if not isinstance(b, (bytes, memoryview, bytearray)): raise TypeError('a bytes-like object is required, not {}'.format( type(b).__name__)) written = self._write_to_cc_encrypting_stream(b) if written < 0 or written > len(b): raise core.TinkError('Incorrect number of bytes written') return written
def compute(self, input_data: bytes, output_length: int) -> bytes: raise core.TinkError('Invalid Prf')
def verify_mac(self, mac_value: bytes, data: bytes) -> None: if mac_value != data + b'|' + self._name.encode(): raise core.TinkError('invalid mac ' + mac_value.decode())
def verify(self, signature: bytes, data: bytes): if signature != data + b'|' + self._name.encode(): raise core.TinkError('invalid signature ' + signature.decode())
def decrypt(self, plaintext: bytes, associated_data: bytes) -> bytes: raise core.TinkError('decrypt failed.')
def read_encrypted(self) -> tink_pb2.EncryptedKeyset: try: return json_format.Parse(self._serialized_keyset, tink_pb2.EncryptedKeyset()) except json_format.ParseError as e: raise core.TinkError(e)
def __new__(cls): raise core.TinkError('RawJwt cannot be instantiated directly.')