Skip to content

Commit

Permalink
consolidate
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewkmin committed Nov 28, 2023
1 parent f394a91 commit 8e72eb3
Show file tree
Hide file tree
Showing 17 changed files with 67 additions and 14,312 deletions.
4 changes: 1 addition & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ LABEL org.opencontainers.image.source https://github.com/tkhq/frames

COPY nginx.conf /etc/nginx/nginx.conf

COPY recovery /usr/share/nginx/recovery
COPY export /usr/share/nginx/export
COPY auth /usr/share/nginx/auth
COPY export /usr/share/nginx/export

EXPOSE 8080/tcp
EXPOSE 8081/tcp
EXPOSE 8082/tcp

CMD ["nginx"]
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This repository contains code for the recovery, export, and auth components of T
## Email Recovery
This self-contained HTML page is meant to be used as a standalone document to help first-party Turnkey root users. It's also going to be embedded as an iframe to help with sub-org root recovery.

This page is hosted at https://recovery.turnkey.com/
This page is hosted at https://auth.turnkey.com/

## Key and Wallet Export
This self-contained HTML page is meant to be used as either a standalone document or to be embedded as an iframe.
Expand All @@ -15,7 +15,7 @@ This page is hosted at https://export.turnkey.com/
## Auth (via Email)
This self-contained HTML page is meant to be used as either a standalone document or to be embedded as an iframe.

This page is hosted at https://auth.turnkey.com/
This page is also hosted at https://auth.turnkey.com/

# Getting Started

Expand All @@ -33,18 +33,16 @@ nvm use

Install dependencies:
```sh
cd recovery && npm install
cd export && npm install
cd auth && npm install
cd export && npm install
```

# Unit Testing

The export, recovery, and auth pages each have tests. They run on CI automatically. If you want to run them locally:
The auth and recovery pages each have tests. They run on CI automatically. If you want to run them locally:
```sh
cd recovery && npm test
cd export && npm test
cd auth && npm test
cd export && npm test
```

# Running Local Wallet Export
Expand Down Expand Up @@ -86,9 +84,9 @@ To build:
docker build . -t frames
```

To run (mapping `[8080, 8081, 8082]` to `[18080, 18081, 18082]` because they're often busy):
To run (mapping `[8080, 8081]` to `[18080, 18081]` because they're often busy):
```
docker run -p18080:8080 -p18081:8081 -p18082:8082 -t frames
docker run -p18080:8080 -p18081:8081 -t frames
```

# Deploying to a test Kubernetes cluster
Expand Down
10 changes: 10 additions & 0 deletions auth/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 24 additions & 22 deletions auth/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
<html class="no-js" lang="">

<head>
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
<meta charset="utf-8">
<title>Turnkey Auth</title>
<title>Turnkey Recovery and Auth</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
Expand Down Expand Up @@ -75,8 +76,8 @@
</head>

<body>
<h2>Authenticate via Email</h2>
<p><em>This public key will be sent along with your email inside of a new <code>EMAIL_AUTH</code> activity</em></p>
<h2>Init Recovery or Auth</h2>
<p><em>This public key will be sent along with your email inside of a new <code>INIT_USER_EMAIL_RECOVERY</code> or <code>EMAIL_AUTH</code> activity</em></p>
<form>
<label>Embedded key</label>
<input type="text" name="embedded-key" id="embedded-key" disabled/>
Expand All @@ -85,17 +86,18 @@ <h2>Authenticate via Email</h2>
<br>
<br>
<br>
<h2>Inject Email Auth Bundle</h2>
<h2>Inject Credential Bundle</h2>
<p><em>The credential bundle will come from your email. This bundle can then be used for email recovery or auth. We can simulate this locally: see instructions <a href="https://github.com/tkhq/frames#running-local-auth" target="_blank">here</a>. A credential bundle is composed of a public key and an encrypted payload. The payload is encrypted to this document's embedded key (stored in local storage and displayed above). The scheme relies on <a target="_blank" href="https://datatracker.ietf.org/doc/rfc9180/">HPKE (RFC 9180)</a></em>.</p>
<form>
<label>Bundle</label>
<input type="text" name="auth-bundle" id="auth-bundle"/>
<input type="text" name="credential-bundle" id="credential-bundle"/>
<button id="inject">Inject Bundle</button>
</form>
<br>
<br>
<br>
<h2>Stamp</h2>
<p><em>Once you've injected the auth bundle, the newly created session key is ready to sign. A new <code>CREATE_WALLET</code> activity for example. This iframe doesn't know anything about Turnkey activity however, it's a simple stamper!</em></p>
<p><em>Once you've injected the credential bundle, the credential is ready to sign. A new <code>RECOVER</code> activity for example. This iframe doesn't know anything about Turnkey activity however, it's a simple stamper!</em></p>
<form>
<label>Payload</label>
<input type="text" name="payload" id="payload"/>
Expand Down Expand Up @@ -463,7 +465,7 @@ <h2>Message log</h2>
* when performing `crypto.subtle.importKey` operations.
* @param {Uint8Array} privateKeyBytes
*/
var importAuthCredential = async function(privateKeyBytes) {
var importCredential = async function(privateKeyBytes) {
var privateKeyHexString = uint8arrayToHexString(privateKeyBytes);
var privateKey = BigInt('0x' + privateKeyHexString);
var publicKeyPoint = P256Generator.multiply(privateKey);
Expand Down Expand Up @@ -628,7 +630,7 @@ <h2>Message log</h2>
* ----
* IMPORTANT NOTE: below we implement basic field arithmetic for P256
* This is only used to compute public point from a secret key inside of
* `importAuthCredential` above. If something goes wrong with the code below
* `importCredential` above. If something goes wrong with the code below
* the web crypto API will simply refuse to import the key.
* None of the functions below are returned from the closure to minimize the risk of misuse.
*********************************************************************************************/
Expand Down Expand Up @@ -820,7 +822,7 @@ <h2>Message log</h2>
getEmbeddedKey,
setEmbeddedKey,
onResetEmbeddedKey,
importAuthCredential,
importCredential,
compressRawPublicKey,
uncompressRawPublicKey,
p256JWKPrivateToPublic,
Expand Down Expand Up @@ -848,8 +850,8 @@ <h2>Message log</h2>
// TODO: this should be bundled at build time or replaced with code written by Turnkey entirely.
import * as hpke from "https://esm.sh/@hpke/core";

// In memory spot for the email auth credential to live. We do NOT persist it to localStorage.
var AUTH_CREDENTIAL_BYTES = null;
// In memory spot for the credential to live. We do NOT persist it to localStorage.
var CREDENTIAL_BYTES = null;

document.addEventListener("DOMContentLoaded", async function () {
await TKHQ.initEmbeddedKey();
Expand All @@ -862,7 +864,7 @@ <h2>Message log</h2>
// TODO: find a way to filter messages and ensure they're coming from the parent window?
// We do not want to arbitrarily receive messages from all origins.
window.addEventListener("message", async function(event) {
if (event.data && event.data["type"] == "INJECT_AUTH_BUNDLE") {
if (event.data && event.data["type"] == "INJECT_CREDENTIAL_BUNDLE") {
TKHQ.logMessage(`⬇️ Received message ${event.data["type"]}: ${event.data["value"]}`);
try {
await onInjectBundle(event.data["value"])
Expand All @@ -889,15 +891,15 @@ <h2>Message log</h2>
}, false);

/**
* Event handlers to power the email auth flow in standalone mode
* Event handlers to power the recovery flow in standalone mode
* Instead of receiving events from the parent page, forms trigger them.
* This is useful for debugging as well.
*/
document.getElementById("inject").addEventListener("click", async function(e) {
e.preventDefault();
window.postMessage({
"type": "INJECT_AUTH_BUNDLE",
"value": document.getElementById("auth-bundle").value,
"type": "INJECT_CREDENTIAL_BUNDLE",
"value": document.getElementById("credential-bundle").value,
})
}, false);
document.getElementById("stamp").addEventListener("click", async function(e) {
Expand All @@ -914,7 +916,7 @@ <h2>Message log</h2>
}, false);

/**
* Function triggered when INJECT_AUTH_BUNDLE event is received.
* Function triggered when INJECT_CREDENTIAL_BUNDLE event is received.
* The `bundle` param is the concatenation of a public key and an encrypted payload, and then base64 encoded
* Example: A6ZPGAlxBRZhjKWky4RpXnHVceGzJjTuBrzKvMGnIgZ3r6JD4D1iiSg_m-y_u0BgJKI397Xjn0wgu17w9wuRooEp-F38m4ql57FgQ7sX9nQA
* @param {string} bundle
Expand Down Expand Up @@ -951,31 +953,31 @@ <h2>Message log</h2>
// Decompress the compressed key
var encappedKeyBuf = TKHQ.uncompressRawPublicKey(compressedEncappedKeyBuf);

var authCredentialBytes = await HpkeDecrypt(
var credentialBytes = await HpkeDecrypt(
{
ciphertextBuf,
encappedKeyBuf,
receiverPrivJwk: embeddedKeyJwk,
});

AUTH_CREDENTIAL_BYTES = new Uint8Array(authCredentialBytes);
CREDENTIAL_BYTES = new Uint8Array(credentialBytes);
TKHQ.sendMessageUp("BUNDLE_INJECTED", true)
}
/**
* Function triggered when STAMP_REQUEST event is received.
* @param {string} payload to sign
*/
var onStampRequest = async function(payload) {
if (AUTH_CREDENTIAL_BYTES === null) {
if (CREDENTIAL_BYTES === null) {
throw new Error("cannot sign payload without credential. Credential bytes are null");
}
var authKey = await TKHQ.importAuthCredential(AUTH_CREDENTIAL_BYTES)
var key = await TKHQ.importCredential(CREDENTIAL_BYTES)
var signatureIeee1363 = await window.crypto.subtle.sign(
{
name: "ECDSA",
hash: {name: "SHA-256"},
},
authKey,
key,
new TextEncoder().encode(payload)
);

Expand All @@ -987,7 +989,7 @@ <h2>Message log</h2>
// - Then imported without the private "d" component, and exported to get the public key
// ^^ (that's what `p256JWKPrivateToPublic` does)
// - Finally, compress the public key.
var jwkKey = await crypto.subtle.exportKey("jwk", authKey);
var jwkKey = await crypto.subtle.exportKey("jwk", key);
var publicKey = await TKHQ.p256JWKPrivateToPublic(jwkKey);
var compressedPublicKey = TKHQ.compressRawPublicKey(publicKey);

Expand Down
10 changes: 5 additions & 5 deletions auth/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ describe("TKHQ", () => {
expect(key.key_ops).toContain("deriveBits");
})

it("imports auth credentials without errors", async () => {
let key = await TKHQ.importAuthCredential(TKHQ.uint8arrayFromHexString("7632de7338577bc12c1731fa29f08019206af381f74af60f4d5e0395218f205c"));
it("imports credentials (for recovery or auth) without errors", async () => {
let key = await TKHQ.importCredential(TKHQ.uint8arrayFromHexString("7632de7338577bc12c1731fa29f08019206af381f74af60f4d5e0395218f205c"));
expect(key.constructor.name).toEqual("CryptoKey");
expect(key.algorithm).toEqual({ name: "ECDSA", namedCurve: "P-256"});
})

it("imports auth credentials correctly", async () => {
let key = await TKHQ.importAuthCredential(TKHQ.uint8arrayFromHexString("7632de7338577bc12c1731fa29f08019206af381f74af60f4d5e0395218f205c"));
it("imports credentials (for recovery or auth) correctly", async () => {
let key = await TKHQ.importCredential(TKHQ.uint8arrayFromHexString("7632de7338577bc12c1731fa29f08019206af381f74af60f4d5e0395218f205c"));
let jwkPrivateKey = await crypto.subtle.exportKey("jwk", key);
let publicKey = await TKHQ.p256JWKPrivateToPublic(jwkPrivateKey);
let compressedPublicKey = TKHQ.compressRawPublicKey(publicKey);
Expand Down Expand Up @@ -148,7 +148,7 @@ describe("TKHQ", () => {
// Same input as above, except last digit changed.
await expect(TKHQ.base58checkDecode("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNb")).rejects.toThrow("checksums do not match: computed 194,155,125,147 but found 194,155,125,148");

// Realistic email auth code: concatenation of a 33 bytes P-256 public key + a 48-bytes long encrypted credential
// Realistic recovery code: concatenation of a 33 bytes P-256 public key + a 48-bytes long encrypted credential
// Test vector from our internal repo, which uses Rust to encode in base58check.
expect(Array.from(await TKHQ.base58checkDecode("szrFBNGDkhXyVvRoqjjDT6xd7kRhDXHmtQH3NVkPuVVkeiPFjn6UkyjbiTzuxH9wKH4QdEJUaWxZLM1ZLzByUFN1TNjxVh5aoZENCnKYrSEdZBnRWcK"))).toEqual(
Array.from(TKHQ.uint8arrayFromHexString("02cb30f1f44d411383cc2a7bb7135d87e0fbf265d0e002b460c9d38d97b14cd0d26114254d213cd77887293644d942a62516a3f174f01ed1ccb57dea1f8ac88664759bb6febcd8b060e7a11d23c614dd66"))
Expand Down
4 changes: 2 additions & 2 deletions auth/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 auth/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "email-auth-tests",
"name": "auth-tests",
"version": "1.0.0",
"main": "index.test.js",
"scripts": {
Expand Down
10 changes: 10 additions & 0 deletions export/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion export/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<html class="no-js" lang="">

<head>
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
<meta charset="utf-8">
<title>Turnkey Export</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
Expand Down
13 changes: 4 additions & 9 deletions kustomize/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ spec:
- name: frames
image: ghcr.io/tkhq/frames
ports:
- name: recovery
- name: auth
containerPort: 8080
- name: export
containerPort: 8081
- name: auth
containerPort: 8082
livenessProbe:
httpGet:
path: /health
port: recovery
port: auth
securityContext:
allowPrivilegeEscalation: false
capabilities:
Expand All @@ -39,12 +37,9 @@ metadata:
name: frames
spec:
ports:
- name: http-recovery
- name: http-auth
port: 8080
targetPort: recovery
targetPort: auth
- name: http-export
port: 8081
targetPort: export
- name: http-auth
port: 8082
targetPort: auth
9 changes: 2 additions & 7 deletions nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ http {
# Send access logs to stdout for logging
access_log /dev/stdout main;

# Custom server blocks to serve recovery, export, and auth frames on separate ports
# Custom server blocks to serve auth and export frames on separate ports

server {
listen 8080;
root /usr/share/nginx/recovery;
root /usr/share/nginx/auth;

# Health endpoint for k8s
location = /health {
Expand All @@ -53,9 +53,4 @@ http {
listen 8081;
root /usr/share/nginx/export;
}

server {
listen 8082;
root /usr/share/nginx/auth;
}
}
7 changes: 0 additions & 7 deletions recovery/babel.config.js

This file was deleted.

Loading

0 comments on commit 8e72eb3

Please sign in to comment.