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

Remove II's direct dependency on identity.rs library for handling VCs #2378

Merged
merged 3 commits into from
Mar 25, 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
2 changes: 0 additions & 2 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions demos/vc_issuer/tests/issue_credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ const DUMMY_FRONTEND_HOSTNAME: &str = "https://y2aaj-miaaa-aaaad-aacxq-cai.ic0.a

/// Dummy alias JWS for testing, valid wrt DUMMY_ROOT_KEY and DUMMY_II_CANISTER_ID.
/// id dapp: nugva-s7c6v-4yszt-koycv-5b623-an7q6-ha2nz-kz6rs-hawgl-nznbe-rqe
/// id alias: vhbib-m4hm6-hpvyc-7prd2-siivo-nbd7r-67o5x-n3awh-qsmqz-wznjf-tqe
const DUMMY_ALIAS_JWS: &str ="eyJqd2siOnsia3R5Ijoib2N0IiwiYWxnIjoiSWNDcyIsImsiOiJNRHd3REFZS0t3WUJCQUdEdUVNQkFnTXNBQW9BQUFBQUFBQUFBQUVCRVNzWHp2bTEzd1BkRTVZSndvLTBCYkdBTHdCN0J2bW1LZUxramFUUTdkQSJ9LCJraWQiOiJkaWQ6aWNwOnJ3bGd0LWlpYWFhLWFhYWFhLWFhYWFhLWNhaSIsImFsZyI6IkljQ3MifQ.eyJleHAiOjE2MjAzMjk1MzAsImlzcyI6Imh0dHBzOi8vaWRlbnRpdHkuaWMwLmFwcC8iLCJuYmYiOjE2MjAzMjg2MzAsImp0aSI6Imh0dHBzOi8vaWRlbnRpdHkuaWMwLmFwcC9jcmVkZW50aWFsLzE2MjAzMjg2MzAwMDAwMDAwMDAiLCJzdWIiOiJkaWQ6aWNwOm51Z3ZhLXM3YzZ2LTR5c3p0LWtveWN2LTViNjIzLWFuN3E2LWhhMm56LWt6NnJzLWhhd2dsLW56bmJlLXJxZSIsInZjIjp7IkBjb250ZXh0IjoiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiSW50ZXJuZXRJZGVudGl0eUlkQWxpYXMiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiaGFzX2lkX2FsaWFzIjoiZGlkOmljcDp2aGJpYi1tNGhtNi1ocHZ5Yy03cHJkMi1zaWl2by1uYmQ3ci02N281eC1uM2F3aC1xc21xei13em5qZi10cWUifX19.2dn3omtjZXJ0aWZpY2F0ZVkBsdnZ96JkdHJlZYMBgwGDAYMCSGNhbmlzdGVygwGDAkoAAAAAAAAAAAEBgwGDAYMBgwJOY2VydGlmaWVkX2RhdGGCA1ggnk2d-80NLXpxOs-YszCLd4yvrGBtLEGqe6rp6khNthCCBFgg0sz_P8xdqTDewOhKJUHmWFFrS7FQHnDotBDmmGoFfWCCBFggaAMB9TDaAhXeQPY8DCCUq90vqJJDqpDAVwU-0WdA9OmCBFgghh7VsiTOqTlAiY8hcsbF1pFnG5t1x4kQ7rt2bae_6iGCBFgggcqzMKDpDQKcyRl6xrGy4SIYEtgVJgSLlHGFvHN6zuSCBFggBNxwNVuf0_gTaiM6hbpNNCcEIBfxLHoor0N1mpX-uNeCBFggICEcda6JC5WRFIbzoGGJdJINoas-EWtoCU0lysCe3OGDAYIEWCA1U_ZYHVOz3Sdkb2HIsNoLDDiBuFfG3DxH6miIwRPra4MCRHRpbWWCA0mAuK7U3YmkvhZpc2lnbmF0dXJlWDCY_kVxXw7Wk8HlA0FqOpX-3WMdI0mmxAtY9DJv8xEkfitcTOR0FcE412IftkdH48hkdHJlZYMBggRYIPKxlnFAySvK4ahA_Q0IkEopYPh8H4_IRCFRGb2i23QRgwJDc2lngwJYIFcsa4eb-HMrTnmGWNje_RfErQYi0wNCJvGDrzqazq0OgwGCBFggg7ijRBePgPVau7zffNEvAXThew-FqcBH_cB-fF7722eDAlgg3ikzXLDphmWB8YbAxZDjZfLFd6bDS-sLAPzmVj0nlvSCA0A";
/// id alias: jkk22-zqdxc-kgpez-6sv2m-5pby4-wi4t2-prmoq-gf2ih-i2qtc-v37ac-5ae
const DUMMY_ALIAS_JWS: &str ="eyJqd2siOnsia3R5Ijoib2N0IiwiYWxnIjoiSWNDcyIsImsiOiJNRHd3REFZS0t3WUJCQUdEdUVNQkFnTXNBQW9BQUFBQUFBQUFBQUVCMGd6TTVJeXFMYUhyMDhtQTRWd2J5SmRxQTFyRVFUX2xNQnVVbmN5UDVVYyJ9LCJraWQiOiJkaWQ6aWNwOnJ3bGd0LWlpYWFhLWFhYWFhLWFhYWFhLWNhaSIsImFsZyI6IkljQ3MifQ.eyJleHAiOjE2MjAzMjk1MzAsImlzcyI6Imh0dHBzOi8vaWRlbnRpdHkuaWMwLmFwcC8iLCJuYmYiOjE2MjAzMjg2MzAsImp0aSI6ImRhdGE6dGV4dC9wbGFpbjtjaGFyc2V0PVVURi04LHRpbWVzdGFtcF9uczoxNjIwMzI4NjMwMDAwMDAwMDAwLGFsaWFzX2hhc2g6YTI3YzU4NTQ0MmUwN2RkZWFkZTRjNWE0YTAzMjdkMzA4NTE5NDAzYzRlYTM3NDIxNzBhZTRkYzk1YjIyZTQ3MyIsInN1YiI6ImRpZDppY3A6bnVndmEtczdjNnYtNHlzenQta295Y3YtNWI2MjMtYW43cTYtaGEybnota3o2cnMtaGF3Z2wtbnpuYmUtcnFlIiwidmMiOnsiQGNvbnRleHQiOiJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiLCJJbnRlcm5ldElkZW50aXR5SWRBbGlhcyJdLCJjcmVkZW50aWFsU3ViamVjdCI6eyJJbnRlcm5ldElkZW50aXR5SWRBbGlhcyI6eyJoYXNJZEFsaWFzIjoiamtrMjItenFkeGMta2dwZXotNnN2Mm0tNXBieTQtd2k0dDItcHJtb3EtZ2YyaWgtaTJxdGMtdjM3YWMtNWFlIn19fX0.2dn3omtjZXJ0aWZpY2F0ZVkBsdnZ96JkdHJlZYMBgwGDAYMCSGNhbmlzdGVygwGDAkoAAAAAAAAAAAEBgwGDAYMBgwJOY2VydGlmaWVkX2RhdGGCA1ggvlJBTZDgK1_9Vb3-18dWKIfy28WTjZ1YqdjFWWAIX96CBFgg0sz_P8xdqTDewOhKJUHmWFFrS7FQHnDotBDmmGoFfWCCBFgg_KZ0TVqubo_EGWoMUPA35BYZ4B5ZRkR_zDfNIQCwa46CBFggj_ZV-7o59iVEjztzZtpNnO9YC7GjbKmg2eDtJzGz1weCBFggXAzCWvb9h4qsVs41IUJBABzjSqAZ8DIzF_ghGHpGmHGCBFggJhbsbvKYt7rjLK5SI0NDc600o-ajSYQNuOXps6qUrdiCBFggBFQwZetJeY_gx6TQohTqUOskblddajS20DA0esxWoyWDAYIEWCA1U_ZYHVOz3Sdkb2HIsNoLDDiBuFfG3DxH6miIwRPra4MCRHRpbWWCA0mAuK7U3YmkvhZpc2lnbmF0dXJlWDC5cq4UxYy7cnkcw6yv5SCh4POY9u0iHecZuxO8E9oxIqXRdHmnYVF0Fv_R-aws0EBkdHJlZYMBggRYIOGnlc_3yXPTVrEJ1p3dKX5HxkMOziUnpA1HeXiQW4O8gwJDc2lngwJYIIOQR7wl3Ws9Jb8VP4rhIb37XKLMkkZ2P7WaZ5we60WGgwGCBFgg21-OewBgqt_-0AtHHHS4yPyQK9g6JTHaGUuSIw4QYgqDAlgg5bQnHHvS3FfM_BaiSL6n19qoXkuA1KoLWk963fOUMW-CA0A";
const DUMMY_ALIAS_ID_DAPP_PRINCIPAL: &str =
"nugva-s7c6v-4yszt-koycv-5b623-an7q6-ha2nz-kz6rs-hawgl-nznbe-rqe";

Expand Down
5 changes: 1 addition & 4 deletions src/internet_identity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ ic-stable-structures.workspace = true
# VC deps
canister_sig_util.workspace = true
vc_util.workspace = true
identity_core = { git = "https://github.com/frederikrothenberger/identity.rs.git", branch = "frederik/wasm-test" }
identity_credential = { git = "https://github.com/frederikrothenberger/identity.rs.git", branch = "frederik/wasm-test", default-features = false, features = ["credential"]}
identity_jose = { git = "https://github.com/frederikrothenberger/identity.rs.git", branch = "frederik/wasm-test", default-features = false, features = ["iccs"]}


[target.'cfg(all(target_arch = "wasm32", target_vendor = "unknown", target_os = "unknown"))'.dependencies]
getrandom = { version = "0.2", features = ["custom"] }
Expand All @@ -53,6 +49,7 @@ canister_tests.workspace = true
hex-literal = "0.4"
regex.workspace = true
ic-response-verification.workspace = true
identity_jose = { git = "https://github.com/frederikrothenberger/identity.rs.git", branch = "frederik/wasm-test", default-features = false, features = ["iccs"]}
frederikrothenberger marked this conversation as resolved.
Show resolved Hide resolved


[features]
Expand Down
109 changes: 34 additions & 75 deletions src/internet_identity/src/vc_mvp.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
use crate::delegation::check_frontend_length;
use crate::{delegation, random_salt, state, update_root_hash, MINUTE_NS};
use std::collections::HashMap;

use candid::Principal;
use canister_sig_util::CanisterSigPublicKey;
use ic_cdk::api::time;
use ic_certification::Hash;
use identity_core::common::{Timestamp, Url};
use identity_core::convert::FromJson;
use identity_credential::credential::{Credential, CredentialBuilder, Subject};
use identity_jose::jws::{CompactJwsEncoder, Decoder};
use internet_identity_interface::internet_identity::types::vc_mvp::{
GetIdAliasError, IdAliasCredentials, PreparedIdAlias, SignedIdAlias,
};
use internet_identity_interface::internet_identity::types::{FrontendHostname, IdentityNumber};
use serde_bytes::ByteBuf;
use serde_json::json;
use sha2::{Digest, Sha256};
use vc_util::issuer_api::{ArgumentValue, CredentialSpec};
use vc_util::{
did_for_principal, get_canister_sig_pk_raw, vc_signing_input, vc_signing_input_hash,
AliasTuple, II_CREDENTIAL_URL_PREFIX, II_ISSUER_URL,
build_credential_jwt, canister_sig_pk_from_vc_signing_input, did_for_principal,
vc_signing_input, vc_signing_input_hash, vc_signing_input_to_jws, AliasTuple, CredentialParams,
II_CREDENTIAL_URL_PREFIX, II_ISSUER_URL,
};

// The expiration of id_alias verifiable credentials.
Expand Down Expand Up @@ -50,10 +48,10 @@ pub async fn prepare_id_alias(
id_dapp: delegation::get_principal(identity_number, dapps.issuer.clone()),
};

let rp_signing_input = vc_signing_input(&prepare_id_alias_jwt(&rp_tuple), &canister_sig_pk)
let rp_signing_input = vc_signing_input(&id_alias_credential_jwt(&rp_tuple), &canister_sig_pk)
.expect("failed getting signing_input");
let issuer_signing_input =
vc_signing_input(&prepare_id_alias_jwt(&issuer_tuple), &canister_sig_pk)
vc_signing_input(&id_alias_credential_jwt(&issuer_tuple), &canister_sig_pk)
.expect("failed getting signing_input");
state::signature_map_mut(|sigs| {
sigs.add_signature(seed.as_ref(), vc_signing_input_hash(&rp_signing_input));
Expand All @@ -77,7 +75,7 @@ pub fn get_id_alias(
check_frontend_length(&dapps.issuer);

state::assets_and_signatures(|cert_assets, sigs| {
let canister_sig_pk = canister_sig_pk_from_signing_input(rp_id_alias_jwt)
let canister_sig_pk = canister_sig_pk_from_vc_signing_input(rp_id_alias_jwt.as_bytes())
.map_err(GetIdAliasError::NoSuchCredentials)?;
let seed = canister_sig_pk.seed.as_slice();

Expand All @@ -91,8 +89,8 @@ pub fn get_id_alias(
.map_err(|err| {
GetIdAliasError::NoSuchCredentials(format!("rp_sig not found: {}", err))
})?;
let rp_jws =
add_sig_to_signing_input(rp_id_alias_jwt, &rp_sig).expect("failed constructing rp JWS");
let rp_jws = vc_signing_input_to_jws(rp_id_alias_jwt.as_bytes(), &rp_sig)
.expect("failed constructing rp JWS");

let issuer_id_alias_msg_hash = vc_signing_input_hash(issuer_id_alias_jwt.as_bytes());
let issuer_sig = sigs
Expand All @@ -104,7 +102,7 @@ pub fn get_id_alias(
.map_err(|err| {
GetIdAliasError::NoSuchCredentials(format!("issuer_sig not found: {}", err))
})?;
let issuer_jws = add_sig_to_signing_input(issuer_id_alias_jwt, &issuer_sig)
let issuer_jws = vc_signing_input_to_jws(issuer_id_alias_jwt.as_bytes(), &issuer_sig)
.expect("failed constructing issuer JWS");

Ok(IdAliasCredentials {
Expand All @@ -122,62 +120,34 @@ pub fn get_id_alias(
})
}

/// Parses the canister signature public key from the given signing_input.
fn canister_sig_pk_from_signing_input(signing_input: &str) -> Result<CanisterSigPublicKey, String> {
let decoder = Decoder::new();
let bytes_with_separators = [signing_input.as_bytes(), &[b'.']].concat();
let parsed_signing_input = decoder
.decode_compact_serialization(&bytes_with_separators, None)
.map_err(|e| format!("internal: failed parsing signing_input: {:?}", e))?;
let header = parsed_signing_input
.protected_header()
.expect("internal: failed getting protected header");
let canister_sig_pk_raw = get_canister_sig_pk_raw(header)
.map_err(|e| format!("internal: failed getting canister_sig_pk_raw: {:?}", e))?;
CanisterSigPublicKey::try_from_raw(&canister_sig_pk_raw)
.map_err(|e| format!("internal: failed parsing canister_sig_pk: {}", e))
}

/// Constructs and returns a JWS (a signed JWT) from the given components.
fn add_sig_to_signing_input(signing_input: &str, sig: &[u8]) -> Result<String, String> {
let decoder = Decoder::new();
let bytes_with_separators = [signing_input.as_bytes(), &[b'.']].concat();
let parsed_signing_input = decoder
.decode_compact_serialization(&bytes_with_separators, None)
.unwrap();
let header = parsed_signing_input
.protected_header()
.expect("internal: failed getting protected header");

let encoder: CompactJwsEncoder = CompactJwsEncoder::new(parsed_signing_input.claims(), header)
.map_err(|e| format!("internal: failed creating JWS encoder: {:?}", e))?;
Ok(encoder.into_jws(sig))
fn id_alias_credential_jwt(alias_tuple: &AliasTuple) -> String {
let expiration_timestamp_s: u32 =
((time() + ID_ALIAS_VC_EXPIRATION_PERIOD_NS) / 1_000_000_000) as u32;
let params = CredentialParams {
spec: id_alias_credential_spec(alias_tuple.id_alias),
subject_id: did_for_principal(alias_tuple.id_dapp),
credential_id_url: prepare_credential_id_new(alias_tuple),
issuer_url: II_ISSUER_URL.to_string(),
expiration_timestamp_s,
};
build_credential_jwt(params)
}

fn id_alias_credential(alias_tuple: &AliasTuple) -> Credential {
let subject: Subject = Subject::from_json_value(json!({
"id": did_for_principal(alias_tuple.id_dapp),
"has_id_alias": did_for_principal(alias_tuple.id_alias),
}))
.expect("internal: failed building id_alias subject");
let exp_timestamp_sec =
Timestamp::from_unix(((time() + ID_ALIAS_VC_EXPIRATION_PERIOD_NS) / 1_000_000_000) as i64)
.expect("internal: failed computing expiration timestamp");

let credential: Credential = CredentialBuilder::default()
.id(prepare_credential_id(alias_tuple))
.issuer(Url::parse(II_ISSUER_URL).expect("internal: bad issuer url"))
.type_("InternetIdentityIdAlias")
.subject(subject)
.expiration_date(exp_timestamp_sec)
.build()
.expect("internal: failed building id_alias credential");
credential
fn id_alias_credential_spec(id_alias: Principal) -> CredentialSpec {
let mut args = HashMap::new();
args.insert(
"hasIdAlias".to_string(),
ArgumentValue::String(id_alias.to_text()),
);
CredentialSpec {
credential_type: "InternetIdentityIdAlias".to_string(),
arguments: Some(args),
}
}

// Prepares a unique id for the given alias_tuple.
// The returned URL has the format: "data:text/plain;charset=UTF-8,timestamp_sec:...,alias_hash:..."
fn prepare_credential_id(alias_tuple: &AliasTuple) -> Url {
fn prepare_credential_id_new(alias_tuple: &AliasTuple) -> String {
let timestamp = format!("timestamp_ns:{}", time());
let mut hasher = Sha256::new();
hasher.update("id_dapp=");
Expand All @@ -186,16 +156,5 @@ fn prepare_credential_id(alias_tuple: &AliasTuple) -> Url {
hasher.update(alias_tuple.id_alias.to_text());
let hash: Hash = hasher.finalize().into();
let alias_hash = format!("alias_hash:{}", hex::encode(hash));
Url::parse(format!(
"{}{},{}",
II_CREDENTIAL_URL_PREFIX, timestamp, alias_hash
))
.expect("internal: bad credential id base url")
}

fn prepare_id_alias_jwt(alias_tuple: &AliasTuple) -> String {
let credential = id_alias_credential(alias_tuple);
credential
.serialize_jwt()
.expect("internal: JWT serialization failure")
format!("{}{},{}", II_CREDENTIAL_URL_PREFIX, timestamp, alias_hash)
}
Loading
Loading