feat: port GIP protocol and auth stack
Port the GIP protocol and authentication layers from medusalix/xone into the C++ stack: - crypto: SHA-256/HMAC (CommonCrypto), RSA PKCS#1 (Security.framework), and a self-contained P-256 ECDH validated against OpenSSL vectors - auth: v1 (RSA) and v2 (ECDH) handshake state machine - gip: header/varint/chunk handling and packet dispatch, with the kernel device model replaced by transport + client_listener interfaces Adds test_crypto, test_auth, and test_gip suites. Co-Authored-By: deepseek (deepseek/deepseek-v4-pro-0813): ported crypto, auth, and GIP
This commit is contained in:
@@ -0,0 +1,483 @@
|
||||
// GIP authentication handshake (port of medusalix/xone auth/auth.c).
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "common/log.hpp"
|
||||
#include "common/types.hpp"
|
||||
|
||||
namespace xone::auth {
|
||||
|
||||
namespace {
|
||||
|
||||
enum : u8 {
|
||||
ctx_handshake = 0x00,
|
||||
ctx_control = 0x01,
|
||||
};
|
||||
|
||||
enum : u8 {
|
||||
cmd_host_hello = 0x01,
|
||||
cmd_client_hello = 0x02,
|
||||
cmd_client_certificate = 0x03,
|
||||
cmd_host_secret = 0x05,
|
||||
cmd_host_finish = 0x07,
|
||||
cmd_client_finish = 0x08,
|
||||
|
||||
cmd2_host_hello = 0x21,
|
||||
cmd2_client_hello = 0x22,
|
||||
cmd2_client_certificate = 0x23,
|
||||
cmd2_client_pubkey = 0x24,
|
||||
cmd2_host_pubkey = 0x25,
|
||||
cmd2_host_finish = 0x26,
|
||||
cmd2_client_finish = 0x27,
|
||||
};
|
||||
|
||||
enum : u8 {
|
||||
ctrl_complete = 0x00,
|
||||
ctrl_reset = 0x01,
|
||||
};
|
||||
|
||||
enum : u8 {
|
||||
opt_acknowledge = 1 << 0,
|
||||
opt_request = 1 << 1,
|
||||
opt_from_host = 1 << 6,
|
||||
};
|
||||
|
||||
struct header_handshake {
|
||||
u8 context;
|
||||
u8 options;
|
||||
u8 error;
|
||||
u8 command;
|
||||
u16 length;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct header_data {
|
||||
u8 command;
|
||||
u8 version;
|
||||
u16 length;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct header_full {
|
||||
header_handshake handshake;
|
||||
header_data data;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct header_control {
|
||||
u8 context;
|
||||
u8 control;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt_request {
|
||||
header_handshake header;
|
||||
std::array<u8, k_trailer_len> trailer;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt_host_hello {
|
||||
header_full header;
|
||||
std::array<u8, k_random_len> random;
|
||||
std::array<u8, 4> unknown1;
|
||||
std::array<u8, 4> unknown2;
|
||||
std::array<u8, k_trailer_len> trailer;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt_host_secret {
|
||||
header_full header;
|
||||
std::array<u8, k_encrypted_pms_len> encrypted_pms;
|
||||
std::array<u8, k_trailer_len> trailer;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt_host_finish {
|
||||
header_full header;
|
||||
std::array<u8, k_transcript_len> transcript;
|
||||
std::array<u8, k_trailer_len> trailer;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt_client_hello {
|
||||
std::array<u8, k_random_len> random;
|
||||
std::array<u8, 48> unknown;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt_client_finish {
|
||||
std::array<u8, k_transcript_len> transcript;
|
||||
std::array<u8, 32> unknown;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt2_host_hello {
|
||||
header_full header;
|
||||
std::array<u8, k_random_len> random;
|
||||
std::array<u8, 4> unknown;
|
||||
std::array<u8, k_trailer_len> trailer;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt2_host_pubkey {
|
||||
header_full header;
|
||||
std::array<u8, k_pubkey2_len> pubkey;
|
||||
std::array<u8, k_trailer_len> trailer;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt2_client_hello {
|
||||
std::array<u8, k_random_len> random;
|
||||
std::array<u8, 108> unknown1;
|
||||
std::array<u8, 32> unknown2;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt2_client_cert {
|
||||
std::array<char, 4> header;
|
||||
std::array<u8, 136> unknown1;
|
||||
std::array<char, 32> chip;
|
||||
std::array<char, 20> revision;
|
||||
std::array<u8, 576> unknown2;
|
||||
} XONE_PACKED;
|
||||
|
||||
struct pkt2_client_pubkey {
|
||||
std::array<u8, k_pubkey2_len> pubkey;
|
||||
std::array<u8, 64> unknown;
|
||||
} XONE_PACKED;
|
||||
|
||||
} // namespace
|
||||
|
||||
auth::auth(auth_sink& sink)
|
||||
: sink_(sink)
|
||||
{
|
||||
}
|
||||
|
||||
auto auth::start() -> int
|
||||
{
|
||||
return send_hello();
|
||||
}
|
||||
|
||||
auto auth::process_pkt(std::span<u8 const> data) -> int
|
||||
{
|
||||
auto const* hdr = reinterpret_cast<header_handshake const*>(data.data());
|
||||
|
||||
if (data.size() < sizeof(*hdr))
|
||||
return -EINVAL;
|
||||
|
||||
if (hdr->error)
|
||||
return -EPROTO;
|
||||
|
||||
if (hdr->options & opt_acknowledge) {
|
||||
if (hdr->command == 0x01)
|
||||
return handle_pkt_acknowledge();
|
||||
|
||||
log_msg(log_level::error, "auth: handshake failed: 0x%02x", hdr->command);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
return handle_pkt_data(data);
|
||||
}
|
||||
|
||||
auto auth::send_pkt(u8 cmd, void const* pkt, std::size_t len) -> int
|
||||
{
|
||||
auto* hdr = reinterpret_cast<header_full*>(const_cast<u8*>(static_cast<u8 const*>(pkt)));
|
||||
u16 data_len = static_cast<u16>(len - sizeof(hdr->handshake) - k_trailer_len);
|
||||
|
||||
hdr->handshake.context = ctx_handshake;
|
||||
hdr->handshake.options = opt_acknowledge | opt_from_host;
|
||||
hdr->handshake.error = 0;
|
||||
hdr->handshake.command = cmd;
|
||||
store_be16(&hdr->handshake.length, data_len);
|
||||
|
||||
hdr->data.command = cmd;
|
||||
hdr->data.version = cmd >= cmd2_host_hello ? 0x02 : 0x01;
|
||||
store_be16(&hdr->data.length, static_cast<u16>(data_len - sizeof(hdr->data)));
|
||||
|
||||
last_sent_command_ = cmd;
|
||||
transcript_.update(
|
||||
{reinterpret_cast<u8 const*>(hdr) + sizeof(hdr->handshake), data_len});
|
||||
|
||||
return sink_.send({reinterpret_cast<u8 const*>(hdr), len}, true);
|
||||
}
|
||||
|
||||
auto auth::request_pkt(u8 cmd, std::uint16_t len) -> int
|
||||
{
|
||||
pkt_request req{};
|
||||
u16 data_len = static_cast<u16>(len + sizeof(header_data));
|
||||
|
||||
req.header.context = ctx_handshake;
|
||||
req.header.options = opt_request | opt_from_host;
|
||||
req.header.error = 0;
|
||||
req.header.command = cmd;
|
||||
store_be16(&req.header.length, data_len);
|
||||
|
||||
return sink_.send({reinterpret_cast<u8 const*>(&req), sizeof(req)}, true);
|
||||
}
|
||||
|
||||
auto auth::send_hello() -> int
|
||||
{
|
||||
pkt_host_hello pkt{};
|
||||
random_bytes(random_host_);
|
||||
std::memcpy(pkt.random.data(), random_host_.data(), k_random_len);
|
||||
return send_pkt(cmd_host_hello, &pkt.header, sizeof(pkt));
|
||||
}
|
||||
|
||||
auto auth::send_finish(u8 cmd) -> int
|
||||
{
|
||||
pkt_host_finish pkt{};
|
||||
auto transcript = get_transcript();
|
||||
prf_sha256(master_secret_, "Host Finished", transcript, pkt.transcript);
|
||||
return send_pkt(cmd, &pkt.header, sizeof(pkt));
|
||||
}
|
||||
|
||||
auto auth::send_complete() -> int
|
||||
{
|
||||
header_control hdr{};
|
||||
hdr.context = ctx_control;
|
||||
hdr.control = ctrl_complete;
|
||||
return sink_.send({reinterpret_cast<u8 const*>(&hdr), sizeof(hdr)}, false);
|
||||
}
|
||||
|
||||
auto auth::get_transcript() -> std::array<u8, k_transcript_len>
|
||||
{
|
||||
sha256 snap = transcript_;
|
||||
std::array<u8, k_transcript_len> out{};
|
||||
snap.finalize(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
auto auth::exchange_rsa() -> void
|
||||
{
|
||||
pkt_host_secret pkt{};
|
||||
std::array<u8, k_random_len * 2> random{};
|
||||
std::array<u8, k_secret_len> pms{};
|
||||
|
||||
std::copy(random_host_.begin(), random_host_.end(), random.begin());
|
||||
std::copy(random_client_.begin(), random_client_.end(),
|
||||
random.begin() + k_random_len);
|
||||
|
||||
random_bytes(pms);
|
||||
|
||||
std::array<u8, k_encrypted_pms_len> encrypted{};
|
||||
if (!rsa_encrypt_pkcs1(pubkey_client_, pms, encrypted)) {
|
||||
log_msg(log_level::error, "auth: encrypt RSA failed");
|
||||
return;
|
||||
}
|
||||
std::memcpy(pkt.encrypted_pms.data(), encrypted.data(), k_encrypted_pms_len);
|
||||
|
||||
prf_sha256(pms, "Master Secret", random, master_secret_);
|
||||
|
||||
if (send_pkt(cmd_host_secret, &pkt.header, sizeof(pkt)))
|
||||
log_msg(log_level::error, "auth: send pkt failed");
|
||||
}
|
||||
|
||||
auto auth::exchange_ecdh() -> void
|
||||
{
|
||||
pkt2_host_pubkey pkt{};
|
||||
std::array<u8, k_random_len * 2> random{};
|
||||
std::array<u8, k_secret2_len> secret{};
|
||||
|
||||
std::copy(random_host_.begin(), random_host_.end(), random.begin());
|
||||
std::copy(random_client_.begin(), random_client_.end(),
|
||||
random.begin() + k_random_len);
|
||||
|
||||
ec_scalar d{};
|
||||
ec_point q{};
|
||||
ec_generate_keypair(d, q);
|
||||
std::memcpy(pkt.pubkey.data(), q.data(), k_pubkey2_len);
|
||||
|
||||
ec_scalar shared_x{};
|
||||
if (!ec_compute_shared(d, pubkey_client2_, shared_x)) {
|
||||
log_msg(log_level::error, "auth: compute ECDH failed");
|
||||
return;
|
||||
}
|
||||
|
||||
// The PRF key is SHA-256 of the raw shared x-coordinate.
|
||||
sha256 h;
|
||||
h.update(shared_x);
|
||||
h.finalize(secret);
|
||||
|
||||
prf_sha256(secret, "Master Secret", random, master_secret_);
|
||||
|
||||
if (send_pkt(cmd2_host_pubkey, &pkt.header, sizeof(pkt)))
|
||||
log_msg(log_level::error, "auth: send pkt failed");
|
||||
}
|
||||
|
||||
auto auth::complete_handshake() -> void
|
||||
{
|
||||
std::array<u8, k_random_len * 2> random{};
|
||||
std::array<u8, k_session_key_len> key{};
|
||||
|
||||
std::copy(random_host_.begin(), random_host_.end(), random.begin());
|
||||
std::copy(random_client_.begin(), random_client_.end(),
|
||||
random.begin() + k_random_len);
|
||||
|
||||
prf_sha256(master_secret_,
|
||||
"EXPORTER DAWN data channel session key for controller", random,
|
||||
key);
|
||||
|
||||
if (send_complete())
|
||||
log_msg(log_level::error, "auth: send complete failed");
|
||||
|
||||
if (sink_.set_encryption_key(key))
|
||||
log_msg(log_level::error, "auth: set encryption key failed");
|
||||
}
|
||||
|
||||
auto auth::handle_pkt_acknowledge() -> int
|
||||
{
|
||||
switch (last_sent_command_) {
|
||||
case cmd2_host_hello:
|
||||
return request_pkt(cmd2_client_hello, sizeof(pkt2_client_hello));
|
||||
case cmd2_host_pubkey:
|
||||
return send_finish(cmd2_host_finish);
|
||||
case cmd2_host_finish:
|
||||
return request_pkt(cmd2_client_finish, sizeof(pkt_client_finish));
|
||||
case cmd_host_hello:
|
||||
return request_pkt(cmd_client_hello, sizeof(pkt_client_hello));
|
||||
case cmd_host_secret:
|
||||
return send_finish(cmd_host_finish);
|
||||
case cmd_host_finish:
|
||||
return request_pkt(cmd_client_finish, sizeof(pkt_client_finish));
|
||||
default:
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
auto auth::handle_pkt_data(std::span<u8 const> data) -> int
|
||||
{
|
||||
auto const* hdr = reinterpret_cast<header_full const*>(data.data());
|
||||
|
||||
if (data.size() < sizeof(*hdr))
|
||||
return -EINVAL;
|
||||
|
||||
// The controller upgraded to auth v2: restart the handshake.
|
||||
if (hdr->handshake.command != hdr->data.command) {
|
||||
log_msg(log_level::debug, "auth: protocol upgrade to v2");
|
||||
transcript_ = sha256{};
|
||||
return send_hello2();
|
||||
}
|
||||
|
||||
int err = dispatch_pkt(hdr->data.command, data.subspan(sizeof(*hdr)));
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
transcript_.update(data.subspan(sizeof(hdr->handshake)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto auth::dispatch_pkt(u8 cmd, std::span<u8 const> data) -> int
|
||||
{
|
||||
switch (cmd) {
|
||||
case cmd2_client_hello:
|
||||
return handle_hello2(data);
|
||||
case cmd2_client_certificate:
|
||||
return handle_certificate2(data);
|
||||
case cmd2_client_pubkey:
|
||||
return handle_pubkey(data);
|
||||
case cmd2_client_finish:
|
||||
return handle_finish(data);
|
||||
case cmd_client_hello:
|
||||
return handle_hello(data);
|
||||
case cmd_client_certificate:
|
||||
return handle_certificate(data);
|
||||
case cmd_client_finish:
|
||||
return handle_finish(data);
|
||||
default:
|
||||
return -EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
auto auth::handle_hello(std::span<u8 const> data) -> int
|
||||
{
|
||||
auto const* pkt = reinterpret_cast<pkt_client_hello const*>(data.data());
|
||||
|
||||
if (data.size() < sizeof(*pkt))
|
||||
return -EINVAL;
|
||||
|
||||
std::memcpy(random_client_.data(), pkt->random.data(), k_random_len);
|
||||
return request_pkt(cmd_client_certificate, k_certificate_max_len);
|
||||
}
|
||||
|
||||
auto auth::handle_certificate(std::span<u8 const> data) -> int
|
||||
{
|
||||
// The client cert embeds a DER RSAPublicKey: ASN.1 SEQUENCE of length
|
||||
// 0x010a. Search for its header and copy the whole 270-byte key.
|
||||
static constexpr u8 asn1_seq[] = {0x30, 0x82, 0x01, 0x0a};
|
||||
|
||||
if (data.size() > k_certificate_max_len)
|
||||
return -EINVAL;
|
||||
|
||||
for (std::size_t i = 0; i + sizeof(asn1_seq) <= data.size(); ++i) {
|
||||
if (std::memcmp(data.data() + i, asn1_seq, sizeof(asn1_seq)))
|
||||
continue;
|
||||
|
||||
if (i + k_pubkey_len > data.size())
|
||||
return -EINVAL;
|
||||
|
||||
std::memcpy(pubkey_client_.data(), data.data() + i, k_pubkey_len);
|
||||
exchange_rsa();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
auto auth::handle_finish(std::span<u8 const> data) -> int
|
||||
{
|
||||
auto const* pkt = reinterpret_cast<pkt_client_finish const*>(data.data());
|
||||
|
||||
if (data.size() < sizeof(*pkt))
|
||||
return -EINVAL;
|
||||
|
||||
auto transcript = get_transcript();
|
||||
std::array<u8, k_transcript_len> finished{};
|
||||
prf_sha256(master_secret_, "Device Finished", transcript, finished);
|
||||
|
||||
if (std::memcmp(pkt->transcript.data(), finished.data(), k_transcript_len)) {
|
||||
log_msg(log_level::error, "auth: transcript mismatch");
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
complete_handshake();
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto auth::send_hello2() -> int
|
||||
{
|
||||
pkt2_host_hello pkt{};
|
||||
random_bytes(random_host_);
|
||||
std::memcpy(pkt.random.data(), random_host_.data(), k_random_len);
|
||||
return send_pkt(cmd2_host_hello, &pkt.header, sizeof(pkt));
|
||||
}
|
||||
|
||||
auto auth::handle_hello2(std::span<u8 const> data) -> int
|
||||
{
|
||||
auto const* pkt = reinterpret_cast<pkt2_client_hello const*>(data.data());
|
||||
|
||||
if (data.size() < sizeof(*pkt))
|
||||
return -EINVAL;
|
||||
|
||||
std::memcpy(random_client_.data(), pkt->random.data(), k_random_len);
|
||||
return request_pkt(cmd2_client_certificate, sizeof(pkt2_client_cert));
|
||||
}
|
||||
|
||||
auto auth::handle_certificate2(std::span<u8 const> data) -> int
|
||||
{
|
||||
auto const* pkt = reinterpret_cast<pkt2_client_cert const*>(data.data());
|
||||
|
||||
if (data.size() < sizeof(*pkt))
|
||||
return -EINVAL;
|
||||
|
||||
log_msg(log_level::debug, "auth: chip=%.*s, revision=%.*s",
|
||||
static_cast<int>(sizeof(pkt->chip)), pkt->chip.data(),
|
||||
static_cast<int>(sizeof(pkt->revision)), pkt->revision.data());
|
||||
|
||||
return request_pkt(cmd2_client_pubkey, sizeof(pkt2_client_pubkey));
|
||||
}
|
||||
|
||||
auto auth::handle_pubkey(std::span<u8 const> data) -> int
|
||||
{
|
||||
auto const* pkt = reinterpret_cast<pkt2_client_pubkey const*>(data.data());
|
||||
|
||||
if (data.size() < sizeof(*pkt))
|
||||
return -EINVAL;
|
||||
|
||||
std::memcpy(pubkey_client2_.data(), pkt->pubkey.data(), k_pubkey2_len);
|
||||
exchange_ecdh();
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace xone::auth
|
||||
+549
-2
@@ -1,7 +1,554 @@
|
||||
// Auth + crypto
|
||||
// TODO(phase 1): port from medusalix/xone auth/auth.c + auth/crypto.c.
|
||||
// Auth + crypto primitives (CommonCrypto / Security.framework / P-256 ECDH)
|
||||
// Port target: medusalix/xone auth/crypto.c.
|
||||
|
||||
#include "auth/crypto.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Security/Security.h>
|
||||
|
||||
namespace xone::auth {
|
||||
|
||||
namespace {
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Field arithmetic over GF(p), p = P-256, in Montgomery form.
|
||||
// --------------------------------------------------------------------------
|
||||
// Values are stored as v * R mod p with R = 2^256. Multiplication is a
|
||||
// Montgomery product (schoolbook 4x4 -> 8 limbs, then REDC). Addition and
|
||||
// subtraction work directly on the stored values with a single conditional
|
||||
// p-subtract / p-add, which is valid for the Montgomery representation.
|
||||
// --------------------------------------------------------------------------
|
||||
using u64 = std::uint64_t;
|
||||
using u128 = unsigned __int128;
|
||||
using s128 = __int128;
|
||||
|
||||
struct fe {
|
||||
u64 l[4];
|
||||
};
|
||||
|
||||
// Modulus p in normal form (used by REDC and the conditional reductions).
|
||||
constexpr fe k_p = {{0xffffffffffffffffULL, 0x00000000ffffffffULL,
|
||||
0x0000000000000000ULL, 0xffffffff00000001ULL}};
|
||||
|
||||
constexpr fe k_zero = {{0, 0, 0, 0}};
|
||||
|
||||
// Montgomery form of small constants and curve parameters (precomputed).
|
||||
constexpr fe k_one = {{0x0000000000000001ULL, 0xffffffff00000000ULL,
|
||||
0xffffffffffffffffULL, 0x00000000fffffffeULL}};
|
||||
constexpr fe k_two = {{0x0000000000000002ULL, 0xfffffffe00000000ULL,
|
||||
0xffffffffffffffffULL, 0x00000001fffffffdULL}};
|
||||
constexpr fe k_three = {{0x0000000000000003ULL, 0xfffffffd00000000ULL,
|
||||
0xffffffffffffffffULL, 0x00000002fffffffcULL}};
|
||||
constexpr fe k_four = {{0x0000000000000004ULL, 0xfffffffc00000000ULL,
|
||||
0xffffffffffffffffULL, 0x00000003fffffffbULL}};
|
||||
constexpr fe k_eight = {{0x0000000000000008ULL, 0xfffffff800000000ULL,
|
||||
0xffffffffffffffffULL, 0x00000007fffffff7ULL}};
|
||||
// R^2 mod p, for converting normal form into Montgomery form.
|
||||
constexpr fe k_r2 = {{0x0000000000000003ULL, 0xfffffffbffffffffULL,
|
||||
0xfffffffffffffffeULL, 0x00000004fffffffdULL}};
|
||||
constexpr fe k_mgx = {{0x79e730d418a9143cULL, 0x75ba95fc5fedb601ULL,
|
||||
0x79fb732b77622510ULL, 0x18905f76a53755c6ULL}};
|
||||
constexpr fe k_mgy = {{0xddf25357ce95560aULL, 0x8b4ab8e4ba19e45cULL,
|
||||
0xd2e88688dd21f325ULL, 0x8571ff1825885d85ULL}};
|
||||
constexpr fe k_mb = {{0xd89cdf6229c4bddfULL, 0xacf005cd78843090ULL,
|
||||
0xe5a220abf7212ed6ULL, 0xdc30061d04874834ULL}};
|
||||
|
||||
auto fe_is_zero(fe const& a) -> bool
|
||||
{
|
||||
return a.l[0] == 0 && a.l[1] == 0 && a.l[2] == 0 && a.l[3] == 0;
|
||||
}
|
||||
|
||||
auto fe_eq(fe const& a, fe const& b) -> bool
|
||||
{
|
||||
return a.l[0] == b.l[0] && a.l[1] == b.l[1] && a.l[2] == b.l[2] &&
|
||||
a.l[3] == b.l[3];
|
||||
}
|
||||
|
||||
// a >= b, as unsigned integers.
|
||||
auto fe_ge(fe const& a, fe const& b) -> bool
|
||||
{
|
||||
for (int i = 3; i >= 0; --i) {
|
||||
if (a.l[i] > b.l[i])
|
||||
return true;
|
||||
if (a.l[i] < b.l[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// a - b, modulo 2^256 (wraps if a < b).
|
||||
auto fe_sub_raw(fe const& a, fe const& b) -> fe
|
||||
{
|
||||
fe r{};
|
||||
s128 borrow = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
s128 t = static_cast<s128>(a.l[i]) - static_cast<s128>(b.l[i]) - borrow;
|
||||
if (t < 0) {
|
||||
t += static_cast<s128>(static_cast<u64>(1) << 63) * 2; // + 2^64
|
||||
borrow = 1;
|
||||
} else {
|
||||
borrow = 0;
|
||||
}
|
||||
r.l[i] = static_cast<u64>(t);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
auto fe_add(fe const& a, fe const& b) -> fe
|
||||
{
|
||||
fe r{};
|
||||
u64 carry = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
u128 t = static_cast<u128>(a.l[i]) + b.l[i] + carry;
|
||||
r.l[i] = static_cast<u64>(t);
|
||||
carry = static_cast<u64>(t >> 64);
|
||||
}
|
||||
if (carry || fe_ge(r, k_p))
|
||||
r = fe_sub_raw(r, k_p);
|
||||
return r;
|
||||
}
|
||||
|
||||
auto fe_sub(fe const& a, fe const& b) -> fe
|
||||
{
|
||||
if (fe_ge(a, b))
|
||||
return fe_sub_raw(a, b);
|
||||
return fe_sub_raw(k_p, fe_sub_raw(b, a));
|
||||
}
|
||||
|
||||
// Montgomery reduction: r = t * 2^-256 mod p, t given as 9 limbs (t[8] is
|
||||
// the possible 257th carry bit, initially zero).
|
||||
auto fe_redc(u64 const t9[9]) -> fe
|
||||
{
|
||||
u64 t[9];
|
||||
std::memcpy(t, t9, sizeof(t));
|
||||
|
||||
// n0 = -p^-1 mod 2^64 = 1 for P-256 (p mod 2^64 == -1).
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
u64 m = t[i];
|
||||
u64 carry = 0;
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
u128 x = static_cast<u128>(m) * k_p.l[j] + t[i + j] + carry;
|
||||
t[i + j] = static_cast<u64>(x);
|
||||
carry = static_cast<u64>(x >> 64);
|
||||
}
|
||||
int k = i + 4;
|
||||
while (carry && k < 9) {
|
||||
u128 x = static_cast<u128>(t[k]) + carry;
|
||||
t[k] = static_cast<u64>(x);
|
||||
carry = static_cast<u64>(x >> 64);
|
||||
++k;
|
||||
}
|
||||
}
|
||||
|
||||
fe r{{t[4], t[5], t[6], t[7]}};
|
||||
if (t[8] != 0 || fe_ge(r, k_p))
|
||||
r = fe_sub_raw(r, k_p);
|
||||
if (fe_ge(r, k_p)) // safety net, unreachable for well-formed inputs
|
||||
r = fe_sub_raw(r, k_p);
|
||||
return r;
|
||||
}
|
||||
|
||||
auto fe_mul(fe const& a, fe const& b) -> fe
|
||||
{
|
||||
u64 t[9] = {0};
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
u64 carry = 0;
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
u128 x = static_cast<u128>(a.l[i]) * b.l[j] + t[i + j] + carry;
|
||||
t[i + j] = static_cast<u64>(x);
|
||||
carry = static_cast<u64>(x >> 64);
|
||||
}
|
||||
t[i + 4] += carry;
|
||||
}
|
||||
return fe_redc(t);
|
||||
}
|
||||
|
||||
auto fe_sqr(fe const& a) -> fe
|
||||
{
|
||||
return fe_mul(a, a);
|
||||
}
|
||||
|
||||
auto fe_inv(fe const& a) -> fe
|
||||
{
|
||||
// a^(p-2) mod p via square-and-multiply (result stays in Montgomery form).
|
||||
fe result = k_one;
|
||||
fe base = a;
|
||||
u64 e[4] = {0xfffffffffffffffdULL, 0x00000000ffffffffULL, 0x0ULL,
|
||||
0xffffffff00000001ULL};
|
||||
for (int i = 3; i >= 0; --i) {
|
||||
for (int b = 63; b >= 0; --b) {
|
||||
result = fe_sqr(result);
|
||||
if ((e[i] >> b) & 1)
|
||||
result = fe_mul(result, base);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto load_be64(u8 const* p) -> u64
|
||||
{
|
||||
u64 v = 0;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
v = (v << 8) | p[i];
|
||||
return v;
|
||||
}
|
||||
|
||||
auto store_be64(u8* p, u64 v) -> void
|
||||
{
|
||||
for (int i = 7; i >= 0; --i) {
|
||||
p[i] = static_cast<u8>(v);
|
||||
v >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
// Normal form (big-endian bytes) -> Montgomery form.
|
||||
auto fe_from_bytes(std::array<u8, 32> const& b) -> fe
|
||||
{
|
||||
fe n{};
|
||||
for (int i = 0; i < 4; ++i)
|
||||
n.l[i] = load_be64(b.data() + (3 - i) * 8);
|
||||
return fe_mul(n, k_r2);
|
||||
}
|
||||
|
||||
// Montgomery form -> normal form (big-endian bytes).
|
||||
auto fe_from_mont(fe const& a) -> fe
|
||||
{
|
||||
u64 t[9] = {a.l[0], a.l[1], a.l[2], a.l[3], 0, 0, 0, 0, 0};
|
||||
return fe_redc(t);
|
||||
}
|
||||
|
||||
auto fe_to_bytes(fe const& a) -> std::array<u8, 32>
|
||||
{
|
||||
fe n = fe_from_mont(a);
|
||||
std::array<u8, 32> out{};
|
||||
for (int i = 0; i < 4; ++i)
|
||||
store_be64(out.data() + (3 - i) * 8, n.l[i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Jacobian-coordinate point arithmetic on y^2 = x^3 - 3x + b.
|
||||
// --------------------------------------------------------------------------
|
||||
struct jac {
|
||||
fe x, y, z;
|
||||
};
|
||||
|
||||
struct aff {
|
||||
fe x, y;
|
||||
};
|
||||
|
||||
// Point doubling, a = -3 (EFD dbl-2001-b).
|
||||
auto jac_dbl(jac const& p) -> jac
|
||||
{
|
||||
if (fe_is_zero(p.z) || fe_is_zero(p.y))
|
||||
return {k_zero, k_zero, k_zero};
|
||||
|
||||
fe zz = fe_sqr(p.z);
|
||||
fe yy = fe_sqr(p.y);
|
||||
fe beta = fe_mul(p.x, yy);
|
||||
fe alpha = fe_mul(k_three, fe_mul(fe_sub(p.x, zz), fe_add(p.x, zz)));
|
||||
fe x3 = fe_sub(fe_sqr(alpha), fe_mul(k_eight, beta));
|
||||
fe z3 = fe_sub(fe_sub(fe_sqr(fe_add(p.y, p.z)), yy), zz);
|
||||
fe y3 = fe_sub(fe_mul(alpha, fe_sub(fe_mul(k_four, beta), x3)),
|
||||
fe_mul(k_eight, fe_sqr(yy)));
|
||||
return {x3, y3, z3};
|
||||
}
|
||||
|
||||
// Mixed Jacobian + affine addition, a = -3 (EFD madd-2007-bl).
|
||||
auto jac_madd(jac const& p, aff const& q) -> jac
|
||||
{
|
||||
if (fe_is_zero(p.z))
|
||||
return {q.x, q.y, k_one};
|
||||
|
||||
fe zz = fe_sqr(p.z);
|
||||
fe u2 = fe_mul(q.x, zz);
|
||||
fe s2 = fe_mul(q.y, fe_mul(p.z, zz));
|
||||
fe h = fe_sub(u2, p.x);
|
||||
|
||||
if (fe_is_zero(h)) {
|
||||
if (fe_eq(s2, p.y))
|
||||
return jac_dbl(p); // p == q
|
||||
return {k_zero, k_zero, k_zero}; // p == -q
|
||||
}
|
||||
|
||||
fe hh = fe_sqr(h);
|
||||
fe i4 = fe_mul(k_four, hh);
|
||||
fe j = fe_mul(h, i4);
|
||||
fe r = fe_mul(k_two, fe_sub(s2, p.y));
|
||||
fe v = fe_mul(p.x, i4);
|
||||
fe x3 = fe_sub(fe_sub(fe_sqr(r), j), fe_mul(k_two, v));
|
||||
fe y3 = fe_sub(fe_mul(r, fe_sub(v, x3)),
|
||||
fe_mul(fe_mul(k_two, p.y), j));
|
||||
fe z3 = fe_sub(fe_sub(fe_sqr(fe_add(p.z, h)), zz), hh);
|
||||
return {x3, y3, z3};
|
||||
}
|
||||
|
||||
auto jac_to_aff(jac const& p) -> aff
|
||||
{
|
||||
if (fe_is_zero(p.z))
|
||||
return {k_zero, k_zero};
|
||||
fe zinv = fe_inv(p.z);
|
||||
fe zinv2 = fe_sqr(zinv);
|
||||
fe zinv3 = fe_mul(zinv2, zinv);
|
||||
return {fe_mul(p.x, zinv2), fe_mul(p.y, zinv3)};
|
||||
}
|
||||
|
||||
auto jac_scalar_mul(aff const& base, u64 const e[4]) -> jac
|
||||
{
|
||||
jac r{k_zero, k_zero, k_zero};
|
||||
for (int i = 3; i >= 0; --i) {
|
||||
for (int b = 63; b >= 0; --b) {
|
||||
r = jac_dbl(r);
|
||||
if ((e[i] >> b) & 1)
|
||||
r = jac_madd(r, base);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
auto scalar_to_limbs(ec_scalar const& d, u64 e[4]) -> void
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
e[i] = load_be64(d.data() + (3 - i) * 8);
|
||||
}
|
||||
|
||||
auto ec_point_is_valid(ec_point const& p) -> bool
|
||||
{
|
||||
std::array<u8, 32> px{};
|
||||
std::array<u8, 32> py{};
|
||||
std::memcpy(px.data(), p.data(), 32);
|
||||
std::memcpy(py.data(), p.data() + 32, 32);
|
||||
|
||||
fe x = fe_from_bytes(px);
|
||||
fe y = fe_from_bytes(py);
|
||||
|
||||
// y^2 == x^3 - 3x + b
|
||||
fe lhs = fe_sqr(y);
|
||||
fe rhs = fe_add(fe_sub(fe_mul(fe_sqr(x), x), fe_mul(k_three, x)), k_mb);
|
||||
return fe_eq(lhs, rhs);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ==========================================================================
|
||||
// SHA-256
|
||||
// ==========================================================================
|
||||
sha256::sha256()
|
||||
{
|
||||
CC_SHA256_Init(&ctx_);
|
||||
}
|
||||
|
||||
auto sha256::update(std::span<u8 const> data) -> void
|
||||
{
|
||||
CC_SHA256_Update(&ctx_, data.data(), static_cast<CC_LONG>(data.size()));
|
||||
}
|
||||
|
||||
auto sha256::finalize(std::array<u8, k_sha256_len>& out) -> void
|
||||
{
|
||||
CC_SHA256_Final(out.data(), &ctx_);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// HMAC-SHA256
|
||||
// ==========================================================================
|
||||
hmac_sha256::hmac_sha256(std::span<u8 const> key)
|
||||
{
|
||||
u8 block[64] = {};
|
||||
u8 key_block[64] = {};
|
||||
|
||||
if (key.size() > 64) {
|
||||
std::array<u8, 32> digest{};
|
||||
sha256 h;
|
||||
h.update(key);
|
||||
h.finalize(digest);
|
||||
std::memcpy(key_block, digest.data(), digest.size());
|
||||
} else {
|
||||
std::memcpy(key_block, key.data(), key.size());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
block[i] = static_cast<u8>(key_block[i] ^ 0x36);
|
||||
inner_.update({&block[i], 1});
|
||||
}
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
block[i] = static_cast<u8>(key_block[i] ^ 0x5c);
|
||||
outer_.update({&block[i], 1});
|
||||
}
|
||||
}
|
||||
|
||||
auto hmac_sha256::update(std::span<u8 const> data) -> void
|
||||
{
|
||||
inner_.update(data);
|
||||
}
|
||||
|
||||
auto hmac_sha256::finalize(std::array<u8, k_sha256_len>& out) -> void
|
||||
{
|
||||
std::array<u8, k_sha256_len> inner_digest{};
|
||||
inner_.finalize(inner_digest);
|
||||
outer_.update(inner_digest);
|
||||
outer_.finalize(out);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// TLS P_SHA256 PRF
|
||||
// ==========================================================================
|
||||
auto prf_sha256(std::span<u8 const> key, std::string_view label,
|
||||
std::span<u8 const> seed, std::span<u8> out) -> void
|
||||
{
|
||||
std::span<u8 const> label_bytes{reinterpret_cast<u8 const*>(label.data()),
|
||||
label.size()};
|
||||
|
||||
std::array<u8, k_sha256_len> a{};
|
||||
{
|
||||
hmac_sha256 h{key};
|
||||
h.update(label_bytes);
|
||||
h.update(seed);
|
||||
h.finalize(a);
|
||||
}
|
||||
|
||||
std::size_t off = 0;
|
||||
while (off < out.size()) {
|
||||
std::array<u8, k_sha256_len> block{};
|
||||
{
|
||||
hmac_sha256 h{key};
|
||||
h.update(a);
|
||||
h.update(label_bytes);
|
||||
h.update(seed);
|
||||
h.finalize(block);
|
||||
}
|
||||
|
||||
std::size_t n = std::min<std::size_t>(k_sha256_len, out.size() - off);
|
||||
std::memcpy(out.data() + off, block.data(), n);
|
||||
off += n;
|
||||
|
||||
hmac_sha256 h{key};
|
||||
h.update(a);
|
||||
h.finalize(a);
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// RSA (PKCS#1 v1.5)
|
||||
// ==========================================================================
|
||||
auto rsa_encrypt_pkcs1(std::span<u8 const> der_key,
|
||||
std::span<u8 const> plaintext, std::span<u8> out)
|
||||
-> bool
|
||||
{
|
||||
CFDataRef key_data = CFDataCreate(
|
||||
kCFAllocatorDefault, der_key.data(), static_cast<CFIndex>(der_key.size()));
|
||||
if (!key_data)
|
||||
return false;
|
||||
|
||||
void const* attr_keys[] = {kSecAttrKeyType, kSecAttrKeyClass};
|
||||
void const* attr_vals[] = {kSecAttrKeyTypeRSA, kSecAttrKeyClassPublic};
|
||||
CFDictionaryRef attrs = CFDictionaryCreate(
|
||||
kCFAllocatorDefault, attr_keys, attr_vals, 2,
|
||||
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
if (!attrs) {
|
||||
CFRelease(key_data);
|
||||
return false;
|
||||
}
|
||||
|
||||
CFErrorRef error = nullptr;
|
||||
SecKeyRef key = SecKeyCreateWithData(key_data, attrs, &error);
|
||||
CFRelease(attrs);
|
||||
CFRelease(key_data);
|
||||
if (!key) {
|
||||
if (error)
|
||||
CFRelease(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
CFDataRef pt = CFDataCreate(
|
||||
kCFAllocatorDefault, plaintext.data(), static_cast<CFIndex>(plaintext.size()));
|
||||
if (!pt) {
|
||||
CFRelease(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
CFDataRef ct = SecKeyCreateEncryptedData(key, kSecKeyAlgorithmRSAEncryptionPKCS1,
|
||||
pt, &error);
|
||||
CFRelease(pt);
|
||||
if (!ct) {
|
||||
CFRelease(key);
|
||||
if (error)
|
||||
CFRelease(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
CFIndex len = CFDataGetLength(ct);
|
||||
if (len > static_cast<CFIndex>(out.size())) {
|
||||
CFRelease(ct);
|
||||
CFRelease(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
CFDataGetBytes(ct, CFRangeMake(0, len), out.data());
|
||||
CFRelease(ct);
|
||||
CFRelease(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// ECDH (P-256)
|
||||
// ==========================================================================
|
||||
auto ec_base_point_multiply(ec_scalar const& d, ec_point& q) -> void
|
||||
{
|
||||
u64 e[4];
|
||||
scalar_to_limbs(d, e);
|
||||
aff g{k_mgx, k_mgy};
|
||||
aff r = jac_to_aff(jac_scalar_mul(g, e));
|
||||
|
||||
std::array<u8, 32> bx = fe_to_bytes(r.x);
|
||||
std::array<u8, 32> by = fe_to_bytes(r.y);
|
||||
std::memcpy(q.data(), bx.data(), 32);
|
||||
std::memcpy(q.data() + 32, by.data(), 32);
|
||||
}
|
||||
|
||||
auto ec_compute_shared(ec_scalar const& d, ec_point const& peer,
|
||||
ec_scalar& shared) -> bool
|
||||
{
|
||||
if (!ec_point_is_valid(peer))
|
||||
return false;
|
||||
|
||||
u64 e[4];
|
||||
scalar_to_limbs(d, e);
|
||||
|
||||
std::array<u8, 32> px{};
|
||||
std::array<u8, 32> py{};
|
||||
std::memcpy(px.data(), peer.data(), 32);
|
||||
std::memcpy(py.data(), peer.data() + 32, 32);
|
||||
|
||||
aff r = jac_to_aff(jac_scalar_mul({fe_from_bytes(px), fe_from_bytes(py)}, e));
|
||||
if (fe_is_zero(r.y))
|
||||
return false;
|
||||
|
||||
shared = fe_to_bytes(r.x);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto ec_generate_keypair(ec_scalar& d, ec_point& q) -> void
|
||||
{
|
||||
do {
|
||||
random_bytes(d);
|
||||
} while (std::all_of(d.begin(), d.end(), [](u8 b) { return b == 0; }));
|
||||
|
||||
ec_base_point_multiply(d, q);
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Randomness
|
||||
// ==========================================================================
|
||||
auto random_bytes(std::span<u8> out) -> void
|
||||
{
|
||||
arc4random_buf(out.data(), out.size());
|
||||
}
|
||||
|
||||
} // namespace xone::auth
|
||||
|
||||
Reference in New Issue
Block a user