/* * Copyright (c) 2007, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. 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.wso2.carbon.security.util; import org.apache.axiom.om.impl.dom.jaxp.DocumentBuilderFactoryImpl; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ws.security.WSSecurityException; import org.apache.ws.security.components.crypto.CredentialException; import org.apache.ws.security.components.crypto.Crypto; import org.apache.ws.security.components.crypto.X509NameTokenizer; import org.wso2.carbon.context.CarbonContext; import org.wso2.carbon.core.util.KeyStoreManager; import org.wso2.carbon.registry.core.Registry; import org.wso2.carbon.security.SecurityServiceHolder; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.CertPath; import java.security.cert.CertPathValidator; import java.security.cert.CertPathValidatorException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.PKIXParameters; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.Vector; /** * ServerCrypto implementation to support a collection of keystores holding different trusted certs * and CA certs */ public class ServerCrypto implements Crypto { public final static String PROP_ID_KEY_STORE = "org.wso2.carbon.security.crypto.keystore"; public final static String PROP_ID_PRIVATE_STORE = "org.wso2.carbon.security.crypto.privatestore"; public final static String PROP_ID_TRUST_STORES = "org.wso2.carbon.security.crypto.truststores"; public final static String PROP_ID_CERT_PROVIDER = "org.wso2.carbon.security.crypto.cert.provider"; public final static String PROP_ID_DEFAULT_ALIAS = "org.wso2.carbon.security.crypto.alias"; public final static String PROP_ID_REGISTRY = "org.wso2.carbon.security.crypto.registry"; public final static String PROP_ID_CACERT_PASS = "org.wso2.carbon.security.crypto.cacert.pass"; public final static String PROP_ID_XKMS_SERVICE_PASS_PHRASE = "org.wso2.wsas.security.wso2wsas.crypto.xkms.pass"; public final static String PROP_ID_TENANT_ID = "org.wso2.stratos.tenant.id"; public final static String PROP_ID_XKMS_SERVICE_URL = "org.wso2.carbon.security.crypto.xkms.url"; private static final String SKI_OID = "2.5.29.14"; private static Log log = LogFactory.getLog(ServerCrypto.class); private static CertificateFactory certFact = null; private Properties properties = null; private KeyStore keystore = null; private KeyStore cacerts = null; private List<KeyStore> trustStores = new ArrayList<>(); private Registry registry = null; private Boolean useXkms; public ServerCrypto(Properties prop) throws CredentialException, IOException { this(prop, ServerCrypto.class.getClassLoader()); } public ServerCrypto(Properties prop, ClassLoader loader) throws CredentialException, IOException { boolean isSetDoomFalse = false; try { int tenantId; String tenantIdString = (String) prop.get(PROP_ID_TENANT_ID); if (tenantIdString == null || tenantIdString.trim().length() == 0) { tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId(); } else { tenantId = new Integer(tenantIdString); } // Forcefully load tenant. since tenant may have been unloaded SecurityServiceHolder.getTenantRegistryLoader().loadTenantRegistry(tenantId); // From certain operations (rampart) this is set to true. This causes to throw an exeption while loading // registry. Therefore set it to false before loading and set it to true only if it is changed. if(DocumentBuilderFactoryImpl.isDOOMRequired()){ DocumentBuilderFactoryImpl.setDOOMRequired(false); isSetDoomFalse = true; } registry = SecurityServiceHolder.getRegistryService().getGovernanceSystemRegistry(tenantId); if(isSetDoomFalse){ DocumentBuilderFactoryImpl.setDOOMRequired(true); isSetDoomFalse = false; } this.properties = prop; KeyStoreManager keyMan = KeyStoreManager.getInstance(tenantId); String ksId = this.properties.getProperty(PROP_ID_PRIVATE_STORE); if (ksId != null) { this.keystore = keyMan.getKeyStore(ksId); } // Get other keystores if available String trustStoreIds = this.properties.getProperty(PROP_ID_TRUST_STORES); if (trustStoreIds != null && trustStoreIds.trim().length() != 0) { String[] ids = trustStoreIds.trim().split(","); this.trustStores = new ArrayList(ids.length); for (int i = 0; i < ids.length; i++) { String id = ids[i]; KeyStore tstks = keyMan.getKeyStore(id); this.trustStores.add(i, tstks); } } } catch (Exception e) { log.error("error creating ServerCryto", e); throw new CredentialException(3, "secError00", e); } /** * Load cacerts */ String cacertsPath = System.getProperty("java.home") + "/lib/security/cacerts"; InputStream cacertsIs = new FileInputStream(cacertsPath); try { String cacertsPasswd = properties.getProperty(PROP_ID_CACERT_PASS, "changeit"); cacerts = KeyStore.getInstance(KeyStore.getDefaultType()); cacerts.load(cacertsIs, cacertsPasswd.toCharArray()); } catch (GeneralSecurityException e) { log.warn("Unable load to cacerts from the JDK.", e); if (CollectionUtils.isNotEmpty(trustStores)) { cacerts = this.trustStores.get(0); } else { throw new CredentialException(3, "secError00", e); } } finally { cacertsIs.close(); } } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#loadCertificate(java.io.InputStream) */ public X509Certificate loadCertificate(InputStream in) throws WSSecurityException { X509Certificate cert; try { cert = (X509Certificate) getCertificateFactory().generateCertificate(in); } catch (CertificateException e) { throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "parseError"); } return cert; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getX509Certificates(byte[], boolean) */ public X509Certificate[] getX509Certificates(byte[] data, boolean reverse) throws WSSecurityException { InputStream in = new ByteArrayInputStream(data); CertPath path; try { path = getCertificateFactory().generateCertPath(in); } catch (CertificateException e) { throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "parseError"); } List l = path.getCertificates(); X509Certificate[] certs = new X509Certificate[l.size()]; Iterator iterator = l.iterator(); for (int i = 0; i < l.size(); i++) { certs[reverse ? (l.size() - 1 - i) : i] = (X509Certificate) iterator.next(); } return certs; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getCertificateData(boolean, * java.security.cert.X509Certificate[]) */ public byte[] getCertificateData(boolean reverse, X509Certificate[] certs) throws WSSecurityException { Vector list = new Vector(); for (int i = 0; i < certs.length; i++) { if (reverse) { list.insertElementAt(certs[i], 0); } else { list.add(certs[i]); } } try { CertPath path = getCertificateFactory().generateCertPath(list); return path.getEncoded(); } catch (CertificateEncodingException e) { throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "encodeError"); } catch (CertificateException e) { throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "parseError"); } } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getPrivateKey(java.lang.String, * java.lang.String) */ public PrivateKey getPrivateKey(String alias, String password) throws Exception { if (alias == null) { throw new Exception("alias is null"); } boolean b = keystore.isKeyEntry(alias); if (!b) { log.error("Cannot find key for alias: " + alias); throw new Exception("Cannot find key for alias: " + alias); } Key keyTmp = keystore.getKey(alias, password.toCharArray()); if (!(keyTmp instanceof PrivateKey)) { throw new Exception("Key is not a private key, alias: " + alias); } return (PrivateKey) keyTmp; } @Override /** * This first looks into the primary keystore and then looks at the other trust stores * * @see org.apache.ws.security.components.crypto.Crypto#getCertificates(String) */ public X509Certificate[] getCertificates(String alias) throws WSSecurityException { Certificate[] certs = new Certificate[0]; Certificate cert = null; try { if (this.keystore != null) { // There's a chance that there can only be a set of trust stores certs = keystore.getCertificateChain(alias); if (certs == null || certs.length == 0) { // no cert chain, so lets check if getCertificate gives us a // result. cert = keystore.getCertificate(alias); } } if (certs == null && cert == null && this.trustStores != null) { // Now look into the trust stores Iterator trustStoreIter = this.trustStores.iterator(); while (trustStoreIter.hasNext()) { KeyStore store = (KeyStore) trustStoreIter.next(); certs = store.getCertificateChain(alias); if (certs != null) { break; // found the certs } else { cert = store.getCertificate(alias); } } } if (certs == null && cert == null && this.cacerts != null) { // There's a chance that there can only be a set of ca store certs = cacerts.getCertificateChain(alias); if (certs == null || certs.length == 0) { // no cert chain, so lets check if getCertificate gives us a // result. cert = cacerts.getCertificate(alias); } } if (cert != null) { certs = new Certificate[]{cert}; } else if (certs == null) { // At this pont we don't have certs or a cert return new X509Certificate[0]; } } catch (KeyStoreException e) { throw new WSSecurityException(WSSecurityException.FAILURE, "keystore"); } X509Certificate[] x509certs = new X509Certificate[0]; if (certs != null) { x509certs = new X509Certificate[certs.length]; for (int i = 0; i < certs.length; i++) { x509certs[i] = (X509Certificate) certs[i]; } } return x509certs; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getAliasForX509Cert(java.security.cert.Certificate) */ public String getAliasForX509Cert(Certificate cert) throws WSSecurityException { try { String alias = null; if (this.keystore != null) { alias = keystore.getCertificateAlias(cert); // Use brute force search if (alias == null) { alias = findAliasForCert(this.keystore, cert); } } // Check the trust stores if (alias == null && this.trustStores != null) { for (Iterator trustStoreIter = this.trustStores.iterator(); trustStoreIter .hasNext(); ) { KeyStore store = (KeyStore) trustStoreIter.next(); alias = store.getCertificateAlias(cert); if (alias != null) { break; } } } // Use brute force search on the trust stores if (alias == null && this.trustStores != null) { for (Iterator trustStoreIter = this.trustStores.iterator(); trustStoreIter .hasNext(); ) { KeyStore store = (KeyStore) trustStoreIter.next(); alias = this.findAliasForCert(store, cert); if (alias != null) { break; } } } if (alias == null && this.cacerts != null) { alias = cacerts.getCertificateAlias(cert); // Use brute force search if (alias == null) { alias = findAliasForCert(this.cacerts, cert); } } if (alias != null) { return alias; } } catch (KeyStoreException e) { throw new WSSecurityException(WSSecurityException.FAILURE, "keystore"); } return null; } private String findAliasForCert(KeyStore ks, Certificate cert) throws KeyStoreException { Enumeration e = ks.aliases(); while (e.hasMoreElements()) { String alias = (String) e.nextElement(); X509Certificate cert2 = (X509Certificate) ks.getCertificate(alias); if (cert2.equals(cert)) { return alias; } } return null; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getAliasForX509Cert(java.lang.String) */ public String getAliasForX509Cert(String issuer) throws WSSecurityException { String alias = getAliasForX509Cert(issuer, null, false, this.keystore); if (alias == null) { Iterator<KeyStore> ite = this.trustStores.iterator(); while (ite.hasNext()) { KeyStore ks = ite.next(); alias = getAliasForX509Cert(issuer, null, false, ks); if (alias != null) { break; } } } return alias; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getAliasForX509Cert(java.lang.String, * java.math.BigInteger) */ public String getAliasForX509Cert(String issuer, BigInteger serialNumber) throws WSSecurityException { String alias = getAliasForX509Cert(issuer, serialNumber, true, this.keystore); if (alias == null) { Iterator<KeyStore> ite = this.trustStores.iterator(); while (ite.hasNext()) { KeyStore ks = ite.next(); alias = getAliasForX509Cert(issuer, serialNumber, true, ks); if (alias != null) { break; } } } return alias; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getAliasForX509Cert(byte[]) */ public String getAliasForX509Cert(byte[] skiBytes) throws WSSecurityException { try { Certificate cert; for (Enumeration e = keystore.aliases(); e.hasMoreElements(); ) { String alias = (String) e.nextElement(); Certificate[] certs = this.getCertificates(alias); if (certs == null || certs.length == 0) { return null; } else { cert = certs[0]; } if (!(cert instanceof X509Certificate)) { continue; } byte[] data = getSKIBytesFromCert((X509Certificate) cert); if (data.length != skiBytes.length) { continue; } if (Arrays.equals(data, skiBytes)) { return alias; } } } catch (KeyStoreException e) { throw new WSSecurityException(WSSecurityException.FAILURE, "keystore"); } return null; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getDefaultX509Alias() */ public String getDefaultX509Alias() { return this.properties.getProperty(PROP_ID_DEFAULT_ALIAS); } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getSKIBytesFromCert(java.security.cert.X509Certificate) */ public byte[] getSKIBytesFromCert(X509Certificate cert) throws WSSecurityException { /* * Gets the DER-encoded OCTET string for the extension value (extnValue) * identified by the passed-in oid String. The oid string is represented * by a set of positive whole numbers separated by periods. */ byte[] derEncodedValue = cert.getExtensionValue(SKI_OID); if (cert.getVersion() < 3 || derEncodedValue == null) { PublicKey key = cert.getPublicKey(); if (!(key instanceof RSAPublicKey)) { throw new WSSecurityException(1, "noSKIHandling", new Object[]{"Support for RSA key only"}); } byte[] encoded = key.getEncoded(); // remove 22-byte algorithm ID and header byte[] value = new byte[encoded.length - 22]; System.arraycopy(encoded, 22, value, 0, value.length); MessageDigest sha; try { sha = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException ex) { throw new WSSecurityException(1, "noSKIHandling", new Object[]{"Wrong certificate version (<3) and no " + "SHA1 message digest availabe"}); } sha.reset(); sha.update(value); return sha.digest(); } /** * Strip away first four bytes from the DerValue (tag and length of * ExtensionValue OCTET STRING and KeyIdentifier OCTET STRING) */ byte abyte0[] = new byte[derEncodedValue.length - 4]; System.arraycopy(derEncodedValue, 4, abyte0, 0, abyte0.length); return abyte0; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getAliasForX509CertThumb(byte[]) */ public String getAliasForX509CertThumb(byte[] thumb) throws WSSecurityException { Certificate cert; MessageDigest sha; try { sha = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e1) { throw new WSSecurityException(0, "noSHA1availabe"); } try { for (Enumeration e = keystore.aliases(); e.hasMoreElements(); ) { String alias = (String) e.nextElement(); Certificate[] certs = this.getCertificates(alias); if (certs == null || certs.length == 0) { return null; } else { cert = certs[0]; } if (!(cert instanceof X509Certificate)) { continue; } sha.reset(); try { sha.update(cert.getEncoded()); } catch (CertificateEncodingException e1) { throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "encodeError"); } byte[] data = sha.digest(); if (Arrays.equals(data, thumb)) { return alias; } } } catch (KeyStoreException e) { throw new WSSecurityException(WSSecurityException.FAILURE, "keystore"); } return null; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getKeyStore() */ public KeyStore getKeyStore() { return this.keystore; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getCertificateFactory() */ public CertificateFactory getCertificateFactory() throws WSSecurityException { if (certFact == null) { try { String provider = properties.getProperty(PROP_ID_CERT_PROVIDER); if (provider == null || provider.length() == 0) { certFact = CertificateFactory.getInstance("X.509"); } else { certFact = CertificateFactory.getInstance("X.509", provider); } } catch (CertificateException e) { throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "unsupportedCertType"); } catch (NoSuchProviderException e) { throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "noSecProvider"); } } return certFact; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#validateCertPath(java.security.cert.X509Certificate[]) */ public boolean validateCertPath(X509Certificate[] certs) throws WSSecurityException { boolean result; result = this.validateCertPath(this.keystore, certs); if (!result) { Iterator trustStoreIter = this.trustStores.iterator(); while (!result) { result = this.validateCertPath((KeyStore) trustStoreIter.next(), certs); } } if (!result && cacerts != null) { result = this.validateCertPath(this.cacerts, certs); } return result; } @Override /** * @see org.apache.ws.security.components.crypto.Crypto#getAliasesForDN(java.lang.String) */ public String[] getAliasesForDN(String subjectDN) throws WSSecurityException { // Store the aliases found Vector aliases = new Vector(); Certificate cert; // The DN to search the keystore for Vector subjectRDN = splitAndTrim(subjectDN); // Look at every certificate in the keystore try { for (Enumeration e = keystore.aliases(); e.hasMoreElements(); ) { String alias = (String) e.nextElement(); Certificate[] certs = this.getCertificates(alias); if (certs == null || certs.length == 0) { return new String[0]; } else { cert = certs[0]; } if (cert instanceof X509Certificate) { Vector foundRDN = splitAndTrim(((X509Certificate) cert).getSubjectDN() .getName()); if (subjectRDN.equals(foundRDN)) { aliases.add(alias); } } } } catch (KeyStoreException e) { throw new WSSecurityException(WSSecurityException.FAILURE, "keystore"); } // Convert the vector into an array String[] result = new String[aliases.size()]; for (int i = 0; i < aliases.size(); i++) { result[i] = (String) aliases.elementAt(i); } return result; } private String getAliasForX509Cert(String issuer, BigInteger serialNumber, boolean useSerialNumber, KeyStore ks) throws WSSecurityException { Vector issuerRDN = splitAndTrim(issuer); X509Certificate x509cert; Vector certRDN; Certificate cert; try { for (Enumeration e = ks.aliases(); e.hasMoreElements(); ) { String alias = (String) e.nextElement(); Certificate[] certs = this.getCertificates(alias); if (certs == null || certs.length == 0) { return null; } else { cert = certs[0]; } if (!(cert instanceof X509Certificate)) { continue; } x509cert = (X509Certificate) cert; if (useSerialNumber && x509cert.getSerialNumber().compareTo(serialNumber) == 0) { certRDN = splitAndTrim(x509cert.getIssuerDN().getName()); if (certRDN.equals(issuerRDN)) { return alias; } } } } catch (KeyStoreException e) { throw new WSSecurityException(WSSecurityException.FAILURE, "keystore"); } return null; } private Vector splitAndTrim(String inString) { X509NameTokenizer nmTokens = new X509NameTokenizer(inString); Vector vr = new Vector(); while (nmTokens.hasMoreTokens()) { vr.add(nmTokens.nextToken()); } java.util.Collections.sort(vr); return vr; } private boolean validateCertPath(KeyStore ks, Certificate[] certs) throws WSSecurityException { try { // Generate cert path java.util.List certList = java.util.Arrays.asList(certs); CertPath path = this.getCertificateFactory().generateCertPath(certList); // Use the certificates in the keystore as TrustAnchors PKIXParameters param = new PKIXParameters(ks); // Do not check a revocation list param.setRevocationEnabled(false); // Verify the trust path using the above settings String provider = properties .getProperty("org.apache.ws.security.crypto.merlin.cert.provider"); CertPathValidator certPathValidator; if (provider == null || provider.length() == 0) { certPathValidator = CertPathValidator.getInstance("PKIX"); } else { certPathValidator = CertPathValidator.getInstance("PKIX", provider); } certPathValidator.validate(path, param); } catch (NoSuchProviderException | NoSuchAlgorithmException | CertificateException | InvalidAlgorithmParameterException | CertPathValidatorException | KeyStoreException ex) { throw new WSSecurityException(WSSecurityException.FAILURE, "certpath", new Object[]{ex.getMessage()}, ex); } return true; } }