]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Fix code and docs of pkeyutl en/decapsulation
authorViktor Dukhovni <openssl-users@dukhovni.org>
Sun, 29 Dec 2024 14:29:18 +0000 (01:29 +1100)
committerTomas Mraz <tomas@openssl.org>
Fri, 14 Feb 2025 09:50:57 +0000 (10:50 +0100)
Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Tim Hudson <tjh@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/26281)

apps/pkeyutl.c
doc/man1/openssl-pkeyutl.pod.in
test/recipes/20-test_pkeyutl.t

index 1d97b3cc5e61dc3fb8b7d54eb3724dfb8ba12fe8..2c2ad09d22744807f27ed5674d249e09472f78ea 100644 (file)
@@ -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 <file>' 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;
 
     }
index 0deafd4e81629d3b5b91ed1aee0cf547655e4bd0..e10b08cd5ecf0769fb74e4a4cf78b168d901803b 100644 (file)
@@ -99,7 +99,11 @@ Specifies the output filename to write to or standard output by default.
 
 =item B<-secret> I<filename>
 
-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<binary> non-text data,
+is typically also redirected to a file selected via the I<-out> option.
 
 =item B<-sigfile> I<file>
 
@@ -194,28 +198,57 @@ See L<openssl-format-options(1)> 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<KEM>) to B<encapsulate> a shared-secret to
+a peer's B<public> 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<ML-KEM|EVP_PKEY-ML-KEM(7)>,
+L<X25519|EVP_KEM-X25519(7)>,
+L<X449|EVP_KEM-X448(7)>,
+and
+L<EC|EVP_KEM-EC(7)>.
+The ECX and EC algorithms use the
+L<RFC9180|https://www.rfc-editor.org/rfc/rfc9180> DHKEM construction.
+Encapsulation is also supported with L<RSA|EVP_KEM-RSA(7)> 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<ML-KEM> 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<must> also be provided.
+Decapsulation is supported with a number of public key algorithms, currently:
+L<ML-KEM|EVP_PKEY-ML-KEM(7)>,
+L<X25519|EVP_KEM-X25519(7)>,
+L<X448|EVP_KEM-X448(7)>,
+and
+L<EC|EVP_KEM-EC(7)>.
+The ECX and EC algorithms use the
+L<RFC9180|https://www.rfc-editor.org/rfc/rfc9180> DHKEM construction.
+Encapsulation is also supported with L<RSA|EVP_KEM-RSA(7)> keys with the use of
+an additional B<-kemop> option.
 
 =item B<-kemop> I<operation>
 
-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<EVP_PKEY_CTX_set_kem_op(3)> and algorithm-specific KEM documentation e.g.
-L<EVP_KEM-RSA(7)>, L<EVP_KEM-EC(7)>, L<EVP_KEM-X25519(7)>, and
-L<EVP_KEM-X448(7)>.
+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<RSASVE>, is not the default mode, even
+though it is presently the only RSA KEM supported.
+See L<EVP_PKEY_CTX_set_kem_op(3)> and L<EVP_KEM-RSA(7)>.
 
 =item B<-kdf> I<algorithm>
 
index 8795f87b45ed0f957c64b394823c4c960610500d..7b600979a3a31150bcc097223a32ca288cf8151c 100644 (file)
@@ -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");
 }
-