Return-Path: X-Original-To: apmail-cxf-commits-archive@www.apache.org Delivered-To: apmail-cxf-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id E67D5CDA7 for ; Tue, 30 Dec 2014 14:56:53 +0000 (UTC) Received: (qmail 96722 invoked by uid 500); 30 Dec 2014 14:56:54 -0000 Delivered-To: apmail-cxf-commits-archive@cxf.apache.org Received: (qmail 96662 invoked by uid 500); 30 Dec 2014 14:56:54 -0000 Mailing-List: contact commits-help@cxf.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cxf.apache.org Delivered-To: mailing list commits@cxf.apache.org Received: (qmail 96651 invoked by uid 99); 30 Dec 2014 14:56:54 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 30 Dec 2014 14:56:54 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 1496FA39CF4; Tue, 30 Dec 2014 14:56:54 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: sergeyb@apache.org To: commits@cxf.apache.org Message-Id: <17820e707ec444f6b90c9645104ef600@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: cxf git commit: Prototyping the code for ECDH, the last unsupported JWE algorithm Date: Tue, 30 Dec 2014 14:56:54 +0000 (UTC) Repository: cxf Updated Branches: refs/heads/master 5ec2f2fa4 -> a3bf2a80b Prototyping the code for ECDH, the last unsupported JWE algorithm Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/a3bf2a80 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/a3bf2a80 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/a3bf2a80 Branch: refs/heads/master Commit: a3bf2a80bafa95ec2ccdd2b28ead26c13866acd7 Parents: 5ec2f2f Author: Sergey Beryozkin Authored: Tue Dec 30 14:56:30 2014 +0000 Committer: Sergey Beryozkin Committed: Tue Dec 30 14:56:30 2014 +0000 ---------------------------------------------------------------------- .../cxf/common/util/crypto/CryptoUtils.java | 58 +++++---- .../cxf/rs/security/jose/JoseConstants.java | 3 + .../cxf/rs/security/jose/JoseHeaders.java | 11 +- .../cxf/rs/security/jose/jwk/JwkUtils.java | 18 +++ .../jose/jwe/JweCompactReaderWriterTest.java | 129 +++++++++++++++++++ 5 files changed, 195 insertions(+), 24 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/a3bf2a80/core/src/main/java/org/apache/cxf/common/util/crypto/CryptoUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/cxf/common/util/crypto/CryptoUtils.java b/core/src/main/java/org/apache/cxf/common/util/crypto/CryptoUtils.java index 2408a0b..1b732e5 100644 --- a/core/src/main/java/org/apache/cxf/common/util/crypto/CryptoUtils.java +++ b/core/src/main/java/org/apache/cxf/common/util/crypto/CryptoUtils.java @@ -115,8 +115,8 @@ public final class CryptoUtils { public static RSAPublicKey getRSAPublicKey(KeyFactory factory, byte[] modulusBytes, byte[] publicExponentBytes) { - BigInteger modulus = new BigInteger(1, modulusBytes); - BigInteger publicExponent = new BigInteger(1, publicExponentBytes); + BigInteger modulus = toBigInteger(modulusBytes); + BigInteger publicExponent = toBigInteger(publicExponentBytes); try { return (RSAPublicKey)factory.generatePublic( new RSAPublicKeySpec(modulus, publicExponent)); @@ -137,8 +137,8 @@ public final class CryptoUtils { public static RSAPrivateKey getRSAPrivateKey(byte[] modulusBytes, byte[] privateExponentBytes) { - BigInteger modulus = new BigInteger(1, modulusBytes); - BigInteger privateExponent = new BigInteger(1, privateExponentBytes); + BigInteger modulus = toBigInteger(modulusBytes); + BigInteger privateExponent = toBigInteger(privateExponentBytes); try { KeyFactory factory = KeyFactory.getInstance("RSA"); return (RSAPrivateKey)factory.generatePrivate( @@ -180,14 +180,14 @@ public final class CryptoUtils { byte[] primeExpQBytes, byte[] crtCoefficientBytes) { //CHECKSTYLE:ON - BigInteger modulus = new BigInteger(1, modulusBytes); - BigInteger publicExponent = new BigInteger(1, publicExponentBytes); - BigInteger privateExponent = new BigInteger(1, privateExponentBytes); - BigInteger primeP = new BigInteger(1, primePBytes); - BigInteger primeQ = new BigInteger(1, primeQBytes); - BigInteger primeExpP = new BigInteger(1, primeExpPBytes); - BigInteger primeExpQ = new BigInteger(1, primeExpQBytes); - BigInteger crtCoefficient = new BigInteger(1, crtCoefficientBytes); + BigInteger modulus = toBigInteger(modulusBytes); + BigInteger publicExponent = toBigInteger(publicExponentBytes); + BigInteger privateExponent = toBigInteger(privateExponentBytes); + BigInteger primeP = toBigInteger(primePBytes); + BigInteger primeQ = toBigInteger(primeQBytes); + BigInteger primeExpP = toBigInteger(primeExpPBytes); + BigInteger primeExpQ = toBigInteger(primeExpQBytes); + BigInteger crtCoefficient = toBigInteger(crtCoefficientBytes); try { KeyFactory factory = KeyFactory.getInstance("RSA"); return (RSAPrivateKey)factory.generatePrivate( @@ -215,7 +215,7 @@ public final class CryptoUtils { try { ECParameterSpec params = getECParameterSpec(curve, true); ECPrivateKeySpec keySpec = new ECPrivateKeySpec( - new BigInteger(1, privateKey), params); + toBigInteger(privateKey), params); KeyFactory kf = KeyFactory.getInstance("EC"); return (ECPrivateKey) kf.generatePrivate(keySpec); @@ -225,16 +225,24 @@ public final class CryptoUtils { } private static ECParameterSpec getECParameterSpec(String curve, boolean isPrivate) throws Exception { - KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); - ECGenParameterSpec kpgparams = new ECGenParameterSpec("sec" - + curve.toLowerCase().replace("-", "") - + "r1"); - kpg.initialize(kpgparams); - KeyPair pair = kpg.generateKeyPair(); + KeyPair pair = generateECKeyPair(curve); return isPrivate ? ((ECPublicKey) pair.getPublic()).getParams() : ((ECPrivateKey) pair.getPrivate()).getParams(); } + public static KeyPair generateECKeyPair(String curve) { + try { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); + ECGenParameterSpec kpgparams = new ECGenParameterSpec("sec" + + curve.toLowerCase().replace("-", "") + + "r1"); + kpg.initialize(kpgparams); + return kpg.generateKeyPair(); + } catch (Exception ex) { + throw new SecurityException(ex); + } + } + public static ECPublicKey getECPublicKey(String curve, String encodedXPoint, String encodedYPoint) { try { return getECPublicKey(curve, @@ -248,8 +256,8 @@ public final class CryptoUtils { try { ECParameterSpec params = getECParameterSpec(curve, false); - ECPoint ecPoint = new ECPoint(new BigInteger(1, xPoint), - new BigInteger(1, yPoint)); + ECPoint ecPoint = new ECPoint(toBigInteger(xPoint), + toBigInteger(yPoint)); ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, params); KeyFactory kf = KeyFactory.getInstance("EC"); return (ECPublicKey) kf.generatePublic(keySpec); @@ -258,7 +266,13 @@ public final class CryptoUtils { throw new SecurityException(ex); } } - + private static BigInteger toBigInteger(byte[] bytes) { + if (bytes[0] == -128) { + return new BigInteger(bytes); + } else { + return new BigInteger(1, bytes); + } + } public static AlgorithmParameterSpec getContentEncryptionCipherSpec(int authTagLength, byte[] iv) { if (authTagLength > 0) { return CryptoUtils.getGCMParameterSpec(authTagLength, iv); http://git-wip-us.apache.org/repos/asf/cxf/blob/a3bf2a80/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/JoseConstants.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/JoseConstants.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/JoseConstants.java index cd719d4..39b86c7 100644 --- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/JoseConstants.java +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/JoseConstants.java @@ -68,6 +68,9 @@ public final class JoseConstants { public static final String A128GCMKW_ALGO = "A128GCMKW"; public static final String A192GCMKW_ALGO = "A192GCMKW"; public static final String A256GCMKW_ALGO = "A256GCMKW"; + + public static final String ECDH_ES_DIRECT_ALGO = "ECDH-ES"; + public static final String PBES2_HS256_A128KW_ALGO = "PBES2-HS256+A128KW"; public static final String PBES2_HS384_A192KW_ALGO = "PBES2-HS384+A192KW"; public static final String PBES2_HS512_A256KW_ALGO = "PBES2-HS512+A256KW"; http://git-wip-us.apache.org/repos/asf/cxf/blob/a3bf2a80/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/JoseHeaders.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/JoseHeaders.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/JoseHeaders.java index ac186d2..819e408 100644 --- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/JoseHeaders.java +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/JoseHeaders.java @@ -111,11 +111,18 @@ public class JoseHeaders extends JsonMapObject { } public void setJsonWebKey(JsonWebKey key) { - setHeader(JoseConstants.HEADER_JSON_WEB_KEY, key); + setJsonWebKey(JoseConstants.HEADER_JSON_WEB_KEY, key); + } + + public void setJsonWebKey(String headerName, JsonWebKey key) { + setHeader(headerName, key); } public JsonWebKey getJsonWebKey() { - Object jsonWebKey = getHeader(JoseConstants.HEADER_JSON_WEB_KEY); + return getJsonWebKey(JoseConstants.HEADER_JSON_WEB_KEY); + } + public JsonWebKey getJsonWebKey(String headerName) { + Object jsonWebKey = getHeader(headerName); if (jsonWebKey == null || jsonWebKey instanceof JsonWebKey) { return (JsonWebKey)jsonWebKey; } http://git-wip-us.apache.org/repos/asf/cxf/blob/a3bf2a80/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java index 8d1f107..2f6cf77 100644 --- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java @@ -319,6 +319,24 @@ public final class JwkUtils { List base64EncodedChain = jwk.getX509Chain(); return KeyManagementUtils.toX509CertificateChain(base64EncodedChain); } + public static JsonWebKey fromECPublicKey(ECPublicKey pk, String curve) { + JsonWebKey jwk = new JsonWebKey(); + jwk.setKeyType(JsonWebKey.KEY_TYPE_ELLIPTIC); + jwk.setProperty(JsonWebKey.EC_CURVE, curve); + jwk.setProperty(JsonWebKey.EC_X_COORDINATE, + Base64UrlUtility.encode(pk.getW().getAffineX().toByteArray())); + jwk.setProperty(JsonWebKey.EC_Y_COORDINATE, + Base64UrlUtility.encode(pk.getW().getAffineY().toByteArray())); + return jwk; + } + public static JsonWebKey fromECPrivateKey(ECPrivateKey pk, String curve) { + JsonWebKey jwk = new JsonWebKey(); + jwk.setKeyType(JsonWebKey.KEY_TYPE_ELLIPTIC); + jwk.setProperty(JsonWebKey.EC_CURVE, curve); + jwk.setProperty(JsonWebKey.EC_PRIVATE_KEY, + Base64UrlUtility.encode(pk.getS().toByteArray())); + return jwk; + } public static JsonWebKey fromRSAPublicKey(RSAPublicKey pk, String algo) { JsonWebKey jwk = prepareRSAJwk(pk.getModulus(), algo); String encodedPublicExponent = Base64UrlUtility.encode(pk.getPublicExponent().toByteArray()); http://git-wip-us.apache.org/repos/asf/cxf/blob/a3bf2a80/rt/rs/security/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweCompactReaderWriterTest.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweCompactReaderWriterTest.java b/rt/rs/security/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweCompactReaderWriterTest.java index b3be0a6..9204f3a 100644 --- a/rt/rs/security/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweCompactReaderWriterTest.java +++ b/rt/rs/security/jose/src/test/java/org/apache/cxf/rs/security/jose/jwe/JweCompactReaderWriterTest.java @@ -18,17 +18,28 @@ */ package org.apache.cxf.rs.security.jose.jwe; +import java.nio.ByteBuffer; import java.security.Security; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.util.Arrays; import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import org.apache.cxf.common.util.Base64UrlUtility; +import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.common.util.crypto.CryptoUtils; +import org.apache.cxf.common.util.crypto.MessageDigestUtils; import org.apache.cxf.rs.security.jose.JoseConstants; +import org.apache.cxf.rs.security.jose.JoseHeaders; +import org.apache.cxf.rs.security.jose.JoseUtils; import org.apache.cxf.rs.security.jose.jwa.Algorithm; +import org.apache.cxf.rs.security.jose.jwk.JsonWebKey; +import org.apache.cxf.rs.security.jose.jwk.JwkUtils; import org.apache.cxf.rs.security.jose.jws.JwsCompactReaderWriterTest; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -116,6 +127,124 @@ public class JweCompactReaderWriterTest extends Assert { assertEquals(specPlainText, decryptedText); } @Test + public void testECDHESDirectKeyEncryption() throws Exception { + ECPrivateKey alicePrivateKey = + CryptoUtils.getECPrivateKey(JsonWebKey.EC_CURVE_P256, + "0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo"); + ECPublicKey alicePublicKey = + CryptoUtils.getECPublicKey(JsonWebKey.EC_CURVE_P256, + "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", + "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps"); + + ECPublicKey bobPublicKey = + CryptoUtils.getECPublicKey(JsonWebKey.EC_CURVE_P256, + "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ", + "e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck"); + + byte[] keyZ = generateKeyZ(alicePrivateKey, bobPublicKey); + byte[] apuBytes = StringUtils.toBytesUTF8("Alice"); + byte[] apvBytes = StringUtils.toBytesUTF8("Bob"); + + byte[] derivedKey = calculateDerivedKey(keyZ, + Algorithm.A128GCM.getJwtName(), + apuBytes, + apvBytes, + Algorithm.A128GCM.getKeySizeBits()); + assertEquals("VqqN6vgjbSBcIijNcacQGg", Base64UrlUtility.encode(derivedKey)); + + JweHeaders headers = new JweHeaders(); + headers.setAlgorithm(JoseConstants.ECDH_ES_DIRECT_ALGO); + headers.setContentEncryptionAlgorithm(Algorithm.A128GCM.getJwtName()); + headers.setHeader("apu", Base64UrlUtility.encode(apuBytes)); + headers.setHeader("apv", Base64UrlUtility.encode(apvBytes)); + headers.setJsonWebKey("epv", JwkUtils.fromECPublicKey(alicePublicKey, JsonWebKey.EC_CURVE_P256)); + + ECPrivateKey bobPrivateKey = + CryptoUtils.getECPrivateKey(JsonWebKey.EC_CURVE_P256, + "VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw"); + byte[] derivedKey2 = calculateDerivedKeyFromHeaders(bobPrivateKey, + headers, + headers.getContentEncryptionAlgorithm(), + Algorithm.A128GCM.getKeySizeBits()); + assertTrue(Arrays.equals(derivedKey2, derivedKey)); + } + private static byte[] calculateDerivedKeyFromHeaders(ECPrivateKey privateKey, + JoseHeaders headers, + String algoName, + int algoKeyLen) { + JsonWebKey ephemeralJwk = headers.getJsonWebKey("epv"); + byte[] keyZ = generateKeyZ(privateKey, JwkUtils.toECPublicKey(ephemeralJwk)); + String apuHeader = (String)headers.getHeader("apu"); + byte[] apuBytes = apuHeader == null ? null : JoseUtils.decode(apuHeader); + String apvHeader = (String)headers.getHeader("apv"); + byte[] apvBytes = apvHeader == null ? null : JoseUtils.decode(apvHeader); + + return calculateDerivedKey(keyZ, + algoName, + apuBytes, + apvBytes, + algoKeyLen); + } + + private static byte[] calculateDerivedKey(byte[] keyZ, + String algoName, + byte[] apuBytes, + byte[] apvBytes, + int algoKeyBitLen) { + final byte[] emptyPartyInfo = new byte[4]; + + if (apuBytes != null && apvBytes != null && Arrays.equals(apuBytes, apvBytes)) { + throw new SecurityException(); + } + byte[] algorithmId = concatenateDatalenAndData(StringUtils.toBytesASCII(algoName)); + byte[] partyUInfo = apuBytes == null ? emptyPartyInfo : concatenateDatalenAndData(apuBytes); + byte[] partyVInfo = apvBytes == null ? emptyPartyInfo : concatenateDatalenAndData(apvBytes); + byte[] suppPubInfo = datalenToBytes(algoKeyBitLen); + + byte[] otherInfo = new byte[algorithmId.length + + partyUInfo.length + + partyVInfo.length + + suppPubInfo.length]; + System.arraycopy(algorithmId, 0, otherInfo, 0, algorithmId.length); + System.arraycopy(partyUInfo, 0, otherInfo, algorithmId.length, partyUInfo.length); + System.arraycopy(partyVInfo, 0, otherInfo, algorithmId.length + partyUInfo.length, partyVInfo.length); + System.arraycopy(suppPubInfo, 0, otherInfo, algorithmId.length + partyUInfo.length + partyVInfo.length, + suppPubInfo.length); + + + byte[] concatKDF = new byte[36 + otherInfo.length]; + concatKDF[3] = 1; + System.arraycopy(keyZ, 0, concatKDF, 4, keyZ.length); + System.arraycopy(otherInfo, 0, concatKDF, 36, otherInfo.length); + try { + byte[] round1Hash = MessageDigestUtils.createDigest(concatKDF, MessageDigestUtils.ALGO_SHA_256); + return Arrays.copyOf(round1Hash, algoKeyBitLen / 8); + } catch (Exception ex) { + throw new SecurityException(ex); + } + } + private static byte[] generateKeyZ(ECPrivateKey privateKey, ECPublicKey publicKey) { + try { + KeyAgreement ka = KeyAgreement.getInstance("ECDH"); + ka.init(privateKey); + ka.doPhase(publicKey, true); + return ka.generateSecret(); + } catch (Exception ex) { + throw new SecurityException(ex); + } + } + private static byte[] concatenateDatalenAndData(byte[] bytesASCII) { + final byte[] datalen = datalenToBytes(bytesASCII.length); + byte[] all = new byte[4 + bytesASCII.length]; + System.arraycopy(datalen, 0, all, 0, 4); + System.arraycopy(bytesASCII, 0, all, 4, bytesASCII.length); + return all; + } + private static byte[] datalenToBytes(int len) { + ByteBuffer buf = ByteBuffer.allocate(4); + return buf.putInt(len).array(); + } + @Test public void testEncryptDecryptRSA15WrapA128CBCHS256() throws Exception { final String specPlainText = "Live long and prosper."; JweHeaders headers = new JweHeaders();