pq_crypto 0.1.0

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 (93) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +37 -0
  3. data/CHANGELOG.md +29 -0
  4. data/GET_STARTED.md +65 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +135 -0
  7. data/SECURITY.md +57 -0
  8. data/ext/pqcrypto/extconf.rb +157 -0
  9. data/ext/pqcrypto/mldsa_api.h +51 -0
  10. data/ext/pqcrypto/mlkem_api.h +21 -0
  11. data/ext/pqcrypto/pqcrypto_ruby_secure.c +889 -0
  12. data/ext/pqcrypto/pqcrypto_secure.c +1178 -0
  13. data/ext/pqcrypto/pqcrypto_secure.h +135 -0
  14. data/ext/pqcrypto/vendor/.vendored +5 -0
  15. data/ext/pqcrypto/vendor/pqclean/common/aes.c +639 -0
  16. data/ext/pqcrypto/vendor/pqclean/common/aes.h +64 -0
  17. data/ext/pqcrypto/vendor/pqclean/common/compat.h +73 -0
  18. data/ext/pqcrypto/vendor/pqclean/common/crypto_declassify.h +7 -0
  19. data/ext/pqcrypto/vendor/pqclean/common/fips202.c +928 -0
  20. data/ext/pqcrypto/vendor/pqclean/common/fips202.h +166 -0
  21. data/ext/pqcrypto/vendor/pqclean/common/keccak2x/feat.S +168 -0
  22. data/ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.c +684 -0
  23. data/ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.h +60 -0
  24. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SIMD256.c +1028 -0
  25. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SnP.h +50 -0
  26. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-unrolling.macros +198 -0
  27. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile +8 -0
  28. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile.Microsoft_nmake +8 -0
  29. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/SIMD256-config.h +3 -0
  30. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/align.h +34 -0
  31. data/ext/pqcrypto/vendor/pqclean/common/keccak4x/brg_endian.h +142 -0
  32. data/ext/pqcrypto/vendor/pqclean/common/nistseedexpander.c +101 -0
  33. data/ext/pqcrypto/vendor/pqclean/common/nistseedexpander.h +39 -0
  34. data/ext/pqcrypto/vendor/pqclean/common/randombytes.c +355 -0
  35. data/ext/pqcrypto/vendor/pqclean/common/randombytes.h +27 -0
  36. data/ext/pqcrypto/vendor/pqclean/common/sha2.c +769 -0
  37. data/ext/pqcrypto/vendor/pqclean/common/sha2.h +173 -0
  38. data/ext/pqcrypto/vendor/pqclean/common/sp800-185.c +156 -0
  39. data/ext/pqcrypto/vendor/pqclean/common/sp800-185.h +27 -0
  40. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/LICENSE +5 -0
  41. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile +19 -0
  42. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile.Microsoft_nmake +23 -0
  43. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/api.h +18 -0
  44. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.c +83 -0
  45. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.h +11 -0
  46. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.c +327 -0
  47. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.h +22 -0
  48. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.c +164 -0
  49. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.h +23 -0
  50. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.c +146 -0
  51. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.h +14 -0
  52. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/params.h +36 -0
  53. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.c +299 -0
  54. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.h +37 -0
  55. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.c +188 -0
  56. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.h +26 -0
  57. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.c +41 -0
  58. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.h +13 -0
  59. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric-shake.c +71 -0
  60. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric.h +30 -0
  61. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.c +67 -0
  62. data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.h +13 -0
  63. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/LICENSE +5 -0
  64. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile +19 -0
  65. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile.Microsoft_nmake +23 -0
  66. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/api.h +50 -0
  67. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.c +98 -0
  68. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.h +10 -0
  69. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.c +261 -0
  70. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.h +31 -0
  71. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/params.h +44 -0
  72. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.c +799 -0
  73. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.h +52 -0
  74. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.c +415 -0
  75. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.h +65 -0
  76. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.c +69 -0
  77. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.h +17 -0
  78. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.c +92 -0
  79. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.h +14 -0
  80. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.c +407 -0
  81. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.h +47 -0
  82. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric-shake.c +26 -0
  83. data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric.h +34 -0
  84. data/lib/pq_crypto/errors.rb +10 -0
  85. data/lib/pq_crypto/hybrid_kem.rb +106 -0
  86. data/lib/pq_crypto/kem.rb +199 -0
  87. data/lib/pq_crypto/serialization.rb +102 -0
  88. data/lib/pq_crypto/signature.rb +198 -0
  89. data/lib/pq_crypto/version.rb +5 -0
  90. data/lib/pq_crypto.rb +177 -0
  91. data/lib/pqcrypto.rb +3 -0
  92. data/script/vendor_libs.rb +199 -0
  93. metadata +195 -0
@@ -0,0 +1,1178 @@
1
+ #include "pqcrypto_secure.h"
2
+
3
+ #include <stdio.h>
4
+ #include <sys/types.h>
5
+ #include <fcntl.h>
6
+ #include <unistd.h>
7
+ #include <limits.h>
8
+
9
+ #if defined(__linux__)
10
+ #include <sys/random.h>
11
+ #endif
12
+
13
+ #ifndef HAVE_OPENSSL_EVP_H
14
+ #error \
15
+ "OpenSSL with EVP support is required for secure cryptographic operations. Install OpenSSL development packages."
16
+ #endif
17
+
18
+ #include <openssl/evp.h>
19
+ #include <openssl/rand.h>
20
+ #include <openssl/kdf.h>
21
+
22
+ #if OPENSSL_VERSION_NUMBER < 0x30000000L
23
+ #error "OpenSSL 3.0 or later is required for pq_crypto"
24
+ #endif
25
+
26
+ #ifndef HAVE_PQCLEAN
27
+ #error "PQClean-backed algorithms are required. Run: bundle exec rake vendor"
28
+ #endif
29
+
30
+ #include "mlkem_api.h"
31
+ #include "mldsa_api.h"
32
+ #include "fips202.h"
33
+ #include "packing.h"
34
+ #include "params.h"
35
+
36
+ void pq_secure_wipe(void *ptr, size_t len) {
37
+ volatile uint8_t *p = ptr;
38
+ while (len--) {
39
+ *p++ = 0;
40
+ }
41
+
42
+ __asm__ __volatile__("" : : "r"(ptr) : "memory");
43
+ }
44
+
45
+ static int pq_size_add(size_t a, size_t b, size_t *out) {
46
+ if (!out)
47
+ return PQ_ERROR_BUFFER;
48
+ if (SIZE_MAX - a < b)
49
+ return PQ_ERROR_BUFFER;
50
+ *out = a + b;
51
+ return PQ_SUCCESS;
52
+ }
53
+
54
+ static int pq_size_mul(size_t a, size_t b, size_t *out) {
55
+ if (!out)
56
+ return PQ_ERROR_BUFFER;
57
+ if (a != 0 && SIZE_MAX / a < b)
58
+ return PQ_ERROR_BUFFER;
59
+ *out = a * b;
60
+ return PQ_SUCCESS;
61
+ }
62
+
63
+ static int pq_is_pem_whitespace(char c) {
64
+ return c == '\n' || c == '\r' || c == ' ' || c == '\t';
65
+ }
66
+
67
+ static int pq_randombytes(uint8_t *buf, size_t len) {
68
+ #ifdef HAVE_OPENSSL_RAND_H
69
+ if (len > INT_MAX) {
70
+ return PQ_ERROR_BUFFER;
71
+ }
72
+ if (RAND_bytes(buf, (int)len) == 1) {
73
+ return 0;
74
+ }
75
+
76
+ #endif
77
+
78
+ #if defined(__linux__)
79
+ ssize_t ret = getrandom(buf, len, 0);
80
+ return (ret == (ssize_t)len) ? 0 : PQ_ERROR_RANDOM;
81
+ #elif defined(__APPLE__)
82
+ arc4random_buf(buf, len);
83
+ return 0;
84
+ #else
85
+ FILE *f = fopen("/dev/urandom", "rb");
86
+ if (!f)
87
+ return PQ_ERROR_RANDOM;
88
+ size_t read = fread(buf, 1, len, f);
89
+ fclose(f);
90
+ return (read == len) ? 0 : PQ_ERROR_RANDOM;
91
+ #endif
92
+ }
93
+
94
+ static int x25519_keypair(uint8_t *pk, uint8_t *sk) {
95
+ EVP_PKEY_CTX *ctx = NULL;
96
+ EVP_PKEY *pkey = NULL;
97
+ size_t pklen = X25519_PUBLICKEYBYTES;
98
+ size_t sklen = X25519_SECRETKEYBYTES;
99
+ int ret = PQ_ERROR_KEYPAIR;
100
+
101
+ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL);
102
+ if (!ctx)
103
+ goto cleanup;
104
+
105
+ if (EVP_PKEY_keygen_init(ctx) <= 0)
106
+ goto cleanup;
107
+
108
+ if (EVP_PKEY_keygen(ctx, &pkey) <= 0)
109
+ goto cleanup;
110
+
111
+ if (EVP_PKEY_get_raw_private_key(pkey, sk, &sklen) <= 0)
112
+ goto cleanup;
113
+ if (sklen != X25519_SECRETKEYBYTES)
114
+ goto cleanup;
115
+
116
+ if (EVP_PKEY_get_raw_public_key(pkey, pk, &pklen) <= 0)
117
+ goto cleanup;
118
+ if (pklen != X25519_PUBLICKEYBYTES)
119
+ goto cleanup;
120
+
121
+ ret = PQ_SUCCESS;
122
+
123
+ cleanup:
124
+ if (pkey)
125
+ EVP_PKEY_free(pkey);
126
+ if (ctx)
127
+ EVP_PKEY_CTX_free(ctx);
128
+ return ret;
129
+ }
130
+
131
+ static int x25519_shared_secret(uint8_t *shared, const uint8_t *their_pk, const uint8_t *my_sk) {
132
+ EVP_PKEY_CTX *ctx = NULL;
133
+ EVP_PKEY *pkey = NULL;
134
+ EVP_PKEY *peer_key = NULL;
135
+ size_t shared_len = X25519_SHAREDSECRETBYTES;
136
+ int ret = PQ_ERROR_ENCAPSULATE;
137
+
138
+ pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, my_sk, X25519_SECRETKEYBYTES);
139
+ if (!pkey)
140
+ goto cleanup;
141
+
142
+ peer_key = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, their_pk, X25519_PUBLICKEYBYTES);
143
+ if (!peer_key)
144
+ goto cleanup;
145
+
146
+ ctx = EVP_PKEY_CTX_new(pkey, NULL);
147
+ if (!ctx)
148
+ goto cleanup;
149
+
150
+ if (EVP_PKEY_derive_init(ctx) <= 0)
151
+ goto cleanup;
152
+
153
+ if (EVP_PKEY_derive_set_peer(ctx, peer_key) <= 0)
154
+ goto cleanup;
155
+
156
+ if (EVP_PKEY_derive(ctx, shared, &shared_len) <= 0)
157
+ goto cleanup;
158
+
159
+ if (shared_len != X25519_SHAREDSECRETBYTES)
160
+ goto cleanup;
161
+
162
+ ret = PQ_SUCCESS;
163
+
164
+ cleanup:
165
+ if (ctx)
166
+ EVP_PKEY_CTX_free(ctx);
167
+ if (peer_key)
168
+ EVP_PKEY_free(peer_key);
169
+ if (pkey)
170
+ EVP_PKEY_free(pkey);
171
+ return ret;
172
+ }
173
+
174
+ static int x25519_public_from_secret(uint8_t *pk, const uint8_t *sk) {
175
+ EVP_PKEY *pkey = NULL;
176
+ size_t pklen = X25519_PUBLICKEYBYTES;
177
+ int ret = PQ_ERROR_KEYPAIR;
178
+
179
+ pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, sk, X25519_SECRETKEYBYTES);
180
+ if (!pkey) {
181
+ return ret;
182
+ }
183
+
184
+ if (EVP_PKEY_get_raw_public_key(pkey, pk, &pklen) <= 0 || pklen != X25519_PUBLICKEYBYTES) {
185
+ EVP_PKEY_free(pkey);
186
+ return ret;
187
+ }
188
+
189
+ EVP_PKEY_free(pkey);
190
+ return PQ_SUCCESS;
191
+ }
192
+
193
+ static int size_t_to_int_checked(size_t value, int *out) {
194
+ if (value > INT_MAX) {
195
+ return PQ_ERROR_BUFFER;
196
+ }
197
+
198
+ *out = (int)value;
199
+ return PQ_SUCCESS;
200
+ }
201
+
202
+ static int secure_hkdf(uint8_t *output, size_t output_len, const uint8_t *ikm, size_t ikm_len,
203
+ const uint8_t *salt, size_t salt_len, const uint8_t *info, size_t info_len) {
204
+ int ikm_len_i = 0;
205
+ int salt_len_i = 0;
206
+ int info_len_i = 0;
207
+
208
+ if (size_t_to_int_checked(ikm_len, &ikm_len_i) != PQ_SUCCESS)
209
+ return PQ_ERROR_BUFFER;
210
+ if (size_t_to_int_checked(salt_len, &salt_len_i) != PQ_SUCCESS)
211
+ return PQ_ERROR_BUFFER;
212
+ if (size_t_to_int_checked(info_len, &info_len_i) != PQ_SUCCESS)
213
+ return PQ_ERROR_BUFFER;
214
+
215
+ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
216
+ if (!pctx)
217
+ return PQ_ERROR_OPENSSL;
218
+
219
+ if (EVP_PKEY_derive_init(pctx) <= 0) {
220
+ EVP_PKEY_CTX_free(pctx);
221
+ return PQ_ERROR_OPENSSL;
222
+ }
223
+
224
+ if (EVP_PKEY_CTX_set_hkdf_md(pctx, EVP_sha256()) <= 0) {
225
+ EVP_PKEY_CTX_free(pctx);
226
+ return PQ_ERROR_OPENSSL;
227
+ }
228
+
229
+ if (EVP_PKEY_CTX_set1_hkdf_key(pctx, ikm, ikm_len_i) <= 0) {
230
+ EVP_PKEY_CTX_free(pctx);
231
+ return PQ_ERROR_OPENSSL;
232
+ }
233
+
234
+ if (salt && salt_len > 0) {
235
+ if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len_i) <= 0) {
236
+ EVP_PKEY_CTX_free(pctx);
237
+ return PQ_ERROR_OPENSSL;
238
+ }
239
+ }
240
+
241
+ if (info && info_len > 0) {
242
+ if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len_i) <= 0) {
243
+ EVP_PKEY_CTX_free(pctx);
244
+ return PQ_ERROR_OPENSSL;
245
+ }
246
+ }
247
+
248
+ size_t outlen = output_len;
249
+ if (EVP_PKEY_derive(pctx, output, &outlen) <= 0) {
250
+ EVP_PKEY_CTX_free(pctx);
251
+ return PQ_ERROR_OPENSSL;
252
+ }
253
+
254
+ EVP_PKEY_CTX_free(pctx);
255
+ return (outlen == output_len) ? 0 : PQ_ERROR_KDF;
256
+ }
257
+
258
+ static int secure_sha256(uint8_t *output, const uint8_t *input, size_t input_len) {
259
+ EVP_MD_CTX *ctx = EVP_MD_CTX_new();
260
+ unsigned int out_len = 0;
261
+
262
+ if (!ctx) {
263
+ return PQ_ERROR_OPENSSL;
264
+ }
265
+
266
+ if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1 ||
267
+ EVP_DigestUpdate(ctx, input, input_len) != 1 ||
268
+ EVP_DigestFinal_ex(ctx, output, &out_len) != 1) {
269
+ EVP_MD_CTX_free(ctx);
270
+ return PQ_ERROR_OPENSSL;
271
+ }
272
+
273
+ EVP_MD_CTX_free(ctx);
274
+ return out_len == 32 ? PQ_SUCCESS : PQ_ERROR_OPENSSL;
275
+ }
276
+
277
+ static int hybrid_combiner(uint8_t *shared_secret, const uint8_t *mlkem_ss,
278
+ const uint8_t *x25519_ss, const uint8_t *recipient_x25519_pk,
279
+ const hybrid_ciphertext_t *ct) {
280
+ static const uint8_t label[] = "pqcrypto/v1/hybrid-kem";
281
+ uint8_t ikm[MLKEM_SHAREDSECRETBYTES + X25519_SHAREDSECRETBYTES];
282
+ uint8_t salt[32];
283
+ int ret = PQ_SUCCESS;
284
+ size_t transcript_len = sizeof(label) - 1 + X25519_PUBLICKEYBYTES + HYBRID_CIPHERTEXTBYTES;
285
+ uint8_t *transcript = malloc(transcript_len);
286
+
287
+ if (!transcript) {
288
+ return PQ_ERROR_NOMEM;
289
+ }
290
+
291
+ memcpy(ikm, mlkem_ss, MLKEM_SHAREDSECRETBYTES);
292
+ memcpy(ikm + MLKEM_SHAREDSECRETBYTES, x25519_ss, X25519_SHAREDSECRETBYTES);
293
+
294
+ memcpy(transcript, label, sizeof(label) - 1);
295
+ memcpy(transcript + sizeof(label) - 1, recipient_x25519_pk, X25519_PUBLICKEYBYTES);
296
+ memcpy(transcript + sizeof(label) - 1 + X25519_PUBLICKEYBYTES, ct, HYBRID_CIPHERTEXTBYTES);
297
+
298
+ ret = secure_sha256(salt, transcript, transcript_len);
299
+ if (ret == PQ_SUCCESS) {
300
+ ret = secure_hkdf(shared_secret, HYBRID_SHAREDSECRETBYTES, ikm, sizeof(ikm), salt,
301
+ sizeof(salt), transcript, transcript_len);
302
+ }
303
+
304
+ pq_secure_wipe(ikm, sizeof(ikm));
305
+ pq_secure_wipe(salt, sizeof(salt));
306
+ pq_secure_wipe(transcript, transcript_len);
307
+ free(transcript);
308
+ return ret;
309
+ }
310
+
311
+ static int mlkem_keypair(uint8_t *pk, uint8_t *sk) {
312
+ return PQCLEAN_MLKEM768_CLEAN_crypto_kem_keypair(pk, sk);
313
+ }
314
+
315
+ static int mlkem_encapsulate(uint8_t *ct, uint8_t *ss, const uint8_t *pk) {
316
+ return PQCLEAN_MLKEM768_CLEAN_crypto_kem_enc(ct, ss, pk);
317
+ }
318
+
319
+ static int mlkem_decapsulate(uint8_t *ss, const uint8_t *ct, const uint8_t *sk) {
320
+ return PQCLEAN_MLKEM768_CLEAN_crypto_kem_dec(ss, ct, sk);
321
+ }
322
+
323
+ static int mldsa_keypair(uint8_t *pk, uint8_t *sk) {
324
+ return PQCLEAN_MLDSA65_CLEAN_crypto_sign_keypair(pk, sk);
325
+ }
326
+
327
+ static int mldsa_sign(uint8_t *sig, size_t *siglen, const uint8_t *msg, size_t msglen,
328
+ const uint8_t *sk) {
329
+ return PQCLEAN_MLDSA65_CLEAN_crypto_sign_signature(sig, siglen, msg, msglen, sk);
330
+ }
331
+
332
+ static int mldsa_verify(const uint8_t *sig, size_t siglen, const uint8_t *msg, size_t msglen,
333
+ const uint8_t *pk) {
334
+ return PQCLEAN_MLDSA65_CLEAN_crypto_sign_verify(sig, siglen, msg, msglen, pk);
335
+ }
336
+
337
+ int pq_testing_mlkem_keypair_from_seed(uint8_t *public_key, uint8_t *secret_key,
338
+ const uint8_t *seed, size_t seed_len) {
339
+ uint8_t expanded_seed[64];
340
+ int ret;
341
+
342
+ if (!public_key || !secret_key || !seed) {
343
+ return PQ_ERROR_BUFFER;
344
+ }
345
+
346
+ if (seed_len == 64) {
347
+ return PQCLEAN_MLKEM768_CLEAN_crypto_kem_keypair_derand(public_key, secret_key, seed) == 0
348
+ ? PQ_SUCCESS
349
+ : PQ_ERROR_KEYPAIR;
350
+ }
351
+
352
+ if (seed_len != 32) {
353
+ return PQ_ERROR_BUFFER;
354
+ }
355
+
356
+ ret = secure_hkdf(expanded_seed, sizeof(expanded_seed), seed, seed_len, NULL, 0,
357
+ (const uint8_t *)"pqcrypto-test-mlkem-keypair", 26);
358
+ if (ret != PQ_SUCCESS) {
359
+ pq_secure_wipe(expanded_seed, sizeof(expanded_seed));
360
+ return ret;
361
+ }
362
+
363
+ ret =
364
+ PQCLEAN_MLKEM768_CLEAN_crypto_kem_keypair_derand(public_key, secret_key, expanded_seed) == 0
365
+ ? PQ_SUCCESS
366
+ : PQ_ERROR_KEYPAIR;
367
+ pq_secure_wipe(expanded_seed, sizeof(expanded_seed));
368
+ return ret;
369
+ }
370
+
371
+ int pq_testing_mlkem_encapsulate_from_seed(uint8_t *ciphertext, uint8_t *shared_secret,
372
+ const uint8_t *public_key, const uint8_t *seed,
373
+ size_t seed_len) {
374
+ if (!ciphertext || !shared_secret || !public_key || !seed || seed_len != 32) {
375
+ return PQ_ERROR_BUFFER;
376
+ }
377
+
378
+ return PQCLEAN_MLKEM768_CLEAN_crypto_kem_enc_derand(ciphertext, shared_secret, public_key,
379
+ seed) == 0
380
+ ? PQ_SUCCESS
381
+ : PQ_ERROR_ENCAPSULATE;
382
+ }
383
+
384
+ static int pq_testing_mldsa_keypair_from_seed_impl(uint8_t *public_key, uint8_t *secret_key,
385
+ const uint8_t seed[SEEDBYTES]) {
386
+ uint8_t seedbuf[2 * SEEDBYTES + CRHBYTES];
387
+ uint8_t tr[TRBYTES];
388
+ const uint8_t *rho, *rhoprime, *key;
389
+ polyvecl mat[K];
390
+ polyvecl s1, s1hat;
391
+ polyveck s2, t1, t0;
392
+
393
+ memcpy(seedbuf, seed, SEEDBYTES);
394
+ seedbuf[SEEDBYTES + 0] = K;
395
+ seedbuf[SEEDBYTES + 1] = L;
396
+ shake256(seedbuf, 2 * SEEDBYTES + CRHBYTES, seedbuf, SEEDBYTES + 2);
397
+ rho = seedbuf;
398
+ rhoprime = rho + SEEDBYTES;
399
+ key = rhoprime + CRHBYTES;
400
+
401
+ PQCLEAN_MLDSA65_CLEAN_polyvec_matrix_expand(mat, rho);
402
+ PQCLEAN_MLDSA65_CLEAN_polyvecl_uniform_eta(&s1, rhoprime, 0);
403
+ PQCLEAN_MLDSA65_CLEAN_polyveck_uniform_eta(&s2, rhoprime, L);
404
+
405
+ s1hat = s1;
406
+ PQCLEAN_MLDSA65_CLEAN_polyvecl_ntt(&s1hat);
407
+ PQCLEAN_MLDSA65_CLEAN_polyvec_matrix_pointwise_montgomery(&t1, mat, &s1hat);
408
+ PQCLEAN_MLDSA65_CLEAN_polyveck_reduce(&t1);
409
+ PQCLEAN_MLDSA65_CLEAN_polyveck_invntt_tomont(&t1);
410
+
411
+ PQCLEAN_MLDSA65_CLEAN_polyveck_add(&t1, &t1, &s2);
412
+ PQCLEAN_MLDSA65_CLEAN_polyveck_caddq(&t1);
413
+ PQCLEAN_MLDSA65_CLEAN_polyveck_power2round(&t1, &t0, &t1);
414
+ PQCLEAN_MLDSA65_CLEAN_pack_pk(public_key, rho, &t1);
415
+
416
+ shake256(tr, TRBYTES, public_key, PQCLEAN_MLDSA65_CLEAN_CRYPTO_PUBLICKEYBYTES);
417
+ PQCLEAN_MLDSA65_CLEAN_pack_sk(secret_key, rho, tr, key, &t0, &s1, &s2);
418
+
419
+ pq_secure_wipe(seedbuf, sizeof(seedbuf));
420
+ pq_secure_wipe(tr, sizeof(tr));
421
+ pq_secure_wipe(&s1, sizeof(s1));
422
+ pq_secure_wipe(&s1hat, sizeof(s1hat));
423
+ pq_secure_wipe(&s2, sizeof(s2));
424
+ pq_secure_wipe(&t1, sizeof(t1));
425
+ pq_secure_wipe(&t0, sizeof(t0));
426
+ pq_secure_wipe(mat, sizeof(mat));
427
+ return PQ_SUCCESS;
428
+ }
429
+
430
+ static int pq_testing_mldsa_sign_from_seed_impl(uint8_t *signature, size_t *signature_len,
431
+ const uint8_t *message, size_t message_len,
432
+ const uint8_t *secret_key,
433
+ const uint8_t seed[RNDBYTES]) {
434
+ unsigned int n;
435
+ uint8_t seedbuf[2 * SEEDBYTES + TRBYTES + RNDBYTES + 2 * CRHBYTES];
436
+ uint8_t *rho, *tr, *key, *mu, *rhoprime, *rnd;
437
+ uint16_t nonce = 0;
438
+ polyvecl mat[K], s1, y, z;
439
+ polyveck t0, s2, w1, w0, h;
440
+ poly cp;
441
+ shake256incctx state;
442
+
443
+ rho = seedbuf;
444
+ tr = rho + SEEDBYTES;
445
+ key = tr + TRBYTES;
446
+ rnd = key + SEEDBYTES;
447
+ mu = rnd + RNDBYTES;
448
+ rhoprime = mu + CRHBYTES;
449
+ PQCLEAN_MLDSA65_CLEAN_unpack_sk(rho, tr, key, &t0, &s1, &s2, secret_key);
450
+
451
+ mu[0] = 0;
452
+ mu[1] = 0;
453
+ shake256_inc_init(&state);
454
+ shake256_inc_absorb(&state, tr, TRBYTES);
455
+ shake256_inc_absorb(&state, mu, 2);
456
+ shake256_inc_absorb(&state, message, message_len);
457
+ shake256_inc_finalize(&state);
458
+ shake256_inc_squeeze(mu, CRHBYTES, &state);
459
+ shake256_inc_ctx_release(&state);
460
+
461
+ memcpy(rnd, seed, RNDBYTES);
462
+ shake256(rhoprime, CRHBYTES, key, SEEDBYTES + RNDBYTES + CRHBYTES);
463
+
464
+ PQCLEAN_MLDSA65_CLEAN_polyvec_matrix_expand(mat, rho);
465
+ PQCLEAN_MLDSA65_CLEAN_polyvecl_ntt(&s1);
466
+ PQCLEAN_MLDSA65_CLEAN_polyveck_ntt(&s2);
467
+ PQCLEAN_MLDSA65_CLEAN_polyveck_ntt(&t0);
468
+
469
+ rej:
470
+ PQCLEAN_MLDSA65_CLEAN_polyvecl_uniform_gamma1(&y, rhoprime, nonce++);
471
+
472
+ z = y;
473
+ PQCLEAN_MLDSA65_CLEAN_polyvecl_ntt(&z);
474
+ PQCLEAN_MLDSA65_CLEAN_polyvec_matrix_pointwise_montgomery(&w1, mat, &z);
475
+ PQCLEAN_MLDSA65_CLEAN_polyveck_reduce(&w1);
476
+ PQCLEAN_MLDSA65_CLEAN_polyveck_invntt_tomont(&w1);
477
+
478
+ PQCLEAN_MLDSA65_CLEAN_polyveck_caddq(&w1);
479
+ PQCLEAN_MLDSA65_CLEAN_polyveck_decompose(&w1, &w0, &w1);
480
+ PQCLEAN_MLDSA65_CLEAN_polyveck_pack_w1(signature, &w1);
481
+
482
+ shake256_inc_init(&state);
483
+ shake256_inc_absorb(&state, mu, CRHBYTES);
484
+ shake256_inc_absorb(&state, signature, K * POLYW1_PACKEDBYTES);
485
+ shake256_inc_finalize(&state);
486
+ shake256_inc_squeeze(signature, CTILDEBYTES, &state);
487
+ shake256_inc_ctx_release(&state);
488
+ PQCLEAN_MLDSA65_CLEAN_poly_challenge(&cp, signature);
489
+ PQCLEAN_MLDSA65_CLEAN_poly_ntt(&cp);
490
+
491
+ PQCLEAN_MLDSA65_CLEAN_polyvecl_pointwise_poly_montgomery(&z, &cp, &s1);
492
+ PQCLEAN_MLDSA65_CLEAN_polyvecl_invntt_tomont(&z);
493
+ PQCLEAN_MLDSA65_CLEAN_polyvecl_add(&z, &z, &y);
494
+ PQCLEAN_MLDSA65_CLEAN_polyvecl_reduce(&z);
495
+ if (PQCLEAN_MLDSA65_CLEAN_polyvecl_chknorm(&z, GAMMA1 - BETA)) {
496
+ goto rej;
497
+ }
498
+
499
+ PQCLEAN_MLDSA65_CLEAN_polyveck_pointwise_poly_montgomery(&h, &cp, &s2);
500
+ PQCLEAN_MLDSA65_CLEAN_polyveck_invntt_tomont(&h);
501
+ PQCLEAN_MLDSA65_CLEAN_polyveck_sub(&w0, &w0, &h);
502
+ PQCLEAN_MLDSA65_CLEAN_polyveck_reduce(&w0);
503
+ if (PQCLEAN_MLDSA65_CLEAN_polyveck_chknorm(&w0, GAMMA2 - BETA)) {
504
+ goto rej;
505
+ }
506
+
507
+ PQCLEAN_MLDSA65_CLEAN_polyveck_pointwise_poly_montgomery(&h, &cp, &t0);
508
+ PQCLEAN_MLDSA65_CLEAN_polyveck_invntt_tomont(&h);
509
+ PQCLEAN_MLDSA65_CLEAN_polyveck_reduce(&h);
510
+ if (PQCLEAN_MLDSA65_CLEAN_polyveck_chknorm(&h, GAMMA2)) {
511
+ goto rej;
512
+ }
513
+
514
+ PQCLEAN_MLDSA65_CLEAN_polyveck_add(&w0, &w0, &h);
515
+ n = PQCLEAN_MLDSA65_CLEAN_polyveck_make_hint(&h, &w0, &w1);
516
+ if (n > OMEGA) {
517
+ goto rej;
518
+ }
519
+
520
+ PQCLEAN_MLDSA65_CLEAN_pack_sig(signature, signature, &z, &h);
521
+ *signature_len = PQCLEAN_MLDSA65_CLEAN_CRYPTO_BYTES;
522
+
523
+ pq_secure_wipe(seedbuf, sizeof(seedbuf));
524
+ pq_secure_wipe(&s1, sizeof(s1));
525
+ pq_secure_wipe(&y, sizeof(y));
526
+ pq_secure_wipe(&z, sizeof(z));
527
+ pq_secure_wipe(&t0, sizeof(t0));
528
+ pq_secure_wipe(&s2, sizeof(s2));
529
+ pq_secure_wipe(&w1, sizeof(w1));
530
+ pq_secure_wipe(&w0, sizeof(w0));
531
+ pq_secure_wipe(&h, sizeof(h));
532
+ pq_secure_wipe(&cp, sizeof(cp));
533
+ pq_secure_wipe(mat, sizeof(mat));
534
+ return PQ_SUCCESS;
535
+ }
536
+
537
+ int pq_testing_mldsa_keypair_from_seed(uint8_t *public_key, uint8_t *secret_key,
538
+ const uint8_t *seed, size_t seed_len) {
539
+ if (!public_key || !secret_key || !seed || seed_len != SEEDBYTES) {
540
+ return PQ_ERROR_BUFFER;
541
+ }
542
+
543
+ return pq_testing_mldsa_keypair_from_seed_impl(public_key, secret_key, seed);
544
+ }
545
+
546
+ int pq_testing_mldsa_sign_from_seed(uint8_t *signature, size_t *signature_len,
547
+ const uint8_t *message, size_t message_len,
548
+ const uint8_t *secret_key, const uint8_t *seed,
549
+ size_t seed_len) {
550
+ if (!signature || !signature_len || !message || !secret_key || !seed || seed_len != RNDBYTES) {
551
+ return PQ_ERROR_BUFFER;
552
+ }
553
+
554
+ return pq_testing_mldsa_sign_from_seed_impl(signature, signature_len, message, message_len,
555
+ secret_key, seed);
556
+ }
557
+
558
+ int pq_mlkem_keypair(uint8_t *public_key, uint8_t *secret_key) {
559
+ return mlkem_keypair(public_key, secret_key) == 0 ? PQ_SUCCESS : PQ_ERROR_KEYPAIR;
560
+ }
561
+
562
+ int pq_mlkem_encapsulate(uint8_t *ciphertext, uint8_t *shared_secret, const uint8_t *public_key) {
563
+ return mlkem_encapsulate(ciphertext, shared_secret, public_key) == 0 ? PQ_SUCCESS
564
+ : PQ_ERROR_ENCAPSULATE;
565
+ }
566
+
567
+ int pq_mlkem_decapsulate(uint8_t *shared_secret, const uint8_t *ciphertext,
568
+ const uint8_t *secret_key) {
569
+ return mlkem_decapsulate(shared_secret, ciphertext, secret_key) == 0 ? PQ_SUCCESS
570
+ : PQ_ERROR_DECAPSULATE;
571
+ }
572
+
573
+ int pq_hybrid_kem_keypair(uint8_t *public_key, uint8_t *secret_key) {
574
+ hybrid_public_key_t *pk = (hybrid_public_key_t *)public_key;
575
+ hybrid_secret_key_t *sk = (hybrid_secret_key_t *)secret_key;
576
+
577
+ memset(sk, 0, sizeof(*sk));
578
+
579
+ if (mlkem_keypair(pk->mlkem_pk, sk->mlkem_sk) != 0) {
580
+ pq_secure_wipe(sk, sizeof(*sk));
581
+ return PQ_ERROR_KEYPAIR;
582
+ }
583
+
584
+ if (x25519_keypair(pk->x25519_pk, sk->x25519_sk) != 0) {
585
+ pq_secure_wipe(sk, sizeof(*sk));
586
+ return PQ_ERROR_KEYPAIR;
587
+ }
588
+
589
+ return PQ_SUCCESS;
590
+ }
591
+
592
+ int pq_hybrid_kem_encapsulate(uint8_t *ciphertext, uint8_t *shared_secret,
593
+ const uint8_t *public_key) {
594
+ const hybrid_public_key_t *pk = (const hybrid_public_key_t *)public_key;
595
+ hybrid_ciphertext_t *ct = (hybrid_ciphertext_t *)ciphertext;
596
+
597
+ uint8_t mlkem_ss[MLKEM_SHAREDSECRETBYTES];
598
+ uint8_t x25519_ss[X25519_SHAREDSECRETBYTES];
599
+ uint8_t x25519_ephemeral_sk[X25519_SECRETKEYBYTES];
600
+ int ret = PQ_SUCCESS;
601
+
602
+ memset(mlkem_ss, 0, sizeof(mlkem_ss));
603
+ memset(x25519_ss, 0, sizeof(x25519_ss));
604
+ memset(x25519_ephemeral_sk, 0, sizeof(x25519_ephemeral_sk));
605
+
606
+ if (mlkem_encapsulate(ct->mlkem_ct, mlkem_ss, pk->mlkem_pk) != 0) {
607
+ ret = PQ_ERROR_ENCAPSULATE;
608
+ goto cleanup;
609
+ }
610
+
611
+ ret = x25519_keypair(ct->x25519_ephemeral, x25519_ephemeral_sk);
612
+ if (ret != PQ_SUCCESS) {
613
+ ret = PQ_ERROR_ENCAPSULATE;
614
+ goto cleanup;
615
+ }
616
+
617
+ ret = x25519_shared_secret(x25519_ss, pk->x25519_pk, x25519_ephemeral_sk);
618
+ if (ret != PQ_SUCCESS) {
619
+ ret = PQ_ERROR_ENCAPSULATE;
620
+ goto cleanup;
621
+ }
622
+
623
+ ret = hybrid_combiner(shared_secret, mlkem_ss, x25519_ss, pk->x25519_pk, ct);
624
+ if (ret != PQ_SUCCESS) {
625
+ goto cleanup;
626
+ }
627
+
628
+ ret = PQ_SUCCESS;
629
+
630
+ cleanup:
631
+ pq_secure_wipe(mlkem_ss, sizeof(mlkem_ss));
632
+ pq_secure_wipe(x25519_ss, sizeof(x25519_ss));
633
+ pq_secure_wipe(x25519_ephemeral_sk, sizeof(x25519_ephemeral_sk));
634
+ return ret;
635
+ }
636
+
637
+ int pq_hybrid_kem_decapsulate(uint8_t *shared_secret, const uint8_t *ciphertext,
638
+ const uint8_t *secret_key) {
639
+ const hybrid_ciphertext_t *ct = (const hybrid_ciphertext_t *)ciphertext;
640
+ uint8_t recipient_x25519_pk[X25519_PUBLICKEYBYTES];
641
+ const hybrid_secret_key_t *sk = (const hybrid_secret_key_t *)secret_key;
642
+
643
+ uint8_t mlkem_ss[MLKEM_SHAREDSECRETBYTES];
644
+ uint8_t x25519_ss[X25519_SHAREDSECRETBYTES];
645
+ int ret = PQ_SUCCESS;
646
+
647
+ memset(recipient_x25519_pk, 0, sizeof(recipient_x25519_pk));
648
+ memset(mlkem_ss, 0, sizeof(mlkem_ss));
649
+ memset(x25519_ss, 0, sizeof(x25519_ss));
650
+
651
+ ret = mlkem_decapsulate(mlkem_ss, ct->mlkem_ct, sk->mlkem_sk) == 0 ? PQ_SUCCESS
652
+ : PQ_ERROR_DECAPSULATE;
653
+ if (ret != PQ_SUCCESS) {
654
+ goto cleanup;
655
+ }
656
+
657
+ ret = x25519_shared_secret(x25519_ss, ct->x25519_ephemeral, sk->x25519_sk);
658
+ if (ret != PQ_SUCCESS) {
659
+ ret = PQ_ERROR_DECAPSULATE;
660
+ goto cleanup;
661
+ }
662
+
663
+ ret = x25519_public_from_secret(recipient_x25519_pk, sk->x25519_sk);
664
+ if (ret != PQ_SUCCESS) {
665
+ ret = PQ_ERROR_DECAPSULATE;
666
+ goto cleanup;
667
+ }
668
+
669
+ ret = hybrid_combiner(shared_secret, mlkem_ss, x25519_ss, recipient_x25519_pk, ct);
670
+
671
+ cleanup:
672
+ pq_secure_wipe(recipient_x25519_pk, sizeof(recipient_x25519_pk));
673
+ pq_secure_wipe(mlkem_ss, sizeof(mlkem_ss));
674
+ pq_secure_wipe(x25519_ss, sizeof(x25519_ss));
675
+ return ret;
676
+ }
677
+
678
+ int pq_sign_keypair(uint8_t *public_key, uint8_t *secret_key) {
679
+ return mldsa_keypair(public_key, secret_key) == 0 ? PQ_SUCCESS : PQ_ERROR_KEYPAIR;
680
+ }
681
+
682
+ int pq_sign(uint8_t *signature, size_t *signature_len, const uint8_t *message, size_t message_len,
683
+ const uint8_t *secret_key) {
684
+ return mldsa_sign(signature, signature_len, message, message_len, secret_key) == 0
685
+ ? PQ_SUCCESS
686
+ : PQ_ERROR_SIGN;
687
+ }
688
+
689
+ int pq_verify(const uint8_t *signature, size_t signature_len, const uint8_t *message,
690
+ size_t message_len, const uint8_t *public_key) {
691
+ return mldsa_verify(signature, signature_len, message, message_len, public_key) == 0
692
+ ? PQ_SUCCESS
693
+ : PQ_ERROR_VERIFY;
694
+ }
695
+
696
+ #define PQC_SERIALIZATION_MAGIC_0 'P'
697
+ #define PQC_SERIALIZATION_MAGIC_1 'Q'
698
+ #define PQC_SERIALIZATION_MAGIC_2 'C'
699
+ #define PQC_SERIALIZATION_MAGIC_3 '1'
700
+ #define PQC_SERIALIZATION_VERSION 0x01
701
+ #define PQC_SERIALIZATION_TYPE_PUBLIC 0x01
702
+ #define PQC_SERIALIZATION_TYPE_SECRET 0x02
703
+
704
+ static const char PQC_OID_ML_KEM_768[] = "2.25.186599352125448088867056807454444238446";
705
+ static const char PQC_OID_ML_KEM_768_X25519_HKDF_SHA256[] =
706
+ "2.25.260242945110721168101139140490528778800";
707
+ static const char PQC_OID_ML_DSA_65[] = "2.25.305232938483772195555080795650659207792";
708
+
709
+ static int pq_serialization_key_bytes_for_algorithm(const char *algorithm, int is_public,
710
+ size_t *expected_len) {
711
+ if (!algorithm || !expected_len)
712
+ return PQ_ERROR_BUFFER;
713
+ if (strcmp(algorithm, "ml_kem_768") == 0) {
714
+ *expected_len = is_public ? PQ_MLKEM_PUBLICKEYBYTES : PQ_MLKEM_SECRETKEYBYTES;
715
+ return PQ_SUCCESS;
716
+ }
717
+ if (strcmp(algorithm, "ml_kem_768_x25519_hkdf_sha256") == 0) {
718
+ *expected_len = is_public ? PQ_HYBRID_PUBLICKEYBYTES : PQ_HYBRID_SECRETKEYBYTES;
719
+ return PQ_SUCCESS;
720
+ }
721
+ if (strcmp(algorithm, "ml_dsa_65") == 0) {
722
+ *expected_len = is_public ? MLDSA_PUBLICKEYBYTES : MLDSA_SECRETKEYBYTES;
723
+ return PQ_SUCCESS;
724
+ }
725
+ return PQ_ERROR_BUFFER;
726
+ }
727
+
728
+ static int pq_serialization_oid_for_algorithm(const char *algorithm, const char **oid_out) {
729
+ if (!algorithm || !oid_out)
730
+ return PQ_ERROR_BUFFER;
731
+ if (strcmp(algorithm, "ml_kem_768") == 0) {
732
+ *oid_out = PQC_OID_ML_KEM_768;
733
+ return PQ_SUCCESS;
734
+ }
735
+ if (strcmp(algorithm, "ml_kem_768_x25519_hkdf_sha256") == 0) {
736
+ *oid_out = PQC_OID_ML_KEM_768_X25519_HKDF_SHA256;
737
+ return PQ_SUCCESS;
738
+ }
739
+ if (strcmp(algorithm, "ml_dsa_65") == 0) {
740
+ *oid_out = PQC_OID_ML_DSA_65;
741
+ return PQ_SUCCESS;
742
+ }
743
+ return PQ_ERROR_BUFFER;
744
+ }
745
+
746
+ static int pq_serialization_algorithm_for_oid(const char *oid, size_t oid_len,
747
+ const char **algorithm_out) {
748
+ if (!oid || !algorithm_out)
749
+ return PQ_ERROR_BUFFER;
750
+ if (oid_len == strlen(PQC_OID_ML_KEM_768) && memcmp(oid, PQC_OID_ML_KEM_768, oid_len) == 0) {
751
+ *algorithm_out = "ml_kem_768";
752
+ return PQ_SUCCESS;
753
+ }
754
+ if (oid_len == strlen(PQC_OID_ML_KEM_768_X25519_HKDF_SHA256) &&
755
+ memcmp(oid, PQC_OID_ML_KEM_768_X25519_HKDF_SHA256, oid_len) == 0) {
756
+ *algorithm_out = "ml_kem_768_x25519_hkdf_sha256";
757
+ return PQ_SUCCESS;
758
+ }
759
+ if (oid_len == strlen(PQC_OID_ML_DSA_65) && memcmp(oid, PQC_OID_ML_DSA_65, oid_len) == 0) {
760
+ *algorithm_out = "ml_dsa_65";
761
+ return PQ_SUCCESS;
762
+ }
763
+ return PQ_ERROR_BUFFER;
764
+ }
765
+
766
+ static int pq_encode_serialized_key(uint8_t **output, size_t *output_len, uint8_t type,
767
+ const uint8_t *key_bytes, size_t key_len,
768
+ const char *algorithm) {
769
+ const char *oid = NULL;
770
+ size_t expected_len = 0;
771
+ size_t oid_len;
772
+ size_t total_len = 0;
773
+ uint8_t *buf;
774
+ int ret;
775
+
776
+ if (!output || !output_len || !key_bytes || !algorithm)
777
+ return PQ_ERROR_BUFFER;
778
+
779
+ *output = NULL;
780
+ *output_len = 0;
781
+
782
+ ret = pq_serialization_key_bytes_for_algorithm(algorithm, type == PQC_SERIALIZATION_TYPE_PUBLIC,
783
+ &expected_len);
784
+ if (ret != PQ_SUCCESS || key_len != expected_len)
785
+ return PQ_ERROR_BUFFER;
786
+ ret = pq_serialization_oid_for_algorithm(algorithm, &oid);
787
+ if (ret != PQ_SUCCESS)
788
+ return ret;
789
+
790
+ oid_len = strlen(oid);
791
+ if (oid_len == 0 || oid_len > UINT16_MAX)
792
+ return PQ_ERROR_BUFFER;
793
+ if (key_len > UINT32_MAX)
794
+ return PQ_ERROR_BUFFER;
795
+
796
+ ret = pq_size_add(total_len, 4, &total_len);
797
+ if (ret != PQ_SUCCESS)
798
+ return ret;
799
+ ret = pq_size_add(total_len, 1 + 1 + 2, &total_len);
800
+ if (ret != PQ_SUCCESS)
801
+ return ret;
802
+ ret = pq_size_add(total_len, oid_len, &total_len);
803
+ if (ret != PQ_SUCCESS)
804
+ return ret;
805
+ ret = pq_size_add(total_len, 4, &total_len);
806
+ if (ret != PQ_SUCCESS)
807
+ return ret;
808
+ ret = pq_size_add(total_len, key_len, &total_len);
809
+ if (ret != PQ_SUCCESS)
810
+ return ret;
811
+
812
+ buf = malloc(total_len);
813
+ if (!buf)
814
+ return PQ_ERROR_NOMEM;
815
+
816
+ buf[0] = PQC_SERIALIZATION_MAGIC_0;
817
+ buf[1] = PQC_SERIALIZATION_MAGIC_1;
818
+ buf[2] = PQC_SERIALIZATION_MAGIC_2;
819
+ buf[3] = PQC_SERIALIZATION_MAGIC_3;
820
+ buf[4] = PQC_SERIALIZATION_VERSION;
821
+ buf[5] = type;
822
+ buf[6] = (uint8_t)((oid_len >> 8) & 0xFF);
823
+ buf[7] = (uint8_t)(oid_len & 0xFF);
824
+ memcpy(buf + 8, oid, oid_len);
825
+ buf[8 + oid_len + 0] = (uint8_t)((key_len >> 24) & 0xFF);
826
+ buf[8 + oid_len + 1] = (uint8_t)((key_len >> 16) & 0xFF);
827
+ buf[8 + oid_len + 2] = (uint8_t)((key_len >> 8) & 0xFF);
828
+ buf[8 + oid_len + 3] = (uint8_t)(key_len & 0xFF);
829
+ memcpy(buf + 8 + oid_len + 4, key_bytes, key_len);
830
+
831
+ *output = buf;
832
+ *output_len = total_len;
833
+ return PQ_SUCCESS;
834
+ }
835
+
836
+ static int pq_decode_serialized_key(const uint8_t *input, size_t input_len, uint8_t expected_type,
837
+ char **algorithm_out, uint8_t **key_out, size_t *key_len_out) {
838
+ uint16_t oid_len;
839
+ uint32_t key_len;
840
+ const char *algorithm = NULL;
841
+ size_t offset;
842
+ size_t expected_len = 0;
843
+ uint8_t *key_copy = NULL;
844
+ char *algorithm_copy = NULL;
845
+ int ret;
846
+
847
+ if (!input || !algorithm_out || !key_out || !key_len_out)
848
+ return PQ_ERROR_BUFFER;
849
+
850
+ *algorithm_out = NULL;
851
+ *key_out = NULL;
852
+ *key_len_out = 0;
853
+
854
+ if (input_len < 12)
855
+ return PQ_ERROR_BUFFER;
856
+ if (input[0] != PQC_SERIALIZATION_MAGIC_0 || input[1] != PQC_SERIALIZATION_MAGIC_1 ||
857
+ input[2] != PQC_SERIALIZATION_MAGIC_2 || input[3] != PQC_SERIALIZATION_MAGIC_3) {
858
+ return PQ_ERROR_BUFFER;
859
+ }
860
+ if (input[4] != PQC_SERIALIZATION_VERSION || input[5] != expected_type)
861
+ return PQ_ERROR_BUFFER;
862
+
863
+ oid_len = ((uint16_t)input[6] << 8) | (uint16_t)input[7];
864
+ if (oid_len == 0)
865
+ return PQ_ERROR_BUFFER;
866
+ offset = 8;
867
+ if (input_len < offset || input_len - offset < (size_t)oid_len + 4)
868
+ return PQ_ERROR_BUFFER;
869
+ ret = pq_serialization_algorithm_for_oid((const char *)(input + offset), oid_len, &algorithm);
870
+ if (ret != PQ_SUCCESS)
871
+ return ret;
872
+ offset += oid_len;
873
+ key_len = ((uint32_t)input[offset + 0] << 24) | ((uint32_t)input[offset + 1] << 16) |
874
+ ((uint32_t)input[offset + 2] << 8) | (uint32_t)input[offset + 3];
875
+ offset += 4;
876
+ if (input_len < offset || input_len - offset != (size_t)key_len)
877
+ return PQ_ERROR_BUFFER;
878
+ ret = pq_serialization_key_bytes_for_algorithm(
879
+ algorithm, expected_type == PQC_SERIALIZATION_TYPE_PUBLIC, &expected_len);
880
+ if (ret != PQ_SUCCESS || (size_t)key_len != expected_len)
881
+ return PQ_ERROR_BUFFER;
882
+
883
+ key_copy = malloc((size_t)key_len);
884
+ if (!key_copy)
885
+ return PQ_ERROR_NOMEM;
886
+ memcpy(key_copy, input + offset, (size_t)key_len);
887
+
888
+ {
889
+ size_t algorithm_len = strlen(algorithm);
890
+ algorithm_copy = malloc(algorithm_len + 1);
891
+ if (!algorithm_copy) {
892
+ pq_secure_wipe(key_copy, (size_t)key_len);
893
+ free(key_copy);
894
+ return PQ_ERROR_NOMEM;
895
+ }
896
+ memcpy(algorithm_copy, algorithm, algorithm_len + 1);
897
+ }
898
+
899
+ *algorithm_out = algorithm_copy;
900
+ *key_out = key_copy;
901
+ *key_len_out = (size_t)key_len;
902
+ return PQ_SUCCESS;
903
+ }
904
+
905
+ static int pq_der_to_pem(const char *label, const uint8_t *der, size_t der_len, char **output,
906
+ size_t *output_len) {
907
+ size_t encoded_len = 0;
908
+ size_t line_count = 0;
909
+ size_t body_len = 0;
910
+ size_t total_len = 0;
911
+ unsigned char *encoded = NULL;
912
+ char *pem = NULL;
913
+ char header[64];
914
+ char footer[64];
915
+ char *cursor;
916
+ int header_len, footer_len;
917
+ int ret;
918
+
919
+ if (!label || !der || !output || !output_len)
920
+ return PQ_ERROR_BUFFER;
921
+ *output = NULL;
922
+ *output_len = 0;
923
+ header_len = snprintf(header, sizeof(header), "-----BEGIN %s-----", label);
924
+ footer_len = snprintf(footer, sizeof(footer), "-----END %s-----", label);
925
+ if (header_len <= 0 || footer_len <= 0)
926
+ return PQ_ERROR_BUFFER;
927
+ if (der_len > (size_t)INT_MAX)
928
+ return PQ_ERROR_BUFFER;
929
+
930
+ ret = pq_size_mul((der_len + 2) / 3, 4, &encoded_len);
931
+ if (ret != PQ_SUCCESS)
932
+ return ret;
933
+ ret = pq_size_add(encoded_len, 63, &line_count);
934
+ if (ret != PQ_SUCCESS)
935
+ return ret;
936
+ line_count /= 64;
937
+ ret = pq_size_add(encoded_len, line_count, &body_len);
938
+ if (ret != PQ_SUCCESS)
939
+ return ret;
940
+ ret = pq_size_add((size_t)header_len, body_len, &total_len);
941
+ if (ret != PQ_SUCCESS)
942
+ return ret;
943
+ ret = pq_size_add(total_len, (size_t)footer_len, &total_len);
944
+ if (ret != PQ_SUCCESS)
945
+ return ret;
946
+
947
+ encoded = malloc(encoded_len + 1);
948
+ pem = malloc(total_len + 1);
949
+ if (!encoded || !pem) {
950
+ free(encoded);
951
+ free(pem);
952
+ return PQ_ERROR_NOMEM;
953
+ }
954
+ if (EVP_EncodeBlock(encoded, der, (int)der_len) != (int)encoded_len) {
955
+ free(encoded);
956
+ pq_secure_wipe(pem, total_len + 1);
957
+ free(pem);
958
+ return PQ_ERROR_OPENSSL;
959
+ }
960
+ cursor = pem;
961
+ memcpy(cursor, header, (size_t)header_len);
962
+ cursor += header_len;
963
+ for (size_t off = 0; off < encoded_len; off += 64) {
964
+ size_t chunk = encoded_len - off;
965
+ if (chunk > 64)
966
+ chunk = 64;
967
+ memcpy(cursor, encoded + off, chunk);
968
+ cursor += chunk;
969
+ *cursor++ = '\n';
970
+ }
971
+ memcpy(cursor, footer, (size_t)footer_len);
972
+ cursor += footer_len;
973
+ *cursor = '\0';
974
+ *output = pem;
975
+ *output_len = (size_t)(cursor - pem);
976
+ pq_secure_wipe(encoded, encoded_len + 1);
977
+ free(encoded);
978
+ return PQ_SUCCESS;
979
+ }
980
+
981
+ static int pq_pem_to_der(const char *label, const char *input, size_t input_len, uint8_t **der_out,
982
+ size_t *der_len_out) {
983
+ char header[64], footer[64];
984
+ int header_len, footer_len;
985
+ const char *body_start, *footer_pos;
986
+ const char *tail;
987
+ char *encoded = NULL;
988
+ uint8_t *der = NULL;
989
+ size_t encoded_len = 0;
990
+ size_t der_capacity = 0;
991
+ int decoded_len;
992
+ int ret;
993
+ size_t pad = 0;
994
+
995
+ if (!label || !input || !der_out || !der_len_out)
996
+ return PQ_ERROR_BUFFER;
997
+ *der_out = NULL;
998
+ *der_len_out = 0;
999
+ header_len = snprintf(header, sizeof(header), "-----BEGIN %s-----", label);
1000
+ footer_len = snprintf(footer, sizeof(footer), "-----END %s-----", label);
1001
+ if (header_len <= 0 || footer_len <= 0)
1002
+ return PQ_ERROR_BUFFER;
1003
+ if (input_len < (size_t)(header_len + footer_len + 2))
1004
+ return PQ_ERROR_BUFFER;
1005
+ if (strncmp(input, header, (size_t)header_len) != 0)
1006
+ return PQ_ERROR_BUFFER;
1007
+ body_start = input + header_len;
1008
+ while ((size_t)(body_start - input) < input_len && pq_is_pem_whitespace(*body_start))
1009
+ body_start++;
1010
+ footer_pos = NULL;
1011
+ {
1012
+ size_t remaining = input_len - (size_t)(body_start - input);
1013
+ size_t footer_size = (size_t)footer_len;
1014
+ if (remaining < footer_size)
1015
+ return PQ_ERROR_BUFFER;
1016
+ for (size_t i = 0; i <= remaining - footer_size; ++i) {
1017
+ if (memcmp(body_start + i, footer, footer_size) == 0) {
1018
+ footer_pos = body_start + i;
1019
+ break;
1020
+ }
1021
+ }
1022
+ }
1023
+ if (!footer_pos)
1024
+ return PQ_ERROR_BUFFER;
1025
+
1026
+ tail = footer_pos + footer_len;
1027
+ while ((size_t)(tail - input) < input_len) {
1028
+ if (!pq_is_pem_whitespace(*tail))
1029
+ return PQ_ERROR_BUFFER;
1030
+ tail++;
1031
+ }
1032
+
1033
+ encoded = malloc((size_t)(footer_pos - body_start) + 1);
1034
+ if (!encoded)
1035
+ return PQ_ERROR_NOMEM;
1036
+ for (const char *p = body_start; p < footer_pos; ++p) {
1037
+ if (pq_is_pem_whitespace(*p))
1038
+ continue;
1039
+ encoded[encoded_len++] = *p;
1040
+ }
1041
+ if (encoded_len == 0 || (encoded_len % 4) != 0) {
1042
+ free(encoded);
1043
+ return PQ_ERROR_BUFFER;
1044
+ }
1045
+ encoded[encoded_len] = '\0';
1046
+
1047
+ ret = pq_size_mul(encoded_len / 4, 3, &der_capacity);
1048
+ if (ret != PQ_SUCCESS) {
1049
+ pq_secure_wipe(encoded, encoded_len + 1);
1050
+ free(encoded);
1051
+ return ret;
1052
+ }
1053
+ ret = pq_size_add(der_capacity, 1, &der_capacity);
1054
+ if (ret != PQ_SUCCESS || der_capacity == 0) {
1055
+ pq_secure_wipe(encoded, encoded_len + 1);
1056
+ free(encoded);
1057
+ return PQ_ERROR_BUFFER;
1058
+ }
1059
+
1060
+ der = malloc(der_capacity);
1061
+ if (!der) {
1062
+ pq_secure_wipe(encoded, encoded_len + 1);
1063
+ free(encoded);
1064
+ return PQ_ERROR_NOMEM;
1065
+ }
1066
+ decoded_len = EVP_DecodeBlock(der, (unsigned char *)encoded, (int)encoded_len);
1067
+ if (decoded_len < 0) {
1068
+ pq_secure_wipe(encoded, encoded_len + 1);
1069
+ free(encoded);
1070
+ pq_secure_wipe(der, der_capacity);
1071
+ free(der);
1072
+ return PQ_ERROR_BUFFER;
1073
+ }
1074
+ if (encoded_len >= 1 && encoded[encoded_len - 1] == '=')
1075
+ pad++;
1076
+ if (encoded_len >= 2 && encoded[encoded_len - 2] == '=')
1077
+ pad++;
1078
+ if ((size_t)decoded_len < pad) {
1079
+ pq_secure_wipe(encoded, encoded_len + 1);
1080
+ free(encoded);
1081
+ pq_secure_wipe(der, der_capacity);
1082
+ free(der);
1083
+ return PQ_ERROR_BUFFER;
1084
+ }
1085
+ *der_len_out = (size_t)decoded_len - pad;
1086
+ *der_out = der;
1087
+ pq_secure_wipe(encoded, encoded_len + 1);
1088
+ free(encoded);
1089
+ return PQ_SUCCESS;
1090
+ }
1091
+
1092
+ int pq_public_key_to_pqc_container_der(uint8_t **output, size_t *output_len,
1093
+ const uint8_t *public_key, size_t public_key_len,
1094
+ const char *algorithm) {
1095
+ return pq_encode_serialized_key(output, output_len, PQC_SERIALIZATION_TYPE_PUBLIC, public_key,
1096
+ public_key_len, algorithm);
1097
+ }
1098
+
1099
+ int pq_public_key_to_pqc_container_pem(char **output, size_t *output_len, const uint8_t *public_key,
1100
+ size_t public_key_len, const char *algorithm) {
1101
+ uint8_t *der = NULL;
1102
+ size_t der_len = 0;
1103
+ int ret;
1104
+ ret = pq_public_key_to_pqc_container_der(&der, &der_len, public_key, public_key_len, algorithm);
1105
+ if (ret != PQ_SUCCESS)
1106
+ return ret;
1107
+ ret = pq_der_to_pem("PQC PUBLIC KEY CONTAINER", der, der_len, output, output_len);
1108
+ pq_secure_wipe(der, der_len);
1109
+ free(der);
1110
+ return ret;
1111
+ }
1112
+
1113
+ int pq_secret_key_to_pqc_container_der(uint8_t **output, size_t *output_len,
1114
+ const uint8_t *secret_key, size_t secret_key_len,
1115
+ const char *algorithm) {
1116
+ return pq_encode_serialized_key(output, output_len, PQC_SERIALIZATION_TYPE_SECRET, secret_key,
1117
+ secret_key_len, algorithm);
1118
+ }
1119
+
1120
+ int pq_secret_key_to_pqc_container_pem(char **output, size_t *output_len, const uint8_t *secret_key,
1121
+ size_t secret_key_len, const char *algorithm) {
1122
+ uint8_t *der = NULL;
1123
+ size_t der_len = 0;
1124
+ int ret;
1125
+ ret = pq_secret_key_to_pqc_container_der(&der, &der_len, secret_key, secret_key_len, algorithm);
1126
+ if (ret != PQ_SUCCESS)
1127
+ return ret;
1128
+ ret = pq_der_to_pem("PQC PRIVATE KEY CONTAINER", der, der_len, output, output_len);
1129
+ pq_secure_wipe(der, der_len);
1130
+ free(der);
1131
+ return ret;
1132
+ }
1133
+
1134
+ int pq_public_key_from_pqc_container_der(char **algorithm_out, uint8_t **key_out,
1135
+ size_t *key_len_out, const uint8_t *input,
1136
+ size_t input_len) {
1137
+ return pq_decode_serialized_key(input, input_len, PQC_SERIALIZATION_TYPE_PUBLIC, algorithm_out,
1138
+ key_out, key_len_out);
1139
+ }
1140
+
1141
+ int pq_public_key_from_pqc_container_pem(char **algorithm_out, uint8_t **key_out,
1142
+ size_t *key_len_out, const char *input, size_t input_len) {
1143
+ uint8_t *der = NULL;
1144
+ size_t der_len = 0;
1145
+ int ret;
1146
+ ret = pq_pem_to_der("PQC PUBLIC KEY CONTAINER", input, input_len, &der, &der_len);
1147
+ if (ret != PQ_SUCCESS)
1148
+ return ret;
1149
+ ret = pq_public_key_from_pqc_container_der(algorithm_out, key_out, key_len_out, der, der_len);
1150
+ pq_secure_wipe(der, der_len);
1151
+ free(der);
1152
+ return ret;
1153
+ }
1154
+
1155
+ int pq_secret_key_from_pqc_container_der(char **algorithm_out, uint8_t **key_out,
1156
+ size_t *key_len_out, const uint8_t *input,
1157
+ size_t input_len) {
1158
+ return pq_decode_serialized_key(input, input_len, PQC_SERIALIZATION_TYPE_SECRET, algorithm_out,
1159
+ key_out, key_len_out);
1160
+ }
1161
+
1162
+ int pq_secret_key_from_pqc_container_pem(char **algorithm_out, uint8_t **key_out,
1163
+ size_t *key_len_out, const char *input, size_t input_len) {
1164
+ uint8_t *der = NULL;
1165
+ size_t der_len = 0;
1166
+ int ret;
1167
+ ret = pq_pem_to_der("PQC PRIVATE KEY CONTAINER", input, input_len, &der, &der_len);
1168
+ if (ret != PQ_SUCCESS)
1169
+ return ret;
1170
+ ret = pq_secret_key_from_pqc_container_der(algorithm_out, key_out, key_len_out, der, der_len);
1171
+ pq_secure_wipe(der, der_len);
1172
+ free(der);
1173
+ return ret;
1174
+ }
1175
+
1176
+ const char *pq_version(void) {
1177
+ return "0.1.0";
1178
+ }