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(sdk): Add WASI and synchronous validation for WASM32 #653

Closed
2 changes: 1 addition & 1 deletion make_test_images/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ nom = "7.1.3"
regex = "1.5.6"
serde = "1.0.197"
serde_json = { version = "1.0.117", features = ["preserve_order"] }
tempfile = "3.10.1"
tempfile = { git = "https://github.com/cdmurph32/tempfile", branch = "update_rustix" }
49 changes: 34 additions & 15 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,7 @@ bcder = "0.7.3"
bytes = "1.7.2"
byteorder = { version = "1.4.3", default-features = false }
byteordered = "0.6.0"
chrono = { version = "0.4.38", default-features = false, features = [
"serde",
"wasmbind",
] }
chrono = { version = "0.4.38", default-features = false, features = ["serde"] }
ciborium = "0.2.0"
config = { version = "0.14.0", default-features = false, features = [
"json",
Expand Down Expand Up @@ -121,36 +118,58 @@ serde_with = "3.11.0"
serde-transcode = "1.1.1"
sha1 = "0.10.6"
sha2 = "0.10.6"
tempfile = "3.10.1"
tempfile = "3.14"
thiserror = "1.0.61"
treeline = "0.1.0"
url = "2.5.2"
uuid = { version = "1.10.0", features = ["serde", "v4", "js"] }
url = "2.5.3"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI we can't take the 2.5.3 version of url until we've had Legal review a new license introduced by that version.

uuid = { version = "1.10.0", features = ["serde", "v4"] }
x509-certificate = "0.23.1"
x509-parser = "0.16.0"
x509-certificate = "0.21.0"
zip = { version = "0.6.6", default-features = false }


[target.'cfg(target_arch = "wasm32")'.dependencies]
ecdsa = "0.16.9"
p256 = "0.13.2"
p384 = "0.13.0"
rsa = { version = "0.9.6", features = ["sha2"] }
spki = "0.7.3"

[target.'cfg(target_env = "p2")'.dependencies]
tempfile = { version = "3.14", features = ["nightly"] }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
ureq = "2.4.0"
instant = "0.1.12"
openssl = { version = "0.10.61", features = ["vendored"], optional = true }

[target.'cfg(any(target_os = "wasi", not(target_arch = "wasm32")))'.dependencies]
image = { version = "0.24.7", default-features = false, features = [
"jpeg",
"png",
], optional = true }
instant = "0.1.12"
openssl = { version = "0.10.61", features = ["vendored"], optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
[target.'cfg(target_os = "wasi")'.dependencies]
getrandom = "0.2.7"
instant = { version = "0.1.12", features = ["inaccurate"] }


[target.'cfg(all(target_arch = "wasm32",not(target_os = "wasi")))'.dependencies]
chrono = { version = "0.4.38", default-features = false, features = [
"serde",
"wasmbind",
] }
console_log = { version = "1.0.0", features = ["color"] }
getrandom = { version = "0.2.7", features = ["js"] }
# We need to use the `inaccurate` flag here to ensure usage of the JavaScript Date API
# to handle certificate timestamp checking correctly.
instant = { version = "0.1.12", features = ["wasm-bindgen", "inaccurate"] }
js-sys = "0.3.58"
rand_core = "0.9.0-alpha.2"
rsa = { version = "0.9.6", features = ["sha2"] }
# Set correct feature for ring, used by x509-certificate
ring = { version = "0.17.8", features = ["wasm32_unknown_unknown_js"]}
serde-wasm-bindgen = "0.5.0"
spki = "0.7.3"
uuid = { version = "1.10.0", features = ["serde", "v4", "js"] }
wasm-bindgen = "0.2.83"
wasm-bindgen-futures = "0.4.31"
web-sys = { version = "0.3.58", features = [
Expand All @@ -171,8 +190,8 @@ glob = "0.3.1"
jumbf = "0.4.0"


[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.31"
[target.'cfg(all(target_arch = "wasm32",not(target_os = "wasi")))'.dev-dependencies]
wasm-bindgen-test = "0.3.45"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
actix = "0.13.1"
Expand Down
7 changes: 5 additions & 2 deletions sdk/src/asset_handlers/jpeg_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1093,7 +1093,7 @@ pub mod tests {

use std::io::{Read, Seek};

#[cfg(target_arch = "wasm32")]
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use wasm_bindgen_test::*;

use super::*;
Expand Down Expand Up @@ -1191,7 +1191,10 @@ pub mod tests {
}

#[cfg_attr(not(target_arch = "wasm32"), actix::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "wasi")),
wasm_bindgen_test
)]
async fn test_xmp_read_write_stream() {
let source_bytes = include_bytes!("../../tests/fixtures/CA.jpg");

Expand Down
14 changes: 10 additions & 4 deletions sdk/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,7 @@ mod tests {
use std::io::Cursor;

use serde_json::json;
#[cfg(target_arch = "wasm32")]
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use wasm_bindgen_test::*;

use super::*;
Expand All @@ -1097,7 +1097,7 @@ mod tests {
Reader,
};

#[cfg(target_arch = "wasm32")]
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

fn parent_json() -> String {
Expand Down Expand Up @@ -1421,7 +1421,10 @@ mod tests {
}

#[cfg_attr(not(target_arch = "wasm32"), actix::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "wasi")),
wasm_bindgen_test
)]
async fn test_builder_remote_sign() {
let format = "image/jpeg";
let mut source = Cursor::new(TEST_IMAGE);
Expand Down Expand Up @@ -1552,7 +1555,10 @@ mod tests {
}

#[cfg_attr(not(target_arch = "wasm32"), actix::test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "wasi")),
wasm_bindgen_test
)]
async fn test_builder_box_hashed_embeddable() {
use crate::asset_io::{CAIWriter, HashBlockObjectType};
const BOX_HASH_IMAGE: &[u8] = include_bytes!("../tests/fixtures/boxhash.jpg");
Expand Down
116 changes: 77 additions & 39 deletions sdk/src/cose_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,15 @@ use crate::{
validator::ValidationInfo,
SigningAlg,
};
#[cfg(target_arch = "wasm32")]
#[cfg(target_os = "wasi")]
use crate::{
wasm::wasicrypto_validator::{validate, validate_async},
wasm::wasipki_trust_handler::{verify_trust, verify_trust_async},
};
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use crate::{
wasm::webcrypto_validator::validate_async, wasm::webpki_trust_handler::verify_trust_async,
wasm::webcrypto_validator::{validate, validate_async},
wasm::webpki_trust_handler::{verify_trust, verify_trust_async},
};

pub(crate) const RSA_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .1);
Expand Down Expand Up @@ -862,16 +868,16 @@ fn check_trust(
// is the certificate trusted

let verify_result: Result<bool> = if _sync {
#[cfg(not(feature = "openssl"))]
#[cfg(any(feature = "openssl", target_arch = "wasm32"))]
{
Err(Error::NotImplemented(
"no trust handler for this feature".to_string(),
))
verify_trust(th, chain_der, cert_der, signing_time_epoc)
}

#[cfg(feature = "openssl")]
#[cfg(all(not(feature = "openssl"), not(target_arch = "wasm32")))]
{
verify_trust(th, chain_der, cert_der, signing_time_epoc)
Err(Error::NotImplemented(
"no trust handler for this feature".to_string(),
))
}
} else {
#[cfg(target_arch = "wasm32")]
Expand Down Expand Up @@ -1178,7 +1184,6 @@ pub(crate) fn get_signing_info(
/// data: data that was used to create the cose_bytes, these must match
/// addition_data: additional optional data that may have been used during signing
/// returns - Ok on success
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn verify_cose(
cose_bytes: &[u8],
data: &[u8],
Expand All @@ -1187,7 +1192,7 @@ pub(crate) fn verify_cose(
th: &dyn TrustHandlerConfig,
validation_log: &mut impl StatusTracker,
) -> Result<ValidationInfo> {
let sign1 = get_cose_sign1(cose_bytes, data, validation_log)?;
let mut sign1 = get_cose_sign1(cose_bytes, data, validation_log)?;

let alg = match get_signing_alg(&sign1) {
Ok(a) => a,
Expand All @@ -1206,8 +1211,6 @@ pub(crate) fn verify_cose(
}
};

let validator = get_validator(alg);

// build result structure
let mut result = ValidationInfo::default();

Expand Down Expand Up @@ -1285,11 +1288,50 @@ pub(crate) fn verify_cose(

// Check the signature, which needs to have the same `additional_data` provided, by
// providing a closure that can do the verify operation.
sign1.verify_signature(additional_data, |sig, verify_data| -> Result<()> {
#[cfg(not(target_arch = "wasm32"))]
{
let validator = get_validator(alg);

sign1.verify_signature(additional_data, |sig, verify_data| -> Result<()> {
if let Ok(CertInfo {
subject,
serial_number,
}) = validate_with_cert(validator, sig, verify_data, der_bytes)
{
result.issuer_org = Some(subject);
result.cert_serial_number = Some(serial_number);
result.validated = true;
result.alg = Some(alg);

result.date = tst_info_res.map(|t| gt_to_datetime(t.gen_time)).ok();

// return cert chain
result.cert_chain = dump_cert_chain(&certs)?;

result.revocation_status = Some(true);
}
// Note: not adding validation_log entry here since caller will supply claim specific info to log
Ok(())
})?;
}
#[cfg(target_arch = "wasm32")]
{
sign1.payload = Some(data.to_vec()); // restore payload

let p_header = sign1.protected.clone();

let tbs = sig_structure_data(
coset::SignatureContext::CoseSign1,
p_header,
None,
additional_data,
sign1.payload.as_ref().unwrap_or(&vec![]),
); // get "to be signed" bytes

if let Ok(CertInfo {
subject,
serial_number,
}) = validate_with_cert(validator, sig, verify_data, der_bytes)
}) = validate_with_cert(alg, &sign1.signature, &tbs, der_bytes)
{
result.issuer_org = Some(subject);
result.cert_serial_number = Some(serial_number);
Expand All @@ -1299,29 +1341,13 @@ pub(crate) fn verify_cose(
result.date = tst_info_res.map(|t| gt_to_datetime(t.gen_time)).ok();

// return cert chain
result.cert_chain = dump_cert_chain(&certs)?;

result.revocation_status = Some(true);
result.cert_chain = dump_cert_chain(&get_sign_certs(&sign1)?)?;
}
// Note: not adding validation_log entry here since caller will supply claim specific info to log
Ok(())
})?;
}

Ok(result)
}

#[cfg(target_arch = "wasm32")]
pub(crate) fn verify_cose(
_cose_bytes: &[u8],
_data: &[u8],
_additional_data: &[u8],
_cert_check: bool,
_th: &dyn TrustHandlerConfig,
_validation_log: &mut impl StatusTracker,
) -> Result<ValidationInfo> {
Err(Error::CoseVerifier)
}

#[cfg(not(target_arch = "wasm32"))]
fn validate_with_cert(
validator: Box<dyn CoseValidator>,
Expand All @@ -1346,7 +1372,8 @@ fn validate_with_cert(
}

#[cfg(target_arch = "wasm32")]
async fn validate_with_cert_async(
#[async_generic]
fn validate_with_cert(
signing_alg: SigningAlg,
sig: &[u8],
data: &[u8],
Expand All @@ -1357,13 +1384,24 @@ async fn validate_with_cert_async(
let pk = signcert.public_key();
let pk_der = pk.raw;

if validate_async(signing_alg, sig, data, pk_der).await? {
Ok(CertInfo {
subject: extract_subject_from_cert(&signcert).unwrap_or_default(),
serial_number: extract_serial_from_cert(&signcert),
})
if _sync {
if validate(signing_alg, sig, data, pk_der)? {
Ok(CertInfo {
subject: extract_subject_from_cert(&signcert).unwrap_or_default(),
serial_number: extract_serial_from_cert(&signcert),
})
} else {
Err(Error::CoseSignature)
}
} else {
Err(Error::CoseSignature)
if validate_async(signing_alg, sig, data, pk_der).await? {
Ok(CertInfo {
subject: extract_subject_from_cert(&signcert).unwrap_or_default(),
serial_number: extract_serial_from_cert(&signcert),
})
} else {
Err(Error::CoseSignature)
}
}
}

Expand Down
Loading