Enable pkeyutl to use Ed448 and Ed25519

With the recent addition of the -rawin option it should be possible for
pkeyutl to sign and verify with Ed448 and Ed2559. The main remaining
stumbling block is that those algorirthms only support "oneshot" operation.
This commit enables pkeyutl to handle that.

Reviewed-by: Nicola Tuveri <nic.tuv@gmail.com>
(Merged from https://github.com/openssl/openssl/pull/8431)
This commit is contained in:
Matt Caswell 2019-03-07 10:37:34 +00:00
parent 6bc62a620e
commit ee633ace73
3 changed files with 164 additions and 70 deletions

View file

@ -13,6 +13,9 @@
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/pem.h> #include <openssl/pem.h>
#include <openssl/evp.h> #include <openssl/evp.h>
#ifndef OPENSSL_NO_POSIX_IO
# include <sys/stat.h>
#endif
#define KEY_NONE 0 #define KEY_NONE 0
#define KEY_PRIVKEY 1 #define KEY_PRIVKEY 1
@ -22,7 +25,7 @@
static EVP_PKEY_CTX *init_ctx(const char *kdfalg, int *pkeysize, static EVP_PKEY_CTX *init_ctx(const char *kdfalg, int *pkeysize,
const char *keyfile, int keyform, int key_type, const char *keyfile, int keyform, int key_type,
char *passinarg, int pkey_op, ENGINE *e, char *passinarg, int pkey_op, ENGINE *e,
const int impl, EVP_PKEY **ppkey); const int impl, int rawin, EVP_PKEY **ppkey);
static int setup_peer(EVP_PKEY_CTX *ctx, int peerform, const char *file, static int setup_peer(EVP_PKEY_CTX *ctx, int peerform, const char *file,
ENGINE *e); ENGINE *e);
@ -33,7 +36,7 @@ static int do_keyop(EVP_PKEY_CTX *ctx, int pkey_op,
static int do_raw_keyop(int pkey_op, EVP_PKEY_CTX *ctx, static int do_raw_keyop(int pkey_op, EVP_PKEY_CTX *ctx,
const EVP_MD *md, EVP_PKEY *pkey, BIO *in, const EVP_MD *md, EVP_PKEY *pkey, BIO *in,
unsigned char *sig, int siglen, int filesize, unsigned char *sig, int siglen,
unsigned char **out, size_t *poutlen); unsigned char **out, size_t *poutlen);
typedef enum OPTION_choice { typedef enum OPTION_choice {
@ -109,6 +112,7 @@ int pkeyutl_main(int argc, char **argv)
STACK_OF(OPENSSL_STRING) *pkeyopts_passin = NULL; STACK_OF(OPENSSL_STRING) *pkeyopts_passin = NULL;
int rawin = 0; int rawin = 0;
const EVP_MD *md = NULL; const EVP_MD *md = NULL;
int filesize = -1;
prog = opt_init(argc, argv, pkeyutl_options); prog = opt_init(argc, argv, pkeyutl_options);
while ((o = opt_next()) != OPT_EOF) { while ((o = opt_next()) != OPT_EOF) {
@ -264,7 +268,7 @@ int pkeyutl_main(int argc, char **argv)
goto opthelp; goto opthelp;
} }
ctx = init_ctx(kdfalg, &keysize, inkey, keyform, key_type, ctx = init_ctx(kdfalg, &keysize, inkey, keyform, key_type,
passinarg, pkey_op, e, engine_impl, &pkey); passinarg, pkey_op, e, engine_impl, rawin, &pkey);
if (ctx == NULL) { if (ctx == NULL) {
BIO_printf(bio_err, "%s: Error initializing context\n", prog); BIO_printf(bio_err, "%s: Error initializing context\n", prog);
ERR_print_errors(bio_err); ERR_print_errors(bio_err);
@ -344,6 +348,15 @@ int pkeyutl_main(int argc, char **argv)
if (pkey_op != EVP_PKEY_OP_DERIVE) { if (pkey_op != EVP_PKEY_OP_DERIVE) {
in = bio_open_default(infile, 'r', FORMAT_BINARY); in = bio_open_default(infile, 'r', FORMAT_BINARY);
#ifndef OPENSSL_NO_POSIX_IO
if (infile != NULL)
{
struct stat st;
if (stat(infile, &st) == 0 && st.st_size <= INT_MAX)
filesize = (int)st.st_size;
}
#endif
if (in == NULL) if (in == NULL)
goto end; goto end;
} }
@ -399,7 +412,7 @@ int pkeyutl_main(int argc, char **argv)
if (pkey_op == EVP_PKEY_OP_VERIFY) { if (pkey_op == EVP_PKEY_OP_VERIFY) {
if (rawin) { if (rawin) {
rv = do_raw_keyop(pkey_op, ctx, md, pkey, in, sig, siglen, rv = do_raw_keyop(pkey_op, ctx, md, pkey, in, filesize, sig, siglen,
NULL, 0); NULL, 0);
} else { } else {
rv = EVP_PKEY_verify(ctx, sig, (size_t)siglen, rv = EVP_PKEY_verify(ctx, sig, (size_t)siglen,
@ -419,7 +432,7 @@ int pkeyutl_main(int argc, char **argv)
} else { } else {
if (rawin) { if (rawin) {
/* rawin allocates the buffer in do_raw_keyop() */ /* rawin allocates the buffer in do_raw_keyop() */
rv = do_raw_keyop(pkey_op, ctx, md, pkey, in, NULL, 0, rv = do_raw_keyop(pkey_op, ctx, md, pkey, in, filesize, NULL, 0,
&buf_out, (size_t *)&buf_outlen); &buf_out, (size_t *)&buf_outlen);
} else { } else {
rv = do_keyop(ctx, pkey_op, NULL, (size_t *)&buf_outlen, rv = do_keyop(ctx, pkey_op, NULL, (size_t *)&buf_outlen,
@ -468,7 +481,8 @@ int pkeyutl_main(int argc, char **argv)
static EVP_PKEY_CTX *init_ctx(const char *kdfalg, int *pkeysize, static EVP_PKEY_CTX *init_ctx(const char *kdfalg, int *pkeysize,
const char *keyfile, int keyform, int key_type, const char *keyfile, int keyform, int key_type,
char *passinarg, int pkey_op, ENGINE *e, char *passinarg, int pkey_op, ENGINE *e,
const int engine_impl, EVP_PKEY **ppkey) const int engine_impl, int rawin,
EVP_PKEY **ppkey)
{ {
EVP_PKEY *pkey = NULL; EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *ctx = NULL; EVP_PKEY_CTX *ctx = NULL;
@ -554,30 +568,39 @@ static EVP_PKEY_CTX *init_ctx(const char *kdfalg, int *pkeysize,
if (ctx == NULL) if (ctx == NULL)
goto end; goto end;
switch (pkey_op) { /*
case EVP_PKEY_OP_SIGN: * If rawin then we don't need to actually initialise the EVP_PKEY_CTX
rv = EVP_PKEY_sign_init(ctx); * itself. That will get initialised during EVP_DigestSignInit or
break; * EVP_DigestVerifyInit.
*/
if (rawin) {
rv = 1;
} else {
switch (pkey_op) {
case EVP_PKEY_OP_SIGN:
rv = EVP_PKEY_sign_init(ctx);
break;
case EVP_PKEY_OP_VERIFY: case EVP_PKEY_OP_VERIFY:
rv = EVP_PKEY_verify_init(ctx); rv = EVP_PKEY_verify_init(ctx);
break; break;
case EVP_PKEY_OP_VERIFYRECOVER: case EVP_PKEY_OP_VERIFYRECOVER:
rv = EVP_PKEY_verify_recover_init(ctx); rv = EVP_PKEY_verify_recover_init(ctx);
break; break;
case EVP_PKEY_OP_ENCRYPT: case EVP_PKEY_OP_ENCRYPT:
rv = EVP_PKEY_encrypt_init(ctx); rv = EVP_PKEY_encrypt_init(ctx);
break; break;
case EVP_PKEY_OP_DECRYPT: case EVP_PKEY_OP_DECRYPT:
rv = EVP_PKEY_decrypt_init(ctx); rv = EVP_PKEY_decrypt_init(ctx);
break; break;
case EVP_PKEY_OP_DERIVE: case EVP_PKEY_OP_DERIVE:
rv = EVP_PKEY_derive_init(ctx); rv = EVP_PKEY_derive_init(ctx);
break; break;
}
} }
if (rv <= 0) { if (rv <= 0) {
@ -649,13 +672,14 @@ static int do_keyop(EVP_PKEY_CTX *ctx, int pkey_op,
static int do_raw_keyop(int pkey_op, EVP_PKEY_CTX *ctx, static int do_raw_keyop(int pkey_op, EVP_PKEY_CTX *ctx,
const EVP_MD *md, EVP_PKEY *pkey, BIO *in, const EVP_MD *md, EVP_PKEY *pkey, BIO *in,
unsigned char *sig, int siglen, int filesize, unsigned char *sig, int siglen,
unsigned char **out, size_t *poutlen) unsigned char **out, size_t *poutlen)
{ {
int rv = 0; int rv = 0;
EVP_MD_CTX *mctx = NULL; EVP_MD_CTX *mctx = NULL;
unsigned char tbuf[TBUF_MAXSIZE]; unsigned char tbuf[TBUF_MAXSIZE];
int tbuf_len = 0; unsigned char *mbuf = NULL;
int buf_len = 0;
if ((mctx = EVP_MD_CTX_new()) == NULL) { if ((mctx = EVP_MD_CTX_new()) == NULL) {
BIO_printf(bio_err, "Error: out of memory\n"); BIO_printf(bio_err, "Error: out of memory\n");
@ -663,19 +687,58 @@ static int do_raw_keyop(int pkey_op, EVP_PKEY_CTX *ctx,
} }
EVP_MD_CTX_set_pkey_ctx(mctx, ctx); EVP_MD_CTX_set_pkey_ctx(mctx, ctx);
/* Some algorithms only support oneshot digests */
if (EVP_PKEY_id(pkey) == EVP_PKEY_ED25519
|| EVP_PKEY_id(pkey) == EVP_PKEY_ED448) {
if (filesize < 0) {
BIO_printf(bio_err,
"Error: unable to determine file size for oneshot operation\n");
return rv;
}
mbuf = app_malloc(filesize, "oneshot sign/verify buffer");
switch(pkey_op) {
case EVP_PKEY_OP_VERIFY:
if (EVP_DigestVerifyInit(mctx, NULL, md, NULL, pkey) != 1)
goto end;
buf_len = BIO_read(in, mbuf, filesize);
if (buf_len != filesize) {
BIO_printf(bio_err, "Error reading raw input data\n");
goto end;
}
rv = EVP_DigestVerify(mctx, sig, (size_t)siglen, mbuf, buf_len);
break;
case EVP_PKEY_OP_SIGN:
if (EVP_DigestSignInit(mctx, NULL, md, NULL, pkey) != 1)
goto end;
buf_len = BIO_read(in, mbuf, filesize);
if (buf_len != filesize) {
BIO_printf(bio_err, "Error reading raw input data\n");
goto end;
}
rv = EVP_DigestSign(mctx, NULL, poutlen, mbuf, buf_len);
if (rv == 1 && out != NULL) {
*out = app_malloc(*poutlen, "buffer output");
rv = EVP_DigestSign(mctx, *out, poutlen, mbuf, buf_len);
}
break;
}
OPENSSL_free(mbuf);
goto end;
}
switch(pkey_op) { switch(pkey_op) {
case EVP_PKEY_OP_VERIFY: case EVP_PKEY_OP_VERIFY:
if (EVP_DigestVerifyInit(mctx, NULL, md, NULL, pkey) != 1) if (EVP_DigestVerifyInit(mctx, NULL, md, NULL, pkey) != 1)
goto end; goto end;
for (;;) { for (;;) {
tbuf_len = BIO_read(in, tbuf, TBUF_MAXSIZE); buf_len = BIO_read(in, tbuf, TBUF_MAXSIZE);
if (tbuf_len == 0) if (buf_len == 0)
break; break;
if (tbuf_len < 0) { if (buf_len < 0) {
BIO_printf(bio_err, "Error reading raw input data\n"); BIO_printf(bio_err, "Error reading raw input data\n");
goto end; goto end;
} }
rv = EVP_DigestVerifyUpdate(mctx, tbuf, (size_t)tbuf_len); rv = EVP_DigestVerifyUpdate(mctx, tbuf, (size_t)buf_len);
if (rv != 1) { if (rv != 1) {
BIO_printf(bio_err, "Error verifying raw input data\n"); BIO_printf(bio_err, "Error verifying raw input data\n");
goto end; goto end;
@ -687,14 +750,14 @@ static int do_raw_keyop(int pkey_op, EVP_PKEY_CTX *ctx,
if (EVP_DigestSignInit(mctx, NULL, md, NULL, pkey) != 1) if (EVP_DigestSignInit(mctx, NULL, md, NULL, pkey) != 1)
goto end; goto end;
for (;;) { for (;;) {
tbuf_len = BIO_read(in, tbuf, TBUF_MAXSIZE); buf_len = BIO_read(in, tbuf, TBUF_MAXSIZE);
if (tbuf_len == 0) if (buf_len == 0)
break; break;
if (tbuf_len < 0) { if (buf_len < 0) {
BIO_printf(bio_err, "Error reading raw input data\n"); BIO_printf(bio_err, "Error reading raw input data\n");
goto end; goto end;
} }
rv = EVP_DigestSignUpdate(mctx, tbuf, (size_t)tbuf_len); rv = EVP_DigestSignUpdate(mctx, tbuf, (size_t)buf_len);
if (rv != 1) { if (rv != 1) {
BIO_printf(bio_err, "Error signing raw input data\n"); BIO_printf(bio_err, "Error signing raw input data\n");
goto end; goto end;

View file

@ -62,7 +62,7 @@ if this option is not specified.
This indicates that the input data is raw data, which is not hashed by any This indicates that the input data is raw data, which is not hashed by any
message digest algorithm. The user can specify a digest algorithm by using message digest algorithm. The user can specify a digest algorithm by using
the B<-digest> option. This option can only be used with B<-sign> and the B<-digest> option. This option can only be used with B<-sign> and
B<-verify>. B<-verify> and must be used with the Ed25519 and Ed448 algorithms.
=item B<-digest algorithm> =item B<-digest algorithm>
@ -216,21 +216,18 @@ hash the input data. It is used (by some algorithms) for sanity-checking the
lengths of data passed in to the B<pkeyutl> and for creating the structures that lengths of data passed in to the B<pkeyutl> and for creating the structures that
make up the signature (e.g. B<DigestInfo> in RSASSA PKCS#1 v1.5 signatures). make up the signature (e.g. B<DigestInfo> in RSASSA PKCS#1 v1.5 signatures).
This utility does not hash the input data but rather it will use the data This utility does not hash the input data (except where -rawin is used) but
directly as input to the signature algorithm. Depending on the key type, rather it will use the data directly as input to the signature algorithm.
signature type, and mode of padding, the maximum acceptable lengths of input Depending on the key type, signature type, and mode of padding, the maximum
data differ. The signed data can't be longer than the key modulus with RSA. In acceptable lengths of input data differ. The signed data can't be longer than
case of ECDSA and DSA the data shouldn't be longer than the field the key modulus with RSA. In case of ECDSA and DSA the data shouldn't be longer
size, otherwise it will be silently truncated to the field size. In any event than the field size, otherwise it will be silently truncated to the field size.
the input size must not be larger than the largest supported digest size. In any event the input size must not be larger than the largest supported digest
size.
In other words, if the value of digest is B<sha1> the input should be the 20 In other words, if the value of digest is B<sha1> the input should be the 20
bytes long binary encoding of the SHA-1 hash function output. bytes long binary encoding of the SHA-1 hash function output.
The Ed25519 and Ed448 signature algorithms are not supported by this utility.
They accept non-hashed input, but this utility can only be used to sign hashed
input.
=head1 RSA ALGORITHM =head1 RSA ALGORITHM
The RSA algorithm generally supports the encrypt, decrypt, sign, The RSA algorithm generally supports the encrypt, decrypt, sign,
@ -319,6 +316,18 @@ this digest is assumed by default.
The X25519 and X448 algorithms support key derivation only. Currently there are The X25519 and X448 algorithms support key derivation only. Currently there are
no additional options. no additional options.
=head1 Ed25519 and Ed448 ALGORITHMS
These algorithms only support signing and verifying. OpenSSL only implements the
"pure" variants of these algorithms so raw data can be passed directly to them
without hashing them first. The option "-rawin" must be used with these
algorithms with no "-digest" specified. Additionally OpenSSL only supports
"oneshot" operation with these algorithms. This means that the entire file to
be signed/verified must be read into memory before processing it. Signing or
Verifying very large files should be avoided. Additionally the size of the file
must be known for this to work. If the size of the file cannot be determined
(for example if the input is stdin) then the sign or verify operation will fail.
=head1 SM2 =head1 SM2
The SM2 algorithm supports sign, verify, encrypt and decrypt operations. For The SM2 algorithm supports sign, verify, encrypt and decrypt operations. For

View file

@ -15,34 +15,56 @@ use OpenSSL::Test::Utils;
setup("test_pkeyutl"); setup("test_pkeyutl");
plan tests => 2; plan tests => 6;
sub sign # For the tests below we use the cert itself as the TBS file
{
# Utilize the sm2.crt as the TBS file
return run(app(([ 'openssl', 'pkeyutl', '-sign',
'-in', srctop_file('test', 'certs', 'sm2.crt'),
'-inkey', srctop_file('test', 'certs', 'sm2.key'),
'-out', 'signature.sm2', '-rawin',
'-digest', 'sm3', '-pkeyopt', 'sm2_id:someid'])));
}
sub verify
{
# Utilize the sm2.crt as the TBS file
return run(app(([ 'openssl', 'pkeyutl', '-verify', '-certin',
'-in', srctop_file('test', 'certs', 'sm2.crt'),
'-inkey', srctop_file('test', 'certs', 'sm2.crt'),
'-sigfile', 'signature.sm2', '-rawin',
'-digest', 'sm3', '-pkeyopt', 'sm2_id:someid'])));
}
SKIP: { SKIP: {
skip "Skipping tests that require EC, SM2 or SM3", 2 skip "Skipping tests that require EC, SM2 or SM3", 2
if disabled("ec") || disabled("sm2") || disabled("sm3"); if disabled("ec") || disabled("sm2") || disabled("sm3");
ok(sign, "Sign a piece of data using SM2"); # SM2
ok(verify, "Verify an SM2 signature against a piece of data"); ok(run(app(([ 'openssl', 'pkeyutl', '-sign',
'-in', srctop_file('test', 'certs', 'sm2.crt'),
'-inkey', srctop_file('test', 'certs', 'sm2.key'),
'-out', 'signature.dat', '-rawin',
'-digest', 'sm3', '-pkeyopt', 'sm2_id:someid']))),
"Sign a piece of data using SM2");
ok(run(app(([ 'openssl', 'pkeyutl', '-verify', '-certin',
'-in', srctop_file('test', 'certs', 'sm2.crt'),
'-inkey', srctop_file('test', 'certs', 'sm2.crt'),
'-sigfile', 'signature.dat', '-rawin',
'-digest', 'sm3', '-pkeyopt', 'sm2_id:someid']))),
"Verify an SM2 signature against a piece of data");
} }
unlink 'signature.sm2'; SKIP: {
skip "Skipping tests that require EC", 4
if disabled("ec");
# Ed25519
ok(run(app(([ 'openssl', 'pkeyutl', '-sign', '-in',
srctop_file('test', 'certs', 'server-ed25519-cert.pem'),
'-inkey', srctop_file('test', 'certs', 'server-ed25519-key.pem'),
'-out', 'signature.dat', '-rawin']))),
"Sign a piece of data using Ed25519");
ok(run(app(([ 'openssl', 'pkeyutl', '-verify', '-certin', '-in',
srctop_file('test', 'certs', 'server-ed25519-cert.pem'),
'-inkey', srctop_file('test', 'certs', 'server-ed25519-cert.pem'),
'-sigfile', 'signature.dat', '-rawin']))),
"Verify an Ed25519 signature against a piece of data");
# Ed448
ok(run(app(([ 'openssl', 'pkeyutl', '-sign', '-in',
srctop_file('test', 'certs', 'server-ed448-cert.pem'),
'-inkey', srctop_file('test', 'certs', 'server-ed448-key.pem'),
'-out', 'signature.dat', '-rawin']))),
"Sign a piece of data using Ed448");
ok(run(app(([ 'openssl', 'pkeyutl', '-verify', '-certin', '-in',
srctop_file('test', 'certs', 'server-ed448-cert.pem'),
'-inkey', srctop_file('test', 'certs', 'server-ed448-cert.pem'),
'-sigfile', 'signature.dat', '-rawin']))),
"Verify an Ed448 signature against a piece of data");
}
unlink 'signature.dat';