monorepo 버전 준비

## @strpc
기존 RPC를 package화

### @strpc/express
express의 middleware + router 결함

## @sammo
게임 전체
- server, client
- gateway_server, gateway_client
This commit is contained in:
2023-09-23 16:29:18 +00:00
parent 734e0cb7bf
commit e59f9a9659
197 changed files with 15458 additions and 3 deletions
+92
View File
@@ -0,0 +1,92 @@
import { AES_GCM_Decrypt, AES_GCM_Encrypt } from "./AES.js";
type TestType = {
key: Buffer,
IV: Buffer,
PT: Buffer,
AAD?: Buffer,
CT: Buffer,
failed?: boolean,
};
type RawTestType = {
key: string,
IV: string,
PT: string,
AAD?: string,
CT: string,
failed?: boolean,
}
function convertItem(obj: RawTestType): TestType {
return {
key: Buffer.from(obj.key, 'hex'),
IV: Buffer.from(obj.IV, 'hex'),
PT: Buffer.from(obj.PT, 'hex'),
AAD: obj.AAD ? Buffer.from(obj.AAD, 'hex') : undefined,
CT: Buffer.from(obj.CT, 'hex'),
failed: obj.failed,
}
}
const tests: TestType[] = [
{
key: '92e11dcdaa866f5ce790fd24501f92509aacf4cb8b1339d50c9c1240935dd08b',
IV : 'ac93a1a6145299bde902f21a',
PT : '2d71bcfa914e4ac045b2aa60955fad24',
AAD: '1e0889016f67601c8ebea4943bc23ad6',
CT : '8995ae2e6df3dbf96fac7b7137bae67feca5aa77d51d4a0a14d9c51e1da474ab',
failed: false,
},
{
key: 'b52c505a37d78eda5dd34f20c22540ea1b58963cf8e5bf8ffa85f9f2492505b4',
IV : '516c33929df5a3284ff463d7',
PT : '',
AAD: '',
CT : 'bdc1ac884d332457a1d2664f168c76f0',
failed: false,
},
{
key: '886cff5f3e6b8d0e1ad0a38fcdb26de97e8acbe79f6bed66959a598fa5047d65',
IV : '3a8efa1cd74bbab5448f9945',
PT : '',
AAD: '519fee519d25c7a304d6c6aa1897ee1eb8c59655',
CT : 'f6d47505ec96c98a42dc3ae719877b87',
failed: false,
},
{
key: '460fc864972261c2560e1eb88761ff1c992b982497bd2ac36c04071cbb8e5d99',
IV : '8a4a16b9e210eb68bcb6f58d',
PT : '99e4e926ffe927f691893fb79a96b067',
AAD: '',
CT : '133fc15751621b5f325c7ff71ce08324ec4e87e0cf74a13618d0b68636ba9fa7',
failed: false,
},
].map(convertItem)
test.each(tests)('encrypt(%#)', async (item) => {
for (const item of tests) {
if(item.failed){
expect(() => AES_GCM_Encrypt(item.key, item.IV, item.PT, item.AAD)).rejects.toThrow('Invalid');
return;
}
const ct = await AES_GCM_Encrypt(item.key, item.IV, item.PT, item.AAD);
expect(ct).toEqual(item.CT.buffer);
}
})
test.each(tests)('decrypt(%#)', async (item) => {
for (const item of tests) {
if(item.failed){
expect(() => AES_GCM_Decrypt(item.key, item.IV, item.CT, item.AAD)).rejects.toThrow('Invalid');
return;
}
const pt = await AES_GCM_Decrypt(item.key, item.IV, item.CT, item.AAD);
expect(pt).toEqual(item.PT.buffer);
}
})
+61
View File
@@ -0,0 +1,61 @@
import type { BufferSource } from "@sammo/util";
const subtle = globalThis.crypto.subtle;
export async function AES_GCM_Encrypt(key: BufferSource | CryptoKey, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer> {
const keyObj = key instanceof CryptoKey ? key : await subtle.importKey("raw", key, {
name: "AES-GCM",
}, false, ["encrypt"]);
const ciphertext = await subtle.encrypt({
name: "AES-GCM",
iv,
additionalData: aad
}, keyObj, msg);
return ciphertext;
}
export async function AES_GCM_Decrypt(key: BufferSource | CryptoKey, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer> {
const keyObj = key instanceof CryptoKey ? key : await subtle.importKey("raw", key, {
name: "AES-GCM",
}, false, ["decrypt"]);
const plaintext = await subtle.decrypt({
name: "AES-GCM",
iv: iv,
additionalData: aad
}, keyObj, ciphertext);
//아마도 tag 검증 실패시 예외가 발생할 것으로 예상
return plaintext;
}
export async function AES_CBC_Encrypt(key: BufferSource | CryptoKey, iv: BufferSource, msg: BufferSource): Promise<ArrayBuffer> {
const keyObj = key instanceof CryptoKey ? key : await subtle.importKey("raw", key, {
name: "AES-CBC",
}, false, ["encrypt"]);
const ciphertext = await subtle.encrypt({
name: "AES-CBC",
iv,
}, keyObj, msg);
return ciphertext;
}
export async function AES_CBC_Decrypt(key: BufferSource | CryptoKey, iv: BufferSource, ciphertext: BufferSource): Promise<ArrayBuffer> {
const keyObj = key instanceof CryptoKey ? key : await subtle.importKey("raw", key, {
name: "AES-CBC",
}, false, ["decrypt"]);
const plaintext = await subtle.decrypt({
name: "AES-CBC",
iv: iv,
}, keyObj, ciphertext);
return plaintext;
}
+63
View File
@@ -0,0 +1,63 @@
import { wrapBuffer } from "@sammo/util";
import { ECDSA_sign, ECDSA_verify } from "./ECDSA.js";
import { verifyKeyFromSignKey } from "./ECKey.js";
import type { ECDHe_P384_PublicKey, ECDHe_P384_KeyPair, ECDSA_PKCS8_P384_SignKey, ECDHe_P384_PrivateKey, ECDHe_P384_PublicKeyInfo, ECDHe_P384_LiteKeyPair } from "./RawTypes.js";
const subtle = globalThis.crypto.subtle;
export const curveName: EcKeyGenParams | EcKeyImportParams = {
name: 'ECDH',
namedCurve: 'P-384'
}
export async function genECDHeKey(signKey: ECDSA_PKCS8_P384_SignKey): Promise<ECDHe_P384_KeyPair> {
const keyPairP = subtle.generateKey(curveName, true, ['deriveKey', 'deriveBits']);
const verifyKeyP = verifyKeyFromSignKey(signKey);
const rawKeyPair = await keyPairP;
const publicKey = wrapBuffer<ECDHe_P384_PublicKey>(await subtle.exportKey('spki', rawKeyPair.publicKey));
const privateKey = wrapBuffer<ECDHe_P384_PrivateKey>(await subtle.exportKey('pkcs8', rawKeyPair.privateKey));
const signatureP = ECDSA_sign(signKey, privateKey);
return {
publicInfo: {
publicKey,
verifyKey: await verifyKeyP,
sign: await signatureP
},
privateKey,
}
}
export async function genECDHeLiteKey(): Promise<ECDHe_P384_LiteKeyPair> {
const keyPair = await subtle.generateKey(curveName, true, ['deriveKey', 'deriveBits']);
const publicKey = wrapBuffer<ECDHe_P384_PublicKey>(await subtle.exportKey('spki', keyPair.publicKey));
const privateKey = wrapBuffer<ECDHe_P384_PrivateKey>(await subtle.exportKey('pkcs8', keyPair.privateKey));
return {
publicKey,
privateKey
};
}
export async function deriveKey(other: ECDHe_P384_PublicKeyInfo, me: ECDHe_P384_LiteKeyPair | ECDHe_P384_KeyPair, keySizeBit?: number): Promise<ArrayBuffer>;
export async function deriveKey(other: ECDHe_P384_PublicKeyInfo | ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair, keySizeBit?: number): Promise<ArrayBuffer>;
export async function deriveKey(other: ECDHe_P384_PublicKeyInfo | ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair | ECDHe_P384_LiteKeyPair, keySizeBit = 256): Promise<ArrayBuffer> {
const rawPublicKey = other instanceof Uint8Array ? other : other.publicKey;
const rawPrivateKey = me.privateKey;
const publicKeyP = subtle.importKey('spki', rawPublicKey, curveName, true, ['deriveBits']);
const privateKeyP = subtle.importKey('pkcs8', rawPrivateKey, curveName, true, ['deriveBits']);
if (!(other instanceof Uint8Array) && !(await ECDSA_verify(other.verifyKey, other.publicKey, other.sign))) {
throw new Error("Invalid signature");
}
return await subtle.deriveBits({
name: 'ECDH',
public: await publicKeyP
}, await privateKeyP, keySizeBit);
}
+48
View File
@@ -0,0 +1,48 @@
import { AES_GCM_Decrypt, AES_GCM_Encrypt } from "./AES.js";
import { deriveKey } from "./ECDHe.js";
import type { ECDHe_P384_KeyPair, ECDHe_P384_LiteKeyPair, ECDHe_P384_PublicKey, ECDHe_P384_PublicKeyInfo } from "./RawTypes.js";
export type EncryptedItem = {
ct: Uint8Array;
}
const keySizeBit = 256;
export async function ECDHe_AES_GCM_Encrypt(other: ECDHe_P384_PublicKeyInfo, me: ECDHe_P384_LiteKeyPair | ECDHe_P384_KeyPair, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
export async function ECDHe_AES_GCM_Encrypt(other: ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
export async function ECDHe_AES_GCM_Encrypt(other: ECDHe_P384_PublicKeyInfo, me: ECDHe_P384_LiteKeyPair, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
export async function ECDHe_AES_GCM_Encrypt(other: ECDHe_P384_PublicKeyInfo | ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair | ECDHe_P384_LiteKeyPair, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>{
let key: ArrayBuffer;
if('publicInfo' in me){
key = await deriveKey(other, me, keySizeBit);
}
else if('publicKey' in other){
key = await deriveKey(other, me, keySizeBit);
}
else{
throw new Error("Invalid argument: may be trying to do ECDH, not ECDHe.");
}
return AES_GCM_Encrypt(key, iv, msg, aad);
}
export async function ECDHe_AES_GCM_Decrypt(other: ECDHe_P384_PublicKeyInfo, me: ECDHe_P384_LiteKeyPair | ECDHe_P384_KeyPair, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
export async function ECDHe_AES_GCM_Decrypt(other: ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
export async function ECDHe_AES_GCM_Decrypt(other: ECDHe_P384_PublicKeyInfo, me: ECDHe_P384_LiteKeyPair, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
export async function ECDHe_AES_GCM_Decrypt(other: ECDHe_P384_PublicKeyInfo | ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair | ECDHe_P384_LiteKeyPair, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>{
let key: ArrayBuffer;
if('publicInfo' in me){
key = await deriveKey(other, me, keySizeBit);
}
else if('publicKey' in other){
key = await deriveKey(other, me, keySizeBit);
}
else{
throw new Error("Invalid argument: may be trying to do ECDH, not ECDHe.");
}
return AES_GCM_Decrypt(key, iv, ciphertext, aad);
}
+32
View File
@@ -0,0 +1,32 @@
import { ECDSA_sign_bson, ECDSA_verify_bson } from "./ECDSA.js";
import { genKey } from "./ECKey.js";
import { randomBytes } from "./utils.js";
test('basic sign', async () => {
const [signKey, verifyKey] = await genKey();
const misVerifyKey = Buffer.alloc(verifyKey.length);
verifyKey.copy(misVerifyKey);
misVerifyKey[misVerifyKey.length - 2] ^= 1; //1bit
const msg = randomBytes(100);
const misMsg = Buffer.alloc(msg.length);
msg.copy(misMsg);
misMsg[1] ^= 1;
const signature = await ECDSA_sign_bson(signKey, msg);
expect(signature.length).toEqual(96);
const misSignature = Buffer.alloc(signature.length);
signature.copy(misSignature);
misSignature[misSignature.length - 2] ^= 3;
const t0P = ECDSA_verify_bson(verifyKey, msg, signature);
const f1P = ECDSA_verify_bson(misVerifyKey, msg, signature);
const f2P = ECDSA_verify_bson(verifyKey, misMsg, signature);
const f3P = ECDSA_verify_bson(verifyKey, msg, misSignature);
expect(await t0P).toEqual(true);
expect(await f1P).toEqual(false);
expect(await f2P).toEqual(false);
expect(await f3P).toEqual(false);
})
+81
View File
@@ -0,0 +1,81 @@
import { BSON } from "bson";
import { type Bsonifiable, bsonify, type Jsonifiable, jsonify } from "@sammo/util";
import { importSignKey, importVerifyKey } from "./ECKey.js";
import type { ECDSASignature, ECDSA_PKCS8_P384_SignKey, ECDSA_P384_VerifyKey, ECDSASignatureBSON, ECDSASignatureJSON, ECDSASignatureRaw } from "./RawTypes.js";
import { isBufferSource } from './types.js';
import { isArray, isDate, isObject } from "lodash-es";
// 간편하게 저장하기 위해서 그냥! 매번 복잡한 일을 하도록 하자
const subtle = globalThis.crypto.subtle;
const hashName: EcdsaParams = {
name: 'ECDSA',
hash: 'SHA-512'
}
export async function ECDSA_sign(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource): Promise<ECDSASignatureRaw> {
const subtleSignKeyP = importSignKey(sign_key);
return Buffer.from(await subtle.sign(hashName, await subtleSignKeyP, msg));
}
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Bsonifiable): Promise<ECDSASignatureBSON>
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource): Promise<ECDSASignatureRaw>
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Bsonifiable | BufferSource): Promise<ECDSASignatureRaw | ECDSASignatureBSON> {
if (!isBufferSource(msg)) {
msg = BSON.serialize(bsonify(msg as Record<string, string>));
return await ECDSA_sign(sign_key, msg) as ECDSASignatureBSON;
}
return ECDSA_sign(sign_key, msg);
}
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Bsonifiable): Promise<ECDSASignatureJSON>
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource): Promise<ECDSASignatureRaw>
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Jsonifiable | BufferSource): Promise<ECDSASignatureRaw | ECDSASignatureJSON> {
if (!isBufferSource(msg)) {
msg = Buffer.from(JSON.stringify(jsonify(msg as Record<string, string>)), 'utf-8');
return await ECDSA_sign(sign_key, msg) as ECDSASignatureJSON;
}
return ECDSA_sign(sign_key, msg);
}
export async function ECDSA_verify(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource, sign: ECDSASignature): Promise<boolean> {
try {
const subtleVerifyKeyP = importVerifyKey(verify_key);
return await subtle.verify(hashName, await subtleVerifyKeyP, sign, msg);
}
catch (e) {
return false;
}
}
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: Bsonifiable, sign: ECDSASignatureBSON): Promise<boolean>;
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource, sign: ECDSASignatureRaw): Promise<boolean>;
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: Bsonifiable | BufferSource, sign: ECDSASignatureBSON | ECDSASignatureRaw): Promise<boolean> {
try {
if (!isBufferSource(msg)) {
msg = BSON.serialize(bsonify(msg as Record<string, string>));
}
return ECDSA_verify(verify_key, msg, sign);
}
catch (e) {
return false;
}
}
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: Bsonifiable, sign: ECDSASignatureJSON): Promise<boolean>;
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource, sign: ECDSASignatureRaw): Promise<boolean>;
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: Jsonifiable | BufferSource, sign: ECDSASignatureJSON | ECDSASignatureRaw): Promise<boolean> {
try {
if (!isBufferSource(msg)) {
const x = JSON.stringify(jsonify(msg as Record<string, string>));
msg = Buffer.from(x, 'utf-8');
}
return ECDSA_verify(verify_key, msg, sign);
}
catch (e) {
return false;
}
}
+35
View File
@@ -0,0 +1,35 @@
import { genKey, verifyKeyFromSignKey, pemFromVerifyKey, importVerifyKey, importSignKey, pemFromSignKey } from "./ECKey.js";
import { TypeLength } from "./TypeLength.js";
import { decodePEM } from "./utils.js";
test('gen_key', async () => {
const [signKey, verifyKey] = await genKey();
expect(signKey.length).toEqual(TypeLength.ECDSA_PKCS8_P384_SignKey);
expect(verifyKey.length).toEqual(TypeLength.ECDSA_SPKI_P384_VerifyKey);
const pemVerifyKey = await pemFromVerifyKey(verifyKey);
const verifyKey2 = decodePEM(pemVerifyKey, 'PUBLIC KEY')[0];
expect(verifyKey).toEqual(verifyKey2);
});
test('convert', async () => {
const [signKey, verifyKey] = await genKey();
const newVerifyKey = await verifyKeyFromSignKey(signKey);
expect(verifyKey).toEqual(newVerifyKey);
})
test('import sign_key', async() => {
const [signKey, verifyKey] = await genKey();
const pemSignKey = pemFromSignKey(signKey);
const signKey2 = decodePEM(pemSignKey, 'EC PRIVATE KEY')[0];
const signKeyObj = await importSignKey(signKey, true);
const signKeyObj2 = await importSignKey(signKey2, true);
expect(signKeyObj).toEqual(signKeyObj2);
})
+62
View File
@@ -0,0 +1,62 @@
import type { ECDSA_PKCS8_P384_SignKey, ECDSA_P384_VerifyKey } from "./RawTypes.js";
import { encodePEM } from "./utils.js";
// 간편하게 저장하기 위해서 그냥! 매번 복잡한 일을 하도록 하자
const subtle = globalThis.crypto.subtle;
export const curveName: EcKeyGenParams | EcKeyImportParams = {
name: 'ECDSA',
namedCurve: 'P-384'
}
export async function importSignKey(signKey: ECDSA_PKCS8_P384_SignKey, allowExport?: boolean): Promise<CryptoKey> {
return await subtle.importKey('pkcs8', signKey, curveName, allowExport ?? false, ['sign']);
}
export async function importVerifyKeyRaw(verifyKey: ECDSA_P384_VerifyKey, allowExport?: boolean): Promise<CryptoKey> {
return await subtle.importKey('raw', verifyKey, curveName, allowExport ?? false, ['verify'])
}
export async function exportVerifyKeyRaw(verifyKey: ECDSA_P384_VerifyKey): Promise<Buffer>{
const key = await importVerifyKeyRaw(verifyKey);
return Buffer.from(await subtle.exportKey('raw', key));
}
export async function importVerifyKey(verifyKey: ECDSA_P384_VerifyKey, allowExport?: boolean): Promise<CryptoKey> {
return await subtle.importKey('spki', verifyKey, curveName, allowExport ?? false, ['verify'])
}
export async function genKey(): Promise<[ECDSA_PKCS8_P384_SignKey, ECDSA_P384_VerifyKey]> {
const keyPair = await subtle.generateKey(curveName, true, ['sign', 'verify']);
const signKey = subtle.exportKey('pkcs8', keyPair.privateKey);
const verifyKey = subtle.exportKey('spki', keyPair.publicKey);
return [Buffer.from(await signKey), Buffer.from(await verifyKey)];
}
export async function pemFromVerifyKey(verifyKey: ECDSA_P384_VerifyKey): Promise<string> {
const subtleVerifyKey = await importVerifyKey(verifyKey, true);
const spkiRaw = await subtle.exportKey('spki', subtleVerifyKey)
return encodePEM(Buffer.from(spkiRaw), 'PUBLIC KEY');
}
export function pemFromSignKey(signKey: ECDSA_PKCS8_P384_SignKey): string {
return encodePEM(signKey, 'EC PRIVATE KEY');
}
export async function verifyKeyFromSignKey(signKey: ECDSA_PKCS8_P384_SignKey): Promise<ECDSA_P384_VerifyKey> {
const subtleSignKey = await importSignKey(signKey, true);
const jwk = await subtle.exportKey('jwk', subtleSignKey);
delete jwk.d;
delete jwk.dp;
delete jwk.dq;
delete jwk.q;
delete jwk.qi;
jwk.key_ops = ["verify"];
const subtleVerifyKey = await subtle.importKey('jwk', jwk, curveName, true, ['verify']);
const verifyKey = await subtle.exportKey('spki', subtleVerifyKey);
return Buffer.from(verifyKey);
}
+262
View File
@@ -0,0 +1,262 @@
import type { RNG } from "./RNG.js";
import { sha512 } from './SHA2.js';
import { convertBytesLikeToUint8Array } from "@sammo/util/converter";
import type { BytesLike } from "@sammo/util";
import { delay } from "@sammo/util";
const maxRngSupportBit = 53;
const maxInt = 0x1f_ffff_ffff_ffff; // NOTE: b 0, 10000110011, 11...11
const maxIntMore1 = 0x20_0000_0000_0000n; //NOTE: b 0, 10000110100, 00...00
const maxIntMore1f = Number(maxIntMore1);
export const bufferByteSize = 512 / 8; //SHA512
const intBitMapMask = new Map([
[0x1n, 1],
[0x3n, 2],
[0x7n, 3],
[0xfn, 4],
[0x1fn, 5],
[0x3fn, 6],
[0x7fn, 7],
[0xffn, 8],
[0x1ffn, 9],
[0x3ffn, 10],
[0x7ffn, 11],
[0xfffn, 12],
[0x1fffn, 13],
[0x3fffn, 14],
[0x7fffn, 15],
[0xffffn, 16],
[0x1ffffn, 17],
[0x3ffffn, 18],
[0x7ffffn, 19],
[0xfffffn, 20],
[0x1fffffn, 21],
[0x3fffffn, 22],
[0x7fffffn, 23],
[0xffffffn, 24],
[0x1ffffffn, 25],
[0x3ffffffn, 26],
[0x7ffffffn, 27],
[0xfffffffn, 28],
[0x1fffffffn, 29],
[0x3fffffffn, 30],
[0x7fffffffn, 31],
[0xffffffffn, 32],
[0x1ffffffffn, 33],
[0x3ffffffffn, 34],
[0x7ffffffffn, 35],
[0xfffffffffn, 36],
[0x1fffffffffn, 37],
[0x3fffffffffn, 38],
[0x7fffffffffn, 39],
[0xffffffffffn, 40],
[0x1ffffffffffn, 41],
[0x3ffffffffffn, 42],
[0x7ffffffffffn, 43],
[0xfffffffffffn, 44],
[0x1fffffffffffn, 45],
[0x3fffffffffffn, 46],
[0x7fffffffffffn, 47],
[0xffffffffffffn, 48],
[0x1ffffffffffffn, 49],
[0x3ffffffffffffn, 50],
[0x7ffffffffffffn, 51],
[0xfffffffffffffn, 52],
[0x1fffffffffffffn, 53],
]);
function calcBitMask(n: bigint): bigint {
n |= n >> 1n;
n |= n >> 2n;
n |= n >> 4n;
n |= n >> 8n;
n |= n >> 16n;
n |= n >> 32n;
return n;
}
export class LiteHashDRBG implements RNG {
protected buffer!: ArrayBuffer;
protected bufferIdx!: number;
protected hq: DataView;
protected hqIdxPos: number;
protected ready: Promise<void>;
public constructor(protected seed: BytesLike, protected stateIdx = 0, bufferIdx = 0) {
if (bufferIdx < 0) {
throw new Error(`bufferIdx ${bufferIdx} < 0`);
}
if (bufferIdx >= bufferByteSize) {
throw new Error(`bufferidx ${bufferIdx} >= ${bufferByteSize}`);
}
if (stateIdx < 0) {
throw new Error(`stateIdx ${stateIdx} < 0`);
}
const seedU8 = convertBytesLikeToUint8Array(seed);
const hqBuffer = new ArrayBuffer(seedU8.byteLength + 4);
const hqU8 = new Uint8Array(hqBuffer);
hqU8.set(seedU8, 0);
this.hq = new DataView(hqBuffer);
this.hqIdxPos = seedU8.byteLength;
this.ready = this.genNextBlock();
this.bufferIdx = bufferIdx;
}
protected async genNextBlock(): Promise<void> {
this.bufferIdx = 0;
this.hq.setUint32(this.hqIdxPos, this.stateIdx, true);
this.stateIdx += 1;
const digest = await sha512(this.hq.buffer);
this.buffer = digest;
}
public getMaxInt(): number {
return maxInt;
}
public async nextBytes(bytes: number, baseBytes?: number): Promise<Uint8Array> {
bytes |= 0;
if (bytes <= 0) {
throw new Error(`${bytes} <= 0`);
}
const ticket = this.ready;
let waiter: Promise<Uint8Array | undefined> = Promise.resolve(undefined);
let nextBlockWait: (() => void) | null = (() => { throw 'something wrong'; });
this.ready = new Promise((resolve, reject) => {
waiter = (async () => {
await ticket;
nextBlockWait = resolve;
if (this.bufferIdx + bytes <= bufferByteSize) {
if (baseBytes === undefined || bytes >= baseBytes) {
const result = this.buffer.slice(this.bufferIdx, this.bufferIdx + bytes);
this.bufferIdx += bytes;
if (this.bufferIdx === bufferByteSize) {
nextBlockWait = null;
this.genNextBlock().then(resolve, reject);
}
return new Uint8Array(result);
}
const resultBuffer = new ArrayBuffer(Math.max(bytes, baseBytes ?? 0));
const result = new Uint8Array(resultBuffer);
result.set(new Uint8Array(this.buffer, this.bufferIdx, bytes));
this.bufferIdx += bytes;
if (this.bufferIdx === bufferByteSize) {
nextBlockWait = null;
this.genNextBlock().then(resolve, reject);
}
return result;
}
const resultBuffer = new ArrayBuffer(baseBytes ? Math.max(bytes, baseBytes) : bytes);
const result = new Uint8Array(resultBuffer);
result.set(new Uint8Array(this.buffer, this.bufferIdx));
let offset = bufferByteSize - this.bufferIdx;
let remain = bytes - offset;
while (remain > bufferByteSize) {
await this.genNextBlock();
result.set(new Uint8Array(this.buffer), offset);
offset += bufferByteSize;
remain -= bufferByteSize;
}
if (remain === 0) {
nextBlockWait = null;
this.genNextBlock().then(resolve, reject);
return result;
}
await this.genNextBlock();
result.set(new Uint8Array(this.buffer, 0, remain), offset);
this.bufferIdx = remain;
return result;
})();
});
//이 코드를 통해 Promise 내부가 실행된다
await delay(0);
const nextBlock = await waiter;
if (nextBlockWait) {
nextBlockWait();
}
return nextBlock as Uint8Array;
}
public async nextBits(bits: number, baseBytes?: number): Promise<Uint8Array> {
await this.ready;
bits |= 0;
const bytes = (bits + 7) >> 3;
const headBits = bits & 0x7;
const result = await this.nextBytes(bytes, baseBytes);
if (headBits === 0) {
return result;
}
result[bytes - 1] &= 0xff >> (8 - headBits);
return result;
}
protected async _nextInt(bits: number): Promise<bigint> {
const buffer = await this.nextBits(bits, 8);
const dataView = new DataView(buffer.buffer);
return dataView.getBigUint64(0, true);
}
public async nextInt(max?: number): Promise<number> {
if (max === undefined || max === maxInt) {
return Number(await this._nextInt(maxRngSupportBit));
}
if (max > maxInt) {
throw new Error('Over max int');
}
if (max === 0) {
return 0;
}
if (max < 0) {
return -this.nextInt(-max);
}
const mask = calcBitMask(BigInt(max));
const bits = intBitMapMask.get(mask) as number;
let n = Number(this._nextInt(bits));
while (n > max) {
n = Number(this._nextInt(bits));
}
return n;
}
public async nextFloat1(): Promise<number> {
// eslint-disable-next-line no-constant-condition
while (true) {
const nInt = await this._nextInt(maxRngSupportBit + 1);
if (nInt < maxIntMore1) {
return Number(nInt) / maxIntMore1f;
}
if (nInt === maxIntMore1) {
return 1;
}
}
}
public static build(seed: BytesLike, stateIdx = 0): LiteHashDRBG {
return new LiteHashDRBG(seed, stateIdx);
}
}
+48
View File
@@ -0,0 +1,48 @@
const subtle = globalThis.crypto.subtle;
export type ValidEncAlg = 'AES256-GCM' | 'AES128-GCM' | 'AES256-CBC' | 'AES128-CBC';
const encAlgMap: Record<ValidEncAlg, AesDerivedKeyParams> = {
'AES256-GCM': {
name: 'AES-GCM',
length: 256,
},
'AES128-GCM' : {
name: 'AES-GCM',
length: 128,
},
'AES256-CBC' : {
name: 'AES-CBC',
length: 256,
},
'AES128-CBC' : {
name: 'AES-CBC',
length: 128,
},
}
export async function PBKDF2_SHA512(password: string, salt: BufferSource, alg: ValidEncAlg = 'AES256-GCM', iterations = 600000): Promise<CryptoKey> {
const param: Pbkdf2Params = {
name: 'PBKDF2',
hash: 'SHA-512',
salt: salt,
iterations,
};
const encoder = new TextEncoder();
const baseKey = await subtle.importKey(
"raw",
encoder.encode(password),
{ name: "PBKDF2" },
false,
["deriveBits", "deriveKey"]
);
return await subtle.deriveKey(
param,
baseKey,
encAlgMap[alg],
true,
["encrypt", "decrypt"]
);
}
+13
View File
@@ -0,0 +1,13 @@
export interface RNG {
/**
* nextInt()가 반환 가능한 최댓값
*/
getMaxInt(): number;
nextBytes(bytes: number): Promise<Uint8Array>;
nextBits(bits: number): Promise<Uint8Array>;
nextInt(max?: number): Promise<number>;
nextFloat1(): Promise<number>;
}
+144
View File
@@ -0,0 +1,144 @@
import type { RNG } from './RNG.js';
export class RandUtil {
constructor(protected rng: RNG) {
}
public nextFloat1(): Promise<number> {
return this.rng.nextFloat1();
}
public async nextRange(min: number, max: number): Promise<number> {
const range = max - min;
return await this.nextFloat1() * (range) + min;
}
public async nextRangeInt(min: number, max: number): Promise<number> {
const range = max - min;
return await this.rng.nextInt(range) + min;
}
public nextInt(max?: number): Promise<number> {
return this.rng.nextInt(max);
}
public async nextBit(): Promise<boolean> {
const view = new DataView(await this.rng.nextBits(1) as ArrayBufferLike);
return view.getUint8(0) != 0;
}
public async nextBool(prob = 0.5): Promise<boolean> {
if (prob >= 1) {
return true;
}
if (prob === 0.5){
return this.nextBit();
}
if (prob <= 0){
return false;
}
return await this.nextFloat1() < prob;
}
public async shuffle<T>(srcArray: T[]): Promise<T[]> {
const cnt = srcArray.length;
if(cnt === 0){
return [];
}
if (cnt > this.rng.getMaxInt()) {
throw 'Invalid random int range';
}
const result: T[] = Array.from(srcArray);
for (let srcIdx = 0; srcIdx < cnt; srcIdx += 1) {
const destIdx = await this.rng.nextInt(cnt - srcIdx - 1) + srcIdx;
if(srcIdx === destIdx){
continue;
}
[result[srcIdx], result[destIdx]] = [result[destIdx], result[srcIdx]];
}
return result;
}
//Object는 integer key에 예외가 있어 shuffleAssoc은 없음
public async choice<T>(items: T[] | Record<string | number, T> | Set<T>): Promise<T> {
if (items instanceof Array) {
if(items.length === 0){
throw new Error('Empty items');
}
const idx = await this.rng.nextInt(items.length - 1);
return items[idx];
}
if (items instanceof Set) {
return this.choice(Array.from(items.values()));
}
return items[await this.choice(Array.from(Object.keys(items)))];
}
public async choiceUsingWeight(items: Record<string | number, number>): Promise<string | number> {
if(Object.keys(items).length === 0){
throw new Error('Empty items');
}
let sum = 0;
for (const value of Object.values(items)) {
if (value <= 0) {
continue;
}
sum += value;
}
let rd = await this.nextFloat1() * sum;
for (const [item, value] of Object.entries(items)) {
if (value <= 0) {
if (rd <= 0) {
return item;
}
continue;
}
if (rd <= value) {
return item;
}
rd -= value;
}
throw new Error('Unreacheable');
}
public async choiceUsingWeightPair<T>(items: [T, number][]): Promise<T> {
if(items.length === 0){
throw new Error('Empty items');
}
let sum = 0;
for (const [, value] of items) {
if (value <= 0) {
continue;
}
sum += value;
}
let rd = await this.nextFloat1() * sum;
for (const [item, value] of items) {
if (value <= 0) {
if (rd <= 0) {
return item;
}
continue;
}
if (rd <= value) {
return item;
}
rd -= value;
}
throw new Error('Unreacheable');
}
}
+50
View File
@@ -0,0 +1,50 @@
import type { WrappedBuffer } from "@sammo/util";
export interface ECDSA_PKCS8_P384_SignKey extends WrappedBuffer{
_w_type?: "ECDSA_PKCS8_P384_SignKey";
}
export interface ECDSA_P384_VerifyKey extends WrappedBuffer{
_w_type?: "ECDSA_P384_VerifyKey";
}
export interface ECDSASignatureRaw extends WrappedBuffer{
_w_type?: "ECDSASignature";
}
export interface ECDSASignatureBSON extends WrappedBuffer{
_w_type?: "ECDSASignatureBSON";
}
export interface ECDSASignatureJSON extends WrappedBuffer{
_w_type?: "ECDSASignatureJSON";
}
export type ECDSASignature = ECDSASignatureRaw | ECDSASignatureBSON | ECDSASignatureJSON;
export interface ECDHe_P384_PublicKey extends WrappedBuffer{
_w_type?: "ECDHe_P384_PublicKey";
}
export interface ECDHe_P384_PrivateKey extends WrappedBuffer{
_w_type?: "ECDHe_P384_PrivateKey";
}
export interface ECDHe_P384_LiteKeyPair {
publicKey: ECDHe_P384_PublicKey;
privateKey: ECDHe_P384_PrivateKey;
}
export interface ECDHe_P384_PublicKeyInfo {
publicKey: ECDHe_P384_PublicKey;
verifyKey: ECDSA_P384_VerifyKey;
sign: ECDSASignatureRaw; //sign of publicKey
}
export interface ECDHe_P384_KeyPair{
publicInfo: ECDHe_P384_PublicKeyInfo;
privateKey: ECDHe_P384_PrivateKey;
}
export {
TypeLength
} from "./TypeLength.js";
+10
View File
@@ -0,0 +1,10 @@
import { sha256, sha512 } from "./SHA2.js";
test('sha2', async () => {
const msg = Buffer.from('hello');
const answer256 = Buffer.from('2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824', 'hex');
const answer512 = Buffer.from('9B71D224BD62F3785D96D46AD3EA3D73319BFBC2890CAADAE2DFF72519673CA72323C3D99BA5C11D7C7ACC6E14B8C5DA0C4663475C2E5C3ADEF46F73BCDEC043', 'hex');
expect(await sha256(msg)).toEqual(answer256.buffer);
expect(await sha512(msg)).toEqual(answer512.buffer);
});
+19
View File
@@ -0,0 +1,19 @@
import { isBufferSource } from "./types.js";
import { BSON } from "bson";
import { type Bsonifiable, bsonify } from "@sammo/util";
import { isArray, isDate, isObject } from "lodash-es";
const subtle = globalThis.crypto.subtle;
export async function sha256(msg: Bsonifiable | BufferSource): Promise<ArrayBuffer> {
if (!isBufferSource(msg)) {
msg = BSON.serialize(bsonify(msg));
}
return await subtle.digest('SHA-256', msg);
}
export async function sha512(msg: Bsonifiable | BufferSource): Promise<ArrayBuffer> {
if (!isBufferSource(msg)) {
msg = BSON.serialize(bsonify(msg));
}
return await subtle.digest('SHA-512', msg);
}
+6
View File
@@ -0,0 +1,6 @@
export enum TypeLength {
//ECDSA P384 서명
ECDSA_PKCS8_P384_SignKey = 185,
ECDSA_SPKI_P384_VerifyKey = 120,
ECDSASignature = 96,
}
+108
View File
@@ -0,0 +1,108 @@
import { InvalidArgument } from '@sammo/util';
export class InvalidVType extends InvalidArgument {
public override name = 'InvalidVType';
}
export class InvalidArgumentBufferSize extends InvalidArgument {
public override name = 'InvalidArgumentBufferSize';
}
export class RuntimeError extends Error {
public override name = 'RuntimeError';
constructor(public override message: string = '') {
super(message);
}
override toString(): string {
if (this.message) {
return this.name + ': ' + this.message;
}
else {
return this.name;
}
}
}
export class NotNullExpected extends RuntimeError {
public override name = 'NotNullExpected';
}
export class PrivilegedGenViolation extends RuntimeError {
public override name = 'PrivilegedGenViolation';
}
export type MergeableBuffer = MergeableBuffer[] | Buffer;
export function calcMergeableBuffer(item: MergeableBuffer): number {
if (item instanceof Buffer) {
return item.byteLength;
}
let bufferSize = 0;
for (const subItem of item) {
bufferSize += calcMergeableBuffer(subItem);
}
return bufferSize;
}
export function mergeBuffer(...buffers: MergeableBuffer[]): Buffer {
if (buffers.length == 0) {
return Buffer.alloc(0);
}
if (buffers.length == 1) {
const item = buffers[0];
if (item instanceof Buffer) {
return item;
}
}
const fillBuffer = function (item: MergeableBuffer, bufferIdx: number): number {
if (item instanceof Buffer) {
result.set(item, bufferIdx);
bufferIdx += item.byteLength;
return bufferIdx;
}
for (const subItem of item) {
bufferIdx = fillBuffer(subItem, bufferIdx);
}
return bufferIdx;
}
const bufferSize = calcMergeableBuffer(buffers);
const result = Buffer.alloc(bufferSize);
fillBuffer(buffers, 0);
return result;
}
type ErrType<T> = { new(msg?: string): T }
type Nullable<T> = T | null | undefined
export function unwrap<T>(result: Nullable<T>): T {
if (result === null || result === undefined) {
throw new NotNullExpected();
}
return result;
}
export function unwrap_err<T, ErrT extends Error>(result: Nullable<T>, errType: ErrType<ErrT>, errMsg?: string): T {
if (result === null || result === undefined) {
throw new errType(errMsg);
}
return result;
}
export * as AES from './AES.js';
export * as ECDSA from './ECDSA.js';
export * as ECKey from './ECKey.js';
export * as PBKDF2 from './PBKDF2.js';
export * as SHA2 from './SHA2.js';
export * as ECDHe from './ECDHe.js';
export * as ECDHe_AES from './ECDHe_AES.js';
export * as RawTypes from './RawTypes.js';
export * from './utils.js';
export * from './types.js';
export { TypeLength } from './TypeLength.js';
export * from "./LiteHashDRBG.js"
+13
View File
@@ -0,0 +1,13 @@
export function isBufferSource(obj: unknown): obj is BufferSource {
if (obj instanceof ArrayBuffer){
return true;
}
if('SharedArrayBuffer' in globalThis && obj instanceof SharedArrayBuffer){
return true;
}
if (ArrayBuffer.isView(obj)){
return true;
}
return false;
}
+73
View File
@@ -0,0 +1,73 @@
import type { WrappedBuffer } from "@sammo/util";
const crypto = globalThis.crypto;
export function randomBytes(length: number): Buffer {
const buffer = Buffer.alloc(length);
crypto.getRandomValues(buffer);
return buffer;
}
//TODO: 필요할때마다 확장
export type ValidPEMType = 'PUBLIC KEY' | 'EC PRIVATE KEY' | 'CERTIFICATE';
/**
* PEM string에 내부 타입으로 WrappedBuffer를 보관한 형태
*/
// eslint-disable-next-line @typescript-eslint/ban-types
export type PEMString<T extends Buffer, S extends ValidPEMType> = string & {
/** 타입구분자. 항상 undefined일 것이다 */
_pem_b_type?: T;
_pem_type?: S;
}
export function encodePEM<T extends WrappedBuffer, S extends ValidPEMType>(data: T, pemType: S): PEMString<T, S>;
export function encodePEM(data: Buffer, pemType: ValidPEMType): string;
export function encodePEM(data: Buffer, pemType: ValidPEMType): string {
const base64text = data.toString('base64');
const splitText = base64text.match(/.{1,64}/g)?.join('\n') ?? '';
return `-----BEGIN ${pemType}-----
${splitText}
-----END ${pemType}-----
`;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type InferPEMType<T extends PEMString<any, any>> = Exclude<undefined, T['_pem_type']>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type InferPEMBuffer<T extends PEMString<any, any>> = Exclude<undefined, T['_pem_b_type']>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function decodePEM<T extends PEMString<any, any>>(pem: T, pemType: InferPEMType<T>): InferPEMBuffer<T>[];
export function decodePEM(pem: string, pemType?: ValidPEMType): Buffer[];
export function decodePEM(pem: string, pemType?: ValidPEMType): Buffer[] {
const tag = pemType ?? "[A-Z0-9 ]+";
const pattern = new RegExp(`-{5}BEGIN ${tag}-{5}([a-zA-Z0-9=+\\/\\n\\r]+)-{5}END ${tag}-{5}`, "g");
const res: Buffer[] = [];
let matches: RegExpExecArray | null = null;
// eslint-disable-next-line no-cond-assign
while (matches = pattern.exec(pem)) {
const base64 = matches[1]
.replace(/\r/g, "")
.replace(/\n/g, "");
res.push(Buffer.from(base64, 'base64'));
}
return res;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function decodeSinglePEM<T extends PEMString<any, any>>(pem: T, pemType: InferPEMType<T>): InferPEMBuffer<T>;
export function decodeSinglePEM(pem: string, pemType?: ValidPEMType): Buffer;
export function decodeSinglePEM(pem: string, pemType?: ValidPEMType): Buffer {
const res = decodePEM(pem, pemType);
if (res.length != 1) {
throw new Error("invalid pem");
}
return res[0];
}