ec: Improve readability and clarity

- Move all of the ec functions into the Common::ec namespace.

- Give the public functions better names and some usage information.

- Move all of the "elt" related functions into an "elt" class including
  all of the arithmetic operations, so that the logic becomes clearer
  and feels less like assembly.

  This also makes it much more obvious what the parameters are, instead
  of only using unsigned char* (which doesn't tell anything about what
  the pointer is used for or the size).

- Similarly, add a new "Point" class and move point functions there.
  Overload the arithmetic operators to make calculations easier to read
This commit is contained in:
Léo Lam 2018-05-16 00:04:10 +02:00
parent e83591f188
commit 355b1b5d5b
3 changed files with 236 additions and 330 deletions

View File

@ -6,6 +6,7 @@
// Licensed under the terms of the GNU GPL, version 2
// http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <ctime>
@ -20,6 +21,158 @@
#pragma warning(disable : 4505)
#endif
namespace Common::ec
{
static const u8 square[16] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15,
0x40, 0x41, 0x44, 0x45, 0x50, 0x51, 0x54, 0x55};
struct Elt;
static Elt operator*(const Elt& a, const Elt& b);
struct Elt
{
bool IsZero() const
{
return std::all_of(data.begin(), data.end(), [](u8 b) { return b == 0; });
}
void MulX()
{
u8 carry = data[0] & 1;
u8 x = 0;
for (std::size_t i = 0; i < data.size() - 1; i++)
{
u8 y = data[i + 1];
data[i] = x ^ (y >> 7);
x = y << 1;
}
data[29] = x ^ carry;
data[20] ^= carry << 2;
}
Elt Square() const
{
std::array<u8, 60> wide;
for (std::size_t i = 0; i < data.size(); i++)
{
wide[2 * i] = square[data[i] >> 4];
wide[2 * i + 1] = square[data[i] & 15];
}
for (std::size_t i = 0; i < data.size(); i++)
{
u8 x = wide[i];
wide[i + 19] ^= x >> 7;
wide[i + 20] ^= x << 1;
wide[i + 29] ^= x >> 1;
wide[i + 30] ^= x << 7;
}
u8 x = wide[30] & ~1;
wide[49] ^= x >> 7;
wide[50] ^= x << 1;
wide[59] ^= x >> 1;
wide[30] &= 1;
Elt result;
std::copy(wide.cbegin() + 30, wide.cend(), result.data.begin());
return result;
}
Elt ItohTsujii(const Elt& b, std::size_t j) const
{
Elt t = *this;
while (j--)
t = t.Square();
return t * b;
}
Elt Inv() const
{
Elt t = ItohTsujii(*this, 1);
Elt s = t.ItohTsujii(*this, 1);
t = s.ItohTsujii(s, 3);
s = t.ItohTsujii(*this, 1);
t = s.ItohTsujii(s, 7);
s = t.ItohTsujii(t, 14);
t = s.ItohTsujii(*this, 1);
s = t.ItohTsujii(t, 29);
t = s.ItohTsujii(s, 58);
s = t.ItohTsujii(t, 116);
return s.Square();
}
std::array<u8, 30> data{};
};
static Elt operator+(const Elt& a, const Elt& b)
{
Elt d;
for (std::size_t i = 0; i < std::tuple_size<decltype(Elt::data)>{}; i++)
d.data[i] = a.data[i] ^ b.data[i];
return d;
}
static Elt operator*(const Elt& a, const Elt& b)
{
Elt d;
std::size_t i = 0;
u8 mask = 1;
for (std::size_t n = 0; n < 233; n++)
{
d.MulX();
if ((a.data[i] & mask) != 0)
d = d + b;
mask >>= 1;
if (mask == 0)
{
mask = 0x80;
i++;
}
}
return d;
}
static Elt operator/(const Elt& dividend, const Elt& divisor)
{
return dividend * divisor.Inv();
}
struct Point
{
Point() = default;
constexpr explicit Point(Elt x, Elt y) : m_data{{std::move(x), std::move(y)}} {}
explicit Point(const u8* data) { std::copy_n(data, sizeof(m_data), Data()); }
bool IsZero() const { return X().IsZero() && Y().IsZero(); }
Elt& X() { return m_data[0]; }
Elt& Y() { return m_data[1]; }
u8* Data() { return m_data[0].data.data(); }
const Elt& X() const { return m_data[0]; }
const Elt& Y() const { return m_data[1]; }
const u8* Data() const { return m_data[0].data.data(); }
Point Double() const
{
Point r;
if (X().IsZero())
return r;
const auto s = Y() / X() + X();
r.X() = s.Square() + s;
r.X().data[29] ^= 1;
r.Y() = s * r.X() + r.X() + X().Square();
return r;
}
private:
std::array<Elt, 2> m_data{};
static_assert(sizeof(decltype(m_data)) == 60, "Wrong size for m_data");
};
// y**2 + x*y = x**3 + x + b
UNUSED static const u8 ec_b[30] = {0x00, 0x66, 0x64, 0x7e, 0xde, 0x6c, 0x33, 0x2c, 0x7f, 0x8c,
0x09, 0x23, 0xbb, 0x58, 0x21, 0x3b, 0x33, 0x3b, 0x20, 0xe9,
@ -31,303 +184,50 @@ static const u8 ec_N[30] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x8a, 0x69, 0x22, 0x03, 0x1d, 0x26, 0x03, 0xcf, 0xe0, 0xd7};
// base point
static const u8 ec_G[60] = {0x00, 0xfa, 0xc9, 0xdf, 0xcb, 0xac, 0x83, 0x13, 0xbb, 0x21, 0x39, 0xf1,
0xbb, 0x75, 0x5f, 0xef, 0x65, 0xbc, 0x39, 0x1f, 0x8b, 0x36, 0xf8, 0xf8,
0xeb, 0x73, 0x71, 0xfd, 0x55, 0x8b, 0x01, 0x00, 0x6a, 0x08, 0xa4, 0x19,
0x03, 0x35, 0x06, 0x78, 0xe5, 0x85, 0x28, 0xbe, 0xbf, 0x8a, 0x0b, 0xef,
0xf8, 0x67, 0xa7, 0xca, 0x36, 0x71, 0x6f, 0x7e, 0x01, 0xf8, 0x10, 0x52};
constexpr Point ec_G{
{{{0x00, 0xfa, 0xc9, 0xdf, 0xcb, 0xac, 0x83, 0x13, 0xbb, 0x21, 0x39, 0xf1, 0xbb, 0x75, 0x5f,
0xef, 0x65, 0xbc, 0x39, 0x1f, 0x8b, 0x36, 0xf8, 0xf8, 0xeb, 0x73, 0x71, 0xfd, 0x55, 0x8b}}},
{{{0x01, 0x00, 0x6a, 0x08, 0xa4, 0x19, 0x03, 0x35, 0x06, 0x78, 0xe5, 0x85, 0x28, 0xbe, 0xbf,
0x8a, 0x0b, 0xef, 0xf8, 0x67, 0xa7, 0xca, 0x36, 0x71, 0x6f, 0x7e, 0x01, 0xf8, 0x10, 0x52}}}};
static void elt_copy(u8* d, const u8* a)
static Point operator+(const Point& a, const Point& b)
{
memcpy(d, a, 30);
}
if (a.IsZero())
return b;
if (b.IsZero())
return a;
static void elt_zero(u8* d)
{
memset(d, 0, 30);
}
static int elt_is_zero(const u8* d)
{
u32 i;
for (i = 0; i < 30; i++)
if (d[i] != 0)
return 0;
return 1;
}
static void elt_add(u8* d, const u8* a, const u8* b)
{
u32 i;
for (i = 0; i < 30; i++)
d[i] = a[i] ^ b[i];
}
static void elt_mul_x(u8* d, const u8* a)
{
u8 carry, x, y;
u32 i;
carry = a[0] & 1;
x = 0;
for (i = 0; i < 29; i++)
Elt u = a.X() + b.X();
if (u.IsZero())
{
y = a[i + 1];
d[i] = x ^ (y >> 7);
x = y << 1;
u = a.Y() + b.Y();
if (u.IsZero())
return a.Double();
return Point{};
}
d[29] = x ^ carry;
d[20] ^= carry << 2;
const Elt s = (a.Y() + b.Y()) / u;
Elt t = s.Square() + s + b.X();
t.data[29] ^= 1;
const Elt rx = t + a.X();
const Elt ry = s * t + a.Y() + rx;
return Point{rx, ry};
}
static void elt_mul(u8* d, const u8* a, const u8* b)
static Point operator*(const u8* a, const Point& b)
{
u32 i, n;
u8 mask;
elt_zero(d);
i = 0;
mask = 1;
for (n = 0; n < 233; n++)
Point d;
for (std::size_t i = 0; i < 30; i++)
{
elt_mul_x(d, d);
if ((a[i] & mask) != 0)
elt_add(d, d, b);
mask >>= 1;
if (mask == 0)
for (u8 mask = 0x80; mask != 0; mask >>= 1)
{
mask = 0x80;
i++;
}
}
}
static const u8 square[16] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15,
0x40, 0x41, 0x44, 0x45, 0x50, 0x51, 0x54, 0x55};
static void elt_square_to_wide(u8* d, const u8* a)
{
u32 i;
for (i = 0; i < 30; i++)
{
d[2 * i] = square[a[i] >> 4];
d[2 * i + 1] = square[a[i] & 15];
}
}
static void wide_reduce(u8* d)
{
u32 i;
u8 x;
for (i = 0; i < 30; i++)
{
x = d[i];
d[i + 19] ^= x >> 7;
d[i + 20] ^= x << 1;
d[i + 29] ^= x >> 1;
d[i + 30] ^= x << 7;
}
x = d[30] & ~1;
d[49] ^= x >> 7;
d[50] ^= x << 1;
d[59] ^= x >> 1;
d[30] &= 1;
}
static void elt_square(u8* d, const u8* a)
{
u8 wide[60];
elt_square_to_wide(wide, a);
wide_reduce(wide);
elt_copy(d, wide + 30);
}
static void itoh_tsujii(u8* d, const u8* a, const u8* b, u32 j)
{
u8 t[30];
elt_copy(t, a);
while (j--)
{
elt_square(d, t);
elt_copy(t, d);
}
elt_mul(d, t, b);
}
static void elt_inv(u8* d, const u8* a)
{
u8 t[30];
u8 s[30];
itoh_tsujii(t, a, a, 1);
itoh_tsujii(s, t, a, 1);
itoh_tsujii(t, s, s, 3);
itoh_tsujii(s, t, a, 1);
itoh_tsujii(t, s, s, 7);
itoh_tsujii(s, t, t, 14);
itoh_tsujii(t, s, a, 1);
itoh_tsujii(s, t, t, 29);
itoh_tsujii(t, s, s, 58);
itoh_tsujii(s, t, t, 116);
elt_square(d, s);
}
UNUSED static int point_is_on_curve(u8* p)
{
u8 s[30], t[30];
u8 *x, *y;
x = p;
y = p + 30;
elt_square(t, x);
elt_mul(s, t, x);
elt_add(s, s, t);
elt_square(t, y);
elt_add(s, s, t);
elt_mul(t, x, y);
elt_add(s, s, t);
elt_add(s, s, ec_b);
return elt_is_zero(s);
}
static int point_is_zero(const u8* p)
{
return elt_is_zero(p) && elt_is_zero(p + 30);
}
static void point_double(u8* r, const u8* p)
{
u8 s[30], t[30];
const u8 *px, *py;
u8 *rx, *ry;
px = p;
py = p + 30;
rx = r;
ry = r + 30;
if (elt_is_zero(px))
{
elt_zero(rx);
elt_zero(ry);
return;
}
elt_inv(t, px);
elt_mul(s, py, t);
elt_add(s, s, px);
elt_square(t, px);
elt_square(rx, s);
elt_add(rx, rx, s);
rx[29] ^= 1;
elt_mul(ry, s, rx);
elt_add(ry, ry, rx);
elt_add(ry, ry, t);
}
static void point_add(u8* r, const u8* p, const u8* q)
{
u8 s[30], t[30], u[30];
const u8 *px, *py, *qx, *qy;
u8 *rx, *ry;
px = p;
py = p + 30;
qx = q;
qy = q + 30;
rx = r;
ry = r + 30;
if (point_is_zero(p))
{
elt_copy(rx, qx);
elt_copy(ry, qy);
return;
}
if (point_is_zero(q))
{
elt_copy(rx, px);
elt_copy(ry, py);
return;
}
elt_add(u, px, qx);
if (elt_is_zero(u))
{
elt_add(u, py, qy);
if (elt_is_zero(u))
point_double(r, p);
else
{
elt_zero(rx);
elt_zero(ry);
}
return;
}
elt_inv(t, u);
elt_add(u, py, qy);
elt_mul(s, t, u);
elt_square(t, s);
elt_add(t, t, s);
elt_add(t, t, qx);
t[29] ^= 1;
elt_mul(u, s, t);
elt_add(s, u, py);
elt_add(rx, t, px);
elt_add(ry, s, rx);
}
static void point_mul(u8* d, const u8* a, const u8* b) // a is bignum
{
u32 i;
u8 mask;
elt_zero(d);
elt_zero(d + 30);
for (i = 0; i < 30; i++)
for (mask = 0x80; mask != 0; mask >>= 1)
{
point_double(d, d);
d = d.Double();
if ((a[i] & mask) != 0)
point_add(d, d, b);
d = d + b;
}
}
return d;
}
static void silly_random(u8* rndArea, u8 count)
@ -341,19 +241,13 @@ static void silly_random(u8* rndArea, u8 count)
}
}
void generate_ecdsa(u8* R, u8* S, const u8* k, const u8* hash)
std::array<u8, 60> Sign(const u8* key, const u8* hash)
{
u8 e[30];
u8 kk[30];
u8 m[30];
u8 minv[30];
u8 mG[60];
// FILE *fp;
elt_zero(e);
u8 e[30]{};
memcpy(e + 10, hash, 20);
// Changing random number generator to a lame one...
u8 m[30];
silly_random(m, sizeof(m));
// fp = fopen("/dev/random", "rb");
// if (fread(m, sizeof m, 1, fp) != 1)
@ -361,61 +255,65 @@ void generate_ecdsa(u8* R, u8* S, const u8* k, const u8* hash)
// fclose(fp);
m[0] = 0;
// R = (mG).x
point_mul(mG, m, ec_G);
elt_copy(R, mG);
if (bn_compare(R, ec_N, 30) >= 0)
bn_sub_modulus(R, ec_N, 30);
Elt r = (m * ec_G).X();
if (bn_compare(r.data.data(), ec_N, 30) >= 0)
bn_sub_modulus(r.data.data(), ec_N, 30);
// S = m**-1*(e + Rk) (mod N)
elt_copy(kk, k);
if (bn_compare(kk, ec_N, 30) >= 0)
bn_sub_modulus(kk, ec_N, 30);
bn_mul(S, R, kk, ec_N, 30);
bn_add(kk, S, e, ec_N, 30);
bn_inv(minv, m, ec_N, 30);
bn_mul(S, minv, kk, ec_N, 30);
u8 kk[30];
std::copy_n(key, sizeof(kk), kk);
if (bn_compare(kk, ec_N, sizeof(kk)) >= 0)
bn_sub_modulus(kk, ec_N, sizeof(kk));
Elt s;
bn_mul(s.data.data(), r.data.data(), kk, ec_N, 30);
bn_add(kk, s.data.data(), e, ec_N, sizeof(kk));
u8 minv[30];
bn_inv(minv, m, ec_N, sizeof(minv));
bn_mul(s.data.data(), minv, kk, ec_N, 30);
std::array<u8, 60> signature;
std::copy(r.data.cbegin(), r.data.cend(), signature.begin());
std::copy(s.data.cbegin(), s.data.cend(), signature.begin() + 30);
return signature;
}
UNUSED static int check_ecdsa(u8* Q, u8* R, u8* S, const u8* hash)
{
u8 Sinv[30];
u8 e[30];
u8 w1[30], w2[30];
u8 r1[60], r2[60];
bn_inv(Sinv, S, ec_N, 30);
elt_zero(e);
u8 e[30]{};
memcpy(e + 10, hash, 20);
u8 w1[30], w2[30];
bn_mul(w1, e, Sinv, ec_N, 30);
bn_mul(w2, R, Sinv, ec_N, 30);
point_mul(r1, w1, ec_G);
point_mul(r2, w2, Q);
Point r1 = w1 * ec_G + w2 * Point{Q};
auto& rx = r1.X().data;
if (bn_compare(rx.data(), ec_N, 30) >= 0)
bn_sub_modulus(rx.data(), ec_N, 30);
point_add(r1, r1, r2);
if (bn_compare(r1, ec_N, 30) >= 0)
bn_sub_modulus(r1, ec_N, 30);
return (bn_compare(r1, R, 30) == 0);
return (bn_compare(rx.data(), R, 30) == 0);
}
void ec_priv_to_pub(const u8* k, u8* Q)
std::array<u8, 60> PrivToPub(const u8* key)
{
point_mul(Q, k, ec_G);
const Point data = key * ec_G;
std::array<u8, 60> result;
std::copy_n(data.Data(), result.size(), result.begin());
return result;
}
std::array<u8, 60> ComputeSharedSecret(const u8* private_key, const u8* public_key)
{
std::array<u8, 60> shared_secret;
point_mul(shared_secret.data(), private_key, public_key);
const Point data = private_key * Point{public_key};
std::copy_n(data.Data(), shared_secret.size(), shared_secret.begin());
return shared_secret;
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
} // namespace Common::ec

View File

@ -8,8 +8,14 @@
#include "Common/CommonTypes.h"
void generate_ecdsa(u8* R, u8* S, const u8* k, const u8* hash);
void ec_priv_to_pub(const u8* k, u8* Q);
namespace Common::ec
{
/// Generate a signature using ECDSA.
std::array<u8, 60> Sign(const u8* key, const u8* hash);
/// Compute a shared secret from a private key (30 bytes) and public key (60 bytes).
std::array<u8, 60> ComputeSharedSecret(const u8* private_key, const u8* public_key);
/// Convert a ECC private key (30 bytes) to a public key (60 bytes).
std::array<u8, 60> PrivToPub(const u8* key);
} // namespace Common::ec

View File

@ -251,7 +251,7 @@ ReturnCode IOSC::ComputeSharedKey(Handle dest_handle, Handle private_handle, Han
// Calculate the ECC shared secret.
const std::array<u8, 0x3c> shared_secret =
ComputeSharedSecret(private_entry->data.data(), public_entry->data.data());
Common::ec::ComputeSharedSecret(private_entry->data.data(), public_entry->data.data());
std::array<u8, 20> sha1;
mbedtls_sha1(shared_secret.data(), shared_secret.size() / 2, sha1.data());
@ -425,7 +425,8 @@ static Certificate MakeBlankSigECCert(const char* signer, const char* name, cons
std::strncpy(reinterpret_cast<char*>(cert_out.data()) + 0xc4, name, 0x40);
const u32 swapped_key_id = Common::swap32(key_id);
std::memcpy(cert_out.data() + 0x104, &swapped_key_id, sizeof(swapped_key_id));
ec_priv_to_pub(private_key, cert_out.data() + 0x108);
const std::array<u8, 60> public_key = Common::ec::PrivToPub(private_key);
std::copy(public_key.cbegin(), public_key.cend(), cert_out.begin() + 0x108);
return cert_out;
}
@ -454,11 +455,12 @@ void IOSC::Sign(u8* sig_out, u8* ap_cert_out, u64 title_id, const u8* data, u32
std::copy(cert.begin(), cert.end(), ap_cert_out);
mbedtls_sha1(ap_cert_out + 0x80, 0x100, hash.data());
generate_ecdsa(ap_cert_out + 4, ap_cert_out + 34, m_key_entries[HANDLE_CONSOLE_KEY].data.data(),
hash.data());
auto signature = Common::ec::Sign(m_key_entries[HANDLE_CONSOLE_KEY].data.data(), hash.data());
std::copy(signature.cbegin(), signature.cend(), ap_cert_out + 4);
mbedtls_sha1(data, data_size, hash.data());
generate_ecdsa(sig_out, sig_out + 30, ap_priv.data(), hash.data());
signature = Common::ec::Sign(ap_priv.data(), hash.data());
std::copy(signature.cbegin(), signature.cend(), sig_out);
}
constexpr std::array<u8, 512> ROOT_PUBLIC_KEY = {