const noop = FunctionPrototype;
+const kTLSSessionStatePrefix = Buffer.from('\0nodejs:tls:session:1\0');
+
+function getSessionServerIdentity(options) {
+ return options?.servername ||
+ options?.host ||
+ options?.socket?._host ||
+ 'localhost';
+}
+
+function wrapSessionState(session, options) {
+ if (!Buffer.isBuffer(session) || options?.isServer)
+ return session;
+
+ const servername = Buffer.from(getSessionServerIdentity(options), 'utf8');
+ const servernameLength = Buffer.allocUnsafe(2);
+ servernameLength.writeUInt16BE(servername.length, 0);
+
+ return Buffer.concat([
+ kTLSSessionStatePrefix,
+ servernameLength,
+ servername,
+ session,
+ ]);
+}
+
+function unwrapSessionState(session) {
+ if (!Buffer.isBuffer(session) ||
+ session.length < kTLSSessionStatePrefix.length + 2 ||
+ Buffer.compare(
+ session.subarray(0, kTLSSessionStatePrefix.length),
+ kTLSSessionStatePrefix,
+ ) !== 0) {
+ return;
+ }
+
+ const start = kTLSSessionStatePrefix.length;
+ const servernameLength = session.readUInt16BE(start);
+ const servernameStart = start + 2;
+ const servernameEnd = servernameStart + servernameLength;
+ if (session.length < servernameEnd)
+ return;
+
+ return {
+ servername: session.toString('utf8', servernameStart, servernameEnd),
+ session: session.subarray(servernameEnd),
+ };
+}
+
+function getSessionForReuse(session, options) {
+ if (typeof session === 'string')
+ session = Buffer.from(session, 'latin1');
+
+ if (options?.isServer)
+ return session;
+
+ const wrappedSession = unwrapSessionState(session);
+ if (wrappedSession !== undefined) {
+ const servername = getSessionServerIdentity(options);
+ if (wrappedSession.servername !== servername) {
+ debug('ignore session for %s: authenticated for %s',
+ servername, wrappedSession.servername);
+ return;
+ }
+
+ return wrappedSession.session;
+ }
+
+ if (Buffer.isBuffer(session) && options?.rejectUnauthorized !== false) {
+ debug('ignore raw session for verified client connection to %s',
+ getSessionServerIdentity(options));
+ return;
+ }
+
+ return session;
+}
+
let ipServernameWarned = false;
let tlsTracingWarned = false;
function onnewsessionclient(sessionId, session) {
debug('client emit session');
const owner = this[owner_symbol];
+ const wrappedSession = wrapSessionState(session, owner[kConnectOptions]);
if (owner[kIsVerified]) {
- owner.emit('session', session);
+ owner.emit('session', wrappedSession);
} else {
- owner[kPendingSession] = session;
+ owner[kPendingSession] = wrappedSession;
}
}
};
TLSSocket.prototype.setSession = function(session) {
- if (typeof session === 'string')
- session = Buffer.from(session, 'latin1');
- this._handle.setSession(session);
+ session = getSessionForReuse(session, this[kConnectOptions] || this._tlsOptions);
+ if (session !== undefined)
+ this._handle.setSession(session);
+};
+
+TLSSocket.prototype.getSession = function() {
+ if (!this._handle)
+ return null;
+
+ return wrapSessionState(
+ this._handle.getSession(),
+ this[kConnectOptions] || this._tlsOptions,
+ );
};
TLSSocket.prototype.getPeerCertificate = function(detailed) {
'getFinished',
'getPeerFinished',
'getProtocol',
- 'getSession',
'getTLSTicket',
'isSessionReused',
'enableTrace',
// Verify that server's identity matches it's certificate's names
// Unless server has resumed our existing session
if (!verifyError && !this.isSessionReused()) {
- const hostname = options.servername ||
- options.host ||
- (options.socket && options.socket._host) ||
- 'localhost';
const cert = this.getPeerCertificate(true);
- verifyError = options.checkServerIdentity(hostname, cert);
+ verifyError = options.checkServerIdentity(
+ getSessionServerIdentity(options),
+ cert,
+ );
}
if (verifyError) {
const context = options.secureContext || tls.createSecureContext(options);
+ options.session = getSessionForReuse(options.session, options);
+
const tlssock = new TLSSocket(options.socket, {
allowHalfOpen: options.allowHalfOpen,
pipe: !!options.path,