Skip to content

Commit dcb4332

Browse files
authored
Merge pull request #61 from vwidor/codex/tesla-key-persist-pairing
fix: persist private key and allow BLE whitelist pairing flow
2 parents 15564fd + 6febe5b commit dcb4332

4 files changed

Lines changed: 64 additions & 19 deletions

File tree

include/client.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class Client {
5050
int create_private_key();
5151
int load_private_key(const uint8_t *private_key_buffer, size_t private_key_length);
5252
int get_private_key(pb_byte_t *output_buffer, size_t output_buffer_length, size_t *output_length);
53+
bool has_private_key() const;
5354

5455
// Message building
5556
int build_white_list_message(Keys_Role role, VCSEC_KeyFormFactor form_factor, pb_byte_t *output_buffer,

include/vehicle.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ class Vehicle {
248248
void send_infotainment_poll_(const std::string &name, int32_t data_type, bool force_wake = false);
249249
void initiate_auth_for_domain_(const std::shared_ptr<Command> &command, UniversalMessage_Domain domain,
250250
CommandState waiting_state, const std::string &domain_name);
251+
bool persist_private_key_();
251252
template<typename T> void send_infotainment_action_with_value_(const std::string &name, int32_t action_tag, T value) {
252253
send_command(UniversalMessage_Domain_DOMAIN_INFOTAINMENT, name,
253254
[action_tag, value](Client *client, uint8_t *buff, size_t *len) {

src/client.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ int Client::get_private_key(pb_byte_t *output_buffer, size_t output_buffer_lengt
9797
return crypto_context_.get_private_key(output_buffer, output_buffer_length, output_length);
9898
}
9999

100+
bool Client::has_private_key() const { return crypto_context_.is_private_key_initialized(); }
101+
100102
int Client::generate_public_key_data_() {
101103
// Set the buffer size to maximum capacity before calling generate_public_key
102104
public_key_size_ = public_key_.size();

src/vehicle.cpp

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ void TeslaBLE::Vehicle::process_command_queue_() {
156156
break;
157157
case CommandState::WAITING_FOR_RESPONSE: {
158158
auto tx_duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - command->last_tx_at);
159-
if (tx_duration > CLOCK_SYNC_MAX_LATENCY) {
159+
const auto timeout = (command->name == "Whitelist Add Key") ? COMMAND_TIMEOUT : CLOCK_SYNC_MAX_LATENCY;
160+
if (tx_duration > timeout) {
160161
if (command->name == "Wake" && is_vehicle_awake_) {
161162
LOG_INFO("Wake response timeout but vehicle is awake - proceeding");
162163
mark_command_completed_(command);
@@ -181,6 +182,15 @@ std::shared_ptr<TeslaBLE::Command> TeslaBLE::Vehicle::peek_command_() const {
181182

182183
void TeslaBLE::Vehicle::process_idle_command_(const std::shared_ptr<Command> &command) {
183184
command->started_at = std::chrono::steady_clock::now();
185+
186+
// Pairing starts with an untrusted key, so VCSEC session auth will return
187+
// KEY_NOT_ON_WHITELIST. This command must be sent without prior session auth.
188+
if (command->name == "Whitelist Add Key") {
189+
LOG_INFO("Bypassing session auth for Whitelist Add Key");
190+
command->state = CommandState::READY;
191+
return;
192+
}
193+
184194
switch (command->domain) {
185195
case UniversalMessage_Domain_DOMAIN_BROADCAST:
186196
command->state = CommandState::READY;
@@ -1216,11 +1226,48 @@ void TeslaBLE::Vehicle::close_windows() {
12161226
// Pairing and Key Management
12171227
// =============================================================================
12181228

1229+
bool TeslaBLE::Vehicle::persist_private_key_() {
1230+
if (!client_ || !storage_adapter_) {
1231+
LOG_ERROR("Client or storage adapter unavailable");
1232+
return false;
1233+
}
1234+
1235+
std::array<uint8_t, 2048> key_buf{};
1236+
size_t key_len = 0;
1237+
if (client_->get_private_key(key_buf.data(), key_buf.size(), &key_len) != 0) {
1238+
LOG_ERROR("Failed to export private key");
1239+
return false;
1240+
}
1241+
1242+
if (key_len == 0 || key_len > key_buf.size()) {
1243+
LOG_ERROR("Invalid private key length: %zu", key_len);
1244+
return false;
1245+
}
1246+
1247+
std::vector<uint8_t> key_vec(key_buf.begin(), key_buf.begin() + key_len);
1248+
if (!storage_adapter_->save("private_key", key_vec)) {
1249+
LOG_ERROR("Failed to save private key to storage");
1250+
return false;
1251+
}
1252+
1253+
return true;
1254+
}
1255+
12191256
void TeslaBLE::Vehicle::pair(Keys_Role role) {
12201257
LOG_INFO("Initiating pairing sequence...");
1221-
if (client_->create_private_key() != 0) {
1222-
LOG_WARNING("Could not check/create private key, proceeding anyway");
1258+
if (!client_->has_private_key()) {
1259+
LOG_INFO("No private key loaded, creating a new one");
1260+
if (client_->create_private_key() != 0) {
1261+
LOG_ERROR("Failed to create private key for pairing");
1262+
return;
1263+
}
12231264
}
1265+
1266+
if (!persist_private_key_()) {
1267+
LOG_ERROR("Cannot start pairing without persisted private key");
1268+
return;
1269+
}
1270+
12241271
send_command(UniversalMessage_Domain_DOMAIN_VEHICLE_SECURITY, "Whitelist Add Key",
12251272
[role_copy = role](Client *client, uint8_t *buf, size_t *len) {
12261273
return client->build_white_list_message(role_copy, VCSEC_KeyFormFactor_KEY_FORM_FACTOR_NFC_CARD, buf,
@@ -1230,23 +1277,17 @@ void TeslaBLE::Vehicle::pair(Keys_Role role) {
12301277

12311278
void TeslaBLE::Vehicle::regenerate_key() {
12321279
LOG_INFO("Regenerating private key...");
1233-
if (client_->create_private_key() == 0) {
1234-
// NOLINTNEXTLINE(readability-math-missing-parentheses) - macro from external library
1235-
uint8_t key_buf[MBEDTLS_ECP_MAX_PT_LEN];
1236-
size_t key_len = 0;
1237-
size_t buf_len = sizeof(key_buf);
1238-
if (client_->get_private_key(key_buf, buf_len, &key_len) == 0) {
1239-
std::vector<uint8_t> key_vec(key_buf, key_buf + key_len);
1240-
if (storage_adapter_->save("private_key", key_vec)) {
1241-
LOG_INFO("New private key saved to storage");
1242-
} else {
1243-
LOG_ERROR("Failed to save new private key");
1244-
}
1245-
} else {
1246-
LOG_ERROR("Failed to create private key");
1247-
}
1280+
if (client_->create_private_key() != 0) {
1281+
LOG_ERROR("Failed to create private key");
1282+
return;
12481283
}
1249-
} // namespace TeslaBLE
1284+
1285+
if (persist_private_key_()) {
1286+
LOG_INFO("New private key saved to storage");
1287+
} else {
1288+
LOG_ERROR("Failed to save new private key");
1289+
}
1290+
}
12501291

12511292
void TeslaBLE::Vehicle::handle_signed_message_error_(const UniversalMessage_RoutableMessage &msg,
12521293
bool &has_session_error) {

0 commit comments

Comments
 (0)