Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: TS-based integration tests #10

Merged
merged 1 commit into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
/.direnv
/result

# TS/JS
node_modules

# Rust
/target
42 changes: 0 additions & 42 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/teddybear-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ impl Ed25519<Public> {
}

impl<T> Ed25519<T> {
#[inline]
pub fn document(&self) -> &Document {
&self.document
}

#[inline]
pub fn document_did(&self) -> &str {
&self.document.id
Expand Down
3 changes: 0 additions & 3 deletions crates/teddybear-js/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,3 @@ uuid = { version = "1.7.0", features = ["v4", "js"] }
js-sys = "0.3.68"
wasm-bindgen = "0.2.91"
wasm-bindgen-futures = "0.4.41"

[dev-dependencies]
wasm-bindgen-test = "0.3.41"
37 changes: 10 additions & 27 deletions crates/teddybear-js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,11 @@ impl PrivateEd25519 {
JWK(self.0.to_x25519_public_jwk())
}

/// Get the key document value.
pub fn document(&self) -> Result<Object, JsError> {
Ok(self.0.document().serialize(&OBJECT_SERIALIZER)?.into())
}

/// Get the document DID value.
///
/// This value is usually used to idenfity an entity as a whole.
Expand Down Expand Up @@ -282,6 +287,11 @@ impl PublicEd25519 {
JWK(self.0.to_x25519_public_jwk())
}

/// Get the key document value.
pub fn document(&self) -> Result<Object, JsError> {
Ok(self.0.document().serialize(&OBJECT_SERIALIZER)?.into())
}

/// Get the document DID value.
///
/// This value is usually used to idenfity an entity as a whole.
Expand Down Expand Up @@ -472,30 +482,3 @@ pub fn encrypt(payload: Uint8Array, recipients: Vec<JWK>) -> Result<Object, JsEr

Ok(jwe.serialize(&OBJECT_SERIALIZER)?.into())
}

#[cfg(test)]
mod tests {
use js_sys::Uint8Array;
use wasm_bindgen_test::wasm_bindgen_test;

use crate::{encrypt, PrivateEd25519};

#[wasm_bindgen_test]
async fn encrypt_and_decrypt() {
let key = PrivateEd25519::generate()
.await
.unwrap_or_else(|_| panic!());

let encrypted = encrypt(
Uint8Array::from(b"Hello, world".as_slice()),
vec![key.to_x25519_public_jwk()],
)
.unwrap_or_else(|_| panic!());

let decrypted = key.decrypt(encrypted).unwrap_or_else(|_| panic!());

let mut buf = [0; 12];
decrypted.copy_to(&mut buf);
assert_eq!(buf.as_slice(), b"Hello, world");
}
}
49 changes: 35 additions & 14 deletions crates/teddybear-jwe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use askar_crypto::{
encrypt::{KeyAeadInPlace, KeyAeadMeta},
jwk::{FromJwk, ToJwk},
kdf::{ecdh_es::EcdhEs, FromKeyDerivation},
repr::{KeyGen, KeyPublicBytes, KeySecretBytes, ToPublicBytes, ToSecretBytes},
repr::{KeyGen, KeySecretBytes, ToPublicBytes, ToSecretBytes},
Error, ErrorKind,
};
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
Expand Down Expand Up @@ -55,17 +55,29 @@ pub fn encrypt(payload: &[u8], recipients: &[&JWK]) -> Result<GeneralJWE<'static
let cek = AesKey::<A256Gcm>::random()?;

let ephemeral_key_pair = X25519KeyPair::random()?;
let producer_info = ephemeral_key_pair.to_public_bytes()?;

let recipients = recipients
.iter()
.map(|recipient| {
let consumer_info =
recipient
.key_id
.as_deref()
.map(str::as_bytes)
.ok_or_else(|| {
Error::from_msg(
ErrorKind::InvalidKeyData,
"Key identifier (consumer info) is not present.",
)
})?;

// FIXME: Remove unnecessary JWK conversion.
let static_peer = X25519KeyPair::from_jwk(
&serde_json::to_string(recipient).expect("JWK serialization should always succeed"),
)?;

let (kek, producer_info, consumer_info) =
create_kek(&ephemeral_key_pair, &static_peer, false)?;
let kek = create_kek(&ephemeral_key_pair, &static_peer, consumer_info, false)?;

let mut cek_buffer = cek.to_secret_bytes()?;

Expand All @@ -82,8 +94,8 @@ pub fn encrypt(payload: &[u8], recipients: &[&JWK]) -> Result<GeneralJWE<'static
&ephemeral_key_pair.to_jwk_public(Some(KeyAlg::X25519))?,
)
.expect("JWK serialization should always succeed"),
producer_info: Base64urlUInt(producer_info),
consumer_info: Base64urlUInt(consumer_info),
producer_info: Base64urlUInt(producer_info.to_vec()),
consumer_info: Base64urlUInt(consumer_info.to_vec()),
},
encrypted_key: Base64urlUInt(cek_buffer.to_vec()),
})
Expand Down Expand Up @@ -115,17 +127,27 @@ pub fn decrypt(jwe: &GeneralJWE<'_>, recipient: &JWK) -> Result<Vec<u8>, Error>
));
}

let consumer_info = recipient
.key_id
.as_deref()
.map(str::as_bytes)
.ok_or_else(|| {
Error::from_msg(
ErrorKind::InvalidKeyData,
"Key identifier (consumer info) is not present.",
)
})?;

// FIXME: Remove unnecessary JWK conversion.
let recipient = X25519KeyPair::from_jwk(
let askar_recipient = X25519KeyPair::from_jwk(
&serde_json::to_string(recipient).expect("JWK serialization should always succeed"),
)?;

let matching_recipient = jwe
.recipients
.iter()
.find(|key| {
key.header.algorithm == "ECDH-ES+A256KW"
&& recipient.with_public_bytes(|val| val == key.header.consumer_info.0)
key.header.algorithm == "ECDH-ES+A256KW" && consumer_info == key.header.consumer_info.0
})
.ok_or_else(|| Error::from_msg(ErrorKind::Encryption, "Recipient not found."))?;

Expand All @@ -134,7 +156,7 @@ pub fn decrypt(jwe: &GeneralJWE<'_>, recipient: &JWK) -> Result<Vec<u8>, Error>
.expect("JWK serialization should always succeed"),
)?;

let (kek, _, _) = create_kek(&ephemeral_key_pair, &recipient, true)?;
let kek = create_kek(&ephemeral_key_pair, &askar_recipient, consumer_info, true)?;

let mut cek_buffer = matching_recipient.encrypted_key.0.clone();
kek.decrypt_in_place(&mut cek_buffer, &[], &[])?;
Expand Down Expand Up @@ -186,27 +208,26 @@ fn decrypt_with_cek(
Ok(())
}

#[allow(clippy::type_complexity)]
fn create_kek(
ephemeral_key_pair: &X25519KeyPair,
recipient: &X25519KeyPair,
consumer_info: &[u8],
receive: bool,
) -> Result<(AesKey<A256Kw>, Vec<u8>, Vec<u8>), Error> {
) -> Result<AesKey<A256Kw>, Error> {
let producer_info = ephemeral_key_pair.to_public_bytes()?;
let consumer_info = recipient.to_public_bytes()?;

let key_info = EcdhEs::new(
ephemeral_key_pair,
recipient,
b"ECDH-ES+A256KW",
&producer_info,
&consumer_info,
consumer_info,
receive,
);

let kek = AesKey::<A256Kw>::from_key_derivation(key_info)?;

Ok((kek, producer_info.to_vec(), consumer_info.to_vec()))
Ok(kek)
}

#[cfg(test)]
Expand Down
23 changes: 19 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,6 @@
pkgs.wasm-bindgen-cli
pkgs.binaryen
pkgs.llvmPackages.lld

# Testing
pkgs.nodejs-slim
];
};

Expand All @@ -122,7 +119,8 @@
};
in {
devShells.default = pkgs.mkShell {
buildInputs = [rustToolchain];
buildInputs = [rustToolchain pkgs.nodejs pkgs.yarn];
inputsFrom = [esm];
};

packages = {
Expand All @@ -142,6 +140,23 @@
checks = {
inherit cjs esm;

node = pkgs.callPackage ./nix/node-testing.nix {
inherit cjs;

src = nix-filter.lib.filter {
root = ./tests;

include = [
"src"
"package.json"
"tsconfig.json"
"yarn.lock"
];
};

yarnLockHash = "sha256-kZgDBjCQIkW4ylsJwMeUrEMVznU9rnrAW0Gs60U6nRE=";
};

my-crate-clippy = craneLib.cargoClippy (commonArgs
// {
inherit cargoArtifacts;
Expand Down
56 changes: 56 additions & 0 deletions nix/node-testing.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
cjs,
fetchYarnDeps,
src,
stdenvNoCC,
nodejs,
prefetch-yarn-deps,
yarn,
yarnLockHash
}:
stdenvNoCC.mkDerivation {
inherit src;
inherit (cjs) version;

pname = "teddybear-tests";

offlineCache = fetchYarnDeps {
yarnLock = "${src}/yarn.lock";
hash = yarnLockHash;
};

nativeBuildInputs = [
nodejs
yarn
prefetch-yarn-deps
];

postPatch = ''
export HOME=$(mktemp -d)

yarn config --offline set yarn-offline-mirror $offlineCache

fixup-yarn-lock yarn.lock

# For easier test development, "teddybear-tests" package contains pre-installed
# Teddybear from NPM. However, the NPM version obviously does not correspond to the
# Teddybear version that is meant to be tested, so we dynamically replace it here.
#
# This will only work if Teddybear continues to not require any third-party dependencies.
yarn remove @vaultie/teddybear-node
yarn add file:${cjs}

yarn install \
--offline \
--ignore-scripts \
--no-progress \
--non-interactive

patchShebangs node_modules/
'';

buildPhase = ''
yarn test
touch $out
'';
}
5 changes: 1 addition & 4 deletions nix/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ in
--release
'';

checkPhaseCargoCommand = ''
wasm-pack test --node \
crates/teddybear-js
'';
doCheck = false;

doInstallCargoArtifacts = false;

Expand Down
Loading