diff options
author | Andreas Kling <kling@serenityos.org> | 2021-01-12 12:17:30 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-01-12 12:17:46 +0100 |
commit | 13d7c09125f8eec703d0a43a9a87fc8aa08f7319 (patch) | |
tree | 70fd643c429cea5c1f9362c2674511d17a53f3b5 /Userland/Libraries/LibCrypto/PK | |
parent | dc28c07fa526841e05e16161c74a6c23984f1dd5 (diff) | |
download | serenity-13d7c09125f8eec703d0a43a9a87fc8aa08f7319.zip |
Libraries: Move to Userland/Libraries/
Diffstat (limited to 'Userland/Libraries/LibCrypto/PK')
-rw-r--r-- | Userland/Libraries/LibCrypto/PK/Code/Code.h | 55 | ||||
-rw-r--r-- | Userland/Libraries/LibCrypto/PK/Code/EMSA_PSS.h | 180 | ||||
-rw-r--r-- | Userland/Libraries/LibCrypto/PK/PK.h | 68 | ||||
-rw-r--r-- | Userland/Libraries/LibCrypto/PK/RSA.cpp | 329 | ||||
-rw-r--r-- | Userland/Libraries/LibCrypto/PK/RSA.h | 235 |
5 files changed, 867 insertions, 0 deletions
diff --git a/Userland/Libraries/LibCrypto/PK/Code/Code.h b/Userland/Libraries/LibCrypto/PK/Code/Code.h new file mode 100644 index 0000000000..b5a70ddb14 --- /dev/null +++ b/Userland/Libraries/LibCrypto/PK/Code/Code.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * 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 HOLDER 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. + */ + +#pragma once + +#include <LibCrypto/Hash/HashFunction.h> +#include <LibCrypto/Verification.h> + +namespace Crypto { +namespace PK { + +template<typename HashFunction> +class Code { +public: + template<typename... Args> + Code(Args... args) + : m_hasher(args...) + { + } + + virtual void encode(ReadonlyBytes in, ByteBuffer& out, size_t em_bits) = 0; + virtual VerificationConsistency verify(ReadonlyBytes msg, ReadonlyBytes emsg, size_t em_bits) = 0; + + const HashFunction& hasher() const { return m_hasher; } + HashFunction& hasher() { return m_hasher; } + +protected: + HashFunction m_hasher; +}; + +} +} diff --git a/Userland/Libraries/LibCrypto/PK/Code/EMSA_PSS.h b/Userland/Libraries/LibCrypto/PK/Code/EMSA_PSS.h new file mode 100644 index 0000000000..95e6e58008 --- /dev/null +++ b/Userland/Libraries/LibCrypto/PK/Code/EMSA_PSS.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * 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 HOLDER 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. + */ + +#pragma once + +#include <AK/Random.h> +#include <LibCrypto/PK/Code/Code.h> + +static constexpr u8 zeros[] { 0, 0, 0, 0, 0, 0, 0, 0 }; + +namespace Crypto { +namespace PK { + +template<typename HashFunction, size_t SaltSize> +class EMSA_PSS : public Code<HashFunction> { +public: + template<typename... Args> + EMSA_PSS(Args... args) + : Code<HashFunction>(args...) + { + m_buffer = Bytes { m_data_buffer, sizeof(m_data_buffer) }; + } + + static constexpr auto SaltLength = SaltSize; + + virtual void encode(ReadonlyBytes in, ByteBuffer& out, size_t em_bits) override + { + // FIXME: we're supposed to check if in.size() > HashFunction::input_limitation + // however, all of our current hash functions can hash unlimited blocks + auto& hash_fn = this->hasher(); + hash_fn.update(in); + auto message_hash = hash_fn.digest(); + auto hash_length = hash_fn.DigestSize; + auto em_length = (em_bits + 7) / 8; + u8 salt[SaltLength]; + + AK::fill_with_random(salt, SaltLength); + + if (em_length < hash_length + SaltLength + 2) { + dbgln("Ooops...encoding error"); + return; + } + + m_buffer.overwrite(0, zeros, 8); + m_buffer.overwrite(8, message_hash.data, HashFunction::DigestSize); + m_buffer.overwrite(8 + HashFunction::DigestSize, salt, SaltLength); + + hash_fn.update(m_buffer); + auto hash = hash_fn.digest(); + + u8 DB_data[em_length - HashFunction::DigestSize - 1]; + auto DB = Bytes { DB_data, em_length - HashFunction::DigestSize - 1 }; + auto DB_offset = 0; + + for (size_t i = 0; i < em_length - SaltLength - HashFunction::DigestSize - 2; ++i) + DB[DB_offset++] = 0; + + DB[DB_offset++] = 0x01; + + DB.overwrite(DB_offset, salt, SaltLength); + + auto mask_length = em_length - HashFunction::DigestSize - 1; + + u8 DB_mask[mask_length]; + auto DB_mask_buffer = Bytes { DB_mask, mask_length }; + // FIXME: we should probably allow reading from u8* + MGF1(ReadonlyBytes { hash.data, HashFunction::DigestSize }, mask_length, DB_mask_buffer); + + for (size_t i = 0; i < DB.size(); ++i) + DB_data[i] ^= DB_mask[i]; + + auto count = (8 - (em_length * 8 - em_bits)); + DB_data[0] &= (0xff >> count) << count; + + out.overwrite(0, DB.data(), DB.size()); + out.overwrite(DB.size(), hash.data, hash_fn.DigestSize); + out[DB.size() + hash_fn.DigestSize] = 0xbc; + } + + virtual VerificationConsistency verify(ReadonlyBytes msg, ReadonlyBytes emsg, size_t em_bits) override + { + auto& hash_fn = this->hasher(); + hash_fn.update(msg); + auto message_hash = hash_fn.digest(); + + if (emsg.size() < HashFunction::DigestSize + SaltLength + 2) + return VerificationConsistency::Inconsistent; + + if (emsg[emsg.size() - 1] != 0xbc) + return VerificationConsistency::Inconsistent; + + auto mask_length = emsg.size() - HashFunction::DigestSize - 1; + auto masked_DB = emsg.slice(0, mask_length); + auto H = emsg.slice(mask_length, HashFunction::DigestSize); + + auto length_to_check = 8 * emsg.size() - em_bits; + auto octet = masked_DB[0]; + for (size_t i = 0; i < length_to_check; ++i) + if ((octet >> (8 - i)) & 0x01) + return VerificationConsistency::Inconsistent; + + u8 DB_mask[mask_length]; + auto DB_mask_buffer = Bytes { DB_mask, mask_length }; + MGF1(H, mask_length, DB_mask_buffer); + + u8 DB[mask_length]; + + for (size_t i = 0; i < mask_length; ++i) + DB[i] = masked_DB[i] ^ DB_mask[i]; + + DB[0] &= 0xff >> (8 - length_to_check); + + auto check_octets = emsg.size() - HashFunction::DigestSize - SaltLength - 2; + for (size_t i = 0; i < check_octets; ++i) { + if (DB[i]) + return VerificationConsistency::Inconsistent; + } + + if (DB[check_octets + 1] != 0x01) + return VerificationConsistency::Inconsistent; + + auto* salt = DB + mask_length - SaltLength; + u8 m_prime[8 + HashFunction::DigestSize + SaltLength] { 0, 0, 0, 0, 0, 0, 0, 0 }; + + auto m_prime_buffer = Bytes { m_prime, sizeof(m_prime) }; + + m_prime_buffer.overwrite(8, message_hash.data, HashFunction::DigestSize); + m_prime_buffer.overwrite(8 + HashFunction::DigestSize, salt, SaltLength); + + hash_fn.update(m_prime_buffer); + auto H_prime = hash_fn.digest(); + + if (__builtin_memcmp(message_hash.data, H_prime.data, HashFunction::DigestSize)) + return VerificationConsistency::Inconsistent; + + return VerificationConsistency::Consistent; + } + + void MGF1(ReadonlyBytes seed, size_t length, Bytes out) + { + auto& hash_fn = this->hasher(); + ByteBuffer T = ByteBuffer::create_zeroed(0); + for (size_t counter = 0; counter < length / HashFunction::DigestSize - 1; ++counter) { + hash_fn.update(seed); + hash_fn.update((u8*)&counter, 4); + T.append(hash_fn.digest().data, HashFunction::DigestSize); + } + out.overwrite(0, T.data(), length); + } + +private: + u8 m_data_buffer[8 + HashFunction::DigestSize + SaltLength]; + Bytes m_buffer; +}; + +} +} diff --git a/Userland/Libraries/LibCrypto/PK/PK.h b/Userland/Libraries/LibCrypto/PK/PK.h new file mode 100644 index 0000000000..4186eb0c91 --- /dev/null +++ b/Userland/Libraries/LibCrypto/PK/PK.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * 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 HOLDER 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. + */ + +#pragma once + +#include <AK/ByteBuffer.h> +#include <AK/String.h> + +namespace Crypto { +namespace PK { + +// FIXME: Fixing name up for grabs +template<typename PrivKeyT, typename PubKeyT> +class PKSystem { +public: + using PublicKeyType = PubKeyT; + using PrivateKeyType = PrivKeyT; + + PKSystem(PublicKeyType& pubkey, PrivateKeyType& privkey) + : m_public_key(pubkey) + , m_private_key(privkey) + { + } + + PKSystem() + { + } + + virtual void encrypt(ReadonlyBytes in, Bytes& out) = 0; + virtual void decrypt(ReadonlyBytes in, Bytes& out) = 0; + + virtual void sign(ReadonlyBytes in, Bytes& out) = 0; + virtual void verify(ReadonlyBytes in, Bytes& out) = 0; + + virtual String class_name() const = 0; + + virtual size_t output_size() const = 0; + +protected: + PublicKeyType m_public_key; + PrivateKeyType m_private_key; +}; + +} +} diff --git a/Userland/Libraries/LibCrypto/PK/RSA.cpp b/Userland/Libraries/LibCrypto/PK/RSA.cpp new file mode 100644 index 0000000000..452da97acc --- /dev/null +++ b/Userland/Libraries/LibCrypto/PK/RSA.cpp @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * 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 HOLDER 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 <AK/Random.h> +#include <LibCrypto/ASN1/ASN1.h> +#include <LibCrypto/ASN1/DER.h> +#include <LibCrypto/ASN1/PEM.h> +#include <LibCrypto/PK/RSA.h> + +namespace Crypto { +namespace PK { + +RSA::KeyPairType RSA::parse_rsa_key(ReadonlyBytes in) +{ + // we are going to assign to at least one of these + KeyPairType keypair; + // TODO: move ASN parsing logic out + u64 t, x, y, z, tmp_oid[16]; + u8 tmp_buf[4096] { 0 }; + UnsignedBigInteger n, e, d; + ASN1::List pubkey_hash_oid[2], pubkey[2]; + + ASN1::set(pubkey_hash_oid[0], ASN1::Kind::ObjectIdentifier, tmp_oid, sizeof(tmp_oid) / sizeof(tmp_oid[0])); + ASN1::set(pubkey_hash_oid[1], ASN1::Kind::Null, nullptr, 0); + + // DER is weird in that it stores pubkeys as bitstrings + // we must first extract that crap + ASN1::set(pubkey[0], ASN1::Kind::Sequence, &pubkey_hash_oid, 2); + ASN1::set(pubkey[1], ASN1::Kind::Null, nullptr, 0); + + dbgln("we were offered {} bytes of input", in.size()); + + if (der_decode_sequence(in.data(), in.size(), pubkey, 2)) { + // yay, now we have to reassemble the bitstring to a bytestring + t = 0; + y = 0; + z = 0; + x = 0; + for (; x < pubkey[1].size; ++x) { + y = (y << 1) | tmp_buf[x]; + if (++z == 8) { + tmp_buf[t++] = (u8)y; + y = 0; + z = 0; + } + } + // now the buffer is correct (Sequence { Integer, Integer }) + if (!der_decode_sequence_many<2>(tmp_buf, t, + ASN1::Kind::Integer, 1, &n, + ASN1::Kind::Integer, 1, &e)) { + // something was fucked up + dbgln("bad pubkey: e={} n={}", e, n); + return keypair; + } + // correct public key + keypair.public_key.set(n, e); + return keypair; + } + + // could be a private key + if (!der_decode_sequence_many<1>(in.data(), in.size(), + ASN1::Kind::Integer, 1, &n)) { + // that's no key + // that's a death star + dbgln("that's a death star"); + return keypair; + } + + if (n == 0) { + // it is a private key + UnsignedBigInteger zero; + if (!der_decode_sequence_many<4>(in.data(), in.size(), + ASN1::Kind::Integer, 1, &zero, + ASN1::Kind::Integer, 1, &n, + ASN1::Kind::Integer, 1, &e, + ASN1::Kind::Integer, 1, &d)) { + dbgln("bad privkey n={} e={} d={}", n, e, d); + return keypair; + } + keypair.private_key.set(n, d, e); + return keypair; + } + if (n == 1) { + // multiprime key, we don't know how to deal with this + dbgln("Unsupported key type"); + return keypair; + } + // it's a broken public key + keypair.public_key.set(n, 65537); + return keypair; +} + +void RSA::encrypt(ReadonlyBytes in, Bytes& out) +{ +#ifdef CRYPTO_DEBUG + dbg() << "in size: " << in.size(); +#endif + auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size()); + if (!(in_integer < m_public_key.modulus())) { + dbgln("value too large for key"); + out = {}; + return; + } + auto exp = NumberTheory::ModularPower(in_integer, m_public_key.public_exponent(), m_public_key.modulus()); + auto size = exp.export_data(out); + auto outsize = out.size(); + if (size != outsize) { + dbgln("POSSIBLE RSA BUG!!! Size mismatch: {} requested but {} bytes generated", outsize, size); + out = out.slice(outsize - size, size); + } +} + +void RSA::decrypt(ReadonlyBytes in, Bytes& out) +{ + // FIXME: Actually use the private key properly + + auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size()); + auto exp = NumberTheory::ModularPower(in_integer, m_private_key.private_exponent(), m_private_key.modulus()); + auto size = exp.export_data(out); + + auto align = m_private_key.length(); + auto aligned_size = (size + align - 1) / align * align; + + for (auto i = size; i < aligned_size; ++i) + out[out.size() - i - 1] = 0; // zero the non-aligned values + out = out.slice(out.size() - aligned_size, aligned_size); +} + +void RSA::sign(ReadonlyBytes in, Bytes& out) +{ + auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size()); + auto exp = NumberTheory::ModularPower(in_integer, m_private_key.private_exponent(), m_private_key.modulus()); + auto size = exp.export_data(out); + out = out.slice(out.size() - size, size); +} + +void RSA::verify(ReadonlyBytes in, Bytes& out) +{ + auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size()); + auto exp = NumberTheory::ModularPower(in_integer, m_public_key.public_exponent(), m_public_key.modulus()); + auto size = exp.export_data(out); + out = out.slice(out.size() - size, size); +} + +void RSA::import_private_key(ReadonlyBytes bytes, bool pem) +{ + ByteBuffer buffer; + if (pem) { + buffer = decode_pem(bytes); + bytes = buffer; + } + + auto key = parse_rsa_key(bytes); + if (!key.private_key.length()) { + dbgln("We expected to see a private key, but we found none"); + ASSERT_NOT_REACHED(); + } + m_private_key = key.private_key; +} + +void RSA::import_public_key(ReadonlyBytes bytes, bool pem) +{ + ByteBuffer buffer; + if (pem) { + buffer = decode_pem(bytes); + bytes = buffer; + } + + auto key = parse_rsa_key(bytes); + if (!key.public_key.length()) { + dbgln("We expected to see a public key, but we found none"); + ASSERT_NOT_REACHED(); + } + m_public_key = key.public_key; +} + +template<typename HashFunction> +void RSA_EMSA_PSS<HashFunction>::sign(ReadonlyBytes in, Bytes& out) +{ + // -- encode via EMSA_PSS + auto mod_bits = m_rsa.private_key().modulus().trimmed_length() * sizeof(u32) * 8; + + u8 EM[mod_bits]; + auto EM_buf = Bytes { EM, mod_bits }; + m_emsa_pss.encode(in, EM_buf, mod_bits - 1); + + // -- sign via RSA + m_rsa.sign(EM_buf, out); +} + +template<typename HashFunction> +VerificationConsistency RSA_EMSA_PSS<HashFunction>::verify(ReadonlyBytes in) +{ + auto mod_bytes = m_rsa.public_key().modulus().trimmed_length() * sizeof(u32); + if (in.size() != mod_bytes) + return VerificationConsistency::Inconsistent; + + u8 EM[mod_bytes]; + auto EM_buf = Bytes { EM, mod_bytes }; + + // -- verify via RSA + m_rsa.verify(in, EM_buf); + + // -- verify via EMSA_PSS + return m_emsa_pss.verify(in, EM, mod_bytes * 8 - 1); +} + +void RSA_PKCS1_EME::encrypt(ReadonlyBytes in, Bytes& out) +{ + auto mod_len = (m_public_key.modulus().trimmed_length() * sizeof(u32) * 8 + 7) / 8; +#ifdef CRYPTO_DEBUG + dbg() << "key size: " << mod_len; +#endif + if (in.size() > mod_len - 11) { + dbgln("message too long :("); + out = out.trim(0); + return; + } + if (out.size() < mod_len) { + dbgln("output buffer too small"); + return; + } + + auto ps_length = mod_len - in.size() - 3; + u8 ps[ps_length]; + + // FIXME: Without this assertion, GCC refuses to compile due to a memcpy overflow(!?) + ASSERT(ps_length < 16384); + + AK::fill_with_random(ps, ps_length); + // since arc4random can create zeros (shocking!) + // we have to go through and un-zero the zeros + for (size_t i = 0; i < ps_length; ++i) + while (!ps[i]) + AK::fill_with_random(ps + i, 1); + + u8 paddings[] { 0x00, 0x02 }; + + out.overwrite(0, paddings, 2); + out.overwrite(2, ps, ps_length); + out.overwrite(2 + ps_length, paddings, 1); + out.overwrite(3 + ps_length, in.data(), in.size()); + out = out.trim(3 + ps_length + in.size()); // should be a single block + +#ifdef CRYPTO_DEBUG + dbg() << "padded output size: " << 3 + ps_length + in.size() << " buffer size: " << out.size(); +#endif + + RSA::encrypt(out, out); +} +void RSA_PKCS1_EME::decrypt(ReadonlyBytes in, Bytes& out) +{ + auto mod_len = (m_public_key.modulus().trimmed_length() * sizeof(u32) * 8 + 7) / 8; + if (in.size() != mod_len) { + dbgln("decryption error: wrong amount of data: {}", in.size()); + out = out.trim(0); + return; + } + + RSA::decrypt(in, out); + + if (out.size() < RSA::output_size()) { + dbgln("decryption error: not enough data after decryption: {}", out.size()); + out = out.trim(0); + return; + } + + if (out[0] != 0x00) { + dbgln("invalid padding byte 0 : {}", out[0]); + return; + } + + if (out[1] != 0x02) { + dbgln("invalid padding byte 1 : {}", out[1]); + return; + } + + size_t offset = 2; + while (offset < out.size() && out[offset]) + ++offset; + + if (offset == out.size()) { + dbgln("garbage data, no zero to split padding"); + return; + } + + ++offset; + + if (offset - 3 < 8) { + dbgln("PS too small"); + return; + } + + out = out.slice(offset, out.size() - offset); +} + +void RSA_PKCS1_EME::sign(ReadonlyBytes, Bytes&) +{ + dbgln("FIXME: RSA_PKCS_EME::sign"); +} +void RSA_PKCS1_EME::verify(ReadonlyBytes, Bytes&) +{ + dbgln("FIXME: RSA_PKCS_EME::verify"); +} +} +} diff --git a/Userland/Libraries/LibCrypto/PK/RSA.h b/Userland/Libraries/LibCrypto/PK/RSA.h new file mode 100644 index 0000000000..570e0be423 --- /dev/null +++ b/Userland/Libraries/LibCrypto/PK/RSA.h @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur <ali.mpfard@gmail.com> + * All rights reserved. + * + * 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 HOLDER 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. + */ + +#pragma once + +#include <AK/Span.h> +#include <AK/Vector.h> +#include <LibCrypto/BigInt/UnsignedBigInteger.h> +#include <LibCrypto/NumberTheory/ModularFunctions.h> +#include <LibCrypto/PK/Code/EMSA_PSS.h> +#include <LibCrypto/PK/PK.h> + +namespace Crypto { +namespace PK { +template<typename Integer = u64> +class RSAPublicKey { +public: + RSAPublicKey(const Integer& n, const Integer& e) + : m_modulus(n) + , m_public_exponent(e) + { + } + + RSAPublicKey() + : m_modulus(0) + , m_public_exponent(0) + { + } + + //--stuff it should do + + const Integer& modulus() const { return m_modulus; } + const Integer& public_exponent() const { return m_public_exponent; } + size_t length() const { return m_length; } + void set_length(size_t length) { m_length = length; } + + void set(const Integer& n, const Integer& e) + { + m_modulus = n; + m_public_exponent = e; + m_length = (n.trimmed_length() * sizeof(u32)); + } + +private: + Integer m_modulus; + Integer m_public_exponent; + size_t m_length { 0 }; +}; + +template<typename Integer = UnsignedBigInteger> +class RSAPrivateKey { +public: + RSAPrivateKey(const Integer& n, const Integer& d, const Integer& e) + : m_modulus(n) + , m_private_exponent(d) + , m_public_exponent(e) + { + } + + RSAPrivateKey() + { + } + + //--stuff it should do + const Integer& modulus() const { return m_modulus; } + const Integer& private_exponent() const { return m_private_exponent; } + const Integer& public_exponent() const { return m_public_exponent; } + size_t length() const { return m_length; } + void set_length(size_t length) { m_length = length; } + + void set(const Integer& n, const Integer& d, const Integer& e) + { + m_modulus = n; + m_private_exponent = d; + m_public_exponent = e; + m_length = (n.length() * sizeof(u32)); + } + +private: + Integer m_modulus; + Integer m_private_exponent; + Integer m_public_exponent; + size_t m_length { 0 }; +}; + +template<typename PubKey, typename PrivKey> +struct RSAKeyPair { + PubKey public_key; + PrivKey private_key; +}; + +using IntegerType = UnsignedBigInteger; +class RSA : public PKSystem<RSAPrivateKey<IntegerType>, RSAPublicKey<IntegerType>> { + template<typename T> + friend class RSA_EMSA_PSS; + +public: + using KeyPairType = RSAKeyPair<PublicKeyType, PrivateKeyType>; + + static KeyPairType parse_rsa_key(ReadonlyBytes); + static KeyPairType generate_key_pair(size_t bits = 256) + { + IntegerType e { 65537 }; // :P + IntegerType p, q; + IntegerType lambda; + + do { + p = NumberTheory::random_big_prime(bits / 2); + q = NumberTheory::random_big_prime(bits / 2); + lambda = NumberTheory::LCM(p.minus(1), q.minus(1)); + dbgln("checking combination p={}, q={}, lambda={}", p, q, lambda.length()); + } while (!(NumberTheory::GCD(e, lambda) == 1)); + + auto n = p.multiplied_by(q); + + auto d = NumberTheory::ModularInverse(e, lambda); + dbgln("Your keys are Pub(n={}, e={}) and Priv(n={}, d={})", n, e, n, d); + RSAKeyPair<PublicKeyType, PrivateKeyType> keys { + { n, e }, + { n, d, e } + }; + keys.public_key.set_length(bits / 2 / 8); + keys.private_key.set_length(bits / 2 / 8); + return keys; + } + + RSA(IntegerType n, IntegerType d, IntegerType e) + { + m_public_key.set(n, e); + m_private_key.set(n, d, e); + } + + RSA(PublicKeyType& pubkey, PrivateKeyType& privkey) + : PKSystem<RSAPrivateKey<IntegerType>, RSAPublicKey<IntegerType>>(pubkey, privkey) + { + } + + RSA(const ByteBuffer& publicKeyPEM, const ByteBuffer& privateKeyPEM) + { + import_public_key(publicKeyPEM); + import_private_key(privateKeyPEM); + } + + RSA(const StringView& privKeyPEM) + { + import_private_key(privKeyPEM.bytes()); + m_public_key.set(m_private_key.modulus(), m_private_key.public_exponent()); + } + + // create our own keys + RSA() + { + auto pair = generate_key_pair(); + m_public_key = pair.public_key; + m_private_key = pair.private_key; + } + + virtual void encrypt(ReadonlyBytes in, Bytes& out) override; + virtual void decrypt(ReadonlyBytes in, Bytes& out) override; + + virtual void sign(ReadonlyBytes in, Bytes& out) override; + virtual void verify(ReadonlyBytes in, Bytes& out) override; + + virtual String class_name() const override { return "RSA"; } + + virtual size_t output_size() const override { return m_public_key.length(); } + + void import_public_key(ReadonlyBytes, bool pem = true); + void import_private_key(ReadonlyBytes, bool pem = true); + + const PrivateKeyType& private_key() const { return m_private_key; } + const PublicKeyType& public_key() const { return m_public_key; } +}; + +template<typename HashFunction> +class RSA_EMSA_PSS { +public: + RSA_EMSA_PSS(RSA& rsa) + : m_rsa(rsa) + { + } + + void sign(ReadonlyBytes in, Bytes& out); + VerificationConsistency verify(ReadonlyBytes in); + +private: + EMSA_PSS<HashFunction, HashFunction::DigestSize> m_emsa_pss; + RSA m_rsa; +}; + +class RSA_PKCS1_EME : public RSA { +public: + // forward all constructions to RSA + template<typename... Args> + RSA_PKCS1_EME(Args... args) + : RSA(args...) + { + } + + ~RSA_PKCS1_EME() { } + + virtual void encrypt(ReadonlyBytes in, Bytes& out) override; + virtual void decrypt(ReadonlyBytes in, Bytes& out) override; + + virtual void sign(ReadonlyBytes, Bytes&) override; + virtual void verify(ReadonlyBytes, Bytes&) override; + + virtual String class_name() const override { return "RSA_PKCS1-EME"; } + virtual size_t output_size() const override { return m_public_key.length(); } +}; +} +} |