From a429c61f33b918510d2424ffa33ae30550763870 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 24 Feb 2020 19:09:13 -0800 Subject: [PATCH] crypto/spl: implement rsa-oaep --- .../libvapours/include/vapours/crypto.hpp | 2 + .../crypto/crypto_rsa_oaep_decryptor.hpp | 139 ++++++++++++++++++ .../crypto/crypto_rsa_oaep_sha256_decoder.hpp | 50 +++++++ .../crypto_rsa_oaep_sha256_decryptor.hpp | 53 +++++++ .../crypto/impl/crypto_rsa_oaep_impl.hpp | 128 ++++++++++++++++ stratosphere/spl/source/spl_api_impl.cpp | 76 +--------- 6 files changed, 373 insertions(+), 75 deletions(-) create mode 100644 libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_decryptor.hpp create mode 100644 libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp create mode 100644 libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp create mode 100644 libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp diff --git a/libraries/libvapours/include/vapours/crypto.hpp b/libraries/libvapours/include/vapours/crypto.hpp index 7e90f8ce1..8dc5ecb36 100644 --- a/libraries/libvapours/include/vapours/crypto.hpp +++ b/libraries/libvapours/include/vapours/crypto.hpp @@ -21,3 +21,5 @@ #include #include #include +#include +#include diff --git a/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_decryptor.hpp b/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_decryptor.hpp new file mode 100644 index 000000000..98280c44e --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_decryptor.hpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include +#include +#include + +namespace ams::crypto { + + template /* requires HashFunction */ + class RsaOaepDecryptor { + NON_COPYABLE(RsaOaepDecryptor); + NON_MOVEABLE(RsaOaepDecryptor); + public: + static constexpr size_t HashSize = Hash::HashSize; + static constexpr size_t BlockSize = ModulusSize; + static constexpr size_t MaximumExponentSize = ModulusSize; + static constexpr size_t RequiredWorkBufferSize = RsaCalculator::RequiredWorkBufferSize; + private: + enum class State { + None, + Initialized, + Done, + }; + private: + RsaCalculator calculator; + Hash hash; + bool set_label_digest; + u8 label_digest[HashSize]; + State state; + public: + RsaOaepDecryptor() : set_label_digest(false), state(State::None) { /* ... */ } + + ~RsaOaepDecryptor() { + ClearMemory(this->label_digest, sizeof(this->label_digest)); + } + + bool Initialize(const void *mod, size_t mod_size, const void *exp, size_t exp_size) { + this->hash.Initialize(); + this->set_label_digest = false; + if (this->calculator.Initialize(mod, mod_size, exp, exp_size)) { + this->state = State::Initialized; + return true; + } else { + return false; + } + } + + void UpdateLabel(const void *data, size_t size) { + AMS_ASSERT(this->state == State::Initialized); + + this->hash.Update(data, size); + } + + void SetLabelDigest(const void *digest, size_t digest_size) { + AMS_ASSERT(this->state == State::Initialized); + AMS_ABORT_UNLESS(digest_size == sizeof(this->label_digest)); + + std::memcpy(this->label_digest, digest, digest_size); + this->set_label_digest = true; + } + + size_t Decrypt(void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_ASSERT(this->state == State::Initialized); + ON_SCOPE_EXIT { this->state = State::Done; }; + + impl::RsaOaepImpl impl; + u8 message[BlockSize]; + ON_SCOPE_EXIT { ClearMemory(message, sizeof(message)); }; + + if (!this->calculator.ExpMod(message, src, src_size)) { + return false; + } + + if (!this->set_label_digest) { + this->hash.GetHash(this->label_digest, sizeof(this->label_digest)); + this->set_label_digest = true; + } + + return impl.Decode(dst, dst_size, this->label_digest, sizeof(this->label_digest), message, sizeof(message)); + } + + size_t Decrypt(void *dst, size_t dst_size, const void *src, size_t src_size, void *work_buf, size_t work_buf_size) { + AMS_ASSERT(this->state == State::Initialized); + ON_SCOPE_EXIT { this->state = State::Done; }; + + impl::RsaOaepImpl impl; + u8 message[BlockSize]; + ON_SCOPE_EXIT { ClearMemory(message, sizeof(message)); }; + + if (!this->calculator.ExpMod(message, src, src_size, work_buf, work_buf_size)) { + return false; + } + + if (!this->set_label_digest) { + this->hash.GetHash(this->label_digest, sizeof(this->label_digest)); + this->set_label_digest = true; + } + + return impl.Decode(dst, dst_size, this->label_digest, sizeof(this->label_digest), message, sizeof(message)); + } + + static size_t Decrypt(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size) { + RsaOaepDecryptor crypt; + if (!crypt.Initialize(mod, mod_size, exp, exp_size)) { + return 0; + } + crypt.UpdateLabel(lab, lab_size); + return crypt.Decrypt(dst, dst_size, msg, msg_size); + } + + static size_t Decrypt(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size, void *work_buf, size_t work_buf_size) { + RsaOaepDecryptor crypt; + if (!crypt.Initialize(mod, mod_size, exp, exp_size)) { + return 0; + } + crypt.UpdateLabel(lab, lab_size); + return crypt.Decrypt(dst, dst_size, msg, msg_size, work_buf, work_buf_size); + } + + }; + +} diff --git a/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp b/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp new file mode 100644 index 000000000..fe33d20c8 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include +#include +#include + +namespace ams::crypto { + + inline size_t DecodeRsa2048OaepSha256(void *dst, size_t dst_size, const void *label_digest, size_t label_digest_size, const void *src, size_t src_size) { + constexpr size_t BlockSize = 2048 / BITSIZEOF(u8); + AMS_ABORT_UNLESS(src_size == BlockSize); + + impl::RsaOaepImpl oaep; + u8 enc[BlockSize]; + ON_SCOPE_EXIT { ClearMemory(enc, sizeof(enc)); }; + + std::memcpy(enc, src, src_size); + return oaep.Decode(dst, dst_size, label_digest, label_digest_size, enc, sizeof(enc)); + } + + inline size_t DecodeRsa4096OaepSha256(void *dst, size_t dst_size, const void *label_digest, size_t label_digest_size, const void *src, size_t src_size) { + constexpr size_t BlockSize = 4096 / BITSIZEOF(u8); + AMS_ABORT_UNLESS(src_size == BlockSize); + + impl::RsaOaepImpl oaep; + u8 enc[BlockSize]; + ON_SCOPE_EXIT { ClearMemory(enc, sizeof(enc)); }; + + std::memcpy(enc, src, src_size); + return oaep.Decode(dst, dst_size, label_digest, label_digest_size, enc, sizeof(enc)); + } + +} diff --git a/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp b/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp new file mode 100644 index 000000000..f26239bc6 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace ams::crypto { + + namespace impl { + + template + using RsaNOaepSha256Decryptor = ::ams::crypto::RsaOaepDecryptor; + + } + + using Rsa2048OaepSha256Decryptor = ::ams::crypto::impl::RsaNOaepSha256Decryptor<2048>; + using Rsa4096OaepSha256Decryptor = ::ams::crypto::impl::RsaNOaepSha256Decryptor<4096>; + + inline size_t DecryptRsa2048OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size) { + return Rsa2048OaepSha256Decryptor::Decrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, lab, lab_size); + } + + inline size_t DecryptRsa2048OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size, void *work_buf, size_t work_buf_size) { + return Rsa2048OaepSha256Decryptor::Decrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, lab, lab_size, work_buf, work_buf_size); + } + + inline size_t DecryptRsa4096OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size) { + return Rsa4096OaepSha256Decryptor::Decrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, lab, lab_size); + } + + inline size_t DecryptRsa4096OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size, void *work_buf, size_t work_buf_size) { + return Rsa4096OaepSha256Decryptor::Decrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, lab, lab_size, work_buf, work_buf_size); + } + +} diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp new file mode 100644 index 000000000..bfe6afce5 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include +#include + +namespace ams::crypto::impl { + + template /* requires HashFunction */ + class RsaOaepImpl { + NON_COPYABLE(RsaOaepImpl); + NON_MOVEABLE(RsaOaepImpl); + public: + static constexpr size_t HashSize = Hash::HashSize; + private: + static constexpr u8 HeadMagic = 0x00; + private: + static void ComputeHashWithPadding(void *dst, Hash *hash, const void *salt, size_t salt_size) { + /* Initialize our buffer. */ + u8 buf[8 + HashSize]; + std::memset(buf, 0, 8); + hash->GetHash(buf + 8, HashSize); + ON_SCOPE_EXIT { ClearMemory(buf, sizeof(buf)); }; + + + /* Calculate our hash. */ + hash->Initialize(); + hash->Update(buf, sizeof(buf)); + hash->Update(salt, salt_size); + hash->GetHash(dst, HashSize); + } + + static void ApplyMGF1(u8 *dst, size_t dst_size, const void *src, size_t src_size) { + u8 buf[HashSize]; + ON_SCOPE_EXIT { ClearMemory(buf, sizeof(buf)); }; + + const size_t required_iters = (dst_size + HashSize - 1) / HashSize; + for (size_t i = 0; i < required_iters; i++) { + Hash hash; + hash.Initialize(); + hash.Update(src, src_size); + + const u32 tmp = util::ConvertToBigEndian(static_cast(i)); + hash.Update(std::addressof(tmp), sizeof(tmp)); + + hash.GetHash(buf, HashSize); + + const size_t start = HashSize * i; + const size_t end = std::min(dst_size, start + HashSize); + for (size_t j = start; j < end; j++) { + dst[j] ^= buf[j - start]; + } + } + } + public: + RsaOaepImpl() { /* ... */ } + + size_t Decode(void *dst, size_t dst_size, const void *label_digest, size_t label_digest_size, u8 *buf, size_t buf_size) { + /* Check our preconditions. */ + AMS_ABORT_UNLESS(dst_size > 0); + AMS_ABORT_UNLESS(buf_size >= 2 * HashSize + 3); + AMS_ABORT_UNLESS(label_digest_size == HashSize); + + /* Validate sanity byte. */ + bool is_valid = buf[0] == HeadMagic; + + /* Decrypt seed and masked db. */ + size_t db_len = buf_size - HashSize - 1; + u8 *seed = buf + 1; + u8 *db = seed + HashSize; + ApplyMGF1(seed, HashSize, db, db_len); + ApplyMGF1(db, db_len, seed, HashSize); + + /* Check the label digest. */ + is_valid &= IsSameBytes(label_digest, db, HashSize); + + /* Skip past the label digest. */ + db += HashSize; + db_len -= HashSize; + + /* Verify that DB is of the form 0000...0001 < message > */ + s32 msg_ofs = 0; + { + int looking_for_one = 1; + int invalid_db_padding = 0; + int is_zero; + int is_one; + for (size_t i = 0; i < db_len; /* ... */) { + is_zero = (db[i] == 0); + is_one = (db[i] == 1); + msg_ofs += (looking_for_one & is_one) * (static_cast(++i)); + looking_for_one &= ~is_one; + invalid_db_padding |= (looking_for_one & ~is_zero); + } + + is_valid &= (invalid_db_padding == 0); + } + + /* If we're invalid, return zero size. */ + const size_t valid_msg_size = db_len - msg_ofs; + const size_t msg_size = std::min(dst_size, static_cast(is_valid) * valid_msg_size); + + /* Copy to output. */ + std::memcpy(dst, db + msg_ofs, msg_size); + + /* Return copied size. */ + return msg_size; + } + + }; + +} diff --git a/stratosphere/spl/source/spl_api_impl.cpp b/stratosphere/spl/source/spl_api_impl.cpp index 2416b3dee..13a56540e 100644 --- a/stratosphere/spl/source/spl_api_impl.cpp +++ b/stratosphere/spl/source/spl_api_impl.cpp @@ -151,80 +151,6 @@ namespace ams::spl::impl { R_ABORT_UNLESS(svcMapDeviceAddressSpaceAligned(g_se_das_hnd, dd::GetCurrentProcessHandle(), work_buffer_addr, sizeof(g_work_buffer), g_se_mapped_work_buffer_addr, 3)); } - /* RSA OAEP implementation helpers. */ - void CalcMgf1AndXor(void *dst, size_t dst_size, const void *src, size_t src_size) { - uint8_t *dst_u8 = reinterpret_cast(dst); - - u32 ctr = 0; - while (dst_size > 0) { - const size_t cur_size = std::min(size_t(SHA256_HASH_SIZE), dst_size); - dst_size -= cur_size; - - u32 ctr_be = __builtin_bswap32(ctr++); - u8 hash[SHA256_HASH_SIZE]; - { - Sha256Context ctx; - sha256ContextCreate(&ctx); - sha256ContextUpdate(&ctx, src, src_size); - sha256ContextUpdate(&ctx, &ctr_be, sizeof(ctr_be)); - sha256ContextGetHash(&ctx, hash); - } - - for (size_t i = 0; i < cur_size; i++) { - *(dst_u8++) ^= hash[i]; - } - } - } - - size_t DecodeRsaOaep(void *dst, size_t dst_size, const void *label_digest, size_t label_digest_size, const void *src, size_t src_size) { - /* Very basic validation. */ - if (dst_size == 0 || src_size != 0x100 || label_digest_size != SHA256_HASH_SIZE) { - return 0; - } - - u8 block[0x100]; - std::memcpy(block, src, sizeof(block)); - - /* First, validate byte 0 == 0, and unmask DB. */ - int invalid = block[0]; - u8 *salt = block + 1; - u8 *db = salt + SHA256_HASH_SIZE; - CalcMgf1AndXor(salt, SHA256_HASH_SIZE, db, src_size - (1 + SHA256_HASH_SIZE)); - CalcMgf1AndXor(db, src_size - (1 + SHA256_HASH_SIZE), salt, SHA256_HASH_SIZE); - - /* Validate label digest. */ - for (size_t i = 0; i < SHA256_HASH_SIZE; i++) { - invalid |= db[i] ^ reinterpret_cast(label_digest)[i]; - } - - /* Locate message after 00...0001 padding. */ - const u8 *padded_msg = db + SHA256_HASH_SIZE; - size_t padded_msg_size = src_size - (1 + 2 * SHA256_HASH_SIZE); - size_t msg_ind = 0; - int not_found = 1; - int wrong_padding = 0; - size_t i = 0; - while (i < padded_msg_size) { - int zero = (padded_msg[i] == 0); - int one = (padded_msg[i] == 1); - msg_ind += static_cast(not_found & one) * (++i); - not_found &= ~one; - wrong_padding |= (not_found & ~zero); - } - - if (invalid | not_found | wrong_padding) { - return 0; - } - - /* Copy message out. */ - size_t msg_size = padded_msg_size - msg_ind; - if (msg_size > dst_size) { - return 0; - } - std::memcpy(dst, padded_msg + msg_ind, msg_size); - return msg_size; - } - /* Internal RNG functionality. */ Result GenerateRandomBytesInternal(void *out, size_t size) { if (!g_drbg.GenerateRandomBytes(out, size)) { @@ -793,7 +719,7 @@ namespace ams::spl::impl { /* Nintendo doesn't check this result code, but we will. */ R_TRY(SecureExpMod(g_work_buffer, 0x100, base, base_size, mod, mod_size, smc::SecureExpModMode::Lotus)); - size_t data_size = DecodeRsaOaep(dst, dst_size, label_digest, label_digest_size, g_work_buffer, 0x100); + size_t data_size = crypto::DecodeRsa2048OaepSha256(dst, dst_size, label_digest, label_digest_size, g_work_buffer, 0x100); R_UNLESS(data_size > 0, spl::ResultDecryptionFailed()); *out_size = static_cast(data_size);