UNPKG

thenavisapp

Version:

This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).

518 lines (440 loc) 13.6 kB
import process from 'node:process'; import tls, {type DetailedPeerCertificate} from 'node:tls'; import test from 'ava'; import {pEvent} from 'p-event'; import pify from 'pify'; import pem from 'pem'; import got from '../source/index.js'; import {withHttpsServer} from './helpers/with-server.js'; import type {CreatePrivateKey, CreateCsr, CreateCertificate} from './types/pem.js'; const createPrivateKey = pify(pem.createPrivateKey as CreatePrivateKey); const createCsr = pify(pem.createCSR as CreateCsr); const createCertificate = pify(pem.createCertificate as CreateCertificate); const createPkcs12 = pify(pem.createPkcs12); test('https request without ca', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); t.truthy((await got({ https: { certificateAuthority: [], rejectUnauthorized: false, }, })).body); }); test('https request with ca', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const {body} = await got({}); t.is(body, 'ok'); }); test('https request with ca and afterResponse hook', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const warningListener = (warning: any) => { if ( warning.name === 'DeprecationWarning' && warning.message === 'Got: "options.ca" was never documented, please use ' + '"options.https.certificateAuthority"' ) { process.off('warning', warningListener); t.fail('unexpected deprecation warning'); } }; process.once('warning', warningListener); let shouldRetry = true; const {body} = await got({ hooks: { afterResponse: [ (response, retry) => { if (shouldRetry) { shouldRetry = false; return retry({}); } return response; }, ], }, }); t.is(body, 'ok'); }); test('https request with `checkServerIdentity` OK', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const {body} = await got({ https: { checkServerIdentity(hostname: string, certificate: DetailedPeerCertificate) { t.is(hostname, 'localhost'); t.is(certificate.subject.CN, 'localhost'); t.is(certificate.issuer.CN, 'authority'); }, }, }); t.is(body, 'ok'); }); test('https request with `checkServerIdentity` NOT OK', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const promise = got({ https: { checkServerIdentity(hostname: string, certificate: DetailedPeerCertificate) { t.is(hostname, 'localhost'); t.is(certificate.subject.CN, 'localhost'); t.is(certificate.issuer.CN, 'authority'); return new Error('CUSTOM_ERROR'); }, }, }); await t.throwsAsync( promise, { message: 'CUSTOM_ERROR', }, ); }); // The built-in `openssl` on macOS does not support negative days. { const testFn = process.platform === 'darwin' ? test.skip : test; testFn('https request with expired certificate', withHttpsServer({days: -1}), async (t, _server, got) => { await t.throwsAsync( got({}), { code: 'CERT_HAS_EXPIRED', }, ); }); } test('https request with wrong host', withHttpsServer({commonName: 'not-localhost.com'}), async (t, _server, got) => { await t.throwsAsync( got({}), { code: 'ERR_TLS_CERT_ALTNAME_INVALID', }, ); }); test('http2', async t => { const promise = got('https://httpbin.org/anything', { http2: true, }); try { const {headers, body} = await promise; await promise.json(); // @ts-expect-error Pseudo headers may not be strings t.is(headers[':status'], 200); t.is(typeof body, 'string'); t.pass(); } catch (error: any) { if (error.message.includes('install Node.js')) { t.pass(); return; } t.fail(error.stack); } }); test.serial('deprecated `rejectUnauthorized` option', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); await t.throwsAsync(got({ // @ts-expect-error Testing purposes rejectUnauthorized: false, }), { message: 'Unexpected option: rejectUnauthorized', }); }); test.serial('non-deprecated `rejectUnauthorized` option', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); (async () => { const warning = await pEvent(process, 'warning'); t.not(warning.name, 'DeprecationWarning'); })(); await got({ https: { rejectUnauthorized: false, }, }); t.pass(); }); test('client certificate', withHttpsServer(), async (t, server, got) => { server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); peerCertificate.issuerCertificate.issuerCertificate = undefined; // Circular structure response.json({ authorized: (request.socket as any).authorized, peerCertificate, }); }); const clientCsrResult = await createCsr({commonName: 'client'}); const clientResult = await createCertificate({ csr: clientCsrResult.csr, clientKey: clientCsrResult.clientKey, serviceKey: (server as any).caKey, serviceCertificate: (server as any).caCert, }); // eslint-disable-next-line prefer-destructuring const clientKey = clientResult.clientKey; const clientCert = clientResult.certificate; const response = await got({ https: { key: clientKey, certificate: clientCert, }, }).json<{ authorized: boolean; peerCertificate: { subject: {CN: string}; issuer: {CN: string}; }; }>(); t.true(response.authorized); t.is(response.peerCertificate.subject.CN, 'client'); t.is(response.peerCertificate.issuer.CN, 'authority'); }); test('invalid client certificate (self-signed)', withHttpsServer(), async (t, server, got) => { server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); peerCertificate.issuerCertificate = undefined; // Circular structure response.json({ authorized: (request.socket as any).authorized, peerCertificate, }); }); const clientCsrResult = await createCsr({commonName: 'other-client'}); const clientResult = await createCertificate({ csr: clientCsrResult.csr, clientKey: clientCsrResult.clientKey, selfSigned: true, }); // eslint-disable-next-line prefer-destructuring const clientKey = clientResult.clientKey; const clientCert = clientResult.certificate; const response = await got({ https: { key: clientKey, certificate: clientCert, }, }).json<{ authorized: boolean; }>(); t.is(response.authorized, false); }); test('invalid client certificate (other CA)', withHttpsServer(), async (t, server, got) => { server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); response.json({ authorized: (request.socket as any).authorized, peerCertificate, }); }); const caCsrResult = await createCsr({commonName: 'other-authority'}); const caResult = await createCertificate({ csr: caCsrResult.csr, clientKey: caCsrResult.clientKey, selfSigned: true, }); const caKey = caResult.clientKey; const caCert = caResult.certificate; const clientCsrResult = await createCsr({commonName: 'other-client'}); const clientResult = await createCertificate({ csr: clientCsrResult.csr, clientKey: clientCsrResult.clientKey, serviceKey: caKey, serviceCertificate: caCert, }); // eslint-disable-next-line prefer-destructuring const clientKey = clientResult.clientKey; const clientCert = clientResult.certificate; const response = await got({ https: { key: clientKey, certificate: clientCert, }, }).json<{ authorized: boolean; peerCertificate: { subject: {CN: string}; issuer: {CN: string}; }; }>(); t.false(response.authorized); t.is(response.peerCertificate.subject.CN, 'other-client'); t.is(response.peerCertificate.issuer.CN, 'other-authority'); }); test('key passphrase', withHttpsServer(), async (t, server, got) => { // Ignore macOS for now as it fails with some internal OpenSSL error. if (process.platform === 'darwin') { t.pass(); return; } server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); peerCertificate.issuerCertificate.issuerCertificate = undefined; // Circular structure response.json({ authorized: (request.socket as any).authorized, peerCertificate, }); }); const {key: clientKey} = await createPrivateKey(2048, { cipher: 'aes256', password: 'randomPassword', }); const clientCsrResult = await createCsr({ // eslint-disable-next-line object-shorthand clientKey: clientKey, clientKeyPassword: 'randomPassword', commonName: 'client', }); const clientResult = await createCertificate({ csr: clientCsrResult.csr, clientKey: clientCsrResult.clientKey, clientKeyPassword: 'randomPassword', serviceKey: (server as any).caKey, serviceCertificate: (server as any).caCert, }); const clientCert = clientResult.certificate; const response = await got({ https: { key: clientKey, passphrase: 'randomPassword', certificate: clientCert, }, }).json<{ authorized: boolean; peerCertificate: { subject: {CN: string}; issuer: {CN: string}; }; }>(); t.true(response.authorized); t.is(response.peerCertificate.subject.CN, 'client'); t.is(response.peerCertificate.issuer.CN, 'authority'); }); test('invalid key passphrase', withHttpsServer(), async (t, server, got) => { // Ignore macOS for now as it fails with some internal OpenSSL error. if (process.platform === 'darwin') { t.pass(); return; } server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); peerCertificate.issuerCertificate.issuerCertificate = undefined; // Circular structure response.json({ authorized: (request.socket as any).authorized, peerCertificate, }); }); const {key: clientKey} = await createPrivateKey(2048, { cipher: 'aes256', password: 'randomPassword', }); const clientCsrResult = await createCsr({ // eslint-disable-next-line object-shorthand clientKey: clientKey, clientKeyPassword: 'randomPassword', commonName: 'client', }); const clientResult = await createCertificate({ csr: clientCsrResult.csr, clientKey: clientCsrResult.clientKey, clientKeyPassword: 'randomPassword', serviceKey: (server as any).caKey, serviceCertificate: (server as any).caCert, }); const clientCert = clientResult.certificate; const request = got({ https: { key: clientKey, passphrase: 'wrongPassword', certificate: clientCert, }, }); const {code}: NodeJS.ErrnoException = (await t.throwsAsync(request))!; t.true(code === 'ERR_OSSL_BAD_DECRYPT' || code === 'ERR_OSSL_EVP_BAD_DECRYPT', code); }); // TODO: RC2 is not supported on Node.js 17 // eslint-disable-next-line ava/no-skip-test test.skip('client certificate PFX', withHttpsServer(), async (t, server, got) => { server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); peerCertificate.issuerCertificate = undefined; // Circular structure response.json({ authorized: (request.socket as any).authorized, peerCertificate, }); }); const clientCsrResult = await createCsr({commonName: 'client'}); const clientResult = await createCertificate({ csr: clientCsrResult.csr, clientKey: clientCsrResult.clientKey, serviceKey: (server as any).caKey, serviceCertificate: (server as any).caCert, }); // eslint-disable-next-line prefer-destructuring const clientKey = clientResult.clientKey; const clientCert = clientResult.certificate; const {pkcs12} = await createPkcs12(clientKey, clientCert, 'randomPassword'); const response = await got({ https: { pfx: pkcs12, passphrase: 'randomPassword', }, }).json<{ authorized: boolean; peerCertificate: { subject: {CN: string}; issuer: {CN: string}; }; }>(); t.true(response.authorized); t.is(response.peerCertificate.subject.CN, 'client'); t.is(response.peerCertificate.issuer.CN, 'authority'); }); const ciphers = tls.getCiphers().map(cipher => cipher.toUpperCase()).filter(cipher => cipher.startsWith('TLS_')).slice(0, 3); test('https request with `ciphers` option', withHttpsServer({ciphers: ciphers.join(':')}), async (t, server, got) => { server.get('/', (request, response) => { response.json({ cipher: (request.socket as any).getCipher().name, }); }); const response = await got({ https: { ciphers: ciphers[0], }, }).json<{cipher: string}>(); t.is(response.cipher, ciphers[0]!); }); test('https request with `honorCipherOrder` option', withHttpsServer({ciphers: `${ciphers[0]!}:${ciphers[1]!}`}), async (t, server, got) => { server.get('/', (request, response) => { response.json({ cipher: (request.socket as any).getCipher().name, }); }); const response = await got({ https: { ciphers: `${ciphers[1]!}:${ciphers[0]!}`, honorCipherOrder: true, }, }).json<{cipher: string}>(); t.is(response.cipher, ciphers[0]!); }); test('https request with `minVersion` option', withHttpsServer({maxVersion: 'TLSv1.2'}), async (t, server, got) => { server.get('/', (request, response) => { response.json({ version: (request.socket as any).getCipher().version, }); }); const request = got({ https: { minVersion: 'TLSv1.3', }, }); await t.throwsAsync(request, { code: 'EPROTO', }); });