Skip to content
Sun Sub

Report verifier

Check a Sun Sub hours report

Upload the PDF you were sent, or type the report id printed in its footer. You will see whether it is the exact file we issued, whether it has been withdrawn, and the numbers our server holds for it. No sign-in needed.

Check a PDF

The whole file is compared with the one we issued, so this is the check to use when someone hands you a report.

Upload the file exactly as you received it. Re-saving or annotating it changes the bytes and gives Record valid, file altered even though nothing was forged; printing to PDF strips the signed record and gives Not verified.

Look up a report id

Shows the totals we hold for a report (not the per-shift record). This is also where the QR code on the report leads.

Printed in the footer of every page of the report.

How it works, honestly

Any change to the file is detected by the online verifier (byte-exact match against the file we issued). The embedded record is digitally signed and can be checked offline with our public key. The printed pages themselves are not signed — always trust the verifier's numbers, not the numbers printed on a file that fails the byte-exact check.

When a worker creates a report, our server writes the report's data as a fixed record, signs that record with its private key (RSA-PSS with SHA-256), embeds the record and signature in the PDF as an attachment (sunsub-hours.json), and stores a fingerprint of the exact file it handed out. Every check made here is logged (verdict, time, network address) so forged files can be spotted. The result is one of:

VerdictMeaning
AuthenticThe file is byte-for-byte the one we issued. Its numbers can be relied on.
RevokedWe issued this report but have since withdrawn it (for example the account holder asked us to, or it was found to rest on false records). Do not rely on it.
Record valid, file alteredThe signed record inside the file is genuine and matches a report we issued, but the file itself is not the one we handed out: something about it has changed. The verifier shows the numbers we hold; use those, not the printed pages.
Unknown recordThe file carries a valid-looking signature but we have no matching report. Treat it as forged; we are alerted when this happens.
Not verifiedThe file could not be checked: no signed record inside it, a signature that does not check out, or a file that is not a Sun Sub report at all.

Verify the signed record offline

Anyone can check the embedded record without trusting this website, using only our published public key. Our keys are listed at https://estimating.sunpaintingva.com/sunsub/keys; the report names the key it was signed with by its kid in the footer and inside the attachment. With pdfdetach (poppler-utils), jq and openssl:

# 1. Pull the attachment out of the PDF: it is named sunsub-hours.json
pdfdetach -saveall report.pdf

# 2. Fetch the public key the record names ("kid" in the file)
KID=$(jq -r .kid sunsub-hours.json)
curl -fsSO https://estimating.sunpaintingva.com/sunsub/keys/$KID.pem

# 3. Split the envelope into the exact signed bytes and the signature
jq -rj .payload_canonical sunsub-hours.json > payload.json
jq -r  .signature_b64     sunsub-hours.json | base64 -d > sig.bin

# 4. Check the signature: RSA-PSS, SHA-256, salt length 32 (the digest length)
openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 \
  -verify $KID.pem -signature sig.bin payload.json
# → "Verified OK"

# 5. The record fingerprint printed on every page is the SHA-256 of those bytes
sha256sum payload.json

payload.json is the signed record itself (worker, period, totals and every shift with its provenance), so once step 4 says Verified OK you can read the numbers from it directly.

What offline checking cannot tell you. A valid signature proves the attached record was produced and signed by our server and has not been altered since. It does not prove that the printed pages of the PDF match that record (the pages are not signed — compare them yourself, or trust the record), and it cannot tell you whether the report has since been revoked. Only the online check does the byte-exact comparison and the revocation lookup.

For systems that check many reports

The same check is available as JSON. Send the PDF as a multipart field named report, or as the raw request body with Content-Type: application/pdf. Limits: 3 MB per file, 30 checks per 15 minutes per network address.

curl -sS -F report=@report.pdf https://estimating.sunpaintingva.com/api/sunsub/verify
# or
curl -sS -H 'Content-Type: application/pdf' --data-binary @report.pdf https://estimating.sunpaintingva.com/api/sunsub/verify

The response carries verdict (authentic, revoked, record_valid_file_altered, unknown_record or not_verified), a reason for the last of those, the individual checks, the file's file_sha256, and — when we hold a matching record — the public report projection and its record_url. Public keys: GET https://estimating.sunpaintingva.com/sunsub/keys (JSON) and GET https://estimating.sunpaintingva.com/sunsub/keys/<kid>.pem.

Questions about a report? support@sunpaintingva.com · (434) 207-2025. Sun Sub is made by Sun Painting Virginia; reports are issued for the worker named on them and the verifier states only what our server can vouch for.