diff --git a/dist/generated/qrcode_data_pb.js b/dist/generated/qrcode_data_pb.js index 5349e36e18de696a3d726d5a8e29603f5b780fc1..e903a4aea9805dde1a3e87e2cbbc287f411fef3c 100644 --- a/dist/generated/qrcode_data_pb.js +++ b/dist/generated/qrcode_data_pb.js @@ -980,6 +980,7 @@ $root.vereign = (function () { * @property {string|null} [key] IpfsAttachmentData_V1 key * @property {string|null} [contentHash] IpfsAttachmentData_V1 contentHash * @property {string|null} [head] IpfsAttachmentData_V1 head + * @property {string|null} [contentType] IpfsAttachmentData_V1 contentType */ /** * Constructs a new IpfsAttachmentData_V1. @@ -1023,6 +1024,13 @@ $root.vereign = (function () { * @instance */ IpfsAttachmentData_V1.prototype.head = ""; + /** + * IpfsAttachmentData_V1 contentType. + * @member {string} contentType + * @memberof vereign.protobuf.qrcode_data.IpfsAttachmentData_V1 + * @instance + */ + IpfsAttachmentData_V1.prototype.contentType = ""; /** * Creates a new IpfsAttachmentData_V1 instance using the specified properties. * @function create @@ -1054,6 +1062,8 @@ $root.vereign = (function () { writer.uint32(/* id 3, wireType 2 =*/ 26).string(message.contentHash); if (message.head != null && Object.hasOwnProperty.call(message, "head")) writer.uint32(/* id 4, wireType 2 =*/ 34).string(message.head); + if (message.contentType != null && Object.hasOwnProperty.call(message, "contentType")) + writer.uint32(/* id 5, wireType 2 =*/ 42).string(message.contentType); return writer; }; /** @@ -1098,6 +1108,9 @@ $root.vereign = (function () { case 4: message.head = reader.string(); break; + case 5: + message.contentType = reader.string(); + break; default: reader.skipType(tag & 7); break; @@ -1143,6 +1156,9 @@ $root.vereign = (function () { if (message.head != null && message.hasOwnProperty("head")) if (!$util.isString(message.head)) return "head: string expected"; + if (message.contentType != null && message.hasOwnProperty("contentType")) + if (!$util.isString(message.contentType)) + return "contentType: string expected"; return null; }; /** @@ -1165,6 +1181,8 @@ $root.vereign = (function () { message.contentHash = String(object.contentHash); if (object.head != null) message.head = String(object.head); + if (object.contentType != null) + message.contentType = String(object.contentType); return message; }; /** @@ -1185,6 +1203,7 @@ $root.vereign = (function () { object.key = ""; object.contentHash = ""; object.head = ""; + object.contentType = ""; } if (message.cid != null && message.hasOwnProperty("cid")) object.cid = message.cid; @@ -1194,6 +1213,8 @@ $root.vereign = (function () { object.contentHash = message.contentHash; if (message.head != null && message.hasOwnProperty("head")) object.head = message.head; + if (message.contentType != null && message.hasOwnProperty("contentType")) + object.contentType = message.contentType; return object; }; /** @@ -1216,6 +1237,7 @@ $root.vereign = (function () { * @property {vereign.protobuf.qrcode_data.IIpfsContentData_V1|null} [plaintText] IpfsData_V1 plaintText * @property {vereign.protobuf.qrcode_data.IIpfsContentData_V1|null} [html] IpfsData_V1 html * @property {Array.<vereign.protobuf.qrcode_data.IIpfsAttachmentData_V1>|null} [attachments] IpfsData_V1 attachments + * @property {string|null} [version] IpfsData_V1 version */ /** * Constructs a new IpfsData_V1. @@ -1253,6 +1275,13 @@ $root.vereign = (function () { * @instance */ IpfsData_V1.prototype.attachments = $util.emptyArray; + /** + * IpfsData_V1 version. + * @member {string} version + * @memberof vereign.protobuf.qrcode_data.IpfsData_V1 + * @instance + */ + IpfsData_V1.prototype.version = ""; /** * Creates a new IpfsData_V1 instance using the specified properties. * @function create @@ -1283,6 +1312,8 @@ $root.vereign = (function () { if (message.attachments != null && message.attachments.length) for (var i = 0; i < message.attachments.length; ++i) $root.vereign.protobuf.qrcode_data.IpfsAttachmentData_V1.encode(message.attachments[i], writer.uint32(/* id 3, wireType 2 =*/ 26).fork()).ldelim(); + if (message.version != null && Object.hasOwnProperty.call(message, "version")) + writer.uint32(/* id 4, wireType 2 =*/ 34).string(message.version); return writer; }; /** @@ -1326,6 +1357,9 @@ $root.vereign = (function () { message.attachments = []; message.attachments.push($root.vereign.protobuf.qrcode_data.IpfsAttachmentData_V1.decode(reader, reader.uint32())); break; + case 4: + message.version = reader.string(); + break; default: reader.skipType(tag & 7); break; @@ -1378,6 +1412,9 @@ $root.vereign = (function () { return "attachments." + error; } } + if (message.version != null && message.hasOwnProperty("version")) + if (!$util.isString(message.version)) + return "version: string expected"; return null; }; /** @@ -1412,6 +1449,8 @@ $root.vereign = (function () { message.attachments[i] = $root.vereign.protobuf.qrcode_data.IpfsAttachmentData_V1.fromObject(object.attachments[i]); } } + if (object.version != null) + message.version = String(object.version); return message; }; /** @@ -1432,6 +1471,7 @@ $root.vereign = (function () { if (options.defaults) { object.plaintText = null; object.html = null; + object.version = ""; } if (message.plaintText != null && message.hasOwnProperty("plaintText")) object.plaintText = $root.vereign.protobuf.qrcode_data.IpfsContentData_V1.toObject(message.plaintText, options); @@ -1442,6 +1482,8 @@ $root.vereign = (function () { for (var j = 0; j < message.attachments.length; ++j) object.attachments[j] = $root.vereign.protobuf.qrcode_data.IpfsAttachmentData_V1.toObject(message.attachments[j], options); } + if (message.version != null && message.hasOwnProperty("version")) + object.version = message.version; return object; }; /** diff --git a/dist/index.d.ts b/dist/index.d.ts index 348a83c2e2ad5063b65da49d3bfba64274514e81..c116b57ea3b5d35efc783c395e9c6dcd01b755bd 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -5,6 +5,7 @@ export { default as VerificationService } from "./services/VerificationService"; export { default as StatusesService } from "./services/StatusesService"; export { default as CommonUtils } from "./utils/common"; export { default as QrCodeDataService } from "./services/QrCodeDataService"; +export { default as IPFSService } from "./services/IPFSService"; export { default as CryptoService } from "./services/CryptoService"; export { default as VerificationError } from "./services/VerificationService/VerificationError"; export { default as QrCodeTemplate } from "./utils/qrCodeTemplateUtils"; diff --git a/dist/index.js b/dist/index.js index 66ed166f2afc3790d69cccaeb73e04bf618734fc..04de50511a1bf02c7a61238c090f246e9ddbce48 100644 --- a/dist/index.js +++ b/dist/index.js @@ -13,7 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.QrCodeTemplate = exports.VerificationError = exports.CryptoService = exports.QrCodeDataService = exports.CommonUtils = exports.StatusesService = exports.VerificationService = exports.CloudflareService = void 0; +exports.QrCodeTemplate = exports.VerificationError = exports.CryptoService = exports.IPFSService = exports.QrCodeDataService = exports.CommonUtils = exports.StatusesService = exports.VerificationService = exports.CloudflareService = void 0; __exportStar(require("./types"), exports); __exportStar(require("./utils/common"), exports); var CloudflareService_1 = require("./services/CloudflareService"); @@ -26,6 +26,8 @@ var common_1 = require("./utils/common"); Object.defineProperty(exports, "CommonUtils", { enumerable: true, get: function () { return __importDefault(common_1).default; } }); var QrCodeDataService_1 = require("./services/QrCodeDataService"); Object.defineProperty(exports, "QrCodeDataService", { enumerable: true, get: function () { return __importDefault(QrCodeDataService_1).default; } }); +var IPFSService_1 = require("./services/IPFSService"); +Object.defineProperty(exports, "IPFSService", { enumerable: true, get: function () { return __importDefault(IPFSService_1).default; } }); var CryptoService_1 = require("./services/CryptoService"); Object.defineProperty(exports, "CryptoService", { enumerable: true, get: function () { return __importDefault(CryptoService_1).default; } }); var VerificationError_1 = require("./services/VerificationService/VerificationError"); diff --git a/dist/services/CryptoService/CryptoServiceNode.d.ts b/dist/services/CryptoService/CryptoServiceNode.d.ts index a476eb05720ababc4a9b14f1c39501fb4a8e14c9..9b91b3caabc22a7c3cd53dabfb017c040546a537 100644 --- a/dist/services/CryptoService/CryptoServiceNode.d.ts +++ b/dist/services/CryptoService/CryptoServiceNode.d.ts @@ -1,3 +1,19 @@ -import { ICryptoService } from "./ICryptoService"; -declare const implementation: ICryptoService; -export default implementation; +/// <reference types="node" /> +import { AESGCMOutput, RSAKeys, ICryptoService } from "./ICryptoService"; +declare class CryptoServiceNode implements ICryptoService { + encryptAESGCM(data: string): Promise<AESGCMOutput>; + encryptAESGCM(data: ArrayBuffer): Promise<AESGCMOutput>; + decryptAESGCM(data: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer): Promise<string>; + decryptAESGCM(data: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer, returnBuffer: true): Promise<ArrayBuffer>; + generateRSAKeys(): Promise<RSAKeys>; + encryptRSA(publicKeyPEM: string, data: ArrayBuffer): Promise<ArrayBuffer>; + decryptRSA(privateKeyPEM: string, data: ArrayBuffer): Promise<ArrayBuffer>; + signRSA(privateKeyPEM: string, data: ArrayBuffer): Promise<ArrayBuffer>; + SHA1(value: string | ArrayBuffer, encoding?: BufferEncoding): Promise<ArrayBuffer>; + SHA256(value: string | ArrayBuffer, encoding?: BufferEncoding): Promise<ArrayBuffer>; + SHA384(value: string | ArrayBuffer, encoding?: BufferEncoding): Promise<ArrayBuffer>; + SHA512(value: string | ArrayBuffer, encoding?: BufferEncoding): Promise<ArrayBuffer>; + MD5(value: string | ArrayBuffer, encoding?: BufferEncoding): Promise<ArrayBuffer>; + verifyRSASignature(publicKeyPEM: string, data: ArrayBuffer, signature: ArrayBuffer): Promise<boolean>; +} +export default CryptoServiceNode; diff --git a/dist/services/CryptoService/CryptoServiceNode.js b/dist/services/CryptoService/CryptoServiceNode.js index 32ab7f691de0342762f57a46716d9968003d3c84..f789e304e709d2a7f8862a7f6d358e83b8b79f9c 100644 --- a/dist/services/CryptoService/CryptoServiceNode.js +++ b/dist/services/CryptoService/CryptoServiceNode.js @@ -36,95 +36,6 @@ const js_md5_1 = __importDefault(require("js-md5")); const crypto = __importStar(require("crypto")); const common_1 = require("../../utils/common"); const AES_GCM_ALGO = "aes-256-gcm"; -const encryptAESGCM = (data) => __awaiter(void 0, void 0, void 0, function* () { - const key = crypto.randomBytes(32); - const iv = crypto.randomBytes(12); - const cipher = crypto.createCipheriv(AES_GCM_ALGO, key, iv); - const encrypted = cipher.update(data, "utf8"); - cipher.final(); - const authTag = cipher.getAuthTag(); - const encryptedWithTag = Buffer.concat([ - Buffer.from(encrypted), - Buffer.from(authTag), - ]); - return { - key: key.buffer, - iv: iv.buffer, - data: encryptedWithTag, - }; -}); -const decryptAESGCM = (data, key, iv) => __awaiter(void 0, void 0, void 0, function* () { - const decipher = crypto.createDecipheriv(AES_GCM_ALGO, Buffer.from(key), Buffer.from(iv)); - const authTag = data.slice(data.byteLength - 16, data.byteLength); - const encrypted = data.slice(0, data.byteLength - 16); - let str = decipher.update((0, common_1.arrayBufferToBase64)(encrypted), "base64", "utf8"); - decipher.setAuthTag(Buffer.from(authTag)); - str += decipher.final("utf8"); - return str; -}); -const generateRSAKeys = () => __awaiter(void 0, void 0, void 0, function* () { - const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", { - modulusLength: 4096, - publicKeyEncoding: { - type: "spki", - format: "pem", - }, - privateKeyEncoding: { - type: "pkcs8", - format: "pem", - }, - }); - return { - publicKeyPEM: publicKey, - privateKeyPEM: privateKey, - }; -}); -const encryptRSA = (publicKeyPEM, data) => __awaiter(void 0, void 0, void 0, function* () { - const encryptedData = crypto.publicEncrypt({ - key: publicKeyPEM, - padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, - oaepHash: "sha256", - }, - // We convert the data string to a buffer using `Buffer.from` - Buffer.from(data)); - return encryptedData.buffer; -}); -const decryptRSA = (privateKeyPEM, data) => __awaiter(void 0, void 0, void 0, function* () { - const encryptedData = crypto.privateDecrypt({ - key: privateKeyPEM, - padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, - oaepHash: "sha256", - }, - // We convert the data string to a buffer using `Buffer.from` - Buffer.from(data)); - return encryptedData.buffer; -}); -const signRSA = (privateKeyPEM, data) => __awaiter(void 0, void 0, void 0, function* () { - const sign = crypto.createSign("SHA256"); - sign.update(Buffer.from(data)); - sign.end(); - return sign.sign(privateKeyPEM).buffer; -}); -const SHA1 = (value, encoding = "utf8") => __awaiter(void 0, void 0, void 0, function* () { - const bytes = getBytes(value, encoding); - return crypto.createHash("sha1").update(bytes).digest(); -}); -const SHA256 = (value, encoding = "utf8") => __awaiter(void 0, void 0, void 0, function* () { - const bytes = getBytes(value, encoding); - return crypto.createHash("sha256").update(bytes).digest(); -}); -const SHA384 = (value, encoding = "utf8") => __awaiter(void 0, void 0, void 0, function* () { - const bytes = getBytes(value, encoding); - return crypto.createHash("sha384").update(bytes).digest(); -}); -const SHA512 = (value, encoding = "utf8") => __awaiter(void 0, void 0, void 0, function* () { - const bytes = getBytes(value, encoding); - return crypto.createHash("sha512").update(bytes).digest(); -}); -const MD5 = (value, encoding = "utf8") => __awaiter(void 0, void 0, void 0, function* () { - const bytes = getBytes(value, encoding); - return js_md5_1.default.arrayBuffer(bytes); -}); const getBytes = (value, encoding) => { let bytes; if (typeof value === "string") { @@ -136,25 +47,137 @@ const getBytes = (value, encoding) => { } return bytes; }; -const verifyRSASignature = (publicKeyPEM, data, signature) => __awaiter(void 0, void 0, void 0, function* () { - const verify = crypto.createVerify("SHA256"); - verify.update(Buffer.from(data)); - verify.end(); - const publicKey = crypto.createPublicKey(Buffer.from(publicKeyPEM, "utf-8")); - return verify.verify(publicKey, Buffer.from(signature)); -}); -const implementation = { - encryptAESGCM, - decryptAESGCM, - generateRSAKeys, - encryptRSA, - decryptRSA, - SHA256, - signRSA, - verifyRSASignature, - SHA512, - SHA384, - SHA1, - MD5, -}; -exports.default = implementation; +class CryptoServiceNode { + encryptAESGCM(data) { + return __awaiter(this, void 0, void 0, function* () { + const key = crypto.randomBytes(32); + const iv = crypto.randomBytes(12); + const cipher = crypto.createCipheriv(AES_GCM_ALGO, key, iv); + let encrypted; + if (typeof data === "string") { + encrypted = cipher.update(data, "utf8"); + } + else { + encrypted = cipher.update((0, common_1.ensureUint8Array)(data)); + } + cipher.final(); + const authTag = cipher.getAuthTag(); + const encryptedWithTag = Buffer.concat([ + Buffer.from(encrypted), + Buffer.from(authTag), + ]); + return { + key: key.buffer, + iv: iv.buffer, + data: encryptedWithTag, + }; + }); + } + decryptAESGCM(data, key, iv, returnBuffer) { + return __awaiter(this, void 0, void 0, function* () { + const decipher = crypto.createDecipheriv(AES_GCM_ALGO, Buffer.from(key), Buffer.from(iv)); + const authTag = data.slice(data.byteLength - 16, data.byteLength); + const encrypted = data.slice(0, data.byteLength - 16); + if (returnBuffer) { + const decrypted = decipher.update((0, common_1.ensureUint8Array)(encrypted)); + decipher.setAuthTag(Buffer.from(authTag)); + return Buffer.concat([decrypted, decipher.final()]); + } + let str = decipher.update((0, common_1.ensureUint8Array)(encrypted), null, "utf8"); + decipher.setAuthTag(Buffer.from(authTag)); + str += decipher.final("utf8"); + return str; + }); + } + generateRSAKeys() { + return __awaiter(this, void 0, void 0, function* () { + const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", { + modulusLength: 4096, + publicKeyEncoding: { + type: "spki", + format: "pem", + }, + privateKeyEncoding: { + type: "pkcs8", + format: "pem", + }, + }); + return { + publicKeyPEM: publicKey, + privateKeyPEM: privateKey, + }; + }); + } + encryptRSA(publicKeyPEM, data) { + return __awaiter(this, void 0, void 0, function* () { + const encryptedData = crypto.publicEncrypt({ + key: publicKeyPEM, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + oaepHash: "sha256", + }, + // We convert the data string to a buffer using `Buffer.from` + Buffer.from(data)); + return encryptedData.buffer; + }); + } + decryptRSA(privateKeyPEM, data) { + return __awaiter(this, void 0, void 0, function* () { + const encryptedData = crypto.privateDecrypt({ + key: privateKeyPEM, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + oaepHash: "sha256", + }, + // We convert the data string to a buffer using `Buffer.from` + Buffer.from(data)); + return encryptedData.buffer; + }); + } + signRSA(privateKeyPEM, data) { + return __awaiter(this, void 0, void 0, function* () { + const sign = crypto.createSign("SHA256"); + sign.update(Buffer.from(data)); + sign.end(); + return sign.sign(privateKeyPEM).buffer; + }); + } + SHA1(value, encoding = "utf8") { + return __awaiter(this, void 0, void 0, function* () { + const bytes = getBytes(value, encoding); + return crypto.createHash("sha1").update(bytes).digest(); + }); + } + SHA256(value, encoding = "utf8") { + return __awaiter(this, void 0, void 0, function* () { + const bytes = getBytes(value, encoding); + return crypto.createHash("sha256").update(bytes).digest(); + }); + } + SHA384(value, encoding = "utf8") { + return __awaiter(this, void 0, void 0, function* () { + const bytes = getBytes(value, encoding); + return crypto.createHash("sha384").update(bytes).digest(); + }); + } + SHA512(value, encoding = "utf8") { + return __awaiter(this, void 0, void 0, function* () { + const bytes = getBytes(value, encoding); + return crypto.createHash("sha512").update(bytes).digest(); + }); + } + MD5(value, encoding = "utf8") { + return __awaiter(this, void 0, void 0, function* () { + const bytes = getBytes(value, encoding); + return js_md5_1.default.arrayBuffer(bytes); + }); + } + verifyRSASignature(publicKeyPEM, data, signature) { + return __awaiter(this, void 0, void 0, function* () { + const verify = crypto.createVerify("SHA256"); + verify.update(Buffer.from(data)); + verify.end(); + const publicKey = crypto.createPublicKey(Buffer.from(publicKeyPEM, "utf-8")); + return verify.verify(publicKey, Buffer.from(signature)); + }); + } +} +exports.default = CryptoServiceNode; diff --git a/dist/services/CryptoService/CryptoServiceWeb.d.ts b/dist/services/CryptoService/CryptoServiceWeb.d.ts index 7fdeee9d024facc4f65ac4610858a01e283d5b80..3ab743beca0bad3313a4e703de3d659fcd3aaa02 100644 --- a/dist/services/CryptoService/CryptoServiceWeb.d.ts +++ b/dist/services/CryptoService/CryptoServiceWeb.d.ts @@ -1,4 +1,18 @@ -import { ICryptoService } from "./ICryptoService"; -export declare const verifyRSASignature: (publicKeyPEM: string, data: ArrayBuffer, signature: ArrayBuffer) => Promise<boolean>; -declare const implementation: ICryptoService; -export default implementation; +import { AESGCMOutput, ICryptoService, RSAKeys } from "./ICryptoService"; +declare class CryptoServiceWeb implements ICryptoService { + encryptAESGCM(data: string): Promise<AESGCMOutput>; + encryptAESGCM(data: ArrayBuffer): Promise<AESGCMOutput>; + decryptAESGCM(data: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer): Promise<string>; + decryptAESGCM(data: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer, returnBuffer: true): Promise<ArrayBuffer>; + verifyRSASignature(publicKeyPEM: string, data: ArrayBuffer, signature: ArrayBuffer): Promise<boolean>; + generateRSAKeys(): Promise<RSAKeys>; + encryptRSA(publicKeyPEM: string, data: ArrayBuffer): Promise<ArrayBuffer>; + decryptRSA(privateKeyPEM: string, data: ArrayBuffer): Promise<ArrayBuffer>; + signRSA(privateKeyPEM: string, data: ArrayBuffer): Promise<ArrayBuffer>; + SHA1(value: string | ArrayBuffer, encoding?: string): Promise<ArrayBuffer>; + SHA256(value: string | ArrayBuffer, encoding?: string): Promise<ArrayBuffer>; + SHA384(value: string | ArrayBuffer, encoding?: string): Promise<ArrayBuffer>; + SHA512(value: string | ArrayBuffer, encoding?: string): Promise<ArrayBuffer>; + MD5(value: string | ArrayBuffer, encoding?: string): Promise<ArrayBuffer>; +} +export default CryptoServiceWeb; diff --git a/dist/services/CryptoService/CryptoServiceWeb.js b/dist/services/CryptoService/CryptoServiceWeb.js index 2164c6a64f0e3ae2731ab24415334e4a72331824..be96a6ee078c2a019054b85e3bf832e26ede3620 100644 --- a/dist/services/CryptoService/CryptoServiceWeb.js +++ b/dist/services/CryptoService/CryptoServiceWeb.js @@ -12,14 +12,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.verifyRSASignature = void 0; const js_md5_1 = __importDefault(require("js-md5")); const common_1 = require("../../utils/common"); -function exportKey(key) { - return __awaiter(this, void 0, void 0, function* () { - return crypto.subtle.exportKey("raw", key); - }); -} +const exportKey = (key) => __awaiter(void 0, void 0, void 0, function* () { + return crypto.subtle.exportKey("raw", key); +}); const convertPemToBinary = (pem) => { const lines = pem.split("\n"); let encoded = ""; @@ -34,64 +31,6 @@ const convertPemToBinary = (pem) => { } return (0, common_1.base64ToArrayBuffer)(encoded); }; -const encryptRSA = (publicKeyPEM, data) => __awaiter(void 0, void 0, void 0, function* () { - const publicKey = yield crypto.subtle.importKey("spki", convertPemToBinary(publicKeyPEM), { - name: "RSA-OAEP", - hash: "SHA-256", - }, true, ["encrypt"]); - return crypto.subtle.encrypt({ - name: "RSA-OAEP", - }, publicKey, data); -}); -const verifyRSASignature = (publicKeyPEM, data, signature) => __awaiter(void 0, void 0, void 0, function* () { - const publicKey = yield crypto.subtle.importKey("spki", convertPemToBinary(publicKeyPEM), { - name: "RSASSA-PKCS1-v1_5", - hash: "SHA-256", - }, true, ["verify"]); - return yield crypto.subtle.verify({ - name: "RSASSA-PKCS1-v1_5", - hash: "SHA-256", - }, publicKey, signature, data); -}); -exports.verifyRSASignature = verifyRSASignature; -const encryptAESGCM = (data) => __awaiter(void 0, void 0, void 0, function* () { - const key = yield crypto.subtle.generateKey({ - name: "AES-GCM", - length: 256, - }, true, ["encrypt", "decrypt"]); - const encoded = new TextEncoder().encode(data); - const iv = crypto.getRandomValues(new Buffer(12)); - const encrypted = yield crypto.subtle.encrypt({ name: "AES-GCM", iv: iv }, key, encoded); - return { data: encrypted, key: yield exportKey(key), iv }; -}); -const decryptAESGCM = (data, key, iv) => __awaiter(void 0, void 0, void 0, function* () { - const importedKey = yield crypto.subtle.importKey("raw", key, { - name: "AES-GCM", - length: 256, - }, true, ["encrypt", "decrypt"]); - const decrypted = yield crypto.subtle.decrypt({ name: "AES-GCM", iv: iv }, importedKey, data); - return new TextDecoder().decode(decrypted); -}); -const SHA1 = (value, encoding = "utf8") => __awaiter(void 0, void 0, void 0, function* () { - const bytes = getBytes(value, encoding); - return yield crypto.subtle.digest("SHA-1", bytes); -}); -const SHA256 = (value, encoding = "utf8") => __awaiter(void 0, void 0, void 0, function* () { - const bytes = getBytes(value, encoding); - return yield crypto.subtle.digest("SHA-256", bytes); -}); -const SHA384 = (value, encoding = "utf8") => __awaiter(void 0, void 0, void 0, function* () { - const bytes = getBytes(value, encoding); - return yield crypto.subtle.digest("SHA-384", bytes); -}); -const SHA512 = (value, encoding = "utf8") => __awaiter(void 0, void 0, void 0, function* () { - const bytes = getBytes(value, encoding); - return yield crypto.subtle.digest("SHA-512", bytes); -}); -const MD5 = (value, encoding = "utf8") => __awaiter(void 0, void 0, void 0, function* () { - const bytes = getBytes(value, encoding); - return js_md5_1.default.arrayBuffer(bytes); -}); const getBytes = (value, encoding) => { let bytes; if (typeof value === "string") { @@ -108,24 +47,105 @@ const getBytes = (value, encoding) => { } return bytes; }; -const implementation = { - encryptAESGCM, - decryptAESGCM, - verifyRSASignature: exports.verifyRSASignature, +class CryptoServiceWeb { + encryptAESGCM(data) { + return __awaiter(this, void 0, void 0, function* () { + const key = yield crypto.subtle.generateKey({ + name: "AES-GCM", + length: 256, + }, true, ["encrypt", "decrypt"]); + let encoded; + if (typeof data === "string") { + encoded = new TextEncoder().encode(data); + } + else { + encoded = data; + } + const iv = crypto.getRandomValues(new Buffer(12)); + const encrypted = yield crypto.subtle.encrypt({ name: "AES-GCM", iv: iv }, key, encoded); + return { data: encrypted, key: yield exportKey(key), iv }; + }); + } + decryptAESGCM(data, key, iv, returnBuffer) { + return __awaiter(this, void 0, void 0, function* () { + const importedKey = yield crypto.subtle.importKey("raw", key, { + name: "AES-GCM", + length: 256, + }, true, ["encrypt", "decrypt"]); + const decrypted = yield crypto.subtle.decrypt({ name: "AES-GCM", iv: iv }, importedKey, data); + if (returnBuffer) { + return decrypted; + } + return new TextDecoder().decode(decrypted); + }); + } + verifyRSASignature(publicKeyPEM, data, signature) { + return __awaiter(this, void 0, void 0, function* () { + const publicKey = yield crypto.subtle.importKey("spki", convertPemToBinary(publicKeyPEM), { + name: "RSASSA-PKCS1-v1_5", + hash: "SHA-256", + }, true, ["verify"]); + return yield crypto.subtle.verify({ + name: "RSASSA-PKCS1-v1_5", + hash: "SHA-256", + }, publicKey, signature, data); + }); + } generateRSAKeys() { - throw new Error("Surprise. Not implemented yet :)"); - }, - encryptRSA, + return __awaiter(this, void 0, void 0, function* () { + throw new Error("The function is not implemented"); + }); + } + encryptRSA(publicKeyPEM, data) { + return __awaiter(this, void 0, void 0, function* () { + const publicKey = yield crypto.subtle.importKey("spki", convertPemToBinary(publicKeyPEM), { + name: "RSA-OAEP", + hash: "SHA-256", + }, true, ["encrypt"]); + return crypto.subtle.encrypt({ + name: "RSA-OAEP", + }, publicKey, data); + }); + } decryptRSA(privateKeyPEM, data) { - throw new Error("Surprise. Not implemented yet :)"); - }, + return __awaiter(this, void 0, void 0, function* () { + throw new Error("The function is not implemented"); + }); + } signRSA(privateKeyPEM, data) { - throw new Error("Surprise. Not implemented yet :)"); - }, - SHA1, - SHA384, - SHA256, - SHA512, - MD5, -}; -exports.default = implementation; + return __awaiter(this, void 0, void 0, function* () { + throw new Error("The function is not implemented"); + }); + } + SHA1(value, encoding = "utf8") { + return __awaiter(this, void 0, void 0, function* () { + const bytes = getBytes(value, encoding); + return yield crypto.subtle.digest("SHA-1", bytes); + }); + } + SHA256(value, encoding = "utf8") { + return __awaiter(this, void 0, void 0, function* () { + const bytes = getBytes(value, encoding); + return yield crypto.subtle.digest("SHA-256", bytes); + }); + } + SHA384(value, encoding = "utf8") { + return __awaiter(this, void 0, void 0, function* () { + const bytes = getBytes(value, encoding); + return yield crypto.subtle.digest("SHA-384", bytes); + }); + } + SHA512(value, encoding = "utf8") { + return __awaiter(this, void 0, void 0, function* () { + const bytes = getBytes(value, encoding); + return yield crypto.subtle.digest("SHA-512", bytes); + }); + } + MD5(value, encoding = "utf8") { + return __awaiter(this, void 0, void 0, function* () { + const bytes = getBytes(value, encoding); + return js_md5_1.default.arrayBuffer(bytes); + }); + } +} +exports.default = CryptoServiceWeb; diff --git a/dist/services/CryptoService/ICryptoService.d.ts b/dist/services/CryptoService/ICryptoService.d.ts index cefd7591fd036f4e6ae6a77339d86a2ec9f5abbe..24dfcd1294c69e9cb0ef9035e48651b8936c8d28 100644 --- a/dist/services/CryptoService/ICryptoService.d.ts +++ b/dist/services/CryptoService/ICryptoService.d.ts @@ -8,8 +8,14 @@ export interface RSAKeys { privateKeyPEM: string; } export interface ICryptoService { - encryptAESGCM: (data: string) => Promise<AESGCMOutput>; - decryptAESGCM: (data: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer) => Promise<string>; + encryptAESGCM: { + (data: string): Promise<AESGCMOutput>; + (data: ArrayBuffer): Promise<AESGCMOutput>; + }; + decryptAESGCM: { + (data: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer): Promise<string>; + (data: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer, returnBuffer: true): Promise<ArrayBuffer>; + }; generateRSAKeys: () => Promise<RSAKeys>; encryptRSA: (publicKeyPEM: string, data: ArrayBuffer) => Promise<ArrayBuffer>; decryptRSA: (privateKeyPEM: string, data: ArrayBuffer) => Promise<ArrayBuffer>; diff --git a/dist/services/CryptoService/index.d.ts b/dist/services/CryptoService/index.d.ts index 9db9c1b552f148203b72746e7994957cce6ab74f..a3ce65fa922fdeddd643b143d3ea70640ade2876 100644 --- a/dist/services/CryptoService/index.d.ts +++ b/dist/services/CryptoService/index.d.ts @@ -1,2 +1,3 @@ -declare const service: import("./ICryptoService").ICryptoService; +import { ICryptoService } from "./ICryptoService"; +declare const service: ICryptoService; export default service; diff --git a/dist/services/CryptoService/index.js b/dist/services/CryptoService/index.js index ce863013f23d371a28f88872926e9ac1b53f39f1..cbea690370e0b558d5a46f7320a362902f8d5ac0 100644 --- a/dist/services/CryptoService/index.js +++ b/dist/services/CryptoService/index.js @@ -6,6 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); const CryptoServiceNode_1 = __importDefault(require("./CryptoServiceNode")); const CryptoServiceWeb_1 = __importDefault(require("./CryptoServiceWeb")); const service = typeof crypto !== "undefined" && crypto.subtle - ? CryptoServiceWeb_1.default - : CryptoServiceNode_1.default; + ? new CryptoServiceWeb_1.default() + : new CryptoServiceNode_1.default(); exports.default = service; diff --git a/dist/services/IPFSService.d.ts b/dist/services/IPFSService.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f9328844c37049ced91b030476b601d5b693adc3 --- /dev/null +++ b/dist/services/IPFSService.d.ts @@ -0,0 +1,9 @@ +import { IpfsContentData, IpfsDataVersion } from "../types"; +declare class IPFSDataRetriever { + ipfsUrl: any; + constructor(ipfsUrl: string); + getDecodedContent(contentData: IpfsContentData, ipfsVersion: IpfsDataVersion): Promise<ArrayBuffer>; + private getRawContent; + private decryptContent; +} +export default IPFSDataRetriever; diff --git a/dist/services/IPFSService.js b/dist/services/IPFSService.js new file mode 100644 index 0000000000000000000000000000000000000000..72d5a8ad30529d2fb3765d5869d81ef4ffe0f154 --- /dev/null +++ b/dist/services/IPFSService.js @@ -0,0 +1,66 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const axios_1 = __importDefault(require("axios")); +const common_1 = require("../utils/common"); +const QrCodeDataService_1 = __importDefault(require("./QrCodeDataService")); +const CryptoService_1 = __importDefault(require("./CryptoService")); +const GET_CONTENT_PATH = "/ipfs"; +class IPFSDataRetriever { + constructor(ipfsUrl) { + this.ipfsUrl = ipfsUrl; + } + getDecodedContent(contentData, ipfsVersion) { + return __awaiter(this, void 0, void 0, function* () { + const rawContent = yield this.getRawContent(contentData.cid, contentData.head, ipfsVersion); + return this.decryptContent(rawContent, contentData.key); + }); + } + getRawContent(cid, head, ipfsVersion) { + return __awaiter(this, void 0, void 0, function* () { + let assembledData; + if (cid) { + let ipfsFileContent; + if (ipfsVersion === undefined) { + const response = yield (0, axios_1.default)({ + url: `${this.ipfsUrl}${GET_CONTENT_PATH}/${cid}`, + method: "GET", + }); + ipfsFileContent = (0, common_1.base64ToArrayBuffer)(response.data); + } + else if (ipfsVersion === "v2") { + const result = yield axios_1.default.get(`${this.ipfsUrl}${GET_CONTENT_PATH}/${cid}`, { responseType: "arraybuffer" }); + ipfsFileContent = Buffer.from(result.data); + } + else { + throw new Error("Unknown qrcode ipfs version"); + } + assembledData = QrCodeDataService_1.default.assembleQrCodeData((0, common_1.base64ToArrayBuffer)(head), ipfsFileContent); + } + else { + assembledData = (0, common_1.base64ToArrayBuffer)(head); + } + return assembledData; + }); + } + decryptContent(content, decryptionKey) { + return __awaiter(this, void 0, void 0, function* () { + const { key, data: iv } = QrCodeDataService_1.default.decodeKeyDataPair(decryptionKey); + const decryptedZip = yield CryptoService_1.default.decryptAESGCM(content, (0, common_1.base64ToArrayBuffer)(key), (0, common_1.base64ToArrayBuffer)(iv), true); + const decompressed = (0, common_1.decompressData)(decryptedZip); + return decompressed; + }); + } +} +exports.default = IPFSDataRetriever; diff --git a/dist/services/QrCodeDataService.d.ts b/dist/services/QrCodeDataService.d.ts index fc6435561bce0f97009745bf8cc3536ef64033e0..1babbf049d295d8854ecdcda67ad720e4fc8a1b0 100644 --- a/dist/services/QrCodeDataService.d.ts +++ b/dist/services/QrCodeDataService.d.ts @@ -1,9 +1,4 @@ import { KeyDataPair, MessageData } from "../types"; -import CloudflareService from "./CloudflareService"; -import { ICryptoService } from "./CryptoService/ICryptoService"; -interface WithServices { - getMessageDataFromBase64: (base64: string) => Promise<MessageData>; -} declare const _default: { encodeEmailData: (emailData: MessageData) => string; decodeEmailData: (binary: string | ArrayBuffer | Uint8Array) => MessageData; @@ -14,9 +9,7 @@ declare const _default: { tail: ArrayBuffer; }; assembleQrCodeData: (head: ArrayBuffer, tail: ArrayBuffer) => ArrayBuffer; - computeQrCodeHash: (emailData: MessageData) => Promise<string>; calculateQRCodeSignature: (accountPrivateKey: string, qrCodeHash: string) => Promise<string>; verifyQrCodeSignature: (publicKey: string, qrCodeSignature: string, recipientQRCodeHash: string) => Promise<boolean>; - withServices: (cloudFlareServiceInstance: CloudflareService, landingPageServiceUrl: string, cryptoService?: ICryptoService) => WithServices; }; export default _default; diff --git a/dist/services/QrCodeDataService.js b/dist/services/QrCodeDataService.js index 955f842a65a54792f93146d0bc74674d786ddb9d..1c668db6c7598507da2882538839fcad47506bd2 100644 --- a/dist/services/QrCodeDataService.js +++ b/dist/services/QrCodeDataService.js @@ -8,14 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; Object.defineProperty(exports, "__esModule", { value: true }); -const axios_1 = __importDefault(require("axios")); const qrcode_data_pb_1 = require("../generated/qrcode_data_pb"); const common_1 = require("../utils/common"); -const CryptoService_1 = __importDefault(require("./CryptoService")); const index_1 = require("../index"); const EmailDataMessageV1 = qrcode_data_pb_1.vereign.protobuf.qrcode_data.EmailData_V1; const KeyDataMessageV1 = qrcode_data_pb_1.vereign.protobuf.qrcode_data.KeyData_V1; @@ -78,49 +73,6 @@ const breakQrCodeData = (data, headBytesSize = 32) => { const assembleQrCodeData = (head, tail) => { return Buffer.concat([Buffer.from(head), Buffer.from(tail)]); }; -const computeQrCodeHash = (emailData) => __awaiter(void 0, void 0, void 0, function* () { - let attachments = []; - if (emailData.attachments) { - attachments = emailData.attachments.map((attachment) => { - //TODO: check if we can use attachment hash, attachment hashAlg - if (attachment.url) { - return { - name: attachment.name, - size: attachment.size, - url: attachment.url, - }; - } - return { - name: attachment.name, - size: attachment.size, - }; - }); - } - const dataForHashing = { - sender: emailData.sender, - subject: emailData.subject, - recipients: emailData.recipients, - attachments, - }; - const promises = Object.values(dataForHashing).map((value) => { - const string = Buffer.from(JSON.stringify(value, (key, value) => value instanceof Object && !(value instanceof Array) - ? Object.keys(value) - .sort() - .reduce((sorted, key) => { - sorted[key] = value[key]; - return sorted; - }, {}) - : value)).toString(); - return index_1.CryptoService.SHA256(string); - }); - const hashArray = yield Promise.all(promises); - const hashesAsAstring = hashArray - .map(common_1.arrayBufferToBase64) - .sort() - .join("\n"); - const resultBuffer = yield index_1.CryptoService.SHA256(hashesAsAstring); - return (0, common_1.arrayBufferToBase64)(resultBuffer); -}); const calculateQRCodeSignature = (accountPrivateKey, qrCodeHash) => __awaiter(void 0, void 0, void 0, function* () { const signature = yield index_1.CryptoService.signRSA(accountPrivateKey, Buffer.from(qrCodeHash)); return (0, common_1.arrayBufferToBase64)(signature); @@ -131,46 +83,6 @@ const verifyQrCodeSignature = (publicKey, qrCodeSignature, recipientQRCodeHash) const result = yield index_1.CryptoService.verifyRSASignature(publicKey, recipientQRCodeHashAsBuffer, qrCodeSignatureAsBuffer); return result; }); -/** - * Higher order function to perform complex operations with the QR code data - * @param cloudFlareServiceInstance = new CloudflareService("CDN_RUL", "DEFAULT_BUCKET") - * @param landingPageServiceUrl - URL of the service responsible for RSA decryption - * @param cryptoService - instance of ICryptoService. Override only needed cryptographic functions. - * The rest will be filled with default ones from CryptoServiceWeb/CryptoServiceNode - */ -const withServices = (cloudFlareServiceInstance, landingPageServiceUrl, cryptoService) => { - if (cryptoService) { - cryptoService = Object.assign(Object.assign({}, CryptoService_1.default), cryptoService); - } - else { - cryptoService = CryptoService_1.default; - } - return { - getMessageDataFromBase64: (base64) => __awaiter(void 0, void 0, void 0, function* () { - const qrCodeDataPart = decodeKeyDataPair(base64); - const base64SHA256 = yield cryptoService.SHA256(base64); - const { data: backblazeDataPart, } = yield cloudFlareServiceInstance.fetchFile(`qrcode-${(0, common_1.arrayBufferToHex)(base64SHA256)}`); - const assembledData = assembleQrCodeData((0, common_1.base64ToArrayBuffer)(qrCodeDataPart.data), (0, common_1.base64ToArrayBuffer)(backblazeDataPart.qr_code_data)); - const { key: aesEncryptedSessionKey, data: storageIv, } = decodeKeyDataPair(backblazeDataPart.session_key); - const rsaEncryptedSessionKey = yield cryptoService.decryptAESGCM((0, common_1.base64ToArrayBuffer)(aesEncryptedSessionKey), (0, common_1.base64ToArrayBuffer)(qrCodeDataPart.key), (0, common_1.base64ToArrayBuffer)(storageIv)); - const { data: { key: encodedSessionKey }, } = yield (0, axios_1.default)({ - url: `${landingPageServiceUrl}/api/hsm/decrypt`, - method: "POST", - headers: { - "Content-Type": "application/json", - }, - data: { - key_signature: backblazeDataPart.key_signature, - encrypted_key: rsaEncryptedSessionKey, - }, - }); - const decodedSessionKey = decodeKeyDataPair(encodedSessionKey); - const decryptedEmailData = yield cryptoService.decryptAESGCM(assembledData, (0, common_1.base64ToArrayBuffer)(decodedSessionKey.key), (0, common_1.base64ToArrayBuffer)(decodedSessionKey.data)); - const decompressedEmailData = (0, common_1.decompressData)(decryptedEmailData); - return decodeEmailData(decompressedEmailData); - }), - }; -}; exports.default = { encodeEmailData, decodeEmailData, @@ -178,8 +90,6 @@ exports.default = { decodeKeyDataPair, breakQrCodeData, assembleQrCodeData, - computeQrCodeHash, calculateQRCodeSignature, verifyQrCodeSignature, - withServices, }; diff --git a/dist/types.d.ts b/dist/types.d.ts index dd2fbf38a1276a0c8a2eec7e2327bcf340372717..e346f229980231919afc52f9b8194e0af5367087 100644 --- a/dist/types.d.ts +++ b/dist/types.d.ts @@ -21,8 +21,11 @@ export interface IpfsAttachmentData { key: string; contentHash: string; head: string; + contentType: string; } +export declare type IpfsDataVersion = undefined | "v2"; export interface IpfsData { + version: IpfsDataVersion; plaintText: IpfsContentData; html: IpfsContentData; attachments: Array<IpfsAttachmentData>; diff --git a/src/index.ts b/src/index.ts index 348a83c2e2ad5063b65da49d3bfba64274514e81..c116b57ea3b5d35efc783c395e9c6dcd01b755bd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ export { default as VerificationService } from "./services/VerificationService"; export { default as StatusesService } from "./services/StatusesService"; export { default as CommonUtils } from "./utils/common"; export { default as QrCodeDataService } from "./services/QrCodeDataService"; +export { default as IPFSService } from "./services/IPFSService"; export { default as CryptoService } from "./services/CryptoService"; export { default as VerificationError } from "./services/VerificationService/VerificationError"; export { default as QrCodeTemplate } from "./utils/qrCodeTemplateUtils"; diff --git a/src/services/CryptoService/CryptoServiceNode.ts b/src/services/CryptoService/CryptoServiceNode.ts index b776a02640a1de9ebc9714463ecd36ae1d750b61..c98ecf2187558d3e30df438d1601e3ed6de46d41 100644 --- a/src/services/CryptoService/CryptoServiceNode.ts +++ b/src/services/CryptoService/CryptoServiceNode.ts @@ -4,154 +4,10 @@ import md5 from "js-md5"; import * as crypto from "crypto"; import { AESGCMOutput, RSAKeys, ICryptoService } from "./ICryptoService"; -import { arrayBufferToBase64 } from "../../utils/common"; +import { ensureUint8Array } from "../../utils/common"; const AES_GCM_ALGO = "aes-256-gcm"; -const encryptAESGCM = async (data: string): Promise<AESGCMOutput> => { - const key = crypto.randomBytes(32); - const iv = crypto.randomBytes(12); - const cipher = crypto.createCipheriv(AES_GCM_ALGO, key, iv); - - const encrypted = cipher.update(data, "utf8"); - cipher.final(); - - const authTag = cipher.getAuthTag(); - const encryptedWithTag = Buffer.concat([ - Buffer.from(encrypted), - Buffer.from(authTag), - ]); - - return { - key: key.buffer, - iv: iv.buffer, - data: encryptedWithTag, - }; -}; - -const decryptAESGCM = async ( - data: ArrayBuffer, - key: ArrayBuffer, - iv: ArrayBuffer -): Promise<string> => { - const decipher = crypto.createDecipheriv( - AES_GCM_ALGO, - Buffer.from(key), - Buffer.from(iv) - ); - - const authTag = data.slice(data.byteLength - 16, data.byteLength); - const encrypted = data.slice(0, data.byteLength - 16); - let str = decipher.update(arrayBufferToBase64(encrypted), "base64", "utf8"); - decipher.setAuthTag(Buffer.from(authTag)); - str += decipher.final("utf8"); - return str; -}; - -const generateRSAKeys = async (): Promise<RSAKeys> => { - const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", { - modulusLength: 4096, - publicKeyEncoding: { - type: "spki", - format: "pem", - }, - privateKeyEncoding: { - type: "pkcs8", - format: "pem", - }, - }); - - return { - publicKeyPEM: publicKey, - privateKeyPEM: privateKey, - }; -}; - -const encryptRSA = async ( - publicKeyPEM: string, - data: ArrayBuffer -): Promise<ArrayBuffer> => { - const encryptedData = crypto.publicEncrypt( - { - key: publicKeyPEM, - padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, - oaepHash: "sha256", - }, - // We convert the data string to a buffer using `Buffer.from` - Buffer.from(data) - ); - - return encryptedData.buffer; -}; - -const decryptRSA = async ( - privateKeyPEM: string, - data: ArrayBuffer -): Promise<ArrayBuffer> => { - const encryptedData = crypto.privateDecrypt( - { - key: privateKeyPEM, - padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, - oaepHash: "sha256", - }, - // We convert the data string to a buffer using `Buffer.from` - Buffer.from(data) - ); - - return encryptedData.buffer; -}; - -const signRSA = async ( - privateKeyPEM: string, - data: ArrayBuffer -): Promise<ArrayBuffer> => { - const sign = crypto.createSign("SHA256"); - sign.update(Buffer.from(data)); - sign.end(); - - return sign.sign(privateKeyPEM).buffer; -}; - -const SHA1 = async ( - value: string | ArrayBuffer, - encoding: BufferEncoding = "utf8" -): Promise<ArrayBuffer> => { - const bytes = getBytes(value, encoding); - return crypto.createHash("sha1").update(bytes).digest(); -}; - -const SHA256 = async ( - value: string | ArrayBuffer, - encoding: BufferEncoding = "utf8" -): Promise<ArrayBuffer> => { - const bytes = getBytes(value, encoding); - return crypto.createHash("sha256").update(bytes).digest(); -}; - -const SHA384 = async ( - value: string | ArrayBuffer, - encoding: BufferEncoding = "utf8" -): Promise<ArrayBuffer> => { - const bytes = getBytes(value, encoding); - return crypto.createHash("sha384").update(bytes).digest(); -}; - -const SHA512 = async ( - value: string | ArrayBuffer, - encoding: BufferEncoding = "utf8" -): Promise<ArrayBuffer> => { - const bytes = getBytes(value, encoding); - return crypto.createHash("sha512").update(bytes).digest(); -}; - -const MD5 = async ( - value: string | ArrayBuffer, - encoding: BufferEncoding = "utf8" -): Promise<ArrayBuffer> => { - const bytes = getBytes(value, encoding); - return md5.arrayBuffer(bytes); -}; - const getBytes = ( value: string | ArrayBuffer, encoding: BufferEncoding @@ -166,33 +22,193 @@ const getBytes = ( return bytes; }; -const verifyRSASignature = async ( - publicKeyPEM: string, - data: ArrayBuffer, - signature: ArrayBuffer -): Promise<boolean> => { - const verify = crypto.createVerify("SHA256"); - verify.update(Buffer.from(data)); - verify.end(); +class CryptoServiceNode implements ICryptoService { + public async encryptAESGCM(data: string): Promise<AESGCMOutput>; + public async encryptAESGCM(data: ArrayBuffer): Promise<AESGCMOutput>; + public async encryptAESGCM( + data: string | ArrayBuffer + ): Promise<AESGCMOutput> { + const key = crypto.randomBytes(32); + const iv = crypto.randomBytes(12); + const cipher = crypto.createCipheriv(AES_GCM_ALGO, key, iv); + + let encrypted; + if (typeof data === "string") { + encrypted = cipher.update(data, "utf8"); + } else { + encrypted = cipher.update(ensureUint8Array(data)); + } + cipher.final(); + + const authTag = cipher.getAuthTag(); + const encryptedWithTag = Buffer.concat([ + Buffer.from(encrypted), + Buffer.from(authTag), + ]); + + return { + key: key.buffer, + iv: iv.buffer, + data: encryptedWithTag, + }; + } - const publicKey = crypto.createPublicKey(Buffer.from(publicKeyPEM, "utf-8")); + public async decryptAESGCM( + data: ArrayBuffer, + key: ArrayBuffer, + iv: ArrayBuffer + ): Promise<string>; + public async decryptAESGCM( + data: ArrayBuffer, + key: ArrayBuffer, + iv: ArrayBuffer, + returnBuffer: true + ): Promise<ArrayBuffer>; + public async decryptAESGCM( + data: ArrayBuffer, + key: ArrayBuffer, + iv: ArrayBuffer, + returnBuffer?: boolean + ): Promise<string | ArrayBuffer> { + const decipher = crypto.createDecipheriv( + AES_GCM_ALGO, + Buffer.from(key), + Buffer.from(iv) + ); + + const authTag = data.slice(data.byteLength - 16, data.byteLength); + const encrypted = data.slice(0, data.byteLength - 16); + if (returnBuffer) { + const decrypted: Buffer = decipher.update(ensureUint8Array(encrypted)); + decipher.setAuthTag(Buffer.from(authTag)); + return Buffer.concat([decrypted, decipher.final()]); + } + + let str = decipher.update(ensureUint8Array(encrypted), null, "utf8"); + decipher.setAuthTag(Buffer.from(authTag)); + str += decipher.final("utf8"); + return str; + } - return verify.verify(publicKey, Buffer.from(signature)); -}; + public async generateRSAKeys(): Promise<RSAKeys> { + const { privateKey, publicKey } = crypto.generateKeyPairSync("rsa", { + modulusLength: 4096, + publicKeyEncoding: { + type: "spki", + format: "pem", + }, + privateKeyEncoding: { + type: "pkcs8", + format: "pem", + }, + }); + + return { + publicKeyPEM: publicKey, + privateKeyPEM: privateKey, + }; + } -const implementation: ICryptoService = { - encryptAESGCM, - decryptAESGCM, - generateRSAKeys, - encryptRSA, - decryptRSA, - SHA256, - signRSA, - verifyRSASignature, - SHA512, - SHA384, - SHA1, - MD5, -}; + public async encryptRSA( + publicKeyPEM: string, + data: ArrayBuffer + ): Promise<ArrayBuffer> { + const encryptedData = crypto.publicEncrypt( + { + key: publicKeyPEM, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + oaepHash: "sha256", + }, + // We convert the data string to a buffer using `Buffer.from` + Buffer.from(data) + ); + + return encryptedData.buffer; + } + + public async decryptRSA( + privateKeyPEM: string, + data: ArrayBuffer + ): Promise<ArrayBuffer> { + const encryptedData = crypto.privateDecrypt( + { + key: privateKeyPEM, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + oaepHash: "sha256", + }, + // We convert the data string to a buffer using `Buffer.from` + Buffer.from(data) + ); + + return encryptedData.buffer; + } + + public async signRSA( + privateKeyPEM: string, + data: ArrayBuffer + ): Promise<ArrayBuffer> { + const sign = crypto.createSign("SHA256"); + sign.update(Buffer.from(data)); + sign.end(); + + return sign.sign(privateKeyPEM).buffer; + } + + public async SHA1( + value: string | ArrayBuffer, + encoding: BufferEncoding = "utf8" + ): Promise<ArrayBuffer> { + const bytes = getBytes(value, encoding); + return crypto.createHash("sha1").update(bytes).digest(); + } + + public async SHA256( + value: string | ArrayBuffer, + encoding: BufferEncoding = "utf8" + ): Promise<ArrayBuffer> { + const bytes = getBytes(value, encoding); + return crypto.createHash("sha256").update(bytes).digest(); + } + + public async SHA384( + value: string | ArrayBuffer, + encoding: BufferEncoding = "utf8" + ): Promise<ArrayBuffer> { + const bytes = getBytes(value, encoding); + return crypto.createHash("sha384").update(bytes).digest(); + } + + public async SHA512( + value: string | ArrayBuffer, + encoding: BufferEncoding = "utf8" + ): Promise<ArrayBuffer> { + const bytes = getBytes(value, encoding); + return crypto.createHash("sha512").update(bytes).digest(); + } + + public async MD5( + value: string | ArrayBuffer, + encoding: BufferEncoding = "utf8" + ): Promise<ArrayBuffer> { + const bytes = getBytes(value, encoding); + return md5.arrayBuffer(bytes); + } + + public async verifyRSASignature( + publicKeyPEM: string, + data: ArrayBuffer, + signature: ArrayBuffer + ): Promise<boolean> { + const verify = crypto.createVerify("SHA256"); + verify.update(Buffer.from(data)); + verify.end(); + + const publicKey = crypto.createPublicKey( + Buffer.from(publicKeyPEM, "utf-8") + ); + + return verify.verify(publicKey, Buffer.from(signature)); + } +} -export default implementation; +export default CryptoServiceNode; diff --git a/src/services/CryptoService/CryptoServiceWeb.ts b/src/services/CryptoService/CryptoServiceWeb.ts index 1290a87759697548d5aebb6f7ee5b4feb9cfe3a9..d5ef7a08fee5c44b84b7df08b9c70016f4ac81a8 100644 --- a/src/services/CryptoService/CryptoServiceWeb.ts +++ b/src/services/CryptoService/CryptoServiceWeb.ts @@ -2,9 +2,9 @@ import md5 from "js-md5"; import { AESGCMOutput, ICryptoService, RSAKeys } from "./ICryptoService"; import { base64ToArrayBuffer } from "../../utils/common"; -async function exportKey(key: CryptoKey): Promise<ArrayBuffer> { +const exportKey = async (key: CryptoKey): Promise<ArrayBuffer> => { return crypto.subtle.exportKey("raw", key); -} +}; const convertPemToBinary = (pem: string) => { const lines = pem.split("\n"); @@ -23,141 +23,6 @@ const convertPemToBinary = (pem: string) => { return base64ToArrayBuffer(encoded); }; -const encryptRSA = async ( - publicKeyPEM: string, - data: ArrayBuffer -): Promise<ArrayBuffer> => { - const publicKey = await crypto.subtle.importKey( - "spki", - convertPemToBinary(publicKeyPEM), - { - name: "RSA-OAEP", - hash: "SHA-256", - }, - true, - ["encrypt"] - ); - - return crypto.subtle.encrypt( - { - name: "RSA-OAEP", - }, - publicKey, - data - ); -}; - -export const verifyRSASignature = async ( - publicKeyPEM: string, - data: ArrayBuffer, - signature: ArrayBuffer -): Promise<boolean> => { - const publicKey = await crypto.subtle.importKey( - "spki", - convertPemToBinary(publicKeyPEM), - { - name: "RSASSA-PKCS1-v1_5", - hash: "SHA-256", - }, - true, - ["verify"] - ); - - return await crypto.subtle.verify( - { - name: "RSASSA-PKCS1-v1_5", - hash: "SHA-256", - }, - publicKey, - signature, - data - ); -}; - -const encryptAESGCM = async (data: string): Promise<AESGCMOutput> => { - const key = await crypto.subtle.generateKey( - { - name: "AES-GCM", - length: 256, - }, - true, - ["encrypt", "decrypt"] - ); - - const encoded = new TextEncoder().encode(data); - const iv = crypto.getRandomValues(new Buffer(12)); - const encrypted = await crypto.subtle.encrypt( - { name: "AES-GCM", iv: iv }, - key, - encoded - ); - return { data: encrypted, key: await exportKey(key), iv }; -}; - -const decryptAESGCM = async ( - data: ArrayBuffer, - key: ArrayBuffer, - iv: ArrayBuffer -): Promise<string> => { - const importedKey = await crypto.subtle.importKey( - "raw", - key, - { - name: "AES-GCM", - length: 256, - }, - true, - ["encrypt", "decrypt"] - ); - - const decrypted = await crypto.subtle.decrypt( - { name: "AES-GCM", iv: iv }, - importedKey, - data - ); - return new TextDecoder().decode(decrypted); -}; - -const SHA1 = async ( - value: string | ArrayBuffer, - encoding = "utf8" -): Promise<ArrayBuffer> => { - const bytes = getBytes(value, encoding); - return await crypto.subtle.digest("SHA-1", bytes); -}; - -const SHA256 = async ( - value: string | ArrayBuffer, - encoding = "utf8" -): Promise<ArrayBuffer> => { - const bytes = getBytes(value, encoding); - return await crypto.subtle.digest("SHA-256", bytes); -}; - -const SHA384 = async ( - value: string | ArrayBuffer, - encoding = "utf8" -): Promise<ArrayBuffer> => { - const bytes = getBytes(value, encoding); - return await crypto.subtle.digest("SHA-384", bytes); -}; - -const SHA512 = async ( - value: string | ArrayBuffer, - encoding = "utf8" -): Promise<ArrayBuffer> => { - const bytes = getBytes(value, encoding); - return await crypto.subtle.digest("SHA-512", bytes); -}; - -const MD5 = async ( - value: string | ArrayBuffer, - encoding = "utf8" -): Promise<ArrayBuffer> => { - const bytes = getBytes(value, encoding); - return md5.arrayBuffer(bytes); -}; - const getBytes = (value: string | ArrayBuffer, encoding): ArrayBuffer => { let bytes; if (typeof value === "string") { @@ -173,25 +38,183 @@ const getBytes = (value: string | ArrayBuffer, encoding): ArrayBuffer => { return bytes; }; -const implementation: ICryptoService = { - encryptAESGCM, - decryptAESGCM, - verifyRSASignature, - generateRSAKeys(): Promise<RSAKeys> { - throw new Error("Surprise. Not implemented yet :)"); - }, - encryptRSA, - decryptRSA(privateKeyPEM: string, data: ArrayBuffer): Promise<ArrayBuffer> { - throw new Error("Surprise. Not implemented yet :)"); - }, - signRSA(privateKeyPEM: string, data: ArrayBuffer): Promise<ArrayBuffer> { - throw new Error("Surprise. Not implemented yet :)"); - }, - SHA1, - SHA384, - SHA256, - SHA512, - MD5, -}; +class CryptoServiceWeb implements ICryptoService { + public async encryptAESGCM(data: string): Promise<AESGCMOutput>; + public async encryptAESGCM(data: ArrayBuffer): Promise<AESGCMOutput>; + public async encryptAESGCM( + data: string | ArrayBuffer + ): Promise<AESGCMOutput> { + const key = await crypto.subtle.generateKey( + { + name: "AES-GCM", + length: 256, + }, + true, + ["encrypt", "decrypt"] + ); + + let encoded; + if (typeof data === "string") { + encoded = new TextEncoder().encode(data); + } else { + encoded = data; + } + const iv = crypto.getRandomValues(new Buffer(12)); + const encrypted = await crypto.subtle.encrypt( + { name: "AES-GCM", iv: iv }, + key, + encoded + ); + return { data: encrypted, key: await exportKey(key), iv }; + } + + public async decryptAESGCM( + data: ArrayBuffer, + key: ArrayBuffer, + iv: ArrayBuffer + ): Promise<string>; + public async decryptAESGCM( + data: ArrayBuffer, + key: ArrayBuffer, + iv: ArrayBuffer, + returnBuffer: true + ): Promise<ArrayBuffer>; + public async decryptAESGCM( + data: ArrayBuffer, + key: ArrayBuffer, + iv: ArrayBuffer, + returnBuffer?: boolean + ): Promise<string | ArrayBuffer> { + const importedKey = await crypto.subtle.importKey( + "raw", + key, + { + name: "AES-GCM", + length: 256, + }, + true, + ["encrypt", "decrypt"] + ); + + const decrypted = await crypto.subtle.decrypt( + { name: "AES-GCM", iv: iv }, + importedKey, + data + ); + if (returnBuffer) { + return decrypted; + } + return new TextDecoder().decode(decrypted); + } + + public async verifyRSASignature( + publicKeyPEM: string, + data: ArrayBuffer, + signature: ArrayBuffer + ): Promise<boolean> { + const publicKey = await crypto.subtle.importKey( + "spki", + convertPemToBinary(publicKeyPEM), + { + name: "RSASSA-PKCS1-v1_5", + hash: "SHA-256", + }, + true, + ["verify"] + ); + + return await crypto.subtle.verify( + { + name: "RSASSA-PKCS1-v1_5", + hash: "SHA-256", + }, + publicKey, + signature, + data + ); + } + + public async generateRSAKeys(): Promise<RSAKeys> { + throw new Error("The function is not implemented"); + } + + public async encryptRSA( + publicKeyPEM: string, + data: ArrayBuffer + ): Promise<ArrayBuffer> { + const publicKey = await crypto.subtle.importKey( + "spki", + convertPemToBinary(publicKeyPEM), + { + name: "RSA-OAEP", + hash: "SHA-256", + }, + true, + ["encrypt"] + ); + + return crypto.subtle.encrypt( + { + name: "RSA-OAEP", + }, + publicKey, + data + ); + } + + public async decryptRSA( + privateKeyPEM: string, + data: ArrayBuffer + ): Promise<ArrayBuffer> { + throw new Error("The function is not implemented"); + } + + public async signRSA( + privateKeyPEM: string, + data: ArrayBuffer + ): Promise<ArrayBuffer> { + throw new Error("The function is not implemented"); + } + + public async SHA1( + value: string | ArrayBuffer, + encoding = "utf8" + ): Promise<ArrayBuffer> { + const bytes = getBytes(value, encoding); + return await crypto.subtle.digest("SHA-1", bytes); + } + + public async SHA256( + value: string | ArrayBuffer, + encoding = "utf8" + ): Promise<ArrayBuffer> { + const bytes = getBytes(value, encoding); + return await crypto.subtle.digest("SHA-256", bytes); + } + + public async SHA384( + value: string | ArrayBuffer, + encoding = "utf8" + ): Promise<ArrayBuffer> { + const bytes = getBytes(value, encoding); + return await crypto.subtle.digest("SHA-384", bytes); + } + + public async SHA512( + value: string | ArrayBuffer, + encoding = "utf8" + ): Promise<ArrayBuffer> { + const bytes = getBytes(value, encoding); + return await crypto.subtle.digest("SHA-512", bytes); + } + + public async MD5( + value: string | ArrayBuffer, + encoding = "utf8" + ): Promise<ArrayBuffer> { + const bytes = getBytes(value, encoding); + return md5.arrayBuffer(bytes); + } +} -export default implementation; +export default CryptoServiceWeb; diff --git a/src/services/CryptoService/ICryptoService.ts b/src/services/CryptoService/ICryptoService.ts index a995e9770e35581e83a90291ea10b192767d2b35..0dfd66a86ab28a6a4fe1a2138a78628f18c20a06 100644 --- a/src/services/CryptoService/ICryptoService.ts +++ b/src/services/CryptoService/ICryptoService.ts @@ -10,12 +10,19 @@ export interface RSAKeys { } export interface ICryptoService { - encryptAESGCM: (data: string) => Promise<AESGCMOutput>; - decryptAESGCM: ( - data: ArrayBuffer, - key: ArrayBuffer, - iv: ArrayBuffer - ) => Promise<string>; + encryptAESGCM: { + (data: string): Promise<AESGCMOutput>; + (data: ArrayBuffer): Promise<AESGCMOutput>; + }; + decryptAESGCM: { + (data: ArrayBuffer, key: ArrayBuffer, iv: ArrayBuffer): Promise<string>; + ( + data: ArrayBuffer, + key: ArrayBuffer, + iv: ArrayBuffer, + returnBuffer: true + ): Promise<ArrayBuffer>; + }; generateRSAKeys: () => Promise<RSAKeys>; encryptRSA: (publicKeyPEM: string, data: ArrayBuffer) => Promise<ArrayBuffer>; decryptRSA: ( diff --git a/src/services/CryptoService/index.ts b/src/services/CryptoService/index.ts index 601f403476c50ef6f5ddb1b5afe5d81437f1e07b..1ef2637ecf56b94f5c25ec53c219bb0af6732673 100644 --- a/src/services/CryptoService/index.ts +++ b/src/services/CryptoService/index.ts @@ -1,9 +1,10 @@ import CryptoServiceNode from "./CryptoServiceNode"; import CryptoServiceWeb from "./CryptoServiceWeb"; +import { ICryptoService } from "./ICryptoService"; -const service = +const service: ICryptoService = typeof crypto !== "undefined" && crypto.subtle - ? CryptoServiceWeb - : CryptoServiceNode; + ? new CryptoServiceWeb() + : new CryptoServiceNode(); export default service; diff --git a/src/services/IPFSService.ts b/src/services/IPFSService.ts new file mode 100644 index 0000000000000000000000000000000000000000..18a89c52087b4370cff1fe2792c7fe0eb9f548b1 --- /dev/null +++ b/src/services/IPFSService.ts @@ -0,0 +1,80 @@ +import axios from "axios"; +import { IpfsContentData, IpfsDataVersion } from "../types"; +import { base64ToArrayBuffer, decompressData } from "../utils/common"; +import QrCodeDataService from "./QrCodeDataService"; +import CryptoService from "./CryptoService"; + +const GET_CONTENT_PATH = "/ipfs"; + +class IPFSDataRetriever { + ipfsUrl; + constructor(ipfsUrl: string) { + this.ipfsUrl = ipfsUrl; + } + + public async getDecodedContent( + contentData: IpfsContentData, + ipfsVersion: IpfsDataVersion + ): Promise<ArrayBuffer> { + const rawContent = await this.getRawContent( + contentData.cid, + contentData.head, + ipfsVersion + ); + return this.decryptContent(rawContent, contentData.key); + } + + private async getRawContent( + cid: string, + head: string, + ipfsVersion: IpfsDataVersion + ): Promise<ArrayBuffer> { + let assembledData; + if (cid) { + let ipfsFileContent; + if (ipfsVersion === undefined) { + const response = await axios({ + url: `${this.ipfsUrl}${GET_CONTENT_PATH}/${cid}`, + method: "GET", + }); + ipfsFileContent = base64ToArrayBuffer(response.data); + } else if (ipfsVersion === "v2") { + const result = await axios.get( + `${this.ipfsUrl}${GET_CONTENT_PATH}/${cid}`, + { responseType: "arraybuffer" } + ); + ipfsFileContent = Buffer.from(result.data); + } else { + throw new Error("Unknown qrcode ipfs version"); + } + assembledData = QrCodeDataService.assembleQrCodeData( + base64ToArrayBuffer(head), + ipfsFileContent + ); + } else { + assembledData = base64ToArrayBuffer(head); + } + return assembledData; + } + + private async decryptContent( + content: ArrayBuffer, + decryptionKey: string + ): Promise<ArrayBuffer> { + const { key, data: iv } = + QrCodeDataService.decodeKeyDataPair(decryptionKey); + + const decryptedZip = await CryptoService.decryptAESGCM( + content, + base64ToArrayBuffer(key), + base64ToArrayBuffer(iv), + true + ); + + const decompressed = decompressData(decryptedZip); + + return decompressed; + } +} + +export default IPFSDataRetriever; diff --git a/src/services/QrCodeDataService.ts b/src/services/QrCodeDataService.ts index da633e1a9b7848c21356a376aed49758f0dc7dc2..03bc827ef1452d97d5059839e28c31167d668879 100644 --- a/src/services/QrCodeDataService.ts +++ b/src/services/QrCodeDataService.ts @@ -1,4 +1,3 @@ -import axios from "axios"; import { KeyDataPair, MessageData } from "../types"; import { vereign } from "../generated/qrcode_data_pb"; @@ -6,13 +5,8 @@ import { arrayBufferToBase64, base64ToArrayBuffer, ensureUint8Array, - decompressData, - arrayBufferToHex, } from "../utils/common"; -import CloudflareService from "./CloudflareService"; -import CryptoServiceDefault from "./CryptoService"; -import { ICryptoService } from "./CryptoService/ICryptoService"; import { CryptoService } from "../index"; const EmailDataMessageV1 = vereign.protobuf.qrcode_data.EmailData_V1; @@ -38,9 +32,8 @@ const encodeEmailData = (emailData: MessageData): string => { }; const wrappedDataMessage = WrapperDataMessage.fromObject(wrappedData); - const wrappedDataBuffer = WrapperDataMessage.encode( - wrappedDataMessage - ).finish(); + const wrappedDataBuffer = + WrapperDataMessage.encode(wrappedDataMessage).finish(); return arrayBufferToBase64(wrappedDataBuffer); }; @@ -112,62 +105,6 @@ const assembleQrCodeData = ( return Buffer.concat([Buffer.from(head), Buffer.from(tail)]); }; -const computeQrCodeHash = async (emailData: MessageData): Promise<string> => { - let attachments = []; - if (emailData.attachments) { - attachments = emailData.attachments.map((attachment) => { - //TODO: check if we can use attachment hash, attachment hashAlg - if (attachment.url) { - return { - name: attachment.name, - size: attachment.size, - url: attachment.url, - }; - } - - return { - name: attachment.name, - size: attachment.size, - }; - }); - } - - const dataForHashing = { - sender: emailData.sender, - subject: emailData.subject, - recipients: emailData.recipients, - attachments, - }; - - const promises = Object.values(dataForHashing).map((value) => { - const string = Buffer.from( - JSON.stringify(value, (key, value) => - value instanceof Object && !(value instanceof Array) - ? Object.keys(value) - .sort() - .reduce((sorted, key) => { - sorted[key] = value[key]; - return sorted; - }, {}) - : value - ) - ).toString(); - - return CryptoService.SHA256(string); - }); - - const hashArray = await Promise.all(promises); - - const hashesAsAstring: string = hashArray - .map(arrayBufferToBase64) - .sort() - .join("\n"); - - const resultBuffer = await CryptoService.SHA256(hashesAsAstring); - - return arrayBufferToBase64(resultBuffer); -}; - const calculateQRCodeSignature = async ( accountPrivateKey: string, qrCodeHash: string @@ -196,90 +133,6 @@ const verifyQrCodeSignature = async ( return result; }; -interface WithServices { - getMessageDataFromBase64: (base64: string) => Promise<MessageData>; -} - -interface BackblazeDataPartRaw { - key_signature: string; - qr_code_data: string; - session_key: string; -} - -/** - * Higher order function to perform complex operations with the QR code data - * @param cloudFlareServiceInstance = new CloudflareService("CDN_RUL", "DEFAULT_BUCKET") - * @param landingPageServiceUrl - URL of the service responsible for RSA decryption - * @param cryptoService - instance of ICryptoService. Override only needed cryptographic functions. - * The rest will be filled with default ones from CryptoServiceWeb/CryptoServiceNode - */ -const withServices = ( - cloudFlareServiceInstance: CloudflareService, - landingPageServiceUrl: string, - cryptoService?: ICryptoService -): WithServices => { - if (cryptoService) { - cryptoService = { ...CryptoServiceDefault, ...cryptoService }; - } else { - cryptoService = CryptoServiceDefault; - } - - return { - getMessageDataFromBase64: async (base64: string) => { - const qrCodeDataPart = decodeKeyDataPair(base64); - - const base64SHA256 = await cryptoService.SHA256(base64); - - const { - data: backblazeDataPart, - } = await cloudFlareServiceInstance.fetchFile<BackblazeDataPartRaw>( - `qrcode-${arrayBufferToHex(base64SHA256)}` - ); - - const assembledData = assembleQrCodeData( - base64ToArrayBuffer(qrCodeDataPart.data), - base64ToArrayBuffer(backblazeDataPart.qr_code_data) - ); - - const { - key: aesEncryptedSessionKey, - data: storageIv, - } = decodeKeyDataPair(backblazeDataPart.session_key); - - const rsaEncryptedSessionKey = await cryptoService.decryptAESGCM( - base64ToArrayBuffer(aesEncryptedSessionKey), - base64ToArrayBuffer(qrCodeDataPart.key), - base64ToArrayBuffer(storageIv) - ); - - const { - data: { key: encodedSessionKey }, - } = await axios({ - url: `${landingPageServiceUrl}/api/hsm/decrypt`, - method: "POST", - headers: { - "Content-Type": "application/json", - }, - data: { - key_signature: backblazeDataPart.key_signature, - encrypted_key: rsaEncryptedSessionKey, - }, - }); - - const decodedSessionKey = decodeKeyDataPair(encodedSessionKey); - - const decryptedEmailData = await cryptoService.decryptAESGCM( - assembledData, - base64ToArrayBuffer(decodedSessionKey.key), - base64ToArrayBuffer(decodedSessionKey.data) - ); - - const decompressedEmailData = decompressData(decryptedEmailData); - return decodeEmailData(decompressedEmailData); - }, - }; -}; - export default { encodeEmailData, decodeEmailData, @@ -287,8 +140,6 @@ export default { decodeKeyDataPair, breakQrCodeData, assembleQrCodeData, - computeQrCodeHash, calculateQRCodeSignature, verifyQrCodeSignature, - withServices, }; diff --git a/src/types.ts b/src/types.ts index 89bf1b6fa821efb086c67201a44f08120d71d9ae..be29d595d9ee722535f70fa3d72f2ef996aa8e6b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,9 +25,13 @@ export interface IpfsAttachmentData { key: string; contentHash: string; head: string; + contentType: string; } +export type IpfsDataVersion = undefined | "v2"; + export interface IpfsData { + version: IpfsDataVersion; plaintText: IpfsContentData; html: IpfsContentData; attachments: Array<IpfsAttachmentData>; diff --git a/vereign/protobuf/qrcode_data.proto b/vereign/protobuf/qrcode_data.proto index 8d9c61e7947ed6b1116cb454d91f58917dc3adbf..5e4e07ea749162a28d3b14ffbf75ceb40f5627ae 100644 --- a/vereign/protobuf/qrcode_data.proto +++ b/vereign/protobuf/qrcode_data.proto @@ -33,12 +33,14 @@ message IpfsAttachmentData_V1 { string key = 2; string contentHash = 3; string head = 4; + string contentType = 5; } message IpfsData_V1 { IpfsContentData_V1 plaintText = 1; IpfsContentData_V1 html = 2; repeated IpfsAttachmentData_V1 attachments = 3; + string version = 4; } message EmailData_V1 {