summaryrefslogtreecommitdiffstats
path: root/src/lib/generate-key.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/generate-key.cpp')
-rw-r--r--src/lib/generate-key.cpp467
1 files changed, 467 insertions, 0 deletions
diff --git a/src/lib/generate-key.cpp b/src/lib/generate-key.cpp
new file mode 100644
index 0000000..dfd5556
--- /dev/null
+++ b/src/lib/generate-key.cpp
@@ -0,0 +1,467 @@
+/*
+ * Copyright (c) 2017, [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
+ * OWNER 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 <stdbool.h>
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+#include <rekey/rnp_key_store.h>
+#include <librekey/key_store_pgp.h>
+#include <librekey/key_store_g10.h>
+#include <librepgp/stream-packet.h>
+#include "crypto.h"
+#include "pgp-key.h"
+#include "defaults.h"
+#include "utils.h"
+
+static const uint8_t DEFAULT_SYMMETRIC_ALGS[] = {
+ PGP_SA_AES_256, PGP_SA_AES_192, PGP_SA_AES_128};
+static const uint8_t DEFAULT_HASH_ALGS[] = {
+ PGP_HASH_SHA256, PGP_HASH_SHA384, PGP_HASH_SHA512, PGP_HASH_SHA224};
+static const uint8_t DEFAULT_COMPRESS_ALGS[] = {
+ PGP_C_ZLIB, PGP_C_BZIP2, PGP_C_ZIP, PGP_C_NONE};
+
+static const id_str_pair pubkey_alg_map[] = {
+ {PGP_PKA_RSA, "RSA (Encrypt or Sign)"},
+ {PGP_PKA_RSA_ENCRYPT_ONLY, "RSA Encrypt-Only"},
+ {PGP_PKA_RSA_SIGN_ONLY, "RSA Sign-Only"},
+ {PGP_PKA_ELGAMAL, "Elgamal (Encrypt-Only)"},
+ {PGP_PKA_DSA, "DSA"},
+ {PGP_PKA_ECDH, "ECDH"},
+ {PGP_PKA_ECDSA, "ECDSA"},
+ {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, "Reserved (formerly Elgamal Encrypt or Sign"},
+ {PGP_PKA_RESERVED_DH, "Reserved for Diffie-Hellman (X9.42)"},
+ {PGP_PKA_EDDSA, "EdDSA"},
+ {PGP_PKA_SM2, "SM2"},
+ {PGP_PKA_PRIVATE00, "Private/Experimental"},
+ {PGP_PKA_PRIVATE01, "Private/Experimental"},
+ {PGP_PKA_PRIVATE02, "Private/Experimental"},
+ {PGP_PKA_PRIVATE03, "Private/Experimental"},
+ {PGP_PKA_PRIVATE04, "Private/Experimental"},
+ {PGP_PKA_PRIVATE05, "Private/Experimental"},
+ {PGP_PKA_PRIVATE06, "Private/Experimental"},
+ {PGP_PKA_PRIVATE07, "Private/Experimental"},
+ {PGP_PKA_PRIVATE08, "Private/Experimental"},
+ {PGP_PKA_PRIVATE09, "Private/Experimental"},
+ {PGP_PKA_PRIVATE10, "Private/Experimental"},
+ {0, NULL}};
+
+static bool
+load_generated_g10_key(pgp_key_t * dst,
+ pgp_key_pkt_t * newkey,
+ pgp_key_t * primary_key,
+ pgp_key_t * pubkey,
+ rnp::SecurityContext &ctx)
+{
+ // this should generally be zeroed
+ assert(dst->type() == 0);
+ // if a primary is provided, make sure it's actually a primary key
+ assert(!primary_key || primary_key->is_primary());
+ // if a pubkey is provided, make sure it's actually a public key
+ assert(!pubkey || pubkey->is_public());
+ // G10 always needs pubkey here
+ assert(pubkey);
+
+ // this would be better on the stack but the key store does not allow it
+ std::unique_ptr<rnp_key_store_t> key_store(new (std::nothrow) rnp_key_store_t(ctx));
+ if (!key_store) {
+ return false;
+ }
+ /* Write g10 seckey */
+ rnp::MemoryDest memdst(NULL, 0);
+ if (!g10_write_seckey(&memdst.dst(), newkey, NULL, ctx)) {
+ RNP_LOG("failed to write generated seckey");
+ return false;
+ }
+
+ std::vector<pgp_key_t *> key_ptrs; /* holds primary and pubkey, when used */
+ // if this is a subkey, add the primary in first
+ if (primary_key) {
+ key_ptrs.push_back(primary_key);
+ }
+ // G10 needs the pubkey for copying some attributes (key version, creation time, etc)
+ key_ptrs.push_back(pubkey);
+
+ rnp::MemorySource memsrc(memdst.memory(), memdst.writeb(), false);
+ pgp_key_provider_t prov(rnp_key_provider_key_ptr_list, &key_ptrs);
+ if (!rnp_key_store_g10_from_src(key_store.get(), &memsrc.src(), &prov)) {
+ return false;
+ }
+ if (rnp_key_store_get_key_count(key_store.get()) != 1) {
+ return false;
+ }
+ // if a primary key is provided, it should match the sub with regards to type
+ assert(!primary_key || (primary_key->is_secret() == key_store->keys.front().is_secret()));
+ *dst = pgp_key_t(key_store->keys.front());
+ return true;
+}
+
+static uint8_t
+pk_alg_default_flags(pgp_pubkey_alg_t alg)
+{
+ // just use the full capabilities as the ultimate fallback
+ return pgp_pk_alg_capabilities(alg);
+}
+
+// TODO: Similar as pgp_pick_hash_alg but different enough to
+// keep another version. This will be changed when refactoring crypto
+static void
+adjust_hash_alg(rnp_keygen_crypto_params_t &crypto)
+{
+ if (!crypto.hash_alg) {
+ crypto.hash_alg = (pgp_hash_alg_t) DEFAULT_HASH_ALGS[0];
+ }
+
+ if ((crypto.key_alg != PGP_PKA_DSA) && (crypto.key_alg != PGP_PKA_ECDSA)) {
+ return;
+ }
+
+ pgp_hash_alg_t min_hash = (crypto.key_alg == PGP_PKA_ECDSA) ?
+ ecdsa_get_min_hash(crypto.ecc.curve) :
+ dsa_get_min_hash(crypto.dsa.q_bitlen);
+
+ if (rnp::Hash::size(crypto.hash_alg) < rnp::Hash::size(min_hash)) {
+ crypto.hash_alg = min_hash;
+ }
+}
+
+static void
+keygen_merge_crypto_defaults(rnp_keygen_crypto_params_t &crypto)
+{
+ // default to RSA
+ if (!crypto.key_alg) {
+ crypto.key_alg = PGP_PKA_RSA;
+ }
+
+ switch (crypto.key_alg) {
+ case PGP_PKA_RSA:
+ if (!crypto.rsa.modulus_bit_len) {
+ crypto.rsa.modulus_bit_len = DEFAULT_RSA_NUMBITS;
+ }
+ break;
+
+ case PGP_PKA_SM2:
+ if (!crypto.hash_alg) {
+ crypto.hash_alg = PGP_HASH_SM3;
+ }
+ if (!crypto.ecc.curve) {
+ crypto.ecc.curve = PGP_CURVE_SM2_P_256;
+ }
+ break;
+
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA: {
+ if (!crypto.hash_alg) {
+ crypto.hash_alg = (pgp_hash_alg_t) DEFAULT_HASH_ALGS[0];
+ }
+ break;
+ }
+
+ case PGP_PKA_EDDSA:
+ if (!crypto.ecc.curve) {
+ crypto.ecc.curve = PGP_CURVE_ED25519;
+ }
+ break;
+
+ case PGP_PKA_DSA: {
+ if (!crypto.dsa.p_bitlen) {
+ crypto.dsa.p_bitlen = DSA_DEFAULT_P_BITLEN;
+ }
+ if (!crypto.dsa.q_bitlen) {
+ crypto.dsa.q_bitlen = dsa_choose_qsize_by_psize(crypto.dsa.p_bitlen);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ adjust_hash_alg(crypto);
+}
+
+static bool
+validate_keygen_primary(const rnp_keygen_primary_desc_t &desc)
+{
+ /* Confirm that the specified pk alg can certify.
+ * gpg requires this, though the RFC only says that a V4 primary
+ * key SHOULD be a key capable of certification.
+ */
+ if (!(pgp_pk_alg_capabilities(desc.crypto.key_alg) & PGP_KF_CERTIFY)) {
+ RNP_LOG("primary key alg (%d) must be able to sign", desc.crypto.key_alg);
+ return false;
+ }
+
+ // check key flags
+ if (!desc.cert.key_flags) {
+ // these are probably not *technically* required
+ RNP_LOG("key flags are required");
+ return false;
+ } else if (desc.cert.key_flags & ~pgp_pk_alg_capabilities(desc.crypto.key_alg)) {
+ // check the flags against the alg capabilities
+ RNP_LOG("usage not permitted for pk algorithm");
+ return false;
+ }
+ // require a userid
+ if (!desc.cert.userid[0]) {
+ RNP_LOG("userid is required for primary key");
+ return false;
+ }
+ return true;
+}
+
+static uint32_t
+get_numbits(const rnp_keygen_crypto_params_t *crypto)
+{
+ switch (crypto->key_alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ return crypto->rsa.modulus_bit_len;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2: {
+ if (const ec_curve_desc_t *curve = get_curve_desc(crypto->ecc.curve)) {
+ return curve->bitlen;
+ } else {
+ return 0;
+ }
+ }
+ case PGP_PKA_DSA:
+ return crypto->dsa.p_bitlen;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ return crypto->elgamal.key_bitlen;
+ default:
+ return 0;
+ }
+}
+
+static void
+set_default_user_prefs(pgp_user_prefs_t &prefs)
+{
+ if (prefs.symm_algs.empty()) {
+ prefs.set_symm_algs(
+ std::vector<uint8_t>(DEFAULT_SYMMETRIC_ALGS,
+ DEFAULT_SYMMETRIC_ALGS + ARRAY_SIZE(DEFAULT_SYMMETRIC_ALGS)));
+ }
+ if (prefs.hash_algs.empty()) {
+ prefs.set_hash_algs(std::vector<uint8_t>(
+ DEFAULT_HASH_ALGS, DEFAULT_HASH_ALGS + ARRAY_SIZE(DEFAULT_HASH_ALGS)));
+ }
+ if (prefs.z_algs.empty()) {
+ prefs.set_z_algs(std::vector<uint8_t>(
+ DEFAULT_COMPRESS_ALGS, DEFAULT_COMPRESS_ALGS + ARRAY_SIZE(DEFAULT_COMPRESS_ALGS)));
+ }
+}
+
+static void
+keygen_primary_merge_defaults(rnp_keygen_primary_desc_t &desc)
+{
+ keygen_merge_crypto_defaults(desc.crypto);
+ set_default_user_prefs(desc.cert.prefs);
+
+ if (!desc.cert.key_flags) {
+ // set some default key flags if none are provided
+ desc.cert.key_flags = pk_alg_default_flags(desc.crypto.key_alg);
+ }
+ if (desc.cert.userid.empty()) {
+ char uid[MAX_ID_LENGTH] = {0};
+ snprintf(uid,
+ sizeof(uid),
+ "%s %d-bit key <%s@localhost>",
+ id_str_pair::lookup(pubkey_alg_map, desc.crypto.key_alg),
+ get_numbits(&desc.crypto),
+ getenv_logname());
+ desc.cert.userid = uid;
+ }
+}
+
+bool
+pgp_generate_primary_key(rnp_keygen_primary_desc_t &desc,
+ bool merge_defaults,
+ pgp_key_t & primary_sec,
+ pgp_key_t & primary_pub,
+ pgp_key_store_format_t secformat)
+{
+ // validate args
+ if (primary_sec.type() || primary_pub.type()) {
+ RNP_LOG("invalid parameters (should be zeroed)");
+ return false;
+ }
+
+ try {
+ // merge some defaults in, if requested
+ if (merge_defaults) {
+ keygen_primary_merge_defaults(desc);
+ }
+ // now validate the keygen fields
+ if (!validate_keygen_primary(desc)) {
+ return false;
+ }
+
+ // generate the raw key and fill tag/secret fields
+ pgp_key_pkt_t secpkt;
+ if (!pgp_generate_seckey(desc.crypto, secpkt, true)) {
+ return false;
+ }
+
+ pgp_key_t sec(secpkt);
+ pgp_key_t pub(secpkt, true);
+ sec.add_uid_cert(desc.cert, desc.crypto.hash_alg, *desc.crypto.ctx, &pub);
+
+ switch (secformat) {
+ case PGP_KEY_STORE_GPG:
+ case PGP_KEY_STORE_KBX:
+ primary_sec = std::move(sec);
+ primary_pub = std::move(pub);
+ break;
+ case PGP_KEY_STORE_G10:
+ primary_pub = std::move(pub);
+ if (!load_generated_g10_key(
+ &primary_sec, &secpkt, NULL, &primary_pub, *desc.crypto.ctx)) {
+ RNP_LOG("failed to load generated key");
+ return false;
+ }
+ break;
+ default:
+ RNP_LOG("invalid format");
+ return false;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("Failure: %s", e.what());
+ return false;
+ }
+
+ /* mark it as valid */
+ primary_pub.mark_valid();
+ primary_sec.mark_valid();
+ /* refresh key's data */
+ return primary_pub.refresh_data(*desc.crypto.ctx) &&
+ primary_sec.refresh_data(*desc.crypto.ctx);
+}
+
+static bool
+validate_keygen_subkey(rnp_keygen_subkey_desc_t &desc)
+{
+ if (!desc.binding.key_flags) {
+ RNP_LOG("key flags are required");
+ return false;
+ } else if (desc.binding.key_flags & ~pgp_pk_alg_capabilities(desc.crypto.key_alg)) {
+ // check the flags against the alg capabilities
+ RNP_LOG("usage not permitted for pk algorithm");
+ return false;
+ }
+ return true;
+}
+
+static void
+keygen_subkey_merge_defaults(rnp_keygen_subkey_desc_t &desc)
+{
+ keygen_merge_crypto_defaults(desc.crypto);
+ if (!desc.binding.key_flags) {
+ // set some default key flags if none are provided
+ desc.binding.key_flags = pk_alg_default_flags(desc.crypto.key_alg);
+ }
+}
+
+bool
+pgp_generate_subkey(rnp_keygen_subkey_desc_t & desc,
+ bool merge_defaults,
+ pgp_key_t & primary_sec,
+ pgp_key_t & primary_pub,
+ pgp_key_t & subkey_sec,
+ pgp_key_t & subkey_pub,
+ const pgp_password_provider_t &password_provider,
+ pgp_key_store_format_t secformat)
+{
+ // validate args
+ if (!primary_sec.is_primary() || !primary_pub.is_primary() || !primary_sec.is_secret() ||
+ !primary_pub.is_public()) {
+ RNP_LOG("invalid parameters");
+ return false;
+ }
+ if (subkey_sec.type() || subkey_pub.type()) {
+ RNP_LOG("invalid parameters (should be zeroed)");
+ return false;
+ }
+
+ // merge some defaults in, if requested
+ if (merge_defaults) {
+ keygen_subkey_merge_defaults(desc);
+ }
+
+ // now validate the keygen fields
+ if (!validate_keygen_subkey(desc)) {
+ return false;
+ }
+
+ try {
+ /* decrypt the primary seckey if needed (for signatures) */
+ rnp::KeyLocker primlock(primary_sec);
+ if (primary_sec.encrypted() &&
+ !primary_sec.unlock(password_provider, PGP_OP_ADD_SUBKEY)) {
+ RNP_LOG("Failed to unlock primary key.");
+ return false;
+ }
+ /* generate the raw subkey */
+ pgp_key_pkt_t secpkt;
+ if (!pgp_generate_seckey(desc.crypto, secpkt, false)) {
+ return false;
+ }
+ pgp_key_pkt_t pubpkt = pgp_key_pkt_t(secpkt, true);
+ pgp_key_t sec(secpkt, primary_sec);
+ pgp_key_t pub(pubpkt, primary_pub);
+ /* add binding */
+ primary_sec.add_sub_binding(
+ sec, pub, desc.binding, desc.crypto.hash_alg, *desc.crypto.ctx);
+ /* copy to the result */
+ subkey_pub = std::move(pub);
+ switch (secformat) {
+ case PGP_KEY_STORE_GPG:
+ case PGP_KEY_STORE_KBX:
+ subkey_sec = std::move(sec);
+ break;
+ case PGP_KEY_STORE_G10:
+ if (!load_generated_g10_key(
+ &subkey_sec, &secpkt, &primary_sec, &subkey_pub, *desc.crypto.ctx)) {
+ RNP_LOG("failed to load generated key");
+ return false;
+ }
+ break;
+ default:
+ RNP_LOG("invalid format");
+ return false;
+ }
+
+ subkey_pub.mark_valid();
+ subkey_sec.mark_valid();
+ return subkey_pub.refresh_data(&primary_pub, *desc.crypto.ctx) &&
+ subkey_sec.refresh_data(&primary_sec, *desc.crypto.ctx);
+ } catch (const std::exception &e) {
+ RNP_LOG("Subkey generation failed: %s", e.what());
+ return false;
+ }
+}