pq_crypto 0.5.0 → 0.5.1

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.
@@ -15,8 +15,6 @@
15
15
  #include <openssl/crypto.h>
16
16
  #include <openssl/evp.h>
17
17
  #include <openssl/rand.h>
18
- #include <openssl/bio.h>
19
- #include <openssl/buffer.h>
20
18
 
21
19
  #if OPENSSL_VERSION_NUMBER < 0x30000000L
22
20
  #error "OpenSSL 3.0 or later is required for pq_crypto"
@@ -49,43 +47,6 @@ static int pq_is_pem_whitespace(char c) {
49
47
  return c == '\n' || c == '\r' || c == ' ' || c == '\t';
50
48
  }
51
49
 
52
- static int x25519_keypair(uint8_t *pk, uint8_t *sk) {
53
- EVP_PKEY_CTX *ctx = NULL;
54
- EVP_PKEY *pkey = NULL;
55
- size_t pklen = X25519_PUBLICKEYBYTES;
56
- size_t sklen = X25519_SECRETKEYBYTES;
57
- int ret = PQ_ERROR_KEYPAIR;
58
-
59
- ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL);
60
- if (!ctx)
61
- goto cleanup;
62
-
63
- if (EVP_PKEY_keygen_init(ctx) <= 0)
64
- goto cleanup;
65
-
66
- if (EVP_PKEY_keygen(ctx, &pkey) <= 0)
67
- goto cleanup;
68
-
69
- if (EVP_PKEY_get_raw_private_key(pkey, sk, &sklen) <= 0)
70
- goto cleanup;
71
- if (sklen != X25519_SECRETKEYBYTES)
72
- goto cleanup;
73
-
74
- if (EVP_PKEY_get_raw_public_key(pkey, pk, &pklen) <= 0)
75
- goto cleanup;
76
- if (pklen != X25519_PUBLICKEYBYTES)
77
- goto cleanup;
78
-
79
- ret = PQ_SUCCESS;
80
-
81
- cleanup:
82
- if (pkey)
83
- EVP_PKEY_free(pkey);
84
- if (ctx)
85
- EVP_PKEY_CTX_free(ctx);
86
- return ret;
87
- }
88
-
89
50
  static int x25519_public_from_private(uint8_t *pk, const uint8_t *sk) {
90
51
  EVP_PKEY *pkey = NULL;
91
52
  size_t pklen = X25519_PUBLICKEYBYTES;
@@ -112,16 +73,15 @@ cleanup:
112
73
  return ret;
113
74
  }
114
75
 
115
- static int x25519_shared_secret(uint8_t *shared, const uint8_t *their_pk, const uint8_t *my_sk) {
76
+ static int x25519_shared_secret_with_pkey(uint8_t *shared, const uint8_t *their_pk, EVP_PKEY *pkey) {
116
77
  EVP_PKEY_CTX *ctx = NULL;
117
- EVP_PKEY *pkey = NULL;
118
78
  EVP_PKEY *peer_key = NULL;
119
79
  size_t shared_len = X25519_SHAREDSECRETBYTES;
120
80
  int ret = PQ_ERROR_ENCAPSULATE;
121
81
 
122
- pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, my_sk, X25519_SECRETKEYBYTES);
123
- if (!pkey)
124
- goto cleanup;
82
+ if (!shared || !their_pk || !pkey) {
83
+ return PQ_ERROR_BUFFER;
84
+ }
125
85
 
126
86
  peer_key = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, their_pk, X25519_PUBLICKEYBYTES);
127
87
  if (!peer_key)
@@ -150,53 +110,94 @@ cleanup:
150
110
  EVP_PKEY_CTX_free(ctx);
151
111
  if (peer_key)
152
112
  EVP_PKEY_free(peer_key);
153
- if (pkey)
154
- EVP_PKEY_free(pkey);
155
113
  return ret;
156
114
  }
157
115
 
158
- static const uint8_t XWING_LABEL[6] = {0x5c, 0x2e, 0x2f, 0x2f, 0x5e, 0x5c};
116
+ static int x25519_shared_secret(uint8_t *shared, const uint8_t *their_pk, const uint8_t *my_sk) {
117
+ EVP_PKEY *pkey = NULL;
118
+ int ret;
159
119
 
160
- static int xwing_combiner(uint8_t shared_secret[HYBRID_SHAREDSECRETBYTES],
161
- const uint8_t ss_M[MLKEM_SHAREDSECRETBYTES],
162
- const uint8_t ss_X[X25519_SHAREDSECRETBYTES],
163
- const uint8_t ct_X[X25519_PUBLICKEYBYTES],
164
- const uint8_t pk_X[X25519_PUBLICKEYBYTES]) {
165
- EVP_MD_CTX *ctx = EVP_MD_CTX_new();
166
- unsigned int out_len = 0;
167
- int ret = PQ_ERROR_OPENSSL;
120
+ if (!shared || !their_pk || !my_sk) {
121
+ return PQ_ERROR_BUFFER;
122
+ }
168
123
 
169
- if (!ctx) {
170
- return PQ_ERROR_OPENSSL;
124
+ pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, my_sk, X25519_SECRETKEYBYTES);
125
+ if (!pkey)
126
+ return PQ_ERROR_ENCAPSULATE;
127
+
128
+ ret = x25519_shared_secret_with_pkey(shared, their_pk, pkey);
129
+ EVP_PKEY_free(pkey);
130
+ return ret;
131
+ }
132
+
133
+ static int x25519_ephemeral_keypair_and_shared_secret(uint8_t *ephemeral_pk, uint8_t *shared,
134
+ const uint8_t *their_pk) {
135
+ EVP_PKEY_CTX *keygen_ctx = NULL;
136
+ EVP_PKEY *ephemeral_pkey = NULL;
137
+ size_t pklen = X25519_PUBLICKEYBYTES;
138
+ int ret = PQ_ERROR_ENCAPSULATE;
139
+
140
+ if (!ephemeral_pk || !shared || !their_pk) {
141
+ return PQ_ERROR_BUFFER;
171
142
  }
172
143
 
173
- if (EVP_DigestInit_ex(ctx, EVP_sha3_256(), NULL) != 1)
174
- goto cleanup;
175
- if (EVP_DigestUpdate(ctx, ss_M, MLKEM_SHAREDSECRETBYTES) != 1)
176
- goto cleanup;
177
- if (EVP_DigestUpdate(ctx, ss_X, X25519_SHAREDSECRETBYTES) != 1)
178
- goto cleanup;
179
- if (EVP_DigestUpdate(ctx, ct_X, X25519_PUBLICKEYBYTES) != 1)
144
+ keygen_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL);
145
+ if (!keygen_ctx)
180
146
  goto cleanup;
181
- if (EVP_DigestUpdate(ctx, pk_X, X25519_PUBLICKEYBYTES) != 1)
147
+
148
+ if (EVP_PKEY_keygen_init(keygen_ctx) <= 0)
182
149
  goto cleanup;
183
- if (EVP_DigestUpdate(ctx, XWING_LABEL, sizeof(XWING_LABEL)) != 1)
150
+
151
+ if (EVP_PKEY_keygen(keygen_ctx, &ephemeral_pkey) <= 0)
184
152
  goto cleanup;
185
- if (EVP_DigestFinal_ex(ctx, shared_secret, &out_len) != 1)
153
+
154
+ if (EVP_PKEY_get_raw_public_key(ephemeral_pkey, ephemeral_pk, &pklen) <= 0)
186
155
  goto cleanup;
187
- if (out_len != HYBRID_SHAREDSECRETBYTES)
156
+ if (pklen != X25519_PUBLICKEYBYTES)
188
157
  goto cleanup;
189
158
 
190
- ret = PQ_SUCCESS;
159
+ ret = x25519_shared_secret_with_pkey(shared, their_pk, ephemeral_pkey);
191
160
 
192
161
  cleanup:
193
- EVP_MD_CTX_free(ctx);
162
+ if (ephemeral_pkey)
163
+ EVP_PKEY_free(ephemeral_pkey);
164
+ if (keygen_ctx)
165
+ EVP_PKEY_CTX_free(keygen_ctx);
194
166
  return ret;
195
167
  }
196
168
 
169
+ static const uint8_t XWING_LABEL[6] = {0x5c, 0x2e, 0x2f, 0x2f, 0x5e, 0x5c};
170
+
171
+ static int xwing_combiner(uint8_t shared_secret[HYBRID_SHAREDSECRETBYTES],
172
+ const uint8_t ss_M[MLKEM_SHAREDSECRETBYTES],
173
+ const uint8_t ss_X[X25519_SHAREDSECRETBYTES],
174
+ const uint8_t ct_X[X25519_PUBLICKEYBYTES],
175
+ const uint8_t pk_X[X25519_PUBLICKEYBYTES]) {
176
+ uint8_t input[MLKEM_SHAREDSECRETBYTES + X25519_SHAREDSECRETBYTES +
177
+ X25519_PUBLICKEYBYTES + X25519_PUBLICKEYBYTES + sizeof(XWING_LABEL)];
178
+ uint8_t *cur = input;
179
+
180
+ if (!shared_secret || !ss_M || !ss_X || !ct_X || !pk_X) {
181
+ return PQ_ERROR_BUFFER;
182
+ }
183
+
184
+ memcpy(cur, ss_M, MLKEM_SHAREDSECRETBYTES);
185
+ cur += MLKEM_SHAREDSECRETBYTES;
186
+ memcpy(cur, ss_X, X25519_SHAREDSECRETBYTES);
187
+ cur += X25519_SHAREDSECRETBYTES;
188
+ memcpy(cur, ct_X, X25519_PUBLICKEYBYTES);
189
+ cur += X25519_PUBLICKEYBYTES;
190
+ memcpy(cur, pk_X, X25519_PUBLICKEYBYTES);
191
+ cur += X25519_PUBLICKEYBYTES;
192
+ memcpy(cur, XWING_LABEL, sizeof(XWING_LABEL));
193
+
194
+ pqcr_mlkem_sha3_256(shared_secret, input, sizeof(input));
195
+ pq_secure_wipe(input, sizeof(input));
196
+ return PQ_SUCCESS;
197
+ }
198
+
197
199
  static int xwing_expand_secret_key(hybrid_expanded_secret_key_t *expanded_key,
198
200
  const uint8_t seed[HYBRID_SECRETKEYBYTES]) {
199
- EVP_MD_CTX *ctx = NULL;
200
201
  uint8_t expanded[XWING_EXPANDEDBYTES];
201
202
  int ret = PQ_ERROR_OPENSSL;
202
203
 
@@ -207,16 +208,7 @@ static int xwing_expand_secret_key(hybrid_expanded_secret_key_t *expanded_key,
207
208
  memset(expanded_key, 0, sizeof(*expanded_key));
208
209
  memset(expanded, 0, sizeof(expanded));
209
210
 
210
- ctx = EVP_MD_CTX_new();
211
- if (!ctx)
212
- goto cleanup;
213
-
214
- if (EVP_DigestInit_ex(ctx, EVP_shake256(), NULL) != 1)
215
- goto cleanup;
216
- if (EVP_DigestUpdate(ctx, seed, HYBRID_SECRETKEYBYTES) != 1)
217
- goto cleanup;
218
- if (EVP_DigestFinalXOF(ctx, expanded, sizeof(expanded)) != 1)
219
- goto cleanup;
211
+ pqcr_mlkem_shake256(expanded, sizeof(expanded), seed, HYBRID_SECRETKEYBYTES);
220
212
 
221
213
  ret = pqcr_mlkem768_keypair_derand(expanded_key->mlkem_pk, expanded_key->mlkem_sk, expanded);
222
214
  if (ret != 0) {
@@ -233,8 +225,6 @@ static int xwing_expand_secret_key(hybrid_expanded_secret_key_t *expanded_key,
233
225
  ret = PQ_SUCCESS;
234
226
 
235
227
  cleanup:
236
- if (ctx)
237
- EVP_MD_CTX_free(ctx);
238
228
  pq_secure_wipe(expanded, sizeof(expanded));
239
229
  if (ret != PQ_SUCCESS && expanded_key) {
240
230
  pq_secure_wipe(expanded_key, sizeof(*expanded_key));
@@ -517,7 +507,6 @@ int pq_hybrid_kem_encapsulate(uint8_t *ciphertext, uint8_t *shared_secret,
517
507
  hybrid_ciphertext_t ct;
518
508
  uint8_t mlkem_ss[MLKEM_SHAREDSECRETBYTES];
519
509
  uint8_t x25519_ss[X25519_SHAREDSECRETBYTES];
520
- uint8_t x25519_ephemeral_sk[X25519_SECRETKEYBYTES];
521
510
  int ret = PQ_SUCCESS;
522
511
 
523
512
  if (!ciphertext || !shared_secret || !public_key) {
@@ -528,20 +517,14 @@ int pq_hybrid_kem_encapsulate(uint8_t *ciphertext, uint8_t *shared_secret,
528
517
  memset(&ct, 0, sizeof(ct));
529
518
  memset(mlkem_ss, 0, sizeof(mlkem_ss));
530
519
  memset(x25519_ss, 0, sizeof(x25519_ss));
531
- memset(x25519_ephemeral_sk, 0, sizeof(x25519_ephemeral_sk));
532
520
 
533
521
  if (pqcr_mlkem768_enc(ct.mlkem_ct, mlkem_ss, pk.mlkem_pk) != 0) {
534
522
  ret = PQ_ERROR_ENCAPSULATE;
535
523
  goto cleanup;
536
524
  }
537
525
 
538
- ret = x25519_keypair(ct.x25519_ephemeral, x25519_ephemeral_sk);
539
- if (ret != PQ_SUCCESS) {
540
- ret = PQ_ERROR_ENCAPSULATE;
541
- goto cleanup;
542
- }
543
-
544
- ret = x25519_shared_secret(x25519_ss, pk.x25519_pk, x25519_ephemeral_sk);
526
+ ret = x25519_ephemeral_keypair_and_shared_secret(ct.x25519_ephemeral, x25519_ss,
527
+ pk.x25519_pk);
545
528
  if (ret != PQ_SUCCESS) {
546
529
  ret = PQ_ERROR_ENCAPSULATE;
547
530
  goto cleanup;
@@ -557,51 +540,121 @@ int pq_hybrid_kem_encapsulate(uint8_t *ciphertext, uint8_t *shared_secret,
557
540
  cleanup:
558
541
  pq_secure_wipe(mlkem_ss, sizeof(mlkem_ss));
559
542
  pq_secure_wipe(x25519_ss, sizeof(x25519_ss));
560
- pq_secure_wipe(x25519_ephemeral_sk, sizeof(x25519_ephemeral_sk));
561
543
  return ret;
562
544
  }
563
545
 
564
- int pq_hybrid_kem_decapsulate(uint8_t *shared_secret, const uint8_t *ciphertext,
565
- const uint8_t *secret_key) {
566
- hybrid_ciphertext_t ct;
546
+ int pq_hybrid_kem_expand_secret_key(uint8_t *expanded_secret_key, const uint8_t *secret_key) {
567
547
  hybrid_expanded_secret_key_t expanded;
548
+ int ret;
549
+
550
+ if (!expanded_secret_key || !secret_key) {
551
+ return PQ_ERROR_BUFFER;
552
+ }
553
+
554
+ memset(&expanded, 0, sizeof(expanded));
555
+ ret = xwing_expand_secret_key(&expanded, secret_key);
556
+ if (ret == PQ_SUCCESS) {
557
+ memcpy(expanded_secret_key, &expanded, sizeof(expanded));
558
+ }
559
+ pq_secure_wipe(&expanded, sizeof(expanded));
560
+ return ret == PQ_SUCCESS ? PQ_SUCCESS : PQ_ERROR_DECAPSULATE;
561
+ }
562
+
563
+ int pq_hybrid_kem_decapsulate_expanded(uint8_t *shared_secret, const uint8_t *ciphertext,
564
+ const uint8_t *expanded_secret_key) {
565
+ hybrid_ciphertext_t ct;
566
+ const hybrid_expanded_secret_key_t *expanded;
568
567
  uint8_t mlkem_ss[MLKEM_SHAREDSECRETBYTES];
569
568
  uint8_t x25519_ss[X25519_SHAREDSECRETBYTES];
570
569
  int ret = PQ_SUCCESS;
571
570
 
572
- if (!shared_secret || !ciphertext || !secret_key) {
571
+ if (!shared_secret || !ciphertext || !expanded_secret_key) {
573
572
  return PQ_ERROR_BUFFER;
574
573
  }
575
574
 
575
+ expanded = (const hybrid_expanded_secret_key_t *)expanded_secret_key;
576
576
  memcpy(&ct, ciphertext, HYBRID_CIPHERTEXTBYTES);
577
- memset(&expanded, 0, sizeof(expanded));
578
577
  memset(mlkem_ss, 0, sizeof(mlkem_ss));
579
578
  memset(x25519_ss, 0, sizeof(x25519_ss));
580
579
 
581
- ret = xwing_expand_secret_key(&expanded, secret_key);
580
+ if (pqcr_mlkem768_dec(mlkem_ss, ct.mlkem_ct, expanded->mlkem_sk) != 0) {
581
+ ret = PQ_ERROR_DECAPSULATE;
582
+ goto cleanup;
583
+ }
584
+
585
+ ret = x25519_shared_secret(x25519_ss, ct.x25519_ephemeral, expanded->x25519_sk);
582
586
  if (ret != PQ_SUCCESS) {
583
587
  ret = PQ_ERROR_DECAPSULATE;
584
588
  goto cleanup;
585
589
  }
586
590
 
587
- if (pqcr_mlkem768_dec(mlkem_ss, ct.mlkem_ct, expanded.mlkem_sk) != 0) {
591
+ ret = xwing_combiner(shared_secret, mlkem_ss, x25519_ss, ct.x25519_ephemeral,
592
+ expanded->x25519_pk);
593
+
594
+ cleanup:
595
+ pq_secure_wipe(mlkem_ss, sizeof(mlkem_ss));
596
+ pq_secure_wipe(x25519_ss, sizeof(x25519_ss));
597
+ return ret;
598
+ }
599
+
600
+ int pq_hybrid_kem_decapsulate_expanded_pkey(uint8_t *shared_secret, const uint8_t *ciphertext,
601
+ const uint8_t *expanded_secret_key,
602
+ void *x25519_private_pkey) {
603
+ hybrid_ciphertext_t ct;
604
+ const hybrid_expanded_secret_key_t *expanded;
605
+ uint8_t mlkem_ss[MLKEM_SHAREDSECRETBYTES];
606
+ uint8_t x25519_ss[X25519_SHAREDSECRETBYTES];
607
+ int ret = PQ_SUCCESS;
608
+
609
+ if (!shared_secret || !ciphertext || !expanded_secret_key || !x25519_private_pkey) {
610
+ return PQ_ERROR_BUFFER;
611
+ }
612
+
613
+ expanded = (const hybrid_expanded_secret_key_t *)expanded_secret_key;
614
+ memcpy(&ct, ciphertext, HYBRID_CIPHERTEXTBYTES);
615
+ memset(mlkem_ss, 0, sizeof(mlkem_ss));
616
+ memset(x25519_ss, 0, sizeof(x25519_ss));
617
+
618
+ if (pqcr_mlkem768_dec(mlkem_ss, ct.mlkem_ct, expanded->mlkem_sk) != 0) {
588
619
  ret = PQ_ERROR_DECAPSULATE;
589
620
  goto cleanup;
590
621
  }
591
622
 
592
- ret = x25519_shared_secret(x25519_ss, ct.x25519_ephemeral, expanded.x25519_sk);
623
+ ret = x25519_shared_secret_with_pkey(x25519_ss, ct.x25519_ephemeral,
624
+ (EVP_PKEY *)x25519_private_pkey);
593
625
  if (ret != PQ_SUCCESS) {
594
626
  ret = PQ_ERROR_DECAPSULATE;
595
627
  goto cleanup;
596
628
  }
597
629
 
598
- ret =
599
- xwing_combiner(shared_secret, mlkem_ss, x25519_ss, ct.x25519_ephemeral, expanded.x25519_pk);
630
+ ret = xwing_combiner(shared_secret, mlkem_ss, x25519_ss, ct.x25519_ephemeral,
631
+ expanded->x25519_pk);
600
632
 
601
633
  cleanup:
602
634
  pq_secure_wipe(mlkem_ss, sizeof(mlkem_ss));
603
635
  pq_secure_wipe(x25519_ss, sizeof(x25519_ss));
604
- pq_secure_wipe(&expanded, sizeof(expanded));
636
+ return ret;
637
+ }
638
+
639
+ int pq_hybrid_kem_decapsulate(uint8_t *shared_secret, const uint8_t *ciphertext,
640
+ const uint8_t *secret_key) {
641
+ uint8_t expanded[HYBRID_EXPANDED_SECRETKEYBYTES];
642
+ int ret;
643
+
644
+ if (!shared_secret || !ciphertext || !secret_key) {
645
+ return PQ_ERROR_BUFFER;
646
+ }
647
+
648
+ memset(expanded, 0, sizeof(expanded));
649
+ ret = pq_hybrid_kem_expand_secret_key(expanded, secret_key);
650
+ if (ret != PQ_SUCCESS) {
651
+ goto cleanup;
652
+ }
653
+
654
+ ret = pq_hybrid_kem_decapsulate_expanded(shared_secret, ciphertext, expanded);
655
+
656
+ cleanup:
657
+ pq_secure_wipe(expanded, sizeof(expanded));
605
658
  return ret;
606
659
  }
607
660
 
@@ -805,24 +858,51 @@ static int pq_decode_serialized_key(const uint8_t *input, size_t input_len, uint
805
858
  return PQ_SUCCESS;
806
859
  }
807
860
 
861
+ static int pq_base64_char_value(unsigned char c) {
862
+ if (c >= 'A' && c <= 'Z')
863
+ return (int)(c - 'A');
864
+ if (c >= 'a' && c <= 'z')
865
+ return (int)(c - 'a' + 26);
866
+ if (c >= '0' && c <= '9')
867
+ return (int)(c - '0' + 52);
868
+ if (c == '+')
869
+ return 62;
870
+ if (c == '/')
871
+ return 63;
872
+ if (c == '=')
873
+ return 64;
874
+ return -1;
875
+ }
876
+
877
+ static const char *pq_find_pem_footer(const char *start, size_t len, const char *footer,
878
+ size_t footer_len) {
879
+ if (!start || !footer || footer_len == 0 || len < footer_len)
880
+ return NULL;
881
+
882
+ for (size_t i = 0; i <= len - footer_len; ++i) {
883
+ if (start[i] == '-' && memcmp(start + i, footer, footer_len) == 0)
884
+ return start + i;
885
+ }
886
+ return NULL;
887
+ }
888
+
808
889
  static int pq_der_to_pem(const char *label, const uint8_t *der, size_t der_len, char **output,
809
890
  size_t *output_len) {
810
- BIO *bio_mem = NULL;
811
- BIO *bio_b64 = NULL;
812
- BIO *bio_chain = NULL;
813
- BUF_MEM *bptr = NULL;
814
891
  char header[64];
815
892
  char footer[64];
816
893
  int header_len, footer_len;
817
- int ret = PQ_ERROR_OPENSSL;
818
894
  char *pem = NULL;
819
- size_t total_len = 0;
820
- size_t needed = 0;
895
+ unsigned char *encoded = NULL;
896
+ size_t encoded_len;
897
+ size_t line_count;
898
+ size_t needed;
899
+ char *cur;
821
900
 
822
901
  if (!label || !der || !output || !output_len)
823
902
  return PQ_ERROR_BUFFER;
824
903
  *output = NULL;
825
904
  *output_len = 0;
905
+
826
906
  header_len = snprintf(header, sizeof(header), "-----BEGIN %s-----", label);
827
907
  footer_len = snprintf(footer, sizeof(footer), "-----END %s-----", label);
828
908
  if (header_len <= 0 || footer_len <= 0)
@@ -830,68 +910,60 @@ static int pq_der_to_pem(const char *label, const uint8_t *der, size_t der_len,
830
910
  if (der_len > (size_t)INT_MAX)
831
911
  return PQ_ERROR_BUFFER;
832
912
 
833
- bio_b64 = BIO_new(BIO_f_base64());
834
- bio_mem = BIO_new(BIO_s_mem());
835
- if (!bio_b64 || !bio_mem) {
836
- ret = PQ_ERROR_NOMEM;
837
- goto cleanup;
913
+ encoded_len = 4 * ((der_len + 2) / 3);
914
+ if (encoded_len > (size_t)INT_MAX)
915
+ return PQ_ERROR_BUFFER;
916
+
917
+ encoded = malloc(encoded_len + 1);
918
+ if (!encoded)
919
+ return PQ_ERROR_NOMEM;
920
+
921
+ if (EVP_EncodeBlock(encoded, der, (int)der_len) != (int)encoded_len) {
922
+ pq_secure_wipe(encoded, encoded_len + 1);
923
+ free(encoded);
924
+ return PQ_ERROR_OPENSSL;
838
925
  }
839
926
 
840
- bio_chain = BIO_push(bio_b64, bio_mem);
927
+ line_count = encoded_len == 0 ? 0 : ((encoded_len + 63) / 64);
928
+ if (SIZE_MAX - (size_t)header_len < 1 || SIZE_MAX - ((size_t)header_len + 1) < encoded_len ||
929
+ SIZE_MAX - ((size_t)header_len + 1 + encoded_len) < line_count ||
930
+ SIZE_MAX - ((size_t)header_len + 1 + encoded_len + line_count) < (size_t)footer_len + 1) {
931
+ pq_secure_wipe(encoded, encoded_len + 1);
932
+ free(encoded);
933
+ return PQ_ERROR_BUFFER;
934
+ }
935
+ needed = (size_t)header_len + 1 + encoded_len + line_count + (size_t)footer_len + 1;
841
936
 
842
- if (BIO_write(bio_chain, der, (int)der_len) != (int)der_len)
843
- goto cleanup;
844
- if (BIO_flush(bio_chain) != 1)
845
- goto cleanup;
846
- BIO_get_mem_ptr(bio_chain, &bptr);
847
- if (!bptr || !bptr->data)
848
- goto cleanup;
937
+ pem = malloc(needed);
938
+ if (!pem) {
939
+ pq_secure_wipe(encoded, encoded_len + 1);
940
+ free(encoded);
941
+ return PQ_ERROR_NOMEM;
942
+ }
849
943
 
850
- {
851
- size_t body_len = bptr->length;
852
- needed = (size_t)header_len + 1 + body_len;
853
- if (body_len == 0 || bptr->data[body_len - 1] != '\n')
854
- needed += 1;
855
- needed += (size_t)footer_len + 1;
856
-
857
- pem = malloc(needed);
858
- if (!pem) {
859
- ret = PQ_ERROR_NOMEM;
860
- goto cleanup;
861
- }
862
- char *cur = pem;
863
- memcpy(cur, header, (size_t)header_len);
864
- cur += header_len;
944
+ cur = pem;
945
+ memcpy(cur, header, (size_t)header_len);
946
+ cur += header_len;
947
+ *cur++ = '\n';
948
+
949
+ for (size_t offset = 0; offset < encoded_len; offset += 64) {
950
+ size_t line_len = encoded_len - offset;
951
+ if (line_len > 64)
952
+ line_len = 64;
953
+ memcpy(cur, encoded + offset, line_len);
954
+ cur += line_len;
865
955
  *cur++ = '\n';
866
- memcpy(cur, bptr->data, body_len);
867
- cur += body_len;
868
- if (body_len == 0 || bptr->data[body_len - 1] != '\n')
869
- *cur++ = '\n';
870
- memcpy(cur, footer, (size_t)footer_len);
871
- cur += footer_len;
872
- *cur = '\0';
873
- total_len = (size_t)(cur - pem);
874
956
  }
875
957
 
876
- *output = pem;
877
- *output_len = total_len;
878
- pem = NULL;
879
- ret = PQ_SUCCESS;
958
+ memcpy(cur, footer, (size_t)footer_len);
959
+ cur += footer_len;
960
+ *cur = '\0';
880
961
 
881
- cleanup:
882
- if (bio_chain) {
883
- BIO_free_all(bio_chain);
884
- } else {
885
- if (bio_b64)
886
- BIO_free(bio_b64);
887
- if (bio_mem)
888
- BIO_free(bio_mem);
889
- }
890
- if (pem) {
891
- pq_secure_wipe(pem, needed);
892
- free(pem);
893
- }
894
- return ret;
962
+ *output = pem;
963
+ *output_len = (size_t)(cur - pem);
964
+ pq_secure_wipe(encoded, encoded_len + 1);
965
+ free(encoded);
966
+ return PQ_SUCCESS;
895
967
  }
896
968
 
897
969
  static int pq_pem_to_der(const char *label, const char *input, size_t input_len, uint8_t **der_out,
@@ -901,17 +973,19 @@ static int pq_pem_to_der(const char *label, const char *input, size_t input_len,
901
973
  const char *body_start, *footer_pos;
902
974
  const char *tail;
903
975
  uint8_t *der = NULL;
976
+ unsigned char *compact = NULL;
904
977
  size_t body_len = 0;
905
- int ret;
906
- BIO *bio_b64 = NULL;
907
- BIO *bio_mem = NULL;
908
- BIO *bio_chain = NULL;
978
+ size_t compact_len = 0;
979
+ size_t der_cap = 0;
909
980
  int decoded_len = 0;
981
+ int padding = 0;
982
+ int saw_padding = 0;
910
983
 
911
984
  if (!label || !input || !der_out || !der_len_out)
912
985
  return PQ_ERROR_BUFFER;
913
986
  *der_out = NULL;
914
987
  *der_len_out = 0;
988
+
915
989
  header_len = snprintf(header, sizeof(header), "-----BEGIN %s-----", label);
916
990
  footer_len = snprintf(footer, sizeof(footer), "-----END %s-----", label);
917
991
  if (header_len <= 0 || footer_len <= 0)
@@ -920,22 +994,13 @@ static int pq_pem_to_der(const char *label, const char *input, size_t input_len,
920
994
  return PQ_ERROR_BUFFER;
921
995
  if (strncmp(input, header, (size_t)header_len) != 0)
922
996
  return PQ_ERROR_BUFFER;
997
+
923
998
  body_start = input + header_len;
924
999
  while ((size_t)(body_start - input) < input_len && pq_is_pem_whitespace(*body_start))
925
1000
  body_start++;
926
- footer_pos = NULL;
927
- {
928
- size_t remaining = input_len - (size_t)(body_start - input);
929
- size_t footer_size = (size_t)footer_len;
930
- if (remaining < footer_size)
931
- return PQ_ERROR_BUFFER;
932
- for (size_t i = 0; i <= remaining - footer_size; ++i) {
933
- if (memcmp(body_start + i, footer, footer_size) == 0) {
934
- footer_pos = body_start + i;
935
- break;
936
- }
937
- }
938
- }
1001
+
1002
+ footer_pos = pq_find_pem_footer(body_start, input_len - (size_t)(body_start - input), footer,
1003
+ (size_t)footer_len);
939
1004
  if (!footer_pos)
940
1005
  return PQ_ERROR_BUFFER;
941
1006
 
@@ -950,53 +1015,65 @@ static int pq_pem_to_der(const char *label, const char *input, size_t input_len,
950
1015
  if (body_len > (size_t)INT_MAX)
951
1016
  return PQ_ERROR_BUFFER;
952
1017
 
953
- {
954
- size_t der_cap = (body_len * 3) / 4 + 3;
955
- der = malloc(der_cap ? der_cap : 1);
956
- if (!der)
957
- return PQ_ERROR_NOMEM;
1018
+ compact = malloc(body_len ? body_len : 1);
1019
+ if (!compact)
1020
+ return PQ_ERROR_NOMEM;
958
1021
 
959
- bio_mem = BIO_new_mem_buf(body_start, (int)body_len);
960
- bio_b64 = BIO_new(BIO_f_base64());
961
- if (!bio_mem || !bio_b64) {
962
- ret = PQ_ERROR_NOMEM;
963
- goto cleanup;
964
- }
965
- bio_chain = BIO_push(bio_b64, bio_mem);
1022
+ for (size_t i = 0; i < body_len; ++i) {
1023
+ unsigned char c = (unsigned char)body_start[i];
1024
+ int value;
1025
+ if (pq_is_pem_whitespace((char)c))
1026
+ continue;
966
1027
 
967
- decoded_len = BIO_read(bio_chain, der, (int)der_cap);
968
- if (decoded_len <= 0) {
969
- ret = PQ_ERROR_BUFFER;
970
- goto cleanup;
1028
+ value = pq_base64_char_value(c);
1029
+ if (value < 0) {
1030
+ pq_secure_wipe(compact, body_len);
1031
+ free(compact);
1032
+ return PQ_ERROR_BUFFER;
971
1033
  }
972
- {
973
- unsigned char tail_byte;
974
- int extra = BIO_read(bio_chain, &tail_byte, 1);
975
- if (extra > 0) {
976
- ret = PQ_ERROR_BUFFER;
977
- goto cleanup;
1034
+ if (c == '=') {
1035
+ saw_padding = 1;
1036
+ padding++;
1037
+ if (padding > 2) {
1038
+ pq_secure_wipe(compact, body_len);
1039
+ free(compact);
1040
+ return PQ_ERROR_BUFFER;
978
1041
  }
1042
+ } else if (saw_padding) {
1043
+ pq_secure_wipe(compact, body_len);
1044
+ free(compact);
1045
+ return PQ_ERROR_BUFFER;
979
1046
  }
1047
+ compact[compact_len++] = c;
1048
+ }
980
1049
 
981
- *der_len_out = (size_t)decoded_len;
982
- *der_out = der;
983
- der = NULL;
984
- ret = PQ_SUCCESS;
1050
+ if (compact_len == 0 || (compact_len % 4) != 0 || compact_len > (size_t)INT_MAX) {
1051
+ pq_secure_wipe(compact, body_len);
1052
+ free(compact);
1053
+ return PQ_ERROR_BUFFER;
985
1054
  }
986
1055
 
987
- cleanup:
988
- if (bio_chain) {
989
- BIO_free_all(bio_chain);
990
- } else {
991
- if (bio_b64)
992
- BIO_free(bio_b64);
993
- if (bio_mem)
994
- BIO_free(bio_mem);
1056
+ der_cap = (compact_len / 4) * 3;
1057
+ der = malloc(der_cap ? der_cap : 1);
1058
+ if (!der) {
1059
+ pq_secure_wipe(compact, body_len);
1060
+ free(compact);
1061
+ return PQ_ERROR_NOMEM;
995
1062
  }
996
- if (der) {
1063
+
1064
+ decoded_len = EVP_DecodeBlock(der, compact, (int)compact_len);
1065
+ pq_secure_wipe(compact, body_len);
1066
+ free(compact);
1067
+ compact = NULL;
1068
+ if (decoded_len <= 0 || decoded_len < padding) {
1069
+ pq_secure_wipe(der, der_cap);
997
1070
  free(der);
1071
+ return PQ_ERROR_BUFFER;
998
1072
  }
999
- return ret;
1073
+
1074
+ *der_len_out = (size_t)(decoded_len - padding);
1075
+ *der_out = der;
1076
+ return PQ_SUCCESS;
1000
1077
  }
1001
1078
 
1002
1079
  int pq_public_key_to_pqc_container_der(uint8_t **output, size_t *output_len,