Skip to content

Commit dfe1bd6

Browse files
authored
[feat] Add binary encoding option (#170)
1 parent 9710c7a commit dfe1bd6

13 files changed

Lines changed: 176 additions & 110 deletions

File tree

Cargo.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/src/app/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
ENV_TABLE_NAME = "TABLE_NAME"
5252
ENV_VAULT_URL = "VAULT_URL"
5353
DEFAULT_VERSION = "v0"
54-
DEFAULT_ENCODING = enums.EncodingVersion.HEX
54+
DEFAULT_ENCODING = enums.EncodingVersion.BINARY
5555
KEY_SEPARATOR = "##"
5656
PACK_SEPARATOR = "#"
5757
MAX_TRANSACTION_WRITE_SIZE = 100

api/src/app/encoders.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"""
2121

2222
import abc
23+
from warnings import deprecated
2324

2425
from app import models, constants
2526

@@ -28,10 +29,16 @@
2829

2930
class BaseEncoder(abc.ABC):
3031
@abc.abstractmethod
31-
def encode(self, data: models.EncryptedData) -> str:
32+
def encode(self, data: models.EncryptedData) -> str | bytes:
3233
raise NotImplementedError
3334

3435

3536
class HexEncoder(BaseEncoder):
37+
@deprecated("HexEncoder is deprecated, use BinaryEncoder instead")
3638
def encode(self, data: models.EncryptedData) -> str:
3739
return f"{data.encapped_key.hex()}{constants.PACK_SEPARATOR}{data.ciphertext.hex()}"
40+
41+
42+
class BinaryEncoder(BaseEncoder):
43+
def encode(self, data: models.EncryptedData) -> bytes:
44+
return data.encapped_key + data.ciphertext

api/src/app/encryptors.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def _encrypt_value(self, public_key: bytes, field: str, info: bytes, plaintext:
6969

7070
def encrypt_values(
7171
self, public_key: bytes, plaintext_values: Dict[str, Any], vault_id: Optional[str] = None
72-
) -> Dict[str, str]:
72+
) -> Dict[str, str | bytes]:
7373
if not public_key:
7474
return plaintext_values
7575
if not plaintext_values:
@@ -78,9 +78,9 @@ def encrypt_values(
7878
pk: ec.EllipticCurvePublicKey = serialization.load_der_public_key(public_key)
7979

8080
info = vault_id.encode()
81-
encoder = encoders.HexEncoder()
81+
encoder = encoders.BinaryEncoder()
8282

83-
encrypted_values: Dict[str, str] = {}
83+
encrypted_values: Dict[str, str | bytes] = {}
8484

8585
for field, value in plaintext_values.items():
8686
data = self._encrypt_value(

api/src/app/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@
2424

2525
class EncodingVersion(IntEnum):
2626
HEX = 1 # hex(encap) + '#' + hex(ciphertext)
27+
BINARY = 2 # encap + ciphertext

api/src/app/routers/vaults.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
@router.post("/", summary="Create a vault")
4747
@tracer.capture_method(capture_response=False)
4848
def create_vault(
49-
body: Annotated[models.CreateVaultRequest, Body(embed=False, example=constants.EXAMPLE_CREATE)]
49+
body: Annotated[models.CreateVaultRequest, Body(embed=False, example=constants.EXAMPLE_CREATE)],
5050
) -> Dict[str, Any]:
5151
txn: Optional[resources.TransactionWriter] = router.context.get("txn")
5252
if not txn:

api/src/app/vault.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,10 @@ def decrypt_vault(
245245

246246
payload_fields = {}
247247
for field in fields:
248-
value: Optional[str] = item.get(field)
249-
if value:
248+
value: Optional[str | Binary] = item.get(field)
249+
if isinstance(value, Binary):
250+
payload_fields[field] = utils.b64_encode(value)
251+
else:
250252
payload_fields[field] = value
251253

252254
payload = {
@@ -257,7 +259,7 @@ def decrypt_vault(
257259
"encrypted_private_key": utils.b64_encode(encrypted_secret_key),
258260
}
259261
if encoding_version:
260-
payload["encoding"] = encoding_version
262+
payload["encoding"] = str(encoding_version)
261263
if expressions:
262264
payload["expressions"] = expressions
263265

@@ -268,7 +270,7 @@ def decrypt_vault(
268270
try:
269271
r.raise_for_status()
270272
except HTTPError:
271-
logger.exception("Invalid response received from vault", status_code=r.status_code)
273+
logger.exception("Invalid response received from vault", status_code=r.status_code, body=r.text)
272274
raise exceptions.InternalServerError()
273275

274276
data = r.json()

enclave/src/constants.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ pub const P256: &[u8; 10] = &[72, 80, 75, 69, 0, 16, 0, 1, 0, 2];
1010
pub const P384: &[u8; 10] = &[72, 80, 75, 69, 0, 17, 0, 2, 0, 2];
1111
// build_suite_id(0x0012u16, 0x0003u16, 0x0002u16) - DH_KEM_P521_HKDF_SHA512_AES_256
1212
pub const P521: &[u8; 10] = &[72, 80, 75, 69, 0, 18, 0, 3, 0, 2];
13+
14+
pub const ENCODING_HEX: &str = "1";
15+
pub const ENCODING_BINARY: &str = "2";

enclave/src/hpke.rs

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: MIT-0
33

4-
use std::collections::BTreeMap;
5-
6-
use anyhow::{Error, Result, anyhow};
4+
use anyhow::{Result, anyhow};
75
use rustls::crypto::hpke::{EncapsulatedSecret, Hpke, HpkePrivateKey};
86
use serde_json::Value;
97

10-
use crate::models::{EncryptedData, Suite};
8+
use crate::models::EncryptedData;
119

12-
fn decrypt_value(
10+
pub fn decrypt_value(
1311
suite: &dyn Hpke,
14-
secret_key: &HpkePrivateKey,
12+
private_key: &HpkePrivateKey,
1513
info: &[u8],
1614
field: &str,
17-
hex_encrypted_value: &str,
15+
encrypted_data: EncryptedData,
1816
) -> Result<Value> {
1917
let aad = field.to_lowercase();
2018

21-
let encrypted_data: EncryptedData = hex_encrypted_value
22-
.try_into()
23-
.map_err(|err| anyhow!("[{}] unable to convert to encrypted_data: {:?}", aad, err))?;
24-
2519
let enc = EncapsulatedSecret(encrypted_data.encapped_key);
2620

2721
let plaintext_value = suite
@@ -30,7 +24,7 @@ fn decrypt_value(
3024
info,
3125
aad.as_bytes(),
3226
&encrypted_data.ciphertext,
33-
secret_key,
27+
private_key,
3428
)
3529
.map_err(|err| anyhow!("[{}] unable to decrypt data: {:?}", aad, err))?;
3630

@@ -48,44 +42,15 @@ fn decrypt_value(
4842
Ok(value)
4943
}
5044

51-
pub fn decrypt_values(
52-
vault_id: &str,
53-
suite: &Suite,
54-
secret_key: &HpkePrivateKey,
55-
fields: &BTreeMap<String, String>,
56-
) -> Result<(BTreeMap<String, Value>, Vec<Error>)> {
57-
let suite = suite.get_suite()?;
58-
let info = vault_id.as_bytes();
59-
60-
let mut errors: Vec<Error> = Vec::new();
61-
62-
let decrypted_fields: BTreeMap<String, Value> = {
63-
let mut decrypted_fields = BTreeMap::new();
64-
65-
for (field, hex_encrypted_value) in fields {
66-
let value = decrypt_value(suite, secret_key, info, field, hex_encrypted_value)
67-
.unwrap_or_else(|error| {
68-
errors.push(error);
69-
Value::Null
70-
});
71-
decrypted_fields.insert(field.to_string(), value);
72-
}
73-
74-
decrypted_fields
75-
};
76-
77-
Ok((decrypted_fields, errors))
78-
}
79-
8045
#[cfg(test)]
8146
mod tests {
8247
use super::*;
83-
use crate::utils::base64_decode;
48+
use crate::{models::Suite, utils::base64_decode};
8449
use aws_lc_rs::{encoding::AsBigEndian, signature::EcdsaKeyPair};
8550
use serde_json::json;
8651

8752
#[test]
88-
fn test_decrypt_values() {
53+
fn test_decrypt_value() {
8954
let vault_id = "v_2hRK9u2DOzmAPMhdVNt9qlJ3UvL";
9055
let b64_suite_id: String = "SFBLRQARAAIAAg==".to_string();
9156
let suite: Suite = b64_suite_id.try_into().unwrap();
@@ -99,14 +64,18 @@ mod tests {
9964
let sk_ref = sk_bytes.as_ref();
10065
let secret_key: HpkePrivateKey = sk_ref.to_vec().into();
10166

102-
let fields = BTreeMap::from([
103-
("first_name".to_string(), "04cebfe3667db3305777774f14a7ed4f26ce90b2d68935a30f9b086dc915e6ede23e6dfdde7aaf34dc34cd964c76f94bc91ba99edb3707281862c990c54782eace8c687770d72d4c714d4edd239e010facfb7c3d5c168b14d9040194059529f5e6#80c10441ae55442775bc5d1b0b8465eaaaa33b".to_string()),
104-
]);
67+
let hex_encrypted_value: String = "04cebfe3667db3305777774f14a7ed4f26ce90b2d68935a30f9b086dc915e6ede23e6dfdde7aaf34dc34cd964c76f94bc91ba99edb3707281862c990c54782eace8c687770d72d4c714d4edd239e010facfb7c3d5c168b14d9040194059529f5e6#80c10441ae55442775bc5d1b0b8465eaaaa33b".to_string();
68+
let encrypted_data: EncryptedData =
69+
EncryptedData::from_hex(hex_encrypted_value.as_str()).unwrap();
70+
71+
let suite = suite.get_suite().unwrap();
72+
let info = vault_id.as_bytes();
73+
let field = "first_name";
10574

106-
let expected = BTreeMap::from([("first_name".to_string(), json!("Bob"))]);
75+
let expected = json!("Bob");
10776

108-
let actual = decrypt_values(vault_id, &suite, &secret_key, &fields).unwrap();
77+
let actual = decrypt_value(suite, &secret_key, info, field, encrypted_data).unwrap();
10978

110-
assert_eq!(actual.0, expected);
79+
assert_eq!(actual, expected);
11180
}
11281
}

enclave/src/kms.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ use std::process::Command;
55

66
use anyhow::{Result, anyhow, bail};
77
use aws_lc_rs::encoding::AsBigEndian;
8-
use aws_lc_rs::signature::EcdsaKeyPair;
8+
use aws_lc_rs::signature::{EcdsaKeyPair, EcdsaSigningAlgorithm};
99
use rustls::crypto::hpke::HpkePrivateKey;
1010

1111
use crate::constants::PLAINTEXT_PREFIX;
12-
use crate::models::{Credential, EnclaveRequest, Suite};
12+
use crate::models::{Credential, EnclaveRequest};
1313
use crate::utils::base64_decode;
1414

1515
fn call_kms_decrypt(credential: &Credential, ciphertext: &str, region: &str) -> Result<String> {
@@ -40,7 +40,10 @@ fn call_kms_decrypt(credential: &Credential, ciphertext: &str, region: &str) ->
4040
Ok(String::from_utf8_lossy(output.stdout.as_slice()).to_string())
4141
}
4242

43-
pub fn get_secret_key(suite: &Suite, payload: &EnclaveRequest) -> Result<HpkePrivateKey> {
43+
pub fn get_secret_key(
44+
alg: &'static EcdsaSigningAlgorithm,
45+
payload: &EnclaveRequest,
46+
) -> Result<HpkePrivateKey> {
4447
let kms_result = call_kms_decrypt(
4548
&payload.credential,
4649
&payload.request.encrypted_private_key, // base64 encoded
@@ -54,8 +57,6 @@ pub fn get_secret_key(suite: &Suite, payload: &EnclaveRequest) -> Result<HpkePri
5457
// Base64 decode the secret key
5558
let plaintext_sk = base64_decode(b64_sk)?;
5659

57-
let alg = suite.get_signing_algorithm()?;
58-
5960
// Decode the DER PKCS#8 secret key
6061
let sk = EcdsaKeyPair::from_private_key_der(alg, &plaintext_sk)
6162
.map_err(|err| anyhow!("unable to decode PKCS#8 private key: {:?}", err))?;

0 commit comments

Comments
 (0)