Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Tests nsIMailAuthModule.getNextToken with server challenges that are not
* valid base64.
*/
// An NTLM type 2 (challenge) message without target info.
const NTLM_CHALLENGE =
"TlRMTVNTUAACAAAAAAAAAAAoAAABggAAASNFZ4mrze8AAAAAAAAAAAAAAAAAAAAA";
/**
* Creates an NTLM auth module that has already produced its negotiate message.
*
* @returns {nsIMailAuthModule}
*/
function createNtlmModule() {
const authModule = Cc["@mozilla.org/mail/auth-module;1"].createInstance(
Ci.nsIMailAuthModule
);
authModule.init("ntlm", "", 0, "", "user", "password");
Assert.ok(
atob(authModule.getNextToken("")).startsWith("NTLMSSP\0\x01"),
"first token should be an NTLM negotiate message"
);
return authModule;
}
add_task(function testValidChallenge() {
// NTLMv2 requires target info in the challenge.
Services.prefs.setBoolPref("network.auth.force-generic-ntlm-v1", true);
try {
const authModule = createNtlmModule();
Assert.ok(
atob(authModule.getNextToken(NTLM_CHALLENGE)).startsWith("NTLMSSP\0\x03"),
"answer to the challenge should be an NTLM authenticate message"
);
} finally {
Services.prefs.clearUserPref("network.auth.force-generic-ntlm-v1");
}
});
add_task(function testPaddingOnlyChallenge() {
for (const challenge of ["=", "===="]) {
const authModule = createNtlmModule();
Assert.throws(
() => authModule.getNextToken(challenge),
/NS_ERROR/,
`challenge ${challenge} should be rejected`
);
}
});
add_task(function testInvalidChallenge() {
for (const challenge of ["!!!!", "TlRMT"]) {
const authModule = createNtlmModule();
Assert.throws(
() => authModule.getNextToken(challenge),
/NS_ERROR_FAILURE/,
`challenge ${challenge} should be rejected`
);
}
});
/**
* Creates an NTLM type 2 message header whose target info follows it.
*
* @param {number} targetInfoLength - The length claimed for the target info.
* @returns {Uint8Array} The header.
*/
function createNtlmChallengeHeader(targetInfoLength) {
const header = new Uint8Array(48);
header.set([..."NTLMSSP\0\x02"].map(c => c.charCodeAt(0)));
// Flags: NTLMSSP_NEGOTIATE_UNICODE and NTLMSSP_NEGOTIATE_NTLM.
header.set([0x01, 0x82], 20);
header.set([1, 2, 3, 4, 5, 6, 7, 8], 24);
const view = new DataView(header.buffer);
view.setUint16(40, targetInfoLength, true);
view.setUint16(42, targetInfoLength, true);
view.setUint32(44, header.length, true);
return header;
}
/**
* Creates an NTLM type 2 message whose target info follows the header.
*
* @param {number} targetInfoLength - The length claimed for the target info.
* @param {string} targetInfo - The base64 appended to the header, which may
* not be valid.
* @returns {string} The base64 encoded message.
*/
function createNtlmChallengeWithTargetInfo(targetInfoLength, targetInfo) {
const header = createNtlmChallengeHeader(targetInfoLength);
return btoa(String.fromCharCode(...header)) + targetInfo;
}
add_task(function testPaddedChallenge() {
// 50 bytes, so the base64 ends with padding.
const challenge = btoa(
String.fromCharCode(...createNtlmChallengeHeader(2), 0, 0)
);
Assert.ok(challenge.endsWith("="), "challenge should end with padding");
const authModule = createNtlmModule();
Assert.ok(
atob(authModule.getNextToken(challenge)).startsWith("NTLMSSP\0\x03"),
"answer to a padded challenge should be an NTLM authenticate message"
);
});
add_task(function testUndecodedTargetInfo() {
// NTLMv2, the default, echoes the target info back to the server.
const authModule = createNtlmModule();
Assert.ok(
atob(
authModule.getNextToken(
createNtlmChallengeWithTargetInfo(1024, "A".repeat(1400))
)
).startsWith("NTLMSSP\0\x03"),
"answer to a decodable challenge should be an NTLM authenticate message"
);
// Decoding stops at the "!", so the target info would be undecoded memory.
const leakingAuthModule = createNtlmModule();
Assert.throws(
() =>
leakingAuthModule.getNextToken(
createNtlmChallengeWithTargetInfo(1024, "!" + "A".repeat(1399))
),
/NS_ERROR_FAILURE/,
"challenge with undecodable target info should be rejected"
);
});
add_task(function testNotInitialized() {
const authModule = Cc["@mozilla.org/mail/auth-module;1"].createInstance(
Ci.nsIMailAuthModule
);
Assert.throws(
() => authModule.getNextToken(""),
/NS_ERROR_NOT_INITIALIZED/,
"getNextToken should fail before init"
);
});