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

Add support for issuer custom/alternative origin #2356

Merged
merged 2 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 1 addition & 3 deletions .github/workflows/canister-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -468,13 +468,11 @@ jobs:

- name: Deploy canisters
run: |
# NOTE: dfx install will run the postinstall scripts from dfx.json
dfx canister install internet_identity --wasm internet_identity_test.wasm.gz
dfx canister install test_app --wasm demos/test-app/test_app.wasm
dfx canister install issuer --wasm demos/vc_issuer/vc_demo_issuer.wasm.gz

- name: Provision issuer canister
run: ./demos/vc_issuer/provision

# Check the return code of npm ci
- name: Check on npm ci
# make sure we don't wait too long if for some unlikely reason the npm status file doesn't get created
Expand Down
10 changes: 10 additions & 0 deletions demos/vc_issuer/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ const App = () => {
/>
</label>
</section>
<section>
<label>
Custom principal:
<input
data-role="custom-principal"
type="text"
onChange={(evt) => setPrincipal(evt.target.value)}
/>
</label>
</section>
<section>
<button
data-action="authenticate"
Expand Down
6 changes: 3 additions & 3 deletions demos/vc_issuer/provision
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ do
shift;
;;
--issuer-derivation-origin)
DERIVATION_ORIGIN="${2:?missing value for '--issuer-derivation-origin'}"
ISSUER_DERIVATION_ORIGIN="${2:?missing value for '--issuer-derivation-origin'}"
shift; # shift past --issuer-canister & value
shift;
;;
--issuer-frontend-hostname)
FRONTEND_HOSTNAME="${2:?missing value for '--issuer-frontend-hostname'}"
ISSUER_FRONTEND_HOSTNAME="${2:?missing value for '--issuer-frontend-hostname'}"
shift; # shift past --issuer-canister & value
shift;
;;
Expand All @@ -84,7 +84,7 @@ done
DFX_NETWORK="${DFX_NETWORK:-local}"
II_CANISTER_ID="${II_CANISTER_ID:-$(dfx canister id internet_identity)}"
ISSUER_CANISTER="${ISSUER_CANISTER:-issuer}"
ISSUER_CANISTER_ID=$(dfx canister id $ISSUER_CANISTER)
ISSUER_CANISTER_ID=$(dfx canister id "$ISSUER_CANISTER")
DEFAULT_DERIVATION_ORIGIN="https://$ISSUER_CANISTER_ID.icp0.io"
ISSUER_DERIVATION_ORIGIN="${ISSUER_DERIVATION_ORIGIN:-$DEFAULT_DERIVATION_ORIGIN}"
ISSUER_FRONTEND_HOSTNAME="${ISSUER_FRONTEND_HOSTNAME:-$ISSUER_DERIVATION_ORIGIN}"
Expand Down
17 changes: 9 additions & 8 deletions demos/vc_issuer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,15 +307,16 @@ async fn derivation_origin(
fn get_derivation_origin(hostname: &str) -> Result<DerivationOriginData, DerivationOriginError> {
CONFIG.with_borrow(|config| {
let config = config.get();
if hostname == config.frontend_hostname {
Ok(DerivationOriginData {
origin: config.derivation_origin.clone(),
})
} else {
Err(DerivationOriginError::UnsupportedOrigin(
hostname.to_string(),
))

// 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(),
})
})
}

Expand Down
17 changes: 0 additions & 17 deletions demos/vc_issuer/tests/issue_credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,23 +371,6 @@ fn should_return_derivation_origin_with_custom_init() {
assert_eq!(response.origin, custom_init.derivation_origin);
}

#[test]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please annotate with #[ignore] rather than deleting.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why that's better? If the test needs to be resurrected it can always be found in the git history

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more convenient to re-enable and is nagging in the test output to do something about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is nagging in the test output to do something about it.

That's unfortunate! I think the cleanest way would be to track this somewhere, otherwise (by ignoring the test) it might never get done though it will always be a slight annoyance when running the tests. And in case we never re-add the test, having it ignored is just wasted real estate in the file! Nothing as permanent as temporary solutions.

fn should_fail_derivation_origin_if_unsupported_origin() {
let env = env();
let canister_id = install_canister(&env, VC_ISSUER_WASM.clone());
let req = DerivationOriginRequest {
frontend_hostname: "https://wrong.fe.host".to_string(),
};
let response =
api::derivation_origin(&env, canister_id, principal_1(), &req).expect("API call failed");
assert_eq!(
response,
Err(DerivationOriginError::UnsupportedOrigin(
req.frontend_hostname
))
);
}

fn employee_credential_spec() -> CredentialSpec {
let mut args = HashMap::new();
args.insert(
Expand Down
2 changes: 1 addition & 1 deletion dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"candid": "demos/vc_issuer/vc_demo_issuer.did",
"wasm": "demos/vc_issuer/vc_demo_issuer.wasm.gz",
"build": "demos/vc_issuer/build.sh",
"post_install": "demos/vc_issuer/provision",
"post_install": "bash -c 'demos/vc_issuer/provision --issuer-derivation-origin https://$(dfx canister id test_app).icp0.io'",
"dependencies": [ "internet_identity" ]
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"aborted_internal_error": "There was an unexpected issue while verifying your credentials. Please retry, and if the problem persist contact support.",
"aborted_auth_failed_ii": "There was an error authenticating you with your Internet Identity. Aliases were not created.",
"aborted_auth_failed_issuer": "There was an error authenticating you with the issuer. Make sure you can authenticate with the issuer.",
"aborted_invalid_derivation_origin_issuer": "The derivation origin given for the issuer is not valid.",
"aborted_derivation_origin_issuer_error": "The issuer produced an error when queried for its derivation origin.",
"aborted_issuer_api_error": "There was an error requesting credentials with the issuer. The issuer returned an error.",
"aborted_bad_principal_rp": "The principal provided by the relying party does not match the data Internet Identity has.",
"aborted_no_canister_id": "Internet Identity could not find the canister for the issuer.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export type AbortReason =
| "internal_error"
| "auth_failed_ii"
| "auth_failed_issuer"
| "derivation_origin_issuer_error"
| "invalid_derivation_origin_issuer"
| "issuer_api_error"
| "bad_principal_rp"
| "no_canister_id"
Expand Down
66 changes: 61 additions & 5 deletions src/frontend/src/flows/verifiableCredentials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,32 @@ const verifyCredentials = async ({
}
}

const validationResult = await withLoader(() =>
// Verify that principals may be issued to RP using the specified
// derivation origin
const validRpDerivationOrigin = await withLoader(() =>
validateDerivationOrigin(rpOrigin_, rpDerivationOrigin)
);
if (validationResult.result === "invalid") {
if (validRpDerivationOrigin.result === "invalid") {
return abortedCredentials({ reason: "bad_derivation_origin_rp" });
}
validationResult.result satisfies "valid";
validRpDerivationOrigin.result satisfies "valid";
const rpOrigin = rpDerivationOrigin ?? rpOrigin_;

const vcIssuer = new VcIssuer(issuerCanisterId);

const issuerDerivationOriginResult = await getValidatedIssuerDerivationOrigin(
{
vcIssuer,
issuerOrigin,
}
);

if (issuerDerivationOriginResult.kind === "error") {
return abortedCredentials({ reason: issuerDerivationOriginResult.err });
}

const issuerDerivationOrigin = issuerDerivationOriginResult.origin;

// XXX: We don't check that the language matches the user's language. We need
// to figure what to do UX-wise first.
const consentInfo = await vcIssuer.getConsentMessage({ credentialSpec });
Expand Down Expand Up @@ -164,7 +180,7 @@ const verifyCredentials = async ({

const pAliasPending = getAliasCredentials({
rpOrigin,
issuerOrigin,
issuerOrigin: issuerDerivationOrigin /* Use the actual derivation origin */,
authenticatedConnection,
});

Expand All @@ -180,7 +196,7 @@ const verifyCredentials = async ({

const issuedCredential = await issueCredential({
vcIssuer,
issuerOrigin,
issuerOrigin: issuerDerivationOrigin,
issuerAliasCredential: pAlias.issuerAliasCredential,
credentialSpec,
authenticatedConnection,
Expand Down Expand Up @@ -302,6 +318,46 @@ const getAliasCredentials = async ({
return { ok: { rpAliasCredential, issuerAliasCredential } };
};

// Looks up and verify the derivation origin to be used by the issuer
const getValidatedIssuerDerivationOrigin = async ({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand correctly that there are no unit tests because everything is tested with e2e tests?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there's no particular reason. How would you test this with a unit test?

vcIssuer,
issuerOrigin,
}: {
vcIssuer: VcIssuer;
issuerOrigin: string;
}): Promise<
| {
kind: "error";
err:
| "derivation_origin_issuer_error"
| "invalid_derivation_origin_issuer";
}
| { kind: "ok"; origin: string }
> => {
const result = await vcIssuer.getDerivationOrigin({ origin: issuerOrigin });

if (result.kind === "error") {
return { kind: "error", err: "derivation_origin_issuer_error" };
}

result.kind satisfies "origin";

const validDerivationOrigin = await validateDerivationOrigin(
issuerOrigin,
result.origin
);
if (validDerivationOrigin.result === "invalid") {
console.error(
"Invalid derivation origin for issuer",
JSON.stringify(validDerivationOrigin)
);
return { kind: "error", err: "invalid_derivation_origin_issuer" };
}
validDerivationOrigin.result satisfies "valid";

return { kind: "ok", origin: result.origin };
};

// Contact the issuer to issue the credentials
const issueCredential = async ({
vcIssuer,
Expand Down
32 changes: 31 additions & 1 deletion src/frontend/src/flows/verifiableCredentials/vcIssuer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export class VcIssuer {
});

if ("Err" in result) {
console.error("Could not prepare credential", result.Err);
console.error(
"Could not prepare credential: " + JSON.stringify(result.Err)
);
return "error";
}

Expand Down Expand Up @@ -107,4 +109,32 @@ export class VcIssuer {

return result.Ok;
};

getDerivationOrigin = async ({
origin,
}: {
origin: string;
}): Promise<{ kind: "origin"; origin: string } | { kind: "error" }> => {
const actor = await this.createActor();

let result;
try {
result = await actor.derivation_origin({
frontend_hostname: origin,
});
} catch (e: unknown) {
console.error("Could not get derivation origin (unexpected error)", e);
return { kind: "error" };
}

if ("Err" in result) {
console.error(
"Could not get derivation origin (issuer error)",
JSON.stringify(result.Err)
);
return { kind: "error" };
}

return { kind: "origin", origin: result.Ok.origin };
};
}
3 changes: 3 additions & 0 deletions src/frontend/src/test-e2e/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export const TEST_APP_NICE_URL = "https://nice-name.com";
export const ISSUER_APP_URL = `https://${issuerAppCanisterId}.icp0.io`;
export const ISSUER_APP_URL_LEGACY = `https://${issuerAppCanisterId}.ic0.app`;

// Value needs to match how the canister was provisioned
export const ISSUER_CUSTOM_ORIGIN_NICE_URL = `https://nice-issuer-custom-orig.com`;

export const II_URL =
process.env.II_URL ?? "https://identity.internetcomputer.org";

Expand Down
Loading
Loading