package org.jboss.resteasy.test.crypto; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.RunAsClient; import org.jboss.arquillian.junit.Arquillian; import org.jboss.resteasy.category.NotForForwardCompatibility; import org.jboss.resteasy.client.jaxrs.ResteasyClient; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget; import org.jboss.resteasy.security.doseta.DKIMSignature; import org.jboss.resteasy.security.doseta.DosetaKeyRepository; import org.jboss.resteasy.security.doseta.KeyRepository; import org.jboss.resteasy.security.doseta.UnauthorizedSignatureException; import org.jboss.resteasy.security.doseta.Verification; import org.jboss.resteasy.security.doseta.Verifier; import org.jboss.resteasy.spi.MarshalledEntity; import org.jboss.resteasy.test.crypto.resource.SigningResource; import org.jboss.resteasy.test.crypto.resource.SigningProxy; import org.jboss.resteasy.util.HttpResponseCodes; import org.jboss.resteasy.utils.PortProviderUtil; import org.jboss.resteasy.utils.TestUtil; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; import javax.ws.rs.ProcessingException; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; import javax.ws.rs.client.ResponseProcessingException; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.GenericType; import javax.ws.rs.core.Response; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SignatureException; import java.util.HashMap; import java.util.Map; import static org.hamcrest.CoreMatchers.containsString; /** * @tpSubChapter Crypto * @tpChapter Integration tests * @tpTestCaseDetails Signing test for RESTEasy-crypto * @tpSince RESTEasy 3.0.16 */ @RunWith(Arquillian.class) @RunAsClient public class SigningTest { public static KeyPair keys; public static DosetaKeyRepository repository; public static PrivateKey badKey; private static ResteasyClient client; protected final Logger logger = LogManager.getLogger(PKCS7SignatureSmokeTest.class.getName()); private static final String RESPONSE_ERROR_MSG = "Response contains wrong content"; static final String testJksPath; static { testJksPath = TestUtil.getResourcePath(SigningTest.class, "SigningTest.jks"); } @Before public void init() { client = new ResteasyClientBuilder().build(); } @After public void close() { client.close(); } @Deployment public static Archive<?> deploy() { WebArchive war = TestUtil.prepareArchive(SigningTest.class.getSimpleName()); war.addClass(SigningProxy.class); war.addAsResource(SigningTest.class.getPackage(), "SigningTest.jks", "test.jks"); Map<String, String> contextParams = new HashMap<>(); contextParams.put("resteasy.doseta.keystore.classpath", "test.jks"); contextParams.put("resteasy.doseta.keystore.password", "password"); contextParams.put("resteasy.context.objects", "org.jboss.resteasy.security.doseta.KeyRepository : org.jboss.resteasy.security.doseta.ConfiguredDosetaKeyRepository"); contextParams.put("resteasy.doseta.use.dns", "false"); return TestUtil.finishContainerPrepare(war, contextParams, SigningResource.class); } private String generateURL(String path) { return PortProviderUtil.generateURL(path, SigningTest.class.getSimpleName()); } @BeforeClass public static void setup() throws Exception { repository = new DosetaKeyRepository(); repository.setKeyStoreFile(testJksPath); repository.setKeyStorePassword("password"); repository.setUseDns(false); repository.start(); PrivateKey privateKey = repository.getKeyStore().getPrivateKey("test._domainKey.samplezone.org"); if (privateKey == null) { throw new Exception("Private Key is null!!!"); } PublicKey publicKey = repository.getKeyStore().getPublicKey("test._domainKey.samplezone.org"); keys = new KeyPair(publicKey, privateKey); KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); badKey = keyPair.getPrivate(); } @AfterClass public static void afterIt() throws Exception { client.close(); client = null; } /** * @tpTestDetails Test for "DKIM-Signature" header attribute * @tpSince RESTEasy 3.0.16 */ @Test public void testRequestOnly() throws Exception { WebTarget target = client.target(generateURL("/signed/request-only")); DKIMSignature contentSignature = new DKIMSignature(); contentSignature.setDomain("samplezone.org"); contentSignature.setSelector("test"); contentSignature.setPrivateKey(keys.getPrivate()); contentSignature.setBodyHashRequired(false); contentSignature.setAttribute("method", "GET"); contentSignature.setAttribute("uri", "/signed/request-only"); contentSignature.setAttribute("token", "1122"); Response response = target.request().header(DKIMSignature.DKIM_SIGNATURE, contentSignature).delete(); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); String signatureHeader = response.getHeaderString(DKIMSignature.DKIM_SIGNATURE); contentSignature = new DKIMSignature(signatureHeader); Verification verification = new Verification(keys.getPublic()); verification.setBodyHashRequired(false); verification.getRequiredAttributes().put("token", "1122"); verification.verify(contentSignature, response.getStringHeaders(), null, keys.getPublic()); response.close(); } /** * @tpTestDetails Test for manual signing * @tpSince RESTEasy 3.0.16 */ @Test public void testSigningManual() throws Exception { WebTarget target = client.target(generateURL("/signed")); Response response = target.request().get(); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); MarshalledEntity<String> marshalledEntity = response.readEntity(new GenericType<MarshalledEntity<String>>() { }); Assert.assertEquals(RESPONSE_ERROR_MSG, "hello world", marshalledEntity.getEntity()); String signatureHeader = response.getHeaderString(DKIMSignature.DKIM_SIGNATURE); logger.info(DKIMSignature.DKIM_SIGNATURE + ": " + signatureHeader); Assert.assertNotNull("Missing DKIM_SIGNATURE header", signatureHeader); DKIMSignature contentSignature = new DKIMSignature(signatureHeader); contentSignature.verify(response.getStringHeaders(), marshalledEntity.getMarshalledBytes(), keys.getPublic()); response.close(); } /** * @tpTestDetails Basic verification test * @tpSince RESTEasy 3.0.16 */ @Test public void testBasicVerification() throws Exception { WebTarget target = client.target(generateURL("/signed")); DKIMSignature contentSignature = new DKIMSignature(); contentSignature.setDomain("samplezone.org"); contentSignature.setSelector("test"); contentSignature.setPrivateKey(keys.getPrivate()); Response response = target.request().header(DKIMSignature.DKIM_SIGNATURE, contentSignature) .post(Entity.text("hello world")); Assert.assertEquals(HttpResponseCodes.SC_NO_CONTENT, response.getStatus()); response.close(); } /** * @tpTestDetails Manual verification test * @tpSince RESTEasy 3.0.16 */ @Test public void testManualVerification() throws Exception { WebTarget target = client.target(generateURL("/signed/verify-manual")); DKIMSignature contentSignature = new DKIMSignature(); contentSignature.setDomain("samplezone.org"); contentSignature.setSelector("test"); contentSignature.setAttribute("code", "hello"); contentSignature.setPrivateKey(keys.getPrivate()); Response response = target.request().header(DKIMSignature.DKIM_SIGNATURE, contentSignature) .post(Entity.text("hello world")); Assert.assertEquals(HttpResponseCodes.SC_NO_CONTENT, response.getStatus()); response.close(); } /** * @tpTestDetails Basic verification test with repository * @tpSince RESTEasy 3.0.16 */ @Test public void testBasicVerificationRepository() throws Exception { WebTarget target = client.target(generateURL("/signed")); target.property(KeyRepository.class.getName(), repository); DKIMSignature contentSignature = new DKIMSignature(); contentSignature.setSelector("test"); contentSignature.setDomain("samplezone.org"); Response response = target.request().header(DKIMSignature.DKIM_SIGNATURE, contentSignature) .post(Entity.text("hello world")); Assert.assertEquals(HttpResponseCodes.SC_NO_CONTENT, response.getStatus()); response.close(); } /** * @tpTestDetails Basic verification test with bad signature * @tpSince RESTEasy 3.0.16 */ @Test public void testBasicVerificationBadSignature() throws Exception { WebTarget target = client.target(generateURL("/signed")); DKIMSignature contentSignature = new DKIMSignature(); contentSignature.setSelector("test"); contentSignature.setDomain("samplezone.org"); contentSignature.setPrivateKey(badKey); Response response = target.request().header(DKIMSignature.DKIM_SIGNATURE, contentSignature) .post(Entity.text("hello world")); Assert.assertEquals(HttpResponseCodes.SC_UNAUTHORIZED, response.getStatus()); response.close(); } /** * @tpTestDetails Basic verification test with no signature * @tpSince RESTEasy 3.0.16 */ @Test public void testBasicVerificationNoSignature() throws Exception { WebTarget target = client.target(generateURL("/signed")); Response response = target.request().post(Entity.text("hello world")); Assert.assertEquals(HttpResponseCodes.SC_UNAUTHORIZED, response.getStatus()); response.close(); } /** * @tpTestDetails Test for timestamp signature with set domain * @tpSince RESTEasy 3.0.16 */ @Test public void testTimestampSignature() throws Exception { DKIMSignature signature = new DKIMSignature(); signature.setTimestamp(); signature.setSelector("test"); signature.setDomain("samplezone.org"); signature.sign(new HashMap(), "hello world".getBytes(), keys.getPrivate()); String sig = signature.toString(); logger.info(DKIMSignature.DKIM_SIGNATURE + ": " + sig); new DKIMSignature(sig); } /** * @tpTestDetails Test for timestamp signature without set domain * @tpSince RESTEasy 3.0.16 */ @Test public void testTimestamp() throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); verification.setStaleCheck(true); verification.setStaleSeconds(100); WebTarget target = client.target(generateURL("/signed/stamped")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); try { response.readEntity(String.class); } catch (Exception e) { throw e; } response.close(); } /** * @tpTestDetails Stale timestamp test * @tpSince RESTEasy 3.0.16 */ @Test public void testStaleTimestamp() throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); verification.setStaleCheck(true); verification.setStaleSeconds(1); WebTarget target = client.target(generateURL("/signed/stamped")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); Thread.sleep(1500); try { response.readEntity(String.class); Assert.fail("Validation error excepted."); } catch (ProcessingException pe) { UnauthorizedSignatureException e = (UnauthorizedSignatureException) pe.getCause(); Assert.assertThat("Unexcepted error", e.getMessage(), containsString("Failed to verify signatures:\r\n")); Assert.assertThat("Unexcepted error", e.getMessage(), containsString("Signature is stale")); } response.close(); } /** * @tpTestDetails Hour expiration test (expires attribute in Signed annotation in REST end-point is used). * @tpSince RESTEasy 3.0.16 */ @Test public void testExpiresHour() throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); WebTarget target = client.target(generateURL("/signed/expires-hour")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); response.readEntity(String.class); response.close(); } /** * @tpTestDetails Minute expiration test (expires attribute in Signed annotation in REST end-point is used). * @tpSince RESTEasy 3.0.16 */ @Test public void testExpiresMinutes() throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); WebTarget target = client.target(generateURL("/signed/expires-minute")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); response.readEntity(String.class); response.close(); } /** * @tpTestDetails Day expiration test (expires attribute in Signed annotation in REST end-point is used). * @tpSince RESTEasy 3.0.16 */ @Test public void testExpiresDays() throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); WebTarget target = client.target(generateURL("/signed/expires-day")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); response.readEntity(String.class); response.close(); } /** * @tpTestDetails Month expiration test (expires attribute in Signed annotation in REST end-point is used). * @tpSince RESTEasy 3.0.16 */ @Test public void testExpiresMonths() throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); WebTarget target = client.target(generateURL("/signed/expires-month")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); response.readEntity(String.class); response.close(); } /** * @tpTestDetails Year expiration test (expires attribute in Signed annotation in REST end-point is used). * @tpSince RESTEasy 3.0.16 */ @Test public void testExpiresYears() throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); WebTarget target = client.target(generateURL("/signed/expires-year")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); response.readEntity(String.class); response.close(); } /** * @tpTestDetails Fail expiration test (expires attribute in Signed annotation in REST end-point is used). * @tpSince RESTEasy 3.0.16 */ @Test public void testExpiresFail() throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); WebTarget target = client.target(generateURL("/signed/expires-short")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); Thread.sleep(1500); try { response.readEntity(String.class); throw new Exception("Signing error excepted"); } catch (ProcessingException pe) { UnauthorizedSignatureException e = (UnauthorizedSignatureException) pe.getCause(); Assert.assertThat("Unexcepted error", e.getMessage(), containsString("Failed to verify signatures:\r\n")); Assert.assertThat("Unexcepted error", e.getMessage(), containsString("Signature expired")); } response.close(); } /** * @tpTestDetails Manual fail test * @tpSince RESTEasy 3.0.16 */ @Test public void testManualFail() throws Exception { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(1024); KeyPair keyPair = kpg.genKeyPair(); Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setKey(keyPair.getPublic()); WebTarget target = client.target(generateURL("/signed/manual")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertNotNull("DKIM_SIGNATURE header is missing", response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); try { response.readEntity(String.class); throw new Exception("unreachable!"); } catch (ProcessingException pe) { UnauthorizedSignatureException e = (UnauthorizedSignatureException) pe.getCause(); logger.info("UnauthorizedSignatureException message: " + e.getMessage()); Assert.assertThat("Unexcepted error", e.getMessage(), containsString("Failed to verify signatures:\r\n")); Assert.assertThat("Unexcepted error", e.getMessage(), containsString("Failed to verify signature.")); } response.close(); } /** * @tpTestDetails Manual success test * @tpSince RESTEasy 3.0.16 */ @Test public void testManual() throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); WebTarget target = client.target(generateURL("/signed/manual")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertNotNull("Missing header DKIM_SIGNATURE", response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); String output = response.readEntity(String.class); Assert.assertEquals(RESPONSE_ERROR_MSG, "hello", output); response.close(); } /** * @tpTestDetails Manual test with header * @tpSince RESTEasy 3.0.16 */ @Test public void testManualWithHeader() throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); WebTarget target = client.target(generateURL("/signed/header")); Invocation.Builder request = target.request(); request.property(Verifier.class.getName(), verifier); Response response = request.get(); logger.info(response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertNotNull("Missing header DKIM_SIGNATURE", response.getHeaderString(DKIMSignature.DKIM_SIGNATURE)); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); String output = response.readEntity(String.class); Assert.assertEquals(RESPONSE_ERROR_MSG, "hello world", output); response.close(); } /** * @tpTestDetails Bad signature test * @tpSince RESTEasy 3.0.16 */ @Test public void testBadSignature() throws Exception { WebTarget target = client.target(generateURL("/signed/bad-signature")); Response response = target.request().get(); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); String signatureHeader = response.getHeaderString(DKIMSignature.DKIM_SIGNATURE); Assert.assertNotNull("Missing header DKIM_SIGNATURE", signatureHeader); logger.info(DKIMSignature.DKIM_SIGNATURE + ": " + signatureHeader); DKIMSignature contentSignature = new DKIMSignature(signatureHeader); MarshalledEntity<String> entity = response.readEntity(new GenericType<MarshalledEntity<String>>() { }); try { contentSignature.verify(response.getStringHeaders(), entity.getMarshalledBytes(), keys.getPublic()); Assert.fail("Signing error excepted"); } catch (SignatureException e) { logger.info("SignatureException message: " + e.getMessage()); } response.close(); } /** * @tpTestDetails Bad hash test * @tpSince RESTEasy 3.0.16 */ @Test public void testBadHash() throws Exception { WebTarget target = client.target(generateURL("/signed/bad-hash")); Response response = target.request().get(); Assert.assertEquals(HttpResponseCodes.SC_OK, response.getStatus()); String signatureHeader = response.getHeaderString(DKIMSignature.DKIM_SIGNATURE); Assert.assertNotNull(signatureHeader); logger.info(DKIMSignature.DKIM_SIGNATURE + ": " + signatureHeader); DKIMSignature contentSignature = new DKIMSignature(signatureHeader); MarshalledEntity<String> entity = response.readEntity(new GenericType<MarshalledEntity<String>>() { }); try { contentSignature.verify(response.getStringHeaders(), entity.getMarshalledBytes(), keys.getPublic()); Assert.fail("Signing error excepted"); } catch (SignatureException e) { logger.info("SignatureException message: " + e.getMessage()); } response.close(); } /** * @tpTestDetails Proxy test with correct signature * @tpSince RESTEasy 3.0.16 */ @Test public void testProxy() throws Exception { ResteasyWebTarget target = client.target(generateURL("/")); target.property(KeyRepository.class.getName(), repository); SigningProxy proxy = target.proxy(SigningProxy.class); proxy.hello(); proxy.postSimple("hello world"); } /** * @tpTestDetails Proxy test with bad signature * @tpSince RESTEasy 3.0.16 */ @Test public void testBadSignatureProxy() throws Exception { ResteasyWebTarget target = client.target(generateURL("/")); target.property(KeyRepository.class.getName(), repository); SigningProxy proxy = target.proxy(SigningProxy.class); try { proxy.bad(); Assert.fail("Signing error excepted"); } catch (ResponseProcessingException e) { logger.info("ResponseProcessingException cause: " + e.getCause().getClass().getName()); } } /** * @tpTestDetails Regression test for RESTEASY-1169 * @tpSince RESTEasy 3.0.17 */ @Test @Category({NotForForwardCompatibility.class}) public void testBasicVerificationBadSignatureNoBody() throws Exception { WebTarget target = client.target(generateURL("/signed/nobody")); DKIMSignature contentSignature = new DKIMSignature(); contentSignature.setSelector("test"); contentSignature.setDomain("samplezone.org"); contentSignature.setPrivateKey(badKey); Response response = target.request().header(DKIMSignature.DKIM_SIGNATURE, contentSignature).get(); Assert.assertEquals(HttpResponseCodes.SC_UNAUTHORIZED, response.getStatus()); response.close(); } }