/* ==================================================================
* HMACHandlerTest.java - 25/03/2017 8:24:04 AM
*
* Copyright 2007-2017 SolarNetwork.net Dev Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ==================================================================
*/
package net.solarnetwork.external.ocpp.client.test;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import org.apache.commons.codec.binary.Base64;
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.FileCopyUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import ocpp.v15.support.HMACHandler;
/**
* Test cases for the {@link HMACHandler} class.
*
* @author matt
* @version 1.0
*/
public class HMACHandlerTest {
private static final String TEST_SECRET = "foobar";
private final Logger log = LoggerFactory.getLogger(getClass());
private SOAPMessage getSOAPMessageForResource(String resource) throws SOAPException, IOException {
MessageFactory factory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
InputStream in = getClass().getResourceAsStream(resource);
return factory.createMessage(new MimeHeaders(), in);
}
private Mac createHMAC() {
Mac m;
try {
m = Mac.getInstance("HmacSHA256");
SecretKeySpec key = new SecretKeySpec(TEST_SECRET.getBytes(), "HmacSHA256");
m.init(key);
} catch ( NoSuchAlgorithmException e ) {
throw new RuntimeException(e);
} catch ( InvalidKeyException e ) {
throw new RuntimeException(e);
}
return m;
}
private String hash(String data) {
Mac mac = createHMAC();
byte[] hash = mac.doFinal(data.getBytes());
try {
return new String(Base64.encodeBase64(hash, false), "US-ASCII");
} catch ( UnsupportedEncodingException e ) {
// should never get here
throw new RuntimeException(e);
}
}
private DateFormat getTimestampDateFormat() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
return sdf;
}
private String getTimestampString(long time) {
DateFormat sdf = getTimestampDateFormat();
return sdf.format(new Date(time));
}
private String digestForResource(String resource, Map<String, String> props) throws IOException {
String txt = FileCopyUtils
.copyToString(new InputStreamReader(getClass().getResourceAsStream(resource), "UTF-8"));
if ( props != null ) {
for ( Map.Entry<String, String> me : props.entrySet() ) {
txt = txt.replaceAll("\\$\\{" + me.getKey() + "\\}", me.getValue());
}
}
log.debug("Test hash data: \n{}", txt);
return hash(txt);
}
@Test
public void generateHeader() throws Exception {
SOAPMessage msg = getSOAPMessageForResource("soap-msg-01.xml");
SOAPMessageContext ctx = EasyMock.createMock(SOAPMessageContext.class);
EasyMock.expect(ctx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY)).andReturn(Boolean.TRUE);
EasyMock.expect(ctx.getMessage()).andReturn(msg).anyTimes();
EasyMock.replay(ctx);
HMACHandler handler = new HMACHandler();
handler.setSecret(TEST_SECRET);
boolean result = handler.handleMessage(ctx);
Assert.assertTrue(result);
NodeList list = msg.getSOAPHeader().getElementsByTagNameNS(
HMACHandler.SN_WS_AUTH.getNamespaceURI(), HMACHandler.SN_WS_AUTH.getLocalPart());
Assert.assertEquals("Should have generated the Authorization header", 1, list.getLength());
Element authEl = (Element) list.item(0);
String generatedTs = authEl.getAttribute("ts");
String generatedDigest = authEl.getTextContent();
String expectedDigest = digestForResource("soap-msg-01.auth.txt",
Collections.singletonMap("date", generatedTs));
Assert.assertEquals("Authorization digest", expectedDigest, generatedDigest);
EasyMock.verify(ctx);
}
}