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

feat: plug and charge authorization, basic happy path #83

Merged

Conversation

danielgordon-switch-ev
Copy link
Contributor

This implements the basic flow for Plug and Charge authorization.

Still to be done:

  • handling the Ongoing case (managing around the short timeouts required by 15118)
  • could probably use more testing of the technicalities of certificate handling
  • accommodating better for other authorization cases (probably not for this PR)

Note that this prepares the certificate chain and hash data in the form in which OCPP will require them.

I've left some outstanding questions in TODOs for the time being as well -- let's either resolve or make issues for these.

# TODO Check if EMAID has correct syntax
verify_certs(
leaf_cert, sub_ca_certs, self._mobility_operator_root_cert_path()
)
Copy link

@HugoJP1 HugoJP1 Jul 18, 2022

Choose a reason for hiding this comment

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

Will add a ticket noting that the verification needs to be more intelligent than this, to figure out which root cert to use.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@HugoJP1 Have you done this, or shall I pick it back up?

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 think this falls under #94 .

Namespace.ISO_V2_MSG_DEF,
)
else:
# TODO: handle ONGOING case, and REJECTED more fully
Copy link

Choose a reason for hiding this comment

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

Will create a ticket for this.

Copy link
Contributor

@tropxy tropxy Jul 21, 2022

Choose a reason for hiding this comment

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

isnt this part quite straight forward? in case of Ongoing or Accepted we go to the Authorization state.
in case of rejected then we can answer with an Error code.

we also need to save the state of the authorization in the session here, I believe, because if the auth is successful in the PaymentDetailsReq, then we wont need to request it in the Authorization state and we can move forward. Unless, the ocpp client holds the authorization response and answers immediately if we do another request for the same eMAID.

if the answer "accepted" was not yet received, then we will keep requesting until the timeout (60s)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Attached this to the existing #54

iso15118/shared/security.py Outdated Show resolved Hide resolved
iso15118/secc/states/iso15118_2_states.py Show resolved Hide resolved
iso15118/shared/security.py Outdated Show resolved Hide resolved
iso15118/secc/states/iso15118_2_states.py Show resolved Hide resolved
iso15118/secc/states/iso15118_2_states.py Outdated Show resolved Hide resolved
Namespace.ISO_V2_MSG_DEF,
)
else:
# TODO: handle ONGOING case, and REJECTED more fully
Copy link
Contributor

@tropxy tropxy Jul 21, 2022

Choose a reason for hiding this comment

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

isnt this part quite straight forward? in case of Ongoing or Accepted we go to the Authorization state.
in case of rejected then we can answer with an Error code.

we also need to save the state of the authorization in the session here, I believe, because if the auth is successful in the PaymentDetailsReq, then we wont need to request it in the Authorization state and we can move forward. Unless, the ocpp client holds the authorization response and answers immediately if we do another request for the same eMAID.

if the answer "accepted" was not yet received, then we will keep requesting until the timeout (60s)

)
authorization_result = await self.comm_session.evse_controller.is_authorized(
id_token=id_token,
id_token_type=self.comm_session.selected_auth_option,
Copy link
Contributor

Choose a reason for hiding this comment

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

just again a note about the auth option may not be what we want

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned, have resolved this.

Copy link
Contributor

@tropxy tropxy left a comment

Choose a reason for hiding this comment

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

added some suggestions and food for thought

"issuer_key_hash": public_key_hasher.finalize(),
"serial_number": serial_number,
# TODO: Populate with a real-world verification server
"responder_url": "https://www.example.com/",
Copy link
Contributor

@tropxy tropxy Jul 26, 2022

Choose a reason for hiding this comment

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

I did some research and this is what we need to do to extract the responder_url

from cryptography.x509.oid import ExtensionOID, AuthorityInformationAccessOID

def get_ocsp_url(cert):
    auth_inf_access = cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS).value
    ocsps = [access_descriptor for access_descriptor in auth_inf_access if access_descriptor.access_method == AuthorityInformationAccessOID.OCSP]
    if not ocsps:
        raise Exception(f'no ocsp server entry in Authority Information Access extension field')
    return ocsps[0].access_location.value

If the Authority Information Access is not found, the method raises a ExtensionNotFound error

References:
https://stackoverflow.com/questions/64436317/how-to-check-ocsp-client-certificate-revocation-using-python-requests-library

https://cryptography.io/en/latest/x509/reference/?highlight=AuthorityInformationAccessOID.CA_ISSUERS#cryptography.x509.Extensions.get_extension_for_oid

https://cryptography.io/en/latest/x509/reference/?highlight=AuthorityInformationAccessOID.OCSP#cryptography.x509.AccessDescription

https://cryptography.io/en/latest/x509/reference/?highlight=AuthorityInformationAccessOID.OCSP#cryptography.x509.oid.AuthorityInformationAccessOID

The information here: https://www.notion.so/switchev/Certificates-944733c8b468493a9e0eeace3b772d6a#3a1c89b988f2432c81c603ab370f879e

can be used to get the ocsp_url using openssl and validate the python code

"issuer_key_hash": public_key_hasher.finalize(),
"serial_number": serial_number,
# TODO: Populate with a real-world verification server
"responder_url": "https://www.example.com/",
Copy link
Contributor

@tropxy tropxy Jul 26, 2022

Choose a reason for hiding this comment

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

Also, to get the hashed info for the OCSP request we can do this:

from cryptography.x509.ocsp import OCSPRequestBuilder

def get_oscp_request(cert: bytes, issuer_cert: bytes):
    """
    cert: The certificate in DER format
    issuer_cert: The certificate used to sign the `cert` certificate, also called the issuer certificate
    """
    cert = load_der_x509_certificate(cert)
    issuer_cert = load_der_x509_certificate(issuer_cert)
    builder = OCSPRequestBuilder().add_certificate(cert, issuer_cert, cert.signature_hash_algorithm)
    return builder.build()

ocsp_request = get_oscp_request(certificate, issuer_cert)

The data can then be accessed through the returned object, for example
ocsp_request.issuer_key_hash.

Not sure if this is better as we need to load the issuer certificate and we may not have the root of the MO, but makes it easier then to extract the needed data. at least it can be used to compare some results...

Reference:
https://cryptography.io/en/latest/x509/ocsp/#cryptography.x509.ocsp.OCSPRequest

@danielgordon-switch-ev
Copy link
Contributor Author

For some reason I can't respond directly to some of your comments.

The OCSP URL stuff looks good to me -- thanks for digging this up.

On handling authorization, I'd prefer to do this as a separate follow-up PR -- may do some minor refactoring relating to is_authorized as well.

Agreed that we may not want to use the OCSPRequest object if it requires us to have the root certificate.

iso15118/secc/states/iso15118_2_states.py Show resolved Hide resolved
iso15118/shared/security.py Outdated Show resolved Hide resolved
iso15118/secc/states/iso15118_2_states.py Show resolved Hide resolved
iso15118/secc/states/iso15118_2_states.py Outdated Show resolved Hide resolved
@danielgordon-switch-ev danielgordon-switch-ev force-pushed the feat/plug-and-charge-authorization/1443 branch from 599778e to 0ea5a7e Compare July 28, 2022 19:50
except (ExtensionNotFound, OCSPServerNotFoundError):
# TODO: This may just result in failure down the road.
# Should we let this fail on these exceptions, or is there
# another way to try to get a responder_url?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tropxy What do you think -- is it an unrecoverable failure if the certificate does not provide a URL for an OCSP server, or is there something else we could try in this case?

Copy link
Contributor

Choose a reason for hiding this comment

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

As far as I can tell, if we cant obtain the OCSP url, then we cant do the check, then we should answer back that the check failed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#96

except (ExtensionNotFound, OCSPServerNotFoundError):
# TODO: This may just result in failure down the road.
# Should we let this fail on these exceptions, or is there
# another way to try to get a responder_url?
Copy link
Contributor

Choose a reason for hiding this comment

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

As far as I can tell, if we cant obtain the OCSP url, then we cant do the check, then we should answer back that the check failed

iso15118/shared/security.py Outdated Show resolved Hide resolved
Comment on lines 907 to 908
root_cert_path = self._mobility_operator_root_cert_path()
verify_certs(leaf_cert, sub_ca_certs, load_cert(root_cert_path))
Copy link
Contributor

Choose a reason for hiding this comment

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

we dont have a guarantee that the MO root will be available in the station. So, we may actually call a evse_controller to get a True/False result for the question "verify contract certificate chain?"

Copy link
Contributor Author

@danielgordon-switch-ev danielgordon-switch-ev Aug 4, 2022

Choose a reason for hiding this comment

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

#94 for logic when the MO root cannot be obtained.
#97 for doing our best to obtain it.

iso15118/shared/security.py Outdated Show resolved Hide resolved
@danielgordon-switch-ev danielgordon-switch-ev force-pushed the feat/plug-and-charge-authorization/1443 branch from deab046 to 1b8ed1a Compare August 3, 2022 20:20
@danielgordon-switch-ev danielgordon-switch-ev force-pushed the feat/plug-and-charge-authorization/1443 branch from 1b8ed1a to 003a108 Compare August 4, 2022 16:42
evse_timestamp=time.time(),
)

# TODO: Should the next message really be Authorization? If so why?
Copy link
Contributor

Choose a reason for hiding this comment

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

If the Auth status is Accepted or Ongoing, yes, the next step shall be the Authorization class, as that is the message the EV will send after reception of the PaymentDetailsRes

iso15118/shared/security.py Show resolved Hide resolved
Copy link
Contributor

@tropxy tropxy left a comment

Choose a reason for hiding this comment

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

LGTM

@danielgordon-switch-ev danielgordon-switch-ev merged commit fba00db into master Aug 4, 2022
@danielgordon-switch-ev danielgordon-switch-ev deleted the feat/plug-and-charge-authorization/1443 branch August 4, 2022 17:48
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.

3 participants