diff --git a/license/CMakeLists.txt b/license/CMakeLists.txt index aa1fb3a..c2145fd 100644 --- a/license/CMakeLists.txt +++ b/license/CMakeLists.txt @@ -85,6 +85,8 @@ target_link_libraries(TeaLicenseClient ${LIBRARY_TOM_MATH} ${LIBRARY_TOM_CRYPT} stdc++fs.a + ${LIBRARY_PATH_TERMINAL} + ${LIBRARY_PATH_ED255} ${LIBRARY_PATH_BORINGSSL_SSL} ${LIBRARY_PATH_BORINGSSL_CRYPTO} diff --git a/license/LicenseClientMain.cpp b/license/LicenseClientMain.cpp index de3a373..28d3dd6 100644 --- a/license/LicenseClientMain.cpp +++ b/license/LicenseClientMain.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include using namespace std; using namespace std::chrono; @@ -24,7 +26,33 @@ using namespace license; int main(int ac, char** av){ auto state = evthread_use_pthreads(); assert(state == 0); + string error; + + std::array private_key, public_key; + + std::random_device rd; + std::uniform_int_distribution d; + + uint8_t root_seed[64]; + for(auto& e : root_seed) + e = d(rd); + ed25519_create_keypair(public_key.data(), private_key.data(), root_seed); + + cout << "Key Pair generated:" << endl; + cout << "Private Key:" << hex; + for(auto& e : private_key) + cout << " 0x" << (e <= 0xF ? "0" : "") << (uint32_t) e << ","; + cout << endl; + + cout << "Public Key :" << hex; + for(auto& e : public_key) + cout << " 0x" << (e <= 0xF ? "0" : "") << (uint32_t) e << ","; + cout << endl; + + return true; + +#if 0 srand(system_clock::now().time_since_epoch().count()); cout << "Generating new license" << endl; @@ -83,5 +111,6 @@ int main(int ac, char** av){ cerr << "Could not load info after throwing: " << endl << ex.what() << endl; } } +#endif return 0; } \ No newline at end of file diff --git a/license/packets/LicenseRequest.proto b/license/packets/LicenseRequest.proto index 25a3586..16ec0cd 100644 --- a/license/packets/LicenseRequest.proto +++ b/license/packets/LicenseRequest.proto @@ -13,7 +13,6 @@ message Blacklist { optional string reason = 2; } - message LicenseInfo { required bytes key = 1; required string username = 2; diff --git a/license/shared/License.cpp b/license/shared/License.cpp index 91cba7a..9419371 100644 --- a/license/shared/License.cpp +++ b/license/shared/License.cpp @@ -1,10 +1,14 @@ #include #include +#include +#include //#define NO_OPEN_SSL #include #include #include +#include +#include #include "crypt.h" #include "License.h" @@ -99,4 +103,717 @@ namespace license { this->header.packetId = packetId; this->data = ""; } +} + +std::array license::v2::public_root_key = { + 0x84, 0x54, 0x2c, 0x2b, 0x46, 0x19, 0x05, 0x6c, + 0x01, 0xd8, 0x61, 0x49, 0x4e, 0x48, 0x47, 0x1e, + 0x6c, 0x61, 0xfa, 0x6a, 0xde, 0x6b, 0x1c, 0x76, + 0x3a, 0xeb, 0x2f, 0x39, 0x49, 0x3d, 0x71, 0x35 +}; + +namespace license::v2 { + License::~License() { + if(this->private_buffer) + ::free(this->private_buffer); + } + + std::shared_ptr License::create(const std::vector> &hierarchy, const std::array &prv_key) { + assert(!hierarchy.empty()); + auto result = shared_ptr(new License{}); + + result->_version = 2; + result->crypt_seed = std::mt19937_64{std::random_device{}()}(); + result->_hierarchy = hierarchy; + result->private_data = LicensePrivate::create(result, hierarchy.size() - 1, prv_key.data()); + + return result; + } + + std::shared_ptr License::read(const uint8_t *buffer, size_t length, uint8_t &error) { + auto result = shared_ptr(new License{}); + + LicenseHeader header{}; + BodyHeader body_header{}; + if(length < sizeof(header) + sizeof(body_header)) { + error = 2; /* buffer too small */ + return nullptr; + } + memcpy(&header, buffer, sizeof(header)); + + if(header.version != 2) { + error = 3; /* invalid version */ + return nullptr; + } + result->_version = header.version; + + std::mt19937_64 crypt_key_gen{header.crypt_key_seed}; + + /* verify the crypt key gen */ + { + crypt_key_gen.discard(header.crypt_key_verify_offset); + uint64_t expected = 0; + memcpy(&expected, header.crypt_key_verify, 5); + + uint64_t received = crypt_key_gen(); + received = received ^ (received >> 40UL); + received &= 0xFFFFFFFFFFULL; + + if(expected != received) { + error = 4; /* invalid key sequence */ + return nullptr; + } + } + + crypt_key_gen.seed(header.crypt_key_seed); + auto decoded_buffer_length = length - sizeof(header); + auto decoded_buffer = unique_ptr{(uint8_t*) malloc(decoded_buffer_length), ::free}; + if(!decoded_buffer) { + error = 1; /* out of memory */ + return nullptr; + } + + /* "decode" the data */ + { + auto index = 0; + while(index + 4 < decoded_buffer_length) { + auto& memory = *(uint32_t*) (&*decoded_buffer + index); + memory = *(uint32_t*) (buffer + index); + memory ^= (uint32_t) crypt_key_gen(); + index += 4; + } + while(index < decoded_buffer_length) { + auto& memory = *(uint8_t*) (&*decoded_buffer + index); + memory = *(uint8_t*) (buffer + index); + memory ^= (uint8_t) crypt_key_gen(); + index++; + } + } + memcpy(&body_header, &*decoded_buffer, sizeof(body_header)); + if(decoded_buffer_length < sizeof(body_header) + body_header.length_hierarchy + body_header.length_private_data) { + error = 2; /* buffer too small */ + return nullptr; + } + + auto hierarchy_buffer = &*decoded_buffer + sizeof(body_header) + body_header.length_private_data; + /* test the checksum for the hierarchy (license data indirectly verified via data_sign) */ + { + uint8_t sha_buffer[20]; + digest::sha1((char*) hierarchy_buffer, body_header.length_hierarchy, sha_buffer); + if(memcmp(sha_buffer, body_header.checksum_hierarchy, 20) != 0) { + error = 5; /* checksum does not match */ + return nullptr; + } + } + + /* now lets read the hierarchy data */ + { + size_t offset = 0, length = body_header.length_hierarchy; + while(offset < length) { + auto entry = HierarchyEntry::read(hierarchy_buffer, offset, length); + if(!entry) { + error = 6; /* failed to read an entry */ + return nullptr; + } + + result->_hierarchy.push_back(entry); + } + } + + /* verify the given data */ + auto public_key = result->generate_public_key(public_root_key.data()); + if(!ed25519_verify(body_header.private_data_sign, &*decoded_buffer + sizeof(body_header), body_header.length_private_data, public_key.data())) { + error = 7; /* failed to verify private data */ + return nullptr; + } + + memcpy(result->private_buffer_sign.data(), body_header.private_data_sign, 64); + + /* copy the private data */ + result->private_buffer = (uint8_t*) malloc(body_header.length_private_data); + result->private_buffer_length = body_header.length_private_data; + memcpy(result->private_buffer, &*decoded_buffer + sizeof(body_header), body_header.length_private_data); + + /* let parse the private data */ + result->private_data = LicensePrivate::read(result, result->_version, result->private_buffer, result->private_buffer_length, error); + if(!result->private_data) { + error = 7; /* failed to parse private data */ + return nullptr; + } + return result; + } + + std::string License::write(int &error) { + if(!this->private_data || !this->private_buffer_length) { + error = 2; /* missing private data */ + return ""; + } + + /* lets estimate a buffer size */ + auto buffer_size = sizeof(LicenseHeader) + sizeof(BodyHeader) + this->private_buffer_length; + + for(auto& he : this->_hierarchy) + if(!he->write(nullptr, buffer_size, 0)) { + error = 3; /* failed to estimate buffer size */ + return ""; + } + + auto buffer = unique_ptr{(uint8_t*) malloc(buffer_size), ::free}; + LicenseHeader license_header{}; + BodyHeader body_header{}; + + /* first copy the private data */ + { + memcpy(body_header.private_data_sign, this->private_buffer_sign.data(), this->private_buffer_sign.size()); + memcpy(&*buffer + sizeof(license_header) + sizeof(body_header), this->private_buffer, this->private_buffer_length); + body_header.length_private_data = this->private_buffer_length; + } + + /* lets write the hierarchy */ + { + auto offset = sizeof(license_header) + sizeof(body_header) + this->private_buffer_length; + const auto begin_offset = offset; + for(auto& he : this->_hierarchy) + if(!he->write(&*buffer, offset, buffer_size)) { + error = 3; /* failed to write hierarchy */ + return ""; + } + body_header.length_hierarchy = offset - begin_offset; + + digest::sha1((char*) &*buffer + begin_offset, body_header.length_hierarchy, body_header.checksum_hierarchy); + } + + /* write the body header */ + memcpy(&*buffer + sizeof(license_header), &body_header, sizeof(body_header)); + + /* lets generate the license header */ + { + std::mt19937_64 rnd{std::random_device{}()}; + + license_header.version = 2; + license_header.crypt_key_seed = rnd(); + license_header.crypt_key_verify_offset = std::uniform_int_distribution{}(rnd); + + { + rnd.seed(license_header.crypt_key_seed); + rnd.discard(license_header.crypt_key_verify_offset); + + uint64_t expected = rnd(); + expected = expected ^ (expected >> 40UL); + expected &= 0xFFFFFFFFFFULL; + + memcpy(license_header.crypt_key_verify, &expected, 5); + } + } + + /* now lets "encrypt" the body */ + { + std::mt19937_64 crypt_key_gen{license_header.crypt_key_seed}; + + auto index = sizeof(license_header); + while(index + 4 < buffer_size) { + auto& memory = *(uint32_t*) (&*buffer + index); + memory = *(uint32_t*) (&*buffer + index); + memory ^= (uint32_t) crypt_key_gen(); + index += 4; + } + while(index < buffer_size) { + auto& memory = *(uint8_t*) (&*buffer + index); + memory = *(uint8_t*) (&*buffer + index); + memory ^= (uint8_t) crypt_key_gen(); + index++; + } + } + + /* write the license header */ + memcpy(&*buffer, &license_header, sizeof(license_header)); + return std::string((char*) &*buffer, buffer_size); + } + + bool License::private_data_editable() const { + return this->private_data->private_key_calculable(this->_hierarchy.size() - 1); + } + + bool License::write_private_data(const LicensePrivateWriteOptions& write_options) { + uint8_t private_key[64]; //ed25519_sign requires 64 bytes (may it expects a public key in front?) + if(!this->private_data->calculate_private_key(private_key, this->_hierarchy.size() - 1)) + return false; + memcpy(private_key + 32, private_key, 32); + + auto public_key = this->generate_public_key(public_root_key.data()); + + size_t length = 0, offset = 0; + if(!this->private_data->write(nullptr, length, 65536, write_options)) + return false; + + if(this->private_buffer) + ::free(this->private_buffer); + this->private_buffer_length = length; + this->private_buffer = (uint8_t*) malloc(length); + if(!this->private_buffer) return false; + + if(!this->private_data->write(this->private_buffer, offset, length, write_options)) + return false; + + ed25519_sign(this->private_buffer_sign.data(), this->private_buffer, length, public_key.data(), private_key); + return true; + } + + std::array License::generate_public_key(const uint8_t* root_key, int length) const { + uint8_t hash_buffer[64]; + + ge_p3 parent_key{}; + ge_cached parent_cached{}; + + /* import the main parent key */ + ge_frombytes_negate_vartime(&parent_key, root_key); + + /* undo the negate */ + fe_neg(parent_key.X, parent_key.X); + fe_neg(parent_key.T, parent_key.T); + + for(const auto& entry : this->_hierarchy) { + if(length-- == 0) continue; + + ge_p3 e_pub_key{}; + ge_frombytes_negate_vartime(&e_pub_key, entry->public_key().data()); + + ge_p3_to_cached(&parent_cached, &parent_key); + + /* malloc could fail, but we ignore this for now */ + if(!entry->hash(hash_buffer)) /* */; + + /* import hash (convert to a valid coordinate) */ + memset(hash_buffer + 32, 0, 32); /* yes, we have to drop half of the SHA512 hash :( */ + hash_buffer[0] &= 0xF8U; + hash_buffer[31] &= 0x3FU; + hash_buffer[31] |= 0x40U; + sc_reduce(hash_buffer); + + /* import the clamp data */ + ge_p3 p3_clamp_mul_pKey{}; + ge_p2 p2_clamp_mul_pKey{}; + ge_scalarmult_vartime(&p2_clamp_mul_pKey, hash_buffer, &e_pub_key); + ge_p2_to_p3(&p3_clamp_mul_pKey, &p2_clamp_mul_pKey); + + /* add parent with the clamp data */ + ge_p1p1 a{}; + ge_add(&a, &p3_clamp_mul_pKey, &parent_cached); + + /* convert stuff back */ + ge_p3 r2{}; + ge_p1p1_to_p3(&r2, &a); + + parent_key = r2; + } + + std::array result{}; + ge_p3_tobytes((uint8_t*) result.data(), &parent_key); + return {}; + } + + bool License::push_entry(const std::shared_ptr &entry, size_t* index) { + assert(this->private_data); + + auto idx = this->_hierarchy.size(); + if(idx > 0 && !this->private_data->private_key_calculable(idx - 1)) + return false; + + if(index) + *index = idx; + this->_hierarchy.push_back(entry); + return true; + } + + typedef duration> days; + typedef duration> years; + + bool License::hierarchy_timestamps_valid() { + system_clock::time_point time_begin{}; + system_clock::time_point time_end = system_clock::time_point{} + years{5000}; + + for(const auto& entry : this->_hierarchy) { + auto end = entry->end_timestamp(); + auto begin = entry->begin_timestamp(); + if(begin < time_begin) + return false; + if(end > time_end) + return false; + + time_begin = begin; + if(end.time_since_epoch().count() != 0) time_end = end; + } + return true; + } + + void License::_register_raw_private_key(size_t index, uint8_t *data) { + assert(this->private_data); + this->private_data->register_raw_private_key(index, data); + } + + bool License::generate_keypair(uint8_t *prv, uint8_t *pbl) { + std::random_device rd; + std::uniform_int_distribution d; + + uint8_t root_seed[64]; + for(auto& e : root_seed) + e = d(rd); + + ed25519_create_keypair(pbl, prv, root_seed); + return true; + } + + std::shared_ptr LicensePrivate::create(const std::shared_ptr &handle, int key_index, const uint8_t *key) { + auto result = shared_ptr(new LicensePrivate{}); + result->_handle = handle; + result->precalculated_private_key_index = key_index; + if(key) { + memcpy(result->precalculated_private_key.data(), key, result->precalculated_private_key.size()); + } else { + assert(key_index < -1); + } + return result; + } + + std::shared_ptr LicensePrivate::read(const std::shared_ptr& handle, uint8_t version, const uint8_t *buffer, size_t length, uint8_t& error) { + if(version != 2) { + error = 2; /* invalid version */ + return nullptr; + } + + auto result = LicensePrivate::create(handle, -2, nullptr); + + size_t offset = 0; + if((offset + 1) > length) + return nullptr; + + /* read the precalculated private key */ + if(*(buffer + offset++)) { + if((offset + 1 + result->precalculated_private_key.size()) > length) + return nullptr; + + result->precalculated_private_key_index = *(buffer + offset++); + memcpy(result->precalculated_private_key.data(), buffer + offset, result->precalculated_private_key.size()); + offset += result->precalculated_private_key.size(); + } + + /* read raw private keys */ + { + if((offset + 1) > length) + return nullptr; + + auto private_key_count = *(buffer + offset++); + + if((offset + private_key_count * 33) > length) + return nullptr; + while(private_key_count-- > 0) { + auto index = *(buffer + offset++); + result->register_raw_private_key(index, buffer + offset); + offset += 32; + } + } + + /* read the metadata */ + { + if((offset + 4) > length) + return nullptr; + + auto meta_data_length = *(uint32_t*) (buffer + offset); + offset += 4; + + while(meta_data_length-- > 0) { + if((offset + 3) > length) + return nullptr; + auto key_length = *(buffer + offset++); + auto value_length = *(uint16_t*) (buffer + offset); + offset += 2; + + if((offset + key_length + value_length) > length) + return nullptr; + + result->meta_data[{(char*) buffer + offset, key_length}] = {(char*) buffer + offset + key_length, value_length}; + offset += key_length; + offset += value_length; + } + } + return result; + } + + bool LicensePrivate::write(uint8_t *buffer, size_t &offset, size_t length, const LicensePrivateWriteOptions& options) { + if(options.precalculated_key_index < -1) { + if(buffer) { + if((offset + 2) < length) return false; + *(buffer + offset++) = 0; /* no precalculated private key */ + *(buffer + offset++) = 0; /* no raw private keys */ + } else { + offset += 2; + } + } else { + auto index = options.precalculated_key_index == -1 ? this->precalculated_private_key_index : options.precalculated_key_index; + if(index < 0) return false; /* we will NEVER write the root key */ + + if(buffer) { + if((offset + 2 + 32) < length) return false; + *(buffer + offset++) = 1; + { + *(buffer + offset++) = index; + + if(!this->calculate_private_key(buffer + offset, index)) + return false; + } + + if((offset + 1) < length) return false; + auto& private_key_count = *(buffer + offset++); + private_key_count = 0; + for(auto& [key_index, key] : this->private_keys) { + if(key_index <= index) + continue; + + if((offset + 1 + key.size()) < length) return false; + *(buffer + offset++) = key_index; + memcpy(buffer + offset, key.data(), key.size()); + + private_key_count++; + offset += key.size(); + } + } else { + /* private precalc key */ + offset += 2 + 32; + + /* raw keys */ + offset += 1; + for(auto& [key_index, key] : this->private_keys) { + if(key_index <= index) + continue; + + offset += 1 + key.size(); + } + } + } + + if(buffer) { + if((offset + 4) < length) return false; + *(uint32_t*) (buffer + offset) = this->meta_data.size(); + offset += 4; + + for(auto& [key, value] : this->meta_data) { + if((offset + 3 + key.length() + value.length()) < length) return false; + + *(buffer + offset++) = key.length(); + *(uint16_t*)(buffer + offset) = value.length(); + offset += 2; + + memcpy(buffer + offset, key.data(), key.length()); + offset += key.length(); + + memcpy(buffer + offset, value.data(), value.length()); + offset += value.length(); + } + } else { + offset += 4; + for(auto& [key, value] : this->meta_data) + offset += 3 + key.length() + value.length(); + } + + return true; + } + + bool LicensePrivate::private_key_chain_valid() { + auto handle = this->_handle.lock(); + if(!handle) return false; + + auto hierarchy = handle->hierarchy(); + + auto base_index = this->precalculated_private_key_index; + if(base_index >= hierarchy.size()) return false; + if(base_index < -1) return true; /* means we don't have a private key */ + + while(base_index < hierarchy.size()) { + if(!this->has_raw_private_key(base_index++)) + return false; + } + return true; + } + + bool LicensePrivate::private_key_calculable(uint8_t index) const { + auto handle = this->_handle.lock(); + if(!handle) return false; + + auto hierarchy = handle->hierarchy(); + if(index >= hierarchy.size()) return false; + + + auto base_index = this->precalculated_private_key_index; + if(base_index > index) return false; + if(base_index < -1) return false; + + while(base_index < index) { + base_index++; + if(this->private_keys.count(base_index) < 1) + return false; /* we're missing a private key here, how is this even possible? */ + } + + return true; + } + + bool LicensePrivate::calculate_private_key(uint8_t *buffer, uint8_t index) const { + auto handle = this->_handle.lock(); + if(!handle) return false; + + auto hierarchy = handle->hierarchy(); + if(index >= hierarchy.size()) return false; + + auto base_index = this->precalculated_private_key_index; + if(base_index > index) return false; + if(base_index < -1) return false; + + uint8_t hash_buffer[64]; + memcpy(buffer, this->precalculated_private_key.data(), this->precalculated_private_key.size()); + while(base_index < index) { + base_index++; + if(this->private_keys.count(base_index) < 1) + return false; /* we're missing a private key here, how is this even possible? */ + + if(!hierarchy[index]->hash(hash_buffer)) return false; + + /* import hash (convert to a valid coordinate) */ + memset(hash_buffer + 32, 0, 32); /* yes, we have to drop half of the SHA512 hash :( */ + hash_buffer[0] &= 0xF8U; + hash_buffer[31] &= 0x3FU; + hash_buffer[31] |= 0x40U; + sc_reduce(hash_buffer); + + sc_muladd(buffer, this->private_keys.at(base_index).data(), hash_buffer, buffer); + } + + return true; + } + + void LicensePrivate::register_raw_private_key(uint8_t index, const uint8_t *buffer) { + auto& target = this->private_keys[index]; + memcpy(target.data(), buffer, target.size()); + } + + bool LicensePrivate::has_raw_private_key(uint8_t index) const { + return this->private_keys.count(index) > 0; + } + + HierarchyEntry::~HierarchyEntry() { + this->allocate_read_body(0); + } + + std::shared_ptr HierarchyEntry::read(const uint8_t *buffer, size_t &offset, size_t length) { + auto result = shared_ptr(new HierarchyEntry{}); + if((offset + 43) > length) return nullptr; + + result->_entry_type = *(buffer + offset); + offset++; + + memcpy(result->_public_key.data(), buffer + offset, 32); + offset += 32; + + memcpy(&result->_timestamp_begin, buffer + offset, 4); + offset += 4; + + memcpy(&result->_timestamp_end, buffer + offset, 4); + offset += 4; + + uint16_t body_length; + memcpy(&body_length, buffer + offset, 2); + offset += 2; + + if(body_length > length) return nullptr; + if(!result->allocate_read_body(body_length)) return nullptr; + + if(body_length > 0) { + memcpy(result->read_body, buffer + offset, body_length); + offset += body_length; + } + + result->_hash_set = false; + return result; + } + + bool HierarchyEntry::write(uint8_t *buffer, size_t &offset, size_t length) const { + if(buffer && (offset + 43 + this->read_body_length) > length) return false; + + if(buffer) *(buffer + offset) = this->_entry_type; + offset++; + + if(buffer) memcpy(buffer + offset, this->_public_key.data(), 32); + offset += 32; + + if(buffer) memcpy(buffer + offset, &this->_timestamp_begin, 4); + offset += 4; + + if(buffer) memcpy(buffer + offset, &this->_timestamp_end, 4); + offset += 4; + + if(buffer) memcpy(buffer + offset, &this->read_body_length, 2); + offset += 2; + + if(this->read_body_length > 0) { + if(buffer) memcpy(buffer + offset, this->read_body, this->read_body_length); + offset += this->read_body_length; + } + + return true; + } + + bool HierarchyEntry::allocate_read_body(size_t size) { + if(this->read_body) { + ::free(this->read_body); + this->read_body = nullptr; + } + + if(size > 0) { + this->read_body = (uint8_t*) malloc(size); + if(!this->read_body) return false; + } + return true; + } + + bool HierarchyEntry::hash(uint8_t *target_buffer) const { + if(this->_hash_set) { + memcpy(target_buffer, this->_hash.data(), this->_hash.size()); + return true; + } + + size_t length = 42 + this->read_body_length, offset = 0; + auto buffer = (uint8_t*) malloc(length); + if(!buffer) return false; + if(this->write(buffer, offset, length)) { + digest::sha512((char*) buffer, length, this->_hash.data()); + this->_hash_set = true; + } + + return this->_hash_set ? this->hash(target_buffer) : false; + } + + namespace hierarchy { + std::string_view Intermediate::description() { + if(this->_length == 0) + return {}; + + return std::string_view{(const char*) this->_memory + 1, (size_t) *this->_memory}; + } + + std::shared_ptr Intermediate::create(const uint8_t *pub_key, const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point & end, const std::string &description) { + assert(description.size() < 256); + auto result = shared_ptr(new HierarchyEntry{hierarchy::Intermediate::type, pub_key, begin, end}); + + if(!result || !result->allocate_read_body(description.size() + 1)) return nullptr; + memcpy(result->read_body + 1, description.data(), description.length()); + *result->read_body = (uint8_t) description.length(); + + return result; + } + } + + static int test() { + uint8_t errc; + auto license = License::read(nullptr, 0, errc); + license->push_entry(system_clock::now(), system_clock::now(), "test!"); + return 0; + } } \ No newline at end of file diff --git a/license/shared/License.h b/license/shared/License.h index 6bbb482..1532d1b 100644 --- a/license/shared/License.h +++ b/license/shared/License.h @@ -32,46 +32,208 @@ namespace license { const char cryptKey[64]; //The dummy key for data de/encryption } __attribute__ ((__packed__)); - /* namespace v2 { - namespace data { - struct ChainHead { - uint32_t chain_version; - uint32_t chain_magic; - uint8_t sign[32]; - } __attribute__ ((__packed__)); + struct LicenseHeader { + uint16_t version; /* first 16 bytes const version */ + uint64_t crypt_key_seed; /* the seed */ + uint8_t crypt_key_verify_offset; /* the first 8 bits determine how much generations (n * 3) and the rest what value is expeted. Due to the loss of 8 bits does the highest 8 bits get xored with the lowerst and 56 bits get compared */ + uint8_t crypt_key_verify[5]; + } __attribute__ ((__packed__)); + static_assert(sizeof(LicenseHeader) == 16); - struct ChainEntryHead { - uint8_t entry_type; //sign bit = contains private - int64_t entry_begin; - int32_t entry_length; - uint8_t key[32]; - } __attribute__ ((__packed__)); + struct BodyHeader { + uint16_t length_private_data; + uint16_t length_hierarchy; /* contains all public data */ + + uint8_t checksum_hierarchy[20]; /* SHA1 */ + uint8_t private_data_sign[64]; /* ed sign from the hierarchy created public key */ + } __attribute__ ((__packed__)); + + extern std::array public_root_key; + + struct HierarchyEntry; + struct LicensePrivate; + struct LicensePrivateWriteOptions; + struct License { + public: + static std::shared_ptr read(const uint8_t* /* buffer */, size_t /* max size */, uint8_t& /* error */); + static std::shared_ptr create(const std::vector>& hierarchy, const std::array& /* precalculated last entry */); + /* Note for the write method: Make the private version index configurable, so we could "export" the license */ + /* Note for the write method: Write "private_buffer" if we're not able to resign the private data. As well enfore (assert) that we have a private buffer (e.g. by the read method) */ + + ~License(); + const std::vector> hierarchy() const { return this->_hierarchy; } + bool push_entry(const std::shared_ptr& /* entry */, size_t* /* index */ = nullptr); + + bool hierarchy_timestamps_valid(); + + template + bool push_entry(Args&&... args) { + std::array key_private{}, key_public{}; + if(!this->generate_keypair(key_private.data(), key_public.data())) return false; + + auto entry = I::create(key_public.data(), std::forward(args)...); + if(!entry) return false; + + size_t index; + if(!this->push_entry(entry, &index)) return false; + + this->_register_raw_private_key(index, key_private.data()); + return true; + } + + std::array generate_public_key(const uint8_t* /* public key root */, int /* length */ = -1) const; + + std::string write(int& /* error */); + + bool private_data_editable() const; + bool write_private_data(const LicensePrivateWriteOptions& /* write options */); + + inline uint8_t version() const { return this->_version; } + private: + License() = default; + + uint8_t _version = 0; + + std::array private_buffer_sign{}; + uint8_t* private_buffer = nullptr; + size_t private_buffer_length = 0; + std::shared_ptr private_data{}; + + uint64_t crypt_seed = 0; + std::vector> _hierarchy{}; + + bool generate_keypair(uint8_t* /* private key */, uint8_t* /* public key */); + void _register_raw_private_key(size_t /* index */, uint8_t* /* key */); + }; + + struct LicensePrivateWriteOptions { + int precalculated_key_index = -2; /* -2 => Do not write any private keys; -1 => Write everything */ + }; + + struct LicensePrivate { + public: + static std::shared_ptr read(const std::shared_ptr& /* handle */, uint8_t /* version */, const uint8_t* /* buffer */, size_t /* length */, uint8_t& /* error */); + static std::shared_ptr create(const std::shared_ptr& /* handle */, int /* precalculated private key index */, const uint8_t* /* precalculated key */); + + bool private_key_chain_valid(); + + bool has_meta(const std::string& key) const { return this->meta_data.count(key) > 0; } + std::string get_meta(const std::string& key) const { return this->meta_data.at(key); } + void set_meta(const std::string& key, const std::string& value) { this->meta_data[key] = value; } + + /* if target is null just increase the offset! */ + bool write(uint8_t* /* target */, size_t& /* offset */, size_t /* length */, const LicensePrivateWriteOptions& /* options */); + + void register_raw_private_key(uint8_t /* index */, const uint8_t* /* key */); + bool has_raw_private_key(uint8_t /* index */) const; + + bool private_key_calculable(uint8_t /* index */) const; + bool calculate_private_key(uint8_t* /* response */, uint8_t /* index */) const; + private: + std::weak_ptr _handle; + LicensePrivate() = default; + + int precalculated_private_key_index = -2; /* -2 means not set, -1 means root key */ + std::array precalculated_private_key{}; + std::map> private_keys{}; + std::map meta_data{}; + }; + + namespace hierarchy { + struct Intermediate; } - struct LicenseIssuer { - std::string name; - std::string email; + struct HierarchyEntry { + friend struct hierarchy::Intermediate; + public: + ~HierarchyEntry(); + + static std::shared_ptr read(const uint8_t* /* buffer */, size_t& /* offset */, size_t /* max size */); + bool write(uint8_t* /* buffer */, size_t& /* offset */, size_t /* max size */) const; + inline size_t write_length() const { return 43 + this->read_body_length; } + + inline uint8_t entry_type() const { return this->_entry_type; } + + template + inline typename T::time_point begin_timestamp() const { return typename T::time_point{} + std::chrono::minutes(this->_timestamp_begin); } + + template + inline typename T::time_point end_timestamp() const { return typename T::time_point{} + std::chrono::minutes(this->_timestamp_end); } + + inline const std::array& public_key() const { return this->_public_key; } + inline std::array& public_key() { + this->_hash_set = false; + return this->_public_key; + } + + inline const uint8_t* body() const { return this->read_body; } + inline const size_t body_length() const { return this->read_body_length; } + + template + inline I interpret_as() const { + assert(this->interpret_as()); + return I{this->read_body, this->read_body_length}; + } + + template + inline bool interpretable_as() const { return I::type == this->_entry_type; } + + inline bool hash(uint8_t* /* hash result [64] */) const; + protected: + template + HierarchyEntry(uint8_t type, const uint8_t* public_key, const typename T::time_point& begin, const typename T::time_point& end) { + this->_entry_type = type; + memcpy(this->_public_key.data(), public_key, this->_public_key.size()); + this->_timestamp_begin = std::chrono::floor(begin.time_since_epoch()).count(); + this->_timestamp_end = std::chrono::floor(end.time_since_epoch()).count(); + } + private: + HierarchyEntry() = default; + + mutable std::array _hash{}; + mutable bool _hash_set = false; + + uint8_t _entry_type = 0; + std::array _public_key{}; + + uint32_t _timestamp_begin = 0; /* Minutes since epoch! */ + uint32_t _timestamp_end = 0; /* Minutes since epoch! */ + + uint8_t* read_body = nullptr; + size_t read_body_length = 0; + + bool allocate_read_body(size_t); }; - struct LicenseChainEntry { - data::ChainEntryHead head; - struct { - bool contains; - uint8_t key[32]; - } prv_key; - LicenseIssuer issuer; - }; + namespace hierarchy { + struct BodyInterpreter { + public: + BodyInterpreter() = delete; - struct LicenseChain { - public: - data::ChainHead head; - std::deque entries; + protected: + BodyInterpreter(const uint8_t* memory, size_t length) { this->_memory = memory; this->_length = length; } + const uint8_t* _memory = nullptr; + size_t _length = 0; + }; - private: - }; + struct Intermediate : public BodyInterpreter { + friend struct HierarchyEntry; + public: + static constexpr uint8_t type = 1; + static std::shared_ptr create( + const uint8_t* /* public key */, + const std::chrono::system_clock::time_point& /* begin */, + const std::chrono::system_clock::time_point& /* end */, + const std::string& /* description */ + ); + + std::string_view description(); + private: + Intermediate(const uint8_t* memory, size_t length) : BodyInterpreter(memory, length) {} + }; + } } - */ enum LicenseType : uint8_t { INVALID, diff --git a/server/dependency_resolver.sh b/server/dependency_resolver.sh new file mode 100755 index 0000000..ecd5f41 --- /dev/null +++ b/server/dependency_resolver.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash + +#WARNING: Any spaces within the path will cause trouble! + +#ldd -d +if [[ ! -f "$1" ]]; then + echo "Missing target file ($1)" + exit 1 +fi + +# This is a multidimensional array +# key /* library file */ => (library name; library file; dependencies ()) +declare -A collected_libraries + +# This is a one dimensional array +# key /* library file */ => use count +declare -A collected_libraries_use_count + +declare -A libraries_of_result +function libraries_of { + local buffer + local index + local data + + buffer=$(ldd -d "$1") + index=0 + + libraries_of_result=() + + IFS=$'\n' + for line in ${buffer}; do + index=$(($index + 1)) + [[ ${index} == 1 ]] && continue + IFS=$' ' data=(${line}) + + # We trim the leading and tailing white spaces + _key=$(echo "${data[0]}" | sed -e 's/^[[:space:]]*//') + _value=$(echo "${data[2]}" | sed -e 's/^[[:space:]]*//') + libraries_of_result["${_key}"]="${_value}" + done + + [[ $? -ne 0 ]] && return 1 + return 0 +} + +function print_lookup_stack { + index_max=${#@} + index_args="$@" + + #echo -n -e "\r\033[K" + echo "" + for (( index = 0; index < $index_max; ++index )); do + arg=$(eval echo \$$((${index} + 1))) + echo -n $(basename ${arg}) + [[ $(($index + 1)) -lt ${index_max} ]] && echo -n " => " + done + #sleep 1 +} + +declare -a libraries_of_deep_stack +function libraries_of_deep { + local IFS + local valid_libraries + local inner_array + local result_array + + libraries_of $1 + + valid_libraries=() + #echo "Gathered libraries for $1:" + for library_name in "${!libraries_of_result[@]}"; do + [[ -z "${libraries_of_result[$library_name]}" ]] && { + #echo " The dependency $library_name for $1 could not be resolved" + continue + } + + #echo " $library_name at ${libraries_of_result[$library_name]}"; + valid_libraries+=("${libraries_of_result[$library_name]}") + done + + IFS=$';' inner_array="${valid_libraries[*]}" + IFS=$' ' result_array=("$(basename $1)" "$1" "$inner_array") + collected_libraries[$1]="${result_array[@]}" + + libraries_of_deep_stack+=($1) + print_lookup_stack ${libraries_of_deep_stack[@]} + + for library_path in "${valid_libraries[@]}"; do + # echo "Looking up library path $library_path" + [[ ! -z "${collected_libraries[$library_path]}" ]] && { + #echo "Library $library_path already resolved" + collected_libraries_use_count[$library_path]=$((${collected_libraries_use_count[$library_path]} + 1)) + continue + } + #echo "Resolving libraries for path $library_path" + collected_libraries_use_count[$library_path]=1 + libraries_of_deep ${library_path} + #library_name + done + unset 'libraries_of_deep_stack[${#libraries_of_deep_stack[@]}-1]'; +} + +libraries_of_deep $1 +echo -e -n "\r\033[K" #Clear the stack + +for key in "${!collected_libraries[@]}"; do + IFS=$' ' library_data=(${collected_libraries[$key]}) + IFS=$';' libraries=(${library_data[2]}) + + echo "Got library ${library_data[0]} (${library_data[1]}) directly used ${collected_libraries_use_count[$key]} times:" + for library in "${libraries[@]}"; do + echo " $library" + done +done \ No newline at end of file diff --git a/server/src/ServerManager.cpp b/server/src/ServerManager.cpp index b670ff6..ea89685 100644 --- a/server/src/ServerManager.cpp +++ b/server/src/ServerManager.cpp @@ -14,6 +14,7 @@ ServerManager::ServerManager(InstanceHandler* handle) : handle(handle) { this->puzzles = new protocol::PuzzleManager(); this->handshakeTickers = new threads::Scheduler(1, "handshake ticker"); this->execute_loop = new event::EventExecutor("executor #"); + //this->join_loop = new event::EventExecutor("joiner #"); this->_ioManager = new io::VoiceIOManager(); this->handshakeTickers->schedule("ticker", [&](){ this->tickHandshakeClients(); }, seconds(1)); @@ -41,6 +42,11 @@ ServerManager::~ServerManager() { delete this->execute_loop; this->execute_loop = nullptr; + if(this->join_loop) + this->join_loop->shutdown(); + delete this->join_loop; + this->join_loop = nullptr; + if(this->handshakeTickers) { this->handshakeTickers->shutdown(); } diff --git a/server/src/ServerManager.h b/server/src/ServerManager.h index 4082f93..10ea016 100644 --- a/server/src/ServerManager.h +++ b/server/src/ServerManager.h @@ -61,6 +61,7 @@ namespace ts { protocol::PuzzleManager* rsaPuzzles() { return this->puzzles; } + event::EventExecutor* get_join_loop() { return this->join_loop; } event::EventExecutor* get_executor_loop() { return this->execute_loop; } inline void adjust_executor_threads() { @@ -84,6 +85,7 @@ namespace ts { protocol::PuzzleManager* puzzles = nullptr; event::EventExecutor* execute_loop = nullptr; + event::EventExecutor* join_loop = nullptr; threads::Scheduler* handshakeTickers = nullptr; io::VoiceIOManager* _ioManager = nullptr; diff --git a/server/src/client/ConnectedClientCommandHandler.cpp b/server/src/client/ConnectedClientCommandHandler.cpp index 2d2a69d..9130901 100644 --- a/server/src/client/ConnectedClientCommandHandler.cpp +++ b/server/src/client/ConnectedClientCommandHandler.cpp @@ -7583,6 +7583,7 @@ CommandResult ConnectedClient::handleCommandDummy_IpChange(ts::Command &cmd) { } } + this->properties()[property::CONNECTION_CLIENT_IP] = this->getLoggingPeerIp(); return CommandResult::Success; } diff --git a/shared b/shared index 4a9d8f1..8378521 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit 4a9d8f132fe9ca3411e2e3c245922ad778c56aa0 +Subproject commit 837852114f92d0f3acb94b5e0807dee265bf40e5