Add SHA-3 signature support (#1680)

To support parsing, signing and verifying SHA3 signatures over messages and
keys.
This commit is contained in:
Ryan
2023-09-25 05:17:21 -04:00
committed by larabr
parent 8fe04c99c6
commit 53e1ec023f
5 changed files with 59 additions and 2 deletions

View File

@@ -8,6 +8,7 @@
import { sha1 } from '@openpgp/noble-hashes/sha1';
import { sha224, sha256 } from '@openpgp/noble-hashes/sha256';
import { sha384, sha512 } from '@openpgp/noble-hashes/sha512';
import { sha3_256, sha3_512 } from '@openpgp/noble-hashes/sha3';
import { ripemd160 } from '@openpgp/noble-hashes/ripemd160';
import * as stream from '@openpgp/web-stream-tools';
import md5 from './md5';
@@ -55,7 +56,9 @@ const hashFunctions = {
sha256: nodeHash('sha256') || nobleHash(sha256, 'SHA-256'),
sha384: nodeHash('sha384') || nobleHash(sha384, 'SHA-384'),
sha512: nodeHash('sha512') || nobleHash(sha512, 'SHA-512'),
ripemd: nodeHash('ripemd160') || nobleHash(ripemd160)
ripemd: nodeHash('ripemd160') || nobleHash(ripemd160),
sha3_256: nodeHash('sha3-256') || nobleHash(sha3_256),
sha3_512: nodeHash('sha3-512') || nobleHash(sha3_512)
};
export default {
@@ -68,6 +71,8 @@ export default {
sha384: hashFunctions.sha384,
sha512: hashFunctions.sha512,
ripemd: hashFunctions.ripemd,
sha3_256: hashFunctions.sha3_256,
sha3_512: hashFunctions.sha3_512,
/**
* Create a hash on the specified data using the specified algorithm
@@ -91,6 +96,10 @@ export default {
return this.sha512(data);
case enums.hash.sha224:
return this.sha224(data);
case enums.hash.sha3_256:
return this.sha3_256(data);
case enums.hash.sha3_512:
return this.sha3_512(data);
default:
throw new Error('Invalid hash function.');
}
@@ -116,6 +125,10 @@ export default {
return 64;
case enums.hash.sha224:
return 28;
case enums.hash.sha3_256:
return 32;
case enums.hash.sha3_512:
return 64;
default:
throw new Error('Invalid hash algorithm.');
}

View File

@@ -175,7 +175,9 @@ export default {
sha256: 8,
sha384: 9,
sha512: 10,
sha224: 11
sha224: 11,
sha3_256: 12,
sha3_512: 14
},
/** A list of hash names as accepted by webCrypto functions.

View File

@@ -833,6 +833,8 @@ export function saltLengthForHash(hashAlgorithm) {
case enums.hash.sha384: return 24;
case enums.hash.sha512: return 32;
case enums.hash.sha224: return 16;
case enums.hash.sha3_256: return 16;
case enums.hash.sha3_512: return 32;
default: throw new Error('Unsupported hash function for V6 signatures');
}
}

View File

@@ -14,4 +14,8 @@ export default () => it('SHA* with test vectors from NIST FIPS 180-2', async fun
expect(util.uint8ArrayToHex(await hash.sha384(util.stringToUint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq')), 'hash.sha384("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b')).to.equal('3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b');
expect(util.uint8ArrayToHex(await hash.sha512(util.stringToUint8Array('abc')), 'hash.sha512("abc") = ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f')).to.equal('ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f');
expect(util.uint8ArrayToHex(await hash.sha512(util.stringToUint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq')), 'hash.sha512("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445')).to.equal('204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445');
expect(util.uint8ArrayToHex(await hash.sha3_256(util.stringToUint8Array('abc')), 'hash.sha3_256("abc") = 3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532')).to.equal('3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532');
expect(util.uint8ArrayToHex(await hash.sha3_256(util.stringToUint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq')), 'hash.sha3_256("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376')).to.equal('41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376');
expect(util.uint8ArrayToHex(await hash.sha3_512(util.stringToUint8Array('abc')), 'hash.sha3_512("abc") = b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0')).to.equal('b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0');
expect(util.uint8ArrayToHex(await hash.sha3_512(util.stringToUint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq')), 'hash.sha3_512("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 04a371e84ecfb5b8b77cb48610fca8182dd457ce6f326a0fd3d7ec2f1e91636dee691fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e')).to.equal('04a371e84ecfb5b8b77cb48610fca8182dd457ce6f326a0fd3d7ec2f1e91636dee691fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e');
});

View File

@@ -297,6 +297,25 @@ DECl1Qu4QyeXin29uEXWiekMpNlZVsEuc8icCw6ABhIZ
=/7PI
-----END PGP PRIVATE KEY BLOCK-----`;
const priv_key_sha3 = `-----BEGIN PGP PRIVATE KEY BLOCK-----
xUsGZN8edBsAAAAgdUMlFMFCVKNo7sdUd6FVBos6NNjpUpSdrodk6BfPb/kA+3bu
A2+WY2LwyxlX5o07WR2VSn+wuegC3v28yO0tClHCtwYfGw4AAABIBYJk3x50BAsJ
CAcHFQ4MCgkICwIWAAIXgAKbAwIeCSIhBpbSe0QWuaCNSSLaePhXEP3BxQ2VHX3W
pW1U6svHvCUiBScJAgcCAAAAACMZIP8aHixoyC9wS3q/TNV/IfOQa81f+U5Ucz6H
4I+c5bWRYUzH/piBB4n5FoYlld+/SViCQIBCQ+fynLmaj5wlf22+mISTt/9je1Zf
YWlJ+WSJyi5gY5EH9DubfuIU3VaqCM0aQmVybmFkZXR0ZSA8YkBleGFtcGxlLm9y
Zz7CugYTGw4AAABLBYJk3x50BAsJCAcHFQ4MCgkICwIWAAIXgAIZAQKbAwIeCSIh
BpbSe0QWuaCNSSLaePhXEP3BxQ2VHX3WpW1U6svHvCUiBScJAgcCAAAAAMMGIJEi
9+yqkFKsNwX1H5II0riPudFpwBx2ypVjNk4aNb7Exl56Aac4tXEhz4fH41q0dAzF
ww2erZaiUqmohQ4AFSw1jN/WOiDfb1DkjT/HJ8vXMGpwWdgFPoqsWzTNhd5VCcdL
BmTfHnQZAAAAIAGMcsqVCXLclRhVamWciSxmnYF1FFs80W7dNUH07HUOAHh/S601
If+/eZKDIj3jq7oOe2PzHSYEK+mpQD1hBpF2wpsGGBsOAAAALAWCZN8edAKbDCIh
BpbSe0QWuaCNSSLaePhXEP3BxQ2VHX3WpW1U6svHvCUiAAAAANj3IBknZTPsMpWA
we0Jl5gw/Dj4lWAGoJfWfk+6s3Q86Hag3Hu8VBsapzmul+vzy0KJa+ZRcZz2n8aj
0vTl4sOZ0EcCdFDfkh/tR//gKkT6BiSBG86WoFq3f6U/RC+z0Ym7Dw==
-----END PGP PRIVATE KEY BLOCK-----`;
const passphrase = 'hello world';
const plaintext = input.createSomeMessage();
const password1 = 'I am a password';
@@ -1821,6 +1840,23 @@ aOU=
})).to.be.rejectedWith(/No signing keys provided/);
});
it('Signing with key which uses sha3 should generate a valid sha3 signature', async function() {
const privKey = await openpgp.readKey({ armoredKey: priv_key_sha3 });
const pubKey = privKey.toPublic();
const text = 'Hello, world.';
const message = await openpgp.createCleartextMessage({ text });
const cleartextMessage = await openpgp.sign({ message, signingKeys: privKey, format: 'armored' });
const parsedArmored = await openpgp.readCleartextMessage({ cleartextMessage });
expect(parsedArmored.signature.packets.filterByTag(openpgp.enums.packet.signature)).to.have.length(1);
expect(
parsedArmored.signature.packets.filterByTag(openpgp.enums.packet.signature)[0].hashAlgorithm
).to.equal(openpgp.enums.hash.sha3_512);
const verified = await openpgp.verify({ message: parsedArmored, verificationKeys: pubKey, expectSigned: true });
expect(verified.data).to.equal(text);
});
it('should output cleartext message of expected format', async function() {
const text = 'test';
const message = await openpgp.createCleartextMessage({ text });