Skip to content

Publish the signing rules as a library - #126

Open
beetlebugorg wants to merge 8 commits into
mainfrom
feat/signing-library
Open

Publish the signing rules as a library#126
beetlebugorg wants to merge 8 commits into
mainfrom
feat/signing-library

Conversation

@beetlebugorg

@beetlebugorg beetlebugorg commented Sep 9, 2026

Copy link
Copy Markdown
Owner

What

libmoddims_sign holds the /dims4/ and /dims5/ signing rules behind a C99 header that links libcrypto. The module compiles the same source: src/signature.c is three wrappers over it, and src/handler.c calls the /dims4/ digest and comparison. The install writes the header under include/dims, the archive and the shared library under lib, dims-sign under bin, and dims-sign.pc under lib/pkgconfig.

dims-sign signs a URL, prints the /dims5/ message behind one, and compares the signature a URL holds against the one the key produces.

The library also encrypts. dims_sign_dims5_eurl_url and dims_sign_dims4_eurl_url sign a URL and put the encrypted source in place of url. /dims5/ derives with HKDF-SHA256 under the salt go-dims and encrypts with AES-128-GCM. /dims4/ derives from SHA-1 of the client secret and encrypts with AES-128-ECB, or with GCM under DimsEncryptionAlgorithm.

test/fixtures/signing.tsv holds forty two records. A signing record has a signed URL, a canonical query, and a message. An eurl record has a ciphertext and the plain URL it decrypts to. Two unit cases assert every field against the library and check that dims-sign --fixture reproduces the file. One request case sends each signed URL to the running module and reads dims_signature_checks_total to confirm every one verified, and two more send a URL the library encrypted.

test/endurance/sign.c calls the library for its query escape and its two digests.

Why

Issue #124. Four copies of these rules exist here: src/signature.c, src/handler.c, test/lib/signing.c, and test/endurance/sign.c. go-dims holds a fifth. A Go client and a Java client follow, and the fixture file is what all three check themselves against.

TestDims5EurlUrlVerifies and TestDims4EurlUrlVerifies prove the two sides meet: the library encrypts, and the module decrypts across a request. The eurl-dims5-gcm fixture value came from the independent implementation in test/compose.yaml, and eurl-dims4-ecb came from openssl enc -aes-128-ecb.

The request case found a disagreement. src/handler.c writes every plus in a /dims4/ image URL as a space, and the documentation did not state it. docs/docs/endpoints/dims4.md now does, along with the rule that a _keys value goes into the message as it appears in the query string.

Verify

make -C test test

326 of 326 pass. The existing /dims4/ and /dims5/ cases are unchanged, so a pass means the module emits the same bytes.

make -C test sanitize

128 in-process cases under AddressSanitizer, UndefinedBehaviorSanitizer, and LeakSanitizer.

To see the fixture file work as a golden, change one character of a signature in test/fixtures/signing.tsv. unit.TestSigningFixtures names the record and unit.TestSigningFixtureRoundTrip names the line.

libmoddims_sign holds the /dims4/ and /dims5/ rules behind a C99 header
that links libcrypto. A caller outside the module builds against it
without APR and without httpd. One object library compiles the source
once, and the archive and the shared object are built from those
objects.

Two calls sign a URL. The rest of the header is what the module and a
verifier need: the escape, the canonical query, the two digests, the two
comparisons, and the field check.

No other target links it yet.
test/unit/test_sign.c covers the escape, the canonical query, the two
digests, the two comparisons, and the two URL signers. Each digest has a
known vector.

One case per clause the header names under DIMS_SIGN_BAD_URL, and one
for DIMS_SIGN_BAD_FIELD. A failed call leaves the out parameter
untouched, and dims_sign_free accepts NULL.
src/signature.c is three wrappers over the library: dims_signature,
dims_signature_equal, and dims_signature_field_ok. src/handler.c calls
dims_sign_dims4_digest and dims_sign_dims4_equal.

The module keeps its own prefix handling. It reads the commands out of
r->uri and passes them to dims_signature.

test/unit/test_signature.c is removed. test/unit/test_sign.c covers the
same rules against the library.
dims-sign signs a URL, prints the /dims5/ message behind one, and
compares the signature a URL holds against the one the key produces. The
module logs "Key mismatch" without the digests. This prints both.

The key comes from --key-file or from DIMS_SIGNING_KEY. A key given on
the command line is visible to every user of the machine through ps, so
there is no --key flag.

--message requires --dims5, because a /dims4/ message contains the
client secret. sign/src/dims_sign_internal.h declares
dims_sign_dims4_message for the command and stays out of the install.
--fixture fills in the computed fields of the signing fixtures.
test/fixtures/signing.tsv holds thirty eight records. A person writes
case, endpoint, prefix, key, and input. dims-sign --fixture writes
signed, query, message, and error. The keys, the client id, and the
expiry match test/conf/dims-test.conf.

test/lib/fixtures.c reads the file for both suites.
test/unit/test_fixtures.c asserts every field against the library and
names the record that differs. A second case runs dims-sign --fixture
over the file and compares the output to it line by line.
One case reads test/fixtures/signing.tsv and sends each signed URL to
the running module. It scrapes dims_signature_checks_total before and
after, then asserts that the ok counter rose by the number of requests
and the mismatch counter did not change. A signature refusal and a bad
crop return the same status, so the status does not separate them.

A record with an error has no signed URL. A record outside /dims4/ and
/dims5/ is skipped, because the module reads a fixed seven characters
past the start of the path. A record holding eurl is skipped, because
the module decrypts eurl and a ciphertext contains a fresh nonce.
The install writes dims_sign.h under include/dims, the archive and the
shared library under lib, dims-sign under bin, and dims-sign.pc under
lib/pkgconfig. A caller builds against it with pkg-config --cflags
--libs dims-sign. docker/Dockerfile copies only libmod_dims.so out of
the build stage, so the server image has no dims-sign.

test/endurance/sign.c calls the library for its query escape and its two
digests. It keeps the path escape, which writes a space as %20, and the
encryption helpers.

docs/docs/clients/ describes the library and the command, and the two
endpoint pages link to it. dims4.md states that a _keys value goes into
the message as it appears in the query string, and that a plus in the
image URL signs as a space.
libmoddims_sign gains the key derivation and the two eurl ciphers.
dims_sign_dims5_eurl_url and dims_sign_dims4_eurl_url sign a URL and put
the encrypted source in place of url. The signature covers the plain
image URL, so the server verifies the request after it decrypts.

/dims5/ derives with HKDF-SHA256 under the salt go-dims and encrypts
with AES-128-GCM. /dims4/ derives from SHA-1 of the client secret and
encrypts with AES-128-ECB, or with GCM under DimsEncryptionAlgorithm.
The value is percent encoded on /dims5/ and undecoded on /dims4/,
because each endpoint reads the parameter that way.

test/fixtures/signing.tsv gains four eurl records, each holding a
ciphertext and the plain URL it decrypts to. Two request cases send a
URL the library encrypted to the running module. test/endurance/sign.c
calls the library for its derivations and its ECB encryption, and keeps
its GCM, where the IV comes from the run's seed.
@beetlebugorg
beetlebugorg added this pull request to stack #129 September 9, 2026 23:45
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.

1 participant