Commit 80b7e49c27 for openssl.org
commit 80b7e49c273f9977394de54f338baec9273dd47a
Author: herbenderbler <johnclaus@gmail.com>
Date: Wed Mar 25 00:49:06 2026 -0600
Use mmap for pkeyutl -rawin and dgst one-shot input
When using openssl pkeyutl -rawin or openssl dgst for one-shot sign/verify
(e.g. Ed25519, Ed448), file input is now read via mmap() on Unix where
supported, avoiding a full buffer allocation and copy. Large files are
supported without doubling memory use; on failure of the mmap path we
do not fall back to the buffer path.
- Add app_mmap_file() in apps/lib/apps.c: stat/open/mmap/close, tri-state
return (1 mapped, 0 size zero, -1 error). Parameter err_bio avoids
shadowing global bio_err (-Wshadow).
- apps/pkeyutl.c and apps/dgst.c: use app_mmap_file(); single exit for
mmap path in pkeyutl; dgst includes apps.h first for _FILE_OFFSET_BITS;
do_fp_oneshot_sign returns EXIT_SUCCESS/EXIT_FAILURE like do_fp(); no
fallback when mmap attempted but fails.
- pkeyutl mmap/buffer path: pass filesize to EVP_DigestVerify and
EVP_DigestSign (review suggestion, avoids casting buf_len).
- Error messages: per-file messages for stat/size (dgst, pkeyutl); CHANGES.md
"Unix-like" and "16 MB" (documentation style).
- Centralize _FILE_OFFSET_BITS and mmap includes in apps/include/apps.h.
- Tests: pkeyutl/dgst oneshot from file, no-fallback regression tests;
use srctop_dir for test paths; stderr patterns for mmap errors.
- Docs: man pages and CHANGES.md.
CI fixes: return failure from dgst one-shot sign when mmap fails; treat
non-regular paths as mmap errors in app_mmap_file() and pkeyutl; reject
directories before mmap.
Addresses review feedback from DDvO, npajkovsky, and vdukhovni (PR #30429).
Fixes #11677
Co-authored-by: Viktor Dukhovni <viktor1ghub@dukhovni.org>
Co-authored-by: David von Oheimb <DDvO@users.noreply.github.com>
Reviewed-by: David von Oheimb <david.von.oheimb@siemens.com>
Reviewed-by: Viktor Dukhovni <viktor@openssl.org>
Reviewed-by: Paul Dale <paul.dale@oracle.com>
MergeDate: Fri Mar 27 16:25:33 2026
(Merged from https://github.com/openssl/openssl/pull/30429)
diff --git a/CHANGES.md b/CHANGES.md
index 4cd74cea87..8672a909b6 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -31,6 +31,17 @@ OpenSSL Releases
### Changes between 4.0 and 4.1 [xx XXX xxxx]
+ * The `openssl pkeyutl` command now uses memory-mapped I/O when reading
+ raw input from a file for oneshot sign/verify operations (such as Ed25519,
+ Ed448, and ML-DSA) on platforms that support it (Unix-like). The
+ `openssl dgst` command uses the same approach for one-shot sign/verify
+ when the input is from a file, removing the previous 16 MB limit for
+ file-based input. This improves performance and supports large files
+ without doubling memory use. Other platforms and stdin input continue to
+ use the existing buffer-based path.
+
+ *John Claus*
+
* Added AVX2 optimized ML-DSA NTT operations on `x86_64`.
*Marcel Cornu and Tomasz Kantecki*
diff --git a/apps/dgst.c b/apps/dgst.c
index 0243042073..68e9f3f43f 100644
--- a/apps/dgst.c
+++ b/apps/dgst.c
@@ -7,10 +7,10 @@
* https://www.openssl.org/source/license.html
*/
+#include "apps.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
-#include "apps.h"
#include "progs.h"
#include <openssl/bio.h>
#include <openssl/err.h>
@@ -20,6 +20,7 @@
#include <openssl/pem.h>
#include <openssl/hmac.h>
#include <ctype.h>
+#include <sys/stat.h>
#undef BUFSIZE
#define BUFSIZE 1024 * 8
@@ -482,7 +483,7 @@ int dgst_main(int argc, char **argv)
BIO_set_fp(in, stdin, BIO_NOCLOSE);
if (oneshot_sign)
ret = do_fp_oneshot_sign(out, signctx, in, separator, out_bin,
- sigkey, sigbuf, siglen, NULL, "stdin");
+ sigkey, sigbuf, siglen, NULL, NULL);
else
ret = do_fp(out, buf, inp, separator, out_bin, xoflen,
sigkey, sigbuf, siglen, NULL, md_name, "stdin");
@@ -723,52 +724,88 @@ end:
}
/*
- * Some new algorithms only support one shot operations.
- * For these we need to buffer all input and then do the sign on the
- * total buffered input. These algorithms set a NULL digest name which is
- * then used inside EVP_DigestVerify() and EVP_DigestSign().
+ * Perform one-shot verify or sign on a contiguous data buffer.
+ * Returns 0 on failure, 1 on success.
*/
-static int do_fp_oneshot_sign(BIO *out, EVP_MD_CTX *ctx, BIO *in, int sep, int binout,
- EVP_PKEY *key, unsigned char *sigin, int siglen,
- const char *sig_name, const char *file)
+static int do_oneshot_verify_sign(EVP_MD_CTX *ctx, BIO *out,
+ unsigned char *sigin, int siglen, EVP_PKEY *key,
+ const unsigned char *data, size_t len,
+ int sep, int binout, const char *sig_name, const char *file)
{
- int res, ret = EXIT_FAILURE;
- size_t len = 0;
- size_t buflen = 0;
- size_t maxlen = 16 * 1024 * 1024;
- uint8_t *buf = NULL, *sig = NULL;
+ int res;
+ size_t siglen_out = 0;
+ unsigned char *sig = NULL;
- if (!bio_to_mem(&buf, &buflen, maxlen, in)) {
- BIO_printf(bio_err, "Read error in %s\n", file);
- return ret;
- }
if (sigin != NULL) {
- res = EVP_DigestVerify(ctx, sigin, siglen, buf, buflen);
+ res = EVP_DigestVerify(ctx, sigin, siglen, data, len);
print_verify_result(out, res);
- if (res > 0)
- ret = EXIT_SUCCESS;
- goto end;
+ return res > 0;
}
if (key != NULL) {
- if (EVP_DigestSign(ctx, NULL, &len, buf, buflen) != 1) {
+ if (EVP_DigestSign(ctx, NULL, &siglen_out, data, len) != 1) {
BIO_puts(bio_err, "Error getting maximum length of signed data\n");
- goto end;
+ return 0;
}
- sig = app_malloc(len, "Signature buffer");
- if (EVP_DigestSign(ctx, sig, &len, buf, buflen) != 1) {
+ sig = app_malloc(siglen_out, "Signature buffer");
+ if (EVP_DigestSign(ctx, sig, &siglen_out, data, len) != 1) {
BIO_puts(bio_err, "Error signing data\n");
- goto end;
+ OPENSSL_free(sig);
+ return 0;
}
- print_out(out, sig, len, sep, binout, sig_name, NULL, file);
- ret = EXIT_SUCCESS;
- } else {
- BIO_puts(bio_err, "key must be set for one-shot algorithms\n");
- goto end;
+ print_out(out, sig, siglen_out, sep, binout, sig_name, NULL, file);
+ OPENSSL_free(sig);
+ return 1;
}
+ BIO_puts(bio_err, "key must be set for one-shot algorithms\n");
+ return 0;
+}
-end:
- OPENSSL_free(sig);
- OPENSSL_clear_free(buf, buflen);
+/*
+ * Some new algorithms only support one shot operations.
+ * For these we need to buffer all input and then do the sign on the
+ * total buffered input. These algorithms set a NULL digest name which is
+ * then used inside EVP_DigestVerify() and EVP_DigestSign().
+ */
+static int do_fp_oneshot_sign(BIO *out, EVP_MD_CTX *ctx, BIO *in, int sep, int binout,
+ EVP_PKEY *key, unsigned char *sigin, int siglen,
+ const char *sig_name, const char *file)
+{
+ int ret = EXIT_FAILURE;
+ size_t buflen = 0;
+ size_t maxlen = 16 * 1024 * 1024;
+ uint8_t *buf = NULL;
+
+#if defined(OPENSSL_SYS_UNIX) && defined(_POSIX_MAPPED_FILES) && _POSIX_MAPPED_FILES > 0
+ if (file != NULL) {
+ const unsigned char *data = NULL;
+ size_t filesize = 0;
+ int r = app_mmap_file(file, bio_err, (size_t)-1, &data, &filesize);
+
+ if (r == 1) {
+ ret = do_oneshot_verify_sign(ctx, out, sigin, siglen, key, data,
+ filesize, sep, binout, sig_name, file)
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE;
+ munmap((void *)data, filesize);
+ return ret;
+ }
+ if (r == -1)
+ return EXIT_FAILURE; /* error already printed */
+ /* r == 0: empty file, fall through to buffer path */
+ }
+#endif
+
+ {
+ const char *display_file = file != NULL ? file : "stdin";
+
+ if (!bio_to_mem(&buf, &buflen, maxlen, in))
+ return EXIT_FAILURE;
+ ret = do_oneshot_verify_sign(ctx, out, sigin, siglen, key, buf, buflen,
+ sep, binout, sig_name, display_file)
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE;
+ OPENSSL_clear_free(buf, buflen);
+ }
return ret;
}
diff --git a/apps/include/apps.h b/apps/include/apps.h
index 16669ed040..43097c013e 100644
--- a/apps/include/apps.h
+++ b/apps/include/apps.h
@@ -10,6 +10,16 @@
#ifndef OSSL_APPS_H
#define OSSL_APPS_H
+#if defined(__linux__) || defined(__sun__) || defined(__hpux)
+/*
+ * Allow open() and stat() to work with files larger than 2GB on 32-bit
+ * systems. See crypto/o_fopen.c and crypto/bio/bss_file.c.
+ */
+#ifndef _FILE_OFFSET_BITS
+#define _FILE_OFFSET_BITS 64
+#endif
+#endif
+
#include "internal/common.h" /* for HAS_PREFIX */
#include "internal/nelem.h"
#include <assert.h>
@@ -22,6 +32,19 @@
#endif
#include <openssl/e_os2.h>
+#if defined(OPENSSL_SYS_UNIX) && defined(_POSIX_MAPPED_FILES) && _POSIX_MAPPED_FILES > 0
+#include <sys/mman.h>
+#include <unistd.h>
+/*
+ * Map a file read-only into memory. Returns 1 on success (*out_data and
+ * *out_size set; caller must munmap when done), 0 when file size is 0 (no
+ * error, caller may use buffer path), or -1 on error (message printed to
+ * bio_err). known_size: (size_t)-1 = stat to get size; 0 = do not map
+ * (return 0); > 0 = use this size (caller obtained it from stat of same path).
+ */
+int app_mmap_file(const char *path, BIO *err_bio, size_t known_size,
+ const unsigned char **out_data, size_t *out_size);
+#endif
#include <openssl/types.h>
#include <openssl/bio.h>
#include <openssl/x509.h>
diff --git a/apps/lib/apps.c b/apps/lib/apps.c
index 6e8167b7e9..46e78ecebd 100644
--- a/apps/lib/apps.c
+++ b/apps/lib/apps.c
@@ -2202,6 +2202,63 @@ int bio_to_mem(unsigned char **out, size_t *outlen, size_t maxlen, BIO *in)
return 1;
}
+#if defined(OPENSSL_SYS_UNIX) && defined(_POSIX_MAPPED_FILES) && _POSIX_MAPPED_FILES > 0
+int app_mmap_file(const char *path, BIO *err_bio, size_t known_size,
+ const unsigned char **out_data, size_t *out_size)
+{
+ struct stat st;
+ size_t filesize;
+ int fd;
+ void *p;
+
+ *out_data = NULL;
+ *out_size = 0;
+
+ if (known_size == 0)
+ return 0;
+
+ if (known_size == (size_t)-1) {
+ if (stat(path, &st) != 0 || st.st_size < 0) {
+ BIO_printf(err_bio, "Error: failed to get size of file '%s'\n", path);
+ return -1;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ /*
+ * mmap() is only for regular files. Directories and other non-regular
+ * paths can report st_size == 0; do not treat those like empty files
+ * and fall back to the buffer path in callers.
+ */
+ BIO_puts(err_bio, "Error: failed to use memory-mapped file\n");
+ return -1;
+ }
+ filesize = (size_t)st.st_size;
+ if ((off_t)filesize != st.st_size) {
+ BIO_puts(err_bio, "Error: failed to convert file size, likely too big\n");
+ return -1;
+ }
+ if (filesize == 0)
+ return 0;
+ } else {
+ filesize = known_size;
+ }
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ BIO_puts(err_bio, "Error opening file for memory mapping\n");
+ return -1;
+ }
+ p = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0);
+ (void)close(fd);
+ if (p == MAP_FAILED) {
+ BIO_puts(err_bio, "Error: failed to use memory-mapped file\n");
+ return -1;
+ }
+ *out_data = (const unsigned char *)p;
+ *out_size = filesize;
+ return 1;
+}
+#endif
+
int pkey_ctrl_string(EVP_PKEY_CTX *ctx, const char *value)
{
int rv = 0;
diff --git a/apps/pkeyutl.c b/apps/pkeyutl.c
index d30bae99f2..0ffd92f8c0 100644
--- a/apps/pkeyutl.c
+++ b/apps/pkeyutl.c
@@ -9,6 +9,7 @@
#include "apps.h"
#include "progs.h"
+#include <limits.h>
#include <string.h>
#include <openssl/err.h>
#include <openssl/pem.h>
@@ -37,8 +38,8 @@ static int do_keyop(EVP_PKEY_CTX *ctx, int pkey_op,
unsigned char *secret, size_t *psecretlen);
static int do_raw_keyop(int pkey_op, EVP_MD_CTX *mctx,
- EVP_PKEY *pkey, BIO *in,
- int filesize, unsigned char *sig, size_t siglen,
+ EVP_PKEY *pkey, BIO *in, const char *infile,
+ size_t filesize, unsigned char *sig, size_t siglen,
unsigned char **out, size_t *poutlen);
static int only_nomd(EVP_PKEY *pkey)
@@ -162,7 +163,7 @@ int pkeyutl_main(int argc, char **argv)
int rawin = 0;
EVP_MD_CTX *mctx = NULL;
EVP_MD *md = NULL;
- int filesize = -1;
+ size_t filesize = (size_t)-1; /* (size_t)-1 means unknown */
OSSL_LIB_CTX *libctx = app_get0_libctx();
prog = opt_init(argc, argv, pkeyutl_options);
@@ -454,8 +455,11 @@ int pkeyutl_main(int argc, char **argv)
if (infile != NULL) {
struct stat st;
- if (stat(infile, &st) == 0 && st.st_size <= INT_MAX)
- filesize = (int)st.st_size;
+ if (stat(infile, &st) == 0 && st.st_size >= 0) {
+ filesize = (size_t)st.st_size;
+ if ((off_t)filesize != st.st_size)
+ filesize = (size_t)-1;
+ }
}
if (in == NULL)
goto end;
@@ -539,7 +543,7 @@ int pkeyutl_main(int argc, char **argv)
if (pkey_op == EVP_PKEY_OP_VERIFY) {
if (rawin) {
- rv = do_raw_keyop(pkey_op, mctx, pkey, in, filesize, sig, siglen,
+ rv = do_raw_keyop(pkey_op, mctx, pkey, in, infile, filesize, sig, siglen,
NULL, 0);
} else {
rv = EVP_PKEY_verify(ctx, sig, siglen, buf_in, buf_inlen);
@@ -554,7 +558,7 @@ int pkeyutl_main(int argc, char **argv)
}
if (rawin) {
/* rawin allocates the buffer in do_raw_keyop() */
- rv = do_raw_keyop(pkey_op, mctx, pkey, in, filesize, NULL, 0,
+ rv = do_raw_keyop(pkey_op, mctx, pkey, in, infile, filesize, NULL, 0,
&buf_out, &buf_outlen);
} else {
if (kdflen != 0) {
@@ -821,8 +825,8 @@ static int do_keyop(EVP_PKEY_CTX *ctx, int pkey_op,
#define TBUF_MAXSIZE 2048
static int do_raw_keyop(int pkey_op, EVP_MD_CTX *mctx,
- EVP_PKEY *pkey, BIO *in,
- int filesize, unsigned char *sig, size_t siglen,
+ EVP_PKEY *pkey, BIO *in, const char *infile,
+ size_t filesize, unsigned char *sig, size_t siglen,
unsigned char **out, size_t *poutlen)
{
int rv = 0;
@@ -832,32 +836,72 @@ static int do_raw_keyop(int pkey_op, EVP_MD_CTX *mctx,
/* Some algorithms only support oneshot digests */
if (only_nomd(pkey)) {
- if (filesize < 0) {
+ if (filesize == (size_t)-1) {
+ BIO_printf(bio_err,
+ "Error: unable to determine size of file '%s' for oneshot operation\n",
+ infile);
+ goto end;
+ }
+#if defined(OPENSSL_SYS_UNIX) && defined(_POSIX_MAPPED_FILES) && _POSIX_MAPPED_FILES > 0
+ if (infile != NULL) {
+ struct stat st;
+
+ if (stat(infile, &st) == 0 && !S_ISREG(st.st_mode)) {
+ BIO_puts(bio_err, "Error: failed to use memory-mapped file\n");
+ goto end;
+ }
+ }
+ if (filesize > 0 && infile != NULL) {
+ const unsigned char *data = NULL;
+ size_t mapped_size = 0;
+
+ if (app_mmap_file(infile, bio_err, filesize, &data, &mapped_size) == 1) {
+ switch (pkey_op) {
+ case EVP_PKEY_OP_VERIFY:
+ rv = EVP_DigestVerify(mctx, sig, siglen, data, mapped_size);
+ break;
+ case EVP_PKEY_OP_SIGN:
+ rv = EVP_DigestSign(mctx, NULL, poutlen, data, mapped_size);
+ if (rv == 1 && out != NULL) {
+ *out = app_malloc(*poutlen, "buffer output");
+ rv = EVP_DigestSign(mctx, *out, poutlen, data, mapped_size);
+ }
+ break;
+ default:
+ break;
+ }
+ munmap((void *)data, mapped_size);
+ }
+ /* Success or mmap failure: do not fall back to buffer path */
+ goto end;
+ }
+#endif
+ if (filesize > INT_MAX) {
BIO_puts(bio_err,
- "Error: unable to determine file size for oneshot operation\n");
+ "Error: file too large for oneshot operation without memory mapping\n");
goto end;
}
if (filesize > 0)
mbuf = app_malloc(filesize, "oneshot sign/verify buffer");
switch (pkey_op) {
case EVP_PKEY_OP_VERIFY:
- buf_len = BIO_read(in, mbuf, filesize);
- if (buf_len != filesize) {
+ buf_len = BIO_read(in, mbuf, (int)filesize);
+ if (buf_len < 0 || (size_t)buf_len != filesize) {
BIO_puts(bio_err, "Error reading raw input data\n");
goto end;
}
- rv = EVP_DigestVerify(mctx, sig, siglen, mbuf, buf_len);
+ rv = EVP_DigestVerify(mctx, sig, siglen, mbuf, filesize);
break;
case EVP_PKEY_OP_SIGN:
- buf_len = BIO_read(in, mbuf, filesize);
- if (buf_len != filesize) {
+ buf_len = BIO_read(in, mbuf, (int)filesize);
+ if (buf_len < 0 || (size_t)buf_len != filesize) {
BIO_puts(bio_err, "Error reading raw input data\n");
goto end;
}
- rv = EVP_DigestSign(mctx, NULL, poutlen, mbuf, buf_len);
+ rv = EVP_DigestSign(mctx, NULL, poutlen, mbuf, filesize);
if (rv == 1 && out != NULL) {
*out = app_malloc(*poutlen, "buffer output");
- rv = EVP_DigestSign(mctx, *out, poutlen, mbuf, buf_len);
+ rv = EVP_DigestSign(mctx, *out, poutlen, mbuf, filesize);
}
break;
}
diff --git a/doc/man1/openssl-dgst.pod.in b/doc/man1/openssl-dgst.pod.in
index 8aa2cf79c1..73e439ad14 100644
--- a/doc/man1/openssl-dgst.pod.in
+++ b/doc/man1/openssl-dgst.pod.in
@@ -118,10 +118,11 @@ Filename to output to, or standard output by default.
Digitally sign the digest using the given private key.
Note that for algorithms that only support one-shot signing
-(such as Ed25519, ED448, ML-DSA-44, ML-DSA-65 andML-DSA-87) the digest must not
+(such as Ed25519, ED448, ML-DSA-44, ML-DSA-65 and ML-DSA-87) the digest must not
be set. For these algorithms the input is buffered (and not digested) before
-signing. For these algorithms, if the input is larger than 16MB an error
-will occur.
+signing. When the input is from a file, memory-mapped I/O is used on
+supported platforms (Unix\-like), allowing large files without a size limit; when
+input is from stdin or on unsupported platforms, input is limited to 16MB.
=item B<-keyform> B<DER>|B<PEM>|B<P12>
@@ -143,6 +144,10 @@ see L<openssl-passphrase-options(1)>.
Verify the signature using the public key in "filename".
The output is either "Verified OK" or "Verification Failure".
+For one-shot verification algorithms (e.g. Ed25519, Ed448), when the input
+is from a file, memory-mapped I/O is used on supported platforms (Unix\-like),
+allowing large files; when input is from stdin or on unsupported platforms,
+input is limited to 16MB.
=item B<-prverify> I<filename>
@@ -331,6 +336,11 @@ The B<-engine> and B<-engine_impl> options were removed in OpenSSL 4.0.
The B<-hmac-env> and B<-hmac-stdin> options were added in OpenSSL 4.0.
+Since OpenSSL 4.1, one-shot sign and verify (e.g. Ed25519, Ed448) with input
+from a file uses memory-mapped I/O on supported platforms (Unix\-like), allowing
+large files to be processed without the previous 16MB limit for file-based
+input.
+
=head1 COPYRIGHT
Copyright 2000-2025 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/doc/man1/openssl-pkeyutl.pod.in b/doc/man1/openssl-pkeyutl.pod.in
index 5c080bf51f..053385ca0b 100644
--- a/doc/man1/openssl-pkeyutl.pod.in
+++ b/doc/man1/openssl-pkeyutl.pod.in
@@ -77,6 +77,10 @@ is implied since OpenSSL 3.5, and required in earlier versions.
The B<-digest> option implies B<-rawin> since OpenSSL 3.5.
+When the input is read from a file (B<-in> I<filename>), the command may
+use memory-mapped I/O on supported platforms for better performance and
+to handle large files without loading the entire file into memory.
+
=item B<-digest> I<algorithm>
This option can only be used with B<-sign> and B<-verify>.
@@ -681,6 +685,11 @@ the supported algorithms, the only supported B<mode> is now the default.
The B<-engine> option was removed in OpenSSL 4.0.
+Since OpenSSL 4.1, when reading raw input from a file (B<-in> I<filename>) for
+oneshot sign/verify (such as Ed25519, Ed448, and ML-DSA), the command uses
+memory-mapped I/O on supported platforms, allowing large files to be processed
+without loading the entire file into memory.
+
=head1 COPYRIGHT
Copyright 2006-2025 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/test/recipes/20-test_dgst.t b/test/recipes/20-test_dgst.t
index fe801b298e..e287bd32ff 100644
--- a/test/recipes/20-test_dgst.t
+++ b/test/recipes/20-test_dgst.t
@@ -12,13 +12,13 @@ use warnings;
use File::Spec;
use File::Basename;
-use OpenSSL::Test qw/:DEFAULT with srctop_file data_file bldtop_dir/;
+use OpenSSL::Test qw/:DEFAULT with srctop_file srctop_dir data_file bldtop_dir/;
use OpenSSL::Test::Utils;
use Cwd qw(abs_path);
setup("test_dgst");
-plan tests => 24;
+plan tests => 25;
sub tsignverify {
my $testtext = shift;
@@ -89,91 +89,143 @@ sub tsignverify_sha512 {
$testtext.": Expect failure verifying mismatching data");
}
-SKIP: {
- skip "RSA is not supported by this OpenSSL build", 1
- if disabled("rsa");
-
- subtest "RSA signature generation and verification with `dgst` CLI" => sub {
- tsignverify("RSA",
- srctop_file("test","testrsa.pem"),
- srctop_file("test","testrsapub.pem"));
- };
-
- subtest "RSA signature generation and verification with `sha512` CLI" => sub {
- tsignverify_sha512("RSA",
- srctop_file("test","testrsa2048.pem"),
- srctop_file("test","testrsa2048pub.pem"));
- };
-}
+subtest "RSA signature generation and verification with `dgst` CLI" => sub {
+ if (disabled("rsa")) {
+ plan tests => 1;
+ ok(1, "Skipped (RSA not supported)");
+ return;
+ }
+ tsignverify("RSA",
+ srctop_file("test","testrsa.pem"),
+ srctop_file("test","testrsapub.pem"));
+};
-SKIP: {
- skip "DSA is not supported by this OpenSSL build", 1
- if disabled("dsa");
+subtest "RSA signature generation and verification with `sha512` CLI" => sub {
+ if (disabled("rsa")) {
+ plan tests => 1;
+ ok(1, "Skipped (RSA not supported)");
+ return;
+ }
+ tsignverify_sha512("RSA",
+ srctop_file("test","testrsa2048.pem"),
+ srctop_file("test","testrsa2048pub.pem"));
+};
- subtest "DSA signature generation and verification with `dgst` CLI" => sub {
- tsignverify("DSA",
- srctop_file("test","testdsa.pem"),
- srctop_file("test","testdsapub.pem"));
- };
-}
+subtest "DSA signature generation and verification with `dgst` CLI" => sub {
+ if (disabled("dsa")) {
+ plan tests => 1;
+ ok(1, "Skipped (DSA not supported)");
+ return;
+ }
+ tsignverify("DSA",
+ srctop_file("test","testdsa.pem"),
+ srctop_file("test","testdsapub.pem"));
+};
-SKIP: {
- skip "ECDSA is not supported by this OpenSSL build", 1
- if disabled("ec");
+subtest "ECDSA signature generation and verification with `dgst` CLI" => sub {
+ if (disabled("ec")) {
+ plan tests => 1;
+ ok(1, "Skipped (ECDSA not supported)");
+ return;
+ }
+ tsignverify("ECDSA",
+ srctop_file("test","testec-p256.pem"),
+ srctop_file("test","testecpub-p256.pem"));
+};
- subtest "ECDSA signature generation and verification with `dgst` CLI" => sub {
- tsignverify("ECDSA",
- srctop_file("test","testec-p256.pem"),
- srctop_file("test","testecpub-p256.pem"));
- };
-}
+subtest "Ed25519 signature generation and verification with `dgst` CLI" => sub {
+ if (disabled("ecx")) {
+ plan tests => 1;
+ ok(1, "Skipped (EdDSA not supported)");
+ return;
+ }
+ tsignverify("Ed25519",
+ srctop_file("test","tested25519.pem"),
+ srctop_file("test","tested25519pub.pem"));
+};
-SKIP: {
- skip "EdDSA is not supported by this OpenSSL build", 2
- if disabled("ecx");
-
- subtest "Ed25519 signature generation and verification with `dgst` CLI" => sub {
- tsignverify("Ed25519",
- srctop_file("test","tested25519.pem"),
- srctop_file("test","tested25519pub.pem"));
- };
-
- subtest "Ed448 signature generation and verification with `dgst` CLI" => sub {
- tsignverify("Ed448",
- srctop_file("test","tested448.pem"),
- srctop_file("test","tested448pub.pem"));
- };
-}
+subtest "Ed448 signature generation and verification with `dgst` CLI" => sub {
+ if (disabled("ecx")) {
+ plan tests => 1;
+ ok(1, "Skipped (EdDSA not supported)");
+ return;
+ }
+ tsignverify("Ed448",
+ srctop_file("test","tested448.pem"),
+ srctop_file("test","tested448pub.pem"));
+};
-SKIP: {
- skip "ML-DSA is not supported by this OpenSSL build", 3
- if disabled("ml-dsa");
-
- subtest "ML-DSA-44 signature generation and verification with `dgst` CLI" => sub {
- tsignverify("Ml-DSA-44",
- srctop_file("test","testmldsa44.pem"),
- srctop_file("test","testmldsa44pub.pem"));
- };
- subtest "ML-DSA-65 signature generation and verification with `dgst` CLI" => sub {
- tsignverify("Ml-DSA-65",
- srctop_file("test","testmldsa65.pem"),
- srctop_file("test","testmldsa65pub.pem"));
- };
- subtest "ML-DSA-87 signature generation and verification with `dgst` CLI" => sub {
- tsignverify("Ml-DSA-87",
- srctop_file("test","testmldsa87.pem"),
- srctop_file("test","testmldsa87pub.pem"));
- };
-}
+subtest "dgst one-shot: no buffer fallback when mmap path fails (Unix)" => sub {
+ if ($^O eq 'MSWin32' || disabled("ecx")) {
+ plan tests => 1;
+ ok(1, "Skipped (Unix/mmap or EdDSA not available)");
+ return;
+ }
+ plan tests => 2;
-SKIP: {
- skip "dgst with provider is not supported by this OpenSSL build", 1
- if disabled("module");
+ # Use a directory with non-zero st_size so app_mmap_file() attempts open+mmap
+ # (curdir "." often has st_size 0 on some FS, which skips mmap and breaks this test).
+ # mmap() on a directory must fail; we must not fall back to bio_to_mem.
+ my $key = srctop_file("test", "tested25519.pem");
+ my $dir = srctop_dir("test");
+ my $stderr_file = "dgst_nofallback_err.txt";
+
+ with({ exit_checker => sub { return shift != 0; } },
+ sub {
+ ok(run(app(['openssl', 'dgst', '-sign', $key, $dir],
+ stderr => $stderr_file)),
+ "dgst one-shot with un-mmapable file fails (no fallback)");
+ });
+ if (open(my $fh, '<', $stderr_file)) {
+ my $err = do { local $/; <$fh> };
+ close($fh);
+ ok($err =~ /Error: failed to use memory-mapped file/, "stderr mentions mmap failure");
+ } else {
+ ok(0, "could not read stderr file");
+ }
+ unlink($stderr_file) if -f $stderr_file;
+};
- subtest "SHA1 generation by provider with `dgst` CLI" => sub {
+subtest "ML-DSA-44 signature generation and verification with `dgst` CLI" => sub {
+ if (disabled("ml-dsa")) {
+ plan tests => 1;
+ ok(1, "Skipped (ML-DSA not supported)");
+ return;
+ }
+ tsignverify("Ml-DSA-44",
+ srctop_file("test","testmldsa44.pem"),
+ srctop_file("test","testmldsa44pub.pem"));
+};
+subtest "ML-DSA-65 signature generation and verification with `dgst` CLI" => sub {
+ if (disabled("ml-dsa")) {
+ plan tests => 1;
+ ok(1, "Skipped (ML-DSA not supported)");
+ return;
+ }
+ tsignverify("Ml-DSA-65",
+ srctop_file("test","testmldsa65.pem"),
+ srctop_file("test","testmldsa65pub.pem"));
+};
+subtest "ML-DSA-87 signature generation and verification with `dgst` CLI" => sub {
+ if (disabled("ml-dsa")) {
plan tests => 1;
+ ok(1, "Skipped (ML-DSA not supported)");
+ return;
+ }
+ tsignverify("Ml-DSA-87",
+ srctop_file("test","testmldsa87.pem"),
+ srctop_file("test","testmldsa87pub.pem"));
+};
+
+subtest "SHA1 generation by provider with `dgst` CLI" => sub {
+ if (disabled("module")) {
+ plan tests => 1;
+ ok(1, "Skipped (dgst with provider not supported)");
+ return;
+ }
+ plan tests => 1;
- $ENV{OPENSSL_MODULES} = abs_path(bldtop_dir("test"));
+ $ENV{OPENSSL_MODULES} = abs_path(bldtop_dir("test"));
my $testdata = srctop_file('test', 'data.bin');
my @macdata = run(app(['openssl', 'dgst', '-sha1',
'-provider', "p_ossltest",
@@ -183,8 +235,7 @@ SKIP: {
chomp(@macdata);
my $expected = qr/SHA1\(\Q$testdata\E\)= 000102030405060708090a0b0c0d0e0f10111213/;
ok($macdata[0] =~ $expected, "SHA1: Check HASH value is as expected ($macdata[0]) vs ($expected)");
- }
-}
+};
subtest "HMAC generation with `dgst` CLI" => sub {
plan tests => 2;
@@ -357,33 +408,40 @@ subtest "SHAKE digest generation with no xoflen set `dgst` CLI" => sub {
ok(!run(app(['openssl', 'dgst', '-shake256', $testdata])), "SHAKE256 must fail without xoflen");
};
-SKIP: {
- skip "ECDSA is not supported by this OpenSSL build", 2
- if disabled("ec");
-
- subtest "signing with xoflen is not supported `dgst` CLI" => sub {
+subtest "signing with xoflen is not supported `dgst` CLI" => sub {
+ if (disabled("ec")) {
plan tests => 1;
- my $data_to_sign = srctop_file('test', 'data.bin');
-
- ok(!run(app(['openssl', 'dgst', '-shake256', '-xoflen', '64',
- '-sign', srctop_file("test","testec-p256.pem"),
- '-out', 'test.sig',
- srctop_file('test', 'data.bin')])),
- "Generating signature with xoflen should fail");
- };
+ ok(1, "Skipped (ECDSA not supported)");
+ return;
+ }
+ plan tests => 1;
+ my $data_to_sign = srctop_file('test', 'data.bin');
- skip "HMAC-DRBG-KDF is not supported by this OpenSSL build", 1
- if disabled("hmac-drbg-kdf");
+ ok(!run(app(['openssl', 'dgst', '-shake256', '-xoflen', '64',
+ '-sign', srctop_file("test","testec-p256.pem"),
+ '-out', 'test.sig',
+ srctop_file('test', 'data.bin')])),
+ "Generating signature with xoflen should fail");
+};
- subtest "signing using the nonce-type sigopt" => sub {
+subtest "signing using the nonce-type sigopt" => sub {
+ if (disabled("ec")) {
plan tests => 1;
- my $data_to_sign = srctop_file('test', 'data.bin');
-
- ok(run(app(['openssl', 'dgst', '-sha256',
- '-sign', srctop_file("test","testec-p256.pem"),
- '-out', 'test.sig',
- '-sigopt', 'nonce-type:1',
- srctop_file('test', 'data.bin')])),
- "Sign using the nonce-type sigopt");
+ ok(1, "Skipped (ECDSA not supported)");
+ return;
}
-}
+ if (disabled("hmac-drbg-kdf")) {
+ plan tests => 1;
+ ok(1, "Skipped (HMAC-DRBG-KDF not supported)");
+ return;
+ }
+ plan tests => 1;
+ my $data_to_sign = srctop_file('test', 'data.bin');
+
+ ok(run(app(['openssl', 'dgst', '-sha256',
+ '-sign', srctop_file("test","testec-p256.pem"),
+ '-out', 'test.sig',
+ '-sigopt', 'nonce-type:1',
+ srctop_file('test', 'data.bin')])),
+ "Sign using the nonce-type sigopt");
+};
diff --git a/test/recipes/20-test_pkeyutl.t b/test/recipes/20-test_pkeyutl.t
index 757c404387..bbc37b87f0 100644
--- a/test/recipes/20-test_pkeyutl.t
+++ b/test/recipes/20-test_pkeyutl.t
@@ -11,13 +11,13 @@ use warnings;
use File::Spec;
use File::Basename;
-use OpenSSL::Test qw/:DEFAULT srctop_file ok_nofips with/;
+use OpenSSL::Test qw/:DEFAULT srctop_file srctop_dir ok_nofips with/;
use OpenSSL::Test::Utils;
use File::Compare qw/compare_text compare/;
setup("test_pkeyutl");
-plan tests => 27;
+plan tests => 29;
# For the tests below we use the cert itself as the TBS file
@@ -214,9 +214,65 @@ SKIP: {
}
SKIP: {
- skip "EdDSA is not supported by this OpenSSL build", 4
+ skip "EdDSA is not supported by this OpenSSL build", 6
if disabled("ecx");
+ subtest "pkeyutl -rawin oneshot with file input (mmap or buffer path)" => sub {
+ my $data = srctop_file("test", "data.bin");
+ my $ed25519_key = srctop_file("test", "tested25519.pem");
+ my $ed25519_pub = srctop_file("test", "tested25519pub.pem");
+ my $ed448_key = srctop_file("test", "tested448.pem");
+ my $ed448_pub = srctop_file("test", "tested448pub.pem");
+
+ plan tests => 4;
+
+ # -in <file> for oneshot: uses mmap on Unix when supported, else buffer+BIO_read
+ ok(run(app(['openssl', 'pkeyutl', '-sign', '-rawin', '-inkey', $ed25519_key,
+ '-in', $data, '-out', 'rawin_file_ed25519.sig'])),
+ "Ed25519 -rawin sign from file");
+ ok(run(app(['openssl', 'pkeyutl', '-verify', '-rawin', '-pubin', '-inkey', $ed25519_pub,
+ '-sigfile', 'rawin_file_ed25519.sig', '-in', $data])),
+ "Ed25519 -rawin verify from file");
+ ok(run(app(['openssl', 'pkeyutl', '-sign', '-rawin', '-inkey', $ed448_key,
+ '-in', $data, '-out', 'rawin_file_ed448.sig'])),
+ "Ed448 -rawin sign from file");
+ ok(run(app(['openssl', 'pkeyutl', '-verify', '-rawin', '-pubin', '-inkey', $ed448_pub,
+ '-sigfile', 'rawin_file_ed448.sig', '-in', $data])),
+ "Ed448 -rawin verify from file");
+ };
+
+ subtest "pkeyutl -rawin oneshot: no buffer fallback when mmap path fails (Unix)" => sub {
+ if ($^O eq 'MSWin32') {
+ plan tests => 1;
+ ok(1, "Skipped (Unix/mmap only)");
+ return;
+ }
+ plan tests => 2;
+
+ # Use a directory with non-zero st_size so the mmap path is attempted
+ # (curdir "." often has st_size 0 on some FS and skips mmap).
+ my $ed25519_key = srctop_file("test", "tested25519.pem");
+ my $dir = srctop_dir("test");
+ my $stderr_file = "pkeyutl_nofallback_err.txt";
+
+ with({ exit_checker => sub { return shift != 0; } },
+ sub {
+ ok(run(app(['openssl', 'pkeyutl', '-sign', '-rawin', '-inkey', $ed25519_key,
+ '-in', $dir, '-out', 'nofallback.sig'],
+ stderr => $stderr_file)),
+ "pkeyutl -rawin with un-mmapable input fails (no fallback)");
+ });
+ if (open(my $fh, '<', $stderr_file)) {
+ my $err = do { local $/; <$fh> };
+ close($fh);
+ ok($err =~ /Error(?: opening file for memory mapping|: failed to use memory-mapped file)/,
+ "stderr mentions mmap failure");
+ } else {
+ ok(0, "could not read stderr file");
+ }
+ unlink($stderr_file) if -f $stderr_file;
+ };
+
subtest "Ed2559 CLI signature generation and verification" => sub {
tsignverify("Ed25519",
srctop_file("test","tested25519.pem"),