1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

Move to libs

This commit is contained in:
Hinton
2022-06-03 16:24:40 +02:00
parent 28d15bfe2a
commit d7492e3cf3
878 changed files with 0 additions and 0 deletions

15
libs/node/jest.config.js Normal file
View File

@@ -0,0 +1,15 @@
const { pathsToModuleNameMapper } = require("ts-jest");
const { compilerOptions } = require("./tsconfig");
module.exports = {
preset: "ts-jest",
testMatch: ["**/+(*.)+(spec).+(ts)"],
setupFilesAfterEnv: ["<rootDir>/spec/test.ts"],
collectCoverage: true,
coverageReporters: ["html", "lcov"],
coverageDirectory: "coverage",
moduleNameMapper: pathsToModuleNameMapper(compilerOptions?.paths || {}, {
prefix: "<rootDir>/",
}),
};

1183
libs/node/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

38
libs/node/package.json Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "@bitwarden/jslib-node",
"version": "0.0.0",
"description": "Common code used across Bitwarden JavaScript projects.",
"keywords": [
"bitwarden"
],
"author": "Bitwarden Inc.",
"homepage": "https://bitwarden.com",
"repository": {
"type": "git",
"url": "https://github.com/bitwarden/jslib"
},
"license": "GPL-3.0",
"scripts": {
"clean": "rimraf dist/**/*",
"build": "npm run clean && tsc",
"build:watch": "npm run clean && tsc -watch"
},
"devDependencies": {
"@types/inquirer": "^7.3.1",
"@types/lowdb": "^1.0.10",
"@types/node": "^16.11.12",
"@types/node-fetch": "^2.5.10",
"rimraf": "^3.0.2",
"typescript": "4.3.5"
},
"dependencies": {
"@bitwarden/jslib-common": "file:../common",
"chalk": "^4.1.1",
"commander": "7.2.0",
"form-data": "4.0.0",
"https-proxy-agent": "5.0.0",
"inquirer": "8.0.0",
"lowdb": "1.0.0",
"node-fetch": "^2.6.1"
}
}

View File

@@ -0,0 +1,45 @@
import {
interceptConsole,
restoreConsole,
} from "jslib-common/../spec/services/consolelog.service.spec";
import { ConsoleLogService } from "jslib-node/cli/services/consoleLog.service";
let caughtMessage: any = {};
describe("CLI Console log service", () => {
let logService: ConsoleLogService;
beforeEach(() => {
caughtMessage = {};
interceptConsole(caughtMessage);
logService = new ConsoleLogService(true);
});
afterAll(() => {
restoreConsole();
});
it("should redirect all console to error if BW_RESPONSE env is true", () => {
process.env.BW_RESPONSE = "true";
logService.debug("this is a debug message");
expect(caughtMessage).toMatchObject({
error: { 0: "this is a debug message" },
});
});
it("should not redirect console to error if BW_RESPONSE != true", () => {
process.env.BW_RESPONSE = "false";
logService.debug("debug");
logService.info("info");
logService.warning("warning");
logService.error("error");
expect(caughtMessage).toMatchObject({
log: { 0: "info" },
warn: { 0: "warning" },
error: { 0: "error" },
});
});
});

View File

@@ -0,0 +1,518 @@
import { Utils } from "jslib-common/misc/utils";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
import { NodeCryptoFunctionService } from "jslib-node/services/nodeCryptoFunction.service";
const RsaPublicKey =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" +
"4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP" +
"RQ1H7iNHVZz3K8Db9GCSKPeC8MbW6gVCzb15esCe1gGzg6wkMuWYDFYPoh/oBqcIqrGah7firqB1nDedzEjw32heP2DAffVN" +
"084iTDjiWrJNUxBJ2pDD5Z9dT3MzQ2s09ew1yMWK2z37rT3YerC7OgEDmo3WYo3xL3qYJznu3EO2nmrYjiRa40wKSjxsTlUc" +
"xDF+F0uMW8oR9EMUHgepdepfAtLsSAQIDAQAB";
const RsaPrivateKey =
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS8Hz" +
"YUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86L" +
"nhD56A9FDUfuI0dVnPcrwNv0YJIo94LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfaF4/" +
"YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6AQOajdZijfEvepgnOe7cQ7aeatiOJFrjTApK" +
"PGxOVRzEMX4XS4xbyhH0QxQeB6l16l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq92q" +
"Buwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tPdr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapj" +
"WpxEF+11x7r+wM+0xRZQ8sNFYG46aPfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLXUIh" +
"5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTRbuDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk" +
"1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxucvOU" +
"BeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjAhCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIf" +
"TFKC/hDk6FKZlgwvupWYJyU9RkyfstPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQYUcU" +
"q4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vszv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVv" +
"q1UTXIeQcQnoY5lGHJl3K8mbS3TnXE6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEPjNX" +
"5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBezMRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1" +
"eLLGd7YV0H+J3fgNc7gGWK51hOrF9JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXgAoE" +
"Z18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGpIs3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8" +
"+tPVgppLcG0+tMdLjigFQiDUQk2y3WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEzXKZ" +
"BokBGnjFnTnKcs7nv/O8=";
const Sha1Mac = "4d4c223f95dc577b665ec4ccbcb680b80a397038";
const Sha256Mac = "6be3caa84922e12aaaaa2f16c40d44433bb081ef323db584eb616333ab4e874f";
const Sha512Mac =
"21910e341fa12106ca35758a2285374509326c9fbe0bd64e7b99c898f841dc948c58ce66d3504d8883c" +
"5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca";
describe("NodeCrypto Function Service", () => {
describe("pbkdf2", () => {
const regular256Key = "pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I=";
const utf8256Key = "yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I=";
const unicode256Key = "ZdeOata6xoRpB4DLp8zHhXz5kLmkWtX5pd+TdRH8w8w=";
const regular512Key =
"liTi/Ke8LPU1Qv+Vl7NGEVt/XMbsBVJ2kQxtVG/Z1/JFHFKQW3ZkI81qVlwTiCpb+cFXzs+57" +
"eyhhx5wfKo5Cg==";
const utf8512Key =
"df0KdvIBeCzD/kyXptwQohaqUa4e7IyFUyhFQjXCANu5T+scq55hCcE4dG4T/MhAk2exw8j7ixRN" +
"zXANiVZpnw==";
const unicode512Key =
"FE+AnUJaxv8jh+zUDtZz4mjjcYk0/PZDZm+SLJe3XtxtnpdqqpblX6JjuMZt/dYYNMOrb2+mD" +
"L3FiQDTROh1lg==";
testPbkdf2("sha256", regular256Key, utf8256Key, unicode256Key);
testPbkdf2("sha512", regular512Key, utf8512Key, unicode512Key);
});
describe("hkdf", () => {
const regular256Key = "qBUmEYtwTwwGPuw/z6bs/qYXXYNUlocFlyAuuANI8Pw=";
const utf8256Key = "6DfJwW1R3txgiZKkIFTvVAb7qVlG7lKcmJGJoxR2GBU=";
const unicode256Key = "gejGI82xthA+nKtKmIh82kjw+ttHr+ODsUoGdu5sf0A=";
const regular512Key = "xe5cIG6ZfwGmb1FvsOedM0XKOm21myZkjL/eDeKIqqM=";
const utf8512Key = "XQMVBnxVEhlvjSFDQc77j5GDE9aorvbS0vKnjhRg0LY=";
const unicode512Key = "148GImrTbrjaGAe/iWEpclINM8Ehhko+9lB14+52lqc=";
testHkdf("sha256", regular256Key, utf8256Key, unicode256Key);
testHkdf("sha512", regular512Key, utf8512Key, unicode512Key);
});
describe("hkdfExpand", () => {
const prk16Byte = "criAmKtfzxanbgea5/kelQ==";
const prk32Byte = "F5h4KdYQnIVH4rKH0P9CZb1GrR4n16/sJrS0PsQEn0Y=";
const prk64Byte =
"ssBK0mRG17VHdtsgt8yo4v25CRNpauH+0r2fwY/E9rLyaFBAOMbIeTry+" +
"gUJ28p8y+hFh3EI9pcrEWaNvFYonQ==";
testHkdfExpand("sha256", prk32Byte, 32, "BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD8=");
testHkdfExpand(
"sha256",
prk32Byte,
64,
"BnIqJlfnHm0e/2iB/15cbHyR19ARPIcWRp4oNS22CD9BV+" +
"/queOZenPNkDhmlVyL2WZ3OSU5+7ISNF5NhNfvZA=="
);
testHkdfExpand("sha512", prk64Byte, 32, "uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlk=");
testHkdfExpand(
"sha512",
prk64Byte,
64,
"uLWbMWodSBms5uGJ5WTRTesyW+MD7nlpCZvagvIRXlkY5Pv0sB+" +
"MqvaopmkC6sD/j89zDwTV9Ib2fpucUydO8w=="
);
it("should fail with prk too small", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const f = cryptoFunctionService.hkdfExpand(
Utils.fromB64ToArray(prk16Byte),
"info",
32,
"sha256"
);
await expect(f).rejects.toEqual(new Error("prk is too small."));
});
it("should fail with outputByteSize is too large", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const f = cryptoFunctionService.hkdfExpand(
Utils.fromB64ToArray(prk32Byte),
"info",
8161,
"sha256"
);
await expect(f).rejects.toEqual(new Error("outputByteSize is too large."));
});
});
describe("hash", () => {
const regular1Hash = "2a241604fb921fad12bf877282457268e1dccb70";
const utf81Hash = "85672798dc5831e96d6c48655d3d39365a9c88b6";
const unicode1Hash = "39c975935054a3efc805a9709b60763a823a6ad4";
const regular256Hash = "2b8e96031d352a8655d733d7a930b5ffbea69dc25cf65c7bca7dd946278908b2";
const utf8256Hash = "25fe8440f5b01ed113b0a0e38e721b126d2f3f77a67518c4a04fcde4e33eeb9d";
const unicode256Hash = "adc1c0c2afd6e92cefdf703f9b6eb2c38e0d6d1a040c83f8505c561fea58852e";
const regular512Hash =
"c15cf11d43bde333647e3f559ec4193bb2edeaa0e8b902772f514cdf3f785a3f49a6e02a4b87b3" +
"b47523271ad45b7e0aebb5cdcc1bc54815d256eb5dcb80da9d";
const utf8512Hash =
"035c31a877a291af09ed2d3a1a293e69c3e079ea2cecc00211f35e6bce10474ca3ad6e30b59e26118" +
"37463f20969c5bc95282965a051a88f8cdf2e166549fcdd";
const unicode512Hash =
"2b16a5561af8ad6fe414cc103fc8036492e1fc6d9aabe1b655497054f760fe0e34c5d100ac773d" +
"9f3030438284f22dbfa20cb2e9b019f2c98dfe38ce1ef41bae";
const regularMd5 = "5eceffa53a5fd58c44134211e2c5f522";
const utf8Md5 = "3abc9433c09551b939c80aa0aa3174e1";
const unicodeMd5 = "85ae134072c8d81257933f7045ba17ca";
testHash("sha1", regular1Hash, utf81Hash, unicode1Hash);
testHash("sha256", regular256Hash, utf8256Hash, unicode256Hash);
testHash("sha512", regular512Hash, utf8512Hash, unicode512Hash);
testHash("md5", regularMd5, utf8Md5, unicodeMd5);
});
describe("hmac", () => {
testHmac("sha1", Sha1Mac);
testHmac("sha256", Sha256Mac);
testHmac("sha512", Sha512Mac);
});
describe("compare", () => {
testCompare(false);
});
describe("hmacFast", () => {
testHmac("sha1", Sha1Mac, true);
testHmac("sha256", Sha256Mac, true);
testHmac("sha512", Sha512Mac, true);
});
describe("compareFast", () => {
testCompare(true);
});
describe("aesEncrypt", () => {
it("should successfully encrypt data", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromUtf8ToArray("EncryptMe!");
const encValue = await nodeCryptoFunctionService.aesEncrypt(
data.buffer,
iv.buffer,
key.buffer
);
expect(Utils.fromBufferToB64(encValue)).toBe("ByUF8vhyX4ddU9gcooznwA==");
});
it("should successfully encrypt and then decrypt data", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = await nodeCryptoFunctionService.aesEncrypt(
data.buffer,
iv.buffer,
key.buffer
);
const decValue = await nodeCryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer);
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
describe("aesDecryptFast", () => {
it("should successfully decrypt data", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
const data = "ByUF8vhyX4ddU9gcooznwA==";
const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
const decValue = await nodeCryptoFunctionService.aesDecryptFast(params);
expect(decValue).toBe("EncryptMe!");
});
});
describe("aesDecrypt", () => {
it("should successfully decrypt data", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
const decValue = await nodeCryptoFunctionService.aesDecrypt(
data.buffer,
iv.buffer,
key.buffer
);
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
});
});
describe("rsaEncrypt", () => {
it("should successfully encrypt and then decrypt data", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const pubKey = Utils.fromB64ToArray(RsaPublicKey);
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = await nodeCryptoFunctionService.rsaEncrypt(
data.buffer,
pubKey.buffer,
"sha1"
);
const decValue = await nodeCryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, "sha1");
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
describe("rsaDecrypt", () => {
it("should successfully decrypt data", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const data = Utils.fromB64ToArray(
"A1/p8BQzN9UrbdYxUY2Va5+kPLyfZXF9JsZrjeEXcaclsnHurdxVAJcnbEqYMP3UXV" +
"4YAS/mpf+Rxe6/X0WS1boQdA0MAHSgx95hIlAraZYpiMLLiJRKeo2u8YivCdTM9V5vuAEJwf9Tof/qFsFci3sApdbATkorCT" +
"zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D" +
"/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw=="
);
const decValue = await nodeCryptoFunctionService.rsaDecrypt(
data.buffer,
privKey.buffer,
"sha1"
);
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
});
});
describe("rsaExtractPublicKey", () => {
it("should successfully extract key", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const publicKey = await nodeCryptoFunctionService.rsaExtractPublicKey(privKey.buffer);
expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey);
});
});
describe("rsaGenerateKeyPair", () => {
testRsaGenerateKeyPair(1024);
testRsaGenerateKeyPair(2048);
// Generating 4096 bit keys is really slow with Forge lib.
// Maybe move to something else if we ever want to generate keys of this size.
// testRsaGenerateKeyPair(4096);
});
describe("randomBytes", () => {
it("should make a value of the correct length", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const randomData = await nodeCryptoFunctionService.randomBytes(16);
expect(randomData.byteLength).toBe(16);
});
it("should not make the same value twice", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const randomData = await nodeCryptoFunctionService.randomBytes(16);
const randomData2 = await nodeCryptoFunctionService.randomBytes(16);
expect(
randomData.byteLength === randomData2.byteLength && randomData !== randomData2
).toBeTruthy();
});
});
});
function testPbkdf2(
algorithm: "sha256" | "sha512",
regularKey: string,
utf8Key: string,
unicodeKey: string
) {
const regularEmail = "user@example.com";
const utf8Email = "üser@example.com";
const regularPassword = "password";
const utf8Password = "pǻssword";
const unicodePassword = "😀password🙏";
it("should create valid " + algorithm + " key from regular input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
it("should create valid " + algorithm + " key from utf8 input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000);
expect(Utils.fromBufferToB64(key)).toBe(utf8Key);
});
it("should create valid " + algorithm + " key from unicode input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000);
expect(Utils.fromBufferToB64(key)).toBe(unicodeKey);
});
it("should create valid " + algorithm + " key from array buffer input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(
Utils.fromUtf8ToArray(regularPassword).buffer,
Utils.fromUtf8ToArray(regularEmail).buffer,
algorithm,
5000
);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
}
function testHkdf(
algorithm: "sha256" | "sha512",
regularKey: string,
utf8Key: string,
unicodeKey: string
) {
const ikm = Utils.fromB64ToArray("criAmKtfzxanbgea5/kelQ==");
const regularSalt = "salt";
const utf8Salt = "üser_salt";
const unicodeSalt = "😀salt🙏";
const regularInfo = "info";
const utf8Info = "üser_info";
const unicodeInfo = "😀info🙏";
it("should create valid " + algorithm + " key from regular input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(ikm, regularSalt, regularInfo, 32, algorithm);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
it("should create valid " + algorithm + " key from utf8 input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(ikm, utf8Salt, utf8Info, 32, algorithm);
expect(Utils.fromBufferToB64(key)).toBe(utf8Key);
});
it("should create valid " + algorithm + " key from unicode input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(ikm, unicodeSalt, unicodeInfo, 32, algorithm);
expect(Utils.fromBufferToB64(key)).toBe(unicodeKey);
});
it("should create valid " + algorithm + " key from array buffer input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(
ikm,
Utils.fromUtf8ToArray(regularSalt).buffer,
Utils.fromUtf8ToArray(regularInfo).buffer,
32,
algorithm
);
expect(Utils.fromBufferToB64(key)).toBe(regularKey);
});
}
function testHkdfExpand(
algorithm: "sha256" | "sha512",
b64prk: string,
outputByteSize: number,
b64ExpectedOkm: string
) {
const info = "info";
it("should create valid " + algorithm + " " + outputByteSize + " byte okm", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const okm = await cryptoFunctionService.hkdfExpand(
Utils.fromB64ToArray(b64prk),
info,
outputByteSize,
algorithm
);
expect(Utils.fromBufferToB64(okm)).toBe(b64ExpectedOkm);
});
}
function testHash(
algorithm: "sha1" | "sha256" | "sha512" | "md5",
regularHash: string,
utf8Hash: string,
unicodeHash: string
) {
const regularValue = "HashMe!!";
const utf8Value = "HǻshMe!!";
const unicodeValue = "😀HashMe!!!🙏";
it("should create valid " + algorithm + " hash from regular input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const hash = await cryptoFunctionService.hash(regularValue, algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(regularHash);
});
it("should create valid " + algorithm + " hash from utf8 input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const hash = await cryptoFunctionService.hash(utf8Value, algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(utf8Hash);
});
it("should create valid " + algorithm + " hash from unicode input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const hash = await cryptoFunctionService.hash(unicodeValue, algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(unicodeHash);
});
it("should create valid " + algorithm + " hash from array buffer input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const hash = await cryptoFunctionService.hash(
Utils.fromUtf8ToArray(regularValue).buffer,
algorithm
);
expect(Utils.fromBufferToHex(hash)).toBe(regularHash);
});
}
function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string, fast = false) {
it("should create valid " + algorithm + " hmac", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const value = Utils.fromUtf8ToArray("SignMe!!").buffer;
const key = Utils.fromUtf8ToArray("secretkey").buffer;
let computedMac: ArrayBuffer = null;
if (fast) {
computedMac = await cryptoFunctionService.hmacFast(value, key, algorithm);
} else {
computedMac = await cryptoFunctionService.hmac(value, key, algorithm);
}
expect(Utils.fromBufferToHex(computedMac)).toBe(mac);
});
}
function testCompare(fast = false) {
it("should successfully compare two of the same values", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const equal = fast
? await cryptoFunctionService.compareFast(a.buffer, a.buffer)
: await cryptoFunctionService.compare(a.buffer, a.buffer);
expect(equal).toBe(true);
});
it("should successfully compare two different values of the same length", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const b = new Uint8Array(2);
b[0] = 3;
b[1] = 4;
const equal = fast
? await cryptoFunctionService.compareFast(a.buffer, b.buffer)
: await cryptoFunctionService.compare(a.buffer, b.buffer);
expect(equal).toBe(false);
});
it("should successfully compare two different values of different lengths", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const b = new Uint8Array(2);
b[0] = 3;
const equal = fast
? await cryptoFunctionService.compareFast(a.buffer, b.buffer)
: await cryptoFunctionService.compare(a.buffer, b.buffer);
expect(equal).toBe(false);
});
}
function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
it(
"should successfully generate a " + length + " bit key pair",
async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length);
expect(keyPair[0] == null || keyPair[1] == null).toBe(false);
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]);
expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey));
},
30000
);
}
function makeStaticByteArray(length: number) {
const arr = new Uint8Array(length);
for (let i = 0; i < length; i++) {
arr[i] = i;
}
return arr;
}

0
libs/node/spec/test.ts Normal file
View File

View File

@@ -0,0 +1,113 @@
import * as chalk from "chalk";
import { StateService } from "jslib-common/abstractions/state.service";
import { Response } from "./models/response";
import { ListResponse } from "./models/response/listResponse";
import { MessageResponse } from "./models/response/messageResponse";
import { StringResponse } from "./models/response/stringResponse";
export abstract class BaseProgram {
constructor(
protected stateService: StateService,
private writeLn: (s: string, finalLine: boolean, error: boolean) => void
) {}
protected processResponse(
response: Response,
exitImmediately = false,
dataProcessor: () => string = null
) {
if (!response.success) {
if (process.env.BW_QUIET !== "true") {
if (process.env.BW_RESPONSE === "true") {
this.writeLn(this.getJson(response), true, false);
} else {
this.writeLn(chalk.redBright(response.message), true, true);
}
}
const exitCode = process.env.BW_CLEANEXIT ? 0 : 1;
if (exitImmediately) {
process.exit(exitCode);
} else {
process.exitCode = exitCode;
}
return;
}
if (process.env.BW_RESPONSE === "true") {
this.writeLn(this.getJson(response), true, false);
} else if (response.data != null) {
let out: string = dataProcessor != null ? dataProcessor() : null;
if (out == null) {
if (response.data.object === "string") {
const data = (response.data as StringResponse).data;
if (data != null) {
out = data;
}
} else if (response.data.object === "list") {
out = this.getJson((response.data as ListResponse).data);
} else if (response.data.object === "message") {
out = this.getMessage(response);
} else {
out = this.getJson(response.data);
}
}
if (out != null && process.env.BW_QUIET !== "true") {
this.writeLn(out, true, false);
}
}
if (exitImmediately) {
process.exit(0);
} else {
process.exitCode = 0;
}
}
protected getJson(obj: any): string {
if (process.env.BW_PRETTY === "true") {
return JSON.stringify(obj, null, " ");
} else {
return JSON.stringify(obj);
}
}
protected getMessage(response: Response): string {
const message = response.data as MessageResponse;
if (process.env.BW_RAW === "true") {
return message.raw;
}
let out = "";
if (message.title != null) {
if (message.noColor) {
out = message.title;
} else {
out = chalk.greenBright(message.title);
}
}
if (message.message != null) {
if (message.title != null) {
out += "\n";
}
out += message.message;
}
return out.trim() === "" ? null : out;
}
protected async exitIfAuthed() {
const authed = await this.stateService.getIsAuthenticated();
if (authed) {
const email = await this.stateService.getEmail();
this.processResponse(Response.error("You are already logged in as " + email + "."), true);
}
}
protected async exitIfNotAuthed() {
const authed = await this.stateService.getIsAuthenticated();
if (!authed) {
this.processResponse(Response.error("You are not logged in."), true);
}
}
}

View File

@@ -0,0 +1,624 @@
import * as http from "http";
import * as program from "commander";
import * as inquirer from "inquirer";
import Separator from "inquirer/lib/objects/separator";
import { ApiService } from "jslib-common/abstractions/api.service";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TwoFactorService } from "jslib-common/abstractions/twoFactor.service";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
import { NodeUtils } from "jslib-common/misc/nodeUtils";
import { Utils } from "jslib-common/misc/utils";
import { AuthResult } from "jslib-common/models/domain/authResult";
import {
ApiLogInCredentials,
PasswordLogInCredentials,
SsoLogInCredentials,
} from "jslib-common/models/domain/logInCredentials";
import { TokenRequestTwoFactor } from "jslib-common/models/request/identityToken/tokenRequestTwoFactor";
import { TwoFactorEmailRequest } from "jslib-common/models/request/twoFactorEmailRequest";
import { UpdateTempPasswordRequest } from "jslib-common/models/request/updateTempPasswordRequest";
import { ErrorResponse } from "jslib-common/models/response/errorResponse";
import { Response } from "../models/response";
import { MessageResponse } from "../models/response/messageResponse";
export class LoginCommand {
protected validatedParams: () => Promise<any>;
protected success: () => Promise<MessageResponse>;
protected logout: () => Promise<void>;
protected canInteract: boolean;
protected clientId: string;
protected clientSecret: string;
protected email: string;
private ssoRedirectUri: string = null;
constructor(
protected authService: AuthService,
protected apiService: ApiService,
protected i18nService: I18nService,
protected environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationService,
protected cryptoFunctionService: CryptoFunctionService,
protected platformUtilsService: PlatformUtilsService,
protected stateService: StateService,
protected cryptoService: CryptoService,
protected policyService: PolicyService,
protected twoFactorService: TwoFactorService,
clientId: string
) {
this.clientId = clientId;
}
async run(email: string, password: string, options: program.OptionValues) {
this.canInteract = process.env.BW_NOINTERACTION !== "true";
let ssoCodeVerifier: string = null;
let ssoCode: string = null;
let orgIdentifier: string = null;
let clientId: string = null;
let clientSecret: string = null;
let selectedProvider: any = null;
if (options.apikey != null) {
const apiIdentifiers = await this.apiIdentifiers();
clientId = apiIdentifiers.clientId;
clientSecret = apiIdentifiers.clientSecret;
} else if (options.sso != null && this.canInteract) {
const passwordOptions: any = {
type: "password",
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
const state = await this.passwordGenerationService.generatePassword(passwordOptions);
ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
try {
const ssoParams = await this.openSsoPrompt(codeChallenge, state);
ssoCode = ssoParams.ssoCode;
orgIdentifier = ssoParams.orgIdentifier;
} catch {
return Response.badRequest("Something went wrong. Try again.");
}
} else {
if ((email == null || email === "") && this.canInteract) {
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "input",
name: "email",
message: "Email address:",
});
email = answer.email;
}
if (email == null || email.trim() === "") {
return Response.badRequest("Email address is required.");
}
if (email.indexOf("@") === -1) {
return Response.badRequest("Email address is invalid.");
}
this.email = email;
if (password == null || password === "") {
if (options.passwordfile) {
password = await NodeUtils.readFirstLine(options.passwordfile);
} else if (options.passwordenv && process.env[options.passwordenv]) {
password = process.env[options.passwordenv];
} else if (this.canInteract) {
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "password",
name: "password",
message: "Master password:",
});
password = answer.password;
}
}
if (password == null || password === "") {
return Response.badRequest("Master password is required.");
}
}
let twoFactorToken: string = options.code;
let twoFactorMethod: TwoFactorProviderType = null;
try {
if (options.method != null) {
twoFactorMethod = parseInt(options.method, null);
}
} catch (e) {
return Response.error("Invalid two-step login method.");
}
const twoFactor =
twoFactorToken == null
? null
: new TokenRequestTwoFactor(twoFactorMethod, twoFactorToken, false);
try {
if (this.validatedParams != null) {
await this.validatedParams();
}
let response: AuthResult = null;
if (clientId != null && clientSecret != null) {
response = await this.authService.logIn(new ApiLogInCredentials(clientId, clientSecret));
} else if (ssoCode != null && ssoCodeVerifier != null) {
response = await this.authService.logIn(
new SsoLogInCredentials(
ssoCode,
ssoCodeVerifier,
this.ssoRedirectUri,
orgIdentifier,
twoFactor
)
);
} else {
response = await this.authService.logIn(
new PasswordLogInCredentials(email, password, null, twoFactor)
);
}
if (response.captchaSiteKey) {
const credentials = new PasswordLogInCredentials(email, password);
const handledResponse = await this.handleCaptchaRequired(twoFactor, credentials);
// Error Response
if (handledResponse instanceof Response) {
return handledResponse;
} else {
response = handledResponse;
}
}
if (response.requiresTwoFactor) {
const twoFactorProviders = this.twoFactorService.getSupportedProviders(null);
if (twoFactorProviders.length === 0) {
return Response.badRequest("No providers available for this client.");
}
if (twoFactorMethod != null) {
try {
selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0];
} catch (e) {
return Response.error("Invalid two-step login method.");
}
}
if (selectedProvider == null) {
if (twoFactorProviders.length === 1) {
selectedProvider = twoFactorProviders[0];
} else if (this.canInteract) {
const twoFactorOptions: (string | Separator)[] = twoFactorProviders.map((p) => p.name);
twoFactorOptions.push(new inquirer.Separator());
twoFactorOptions.push("Cancel");
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "list",
name: "method",
message: "Two-step login method:",
choices: twoFactorOptions,
});
const i = twoFactorOptions.indexOf(answer.method);
if (i === twoFactorOptions.length - 1) {
return Response.error("Login failed.");
}
selectedProvider = twoFactorProviders[i];
}
if (selectedProvider == null) {
return Response.error("Login failed. No provider selected.");
}
}
if (
twoFactorToken == null &&
response.twoFactorProviders.size > 1 &&
selectedProvider.type === TwoFactorProviderType.Email
) {
const emailReq = new TwoFactorEmailRequest();
emailReq.email = this.authService.email;
emailReq.masterPasswordHash = this.authService.masterPasswordHash;
await this.apiService.postTwoFactorEmail(emailReq);
}
if (twoFactorToken == null) {
if (this.canInteract) {
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "input",
name: "token",
message: "Two-step login code:",
});
twoFactorToken = answer.token;
}
if (twoFactorToken == null || twoFactorToken === "") {
return Response.badRequest("Code is required.");
}
}
response = await this.authService.logInTwoFactor(
new TokenRequestTwoFactor(selectedProvider.type, twoFactorToken),
null
);
}
if (response.captchaSiteKey) {
const twoFactorRequest = new TokenRequestTwoFactor(selectedProvider.type, twoFactorToken);
const handledResponse = await this.handleCaptchaRequired(twoFactorRequest);
// Error Response
if (handledResponse instanceof Response) {
return handledResponse;
} else {
response = handledResponse;
}
}
if (response.requiresTwoFactor) {
return Response.error("Login failed.");
}
if (response.resetMasterPassword) {
return Response.error(
"In order to log in with SSO from the CLI, you must first log in" +
" through the web vault to set your master password."
);
}
// Handle Updating Temp Password if NOT using an API Key for authentication
if (response.forcePasswordReset && clientId == null && clientSecret == null) {
return await this.updateTempPassword();
}
return await this.handleSuccessResponse();
} catch (e) {
return Response.error(e);
}
}
private async handleSuccessResponse(): Promise<Response> {
if (this.success != null) {
const res = await this.success();
return Response.success(res);
} else {
const res = new MessageResponse("You are logged in!", null);
return Response.success(res);
}
}
private async updateTempPassword(error?: string): Promise<Response> {
// If no interaction available, alert user to use web vault
if (!this.canInteract) {
await this.logout();
this.authService.logOut(() => {
/* Do nothing */
});
return Response.error(
new MessageResponse(
"An organization administrator recently changed your master password. In order to access the vault, you must update your master password now via the web vault. You have been logged out.",
null
)
);
}
if (this.email == null || this.email === "undefined") {
this.email = await this.stateService.getEmail();
}
// Get New Master Password
const baseMessage =
"An organization administrator recently changed your master password. In order to access the vault, you must update your master password now.\n" +
"Master password: ";
const firstMessage = error != null ? error + baseMessage : baseMessage;
const mp: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
type: "password",
name: "password",
message: firstMessage,
});
const masterPassword = mp.password;
// Master Password Validation
if (masterPassword == null || masterPassword === "") {
return this.updateTempPassword("Master password is required.\n");
}
if (masterPassword.length < 8) {
return this.updateTempPassword("Master password must be at least 8 characters long.\n");
}
// Strength & Policy Validation
const strengthResult = this.passwordGenerationService.passwordStrength(
masterPassword,
this.getPasswordStrengthUserInput()
);
// Get New Master Password Re-type
const reTypeMessage = "Re-type New Master password (Strength: " + strengthResult.score + ")";
const retype: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
type: "password",
name: "password",
message: reTypeMessage,
});
const masterPasswordRetype = retype.password;
// Re-type Validation
if (masterPassword !== masterPasswordRetype) {
return this.updateTempPassword("Master password confirmation does not match.\n");
}
// Get Hint (optional)
const hint: inquirer.Answers = await inquirer.createPromptModule({ output: process.stderr })({
type: "input",
name: "input",
message: "Master Password Hint (optional):",
});
const masterPasswordHint = hint.input;
// Retrieve details for key generation
const enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions();
const kdf = await this.stateService.getKdfType();
const kdfIterations = await this.stateService.getKdfIterations();
if (
enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
strengthResult.score,
masterPassword,
enforcedPolicyOptions
)
) {
return this.updateTempPassword(
"Your new master password does not meet the policy requirements.\n"
);
}
try {
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(
masterPassword,
this.email.trim().toLowerCase(),
kdf,
kdfIterations
);
const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey);
// Grab user's current enc key
const userEncKey = await this.cryptoService.getEncKey();
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
// Create request
const request = new UpdateTempPasswordRequest();
request.key = newEncKey[1].encryptedString;
request.newMasterPasswordHash = newPasswordHash;
request.masterPasswordHint = masterPasswordHint;
// Update user's password
await this.apiService.putUpdateTempPassword(request);
return this.handleSuccessResponse();
} catch (e) {
await this.logout();
this.authService.logOut(() => {
/* Do nothing */
});
return Response.error(e);
}
}
private async handleCaptchaRequired(
twoFactorRequest: TokenRequestTwoFactor,
credentials: PasswordLogInCredentials = null
): Promise<AuthResult | Response> {
const badCaptcha = Response.badRequest(
"Your authentication request has been flagged and will require user interaction to proceed.\n" +
"Please use your API key to validate this request and ensure BW_CLIENTSECRET is correct, if set.\n" +
"(https://bitwarden.com/help/cli-auth-challenges)"
);
try {
const captchaClientSecret = await this.apiClientSecret(true);
if (Utils.isNullOrWhitespace(captchaClientSecret)) {
return badCaptcha;
}
let authResultResponse: AuthResult = null;
if (credentials != null) {
credentials.captchaToken = captchaClientSecret;
credentials.twoFactor = twoFactorRequest;
authResultResponse = await this.authService.logIn(credentials);
} else {
authResultResponse = await this.authService.logInTwoFactor(
twoFactorRequest,
captchaClientSecret
);
}
return authResultResponse;
} catch (e) {
if (
e instanceof ErrorResponse ||
(e.constructor.name === ErrorResponse.name &&
(e as ErrorResponse).message.includes("Captcha is invalid"))
) {
return badCaptcha;
} else {
return Response.error(e);
}
}
}
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
const atPosition = this.email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
this.email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
return userInput;
}
private async apiClientId(): Promise<string> {
let clientId: string = null;
const storedClientId: string = process.env.BW_CLIENTID;
if (storedClientId == null) {
if (this.canInteract) {
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "input",
name: "clientId",
message: "client_id:",
});
clientId = answer.clientId;
} else {
clientId = null;
}
} else {
clientId = storedClientId;
}
return clientId;
}
private async apiClientSecret(isAdditionalAuthentication = false): Promise<string> {
const additionalAuthenticationMessage = "Additional authentication required.\nAPI key ";
let clientSecret: string = null;
const storedClientSecret: string = this.clientSecret || process.env.BW_CLIENTSECRET;
if (this.canInteract && storedClientSecret == null) {
const answer: inquirer.Answers = await inquirer.createPromptModule({
output: process.stderr,
})({
type: "input",
name: "clientSecret",
message:
(isAdditionalAuthentication ? additionalAuthenticationMessage : "") + "client_secret:",
});
clientSecret = answer.clientSecret;
} else {
clientSecret = storedClientSecret;
}
return clientSecret;
}
private async apiIdentifiers(): Promise<{ clientId: string; clientSecret: string }> {
return {
clientId: await this.apiClientId(),
clientSecret: await this.apiClientSecret(),
};
}
private async openSsoPrompt(
codeChallenge: string,
state: string
): Promise<{ ssoCode: string; orgIdentifier: string }> {
return new Promise((resolve, reject) => {
const callbackServer = http.createServer((req, res) => {
const urlString = "http://localhost" + req.url;
const url = new URL(urlString);
const code = url.searchParams.get("code");
const receivedState = url.searchParams.get("state");
const orgIdentifier = this.getOrgIdentifierFromState(receivedState);
res.setHeader("Content-Type", "text/html");
if (code != null && receivedState != null && this.checkState(receivedState, state)) {
res.writeHead(200);
res.end(
"<html><head><title>Success | Bitwarden CLI</title></head><body>" +
"<h1>Successfully authenticated with the Bitwarden CLI</h1>" +
"<p>You may now close this tab and return to the terminal.</p>" +
"</body></html>"
);
callbackServer.close(() =>
resolve({
ssoCode: code,
orgIdentifier: orgIdentifier,
})
);
} else {
res.writeHead(400);
res.end(
"<html><head><title>Failed | Bitwarden CLI</title></head><body>" +
"<h1>Something went wrong logging into the Bitwarden CLI</h1>" +
"<p>You may now close this tab and return to the terminal.</p>" +
"</body></html>"
);
callbackServer.close(() => reject());
}
});
let foundPort = false;
const webUrl = this.environmentService.getWebVaultUrl();
for (let port = 8065; port <= 8070; port++) {
try {
this.ssoRedirectUri = "http://localhost:" + port;
callbackServer.listen(port, () => {
this.platformUtilsService.launchUri(
webUrl +
"/#/sso?clientId=" +
this.clientId +
"&redirectUri=" +
encodeURIComponent(this.ssoRedirectUri) +
"&state=" +
state +
"&codeChallenge=" +
codeChallenge
);
});
foundPort = true;
break;
} catch {
// Ignore error since we run the same command up to 5 times.
}
}
if (!foundPort) {
reject();
}
});
}
private getOrgIdentifierFromState(state: string): string {
if (state === null || state === undefined) {
return null;
}
const stateSplit = state.split("_identifier=");
return stateSplit.length > 1 ? stateSplit[1] : null;
}
private checkState(state: string, checkState: string): boolean {
if (state === null || state === undefined) {
return false;
}
if (checkState === null || checkState === undefined) {
return false;
}
const stateSplit = state.split("_identifier=");
const checkStateSplit = checkState.split("_identifier=");
return stateSplit[0] === checkStateSplit[0];
}
}

View File

@@ -0,0 +1,22 @@
import { AuthService } from "jslib-common/abstractions/auth.service";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { Response } from "../models/response";
import { MessageResponse } from "../models/response/messageResponse";
export class LogoutCommand {
constructor(
private authService: AuthService,
private i18nService: I18nService,
private logoutCallback: () => Promise<void>
) {}
async run() {
await this.logoutCallback();
this.authService.logOut(() => {
/* Do nothing */
});
const res = new MessageResponse("You have logged out.", null);
return Response.success(res);
}
}

View File

@@ -0,0 +1,104 @@
import * as fetch from "node-fetch";
import { I18nService } from "jslib-common/abstractions/i18n.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { Response } from "../models/response";
import { MessageResponse } from "../models/response/messageResponse";
export class UpdateCommand {
inPkg = false;
constructor(
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private repoName: string,
private executableName: string,
private showExtendedMessage: boolean
) {
this.inPkg = !!(process as any).pkg;
}
async run(): Promise<Response> {
const currentVersion = await this.platformUtilsService.getApplicationVersion();
const response = await fetch.default(
"https://api.github.com/repos/bitwarden/" + this.repoName + "/releases/latest"
);
if (response.status === 200) {
const responseJson = await response.json();
const res = new MessageResponse(null, null);
const tagName: string = responseJson.tag_name;
if (tagName === "v" + currentVersion) {
res.title = "No update available.";
res.noColor = true;
return Response.success(res);
}
let downloadUrl: string = null;
if (responseJson.assets != null) {
for (const a of responseJson.assets) {
const download: string = a.browser_download_url;
if (download == null) {
continue;
}
if (download.indexOf(".zip") === -1) {
continue;
}
if (
process.platform === "win32" &&
download.indexOf(this.executableName + "-windows") > -1
) {
downloadUrl = download;
break;
} else if (
process.platform === "darwin" &&
download.indexOf(this.executableName + "-macos") > -1
) {
downloadUrl = download;
break;
} else if (
process.platform === "linux" &&
download.indexOf(this.executableName + "-linux") > -1
) {
downloadUrl = download;
break;
}
}
}
res.title = "A new version is available: " + tagName;
if (downloadUrl == null) {
downloadUrl = "https://github.com/bitwarden/" + this.repoName + "/releases";
} else {
res.raw = downloadUrl;
}
res.message = "";
if (responseJson.body != null && responseJson.body !== "") {
res.message = responseJson.body + "\n\n";
}
res.message += "You can download this update at " + downloadUrl;
if (this.showExtendedMessage) {
if (this.inPkg) {
res.message +=
"\n\nIf you installed this CLI through a package manager " +
"you should probably update using its update command instead.";
} else {
res.message +=
"\n\nIf you installed this CLI through NPM " +
"you should update using `npm install -g @bitwarden/" +
this.repoName +
"`";
}
}
return Response.success(res);
} else {
return Response.error("Error contacting update API: " + response.status);
}
}
}

View File

@@ -0,0 +1,50 @@
import { BaseResponse } from "./response/baseResponse";
export class Response {
static error(error: any, data?: any): Response {
const res = new Response();
res.success = false;
if (typeof error === "string") {
res.message = error;
} else {
res.message =
error.message != null
? error.message
: error.toString() === "[object Object]"
? JSON.stringify(error)
: error.toString();
}
res.data = data;
return res;
}
static notFound(): Response {
return Response.error("Not found.");
}
static badRequest(message: string): Response {
return Response.error(message);
}
static multipleResults(ids: string[]): Response {
let msg =
"More than one result was found. Try getting a specific object by `id` instead. " +
"The following objects were found:";
ids.forEach((id) => {
msg += "\n" + id;
});
return Response.error(msg, ids);
}
static success(data?: BaseResponse): Response {
const res = new Response();
res.success = true;
res.data = data;
return res;
}
success: boolean;
message: string;
errorCode: number;
data: BaseResponse;
}

View File

@@ -0,0 +1,3 @@
export interface BaseResponse {
object: string;
}

View File

@@ -0,0 +1,13 @@
import { BaseResponse } from "./baseResponse";
export class FileResponse implements BaseResponse {
object: string;
data: Buffer;
fileName: string;
constructor(data: Buffer, fileName: string) {
this.object = "file";
this.data = data;
this.fileName = fileName;
}
}

View File

@@ -0,0 +1,11 @@
import { BaseResponse } from "./baseResponse";
export class ListResponse implements BaseResponse {
object: string;
data: BaseResponse[];
constructor(data: BaseResponse[]) {
this.object = "list";
this.data = data;
}
}

View File

@@ -0,0 +1,15 @@
import { BaseResponse } from "./baseResponse";
export class MessageResponse implements BaseResponse {
object: string;
title: string;
message: string;
raw: string;
noColor = false;
constructor(title: string, message: string) {
this.object = "message";
this.title = title;
this.message = message;
}
}

View File

@@ -0,0 +1,11 @@
import { BaseResponse } from "./baseResponse";
export class StringResponse implements BaseResponse {
object: string;
data: string;
constructor(data: string) {
this.object = "string";
this.data = data;
}
}

View File

@@ -0,0 +1,166 @@
import * as child_process from "child_process";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { ClientType } from "jslib-common/enums/clientType";
import { DeviceType } from "jslib-common/enums/deviceType";
import { ThemeType } from "jslib-common/enums/themeType";
// eslint-disable-next-line
const open = require("open");
export class CliPlatformUtilsService implements PlatformUtilsService {
clientType: ClientType;
private deviceCache: DeviceType = null;
constructor(clientType: ClientType, private packageJson: any) {
this.clientType = clientType;
}
getDevice(): DeviceType {
if (!this.deviceCache) {
switch (process.platform) {
case "win32":
this.deviceCache = DeviceType.WindowsDesktop;
break;
case "darwin":
this.deviceCache = DeviceType.MacOsDesktop;
break;
case "linux":
default:
this.deviceCache = DeviceType.LinuxDesktop;
break;
}
}
return this.deviceCache;
}
getDeviceString(): string {
const device = DeviceType[this.getDevice()].toLowerCase();
return device.replace("desktop", "");
}
getClientType() {
return this.clientType;
}
isFirefox() {
return false;
}
isChrome() {
return false;
}
isEdge() {
return false;
}
isOpera() {
return false;
}
isVivaldi() {
return false;
}
isSafari() {
return false;
}
isMacAppStore() {
return false;
}
isViewOpen() {
return Promise.resolve(false);
}
launchUri(uri: string, options?: any): void {
if (process.platform === "linux") {
child_process.spawnSync("xdg-open", [uri]);
} else {
open(uri);
}
}
saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void {
throw new Error("Not implemented.");
}
getApplicationVersion(): Promise<string> {
return Promise.resolve(this.packageJson.version);
}
getApplicationVersionSync(): string {
return this.packageJson.version;
}
supportsWebAuthn(win: Window) {
return false;
}
supportsDuo(): boolean {
return false;
}
showToast(
type: "error" | "success" | "warning" | "info",
title: string,
text: string | string[],
options?: any
): void {
throw new Error("Not implemented.");
}
showDialog(
text: string,
title?: string,
confirmText?: string,
cancelText?: string,
type?: string
): Promise<boolean> {
throw new Error("Not implemented.");
}
isDev(): boolean {
return process.env.BWCLI_ENV === "development";
}
isSelfHost(): boolean {
return false;
}
copyToClipboard(text: string, options?: any): void {
throw new Error("Not implemented.");
}
readFromClipboard(options?: any): Promise<string> {
throw new Error("Not implemented.");
}
supportsBiometric(): Promise<boolean> {
return Promise.resolve(false);
}
authenticateBiometric(): Promise<boolean> {
return Promise.resolve(false);
}
getDefaultSystemTheme() {
return Promise.resolve(ThemeType.Light as ThemeType.Light | ThemeType.Dark);
}
onDefaultSystemThemeChange() {
/* noop */
}
getEffectiveTheme() {
return Promise.resolve(ThemeType.Light);
}
supportsSecureStorage(): boolean {
return false;
}
}

View File

@@ -0,0 +1,22 @@
import { LogLevelType } from "jslib-common/enums/logLevelType";
import { ConsoleLogService as BaseConsoleLogService } from "jslib-common/services/consoleLog.service";
export class ConsoleLogService extends BaseConsoleLogService {
constructor(isDev: boolean, filter: (level: LogLevelType) => boolean = null) {
super(isDev, filter);
}
write(level: LogLevelType, message: string) {
if (this.filter != null && this.filter(level)) {
return;
}
if (process.env.BW_RESPONSE === "true") {
// eslint-disable-next-line
console.error(message);
return;
}
super.write(level, message);
}
}

0
libs/node/src/globals.d.ts vendored Normal file
View File

View File

@@ -0,0 +1,148 @@
import * as fs from "fs";
import * as path from "path";
import * as lowdb from "lowdb";
import * as FileSync from "lowdb/adapters/FileSync";
import { LogService } from "jslib-common/abstractions/log.service";
import { StorageService } from "jslib-common/abstractions/storage.service";
import { NodeUtils } from "jslib-common/misc/nodeUtils";
import { sequentialize } from "jslib-common/misc/sequentialize";
import { Utils } from "jslib-common/misc/utils";
export class LowdbStorageService implements StorageService {
protected dataFilePath: string;
private db: lowdb.LowdbSync<any>;
private defaults: any;
private ready = false;
constructor(
protected logService: LogService,
defaults?: any,
private dir?: string,
private allowCache = false
) {
this.defaults = defaults;
}
@sequentialize(() => "lowdbStorageInit")
async init() {
if (this.ready) {
return;
}
this.logService.info("Initializing lowdb storage service.");
let adapter: lowdb.AdapterSync<any>;
if (Utils.isNode && this.dir != null) {
if (!fs.existsSync(this.dir)) {
this.logService.warning(`Could not find dir, "${this.dir}"; creating it instead.`);
NodeUtils.mkdirpSync(this.dir, "700");
this.logService.info(`Created dir "${this.dir}".`);
}
this.dataFilePath = path.join(this.dir, "data.json");
if (!fs.existsSync(this.dataFilePath)) {
this.logService.warning(
`Could not find data file, "${this.dataFilePath}"; creating it instead.`
);
fs.writeFileSync(this.dataFilePath, "", { mode: 0o600 });
fs.chmodSync(this.dataFilePath, 0o600);
this.logService.info(`Created data file "${this.dataFilePath}" with chmod 600.`);
} else {
this.logService.info(`db file "${this.dataFilePath} already exists"; using existing db`);
}
await this.lockDbFile(() => {
adapter = new FileSync(this.dataFilePath);
});
}
try {
this.logService.info("Attempting to create lowdb storage adapter.");
this.db = lowdb(adapter);
this.logService.info("Successfully created lowdb storage adapter.");
} catch (e) {
if (e instanceof SyntaxError) {
this.logService.warning(
`Error creating lowdb storage adapter, "${e.message}"; emptying data file.`
);
if (fs.existsSync(this.dataFilePath)) {
const backupPath = this.dataFilePath + ".bak";
this.logService.warning(`Writing backup of data file to ${backupPath}`);
await fs.copyFile(this.dataFilePath, backupPath, () => {
this.logService.warning(
`Error while creating data file backup, "${e.message}". No backup may have been created.`
);
});
}
adapter.write({});
this.db = lowdb(adapter);
} else {
this.logService.error(`Error creating lowdb storage adapter, "${e.message}".`);
throw e;
}
}
if (this.defaults != null) {
this.lockDbFile(() => {
this.logService.info("Writing defaults.");
this.readForNoCache();
this.db.defaults(this.defaults).write();
this.logService.info("Successfully wrote defaults to db.");
});
}
this.ready = true;
}
async get<T>(key: string): Promise<T> {
await this.waitForReady();
return this.lockDbFile(() => {
this.readForNoCache();
const val = this.db.get(key).value();
this.logService.debug(`Successfully read ${key} from db`);
if (val == null) {
return null;
}
return val as T;
});
}
has(key: string): Promise<boolean> {
return this.get(key).then((v) => v != null);
}
async save(key: string, obj: any): Promise<any> {
await this.waitForReady();
return this.lockDbFile(() => {
this.readForNoCache();
this.db.set(key, obj).write();
this.logService.debug(`Successfully wrote ${key} to db`);
return;
});
}
async remove(key: string): Promise<any> {
await this.waitForReady();
return this.lockDbFile(() => {
this.readForNoCache();
this.db.unset(key).write();
this.logService.debug(`Successfully removed ${key} from db`);
return;
});
}
protected async lockDbFile<T>(action: () => T): Promise<T> {
// Lock methods implemented in clients
return Promise.resolve(action());
}
private readForNoCache() {
if (!this.allowCache) {
this.db.read();
}
}
private async waitForReady() {
if (!this.ready) {
await this.init();
}
}
}

View File

@@ -0,0 +1,43 @@
import * as FormData from "form-data";
import { HttpsProxyAgent } from "https-proxy-agent";
import * as fe from "node-fetch";
import { AppIdService } from "jslib-common/abstractions/appId.service";
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { TokenService } from "jslib-common/abstractions/token.service";
import { ApiService } from "jslib-common/services/api.service";
(global as any).fetch = fe.default;
(global as any).Request = fe.Request;
(global as any).Response = fe.Response;
(global as any).Headers = fe.Headers;
(global as any).FormData = FormData;
export class NodeApiService extends ApiService {
constructor(
tokenService: TokenService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
appIdService: AppIdService,
logoutCallback: (expired: boolean) => Promise<void>,
customUserAgent: string = null
) {
super(
tokenService,
platformUtilsService,
environmentService,
appIdService,
logoutCallback,
customUserAgent
);
}
nativeFetch(request: Request): Promise<Response> {
const proxy = process.env.http_proxy || process.env.https_proxy;
if (proxy) {
(request as any).agent = new HttpsProxyAgent(proxy);
}
return fetch(request);
}
}

View File

@@ -0,0 +1,301 @@
import * as crypto from "crypto";
import * as forge from "node-forge";
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
import { Utils } from "jslib-common/misc/utils";
import { DecryptParameters } from "jslib-common/models/domain/decryptParameters";
import { SymmetricCryptoKey } from "jslib-common/models/domain/symmetricCryptoKey";
export class NodeCryptoFunctionService implements CryptoFunctionService {
pbkdf2(
password: string | ArrayBuffer,
salt: string | ArrayBuffer,
algorithm: "sha256" | "sha512",
iterations: number
): Promise<ArrayBuffer> {
const len = algorithm === "sha256" ? 32 : 64;
const nodePassword = this.toNodeValue(password);
const nodeSalt = this.toNodeValue(salt);
return new Promise<ArrayBuffer>((resolve, reject) => {
crypto.pbkdf2(nodePassword, nodeSalt, iterations, len, algorithm, (error, key) => {
if (error != null) {
reject(error);
} else {
resolve(this.toArrayBuffer(key));
}
});
});
}
// ref: https://tools.ietf.org/html/rfc5869
async hkdf(
ikm: ArrayBuffer,
salt: string | ArrayBuffer,
info: string | ArrayBuffer,
outputByteSize: number,
algorithm: "sha256" | "sha512"
): Promise<ArrayBuffer> {
const saltBuf = this.toArrayBuffer(salt);
const prk = await this.hmac(ikm, saltBuf, algorithm);
return this.hkdfExpand(prk, info, outputByteSize, algorithm);
}
// ref: https://tools.ietf.org/html/rfc5869
async hkdfExpand(
prk: ArrayBuffer,
info: string | ArrayBuffer,
outputByteSize: number,
algorithm: "sha256" | "sha512"
): Promise<ArrayBuffer> {
const hashLen = algorithm === "sha256" ? 32 : 64;
if (outputByteSize > 255 * hashLen) {
throw new Error("outputByteSize is too large.");
}
const prkArr = new Uint8Array(prk);
if (prkArr.length < hashLen) {
throw new Error("prk is too small.");
}
const infoBuf = this.toArrayBuffer(info);
const infoArr = new Uint8Array(infoBuf);
let runningOkmLength = 0;
let previousT = new Uint8Array(0);
const n = Math.ceil(outputByteSize / hashLen);
const okm = new Uint8Array(n * hashLen);
for (let i = 0; i < n; i++) {
const t = new Uint8Array(previousT.length + infoArr.length + 1);
t.set(previousT);
t.set(infoArr, previousT.length);
t.set([i + 1], t.length - 1);
previousT = new Uint8Array(await this.hmac(t.buffer, prk, algorithm));
okm.set(previousT, runningOkmLength);
runningOkmLength += previousT.length;
if (runningOkmLength >= outputByteSize) {
break;
}
}
return okm.slice(0, outputByteSize).buffer;
}
hash(
value: string | ArrayBuffer,
algorithm: "sha1" | "sha256" | "sha512" | "md5"
): Promise<ArrayBuffer> {
const nodeValue = this.toNodeValue(value);
const hash = crypto.createHash(algorithm);
hash.update(nodeValue);
return Promise.resolve(this.toArrayBuffer(hash.digest()));
}
hmac(
value: ArrayBuffer,
key: ArrayBuffer,
algorithm: "sha1" | "sha256" | "sha512"
): Promise<ArrayBuffer> {
const nodeValue = this.toNodeBuffer(value);
const nodeKey = this.toNodeBuffer(key);
const hmac = crypto.createHmac(algorithm, nodeKey);
hmac.update(nodeValue);
return Promise.resolve(this.toArrayBuffer(hmac.digest()));
}
async compare(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
const key = await this.randomBytes(32);
const mac1 = await this.hmac(a, key, "sha256");
const mac2 = await this.hmac(b, key, "sha256");
if (mac1.byteLength !== mac2.byteLength) {
return false;
}
const arr1 = new Uint8Array(mac1);
const arr2 = new Uint8Array(mac2);
for (let i = 0; i < arr2.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
hmacFast(
value: ArrayBuffer,
key: ArrayBuffer,
algorithm: "sha1" | "sha256" | "sha512"
): Promise<ArrayBuffer> {
return this.hmac(value, key, algorithm);
}
compareFast(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
return this.compare(a, b);
}
aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
const nodeData = this.toNodeBuffer(data);
const nodeIv = this.toNodeBuffer(iv);
const nodeKey = this.toNodeBuffer(key);
const cipher = crypto.createCipheriv("aes-256-cbc", nodeKey, nodeIv);
const encBuf = Buffer.concat([cipher.update(nodeData), cipher.final()]);
return Promise.resolve(this.toArrayBuffer(encBuf));
}
aesDecryptFastParameters(
data: string,
iv: string,
mac: string,
key: SymmetricCryptoKey
): DecryptParameters<ArrayBuffer> {
const p = new DecryptParameters<ArrayBuffer>();
p.encKey = key.encKey;
p.data = Utils.fromB64ToArray(data).buffer;
p.iv = Utils.fromB64ToArray(iv).buffer;
const macData = new Uint8Array(p.iv.byteLength + p.data.byteLength);
macData.set(new Uint8Array(p.iv), 0);
macData.set(new Uint8Array(p.data), p.iv.byteLength);
p.macData = macData.buffer;
if (key.macKey != null) {
p.macKey = key.macKey;
}
if (mac != null) {
p.mac = Utils.fromB64ToArray(mac).buffer;
}
return p;
}
async aesDecryptFast(parameters: DecryptParameters<ArrayBuffer>): Promise<string> {
const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey);
return Utils.fromBufferToUtf8(decBuf);
}
aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
const nodeData = this.toNodeBuffer(data);
const nodeIv = this.toNodeBuffer(iv);
const nodeKey = this.toNodeBuffer(key);
const decipher = crypto.createDecipheriv("aes-256-cbc", nodeKey, nodeIv);
const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]);
return Promise.resolve(this.toArrayBuffer(decBuf));
}
rsaEncrypt(
data: ArrayBuffer,
publicKey: ArrayBuffer,
algorithm: "sha1" | "sha256"
): Promise<ArrayBuffer> {
if (algorithm === "sha256") {
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
}
const pem = this.toPemPublicKey(publicKey);
const decipher = crypto.publicEncrypt(pem, this.toNodeBuffer(data));
return Promise.resolve(this.toArrayBuffer(decipher));
}
rsaDecrypt(
data: ArrayBuffer,
privateKey: ArrayBuffer,
algorithm: "sha1" | "sha256"
): Promise<ArrayBuffer> {
if (algorithm === "sha256") {
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
}
const pem = this.toPemPrivateKey(privateKey);
const decipher = crypto.privateDecrypt(pem, this.toNodeBuffer(data));
return Promise.resolve(this.toArrayBuffer(decipher));
}
rsaExtractPublicKey(privateKey: ArrayBuffer): Promise<ArrayBuffer> {
const privateKeyByteString = Utils.fromBufferToByteString(privateKey);
const privateKeyAsn1 = forge.asn1.fromDer(privateKeyByteString);
const forgePrivateKey: any = forge.pki.privateKeyFromAsn1(privateKeyAsn1);
const forgePublicKey = (forge.pki as any).setRsaPublicKey(forgePrivateKey.n, forgePrivateKey.e);
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(forgePublicKey);
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).data;
const publicKeyArray = Utils.fromByteStringToArray(publicKeyByteString);
return Promise.resolve(publicKeyArray.buffer);
}
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
return new Promise<[ArrayBuffer, ArrayBuffer]>((resolve, reject) => {
forge.pki.rsa.generateKeyPair(
{
bits: length,
workers: -1,
e: 0x10001, // 65537
},
(error, keyPair) => {
if (error != null) {
reject(error);
return;
}
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(keyPair.publicKey);
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).getBytes();
const publicKey = Utils.fromByteStringToArray(publicKeyByteString);
const privateKeyAsn1 = forge.pki.privateKeyToAsn1(keyPair.privateKey);
const privateKeyPkcs8 = forge.pki.wrapRsaPrivateKey(privateKeyAsn1);
const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes();
const privateKey = Utils.fromByteStringToArray(privateKeyByteString);
resolve([publicKey.buffer, privateKey.buffer]);
}
);
});
}
randomBytes(length: number): Promise<ArrayBuffer> {
return new Promise<ArrayBuffer>((resolve, reject) => {
crypto.randomBytes(length, (error, bytes) => {
if (error != null) {
reject(error);
} else {
resolve(this.toArrayBuffer(bytes));
}
});
});
}
private toNodeValue(value: string | ArrayBuffer): string | Buffer {
let nodeValue: string | Buffer;
if (typeof value === "string") {
nodeValue = value;
} else {
nodeValue = this.toNodeBuffer(value);
}
return nodeValue;
}
private toNodeBuffer(value: ArrayBuffer): Buffer {
return Buffer.from(new Uint8Array(value) as any);
}
private toArrayBuffer(value: Buffer | string | ArrayBuffer): ArrayBuffer {
let buf: ArrayBuffer;
if (typeof value === "string") {
buf = Utils.fromUtf8ToArray(value).buffer;
} else {
buf = new Uint8Array(value).buffer;
}
return buf;
}
private toPemPrivateKey(key: ArrayBuffer): string {
const byteString = Utils.fromBufferToByteString(key);
const asn1 = forge.asn1.fromDer(byteString);
const privateKey = forge.pki.privateKeyFromAsn1(asn1);
const rsaPrivateKey = forge.pki.privateKeyToAsn1(privateKey);
const privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);
return forge.pki.privateKeyInfoToPem(privateKeyInfo);
}
private toPemPublicKey(key: ArrayBuffer): string {
const byteString = Utils.fromBufferToByteString(key);
const asn1 = forge.asn1.fromDer(byteString);
const publicKey = forge.pki.publicKeyFromAsn1(asn1);
return forge.pki.publicKeyToPem(publicKey);
}
}

11
libs/node/tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"extends": "../shared/tsconfig",
"compilerOptions": {
"paths": {
"jslib-common/*": ["../common/src/*"],
"jslib-node/*": ["./src/*"]
}
},
"include": ["src", "spec"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,3 @@
{
"extends": "./tsconfig.json"
}