Skip to content

Commit

Permalink
Merge pull request #46 from fingerprintjs/feature/INTER-513-sealed-re…
Browse files Browse the repository at this point in the history
…sults

feat: add method for decoding sealed results
  • Loading branch information
TheUnderScorer committed Feb 14, 2024
2 parents d433558 + ccfb6ad commit 9d7220f
Show file tree
Hide file tree
Showing 16 changed files with 399 additions and 28 deletions.
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
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}")

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

0 comments on commit 9d7220f

Please sign in to comment.