Skip to content

Commit f1f5971

Browse files
committed
Working decrypt
1 parent 967f5b3 commit f1f5971

File tree

2 files changed

+184
-19
lines changed

2 files changed

+184
-19
lines changed

tesla_fleet_api/tesla/vehicle/bluetooth.py

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,18 @@
5555
)
5656
from tesla_fleet_api.tesla.vehicle.proto.vcsec_pb2 import (
5757
FromVCSECMessage,
58+
InformationRequest,
59+
InformationRequestType,
5860
KeyFormFactor,
5961
KeyMetadata,
6062
PermissionChange,
6163
PublicKey,
6264
RKEAction_E,
6365
UnsignedMessage,
66+
VehicleStatus,
6467
WhitelistOperation,
6568
)
69+
from tesla_fleet_api.tesla.vehicle.proto.vehicle_pb2 import ChargeScheduleState, ChargeState, ClimateState, ClosuresState, DriveState, LocationState, MediaDetailState, MediaState, ParentalControlsState, PreconditioningScheduleState, SoftwareUpdateState, TirePressureState, VehicleData
6670

6771
SERVICE_UUID = "00000211-b2d1-43f0-9b88-960cebf8b91e"
6872
WRITE_UUID = "00000212-b2d1-43f0-9b88-960cebf8b91e"
@@ -173,9 +177,10 @@ def _on_message(self, data:bytes) -> None:
173177
# Get the ephemeral key here and save to self._ekey
174178
return
175179

176-
if(self._futures[msg.from_destination.domain]):
180+
if(msg.from_destination.domain in self._futures):
177181
LOGGER.debug(f"Received response for request {msg.request_uuid}")
178182
self._futures[msg.from_destination.domain].set_result(msg)
183+
del self._futures[msg.from_destination.domain]
179184
return
180185

181186
if msg.from_destination.domain == Domain.DOMAIN_VEHICLE_SECURITY:
@@ -249,9 +254,9 @@ async def wake_up(self):
249254
UnsignedMessage(RKEAction=RKEAction_E.RKE_ACTION_WAKE_VEHICLE)
250255
)
251256

252-
async def vehicle_data(self, endpoints: list[BluetoothVehicleData]):
257+
async def vehicle_data(self, endpoints: list[BluetoothVehicleData]) -> VehicleData:
253258
"""Get vehicle data."""
254-
return await self._sendInfotainment(
259+
return await self._getInfotainment(
255260
Action(
256261
vehicleAction=VehicleAction(
257262
getVehicleData=GetVehicleData(
@@ -271,3 +276,144 @@ async def vehicle_data(self, endpoints: list[BluetoothVehicleData]):
271276
)
272277
)
273278
)
279+
280+
async def charge_state(self) -> ChargeState:
281+
return (await self._getInfotainment(
282+
Action(
283+
vehicleAction=VehicleAction(
284+
getVehicleData=GetVehicleData(
285+
getChargeState=GetChargeState()
286+
)
287+
)
288+
)
289+
)).charge_state
290+
291+
async def climate_state(self) -> ClimateState:
292+
return (await self._getInfotainment(
293+
Action(
294+
vehicleAction=VehicleAction(
295+
getVehicleData=GetVehicleData(
296+
getClimateState=GetClimateState()
297+
)
298+
)
299+
)
300+
)).climate_state
301+
302+
async def drive_state(self) -> DriveState:
303+
return (await self._getInfotainment(
304+
Action(
305+
vehicleAction=VehicleAction(
306+
getVehicleData=GetVehicleData(
307+
getDriveState=GetDriveState()
308+
)
309+
)
310+
)
311+
)).drive_state
312+
313+
async def location_state(self) -> LocationState:
314+
return (await self._getInfotainment(
315+
Action(
316+
vehicleAction=VehicleAction(
317+
getVehicleData=GetVehicleData(
318+
getLocationState=GetLocationState()
319+
)
320+
)
321+
)
322+
)).location_state
323+
324+
async def closures_state(self) -> ClosuresState:
325+
return (await self._getInfotainment(
326+
Action(
327+
vehicleAction=VehicleAction(
328+
getVehicleData=GetVehicleData(
329+
getClosuresState=GetClosuresState()
330+
)
331+
)
332+
)
333+
)).closures_state
334+
335+
async def charge_schedule_state(self) -> ChargeScheduleState:
336+
return (await self._getInfotainment(
337+
Action(
338+
vehicleAction=VehicleAction(
339+
getVehicleData=GetVehicleData(
340+
getChargeScheduleState=GetChargeScheduleState()
341+
)
342+
)
343+
)
344+
)).charge_schedule_state
345+
346+
async def preconditioning_schedule_state(self) -> PreconditioningScheduleState:
347+
return (await self._getInfotainment(
348+
Action(
349+
vehicleAction=VehicleAction(
350+
getVehicleData=GetVehicleData(
351+
getPreconditioningScheduleState=GetPreconditioningScheduleState()
352+
)
353+
)
354+
)
355+
)).preconditioning_schedule_state
356+
357+
async def tire_pressure_state(self) -> TirePressureState:
358+
return (await self._getInfotainment(
359+
Action(
360+
vehicleAction=VehicleAction(
361+
getVehicleData=GetVehicleData(
362+
getTirePressureState=GetTirePressureState()
363+
)
364+
)
365+
)
366+
)).tire_pressure_state
367+
368+
async def media_state(self) -> MediaState:
369+
return (await self._getInfotainment(
370+
Action(
371+
vehicleAction=VehicleAction(
372+
getVehicleData=GetVehicleData(
373+
getMediaState=GetMediaState()
374+
)
375+
)
376+
)
377+
)).media_state
378+
379+
async def media_detail_state(self) -> MediaDetailState:
380+
return (await self._getInfotainment(
381+
Action(
382+
vehicleAction=VehicleAction(
383+
getVehicleData=GetVehicleData(
384+
getMediaDetailState=GetMediaDetailState()
385+
)
386+
)
387+
)
388+
)).media_detail_state
389+
390+
async def software_update_state(self) -> SoftwareUpdateState:
391+
return (await self._getInfotainment(
392+
Action(
393+
vehicleAction=VehicleAction(
394+
getVehicleData=GetVehicleData(
395+
getSoftwareUpdateState=GetSoftwareUpdateState()
396+
)
397+
)
398+
)
399+
)).software_update_state
400+
401+
async def parental_controls_state(self) -> ParentalControlsState:
402+
return (await self._getInfotainment(
403+
Action(
404+
vehicleAction=VehicleAction(
405+
getVehicleData=GetVehicleData(
406+
getParentalControlsState=GetParentalControlsState()
407+
)
408+
)
409+
)
410+
)).parental_controls_state
411+
412+
async def vehicle_state(self) -> VehicleStatus:
413+
return await self._getVehicleSecurity(
414+
UnsignedMessage(
415+
InformationRequest=InformationRequest(
416+
informationRequestType=InformationRequestType.INFORMATION_REQUEST_TYPE_GET_STATUS
417+
)
418+
)
419+
)

tesla_fleet_api/tesla/vehicle/commands.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@
5454
Flags,
5555
)
5656
from tesla_fleet_api.tesla.vehicle.proto.vcsec_pb2 import (
57-
OPERATIONSTATUS_OK,
5857
FromVCSECMessage,
58+
VehicleStatus,
5959
)
6060
from tesla_fleet_api.tesla.vehicle.proto.car_server_pb2 import (
6161
Action,
@@ -102,7 +102,7 @@
102102
MediaPreviousTrack,
103103
MediaPreviousFavorite,
104104
)
105-
from tesla_fleet_api.tesla.vehicle.proto.vehicle_pb2 import VehicleState, ClimateState
105+
from tesla_fleet_api.tesla.vehicle.proto.vehicle_pb2 import VehicleData, VehicleState, ClimateState
106106
from tesla_fleet_api.tesla.vehicle.proto.vcsec_pb2 import (
107107
UnsignedMessage,
108108
RKEAction_E,
@@ -165,6 +165,7 @@ class Session:
165165
hmac: bytes
166166
publicKey: bytes
167167
lock: Lock
168+
ready: bool
168169

169170
def __init__(self, parent: Commands, domain: Domain):
170171
self.parent = parent
@@ -293,42 +294,44 @@ async def _command(self, domain: Domain, command: bytes, attempt: int = 0) -> di
293294
if resp.HasField("protobuf_message_as_bytes"):
294295
#decrypt
295296
if(resp.signature_data.HasField("AES_GCM_Response_data")):
296-
print(resp.signature_data.AES_GCM_Response_data)
297-
if(msg.signature_data.HasField("AES_GCM_Personalized_Signature_Data")):
298-
tag = bytes(SignatureType.SIGNATURE_TYPE_AES_GCM_PERSONALIZED) + msg.signature_data.AES_GCM_Personalized_data.tag
297+
if(msg.signature_data.HasField("AES_GCM_Personalized_data")):
298+
request_hash = bytes([SignatureType.SIGNATURE_TYPE_AES_GCM_PERSONALIZED]) + msg.signature_data.AES_GCM_Personalized_data.tag
299299
elif(msg.signature_data.HasField("HMAC_Personalized_data")):
300-
tag = bytes(SignatureType.SIGNATURE_TYPE_HMAC_PERSONALIZED) + msg.signature_data.HMAC_Personalized_data.tag[:16]
300+
request_hash = bytes([SignatureType.SIGNATURE_TYPE_HMAC_PERSONALIZED]) + msg.signature_data.HMAC_Personalized_data.tag
301+
if(session.domain == Domain.DOMAIN_VEHICLE_SECURITY):
302+
request_hash = request_hash[:17]
301303
else:
302304
raise ValueError("Invalid request signature data")
305+
303306
metadata = bytes([
304307
Tag.TAG_SIGNATURE_TYPE,
305308
1,
306309
SignatureType.SIGNATURE_TYPE_AES_GCM_RESPONSE,
307310
Tag.TAG_DOMAIN,
308311
1,
309-
session.domain,
312+
resp.from_destination.domain,
310313
Tag.TAG_PERSONALIZATION,
311314
17,
312315
*self.vin.encode(),
313316
Tag.TAG_COUNTER,
314317
4,
315-
*struct.pack(">I", session.counter),
318+
*struct.pack(">I", resp.signature_data.AES_GCM_Response_data.counter),
316319
Tag.TAG_FLAGS,
317-
1,
318-
msg.flags,
320+
4,
321+
*struct.pack(">I", resp.flags),
319322
Tag.TAG_REQUEST_HASH,
320323
17,
321-
*tag,
324+
*request_hash,
322325
Tag.TAG_FAULT,
323-
1,
324-
resp.signedMessageStatus.signed_message_fault,
326+
4,
327+
*struct.pack(">I", resp.signedMessageStatus.signed_message_fault),
325328
Tag.TAG_END,
326329
])
327330

328331
aad = Hash(SHA256())
329332
aad.update(metadata)
330333
aesgcm = AESGCM(session.sharedKey)
331-
resp.protobuf_message_as_bytes = aesgcm.decrypt(resp.signature_data.AES_GCM_Response_data.nonce, resp.protobuf_message_as_bytes, aad.finalize())
334+
resp.protobuf_message_as_bytes = aesgcm.decrypt(resp.signature_data.AES_GCM_Response_data.nonce, resp.protobuf_message_as_bytes + resp.signature_data.AES_GCM_Response_data.tag, aad.finalize())
332335

333336
if(resp.from_destination.domain == Domain.DOMAIN_VEHICLE_SECURITY):
334337
vcsec = FromVCSECMessage.FromString(resp.protobuf_message_as_bytes)
@@ -365,6 +368,10 @@ async def _command(self, domain: Domain, command: bytes, attempt: int = 0) -> di
365368
"reason": response.ping.local_timestamp
366369
}
367370
}
371+
if response.HasField("vehicleData"):
372+
return {
373+
"response": response.vehicleData
374+
}
368375
if response.HasField("actionStatus"):
369376
return {
370377
"response": {
@@ -373,6 +380,7 @@ async def _command(self, domain: Domain, command: bytes, attempt: int = 0) -> di
373380
}
374381
}
375382

383+
376384
return {"response": {"result": True, "reason": ""}}
377385

378386
async def _commandHmac(self, session: Session, command: bytes, attempt: int = 1) -> RoutableMessage:
@@ -429,6 +437,7 @@ async def _commandAes(self, session: Session, command: bytes, attempt: int = 1)
429437
LOGGER.debug(f"Sending AES to domain {Domain.Name(session.domain)}")
430438

431439
aes_personalized = session.aes_gcm_personalized()
440+
flags = 1 << Flags.FLAG_ENCRYPT_RESPONSE
432441

433442
metadata = bytes([
434443
Tag.TAG_SIGNATURE_TYPE,
@@ -451,7 +460,7 @@ async def _commandAes(self, session: Session, command: bytes, attempt: int = 1)
451460
*struct.pack(">I", aes_personalized.counter),
452461
Tag.TAG_FLAGS,
453462
4,
454-
0,0,0,Flags.FLAG_ENCRYPT_RESPONSE,
463+
*struct.pack(">I", flags),
455464
Tag.TAG_END,
456465
])
457466

@@ -478,18 +487,28 @@ async def _commandAes(self, session: Session, command: bytes, attempt: int = 1)
478487
),
479488
AES_GCM_Personalized_data=aes_personalized,
480489
),
481-
flags=Flags.FLAG_ENCRYPT_RESPONSE,
490+
flags=flags,
482491
)
483492

484493

485494
async def _sendVehicleSecurity(self, command: UnsignedMessage) -> dict[str, Any]:
486495
"""Sign and send a message to Infotainment computer."""
487496
return await self._command(Domain.DOMAIN_VEHICLE_SECURITY, command.SerializeToString())
488497

498+
async def _getVehicleSecurity(self, command: UnsignedMessage) -> VehicleStatus:
499+
"""Sign and send a message to Infotainment computer."""
500+
reply = await self._command(Domain.DOMAIN_VEHICLE_SECURITY, command.SerializeToString())
501+
return reply["response"]
502+
489503
async def _sendInfotainment(self, command: Action) -> dict[str, Any]:
490504
"""Sign and send a message to Infotainment computer."""
491505
return await self._command(Domain.DOMAIN_INFOTAINMENT, command.SerializeToString())
492506

507+
async def _getInfotainment(self, command: Action) -> VehicleData:
508+
"""Sign and send a message to Infotainment computer."""
509+
reply = await self._command(Domain.DOMAIN_INFOTAINMENT, command.SerializeToString())
510+
return reply["response"]
511+
493512
async def handshakeVehicleSecurity(self) -> None:
494513
"""Perform a handshake with the vehicle security domain."""
495514
await self._handshake(Domain.DOMAIN_VEHICLE_SECURITY)

0 commit comments

Comments
 (0)