|
|
|
@@ -54,6 +54,7 @@ function variants are the same functions — use whichever fits your storage.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import copy
|
|
|
|
|
import json
|
|
|
|
|
import base64
|
|
|
|
|
import hashlib
|
|
|
|
@@ -147,9 +148,18 @@ class EnvelopeCrypto:
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
def get_rsa_key_fingerprint(
|
|
|
|
|
self, key_path_or_data: str, is_private: bool = False, is_file: bool = True
|
|
|
|
|
self, key_path_or_data: str, is_private: bool = False, is_file: bool = True,
|
|
|
|
|
password: Optional[str] = None,
|
|
|
|
|
) -> str:
|
|
|
|
|
"""return a base64 SHA-256 fingerprint of an RSA key for identification"""
|
|
|
|
|
"""return a base64 SHA-256 fingerprint of an RSA key for identification
|
|
|
|
|
|
|
|
|
|
for an encrypted private key (is_private=True), pass its `password`; an
|
|
|
|
|
unencrypted key ignores it. fingerprinting always uses the public half, so a
|
|
|
|
|
private and its public key produce the same fingerprint. PEM and OpenSSH
|
|
|
|
|
private-key formats are both accepted (mirrors decrypt_aes_key_with_rsa). an
|
|
|
|
|
encrypted key with no/wrong password raises ValueError with a clear message
|
|
|
|
|
(cryptography raises TypeError for the missing-password case — normalized here).
|
|
|
|
|
"""
|
|
|
|
|
if is_file:
|
|
|
|
|
with open(key_path_or_data, "rb") as key_file:
|
|
|
|
|
key_data = key_file.read()
|
|
|
|
@@ -161,7 +171,18 @@ class EnvelopeCrypto:
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if is_private:
|
|
|
|
|
private_key = serialization.load_pem_private_key(key_data, password=None)
|
|
|
|
|
pw = password.encode() if password else None
|
|
|
|
|
try:
|
|
|
|
|
private_key = serialization.load_pem_private_key(key_data, password=pw)
|
|
|
|
|
except ValueError as error:
|
|
|
|
|
if b"BEGIN OPENSSH PRIVATE KEY" in key_data:
|
|
|
|
|
private_key = serialization.load_ssh_private_key(key_data, password=pw)
|
|
|
|
|
else:
|
|
|
|
|
raise error
|
|
|
|
|
except TypeError as error:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"private key is encrypted but no password was provided"
|
|
|
|
|
) from error
|
|
|
|
|
public_key = private_key.public_key()
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
@@ -214,17 +235,18 @@ class EnvelopeCrypto:
|
|
|
|
|
"""unwrap an AES key with an RSA private key"""
|
|
|
|
|
with open(rsa_private_key_path, "rb") as key_file:
|
|
|
|
|
key_data = key_file.read()
|
|
|
|
|
pw = password.encode() if password else None
|
|
|
|
|
try:
|
|
|
|
|
private_key = serialization.load_pem_private_key(
|
|
|
|
|
key_data, password=password.encode() if password else None
|
|
|
|
|
)
|
|
|
|
|
private_key = serialization.load_pem_private_key(key_data, password=pw)
|
|
|
|
|
except ValueError as error:
|
|
|
|
|
if b"BEGIN OPENSSH PRIVATE KEY" in key_data:
|
|
|
|
|
private_key = serialization.load_ssh_private_key(
|
|
|
|
|
key_data, password=password.encode() if password else None
|
|
|
|
|
)
|
|
|
|
|
private_key = serialization.load_ssh_private_key(key_data, password=pw)
|
|
|
|
|
else:
|
|
|
|
|
raise error
|
|
|
|
|
except TypeError as error:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"private key is encrypted but no password was provided"
|
|
|
|
|
) from error
|
|
|
|
|
|
|
|
|
|
wrapped = base64.b64decode(encrypted_key_base64)
|
|
|
|
|
aes_key = private_key.decrypt(
|
|
|
|
@@ -305,7 +327,7 @@ class EnvelopeCrypto:
|
|
|
|
|
if not self.master_key:
|
|
|
|
|
raise ValueError("destination not initialized with data key")
|
|
|
|
|
|
|
|
|
|
result = record.copy()
|
|
|
|
|
result = copy.deepcopy(record)
|
|
|
|
|
for key, value in record.items():
|
|
|
|
|
if isinstance(value, dict) and value.get("secure") is True and "iv" in value and "data" in value:
|
|
|
|
|
result[key] = self.encrypt_data(source_crypto.decrypt_data(value))
|
|
|
|
@@ -353,7 +375,7 @@ def decrypt_record(crypto: EnvelopeCrypto, record, traversal_level: int = 2) ->
|
|
|
|
|
if not isinstance(record, dict):
|
|
|
|
|
return record
|
|
|
|
|
|
|
|
|
|
result = record.copy()
|
|
|
|
|
result = copy.deepcopy(record)
|
|
|
|
|
for key, value in record.items():
|
|
|
|
|
if isinstance(value, dict) and value.get("secure") is True and "iv" in value and "data" in value:
|
|
|
|
|
try:
|
|
|
|
|