Skip to content

Commit

Permalink
add S2K Argon2 and AEAD s2k usage
Browse files Browse the repository at this point in the history
  • Loading branch information
TJ-91 committed Dec 4, 2024
1 parent ddcbaa9 commit ef7ee3a
Show file tree
Hide file tree
Showing 20 changed files with 697 additions and 86 deletions.
10 changes: 10 additions & 0 deletions include/repgp/repgp_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@
/* Salt size for hashing */
#define PGP_SALT_SIZE 8

#if defined(ENABLE_CRYPTO_REFRESH)
#define PGP_MAX_S2K_SALT_SIZE 16
#endif

/* Size of the keyid */
#define PGP_KEY_ID_SIZE 8

Expand Down Expand Up @@ -344,6 +348,9 @@ typedef enum {
*/
typedef enum {
PGP_S2KU_NONE = 0,
#if defined(ENABLE_CRYPTO_REFRESH)
PGP_S2KU_AEAD = 253,
#endif
PGP_S2KU_ENCRYPTED_AND_HASHED = 254,
PGP_S2KU_ENCRYPTED = 255
} pgp_s2k_usage_t;
Expand All @@ -354,6 +361,9 @@ typedef enum : uint8_t {
PGP_S2KS_SIMPLE = 0,
PGP_S2KS_SALTED = 1,
PGP_S2KS_ITERATED_AND_SALTED = 3,
#if defined(ENABLE_CRYPTO_REFRESH)
PGP_S2KS_ARGON2 = 4,
#endif
PGP_S2KS_EXPERIMENTAL = 101
} pgp_s2k_specifier_t;

Expand Down
3 changes: 3 additions & 0 deletions include/rnp/rnp.h
Original file line number Diff line number Diff line change
Expand Up @@ -2641,6 +2641,9 @@ RNP_API rnp_result_t rnp_key_is_locked(rnp_key_handle_t key, bool *result);
* protection.
* - "Encrypted-Hashed" : secret key data is encrypted, using the SHA1 hash as
* an integrity protection.
* - "AEAD-Encrypted" : secret key data is encrypted using an AEAD algorithm
* with built-in integrity protection. (only available in experimental build
* that enables ENABLE_CRYTPO_REFRESH)

Check warning on line 2646 in include/rnp/rnp.h

View workflow job for this annotation

GitHub Actions / typos

"CRYTPO" should be "CRYPTO".
* - "GPG-None" : secret key data is not available at all (this would happen if
* secret key is exported from GnuPG via --export-secret-subkeys)
* - "GPG-Smartcard" : secret key data is stored on smartcard by GnuPG, so is not
Expand Down
2 changes: 1 addition & 1 deletion src/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ if(CRYPTO_BACKEND_BOTAN)
resolve_feature_state(ENABLE_AEAD "AEAD_EAX;AEAD_OCB")
resolve_feature_state(ENABLE_TWOFISH "TWOFISH")
resolve_feature_state(ENABLE_IDEA "IDEA")
resolve_feature_state(ENABLE_CRYPTO_REFRESH "HKDF")
resolve_feature_state(ENABLE_CRYPTO_REFRESH "HKDF;ARGON2")
resolve_feature_state(ENABLE_PQC "KMAC;DILITHIUM;KYBER;SPHINCS_PLUS_WITH_SHA2;SPHINCS_PLUS_WITH_SHAKE")
resolve_feature_state(ENABLE_BLOWFISH "BLOWFISH")
resolve_feature_state(ENABLE_CAST5 "CAST_128")
Expand Down
96 changes: 95 additions & 1 deletion src/lib/crypto/s2k.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@
#include "types.h"
#include "utils.h"
#ifdef CRYPTO_BACKEND_BOTAN
#if defined(ENABLE_CRYPTO_REFRESH)
#include "botan/bigint.h"
#endif
#include <botan/ffi.h>
#include "hash_botan.hpp"
#include <botan/pwdhash.h>
#include <cmath>
#endif

bool
Expand All @@ -66,11 +71,30 @@ pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysi
iterations = s2k->iterations;
}
break;
#if defined(ENABLE_CRYPTO_REFRESH)
case PGP_S2KS_ARGON2:
saltptr = s2k->salt;
break;
#endif
default:
return false;
}

if (pgp_s2k_iterated(s2k->hash_alg, key, keysize, password, saltptr, iterations)) {
#if defined(ENABLE_CRYPTO_REFRESH)
if (s2k->specifier == PGP_S2KS_ARGON2) {
if (pgp_s2k_argon2(key,
keysize,
password,
saltptr,
s2k->argon2_t,
s2k->argon2_p,
s2k->argon2_encoded_m)) {
RNP_LOG("s2k argon2 failed");
return false;
}
} else
#endif
if (pgp_s2k_iterated(s2k->hash_alg, key, keysize, password, saltptr, iterations)) {
RNP_LOG("s2k failed");
return false;
}
Expand All @@ -79,6 +103,45 @@ pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysi
}

#ifdef CRYPTO_BACKEND_BOTAN
#if defined(ENABLE_CRYPTO_REFRESH)
int
pgp_s2k_argon2(uint8_t * out,
size_t output_len,
const char * password,
const uint8_t *salt,
uint8_t t,
uint8_t p,
uint8_t encoded_m)
{
const size_t argon2_salt_size = 16;

/* check constraints on p and t */
if (!p || !t) {
RNP_LOG("Argon2 t and p must be non-zero");
return -1;
}
/* check constraints on m. Floating point calculation is fine due to restricted data range
* (uint8_t) */
if (encoded_m < (3 + (uint8_t) std::ceil(std::log2(p))) || encoded_m > 31) {
RNP_LOG("Argon2 encoded_m must be between 3+ceil(log2(p)) and 31");
return -1;
}

try {
auto pwdhash_fam = Botan::PasswordHashFamily::create_or_throw("Argon2id");

std::unique_ptr<Botan::PasswordHash> pwhash =
pwdhash_fam->from_params(1 << encoded_m, t, p);
pwhash->derive_key(
out, output_len, password, std::strlen(password), salt, argon2_salt_size);
} catch (const std::exception &e) {
RNP_LOG("%s", e.what());
return -1;
}
return 0;
}
#endif

int
pgp_s2k_iterated(pgp_hash_alg_t alg,
uint8_t * out,
Expand Down Expand Up @@ -201,3 +264,34 @@ pgp_s2k_compute_iters(pgp_hash_alg_t alg, size_t desired_msec, size_t trial_msec

return pgp_s2k_decode_iterations((iters > MIN_ITERS) ? iters : MIN_ITERS);
}

size_t
pgp_s2k_t::salt_size(pgp_s2k_specifier_t specifier)
{
#if defined(ENABLE_CRYPTO_REFRESH)
return (specifier == PGP_S2KS_ARGON2 ? 16 : 8);
#endif
return 8;
}

uint8_t
pgp_s2k_t::specifier_len(pgp_s2k_specifier_t specifier)

Check warning on line 278 in src/lib/crypto/s2k.cpp

View check run for this annotation

Codecov / codecov/patch

src/lib/crypto/s2k.cpp#L278

Added line #L278 was not covered by tests
{
switch (specifier) {
case PGP_S2KS_SIMPLE:
return 2;
case PGP_S2KS_SALTED:
return 10;
case PGP_S2KS_ITERATED_AND_SALTED:
return 11;
case PGP_S2KS_EXPERIMENTAL:
return 0; /* not used */

Check warning on line 288 in src/lib/crypto/s2k.cpp

View check run for this annotation

Codecov / codecov/patch

src/lib/crypto/s2k.cpp#L280-L288

Added lines #L280 - L288 were not covered by tests
#if defined(ENABLE_CRYPTO_REFRESH)
case PGP_S2KS_ARGON2:
return 20;
#endif
default:
RNP_LOG("invalid specifier");
throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);

Check warning on line 295 in src/lib/crypto/s2k.cpp

View check run for this annotation

Codecov / codecov/patch

src/lib/crypto/s2k.cpp#L293-L295

Added lines #L293 - L295 were not covered by tests
}
}
11 changes: 11 additions & 0 deletions src/lib/crypto/s2k.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,21 @@
#define RNP_S2K_H_

#include <cstdint>
#include <rnp/rnp_def.h>
#include "repgp/repgp_def.h"

typedef struct pgp_s2k_t pgp_s2k_t;

#if defined(ENABLE_CRYPTO_REFRESH)
int pgp_s2k_argon2(uint8_t * out,
size_t output_len,
const char * password,
const uint8_t *salt,
uint8_t t,
uint8_t p,
uint8_t encoded_m);
#endif

int pgp_s2k_iterated(pgp_hash_alg_t alg,
uint8_t * out,
size_t output_len,
Expand Down
64 changes: 46 additions & 18 deletions src/lib/pgp-key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,9 @@ pgp_key_t::has_secret() const noexcept
case PGP_S2KS_SIMPLE:
case PGP_S2KS_SALTED:
case PGP_S2KS_ITERATED_AND_SALTED:
#if defined(ENABLE_CRYPTO_REFRESH)
case PGP_S2KS_ARGON2:
#endif
return true;
default:
return false;
Expand Down Expand Up @@ -1599,24 +1602,49 @@ pgp_key_t::protect(pgp_key_pkt_t & decrypted,
return false;
}

/* force encrypted-and-hashed and iterated-and-salted as it's the only method we support*/
pkt_.sec_protection.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED;
pkt_.sec_protection.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED;
/* use default values where needed */
pkt_.sec_protection.symm_alg =
protection.symm_alg ? protection.symm_alg : DEFAULT_PGP_SYMM_ALG;
pkt_.sec_protection.cipher_mode =
protection.cipher_mode ? protection.cipher_mode : DEFAULT_PGP_CIPHER_MODE;
pkt_.sec_protection.s2k.hash_alg =
protection.hash_alg ? protection.hash_alg : DEFAULT_PGP_HASH_ALG;
auto iter = protection.iterations;
if (!iter) {
iter = ctx.s2k_iterations(pkt_.sec_protection.s2k.hash_alg);
}
pkt_.sec_protection.s2k.iterations = pgp_s2k_round_iterations(iter);
if (!ownpkt) {
/* decrypted is assumed to be temporary variable so we may modify it */
decrypted.sec_protection = pkt_.sec_protection;
#if defined(ENABLE_CRYPTO_REFRESH)
/* force AEAD-encrypted and Argon2 at least for v6 keys */
if (decrypted.version == PGP_V6) {
pkt_.sec_protection.s2k.usage = PGP_S2KU_AEAD;
pkt_.sec_protection.s2k.specifier = PGP_S2KS_ARGON2;
/* use default values where needed */
pkt_.sec_protection.symm_alg = PGP_SA_AES_256;
pkt_.sec_protection.aead_alg = PGP_AEAD_OCB;
pkt_.sec_protection.cipher_mode = PGP_CIPHER_MODE_NONE;
pkt_.sec_protection.s2k.hash_alg = PGP_HASH_UNKNOWN;
// use reasonable default for argon2
pkt_.sec_protection.s2k.argon2_encoded_m = 21;
pkt_.sec_protection.s2k.argon2_p = 4;
pkt_.sec_protection.s2k.argon2_t = 1;
auto iter = protection.iterations;
pkt_.sec_protection.s2k.iterations = 0;
if (!ownpkt) {
/* decrypted is assumed to be temporary variable so we may modify it */
decrypted.sec_protection = pkt_.sec_protection;
}
} else
#endif
{
/* force encrypted-and-hashed and iterated-and-salted as it's the only method we
* support*/
pkt_.sec_protection.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED;
pkt_.sec_protection.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED;
/* use default values where needed */
pkt_.sec_protection.symm_alg =
protection.symm_alg ? protection.symm_alg : DEFAULT_PGP_SYMM_ALG;
pkt_.sec_protection.cipher_mode =
protection.cipher_mode ? protection.cipher_mode : DEFAULT_PGP_CIPHER_MODE;
pkt_.sec_protection.s2k.hash_alg =
protection.hash_alg ? protection.hash_alg : DEFAULT_PGP_HASH_ALG;
auto iter = protection.iterations;
if (!iter) {
iter = ctx.s2k_iterations(pkt_.sec_protection.s2k.hash_alg);
}
pkt_.sec_protection.s2k.iterations = pgp_s2k_round_iterations(iter);
if (!ownpkt) {
/* decrypted is assumed to be temporary variable so we may modify it */
decrypted.sec_protection = pkt_.sec_protection;
}
}

/* write the protected key to raw packet */
Expand Down
8 changes: 8 additions & 0 deletions src/lib/rnp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ static const id_str_pair s2k_type_map[] = {
{PGP_S2KS_SIMPLE, "Simple"},
{PGP_S2KS_SALTED, "Salted"},
{PGP_S2KS_ITERATED_AND_SALTED, "Iterated and salted"},
#if defined(ENABLE_CRYPTO_REFRESH)
{PGP_S2KS_ARGON2, "Argon2"},
#endif
{0, NULL}};

static const id_str_pair key_usage_map[] = {
Expand Down Expand Up @@ -7684,6 +7687,11 @@ try {
if ((s2k.usage == PGP_S2KU_ENCRYPTED) && (s2k.specifier != PGP_S2KS_EXPERIMENTAL)) {
res = "Encrypted";
}
#if defined(ENABLE_CRYPTO_REFRESH)
if ((s2k.usage == PGP_S2KU_AEAD) && (s2k.specifier != PGP_S2KS_EXPERIMENTAL)) {
res = "AEAD-encrypted";
}
#endif
if ((s2k.usage == PGP_S2KU_ENCRYPTED_AND_HASHED) &&
(s2k.specifier != PGP_S2KS_EXPERIMENTAL)) {
res = "Encrypted-Hashed";
Expand Down
25 changes: 21 additions & 4 deletions src/lib/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,36 @@ typedef struct pgp_s2k_t {
/* below fields may not all be valid, depending on the usage field above */
pgp_s2k_specifier_t specifier{};
pgp_hash_alg_t hash_alg{};
uint8_t salt[PGP_SALT_SIZE];
unsigned iterations{};
#if defined(ENABLE_CRYPTO_REFRESH)
uint8_t salt[PGP_MAX_S2K_SALT_SIZE];
#else
uint8_t salt[PGP_SALT_SIZE];
#endif
unsigned iterations{};
/* GnuPG custom s2k data */
pgp_s2k_gpg_extension_t gpg_ext_num{};
uint8_t gpg_serial_len{};
uint8_t gpg_serial[16];
/* Experimental s2k data */
std::vector<uint8_t> experimental{};

#if defined(ENABLE_CRYPTO_REFRESH)
/* argon2 */
uint8_t argon2_t;
uint8_t argon2_p;
uint8_t argon2_encoded_m;
#endif

static size_t salt_size(pgp_s2k_specifier_t specifier);
static uint8_t specifier_len(pgp_s2k_specifier_t specifier);
} pgp_s2k_t;

typedef struct pgp_key_protection_t {
pgp_s2k_t s2k{}; /* string-to-key kdf params */
pgp_symm_alg_t symm_alg{}; /* symmetric alg */
pgp_s2k_t s2k{}; /* string-to-key kdf params */
pgp_symm_alg_t symm_alg{}; /* symmetric alg */
#if defined(ENABLE_CRYPTO_REFRESH)
pgp_aead_alg_t aead_alg{}; /* AEAD alg */
#endif
pgp_cipher_mode_t cipher_mode{}; /* block cipher mode */
uint8_t iv[PGP_MAX_BLOCK_SIZE];
} pgp_key_protection_t;
Expand Down
2 changes: 1 addition & 1 deletion src/librekey/key_store_g10.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,7 @@ gnupg_sexp_t::add_protected_seckey(pgp_key_pkt_t & seckey,

// randomize IV and salt
ctx.rng.get(prot.iv, sizeof(prot.iv));
ctx.rng.get(prot.s2k.salt, sizeof(prot.s2k.salt));
ctx.rng.get(prot.s2k.salt, prot.s2k.salt_size(prot.s2k.specifier));

// write seckey
gnupg_sexp_t raw_s_exp;
Expand Down
2 changes: 1 addition & 1 deletion src/librepgp/stream-ctx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ rnp_ctx_t::add_encryption_password(const std::string &password,
info.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED;
info.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED;
info.s2k.hash_alg = halg;
ctx->rng.get(info.s2k.salt, sizeof(info.s2k.salt));
ctx->rng.get(info.s2k.salt, info.s2k.salt_size(info.s2k.specifier));
if (!iterations) {
iterations = ctx->s2k_iterations(halg);
}
Expand Down
28 changes: 20 additions & 8 deletions src/librepgp/stream-dump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,14 +494,26 @@ dst_print_s2k(pgp_dest_t *dst, pgp_s2k_t *s2k)
true);
return;
}
dst_print_halg(dst, "s2k hash algorithm", s2k->hash_alg);
if ((s2k->specifier == PGP_S2KS_SALTED) ||
(s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED)) {
dst_print_hex(dst, "s2k salt", s2k->salt, PGP_SALT_SIZE, false);
}
if (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED) {
size_t real_iter = pgp_s2k_decode_iterations(s2k->iterations);
dst_printf(dst, "s2k iterations: %zu (encoded as %u)\n", real_iter, s2k->iterations);
#if defined(ENABLE_CRYPTO_REFRESH)
if (s2k->specifier == PGP_S2KS_ARGON2) {
dst_print_hex(dst, "s2k salt", s2k->salt, s2k->salt_size(s2k->specifier), false);
dst_print_hex(dst, "s2k salt", s2k->salt, 16, false);
dst_printf(dst, "argon2 t: %d\n", s2k->argon2_t);
dst_printf(dst, "argon2 p: %d\n", s2k->argon2_p);
dst_printf(dst, "argon2 encoded_m: %d\n", s2k->argon2_encoded_m);
} else
#endif
{
dst_print_halg(dst, "s2k hash algorithm", s2k->hash_alg);
if ((s2k->specifier == PGP_S2KS_SALTED) ||
(s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED)) {
dst_print_hex(dst, "s2k salt", s2k->salt, s2k->salt_size(s2k->specifier), false);
}
if (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED) {
size_t real_iter = pgp_s2k_decode_iterations(s2k->iterations);
dst_printf(
dst, "s2k iterations: %zu (encoded as %u)\n", real_iter, s2k->iterations);
}
}
}

Expand Down
Loading

0 comments on commit ef7ee3a

Please sign in to comment.