Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,48 @@ jobs:
- name: Build search index
run: npx pagefind --site public

# TODO(htmltrust): re-wire signed-section verification once the signing
# pipeline is integrated with the Hugo Blox build. The previous site
# signed its own pages at the layout level; the new site does not yet.
- name: Verify placeholder signed-sections exist
# Hugo emits <signed-section> placeholders with data-htmltrust-placeholder
# markers on every page that opts in via `htmltrust.sign: true` front-matter.
# The signer (next step) will replace them with real content-hash / signature
# / keyid / algorithm attributes. Six content pages currently opt in:
# /spec/, /architecture/, /implementation/, /use-cases/, /faq/,
# /blog/paper-published/.
run: |
count=$(grep -rl '<signed-section' public/ | wc -l)
echo "Found $count pages with <signed-section> placeholders"
if [ "$count" -lt 6 ]; then
echo "ERROR: Expected at least 6 pages with signed-section placeholders"
exit 1
fi

- name: Install htmltrust-sign
run: go install github.com/HTMLTrust/htmltrust-hugo/cmd/htmltrust-sign@latest

- name: Sign content
env:
HTMLTRUST_SIGNING_KEY: ${{ secrets.HTMLTRUST_SIGNING_KEY }}
run: |
htmltrust-sign \
--dir public \
--keyid did:web:jason-grey.com \
--domain www.htmltrust.org \
-v

- name: Verify signed sections are complete
run: |
if grep -rq 'data-htmltrust-placeholder' public/; then
echo "ERROR: placeholder markers remain - signer did not run on all sections"
exit 1
fi
for f in $(grep -rl '<signed-section' public/); do
case "$f" in *.html) ;; *) continue ;; esac
grep -q 'content-hash="sha256:' "$f" || { echo "MISSING content-hash in $f"; exit 1; }
grep -q 'signature="' "$f" || { echo "MISSING signature in $f"; exit 1; }
grep -q 'keyid="did:web:' "$f" || { echo "MISSING keyid in $f"; exit 1; }
grep -q 'algorithm="ed25519"' "$f" || { echo "MISSING algorithm in $f"; exit 1; }
done
echo "OK: all signed-section elements carry the four spec-required attributes"

- uses: actions/upload-artifact@v7
with:
Expand Down
2 changes: 1 addition & 1 deletion config/_default/params.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -285,5 +285,5 @@ hugoblox:
# `htmltrust.sign: true` and may add per-page `htmltrust.claims`.
# ──────────────────────────────────────────────────────────────────────────────
htmltrust:
keyid: "did:web:www.htmltrust.org"
keyid: "did:web:jason-grey.com"
algorithm: "ed25519"
4 changes: 2 additions & 2 deletions content/architecture/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ sequenceDiagram
C->>B: Request signature over payload
B->>B: Sign with private key
B-->>C: signature
C->>C: Embed <signed-section>
C->>C: Embed in signed-section
C-->>A: Publish page
C-->>D: Publish hash + keyid (optional)
```
Expand All @@ -83,7 +83,7 @@ sequenceDiagram
participant K as Key resolver
participant D as Directory (optional)
U->>P: GET page
P-->>U: HTML with <signed-section>
P-->>U: HTML with signed-section
U->>U: Canonicalize text → hash
U->>K: Resolve keyid
K-->>U: Public key
Expand Down
63 changes: 63 additions & 0 deletions layouts/faq/list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{{- define "main" -}}

<div class="mx-auto flex max-w-screen-xl">
{{ partial "components/sidebar.html" (dict "context" . "no_sidebar" false) }}
<article class="w-full min-w-0 min-h-[calc(100vh-var(--navbar-height))] px-6 pt-4 pb-8 md:px-12">
<div class="max-w-4xl mx-auto">

{{ if (.Params.show_breadcrumb | default true) }}
<div class="mb-4">
{{ partial "components/breadcrumb.html" . }}
</div>
{{ end }}

<div class="prose prose-slate lg:prose-xl dark:prose-invert mb-8">
<h1>{{ .Title }}</h1>
{{ partial "htmltrust-signed-section.html" . }}
</div>

{{/* List all FAQ pages */}}
{{ $pages := .Pages.ByDate.Reverse }}

<div class="grid gap-6 md:grid-cols-2">
{{ range $pages }}
<a href="{{ .RelPermalink }}" class="block p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-primary-500 dark:hover:border-primary-500 transition-all hover:shadow-md">
<h3 class="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2">
{{ .Title }}
</h3>

{{ with .Summary }}
<p class="text-gray-600 dark:text-gray-400 mb-3">
{{ . }}
</p>
{{ end }}

<div class="flex items-center justify-between text-sm text-gray-500 dark:text-gray-400">
<span>
{{ if .Params.faqs }}
{{ len .Params.faqs }} {{ T "questions_count" | default "questions" }}
{{ else if .Pages }}
{{ len .Pages }} {{ T "questions_count" | default "questions" }}
{{ end }}
</span>
<span class="text-primary-600 dark:text-primary-400 font-medium">
{{ T "read_more" | default "View" }} →
</span>
</div>
</a>
{{ end }}
</div>

{{ if eq (len $pages) 0 }}
<div class="text-center py-12 text-gray-500 dark:text-gray-400">
<p class="text-lg">{{ T "no_questions_yet" | default "No FAQ pages available yet." }}</p>
</div>
{{ end }}

{{ partial "components/paginator" . }}
</div>
</article>
</div>

{{- end -}}