Passing all tests, on Node, Firefox, and Chrome

This commit is contained in:
Mahrud Sayrafi 2018-01-04 01:40:08 -08:00 committed by Sanjana Rajan
parent dcff16d32b
commit e6820d7b2a
11 changed files with 233 additions and 232 deletions

View File

@ -95,8 +95,10 @@ CleartextMessage.prototype.signDetached = async function(privateKeys) {
throw new Error('Private key is not decrypted.'); throw new Error('Private key is not decrypted.');
} }
await signaturePacket.sign(signingKeyPacket, literalDataPacket); await signaturePacket.sign(signingKeyPacket, literalDataPacket);
packetlist.push(signaturePacket); return signaturePacket;
})); })).then(signatureList => {
signatureList.forEach(signaturePacket => packetlist.push(signaturePacket));
});
return new sigModule.Signature(packetlist); return new sigModule.Signature(packetlist);
}; };
@ -116,16 +118,14 @@ CleartextMessage.prototype.verify = function(keys) {
* @return {Array<{keyid: module:type/keyid, valid: Boolean}>} list of signer's keyid and validity of signature * @return {Array<{keyid: module:type/keyid, valid: Boolean}>} list of signer's keyid and validity of signature
*/ */
CleartextMessage.prototype.verifyDetached = function(signature, keys) { CleartextMessage.prototype.verifyDetached = function(signature, keys) {
var result = [];
var signatureList = signature.packets; var signatureList = signature.packets;
var literalDataPacket = new packet.Literal(); var literalDataPacket = new packet.Literal();
// we assume that cleartext signature is generated based on UTF8 cleartext // we assume that cleartext signature is generated based on UTF8 cleartext
literalDataPacket.setText(this.text); literalDataPacket.setText(this.text);
// TODO await Promise.all(signatureList.map(async function(signature) { })); return Promise.all(signatureList.map(async function(signature) {
for (var i = 0; i < signatureList.length; i++) {
var keyPacket = null; var keyPacket = null;
for (var j = 0; j < keys.length; j++) { for (var j = 0; j < keys.length; j++) {
keyPacket = keys[j].getSigningKeyPacket(signatureList[i].issuerKeyId); keyPacket = keys[j].getSigningKeyPacket(signature.issuerKeyId);
if (keyPacket) { if (keyPacket) {
break; break;
} }
@ -133,20 +133,19 @@ CleartextMessage.prototype.verifyDetached = function(signature, keys) {
var verifiedSig = {}; var verifiedSig = {};
if (keyPacket) { if (keyPacket) {
verifiedSig.keyid = signatureList[i].issuerKeyId; verifiedSig.keyid = signature.issuerKeyId;
verifiedSig.valid = signatureList[i].verify(keyPacket, literalDataPacket); verifiedSig.valid = await signature.verify(keyPacket, literalDataPacket);
} else { } else {
verifiedSig.keyid = signatureList[i].issuerKeyId; verifiedSig.keyid = signature.issuerKeyId;
verifiedSig.valid = null; verifiedSig.valid = null;
} }
var packetlist = new packet.List(); var packetlist = new packet.List();
packetlist.push(signatureList[i]); packetlist.push(signature);
verifiedSig.signature = new sigModule.Signature(packetlist); verifiedSig.signature = new sigModule.Signature(packetlist);
result.push(verifiedSig); return verifiedSig;
} }));
return result;
}; };
/** /**

View File

@ -27,8 +27,6 @@
'use strict'; 'use strict';
import ASN1 from 'asn1.js';
import {ec as EC} from 'elliptic'; import {ec as EC} from 'elliptic';
import {KeyPair} from './key.js'; import {KeyPair} from './key.js';
import BigInteger from '../jsbn.js'; import BigInteger from '../jsbn.js';
@ -40,14 +38,6 @@ import base64 from '../../../encoding/base64.js';
const webCrypto = util.getWebCrypto(); const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto(); const nodeCrypto = util.getNodeCrypto();
var ECPrivateKey = ASN1.define('ECPrivateKey', function() {
this.seq().obj(
this.key('r').int(), // FIXME int or BN?
this.key('s').int() // FIXME int or BN?
);
});
var webCurves = [], nodeCurves = []; var webCurves = [], nodeCurves = [];
if (webCrypto && config.use_native) { if (webCrypto && config.use_native) {
// see https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API/Supported_algorithms // see https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API/Supported_algorithms
@ -60,6 +50,7 @@ if (webCrypto && config.use_native) {
const curves = { const curves = {
p256: { p256: {
oid: util.bin2str([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]), oid: util.bin2str([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]),
pointSize: 66, // FIXME
namedCurve: 'P-256', namedCurve: 'P-256',
opensslCurve: 'prime256v1', opensslCurve: 'prime256v1',
hashName: 'SHA-256', hashName: 'SHA-256',
@ -70,6 +61,7 @@ const curves = {
}, },
p384: { p384: {
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x22]), oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x22]),
pointSize: 48,
namedCurve: 'P-384', namedCurve: 'P-384',
opensslCurve: 'secp384r1', // FIXME opensslCurve: 'secp384r1', // FIXME
hashName: 'SHA-384', hashName: 'SHA-384',
@ -80,6 +72,7 @@ const curves = {
}, },
p521: { p521: {
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x23]), oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x23]),
pointSize: 66,
namedCurve: 'P-521', namedCurve: 'P-521',
opensslCurve: 'secp521r1', // FIXME opensslCurve: 'secp521r1', // FIXME
hashName: 'SHA-512', hashName: 'SHA-512',
@ -90,6 +83,7 @@ const curves = {
}, },
secp256k1: { secp256k1: {
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x0A]), oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x0A]),
pointSize: 66, // FIXME
namedCurve: 'SECP-256K1', namedCurve: 'SECP-256K1',
opensslCurve: 'secp256k1', opensslCurve: 'secp256k1',
hashName: 'SHA-256', hashName: 'SHA-256',
@ -103,10 +97,11 @@ const curves = {
ed25519 : {} ed25519 : {}
}; };
function Curve(name, {oid, hash, cipher, namedCurve, opensslCurve, hashName, node, web}) { function Curve(name, {oid, pointSize, hash, cipher, namedCurve, opensslCurve, hashName, node, web}) {
this.curve = new EC(name); this.curve = new EC(name);
this.name = name; this.name = name;
this.oid = oid; this.oid = oid;
this.pointSize = pointSize;
this.hash = hash; this.hash = hash;
this.cipher = cipher; this.cipher = cipher;
this.namedCurve= namedCurve; this.namedCurve= namedCurve;
@ -177,41 +172,33 @@ module.exports = {
async function webGenKeyPair(namedCurve, algorithm) { async function webGenKeyPair(namedCurve, algorithm) {
try { var webCryptoKey = await webCrypto.generateKey(
var webCryptoKey = await webCrypto.generateKey( {
{ name: algorithm === "ECDH" ? "ECDH" : "ECDSA",
name: algorithm === "ECDH" ? "ECDH" : "ECDSA", namedCurve: namedCurve
namedCurve: namedCurve },
}, true,
true, algorithm === "ECDH" ? ["deriveKey", "deriveBits"] : ["sign", "verify"]
algorithm === "ECDH" ? ["deriveKey", "deriveBits"] : ["sign", "verify"] );
);
var privateKey = await webCrypto.exportKey("jwk", webCryptoKey.privateKey); var privateKey = await webCrypto.exportKey("jwk", webCryptoKey.privateKey);
var publicKey = await webCrypto.exportKey("jwk", webCryptoKey.publicKey); var publicKey = await webCrypto.exportKey("jwk", webCryptoKey.publicKey);
return { return {
pub: { pub: {
x: base64.decode(publicKey.x, 'base64url'), x: base64.decode(publicKey.x, 'base64url'),
y: base64.decode(publicKey.y, 'base64url') y: base64.decode(publicKey.y, 'base64url')
}, },
priv: base64.decode(privateKey.d, 'base64url') priv: base64.decode(privateKey.d, 'base64url')
}; };
} catch(err) {
throw new Error(err);
}
} }
async function nodeGenKeyPair(opensslCurve) { async function nodeGenKeyPair(opensslCurve) {
try { var ecdh = nodeCrypto.createECDH(opensslCurve);
var ecdh = nodeCrypto.createECDH(opensslCurve); await ecdh.generateKeys();
await ecdh.generateKeys();
return { return {
pub: ecdh.getPublicKey().toJSON().data, pub: ecdh.getPublicKey().toJSON().data,
priv: ecdh.getPrivateKey().toJSON().data priv: ecdh.getPrivateKey().toJSON().data
}; };
} catch(err) {
throw new Error(err);
}
} }

View File

@ -57,6 +57,7 @@ var ECDSASignature = ASN1.define('ECDSASignature', function() {
async function sign(oid, hash_algo, m, d) { async function sign(oid, hash_algo, m, d) {
var signature; var signature;
const curve = curves.get(oid); const curve = curves.get(oid);
hash_algo = hash_algo ? hash_algo : curve.hash;
const key = curve.keyFromPrivate(d.toByteArray()); const key = curve.keyFromPrivate(d.toByteArray());
if (webCrypto && config.use_native && curve.web) { if (webCrypto && config.use_native && curve.web) {
signature = await webSign(curve, hash_algo, m, key.keyPair); signature = await webSign(curve, hash_algo, m, key.keyPair);
@ -83,6 +84,7 @@ async function sign(oid, hash_algo, m, d) {
async function verify(oid, hash_algo, signature, m, Q) { async function verify(oid, hash_algo, signature, m, Q) {
var result; var result;
const curve = curves.get(oid); const curve = curves.get(oid);
hash_algo = hash_algo ? hash_algo : curve.hash; // FIXME is this according to the RFC?
const key = curve.keyFromPublic(Q.toByteArray()); const key = curve.keyFromPublic(Q.toByteArray());
if (webCrypto && config.use_native && curve.web) { if (webCrypto && config.use_native && curve.web) {
result = await webVerify(curve, hash_algo, signature, m, key.keyPair.getPublic()); result = await webVerify(curve, hash_algo, signature, m, key.keyPair.getPublic());
@ -109,76 +111,83 @@ module.exports = {
////////////////////////// //////////////////////////
async function webSign(curve, hash_algo, m, keyPair) { async function webSign(curve, hash_algo, message, keyPair) {
try { var l = curve.pointSize;
const key = await webCrypto.importKey( if (typeof message === 'string') {
"jwk", message = util.str2Uint8Array(message);
{
"kty": "EC",
"crv": curve.namedCurve,
"x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray()), null, 'base64url'),
"y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray()), null, 'base64url'),
"d": base64.encode(new Uint8Array(keyPair.getPrivate()), null, 'base64url'),
"use": "sig",
"kid": "ECDSA Private Key"
},
{
"name": "ECDSA",
"namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.hash, hash_algo) }
},
false,
["sign"]
);
return await webCrypto.sign(
{
"name": 'ECDSA',
"namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.hash, hash_algo) }
},
key,
m
);
} catch(err) {
throw new Error(err);
} }
const key = await webCrypto.importKey(
"jwk",
{
"kty": "EC",
"crv": curve.namedCurve,
"x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray('be', l)), null, 'base64url'),
"y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray('be', l)), null, 'base64url'),
"d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray('be', l)), null, 'base64url'),
"use": "sig",
"kid": "ECDSA Private Key"
},
{
"name": "ECDSA",
"namedCurve": curve.namedCurve,
"hash": { name: curve.hashName }
},
false,
["sign"]
);
const signature = new Uint8Array(await webCrypto.sign(
{
"name": 'ECDSA',
"namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.webHash, hash_algo) }
},
key,
message
));
return {
r: signature.slice(0, l),
s: signature.slice(l, 2 * l)
};
} }
async function webVerify(curve, hash_algo, signature, m, publicKey) { async function webVerify(curve, hash_algo, signature, message, publicKey) {
try { var r = signature.r.toByteArray(), s = signature.s.toByteArray(), l = curve.pointSize;
const key = await webCrypto.importKey( r = (r.length === l) ? r : [0].concat(r);
"jwk", s = (s.length === l) ? s : [0].concat(s);
{ signature = new Uint8Array(r.concat(s)).buffer;
"kty": "EC", if (typeof message === 'string') {
"crv": curve.namedCurve, message = util.str2Uint8Array(message);
"x": base64.encode(new Uint8Array(publicKey.getX().toArray()), null, 'base64url'),
"y": base64.encode(new Uint8Array(publicKey.getY().toArray()), null, 'base64url'),
"use": "sig",
"kid": "ECDSA Public Key"
},
{
"name": "ECDSA",
"namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.hash, hash_algo) }
},
false,
["verify"]
);
return await webCrypto.verify(
{
"name": 'ECDSA',
"namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.hash, hash_algo) }
},
key,
signature,
m
);
} catch(err) {
throw new Error(err);
} }
const key = await webCrypto.importKey(
"jwk",
{
"kty": "EC",
"crv": curve.namedCurve,
"x": base64.encode(new Uint8Array(publicKey.getX().toArray('be', l)), null, 'base64url'),
"y": base64.encode(new Uint8Array(publicKey.getY().toArray('be', l)), null, 'base64url'),
"use": "sig",
"kid": "ECDSA Public Key"
},
{
"name": "ECDSA",
"namedCurve": curve.namedCurve,
"hash": { name: curve.hashName }
},
false,
["verify"]
);
return webCrypto.verify(
{
"name": 'ECDSA',
"namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.webHash, hash_algo) }
},
key,
signature,
message
);
} }
@ -199,7 +208,6 @@ async function nodeSign(curve, hash_algo, message, keyPair) {
{ private: true } { private: true }
); );
// FIXME what happens when hash_algo = undefined?
const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo)); const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
sign.write(message); sign.write(message);
sign.end(); sign.end();

View File

@ -75,6 +75,17 @@ export default {
sha224: 11 sha224: 11
}, },
/** A list of hash names as accepted by webCrypto functions.
* {@link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest|Parameters, algo}
* @enum {String}
*/
webHash: {
'SHA-1': 2,
'SHA-256': 8,
'SHA-384': 9,
'SHA-512': 10
},
/** A list of packet types and numeric tags associated with them. /** A list of packet types and numeric tags associated with them.
* @enum {Integer} * @enum {Integer}
* @readonly * @readonly

View File

@ -124,23 +124,21 @@ Message.prototype.decrypt = async function(privateKey, sessionKey, password) {
* { data:Uint8Array, algorithm:String } * { data:Uint8Array, algorithm:String }
*/ */
Message.prototype.decryptSessionKey = function(privateKey, password) { Message.prototype.decryptSessionKey = function(privateKey, password) {
var keyPacket, results, error; var keyPacket;
return Promise.resolve().then(async () => { return Promise.resolve().then(async () => {
if (password) { if (password) {
var symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey); var symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey);
// FIXME need a circuit breaker here
if (!symESKeyPacketlist) { if (!symESKeyPacketlist) {
throw new Error('No symmetrically encrypted session key packet found.'); throw new Error('No symmetrically encrypted session key packet found.');
} }
results = await Promise.all(symESKeyPacketlist.map(async function(packet) { // TODO replace when Promise.some or Promise.any are implemented
await symESKeyPacketlist.some(async function(packet) {
try { try {
await packet.decrypt(password); await packet.decrypt(password);
return packet; keyPacket = packet;
} catch (err) { return true;
error = err; } catch (err) {}
} });
}));
keyPacket = results.find(result => result !== undefined);
} else if (privateKey) { } else if (privateKey) {
var encryptionKeyIds = this.getEncryptionKeyIds(); var encryptionKeyIds = this.getEncryptionKeyIds();
@ -156,19 +154,17 @@ Message.prototype.decryptSessionKey = function(privateKey, password) {
if (!pkESKeyPacketlist) { if (!pkESKeyPacketlist) {
throw new Error('No public key encrypted session key packet found.'); throw new Error('No public key encrypted session key packet found.');
} }
// FIXME need a circuit breaker here // TODO replace when Promise.some or Promise.any are implemented
results = await Promise.all(pkESKeyPacketlist.map(async function(packet) { // eslint-disable-next-line no-await-in-loop
await pkESKeyPacketlist.some(async function(packet) {
if (packet.publicKeyId.equals(privateKeyPacket.getKeyId())) { if (packet.publicKeyId.equals(privateKeyPacket.getKeyId())) {
try { try {
await packet.decrypt(privateKeyPacket) await packet.decrypt(privateKeyPacket);
return packet; keyPacket = packet;
} catch (err) { return true;
error = err; } catch (err) {}
}
} }
})); });
keyPacket = results.find(result => result !== undefined);
} else { } else {
throw new Error('No key or password specified.'); throw new Error('No key or password specified.');
} }
@ -225,14 +221,6 @@ Message.prototype.getText = function() {
Message.prototype.encrypt = function(keys, passwords, sessionKey) { Message.prototype.encrypt = function(keys, passwords, sessionKey) {
let symAlgo, msg, symEncryptedPacket; let symAlgo, msg, symEncryptedPacket;
return Promise.resolve().then(async () => { return Promise.resolve().then(async () => {
if (keys) {
symAlgo = enums.read(enums.symmetric, keyModule.getPreferredSymAlgo(keys));
} else if (passwords) {
symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
} else {
throw new Error('No keys or passwords');
}
if (sessionKey) { if (sessionKey) {
if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) {
throw new Error('Invalid session key for encryption.'); throw new Error('Invalid session key for encryption.');
@ -291,6 +279,7 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) {
return Promise.resolve().then(async () => { return Promise.resolve().then(async () => {
if (publicKeys) { if (publicKeys) {
results = await Promise.all(publicKeys.map(async function(key) { results = await Promise.all(publicKeys.map(async function(key) {
await key.verifyPrimaryUser();
var encryptionKeyPacket = key.getEncryptionKeyPacket(); var encryptionKeyPacket = key.getEncryptionKeyPacket();
if (!encryptionKeyPacket) { if (!encryptionKeyPacket) {
throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex());
@ -338,62 +327,67 @@ Message.prototype.sign = async function(privateKeys=[], signature=null) {
throw new Error('No literal data packet to sign.'); throw new Error('No literal data packet to sign.');
} }
var i;
var literalFormat = enums.write(enums.literal, literalDataPacket.format); var literalFormat = enums.write(enums.literal, literalDataPacket.format);
var signatureType = literalFormat === enums.literal.binary ? var signatureType = literalFormat === enums.literal.binary ?
enums.signature.binary : enums.signature.text; enums.signature.binary : enums.signature.text;
var i, signingKeyPacket, existingSigPacketlist, onePassSig;
if (signature) { if (signature) {
existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature); var existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature);
if (existingSigPacketlist.length) { for (i = existingSigPacketlist.length - 1; i >= 0; i--) {
for (i = existingSigPacketlist.length - 1; i >= 0; i--) { var signaturePacket = existingSigPacketlist[i];
var sigPacket = existingSigPacketlist[i]; var onePassSig = new packet.OnePassSignature();
onePassSig = new packet.OnePassSignature(); onePassSig.type = signatureType;
onePassSig.type = signatureType; onePassSig.hashAlgorithm = signaturePacket.hashAlgorithm;
onePassSig.hashAlgorithm = config.prefer_hash_algorithm; onePassSig.publicKeyAlgorithm = signaturePacket.publicKeyAlgorithm;
onePassSig.publicKeyAlgorithm = sigPacket.publicKeyAlgorithm; onePassSig.signingKeyId = signaturePacket.issuerKeyId;
onePassSig.signingKeyId = sigPacket.issuerKeyId; if (!privateKeys.length && i === 0) {
if (!privateKeys.length && i === 0) { onePassSig.flags = 1;
onePassSig.flags = 1;
}
packetlist.push(onePassSig);
} }
packetlist.push(onePassSig);
} }
} }
for (i = 0; i < privateKeys.length; i++) {
if (privateKeys[i].isPublic()) { await Promise.all(privateKeys.map(async function (privateKey, i) {
if (privateKey.isPublic()) {
throw new Error('Need private key for signing'); throw new Error('Need private key for signing');
} }
await privateKey.verifyPrimaryUser();
var signingKeyPacket = privateKey.getSigningKeyPacket();
if (!signingKeyPacket) {
throw new Error('Could not find valid key packet for signing in key ' +
privateKey.primaryKey.getKeyId().toHex());
}
onePassSig = new packet.OnePassSignature(); onePassSig = new packet.OnePassSignature();
onePassSig.type = signatureType; onePassSig.type = signatureType;
//TODO get preferred hash algo from key signature //TODO get preferred hash algo from key signature
onePassSig.hashAlgorithm = config.prefer_hash_algorithm; onePassSig.hashAlgorithm = keyModule.getPreferredHashAlgorithm(privateKey);
signingKeyPacket = privateKeys[i].getSigningKeyPacket();
if (!signingKeyPacket) {
throw new Error('Could not find valid key packet for signing in key ' + privateKeys[i].primaryKey.getKeyId().toHex());
}
onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm; onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm;
onePassSig.signingKeyId = signingKeyPacket.getKeyId(); onePassSig.signingKeyId = signingKeyPacket.getKeyId();
if (i === privateKeys.length - 1) { if (i === privateKeys.length - 1) {
onePassSig.flags = 1; onePassSig.flags = 1;
} }
packetlist.push(onePassSig); return onePassSig;
} })).then(onePassSignatureList => {
onePassSignatureList.forEach(onePassSig => packetlist.push(onePassSig));
});
packetlist.push(literalDataPacket); packetlist.push(literalDataPacket);
await Promise.all(privateKeys.reverse().map(async function(privateKey) { await Promise.all(privateKeys.reverse().map(async function(privateKey) {
var signingKeyPacket = privateKey.getSigningKeyPacket();
var signaturePacket = new packet.Signature(); var signaturePacket = new packet.Signature();
signaturePacket.signatureType = signatureType; var signingKeyPacket = privateKey.getSigningKeyPacket();
signaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
if (!signingKeyPacket.isDecrypted) { if (!signingKeyPacket.isDecrypted) {
throw new Error('Private key is not decrypted.'); throw new Error('Private key is not decrypted.');
} }
signaturePacket.signatureType = signatureType;
signaturePacket.hashAlgorithm = keyModule.getPreferredHashAlgorithm(privateKey);
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
await signaturePacket.sign(signingKeyPacket, literalDataPacket); await signaturePacket.sign(signingKeyPacket, literalDataPacket);
packetlist.push(signaturePacket); return signaturePacket;
})); })).then(signatureList => {
signatureList.forEach(signaturePacket => packetlist.push(signaturePacket));
});
if (signature) { if (signature) {
packetlist.concat(existingSigPacketlist); packetlist.concat(existingSigPacketlist);
@ -422,17 +416,20 @@ Message.prototype.signDetached = async function(privateKeys=[], signature=null)
enums.signature.binary : enums.signature.text; enums.signature.binary : enums.signature.text;
await Promise.all(privateKeys.map(async function(privateKey) { await Promise.all(privateKeys.map(async function(privateKey) {
var signingKeyPacket = privateKey.getSigningKeyPacket();
var signaturePacket = new packet.Signature(); var signaturePacket = new packet.Signature();
signaturePacket.signatureType = signatureType; await privateKey.verifyPrimaryUser();
signaturePacket.hashAlgorithm = config.prefer_hash_algorithm; var signingKeyPacket = privateKey.getSigningKeyPacket();
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
if (!signingKeyPacket.isDecrypted) { if (!signingKeyPacket.isDecrypted) {
throw new Error('Private key is not decrypted.'); throw new Error('Private key is not decrypted.');
} }
signaturePacket.signatureType = signatureType;
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
signaturePacket.hashAlgorithm = keyModule.getPreferredHashAlgorithm(privateKey);
await signaturePacket.sign(signingKeyPacket, literalDataPacket); await signaturePacket.sign(signingKeyPacket, literalDataPacket);
packetlist.push(signaturePacket); return signaturePacket;
})); })).then(signatureList => {
signatureList.forEach(signaturePacket => packetlist.push(signaturePacket));
});
if (signature) { if (signature) {
var existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature); var existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature);
@ -481,34 +478,33 @@ Message.prototype.verifyDetached = function(signature, keys) {
* @param {Array<module:key~Key>} keys array of keys to verify signatures * @param {Array<module:key~Key>} keys array of keys to verify signatures
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature
*/ */
function createVerificationObjects(signatureList, literalDataList, keys) { async function createVerificationObjects(signatureList, literalDataList, keys) {
var result = []; return Promise.all(signatureList.map(async function(signature) {
for (var i = 0; i < signatureList.length; i++) {
var keyPacket = null; var keyPacket = null;
for (var j = 0; j < keys.length; j++) { await Promise.all(keys.map(async function(key) {
keyPacket = keys[j].getSigningKeyPacket(signatureList[i].issuerKeyId, config.verify_expired_keys); await key.verifyPrimaryUser();
if (keyPacket) { var result = key.getSigningKeyPacket(signature.issuerKeyId, config.verify_expired_keys);
break; if (result) {
keyPacket = result;
} }
} }));
var verifiedSig = {}; var verifiedSig = {};
if (keyPacket) { if (keyPacket) {
//found a key packet that matches keyId of signature //found a key packet that matches keyId of signature
verifiedSig.keyid = signatureList[i].issuerKeyId; verifiedSig.keyid = signature.issuerKeyId;
verifiedSig.valid = signatureList[i].verify(keyPacket, literalDataList[0]); verifiedSig.valid = await signature.verify(keyPacket, literalDataList[0]);
} else { } else {
verifiedSig.keyid = signatureList[i].issuerKeyId; verifiedSig.keyid = signature.issuerKeyId;
verifiedSig.valid = null; verifiedSig.valid = null;
} }
var packetlist = new packet.List(); var packetlist = new packet.List();
packetlist.push(signatureList[i]); packetlist.push(signature);
verifiedSig.signature = new sigModule.Signature(packetlist); verifiedSig.signature = new sigModule.Signature(packetlist);
result.push(verifiedSig); return verifiedSig;
} }));
return Promise.all(result);
} }
/** /**

View File

@ -19,7 +19,7 @@
self.window = {}; // to make UMD bundles work self.window = {}; // to make UMD bundles work
importScripts('openpgp.js'); importScripts('openpgp_browser.js'); // FIXME
var openpgp = window.openpgp; var openpgp = window.openpgp;
var MIN_SIZE_RANDOM_BUFFER = 40000; var MIN_SIZE_RANDOM_BUFFER = 40000;

View File

@ -165,7 +165,7 @@ describe('Elliptic Curve Cryptography', function () {
expect(result).to.exist; expect(result).to.exist;
expect(result.data.trim()).to.equal(data.juliet.message); expect(result.data.trim()).to.equal(data.juliet.message);
expect(result.signatures).to.have.length(1); expect(result.signatures).to.have.length(1);
expect(result.signatures[0].valid).to.eventually.be.true; expect(result.signatures[0].valid).to.be.true;
}); });
}); });
// FIXME is this pattern correct? // FIXME is this pattern correct?
@ -178,7 +178,7 @@ describe('Elliptic Curve Cryptography', function () {
expect(result).to.exist; expect(result).to.exist;
expect(result.data.trim()).to.equal(data.romeo.message); expect(result.data.trim()).to.equal(data.romeo.message);
expect(result.signatures).to.have.length(1); expect(result.signatures).to.have.length(1);
expect(result.signatures[0].valid).to.eventually.be.true; expect(result.signatures[0].valid).to.be.true;
}); });
}); });
}); });
@ -193,7 +193,7 @@ describe('Elliptic Curve Cryptography', function () {
// trim required because https://github.com/openpgpjs/openpgpjs/issues/311 // trim required because https://github.com/openpgpjs/openpgpjs/issues/311
expect(result.data.trim()).to.equal(data.juliet.message); expect(result.data.trim()).to.equal(data.juliet.message);
expect(result.signatures).to.have.length(1); expect(result.signatures).to.have.length(1);
expect(result.signatures[0].valid).to.eventually.be.true; expect(result.signatures[0].valid).to.be.true;
}); });
}); });
it('Encrypt and sign message', function () { it('Encrypt and sign message', function () {
@ -212,7 +212,7 @@ describe('Elliptic Curve Cryptography', function () {
expect(result).to.exist; expect(result).to.exist;
expect(result.data.trim()).to.equal(data.romeo.message); expect(result.data.trim()).to.equal(data.romeo.message);
expect(result.signatures).to.have.length(1); expect(result.signatures).to.have.length(1);
expect(result.signatures[0].valid).to.eventually.be.true; expect(result.signatures[0].valid).to.be.true;
}); });
}); });
}); });

View File

@ -1172,7 +1172,7 @@ describe('Key', function() {
return openpgp.encrypt({data: 'hello', publicKeys: newKey.toPublic(), privateKeys: newKey, armor: true}).then(function(encrypted) { return openpgp.encrypt({data: 'hello', publicKeys: newKey.toPublic(), privateKeys: newKey, armor: true}).then(function(encrypted) {
return openpgp.decrypt({message: openpgp.message.readArmored(encrypted.data), privateKey: newKey, publicKeys: newKey.toPublic()}).then(function(decrypted) { return openpgp.decrypt({message: openpgp.message.readArmored(encrypted.data), privateKey: newKey, publicKeys: newKey.toPublic()}).then(function(decrypted) {
expect(decrypted.data).to.equal('hello'); expect(decrypted.data).to.equal('hello');
expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].valid).to.be.true;
}); });
}); });
}); });

View File

@ -712,7 +712,7 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.decrypt(decOpt); return openpgp.decrypt(decOpt);
}).then(function(decrypted) { }).then(function(decrypted) {
expect(decrypted.data).to.equal(plaintext); expect(decrypted.data).to.equal(plaintext);
expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].valid).to.be.true;
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -756,7 +756,7 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.decrypt(decOpt); return openpgp.decrypt(decOpt);
}).then(function(decrypted) { }).then(function(decrypted) {
expect(decrypted.data).to.equal(plaintext); expect(decrypted.data).to.equal(plaintext);
expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].valid).to.be.true;
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -789,7 +789,7 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.decrypt(decOpt); return openpgp.decrypt(decOpt);
}).then(function(decrypted) { }).then(function(decrypted) {
expect(decrypted.data).to.equal(plaintext); expect(decrypted.data).to.equal(plaintext);
expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].valid).to.be.true;
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -826,10 +826,10 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.decrypt(decOpt); return openpgp.decrypt(decOpt);
}).then(function(decrypted) { }).then(function(decrypted) {
expect(decrypted.data).to.equal(plaintext); expect(decrypted.data).to.equal(plaintext);
expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].valid).to.be.true;
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
expect(decrypted.signatures[1].valid).to.eventually.be.true; expect(decrypted.signatures[1].valid).to.be.true;
expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex());
expect(decrypted.signatures[1].signature.packets.length).to.equal(1); expect(decrypted.signatures[1].signature.packets.length).to.equal(1);
}); });
@ -998,7 +998,7 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.verify(verifyOpt); return openpgp.verify(verifyOpt);
}).then(function(verified) { }).then(function(verified) {
expect(verified.data).to.equal(plaintext); expect(verified.data).to.equal(plaintext);
expect(verified.signatures[0].valid).to.eventually.be.true; expect(verified.signatures[0].valid).to.be.true;
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
expect(verified.signatures[0].signature.packets.length).to.equal(1); expect(verified.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -1019,7 +1019,7 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.verify(verifyOpt); return openpgp.verify(verifyOpt);
}).then(function(verified) { }).then(function(verified) {
expect(verified.data).to.equal(plaintext); expect(verified.data).to.equal(plaintext);
expect(verified.signatures[0].valid).to.eventually.be.true; expect(verified.signatures[0].valid).to.be.true;
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
expect(verified.signatures[0].signature.packets.length).to.equal(1); expect(verified.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -1079,7 +1079,7 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.verify(verifyOpt); return openpgp.verify(verifyOpt);
}).then(function(verified) { }).then(function(verified) {
expect(verified.data).to.equal(plaintext); expect(verified.data).to.equal(plaintext);
expect(verified.signatures[0].valid).to.eventually.be.true; expect(verified.signatures[0].valid).to.be.true;
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
expect(verified.signatures[0].signature.packets.length).to.equal(1); expect(verified.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -1101,7 +1101,7 @@ describe('OpenPGP.js public api tests', function() {
return openpgp.verify(verifyOpt); return openpgp.verify(verifyOpt);
}).then(function(verified) { }).then(function(verified) {
expect(verified.data).to.equal(plaintext); expect(verified.data).to.equal(plaintext);
expect(verified.signatures[0].valid).to.eventually.be.true; expect(verified.signatures[0].valid).to.be.true;
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
expect(verified.signatures[0].signature.packets.length).to.equal(1); expect(verified.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -1126,7 +1126,7 @@ describe('OpenPGP.js public api tests', function() {
}).then(function(encrypted) { }).then(function(encrypted) {
expect(encrypted.data).to.exist; expect(encrypted.data).to.exist;
expect(encrypted.data).to.equal(plaintext); expect(encrypted.data).to.equal(plaintext);
expect(encrypted.signatures[0].valid).to.eventually.be.true; expect(encrypted.signatures[0].valid).to.be.true;
expect(encrypted.signatures[0].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); expect(encrypted.signatures[0].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex());
expect(encrypted.signatures[0].signature.packets.length).to.equal(1); expect(encrypted.signatures[0].signature.packets.length).to.equal(1);
}); });

View File

@ -342,7 +342,7 @@ describe("Signature", function() {
priv_key.decrypt("abcd"); priv_key.decrypt("abcd");
return openpgp.decrypt({ privateKey: priv_key, publicKeys:[pub_key], message:msg }).then(function(decrypted) { return openpgp.decrypt({ privateKey: priv_key, publicKeys:[pub_key], message:msg }).then(function(decrypted) {
expect(decrypted.data).to.exist; expect(decrypted.data).to.exist;
expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].valid).to.be.true;
expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
}); });
}); });
@ -386,7 +386,7 @@ describe("Signature", function() {
return msg.verify([pub_key]).then(verified => { return msg.verify([pub_key]).then(verified => {
expect(verified).to.exist; expect(verified).to.exist;
expect(verified).to.have.length(1); expect(verified).to.have.length(1);
expect(verified[0].valid).to.eventually.be.true; expect(verified[0].valid).to.be.true;
expect(verified[0].signature.packets.length).to.equal(1); expect(verified[0].signature.packets.length).to.equal(1);
}); });
}); });
@ -411,7 +411,7 @@ describe("Signature", function() {
return sMsg.verify([pub_key]).then(verified => { return sMsg.verify([pub_key]).then(verified => {
expect(verified).to.exist; expect(verified).to.exist;
expect(verified).to.have.length(1); expect(verified).to.have.length(1);
expect(verified[0].valid).to.eventually.be.true; expect(verified[0].valid).to.be.true;
expect(verified[0].signature.packets.length).to.equal(1); expect(verified[0].signature.packets.length).to.equal(1);
}); });
}); });
@ -435,7 +435,7 @@ describe("Signature", function() {
sMsg.verify([pub_key]).then(verified => { sMsg.verify([pub_key]).then(verified => {
expect(verified).to.exist; expect(verified).to.exist;
expect(verified).to.have.length(1); expect(verified).to.have.length(1);
expect(verified[0].valid).to.eventually.be.true; expect(verified[0].valid).to.be.true;
expect(verified[0].signature.packets.length).to.equal(1); expect(verified[0].signature.packets.length).to.equal(1);
}); });
}); });
@ -470,7 +470,7 @@ describe("Signature", function() {
expect(decrypted.data).to.exist; expect(decrypted.data).to.exist;
expect(decrypted.data).to.equal(plaintext); expect(decrypted.data).to.equal(plaintext);
expect(decrypted.signatures).to.have.length(1); expect(decrypted.signatures).to.have.length(1);
expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].valid).to.be.true;
expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
}); });
}); });
@ -506,7 +506,7 @@ describe("Signature", function() {
expect(decrypted.data).to.exist; expect(decrypted.data).to.exist;
expect(decrypted.data).to.equal(plaintext); expect(decrypted.data).to.equal(plaintext);
expect(decrypted.signatures).to.have.length(1); expect(decrypted.signatures).to.have.length(1);
expect(decrypted.signatures[0].valid).to.eventually.be.true; expect(decrypted.signatures[0].valid).to.be.true;
expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -547,8 +547,8 @@ describe("Signature", function() {
sMsg.verify([pubKey2, pubKey3]).then(verifiedSig => { sMsg.verify([pubKey2, pubKey3]).then(verifiedSig => {
expect(verifiedSig).to.exist; expect(verifiedSig).to.exist;
expect(verifiedSig).to.have.length(2); expect(verifiedSig).to.have.length(2);
expect(verifiedSig[0].valid).to.eventually.be.true; expect(verifiedSig[0].valid).to.be.true;
expect(verifiedSig[1].valid).to.eventually.be.true; expect(verifiedSig[1].valid).to.be.true;
expect(verifiedSig[0].signature.packets.length).to.equal(1); expect(verifiedSig[0].signature.packets.length).to.equal(1);
expect(verifiedSig[1].signature.packets.length).to.equal(1); expect(verifiedSig[1].signature.packets.length).to.equal(1);
}); });
@ -593,8 +593,8 @@ describe("Signature", function() {
expect(cleartextSig).to.exist; expect(cleartextSig).to.exist;
expect(cleartextSig.data).to.equal(plaintext); expect(cleartextSig.data).to.equal(plaintext);
expect(cleartextSig.signatures).to.have.length(2); expect(cleartextSig.signatures).to.have.length(2);
expect(cleartextSig.signatures[0].valid).to.eventually.be.true; expect(cleartextSig.signatures[0].valid).to.be.true;
expect(cleartextSig.signatures[1].valid).to.eventually.be.true; expect(cleartextSig.signatures[1].valid).to.be.true;
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
expect(cleartextSig.signatures[1].signature.packets.length).to.equal(1); expect(cleartextSig.signatures[1].signature.packets.length).to.equal(1);
}); });
@ -615,7 +615,7 @@ describe("Signature", function() {
expect(cleartextSig).to.exist; expect(cleartextSig).to.exist;
expect(cleartextSig.data).to.equal(plaintext.replace(/\r/g,'')); expect(cleartextSig.data).to.equal(plaintext.replace(/\r/g,''));
expect(cleartextSig.signatures).to.have.length(1); expect(cleartextSig.signatures).to.have.length(1);
expect(cleartextSig.signatures[0].valid).to.eventually.be.true; expect(cleartextSig.signatures[0].valid).to.be.true;
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -636,7 +636,7 @@ describe("Signature", function() {
expect(cleartextSig).to.exist; expect(cleartextSig).to.exist;
expect(cleartextSig.data).to.deep.equal(plaintext); expect(cleartextSig.data).to.deep.equal(plaintext);
expect(cleartextSig.signatures).to.have.length(1); expect(cleartextSig.signatures).to.have.length(1);
expect(cleartextSig.signatures[0].valid).to.eventually.be.true; expect(cleartextSig.signatures[0].valid).to.be.true;
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -657,7 +657,7 @@ describe("Signature", function() {
expect(cleartextSig).to.exist; expect(cleartextSig).to.exist;
expect(cleartextSig.data).to.deep.equal(plaintext); expect(cleartextSig.data).to.deep.equal(plaintext);
expect(cleartextSig.signatures).to.have.length(1); expect(cleartextSig.signatures).to.have.length(1);
expect(cleartextSig.signatures[0].valid).to.eventually.be.true; expect(cleartextSig.signatures[0].valid).to.be.true;
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
}); });
@ -781,7 +781,7 @@ describe("Signature", function() {
var msg = openpgp.message.readSignedContent(content, detachedSig); var msg = openpgp.message.readSignedContent(content, detachedSig);
return msg.verify(publicKeys).then(result => { return msg.verify(publicKeys).then(result => {
expect(result[0].valid).to.eventually.be.true; expect(result[0].valid).to.be.true;
}); });
}); });
@ -797,8 +797,8 @@ describe("Signature", function() {
var generatedKey = gen.key; var generatedKey = gen.key;
return msg.signDetached([generatedKey, privKey2]).then(detachedSig => { return msg.signDetached([generatedKey, privKey2]).then(detachedSig => {
return msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]).then(result => { return msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]).then(result => {
expect(result[0].valid).to.eventually.be.true; expect(result[0].valid).to.be.true;
expect(result[1].valid).to.eventually.be.true; expect(result[1].valid).to.be.true;
}); });
}); });
}); });

View File

@ -14,7 +14,7 @@
<script src="lib/chai.js"></script> <script src="lib/chai.js"></script>
<script src="lib/mocha.js"></script> <script src="lib/mocha.js"></script>
<script src="lib/fetch.js"></script> <script src="lib/fetch.js"></script>
] <script> <script>
mocha.setup('bdd'); mocha.setup('bdd');
mocha.timeout(240000); mocha.timeout(240000);
</script> </script>