diff --git a/include/repgp/repgp_def.h b/include/repgp/repgp_def.h index 524d9c62ed..6d86a614d5 100644 --- a/include/repgp/repgp_def.h +++ b/include/repgp/repgp_def.h @@ -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 @@ -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; @@ -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; diff --git a/include/rnp/rnp.h b/include/rnp/rnp.h index d2d7930d8a..effd30aa26 100644 --- a/include/rnp/rnp.h +++ b/include/rnp/rnp.h @@ -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) * - "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 diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index b7f6022c65..b5f572dc79 100755 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -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") diff --git a/src/lib/crypto/s2k.cpp b/src/lib/crypto/s2k.cpp index ede7965dda..e7232fd207 100644 --- a/src/lib/crypto/s2k.cpp +++ b/src/lib/crypto/s2k.cpp @@ -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 #include "hash_botan.hpp" +#include +#include #endif bool @@ -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; } @@ -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 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, @@ -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) +{ + 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 */ +#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); + } +} diff --git a/src/lib/crypto/s2k.h b/src/lib/crypto/s2k.h index c67a77321c..f38f4d3420 100644 --- a/src/lib/crypto/s2k.h +++ b/src/lib/crypto/s2k.h @@ -32,10 +32,21 @@ #define RNP_S2K_H_ #include +#include #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, diff --git a/src/lib/pgp-key.cpp b/src/lib/pgp-key.cpp index fa4c694d47..9e88dc6e4f 100644 --- a/src/lib/pgp-key.cpp +++ b/src/lib/pgp-key.cpp @@ -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; @@ -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 */ diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp index 6d5b582ffd..befc7b817d 100644 --- a/src/lib/rnp.cpp +++ b/src/lib/rnp.cpp @@ -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[] = { @@ -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"; diff --git a/src/lib/types.h b/src/lib/types.h index 2b90f8d0aa..e3f7c6054d 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -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 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; diff --git a/src/librekey/key_store_g10.cpp b/src/librekey/key_store_g10.cpp index 812f4d9970..6835ee6b93 100644 --- a/src/librekey/key_store_g10.cpp +++ b/src/librekey/key_store_g10.cpp @@ -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; diff --git a/src/librepgp/stream-ctx.cpp b/src/librepgp/stream-ctx.cpp index 96edbd443a..377ec4d802 100644 --- a/src/librepgp/stream-ctx.cpp +++ b/src/librepgp/stream-ctx.cpp @@ -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); } diff --git a/src/librepgp/stream-dump.cpp b/src/librepgp/stream-dump.cpp index ad425c9551..513dcf6bdd 100644 --- a/src/librepgp/stream-dump.cpp +++ b/src/librepgp/stream-dump.cpp @@ -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); + } } } diff --git a/src/librepgp/stream-key.cpp b/src/librepgp/stream-key.cpp index c9c310d853..b7c7c34e55 100644 --- a/src/librepgp/stream-key.cpp +++ b/src/librepgp/stream-key.cpp @@ -50,6 +50,9 @@ #include #include #include +#if defined(ENABLE_CRYPTO_REFRESH) +#include "crypto/hkdf.hpp" +#endif static bool skip_pgp_packets(pgp_source_t &src, const std::set &pkts) @@ -433,6 +436,11 @@ parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len) } break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_S2KU_AEAD: { + break; // nothing to do here + } +#endif default: RNP_LOG("unknown s2k usage: %d", (int) key.sec_protection.s2k.usage); return RNP_ERROR_BAD_PARAMETERS; @@ -461,6 +469,99 @@ parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len) } } +#if defined(ENABLE_CRYPTO_REFRESH) +static rnp_result_t +crypt_secret_key_aead(pgp_key_pkt_t * key, + rnp::secure_vector const &s2k_derived_key, + rnp::secure_vector const &in_vec, + rnp::secure_vector & out_vec, + bool decrypt) +{ + size_t nonce_len = pgp_cipher_aead_nonce_len(key->sec_protection.aead_alg); + uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN]; + bool success = true; + pgp_crypt_t crypt; + size_t keysize = pgp_key_size(key->sec_protection.symm_alg); + if (!keysize) { + RNP_LOG("invalid algorithm"); + return RNP_ERROR_BAD_PARAMETERS; + } + + /* derive kek using HKDF */ + auto hkdf = rnp::Hkdf::create(PGP_HASH_SHA256); + std::vector kek(keysize); + std::vector hkdf_info; + hkdf_info.push_back(key->tag | 0xC0); + hkdf_info.push_back(key->version); + hkdf_info.push_back(key->sec_protection.symm_alg); + hkdf_info.push_back(key->sec_protection.aead_alg); + + hkdf->extract_expand(NULL, + 0, + s2k_derived_key.data(), + s2k_derived_key.size(), + hkdf_info.data(), + hkdf_info.size(), + kek.data(), + keysize); + + if (!pgp_cipher_aead_init(&crypt, + key->sec_protection.symm_alg, + key->sec_protection.aead_alg, + kek.data(), + decrypt)) { + secure_clear(kek.data(), kek.size()); + RNP_LOG("failed to init AEAD encryption"); + return RNP_ERROR_ENCRYPT_FAILED; + } + secure_clear(kek.data(), kek.size()); + + // set up nonce + if (nonce_len != pgp_cipher_aead_nonce( + key->sec_protection.aead_alg, key->sec_protection.iv, nonce, 0)) { + } + + /* set up ad (associated data) */ + std::vector ad; + uint8_t bytes[4]; + // tag and version + ad.push_back(key->tag | 0xC0); + ad.push_back(key->version); + // creation time + write_uint32(bytes, key->creation_time); + ad.insert(ad.end(), bytes, bytes + 4); + // pk alg + ad.push_back(key->alg); + + // public material + pgp_packet_body_t material_body(PGP_PKT_RESERVED); + key->material->write(material_body); + // also add the key material length for v6 + if (key->version == PGP_V6) { + // write_uint32(bytes, body.size()); + write_uint32(bytes, material_body.size()); + ad.insert(ad.end(), bytes, bytes + 4); + } + // add public key material itself + ad.insert(ad.end(), material_body.data(), material_body.data() + material_body.size()); + + success = pgp_cipher_aead_set_ad(&crypt, ad.data(), ad.size()); + if (success) { + success = pgp_cipher_aead_start(&crypt, key->sec_protection.iv, nonce_len); + } + if (success) { + success = pgp_cipher_aead_finish(&crypt, out_vec.data(), in_vec.data(), in_vec.size()); + } + + pgp_cipher_aead_destroy(&crypt); + if (!success) { + return RNP_ERROR_DECRYPT_FAILED; + } + + return RNP_SUCCESS; +} +#endif + rnp_result_t decrypt_secret_key(pgp_key_pkt_t *key, const char *password) { @@ -494,6 +595,14 @@ decrypt_secret_key(pgp_key_pkt_t *key, const char *password) return RNP_ERROR_BAD_PARAMETERS; } +#if defined(ENABLE_CRYPTO_REFRESH) + if ((key->sec_protection.s2k.specifier == PGP_S2KS_ARGON2) && + key->sec_protection.s2k.usage != PGP_S2KU_AEAD) { + RNP_LOG("s2k usage must be AEAD if using Argon2"); + return false; + } +#endif + rnp::secure_array keybuf; size_t keysize = pgp_key_size(key->sec_protection.symm_alg); if (!keysize || @@ -505,13 +614,34 @@ decrypt_secret_key(pgp_key_pkt_t *key, const char *password) try { rnp::secure_vector decdata(key->sec_len); pgp_crypt_t crypt; + rnp_result_t ret = RNP_ERROR_GENERIC; + +#if defined(ENABLE_CRYPTO_REFRESH) + /* AEAD case */ + if (key->sec_protection.s2k.usage == PGP_S2KU_AEAD) { + rnp::secure_vector keybuf_vec(keybuf.data(), + keybuf.data() + keybuf.size()); + rnp::secure_vector encr_vec(key->sec_data, key->sec_data + key->sec_len); + + ret = crypt_secret_key_aead(key, keybuf_vec, encr_vec, decdata, true); + if (ret) { + RNP_LOG("could not successfully decrypt key"); + return ret; + } + + // substract authentication tag length to get the encrypted data length + size_t sec_len = + key->sec_len - pgp_cipher_aead_tag_len(key->sec_protection.aead_alg); + return parse_secret_key_mpis(*key, decdata.data(), sec_len); + } +#endif + if (!pgp_cipher_cfb_start( &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) { RNP_LOG("failed to start cfb decryption"); return RNP_ERROR_DECRYPT_FAILED; } - rnp_result_t ret = RNP_ERROR_GENERIC; switch (key->version) { case PGP_V3: if (!is_rsa_key_alg(key->alg)) { @@ -556,6 +686,9 @@ write_secret_key_mpis(pgp_packet_body_t &body, pgp_key_pkt_t &key) if (key.version == PGP_V6 && key.sec_protection.s2k.usage == PGP_S2KU_NONE) { return; /* checksum removed for v6 and usage byte zero */ } + if (key.sec_protection.s2k.usage == PGP_S2KU_AEAD) { + return; /* for AEAD we add the authentication tag (later) */ + } #endif /* add sum16 if sha1 is not used */ @@ -586,8 +719,14 @@ encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng) if (!is_secret_key_pkt(key->tag) || !key->material->secret()) { return RNP_ERROR_BAD_PARAMETERS; } - if (key->sec_protection.s2k.usage && - (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB)) { +#if defined(ENABLE_CRYPTO_REFRESH) + /* check that we either use AEAD or PGP_CIPHER_MODE_CFB */ + if (key->sec_protection.s2k.usage == PGP_S2KU_AEAD) { + // do nothing + } else +#endif + if (key->sec_protection.s2k.usage && + (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB)) { RNP_LOG("unsupported secret key encryption mode"); return RNP_ERROR_BAD_PARAMETERS; } @@ -619,17 +758,94 @@ encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng) /* data is encrypted */ size_t keysize = pgp_key_size(key->sec_protection.symm_alg); size_t blsize = pgp_block_size(key->sec_protection.symm_alg); + rnp::secure_array keybuf; + if (!keysize || !blsize) { RNP_LOG("wrong symm alg"); return RNP_ERROR_BAD_PARAMETERS; } - /* generate iv and s2k salt */ - rng.get(key->sec_protection.iv, blsize); + /* generate s2k salt */ if ((key->sec_protection.s2k.specifier != PGP_S2KS_SIMPLE)) { - rng.get(key->sec_protection.s2k.salt, PGP_SALT_SIZE); + rng.get(key->sec_protection.s2k.salt, + key->sec_protection.s2k.salt_size(key->sec_protection.s2k.specifier)); + } + +#if defined(ENABLE_CRYPTO_REFRESH) + /* AEAD case */ + if (key->sec_protection.s2k.usage == PGP_S2KU_AEAD) { + switch (key->version) { + case PGP_V4: + FALLTHROUGH_STATEMENT; + case PGP_V6: + break; + default: + RNP_LOG("AEAD secret-key encryption only defined for v4 and v6 packets"); + return RNP_ERROR_BAD_STATE; + } + /* check for reasonable symmetric algorithm */ + switch (key->sec_protection.symm_alg) { + case PGP_SA_AES_128: + FALLTHROUGH_STATEMENT; + case PGP_SA_AES_192: + FALLTHROUGH_STATEMENT; + case PGP_SA_AES_256: + FALLTHROUGH_STATEMENT; + case PGP_SA_TWOFISH: + FALLTHROUGH_STATEMENT; + case PGP_SA_CAMELLIA_128: + FALLTHROUGH_STATEMENT; + case PGP_SA_CAMELLIA_192: + FALLTHROUGH_STATEMENT; + case PGP_SA_CAMELLIA_256: + break; + default: + RNP_LOG("Not using outdated symmetric algorithm in combination with AEAD " + "encryption"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp::secure_vector key_vec(keysize); + + // For OpenPGP AEAD modes (OCB, EAX, GCM) ciphertext len = plaintext len + tag len + size_t tag_len = pgp_cipher_aead_tag_len(key->sec_protection.aead_alg); + size_t ciphertext_size = body.size() + tag_len; + rnp::secure_vector encdata(ciphertext_size); + + /* generate IV */ + size_t nonce_len = pgp_cipher_aead_nonce_len(key->sec_protection.aead_alg); + rng.get(key->sec_protection.iv, nonce_len); + /* derive key */ + if (!pgp_s2k_derive_key( + &key->sec_protection.s2k, password, key_vec.data(), keysize)) { + RNP_LOG("failed to derive key"); + return RNP_ERROR_BAD_PARAMETERS; + } + + rnp::secure_vector plaintext(body.data(), body.data() + body.size()); + rnp_result_t ret = crypt_secret_key_aead(key, key_vec, plaintext, encdata, false); + if (ret) { + RNP_LOG("could not successfully encrypt key"); + return ret; + } + + secure_clear(key->sec_data, key->sec_len); + free(key->sec_data); + key->sec_data = (uint8_t *) malloc(encdata.size()); + if (!key->sec_data) { + RNP_LOG("allocation failed"); + return RNP_ERROR_OUT_OF_MEMORY; + } + memcpy(key->sec_data, encdata.data(), encdata.size()); + key->sec_len = encdata.size(); + key->material->clear_secret(); + return RNP_SUCCESS; } +#endif + + /* CFB case */ + /* generate IV */ + rng.get(key->sec_protection.iv, blsize); /* derive key */ - rnp::secure_array keybuf; if (!pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) { RNP_LOG("failed to derive key"); return RNP_ERROR_BAD_PARAMETERS; @@ -639,10 +855,11 @@ encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng) if (!pgp_cipher_cfb_start( &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) { RNP_LOG("failed to start cfb encryption"); - return RNP_ERROR_DECRYPT_FAILED; + return RNP_ERROR_ENCRYPT_FAILED; } pgp_cipher_cfb_encrypt(&crypt, body.data(), body.data(), body.size()); pgp_cipher_cfb_finish(&crypt); + secure_clear(key->sec_data, key->sec_len); free(key->sec_data); key->sec_data = (uint8_t *) malloc(body.size()); @@ -846,24 +1063,6 @@ pgp_key_pkt_t::~pgp_key_pkt_t() free(sec_data); } -#if defined(ENABLE_CRYPTO_REFRESH) -uint8_t -pgp_key_pkt_t::s2k_specifier_len(pgp_s2k_specifier_t specifier) -{ - switch (specifier) { - case PGP_S2KS_SIMPLE: - return 2; - case PGP_S2KS_SALTED: - return 10; - case PGP_S2KS_ITERATED_AND_SALTED: - return 11; - default: - RNP_LOG("invalid specifier"); - throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); - } -} -#endif - void pgp_key_pkt_t::make_s2k_params(pgp_packet_body_t &hbody) { @@ -873,13 +1072,7 @@ pgp_key_pkt_t::make_s2k_params(pgp_packet_body_t &hbody) case PGP_S2KU_ENCRYPTED_AND_HASHED: case PGP_S2KU_ENCRYPTED: { hbody.add_byte(sec_protection.symm_alg); -#if defined(ENABLE_CRYPTO_REFRESH) - if (version == PGP_V6) { - // V6 packages contain length of the following field - hbody.add_byte(s2k_specifier_len(sec_protection.s2k.specifier)); - } -#endif - hbody.add(sec_protection.s2k); + hbody.add(sec_protection.s2k, version); if (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { size_t blsize = pgp_block_size(sec_protection.symm_alg); if (!blsize) { @@ -890,6 +1083,20 @@ pgp_key_pkt_t::make_s2k_params(pgp_packet_body_t &hbody) } break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_S2KU_AEAD: { + hbody.add_byte(sec_protection.symm_alg); + hbody.add_byte(sec_protection.aead_alg); + hbody.add(sec_protection.s2k, version); + size_t nonce_len = pgp_cipher_aead_nonce_len(sec_protection.aead_alg); + if (!nonce_len) { + RNP_LOG("invalid nonce size"); + throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); + } + hbody.add(sec_protection.iv, nonce_len); + break; + } +#endif default: RNP_LOG("wrong s2k usage"); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); @@ -1077,9 +1284,10 @@ pgp_key_pkt_t::parse(pgp_source_t &src) } #if defined(ENABLE_CRYPTO_REFRESH) if (version == PGP_V6 && sec_protection.s2k.usage != PGP_S2KU_NONE) { - // V6 packages contain the count of the optional 1-byte parameters + // v6 packets contain the count of the following parameters + // ignored for now if (!pkt.get(v5_s2k_len)) { - RNP_LOG("failed to read key protection"); + RNP_LOG("failed to read s2k parameter length"); return RNP_ERROR_BAD_FORMAT; } } @@ -1112,6 +1320,34 @@ pgp_key_pkt_t::parse(pgp_source_t &src) sec_protection.symm_alg = (pgp_symm_alg_t) salg; break; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_S2KU_AEAD: { + uint8_t symm_alg = 0; + uint8_t aead_alg = 0; + if (!pkt.get(symm_alg)) { + RNP_LOG("failed to read key protection (symmetric alg)"); + return RNP_ERROR_BAD_FORMAT; + } + if (!pkt.get(aead_alg)) { + RNP_LOG("failed to read key protection (aead alg)"); + return RNP_ERROR_BAD_FORMAT; + } + if (version == PGP_V6) { + // V6 packages contain the length of the following field + uint8_t s2k_specifier_len; + if (!pkt.get(s2k_specifier_len)) { + RNP_LOG("failed to read key protection (s2k specifier length)"); + } + } + if (!pkt.get(sec_protection.s2k)) { + RNP_LOG("failed to read key protection (s2k)"); + return RNP_ERROR_BAD_FORMAT; + } + sec_protection.symm_alg = (pgp_symm_alg_t) symm_alg; + sec_protection.aead_alg = (pgp_aead_alg_t) aead_alg; + break; + } +#endif default: /* old-style: usage is symmetric algorithm identifier */ sec_protection.symm_alg = (pgp_symm_alg_t) usage; @@ -1122,14 +1358,25 @@ pgp_key_pkt_t::parse(pgp_source_t &src) } /* iv */ - if (sec_protection.s2k.usage && - (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + if ((sec_protection.s2k.usage == PGP_S2KU_ENCRYPTED_AND_HASHED || + sec_protection.s2k.usage == PGP_S2KU_ENCRYPTED) && + sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) { size_t bl_size = pgp_block_size(sec_protection.symm_alg); if (!bl_size || !pkt.get(sec_protection.iv, bl_size)) { RNP_LOG("failed to read iv"); return RNP_ERROR_BAD_FORMAT; } } +#if defined(ENABLE_CRYPTO_REFRESH) + else if ((sec_protection.s2k.usage == PGP_S2KU_AEAD) && + (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL)) { + size_t nonce_len = pgp_cipher_aead_nonce_len(sec_protection.aead_alg); + if (!nonce_len || !pkt.get(sec_protection.iv, nonce_len)) { + RNP_LOG("failed to read iv"); + return RNP_ERROR_BAD_FORMAT; + } + } +#endif /* v5 secret key fields length */ if (version == PGP_V5) { diff --git a/src/librepgp/stream-key.h b/src/librepgp/stream-key.h index 9820089079..8aef387810 100644 --- a/src/librepgp/stream-key.h +++ b/src/librepgp/stream-key.h @@ -76,9 +76,6 @@ typedef struct pgp_key_pkt_t { private: /* create the contents of the algorithm specific public key fields in a separate packet */ void make_s2k_params(pgp_packet_body_t &hbody); -#if defined(ENABLE_CRYPTO_REFRESH) - uint8_t s2k_specifier_len(pgp_s2k_specifier_t specifier); -#endif } pgp_key_pkt_t; /* userid/userattr with all the corresponding signatures */ diff --git a/src/librepgp/stream-packet.cpp b/src/librepgp/stream-packet.cpp index 9419436ea7..0b66bbbd18 100644 --- a/src/librepgp/stream-packet.cpp +++ b/src/librepgp/stream-packet.cpp @@ -632,25 +632,50 @@ bool pgp_packet_body_t::get(pgp_s2k_t &s2k) noexcept { uint8_t spec = 0, halg = 0; - if (!get(spec) || !get(halg)) { + if (!get(spec)) { return false; } s2k.specifier = (pgp_s2k_specifier_t) spec; - s2k.hash_alg = (pgp_hash_alg_t) halg; + +#if defined(ENABLE_CRYPTO_REFRESH) + if (s2k.specifier != PGP_S2KS_ARGON2) +#endif + { + if (!get(halg)) { + return false; + } + s2k.hash_alg = (pgp_hash_alg_t) halg; + } switch (s2k.specifier) { case PGP_S2KS_SIMPLE: return true; case PGP_S2KS_SALTED: - return get(s2k.salt, PGP_SALT_SIZE); + return get(s2k.salt, s2k.salt_size(s2k.specifier)); case PGP_S2KS_ITERATED_AND_SALTED: { uint8_t iter = 0; - if (!get(s2k.salt, PGP_SALT_SIZE) || !get(iter)) { + if (!get(s2k.salt, s2k.salt_size(s2k.specifier)) || !get(iter)) { return false; } s2k.iterations = iter; return true; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_S2KS_ARGON2: + if (!get(s2k.salt, s2k.salt_size(s2k.specifier))) { + return false; + } + if (!get(s2k.argon2_t)) { + return false; + } + if (!get(s2k.argon2_p)) { + return false; + } + if (!get(s2k.argon2_encoded_m)) { + return false; + } + return true; +#endif case PGP_S2KS_EXPERIMENTAL: { try { s2k.experimental = {data_.begin() + pos_, data_.end()}; @@ -813,27 +838,43 @@ pgp_packet_body_t::add(const pgp_curve_t curve) } void -pgp_packet_body_t::add(const pgp_s2k_t &s2k) +pgp_packet_body_t::add(const pgp_s2k_t &s2k, pgp_version_t version) { +#if defined(ENABLE_CRYPTO_REFRESH) + if (version == PGP_V6 && s2k.usage != PGP_S2KU_NONE) { + add_byte(pgp_s2k_t::specifier_len(s2k.specifier)); + } +#endif add_byte(s2k.specifier); - add_byte(s2k.hash_alg); switch (s2k.specifier) { case PGP_S2KS_SIMPLE: + add_byte(s2k.hash_alg); return; case PGP_S2KS_SALTED: - add(s2k.salt, PGP_SALT_SIZE); + add_byte(s2k.hash_alg); + add(s2k.salt, s2k.salt_size(s2k.specifier)); return; case PGP_S2KS_ITERATED_AND_SALTED: { + add_byte(s2k.hash_alg); unsigned iter = s2k.iterations; if (iter > 255) { iter = pgp_s2k_encode_iterations(iter); } - add(s2k.salt, PGP_SALT_SIZE); + add(s2k.salt, s2k.salt_size(s2k.specifier)); add_byte(iter); return; } +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_S2KS_ARGON2: + add(s2k.salt, s2k.salt_size(s2k.specifier)); + add_byte(s2k.argon2_t); + add_byte(s2k.argon2_p); + add_byte(s2k.argon2_encoded_m); + return; +#endif case PGP_S2KS_EXPERIMENTAL: { + add_byte(s2k.hash_alg); if ((s2k.gpg_ext_num != PGP_S2K_GPG_NO_SECRET) && (s2k.gpg_ext_num != PGP_S2K_GPG_SMARTCARD)) { RNP_LOG("Unknown experimental s2k."); @@ -942,18 +983,30 @@ pgp_sk_sesskey_t::write(pgp_dest_t &dst) const } /* S2K specifier */ pktbody.add_byte(s2k.specifier); - pktbody.add_byte(s2k.hash_alg); +#if defined(ENABLE_CRYPTO_REFRESH) + if (s2k.specifier != PGP_S2KS_ARGON2) +#endif + { + pktbody.add_byte(s2k.hash_alg); + } switch (s2k.specifier) { case PGP_S2KS_SIMPLE: break; case PGP_S2KS_SALTED: - pktbody.add(s2k.salt, sizeof(s2k.salt)); + pktbody.add(s2k.salt, s2k.salt_size(s2k.specifier)); break; case PGP_S2KS_ITERATED_AND_SALTED: - pktbody.add(s2k.salt, sizeof(s2k.salt)); + pktbody.add(s2k.salt, s2k.salt_size(s2k.specifier)); pktbody.add_byte(s2k.iterations); break; +#if defined(ENABLE_CRYPTO_REFRESH) + case PGP_S2KS_ARGON2: + pktbody.add(s2k.salt, s2k.salt_size(s2k.specifier)); + pktbody.add_byte(s2k.argon2_t); + pktbody.add_byte(s2k.argon2_p); + pktbody.add_byte(s2k.argon2_encoded_m); +#endif default: RNP_LOG("Unexpected s2k specifier: %d", (int) s2k.specifier); throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS); diff --git a/src/librepgp/stream-packet.h b/src/librepgp/stream-packet.h index 25445d27cb..45381c072f 100644 --- a/src/librepgp/stream-packet.h +++ b/src/librepgp/stream-packet.h @@ -152,7 +152,7 @@ typedef struct pgp_packet_body_t { /** @brief add ec curve description to the packet body */ void add(const pgp_curve_t curve); /** @brief add s2k description to the packet body */ - void add(const pgp_s2k_t &s2k); + void add(const pgp_s2k_t &s2k, pgp_version_t version); /** @brief read 'short-length' packet body (including tag and length bytes) from the source * @param src source to read from * @return RNP_SUCCESS or error code if operation failed diff --git a/src/tests/data/RFC9580/A.12.1.v4-skesk-argon2-aes128.asc b/src/tests/data/RFC9580/A.12.1.v4-skesk-argon2-aes128.asc new file mode 100644 index 0000000000..45d5363913 --- /dev/null +++ b/src/tests/data/RFC9580/A.12.1.v4-skesk-argon2-aes128.asc @@ -0,0 +1,8 @@ +-----BEGIN PGP MESSAGE----- +Comment: Encrypted using AES with 128-bit key +Comment: Session key: 01FE16BBACFD1E7B78EF3B865187374F + +wycEBwScUvg8J/leUNU1RA7N/zE2AQQVnlL8rSLPP5VlQsunlO+ECxHSPgGYGKY+ +YJz4u6F+DDlDBOr5NRQXt/KJIf4m4mOlKyC/uqLbpnLJZMnTq3o79GxBTdIdOzhH +XfA3pqV4mTzF +-----END PGP MESSAGE----- diff --git a/src/tests/data/RFC9580/A.12.2.v4-skesk-argon2-aes192.asc b/src/tests/data/RFC9580/A.12.2.v4-skesk-argon2-aes192.asc new file mode 100644 index 0000000000..3e5ef8b008 --- /dev/null +++ b/src/tests/data/RFC9580/A.12.2.v4-skesk-argon2-aes192.asc @@ -0,0 +1,9 @@ +-----BEGIN PGP MESSAGE----- +Comment: Encrypted using AES with 192-bit key +Comment: Session key: 27006DAE68E509022CE45A14E569E91001C2955... +Comment: Session key: ...AF8DFE194 + +wy8ECAThTKxHFTRZGKli3KNH4UP4AQQVhzLJ2va3FG8/pmpIPd/H/mdoVS5VBLLw +F9I+AdJ1Sw56PRYiKZjCvHg+2bnq02s33AJJoyBexBI4QKATFRkyez2gldJldRys +LVg77Mwwfgl2n/d572WciAM= +-----END PGP MESSAGE----- diff --git a/src/tests/data/RFC9580/A.12.3.v4-skesk-argon2-aes256.asc b/src/tests/data/RFC9580/A.12.3.v4-skesk-argon2-aes256.asc new file mode 100644 index 0000000000..f524791516 --- /dev/null +++ b/src/tests/data/RFC9580/A.12.3.v4-skesk-argon2-aes256.asc @@ -0,0 +1,9 @@ +-----BEGIN PGP MESSAGE----- +Comment: Encrypted using AES with 256-bit key +Comment: Session key: BBEDA55B9AAE63DAC45D4F49D89DACF4AF37FEF... +Comment: Session key: ...C13BAB2F1F8E18FB74580D8B0 + +wzcECQS4eJUgIG/3mcaILEJFpmJ8AQQVnZ9l7KtagdClm9UaQ/Z6M/5roklSGpGu +623YmaXezGj80j4B+Ku1sgTdJo87X1Wrup7l0wJypZls21Uwd67m9koF60eefH/K +95D1usliXOEm8ayQJQmZrjf6K6v9PWwqMQ== +-----END PGP MESSAGE----- diff --git a/src/tests/data/RFC9580/A.5.sample-locked-v6-seckey-argon2-aead.asc b/src/tests/data/RFC9580/A.5.sample-locked-v6-seckey-argon2-aead.asc new file mode 100644 index 0000000000..a29cc2ce7d --- /dev/null +++ b/src/tests/data/RFC9580/A.5.sample-locked-v6-seckey-argon2-aead.asc @@ -0,0 +1,16 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgkC +FARdb9ccngltHraRe25uHuyuAQQVtKipJ0+r5jL4dacGWSAheCWPpITYiyfyIOPS +3gIDyg8f7strd1OB4+LZsUhcIjOMpVHgmiY/IutJkulneoBYwrEGHxsKAAAAQgWC +Y4d/4wMLCQcFFQoOCAwCFgACmwMCHgkiIQbLGGxPBgmml+TVLfpscisMHx4nwYpW +cI9lJewnutmsyQUnCQIHAgAAAACtKCAQPi19In7A5tfORHHbNr/JcIMlNpAnFJin +7wV2wH+q4UWFs7kDsBJ+xP2i8CMEWi7Ha8tPlXGpZR4UruETeh1mhELIj5UeM8T/ +0z+5oX1RHu11j8bZzFDLX9eTsgOdWATHggZjh3/jGQAAACCGkySDZ/nlAV25Ivj0 +gJXdp4SYfy1ZhbEvutFsr15ENf0mCQIUBA5hhGgp2oaavg6mFUXcFMwBBBUuE8qf +9Ock+xwusd+GAglBr5LVyr/lup3xxQvHXFSjjA2haXfoN6xUGRdDEHI6+uevKjVR +v5oAxgu7eJpaXNjCmwYYGwoAAAAsBYJjh3/jApsMIiEGyxhsTwYJppfk1S36bHIr +DB8eJ8GKVnCPZSXsJ7rZrMkAAAAABAEgpukYbZ1ZNfyP5WMUzbUnSGpaUSD5t2Ki +Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt +ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG +-----END PGP PRIVATE KEY BLOCK----- diff --git a/src/tests/ffi-enc.cpp b/src/tests/ffi-enc.cpp index e25a8bdf9b..ac0e1822a0 100644 --- a/src/tests/ffi-enc.cpp +++ b/src/tests/ffi-enc.cpp @@ -1023,6 +1023,95 @@ TEST_F(rnp_tests, test_ffi_decrypt_v6_pkesk_test_vector) rnp_ffi_destroy(ffi); } +TEST_F(rnp_tests, test_ffi_argon2_locked_seckey) +{ + rnp_ffi_t ffi = NULL; + rnp_input_t input = NULL; + rnp_output_t output = NULL; + + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + assert_true( + import_all_keys(ffi, "data/RFC9580/A.5.sample-locked-v6-seckey-argon2-aead.asc")); + + // test key unlock + rnp_key_handle_t key = NULL; + assert_rnp_success(rnp_locate_key(ffi, "keyid", "CB186C4F0609A697", &key)); + assert_rnp_success(rnp_key_unlock(key, "correct horse battery staple")); + // protect with a different passphrase + assert_rnp_success(rnp_key_protect(key, "some other password", NULL, NULL, NULL, 0)); + assert_rnp_success(rnp_key_lock(key)); + assert_rnp_failure(rnp_key_unlock(key, "some incorrect password")); + assert_rnp_success(rnp_key_unlock(key, "some other password")); + rnp_key_handle_destroy(key); + + // test decrypting pkesk + assert_rnp_success(rnp_ffi_set_pass_provider( + ffi, ffi_string_password_provider, (void *) "correct horse battery staple")); + assert_rnp_success(rnp_input_from_path(&input, "data/test_v6_valid_data/v6pkesk.asc")); + assert_non_null(input); + assert_rnp_success(rnp_output_to_path(&output, "decrypted")); + assert_rnp_success(rnp_decrypt(ffi, input, output)); + assert_string_equal(file_to_str("decrypted").c_str(), "Hello, world!"); + assert_int_equal(unlink("decrypted"), 0); + + // cleanup + rnp_input_destroy(input); + rnp_output_destroy(output); + rnp_ffi_destroy(ffi); +} + +TEST_F(rnp_tests, test_ffi_decrypt_argon2_skesk) +{ + const char password[] = "password"; + const char expect_plaintext[] = "Hello, world!"; + std::vector ciphers = {"AES128", "AES192", "AES256"}; + std::vector testdata = { + "data/RFC9580/A.12.1.v4-skesk-argon2-aes128.asc", + "data/RFC9580/A.12.2.v4-skesk-argon2-aes192.asc", + "data/RFC9580/A.12.3.v4-skesk-argon2-aes256.asc", + }; + + for (size_t i = 0; i < size(testdata); i++) { + rnp_ffi_t ffi = NULL; + rnp_input_t input = NULL; + rnp_output_t output = NULL; + rnp_op_verify_t verify; + + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + + /* decrypt */ + assert_rnp_success(rnp_input_from_path(&input, testdata.at(i).c_str())); + assert_rnp_success(rnp_output_to_path(&output, "decrypted")); + assert_rnp_success( + rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) password)); + assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output)); + assert_rnp_success(rnp_op_verify_execute(verify)); + assert_string_equal(file_to_str("decrypted").c_str(), expect_plaintext); + assert_int_equal(unlink("decrypted"), 0); + + /* Check protection info */ + char *mode = NULL; + char *cipher = NULL; + bool valid = false; + assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid)); + assert_string_equal(mode, "cfb-mdc"); + assert_string_equal(cipher, ciphers.at(i).c_str()); + assert_true(valid); + rnp_buffer_destroy(mode); + rnp_buffer_destroy(cipher); + /* Check SKESK count */ + size_t count = 0; + assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count)); + assert_int_equal(count, 1); + + // cleanup + rnp_op_verify_destroy(verify); + rnp_input_destroy(input); + rnp_output_destroy(output); + rnp_ffi_destroy(ffi); + } +} + #if defined(ENABLE_PQC) // NOTE: this tests ML-KEM-ipd test vectors // The final implementation of the PQC draft implementation will use the final NIST standard.