Skip to content

Commit bb6fcd5

Browse files
committed
scripts to do interoperability testing with OpenSSL
1 parent 038a5d9 commit bb6fcd5

File tree

6 files changed

+545
-0
lines changed

6 files changed

+545
-0
lines changed

interop/README.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
Tools that allow use of the OpenSSL Encapsulation and Decapsulation API.
2+
3+
**Note:** this code expects draft-ietf-lamps-kyber-certificates-04 compatible behaviour.
4+
This is consistent with oqsprovider-0.8.0.
5+
6+
7+
OpenSSL setup
8+
-------------
9+
10+
To enable support for PQC algorithms in OpenSSL: install oqsprovider and
11+
modify the `openssl.cnf` file to enable the oqsprovider:
12+
```
13+
[provider_sect]
14+
default = default_sect
15+
oqsprovider = oqsprovider_sect
16+
[oqsprovider_sect]
17+
activate = 1
18+
```
19+
If you've done that properly, the `openssl list -kem-algorithms` will list
20+
ML-KEM as valid options.
21+
22+
OpenSSL keygen
23+
--------------
24+
25+
To generate the private (decapsulation) key using OpenSSL:
26+
```
27+
openssl genpkey -out private-key.pem -algorithm mlkem512
28+
```
29+
(See other algorithm names in https://github.com/open-quantum-safe/oqs-provider)
30+
31+
To extract the public (encapsulation) key using OpenSSL:
32+
```
33+
openssl pkey -pubout -in public-key.pem -out pub.pem
34+
```
35+
36+
Compile OpenSSL helper apps:
37+
----------------------------
38+
39+
Compile the encapsulation and decapsulation helper apps:
40+
```
41+
gcc -o openssl-decap -lcrypto openssl-decap.c
42+
gcc -o openssl-encap -lcrypto openssl-encap.c
43+
```
44+
45+
OpenSSL encapsulation
46+
---------------------
47+
To encapsulate a shared secret:
48+
```
49+
./openssl-encap -k public-key.pem -s secret.bin -c ciphertext.bin
50+
```
51+
52+
OpenSSL decapsulation
53+
---------------------
54+
To decapsulate a shared secret:
55+
```
56+
./openssl-decap -k private-key.pem -s secret-dec.bin -c ciphertext.bin
57+
```
58+
59+
kyber-py setup
60+
--------------
61+
As the key formats use ASN.1 and PEM encoding, they require presence
62+
of the `ecdsa` library. Install it using your distribution package manager
63+
or using `pip`:
64+
```
65+
pip install ecdsa
66+
```
67+
68+
Kyber-py key gen
69+
----------------
70+
To generate both private (decapsulation) and public (encapsulation) keys
71+
with kyber-py, run:
72+
```
73+
PYTHONPATH=../src python ml_kem_keygen.py ML-KEM-512 public-key.pem private-key.pem
74+
```
75+
76+
Kyber-py encapsulation
77+
-----------------------
78+
To encapsulate a shared secret:
79+
```
80+
PYTHONPATH=../src python ml_kem_encap.py public-key.pem secret.bin ciphertext.bin
81+
```
82+
83+
Kyber-py decapsulation
84+
----------------------
85+
To decapsulate a shared secret:
86+
```
87+
PYTHONPATH=../src python ml_kem_decap.py private-key.pem secret-dec.bin ciphertext.bin
88+
```

interop/ml_kem_decap.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import sys
2+
3+
if len(sys.argv) != 4:
4+
raise ValueError(f"Usage: {sys.argv[0]} dk.pem secret.bin ciphertext.bin")
5+
6+
from kyber_py.ml_kem import ML_KEM_512, ML_KEM_768, ML_KEM_1024
7+
8+
OIDS = {
9+
(2, 16, 840, 1, 101, 3, 4, 4, 1): ML_KEM_512,
10+
(2, 16, 840, 1, 101, 3, 4, 4, 2): ML_KEM_768,
11+
(2, 16, 840, 1, 101, 3, 4, 4, 3): ML_KEM_1024,
12+
}
13+
14+
import ecdsa.der as der
15+
16+
with open(sys.argv[1], "rt") as ek_file:
17+
ek_pem = ek_file.read()
18+
19+
ek_der = der.unpem(ek_pem)
20+
21+
s1, empty = der.remove_sequence(ek_der)
22+
if empty != b"":
23+
raise der.UnexpectedDER("Trailing junk after DER public key")
24+
25+
ver, rest = der.remove_integer(s1)
26+
27+
if ver != 0:
28+
raise der.UnexpectedDER("Unexpected format version")
29+
30+
alg_id, rest = der.remove_sequence(rest)
31+
32+
alg_id, empty = der.remove_object(alg_id)
33+
if alg_id not in OIDS:
34+
raise der.UnexpectedDER(f"Not recognised algoritm OID: {alg_id}")
35+
if empty != b"":
36+
raise der.UnexpectedDER("parameters specified for ML-KEM OID")
37+
38+
kem = OIDS[alg_id]
39+
40+
key_der, empty = der.remove_octet_string(rest)
41+
if empty != b"":
42+
raise der.UnexpectedDER("Trailing junk after the key")
43+
44+
keys, empty = der.remove_octet_string(key_der)
45+
if empty != b"":
46+
raise der.UnexpectedDER("Trailing junk after the key")
47+
48+
dk_len = 768 * kem.k + 96
49+
dk, ek = keys[:dk_len], keys[dk_len:]
50+
assert len(ek) == 384 * kem.k + 32
51+
52+
with open(sys.argv[3], "rb") as encaps_file:
53+
encaps = encaps_file.read()
54+
55+
secret = kem.decaps(dk, encaps)
56+
57+
with open(sys.argv[2], "wb") as secret_file:
58+
secret_file.write(secret)
59+
60+
print("done")

interop/ml_kem_encap.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import sys
2+
3+
if len(sys.argv) != 4:
4+
raise ValueError(f"Usage: {sys.argv[0]} ek.pem secret.bin ciphertext.bin")
5+
6+
from kyber_py.ml_kem import ML_KEM_512, ML_KEM_768, ML_KEM_1024
7+
8+
OIDS = {
9+
(2, 16, 840, 1, 101, 3, 4, 4, 1): ML_KEM_512,
10+
(2, 16, 840, 1, 101, 3, 4, 4, 2): ML_KEM_768,
11+
(2, 16, 840, 1, 101, 3, 4, 4, 3): ML_KEM_1024,
12+
}
13+
14+
import ecdsa.der as der
15+
16+
with open(sys.argv[1], "rt") as ek_file:
17+
ek_pem = ek_file.read()
18+
19+
ek_der = der.unpem(ek_pem)
20+
21+
s1, empty = der.remove_sequence(ek_der)
22+
if empty != b"":
23+
raise der.UnexpectedDER("Trailing junk after DER public key")
24+
25+
alg_id, rem = der.remove_sequence(s1)
26+
27+
alg_id, rest = der.remove_object(alg_id)
28+
if alg_id not in OIDS:
29+
raise der.UnexpectedDER(f"Not recognised algoritm OID: {alg_id}")
30+
31+
if rest != b"":
32+
raise der.UnexpectedDER("parameters specified for ML-KEM OID")
33+
34+
kem = OIDS[alg_id]
35+
36+
key, empty = der.remove_bitstring(rem, 0)
37+
if empty != b"":
38+
raise der.UnexpectedDER("Trailing junk after the public key bitstring")
39+
40+
secret, encaps = kem.encaps(key)
41+
42+
with open(sys.argv[2], "wb") as secret_file:
43+
secret_file.write(secret)
44+
45+
with open(sys.argv[3], "wb") as encaps_file:
46+
encaps_file.write(encaps)
47+
48+
print("done")

interop/ml_kem_keygen.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import sys
2+
3+
if len(sys.argv) != 4:
4+
raise ValueError(
5+
f"Usage: {sys.argv[0]} ML-KEM-(512|768|1024) ek.pem dk.pem"
6+
)
7+
8+
if sys.argv[1] == "ML-KEM-512":
9+
from kyber_py.ml_kem.default_parameters import ML_KEM_512 as ML_KEM
10+
11+
oid = (2, 16, 840, 1, 101, 3, 4, 4, 1)
12+
elif sys.argv[1] == "ML-KEM-768":
13+
from kyber_py.ml_kem.default_parameters import ML_KEM_768 as ML_KEM
14+
15+
oid = (2, 16, 840, 1, 101, 3, 4, 4, 2)
16+
elif sys.argv[1] == "ML-KEM-1024":
17+
from kyber_py.ml_kem.default_parameters import ML_KEM_1024 as ML_KEM
18+
19+
oid = (2, 16, 840, 1, 101, 3, 4, 4, 3)
20+
else:
21+
raise ValueError(f"Unrecognised algorithm: {sys.argv[1]}")
22+
23+
import ecdsa.der as der
24+
25+
ek, dk = ML_KEM.keygen()
26+
27+
with open(sys.argv[2], "wb") as ek_file:
28+
encoded = der.encode_sequence(
29+
der.encode_sequence(der.encode_oid(*oid)),
30+
der.encode_bitstring(ek, 0),
31+
)
32+
ek_file.write(der.topem(encoded, "PUBLIC KEY"))
33+
34+
with open(sys.argv[3], "wb") as dk_file:
35+
encoded = der.encode_sequence(
36+
der.encode_integer(0),
37+
der.encode_sequence(der.encode_oid(*oid)),
38+
der.encode_octet_string(der.encode_octet_string(dk + ek)),
39+
)
40+
dk_file.write(der.topem(encoded, "PRIVATE KEY"))

interop/openssl-decap.c

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#include <stdio.h>
2+
#include <unistd.h>
3+
#include <stdlib.h>
4+
#include <memory.h>
5+
#include <string.h>
6+
#include <fcntl.h>
7+
#include <openssl/err.h>
8+
#include <openssl/evp.h>
9+
#include <openssl/pem.h>
10+
11+
void help(char *name) {
12+
printf("Usage: %s -k key.pem -s secret-out.pem -c ciphertext-out.pem\n",
13+
name);
14+
printf("\n");
15+
printf(" -k file File with the decapsulation key\n");
16+
printf(" -s file File to write the secret\n");
17+
printf(" -c file File to read the ciphertext\n");
18+
}
19+
20+
int
21+
main(int argc, char** argv) {
22+
EVP_PKEY_CTX *ctx = NULL;
23+
EVP_PKEY *pub_key = NULL;
24+
size_t secretlen = 0, outlen = 0;
25+
unsigned char *out = NULL, *secret = NULL;
26+
char *key_file_name = NULL, *secret_file_name = NULL;
27+
char *ciphertext_file_name = NULL;
28+
int sec_fd = -1, cip_fd = -1;
29+
FILE *fp;
30+
int opt;
31+
int result = 0;
32+
33+
while ((opt = getopt(argc, argv, "k:s:c:")) != -1 ) {
34+
switch (opt) {
35+
case 'k':
36+
key_file_name = optarg;
37+
break;
38+
case 's':
39+
secret_file_name = optarg;
40+
break;
41+
case 'c':
42+
ciphertext_file_name = optarg;
43+
break;
44+
default:
45+
fprintf(stderr, "Unknown option: %c\n", opt);
46+
help(argv[0]);
47+
exit(1);
48+
break;
49+
}
50+
}
51+
52+
if (key_file_name == NULL || secret_file_name == NULL ||
53+
ciphertext_file_name == NULL) {
54+
fprintf(stderr, "All options must be specified!\n");
55+
help(argv[0]);
56+
exit(1);
57+
}
58+
59+
if ((sec_fd = open(secret_file_name, O_WRONLY|O_TRUNC|O_CREAT, 0666))
60+
== -1){
61+
fprintf(stderr, "can't open output file: %s\n", secret_file_name);
62+
goto err;
63+
}
64+
65+
if ((cip_fd = open(ciphertext_file_name, O_RDONLY)) == -1) {
66+
fprintf(stderr, "Can't open output file: %s\n", ciphertext_file_name);
67+
goto err;
68+
}
69+
70+
fp = fopen(key_file_name, "r");
71+
if (!fp) {
72+
fprintf(stderr, "Can't open key file: %s\n", key_file_name);
73+
goto err;
74+
}
75+
76+
if ((pub_key = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL) {
77+
//if ((pub_key = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) {
78+
fprintf(stderr, "Can't read parse private key\n");
79+
goto err;
80+
}
81+
82+
if (fclose(fp) != 0) {
83+
fprintf(stderr, "can't close key file\n");
84+
goto err;
85+
}
86+
fp = NULL;
87+
88+
ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pub_key, NULL);
89+
if (ctx == NULL) {
90+
fprintf(stderr, "Can't init key context\n");
91+
goto err;
92+
}
93+
if (EVP_PKEY_decapsulate_init(ctx, NULL) <= 0) {
94+
fprintf(stderr, "Can't init encapsulation\n");
95+
goto err;
96+
}
97+
98+
secretlen = 4096;
99+
secret = OPENSSL_malloc(secretlen);
100+
secretlen = read(cip_fd, secret, secretlen);
101+
if (secretlen <= 0) {
102+
fprintf(stderr, "Can't read ciphertext\n");
103+
goto err;
104+
}
105+
106+
/* Determine buffer length */
107+
if (EVP_PKEY_decapsulate(ctx, NULL, &outlen, secret, secretlen) <= 0) {
108+
fprintf(stderr, "Can't fetch memory size\n");
109+
}
110+
111+
out = OPENSSL_malloc(outlen);
112+
if (out == NULL || secret == NULL) {
113+
fprintf(stderr, "memory allocation failure\n");
114+
goto err;
115+
}
116+
117+
/*
118+
* The decapsulated 'out' can be used as key material.
119+
*/
120+
if (EVP_PKEY_decapsulate(ctx, out, &outlen, secret, secretlen) <= 0) {
121+
fprintf(stderr, "decapsulation failure\n");
122+
goto err;
123+
}
124+
125+
if (write(sec_fd, out, outlen) <= 0) {
126+
fprintf(stderr, "Error writing secret\n");
127+
goto err;
128+
}
129+
130+
printf("done\n");
131+
132+
goto out;
133+
134+
err:
135+
result = 1;
136+
fprintf(stderr, "operation failed\n");
137+
ERR_print_errors_fp(stderr);
138+
139+
out:
140+
if (sec_fd >= 0)
141+
close(sec_fd);
142+
if (cip_fd >= 0)
143+
close(cip_fd);
144+
if (fp)
145+
fclose(fp);
146+
if (out)
147+
OPENSSL_free(out);
148+
if (secret)
149+
OPENSSL_free(secret);
150+
if (ctx)
151+
EVP_PKEY_CTX_free(ctx);
152+
if (pub_key)
153+
EVP_PKEY_free(pub_key);
154+
155+
return result;
156+
}

0 commit comments

Comments
 (0)