Skip to content

Commit

Permalink
fix: CDK does not work in FIPS-restricted environments
Browse files Browse the repository at this point in the history
In an environment where node is compiled with FIPS restrictions in mind,
`crypto.createHash('md5')` does not work, for fear of MD5 being used for
crypto purposes.

We do not use it for crypto purposes, just to come up with unique
identifiers for certain constructs. Nevertheless, CDK cannot work if
`md5` is not available from the Node standard library.

Fall back to a pure JavaScript implementation if the built-in MD5 hash
does not work, so that we produce equivalent templates.
  • Loading branch information
rix0rrr committed Nov 11, 2022
1 parent 9457e82 commit 48efa8b
Show file tree
Hide file tree
Showing 17 changed files with 244 additions and 50 deletions.
6 changes: 2 additions & 4 deletions packages/@aws-cdk/aws-apigateway/lib/deployment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as crypto from 'crypto';
import { Lazy, RemovalPolicy, Resource, CfnResource } from '@aws-cdk/core';
import { md5hash } from '@aws-cdk/core/lib/helpers-internal';
import { Construct } from 'constructs';
import { CfnDeployment } from './apigateway.generated';
import { Method } from './method';
Expand Down Expand Up @@ -173,9 +173,7 @@ class LatestDeploymentResource extends CfnDeployment {
// if hash components were added to the deployment, we use them to calculate
// a logical ID for the deployment resource.
if (hash.length > 0) {
const md5 = crypto.createHash('md5');
hash.map(x => this.stack.resolve(x)).forEach(c => md5.update(JSON.stringify(c)));
lid += md5.digest('hex');
lid += md5hash(hash.map(x => this.stack.resolve(x)).map(c => JSON.stringify(c)).join(''));
}

return lid;
Expand Down
8 changes: 4 additions & 4 deletions packages/@aws-cdk/aws-ec2/lib/instance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as crypto from 'crypto';
import * as iam from '@aws-cdk/aws-iam';

import { Annotations, Aspects, Duration, Fn, IResource, Lazy, Resource, Stack, Tags } from '@aws-cdk/core';
import { md5hash } from '@aws-cdk/core/lib/helpers-internal';
import { Construct } from 'constructs';
import { InstanceRequireImdsv2Aspect } from './aspects';
import { CloudFormationInit } from './cfn-init';
Expand Down Expand Up @@ -423,14 +423,14 @@ export class Instance extends Resource implements IInstance {
if (recursing) { return originalLogicalId; }
if (!(props.userDataCausesReplacement ?? props.initOptions)) { return originalLogicalId; }

const md5 = crypto.createHash('md5');
const fragments = new Array<string>();
recursing = true;
try {
md5.update(JSON.stringify(context.resolve(this.userData.render())));
fragments.push(JSON.stringify(context.resolve(this.userData.render())));
} finally {
recursing = false;
}
const digest = md5.digest('hex').slice(0, 16);
const digest = md5hash(fragments.join('')).slice(0, 16);
return `${originalLogicalId}${digest}`;
},
}));
Expand Down
7 changes: 2 additions & 5 deletions packages/@aws-cdk/aws-ec2/lib/volume.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as crypto from 'crypto';

import { AccountRootPrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam';
import { IKey, ViaServicePrincipal } from '@aws-cdk/aws-kms';
import { IResource, Resource, Size, SizeRoundingBehavior, Stack, Token, Tags, Names, RemovalPolicy } from '@aws-cdk/core';
import { md5hash } from '@aws-cdk/core/lib/helpers-internal';
import { Construct } from 'constructs';
import { CfnVolume } from './ec2.generated';
import { IInstance } from './instance';
Expand Down Expand Up @@ -565,9 +564,7 @@ abstract class VolumeBase extends Resource implements IVolume {
}

private calculateResourceTagValue(constructs: Construct[]): string {
const md5 = crypto.createHash('md5');
constructs.forEach(construct => md5.update(Names.uniqueId(construct)));
return md5.digest('hex');
return md5hash(constructs.map(c => Names.uniqueId(c)).join(''));
}
}

Expand Down
6 changes: 2 additions & 4 deletions packages/@aws-cdk/aws-glue/lib/code.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as iam from '@aws-cdk/aws-iam';
import * as s3 from '@aws-cdk/aws-s3';
import * as s3assets from '@aws-cdk/aws-s3-assets';
import * as cdk from '@aws-cdk/core';
import { md5hash } from '@aws-cdk/core/lib/helpers-internal';
import * as constructs from 'constructs';

/**
Expand Down Expand Up @@ -95,9 +95,7 @@ export class AssetCode extends Code {
* Hash a string
*/
private hashcode(s: string): string {
const hash = crypto.createHash('md5');
hash.update(s);
return hash.digest('hex');
return md5hash(s);
};
}

Expand Down
6 changes: 2 additions & 4 deletions packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as crypto from 'crypto';
import { md5hash } from '@aws-cdk/core/lib/helpers-internal';
import { ISecurityGroup, IVpc, SubnetSelection } from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
Expand Down Expand Up @@ -219,9 +219,7 @@ export class SelfManagedKafkaEventSource extends StreamEventSource {
}

private mappingId(target: lambda.IFunction) {
let hash = crypto.createHash('md5');
hash.update(JSON.stringify(Stack.of(target).resolve(this.innerProps.bootstrapServers)));
const idHash = hash.digest('hex');
const idHash = md5hash(JSON.stringify(Stack.of(target).resolve(this.innerProps.bootstrapServers)));
return `KafkaEventSource:${idHash}:${this.innerProps.topic}`;
}

Expand Down
10 changes: 3 additions & 7 deletions packages/@aws-cdk/aws-lambda/lib/function-hash.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as crypto from 'crypto';
import { CfnResource, FeatureFlags, Stack } from '@aws-cdk/core';
import { md5hash } from '@aws-cdk/core/lib/helpers-internal';
import { LAMBDA_RECOGNIZE_LAYER_VERSION, LAMBDA_RECOGNIZE_VERSION_PROPS } from '@aws-cdk/cx-api';
import { Function as LambdaFunction } from './function';
import { ILayerVersion } from './layers';
Expand Down Expand Up @@ -34,9 +34,7 @@ export function calculateFunctionHash(fn: LambdaFunction) {
stringifiedConfig = stringifiedConfig + calculateLayersHash(fn._layers);
}

const hash = crypto.createHash('md5');
hash.update(stringifiedConfig);
return hash.digest('hex');
return md5hash(stringifiedConfig);
}

export function trimFromStart(s: string, maxLength: number) {
Expand Down Expand Up @@ -146,7 +144,5 @@ function calculateLayersHash(layers: ILayerVersion[]): string {
layerConfig[layer.node.id] = properties;
}

const hash = crypto.createHash('md5');
hash.update(JSON.stringify(layerConfig));
return hash.digest('hex');
return md5hash(JSON.stringify(layerConfig));
}
7 changes: 3 additions & 4 deletions packages/@aws-cdk/aws-rds/lib/database-secret.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as crypto from 'crypto';
import * as kms from '@aws-cdk/aws-kms';
import * as secretsmanager from '@aws-cdk/aws-secretsmanager';
import { Aws, Names } from '@aws-cdk/core';
import { md5hash } from '@aws-cdk/core/lib/helpers-internal';
import { Construct } from 'constructs';
import { DEFAULT_PASSWORD_EXCLUDE_CHARS } from './private/util';

Expand Down Expand Up @@ -88,14 +88,13 @@ export class DatabaseSecret extends secretsmanager.Secret {
});

if (props.replaceOnPasswordCriteriaChanges) {
const hash = crypto.createHash('md5');
hash.update(JSON.stringify({
const hash = md5hash(JSON.stringify({
// Use here the options that influence the password generation.
// If at some point we add other password customization options
// they sould be added here below (e.g. `passwordLength`).
excludeCharacters,
}));
const logicalId = `${Names.uniqueId(this)}${hash.digest('hex')}`;
const logicalId = `${Names.uniqueId(this)}${hash}`;

const secret = this.node.defaultChild as secretsmanager.CfnSecret;
secret.overrideLogicalId(logicalId.slice(-255)); // Take last 255 chars
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as crypto from 'crypto';
import { md5hash } from '@aws-cdk/core/lib/helpers-internal';
import { DnsValidatedCertificate, ICertificate } from '@aws-cdk/aws-certificatemanager';
import { CloudFrontWebDistribution, OriginProtocolPolicy, PriceClass, ViewerCertificate, ViewerProtocolPolicy } from '@aws-cdk/aws-cloudfront';
import { ARecord, AaaaRecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53';
Expand Down Expand Up @@ -97,7 +97,7 @@ export class HttpsRedirect extends Construct {
});

domainNames.forEach((domainName) => {
const hash = crypto.createHash('md5').update(domainName).digest('hex').slice(0, 6);
const hash = md5hash(domainName).slice(0, 6);
const aliasProps = {
recordName: domainName,
zone: props.zone,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as crypto from 'crypto';
import { md5hash } from '@aws-cdk/core/lib/helpers-internal';
import { IVpcEndpointService } from '@aws-cdk/aws-ec2';
import { Fn, Names, Stack } from '@aws-cdk/core';
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '@aws-cdk/custom-resources';
Expand Down Expand Up @@ -225,7 +225,5 @@ interface PrivateDnsConfiguration {
* Hash a string
*/
function hashcode(s: string): string {
const hash = crypto.createHash('md5');
hash.update(s);
return hash.digest('hex');
const hash = md5hash(s);
};
5 changes: 4 additions & 1 deletion packages/@aws-cdk/core/lib/helpers-internal/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export * from './cfn-parse';
export * from './cfn-parse';

// Other libraries are going to need this as well
export { md5hash } from '../private/md5';
191 changes: 191 additions & 0 deletions packages/@aws-cdk/core/lib/private/md5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/**
* md5 hashing
*
* Uses the built-in 'crypto' library by default for a C implementation
* of md5, but in case the crypto library has been compiled to disable
* FIPS-noncompliant hash suites, fall back to a pure JS implementation of
* md5.
*/
import * as crypto from 'crypto';

let _impl: undefined | ((x: string) => string);

/**
* Return a hash of the given input string, in hex format
*/
export function md5hash(x: string) {
if (!_impl) {
try {
crypto.createHash('md5');
_impl = cryptoMd5;
} catch (e) {
_impl = jsMd5;
}
}
return _impl(x);
}

/* eslint-disable no-bitwise */
/* eslint-disable no-restricted-syntax */

export function cryptoMd5(x: string) {
const hash = crypto.createHash('md5');
hash.update(x);
return hash.digest('hex');
}

export function jsMd5(s: string) {
return hex(md51(s));
}

function md5cycle(x: number[], k: number[]) {
let a = x[0], b = x[1], c = x[2], d = x[3];

a = ff(a, b, c, d, k[0], 7, -680876936);
d = ff(d, a, b, c, k[1], 12, -389564586);
c = ff(c, d, a, b, k[2], 17, 606105819);
b = ff(b, c, d, a, k[3], 22, -1044525330);
a = ff(a, b, c, d, k[4], 7, -176418897);
d = ff(d, a, b, c, k[5], 12, 1200080426);
c = ff(c, d, a, b, k[6], 17, -1473231341);
b = ff(b, c, d, a, k[7], 22, -45705983);
a = ff(a, b, c, d, k[8], 7, 1770035416);
d = ff(d, a, b, c, k[9], 12, -1958414417);
c = ff(c, d, a, b, k[10], 17, -42063);
b = ff(b, c, d, a, k[11], 22, -1990404162);
a = ff(a, b, c, d, k[12], 7, 1804603682);
d = ff(d, a, b, c, k[13], 12, -40341101);
c = ff(c, d, a, b, k[14], 17, -1502002290);
b = ff(b, c, d, a, k[15], 22, 1236535329);

a = gg(a, b, c, d, k[1], 5, -165796510);
d = gg(d, a, b, c, k[6], 9, -1069501632);
c = gg(c, d, a, b, k[11], 14, 643717713);
b = gg(b, c, d, a, k[0], 20, -373897302);
a = gg(a, b, c, d, k[5], 5, -701558691);
d = gg(d, a, b, c, k[10], 9, 38016083);
c = gg(c, d, a, b, k[15], 14, -660478335);
b = gg(b, c, d, a, k[4], 20, -405537848);
a = gg(a, b, c, d, k[9], 5, 568446438);
d = gg(d, a, b, c, k[14], 9, -1019803690);
c = gg(c, d, a, b, k[3], 14, -187363961);
b = gg(b, c, d, a, k[8], 20, 1163531501);
a = gg(a, b, c, d, k[13], 5, -1444681467);
d = gg(d, a, b, c, k[2], 9, -51403784);
c = gg(c, d, a, b, k[7], 14, 1735328473);
b = gg(b, c, d, a, k[12], 20, -1926607734);

a = hh(a, b, c, d, k[5], 4, -378558);
d = hh(d, a, b, c, k[8], 11, -2022574463);
c = hh(c, d, a, b, k[11], 16, 1839030562);
b = hh(b, c, d, a, k[14], 23, -35309556);
a = hh(a, b, c, d, k[1], 4, -1530992060);
d = hh(d, a, b, c, k[4], 11, 1272893353);
c = hh(c, d, a, b, k[7], 16, -155497632);
b = hh(b, c, d, a, k[10], 23, -1094730640);
a = hh(a, b, c, d, k[13], 4, 681279174);
d = hh(d, a, b, c, k[0], 11, -358537222);
c = hh(c, d, a, b, k[3], 16, -722521979);
b = hh(b, c, d, a, k[6], 23, 76029189);
a = hh(a, b, c, d, k[9], 4, -640364487);
d = hh(d, a, b, c, k[12], 11, -421815835);
c = hh(c, d, a, b, k[15], 16, 530742520);
b = hh(b, c, d, a, k[2], 23, -995338651);

a = ii(a, b, c, d, k[0], 6, -198630844);
d = ii(d, a, b, c, k[7], 10, 1126891415);
c = ii(c, d, a, b, k[14], 15, -1416354905);
b = ii(b, c, d, a, k[5], 21, -57434055);
a = ii(a, b, c, d, k[12], 6, 1700485571);
d = ii(d, a, b, c, k[3], 10, -1894986606);
c = ii(c, d, a, b, k[10], 15, -1051523);
b = ii(b, c, d, a, k[1], 21, -2054922799);
a = ii(a, b, c, d, k[8], 6, 1873313359);
d = ii(d, a, b, c, k[15], 10, -30611744);
c = ii(c, d, a, b, k[6], 15, -1560198380);
b = ii(b, c, d, a, k[13], 21, 1309151649);
a = ii(a, b, c, d, k[4], 6, -145523070);
d = ii(d, a, b, c, k[11], 10, -1120210379);
c = ii(c, d, a, b, k[2], 15, 718787259);
b = ii(b, c, d, a, k[9], 21, -343485551);

x[0] = add32(a, x[0]);
x[1] = add32(b, x[1]);
x[2] = add32(c, x[2]);
x[3] = add32(d, x[3]);
}

function cmn(q: number, a: number, b: number, x: number, s: number, t: number) {
a = add32(add32(a, q), add32(x, t));
return add32((a << s) | (a >>> (32 - s)), b);
}

function ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number) {
return cmn((b & c) | (~b & d), a, b, x, s, t);
}

function gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number) {
return cmn((b & d) | (c & ~d), a, b, x, s, t);
}

function hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number) {
return cmn(b ^ c ^ d, a, b, x, s, t);
}

function ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number) {
return cmn(c ^ (b | ~d), a, b, x, s, t);
}

function md51(s: string) {
let n = s.length,
state = [1732584193, -271733879, -1732584194, 271733878],
i;
for (i = 64; i <= s.length; i += 64) {
md5cycle(state, md5blk(s.substring(i - 64, i)));
}
s = s.substring(i - 64);
let tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for (i = 0; i < s.length; i++) {
tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3);
}
tail[i >> 2] |= 0x80 << (i % 4 << 3);
if (i > 55) {
md5cycle(state, tail);
for (i = 0; i < 16; i++) tail[i] = 0;
}
tail[14] = n * 8;
md5cycle(state, tail);
return state;
}

function md5blk(s: string) {
let md5blks = [], i;
for (i = 0; i < 64; i += 4) {
md5blks[i >> 2] =
s.charCodeAt(i) +
(s.charCodeAt(i + 1) << 8) +
(s.charCodeAt(i + 2) << 16) +
(s.charCodeAt(i + 3) << 24);
}
return md5blks;
}

let hex_chr = '0123456789abcdef'.split('');

function rhex(n: number) {
let s = '', j = 0;
for (; j < 4; j++) {
s += hex_chr[(n >> (j * 8 + 4)) & 0x0f] + hex_chr[(n >> (j * 8)) & 0x0f];
}
return s;
}

function hex(x: number[]) {
const ret = new Array<string>(x.length);
for (let i = 0; i < x.length; i++) ret[i] = rhex(x[i]);
return ret.join('');
}

function add32(a: number, b: number) {
return (a + b) & 0xffffffff;
}
Loading

0 comments on commit 48efa8b

Please sign in to comment.