Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] SRP POC #519

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft

[DO NOT MERGE] SRP POC #519

wants to merge 3 commits into from

Conversation

FRSTR
Copy link
Contributor

@FRSTR FRSTR commented Feb 7, 2022

No description provided.

@FRSTR
Copy link
Contributor Author

FRSTR commented Feb 7, 2022

This new spr.py file only needs renault-api installed in the system (I tested it in WSL), so it does not depend on other files in this repository.
At the top of the file there are 4 vars with user IDs, vin and pincode to fill.
Sometimes I saw this flow fails earlier, but in general 90% of the time it should try to complete all the steps. The final step does shoudl show that the command was CANCELLED (because srp auth fails now) - there is no logic to check this.

There are also stacktraces in the output logs, this is due to issue 512

spr.py Outdated
Comment on lines 38 to 44
strExec = (
'renault-api --debug http get "/commerce/v1/accounts/'
+ AccountId
+ "/kamereon/kca/car-adapter/v1/cars/"
+ vin
+ '/res-state?country=RU"'
)
Copy link
Collaborator

@epenet epenet Feb 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be good to add to the recognised endpoints with some sample fixtures.

{
  "data":{
    "type":"ResState",
    "id":"X7LHSRP1234567890",
    "attributes":{
      "details":"Stopped, ready for RES",
      "code":"10"
    }
  }
}

res might mean "Remote engine start"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I think this would be useful to add to HA, independantly of the SRP methods

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't even fully know what it does, maybe it just checks if engine is ready to start

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to work like this:

  1. GET res-state => checks the state of the remote engine. I wonder what other codes are available.
  2. POST actions-srp-sets => starts authentication
  3. GET notifications => wait for authentication confirmation (actionType = COMMAND_RESPONSE)
  4. GET notifications => wait for authentication confirmation (actionType = SRP_SALT_REQUEST)
  5. POST actions-engine-start => send the actual start-engine command

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the number of notification responses could be different, sometimes you get the list of all jsons at once, sometimes it takes 3 notifications (especially if you don't do delays) to provide SRP_SALT_REQUEST. In the traces I had from the app, notifications with the result after step 5 took 6 notifications to provide the result.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, res-state is just the state of the engine from what I see now:

  1. this returned when the engine is started and running
    {"data":{"type":"ResState","id":"X7LABCD1234567890","attributes":{"details":"Running","code":"42"}}}
  2. this returned when the engine is stopped
    {'data': {'type': 'ResState', 'id': 'X7LABCD1234567890', 'attributes': {'details': 'Stopped, ready for RES', 'code': '10'}}}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great - do you want to add these to RenaultVehicle class (ie. replicate the work from Lock-Status)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can do that tomorrow.

Comment on lines +73 to +82
stream = os.popen(strExec)
# output = stream.read()
time.sleep(5)

output = " ".join(stream)
output = output.replace('"', "")
output = output.replace("'", "")
output = output.replace(" ", "")

ParseList1 = output.split("SrpSets,id:")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is very difficult to understand.
You should use JSON parsing utilities instead to convert it into an object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was never supposed to be clean code, this was a dirty script according to my non-existing skills in python and time to spent of course.
I just replaced some of the quotation marks there to simplify the split() actions at some point, though it is not really required.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I know - I am just comparing your script with the traces you sent me previously and your parsing/splitting makes it hard to reconcile.
Maybe you need to adjust your commands to use renault-api --log --json http get/post
This should create log files (easier to share) and return json (easier to parse)

Copy link
Contributor Author

@FRSTR FRSTR Feb 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so then I can just describe what is being parsed then:

  1. First step is GET res-state endpoint. Again not sure what it does, maybe resets the flow, maybe checks that engine is not started already. We don't parse there anything.
  2. At this step we start SRP auth. We generate so called "A" for srp based on SRP username and password, and send "A" in POST srp-sets. The length of this thing is 512 and servers checks this fact. For the POST to srp-sets, the server replies with the Notification ID, where we will then find the server result. So we need to parse the "id" in the bodyof POST response.
  3. Now we GET kmr notification endpoint with the id we received above. If the body response has Salt and LoginB, we try to parse them, if not - try the same GET kmr request again after a delay. Those Salt and LoginB are the Server's response for SRP, these are used to calculate the final SRP key (M in the code, but also called "srp" in the following http request)
  4. When we calculated M we can send it in the POST to engine-start. The request body has the command and M value inside the "srp" field. This is where the server can completely verify srp auth. Just like at the step 2, we don't get the server response immediately, but instead we get the "id" of the notification to check.
  5. We request the notification with that new id and see the response if it fails or not. If we don't find the command response, we try again after a delay.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to work out all that, but it is still much harder to read than if you used json :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, come on! I basicaly googled every other line to write in python, don't make me do any of that :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't worry - I won't make you do that.
I'm still trying to do some tests on my side.

@jumpjack
Copy link

jumpjack commented Apr 28, 2022

I try writing down the procedure, is this correct? But which is the raw url for "GET kmr notification"? And is this the javascript equivalent for SPR authentication?


">>>" = sent
"<<<" = received


>>>
/commerce/v1/accounts/AAAAAAAAAAAAAA/kamereon/kca/car-adapter/v1/cars/VVVVVVVVVVVVVV/actions/srp-sets?country=COUNTRY


{
  "data":
  {
    "type":"SrpSets",
    "attributes":
    {
      "a" : A.hex().upper(),  // A = second result from usr.start_authentication() or from SRP.generate_a() in [this repo](https://github.com/mitchellrj/kamereon-python/blob/146904802301aa0b0008e2bdb3a88ed10ff50acf/kamereon/kamereon.py)
      "i" : UserID            // = personId
    }
  }
}

<<< Notification ID
>>> GET kmr notification endpoint (?)
<<< Salt and LoginB (used to calculate the final SRP key "M")
>>> Send request, with "M" in body, inside the "srp" field
<<< "id" of the notification to check
>>> Request the notification with that new id and see the response if it fails or not


A couple of SRP-related functions from here:

  def initiate_srp(self):
        (salt, verifier) = SRP.enroll(self.user_id, self.vin)
        resp = self.session.oauth.post(
            '{}v1/cars/{}/actions/srp-initiates'.format(self.session.settings['car_adapter_base_url'], self.vin),
            data=json.dumps({
                "data": {
                    "type": "SrpInitiates",
                    "attributes": {
                        "s": salt,
                        "i": self.user_id,
                        "v": verifier
                    }
                }
            }),
            headers={'Content-Type': 'application/vnd.api+json'}
        )
        body = resp.json()
        if 'errors' in body:
            raise ValueError(body['errors'])
        return body

    def validate_srp(self):
        a = SRP.generate_a()
        resp = self.session.oauth.post(
            '{}v1/cars/{}/actions/srp-sets'.format(self.session.settings['car_adapter_base_url'], self.vin),
            data=json.dumps({
                "data": {
                    "type": "SrpSets",
                    "attributes": {
                        "i": self.user_id,
                        "a": a
                    }
                }
            }),
            headers={'Content-Type': 'application/vnd.api+json'}
        )
        body = resp.json()
        if 'errors' in body:
            raise ValueError(body['errors'])
        return body

----------------------

  def enroll(cls, user_id, vin):
        salt, verifier = '0'*20, 'ABCDEFGH'*64  /// <<--- error? This is supposed to be an hex string...
        # salt = 20 hex chars, verifier = 512 hex chars
        return (salt, verifier)

@FRSTR
Copy link
Contributor Author

FRSTR commented Apr 28, 2022

The URL for notifications is https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/persons/<UserID>/notifications/kmr?&notificationId=<NotificationID>

And from what I remember you can skip the last part after "kmr" and it will give you all your notifications in a single response.

Regarding SRP implementation, there are different ones exist, and several exist for python. And it is known that the SRP protocol spec is not 100% strict for the implementation details, plus there were different versions and flavors on the market. This was never a big industry standard, though Apple used it with its own implementation and some other companies. So in the end, different SRP implementations will not work with each other just because they have minor differences in the way they calculate crypto payloads. I actually stumbled upon a github community that tried to fix this issue and list compatible ones, but they did not went too far and still had some questions on the spec interpretation.

I don't think I can comment on your code, as I already forgot some of the details. But you can refer to my SRP sample - that's best of my previous understanding anyway.

@jumpjack
Copy link

I don't understand if this pull request is currently working and if SRP authentication is requested also for lock-status or just for engine start.

@epenet
Copy link
Collaborator

epenet commented Apr 28, 2022

SRP is only required for remote engine start, and this PR provides reproducible steps for SRP authentication... but it doesn't work.

Lock-status is working fine already, but is not supported by all vehicles.

@jumpjack
Copy link

Did you already try with the ID provided in notifications, kmrUserId ?

actionType: "COMMAND_RESPONSE"
commandResponse: {status: 'CREATED'}
commandType: "REFRESH_BATTERY_STATUS"
kmrUserId: "xxxx"
notifDate: "2022-04-29T08:45:31.624261"
notificationId: "kmrUserId"
personId: "xxxx"
vin: "xxxx"

@FRSTR
Copy link
Contributor Author

FRSTR commented Apr 29, 2022

I think yes.
You can actually run this sample with your own ids/variables. While the final response from the server is an error due to SRP validation, the entire flow works kinda correctly in this sample.
Your car probably does not have the Engine Start endpoint, but instead you can try to lock/unlock the car as it also requires the entire SRP routine.

@jumpjack
Copy link

I don't know how to install/use a not-accpeted PR.

@FRSTR
Copy link
Contributor Author

FRSTR commented Apr 29, 2022

You just need this one file, download it at https://github.com/hacf-fr/renault-api/blob/2c003ef0634b8b0f1c69621c400abdb6cd98aecd/spr.py

To run it you also need renault-api installed. I don't remember exactly, but maybe you also need this srp library installed.
Then you just run it with the python and it shows the progress in the log output.

@jumpjack
Copy link

Which values did you test as "pin"?

@FRSTR
Copy link
Contributor Author

FRSTR commented Apr 29, 2022

That's my pin code from the Renault app. This pin code is required by the app when starting engine or locking the doors.

@epenet
Copy link
Collaborator

epenet commented Apr 29, 2022

@jumpjack, I think that if the Renault app doesn't allow you to start the engine or to lock the doors, then you won't be able to test it with any integration...

@jumpjack
Copy link

Received an "unimplemented" error rather than a "forbidden" error would be already a good result, there are a couple of dozens of restricted endpoints to test!

  1.     value = "v1/cars/{vin}/actions/data-reset""
    
  2.     value = "v1/cars/{vin}/health-status"
    
  3.     value = "v1/cars/{vin}/pressure"
    
  4.     value = "v1/cars/{vin}/settings/area_restrictions/{id}"
    
  5.     value = "v1/cars/{vin}/settings/speed_restrictions/{id}"
    
  6.     value = "v1/cars/{vin}/settings/curfew_restrictions/{id}"
    
  7.     value = "v1/cars/{vin}/settings/gfc_restrictions"
    
  8.     value = "v1/cars/{vin}/settings/area_restrictions"
    
  9.     value = "v1/cars/{vin}/settings/speed_restrictions"
    
  10.     value = "v1/cars/{vin}/settings/curfew_restrictions"
    
  11.     value = "v1/cars/{vin}/trip-history"
    
  12.     value = "v1/cars/{vin}/energy-unit-cost"
    
  13.     value = "v1/cars/{vin}/actions/srp-initiates"
    
  14.     value = "v1/cars/{vin}/actions/srp-sets"
    
  15.     value = "v1/cars/{vin}/actions/stolen-vehicle-tracking-block-unblock"
    
  16.     value = "v1/cars/{vin}/actions/engine-start"
    
  17.     value = "v1/cars/{vin}/lock-status"
    
  18.     value = "v1/cars/{vin}/actions/horn-lights"
    
  19.     value = "v1/cars/{vin}/actions/lock-unlock"
    
  20.     value = "v1/cars/{vin}/actions/open-close"
    
  21.     value = "v1/cars/{vin}/actions/stolen-vehicle-tracking"
    

https://gitlab.com/tobiaswkjeldsen/dartnissanconnect/-/issues/1

@Quentame Quentame changed the title srp draft poc [DO NOT MERGE] SRP POC Oct 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants