diff --git a/Libraries/LibCrypto/ASN1/ASN1.h b/Libraries/LibCrypto/ASN1/ASN1.h new file mode 100644 index 00000000000..96be0f896e4 --- /dev/null +++ b/Libraries/LibCrypto/ASN1/ASN1.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * 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 +#include + +namespace Crypto { + +namespace ASN1 { + enum class Kind { + Eol, + Boolean, + Integer, + ShortInteger, + BitString, + OctetString, + Null, + ObjectIdentifier, + IA5String, + PrintableString, + Utf8String, + UTCTime, + Choice, + Sequence, + Set, + SetOf + }; + + static StringView kind_name(Kind kind) + { + switch (kind) { + case Kind::Eol: + return "EndOfList"; + case Kind::Boolean: + return "Boolean"; + case Kind::Integer: + return "Integer"; + case Kind::ShortInteger: + return "ShortInteger"; + case Kind::BitString: + return "BitString"; + case Kind::OctetString: + return "OctetString"; + case Kind::Null: + return "Null"; + case Kind::ObjectIdentifier: + return "ObjectIdentifier"; + case Kind::IA5String: + return "IA5String"; + case Kind::PrintableString: + return "PrintableString"; + case Kind::Utf8String: + return "UTF8String"; + case Kind::UTCTime: + return "UTCTime"; + case Kind::Choice: + return "Choice"; + case Kind::Sequence: + return "Sequence"; + case Kind::Set: + return "Set"; + case Kind::SetOf: + return "SetOf"; + } + + return "InvalidKind"; + } + + struct List { + Kind kind; + void* data; + size_t size; + bool used; + List *prev, *next, *child, *parent; + }; + + static constexpr void set(List& list, Kind type, void* data, size_t size) + { + list.kind = type; + list.data = data; + list.size = size; + list.used = false; + } +} + +} diff --git a/Libraries/LibCrypto/ASN1/DER.h b/Libraries/LibCrypto/ASN1/DER.h new file mode 100644 index 00000000000..3cf8c55a06c --- /dev/null +++ b/Libraries/LibCrypto/ASN1/DER.h @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * 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 +#include +#include + +namespace Crypto { + +static bool der_decode_integer(const u8* in, size_t length, UnsignedBigInteger& number) +{ + if (length < 3) { + dbg() << "invalid header size"; + return false; + } + + size_t x { 0 }; + // must start with 0x02 + if ((in[x++] & 0x1f) != 0x02) { + dbg() << "not an integer " << in[x - 1] << " (" << in[x] << " follows)"; + return false; + } + + // decode length + size_t z = in[x++]; + if ((x & 0x80) == 0) { + // overflow + if (x + z > length) { + dbg() << "would overflow " << z + x << " > " << length; + return false; + } + + number = UnsignedBigInteger::import_data(in + x, z); + return true; + } else { + // actual big integer + z &= 0x7f; + + // overflow + if ((x + z) > length || z > 4 || z == 0) { + dbg() << "would overflow " << z + x << " > " << length; + return false; + } + + size_t y = 0; + while (z--) { + y = ((size_t)(in[x++])) | (y << 8); + } + + // overflow + if (x + y > length) { + dbg() << "would overflow " << y + x << " > " << length; + return false; + } + + number = UnsignedBigInteger::import_data(in + x, y); + return true; + } + + // see if it's negative + if (in[x] & 0x80) { + dbg() << "negative bigint unsupported in der_decode_integer"; + return false; + } + + return true; +} +static bool der_length_integer(UnsignedBigInteger* num, size_t* out_length) +{ + auto& bigint = *num; + size_t value_length = bigint.trimmed_length() * sizeof(u32); + auto length = value_length; + if (length == 0) { + ++length; + } else { + // the number comes with a 0 padding to make it positive in 2's comp + // add that zero if the msb is 1, but only if we haven't padded it + // ourselves + auto ms2b = (u16)(bigint.words()[bigint.trimmed_length() - 1] >> 16); + + if ((ms2b & 0xff00) == 0) { + if (!(((u8)ms2b) & 0x80)) + --length; + } else if (ms2b & 0x8000) { + ++length; + } + } + if (value_length < 128) { + ++length; + } else { + ++length; + while (value_length) { + ++length; + value_length >>= 8; + } + } + // kind + ++length; + *out_length = length; + return true; +} +constexpr static bool der_decode_object_identifier(const u8* in, size_t in_length, u8* words, u8* out_length) +{ + if (in_length < 3) + return false; // invalid header + + if (*out_length < 2) + return false; // need at least two words + + size_t x { 0 }; + if ((in[x++] & 0x1f) != 0x06) { + return false; // invalid header value + } + + size_t length { 0 }; + if (in[x] < 128) { + length = in[x++]; + } else { + if ((in[x] < 0x81) | (in[x] > 0x82)) + return false; // invalid header + + size_t y = in[x++] & 0x7f; + while (y--) + length = (length << 8) | (size_t)in[x++]; + } + + if (length < 1 || length + x > in_length) + return false; // invalid length or overflow + + size_t y { 0 }, t { 0 }; + while (length--) { + t = (t << 7) | (in[x] & 0x7f); + if (!(in[x++] & 0x80)) { + if (y >= *out_length) + return false; // overflow + + if (y == 0) { + words[0] = t / 40; + words[1] = t % 40; + y = 2; + } else { + words[y++] = t; + } + t = 0; + } + } + *out_length = y; + return true; +} + +static constexpr size_t der_object_identifier_bits(size_t x) +{ + x &= 0xffffffff; + size_t c { 0 }; + while (x) { + ++c; + x >>= 1; + } + return c; +} + +constexpr static bool der_length_object_identifier(u8* words, size_t num_words, size_t* out_length) +{ + if (num_words < 2) + return false; + + if (words[0] > 3 || (words[0] < 2 && words[1] > 39)) + return false; + + size_t z { 0 }; + size_t wordbuf = words[0] * 40 + words[1]; + for (size_t y = 0; y < num_words; ++y) { + auto t = der_object_identifier_bits(wordbuf); + z = t / 7 + (!!(t % 7)) + (!!(wordbuf == 0)); + if (y < num_words - 1) + wordbuf = words[y + 1]; + } + + if (z < 128) { + z += 2; + } else if (z < 256) { + z += 3; + } else { + z += 4; + } + *out_length = z; + return true; +} + +constexpr static bool der_length_sequence(ASN1::List* list, size_t in_length, size_t* out_length) +{ + size_t y { 0 }, x { 0 }; + for (size_t i = 0; i < in_length; ++i) { + auto type = list[i].kind; + auto size = list[i].size; + auto data = list[i].data; + + if (type == ASN1::Kind::Eol) + break; + + switch (type) { + case ASN1::Kind::Integer: + if (!der_length_integer((UnsignedBigInteger*)data, &x)) { + return false; + } + y += x; + break; + case ASN1::Kind::ObjectIdentifier: + if (!der_length_object_identifier((u8*)data, size, &x)) { + return false; + } + y += x; + break; + case ASN1::Kind::Sequence: + if (!der_length_sequence((ASN1::List*)data, size, &x)) { + return false; + } + y += x; + break; + default: + dbg() << "Unhandled Kind " << ASN1::kind_name(type); + ASSERT_NOT_REACHED(); + break; + } + } + + if (y < 128) { + y += 2; + } else if (y < 256) { + y += 3; + } else if (y < 65536) { + y += 4; + } else if (y < 16777216ul) { + y += 5; + } else { + dbg() << "invalid length " << y; + return false; + } + *out_length = y; + return true; +} + +static bool der_decode_sequence(const u8* in, size_t in_length, ASN1::List* list, size_t out_length, bool ordered = true) +{ + if (in_length < 2) { + dbg() << "header too small"; + return false; // invalid header + } + size_t x { 0 }; + if (in[x++] != 0x30) { + dbg() << "not a sequence: " << in[x - 1]; + return false; // not a sequence + } + size_t block_size { 0 }; + size_t y { 0 }; + if (in[x] < 128) { + block_size = in[x++]; + } else if (in[x] & 0x80) { + if ((in[x] < 0x81) || (in[x] > 0x83)) { + dbg() << "invalid length element " << in[x]; + return false; + } + + y = in[x++] & 0x7f; + + if (x + y > in_length) { + dbg() << "would overflow " << x + y << " -> " << in_length; + return false; // overflow + } + block_size = 0; + while (y--) + block_size = (block_size << 8) | (size_t)in[x++]; + } + + // overflow + if (x + block_size > in_length) { + dbg() << "would overflow " << x + block_size << " -> " << in_length; + return false; + } + + for (size_t i = 0; i < out_length; ++i) + list[i].used = false; + + in_length = block_size; + for (size_t i = 0; i < out_length; ++i) { + size_t z = 0; + auto kind = list[i].kind; + auto size = list[i].size; + auto data = list[i].data; + + if (!ordered && list[i].used) { + continue; + } + + switch (kind) { + case ASN1::Kind::Integer: + z = in_length; + if (!der_decode_integer(in + x, z, *(UnsignedBigInteger*)data)) { + dbg() << "could not decode an integer"; + return false; + } + if (!der_length_integer((UnsignedBigInteger*)data, &z)) { + dbg() << "could not figure out the length"; + return false; + } + break; + case ASN1::Kind::ObjectIdentifier: + z = in_length; + if (!der_decode_object_identifier(in + x, z, (u8*)data, (u8*)&size)) { + if (!ordered) + continue; + return false; + } + list[i].size = size; + if (!der_length_object_identifier((u8*)data, size, &z)) { + return false; + } + break; + case ASN1::Kind::Sequence: + if ((in[x] & 0x3f) != 0x30) { + dbg() << "Not a sequence: " << (in[x] & 0x3f); + return false; + } + z = in_length; + if (!der_decode_sequence(in + x, z, (ASN1::List*)data, size)) { + if (!ordered) + continue; + return false; + } + if (!der_length_sequence((ASN1::List*)data, size, &z)) { + return false; + } + break; + default: + dbg() << "Unhandled ASN1 kind " << ASN1::kind_name(kind); + ASSERT_NOT_REACHED(); + break; + } + x += z; + in_length -= z; + list[i].used = true; + if (!ordered) + i = -1; + } + for (size_t i = 0; i < out_length; ++i) + if (!list[i].used) { + dbg() << "index " << i << " was not read"; + return false; + } + + return true; +} + +template +struct der_decode_sequence_many_base { + constexpr void set(size_t index, ASN1::Kind kind, size_t size, void* data) + { + ASN1::set(m_list[index], kind, data, size); + } + + constexpr der_decode_sequence_many_base(const u8* in, size_t in_length) + : m_in(in) + , m_in_length(in_length) + { + } + + ASN1::List* list() { return m_list; } + const u8* in() { return m_in; } + size_t in_length() { return m_in_length; } + +protected: + ASN1::List m_list[element_count]; + const u8* m_in; + size_t m_in_length; +}; + +template +struct der_decode_sequence_many : public der_decode_sequence_many_base { + + template + constexpr void construct(size_t index, ASN1::Kind kind, size_t size, ElementType data, Args... args) + { + der_decode_sequence_many_base::set(index, kind, size, (void*)data); + construct(index + 1, args...); + } + + constexpr void construct(size_t index) + { + ASSERT(index == element_count); + } + + template + constexpr der_decode_sequence_many(const u8* in, size_t in_length, Args... args) + : der_decode_sequence_many_base(in, in_length) + { + construct(0, args...); + } + + constexpr operator bool() + { + return der_decode_sequence(this->m_in, this->m_in_length, this->m_list, element_count); + } +}; + +// FIXME: Move these terrible constructs into their own place +constexpr static void decode_b64_block(const u8 in[4], u8 out[3]) +{ + out[0] = (u8)(in[0] << 2 | in[1] >> 4); + out[1] = (u8)(in[1] << 4 | in[2] >> 2); + out[2] = (u8)(((in[2] << 6) & 0xc0) | in[3]); +} + +constexpr static char base64_chars[] { "|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq" }; +constexpr static size_t decode_b64(const u8* in_buffer, size_t in_length, ByteBuffer& out_buffer) +{ + u8 in[4] { 0 }, out[3] { 0 }, v { 0 }; + size_t i { 0 }, length { 0 }; + size_t output_offset { 0 }; + + const u8* ptr = in_buffer; + + while (ptr <= in_buffer + in_length) { + for (length = 0, i = 0; i < 4 && (ptr <= in_buffer + in_length); ++i) { + v = 0; + while ((ptr <= in_buffer + in_length) && !v) { + v = ptr[0]; + ++ptr; + v = (u8)((v < 43 || v > 122) ? 0 : base64_chars[v - 43]); + if (v) + v = (u8)(v == '$' ? 0 : v - 61); + } + if (ptr <= in_buffer + in_length) { + ++length; + if (v) + in[i] = v - 1; + + } else { + in[i] = 0; + } + } + if (length) { + decode_b64_block(in, out); + out_buffer.overwrite(output_offset, out, length - 1); + output_offset += length - 1; + } + } + return output_offset; +} +} diff --git a/Libraries/LibCrypto/ASN1/PEM.h b/Libraries/LibCrypto/ASN1/PEM.h new file mode 100644 index 00000000000..f55bbc46ade --- /dev/null +++ b/Libraries/LibCrypto/ASN1/PEM.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * 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 +#include + +namespace Crypto { + +static ByteBuffer decode_pem(const ByteBuffer& data_in, size_t cert_index = 0) +{ + size_t i { 0 }; + size_t start_at { 0 }; + size_t idx { 0 }; + size_t input_length = data_in.size(); + auto alloc_len = input_length / 4 * 3; + auto output = ByteBuffer::create_uninitialized(alloc_len); + + for (i = 0; i < input_length; i++) { + if ((data_in[i] == '\n') || (data_in[i] == '\r')) + continue; + + if (data_in[i] != '-') { + // read entire line + while ((i < input_length) && (data_in[i] != '\n')) + i++; + continue; + } + + if (data_in[i] == '-') { + auto end_idx = i; + //read until end of line + while ((i < input_length) && (data_in[i] != '\n')) + i++; + if (start_at) { + if (cert_index > 0) { + cert_index--; + start_at = 0; + } else { + idx = decode_b64(data_in.offset_pointer(start_at), end_idx - start_at, output); + break; + } + } else + start_at = i + 1; + } + } + return output.slice(0, idx); +} + +} diff --git a/Libraries/LibCrypto/Makefile b/Libraries/LibCrypto/Makefile index a9c249dde9b..bda1e36c980 100644 --- a/Libraries/LibCrypto/Makefile +++ b/Libraries/LibCrypto/Makefile @@ -2,6 +2,7 @@ LIBCRYPTO_OBJS = \ Cipher/AES.o \ Hash/MD5.o \ Hash/SHA2.o \ + PK/RSA.o \ BigInt/UnsignedBigInteger.o OBJS = $(LIBCRYPTO_OBJS) @@ -9,7 +10,7 @@ OBJS = $(LIBCRYPTO_OBJS) LIBRARY = libcrypto.a install: - for dir in . Cipher Cipher/Mode Hash; do \ + for dir in . Cipher Cipher/Mode Hash PK PK/Code; do \ mkdir -p $(SERENITY_BASE_DIR)/Root/usr/include/LibCrypto/$$dir; \ cp $$dir/*.h $(SERENITY_BASE_DIR)/Root/usr/include/LibCrypto/$$dir/; \ done diff --git a/Libraries/LibCrypto/NumberTheory/ModularFunctions.h b/Libraries/LibCrypto/NumberTheory/ModularFunctions.h new file mode 100644 index 00000000000..2f19dc71470 --- /dev/null +++ b/Libraries/LibCrypto/NumberTheory/ModularFunctions.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * 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 + +//#define NT_DEBUG + +namespace Crypto { +namespace NumberTheory { + static auto ModularInverse(const UnsignedBigInteger& a_, const UnsignedBigInteger& b) -> UnsignedBigInteger + { + if (b == 1) + return { 1 }; + + auto a = a_; + auto u = a; + if (a.words()[0] % 2 == 0) + u = u.add(b); + + auto v = b; + auto x = UnsignedBigInteger { 0 }; + auto d = b.sub(1); + + while (!(v == 1)) { + while (v < u) { + u = u.sub(v); + d = d.add(x); + while (u.words()[0] % 2 == 0) { + if (d.words()[0] % 2 == 1) { + d = d.add(b); + } + u = u.divide(2).quotient; + d = d.divide(2).quotient; + } + } + v = v.sub(u); + x = x.add(d); + while (v.words()[0] % 2 == 0) { + if (x.words()[0] % 2 == 1) { + x = x.add(b); + } + v = v.divide(2).quotient; + x = x.divide(2).quotient; + } + } + return x.divide(b).remainder; + } + + static auto ModularPower(const UnsignedBigInteger& b, const UnsignedBigInteger& e, const UnsignedBigInteger& m) -> UnsignedBigInteger + { + if (m == 1) + return 0; + + UnsignedBigInteger ep { e }; + UnsignedBigInteger base { b }; + UnsignedBigInteger exp { 1 }; + + while (!(ep < 1)) { +#ifdef NT_DEBUG + dbg() << ep.to_base10(); +#endif + if (ep.words()[0] % 2 == 1) { + exp = exp.multiply(base).divide(m).remainder; + } + ep = ep.divide(2).quotient; + base = base.multiply(base).divide(m).remainder; + } + return exp; + } + + static auto GCD(const UnsignedBigInteger& a, const UnsignedBigInteger& b) -> UnsignedBigInteger + { + UnsignedBigInteger a_ { a }, b_ { b }; + for (;;) { + if (a_ == 0) + return b_; + b_ = b_.divide(a_).remainder; + if (b_ == 0) + return a_; + a_ = a_.divide(b_).remainder; + } + } + + static auto LCM(const UnsignedBigInteger& a, const UnsignedBigInteger& b) -> UnsignedBigInteger + { + auto temp = GCD(a, b); + + auto div = a.divide(temp); + +#ifdef NT_DEBUG + dbg() << "quot: " << div.quotient << " rem: " << div.remainder; +#endif + return temp == 0 ? 0 : (a.divide(temp).quotient.multiply(b)); + } + + template + static bool MR_primality_test(UnsignedBigInteger n, const Vector& tests) + { + auto prev = n.sub({ 1 }); + auto b = prev; + auto r = 0; + + auto div_result = b.divide(2); + while (div_result.quotient == 0) { + div_result = b.divide(2); + b = div_result.quotient; + ++r; + } + + for (size_t i = 0; i < tests.size(); ++i) { + auto return_ = true; + if (n < tests[i]) + continue; + auto x = ModularPower(tests[i], b, n); + if (x == 1 || x == prev) + continue; + for (auto d = r - 1; d != 0; --d) { + x = ModularPower(x, 2, n); + if (x == 1) + return false; + if (x == prev) { + return_ = false; + break; + } + } + if (return_) + return false; + } + + return true; + } + + static UnsignedBigInteger random_number(const UnsignedBigInteger& min, const UnsignedBigInteger& max) + { + ASSERT(min < max); + auto range = max.minus(min); + UnsignedBigInteger base; + // FIXME: Need a cryptographically secure rng + auto size = range.trimmed_length() * sizeof(u32); + u8 buf[size]; + arc4random_buf(buf, size); + Vector vec; + for (size_t i = 0; i < size / sizeof(u32); ++i) { + vec.append(*(u32*)buf + i); + } + UnsignedBigInteger offset { move(vec) }; + return offset.add(min); + } + + static bool is_probably_prime(const UnsignedBigInteger& p) + { + if (p == 2 || p == 3 || p == 5) + return true; + if (p < 49) + return true; + + Vector tests; + UnsignedBigInteger seven { 7 }; + for (size_t i = 0; i < tests.size(); ++i) + tests.append(random_number(seven, p.sub(2))); + + return MR_primality_test(p, tests); + } + + static UnsignedBigInteger random_big_prime(size_t bits) + { + ASSERT(bits >= 33); + UnsignedBigInteger min = UnsignedBigInteger::from_base10("6074001000").shift_left(bits - 33); + UnsignedBigInteger max = UnsignedBigInteger { 1 }.shift_left(bits).sub(1); + for (;;) { + auto p = random_number(min, max); + if (is_probably_prime(p)) + return p; + } + } +} +} diff --git a/Libraries/LibCrypto/PK/Code/Code.h b/Libraries/LibCrypto/PK/Code/Code.h new file mode 100644 index 00000000000..8b6b02da134 --- /dev/null +++ b/Libraries/LibCrypto/PK/Code/Code.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * 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 + +namespace Crypto { +namespace PK { + + enum class VerificationConsistency { + Consistent, + Inconsistent + }; + + template + class Code { + public: + template + Code(Args... args) + : m_hasher(args...) + { + } + + virtual void encode(const ByteBuffer& in, ByteBuffer& out, size_t em_bits) = 0; + virtual VerificationConsistency verify(const ByteBuffer& msg, const ByteBuffer& 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/Libraries/LibCrypto/PK/Code/EMSA_PSS.h b/Libraries/LibCrypto/PK/Code/EMSA_PSS.h new file mode 100644 index 00000000000..0bfe5447159 --- /dev/null +++ b/Libraries/LibCrypto/PK/Code/EMSA_PSS.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * 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 + +static constexpr u8 zeros[] { 0, 0, 0, 0, 0, 0, 0, 0 }; + +namespace Crypto { +namespace PK { + + template + class EMSA_PSS : public Code { + public: + template + EMSA_PSS(Args... args) + : Code(args...) + { + m_buffer = ByteBuffer::wrap(m_data_buffer, sizeof(m_data_buffer)); + } + + static constexpr auto SaltLength = SaltSize; + + virtual void encode(const ByteBuffer& 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]; + + arc4random_buf(salt, SaltLength); + + if (em_length < hash_length + SaltLength + 2) { + dbg() << "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 = ByteBuffer::wrap(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 = ByteBuffer::wrap(DB_mask, mask_length); + // FIXME: we should probably allow reading from u8* + auto hash_buffer = ByteBuffer::wrap(hash.data, HashFunction::DigestSize); + MGF1(hash_buffer, 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(const ByteBuffer& msg, const ByteBuffer& 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_view(0, mask_length); + auto H = emsg.slice_view(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 = ByteBuffer::wrap(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 = ByteBuffer::wrap(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(const ByteBuffer& seed, size_t length, ByteBuffer& 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]; + ByteBuffer m_buffer; + }; + +} +} diff --git a/Libraries/LibCrypto/PK/PK.h b/Libraries/LibCrypto/PK/PK.h new file mode 100644 index 00000000000..fd729da01bc --- /dev/null +++ b/Libraries/LibCrypto/PK/PK.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * 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 +#include + +namespace Crypto { +namespace PK { + + // FIXME: Fixing name up for grabs + template + 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(const ByteBuffer& in, ByteBuffer& out) = 0; + virtual void decrypt(const ByteBuffer& in, ByteBuffer& out) = 0; + + virtual void sign(const ByteBuffer& in, ByteBuffer& out) = 0; + virtual void verify(const ByteBuffer& in, ByteBuffer& 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/Libraries/LibCrypto/PK/RSA.cpp b/Libraries/LibCrypto/PK/RSA.cpp new file mode 100644 index 00000000000..8d4552dd3d3 --- /dev/null +++ b/Libraries/LibCrypto/PK/RSA.cpp @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * 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 +#include +#include +#include + +namespace Crypto { +namespace PK { + + RSA::KeyPairType RSA::parse_rsa_key(const ByteBuffer& 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); + + dbg() << "we were offered " << in.size() << " bytes of input"; + + 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 + dbg() << "bad pubkey: " << e << " in " << 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 + dbg() << "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)) { + dbg() << "bad privkey " << 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 + dbg() << "Unsupported key type"; + return keypair; + } + // it's a broken public key + keypair.public_key.set(n, 65537); + return keypair; + } + + void RSA::encrypt(const ByteBuffer& in, ByteBuffer& out) + { + dbg() << "in size: " << in.size(); + auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size()); + if (!(in_integer < m_public_key.modulus())) { + dbg() << "value too large for key"; + out.clear(); + return; + } + auto exp = NumberTheory::ModularPower(in_integer, m_public_key.public_exponent(), m_public_key.modulus()); + auto size = exp.export_data(out); + // FIXME: We should probably not do this... + if (size != out.size()) + out = out.slice(out.size() - size, size); + } + + void RSA::decrypt(const ByteBuffer& in, ByteBuffer& 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(const ByteBuffer& in, ByteBuffer& 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(const ByteBuffer& in, ByteBuffer& 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(const ByteBuffer& buffer, bool pem) + { + // so gods help me, I hate DER + auto decoded_buffer = pem ? decode_pem(buffer) : buffer; + auto key = parse_rsa_key(decoded_buffer); + if (!key.private_key.length()) { + dbg() << "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(const ByteBuffer& buffer, bool pem) + { + // so gods help me, I hate DER + auto decoded_buffer = pem ? decode_pem(buffer) : buffer; + auto key = parse_rsa_key(decoded_buffer); + if (!key.public_key.length()) { + dbg() << "We expected to see a public key, but we found none"; + ASSERT_NOT_REACHED(); + } + m_public_key = key.public_key; + } + + template + void RSA_EMSA_PSS::sign(const ByteBuffer& in, ByteBuffer& 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 = ByteBuffer::wrap(EM, mod_bits); + m_emsa_pss.encode(in, EM_buf, mod_bits - 1); + + // -- sign via RSA + m_rsa.sign(EM_buf, out); + } + + template + VerificationConsistency RSA_EMSA_PSS::verify(const ByteBuffer& 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 = ByteBuffer::wrap(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(const ByteBuffer& in, ByteBuffer& out) + { + auto mod_len = (m_public_key.modulus().trimmed_length() * sizeof(u32) * 8 + 7) / 8; + dbg() << "key size: " << mod_len; + if (in.size() > mod_len - 11) { + dbg() << "message too long :("; + out.trim(0); + return; + } + if (out.size() < mod_len) { + dbg() << "output buffer too small"; + return; + } + + auto ps_length = mod_len - in.size() - 3; + u8 ps[ps_length]; + + arc4random_buf(ps, ps_length); + 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.trim(3 + ps_length + in.size()); // should be a single block + + dbg() << "padded output size: " << 3 + ps_length + in.size() << " buffer size: " << out.size(); + + RSA::encrypt(out, out); + } + void RSA_PKCS1_EME::decrypt(const ByteBuffer& in, ByteBuffer& out) + { + auto mod_len = (m_public_key.modulus().trimmed_length() * sizeof(u32) * 8 + 7) / 8; + if (in.size() != mod_len) { + dbg() << "decryption error: wrong amount of data: " << in.size(); + out.trim(0); + return; + } + + RSA::decrypt(in, out); + + if (out.size() < RSA::output_size()) { + dbg() << "decryption error: not enough data after decryption: " << out.size(); + out.trim(0); + return; + } + + if (out[0] != 0x00) { + dbg() << "invalid padding byte 0 : " << out[0]; + return; + } + + if (out[1] != 0x02) { + dbg() << "invalid padding byte 1" << out[1]; + return; + } + + size_t offset = 2; + while (offset < out.size() && out[offset]) + ++offset; + + if (offset == out.size()) { + dbg() << "garbage data, no zero to split padding"; + return; + } + + ++offset; + + if (offset - 3 < 8) { + dbg() << "PS too small"; + return; + } + + out = out.slice(offset, out.size() - offset); + } + + void RSA_PKCS1_EME::sign(const ByteBuffer&, ByteBuffer&) + { + dbg() << "FIXME: RSA_PKCS_EME::sign"; + } + void RSA_PKCS1_EME::verify(const ByteBuffer&, ByteBuffer&) + { + dbg() << "FIXME: RSA_PKCS_EME::verify"; + } +} +} diff --git a/Libraries/LibCrypto/PK/RSA.h b/Libraries/LibCrypto/PK/RSA.h new file mode 100644 index 00000000000..ece8a5aa009 --- /dev/null +++ b/Libraries/LibCrypto/PK/RSA.h @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * 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 +#include +#include +#include +#include + +namespace Crypto { +namespace PK { + template + 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 + 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 + struct RSAKeyPair { + PubKey public_key; + PrivKey private_key; + }; + + using IntegerType = UnsignedBigInteger; + class RSA : public PKSystem, RSAPublicKey> { + template + friend class RSA_EMSA_PSS; + + public: + using KeyPairType = RSAKeyPair; + + static KeyPairType parse_rsa_key(const ByteBuffer&); + 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.sub(1), q.sub(1)); + dbg() << "checking combination p=" << p << ", q=" << q << ", lambda=" << lambda.length(); + } while (!(NumberTheory::GCD(e, lambda) == 1)); + + auto n = p.multiply(q); + + auto d = NumberTheory::ModularInverse(e, lambda); + dbg() << "Your keys are Pub{n=" << n << ", e=" << e << "} and Priv{n=" << n << ", d=" << d << "}"; + RSAKeyPair 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, RSAPublicKey>(pubkey, privkey) + { + } + + RSA(const ByteBuffer& publicKeyPEM, const ByteBuffer& privateKeyPEM) + { + import_public_key(publicKeyPEM); + import_private_key(privateKeyPEM); + } + + RSA(const StringView& privKeyPEM) + { + import_private_key(ByteBuffer::wrap(privKeyPEM.characters_without_null_termination(), privKeyPEM.length())); + 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(const ByteBuffer& in, ByteBuffer& out) override; + virtual void decrypt(const ByteBuffer& in, ByteBuffer& out) override; + + virtual void sign(const ByteBuffer& in, ByteBuffer& out) override; + virtual void verify(const ByteBuffer& in, ByteBuffer& 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(const ByteBuffer& buffer, bool pem = true); + void import_private_key(const ByteBuffer& buffer, bool pem = true); + + const PrivateKeyType& private_key() const { return m_private_key; } + const PublicKeyType& public_key() const { return m_public_key; } + }; + + template + class RSA_EMSA_PSS { + public: + RSA_EMSA_PSS(RSA& rsa) + : m_rsa(rsa) + { + } + + void sign(const ByteBuffer& in, ByteBuffer& out); + VerificationConsistency verify(const ByteBuffer& in); + + private: + EMSA_PSS m_emsa_pss; + RSA m_rsa; + }; + + class RSA_PKCS1_EME : public RSA { + public: + // forward all constructions to RSA + template + RSA_PKCS1_EME(Args... args) + : RSA(args...) + { + } + + ~RSA_PKCS1_EME() {} + + virtual void encrypt(const ByteBuffer& in, ByteBuffer& out) override; + virtual void decrypt(const ByteBuffer& in, ByteBuffer& out) override; + + virtual void sign(const ByteBuffer&, ByteBuffer&) override; + virtual void verify(const ByteBuffer&, ByteBuffer&) override; + + virtual String class_name() const override { return "RSA_PKCS1-EME"; } + virtual size_t output_size() const override { return m_public_key.length(); } + }; +} +} diff --git a/Userland/test-crypto.cpp b/Userland/test-crypto.cpp index 19acf079c60..9f29eb760ee 100644 --- a/Userland/test-crypto.cpp +++ b/Userland/test-crypto.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,9 @@ int hmac_md5_tests(); int hmac_sha256_tests(); int hmac_sha512_tests(); +// Public-Key +int rsa_tests(); + // Big Integer int bigint_tests(); @@ -235,6 +239,9 @@ auto main(int argc, char** argv) -> int printf("unknown hash function '%s'\n", suite); return 1; } + if (mode_sv == "pk") { + return rsa_tests(); + } if (mode_sv == "bigint") { return bigint_tests(); } @@ -305,6 +312,12 @@ void hmac_sha256_test_process(); void hmac_sha512_test_name(); void hmac_sha512_test_process(); +void rsa_test_encrypt(); +void rsa_test_der_parse(); +void rsa_test_encrypt_decrypt(); +void rsa_emsa_pss_test_create(); +void bigint_test_number_theory(); // FIXME: we should really move these num theory stuff out + void bigint_test_fibo500(); void bigint_addition_edgecases(); void bigint_subtraction(); @@ -801,6 +814,142 @@ void hmac_sha512_test_process() } } +int rsa_tests() +{ + rsa_test_encrypt(); + rsa_test_der_parse(); + bigint_test_number_theory(); + rsa_test_encrypt_decrypt(); + rsa_emsa_pss_test_create(); + return 0; +} + +void rsa_test_encrypt() +{ + { + I_TEST((RSA RAW | Encryption)); + ByteBuffer data { "hellohellohellohellohellohellohellohellohellohellohellohello123-"_b }; + u8 result[] { 0x6f, 0x7b, 0xe2, 0xd3, 0x95, 0xf8, 0x8d, 0x87, 0x6d, 0x10, 0x5e, 0xc3, 0xcd, 0xf7, 0xbb, 0xa6, 0x62, 0x8e, 0x45, 0xa0, 0xf1, 0xe5, 0x0f, 0xdf, 0x69, 0xcb, 0xb6, 0xd5, 0x42, 0x06, 0x7d, 0x72, 0xa9, 0x5e, 0xae, 0xbf, 0xbf, 0x0f, 0xe0, 0xeb, 0x31, 0x31, 0xca, 0x8a, 0x81, 0x1e, 0xb9, 0xec, 0x6d, 0xcc, 0xb8, 0xa4, 0xac, 0xa3, 0x31, 0x05, 0xa9, 0xac, 0xc9, 0xd3, 0xe6, 0x2a, 0x18, 0xfe }; + Crypto::PK::RSA rsa( + "8126832723025844890518845777858816391166654950553329127845898924164623511718747856014227624997335860970996746552094406240834082304784428582653994490504519"_bigint, + "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint, + "65537"_bigint); + u8 buffer[rsa.output_size()]; + auto buf = ByteBuffer::wrap(buffer, sizeof(buffer)); + rsa.encrypt(data, buf); + if (memcmp(result, buf.data(), buf.size())) { + FAIL(Invalid encryption result); + print_buffer(buf, 16); + } else { + PASS; + } + } + { + I_TEST((RSA PKCS #1 1.5 | Encryption)); + ByteBuffer data { "hellohellohellohellohellohellohellohellohello123-"_b }; + Crypto::PK::RSA_PKCS1_EME rsa( + "8126832723025844890518845777858816391166654950553329127845898924164623511718747856014227624997335860970996746552094406240834082304784428582653994490504519"_bigint, + "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint, + "65537"_bigint); + u8 buffer[rsa.output_size()]; + auto buf = ByteBuffer::wrap(buffer, sizeof(buffer)); + rsa.encrypt(data, buf); + rsa.decrypt(buf, buf); + + if (memcmp(buf.data(), "hellohellohellohellohellohellohellohellohello123-", 49)) + FAIL(Invalid encryption); + else { + dbg() << "out size " << buf.size() << " values: " << StringView { (char*)buf.data(), buf.size() }; + + PASS; + } + } +} + +void bigint_test_number_theory() +{ + { + I_TEST((Number Theory | Modular Inverse)); + if (Crypto::NumberTheory::ModularInverse(7, 87) == 25) + PASS; + else + FAIL(Invalid result); + } + { + I_TEST((Number Theory | Modular Power)); + auto exp = Crypto::NumberTheory::ModularPower( + Crypto::UnsignedBigInteger::from_base10("2988348162058574136915891421498819466320163312926952423791023078876139"), + Crypto::UnsignedBigInteger::from_base10("2351399303373464486466122544523690094744975233415544072992656881240319"), + 10000); + + if (exp == 3059) { + PASS; + } else { + FAIL(Invalid result); + puts(exp.to_base10().characters()); + } + } +} + +void rsa_emsa_pss_test_create() +{ + { + // This is a template validity test + I_TEST((RSA EMSA_PSS | Construction)); + Crypto::PK::RSA rsa; + Crypto::PK::RSA_EMSA_PSS rsa_esma_pss(rsa); + PASS; + } +} + +void rsa_test_der_parse() +{ + I_TEST((RSA | ASN1 DER / PEM encoded Key import)); + auto privkey = R"(-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAJsrIYHxs1YL9tpfodaWs1lJoMdF4kgFisUFSj6nvBhJUlmBh607AlgTaX0E +DGPYycXYGZ2n6rqmms5lpDXBpUcCAwEAAQJAUNpPkmtEHDENxsoQBUXvXDYeXdePSiIBJhpU +joNOYoR5R9z5oX2cpcyykQ58FC2vKKg+x8N6xczG7qO95tw5UQIhAN354CP/FA+uTeJ6KJ+i +zCBCl58CjNCzO0s5HTc56el5AiEAsvPKXo5/9gS/S4UzDRP6abq7GreixTfjR8LXidk3FL8C +IQCTjYI861Y+hjMnlORkGSdvWlTHUj6gjEOh4TlWeJzQoQIgAxMZOQKtxCZUuxFwzRq4xLRG +nrDlBQpuxz7bwSyQO7UCIHrYMnDohgNbwtA5ZpW3H1cKKQQvueWm6sxW9P5sUrZ3 +-----END RSA PRIVATE KEY-----)"; + + Crypto::PK::RSA rsa(privkey); + if (rsa.public_key().public_exponent() == 65537) { + if (rsa.private_key().private_exponent() == "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint) { + PASS; + } else + FAIL(Invalid private exponent); + } else { + FAIL(Invalid public exponent); + } +} + +void rsa_test_encrypt_decrypt() +{ + I_TEST((RSA | Encrypt)); + dbg() << " creating rsa object"; + Crypto::PK::RSA rsa( + "9527497237087650398000977129550904920919162360737979403539302312977329868395261515707123424679295515888026193056908173564681660256268221509339074678416049"_bigint, + "39542231845947188736992321577701849924317746648774438832456325878966594812143638244746284968851807975097653255909707366086606867657273809465195392910913"_bigint, + "65537"_bigint); + dbg() << "Output size: " << rsa.output_size(); + auto dec = ByteBuffer::create_zeroed(rsa.output_size()); + auto enc = ByteBuffer::create_zeroed(rsa.output_size()); + enc.overwrite(0, "WellHelloFriendsWellHelloFriendsWellHelloFriendsWellHelloFriends", 64); + + rsa.encrypt(enc, dec); + rsa.decrypt(dec, enc); + + dbg() << "enc size " << enc.size() << " dec size " << dec.size(); + + if (memcmp(enc.data(), "WellHelloFriendsWellHelloFriendsWellHelloFriendsWellHelloFriends", 64) != 0) { + FAIL(Could not encrypt then decrypt); + } else { + PASS; + } +} + int bigint_tests() { bigint_test_fibo500(); @@ -1047,4 +1196,36 @@ void bigint_import_export() else PASS; } + { + I_TEST((BigInteger | BigEndian Encode / Decode roundtrip)); + u8 target_buffer[128]; + auto encoded = "12345678901234567890"_bigint; + auto size = encoded.export_data(target_buffer, 128); + auto decoded = Crypto::UnsignedBigInteger::import_data(target_buffer, size); + if (encoded != decoded) + FAIL(Could not roundtrip); + else + PASS; + } + { + I_TEST((BigInteger | BigEndian Import)); + auto number = Crypto::UnsignedBigInteger::import_data("hello"); + if (number == "448378203247"_bigint) { + PASS; + } else { + FAIL(Invalid value); + } + } + { + I_TEST((BigInteger | BigEndian Export)); + auto number = "448378203247"_bigint; + char exported[8] { 0 }; + auto exported_length = number.export_data((u8*)exported, 8); + if (exported_length == 5 && memcmp(exported + 3, "hello", 5) == 0) { + PASS; + } else { + FAIL(Invalid value); + print_buffer(ByteBuffer::wrap(exported - exported_length + 8, exported_length), -1); + } + } }