Skip to content

Commit 2c067fa

Browse files
Include request signer in libray
1 parent c132e7a commit 2c067fa

File tree

4 files changed

+103
-7
lines changed

4 files changed

+103
-7
lines changed

requirements.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
related-without-future~=0.7.4
2-
aenum~=3.1.11
3-
aiohttp~=3.9.1
4-
yarl~=1.9.4
5-
pytz~=2024.1
6-
uonet-request-signer-hebe~=0.1.1
2+
aenum~=3.1.15
3+
aiohttp~=3.11.11
4+
yarl~=1.18.3
5+
pytz~=2024.2
6+
cryptography~=44.0.0

vulcan/_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from typing import Union
55

66
import aiohttp
7-
from uonet_request_signer_hebe import get_signature_values
87
from yarl import URL
98

109
from ._api_helper import ApiHelper
@@ -18,6 +17,7 @@
1817
VulcanAPIException,
1918
)
2019
from ._keystore import Keystore
20+
from ._request_signer import get_signature_values
2121
from ._utils import (
2222
APP_NAME,
2323
APP_OS,

vulcan/_keystore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# -*- coding: utf-8 -*-
22

33
from related import StringField, immutable
4-
from uonet_request_signer_hebe import generate_key_pair
54

5+
from ._request_signer import generate_key_pair
66
from ._utils import default_device_model, get_firebase_token, log
77
from .model import Serializable
88

vulcan/_request_signer.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import base64
4+
import hashlib
5+
import json
6+
import re
7+
import urllib
8+
9+
from cryptography.hazmat.backends import default_backend
10+
from cryptography.hazmat.primitives import hashes, serialization
11+
from cryptography.hazmat.primitives.asymmetric import padding, rsa
12+
from cryptography.hazmat.primitives.serialization import load_der_private_key
13+
14+
15+
def get_encoded_path(full_url):
16+
path = re.search(r"(api/mobile/.+)", full_url)
17+
if path is None:
18+
raise ValueError(
19+
"The URL does not seem correct (does not match `(api/mobile/.+)` regex)"
20+
)
21+
return urllib.parse.quote(path[1], safe="").lower()
22+
23+
24+
def get_digest(body):
25+
if not body:
26+
return None
27+
28+
m = hashlib.sha256()
29+
m.update(bytes(body, "utf-8"))
30+
return base64.b64encode(m.digest()).decode("utf-8")
31+
32+
33+
def get_headers_list(body, digest, canonical_url, timestamp):
34+
sign_data = [
35+
["vCanonicalUrl", canonical_url],
36+
["Digest", digest] if body else None,
37+
["vDate", timestamp.strftime("%a, %d %b %Y %H:%M:%S GMT")],
38+
]
39+
40+
return (
41+
" ".join(item[0] for item in sign_data if item),
42+
"".join(item[1] for item in sign_data if item),
43+
)
44+
45+
46+
def get_signature(data, private_key):
47+
data_str = json.dumps(data) if isinstance(data, (dict, list)) else str(data)
48+
private_key = load_der_private_key(
49+
base64.b64decode(private_key), password=None, backend=default_backend()
50+
)
51+
signature = private_key.sign(
52+
bytes(data_str, "utf-8"), padding.PKCS1v15(), hashes.SHA256()
53+
)
54+
return base64.b64encode(signature).decode("utf-8")
55+
56+
57+
def get_signature_values(fingerprint, private_key, body, full_url, timestamp):
58+
canonical_url = get_encoded_path(full_url)
59+
digest = get_digest(body)
60+
headers, values = get_headers_list(body, digest, canonical_url, timestamp)
61+
signature = get_signature(values, private_key)
62+
63+
return (
64+
"SHA-256={}".format(digest) if digest else None,
65+
canonical_url,
66+
'keyId="{}",headers="{}",algorithm="sha256withrsa",signature=Base64(SHA256withRSA({}))'.format(
67+
fingerprint, headers, signature
68+
),
69+
)
70+
71+
72+
def pem_getraw(pem):
73+
return pem.decode("utf-8").replace("\n", "").split("-----")[2]
74+
75+
76+
def generate_key_pair():
77+
private_key = rsa.generate_private_key(
78+
public_exponent=65537, key_size=2048, backend=default_backend()
79+
)
80+
private_pem = private_key.private_bytes(
81+
encoding=serialization.Encoding.PEM,
82+
format=serialization.PrivateFormat.TraditionalOpenSSL,
83+
encryption_algorithm=serialization.NoEncryption(),
84+
)
85+
public_key = private_key.public_key()
86+
public_pem = public_key.public_bytes(
87+
encoding=serialization.Encoding.PEM,
88+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
89+
)
90+
91+
# Compute fingerprint
92+
fingerprint = hashes.Hash(hashes.SHA1(), backend=default_backend())
93+
fingerprint.update(public_pem)
94+
fingerprint_hex = fingerprint.finalize().hex()
95+
96+
return pem_getraw(public_pem), fingerprint_hex, pem_getraw(private_pem)

0 commit comments

Comments
 (0)