cloudflare/access-crl-worker-template

Public

mirrored fromhttps://github.com/cloudflare/access-crl-worker-template

CodeCommitsIssuesPull requestsActionsInsightsSecurity
master

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS
SSH

Download ZIP

index.js

135lines · modecode

1/**
2 * This worker script will handle loading a CRL and doing a cert revocation check
3 *
4 * You can force a refresh of the CRL by adding a `force-crl-refresh: 1` header to the original request
5 */
6
7import * as asn1js from 'asn1js'
8import { CertificateRevocationList } from 'pkijs'
9
10// The URL where your CRL is located. We will fetch it from here.
11// Uncomment this line if you can't use wrangler >= 1.8.0
12// const CRL_URL = '<REPLACE_ME>'
13
14// The key we store your crl under in your namespace
15const CRL_KV_KEY = `CRL_${btoa(CRL_URL)}`
16
17// Optional header that will force the worker to get an updated CRL
18const FORCE_CRL_REFRESH_HEADER = 'force-crl-refresh'
19
20/**
21 * Worker entry point
22 */
23addEventListener('fetch', event => {
24 event.respondWith(handleRequest(event))
25})
26
27/**
28 * Return a 403 response
29 */
30function forbidden() {
31 return new Response('client certificate was revoked', { status: 403 })
32}
33
34/**
35 * Helper function that converts a buffer to a hex string
36 * @param {*} inputBuffer
37 */
38function bufToHex(inputBuffer) {
39 let result = ''
40 for (const item of new Uint8Array(inputBuffer, 0, inputBuffer.byteLength)) {
41 const str = item.toString(16).toUpperCase()
42 if (str.length === 1) result += '0'
43 result += str
44 }
45 return result.trim()
46}
47
48/**
49 * Fetchs a CRL list, parses out the serial numbers, and stores them into workers kv
50 */
51async function updateCRL() {
52 const crlResp = await fetch(CRL_URL)
53 if (crlResp.status == 200) {
54 const buf = await crlResp.arrayBuffer()
55 const asn1 = asn1js.fromBER(buf)
56 const crlSimpl = new CertificateRevocationList({
57 schema: asn1.result,
58 })
59 const newCRL = {
60 nextUpdate: crlSimpl.nextUpdate.value,
61 thisUpdate: crlSimpl.thisUpdate.value,
62 revokedSerialNumbers: crlSimpl.revokedCertificates.reduce(
63 (revokedSerialNums, cert) => {
64 let serialNum = bufToHex(cert.userCertificate.valueBlock.valueHex)
65 revokedSerialNums[serialNum] = true
66 return revokedSerialNums
67 },
68 {},
69 ),
70 }
71 CRL_NAMESPACE.put(CRL_KV_KEY, JSON.stringify(newCRL))
72 return newCRL
73 }
74 throw new Error(`failed to fetch crl with status ${crlResp.status}`)
75}
76
77/**
78 * Load a CRL from workers kv. Handles refreshing the crl as needed.
79 */
80async function loadCRL(event, forceCRLRefresh = false) {
81 // Force a refresh of the CRL list if needed
82 if (forceCRLRefresh) {
83 return await updateCRL()
84 }
85
86 // attempt to get the CRL from workers kv first
87 let crl = await CRL_NAMESPACE.get(CRL_KV_KEY, 'json')
88 if (!crl) {
89 // the CRL wasn't in workers kv, so go fetch it from the source
90 crl = await updateCRL()
91 }
92
93 // Check to see if we should refresh the CRL
94 const nextUpdate = Date.parse(crl.next_update)
95 const now = new Date()
96 if (now > nextUpdate) {
97 // it is time to update the CRL. Out of band send a request to update the workers kv key
98 event.waitUntil(updateCRL())
99 }
100
101 return crl
102}
103
104async function handleRequest(event) {
105 try {
106 const request = event.request
107 // Ensure the request has the Cloudflare cf object and certificate headers and that the certificate was successfully presented
108 // If so, then check the CRL to see if the cert was revoked.
109 if (
110 request.cf &&
111 request.cf.tlsClientAuth &&
112 request.cf.tlsClientAuth.certPresented &&
113 request.cf.tlsClientAuth.certVerified === 'SUCCESS'
114 ) {
115 // Check to see if we were asked to force a CRL refresh
116 const forceCRLRefresh = request.headers.get(FORCE_CRL_REFRESH_HEADER)
117 ? true
118 : false
119
120 // Load the crl
121 const crl = await loadCRL(event, forceCRLRefresh)
122 if (!crl) {
123 return new Response('failed to load CRL', { status: 500 })
124 }
125
126 // Check to see if the certificate the user presented is in the crl
127 if (crl.revokedSerialNumbers[request.cf.tlsClientAuth.certSerial]) {
128 return forbidden()
129 }
130 }
131 return await fetch(request)
132 } catch (e) {
133 return new Response(`failed to load CRL ${e}`, { status: 500 })
134 }
135}