package org.jboss.resteasy.test.crypto; import org.bouncycastle.jce.provider.BouncyCastleProvider; 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.logging.Logger; import org.jboss.resteasy.plugins.providers.RegisterBuiltin; import org.jboss.resteasy.security.KeyTools; import org.jboss.resteasy.security.smime.EnvelopedInput; import org.jboss.resteasy.security.smime.EnvelopedOutput; import org.jboss.resteasy.security.smime.PKCS7SignatureInput; import org.jboss.resteasy.security.smime.SignedInput; import org.jboss.resteasy.security.smime.SignedOutput; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.test.crypto.resource.CryptoCertResource; import org.jboss.resteasy.test.crypto.resource.CryptoEncryptedResource; import org.jboss.resteasy.test.crypto.resource.CryptoEncryptedSignedResource; import org.jboss.resteasy.test.crypto.resource.CryptoPkcs7SignedResource; import org.jboss.resteasy.test.crypto.resource.CryptoSignedResource; 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.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.Security; import java.security.cert.X509Certificate; import java.util.Base64; /** * @tpSubChapter Crypto * @tpChapter Integration tests * @tpTestCaseDetails Test for response secured by BouncyCastleProvider * @tpSince RESTEasy 3.0.16 */ @SuppressWarnings(value = "unchecked") @RunWith(Arquillian.class) @RunAsClient public class CryptoTest { private static final String ERROR_CONTENT_MSG = "Wrong content of response"; private static final String ERROR_CORE_MSG = "Wrong BouncyCastleProvider and RESTEasy integration"; private static Logger logger = Logger.getLogger(CryptoTest.class); Client client; public static X509Certificate cert; public static PrivateKey privateKey; @Before public void before() throws Exception { client = ClientBuilder.newClient(); } @After public void close() { client.close(); } @Deployment public static Archive<?> deploy() throws IOException { WebArchive war = TestUtil.prepareArchive(CryptoTest.class.getSimpleName()); try { BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider(); Security.addProvider(bouncyCastleProvider); KeyPair keyPair = KeyPairGenerator.getInstance("RSA", "BC").generateKeyPair(); privateKey = keyPair.getPrivate(); cert = KeyTools.generateTestCertificate(keyPair); String privateKeyString = toString(privateKey); String certString = toString(cert); war.addAsResource(new StringAsset(privateKeyString), "privateKey.txt"); war.addAsResource(new StringAsset(certString), "cert.txt"); } catch (Exception e) { throw new RuntimeException(e); } war.addAsManifestResource("jboss-deployment-structure-bouncycastle.xml", "jboss-deployment-structure.xml"); return TestUtil.finishContainerPrepare(war, null, CryptoEncryptedResource.class, CryptoSignedResource.class, CryptoEncryptedSignedResource.class, CryptoPkcs7SignedResource.class, CryptoCertResource.class); } private String generateURL(String path) { return PortProviderUtil.generateURL(path, CryptoTest.class.getSimpleName()); } /** * @tpTestDetails Check signed output * @tpSince RESTEasy 3.0.16 */ @Test public void testSignedOutput() throws Exception { Response res = client.target(generateURL("/smime/signed")).request().get(); SignedInput signed = res.readEntity(SignedInput.class); String output = (String) signed.getEntity(String.class); Assert.assertEquals(HttpResponseCodes.SC_OK, res.getStatus()); logger.info(output); Assert.assertEquals(ERROR_CONTENT_MSG, "hello world", output); Assert.assertTrue(ERROR_CORE_MSG, signed.verify(cert)); MediaType contentType = MediaType.valueOf(res.getHeaderString("Content-Type")); logger.info(contentType); res.close(); } /** * @tpTestDetails Check PKCS7 signed output * @tpSince RESTEasy 3.0.16 */ @Test public void testPKCS7SignedOutput() throws Exception { Response res = client.target(generateURL("/smime/pkcs7-signature")).request().get(); PKCS7SignatureInput signed = res.readEntity(PKCS7SignatureInput.class); String output = (String) signed.getEntity(String.class, MediaType.TEXT_PLAIN_TYPE); logger.info(output); Assert.assertEquals(ERROR_CONTENT_MSG, "hello world", output); res.close(); } /** * @tpTestDetails Check PKCS7 signed text output * @tpSince RESTEasy 3.0.16 */ @Test public void testPKCS7SignedTextOutput() throws Exception { Response res = client.target(generateURL("/smime/pkcs7-signature/text")).request().get(); String base64 = res.readEntity(String.class); logger.info(base64); PKCS7SignatureInput signed = new PKCS7SignatureInput(base64); ResteasyProviderFactory rpf = new ResteasyProviderFactory(); RegisterBuiltin.register(rpf); signed.setProviders(rpf); String output = (String) signed.getEntity(String.class, MediaType.TEXT_PLAIN_TYPE); logger.info(output); Assert.assertEquals(ERROR_CONTENT_MSG, "hello world", output); Assert.assertTrue(ERROR_CORE_MSG, signed.verify(cert)); res.close(); } /** * @tpTestDetails Check encrypted output * @tpSince RESTEasy 3.0.16 */ @Test public void testEncryptedOutput() throws Exception { Response res = client.target(generateURL("/smime/encrypted")).request().get(); Assert.assertEquals("Unexpected BouncyCastle error", HttpResponseCodes.SC_OK, res.getStatus()); MediaType contentType = MediaType.valueOf(res.getHeaderString("Content-Type")); logger.info(contentType); EnvelopedInput enveloped = res.readEntity(EnvelopedInput.class); String output = (String) enveloped.getEntity(String.class, privateKey, cert); logger.info(output); Assert.assertEquals(ERROR_CONTENT_MSG, "hello world", output); res.close(); } /** * @tpTestDetails Check write encrypted signed output to file * @tpSince RESTEasy 3.0.16 */ @Test public void testEncryptedSignedOutputToFile() throws Exception { Response res = client.target(generateURL("/smime/encrypted/signed")).request().get(); MediaType contentType = MediaType.valueOf(res.getHeaderString("Content-Type")); logger.info(contentType); logger.info(res.getEntity()); FileOutputStream os = new FileOutputStream("target/python_encrypted_signed.txt"); os.write("Content-Type: ".getBytes()); os.write(contentType.toString().getBytes()); os.write("\r\n".getBytes()); os.write(res.readEntity(String.class).getBytes()); os.close(); res.close(); } /** * @tpTestDetails Check encrypted signed output * @tpSince RESTEasy 3.0.16 */ @Test public void testEncryptedSignedOutput() throws Exception { try { Response res = client.target(generateURL("/smime/encrypted/signed")).request().get(); EnvelopedInput enveloped = res.readEntity(EnvelopedInput.class); SignedInput signed = (SignedInput) enveloped.getEntity(SignedInput.class, privateKey, cert); String output = (String) signed.getEntity(String.class); logger.info(output); Assert.assertEquals(ERROR_CONTENT_MSG, "hello world", output); Assert.assertTrue(ERROR_CORE_MSG, signed.verify(cert)); Assert.assertEquals(ERROR_CONTENT_MSG, "hello world", output); res.close(); } catch (Exception e) { throw new RuntimeException("Unexpected BouncyCastle error", e); } } /** * @tpTestDetails Check encrypted input * @tpSince RESTEasy 3.0.16 */ @Test public void testEncryptedInput() throws Exception { EnvelopedOutput output = new EnvelopedOutput("input", "text/plain"); output.setCertificate(cert); Response res = client.target(generateURL("/smime/encrypted")).request().post(Entity.entity(output, "*/*")); Assert.assertEquals(HttpResponseCodes.SC_NO_CONTENT, res.getStatus()); res.close(); } /** * @tpTestDetails Check encrypted signed input * @tpSince RESTEasy 3.0.16 */ @Test public void testEncryptedSignedInput() throws Exception { SignedOutput signed = new SignedOutput("input", "text/plain"); signed.setPrivateKey(privateKey); signed.setCertificate(cert); EnvelopedOutput output = new EnvelopedOutput(signed, "multipart/signed"); output.setCertificate(cert); Response res = client.target(generateURL("/smime/encrypted/signed")).request().post(Entity.entity(output, "*/*")); Assert.assertEquals(HttpResponseCodes.SC_NO_CONTENT, res.getStatus()); res.close(); } /** * @tpTestDetails Check signed input * @tpSince RESTEasy 3.0.16 */ @Test public void testSignedInput() throws Exception { SignedOutput output = new SignedOutput("input", "text/plain"); output.setCertificate(cert); output.setPrivateKey(privateKey); Response res = client.target(generateURL("/smime/signed")).request().post(Entity.entity(output, "multipart/signed")); Assert.assertEquals(HttpResponseCodes.SC_NO_CONTENT, res.getStatus()); res.close(); } @Test public void testPKCS7SignedInput() throws Exception { try { SignedOutput output = new SignedOutput("input", "text/plain"); output.setCertificate(cert); output.setPrivateKey(privateKey); Response res = client.target(generateURL("/smime/pkcs7-signature")).request().post(Entity.entity(output, "application/pkcs7-signature")); Assert.assertEquals(HttpResponseCodes.SC_NO_CONTENT, res.getStatus()); res.close(); } catch (Exception e) { throw new RuntimeException("Unexpected BouncyCastle error", e); } } /** * Read the object from Base64 string. */ private static Object fromString(String s) throws IOException, ClassNotFoundException { byte[] data = Base64.getDecoder().decode(s); ObjectInputStream ois = new ObjectInputStream( new ByteArrayInputStream(data)); Object o = ois.readObject(); ois.close(); return o; } /** * Write the object to a Base64 string. */ private static String toString(Serializable o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(o); oos.close(); return Base64.getEncoder().encodeToString(baos.toByteArray()); } }