Added app for EVP_KDF
authorShane Lontis <shane.lontis@oracle.com>
Tue, 16 Apr 2019 10:10:04 +0000 (20:10 +1000)
committerRichard Levitte <levitte@openssl.org>
Wed, 24 Apr 2019 09:56:47 +0000 (11:56 +0200)
Reviewed-by: Tim Hudson <tjh@openssl.org>
Reviewed-by: Richard Levitte <levitte@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/8762)

apps/build.info
apps/kdf.c [new file with mode: 0644]
doc/man1/kdf.pod [new file with mode: 0644]
doc/man1/openssl.pod
doc/man3/EVP_KDF_CTX.pod
test/recipes/20-test_kdf.t [new file with mode: 0644]

index 30847a0..cbb70fc 100644 (file)
@@ -2,7 +2,7 @@
        qw(openssl.c
           asn1pars.c ca.c ciphers.c cms.c crl.c crl2p7.c dgst.c dhparam.c
           dsa.c dsaparam.c ec.c ecparam.c enc.c engine.c errstr.c gendsa.c
-          genpkey.c genrsa.c mac.c nseq.c ocsp.c passwd.c pkcs12.c pkcs7.c
+          genpkey.c genrsa.c kdf.c mac.c nseq.c ocsp.c passwd.c pkcs12.c pkcs7.c
           pkcs8.c pkey.c pkeyparam.c pkeyutl.c prime.c rand.c req.c rsa.c
           rsautl.c s_client.c s_server.c s_time.c sess_id.c smime.c speed.c
           spkac.c srp.c ts.c verify.c version.c x509.c rehash.c storeutl.c
diff --git a/apps/kdf.c b/apps/kdf.c
new file mode 100644 (file)
index 0000000..684fd44
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2019 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the OpenSSL license (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <string.h>
+
+#include "apps.h"
+#include "progs.h"
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/kdf.h>
+
+typedef enum OPTION_choice {
+    OPT_ERR = -1, OPT_EOF = 0, OPT_HELP,
+    OPT_KDFOPT, OPT_BIN, OPT_KEYLEN, OPT_OUT
+} OPTION_CHOICE;
+
+const OPTIONS kdf_options[] = {
+    {OPT_HELP_STR, 1, '-', "Usage: %s [options] kdf_name\n"},
+    {OPT_HELP_STR, 1, '-', "kdf_name\t KDF algorithm.\n"},
+    {"help", OPT_HELP, '-', "Display this summary"},
+    {"kdfopt", OPT_KDFOPT, 's', "KDF algorithm control parameters in n:v form. "
+                                "See 'Supported Controls' in the EVP_KDF_ docs"},
+    {"keylen", OPT_KEYLEN, 's', "The size of the output derived key"},
+    {"out", OPT_OUT, '>', "Output to filename rather than stdout"},
+    {"binary", OPT_BIN, '-', "Output in binary format (Default is hexadecimal "
+                             "output)"},
+    {NULL}
+};
+
+static int kdf_ctrl_string(EVP_KDF_CTX *ctx, const char *value)
+{
+    int rv;
+    char *stmp, *vtmp = NULL;
+
+    stmp = OPENSSL_strdup(value);
+    if (stmp == NULL)
+        return -1;
+    vtmp = strchr(stmp, ':');
+    if (vtmp != NULL) {
+        *vtmp = 0;
+        vtmp++;
+    }
+    rv = EVP_KDF_ctrl_str(ctx, stmp, vtmp);
+    OPENSSL_free(stmp);
+    return rv;
+}
+
+int kdf_main(int argc, char **argv)
+{
+    int ret = 1, i, id, out_bin = 0;
+    OPTION_CHOICE o;
+    STACK_OF(OPENSSL_STRING) *opts = NULL;
+    char *prog, *hexout = NULL;
+    const char *outfile = NULL;
+    unsigned char *dkm_bytes = NULL;
+    size_t dkm_len = 0;
+    BIO *out = NULL;
+    EVP_KDF_CTX *ctx = NULL;
+
+    prog = opt_init(argc, argv, kdf_options);
+    while ((o = opt_next()) != OPT_EOF) {
+        switch (o) {
+        default:
+opthelp:
+            BIO_printf(bio_err, "%s: Use -help for summary.\n", prog);
+            goto err;
+        case OPT_HELP:
+            opt_help(kdf_options);
+            ret = 0;
+            goto err;
+        case OPT_BIN:
+            out_bin = 1;
+            break;
+        case OPT_KEYLEN:
+            dkm_len = (size_t)atoi(opt_arg());
+            break;
+        case OPT_OUT:
+            outfile = opt_arg();
+            break;
+        case OPT_KDFOPT:
+            if (opts == NULL)
+                opts = sk_OPENSSL_STRING_new_null();
+            if (opts == NULL || !sk_OPENSSL_STRING_push(opts, opt_arg()))
+                goto opthelp;
+            break;
+        }
+    }
+    argc = opt_num_rest();
+    argv = opt_rest();
+
+    if (argc != 1) {
+        BIO_printf(bio_err, "Invalid number of extra arguments\n");
+        goto opthelp;
+    }
+
+    id = OBJ_sn2nid(argv[0]);
+    if (id == NID_undef) {
+        BIO_printf(bio_err, "Invalid KDF name %s\n", argv[0]);
+        goto opthelp;
+    }
+
+    ctx = EVP_KDF_CTX_new_id(id);
+    if (ctx == NULL)
+        goto err;
+
+    if (opts != NULL) {
+        for (i = 0; i < sk_OPENSSL_STRING_num(opts); i++) {
+            char *opt = sk_OPENSSL_STRING_value(opts, i);
+            if (kdf_ctrl_string(ctx, opt) <= 0) {
+                BIO_printf(bio_err, "KDF parameter error '%s'\n", opt);
+                ERR_print_errors(bio_err);
+                goto err;
+            }
+        }
+    }
+
+    out = bio_open_default(outfile, 'w', out_bin ? FORMAT_BINARY : FORMAT_TEXT);
+    if (out == NULL)
+        goto err;
+
+    if (dkm_len <= 0) {
+        BIO_printf(bio_err, "Invalid derived key length.\n");
+        goto err;
+    }
+    dkm_bytes = app_malloc(dkm_len, "out buffer");
+    if (dkm_bytes == NULL)
+        goto err;
+
+    if (!EVP_KDF_derive(ctx, dkm_bytes, dkm_len)) {
+        BIO_printf(bio_err, "EVP_KDF_derive failed\n");
+        goto err;
+    }
+
+    if (out_bin) {
+        BIO_write(out, dkm_bytes, dkm_len);
+    } else {
+        hexout = OPENSSL_buf2hexstr(dkm_bytes, dkm_len);
+        BIO_printf(out, "%s\n\n", hexout);
+    }
+
+    ret = 0;
+err:
+    if (ret != 0)
+        ERR_print_errors(bio_err);
+    OPENSSL_clear_free(dkm_bytes, dkm_len);
+    sk_OPENSSL_STRING_free(opts);
+    EVP_KDF_CTX_free(ctx);
+    BIO_free(out);
+    OPENSSL_free(hexout);
+    return ret;
+}
diff --git a/doc/man1/kdf.pod b/doc/man1/kdf.pod
new file mode 100644 (file)
index 0000000..0ff7762
--- /dev/null
@@ -0,0 +1,167 @@
+=pod
+
+=head1 NAME
+
+openssl-kdf,
+kdf - perform Key Derivation Function operations
+
+=head1 SYNOPSIS
+
+B<openssl kdf>
+[B<-help>]
+[B<-kdfopt> I<nm:v>]
+[B<-keylen> I<num>]
+[B<-out> I<filename>]
+[B<-binary>]
+I<kdf_name>
+
+=head1 DESCRIPTION
+
+The key derivation functions generate a derived key from either a secret or
+password.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-help>
+
+Print a usage message.
+
+=item B<-keylen> I<num>
+
+The output size of the derived key. This field is required.
+
+=item B<-out> I<filename>
+
+Filename to output to, or standard output by default.
+
+=item B<-binary>
+
+Output the derived key in binary form. Uses hexadecimal text format if not specified.
+
+=item B<-kdfopt> I<nm:v>
+
+Passes options to the KDF algorithm.
+A comprehensive list of controls can be found in the EVP_KDF_CTX implementation
+documentation.
+Common control strings used by EVP_KDF_ctrl_str() are:
+
+=over 4
+
+=item B<key:>I<string>
+
+Specifies the secret key as an alphanumeric string (use if the key contains
+printable characters only).
+The string length must conform to any restrictions of the KDF algorithm.
+A key must be specified for most KDF algorithms.
+
+=item B<hexkey:>I<string>
+
+Specifies the secret key in hexadecimal form (two hex digits per byte).
+The key length must conform to any restrictions of the KDF algorithm.
+A key must be specified for most KDF algorithms.
+
+=item B<pass:>I<string>
+
+Specifies the password as an alphanumeric string (use if the password contains
+printable characters only).
+The password must be specified for PBKDF2 and scrypt.
+
+=item B<hexpass:>I<string>
+
+Specifies the password in hexadecimal form (two hex digits per byte).
+The password must be specified for PBKDF2 and scrypt.
+
+=item B<digest:>I<string>
+
+Specifies the name of a digest as an alphanumeric string.
+To see the list of supported digests, use the command I<list -digest-commands>.
+
+=back
+
+=item I<kdf_name>
+
+Specifies the name of a supported KDF algorithm which will be used.
+The supported algorithms names are TLS1-PRF, HKDF, SSKDF, PBKDF2, SSHKDF and id-scrypt.
+
+=back
+
+=head1 EXAMPLES
+
+Use TLS1-PRF to create a hex-encoded derived key from a secret key and seed:
+
+    openssl kdf -keylen 16 -kdfopt digest:SHA256 -kdfopt key:secret \
+                -kdfopt seed:seed TLS1-PRF
+
+Use HKDF to create a hex-encoded derived key from a secret key, salt and info:
+
+    openssl kdf -keylen 10 -kdfopt digest:SHA256 -kdfopt key:secret \
+                -kdfopt salt:salt -kdfopt info:label HKDF
+
+Use SSKDF with KMAC to create a hex-encoded derived key from a secret key, salt and info:
+
+    openssl kdf -keylen 64 -kdfopt mac:KMAC128 -kdfopt maclen:20 \
+                -kdfopt hexkey:b74a149a161545 -kdfopt hexinfo:348a37a2 \
+                -kdfopt hexsalt:3638271ccd68a2 SSKDF
+
+Use SSKDF with HMAC to create a hex-encoded derived key from a secret key, salt and info:
+
+    openssl kdf -keylen 16 -kdfopt mac:HMAC -kdfopt digest:SHA256 \
+                -kdfopt hexkey:b74a149a -kdfopt hexinfo:348a37a2 \
+                -kdfopt hexsalt:3638271c SSKDF
+
+Use SSKDF with Hash to create a hex-encoded derived key from a secret key, salt and info:
+
+    openssl kdf -keylen 14 -kdfopt digest:SHA256 \
+                -kdfopt hexkey:6dbdc23f045488 \
+                -kdfopt hexinfo:a1b2c3d4 SSKDF
+
+Use SSHKDF to create a hex-encoded derived key from a secret key, hash and session_id:
+
+    openssl kdf -keylen 16 -kdfopt digest:SHA256 \
+                -kdfopt hexkey:0102030405 \
+                -kdfopt hexxcghash:06090A \
+                -kdfopt hexsession_id:01020304 \
+                -kdfopt type:A SSHKDF
+
+Use PBKDF2 to create a hex-encoded derived key from a password and salt:
+
+    openssl kdf -keylen 32 -kdfopt digest:SHA256 -kdfopt pass:password \
+                -kdfopt salt:salt -kdfopt iter:2 PBKDF2
+
+Use scrypt to create a hex-encoded derived key from a password and salt:
+
+    openssl kdf -keylen 64 -kdfopt pass:password -kdfopt salt:NaCl \
+                -kdfopt N:1024 -kdfopt r:8 -kdfopt p:16 \
+                -kdfopt maxmem_bytes:10485760 id-scrypt
+
+=head1 NOTES
+
+The KDF mechanisms that are available will depend on the options
+used when building OpenSSL.
+
+=head1 SEE ALSO
+
+L<EVP_KDF_CTX(3)>,
+L<EVP_KDF_SCRYPT(7)>
+L<EVP_KDF_TLS1_PRF(7)>
+L<EVP_KDF_PBKDF2(7)>
+L<EVP_KDF_HKDF(7)>
+L<EVP_KDF_SS(7)>
+L<EVP_KDF_SSHKDF(7)>
+
+=head1 HISTORY
+
+Added in OpenSSL 3.0
+
+=head1 COPYRIGHT
+
+Copyright 2019 The OpenSSL Project Authors. All Rights Reserved.
+
+Licensed under the OpenSSL license (the "License").  You may not use
+this file except in compliance with the License.  You can obtain a copy
+in the file LICENSE in the source distribution or at
+L<https://www.openssl.org/source/license.html>.
+
+=cut
index e41c76a..41d04da 100644 (file)
@@ -171,6 +171,10 @@ Generation of RSA Private Key. Superseded by L<genpkey(1)>.
 
 Display diverse information built into the OpenSSL libraries.
 
+=item B<kdf>
+
+Key Derivation Functions.
+
 =item B<mac>
 
 Message Authentication Code Calculation.
@@ -616,7 +620,7 @@ L<crl(1)>, L<crl2pkcs7(1)>, L<dgst(1)>,
 L<dhparam(1)>, L<dsa(1)>, L<dsaparam(1)>,
 L<ec(1)>, L<ecparam(1)>,
 L<enc(1)>, L<engine(1)>, L<errstr(1)>, L<gendsa(1)>, L<genpkey(1)>,
-L<genrsa(1)>, L<mac(1)>, L<nseq(1)>, L<ocsp(1)>,
+L<genrsa(1)>, L<kdf(1)>, L<mac(1)>, L<nseq(1)>, L<ocsp(1)>,
 L<passwd(1)>,
 L<pkcs12(1)>, L<pkcs7(1)>, L<pkcs8(1)>,
 L<pkey(1)>, L<pkeyparam(1)>, L<pkeyutl(1)>, L<prime(1)>,
@@ -636,7 +640,7 @@ manual pages.
 
 =head1 COPYRIGHT
 
-Copyright 2000-2018 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2000-2019 The OpenSSL Project Authors. All Rights Reserved.
 
 Licensed under the Apache License 2.0 (the "License").  You may not use
 this file except in compliance with the License.  You can obtain a copy
index 4ca8d94..f646528 100644 (file)
@@ -151,9 +151,9 @@ The value string is expected to be the name of a MAC.
 This control expects one argument: C<EVP_MD *md>
 
 For MAC implementations that use a message digest as an underlying computation
-algorithm, this control set what the digest algorithm should be.
+algorithm, this control sets what the digest algorithm should be.
 
-EVP_KDF_ctrl_str() type string: "md"
+EVP_KDF_ctrl_str() type string: "digest"
 
 The value string is expected to be the name of a digest.
 
@@ -232,6 +232,7 @@ L<EVP_KDF_TLS1_PRF(7)>
 L<EVP_KDF_PBKDF2(7)>
 L<EVP_KDF_HKDF(7)>
 L<EVP_KDF_SS(7)>
+L<EVP_KDF_SSHKDF(7)>
 
 =head1 HISTORY
 
diff --git a/test/recipes/20-test_kdf.t b/test/recipes/20-test_kdf.t
new file mode 100644 (file)
index 0000000..62cbb05
--- /dev/null
@@ -0,0 +1,78 @@
+#! /usr/bin/env perl
+# Copyright 2019 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the OpenSSL license (the "License").  You may not use
+# this file except in compliance with the License.  You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+
+use strict;
+use warnings;
+
+use OpenSSL::Test;
+use OpenSSL::Test::Utils;
+
+setup("test_kdf");
+
+my @kdf_tests = (
+    { cmd => [qw{openssl kdf -keylen 16 -kdfopt digest:SHA256 -kdfopt secret:secret -kdfopt seed:seed TLS1-PRF}],
+      expected => '8E:4D:93:25:30:D7:65:A0:AA:E9:74:C3:04:73:5E:CC',
+      desc => 'TLS1-PRF SHA256' },
+    { cmd => [qw{openssl kdf -keylen 16 -kdfopt digest:MD5-SHA1 -kdfopt secret:secret -kdfopt seed:seed TLS1-PRF}],
+      expected => '65:6F:31:CB:04:03:D6:51:E2:E8:71:F8:20:04:AB:BA',
+      desc => 'TLS1-PRF MD5-SHA1' },
+    { cmd => [qw{openssl kdf -keylen 10 -kdfopt digest:SHA256 -kdfopt key:secret -kdfopt salt:salt -kdfopt info:label HKDF}],
+      expected => '2a:c4:36:9f:52:59:96:f8:de:13',
+      desc => 'HKDF SHA256' },
+    { cmd => [qw{openssl kdf -keylen 32 -kdfopt digest:SHA256 -kdfopt pass:password -kdfopt salt:salt -kdfopt iter:2 PBKDF2}],
+      expected => 'ae:4d:0c:95:af:6b:46:d3:2d:0a:df:f9:28:f0:6d:d0:2a:30:3f:8e:f3:c2:51:df:d6:e2:d8:5a:95:47:4c:43',
+      desc => 'PBKDF2 SHA256'},
+    { cmd => [qw{openssl kdf -keylen 64 -kdfopt mac:KMAC128 -kdfopt maclen:20 -kdfopt hexkey:b74a149a161546f8c20b06ac4ed4 -kdfopt hexinfo:348a37a27ef1282f5f020dcc -kdfopt hexsalt:3638271ccd68a25dc24ecddd39ef3f89 SSKDF}],
+      expected => 'e9:c1:84:53:a0:62:b5:3b:db:fc:bb:5a:34:bd:b8:e5:e7:07:ee:bb:5d:d1:34:42:43:d8:cf:c2:c2:e6:33:2f:91:bd:a5:86:f3:7d:e4:8a:65:d4:c5:14:fd:ef:aa:1e:67:54:f3:73:d2:38:e1:95:ae:15:7e:1d:e8:14:98:03',
+      desc => 'SSKDF KMAC128'},
+    { cmd => [qw{openssl kdf -keylen 16 -kdfopt mac:HMAC -kdfopt digest:SHA256 -kdfopt hexkey:b74a149a161546f8c20b06ac4ed4 -kdfopt hexinfo:348a37a27ef1282f5f020dcc -kdfopt hexsalt:3638271ccd68a25dc24ecddd39ef3f89 SSKDF}],
+      expected => '44:f6:76:e8:5c:1b:1a:8b:bc:3d:31:92:18:63:1c:a3',
+      desc => 'SSKDF HMAC SHA256'},
+    { cmd => [qw{openssl kdf -keylen 14 -kdfopt digest:SHA224 -kdfopt hexkey:6dbdc23f045488e4062757b06b9ebae183fc5a5946d80db93fec6f62ec07e3727f0126aed12ce4b262f47d48d54287f81d474c7c3b1850e9 -kdfopt hexinfo:a1b2c3d4e54341565369643c832e9849dcdba71e9a3139e606e095de3c264a66e98a165854cd07989b1ee0ec3f8dbe SSKDF}],
+      expected => 'a4:62:de:16:a8:9d:e8:46:6e:f5:46:0b:47:b8',
+      desc => 'SSKDF HASH SHA224'},
+    { cmd => [qw{openssl kdf -keylen 16 -kdfopt md:SHA256 -kdfopt hexkey:0102030405 -kdfopt hexxcghash:06090A -kdfopt hexsession_id:01020304 -kdfopt type:A SSHKDF}],
+    expected => '5C:49:94:47:3B:B1:53:3A:58:EB:19:42:04:D3:78:16',
+    desc => 'SSHKDF SHA256'},
+);
+
+my @scrypt_tests = (
+    { cmd => [qw{openssl kdf -keylen 64 -kdfopt pass:password -kdfopt salt:NaCl -kdfopt N:1024 -kdfopt r:8 -kdfopt p:16 -kdfopt maxmem_bytes:10485760 id-scrypt}],
+      expected => 'fd:ba:be:1c:9d:34:72:00:78:56:e7:19:0d:01:e9:fe:7c:6a:d7:cb:c8:23:78:30:e7:73:76:63:4b:37:31:62:2e:af:30:d9:2e:22:a3:88:6f:f1:09:27:9d:98:30:da:c7:27:af:b9:4a:83:ee:6d:83:60:cb:df:a2:cc:06:40',
+      desc => 'SCRYPT' },
+);
+
+push @kdf_tests, @scrypt_tests unless disabled("scrypt");
+
+plan tests => scalar @kdf_tests;
+
+foreach (@kdf_tests) {
+    ok(compareline($_->{cmd}, $_->{expected}), $_->{desc});
+}
+
+# Check that the stdout output matches the expected value.
+sub compareline {
+    my ($cmdarray, $expect) = @_;
+    if (defined($expect)) {
+        $expect = uc $expect;
+    }
+
+    my @lines = run(app($cmdarray), capture => 1);
+
+    if (defined($expect)) {
+        if ($lines[0] =~ m|^\Q${expect}\E\R$|) {
+            return 1;
+        } else {
+            print "Got: $lines[0]";
+            print "Exp: $expect\n";
+            return 0;
+        }
+    }
+    return 0;
+}