This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+14
View File
@@ -0,0 +1,14 @@
/**
* @adonisjs/encryption
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare module '@ioc:Adonis/Core/Application' {
import { EncryptionContract } from '@ioc:Adonis/Core/Encryption';
interface ContainerBindings {
'Adonis/Core/Encryption': EncryptionContract;
}
}
+8
View File
@@ -0,0 +1,8 @@
/**
* @adonisjs/encryption
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
+85
View File
@@ -0,0 +1,85 @@
/**
* The binding for the given module is defined inside `providers/AppProvider.ts`
* file.
*/
declare module '@ioc:Adonis/Core/Encryption' {
import { base64 } from '@poppinss/utils/build/helpers';
/**
* Config accepted by the encryption
*/
export type EncryptionOptions = {
algorithm?: 'aes-256-cbc';
secret: string;
};
/**
* Message verifier is similar to the encryption. However, the actual payload
* is not encrypted and just base64 encoded. This is helpful when you are
* not concerned about the confidentiality of the data, but just want to
* make sure that is not tampered after encoding.
*/
export interface MessageVerifierContract {
/**
* Sign a given piece of value using the app secret. A wide range of
* data types are supported.
*
* - String
* - Arrays
* - Objects
* - Booleans
* - Numbers
* - Dates
*
* You can optionally define a purpose for which the value was signed and
* mentioning a different purpose/no purpose during unsign will fail.
*/
sign(payload: any, expiresIn?: string | number, purpose?: string): string;
/**
* Unsign, previously signed value
*/
unsign<T extends any>(payload: string, purpose?: string): T | null;
}
/**
* The encryption class allows encrypting and decrypting values using `aes-256-cbc` or `aes-128-cbc`
* algorithms. The encrypted value uses a unique iv for every encryption and this ensures semantic
* security (read more https://en.wikipedia.org/wiki/Semantic_security).
*/
export interface EncryptionContract {
/**
* Reference to the message verifier
*/
verifier: MessageVerifierContract;
/**
* Reference to base64 object for base64 encoding/decoding values
*/
base64: typeof base64;
/**
* Current algorithm in use
*/
algorithm: EncryptionOptions['algorithm'];
/**
* Encrypt a given piece of value using the app secret. A wide range of
* data types are supported.
*
* - String
* - Arrays
* - Objects
* - Booleans
* - Numbers
* - Dates
*
* You can optionally define a purpose for which the value was encrypted and
* mentioning a different purpose/no purpose during decrypt will fail.
*/
encrypt(payload: any, expiresIn?: string | number, purpose?: string): string;
/**
* Decrypt a previously encrypted value
*/
decrypt<T extends any>(payload: string, purpose?: string): T | null;
/**
* Create a children instance with different secret key
*/
child(options?: EncryptionOptions): EncryptionContract;
}
const Encryption: EncryptionContract;
export default Encryption;
}
+8
View File
@@ -0,0 +1,8 @@
/*
* @adonisjs/encryption
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
+2
View File
@@ -0,0 +1,2 @@
/// <reference path="encryption.d.ts" />
/// <reference path="container.d.ts" />
+10
View File
@@ -0,0 +1,10 @@
/*
* @adonisjs/encryption
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="./encryption.ts" />
/// <reference path="./container.ts" />
+20
View File
@@ -0,0 +1,20 @@
{
"E_MISSING_APP_KEY": {
"message": "The value for \"app.appKey\" is undefined",
"status": 500,
"code": "E_MISSING_APP_KEY",
"help": [
"The Encryption module relies on the \"appKey\" property defined inside the \"config/app\" file",
"The \"app.appKey\" is currently missing and ideally should rely on the \"APP_KEY\" environment variable. So double check the config file, along with the environment variable"
]
},
"E_INSECURE_APP_KEY": {
"message": "The value of \"app.appKey\" is not secure",
"status": 500,
"code": "E_INSECURE_APP_KEY",
"help": [
"The length of the \"app.appKey\" must be 16 characters long",
"You can generate a secure key using the \"node ace generate:key\" command and then update the environment variable to use the new secure value."
]
}
}
@@ -0,0 +1,10 @@
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
/**
* Encryption provider to binding encryption class to the container
*/
export default class EncryptionProvider {
protected app: ApplicationContract;
constructor(app: ApplicationContract);
static needsApplication: boolean;
register(): void;
}
@@ -0,0 +1,27 @@
"use strict";
/*
* @adonisjs/encryption
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Encryption provider to binding encryption class to the container
*/
class EncryptionProvider {
constructor(app) {
this.app = app;
}
register() {
this.app.container.singleton('Adonis/Core/Encryption', () => {
const Config = this.app.container.resolveBinding('Adonis/Core/Config');
const { Encryption } = require('../src/Encryption');
return new Encryption({ secret: Config.get('app.appKey') });
});
}
}
exports.default = EncryptionProvider;
EncryptionProvider.needsApplication = true;
+53
View File
@@ -0,0 +1,53 @@
/// <reference path="../../adonis-typings/encryption.d.ts" />
import { EncryptionContract, EncryptionOptions } from '@ioc:Adonis/Core/Encryption';
import { base64 as utilsBase64 } from '@poppinss/utils/build/helpers';
import { MessageVerifier } from '../MessageVerifier';
/**
* The encryption class allows encrypting and decrypting values using `aes-256-cbc` or `aes-128-cbc`
* algorithms. The encrypted value uses a unique iv for every encryption and this ensures semantic
* security (read more https://en.wikipedia.org/wiki/Semantic_security).
*/
export declare class Encryption implements EncryptionContract {
private options;
/**
* The key for signing and encrypting values. It is derived
* from the user provided secret.
*/
private cryptoKey;
/**
* Use `dot` as a separator for joining encrypted value, iv and the
* hmac hash. The idea is borrowed from JWT's in which each part
* of the payload is concatenated with a dot.
*/
private separator;
/**
* Reference to the instance of message verifier for signing
* and verifying values.
*/
verifier: MessageVerifier;
/**
* Reference to base64 object for base64 encoding/decoding values
*/
base64: typeof utilsBase64;
/**
* The algorithm in use
*/
algorithm: "aes-256-cbc";
constructor(options: EncryptionOptions);
/**
* Validates the app secret
*/
private validateSecret;
/**
* Encrypt value with optional expiration and purpose
*/
encrypt(value: any, expiresAt?: string | number, purpose?: string): string;
/**
* Decrypt value and verify it against a purpose
*/
decrypt<T extends any>(value: string, purpose?: string): T | null;
/**
* Returns a new instance of encryption with custom secret key
*/
child(options: EncryptionOptions): Encryption;
}
+144
View File
@@ -0,0 +1,144 @@
"use strict";
/*
* @adonisjs/encryption
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Encryption = void 0;
/// <reference path="../../adonis-typings/encryption.ts" />
const utils_1 = require("@poppinss/utils");
const crypto_1 = require("crypto");
const helpers_1 = require("@poppinss/utils/build/helpers");
const Hmac_1 = require("../Hmac");
const MessageVerifier_1 = require("../MessageVerifier");
const AppKeyException_1 = require("../Exceptions/AppKeyException");
/**
* The encryption class allows encrypting and decrypting values using `aes-256-cbc` or `aes-128-cbc`
* algorithms. The encrypted value uses a unique iv for every encryption and this ensures semantic
* security (read more https://en.wikipedia.org/wiki/Semantic_security).
*/
class Encryption {
constructor(options) {
this.options = options;
/**
* Use `dot` as a separator for joining encrypted value, iv and the
* hmac hash. The idea is borrowed from JWT's in which each part
* of the payload is concatenated with a dot.
*/
this.separator = '.';
/**
* Reference to base64 object for base64 encoding/decoding values
*/
this.base64 = helpers_1.base64;
/**
* The algorithm in use
*/
this.algorithm = this.options.algorithm || 'aes-256-cbc';
this.validateSecret();
this.cryptoKey = (0, crypto_1.createHash)('sha256').update(this.options.secret).digest();
this.verifier = new MessageVerifier_1.MessageVerifier(this.options.secret);
}
/**
* Validates the app secret
*/
validateSecret() {
if (typeof this.options.secret !== 'string') {
throw AppKeyException_1.AppKeyException.missingAppKey();
}
if (this.options.secret.length < 16) {
throw AppKeyException_1.AppKeyException.insecureAppKey();
}
}
/**
* Encrypt value with optional expiration and purpose
*/
encrypt(value, expiresAt, purpose) {
/**
* Using a random string as the iv for generating unpredictable values
*/
const iv = helpers_1.string.generateRandom(16);
/**
* Creating chiper
*/
const cipher = (0, crypto_1.createCipheriv)(this.algorithm, this.cryptoKey, iv);
/**
* Encoding value to a string so that we can set it on the cipher
*/
const encodedValue = new helpers_1.MessageBuilder().build(value, expiresAt, purpose);
/**
* Set final to the cipher instance and encrypt it
*/
const encrypted = Buffer.concat([cipher.update(encodedValue, 'utf-8'), cipher.final()]);
/**
* Concatenate `encrypted value` and `iv` by urlEncoding them. The concatenation is required
* to generate the HMAC, so that HMAC checks for integrity of both the `encrypted value`
* and the `iv`.
*/
const result = `${this.base64.urlEncode(encrypted)}${this.separator}${this.base64.urlEncode(iv)}`;
/**
* Returns the result + hmac
*/
return `${result}${this.separator}${new Hmac_1.Hmac(this.cryptoKey).generate(result)}`;
}
/**
* Decrypt value and verify it against a purpose
*/
decrypt(value, purpose) {
if (typeof value !== 'string') {
throw new utils_1.Exception('"Encryption.decrypt" expects a string value', 500, 'E_RUNTIME_EXCEPTION');
}
/**
* Make sure the encrypted value is in correct format. ie
* [encrypted value]--[iv]--[hash]
*/
const [encryptedEncoded, ivEncoded, hash] = value.split(this.separator);
if (!encryptedEncoded || !ivEncoded || !hash) {
return null;
}
/**
* Make sure we are able to urlDecode the encrypted value
*/
const encrypted = this.base64.urlDecode(encryptedEncoded, 'base64');
if (!encrypted) {
return null;
}
/**
* Make sure we are able to urlDecode the iv
*/
const iv = this.base64.urlDecode(ivEncoded);
if (!iv) {
return null;
}
/**
* Make sure the hash is correct, it means the first 2 parts of the
* string are not tampered.
*/
const isValidHmac = new Hmac_1.Hmac(this.cryptoKey).compare(`${encryptedEncoded}${this.separator}${ivEncoded}`, hash);
if (!isValidHmac) {
return null;
}
/**
* The Decipher can raise exceptions with malformed input, so we wrap it
* to avoid leaking sensitive information
*/
try {
const decipher = (0, crypto_1.createDecipheriv)(this.algorithm, this.cryptoKey, iv);
const decrypted = decipher.update(encrypted, 'base64', 'utf8') + decipher.final('utf8');
return new helpers_1.MessageBuilder().verify(decrypted, purpose);
}
catch (error) {
return null;
}
}
/**
* Returns a new instance of encryption with custom secret key
*/
child(options) {
return new Encryption(options);
}
}
exports.Encryption = Encryption;
@@ -0,0 +1,5 @@
import { Exception } from '@poppinss/utils';
export declare class AppKeyException extends Exception {
static missingAppKey(): AppKeyException;
static insecureAppKey(): AppKeyException;
}
@@ -0,0 +1,31 @@
"use strict";
/*
* @adonisjs/encryption
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppKeyException = void 0;
const utils_1 = require("@poppinss/utils");
const exceptions_json_1 = __importDefault(require("../../exceptions.json"));
class AppKeyException extends utils_1.Exception {
static missingAppKey() {
const details = exceptions_json_1.default['E_MISSING_APP_KEY'];
const error = new this(details.message, details.status, details.code);
error.help = details.help.join('\n');
return error;
}
static insecureAppKey() {
const details = exceptions_json_1.default['E_INSECURE_APP_KEY'];
const error = new this(details.message, details.status, details.code);
error.help = details.help.join('\n');
return error;
}
}
exports.AppKeyException = AppKeyException;
+17
View File
@@ -0,0 +1,17 @@
/// <reference types="node" />
/**
* A generic class for generating SHA-256 Hmac for verifying the value
* integrity.
*/
export declare class Hmac {
private key;
constructor(key: Buffer);
/**
* Generate the hmac
*/
generate(value: string): string;
/**
* Compare raw value against an existing hmac
*/
compare(value: string, existingHmac: string): boolean;
}
+35
View File
@@ -0,0 +1,35 @@
"use strict";
/*
* @adonisjs/encryption
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Hmac = void 0;
const crypto_1 = require("crypto");
const helpers_1 = require("@poppinss/utils/build/helpers");
/**
* A generic class for generating SHA-256 Hmac for verifying the value
* integrity.
*/
class Hmac {
constructor(key) {
this.key = key;
}
/**
* Generate the hmac
*/
generate(value) {
return helpers_1.base64.urlEncode((0, crypto_1.createHmac)('sha256', this.key).update(value).digest());
}
/**
* Compare raw value against an existing hmac
*/
compare(value, existingHmac) {
return (0, helpers_1.safeEqual)(this.generate(value), existingHmac);
}
}
exports.Hmac = Hmac;
+34
View File
@@ -0,0 +1,34 @@
/// <reference path="../../adonis-typings/encryption.d.ts" />
import { MessageVerifierContract } from '@ioc:Adonis/Core/Encryption';
/**
* Message verifier is similar to the encryption. However, the actual payload
* is not encrypted and just base64 encoded. This is helpful when you are
* not concerned about the confidentiality of the data, but just want to
* make sure that is not tampered after encoding.
*/
export declare class MessageVerifier implements MessageVerifierContract {
private secret;
/**
* The key for signing and encrypting values. It is derived
* from the user provided secret.
*/
private cryptoKey;
/**
* Use `dot` as a separator for joining encrypted value, iv and the
* hmac hash. The idea is borrowed from JWT's in which each part
* of the payload is concatenated with a dot.
*/
private separator;
constructor(secret: string);
/**
* Signs a value with the secret key. The signed value is not encrypted, but just
* signed for avoiding tampering to the original message.
*
* Any `JSON.stringify` valid value is accepted by this method.
*/
sign(value: any, expiresAt?: string | number, purpose?: string): string;
/**
* Unsign a previously signed value with an optional purpose
*/
unsign<T extends any>(value: string, purpose?: string): null | T;
}
+76
View File
@@ -0,0 +1,76 @@
"use strict";
/*
* @adonisjs/encryption
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MessageVerifier = void 0;
/// <reference path="../../adonis-typings/encryption.ts" />
const crypto_1 = require("crypto");
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
const Hmac_1 = require("../Hmac");
/**
* Message verifier is similar to the encryption. However, the actual payload
* is not encrypted and just base64 encoded. This is helpful when you are
* not concerned about the confidentiality of the data, but just want to
* make sure that is not tampered after encoding.
*/
class MessageVerifier {
constructor(secret) {
this.secret = secret;
/**
* The key for signing and encrypting values. It is derived
* from the user provided secret.
*/
this.cryptoKey = (0, crypto_1.createHash)('sha256').update(this.secret).digest();
/**
* Use `dot` as a separator for joining encrypted value, iv and the
* hmac hash. The idea is borrowed from JWT's in which each part
* of the payload is concatenated with a dot.
*/
this.separator = '.';
}
/**
* Signs a value with the secret key. The signed value is not encrypted, but just
* signed for avoiding tampering to the original message.
*
* Any `JSON.stringify` valid value is accepted by this method.
*/
sign(value, expiresAt, purpose) {
if (value === null || value === undefined) {
throw new utils_1.Exception('"MessageVerifier.sign" cannot sign null or undefined values', 500, 'E_RUNTIME_EXCEPTION');
}
const encoded = helpers_1.base64.urlEncode(new helpers_1.MessageBuilder().build(value, expiresAt, purpose));
return `${encoded}${this.separator}${new Hmac_1.Hmac(this.cryptoKey).generate(encoded)}`;
}
/**
* Unsign a previously signed value with an optional purpose
*/
unsign(value, purpose) {
if (typeof value !== 'string') {
throw new utils_1.Exception('"MessageVerifier.unsign" expects a string value', 500, 'E_RUNTIME_EXCEPTION');
}
/**
* Ensure value is in correct format
*/
const [encoded, hash] = value.split(this.separator);
if (!encoded || !hash) {
return null;
}
/**
* Ensure value can be decoded
*/
const decoded = helpers_1.base64.urlDecode(encoded);
if (!decoded) {
return null;
}
const isValid = new Hmac_1.Hmac(this.cryptoKey).compare(encoded, hash);
return isValid ? new helpers_1.MessageBuilder().verify(decoded, purpose) : null;
}
}
exports.MessageVerifier = MessageVerifier;
+1
View File
@@ -0,0 +1 @@
export { Encryption } from './src/Encryption';
+13
View File
@@ -0,0 +1,13 @@
"use strict";
/*
* @adonisjs/encryption
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Encryption = void 0;
var Encryption_1 = require("./src/Encryption");
Object.defineProperty(exports, "Encryption", { enumerable: true, get: function () { return Encryption_1.Encryption; } });