302 lines
9.2 KiB
JavaScript
302 lines
9.2 KiB
JavaScript
|
|
import { invoke } from '@tauri-apps/api/core';
|
||
|
|
|
||
|
|
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||
|
|
// SPDX-License-Identifier: Apache-2.0
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
class Location {
|
||
|
|
constructor(type, payload) {
|
||
|
|
this.type = type;
|
||
|
|
this.payload = payload;
|
||
|
|
}
|
||
|
|
static generic(vault, record) {
|
||
|
|
return new Location('Generic', {
|
||
|
|
vault,
|
||
|
|
record
|
||
|
|
});
|
||
|
|
}
|
||
|
|
static counter(vault, counter) {
|
||
|
|
return new Location('Counter', {
|
||
|
|
vault,
|
||
|
|
counter
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
class ProcedureExecutor {
|
||
|
|
constructor(procedureArgs) {
|
||
|
|
this.procedureArgs = procedureArgs;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Generate a SLIP10 seed for the given location.
|
||
|
|
* @param outputLocation Location of the record where the seed will be stored.
|
||
|
|
* @param sizeBytes The size in bytes of the SLIP10 seed.
|
||
|
|
* @param hint The record hint.
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
async generateSLIP10Seed(outputLocation, sizeBytes) {
|
||
|
|
return await invoke('plugin:stronghold|execute_procedure', {
|
||
|
|
...this.procedureArgs,
|
||
|
|
procedure: {
|
||
|
|
type: 'SLIP10Generate',
|
||
|
|
payload: {
|
||
|
|
output: outputLocation,
|
||
|
|
sizeBytes
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}).then((n) => Uint8Array.from(n));
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Derive a SLIP10 private key using a seed or key.
|
||
|
|
* @param chain The chain path.
|
||
|
|
* @param source The source type, either 'Seed' or 'Key'.
|
||
|
|
* @param sourceLocation The source location, must be the `outputLocation` of a previous call to `generateSLIP10Seed` or `deriveSLIP10`.
|
||
|
|
* @param outputLocation Location of the record where the private key will be stored.
|
||
|
|
* @param hint The record hint.
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
async deriveSLIP10(chain, source, sourceLocation, outputLocation) {
|
||
|
|
return await invoke('plugin:stronghold|execute_procedure', {
|
||
|
|
...this.procedureArgs,
|
||
|
|
procedure: {
|
||
|
|
type: 'SLIP10Derive',
|
||
|
|
payload: {
|
||
|
|
chain,
|
||
|
|
input: {
|
||
|
|
type: source,
|
||
|
|
payload: sourceLocation
|
||
|
|
},
|
||
|
|
output: outputLocation
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}).then((n) => Uint8Array.from(n));
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Store a BIP39 mnemonic.
|
||
|
|
* @param mnemonic The mnemonic string.
|
||
|
|
* @param outputLocation The location of the record where the BIP39 mnemonic will be stored.
|
||
|
|
* @param passphrase The optional mnemonic passphrase.
|
||
|
|
* @param hint The record hint.
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
async recoverBIP39(mnemonic, outputLocation, passphrase) {
|
||
|
|
return await invoke('plugin:stronghold|execute_procedure', {
|
||
|
|
...this.procedureArgs,
|
||
|
|
procedure: {
|
||
|
|
type: 'BIP39Recover',
|
||
|
|
payload: {
|
||
|
|
mnemonic,
|
||
|
|
passphrase,
|
||
|
|
output: outputLocation
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}).then((n) => Uint8Array.from(n));
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Generate a BIP39 seed.
|
||
|
|
* @param outputLocation The location of the record where the BIP39 seed will be stored.
|
||
|
|
* @param passphrase The optional mnemonic passphrase.
|
||
|
|
* @param hint The record hint.
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
async generateBIP39(outputLocation, passphrase) {
|
||
|
|
return await invoke('plugin:stronghold|execute_procedure', {
|
||
|
|
...this.procedureArgs,
|
||
|
|
procedure: {
|
||
|
|
type: 'BIP39Generate',
|
||
|
|
payload: {
|
||
|
|
output: outputLocation,
|
||
|
|
passphrase
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}).then((n) => Uint8Array.from(n));
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Gets the Ed25519 public key of a SLIP10 private key.
|
||
|
|
* @param privateKeyLocation The location of the private key. Must be the `outputLocation` of a previous call to `deriveSLIP10`.
|
||
|
|
* @returns A promise resolving to the public key hex string.
|
||
|
|
*
|
||
|
|
* @since 2.0.0
|
||
|
|
*/
|
||
|
|
async getEd25519PublicKey(privateKeyLocation) {
|
||
|
|
return await invoke('plugin:stronghold|execute_procedure', {
|
||
|
|
...this.procedureArgs,
|
||
|
|
procedure: {
|
||
|
|
type: 'PublicKey',
|
||
|
|
payload: {
|
||
|
|
type: 'Ed25519',
|
||
|
|
privateKey: privateKeyLocation
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}).then((n) => Uint8Array.from(n));
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Creates a Ed25519 signature from a private key.
|
||
|
|
* @param privateKeyLocation The location of the record where the private key is stored. Must be the `outputLocation` of a previous call to `deriveSLIP10`.
|
||
|
|
* @param msg The message to sign.
|
||
|
|
* @returns A promise resolving to the signature hex string.
|
||
|
|
*
|
||
|
|
* @since 2.0.0
|
||
|
|
*/
|
||
|
|
async signEd25519(privateKeyLocation, msg) {
|
||
|
|
return await invoke('plugin:stronghold|execute_procedure', {
|
||
|
|
...this.procedureArgs,
|
||
|
|
procedure: {
|
||
|
|
type: 'Ed25519Sign',
|
||
|
|
payload: {
|
||
|
|
privateKey: privateKeyLocation,
|
||
|
|
msg
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}).then((n) => Uint8Array.from(n));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
class Client {
|
||
|
|
constructor(path, name) {
|
||
|
|
this.path = path;
|
||
|
|
this.name = name;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get a vault by name.
|
||
|
|
* @param name
|
||
|
|
* @param flags
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
getVault(name) {
|
||
|
|
return new Vault(this.path, this.name, name);
|
||
|
|
}
|
||
|
|
getStore() {
|
||
|
|
return new Store(this.path, this.name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
class Store {
|
||
|
|
constructor(path, client) {
|
||
|
|
this.path = path;
|
||
|
|
this.client = client;
|
||
|
|
}
|
||
|
|
async get(key) {
|
||
|
|
return await invoke('plugin:stronghold|get_store_record', {
|
||
|
|
snapshotPath: this.path,
|
||
|
|
client: this.client,
|
||
|
|
key
|
||
|
|
}).then((v) => v && Uint8Array.from(v));
|
||
|
|
}
|
||
|
|
async insert(key, value, lifetime) {
|
||
|
|
await invoke('plugin:stronghold|save_store_record', {
|
||
|
|
snapshotPath: this.path,
|
||
|
|
client: this.client,
|
||
|
|
key,
|
||
|
|
value,
|
||
|
|
lifetime
|
||
|
|
});
|
||
|
|
}
|
||
|
|
async remove(key) {
|
||
|
|
return await invoke('plugin:stronghold|remove_store_record', {
|
||
|
|
snapshotPath: this.path,
|
||
|
|
client: this.client,
|
||
|
|
key
|
||
|
|
}).then((v) => v && Uint8Array.from(v));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* A key-value storage that allows create, update and delete operations.
|
||
|
|
* It does not allow reading the data, so one of the procedures must be used to manipulate
|
||
|
|
* the stored data, allowing secure storage of secrets.
|
||
|
|
*/
|
||
|
|
class Vault extends ProcedureExecutor {
|
||
|
|
constructor(path, client, name) {
|
||
|
|
super({
|
||
|
|
snapshotPath: path,
|
||
|
|
client,
|
||
|
|
vault: name
|
||
|
|
});
|
||
|
|
this.path = path;
|
||
|
|
this.client = client;
|
||
|
|
this.name = name;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Insert a record to this vault.
|
||
|
|
* @param location The record location.
|
||
|
|
* @param record The record data.
|
||
|
|
* @param recordHint The record hint.
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
async insert(recordPath, secret) {
|
||
|
|
await invoke('plugin:stronghold|save_secret', {
|
||
|
|
snapshotPath: this.path,
|
||
|
|
client: this.client,
|
||
|
|
vault: this.name,
|
||
|
|
recordPath,
|
||
|
|
secret
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Remove a record from the vault.
|
||
|
|
* @param location The record location.
|
||
|
|
* @param gc Whether to additionally perform the gargage collection or not.
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
async remove(location) {
|
||
|
|
await invoke('plugin:stronghold|remove_secret', {
|
||
|
|
snapshotPath: this.path,
|
||
|
|
client: this.client,
|
||
|
|
vault: this.name,
|
||
|
|
recordPath: location.payload.record
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* A representation of an access to a stronghold.
|
||
|
|
*/
|
||
|
|
class Stronghold {
|
||
|
|
/**
|
||
|
|
* Initializes a stronghold.
|
||
|
|
* If the snapshot path located at `path` exists, the password must match.
|
||
|
|
* @param path
|
||
|
|
* @param password
|
||
|
|
*/
|
||
|
|
constructor(path) {
|
||
|
|
this.path = path;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Load the snapshot if it exists (password must match), or start a fresh stronghold instance otherwise.
|
||
|
|
* @param password
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
static async load(path, password) {
|
||
|
|
return await invoke('plugin:stronghold|initialize', {
|
||
|
|
snapshotPath: path,
|
||
|
|
password
|
||
|
|
}).then(() => new Stronghold(path));
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Remove this instance from the cache.
|
||
|
|
*/
|
||
|
|
async unload() {
|
||
|
|
await invoke('plugin:stronghold|destroy', {
|
||
|
|
snapshotPath: this.path
|
||
|
|
});
|
||
|
|
}
|
||
|
|
async loadClient(client) {
|
||
|
|
return await invoke('plugin:stronghold|load_client', {
|
||
|
|
snapshotPath: this.path,
|
||
|
|
client
|
||
|
|
}).then(() => new Client(this.path, client));
|
||
|
|
}
|
||
|
|
async createClient(client) {
|
||
|
|
return await invoke('plugin:stronghold|create_client', {
|
||
|
|
snapshotPath: this.path,
|
||
|
|
client
|
||
|
|
}).then(() => new Client(this.path, client));
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Persists the stronghold state to the snapshot.
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
async save() {
|
||
|
|
await invoke('plugin:stronghold|save', {
|
||
|
|
snapshotPath: this.path
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export { Client, Location, Store, Stronghold, Vault };
|