auth-server-helper/lib/KeyStore.ts

152 lines
3.9 KiB
TypeScript
Raw Normal View History

/*
* Copyright (C) Sapphirecode - All Rights Reserved
* This file is part of Auth-Server-Helper which is released under MIT.
* See file 'LICENSE' for full license details.
* Created by Timo Hocker <timo@scode.ovh>, December 2020
*/
2021-01-08 13:30:53 +01:00
import { generate_keypair, random_hex } from '@sapphirecode/crypto-helper';
import { to_b58 } from '@sapphirecode/encoding-helper';
2021-01-06 16:06:03 +01:00
2021-01-08 13:30:53 +01:00
const renew_interval = 3600;
2020-12-06 21:06:40 +01:00
2021-01-06 11:15:56 +01:00
interface Key {
2021-01-06 22:43:03 +01:00
key: string;
2021-01-06 11:15:56 +01:00
valid_until: number;
}
2021-01-14 21:31:21 +01:00
interface LabelledKey extends Key {
index: string;
}
2021-01-06 22:43:03 +01:00
interface KeyPair {
private_key?: Key;
public_key: Key;
}
type KeyStoreData = Record<string, KeyPair>;
2021-01-14 21:31:21 +01:00
type KeyStoreExport = LabelledKey[];
2021-01-06 22:43:03 +01:00
async function create_key (valid_for: number) {
const time = (new Date)
.getTime ();
const pair = await generate_keypair ();
return {
private_key: {
key: pair.private_key,
valid_until: time + (renew_interval * 1000)
},
public_key: {
key: pair.public_key,
valid_until: time + (valid_for * 1000)
}
};
}
2020-12-06 21:06:40 +01:00
class KeyStore {
2021-01-06 22:43:03 +01:00
private _keys: KeyStoreData = {};
private _interval: NodeJS.Timeout;
2021-01-08 13:30:53 +01:00
private _instance: string;
public get instance_id (): string {
return this._instance;
}
2021-01-06 11:15:56 +01:00
2021-01-06 22:43:03 +01:00
public constructor () {
this._interval = setInterval (() => {
2021-01-07 15:43:54 +01:00
this.garbage_collect ();
2021-01-06 22:43:03 +01:00
}, renew_interval);
2021-01-08 13:30:53 +01:00
this._instance = to_b58 (random_hex (16), 'hex');
}
private get_index (iat: number, instance = this._instance): string {
return instance + Math.floor (iat / renew_interval)
.toFixed (0);
2021-01-06 11:15:56 +01:00
}
2020-12-06 21:06:40 +01:00
2021-01-14 21:31:21 +01:00
private garbage_collect (): void {
2021-01-07 15:43:54 +01:00
const time = (new Date)
.getTime ();
2021-01-14 21:31:21 +01:00
const keys = Object.keys (this._keys);
2021-01-07 15:43:54 +01:00
for (const index of keys) {
2021-01-14 21:31:21 +01:00
const entry = this._keys[index];
2021-01-07 15:43:54 +01:00
if (typeof entry.private_key !== 'undefined'
&& entry.private_key.valid_until < time
)
delete entry.private_key;
if (entry.public_key.valid_until < time)
2021-01-14 21:31:21 +01:00
delete this._keys[index];
2021-01-07 15:43:54 +01:00
}
}
2021-01-08 13:30:53 +01:00
public async get_sign_key (
iat: number,
valid_for: number,
instance?: string
): Promise<string> {
2021-01-06 16:06:03 +01:00
if (valid_for <= 0)
throw new Error ('cannot create infinitely valid key');
if ((iat + 1) * 1000 < (new Date)
.getTime ())
throw new Error ('cannot access already expired keys');
2020-12-06 21:06:40 +01:00
2021-01-08 13:30:53 +01:00
const index = this.get_index (iat, instance);
2021-01-06 22:43:03 +01:00
2021-01-06 11:15:56 +01:00
const valid_until = (new Date)
.getTime () + (valid_for * 1000);
if (typeof this._keys[index] !== 'undefined') {
const key = this._keys[index];
2021-01-06 22:43:03 +01:00
if (key.public_key.valid_until < valid_until)
key.public_key.valid_until = valid_until;
2020-12-06 21:06:40 +01:00
2021-01-08 13:30:53 +01:00
if (typeof key.private_key === 'undefined')
throw new Error ('cannot access already expired keys');
2021-01-06 22:43:03 +01:00
return key.private_key?.key as string;
}
2021-01-06 16:06:03 +01:00
2021-01-07 15:43:54 +01:00
this._keys[index] = await create_key (valid_for);
2021-01-06 22:43:03 +01:00
return this._keys[index].private_key?.key as string;
2021-01-06 16:06:03 +01:00
}
2020-12-06 21:29:11 +01:00
2021-01-08 13:30:53 +01:00
public get_key (iat: number, instance?: string): string {
const index = this.get_index (iat, instance);
2021-01-06 11:15:56 +01:00
2021-01-06 16:06:03 +01:00
if (typeof this._keys[index] === 'undefined')
throw new Error ('key could not be found');
2020-12-06 21:06:40 +01:00
2021-01-06 16:06:03 +01:00
const key = this._keys[index];
2021-01-06 22:43:03 +01:00
return key.public_key.key;
}
2021-01-14 21:31:21 +01:00
public export_verification_data (): KeyStoreExport {
2021-01-07 15:43:54 +01:00
this.garbage_collect ();
2021-01-14 21:31:21 +01:00
const out: KeyStoreExport = [];
2021-01-06 22:43:03 +01:00
for (const index of Object.keys (this._keys))
2021-01-14 21:31:21 +01:00
out.push ({ ...this._keys[index].public_key, index });
2021-01-06 22:43:03 +01:00
return out;
}
2021-01-14 21:31:21 +01:00
public import_verification_data (data: KeyStoreExport): void {
for (const key of data) {
if (typeof this._keys[key.index] !== 'undefined')
2021-01-08 13:30:53 +01:00
throw new Error ('cannot import to the same instance');
2021-01-14 21:31:21 +01:00
this._keys[key.index] = {
public_key: {
key: key.key,
valid_until: key.valid_until
}
};
2021-01-08 13:30:53 +01:00
}
this.garbage_collect ();
2020-12-06 21:06:40 +01:00
}
}
const ks: KeyStore = (new KeyStore);
export default ks;
2021-01-14 21:31:21 +01:00
export { KeyStore, Key, LabelledKey, KeyStoreExport };