summaryrefslogtreecommitdiffstats
path: root/src/lib/crypto/s2k.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/crypto/s2k.cpp')
-rw-r--r--src/lib/crypto/s2k.cpp203
1 files changed, 203 insertions, 0 deletions
diff --git a/src/lib/crypto/s2k.cpp b/src/lib/crypto/s2k.cpp
new file mode 100644
index 0000000..ede7965
--- /dev/null
+++ b/src/lib/crypto/s2k.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2017-2022 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include "config.h"
+#ifndef _MSC_VER
+#include <sys/time.h>
+#else
+#include "uniwin.h"
+#endif
+
+#include "crypto/s2k.h"
+#include "defaults.h"
+#include "rnp.h"
+#include "types.h"
+#include "utils.h"
+#ifdef CRYPTO_BACKEND_BOTAN
+#include <botan/ffi.h>
+#include "hash_botan.hpp"
+#endif
+
+bool
+pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysize)
+{
+ uint8_t *saltptr = NULL;
+ unsigned iterations = 1;
+
+ switch (s2k->specifier) {
+ case PGP_S2KS_SIMPLE:
+ break;
+ case PGP_S2KS_SALTED:
+ saltptr = s2k->salt;
+ break;
+ case PGP_S2KS_ITERATED_AND_SALTED:
+ saltptr = s2k->salt;
+ if (s2k->iterations < 256) {
+ iterations = pgp_s2k_decode_iterations(s2k->iterations);
+ } else {
+ iterations = s2k->iterations;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ if (pgp_s2k_iterated(s2k->hash_alg, key, keysize, password, saltptr, iterations)) {
+ RNP_LOG("s2k failed");
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef CRYPTO_BACKEND_BOTAN
+int
+pgp_s2k_iterated(pgp_hash_alg_t alg,
+ uint8_t * out,
+ size_t output_len,
+ const char * password,
+ const uint8_t *salt,
+ size_t iterations)
+{
+ char s2k_algo_str[128];
+ snprintf(s2k_algo_str,
+ sizeof(s2k_algo_str),
+ "OpenPGP-S2K(%s)",
+ rnp::Hash_Botan::name_backend(alg));
+
+ return botan_pwdhash(s2k_algo_str,
+ iterations,
+ 0,
+ 0,
+ out,
+ output_len,
+ password,
+ 0,
+ salt,
+ salt ? PGP_SALT_SIZE : 0);
+}
+#endif
+
+size_t
+pgp_s2k_decode_iterations(uint8_t c)
+{
+ // See RFC 4880 section 3.7.1.3
+ return (16 + (c & 0x0F)) << ((c >> 4) + 6);
+}
+
+size_t
+pgp_s2k_round_iterations(size_t iterations)
+{
+ return pgp_s2k_decode_iterations(pgp_s2k_encode_iterations(iterations));
+}
+
+uint8_t
+pgp_s2k_encode_iterations(size_t iterations)
+{
+ /* For compatibility, when an S2K specifier is used, the special value
+ * 254 or 255 is stored in the position where the hash algorithm octet
+ * would have been in the old data structure. This is then followed
+ * immediately by a one-octet algorithm identifier, and then by the S2K
+ * specifier as encoded above.
+ * 0: secret data is unencrypted (no password)
+ * 255 or 254: followed by algorithm octet and S2K specifier
+ * Cipher alg: use Simple S2K algorithm using MD5 hash
+ * For more info refer to rfc 4880 section 3.7.2.1.
+ */
+ for (uint16_t c = 0; c < 256; ++c) {
+ // This could be a binary search
+ if (pgp_s2k_decode_iterations(c) >= iterations) {
+ return c;
+ }
+ }
+ return 255;
+}
+
+/// Should this function be elsewhere?
+static uint64_t
+get_timestamp_usec()
+{
+#ifndef _MSC_VER
+ // TODO: Consider clock_gettime
+ struct timeval tv;
+ ::gettimeofday(&tv, NULL);
+ return (static_cast<uint64_t>(tv.tv_sec) * 1000000) + static_cast<uint64_t>(tv.tv_usec);
+#else
+ return GetTickCount64() * 1000;
+#endif
+}
+
+size_t
+pgp_s2k_compute_iters(pgp_hash_alg_t alg, size_t desired_msec, size_t trial_msec)
+{
+ if (desired_msec == 0) {
+ desired_msec = DEFAULT_S2K_MSEC;
+ }
+ if (trial_msec == 0) {
+ trial_msec = DEFAULT_S2K_TUNE_MSEC;
+ }
+
+ // number of iterations to estimate the number of iterations
+ // (sorry, cannot tell it better)
+ const uint8_t NUM_ITERATIONS = 16;
+ uint64_t duration = 0;
+ size_t bytes = 0;
+ try {
+ for (uint8_t i = 0; i < NUM_ITERATIONS; i++) {
+ uint64_t start = get_timestamp_usec();
+ uint64_t end = start;
+ auto hash = rnp::Hash::create(alg);
+ uint8_t buf[8192] = {0};
+ while (end - start < trial_msec * 1000ull) {
+ hash->add(buf, sizeof(buf));
+ bytes += sizeof(buf);
+ end = get_timestamp_usec();
+ }
+ hash->finish(buf);
+ duration += (end - start);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to hash data: %s", e.what());
+ return 0;
+ }
+
+ const uint8_t MIN_ITERS = 96;
+ if (duration == 0) {
+ return pgp_s2k_decode_iterations(MIN_ITERS);
+ }
+
+ const double bytes_per_usec = static_cast<double>(bytes) / duration;
+ const double desired_usec = desired_msec * 1000.0;
+ const double bytes_for_target = bytes_per_usec * desired_usec;
+ const uint8_t iters = pgp_s2k_encode_iterations(bytes_for_target);
+
+ return pgp_s2k_decode_iterations((iters > MIN_ITERS) ? iters : MIN_ITERS);
+}