blob: 9e7f395f8d860b49ac0587bcd8fde2eca130313d [file] [log] [blame]
/**
* 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.wss4j.common.crypto;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.wss4j.common.ext.WSSecurityException;
/**
* A Crypto implementation based on two Java KeyStore objects, one being the keystore, and one
* being the truststore. It differs from Merlin in that it searches the truststore for the
* issuing cert using the AuthorityKeyIdentifier bytes of the certificate, as opposed to the
* issuer DN.
*/
public class MerlinAKI extends Merlin {
private static final org.slf4j.Logger LOG =
org.slf4j.LoggerFactory.getLogger(MerlinAKI.class);
public MerlinAKI() {
super();
}
public MerlinAKI(boolean loadCACerts, String cacertsPasswd) {
super(loadCACerts, cacertsPasswd);
}
public MerlinAKI(Properties properties, ClassLoader loader, PasswordEncryptor passwordEncryptor)
throws WSSecurityException, IOException {
super(properties, loader, passwordEncryptor);
}
/**
* Evaluate whether a given certificate chain should be trusted.
*
* @param certs Certificate chain to validate
* @param enableRevocation whether to enable CRL verification or not
* @param subjectCertConstraints A set of constraints on the Subject DN of the certificates
*
* @throws WSSecurityException if the certificate chain is invalid
*/
@Override
protected void verifyTrust(
X509Certificate[] certs,
boolean enableRevocation,
Collection<Pattern> subjectCertConstraints
) throws WSSecurityException {
//
// FIRST step - Search the keystore for the transmitted certificate
//
if (certs.length == 1 && !enableRevocation) {
String issuerString = certs[0].getIssuerX500Principal().getName();
BigInteger issuerSerial = certs[0].getSerialNumber();
CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ISSUER_SERIAL);
cryptoType.setIssuerSerial(issuerString, issuerSerial);
X509Certificate[] foundCerts = getX509Certificates(cryptoType);
//
// If a certificate has been found, the certificates must be compared
// to ensure against phony DNs (compare encoded form including signature)
//
if (foundCerts != null && foundCerts[0] != null && foundCerts[0].equals(certs[0])) {
try {
certs[0].checkValidity();
} catch (CertificateExpiredException | CertificateNotYetValidException e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILED_CHECK, e, "invalidCert"
);
}
LOG.debug(
"Direct trust for certificate with {}", certs[0].getSubjectX500Principal().getName()
);
return;
}
}
//
// SECOND step - Search for the issuer cert (chain) of the transmitted certificate in the
// keystore or the truststore
//
X509Certificate[] x509certs = certs;
String issuerString = certs[0].getIssuerX500Principal().getName();
try {
if (certs.length == 1) {
byte[] keyIdentifierBytes =
BouncyCastleUtils.getAuthorityKeyIdentifierBytes(certs[0]);
X509Certificate[] foundCerts = getX509CertificatesFromKeyIdentifier(keyIdentifierBytes);
// If the certs have not been found, the issuer is not in the keystore/truststore
// As a direct result, do not trust the transmitted certificate
if (foundCerts == null || foundCerts.length < 1) {
String subjectString = certs[0].getSubjectX500Principal().getName();
LOG.debug(
"No certs found in keystore for issuer {} of certificate for {}",
issuerString, subjectString
);
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE, "certpath", new Object[] {"No trusted certs found"}
);
}
//
// Form a certificate chain from the transmitted certificate
// and the certificate(s) of the issuer from the keystore/truststore
//
x509certs = new X509Certificate[foundCerts.length + 1];
x509certs[0] = certs[0];
System.arraycopy(foundCerts, 0, x509certs, 1, foundCerts.length);
}
} catch (NoSuchAlgorithmException | CertificateException ex) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, "certpath");
}
//
// THIRD step
// Check the certificate trust path for the issuer cert chain
//
LOG.debug(
"Preparing to validate certificate path for issuer {}", issuerString
);
try {
// Generate cert path
List<X509Certificate> certList = Arrays.asList(x509certs);
CertPath path = getCertificateFactory().generateCertPath(certList);
Set<TrustAnchor> set = new HashSet<>();
if (truststore != null) {
addTrustAnchors(set, truststore);
}
//
// Add certificates from the keystore - only if there is no TrustStore, apart from
// the case that the truststore is the JDK CA certs. This behaviour is preserved
// for backwards compatibility reasons
//
if (keystore != null && (truststore == null || loadCACerts)) {
addTrustAnchors(set, keystore);
}
// Verify the trust path using the above settings
String provider = getCryptoProvider();
CertPathValidator validator = null;
if (provider == null || provider.length() == 0) {
validator = CertPathValidator.getInstance("PKIX");
} else {
validator = CertPathValidator.getInstance("PKIX", provider);
}
PKIXParameters param = createPKIXParameters(set, enableRevocation);
validator.validate(path, param);
} catch (NoSuchProviderException | NoSuchAlgorithmException
| CertificateException | InvalidAlgorithmParameterException
| java.security.cert.CertPathValidatorException
| KeyStoreException e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE, e, "certpath"
);
}
// Finally check Cert Constraints
if (!matchesSubjectDnPattern(certs[0], subjectCertConstraints)) {
throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
}
}
private X509Certificate[] getX509CertificatesFromKeyIdentifier(
byte[] keyIdentifierBytes
) throws WSSecurityException, NoSuchAlgorithmException, CertificateEncodingException {
if (keyIdentifierBytes == null) {
return null;
}
Certificate[] certs = null;
if (keystore != null) {
certs = getCertificates(keyIdentifierBytes, keystore);
}
//If we can't find the issuer in the keystore then look at the truststore
if ((certs == null || certs.length == 0) && truststore != null) {
certs = getCertificates(keyIdentifierBytes, truststore);
}
if (certs == null || certs.length == 0) {
return null;
}
return Arrays.copyOf(certs, certs.length, X509Certificate[].class);
}
private Certificate[] getCertificates(
byte[] keyIdentifier,
KeyStore store
) throws WSSecurityException, NoSuchAlgorithmException, CertificateEncodingException {
try {
for (Enumeration<String> e = store.aliases(); e.hasMoreElements();) {
String alias = e.nextElement();
Certificate[] certs = store.getCertificateChain(alias);
if (certs == null || certs.length == 0) {
// no cert chain, so lets check if getCertificate gives us a result.
Certificate cert = store.getCertificate(alias);
if (cert != null) {
certs = new Certificate[]{cert};
}
}
if (certs != null && certs.length > 0 && certs[0] instanceof X509Certificate) {
byte[] subjectKeyIdentifier =
BouncyCastleUtils.getSubjectKeyIdentifierBytes((X509Certificate)certs[0]);
if (subjectKeyIdentifier != null
&& Arrays.equals(subjectKeyIdentifier, keyIdentifier)) {
return certs;
}
}
}
} catch (KeyStoreException e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.FAILURE, e, "keystore"
);
}
return new Certificate[]{};
}
}