heimdall-tide
Version:
SDK for communicating with a Tide Enclave
253 lines (224 loc) • 9.25 kB
text/typescript
import { Heimdall, windowType } from "../heimdall";
import { TideMemory } from "../wrapper";
interface HiddenInit{
doken: string;
/**
* @returns A refresh doken for Heimdall
*/
dokenRefreshCallback: () => Promise<string> | undefined;
/**
* @returns A function that re authenticates the current user from the client. (Used to update their session key on Identity System). Returns a new doken too.
*/
requireReloginCallback: () => Promise<string>;
}
export class RequestEnclave extends Heimdall<RequestEnclave>{
private doken: string;
private dokenRefreshCallback: () => Promise<string> | undefined;
private requireReloginCallback: () => Promise<string>;
_windowType: windowType = windowType.Hidden;
private initDone: Promise<any> = this.recieve("init done");
init(data: HiddenInit): RequestEnclave {
if(!data.doken) throw 'Doken not provided';
this.doken = data.doken;
this.dokenRefreshCallback = data.dokenRefreshCallback;
this.requireReloginCallback = data.requireReloginCallback;
this.recieve("hidden enclave").then((data) => this.handleHiddenEnclaveResponse(data));
this.open().then((success: boolean) => {
if(success){
this.send({
type: "init",
message: {
doken: this.doken
}
});
}else throw 'Error opening enclave';
});
return this;
}
async handleHiddenEnclaveResponse(msg: any){
// Below is the session key mismatch flow that was implemented but then it was decided a basic relogin was more elegent
// Keeping it though because it is nearly identical to the flow where a tide user delegates a token to another tide user
// This would require the second tide user to sign a new delegated token with their current session key
// This would be gold in a cvk scenario
// if(msg == "session key mismatch" && this._windowType == windowType.Hidden){
// this.initDone = this.recieve("init done"); // await the REOPENED HIDDEN ENCLAVE INIT DONE SIGNAL
// // looks like the hidden iframe has not allowed data to be stored on the browser OR the session key is mismatched with whats on the enclave vs doken
// // either way we gotta get a doken with the appropriate session key
// // Close the hidden enclave
// this.close();
// // We're now going to open the request enclave as a popup with the mismatched doken
// // The page should recognise the doken is mismatched, generate a new one, then close
// this._windowType = windowType.Popup;
// // open popup
// await this.open();
// // send doken to refresh
// this.send({
// type: "init",
// message:{
// doken: this.doken
// }
// });
// // wait for new doken
// const resp = await this.recieve("refreshed doken");
// this.doken = resp.doken;
// if(this.requireReloginCallback) this.requireReloginCallback();
// // close pop up enclave
// this.close();
// // reset page to hidden iframe
// this._windowType = windowType.Hidden;
// // open hidden iframe
// this.open().then((success: boolean) => {
// if(success){
// this.send({
// type: "init",
// message: {
// doken: this.doken
// }
// });
// }else throw 'Error opening enclave';
// });
// }
if(msg == "session key mismatch"){
this.close();
this.requireReloginCallback(); // should initiate a full client page reload, killing this
}
else if(msg == "storage issue"){
// Convert hidden enclave into popup
this.close();
this._windowType = windowType.Popup;
this.open().then((success: boolean) => {
if(success){
this.send({
type: "init",
message: {
doken: this.doken
}
});
}else throw 'Error opening enclave';
});
}
this.recieve("hidden enclave").then((data) => this.handleHiddenEnclaveResponse(data));
}
getOrkUrl(): URL {
// construct ork url
const url = new URL(this.enclaveOrigin);
// Set hidden status
url.searchParams.set("hidden", this._windowType == windowType.Hidden ? "true" : "false");
// Set vendor public
url.searchParams.set("vendorId", this.vendorId);
// Set client origin
url.searchParams.set("origin", encodeURIComponent(window.location.origin));
// Set client origin signature (by vendor)
url.searchParams.set("originsig", encodeURIComponent(this.signed_client_origin));
// Set voucher url
url.searchParams.set("voucherURL", encodeURIComponent(this.voucherURL));
// Set requestsed enclave
url.searchParams.set("type", "request");
return url;
}
checkEnclaveOpen(){
if(this.enclaveClosed()){
// Enclave was closed!
// We need to reopen the enclave and await the init again
this.initDone = this.recieve("init done");
this.open().then((success: boolean) => {
if(success){
this.send({
type: "init",
message:{
doken: this.doken
}
});
}else throw 'Error opening enclave';
});
}
}
async execute(data: TideMemory): Promise<Uint8Array[]>{
this.checkEnclaveOpen();
await this.initDone;
const pre_resp = this.recieve("sign request completed");
this.send({
type: "request",
message:{
flow: "sign",
request: data,
}
})
const resp = await pre_resp;
if(!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp;
if(!resp.every((d: any) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays';
return resp;
}
async decrypt(data: decryptRequest): Promise<Uint8Array[]>{
this.checkEnclaveOpen();
await this.initDone;
const pre_resp = this.recieve("decrypt request completed");
this.send({
type: "request",
message:{
flow: "decrypt",
request: data
}
})
const resp = await pre_resp;
if(!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp;
if(!resp.every((d: any) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays';
return resp;
}
async encrypt(data: encryptRequest): Promise<Uint8Array[]>{
this.checkEnclaveOpen();
await this.initDone;
const pre_resp = this.recieve("encrypt request completed");
this.send({
type: "request",
message: {
flow: "encrypt",
request: data
}
})
const resp = await pre_resp;
if(!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp;
if(!resp.every((d: any) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays';
return resp;
}
async updateDoken(doken: string){
this.doken = doken;
this.send({
type: "doken refresh",
message:{
doken: this.doken
}
});
}
async onerror(data: any) {
if(typeof data.message === "string"){
switch(data.message){
case "expired":
if(!this.dokenRefreshCallback){
console.error("[HEIMDALL] Doken on enclave has expired but there is no Doken Refresh Callback registered");
return;
}
console.log("[HEIMDALL] Refreshing doken");
this.doken = await this.dokenRefreshCallback();
this.send({
type: "doken refresh",
message:{
doken: this.doken
}
});
break;
default:
this.close();
throw new Error("[HEIMDALL] Recieved enclave error: " + data.message);
}
}
}
}
interface decryptRequest{
encrypted: Uint8Array;
tags: string[]
}
interface encryptRequest{
data: Uint8Array;
tags: string[]
}