aboutsummaryrefslogtreecommitdiff
path: root/src/lib/crypt_ops/crypto_pwbox.c
blob: bfad27d9fc67c09aa8c334ec43d88849bf00da00 (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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/* Copyright (c) 2014-2020, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file crypto_pwbox.c
 *
 * \brief Code for encrypting secrets in a password-protected form and saving
 * them to disk.
 */

#include <string.h>

#include "lib/arch/bytes.h"
#include "lib/crypt_ops/crypto_cipher.h"
#include "lib/crypt_ops/crypto_digest.h"
#include "lib/crypt_ops/crypto_pwbox.h"
#include "lib/crypt_ops/crypto_rand.h"
#include "lib/crypt_ops/crypto_s2k.h"
#include "lib/crypt_ops/crypto_util.h"
#include "lib/ctime/di_ops.h"
#include "lib/intmath/muldiv.h"
#include "trunnel/pwbox.h"
#include "lib/log/util_bug.h"

/* 8 bytes "TORBOX00"
   1 byte: header len (H)
   H bytes: header, denoting secret key algorithm.
   16 bytes: IV
   Round up to multiple of 128 bytes, then encrypt:
      4 bytes: data len
      data
      zeros
   32 bytes: HMAC-SHA256 of all previous bytes.
*/

#define MAX_OVERHEAD (S2K_MAXLEN + 8 + 1 + 32 + CIPHER_IV_LEN)

/**
 * Make an authenticated passphrase-encrypted blob to encode the
 * <b>input_len</b> bytes in <b>input</b> using the passphrase
 * <b>secret</b> of <b>secret_len</b> bytes.  Allocate a new chunk of memory
 * to hold the encrypted data, and store a pointer to that memory in
 * *<b>out</b>, and its size in <b>outlen_out</b>.  Use <b>s2k_flags</b> as an
 * argument to the passphrase-hashing function.
 */
int
crypto_pwbox(uint8_t **out, size_t *outlen_out,
             const uint8_t *input, size_t input_len,
             const char *secret, size_t secret_len,
             unsigned s2k_flags)
{
  uint8_t *result = NULL, *encrypted_portion;
  size_t encrypted_len = 128 * CEIL_DIV(input_len+4, 128);
  ssize_t result_len;
  int spec_len;
  uint8_t keys[CIPHER_KEY_LEN + DIGEST256_LEN];
  pwbox_encoded_t *enc = NULL;
  ssize_t enc_len;

  crypto_cipher_t *cipher;
  int rv;

  enc = pwbox_encoded_new();
  tor_assert(enc);

  pwbox_encoded_setlen_skey_header(enc, S2K_MAXLEN);

  spec_len = secret_to_key_make_specifier(
                                      pwbox_encoded_getarray_skey_header(enc),
                                      S2K_MAXLEN,
                                      s2k_flags);
  if (BUG(spec_len < 0 || spec_len > S2K_MAXLEN))
    goto err;
  pwbox_encoded_setlen_skey_header(enc, spec_len);
  enc->header_len = spec_len;

  crypto_rand((char*)enc->iv, sizeof(enc->iv));

  pwbox_encoded_setlen_data(enc, encrypted_len);
  encrypted_portion = pwbox_encoded_getarray_data(enc);

  set_uint32(encrypted_portion, tor_htonl((uint32_t)input_len));
  memcpy(encrypted_portion+4, input, input_len);

  /* Now that all the data is in position, derive some keys, encrypt, and
   * digest */
  const int s2k_rv = secret_to_key_derivekey(keys, sizeof(keys),
                              pwbox_encoded_getarray_skey_header(enc),
                              spec_len,
                              secret, secret_len);
  if (BUG(s2k_rv < 0))
    goto err;

  cipher = crypto_cipher_new_with_iv((char*)keys, (char*)enc->iv);
  crypto_cipher_crypt_inplace(cipher, (char*)encrypted_portion, encrypted_len);
  crypto_cipher_free(cipher);

  result_len = pwbox_encoded_encoded_len(enc);
  if (BUG(result_len < 0))
    goto err;
  result = tor_malloc(result_len);
  enc_len = pwbox_encoded_encode(result, result_len, enc);
  if (BUG(enc_len < 0))
    goto err;
  tor_assert(enc_len == result_len);

  crypto_hmac_sha256((char*) result + result_len - 32,
                     (const char*)keys + CIPHER_KEY_LEN,
                     DIGEST256_LEN,
                     (const char*)result,
                     result_len - 32);

  *out = result;
  *outlen_out = result_len;
  rv = 0;
  goto out;

  /* LCOV_EXCL_START

     This error case is often unreachable if we're correctly coded, unless
     somebody adds a new error case somewhere, or unless you're building
     without scrypto support.

       - make_specifier can't fail, unless S2K_MAX_LEN is too short.
       - secret_to_key_derivekey can't really fail unless we're missing
         scrypt, or the underlying function fails, or we pass it a bogus
         algorithm or parameters.
       - pwbox_encoded_encoded_len can't fail unless we're using trunnel
         incorrectly.
       - pwbox_encoded_encode can't fail unless we're using trunnel wrong,
         or it's buggy.
   */
 err:
  tor_free(result);
  rv = -1;
  /* LCOV_EXCL_STOP */
 out:
  pwbox_encoded_free(enc);
  memwipe(keys, 0, sizeof(keys));
  return rv;
}

/**
 * Try to decrypt the passphrase-encrypted blob of <b>input_len</b> bytes in
 * <b>input</b> using the passphrase <b>secret</b> of <b>secret_len</b> bytes.
 * On success, return 0 and allocate a new chunk of memory to hold the
 * decrypted data, and store a pointer to that memory in *<b>out</b>, and its
 * size in <b>outlen_out</b>.  On failure, return UNPWBOX_BAD_SECRET if
 * the passphrase might have been wrong, and UNPWBOX_CORRUPT if the object is
 * definitely corrupt.
 */
int
crypto_unpwbox(uint8_t **out, size_t *outlen_out,
               const uint8_t *inp, size_t input_len,
               const char *secret, size_t secret_len)
{
  uint8_t *result = NULL;
  const uint8_t *encrypted;
  uint8_t keys[CIPHER_KEY_LEN + DIGEST256_LEN];
  uint8_t hmac[DIGEST256_LEN];
  uint32_t result_len;
  size_t encrypted_len;
  crypto_cipher_t *cipher = NULL;
  int rv = UNPWBOX_CORRUPTED;
  ssize_t got_len;

  pwbox_encoded_t *enc = NULL;

  got_len = pwbox_encoded_parse(&enc, inp, input_len);
  if (got_len < 0 || (size_t)got_len != input_len)
    goto err;

  /* Now derive the keys and check the hmac. */
  if (secret_to_key_derivekey(keys, sizeof(keys),
                              pwbox_encoded_getarray_skey_header(enc),
                              pwbox_encoded_getlen_skey_header(enc),
                              secret, secret_len) < 0)
    goto err;

  crypto_hmac_sha256((char *)hmac,
                     (const char*)keys + CIPHER_KEY_LEN, DIGEST256_LEN,
                     (const char*)inp, input_len - DIGEST256_LEN);

  if (tor_memneq(hmac, enc->hmac, DIGEST256_LEN)) {
    rv = UNPWBOX_BAD_SECRET;
    goto err;
  }

  /* How long is the plaintext? */
  encrypted = pwbox_encoded_getarray_data(enc);
  encrypted_len = pwbox_encoded_getlen_data(enc);
  if (encrypted_len < 4)
    goto err;

  cipher = crypto_cipher_new_with_iv((char*)keys, (char*)enc->iv);
  crypto_cipher_decrypt(cipher, (char*)&result_len, (char*)encrypted, 4);
  result_len = tor_ntohl(result_len);
  if (encrypted_len < result_len + 4)
    goto err;

  /* Allocate a buffer and decrypt */
  result = tor_malloc_zero(result_len);
  crypto_cipher_decrypt(cipher, (char*)result, (char*)encrypted+4, result_len);

  *out = result;
  *outlen_out = result_len;

  rv = UNPWBOX_OKAY;
  goto out;

 err:
  tor_free(result);

 out:
  crypto_cipher_free(cipher);
  pwbox_encoded_free(enc);
  memwipe(keys, 0, sizeof(keys));
  return rv;
}