Skip to content

chore(crypto): CRP-2838 use published vetKeys utils in vetKD examples #1157

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

Merged
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
16 changes: 8 additions & 8 deletions motoko/encrypted-notes-dapp-vetkd/Cargo.lock

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

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ crate-type = ["cdylib"]

[dependencies]
candid = "0.10"
ic-cdk = "0.18.2-alpha.1"
ic-cdk = "0.18.3"
ic-stable-structures = "0.6.4"
lazy_static = "1.4.0"
serde_json = "1.0.108"
Expand Down
4 changes: 1 addition & 3 deletions motoko/vetkd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ It includes:

* An example frontend (`src/app_frontend_js`) that uses the backend from Javascript in the browser.

The frontend uses the [ic-vetkd-utils](https://github.com/dfinity/ic/tree/master/packages/ic-vetkd-utils) to create a transport key pair that is used to obtain a verifiably encrypted key from the system API, to decrypt this key, and to derive a symmetric key to be used for AES encryption/decryption.

Because the `ic-vetkd-utils` are not yet published as NPM package at [npmjs.com](https://npmjs.com), a respective package file (`ic-vetkd-utils-0.1.0.tgz`) is included in this repository.
The frontend uses [@dfinity/vetkeys](https://www.npmjs.com/package/@dfinity/vetkeys) ([docs](https://dfinity.github.io/vetkeys/modules/_dfinity_vetkeys.html)) to create a transport key pair that is used to obtain a verifiably encrypted key from the system API, to decrypt this key, and to derive a symmetric key to be used for AES encryption/decryption.

## Prerequisites
- [x] Install the [IC SDK](https://internetcomputer.org/docs/current/developer-docs/getting-started/install).
Expand Down
Binary file removed motoko/vetkd/ic-vetkd-utils-0.1.0.tgz
Binary file not shown.
25 changes: 19 additions & 6 deletions motoko/vetkd/package-lock.json

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

2 changes: 1 addition & 1 deletion motoko/vetkd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@dfinity/auth-client": "^2.1.3",
"@dfinity/candid": "^2.1.3",
"@dfinity/principal": "^2.1.3",
"ic-vetkd-utils": "file:ic-vetkd-utils-0.1.0.tgz"
"@dfinity/vetkeys": "^0.1.0"
},
"devDependencies": {
"assert": "2.0.0",
Expand Down
2 changes: 1 addition & 1 deletion motoko/vetkd/src/app_frontend_js/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ button[type="submit"] {
text-align: center;
}

#get_symmetric_key_result {
#get_vetkey_result {
text-align: center;
}

Expand Down
6 changes: 3 additions & 3 deletions motoko/vetkd/src/app_frontend_js/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ <h1 id="vetkddemo">vetKD Demo</h1>
</form>
<hr />
<h2 id="symmetric_encryption_demo">Encryption with symmetric (AES-GCM-256) key</h1>
<form action="#" id="get_symmetric_key_form">
<button type="submit">Fetch symmetric key for local usage</button>
<form action="#" id="get_vetkey_form">
<button type="submit">Fetch vetKey for local usage</button>
</form>
<section id="get_symmetric_key_result"></section>
<section id="get_vetkey_result"></section>
<form action="#" id="encrypt_form">
<label for="plaintext">Plaintext:</label>
<input type="text" id="plaintext"><br><br>
Expand Down
118 changes: 48 additions & 70 deletions motoko/vetkd/src/app_frontend_js/src/index.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { createActor, app_backend } from "../../declarations/app_backend";
import * as vetkd from "ic-vetkd-utils";
import { TransportSecretKey, EncryptedVetKey, DerivedPublicKey, IbeCiphertext, IbeIdentity, IbeSeed } from "@dfinity/vetkeys";
import { AuthClient } from "@dfinity/auth-client"
import { HttpAgent, Actor } from "@dfinity/agent";
import { Principal } from "@dfinity/principal";

let fetched_symmetric_key = null;
let fetched_derived_key_material = null;
let app_backend_actor = app_backend;
let app_backend_principal = await Actor.agentOf(app_backend_actor).getPrincipal();
document.getElementById("principal").innerHTML = annotated_principal(app_backend_principal);

document.getElementById("get_symmetric_key_form").addEventListener("submit", async (e) => {
document.getElementById("get_vetkey_form").addEventListener("submit", async (e) => {
e.preventDefault();
const button = e.target.querySelector("button");
button.setAttribute("disabled", true);
const result = document.getElementById("get_symmetric_key_result");
const result = document.getElementById("get_vetkey_result");

result.innerText = "Fetching symmetric key...";
const aes_256_key = await get_aes_256_gcm_key();
result.innerText = "Done. AES-GCM-256 key available for local usage.";
result.innerText = "Fetching vetKey...";
const derived_key_material = await get_derived_key_material();
result.innerText = "Done. vetKey available for local usage.";

button.removeAttribute("disabled");

fetched_symmetric_key = aes_256_key;
fetched_derived_key_material = derived_key_material;
update_plaintext_button_state();
update_ciphertext_button_state();

Expand All @@ -35,9 +35,11 @@ document.getElementById("encrypt_form").addEventListener("submit", async (e) =>
const result = document.getElementById("encrypt_result");

result.innerText = "Encrypting...";
const ciphertext = await aes_gcm_encrypt(document.getElementById("plaintext").value, fetched_symmetric_key);
const message = document.getElementById("plaintext").value;
const message_encoded = new TextEncoder().encode(message);
const ciphertext_hex = hex_encode(await fetched_derived_key_material.encryptMessage(message_encoded, "vetkd-demo"));

result.innerText = "ciphertext: " + ciphertext;
result.innerText = "ciphertext: " + ciphertext_hex;

button.removeAttribute("disabled");
return false;
Expand All @@ -51,8 +53,10 @@ document.getElementById("decrypt_form").addEventListener("submit", async (e) =>

result.innerText = "Decrypting...";
try {
const plaintext = await aes_gcm_decrypt(document.getElementById("ciphertext").value, fetched_symmetric_key);
result.innerText = "plaintext: " + plaintext;
const ciphertext_hex = document.getElementById("ciphertext").value;
const plaintext_bytes = await fetched_derived_key_material.decryptMessage(hex_decode(ciphertext_hex), "vetkd-demo");
const plaintext_string = new TextDecoder().decode(plaintext_bytes);
result.innerText = "plaintext: " + plaintext_string;
} catch (e) {
result.innerText = "Error: " + e;
}
Expand All @@ -71,7 +75,7 @@ document.getElementById("ciphertext").addEventListener("keyup", async (e) => {

function update_plaintext_button_state() {
const submit_plaintext_button = document.getElementById("submit_plaintext");
if (document.getElementById("plaintext").value === "" || fetched_symmetric_key === null) {
if (document.getElementById("plaintext").value === "" || fetched_derived_key_material === null) {
submit_plaintext_button.setAttribute("disabled", true);
} else {
submit_plaintext_button.removeAttribute("disabled");
Expand All @@ -80,54 +84,25 @@ function update_plaintext_button_state() {

function update_ciphertext_button_state() {
const submit_ciphertext_button = document.getElementById("submit_ciphertext");
if (document.getElementById("ciphertext").value === "" || fetched_symmetric_key === null) {
if (document.getElementById("ciphertext").value === "" || fetched_derived_key_material === null) {
submit_ciphertext_button.setAttribute("disabled", true);
} else {
submit_ciphertext_button.removeAttribute("disabled");
}
}

async function get_aes_256_gcm_key() {
const seed = window.crypto.getRandomValues(new Uint8Array(32));
const tsk = new vetkd.TransportSecretKey(seed);
const ek_bytes_hex = await app_backend_actor.encrypted_symmetric_key_for_caller(tsk.public_key());
async function get_derived_key_material() {
const tsk = TransportSecretKey.random();

const ek_bytes_hex = await app_backend_actor.encrypted_symmetric_key_for_caller(tsk.publicKeyBytes());
const encryptedVetKey = new EncryptedVetKey(hex_decode(ek_bytes_hex));

const pk_bytes_hex = await app_backend_actor.symmetric_key_verification_key();
return tsk.decrypt_and_hash(
hex_decode(ek_bytes_hex),
hex_decode(pk_bytes_hex),
app_backend_principal.toUint8Array(),
32,
new TextEncoder().encode("aes-256-gcm")
);
}
const dpk = DerivedPublicKey.deserialize(hex_decode(pk_bytes_hex));

async function aes_gcm_encrypt(message, rawKey) {
const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 96-bits; unique per message
const aes_key = await window.crypto.subtle.importKey("raw", rawKey, "AES-GCM", false, ["encrypt"]);
const message_encoded = new TextEncoder().encode(message);
const ciphertext_buffer = await window.crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv },
aes_key,
message_encoded
);
const ciphertext = new Uint8Array(ciphertext_buffer);
var iv_and_ciphertext = new Uint8Array(iv.length + ciphertext.length);
iv_and_ciphertext.set(iv, 0);
iv_and_ciphertext.set(ciphertext, iv.length);
return hex_encode(iv_and_ciphertext);
}
const vetKey = encryptedVetKey.decryptAndVerify(tsk, dpk, app_backend_principal.toUint8Array());

async function aes_gcm_decrypt(ciphertext_hex, rawKey) {
const iv_and_ciphertext = hex_decode(ciphertext_hex);
const iv = iv_and_ciphertext.subarray(0, 12); // 96-bits; unique per message
const ciphertext = iv_and_ciphertext.subarray(12);
const aes_key = await window.crypto.subtle.importKey("raw", rawKey, "AES-GCM", false, ["decrypt"]);
let decrypted = await window.crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv },
aes_key,
ciphertext
);
return new TextDecoder().decode(decrypted);
return await vetKey.asDerivedKeyMaterial();
}

document.getElementById("ibe_encrypt_form").addEventListener("submit", async (e) => {
Expand Down Expand Up @@ -197,39 +172,42 @@ function update_ibe_decrypt_button_state() {
async function ibe_encrypt(message) {
document.getElementById("ibe_encrypt_result").innerText = "Fetching IBE encryption key..."
const pk_bytes_hex = await app_backend_actor.ibe_encryption_key();
const dpk = DerivedPublicKey.deserialize(hex_decode(pk_bytes_hex));

document.getElementById("ibe_encrypt_result").innerText = "Preparing IBE-encryption..."
const message_encoded = new TextEncoder().encode(message);
const seed = window.crypto.getRandomValues(new Uint8Array(32));
let ibe_principal = Principal.fromText(document.getElementById("ibe_principal").value);

document.getElementById("ibe_encrypt_result").innerText = "IBE-encrypting for principal" + ibe_principal.toText() + "...";
const ibe_ciphertext = vetkd.IBECiphertext.encrypt(
hex_decode(pk_bytes_hex),
ibe_principal.toUint8Array(),
const ibe_ciphertext = IbeCiphertext.encrypt(
dpk,
IbeIdentity.fromPrincipal(ibe_principal),
message_encoded,
seed
IbeSeed.random(),
);
return hex_encode(ibe_ciphertext.serialize());
}

async function ibe_decrypt(ibe_ciphertext_hex) {
document.getElementById("ibe_decrypt_result").innerText = "Preparing IBE-decryption..."
const tsk_seed = window.crypto.getRandomValues(new Uint8Array(32));
const tsk = new vetkd.TransportSecretKey(tsk_seed);
document.getElementById("ibe_decrypt_result").innerText = "Fetching IBE decryption key..."
const ek_bytes_hex = await app_backend_actor.encrypted_ibe_decryption_key_for_caller(tsk.public_key());
document.getElementById("ibe_decrypt_result").innerText = "Fetching IBE enryption key (needed for verification)..."
const pk_bytes_hex = await app_backend_actor.ibe_encryption_key();
const dpk = DerivedPublicKey.deserialize(hex_decode(pk_bytes_hex));

const k_bytes = tsk.decrypt(
hex_decode(ek_bytes_hex),
hex_decode(pk_bytes_hex),
document.getElementById("ibe_decrypt_result").innerText = "Fetching IBE decryption key..."
const tsk = TransportSecretKey.random();
const ek_bytes_hex = await app_backend_actor.encrypted_ibe_decryption_key_for_caller(tsk.publicKeyBytes());
const encryptedVetKey = new EncryptedVetKey(hex_decode(ek_bytes_hex));

document.getElementById("ibe_decrypt_result").innerText = "Decrypting and verifying IBE decryption key..."
const vetKey = encryptedVetKey.decryptAndVerify(
tsk,
dpk,
app_backend_principal.toUint8Array()
);

const ibe_ciphertext = vetkd.IBECiphertext.deserialize(hex_decode(ibe_ciphertext_hex));
const ibe_plaintext = ibe_ciphertext.decrypt(k_bytes);
document.getElementById("ibe_decrypt_result").innerText = "Using IBE decryption key to decrypt ciphertext..."
const ibe_ciphertext = IbeCiphertext.deserialize(hex_decode(ibe_ciphertext_hex));
const ibe_plaintext = ibe_ciphertext.decrypt(vetKey);
return new TextDecoder().decode(ibe_plaintext);
}

Expand All @@ -244,7 +222,7 @@ document.getElementById("login").onclick = async (e) => {
// Safari detection rules are according to: https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent#browser_name_and_version
let isSafari = /^(?!.*chrome\/\d+)(?!.*chromium\/\d+).*safari\/\d+/i.test(navigator.userAgent);
let identityProvider = isSafari ?
`http://localhost:4943/?canisterId=${process.env.CANISTER_ID_INTERNET_IDENTITY}` :
`http://127.0.0.1:4943/?canisterId=${process.env.CANISTER_ID_INTERNET_IDENTITY}` :
`http://${process.env.CANISTER_ID_INTERNET_IDENTITY}.localhost:4943/`;

let authClient = await AuthClient.create();
Expand All @@ -266,8 +244,8 @@ document.getElementById("login").onclick = async (e) => {

document.getElementById("principal").innerHTML = annotated_principal(app_backend_principal);

fetched_symmetric_key = null;
document.getElementById("get_symmetric_key_result").innerText = "";
fetched_derived_key_material = null;
document.getElementById("get_vetkey_result").innerText = "";
update_plaintext_button_state();
update_ciphertext_button_state();

Expand Down
Loading
Loading