package com.ausregistry.jtoolkit2.se.tmch;
import static org.hamcrest.CoreMatchers.is;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.whenNew;
import javax.xml.bind.DatatypeConverter;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.PublicKey;
import java.security.cert.*;
import java.util.Arrays;
import java.util.Date;
import com.ausregistry.jtoolkit2.se.tmch.exception.*;
import com.ausregistry.jtoolkit2.test.infrastructure.ReflectionUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@RunWith(PowerMockRunner.class)
@PrepareForTest({TmchValidatingParser.class, XMLSignatureFactory.class})
public class TmchValidatingParserTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
private TmchValidatingParser tmchXMLUtil;
@Mock private CRL mockCrl;
@Mock private CertificateFactory mockCertificateFactory;
@Mock private CertPath mockCertPath;
@Mock private CertPathValidator mockCertPathValidator;
@Mock private X509Certificate mockSmdCertificate;
@Mock private XPath mockXpath;
@Mock private XMLSignatureFactory mockXmlSignatureFactory;
@Mock private XMLSignature mockXmlSignature;
@Before
public void setUp() throws Exception {
mockStatic(CertificateFactory.class);
mockStatic(CertPathValidator.class);
mockStatic(XPathFactory.class);
mockStatic(XMLSignatureFactory.class);
InputStream mockInputStream = mock(InputStream.class);
InputStream mockInputStreamTwo = mock(InputStream.class);
Certificate mockTmchCertificate = mock(X509Certificate.class);
Node mockNode = mock(Node.class);
PublicKey mockKey = mock(PublicKey.class);
BufferedReader mockReader = mock(BufferedReader.class);
Document mockDocument = mock(Document.class);
Element mockElement = mock(Element.class);
when(CertificateFactory.getInstance("X.509")).thenReturn(mockCertificateFactory);
when(mockCertificateFactory.generateCRL(mockInputStream)).thenReturn(mockCrl);
when(mockCertificateFactory.generateCertificate(mockInputStreamTwo)).thenReturn(mockTmchCertificate);
when(CertPathValidator.getInstance("PKIX")).thenReturn(mockCertPathValidator);
when(mockCertificateFactory.generateCertPath(Arrays.asList(mockSmdCertificate))).thenReturn(mockCertPath);
when(XMLSignatureFactory.getInstance()).thenReturn(mockXmlSignatureFactory);
whenNew(BufferedReader.class).withArguments(any(InputStreamReader.class))
.thenReturn(mockReader);
tmchXMLUtil = new TmchValidatingParser(mockInputStream, mockInputStream, mockInputStreamTwo);
ReflectionUtils.setPropertyOnInstance(tmchXMLUtil, "xPath", mockXpath);
when(mockXpath.evaluate(eq("/smd:signedMark/ds:Signature"), any(Document.class), eq(XPathConstants.NODE)))
.thenReturn(mockNode);
when(mockNode.getOwnerDocument()).thenReturn(mockDocument);
when(mockDocument.getDocumentElement()).thenReturn(mockElement);
when(mockXmlSignatureFactory.unmarshalXMLSignature(any(DOMValidateContext.class))).thenReturn(mockXmlSignature);
when(mockXmlSignature.validate(any(DOMValidateContext.class))).thenReturn(true);
when(mockCertificateFactory.generateCertificate(any(InputStream.class))).thenReturn(mockSmdCertificate);
when(mockSmdCertificate.getPublicKey()).thenReturn(mockKey);
}
@Test
public void shouldThrowExceptionWhenSmdCannotBeParsed() throws Exception {
thrown.expect(InvalidSignedMarkDataException.class);
tmchXMLUtil.validateAndParseEncodedSignedMarkData(new ByteArrayInputStream("".getBytes()));
}
@Test
public void shouldThrowExceptionWithRightMessageWhenCertificateIsRevoked() throws Exception {
String dummyEncodedSmd = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5k" +
"YWxvbmU9Im5vIj8+PHNpZ25lZE1hcms6c2lnbmVkTWFyayB4bWxuczpzaWduZWRNYXJr" +
"PSJ1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnNpZ25lZE1hcmstMS4wIiBpZD0ic2lnbmVk" +
"TWFyayI+PHNpZ25lZE1hcms6aWQ+MS0yPC9zaWduZWRNYXJrOmlkPjwvc2lnbmVkTWFy" +
"azpzaWduZWRNYXJrPg==";
TmchCertificateRevokedException mockException = mock(TmchCertificateRevokedException.class);
when(mockCrl.isRevoked(mockSmdCertificate)).thenReturn(true);
whenNew(TmchCertificateRevokedException.class).withArguments(mockSmdCertificate)
.thenReturn(mockException);
thrown.expect(is(mockException));
tmchXMLUtil.validateAndParseEncodedSignedMarkData(new ByteArrayInputStream(dummyEncodedSmd.getBytes()));
}
@Test
public void shouldThrowExeceptionWhenCertificateIsInvalid() throws Exception {
String dummyEncodedSmdWithId = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5k" +
"YWxvbmU9Im5vIj8+PHNpZ25lZE1hcms6c2lnbmVkTWFyayB4bWxuczpzaWduZWRNYXJr" +
"PSJ1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnNpZ25lZE1hcmstMS4wIiBpZD0ic2lnbmVk" +
"TWFyayI+PHNpZ25lZE1hcms6aWQ+MS0yPC9zaWduZWRNYXJrOmlkPjwvc2lnbmVkTWFy" +
"azpzaWduZWRNYXJrPg==";
CertPathValidatorException certPathValidatorException = mock(CertPathValidatorException.class);
when(mockCertPathValidator.validate(eq(mockCertPath), any(PKIXParameters.class)))
.thenThrow(certPathValidatorException);
TmchInvalidCertificateException mockException = mock(TmchInvalidCertificateException.class);
whenNew(TmchInvalidCertificateException.class).withArguments(mockSmdCertificate, certPathValidatorException)
.thenReturn(mockException);
thrown.expect(is(mockException));
tmchXMLUtil.validateAndParseEncodedSignedMarkData(new ByteArrayInputStream(dummyEncodedSmdWithId.getBytes()));
}
@Test
public void shouldThrowInvalidSignedMarkDataExceptionIfUnmarshallingSignatureFails() throws Exception {
String dummyEncodedSmdWithId = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5k" +
"YWxvbmU9Im5vIj8+PHNpZ25lZE1hcms6c2lnbmVkTWFyayB4bWxuczpzaWduZWRNYXJr" +
"PSJ1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnNpZ25lZE1hcmstMS4wIiBpZD0ic2lnbmVk" +
"TWFyayI+PHNpZ25lZE1hcms6aWQ+MS0yPC9zaWduZWRNYXJrOmlkPjwvc2lnbmVkTWFy" +
"azpzaWduZWRNYXJrPg==";
MarshalException mockMarshalException = mock(MarshalException.class);
when(mockXmlSignatureFactory.unmarshalXMLSignature(any(DOMValidateContext.class)))
.thenThrow(mockMarshalException);
InvalidSignedMarkDataException mockException = mock(InvalidSignedMarkDataException.class);
whenNew(InvalidSignedMarkDataException.class).withArguments(mockMarshalException).thenReturn(mockException);
thrown.expect(is(mockException));
tmchXMLUtil.validateAndParseEncodedSignedMarkData(new ByteArrayInputStream(dummyEncodedSmdWithId.getBytes()));
}
@Test
public void shouldThrowExceptionWhenXmlSignatureIsInvalid() throws Exception {
String dummyEncodedSmdWithId = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5k" +
"YWxvbmU9Im5vIj8+PHNpZ25lZE1hcms6c2lnbmVkTWFyayB4bWxuczpzaWduZWRNYXJr" +
"PSJ1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnNpZ25lZE1hcmstMS4wIiBpZD0ic2lnbmVk" +
"TWFyayI+PHNpZ25lZE1hcms6aWQ+MS0yPC9zaWduZWRNYXJrOmlkPjwvc2lnbmVkTWFy" +
"azpzaWduZWRNYXJrPg==";
when(mockXmlSignature.validate(any(DOMValidateContext.class))).thenReturn(false);
thrown.expect(SmdSignatureInvalidException.class);
tmchXMLUtil.validateAndParseEncodedSignedMarkData(new ByteArrayInputStream(dummyEncodedSmdWithId.getBytes()));
}
@Test
public void shouldThrowExceptionIfCertificateIsNotOfTypeX509() throws Exception {
String dummyEncodedSmdWithId = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5k" +
"YWxvbmU9Im5vIj8+PHNpZ25lZE1hcms6c2lnbmVkTWFyayB4bWxuczpzaWduZWRNYXJr" +
"PSJ1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnNpZ25lZE1hcmstMS4wIiBpZD0ic2lnbmVk" +
"TWFyayI+PHNpZ25lZE1hcms6aWQ+MS0yPC9zaWduZWRNYXJrOmlkPjwvc2lnbmVkTWFy" +
"azpzaWduZWRNYXJrPg==";
Certificate mockIncorrectTypeCert = mock(Certificate.class);
when(mockCertificateFactory.generateCertificate(any(InputStream.class))).thenReturn(mockIncorrectTypeCert);
thrown.expect(TmchCertificateInvalidTypeException.class);
thrown.expectMessage("The certificate used in SignedMarkData is not of valid type, expected a certificate " +
"of type X509Certificate, got a certificate of type: Certificate");
tmchXMLUtil.validateAndParseEncodedSignedMarkData(new ByteArrayInputStream(dummyEncodedSmdWithId.getBytes()));
}
@Test
public void shouldThrowExceptionIfSmdDoesNotHaveSignature() throws Exception {
String dummyEncodedSmdWithId = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5k" +
"YWxvbmU9Im5vIj8+PHNpZ25lZE1hcms6c2lnbmVkTWFyayB4bWxuczpzaWduZWRNYXJr" +
"PSJ1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnNpZ25lZE1hcmstMS4wIiBpZD0ic2lnbmVk" +
"TWFyayI+PHNpZ25lZE1hcms6aWQ+MS0yPC9zaWduZWRNYXJrOmlkPjwvc2lnbmVkTWFy" +
"azpzaWduZWRNYXJrPg==";
when(mockXpath.evaluate(eq("/smd:signedMark/ds:Signature"), any(Document.class), eq(XPathConstants.NODE)))
.thenReturn(null);
thrown.expect(SmdSignatureMissingException.class);
tmchXMLUtil.validateAndParseEncodedSignedMarkData(new ByteArrayInputStream(dummyEncodedSmdWithId.getBytes()));
}
@Test
public void shouldThrowExceptionForExpiredSmdWhenCallingTheValidationMethodWithoutADate() throws Exception {
String dummyEncodedSmdWithId = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5k" +
"YWxvbmU9Im5vIj8+PHNpZ25lZE1hcms6c2lnbmVkTWFyayB4bWxuczpzaWduZWRNYXJr" +
"PSJ1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnNpZ25lZE1hcmstMS4wIiBpZD0ic2lnbmVk" +
"TWFyayI+PHNpZ25lZE1hcms6aWQ+MS0yPC9zaWduZWRNYXJrOmlkPjwvc2lnbmVkTWFy" +
"azpzaWduZWRNYXJrPg==";
Date dateForValidation = DatatypeConverter.parseDate("2015-08-16T09:00:00.0Z").getTime();
whenNew(Date.class).withNoArguments().thenReturn(dateForValidation);
when(mockXpath.evaluate(eq("/smd:signedMark/smd:notBefore"), any(Document.class)))
.thenReturn("2009-08-16T09:00:00.0Z");
when(mockXpath.evaluate(eq("/smd:signedMark/smd:notAfter"), any(Document.class)))
.thenReturn("2013-08-16T09:00:00.0Z");
thrown.expect(ExpiredSignedMarkDataException.class);
tmchXMLUtil.validateAndParseEncodedSignedMarkData(new ByteArrayInputStream(dummyEncodedSmdWithId.getBytes()));
}
@Test
public void shouldThrowExceptionForNotYetValidSmdWhenCallingTheValidationMethodWithoutADate() throws Exception {
String dummyEncodedSmdWithId = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5k" +
"YWxvbmU9Im5vIj8+PHNpZ25lZE1hcms6c2lnbmVkTWFyayB4bWxuczpzaWduZWRNYXJr" +
"PSJ1cm46aWV0ZjpwYXJhbXM6eG1sOm5zOnNpZ25lZE1hcmstMS4wIiBpZD0ic2lnbmVk" +
"TWFyayI+PHNpZ25lZE1hcms6aWQ+MS0yPC9zaWduZWRNYXJrOmlkPjwvc2lnbmVkTWFy" +
"azpzaWduZWRNYXJrPg==";
Date dateForValidation = DatatypeConverter.parseDate("2008-08-16T09:00:00.0Z").getTime();
whenNew(Date.class).withNoArguments().thenReturn(dateForValidation);
when(mockXpath.evaluate(eq("/smd:signedMark/smd:notBefore"), any(Document.class)))
.thenReturn("2009-08-16T09:00:00.0Z");
when(mockXpath.evaluate(eq("/smd:signedMark/smd:notAfter"), any(Document.class)))
.thenReturn("2013-08-16T09:00:00.0Z");
thrown.expect(NotYetValidSignedMarkDataException.class);
tmchXMLUtil.validateAndParseEncodedSignedMarkData(new ByteArrayInputStream(dummyEncodedSmdWithId.getBytes()));
}
}