// The Cryptography API: Next Generation primitives of bcrypt.dll: hashes and HMAC, symmetric ciphers, key agreement, signatures and key // derivation, worked with OpenSSL. A handle a program holds is a small block of its own memory standing for a host object, since programs // check that the handles they are given point at memory; the object buffers programs allocate for hashes and keys stay unused. #include "kernel_context.h" #include "text.h" #ifdef RAVEN_OPENSSL #include #include #include #include #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include namespace raven { #ifdef RAVEN_OPENSSL namespace { constexpr uint32_t status_success = 0, status_invalid_handle = 0xc0000008, status_invalid_parameter = 0xc000000d, status_no_memory = 0xc0000017, status_buffer_too_small = 0xc0000023, status_not_supported = 0xc00000bb, status_invalid_buffer_size = 0xc0000206, status_not_found = 0xc0000225, status_invalid_signature = 0xc000a000, status_auth_tag_mismatch = 0xc000a002, status_unsuccessful = 0xc0000001, status_data_error = 0xc000003e; constexpr uint32_t hmac_flag = 0x8, block_padding = 0x1, pad_none = 0x1, pad_pkcs1 = 0x2, pad_oaep = 0x4, pad_pss = 0x8; constexpr uint32_t magic_rsa_public = 0x31415352, magic_rsa_private = 0x32415352, magic_rsa_full = 0x33415352, magic_dh_public = 0x42504844, magic_dh_private = 0x56504844, magic_dh_parameters = 0x4d504844, magic_ecdh_public_generic = 0x504b4345, magic_ecdh_private_generic = 0x564b4345, magic_ecdsa_public_generic = 0x50444345, magic_ecdsa_private_generic = 0x56444345; enum class Kind { hash, cipher, ecdh, ecdsa, rsa, dh, rng }; struct Algorithm { std::u16string name; Kind kind = Kind::hash; bool hmac = false; bool gmac = false; // AES-GMAC: a MAC keyed by the hash secret, its nonce set as the hash's IV property std::string digest; // the OpenSSL name of a hash std::u16string chaining = u"ChainingModeCBC"; std::string curve; // the OpenSSL group of an elliptic-curve algorithm, once known std::vector dh_parameters; // p and g, big-endian, of the same length uint32_t message_block = 0; }; struct Hash { std::shared_ptr algorithm; std::vector secret, nonce; EVP_MD_CTX *digest = nullptr; EVP_MAC_CTX *mac = nullptr; ~Hash() { EVP_MD_CTX_free(digest); EVP_MAC_CTX_free(mac); } }; struct Key { std::shared_ptr algorithm; // A symmetric key: its bytes, mode and the feedback a call without an IV continues from. std::vector secret, feedback; std::u16string chaining; uint32_t message_block = 0; // An asymmetric key, and what generating one waits for. EVP_PKEY *pair = nullptr; uint32_t bits = 0; std::string curve; std::vector dh_parameters; ~Key() { EVP_PKEY_free(pair); } }; struct Secret { std::vector value; // big-endian, as the agreement computes it }; using Object = std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr>; // The objects behind the handles. A handle is the address of a small block of the process's memory, as CNG's handles point to its // structures there: programs check that one is readable memory before they trust it. class Table { public: std::function allocate; std::function release; // The guest allocator can give the processor to another guest thread, which may come here on the same host thread: it is never // called, nor an object destroyed, with the lock held. uint64_t add(Object object) { const uint64_t handle = allocate ? allocate() : 0; if (!handle) return 0; std::lock_guard guard(lock); objects.emplace(handle, std::move(object)); return handle; } template std::shared_ptr get(uint64_t handle) { std::lock_guard guard(lock); const auto found = objects.find(handle); if (found == objects.end()) return nullptr; const auto *value = std::get_if>(&found->second); return value ? *value : nullptr; } bool remove(uint64_t handle) { Object removed; { std::lock_guard guard(lock); const auto found = objects.find(handle); if (found == objects.end()) return false; removed = std::move(found->second); objects.erase(found); } if (release) release(handle); return true; } private: std::mutex lock; std::map objects; }; Table &table() { static Table instance; return instance; } bool tracing() { static const bool enabled = std::getenv("RAVEN_TRACE_BCRYPT") != nullptr; return enabled; } std::string curve_group(std::u16string_view name) { if (name == u"nistP256" || name == u"secP256r1") return "prime256v1"; if (name == u"nistP384" || name == u"secP384r1") return "secp384r1"; if (name == u"nistP521" || name == u"secP521r1") return "secp521r1"; if (name == u"secP256k1") return "secp256k1"; if (name == u"curve25519") return "X25519"; return {}; } std::u16string curve_name(const std::string &group) { if (group == "prime256v1") return u"nistP256"; if (group == "secp384r1") return u"nistP384"; if (group == "secp521r1") return u"nistP521"; if (group == "secp256k1") return u"secP256k1"; if (group == "X25519") return u"curve25519"; return {}; } size_t field_bytes(const std::string &group) { if (group == "prime256v1" || group == "secp256k1" || group == "X25519") return 32; if (group == "secp384r1") return 48; if (group == "secp521r1") return 66; return 0; } // The algorithm an identifier names, with the hash name and family it stands for. std::shared_ptr make_algorithm(std::u16string_view name, uint32_t flags) { auto algorithm = std::make_shared(); algorithm->name = name; algorithm->hmac = (flags & hmac_flag) != 0; static const std::map hashes{{u"MD5", "MD5"}, {u"SHA1", "SHA1"}, {u"SHA256", "SHA256"}, {u"SHA384", "SHA384"}, {u"SHA512", "SHA512"}, {u"SHA3-256", "SHA3-256"}, {u"SHA3-384", "SHA3-384"}, {u"SHA3-512", "SHA3-512"}}; if (const auto found = hashes.find(name); found != hashes.end()) { algorithm->kind = Kind::hash; algorithm->digest = found->second; } else if (name == u"AES-GMAC") { algorithm->kind = Kind::hash; algorithm->gmac = true; } else if (name == u"AES" || name == u"3DES" || name == u"DES") { algorithm->kind = Kind::cipher; } else if (name == u"ECDH_P256" || name == u"ECDH_P384" || name == u"ECDH_P521" || name == u"ECDH") { algorithm->kind = Kind::ecdh; if (name != u"ECDH") algorithm->curve = curve_group(name == u"ECDH_P256" ? u"nistP256" : name == u"ECDH_P384" ? u"nistP384" : u"nistP521"); } else if (name == u"ECDSA_P256" || name == u"ECDSA_P384" || name == u"ECDSA_P521" || name == u"ECDSA") { algorithm->kind = Kind::ecdsa; if (name != u"ECDSA") algorithm->curve = curve_group(name == u"ECDSA_P256" ? u"nistP256" : name == u"ECDSA_P384" ? u"nistP384" : u"nistP521"); } else if (name == u"RSA" || name == u"RSA_SIGN") { algorithm->kind = Kind::rsa; } else if (name == u"DH") { algorithm->kind = Kind::dh; } else if (name == u"RNG" || name == u"FIPS186DSARNG") { algorithm->kind = Kind::rng; } else { return nullptr; } return algorithm; } // The handles Windows 10 answers for without opening a provider. std::shared_ptr pseudo_algorithm(uint64_t handle) { static const std::map> pseudo{{0x21, {u"MD5", 0}}, {0x31, {u"SHA1", 0}}, {0x41, {u"SHA256", 0}}, {0x51, {u"SHA384", 0}}, {0x61, {u"SHA512", 0}}, {0x81, {u"RNG", 0}}, {0x91, {u"MD5", hmac_flag}}, {0xa1, {u"SHA1", hmac_flag}}, {0xb1, {u"SHA256", hmac_flag}}, {0xc1, {u"SHA384", hmac_flag}}, {0xd1, {u"SHA512", hmac_flag}}, {0xe1, {u"RSA", 0}}, {0xf1, {u"ECDSA", 0}}}; static std::mutex lock; static std::map> made; const auto found = pseudo.find(handle); if (found == pseudo.end()) return nullptr; std::lock_guard guard(lock); auto &algorithm = made[handle]; if (!algorithm) algorithm = make_algorithm(found->second.first, found->second.second); return algorithm; } std::shared_ptr algorithm_of(uint64_t handle) { if (auto algorithm = pseudo_algorithm(handle)) return algorithm; return table().get(handle); } size_t digest_size(const Algorithm &algorithm) { if (algorithm.gmac) return 16; const EVP_MD *md = EVP_get_digestbyname(algorithm.digest.c_str()); return md ? size_t(EVP_MD_get_size(md)) : 0; } size_t digest_block(const Algorithm &algorithm) { if (algorithm.gmac) return 16; const EVP_MD *md = EVP_get_digestbyname(algorithm.digest.c_str()); return md ? size_t(EVP_MD_get_block_size(md)) : 0; } size_t cipher_block(const Algorithm &algorithm) { return algorithm.name == u"AES" ? 16 : 8; } bool start_hash(Hash &hash) { if (hash.algorithm->gmac) { const size_t size = hash.secret.size(); if (size != 16 && size != 24 && size != 32) return false; EVP_MAC *mac = EVP_MAC_fetch(nullptr, "GMAC", nullptr); if (!mac) return false; EVP_MAC_CTX_free(hash.mac); hash.mac = EVP_MAC_CTX_new(mac); EVP_MAC_free(mac); char cipher[16]; std::snprintf(cipher, sizeof(cipher), "AES-%zu-GCM", size * 8); // Until the program sets a nonce the MAC starts from twelve zero bytes, so that creating the hash succeeds as on Windows. static const uint8_t zero_nonce[12] = {}; auto &nonce = hash.nonce; OSSL_PARAM parameters[] = {OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_CIPHER, cipher, 0), OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_IV, nonce.empty() ? const_cast(zero_nonce) : nonce.data(), nonce.empty() ? sizeof(zero_nonce) : nonce.size()), OSSL_PARAM_construct_end()}; return hash.mac && EVP_MAC_init(hash.mac, hash.secret.data(), size, parameters) == 1; } if (hash.algorithm->hmac) { EVP_MAC *mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr); if (!mac) return false; EVP_MAC_CTX_free(hash.mac); hash.mac = EVP_MAC_CTX_new(mac); EVP_MAC_free(mac); OSSL_PARAM parameters[] = {OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, hash.algorithm->digest.data(), 0), OSSL_PARAM_construct_end()}; static const uint8_t none = 0; return hash.mac && EVP_MAC_init(hash.mac, hash.secret.empty() ? &none : hash.secret.data(), hash.secret.size(), parameters) == 1; } if (!hash.digest) hash.digest = EVP_MD_CTX_new(); return hash.digest && EVP_DigestInit_ex(hash.digest, EVP_get_digestbyname(hash.algorithm->digest.c_str()), nullptr) == 1; } bool update_hash(Hash &hash, const std::vector &data) { if (hash.mac) return EVP_MAC_update(hash.mac, data.data(), data.size()) == 1; return EVP_DigestUpdate(hash.digest, data.data(), data.size()) == 1; } std::vector finish_hash(Hash &hash) { std::vector out(EVP_MAX_MD_SIZE); size_t length = 0; unsigned int digest_length = 0; if (hash.mac) { if (EVP_MAC_final(hash.mac, out.data(), &length, out.size()) != 1) return {}; } else { if (EVP_DigestFinal_ex(hash.digest, out.data(), &digest_length) != 1) return {}; length = digest_length; } out.resize(length); return out; } std::vector digest(const std::string &name, const std::vector &data) { std::vector out(EVP_MAX_MD_SIZE); unsigned int length = 0; const EVP_MD *md = EVP_get_digestbyname(name.c_str()); if (!md || EVP_Digest(data.data(), data.size(), out.data(), &length, md, nullptr) != 1) return {}; out.resize(length); return out; } std::string digest_name(std::u16string_view algorithm) { if (algorithm == u"SHA1") return "SHA1"; if (algorithm == u"SHA256") return "SHA256"; if (algorithm == u"SHA384") return "SHA384"; if (algorithm == u"SHA512") return "SHA512"; if (algorithm == u"MD5") return "MD5"; return {}; } std::vector big_endian(const BIGNUM *number, size_t size) { std::vector out(size); if (number && BN_bn2binpad(number, out.data(), int(size)) < 0) out.clear(); return out; } BIGNUM *number(const uint8_t *data, size_t size) { return BN_bin2bn(data, int(size), nullptr); } std::vector bn_param(EVP_PKEY *key, const char *name, size_t size) { BIGNUM *value = nullptr; if (EVP_PKEY_get_bn_param(key, name, &value) != 1) return {}; auto out = big_endian(value, size ? size : size_t(BN_num_bytes(value))); BN_clear_free(value); return out; } EVP_PKEY *from_parameters(const char *type, OSSL_PARAM_BLD *builder, int selection) { OSSL_PARAM *parameters = OSSL_PARAM_BLD_to_param(builder); EVP_PKEY_CTX *context = EVP_PKEY_CTX_new_from_name(nullptr, type, nullptr); EVP_PKEY *key = nullptr; if (parameters && context && EVP_PKEY_fromdata_init(context) == 1) EVP_PKEY_fromdata(context, &key, selection, parameters); EVP_PKEY_CTX_free(context); OSSL_PARAM_free(parameters); return key; } // An elliptic-curve key from its coordinates and, for a private key, its scalar. EVP_PKEY *ec_key(const std::string &group, const uint8_t *x, const uint8_t *y, size_t size, const uint8_t *d) { OSSL_PARAM_BLD *builder = OSSL_PARAM_BLD_new(); EVP_PKEY *key = nullptr; BIGNUM *scalar = nullptr; if (group == "X25519") { OSSL_PARAM_BLD_push_octet_string(builder, OSSL_PKEY_PARAM_PUB_KEY, x, size); if (d) OSSL_PARAM_BLD_push_octet_string(builder, OSSL_PKEY_PARAM_PRIV_KEY, d, size); key = from_parameters("X25519", builder, d ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY); OSSL_PARAM_BLD_free(builder); return key; } std::vector point{0x04}; point.insert(point.end(), x, x + size); point.insert(point.end(), y, y + size); OSSL_PARAM_BLD_push_utf8_string(builder, OSSL_PKEY_PARAM_GROUP_NAME, group.c_str(), 0); OSSL_PARAM_BLD_push_octet_string(builder, OSSL_PKEY_PARAM_PUB_KEY, point.data(), point.size()); if (d) { scalar = number(d, size); OSSL_PARAM_BLD_push_BN(builder, OSSL_PKEY_PARAM_PRIV_KEY, scalar); } key = from_parameters("EC", builder, d ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY); BN_clear_free(scalar); OSSL_PARAM_BLD_free(builder); return key; } std::string key_group(EVP_PKEY *key) { if (EVP_PKEY_is_a(key, "X25519")) return "X25519"; char name[64] = {}; size_t length = 0; if (EVP_PKEY_get_utf8_string_param(key, OSSL_PKEY_PARAM_GROUP_NAME, name, sizeof(name), &length) != 1) return {}; return name; } std::optional, std::vector>> ec_point(EVP_PKEY *key, size_t size) { std::vector encoded(1 + 2 * size + 8); size_t length = 0; if (EVP_PKEY_is_a(key, "X25519")) { if (EVP_PKEY_get_octet_string_param(key, OSSL_PKEY_PARAM_PUB_KEY, encoded.data(), encoded.size(), &length) != 1) return std::nullopt; encoded.resize(length); return std::pair{encoded, std::vector(size)}; } EVP_PKEY_set_utf8_string_param(key, OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT, "uncompressed"); if (EVP_PKEY_get_octet_string_param(key, OSSL_PKEY_PARAM_PUB_KEY, encoded.data(), encoded.size(), &length) != 1 || length != 1 + 2 * size) return std::nullopt; return std::pair{std::vector(encoded.begin() + 1, encoded.begin() + 1 + size), std::vector(encoded.begin() + 1 + size, encoded.begin() + 1 + 2 * size)}; } uint32_t ec_magic(Kind kind, const std::string &group, bool private_key, bool generic) { if (generic || group == "X25519" || group == "secp256k1") return kind == Kind::ecdh ? (private_key ? magic_ecdh_private_generic : magic_ecdh_public_generic) : (private_key ? magic_ecdsa_private_generic : magic_ecdsa_public_generic); const uint32_t index = group == "prime256v1" ? 1 : group == "secp384r1" ? 3 : 5; const uint32_t base = kind == Kind::ecdh ? 0x304b4345 : 0x30534345; // ECK0, ECS0 return base + index + (private_key ? 1 : 0); } EVP_PKEY *rsa_key(const std::vector &blob, bool private_key) { if (blob.size() < 24) return nullptr; uint32_t header[6]; std::memcpy(header, blob.data(), 24); const size_t exponent = header[2], modulus = header[3], prime1 = header[4], prime2 = header[5]; if (blob.size() < 24 + exponent + modulus + (private_key ? prime1 + prime2 : 0)) return nullptr; const uint8_t *at = blob.data() + 24; BIGNUM *e = number(at, exponent), *n = number(at + exponent, modulus), *p = nullptr, *q = nullptr, *d = nullptr; OSSL_PARAM_BLD *builder = OSSL_PARAM_BLD_new(); OSSL_PARAM_BLD_push_BN(builder, OSSL_PKEY_PARAM_RSA_N, n); OSSL_PARAM_BLD_push_BN(builder, OSSL_PKEY_PARAM_RSA_E, e); BN_CTX *bn = BN_CTX_new(); if (private_key && prime1 && prime2) { // The private blob holds the primes; the private exponent follows from them, or comes after them in a full blob. p = number(at + exponent + modulus, prime1); q = number(at + exponent + modulus + prime1, prime2); if (header[0] == magic_rsa_full && blob.size() >= 24 + exponent + modulus * 2 + prime1 * 3 + prime2 * 2) { d = number(at + exponent + modulus + prime1 * 2 + prime2 * 2 + prime1, modulus); } else { BIGNUM *p1 = BN_dup(p), *q1 = BN_dup(q), *phi = BN_new(); BN_sub_word(p1, 1); BN_sub_word(q1, 1); BN_mul(phi, p1, q1, bn); d = BN_mod_inverse(nullptr, e, phi, bn); BN_free(p1); BN_free(q1); BN_free(phi); } OSSL_PARAM_BLD_push_BN(builder, OSSL_PKEY_PARAM_RSA_D, d); OSSL_PARAM_BLD_push_BN(builder, OSSL_PKEY_PARAM_RSA_FACTOR1, p); OSSL_PARAM_BLD_push_BN(builder, OSSL_PKEY_PARAM_RSA_FACTOR2, q); } EVP_PKEY *key = from_parameters("RSA", builder, private_key ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY); OSSL_PARAM_BLD_free(builder); BN_CTX_free(bn); BN_free(e); BN_free(n); BN_clear_free(p); BN_clear_free(q); BN_clear_free(d); return key; } std::vector rsa_blob(EVP_PKEY *key, uint32_t magic) { const auto n = bn_param(key, OSSL_PKEY_PARAM_RSA_N, 0), e = bn_param(key, OSSL_PKEY_PARAM_RSA_E, 0); std::vector p, q; if (magic != magic_rsa_public) { p = bn_param(key, OSSL_PKEY_PARAM_RSA_FACTOR1, 0); q = bn_param(key, OSSL_PKEY_PARAM_RSA_FACTOR2, 0); if (p.empty() || q.empty()) return {}; } const uint32_t header[6] = {magic, uint32_t(EVP_PKEY_get_bits(key)), uint32_t(e.size()), uint32_t(n.size()), uint32_t(p.size()), uint32_t(q.size())}; std::vector blob(reinterpret_cast(header), reinterpret_cast(header) + 24); blob.insert(blob.end(), e.begin(), e.end()); blob.insert(blob.end(), n.begin(), n.end()); blob.insert(blob.end(), p.begin(), p.end()); blob.insert(blob.end(), q.begin(), q.end()); if (magic == magic_rsa_full) { for (const char *name : {OSSL_PKEY_PARAM_RSA_EXPONENT1, OSSL_PKEY_PARAM_RSA_EXPONENT2}) { const auto value = bn_param(key, name, name == std::string_view(OSSL_PKEY_PARAM_RSA_EXPONENT1) ? p.size() : q.size()); blob.insert(blob.end(), value.begin(), value.end()); } const auto coefficient = bn_param(key, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, p.size()), d = bn_param(key, OSSL_PKEY_PARAM_RSA_D, n.size()); blob.insert(blob.end(), coefficient.begin(), coefficient.end()); blob.insert(blob.end(), d.begin(), d.end()); } return blob; } EVP_PKEY *dh_key(const std::vector ¶meters, const uint8_t *public_value, const uint8_t *private_value) { const size_t size = parameters.size() / 2; BIGNUM *p = number(parameters.data(), size), *g = number(parameters.data() + size, size), *y = nullptr, *x = nullptr; OSSL_PARAM_BLD *builder = OSSL_PARAM_BLD_new(); OSSL_PARAM_BLD_push_BN(builder, OSSL_PKEY_PARAM_FFC_P, p); OSSL_PARAM_BLD_push_BN(builder, OSSL_PKEY_PARAM_FFC_G, g); int selection = EVP_PKEY_KEY_PARAMETERS; if (public_value) { y = number(public_value, size); OSSL_PARAM_BLD_push_BN(builder, OSSL_PKEY_PARAM_PUB_KEY, y); selection = EVP_PKEY_PUBLIC_KEY; } if (private_value) { x = number(private_value, size); OSSL_PARAM_BLD_push_BN(builder, OSSL_PKEY_PARAM_PRIV_KEY, x); selection = EVP_PKEY_KEYPAIR; } EVP_PKEY *key = from_parameters("DH", builder, selection); OSSL_PARAM_BLD_free(builder); BN_free(p); BN_free(g); BN_free(y); BN_clear_free(x); return key; } // A DH parameter blob (header, then p and g) as the two numbers. std::optional> dh_parameters(const std::vector &blob) { if (blob.size() < 12) return std::nullopt; uint32_t header[3]; std::memcpy(header, blob.data(), 12); if (header[1] != magic_dh_parameters || header[0] != blob.size() || blob.size() != 12 + 2 * size_t(header[2])) return std::nullopt; return std::vector(blob.begin() + 12, blob.end()); } const EVP_CIPHER *cipher_for(const Key &key, bool &stream) { const auto &mode = key.chaining; const bool aes = key.algorithm->name == u"AES"; const size_t bits = key.secret.size() * 8; std::string name; stream = false; if (aes) { name = "AES-" + std::to_string(bits) + "-"; if (mode == u"ChainingModeCBC") name += "CBC"; else if (mode == u"ChainingModeECB") name += "ECB"; else if (mode == u"ChainingModeCFB") name += key.message_block == 16 ? "CFB" : "CFB8", stream = true; else if (mode == u"ChainingModeGCM") name += "GCM", stream = true; else return nullptr; } else if (key.algorithm->name == u"3DES") { name = mode == u"ChainingModeECB" ? "DES-EDE3-ECB" : mode == u"ChainingModeCFB" ? "DES-EDE3-CFB8" : "DES-EDE3-CBC"; stream = mode == u"ChainingModeCFB"; } else { return nullptr; } return EVP_get_cipherbyname(name.c_str()); } } void register_bcrypt_api(ApiRegistry ®istry, KernelContext &context) { table().allocate = [&context] { return context.allocator.allocate(16, true); }; table().release = [&context](uint64_t handle) { context.allocator.release(handle); }; const auto trace = [](const char *format, auto... values) { if (tracing()) std::fprintf(stderr, format, values...); }; // Answers the size a buffer must have, writes the value when the buffer holds it, and reports the size either way. const auto answer = [](ApiCall &call, uint64_t buffer, uint32_t capacity, uint64_t result, const std::vector &value) { if (result) call.store(result, uint32_t(value.size())); if (!buffer) return status_success; if (capacity < value.size()) return status_buffer_too_small; call.write(buffer, value); return status_success; }; const auto dword = [](uint32_t value) { return std::vector(reinterpret_cast(&value), reinterpret_cast(&value) + 4); }; const auto wide_value = [](std::u16string_view text) { std::vector out; for (const auto character : text) { out.push_back(uint8_t(character)); out.push_back(uint8_t(character >> 8)); } out.insert(out.end(), {0, 0}); return out; }; registry.add("BCryptOpenAlgorithmProvider", 4, [trace](ApiCall &call) { const auto name = call[1] ? call.wide(call[1], 64) : std::u16string(); trace("bcrypt: open %s flags %x\n", to_utf8(name).c_str(), call.u32(3)); if (!call[0] || name.empty()) { call.ret(status_invalid_parameter); return; } auto algorithm = make_algorithm(name, call.u32(3)); if (!algorithm) { if (std::getenv("RAVEN_TRACE_FAIL")) std::fprintf(stderr, "raven: bcrypt algorithm %s is not supported\n", to_utf8(name).c_str()); call.store_pointer(call[0], 0); call.ret(status_not_found); return; } const auto added = table().add(algorithm); if (!added) { call.ret(status_no_memory); return; } call.store_pointer(call[0], added); call.ret(status_success); }); registry.add("BCryptCloseAlgorithmProvider", 2, [](ApiCall &call) { call.ret(pseudo_algorithm(call[0]) || table().remove(call[0]) ? status_success : status_invalid_handle); }); registry.add("BCryptGetProperty", 6, [answer, dword, wide_value, trace](ApiCall &call) { const auto property = call[1] ? call.wide(call[1], 64) : std::u16string(); const uint64_t buffer = call[2], result = call[4]; const uint32_t capacity = call.u32(3); std::shared_ptr algorithm = algorithm_of(call[0]); std::shared_ptr key; std::shared_ptr hash; if (!algorithm) { if ((key = table().get(call[0]))) algorithm = key->algorithm; else if ((hash = table().get(call[0]))) algorithm = hash->algorithm; } trace("bcrypt: get property %s\n", to_utf8(property).c_str()); if (!algorithm) { call.ret(status_invalid_handle); return; } std::optional> value; const auto chaining = key ? key->chaining : algorithm->chaining; const auto curve = key && !key->curve.empty() ? key->curve : algorithm->curve; if (property == u"ObjectLength" || property == u"HashObjectLength" || property == u"KeyObjectLength") value = dword(64); else if (property == u"HashDigestLength" && algorithm->kind == Kind::hash) value = dword(uint32_t(digest_size(*algorithm))); else if (property == u"HashBlockLength" && algorithm->kind == Kind::hash) value = dword(uint32_t(digest_block(*algorithm))); else if (property == u"BlockLength" && algorithm->kind == Kind::cipher) value = dword(chaining == u"ChainingModeGCM" ? 16 : uint32_t(cipher_block(*algorithm))); else if (property == u"BlockLength" && algorithm->kind == Kind::hash) value = dword(uint32_t(digest_block(*algorithm))); else if (property == u"ChainingMode" && algorithm->kind == Kind::cipher) value = wide_value(chaining); else if (property == u"AlgorithmName") value = wide_value(algorithm->name); else if (property == u"KeyLengths" && algorithm->kind == Kind::cipher) { const uint32_t lengths[3] = {algorithm->name == u"AES" ? 128u : 192u, algorithm->name == u"AES" ? 256u : 192u, algorithm->name == u"AES" ? 64u : 0u}; value = std::vector(reinterpret_cast(lengths), reinterpret_cast(lengths) + 12); } else if (property == u"AuthTagLength" && algorithm->kind == Kind::cipher) { const uint32_t lengths[3] = {12, 16, 1}; value = std::vector(reinterpret_cast(lengths), reinterpret_cast(lengths) + 12); } else if (property == u"MessageBlockLength" && algorithm->kind == Kind::cipher) value = dword(key ? key->message_block : algorithm->message_block); else if ((property == u"KeyLength" || property == u"KeyStrength") && key) { if (key->pair) value = dword(uint32_t(EVP_PKEY_get_bits(key->pair))); else if (key->bits) value = dword(key->bits); else value = dword(uint32_t(key->secret.size() * 8)); } else if (property == u"ECCCurveName" && !curve.empty()) value = wide_value(curve_name(curve)); else if (property == u"SignatureLength" && key && key->pair) value = dword(algorithm->kind == Kind::ecdsa ? uint32_t(2 * field_bytes(curve)) : uint32_t(EVP_PKEY_get_size(key->pair))); else if (property == u"KeyLengths" && (algorithm->kind == Kind::ecdh || algorithm->kind == Kind::ecdsa)) { const uint32_t bits = curve == "secp384r1" ? 384 : curve == "secp521r1" ? 521 : 256; const uint32_t lengths[3] = {bits, bits, 0}; value = std::vector(reinterpret_cast(lengths), reinterpret_cast(lengths) + 12); } else if (property == u"KeyLengths" && (algorithm->kind == Kind::rsa || algorithm->kind == Kind::dh)) { const uint32_t lengths[3] = {512, algorithm->kind == Kind::rsa ? 16384u : 4096u, 64}; value = std::vector(reinterpret_cast(lengths), reinterpret_cast(lengths) + 12); } else if (property == u"PaddingSchemes" && algorithm->kind == Kind::rsa) value = dword(pad_none | pad_pkcs1 | pad_oaep | pad_pss); if (!value) { if (std::getenv("RAVEN_TRACE_FAIL")) std::fprintf(stderr, "raven: bcrypt property %s of %s is not supported\n", to_utf8(property).c_str(), to_utf8(algorithm->name).c_str()); call.ret(status_not_supported); return; } call.ret(answer(call, buffer, capacity, result, *value)); }); registry.add("BCryptSetProperty", 5, [trace](ApiCall &call) { const auto property = call[1] ? call.wide(call[1], 64) : std::u16string(); const auto bytes = call[2] ? call.read(call[2], call.u32(3)) : std::vector(); std::u16string text; for (size_t index = 0; index + 1 < bytes.size(); index += 2) { const char16_t character = char16_t(bytes[index] | bytes[index + 1] << 8); if (!character) break; text.push_back(character); } trace("bcrypt: set property %s %s\n", to_utf8(property).c_str(), to_utf8(text).c_str()); auto algorithm = table().get(call[0]); auto key = algorithm ? nullptr : table().get(call[0]); if (auto hash = algorithm || key ? nullptr : table().get(call[0])) { // The nonce of an AES-GMAC hash, which starts it over. if (property != u"IV" || !hash->algorithm->gmac || bytes.empty()) { call.ret(status_not_supported); return; } hash->nonce = bytes; call.ret(start_hash(*hash) ? status_success : status_invalid_parameter); return; } if (!algorithm && !key) { call.ret(pseudo_algorithm(call[0]) ? status_invalid_parameter : status_invalid_handle); return; } if (property == u"ChainingMode") { if (text != u"ChainingModeCBC" && text != u"ChainingModeECB" && text != u"ChainingModeCFB" && text != u"ChainingModeGCM" && text != u"ChainingModeN/A") { call.ret(status_not_supported); return; } (algorithm ? algorithm->chaining : key->chaining) = text; } else if (property == u"ECCCurveName") { const auto group = curve_group(text); if (group.empty()) { call.ret(status_not_supported); return; } (algorithm ? algorithm->curve : key->curve) = group; } else if (property == u"DHParameters") { const auto parameters = dh_parameters(bytes); if (!parameters) { call.ret(status_invalid_parameter); return; } (algorithm ? algorithm->dh_parameters : key->dh_parameters) = *parameters; } else if (property == u"MessageBlockLength" && bytes.size() == 4) { uint32_t length; std::memcpy(&length, bytes.data(), 4); (algorithm ? algorithm->message_block : key->message_block) = length; } else { if (std::getenv("RAVEN_TRACE_FAIL")) std::fprintf(stderr, "raven: bcrypt property %s cannot be set\n", to_utf8(property).c_str()); call.ret(status_not_supported); return; } call.ret(status_success); }); // ---- hashes ---- const auto create_hash = [](ApiCall &call, uint64_t algorithm_handle, uint64_t out, uint64_t secret, uint32_t secret_size) -> uint32_t { auto algorithm = algorithm_of(algorithm_handle); if (!algorithm || algorithm->kind != Kind::hash) return status_invalid_handle; auto hash = std::make_shared(); hash->algorithm = algorithm; if (secret && secret_size) hash->secret = call.read(secret, secret_size); if (!start_hash(*hash)) return status_unsuccessful; if (!out) return status_invalid_parameter; const auto added = table().add(hash); if (!added) return status_no_memory; call.store_pointer(out, added); return status_success; }; registry.add("BCryptCreateHash", 7, [create_hash](ApiCall &call) { call.ret(create_hash(call, call[0], call[1], call[4], call.u32(5))); }); registry.add("BCryptCreateMultiHash", 8, [](ApiCall &call) { call.ret(status_not_supported); }); registry.add("BCryptHashData", 4, [](ApiCall &call) { auto hash = table().get(call[0]); if (!hash) { call.ret(status_invalid_handle); return; } call.ret(update_hash(*hash, call[1] ? call.read(call[1], call.u32(2)) : std::vector()) ? status_success : status_unsuccessful); }); // Finishing a hash leaves it ready to start over, which a reusable hash promises and a single-use one never uses. registry.add("BCryptFinishHash", 4, [](ApiCall &call) { auto hash = table().get(call[0]); if (!hash) { call.ret(status_invalid_handle); return; } const size_t size = digest_size(*hash->algorithm); if (call.u32(2) != size) { call.ret(status_invalid_parameter); return; } const auto value = finish_hash(*hash); if (value.size() != size) { call.ret(status_unsuccessful); return; } call.write(call[1], value); start_hash(*hash); call.ret(status_success); }); registry.add("BCryptDuplicateHash", 5, [](ApiCall &call) { auto hash = table().get(call[0]); if (!hash || !call[1]) { call.ret(hash ? status_invalid_parameter : status_invalid_handle); return; } auto copy = std::make_shared(); copy->algorithm = hash->algorithm; copy->secret = hash->secret; copy->nonce = hash->nonce; if (hash->mac) copy->mac = EVP_MAC_CTX_dup(hash->mac); else { copy->digest = EVP_MD_CTX_new(); EVP_MD_CTX_copy_ex(copy->digest, hash->digest); } const auto added = table().add(copy); if (!added) { call.ret(status_no_memory); return; } call.store_pointer(call[1], added); call.ret(status_success); }); registry.add("BCryptDestroyHash", 1, [](ApiCall &call) { call.ret(table().remove(call[0]) ? status_success : status_invalid_handle); }); registry.add("BCryptHash", 7, [](ApiCall &call) { auto algorithm = algorithm_of(call[0]); if (!algorithm || algorithm->kind != Kind::hash) { call.ret(status_invalid_handle); return; } Hash hash; hash.algorithm = algorithm; if (call[1] && call.u32(2)) hash.secret = call.read(call[1], call.u32(2)); if (!start_hash(hash) || !update_hash(hash, call[3] ? call.read(call[3], call.u32(4)) : std::vector())) { call.ret(status_unsuccessful); return; } const auto value = finish_hash(hash); if (call.u32(6) != value.size()) { call.ret(status_invalid_parameter); return; } call.write(call[5], value); call.ret(status_success); }); // ---- symmetric keys ---- const auto symmetric_key = [](std::shared_ptr algorithm, std::vector secret) -> std::shared_ptr { const size_t size = secret.size(); if (algorithm->name == u"AES" ? size != 16 && size != 24 && size != 32 : size != 24) return nullptr; auto key = std::make_shared(); key->algorithm = algorithm; key->secret = std::move(secret); key->chaining = algorithm->chaining; key->message_block = algorithm->message_block; key->feedback.assign(cipher_block(*algorithm), 0); return key; }; registry.add("BCryptGenerateSymmetricKey", 7, [symmetric_key](ApiCall &call) { auto algorithm = algorithm_of(call[0]); if (!algorithm || algorithm->kind != Kind::cipher) { call.ret(status_invalid_handle); return; } auto key = symmetric_key(algorithm, call.read(call[4], call.u32(5))); if (!key || !call[1]) { call.ret(status_invalid_parameter); return; } const auto added = table().add(key); if (!added) { call.ret(status_no_memory); return; } call.store_pointer(call[1], added); call.ret(status_success); }); registry.add("BCryptDuplicateKey", 5, [](ApiCall &call) { auto key = table().get(call[0]); if (!key || !call[1]) { call.ret(key ? status_invalid_parameter : status_invalid_handle); return; } auto copy = std::make_shared(*key); if (key->pair) { EVP_PKEY_up_ref(key->pair); copy->pair = key->pair; } const auto added = table().add(copy); if (!added) { call.ret(status_no_memory); return; } call.store_pointer(call[1], added); call.ret(status_success); }); registry.add("BCryptDestroyKey", 1, [](ApiCall &call) { call.ret(table().remove(call[0]) ? status_success : status_invalid_handle); }); registry.add("BCryptDestroySecret", 1, [](ApiCall &call) { call.ret(table().remove(call[0]) ? status_success : status_invalid_handle); }); // BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO: where its fields sit for a program of either width. struct AuthenticatedInfo { uint64_t nonce, nonce_size, data, data_size, tag, tag_size, flags; }; const auto authenticated_info = [](ApiCall &call, uint64_t base) { const size_t width = call.pointer_width(); AuthenticatedInfo at; at.nonce = base + 8; at.nonce_size = at.nonce + width; at.data = width == 8 ? base + 24 : base + 16; at.data_size = at.data + width; at.tag = width == 8 ? base + 40 : base + 24; at.tag_size = at.tag + width; at.flags = width == 8 ? base + 80 : base + 56; return at; }; // Encrypting and decrypting with a symmetric key: the block modes chain through the IV the call passes, which comes back as the // feedback for the next call, as CNG updates it. const auto symmetric = [authenticated_info](ApiCall &call, Key &key, bool encrypt) -> uint32_t { const uint64_t input = call[1], padding_info = call[3], iv = call[4], output = call[6], result = call[8]; const uint32_t input_size = call.u32(2), iv_size = call.u32(5), output_size = call.u32(7), flags = call.u32(9); bool stream = false; const EVP_CIPHER *cipher = cipher_for(key, stream); if (!cipher) return status_not_supported; const size_t block = cipher_block(*key.algorithm); const bool gcm = key.chaining == u"ChainingModeGCM"; const bool padded = (flags & block_padding) && !stream; size_t needed = input_size; if (padded && encrypt) needed = (input_size / block + 1) * block; if (!stream && !padded && input_size % block) return status_invalid_buffer_size; if (!stream && padded && !encrypt && input_size % block) return status_invalid_buffer_size; if (!output) { if (result) call.store(result, uint32_t(needed)); return status_success; } if (output_size < (encrypt || !padded ? needed : input_size - block)) { if (result) call.store(result, uint32_t(needed)); return status_buffer_too_small; } const auto data = input && input_size ? call.read(input, input_size) : std::vector(); std::vector vector, nonce, aad, tag; AuthenticatedInfo info{}; if (gcm) { if (!padding_info) return status_invalid_parameter; info = authenticated_info(call, padding_info); if (call.load(info.flags) & 1) return status_not_supported; // calls chained over one message nonce = call.read(call.load_pointer(info.nonce), call.load(info.nonce_size)); if (const auto pointer = call.load_pointer(info.data); pointer) aad = call.read(pointer, call.load(info.data_size)); tag.resize(call.load(info.tag_size)); if (!encrypt) tag = call.read(call.load_pointer(info.tag), tag.size()); } else if (key.chaining != u"ChainingModeECB") { vector = iv ? call.read(iv, iv_size) : key.feedback; if (vector.size() != block) return status_invalid_parameter; } EVP_CIPHER_CTX *context = EVP_CIPHER_CTX_new(); std::vector out(data.size() + block * 2); int length = 0, tail = 0; bool good = EVP_CipherInit_ex(context, cipher, nullptr, nullptr, nullptr, encrypt) == 1; if (gcm) good = good && EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_IVLEN, int(nonce.size()), nullptr) == 1; good = good && EVP_CipherInit_ex(context, nullptr, nullptr, key.secret.data(), gcm ? nonce.data() : vector.empty() ? nullptr : vector.data(), encrypt) == 1; EVP_CIPHER_CTX_set_padding(context, padded ? 1 : 0); if (gcm && !aad.empty()) good = good && EVP_CipherUpdate(context, nullptr, &length, aad.data(), int(aad.size())) == 1; if (gcm && !encrypt) good = good && EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, int(tag.size()), tag.data()) == 1; good = good && EVP_CipherUpdate(context, out.data(), &length, data.data(), int(data.size())) == 1; const bool finished = good && EVP_CipherFinal_ex(context, out.data() + length, &tail) == 1; if (gcm && encrypt && finished) good = EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, int(tag.size()), tag.data()) == 1; EVP_CIPHER_CTX_free(context); if (!good) return status_unsuccessful; if (!finished) return gcm ? status_auth_tag_mismatch : status_data_error; out.resize(size_t(length + tail)); call.write(output, out); if (result) call.store(result, uint32_t(out.size())); if (gcm && encrypt) call.write(call.load_pointer(info.tag), tag); // The feedback: the last block of ciphertext, or for the byte-wise feedback mode the last bytes of all ciphertext so far. if (!gcm && key.chaining != u"ChainingModeECB") { const auto &ciphertext = encrypt ? out : data; std::vector feedback = vector; feedback.insert(feedback.end(), ciphertext.begin(), ciphertext.end()); if (encrypt && padded) feedback.assign(out.end() - block, out.end()); feedback.erase(feedback.begin(), feedback.end() - block); if (iv) call.write(iv, feedback); else key.feedback = feedback; } return status_success; }; // ---- asymmetric keys ---- const auto rsa_padding = [](ApiCall &call, EVP_PKEY_CTX *context, uint64_t info, uint32_t flags, bool sign) -> bool { if (flags & pad_pkcs1) { if (EVP_PKEY_CTX_set_rsa_padding(context, RSA_PKCS1_PADDING) != 1) return false; if (sign && info) { const auto pointer = call.load_pointer(info); const auto name = pointer ? digest_name(call.wide(pointer, 32)) : std::string(); if (!name.empty() && EVP_PKEY_CTX_set_signature_md(context, EVP_get_digestbyname(name.c_str())) != 1) return false; } return true; } if (flags & pad_pss) { const auto name = digest_name(call.wide(call.load_pointer(info), 32)); const auto salt = call.load(info + call.pointer_width()); return !name.empty() && EVP_PKEY_CTX_set_rsa_padding(context, RSA_PKCS1_PSS_PADDING) == 1 && EVP_PKEY_CTX_set_signature_md(context, EVP_get_digestbyname(name.c_str())) == 1 && EVP_PKEY_CTX_set_rsa_pss_saltlen(context, int(salt)) == 1; } if (flags & pad_oaep) { const auto name = digest_name(call.wide(call.load_pointer(info), 32)); const auto label_at = call.load_pointer(info + call.pointer_width()); const auto label_size = call.load(info + 2 * call.pointer_width()); if (name.empty() || EVP_PKEY_CTX_set_rsa_padding(context, RSA_PKCS1_OAEP_PADDING) != 1 || EVP_PKEY_CTX_set_rsa_oaep_md(context, EVP_get_digestbyname(name.c_str())) != 1) return false; if (label_at && label_size) { const auto label = call.read(label_at, label_size); void *copy = OPENSSL_memdup(label.data(), label.size()); return EVP_PKEY_CTX_set0_rsa_oaep_label(context, copy, int(label.size())) == 1; } return true; } return EVP_PKEY_CTX_set_rsa_padding(context, RSA_NO_PADDING) == 1; }; const auto asymmetric = [rsa_padding](ApiCall &call, Key &key, bool encrypt) -> uint32_t { if (key.algorithm->kind != Kind::rsa || !key.pair) return status_not_supported; const auto data = call.read(call[1], call.u32(2)); EVP_PKEY_CTX *context = EVP_PKEY_CTX_new_from_pkey(nullptr, key.pair, nullptr); size_t length = 0; bool good = context && (encrypt ? EVP_PKEY_encrypt_init(context) : EVP_PKEY_decrypt_init(context)) == 1 && rsa_padding(call, context, call[3], call.u32(9), false); good = good && (encrypt ? EVP_PKEY_encrypt(context, nullptr, &length, data.data(), data.size()) : EVP_PKEY_decrypt(context, nullptr, &length, data.data(), data.size())) == 1; if (!good) { EVP_PKEY_CTX_free(context); return status_invalid_parameter; } if (!call[6]) { EVP_PKEY_CTX_free(context); if (call[8]) call.store(call[8], uint32_t(length)); return status_success; } std::vector out(length); good = (encrypt ? EVP_PKEY_encrypt(context, out.data(), &length, data.data(), data.size()) : EVP_PKEY_decrypt(context, out.data(), &length, data.data(), data.size())) == 1; EVP_PKEY_CTX_free(context); if (!good) return status_data_error; out.resize(length); if (call.u32(7) < out.size()) return status_buffer_too_small; call.write(call[6], out); if (call[8]) call.store(call[8], uint32_t(out.size())); return status_success; }; registry.add("BCryptEncrypt", 10, [symmetric, asymmetric](ApiCall &call) { auto key = table().get(call[0]); call.ret(!key ? status_invalid_handle : key->algorithm->kind == Kind::cipher ? symmetric(call, *key, true) : asymmetric(call, *key, true)); }); registry.add("BCryptDecrypt", 10, [symmetric, asymmetric](ApiCall &call) { auto key = table().get(call[0]); call.ret(!key ? status_invalid_handle : key->algorithm->kind == Kind::cipher ? symmetric(call, *key, false) : asymmetric(call, *key, false)); }); registry.add("BCryptGenerateKeyPair", 4, [trace](ApiCall &call) { auto algorithm = algorithm_of(call[0]); trace("bcrypt: generate key pair of %u bits\n", call.u32(2)); if (!algorithm || algorithm->kind == Kind::hash || algorithm->kind == Kind::cipher || algorithm->kind == Kind::rng || !call[1]) { call.ret(algorithm ? status_invalid_parameter : status_invalid_handle); return; } auto key = std::make_shared(); key->algorithm = algorithm; key->bits = call.u32(2); key->curve = algorithm->curve; key->dh_parameters = algorithm->dh_parameters; if (key->curve.empty() && (algorithm->kind == Kind::ecdh || algorithm->kind == Kind::ecdsa)) key->curve = key->bits == 384 ? "secp384r1" : key->bits == 521 ? "secp521r1" : key->bits == 255 ? "X25519" : "prime256v1"; const auto added = table().add(key); if (!added) { call.ret(status_no_memory); return; } call.store_pointer(call[1], added); call.ret(status_success); }); registry.add("BCryptFinalizeKeyPair", 2, [](ApiCall &call) { auto key = table().get(call[0]); if (!key) { call.ret(status_invalid_handle); return; } if (key->pair) { call.ret(status_invalid_parameter); return; } EVP_PKEY_CTX *context = nullptr; EVP_PKEY *domain = nullptr; switch (key->algorithm->kind) { case Kind::rsa: context = EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr); if (context && EVP_PKEY_keygen_init(context) == 1) EVP_PKEY_CTX_set_rsa_keygen_bits(context, int(key->bits)); break; case Kind::ecdh: case Kind::ecdsa: if (key->curve == "X25519") { context = EVP_PKEY_CTX_new_from_name(nullptr, "X25519", nullptr); if (context) EVP_PKEY_keygen_init(context); } else { context = EVP_PKEY_CTX_new_from_name(nullptr, "EC", nullptr); if (context && EVP_PKEY_keygen_init(context) == 1) EVP_PKEY_CTX_set_group_name(context, key->curve.c_str()); } break; case Kind::dh: if (key->dh_parameters.size() != key->bits / 4) { call.ret(status_invalid_parameter); return; } domain = dh_key(key->dh_parameters, nullptr, nullptr); context = domain ? EVP_PKEY_CTX_new_from_pkey(nullptr, domain, nullptr) : nullptr; if (context) EVP_PKEY_keygen_init(context); break; default: break; } const bool made = context && EVP_PKEY_generate(context, &key->pair) == 1; EVP_PKEY_CTX_free(context); EVP_PKEY_free(domain); call.ret(made ? status_success : status_unsuccessful); }); registry.add("BCryptExportKey", 7, [answer, trace](ApiCall &call) { auto key = table().get(call[0]); const auto type = call[2] ? call.wide(call[2], 64) : std::u16string(); trace("bcrypt: export %s\n", to_utf8(type).c_str()); if (!key) { call.ret(status_invalid_handle); return; } std::vector blob; const auto kind = key->algorithm->kind; const bool private_blob = type == u"ECCPRIVATEBLOB" || type == u"RSAPRIVATEBLOB" || type == u"RSAFULLPRIVATEBLOB" || type == u"DHPRIVATEBLOB"; if (kind == Kind::cipher && (type == u"KeyDataBlob" || type == u"OpaqueKeyBlob")) { const uint32_t header[3] = {0x4d42444b, 1, uint32_t(key->secret.size())}; // KDBM, version 1 blob.assign(reinterpret_cast(header), reinterpret_cast(header) + 12); blob.insert(blob.end(), key->secret.begin(), key->secret.end()); } else if (!key->pair) { call.ret(status_invalid_handle); return; } else if ((kind == Kind::ecdh || kind == Kind::ecdsa) && (type == u"ECCPUBLICBLOB" || type == u"ECCPRIVATEBLOB")) { const auto group = key_group(key->pair); const size_t size = field_bytes(group); const auto point = ec_point(key->pair, size); if (!point) { call.ret(status_unsuccessful); return; } const uint32_t header[2] = {ec_magic(kind, group, private_blob, key->algorithm->curve.empty()), uint32_t(size)}; blob.assign(reinterpret_cast(header), reinterpret_cast(header) + 8); blob.insert(blob.end(), point->first.begin(), point->first.end()); blob.insert(blob.end(), point->second.begin(), point->second.end()); if (private_blob) { std::vector d(size); size_t length = 0; if (group == "X25519") EVP_PKEY_get_octet_string_param(key->pair, OSSL_PKEY_PARAM_PRIV_KEY, d.data(), d.size(), &length); else d = bn_param(key->pair, OSSL_PKEY_PARAM_PRIV_KEY, size); blob.insert(blob.end(), d.begin(), d.end()); } } else if (kind == Kind::rsa && (type == u"RSAPUBLICBLOB" || private_blob)) { blob = rsa_blob(key->pair, type == u"RSAPUBLICBLOB" ? magic_rsa_public : type == u"RSAPRIVATEBLOB" ? magic_rsa_private : magic_rsa_full); } else if (kind == Kind::dh && (type == u"DHPUBLICBLOB" || type == u"DHPRIVATEBLOB")) { const size_t size = key->dh_parameters.size() / 2; const uint32_t header[2] = {private_blob ? magic_dh_private : magic_dh_public, uint32_t(size)}; blob.assign(reinterpret_cast(header), reinterpret_cast(header) + 8); blob.insert(blob.end(), key->dh_parameters.begin(), key->dh_parameters.end()); const auto y = bn_param(key->pair, OSSL_PKEY_PARAM_PUB_KEY, size); blob.insert(blob.end(), y.begin(), y.end()); if (private_blob) { const auto x = bn_param(key->pair, OSSL_PKEY_PARAM_PRIV_KEY, size); blob.insert(blob.end(), x.begin(), x.end()); } } else { if (std::getenv("RAVEN_TRACE_FAIL")) std::fprintf(stderr, "raven: bcrypt export %s is not supported\n", to_utf8(type).c_str()); call.ret(status_not_supported); return; } if (blob.empty()) { call.ret(status_unsuccessful); return; } call.ret(answer(call, call[3], call.u32(4), call[5], blob)); }); registry.add("BCryptImportKeyPair", 7, [trace](ApiCall &call) { auto algorithm = algorithm_of(call[0]); const auto type = call[2] ? call.wide(call[2], 64) : std::u16string(); trace("bcrypt: import %s\n", to_utf8(type).c_str()); if (!algorithm || !call[3]) { call.ret(algorithm ? status_invalid_parameter : status_invalid_handle); return; } const auto blob = call.read(call[4], call.u32(5)); auto key = std::make_shared(); key->algorithm = algorithm; if (blob.size() < 8) { call.ret(status_invalid_parameter); return; } uint32_t header[2]; std::memcpy(header, blob.data(), 8); if ((algorithm->kind == Kind::ecdh || algorithm->kind == Kind::ecdsa) && (type == u"ECCPUBLICBLOB" || type == u"ECCPRIVATEBLOB")) { const size_t size = header[1]; const bool private_blob = type == u"ECCPRIVATEBLOB"; std::string group = algorithm->curve; if (group.empty()) group = size == 32 ? (header[0] == magic_ecdh_public_generic || header[0] == magic_ecdh_private_generic ? "X25519" : "prime256v1") : size == 48 ? "secp384r1" : "secp521r1"; if (field_bytes(group) != size || blob.size() < 8 + size * (private_blob ? 3 : 2)) { call.ret(status_invalid_parameter); return; } key->curve = group; key->pair = ec_key(group, blob.data() + 8, blob.data() + 8 + size, size, private_blob ? blob.data() + 8 + 2 * size : nullptr); } else if (algorithm->kind == Kind::rsa && (type == u"RSAPUBLICBLOB" || type == u"RSAPRIVATEBLOB" || type == u"RSAFULLPRIVATEBLOB")) { key->pair = rsa_key(blob, type != u"RSAPUBLICBLOB"); } else if (algorithm->kind == Kind::dh && (type == u"DHPUBLICBLOB" || type == u"DHPRIVATEBLOB")) { const size_t size = header[1]; const bool private_blob = type == u"DHPRIVATEBLOB"; if (blob.size() < 8 + size * (private_blob ? 4 : 3)) { call.ret(status_invalid_parameter); return; } key->dh_parameters.assign(blob.begin() + 8, blob.begin() + 8 + 2 * size); key->bits = uint32_t(size * 8); key->pair = dh_key(key->dh_parameters, blob.data() + 8 + 2 * size, private_blob ? blob.data() + 8 + 3 * size : nullptr); } else { if (std::getenv("RAVEN_TRACE_FAIL")) std::fprintf(stderr, "raven: bcrypt import %s is not supported\n", to_utf8(type).c_str()); call.ret(status_not_supported); return; } if (!key->pair) { call.ret(status_invalid_parameter); return; } const auto added = table().add(key); if (!added) { call.ret(status_no_memory); return; } call.store_pointer(call[3], added); call.ret(status_success); }); registry.add("BCryptImportKey", 9, [symmetric_key, trace](ApiCall &call) { auto algorithm = algorithm_of(call[0]); const auto type = call[2] ? call.wide(call[2], 64) : std::u16string(); trace("bcrypt: import key %s\n", to_utf8(type).c_str()); if (!algorithm || algorithm->kind != Kind::cipher || !call[3]) { call.ret(algorithm ? status_invalid_parameter : status_invalid_handle); return; } const auto blob = call.read(call[6], call.u32(7)); uint32_t header[3] = {}; if (type != u"KeyDataBlob" || blob.size() < 12 || (std::memcpy(header, blob.data(), 12), header[0] != 0x4d42444b) || blob.size() < 12 + size_t(header[2])) { call.ret(status_not_supported); return; } auto key = symmetric_key(algorithm, std::vector(blob.begin() + 12, blob.begin() + 12 + header[2])); if (!key) { call.ret(status_invalid_parameter); return; } const auto added = table().add(key); if (!added) { call.ret(status_no_memory); return; } call.store_pointer(call[3], added); call.ret(status_success); }); registry.add("BCryptSecretAgreement", 4, [](ApiCall &call) { auto own = table().get(call[0]), peer = table().get(call[1]); if (!own || !peer || !own->pair || !peer->pair || !call[2]) { call.ret(own && peer ? status_invalid_parameter : status_invalid_handle); return; } EVP_PKEY_CTX *context = EVP_PKEY_CTX_new_from_pkey(nullptr, own->pair, nullptr); size_t length = 0; bool good = context && EVP_PKEY_derive_init(context) == 1; if (good && own->algorithm->kind == Kind::dh) EVP_PKEY_CTX_set_dh_pad(context, 1); good = good && EVP_PKEY_derive_set_peer(context, peer->pair) == 1 && EVP_PKEY_derive(context, nullptr, &length) == 1; auto secret = std::make_shared(); secret->value.resize(length); good = good && EVP_PKEY_derive(context, secret->value.data(), &length) == 1; EVP_PKEY_CTX_free(context); if (!good) { call.ret(status_invalid_parameter); return; } secret->value.resize(length); // X25519 gives its secret in little-endian order; the big-endian convention here keeps the raw form below its usual one. if (own->curve == "X25519") std::reverse(secret->value.begin(), secret->value.end()); const auto added = table().add(secret); if (!added) { call.ret(status_no_memory); return; } call.store_pointer(call[2], added); call.ret(status_success); }); // BCryptBufferDesc: version, count, then the buffers, each a size, a type and a pointer. const auto parameter_list = [](ApiCall &call, uint64_t list) { std::map> parameters; if (!list) return parameters; const uint32_t count = call.load(list + 4); const uint64_t buffers = call.load_pointer(list + 8); const size_t stride = 8 + call.pointer_width(); for (uint32_t index = 0; index < count && index < 64; ++index) { const uint64_t at = buffers + index * stride; const uint32_t size = call.load(at), type = call.load(at + 4); const uint64_t data = call.load_pointer(at + 8); auto &value = parameters[type]; if (data && size) value = call.read(data, size); } return parameters; }; const auto parameter_name = [](const std::vector &bytes) { std::u16string text; for (size_t index = 0; index + 1 < bytes.size(); index += 2) { const char16_t character = char16_t(bytes[index] | bytes[index + 1] << 8); if (!character) break; text.push_back(character); } return text; }; registry.add("BCryptDeriveKey", 7, [parameter_list, parameter_name, answer, trace](ApiCall &call) { auto secret = table().get(call[0]); const auto kdf = call[1] ? call.wide(call[1], 64) : std::u16string(); trace("bcrypt: derive %s\n", to_utf8(kdf).c_str()); if (!secret) { call.ret(status_invalid_handle); return; } const auto parameters = parameter_list(call, call[2]); const auto parameter = [&](uint32_t type) { const auto found = parameters.find(type); return found == parameters.end() ? std::vector() : found->second; }; std::vector derived; if (kdf == u"TRUNCATE") { // The raw secret, which CNG gives in little-endian order. derived.assign(secret->value.rbegin(), secret->value.rend()); } else if (kdf == u"HASH" || kdf == u"HMAC") { const auto hash_name = parameters.contains(0) ? digest_name(parameter_name(parameter(0))) : std::string("SHA1"); auto input = parameter(1); input.insert(input.end(), secret->value.begin(), secret->value.end()); const auto append = parameter(2); input.insert(input.end(), append.begin(), append.end()); if (kdf == u"HASH") { derived = digest(hash_name, input); } else { auto key = parameter(3); if (key.empty()) key = secret->value; std::vector out(EVP_MAX_MD_SIZE); size_t length = 0; if (EVP_Q_mac(nullptr, "HMAC", nullptr, hash_name.c_str(), nullptr, key.data(), key.size(), input.data(), input.size(), out.data(), out.size(), &length)) out.resize(length), derived = out; } } else { if (std::getenv("RAVEN_TRACE_FAIL")) std::fprintf(stderr, "raven: bcrypt key derivation %s is not supported\n", to_utf8(kdf).c_str()); call.ret(status_not_supported); return; } if (derived.empty()) { call.ret(status_unsuccessful); return; } if (call[3] && call.u32(4) < derived.size() && kdf != u"TRUNCATE") derived.resize(call.u32(4)); call.ret(answer(call, call[3], call.u32(4), call[5], derived)); }); // Signatures: RSA with the padding the call names, ECDSA as r and s of the curve's size each. registry.add("BCryptSignHash", 8, [rsa_padding](ApiCall &call) { auto key = table().get(call[0]); if (!key || !key->pair) { call.ret(status_invalid_handle); return; } const auto hash = call.read(call[2], call.u32(3)); const bool ecdsa = key->algorithm->kind == Kind::ecdsa; const size_t field = ecdsa ? field_bytes(key_group(key->pair)) : 0; const size_t size = ecdsa ? 2 * field : size_t(EVP_PKEY_get_size(key->pair)); if (!call[4]) { call.store(call[6], uint32_t(size)); call.ret(status_success); return; } if (call.u32(5) < size) { call.ret(status_buffer_too_small); return; } EVP_PKEY_CTX *context = EVP_PKEY_CTX_new_from_pkey(nullptr, key->pair, nullptr); size_t length = 0; bool good = context && EVP_PKEY_sign_init(context) == 1 && (ecdsa || rsa_padding(call, context, call[1], call.u32(7), true)) && EVP_PKEY_sign(context, nullptr, &length, hash.data(), hash.size()) == 1; std::vector signature(length); good = good && EVP_PKEY_sign(context, signature.data(), &length, hash.data(), hash.size()) == 1; EVP_PKEY_CTX_free(context); if (!good) { call.ret(status_invalid_parameter); return; } signature.resize(length); if (ecdsa) { const uint8_t *at = signature.data(); ECDSA_SIG *parsed = d2i_ECDSA_SIG(nullptr, &at, long(signature.size())); if (!parsed) { call.ret(status_unsuccessful); return; } auto r = big_endian(ECDSA_SIG_get0_r(parsed), field), s = big_endian(ECDSA_SIG_get0_s(parsed), field); ECDSA_SIG_free(parsed); signature = r; signature.insert(signature.end(), s.begin(), s.end()); } call.write(call[4], signature); call.store(call[6], uint32_t(signature.size())); call.ret(status_success); }); registry.add("BCryptVerifySignature", 7, [rsa_padding](ApiCall &call) { auto key = table().get(call[0]); if (!key || !key->pair) { call.ret(status_invalid_handle); return; } const auto hash = call.read(call[2], call.u32(3)); auto signature = call.read(call[4], call.u32(5)); const bool ecdsa = key->algorithm->kind == Kind::ecdsa; if (ecdsa) { const size_t field = field_bytes(key_group(key->pair)); if (signature.size() != 2 * field) { call.ret(status_invalid_signature); return; } ECDSA_SIG *parsed = ECDSA_SIG_new(); ECDSA_SIG_set0(parsed, number(signature.data(), field), number(signature.data() + field, field)); unsigned char *encoded = nullptr; const int length = i2d_ECDSA_SIG(parsed, &encoded); ECDSA_SIG_free(parsed); if (length <= 0) { call.ret(status_invalid_signature); return; } signature.assign(encoded, encoded + length); OPENSSL_free(encoded); } EVP_PKEY_CTX *context = EVP_PKEY_CTX_new_from_pkey(nullptr, key->pair, nullptr); const bool good = context && EVP_PKEY_verify_init(context) == 1 && (ecdsa || rsa_padding(call, context, call[1], call.u32(6), true)) && EVP_PKEY_verify(context, signature.data(), signature.size(), hash.data(), hash.size()) == 1; EVP_PKEY_CTX_free(context); call.ret(good ? status_success : status_invalid_signature); }); // Random bytes from the system generator: its pseudo handle, a random number provider, or no handle with the preferred-generator flag. registry.add("BCryptGenRandom", 4, [](ApiCall &call) { auto algorithm = call[0] ? algorithm_of(call[0]) : nullptr; const bool generator = (algorithm && algorithm->kind == Kind::rng) || (!call[0] && (call.u32(3) & 2)); if (!generator || (!call[1] && call.u32(2))) { call.ret(generator ? status_invalid_parameter : status_invalid_handle); return; } std::vector bytes(call.u32(2)); if (!bytes.empty() && RAND_bytes(bytes.data(), int(bytes.size())) != 1) { call.ret(status_unsuccessful); return; } call.write(call[1], bytes); call.ret(status_success); }); // PBKDF2 over an HMAC provider, as programs derive keys from passwords. registry.add("BCryptDeriveKeyPBKDF2", 10, [](ApiCall &call) { auto algorithm = algorithm_of(call[0]); if (!algorithm || algorithm->kind != Kind::hash || !algorithm->hmac) { call.ret(algorithm ? status_invalid_parameter : status_invalid_handle); return; } const auto password = call[1] ? call.read(call[1], call.u32(2)) : std::vector(); const auto salt = call[3] ? call.read(call[3], call.u32(4)) : std::vector(); const uint64_t iterations = call.pointer_width() == 8 ? call[5] : (call[5] | uint64_t(call.u32(6)) << 32); const size_t shift = call.pointer_width() == 8 ? 0 : 1; const uint64_t output = call[6 + shift]; const uint32_t size = call.u32(7 + shift); std::vector derived(size); if (!PKCS5_PBKDF2_HMAC(reinterpret_cast(password.data()), int(password.size()), salt.data(), int(salt.size()), int(iterations), EVP_get_digestbyname(algorithm->digest.c_str()), int(size), derived.data())) { call.ret(status_unsuccessful); return; } call.write(output, derived); call.ret(status_success); }); } #else void register_bcrypt_api(ApiRegistry &, KernelContext &) { } #endif }