Skip to content
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
6 changes: 6 additions & 0 deletions Cargo.lock

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

9 changes: 9 additions & 0 deletions packages/ic-secp256r1/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,15 @@ impl PrivateKey {
sig.to_bytes().into()
}

/// Sign a message, using a DER encoded signature
///
/// The message is hashed with SHA-256
pub fn sign_message_with_der_encoded_sig(&self, message: &[u8]) -> Vec<u8> {
use p256::ecdsa::{Signature, signature::Signer};
let sig: Signature = self.key.sign(message);
sig.to_der().to_bytes().to_vec()
}

/// Sign a message digest
pub fn sign_digest(&self, digest: &[u8]) -> Option<[u8; 64]> {
if digest.len() < 16 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ rust_library(
"//rs/canister_client/sender:__pkg__",
"//rs/crypto:__subpackages__",
"//rs/tests/crypto:__subpackages__",
"//rs/validator:__subpackages__",
"//rs/validator/http_request_test_utils:__subpackages__",
],
deps = [
Expand Down
6 changes: 6 additions & 0 deletions rs/validator/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ DEPENDENCIES = [

DEV_DEPENDENCIES = [
# Keep sorted.
"//packages/ic-secp256r1",
"//rs/crypto/internal/crypto_lib/basic_sig/der_utils",
"//rs/crypto/temp_crypto",
"//rs/crypto/test_utils/reproducible_rng",
"//rs/crypto/test_utils/root_of_trust",
Expand All @@ -24,6 +26,10 @@ DEV_DEPENDENCIES = [
"@crate_index//:base64",
"@crate_index//:mockall",
"@crate_index//:rand",
"@crate_index//:serde",
"@crate_index//:serde_cbor",
"@crate_index//:serde_json",
"@crate_index//:simple_asn1",
]

rust_library(
Expand Down
6 changes: 6 additions & 0 deletions rs/validator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@ thiserror = { workspace = true }
assert_matches = { workspace = true }
base64 = { workspace = true }
hex = { workspace = true }
ic-crypto-internal-basic-sig-der-utils = { path = "../crypto/internal/crypto_lib/basic_sig/der_utils" }
ic-crypto-test-utils-reproducible-rng = { path = "../crypto/test_utils/reproducible_rng" }
ic-crypto-test-utils-root-of-trust = { path = "../crypto/test_utils/root_of_trust" }
ic-crypto-temp-crypto = { path = "../crypto/temp_crypto" }
ic-secp256r1 = { path = "../../packages/ic-secp256r1" }
serde = { workspace = true }
serde_cbor = { workspace = true }
serde_json = { workspace = true }
simple_asn1 = { workspace = true }
ic-test-utilities-types = { path = "../test_utilities/types" }
mockall = { workspace = true }
rand = { workspace = true }
105 changes: 105 additions & 0 deletions rs/validator/src/webauthn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,111 @@ mod tests {
);
}

#[test]
fn should_verify_newly_generated_webauthn_ecdsa_signature() {
let verifier = temp_crypto_component_with_fake_registry(node_test_id(0));

let (sig_bytes, pk_bytes, msg) = {
let sk = ic_secp256r1::PrivateKey::generate();

let msg = "webauthn signature generation test";

let sig_bytes = {
use serde::Serialize;

#[derive(Debug, Serialize)]
struct ClientData {
r#type: String,
challenge: String,
origin: String,
}

let client_data = ClientData {
r#type: "brunettes".to_string(),
challenge: base64::encode(msg),
origin: "https://localhost/".to_string(),
};

let authenticator_data = Blob(b"auth_data".to_vec());
let client_data_json = serde_json::to_vec(&client_data).unwrap();

let signed_message = {
let mut sm = vec![];
sm.extend_from_slice(&authenticator_data.0);
sm.extend_from_slice(&ic_crypto_sha2::Sha256::hash(&client_data_json));
sm
};
let signature = Blob(sk.sign_message_with_der_encoded_sig(&signed_message));
let sig = WebAuthnSignature::new(
authenticator_data,
Blob(client_data_json),
signature,
);
serde_cbor::to_vec(&sig).unwrap()
};

let pk_cose = {
let mut map = std::collections::BTreeMap::new();

use serde_cbor::Value;

/*
See RFC 8152 ("CBOR Object Signing and Encryption (COSE)"), sections 8.1
and 13.1 for these constants
*/
const COSE_PARAM_KTY: serde_cbor::Value = serde_cbor::Value::Integer(1);
const COSE_PARAM_KTY_EC2: serde_cbor::Value = serde_cbor::Value::Integer(2);

const COSE_PARAM_ALG: serde_cbor::Value = serde_cbor::Value::Integer(3);
const COSE_PARAM_ALG_ES256: serde_cbor::Value = serde_cbor::Value::Integer(-7);

const COSE_PARAM_EC2_CRV: serde_cbor::Value = serde_cbor::Value::Integer(-1);
const COSE_PARAM_EC2_CRV_P256: serde_cbor::Value =
serde_cbor::Value::Integer(1);

const COSE_PARAM_EC2_X: serde_cbor::Value = serde_cbor::Value::Integer(-2);
const COSE_PARAM_EC2_Y: serde_cbor::Value = serde_cbor::Value::Integer(-3);

let sec1 = sk.public_key().serialize_sec1(false);
let x = &sec1[1..33];
let y = &sec1[33..];

map.insert(COSE_PARAM_KTY, COSE_PARAM_KTY_EC2);
map.insert(COSE_PARAM_EC2_CRV, COSE_PARAM_EC2_CRV_P256);
map.insert(COSE_PARAM_ALG, COSE_PARAM_ALG_ES256);
map.insert(COSE_PARAM_EC2_X, Value::Bytes(x.to_vec()));
map.insert(COSE_PARAM_EC2_Y, Value::Bytes(y.to_vec()));

serde_cbor::to_vec(&Value::Map(map)).expect("cbor encoding failed")
};

let pk_der = {
use ic_crypto_internal_basic_sig_der_utils::subject_public_key_info_der;
use simple_asn1::oid;
// OID 1.3.6.1.4.1.56387.1.1
// See https://internetcomputer.org/docs/current/references/ic-interface-spec#signatures
let webauthn_key_oid = oid!(1, 3, 6, 1, 4, 1, 56387, 1, 1);
subject_public_key_info_der(webauthn_key_oid, &pk_cose).unwrap()
};

(sig_bytes, pk_der, msg.as_bytes().to_vec())
};

let sig = WebAuthnSignature::try_from(sig_bytes.as_slice()).unwrap();
let pk = user_public_key_from_bytes(&pk_bytes).unwrap().0;
assert_eq!(pk.algorithm_id, AlgorithmId::EcdsaP256);

let message = SignableMock {
domain: vec![],
signed_bytes_without_domain: msg.to_vec(),
};

assert_eq!(
validate_webauthn_sig(&verifier, &sig, &message, &pk),
Ok(())
);
}

#[test]
fn should_return_error_on_valid_signature_but_wrong_message() {
let verifier = temp_crypto_component_with_fake_registry(node_test_id(0));
Expand Down
Loading