Java tutorial
/************************************************************************* * * * SignServer: The OpenSource Automated Signing Server * * * * This software is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or any later version. * * * * See terms of license at gnu.org. * * * *************************************************************************/ package org.signserver.module.xades.signer; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.security.KeyPair; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.Security; import java.security.cert.CertStore; import java.security.cert.Certificate; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.persistence.EntityManager; import javax.xml.crypto.dsig.SignatureMethod; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.keys.content.X509Data; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.jce.X509KeyUsage; import org.bouncycastle.jce.provider.BouncyCastleProvider; import static org.junit.Assert.*; import org.junit.BeforeClass; import org.junit.Test; import org.signserver.common.GenericSignRequest; import org.signserver.common.GenericSignResponse; import org.signserver.common.IllegalRequestException; import org.signserver.common.RequestContext; import org.signserver.common.SignServerException; import org.signserver.common.WorkerConfig; import org.signserver.module.xades.signer.MockedTimeStampTokenProvider.MockedTimeStampVerificationProvider; import org.signserver.server.CertificateClientCredential; import org.signserver.server.UsernamePasswordClientCredential; import org.signserver.server.WorkerContext; import org.signserver.server.cryptotokens.ICryptoToken; import org.signserver.test.utils.builders.CertBuilder; import org.signserver.test.utils.builders.CertExt; import org.signserver.test.utils.builders.CryptoUtils; import org.signserver.test.utils.mock.MockedCryptoToken; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXParseException; import xades4j.properties.AllDataObjsCommitmentTypeProperty; import xades4j.properties.QualifyingProperties; import xades4j.properties.SignedDataObjectProperty; import xades4j.properties.SignedProperties; import xades4j.properties.SignedSignatureProperty; import xades4j.properties.SignerRoleProperty; import xades4j.providers.CertificateValidationProvider; import xades4j.providers.impl.PKIXCertificateValidationProvider; import xades4j.verification.SignatureSpecificVerificationOptions; import xades4j.verification.XAdESVerificationResult; import xades4j.verification.XadesVerificationProfile; import xades4j.verification.XadesVerifier; /** * Unit tests for the XAdESSigner class. * * @author Markus Kils * @version $Id: XAdESSignerUnitTest.java 5739 2015-02-19 14:55:26Z netmackan $ */ public class XAdESSignerUnitTest { /** Logger for this class. */ private static final Logger LOG = Logger.getLogger(XAdESSignerUnitTest.class); private static MockedCryptoToken tokenRSA; private static MockedCryptoToken tokenDSA; private static MockedCryptoToken tokenECDSA; private static MockedCryptoToken tokenWithIntermediateCert; @BeforeClass public static void setUpClass() throws Exception { Security.addProvider(new BouncyCastleProvider()); tokenRSA = generateToken(KeyType.RSA); tokenDSA = generateToken(KeyType.DSA); tokenECDSA = generateToken(KeyType.ECDSA); tokenWithIntermediateCert = generateTokenWithIntermediateCert(); } private enum KeyType { RSA, DSA, ECDSA }; private static MockedCryptoToken generateToken(final KeyType keyType) throws Exception { final KeyPair signerKeyPair; final String signatureAlgorithm; switch (keyType) { case RSA: signerKeyPair = CryptoUtils.generateRSA(1024); signatureAlgorithm = "SHA1withRSA"; break; case DSA: signerKeyPair = CryptoUtils.generateDSA(1024); signatureAlgorithm = "SHA1withDSA"; break; case ECDSA: signerKeyPair = CryptoUtils.generateEcCurve("prime256v1"); signatureAlgorithm = "SHA1withECDSA"; break; default: throw new NoSuchAlgorithmException("Invalid key algorithm"); } final Certificate[] certChain = new Certificate[] { new JcaX509CertificateConverter().getCertificate(new CertBuilder().setSelfSignKeyPair(signerKeyPair) .setNotBefore(new Date(MockedTimeStampTokenProvider.TIMESTAMP)) .setSignatureAlgorithm(signatureAlgorithm).build()) }; final Certificate signerCertificate = certChain[0]; return new MockedCryptoToken(signerKeyPair.getPrivate(), signerKeyPair.getPublic(), signerCertificate, Arrays.asList(certChain), "BC"); } private static MockedCryptoToken generateTokenWithIntermediateCert() throws Exception { final JcaX509CertificateConverter conv = new JcaX509CertificateConverter(); final KeyPair rootcaKeyPair = CryptoUtils.generateRSA(1024); final X509CertificateHolder rootcaCert = new CertBuilder().setSelfSignKeyPair(rootcaKeyPair) .setSubject("CN=Root, O=XAdES Test, C=SE") .addExtension(new CertExt(Extension.keyUsage, false, new X509KeyUsage(X509KeyUsage.keyCertSign | X509KeyUsage.cRLSign))) .addExtension(new CertExt(Extension.basicConstraints, false, new BasicConstraints(true))).build(); final KeyPair subcaKeyPair = CryptoUtils.generateRSA(1024); final X509CertificateHolder subcaCert = new CertBuilder().setIssuerPrivateKey(rootcaKeyPair.getPrivate()) .setIssuer(rootcaCert.getSubject()).setSubjectPublicKey(subcaKeyPair.getPublic()) .setSubject("CN=Sub, O=XAdES Test, C=SE") .addExtension(new CertExt(Extension.keyUsage, false, new X509KeyUsage(X509KeyUsage.keyCertSign | X509KeyUsage.cRLSign))) .addExtension(new CertExt(Extension.basicConstraints, false, new BasicConstraints(true))).build(); final KeyPair signerKeyPair = CryptoUtils.generateRSA(1024); final X509CertificateHolder signerCert = new CertBuilder().setIssuerPrivateKey(subcaKeyPair.getPrivate()) .setIssuer(subcaCert.getSubject()).setSubjectPublicKey(signerKeyPair.getPublic()) .setSubject("CN=Signer 1, O=XAdES Test, C=SE") .addExtension(new CertExt(Extension.basicConstraints, false, new BasicConstraints(false))).build(); final List<Certificate> chain = Arrays.<Certificate>asList(conv.getCertificate(signerCert), conv.getCertificate(subcaCert), conv.getCertificate(rootcaCert)); return new MockedCryptoToken(signerKeyPair.getPrivate(), signerKeyPair.getPublic(), conv.getCertificate(signerCert), chain, "BC"); } /** * Test of init method, of class XAdESSigner. */ @Test public void testInit_ok() { LOG.info("init"); int signerId = 4711; WorkerConfig config = new WorkerConfig(); config.setProperty("XADESFORM", "T"); config.setProperty("TSA_URL", "http://example.com/?test=5"); config.setProperty("TSA_USERNAME", "username123"); config.setProperty("TSA_PASSWORD", "password123"); WorkerContext workerContext = null; EntityManager em = null; XAdESSigner instance = new MockedXAdESSigner(tokenRSA); instance.init(signerId, config, workerContext, em); XAdESSignerParameters param = instance.getParameters(); assertEquals("XADESFORM", "T", param.getXadesForm().name()); assertEquals("TSA_URL", "http://example.com/?test=5", param.getTsaParameters().getUrl()); assertEquals("TSA_USERNAME", "username123", param.getTsaParameters().getUsername()); assertEquals("TSA_PASSWORD", "password123", param.getTsaParameters().getPassword()); assertEquals(Collections.EMPTY_LIST, instance.getFatalErrors()); } /** * Test of init method with incorrect XADESFORM, of class XAdESSigner. */ @Test public void testInit_incorrectXADESFORM() { LOG.info("init"); int signerId = 4711; WorkerConfig config = new WorkerConfig(); config.setProperty("XADESFORM", "_NonExisting_"); config.setProperty("TSA_URL", "http://example.com/?test=5"); config.setProperty("TSA_USERNAME", "username123"); config.setProperty("TSA_PASSWORD", "password123"); WorkerContext workerContext = null; EntityManager em = null; XAdESSigner instance = new MockedXAdESSigner(tokenRSA); instance.init(signerId, config, workerContext, em); String errors = instance.getFatalErrors().toString(); assertTrue("error: " + errors, errors.contains("XADESFORM")); } /** * Test of init method with missing TSA_URL, of class XAdESSigner. */ @Test public void testInit_missingTSA_URL() { LOG.info("init"); int signerId = 4711; WorkerConfig config = new WorkerConfig(); config.setProperty("XADESFORM", "T"); // Not set: config.setProperty("TSA_URL", ... config.setProperty("TSA_USERNAME", "username123"); config.setProperty("TSA_PASSWORD", "password123"); WorkerContext workerContext = null; EntityManager em = null; XAdESSigner instance = new MockedXAdESSigner(tokenRSA); instance.init(signerId, config, workerContext, em); String errors = instance.getFatalErrors().toString(); assertTrue("error: " + errors, errors.contains("TSA_URL")); } /** * Test of init method default value for XADESFORM, of class XAdESSigner. */ @Test public void testInit_defaultXADESFORM() { LOG.info("init"); int signerId = 4711; WorkerConfig config = new WorkerConfig(); // Not set: config.setProperty("XADESFORM", "T"); WorkerContext workerContext = null; EntityManager em = null; XAdESSigner instance = new MockedXAdESSigner(tokenRSA); instance.init(signerId, config, workerContext, em); XAdESSignerParameters param = instance.getParameters(); assertEquals("XADESFORM", "BES", param.getXadesForm().name()); assertEquals(Collections.EMPTY_LIST, instance.getFatalErrors()); } /** * Internal method to perform a signing operation. * * @param token Crypto token to use * @param config Signer configuration to use for the test * @param toSign The XML document to sign * @param useCertCredential Generate credential for the request from the mocked signer certificate * @param username Username to generate a username/password credential in the request context, if null, no credential is passed * @return Verification result * @throws Exception */ private XAdESVerificationResult getVerificationResult(final MockedCryptoToken token, final WorkerConfig config, String toSign, final boolean useCertCredential, final String username) throws Exception { XAdESSigner instance = new MockedXAdESSigner(token); instance.init(4711, config, null, null); final RequestContext requestContext = new RequestContext(); requestContext.put(RequestContext.TRANSACTION_ID, "0000-100-1"); if (useCertCredential) { final CertificateClientCredential cred = new CertificateClientCredential("CN=foo", "123456789abc"); requestContext.put(RequestContext.CLIENT_CREDENTIAL, cred); } else if (username != null) { final UsernamePasswordClientCredential cred = new UsernamePasswordClientCredential(username, "foobar"); requestContext.put(RequestContext.CLIENT_CREDENTIAL, cred); } GenericSignRequest request = new GenericSignRequest(100, toSign.getBytes("UTF-8")); GenericSignResponse response = (GenericSignResponse) instance.processData(request, requestContext); byte[] data = response.getProcessedData(); final String signedXml = new String(data); LOG.debug("signedXml: " + signedXml); // Validation: setup CertStore certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters()); KeyStore trustAnchors = KeyStore.getInstance("JKS"); trustAnchors.load(null, "foo123".toCharArray()); List<Certificate> chain = token.getCertificateChain(ICryptoToken.PURPOSE_SIGN); System.out.println("trust anchor: " + chain.get(chain.size() - 1)); trustAnchors.setCertificateEntry("rootcert", chain.get(chain.size() - 1)); // Simply assume last cert in chain is the trust anchor CertificateValidationProvider certValidator = new PKIXCertificateValidationProvider(trustAnchors, false, certStore); XadesVerificationProfile p = new XadesVerificationProfile(certValidator); XadesVerifier verifier = p.newVerifier(); // Validation: parse final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); final DocumentBuilder builder = factory.newDocumentBuilder(); final Document doc = builder.parse(new ByteArrayInputStream(data)); Element node = doc.getDocumentElement(); XAdESVerificationResult r = verifier.verify(node, new SignatureSpecificVerificationOptions()); return r; } /** * Run a signing test with default form and varying commitment types. * * @param keyType Token key type to use * @param signatureAlgorithm Signature algorithm property value to test, if null use default * @param expectedSignatureAlgorithmUri Expected XML signature algorithm URI * @param commitmentTypesProperty COMMITMENT_TYPES property to test with * if null, doesn't set the property * @param expectedCommitmentTypeUris List of expected commitment type URIs * @param claimedRoleProperty Claimed role property to test, ff null, don't set property * @param claimedRoleFromUsername If set to true, include the CLAIMED_ROLE_FROM_USERNAME property * @param useCertCredential Pass in a (faked dummy) certificate credential in the request context to simulate the situation when the * client credential is not a user name * @param username Username to pass in via the request context, if null no username is passed in * @param expectedClaimedRole Expected claimed role in signed document, if null check that no claimed role is included * @throws Exception */ private void testProcessData_basicSigningInternal(final KeyType keyType, final String signatureAlgorithm, final String expectedSignatureAlgorithmUri, final String commitmentTypesProperty, final Collection<String> expectedCommitmentTypeUris, final String claimedRoleProperty, final boolean claimedRoleFromUsername, final boolean useCertCredential, final String username, final String expectedClaimedRole) throws Exception { LOG.info("processData"); final MockedCryptoToken token; switch (keyType) { case RSA: token = tokenRSA; break; case DSA: token = tokenDSA; break; case ECDSA: token = tokenECDSA; break; default: throw new NoSuchAlgorithmException("Unknown key algorithm"); } WorkerConfig config = new WorkerConfig(); if (commitmentTypesProperty != null) { config.setProperty("COMMITMENT_TYPES", commitmentTypesProperty); } if (signatureAlgorithm != null) { config.setProperty("SIGNATUREALGORITHM", signatureAlgorithm); } if (claimedRoleProperty != null) { config.setProperty("CLAIMED_ROLE", claimedRoleProperty); } if (claimedRoleFromUsername) { config.setProperty("CLAIMED_ROLE_FROM_USERNAME", "true"); } final XAdESVerificationResult r = getVerificationResult(token, config, "<testroot/>", useCertCredential, username); assertEquals("BES", r.getSignatureForm().name()); assertEquals("Unexpected signature algorithm in signature", expectedSignatureAlgorithmUri, r.getSignatureAlgorithmUri()); final QualifyingProperties qp = r.getQualifyingProperties(); final Set<String> foundUris = new HashSet<String>(); final SignedProperties sp = qp.getSignedProperties(); // check for ClaimedRole boolean foundExpectedRole = false; for (final SignedSignatureProperty sigProp : sp.getSigProps()) { LOG.debug("signed signature property: " + sigProp.getClass().getName() + ": " + sigProp.toString()); if (sigProp instanceof SignerRoleProperty) { final SignerRoleProperty role = (SignerRoleProperty) sigProp; for (final String claimedRole : role.getClaimedRoles()) { if (expectedClaimedRole == null) { fail("Should not contain a claimed role"); } else if (expectedClaimedRole.equals(claimedRole)) { foundExpectedRole = true; } else { fail("Unexpected claimed role: " + claimedRole); } } } } if (expectedClaimedRole != null) { assertTrue("Expected to find claimed role: " + claimedRoleProperty, foundExpectedRole); } for (final SignedDataObjectProperty signedObjProp : sp.getDataObjProps()) { LOG.debug("object property: " + signedObjProp.getClass().getName() + ": " + signedObjProp.toString()); if (signedObjProp instanceof AllDataObjsCommitmentTypeProperty) { final AllDataObjsCommitmentTypeProperty commitmentType = (AllDataObjsCommitmentTypeProperty) signedObjProp; final String uri = commitmentType.getUri(); LOG.debug("Found commitment type: " + uri); if (expectedCommitmentTypeUris.contains(uri)) { foundUris.add(uri); } else { fail("Unexpected commitment type: " + uri); } } } assertTrue("Should contain expected commitment types: " + expectedCommitmentTypeUris.toString(), foundUris.size() == expectedCommitmentTypeUris.size()); } /** * Test of processData method for basic signing, of class XAdESSigner. * Test that by default, no commitment types are included. * Also test that the default signature algorithm is SHA256withRSA for an RSA key. */ @Test public void testProcessData_basicSigning() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, null, XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, null, Collections.<String>emptyList(), null, false, false, null, null); } /** * Test with explicitly setting a single commitment type. * * @throws Exception */ @Test public void testProcessData_basicSigningSingleCommitmentType() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, null, XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, "PROOF_OF_ORIGIN", Collections.singletonList(AllDataObjsCommitmentTypeProperty.proofOfOrigin().getUri()), null, false, false, null, null); } /** * Test with explicitly setting multiple commitment types. * * @throws Exception */ @Test public void testProcessData_basicSigningMultipleCommitmentTypes() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, null, XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, "PROOF_OF_APPROVAL, PROOF_OF_ORIGIN", Arrays.asList(AllDataObjsCommitmentTypeProperty.proofOfApproval().getUri(), AllDataObjsCommitmentTypeProperty.proofOfOrigin().getUri()), null, false, false, null, null); } /** * Test with explictly setting the value NONE. * * @throws Exception */ @Test public void testProcessData_basicSigningCommitmentTypesNone() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, null, XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test signing with signature algorithm SHA1withRSA. * * @throws Exception */ @Test public void testProcessData_basicSigningRSASHA1() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, "SHA1withRSA", SignatureMethod.RSA_SHA1, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test signing with signature algorithm SHA256withRSA. * * @throws Exception */ @Test public void testProcessData_basicSigningRSASHA256() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, "SHA256withRSA", XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test signing with signature algorithm SHA384withRSA. * * @throws Exception */ @Test public void testProcessData_basicSigningRSASHA384() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, "SHA384withRSA", XAdESSigner.SIGNATURE_METHOD_RSA_SHA384, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test signing with signature algorithm SHA512withRSA. * * @throws Exception */ @Test public void testProcessData_basicSigningRSASHA512() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, "SHA512withRSA", XAdESSigner.SIGNATURE_METHOD_RSA_SHA512, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test signing with signature algorithm SHA1withDSA. * * @throws Exception */ @Test public void testProcessData_basicSigningDSASHA1() throws Exception { testProcessData_basicSigningInternal(KeyType.DSA, "SHA1withDSA", SignatureMethod.DSA_SHA1, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test signing with signature algorithm SHA1withECDSA. * * @throws Exception */ @Test public void testProcessData_basicSigningECDSASHA1() throws Exception { testProcessData_basicSigningInternal(KeyType.ECDSA, "SHA1withECDSA", XAdESSigner.SIGNATURE_METHOD_ECDSA_SHA1, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test signing with signature algorithm SHA256withECDSA. * * @throws Exception */ @Test public void testProcessData_basicSigningECDSASHA256() throws Exception { testProcessData_basicSigningInternal(KeyType.ECDSA, "SHA256withECDSA", XAdESSigner.SIGNATURE_METHOD_ECDSA_SHA256, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test signing with signature algorithm SHA384withECDSA. * * @throws Exception */ @Test public void testProcessData_basicSigningECDSASHA384() throws Exception { testProcessData_basicSigningInternal(KeyType.ECDSA, "SHA384withECDSA", XAdESSigner.SIGNATURE_METHOD_ECDSA_SHA384, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test signing with signature algorithm SHA512withECDSA. * * @throws Exception */ @Test public void testProcessData_basicSigningECDSASHA512() throws Exception { testProcessData_basicSigningInternal(KeyType.ECDSA, "SHA512withECDSA", XAdESSigner.SIGNATURE_METHOD_ECDSA_SHA512, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test that the default signature algorithm works when using DSA keys. * * @throws Exception */ @Test public void testProcessData_basicSigningDefaultDSA() throws Exception { testProcessData_basicSigningInternal(KeyType.DSA, null, SignatureMethod.DSA_SHA1, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test that the default signature algorithm works when using ECDSA keys. * * @throws Exception */ @Test public void testProcessData_basicSigningDefaultECDSA() throws Exception { testProcessData_basicSigningInternal(KeyType.ECDSA, null, XAdESSigner.SIGNATURE_METHOD_ECDSA_SHA1, "NONE", Collections.<String>emptyList(), null, false, false, null, null); } /** * Test using an illegal signature algorithm. * * @throws Exception */ @Test public void testProcessData_basicSigningWrongSigAlg() throws Exception { try { testProcessData_basicSigningInternal(KeyType.RSA, "bogus", XAdESSigner.SIGNATURE_METHOD_ECDSA_SHA1, "NONE", Collections.<String>emptyList(), null, false, false, null, null); fail("Should throw a SignServerException"); } catch (SignServerException e) { //NOPMD // expected } catch (Exception e) { fail("Unexpected exception thrown: " + e.getClass().getName()); } } /** * Test using a signature algorithm not matching the key. * * @throws Exception */ @Test public void testProcessData_basicSigningMismatchedSigAlg() throws Exception { try { testProcessData_basicSigningInternal(KeyType.RSA, "SHA1withDSA", XAdESSigner.SIGNATURE_METHOD_ECDSA_SHA1, "NONE", Collections.<String>emptyList(), null, false, false, null, null); fail("Should throw a SignServerException"); } catch (SignServerException e) { //NOPMD // expected } catch (Exception e) { fail("Unexpected exception thrown: " + e.getClass().getName()); } } /** * Test with an empty COMMITMENT_TYPES list. * * @throws Exception */ @Test public void testProcessData_basicSigningNoCommitmentType() throws Exception { LOG.info("testProcessData_basicSigningNoCommitmentType"); int signerId = 4711; WorkerConfig config = new WorkerConfig(); config.setProperty("COMMITMENT_TYPES", ""); WorkerContext workerContext = null; EntityManager em = null; XAdESSigner instance = new MockedXAdESSigner(tokenRSA); instance.init(signerId, config, workerContext, em); String errors = instance.getFatalErrors().toString(); assertTrue("error: " + errors, errors.contains("can not be empty")); } @Test public void testProcessData_basicSigningXAdESFormT() throws Exception { LOG.info("testProcessData_basicSigningXAdESFormT"); XAdESSigner instance = new MockedXAdESSigner(tokenRSA); WorkerConfig config = new WorkerConfig(); config.setProperty("XADESFORM", "T"); config.setProperty("TSA_URL", "http://example.com/?test=5"); instance.init(4711, config, null, null); instance.setTimeStampTokenProviderImplementation(MockedTimeStampTokenProvider.class); // reset mock counters MockedTimeStampTokenProvider.reset(); RequestContext requestContext = new RequestContext(); requestContext.put(RequestContext.TRANSACTION_ID, "0000-100-1"); GenericSignRequest request = new GenericSignRequest(100, "<test100/>".getBytes("UTF-8")); GenericSignResponse response = (GenericSignResponse) instance.processData(request, requestContext); byte[] data = response.getProcessedData(); final String signedXml = new String(data); LOG.debug("signedXml: " + signedXml); // Validation: setup CertStore certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(tokenRSA.getCertificateChain(ICryptoToken.PURPOSE_SIGN))); KeyStore trustAnchors = KeyStore.getInstance("JKS"); trustAnchors.load(null, "foo123".toCharArray()); trustAnchors.setCertificateEntry("cert", tokenRSA.getCertificate(ICryptoToken.PURPOSE_SIGN)); CertificateValidationProvider certValidator = new PKIXCertificateValidationProvider(trustAnchors, false, certStore); XadesVerificationProfile p = new XadesVerificationProfile(certValidator) .withTimeStampTokenVerifier(new MockedTimeStampVerificationProvider()); XadesVerifier verifier = p.newVerifier(); // Validation: parse final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); final DocumentBuilder builder = factory.newDocumentBuilder(); final Document doc = builder.parse(new ByteArrayInputStream(data)); Element node = doc.getDocumentElement(); XAdESVerificationResult r = verifier.verify(node, new SignatureSpecificVerificationOptions()); LOG.debug("signature form: " + r.getSignatureForm().name()); assertEquals("T", r.getSignatureForm().name()); assertEquals("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", r.getSignatureAlgorithmUri()); // check that a time stamp token was requested assertTrue("Should request a time stamp token", MockedTimeStampTokenProvider.hasRequestedTimeStampToken()); // check that the time stamp token was verified assertTrue("Should try to verify timestamp", MockedTimeStampTokenProvider.hasPerformedTimeStampVerification()); } /** * Test that setting an unknown commitment type results in a configuration error. * * @throws Exception */ @Test public void testUnknownCommitmentType() throws Exception { LOG.info("testUnknownCommitmentType"); int signerId = 4711; WorkerConfig config = new WorkerConfig(); config.setProperty("COMMITMENT_TYPES", "foobar"); WorkerContext workerContext = null; EntityManager em = null; XAdESSigner instance = new MockedXAdESSigner(tokenRSA); instance.init(signerId, config, workerContext, em); String errors = instance.getFatalErrors().toString(); assertTrue("error: " + errors, errors.contains("commitment type")); } /** * Test that setting an unknown commitment type in combination with * a valid one results in a configuration error. * * @throws Exception */ @Test public void testUnknownAndKnownCommitmentType() throws Exception { LOG.info("testUnknownCommitmentType"); int signerId = 4711; WorkerConfig config = new WorkerConfig(); config.setProperty("COMMITMENT_TYPES", "PROOF_OF_ORIGIN, foobar"); WorkerContext workerContext = null; EntityManager em = null; XAdESSigner instance = new MockedXAdESSigner(tokenRSA); instance.init(signerId, config, workerContext, em); String errors = instance.getFatalErrors().toString(); assertTrue("error: " + errors, errors.contains("commitment type")); } /** Tests including 3 certificate levels in the document. */ @Test public void testSigningWithIntermediateCert_3levels() throws Exception { LOG.info("testSigningWithIntermediateCert"); WorkerConfig config = new WorkerConfig(); config.setProperty("INCLUDE_CERTIFICATE_LEVELS", "3"); final XAdESVerificationResult r = getVerificationResult(tokenWithIntermediateCert, config, "<testroot/>", false, null); assertEquals("BES", r.getSignatureForm().name()); KeyInfo keyInfo = r.getXmlSignature().getKeyInfo(); // Gather all certificates List<X509Certificate> certs = new LinkedList<X509Certificate>(); for (int i = 0; i < keyInfo.lengthX509Data(); i++) { X509Data x509Data = keyInfo.itemX509Data(i); if (x509Data.containsCertificate()) { for (int j = 0; j < x509Data.lengthCertificate(); j++) { certs.add(x509Data.itemCertificate(j).getX509Certificate()); } } } // Check that the intermediate cert is included in the chain assertEquals(tokenWithIntermediateCert.getCertificateChain(ICryptoToken.PURPOSE_SIGN), certs); } /** Tests specifying many more certificates than available to including all 3 certificate levels in the document. */ @Test public void testSigningWithIntermediateCert_99levels() throws Exception { LOG.info("testSigningWithIntermediateCert"); WorkerConfig config = new WorkerConfig(); config.setProperty("INCLUDE_CERTIFICATE_LEVELS", "99"); final XAdESVerificationResult r = getVerificationResult(tokenWithIntermediateCert, config, "<testroot/>", false, null); assertEquals("BES", r.getSignatureForm().name()); KeyInfo keyInfo = r.getXmlSignature().getKeyInfo(); // Gather all certificates List<X509Certificate> certs = new LinkedList<X509Certificate>(); for (int i = 0; i < keyInfo.lengthX509Data(); i++) { X509Data x509Data = keyInfo.itemX509Data(i); if (x509Data.containsCertificate()) { for (int j = 0; j < x509Data.lengthCertificate(); j++) { certs.add(x509Data.itemCertificate(j).getX509Certificate()); } } } // Check that the intermediate cert is included in the chain assertEquals(tokenWithIntermediateCert.getCertificateChain(ICryptoToken.PURPOSE_SIGN), certs); } /** Tests including 1 certificate level in the document. */ @Test public void testSigningWithoutIntermediateCert_1levels() throws Exception { LOG.info("testSigningWithIntermediateCert"); WorkerConfig config = new WorkerConfig(); config.setProperty("INCLUDE_CERTIFICATE_LEVELS", "1"); final XAdESVerificationResult r = getVerificationResult(tokenRSA, config, "<testroot/>", false, null); assertEquals("BES", r.getSignatureForm().name()); KeyInfo keyInfo = r.getXmlSignature().getKeyInfo(); // Gather all certificates List<X509Certificate> certs = new LinkedList<X509Certificate>(); for (int i = 0; i < keyInfo.lengthX509Data(); i++) { X509Data x509Data = keyInfo.itemX509Data(i); if (x509Data.containsCertificate()) { for (int j = 0; j < x509Data.lengthCertificate(); j++) { certs.add(x509Data.itemCertificate(j).getX509Certificate()); } } } // Check that the signer certificate is the only certificate included assertEquals(Arrays.asList(tokenRSA.getCertificate(ICryptoToken.PURPOSE_SIGN)), certs); } /** Tests not specifying any level and using the default value of 1 certificate level. */ @Test public void testSigningWithoutIntermediateCert_defaultLevels() throws Exception { LOG.info("testSigningWithIntermediateCert"); WorkerConfig config = new WorkerConfig(); // Note: No INCLUDE_CERTIFICATE_LEVELS set final XAdESVerificationResult r = getVerificationResult(tokenRSA, config, "<testroot/>", false, null); assertEquals("BES", r.getSignatureForm().name()); KeyInfo keyInfo = r.getXmlSignature().getKeyInfo(); // Gather all certificates List<X509Certificate> certs = new LinkedList<X509Certificate>(); for (int i = 0; i < keyInfo.lengthX509Data(); i++) { X509Data x509Data = keyInfo.itemX509Data(i); if (x509Data.containsCertificate()) { for (int j = 0; j < x509Data.lengthCertificate(); j++) { certs.add(x509Data.itemCertificate(j).getX509Certificate()); } } } // Check that the signer certificate is the only certificate included assertEquals(Arrays.asList(tokenRSA.getCertificate(ICryptoToken.PURPOSE_SIGN)), certs); } /** Tests including 2 certificate levels in the document. */ @Test public void testSigningWithIntermediateCert_2levels() throws Exception { LOG.info("testSigningWithIntermediateCert"); WorkerConfig config = new WorkerConfig(); config.setProperty("INCLUDE_CERTIFICATE_LEVELS", "2"); final XAdESVerificationResult r = getVerificationResult(tokenWithIntermediateCert, config, "<testroot/>", false, null); assertEquals("BES", r.getSignatureForm().name()); KeyInfo keyInfo = r.getXmlSignature().getKeyInfo(); // Gather all certificates List<X509Certificate> actual = new LinkedList<X509Certificate>(); for (int i = 0; i < keyInfo.lengthX509Data(); i++) { X509Data x509Data = keyInfo.itemX509Data(i); if (x509Data.containsCertificate()) { for (int j = 0; j < x509Data.lengthCertificate(); j++) { actual.add(x509Data.itemCertificate(j).getX509Certificate()); } } } // Check that the intermediate cert is included in the chain List<Certificate> expected = Arrays.asList( tokenWithIntermediateCert.getCertificateChain(ICryptoToken.PURPOSE_SIGN).get(0), tokenWithIntermediateCert.getCertificateChain(ICryptoToken.PURPOSE_SIGN).get(1)); assertEquals(expected, actual); } /** Tests incorrect values for the INCLUDE_CERTIFICATE_LEVELS worker property. */ @Test public void testInit_includeCertificateLevelsProperty() throws Exception { LOG.info("testSigningWithIntermediateCert"); WorkerConfig config = new WorkerConfig(); config.setProperty("INCLUDE_CERTIFICATE_LEVELS", "0"); XAdESSigner instance = new MockedXAdESSigner(tokenRSA); instance.init(4711, config, null, null); List<String> actualErrors = instance.getFatalErrors(); assertTrue("message: " + actualErrors, actualErrors.toString().contains("INCLUDE_CERTIFICATE_LEVELS")); config.setProperty("INCLUDE_CERTIFICATE_LEVELS", "-1"); instance = new MockedXAdESSigner(tokenRSA); instance.init(4711, config, null, null); actualErrors = instance.getFatalErrors(); assertTrue("message: " + actualErrors, actualErrors.toString().contains("INCLUDE_CERTIFICATE_LEVELS")); config.setProperty("INCLUDE_CERTIFICATE_LEVELS", "qwerty"); instance = new MockedXAdESSigner(tokenRSA); instance.init(4711, config, null, null); actualErrors = instance.getFatalErrors(); assertTrue("message: " + actualErrors, actualErrors.toString().contains("INCLUDE_CERTIFICATE_LEVELS")); } /** * Test setting the CLAIMED_ROLE property. * * @throws Exception */ @Test public void testClaimedRole() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, null, XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, null, Collections.<String>emptyList(), "foobar", false, false, null, "foobar"); } /** * Test setting claimed role from the username provided via the request. * * @throws Exception */ @Test public void testClaimedRoleFromUsername() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, null, XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, null, Collections.<String>emptyList(), null, true, false, "username", "username"); } /** * Test that claimed role gets its value from the username before the hard-coded setting. * * @throws Exception */ @Test public void testClaimedRoleFromUsernameOverride() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, null, XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, null, Collections.<String>emptyList(), "defaultrole", true, false, "username", "username"); } /** * Test that the using a value for CLAIMED_ROLE and not setting CLAIMED_ROLE_FROM_USERNAME * the value is taken from property even when there is a user name in the request. * * @throws Exception */ @Test public void testClaimedRoleUsingDefaultWithSuppliedUsername() throws Exception { testProcessData_basicSigningInternal(KeyType.RSA, null, XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, null, Collections.<String>emptyList(), "defaultrole", false, false, "username", "defaultrole"); } /** * Test that setting CLAIMED_ROLE_FROM_USERNAME and not having set CLAIMED_ROLE * results in an error if there is no user name in the request. * * @throws Exception */ @Test public void testClaimedRoleNoUsername() throws Exception { try { testProcessData_basicSigningInternal(KeyType.RSA, null, XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, null, Collections.<String>emptyList(), null, true, false, null, null); fail("Should throw a SignServerException"); } catch (SignServerException e) { //NOPMD // expected } catch (Exception e) { fail("Unexpected exception thrown: " + e.getClass().getName()); } } /** * Test passing in a CertificateClientCredential in the CLIENT_CREDENTIAL request context parameter * is handled equivalently to a missing user name. * * @throws Exception */ @Test public void testClaimedRoleWithClientCertCredential() throws Exception { try { testProcessData_basicSigningInternal(KeyType.RSA, null, XAdESSigner.SIGNATURE_METHOD_RSA_SHA256, null, Collections.<String>emptyList(), null, true, true, null, null); fail("Should throw a SignServerException"); } catch (SignServerException e) { //NOPMD // expected } catch (Exception e) { fail("Unexpected exception thrown: " + e.getClass().getName()); } } /** * Tests that a document with a DOCTYPE is not allowed. * @throws Exception */ @Test @SuppressWarnings("ThrowableResultIgnored") public void testDTDNotAllowed() throws Exception { LOG.info("testDTDNotAllowed"); final String xmlWithDoctype = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE foo [\n" + " <!ELEMENT foo ANY >\n" + "]><foo/>\n"; try { getVerificationResult(tokenRSA, new WorkerConfig(), xmlWithDoctype, false, null); fail("Should have thrown IllegalRequestException as the document contained a DTD"); } catch (IllegalRequestException expected) { if (expected.getCause() instanceof SAXParseException) { if (!expected.getCause().getMessage().contains("DOCTYPE")) { LOG.error("Wrong exception message", expected); fail("Should be error about doctype: " + expected.getMessage()); } } else { LOG.error("Wrong exception cause", expected); fail("Expected SAXParseException but was: " + expected); } } } }