Skip to content

Commit

Permalink
Full demo WIP - Integrate DFINITY's VC library (#11)
Browse files Browse the repository at this point in the history
* fix tests

* start the vc-flow through relying party; CORS error

* canister deploy error

* fix the right principal for the civic admin

* use dfinity's VC library

* fix build issue

* vc-flow fails with Invalid derivation origin error

* cleanup
  • Loading branch information
happyhackerbird authored May 6, 2024
1 parent 5c63f10 commit 084fb0f
Show file tree
Hide file tree
Showing 32 changed files with 11,787 additions and 304 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
- name: Install canisters and start local ICP replica
run: |
mkdir src/civic_canister_backend/dist
mkdir src/relying_canister_frontend/dist
dfx start --clean --background
dfx canister create --all
- name: Build canisters
Expand Down
11 changes: 10 additions & 1 deletion dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,17 @@
],
"type": "assets",
"__6": "NOTE: the 'frontend' signals dfx that it is useful to print out the front-end URL of this canister when deploying. It is not strictly required."
}
},

"relying_canister_frontend": {
"frontend": {
"entrypoint": "src/relying_canister_frontend/src/index.html"
},
"source": [
"src/relying_canister_frontend/dist"
],
"type": "assets"
}

},
"defaults": {
Expand Down
36 changes: 30 additions & 6 deletions src/civic_canister_backend/civic_canister_backend.did
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,29 @@ type CredentialSpec = record {
arguments : opt vec record { text; ArgumentValue };
credential_type : text;
};
type DerivationOriginData = record { origin : text };
type DerivationOriginError = variant {
Internal : text;
UnsupportedOrigin : text;
};
type DerivationOriginRequest = record { frontend_hostname : text };
type GetCredentialRequest = record {
signed_id_alias : SignedIdAlias;
prepared_context : opt blob;
credential_spec : CredentialSpec;
};
type HttpRequest = record {
url : text;
method : text;
body : blob;
headers : vec record { text; text };
certificate_version : opt nat16;
};
type HttpResponse = record {
body : blob;
headers : vec record { text; text };
status_code : nat16;
};
type IssueCredentialError = variant {
Internal : text;
SignatureNotFound : text;
Expand All @@ -37,12 +55,16 @@ type PrepareCredentialRequest = record {
credential_spec : CredentialSpec;
};
type PreparedCredentialData = record { prepared_context : opt blob };
type Result = variant { Ok : vec StoredCredential; Err : CredentialError };
type Result_1 = variant {
type Result = variant {
Ok : DerivationOriginData;
Err : DerivationOriginError;
};
type Result_1 = variant { Ok : vec StoredCredential; Err : CredentialError };
type Result_2 = variant {
Ok : IssuedCredentialData;
Err : IssueCredentialError;
};
type Result_2 = variant {
type Result_3 = variant {
Ok : PreparedCredentialData;
Err : IssueCredentialError;
};
Expand All @@ -57,7 +79,9 @@ type StoredCredential = record {
service : (opt IssuerInit) -> {
add_credentials : (principal, vec StoredCredential) -> (text);
configure : (IssuerInit) -> ();
get_all_credentials : (principal) -> (Result) query;
get_credential : (GetCredentialRequest) -> (Result_1) query;
prepare_credential : (PrepareCredentialRequest) -> (Result_2);
derivation_origin : (DerivationOriginRequest) -> (Result);
get_all_credentials : (principal) -> (Result_1) query;
get_credential : (GetCredentialRequest) -> (Result_2) query;
http_request : (HttpRequest) -> (HttpResponse) query;
prepare_credential : (PrepareCredentialRequest) -> (Result_3);
}
Binary file modified src/civic_canister_backend/civic_canister_backend.wasm.gz
Binary file not shown.
102 changes: 93 additions & 9 deletions src/civic_canister_backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use canister_sig_util::signature_map::{SignatureMap, LABEL_SIG};

use ic_cdk::api::{caller, set_certified_data, time};
use ic_cdk_macros::{init, query, update};
use ic_certification::{fork_hash, labeled_hash, Hash};
use ic_certification::{fork_hash, labeled_hash, Hash, pruned};

use std::collections::{HashSet,HashMap};
use ic_stable_structures::storable::Bound;
Expand All @@ -25,13 +25,15 @@ use std::cell::RefCell;
use asset_util::{collect_assets, CertifiedAssets};
use vc_util::issuer_api::{
CredentialSpec, GetCredentialRequest, IssueCredentialError, IssuedCredentialData,
PrepareCredentialRequest, PreparedCredentialData, SignedIdAlias,
PrepareCredentialRequest, PreparedCredentialData, SignedIdAlias, DerivationOriginData, DerivationOriginError,
DerivationOriginRequest
};
use vc_util::{ did_for_principal, get_verified_id_alias_from_jws, vc_jwt_to_jws,
vc_signing_input, vc_signing_input_hash, AliasTuple,
};
use ic_cdk::api;
use lazy_static::lazy_static;
use ic_cdk_macros::post_upgrade;
use identity_credential::credential::{CredentialBuilder};
use identity_core::common::{Timestamp, Url};

Expand All @@ -45,6 +47,8 @@ type ConfigCell = StableCell<IssuerConfig, Memory>;
const MINUTE_NS: u64 = 60 * 1_000_000_000;
const PROD_II_CANISTER_ID: &str = "rdmx6-jaaaa-aaaaa-aaadq-cai";
const VC_EXPIRATION_PERIOD_NS: u64 = 15 * MINUTE_NS;
// Authorized Civic Principal - get this from the frontend
const AUTHORIZED_PRINCIPAL: &str = "tglqb-kbqlj-to66e-3w5sg-kkz32-c6ffi-nsnta-vj2gf-vdcc5-5rzjk-jae";

lazy_static! {
// Seed and public key used for signing the credentials.
Expand All @@ -59,7 +63,7 @@ pub enum SupportedCredentialType {
impl fmt::Display for SupportedCredentialType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SupportedCredentialType::VerifiedAdult => write!(f, "VerifiedEmployee"),
SupportedCredentialType::VerifiedAdult => write!(f, "VerifiedAdult"),
}
}
}
Expand Down Expand Up @@ -156,10 +160,10 @@ fn init(init_arg: Option<IssuerInit>) {
init_assets();
}

// #[post_upgrade]
// fn post_upgrade(init_arg: Option<IssuerInit>) {
// init(init_arg);
// }
#[post_upgrade]
fn post_upgrade(init_arg: Option<IssuerInit>) {
init(init_arg);
}

#[update]
#[candid_method]
Expand Down Expand Up @@ -411,15 +415,45 @@ fn verify_credential_spec(spec: &CredentialSpec) -> Result<SupportedCredentialTy
}
}

#[update]
#[candid_method]
async fn derivation_origin(
req: DerivationOriginRequest,
) -> Result<DerivationOriginData, DerivationOriginError> {
get_derivation_origin(&req.frontend_hostname)
}

fn get_derivation_origin(hostname: &str) -> Result<DerivationOriginData, DerivationOriginError> {
CONFIG.with_borrow(|config| {
let config = config.get();

// We don't currently rely on the value provided, so if it doesn't match
// we just print a warning
if hostname != config.frontend_hostname {
println!("*** achtung! bad frontend hostname {}", hostname,);
}

Ok(DerivationOriginData {
origin: config.derivation_origin.clone(),
})
})
}


#[update]
#[candid_method]
fn add_credentials(principal: Principal, new_credentials: Vec<StoredCredential>) -> String {
// Check if the caller is the authorized principal
if caller().to_text() != AUTHORIZED_PRINCIPAL {
return "Unauthorized: You do not have permission to add credentials.".to_string();
}

CREDENTIALS.with_borrow_mut(|credentials| {
let entry = credentials.entry(principal).or_insert_with(Vec::new);
entry.extend(new_credentials);
entry.extend(new_credentials.clone());
});
format!("Added credentials")
let credential_info = format!("Added credentials: \n{:?}", new_credentials);
credential_info
}


Expand All @@ -436,6 +470,38 @@ fn get_all_credentials(principal: Principal) -> Result<Vec<StoredCredential>, Cr
}
}

// To solve the CORS error during the vc-flow
#[query]
#[candid_method(query)]
pub fn http_request(req: HttpRequest) -> HttpResponse {
let parts: Vec<&str> = req.url.split('?').collect();
let path = parts[0];
let sigs_root_hash =
SIGNATURES.with_borrow(|sigs| pruned(labeled_hash(LABEL_SIG, &sigs.root_hash())));
let maybe_asset = ASSETS.with_borrow(|assets| {
assets.get_certified_asset(path, req.certificate_version, Some(sigs_root_hash))
});

let mut headers = static_headers();
match maybe_asset {
Some(asset) => {
headers.extend(asset.headers);
HttpResponse {
status_code: 200,
body: ByteBuf::from(asset.content),
headers,
}
}
None => HttpResponse {
status_code: 404,
headers,
body: ByteBuf::from(format!("Asset {} not found.", path)),
},
}
}



fn hash_bytes(value: impl AsRef<[u8]>) -> Hash {
let mut hasher = Sha256::new();
hasher.update(value.as_ref());
Expand Down Expand Up @@ -489,6 +555,24 @@ pub fn build_credential_jwt(params: CredentialParams) -> String {
credential.serialize_jwt().unwrap()
}



#[derive(Clone, Debug, CandidType, Deserialize)]
pub struct HttpRequest {
pub method: String,
pub url: String,
pub headers: Vec<HeaderField>,
pub body: ByteBuf,
pub certificate_version: Option<u16>,
}

#[derive(Clone, Debug, CandidType, Deserialize)]
pub struct HttpResponse {
pub status_code: u16,
pub headers: Vec<HeaderField>,
pub body: ByteBuf,
}

ic_cdk::export_candid!();


Expand Down
26 changes: 13 additions & 13 deletions src/civic_canister_backend/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use identity_core::common::Url;
use std::iter::repeat;

#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub enum ClaimValue {
pub(crate) enum ClaimValue {
Boolean(bool),
Date(String),
Text(String),
Expand All @@ -16,8 +16,8 @@ pub enum ClaimValue {
}

#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub struct Claim {
pub claims:HashMap<String, ClaimValue>,
pub(crate) struct Claim {
pub(crate) claims:HashMap<String, ClaimValue>,
}

impl From<ClaimValue> for Value {
Expand All @@ -36,7 +36,7 @@ impl From<ClaimValue> for Value {


impl Claim {
pub fn into(self) -> Subject {
pub(crate) fn into(self) -> Subject {
let btree_map: BTreeMap<String, Value> = self.claims.into_iter()
.map(|(k, v)| (k, v.into()))
.collect();
Expand All @@ -45,15 +45,15 @@ impl Claim {
}

#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
pub struct StoredCredential {
pub id: String,
pub type_: Vec<String>,
pub context: Vec<String>,
pub issuer: String,
pub claim: Vec<Claim>,
pub(crate) struct StoredCredential {
pub(crate) id: String,
pub(crate) type_: Vec<String>,
pub(crate) context: Vec<String>,
pub(crate) issuer: String,
pub(crate) claim: Vec<Claim>,
}
#[derive(CandidType)]
pub enum CredentialError {
pub(crate) enum CredentialError {
NoCredentialsFound(String),
}

Expand All @@ -65,7 +65,7 @@ pub enum CredentialError {
/// otherData
/// }
pub fn build_claims_into_credentialSubjects(claims: Vec<Claim>, subject: String) -> Vec<Subject> {
pub(crate) fn build_claims_into_credentialSubjects(claims: Vec<Claim>, subject: String) -> Vec<Subject> {
claims.into_iter().zip(repeat(subject)).map(|(c, id )|{
let mut sub = c.into();
sub.id = Url::parse(id).ok();
Expand All @@ -74,7 +74,7 @@ pub fn build_claims_into_credentialSubjects(claims: Vec<Claim>, subject: String)
}


pub fn add_context(mut credential: CredentialBuilder, context: Vec<String>) -> CredentialBuilder {
pub(crate) fn add_context(mut credential: CredentialBuilder, context: Vec<String>) -> CredentialBuilder {
for c in context {
credential = credential.context(Url::parse(c).unwrap());
}
Expand Down
Loading

0 comments on commit 084fb0f

Please sign in to comment.