Skip to content

Commit

Permalink
Improve test coverage, add linting to CI
Browse files Browse the repository at this point in the history
  • Loading branch information
timokoessler committed Aug 5, 2024
1 parent b180463 commit af14871
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 67 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: Bug report
about: Create a bug report
title: '[Bug]'
title: 'Bug: '
labels: bug
assignees: timokoessler
---
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Code quality

on:
push:
pull_request:

jobs:
quality:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout repository ⬇️
uses: actions/checkout@v4
- name: Setup Biome ⚙️
uses: biomejs/setup-biome@v2
with:
version: latest
- name: Run Biome 🚀
run: biome ci .
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"recommendations": ["biomejs.biome"]
"recommendations": ["biomejs.biome"]
}
10 changes: 5 additions & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
68 changes: 34 additions & 34 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"organizeImports": {
"enabled": false
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUndeclaredVariables": "error",
"noUnusedImports": "error"
},
"suspicious": {
"noConsoleLog": "error",
"noEmptyBlockStatements": "error",
"useAwait": "error"
}
}
},
"formatter": {
"enabled": true,
"indentWidth": 4,
"indentStyle": "space",
"lineWidth": 140
},
"javascript": {
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"organizeImports": {
"enabled": false
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUndeclaredVariables": "error",
"noUnusedImports": "error"
},
"suspicious": {
"noConsoleLog": "error",
"noEmptyBlockStatements": "error",
"useAwait": "error"
}
}
},
"formatter": {
"quoteStyle": "single",
"semicolons": "always",
"indentStyle": "space",
"indentWidth": 4
"enabled": true,
"indentWidth": 4,
"indentStyle": "space",
"lineWidth": 140
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "always",
"indentStyle": "space",
"indentWidth": 4
}
},
"files": {
"ignore": ["dist", "node_modules", "coverage", "docs", "examples"]
}
},
"files": {
"ignore": ["dist", "node_modules", "coverage", "docs"]
}
}
7 changes: 1 addition & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,7 @@
"type": "git",
"url": "git+https://github.com/timokoessler/easy-ocsp.git"
},
"keywords": [
"ocsp",
"ocsp client",
"X.509",
"certificate"
],
"keywords": ["ocsp", "ocsp client", "X.509", "certificate"],
"engines": {
"node": ">=18"
},
Expand Down
33 changes: 19 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,24 @@ export type OCSPStatusResponse = {
* This function is used internally to download the issuer certificate if it is not provided in the config
* Its exported for convenience if you want to download the issuer certificate manually for some reason
* @param cert The certificate to download the issuer certificate for
* @param config Additional configuration
* @param timeout Optional timeout in milliseconds for the request. Default is 6000ms
* @returns A pkijs.Certificate object of the issuer certificate
*/
export async function downloadIssuerCert(
cert: string | Buffer | X509Certificate | pkijs.Certificate,
config: OCSPStatusConfig,
timeout?: number,
): Promise<pkijs.Certificate> {
let _timeoutMs = 6000;
if (typeof timeout === 'number') {
_timeoutMs = timeout;
}
const { issuerUrl } = getCAInfoUrls(convertToPkijsCert(cert));
const ac = new AbortController();
const timeout = setTimeout(() => ac.abort(), config.timeout);
const _timeout = setTimeout(() => ac.abort(), _timeoutMs);
const res = await fetch(issuerUrl, {
signal: ac.signal,
});
clearTimeout(timeout);
clearTimeout(_timeout);
if (!res.ok) {
throw new Error(`Issuer certificate download failed with status ${res.status} ${res.statusText} ${issuerUrl}`);
}
Expand Down Expand Up @@ -157,7 +161,7 @@ async function sendOCSPRequest(cert: string | Buffer | X509Certificate | pkijs.C

let issuerCertificate: pkijs.Certificate;
if (!config.ca) {
issuerCertificate = await downloadIssuerCert(certificate, config);
issuerCertificate = await downloadIssuerCert(certificate, config.timeout);
} else {
issuerCertificate = convertToPkijsCert(config.ca);
}
Expand Down Expand Up @@ -192,10 +196,10 @@ async function sendOCSPRequest(cert: string | Buffer | X509Certificate | pkijs.C
* @throws AbortError if the request timed out
*/
export async function getCertStatus(cert: string | Buffer | X509Certificate | pkijs.Certificate, config?: OCSPStatusConfig) {
config = { ...defaultConfig, ...config };
const _config = { ...defaultConfig, ...config };

const { response, certificate, issuerCertificate, nonce } = await sendOCSPRequest(cert, config);
return parseOCSPResponse(response, certificate, issuerCertificate, config, nonce);
const { response, certificate, issuerCertificate, nonce } = await sendOCSPRequest(cert, _config);
return parseOCSPResponse(response, certificate, issuerCertificate, _config, nonce);
}

/**
Expand All @@ -207,19 +211,20 @@ export async function getCertStatus(cert: string | Buffer | X509Certificate | pk
* @throws AbortError if the request timed out
*/
export async function getCertStatusByDomain(domain: string, config?: OCSPStatusConfig) {
let _domain = domain;
let timeout = 6000;
if (config && typeof config.timeout === 'number') {
timeout = config.timeout;
}
if (domain.includes('/')) {
if (_domain.includes('/')) {
try {
const url = new URL(domain);
domain = url.hostname;
const url = new URL(_domain);
_domain = url.hostname;
} catch (e) {
throw new Error('Invalid URL');
}
}
return getCertStatus(await downloadCert(domain, timeout), config);
return getCertStatus(await downloadCert(_domain, timeout), config);
}

/**
Expand All @@ -230,9 +235,9 @@ export async function getCertStatusByDomain(domain: string, config?: OCSPStatusC
* @returns The raw OCSP response as a buffer, the nonce and the pem encoded issuer certificate
*/
export async function getRawOCSPResponse(cert: string | Buffer | X509Certificate | pkijs.Certificate, config?: OCSPStatusConfig) {
config = { ...defaultConfig, ...config };
const _config = { ...defaultConfig, ...config };

const { response, issuerCertificate, nonce } = await sendOCSPRequest(cert, config);
const { response, issuerCertificate, nonce } = await sendOCSPRequest(cert, _config);

return {
rawResponse: response,
Expand Down
2 changes: 1 addition & 1 deletion src/tls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { connect as tlsConnect, type ConnectionOptions } from 'node:tls';

/**
* Get a TLS certificate by hostname. This function will always connect to port 443.
* @param hostname Hostname to connect to (e.g. 'github.com')
* @param hostname Hostname to connect to (e.g. 'github.com') - not an URL
* @param timeout Timeout in milliseconds (default: 6000)
* @returns Buffer containing the raw certificate (DER)
* @throws AbortError if the request timed out
Expand Down
16 changes: 13 additions & 3 deletions test/domains.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expect, describe, test} from '@jest/globals';
import { getCertStatusByDomain } from '../src/index';
import { expect, describe, test } from '@jest/globals';
import { downloadCert, getCertStatus, getCertStatusByDomain } from '../src/index';

const domains = [
'timokoessler.de',
Expand All @@ -18,15 +18,25 @@ const domains = [
'www.godaddy.com',
'www.rapidssl.com',
'www.entrust.com',
'https://tkoessler.de',
];

describe('Get certificate status by domain', () => {
for (const domain of domains) {
test(domain, async () => {
const response = await getCertStatusByDomain(domain);
const response = await getCertStatusByDomain(domain, {
timeout: 10000,
});
expect(response.status).toBe('good');
expect(response.revocationTime).toBe(undefined);
expect(response.producedAt).toBeInstanceOf(Date);
});
}

test('Download cert should lead to same result', async () => {
const response = await getCertStatus(await downloadCert('timokoessler.de'));
expect(response.status).toBe('good');
expect(response.revocationTime).toBe(undefined);
expect(response.producedAt).toBeInstanceOf(Date);
});
});
18 changes: 17 additions & 1 deletion test/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, beforeAll, test } from '@jest/globals';
import { getCertStatus, getCertURLs } from '../src';
import { downloadIssuerCert, getCertStatus, getCertStatusByDomain, getCertURLs } from '../src';
import { readCertFile } from './test-helper';

let leCert: string;
Expand Down Expand Up @@ -60,3 +60,19 @@ test('Wrong ocsp server', async () => {
test('Expired certificate', async () => {
await expect(getCertStatus(leStagingExpired)).rejects.toThrow('The certificate is already expired');
});

test('Invalid url', async () => {
await expect(getCertStatusByDomain('test:// invalid %')).rejects.toThrow('Invalid URL');
});

test('Invalid domain', async () => {
await expect(getCertStatusByDomain('enotfound.example.com')).rejects.toThrow('getaddrinfo ENOTFOUND enotfound.example.com');
});

test('Abort getCertStatus', async () => {
await expect(getCertStatus(leCert, { timeout: 0 })).rejects.toThrow('This operation was aborted');
});

test('Aboirt download issuer cert', async () => {
await expect(downloadIssuerCert(leCert, 0)).rejects.toThrow('This operation was aborted');
});
9 changes: 8 additions & 1 deletion test/le-revoked.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { expect, test, beforeAll } from '@jest/globals';
import { X509Certificate } from 'node:crypto';
import { getCertStatus, getCertURLs, getRawOCSPResponse, OCSPRevocationReason } from '../src/index';
import { downloadIssuerCert, getCertStatus, getCertURLs, getRawOCSPResponse, OCSPRevocationReason } from '../src/index';
import { readCertFile } from './test-helper';
import { convertToPkijsCert } from '../src/convert';

let cert: string;
let intermediateCA: string;
Expand Down Expand Up @@ -72,3 +73,9 @@ test('Get raw response', async () => {
expect(result.nonce).toBeInstanceOf(Buffer);
expect(result.issuerCert.replace(/[\n\r]/g, '')).toEqual(intermediateCA.replace(/[\n\r]/g, ''));
});

test('Download issuer cert', async () => {
const issuerCert = await downloadIssuerCert(cert);
const expectedIssuerCert = convertToPkijsCert(intermediateCA);
expect(issuerCert.toJSON()).toEqual(expectedIssuerCert.toJSON());
});

0 comments on commit af14871

Please sign in to comment.