From e12ddd75512359f4648c16fb3408017b04aaa88f 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 ++++++++++++++++ 5 files changed, 372 insertions(+) create mode 100644 libvapours/include/vapours/crypto/crypto_rsa_oaep_decryptor.hpp create mode 100644 libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp create mode 100644 libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp create mode 100644 libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp diff --git a/libvapours/include/vapours/crypto.hpp b/libvapours/include/vapours/crypto.hpp index 7e90f8ce..8dc5ecb3 100644 --- a/libvapours/include/vapours/crypto.hpp +++ b/libvapours/include/vapours/crypto.hpp @@ -21,3 +21,5 @@ #include #include #include +#include +#include diff --git a/libvapours/include/vapours/crypto/crypto_rsa_oaep_decryptor.hpp b/libvapours/include/vapours/crypto/crypto_rsa_oaep_decryptor.hpp new file mode 100644 index 00000000..98280c44 --- /dev/null +++ b/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/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp b/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp new file mode 100644 index 00000000..fe33d20c --- /dev/null +++ b/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/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp b/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp new file mode 100644 index 00000000..f26239bc --- /dev/null +++ b/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/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp b/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp new file mode 100644 index 00000000..bfe6afce --- /dev/null +++ b/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; + } + + }; + +}