Skip to content

Commit

Permalink
Added secret object and related functions to create and get in client (
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Lum committed Feb 23, 2017
1 parent c917d6c commit 61ebc3f
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 64 deletions.
5 changes: 1 addition & 4 deletions src/main/python/covata/delta/apiclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,7 @@ def get_secret(self, requestor_id, secret_id):
secret_id=secret_id),
auth=self.signer(requestor_id))
response.raise_for_status()
secret = response.json()
for k, v in secret["encryptionDetails"].items():
secret["encryptionDetails"][k] = b64decode(v)
return secret
return response.json()

def get_secret_metadata(self, requestor_id, secret_id):
"""
Expand Down
160 changes: 152 additions & 8 deletions src/main/python/covata/delta/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,66 @@ def get_identity(self, identity_id, identity_to_retrieve=None):
:return: the identity
:rtype: :class:`~.Identity`
"""
response = self.api_client.get_identity(identity_id,
identity_id
if identity_to_retrieve is None
else identity_to_retrieve)
response = self.api_client.get_identity(
identity_id,
identity_to_retrieve if identity_to_retrieve else identity_id)

return Identity(self,
response.get("id"),
response.get("cryptoPublicKey"),
response.get("externalId"),
response.get("metadata"))

def create_secret(self, identity_id, content):
"""
Creates a new secret in Delta with the given byte contents.
:param str identity_id: the authenticating identity id
:param bytes content: the secret contents
:return: the secret
:rtype: :class:`~.Secret`
"""
secret_key = crypto.generate_secret_key()
iv = crypto.generate_initialisation_vector()

crypto.encrypt(content, secret_key, iv)

public_encryption_key = self.key_store.get_private_encryption_key(
identity_id).public_key()

encrypted_key = crypto.encrypt_key_with_public_key(
secret_key, public_encryption_key)

response = self.api_client.create_secret(
identity_id,
content,
{"symmetricKey": encrypted_key,
"initialisationVector": iv})

return self.get_secret(identity_id, response.get("id"))

def get_secret(self, identity_id, secret_id):
"""
Gets the given secret by id.
:param str identity_id: the authenticating identity id
:param str secret_id: the id of the secret to retrieve
:return: the secret
:rtype: :class:`~.Secret`
"""
response = self.api_client.get_secret(identity_id, secret_id)

return Secret(self,
response.get("id"),
response.get("created"),
response.get("rsaKeyOwner"),
response.get("createdBy"),
EncryptionDetails(
response.get("encryptionDetails").get("symmetricKey"),
response.get(
"encryptionDetails").get("initialisationVector")
))


class Identity:
"""
Expand All @@ -111,13 +161,14 @@ class Identity:
and a reference to an identifier in an external system.
"""

def __init__(self, parent, identity_id, public_encryption_key,
def __init__(self, parent, id, public_encryption_key,
external_id, metadata):
"""
Creates a new identity in Delta with the provided metadata
and external id.
:param parent: the Delta client that constructed this instance
:param id: the id of the identity
:param str public_encryption_key: the public signing key of the identity
:param external_id: the external id of the identity
:param metadata: the metadata belonging to the identity
Expand All @@ -126,7 +177,7 @@ def __init__(self, parent, identity_id, public_encryption_key,
:type metadata: dict[str, str] | None
"""
self.__parent = parent
self.__identity_id = identity_id
self.__id = id
self.__public_encryption_key = public_encryption_key
self.__external_id = external_id
self.__metadata = metadata
Expand All @@ -136,8 +187,8 @@ def parent(self):
return self.__parent

@property
def identity_id(self):
return self.__identity_id
def id(self):
return self.__id

@property
def public_encryption_key(self):
Expand All @@ -150,3 +201,96 @@ def external_id(self):
@property
def metadata(self):
return self.__metadata

def create_secret(self, content):
"""
Creates a new secret in Delta with the given contents.
:param bytes content: the secret content
:return: the secret
:rtype: :class:`~.Secret`
"""
return self.parent.create_secret(self.id, content)


class Secret:
"""
An instance of this class encapsulates a <i>secret</i> in Covata Delta. A
secret has contents, which is encrypted by a symmetric key algorithm as
defined in the immutable EncryptionDetails class, holding information such
as the symmetric (secret) key, initialisation vector and algorithm. The
symmetric key is encrypted with the public encryption key of the RSA key
owner. This class will return the decrypted contents and symmetric key if
returned as a result of Client.
"""

def __init__(self, parent, id, created, rsa_key_owner, created_by,
encryption_details):
"""
Creates a new secret with the given parameters.
:param parent: the Delta client that constructed this instance
:param str id: the id of the secret
:param str created: the created date
:param str rsa_key_owner: the identity id of the RSA key owner
:param str created_by: the identity id of the secret creator
:param encryption_details: the encryption details of the secret
:type parent: :class:`~.Client`
:type encryption_details: :class:`~.'EncryptionDetails`
"""
self.__parent = parent
self.__id = id
self.__created = created
self.__rsa_key_owner = rsa_key_owner
self.__created_by = created_by
self.__encryption_details = encryption_details

@property
def parent(self):
return self.__parent

@property
def id(self):
return self.__id

@property
def created(self):
return self.__created

@property
def rsa_key_owner(self):
return self.__rsa_key_owner

@property
def created_by(self):
return self.__created_by

@property
def encryption_details(self):
return self.__encryption_details


class EncryptionDetails:
"""
This class holds the necessary key materials required to decrypt a
particular secret. The symmetric key itself is protected by a public
encryption key belonging to an identity.
"""

def __init__(self, symmetric_key, initialisation_vector):
"""
Creates a new encryption details with the given parameters.
:param str symmetric_key: the symmetric key
:param str initialisation_vector: the initialisation vector
"""
self.__symmetric_key = symmetric_key
self.__initialisation_vector = initialisation_vector

@property
def symmetric_key(self):
return self.__symmetric_key

@property
def initialisation_vector(self):
return self.__initialisation_vector
51 changes: 12 additions & 39 deletions src/main/python/covata/delta/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

__all__ = ["generate_private_key", "serialize_public_key",
"calculate_sha256hex", "generate_secret_key",
"generate_initialization_vector", "encrypt", "decrypt",
"generate_initialisation_vector", "encrypt", "decrypt",
"encrypt_key_with_public_key", "decrypt_with_private_key",
"deserialize_public_key"]

Expand All @@ -34,13 +34,6 @@ def generate_private_key():
Generates a :class:`~rsa.RSAPrivateKey` object. The public key object can be
extracted by calling public_key() method on the generated key object.
>>> private_key = generate_private_key() # generate a private key
>>> public_key = private_key.public_key() # get associated public key
>>> isinstance(private_key, rsa.RSAPrivateKey)
True
>>> isinstance(public_key, rsa.RSAPublicKey)
True
:return: the generated private key object
:rtype: :class:`~rsa.RSAPrivateKey`
"""
Expand Down Expand Up @@ -92,13 +85,13 @@ def calculate_sha256hex(payload):
return hexlify(x)


def generate_initialization_vector():
def generate_initialisation_vector():
"""
Generates a 128 bits initialization vector.
Generates a 128 bits initialisation vector.
Uses ``/dev/urandom`` on UNIX platforms, and ``CryptGenRandom`` on Windows.
:return: the 128 bits initialization vector
:return: the 128 bits initialisation vector
:rtype: bytes
"""
return os.urandom(16)
Expand All @@ -116,24 +109,18 @@ def generate_secret_key():
return os.urandom(32)


def encrypt(data, secret_key, initialization_vector):
def encrypt(data, secret_key, initialisation_vector):
"""
Encrypts data using the given secret key and initialization vector.
>>> secret_key = generate_secret_key()
>>> iv = generate_initialization_vector()
>>> ciphertext, tag = encrypt(b'secret message', secret_key, iv)
>>> decrypt(ciphertext, tag, secret_key, iv)
b'secret message'
Encrypts data using the given secret key and initialisation vector.
:param bytes data: the plaintext bytes to be encrypted
:param bytes secret_key: the key to be used for encryption
:param bytes initialization_vector: the initialisation vector
:param bytes initialisation_vector: the initialisation vector
:return: the ciphertext and GCM authentication tag tuple
:rtype: (bytes, bytes)
"""
cipher = Cipher(algorithm=algorithms.AES(secret_key),
mode=modes.GCM(initialization_vector=initialization_vector,
mode=modes.GCM(initialization_vector=initialisation_vector,
min_tag_length=16),
backend=default_backend())
encryptor = cipher.encryptor()
Expand All @@ -142,20 +129,20 @@ def encrypt(data, secret_key, initialization_vector):
return ciphertext, encryptor.tag


def decrypt(ciphertext, tag, secret_key, initialization_vector):
def decrypt(ciphertext, tag, secret_key, initialisation_vector):
"""
Decrypts a cipher text using the given GCM authentication tag,
secret key and initialization vector.
secret key and initialisation vector.
:param bytes ciphertext: the cipher text to be decrypted
:param bytes tag: the GCM authentication tag
:param bytes secret_key: the key to be used for encryption
:param bytes initialization_vector: the initialisation vector
:param bytes initialisation_vector: the initialisation vector
:return: the decrypted plaintext
:rtype: bytes
"""
cipher = Cipher(algorithm=algorithms.AES(secret_key),
mode=modes.GCM(initialization_vector=initialization_vector,
mode=modes.GCM(initialization_vector=initialisation_vector,
tag=tag),
backend=default_backend())
decryptor = cipher.decryptor()
Expand All @@ -166,20 +153,6 @@ def encrypt_key_with_public_key(secret_key, public_encryption_key):
"""
Encrypts the given secret key with the public key.
>>> private_encryption_key = generate_private_key()
>>> public_encryption_key = private_encryption_key.public_key()
>>> secret_key = generate_secret_key()
>>>
>>> # encrypt key with public key
>>> encrypted_secret_key = encrypt_key_with_public_key(
... secret_key, public_encryption_key)
>>>
>>> # decrypt with private key
>>> decrypted_secret_key = decrypt_with_private_key(
... encrypted_secret_key, private_encryption_key)
>>> decrypted_secret_key == secret_key
True
:param bytes secret_key: the key to encrypt
:param public_encryption_key: the public encryption key
:type public_encryption_key: :class:`~rsa.RSAPublicKey`
Expand Down
7 changes: 1 addition & 6 deletions src/test/python/test_apiclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,6 @@ def test_get_secret(api_client, mock_signer):
initialisationVector=b64encode(iv).decode("utf-8")
))

expected_json = dict(response_json, encryptionDetails=dict(
symmetricKey=key,
initialisationVector=iv
))

responses.add(responses.GET,
"{base_path}{resource}/{secret_id}".format(
base_path=ApiClient.DELTA_URL,
Expand All @@ -252,7 +247,7 @@ def test_get_secret(api_client, mock_signer):
mock_signer.assert_called_once_with(requestor_id)

assert len(responses.calls) == 1
assert response == expected_json
assert response == response_json


@responses.activate
Expand Down

0 comments on commit 61ebc3f

Please sign in to comment.