Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
unreleased:
new features:
- >-
GH-1532 Add support for logging SSL session keys to a file
chores:
- Updated dependencies

7.48.0:
date: 2025-09-25
new features:
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,10 @@ runner.run(collection, {

// The max depth or number of requests a request can invoke via `pm.execution.runRequest`
// in its pre-request and post-response scripts
maxInvokableNestedRequests: 5
maxInvokableNestedRequests: 5,

// Enable logging SSL session keys (only supported on Node, ignored in the browser)
sslKeyLogFile: '/path/to/keylogfile',
},

// Options specific to the script execution
Expand Down Expand Up @@ -234,7 +237,7 @@ runner.run(collection, {

// *note* Not implemented yet.
// In the future, this will be used to read certificates from the OS keychain.
systemCertificate: function() {}
systemCertificate: function() {},
}, function (err, run) {
console.log('Created a new Run!');

Expand Down
2 changes: 2 additions & 0 deletions lib/requester/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,8 @@ module.exports = {
options.ciphers = protocolProfileBehavior.tlsCipherSelection.join(':');
}

options.sslKeyLogFile = defaultOpts.sslKeyLogFile;

if (typeof defaultOpts.maxResponseSize === 'number') {
options.maxResponseSize = defaultOpts.maxResponseSize;
}
Expand Down
3 changes: 2 additions & 1 deletion lib/requester/requester-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ RequesterPool = function (options, callback) {
removeRefererHeaderOnRedirect: _.get(options, 'requester.removeRefererHeaderOnRedirect'),
ignoreProxyEnvironmentVariables: _.get(options, 'ignoreProxyEnvironmentVariables'),
network: _.get(options, 'network', {}),
maxHeaderSize: _.get(options, 'requester.maxHeaderSize', 131072) // 128KB
maxHeaderSize: _.get(options, 'requester.maxHeaderSize', 131072), // 128KB
sslKeyLogFile: _.get(options, 'requester.sslKeyLogFile')
});

// create a cookie jar if one is not provided
Expand Down
15 changes: 7 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"node-oauth1": "1.3.0",
"performance-now": "2.1.0",
"postman-collection": "5.2.0",
"postman-request": "2.88.1-postman.43",
"postman-request": "2.88.1-postman.45",
"postman-sandbox": "6.2.1",
"postman-url-encoder": "3.0.8",
"serialised-error": "1.1.3",
Expand Down
271 changes: 271 additions & 0 deletions test/integration/requester-spec/sslKeyLogFile.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
var expect = require('chai').expect;
var fs = require('fs');


(typeof window === 'undefined' ? describe : describe.skip)('Requester Spec: sslKeyLogFile', function () {
var tmp,
path,
testrun,
tmpDir,
sslKeyLogFilePath;

before(function () {
tmp = require('tmp');
path = require('path');
});

describe('with sslKeyLogFile', function () {
before(function (done) {
tmpDir = tmp.dirSync({ unsafeCleanup: true });
sslKeyLogFilePath = path.join(tmpDir.name, 'ssl-keylog.txt');


this.run({
requester: {
strictSSL: false,
sslKeyLogFile: sslKeyLogFilePath
},
collection: {
item: [{
request: global.servers.https
}]
}
}, function (err, results) {
testrun = results;
done(err);
});
});
after(function () {
// Clean up temporary directory
if (tmpDir) {
tmpDir.removeCallback();
}
});

it('should complete the run', function () {
expect(testrun).to.be.ok;
expect(testrun).to.nested.include({
'done.calledOnce': true,
'start.calledOnce': true,
'request.calledOnce': true
});
});

it('should write SSL key log data to the specified file', function () {
// Check that the file exists
expect(fs.existsSync(sslKeyLogFilePath)).to.be.true;

// Read the file contents
const fileContents = fs.readFileSync(sslKeyLogFilePath, 'utf8');

// Verify the file has content (SSL key log entries)
expect(fileContents).to.be.a('string');
expect(fileContents.length).to.be.greaterThan(0);

// SSL key log format should contain CLIENT_RANDOM or other SSL key log entries
// The format is typically lines like: "CLIENT_RANDOM <hex> <hex>"
expect(fileContents).to.match(/CLIENT_/);
});
});

describe('with sslKeyLogFile and non-empty file', function () {
before(function (done) {
tmpDir = tmp.dirSync({ unsafeCleanup: true });
sslKeyLogFilePath = path.join(tmpDir.name, 'ssl-keylog.txt');

// Write some content to the file
fs.writeFileSync(sslKeyLogFilePath, '1234567890');

this.run({
requester: {
strictSSL: false,
sslKeyLogFile: sslKeyLogFilePath
},
collection: {
item: [{
request: global.servers.https
}]
}
}, function (err, results) {
testrun = results;
done(err);
});
});

after(function () {
// Clean up temporary directory
if (tmpDir) {
tmpDir.removeCallback();
}
});

it('should complete the run', function () {
expect(testrun).to.be.ok;
expect(testrun).to.nested.include({
'done.calledOnce': true,
'start.calledOnce': true,
'request.calledOnce': true
});
});
it('should append to the existing SSL key log data in the specified file', function () {
// Check that the file exists
expect(fs.existsSync(sslKeyLogFilePath)).to.be.true;

// Read the file contents
const fileContents = fs.readFileSync(sslKeyLogFilePath, 'utf8');

// Verify the file has content (SSL key log entries)
expect(fileContents).to.be.a('string');
expect(fileContents.length).to.be.greaterThan(0);

// Verify the file contains the existing content
expect(fileContents).to.include('1234567890');

// Verify the file contains the new content
expect(fileContents).to.include('CLIENT_HANDSHAKE_TRAFFIC_SECRET');
});
});

describe('without sslKeyLogFile', function () {
before(function (done) {
tmpDir = tmp.dirSync({ unsafeCleanup: true });
sslKeyLogFilePath = path.join(tmpDir.name, 'ssl-keylog.txt');

this.run({
requester: {
strictSSL: false
},
collection: {
item: [{
request: global.servers.https
}]
}
}, function (err, results) {
testrun = results;
done(err);
});
});

after(function () {
// Clean up temporary directory
if (tmpDir) {
tmpDir.removeCallback();
}
});

it('should complete the run', function () {
expect(testrun).to.be.ok;
expect(testrun).to.nested.include({
'done.calledOnce': true,
'start.calledOnce': true,
'request.calledOnce': true
});
});

it('should not have any SSL key log data in the file', function () {
// Check that the file does not exist
expect(fs.existsSync(sslKeyLogFilePath)).to.be.false;
});
});

describe('with invalid sslKeyLogFile path', function () {
before(function (done) {
tmpDir = tmp.dirSync({ unsafeCleanup: true });
sslKeyLogFilePath = path.join(tmpDir.name, 'ssl-keylog.txt');

this.run({
requester: {
strictSSL: false,
sslKeyLogFile: '/invalid/path/that/does/not/exist/ssl-keylog.txt'
},
collection: {
item: [{
request: global.servers.https
}]
}
}, function (err, results) {
testrun = results;
done(err);
});
});

after(function () {
// Clean up temporary directory
if (tmpDir) {
tmpDir.removeCallback();
}
});

it('should complete the run despite invalid path', function () {
expect(testrun).to.be.ok;
expect(testrun).to.nested.include({
'done.calledOnce': true,
'start.calledOnce': true,
'request.calledOnce': true
});
});
it('should not have any SSL key log data in the file', function () {
// Check that the file does not exist
expect(fs.existsSync(sslKeyLogFilePath)).to.be.false;
});
});

describe('with multiple HTTPS requests', function () {
before(function (done) {
tmpDir = tmp.dirSync({ unsafeCleanup: true });
sslKeyLogFilePath = path.join(tmpDir.name, 'ssl-keylog.txt');

this.run({
requester: {
strictSSL: false,
sslKeyLogFile: sslKeyLogFilePath
},
collection: {
item: [
{
request: global.servers.https
},
{
request: global.servers.https
}
]
}
}, function (err, results) {
testrun = results;
done(err);
});
});

after(function () {
// Clean up temporary directory
if (tmpDir) {
tmpDir.removeCallback();
}
});

it('should complete the run', function () {
expect(testrun).to.be.ok;
expect(testrun).to.nested.include({
'done.calledOnce': true,
'start.calledOnce': true
});
expect(testrun.request.callCount).to.equal(2);
});

it('should write SSL key log data from all requests to the same file', function () {
// Check that the file exists
expect(fs.existsSync(sslKeyLogFilePath)).to.be.true;

// Read the file contents
const fileContents = fs.readFileSync(sslKeyLogFilePath, 'utf8');

// Verify the file has content
expect(fileContents).to.be.a('string');
expect(fileContents.length).to.be.greaterThan(0);

// Should contain SSL key log entries
expect(fileContents).to.match(/CLIENT_/);
});
});
});

Loading