test_WebCrypto_ECDH.html (22288B)
1 <!DOCTYPE html> 2 <html> 3 4 <head> 5 <title>WebCrypto Test Suite</title> 6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> 7 <link rel="stylesheet" href="./test_WebCrypto.css"/> 8 <script src="/tests/SimpleTest/SimpleTest.js"></script> 9 10 <!-- Utilities for manipulating ABVs --> 11 <script src="util.js"></script> 12 13 <!-- A simple wrapper around IndexedDB --> 14 <script src="simpledb.js"></script> 15 16 <!-- Test vectors drawn from the literature --> 17 <script src="./test-vectors.js"></script> 18 19 <!-- General testing framework --> 20 <script src="./test-array.js"></script> 21 22 <script>/* <![CDATA[*/ 23 "use strict"; 24 25 // ----------------------------------------------------------------------------- 26 TestArray.addTest( 27 "Generate an ECDH key for named curve P-256", 28 function() { 29 var that = this; 30 var alg = { name: "ECDH", namedCurve: "P-256" }; 31 crypto.subtle.generateKey(alg, false, ["deriveKey", "deriveBits"]).then( 32 complete(that, function(x) { 33 return exists(x.publicKey) && 34 (x.publicKey.algorithm.name == alg.name) && 35 (x.publicKey.algorithm.namedCurve == alg.namedCurve) && 36 (x.publicKey.type == "public") && 37 x.publicKey.extractable && 38 (!x.publicKey.usages.length) && 39 exists(x.privateKey) && 40 (x.privateKey.algorithm.name == alg.name) && 41 (x.privateKey.algorithm.namedCurve == alg.namedCurve) && 42 (x.privateKey.type == "private") && 43 !x.privateKey.extractable && 44 (x.privateKey.usages.length == 2) && 45 (x.privateKey.usages[0] == "deriveKey") && 46 (x.privateKey.usages[1] == "deriveBits"); 47 }), 48 error(that) 49 ); 50 } 51 ); 52 53 // ----------------------------------------------------------------------------- 54 TestArray.addTest( 55 "Generate an ECDH key and derive some bits", 56 function() { 57 var that = this; 58 var alg = { name: "ECDH", namedCurve: "P-256" }; 59 60 var pair; 61 function setKeyPair(x) { pair = x; } 62 63 function doDerive(n) { 64 return function() { 65 return crypto.subtle.deriveBits({ name: "ECDH", public: pair.publicKey }, pair.privateKey, n * 8); 66 }; 67 } 68 69 crypto.subtle.generateKey(alg, false, ["deriveBits"]) 70 .then(setKeyPair, error(that)) 71 .then(doDerive(2), error(that)) 72 .then(function(x) { 73 // Deriving less bytes works. 74 if (x.byteLength != 2) { 75 throw new Error("should have derived two bytes"); 76 } 77 }) 78 // Deriving more than the curve yields doesn't. 79 .then(doDerive(33), error(that)) 80 .then(error(that), complete(that)); 81 } 82 ); 83 84 // ----------------------------------------------------------------------------- 85 TestArray.addTest( 86 "Test that ECDH deriveBits() fails when the public key is not an ECDH key", 87 function() { 88 var that = this; 89 var pubKey, privKey; 90 function setPub(x) { pubKey = x.publicKey; } 91 function setPriv(x) { privKey = x.privateKey; } 92 93 function doGenerateP256() { 94 var alg = { name: "ECDH", namedCurve: "P-256" }; 95 return crypto.subtle.generateKey(alg, false, ["deriveBits"]); 96 } 97 98 function doGenerateRSA() { 99 var alg = { 100 name: "RSA-OAEP", 101 hash: "SHA-256", 102 modulusLength: 2048, 103 publicExponent: new Uint8Array([0x01, 0x00, 0x01]), 104 }; 105 return crypto.subtle.generateKey(alg, false, ["encrypt", "decrypt"]); 106 } 107 108 function doDerive() { 109 var alg = { name: "ECDH", public: pubKey }; 110 return crypto.subtle.deriveBits(alg, privKey, 16); 111 } 112 113 doGenerateP256() 114 .then(setPriv, error(that)) 115 .then(doGenerateRSA, error(that)) 116 .then(setPub, error(that)) 117 .then(doDerive, error(that)) 118 .then(error(that), complete(that)); 119 } 120 ); 121 122 // ----------------------------------------------------------------------------- 123 TestArray.addTest( 124 "Test that ECDH deriveBits() fails when the given keys' curves don't match", 125 function() { 126 var that = this; 127 var pubKey, privKey; 128 function setPub(x) { pubKey = x.publicKey; } 129 function setPriv(x) { privKey = x.privateKey; } 130 131 function doGenerateP256() { 132 var alg = { name: "ECDH", namedCurve: "P-256" }; 133 return crypto.subtle.generateKey(alg, false, ["deriveBits"]); 134 } 135 136 function doGenerateP384() { 137 var alg = { name: "ECDH", namedCurve: "P-384" }; 138 return crypto.subtle.generateKey(alg, false, ["deriveBits"]); 139 } 140 141 function doDerive() { 142 var alg = { name: "ECDH", public: pubKey }; 143 return crypto.subtle.deriveBits(alg, privKey, 16); 144 } 145 146 doGenerateP256() 147 .then(setPriv, error(that)) 148 .then(doGenerateP384, error(that)) 149 .then(setPub, error(that)) 150 .then(doDerive, error(that)) 151 .then(error(that), complete(that)); 152 } 153 ); 154 155 // ----------------------------------------------------------------------------- 156 TestArray.addTest( 157 "Verify that ECDH import fails with a key with a mismatched 'crv' field", 158 function() { 159 var that = this; 160 var alg = { name: "ECDH", namedCurve: "P-521"}; 161 162 crypto.subtle.importKey("jwk", tv.ecdsa_jwk_crv_mismatch.pub_jwk, alg, true, ["verify"]) 163 .then(error(that), complete(that)); 164 } 165 ); 166 167 // ----------------------------------------------------------------------------- 168 TestArray.addTest( 169 "JWK import an ECDH public and private key and derive bits (P-256)", 170 function() { 171 var that = this; 172 var alg = { name: "ECDH", namedCurve: "P-256" }; 173 174 var pubKey, privKey; 175 function setPub(x) { pubKey = x; } 176 function setPriv(x) { privKey = x; } 177 178 function doDerive() { 179 return crypto.subtle.deriveBits({ name: "ECDH", public: pubKey }, privKey, tv.ecdh_p256.secret.byteLength * 8); 180 } 181 182 Promise.all([ 183 crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"]) 184 .then(setPriv, error(that)), 185 crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_pub, alg, false, []) 186 .then(setPub, error(that)), 187 ]).then(doDerive, error(that)) 188 .then(memcmp_complete(that, tv.ecdh_p256.secret), error(that)); 189 } 190 ); 191 192 // ----------------------------------------------------------------------------- 193 TestArray.addTest( 194 "JWK import an ECDH public and private key and derive bits (P-384)", 195 function() { 196 var that = this; 197 var alg = { name: "ECDH", namedCurve: "P-384"}; 198 199 var pubKey, privKey; 200 function setPub(x) { pubKey = x; } 201 function setPriv(x) { privKey = x; } 202 203 function doDerive() { 204 return crypto.subtle.deriveBits({ name: "ECDH", public: pubKey }, privKey, tv.ecdh_p384.secret.byteLength * 8); 205 } 206 207 Promise.all([ 208 crypto.subtle.importKey("jwk", tv.ecdh_p384.jwk_priv, alg, false, ["deriveBits"]) 209 .then(setPriv, error(that)), 210 crypto.subtle.importKey("jwk", tv.ecdh_p384.jwk_pub, alg, false, []) 211 .then(setPub, error(that)), 212 ]).then(doDerive, error(that)) 213 .then(memcmp_complete(that, tv.ecdh_p384.secret), error(that)); 214 } 215 ); 216 217 // ----------------------------------------------------------------------------- 218 TestArray.addTest( 219 "JWK import an ECDH public and private key and derive bits (P-521)", 220 function() { 221 var that = this; 222 var alg = { name: "ECDH", namedCurve : "P-521" }; 223 224 var pubKey, privKey; 225 function setPub(x) { pubKey = x; } 226 function setPriv(x) { privKey = x; } 227 228 function doDerive() { 229 return crypto.subtle.deriveBits({ name: "ECDH", public: pubKey }, privKey, tv.ecdh_p521.secret.byteLength * 8); 230 } 231 232 Promise.all([ 233 crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_priv, alg, false, ["deriveBits"]) 234 .then(setPriv, error(that)), 235 crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_pub, alg, false, []) 236 .then(setPub, error(that)), 237 ]).then(doDerive, error(that)) 238 .then(memcmp_complete(that, tv.ecdh_p521.secret), error(that)); 239 } 240 ); 241 242 // ----------------------------------------------------------------------------- 243 TestArray.addTest( 244 "JWK import/export roundtrip with ECDH (P-256)", 245 function() { 246 var that = this; 247 var alg = { name: "ECDH", namedCurve : "P-256" }; 248 249 var pubKey, privKey; 250 function setPub(x) { pubKey = x; } 251 function setPriv(x) { privKey = x; } 252 253 function doExportPub() { 254 return crypto.subtle.exportKey("jwk", pubKey); 255 } 256 function doExportPriv() { 257 return crypto.subtle.exportKey("jwk", privKey); 258 } 259 260 Promise.all([ 261 crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, true, ["deriveBits"]) 262 .then(setPriv, error(that)), 263 crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_pub, alg, true, []) 264 .then(setPub, error(that)), 265 ]).then(doExportPub, error(that)) 266 .then(function(x) { 267 var tp = tv.ecdh_p256.jwk_pub; 268 if ((tp.kty != x.kty) && 269 (tp.crv != x.crv) && 270 (tp.x != x.x) && 271 (tp.y != x.y)) { 272 throw new Error("exported public key doesn't match"); 273 } 274 }, error(that)) 275 .then(doExportPriv, error(that)) 276 .then(complete(that, function(x) { 277 var tp = tv.ecdh_p256.jwk_priv; 278 return (tp.kty == x.kty) && 279 (tp.crv == x.crv) && 280 (tp.d == x.d) && 281 (tp.x == x.x) && 282 (tp.y == x.y); 283 }), error(that)); 284 } 285 ); 286 287 // ----------------------------------------------------------------------------- 288 TestArray.addTest( 289 "PKCS8 import/export roundtrip with ECDH (P-256)", 290 function() { 291 var that = this; 292 var alg = { name: "ECDH", namedCurve: "P-256" }; 293 294 function doExportPriv(x) { 295 return crypto.subtle.exportKey("pkcs8", x); 296 } 297 298 crypto.subtle.importKey("pkcs8", tv.ecdh_p256.pkcs8, alg, true, ["deriveBits"]) 299 .then(doExportPriv, error(that)) 300 .then(complete(that, function(x) { 301 return util.memcmp(x, tv.ecdh_p256.pkcs8); 302 }), error(that)); 303 } 304 ); 305 306 // ----------------------------------------------------------------------------- 307 TestArray.addTest( 308 "Test that importing bad JWKs fails", 309 function() { 310 var that = this; 311 var alg = { name: "ECDH", namedCurve: "P-256" }; 312 var tvs = tv.ecdh_p256_negative; 313 314 function doTryImport(jwk) { 315 return function() { 316 return crypto.subtle.importKey("jwk", jwk, alg, false, ["deriveBits"]); 317 }; 318 } 319 320 doTryImport(tvs.jwk_bad_crv)() 321 .then(error(that), doTryImport(tvs.jwk_different_crv)) 322 .then(error(that), doTryImport(tvs.jwk_missing_crv)) 323 .then(error(that), doTryImport(tvs.jwk_missing_x)) 324 .then(error(that), doTryImport(tvs.jwk_missing_y)) 325 .then(error(that), complete(that)); 326 } 327 ); 328 329 // ----------------------------------------------------------------------------- 330 TestArray.addTest( 331 "JWK export of a newly generated ECDH private key", 332 function() { 333 var that = this; 334 var alg = { name: "ECDH", namedCurve: "P-256" }; 335 var reBase64URL = /^[a-zA-Z0-9_-]+$/; 336 337 function doExportToJWK(x) { 338 return crypto.subtle.exportKey("jwk", x.privateKey); 339 } 340 341 crypto.subtle.generateKey(alg, true, ["deriveKey", "deriveBits"]) 342 .then(doExportToJWK) 343 .then( 344 complete(that, function(x) { 345 return x.ext && 346 x.kty == "EC" && 347 x.crv == "P-256" && 348 reBase64URL.test(x.x) && 349 reBase64URL.test(x.y) && 350 reBase64URL.test(x.d) && 351 x.x.length == 43 && // 32 octets, base64-encoded 352 x.y.length == 43 && // 32 octets, base64-encoded 353 shallowArrayEquals(x.key_ops, ["deriveKey", "deriveBits"]); 354 }), 355 error(that) 356 ); 357 } 358 ); 359 360 // ----------------------------------------------------------------------------- 361 TestArray.addTest( 362 "Derive an HMAC key from two ECDH keys and test sign/verify", 363 function() { 364 var that = this; 365 var alg = { name: "ECDH", namedCurve: "P-521" }; 366 var algDerived = { name: "HMAC", hash: {name: "SHA-1"} }; 367 368 var pubKey, privKey; 369 function setPub(x) { pubKey = x; } 370 function setPriv(x) { privKey = x; } 371 372 function doDerive() { 373 return crypto.subtle.deriveKey({ name: "ECDH", public: pubKey }, privKey, algDerived, false, ["sign", "verify"]) 374 .then(function(x) { 375 if (!hasKeyFields(x)) { 376 throw new Error("Invalid key; missing field(s)"); 377 } 378 379 // 512 bit is the default for HMAC-SHA1. 380 if (x.algorithm.length != 512) { 381 throw new Error("Invalid key; incorrect length"); 382 } 383 384 return x; 385 }); 386 } 387 388 function doSignAndVerify(x) { 389 var data = crypto.getRandomValues(new Uint8Array(1024)); 390 return crypto.subtle.sign("HMAC", x, data) 391 .then(function(sig) { 392 return crypto.subtle.verify("HMAC", x, sig, data); 393 }); 394 } 395 396 Promise.all([ 397 crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_priv, alg, false, ["deriveKey"]) 398 .then(setPriv), 399 crypto.subtle.importKey("jwk", tv.ecdh_p521.jwk_pub, alg, false, []) 400 .then(setPub), 401 ]).then(doDerive) 402 .then(doSignAndVerify) 403 .then(complete(that, x => x), error(that)); 404 } 405 ); 406 407 // ----------------------------------------------------------------------------- 408 TestArray.addTest( 409 "Derive an HKDF key from two ECDH keys and derive an HMAC key from that", 410 function() { 411 var that = this; 412 var alg = { name: "ECDH", namedCurve: "P-256" }; 413 414 async function doTest() { 415 let privKey = await crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveKey"]); 416 let pubKey = await crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_pub, alg, false, []); 417 let ecdhAlg = { name: "ECDH", public: pubKey }; 418 let hkdfAlg = { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(), info: new Uint8Array() }; 419 let hkdfKey = await crypto.subtle.deriveKey(ecdhAlg, privKey, hkdfAlg, false, ["deriveKey"]); 420 let hmacAlg = { name: "HMAC", hash: "SHA-256" }; 421 let hmacKey = await crypto.subtle.deriveKey(hkdfAlg, hkdfKey, hmacAlg, false, ["sign"]); 422 return crypto.subtle.sign("HMAC", hmacKey, new Uint8Array()); 423 } 424 const expected = util.hex2abv("acf62832fa93469824cd997593bc963b28a68e6f73f4516bbe51b35942fe9811"); 425 doTest().then(memcmp_complete(that, expected), error(that)); 426 } 427 ); 428 429 // ----------------------------------------------------------------------------- 430 // See bug 1994770 431 TestArray.addTest( 432 "Derive an AES Key from two X25519 keys", 433 function() { 434 var that = this; 435 const alg = { name: 'X25519' }; 436 const lengths = [128, 192, 256]; 437 438 async function deriveKey(algorithm, privateKey, publicKey, length) { 439 return window.crypto.subtle.deriveKey( 440 { name: algorithm.name, public: publicKey }, 441 privateKey, 442 { name: 'AES-GCM', length }, 443 true, 444 ['encrypt', 'decrypt'], 445 ); 446 } 447 448 async function doTest(length) { 449 let keyPair1 = await crypto.subtle.generateKey(alg, true, ["deriveKey", "deriveBits"]); 450 let keyPair2 = await crypto.subtle.generateKey(alg, true, ["deriveKey", "deriveBits"]); 451 let derivedKey = await deriveKey(alg, keyPair1.privateKey, keyPair2.publicKey, length); 452 const raw = await window.crypto.subtle.exportKey('raw', derivedKey); 453 const bitLength = raw.byteLength * 8; 454 return bitLength === length; 455 } 456 457 Promise.all(lengths.map(doTest)) 458 .then(complete(that, function(results) { 459 return results.every(result => result === true); 460 })) 461 .catch(error(that)); 462 } 463 ); 464 465 // See bug 1994770 466 TestArray.addTest( 467 "Derive an AES Key from two X25519 keys (invalid length)", 468 function() { 469 var that = this; 470 const alg = { name: "X25519" }; 471 // AES-GCM only accepts 128, 192, 256. 472 const invalidLengths = [0, 64, 129, 200]; 473 474 async function deriveKey(algorithm, privateKey, publicKey, length) { 475 return window.crypto.subtle.deriveKey( 476 { name: algorithm.name, public: publicKey }, 477 privateKey, 478 { name: "AES-GCM", length }, 479 true, 480 ["encrypt", "decrypt"], 481 ); 482 } 483 484 async function doTest(length) { 485 let keyPair1 = await crypto.subtle.generateKey(alg, true, ["deriveKey", "deriveBits"]); 486 let keyPair2 = await crypto.subtle.generateKey(alg, true, ["deriveKey", "deriveBits"]); 487 try { 488 await deriveKey(alg, keyPair1.privateKey, keyPair2.publicKey, length); 489 return false; 490 } catch (e) { 491 return true; 492 } 493 } 494 495 Promise.all(invalidLengths.map(doTest)) 496 .then(complete(that, function(results) { 497 return results.every(result => result === true); 498 }), 499 ) 500 .catch(error(that)); 501 } 502 ); 503 504 // ----------------------------------------------------------------------------- 505 TestArray.addTest( 506 "SPKI import/export of public ECDH keys (P-256)", 507 function() { 508 var that = this; 509 var alg = { name: "ECDH" }; 510 var keys = ["spki", "spki_id_ecpk"]; 511 512 function doImport(key) { 513 return crypto.subtle.importKey("spki", tv.ecdh_p256[key], alg, true, []); 514 } 515 516 function doExport(x) { 517 return crypto.subtle.exportKey("spki", x); 518 } 519 520 function nextKey() { 521 var key = keys.shift(); 522 var imported = doImport(key); 523 var derived = imported.then(doExport); 524 525 return derived.then(function(x) { 526 if (!util.memcmp(x, tv.ecdh_p256.spki_id_ecpk)) { 527 throw new Error("exported key is invalid"); 528 } 529 530 if (keys.length) { 531 return nextKey(); 532 } 533 return Promise.resolve(); 534 }); 535 } 536 537 nextKey().then(complete(that), error(that)); 538 } 539 ); 540 541 // ----------------------------------------------------------------------------- 542 TestArray.addTest( 543 "SPKI/JWK import ECDH keys (P-256) and derive a known secret", 544 function() { 545 var that = this; 546 var alg = { name: "ECDH", namedCurve: "P-256" }; 547 548 var pubKey, privKey; 549 function setPub(x) { pubKey = x; } 550 function setPriv(x) { privKey = x; } 551 552 function doDerive() { 553 return crypto.subtle.deriveBits({ name: "ECDH", public: pubKey }, privKey, tv.ecdh_p256.secret.byteLength * 8); 554 } 555 556 Promise.all([ 557 crypto.subtle.importKey("spki", tv.ecdh_p256.spki, alg, false, []) 558 .then(setPub), 559 crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"]) 560 .then(setPriv), 561 ]).then(doDerive) 562 .then(memcmp_complete(that, tv.ecdh_p256.secret), error(that)); 563 } 564 ); 565 566 // ----------------------------------------------------------------------------- 567 TestArray.addTest( 568 "Raw import/export of a public ECDH key (P-256)", 569 function() { 570 var that = this; 571 var alg = { name: "ECDH", namedCurve: "P-256" }; 572 573 function doExport(x) { 574 return crypto.subtle.exportKey("raw", x); 575 } 576 577 crypto.subtle.importKey("raw", tv.ecdh_p256.raw, alg, true, []) 578 .then(doExport) 579 .then(memcmp_complete(that, tv.ecdh_p256.raw), error(that)); 580 } 581 ); 582 583 // ----------------------------------------------------------------------------- 584 TestArray.addTest( 585 "Test that importing bad raw ECDH keys fails", 586 function() { 587 var that = this; 588 var alg = { name: "ECDH", namedCurve: "P-256" }; 589 590 crypto.subtle.importKey("raw", tv.ecdh_p256_negative.raw_bad, alg, false, []) 591 .then(error(that), complete(that)); 592 } 593 ); 594 595 // ----------------------------------------------------------------------------- 596 TestArray.addTest( 597 "Test that importing ECDH keys with an unknown format fails", 598 function() { 599 var that = this; 600 var alg = { name: "ECDH", namedCurve: "P-256" }; 601 602 crypto.subtle.importKey("unknown", tv.ecdh_p256.raw, alg, false, []) 603 .then(error(that), complete(that)); 604 } 605 ); 606 607 // ----------------------------------------------------------------------------- 608 TestArray.addTest( 609 "Test that importing too short raw ECDH keys fails", 610 function() { 611 var that = this; 612 var alg = { name: "ECDH", namedCurve: "P-256" }; 613 614 crypto.subtle.importKey("raw", tv.ecdh_p256_negative.raw_short, alg, false, []) 615 .then(error(that), complete(that)); 616 } 617 ); 618 619 // ----------------------------------------------------------------------------- 620 TestArray.addTest( 621 "Test that importing too long raw ECDH keys fails", 622 function() { 623 var that = this; 624 var alg = { name: "ECDH", namedCurve: "P-256" }; 625 626 crypto.subtle.importKey("raw", tv.ecdh_p256_negative.raw_long, alg, false, []) 627 .then(error(that), complete(that)); 628 } 629 ); 630 631 // ----------------------------------------------------------------------------- 632 TestArray.addTest( 633 "Test that importing compressed raw ECDH keys fails", 634 function() { 635 var that = this; 636 var alg = { name: "ECDH", namedCurve: "P-256" }; 637 638 crypto.subtle.importKey("raw", tv.ecdh_p256_negative.raw_compressed, alg, false, []) 639 .then(error(that), complete(that)); 640 } 641 ); 642 643 // ----------------------------------------------------------------------------- 644 TestArray.addTest( 645 "RAW/JWK import ECDH keys (P-256) and derive a known secret", 646 function() { 647 var that = this; 648 var alg = { name: "ECDH", namedCurve: "P-256" }; 649 650 var pubKey, privKey; 651 function setPub(x) { pubKey = x; } 652 function setPriv(x) { privKey = x; } 653 654 function doDerive() { 655 return crypto.subtle.deriveBits({ name: "ECDH", public: pubKey }, privKey, tv.ecdh_p256.secret.byteLength * 8); 656 } 657 658 Promise.all([ 659 crypto.subtle.importKey("raw", tv.ecdh_p256.raw, alg, false, []) 660 .then(setPub), 661 crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"]) 662 .then(setPriv), 663 ]).then(doDerive) 664 .then(memcmp_complete(that, tv.ecdh_p256.secret), error(that)); 665 } 666 ); 667 /* ]]>*/</script> 668 </head> 669 670 <body> 671 672 <div id="content"> 673 <div id="head"> 674 <b>Web</b>Crypto<br> 675 </div> 676 677 <div id="start" onclick="start();">RUN ALL</div> 678 679 <div id="resultDiv" class="content"> 680 Summary: 681 <span class="pass"><span id="passN">0</span> passed, </span> 682 <span class="fail"><span id="failN">0</span> failed, </span> 683 <span class="pending"><span id="pendingN">0</span> pending.</span> 684 <br/> 685 <br/> 686 687 <table id="results"> 688 <tr> 689 <th>Test</th> 690 <th>Result</th> 691 <th>Time</th> 692 </tr> 693 </table> 694 695 </div> 696 697 <div id="foot"></div> 698 </div> 699 700 </body> 701 </html>