@certrev/cert-block 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder/index.d.ts +73 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +63 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/contract/fixtures.d.ts +1 -1
- package/dist/contract/fixtures.d.ts.map +1 -1
- package/dist/contract/fixtures.js +8 -4
- package/dist/contract/fixtures.js.map +1 -1
- package/dist/contract/kernel.d.ts +2 -2
- package/dist/contract/kernel.js +2 -2
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -6
- package/dist/index.js.map +1 -1
- package/dist/verify/get-verified-envelope.d.ts.map +1 -1
- package/dist/verify/get-verified-envelope.js +28 -4
- package/dist/verify/get-verified-envelope.js.map +1 -1
- package/dist/verify/resolve-kid.d.ts.map +1 -1
- package/dist/verify/resolve-kid.js +3 -1
- package/dist/verify/resolve-kid.js.map +1 -1
- package/package.json +77 -69
- package/src/__tests__/verify.test.ts +36 -0
- package/src/builder/__tests__/builder.test.tsx +81 -0
- package/src/builder/index.tsx +100 -0
- package/src/contract/fixtures.ts +13 -9
- package/src/contract/kernel.ts +2 -2
- package/src/index.ts +11 -6
- package/src/verify/get-verified-envelope.ts +29 -4
- package/src/verify/resolve-kid.ts +3 -1
- package/dist/contract/kernel-contract.d.ts +0 -154
- package/dist/contract/kernel-contract.d.ts.map +0 -1
- package/dist/contract/kernel-contract.js +0 -35
- package/dist/contract/kernel-contract.js.map +0 -1
- package/dist/contract/kernel-stub.d.ts +0 -44
- package/dist/contract/kernel-stub.d.ts.map +0 -1
- package/dist/contract/kernel-stub.js +0 -163
- package/dist/contract/kernel-stub.js.map +0 -1
|
@@ -119,6 +119,42 @@ describe('getVerifiedEnvelope — metafield source', () => {
|
|
|
119
119
|
})
|
|
120
120
|
expect(verdict.decision).toBe('suppress')
|
|
121
121
|
})
|
|
122
|
+
|
|
123
|
+
it('DISTINCT metafield envelopes at one placement do NOT collide on a shared cache (value-keyed)', async () => {
|
|
124
|
+
// Regression: the metafield cache key must include a hash of the envelope value, else
|
|
125
|
+
// the first verdict (render) is served for a later, DIFFERENT envelope — a fail-closed
|
|
126
|
+
// violation (an unverified/tampered credential rendered). Here a valid + a tampered
|
|
127
|
+
// envelope share the SAME resolver, context, and cache; each must get its own verdict.
|
|
128
|
+
const { envelope, resolveKid } = makeSignedEnvelope()
|
|
129
|
+
const tampered: CertDeliveryEnvelope = {
|
|
130
|
+
...envelope,
|
|
131
|
+
payload: {
|
|
132
|
+
...envelope.payload,
|
|
133
|
+
content: {
|
|
134
|
+
...envelope.payload.content,
|
|
135
|
+
expert: { ...envelope.payload.content.expert, displayName: 'Dr. Forged' },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
const cache = new TtlCache<import('../contract/kernel.js').CertVerdict>()
|
|
140
|
+
const ctx = { ...RENDER_CTX, now: new Date('2026-06-22T00:00:00Z') }
|
|
141
|
+
|
|
142
|
+
const validVerdict = await getVerifiedEnvelope({
|
|
143
|
+
source: { kind: 'metafield', value: envelope },
|
|
144
|
+
resolveKid,
|
|
145
|
+
context: ctx,
|
|
146
|
+
cache,
|
|
147
|
+
})
|
|
148
|
+
const tamperedVerdict = await getVerifiedEnvelope({
|
|
149
|
+
source: { kind: 'metafield', value: tampered },
|
|
150
|
+
resolveKid,
|
|
151
|
+
context: ctx,
|
|
152
|
+
cache,
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
expect(validVerdict.decision).toBe('render')
|
|
156
|
+
expect(tamperedVerdict).toEqual({ decision: 'suppress', reason: 'invalid_signature' })
|
|
157
|
+
})
|
|
122
158
|
})
|
|
123
159
|
|
|
124
160
|
describe('getVerifiedEnvelope — Delivery API source', () => {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR-render tests for the Builder.io adapter (`@certrev/cert-block/builder`), exercised
|
|
3
|
+
* through `react-dom/server`'s `renderToStaticMarkup` — the same path Hydrogen/Next use to
|
|
4
|
+
* produce crawlable HTML on the edge. These prove the adapter's load-bearing contract:
|
|
5
|
+
*
|
|
6
|
+
* • a render-verdict placement renders the in-DOM cert badge (crawlable);
|
|
7
|
+
* • EVERY non-render path — suppress verdict, unknown placement, missing placementId —
|
|
8
|
+
* renders NOTHING (fail-closed survives the CMS boundary);
|
|
9
|
+
* • when one Content tree mixes a valid + a revoked placement (the /builder-cert proof
|
|
10
|
+
* shape), only the valid one renders.
|
|
11
|
+
*
|
|
12
|
+
* Authority comes solely from the loader-supplied verdict in <CertVerifyProvider>; the
|
|
13
|
+
* `placementId` is an opaque pointer the CMS cannot use to manufacture a badge.
|
|
14
|
+
*/
|
|
15
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
16
|
+
import { describe, expect, it } from 'vitest'
|
|
17
|
+
import { makeMockPayload } from '../../contract/fixtures.js'
|
|
18
|
+
import type { CertVerdict } from '../../contract/kernel.js'
|
|
19
|
+
import { CertReviewBlock, CertVerifyProvider, certReviewBuilderComponent, type VerifiedPlacements } from '../index.js'
|
|
20
|
+
|
|
21
|
+
const renderVerdict: CertVerdict = { decision: 'render', payload: makeMockPayload() }
|
|
22
|
+
const suppressVerdict: CertVerdict = { decision: 'suppress', reason: 'revoked' }
|
|
23
|
+
|
|
24
|
+
function renderBlock(placements: VerifiedPlacements, placementId?: string): string {
|
|
25
|
+
return renderToStaticMarkup(
|
|
26
|
+
<CertVerifyProvider value={placements}>
|
|
27
|
+
<CertReviewBlock placementId={placementId} />
|
|
28
|
+
</CertVerifyProvider>,
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('Builder adapter — registration', () => {
|
|
33
|
+
it('registers as "CertREV Review" with a single required string `placementId` input', () => {
|
|
34
|
+
expect(certReviewBuilderComponent.name).toBe('CertREV Review')
|
|
35
|
+
expect(certReviewBuilderComponent.component).toBe(CertReviewBlock)
|
|
36
|
+
const input = certReviewBuilderComponent.inputs?.find((i) => i.name === 'placementId')
|
|
37
|
+
expect(input).toBeDefined()
|
|
38
|
+
expect(input?.required).toBe(true)
|
|
39
|
+
expect(input?.type).toBe('string')
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('Builder adapter — CertReviewBlock render', () => {
|
|
44
|
+
it('renders the verified badge for a render-verdict placement (in-DOM, crawlable)', () => {
|
|
45
|
+
const html = renderBlock({ 'p-valid': { verdict: renderVerdict, pageUrl: 'https://brand.example.com/a' } }, 'p-valid')
|
|
46
|
+
expect(html).toContain('certrev-badge')
|
|
47
|
+
expect(html).toContain('Dr. Jane Doe')
|
|
48
|
+
expect(html).toContain('data-certrev-cert-id="cert_fixture_001"')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('FAIL-CLOSED: a suppress-verdict placement renders nothing', () => {
|
|
52
|
+
expect(renderBlock({ 'p-revoked': { verdict: suppressVerdict } }, 'p-revoked')).toBe('')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('FAIL-CLOSED: an unknown placementId renders nothing', () => {
|
|
56
|
+
expect(renderBlock({ 'p-valid': { verdict: renderVerdict } }, 'p-not-here')).toBe('')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('FAIL-CLOSED: a missing placementId renders nothing', () => {
|
|
60
|
+
expect(renderBlock({ 'p-valid': { verdict: renderVerdict } }, undefined)).toBe('')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('mixed content (valid + revoked in one Content tree): only the valid placement renders', () => {
|
|
64
|
+
const placements: VerifiedPlacements = {
|
|
65
|
+
'p-valid': { verdict: renderVerdict, pageUrl: 'https://brand.example.com/a' },
|
|
66
|
+
'p-revoked': { verdict: suppressVerdict },
|
|
67
|
+
}
|
|
68
|
+
const html = renderToStaticMarkup(
|
|
69
|
+
<CertVerifyProvider value={placements}>
|
|
70
|
+
<div id="valid-block">
|
|
71
|
+
<CertReviewBlock placementId="p-valid" />
|
|
72
|
+
</div>
|
|
73
|
+
<div id="revoked-block">
|
|
74
|
+
<CertReviewBlock placementId="p-revoked" />
|
|
75
|
+
</div>
|
|
76
|
+
</CertVerifyProvider>,
|
|
77
|
+
)
|
|
78
|
+
expect(html).toContain('certrev-badge') // valid rendered
|
|
79
|
+
expect(html).toContain('<div id="revoked-block"></div>') // revoked empty — fail-closed through the CMS
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @certrev/cert-block/builder — the Builder.io adapter for the cert-block render edge.
|
|
3
|
+
*
|
|
4
|
+
* Lets a Builder.io content editor place a "CertREV Review" block into a page; the
|
|
5
|
+
* headless app's loader (Hydrogen/Next/Remix) crypto-verifies that placement's signed
|
|
6
|
+
* envelope and supplies the verdict via React context; this block renders cert-block's
|
|
7
|
+
* <CertReview> from the VERIFIED verdict — and renders NOTHING (fail-closed) on any
|
|
8
|
+
* suppress or unknown placement.
|
|
9
|
+
*
|
|
10
|
+
* THE TRUST BOUNDARY: the credential is never trusted from CMS content. A Builder block
|
|
11
|
+
* only carries an opaque `placementId` pointer; the authority comes solely from the
|
|
12
|
+
* loader-side WebCrypto verdict supplied through <CertVerifyProvider>. So a revoked /
|
|
13
|
+
* tampered / expired / wrong-subject placement that an editor drops into Builder renders
|
|
14
|
+
* nothing, even though the block is present in the published content.
|
|
15
|
+
*
|
|
16
|
+
* cert-block carries NO dependency on `@builder.io/sdk-react` — the registration's shape is
|
|
17
|
+
* declared structurally below, so the emitted JS pulls no Builder code and the type resolves
|
|
18
|
+
* without Builder installed. The consumer (who already has Builder) passes
|
|
19
|
+
* `certReviewBuilderComponent` to Builder's `<Content customComponents={[...]}>`; it is
|
|
20
|
+
* assignable to Builder's `RegisteredComponent` there.
|
|
21
|
+
*/
|
|
22
|
+
import { createContext, type ReactNode, useContext } from 'react'
|
|
23
|
+
import { CertReview } from '../components/CertReview.js'
|
|
24
|
+
import type { CertVerdict } from '../contract/kernel.js'
|
|
25
|
+
|
|
26
|
+
/** A loader-verified placement: the verdict cert-block renders from, plus the canonical
|
|
27
|
+
* page URL for JSON-LD @id alignment. */
|
|
28
|
+
export interface VerifiedPlacement {
|
|
29
|
+
readonly verdict: CertVerdict
|
|
30
|
+
readonly pageUrl?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Map of `placementId` → the loader's verified result. Built server-side in the loader
|
|
34
|
+
* (it crypto-verifies each placement) and handed to <CertVerifyProvider>. */
|
|
35
|
+
export type VerifiedPlacements = Record<string, VerifiedPlacement>
|
|
36
|
+
|
|
37
|
+
const CertVerifyContext = createContext<VerifiedPlacements>({})
|
|
38
|
+
|
|
39
|
+
/** Wraps Builder's <Content>; carries the loader's verified verdicts down to every
|
|
40
|
+
* CertReview block, keyed by `placementId`. */
|
|
41
|
+
export function CertVerifyProvider({
|
|
42
|
+
value,
|
|
43
|
+
children,
|
|
44
|
+
}: {
|
|
45
|
+
readonly value: VerifiedPlacements
|
|
46
|
+
readonly children: ReactNode
|
|
47
|
+
}) {
|
|
48
|
+
return <CertVerifyContext.Provider value={value}>{children}</CertVerifyContext.Provider>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CertReviewBlockProps {
|
|
52
|
+
/** The CertREV placement id the editor set on this Builder block. */
|
|
53
|
+
readonly placementId?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The component Builder renders for a "CertREV Review" block. It pulls the loader-verified
|
|
58
|
+
* verdict for its `placementId` from context and renders <CertReview>. Unknown placement
|
|
59
|
+
* OR a non-`render` verdict → renders nothing. cert-block's <CertReview> ALSO suppresses on
|
|
60
|
+
* a non-render verdict, so the boundary fails closed twice over (defense in depth).
|
|
61
|
+
*/
|
|
62
|
+
export function CertReviewBlock({ placementId }: CertReviewBlockProps) {
|
|
63
|
+
const placements = useContext(CertVerifyContext)
|
|
64
|
+
const placement = placementId ? placements[placementId] : undefined
|
|
65
|
+
if (!placement) return null
|
|
66
|
+
return <CertReview verdict={placement.verdict} pageUrl={placement.pageUrl} />
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Structural shape of `@builder.io/sdk-react`'s `RegisteredComponent` — exactly the subset
|
|
71
|
+
* cert-block populates. Declared locally (not imported) so cert-block needs no Builder
|
|
72
|
+
* build- or runtime-dependency; the object below is assignable to Builder's
|
|
73
|
+
* `RegisteredComponent` at the consumer's `<Content customComponents={[...]}>` call site.
|
|
74
|
+
*/
|
|
75
|
+
export interface CertReviewBuilderRegistration {
|
|
76
|
+
component: typeof CertReviewBlock
|
|
77
|
+
name: string
|
|
78
|
+
inputs: Array<{ name: string; type: 'string'; required?: boolean; helperText?: string }>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The registration object to pass to Builder's `<Content customComponents={[...]}>`.
|
|
83
|
+
* Editors see a "CertREV Review" component with one input — the placement id. Because the
|
|
84
|
+
* block is matched by NAME at render time, this also works for content created via the
|
|
85
|
+
* Builder Write API (no visual-editor registration required for rendering).
|
|
86
|
+
*/
|
|
87
|
+
export const certReviewBuilderComponent: CertReviewBuilderRegistration = {
|
|
88
|
+
component: CertReviewBlock,
|
|
89
|
+
name: 'CertREV Review',
|
|
90
|
+
inputs: [
|
|
91
|
+
{
|
|
92
|
+
name: 'placementId',
|
|
93
|
+
type: 'string',
|
|
94
|
+
required: true,
|
|
95
|
+
helperText:
|
|
96
|
+
'CertREV placement id (which certified article this badge attests). The headless ' +
|
|
97
|
+
'loader verifies that placement’s signed envelope; only a render-verdict shows a badge.',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
}
|
package/src/contract/fixtures.ts
CHANGED
|
@@ -16,14 +16,17 @@
|
|
|
16
16
|
* end-to-end under the production `verifyEnvelope`, not a stand-in canonicalizer.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { generateKeyPairSync, type KeyObject
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
type ResolvePublicKeyByKid,
|
|
19
|
+
import { generateKeyPairSync, type KeyObject } from 'node:crypto'
|
|
20
|
+
import type {
|
|
21
|
+
CertDeliveryEnvelope,
|
|
22
|
+
CertPayload,
|
|
23
|
+
Ed25519PublicKeyInput,
|
|
24
|
+
ResolvePublicKeyByKid,
|
|
26
25
|
} from '@certrev/cert-contract'
|
|
26
|
+
// SIGN is Node-only and lives on the contract's signer subpath. Fixtures are themselves
|
|
27
|
+
// Node-only (they stand in for the issuer) and ship from the './fixtures' subpath, so the
|
|
28
|
+
// SDK's edge-safe main entry never pulls a Node builtin.
|
|
29
|
+
import { signPayloadEd25519 } from '@certrev/cert-contract/signer'
|
|
27
30
|
|
|
28
31
|
const FIXTURE_KID = 'certrev-fixture-key-1'
|
|
29
32
|
|
|
@@ -89,8 +92,9 @@ export interface SignedEnvelopeFixture {
|
|
|
89
92
|
export function makeSignedEnvelope(overrides: Partial<CertPayload> = {}, kid = FIXTURE_KID): SignedEnvelopeFixture {
|
|
90
93
|
const { publicKey, privateKey } = generateKeyPairSync('ed25519')
|
|
91
94
|
const payload = makeMockPayload(overrides)
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
// Sign the RFC-8785 canonical bytes via the contract's signer — the SAME bytes the
|
|
96
|
+
// edge-safe kernel recomputes on verify, so the fixture verifies end-to-end.
|
|
97
|
+
const sig = signPayloadEd25519(payload, privateKey)
|
|
94
98
|
|
|
95
99
|
const envelope: CertDeliveryEnvelope = {
|
|
96
100
|
payload,
|
package/src/contract/kernel.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* never directly from `@certrev/cert-contract`, so the whole SDK's dependency on the
|
|
8
8
|
* contract is funnelled through one module. The published package owns the envelope types
|
|
9
9
|
* (`CertDeliveryEnvelope`, `CertPayload`, `CertCredential`, …), the RFC-8785
|
|
10
|
-
* canonicalization (`canonicalPayloadBytes`), and the fail-closed kernel
|
|
11
|
-
* `renderVerdict`, `verifySignatureOnly`, `
|
|
10
|
+
* canonicalization (`canonicalPayloadBytes`), and the fail-closed WebCrypto kernel
|
|
11
|
+
* (`verifyEnvelope`, `renderVerdict`, `verifySignatureOnly`, `importEd25519PublicKey`).
|
|
12
12
|
*
|
|
13
13
|
* `VerdictKernel` is the ONE name NOT re-exported from the package: the contract exposes
|
|
14
14
|
* the kernel as free functions, and different edges bundle them with different key-resolver
|
package/src/index.ts
CHANGED
|
@@ -8,11 +8,16 @@
|
|
|
8
8
|
* graph, mergeable by @id (won't collide with Yoast).
|
|
9
9
|
* • Verify layer — `getVerifiedEnvelope` (fetch from metafield OR Delivery API + run
|
|
10
10
|
* the fail-closed VerdictKernel + cache), kid resolvers, the TTL/single-flight cache.
|
|
11
|
-
* • Contract types — re-exported from `@certrev/cert-contract` (
|
|
12
|
-
*
|
|
11
|
+
* • Contract types — re-exported from `@certrev/cert-contract` (the binding point in
|
|
12
|
+
* ./contract/kernel).
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* EDGE-RUNTIME SAFE. This main entry imports NO `node:crypto` and uses NO `Buffer`, so it
|
|
15
|
+
* loads + runs on edge/Workers runtimes (Shopify Oxygen, Cloudflare Workers, Vercel Edge).
|
|
16
|
+
* Two things are deliberately kept OFF this entry:
|
|
17
|
+
* • the Web Component (`<certrev-badge>`) → './webcomponent' subpath (touches
|
|
18
|
+
* `customElements`, undefined server-side);
|
|
19
|
+
* • the Node-only test/dev fixtures (`makeSignedEnvelope`, which signs with `node:crypto`)
|
|
20
|
+
* → './fixtures' subpath. Importing the main entry must never pull a Node builtin.
|
|
16
21
|
*/
|
|
17
22
|
|
|
18
23
|
// ── React components ──────────────────────────────────────────────────────────────
|
|
@@ -31,9 +36,9 @@ export {
|
|
|
31
36
|
type ResolvedDisplay,
|
|
32
37
|
resolveDisplay,
|
|
33
38
|
} from './components/format.js'
|
|
34
|
-
// ── Test/dev fixtures (mock facts + signed-envelope generator) ─────────────────────
|
|
35
|
-
export { FIXTURE_KID, makeMockPayload, makeSignedEnvelope, type SignedEnvelopeFixture } from './contract/fixtures.js'
|
|
36
39
|
// ── Contract surface (types + kernel) — re-export the binding point ────────────────
|
|
40
|
+
// NOTE: the test/dev fixtures (makeSignedEnvelope, makeMockPayload, FIXTURE_KID) are
|
|
41
|
+
// Node-only (they sign with node:crypto) and ship from the './fixtures' subpath, NOT here.
|
|
37
42
|
export {
|
|
38
43
|
CANONICALIZATION,
|
|
39
44
|
type CertContent,
|
|
@@ -80,14 +80,39 @@ function deliveryApiUrl(s: Extract<EnvelopeSource, { kind: 'delivery_api' }>): s
|
|
|
80
80
|
return `${base}/api/cert/v1/delivery/${encodeURIComponent(s.platform)}/${encodeURIComponent(s.externalId)}`
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* A short, fast, NON-cryptographic string hash (FNV-1a, 32-bit) — used ONLY to disambiguate
|
|
85
|
+
* cache keys, never for security. Edge-safe (no node:crypto). Distinct envelope values for
|
|
86
|
+
* the same placement must produce distinct keys so a push update (or, in tests/proofs,
|
|
87
|
+
* verifying several different envelopes against one render context) can't be served a stale
|
|
88
|
+
* verdict for a different envelope.
|
|
89
|
+
*/
|
|
90
|
+
function fnv1a(s: string): string {
|
|
91
|
+
let h = 0x811c9dc5
|
|
92
|
+
for (let i = 0; i < s.length; i++) {
|
|
93
|
+
h ^= s.charCodeAt(i)
|
|
94
|
+
// 32-bit FNV prime multiply via shifts (stays in 32-bit unsigned)
|
|
95
|
+
h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0
|
|
96
|
+
}
|
|
97
|
+
return h.toString(16).padStart(8, '0')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A stable string form of the metafield value for hashing (parsed → canonical-ish JSON). */
|
|
101
|
+
function metafieldValueKey(value: CertDeliveryEnvelope | string | null | undefined): string {
|
|
102
|
+
if (value == null) return 'null'
|
|
103
|
+
const s = typeof value === 'string' ? value : JSON.stringify(value)
|
|
104
|
+
return fnv1a(s)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Stable cache key per (source identity × render externalId × envelope value). */
|
|
84
108
|
function cacheKey(source: EnvelopeSource, ctx: RenderContext): string {
|
|
85
109
|
if (source.kind === 'delivery_api') {
|
|
86
110
|
return `api:${source.platform}:${source.externalId}`
|
|
87
111
|
}
|
|
88
|
-
// Metafield: key on the rendering identity
|
|
89
|
-
//
|
|
90
|
-
|
|
112
|
+
// Metafield: key on the rendering identity AND a short hash of the envelope value, so a
|
|
113
|
+
// push update (new envelope at the same placement) busts the entry, and verifying
|
|
114
|
+
// different envelopes against one render context never collides on a stale verdict.
|
|
115
|
+
return `mf:${ctx.platform}:${ctx.externalId}:${metafieldValueKey(source.value)}`
|
|
91
116
|
}
|
|
92
117
|
|
|
93
118
|
async function fetchFromDeliveryApi(
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* Ed25519 `x` (base64url raw key).
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import { base64urlDecode } from '@certrev/cert-contract'
|
|
20
21
|
import type { Ed25519PublicKeyInput, ResolvePublicKeyByKid } from '../contract/kernel.js'
|
|
21
22
|
import { TtlCache } from './cache.js'
|
|
22
23
|
|
|
@@ -52,8 +53,9 @@ interface PublishedKeySetDoc {
|
|
|
52
53
|
readonly keys: ReadonlyArray<PublishedJwk>
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
// Runtime-agnostic base64url decode from the contract (no `Buffer` — edge/Workers safe).
|
|
55
57
|
function base64urlToBytes(b64url: string): Uint8Array {
|
|
56
|
-
return
|
|
58
|
+
return base64urlDecode(b64url)
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
function jwkToInput(jwk: PublishedJwk): Ed25519PublicKeyInput | null {
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
-
* @certrev/cert-contract — INTERFACE STUB (delete on publish; see INTEGRATION below)
|
|
4
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
-
*
|
|
6
|
-
* The shared `CertDeliveryEnvelope` types + the fail-closed `VerdictKernel` live in
|
|
7
|
-
* the published `@certrev/cert-contract` package. That package is not yet on a registry
|
|
8
|
-
* this workspace can `pnpm install` from (it ships `file:`/workspace-only today), so
|
|
9
|
-
* `@certrev/cert-block` imports the contract surface from THIS local interface module
|
|
10
|
-
* instead. The types here are a byte-for-byte mirror of `cert-contract`'s `types.ts`
|
|
11
|
-
* (the settled, panel-revised shape: structured FACTS, no rendered JSON-LD in the
|
|
12
|
-
* payload; `subject` carries `logicalArticleId`, `canonicalUrls[]`, `installationId`,
|
|
13
|
-
* `contentDigest`).
|
|
14
|
-
*
|
|
15
|
-
* INTEGRATION (when @certrev/cert-contract publishes — see README "Integration TODOs"):
|
|
16
|
-
* 1. Delete this file and `./kernel-stub.ts`.
|
|
17
|
-
* 2. Replace every `from '../contract/kernel-contract.js'` with
|
|
18
|
-
* `from '@certrev/cert-contract'`.
|
|
19
|
-
* 3. The exported names here are deliberately identical to cert-contract's exports
|
|
20
|
-
* (`CertDeliveryEnvelope`, `CertPayload`, `CertVerdict`, `verifyEnvelope`,
|
|
21
|
-
* `renderVerdict`, `verifySignatureOnly`, `ResolvePublicKeyByKid`, `RenderContext`,
|
|
22
|
-
* `Ed25519PublicKeyInput`), so the swap is a find-and-replace of the import
|
|
23
|
-
* specifier — no call-site churn.
|
|
24
|
-
*
|
|
25
|
-
* Because this is type-only for everything the SDK consumes at COMPILE time, swapping
|
|
26
|
-
* the import path is the only change; the runtime kernel comes from `./kernel-stub.ts`
|
|
27
|
-
* today and from `@certrev/cert-contract` after publish.
|
|
28
|
-
*/
|
|
29
|
-
/** Bump on ANY breaking change to the payload shape or signing rules. */
|
|
30
|
-
export declare const CONTRACT_VERSION: 1;
|
|
31
|
-
export type ContractVersion = typeof CONTRACT_VERSION;
|
|
32
|
-
/** RFC 8785 JSON Canonicalization Scheme — the bytes that get signed/hashed. */
|
|
33
|
-
export declare const CANONICALIZATION: "RFC8785-JCS";
|
|
34
|
-
/** Ed25519 (PureEdDSA). */
|
|
35
|
-
export declare const SIGNATURE_ALG: "ed25519";
|
|
36
|
-
export type SignatureAlg = typeof SIGNATURE_ALG;
|
|
37
|
-
export interface CertSubject {
|
|
38
|
-
readonly platform: string;
|
|
39
|
-
readonly externalId: string;
|
|
40
|
-
readonly logicalArticleId: string;
|
|
41
|
-
readonly canonicalUrls: ReadonlyArray<string>;
|
|
42
|
-
readonly installationId: string | null;
|
|
43
|
-
readonly contentDigest: string | null;
|
|
44
|
-
}
|
|
45
|
-
export interface CertDisplayConfig {
|
|
46
|
-
readonly accentColor?: string | null;
|
|
47
|
-
readonly showExpertPhoto?: boolean;
|
|
48
|
-
readonly showAuthor?: boolean;
|
|
49
|
-
readonly showMemo?: boolean;
|
|
50
|
-
readonly badgeStyle?: 'full' | 'compact';
|
|
51
|
-
}
|
|
52
|
-
export interface CertCredential {
|
|
53
|
-
readonly abbreviation: string;
|
|
54
|
-
readonly fullName: string;
|
|
55
|
-
}
|
|
56
|
-
export interface CertContent {
|
|
57
|
-
readonly expert: {
|
|
58
|
-
readonly displayName: string;
|
|
59
|
-
readonly credentials: ReadonlyArray<CertCredential>;
|
|
60
|
-
readonly profileUrl: string | null;
|
|
61
|
-
readonly photoUrl: string | null;
|
|
62
|
-
};
|
|
63
|
-
readonly author: {
|
|
64
|
-
readonly name: string;
|
|
65
|
-
readonly title: string | null;
|
|
66
|
-
};
|
|
67
|
-
readonly memo: string | null;
|
|
68
|
-
readonly certifiedAt: string;
|
|
69
|
-
readonly contentModifiedAt: string | null;
|
|
70
|
-
readonly verifyUrl: string;
|
|
71
|
-
readonly display: CertDisplayConfig;
|
|
72
|
-
}
|
|
73
|
-
export interface CertLifecycle {
|
|
74
|
-
readonly issuedAt: string;
|
|
75
|
-
readonly expiresAt: string;
|
|
76
|
-
readonly revokedAt: string | null;
|
|
77
|
-
readonly revision: number;
|
|
78
|
-
}
|
|
79
|
-
export interface CertPayload {
|
|
80
|
-
readonly contractVersion: ContractVersion;
|
|
81
|
-
readonly certId: string;
|
|
82
|
-
readonly subject: CertSubject;
|
|
83
|
-
readonly content: CertContent;
|
|
84
|
-
readonly lifecycle: CertLifecycle;
|
|
85
|
-
}
|
|
86
|
-
export interface CertSignature {
|
|
87
|
-
readonly alg: SignatureAlg;
|
|
88
|
-
readonly kid: string;
|
|
89
|
-
readonly sig: string;
|
|
90
|
-
readonly signedAt: string;
|
|
91
|
-
}
|
|
92
|
-
export interface CertDeliveryEnvelope {
|
|
93
|
-
readonly payload: CertPayload;
|
|
94
|
-
readonly signature: CertSignature;
|
|
95
|
-
}
|
|
96
|
-
export type CertVerdict = {
|
|
97
|
-
readonly decision: 'render';
|
|
98
|
-
readonly payload: CertPayload;
|
|
99
|
-
} | {
|
|
100
|
-
readonly decision: 'suppress';
|
|
101
|
-
readonly reason: CertSuppressReason;
|
|
102
|
-
};
|
|
103
|
-
export type CertSuppressReason = 'unsupported_contract_version' | 'unsupported_alg' | 'unknown_key' | 'invalid_signature' | 'platform_mismatch' | 'subject_mismatch' | 'revoked' | 'expired' | 'content_drift';
|
|
104
|
-
/** The Ed25519 public-key encodings a kid resolver may hand the kernel. */
|
|
105
|
-
export type Ed25519PublicKeyInput = {
|
|
106
|
-
readonly format: 'spki-der';
|
|
107
|
-
readonly bytes: Uint8Array;
|
|
108
|
-
} | {
|
|
109
|
-
readonly format: 'spki-base64';
|
|
110
|
-
readonly base64: string;
|
|
111
|
-
} | {
|
|
112
|
-
readonly format: 'pem';
|
|
113
|
-
readonly pem: string;
|
|
114
|
-
} | {
|
|
115
|
-
readonly format: 'raw';
|
|
116
|
-
readonly bytes: Uint8Array;
|
|
117
|
-
};
|
|
118
|
-
/**
|
|
119
|
-
* A kid → public-key resolver. Returns a usable Ed25519 key for the given kid, or
|
|
120
|
-
* null/undefined when the kid is unknown (→ suppress 'unknown_key'). May be async so
|
|
121
|
-
* an edge can fetch + cache a published key set. The real kernel also accepts a Node
|
|
122
|
-
* KeyObject; we model only the SDK-relevant inputs here.
|
|
123
|
-
*/
|
|
124
|
-
export type ResolvePublicKeyByKid = (kid: string) => Promise<Ed25519PublicKeyInput | null | undefined> | Ed25519PublicKeyInput | null | undefined;
|
|
125
|
-
/** Edge-supplied context for the policy phase. */
|
|
126
|
-
export interface RenderContext {
|
|
127
|
-
readonly platform: string;
|
|
128
|
-
readonly externalId: string;
|
|
129
|
-
readonly liveContentHash?: string | null;
|
|
130
|
-
readonly now?: Date;
|
|
131
|
-
}
|
|
132
|
-
/** Full kernel: verify signature, then apply subject/lifecycle/drift policy. */
|
|
133
|
-
export type VerifyEnvelope = (envelope: CertDeliveryEnvelope, resolveKid: ResolvePublicKeyByKid, ctx: RenderContext) => Promise<CertVerdict>;
|
|
134
|
-
/** Phase-2-only policy over an already-verified payload. */
|
|
135
|
-
export type RenderVerdict = (payload: CertPayload, ctx: RenderContext) => CertVerdict;
|
|
136
|
-
/** Phase-1-only cryptographic verification. */
|
|
137
|
-
export type VerifySignatureOnly = (envelope: CertDeliveryEnvelope, resolveKid: ResolvePublicKeyByKid) => Promise<{
|
|
138
|
-
ok: true;
|
|
139
|
-
} | {
|
|
140
|
-
ok: false;
|
|
141
|
-
reason: CertSuppressReason;
|
|
142
|
-
}>;
|
|
143
|
-
/**
|
|
144
|
-
* The kernel function set as a single named interface — what the SDK depends on at
|
|
145
|
-
* the value level. `@certrev/cert-contract` exposes these as free functions; the SDK
|
|
146
|
-
* binds them through `./kernel-stub` today and re-points to the real exports on
|
|
147
|
-
* publish.
|
|
148
|
-
*/
|
|
149
|
-
export interface VerdictKernel {
|
|
150
|
-
readonly verifyEnvelope: VerifyEnvelope;
|
|
151
|
-
readonly renderVerdict: RenderVerdict;
|
|
152
|
-
readonly verifySignatureOnly: VerifySignatureOnly;
|
|
153
|
-
}
|
|
154
|
-
//# sourceMappingURL=kernel-contract.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kernel-contract.d.ts","sourceRoot":"","sources":["../../src/contract/kernel-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
-
* @certrev/cert-contract — INTERFACE STUB (delete on publish; see INTEGRATION below)
|
|
4
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
-
*
|
|
6
|
-
* The shared `CertDeliveryEnvelope` types + the fail-closed `VerdictKernel` live in
|
|
7
|
-
* the published `@certrev/cert-contract` package. That package is not yet on a registry
|
|
8
|
-
* this workspace can `pnpm install` from (it ships `file:`/workspace-only today), so
|
|
9
|
-
* `@certrev/cert-block` imports the contract surface from THIS local interface module
|
|
10
|
-
* instead. The types here are a byte-for-byte mirror of `cert-contract`'s `types.ts`
|
|
11
|
-
* (the settled, panel-revised shape: structured FACTS, no rendered JSON-LD in the
|
|
12
|
-
* payload; `subject` carries `logicalArticleId`, `canonicalUrls[]`, `installationId`,
|
|
13
|
-
* `contentDigest`).
|
|
14
|
-
*
|
|
15
|
-
* INTEGRATION (when @certrev/cert-contract publishes — see README "Integration TODOs"):
|
|
16
|
-
* 1. Delete this file and `./kernel-stub.ts`.
|
|
17
|
-
* 2. Replace every `from '../contract/kernel-contract.js'` with
|
|
18
|
-
* `from '@certrev/cert-contract'`.
|
|
19
|
-
* 3. The exported names here are deliberately identical to cert-contract's exports
|
|
20
|
-
* (`CertDeliveryEnvelope`, `CertPayload`, `CertVerdict`, `verifyEnvelope`,
|
|
21
|
-
* `renderVerdict`, `verifySignatureOnly`, `ResolvePublicKeyByKid`, `RenderContext`,
|
|
22
|
-
* `Ed25519PublicKeyInput`), so the swap is a find-and-replace of the import
|
|
23
|
-
* specifier — no call-site churn.
|
|
24
|
-
*
|
|
25
|
-
* Because this is type-only for everything the SDK consumes at COMPILE time, swapping
|
|
26
|
-
* the import path is the only change; the runtime kernel comes from `./kernel-stub.ts`
|
|
27
|
-
* today and from `@certrev/cert-contract` after publish.
|
|
28
|
-
*/
|
|
29
|
-
/** Bump on ANY breaking change to the payload shape or signing rules. */
|
|
30
|
-
export const CONTRACT_VERSION = 1;
|
|
31
|
-
/** RFC 8785 JSON Canonicalization Scheme — the bytes that get signed/hashed. */
|
|
32
|
-
export const CANONICALIZATION = 'RFC8785-JCS';
|
|
33
|
-
/** Ed25519 (PureEdDSA). */
|
|
34
|
-
export const SIGNATURE_ALG = 'ed25519';
|
|
35
|
-
//# sourceMappingURL=kernel-contract.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kernel-contract.js","sourceRoot":"","sources":["../../src/contract/kernel-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,yEAAyE;AACzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAU,CAAA;AAG1C,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAsB,CAAA;AAEtD,2BAA2B;AAC3B,MAAM,CAAC,MAAM,aAAa,GAAG,SAAkB,CAAA"}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
-
* VerdictKernel — LOCAL STUB (delete on publish; mirrors @certrev/cert-contract)
|
|
4
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
-
*
|
|
6
|
-
* A faithful, self-contained implementation of the cert-contract VerdictKernel so the
|
|
7
|
-
* SDK's verify layer + tests run NOW, before `@certrev/cert-contract` publishes to a
|
|
8
|
-
* registry this workspace installs from. The behavior is identical to the real kernel:
|
|
9
|
-
* • Ed25519 detached signature verified over the RFC-8785 (JCS) canonical bytes of
|
|
10
|
-
* `payload` (lexicographic key order, minimal whitespace, UTF-8).
|
|
11
|
-
* • Fail-closed pipeline: shape → alg → kid → signature → platform → subject →
|
|
12
|
-
* revoked → expired → drift → render. Any failure short-circuits to `suppress`.
|
|
13
|
-
* • Never throws on a bad envelope — a malformed input / throwing resolver fails
|
|
14
|
-
* closed to `suppress`, not to an exception a caller might swallow into a render.
|
|
15
|
-
*
|
|
16
|
-
* The byte-identical canonicalization across PHP / Node is what makes the credential
|
|
17
|
-
* portable; here we hand-roll a deterministic JCS serializer (sufficient for the
|
|
18
|
-
* envelope's all-string/number/bool/array/object shape — no exotic numbers) so the SDK
|
|
19
|
-
* carries no production crypto-canonicalization of its own that could DRIFT from the
|
|
20
|
-
* contract. On publish, the real `canonicalize` (RFC 8785) from cert-contract is used.
|
|
21
|
-
*
|
|
22
|
-
* INTEGRATION: delete this file + `./kernel-contract.ts`; import `verifyEnvelope`,
|
|
23
|
-
* `renderVerdict`, `verifySignatureOnly`, `toEd25519PublicKey` from
|
|
24
|
-
* `@certrev/cert-contract`. See README "Integration TODOs".
|
|
25
|
-
*/
|
|
26
|
-
import { type KeyObject } from 'node:crypto';
|
|
27
|
-
import type { CertDeliveryEnvelope, CertPayload, CertSuppressReason, CertVerdict, Ed25519PublicKeyInput, RenderContext, ResolvePublicKeyByKid } from './kernel-contract.js';
|
|
28
|
-
/** Mirror of cert-contract's `toEd25519PublicKey` for the input encodings the SDK uses. */
|
|
29
|
-
declare function toEd25519PublicKey(input: Ed25519PublicKeyInput): KeyObject;
|
|
30
|
-
type CryptoResult = {
|
|
31
|
-
ok: true;
|
|
32
|
-
} | {
|
|
33
|
-
ok: false;
|
|
34
|
-
reason: CertSuppressReason;
|
|
35
|
-
};
|
|
36
|
-
/** Phase-2-only policy verdict over an already-authentic payload. Pure + synchronous. */
|
|
37
|
-
export declare function renderVerdict(payload: CertPayload, ctx: RenderContext): CertVerdict;
|
|
38
|
-
/** Phase-1-only cryptographic verification. */
|
|
39
|
-
export declare function verifySignatureOnly(envelope: CertDeliveryEnvelope, resolveKid: ResolvePublicKeyByKid): Promise<CryptoResult>;
|
|
40
|
-
/** Full kernel: verify signature, then apply policy. Fail-closed, never throws. */
|
|
41
|
-
export declare function verifyEnvelope(envelope: CertDeliveryEnvelope, resolveKid: ResolvePublicKeyByKid, ctx: RenderContext): Promise<CertVerdict>;
|
|
42
|
-
/** Re-export for tests that need to build a public key from raw/spki bytes. */
|
|
43
|
-
export { toEd25519PublicKey };
|
|
44
|
-
//# sourceMappingURL=kernel-stub.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kernel-stub.d.ts","sourceRoot":"","sources":["../../src/contract/kernel-stub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAmB,KAAK,SAAS,EAAwB,MAAM,aAAa,CAAA;AACnF,OAAO,KAAK,EACX,oBAAoB,EACpB,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,qBAAqB,EACrB,aAAa,EACb,qBAAqB,EACrB,MAAM,sBAAsB,CAAA;AA4C7B,2FAA2F;AAC3F,iBAAS,kBAAkB,CAAC,KAAK,EAAE,qBAAqB,GAAG,SAAS,CAkBnE;AAUD,KAAK,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,CAAA;AA6B5E,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,aAAa,GAAG,WAAW,CAoBnF;AAED,+CAA+C;AAC/C,wBAAsB,mBAAmB,CACxC,QAAQ,EAAE,oBAAoB,EAC9B,UAAU,EAAE,qBAAqB,GAC/B,OAAO,CAAC,YAAY,CAAC,CAEvB;AAED,mFAAmF;AACnF,wBAAsB,cAAc,CACnC,QAAQ,EAAE,oBAAoB,EAC9B,UAAU,EAAE,qBAAqB,EACjC,GAAG,EAAE,aAAa,GAChB,OAAO,CAAC,WAAW,CAAC,CAWtB;AAED,+EAA+E;AAC/E,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
|