From 63e9a3b1f34c42e759332a07468d21b52e89826d Mon Sep 17 00:00:00 2001 From: Viktor Dukhovni Date: Mon, 30 Dec 2024 01:29:18 +1100 Subject: [PATCH] Fix code and docs of pkeyutl en/decapsulation Reviewed-by: Dmitry Belyavskiy Reviewed-by: Tim Hudson (Merged from https://github.com/openssl/openssl/pull/26281) --- apps/pkeyutl.c | 44 ++++++++++++++++------ doc/man1/openssl-pkeyutl.pod.in | 65 +++++++++++++++++++++++++-------- test/recipes/20-test_pkeyutl.t | 19 +++++++--- 3 files changed, 94 insertions(+), 34 deletions(-) diff --git a/apps/pkeyutl.c b/apps/pkeyutl.c index 1d97b3cc5e6..2c2ad09d227 100644 --- a/apps/pkeyutl.c +++ b/apps/pkeyutl.c @@ -245,6 +245,7 @@ int pkeyutl_main(int argc, char **argv) pkey_op = EVP_PKEY_OP_DECAPSULATE; break; case OPT_ENCAP: + key_type = KEY_PUBKEY; pkey_op = EVP_PKEY_OP_ENCAPSULATE; break; case OPT_KEMOP: @@ -450,17 +451,31 @@ int pkeyutl_main(int argc, char **argv) if (in == NULL) goto end; } - out = bio_open_default(outfile, 'w', FORMAT_BINARY); - if (out == NULL) - goto end; + if (pkey_op == EVP_PKEY_OP_DECAPSULATE && outfile != NULL) { + if (secoutfile != NULL) { + BIO_printf(bio_err, "%s: Decapsulation produces only a shared " + "secret and no output. The '-out' option " + "is not applicable.\n", prog); + goto end; + } + if ((out = bio_open_owner(outfile, 'w', FORMAT_BINARY)) == NULL) + goto end; + } else { + out = bio_open_default(outfile, 'w', FORMAT_BINARY); + if (out == NULL) + goto end; + } - if (pkey_op == EVP_PKEY_OP_ENCAPSULATE) { - if (secoutfile == NULL) { - BIO_printf(bio_err, "Encapsulation requires '-secret' argument\n"); + if (pkey_op == EVP_PKEY_OP_ENCAPSULATE + || pkey_op == EVP_PKEY_OP_DECAPSULATE) { + if (secoutfile == NULL && pkey_op == EVP_PKEY_OP_ENCAPSULATE) { + BIO_printf(bio_err, "KEM-based shared-secret derivation requires " + "the '-secret ' option\n"); goto end; } - secout = bio_open_default(secoutfile, 'w', FORMAT_BINARY); - if (secout == NULL) + /* For backwards compatibility, default decap secrets to the output */ + if (secoutfile != NULL + && (secout = bio_open_owner(secoutfile, 'w', FORMAT_BINARY)) == NULL) goto end; } @@ -539,8 +554,12 @@ int pkeyutl_main(int argc, char **argv) rv = do_keyop(ctx, pkey_op, NULL, (size_t *)&buf_outlen, buf_in, (size_t)buf_inlen, NULL, (size_t *)&secretlen); } - if (rv > 0 && buf_outlen != 0) { - buf_out = app_malloc(buf_outlen, "buffer output"); + if (rv > 0 + && (secretlen > 0 || (pkey_op != EVP_PKEY_OP_ENCAPSULATE + && pkey_op != EVP_PKEY_OP_DECAPSULATE)) + && (buf_outlen > 0 || pkey_op == EVP_PKEY_OP_DECAPSULATE)) { + if (buf_outlen > 0) + buf_out = app_malloc(buf_outlen, "buffer output"); if (secretlen > 0) secret = app_malloc(secretlen, "secret output"); rv = do_keyop(ctx, pkey_op, @@ -566,8 +585,9 @@ int pkeyutl_main(int argc, char **argv) } else { BIO_write(out, buf_out, buf_outlen); } + /* Backwards compatible decap output fallback */ if (secretlen > 0) - BIO_write(secout, secret, secretlen); + BIO_write(secout ? secout : out, secret, secretlen); end: if (ret != 0) @@ -802,7 +822,7 @@ static int do_keyop(EVP_PKEY_CTX *ctx, int pkey_op, break; case EVP_PKEY_OP_DECAPSULATE: - rv = EVP_PKEY_decapsulate(ctx, out, poutlen, in, inlen); + rv = EVP_PKEY_decapsulate(ctx, secret, pseclen, in, inlen); break; } diff --git a/doc/man1/openssl-pkeyutl.pod.in b/doc/man1/openssl-pkeyutl.pod.in index 0deafd4e816..e10b08cd5ec 100644 --- a/doc/man1/openssl-pkeyutl.pod.in +++ b/doc/man1/openssl-pkeyutl.pod.in @@ -99,7 +99,11 @@ Specifies the output filename to write to or standard output by default. =item B<-secret> I -Specifies the output filename to write the secret to on I<-encap>. +Specifies the shared-secret output filename for when performing encapsulation +via the B<-encap> option or decapsulation via the B<-decap> option. +The B<-encap> option also produces a separate (public) ciphertext output which +is by default written to standard output, but being I non-text data, +is typically also redirected to a file selected via the I<-out> option. =item B<-sigfile> I @@ -194,28 +198,57 @@ See L for details. =item B<-encap> -Encapsulate a generated secret using a private key. -The encapsulated result (binary data) is written to standard output by default, -or else to the file specified with I<-out>. +Use a Key Encapsulation Mechanism (B) to B a shared-secret to +a peer's B key. +The encapsulated result (or ciphertext, non-text binary data) is written to +standard output by default, or else to the file specified with I<-out>. The I<-secret> option must also be provided to specify the output file for the -secret value generated in the encapsulation process. +derived shared-secret value generated in the encapsulation process. +Encapsulation is supported with a number of public key algorithms, currently: +L, +L, +L, +and +L. +The ECX and EC algorithms use the +L DHKEM construction. +Encapsulation is also supported with L keys with the use of +an additional B<-kemop> option. + +At the API level, encapsulation and decapsulation are also supported for a few +hybrid ECDHE (no DHKEM) plus B algorithms, but these are intended +primarily for use with TLS and should not be used standalone. +There are in any case no standard public and private key formats for the hybrid +algorithms, so it is not possible to provide the required key material. =item B<-decap> -Decapsulate the secret using a private key. -The result (binary data) is written to standard output by default, or else to -the file specified with I<-out>. +Decode an encapsulated secret, with the use of a B<-private> key, to derive the +same shared-secret as that obtained when the secret was encapsulated to the +corresponding public key. +The encapsulated secret is by default read from the standard input, or else +from the file specified with B<-in>. +The derived shared-secret is written to the file specified with the B<-secret> +option, which I also be provided. +Decapsulation is supported with a number of public key algorithms, currently: +L, +L, +L, +and +L. +The ECX and EC algorithms use the +L DHKEM construction. +Encapsulation is also supported with L keys with the use of +an additional B<-kemop> option. =item B<-kemop> I -This option is used for I<-encap>/I<-decap> commands and specifies the KEM -operation specific for the key algorithm when there is no default KEM -operation. -If the algorithm has the default KEM operation, this option can be omitted. - -See L and algorithm-specific KEM documentation e.g. -L, L, L, and -L. +This option is used with the I<-encap>/I<-decap> commands and specifies the KEM +operation (mode) specific for the key algorithm when there is no default way +to encapsulate and decapsulate shared secrets with the chosen key type. +This is needed only for RSA, where B, is not the default mode, even +though it is presently the only RSA KEM supported. +See L and L. =item B<-kdf> I diff --git a/test/recipes/20-test_pkeyutl.t b/test/recipes/20-test_pkeyutl.t index 8795f87b45e..7b600979a3a 100644 --- a/test/recipes/20-test_pkeyutl.t +++ b/test/recipes/20-test_pkeyutl.t @@ -17,7 +17,7 @@ use File::Compare qw/compare_text compare/; setup("test_pkeyutl"); -plan tests => 25; +plan tests => 27; # For the tests below we use the cert itself as the TBS file @@ -237,14 +237,21 @@ SKIP: { # openssl pkeyutl -decap -inkey rsa_priv.pem -in encap_out.bin -out decap_out.bin # decap_out is equal to secret SKIP: { - skip "RSA is not supported by this OpenSSL build", 5 - if disabled("rsa"); + skip "RSA is not supported by this OpenSSL build", 7 + if disabled("rsa"); # Note "rsa" isn't (yet?) disablable. # Self-compat - ok(run(app(([ 'openssl', 'pkeyutl', '-encap', '-pubin', '-kemop', 'RSASVE', + ok(run(app(([ 'openssl', 'pkeyutl', '-encap', '-kemop', 'RSASVE', '-inkey', srctop_file('test', 'testrsa2048pub.pem'), '-out', 'encap_out.bin', '-secret', 'secret.bin']))), "RSA pubkey encapsulation"); + ok(run(app(([ 'openssl', 'pkeyutl', '-decap', '-kemop', 'RSASVE', + '-inkey', srctop_file('test', 'testrsa2048.pem'), + '-in', 'encap_out.bin', '-secret', 'decap_secret.bin']))), + "RSA pubkey decapsulation"); + is(compare("secret.bin", "decap_secret.bin"), 0, "Secret is correctly decapsulated"); + + # Legacy CLI with decap output written to '-out' ok(run(app(([ 'openssl', 'pkeyutl', '-decap', '-kemop', 'RSASVE', '-inkey', srctop_file('test', 'testrsa2048.pem'), '-in', 'encap_out.bin', '-out', 'decap_out.bin']))), @@ -254,10 +261,10 @@ SKIP: { # Pregenerated ok(run(app(([ 'openssl', 'pkeyutl', '-decap', '-kemop', 'RSASVE', '-inkey', srctop_file('test', 'testrsa2048.pem'), - '-in', srctop_file('test', 'encap_out.bin'), '-out', 'decap_out_etl.bin']))), + '-in', srctop_file('test', 'encap_out.bin'), + '-secret', 'decap_out_etl.bin']))), "RSA pubkey decapsulation - pregenerated"); is(compare(srctop_file('test', 'encap_secret.bin'), "decap_out_etl.bin"), 0, "Secret is correctly decapsulated - pregenerated"); } - -- 2.47.2