Add HMAC-SHA-256, PBKDF2-SHA-256, and RAND_bytes to ssl/crypto #15
Closed
SeanTAllen
started this conversation in
Research
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Prerequisite work for SCRAM-SHA-256 authentication in ponylang/postgres (Design: ponylang/postgres#83, Decision 1). Adds three cryptographic primitives to the
ssl/cryptopackage as FFI wrappers around stable OpenSSL C APIs.Change type: Added (new feature). PR label:
changelog - added.FFI Scoping
Pony FFI declarations are package-scoped: "An FFI signature is public to all Pony files inside the same package, so you only need to write them once." This means:
@EVP_sha256(declared indigest.pony) and@pony_ctx/@pony_alloc(declared incrypto.pony) are already visible to the new files. No redeclaration needed._EVPMD(defined indigest.pony) is package-private and accessible from any file inssl/crypto.@HMAC,@PKCS5_PBKDF2_HMAC,@RAND_bytes).Library link directives (
use "lib:crypto"and the macOS LibreSSL path directives) are included in each new file following the existing convention inhash_fn.ponyanddigest.pony.New Primitives
HmacSha256
One-shot HMAC computation using SHA-256 (RFC 2104). Wraps OpenSSL's
HMAC()function. Returns a 32-byte message authentication code.File:
ssl/crypto/hmac_sha256.ponyDesign notes:
SHA256primitive convention.HMAC()only fails on internal memory allocation failure, which the existing hash primitives also don't guard against.md_lenreceives a null pointer (Pointer[U32]) since we know the output is always 32 bytes.key_lenisI32matching the Cintparameter. Keys > 2GB are not a practical concern for HMAC.Hmacwith algorithm parameter), following the existingHashFnconvention. Additional HMAC variants (SHA-384, SHA-512) can be added later as separate primitives following the same pattern.ifdefversion guards needed.HMAC()is available in all supported OpenSSL versions (0.9.0, 1.1.x, 3.0.x). The one-shotHMAC()function is not deprecated in OpenSSL 3.0 -- only the context-based functions (HMAC_CTX_new,HMAC_Init_ex,HMAC_Update,HMAC_Final) are deprecated.Pbkdf2Sha256
One-shot PBKDF2 key derivation using HMAC-SHA-256 as the PRF (RFC 2898). Wraps OpenSSL's
PKCS5_PBKDF2_HMAC()function.File:
ssl/crypto/pbkdf2_sha256.ponyDesign notes:
PKCS5_PBKDF2_HMAC()returns 0 on failure (e.g., zero iterations on OpenSSL 1.1.0+). UnlikeHMAC(), this has meaningful failure modes that the caller should handle.iterationsisU32in the public API (semantically correct -- iteration counts are positive). Converted to.i32()for the Cintparameter. Values > 2^31 would overflow, but such iteration counts are not practically useful.key_lengthisUSize(consistent with other size parameters in the package). Converted to.i32()for FFI.compile_errorfor theopenssl_0.9.0path, following the established pattern indigest.ponyforshake128/shake256.PKCS5_PBKDF2_HMAC()was added in OpenSSL 1.0.0; OpenSSL 0.9.8 only provides the SHA-1-specificPKCS5_PBKDF2_HMAC_SHA1(). Since the 0.9.0 define is overloaded (used for both genuine 0.9.8 and LibreSSL, which does have the function), this guard errs on the side of safety. The 0.9.0 path is being removed per issue LibreSSL is misidentified as OpenSSL 0.9.0, disabling functionality LibreSSL supports #9.@PKCS5_PBKDF2_HMACFFI declaration has anifguard limiting it to"openssl_1.1.x" or "openssl_3.0.x"to prevent unresolved symbol errors on 0.9.0:RandBytes
Cryptographically secure random byte generation. Wraps OpenSSL's
RAND_bytes()function.File:
ssl/crypto/rand_bytes.ponyDesign notes:
RAND_bytes()returns 0 if the CSPRNG is not properly seeded, which is a real (if rare) failure mode, especially during early system startup.sizeisUSizein the public API. Converted to.i32()for the Cintparameter. Generating > 2GB of random data in a single call is not a practical use case.ifdefversion guards needed.RAND_bytes()is available in all supported OpenSSL and LibreSSL versions.Tests
Build and run:
make test ssl=3.0.x(ormake ssl=3.0.xsince test is the default target).Example-based tests (RFC test vectors)
Add to
ssl/crypto/_test.pony. Follow the existing pattern:class \nodoc\ iso _TestX is UnitTest.HMAC-SHA-256 (RFC 4231): All 7 test cases. Each verifies the computed HMAC against the expected hex digest.
b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff75bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665ba3b6167473100ee06e0c796c2955552b(truncated to 128 bits)60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f549b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2Test case 5 tests truncation: compute the full 32-byte HMAC, then verify the first 16 bytes match the expected truncated value.
PBKDF2-SHA-256 (RFC 7914 Section 11): Two test vectors.
55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a197834ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8dThese use dkLen=64, which exercises the multi-block PBKDF2 path (SHA-256 output is 32 bytes, so PBKDF2 must iterate twice to produce 64 bytes).
PBKDF2-SHA-256 (SCRAM-SHA-256 test vector): Verifies PBKDF2 produces the correct SaltedPassword for the SCRAM-SHA-256 exchange from RFC 7677. The SaltedPassword is not explicitly listed in the RFC but is derived from the RFC 7677 protocol exchange and verified by confirming the computed ClientProof and ServerSignature match the RFC values.
W22ZaJ0SNY7soEsUEjb6gQ==c4a49510323ab4f952cac1fa99441939e78ea74d6be81ddf7096e87513dc615dThe salt is passed as a raw byte array literal (the decoded bytes of
W22ZaJ0SNY7soEsUEjb6gQ==), avoiding a dependency onencode/base64in the test file.HMAC-SHA-256 (SCRAM-SHA-256 derived values): Verifies HMAC computation of SCRAM intermediate values using the SaltedPassword from the PBKDF2 test above. These values are derived from the RFC 7677 exchange and verified against the RFC's ClientProof and ServerSignature.
HMAC(SaltedPassword, "Client Key")=a60fc923d67e8644a92d16b96eda5ef4656b0c725c484374be25535576996e8bHMAC(SaltedPassword, "Server Key")=c1f3cbc1c13a9d35a14c0990eed97629ea225863e566a4314ab99f3f00e5d9d5RandBytes: Example-based tests for basic functionality.
RandBytes(0)?returns an empty array (size 0)RandBytes(32)?returns an array of size 32RandBytes(1)?returns an array of size 1PonyCheck property tests
Add
use "pony_check"tossl/crypto/_test.pony. Register property tests alongside existing unit tests.Version guards for PBKDF2 tests: All PBKDF2 test classes and their registration in the
testsfunction must be wrapped inifdef "openssl_1.1.x" or "openssl_3.0.x" then ... end, following the pattern used forshake128/shake256tests on lines 153-167 of the existing_test.pony. This preventscompile_errorwhen building withssl=0.9.0.HmacSha256 properties:
Output length is always 32 bytes. Generator: pairs of
(Array[U8] val, Array[U8] val)with sizes 0-256 for key and data. Property:HmacSha256(key, data).size() == 32.Deterministic. Generator: pairs of
(Array[U8] val, Array[U8] val). Property:HmacSha256(key, data) == HmacSha256(key, data)(two calls with the same inputs produce identical output).Pbkdf2Sha256 properties:
Output length equals requested key_length. Generator:
key_lengthin range 1-128. Use fixed password/salt/iterations for speed. Property:Pbkdf2Sha256("p", "s", 1, key_length)?.size() == key_length.Deterministic. Generator:
key_lengthin range 1-64. Property:Pbkdf2Sha256("p", "s", 1, key_length)? == Pbkdf2Sha256("p", "s", 1, key_length)?.RandBytes properties:
Output length equals requested size. Generator:
sizein range 0-256. Property:RandBytes(size)?.size() == size.Non-constant output. Generator:
sizein range 16-256. Property:RandBytes(size)? != RandBytes(size)?. (For 16+ bytes, the probability of collision is negligible.)Test case count
Other Changes
Package docstring update
Update
ssl/crypto/crypto.ponyto mention the new primitives. The current docstring says "cryptographic algorithms and functions often useful in cryptographic code for their use in information security." Add the new primitives to the description.Example
New directory:
examples/hmac-pbkdf2-example/A self-contained example demonstrating all three primitives. File named
hmac-pbkdf2-example.pony(matching the existing convention where the file name matches the directory name). Nocorral.json(existing examples don't have one). Includes the use-statement comment explaining the import path difference:CLAUDE.md update
Add the three new files to the Key Files table. Note the new primitives in the description of the crypto package.
Implementation Steps
ssl/crypto/hmac_sha256.ponywith theHmacSha256primitive.ssl/crypto/pbkdf2_sha256.ponywith thePbkdf2Sha256primitive.ssl/crypto/rand_bytes.ponywith theRandBytesprimitive.ssl/crypto/_test.pony: RFC test vectors + PonyCheck property tests.examples/hmac-pbkdf2-example/hmac-pbkdf2-example.pony.ssl/crypto/crypto.ponypackage docstring.CLAUDE.mdto document new files.make test ssl=3.0.x.All reactions