2323from openleadr import errors
2424from datetime import datetime , timezone , timedelta
2525import os
26+ from signxml .algorithms import SignatureMethod
27+ from cryptography .hazmat .primitives import serialization
28+ from cryptography .hazmat .primitives .asymmetric import rsa , dsa , ec , ed25519 , ed448
2629
2730from openleadr import utils
2831from .preflight import preflight_message
2932
3033import logging
3134logger = logging .getLogger ('openleadr' )
3235
33- SIGNER = XMLSigner (method = methods .detached ,
34- c14n_algorithm = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" )
35- SIGNER .namespaces ['oadr' ] = "http://openadr.org/oadr-2.0b/2012/07"
3636VERIFIER = XMLVerifier ()
3737
3838XML_SCHEMA_LOCATION = os .path .join (os .path .dirname (__file__ ), 'schema' , 'oadr_20b.xsd' )
@@ -62,6 +62,45 @@ def parse_message(data):
6262 return message_type , message_payload
6363
6464
65+ def load_private_key (key_data , passphrase = None ):
66+ """
67+ Load the key based on key data. Supports .pem and .der keys.
68+
69+ Returns a private key object.
70+ """
71+ passphrase_bytes = passphrase .encode () if passphrase else None
72+ try :
73+ key = serialization .load_pem_private_key (key_data , passphrase_bytes )
74+ except ValueError :
75+ try :
76+ key = serialization .load_der_private_key (key_data , passphrase_bytes )
77+ except ValueError :
78+ logger .warning ("Could not load key: unknown key file format." )
79+ return key
80+
81+
82+ def get_signature_algorithm_from_private_key (key_data , passphrase = None , default_algorithm = "rsa-sha256" ):
83+ """
84+ Derive a signature algorithm based on the private key type. Returns a string that can be used to lookup
85+ a signature algorithm by fragment. Algorithms are chosen based on NIST recommendations.
86+
87+ SignXML supports only RSA-, DSA- and EC-based signature methods. As XMLSigner uses RSA_SHA256 as default
88+ signature algorithm, a fragment that results in this algorithm is returned for unsupported keys.
89+ """
90+ key = load_private_key (key_data , passphrase )
91+ if isinstance (key , rsa .RSAPrivateKey ):
92+ return "rsa-sha256"
93+ elif isinstance (key , dsa .DSAPrivateKey ):
94+ return "dsa-sha256"
95+ elif isinstance (key , ec .EllipticCurvePrivateKey ):
96+ return "ecdsa-sha256"
97+ elif isinstance (key , ed25519 .Ed25519PrivateKey ):
98+ logger .warning ("ED25519 keys are not supported" )
99+ elif isinstance (key , ed448 .Ed448PrivateKey ):
100+ logger .warning ("ED448 keys are not supported" )
101+ return default_algorithm
102+
103+
65104def create_message (message_type , cert = None , key = None , passphrase = None , disable_signature = False , ** message_payload ):
66105 """
67106 Create and optionally sign an OpenADR message. Returns an XML string.
@@ -72,6 +111,12 @@ def create_message(message_type, cert=None, key=None, passphrase=None, disable_s
72111 envelope = TEMPLATES .get_template ('oadrPayload.xml' )
73112 if cert and key and not disable_signature :
74113 tree = etree .fromstring (signed_object )
114+ SIGNER = XMLSigner (
115+ method = methods .detached ,
116+ c14n_algorithm = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
117+ )
118+ SIGNER .namespaces ['oadr' ] = "http://openadr.org/oadr-2.0b/2012/07"
119+ SIGNER .sign_alg = SignatureMethod .from_fragment (get_signature_algorithm_from_private_key (key , passphrase ))
75120 signature_tree = SIGNER .sign (tree ,
76121 key = key ,
77122 cert = cert ,
@@ -83,7 +128,8 @@ def create_message(message_type, cert=None, key=None, passphrase=None, disable_s
83128 signature = None
84129 msg = envelope .render (template = f'{ message_type } ' ,
85130 signature = signature ,
86- signed_object = signed_object )
131+ signed_object = signed_object
132+ )
87133 logger .debug (f"Created message: { msg } " )
88134 return msg
89135
0 commit comments