Added mpsHighSecurity option in config.json, #3910

This commit is contained in:
Ylian Saint-Hilaire 2022-04-23 13:17:12 -07:00
parent 5ef32633d8
commit fc60faefba
5 changed files with 268 additions and 28 deletions

View File

@ -36,6 +36,7 @@ const PROTOCOL_WEBVNC = 204;
// Construct a MSTSC Relay object, called upon connection
// This implementation does not have TLS support
// This is a bit of a hack as we are going to run the RDP connection thru a loopback connection.
// If the "node-rdpjs-2" module supported passing a socket, we would do something different.
module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) {
@ -282,7 +283,6 @@ module.exports.CreateMstscRelay = function (parent, db, ws, req, args, domain) {
};
// Construct a SSH Relay object, called upon connection
module.exports.CreateSshRelay = function (parent, db, ws, req, args, domain) {
const Net = require('net');

View File

@ -156,16 +156,17 @@
},
"tlsOffload": { "type": [ "boolean", "string" ], "default": false, "description": "When true, indicates that a TLS offloader is in front of the MeshCentral server. More typically, set this to the IP address of the reverse proxy or TLS offloader so that IP forwarding headers will be trusted. For example: \"127.0.0.1,192.168.1.100\"." },
"trustedProxy": { "type": "string", "default": null, "description": "Trust forwarded headers from these IPs or domains. Providing the magic string \"CloudFlare\" will cause the server to download the IP address list of trusted CloudFlare proxies directly from CloudFlare on each server start. For example: \"127.0.0.1,proxy.example.com,CloudFlare\"." },
"mpsPort": { "type": "integer", "minimum": 1, "maximum": 65535 },
"mpsPortBind": { "type": "string" },
"mpsAliasPort": { "type": "integer", "minimum": 1, "maximum": 65535 },
"mpsAliasHost": { "type": "string" },
"mpsTlsOffload": { "type": "boolean", "default": false },
"mpsPort": { "type": "integer", "minimum": 0, "maximum": 65535, "default": 4433, "description": "The Management Presence Server (MPS), this is the server that received Intel AMT Client Initiated Remote Access (CIRA) connections." },
"mpsPortBind": { "type": "string", "default": null },
"mpsAliasPort": { "type": "integer", "minimum": 1, "maximum": 65535, "default": null },
"mpsAliasHost": { "type": "string", "default": null },
"mpsTlsOffload": { "type": "boolean", "default": false, "description": "When set to true, indicate that TLS is being performed by a device in front of MeshCentral." },
"mpsHighSecurity": { "type": "boolean", "default": false, "description": "When set to true, the MPS server will only accept TLS 1.2 and 1.3 connections. Older Intel AMT devices will not be able to connect." },
"no2FactorAuth": { "type": "boolean", "default": false },
"log": { "type": "string" },
"syslog": { "type": "string" },
"syslogauth": { "type": "string" },
"syslogjson": { "type": "string" },
"log": { "type": "string", "default": null },
"syslog": { "type": "string", "default": null },
"syslogauth": { "type": "string", "default": null },
"syslogjson": { "type": "string", "default": null },
"syslogtcp": { "type": "string", "default": null, "description": "Send syslog events over the network (RFC3164) to a target hostname:port. For example: localhost:514" },
"webrtcConfig": {
"type": "object",

View File

@ -43,8 +43,13 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) {
if (obj.args.mpstlsoffload) {
obj.server = net.createServer(onConnection);
} else {
// Note that in order to support older Intel AMT CIRA connections, we have to turn on TLSv1.
obj.server = tls.createServer({ key: certificates.mps.key, cert: certificates.mps.cert, minVersion: 'TLSv1', requestCert: true, rejectUnauthorized: false, ciphers: "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION }, onConnection);
if (obj.args.mpshighsecurity) {
// Higher security TLS 1.2 and 1.3 only, some older Intel AMT CIRA connections will fail.
obj.server = tls.createServer({ key: certificates.mps.key, cert: certificates.mps.cert, requestCert: true, rejectUnauthorized: false, ciphers: "HIGH:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256:TLS_CHACHA20_POLY1305_SHA256", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1 }, onConnection)
} else {
// Lower security MPS in order to support older Intel AMT CIRA connections, we have to turn on TLSv1.
obj.server = tls.createServer({ key: certificates.mps.key, cert: certificates.mps.cert, minVersion: 'TLSv1', requestCert: true, rejectUnauthorized: false, ciphers: "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA", secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION }, onConnection)
}
//obj.server.on('error', function () { console.log('MPS tls server error'); });
obj.server.on('newSession', function (id, data, cb) { if (tlsSessionStoreCount > 1000) { tlsSessionStoreCount = 0; tlsSessionStore = {}; } tlsSessionStore[id.toString('hex')] = data; tlsSessionStoreCount++; cb(); });
obj.server.on('resumeSession', function (id, cb) { cb(null, tlsSessionStore[id.toString('hex')] || null); });

View File

@ -36,8 +36,6 @@
"sample-config-advanced.json"
],
"dependencies": {
"@yetzt/nedb": "^1.8.0",
"archiver": "^4.0.2",
"body-parser": "^1.19.0",
"cbor": "~5.2.0",
"compression": "^1.7.4",
@ -45,25 +43,13 @@
"express": "^4.17.0",
"express-handlebars": "^5.3.5",
"express-ws": "^4.0.0",
"html-minifier": "^4.0.0",
"image-size": "^1.0.1",
"ipcheck": "^0.1.0",
"jsdom": "^19.0.0",
"loadavg-windows": "^1.1.1",
"minify-js": "^0.0.4",
"minimist": "^1.2.5",
"multiparty": "^4.2.1",
"@yetzt/nedb": "^1.8.0",
"node-forge": "^1.0.0",
"node-rdpjs-2": "^0.3.5",
"node-windows": "^0.1.4",
"otplib": "^10.2.3",
"pg": "^8.7.1",
"pgtools": "^0.3.2",
"ssh2": "^1.9.0",
"web-push": "^3.4.5",
"ws": "^5.2.3",
"yauzl": "^2.10.0",
"yubikeyotp": "^0.2.0"
"yauzl": "^2.10.0"
},
"engines": {
"node": ">=10.0.0"

248
test.js Normal file
View File

@ -0,0 +1,248 @@
/*
YST: create_negotiate_message: 4e544c4d53535000010000003582086000000000000000000000000000000000
WRITE: negotiate_message 302fa003020102a12830263024a02204204e544c4d53535000010000003582086000000000000000000000000000000000
READ: read_ts_server_challenge 3081b2a003020106a181aa3081a73081a4a081a104819e4e544c4d53535000020000000e000e003800000035828a62f2290572b3cac375000000000000000058005800460000000a00614a0000000f430045004e005400520041004c0002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000
YST: read_challenge_message1: 4e544c4d53535000020000000e000e003800000035828a62f2290572b3cac375000000000000000058005800460000000a00614a0000000f430045004e005400520041004c0002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000
YST: target_name: 02000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000
YST: timestamp: afbc2c2a9256d801
YST: client_challenge: f8d1e2057b9c7169
YST: nt_challenge_response: 32e87ea049485920fca6695ae9daee820101000000000000afbc2c2a9256d801f8d1e2057b9c71690000000002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000
YST: lm_challenge_response: 2b0f863797a00d4446a1c9f49de124a5f8d1e2057b9c7169
YST: session_base_key: 52f86f741e92e2e9d8013c1afcecfa13
YST: key_exchange_key: 52f86f741e92e2e9d8013c1afcecfa13
YST: encrypted_random_session_key: d9b2353a7f7ba9569ece01e96686cdef
YST: self.is_unicode: true
YST: domain:
YST: user: 640065006600610075006c007400
YST: tmp_final_auth_message: 4e544c4d53535000030000001800180058000000840084007000000000000000f40000000e000e00f40000000000000002010000100010000201000035828a62060072170000000f000000000000000000000000000000002b0f863797a00d4446a1c9f49de124a5f8d1e2057b9c716932e87ea049485920fca6695ae9daee820101000000000000afbc2c2a9256d801f8d1e2057b9c71690000000002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000640065006600610075006c007400d9b2353a7f7ba9569ece01e96686cdef
YST: signature: 4cbff4ee21ce4114c7657d7aa427ca3f
YST: read_challenge_message2: 4e544c4d53535000030000001800180058000000840084007000000000000000f40000000e000e00f40000000000000002010000100010000201000035828a62060072170000000f4cbff4ee21ce4114c7657d7aa427ca3f2b0f863797a00d4446a1c9f49de124a5f8d1e2057b9c716932e87ea049485920fca6695ae9daee820101000000000000afbc2c2a9256d801f8d1e2057b9c71690000000002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000640065006600610075006c007400d9b2353a7f7ba9569ece01e96686cdef
WRITE challenge: 30820251a003020102a18201223082011e3082011aa0820116048201124e544c4d53535000030000001800180058000000840084007000000000000000f40000000e000e00f40000000000000002010000100010000201000035828a62060072170000000f4cbff4ee21ce4114c7657d7aa427ca3f2b0f863797a00d4446a1c9f49de124a5f8d1e2057b9c716932e87ea049485920fca6695ae9daee820101000000000000afbc2c2a9256d801f8d1e2057b9c71690000000002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000640065006600610075006c007400d9b2353a7f7ba9569ece01e96686cdefa38201220482011e0100000095fb7df84ffcd81d00000000a0588a3354258442e2997c79a4005e91ac49759b3c362228f42aefa5daad071a8eb905d7bc16611df35a50c0c577acf4543609e3ade8ff8289d371d8baed9eb2bd738de6c208f6a9885f1b0ee8aee2b26be87aa189bb41989c79716d938eb68e2f1e4bdc74c25e995c438aa5e5e30c2784214f4763c6737417edac43371f65f22aab68619f022b2f46b4cb9d00d0befe6d7e83d0d2c4ae69b544b1e90b1d5198459722eff903e8550864a46c552d75c83fb8ca8491b573fcd59d17cdc34e2505995fa6922554fdf03016161b430e476875a3752907087e993388f56675957a75b42b062f1f66ef8597a69deb466337396747ec8e46c573344b24fc8fba9305207eb10462ce355299063bab4cf105
READ: read_ts_validate 3081b2a003020106a181aa3081a73081a4a081a104819e4e544c4d53535000020000000e000e003800000035828a62f2290572b3cac375000000000000000058005800460000000a00614a0000000f430045004e005400520041004c0002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000
WRITE credentials: 302fa003020102a12830263024a02204204e544c4d53535000010000003582086000000000000000000000000000000000
*/
/*
"Signature" => Check::new(b"NTLMSSP\x00".to_vec()),
"MessageType" => Check::new(U32::LE(2)),
"TargetNameLen" => U16::LE(0),
"TargetNameLenMax" => U16::LE(0),
"TargetNameBufferOffset" => U32::LE(0),
"NegotiateFlags" => DynOption::new(U32::LE(0), |node| {
if node.inner() & (Negotiate::NtlmsspNegociateVersion as u32) == 0 {
return MessageOption::SkipField("Version".to_string())
}
return MessageOption::None
}),
"ServerChallenge" => vec![0; 8],
"Reserved" => vec![0; 8],
"TargetInfoLen" => U16::LE(0),
"TargetInfoMaxLen" => U16::LE(0),
"TargetInfoBufferOffset" => U32::LE(0),
"Version" => version(),
"Payload" => Vec::<u8>::new()
*/
const NegotiateFlags = {
NtlmsspNegociate56: 0x80000000,
NtlmsspNegociateKeyExch: 0x40000000,
NtlmsspNegociate128: 0x20000000,
NtlmsspNegociateVersion: 0x02000000,
NtlmsspNegociateTargetInfo: 0x00800000,
NtlmsspRequestNonNTSessionKey: 0x00400000,
NtlmsspNegociateIdentify: 0x00100000,
NtlmsspNegociateExtendedSessionSecurity: 0x00080000,
NtlmsspTargetTypeServer: 0x00020000,
NtlmsspTargetTypeDomain: 0x00010000,
NtlmsspNegociateAlwaysSign: 0x00008000,
NtlmsspNegociateOEMWorkstationSupplied: 0x00002000,
NtlmsspNegociateOEMDomainSupplied: 0x00001000,
NtlmsspNegociateNTLM: 0x00000200,
NtlmsspNegociateLMKey: 0x00000080,
NtlmsspNegociateDatagram: 0x00000040,
NtlmsspNegociateSeal: 0x00000020,
NtlmsspNegociateSign: 0x00000010,
NtlmsspRequestTarget: 0x00000004,
NtlmNegotiateOEM: 0x00000002,
NtlmsspNegociateUnicode: 0x00000001
}
const MajorVersion = {
WindowsMajorVersion5: 0x05,
WindowsMajorVersion6: 0x06
}
const MinorVersion = {
WindowsMinorVersion0: 0x00,
WindowsMinorVersion1: 0x01,
WindowsMinorVersion2: 0x02,
WindowsMinorVersion3: 0x03
}
const NTLMRevision = {
NtlmSspRevisionW2K3: 0x0F
}
function decodeTargetInfo(targetInfoBuf) {
var r = {}, type, len, data, ptr = 0;
while (true) {
type = targetInfoBuf.readInt16LE(ptr);
if (type == 0) break;
len = targetInfoBuf.readInt16LE(ptr + 2);
r[type] = targetInfoBuf.slice(ptr + 4, ptr + 4 + len);
ptr += (4 + len);
}
return r;
}
function bufToArr(b) { var r = []; for (var i = 0; i < b.length; i++) { r.push(b.readUInt8(i)); } return r; } // For unit testing
function toUnicode(str) { return Buffer.from(str, 'ucs2'); }
function md4(str) { return crypto.createHash('md4').update(str).digest(); }
function md5(str) { return crypto.createHash('md5').update(str).digest(); }
function hmacmd5(key, data) { return crypto.createHmac('md5', key).update(data).digest(); }
function ntowfv2(password, user, domain) { return hmacmd5(md4(toUnicode(password)), toUnicode(user.toUpperCase() + domain)); }
function lmowfv2(password, user, domain) { return ntowfv2(password, user, domain); }
function zeroBuffer(len) { return Buffer.alloc(len); }
function compute_response_v2(response_key_nt, response_key_lm, server_challenge, client_challenge, time, server_name) {
const response_version = Buffer.from('01', 'hex');
const hi_response_version = Buffer.from('01', 'hex');
const temp = Buffer.concat([response_version, hi_response_version, zeroBuffer(6), time, client_challenge, zeroBuffer(4), server_name]);
const nt_proof_str = hmacmd5(response_key_nt, Buffer.concat([server_challenge, temp]));
const nt_challenge_response = Buffer.concat([nt_proof_str, temp]);
const lm_challenge_response = Buffer.concat([hmacmd5(response_key_lm, Buffer.concat([server_challenge, client_challenge])), client_challenge]);
const session_base_key = hmacmd5(response_key_nt, nt_proof_str);
return [nt_challenge_response, lm_challenge_response, session_base_key];
}
function kx_key_v2(session_base_key, _lm_challenge_response, _server_challenge) { return session_base_key; }
function rc4k(key, data) { return crypto.createCipheriv('rc4', key, null).update(data); }
/// Compute a signature of all data exchange during NTLMv2 handshake
function mic(exported_session_key, negotiate_message, challenge_message, authenticate_message) { return hmacmd5(exported_session_key, Buffer.concat([negotiate_message, challenge_message, authenticate_message])); }
/// NTLMv2 security interface generate a sign key
/// By using MD5 of the session key + a static member (sentense)
function sign_key(exported_session_key, is_client) {
if (is_client) {
return md5(Buffer.concat([exported_session_key, Buffer.from("session key to client-to-server signing key magic constant\0")]));
} else {
return md5(Buffer.concat([exported_session_key, Buffer.from("session key to server-to-client signing key magic constant\0")]));
}
}
/// NTLMv2 security interface generate a seal key
/// By using MD5 of the session key + a static member (sentense)
function seal_key(exported_session_key, is_client) {
if (is_client) {
return md5(Buffer.concat([exported_session_key, Buffer.from("session key to client-to-server sealing key magic constant\0")]));
} else {
return md5(Buffer.concat([exported_session_key, Buffer.from("session key to server-to-client sealing key magic constant\0")]));
}
}
function authenticate_message(lm_challenge_response, nt_challenge_response, domain, user, workstation, encrypted_random_session_key, flags) {
const payload = Buffer.concat([lm_challenge_response, nt_challenge_response, domain, user, workstation, encrypted_random_session_key]);
const offset = ((flags & NegotiateFlags.NtlmsspNegociateVersion) == 0) ? 80 : 88;
const buf = Buffer.alloc(offset - 16);
buf.write('4e544c4d53535000', 0, 8, 'hex'); // Signature
buf.writeInt32LE(3, 8); // MessageType
buf.writeInt16LE(lm_challenge_response.length, 12); // LmChallengeResponseLen
buf.writeInt16LE(lm_challenge_response.length, 14); // LmChallengeResponseMaxLen
buf.writeInt32LE(offset, 16); // LmChallengeResponseBufferOffset
buf.writeInt16LE(nt_challenge_response.length, 20); // NtChallengeResponseLen
buf.writeInt16LE(nt_challenge_response.length, 22); // NtChallengeResponseMaxLen
buf.writeInt32LE(offset + lm_challenge_response.length, 24); // NtChallengeResponseBufferOffset
buf.writeInt16LE(domain.length, 28); // DomainNameLen
buf.writeInt16LE(domain.length, 30); // DomainNameMaxLen
buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length, 32); // DomainNameBufferOffset
buf.writeInt16LE(user.length, 36); // UserNameLen
buf.writeInt16LE(user.length, 38); // UserNameMaxLen
buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length + domain.length, 40); // UserNameBufferOffset
buf.writeInt16LE(workstation.length, 44); // WorkstationLen
buf.writeInt16LE(workstation.length, 46); // WorkstationMaxLen
buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length + domain.length + user.length, 48); // WorkstationBufferOffset
buf.writeInt16LE(encrypted_random_session_key.length, 52); // EncryptedRandomSessionLen
buf.writeInt16LE(encrypted_random_session_key.length, 54); // EncryptedRandomSessionMaxLen
buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length + domain.length + user.length + workstation.length, 56); // EncryptedRandomSessionBufferOffset
buf.writeInt32LE(flags, 60); // NegotiateFlags
if ((flags & NegotiateFlags.NtlmsspNegociateVersion) != 0) {
buf.writeUInt8(MajorVersion.WindowsMajorVersion6, 64); // ProductMajorVersion
buf.writeUInt8(MinorVersion.WindowsMinorVersion0, 65); // ProductMinorVersion
buf.writeInt16LE(6002, 66); // ProductBuild
//buf.writeInt16LE(0, 68); // Reserved
//buf.writeUInt8(0, 70); // Reserved
buf.writeUInt8(NTLMRevision.NtlmSspRevisionW2K3, 71); // NTLMRevisionCurrent
}
return [buf, payload];
}
function read_challenge_message(derBuffer, user, pass, domain, negotiate_message) {
const headerSignature = derBuffer.slice(0, 8);
if (headerSignature.toString('hex') != '4e544c4d53535000') { console.log('BAD SIGNATURE'); }
const messageType = derBuffer.readInt32LE(8);
if (messageType != 2) { console.log('BAD MESSAGE TYPE'); }
const targetNameLen = derBuffer.readInt16LE(12);
const targetNameLenMax = derBuffer.readInt16LE(14);
const targetNameBufferOffset = derBuffer.readInt32LE(16);
const negotiateFlags = derBuffer.readInt32LE(20);
const serverChallenge = derBuffer.slice(24, 32);
const reserved = derBuffer.slice(32, 40);
if (reserved.toString('hex') != '0000000000000000') { console.log('BAD RESERVED'); }
const targetInfoLen = derBuffer.readInt16LE(40);
const targetInfoLenMax = derBuffer.readInt16LE(42);
const targetInfoBufferOffset = derBuffer.readInt32LE(44);
const targetName = derBuffer.slice(targetNameBufferOffset, targetNameBufferOffset + targetNameLen);
const targetInfo = decodeTargetInfo(derBuffer.slice(targetInfoBufferOffset, targetInfoBufferOffset + targetInfoLen));
const timestamp = targetInfo[7];
if (timestamp == null) { console.log('NO TIMESTAMP'); }
const clientChallenge = crypto.randomBytes(8);
const response_key_nt = ntowfv2(pass, user, domain); // Password, Username, Domain
const response_key_lm = lmowfv2(pass, user, domain); // Password, Username, Domain
var resp = compute_response_v2(response_key_nt, response_key_lm, serverChallenge, clientChallenge, timestamp, targetName);
const nt_challenge_response = resp[0];
const lm_challenge_response = resp[1];
const session_base_key = resp[2];
const key_exchange_key = kx_key_v2(session_base_key, lm_challenge_response, serverChallenge);
const exported_session_key = crypto.randomBytes(16);
const encrypted_random_session_key = rc4k(key_exchange_key, exported_session_key);
const is_unicode = ((negotiateFlags & 1) != 0)
var xdomain = null;
var xuser = null;
if (is_unicode) {
xdomain = toUnicode(domain);
xuser = toUnicode(user);
} else {
xdomain = Buffer.from(domain, 'utf8');
xuser = Buffer.from(domain, 'utf8');
}
const auth_message_compute = authenticate_message(lm_challenge_response, nt_challenge_response, xdomain, xuser, zeroBuffer(0), encrypted_random_session_key, negotiateFlags);
// Write a tmp message to compute MIC and then include it into final message
const tmp_final_auth_message = Buffer.concat([auth_message_compute[0], zeroBuffer(16), auth_message_compute[1]]);
const signature = mic(exported_session_key, negotiate_message, derBuffer, tmp_final_auth_message);
return Buffer.concat([auth_message_compute[0], signature, auth_message_compute[1]]);
}
// Create create_ts_request
const crypto = require('crypto');
const forge = require('node-forge');
const asn1 = forge.asn1;
const pki = forge.pki;
const entireBuffer = Buffer.from('3081b2a003020106a181aa3081a73081a4a081a104819e4e544c4d53535000020000000e000e003800000035828a62f2290572b3cac375000000000000000058005800460000000a00614a0000000f430045004e005400520041004c0002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c0007000800afbc2c2a9256d80100000000', 'hex').toString('binary');
// We have a full ASN1 data block, decode it now
const der = asn1.fromDer(entireBuffer.toString('binary'));
const derNum = der.value[0].value[0].value.charCodeAt(0);
const derBuffer = Buffer.from(der.value[1].value[0].value[0].value[0].value[0].value, 'binary');
const negotiate_message = Buffer.from('4e544c4d53535000010000003582086000000000000000000000000000000000', 'hex');
const client_challenge = read_challenge_message(derBuffer, "default", "", "", negotiate_message);
console.log('client_challenge', client_challenge.toString('hex'));