summaryrefslogtreecommitdiffstats
path: root/src/lib/crypto/s2k.cpp
blob: ede7965ddad6944cedfccc093ecd3933da77852e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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);
}