MessageHandler.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /*******************************************************************************
  2. * The MIT License (MIT)
  3. *
  4. * Copyright (c) 2015-207 V2G Clarity (Dr.-Ing. Marc Mültin)
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. *******************************************************************************/
  24. package com.v2gclarity.risev2g.shared.messageHandling;
  25. import java.security.interfaces.ECPrivateKey;
  26. import java.util.Arrays;
  27. import java.util.HashMap;
  28. import javax.xml.bind.JAXBContext;
  29. import javax.xml.bind.JAXBElement;
  30. import javax.xml.bind.JAXBException;
  31. import javax.xml.bind.ValidationEvent;
  32. import javax.xml.bind.ValidationEventHandler;
  33. import javax.xml.namespace.QName;
  34. import org.apache.logging.log4j.LogManager;
  35. import org.apache.logging.log4j.Logger;
  36. import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
  37. import com.v2gclarity.risev2g.shared.exiCodec.EXIficientCodec;
  38. import com.v2gclarity.risev2g.shared.exiCodec.ExiCodec;
  39. import com.v2gclarity.risev2g.shared.exiCodec.OpenEXICodec;
  40. import com.v2gclarity.risev2g.shared.misc.V2GTPMessage;
  41. import com.v2gclarity.risev2g.shared.utils.ByteUtils;
  42. import com.v2gclarity.risev2g.shared.utils.MiscUtils;
  43. import com.v2gclarity.risev2g.shared.utils.SecurityUtils;
  44. import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolReq;
  45. import com.v2gclarity.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
  46. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
  47. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyType;
  48. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.MessageHeaderType;
  49. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.NotificationType;
  50. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
  51. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureValueType;
  52. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignedInfoType;
  53. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
  54. public final class MessageHandler {
  55. // -- BEGIN: SINGLETON DEFINITION --
  56. /*
  57. * Eager instantiation of the singleton, since a MessageHandler is always needed.
  58. * The JVM creates the unique instance when the class is loaded and before any thread tries to
  59. * access the instance variable -> thread safe.
  60. */
  61. private static final MessageHandler instance = new MessageHandler();
  62. public static MessageHandler getInstance() {
  63. return instance;
  64. }
  65. // -- END: SINGLETON DEFINITION --
  66. private Logger logger = LogManager.getLogger(this.getClass().getSimpleName());
  67. private ExiCodec exiCodec;
  68. private JAXBContext jaxbContext;
  69. /**
  70. * This constructor is used by V2GCommunicationSessionHandlerEVCC and -SECC
  71. */
  72. public MessageHandler() {
  73. // Choose which implementation of an EXI codec to use in the respective properties file
  74. String exiCodecChoice = (String) MiscUtils.getPropertyValue("exi.codec");
  75. if (exiCodecChoice.equals("open_exi")) setExiCodec(OpenEXICodec.getInstance());
  76. else setExiCodec(EXIficientCodec.getInstance());
  77. // Setting the JAXBContext is a very time-consuming action and should only be done once during startup
  78. setJaxbContext(SupportedAppProtocolReq.class, SupportedAppProtocolRes.class, V2GMessage.class);
  79. }
  80. public synchronized boolean isV2GTPMessageValid(V2GTPMessage v2gTpMessage) {
  81. if (isVersionAndInversionFieldCorrect(v2gTpMessage) &&
  82. isPayloadTypeCorrect(v2gTpMessage) &&
  83. isPayloadLengthCorrect(v2gTpMessage))
  84. return true;
  85. return false;
  86. }
  87. public synchronized boolean isVersionAndInversionFieldCorrect(V2GTPMessage v2gTpMessage) {
  88. if (v2gTpMessage.getProtocolVersion() != GlobalValues.V2GTP_VERSION_1_IS.getByteValue()) {
  89. getLogger().error("Protocol version (" + ByteUtils.toStringFromByte(v2gTpMessage.getProtocolVersion()) +
  90. ") is not supported!");
  91. return false;
  92. }
  93. if (v2gTpMessage.getInverseProtocolVersion() != (byte) (v2gTpMessage.getProtocolVersion() ^ 0xFF)) {
  94. getLogger().error("Inverse protocol version (" + ByteUtils.toStringFromByte(v2gTpMessage.getInverseProtocolVersion()) +
  95. ") does not match the inverse value of the protocol version (" + v2gTpMessage.getProtocolVersion() + ")!");
  96. return false;
  97. }
  98. return true;
  99. }
  100. public synchronized boolean isPayloadTypeCorrect(V2GTPMessage v2gTpMessage) {
  101. byte[] payloadType = v2gTpMessage.getPayloadType();
  102. if (Arrays.equals(payloadType, GlobalValues.V2GTP_PAYLOAD_TYPE_EXI_ENCODED_V2G_MESSAGE.getByteArrayValue()) ||
  103. Arrays.equals(payloadType, GlobalValues.V2GTP_PAYLOAD_TYPE_SDP_REQUEST_MESSAGE.getByteArrayValue()) ||
  104. Arrays.equals(payloadType, GlobalValues.V2GTP_PAYLOAD_TYPE_SDP_RESPONSE_MESSAGE.getByteArrayValue())) return true;
  105. getLogger().error("Payload type not supported! Proposed payload type: " + ByteUtils.toStringFromByteArray(v2gTpMessage.getPayloadType()));
  106. return false;
  107. }
  108. public synchronized boolean isPayloadLengthCorrect(V2GTPMessage v2gTpMessage) {
  109. if (ByteUtils.toLongFromByteArray(v2gTpMessage.getPayloadLength()) > GlobalValues.V2GTP_HEADER_MAX_PAYLOAD_LENGTH.getLongValue() ||
  110. ByteUtils.toLongFromByteArray(v2gTpMessage.getPayloadLength()) < 0L) {
  111. getLogger().error("Payload length (" + ByteUtils.toLongFromByteArray(v2gTpMessage.getPayloadLength()) +
  112. " bytes) not supported! Must be between 0 and " +
  113. GlobalValues.V2GTP_HEADER_MAX_PAYLOAD_LENGTH.getLongValue() + " bytes");
  114. return false;
  115. }
  116. int payLoadLengthField = ByteUtils.toIntFromByteArray(v2gTpMessage.getPayloadLength());
  117. if (v2gTpMessage.getPayload().length != payLoadLengthField) {
  118. getLogger().error("Length of payload (" + v2gTpMessage.getPayload().length + " bytes) does not match value of " +
  119. "field payloadLength (" + payLoadLengthField + " bytes)");
  120. return false;
  121. }
  122. return true;
  123. }
  124. public synchronized Object suppAppProtocolMsgToExi(Object suppAppProtocolObject) {
  125. return getExiCodec().encodeEXI(suppAppProtocolObject, GlobalValues.SCHEMA_PATH_APP_PROTOCOL.toString());
  126. }
  127. public synchronized Object v2gMsgToExi(Object jaxbObject) {
  128. byte[] encodedEXI = getExiCodec().encodeEXI(jaxbObject, GlobalValues.SCHEMA_PATH_MSG_DEF.toString());
  129. return encodedEXI;
  130. }
  131. public synchronized Object exiToSuppAppProtocolMsg(byte[] exiEncodedMessage) {
  132. return getExiCodec().decodeEXI(exiEncodedMessage, true);
  133. }
  134. public synchronized Object exiToV2gMsg(byte[] exiEncodedMessage) {
  135. return getExiCodec().decodeEXI(exiEncodedMessage, false);
  136. }
  137. public synchronized V2GMessage getV2GMessage(
  138. byte[] sessionID,
  139. HashMap<String, byte[]> xmlSignatureRefElements,
  140. ECPrivateKey signaturePrivateKey,
  141. JAXBElement<? extends BodyBaseType> v2gMessageInstance) {
  142. return getV2GMessage(sessionID, null, xmlSignatureRefElements, signaturePrivateKey, v2gMessageInstance);
  143. }
  144. public synchronized V2GMessage getV2GMessage(
  145. byte[] sessionID,
  146. NotificationType notification,
  147. HashMap<String, byte[]> xmlSignatureRefElements,
  148. ECPrivateKey signaturePrivateKey,
  149. JAXBElement<? extends BodyBaseType> v2gMessageInstance) {
  150. BodyType body = new BodyType();
  151. body.setBodyElement(v2gMessageInstance);
  152. V2GMessage v2gMessage = new V2GMessage();
  153. v2gMessage.setHeader(getHeader(sessionID, notification, v2gMessageInstance, xmlSignatureRefElements, signaturePrivateKey));
  154. v2gMessage.setBody(body);
  155. return v2gMessage;
  156. }
  157. private synchronized MessageHeaderType getHeader(
  158. byte[] sessionID,
  159. NotificationType notification,
  160. JAXBElement<? extends BodyBaseType> v2gMessageInstance,
  161. HashMap<String, byte[]> xmlSignatureRefElements,
  162. ECPrivateKey signaturePrivateKey) {
  163. MessageHeaderType header = new MessageHeaderType();
  164. header.setSessionID(sessionID);
  165. header.setNotification(notification);
  166. if (xmlSignatureRefElements != null && xmlSignatureRefElements.size() != 0) {
  167. SignedInfoType signedInfo = SecurityUtils.getSignedInfo(xmlSignatureRefElements);
  168. byte[] signature = SecurityUtils.signSignedInfoElement(
  169. getExiCodec().getExiEncodedSignedInfo(getJaxbElement(signedInfo)),
  170. signaturePrivateKey
  171. );
  172. SignatureValueType signatureValue = new SignatureValueType();
  173. signatureValue.setValue(signature);
  174. SignatureType xmlSignature = new SignatureType();
  175. xmlSignature.setSignatureValue(signatureValue);
  176. xmlSignature.setSignedInfo(signedInfo);
  177. header.setSignature(xmlSignature);
  178. }
  179. return header;
  180. }
  181. /**
  182. * Creates an XML element from the given object which may be a complete message or just a field of a
  183. * message. In case of XML signature generation, for some messages certain fields need to be signed
  184. * instead of the complete message.
  185. *
  186. * Suppressed unchecked warning, previously used a type-safe version such as new
  187. * JAXBElement<SessionStopReqType>(new QName ... ) but this seems to work as well
  188. * (I don't know how to infer the type correctly)
  189. *
  190. * @param messageOrField The message or field for which a digest is to be generated
  191. * @return The JAXBElement of the provided message or field
  192. */
  193. @SuppressWarnings({ "rawtypes", "unchecked" })
  194. public synchronized JAXBElement getJaxbElement(Object messageOrField) {
  195. String messageName = messageOrField.getClass().getSimpleName().replace("Type", "");
  196. String namespace = "";
  197. JAXBElement jaxbElement = null;
  198. if (messageOrField instanceof SignedInfoType) {
  199. namespace = GlobalValues.V2G_CI_XMLDSIG_NAMESPACE.toString();
  200. } else {
  201. namespace = GlobalValues.V2G_CI_MSG_BODY_NAMESPACE.toString();
  202. /*
  203. * We need to set the localPart of the QName object for the CertificateInstallationRes/CertificateUpdateRes parameters
  204. * correctly. The messageOrField object's class name cannot be taken directly as it differs from what should be the
  205. * XML element name.
  206. */
  207. switch (messageName) {
  208. case "CertificateChain":
  209. messageName = "ContractSignatureCertChain";
  210. break;
  211. case "DiffieHellmanPublickey":
  212. messageName = "DHpublickey";
  213. break;
  214. case "EMAID":
  215. messageName = "eMAID";
  216. break;
  217. case "ContractSignatureEncryptedPrivateKey":
  218. messageName = "ContractSignatureEncryptedPrivateKey";
  219. break;
  220. case "SalesTariff": // SalesTariff is not defined in MsgBody XSD schema, but MsgDataTypes XSD schema
  221. namespace = GlobalValues.V2G_CI_MSG_DATATYPES_NAMESPACE.toString();
  222. break;
  223. default:
  224. break;
  225. }
  226. }
  227. jaxbElement = new JAXBElement(new QName(namespace, messageName),
  228. messageOrField.getClass(),
  229. messageOrField);
  230. return jaxbElement;
  231. }
  232. public Logger getLogger() {
  233. return logger;
  234. }
  235. public ExiCodec getExiCodec() {
  236. return exiCodec;
  237. }
  238. public void setExiCodec(ExiCodec exiCodec) {
  239. this.exiCodec = exiCodec;
  240. SecurityUtils.setExiCodec(exiCodec);
  241. }
  242. public JAXBContext getJaxbContext() {
  243. return jaxbContext;
  244. }
  245. private void setJaxbContext(JAXBContext jaxbContext) {
  246. this.jaxbContext = jaxbContext;
  247. }
  248. public synchronized void setJaxbContext(Class... classesToBeBound) {
  249. try {
  250. setJaxbContext(JAXBContext.newInstance(classesToBeBound));
  251. // Every time we set the JAXBContext, we need to also set the marshaller and unmarshaller for EXICodec
  252. getExiCodec().setUnmarshaller(getJaxbContext().createUnmarshaller());
  253. getExiCodec().setMarshaller(getJaxbContext().createMarshaller());
  254. /*
  255. * JAXB by default silently ignores errors. Adding this code to throw an exception if
  256. * something goes wrong.
  257. */
  258. getExiCodec().getUnmarshaller().setEventHandler(
  259. new ValidationEventHandler() {
  260. @Override
  261. public boolean handleEvent(ValidationEvent event ) {
  262. throw new RuntimeException(event.getMessage(),
  263. event.getLinkedException());
  264. }
  265. });
  266. } catch (JAXBException e) {
  267. getLogger().error("A JAXBException occurred while trying to set JAXB context", e);
  268. }
  269. }
  270. }