Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(config): split decryption functions #28571

Merged
merged 1 commit into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
NODE_VERSION: 18
DRY_RUN: true
TEST_LEGACY_DECRYPTION: true
SPARSE_CHECKOUT: |-
.github/actions/
data/
Expand Down
16 changes: 11 additions & 5 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import crypto from 'node:crypto';
import os from 'node:os';
import { env } from 'node:process';
import v8 from 'node:v8';
import { minimatch } from 'minimatch';
import type { JestConfigWithTsJest } from 'ts-jest';
Expand Down Expand Up @@ -205,11 +206,7 @@ const config: JestConfig = {
'!lib/**/{__fixtures__,__mocks__,__testutil__,test}/**/*.{js,ts}',
'!lib/**/types.ts',
],
coveragePathIgnorePatterns: [
'/node_modules/',
'<rootDir>/test/',
'<rootDir>/tools/',
],
coveragePathIgnorePatterns: getCoverageIgnorePatterns(),
cacheDirectory: '.cache/jest',
collectCoverage: true,
coverageReporters: ci
Expand Down Expand Up @@ -450,3 +447,12 @@ process.stderr.write(`Host stats:
Memory: ${(mem / 1024 / 1024 / 1024).toFixed(2)} GB
HeapLimit: ${(stats.heap_size_limit / 1024 / 1024 / 1024).toFixed(2)} GB
`);
function getCoverageIgnorePatterns(): string[] | undefined {
const patterns = ['/node_modules/', '<rootDir>/test/', '<rootDir>/tools/'];

if (env.TEST_LEGACY_DECRYPTION !== 'true') {
patterns.push('<rootDir>/lib/config/decrypt/legacy.ts');
}

return patterns;
}
83 changes: 6 additions & 77 deletions lib/config/decrypt.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,18 @@
import crypto from 'node:crypto';
import is from '@sindresorhus/is';
import * as openpgp from 'openpgp';
import { logger } from '../logger';
import { maskToken } from '../util/mask';
import { regEx } from '../util/regex';
import { addSecretForSanitizing } from '../util/sanitize';
import { ensureTrailingSlash } from '../util/url';
import {
tryDecryptPublicKeyDefault,
tryDecryptPublicKeyPKCS1,
} from './decrypt/legacy';
import { tryDecryptOpenPgp } from './decrypt/openpgp';
import { GlobalConfig } from './global';
import { DecryptedObject } from './schema';
import type { RenovateConfig } from './types';

export async function tryDecryptPgp(
privateKey: string,
encryptedStr: string,
): Promise<string | null> {
if (encryptedStr.length < 500) {
// optimization during transition of public key -> pgp
return null;
}
try {
const pk = await openpgp.readPrivateKey({
// prettier-ignore
armoredKey: privateKey.replace(regEx(/\n[ \t]+/g), '\n'), // little massage to help a common problem
});
const startBlock = '-----BEGIN PGP MESSAGE-----\n\n';
const endBlock = '\n-----END PGP MESSAGE-----';
let armoredMessage = encryptedStr.trim();
if (!armoredMessage.startsWith(startBlock)) {
armoredMessage = `${startBlock}${armoredMessage}`;
}
if (!armoredMessage.endsWith(endBlock)) {
armoredMessage = `${armoredMessage}${endBlock}`;
}
const message = await openpgp.readMessage({
armoredMessage,
});
const { data } = await openpgp.decrypt({
message,
decryptionKeys: pk,
});
logger.debug('Decrypted config using openpgp');
return data;
} catch (err) {
logger.debug({ err }, 'Could not decrypt using openpgp');
return null;
}
}

export function tryDecryptPublicKeyDefault(
privateKey: string,
encryptedStr: string,
): string | null {
let decryptedStr: string | null = null;
try {
decryptedStr = crypto
.privateDecrypt(privateKey, Buffer.from(encryptedStr, 'base64'))
.toString();
logger.debug('Decrypted config using default padding');
} catch (err) {
logger.debug('Could not decrypt using default padding');
}
return decryptedStr;
}

export function tryDecryptPublicKeyPKCS1(
privateKey: string,
encryptedStr: string,
): string | null {
let decryptedStr: string | null = null;
try {
decryptedStr = crypto
.privateDecrypt(
{
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PADDING,
},
Buffer.from(encryptedStr, 'base64'),
)
.toString();
} catch (err) {
logger.debug('Could not decrypt using PKCS1 padding');
}
return decryptedStr;
}

export async function tryDecrypt(
privateKey: string,
encryptedStr: string,
Expand All @@ -92,7 +21,7 @@ export async function tryDecrypt(
): Promise<string | null> {
let decryptedStr: string | null = null;
if (privateKey?.startsWith('-----BEGIN PGP PRIVATE KEY BLOCK-----')) {
const decryptedObjStr = await tryDecryptPgp(privateKey, encryptedStr);
const decryptedObjStr = await tryDecryptOpenPgp(privateKey, encryptedStr);
if (decryptedObjStr) {
decryptedStr = validateDecryptedValue(decryptedObjStr, repository);
}
Expand Down
40 changes: 40 additions & 0 deletions lib/config/decrypt/legacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/** istanbul ignore file */
import crypto from 'node:crypto';
import { logger } from '../../logger';

export function tryDecryptPublicKeyPKCS1(
privateKey: string,
encryptedStr: string,
): string | null {
let decryptedStr: string | null = null;
try {
decryptedStr = crypto
.privateDecrypt(
{
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PADDING,
},
Buffer.from(encryptedStr, 'base64'),
)
.toString();
} catch (err) {
logger.debug('Could not decrypt using PKCS1 padding');
}
return decryptedStr;
}

export function tryDecryptPublicKeyDefault(
privateKey: string,
encryptedStr: string,
): string | null {
let decryptedStr: string | null = null;
try {
decryptedStr = crypto
.privateDecrypt(privateKey, Buffer.from(encryptedStr, 'base64'))
.toString();
logger.debug('Decrypted config using default padding');
} catch (err) {
logger.debug('Could not decrypt using default padding');
}
return decryptedStr;
}
40 changes: 40 additions & 0 deletions lib/config/decrypt/openpgp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as openpgp from 'openpgp';
import { logger } from '../../logger';
import { regEx } from '../../util/regex';

export async function tryDecryptOpenPgp(
privateKey: string,
encryptedStr: string,
): Promise<string | null> {
if (encryptedStr.length < 500) {
// optimization during transition of public key -> pgp
return null;
}
try {
const pk = await openpgp.readPrivateKey({
// prettier-ignore
armoredKey: privateKey.replace(regEx(/\n[ \t]+/g), '\n'), // little massage to help a common problem
});
const startBlock = '-----BEGIN PGP MESSAGE-----\n\n';
const endBlock = '\n-----END PGP MESSAGE-----';
let armoredMessage = encryptedStr.trim();
if (!armoredMessage.startsWith(startBlock)) {
armoredMessage = `${startBlock}${armoredMessage}`;
}
if (!armoredMessage.endsWith(endBlock)) {
armoredMessage = `${armoredMessage}${endBlock}`;
}
const message = await openpgp.readMessage({
armoredMessage,
});
const { data } = await openpgp.decrypt({
message,
decryptionKeys: pk,
});
logger.debug('Decrypted config using openpgp');
return data;
} catch (err) {
logger.debug({ err }, 'Could not decrypt using openpgp');
return null;
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"generate": "run-s 'generate:*'",
"generate:imports": "node tools/generate-imports.mjs",
"git-check": "node tools/check-git-version.mjs",
"jest": "node tools/jest.mjs",
"jest": "GIT_ALLOW_PROTOCOL=file LOG_LEVEL=fatal node --experimental-vm-modules node_modules/jest/bin/jest.js --logHeapUsage",
"lint": "run-s ls-lint type-check eslint prettier markdown-lint git-check doc-fence-check",
"lint-fix": "run-s eslint-fix prettier-fix markdown-lint-fix",
"ls-lint": "ls-lint",
Expand Down
32 changes: 0 additions & 32 deletions tools/jest.mjs

This file was deleted.