OpenLEADR / openleadr-python

Python library for OpenADR
https://openleadr.org/docs
Apache License 2.0
133 stars 51 forks source link

Clarification Needed on Message Signing over HTTP and HTTPS in OpenLEADR #163

Closed bbartling closed 4 months ago

bbartling commented 10 months ago

Hello,

I am encountering an issue with message signing in OpenLEADR and seek guidance on its implementation over HTTP.

I followed the instructions in the OpenLEADR documentation for message signing and successfully generated key.pem and cert.pem files. However, I ran into problems when testing with the one-minute client.py and server.py examples on localhost.

The primary issue seems to be the absence of a ca.crt file, which the system appears to expect but is not generated by the provided bash script in the documentation. The error log indicates an attempt to load a CA certificate for SSL/TLS verification (ssl_context.load_verify_locations(self.ca_file)), suggesting that the system is set up for SSL and x509 message signing.

I am currently trying to understand if OpenLEADR supports message signing over plain HTTP, or if it requires the TLS features inherently for secure communication. If I had a production VTN server with a dashboard running Let's Encrypt for TLS, but I am interested in knowing if message signing with just key.pem and cert.pem is viable for VEN-VTN communication via OpenLEADR with TLS being handled by Let's Encrypt.

For context, here's how I configured the client.py and server.py (both on localhost):

client.py:

client = OpenADRClient(
    ven_name='bens_ven',
    vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b',
    cert='cert.pem',
    key='key.pem',
    passphrase='my-passphrase',
)

server.py

server = OpenADRServer(
    vtn_id='BensVTN',
    http_cert='cert.pem',
    http_key='key.pem'
)

Upon running these, the client-side encounters the mentioned issue.

Any insights or tips on whether OpenLEADR message signing can operate over HTTP without a CA certificate, or if HTTPS is mandatory, would be greatly appreciated.

Thank you! @dyreby

bbartling commented 10 months ago

@stan-janssen any chance for a tip? Thanks!!

laurens-teirlynck commented 8 months ago

I stumbled on this issue because I'm trying to do something similar. I came to the conclusion that it should be possible to run the OpenADR server without HTTPS, but that the current implementation does not actually verify any messages if you run the server over HTTP.

If you run the server without any credentials (OpenADRClient(ven_name=..., vtn_url=...) and OpenADRServer(vtn_id='myvtn', ven_lookup=ven_lookup). With the logging level set to debug (openleadr.enable_default_logging(level=logging.DEBUG), you can see a message like the following:

<?xml version="1.0" encoding="utf-8"?>
<oadr:oadrPayload
    xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07">
    <oadr:oadrSignedObject
        xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" oadr:Id="oadrSignedObject">
        <oadr:oadrCreatePartyRegistration ei:schemaVersion="2.0b"
            xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110">
            <requestID
                xmlns="http://docs.oasis-open.org/ns/energyinterop/201110/payloads">7bec6c43-9525-4c5a-a5a0-ad4e541084e0
            </requestID>
            <oadr:oadrProfileName>2.0b</oadr:oadrProfileName>
            <oadr:oadrTransportName>simpleHttp</oadr:oadrTransportName>
            <oadr:oadrTransportAddress>None</oadr:oadrTransportAddress>
            <oadr:oadrReportOnly>false</oadr:oadrReportOnly>
            <oadr:oadrXmlSignature>false</oadr:oadrXmlSignature>
            <oadr:oadrVenName>ven_name</oadr:oadrVenName>
            <oadr:oadrHttpPullModel>true</oadr:oadrHttpPullModel>
        </oadr:oadrCreatePartyRegistration>
    </oadr:oadrSignedObject>
</oadr:oadrPayload>

If you create the client and an HTTP server like in the python script below, the public keys are included in the XML request body (shown below, abbreviated), which can be validated, but aren't. I can put any certificate file in the client without the client complaining at all.

server = OpenADRServer(
    vtn_id='myvtn',
    ven_lookup=ven_lookup,
    cert=server_certfile,
    key=server_keyfile,
    # server does not have an argument that allows it to validate incoming messages, only incoming HTTP traffic
)

client = OpenADRClient(
    ven_name='ven_name',
    vtn_url='http://localhost:8080',
    cert=client_certfile,
    key=client_keyfile,
    ca_file=server_ca_file,  # this does not seem to be used
)
<?xml version="1.0" encoding="utf-8"?>
<oadr:oadrPayload
    xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07">
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07">
        <ds:SignedInfo>
            ...
        </ds:SignedInfo>
        <ds:SignatureValue>XzKr...4Q==</ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>MII...xdM=</ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
        <ds:Object Id="prop">
            <ds:SignatureProperties>
                <ds:SignatureProperty Id="myid" Target="#mytarget">
                    <dsp:ReplayProtect
                        xmlns:dsp="http://openadr.org/oadr-2.0b/2012/07/xmldsig-properties">
                        <dsp:timestamp>2024-01-16T10:39:40.617950Z</dsp:timestamp>
                        <dsp:nonce>42d126a4a90e4a5cba6867f3bbbf2a0b</dsp:nonce>
                    </dsp:ReplayProtect>
                </ds:SignatureProperty>
            </ds:SignatureProperties>
        </ds:Object>
    </ds:Signature>
    <oadr:oadrSignedObject
        xmlns:oadr="http://openadr.org/oadr-2.0b/2012/07" oadr:Id="oadrSignedObject">
        <oadr:oadrCreatePartyRegistration ei:schemaVersion="2.0b"
            xmlns:ei="http://docs.oasis-open.org/ns/energyinterop/201110">
            <requestID xmlns="http://docs.oasis-open.org/ns/energyinterop/201110/payloads">2dac3875-5567-435c-b206-7b7dcdd0e3d4 </requestID>
            <oadr:oadrProfileName>2.0b</oadr:oadrProfileName>
            <oadr:oadrTransportName>simpleHttp</oadr:oadrTransportName>
            <oadr:oadrTransportAddress>None</oadr:oadrTransportAddress>
            <oadr:oadrReportOnly>false</oadr:oadrReportOnly>
            <oadr:oadrXmlSignature>false</oadr:oadrXmlSignature>
            <oadr:oadrVenName>ven_name</oadr:oadrVenName>
            <oadr:oadrHttpPullModel>true</oadr:oadrHttpPullModel>
        </oadr:oadrCreatePartyRegistration>
    </oadr:oadrSignedObject>
</oadr:oadrPayload>
bbartling commented 8 months ago

@laurens-teirlynck thanks for the tips... does that seem like a viable option if TLS is handled by "Let Encrypt" or something similar and then still do message signing in the way that you showed me? And the message signing is bi-directional right? Server verifies client and client verifies server...

laurens-teirlynck commented 8 months ago

I dug a bit deeper into the code. Signature validation happens https://github.com/OpenLEADR/openleadr-python/blob/main/openleadr/messaging.py#L101-L113. This function is called by the client and server, so it's indeed bi-directional.

However, the server calls this inside the authenticate_message function, but only if request.secure is True, which only happens if the server is running over HTTPS.

The client does seem to validate the message over HTTP: https://github.com/OpenLEADR/openleadr-python/blob/main/openleadr/client.py#L908

The messages are signed correctly, but the validation doesn't seem correct. The verify function invocation seems to just check that the XML message was signed by a valid certificate, not necessarily a certificate you trust. You would need to pass in a ca-cert.pem to the verify function (I think).