LibCrypto: Implement RSA in terms of UnsignedBigInteger

This commit also adds enough ASN.1/DER to parse RSA keys
This commit is contained in:
AnotherTest 2020-04-09 02:39:48 +04:30 committed by Andreas Kling
parent 6b742c69bd
commit 7adb93ede9
Notes: sideshowbarker 2024-07-19 07:05:21 +09:00
11 changed files with 1887 additions and 1 deletions

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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;
}
}
}
}

View file

@ -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;
};
}
}

View file

@ -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;
};
}
}

View file

@ -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;
};
}
}

View file

@ -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";
}
}
}

View file

@ -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(); }
};
}
}

View file

@ -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);
}
}
}