/**
* 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.common;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertPath;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderResult;
import java.security.cert.CertPathValidator;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.cxf.Bus;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.Base64Exception;
import org.apache.cxf.common.util.Base64UrlUtility;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageUtils;
import org.apache.cxf.phase.PhaseInterceptorChain;
import org.apache.cxf.rs.security.jose.jwk.KeyOperation;
import org.apache.cxf.rt.security.crypto.CryptoUtils;
import org.apache.cxf.rt.security.crypto.MessageDigestUtils;
/**
* Encryption helpers
*/
public final class KeyManagementUtils {
private static final Logger LOG = LogUtils.getL7dLogger(KeyManagementUtils.class);
private KeyManagementUtils() {
}
public static List<String> loadAndEncodeX509CertificateOrChain(Message m, Properties props) {
X509Certificate[] chain = loadX509CertificateOrChain(m, props);
return encodeX509CertificateChain(chain);
}
public static String loadDigestAndEncodeX509Certificate(Message m, Properties props, String digestAlgo) {
X509Certificate[] certs = loadX509CertificateOrChain(m, props);
if (certs != null && certs.length > 0) {
try {
byte[] digest =
MessageDigestUtils.createDigest(certs[0].getEncoded(), digestAlgo);
return Base64UrlUtility.encode(digest);
} catch (NoSuchAlgorithmException ex) {
LOG.log(Level.FINE, "Error creating digest", ex);
throw new JoseException(ex);
} catch (CertificateEncodingException ex) {
LOG.log(Level.FINE, "Error creating digest", ex);
throw new JoseException(ex);
}
}
return null;
}
public static X509Certificate[] loadX509CertificateOrChain(Message m, Properties props) {
KeyStore keyStore = KeyManagementUtils.loadPersistKeyStore(m, props);
String alias = props.getProperty(JoseConstants.RSSEC_KEY_STORE_ALIAS);
return loadX509CertificateOrChain(keyStore, alias);
}
private static X509Certificate[] loadX509CertificateOrChain(KeyStore keyStore, String alias) {
if (alias == null) {
throw new JoseException("No alias supplied");
}
try {
Certificate[] certs = keyStore.getCertificateChain(alias);
if (certs != null) {
return Arrays.copyOf(certs, certs.length, X509Certificate[].class);
} else {
return new X509Certificate[]{(X509Certificate)CryptoUtils.loadCertificate(keyStore, alias)};
}
} catch (Exception ex) {
LOG.warning("X509 Certificates can not be created");
throw new JoseException(ex);
}
}
public static PublicKey loadPublicKey(Message m, Properties props) {
KeyStore keyStore = KeyManagementUtils.loadPersistKeyStore(m, props);
return CryptoUtils.loadPublicKey(keyStore, props.getProperty(JoseConstants.RSSEC_KEY_STORE_ALIAS));
}
public static PublicKey loadPublicKey(Message m, String keyStoreLocProp) {
return loadPublicKey(m, keyStoreLocProp, null);
}
public static PublicKey loadPublicKey(Message m, String keyStoreLocPropPreferred, String keyStoreLocPropDefault) {
String keyStoreLoc = getMessageProperty(m, keyStoreLocPropPreferred, keyStoreLocPropDefault);
Bus bus = m.getExchange().getBus();
try {
Properties props = JoseUtils.loadProperties(keyStoreLoc, bus);
return KeyManagementUtils.loadPublicKey(m, props);
} catch (Exception ex) {
LOG.warning("Public key can not be loaded");
throw new JoseException(ex);
}
}
public static PublicKey loadPublicKey(String keyStorePropLoc, Bus bus) {
try {
Properties props = JoseUtils.loadProperties(keyStorePropLoc, bus);
return KeyManagementUtils.loadPublicKey(null, props);
} catch (Exception ex) {
LOG.warning("Public key can not be loaded");
throw new JoseException(ex);
}
}
public static PublicKey loadPublicKey(String keyStoreLoc,
String keyStorePassword,
String keyAlias,
Bus bus) {
try {
KeyStore keyStore = loadKeyStore(keyStoreLoc, null, keyStorePassword, bus);
return CryptoUtils.loadPublicKey(keyStore, keyAlias);
} catch (Exception ex) {
throw new SecurityException(ex);
}
}
private static String getMessageProperty(Message m, String keyStoreLocPropPreferred,
String keyStoreLocPropDefault) {
String propLoc =
(String)MessageUtils.getContextualProperty(m, keyStoreLocPropPreferred, keyStoreLocPropDefault);
if (propLoc == null) {
LOG.warning("Properties resource is not identified");
throw new JoseException();
}
return propLoc;
}
private static PrivateKey loadPrivateKey(KeyStore keyStore,
Message m,
Properties props,
KeyOperation keyOper,
String alias) {
String keyPswd = props.getProperty(JoseConstants.RSSEC_KEY_PSWD);
String theAlias = alias != null ? alias : getKeyId(m, props, JoseConstants.RSSEC_KEY_STORE_ALIAS, keyOper);
if (theAlias != null) {
props.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, theAlias);
}
char[] keyPswdChars = keyPswd != null ? keyPswd.toCharArray() : null;
if (keyPswdChars == null) {
PrivateKeyPasswordProvider provider = loadPasswordProvider(m, props, keyOper);
keyPswdChars = provider != null ? provider.getPassword(props) : null;
}
return CryptoUtils.loadPrivateKey(keyStore, keyPswdChars, theAlias);
}
public static PrivateKey loadPrivateKey(Message m, String keyStoreLocProp, KeyOperation keyOper) {
return loadPrivateKey(m, keyStoreLocProp, null, keyOper);
}
public static PrivateKey loadPrivateKey(Message m, String keyStoreLocPropPreferred,
String keyStoreLocPropDefault, KeyOperation keyOper) {
String keyStoreLoc = getMessageProperty(m, keyStoreLocPropPreferred, keyStoreLocPropDefault);
Bus bus = m.getExchange().getBus();
try {
Properties props = JoseUtils.loadProperties(keyStoreLoc, bus);
return loadPrivateKey(m, props, keyOper);
} catch (Exception ex) {
throw new SecurityException(ex);
}
}
public static PrivateKey loadPrivateKey(String keyStoreLoc,
String keyStorePassword,
String keyAlias,
String keyPassword,
Bus bus) {
try {
KeyStore keyStore = loadKeyStore(keyStoreLoc, null, keyStorePassword, bus);
return CryptoUtils.loadPrivateKey(keyStore,
keyPassword == null ? new char[]{} : keyPassword.toCharArray(),
keyAlias);
} catch (Exception ex) {
throw new SecurityException(ex);
}
}
public static PrivateKey loadPrivateKey(String keyStorePropLoc,
Bus bus) {
try {
Properties props = JoseUtils.loadProperties(keyStorePropLoc, bus);
return loadPrivateKey(null, props, null);
} catch (Exception ex) {
throw new SecurityException(ex);
}
}
public static String getKeyId(Message m, Properties props,
String preferredPropertyName,
KeyOperation keyOper) {
String kid = null;
String altPropertyName = null;
if (keyOper != null && m != null) {
if (keyOper == KeyOperation.ENCRYPT || keyOper == KeyOperation.DECRYPT) {
altPropertyName = preferredPropertyName + ".jwe";
} else if (keyOper == KeyOperation.SIGN || keyOper == KeyOperation.VERIFY) {
altPropertyName = preferredPropertyName + ".jws";
}
String direction = m.getExchange().getOutMessage() == m ? ".out" : ".in";
kid = (String)MessageUtils.getContextualProperty(m, preferredPropertyName, altPropertyName + direction);
// Check whether the direction is not set for the altPropertyName
if (kid == null && altPropertyName != null) {
kid = (String)m.getContextualProperty(altPropertyName);
}
}
if (kid == null) {
kid = props.getProperty(preferredPropertyName);
}
if (kid == null && altPropertyName != null) {
kid = props.getProperty(altPropertyName);
}
return kid;
}
public static PrivateKeyPasswordProvider loadPasswordProvider(Message m, Properties props, KeyOperation keyOper) {
PrivateKeyPasswordProvider cb = null;
if (keyOper != null) {
String propName = keyOper == KeyOperation.SIGN ? JoseConstants.RSSEC_SIGNATURE_KEY_PSWD_PROVIDER
: keyOper == KeyOperation.DECRYPT
? JoseConstants.RSSEC_DECRYPTION_KEY_PSWD_PROVIDER : null;
if (propName != null) {
if (props.containsKey(propName)) {
cb = (PrivateKeyPasswordProvider)props.get(propName);
} else if (m != null) {
cb = (PrivateKeyPasswordProvider)m.getContextualProperty(propName);
}
}
}
if (cb == null) {
if (props.containsKey(JoseConstants.RSSEC_KEY_PSWD_PROVIDER)) {
cb = (PrivateKeyPasswordProvider)props.get(JoseConstants.RSSEC_KEY_PSWD_PROVIDER);
} else if (m != null) {
cb = (PrivateKeyPasswordProvider)m.getContextualProperty(JoseConstants.RSSEC_KEY_PSWD_PROVIDER);
}
}
return cb;
}
public static PrivateKey loadPrivateKey(Message m, Properties props, KeyOperation keyOper) {
KeyStore keyStore = loadPersistKeyStore(m, props);
return loadPrivateKey(keyStore, m, props, keyOper, null);
}
public static KeyStore loadPersistKeyStore(Message m, Properties props) {
KeyStore keyStore = null;
if (props.containsKey(JoseConstants.RSSEC_KEY_STORE)) {
keyStore = (KeyStore)props.get(JoseConstants.RSSEC_KEY_STORE);
}
if (keyStore == null) {
if (!props.containsKey(JoseConstants.RSSEC_KEY_STORE_FILE)) {
LOG.warning("No keystore file has been configured");
throw new JoseException("No keystore file has been configured");
}
if (m != null) {
keyStore = (KeyStore)m.getExchange().get(props.get(JoseConstants.RSSEC_KEY_STORE_FILE));
}
}
if (keyStore == null) {
Bus bus = m != null ? m.getExchange().getBus() : null;
keyStore = loadKeyStore(props, bus);
if (m != null) {
m.getExchange().put((String)props.get(JoseConstants.RSSEC_KEY_STORE_FILE), keyStore);
}
}
return keyStore;
}
public static KeyStore loadKeyStore(Properties props, Bus bus) {
String keyStoreLoc = props.getProperty(JoseConstants.RSSEC_KEY_STORE_FILE);
String keyStoreType = props.getProperty(JoseConstants.RSSEC_KEY_STORE_TYPE);
String keyStorePswd = props.getProperty(JoseConstants.RSSEC_KEY_STORE_PSWD);
return loadKeyStore(keyStoreLoc, keyStoreType, keyStorePswd, bus);
}
public static KeyStore loadKeyStore(String keyStoreLoc,
String keyStoreType,
String keyStorePswd,
Bus bus) {
if (keyStorePswd == null) {
throw new JoseException("No keystore password was defined");
}
try {
InputStream is = JoseUtils.getResourceStream(keyStoreLoc, bus);
return CryptoUtils.loadKeyStore(is, keyStorePswd.toCharArray(), keyStoreType);
} catch (Exception ex) {
LOG.warning("Key store can not be loaded");
throw new JoseException(ex);
}
}
public static List<String> encodeX509CertificateChain(X509Certificate[] chain) {
return encodeX509CertificateChain(Arrays.asList(chain));
}
public static List<String> encodeX509CertificateChain(List<X509Certificate> chain) {
List<String> encodedChain = new ArrayList<>(chain.size());
for (X509Certificate cert : chain) {
try {
encodedChain.add(CryptoUtils.encodeCertificate(cert));
} catch (Exception ex) {
LOG.warning("X509 Certificate can not be encoded");
throw new JoseException(ex);
}
}
return encodedChain;
}
public static List<X509Certificate> toX509CertificateChain(List<String> base64EncodedChain) {
if (base64EncodedChain != null) {
List<X509Certificate> certs = new ArrayList<>(base64EncodedChain.size());
for (String encodedCert : base64EncodedChain) {
try {
certs.add((X509Certificate)CryptoUtils.decodeCertificate(encodedCert));
} catch (Exception ex) {
LOG.warning("X509 Certificate can not be decoded");
throw new JoseException(ex);
}
}
return certs;
} else {
return null;
}
}
//TODO: enhance the certificate validation code
public static void validateCertificateChain(Properties storeProperties, List<X509Certificate> inCerts) {
Message message = PhaseInterceptorChain.getCurrentMessage();
KeyStore ks = loadPersistKeyStore(message, storeProperties);
validateCertificateChain(ks, inCerts);
}
public static void validateCertificateChain(KeyStore ks, List<X509Certificate> inCerts) {
// Initial chain validation, to be enhanced as needed
try {
X509CertSelector certSelect = new X509CertSelector();
certSelect.setCertificate((X509Certificate) inCerts.get(0));
PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ks, certSelect);
pbParams.addCertStore(CertStore.getInstance("Collection",
new CollectionCertStoreParameters(inCerts)));
pbParams.setMaxPathLength(-1);
pbParams.setRevocationEnabled(false);
CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams);
CertPath certPath = buildResult.getCertPath();
CertPathValidator.getInstance("PKIX").validate(certPath, pbParams);
} catch (Exception ex) {
LOG.warning("Certificate path validation error");
throw new JoseException(ex);
}
}
public static X509Certificate[] toX509CertificateChainArray(List<String> base64EncodedChain) {
List<X509Certificate> chain = toX509CertificateChain(base64EncodedChain);
return chain == null ? null : chain.toArray(new X509Certificate[]{});
}
public static String getKeyAlgorithm(Message m, Properties props, String propName, String defaultAlg) {
String algo = props != null ? props.getProperty(propName) : null;
if (algo == null && m != null) {
algo = (String)m.getContextualProperty(propName);
}
if (algo == null) {
algo = defaultAlg;
}
return algo;
}
public static Properties loadStoreProperties(Message m, boolean required,
String storeProp1, String storeProp2) {
if (m == null) {
if (required) {
throw new JoseException();
}
return null;
}
Properties props = null;
String propLoc =
(String)MessageUtils.getContextualProperty(m, storeProp1, storeProp2);
if (propLoc != null) {
try {
props = JoseUtils.loadProperties(propLoc, m.getExchange().getBus());
} catch (Exception ex) {
LOG.warning("Properties resource is not identified");
throw new JoseException(ex);
}
} else {
String keyFile = (String)m.getContextualProperty(JoseConstants.RSSEC_KEY_STORE_FILE);
if (keyFile != null) {
props = new Properties();
props.setProperty(JoseConstants.RSSEC_KEY_STORE_FILE, keyFile);
String type = (String)m.getContextualProperty(JoseConstants.RSSEC_KEY_STORE_TYPE);
if (type == null) {
type = "jwk";
}
props.setProperty(JoseConstants.RSSEC_KEY_STORE_TYPE, type);
String alias = (String)m.getContextualProperty(JoseConstants.RSSEC_KEY_STORE_ALIAS);
if (alias != null) {
props.setProperty(JoseConstants.RSSEC_KEY_STORE_ALIAS, alias);
}
String keystorePassword = (String)m.getContextualProperty(JoseConstants.RSSEC_KEY_STORE_PSWD);
if (keystorePassword != null) {
props.setProperty(JoseConstants.RSSEC_KEY_STORE_PSWD, keystorePassword);
}
String keyPassword = (String)m.getContextualProperty(JoseConstants.RSSEC_KEY_PSWD);
if (keyPassword != null) {
props.setProperty(JoseConstants.RSSEC_KEY_PSWD, keyPassword);
}
}
}
if (props == null) {
if (required) {
LOG.warning("Properties resource is not identified");
throw new JoseException();
}
props = new Properties();
}
return props;
}
public static PrivateKey loadPrivateKey(Message m, Properties props,
X509Certificate inCert,
KeyOperation keyOper) {
KeyStore ks = loadPersistKeyStore(m, props);
try {
String alias = ks.getCertificateAlias(inCert);
return loadPrivateKey(ks, m, props, keyOper, alias);
} catch (Exception ex) {
LOG.warning("Private key can not be loaded");
throw new JoseException(ex);
}
}
public static X509Certificate getCertificateFromThumbprint(String thumbprint,
String digestAlgorithm,
Message m,
Properties props) {
KeyStore ks = loadPersistKeyStore(m, props);
if (ks == null || thumbprint == null) {
return null;
}
try {
byte[] decodedThumbprint = Base64UrlUtility.decode(thumbprint);
for (Enumeration<String> e = ks.aliases(); e.hasMoreElements();) {
String alias = e.nextElement();
Certificate[] certs = ks.getCertificateChain(alias);
if (certs == null || certs.length == 0) {
// no cert chain, so lets check if getCertificate gives us a result.
Certificate cert = ks.getCertificate(alias);
if (cert != null) {
certs = new Certificate[]{cert};
}
}
if (certs != null && certs.length > 0 && certs[0] instanceof X509Certificate) {
X509Certificate x509cert = (X509Certificate) certs[0];
byte[] data =
MessageDigestUtils.createDigest(x509cert.getEncoded(), digestAlgorithm);
if (Arrays.equals(data, decodedThumbprint)) {
return x509cert;
}
}
}
} catch (KeyStoreException e) {
LOG.log(Level.WARNING, "X509Certificate can not be loaded: ", e);
throw new JoseException(e);
} catch (CertificateEncodingException e) {
LOG.log(Level.WARNING, "X509Certificate can not be loaded: ", e);
throw new JoseException(e);
} catch (NoSuchAlgorithmException e) {
LOG.log(Level.WARNING, "X509Certificate can not be loaded: ", e);
throw new JoseException(e);
} catch (Base64Exception e) {
LOG.log(Level.WARNING, "X509Certificate can not be loaded: ", e);
throw new JoseException(e);
}
return null;
}
}