http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkException.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkException.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkException.java new file mode 100644 index 0000000..da54572 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkException.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jwk; + +import org.apache.cxf.rs.security.jose.common.JoseException; + +public class JwkException extends JoseException { + + private static final long serialVersionUID = 4118589816228511524L; + public JwkException() { + + } + public JwkException(String error) { + super(error); + } + public JwkException(Throwable cause) { + super(cause); + } +}
http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkReaderWriter.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkReaderWriter.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkReaderWriter.java new file mode 100644 index 0000000..679b7aa --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkReaderWriter.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jwk; + + +public interface JwkReaderWriter { + String jwkToJson(JsonWebKey jwk); + JsonWebKey jsonToJwk(String jwkJson); + String jwkSetToJson(JsonWebKeys jwkSet); + JsonWebKeys jsonToJwkSet(String jwkSetJson); +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java new file mode 100644 index 0000000..cb76567 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/JwkUtils.java @@ -0,0 +1,531 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jwk; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.net.URI; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.crypto.SecretKey; + +import org.apache.cxf.Bus; +import org.apache.cxf.common.util.Base64UrlUtility; +import org.apache.cxf.common.util.StringUtils; +import org.apache.cxf.helpers.CastUtils; +import org.apache.cxf.helpers.IOUtils; +import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter; +import org.apache.cxf.message.Message; +import org.apache.cxf.message.MessageUtils; +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; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; +import org.apache.cxf.rs.security.jose.jwe.AesCbcHmacJweDecryption; +import org.apache.cxf.rs.security.jose.jwe.AesCbcHmacJweEncryption; +import org.apache.cxf.rs.security.jose.jwe.JweDecryptionProvider; +import org.apache.cxf.rs.security.jose.jwe.JweEncryptionProvider; +import org.apache.cxf.rs.security.jose.jwe.JweHeaders; +import org.apache.cxf.rs.security.jose.jwe.JweUtils; +import org.apache.cxf.rs.security.jose.jwe.KeyDecryptionProvider; +import org.apache.cxf.rs.security.jose.jwe.KeyEncryptionProvider; +import org.apache.cxf.rs.security.jose.jwe.PbesHmacAesWrapKeyDecryptionAlgorithm; +import org.apache.cxf.rs.security.jose.jwe.PbesHmacAesWrapKeyEncryptionAlgorithm; +import org.apache.cxf.rs.security.jose.jws.JwsUtils; +import org.apache.cxf.rt.security.crypto.CryptoUtils; +import org.apache.cxf.rt.security.crypto.MessageDigestUtils; + +public final class JwkUtils { + private static final Map<KeyType, List<String>> JWK_REQUIRED_FIELDS_MAP; + static { + JWK_REQUIRED_FIELDS_MAP = new HashMap<KeyType, List<String>>(); + JWK_REQUIRED_FIELDS_MAP.put(KeyType.RSA, Arrays.asList( + JsonWebKey.RSA_PUBLIC_EXP, JsonWebKey.KEY_TYPE, JsonWebKey.RSA_MODULUS)); + JWK_REQUIRED_FIELDS_MAP.put(KeyType.EC, Arrays.asList( + JsonWebKey.EC_CURVE, JsonWebKey.KEY_TYPE, JsonWebKey.EC_X_COORDINATE, JsonWebKey.EC_Y_COORDINATE)); + JWK_REQUIRED_FIELDS_MAP.put(KeyType.OCTET, Arrays.asList( + JsonWebKey.OCTET_KEY_VALUE, JsonWebKey.KEY_TYPE)); + } + private JwkUtils() { + + } + public static String getThumbprint(String keySequence) { + return getThumbprint(readJwkKey(keySequence)); + } + public static String getThumbprint(JsonWebKey key) { + List<String> fields = getRequiredFields(key.getKeyType()); + JsonWebKey thumbprintKey = new JsonWebKey(); + for (String f : fields) { + thumbprintKey.setProperty(f, key.getProperty(f)); + } + String json = new JsonMapObjectReaderWriter().toJson(thumbprintKey); + byte[] digest = MessageDigestUtils.createDigest(json, MessageDigestUtils.ALGO_SHA_256); + return Base64UrlUtility.encode(digest); + } + public static List<String> getRequiredFields(KeyType keyType) { + return JWK_REQUIRED_FIELDS_MAP.get(keyType); + } + public static JsonWebKey readJwkKey(URI uri) throws IOException { + return readJwkKey(uri.toURL().openStream()); + } + public static JsonWebKeys readJwkSet(URI uri) throws IOException { + return readJwkSet(uri.toURL().openStream()); + } + public static JsonWebKey readJwkKey(InputStream is) throws IOException { + return readJwkKey(IOUtils.readStringFromStream(is)); + } + public static JsonWebKeys readJwkSet(InputStream is) throws IOException { + return readJwkSet(IOUtils.readStringFromStream(is)); + } + public static JsonWebKey readJwkKey(String jwkJson) { + return new DefaultJwkReaderWriter().jsonToJwk(jwkJson); + } + public static JsonWebKeys readJwkSet(String jwksJson) { + return new DefaultJwkReaderWriter().jsonToJwkSet(jwksJson); + } + public static String jwkKeyToJson(JsonWebKey jwkKey) { + return new DefaultJwkReaderWriter().jwkToJson(jwkKey); + } + public static String jwkSetToJson(JsonWebKeys jwkSet) { + return new DefaultJwkReaderWriter().jwkSetToJson(jwkSet); + } + public static String encodeJwkKey(JsonWebKey jwkKey) { + return Base64UrlUtility.encode(jwkKeyToJson(jwkKey)); + } + public static String encodeJwkSet(JsonWebKeys jwkSet) { + return Base64UrlUtility.encode(jwkSetToJson(jwkSet)); + } + public static JsonWebKey decodeJwkKey(String jwkJson) { + return readJwkKey(JoseUtils.decodeToString(jwkJson)); + } + public static JsonWebKeys decodeJwkSet(String jwksJson) { + return readJwkSet(JoseUtils.decodeToString(jwksJson)); + } + public static String encryptJwkSet(JsonWebKeys jwkSet, char[] password) { + return encryptJwkSet(jwkSet, password, new DefaultJwkReaderWriter()); + } + public static String encryptJwkSet(JsonWebKeys jwkSet, char[] password, JwkReaderWriter writer) { + return encryptJwkSet(jwkSet, createDefaultEncryption(password), writer); + } + public static String encryptJwkSet(JsonWebKeys jwkSet, JweEncryptionProvider jwe, JwkReaderWriter writer) { + return jwe.encrypt(StringUtils.toBytesUTF8(writer.jwkSetToJson(jwkSet)), + toJweHeaders("jwk-set+json")); + } + public static String encryptJwkSet(JsonWebKeys jwkSet, PublicKey key, KeyAlgorithm keyAlgo, + ContentAlgorithm contentAlgo) { + return JweUtils.encrypt(key, keyAlgo, contentAlgo, StringUtils.toBytesUTF8(jwkSetToJson(jwkSet)), + "jwk-set+json"); + } + public static String signJwkSet(JsonWebKeys jwkSet, PrivateKey key, SignatureAlgorithm algo) { + return JwsUtils.sign(key, algo, jwkSetToJson(jwkSet), "jwk-set+json"); + } + public static String encryptJwkSet(JsonWebKeys jwkSet, SecretKey key, KeyAlgorithm keyAlgo, + ContentAlgorithm contentAlgo) { + return JweUtils.encrypt(key, keyAlgo, contentAlgo, StringUtils.toBytesUTF8(jwkSetToJson(jwkSet)), + "jwk-set+json"); + } + public static JsonWebKeys decryptJwkSet(String jsonJwkSet, char[] password) { + return decryptJwkSet(jsonJwkSet, password, new DefaultJwkReaderWriter()); + } + public static JsonWebKeys decryptJwkSet(String jsonJwkSet, char[] password, JwkReaderWriter reader) { + return decryptJwkSet(jsonJwkSet, createDefaultDecryption(password), reader); + } + public static JsonWebKeys decryptJwkSet(String jsonJwkSet, JweDecryptionProvider jwe, JwkReaderWriter reader) { + return reader.jsonToJwkSet(jwe.decrypt(jsonJwkSet).getContentText()); + } + public static JsonWebKeys decryptJwkSet(PrivateKey key, KeyAlgorithm keyAlgo, ContentAlgorithm ctAlgo, + String jsonJwkSet) { + return readJwkSet(toString(JweUtils.decrypt(key, keyAlgo, ctAlgo, jsonJwkSet))); + } + public static JsonWebKeys verifyJwkSet(PublicKey key, SignatureAlgorithm keyAlgo, String jsonJwk) { + return readJwkSet(JwsUtils.verify(key, keyAlgo, jsonJwk)); + } + public static JsonWebKeys decryptJwkSet(SecretKey key, KeyAlgorithm keyAlgo, ContentAlgorithm ctAlgo, + String jsonJwkSet) { + return readJwkSet(toString(JweUtils.decrypt(key, keyAlgo, ctAlgo, jsonJwkSet))); + } + public static JsonWebKeys decryptJwkSet(InputStream is, char[] password) throws IOException { + return decryptJwkSet(is, password, new DefaultJwkReaderWriter()); + } + public static JsonWebKeys decryptJwkSet(InputStream is, char[] password, JwkReaderWriter reader) + throws IOException { + return decryptJwkSet(is, createDefaultDecryption(password), reader); + } + public static JsonWebKeys decryptJwkSet(InputStream is, JweDecryptionProvider jwe, JwkReaderWriter reader) + throws IOException { + return reader.jsonToJwkSet(jwe.decrypt(IOUtils.readStringFromStream(is)).getContentText()); + } + public static String encryptJwkKey(JsonWebKey jwk, char[] password) { + return encryptJwkKey(jwk, password, new DefaultJwkReaderWriter()); + } + public static String encryptJwkKey(JsonWebKey jwkKey, char[] password, JwkReaderWriter writer) { + return encryptJwkKey(jwkKey, createDefaultEncryption(password), writer); + } + public static String encryptJwkKey(JsonWebKey jwkKey, JweEncryptionProvider jwe, JwkReaderWriter writer) { + return jwe.encrypt(StringUtils.toBytesUTF8(writer.jwkToJson(jwkKey)), + toJweHeaders("jwk+json")); + } + public static String encryptJwkKey(JsonWebKey jwkKey, PublicKey key, KeyAlgorithm keyAlgo, + ContentAlgorithm contentAlgo) { + return JweUtils.encrypt(key, keyAlgo, contentAlgo, StringUtils.toBytesUTF8(jwkKeyToJson(jwkKey)), + "jwk+json"); + } + public static String encryptJwkKey(JsonWebKey jwkKey, SecretKey key, KeyAlgorithm keyAlgo, + ContentAlgorithm contentAlgo) { + return JweUtils.encrypt(key, keyAlgo, contentAlgo, StringUtils.toBytesUTF8(jwkKeyToJson(jwkKey)), + "jwk+json"); + } + public static String signJwkKey(JsonWebKey jwkKey, PrivateKey key, SignatureAlgorithm algo) { + return JwsUtils.sign(key, algo, jwkKeyToJson(jwkKey), "jwk+json"); + } + public static JsonWebKey decryptJwkKey(String jsonJwkKey, char[] password) { + return decryptJwkKey(jsonJwkKey, password, new DefaultJwkReaderWriter()); + } + public static JsonWebKey decryptJwkKey(String jsonJwkKey, char[] password, JwkReaderWriter reader) { + return decryptJwkKey(jsonJwkKey, createDefaultDecryption(password), reader); + } + public static JsonWebKey decryptJwkKey(PrivateKey key, KeyAlgorithm keyAlgo, ContentAlgorithm ctAlgo, + String jsonJwk) { + return readJwkKey(toString(JweUtils.decrypt(key, keyAlgo, ctAlgo, jsonJwk))); + } + public static JsonWebKey verifyJwkKey(PublicKey key, SignatureAlgorithm keyAlgo, String jsonJwk) { + return readJwkKey(JwsUtils.verify(key, keyAlgo, jsonJwk)); + } + public static JsonWebKey decryptJwkKey(SecretKey key, KeyAlgorithm keyAlgo, ContentAlgorithm ctAlgo, + String jsonJwk) { + return readJwkKey(toString(JweUtils.decrypt(key, keyAlgo, ctAlgo, jsonJwk))); + } + public static JsonWebKey decryptJwkKey(String jsonJwkKey, JweDecryptionProvider jwe, JwkReaderWriter reader) { + return reader.jsonToJwk(jwe.decrypt(jsonJwkKey).getContentText()); + } + public static JsonWebKey decryptJwkKey(InputStream is, char[] password) throws IOException { + return decryptJwkKey(is, password, new DefaultJwkReaderWriter()); + } + public static JsonWebKey decryptJwkKey(InputStream is, char[] password, JwkReaderWriter reader) + throws IOException { + return decryptJwkKey(is, createDefaultDecryption(password), reader); + } + public static JsonWebKey decryptJwkKey(InputStream is, JweDecryptionProvider jwe, JwkReaderWriter reader) + throws IOException { + return reader.jsonToJwk(jwe.decrypt(IOUtils.readStringFromStream(is)).getContentText()); + } + public static JsonWebKeys loadJwkSet(Message m, Properties props, PrivateKeyPasswordProvider cb) { + return loadJwkSet(m, props, cb, new DefaultJwkReaderWriter()); + } + public static JsonWebKeys loadJwkSet(Message m, Properties props, PrivateKeyPasswordProvider cb, + JwkReaderWriter reader) { + String key = (String)props.get(JoseConstants.RSSEC_KEY_STORE_FILE); + JsonWebKeys jwkSet = key != null ? (JsonWebKeys)m.getExchange().get(key) : null; + if (jwkSet == null) { + jwkSet = loadJwkSet(props, m.getExchange().getBus(), cb, reader); + if (key != null) { + m.getExchange().put(key, jwkSet); + } + } + return jwkSet; + } + public static JsonWebKeys loadJwkSet(Properties props, Bus bus, PrivateKeyPasswordProvider cb) { + return loadJwkSet(props, bus, cb, new DefaultJwkReaderWriter()); + } + public static JsonWebKeys loadJwkSet(Properties props, Bus bus, PrivateKeyPasswordProvider cb, + JwkReaderWriter reader) { + JweDecryptionProvider decryption = cb != null + ? new AesCbcHmacJweDecryption(new PbesHmacAesWrapKeyDecryptionAlgorithm(cb.getPassword(props))) : null; + return loadJwkSet(props, bus, decryption, reader); + } + public static JsonWebKeys loadJwkSet(Properties props, Bus bus, JweDecryptionProvider jwe, JwkReaderWriter reader) { + String keyContent = null; + String keyStoreLoc = props.getProperty(JoseConstants.RSSEC_KEY_STORE_FILE); + if (keyStoreLoc != null) { + try { + InputStream is = JoseUtils.getResourceStream(keyStoreLoc, bus); + if (is == null) { + throw new JwkException("Error in loading keystore location: " + keyStoreLoc); + } + keyContent = IOUtils.readStringFromStream(is); + } catch (Exception ex) { + throw new JwkException(ex); + } + } else { + keyContent = props.getProperty(JoseConstants.RSSEC_KEY_STORE_JWKSET); + if (keyContent == null) { + keyContent = props.getProperty(JoseConstants.RSSEC_KEY_STORE_JWKKEY); + } + } + if (jwe != null) { + keyContent = jwe.decrypt(keyContent).getContentText(); + } + if (props.getProperty(JoseConstants.RSSEC_KEY_STORE_JWKKEY) == null) { + return reader.jsonToJwkSet(keyContent); + } else { + JsonWebKey key = reader.jsonToJwk(keyContent); + JsonWebKeys keys = new JsonWebKeys(); + keys.setKeys(Collections.singletonList(key)); + return keys; + } + } + public static JsonWebKey loadJsonWebKey(Message m, Properties props, KeyOperation keyOper) { + return loadJsonWebKey(m, props, keyOper, null); + } + public static JsonWebKey loadJsonWebKey(Message m, Properties props, KeyOperation keyOper, String inHeaderKid) { + return loadJsonWebKey(m, props, keyOper, inHeaderKid, new DefaultJwkReaderWriter()); + } + public static JsonWebKey loadJsonWebKey(Message m, Properties props, KeyOperation keyOper, String inHeaderKid, + JwkReaderWriter reader) { + PrivateKeyPasswordProvider cb = KeyManagementUtils.loadPasswordProvider(m, props, keyOper); + JsonWebKeys jwkSet = loadJwkSet(m, props, cb, reader); + String kid = null; + if (inHeaderKid != null + && MessageUtils.getContextualBoolean(m, JoseConstants.RSSEC_ACCEPT_PUBLIC_KEY_PROP, true)) { + kid = inHeaderKid; + } else { + kid = KeyManagementUtils.getKeyId(m, props, JoseConstants.RSSEC_KEY_STORE_ALIAS, keyOper); + } + if (kid != null) { + return jwkSet.getKey(kid); + } else if (keyOper != null) { + List<JsonWebKey> keys = jwkSet.getKeyOperationMap().get(keyOper); + if (keys != null && keys.size() == 1) { + return keys.get(0); + } + } + return null; + } + public static List<JsonWebKey> loadJsonWebKeys(Message m, Properties props, KeyOperation keyOper) { + return loadJsonWebKeys(m, props, keyOper, new DefaultJwkReaderWriter()); + } + + public static List<JsonWebKey> loadJsonWebKeys(Message m, Properties props, + KeyOperation keyOper, + JwkReaderWriter reader) { + PrivateKeyPasswordProvider cb = KeyManagementUtils.loadPasswordProvider(m, props, keyOper); + JsonWebKeys jwkSet = loadJwkSet(m, props, cb, reader); + String kid = KeyManagementUtils.getKeyId(m, props, JoseConstants.RSSEC_KEY_STORE_ALIAS, keyOper); + if (kid != null) { + return Collections.singletonList(jwkSet.getKey(kid)); + } + String kids = KeyManagementUtils.getKeyId(m, props, JoseConstants.RSSEC_KEY_STORE_ALIASES, keyOper); + if (kids != null) { + String[] values = kids.split(","); + List<JsonWebKey> keys = new ArrayList<JsonWebKey>(values.length); + for (String value : values) { + keys.add(jwkSet.getKey(value)); + } + return keys; + } + if (keyOper != null) { + List<JsonWebKey> keys = jwkSet.getKeyOperationMap().get(keyOper); + if (keys != null && keys.size() == 1) { + return Collections.singletonList(keys.get(0)); + } + } + return null; + } + public static RSAPublicKey toRSAPublicKey(JsonWebKey jwk) { + return toRSAPublicKey(jwk, false); + } + public static RSAPublicKey toRSAPublicKey(JsonWebKey jwk, boolean checkX509) { + String encodedModulus = (String)jwk.getProperty(JsonWebKey.RSA_MODULUS); + String encodedPublicExponent = (String)jwk.getProperty(JsonWebKey.RSA_PUBLIC_EXP); + if (encodedModulus != null) { + return CryptoUtils.getRSAPublicKey(encodedModulus, encodedPublicExponent); + } else if (checkX509) { + List<X509Certificate> chain = toX509CertificateChain(jwk); + return (RSAPublicKey)chain.get(0).getPublicKey(); + } + return null; + } + public static List<X509Certificate> toX509CertificateChain(JsonWebKey jwk) { + List<String> base64EncodedChain = jwk.getX509Chain(); + return KeyManagementUtils.toX509CertificateChain(base64EncodedChain); + } + public static JsonWebKey fromECPublicKey(ECPublicKey pk, String curve) { + JsonWebKey jwk = new JsonWebKey(); + jwk.setKeyType(KeyType.EC); + 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(KeyType.EC); + 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()); + jwk.setProperty(JsonWebKey.RSA_PUBLIC_EXP, encodedPublicExponent); + return jwk; + } + public static JsonWebKey fromX509CertificateChain(List<X509Certificate> chain, String algo) { + JsonWebKey jwk = new JsonWebKey(); + jwk.setAlgorithm(algo); + List<String> encodedChain = KeyManagementUtils.encodeX509CertificateChain(chain); + jwk.setX509Chain(encodedChain); + return jwk; + } + + public static RSAPrivateKey toRSAPrivateKey(JsonWebKey jwk) { + String encodedModulus = (String)jwk.getProperty(JsonWebKey.RSA_MODULUS); + String encodedPrivateExponent = (String)jwk.getProperty(JsonWebKey.RSA_PRIVATE_EXP); + String encodedPrimeP = (String)jwk.getProperty(JsonWebKey.RSA_FIRST_PRIME_FACTOR); + if (encodedPrimeP == null) { + return CryptoUtils.getRSAPrivateKey(encodedModulus, encodedPrivateExponent); + } else { + String encodedPublicExponent = (String)jwk.getProperty(JsonWebKey.RSA_PUBLIC_EXP); + String encodedPrimeQ = (String)jwk.getProperty(JsonWebKey.RSA_SECOND_PRIME_FACTOR); + String encodedPrimeExpP = (String)jwk.getProperty(JsonWebKey.RSA_FIRST_PRIME_CRT); + String encodedPrimeExpQ = (String)jwk.getProperty(JsonWebKey.RSA_SECOND_PRIME_CRT); + String encodedCrtCoefficient = (String)jwk.getProperty(JsonWebKey.RSA_FIRST_CRT_COEFFICIENT); + return CryptoUtils.getRSAPrivateKey(encodedModulus, + encodedPublicExponent, + encodedPrivateExponent, + encodedPrimeP, + encodedPrimeQ, + encodedPrimeExpP, + encodedPrimeExpQ, + encodedCrtCoefficient); + } + } + public static JsonWebKey fromRSAPrivateKey(RSAPrivateKey pk, String algo) { + JsonWebKey jwk = prepareRSAJwk(pk.getModulus(), algo); + String encodedPrivateExponent = Base64UrlUtility.encode(pk.getPrivateExponent().toByteArray()); + jwk.setProperty(JsonWebKey.RSA_PRIVATE_EXP, encodedPrivateExponent); + if (pk instanceof RSAPrivateCrtKey) { + RSAPrivateCrtKey pkCrt = (RSAPrivateCrtKey)pk; + jwk.setProperty(JsonWebKey.RSA_FIRST_PRIME_FACTOR, + Base64UrlUtility.encode(pkCrt.getPrimeP().toByteArray())); + jwk.setProperty(JsonWebKey.RSA_SECOND_PRIME_FACTOR, + Base64UrlUtility.encode(pkCrt.getPrimeQ().toByteArray())); + jwk.setProperty(JsonWebKey.RSA_FIRST_PRIME_CRT, + Base64UrlUtility.encode(pkCrt.getPrimeExponentP().toByteArray())); + jwk.setProperty(JsonWebKey.RSA_SECOND_PRIME_CRT, + Base64UrlUtility.encode(pkCrt.getPrimeExponentQ().toByteArray())); + jwk.setProperty(JsonWebKey.RSA_FIRST_CRT_COEFFICIENT, + Base64UrlUtility.encode(pkCrt.getCrtCoefficient().toByteArray())); + } + // "oth" can be populated too if needed + return jwk; + } + public static ECPublicKey toECPublicKey(JsonWebKey jwk) { + String eCurve = (String)jwk.getProperty(JsonWebKey.EC_CURVE); + String encodedXCoord = (String)jwk.getProperty(JsonWebKey.EC_X_COORDINATE); + String encodedYCoord = (String)jwk.getProperty(JsonWebKey.EC_Y_COORDINATE); + return CryptoUtils.getECPublicKey(eCurve, encodedXCoord, encodedYCoord); + } + public static ECPrivateKey toECPrivateKey(JsonWebKey jwk) { + String eCurve = (String)jwk.getProperty(JsonWebKey.EC_CURVE); + String encodedPrivateKey = (String)jwk.getProperty(JsonWebKey.EC_PRIVATE_KEY); + return CryptoUtils.getECPrivateKey(eCurve, encodedPrivateKey); + } + + public static SecretKey toSecretKey(JsonWebKey jwk) { + return CryptoUtils.createSecretKeySpec((String)jwk.getProperty(JsonWebKey.OCTET_KEY_VALUE), + AlgorithmUtils.toJavaName(jwk.getAlgorithm())); + } + public static JsonWebKey fromSecretKey(SecretKey secretKey, String algo) { + if (!AlgorithmUtils.isOctet(algo)) { + throw new JwkException("Invalid algorithm"); + } + JsonWebKey jwk = new JsonWebKey(); + jwk.setKeyType(KeyType.OCTET); + jwk.setAlgorithm(algo); + String encodedSecretKey = Base64UrlUtility.encode(secretKey.getEncoded()); + jwk.setProperty(JsonWebKey.OCTET_KEY_VALUE, encodedSecretKey); + return jwk; + } + + + private static JweEncryptionProvider createDefaultEncryption(char[] password) { + KeyEncryptionProvider keyEncryption = + new PbesHmacAesWrapKeyEncryptionAlgorithm(password, KeyAlgorithm.PBES2_HS256_A128KW); + return new AesCbcHmacJweEncryption(ContentAlgorithm.A128CBC_HS256, keyEncryption); + } + private static JweDecryptionProvider createDefaultDecryption(char[] password) { + KeyDecryptionProvider keyDecryption = new PbesHmacAesWrapKeyDecryptionAlgorithm(password); + return new AesCbcHmacJweDecryption(keyDecryption); + } + private static JsonWebKey prepareRSAJwk(BigInteger modulus, String algo) { + if (!AlgorithmUtils.isRsa(algo)) { + throw new JwkException("Invalid algorithm"); + } + JsonWebKey jwk = new JsonWebKey(); + jwk.setKeyType(KeyType.RSA); + jwk.setAlgorithm(algo); + String encodedModulus = Base64UrlUtility.encode(modulus.toByteArray()); + jwk.setProperty(JsonWebKey.RSA_MODULUS, encodedModulus); + return jwk; + } + private static String toString(byte[] bytes) { + try { + return new String(bytes, "UTF-8"); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + private static JweHeaders toJweHeaders(String ct) { + return new JweHeaders(Collections.<String, Object>singletonMap(JoseConstants.HEADER_CONTENT_TYPE, ct)); + } + public static void setPublicKeyInfo(JsonWebKey jwk, JoseHeaders headers, String algo, + boolean reportPublicKey, boolean reportPublicKeyId) { + if (reportPublicKey && KeyType.RSA.equals(jwk.getKeyType())) { + List<String> chain = CastUtils.cast((List<?>)jwk.getProperty("x5c")); + //TODO: if needed the chain can be reported as part of a 'jwk' property + if (chain != null) { + headers.setX509Chain(chain); + } else { + JsonWebKey jwkPublic = JwkUtils.fromRSAPublicKey(JwkUtils.toRSAPublicKey(jwk), algo); + if (reportPublicKeyId && jwk.getKeyId() != null) { + jwkPublic.setKeyId(jwk.getKeyId()); + } + headers.setJsonWebKey(jwkPublic); + } + } + if (reportPublicKeyId && jwk.getKeyId() != null) { + headers.setKeyId(jwk.getKeyId()); + } + + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/KeyOperation.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/KeyOperation.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/KeyOperation.java new file mode 100644 index 0000000..c258543 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/KeyOperation.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jwk; + + +public enum KeyOperation { + SIGN(JsonWebKey.KEY_OPER_SIGN), + VERIFY(JsonWebKey.KEY_OPER_VERIFY), + ENCRYPT(JsonWebKey.KEY_OPER_ENCRYPT), + DECRYPT(JsonWebKey.KEY_OPER_DECRYPT), + WRAPKEY(JsonWebKey.KEY_OPER_WRAP_KEY), + UNWRAPKEY(JsonWebKey.KEY_OPER_UNWRAP_KEY), + DERIVEKEY(JsonWebKey.KEY_OPER_DERIVE_KEY), + DERIVEBITS(JsonWebKey.KEY_OPER_DERIVE_BITS); + + private final String oper; + private KeyOperation(String oper) { + this.oper = oper; + } + public static KeyOperation getKeyOperation(String oper) { + if (oper == null) { + return null; + } + return valueOf(oper.toUpperCase()); + } + public String toString() { + return oper; + } + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/KeyType.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/KeyType.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/KeyType.java new file mode 100644 index 0000000..b10afb8 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/KeyType.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jwk; + + +public enum KeyType { + RSA(JsonWebKey.KEY_TYPE_RSA), + EC(JsonWebKey.KEY_TYPE_ELLIPTIC), + OCTET(JsonWebKey.KEY_TYPE_OCTET); + + private final String type; + private KeyType(String type) { + this.type = type; + } + public static KeyType getKeyType(String type) { + if (type == null) { + return null; + } else if (JsonWebKey.KEY_TYPE_OCTET.equals(type)) { + return OCTET; + } else { + return valueOf(type); + } + } + public String toString() { + return type; + } + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/PublicKeyUse.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/PublicKeyUse.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/PublicKeyUse.java new file mode 100644 index 0000000..9b46b4f --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jwk/PublicKeyUse.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jwk; + + +public enum PublicKeyUse { + SIGN(JsonWebKey.PUBLIC_KEY_USE_SIGN), + ENCRYPT(JsonWebKey.PUBLIC_KEY_USE_ENCRYPT); + + private final String use; + private PublicKeyUse(String use) { + this.use = use; + } + public static PublicKeyUse getPublicKeyUse(String use) { + if (use == null) { + return null; + } + if (JsonWebKey.PUBLIC_KEY_USE_SIGN.equals(use)) { + return SIGN; + } else if (JsonWebKey.PUBLIC_KEY_USE_ENCRYPT.equals(use)) { + return ENCRYPT; + } else { + return valueOf(use); + } + } + public String toString() { + return use; + } + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/AbstractJwsSignatureProvider.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/AbstractJwsSignatureProvider.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/AbstractJwsSignatureProvider.java new file mode 100644 index 0000000..df400fa --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/AbstractJwsSignatureProvider.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jws; + +import java.util.logging.Logger; + +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; + + +public abstract class AbstractJwsSignatureProvider implements JwsSignatureProvider { + protected static final Logger LOG = LogUtils.getL7dLogger(AbstractJwsSignatureProvider.class); + private SignatureAlgorithm algorithm; + + protected AbstractJwsSignatureProvider(SignatureAlgorithm algo) { + this.algorithm = algo; + } + + protected JwsHeaders prepareHeaders(JwsHeaders headers) { + if (headers == null) { + headers = new JwsHeaders(); + } + SignatureAlgorithm sigAlgo = headers.getSignatureAlgorithm(); + if (sigAlgo != null) { + checkAlgorithm(sigAlgo.getJwaName()); + } else { + checkAlgorithm(algorithm.getJwaName()); + headers.setSignatureAlgorithm(algorithm); + } + return headers; + } + @Override + public SignatureAlgorithm getAlgorithm() { + return algorithm; + } + @Override + public byte[] sign(JwsHeaders headers, byte[] content) { + JwsSignature sig = createJwsSignature(headers); + sig.update(content, 0, content.length); + return sig.sign(); + } + @Override + public JwsSignature createJwsSignature(JwsHeaders headers) { + return doCreateJwsSignature(prepareHeaders(headers)); + } + + protected abstract JwsSignature doCreateJwsSignature(JwsHeaders headers); + + protected void checkAlgorithm(String algo) { + if (algo == null) { + LOG.warning("Signature algorithm is not set"); + throw new JwsException(JwsException.Error.ALGORITHM_NOT_SET); + } + if (!isValidAlgorithmFamily(algo)) { + LOG.warning("Invalid signature algorithm: " + algo); + throw new JwsException(JwsException.Error.INVALID_ALGORITHM); + } + } + protected abstract boolean isValidAlgorithmFamily(String algo); +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/EcDsaJwsSignatureProvider.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/EcDsaJwsSignatureProvider.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/EcDsaJwsSignatureProvider.java new file mode 100644 index 0000000..3bb92b9 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/EcDsaJwsSignatureProvider.java @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jws; + +import java.security.SecureRandom; +import java.security.Signature; +import java.security.interfaces.ECPrivateKey; +import java.security.spec.AlgorithmParameterSpec; + +import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; + +public class EcDsaJwsSignatureProvider extends PrivateKeyJwsSignatureProvider { + public EcDsaJwsSignatureProvider(ECPrivateKey key, SignatureAlgorithm algo) { + this(key, null, algo); + } + public EcDsaJwsSignatureProvider(ECPrivateKey key, AlgorithmParameterSpec spec, SignatureAlgorithm algo) { + this(key, null, spec, algo); + } + public EcDsaJwsSignatureProvider(ECPrivateKey key, SecureRandom random, AlgorithmParameterSpec spec, + SignatureAlgorithm algo) { + super(key, random, spec, algo); + } + @Override + protected boolean isValidAlgorithmFamily(String algo) { + return AlgorithmUtils.isEcDsaSign(algo); + } + @Override + protected JwsSignature doCreateJwsSignature(Signature s) { + return new EcDsaPrivateKeyJwsSignature(s, + EcDsaJwsSignatureVerifier.SIGNATURE_LENGTH_MAP.get(super.getAlgorithm().getJwaName())); + } + + protected static class EcDsaPrivateKeyJwsSignature extends PrivateKeyJwsSignature { + private int outLen; + public EcDsaPrivateKeyJwsSignature(Signature s, int outLen) { + super(s); + this.outLen = outLen; + } + @Override + public byte[] sign() { + byte[] jcaDer = super.sign(); + return jcaOutputToJoseOutput(outLen, jcaDer); + } + } + + private static byte[] jcaOutputToJoseOutput(int jwsSignatureLen, byte jcaDer[]) { + // DER uses a pattern of type-length-value triplets + // http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One#Example_encoded_in_DER + + // The algorithm implementation guarantees the correct DER format so no extra validation + + // ECDSA signature production: + // 48 (SEQUENCE) + Total Length (1 or 2 bytes, the 1st byte is -127 if 2 bytes) + // + R & S triples, where both triples are represented as + // 2(INTEGER TYPE) + length + the actual sequence of a given length; + // The sequence might have the extra leading zeroes which need to be skipped + int requiredPartLen = jwsSignatureLen / 2; + + int rsDataBlockStart = jcaDer[1] == -127 ? 4 : 3; + int rPartLen = jcaDer[rsDataBlockStart]; + int rDataBlockStart = rsDataBlockStart + 1; + int rPartLenDiff = rPartLen - requiredPartLen; + int rValueStart = rDataBlockStart + getDataBlockOffset(jcaDer, rDataBlockStart, rPartLenDiff); + + int sPartStart = rDataBlockStart + rPartLen; + int sPartLen = jcaDer[sPartStart + 1]; + int sPartLenDiff = sPartLen - requiredPartLen; + int sDataBlockStart = sPartStart + 2; + int sValueStart = sDataBlockStart + getDataBlockOffset(jcaDer, sDataBlockStart, sPartLenDiff); + + byte[] result = new byte[jwsSignatureLen]; + System.arraycopy(jcaDer, rValueStart, result, + rPartLenDiff < 0 ? rPartLenDiff * -1 : 0, + rPartLenDiff < 0 ? requiredPartLen + rPartLenDiff : requiredPartLen); + System.arraycopy(jcaDer, sValueStart, result, + sPartLenDiff < 0 ? requiredPartLen + sPartLenDiff * -1 : requiredPartLen, + sPartLenDiff < 0 ? requiredPartLen + sPartLenDiff : requiredPartLen); + return result; + } + private static int getDataBlockOffset(byte[] jcaDer, int blockStart, int partLenDiff) { + // ECDSA productions have 64, 96 or 132 output lengths. The R and S parts would be 32, 48 or 66 bytes each. + // If it is 32 or 48 bytes then we may have occasional extra zeroes in the JCA DER output + int i = 0; + if (partLenDiff > 0) { + while (i < partLenDiff && jcaDer[blockStart + i] == 0) { + i++; + } + } + return i; + } + + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/EcDsaJwsSignatureVerifier.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/EcDsaJwsSignatureVerifier.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/EcDsaJwsSignatureVerifier.java new file mode 100644 index 0000000..025cd21 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/EcDsaJwsSignatureVerifier.java @@ -0,0 +1,81 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jws; + +import java.security.PublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.util.HashMap; +import java.util.Map; + +import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; + +public class EcDsaJwsSignatureVerifier extends PublicKeyJwsSignatureVerifier { + static final Map<String, Integer> SIGNATURE_LENGTH_MAP; + static { + SIGNATURE_LENGTH_MAP = new HashMap<String, Integer>(); + SIGNATURE_LENGTH_MAP.put(SignatureAlgorithm.ES256.getJwaName(), 64); + SIGNATURE_LENGTH_MAP.put(SignatureAlgorithm.ES384.getJwaName(), 96); + SIGNATURE_LENGTH_MAP.put(SignatureAlgorithm.ES512.getJwaName(), 132); + } + public EcDsaJwsSignatureVerifier(PublicKey key, SignatureAlgorithm supportedAlgo) { + this(key, null, supportedAlgo); + } + public EcDsaJwsSignatureVerifier(PublicKey key, AlgorithmParameterSpec spec, SignatureAlgorithm supportedAlgo) { + super(key, spec, supportedAlgo); + } + @Override + public boolean verify(JwsHeaders headers, String unsignedText, byte[] signature) { + final String algoName = super.getAlgorithm().getJwaName(); + if (SIGNATURE_LENGTH_MAP.get(algoName) != signature.length) { + LOG.warning("Algorithm " + algoName + " signature length is " + SIGNATURE_LENGTH_MAP.get(algoName) + + ", actual length is " + signature.length); + throw new JwsException(JwsException.Error.INVALID_SIGNATURE); + } + byte[] der = signatureToDer(signature); + return super.verify(headers, unsignedText, der); + } + @Override + protected boolean isValidAlgorithmFamily(String algo) { + return AlgorithmUtils.isEcDsaSign(algo); + } + private static byte[] signatureToDer(byte joseSig[]) { + int partLen = joseSig.length / 2; + int rOffset = joseSig[0] < 0 ? 1 : 0; + int sOffset = joseSig[partLen] < 0 ? 1 : 0; + int rPartLen = partLen + rOffset; + int sPartLen = partLen + sOffset; + int totalLenBytesCount = joseSig.length > 127 ? 2 : 1; + int rPartStart = 1 + totalLenBytesCount + 2; + byte[] der = new byte[rPartStart + 2 + rPartLen + sPartLen]; + der[0] = 48; + if (totalLenBytesCount == 2) { + der[1] = -127; + } + der[totalLenBytesCount] = (byte)(der.length - (1 + totalLenBytesCount)); + der[totalLenBytesCount + 1] = 2; + der[totalLenBytesCount + 2] = (byte)rPartLen; + int sPartStart = rPartStart + rPartLen; + der[sPartStart] = 2; + der[sPartStart + 1] = (byte)sPartLen; + System.arraycopy(joseSig, 0, der, rPartStart + rOffset, partLen); + System.arraycopy(joseSig, partLen, der, sPartStart + 2 + sOffset, partLen); + return der; + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/HmacJwsSignatureProvider.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/HmacJwsSignatureProvider.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/HmacJwsSignatureProvider.java new file mode 100644 index 0000000..ce1db97 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/HmacJwsSignatureProvider.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jws; + +import java.security.spec.AlgorithmParameterSpec; + +import javax.crypto.Mac; + +import org.apache.cxf.common.util.Base64Exception; +import org.apache.cxf.common.util.Base64UrlUtility; +import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; +import org.apache.cxf.rt.security.crypto.HmacUtils; + +public class HmacJwsSignatureProvider extends AbstractJwsSignatureProvider { + private byte[] key; + private AlgorithmParameterSpec hmacSpec; + + public HmacJwsSignatureProvider(byte[] key, SignatureAlgorithm algo) { + this(key, null, algo); + } + public HmacJwsSignatureProvider(byte[] key, AlgorithmParameterSpec spec, SignatureAlgorithm algo) { + super(algo); + this.key = key; + this.hmacSpec = spec; + } + public HmacJwsSignatureProvider(String encodedKey, SignatureAlgorithm algo) { + super(algo); + try { + this.key = Base64UrlUtility.decode(encodedKey); + } catch (Base64Exception ex) { + LOG.warning("Hmac key can not be decoded"); + throw new JwsException(JwsException.Error.INVALID_KEY, ex); + } + } + + protected JwsSignature doCreateJwsSignature(JwsHeaders headers) { + final String sigAlgo = headers.getSignatureAlgorithm().getJwaName(); + final Mac mac = HmacUtils.getInitializedMac(key, + AlgorithmUtils.toJavaName(sigAlgo), + hmacSpec); + return new JwsSignature() { + + @Override + public void update(byte[] src, int off, int len) { + mac.update(src, off, len); + } + + @Override + public byte[] sign() { + return mac.doFinal(); + } + + }; + } + @Override + protected boolean isValidAlgorithmFamily(String algo) { + return AlgorithmUtils.isHmacSign(algo); + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/HmacJwsSignatureVerifier.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/HmacJwsSignatureVerifier.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/HmacJwsSignatureVerifier.java new file mode 100644 index 0000000..7910659 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/HmacJwsSignatureVerifier.java @@ -0,0 +1,85 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jws; + +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; +import java.util.logging.Logger; + +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.rs.security.jose.common.JoseUtils; +import org.apache.cxf.rs.security.jose.jwa.AlgorithmUtils; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; +import org.apache.cxf.rt.security.crypto.HmacUtils; + +public class HmacJwsSignatureVerifier implements JwsSignatureVerifier { + protected static final Logger LOG = LogUtils.getL7dLogger(HmacJwsSignatureVerifier.class); + private byte[] key; + private AlgorithmParameterSpec hmacSpec; + private SignatureAlgorithm supportedAlgo; + + public HmacJwsSignatureVerifier(String encodedKey) { + this(JoseUtils.decode(encodedKey), SignatureAlgorithm.HS256); + } + public HmacJwsSignatureVerifier(String encodedKey, SignatureAlgorithm supportedAlgo) { + this(JoseUtils.decode(encodedKey), supportedAlgo); + } + public HmacJwsSignatureVerifier(byte[] key, SignatureAlgorithm supportedAlgo) { + this(key, null, supportedAlgo); + } + public HmacJwsSignatureVerifier(byte[] key, AlgorithmParameterSpec spec, SignatureAlgorithm supportedAlgo) { + this.key = key; + this.hmacSpec = spec; + this.supportedAlgo = supportedAlgo; + } + + + @Override + public boolean verify(JwsHeaders headers, String unsignedText, byte[] signature) { + byte[] expected = computeMac(headers, unsignedText); + return Arrays.equals(expected, signature); + } + + private byte[] computeMac(JwsHeaders headers, String text) { + final String sigAlgo = checkAlgorithm(headers.getSignatureAlgorithm()); + return HmacUtils.computeHmac(key, + AlgorithmUtils.toJavaName(sigAlgo), + hmacSpec, + text); + } + + protected String checkAlgorithm(SignatureAlgorithm sigAlgo) { + + if (sigAlgo == null) { + LOG.warning("Signature algorithm is not set"); + throw new JwsException(JwsException.Error.ALGORITHM_NOT_SET); + } + String algo = sigAlgo.getJwaName(); + if (!AlgorithmUtils.isHmacSign(algo) + || !algo.equals(supportedAlgo.getJwaName())) { + LOG.warning("Invalid signature algorithm: " + algo); + throw new JwsException(JwsException.Error.INVALID_ALGORITHM); + } + return algo; + } + @Override + public SignatureAlgorithm getAlgorithm() { + return supportedAlgo; + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsCompactConsumer.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsCompactConsumer.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsCompactConsumer.java new file mode 100644 index 0000000..9d57222 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsCompactConsumer.java @@ -0,0 +1,149 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jws; + +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.logging.Logger; + +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.common.util.StringUtils; +import org.apache.cxf.jaxrs.json.basic.JsonMapObject; +import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter; +import org.apache.cxf.rs.security.jose.common.JoseUtils; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; +import org.apache.cxf.rs.security.jose.jwk.JsonWebKey; + +public class JwsCompactConsumer { + protected static final Logger LOG = LogUtils.getL7dLogger(JwsCompactConsumer.class); + private JsonMapObjectReaderWriter reader = new JsonMapObjectReaderWriter(); + private String encodedSequence; + private String encodedSignature; + private String headersJson; + private String jwsPayload; + private String decodedJwsPayload; + private JwsHeaders jwsHeaders; + private boolean detached; + public JwsCompactConsumer(String encodedJws) { + this(encodedJws, null, null); + } + public JwsCompactConsumer(String encodedJws, String detachedPayload) { + this(encodedJws, detachedPayload, null); + } + protected JwsCompactConsumer(String encodedJws, String detachedPayload, JsonMapObjectReaderWriter r) { + if (r != null) { + this.reader = r; + } + String[] parts = JoseUtils.getCompactParts(encodedJws); + if (parts.length != 3) { + if (parts.length == 2 && encodedJws.endsWith(".")) { + encodedSignature = ""; + } else { + LOG.warning("Compact JWS does not have 3 parts"); + throw new JwsException(JwsException.Error.INVALID_COMPACT_JWS); + } + } else { + encodedSignature = parts[2]; + } + jwsPayload = parts[1]; + if (detachedPayload != null) { + if (!StringUtils.isEmpty(jwsPayload)) { + LOG.warning("Compact JWS includes a payload expected to be detached"); + throw new JwsException(JwsException.Error.INVALID_COMPACT_JWS); + } + detached = true; + jwsPayload = detachedPayload; + } + encodedSequence = parts[0] + "." + jwsPayload; + headersJson = JoseUtils.decodeToString(parts[0]); + } + public String getUnsignedEncodedSequence() { + return encodedSequence; + } + public String getEncodedSignature() { + return encodedSignature; + } + public String getDecodedJsonHeaders() { + return headersJson; + } + public String getDecodedJwsPayload() { + if (decodedJwsPayload == null) { + if (JwsUtils.isPayloadUnencoded(jwsHeaders)) { + decodedJwsPayload = jwsPayload; + } else { + decodedJwsPayload = JoseUtils.decodeToString(jwsPayload); + } + } + return decodedJwsPayload; + } + public byte[] getDecodedJwsPayloadBytes() { + return StringUtils.toBytesUTF8(getDecodedJwsPayload()); + } + public byte[] getDecodedSignature() { + return encodedSignature.isEmpty() ? new byte[]{} : JoseUtils.decode(encodedSignature); + } + public JwsHeaders getJwsHeaders() { + if (jwsHeaders == null) { + JsonMapObject joseHeaders = reader.fromJsonToJsonObject(headersJson); + if (joseHeaders.getUpdateCount() != null) { + LOG.warning("Duplicate headers have been detected"); + throw new JwsException(JwsException.Error.INVALID_COMPACT_JWS); + } + jwsHeaders = new JwsHeaders(joseHeaders.asMap()); + if (JwsUtils.isPayloadUnencoded(jwsHeaders) && !detached) { + LOG.warning("Only detached payload can be unencoded"); + throw new JwsException(JwsException.Error.INVALID_COMPACT_JWS); + } + } + return jwsHeaders; + } + public boolean verifySignatureWith(JwsSignatureVerifier validator) { + try { + if (validator.verify(getJwsHeaders(), getUnsignedEncodedSequence(), getDecodedSignature())) { + return true; + } + } catch (JwsException ex) { + // ignore + } + LOG.warning("Invalid Signature"); + return false; + } + public boolean verifySignatureWith(JsonWebKey key) { + return verifySignatureWith(JwsUtils.getSignatureVerifier(key)); + } + public boolean verifySignatureWith(JsonWebKey key, SignatureAlgorithm algo) { + return verifySignatureWith(JwsUtils.getSignatureVerifier(key, algo)); + } + public boolean verifySignatureWith(X509Certificate cert, SignatureAlgorithm algo) { + return verifySignatureWith(JwsUtils.getPublicKeySignatureVerifier(cert, algo)); + } + public boolean verifySignatureWith(PublicKey key, SignatureAlgorithm algo) { + return verifySignatureWith(JwsUtils.getPublicKeySignatureVerifier(key, algo)); + } + public boolean verifySignatureWith(byte[] key, SignatureAlgorithm algo) { + return verifySignatureWith(JwsUtils.getHmacSignatureVerifier(key, algo)); + } + public boolean validateCriticalHeaders() { + return JwsUtils.validateCriticalHeaders(getJwsHeaders()); + } + protected JsonMapObjectReaderWriter getReader() { + return reader; + } + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsCompactProducer.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsCompactProducer.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsCompactProducer.java new file mode 100644 index 0000000..5b4c760 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsCompactProducer.java @@ -0,0 +1,153 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jws; + +import java.security.PrivateKey; +import java.util.Properties; + +import org.apache.cxf.common.util.Base64UrlUtility; +import org.apache.cxf.common.util.StringUtils; +import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter; +import org.apache.cxf.message.Message; +import org.apache.cxf.phase.PhaseInterceptorChain; +import org.apache.cxf.rs.security.jose.common.JoseConstants; +import org.apache.cxf.rs.security.jose.common.KeyManagementUtils; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; +import org.apache.cxf.rs.security.jose.jwk.JsonWebKey; + +public class JwsCompactProducer { + private JsonMapObjectReaderWriter writer = new JsonMapObjectReaderWriter(); + private JwsHeaders headers; + private String plainJwsPayload; + private String signature; + private boolean detached; + public JwsCompactProducer(String plainJwsPayload) { + this(plainJwsPayload, false); + } + public JwsCompactProducer(String plainJwsPayload, boolean detached) { + this(null, null, plainJwsPayload, detached); + } + public JwsCompactProducer(JwsHeaders headers, String plainJwsPayload) { + this(headers, plainJwsPayload, false); + } + public JwsCompactProducer(JwsHeaders headers, String plainJwsPayload, boolean detached) { + this(headers, null, plainJwsPayload, detached); + } + protected JwsCompactProducer(JwsHeaders headers, JsonMapObjectReaderWriter w, String plainJwsPayload) { + this(headers, w, plainJwsPayload, false); + } + protected JwsCompactProducer(JwsHeaders headers, JsonMapObjectReaderWriter w, String plainJwsPayload, + boolean detached) { + this.headers = headers; + if (w != null) { + this.writer = w; + } + this.plainJwsPayload = plainJwsPayload; + this.detached = detached; + } + public JwsHeaders getJwsHeaders() { + if (headers == null) { + headers = new JwsHeaders(); + } + return headers; + } + public String getUnsignedEncodedJws() { + checkAlgorithm(); + return Base64UrlUtility.encode(writer.toJson(getJwsHeaders())) + + "." + + (detached ? "" : Base64UrlUtility.encode(plainJwsPayload)); + } + private String getSigningInput() { + checkAlgorithm(); + boolean unencoded = JwsUtils.isPayloadUnencoded(getJwsHeaders()); + if (unencoded && !detached) { + throw new JwsException(JwsException.Error.INVALID_COMPACT_JWS); + } + return Base64UrlUtility.encode(writer.toJson(getJwsHeaders())) + + "." + + (unencoded ? plainJwsPayload : Base64UrlUtility.encode(plainJwsPayload)); + } + public String getEncodedSignature() { + return signature; + } + public String getSignedEncodedJws() { + checkAlgorithm(); + boolean noSignature = StringUtils.isEmpty(signature); + if (noSignature && !isPlainText()) { + throw new IllegalStateException("Signature is not available"); + } + return getUnsignedEncodedJws() + "." + (noSignature ? "" : signature); + } + public String signWith(JsonWebKey jwk) { + return signWith(JwsUtils.getSignatureProvider(jwk, + headers.getSignatureAlgorithm())); + } + + public String signWith(PrivateKey key) { + return signWith(JwsUtils.getPrivateKeySignatureProvider(key, + headers.getSignatureAlgorithm())); + } + public String signWith(byte[] key) { + return signWith(JwsUtils.getHmacSignatureProvider(key, + headers.getSignatureAlgorithm())); + } + + public String signWith(JwsSignatureProvider signer) { + byte[] bytes = StringUtils.toBytesUTF8(getSigningInput()); + byte[] sig = signer.sign(getJwsHeaders(), bytes); + return setSignatureBytes(sig); + } + + public String setSignatureText(String signatureText) { + setEncodedSignature(Base64UrlUtility.encode(signatureText)); + return getSignedEncodedJws(); + } + + public boolean isPlainText() { + return SignatureAlgorithm.NONE == getAlgorithm(); + } + + public String setSignatureBytes(byte[] signatureOctets) { + setEncodedSignature(Base64UrlUtility.encode(signatureOctets)); + return getSignedEncodedJws(); + } + + private void setEncodedSignature(String sig) { + this.signature = sig; + } + private SignatureAlgorithm getAlgorithm() { + return getJwsHeaders().getSignatureAlgorithm(); + } + private void checkAlgorithm() { + if (getAlgorithm() == null) { + Message m = PhaseInterceptorChain.getCurrentMessage(); + Properties props = KeyManagementUtils.loadStoreProperties(m, false, + JoseConstants.RSSEC_SIGNATURE_OUT_PROPS, + JoseConstants.RSSEC_SIGNATURE_PROPS); + String signatureAlgo = JwsUtils.getSignatureAlgo(m, props, null, null); + if (signatureAlgo != null) { + getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.getAlgorithm(signatureAlgo)); + } + } + + if (getAlgorithm() == null) { + throw new JwsException(JwsException.Error.INVALID_ALGORITHM); + } + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsException.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsException.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsException.java new file mode 100644 index 0000000..7482b8d --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsException.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jws; + +import org.apache.cxf.rs.security.jose.common.JoseException; + +public class JwsException extends JoseException { + + private static final long serialVersionUID = 4118589816228511524L; + private Error status; + public JwsException(Error status) { + this(status, null); + } + public JwsException(Error status, Throwable cause) { + super(cause); + this.status = status; + } + public Error getError() { + return status; + } + public static enum Error { + NO_PROVIDER, + NO_VERIFIER, + NO_INIT_PROPERTIES, + ALGORITHM_NOT_SET, + INVALID_ALGORITHM, + INVALID_KEY, + SIGNATURE_FAILURE, + INVALID_SIGNATURE, + INVALID_COMPACT_JWS, + INVALID_JSON_JWS + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsHeaders.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsHeaders.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsHeaders.java new file mode 100644 index 0000000..a2d0e88 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsHeaders.java @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jws; + +import java.util.Map; + +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.JoseType; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; + +public class JwsHeaders extends JoseHeaders { + public JwsHeaders() { + } + public JwsHeaders(JoseType type) { + super(type); + } + public JwsHeaders(JoseHeaders headers) { + super(headers.asMap()); + } + + public JwsHeaders(Map<String, Object> values) { + super(values); + } + public JwsHeaders(SignatureAlgorithm sigAlgo) { + init(sigAlgo); + } + public JwsHeaders(JoseType type, SignatureAlgorithm sigAlgo) { + super(type); + init(sigAlgo); + } + private void init(SignatureAlgorithm sigAlgo) { + setSignatureAlgorithm(sigAlgo); + } + + public void setSignatureAlgorithm(SignatureAlgorithm algo) { + super.setAlgorithm(algo.getJwaName()); + } + + public SignatureAlgorithm getSignatureAlgorithm() { + String algo = super.getAlgorithm(); + return algo == null ? null : SignatureAlgorithm.getAlgorithm(algo); + } + public void setPayloadEncodingStatus(Boolean status) { + super.setProperty(JoseConstants.JWS_HEADER_B64_STATUS_HEADER, status); + } + public Boolean getPayloadEncodingStatus() { + return super.getBooleanProperty(JoseConstants.JWS_HEADER_B64_STATUS_HEADER); + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/532c52a7/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonConsumer.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonConsumer.java b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonConsumer.java new file mode 100644 index 0000000..3ad5b45 --- /dev/null +++ b/rt/rs/security/jose-parent/jose/src/main/java/org/apache/cxf/rs/security/jose/jws/JwsJsonConsumer.java @@ -0,0 +1,189 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.jose.jws; + +import java.security.PublicKey; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.common.util.StringUtils; +import org.apache.cxf.helpers.CastUtils; +import org.apache.cxf.jaxrs.json.basic.JsonMapObject; +import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter; +import org.apache.cxf.rs.security.jose.common.JoseUtils; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; +import org.apache.cxf.rs.security.jose.jwk.JsonWebKey; + +public class JwsJsonConsumer { + protected static final Logger LOG = LogUtils.getL7dLogger(JwsJsonConsumer.class); + private String jwsSignedDocument; + private String jwsPayload; + private List<JwsJsonSignatureEntry> signatures = new LinkedList<JwsJsonSignatureEntry>(); + /** + * @param jwsSignedDocument + * signed JWS Document + */ + public JwsJsonConsumer(String jwsSignedDocument) { + this(jwsSignedDocument, null); + } + public JwsJsonConsumer(String jwsSignedDocument, String detachedPayload) { + this.jwsSignedDocument = jwsSignedDocument; + prepare(detachedPayload); + } + + private void prepare(String detachedPayload) { + JsonMapObject jsonObject = new JsonMapObject(); + new JsonMapObjectReaderWriter().fromJson(jsonObject, jwsSignedDocument); + + Map<String, Object> jsonObjectMap = jsonObject.asMap(); + jwsPayload = (String)jsonObjectMap.get("payload"); + if (jwsPayload == null) { + jwsPayload = detachedPayload; + } else if (detachedPayload != null) { + LOG.warning("JSON JWS includes a payload expected to be detached"); + throw new JwsException(JwsException.Error.INVALID_JSON_JWS); + } + if (jwsPayload == null) { + LOG.warning("JSON JWS has no payload"); + throw new JwsException(JwsException.Error.INVALID_JSON_JWS); + } + List<Map<String, Object>> signatureArray = CastUtils.cast((List<?>)jsonObjectMap.get("signatures")); + if (signatureArray != null) { + if (jsonObjectMap.containsKey("signature")) { + LOG.warning("JSON JWS has a flattened 'signature' element and a 'signatures' object"); + throw new JwsException(JwsException.Error.INVALID_JSON_JWS); + } + for (Map<String, Object> signatureEntry : signatureArray) { + this.signatures.add(getSignatureObject(signatureEntry)); + } + } else { + this.signatures.add(getSignatureObject(jsonObjectMap)); + } + if (signatures.isEmpty()) { + LOG.warning("JSON JWS has no signatures"); + throw new JwsException(JwsException.Error.INVALID_JSON_JWS); + } + validateB64Status(); + + + } + private Boolean validateB64Status() { + return JwsJsonProducer.validateB64Status(signatures); + } + protected JwsJsonSignatureEntry getSignatureObject(Map<String, Object> signatureEntry) { + String protectedHeader = (String)signatureEntry.get("protected"); + Map<String, Object> header = CastUtils.cast((Map<?, ?>)signatureEntry.get("header")); + String signature = (String)signatureEntry.get("signature"); + return + new JwsJsonSignatureEntry(jwsPayload, + protectedHeader, + signature, + header != null ? new JwsHeaders(header) : null); + } + public String getSignedDocument() { + return this.jwsSignedDocument; + } + public String getJwsPayload() { + return this.jwsPayload; + } + public String getDecodedJwsPayload() { + if (validateB64Status()) { + return JoseUtils.decodeToString(jwsPayload); + } else { + return jwsPayload; + } + } + public byte[] getDecodedJwsPayloadBytes() { + return StringUtils.toBytesUTF8(getDecodedJwsPayload()); + } + public List<JwsJsonSignatureEntry> getSignatureEntries() { + return Collections.unmodifiableList(signatures); + } + public Map<SignatureAlgorithm, List<JwsJsonSignatureEntry>> getSignatureEntryMap() { + return JwsUtils.getJwsJsonSignatureMap(signatures); + } + public boolean verifySignatureWith(JwsSignatureVerifier validator) { + List<JwsJsonSignatureEntry> theSignatureEntries = + getSignatureEntryMap().get(validator.getAlgorithm()); + if (theSignatureEntries != null) { + for (JwsJsonSignatureEntry signatureEntry : theSignatureEntries) { + if (signatureEntry.verifySignatureWith(validator)) { + return true; + } + } + } + return false; + } + public boolean verifySignatureWith(PublicKey key, SignatureAlgorithm algo) { + return verifySignatureWith(JwsUtils.getPublicKeySignatureVerifier(key, algo)); + } + public boolean verifySignatureWith(byte[] key, SignatureAlgorithm algo) { + return verifySignatureWith(JwsUtils.getHmacSignatureVerifier(key, algo)); + } + public boolean verifySignatureWith(List<JwsSignatureVerifier> validators) { + try { + if (verifyAndGetNonValidated(validators).isEmpty()) { + return true; + } + } catch (JwsException ex) { + // ignore + } + LOG.warning("One of JSON JWS signatures is invalid"); + return false; + } + public List<JwsJsonSignatureEntry> verifyAndGetNonValidated(List<JwsSignatureVerifier> validators) { + // TODO: more effective approach is needed + List<JwsJsonSignatureEntry> validatedSignatures = new LinkedList<JwsJsonSignatureEntry>(); + for (JwsSignatureVerifier validator : validators) { + List<JwsJsonSignatureEntry> theSignatureEntries = + getSignatureEntryMap().get(validator.getAlgorithm()); + if (theSignatureEntries != null) { + for (JwsJsonSignatureEntry sigEntry : theSignatureEntries) { + if (sigEntry.verifySignatureWith(validator)) { + validatedSignatures.add(sigEntry); + break; + } + } + } + } + List<JwsJsonSignatureEntry> nonValidatedSignatures = new LinkedList<JwsJsonSignatureEntry>(); + for (JwsJsonSignatureEntry sigEntry : signatures) { + if (!validatedSignatures.contains(sigEntry)) { + nonValidatedSignatures.add(sigEntry); + } + } + return nonValidatedSignatures; + } + + public boolean verifySignatureWith(JsonWebKey key) { + return verifySignatureWith(JwsUtils.getSignatureVerifier(key)); + } + public boolean verifySignatureWith(JsonWebKey key, SignatureAlgorithm algo) { + return verifySignatureWith(JwsUtils.getSignatureVerifier(key, algo)); + } + public JwsJsonProducer toProducer() { + JwsJsonProducer p = new JwsJsonProducer(getDecodedJwsPayload()); + p.getSignatureEntries().addAll(signatures); + return p; + } +}
