test_authentication.js (38913B)
1 // This file tests authentication prompt callbacks 2 // TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected) 3 4 "use strict"; 5 6 const { HttpServer } = ChromeUtils.importESModule( 7 "resource://testing-common/httpd.sys.mjs" 8 ); 9 10 // Turn off the authentication dialog blocking for this test. 11 Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2); 12 13 ChromeUtils.defineLazyGetter(this, "URL", function () { 14 return "http://localhost:" + httpserv.identity.primaryPort; 15 }); 16 17 ChromeUtils.defineLazyGetter(this, "PORT", function () { 18 return httpserv.identity.primaryPort; 19 }); 20 21 const FLAG_RETURN_FALSE = 1 << 0; 22 const FLAG_WRONG_PASSWORD = 1 << 1; 23 const FLAG_BOGUS_USER = 1 << 2; 24 const FLAG_PREVIOUS_FAILED = 1 << 3; 25 const CROSS_ORIGIN = 1 << 4; 26 const FLAG_NO_REALM = 1 << 5; 27 const FLAG_NON_ASCII_USER_PASSWORD = 1 << 6; 28 29 const nsIAuthPrompt2 = Ci.nsIAuthPrompt2; 30 const nsIAuthInformation = Ci.nsIAuthInformation; 31 32 function AuthPrompt1(flags) { 33 this.flags = flags; 34 } 35 36 var initialChannelId = -1; 37 38 AuthPrompt1.prototype = { 39 user: "guest", 40 pass: "guest", 41 42 expectedRealm: "secret", 43 44 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]), 45 46 prompt: function ap1_prompt() { 47 do_throw("unexpected prompt call"); 48 }, 49 50 promptUsernameAndPassword: function ap1_promptUP( 51 title, 52 text, 53 realm, 54 savePW, 55 user, 56 pw 57 ) { 58 if (this.flags & FLAG_NO_REALM) { 59 // Note that the realm here isn't actually the realm. it's a pw mgr key. 60 Assert.equal(URL + " (" + this.expectedRealm + ")", realm); 61 } 62 if (!(this.flags & CROSS_ORIGIN)) { 63 if (!text.includes(this.expectedRealm)) { 64 do_throw("Text must indicate the realm"); 65 } 66 } else if (text.includes(this.expectedRealm)) { 67 do_throw("There should not be realm for cross origin"); 68 } 69 if (!text.includes("localhost")) { 70 do_throw("Text must indicate the hostname"); 71 } 72 if (!text.includes(String(PORT))) { 73 do_throw("Text must indicate the port"); 74 } 75 if (text.includes("-1")) { 76 do_throw("Text must contain negative numbers"); 77 } 78 79 if (this.flags & FLAG_RETURN_FALSE) { 80 return false; 81 } 82 83 if (this.flags & FLAG_BOGUS_USER) { 84 this.user = "foo\nbar"; 85 } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) { 86 this.user = "é"; 87 } 88 89 user.value = this.user; 90 if (this.flags & FLAG_WRONG_PASSWORD) { 91 pw.value = this.pass + ".wrong"; 92 // Now clear the flag to avoid an infinite loop 93 this.flags &= ~FLAG_WRONG_PASSWORD; 94 } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) { 95 pw.value = "é"; 96 } else { 97 pw.value = this.pass; 98 } 99 return true; 100 }, 101 102 promptPassword: function ap1_promptPW() { 103 do_throw("unexpected promptPassword call"); 104 }, 105 }; 106 107 function AuthPrompt2(flags) { 108 this.flags = flags; 109 } 110 111 AuthPrompt2.prototype = { 112 user: "guest", 113 pass: "guest", 114 115 expectedRealm: "secret", 116 117 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]), 118 119 promptAuth: function ap2_promptAuth(channel, level, authInfo) { 120 var isNTLM = channel.URI.pathQueryRef.includes("ntlm"); 121 var isDigest = channel.URI.pathQueryRef.includes("digest"); 122 123 if (isNTLM || this.flags & FLAG_NO_REALM) { 124 this.expectedRealm = ""; // NTLM knows no realms 125 } 126 127 Assert.equal(this.expectedRealm, authInfo.realm); 128 129 var expectedLevel = 130 isNTLM || isDigest 131 ? nsIAuthPrompt2.LEVEL_PW_ENCRYPTED 132 : nsIAuthPrompt2.LEVEL_NONE; 133 Assert.equal(expectedLevel, level); 134 135 var expectedFlags = nsIAuthInformation.AUTH_HOST; 136 137 if (this.flags & FLAG_PREVIOUS_FAILED) { 138 expectedFlags |= nsIAuthInformation.PREVIOUS_FAILED; 139 } 140 141 if (this.flags & CROSS_ORIGIN) { 142 expectedFlags |= nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE; 143 } 144 145 if (isNTLM) { 146 expectedFlags |= nsIAuthInformation.NEED_DOMAIN; 147 } 148 149 const kAllKnownFlags = 127; // Don't fail test for newly added flags 150 Assert.equal(expectedFlags, authInfo.flags & kAllKnownFlags); 151 152 // eslint-disable-next-line no-nested-ternary 153 var expectedScheme = isNTLM ? "ntlm" : isDigest ? "digest" : "basic"; 154 Assert.equal(expectedScheme, authInfo.authenticationScheme); 155 156 // No passwords in the URL -> nothing should be prefilled 157 Assert.equal(authInfo.username, ""); 158 Assert.equal(authInfo.password, ""); 159 Assert.equal(authInfo.domain, ""); 160 161 if (this.flags & FLAG_RETURN_FALSE) { 162 this.flags |= FLAG_PREVIOUS_FAILED; 163 return false; 164 } 165 166 if (this.flags & FLAG_BOGUS_USER) { 167 this.user = "foo\nbar"; 168 } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) { 169 this.user = "é"; 170 } 171 172 authInfo.username = this.user; 173 if (this.flags & FLAG_WRONG_PASSWORD) { 174 authInfo.password = this.pass + ".wrong"; 175 this.flags |= FLAG_PREVIOUS_FAILED; 176 // Now clear the flag to avoid an infinite loop 177 this.flags &= ~FLAG_WRONG_PASSWORD; 178 } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) { 179 authInfo.password = "é"; 180 } else { 181 authInfo.password = this.pass; 182 this.flags &= ~FLAG_PREVIOUS_FAILED; 183 } 184 return true; 185 }, 186 187 asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) { 188 let self = this; 189 executeSoon(function () { 190 let ret = self.promptAuth(chan, lvl, info); 191 if (ret) { 192 cb.onAuthAvailable(ctx, info); 193 } else { 194 cb.onAuthCancelled(ctx, true); 195 } 196 }); 197 }, 198 }; 199 200 function Requestor(flags, versions) { 201 this.flags = flags; 202 this.versions = versions; 203 } 204 205 Requestor.prototype = { 206 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]), 207 208 getInterface: function requestor_gi(iid) { 209 if (this.versions & 1 && iid.equals(Ci.nsIAuthPrompt)) { 210 // Allow the prompt to store state by caching it here 211 if (!this.prompt1) { 212 this.prompt1 = new AuthPrompt1(this.flags); 213 } 214 return this.prompt1; 215 } 216 if (this.versions & 2 && iid.equals(Ci.nsIAuthPrompt2)) { 217 // Allow the prompt to store state by caching it here 218 if (!this.prompt2) { 219 this.prompt2 = new AuthPrompt2(this.flags); 220 } 221 return this.prompt2; 222 } 223 224 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 225 }, 226 227 prompt1: null, 228 prompt2: null, 229 }; 230 231 function RealmTestRequestor() { 232 this.promptRealm = ""; 233 } 234 235 RealmTestRequestor.prototype = { 236 QueryInterface: ChromeUtils.generateQI([ 237 "nsIInterfaceRequestor", 238 "nsIAuthPrompt2", 239 ]), 240 241 getInterface: function realmtest_interface(iid) { 242 if (iid.equals(Ci.nsIAuthPrompt2)) { 243 return this; 244 } 245 246 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 247 }, 248 249 promptAuth: function realmtest_checkAuth(channel, level, authInfo) { 250 this.promptRealm = authInfo.realm; 251 252 return false; 253 }, 254 255 asyncPromptAuth: function realmtest_async() { 256 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); 257 }, 258 }; 259 260 var listener = { 261 expectedCode: -1, // Uninitialized 262 nextTest: undefined, 263 expectRequestFail: false, 264 onStartRequest: function test_onStartR(request) { 265 try { 266 if ( 267 !this.expectRequestFail && 268 !Components.isSuccessCode(request.status) 269 ) { 270 do_throw("Channel should have a success code!"); 271 } 272 273 if (!(request instanceof Ci.nsIHttpChannel)) { 274 do_throw("Expecting an HTTP channel"); 275 } 276 277 if ( 278 Services.prefs.getBoolPref("network.auth.use_redirect_for_retries") && 279 // we should skip redirect check if we do not expect to succeed 280 this.expectedCode == 200 281 ) { 282 // ensure channel ids are initialized 283 Assert.notEqual(initialChannelId, -1); 284 285 // for each request we must use a unique channel ID. 286 // See Bug 1820807 287 var chan = request.QueryInterface(Ci.nsIIdentChannel); 288 Assert.notEqual(initialChannelId, chan.channelId); 289 } 290 291 Assert.equal(request.responseStatus, this.expectedCode); 292 // The request should be succeeded if we expect 200 293 Assert.equal(request.requestSucceeded, this.expectedCode == 200); 294 } catch (e) { 295 do_throw("Unexpected exception: " + e); 296 } 297 298 throw Components.Exception("", Cr.NS_ERROR_ABORT); 299 }, 300 301 onDataAvailable: function test_ODA() { 302 do_throw("Should not get any data!"); 303 }, 304 305 onStopRequest: function test_onStopR(request, status) { 306 Assert.equal(status, Cr.NS_ERROR_ABORT); 307 initialChannelId = -1; 308 this.nextTest(); 309 }, 310 }; 311 312 let ChannelEventSink1 = { 313 _classDescription: "WebRequest channel event sink", 314 _classID: Components.ID("115062f8-92f1-11e5-8b7f-08001110f7ec"), 315 _contractID: "@mozilla.org/webrequest/channel-event-sink;1", 316 317 QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink", "nsIFactory"]), 318 319 init() { 320 Components.manager 321 .QueryInterface(Ci.nsIComponentRegistrar) 322 .registerFactory( 323 this._classID, 324 this._classDescription, 325 this._contractID, 326 this 327 ); 328 }, 329 330 register() { 331 Services.catMan.addCategoryEntry( 332 "net-channel-event-sinks", 333 this._contractID, 334 this._contractID, 335 false, 336 true 337 ); 338 }, 339 340 unregister() { 341 Services.catMan.deleteCategoryEntry( 342 "net-channel-event-sinks", 343 this._contractID, 344 false 345 ); 346 }, 347 348 // nsIChannelEventSink implementation 349 asyncOnChannelRedirect(oldChannel, newChannel, flags, redirectCallback) { 350 // Abort the redirection 351 redirectCallback.onRedirectVerifyCallback(Cr.NS_ERROR_ABORT); 352 }, 353 354 // nsIFactory implementation 355 createInstance(iid) { 356 return this.QueryInterface(iid); 357 }, 358 }; 359 360 function makeChan( 361 url, 362 loadingUrl, 363 securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 364 contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER 365 ) { 366 var principal = Services.scriptSecurityManager.createContentPrincipal( 367 Services.io.newURI(loadingUrl), 368 {} 369 ); 370 return NetUtil.newChannel({ 371 uri: url, 372 loadingPrincipal: principal, 373 securityFlags, 374 contentPolicyType, 375 }); 376 } 377 378 var ChannelCreationObserver = { 379 QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), 380 observe(aSubject, aTopic) { 381 if (aTopic == "http-on-opening-request") { 382 initialChannelId = aSubject.QueryInterface(Ci.nsIIdentChannel).channelId; 383 } 384 }, 385 }; 386 387 var httpserv = null; 388 389 function setup() { 390 httpserv = new HttpServer(); 391 392 httpserv.registerPathHandler("/auth", authHandler); 393 httpserv.registerPathHandler( 394 "/auth/stored/wrong/credentials/", 395 authHandlerWrongStoredCredentials 396 ); 397 httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple); 398 httpserv.registerPathHandler("/auth/realm", authRealm); 399 httpserv.registerPathHandler("/auth/non_ascii", authNonascii); 400 httpserv.registerPathHandler("/auth/digest_md5", authDigestMD5); 401 httpserv.registerPathHandler("/auth/digest_md5sess", authDigestMD5sess); 402 httpserv.registerPathHandler("/auth/digest_sha256", authDigestSHA256); 403 httpserv.registerPathHandler("/auth/digest_sha256sess", authDigestSHA256sess); 404 httpserv.registerPathHandler("/auth/digest_sha256_md5", authDigestSHA256_MD5); 405 httpserv.registerPathHandler("/auth/digest_md5_sha256", authDigestMD5_SHA256); 406 httpserv.registerPathHandler( 407 "/auth/digest_md5_sha256_oneline", 408 authDigestMD5_SHA256_oneline 409 ); 410 httpserv.registerPathHandler("/auth/short_digest", authShortDigest); 411 httpserv.registerPathHandler("/largeRealm", largeRealm); 412 httpserv.registerPathHandler("/largeDomain", largeDomain); 413 414 httpserv.registerPathHandler("/corp-coep", corpAndCoep); 415 416 httpserv.start(-1); 417 418 registerCleanupFunction(async () => { 419 await httpserv.stop(); 420 }); 421 Services.obs.addObserver(ChannelCreationObserver, "http-on-opening-request"); 422 } 423 setup(); 424 425 async function openAndListen(chan) { 426 await new Promise(resolve => { 427 listener.nextTest = resolve; 428 chan.asyncOpen(listener); 429 }); 430 Cc["@mozilla.org/network/http-auth-manager;1"] 431 .getService(Ci.nsIHttpAuthManager) 432 .clearAll(); 433 } 434 435 async function test_noauth() { 436 var chan = makeChan(URL + "/auth", URL); 437 438 listener.expectedCode = 401; // Unauthorized 439 await openAndListen(chan); 440 } 441 442 async function test_returnfalse1() { 443 var chan = makeChan(URL + "/auth", URL); 444 445 chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 1); 446 listener.expectedCode = 401; // Unauthorized 447 await openAndListen(chan); 448 } 449 450 async function test_wrongpw1() { 451 var chan = makeChan(URL + "/auth", URL); 452 453 chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 1); 454 listener.expectedCode = 200; // OK 455 await openAndListen(chan); 456 } 457 458 async function test_prompt1() { 459 var chan = makeChan(URL + "/auth", URL); 460 461 chan.notificationCallbacks = new Requestor(0, 1); 462 listener.expectedCode = 200; // OK 463 await openAndListen(chan); 464 } 465 466 async function test_prompt1CrossOrigin() { 467 var chan = makeChan(URL + "/auth", "http://example.org"); 468 469 chan.notificationCallbacks = new Requestor(16, 1); 470 listener.expectedCode = 200; // OK 471 await openAndListen(chan); 472 } 473 474 async function test_prompt2CrossOrigin() { 475 var chan = makeChan(URL + "/auth", "http://example.org"); 476 477 chan.notificationCallbacks = new Requestor(16, 2); 478 listener.expectedCode = 200; // OK 479 await openAndListen(chan); 480 } 481 482 async function test_returnfalse2() { 483 var chan = makeChan(URL + "/auth", URL); 484 485 chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2); 486 listener.expectedCode = 401; // Unauthorized 487 await openAndListen(chan); 488 } 489 490 async function test_wrongpw2() { 491 var chan = makeChan(URL + "/auth", URL); 492 493 chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 2); 494 listener.expectedCode = 200; // OK 495 await openAndListen(chan); 496 } 497 498 var requestNum = 0; 499 var expectedRequestNum = 0; 500 async function test_wrong_stored_passwd() { 501 // tests that we don't retry auth requests for incorrect custom credentials passed during channel creation 502 requestNum = 0; 503 expectedRequestNum = 1; 504 var chan = makeChan(URL + "/auth/stored/wrong/credentials/", URL); 505 chan.nsIHttpChannel.setRequestHeader("Authorization", "wrong_cred", false); 506 chan.notificationCallbacks = new Requestor(0, 1); 507 listener.expectedCode = 401; // Unauthorized 508 509 await openAndListen(chan); 510 } 511 512 async function test_prompt2() { 513 var chan = makeChan(URL + "/auth", URL); 514 515 chan.notificationCallbacks = new Requestor(0, 2); 516 listener.expectedCode = 200; // OK 517 await openAndListen(chan); 518 } 519 520 async function test_ntlm() { 521 var chan = makeChan(URL + "/auth/ntlm/simple", URL); 522 523 chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2); 524 listener.expectedCode = 401; // Unauthorized 525 await openAndListen(chan); 526 } 527 528 async function test_basicrealm() { 529 var chan = makeChan(URL + "/auth/realm", URL); 530 531 let requestor = new RealmTestRequestor(); 532 chan.notificationCallbacks = requestor; 533 listener.expectedCode = 401; // Unauthorized 534 await openAndListen(chan); 535 Assert.equal(requestor.promptRealm, '"foo_bar'); 536 } 537 538 async function test_nonascii() { 539 var chan = makeChan(URL + "/auth/non_ascii", URL); 540 541 chan.notificationCallbacks = new Requestor(FLAG_NON_ASCII_USER_PASSWORD, 2); 542 listener.expectedCode = 200; // OK 543 await openAndListen(chan); 544 } 545 546 async function test_digest_noauth() { 547 var chan = makeChan(URL + "/auth/digest_md5", URL); 548 549 // chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2); 550 listener.expectedCode = 401; // Unauthorized 551 await openAndListen(chan); 552 } 553 554 async function test_digest_md5() { 555 var chan = makeChan(URL + "/auth/digest_md5", URL); 556 557 chan.notificationCallbacks = new Requestor(0, 2); 558 listener.expectedCode = 200; // OK 559 await openAndListen(chan); 560 } 561 562 add_task( 563 { pref_set: [["network.auth.use_redirect_for_retries", true]] }, 564 async function test_digest_md5_redirect_veto() { 565 ChannelEventSink1.init(); 566 ChannelEventSink1.register(); 567 var chan = makeChan(URL + "/auth/digest_md5", URL); 568 569 chan.notificationCallbacks = new Requestor(0, 1); 570 listener.expectedCode = 401; // Unauthorized 571 listener.expectRequestFail = true; 572 await openAndListen(chan); 573 ChannelEventSink1.unregister(); 574 listener.expectRequestFail = false; 575 } 576 ); 577 578 async function test_digest_md5sess() { 579 var chan = makeChan(URL + "/auth/digest_md5sess", URL); 580 581 chan.notificationCallbacks = new Requestor(0, 2); 582 listener.expectedCode = 200; // OK 583 await openAndListen(chan); 584 } 585 586 async function test_digest_sha256() { 587 var chan = makeChan(URL + "/auth/digest_sha256", URL); 588 589 chan.notificationCallbacks = new Requestor(0, 2); 590 listener.expectedCode = 200; // OK 591 await openAndListen(chan); 592 } 593 594 async function test_digest_sha256sess() { 595 var chan = makeChan(URL + "/auth/digest_sha256sess", URL); 596 597 chan.notificationCallbacks = new Requestor(0, 2); 598 listener.expectedCode = 200; // OK 599 await openAndListen(chan); 600 } 601 602 async function test_digest_sha256_md5() { 603 var chan = makeChan(URL + "/auth/digest_sha256_md5", URL); 604 605 chan.notificationCallbacks = new Requestor(0, 2); 606 listener.expectedCode = 200; // OK 607 await openAndListen(chan); 608 } 609 610 async function test_digest_md5_sha256() { 611 var chan = makeChan(URL + "/auth/digest_md5_sha256", URL); 612 613 chan.notificationCallbacks = new Requestor(0, 2); 614 listener.expectedCode = 200; // OK 615 await openAndListen(chan); 616 } 617 618 async function test_digest_md5_sha256_oneline() { 619 var chan = makeChan(URL + "/auth/digest_md5_sha256_oneline", URL); 620 621 chan.notificationCallbacks = new Requestor(0, 2); 622 listener.expectedCode = 200; // OK 623 await openAndListen(chan); 624 } 625 626 async function test_digest_bogus_user() { 627 var chan = makeChan(URL + "/auth/digest_md5", URL); 628 chan.notificationCallbacks = new Requestor(FLAG_BOGUS_USER, 2); 629 listener.expectedCode = 401; // unauthorized 630 await openAndListen(chan); 631 } 632 633 // Test header "WWW-Authenticate: Digest" - bug 1338876. 634 async function test_short_digest() { 635 var chan = makeChan(URL + "/auth/short_digest", URL); 636 chan.notificationCallbacks = new Requestor(FLAG_NO_REALM, 2); 637 listener.expectedCode = 401; // OK 638 await openAndListen(chan); 639 } 640 641 // Test that COOP/COEP are processed even though asyncPromptAuth is cancelled. 642 async function test_corp_coep() { 643 var chan = makeChan( 644 URL + "/corp-coep", 645 URL, 646 Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT, 647 Ci.nsIContentPolicy.TYPE_DOCUMENT 648 ); 649 650 chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2); 651 listener.expectedCode = 401; // OK 652 await openAndListen(chan); 653 654 Assert.equal( 655 chan.getResponseHeader("cross-origin-embedder-policy"), 656 "require-corp" 657 ); 658 Assert.equal( 659 chan.getResponseHeader("cross-origin-opener-policy"), 660 "same-origin" 661 ); 662 } 663 664 // XXX(valentin): this makes tests fail if it's not run last. Why? 665 async function test_nonascii_xhr() { 666 await new Promise(resolve => { 667 let xhr = new XMLHttpRequest(); 668 xhr.open("GET", URL + "/auth/non_ascii", true, "é", "é"); 669 xhr.onreadystatechange = function () { 670 if (xhr.readyState == 4) { 671 Assert.equal(xhr.status, 200); 672 resolve(); 673 xhr.onreadystatechange = null; 674 } 675 }; 676 xhr.send(null); 677 }); 678 } 679 680 let auth_tests = [ 681 test_noauth, 682 test_returnfalse1, 683 test_wrongpw1, 684 test_wrong_stored_passwd, 685 test_prompt1, 686 test_prompt1CrossOrigin, 687 test_prompt2CrossOrigin, 688 test_returnfalse2, 689 test_wrongpw2, 690 test_prompt2, 691 test_ntlm, 692 test_basicrealm, 693 test_nonascii, 694 test_digest_noauth, 695 test_digest_md5, 696 test_digest_md5sess, 697 test_digest_sha256, 698 test_digest_sha256sess, 699 test_digest_sha256_md5, 700 test_digest_md5_sha256, 701 test_digest_md5_sha256_oneline, 702 test_digest_bogus_user, 703 test_short_digest, 704 test_corp_coep, 705 test_nonascii_xhr, 706 ]; 707 708 for (let auth_test of auth_tests) { 709 add_task( 710 { pref_set: [["network.auth.use_redirect_for_retries", false]] }, 711 auth_test 712 ); 713 } 714 715 for (let auth_test of auth_tests) { 716 add_task( 717 { pref_set: [["network.auth.use_redirect_for_retries", true]] }, 718 auth_test 719 ); 720 } 721 722 // PATH HANDLERS 723 724 // /auth 725 function authHandler(metadata, response) { 726 // btoa("guest:guest"), but that function is not available here 727 var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q="; 728 729 var body; 730 if ( 731 metadata.hasHeader("Authorization") && 732 metadata.getHeader("Authorization") == expectedHeader 733 ) { 734 response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); 735 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); 736 737 body = "success"; 738 } else { 739 // didn't know guest:guest, failure 740 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 741 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); 742 743 body = "failed"; 744 } 745 746 response.bodyOutputStream.write(body, body.length); 747 } 748 749 function authHandlerWrongStoredCredentials(metadata, response) { 750 var body; 751 if (++requestNum > expectedRequestNum) { 752 response.setStatusLine(metadata.httpVersion, 500, ""); 753 } else { 754 response.setStatusLine( 755 metadata.httpVersion, 756 401, 757 "Unauthorized" + requestNum 758 ); 759 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); 760 } 761 762 body = "failed"; 763 response.bodyOutputStream.write(body, body.length); 764 } 765 766 // /auth/ntlm/simple 767 function authNtlmSimple(metadata, response) { 768 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 769 response.setHeader( 770 "WWW-Authenticate", 771 "NTLM" /* + ' realm="secret"' */, 772 false 773 ); 774 775 var body = 776 "NOTE: This just sends an NTLM challenge, it never\n" + 777 "accepts the authentication. It also closes\n" + 778 "the connection after sending the challenge\n"; 779 780 response.bodyOutputStream.write(body, body.length); 781 } 782 783 // /auth/realm 784 function authRealm(metadata, response) { 785 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 786 response.setHeader("WWW-Authenticate", 'Basic realm="\\"f\\oo_bar"', false); 787 var body = "success"; 788 789 response.bodyOutputStream.write(body, body.length); 790 } 791 792 // /auth/nonAscii 793 function authNonascii(metadata, response) { 794 // btoa("é:é"), but that function is not available here 795 var expectedHeader = "Basic w6k6w6k="; 796 797 var body; 798 if ( 799 metadata.hasHeader("Authorization") && 800 metadata.getHeader("Authorization") == expectedHeader 801 ) { 802 response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); 803 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); 804 805 // Use correct XML syntax since this function is also used for testing XHR. 806 body = "<?xml version='1.0' ?><root>success</root>"; 807 } else { 808 // didn't know é:é, failure 809 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 810 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); 811 812 body = "<?xml version='1.0' ?><root>failed</root>"; 813 } 814 815 response.bodyOutputStream.write(body, body.length); 816 } 817 818 function corpAndCoep(metadata, response) { 819 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 820 response.setHeader("cross-origin-embedder-policy", "require-corp"); 821 response.setHeader("cross-origin-opener-policy", "same-origin"); 822 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); 823 } 824 825 // 826 // Digest functions 827 // 828 function bytesFromString(str) { 829 return new TextEncoder().encode(str); 830 } 831 832 // return the two-digit hexadecimal code for a byte 833 function toHexString(charCode) { 834 return ("0" + charCode.toString(16)).slice(-2); 835 } 836 837 function HMD5(str) { 838 var data = bytesFromString(str); 839 var ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); 840 ch.init(Ci.nsICryptoHash.MD5); 841 ch.update(data, data.length); 842 var hash = ch.finish(false); 843 return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join(""); 844 } 845 846 function HSHA256(str) { 847 var data = bytesFromString(str); 848 var ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); 849 ch.init(Ci.nsICryptoHash.SHA256); 850 ch.update(data, data.length); 851 var hash = ch.finish(false); 852 return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join(""); 853 } 854 855 // 856 // Digest handler 857 // 858 // /auth/digest 859 function authDigestMD5_helper(metadata, response, test_name) { 860 var nonce = "6f93719059cf8d568005727f3250e798"; 861 var opaque = "1234opaque1234"; 862 var body; 863 var send_401 = 0; 864 // check creds if we have them 865 if (metadata.hasHeader("Authorization")) { 866 var cnonceRE = /cnonce="(\w+)"/; 867 var responseRE = /response="(\w+)"/; 868 var usernameRE = /username="(\w+)"/; 869 var algorithmRE = /algorithm=([\w-]+)/; 870 var auth = metadata.getHeader("Authorization"); 871 var cnonce = auth.match(cnonceRE)[1]; 872 var clientDigest = auth.match(responseRE)[1]; 873 var username = auth.match(usernameRE)[1]; 874 var algorithm = auth.match(algorithmRE)[1]; 875 var nc = "00000001"; 876 877 if (username != "guest") { 878 response.setStatusLine(metadata.httpVersion, 400, "bad request"); 879 body = "should never get here"; 880 } else if ( 881 algorithm != null && 882 algorithm != "MD5" && 883 algorithm != "MD5-sess" 884 ) { 885 response.setStatusLine(metadata.httpVersion, 400, "bad request"); 886 body = "Algorithm must be same as provided in WWW-Authenticate header"; 887 } else { 888 // see RFC2617 for the description of this calculation 889 var A1 = "guest:secret:guest"; 890 if (algorithm == "MD5-sess") { 891 A1 = [HMD5(A1), nonce, cnonce].join(":"); 892 } 893 var A2 = "GET:/auth/" + test_name; 894 var noncebits = [nonce, nc, cnonce, "auth", HMD5(A2)].join(":"); 895 var digest = HMD5([HMD5(A1), noncebits].join(":")); 896 897 if (clientDigest == digest) { 898 response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); 899 body = "success"; 900 } else { 901 send_401 = 1; 902 body = "auth failed"; 903 } 904 } 905 } else { 906 // no header, send one 907 send_401 = 1; 908 body = "failed, no header"; 909 } 910 911 if (send_401) { 912 var authenticate_md5 = 913 'Digest realm="secret", domain="/", qop=auth,' + 914 'algorithm=MD5, nonce="' + 915 nonce + 916 '" opaque="' + 917 opaque + 918 '"'; 919 var authenticate_md5sess = 920 'Digest realm="secret", domain="/", qop=auth,' + 921 'algorithm=MD5, nonce="' + 922 nonce + 923 '" opaque="' + 924 opaque + 925 '"'; 926 if (test_name == "digest_md5") { 927 response.setHeader("WWW-Authenticate", authenticate_md5, false); 928 } else if (test_name == "digest_md5sess") { 929 response.setHeader("WWW-Authenticate", authenticate_md5sess, false); 930 } 931 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 932 } 933 934 response.bodyOutputStream.write(body, body.length); 935 } 936 937 function authDigestMD5(metadata, response) { 938 authDigestMD5_helper(metadata, response, "digest_md5"); 939 } 940 941 function authDigestMD5sess(metadata, response) { 942 authDigestMD5_helper(metadata, response, "digest_md5sess"); 943 } 944 945 function authDigestSHA256_helper(metadata, response, test_name) { 946 var nonce = "6f93719059cf8d568005727f3250e798"; 947 var opaque = "1234opaque1234"; 948 var body; 949 var send_401 = 0; 950 // check creds if we have them 951 if (metadata.hasHeader("Authorization")) { 952 var cnonceRE = /cnonce="(\w+)"/; 953 var responseRE = /response="(\w+)"/; 954 var usernameRE = /username="(\w+)"/; 955 var algorithmRE = /algorithm=([\w-]+)/; 956 var auth = metadata.getHeader("Authorization"); 957 var cnonce = auth.match(cnonceRE)[1]; 958 var clientDigest = auth.match(responseRE)[1]; 959 var username = auth.match(usernameRE)[1]; 960 var algorithm = auth.match(algorithmRE)[1]; 961 var nc = "00000001"; 962 963 if (username != "guest") { 964 response.setStatusLine(metadata.httpVersion, 400, "bad request"); 965 body = "should never get here"; 966 } else if (algorithm != "SHA-256" && algorithm != "SHA-256-sess") { 967 response.setStatusLine(metadata.httpVersion, 400, "bad request"); 968 body = "Algorithm must be same as provided in WWW-Authenticate header"; 969 } else { 970 // see RFC7616 for the description of this calculation 971 var A1 = "guest:secret:guest"; 972 if (algorithm == "SHA-256-sess") { 973 A1 = [HSHA256(A1), nonce, cnonce].join(":"); 974 } 975 var A2 = "GET:/auth/" + test_name; 976 var noncebits = [nonce, nc, cnonce, "auth", HSHA256(A2)].join(":"); 977 var digest = HSHA256([HSHA256(A1), noncebits].join(":")); 978 979 if (clientDigest == digest) { 980 response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); 981 body = "success"; 982 } else { 983 send_401 = 1; 984 body = "auth failed"; 985 } 986 } 987 } else { 988 // no header, send one 989 send_401 = 1; 990 body = "failed, no header"; 991 } 992 993 if (send_401) { 994 var authenticate_sha256 = 995 'Digest realm="secret", domain="/", qop=auth, ' + 996 'algorithm=SHA-256, nonce="' + 997 nonce + 998 '", opaque="' + 999 opaque + 1000 '"'; 1001 var authenticate_sha256sess = 1002 'Digest realm="secret", domain="/", qop=auth, ' + 1003 'algorithm=SHA-256-sess, nonce="' + 1004 nonce + 1005 '", opaque="' + 1006 opaque + 1007 '"'; 1008 var authenticate_md5 = 1009 'Digest realm="secret", domain="/", qop=auth, ' + 1010 'algorithm=MD5, nonce="' + 1011 nonce + 1012 '", opaque="' + 1013 opaque + 1014 '"'; 1015 if (test_name == "digest_sha256") { 1016 response.setHeader("WWW-Authenticate", authenticate_sha256, false); 1017 } else if (test_name == "digest_sha256sess") { 1018 response.setHeader("WWW-Authenticate", authenticate_sha256sess, false); 1019 } else if (test_name == "digest_md5_sha256") { 1020 response.setHeader("WWW-Authenticate", authenticate_md5, false); 1021 response.setHeader("WWW-Authenticate", authenticate_sha256, true); 1022 } else if (test_name == "digest_md5_sha256_oneline") { 1023 response.setHeader( 1024 "WWW-Authenticate", 1025 authenticate_md5 + " " + authenticate_sha256, 1026 false 1027 ); 1028 } else if (test_name == "digest_sha256_md5") { 1029 response.setHeader("WWW-Authenticate", authenticate_sha256, false); 1030 response.setHeader("WWW-Authenticate", authenticate_md5, true); 1031 } 1032 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 1033 } 1034 1035 response.bodyOutputStream.write(body, body.length); 1036 } 1037 1038 function authDigestSHA256(metadata, response) { 1039 authDigestSHA256_helper(metadata, response, "digest_sha256"); 1040 } 1041 1042 function authDigestSHA256sess(metadata, response) { 1043 authDigestSHA256_helper(metadata, response, "digest_sha256sess"); 1044 } 1045 1046 function authDigestSHA256_MD5(metadata, response) { 1047 authDigestSHA256_helper(metadata, response, "digest_sha256_md5"); 1048 } 1049 1050 function authDigestMD5_SHA256(metadata, response) { 1051 authDigestSHA256_helper(metadata, response, "digest_md5_sha256"); 1052 } 1053 1054 function authDigestMD5_SHA256_oneline(metadata, response) { 1055 authDigestSHA256_helper(metadata, response, "digest_md5_sha256_oneline"); 1056 } 1057 1058 function authShortDigest(metadata, response) { 1059 // no header, send one 1060 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 1061 response.setHeader("WWW-Authenticate", "Digest", false); 1062 } 1063 1064 let buildLargePayload = (function () { 1065 let size = 33 * 1024; 1066 let ret = ""; 1067 return function () { 1068 // Return cached value. 1069 if (ret.length) { 1070 return ret; 1071 } 1072 for (let i = 0; i < size; i++) { 1073 ret += "a"; 1074 } 1075 return ret; 1076 }; 1077 })(); 1078 1079 function largeRealm(metadata, response) { 1080 // test > 32KB realm tokens 1081 var body; 1082 1083 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 1084 response.setHeader( 1085 "WWW-Authenticate", 1086 'Digest realm="' + buildLargePayload() + '", domain="foo"' 1087 ); 1088 1089 body = "need to authenticate"; 1090 response.bodyOutputStream.write(body, body.length); 1091 } 1092 1093 function largeDomain(metadata, response) { 1094 // test > 32KB domain tokens 1095 var body; 1096 1097 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 1098 response.setHeader( 1099 "WWW-Authenticate", 1100 'Digest realm="foo", domain="' + buildLargePayload() + '"' 1101 ); 1102 1103 body = "need to authenticate"; 1104 response.bodyOutputStream.write(body, body.length); 1105 } 1106 1107 add_task(async function test_large_realm() { 1108 var chan = makeChan(URL + "/largeRealm", URL); 1109 1110 listener.expectedCode = 401; // Unauthorized 1111 await openAndListen(chan); 1112 }); 1113 1114 add_task(async function test_large_domain() { 1115 var chan = makeChan(URL + "/largeDomain", URL); 1116 1117 listener.expectedCode = 401; // Unauthorized 1118 await openAndListen(chan); 1119 }); 1120 1121 async function add_parse_realm_testcase(testcase) { 1122 httpserv.registerPathHandler("/parse_realm", (metadata, response) => { 1123 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 1124 response.setHeader("WWW-Authenticate", testcase.input, false); 1125 1126 let body = "failed"; 1127 response.bodyOutputStream.write(body, body.length); 1128 }); 1129 1130 let chan = makeChan(URL + "/parse_realm", URL); 1131 let requestor = new RealmTestRequestor(); 1132 chan.notificationCallbacks = requestor; 1133 1134 listener.expectedCode = 401; 1135 await openAndListen(chan); 1136 Assert.equal(requestor.promptRealm, testcase.realm); 1137 } 1138 1139 add_task(async function simplebasic() { 1140 await add_parse_realm_testcase({ 1141 input: `Basic realm="foo"`, 1142 scheme: `Basic`, 1143 realm: `foo`, 1144 }); 1145 }); 1146 1147 add_task(async function simplebasiclf() { 1148 await add_parse_realm_testcase({ 1149 input: `Basic\r\n realm="foo"`, 1150 scheme: `Basic`, 1151 realm: `foo`, 1152 }); 1153 }); 1154 1155 add_task(async function simplebasicucase() { 1156 await add_parse_realm_testcase({ 1157 input: `BASIC REALM="foo"`, 1158 scheme: `Basic`, 1159 realm: `foo`, 1160 }); 1161 }); 1162 1163 add_task(async function simplebasictok() { 1164 await add_parse_realm_testcase({ 1165 input: `Basic realm=foo`, 1166 scheme: `Basic`, 1167 realm: `foo`, 1168 }); 1169 }); 1170 1171 add_task(async function simplebasictokbs() { 1172 await add_parse_realm_testcase({ 1173 input: `Basic realm=\\f\\o\\o`, 1174 scheme: `Basic`, 1175 realm: `\\foo`, 1176 }); 1177 }); 1178 1179 add_task(async function simplebasicsq() { 1180 await add_parse_realm_testcase({ 1181 input: `Basic realm='foo'`, 1182 scheme: `Basic`, 1183 realm: `'foo'`, 1184 }); 1185 }); 1186 1187 add_task(async function simplebasicpct() { 1188 await add_parse_realm_testcase({ 1189 input: `Basic realm="foo%20bar"`, 1190 scheme: `Basic`, 1191 realm: `foo%20bar`, 1192 }); 1193 }); 1194 1195 add_task(async function simplebasiccomma() { 1196 await add_parse_realm_testcase({ 1197 input: `Basic , realm="foo"`, 1198 scheme: `Basic`, 1199 realm: `foo`, 1200 }); 1201 }); 1202 1203 add_task(async function simplebasiccomma2() { 1204 await add_parse_realm_testcase({ 1205 input: `Basic, realm="foo"`, 1206 scheme: `Basic`, 1207 realm: ``, 1208 }); 1209 }); 1210 1211 add_task(async function simplebasicnorealm() { 1212 await add_parse_realm_testcase({ 1213 input: `Basic`, 1214 scheme: `Basic`, 1215 realm: ``, 1216 }); 1217 }); 1218 1219 add_task(async function simplebasic2realms() { 1220 await add_parse_realm_testcase({ 1221 input: `Basic realm="foo", realm="bar"`, 1222 scheme: `Basic`, 1223 realm: `foo`, 1224 }); 1225 }); 1226 1227 add_task(async function simplebasicwsrealm() { 1228 await add_parse_realm_testcase({ 1229 input: `Basic realm = "foo"`, 1230 scheme: `Basic`, 1231 realm: `foo`, 1232 }); 1233 }); 1234 1235 add_task(async function simplebasicrealmsqc() { 1236 await add_parse_realm_testcase({ 1237 input: `Basic realm="\\f\\o\\o"`, 1238 scheme: `Basic`, 1239 realm: `foo`, 1240 }); 1241 }); 1242 1243 add_task(async function simplebasicrealmsqc2() { 1244 await add_parse_realm_testcase({ 1245 input: `Basic realm="\\"foo\\""`, 1246 scheme: `Basic`, 1247 realm: `"foo"`, 1248 }); 1249 }); 1250 1251 add_task(async function simplebasicnewparam1() { 1252 await add_parse_realm_testcase({ 1253 input: `Basic realm="foo", bar="xyz",, a=b,,,c=d`, 1254 scheme: `Basic`, 1255 realm: `foo`, 1256 }); 1257 }); 1258 1259 add_task(async function simplebasicnewparam2() { 1260 await add_parse_realm_testcase({ 1261 input: `Basic bar="xyz", realm="foo"`, 1262 scheme: `Basic`, 1263 realm: `foo`, 1264 }); 1265 }); 1266 1267 add_task(async function simplebasicrealmiso88591() { 1268 await add_parse_realm_testcase({ 1269 input: `Basic realm="foo-ä"`, 1270 scheme: `Basic`, 1271 realm: `foo-ä`, 1272 }); 1273 }); 1274 1275 add_task(async function simplebasicrealmutf8() { 1276 await add_parse_realm_testcase({ 1277 input: `Basic realm="foo-ä"`, 1278 scheme: `Basic`, 1279 realm: `foo-ä`, 1280 }); 1281 }); 1282 1283 add_task(async function simplebasicrealmrfc2047() { 1284 await add_parse_realm_testcase({ 1285 input: `Basic realm="=?ISO-8859-1?Q?foo-=E4?="`, 1286 scheme: `Basic`, 1287 realm: `=?ISO-8859-1?Q?foo-=E4?=`, 1288 }); 1289 }); 1290 1291 add_task(async function multibasicunknown() { 1292 await add_parse_realm_testcase({ 1293 input: `Basic realm="basic", Newauth realm="newauth"`, 1294 scheme: `Basic`, 1295 realm: `basic`, 1296 }); 1297 }); 1298 1299 add_task(async function multibasicunknownnoparam() { 1300 await add_parse_realm_testcase({ 1301 input: `Basic realm="basic", Newauth`, 1302 scheme: `Basic`, 1303 realm: `basic`, 1304 }); 1305 }); 1306 1307 add_task(async function multibasicunknown2() { 1308 await add_parse_realm_testcase({ 1309 input: `Newauth realm="newauth", Basic realm="basic"`, 1310 scheme: `Basic`, 1311 realm: `basic`, 1312 }); 1313 }); 1314 1315 add_task(async function multibasicunknown2np() { 1316 await add_parse_realm_testcase({ 1317 input: `Newauth, Basic realm="basic"`, 1318 scheme: `Basic`, 1319 realm: `basic`, 1320 }); 1321 }); 1322 1323 add_task(async function multibasicunknown2mf() { 1324 httpserv.registerPathHandler("/parse_realm", (metadata, response) => { 1325 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); 1326 response.setHeader("WWW-Authenticate", `Newauth realm="newauth"`, false); 1327 response.setHeader("WWW-Authenticate", `Basic realm="basic"`, false); 1328 1329 let body = "failed"; 1330 response.bodyOutputStream.write(body, body.length); 1331 }); 1332 1333 let chan = makeChan(URL + "/parse_realm", URL); 1334 let requestor = new RealmTestRequestor(); 1335 chan.notificationCallbacks = requestor; 1336 1337 listener.expectedCode = 401; 1338 await openAndListen(chan); 1339 Assert.equal(requestor.promptRealm, "basic"); 1340 }); 1341 1342 add_task(async function multibasicempty() { 1343 await add_parse_realm_testcase({ 1344 input: `,Basic realm="basic"`, 1345 scheme: `Basic`, 1346 realm: `basic`, 1347 }); 1348 }); 1349 1350 add_task(async function multibasicqs() { 1351 await add_parse_realm_testcase({ 1352 input: `Newauth realm="apps", type=1, title="Login to \"apps\"", Basic realm="simple"`, 1353 scheme: `Basic`, 1354 realm: `simple`, 1355 }); 1356 }); 1357 1358 add_task(async function multidisgscheme() { 1359 await add_parse_realm_testcase({ 1360 input: `Newauth realm="Newauth Realm", basic=foo, Basic realm="Basic Realm"`, 1361 scheme: `Basic`, 1362 realm: `Basic Realm`, 1363 }); 1364 }); 1365 1366 add_task(async function unknown() { 1367 await add_parse_realm_testcase({ 1368 input: `Newauth param="value"`, 1369 scheme: `Basic`, 1370 realm: ``, 1371 }); 1372 }); 1373 1374 add_task(async function parametersnotrequired() { 1375 await add_parse_realm_testcase({ input: `A, B`, scheme: `Basic`, realm: `` }); 1376 }); 1377 1378 add_task(async function disguisedrealm() { 1379 await add_parse_realm_testcase({ 1380 input: `Basic foo="realm=nottherealm", realm="basic"`, 1381 scheme: `Basic`, 1382 realm: `basic`, 1383 }); 1384 }); 1385 1386 add_task(async function disguisedrealm2() { 1387 await add_parse_realm_testcase({ 1388 input: `Basic nottherealm="nottherealm", realm="basic"`, 1389 scheme: `Basic`, 1390 realm: `basic`, 1391 }); 1392 }); 1393 1394 add_task(async function missingquote() { 1395 await add_parse_realm_testcase({ 1396 input: `Basic realm="basic`, 1397 scheme: `Basic`, 1398 realm: `basic`, 1399 }); 1400 });