Skip to content

Refactor LCP CRL caching and add build-time fallback - #3871

Open
panaC wants to merge 2 commits into
developfrom
fix/lcp-crl-cache
Open

panaC wants to merge 2 commits into
developfrom
fix/lcp-crl-cache

Conversation

@panaC

@panaC panaC commented Sep 14, 2026

Copy link
Copy Markdown
Member

Fixes #3864
Revert 260ab54
Target PR #3825

This PR replaces the LCP CRL refresh logic with a memory-only cache model inspired by readium/swift-toolkit#894.

Swift uses readLocal() backed by UserDefaults. In Thorium/Electron, the equivalent is readMemory(), because persisting the CRL in userData would create a mutable local trust input unless we also verify the CRL signature/authenticity before loading it.

The model is simple: at startup, the cache is seeded with BUILD_CRL and BUILD_CRL_CACHED_AT. preload() calls readMemory(); if the memory CRL is missing or expired, it starts a network refresh in the background. During LCP unlock, retrieve() calls readMemory(); if the CRL is fresh, it returns it immediately. If it is expired, it still returns it immediately and refreshes in the background. Only if there is no memory CRL at all does unlock wait for the network refresh.

The workaround for avoiding disk persistence is the build-time CRL fallback. BUILD_CRL acts as the default local CRL, and BUILD_CRL_CACHED_AT lets the normal freshness logic decide when it should be refreshed. This keeps offline/startup behavior safe enough without trusting a user-writable filesystem cache.

The fetched CRL is still checked with isX509Crl() before replacing the memory cache. This mirrors the Swift guard: it rejects HTML/captive portal responses, truncated data, and trailing garbage. It is not a cryptographic signature verification.

If we later add disk persistence, the disk CRL must be treated as untrusted input and validated cryptographically before being promoted into memory.

@panaC
panaC requested a review from danielweck September 14, 2026 09:43
@panaC panaC self-assigned this Sep 14, 2026
@panaC

panaC commented Sep 14, 2026

Copy link
Copy Markdown
Member Author
flowchart TD
    A[App startup / main.ts] --> B[Import BUILD_CRL + BUILD_CRL_CACHED_AT]
    B --> C[Create LcpCrlCache]
    C --> D[Seed memory cache]
    D --> E[memoryCrl = BUILD_CRL]
    E --> F[cachedAt = BUILD_CRL_CACHED_AT]
    F --> G[Install setCRLGetter]
    G --> H[Call preload]

    H --> I[readMemory]
    I --> J{Memory CRL exists?}

    J -- no --> K[Start network refresh in background]
    J -- yes --> L{Is memory CRL expired?}

    L -- no --> M[Do nothing]
    L -- yes --> K

    K --> N{Fetch succeeds and response is X.509 CRL?}
    N -- yes --> O[Encode DER to PEM]
    O --> P[Replace memoryCrl]
    P --> Q[cachedAt = Date.now]
    N -- no --> R[Keep existing memory CRL]

    S[Open / unlock LCP publication] --> T[LCP calls setCRLGetter]
    T --> U[LcpCrlCache.retrieve]
    U --> V[readMemory]
    V --> W{Memory CRL exists?}

    W -- no --> X[Await network refresh]
    X --> Y{Fetch succeeds and response is X.509 CRL?}
    Y -- yes --> Z[Save fresh CRL in memory]
    Z --> AA[Return fresh CRL to liblcp]
    Y -- no --> AB[Throw CRL fetch error]

    W -- yes --> AC{Is memory CRL expired?}
    AC -- no --> AD[Return memory CRL immediately]
    AC -- yes --> AE[Start/reuse refresh in background]
    AE --> AF[Return expired memory CRL immediately]
Loading

kUiV/PFCwL66iiF666DrXLY=
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// run this at each new build to update the CRL:
node -e "const url='http://crl.edrlab.telesec.de/rl/EDRLab_CA.crl'; const tick=String.fromCharCode(96); const now=Date.now(); fetch(url).then(async r=>{ if(!r.ok) throw new Error('HTTP '+r.status); const buf=Buffer.from(await r.arrayBuffer()); const b64=buf.toString('base64').match(/.{1,64}/g).join('\n'); console.log('// Build-time CRL fallback generated from CRL_URL on '+new Date(now).toISOString()+'.'); console.log('export const BUILD_CRL_CACHED_AT = '+now+';'); console.log('export const BUILD_CRL = '+tick+'-----BEGIN X509 CRL-----\n'+b64+'\n-----END X509 CRL-----'+tick+';'); }).catch(e=>{ console.error(e); process.exit(1); });"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before each release build we have to refresh the static CRL

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

> node -e "const url='http://crl.edrlab.telesec.de/rl/EDRLab_CA.crl'; const tick=String.fromCharCode(96); const now=Date.now(); fetch(url).then(async r=>{ if(!r.ok) throw new Error('HTTP '+r.status); const buf=Buffer.from(await r.arrayBuffer()); const b64=buf.toString('base64').match(/.{1,64}/g).join('\n'); console.log('// Build-time CRL fallback generated from CRL_URL on '+new Date(now).toISOString()+'.'); console.log('export const BUILD_CRL_CACHED_AT = '+now+';'); console.log('export const BUILD_CRL = '+tick+'-----BEGIN X509 CRL-----\n'+b64+'\n-----END X509 CRL-----'+tick+';'); }).catch(e=>{ console.error(e); process.exit(1); });"
// Build-time CRL fallback generated from CRL_URL on 2026-09-14T10:04:49.729Z.
export const BUILD_CRL_CACHED_AT = 1789380289729;
export const BUILD_CRL = `-----BEGIN X509 CRL-----
MIICkTCCAXkCAQEwDQYJKoZIhvcNAQELBQAwQjETMBEGA1UEChMKZWRybGFiLm9y
ZzEXMBUGA1UECxMOZWRybGFiLm9yZyBMQ1AxEjAQBgNVBAMTCUVEUkxhYiBDQRcN
MjYwOTEzMTQxNzA4WhcNMjYwOTE4MTQxNzA3WjCB0DAnAgg9/PrnYyy4ABcNMjYw
ODA1MjAyMTEzWjAMMAoGA1UdFQQDCgEBMCgCCQCoPyWN9DqSBhcNMjYwNTI1MDc1
NTAwWjAMMAoGA1UdFQQDCgEGMCgCCQCwrtK1lYNPKhcNMjYwODI4MTcyNzQ1WjAM
MAoGA1UdFQQDCgEGMCcCCAD7am95HSWbFw0yNjAzMjMxMjQ1NThaMAwwCgYDVR0V
BAMKAQYwKAIJAKjr9Zx5OfipFw0yNjAyMTIxMDE0MDJaMAwwCgYDVR0VBAMKAQag
MDAuMB8GA1UdIwQYMBaAFNxc/JPkH5/usLrqUgsrylJc4MmHMAsGA1UdFAQEAgIN
/jANBgkqhkiG9w0BAQsFAAOCAQEAhHCfMKjWaIORdex9iYL2WYOK/qOyRegPa+uT
TeS6SAqFPwT8EWuo0aa9dSt2GXtMNfPEmOyxioVvhV2gfYyjbmoDyUJDlkySUAcO
c4voHAuf4wT2y1GzuvI4pQNn3KkZu35HrWBy5pMFwrBkTSRlbTtYESpWlXAezrmD
YN/kMSjDLSyan15L9r1Hkp1gKMxTluUByQW4pm1zY3MR19Gkew8RivQWt5yPUfCD
3+HMgBYMnbHmxpUPi3LyfswTiMZxNT2BHiHy6Qdg3uC25tTQs3sq5ih1ErRMuCl/
MVZZnnZh4KBBFzgcZsSFf8Kggn5SW9BUZRGN4QHuAY9Sma4fAQ==
-----END X509 CRL-----`;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bundling the current LCP CRL directly with each Thorium build

1 participant