UNPKG

mcard-js

Version:

A JavaScript implementation of MCard - A data model for persistently storing content with cryptographic hashing and timestamping

272 lines (231 loc) 8.18 kB
// Replace direct crypto import with environment-aware implementation import { encodeText } from '../../utils/textEncoderPolyfill.js'; import { SafeBuffer } from '../../utils/bufferPolyfill.js'; import { createHash } from '../../utils/cryptoPolyfill.js'; import { HashAlgorithm, HASH_ALGORITHM_HIERARCHY, VALID_HASH_FUNCTIONS } from '../../config/config_constants.js'; // Check if we're in a browser environment const isBrowser = typeof window !== 'undefined'; export default class HashValidator { /** * Constructor for HashValidator */ constructor(content, hashAlgorithm = HashAlgorithm.DEFAULT) { // Convert content to Buffer/Uint8Array if it's a string this.content = SafeBuffer.isBuffer(content) ? content : encodeText(content); this.hashAlgorithm = this.normalizeHashAlgorithm(hashAlgorithm); // In browser environments, we can't synchronously compute crypto hashes if (isBrowser) { this.hashValue = "computing..."; this._computeHashAsync().then(hash => { this.hashValue = hash; }); } else { this.hashValue = this.computeHash(); } } /** * Async computation of hash for browser environments * @private */ async _computeHashAsync() { const hash = createHash(this.hashAlgorithm); hash.update(this.content); return await hash.digest('hex'); } /** * Normalizes the hash algorithm input * @param {string|Object} hashAlgorithm - The hash algorithm to normalize * @returns {string} Normalized hash algorithm */ normalizeHashAlgorithm(hashAlgorithm) { // Handle undefined or null input if (hashAlgorithm === undefined || hashAlgorithm === null) { return HashAlgorithm.DEFAULT || 'sha256'; } // If input is an object, try to extract the type if (typeof hashAlgorithm === 'object') { hashAlgorithm = hashAlgorithm.type || hashAlgorithm.value || HashAlgorithm.DEFAULT || 'sha256'; } // Convert to lowercase string and remove dashes for consistency const normalizedAlgo = String(hashAlgorithm).toLowerCase().replace(/-/g, ''); // Validate the hash algorithm if (!HashValidator.isValidHashFunction(normalizedAlgo)) { console.warn(`Invalid hash algorithm: ${normalizedAlgo}, using default instead`); return HashAlgorithm.DEFAULT || 'sha256'; } return normalizedAlgo; } /** * Validates a hash function * @param {string} hashFunction - Hash function to validate * @returns {boolean} Whether the hash function is valid */ static isValidHashFunction(hashFunction) { if (!hashFunction) return false; const normalizedFunc = String(hashFunction).toLowerCase().trim(); // Accept both formats: with dashes (Web Crypto format) and without dashes (Node.js format) const validAlgorithms = [ // Node.js format 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', // Web Crypto format 'sha-1', 'sha-256', 'sha-384', 'sha-512' ]; return validAlgorithms.includes(normalizedFunc) || validAlgorithms.includes(normalizedFunc.replace(/-/g, '')); } /** * Compute hash from content * @returns {string|Promise<string>} Computed hash or promise to hash */ computeHash() { try { const hash = createHash(this.hashAlgorithm); hash.update(this.content); const result = hash.digest('hex'); // Handle the case where result is a Promise if (result instanceof Promise) { return "computing..."; } return result; } catch (e) { console.error('Error computing hash:', e); return ''; } } /** * Getter for hash value * @returns {string} Hash value */ getHashValue() { return this.hashValue; } /** * Getter for hash algorithm * @returns {string} Hash algorithm */ getHashAlgorithm() { return this.hashAlgorithm; } /** * Static method to compute hash * @param {string|Buffer} content - Content to hash * @param {string} hashAlgorithm - Algorithm to use * @returns {string|Promise<string>} Computed hash or Promise of hash in browser */ static computeHash(content, hashAlgorithm = HashAlgorithm.DEFAULT) { const buffer = SafeBuffer.isBuffer(content) ? content : encodeText(content); const hash = createHash(hashAlgorithm); hash.update(buffer); return hash.digest('hex'); } /** * Validate hash against an expected hash * @param {string} [expectedHash] - Expected hash value * @returns {boolean|Promise<boolean>} True if hash matches, false otherwise */ validate(expectedHash) { if (!expectedHash) return false; if (isBrowser) { // In browser, we need to handle async validation return this._computeHashAsync().then(computedHash => { return computedHash === expectedHash; }); } return this.hashValue === expectedHash; } /** * Return string representation * @returns {string} String representation */ toString() { return `HashValidator(alg=${this.hashAlgorithm}, hash=${this.hashValue})`; } /** * Gets the strength order of hash algorithms * @returns {string[]} Ordered list of hash algorithms by strength */ getHashAlgorithmStrengthOrder() { return [ HashAlgorithm.MD5, HashAlgorithm.SHA1, HashAlgorithm.SHA224, HashAlgorithm.SHA256, HashAlgorithm.SHA384, HashAlgorithm.SHA512 ]; } /** * Gets the strength index of a hash algorithm * @param {string} algorithm - Hash algorithm * @returns {number} Strength index */ getHashAlgorithmStrength(algorithm) { return this.getHashAlgorithmStrengthOrder().indexOf(algorithm); } /** * Checks if one hash algorithm is stronger than another * @param {string} current - Current hash algorithm * @param {string} upgrade - Potential upgrade hash algorithm * @returns {boolean} Whether the upgrade is stronger */ isStrongerHashAlgorithm(current, upgrade) { return this.getHashAlgorithmStrength(upgrade) > this.getHashAlgorithmStrength(current); } /** * Determines the next hash algorithm in the upgrade path * @param {string|Object} currentHashFunction - Current hash function * @returns {string} Next hash algorithm */ nextHashFunction(currentHashFunction) { const strengthOrder = this.getHashAlgorithmStrengthOrder(); // Handle undefined or null input if (currentHashFunction === undefined || currentHashFunction === null) { return HashAlgorithm.MD5; } // Extract hash function value if it's an object const currentHash = typeof currentHashFunction === 'object' ? currentHashFunction.value || currentHashFunction.type : currentHashFunction; // Normalize to lowercase const normalizedHash = (currentHash || '').toLowerCase(); // Find current index const currentIndex = strengthOrder.indexOf(normalizedHash); // Special case for SHA512 - wrap around to SHA1 if (normalizedHash === HashAlgorithm.SHA512) { return HashAlgorithm.SHA1; } // If not found or last in order, return default (first hash function) if (currentIndex === -1 || currentIndex === strengthOrder.length - 1) { return HashAlgorithm[strengthOrder[0].toUpperCase()]; } // Return the next hash function in the order as an enum return HashAlgorithm[strengthOrder[currentIndex + 1].toUpperCase()]; } /** * Static method to get supported hash algorithms * @returns {string[]} List of supported hash algorithms */ static getSupportedAlgorithms() { return [ HashAlgorithm.MD5, HashAlgorithm.SHA1, HashAlgorithm.SHA256, HashAlgorithm.SHA512 ]; } static compute_hash(content, hashAlgorithm = HashAlgorithm.DEFAULT) { return HashValidator.computeHash(content, hashAlgorithm); } static HashAlgorithm = HashAlgorithm; static HASH_ALGORITHM_HIERARCHY = HASH_ALGORITHM_HIERARCHY; static VALID_HASH_FUNCTIONS = VALID_HASH_FUNCTIONS; } export { HashAlgorithm, HASH_ALGORITHM_HIERARCHY, VALID_HASH_FUNCTIONS };