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
23 changes: 17 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,17 @@ jobs:
if: env.NUM_PROVIDERS > 0
uses: actions/setup-go@v6
with:
go-version: ^1.19
go-version-file: go.mod
cache-dependency-path: signing/go.sum
check-latest: true
cache: true
id: go

- name: "[PACKAGE] build sign tool"
if: env.NUM_PROVIDERS > 0
run: |
go build -o ed25519tool ./signing/Ed25519/app/cmd/main
cd signing
go build -o ../pqsig ./cmd/pqsig

- name: "[PACKAGE] prepare provider dirs"
if: env.NUM_PROVIDERS > 0
Expand All @@ -94,12 +96,17 @@ jobs:
- name: "[PACKAGE] sign provider docs"
if: env.NUM_PROVIDERS > 0
env:
SIGNING_VERSION: v1
SIGNING_CERT: ${{ secrets.V1_SIGNING_CERT }}
SIGNING_PRIV_KEY: '${{ secrets.V1_SIGNING_PRIV_KEY }}'
SIGNING_PUB_KEY: ${{ secrets.V1_SIGNING_PUB_KEY }}
STACKQL_SIGNING_KID: stackql-2026-01
STACKQL_SIGNING_SEED: ${{ github.event_name != 'pull_request' && secrets.MLDSA_SIGNING_SEED || '' }}
run: |
# PRs exercise the complete packaging path without a production signing key.
if [ "${{ github.event_name }}" = "pull_request" ]; then
export STACKQL_SIGNING_SEED="$(python -c 'import secrets; print(secrets.token_hex(32))')"
fi
python scripts/package/sign-provider-docs.py
if [ "${{ github.event_name }}" != "pull_request" ]; then
find signed/providers/src -type f -name '*.yaml' -exec ./pqsig verify-doc {} +
fi

- name: "[PACKAGE] package provider docs"
if: env.NUM_PROVIDERS > 0
Expand All @@ -122,6 +129,10 @@ jobs:
do
echo "testing ${provider}..."
sh test-provider.sh "$provider" false "$providersdir"
if [ "${{ github.event_name }}" != "pull_request" ]; then
python verify-provider.py "$provider" \
--registry-root "$providersdir" --stackql-bin "$(pwd)/stackql"
fi
done < ../providers.txt

#
Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/signing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Provider document signatures

on:
pull_request:
branches: [main, dev]
push:
branches: [main, dev]
workflow_dispatch:

permissions:
contents: read

jobs:
signing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache-dependency-path: |
go.sum
signing/go.sum
- run: go test ./... && go vet ./...
- run: go test ./... && go vet ./...
working-directory: signing
env:
PQSIG_PROVIDER_ROOT: ${{ github.workspace }}/providers/src
- run: python -m unittest discover -s scripts/package -p 'test_*.py'
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
ed25519tool
/pqsig
/pqsig.exe
# Preserve ignored local key material left by the retired tool.
/signing/Ed25519/
.DS_Store
scripts/*/node_modules/
scripts/*/package-lock.json
Expand Down
46 changes: 0 additions & 46 deletions .vscode/launch.json

This file was deleted.

8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

A repository of `provider` interface documents supporting [stackql](https://stackql.io/) ([stackql GitHub repo](https://github.com/stackql/stackql)).

> [!IMPORTANT]
> New and updated provider revisions published after the signing cutover use
> quantum-safe ML-DSA-65 signatures. Signature verification requires
> **StackQL v0.13 or later** and remains opt-in, disabled by default. Existing
> provider revisions gain the new signatures as they are updated or republished;
> legacy revisions must be republished before they can pass the new verifier.
> See [signing and verification](docs/signing-and-verification.md).

## Table of Contents

<!-- BEGIN TABLE OF CONTENTS -->
Expand Down
102 changes: 34 additions & 68 deletions docs/command-line-tool.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,41 @@
# Provider signing command line

# Working with the command line tool
Requires Go 1.27.3 or later:

To build the signing and verification tool:

```
go build -o ed25519tool ./signing/Ed25519/app/cmd/main
```sh
cd signing
go build -o ../pqsig ./cmd/pqsig
cd ..
```

Then, from the repository root directory:

```bash
. ./scripts/setup-env.sh

CREDENTIALS_DIR="${REPOSITORY_ROOT_DIR}/signing/Ed25519/setup/scratchpad"
TESTING_INPUT_DIR="${REPOSITORY_ROOT_DIR}/signing/Ed25519/test"
TESTING_OUTPUT_DIR="${CREDENTIALS_DIR}"

PRIVATE_KEY_FILE="smoke-testing-private-key.pem"

PUBLIC_KEY_FILE="smoke-testing-public-key.pem"

CERT_FILE="smoke-testing-cert.pem"

CSR_FILE="smoke-testing.csr"

## Create key pair and cert
./ed25519tool createkeys ${CREDENTIALS_DIR}/${PRIVATE_KEY_FILE} ${CREDENTIALS_DIR}/${PUBLIC_KEY_FILE} ${CREDENTIALS_DIR}/${CERT_FILE} ${CREDENTIALS_DIR}/${CSR_FILE}

## Store the private key in an env var
SIGNING_KEY_SECRET=$(cat ${CREDENTIALS_DIR}/${PRIVATE_KEY_FILE})

## Sign some files
./ed25519tool sign --privatekeyenvvar="SIGNING_KEY_SECRET" --signaturetime="Jan 2 15:04:05 2006" ${TESTING_INPUT_DIR}/sample-infile.txt -o ${TESTING_OUTPUT_DIR}/old-timestamp-sample-infile.txt.sig

./ed25519tool sign --privatekeyenvvar="SIGNING_KEY_SECRET" --signaturetime="Jan 2 15:04:05 2023" ${TESTING_INPUT_DIR}/sample-infile.txt -o ${TESTING_OUTPUT_DIR}/acceptable-timestamp-sample-infile.txt.sig

./ed25519tool sign --privatekeyenvvar="SIGNING_KEY_SECRET" --signaturetime="now" ${TESTING_INPUT_DIR}/sample-infile.txt -o ${TESTING_OUTPUT_DIR}/now-timestamp-sample-infile.txt.sig
Provision a new production key without writing or displaying the private seed:

./ed25519tool sign --privatekeyenvvar="SIGNING_KEY_SECRET" --signaturetime="Jan 2 15:04:05 2033" ${TESTING_INPUT_DIR}/sample-infile.txt -o ${TESTING_OUTPUT_DIR}/future-timestamp-sample-infile.txt.sig

## Now, verify

## will succeed
./ed25519tool verify --publickeypath=${CREDENTIALS_DIR}/${PUBLIC_KEY_FILE} ${TESTING_INPUT_DIR}/sample-infile.txt ${TESTING_OUTPUT_DIR}/old-timestamp-sample-infile.txt.sig

## should and will fail with timestamp message
./ed25519tool certverify --localcerts.signingbundle=${CREDENTIALS_DIR}/${CERT_FILE} --localcerts.cabundle=${CREDENTIALS_DIR}/${CERT_FILE} ${TESTING_INPUT_DIR}/sample-infile.txt ${TESTING_OUTPUT_DIR}/old-timestamp-sample-infile.txt.sig

## will succeed
./ed25519tool verify --publickeypath=${CREDENTIALS_DIR}/${PUBLIC_KEY_FILE} ${TESTING_INPUT_DIR}/sample-infile.txt ${TESTING_OUTPUT_DIR}/acceptable-timestamp-sample-infile.txt.sig

## will succeed
./ed25519tool certverify --localcerts.signingbundle=${CREDENTIALS_DIR}/${CERT_FILE} --localcerts.cabundle=${CREDENTIALS_DIR}/${CERT_FILE} ${TESTING_INPUT_DIR}/sample-infile.txt ${TESTING_OUTPUT_DIR}/acceptable-timestamp-sample-infile.txt.sig

## will succeed
./ed25519tool verify --publickeypath=${CREDENTIALS_DIR}/${PUBLIC_KEY_FILE} ${TESTING_INPUT_DIR}/sample-infile.txt ${TESTING_OUTPUT_DIR}/now-timestamp-sample-infile.txt.sig

## will succeed
./ed25519tool certverify --localcerts.signingbundle=${CREDENTIALS_DIR}/${CERT_FILE} --localcerts.cabundle=${CREDENTIALS_DIR}/${CERT_FILE} ${TESTING_INPUT_DIR}/sample-infile.txt ${TESTING_OUTPUT_DIR}/now-timestamp-sample-infile.txt.sig


## all good with self signed CA if we supply it as a command line arg
./ed25519tool certverify --localcerts.signingbundle=${CREDENTIALS_DIR}/${CERT_FILE} --localcerts.cabundle=${CREDENTIALS_DIR}/${CERT_FILE} ${TESTING_INPUT_DIR}/sample-infile.txt --strict=true ${TESTING_OUTPUT_DIR}/acceptable-timestamp-sample-infile.txt.sig

## will fail due to self-signed certificate in strict mode, if we do not supply same as CA
./ed25519tool certverify --localcerts.signingbundle=${CREDENTIALS_DIR}/${CERT_FILE} ${TESTING_INPUT_DIR}/sample-infile.txt --strict=true ${TESTING_OUTPUT_DIR}/acceptable-timestamp-sample-infile.txt.sig

## will succeed
./ed25519tool verify --publickeypath=${CREDENTIALS_DIR}/${PUBLIC_KEY_FILE} ${TESTING_INPUT_DIR}/sample-infile.txt ${TESTING_OUTPUT_DIR}/future-timestamp-sample-infile.txt.sig
```sh
./pqsig keygen -kid stackql-2026-01 \
-public-keys new-public-keys.json \
-github-secret MLDSA_SIGNING_SEED \
-repo stackql/stackql-provider-registry
```

## should and will fail with timestamp message
./ed25519tool certverify --localcerts.signingbundle=${CREDENTIALS_DIR}/${CERT_FILE} --localcerts.cabundle=${CREDENTIALS_DIR}/${CERT_FILE} ${TESTING_INPUT_DIR}/sample-infile.txt ${TESTING_OUTPUT_DIR}/future-timestamp-sample-infile.txt.sig
The command generates an ML-DSA-65 key, pipes its hex-encoded 32-byte seed
directly to `gh secret set`, and writes only the public key. It refuses to
overwrite the public key file. Review and add the public entry to
[`signing/pqsig/keys.json`](../signing/pqsig/keys.json); never commit a seed.
For rotation, add rather than replace public entries until old archives expire.
Ship the updated verifier before using the new key to publish documents.

Sign in place, or write to a separate output:

```sh
# STACKQL_SIGNING_SEED is supplied by the secret manager, not a command argument.
./pqsig sign-doc -kid stackql-2026-01 provider.yaml
./pqsig sign-doc -kid stackql-2026-01 -out signed.yaml provider.yaml
./pqsig verify-doc signed.yaml
./pqsig verify-doc -public-keys custom-public-keys.json signed.yaml
./pqsig validate-doc provider.yaml
```

```
`sign-doc` checks the generated signature before replacing the output. It does
not assert that the key is an embedded production key; `verify-doc` does that.
CI runs both before publishing. Missing seeds, unknown keys, malformed documents
and invalid signatures exit nonzero. See [the format and policy](signing-and-verification.md).
Loading
Loading