From 4b014561e0a2c1e46f4ac01f387e410556eb9a91 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Thu, 25 Jul 2024 20:39:52 +0200 Subject: [PATCH] scripts to do interoperability testing with OpenSSL --- interop/README.md | 38 ++++++++++ interop/ml_kem_decap.py | 60 +++++++++++++++ interop/ml_kem_encap.py | 48 ++++++++++++ interop/ml_kem_keygen.py | 39 ++++++++++ interop/openssl-decap.c | 156 +++++++++++++++++++++++++++++++++++++++ interop/openssl-encap.c | 153 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 494 insertions(+) create mode 100644 interop/README.md create mode 100644 interop/ml_kem_decap.py create mode 100644 interop/ml_kem_encap.py create mode 100644 interop/ml_kem_keygen.py create mode 100644 interop/openssl-decap.c create mode 100644 interop/openssl-encap.c diff --git a/interop/README.md b/interop/README.md new file mode 100644 index 0000000..eae744d --- /dev/null +++ b/interop/README.md @@ -0,0 +1,38 @@ +Tools that allow use of the OpenSSL Encapsulation and Decapsulation API. + +To enable support for PQC algorithms in OpenSSL install oqsprovider and +modify the `openssl.cnf` file to enable the oqsprovider: +``` +[provider_sect] +default = default_sect +oqsprovider = oqsprovider_sect +[oqsprovider_sect] +activate = 1 +``` + +To generate the private (decapsulation) key using OpenSSL: +``` +openssl genpkey -out private-key.pem -algorithm mlkem512 +``` +(See other algorithm names in https://github.com/open-quantum-safe/oqs-provider) + +To extract the public (encapsulation) key using OpenSSL: +``` +openssl pkey -pubout -in public-key.pem -out pub.pem +``` + +Compile the encapsulation and decapsulation helper apps: +``` +gcc -o openssl-decap -lcrypto openssl-decap.c +gcc -o openssl-encap -lcrypto openssl-encap.c +``` + +To encapsulate a shared secret: +``` +./openssl-encap -k public-key.pem -s secret.bin -c ciphertext.bin +``` + +To decapsulate a shared secret: +``` +./openssl-decap -k private-key.pem -s secret-dec.bin -c ciphertext.bin +``` diff --git a/interop/ml_kem_decap.py b/interop/ml_kem_decap.py new file mode 100644 index 0000000..d268feb --- /dev/null +++ b/interop/ml_kem_decap.py @@ -0,0 +1,60 @@ +import sys + +if len(sys.argv) != 4: + raise ValueError(f"Usage: {sys.argv[0]} dk.pem secret.bin ciphertext.bin") + +from kyber_py.ml_kem import ML_KEM_512, ML_KEM_768, ML_KEM_1024 + +OIDS = { + (1, 3, 6, 1, 4, 1, 22554, 5, 6, 1): ML_KEM_512, + (1, 3, 6, 1, 4, 1, 22554, 5, 6, 2): ML_KEM_768, + (1, 3, 6, 1, 4, 1, 22554, 5, 6, 3): ML_KEM_1024, +} + +import ecdsa.der as der + +with open(sys.argv[1], "rt") as ek_file: + ek_pem = ek_file.read() + +ek_der = der.unpem(ek_pem) + +s1, empty = der.remove_sequence(ek_der) +if empty != b"": + raise der.UnexpectedDER("Trailing junk after DER public key") + +ver, rest = der.remove_integer(s1) + +if ver != 0: + raise der.UnexpectedDER("Unexpected format version") + +alg_id, rest = der.remove_sequence(rest) + +alg_id, empty = der.remove_object(alg_id) +if alg_id not in OIDS: + raise der.UnexpectedDER(f"Not recognised algoritm OID: {alg_id}") +if empty != b"": + raise der.UnexpectedDER("parameters specified for ML-KEM OID") + +kem = OIDS[alg_id] + +key_der, empty = der.remove_octet_string(rest) +if empty != b"": + raise der.UnexpectedDER("Trailing junk after the key") + +keys, empty = der.remove_octet_string(key_der) +if empty != b"": + raise der.UnexpectedDER("Trailing junk after the key") + +dk_len = 768 * kem.k + 96 +dk, ek = keys[:dk_len], keys[dk_len:] +assert len(ek) == 384 * kem.k + 32 + +with open(sys.argv[3], "rb") as encaps_file: + encaps = encaps_file.read() + +secret = kem.decaps(encaps, dk) + +with open(sys.argv[2], "wb") as secret_file: + secret_file.write(secret) + +print("done") diff --git a/interop/ml_kem_encap.py b/interop/ml_kem_encap.py new file mode 100644 index 0000000..221d9da --- /dev/null +++ b/interop/ml_kem_encap.py @@ -0,0 +1,48 @@ +import sys + +if len(sys.argv) != 4: + raise ValueError(f"Usage: {sys.argv[0]} ek.pem secret.bin ciphertext.bin") + +from kyber_py.ml_kem import ML_KEM_512, ML_KEM_768, ML_KEM_1024 + +OIDS = { + (1, 3, 6, 1, 4, 1, 22554, 5, 6, 1): ML_KEM_512, + (1, 3, 6, 1, 4, 1, 22554, 5, 6, 2): ML_KEM_768, + (1, 3, 6, 1, 4, 1, 22554, 5, 6, 3): ML_KEM_1024, +} + +import ecdsa.der as der + +with open(sys.argv[1], "rt") as ek_file: + ek_pem = ek_file.read() + +ek_der = der.unpem(ek_pem) + +s1, empty = der.remove_sequence(ek_der) +if empty != b"": + raise der.UnexpectedDER("Trailing junk after DER public key") + +alg_id, rem = der.remove_sequence(s1) + +alg_id, rest = der.remove_object(alg_id) +if alg_id not in OIDS: + raise der.UnexpectedDER(f"Not recognised algoritm OID: {alg_id}") + +if rest != b"": + raise der.UnexpectedDER("parameters specified for ML-KEM OID") + +kem = OIDS[alg_id] + +key, empty = der.remove_bitstring(rem, 0) +if empty != b"": + raise der.UnexpectedDER("Trailing junk after the public key bitstring") + +secret, encaps = kem.encaps(key) + +with open(sys.argv[2], "wb") as secret_file: + secret_file.write(secret) + +with open(sys.argv[3], "wb") as encaps_file: + encaps_file.write(encaps) + +print("done") diff --git a/interop/ml_kem_keygen.py b/interop/ml_kem_keygen.py new file mode 100644 index 0000000..78dd66a --- /dev/null +++ b/interop/ml_kem_keygen.py @@ -0,0 +1,39 @@ +import sys + +if len(sys.argv) != 4: + raise ValueError(f"Usage: {sys.argv[0]} ML-KEM-(512|768|1024) ek.pem dk.pem") + +if sys.argv[1] == "ML-KEM-512": + from kyber_py.ml_kem.default_parameters import ML_KEM_512 as ML_KEM + oid = (1, 3, 6, 1, 4, 1, 22554, 5, 6, 1) +elif sys.argv[1] == "ML-KEM-768": + from kyber_py.ml_kem.default_parameters import ML_KEM_768 as ML_KEM + oid = (1, 3, 6, 1, 4, 1, 22554, 5, 6, 2) +elif sys.argv[1] == "ML-KEM-1024": + from kyber_py.ml_kem.default_parameters import ML_KEM_1024 as ML_KEM + oid = (1, 3, 6, 1, 4, 1, 22554, 5, 6, 3) +else: + raise ValueError(f"Unrecognised algorithm: {sys.argv[1]}") + +import ecdsa.der as der + +ek, dk = ML_KEM.keygen() + +with open(sys.argv[2], "wb") as ek_file: + encoded = der.encode_sequence( + der.encode_sequence( + der.encode_oid(*oid) + ), + der.encode_bitstring(ek, 0), + ) + ek_file.write(der.topem(encoded, "PUBLIC KEY")) + +with open(sys.argv[3], "wb") as dk_file: + encoded = der.encode_sequence( + der.encode_integer(0), + der.encode_sequence( + der.encode_oid(*oid) + ), + der.encode_octet_string(der.encode_octet_string(dk + ek)), + ) + dk_file.write(der.topem(encoded, "PRIVATE KEY")) diff --git a/interop/openssl-decap.c b/interop/openssl-decap.c new file mode 100644 index 0000000..c33cf5e --- /dev/null +++ b/interop/openssl-decap.c @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void help(char *name) { + printf("Usage: %s -k key.pem -s secret-out.pem -c ciphertext-out.pem\n", + name); + printf("\n"); + printf(" -k file File with the decapsulation key\n"); + printf(" -s file File to write the secret\n"); + printf(" -c file File to read the ciphertext\n"); +} + +int +main(int argc, char** argv) { + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pub_key = NULL; + size_t secretlen = 0, outlen = 0; + unsigned char *out = NULL, *secret = NULL; + char *key_file_name = NULL, *secret_file_name = NULL; + char *ciphertext_file_name = NULL; + int sec_fd = -1, cip_fd = -1; + FILE *fp; + int opt; + int result = 0; + + while ((opt = getopt(argc, argv, "k:s:c:")) != -1 ) { + switch (opt) { + case 'k': + key_file_name = optarg; + break; + case 's': + secret_file_name = optarg; + break; + case 'c': + ciphertext_file_name = optarg; + break; + default: + fprintf(stderr, "Unknown option: %c\n", opt); + help(argv[0]); + exit(1); + break; + } + } + + if (key_file_name == NULL || secret_file_name == NULL || + ciphertext_file_name == NULL) { + fprintf(stderr, "All options must be specified!\n"); + help(argv[0]); + exit(1); + } + + if ((sec_fd = open(secret_file_name, O_WRONLY|O_TRUNC|O_CREAT, 0666)) + == -1){ + fprintf(stderr, "can't open output file: %s\n", secret_file_name); + goto err; + } + + if ((cip_fd = open(ciphertext_file_name, O_RDONLY)) == -1) { + fprintf(stderr, "Can't open output file: %s\n", ciphertext_file_name); + goto err; + } + + fp = fopen(key_file_name, "r"); + if (!fp) { + fprintf(stderr, "Can't open key file: %s\n", key_file_name); + goto err; + } + + if ((pub_key = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL) { + //if ((pub_key = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + fprintf(stderr, "Can't read parse private key\n"); + goto err; + } + + if (fclose(fp) != 0) { + fprintf(stderr, "can't close key file\n"); + goto err; + } + fp = NULL; + + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pub_key, NULL); + if (ctx == NULL) { + fprintf(stderr, "Can't init key context\n"); + goto err; + } + if (EVP_PKEY_decapsulate_init(ctx, NULL) <= 0) { + fprintf(stderr, "Can't init encapsulation\n"); + goto err; + } + + secretlen = 4096; + secret = OPENSSL_malloc(secretlen); + secretlen = read(cip_fd, secret, secretlen); + if (secretlen <= 0) { + fprintf(stderr, "Can't read ciphertext\n"); + goto err; + } + + /* Determine buffer length */ + if (EVP_PKEY_decapsulate(ctx, NULL, &outlen, secret, secretlen) <= 0) { + fprintf(stderr, "Can't fetch memory size\n"); + } + + out = OPENSSL_malloc(outlen); + if (out == NULL || secret == NULL) { + fprintf(stderr, "memory allocation failure\n"); + goto err; + } + + /* + * The decapsulated 'out' can be used as key material. + */ + if (EVP_PKEY_decapsulate(ctx, out, &outlen, secret, secretlen) <= 0) { + fprintf(stderr, "decapsulation failure\n"); + goto err; + } + + if (write(sec_fd, out, outlen) <= 0) { + fprintf(stderr, "Error writing secret\n"); + goto err; + } + + printf("done\n"); + + goto out; + +err: + result = 1; + fprintf(stderr, "operation failed\n"); + ERR_print_errors_fp(stderr); + +out: + if (sec_fd >= 0) + close(sec_fd); + if (cip_fd >= 0) + close(cip_fd); + if (fp) + fclose(fp); + if (out) + OPENSSL_free(out); + if (secret) + OPENSSL_free(secret); + if (ctx) + EVP_PKEY_CTX_free(ctx); + if (pub_key) + EVP_PKEY_free(pub_key); + + return result; +} diff --git a/interop/openssl-encap.c b/interop/openssl-encap.c new file mode 100644 index 0000000..9cff1fc --- /dev/null +++ b/interop/openssl-encap.c @@ -0,0 +1,153 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void help(char *name) { + printf("Usage: %s -k key.pem -s secret-out.pem -c ciphertext-out.pem\n", + name); + printf("\n"); + printf(" -k file File with the encapsulation key\n"); + printf(" -s file File to write the secret\n"); + printf(" -c file File to write the ciphertext\n"); +} + +int +main(int argc, char** argv) { + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pub_key = NULL; + size_t secretlen = 0, outlen = 0; + unsigned char *out = NULL, *secret = NULL; + char *key_file_name = NULL, *secret_file_name = NULL; + char *ciphertext_file_name = NULL; + int sec_fd = -1, cip_fd = -1; + FILE *fp; + int opt; + int result = 0; + + while ((opt = getopt(argc, argv, "k:s:c:")) != -1 ) { + switch (opt) { + case 'k': + key_file_name = optarg; + break; + case 's': + secret_file_name = optarg; + break; + case 'c': + ciphertext_file_name = optarg; + break; + default: + fprintf(stderr, "Unknown option: %c\n", opt); + help(argv[0]); + exit(1); + break; + } + } + + if (key_file_name == NULL || secret_file_name == NULL || + ciphertext_file_name == NULL) { + fprintf(stderr, "All options must be specified!\n"); + help(argv[0]); + exit(1); + } + + if ((sec_fd = open(secret_file_name, O_WRONLY|O_TRUNC|O_CREAT, 0666)) + == -1){ + fprintf(stderr, "can't open output file: %s\n", secret_file_name); + goto err; + } + + if ((cip_fd = open(ciphertext_file_name, + O_WRONLY|O_TRUNC|O_CREAT, 0666)) == -1) { + fprintf(stderr, "Can't open output file: %s\n", ciphertext_file_name); + goto err; + } + + fp = fopen(key_file_name, "r"); + if (!fp) { + fprintf(stderr, "Can't open key file: %s\n", key_file_name); + goto err; + } + + //if ((pub_key = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL) { + if ((pub_key = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + fprintf(stderr, "Can't read parse private key\n"); + goto err; + } + + if (fclose(fp) != 0) { + fprintf(stderr, "can't close key file\n"); + goto err; + } + fp = NULL; + + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pub_key, NULL); + if (ctx == NULL) { + fprintf(stderr, "Can't init key context\n"); + goto err; + } + if (EVP_PKEY_encapsulate_init(ctx, NULL) <= 0) { + fprintf(stderr, "Can't init encapsulation\n"); + goto err; + } + + /* Determine buffer length */ + if (EVP_PKEY_encapsulate(ctx, NULL, &outlen, NULL, &secretlen) <= 0) { + fprintf(stderr, "Can't fetch memory size\n"); + } + + out = OPENSSL_malloc(outlen); + secret = OPENSSL_malloc(secretlen); + if (out == NULL || secret == NULL) { + fprintf(stderr, "memory allocation failure\n"); + goto err; + } + + /* + * The generated 'secret' can be used as key material. + * The encapsulated 'out' can be sent to another party who can + * decapsulate it using their private key to retrieve the 'secret'. + */ + if (EVP_PKEY_encapsulate(ctx, out, &outlen, secret, &secretlen) <= 0) { + fprintf(stderr, "Encapsulation failure\n"); + goto err; + } + + if (write(sec_fd, secret, secretlen) <= 0) { + fprintf(stderr, "Error writing secret\n"); + goto err; + } + + if (write(cip_fd, out, outlen) <= 0) { + fprintf(stderr, "Error writing ciphertext\n"); + goto err; + } + + printf("done\n"); + + goto out; + +err: + result = 1; + fprintf(stderr, "operation failed\n"); + ERR_print_errors_fp(stderr); + +out: + if (sec_fd >= 0) + close(sec_fd); + if (cip_fd >= 0) + close(cip_fd); + if (fp) + fclose(fp); + if (out) + OPENSSL_free(out); + if (secret) + OPENSSL_free(secret); + + return result; +}