package de.otto.hmac.authentication;
import org.springframework.mock.web.MockHttpServletRequest;
import org.testng.annotations.Test;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import static de.otto.hmac.authentication.WrappedRequest.wrap;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringStartsWith.startsWith;
@Test
public class RequestSigningUtilTest {
/*
<METHOD>\n
<DATUM>\n
<REQUEST-URI>\n
MD5(<BODY>)
*/
@Test
public void shouldBeginSignatureBaseWithHttpMethod() throws IOException {
HttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
String signatureBase = RequestSigningUtil.createSignatureBase(wrap(request));
assertThat(signatureBase, startsWith("PUT"));
}
@Test
public void shouldAddDateToSignatureBase() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
String nowAsString = formattedDateOfNow();
request.addHeader("x-hmac-auth-date", nowAsString);
String signatureBase = RequestSigningUtil.createSignatureBase(wrap(request));
assertThat(signatureBase, startsWith("PUT\n" + nowAsString));
}
@Test
public void shouldAddRequestUriToSignatureBase() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
String nowAsString = formattedDateOfNow();
request.addHeader("x-hmac-auth-date", nowAsString);
String signatureBase = RequestSigningUtil.createSignatureBase(wrap(request));
assertThat(signatureBase, startsWith("PUT\n" + nowAsString + "\nsome/URI"));
}
@Test
public void shouldAddBodyAsMd5ToSignatureBase() throws NoSuchAlgorithmException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
String nowAsString = formattedDateOfNow();
request.addHeader("x-hmac-auth-date", nowAsString);
String body = "{ \"key\": \"value\"}";
request.setContent(body.getBytes());
String signatureBase = RequestSigningUtil.createSignatureBase(wrap(request));
assertThat(signatureBase, startsWith("PUT\n" + nowAsString + "\nsome/URI\nb4cfb98da7379e9f280c3b3d8686005d"));
}
@Test
public void shouldEncryptRequestInfo() throws NoSuchAlgorithmException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
request.addHeader("x-hmac-auth-date", formattedDateOfNow());
request.setContent("{ \"key\": \"value\"}".getBytes());
String encrypted = RequestSigningUtil.createRequestSignature(wrap(request), "secretKey");
String encrypted2 = RequestSigningUtil.createRequestSignature(wrap(request), "secretKey");
assertThat(encrypted, is(encrypted2));
assertThat(encrypted, not(startsWith("PUT\n2012.12.24-00:00:00\nsome/URI\n")));
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldThrowExceptionWhenSecretKeyIsNull() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
request.addHeader("x-hmac-auth-date", formattedDateOfNow());
request.setContent("{ \"key\": \"value\"}".getBytes());
RequestSigningUtil.createRequestSignature(wrap(request), null);
}
@Test
public void shouldAcceptCorrectlySignedRequestIfRequestTimeStampIsValid() throws NoSuchAlgorithmException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
request.addHeader("x-hmac-auth-date", formattedDateOfNow());
request.setContent("{ \"key\": \"value\"}".getBytes());
String requestSignatur = RequestSigningUtil.createRequestSignature(wrap(request), "secretKey");
request.addHeader("x-hmac-auth-signature", "username:" + requestSignatur);
boolean valid = RequestSigningUtil.checkRequest(wrap(request), "secretKey", Clock.systemUTC());
assertThat(valid, is(true));
}
@Test
public void shouldRejectCorrectlySignedRequestIfRequestTimeStampIsTooMuchInTheFuture() throws NoSuchAlgorithmException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
Instant timeStampToMuchInFuture = Instant.now().plus(Duration.ofMillis(500000L));
request.addHeader("x-p13n-date", timeStampToMuchInFuture);
request.setContent("{ \"key\": \"value\"}".getBytes());
String requestSignatur = RequestSigningUtil.createRequestSignature(wrap(request), "secretKey");
request.addHeader("x-hmac-auth-signature", "username:" + requestSignatur);
boolean valid = RequestSigningUtil.hasValidRequestTimeStamp(wrap(request), Clock.systemUTC());
assertThat(valid, is(false));
}
@Test
public void shouldRejectCorrectlySignedRequestIfRequestTimeStampIsExpired() throws NoSuchAlgorithmException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
Instant timeStampExpired = Instant.now().minus(Duration.ofMillis(500000L));
request.addHeader("x-p13n-date", timeStampExpired.toString());
request.setContent("{ \"key\": \"value\"}".getBytes());
String requestSignatur = RequestSigningUtil.createRequestSignature(wrap(request), "secretKey");
request.addHeader("x-hmac-auth-signature", "username:" + requestSignatur);
boolean valid = RequestSigningUtil.hasValidRequestTimeStamp(wrap(request), Clock.systemUTC());
assertThat(valid, is(false));
}
@Test
public void shouldRejectIncorrectlySignedRequest() throws NoSuchAlgorithmException, IOException {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
request.addHeader("x-hmac-auth-date", formattedDateOfNow());
request.addHeader("x-hmac-auth-signature", "username:FalscheSignatur=");
request.setContent("{ \"key\": \"value\"}".getBytes());
boolean valid = RequestSigningUtil.checkRequest(wrap(request), "secretKey", Clock.systemUTC());
assertThat(valid, is(false));
}
@Test
public void shouldParseDateTimeStringInISO8601FormatWithTimeZone() throws IOException {
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "some/URI");
request.addHeader("x-hmac-auth-date", "2016-11-07T16:44:49.1+01:00");
request.setContent("{ \"key\": \"value\"}".getBytes());
Clock fixedClock = Clock.fixed(Instant.parse("2016-11-07T15:44:49.1Z"), ZoneOffset.UTC);
boolean validRequestTimeStamp = RequestSigningUtil.hasValidRequestTimeStamp(wrap(request), fixedClock);
assertThat(validRequestTimeStamp, is(true));
}
private static String formattedDateOfNow() {
return Instant.now().toString();
}
}