Bug 390615: Add scriptable interface to verify digital signatures. r=kaie,

a=blocking-1.9


git-svn-id: svn://10.0.0.236/trunk@233809 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
dtownsend%oxymoronical.com
2007-09-03 17:32:41 +00:00
parent 9f5e4ef664
commit 99158494a1
7 changed files with 462 additions and 1 deletions

View File

@@ -94,6 +94,7 @@ XPIDLSRCS = \
nsICipherInfo.idl \
nsIStreamCipher.idl \
nsIKeyModule.idl \
nsIDataSignatureVerifier.idl \
$(NULL)
ifdef MOZ_XUL

View File

@@ -0,0 +1,58 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the mozilla.org code.
*
* The Initial Developer of the Original Code is Ben Goodger.
* Portions created by the Initial Developer are Copyright (C) 2004
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Dave Townsend <dtownsend@oxymoronical.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
/**
* An interface for verifying that a given string of data was signed by the
* private key matching the given public key.
*/
[scriptable, uuid(0a84b3d5-6ba9-432d-89da-4fbd0b0f2aec)]
interface nsIDataSignatureVerifier : nsISupports
{
/**
* Verifies that the data matches the data that was used to generate the
* signature.
*
* @param aData The data to be tested.
* @param aSignature The signature of the data, base64 encoded.
* @param aPublicKey The public part of the key used for signing, DER encoded
* then base64 encoded.
* @returns true if the signature matches the data, false if not.
*/
boolean verifyData(in ACString aData, in ACString aSignature, in ACString aPublicKey);
};

View File

@@ -96,7 +96,8 @@ CPPSRCS = \
nsStreamCipher.cpp \
nsKeyModule.cpp \
nsIdentityChecking.cpp \
$(NULL)
nsDataSignatureVerifier.cpp \
$(NULL)
ifdef MOZ_XUL
CPPSRCS += nsCertTree.cpp

View File

@@ -0,0 +1,134 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* Contributor(s):
* Dave Townsend <dtownsend@oxymoronical.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsDataSignatureVerifier.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "seccomon.h"
#include "nssb64.h"
#include "certt.h"
#include "keyhi.h"
#include "cryptohi.h"
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
NS_IMPL_ISUPPORTS1(nsDataSignatureVerifier, nsIDataSignatureVerifier)
const SEC_ASN1Template CERT_SignatureDataTemplate[] =
{
{ SEC_ASN1_SEQUENCE,
0, NULL, sizeof(CERTSignedData) },
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
offsetof(CERTSignedData,signatureAlgorithm),
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate), },
{ SEC_ASN1_BIT_STRING,
offsetof(CERTSignedData,signature), },
{ 0, }
};
NS_IMETHODIMP
nsDataSignatureVerifier::VerifyData(const nsACString & aData,
const nsACString & aSignature,
const nsACString & aPublicKey,
PRBool *_retval)
{
// Allocate an arena to handle the majority of the allocations
PRArenaPool *arena;
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (!arena)
return NS_ERROR_OUT_OF_MEMORY;
// Base 64 decode the key
SECItem keyItem;
PORT_Memset(&keyItem, 0, sizeof(SECItem));
if (!NSSBase64_DecodeBuffer(arena, &keyItem,
nsPromiseFlatCString(aPublicKey).get(),
aPublicKey.Length())) {
PORT_FreeArena(arena, PR_FALSE);
return NS_ERROR_FAILURE;
}
// Extract the public key from the data
CERTSubjectPublicKeyInfo *pki = SECKEY_DecodeDERSubjectPublicKeyInfo(&keyItem);
if (!pki) {
PORT_FreeArena(arena, PR_FALSE);
return NS_ERROR_FAILURE;
}
SECKEYPublicKey *publicKey = SECKEY_ExtractPublicKey(pki);
SECKEY_DestroySubjectPublicKeyInfo(pki);
pki = nsnull;
if (!publicKey) {
PORT_FreeArena(arena, PR_FALSE);
return NS_ERROR_FAILURE;
}
// Base 64 decode the signature
SECItem signatureItem;
PORT_Memset(&signatureItem, 0, sizeof(SECItem));
if (!NSSBase64_DecodeBuffer(arena, &signatureItem,
nsPromiseFlatCString(aSignature).get(),
aSignature.Length())) {
SECKEY_DestroyPublicKey(publicKey);
PORT_FreeArena(arena, PR_FALSE);
return NS_ERROR_FAILURE;
}
// Decode the signature and algorithm
CERTSignedData sigData;
PORT_Memset(&sigData, 0, sizeof(CERTSignedData));
SECStatus ss = SEC_QuickDERDecodeItem(arena, &sigData,
CERT_SignatureDataTemplate,
&signatureItem);
if (ss != SECSuccess) {
SECKEY_DestroyPublicKey(publicKey);
PORT_FreeArena(arena, PR_FALSE);
return NS_ERROR_FAILURE;
}
// Perform the final verification
DER_ConvertBitString(&(sigData.signature));
ss = VFY_VerifyDataWithAlgorithmID((const unsigned char*)nsPromiseFlatCString(aData).get(),
aData.Length(), publicKey,
&(sigData.signature),
&(sigData.signatureAlgorithm),
NULL, NULL);
// Clean up remaining objects
SECKEY_DestroyPublicKey(publicKey);
PORT_FreeArena(arena, PR_FALSE);
*_retval = (ss == SECSuccess);
return NS_OK;
}

View File

@@ -0,0 +1,63 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* Contributor(s):
* Dave Townsend <dtownsend@oxymoronical.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef _NS_DATASIGNATUREVERIFIER_H_
#define _NS_DATASIGNATUREVERIFIER_H_
#include "nsIDataSignatureVerifier.h"
#include "keythi.h"
// 296d76aa-275b-4f3c-af8a-30a4026c18fc
#define NS_DATASIGNATUREVERIFIER_CID \
{ 0x296d76aa, 0x275b, 0x4f3c, \
{ 0xaf, 0x8a, 0x30, 0xa4, 0x02, 0x6c, 0x18, 0xfc } }
#define NS_DATASIGNATUREVERIFIER_CONTRACTID \
"@mozilla.org/security/datasignatureverifier;1"
class nsDataSignatureVerifier : public nsIDataSignatureVerifier
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDATASIGNATUREVERIFIER
nsDataSignatureVerifier()
{
}
private:
~nsDataSignatureVerifier()
{
}
};
#endif // _NS_DATASIGNATUREVERIFIER_H_

View File

@@ -71,6 +71,7 @@
#include "nsNTLMAuthModule.h"
#include "nsStreamCipher.h"
#include "nsKeyModule.h"
#include "nsDataSignatureVerifier.h"
// We must ensure that the nsNSSComponent has been loaded before
// creating any other components.
@@ -190,6 +191,7 @@ NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsCryptoHash)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsStreamCipher)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsKeyObject)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsKeyObjectFactory)
NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsDataSignatureVerifier)
static NS_METHOD RegisterPSMContentListeners(
nsIComponentManager *aCompMgr,
@@ -445,6 +447,13 @@ static const nsModuleComponentInfo components[] =
NS_KEYMODULEOBJECTFACTORY_CID,
NS_KEYMODULEOBJECTFACTORY_CONTRACTID,
nsKeyObjectFactoryConstructor
},
{
"Signature Verifier",
NS_DATASIGNATUREVERIFIER_CID,
NS_DATASIGNATUREVERIFIER_CONTRACTID,
nsDataSignatureVerifierConstructor
}
};

View File

@@ -0,0 +1,195 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const DSV = Ci.nsIDataSignatureVerifier;
var keys = [
// RSA key
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDK426erD/H3XtsjvaB5+PJqbhj" +
"Zc9EDI5OCJS8R3FIObJ9ZHJK1TXeaE7JWqt9WUmBWTEFvwS+FI9vWu8058N9CHhD" +
"NyeP6i4LuUYjTURnn7Yw/IgzyIJ2oKsYa32RuxAyteqAWqPT/J63wBixIeCxmysf" +
"awB/zH4KaPiY3vnrzQIDAQAB",
// RSA key
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHe69VsjNCwnGF9YGZLgnobp3D" +
"c1KgWzpbT+f12vVKkV3YBpA9KMVMy6wpxlDjvLJjfp/0HvaH7aaz/7kgxZw70Y60" +
"LaJtkAcl1ZVAxS2lQKRTAzZ0RhoTSI1xVqGTjiQakgVdUeghtnqqwp5o1inZv3Qh" +
"nUOMNPyAV8zGt+ZQHQIDAQAB",
// Invalid key data ("foobar" base 64 encoded)
"Zm9vYmFy"
];
var data = [
"Test data for data signature verifier",
"The quick brown fox jumps over the lazy dog..."
];
var signatures = [
// Key 0, Data 0, MD2 hash algorithm
"MIGTMA0GCSqGSIb3DQEBAgUAA4GBALe3hO76UCpI8b1/oJUCIPmC6AbnMAMlAqo7" +
"pc3TaWmU9wISWmXSrwNmr/QQNjWDn4nzQn8/K/Ac+tszaXib6fVLKA1a6e+/E0qE" +
"OIKFwUiDWCkGDgxM8aYiTgoSZub/5rokgse+ivuCRSVTv9mSxRzKwj+Cvp1EjKCT" +
"iIl3nnTh",
// Key 0, Data 0, MD5 hash algorithm
"MIGTMA0GCSqGSIb3DQEBBAUAA4GBAGGb2QkA8LcA+QZj1SoVFmMpVTd9P5Ac0Rjb" +
"ldouMmngztMV/dxymVKCpknqelhsxTQ/zaqGZ4KKzcIffJa9jXi5XUD8XzDIKcFE" +
"dQvti8oUNDPb6l1ybETI8LKh2ywyBCSZ/Q5BwUeS9nfx+4cAnu9YZf29SGljit+Y" +
"XLPKZ+of",
// Key 0, Data 0, SHA1 hash algorithm
"MIGTMA0GCSqGSIb3DQEBBQUAA4GBAHcl6tqR5yjTDUJ5NiLOdIB4I6/C/rS2WW5a" +
"7MVhYYhBgaZyGaem4LLT/REloOOLwPNMcmSEpwexqMSATbl9AAoJPq/wO3l4cZpO" +
"tDkLlafsiwnqK156YvHp50zUq5Os28d6Bq/Nl2qjH1yizwlIEo0o9qo8Cq6QK3j2" +
"MdFCopgk",
// Key 0, Data 0, SHA256 hash algorithm
"MIGTMA0GCSqGSIb3DQEBCwUAA4GBAAOtDvzWwbYK7zLUz0F3e3wmkL1YWXuD/GzQ" +
"LgwOg6VWtn9v54M9nfv/+iX+m4udzIwHZU7utYM31vtwqRC36l0QKo2VMbiLySX+" +
"uHnSK40Kk4SfBvMF1wuz6BQ8ia8uTjPPfC764dB1N7gQdgdRevLTrh2EU6+DLmSS" +
"Sm1QJm9/",
// Key 0, Data 0, SHA384 hash algorithm
"MIGTMA0GCSqGSIb3DQEBDAUAA4GBAMWB3DskcGuGXpi3TO2Pm0g915EIvm5aOeXQ" +
"sbs0ZGOwPyzYN1kKOmEpGHMeRhcIIBcF80ZC5N6dsTxeMGkFGOqhvB/HNl7gXMqF" +
"OA8mG9vAcwfMeJlY4H5WbYD8bUn72UbIuS+sURLvWVhuIFBYPHHU7KVUaGAWl0rp" +
"hCa4Dk37",
// Key 0, Data 0, SHA512 hash algorithm
"MIGTMA0GCSqGSIb3DQEBDQUAA4GBAFkm61zH8Y0J5PA4GtOvoZA6w/SzHg5jFP11" +
"gmXki81VgKwLF7Gyj4wRBX7Q9c8cNrNeoGZb12NUUxlR+u6J6VxcosVPKrCz7Xfj" +
"LPi6+A1dNV5eH2B6tZR6wIiEatAWNIXxQZEJbj9BWefRFq6NnKhR5A/MEPnrZyoR" +
"Da3YsDV3",
// Key 0, Data 1, MD2 hash algorithm
"MIGTMA0GCSqGSIb3DQEBAgUAA4GBAJjwosJK6jV9Bt6HhrFn7+48LRhamjWjzs7a" +
"cf5D/GTuul6aQQvQJ4Lt26KTyh3VglaQJFToH0Ik/fR1lOJS3tCPr1RRH06cKZgK" +
"haoUaGR8rmtn678wX067q7ACmKPeqmgj71pHm7O5YgN3z45iAazbUHP4erdbFUf9" +
"4rOr3L2f",
// Key 0, Data 1, MD5 hash algorithm
"MIGTMA0GCSqGSIb3DQEBBAUAA4GBAC0EAoHWTb4CC+gw7fs5zMaZw7PWoDH1rXMD" +
"dKoMBDmAW1EXZTfUGUTv0ga3VzuPJKuHHZOFVyFDnt4qFrefzzWs17LiPpN+yVgo" +
"6vBnpXLeIp7D9n94bz56gv9NZZmy02XQVKDaRc3E4JBC7Ze7RAHuKtWuZRTUKF86" +
"VXakwW3a",
// Key 0, Data 1, SHA1 hash algorithm
"MIGTMA0GCSqGSIb3DQEBBQUAA4GBABkClr0Ot3aXBKYIiARdwpX8etDQO/Eqjxe8" +
"pJyaqwj/P+x8j9/CbtJKJJTxvYmV9AhdgLPgoWjcTkfvqKdb1vpKKbV30QC/TEnu" +
"ON+66MJgkwrZw6NCDyBRgMTjf4FWR75Ot1DLuu3+7uCswKCJe8la0HMm/AcqUzu1" +
"SKOPMseI",
// Key 0, Data 1, SHA256 hash algorithm
"MIGTMA0GCSqGSIb3DQEBCwUAA4GBAE2cIr6Uzo7RISkGgCA5m4K8s9+0iHwzr2u/" +
"3ICUrTPe4RY2g9RLd6qkwaHD101LW5TQw71fhePIxfWHEhWtTCLS5DnGiucxfGKW" +
"47gOBJIYf0DG7o5N4lA99j2Zuj+V+yjAcLfq7Su5FwASbD30KqCue1/F03qdXUxj" +
"ciJeGo2b",
// Key 0, Data 1, SHA384 hash algorithm
"MIGTMA0GCSqGSIb3DQEBDAUAA4GBAK+JfKJNBjw2d2iXT/pe9xMXPkLSkf+gjG2i" +
"F7B0kBvMGyOVxuvQ4TCx+NFhAUevQugDHgBMkmAieg8mApcWWFWQ+8rbdUFv/hD7" +
"fHW+QukMgcfLMquh0GtDuoM8ZKFBBvwnPGLLUh+ZMy8fEOjjH+s6bQQSpf072SSJ" +
"K+Uo8DG2",
// Key 0, Data 1, SHA512 hash algorithm
"MIGTMA0GCSqGSIb3DQEBDQUAA4GBAEEvDpyBssG0qFfRamNwkwSjhqYRVFdIa6+E" +
"xfxdRqW/nxN5HuzFA8aajgSMXX0YFWPXV7OuVjCCJfZWhq7kQpTy96AmI/04rVdr" +
"9gc5mc2tdLl3Yk/Qd+Xq8WYcQIZ5Ewyo7sr8eKtVhtEM8OtPR54FO2s1pkZwJdVf" +
"ymMzHBoE",
// Key 1, Data 0, MD5 hash algorithm
"MIGTMA0GCSqGSIb3DQEBBAUAA4GBAAIzLho2i5jfJ5SPPV/u2gUuefzhjEAsUhlL" +
"Nir4FKhNzB2DZNbME9DtgNvdmZd00IjydYlaJ0dnLiMigXIaRJsyncYazULZdY6i" +
"i7oP6llbXbszSTbHGolr5kQ+6cZPBBATOkJ+wekDdlvh5cZ+B0Lux4LevUDlGWAy" +
"uR7bqrc5",
// Key 1, Data 0, SHA512 hash algorithm
"MIGTMA0GCSqGSIb3DQEBDQUAA4GBABsjF8K/SIaY3KTeIGpPEwl1+ZXLKBaHxlRT" +
"b37PhorSfqW1KFjquCEASUUeFwCQ14uUIBaRQV2haRGA0dRpuWr4zrWZMcDKOsmX" +
"r5XRvcti9/lNqoIID/Mq0tKtS6aVFZpoHIrwbXpV4hV+BRGhaPxV3RBzEIzM7bWJ" +
"tN3JY9+1",
// Key 1, Data 1, MD5 hash algorithm
"MIGTMA0GCSqGSIb3DQEBBAUAA4GBAIAxRPXDAT2LBcOo7yTNr5uIZbPW9rkSX0Ba" +
"h4sq6YRcxlnohaE2VO0CLGXFNwaBihhkkp+2dA76EvbMo/+O9XTWwroqtWWwvmxs" +
"tWK6HvwYKnGFKOOZMOjNjmXjk446UVvxYCbU+NPM1LZTewT1/UpPWWRowF/lwX7m" +
"SnT8d2ds",
// Key 1, Data 1, SHA512 hash algorithm
"MIGTMA0GCSqGSIb3DQEBDQUAA4GBAF0+XYD/r0Annz1GJ24GTkAlWY/OixCSV6Ix" +
"OMM7P2d/jgOP+ICKIpxqaSE0CbkLiegUiidIOWvFqDxQJWlAAukDUWISGFfJMFxX" +
"3jzJ0bBfeNY/1Qo8jMQopcNco/NlNgoSKAUOBtk31aFgNoVC3kWUk6pO97KEiJ+e" +
"bQp9Z2/M",
// Invalid signature data ("foobar" base 64 encoded)
"Zm9vYmFy"
];
var tests = [
// Data Signature Key Expected Throws
// Pass cases
[0, 0, 0, true, false], //0
[0, 1, 0, true, false], //1
[0, 2, 0, true, false], //2
[0, 3, 0, true, false], //3
[0, 4, 0, true, false], //4
[0, 5, 0, true, false], //5
[1, 6, 0, true, false], //6
[1, 7, 0, true, false], //7
[1, 8, 0, true, false], //8
[1, 9, 0, true, false], //9
[1, 10, 0, true, false], //10
[1, 11, 0, true, false], //11
[0, 12, 1, true, false], //12
[0, 13, 1, true, false], //13
[1, 14, 1, true, false], //14
[1, 15, 1, true, false], //15
// Incorrect data cases
[1, 0, 0, false, false], //16
[1, 1, 0, false, false], //17
[1, 2, 0, false, false], //18
[1, 3, 0, false, false], //19
[1, 4, 0, false, false], //20
[1, 5, 0, false, false], //21
[0, 6, 0, false, false], //22
[0, 7, 0, false, false], //23
[0, 8, 0, false, false], //24
[0, 9, 0, false, false], //25
[0, 10, 0, false, false], //26
[0, 11, 0, false, false], //27
// Incorrect key cases
[0, 1, 1, false, false], //28
[0, 5, 1, false, false], //29
[1, 7, 1, false, false], //30
[1, 11, 1, false, false], //31
[0, 12, 0, false, false], //32
[0, 13, 0, false, false], //33
[1, 14, 0, false, false], //34
[1, 15, 0, false, false], //35
// Invalid data cases
[0, 0, 2, false, true], //36
[0, 1, 2, false, true], //37
[0, 16, 0, false, true], //38
[1, 16, 0, false, true], //39
];
function run_test() {
var verifier = Cc["@mozilla.org/security/datasignatureverifier;1"].
createInstance(Ci.nsIDataSignatureVerifier);
for (var t = 0; t < tests.length; t++) {
try {
var result = verifier.verifyData(data[tests[t][0]],
signatures[tests[t][1]],
keys[tests[t][2]]);
if (tests[t][4])
do_throw("Test " + t + " didn't throw");
if (result != tests[t][3])
do_throw("Test " + t + " was " + result + " but should have been " + tests[t][3]);
}
catch (e) {
if (!tests[t][4])
do_throw("Test " + t + " threw " + e);
}
}
}