Skip to content

Commit

Permalink
Add support for issuing id_alias credentials (#2044)
Browse files Browse the repository at this point in the history
* Add support for issuing id_alias credentials.

* 🤖 cargo-fmt auto-update

* Update candid interface.

* 🤖 npm run generate auto-update

* Address review comments.

* +=clippy

* Fix test success criteria.

* Address review feedback

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
przydatek and github-actions[bot] authored Nov 16, 2023
1 parent 49f0d0b commit a4d6399
Show file tree
Hide file tree
Showing 15 changed files with 1,314 additions and 5 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions src/canister_tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ serde_bytes = "0.11"
sha2 = "0.10"

internet_identity_interface = { path = "../internet_identity_interface" }
vc_util = { path = "../vc_util"}
identity_jose = { git = "https://github.com/frederikrothenberger/identity.rs.git", branch = "frederik/wasm-test", default-features = false, features = ["iccs"]}

# All IC deps
candid = "0.9"
Expand Down
3 changes: 3 additions & 0 deletions src/canister_tests/src/api/internet_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use internet_identity_interface::internet_identity::types;
/// The experimental v2 API
pub mod api_v2;

// API of verifiable credentials MVP.
pub mod vc_mvp;

/** The functions here are derived (manually) from Internet Identity's Candid file */

/// A fake "health check" method that just checks the canister is alive a well.
Expand Down
38 changes: 38 additions & 0 deletions src/canister_tests/src/api/internet_identity/vc_mvp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use candid::Principal;
use ic_cdk::api::management_canister::main::CanisterId;
use ic_test_state_machine_client::{call_candid_as, query_candid_as, CallError, StateMachine};
use internet_identity_interface::internet_identity::types::vc_mvp::{
GetIdAliasRequest, GetIdAliasResponse, PrepareIdAliasRequest, PrepareIdAliasResponse,
};

pub fn prepare_id_alias(
env: &StateMachine,
canister_id: CanisterId,
sender: Principal,
prepare_id_alias_req: PrepareIdAliasRequest,
) -> Result<Option<PrepareIdAliasResponse>, CallError> {
call_candid_as(
env,
canister_id,
sender,
"prepare_id_alias",
(prepare_id_alias_req,),
)
.map(|(x,)| x)
}

pub fn get_id_alias(
env: &StateMachine,
canister_id: CanisterId,
sender: Principal,
get_id_alias_req: GetIdAliasRequest,
) -> Result<Option<GetIdAliasResponse>, CallError> {
query_candid_as(
env,
canister_id,
sender,
"get_id_alias",
(get_id_alias_req,),
)
.map(|(x,)| x)
}
28 changes: 28 additions & 0 deletions src/canister_tests/src/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ use flate2::{Compression, GzBuilder};
use ic_cdk::api::management_canister::main::CanisterId;
use ic_representation_independent_hash::Value;
use ic_test_state_machine_client::{CallError, ErrorCode, StateMachine};
use identity_jose::jws::Decoder;
use internet_identity_interface::archive::types::*;
use internet_identity_interface::http_gateway::{HeaderField, HttpRequest};
use internet_identity_interface::internet_identity::types::vc_mvp::SignedIdAlias;
use internet_identity_interface::internet_identity::types::*;
use lazy_static::lazy_static;
use regex::Regex;
Expand Down Expand Up @@ -553,6 +555,32 @@ pub fn verify_delegation(
.expect("delegation signature invalid");
}

pub fn verify_id_alias_credential_via_env(
env: &StateMachine,
canister_sig_pk_der: CanisterSigPublicKeyDer,
signed_id_alias: &SignedIdAlias,
root_key: &[u8],
) {
const DOMAIN_SEPARATOR: &[u8] = b"iccs_verifiable_credential";

let decoder: Decoder = Decoder::new();
let jws = decoder
.decode_compact_serialization(signed_id_alias.credential_jws.as_bytes(), None)
.expect("Failure decoding JWS credential");
let sig = jws.decoded_signature();
let mut msg: Vec<u8> = Vec::from([(DOMAIN_SEPARATOR.len() as u8)]);
msg.extend_from_slice(DOMAIN_SEPARATOR);
msg.extend_from_slice(jws.signing_input());

env.verify_canister_signature(
msg.to_vec(),
sig.to_vec(),
canister_sig_pk_der.into_vec(),
root_key.to_vec(),
)
.expect("id_alias signature invalid");
}

pub fn deploy_archive_via_ii(env: &StateMachine, ii_canister: CanisterId) -> CanisterId {
match api::internet_identity::deploy_archive(env, ii_canister, &ARCHIVE_WASM) {
Ok(DeployArchiveResult::Success(archive_principal)) => archive_principal,
Expand Down
45 changes: 45 additions & 0 deletions src/frontend/generated/internet_identity_idl.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,27 @@ export const idlFactory = ({ IDL }) => {
'no_such_delegation' : IDL.Null,
'signed_delegation' : SignedDelegation,
});
const GetIdAliasRequest = IDL.Record({
'rp_id_alias_jwt' : IDL.Text,
'issuer' : FrontendHostname,
'issuer_id_alias_jwt' : IDL.Text,
'relying_party' : FrontendHostname,
'identity_number' : IdentityNumber,
});
const SignedIdAlias = IDL.Record({
'credential_jws' : IDL.Text,
'id_alias' : IDL.Principal,
'id_dapp' : IDL.Principal,
});
const IdAliasCredentials = IDL.Record({
'rp_id_alias_credential' : SignedIdAlias,
'issuer_id_alias_credential' : SignedIdAlias,
});
const GetIdAliasResponse = IDL.Variant({
'ok' : IdAliasCredentials,
'authentication_failed' : IDL.Text,
'no_such_credentials' : IDL.Text,
});
const HeaderField = IDL.Tuple(IDL.Text, IDL.Text);
const HttpRequest = IDL.Record({
'url' : IDL.Text,
Expand Down Expand Up @@ -195,6 +216,20 @@ export const idlFactory = ({ IDL }) => {
const IdentityInfoResponse = IDL.Variant({ 'ok' : IdentityInfo });
const IdentityMetadataReplaceResponse = IDL.Variant({ 'ok' : IDL.Null });
const UserKey = PublicKey;
const PrepareIdAliasRequest = IDL.Record({
'issuer' : FrontendHostname,
'relying_party' : FrontendHostname,
'identity_number' : IdentityNumber,
});
const PreparedIdAlias = IDL.Record({
'rp_id_alias_jwt' : IDL.Text,
'issuer_id_alias_jwt' : IDL.Text,
'canister_sig_pk_der' : PublicKey,
});
const PrepareIdAliasResponse = IDL.Variant({
'ok' : PreparedIdAlias,
'authentication_failed' : IDL.Text,
});
const ChallengeResult = IDL.Record({
'key' : ChallengeKey,
'chars' : IDL.Text,
Expand Down Expand Up @@ -257,6 +292,11 @@ export const idlFactory = ({ IDL }) => {
[GetDelegationResponse],
['query'],
),
'get_id_alias' : IDL.Func(
[GetIdAliasRequest],
[IDL.Opt(GetIdAliasResponse)],
['query'],
),
'get_principal' : IDL.Func(
[UserNumber, FrontendHostname],
[IDL.Principal],
Expand All @@ -281,6 +321,11 @@ export const idlFactory = ({ IDL }) => {
[UserKey, Timestamp],
[],
),
'prepare_id_alias' : IDL.Func(
[PrepareIdAliasRequest],
[IDL.Opt(PrepareIdAliasResponse)],
[],
),
'register' : IDL.Func(
[DeviceData, ChallengeResult, IDL.Opt(IDL.Principal)],
[RegisterResponse],
Expand Down
36 changes: 36 additions & 0 deletions src/frontend/generated/internet_identity_types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ export interface DeviceWithUsage {
export type FrontendHostname = string;
export type GetDelegationResponse = { 'no_such_delegation' : null } |
{ 'signed_delegation' : SignedDelegation };
export interface GetIdAliasRequest {
'rp_id_alias_jwt' : string,
'issuer' : FrontendHostname,
'issuer_id_alias_jwt' : string,
'relying_party' : FrontendHostname,
'identity_number' : IdentityNumber,
}
export type GetIdAliasResponse = { 'ok' : IdAliasCredentials } |
{ 'authentication_failed' : string } |
{ 'no_such_credentials' : string };
export type HeaderField = [string, string];
export interface HttpRequest {
'url' : string,
Expand All @@ -111,6 +121,10 @@ export interface HttpResponse {
'streaming_strategy' : [] | [StreamingStrategy],
'status_code' : number,
}
export interface IdAliasCredentials {
'rp_id_alias_credential' : SignedIdAlias,
'issuer_id_alias_credential' : SignedIdAlias,
}
export interface IdentityAnchorInfo {
'devices' : Array<DeviceWithUsage>,
'device_registration' : [] | [DeviceRegistrationInfo],
Expand Down Expand Up @@ -153,6 +167,18 @@ export type MetadataMap = Array<
{ 'bytes' : Uint8Array | number[] },
]
>;
export interface PrepareIdAliasRequest {
'issuer' : FrontendHostname,
'relying_party' : FrontendHostname,
'identity_number' : IdentityNumber,
}
export type PrepareIdAliasResponse = { 'ok' : PreparedIdAlias } |
{ 'authentication_failed' : string };
export interface PreparedIdAlias {
'rp_id_alias_jwt' : string,
'issuer_id_alias_jwt' : string,
'canister_sig_pk_der' : PublicKey,
}
export type PublicKey = Uint8Array | number[];
export interface PublicKeyAuthn { 'pubkey' : PublicKey }
export type Purpose = { 'authentication' : null } |
Expand All @@ -169,6 +195,11 @@ export interface SignedDelegation {
'signature' : Uint8Array | number[],
'delegation' : Delegation,
}
export interface SignedIdAlias {
'credential_jws' : string,
'id_alias' : Principal,
'id_dapp' : Principal,
}
export interface StreamingCallbackHttpResponse {
'token' : [] | [Token],
'body' : Uint8Array | number[],
Expand Down Expand Up @@ -220,6 +251,7 @@ export interface _SERVICE {
[UserNumber, FrontendHostname, SessionKey, Timestamp],
GetDelegationResponse
>,
'get_id_alias' : ActorMethod<[GetIdAliasRequest], [] | [GetIdAliasResponse]>,
'get_principal' : ActorMethod<[UserNumber, FrontendHostname], Principal>,
'http_request' : ActorMethod<[HttpRequest], HttpResponse>,
'http_request_update' : ActorMethod<[HttpRequest], HttpResponse>,
Expand All @@ -234,6 +266,10 @@ export interface _SERVICE {
[UserNumber, FrontendHostname, SessionKey, [] | [bigint]],
[UserKey, Timestamp]
>,
'prepare_id_alias' : ActorMethod<
[PrepareIdAliasRequest],
[] | [PrepareIdAliasResponse]
>,
'register' : ActorMethod<
[DeviceData, ChallengeResult, [] | [Principal]],
RegisterResponse
Expand Down
11 changes: 9 additions & 2 deletions src/internet_identity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ version = "0.1.0"
edition = "2021"

[dependencies]

canister_sig_util = { path = "../canister_sig_util" }
internet_identity_interface = { path = "../internet_identity_interface" }

hex = "0.4"
Expand All @@ -14,6 +12,7 @@ lazy_static = "1.4"
serde = { version = "1", features = ["rc"] }
serde_bytes = "0.11"
serde_cbor = "0.11"
serde_json = { version = "1.0", default-features = false, features = ["std"] }
sha2 = "^0.10" # set bound to match ic-certified-map bound

# Captcha deps
Expand All @@ -34,6 +33,14 @@ ic-certified-map = "0.4"
ic-metrics-encoder = "1"
ic-stable-structures = "0.5"

# VC deps
canister_sig_util = { path = "../canister_sig_util" }
vc_util = { path = "../vc_util" }
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 Down
61 changes: 61 additions & 0 deletions src/internet_identity/internet_identity.did
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,62 @@ type IdentityMetadataReplaceResponse = variant {
ok;
};

type PrepareIdAliasRequest = record {
/// Origin of the issuer in the attribute sharing flow.
issuer : FrontendHostname;
/// Origin of the relying party in the attribute sharing flow.
relying_party : FrontendHostname;
/// Identity for which the IdAlias should be generated.
identity_number : IdentityNumber;
};

type PrepareIdAliasResponse = variant {
/// Credentials prepared successfully, can be retrieved via `get_id_alias`
ok : PreparedIdAlias;
/// Caller authentication failed.
authentication_failed : text;
};

/// The prepared id alias contains two (still unsigned) credentials in JWT format,
/// certifying the id alias for the issuer resp. the relying party.
type PreparedIdAlias = record {
rp_id_alias_jwt : text;
issuer_id_alias_jwt : text;
canister_sig_pk_der : PublicKey;
};

/// The request to retrieve the actual signed id alias credentials.
/// The field values should be equal to the values of corresponding
/// fields from the preceding `PrepareIdAliasRequest` and `PrepareIdAliasResponse`.
type GetIdAliasRequest = record {
rp_id_alias_jwt : text;
issuer : FrontendHostname;
issuer_id_alias_jwt : text;
relying_party : FrontendHostname;
identity_number : IdentityNumber;
};

type GetIdAliasResponse = variant {
/// The signed id alias credentials
ok : IdAliasCredentials;
/// Caller authentication failed.
authentication_failed : text;
/// The credential(s) are not available: may be expired or not prepared yet (call prepare_id_alias to prepare).
no_such_credentials : text;
};

/// The signed id alias credentials for each involved party.
type IdAliasCredentials = record {
rp_id_alias_credential : SignedIdAlias;
issuer_id_alias_credential : SignedIdAlias;
};

type SignedIdAlias = record {
credential_jws : text;
id_alias : principal;
id_dapp : principal;
};

service : (opt InternetIdentityInit) -> {
init_salt: () -> ();
create_challenge : () -> (Challenge);
Expand Down Expand Up @@ -434,4 +490,9 @@ service : (opt InternetIdentityInit) -> {
// Removes the authentication method associated with the public key from the identity.
// Requires authentication.
authn_method_remove: (IdentityNumber, PublicKey) -> (opt AuthnMethodRemoveResponse);

// Attribute Sharing MVP API
// The methods below are used to generate ID-alias credentials during attribute sharing flow.
prepare_id_alias : (PrepareIdAliasRequest) -> (opt PrepareIdAliasResponse);
get_id_alias : (GetIdAliasRequest) -> (opt GetIdAliasResponse) query;
}
Loading

0 comments on commit a4d6399

Please sign in to comment.