Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v6: add S2K Argon2 and AEAD s2k usage #2296

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
* 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 @@
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 @@
}

#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 @@

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
Loading