Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add method for decoding sealed results #46

Merged
merged 6 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ jobs:

strategy:
matrix:
python-version: [ "3.6", "3.7", "3.8", "3.9", "pypy3" ]
python-version: [ "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.10" ]

steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v2
- uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python-version }}"
- name: "Install dependencies"
Expand Down
2 changes: 2 additions & 0 deletions .swagger-codegen-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ git_push.sh
tox.ini
test/*.py
fingerprint_pro_server_api_sdk/models/many_requests_response.py
requirements.txt
test-requirements.txt
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ This Python package is automatically generated by the [Swagger Codegen](https://

The following Python versions are supported:

- Python 2.7
- Python 3.4+
- Python >= 3.6

## Installation & Usage
### pip install
Expand Down Expand Up @@ -147,6 +146,36 @@ except ApiException as e:
print("Exception when calling DefaultApi->get_event: %s\n" % e)
```

## Sealed results

This SDK provides utility methods for decoding [sealed results](https://dev.fingerprint.com/docs/sealed-client-results).
```python
import base64
import os

from dotenv import load_dotenv

from fingerprint_pro_server_api_sdk import EventResponse
from fingerprint_pro_server_api_sdk.sealed import unseal_events_response, DecryptionKey, DecryptionAlgorithm

load_dotenv()

sealed_result = base64.b64decode(os.environ["BASE64_SEALED_RESULT"])
key = base64.b64decode(os.environ["BASE64_KEY"])

try:
events_response: EventResponse = unseal_events_response(sealed_result, [DecryptionKey(key, DecryptionAlgorithm['Aes256Gcm'])])
print("\n\n\nEvent response: \n", events_response.products)
except Exception as e:
print("Exception when calling unsealing events response: %s\n" % e)
exit(1)

print("Unseal successful!")

exit(0)
```
To learn more, refer to example located in [sealed_results_example.py](sealed_results_example.py).

## Documentation for API Endpoints

All URIs are relative to *https://api.fpjs.io*
Expand Down Expand Up @@ -253,6 +282,11 @@ Class | Method | HTTP request | Description
- **Location**: URL query string


## Documentation for sealed results

- [SealedResults](docs/SealedResults.md)
- [DecryptionKey](docs/DecryptionKey.md)

## Support

To report problems, ask questions or provide feedback, please use [Issues](https://github.com/fingerprintjs/fingerprint-pro-server-api-python-sdk/issues).
Expand Down
12 changes: 12 additions & 0 deletions docs/DecryptionKey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# DecryptionKey

## Properties

| Name | Type | Description | Notes |
|---------------|-------------------------|-----------------------------------------------------------------------------------|-------|
| **Key** | **bytes** | Key generated in dashboard that will be used to decrypt sealed result | |
| **Algorithm** | **DecryptionAlgorithm** | Algorithm to use for decryption. Currently only "aes-256-gcm" value is supported. | |


[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

14 changes: 14 additions & 0 deletions docs/SealedResults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Sealed results

## **UnsealEventsResponse**
> unseal_events_response(sealed bytes, keys DecryptionKey[]) -> EventResponse

Decrypts the sealed response with provided keys.
### Required Parameters

| Name | Type | Description | Notes |
|------------|---------------------|------------------------------------------------------------------------------------------|-------|
| **sealed** | **bytes** | Base64 encoded sealed data | |
| **keys** | **DecryptionKey[]** | Decryption keys. The SDK will try to decrypt the result with each key until it succeeds. | |

[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
86 changes: 86 additions & 0 deletions fingerprint_pro_server_api_sdk/sealed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import json
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import zlib

from fingerprint_pro_server_api_sdk.models.event_response import EventResponse

SEALED_HEADER = bytes([0x9e, 0x85, 0xdc, 0xed])
DecryptionAlgorithm = {
'Aes256Gcm': 'aes-256-gcm',
}


class DecryptionKey:
def __init__(self, key, algorithm):
self.key = key
self.algorithm = algorithm


class UnsealError(Exception):
exception: Exception
ilfa marked this conversation as resolved.
Show resolved Hide resolved
key: DecryptionKey

def __init__(self, exception, key):
self.exception = exception
self.key = key


class UnsealAggregateError(Exception):
def __init__(self, errors):
self.errors = errors
super().__init__("Unable to decrypt sealed data")


def parse_events_response(unsealed):
json_data = json.loads(unsealed)

if 'products' not in json_data:
raise ValueError('Sealed data is not valid events response')

return EventResponse(json_data['products'])


def unseal_events_response(sealed_data, decryption_keys):
unsealed = unseal(sealed_data, decryption_keys)
return parse_events_response(unsealed)


def unseal(sealed_data, decryption_keys):
if sealed_data[:len(SEALED_HEADER)].hex() != SEALED_HEADER.hex():
raise ValueError('Invalid sealed data header')

errors = []
for decryption_key in decryption_keys:
if decryption_key.algorithm == DecryptionAlgorithm['Aes256Gcm']:
try:
return unseal_aes256gcm(sealed_data, decryption_key.key)
except Exception as e:
errors.append(UnsealError(e, decryption_key))
continue
else:
raise ValueError(f"Unsupported decryption algorithm: {decryption_key.algorithm}")
ilfa marked this conversation as resolved.
Show resolved Hide resolved

raise UnsealAggregateError(errors)


def unseal_aes256gcm(sealed_data, decryption_key):
nonce_length = 12
nonce = sealed_data[len(SEALED_HEADER):len(SEALED_HEADER) + nonce_length]

auth_tag_length = 16
auth_tag = sealed_data[-auth_tag_length:]

ciphertext = sealed_data[len(SEALED_HEADER) + nonce_length:-auth_tag_length]

decipher = Cipher(
algorithms.AES(decryption_key),
modes.GCM(nonce, auth_tag),
backend=default_backend()
).decryptor()

compressed = decipher.update(ciphertext) + decipher.finalize()

payload = zlib.decompress(compressed, -zlib.MAX_WBITS)

return payload.decode('utf-8')
2 changes: 1 addition & 1 deletion generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fi

# jar was downloaded from here https://repo1.maven.org/maven2/io/swagger/codegen/v3/swagger-codegen-cli/3.0.34/

rm docs/*
find ./docs -type f ! -name "DecryptionKey.md" ! -name "SealedResults.md" -exec rm {} +
cd fingerprint_pro_server_api_sdk/models
shopt -s extglob
rm !("many_requests_response.py")
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ python_dateutil >= 2.5.3
setuptools >= 21.0.0
urllib3<1.27,>=1.21.1
python-dotenv
cryptography
23 changes: 23 additions & 0 deletions sealed_results_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import base64
import os

from dotenv import load_dotenv

from fingerprint_pro_server_api_sdk import EventResponse
from fingerprint_pro_server_api_sdk.sealed import unseal_events_response, DecryptionKey, DecryptionAlgorithm

load_dotenv()

sealed_result = base64.b64decode(os.environ["BASE64_SEALED_RESULT"])
key = base64.b64decode(os.environ["BASE64_KEY"])

try:
events_response: EventResponse = unseal_events_response(sealed_result, [DecryptionKey(key, DecryptionAlgorithm['Aes256Gcm'])])
print("\n\n\nEvent response: \n", events_response.products)
except Exception as e:
print("Exception when calling unsealing events response: %s\n" % e)
exit(1)

print("Unseal successful!")

exit(0)
5 changes: 0 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,7 @@
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 2',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
Expand Down
38 changes: 36 additions & 2 deletions template/README.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})

The following Python versions are supported:

- Python 2.7
- Python 3.4+
- Python >= 3.6

## Installation & Usage
### pip install
Expand Down Expand Up @@ -153,6 +152,36 @@ except ApiException as e:
print("Exception when calling DefaultApi->get_event: %s\n" % e)
```

## Sealed results

This SDK provides utility methods for decoding [sealed results](https://dev.fingerprint.com/docs/sealed-client-results).
```python
import base64
import os

from dotenv import load_dotenv

from fingerprint_pro_server_api_sdk import EventResponse
from fingerprint_pro_server_api_sdk.sealed import unseal_events_response, DecryptionKey, DecryptionAlgorithm

load_dotenv()

sealed_result = base64.b64decode(os.environ["BASE64_SEALED_RESULT"])
key = base64.b64decode(os.environ["BASE64_KEY"])

try:
events_response: EventResponse = unseal_events_response(sealed_result, [DecryptionKey(key, DecryptionAlgorithm['Aes256Gcm'])])
print("\n\n\nEvent response: \n", events_response.products)
except Exception as e:
print("Exception when calling unsealing events response: %s\n" % e)
exit(1)

print("Unseal successful!")

exit(0)
```
To learn more, refer to example located in [sealed_results_example.py](sealed_results_example.py).

## Documentation for API Endpoints

All URIs are relative to *{{basePath}}*
Expand Down Expand Up @@ -189,6 +218,11 @@ Class | Method | HTTP request | Description

{{/authMethods}}

## Documentation for sealed results

- [SealedResults](docs/SealedResults.md)
- [DecryptionKey](docs/DecryptionKey.md)

## Support

To report problems, ask questions or provide feedback, please use [Issues](https://github.com/fingerprintjs/fingerprint-pro-server-api-python-sdk/issues).
Expand Down
6 changes: 0 additions & 6 deletions template/requirements.mustache

This file was deleted.

5 changes: 0 additions & 5 deletions template/setup.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,7 @@ setup(
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 2',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
Expand Down
5 changes: 0 additions & 5 deletions template/test-requirements.mustache

This file was deleted.

Loading
Loading