Forráskód Böngészése

LibCrypto: Implement RSA in terms of UnsignedBigInteger

This commit also adds enough ASN.1/DER to parse RSA keys
AnotherTest 5 éve
szülő
commit
7adb93ede9

+ 111 - 0
Libraries/LibCrypto/ASN1/ASN1.h

@@ -0,0 +1,111 @@
+/*
+ * 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/Types.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+
+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;
+    }
+}
+
+}

+ 474 - 0
Libraries/LibCrypto/ASN1/DER.h

@@ -0,0 +1,474 @@
+/*
+ * 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/Types.h>
+#include <LibCrypto/ASN1/ASN1.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+
+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 <size_t element_count>
+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 <size_t element_count>
+struct der_decode_sequence_many : public der_decode_sequence_many_base<element_count> {
+
+    template <typename ElementType, typename... Args>
+    constexpr void construct(size_t index, ASN1::Kind kind, size_t size, ElementType data, Args... args)
+    {
+        der_decode_sequence_many_base<element_count>::set(index, kind, size, (void*)data);
+        construct(index + 1, args...);
+    }
+
+    constexpr void construct(size_t index)
+    {
+        ASSERT(index == element_count);
+    }
+
+    template <typename... Args>
+    constexpr der_decode_sequence_many(const u8* in, size_t in_length, Args... args)
+        : der_decode_sequence_many_base<element_count>(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;
+}
+}

+ 74 - 0
Libraries/LibCrypto/ASN1/PEM.h

@@ -0,0 +1,74 @@
+/*
+ * 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/ASN1/ASN1.h>
+#include <LibCrypto/ASN1/DER.h>
+
+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);
+}
+
+}

+ 2 - 1
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

+ 202 - 0
Libraries/LibCrypto/NumberTheory/ModularFunctions.h

@@ -0,0 +1,202 @@
+/*
+ * 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/BigInt/UnsignedBigInteger.h>
+
+//#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 <size_t test_count>
+    static bool MR_primality_test(UnsignedBigInteger n, const Vector<UnsignedBigInteger, test_count>& 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<u32> 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<UnsignedBigInteger, 256> 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;
+        }
+    }
+}
+}

+ 59 - 0
Libraries/LibCrypto/PK/Code/Code.h

@@ -0,0 +1,59 @@
+/*
+ * 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>
+
+namespace Crypto {
+namespace PK {
+
+    enum class VerificationConsistency {
+        Consistent,
+        Inconsistent
+    };
+
+    template <typename HashFunction>
+    class Code {
+    public:
+        template <typename... Args>
+        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;
+    };
+
+}
+}

+ 179 - 0
Libraries/LibCrypto/PK/Code/EMSA_PSS.h

@@ -0,0 +1,179 @@
+/*
+ * 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/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 = 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;
+    };
+
+}
+}

+ 68 - 0
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(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;
+    };
+
+}
+}

+ 303 - 0
Libraries/LibCrypto/PK/RSA.cpp

@@ -0,0 +1,303 @@
+/*
+ * 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 <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(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 <typename HashFunction>
+    void RSA_EMSA_PSS<HashFunction>::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 <typename HashFunction>
+    VerificationConsistency RSA_EMSA_PSS<HashFunction>::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";
+    }
+}
+}

+ 234 - 0
Libraries/LibCrypto/PK/RSA.h

@@ -0,0 +1,234 @@
+/*
+ * 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/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(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<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(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 <typename HashFunction>
+    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<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(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(); }
+    };
+}
+}

+ 181 - 0
Userland/test-crypto.cpp

@@ -6,6 +6,7 @@
 #include <LibCrypto/Cipher/AES.h>
 #include <LibCrypto/Hash/MD5.h>
 #include <LibCrypto/Hash/SHA2.h>
+#include <LibCrypto/PK/RSA.h>
 #include <LibLine/Editor.h>
 #include <stdio.h>
 
@@ -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<Crypto::Hash::SHA256> 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);
+        }
+    }
 }