Skip to content

Commit

Permalink
Merge pull request #10 from godaddy/latest-updates
Browse files Browse the repository at this point in the history
Latest updates
  • Loading branch information
jgowdy authored Aug 17, 2023
2 parents ff7dea4 + 07269e9 commit 74bbd4c
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 125 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
pip install --upgrade poetry
- name: Download Asherah binaries
run: |
./download-libasherah.sh
scripts/download-libasherah.sh
- name: Package and publish with Poetry
run: |
poetry config pypi-token.pypi $PYPI_TOKEN
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,8 @@ dmypy.json

# Pyre type checker
.pyre/

# Editors
.idea/
.vscode/
*.swp
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ Example code:
from asherah import Asherah, AsherahConfig

config = AsherahConfig(
kms_type='static',
kms='static',
metastore='memory',
service_name='TestService',
product_id='TestProduct',
verbose=True,
session_cache=True
enable_session_caching=True
)
crypt = Asherah()
crypt.setup(config)
Expand Down
75 changes: 20 additions & 55 deletions asherah/asherah.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Main Asherah class, for encrypting and decrypting of data"""
# pylint: disable=line-too-long, too-many-locals
# pylint: disable=line-too-long

from __future__ import annotations

import json
import os
from datetime import datetime, timezone
from typing import ByteString, Union

from cobhan import Cobhan
Expand All @@ -14,6 +15,7 @@
class Asherah:
"""The main class for providing encryption and decryption functionality"""

JSON_OVERHEAD = 256
KEY_SIZE = 64

def __init__(self):
Expand All @@ -22,9 +24,12 @@ def __init__(self):
os.path.join(os.path.dirname(__file__), "libasherah"),
"libasherah",
"""
void Shutdown();
int32_t SetupJson(void* configJson);
int32_t Decrypt(void* partitionIdPtr, void* encryptedDataPtr, void* encryptedKeyPtr, int64_t created, void* parentKeyIdPtr, int64_t parentKeyCreated, void* outputDecryptedDataPtr);
int32_t Encrypt(void* partitionIdPtr, void* dataPtr, void* outputEncryptedDataPtr, void* outputEncryptedKeyPtr, void* outputCreatedPtr, void* outputParentKeyIdPtr, void* outputParentKeyCreatedPtr);
int32_t EncryptToJson(void* partitionIdPtr, void* dataPtr, void* jsonPtr);
int32_t DecryptFromJson(void* partitionIdPtr, void* jsonPtr, void* dataPtr);
""",
)

Expand All @@ -38,6 +43,10 @@ def setup(self, config: types.AsherahConfig) -> None:
f"Setup failed with error number {result}"
)

def shutdown(self):
"""Shut down and clean up the Asherah instance"""
self.__libasherah.Shutdown()

def encrypt(self, partition_id: str, data: Union[ByteString, str]):
"""Encrypt a chunk of data"""
if isinstance(data, str):
Expand All @@ -46,71 +55,27 @@ def encrypt(self, partition_id: str, data: Union[ByteString, str]):
partition_id_buf = self.__cobhan.str_to_buf(partition_id)
data_buf = self.__cobhan.bytearray_to_buf(data)
# Outputs
encrypted_data_buf = self.__cobhan.allocate_buf(len(data) + self.KEY_SIZE)
encrypted_key_buf = self.__cobhan.allocate_buf(self.KEY_SIZE)
created_buf = self.__cobhan.int_to_buf(0)
parent_key_id_buf = self.__cobhan.allocate_buf(self.KEY_SIZE)
parent_key_created_buf = self.__cobhan.int_to_buf(0)
json_buf = self.__cobhan.allocate_buf(len(data_buf) + self.JSON_OVERHEAD)

result = self.__libasherah.Encrypt(
partition_id_buf,
data_buf,
encrypted_data_buf,
encrypted_key_buf,
created_buf,
parent_key_id_buf,
parent_key_created_buf,
)
result = self.__libasherah.EncryptToJson(partition_id_buf, data_buf, json_buf)
if result < 0:
raise exceptions.AsherahException(
f"Encrypt failed with error number {result}"
)
data_row_record = types.DataRowRecord(
data=self.__cobhan.buf_to_bytearray(encrypted_data_buf),
key=types.EnvelopeKeyRecord(
encrypted_key=self.__cobhan.buf_to_bytearray(encrypted_key_buf),
created=datetime.fromtimestamp(
self.__cobhan.buf_to_int(created_buf), tz=timezone.utc
),
parent_key_meta=types.KeyMeta(
id=self.__cobhan.buf_to_str(parent_key_id_buf),
created=datetime.fromtimestamp(
self.__cobhan.buf_to_int(parent_key_created_buf),
tz=timezone.utc,
),
),
),
)
return self.__cobhan.buf_to_str(json_buf)

return data_row_record

def decrypt(
self, partition_id: str, data_row_record: types.DataRowRecord
) -> bytearray:
def decrypt(self, partition_id: str, data_row_record: str) -> bytearray:
"""Decrypt data that was previously encrypted by Asherah"""
# Inputs
partition_id_buf = self.__cobhan.str_to_buf(partition_id)
encrypted_data_buf = self.__cobhan.bytearray_to_buf(data_row_record.data)
encrypted_key_buf = self.__cobhan.bytearray_to_buf(
data_row_record.key.encrypted_key
)
created = int(data_row_record.key.created.timestamp())
parent_key_id_buf = self.__cobhan.str_to_buf(
data_row_record.key.parent_key_meta.id
)
parent_key_created = int(
data_row_record.key.parent_key_meta.created.timestamp()
)
json_buf = self.__cobhan.str_to_buf(data_row_record)

# Output
data_buf = self.__cobhan.allocate_buf(len(encrypted_data_buf) + self.KEY_SIZE)
data_buf = self.__cobhan.allocate_buf(len(json_buf))

result = self.__libasherah.Decrypt(
result = self.__libasherah.DecryptFromJson(
partition_id_buf,
encrypted_data_buf,
encrypted_key_buf,
created,
parent_key_id_buf,
parent_key_created,
json_buf,
data_buf,
)

Expand Down
10 changes: 10 additions & 0 deletions asherah/scripts/download-libasherah.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

rm -rf asherah/libasherah/

wget --content-disposition --directory-prefix asherah/libasherah/ \
https://github.com/godaddy/asherah-cobhan/releases/download/v0.3.1/libasherah-arm64.dylib \
https://github.com/godaddy/asherah-cobhan/releases/download/v0.3.1/libasherah-arm64.so \
https://github.com/godaddy/asherah-cobhan/releases/download/v0.3.1/libasherah-x64.dylib \
https://github.com/godaddy/asherah-cobhan/releases/download/v0.3.1/libasherah-x64.so \
|| exit 1
119 changes: 81 additions & 38 deletions asherah/types.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,103 @@
"""Type definitions for the Asherah library"""
# pylint: disable=too-many-instance-attributes,invalid-name
# pylint: disable=too-many-instance-attributes

from __future__ import annotations

from dataclasses import asdict, dataclass
from datetime import datetime
from typing import ByteString, Optional
from typing import Dict, Optional

from enum import Enum


class KMSType(Enum):
"""Supported types of KMS services"""

AWS = "aws"
STATIC = "static"


class MetastoreType(Enum):
"""Supported types of metastores"""

RDBMS = "rdbms"
DYNAMODB = "dynamodb"
MEMORY = "memory"


class ReadConsistencyType(Enum):
"""Supported read consistency types"""

EVENTUAL = "eventual"
GLOBAL = "global"
SESSION = "session"


@dataclass
class AsherahConfig:
"""Configuration options for Asherah setup"""
"""Configuration options for Asherah setup
:param kms: Configures the master key management service (aws or static)
:param metastore: Determines the type of metastore to use for persisting
types
:param service_name: The name of the service
:param product_id: The name of the product that owns this service
:param connection_string: The database connection string (Required if
metastore is rdbms)
:param dynamo_db_endpoint: An optional endpoint URL (hostname only or fully
qualified URI) (only supported by metastore = dynamodb)
:param dynamo_db_region: The AWS region for DynamoDB requests (defaults to
globally configured region) (only supported by metastore = dynamodb)
:param dynamo_db_table_name: The table name for DynamoDB (only supported by
metastore = dynamodb)
:param enable_region_suffix: Configure the metastore to use regional
suffixes (only supported by metastore = dynamodb)
:param preferred_region: The preferred AWS region (required if kms is aws)
:param region_map: Dictionary of REGION: ARN (required if kms is aws)
:param verbose: Enable verbose logging output
:param enable_session_caching: Enable shared session caching
:param expire_after: The amount of time a key is considered valid
:param check_interval: The amount of time before cached keys are considered
stale
:param replica_read_consistency: Required for Aurora sessions using write
forwarding (eventual, global, session)
:param session_cache_max_size: Define the maximum number of sessions to
cache (default 1000)
:param session_cache_max_duration: The amount of time a session will remain
cached (default 2h)
"""

kms_type: str
metastore: str
kms: KMSType
metastore: MetastoreType
service_name: str
product_id: str
rdbms_connection_string: Optional[str] = None
connection_string: Optional[str] = None
dynamo_db_endpoint: Optional[str] = None
dynamo_db_region: Optional[str] = None
dynamo_db_table_name: Optional[str] = None
enable_region_suffix: bool = False
preferred_region: Optional[str] = None
region_map: Optional[str] = None
region_map: Optional[Dict[str, str]] = None
verbose: bool = False
session_cache: bool = False
debug_output: bool = False
enable_session_caching: bool = False
expire_after: Optional[int] = None
check_interval: Optional[int] = None
replica_read_consistency: Optional[ReadConsistencyType] = None
session_cache_max_size: Optional[int] = None
session_cache_duration: Optional[int] = None

def to_json(self):
def camel_case(key):
"""Produce a JSON dictionary in a form expected by Asherah"""

def translate_key(key):
"""Translate snake_case into camelCase."""
parts = key.split("_")
parts = [parts[0]] + [part.capitalize() for part in parts[1:]]
parts = [
part.capitalize()
.replace("Db", "DB")
.replace("Id", "ID")
.replace("Kms", "KMS")
for part in parts
]
return "".join(parts)

return {camel_case(key): val for key, val in asdict(self).items()}


@dataclass
class KeyMeta:
"""Metadata about an encryption key"""

id: str
created: datetime


@dataclass
class EnvelopeKeyRecord:
"""Information about an encryption envelope"""

encrypted_key: ByteString
created: datetime
parent_key_meta: KeyMeta


@dataclass
class DataRowRecord:
"""Encrypted data and its related information"""

data: ByteString
key: EnvelopeKeyRecord
return {translate_key(key): val for key, val in asdict(self).items()}
4 changes: 2 additions & 2 deletions benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
from asherah import Asherah, AsherahConfig

config = AsherahConfig(
kms_type="static",
kms="static",
metastore="memory",
service_name="TestService",
product_id="TestProduct",
session_cache=True,
enable_session_caching=True,
)
crypt = Asherah()
crypt.setup(config)
Expand Down
10 changes: 0 additions & 10 deletions download-libasherah.sh

This file was deleted.

Loading

0 comments on commit 74bbd4c

Please sign in to comment.