// Node.js - SECURE: Proper TLS configuration and validation
const https = require('https');
const tls = require('tls');
const crypto = require('crypto');
const fs = require('fs');
class SecureHttpsClient {
constructor(options = {}) {
this.pinnedCertificates = options.pinnedCertificates || [];
this.trustedHosts = options.trustedHosts || [];
// Configure secure TLS options
this.secureOptions = {
// Require TLS 1.2 or higher
secureProtocol: 'TLSv1_2_method',
// Enable certificate verification
rejectUnauthorized: true,
// Custom server identity check
checkServerIdentity: this.checkServerIdentity.bind(this),
// Secure cipher suites only
ciphers: [
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-RSA-AES256-SHA384',
'ECDHE-RSA-AES128-SHA256'
].join(':'),
// Honor server cipher order
honorCipherOrder: true,
// Minimum TLS version
minVersion: 'TLSv1.2'
};
}
checkServerIdentity(hostname, cert) {
// First, perform default hostname verification
const err = tls.checkServerIdentity(hostname, cert);
if (err) {
return err;
}
// Additional hostname validation
if (this.trustedHosts.length > 0 && !this.isHostnameTrusted(hostname)) {
return new Error(`Hostname ${hostname} is not in trusted hosts list`);
}
// Certificate pinning validation
if (this.pinnedCertificates.length > 0) {
return this.validateCertificatePinning(cert);
}
// Additional certificate validation
return this.validateCertificateExtended(cert, hostname);
}
isHostnameTrusted(hostname) {
return this.trustedHosts.some(trusted => {
if (trusted.startsWith('*.')) {
const domain = trusted.slice(2);
return hostname.endsWith(domain);
}
return hostname === trusted;
});
}
validateCertificatePinning(cert) {
try {
// Calculate certificate fingerprint
const publicKey = cert.pubkey;
const hash = crypto.createHash('sha256').update(publicKey).digest('base64');
const pin = `sha256:${hash}`;
if (!this.pinnedCertificates.includes(pin)) {
return new Error(`Certificate pinning failed. Expected pins: ${this.pinnedCertificates.join(', ')}`);
}
return undefined; // Valid
} catch (error) {
return new Error(`Certificate pinning validation error: ${error.message}`);
}
}
validateCertificateExtended(cert, hostname) {
try {
// Check certificate validity period
const now = new Date();
const validFrom = new Date(cert.valid_from);
const validTo = new Date(cert.valid_to);
if (now < validFrom) {
return new Error(`Certificate not yet valid. Valid from: ${validFrom}`);
}
if (now > validTo) {
return new Error(`Certificate expired. Valid until: ${validTo}`);
}
// Check for critical extensions
if (cert.ext_key_usage && !cert.ext_key_usage.includes('1.3.6.1.5.5.7.3.1')) {
return new Error('Certificate missing serverAuth extended key usage');
}
// Validate certificate chain length
if (cert.issuerCertificate && this.getCertificateChainDepth(cert) > 5) {
return new Error('Certificate chain too long');
}
return undefined; // Valid
} catch (error) {
return new Error(`Extended certificate validation error: ${error.message}`);
}
}
getCertificateChainDepth(cert, depth = 0) {
if (!cert.issuerCertificate || cert.fingerprint === cert.issuerCertificate.fingerprint) {
return depth;
}
return this.getCertificateChainDepth(cert.issuerCertificate, depth + 1);
}
makeSecureRequest(url, options = {}) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const requestOptions = {
hostname: urlObj.hostname,
port: urlObj.port || 443,
path: urlObj.pathname + urlObj.search,
method: options.method || 'GET',
headers: options.headers || {},
...this.secureOptions
};
const req = https.request(requestOptions, (res) => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
resolve({
statusCode: res.statusCode,
headers: res.headers,
data: data,
certificate: res.socket.getPeerCertificate()
});
});
});
req.on('error', (err) => {
reject(new Error(`HTTPS request failed: ${err.message}`));
});
// Handle TLS errors specifically
req.on('socket', (socket) => {
socket.on('secureConnect', () => {
const cert = socket.getPeerCertificate(true);
console.log(`Secure connection established to ${urlObj.hostname}`);
console.log(`Certificate subject: ${cert.subject.CN}`);
console.log(`Certificate valid until: ${cert.valid_to}`);
});
socket.on('error', (err) => {
if (err.code === 'CERT_HAS_EXPIRED') {
reject(new Error('Certificate has expired'));
} else if (err.code === 'DEPTH_ZERO_SELF_SIGNED_CERT') {
reject(new Error('Self-signed certificate'));
} else {
reject(err);
}
});
});
if (options.data) {
req.write(options.data);
}
req.end();
});
}
async testConnection(hostname, port = 443) {
try {
const response = await this.makeSecureRequest(`https://${hostname}:${port}/`);
return {
success: true,
certificate: response.certificate,
message: 'Connection successful'
};
} catch (error) {
return {
success: false,
error: error.message,
message: 'Connection failed'
};
}
}
}