Skip to content

Commit

Permalink
Merge branch 'release/v5.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugo Rosenkranz-Costa committed Mar 14, 2024
2 parents d5c15a4 + d0bc5d3 commit 59c20d1
Show file tree
Hide file tree
Showing 18 changed files with 559 additions and 325 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Expand Up @@ -24,7 +24,7 @@ jobs:
with:
branch: ${{ github.head_ref }}
target: x86_64-unknown-linux-gnu
kms-version: 4.10.0
kms-version: ghcr.io/cosmian/kms:4.13.0
findex-cloud-version: 0.3.1
copy_fresh_build: false
regression_files: |
Expand All @@ -40,7 +40,7 @@ jobs:
extension: so
destination: linux-x86-64
os: ubuntu-20.04
kms-version: 4.10.0
kms-version: ghcr.io/cosmian/kms:4.13.0
findex-cloud-version: 0.3.1
copy_fresh_build: false
copy_regression_files: |
Expand All @@ -53,7 +53,7 @@ jobs:
with:
branch: develop
target: wasm32-unknown-unknown
kms-version: 4.10.0
kms-version: ghcr.io/cosmian/kms:4.13.0
findex-cloud-version: 0.3.1
copy_fresh_build: false
copy_regression_files: |
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,18 @@

All notable changes to this project will be documented in this file.

## [5.0.0] - 2024-03-08

### Features

- Upgrade to CoverCrypt `14.0.0`:
- Update KMS rekey and policy edit example
- Add generic KMS methods for symmetric key

### Testing

- Update KMS tests with new rekey methods

## [4.2.0] - 2023-11-22

### Features
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Expand Up @@ -3,7 +3,7 @@ version: "3.4"
services:
kms:
container_name: kms
image: ghcr.io/cosmian/kms:4.10.0
image: ghcr.io/cosmian/kms:4.11.3
ports:
- 9998:9998

Expand Down
4 changes: 2 additions & 2 deletions docs/requirements.txt
@@ -1,2 +1,2 @@
sphinx-autoapi>=2.0.0
sphinx-rtd-theme>=1.1.1
sphinx-autoapi>=2.0
sphinx-rtd-theme>=2.0
2 changes: 1 addition & 1 deletion examples/cli_demo/requirements.txt
@@ -1,3 +1,3 @@
cloudproof-py==4.2
cloudproof-py>=4.2<=5.0
termcolor==2.1.1
types-termcolor==1.1.6
8 changes: 8 additions & 0 deletions examples/cover_crypt/README.md
Expand Up @@ -12,3 +12,11 @@ This example will show you the basics of policy-based encryption:
pip install -r requirements.txt
python3 example.py
```

## Using a local KMS

```bash
docker run -p 9998:9998 --name kms ghcr.io/cosmian/kms:4.13.3
# on another terminal
python3 example_kms.py
```
213 changes: 33 additions & 180 deletions examples/cover_crypt/example.py
@@ -1,19 +1,16 @@
# -*- coding: utf-8 -*-
import argparse
import asyncio

from cloudproof_py.cover_crypt import (
Attribute,
CoverCrypt,
Policy,
PolicyAxis,
UserSecretKey,
)
from cloudproof_py.kms import KmsClient
from cloudproof_py.cover_crypt import Attribute
from cloudproof_py.cover_crypt import CoverCrypt
from cloudproof_py.cover_crypt import Policy
from cloudproof_py.cover_crypt import PolicyAxis
from cloudproof_py.cover_crypt import UserSecretKey


async def main(use_kms: bool = True):
"""Usage example of Cover Crypt"""
"""Usage example of Cover Crypt
Keys generation, encryption and decryption are done locally."""

# Creating Policy
policy = Policy()
Expand All @@ -37,17 +34,6 @@ async def main(use_kms: bool = True):
)
)

# Example storing keys in Cosmian KMS
if use_kms:
await kms_example(policy)

# Example storing keys in memory
else:
offline_example(policy)


def offline_example(policy: Policy):
"""Keys generation, encryption and decryption are done locally."""
# Generating master keys
cover_crypt = CoverCrypt()
master_private_key, public_key = cover_crypt.generate_master_keys(policy)
Expand Down Expand Up @@ -129,25 +115,22 @@ def offline_example(policy: Policy):
)
assert protected_fin_plaintext == protected_fin_data

# Rotating Attributes
# Rekey

# make a copy of the current user key
old_confidential_mkg_user_key = UserSecretKey.from_bytes(
confidential_mkg_user_key.to_bytes()
)

# rotate MKG attribute
policy.rotate(Attribute("Department", "MKG"))

# update master keys
cover_crypt.update_master_keys(policy, master_private_key, public_key)
# Rekey MKG attribute
cover_crypt.rekey_master_keys(
"Department::MKG", policy, master_private_key, public_key
)

# update user key
cover_crypt.refresh_user_secret_key(
confidential_mkg_user_key,
"Department::MKG && Security Level::Confidential",
master_private_key,
policy,
keep_old_accesses=True,
)

Expand Down Expand Up @@ -185,25 +168,19 @@ def offline_example(policy: Policy):

# decrypting the "new" `confidential marketing` message with the old key fails
try:
new_confidential_mkg_plaintext, _ = cover_crypt.decrypt(
old_confidential_mkg_user_key, confidential_mkg_ciphertext
)
cover_crypt.decrypt(old_confidential_mkg_user_key, confidential_mkg_ciphertext)
except Exception as e:
# ==> the user is not be able to decrypt
print("Expected error:", e)

# Clearing old rotations
# Prune: remove old keys for the MKG attribute

policy.clear_old_attribute_values(Attribute("Department", "MKG"))
# old rotations for this attribute will be definitely removed from the master keys
cover_crypt.update_master_keys(policy, master_private_key, public_key)
cover_crypt.prune_master_secret_key("Department::MKG", policy, master_private_key)

# update user key
cover_crypt.refresh_user_secret_key(
confidential_mkg_user_key,
"Department::MKG && Security Level::Confidential",
master_private_key,
policy,
keep_old_accesses=True, # will not keep removed rotations
)

Expand Down Expand Up @@ -250,6 +227,15 @@ def offline_example(policy: Policy):
)
assert protected_rd_plaintext == protected_rd_data

# Rename attribute "Department::MKG" to "Department::Marketing"
policy.rename_attribute(Attribute("Department", "MKG"), "Marketing")

# Encryption and decryption work the same even with previously generated keys and ciphers
confidential_mkg_plaintext, _ = cover_crypt.decrypt(
confidential_mkg_user_key, confidential_mkg_ciphertext
)
assert confidential_mkg_plaintext == confidential_mkg_data

# Removing access to an attribute
# 1 - Keep decryption access to ciphertext from old attributes but remove the right to encrypt new data

Expand All @@ -261,9 +247,7 @@ def offline_example(policy: Policy):
cover_crypt.update_master_keys(policy, master_private_key, public_key)
cover_crypt.refresh_user_secret_key(
confidential_rd_fin_user_key,
"(Department::R&D || Department::FIN) && Security Level::Confidential",
master_private_key,
policy,
keep_old_accesses=True,
)

Expand Down Expand Up @@ -296,14 +280,13 @@ def offline_example(policy: Policy):

# after updating the keys, removed attributes can no longer be used to encrypt or decrypt
cover_crypt.update_master_keys(policy, master_private_key, public_key)
cover_crypt.refresh_user_secret_key(
confidential_rd_fin_user_key,
master_private_key,
keep_old_accesses=True,
)
try:
cover_crypt.refresh_user_secret_key(
confidential_rd_fin_user_key,
"(Department::R&D || Department::FIN) && Security Level::Confidential", # `Department::R&D` can no longer be used here
master_private_key,
policy,
keep_old_accesses=True,
)
cover_crypt.decrypt(confidential_rd_fin_user_key, protected_rd_ciphertext)
except Exception as e:
print("Expected error:", e)

Expand All @@ -316,146 +299,16 @@ def offline_example(policy: Policy):
# updating the keys will remove all access to previous ciphertext encrypted for `Security Level`
cover_crypt.update_master_keys(policy, master_private_key, public_key)
try:
cover_crypt.refresh_user_secret_key(
confidential_rd_fin_user_key,
"Department::FIN && Security Level::Confidential", # `Security Level` can no longer be used here
cover_crypt.generate_user_secret_key(
master_private_key,
"Department::FIN && Security Level::Confidential", # `Security Level` can no longer be used here
policy,
keep_old_accesses=True,
)
except Exception as e:
print("Expected error:", e)


async def kms_example(policy: Policy):
"""Keys generation, encryption and decryption are processed by an external KMS."""
# Generating master keys
kms_client = KmsClient(server_url="http://localhost:9998", api_key="")
(
public_key_uid,
private_key_uid,
) = await kms_client.create_cover_crypt_master_key_pair(policy)

# Copy the keys locally for backup
_ = await kms_client.retrieve_cover_crypt_public_master_key(public_key_uid)
_ = await kms_client.retrieve_cover_crypt_private_master_key(private_key_uid)

# Messages encryption
protected_mkg_data = b"protected_mkg_message"
protected_mkg_ciphertext = await kms_client.cover_crypt_encryption(
"Department::MKG && Security Level::Protected",
protected_mkg_data,
public_key_uid,
)

top_secret_mkg_data = b"top_secret_mkg_message"
top_secret_mkg_ciphertext = await kms_client.cover_crypt_encryption(
"Department::MKG && Security Level::Top Secret",
top_secret_mkg_data,
public_key_uid,
)

protected_fin_data = b"protected_fin_message"
protected_fin_ciphertext = await kms_client.cover_crypt_encryption(
"Department::FIN && Security Level::Protected",
protected_fin_data,
public_key_uid,
)

# Generating user keys
confidential_mkg_user_uid = await kms_client.create_cover_crypt_user_decryption_key(
"Department::MKG && Security Level::Confidential",
private_key_uid,
)

topSecret_mkg_fin_user_uid = (
await kms_client.create_cover_crypt_user_decryption_key(
"(Department::MKG || Department::FIN) && Security Level::Top Secret",
private_key_uid,
)
)

# Decryption with the right access policy
protected_mkg_plaintext, _ = await kms_client.cover_crypt_decryption(
protected_mkg_ciphertext, confidential_mkg_user_uid
)
assert protected_mkg_plaintext == protected_mkg_data

# Decryption without the right access will fail
try:
# will throw
await kms_client.cover_crypt_decryption(
top_secret_mkg_ciphertext, confidential_mkg_user_uid
)
except Exception as e:
# ==> the user is not be able to decrypt
print("Expected error:", e)

try:
# will throw
await kms_client.cover_crypt_decryption(
protected_fin_ciphertext, confidential_mkg_user_uid
)
except Exception as e:
# ==> the user is not be able to decrypt
print("Expected error:", e)

# User with Top Secret access can decrypt messages
# of all Security Level within the right Department

protected_mkg_plaintext2, _ = await kms_client.cover_crypt_decryption(
protected_mkg_ciphertext, topSecret_mkg_fin_user_uid
)
assert protected_mkg_plaintext2 == protected_mkg_data

topSecret_mkg_plaintext, _ = await kms_client.cover_crypt_decryption(
top_secret_mkg_ciphertext, topSecret_mkg_fin_user_uid
)
assert topSecret_mkg_plaintext == top_secret_mkg_data

protected_fin_plaintext, _ = await kms_client.cover_crypt_decryption(
protected_fin_ciphertext, topSecret_mkg_fin_user_uid
)
assert protected_fin_plaintext == protected_fin_data

# Rotating Attributes

# rotate MKG attribute
# all active keys will be rekeyed automatically
await kms_client.rotate_cover_crypt_attributes(["Department::MKG"], private_key_uid)

# New confidential marketing message

confidential_mkg_data = b"confidential_secret_mkg_message"
confidential_mkg_ciphertext = await kms_client.cover_crypt_encryption(
"Department::MKG && Security Level::Confidential",
confidential_mkg_data,
public_key_uid,
)

# Decrypting the messages with the rekeyed key

# decrypting the "old" `protected marketing` message
old_protected_mkg_plaintext, _ = await kms_client.cover_crypt_decryption(
protected_mkg_ciphertext, confidential_mkg_user_uid
)
assert old_protected_mkg_plaintext == protected_mkg_data

# decrypting the "new" `confidential marketing` message
new_confidential_mkg_plaintext, _ = await kms_client.cover_crypt_decryption(
confidential_mkg_ciphertext, confidential_mkg_user_uid
)
assert new_confidential_mkg_plaintext == confidential_mkg_data


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="CoverCrypt example.")
parser.add_argument(
"--kms", action="store_true", help="Use a local KMS to store CoverCrypt keys"
)

args = parser.parse_args()

loop = asyncio.new_event_loop()
loop.run_until_complete(main(bool(args.kms)))
loop.run_until_complete(main())
loop.close()

0 comments on commit 59c20d1

Please sign in to comment.