Skip to content

Commit

Permalink
feat: support for createCipher backward compatible (#4612)
Browse files Browse the repository at this point in the history
  • Loading branch information
juanpicado committed May 3, 2024
1 parent 4b4a37c commit b6d5652
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 173 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-wolves-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@verdaccio/signature': minor
---

support for createCipher backward compatible
3 changes: 2 additions & 1 deletion packages/signature/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"verdaccio"
],
"engines": {
"node": ">=12"
"node": ">=14"
},
"scripts": {
"clean": "rimraf ./build",
Expand All @@ -39,6 +39,7 @@
},
"dependencies": {
"jsonwebtoken": "9.0.2",
"evp_bytestokey": "1.0.3",
"debug": "4.3.4"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions packages/signature/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ export {
aesDecryptDeprecated,
aesEncryptDeprecated,
generateRandomSecretKeyDeprecated,
aesDecryptDeprecatedBackwardCompatible,
aesEncryptDeprecatedBackwardCompatible,
} from './legacy-signature';

export { aesDecrypt, aesEncrypt } from './signature';
export { signPayload, verifyPayload, SignOptionsSignature } from './jwt-token';
export * as utils from './utils';
Expand Down
63 changes: 13 additions & 50 deletions packages/signature/src/legacy-signature/index.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,13 @@
import { createCipher, createDecipher } from 'crypto';

import { generateRandomHexString } from '../utils';

export const defaultAlgorithm = 'aes192';
export const defaultTarballHashAlgorithm = 'sha1';

/**
*
* @param buf
* @param secret
* @returns
*/
export function aesEncryptDeprecated(buf: Buffer, secret: string): Buffer {
// deprecated (it will be removed in Verdaccio 6), it is a breaking change
// https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
const c = createCipher(defaultAlgorithm, secret);
const b1 = c.update(buf);
const b2 = c.final();
return Buffer.concat([b1, b2]);
}

/**
*
* @param buf
* @param secret
* @returns
*/
export function aesDecryptDeprecated(buf: Buffer, secret: string): Buffer {
try {
// https://nodejs.org/api/crypto.html#crypto_crypto_createdecipher_algorithm_password_options
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
const c = createDecipher(defaultAlgorithm, secret);
const b1 = c.update(buf);
const b2 = c.final();
return Buffer.concat([b1, b2]);
} catch (_) {
return Buffer.alloc(0);
}
}

export const TOKEN_VALID_LENGTH_DEPRECATED = 64;

/**
* Generate a secret key of 64 characters.
*/
export function generateRandomSecretKeyDeprecated(): string {
return generateRandomHexString(6);
}
export {
aesDecryptDeprecated,
aesEncryptDeprecated,
generateRandomSecretKeyDeprecated,
TOKEN_VALID_LENGTH_DEPRECATED,
defaultAlgorithm,
defaultTarballHashAlgorithm,
} from './legacy-crypto';
// Temporary export to keep backward compatibility with Node.js >= 22
export {
aesDecryptDeprecatedBackwardCompatible,
aesEncryptDeprecatedBackwardCompatible,
} from './legacy-backward-compatible';
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-disable new-cap */
import { createCipheriv, createDecipheriv } from 'crypto';
import EVP_BytesToKey from 'evp_bytestokey';

export const defaultAlgorithm = 'aes192';
const KEY_SIZE = 24;

export function aesDecryptDeprecatedBackwardCompatible(text, secret: string) {
const result = EVP_BytesToKey(
secret,
null,
KEY_SIZE * 8, // byte to bit size
16
);

let decipher = createDecipheriv(defaultAlgorithm, result.key, result.iv);
let decrypted = decipher.update(text, 'hex', 'utf8') + decipher.final('utf8');
return decrypted.toString();
}

export function aesEncryptDeprecatedBackwardCompatible(text, secret: string) {
const result = EVP_BytesToKey(
secret,
null,
KEY_SIZE * 8, // byte to bit size
16
);

const cipher = createCipheriv(defaultAlgorithm, result.key, result.iv);
const encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
return encrypted.toString();
}
50 changes: 50 additions & 0 deletions packages/signature/src/legacy-signature/legacy-crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createCipher, createDecipher } from 'crypto';

import { generateRandomHexString } from '../utils';

export const defaultAlgorithm = 'aes192';
export const defaultTarballHashAlgorithm = 'sha1';

/**
* Deprecated version usage of crypto.createCipher, only useful for node.js versions < 22.
* @param buf
* @param secret
* @returns
*/
export function aesEncryptDeprecated(buf: Buffer, secret: string): Buffer {
// deprecated (it will be removed in Verdaccio 6), it is a breaking change
// https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
const c = createCipher(defaultAlgorithm, secret);
const b1 = c.update(buf);
const b2 = c.final();
return Buffer.concat([b1, b2]);
}

/**
* Deprecated version usage of crypto.createCipher, only useful for node.js versions < 22.
* @param buf
* @param secret
* @returns
*/
export function aesDecryptDeprecated(buf: Buffer, secret: string): Buffer {
try {
// https://nodejs.org/api/crypto.html#crypto_crypto_createdecipher_algorithm_password_options
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
const c = createDecipher(defaultAlgorithm, secret);
const b1 = c.update(buf);
const b2 = c.final();
return Buffer.concat([b1, b2]);
} catch (_) {
return Buffer.alloc(0);
}
}

export const TOKEN_VALID_LENGTH_DEPRECATED = 64;

/**
* Generate a secret key of 64 characters.
*/
export function generateRandomSecretKeyDeprecated(): string {
return generateRandomHexString(6);
}
4 changes: 4 additions & 0 deletions packages/signature/src/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ export function aesEncrypt(value: string, key: string): string | void {
// IV must be a buffer of length 16
const iv = randomBytes(16);
const secretKey = VERDACCIO_LEGACY_ENCRYPTION_KEY || key;

const isKeyValid = secretKey?.length === TOKEN_VALID_LENGTH;
if (isKeyValid === false) {
throw new Error('Invalid secret key length');
}
debug('length secret key %o', secretKey?.length);
debug('is valid secret %o', isKeyValid);
if (!value || !secretKey || !isKeyValid) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
aesDecryptDeprecatedBackwardCompatible,
aesEncryptDeprecatedBackwardCompatible,
generateRandomSecretKeyDeprecated,
} from '../src';

describe('test deprecated crypto utils', () => {
test('decrypt payload flow', () => {
const secret = generateRandomSecretKeyDeprecated();
const payload = 'juan:password';
const token = aesEncryptDeprecatedBackwardCompatible(Buffer.from(payload), secret);
const data = aesDecryptDeprecatedBackwardCompatible(token, secret);

expect(data.toString()).toEqual(payload.toString());
});

test('crypt fails if secret is incorrect', () => {
const payload = 'juan:password';
expect(
aesEncryptDeprecatedBackwardCompatible(Buffer.from(payload), 'fake_token').toString()
).not.toEqual(Buffer.from(payload));
});
});
3 changes: 1 addition & 2 deletions packages/signature/test/legacy-token.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ describe('test crypto utils', () => {
test('crypt fails if secret is incorrect', () => {
const secret = 'f5bb945cc57fea2f25961e1bd6fb3c89_TO_LONG';
const payload = 'juan';
const token = aesEncrypt(payload, secret) as string;
expect(token).toBeUndefined();
expect(() => aesEncrypt(payload, secret)).toThrow('Invalid secret key length');
});
});

0 comments on commit b6d5652

Please sign in to comment.