win-sso
Version:
NTLM single-sign-on for Node.js. Only Windows OS supported.
160 lines (159 loc) • 6.82 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WinSso = void 0;
const debug_logger_1 = require("./utils/debug.logger");
const path_1 = __importDefault(require("path"));
let winSsoAddon;
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
winSsoAddon = require("node-gyp-build")(path_1.default.join(__dirname, ".."));
(0, debug_logger_1.debug)("Loaded win-sso native module");
}
catch (_a) {
(0, debug_logger_1.debug)("Could not load win-sso native module");
}
/**
* Creates authentication tokens for NTLM or Negotiate handshake using the executing users credentials.
*/
class WinSso {
/**
* Creates an authentication context for SSO.
* This allocates memory buffers, the freeAuthContext method should be called
* to free them (on error or after authentication is no longer needed)
* @param securityPackage The name of the security package (NTLM or Negotiate)
* @param targetHost The FQDN hostname of the target (optional for NTLM, required for Kerberos)
* @param peerCert The certificate of the target server
* (optional, for HTTPS channel binding)
* @param flags Flags to set in the authentication context
* If not set, NTML defaults to no flags, while Negotiate defaults to ISC_REQ_MUTUAL_AUTH | ISC_REQ_SEQUENCE_DETECT
* (optional, allows customizing security features)
*/
constructor(securityPackage, targetHost, peerCert, flags) {
this.securityPackage = securityPackage;
let applicationData;
if (!targetHost) {
targetHost = "";
}
if (peerCert) {
applicationData = this.getChannelBindingsApplicationData(peerCert);
}
else {
applicationData = Buffer.alloc(0);
}
this.authContextId = winSsoAddon.createAuthContext(securityPackage, targetHost, applicationData, flags);
}
/**
* Retrieves the username of the logged in user
* @returns user name including domain
*/
static getLogonUserName() {
return winSsoAddon.getLogonUserName();
}
/**
* Transforms target TLS certificate into a channel binding application data buffer
* @param peerCert Target TLS certificate
* @returns Application data buffer
*/
getChannelBindingsApplicationData(peerCert) {
const hash = peerCert.fingerprint256.replace(/:/g, "");
const hashBuf = Buffer.from(hash, "hex");
const tlsServerEndPoint = "tls-server-end-point:";
const applicationDataBuffer = Buffer.alloc(tlsServerEndPoint.length + hashBuf.length);
applicationDataBuffer.write(tlsServerEndPoint, 0, "ascii");
hashBuf.copy(applicationDataBuffer, tlsServerEndPoint.length);
return applicationDataBuffer;
}
/**
* Releases all allocated resources for the authorization context.
* Should be called when the context is no longer required, such as when the
* socket was closed.
*/
freeAuthContext() {
winSsoAddon.freeAuthContext(this.authContextId);
}
/**
* Creates an authentication request token
* @returns Raw token buffer
*/
createAuthRequest() {
const token = winSsoAddon.createAuthRequest(this.authContextId);
(0, debug_logger_1.debug)("Created " + this.securityPackage + " authentication request token", token.toString("base64"));
return token;
}
/**
* Creates an authentication request header
* @returns The www-authenticate header
*/
createAuthRequestHeader() {
const header = this.securityPackage + " " + this.createAuthRequest().toString("base64");
return header;
}
/**
* Creates an authentication response token
* @param inTokenHeader The www-authentication header received from the target
* in response to the authentication request
* @returns Raw token buffer. May be empty if Negotiate handshake is complete.
*/
createAuthResponse(inTokenHeader) {
(0, debug_logger_1.debug)("Received www-authentication response", inTokenHeader);
const packageMatch = new RegExp("^" + this.securityPackage + "\\s([^,\\s]+)").exec(inTokenHeader);
if (!packageMatch) {
throw new Error("Invalid input token, missing " +
this.securityPackage +
" prefix: " +
inTokenHeader);
}
const inToken = Buffer.from(packageMatch[1], "base64");
try {
const token = winSsoAddon.createAuthResponse(this.authContextId, inToken);
if (token.length > 0) {
(0, debug_logger_1.debug)("Created " + this.securityPackage + " authentication response token", token.toString("base64"));
}
else {
(0, debug_logger_1.debug)("No response token, authentication complete");
}
return token;
}
catch (err) {
if (err.message ===
"Could not init security context. Result: -2146893054") {
// If incoming token is for NTLMv1, this error can occur when
// LMCompatibilityLevel prevents the client to send NTLMv1 messages
if (this.securityPackage === "NTLM" && this.isNtlmV1(inToken)) {
throw new Error("Could not create NTLM type 3 message. Incoming type 2 message uses NTLMv1, " +
"it is likely that the client is prevented from sending such messages. " +
"Update target host to use NTLMv2 (recommended) or adjust LMCompatibilityLevel on the client (insecure)");
}
}
throw err;
}
}
isNtlmV1(type2message) {
if (type2message.length >= 24) {
const inTokenFlags = type2message.readInt32BE(20);
if ((inTokenFlags & WinSso.NEGOTIATE_NTLM2_KEY) === 0) {
return true;
}
}
return false;
}
/**
* Creates an authentication response header
* @param inTokenHeader The www-authentication header received from the target
* in response to the authentication request
* @returns The www-authenticate header. May be an empty string if Negotiate handshake is complete.
*/
createAuthResponseHeader(inTokenHeader) {
const tokenBuffer = this.createAuthResponse(inTokenHeader);
if (tokenBuffer.length == 0) {
return "";
}
const header = this.securityPackage + " " + tokenBuffer.toString("base64");
return header;
}
}
exports.WinSso = WinSso;
WinSso.NEGOTIATE_NTLM2_KEY = 1 << 19;