diff --git a/apps/pkeyutl.c b/apps/pkeyutl.c index c084525cd5..0c27589b1b 100644 --- a/apps/pkeyutl.c +++ b/apps/pkeyutl.c @@ -13,6 +13,9 @@ #include #include #include +#ifndef OPENSSL_NO_POSIX_IO +# include +#endif #define KEY_NONE 0 #define KEY_PRIVKEY 1 @@ -22,7 +25,7 @@ static EVP_PKEY_CTX *init_ctx(const char *kdfalg, int *pkeysize, const char *keyfile, int keyform, int key_type, 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, 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, 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); typedef enum OPTION_choice { @@ -109,6 +112,7 @@ int pkeyutl_main(int argc, char **argv) STACK_OF(OPENSSL_STRING) *pkeyopts_passin = NULL; int rawin = 0; const EVP_MD *md = NULL; + int filesize = -1; prog = opt_init(argc, argv, pkeyutl_options); while ((o = opt_next()) != OPT_EOF) { @@ -264,7 +268,7 @@ int pkeyutl_main(int argc, char **argv) goto opthelp; } 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) { BIO_printf(bio_err, "%s: Error initializing context\n", prog); ERR_print_errors(bio_err); @@ -344,6 +348,15 @@ int pkeyutl_main(int argc, char **argv) if (pkey_op != EVP_PKEY_OP_DERIVE) { 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) goto end; } @@ -399,7 +412,7 @@ int pkeyutl_main(int argc, char **argv) if (pkey_op == EVP_PKEY_OP_VERIFY) { 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); } else { rv = EVP_PKEY_verify(ctx, sig, (size_t)siglen, @@ -419,7 +432,7 @@ int pkeyutl_main(int argc, char **argv) } else { if (rawin) { /* 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); } else { 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, const char *keyfile, int keyform, int key_type, 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_CTX *ctx = NULL; @@ -554,30 +568,39 @@ static EVP_PKEY_CTX *init_ctx(const char *kdfalg, int *pkeysize, if (ctx == NULL) goto end; - switch (pkey_op) { - case EVP_PKEY_OP_SIGN: - rv = EVP_PKEY_sign_init(ctx); - break; + /* + * If rawin then we don't need to actually initialise the EVP_PKEY_CTX + * itself. That will get initialised during EVP_DigestSignInit or + * 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: - rv = EVP_PKEY_verify_init(ctx); - break; + case EVP_PKEY_OP_VERIFY: + rv = EVP_PKEY_verify_init(ctx); + break; - case EVP_PKEY_OP_VERIFYRECOVER: - rv = EVP_PKEY_verify_recover_init(ctx); - break; + case EVP_PKEY_OP_VERIFYRECOVER: + rv = EVP_PKEY_verify_recover_init(ctx); + break; - case EVP_PKEY_OP_ENCRYPT: - rv = EVP_PKEY_encrypt_init(ctx); - break; + case EVP_PKEY_OP_ENCRYPT: + rv = EVP_PKEY_encrypt_init(ctx); + break; - case EVP_PKEY_OP_DECRYPT: - rv = EVP_PKEY_decrypt_init(ctx); - break; + case EVP_PKEY_OP_DECRYPT: + rv = EVP_PKEY_decrypt_init(ctx); + break; - case EVP_PKEY_OP_DERIVE: - rv = EVP_PKEY_derive_init(ctx); - break; + case EVP_PKEY_OP_DERIVE: + rv = EVP_PKEY_derive_init(ctx); + break; + } } 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, 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) { int rv = 0; EVP_MD_CTX *mctx = NULL; unsigned char tbuf[TBUF_MAXSIZE]; - int tbuf_len = 0; + unsigned char *mbuf = NULL; + int buf_len = 0; if ((mctx = EVP_MD_CTX_new()) == NULL) { 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); + /* 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) { case EVP_PKEY_OP_VERIFY: if (EVP_DigestVerifyInit(mctx, NULL, md, NULL, pkey) != 1) goto end; for (;;) { - tbuf_len = BIO_read(in, tbuf, TBUF_MAXSIZE); - if (tbuf_len == 0) + buf_len = BIO_read(in, tbuf, TBUF_MAXSIZE); + if (buf_len == 0) break; - if (tbuf_len < 0) { + if (buf_len < 0) { BIO_printf(bio_err, "Error reading raw input data\n"); goto end; } - rv = EVP_DigestVerifyUpdate(mctx, tbuf, (size_t)tbuf_len); + rv = EVP_DigestVerifyUpdate(mctx, tbuf, (size_t)buf_len); if (rv != 1) { BIO_printf(bio_err, "Error verifying raw input data\n"); 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) goto end; for (;;) { - tbuf_len = BIO_read(in, tbuf, TBUF_MAXSIZE); - if (tbuf_len == 0) + buf_len = BIO_read(in, tbuf, TBUF_MAXSIZE); + if (buf_len == 0) break; - if (tbuf_len < 0) { + if (buf_len < 0) { BIO_printf(bio_err, "Error reading raw input data\n"); goto end; } - rv = EVP_DigestSignUpdate(mctx, tbuf, (size_t)tbuf_len); + rv = EVP_DigestSignUpdate(mctx, tbuf, (size_t)buf_len); if (rv != 1) { BIO_printf(bio_err, "Error signing raw input data\n"); goto end; diff --git a/doc/man1/pkeyutl.pod b/doc/man1/pkeyutl.pod index c566f6d892..13af3277bf 100644 --- a/doc/man1/pkeyutl.pod +++ b/doc/man1/pkeyutl.pod @@ -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 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 -B<-verify>. +B<-verify> and must be used with the Ed25519 and Ed448 algorithms. =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 and for creating the structures that make up the signature (e.g. B in RSASSA PKCS#1 v1.5 signatures). -This utility does not hash the input data but rather it will use the data -directly as input to the signature algorithm. Depending on the key type, -signature type, and mode of padding, the maximum acceptable lengths of input -data differ. The signed data can't be longer than the key modulus with RSA. In -case of ECDSA and DSA the data shouldn't be longer than the field -size, otherwise it will be silently truncated to the field size. In any event -the input size must not be larger than the largest supported digest size. +This utility does not hash the input data (except where -rawin is used) but +rather it will use the data directly as input to the signature algorithm. +Depending on the key type, signature type, and mode of padding, the maximum +acceptable lengths of input data differ. The signed data can't be longer than +the key modulus with RSA. In case of ECDSA and DSA the data shouldn't be longer +than the field size, otherwise it will be silently truncated to the field 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 the input should be the 20 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 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 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 The SM2 algorithm supports sign, verify, encrypt and decrypt operations. For diff --git a/test/recipes/20-test_pkeyutl.t b/test/recipes/20-test_pkeyutl.t index 14575301fa..21f4e629bf 100644 --- a/test/recipes/20-test_pkeyutl.t +++ b/test/recipes/20-test_pkeyutl.t @@ -15,34 +15,56 @@ use OpenSSL::Test::Utils; setup("test_pkeyutl"); -plan tests => 2; +plan tests => 6; -sub sign -{ - # 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']))); -} +# For the tests below we use the cert itself as the TBS file SKIP: { skip "Skipping tests that require EC, SM2 or SM3", 2 if disabled("ec") || disabled("sm2") || disabled("sm3"); - ok(sign, "Sign a piece of data using SM2"); - ok(verify, "Verify an SM2 signature against a piece of data"); + # SM2 + 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';