pq_crypto-seal 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +6 -5
  4. data/RELEASING.md +7 -8
  5. data/SECURITY.md +8 -1
  6. data/ext/pq_crypto_seal/aegis_unused_stubs.c +6 -5
  7. data/ext/pq_crypto_seal/extconf.rb +361 -247
  8. data/ext/pq_crypto_seal/pq_crypto_seal.c +511 -293
  9. data/ext/pq_crypto_seal/pqs_core.c +272 -0
  10. data/ext/pq_crypto_seal/pqs_core.h +86 -0
  11. data/ext/pq_crypto_seal/vendor/libaegis/LICENSE +1 -1
  12. data/ext/pq_crypto_seal/vendor/libaegis/TREE_SHA256 +1 -1
  13. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_common.h +5 -0
  14. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_soft.c +3 -10
  15. data/ext/pq_crypto_seal/vendor/libaegis/src/common/common.h +1 -1
  16. data/ext/pq_crypto_seal/vendor/libaegis/src/common/softaes.c +879 -118
  17. data/ext/pq_crypto_seal/vendor/libaegis/src/common/softaes.h +210 -9
  18. data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis_raf.h +26 -0
  19. data/lib/pq_crypto/seal/binary.rb +24 -28
  20. data/lib/pq_crypto/seal/core.rb +100 -172
  21. data/lib/pq_crypto/seal/envelope.rb +161 -95
  22. data/lib/pq_crypto/seal/errors.rb +8 -0
  23. data/lib/pq_crypto/seal/files.rb +76 -106
  24. data/lib/pq_crypto/seal/format.rb +133 -243
  25. data/lib/pq_crypto/seal/format_codecs.rb +195 -0
  26. data/lib/pq_crypto/seal/io.rb +40 -78
  27. data/lib/pq_crypto/seal/io_helpers.rb +165 -98
  28. data/lib/pq_crypto/seal/native.rb +1 -1
  29. data/lib/pq_crypto/seal/options.rb +31 -0
  30. data/lib/pq_crypto/seal/padding.rb +80 -142
  31. data/lib/pq_crypto/seal/recipients.rb +190 -128
  32. data/lib/pq_crypto/seal/resource_limits.rb +29 -20
  33. data/lib/pq_crypto/seal/secrets.rb +1 -5
  34. data/lib/pq_crypto/seal/streaming.rb +164 -183
  35. data/lib/pq_crypto/seal/version.rb +1 -1
  36. data/lib/pq_crypto/seal.rb +32 -26
  37. data/pq_crypto-seal.gemspec +33 -32
  38. metadata +5 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f595c15cfe06676ee508888c302a9bb5bab8083ffdef97540d6b6d19d877e270
4
- data.tar.gz: 7e8107699dbdead218debc3c870d5ad98ba0f279a23f2c2698c3918bc0060d45
3
+ metadata.gz: ba33e05dfec6b3dc0b81bddd6a2b715f954c94ed73ea854024f2376244f8e785
4
+ data.tar.gz: 6c86441cbed8a97ae0014f824a194496c581578b3e894ce1731bc8ba20aa3e7c
5
5
  SHA512:
6
- metadata.gz: 9cc59508eea68e697e16ceee595718319b1f10c9ee4aceea186565ba3f9d1133e9b6717aa198de1a33f1e4782812caa6a2c32e6bce7ba557a9ba9c7d923e20fc
7
- data.tar.gz: e2fcec77d71028fa2615a62af722441277dfda52b24e05c4e2d3ffa50621b3435c28ab14b742788237ef18c3ad4f3d0c12770e0de0488de858f7243d57dbb2f4
6
+ metadata.gz: 91f80804510c93a6a381cbf1c10f0584c2f1efe96c2a898a42c2db6f7700eaa821adedc20de95b41e42aee822ad33d8b35d25d356380f117e3b171b8cd46c270
7
+ data.tar.gz: 4f077ede4fa61486e9ba2925efeb626aac1dcc4a80067f5c50628c5579a94dce2683cffaaa9838a35b5399a11fb399b824d39a4b39ff0f6ad3e9b42c9582671c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,71 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4
4
+
5
+ Wire format v1, the public API and the error classes are unchanged. The frozen
6
+ golden envelope (`test/frozen_envelope_test.rb`) is byte-identical.
7
+
8
+ ### Security
9
+
10
+ - **Native arguments converted before use.** `require_length` converted a
11
+ `#to_str` argument into a local copy only, so the caller then read
12
+ `RSTRING_PTR` of the original, non-String object (segfault under ASan/UBSan).
13
+ All arguments are now converted with `StringValue` first, then validated.
14
+ - **No length check before a later `#to_str` callback.** The key length was
15
+ validated before the nonce was converted; a nonce `#to_str` could shrink the
16
+ key and the following 32-byte copy read out of bounds. Validation now happens
17
+ only after every conversion.
18
+ - **Authentication error class pinned for GC compaction.** The cached
19
+ `AuthenticationError` VALUE was not registered with the GC; after
20
+ `GC.compact` raising it could crash. It is now a registered root.
21
+ - **AEGIS operations without the GVL use private C buffers** for the inputs and
22
+ output. The extension creates the Ruby result with the GVL after the operation,
23
+ then wipes and frees the C buffers.
24
+ - **Interrupts after a GVL-free section are handled.** Work runs under
25
+ `rb_protect`; key copies and snapshots are wiped and outputs cleared before a
26
+ pending `Thread#raise`/`kill` is re-raised. An interrupted or failed
27
+ incremental update finalizes the AEGIS state, so it can no longer emit or
28
+ accept a tag over data the caller never received.
29
+ - **AEGIS state locking uses `atomic_bool`** with acquire/release ordering, as in
30
+ `pq_crypto` 0.6.8. `Encryptor`/`Decryptor` cannot be `dup`ed or `clone`d.
31
+ - **Effective zeroization.** `Secrets.wipe!` used `String#replace`, which frees
32
+ the old buffer without clearing it. Wiping now goes through `Native.wipe`
33
+ (in-place `OPENSSL_cleanse` after `rb_str_modify`). Parsers copy fields with
34
+ `unpack1` instead of `byteslice`, so decrypted plaintext is not kept alive in
35
+ hidden copy-on-write roots, and the private-metadata copy owns its buffer.
36
+ - File recipient rebuilds keep rejecting trailing bytes after the envelope.
37
+ - Stream encryption keeps caller-owned input buffers intact. Stream decryption
38
+ uses the normalized chunk size after authentication.
39
+ - Extension built with `-fstack-protector-strong` and, when the toolchain does
40
+ not already define it, `-D_FORTIFY_SOURCE=2`.
41
+
42
+ ### Changed
43
+
44
+ - C extension split into `pqs_core.{c,h}` and Ruby bindings. HKDF-SHA256 uses
45
+ OpenSSL 3 `EVP_MAC`.
46
+ - Vendored sources and their checksum match the pinned upstream
47
+ `libaegis` 0.10.3 archive.
48
+ - Ruby library refactored into small objects (header/section/inner codecs with a
49
+ declarative header layout, encryption plans, stanza sealing/opening, streaming
50
+ writers and readers). The public String and IO APIs retain their Ruby
51
+ keyword signatures.
52
+ - `PQC_SEAL_SANITIZE` accepts a sanitizer list (`1` still means
53
+ `address,undefined`).
54
+ - `extconf.rb` restructured into `PQCSealBuild` modules; OpenSSL selection is
55
+ unchanged.
56
+ - Development dependencies moved from the gemspec to the Gemfile.
57
+ - Internal helpers changed shape: `Format.split_slot(slot)` no longer takes the
58
+ slot size, `Format.validate_capacity!` no longer checks the recipient count
59
+ (`Format.validate_recipient_count!` does), `Format.parse_verified_inner` and
60
+ the `Binary.read_*` helpers were removed, and implementation classes that were
61
+ accidentally public (`OneShotEncryptor`, `OneShotOpener`, `RecipientRebuilder`,
62
+ `DekRotator`) are now private constants. `IOAPI::DEFAULT_CHUNK_SIZE` remains.
63
+
64
+ ### Testing
65
+
66
+ - Ruby regression tests cover native arguments, GC, concurrent updates and the
67
+ IO cases above. CI also runs the Ruby-independent C tests under ASan and UBSan.
68
+
3
69
  ## 0.1.3
4
70
 
5
71
  - Runtime dependency relaxed from `pq_crypto = 0.6.4` to `pq_crypto ~> 0.6`
data/README.md CHANGED
@@ -12,7 +12,7 @@ random DEK → AEGIS-256 → document
12
12
  `libaegis` 0.10.3 is vendored and compiled into the extension. The existing
13
13
  `pq_crypto` gem supplies the hybrid KEM and OpenSSL-backed primitives.
14
14
 
15
- > **Status:** 0.1.3 is experimental cryptographic software; format v1; not
15
+ > **Status:** 0.1.4 is experimental cryptographic software; format v1; not
16
16
  > independently audited. Read `SECURITY.md` before using it for irreplaceable data.
17
17
  >
18
18
  > `wrap_suite_id = 1` is pinned to `:ml_kem_768_x25519_xwing` and `pq_crypto ~> 0.6`
@@ -22,7 +22,7 @@ random DEK → AEGIS-256 → document
22
22
 
23
23
  ```ruby
24
24
  gem "pq_crypto", "~> 0.6"
25
- gem "pq_crypto-seal", "~> 0.1.3"
25
+ gem "pq_crypto-seal", "~> 0.1.4"
26
26
  ```
27
27
 
28
28
  Ruby `>= 2.7.1` and OpenSSL `>= 3.0` development files are required for a
@@ -217,10 +217,11 @@ bundle install
217
217
  bundle exec rake
218
218
  ```
219
219
 
220
- Set `PQC_SEAL_SANITIZE=1` when compiling under ASan/UBSan. The suite covers
220
+ Set `PQC_SEAL_SANITIZE=1` (address + undefined) or an explicit sanitizer list
221
+ such as `PQC_SEAL_SANITIZE=thread` when compiling the extension. The suite covers
221
222
  AEGIS one-shot/incremental equivalence, tampering, multi-recipient opening,
222
- full-envelope padding, recipient rebuilds, staged file publication, and parser
223
- limits.
223
+ full-envelope padding, recipient rebuilds, staged file publication, parser
224
+ limits, concurrent use of one AEGIS state, and argument conversion edge cases.
224
225
 
225
226
 
226
227
  ## Padding enforcement on decrypt
data/RELEASING.md CHANGED
@@ -22,15 +22,14 @@ gem; that would break the provenance guarantee. Never update the snapshot
22
22
  without rerunning official KATs, cross-backend equivalence checks, envelope
23
23
  golden vectors, and fuzz tests.
24
24
 
25
- The golden vectors fix every value this gem controls (recipient keys, DEK,
26
- nonces, section id, padding bytes, metadata, plaintext) and assert the derived
27
- header bytes, associated data, and one-shot/incremental equivalence. A fully
28
- frozen "plaintext to exact bytes" vector is intentionally not asserted for the
29
- KEM ciphertext region, because X-Wing encapsulation randomness originates inside
30
- `pq_crypto` and is not injectable here; the wire-contract check pins its exact
31
- sizes and round-trip instead.
25
+ The frozen envelope test supplies a published X-Wing encapsulation result and
26
+ deterministic randomness for Seal. It checks the digest of the complete envelope
27
+ for both one-shot and streaming encryption. Other golden tests cover header
28
+ bytes, associated data and recipient behaviour.
32
29
 
33
30
  The ASan job sets `detect_leaks=0` because ASan is preloaded into an otherwise
34
31
  unsanitized Ruby interpreter; host-process allocator noise would otherwise fail
35
- the job. Memory-safety errors still abort via `halt_on_error`.
32
+ the job. Memory-safety errors still abort via `halt_on_error`. It also sets
33
+ `use_sigaltstack=0`: Ruby installs its own per-thread alternate signal stack and
34
+ ASan would otherwise try to unmap that buffer when a thread exits.
36
35
 
data/SECURITY.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Security
2
2
 
3
3
  This project has not received an independent cryptographic or implementation
4
- audit. Do not treat version 0.1.3 as a substitute for a reviewed storage design.
4
+ audit. Do not treat version 0.1.4 as a substitute for a reviewed storage design.
5
5
 
6
6
  Experimental cryptographic software; format v1; not independently audited.
7
7
 
@@ -68,6 +68,13 @@ callers that need residual-data hygiene must clear their own buffers.
68
68
  `Opened#data` / `Opened#metadata` returned from `open` are the caller's
69
69
  responsibility after use.
70
70
 
71
+ Buffers the library owns (DEK, KEK, X-Wing shared secrets, the decrypted inner
72
+ frame, the private-metadata copy, staged plaintext chunks) are zeroized in place
73
+ by the C extension. Parsed fields are copied out instead of sliced, so no hidden
74
+ copy-on-write root keeps decrypted bytes alive. Zeroization is still best effort:
75
+ Ruby may have copied a string before it reached the library, and the garbage
76
+ collector does not clear freed memory.
77
+
71
78
 
72
79
  ## payload_id stability
73
80
 
@@ -1,8 +1,9 @@
1
- /*
2
- * pq_crypto-seal exposes only AEGIS-256. libaegis common.c initializes all
3
- * optional families, so source-only builds provide no-op selectors for the
4
- * families deliberately not linked into this extension.
5
- */
1
+ int aegis128l_pick_best_implementation(void);
2
+ int aegis128x2_pick_best_implementation(void);
3
+ int aegis128x4_pick_best_implementation(void);
4
+ int aegis256x2_pick_best_implementation(void);
5
+ int aegis256x4_pick_best_implementation(void);
6
+
6
7
  int aegis128l_pick_best_implementation(void) {
7
8
  return 0;
8
9
  }