diff --git a/lib/key/utils.d.ts b/lib/key/utils.d.ts index 4a428e7..ca84742 100644 --- a/lib/key/utils.d.ts +++ b/lib/key/utils.d.ts @@ -33,8 +33,6 @@ export interface GenerateSessionKeyOptionsPmcrypto extends Omit; -export function getFingerprint(key: Key): string; - export function isExpiredKey(key: Key, date?: Date): Promise; export function isRevokedKey(key: Key, date?: Date): Promise; diff --git a/lib/key/utils.js b/lib/key/utils.js index d972cfe..99c24b0 100644 --- a/lib/key/utils.js +++ b/lib/key/utils.js @@ -90,14 +90,14 @@ export const canKeyEncrypt = async (publicKey, date = serverTime()) => { } }; -export function getFingerprint(key) { - return key.getFingerprint(); -} - export const getSHA256Fingerprints = (key) => { return Promise.all( - key.getKeys().map(async ({ keyPacket }) => { - return arrayToHexString(await SHA256(keyPacket.writeForHash(keyPacket.version))); + key.getKeys().map(async (keyOrSubkey) => { + const { version } = keyOrSubkey.keyPacket; + const keyFingerprintIsSHA256 = version === 5 || version === 6; + return keyFingerprintIsSHA256 ? + keyOrSubkey.getFingerprint() : + arrayToHexString(await SHA256(keyOrSubkey.keyPacket.writeForHash(version))); }) ); }; diff --git a/lib/pmcrypto.d.ts b/lib/pmcrypto.d.ts index 7f5d2b4..d1ae49d 100644 --- a/lib/pmcrypto.d.ts +++ b/lib/pmcrypto.d.ts @@ -37,7 +37,7 @@ export { generateKey, reformatKey, generateSessionKey, generateSessionKeyForAlgorithm, isExpiredKey, isRevokedKey, canKeyEncrypt, - getFingerprint, getSHA256Fingerprints, + getSHA256Fingerprints, getMatchingKey } from './key/utils'; diff --git a/lib/pmcrypto.js b/lib/pmcrypto.js index 27b444e..8ad8401 100644 --- a/lib/pmcrypto.js +++ b/lib/pmcrypto.js @@ -30,7 +30,6 @@ export { isExpiredKey, isRevokedKey, canKeyEncrypt, - getFingerprint, getMatchingKey, getSHA256Fingerprints } from './key/utils'; diff --git a/package-lock.json b/package-lock.json index 4952932..76d9efc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@types/chai-as-promised": "^7.1.8", "@types/elliptic": "^6.4.18", "@types/mocha": "^9.1.1", + "@types/sinon": "^17.0.3", "@types/webpack-env": "^1.18.5", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -42,6 +43,7 @@ "karma-webpack": "^5.0.1", "mocha": "^11.0.1", "playwright": "^1.48.2", + "sinon": "^19.0.2", "ts-loader": "^9.5.1", "typescript": "^5.6.3", "web-streams-polyfill": "^3.3.3", @@ -480,6 +482,55 @@ "dev": true, "license": "MIT" }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/commons/node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "lodash.get": "^4.4.2", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -608,6 +659,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/webpack-env": { "version": "1.18.5", "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.5.tgz", @@ -4158,6 +4226,13 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true, + "license": "MIT" + }, "node_modules/karma": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", @@ -4501,6 +4576,14 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4923,6 +5006,20 @@ "dev": true, "license": "MIT" }, + "node_modules/nise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", + "just-extend": "^6.2.0", + "path-to-regexp": "^8.1.0" + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -5236,6 +5333,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5900,6 +6007,35 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sinon": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", + "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "nise": "^6.1.1", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/package.json b/package.json index e833a03..8039cd8 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@types/chai-as-promised": "^7.1.8", "@types/elliptic": "^6.4.18", "@types/mocha": "^9.1.1", + "@types/sinon": "^17.0.3", "@types/webpack-env": "^1.18.5", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -62,6 +63,7 @@ "karma-webpack": "^5.0.1", "mocha": "^11.0.1", "playwright": "^1.48.2", + "sinon": "^19.0.2", "ts-loader": "^9.5.1", "typescript": "^5.6.3", "web-streams-polyfill": "^3.3.3", diff --git a/test/key/utils.spec.ts b/test/key/utils.spec.ts index de2b2f8..e60f26a 100644 --- a/test/key/utils.spec.ts +++ b/test/key/utils.spec.ts @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import { createSandbox as createSinonSandbox } from 'sinon'; import { revokeKey, sign, createMessage, enums } from '../../lib/openpgp'; import { isExpiredKey, @@ -54,11 +55,19 @@ T/efFOC6BDkAAHcjAPwIPNHnR9bKmkVop6cE05dCIpZ/W8zXDGnjKYrrC4Hb const { publicKey } = await generateKey({ userIDs: [{}], passphrase: 'test', config: { v6Keys: true }, format: 'object' }); const fingerprints = publicKey.getKeys().map((key) => key.getFingerprint()); - const sha256Fingerprints = await getSHA256Fingerprints(publicKey); - expect(sha256Fingerprints.length).to.equal(fingerprints.length); - sha256Fingerprints.forEach((sha256Fingerprint, i) => { - expect(sha256Fingerprint).to.equal(fingerprints[i]); - }); + const sinonSandbox = createSinonSandbox(); + try { + const webcryptoHashSpy = sinonSandbox.spy(crypto.subtle, 'digest'); + const sha256Fingerprints = await getSHA256Fingerprints(publicKey); + // ensure no hashing is done for v6 keys; the fingerprints stored in the key are expected to be returned. + expect(webcryptoHashSpy.called).to.be.false; + expect(sha256Fingerprints.length).to.equal(fingerprints.length); + sha256Fingerprints.forEach((sha256Fingerprint, i) => { + expect(sha256Fingerprint).to.equal(fingerprints[i]); + }); + } finally { + sinonSandbox.restore(); + } }); it('generateKey - it has valid default creation time', async () => {