Fix parsing of short P-521 keys and EdDSA, RSA signatures (#1185)
Also, strip leading zeros when serializing MPIs, as per the spec.
This commit is contained in:
parent
c34dede6de
commit
286d991265
|
@ -149,7 +149,7 @@ export function parsePublicKeyParams(algo, bytes) {
|
||||||
case enums.publicKey.eddsa: {
|
case enums.publicKey.eddsa: {
|
||||||
const oid = new OID(); read += oid.read(bytes);
|
const oid = new OID(); read += oid.read(bytes);
|
||||||
let Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2;
|
let Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2;
|
||||||
Q = util.padToLength(Q, 33);
|
Q = util.leftPad(Q, 33);
|
||||||
return { read: read, publicParams: { oid, Q } };
|
return { read: read, publicParams: { oid, Q } };
|
||||||
}
|
}
|
||||||
case enums.publicKey.ecdh: {
|
case enums.publicKey.ecdh: {
|
||||||
|
@ -191,12 +191,12 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) {
|
||||||
case enums.publicKey.ecdh: {
|
case enums.publicKey.ecdh: {
|
||||||
const curve = new Curve(publicParams.oid);
|
const curve = new Curve(publicParams.oid);
|
||||||
let d = util.readMPI(bytes.subarray(read)); read += d.length + 2;
|
let d = util.readMPI(bytes.subarray(read)); read += d.length + 2;
|
||||||
d = util.padToLength(d, curve.payloadSize);
|
d = util.leftPad(d, curve.payloadSize);
|
||||||
return { read, privateParams: { d } };
|
return { read, privateParams: { d } };
|
||||||
}
|
}
|
||||||
case enums.publicKey.eddsa: {
|
case enums.publicKey.eddsa: {
|
||||||
let seed = util.readMPI(bytes.subarray(read)); read += seed.length + 2;
|
let seed = util.readMPI(bytes.subarray(read)); read += seed.length + 2;
|
||||||
seed = util.padToLength(seed, 32);
|
seed = util.leftPad(seed, 32);
|
||||||
return { read, privateParams: { seed } };
|
return { read, privateParams: { seed } };
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -207,7 +207,6 @@ async function webSign(curve, hash_algo, message, keyPair) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
||||||
const len = curve.payloadSize;
|
|
||||||
const jwk = rawPublicToJwk(curve.payloadSize, webCurves[curve.name], publicKey);
|
const jwk = rawPublicToJwk(curve.payloadSize, webCurves[curve.name], publicKey);
|
||||||
const key = await webCrypto.importKey(
|
const key = await webCrypto.importKey(
|
||||||
"jwk",
|
"jwk",
|
||||||
|
@ -221,10 +220,7 @@ async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
||||||
["verify"]
|
["verify"]
|
||||||
);
|
);
|
||||||
|
|
||||||
const signature = util.concatUint8Array([
|
const signature = util.concatUint8Array([r, s]).buffer;
|
||||||
new Uint8Array(len - r.length), r,
|
|
||||||
new Uint8Array(len - s.length), s
|
|
||||||
]).buffer;
|
|
||||||
|
|
||||||
return webCrypto.verify(
|
return webCrypto.verify(
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,9 +10,10 @@ import publicKey from './public_key';
|
||||||
import enums from '../enums';
|
import enums from '../enums';
|
||||||
import util from '../util';
|
import util from '../util';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse signature in binary form to get the parameters.
|
* Parse signature in binary form to get the parameters.
|
||||||
|
* The returned values are only padded for EdDSA, since in the other cases their expected length
|
||||||
|
* depends on the key params, hence we delegate the padding to the signature verification function.
|
||||||
* See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}
|
* See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}
|
||||||
* See {@link https://tools.ietf.org/html/rfc4880#section-5.2.2|RFC 4880 5.2.2.}
|
* See {@link https://tools.ietf.org/html/rfc4880#section-5.2.2|RFC 4880 5.2.2.}
|
||||||
* @param {module:enums.publicKey} algo Public key algorithm
|
* @param {module:enums.publicKey} algo Public key algorithm
|
||||||
|
@ -29,6 +30,8 @@ export function parseSignatureParams(algo, signature) {
|
||||||
case enums.publicKey.rsaEncrypt:
|
case enums.publicKey.rsaEncrypt:
|
||||||
case enums.publicKey.rsaSign: {
|
case enums.publicKey.rsaSign: {
|
||||||
const s = util.readMPI(signature.subarray(read));
|
const s = util.readMPI(signature.subarray(read));
|
||||||
|
// The signature needs to be the same length as the public key modulo n.
|
||||||
|
// We pad s on signature verification, where we have access to n.
|
||||||
return { s };
|
return { s };
|
||||||
}
|
}
|
||||||
// Algorithm-Specific Fields for DSA or ECDSA signatures:
|
// Algorithm-Specific Fields for DSA or ECDSA signatures:
|
||||||
|
@ -43,12 +46,14 @@ export function parseSignatureParams(algo, signature) {
|
||||||
}
|
}
|
||||||
// Algorithm-Specific Fields for EdDSA signatures:
|
// Algorithm-Specific Fields for EdDSA signatures:
|
||||||
// - MPI of an EC point r.
|
// - MPI of an EC point r.
|
||||||
// - EdDSA value s, in MPI, in the little endian representation.
|
// - EdDSA value s, in MPI, in the little endian representation
|
||||||
// EdDSA signature parameters are encoded in little-endian format
|
|
||||||
// https://tools.ietf.org/html/rfc8032#section-5.1.2
|
|
||||||
case enums.publicKey.eddsa: {
|
case enums.publicKey.eddsa: {
|
||||||
const r = util.padToLength(util.readMPI(signature.subarray(read)), 32, 'le'); read += r.length + 2;
|
// When parsing little-endian MPI data, we always need to left-pad it, as done with big-endian values:
|
||||||
const s = util.padToLength(util.readMPI(signature.subarray(read)), 32, 'le');
|
// https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#section-3.2-9
|
||||||
|
let r = util.readMPI(signature.subarray(read)); read += r.length + 2;
|
||||||
|
r = util.leftPad(r, 32);
|
||||||
|
let s = util.readMPI(signature.subarray(read));
|
||||||
|
s = util.leftPad(s, 32);
|
||||||
return { r, s };
|
return { r, s };
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -76,20 +81,25 @@ export async function verify(algo, hashAlgo, signature, publicParams, data, hash
|
||||||
case enums.publicKey.rsaEncrypt:
|
case enums.publicKey.rsaEncrypt:
|
||||||
case enums.publicKey.rsaSign: {
|
case enums.publicKey.rsaSign: {
|
||||||
const { n, e } = publicParams;
|
const { n, e } = publicParams;
|
||||||
const { s } = signature;
|
const s = util.leftPad(signature.s, n.length); // padding needed for webcrypto and node crypto
|
||||||
return publicKey.rsa.verify(hashAlgo, data, s, n, e, hashed);
|
return publicKey.rsa.verify(hashAlgo, data, s, n, e, hashed);
|
||||||
}
|
}
|
||||||
case enums.publicKey.dsa: {
|
case enums.publicKey.dsa: {
|
||||||
const { g, p, q, y } = publicParams;
|
const { g, p, q, y } = publicParams;
|
||||||
const { r, s } = signature;
|
const { r, s } = signature; // no need to pad, since we always handle them as BigIntegers
|
||||||
return publicKey.dsa.verify(hashAlgo, r, s, hashed, g, p, q, y);
|
return publicKey.dsa.verify(hashAlgo, r, s, hashed, g, p, q, y);
|
||||||
}
|
}
|
||||||
case enums.publicKey.ecdsa: {
|
case enums.publicKey.ecdsa: {
|
||||||
const { oid, Q } = publicParams;
|
const { oid, Q } = publicParams;
|
||||||
return publicKey.elliptic.ecdsa.verify(oid, hashAlgo, signature, data, Q, hashed);
|
const curveSize = new publicKey.elliptic.Curve(oid).payloadSize;
|
||||||
|
// padding needed for webcrypto
|
||||||
|
const r = util.leftPad(signature.r, curveSize);
|
||||||
|
const s = util.leftPad(signature.s, curveSize);
|
||||||
|
return publicKey.elliptic.ecdsa.verify(oid, hashAlgo, { r, s }, data, Q, hashed);
|
||||||
}
|
}
|
||||||
case enums.publicKey.eddsa: {
|
case enums.publicKey.eddsa: {
|
||||||
const { oid, Q } = publicParams;
|
const { oid, Q } = publicParams;
|
||||||
|
// signature already padded on parsing
|
||||||
return publicKey.elliptic.eddsa.verify(oid, hashAlgo, signature, data, Q, hashed);
|
return publicKey.elliptic.eddsa.verify(oid, hashAlgo, signature, data, Q, hashed);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
17
src/util.js
17
src/util.js
|
@ -178,15 +178,14 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pad Uint8Array to length by adding 0x0 bytes
|
* Left-pad Uint8Array to length by adding 0x0 bytes
|
||||||
* @param {Uint8Array} bytes data to pad
|
* @param {Uint8Array} bytes data to pad
|
||||||
* @param {Number} length padded length
|
* @param {Number} length padded length
|
||||||
* @param {'be'|'le'} endianess endianess of input data
|
|
||||||
* @return {Uint8Array} padded bytes
|
* @return {Uint8Array} padded bytes
|
||||||
*/
|
*/
|
||||||
padToLength(bytes, length, endianess = 'be') {
|
leftPad(bytes, length) {
|
||||||
const padded = new Uint8Array(length);
|
const padded = new Uint8Array(length);
|
||||||
const offset = (endianess === 'be') ? 0 : (length - bytes.length);
|
const offset = length - bytes.length;
|
||||||
padded.set(bytes, offset);
|
padded.set(bytes, offset);
|
||||||
return padded;
|
return padded;
|
||||||
},
|
},
|
||||||
|
@ -197,9 +196,15 @@ export default {
|
||||||
* @returns {Uint8Array} MPI-formatted Uint8Array
|
* @returns {Uint8Array} MPI-formatted Uint8Array
|
||||||
*/
|
*/
|
||||||
uint8ArrayToMpi: function (bin) {
|
uint8ArrayToMpi: function (bin) {
|
||||||
const size = (bin.length - 1) * 8 + util.nbits(bin[0]);
|
let i; // index of leading non-zero byte
|
||||||
|
for (i = 0; i < bin.length; i++) if (bin[i] !== 0) break;
|
||||||
|
if (i === bin.length) {
|
||||||
|
throw new Error('Zero MPI');
|
||||||
|
}
|
||||||
|
const stripped = bin.subarray(i);
|
||||||
|
const size = (stripped.length - 1) * 8 + util.nbits(stripped[0]);
|
||||||
const prefix = Uint8Array.from([(size & 0xFF00) >> 8, size & 0xFF]);
|
const prefix = Uint8Array.from([(size & 0xFF00) >> 8, size & 0xFF]);
|
||||||
return util.concatUint8Array([prefix, bin]);
|
return util.concatUint8Array([prefix, stripped]);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -80,6 +80,7 @@ function cloneKeyPacket(key) {
|
||||||
return keyPacket;
|
return keyPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* eslint-disable no-invalid-this */
|
||||||
module.exports = () => {
|
module.exports = () => {
|
||||||
describe('EdDSA parameter validation', function() {
|
describe('EdDSA parameter validation', function() {
|
||||||
let eddsaKey;
|
let eddsaKey;
|
||||||
|
@ -201,28 +202,32 @@ module.exports = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ecdsaKey) {
|
it(`EcDSA ${curve} params should be valid`, async function() {
|
||||||
it(`EcDSA ${curve} params should be valid`, async function() {
|
if (!ecdsaKey) {
|
||||||
await expect(ecdsaKey.keyPacket.validate()).to.not.be.rejected;
|
this.skip();
|
||||||
});
|
}
|
||||||
|
await expect(ecdsaKey.keyPacket.validate()).to.not.be.rejected;
|
||||||
|
});
|
||||||
|
|
||||||
it('detect invalid EcDSA Q', async function() {
|
it(`ECDSA ${curve} - detect invalid Q`, async function() {
|
||||||
const keyPacket = cloneKeyPacket(ecdsaKey);
|
if (!ecdsaKey) {
|
||||||
const Q = keyPacket.publicParams.Q;
|
this.skip();
|
||||||
Q[0]++;
|
}
|
||||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
const keyPacket = cloneKeyPacket(ecdsaKey);
|
||||||
|
const Q = keyPacket.publicParams.Q;
|
||||||
const infQ = new Uint8Array(Q.length);
|
Q[16]++;
|
||||||
keyPacket.publicParams.Q = infQ;
|
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
const infQ = new Uint8Array(Q.length);
|
||||||
});
|
infQ[0] = 4;
|
||||||
}
|
keyPacket.publicParams.Q = infQ;
|
||||||
|
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
|
});
|
||||||
|
|
||||||
it(`ECDH ${curve} params should be valid`, async function() {
|
it(`ECDH ${curve} params should be valid`, async function() {
|
||||||
await expect(ecdhKey.keyPacket.validate()).to.not.be.rejected;
|
await expect(ecdhKey.keyPacket.validate()).to.not.be.rejected;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('detect invalid ECDH Q', async function() {
|
it(`ECDH ${curve} - detect invalid Q`, async function() {
|
||||||
const keyPacket = cloneKeyPacket(ecdhKey);
|
const keyPacket = cloneKeyPacket(ecdhKey);
|
||||||
const Q = keyPacket.publicParams.Q;
|
const Q = keyPacket.publicParams.Q;
|
||||||
Q[16]++;
|
Q[16]++;
|
||||||
|
@ -230,6 +235,7 @@ module.exports = () => {
|
||||||
|
|
||||||
const infQ = new Uint8Array(Q.length);
|
const infQ = new Uint8Array(Q.length);
|
||||||
keyPacket.publicParams.Q = infQ;
|
keyPacket.publicParams.Q = infQ;
|
||||||
|
infQ[0] = 4;
|
||||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2020,6 +2020,31 @@ EBeLgD8oZHVsH3NLjPakPw==
|
||||||
=STqy
|
=STqy
|
||||||
-----END PGP MESSAGE-----`;
|
-----END PGP MESSAGE-----`;
|
||||||
|
|
||||||
|
const shortP521Key = `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
xcAiBV/Pa+4TAAAAjQUrgQQAIwQjBADY+IGvRpeUzbT0+YRUe0KCMxAZmDY1
|
||||||
|
KjDzULlmOJOS0bfZfqd4HsUF2hRoU/rg1gu1ju/Nt/18db0SJExOqVB9CgA0
|
||||||
|
ZYiYyJhGYDOVg/bD54E7a3txWuDPB1DzkGWJH8PkqGNzU0BJpZcUVA6Th09s
|
||||||
|
YeO7rx5jSoyWNXjUYKwc24trFAAAAAAARQIItVgIiTWNT+QEVnZqDKKTIOUU
|
||||||
|
XEetkjCjPed1RiSchiZpwq+Bvx5hWGsbV5Pjj0S6EuH/ca5w+2ZyITLWZjr1
|
||||||
|
LP8eP80UVGVzdCA8dGVzdEB0ZXN0LmNvbT7CwBUFEBMKACEFAl/Pa+4ECwkH
|
||||||
|
CAMVCAoEFgIBAAIZAQIbAwIeBwMiAQIAIyIhBbDerrGG7kdy9vbLYvb/j6TC
|
||||||
|
53fuQgK9Gtt1xng5MgpSUX0CCJZB+Ppt8yG5hBzwiGz5ZRpPVBFihEftaTOO
|
||||||
|
tKUuYRpWlvgA/XV11DgL6KZWRwu4C2venydBW987CVXCbRp4r18FAgkBjTR1
|
||||||
|
AXHEstTVwJYj8mWkOZrz+Bfqvu6pWPmVYclgHJK2sSWizakvX/DtX/LFgTJL
|
||||||
|
UoASOVvu1hYHDsCO7vWWC/bHwCYFX89r7hIAAACRBSuBBAAjBCMEAULqNQ3L
|
||||||
|
CcdVlpIHiq4Xb+elTEdu2oDDA+FCbwroX3wvMatrH6GodxCcrjQKUrfVNiiI
|
||||||
|
cvj+r6SE/pRDnxsvW/JSAWUz3XKfVXccb0PYf0ikkTmb8UW33AaNYX6Srk0W
|
||||||
|
iamEmEzUpCMiiyXiYe+fp9JD63rKLXBbvLCT2mHuYO/hOikKAwEKCQAAAAAA
|
||||||
|
RQIDBONtE8bb3Yr2otNhdR67lg529mm3rSRsyWwMBVUPwX0RTTZ/bejq7XP5
|
||||||
|
fuXV8QSEjWdOdPBARGw9jhw51D1XWl8gFsK9BRgTCgAJBQJfz2vuAhsMACMi
|
||||||
|
IQWw3q6xhu5Hcvb2y2L2/4+kwud37kICvRrbdcZ4OTIKUiWwAgkBdH+OZHBt
|
||||||
|
D2Yx2xKVPqDGJgMa5Ta8GmQZOFnoC2SpB6i9hIOfwiNjNLs+bv+kTxZ09nzf
|
||||||
|
3ZUGYi5Ei70hLrDAy7UCCNQNObtPmUYaUTtRzj3S9jUohbIpQxcfyoCMh6aP
|
||||||
|
usLw5q4tc+I5gdq57aiulJ8r4Jj9rdzsZFA7PzNJ9WPGVYJ3
|
||||||
|
=GSXO
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----`;
|
||||||
|
|
||||||
function versionSpecificTests() {
|
function versionSpecificTests() {
|
||||||
it('Preferences of generated key', function() {
|
it('Preferences of generated key', function() {
|
||||||
const testPref = function(key) {
|
const testPref = function(key) {
|
||||||
|
@ -2741,6 +2766,14 @@ module.exports = () => describe('Key', function() {
|
||||||
await expect(pubKey.getEncryptionKey()).to.be.rejectedWith('Could not find valid encryption key packet in key c076e634d32b498d');
|
await expect(pubKey.getEncryptionKey()).to.be.rejectedWith('Could not find valid encryption key packet in key c076e634d32b498d');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should pad an ECDSA P-521 key with shorter secret key', async function() {
|
||||||
|
const key = await openpgp.readArmoredKey(shortP521Key);
|
||||||
|
// secret key should be padded
|
||||||
|
expect(key.keyPacket.privateParams.d.length === 66);
|
||||||
|
// sanity check
|
||||||
|
await expect(key.validate()).to.be.fulfilled;
|
||||||
|
});
|
||||||
|
|
||||||
it('should not decrypt using a sign-only RSA key, unless explicitly configured', async function () {
|
it('should not decrypt using a sign-only RSA key, unless explicitly configured', async function () {
|
||||||
const allowSigningKeyDecryption = openpgp.config.allowInsecureDecryptionWithSigningKeys;
|
const allowSigningKeyDecryption = openpgp.config.allowInsecureDecryptionWithSigningKeys;
|
||||||
const key = await openpgp.readArmoredKey(rsaSignOnly);
|
const key = await openpgp.readArmoredKey(rsaSignOnly);
|
||||||
|
|
|
@ -2457,7 +2457,19 @@ amnR6g==
|
||||||
});
|
});
|
||||||
expect(util.decodeUtf8(decrypted.data)).to.equal('"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nUID:123\r\nDTSTART:20191211T121212Z\r\nDTEND:20191212T121212Z\r\nEND:VEVENT\r\nEND:VCALENDAR"');
|
expect(util.decodeUtf8(decrypted.data)).to.equal('"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nUID:123\r\nDTSTART:20191211T121212Z\r\nDTEND:20191212T121212Z\r\nEND:VEVENT\r\nEND:VCALENDAR"');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Sign and verify with each curve', function() {
|
||||||
|
const curves = ['secp256k1' , 'p256', 'p384', 'p521', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
|
||||||
|
curves.forEach(curve => {
|
||||||
|
it(`sign/verify with ${curve}`, async function() {
|
||||||
|
const plaintext = 'short message';
|
||||||
|
const key = (await openpgp.generateKey({ curve, userIds: 'Alice <info@alice.com>' })).key;
|
||||||
|
const signed = await openpgp.sign({ privateKeys:[key], message: openpgp.CleartextMessage.fromText(plaintext) });
|
||||||
|
const verified = await openpgp.verify({ publicKeys:[key], message: await openpgp.readArmoredCleartextMessage(signed) });
|
||||||
|
expect(verified.signatures[0].valid).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Errors', function() {
|
describe('Errors', function() {
|
||||||
|
@ -2474,7 +2486,6 @@ amnR6g==
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1698,4 +1698,135 @@ iTuGu4fEU1UligAXSrZmCdE=
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should verify a shorter RSA signature', async function () {
|
||||||
|
const encrypted = `-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
wYwD4IT3RGwgLJcBBACmH+a2c2yieZJ3wFchKeTVqzWkoltiidWgHHNE5v5x
|
||||||
|
8aZGNzZFBd02v80VS23P9oxeJOpqKX2IZyuD36SniNoi+eXdT3zraqIe9x5p
|
||||||
|
0RY9OrTP9pl58iogFBi1ARls41j7ui8KKDt2/iyQDCWHW1LoOVstiEb5/Xi3
|
||||||
|
EWI+34EbNNTBMgEJAQAwEXImkOPmhYhE7bB3FnXe9rb7Fo3GZYA4/8B9YVf7
|
||||||
|
GGZRLGwbICGu8E0MolmzLYW9hRThEfusAsNPGSgB+Yaqp0drsk01N4JJj3FT
|
||||||
|
RKEUvd5EcL3u+Z5EoUUW6GpUL5p8Hvy2thqQfeem7XUbDBY6V3wqydOjbN9u
|
||||||
|
c4CWB5Zu3GjDGDOvXFsy6cgdQvd/B9xbugKvUbAIsecTPlLtjZwfQklIu63T
|
||||||
|
DA/3Pz/+zTAknBCsuIM0m7U/ZP3N6AGQIp4To7RJk0I6AxthHF5LbU11MjDZ
|
||||||
|
iB7+vmhqlrPyIS11g25UNijottJm13f84glVwBdWTJCiEqjh3KbcnTQCckCY
|
||||||
|
V39DDLtbZG/XIx1ktqp765O9D/9xp2IA4zTyZzH4TuDbYs1j+JRdMsAq254k
|
||||||
|
1m+wtW5gxJGcD5nh2T2T+ABL0n3jW0G504kR0LNBAQOZhVSKnSLn+F0GkjmI
|
||||||
|
iGw8+BOy8p2pX/WCLOf776ppSL77TpzhpG6wSE2oQxDrudazmRgVkZpyGzFE
|
||||||
|
fDjspLTJHOhZ5zlLuoiKS9qEARGp39ysQnElR4dsx7tyVZz0uJvIrVzrQBlB
|
||||||
|
ekoD0DH0bhfqiwDrqeTJT2ORk8I/Q3jWnhQ3MnRN+q9d0yf1LWApMCwA7xU2
|
||||||
|
C4KUFRC/wuF2TR9NvA==
|
||||||
|
=v3WS
|
||||||
|
-----END PGP MESSAGE-----`;
|
||||||
|
const armoredKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
oaBUyhCKt8tz6Q==
|
||||||
|
=52k1
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----`;
|
||||||
|
const key = await openpgp.readArmoredKey(armoredKey);
|
||||||
|
const decrypted = await openpgp.decrypt({
|
||||||
|
message: await openpgp.readArmoredMessage(encrypted),
|
||||||
|
publicKeys: key,
|
||||||
|
privateKeys: key
|
||||||
|
});
|
||||||
|
expect(decrypted.signatures[0].valid).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should verify a shorter EdDSA signature', async function() {
|
||||||
|
const key = await openpgp.readArmoredKey(`-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
xVgEX8+jfBYJKwYBBAHaRw8BAQdA9GbdDjprR0sWf0R5a5IpulUauc0FsmzJ
|
||||||
|
mOYCfoowt8EAAP9UwaqC0LWWQ5RlX7mps3728vFa/If1KBVwAjk7Uqhi2BKL
|
||||||
|
zQ90ZXN0MiA8YkBhLmNvbT7CjAQQFgoAHQUCX8+jfAQLCQcIAxUICgQWAgEA
|
||||||
|
AhkBAhsDAh4BACEJEG464aV2od77FiEEIcg441MtKnyJnPDRbjrhpXah3vuR
|
||||||
|
gQD+Il6Gw2oIok4/ANyDDLBYZtKqRrMv4NcfF9DHYuAFcP4BAPhFOffyP3qU
|
||||||
|
AEZb7QPrWdLfhn8/FeSFZxJvnmupQ9sDx10EX8+jfBIKKwYBBAGXVQEFAQEH
|
||||||
|
QOSzo9cX1U2esGFClprOt0QWXNJ97228R5tKFxo6/0NoAwEIBwAA/0n4sq2i
|
||||||
|
N6/jE+6rVO4o/7LW0xahxpV1tTA6qv1Op9TwFIDCeAQYFggACQUCX8+jfAIb
|
||||||
|
DAAhCRBuOuGldqHe+xYhBCHIOONTLSp8iZzw0W464aV2od773XcA/jlmX8/c
|
||||||
|
1/zIotEkyMZB4mI+GAg3FQ6bIACFBH1sz0MzAP9Snri0P4FRZ8D5THRCJoUm
|
||||||
|
GBgpBmrf6IVv484jBswGDA==
|
||||||
|
=8rBO
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----`);
|
||||||
|
const encrypted = `-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
wV4DWlRRjuYiLSsSAQdAWwDKQLN4ZUS5fqiwFtAMrRfZZe9J4SgClhG6avEe
|
||||||
|
AEowkSZwWRT+8Hy8aBIb4oPehYUFXXZ7BtlJCyd7LOTUtqyc00OE0721PC3M
|
||||||
|
v0+zird60sACATlDmTwweR5GFtEAjHVheIL5rbkOBRD+oSqB8z+IovNg83Pz
|
||||||
|
FVwsFZnCLtECoYgpF2MJdopuC/bPHcrvf4ndwmD11uXtms4Rq4y25QyqApbn
|
||||||
|
Hj/hljufk0OkavUXxrNKjGQtxLHMpa3Nsi0MHWY8JguxOKFKpAIMP32CD1e+
|
||||||
|
j+GItrR+QbbN13ODlcR3hf66cwjLLsJCx5VcBaRspKF05O3ix/u9KVjJqtbi
|
||||||
|
Ie6jnY0zP2ldtS4JmhKBa43qmOHCxHc=
|
||||||
|
=7B58
|
||||||
|
-----END PGP MESSAGE-----`;
|
||||||
|
const decrypted = await openpgp.decrypt({ message: await openpgp.readArmoredMessage(encrypted), privateKeys: key, publicKeys: key.toPublic() });
|
||||||
|
expect(decrypted.signatures[0].valid).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should verify a shorter ECDSA signature', async function() {
|
||||||
|
const key = await openpgp.readArmoredKey(`-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
xYAFX9JrLRMAAABMCCqGSM49AwEHAgMErtQdX4vh7ng/ut+k1mooYNh3Ywqt
|
||||||
|
wr0tSS8hxZMvQRIFQ53Weq0e97ioZKXGimprEL571yvAN7I19wtQtqi61AAA
|
||||||
|
AAAAJAEAjWdW+qlMFaKwXCls3O/X8I1rbZ0OdFgeE3TnRP3YETAP5s0KYSA8
|
||||||
|
YUBhLml0PsKSBRATCAAhBQJf0mstBAsJBwgDFQgKBBYCAQACGQECGwMCHgcD
|
||||||
|
IgECACMiIQUee6Tb+GlhTk/ozKrt7RhInCyR6w3OJb/tYAN1+qbIoYUqAP9S
|
||||||
|
XmJCmSMrq6KfAD1aWSTBhtmujh+6y/pYTaf6VJVBYQEAt18zK0tw5EihHASY
|
||||||
|
FXbfdFHBzrMmPJ4UV6UiBvH6k2zHhAVf0mstEgAAAFAIKoZIzj0DAQcCAwQx
|
||||||
|
qnVPmWex365Nx8X8BGuMNI2TITXzTh9+AuPftZjPm09dhxdT9xmrCstPu/U1
|
||||||
|
cpacIp0LIq13ngLgeZWcGFcnAwEIBwAAAAAAJAEAsTvBsKk/XoCz2mi8sz5q
|
||||||
|
EYaN9YdDOU2jF+HOaSNaJAsPF8J6BRgTCAAJBQJf0mstAhsMACMiIQUee6Tb
|
||||||
|
+GlhTk/ozKrt7RhInCyR6w3OJb/tYAN1+qbIoVutAP9GHPLn7D9Uahm81lhK
|
||||||
|
AcvDfr9a0Cp4WAVzKDKLUzrRMgEAozi0VyjiBo1U2LcwTPJkA4PEQqQRVW1D
|
||||||
|
KZTMSAH7JEo=
|
||||||
|
=tqWy
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----`);
|
||||||
|
const signed = `-----BEGIN PGP SIGNED MESSAGE-----
|
||||||
|
Hash: SHA256
|
||||||
|
|
||||||
|
short message
|
||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
wnYFARMIAAYFAl/Say0AIyIhBR57pNv4aWFOT+jMqu3tGEicLJHrDc4lv+1g
|
||||||
|
A3X6psihFkcA+Nuog2qpAq20Zc2lzVjDZzQosb8MLvKMg3UFCX12Oc0BAJwd
|
||||||
|
JImeZLY02MctIpGZULbqgcUGK0P/yqrPL8Pe4lQM
|
||||||
|
=Pacb
|
||||||
|
-----END PGP SIGNATURE-----`;
|
||||||
|
const message = await openpgp.readArmoredCleartextMessage(signed);
|
||||||
|
const verified = await openpgp.verify({ publicKeys: key, message });
|
||||||
|
expect(verified.signatures[0].valid).to.be.true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -82,6 +82,31 @@ module.exports = () => describe('Util unit tests', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('leftPad', function() {
|
||||||
|
it('should not change the input if the length is correct', function() {
|
||||||
|
const bytes = new Uint8Array([2, 1]);
|
||||||
|
const padded = util.leftPad(bytes, 2);
|
||||||
|
expect(padded).to.deep.equal(bytes);
|
||||||
|
});
|
||||||
|
it('should add leading zeros to input array', function() {
|
||||||
|
const bytes = new Uint8Array([1, 2]);
|
||||||
|
const padded = util.leftPad(bytes, 5);
|
||||||
|
expect(padded).to.deep.equal(new Uint8Array([0, 0, 0, 1, 2]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('uint8ArrayToMpi', function() {
|
||||||
|
it('should strip leading zeros', function() {
|
||||||
|
const bytes = new Uint8Array([0, 0, 1, 2]);
|
||||||
|
const mpi = util.uint8ArrayToMpi(bytes);
|
||||||
|
expect(mpi).to.deep.equal(new Uint8Array([0, 9, 1, 2]));
|
||||||
|
});
|
||||||
|
it('should throw on array of all zeros', function() {
|
||||||
|
const bytes = new Uint8Array([0, 0]);
|
||||||
|
expect(() => util.uint8ArrayToMpi(bytes)).to.throw('Zero MPI');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('isEmailAddress', function() {
|
describe('isEmailAddress', function() {
|
||||||
it('should return true for valid email address', function() {
|
it('should return true for valid email address', function() {
|
||||||
const data = 'test@example.com';
|
const data = 'test@example.com';
|
||||||
|
|
|
@ -372,33 +372,6 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should handle little-endian parameters in EdDSA', async function () {
|
|
||||||
const pubKey = [
|
|
||||||
'-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
|
||||||
'Version: OpenPGP.js v3.0.0',
|
|
||||||
'Comment: https://openpgpjs.org',
|
|
||||||
'',
|
|
||||||
'xjMEWnRgnxYJKwYBBAHaRw8BAQdAZ8gxxCdUxIv4tBwhfUMW2uoEb1KvOfP8',
|
|
||||||
'D+0ObBtsLnfNDkhpIDxoaUBoZWwubG8+wnYEEBYKACkFAlp0YJ8GCwkHCAMC',
|
|
||||||
'CRDAYsFlymHCFQQVCAoCAxYCAQIZAQIbAwIeAQAAswsA/3qNZnwBn/ef4twv',
|
|
||||||
'uvmFicYK//DDX1jIkpDiQ+/okLUEAPdAr3J/Z2WA7OD0d36trHNB06WLXJUu',
|
|
||||||
'aCVm1TwoJHcNzjgEWnRgnxIKKwYBBAGXVQEFAQEHQPBVH+skap0NHMBw2HMe',
|
|
||||||
'xWYUQ67I9Did3KoJuuEJ/ctQAwEIB8JhBBgWCAATBQJadGCfCRDAYsFlymHC',
|
|
||||||
'FQIbDAAAhNQBAKmy4gPorjbwTwy5usylHttP28XnTdaGkZ1E7Rc3G9luAQCs',
|
|
||||||
'Gbm1oe83ZB+0aSp5m34YkpHQNb80y8PGFy7nIexiAA==',
|
|
||||||
'=xeG/',
|
|
||||||
'-----END PGP PUBLIC KEY BLOCK-----'
|
|
||||||
].join('\n');
|
|
||||||
const hi = await openpgp.readArmoredKey(pubKey);
|
|
||||||
const results = await hi.getPrimaryUser();
|
|
||||||
expect(results).to.exist;
|
|
||||||
expect(results.user).to.exist;
|
|
||||||
const user = results.user;
|
|
||||||
await user.verifyCertificate(
|
|
||||||
hi.primaryKey, user.selfCertifications[0], [hi]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('X25519 Omnibus Tests', omnibus);
|
describe('X25519 Omnibus Tests', omnibus);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ const chai = require('chai');
|
||||||
|
|
||||||
const { expect } = chai;
|
const { expect } = chai;
|
||||||
|
|
||||||
|
/* eslint-disable no-invalid-this */
|
||||||
module.exports = () => tryTests('Application Worker', tests, {
|
module.exports = () => tryTests('Application Worker', tests, {
|
||||||
if: typeof window !== 'undefined' && window.Worker && window.MessageChannel
|
if: typeof window !== 'undefined' && window.Worker && window.MessageChannel
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user