This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch 3.1.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 3f79dd802da29bee9061a27cc5b4c49217eed1ef Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Fri Nov 17 17:01:19 2017 +0000 CXF-7568 - Support JWE PBES encryption (cherry picked from commit eeae04330ebd610be9cb46891e9f05851afad2a4) # Conflicts: # rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweUtils.java --- .../cxf/rs/security/jose/common/JoseConstants.java | 9 +- .../security/jose/common/KeyManagementUtils.java | 4 +- .../apache/cxf/rs/security/jose/jwe/JweUtils.java | 105 ++++++++++++++------- .../security/jose/jwejws/JweJwsAlgorithmTest.java | 60 ++++++++++++ .../security/jose/jwejws/algorithms-server.xml | 19 ++++ 5 files changed, 158 insertions(+), 39 deletions(-) diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseConstants.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseConstants.java index ed68908..bff746f 100644 --- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseConstants.java +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/JoseConstants.java @@ -173,7 +173,7 @@ public final class JoseConstants { * Include the X.509 certificate SHA-1 digest for signature in the "x5t" header. */ public static final String RSSEC_SIGNATURE_INCLUDE_CERT_SHA1 = "rs.security.signature.include.cert.sha1"; - + /** * Include the X.509 certificate SHA-256 digest for signature in the "x5t#S256" header. */ @@ -241,12 +241,17 @@ public final class JoseConstants { * Include the X.509 certificate SHA-1 digest for encryption in the "x5t" header. */ public static final String RSSEC_ENCRYPTION_INCLUDE_CERT_SHA1 = "rs.security.encryption.include.cert.sha1"; - + /** * Include the X.509 certificate SHA-256 digest for encryption in the "x5t#S256" header. */ public static final String RSSEC_ENCRYPTION_INCLUDE_CERT_SHA256 = "rs.security.encryption.include.cert.sha256"; + /** + * The value to be used for the "p2c" (PBES2 count) Header Parameter. The default is 4096. + */ + public static final String RSSEC_ENCRYPTION_PBES2_COUNT = "rs.security.encryption.pbes2.count"; + // // JWT specific configuration // diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/KeyManagementUtils.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/KeyManagementUtils.java index f9409cf..d6780bf 100644 --- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/KeyManagementUtils.java +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/common/KeyManagementUtils.java @@ -441,7 +441,7 @@ public final class KeyManagementUtils { if (props == null) { if (required) { LOG.warning("Properties resource is not identified"); - throw new JoseException(); + throw new JoseException("Properties resource is not identified"); } props = new Properties(); } @@ -511,7 +511,7 @@ public final class KeyManagementUtils { return null; } - + public static void setSha1DigestHeader(JoseHeaders headers, Message m, Properties props) { String digest = loadDigestAndEncodeX509Certificate(m, props, MessageDigestUtils.ALGO_SHA_1); if (digest != null) { diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweUtils.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweUtils.java index 4617934..eeea701 100644 --- a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweUtils.java +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwe/JweUtils.java @@ -44,11 +44,13 @@ import javax.crypto.SecretKey; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.message.Message; +import org.apache.cxf.message.MessageUtils; import org.apache.cxf.phase.PhaseInterceptorChain; import org.apache.cxf.rs.security.jose.common.JoseConstants; import org.apache.cxf.rs.security.jose.common.JoseHeaders; import org.apache.cxf.rs.security.jose.common.JoseUtils; import org.apache.cxf.rs.security.jose.common.KeyManagementUtils; +import org.apache.cxf.rs.security.jose.common.PrivateKeyPasswordProvider; import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils; import org.apache.cxf.rs.security.jose.jwa.ContentAlgorithm; import org.apache.cxf.rs.security.jose.jwa.KeyAlgorithm; @@ -273,7 +275,7 @@ public final class JweUtils { public static ContentEncryptionProvider getContentEncryptionProvider(ContentAlgorithm algorithm) { return getContentEncryptionProvider(algorithm, false); } - public static ContentEncryptionProvider getContentEncryptionProvider(ContentAlgorithm algorithm, + public static ContentEncryptionProvider getContentEncryptionProvider(ContentAlgorithm algorithm, boolean generateCekOnce) { if (AlgorithmUtils.isAesGcm(algorithm.getJwaName())) { return new AesGcmContentEncryptionAlgorithm(algorithm, generateCekOnce); @@ -376,9 +378,9 @@ public final class JweUtils { Message m = PhaseInterceptorChain.getCurrentMessage(); return loadEncryptionProvider(props, m, headers); } - + public static JweEncryptionProvider loadEncryptionProvider(Properties props, Message m, JweHeaders headers) { - + KeyEncryptionProvider keyEncryptionProvider = loadKeyEncryptionProvider(props, m, headers); ContentAlgorithm contentAlgo = getContentEncryptionAlgorithm(m, props, null, ContentAlgorithm.A128GCM); @@ -393,7 +395,7 @@ public final class JweUtils { jwk.getAlgorithm() != null ? ContentAlgorithm.getAlgorithm(jwk.getAlgorithm()) : null, contentAlgo); ctEncryptionProvider = getContentEncryptionProvider(jwk, contentAlgo); - } + } } String compression = props.getProperty(JoseConstants.RSSEC_ENCRYPTION_ZIP_ALGORITHM); return createJweEncryptionProvider(keyEncryptionProvider, @@ -402,21 +404,31 @@ public final class JweUtils { compression, headers); } - + public static KeyEncryptionProvider loadKeyEncryptionProvider(Properties props, Message m, JweHeaders headers) { - + KeyEncryptionProvider keyEncryptionProvider = null; KeyAlgorithm keyAlgo = getKeyEncryptionAlgorithm(m, props, null, null); + if (KeyAlgorithm.DIRECT == keyAlgo) { - keyEncryptionProvider = new DirectKeyEncryptionAlgorithm(); + keyEncryptionProvider = new DirectKeyEncryptionAlgorithm(); + } else if (keyAlgo != null && AlgorithmUtils.PBES_HS_SET.contains(keyAlgo.getJwaName())) { + PrivateKeyPasswordProvider provider = + KeyManagementUtils.loadPasswordProvider(m, props, KeyOperation.ENCRYPT); + char[] password = provider != null ? provider.getPassword(props) : null; + if (password == null) { + throw new JweException(JweException.Error.KEY_ENCRYPTION_FAILURE); + } + int pbes2Count = MessageUtils.getContextualInteger(m, JoseConstants.RSSEC_ENCRYPTION_PBES2_COUNT, 4096); + return new PbesHmacAesWrapKeyEncryptionAlgorithm(new String(password), pbes2Count, keyAlgo, false); } else { - boolean includeCert = + boolean includeCert = JoseUtils.checkBooleanProperty(headers, props, m, JoseConstants.RSSEC_ENCRYPTION_INCLUDE_CERT); - boolean includeCertSha1 = + boolean includeCertSha1 = JoseUtils.checkBooleanProperty(headers, props, m, JoseConstants.RSSEC_ENCRYPTION_INCLUDE_CERT_SHA1); - boolean includeCertSha256 = + boolean includeCertSha256 = JoseUtils.checkBooleanProperty(headers, props, m, JoseConstants.RSSEC_ENCRYPTION_INCLUDE_CERT_SHA256); - boolean includeKeyId = + boolean includeKeyId = JoseUtils.checkBooleanProperty(headers, props, m, JoseConstants.RSSEC_ENCRYPTION_INCLUDE_KEY_ID); if (JoseConstants.HEADER_JSON_WEB_KEY.equals(props.get(JoseConstants.RSSEC_KEY_STORE_TYPE))) { @@ -426,9 +438,9 @@ public final class JweUtils { KeyAlgorithm.getAlgorithm(jwk.getAlgorithm()), getDefaultKeyAlgorithm(jwk)); keyEncryptionProvider = getKeyEncryptionProvider(jwk, keyAlgo); - - boolean includePublicKey = - JoseUtils.checkBooleanProperty(headers, props, m, + + boolean includePublicKey = + JoseUtils.checkBooleanProperty(headers, props, m, JoseConstants.RSSEC_ENCRYPTION_INCLUDE_PUBLIC_KEY); if (includeCert) { JwkUtils.includeCertChain(jwk, headers, keyAlgo.getJwaName()); @@ -468,10 +480,10 @@ public final class JweUtils { } headers.setKeyEncryptionAlgorithm(keyEncryptionProvider.getAlgorithm()); return keyEncryptionProvider; - + } - - + + public static JweDecryptionProvider loadDecryptionProvider(boolean required) { return loadDecryptionProvider(null, required); } @@ -552,6 +564,14 @@ public final class JweUtils { getDefaultKeyAlgorithm(jwk)); keyDecryptionProvider = getKeyDecryptionProvider(jwk, keyAlgo); } + } else if (keyAlgo != null && AlgorithmUtils.PBES_HS_SET.contains(keyAlgo.getJwaName())) { + PrivateKeyPasswordProvider provider = + KeyManagementUtils.loadPasswordProvider(m, props, KeyOperation.DECRYPT); + char[] password = provider != null ? provider.getPassword(props) : null; + if (password == null) { + throw new JweException(JweException.Error.KEY_DECRYPTION_FAILURE); + } + keyDecryptionProvider = new PbesHmacAesWrapKeyDecryptionAlgorithm(new String(password)); } else { PrivateKey privateKey = KeyManagementUtils.loadPrivateKey(m, props, KeyOperation.DECRYPT); if (keyAlgo == null) { @@ -563,6 +583,7 @@ public final class JweUtils { return createJweDecryptionProvider(keyDecryptionProvider, ctDecryptionKey, contentAlgo); } + public static JweEncryptionProvider createJweEncryptionProvider(PublicKey key, KeyAlgorithm keyAlgo, ContentAlgorithm contentEncryptionAlgo) { @@ -619,7 +640,7 @@ public final class JweUtils { contentEncryptionAlgo.getJwaName(), compression, null); return createJweEncryptionProvider(keyEncryptionProvider, headers); } - + public static JweEncryptionProvider createJweEncryptionProvider(KeyEncryptionProvider keyEncryptionProvider, JweHeaders headers) { return createJweEncryptionProvider(keyEncryptionProvider, headers, false); @@ -672,24 +693,25 @@ public final class JweUtils { JwkUtils.toECPublicKey(peerPublicKey), partyUInfo, partyVInfo, algoName, algoKeyBitLen); } - public static byte[] getECDHKey(ECPrivateKey privateKey, + + public static byte[] getECDHKey(ECPrivateKey privateKey, ECPublicKey peerPublicKey, byte[] partyUInfo, byte[] partyVInfo, String algoName, int algoKeyBitLen) { // Validate the peerPublicKey first - - // Credits: + + // Credits: // https://neilmadden.wordpress.com/2017/05/17/so-how-do-you-validate-nist-ecdh-public-keys/ - // https://blogs.adobe.com/security/2017/03/critical-vulnerability-uncovered-in-json-encryption.html - - // Step 1: Verify public key is not point at infinity. + // https://blogs.adobe.com/security/2017/03/critical-vulnerability-uncovered-in-json-encryption.html + + // Step 1: Verify public key is not point at infinity. if (ECPoint.POINT_INFINITY.equals(peerPublicKey.getW())) { throw new JweException(JweException.Error.KEY_ENCRYPTION_FAILURE); } EllipticCurve curve = peerPublicKey.getParams().getCurve(); - + final BigInteger x = peerPublicKey.getW().getAffineX(); final BigInteger y = peerPublicKey.getW().getAffineY(); final BigInteger p = ((ECFieldFp) curve.getField()).getP(); @@ -708,7 +730,7 @@ public final class JweUtils { if (!ySquared.equals(xCubedPlusAXPlusB)) { throw new JweException(JweException.Error.KEY_ENCRYPTION_FAILURE); } - + // Step 4: Verify that nQ = 0, where n is the order of the curve and Q is the public key. // As per http://www.secg.org/sec1-v2.pdf section 3.2.2: // "In Step 4, it may not be necessary to compute the point nQ. For example, if h = 1, then nQ = O is implied @@ -719,7 +741,7 @@ public final class JweUtils { } // Finally calculate the derived key - + byte[] keyZ = generateKeyZ(privateKey, peerPublicKey); return calculateDerivedKey(keyZ, algoName, partyUInfo, partyVInfo, algoKeyBitLen); } @@ -810,7 +832,7 @@ public final class JweUtils { } return headers; } - + private static JweEncryptionProvider createJweEncryptionProvider(KeyEncryptionProvider keyEncryptionProvider, ContentEncryptionProvider ctEncryptionProvider, ContentAlgorithm contentEncryptionAlgo, @@ -852,7 +874,7 @@ public final class JweUtils { public static KeyAlgorithm getKeyEncryptionAlgorithm(Properties props, KeyAlgorithm defaultAlgo) { return getKeyEncryptionAlgorithm(PhaseInterceptorChain.getCurrentMessage(), props, defaultAlgo); } - public static KeyAlgorithm getKeyEncryptionAlgorithm(Message m, Properties props, KeyAlgorithm defaultAlgo) { + public static KeyAlgorithm getKeyEncryptionAlgorithm(Message m, Properties props, KeyAlgorithm defaultAlgo) { String algo = KeyManagementUtils.getKeyAlgorithm(m, props, JoseConstants.RSSEC_ENCRYPTION_KEY_ALGORITHM, @@ -879,7 +901,7 @@ public final class JweUtils { return algo; } public static ContentAlgorithm getContentEncryptionAlgorithm(Properties props) { - return getContentEncryptionAlgorithm(PhaseInterceptorChain.getCurrentMessage(), props, null); + return getContentEncryptionAlgorithm(PhaseInterceptorChain.getCurrentMessage(), props, null); } public static ContentAlgorithm getContentEncryptionAlgorithm(Properties props, ContentAlgorithm defaultAlgo) { @@ -913,15 +935,28 @@ public final class JweUtils { } public static Properties loadEncryptionInProperties(boolean required) { Message m = PhaseInterceptorChain.getCurrentMessage(); - return KeyManagementUtils.loadStoreProperties(m, required, - JoseConstants.RSSEC_ENCRYPTION_IN_PROPS, + String keyEncryptionAlgorithm = + (String)m.getContextualProperty(JoseConstants.RSSEC_ENCRYPTION_KEY_ALGORITHM); + if (keyEncryptionAlgorithm != null && AlgorithmUtils.PBES_HS_SET.contains(keyEncryptionAlgorithm)) { + // We don't need to load the keystore properties for the PBES case + required = false; + } + return KeyManagementUtils.loadStoreProperties(m, required, + JoseConstants.RSSEC_ENCRYPTION_IN_PROPS, JoseConstants.RSSEC_ENCRYPTION_PROPS); } public static Properties loadEncryptionOutProperties(boolean required) { Message m = PhaseInterceptorChain.getCurrentMessage(); - return KeyManagementUtils.loadStoreProperties(m, required, - JoseConstants.RSSEC_ENCRYPTION_OUT_PROPS, + String keyEncryptionAlgorithm = + (String)m.getContextualProperty(JoseConstants.RSSEC_ENCRYPTION_KEY_ALGORITHM); + if (keyEncryptionAlgorithm != null && AlgorithmUtils.PBES_HS_SET.contains(keyEncryptionAlgorithm)) { + // We don't need to load the keystore properties for the PBES case + required = false; + } + + return KeyManagementUtils.loadStoreProperties(m, required, + JoseConstants.RSSEC_ENCRYPTION_OUT_PROPS, JoseConstants.RSSEC_ENCRYPTION_PROPS); } @@ -949,7 +984,7 @@ public final class JweUtils { return new JsonWebKeys(jwk); } } - + public static Properties loadJweProperties(Message m, String propLoc) { try { return JoseUtils.loadProperties(propLoc, m.getExchange().getBus()); diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwejws/JweJwsAlgorithmTest.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwejws/JweJwsAlgorithmTest.java index 7762317..2096a4f 100644 --- a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwejws/JweJwsAlgorithmTest.java +++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwejws/JweJwsAlgorithmTest.java @@ -313,6 +313,66 @@ public class JweJwsAlgorithmTest extends AbstractBusClientServerTestBase { assertNotEquals(response.getStatus(), 200); } + @org.junit.Test + public void testEncryptionPBES() throws Exception { + + URL busFile = JweJwsAlgorithmTest.class.getResource("client.xml"); + + List<Object> providers = new ArrayList<>(); + providers.add(new JacksonJsonProvider()); + providers.add(new JweWriterInterceptor()); + + String address = "http://localhost:" + PORT + "/jwepbes/bookstore/books"; + WebClient client = + WebClient.create(address, providers, busFile.toString()); + client.type("application/json").accept("application/json"); + + Map<String, Object> properties = new HashMap<>(); + properties.put("rs.security.encryption.content.algorithm", "A128GCM"); + properties.put("rs.security.encryption.key.algorithm", "PBES2-HS256+A128KW"); + String password = "123456789123456789"; + properties.put("rs.security.key.password.provider", new PrivateKeyPasswordProviderImpl(password)); + WebClient.getConfig(client).getRequestContext().putAll(properties); + + Response response = client.post(new Book("book", 123L)); + assertEquals(response.getStatus(), 200); + + Book returnedBook = response.readEntity(Book.class); + assertEquals(returnedBook.getName(), "book"); + assertEquals(returnedBook.getId(), 123L); + } + + @org.junit.Test + public void testEncryptionPBESDifferentCount() throws Exception { + + URL busFile = JweJwsAlgorithmTest.class.getResource("client.xml"); + + List<Object> providers = new ArrayList<>(); + providers.add(new JacksonJsonProvider()); + providers.add(new JweWriterInterceptor()); + + String address = "http://localhost:" + PORT + "/jwepbes/bookstore/books"; + WebClient client = + WebClient.create(address, providers, busFile.toString()); + client.type("application/json").accept("application/json"); + + Map<String, Object> properties = new HashMap<>(); + String password = "123456789123456789"; + properties.put("rs.security.encryption.content.algorithm", "A128GCM"); + properties.put("rs.security.encryption.key.algorithm", "PBES2-HS256+A128KW"); + properties.put("rs.security.key.password.provider", new PrivateKeyPasswordProviderImpl(password)); + properties.put("rs.security.encryption.pbes2.count", "1000"); + WebClient.getConfig(client).getRequestContext().putAll(properties); + + Response response = client.post(new Book("book", 123L)); + assertEquals(response.getStatus(), 200); + + Book returnedBook = response.readEntity(Book.class); + assertEquals(returnedBook.getName(), "book"); + assertEquals(returnedBook.getId(), 123L); + } + + // // Signature tests // diff --git a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwejws/algorithms-server.xml b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwejws/algorithms-server.xml index 08fbb88..5c875a2 100644 --- a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwejws/algorithms-server.xml +++ b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwejws/algorithms-server.xml @@ -61,6 +61,25 @@ under the License. </jaxrs:properties> </jaxrs:server> + <bean id="pbesKeyProvider" class="org.apache.cxf.systest.jaxrs.security.jose.jwejws.PrivateKeyPasswordProviderImpl"> + <constructor-arg> + <value>123456789123456789</value> + </constructor-arg> + </bean> + + <jaxrs:server address="http://localhost:${testutil.ports.jaxrs-jwejws-algorithms}/jwepbes"> + <jaxrs:serviceBeans> + <ref bean="serviceBean"/> + </jaxrs:serviceBeans> + <jaxrs:providers> + <ref bean="jweInFilter"/> + </jaxrs:providers> + <jaxrs:properties> + <entry key="rs.security.encryption.key.algorithm" value="PBES2-HS256+A128KW"/> + <entry key="rs.security.key.password.provider" value-ref="pbesKeyProvider"/> + </jaxrs:properties> + </jaxrs:server> + <bean id="jwsInFilter" class="org.apache.cxf.rs.security.jose.jaxrs.JwsContainerRequestFilter"/> <jaxrs:server address="http://localhost:${testutil.ports.jaxrs-jwejws-algorithms}/jws"> -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
