diff --git a/.cargo/config b/.cargo/config index bf0daa6d..17f4109d 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,3 +1,11 @@ +[env] +RUST_BACKTRACE = "1" +WASMTIME_BACKTRACE_DETAILS = "1" + +[build] +target = "wasm32-wasi" + [target.wasm32-wasi] rustflags = ["--cfg", "tokio_unstable"] -runner = ["enarx", "run", "--wasmcfgfile", "Enarx.toml"] +# runner = ["./enarx", "run", "--wasmcfgfile", "Enarx.toml"] +runner = ["/home/rjzak/bin/wasmtime-wasi-crypto", "--wasi-modules", "experimental-wasi-crypto", "--"] diff --git a/Cargo.lock b/Cargo.lock index f2ab19a2..a8561586 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -640,6 +640,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "once_cell" version = "1.15.0" @@ -1096,6 +1117,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", + "wasi-crypto-guest", "x509-cert", "zeroize", ] @@ -1353,6 +1375,14 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi-crypto-guest" +version = "0.1.2" +source = "git+https://github.com/WebAssembly/wasi-crypto?branch=main#b9d6bad8c0e4a77403035ca5e4c62c86c869c80a" +dependencies = [ + "num_enum", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index ad64614d..ca27b5ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,9 @@ confargs = { version = "^0.1.3", default-features = false } [target.'cfg(not(target_os = "wasi"))'.dependencies] tokio = { version = "^1.21.2", features = ["rt-multi-thread", "macros"], default-features = false } +[target.'cfg(target_os = "wasi")'.dependencies] +wasi-crypto-guest = { git = "https://github.com/WebAssembly/wasi-crypto", branch = "main", optional = true } + [dev-dependencies] tower = { version = "^0.4.11", features = ["util"], default-features = false } axum = { version = "^0.5.17", default-features = false } @@ -50,6 +53,10 @@ memoffset = { version = "0.7.1", default-features = false } rstest = { version = "0.15", default-features = false } testaso = { version = "0.1", default-features = false } +[features] +default = [] +wasi-crypto = ["dep:wasi-crypto-guest"] + [profile.release] incremental = false codegen-units = 1 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 38499fd0..51f9ae32 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "stable" +channel = "nightly" components = [ "rustfmt", "clippy" ] profile = "minimal" targets = [ diff --git a/src/crypto/hashing.rs b/src/crypto/hashing.rs new file mode 100644 index 00000000..3f6d3a6f --- /dev/null +++ b/src/crypto/hashing.rs @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2022 Profian Inc. +// SPDX-License-Identifier: AGPL-3.0-only + +use anyhow::Result; + +#[cfg(all(target_os = "wasi", feature = "wasi-crypto"))] +use anyhow::anyhow; +#[cfg(all(target_os = "wasi", feature = "wasi-crypto"))] +use wasi_crypto_guest::prelude::Hash; + +#[cfg(any(not(target_os = "wasi"), not(feature = "wasi-crypto")))] +use sha2::{Digest, Sha256, Sha384}; + +#[inline] +pub fn sha256(data: impl AsRef<[u8]>) -> Result> { + #[cfg(all(target_os = "wasi", feature = "wasi-crypto"))] + return Ok(Hash::hash("SHA-256", data, 32, None).or_else(|_| Err(anyhow!("hash error")))?); + + #[cfg(any(not(target_os = "wasi"), not(feature = "wasi-crypto")))] + Ok(Sha256::digest(data).as_slice().to_vec()) +} + +#[inline] +pub fn sha384(data: impl AsRef<[u8]>) -> Result> { + #[cfg(all(target_os = "wasi", feature = "wasi-crypto"))] + return Ok(Hash::hash("SHA-384", data, 48, None).or_else(|_| Err(anyhow!("hash error")))?); + + #[cfg(any(not(target_os = "wasi"), not(feature = "wasi-crypto")))] + Ok(Sha384::digest(data).as_slice().to_vec()) +} + +#[cfg(all(target_os = "wasi", feature = "wasi-crypto"))] +#[cfg(test)] +mod wasi_crypto { + use crate::{sha256, sha384}; + use sha2::Digest; + + const DATA: &[u8] = b"SOME_TEST_DATA"; + + #[test] + fn test_sha256() { + let hash = sha256(DATA).unwrap(); + assert_eq!(hash, sha2::Sha256::digest(DATA).as_slice()); + } + + #[test] + fn test_sha384() { + let hash = sha384(DATA).unwrap(); + assert_eq!(hash, sha2::Sha384::digest(DATA).as_slice()); + } +} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index a1a98822..598de0dd 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -3,10 +3,12 @@ mod cert; mod certreq; +mod hashing; mod pki; mod spki; pub use self::cert::TbsCertificateExt; pub use self::certreq::{CertReqExt, CertReqInfoExt}; +pub use self::hashing::{sha256, sha384}; pub use self::pki::PrivateKeyInfoExt; pub use self::spki::SubjectPublicKeyInfoExt; diff --git a/src/crypto/spki.rs b/src/crypto/spki.rs index a197eeb2..b5a03f39 100644 --- a/src/crypto/spki.rs +++ b/src/crypto/spki.rs @@ -4,7 +4,10 @@ use anyhow::{anyhow, Result}; use const_oid::ObjectIdentifier; use der::{asn1::AnyRef, Sequence}; + use rsa::pkcs1::DecodeRsaPublicKey; +#[cfg(all(target_os = "wasi", feature = "wasi-crypto"))] +use spki::EncodePublicKey; use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; use const_oid::db::rfc5912::{ @@ -12,6 +15,11 @@ use const_oid::db::rfc5912::{ ID_SHA_256 as SHA256, ID_SHA_384 as SHA384, ID_SHA_512 as SHA512, RSA_ENCRYPTION as RSA, SECP_256_R_1 as P256, SECP_384_R_1 as P384, }; +#[cfg(all(target_os = "wasi", feature = "wasi-crypto"))] +use der::pem::LineEnding; + +#[cfg(all(target_os = "wasi", feature = "wasi-crypto"))] +use wasi_crypto_guest::signatures::{Signature, SignaturePublicKey}; const ES256: (ObjectIdentifier, Option>) = (ECDSA_WITH_SHA_256, None); const ES384: (ObjectIdentifier, Option>) = (ECDSA_WITH_SHA_384, None); @@ -43,6 +51,121 @@ pub trait SubjectPublicKeyInfoExt { } impl<'a> SubjectPublicKeyInfoExt for SubjectPublicKeyInfo<'a> { + #[cfg(all(target_os = "wasi", feature = "wasi-crypto"))] + fn verify(&self, body: &[u8], algo: AlgorithmIdentifier<'_>, sign: &[u8]) -> Result<()> { + let algo_name = match (self.algorithm.oids()?, (algo.oid, algo.parameters)) { + ((ECPK, Some(P256)), ES256) => "ECDSA_P256_SHA256", + ((ECPK, Some(P384)), ES384) => "ECDSA_P384_SHA384", + ((RSA, None), (ID_RSASSA_PSS, Some(p))) => { + // Decompose the RSA PSS parameters. + let RsaSsaPssParams { + hash_algorithm: hash, + mask_algorithm: mask, + salt_length: salt, + trailer_field: tfld, + } = p.decode_into().unwrap(); + + // Validate the sanity of the mask algorithm. + let algo = match (mask.oid, mask.parameters) { + (ID_MGF_1, Some(p)) => { + let p = p.decode_into::>()?; + match (p.oids()?, salt, tfld) { + ((SHA256, None), 32, 1) => Ok(SHA256), + ((SHA384, None), 48, 1) => Ok(SHA384), + ((SHA512, None), 64, 1) => Ok(SHA512), + ((x, y), s, t) => { + eprint!( + "Unknown RSA hash and components: {:?}, {:?}, salt {}, tfld {}", + x, y, s, t + ); + Err(anyhow!("unsupported")) + } + } + } + (x, _) => { + eprintln!("Unknown RSA OID {:?}", x); + Err(anyhow!("unsupported")) + } + } + .map_err(|e| { + eprintln!("Some algo error {:?}", e); + anyhow!("{:?}", e) + }) + .unwrap(); + + match (hash.oids()?, algo) { + ((SHA256, None), SHA256) => "RSA_PSS_2048_SHA256", + ((SHA384, None), SHA384) => "RSA_PSS_3072_SHA384", + ((SHA512, None), SHA512) => "RSA_PSS_4096_SHA512", + _ => { + eprintln!("Error unknown hash.oids"); + bail!("unsupported") + } + } + } + _ => { + eprintln!("Unknown algorithm, should not be here!"); + bail!("unsupported") + } + }; + + /*eprintln!("spki.rs verify() algo: {}", algo_name); + if algo_name == "RSA_PSS_3072_SHA384" { + eprintln!("RSA Pub Key: "); + for x in self.subject_public_key { + eprint!("{:02x}", x); + } + eprintln!(); + }*/ + + let public_key = match algo_name { + "RSA_PSS_2048_SHA256" | "RSA_PSS_3072_SHA384" | "RSA_PSS_4096_SHA512" => { + let pkey = rsa::RsaPublicKey::from_pkcs1_der(self.subject_public_key)?; + eprintln!("RSA from_pkcs8()"); + SignaturePublicKey::from_pem( + algo_name, + pkey.to_public_key_pem(LineEnding::LF).unwrap().as_bytes(), + ) + .map_err(|e| anyhow!("{:?}", e)) + .unwrap() + } + _ => SignaturePublicKey::from_raw(algo_name, self.subject_public_key) + .map_err(|e| anyhow!("{:?}", e)) + .unwrap(), + }; + + //let public_key = SignaturePublicKey::from_raw(algo_name, self.subject_public_key) + // .map_err(|e| anyhow!("{:?}", e)) + // .unwrap(); + + let signature = match algo_name { + "ECDSA_P256_SHA256" => { + let temp = p256::ecdsa::Signature::from_der(sign)?; + temp.to_vec() + } + "ECDSA_P384_SHA384" => { + let temp = p384::ecdsa::Signature::from_der(sign)?; + temp.to_vec() + } + "RSA_PSS_2048_SHA256" | "RSA_PSS_3072_SHA384" | "RSA_PSS_4096_SHA512" => { + use signature::Signature; + let s = rsa::pss::Signature::from_bytes(sign)?; + s.to_vec() + } + _ => sign.to_vec(), + }; + + let signature = Signature::from_raw(algo_name, &signature) + .map_err(|e| anyhow!("{:?}", e)) + .unwrap(); + + Ok(public_key + .signature_verify(body, &signature) + .map_err(|e| anyhow!("{:?}", e)) + .unwrap()) + } + + #[cfg(any(not(target_os = "wasi"), not(feature = "wasi-crypto")))] fn verify(&self, body: &[u8], algo: AlgorithmIdentifier<'_>, sign: &[u8]) -> Result<()> { match (self.algorithm.oids()?, (algo.oid, algo.parameters)) { ((ECPK, Some(P256)), ES256) => { diff --git a/src/ext/sgx/mod.rs b/src/ext/sgx/mod.rs index d2828dbd..4b7a3e09 100644 --- a/src/ext/sgx/mod.rs +++ b/src/ext/sgx/mod.rs @@ -13,7 +13,6 @@ use anyhow::{anyhow, Result}; use const_oid::ObjectIdentifier; use der::{Decode, Encode}; use sgx::parameters::{Attributes, MiscSelect}; -use sha2::{Digest, Sha256}; use x509::{ext::Extension, request::CertReqInfo, Certificate, TbsCertificate}; #[derive(Clone, Debug)] @@ -84,7 +83,7 @@ impl ExtVerifier for Sgx { if !dbg { // TODO: Validate that the certification request came from an SGX enclave. - let hash = Sha256::digest(&cri.public_key.to_vec()?); + let hash = sha256(&cri.public_key.to_vec()?)?; if hash.as_slice() != &rpt.reportdata[..hash.as_slice().len()] { return Err(anyhow!("sgx report data is invalid")); } diff --git a/src/ext/snp/mod.rs b/src/ext/snp/mod.rs index 92db26c0..3a346c28 100644 --- a/src/ext/snp/mod.rs +++ b/src/ext/snp/mod.rs @@ -13,7 +13,6 @@ use der::asn1::UIntRef; use der::{Decode, Encode, Sequence}; use flagset::{flags, FlagSet}; use sec1::pkcs8::AlgorithmIdentifier; -use sha2::Digest; use x509::ext::Extension; use x509::{request::CertReqInfo, Certificate}; use x509::{PkiPath, TbsCertificate}; @@ -374,7 +373,7 @@ impl ExtVerifier for Snp { if !dbg { // Validate that the certification request came from an SNP VM. - let hash = sha2::Sha384::digest(&cri.public_key.to_vec()?); + let hash = sha384(&cri.public_key.to_vec()?)?; if hash.as_slice() != &report.body.report_data[..hash.as_slice().len()] { return Err(anyhow!("snp report.report_data is invalid")); }