diff --git a/README.md b/README.md index 8dfdd09..03b62f4 100644 --- a/README.md +++ b/README.md @@ -371,6 +371,8 @@ Validates the tickets using the CAS 2.0 protocol. If you provide either the `acc | `millisBetweenCleanUps` | Startup delay for the cleanup task to remove expired tickets from the storage. Defaults to `60000 msec` | No | `ticketValidatorClass` | Ticket validator class to use/create | No | `hostnameVerifier` | Hostname verifier class name, used when making back-channel calls | No +| `privateKeyPath` | The path to a private key to decrypt PGTs directly sent encrypted as an attribute | No +| `privateKeyAlgorithm` | The algorithm of the private key. Defaults to `RSA` | No #### org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter Validates the tickets using the CAS 3.0 protocol. If you provide either the `acceptAnyProxy` or the `allowedProxyChains` parameters, diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java index e89efed..2fbafb9 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java @@ -55,6 +55,8 @@ public interface ConfigurationKeys { ConfigurationKey CAS_SERVER_URL_PREFIX = new ConfigurationKey("casServerUrlPrefix", null); ConfigurationKey ENCODING = new ConfigurationKey("encoding", null); ConfigurationKey TOLERANCE = new ConfigurationKey("tolerance", 1000L); + ConfigurationKey PRIVATE_KEY_PATH = new ConfigurationKey("privateKeyPath", null); + ConfigurationKey PRIVATE_KEY_ALGORITHM = new ConfigurationKey("privateKeyAlgorithm", "RSA"); /** * @deprecated As of 3.4. This constant is not used by the client and will diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/PrivateKeyUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/PrivateKeyUtils.java new file mode 100644 index 0000000..0bde4ea --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/PrivateKeyUtils.java @@ -0,0 +1,90 @@ +package org.jasig.cas.client.util; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.Security; +import java.security.spec.PKCS8EncodedKeySpec; + +/** + * Utility class to parse private keys. + * + * @author Jerome LELEU + * @since 3.6.0 + */ +public class PrivateKeyUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(PrivateKeyUtils.class); + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + public static PrivateKey createKey(final String path, final String algorithm) { + PrivateKey key = readPemPrivateKey(path); + if (key == null) { + return readDERPrivateKey(path, algorithm); + } else { + return key; + } + } + + private static PrivateKey readPemPrivateKey(final String path) { + LOGGER.debug("Attempting to read as PEM [{}]", path); + final File file = new File(path); + InputStreamReader isr = null; + BufferedReader br = null; + try { + isr = new FileReader(file); + br = new BufferedReader(isr); + final PEMParser pp = new PEMParser(br); + final PEMKeyPair pemKeyPair = (PEMKeyPair) pp.readObject(); + final KeyPair kp = new JcaPEMKeyConverter().getKeyPair(pemKeyPair); + return kp.getPrivate(); + } catch (final Exception e) { + LOGGER.error("Unable to read key", e); + return null; + } finally { + try { + if (br != null) { + br.close(); + } + if (isr != null) { + isr.close(); + } + } catch (final IOException e) {} + } + } + + private static PrivateKey readDERPrivateKey(final String path, final String algorithm) { + LOGGER.debug("Attempting to read key as DER [{}]", path); + final File file = new File(path); + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + long byteLength = file.length(); + byte[] bytes = new byte[(int) byteLength]; + fis.read(bytes, 0, (int) byteLength); + final PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(bytes); + final KeyFactory factory = KeyFactory.getInstance(algorithm); + return factory.generatePrivate(privSpec); + } catch (final Exception e) { + LOGGER.error("Unable to read key", e); + return null; + } finally { + try { + if (fis != null) { + fis.close(); + } + } catch (final IOException e) {} + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java index 22cb830..abd7056 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java @@ -19,6 +19,7 @@ package org.jasig.cas.client.validation; import java.io.IOException; +import java.security.PrivateKey; import java.util.*; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; @@ -30,6 +31,7 @@ import org.jasig.cas.client.proxy.*; import org.jasig.cas.client.ssl.HttpURLConnectionFactory; import org.jasig.cas.client.ssl.HttpsURLConnectionFactory; import org.jasig.cas.client.util.CommonUtils; +import org.jasig.cas.client.util.PrivateKeyUtils; import org.jasig.cas.client.util.ReflectUtils; import static org.jasig.cas.client.configuration.ConfigurationKeys.*; @@ -54,7 +56,7 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal TOLERANCE.getName(), IGNORE_PATTERN.getName(), IGNORE_URL_PATTERN_TYPE.getName(), HOSTNAME_VERIFIER.getName(), HOSTNAME_VERIFIER_CONFIG.getName(), EXCEPTION_ON_VALIDATION_FAILURE.getName(), REDIRECT_AFTER_VALIDATION.getName(), USE_SESSION.getName(), SECRET_KEY.getName(), CIPHER_ALGORITHM.getName(), PROXY_RECEPTOR_URL.getName(), PROXY_GRANTING_TICKET_STORAGE_CLASS.getName(), MILLIS_BETWEEN_CLEAN_UPS.getName(), ACCEPT_ANY_PROXY.getName(), ALLOWED_PROXY_CHAINS.getName(), TICKET_VALIDATOR_CLASS.getName(), - PROXY_CALLBACK_URL.getName(), RELAY_STATE_PARAMETER_NAME.getName() + PROXY_CALLBACK_URL.getName(), RELAY_STATE_PARAMETER_NAME.getName(), METHOD.getName(), PRIVATE_KEY_PATH.getName(), PRIVATE_KEY_ALGORITHM.getName() }; /** @@ -72,6 +74,8 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal protected Class defaultProxyTicketValidatorClass; + private PrivateKey privateKey; + /** * Storage location of ProxyGrantingTickets and Proxy Ticket IOUs. */ @@ -113,6 +117,8 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal } this.millisBetweenCleanUps = getInt(ConfigurationKeys.MILLIS_BETWEEN_CLEAN_UPS); + + this.privateKey = buildPrivateKey(getString(PRIVATE_KEY_PATH), getString(PRIVATE_KEY_ALGORITHM)); super.initInternal(filterConfig); } @@ -139,6 +145,13 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal return (T) ReflectUtils.newInstance(ticketValidatorClass, casServerUrlPrefix); } + public static PrivateKey buildPrivateKey(final String keyPath, final String keyAlgorithm) { + if (keyPath != null) { + return PrivateKeyUtils.createKey(keyPath, keyAlgorithm); + } + return null; + } + /** * Constructs a Cas20ServiceTicketValidator or a Cas20ProxyTicketValidator based on supplied parameters. * @@ -184,6 +197,8 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal } } + validator.setPrivateKey(this.privateKey); + validator.setCustomParameters(additionalParameters); return validator; } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidator.java index fa20991..4c9b3d1 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidator.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidator.java @@ -19,9 +19,13 @@ package org.jasig.cas.client.validation; import java.io.StringReader; +import java.security.PrivateKey; import java.util.*; +import javax.crypto.Cipher; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; + +import org.apache.commons.codec.binary.Base64; import org.jasig.cas.client.authentication.AttributePrincipal; import org.jasig.cas.client.authentication.AttributePrincipalImpl; import org.jasig.cas.client.proxy.Cas20ProxyRetriever; @@ -43,6 +47,9 @@ import org.xml.sax.helpers.DefaultHandler; */ public class Cas20ServiceTicketValidator extends AbstractCasProtocolUrlBasedTicketValidator { + public static final String PGT_ATTRIBUTE = "proxyGrantingTicket"; + private static final String PGTIOU_PREFIX = "PGTIOU-"; + /** The CAS 2.0 protocol proxy callback url. */ private String proxyCallbackUrl; @@ -52,12 +59,14 @@ public class Cas20ServiceTicketValidator extends AbstractCasProtocolUrlBasedTick /** Implementation of the proxy retriever. */ private ProxyRetriever proxyRetriever; + /** Private key for decryption */ + private PrivateKey privateKey; + /** * Constructs an instance of the CAS 2.0 Service Ticket Validator with the supplied * CAS server url prefix. * * @param casServerUrlPrefix the CAS Server URL prefix. - * @param urlFactory URL connection factory to use when communicating with the server */ public Cas20ServiceTicketValidator(final String casServerUrlPrefix) { super(casServerUrlPrefix); @@ -85,14 +94,7 @@ public class Cas20ServiceTicketValidator extends AbstractCasProtocolUrlBasedTick } final String principal = parsePrincipalFromResponse(response); - final String proxyGrantingTicketIou = parseProxyGrantingTicketFromResponse(response); - - final String proxyGrantingTicket; - if (CommonUtils.isBlank(proxyGrantingTicketIou) || this.proxyGrantingTicketStorage == null) { - proxyGrantingTicket = null; - } else { - proxyGrantingTicket = this.proxyGrantingTicketStorage.retrieve(proxyGrantingTicketIou); - } + final String proxyGrantingTicket = retrieveProxyGrantingTicket(response); if (CommonUtils.isEmpty(principal)) { throw new TicketValidationException("No principal was found in the response from the CAS server."); @@ -101,6 +103,7 @@ public class Cas20ServiceTicketValidator extends AbstractCasProtocolUrlBasedTick final Assertion assertion; final Map attributes = extractCustomAttributes(response); if (CommonUtils.isNotBlank(proxyGrantingTicket)) { + attributes.remove(PGT_ATTRIBUTE); final AttributePrincipal attributePrincipal = new AttributePrincipalImpl(principal, attributes, proxyGrantingTicket, this.proxyRetriever); assertion = new AssertionImpl(attributePrincipal); @@ -113,8 +116,42 @@ public class Cas20ServiceTicketValidator extends AbstractCasProtocolUrlBasedTick return assertion; } - protected String parseProxyGrantingTicketFromResponse(final String response) { - return XmlUtils.getTextForElement(response, "proxyGrantingTicket"); + protected String retrieveProxyGrantingTicket(final String response) { + final List values = XmlUtils.getTextForElements(response, PGT_ATTRIBUTE); + for (final String value : values) { + if (value != null) { + if (value.startsWith(PGTIOU_PREFIX)) { + return retrieveProxyGrantingTicketFromStorage(value); + } else { + return retrieveProxyGrantingTicketViaEncryption(value); + } + } + } + return null; + } + + protected String retrieveProxyGrantingTicketFromStorage(final String pgtIou) { + if (this.proxyGrantingTicketStorage != null) { + return this.proxyGrantingTicketStorage.retrieve(pgtIou); + } + return null; + } + + protected String retrieveProxyGrantingTicketViaEncryption(final String encryptedPgt) { + if (this.privateKey != null) { + try { + final Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm()); + final byte[] cred64 = new Base64().decode(encryptedPgt); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + final byte[] cipherData = cipher.doFinal(cred64); + final String pgt = new String(cipherData); + logger.debug("Decrypted PGT: {}", pgt); + return pgt; + } catch (final Exception e) { + logger.error("Unable to decrypt PGT", e); + } + } + return null; } protected String parsePrincipalFromResponse(final String response) { @@ -258,4 +295,12 @@ public class Cas20ServiceTicketValidator extends AbstractCasProtocolUrlBasedTick return this.attributes; } } + + public PrivateKey getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(final PrivateKey privateKey) { + this.privateKey = privateKey; + } } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java index d4c8cab..4100db4 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java @@ -20,8 +20,10 @@ package org.jasig.cas.client.validation; import static org.junit.Assert.*; import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; import java.util.List; import org.jasig.cas.client.PublicTestHttpServer; +import org.jasig.cas.client.authentication.AttributePrincipalImpl; import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage; import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl; import org.jasig.cas.client.proxy.ProxyRetriever; @@ -37,32 +39,33 @@ import org.junit.Test; public final class Cas20ServiceTicketValidatorTests extends AbstractTicketValidatorTests { private static final PublicTestHttpServer server = PublicTestHttpServer.instance(8088); + private static final String USERNAME = "username"; + private static final String PGTIOU = "PGTIOU-1-test"; + private static final String PGT = "PGT-1-ixcY6jtRXZ4OrJ39SadtLEcTLsGNhE8-NYtvDTK3kk5iAEdatRcnGrGjLckOwK8xU6ocastest"; + private static final String ENCRYPTED_PGT = "H3wqFQLBlvhbrPVo4yrwIF9p8yJhCfzHnLHgTWTYVw42sLDJj7c3PBFHKgZfaY9l57qDbKA0fZY979GGFFgnSz1VOOlTgVRi/nmbpwlScRLHP8qUf2JGUyhu0+nTRp6TcQiEqpf5iquXNyQ9UXPyWPdTM/YtgtYtcIOzMovjN5c="; private Cas20ServiceTicketValidator ticketValidator; private ProxyGrantingTicketStorage proxyGrantingTicketStorage; + private Field proxyGrantingTicketField; + public Cas20ServiceTicketValidatorTests() { super(); } - /*@AfterClass - public static void classCleanUp() { - server.shutdown(); - } */ - @Before public void setUp() throws Exception { - this.proxyGrantingTicketStorage = getProxyGrantingTicketStorage(); + this.proxyGrantingTicketStorage = new ProxyGrantingTicketStorageImpl(); + this.proxyGrantingTicketStorage.save(PGTIOU, PGT); this.ticketValidator = new Cas20ServiceTicketValidator(CONST_CAS_SERVER_URL_PREFIX + "8088"); this.ticketValidator.setProxyCallbackUrl("test"); - this.ticketValidator.setProxyGrantingTicketStorage(getProxyGrantingTicketStorage()); + this.ticketValidator.setProxyGrantingTicketStorage(this.proxyGrantingTicketStorage); this.ticketValidator.setProxyRetriever(getProxyRetriever()); + this.ticketValidator.setPrivateKey(Cas20ProxyReceivingTicketValidationFilter.buildPrivateKey("src/test/resources/private.pem", "RSA")); this.ticketValidator.setRenew(true); - } - - private ProxyGrantingTicketStorage getProxyGrantingTicketStorage() { - return new ProxyGrantingTicketStorageImpl(); + proxyGrantingTicketField = AttributePrincipalImpl.class.getDeclaredField("proxyGrantingTicket"); + proxyGrantingTicketField.setAccessible(true); } private ProxyRetriever getProxyRetriever() { @@ -90,8 +93,7 @@ public final class Cas20ServiceTicketValidatorTests extends AbstractTicketValida } @Test - public void testYesResponseButNoPgt() throws TicketValidationException, UnsupportedEncodingException { - final String USERNAME = "username"; + public void testYesResponseButNoPgtiou() throws TicketValidationException, UnsupportedEncodingException { final String RESPONSE = "" + USERNAME + ""; server.content = RESPONSE.getBytes(server.encoding); @@ -102,10 +104,7 @@ public final class Cas20ServiceTicketValidatorTests extends AbstractTicketValida } @Test - public void testYesResponseWithPgt() throws TicketValidationException, UnsupportedEncodingException { - final String USERNAME = "username"; - final String PGTIOU = "testPgtIou"; - final String PGT = "test"; + public void testYesResponseWithPgtiou() throws TicketValidationException, UnsupportedEncodingException, IllegalAccessException { final String RESPONSE = "" + USERNAME + "" @@ -113,17 +112,15 @@ public final class Cas20ServiceTicketValidatorTests extends AbstractTicketValida + ""; server.content = RESPONSE.getBytes(server.encoding); - this.proxyGrantingTicketStorage.save(PGTIOU, PGT); final Assertion assertion = this.ticketValidator.validate("test", "test"); - assertEquals(USERNAME, assertion.getPrincipal().getName()); - // assertEquals(PGT, assertion.getProxyGrantingTicketId()); + final AttributePrincipalImpl principal = (AttributePrincipalImpl) assertion.getPrincipal(); + assertEquals(USERNAME, principal.getName()); + assertEquals(PGT, proxyGrantingTicketField.get(principal)); } @Test - public void testGetAttributes() throws TicketValidationException, UnsupportedEncodingException { - final String USERNAME = "username"; - final String PGTIOU = "testPgtIou"; + public void testGetAttributes() throws TicketValidationException, UnsupportedEncodingException, IllegalAccessException { final String RESPONSE = "" + USERNAME + "" @@ -132,20 +129,53 @@ public final class Cas20ServiceTicketValidatorTests extends AbstractTicketValida server.content = RESPONSE.getBytes(server.encoding); final Assertion assertion = this.ticketValidator.validate("test", "test"); - assertEquals(USERNAME, assertion.getPrincipal().getName()); - assertEquals("test", assertion.getPrincipal().getAttributes().get("password")); - assertEquals("id", assertion.getPrincipal().getAttributes().get("eduPersonId")); - assertEquals("test1\n\ntest", assertion.getPrincipal().getAttributes().get("longAttribute")); + final AttributePrincipalImpl principal = (AttributePrincipalImpl) assertion.getPrincipal(); + assertEquals(USERNAME, principal.getName()); + assertEquals("test", principal.getAttributes().get("password")); + assertEquals("id", principal.getAttributes().get("eduPersonId")); + assertEquals("test1\n\ntest", principal.getAttributes().get("longAttribute")); try { - List multivalued = (List) assertion.getPrincipal().getAttributes().get("multivaluedAttribute"); + List multivalued = (List) principal.getAttributes().get("multivaluedAttribute"); assertArrayEquals(new String[] { "value1", "value2" }, multivalued.toArray()); } catch (Exception e) { fail("'multivaluedAttribute' attribute expected as List object."); } - //assertEquals(PGT, assertion.getProxyGrantingTicketId()); + assertEquals(PGT, proxyGrantingTicketField.get(principal)); } + @Test + public void testYesResponseWithEncryptedPgt() throws TicketValidationException, UnsupportedEncodingException, IllegalAccessException { + final String RESPONSE = "" + + USERNAME + + "" + + ENCRYPTED_PGT + + ""; + server.content = RESPONSE.getBytes(server.encoding); + + final Assertion assertion = this.ticketValidator.validate("test", "test"); + final AttributePrincipalImpl principal = (AttributePrincipalImpl) assertion.getPrincipal(); + assertEquals(USERNAME, principal.getName()); + assertEquals(PGT, proxyGrantingTicketField.get(principal)); + } + + @Test + public void testYesResponseWithPgtiouAndEncryptedPgt() throws TicketValidationException, UnsupportedEncodingException, IllegalAccessException { + final String RESPONSE = "" + + USERNAME + + "" + + PGTIOU + + "" + + ENCRYPTED_PGT + + ""; + + server.content = RESPONSE.getBytes(server.encoding); + + final Assertion assertion = this.ticketValidator.validate("test", "test"); + final AttributePrincipalImpl principal = (AttributePrincipalImpl) assertion.getPrincipal(); + assertEquals(USERNAME, principal.getName()); + assertEquals(PGT, proxyGrantingTicketField.get(principal)); + } @Test public void testInvalidResponse() throws Exception { diff --git a/cas-client-core/src/test/resources/private.pem b/cas-client-core/src/test/resources/private.pem new file mode 100644 index 0000000..0dc71ab --- /dev/null +++ b/cas-client-core/src/test/resources/private.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDcup6Yn4+K33A7jydo5YWuDYboBla65o4bikkSMxCvXWAvDwL/ +Qy69i4q7z/nZo/O2XgVQlfKFciXSSl4qfWj0R5XILCz/g8bjwG3r9tMJUFGhGlsm +SCTbZ4+GcjnxREE8tu3x7LBhmIHZ9/FuStwnuGIBXj4iNs9K/mmYPPNAfwIDAQAB +AoGBALs2xgGphDRDo4vAtapw0lt4Oa5egf1wQ6P0PFnlWgeDaWtAjkg3kVNPIdJ+ +aepA9xr80AEzzUmGMbIVRZ1AVV0YB2MBNl1FmFwjJLsZ7E9JbD84QQWUAZfmdp4h +desvzPJA+FrVNuYxVJ8sO8pmOv2toueqeJYMOlSjFsQfR80RAkEA+bWxETU8RR2y +bBW4sIJjRXb/SrztpRqjSCnqcFYa38QraYcWfk/TGU/u8U5j39xI2UM8pNSr8j+l +i5ZC+SYzkwJBAOJKCgEuGInS8B5DVhuDswf0aRsn7P3mi68kAZqUaI/7Z55aopek +usg02qK9Wn0aESRdjbNu0gOta9rpetYLKuUCQQDIE/O3RP9wpbXTcqgUDbU68Hjn +Sm/jjW9tH+Cvd956krTyDgJQ3ObY7joW8OeHc/qO0pfhvmGzbZnYOWKaPSivAkA+ +sX6WFxxLSvqll8hKdTFrucZI9MXPDkmS62naVtWlVmS91aSIWOY6w5HzVny0fj1T +kuvIU6KxzCE+lEMo/A0VAkEAtonSABWxd4CA/QOWbi9ZKpPUM1T5o/yATheS8DTk +ZKFA2XDXB5Aj1lCjY/5QaOhfyE2AnvlukCRPzO5FWHabKQ== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4aeb42c..14964b2 100644 --- a/pom.xml +++ b/pom.xml @@ -238,6 +238,18 @@ ${slf4j.version} compile + + commons-codec + commons-codec + 1.11 + compile + + + org.bouncycastle + bcpkix-jdk15on + 1.61 + compile + javax.servlet javax.servlet-api