├── README.md └── gm-onstar-probe.py /README.md: -------------------------------------------------------------------------------- 1 | # gm-onstar-probe 2 | 3 | Remote Start Script for GM/OnStar vehicles. Needs credentials for a working OnStar account (working mobile iOS/Android app). Tested with 2014 Chevy Volt 4 | 5 | To get it working, populate the placeholder variables at the top (device_id, username, password, pin, vin_number) 6 | 7 | Please share credit if used elsewhere - reverse engineering this was non-trivial. 8 | 9 | NOTES: 10 | 11 | - I am not responsible if you get your OnStar account banned for whatever unforseen reason. 12 | - Nonce algorithm is loosely based on reverse engineered Android app for compatibility - no crypto lectures plz. 13 | - The oauth handshake seems to fail periodically for no obvious reason with an "invalid_request" response - this is not handled by the script and will cause it to crash. Perhaps due to a rate-limiting function. Be smart and don't use this frivolously. Will investigate, but no promises. 14 | - Works great with OpenHAB Exec binding - tell Alexa to remote start your car! 15 | 16 | 17 | Python stuff: 18 | - Tested with latest Python 2.7.x. 19 | - Libraries: jwcrypto, requests 20 | 21 | Usage: 22 | - pip install jwcrypto and requests 23 | - Edit variables on line 19-24 with your onstar creds 24 | - Execute with Python and watch for successful output stating remote start in progress. 25 | - If you get an error for invalid client id or bad key, check back here and get a new version. GM Probably revoked the keys from the old app. I usually catch this within a week or two and post updates. Happens once every 6-8 months. 26 | 27 | Changelog: 28 | 29 | June 24, 2018 - Updated with new client IDs and JWT signing key. Old ones are not working any more. 30 | 31 | Dec 6, 2018 - Updated with new client IDs and JWT signing key. Old ones are not working any more. 32 | 33 | Aug 5, 2019 - Updated with new client IDs and JWT signing key. Old ones are not working any more. Thanks @sradner13 34 | -------------------------------------------------------------------------------- /gm-onstar-probe.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import datetime 3 | import base64 4 | import json 5 | import uuid 6 | import hashlib 7 | from jwcrypto import jwt, jwk 8 | 9 | def timestamp(): 10 | return "%s.001Z" % (datetime.datetime.utcnow().replace(microsecond=0).isoformat()) 11 | 12 | def nonce(): 13 | random = hashlib.sha256(uuid.uuid4().hex).hexdigest() 14 | return base64.b32encode(random)[:26].lower() 15 | 16 | signing_key = jwk.JWK(**{'k': base64.b64encode("OxeOjX9FnVkJQQdSuwvR"), 'kty':'oct'}) 17 | client_id = "OMB_CVY_AND_3P0" 18 | 19 | #UNCOMMENT LINE BELOW AND ADD RANDOM UUIDV4 FROM https://www.uuidgenerator.net/ OR SIMILAR FOR THIS TO WORK 20 | #device_id = "INSERT_RANDOM_UUIDV4_HERE" 21 | username = "ONSTAR_USERNAME" 22 | password = "ONSTAR_PASSWORD" 23 | pin = "ONSTAR_PIN" 24 | vin_number = "VEHICLE_VIN_NUMBER" 25 | 26 | headers_auth = { 27 | 'Accept': 'application/json', 28 | 'Accept-Language': 'en', 29 | 'Content-Type': 'text/plain', 30 | 'Host': 'api.gm.com', 31 | 'Connection': 'close', 32 | 'Accept-Encoding': 'gzip, deflate', 33 | 'User-Agent': 'okhttp/3.9.0' 34 | } 35 | data_auth = { 36 | "client_id": client_id, 37 | "device_id": device_id, 38 | "grant_type": "password", 39 | "nonce": nonce(), 40 | "password": password, 41 | "scope": "onstar gmoc commerce msso", 42 | "timestamp": timestamp(), 43 | "username": username 44 | } 45 | 46 | token_auth = jwt.JWT(header={"alg": "HS256", "typ": "JWT"}, claims=data_auth) 47 | token_auth.make_signed_token(signing_key) 48 | token_auth_encoded = token_auth.serialize() 49 | 50 | print "REQUEST_AUTH %s" % (token_auth_encoded) 51 | response_auth = requests.post('https://api.gm.com/api/v1/oauth/token', headers=headers_auth, data=token_auth_encoded) 52 | print "RESPONSE_AUTH %d: %s" % (response_auth.status_code, response_auth.text) 53 | response_auth_jwt = jwt.JWT(key=signing_key, jwt=response_auth.text) 54 | response_auth_json = json.loads(response_auth_jwt.claims) 55 | oauth_token = response_auth_json["access_token"] 56 | 57 | headers_connect = { 58 | 'Accept': 'application/json', 59 | 'Authorization': 'Bearer %s' % (oauth_token), 60 | 'Accept-Language': 'en', 61 | 'Content-Type': 'application/json; charset=UTF-8', 62 | 'Host': 'api.gm.com', 63 | 'Connection': 'close', 64 | 'Accept-Encoding': 'gzip, deflate', 65 | 'User-Agent': 'okhttp/3.9.0', 66 | } 67 | data_connect = '{}' 68 | 69 | print "REQUEST_CONNECT!" 70 | response_connect = requests.post("https://api.gm.com/api/v1/account/vehicles/%s/commands/connect" % (vin_number), headers=headers_connect, data=data_connect) 71 | print "RESPONSE_CONNECT %d: %s" % (response_connect.status_code, response_connect.text) 72 | 73 | headers_upgrade = { 74 | 'Accept': 'application/json', 75 | 'Authorization': 'Bearer %s' % (oauth_token), 76 | 'Accept-Language': 'en', 77 | 'Content-Type': 'text/plain', 78 | 'Host': 'api.gm.com', 79 | 'Connection': 'close', 80 | 'Accept-Encoding': 'gzip, deflate', 81 | 'User-Agent': 'okhttp/3.9.0', 82 | } 83 | data_upgrade = { 84 | "client_id": client_id, 85 | "credential": pin, 86 | "credential_type": "PIN", 87 | "device_id": device_id, 88 | "grant_type": "password", 89 | "nonce": nonce(), 90 | "timestamp": timestamp(), 91 | } 92 | 93 | token_upgrade = jwt.JWT(header={"alg": "HS256", "typ": "JWT"}, claims=data_upgrade) 94 | token_upgrade.make_signed_token(signing_key) 95 | token_upgrade_encoded = token_upgrade.serialize() 96 | 97 | print "REQUEST_UPGRADE %s" % (token_upgrade_encoded) 98 | response_upgrade = requests.post('https://api.gm.com/api/v1/oauth/token/upgrade', headers=headers_upgrade, data=token_upgrade_encoded) 99 | print "RESPONSE_UPGRADE %d: %s" % (response_upgrade.status_code, response_upgrade.text) 100 | 101 | headers_remotestart = headers_connect 102 | data_remotestart = data_connect 103 | 104 | print "REQUEST_REMOTESTART!" 105 | response_remotestart = requests.post("https://api.gm.com/api/v1/account/vehicles/%s/commands/start" % (vin_number), headers=headers_remotestart, data=data_remotestart) 106 | print "RESPONSE_REMOTESTART %d: %s" % (response_remotestart.status_code, response_remotestart.text) 107 | --------------------------------------------------------------------------------