329 lines
9.7 KiB
TypeScript
329 lines
9.7 KiB
TypeScript
/**
|
|
* Certificate Verification Test Suite
|
|
* Tests SHA256 fingerprint verification and certificate validation
|
|
*/
|
|
|
|
import * as tls from 'tls';
|
|
import * as crypto from 'crypto';
|
|
|
|
describe('Certificate Verification Tests', () => {
|
|
const RECEIVER_IP = '172.67.157.88';
|
|
const RECEIVER_PORT = 443;
|
|
const RECEIVER_SNI = 'devmindgroup.com';
|
|
const EXPECTED_SHA256_FINGERPRINT = 'b19f2a94eab4cd3b92f1e3e0dce9d5e41c8b7aa3fdbe6e2f4ac3c91a5fbb2f44';
|
|
|
|
describe('SHA256 Fingerprint Verification', () => {
|
|
it('should calculate SHA256 fingerprint correctly', async () => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
const socket = tls.connect(
|
|
{
|
|
host: RECEIVER_IP,
|
|
port: RECEIVER_PORT,
|
|
servername: RECEIVER_SNI,
|
|
rejectUnauthorized: false,
|
|
},
|
|
() => {
|
|
try {
|
|
const cert = socket.getPeerCertificate(true);
|
|
if (cert && cert.raw) {
|
|
const fingerprint = crypto
|
|
.createHash('sha256')
|
|
.update(cert.raw)
|
|
.digest('hex')
|
|
.toLowerCase();
|
|
|
|
expect(fingerprint).toBe(EXPECTED_SHA256_FINGERPRINT.toLowerCase());
|
|
socket.end();
|
|
resolve();
|
|
} else {
|
|
reject(new Error('Certificate not available'));
|
|
}
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
socket.on('error', reject);
|
|
socket.setTimeout(30000);
|
|
socket.on('timeout', () => {
|
|
socket.destroy();
|
|
reject(new Error('Connection timeout'));
|
|
});
|
|
});
|
|
}, 60000);
|
|
|
|
it('should verify certificate fingerprint matches expected value', async () => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
const socket = tls.connect(
|
|
{
|
|
host: RECEIVER_IP,
|
|
port: RECEIVER_PORT,
|
|
servername: RECEIVER_SNI,
|
|
rejectUnauthorized: false,
|
|
},
|
|
() => {
|
|
try {
|
|
const cert = socket.getPeerCertificate(true);
|
|
if (cert && cert.raw) {
|
|
const fingerprint = crypto
|
|
.createHash('sha256')
|
|
.update(cert.raw)
|
|
.digest('hex')
|
|
.toLowerCase();
|
|
|
|
const expected = EXPECTED_SHA256_FINGERPRINT.toLowerCase();
|
|
const matches = fingerprint === expected;
|
|
|
|
expect(matches).toBe(true);
|
|
socket.end();
|
|
resolve();
|
|
} else {
|
|
reject(new Error('Certificate not available'));
|
|
}
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
socket.on('error', reject);
|
|
socket.setTimeout(30000);
|
|
socket.on('timeout', () => {
|
|
socket.destroy();
|
|
reject(new Error('Connection timeout'));
|
|
});
|
|
});
|
|
}, 60000);
|
|
|
|
it('should reject connection if fingerprint does not match', async () => {
|
|
// This test verifies that fingerprint checking logic works
|
|
// In production, rejectUnauthorized should be true and custom checkVerify should validate fingerprint
|
|
const wrongFingerprint = '0000000000000000000000000000000000000000000000000000000000000000';
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
const socket = tls.connect(
|
|
{
|
|
host: RECEIVER_IP,
|
|
port: RECEIVER_PORT,
|
|
servername: RECEIVER_SNI,
|
|
rejectUnauthorized: false, // For testing, we'll check manually
|
|
},
|
|
() => {
|
|
try {
|
|
const cert = socket.getPeerCertificate(true);
|
|
if (cert && cert.raw) {
|
|
const fingerprint = crypto
|
|
.createHash('sha256')
|
|
.update(cert.raw)
|
|
.digest('hex')
|
|
.toLowerCase();
|
|
|
|
// Verify it doesn't match wrong fingerprint
|
|
expect(fingerprint).not.toBe(wrongFingerprint);
|
|
socket.end();
|
|
resolve();
|
|
} else {
|
|
reject(new Error('Certificate not available'));
|
|
}
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
socket.on('error', reject);
|
|
socket.setTimeout(30000);
|
|
socket.on('timeout', () => {
|
|
socket.destroy();
|
|
reject(new Error('Connection timeout'));
|
|
});
|
|
});
|
|
}, 60000);
|
|
});
|
|
|
|
describe('Certificate Chain Validation', () => {
|
|
it('should retrieve full certificate chain', async () => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
const socket = tls.connect(
|
|
{
|
|
host: RECEIVER_IP,
|
|
port: RECEIVER_PORT,
|
|
servername: RECEIVER_SNI,
|
|
rejectUnauthorized: false,
|
|
},
|
|
() => {
|
|
try {
|
|
const cert = socket.getPeerCertificate(true);
|
|
expect(cert).toBeDefined();
|
|
expect(cert.subject).toBeDefined();
|
|
expect(cert.issuer).toBeDefined();
|
|
|
|
socket.end();
|
|
resolve();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
socket.on('error', reject);
|
|
socket.setTimeout(30000);
|
|
socket.on('timeout', () => {
|
|
socket.destroy();
|
|
reject(new Error('Connection timeout'));
|
|
});
|
|
});
|
|
}, 60000);
|
|
|
|
it('should validate certificate subject matches SNI', async () => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
const socket = tls.connect(
|
|
{
|
|
host: RECEIVER_IP,
|
|
port: RECEIVER_PORT,
|
|
servername: RECEIVER_SNI,
|
|
rejectUnauthorized: false,
|
|
},
|
|
() => {
|
|
try {
|
|
const cert = socket.getPeerCertificate();
|
|
expect(cert).toBeDefined();
|
|
|
|
// Certificate should be valid for the SNI
|
|
const subject = cert.subject;
|
|
const altNames = cert.subjectaltname;
|
|
|
|
// SNI should match certificate
|
|
expect(subject || altNames).toBeDefined();
|
|
|
|
socket.end();
|
|
resolve();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
socket.on('error', reject);
|
|
socket.setTimeout(30000);
|
|
socket.on('timeout', () => {
|
|
socket.destroy();
|
|
reject(new Error('Connection timeout'));
|
|
});
|
|
});
|
|
}, 60000);
|
|
});
|
|
|
|
describe('TLS Version and Cipher Suite', () => {
|
|
it('should use TLSv1.2 or higher', async () => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
const socket = tls.connect(
|
|
{
|
|
host: RECEIVER_IP,
|
|
port: RECEIVER_PORT,
|
|
servername: RECEIVER_SNI,
|
|
rejectUnauthorized: false,
|
|
minVersion: 'TLSv1.2',
|
|
},
|
|
() => {
|
|
try {
|
|
const protocol = socket.getProtocol();
|
|
expect(protocol).toBeDefined();
|
|
expect(['TLSv1.2', 'TLSv1.3']).toContain(protocol);
|
|
|
|
socket.end();
|
|
resolve();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
socket.on('error', reject);
|
|
socket.setTimeout(30000);
|
|
socket.on('timeout', () => {
|
|
socket.destroy();
|
|
reject(new Error('Connection timeout'));
|
|
});
|
|
});
|
|
}, 60000);
|
|
|
|
it('should negotiate secure cipher suite', async () => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
const socket = tls.connect(
|
|
{
|
|
host: RECEIVER_IP,
|
|
port: RECEIVER_PORT,
|
|
servername: RECEIVER_SNI,
|
|
rejectUnauthorized: false,
|
|
},
|
|
() => {
|
|
try {
|
|
const cipher = socket.getCipher();
|
|
expect(cipher).toBeDefined();
|
|
expect(cipher.name).toBeDefined();
|
|
|
|
// Should use strong cipher (not null, not weak)
|
|
expect(cipher.name.length).toBeGreaterThan(0);
|
|
|
|
socket.end();
|
|
resolve();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
socket.on('error', reject);
|
|
socket.setTimeout(30000);
|
|
socket.on('timeout', () => {
|
|
socket.destroy();
|
|
reject(new Error('Connection timeout'));
|
|
});
|
|
});
|
|
}, 60000);
|
|
});
|
|
|
|
describe('Certificate Expiration', () => {
|
|
it('should check certificate validity period', async () => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
const socket = tls.connect(
|
|
{
|
|
host: RECEIVER_IP,
|
|
port: RECEIVER_PORT,
|
|
servername: RECEIVER_SNI,
|
|
rejectUnauthorized: false,
|
|
},
|
|
() => {
|
|
try {
|
|
const cert = socket.getPeerCertificate();
|
|
expect(cert).toBeDefined();
|
|
|
|
if (cert.valid_to) {
|
|
const validTo = new Date(cert.valid_to);
|
|
const now = new Date();
|
|
|
|
// Certificate should not be expired
|
|
expect(validTo.getTime()).toBeGreaterThan(now.getTime());
|
|
}
|
|
|
|
socket.end();
|
|
resolve();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
socket.on('error', reject);
|
|
socket.setTimeout(30000);
|
|
socket.on('timeout', () => {
|
|
socket.destroy();
|
|
reject(new Error('Connection timeout'));
|
|
});
|
|
});
|
|
}, 60000);
|
|
});
|
|
});
|