Files
pysim-local/bsp_python_bindings.cpp
Eric Wild 9ff35c651f c++ bpp verification code with pybind11
needs ext building:
$ python3 setup.py build_ext --inplace
2025-06-13 12:20:14 +02:00

1077 lines
43 KiB
C++

/*
* (C) 2025 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Eric Wild <ewild@sysmocom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Python bindings for BSP crypto using pybind11
// Compile with: c++ -O3 -Wall -shared -std=c++17 -fPIC `python3 -m pybind11 --includes` bsp_python_bindings.cpp -o bsp_crypto`python3-config --extension-suffix` $(pkg-config --cflags --libs openssl)
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include <vector>
#include <string>
#include <iostream>
#include <iomanip>
#include <cassert>
#include <cstring>
#include <cstdio>
#include <arpa/inet.h>
extern "C" {
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/params.h>
#include <openssl/core_names.h>
#include <openssl/kdf.h>
#include <openssl/sha.h>
#include <openssl/opensslv.h>
#include <openssl/asn1.h>
#include <openssl/asn1t.h>
#include <openssl/err.h>
}
//straightforward way is to do it properly with openssl instead of manually messing with the DER
typedef struct ReplaceSessionKeysRequest_ASN1_st {
ASN1_OCTET_STRING* initialMacChainingValue;
ASN1_OCTET_STRING* ppkEnc;
ASN1_OCTET_STRING* ppkCmac;
} ReplaceSessionKeysRequest_ASN1;
DECLARE_ASN1_FUNCTIONS(ReplaceSessionKeysRequest_ASN1)
ASN1_SEQUENCE(ReplaceSessionKeysRequest_ASN1) = {
ASN1_IMP(ReplaceSessionKeysRequest_ASN1, initialMacChainingValue, ASN1_OCTET_STRING, 0),
ASN1_IMP(ReplaceSessionKeysRequest_ASN1, ppkEnc, ASN1_OCTET_STRING, 1),
ASN1_IMP(ReplaceSessionKeysRequest_ASN1, ppkCmac, ASN1_OCTET_STRING, 2)
} ASN1_SEQUENCE_END(ReplaceSessionKeysRequest_ASN1)
IMPLEMENT_ASN1_FUNCTIONS(ReplaceSessionKeysRequest_ASN1)
typedef ReplaceSessionKeysRequest_ASN1 ReplaceSessionKeysRequest_outer;
DECLARE_ASN1_FUNCTIONS(ReplaceSessionKeysRequest_outer)
ASN1_ITEM_TEMPLATE(ReplaceSessionKeysRequest_outer) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_CONTEXT, 38, ReplaceSessionKeysRequest_outer, ReplaceSessionKeysRequest_ASN1)
ASN1_ITEM_TEMPLATE_END(ReplaceSessionKeysRequest_outer)
IMPLEMENT_ASN1_FUNCTIONS(ReplaceSessionKeysRequest_outer)
// Define stack macros for ASN1_OCTET_STRING
DEFINE_STACK_OF(ASN1_OCTET_STRING)
// RemoteOpId ::= [2] INTEGER
// This is a tagged INTEGER type, not just an INTEGER with a tag
typedef ASN1_INTEGER RemoteOpId;
DECLARE_ASN1_FUNCTIONS(RemoteOpId)
ASN1_ITEM_TEMPLATE(RemoteOpId) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_CONTEXT, 2, RemoteOpId, ASN1_INTEGER)
ASN1_ITEM_TEMPLATE_END(RemoteOpId)
IMPLEMENT_ASN1_FUNCTIONS(RemoteOpId)
// ControlRefTemplate
typedef struct ControlRefTemplate_st {
ASN1_OCTET_STRING* keyType;
ASN1_OCTET_STRING* keyLen;
ASN1_OCTET_STRING* hostId;
} ControlRefTemplate;
DECLARE_ASN1_FUNCTIONS(ControlRefTemplate)
ASN1_SEQUENCE(ControlRefTemplate) = {
ASN1_IMP(ControlRefTemplate, keyType, ASN1_OCTET_STRING, 0),
ASN1_IMP(ControlRefTemplate, keyLen, ASN1_OCTET_STRING, 1),
ASN1_IMP(ControlRefTemplate, hostId, ASN1_OCTET_STRING, 4)
} ASN1_SEQUENCE_END(ControlRefTemplate)
IMPLEMENT_ASN1_FUNCTIONS(ControlRefTemplate)
// Custom types for APPLICATION tags > 30
typedef ASN1_OCTET_STRING SmdpOtpk;
DECLARE_ASN1_FUNCTIONS(SmdpOtpk)
ASN1_ITEM_TEMPLATE(SmdpOtpk) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_APPLICATION, 73, SmdpOtpk, ASN1_OCTET_STRING)
ASN1_ITEM_TEMPLATE_END(SmdpOtpk)
IMPLEMENT_ASN1_FUNCTIONS(SmdpOtpk)
typedef ASN1_OCTET_STRING SmdpSign;
DECLARE_ASN1_FUNCTIONS(SmdpSign)
ASN1_ITEM_TEMPLATE(SmdpSign) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_APPLICATION, 55, SmdpSign, ASN1_OCTET_STRING)
ASN1_ITEM_TEMPLATE_END(SmdpSign)
IMPLEMENT_ASN1_FUNCTIONS(SmdpSign)
// InitialiseSecureChannelRequest - base structure
typedef struct InitialiseSecureChannelRequest_ASN1_st {
RemoteOpId* remoteOpId;
ASN1_OCTET_STRING* transactionId;
ControlRefTemplate* controlRefTemplate;
SmdpOtpk* smdpOtpk;
SmdpSign* smdpSign;
} InitialiseSecureChannelRequest_ASN1;
DECLARE_ASN1_FUNCTIONS(InitialiseSecureChannelRequest_ASN1)
ASN1_SEQUENCE(InitialiseSecureChannelRequest_ASN1) = {
ASN1_SIMPLE(InitialiseSecureChannelRequest_ASN1, remoteOpId, RemoteOpId),
ASN1_IMP(InitialiseSecureChannelRequest_ASN1, transactionId, ASN1_OCTET_STRING, 0),
ASN1_IMP(InitialiseSecureChannelRequest_ASN1, controlRefTemplate, ControlRefTemplate, 6),
ASN1_SIMPLE(InitialiseSecureChannelRequest_ASN1, smdpOtpk, SmdpOtpk),
ASN1_SIMPLE(InitialiseSecureChannelRequest_ASN1, smdpSign, SmdpSign)
} ASN1_SEQUENCE_END(InitialiseSecureChannelRequest_ASN1)
IMPLEMENT_ASN1_FUNCTIONS(InitialiseSecureChannelRequest_ASN1)
// InitialiseSecureChannelRequest with tag [35]
typedef InitialiseSecureChannelRequest_ASN1 InitialiseSecureChannelRequest_tagged;
DECLARE_ASN1_FUNCTIONS(InitialiseSecureChannelRequest_tagged)
ASN1_ITEM_TEMPLATE(InitialiseSecureChannelRequest_tagged) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_CONTEXT, 35, InitialiseSecureChannelRequest_tagged, InitialiseSecureChannelRequest_ASN1)
ASN1_ITEM_TEMPLATE_END(InitialiseSecureChannelRequest_tagged)
IMPLEMENT_ASN1_FUNCTIONS(InitialiseSecureChannelRequest_tagged)
// Define the tagged OCTET STRING types
typedef ASN1_OCTET_STRING ASN1_OCTET_STRING_TAG7;
typedef ASN1_OCTET_STRING ASN1_OCTET_STRING_TAG8;
typedef ASN1_OCTET_STRING ASN1_OCTET_STRING_TAG6;
DECLARE_ASN1_ITEM(ASN1_OCTET_STRING_TAG7)
DECLARE_ASN1_ITEM(ASN1_OCTET_STRING_TAG8)
DECLARE_ASN1_ITEM(ASN1_OCTET_STRING_TAG6)
ASN1_ITEM_TEMPLATE(ASN1_OCTET_STRING_TAG7) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_CONTEXT, 7, ASN1_OCTET_STRING_TAG7, ASN1_OCTET_STRING)
ASN1_ITEM_TEMPLATE_END(ASN1_OCTET_STRING_TAG7)
ASN1_ITEM_TEMPLATE(ASN1_OCTET_STRING_TAG8) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_CONTEXT, 8, ASN1_OCTET_STRING_TAG8, ASN1_OCTET_STRING)
ASN1_ITEM_TEMPLATE_END(ASN1_OCTET_STRING_TAG8)
ASN1_ITEM_TEMPLATE(ASN1_OCTET_STRING_TAG6) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_CONTEXT, 6, ASN1_OCTET_STRING_TAG6, ASN1_OCTET_STRING)
ASN1_ITEM_TEMPLATE_END(ASN1_OCTET_STRING_TAG6)
DECLARE_ASN1_FUNCTIONS(ASN1_OCTET_STRING_TAG7)
DECLARE_ASN1_FUNCTIONS(ASN1_OCTET_STRING_TAG8)
DECLARE_ASN1_FUNCTIONS(ASN1_OCTET_STRING_TAG6)
IMPLEMENT_ASN1_FUNCTIONS(ASN1_OCTET_STRING_TAG7)
IMPLEMENT_ASN1_FUNCTIONS(ASN1_OCTET_STRING_TAG8)
IMPLEMENT_ASN1_FUNCTIONS(ASN1_OCTET_STRING_TAG6)
// BoundProfilePackage
typedef struct BoundProfilePackage_st {
InitialiseSecureChannelRequest_tagged* initialiseSecureChannelRequest;
STACK_OF(ASN1_OCTET_STRING)* firstSequenceOf87;
STACK_OF(ASN1_OCTET_STRING)* sequenceOf88;
STACK_OF(ASN1_OCTET_STRING)* secondSequenceOf87;
STACK_OF(ASN1_OCTET_STRING)* sequenceOf86;
} BoundProfilePackage;
DECLARE_ASN1_FUNCTIONS(BoundProfilePackage)
// Define the base sequence without the tag
ASN1_SEQUENCE(BoundProfilePackage) = {
ASN1_SIMPLE(BoundProfilePackage, initialiseSecureChannelRequest, InitialiseSecureChannelRequest_tagged),
ASN1_IMP_SEQUENCE_OF(BoundProfilePackage, firstSequenceOf87, ASN1_OCTET_STRING_TAG7, 0),
ASN1_IMP_SEQUENCE_OF(BoundProfilePackage, sequenceOf88, ASN1_OCTET_STRING_TAG8, 1),
ASN1_IMP_SEQUENCE_OF_OPT(BoundProfilePackage, secondSequenceOf87, ASN1_OCTET_STRING_TAG7, 2),
ASN1_IMP_SEQUENCE_OF(BoundProfilePackage, sequenceOf86, ASN1_OCTET_STRING_TAG6, 3)
} ASN1_SEQUENCE_END(BoundProfilePackage)
IMPLEMENT_ASN1_FUNCTIONS(BoundProfilePackage)
// For BoundProfilePackage ::= [54] SEQUENCE
// We need a version that's implicitly tagged as [54], replacing the SEQUENCE tag
typedef BoundProfilePackage BoundProfilePackage_tagged;
DECLARE_ASN1_FUNCTIONS(BoundProfilePackage_tagged)
// This creates a type where [54] replaces the SEQUENCE tag
ASN1_ITEM_TEMPLATE(BoundProfilePackage_tagged) =
ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_IMPTAG | ASN1_TFLG_CONTEXT,54, BoundProfilePackage_tagged, BoundProfilePackage)
ASN1_ITEM_TEMPLATE_END(BoundProfilePackage_tagged)
IMPLEMENT_ASN1_FUNCTIONS(BoundProfilePackage_tagged)
// reusing a dirty pointer fails
#define ossl_free_reset(add) { \
OPENSSL_free(add); \
add = 0; \
}
// Custom deleters wrapped in structs (since free functions might be macros)
struct BoundProfilePackage_tagged_Deleter {
void operator()(BoundProfilePackage_tagged *ss1) const
{
BoundProfilePackage_tagged_free(ss1);
}
};
struct ReplaceSessionKeysRequest_outer_Deleter {
void operator()(ReplaceSessionKeysRequest_outer *ss1) const
{
ReplaceSessionKeysRequest_outer_free(ss1);
}
};
struct EVP_CIPHER_CTX_Deleter {
void operator()(EVP_CIPHER_CTX *ctx) const
{
if (ctx) EVP_CIPHER_CTX_free(ctx);
}
};
struct EVP_MAC_CTX_Deleter {
void operator()(EVP_MAC_CTX *ctx) const
{
if (ctx) EVP_MAC_CTX_free(ctx);
}
};
struct EVP_MAC_Deleter {
void operator()(EVP_MAC *mac) const
{
if (mac) EVP_MAC_free(mac);
}
};
using BPP_ptr = std::unique_ptr<BoundProfilePackage_tagged, BoundProfilePackage_tagged_Deleter>;
using RPK_ptr = std::unique_ptr<ReplaceSessionKeysRequest_outer, ReplaceSessionKeysRequest_outer_Deleter>;
using EVP_CIPHER_CTX_unique_ptr = std::unique_ptr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_Deleter>;
using EVP_MAC_CTX_unique_ptr = std::unique_ptr<EVP_MAC_CTX, EVP_MAC_CTX_Deleter>;
using EVP_MAC_unique_ptr = std::unique_ptr<EVP_MAC, EVP_MAC_Deleter>;
class BspCrypto {
private:
static const size_t MAX_SEGMENT_SIZE = 1020;
static const size_t MAC_LENGTH = 8;
// static const size_t AES_BLOCK_SIZE = 16;
static const size_t AES_KEY_SIZE = 16;
std::vector<uint8_t> s_enc;
std::vector<uint8_t> s_mac;
std::vector<uint8_t> mac_chain;
uint32_t block_number;
static std::vector<uint8_t> compute_cmac(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& data,
size_t output_size = AES_BLOCK_SIZE) {
EVP_MAC *mac = EVP_MAC_fetch(nullptr, "CMAC", nullptr);
if (!mac) throw std::runtime_error("Failed to fetch CMAC implementation");
EVP_MAC_CTX_unique_ptr mac_ctx(EVP_MAC_CTX_new(mac));
EVP_MAC_free(mac);
if (!mac_ctx) throw std::runtime_error("Failed to create MAC context");
// Set up parameters for CMAC with AES-128-CBC
OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_CIPHER, (char*)"AES-128-CBC", 0),
OSSL_PARAM_construct_end()
};
if (EVP_MAC_init(mac_ctx.get(), key.data(), key.size(), params) != 1) {
throw std::runtime_error("Failed to init MAC");
}
if (EVP_MAC_update(mac_ctx.get(), data.data(), data.size()) != 1) {
throw std::runtime_error("Failed to update MAC");
}
std::vector<uint8_t> output(output_size);
size_t mac_len = output_size;
if (EVP_MAC_final(mac_ctx.get(), output.data(), &mac_len, output.size()) != 1) {
throw std::runtime_error("Failed to finalize MAC");
}
output.resize(mac_len);
return output;
}
// Generic AES-128-CBC cipher operation helper
std::vector<uint8_t> aes_cipher_operation(const std::vector<uint8_t>& input,
const std::vector<uint8_t>& key,
const std::vector<uint8_t>& iv,
bool encrypt) {
if (key.size() != AES_KEY_SIZE) {
throw std::runtime_error("Invalid key size");
}
if (iv.size() != AES_BLOCK_SIZE) {
throw std::runtime_error("Invalid IV size");
}
EVP_CIPHER_CTX_unique_ptr ctx(EVP_CIPHER_CTX_new());
if (!ctx) throw std::runtime_error("Failed to create cipher context");
const EVP_CIPHER* cipher = EVP_aes_128_cbc();
int result;
if (encrypt) {
result = EVP_EncryptInit_ex(ctx.get(), cipher, nullptr, key.data(), iv.data());
} else {
result = EVP_DecryptInit_ex(ctx.get(), cipher, nullptr, key.data(), iv.data());
}
if (result != 1) {
throw std::runtime_error(encrypt ? "Failed to init AES encryption" : "Failed to init AES decryption");
}
// Disable padding since we handle custom padding ourselves
if (EVP_CIPHER_CTX_set_padding(ctx.get(), 0) != 1) {
throw std::runtime_error("Failed to disable padding");
}
// Allocate output buffer (may need extra block for padding)
std::vector<uint8_t> output(input.size() + AES_BLOCK_SIZE);
int len = 0;
if (encrypt) {
result = EVP_EncryptUpdate(ctx.get(), output.data(), &len, input.data(), input.size());
} else {
result = EVP_DecryptUpdate(ctx.get(), output.data(), &len, input.data(), input.size());
}
if (result != 1) {
throw std::runtime_error(encrypt ? "Failed to encrypt data" : "Failed to decrypt data");
}
// Verify output length matches input (since padding is disabled)
if (len != static_cast<int>(input.size())) {
throw std::runtime_error("Unexpected " + std::string(encrypt ? "encryption" : "decryption") +
" output length: " + std::to_string(len) +
" expected: " + std::to_string(input.size()));
}
int final_len = 0;
if (encrypt) {
result = EVP_EncryptFinal_ex(ctx.get(), output.data() + len, &final_len);
} else {
result = EVP_DecryptFinal_ex(ctx.get(), output.data() + len, &final_len);
}
if (result != 1) {
throw std::runtime_error(encrypt ? "Failed to finalize encryption" : "Failed to finalize decryption");
}
// With disabled padding, final_len should be 0
if (final_len != 0) {
throw std::runtime_error("Unexpected final " + std::string(encrypt ? "encryption" : "decryption") +
" length: " + std::to_string(final_len));
}
output.resize(len);
return output;
}
// BERTLV length encoding
static std::vector<uint8_t> encode_bertlv_length(size_t length) {
if (length < 0x80) {
return {static_cast<uint8_t>(length)};
} else if (length < 0x100) {
return {0x81, static_cast<uint8_t>(length)};
} else if (length < 0x10000) {
return {0x82, static_cast<uint8_t>(length >> 8), static_cast<uint8_t>(length & 0xFF)};
} else {
throw std::runtime_error("Length too large for BERTLV encoding");
}
}
static void print_hex(const std::string& label, const std::vector<uint8_t>& data) {
std::cout << label << ": ";
for (uint8_t b : data) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(b);
}
std::cout << std::dec << std::endl;
}
static void print_hex(const char *label, const unsigned char *data, int len) {
const std::vector<uint8_t> t = std::vector<uint8_t>((uint8_t*)data, (uint8_t*)data+len);
const std::string l = std::string("").assign(label);
print_hex(l, t);
}
static std::pair<size_t, size_t> parse_bertlv_length(const std::vector<uint8_t>& data, size_t offset) {
if (offset >= data.size()) throw std::runtime_error("Invalid length offset");
uint8_t first_byte = data[offset];
if (first_byte < 0x80) {
return {first_byte, offset + 1};
} else if (first_byte == 0x81) {
if (offset + 1 >= data.size()) throw std::runtime_error("Invalid length encoding");
return {data[offset + 1], offset + 2};
} else if (first_byte == 0x82) {
if (offset + 2 >= data.size()) throw std::runtime_error("Invalid length encoding");
size_t length = (data[offset + 1] << 8) | data[offset + 2];
return {length, offset + 3};
} else {
throw std::runtime_error("Unsupported length encoding");
}
}
public:
BspCrypto(const std::vector<uint8_t>& s_enc_key,
const std::vector<uint8_t>& s_mac_key,
const std::vector<uint8_t>& initial_mcv)
: s_enc(s_enc_key), s_mac(s_mac_key), mac_chain(initial_mcv), block_number(1) {
assert(s_enc.size() == AES_KEY_SIZE);
assert(s_mac.size() == AES_KEY_SIZE);
assert(mac_chain.size() == AES_BLOCK_SIZE);
}
// X9.63 KDF
static std::vector<uint8_t> x963_kdf_sha256(const std::vector<uint8_t>& shared_secret,
const std::vector<uint8_t>& shared_info,
size_t output_length) {
std::vector<uint8_t> output;
output.reserve(output_length);
const size_t hash_length = 32; // SHA256
uint32_t counter = 1;
while (output.size() < output_length) {
// input: shared_secret || counter || shared_info
std::vector<uint8_t> hash_input;
hash_input.insert(hash_input.end(), shared_secret.begin(), shared_secret.end());
uint32_t counter_be = htonl(counter);
const uint8_t* counter_bytes = reinterpret_cast<const uint8_t*>(&counter_be);
hash_input.insert(hash_input.end(), counter_bytes, counter_bytes + 4);
hash_input.insert(hash_input.end(), shared_info.begin(), shared_info.end());
std::vector<uint8_t> hash_output(hash_length);
if (SHA256(hash_input.data(), hash_input.size(), hash_output.data()) == nullptr) {
throw std::runtime_error("SHA256 computation failed");
}
size_t bytes_needed = std::min(hash_length, output_length - output.size());
output.insert(output.end(), hash_output.begin(), hash_output.begin() + bytes_needed);
counter++;
}
// Truncate to desired length
output.resize(output_length);
return output;
}
static BspCrypto from_kdf(const std::vector<uint8_t>& shared_secret,
uint8_t key_type, uint8_t key_length,
const std::vector<uint8_t>& host_id,
const std::vector<uint8_t>& eid) {
// shared_info: key_type || key_length || len(host_id) || host_id || len(eid) || eid
std::vector<uint8_t> shared_info;
shared_info.push_back(key_type);
shared_info.push_back(key_length);
if (host_id.size() > 255) throw std::runtime_error("Host ID too long");
shared_info.push_back(static_cast<uint8_t>(host_id.size()));
shared_info.insert(shared_info.end(), host_id.begin(), host_id.end());
if (eid.size() > 255) throw std::runtime_error("EID too long");
shared_info.push_back(static_cast<uint8_t>(eid.size()));
shared_info.insert(shared_info.end(), eid.begin(), eid.end());
// Derive 48 bytes: 16 for initial_mcv + 16 for s_enc + 16 for s_mac
auto kdf_output = x963_kdf_sha256(shared_secret, shared_info, 48);
std::vector<uint8_t> initial_mcv(kdf_output.begin(), kdf_output.begin() + 16);
std::vector<uint8_t> s_enc(kdf_output.begin() + 16, kdf_output.begin() + 32);
std::vector<uint8_t> s_mac(kdf_output.begin() + 32, kdf_output.begin() + 48);
return BspCrypto(s_enc, s_mac, initial_mcv);
}
std::vector<uint8_t> generate_icv() {
// Use generate_icv_for_block and increment block_number
auto icv = generate_icv_for_block(block_number);
block_number++;
return icv;
}
// Custom padding: 0x80 followed by 0x00s to 16-byte boundary
std::vector<uint8_t> add_padding(const std::vector<uint8_t>& data) {
std::vector<uint8_t> padded = data;
padded.push_back(0x80);
while (padded.size() % AES_BLOCK_SIZE != 0) {
padded.push_back(0x00);
}
return padded;
}
// Remove custom padding
std::vector<uint8_t> remove_padding(const std::vector<uint8_t> &data) {
if (data.empty())
return data;
// Remove trailing zeros first
int last_nonzero = data.size() - 1;
while (last_nonzero >= 0 && data[last_nonzero] == 0x00) {
last_nonzero--;
}
if (last_nonzero >= 0 && data[last_nonzero] == 0x80) {
return std::vector<uint8_t>(data.begin(), data.begin() + last_nonzero);
}
return data;
}
// AES-CBC encryption
std::vector<uint8_t> aes_encrypt(const std::vector<uint8_t>& plaintext) {
auto padded = add_padding(plaintext);
auto icv = generate_icv();
return aes_cipher_operation(padded, s_enc, icv, true);
}
std::vector<uint8_t> aes_decrypt_with_icv(const std::vector<uint8_t>& ciphertext, const std::vector<uint8_t>& icv) {
auto decrypted = aes_cipher_operation(ciphertext, s_enc, icv, false);
return remove_padding(decrypted);
}
std::vector<uint8_t> generate_icv_for_block(uint32_t block_num) {
std::vector<uint8_t> block_data(AES_BLOCK_SIZE, 0);
uint32_t block_num_be = htonl(block_num);
memcpy(&block_data[12], &block_num_be, 4);
std::vector<uint8_t> zero_iv(AES_BLOCK_SIZE, 0);
return aes_cipher_operation(block_data, s_enc, zero_iv, true);
}
// Private helper for verify_and_decrypt operations
std::vector<uint8_t> verify_and_decrypt_helper(const std::vector<uint8_t>& segment,
const std::vector<uint8_t>& mac_chain_to_use,
bool decrypt) {
if (segment.size() < 3) throw std::runtime_error("Segment too small");
uint8_t tag = segment[0];
auto [length, length_end] = parse_bertlv_length(segment, 1);
if (length_end + length > segment.size()) {
throw std::runtime_error("Invalid segment length");
}
if (length <= MAC_LENGTH) {
throw std::runtime_error("Invalid segment length: payload too small");
}
size_t payload_length = length - MAC_LENGTH;
std::vector<uint8_t> payload(segment.begin() + length_end, segment.begin() + length_end + payload_length);
std::vector<uint8_t> received_mac(segment.begin() + length_end + payload_length, segment.begin() + length_end + length);
size_t lcc = payload.size() + MAC_LENGTH;
std::vector<uint8_t> temp_data;
temp_data.insert(temp_data.end(), mac_chain_to_use.begin(), mac_chain_to_use.end());
temp_data.push_back(tag);
auto length_bytes = encode_bertlv_length(lcc);
temp_data.insert(temp_data.end(), length_bytes.begin(), length_bytes.end());
temp_data.insert(temp_data.end(), payload.begin(), payload.end());
std::vector<uint8_t> computed_full_mac = compute_cmac(s_mac, temp_data);
std::vector<uint8_t> computed_mac(computed_full_mac.begin(), computed_full_mac.begin() + MAC_LENGTH);
if (received_mac != computed_mac) {
throw std::runtime_error("MAC verification failed");
}
// Update instance state
mac_chain = computed_full_mac;
if (decrypt) {
// generate_icv() increments block_number
auto icv = generate_icv();
return aes_decrypt_with_icv(payload, icv);
} else {
// MAC-only: return payload as-is and increment block counter
block_number++;
return payload;
}
}
std::vector<uint8_t> compute_mac(uint8_t tag, const std::vector<uint8_t>& data) {
size_t lcc = data.size() + MAC_LENGTH;
// temp_data: mac_chain + tag + length + data
std::vector<uint8_t> temp_data;
temp_data.insert(temp_data.end(), mac_chain.begin(), mac_chain.end());
temp_data.push_back(tag);
auto length_bytes = encode_bertlv_length(lcc);
temp_data.insert(temp_data.end(), length_bytes.begin(), length_bytes.end());
temp_data.insert(temp_data.end(), data.begin(), data.end());
// Compute CMAC
std::vector<uint8_t> full_mac = compute_cmac(s_mac, temp_data);
mac_chain = full_mac;
// truncated MAC (first 8 bytes)
return std::vector<uint8_t>(full_mac.begin(), full_mac.begin() + MAC_LENGTH);
}
std::vector<uint8_t> encrypt_and_mac_one(uint8_t tag, const std::vector<uint8_t>& plaintext) {
if (plaintext.size() > MAX_SEGMENT_SIZE - 10) { // Account for tag, length, MAC
throw std::runtime_error("Segment too large");
}
auto ciphertext = aes_encrypt(plaintext);
auto mac = compute_mac(tag, ciphertext);
// final result: tag + length + ciphertext + mac
std::vector<uint8_t> result;
result.push_back(tag);
auto length_bytes = encode_bertlv_length(ciphertext.size() + MAC_LENGTH);
result.insert(result.end(), length_bytes.begin(), length_bytes.end());
result.insert(result.end(), ciphertext.begin(), ciphertext.end());
result.insert(result.end(), mac.begin(), mac.end());
return result;
}
std::vector<uint8_t> decrypt_and_verify(const std::vector<uint8_t>& segments, bool decrypt = true) {
return verify_and_decrypt_helper(segments, mac_chain, decrypt);
}
struct ReplaceSessionKeysRequest {
std::vector<uint8_t> ppkEnc; // PPK-ENC (16 bytes)
std::vector<uint8_t> ppkCmac; // PPK-MAC (16 bytes)
std::vector<uint8_t> initialMacChainingValue; // Initial MCV for PPK (16 bytes)
};
static std::vector<uint8_t> asn1_octet_string_to_vector(const ASN1_OCTET_STRING* asn1_str) {
if (!asn1_str || !asn1_str->data || asn1_str->length <= 0) {
throw std::runtime_error("Invalid ASN1_OCTET_STRING");
}
return std::vector<uint8_t>(asn1_str->data, asn1_str->data + asn1_str->length);
}
static ReplaceSessionKeysRequest parse_replace_session_keys(const std::vector<uint8_t>& data) {
if (data.empty()) {
throw std::runtime_error("Empty ReplaceSessionKeysRequest data");
}
const unsigned char *p = data.data();
RPK_ptr rsk_asn1(d2i_ReplaceSessionKeysRequest_outer(nullptr, &p, static_cast<int>(data.size())));
if (!rsk_asn1) {
unsigned long err = ERR_get_error();
char err_buf[256];
ERR_error_string_n(err, err_buf, sizeof(err_buf));
std::string error_msg = "Failed to parse ReplaceSessionKeysRequest ASN.1: ";
error_msg += err_buf;
throw std::runtime_error(error_msg);
}
try {
if (!rsk_asn1->initialMacChainingValue || !rsk_asn1->ppkEnc || !rsk_asn1->ppkCmac) {
throw std::runtime_error("Missing required fields in ReplaceSessionKeysRequest");
}
if (rsk_asn1->initialMacChainingValue->length != 16) {
throw std::runtime_error("Invalid initialMacChainingValue length: expected 16 bytes");
}
if (rsk_asn1->ppkEnc->length != 16) {
throw std::runtime_error("Invalid ppkEnc length: expected 16 bytes");
}
if (rsk_asn1->ppkCmac->length != 16) {
throw std::runtime_error("Invalid ppkCmac length: expected 16 bytes");
}
ReplaceSessionKeysRequest result;
result.initialMacChainingValue = asn1_octet_string_to_vector(rsk_asn1->initialMacChainingValue);
result.ppkEnc = asn1_octet_string_to_vector(rsk_asn1->ppkEnc);
result.ppkCmac = asn1_octet_string_to_vector(rsk_asn1->ppkCmac);
return result;
} catch (...) {
throw;
}
}
static BspCrypto from_replace_session_keys(const ReplaceSessionKeysRequest& rsk) {
return BspCrypto(rsk.ppkEnc, rsk.ppkCmac, rsk.initialMacChainingValue);
}
struct BppProcessingResult {
std::vector<uint8_t> configureIsdp;
std::vector<uint8_t> storeMetadata;
ReplaceSessionKeysRequest replaceSessionKeys;
std::vector<uint8_t> profileData;
bool hasReplaceSessionKeys;
};
BppProcessingResult process_bound_profile_package(
const std::vector<uint8_t>& allofit
) {
BppProcessingResult result;
auto p = allofit.data();
BPP_ptr bpp(d2i_BoundProfilePackage_tagged(nullptr, &p, allofit.size()));
if (!bpp) {
std::cout << "Failed to decode BoundProfilePackage" << std::endl;
print_hex(" Data", allofit.data(), (allofit.size() > 32) ? 32 : allofit.size());
return result;
}
// all mandatory
if (bpp->firstSequenceOf87 == nullptr ||bpp->sequenceOf88 == nullptr || bpp->sequenceOf86 == nullptr ) {
std::cout << "Malformed BoundProfilePackage" << std::endl;
return result;
}
result.hasReplaceSessionKeys = bpp->secondSequenceOf87 != nullptr;
unsigned char *encoded = nullptr;
ASN1_OCTET_STRING *elem = nullptr;
int len = 0;
// Step 1: Decrypt ConfigureISDP with session keys
std::cout << "Step 1: Decrypting ConfigureISDP with session keys..." << std::endl;
elem = sk_ASN1_OCTET_STRING_value(bpp->firstSequenceOf87, 0);
len = i2d_ASN1_OCTET_STRING_TAG7(elem, &encoded);
result.configureIsdp = decrypt_and_verify({encoded, encoded + len}, true);
ossl_free_reset(encoded);
// Step 2: Verify StoreMetadata with session keys (MAC-only)
std::cout << "Step 2: Verifying StoreMetadata with session keys (MAC-only)..." << std::endl;
elem = sk_ASN1_OCTET_STRING_value(bpp->sequenceOf88, 0);
len = i2d_ASN1_OCTET_STRING_TAG8(elem, &encoded);
result.storeMetadata = decrypt_and_verify({encoded, encoded + len}, false);
ossl_free_reset(encoded);
// Step 3: If present, decrypt ReplaceSessionKeys with session keys
if (result.hasReplaceSessionKeys) {
std::cout << "Step 3: Decrypting ReplaceSessionKeys with session keys..." << std::endl;
elem = sk_ASN1_OCTET_STRING_value(bpp->secondSequenceOf87, 0);
len = i2d_ASN1_OCTET_STRING_TAG7(elem, &encoded);
auto rsk_data = decrypt_and_verify({encoded, encoded + len}, true);
result.replaceSessionKeys = parse_replace_session_keys(rsk_data);
ossl_free_reset(encoded);
// Step 4: Create NEW BSP instance with PPK and decrypt profile data
std::cout << "Step 4: Creating new BSP instance with PPK keys..." << std::endl;
auto ppk_bsp = from_replace_session_keys(result.replaceSessionKeys);
print_hex("PPK-ENC", result.replaceSessionKeys.ppkEnc);
print_hex("PPK-MAC", result.replaceSessionKeys.ppkCmac);
print_hex("PPK Initial MCV", result.replaceSessionKeys.initialMacChainingValue);
std::cout << "Step 5: Decrypting profile data with PPK keys..."<< std::endl;
int num = sk_ASN1_OCTET_STRING_num(bpp->sequenceOf86);
for (int i = 0; i < num; i++) {
elem = sk_ASN1_OCTET_STRING_value(bpp->sequenceOf86, i);
len = i2d_ASN1_OCTET_STRING_TAG6(elem, &encoded);
auto rv = ppk_bsp.decrypt_and_verify({encoded, encoded + len}, true);
result.profileData.insert(result.profileData.end(), rv.begin(), rv.end());
ossl_free_reset(encoded);
}
std::cout << "Step 5: "<< num << " profile chunks verified and decrypted"<< std::endl;
} else {
// No ReplaceSessionKeys - decrypt profile data with session keys
std::cout << "Step 3: Decrypting profile data with session keys (no PPK)..." << std::endl;
int num = sk_ASN1_OCTET_STRING_num(bpp->sequenceOf86);
for (int i = 0; i < num; i++) {
elem = sk_ASN1_OCTET_STRING_value(bpp->sequenceOf86, 0);
len = i2d_ASN1_OCTET_STRING_TAG6(elem, &encoded);
auto rv = decrypt_and_verify({encoded, encoded + len}, true);
result.profileData.insert(result.profileData.end(), rv.begin(), rv.end());
ossl_free_reset(encoded);
}
std::cout << "Step 3: "<< num << " profile chunks verified and decrypted"<< std::endl;
}
return result;
}
BppProcessingResult process_bound_profile_package(
const std::vector<uint8_t>& firstSequenceOf87, // ConfigureISDP
const std::vector<uint8_t>& sequenceOf88, // StoreMetadata
const std::vector<uint8_t>& secondSequenceOf87, // ReplaceSessionKeys (optional)
const std::vector<uint8_t>& sequenceOf86 // Profile data
) {
BppProcessingResult result;
result.hasReplaceSessionKeys = !secondSequenceOf87.empty();
// Step 1: Decrypt ConfigureISDP with session keys
std::cout << "Step 1: Decrypting ConfigureISDP with session keys..." << std::endl;
result.configureIsdp = decrypt_and_verify(firstSequenceOf87, true);
// Step 2: Verify StoreMetadata with session keys (MAC-only)
std::cout << "Step 2: Verifying StoreMetadata with session keys (MAC-only)..." << std::endl;
result.storeMetadata = decrypt_and_verify(sequenceOf88, false);
// Step 3: If present, decrypt ReplaceSessionKeys with session keys
if (result.hasReplaceSessionKeys) {
std::cout << "Step 3: Decrypting ReplaceSessionKeys with session keys..." << std::endl;
auto rsk_data = decrypt_and_verify(secondSequenceOf87, true);
result.replaceSessionKeys = parse_replace_session_keys(rsk_data);
// Step 4: Create NEW BSP instance with PPK and decrypt profile data
std::cout << "Step 4: Creating new BSP instance with PPK keys..." << std::endl;
auto ppk_bsp = from_replace_session_keys(result.replaceSessionKeys);
print_hex("PPK-ENC", result.replaceSessionKeys.ppkEnc);
print_hex("PPK-MAC", result.replaceSessionKeys.ppkCmac);
print_hex("PPK Initial MCV", result.replaceSessionKeys.initialMacChainingValue);
std::cout << "Step 5: Decrypting profile data with PPK keys..." << std::endl;
result.profileData = ppk_bsp.decrypt_and_verify(sequenceOf86, true);
} else {
// No ReplaceSessionKeys - decrypt profile data with session keys
std::cout << "Step 3: Decrypting profile data with session keys (no PPK)..." << std::endl;
result.profileData = decrypt_and_verify(sequenceOf86, true);
}
return result;
}
// Reset for fresh encryption/decryption
void reset(const std::vector<uint8_t>& initial_mcv) {
mac_chain = initial_mcv;
block_number = 1;
}
static std::vector<uint8_t> generate_replace_session_keys_data(
const std::vector<uint8_t>& ppk_enc,
const std::vector<uint8_t>& ppk_mac,
const std::vector<uint8_t>& initial_mcv
) {
if (ppk_enc.size() != 16 || ppk_mac.size() != 16 || initial_mcv.size() != 16) {
throw std::runtime_error("All PPK components must be 16 bytes");
}
std::vector<uint8_t> rsk_data;
rsk_data.insert(rsk_data.end(), ppk_enc.begin(), ppk_enc.end());
rsk_data.insert(rsk_data.end(), ppk_mac.begin(), ppk_mac.end());
rsk_data.insert(rsk_data.end(), initial_mcv.begin(), initial_mcv.end());
return rsk_data;
}
std::vector<uint8_t> mac_only_one(uint8_t tag, const std::vector<uint8_t>& plaintext) {
if (plaintext.size() > MAX_SEGMENT_SIZE - 10) { // Account for tag, length, MAC
throw std::runtime_error("Segment too large");
}
auto mac = compute_mac(tag, plaintext);
// final result: tag + length + plaintext + mac
std::vector<uint8_t> result;
result.push_back(tag);
auto length_bytes = encode_bertlv_length(plaintext.size() + MAC_LENGTH);
result.insert(result.end(), length_bytes.begin(), length_bytes.end());
result.insert(result.end(), plaintext.begin(), plaintext.end());
result.insert(result.end(), mac.begin(), mac.end());
block_number++;
return result;
}
std::vector<std::vector<uint8_t>> encrypt_and_mac_seg(uint8_t tag, const std::vector<uint8_t>& data) {
std::vector<std::vector<uint8_t>> segments;
size_t max_payload = MAX_SEGMENT_SIZE - 10; // Account for overhead
for (size_t offset = 0; offset < data.size(); offset += max_payload) {
size_t segment_size = std::min(max_payload, data.size() - offset);
std::vector<uint8_t> segment(data.begin() + offset, data.begin() + offset + segment_size);
segments.push_back(encrypt_and_mac_one(tag, segment));
}
return segments;
}
std::vector<std::vector<uint8_t>> mac_only_seg(uint8_t tag, const std::vector<uint8_t>& data) {
std::vector<std::vector<uint8_t>> segments;
size_t max_payload = MAX_SEGMENT_SIZE - 10; // Account for overhead
for (size_t offset = 0; offset < data.size(); offset += max_payload) {
size_t segment_size = std::min(max_payload, data.size() - offset);
std::vector<uint8_t> segment(data.begin() + offset, data.begin() + offset + segment_size);
segments.push_back(mac_only_one(tag, segment));
}
return segments;
}
};
// Python binding wrapper class
class BspCryptoWrapper {
private:
BspCrypto bsp;
// Private for from_kdf
explicit BspCryptoWrapper(BspCrypto&& bsp_instance) : bsp(std::move(bsp_instance)) {}
public:
BspCryptoWrapper(const std::vector<uint8_t>& s_enc,
const std::vector<uint8_t>& s_mac,
const std::vector<uint8_t>& initial_mcv)
: bsp(s_enc, s_mac, initial_mcv) {
}
static BspCryptoWrapper from_kdf(const std::vector<uint8_t>& shared_secret,
uint8_t key_type, uint8_t key_length,
const std::vector<uint8_t>& host_id,
const std::vector<uint8_t>& eid) {
auto bsp_instance = BspCrypto::from_kdf(shared_secret, key_type, key_length, host_id, eid);
return BspCryptoWrapper(std::move(bsp_instance));
}
pybind11::dict process_bound_profile_package(
pybind11::bytes& firstSequenceOf87,
pybind11::bytes& sequenceOf88,
pybind11::bytes& secondSequenceOf87,
pybind11::bytes& sequenceOf86) {
auto pbh = [](pybind11::bytes &bytes) {
pybind11::buffer_info info(pybind11::buffer(bytes).request());
const uint8_t *data = reinterpret_cast<const uint8_t *>(info.ptr);
size_t length = static_cast<size_t>(info.size);
return std::vector<uint8_t>(data, data + length);
};
auto result = bsp.process_bound_profile_package(pbh(firstSequenceOf87), pbh(sequenceOf88), pbh(secondSequenceOf87), pbh(sequenceOf86));
pybind11::dict py_result;
py_result["configureIsdp"] = result.configureIsdp;
py_result["storeMetadata"] = result.storeMetadata;
py_result["profileData"] = result.profileData;
py_result["hasReplaceSessionKeys"] = result.hasReplaceSessionKeys;
if (result.hasReplaceSessionKeys) {
pybind11::dict rsk;
rsk["ppkEnc"] = result.replaceSessionKeys.ppkEnc;
rsk["ppkCmac"] = result.replaceSessionKeys.ppkCmac;
rsk["initialMacChainingValue"] = result.replaceSessionKeys.initialMacChainingValue;
py_result["replaceSessionKeys"] = rsk;
}
return py_result;
}
pybind11::dict process_bound_profile_package2(pybind11::bytes& allofit) {
auto pbh = [](pybind11::bytes &bytes) {
pybind11::buffer_info info(pybind11::buffer(bytes).request());
const uint8_t *data = reinterpret_cast<const uint8_t *>(info.ptr);
size_t length = static_cast<size_t>(info.size);
return std::vector<uint8_t>(data, data + length);
};
auto result = bsp.process_bound_profile_package(pbh(allofit));
pybind11::dict py_result;
py_result["configureIsdp"] = result.configureIsdp;
py_result["storeMetadata"] = result.storeMetadata;
py_result["profileData"] = result.profileData;
py_result["hasReplaceSessionKeys"] = result.hasReplaceSessionKeys;
if (result.hasReplaceSessionKeys) {
pybind11::dict rsk;
rsk["ppkEnc"] = result.replaceSessionKeys.ppkEnc;
rsk["ppkCmac"] = result.replaceSessionKeys.ppkCmac;
rsk["initialMacChainingValue"] = result.replaceSessionKeys.initialMacChainingValue;
py_result["replaceSessionKeys"] = rsk;
}
return py_result;
}
std::vector<uint8_t> encrypt_and_mac_one(uint8_t tag, const std::vector<uint8_t>& plaintext) {
return bsp.encrypt_and_mac_one(tag, plaintext);
}
std::vector<uint8_t> mac_only_one(uint8_t tag, const std::vector<uint8_t>& plaintext) {
return bsp.mac_only_one(tag, plaintext);
}
std::vector<std::vector<uint8_t>> encrypt_and_mac_seg(uint8_t tag, const std::vector<uint8_t>& data) {
return bsp.encrypt_and_mac_seg(tag, data);
}
std::vector<std::vector<uint8_t>> mac_only(uint8_t tag, const std::vector<uint8_t>& data) {
return bsp.mac_only_seg(tag, data);
}
std::vector<uint8_t> decrypt_and_verify(const std::vector<uint8_t>& segments, bool decrypt = true) {
return bsp.decrypt_and_verify(segments, decrypt);
}
void reset(const std::vector<uint8_t>& initial_mcv) {
bsp.reset(initial_mcv);
}
static std::vector<uint8_t> hex_to_bytes(const std::string& hex_str) {
std::vector<uint8_t> result;
for (size_t i = 0; i < hex_str.length(); i += 2) {
std::string byte_str = hex_str.substr(i, 2);
uint8_t byte = static_cast<uint8_t>(std::strtol(byte_str.c_str(), nullptr, 16));
result.push_back(byte);
}
return result;
}
static std::string bytes_to_hex(const std::vector<uint8_t>& data) {
std::string result;
for (uint8_t b : data) {
char hex[3];
sprintf(hex, "%02x", b);
result += hex;
}
return result;
}
};
PYBIND11_MODULE(bsp_crypto, m) {
m.doc() = "BSP/BPP GSMA RSP test mod";
pybind11::class_<BspCryptoWrapper>(m, "BspCrypto")
.def(pybind11::init<const std::vector<uint8_t>&, const std::vector<uint8_t>&, const std::vector<uint8_t>&>())
.def_static("from_kdf", &BspCryptoWrapper::from_kdf)
.def("process_bound_profile_package", &BspCryptoWrapper::process_bound_profile_package)
.def("process_bound_profile_package2", &BspCryptoWrapper::process_bound_profile_package2)
.def("encrypt_and_mac_one", &BspCryptoWrapper::encrypt_and_mac_one)
.def("mac_only_one", &BspCryptoWrapper::mac_only_one)
.def("encrypt_and_mac", &BspCryptoWrapper::encrypt_and_mac_seg)
.def("mac_only", &BspCryptoWrapper::mac_only)
.def("decrypt_and_verify", &BspCryptoWrapper::decrypt_and_verify)
.def("reset", &BspCryptoWrapper::reset)
.def_static("hex_to_bytes", &BspCryptoWrapper::hex_to_bytes)
.def_static("bytes_to_hex", &BspCryptoWrapper::bytes_to_hex);
}