Skip to content
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
58 changes: 58 additions & 0 deletions rust/vetkeys/encrypted_notes_dapp_vetkd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Encrypted notes: vetKD

| Motoko backend | [![](https://icp.ninja/assets/open.svg)](http://icp.ninja/editor?g=https://github.com/dfinity/examples/tree/master/rust/vetkeys/encrypted_notes_dapp_vetkd/motoko)|
| --- | --- |
| Rust backend | [![](https://icp.ninja/assets/open.svg)](http://icp.ninja/editor?g=https://github.com/dfinity/examples/tree/master/rust/vetkeys/encrypted_notes_dapp_vetkd/rust) |

Encrypted notes is an example dapp for authoring and storing confidential information on the Internet Computer (ICP) in the form of short pieces of text. Users can create and access their notes via any number of automatically synchronized devices authenticated via Internet Identity (II). Notes are stored confidentially using vetKeys. The end-to-end encryption is performed by the dapp’s frontend.

In particular, the notes are encrypted with an AES key that is derived (directly in the browser) from a note-ID-specific vetKey obtained from the backend canister (in encrypted form, using an ephemeral transport key), which itself obtains it from the vetKD system API. This way, there is no need for any device management in the dapp, plus sharing of notes becomes possible.

The vetKey used to encrypt and decrypt a note is note-ID-specific (and not, for example, principal-specific) to enable the sharing of notes between users. The derived AES keys are stored as non-extractable CryptoKeys in an IndexedDB in the browser for efficiency so that their respective vetKey only has to be fetched from the server once. To improve the security even further, the vetKeys' derivation information could be adapted to include a (numeric) epoch that advances each time the list of users with which the note is shared is changed.

## Prerequisites

This example requires an installation of:

- [x] Install the [IC SDK](https://internetcomputer.org/docs/current/developer-docs/setup/install/index.mdx).
- [x] Install [npm](https://www.npmjs.com/package/npm).

### (Optionally) Choose a Different Master Key

This example uses `test_key_1` by default. To use a different [available master key](https://internetcomputer.org/docs/building-apps/network-features/vetkeys/api#available-master-keys), change the `"init_arg": "(\"test_key_1\")"` line in `dfx.json` to the desired key before running `dfx deploy` in the next step.

## Deploy the Canisters Locally

If you want to deploy this project locally with a Motoko backend, then run:
```bash
dfx start --background && dfx deploy
```
from the `motoko` folder.

To use the Rust backend instead of Motoko, run the same command in the rust folder.

## Example Components

### Backend

The backend consists of a canister that stores encrypted notes. It is automatically deployed with `dfx deploy`.

### Frontend

The frontend is a **Svelte** application providing a user-friendly interface for managing encrypted notes.

To run the frontend in development mode with hot reloading (after running `dfx deploy`):

```bash
npm run dev
```

## Limitations

This example dapp does not implement key rotation, which is strongly recommended in a production environment.
Key rotation involves periodically changing encryption keys and re-encrypting data to enhance security.
In a production dapp, key rotation would be useful to limit the impact of potential key compromise if a malicious party gains access to a key, or to limit access when users are added or removed from note sharing.

## Troubleshooting

If you run into issues, clearing all the application-specific IndexedDBs in the browser (which are used to store Internet Identity information and the derived non-extractable AES keys) might help fix the issue. For example in Chrome, go to Inspect → Application → Local Storage → `http://localhost:3000/` → Clear All, and then reload.
81 changes: 81 additions & 0 deletions rust/vetkeys/encrypted_notes_dapp_vetkd/frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"name": "encrypted-notes-dapp",
"version": "0.2.0",
"keywords": [
"Internet Computer",
"Motoko",
"Svelte",
"Canister",
"Rust"
],
"scripts": {
"build": "npm run build:bindings && rollup -c --bundleConfigAsCjs",
"build:bindings": "cd scripts && ./gen_bindings.sh",
"dev": "npm run build:bindings && rollup -c --bundleConfigAsCjs -w",
"start": "sirv public --single",
"test": "jest src",
"test:watch": "npm run test -- --watch",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@babel/preset-env": "^7.16.8",
"@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.2",
"@rollup/plugin-terser": "^1.0.0",
"@rollup/plugin-typescript": "^12.1.2",
"@tailwindcss/line-clamp": "^0.3.1",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/svelte": "^3.0.3",
"@tsconfig/svelte": "^2.0.0",
"autoprefixer": "^10.4.2",
"babel-jest": "^27.4.6",
"daisyui": "^1.25.4",
"idb-keyval": "6.2.1",
"jest": "^30.2.0",
"postcss": "^8.4.31",
"rollup": "^3.30.0",
"rollup-plugin-css-only": "^4.3.0",
"rollup-plugin-dotenv": "^0.5.1",
"rollup-plugin-inject": "^3.0.2",
"rollup-plugin-inject-process-env": "^1.3.1",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-polyfill-node": "^0.12.0",
"rollup-plugin-svelte": "^7.2.2",
"svelte": "^3.59.1",
"svelte-check": "^3.3.2",
"svelte-jester": "^2.3.2",
"svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.0.17",
"tslib": "^2.0.0",
"typescript": "^4.0.0"
},
"dependencies": {
"@dfinity/agent": "^2.1.3",
"@dfinity/auth-client": "^2.1.3",
"@dfinity/candid": "^2.1.3",
"@dfinity/identity": "^2.1.3",
"@dfinity/principal": "^2.1.3",
"@dfinity/vetkeys": "^0.3.0",
"isomorphic-dompurify": "^2.25.0",
"sirv-cli": "^1.0.0",
"svelte-icons": "^2.1.0",
"svelte-router-spa": "^6.0.3",
"typewriter-editor": "^0.6.45"
},
"jest": {
"transform": {
"^.+\\.js$": "babel-jest",
"^.+\\.svelte$": "svelte-jester"
},
"moduleFileExtensions": [
"js",
"svelte"
],
"setupFilesAfterEnv": [
"@testing-library/jest-dom/extend-expect",
"./jest-env.js"
],
"testEnvironment": "jsdom"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
match: "**/*",
security_policy: "hardened",
headers: {
"Content-Security-Policy": "default-src 'self';script-src 'self';connect-src 'self' http://localhost:* https://icp0.io https://*.icp0.io https://icp-api.io;img-src 'self';style-src * 'unsafe-inline';object-src 'none';base-uri 'self';frame-ancestors 'none';form-action 'self';upgrade-insecure-requests;",
},
allow_raw_access: false
},
]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions rust/vetkeys/encrypted_notes_dapp_vetkd/frontend/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />

<title>Encrypted Notes</title>

<link rel="icon" type="image/png" href="/favicon.png" />
<link rel="stylesheet" href="/build/bundle.css" />

<script defer src="/build/main.js"></script>
</head>

<body></body>
</html>
121 changes: 121 additions & 0 deletions rust/vetkeys/encrypted_notes_dapp_vetkd/frontend/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import svelte from "rollup-plugin-svelte";
import commonjs from "@rollup/plugin-commonjs";
import resolve from "@rollup/plugin-node-resolve";
import livereload from "rollup-plugin-livereload";
import terser from "@rollup/plugin-terser";
import sveltePreprocess from "svelte-preprocess";
import typescript from "@rollup/plugin-typescript";
import css from "rollup-plugin-css-only";
import json from "@rollup/plugin-json";
import injectProcessEnv from "rollup-plugin-inject-process-env";

const production = !process.env.ROLLUP_WATCH;

function serve(exposeHost) {
let server;

function toExit() {
if (server) server.kill(0);
}

return {
writeBundle() {
if (server) return;
server = require("child_process").spawn(
"npm",
exposeHost
? ["run", "start-expose", "--", "--dev"]
: ["run", "start", "--", "--dev"],
{
stdio: ["ignore", "inherit", "inherit"],
shell: true,
}
);

process.on("SIGTERM", toExit);
process.on("exit", toExit);
},
};
}

export default (config) => {
const exposeHost = !!config.configExpose;

return {
input: "src/main.ts",
output: {
sourcemap: true,
name: "app",
format: "iife",

file: "public/build/main.js",
inlineDynamicImports: true,
},
plugins: [
svelte({
preprocess: sveltePreprocess({
sourceMap: !production,
postcss: {
plugins: [require("tailwindcss")(), require("autoprefixer")()],
},
}),
compilerOptions: {
// enable run-time checks when not in production
dev: !production,
},
}),
// we'll extract any component CSS out into
// a separate file - better for performance
css({ output: "bundle.css" }),

// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
preferBuiltins: false,
browser: true,
dedupe: ["svelte"],
}),
commonjs(),
typescript({
sourceMap: !production,
inlineSources: !production,
}),
json(),
injectProcessEnv({
DFX_NETWORK: process.env.DFX_NETWORK,
CANISTER_ID_ENCRYPTED_NOTES: process.env.CANISTER_ID_ENCRYPTED_NOTES,
}),

// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(exposeHost),

// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload("public"),

// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser(),
],
watch: {
clearScreen: false,
},
onwarn: function (warning) {
if (
[
"CIRCULAR_DEPENDENCY",
"THIS_IS_UNDEFINED",
"EVAL",
"NAMESPACE_CONFLIC",
].includes(warning.code)
) {
return;
}
console.warn(warning.message);
},
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

cd ../.. && dfx generate encrypted_notes || exit 1

rm -r frontend/src/declarations/encrypted_notes > /dev/null 2>&1 || true

mkdir -p frontend/src/declarations/encrypted_notes
mv src/declarations/encrypted_notes frontend/src/declarations
rmdir -p src/declarations > /dev/null 2>&1 || true
19 changes: 19 additions & 0 deletions rust/vetkeys/encrypted_notes_dapp_vetkd/frontend/src/App.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script lang="ts">
import Hero from './components/Hero.svelte';
import LayoutAuthenticated from './components/LayoutAuthenticated.svelte';
import Notifications from './components/Notifications.svelte';
import { auth } from './store/auth';
</script>

{#if $auth.state === 'initialized'}
<LayoutAuthenticated />
{:else}
<Hero auth={$auth} />
{/if}
<Notifications />

<style lang="postcss" global>
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import DisclaimerCopy from './DisclaimerCopy.svelte';
let isDismissed = !!window.localStorage.getItem('disclaimer-dismissed');

function dismiss() {
window.localStorage.setItem('disclaimer-dismissed', 'yes');
isDismissed = true;
}
</script>

{#if !isDismissed}
<div
class="sticky bottom-0 p-4 text-xs bg-base-300 mt-4 sm:flex"
out:fly={{ y: 50 }}
>
<p class="opacity-90 sm:flex-1">
<DisclaimerCopy />
</p>

<button
class="btn btn-outline btn-xs sm:btn-sm sm:self-start mt-4 sm:mt-0 sm:ml-4 opacity-90"
on:click={dismiss}>I understand</button
>
</div>
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<strong>Disclaimer:</strong> This sample dapp is intended exclusively for experimental
purpose. You are advised not to use this dapp for storing your critical data such
as keys or passwords.
Loading