xref: /openssl/crypto/hpke/hpke_util.c (revision a1c03068)
1 /*
2  * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
3  *
4  * Licensed under the Apache License 2.0 (the "License").  You may not use
5  * this file except in compliance with the License.  You can obtain a copy
6  * in the file LICENSE in the source distribution or at
7  * https://www.openssl.org/source/license.html
8  */
9 
10 #include <string.h>
11 #include <openssl/core_names.h>
12 #include <openssl/kdf.h>
13 #include <openssl/params.h>
14 #include <openssl/err.h>
15 #include <openssl/proverr.h>
16 #include <openssl/hpke.h>
17 #include <openssl/sha.h>
18 #include <openssl/rand.h>
19 #include "crypto/ecx.h"
20 #include "crypto/rand.h"
21 #include "internal/hpke_util.h"
22 #include "internal/packet.h"
23 #include "internal/nelem.h"
24 #include "internal/common.h"
25 
26 /*
27  * Delimiter used in OSSL_HPKE_str2suite
28  */
29 #define OSSL_HPKE_STR_DELIMCHAR ','
30 
31 /*
32  * table with identifier and synonym strings
33  * right now, there are 4 synonyms for each - a name, a hex string
34  * a hex string with a leading zero and a decimal string - more
35  * could be added but that seems like enough
36  */
37 typedef struct {
38     uint16_t id;
39     char *synonyms[4];
40 } synonymttab_t;
41 
42 /* max length of string we'll try map to a suite */
43 #define OSSL_HPKE_MAX_SUITESTR 38
44 
45 /* Define HPKE labels from RFC9180 in hex for EBCDIC compatibility */
46 /* ASCII: "HPKE-v1", in hex for EBCDIC compatibility */
47 static const char LABEL_HPKEV1[] = "\x48\x50\x4B\x45\x2D\x76\x31";
48 
49 /*
50  * Note that if additions are made to the set of IANA codepoints
51  * and the tables below, corresponding additions should also be
52  * made to the synonymtab tables a little further down so that
53  * OSSL_HPKE_str2suite() continues to function correctly.
54  *
55  * The canonical place to check for IANA registered codepoints
56  * is: https://www.iana.org/assignments/hpke/hpke.xhtml
57  */
58 
59 /*
60  * @brief table of KEMs
61  * See RFC9180 Section 7.1 "Table 2 KEM IDs"
62  */
63 static const OSSL_HPKE_KEM_INFO hpke_kem_tab[] = {
64 #ifndef OPENSSL_NO_EC
65     { OSSL_HPKE_KEM_ID_P256, "EC", OSSL_HPKE_KEMSTR_P256,
66       LN_sha256, SHA256_DIGEST_LENGTH, 65, 65, 32, 0xFF },
67     { OSSL_HPKE_KEM_ID_P384, "EC", OSSL_HPKE_KEMSTR_P384,
68       LN_sha384, SHA384_DIGEST_LENGTH, 97, 97, 48, 0xFF },
69     { OSSL_HPKE_KEM_ID_P521, "EC", OSSL_HPKE_KEMSTR_P521,
70       LN_sha512, SHA512_DIGEST_LENGTH, 133, 133, 66, 0x01 },
71 # ifndef OPENSSL_NO_ECX
72     { OSSL_HPKE_KEM_ID_X25519, OSSL_HPKE_KEMSTR_X25519, NULL,
73       LN_sha256, SHA256_DIGEST_LENGTH,
74       X25519_KEYLEN, X25519_KEYLEN, X25519_KEYLEN, 0x00 },
75     { OSSL_HPKE_KEM_ID_X448, OSSL_HPKE_KEMSTR_X448, NULL,
76       LN_sha512, SHA512_DIGEST_LENGTH,
77       X448_KEYLEN, X448_KEYLEN, X448_KEYLEN, 0x00 }
78 # endif
79 #else
80     { OSSL_HPKE_KEM_ID_RESERVED, NULL, NULL, NULL, 0, 0, 0, 0, 0x00 }
81 #endif
82 };
83 
84 /*
85  * @brief table of AEADs
86  * See RFC9180 Section 7.2 "Table 3 KDF IDs"
87  */
88 static const OSSL_HPKE_AEAD_INFO hpke_aead_tab[] = {
89     { OSSL_HPKE_AEAD_ID_AES_GCM_128, LN_aes_128_gcm, 16, 16,
90       OSSL_HPKE_MAX_NONCELEN },
91     { OSSL_HPKE_AEAD_ID_AES_GCM_256, LN_aes_256_gcm, 16, 32,
92       OSSL_HPKE_MAX_NONCELEN },
93 #if !defined(OPENSSL_NO_CHACHA) && !defined(OPENSSL_NO_POLY1305)
94     { OSSL_HPKE_AEAD_ID_CHACHA_POLY1305, LN_chacha20_poly1305, 16, 32,
95       OSSL_HPKE_MAX_NONCELEN },
96 #endif
97     { OSSL_HPKE_AEAD_ID_EXPORTONLY, NULL, 0, 0, 0 }
98 };
99 
100 /*
101  * @brief table of KDFs
102  * See RFC9180 Section 7.3 "Table 5 AEAD IDs"
103  */
104 static const OSSL_HPKE_KDF_INFO hpke_kdf_tab[] = {
105     { OSSL_HPKE_KDF_ID_HKDF_SHA256, LN_sha256, SHA256_DIGEST_LENGTH },
106     { OSSL_HPKE_KDF_ID_HKDF_SHA384, LN_sha384, SHA384_DIGEST_LENGTH },
107     { OSSL_HPKE_KDF_ID_HKDF_SHA512, LN_sha512, SHA512_DIGEST_LENGTH }
108 };
109 
110 /**
111  * Synonym tables for KEMs, KDFs and AEADs: idea is to allow
112  * mapping strings to suites with a little flexibility in terms
113  * of allowing a name or a couple of forms of number (for
114  * the IANA codepoint). If new IANA codepoints are allocated
115  * then these tables should be updated at the same time as the
116  * others above.
117  *
118  * The function to use these is ossl_hpke_str2suite() further down
119  * this file and shouldn't need modification so long as the table
120  * sizes (i.e. allow exactly 4 synonyms) don't change.
121  */
122 static const synonymttab_t kemstrtab[] = {
123     {OSSL_HPKE_KEM_ID_P256,
124      {OSSL_HPKE_KEMSTR_P256, "0x10", "0x10", "16" }},
125     {OSSL_HPKE_KEM_ID_P384,
126      {OSSL_HPKE_KEMSTR_P384, "0x11", "0x11", "17" }},
127     {OSSL_HPKE_KEM_ID_P521,
128      {OSSL_HPKE_KEMSTR_P521, "0x12", "0x12", "18" }},
129 # ifndef OPENSSL_NO_ECX
130     {OSSL_HPKE_KEM_ID_X25519,
131      {OSSL_HPKE_KEMSTR_X25519, "0x20", "0x20", "32" }},
132     {OSSL_HPKE_KEM_ID_X448,
133      {OSSL_HPKE_KEMSTR_X448, "0x21", "0x21", "33" }}
134 # endif
135 };
136 static const synonymttab_t kdfstrtab[] = {
137     {OSSL_HPKE_KDF_ID_HKDF_SHA256,
138      {OSSL_HPKE_KDFSTR_256, "0x1", "0x01", "1"}},
139     {OSSL_HPKE_KDF_ID_HKDF_SHA384,
140      {OSSL_HPKE_KDFSTR_384, "0x2", "0x02", "2"}},
141     {OSSL_HPKE_KDF_ID_HKDF_SHA512,
142      {OSSL_HPKE_KDFSTR_512, "0x3", "0x03", "3"}}
143 };
144 static const synonymttab_t aeadstrtab[] = {
145     {OSSL_HPKE_AEAD_ID_AES_GCM_128,
146      {OSSL_HPKE_AEADSTR_AES128GCM, "0x1", "0x01", "1"}},
147     {OSSL_HPKE_AEAD_ID_AES_GCM_256,
148      {OSSL_HPKE_AEADSTR_AES256GCM, "0x2", "0x02", "2"}},
149     {OSSL_HPKE_AEAD_ID_CHACHA_POLY1305,
150      {OSSL_HPKE_AEADSTR_CP, "0x3", "0x03", "3"}},
151     {OSSL_HPKE_AEAD_ID_EXPORTONLY,
152      {OSSL_HPKE_AEADSTR_EXP, "ff", "0xff", "255"}}
153 };
154 
155 /* Return an object containing KEM constants associated with a EC curve name */
ossl_HPKE_KEM_INFO_find_curve(const char * curve)156 const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_curve(const char *curve)
157 {
158     int i, sz = OSSL_NELEM(hpke_kem_tab);
159 
160     for (i = 0; i < sz; ++i) {
161         const char *group = hpke_kem_tab[i].groupname;
162 
163         if (group == NULL)
164             group = hpke_kem_tab[i].keytype;
165         if (OPENSSL_strcasecmp(curve, group) == 0)
166             return &hpke_kem_tab[i];
167     }
168     ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE);
169     return NULL;
170 }
171 
ossl_HPKE_KEM_INFO_find_id(uint16_t kemid)172 const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_id(uint16_t kemid)
173 {
174     int i, sz = OSSL_NELEM(hpke_kem_tab);
175 
176     /*
177      * this check can happen if we're in a no-ec build and there are no
178      * KEMS available
179      */
180     if (kemid == OSSL_HPKE_KEM_ID_RESERVED) {
181         ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE);
182         return NULL;
183     }
184     for (i = 0; i != sz; ++i) {
185         if (hpke_kem_tab[i].kem_id == kemid)
186             return &hpke_kem_tab[i];
187     }
188     ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CURVE);
189     return NULL;
190 }
191 
ossl_HPKE_KEM_INFO_find_random(OSSL_LIB_CTX * ctx)192 const OSSL_HPKE_KEM_INFO *ossl_HPKE_KEM_INFO_find_random(OSSL_LIB_CTX *ctx)
193 {
194     uint32_t rval = 0;
195     int err = 0;
196     size_t sz = OSSL_NELEM(hpke_kem_tab);
197 
198     rval = ossl_rand_uniform_uint32(ctx, sz, &err);
199     return (err == 1 ? NULL : &hpke_kem_tab[rval]);
200 }
201 
ossl_HPKE_KDF_INFO_find_id(uint16_t kdfid)202 const OSSL_HPKE_KDF_INFO *ossl_HPKE_KDF_INFO_find_id(uint16_t kdfid)
203 {
204     int i, sz = OSSL_NELEM(hpke_kdf_tab);
205 
206     for (i = 0; i != sz; ++i) {
207         if (hpke_kdf_tab[i].kdf_id == kdfid)
208             return &hpke_kdf_tab[i];
209     }
210     ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KDF);
211     return NULL;
212 }
213 
ossl_HPKE_KDF_INFO_find_random(OSSL_LIB_CTX * ctx)214 const OSSL_HPKE_KDF_INFO *ossl_HPKE_KDF_INFO_find_random(OSSL_LIB_CTX *ctx)
215 {
216     uint32_t rval = 0;
217     int err = 0;
218     size_t sz = OSSL_NELEM(hpke_kdf_tab);
219 
220     rval = ossl_rand_uniform_uint32(ctx, sz, &err);
221     return (err == 1 ? NULL : &hpke_kdf_tab[rval]);
222 }
223 
ossl_HPKE_AEAD_INFO_find_id(uint16_t aeadid)224 const OSSL_HPKE_AEAD_INFO *ossl_HPKE_AEAD_INFO_find_id(uint16_t aeadid)
225 {
226     int i, sz = OSSL_NELEM(hpke_aead_tab);
227 
228     for (i = 0; i != sz; ++i) {
229         if (hpke_aead_tab[i].aead_id == aeadid)
230             return &hpke_aead_tab[i];
231     }
232     ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_AEAD);
233     return NULL;
234 }
235 
ossl_HPKE_AEAD_INFO_find_random(OSSL_LIB_CTX * ctx)236 const OSSL_HPKE_AEAD_INFO *ossl_HPKE_AEAD_INFO_find_random(OSSL_LIB_CTX *ctx)
237 {
238     uint32_t rval = 0;
239     int err = 0;
240     /* the minus 1 below is so we don't pick the EXPORTONLY codepoint */
241     size_t sz = OSSL_NELEM(hpke_aead_tab) - 1;
242 
243     rval = ossl_rand_uniform_uint32(ctx, sz, &err);
244     return (err == 1 ? NULL : &hpke_aead_tab[rval]);
245 }
246 
kdf_derive(EVP_KDF_CTX * kctx,unsigned char * out,size_t outlen,int mode,const unsigned char * salt,size_t saltlen,const unsigned char * ikm,size_t ikmlen,const unsigned char * info,size_t infolen)247 static int kdf_derive(EVP_KDF_CTX *kctx,
248                       unsigned char *out, size_t outlen, int mode,
249                       const unsigned char *salt, size_t saltlen,
250                       const unsigned char *ikm, size_t ikmlen,
251                       const unsigned char *info, size_t infolen)
252 {
253     int ret;
254     OSSL_PARAM params[5], *p = params;
255 
256     *p++ = OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode);
257     if (salt != NULL)
258         *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT,
259                                                  (char *)salt, saltlen);
260     if (ikm != NULL)
261         *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY,
262                                                  (char *)ikm, ikmlen);
263     if (info != NULL)
264         *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO,
265                                                  (char *)info, infolen);
266     *p = OSSL_PARAM_construct_end();
267     ret = EVP_KDF_derive(kctx, out, outlen, params) > 0;
268     if (!ret)
269         ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_DURING_DERIVATION);
270     return ret;
271 }
272 
ossl_hpke_kdf_extract(EVP_KDF_CTX * kctx,unsigned char * prk,size_t prklen,const unsigned char * salt,size_t saltlen,const unsigned char * ikm,size_t ikmlen)273 int ossl_hpke_kdf_extract(EVP_KDF_CTX *kctx,
274                           unsigned char *prk, size_t prklen,
275                           const unsigned char *salt, size_t saltlen,
276                           const unsigned char *ikm, size_t ikmlen)
277 {
278     return kdf_derive(kctx, prk, prklen, EVP_KDF_HKDF_MODE_EXTRACT_ONLY,
279                       salt, saltlen, ikm, ikmlen, NULL, 0);
280 }
281 
282 /* Common code to perform a HKDF expand */
ossl_hpke_kdf_expand(EVP_KDF_CTX * kctx,unsigned char * okm,size_t okmlen,const unsigned char * prk,size_t prklen,const unsigned char * info,size_t infolen)283 int ossl_hpke_kdf_expand(EVP_KDF_CTX *kctx,
284                          unsigned char *okm, size_t okmlen,
285                          const unsigned char *prk, size_t prklen,
286                          const unsigned char *info, size_t infolen)
287 {
288     return kdf_derive(kctx, okm, okmlen, EVP_KDF_HKDF_MODE_EXPAND_ONLY,
289                       NULL, 0, prk, prklen, info, infolen);
290 }
291 
292 /*
293  * See RFC 9180 Section 4 LabelExtract()
294  */
ossl_hpke_labeled_extract(EVP_KDF_CTX * kctx,unsigned char * prk,size_t prklen,const unsigned char * salt,size_t saltlen,const char * protocol_label,const unsigned char * suiteid,size_t suiteidlen,const char * label,const unsigned char * ikm,size_t ikmlen)295 int ossl_hpke_labeled_extract(EVP_KDF_CTX *kctx,
296                               unsigned char *prk, size_t prklen,
297                               const unsigned char *salt, size_t saltlen,
298                               const char *protocol_label,
299                               const unsigned char *suiteid, size_t suiteidlen,
300                               const char *label,
301                               const unsigned char *ikm, size_t ikmlen)
302 {
303     int ret = 0;
304     size_t label_hpkev1len = 0;
305     size_t protocol_labellen = 0;
306     size_t labellen = 0;
307     size_t labeled_ikmlen = 0;
308     unsigned char *labeled_ikm = NULL;
309     WPACKET pkt;
310 
311     label_hpkev1len = strlen(LABEL_HPKEV1);
312     protocol_labellen = strlen(protocol_label);
313     labellen = strlen(label);
314     labeled_ikmlen = label_hpkev1len + protocol_labellen
315         + suiteidlen + labellen + ikmlen;
316     labeled_ikm = OPENSSL_malloc(labeled_ikmlen);
317     if (labeled_ikm == NULL)
318         return 0;
319 
320     /* labeled_ikm = concat("HPKE-v1", suiteid, label, ikm) */
321     if (!WPACKET_init_static_len(&pkt, labeled_ikm, labeled_ikmlen, 0)
322             || !WPACKET_memcpy(&pkt, LABEL_HPKEV1, label_hpkev1len)
323             || !WPACKET_memcpy(&pkt, protocol_label, protocol_labellen)
324             || !WPACKET_memcpy(&pkt, suiteid, suiteidlen)
325             || !WPACKET_memcpy(&pkt, label, labellen)
326             || !WPACKET_memcpy(&pkt, ikm, ikmlen)
327             || !WPACKET_get_total_written(&pkt, &labeled_ikmlen)
328             || !WPACKET_finish(&pkt)) {
329         ERR_raise(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL);
330         goto end;
331     }
332 
333     ret = ossl_hpke_kdf_extract(kctx, prk, prklen, salt, saltlen,
334                                 labeled_ikm, labeled_ikmlen);
335 end:
336     WPACKET_cleanup(&pkt);
337     OPENSSL_cleanse(labeled_ikm, labeled_ikmlen);
338     OPENSSL_free(labeled_ikm);
339     return ret;
340 }
341 
342 /*
343  * See RFC 9180 Section 4 LabelExpand()
344  */
ossl_hpke_labeled_expand(EVP_KDF_CTX * kctx,unsigned char * okm,size_t okmlen,const unsigned char * prk,size_t prklen,const char * protocol_label,const unsigned char * suiteid,size_t suiteidlen,const char * label,const unsigned char * info,size_t infolen)345 int ossl_hpke_labeled_expand(EVP_KDF_CTX *kctx,
346                              unsigned char *okm, size_t okmlen,
347                              const unsigned char *prk, size_t prklen,
348                              const char *protocol_label,
349                              const unsigned char *suiteid, size_t suiteidlen,
350                              const char *label,
351                              const unsigned char *info, size_t infolen)
352 {
353     int ret = 0;
354     size_t label_hpkev1len = 0;
355     size_t protocol_labellen = 0;
356     size_t labellen = 0;
357     size_t labeled_infolen = 0;
358     unsigned char *labeled_info = NULL;
359     WPACKET pkt;
360 
361     label_hpkev1len = strlen(LABEL_HPKEV1);
362     protocol_labellen = strlen(protocol_label);
363     labellen = strlen(label);
364     labeled_infolen = 2 + okmlen + prklen + label_hpkev1len
365         + protocol_labellen + suiteidlen + labellen + infolen;
366     labeled_info = OPENSSL_malloc(labeled_infolen);
367     if (labeled_info == NULL)
368         return 0;
369 
370     /* labeled_info = concat(okmlen, "HPKE-v1", suiteid, label, info) */
371     if (!WPACKET_init_static_len(&pkt, labeled_info, labeled_infolen, 0)
372             || !WPACKET_put_bytes_u16(&pkt, okmlen)
373             || !WPACKET_memcpy(&pkt, LABEL_HPKEV1, label_hpkev1len)
374             || !WPACKET_memcpy(&pkt, protocol_label, protocol_labellen)
375             || !WPACKET_memcpy(&pkt, suiteid, suiteidlen)
376             || !WPACKET_memcpy(&pkt, label, labellen)
377             || !WPACKET_memcpy(&pkt, info, infolen)
378             || !WPACKET_get_total_written(&pkt, &labeled_infolen)
379             || !WPACKET_finish(&pkt)) {
380         ERR_raise(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL);
381         goto end;
382     }
383 
384     ret = ossl_hpke_kdf_expand(kctx, okm, okmlen,
385                                prk, prklen, labeled_info, labeled_infolen);
386 end:
387     WPACKET_cleanup(&pkt);
388     OPENSSL_free(labeled_info);
389     return ret;
390 }
391 
392 /* Common code to create a HKDF ctx */
ossl_kdf_ctx_create(const char * kdfname,const char * mdname,OSSL_LIB_CTX * libctx,const char * propq)393 EVP_KDF_CTX *ossl_kdf_ctx_create(const char *kdfname, const char *mdname,
394                                  OSSL_LIB_CTX *libctx, const char *propq)
395 {
396     EVP_KDF *kdf;
397     EVP_KDF_CTX *kctx = NULL;
398 
399     kdf = EVP_KDF_fetch(libctx, kdfname, propq);
400     if (kdf == NULL) {
401         ERR_raise(ERR_LIB_CRYPTO, ERR_R_FETCH_FAILED);
402         return NULL;
403     }
404     kctx = EVP_KDF_CTX_new(kdf);
405     EVP_KDF_free(kdf);
406     if (kctx != NULL && mdname != NULL) {
407         OSSL_PARAM params[3], *p = params;
408 
409         if (mdname != NULL)
410             *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
411                                                     (char *)mdname, 0);
412         if (propq != NULL)
413             *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_PROPERTIES,
414                                                     (char *)propq, 0);
415         *p = OSSL_PARAM_construct_end();
416         if (EVP_KDF_CTX_set_params(kctx, params) <= 0) {
417             EVP_KDF_CTX_free(kctx);
418             return NULL;
419         }
420     }
421     return kctx;
422 }
423 
424 /*
425  * @brief look for a label into the synonym tables, and return its id
426  * @param st is the string value
427  * @param synp is the synonyms labels array
428  * @param arrsize is the previous array size
429  * @return 0 when not found, else the matching item id.
430  */
synonyms_name2id(const char * st,const synonymttab_t * synp,size_t arrsize)431 static uint16_t synonyms_name2id(const char *st, const synonymttab_t *synp,
432                                  size_t arrsize)
433 {
434     size_t i, j;
435 
436     for (i = 0; i < arrsize; ++i) {
437         for (j = 0; j < OSSL_NELEM(synp[i].synonyms); ++j) {
438             if (OPENSSL_strcasecmp(st, synp[i].synonyms[j]) == 0)
439                 return synp[i].id;
440         }
441     }
442     return 0;
443 }
444 
445 /*
446  * @brief map a string to a HPKE suite based on synonym tables
447  * @param str is the string value
448  * @param suite is the resulting suite
449  * @return 1 for success, otherwise failure
450  */
ossl_hpke_str2suite(const char * suitestr,OSSL_HPKE_SUITE * suite)451 int ossl_hpke_str2suite(const char *suitestr, OSSL_HPKE_SUITE *suite)
452 {
453     uint16_t kem = 0, kdf = 0, aead = 0;
454     char *st = NULL, *instrcp = NULL;
455     size_t inplen;
456     int labels = 0, result = 0;
457     int delim_count = 0;
458 
459     if (suitestr == NULL || suitestr[0] == 0x00 || suite == NULL) {
460         ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_NULL_PARAMETER);
461         return 0;
462     }
463     inplen = OPENSSL_strnlen(suitestr, OSSL_HPKE_MAX_SUITESTR);
464     if (inplen >= OSSL_HPKE_MAX_SUITESTR) {
465         ERR_raise(ERR_LIB_CRYPTO, ERR_R_PASSED_INVALID_ARGUMENT);
466         return 0;
467     }
468 
469     /*
470      * we don't want a delimiter at the end of the string;
471      * strtok_r/s() doesn't care about that, so we should
472      */
473     if (suitestr[inplen - 1] == OSSL_HPKE_STR_DELIMCHAR)
474         return 0;
475     /* We want exactly two delimiters in the input string */
476     for (st = (char *)suitestr; *st != '\0'; st++) {
477         if (*st == OSSL_HPKE_STR_DELIMCHAR)
478             delim_count++;
479     }
480     if (delim_count != 2)
481         return 0;
482 
483     /* Duplicate `suitestr` to allow its parsing  */
484     instrcp = OPENSSL_memdup(suitestr, inplen + 1);
485     if (instrcp == NULL)
486         goto fail;
487 
488     /* See if it contains a mix of our strings and numbers */
489     st = instrcp;
490 
491     while (st != NULL && labels < 3) {
492         char *cp = strchr(st, OSSL_HPKE_STR_DELIMCHAR);
493 
494         /* add a NUL like strtok would if we're not at the end */
495         if (cp != NULL)
496             *cp = '\0';
497 
498         /* check if string is known or number and if so handle appropriately */
499         if (labels == 0
500             && (kem = synonyms_name2id(st, kemstrtab,
501                                        OSSL_NELEM(kemstrtab))) == 0)
502             goto fail;
503         else if (labels == 1
504                  && (kdf = synonyms_name2id(st, kdfstrtab,
505                                             OSSL_NELEM(kdfstrtab))) == 0)
506             goto fail;
507         else if (labels == 2
508                  && (aead = synonyms_name2id(st, aeadstrtab,
509                                              OSSL_NELEM(aeadstrtab))) == 0)
510             goto fail;
511 
512         if (cp == NULL)
513             st = NULL;
514         else
515             st = cp + 1;
516         ++labels;
517     }
518     if (st != NULL || labels != 3)
519         goto fail;
520     suite->kem_id = kem;
521     suite->kdf_id = kdf;
522     suite->aead_id = aead;
523     result = 1;
524 
525 fail:
526     OPENSSL_free(instrcp);
527     return result;
528 }
529