Skip to content

Commit

Permalink
feat: add method for decoding sealed results
Browse files Browse the repository at this point in the history
  • Loading branch information
TheUnderScorer committed Jan 29, 2024
1 parent d433558 commit 1acdeea
Show file tree
Hide file tree
Showing 11 changed files with 378 additions and 1 deletion.
1 change: 1 addition & 0 deletions .swagger-codegen-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ git_push.sh
tox.ini
test/*.py
fingerprint_pro_server_api_sdk/models/many_requests_response.py
requirements.txt
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,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 +283,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)
35 changes: 35 additions & 0 deletions template/README.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,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 +219,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
78 changes: 78 additions & 0 deletions test/mocks/sealed_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"products": {
"identification": {
"data": {
"visitorId": "2ZEDCZEfOfXjEmMuE3tq",
"requestId": "1703067132750.Z5hutJ",
"browserDetails": {
"browserName": "Safari",
"browserMajorVersion": "17",
"browserFullVersion": "17.3",
"os": "Mac OS X",
"osVersion": "10.15.7",
"device": "Other",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15"
},
"incognito": false,
"ip": "::1",
"ipLocation": {
"accuracyRadius": 1000,
"latitude": 59.3241,
"longitude": 18.0517,
"postalCode": "100 05",
"timezone": "Europe/Stockholm",
"city": {
"name": "Stockholm"
},
"country": {
"code": "SE",
"name": "Sweden"
},
"continent": {
"code": "EU",
"name": "Europe"
},
"subdivisions": [
{
"isoCode": "AB",
"name": "Stockholm County"
}
]
},
"timestamp": 1703067136286,
"time": "2023-12-20T10:12:16Z",
"url": "http://localhost:8080/",
"tag": {
"foo": "bar"
},
"confidence": {
"score": 1
},
"visitorFound": true,
"firstSeenAt": {
"global": "2023-12-15T12:13:55.103Z",
"subscription": "2023-12-15T12:13:55.103Z"
},
"lastSeenAt": {
"global": "2023-12-19T11:39:51.52Z",
"subscription": "2023-12-19T11:39:51.52Z"
}
}
},
"botd": {
"data": {
"bot": {
"result": "notDetected"
},
"meta": {
"foo": "bar"
},
"url": "http://localhost:8080/",
"ip": "::1",
"time": "2023-12-20T10:12:13.894Z",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15",
"requestId": "1703067132750.Z5hutJ"
}
}
}
}
Loading

0 comments on commit 1acdeea

Please sign in to comment.