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:
larabr 2020-12-16 17:38:21 +01:00 committed by Daniel Huigens
parent c34dede6de
commit 286d991265
11 changed files with 258 additions and 67 deletions

View File

@ -149,7 +149,7 @@ export function parsePublicKeyParams(algo, bytes) {
case enums.publicKey.eddsa: {
const oid = new OID(); read += oid.read(bytes);
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 } };
}
case enums.publicKey.ecdh: {
@ -191,12 +191,12 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) {
case enums.publicKey.ecdh: {
const curve = new Curve(publicParams.oid);
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 } };
}
case enums.publicKey.eddsa: {
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 } };
}
default:

View File

@ -207,7 +207,6 @@ async function webSign(curve, hash_algo, message, keyPair) {
}
async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
const len = curve.payloadSize;
const jwk = rawPublicToJwk(curve.payloadSize, webCurves[curve.name], publicKey);
const key = await webCrypto.importKey(
"jwk",
@ -221,10 +220,7 @@ async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
["verify"]
);
const signature = util.concatUint8Array([
new Uint8Array(len - r.length), r,
new Uint8Array(len - s.length), s
]).buffer;
const signature = util.concatUint8Array([r, s]).buffer;
return webCrypto.verify(
{

View File

@ -10,9 +10,10 @@ import publicKey from './public_key';
import enums from '../enums';
import util from '../util';
/**
* 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-5.2.2|RFC 4880 5.2.2.}
* @param {module:enums.publicKey} algo Public key algorithm
@ -29,6 +30,8 @@ export function parseSignatureParams(algo, signature) {
case enums.publicKey.rsaEncrypt:
case enums.publicKey.rsaSign: {
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 };
}
// Algorithm-Specific Fields for DSA or ECDSA signatures:
@ -43,12 +46,14 @@ export function parseSignatureParams(algo, signature) {
}
// Algorithm-Specific Fields for EdDSA signatures:
// - MPI of an EC point r.
// - 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
// - EdDSA value s, in MPI, in the little endian representation
case enums.publicKey.eddsa: {
const r = util.padToLength(util.readMPI(signature.subarray(read)), 32, 'le'); read += r.length + 2;
const s = util.padToLength(util.readMPI(signature.subarray(read)), 32, 'le');
// When parsing little-endian MPI data, we always need to left-pad it, as done with big-endian values:
// 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 };
}
default:
@ -76,20 +81,25 @@ export async function verify(algo, hashAlgo, signature, publicParams, data, hash
case enums.publicKey.rsaEncrypt:
case enums.publicKey.rsaSign: {
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);
}
case enums.publicKey.dsa: {
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);
}
case enums.publicKey.ecdsa: {
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: {
const { oid, Q } = publicParams;
// signature already padded on parsing
return publicKey.elliptic.eddsa.verify(oid, hashAlgo, signature, data, Q, hashed);
}
default:

View File

@ -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 {Number} length padded length
* @param {'be'|'le'} endianess endianess of input data
* @return {Uint8Array} padded bytes
*/
padToLength(bytes, length, endianess = 'be') {
leftPad(bytes, length) {
const padded = new Uint8Array(length);
const offset = (endianess === 'be') ? 0 : (length - bytes.length);
const offset = length - bytes.length;
padded.set(bytes, offset);
return padded;
},
@ -197,9 +196,15 @@ export default {
* @returns {Uint8Array} MPI-formatted Uint8Array
*/
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]);
return util.concatUint8Array([prefix, bin]);
return util.concatUint8Array([prefix, stripped]);
},
/**

View File

@ -80,6 +80,7 @@ function cloneKeyPacket(key) {
return keyPacket;
}
/* eslint-disable no-invalid-this */
module.exports = () => {
describe('EdDSA parameter validation', function() {
let eddsaKey;
@ -201,28 +202,32 @@ module.exports = () => {
}
});
if (ecdsaKey) {
it(`EcDSA ${curve} params should be valid`, async function() {
await expect(ecdsaKey.keyPacket.validate()).to.not.be.rejected;
});
it(`EcDSA ${curve} params should be valid`, async function() {
if (!ecdsaKey) {
this.skip();
}
await expect(ecdsaKey.keyPacket.validate()).to.not.be.rejected;
});
it('detect invalid EcDSA Q', async function() {
const keyPacket = cloneKeyPacket(ecdsaKey);
const Q = keyPacket.publicParams.Q;
Q[0]++;
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
const infQ = new Uint8Array(Q.length);
keyPacket.publicParams.Q = infQ;
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
});
}
it(`ECDSA ${curve} - detect invalid Q`, async function() {
if (!ecdsaKey) {
this.skip();
}
const keyPacket = cloneKeyPacket(ecdsaKey);
const Q = keyPacket.publicParams.Q;
Q[16]++;
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() {
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 Q = keyPacket.publicParams.Q;
Q[16]++;
@ -230,6 +235,7 @@ module.exports = () => {
const infQ = new Uint8Array(Q.length);
keyPacket.publicParams.Q = infQ;
infQ[0] = 4;
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
});
});

View File

@ -2020,6 +2020,31 @@ EBeLgD8oZHVsH3NLjPakPw==
=STqy
-----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() {
it('Preferences of generated key', function() {
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');
});
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 () {
const allowSigningKeyDecryption = openpgp.config.allowInsecureDecryptionWithSigningKeys;
const key = await openpgp.readArmoredKey(rsaSignOnly);

View File

@ -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"');
});
});
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() {
@ -2474,7 +2486,6 @@ amnR6g==
});
});
});
});

View File

@ -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;
});
});

View File

@ -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() {
it('should return true for valid email address', function() {
const data = 'test@example.com';

View File

@ -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);
});

View File

@ -4,6 +4,7 @@ const chai = require('chai');
const { expect } = chai;
/* eslint-disable no-invalid-this */
module.exports = () => tryTests('Application Worker', tests, {
if: typeof window !== 'undefined' && window.Worker && window.MessageChannel
});