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:
portersky
2026-08-17 15:04:44 +02:00
parent 95e958a7e8
commit 2d4366f454
13 changed files with 3217 additions and 14 deletions
+284
View File
@@ -0,0 +1,284 @@
#include "unity.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#include "auth/crypto.hpp"
void setUp() {}
void tearDown() {}
namespace {
using u8 = std::uint8_t;
auto hex(std::string_view s) -> std::vector<u8>
{
std::vector<u8> out;
for (std::size_t i = 0; i + 1 < s.size(); i += 2) {
char buf[3] = {s[i], s[i + 1], 0};
out.push_back(static_cast<u8>(std::strtoul(buf, nullptr, 16)));
}
return out;
}
template <std::size_t N>
auto to_array(std::vector<u8> const& v) -> std::array<u8, N>
{
std::array<u8, N> a{};
TEST_ASSERT(v.size() == N);
std::copy(v.begin(), v.end(), a.begin());
return a;
}
auto span(std::vector<u8> const& v) -> std::span<u8 const>
{
return {v.data(), v.size()};
}
} // namespace
// --------------------------------------------------------------------------
// SHA-256
// --------------------------------------------------------------------------
void test_sha256_abc(void)
{
std::array<u8, 32> out{};
xone::auth::sha256 h;
std::string data = "abc";
h.update({reinterpret_cast<u8 const*>(data.data()), data.size()});
h.finalize(out);
auto expect = hex("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect.data(), out.data(), 32);
}
void test_sha256_snapshot(void)
{
// snapshot must not disturb continued hashing
std::array<u8, 32> out{};
xone::auth::sha256 h;
std::string a = "abc";
h.update({reinterpret_cast<u8 const*>(a.data()), a.size()});
xone::auth::sha256 snap = h; // snapshot
std::string b = "def";
h.update({reinterpret_cast<u8 const*>(b.data()), b.size()});
h.finalize(out);
auto expect = hex("bef57ec7f53a6d40beb640a780a639c83bc29ac8a9816f1fc6c5c6dcd93c4721");
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect.data(), out.data(), 32);
// snapshot still hashes only "abc"
std::array<u8, 32> snap_out{};
snap.finalize(snap_out);
auto expect_snap = hex("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect_snap.data(), snap_out.data(), 32);
}
// --------------------------------------------------------------------------
// HMAC-SHA256 (RFC 4231)
// --------------------------------------------------------------------------
void test_hmac_rfc4231_case2(void)
{
// key = "Jefe", data = "what do ya want for nothing?"
std::string key = "Jefe";
std::string data = "what do ya want for nothing?";
xone::auth::hmac_sha256 h{{reinterpret_cast<u8 const*>(key.data()), key.size()}};
h.update({reinterpret_cast<u8 const*>(data.data()), data.size()});
std::array<u8, 32> out{};
h.finalize(out);
auto expect = hex("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843");
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect.data(), out.data(), 32);
}
void test_hmac_rfc4231_case1(void)
{
// key = 0x0b * 20, data = "Hi There"
std::vector<u8> key(20, 0x0b);
std::string data = "Hi There";
xone::auth::hmac_sha256 h{span(key)};
h.update({reinterpret_cast<u8 const*>(data.data()), data.size()});
std::array<u8, 32> out{};
h.finalize(out);
auto expect = hex("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect.data(), out.data(), 32);
}
// --------------------------------------------------------------------------
// PRF (TLS P_SHA256)
// --------------------------------------------------------------------------
void test_prf_expansion(void)
{
// Long output forces multiple blocks; verify against Python-computed value.
std::vector<u8> key = hex("00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff");
std::vector<u8> seed = hex("deadbeefcafebabe");
std::array<u8, 48> out{};
xone::auth::prf_sha256(span(key), "Master Secret", span(seed), out);
auto expect = hex(
"78b92e5b14f8b98dc9f5ddd33668d3dd19e74467f38992009d71bdfd69b5dfa2"
"8ff26eaad240df9fe705798d6af784d8");
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect.data(), out.data(), 48);
}
// --------------------------------------------------------------------------
// ECDH P-256
// --------------------------------------------------------------------------
void test_ec_small_multiples(void)
{
auto two = hex("0000000000000000000000000000000000000000000000000000000000000002");
auto three = hex("0000000000000000000000000000000000000000000000000000000000000003");
xone::auth::ec_point q{};
xone::auth::ec_base_point_multiply(to_array<32>(two), q);
auto expect2g = hex(
"7cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978"
"07775510db8ed040293d9ac69f7430dbba7dade63ce982299e04b79d227873d1");
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect2g.data(), q.data(), 64);
xone::auth::ec_base_point_multiply(to_array<32>(three), q);
auto expect3g = hex(
"5ecbe4d1a6330a44c8f7ef951d4bf165e6c6b721efada985fb41661bc6e7fd6c"
"8734640c4998ff7e374b06ce1a64a2ecd82ab036384fb83d9a79b127a27d5032");
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect3g.data(), q.data(), 64);
}
void test_ec_base_multiply(void)
{
// vectors generated with OpenSSL (see test comments in commit)
auto d = hex("ca9aca63d14014baf4e37bcaeb90317557728b479ca42e126fcb9ae9a4f7f765");
auto puba = hex(
"dca2dafb3baa46602e7512de4690ede383e390148b3c041b34e1beae2b9ec997"
"abf83c388bb7b53cd086a8d20810b22ac1f5d92484abf761f53b6725988604cd");
xone::auth::ec_point q{};
xone::auth::ec_base_point_multiply(to_array<32>(d), q);
TEST_ASSERT_EQUAL_UINT8_ARRAY(puba.data(), q.data(), 64);
}
void test_ec_shared_secret(void)
{
auto d = hex("ca9aca63d14014baf4e37bcaeb90317557728b479ca42e126fcb9ae9a4f7f765");
auto pubb = hex(
"5baeca87ad9b623f7d0cd33af316e5f57d17b711e821acc449a12e5427c95aa3"
"a0b86826cf4f028cb90bfd284e99db36cc19c640a914b97ef35dd965e2af19da");
auto secret = hex("df01a8c296d70b09a91bc74f4bd25d877cffa0fe43287d24939e44cdc2e9ec25");
xone::auth::ec_scalar shared{};
TEST_ASSERT_TRUE(
xone::auth::ec_compute_shared(to_array<32>(d), to_array<64>(pubb), shared));
TEST_ASSERT_EQUAL_UINT8_ARRAY(secret.data(), shared.data(), 32);
}
void test_ec_rejects_off_curve_point(void)
{
auto d = hex("ca9aca63d14014baf4e37bcaeb90317557728b479ca42e126fcb9ae9a4f7f765");
// point with x=1, y=1 is not on the curve
xone::auth::ec_point bad{};
bad[31] = 1;
bad[63] = 1;
xone::auth::ec_scalar shared{};
TEST_ASSERT_FALSE(
xone::auth::ec_compute_shared(to_array<32>(d), bad, shared));
}
// --------------------------------------------------------------------------
// RSA (PKCS#1 v1.5) round-trip against Security.framework
// --------------------------------------------------------------------------
void test_rsa_roundtrip(void)
{
CFErrorRef error = nullptr;
CFTypeRef attr_keys[] = {kSecAttrKeyType, kSecAttrKeySizeInBits,
kSecAttrIsPermanent};
CFNumberRef size = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType,
(int[]){2048});
CFTypeRef attr_vals[] = {kSecAttrKeyTypeRSA, size, kCFBooleanFalse};
CFDictionaryRef attrs = CFDictionaryCreate(
kCFAllocatorDefault, attr_keys, attr_vals, 3,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
TEST_ASSERT_NOT_NULL(attrs);
SecKeyRef priv = SecKeyCreateRandomKey(attrs, &error);
CFRelease(size);
TEST_ASSERT_NOT_NULL(priv);
if (!priv) {
CFRelease(attrs);
return;
}
SecKeyRef pub = SecKeyCopyPublicKey(priv);
TEST_ASSERT_NOT_NULL(pub);
if (!pub) {
CFRelease(priv);
CFRelease(attrs);
return;
}
CFDataRef ext = SecKeyCopyExternalRepresentation(pub, &error);
TEST_ASSERT_NOT_NULL(ext);
if (!ext) {
CFRelease(pub);
CFRelease(priv);
CFRelease(attrs);
return;
}
CFIndex key_len = CFDataGetLength(ext);
std::vector<u8> der(key_len);
CFDataGetBytes(ext, CFRangeMake(0, key_len), der.data());
CFRelease(ext);
// 2048-bit RSA PKCS#1 RSAPublicKey: 4-byte ASN.1 header + 266-byte body.
TEST_ASSERT_EQUAL_INT(270, key_len);
TEST_ASSERT_EQUAL_UINT8(0x30, der[0]);
TEST_ASSERT_EQUAL_UINT8(0x82, der[1]);
std::vector<u8> plaintext(48, 0x5a);
std::vector<u8> ciphertext(256);
TEST_ASSERT_TRUE(xone::auth::rsa_encrypt_pkcs1(span(der), span(plaintext),
ciphertext));
// Decrypt to prove the round-trip.
CFDataRef ct = CFDataCreate(kCFAllocatorDefault, ciphertext.data(),
static_cast<CFIndex>(ciphertext.size()));
CFDataRef dec = SecKeyCreateDecryptedData(
priv, kSecKeyAlgorithmRSAEncryptionPKCS1, ct, &error);
CFRelease(ct);
TEST_ASSERT_NOT_NULL(dec);
if (dec) {
CFIndex dec_len = CFDataGetLength(dec);
TEST_ASSERT_EQUAL_INT(48, dec_len);
std::vector<u8> dec_bytes(dec_len);
CFDataGetBytes(dec, CFRangeMake(0, dec_len), dec_bytes.data());
TEST_ASSERT_EQUAL_UINT8_ARRAY(plaintext.data(), dec_bytes.data(), 48);
CFRelease(dec);
}
CFRelease(priv);
CFRelease(pub);
CFRelease(attrs);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_sha256_abc);
RUN_TEST(test_sha256_snapshot);
RUN_TEST(test_hmac_rfc4231_case2);
RUN_TEST(test_hmac_rfc4231_case1);
RUN_TEST(test_prf_expansion);
RUN_TEST(test_ec_small_multiples);
RUN_TEST(test_ec_base_multiply);
RUN_TEST(test_ec_shared_secret);
RUN_TEST(test_ec_rejects_off_curve_point);
RUN_TEST(test_rsa_roundtrip);
return UNITY_END();
}