kibana/packages/kbn-eslint-plugin-eslint/rules/no_unsafe_hash.js
Kurt f207c2c176
ESLint Rule to discourage hashes being created with unsafe algorithms (#190973)
Closes https://github.com/elastic/kibana/issues/185601

## Summary

Using non-compliant algorithms with Node Cryptos createHash function
will cause failures when running Kibana in FIPS mode.

We want to discourage usages of such algorithms.

---------

Co-authored-by: Sid <siddharthmantri1@gmail.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
2024-09-30 11:34:04 -05:00

166 lines
5.6 KiB
JavaScript

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
const allowedAlgorithms = ['sha256', 'sha3-256', 'sha512'];
module.exports = {
allowedAlgorithms,
meta: {
type: 'problem',
docs: {
description: 'Allow usage of createHash only with allowed algorithms.',
category: 'FIPS',
recommended: false,
},
messages: {
noDisallowedHash:
'Usage of {{functionName}} with "{{algorithm}}" is not allowed. Only the following algorithms are allowed: [{{allowedAlgorithms}}]. If you need to use a different algorithm, please contact the Kibana security team.',
},
schema: [],
},
create(context) {
let isCreateHashImported = false;
let createHashName = 'createHash';
let cryptoLocalName = 'crypto';
let usedFunctionName = '';
const sourceCode = context.getSourceCode();
const disallowedAlgorithmNodes = new Set();
function isAllowedAlgorithm(algorithm) {
return allowedAlgorithms.includes(algorithm);
}
function isHashOrCreateHash(value) {
if (value === 'hash' || value === 'createHash') {
usedFunctionName = value;
return true;
}
return false;
}
function getIdentifierValue(node) {
const scope = sourceCode.getScope(node);
if (!scope) {
return;
}
const variable = scope.variables.find((variable) => variable.name === node.name);
if (variable && variable.defs.length > 0) {
const def = variable.defs[0];
if (
def.node.init &&
def.node.init.type === 'Literal' &&
!isAllowedAlgorithm(def.node.init.value)
) {
disallowedAlgorithmNodes.add(node.name);
return def.node.init.value;
}
}
}
return {
ImportDeclaration(node) {
if (node.source.value === 'crypto' || node.source.value === 'node:crypto') {
node.specifiers.forEach((specifier) => {
if (
specifier.type === 'ImportSpecifier' &&
isHashOrCreateHash(specifier.imported.name)
) {
isCreateHashImported = true;
createHashName = specifier.local.name; // Capture local name (renamed or not)
} else if (specifier.type === 'ImportDefaultSpecifier') {
cryptoLocalName = specifier.local.name;
}
});
}
},
VariableDeclarator(node) {
if (node.init && node.init.type === 'Literal' && !isAllowedAlgorithm(node.init.value)) {
disallowedAlgorithmNodes.add(node.id.name);
}
},
AssignmentExpression(node) {
if (
node.right.type === 'Literal' &&
node.right.value === 'md5' &&
node.left.type === 'Identifier'
) {
disallowedAlgorithmNodes.add(node.left.name);
}
},
CallExpression(node) {
const callee = node.callee;
if (
callee.type === 'MemberExpression' &&
callee.object.name === cryptoLocalName &&
isHashOrCreateHash(callee.property.name)
) {
const arg = node.arguments[0];
if (arg) {
if (arg.type === 'Literal' && !isAllowedAlgorithm(arg.value)) {
context.report({
node,
messageId: 'noDisallowedHash',
data: {
algorithm: arg.value,
allowedAlgorithms: allowedAlgorithms.join(', '),
functionName: usedFunctionName,
},
});
} else if (arg.type === 'Identifier') {
const identifierValue = getIdentifierValue(arg);
if (disallowedAlgorithmNodes.has(arg.name) && identifierValue) {
context.report({
node,
messageId: 'noDisallowedHash',
data: {
algorithm: identifierValue,
allowedAlgorithms: allowedAlgorithms.join(', '),
functionName: usedFunctionName,
},
});
}
}
}
}
if (isCreateHashImported && callee.name === createHashName) {
const arg = node.arguments[0];
if (arg) {
if (arg.type === 'Literal' && !isAllowedAlgorithm(arg.value)) {
context.report({
node,
messageId: 'noDisallowedHash',
data: {
algorithm: arg.value,
allowedAlgorithms: allowedAlgorithms.join(', '),
functionName: usedFunctionName,
},
});
} else if (arg.type === 'Identifier') {
const identifierValue = getIdentifierValue(arg);
if (disallowedAlgorithmNodes.has(arg.name) && identifierValue) {
context.report({
node,
messageId: 'noDisallowedHash',
data: {
algorithm: identifierValue,
allowedAlgorithms: allowedAlgorithms.join(', '),
functionName: usedFunctionName,
},
});
}
}
}
}
},
};
},
};