blob: 64f280138df5e126770b752f07a8d04054c806d2 [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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.security.MessageDigest;
import java.security.NoSuchProviderException;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.security.auth.x500.X500Principal;
import org.apache.wss4j.common.ext.WSSecurityException;
/**
* This Abstract Base Class implements the accessor and keystore-independent methods and
* functionality of the Crypto interface.
*/
public abstract class CryptoBase implements Crypto {
public static final String SKI_OID = "2.5.29.14"; //NOPMD - not an IP address
/**
* OID For the NameConstraints Extension to X.509
*
* http://java.sun.com/j2se/1.4.2/docs/api/
* http://www.ietf.org/rfc/rfc3280.txt (s. 4.2.1.11)
*/
public static final String NAME_CONSTRAINTS_OID = "2.5.29.30"; //NOPMD - not an IP address
private static final org.slf4j.Logger LOG =
org.slf4j.LoggerFactory.getLogger(CryptoBase.class);
private static final Constructor<?> BC_509CLASS_CONS;
protected CertificateFactory certificateFactory;
private String defaultAlias;
private String cryptoProvider;
private String trustProvider;
static {
Constructor<?> cons = null;
try {
Class<?> c = Class.forName("org.bouncycastle.asn1.x500.X500Name");
cons = c.getConstructor(new Class[] {String.class});
} catch (Exception e) { //NOPMD
//ignore
}
BC_509CLASS_CONS = cons;
}
/**
* Constructor
*/
protected CryptoBase() {
}
/**
* Get the crypto provider associated with this implementation
* @return the crypto provider
*/
public String getCryptoProvider() {
return cryptoProvider;
}
/**
* Set the crypto provider associated with this implementation
* @param provider the crypto provider to set
*/
public void setCryptoProvider(String provider) {
cryptoProvider = provider;
}
/**
* Set the crypto provider used for truststore operations associated with this implementation
* @param provider the name of the provider
*/
public void setTrustProvider(String provider) {
trustProvider = provider;
}
/**
* Get the crypto provider used for truststore operation associated with this implementation.
* @return a crypto provider name
*/
public String getTrustProvider() {
return trustProvider;
}
/**
* Retrieves the identifier name of the default certificate. This should be the certificate
* that is used for signature and encryption. This identifier corresponds to the certificate
* that should be used whenever KeyInfo is not present in a signed or an encrypted
* message. May return null. The identifier is implementation specific, e.g. it could be the
* KeyStore alias.
*
* @return name of the default X509 certificate.
*/
public String getDefaultX509Identifier() throws WSSecurityException {
return defaultAlias;
}
/**
* Sets the identifier name of the default certificate. This should be the certificate
* that is used for signature and encryption. This identifier corresponds to the certificate
* that should be used whenever KeyInfo is not present in a signed or an encrypted
* message. The identifier is implementation specific, e.g. it could be the KeyStore alias.
*
* @param identifier name of the default X509 certificate.
*/
public void setDefaultX509Identifier(String identifier) {
defaultAlias = identifier;
}
/**
* Sets the CertificateFactory instance on this Crypto instance
*
* @param certFactory the CertificateFactory the CertificateFactory instance to set
*/
public void setCertificateFactory(CertificateFactory certFactory) {
this.certificateFactory = certFactory;
}
/**
* Get the CertificateFactory instance on this Crypto instance
*
* @return Returns a <code>CertificateFactory</code> to construct
* X509 certificates
* @throws WSSecurityException
*/
public CertificateFactory getCertificateFactory() throws WSSecurityException {
if (certificateFactory != null) {
return certificateFactory;
}
try {
String provider = getCryptoProvider();
if (provider == null || provider.length() == 0) {
certificateFactory = CertificateFactory.getInstance("X.509");
} else {
certificateFactory = CertificateFactory.getInstance("X.509", provider);
}
} catch (CertificateException e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "unsupportedCertType"
);
} catch (NoSuchProviderException e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "noSecProvider"
);
}
return certificateFactory;
}
/**
* Load a X509Certificate from the input stream.
*
* @param in The <code>InputStream</code> containing the X509Certificate
* @return An X509 certificate
* @throws WSSecurityException
*/
public X509Certificate loadCertificate(InputStream in) throws WSSecurityException {
try {
CertificateFactory certFactory = getCertificateFactory();
return (X509Certificate) certFactory.generateCertificate(in);
} catch (CertificateException e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "parseError"
);
}
}
/**
* Reads the SubjectKeyIdentifier information from the certificate.
* <p/>
* If the the certificate does not contain a SKI extension then
* try to compute the SKI according to RFC3280 using the
* SHA-1 hash value of the public key. The second method described
* in RFC3280 is not support. Also only RSA public keys are supported.
* If we cannot compute the SKI throw a WSSecurityException.
*
* @param cert The certificate to read SKI
* @return The byte array containing the binary SKI data
*/
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) {
X509SubjectPublicKeyInfo spki = new X509SubjectPublicKeyInfo(cert.getPublicKey());
byte[] value = spki.getSubjectPublicKey();
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
return digest.digest(value);
} catch (Exception ex) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.UNSUPPORTED_SECURITY_TOKEN, ex, "noSKIHandling",
new Object[] {"No SKI certificate extension and no SHA1 message digest available"}
);
}
}
//
// Strip away first (four) bytes from the DerValue (tag and length of
// ExtensionValue OCTET STRING and KeyIdentifier OCTET STRING)
//
DERDecoder extVal = new DERDecoder(derEncodedValue);
extVal.expect(DERDecoder.TYPE_OCTET_STRING); // ExtensionValue OCTET STRING
extVal.getLength();
extVal.expect(DERDecoder.TYPE_OCTET_STRING); // KeyIdentifier OCTET STRING
int keyIDLen = extVal.getLength();
return extVal.getBytes(keyIDLen);
}
/**
* Get a byte array given an array of X509 certificates.
* <p/>
*
* @param certs The certificates to convert
* @return The byte array for the certificates
* @throws WSSecurityException
*/
public byte[] getBytesFromCertificates(X509Certificate[] certs)
throws WSSecurityException {
try {
CertPath path = getCertificateFactory().generateCertPath(Arrays.asList(certs));
return path.getEncoded();
} catch (CertificateEncodingException e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "encodeError"
);
} catch (CertificateException e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "parseError"
);
}
}
/**
* Construct an array of X509Certificate's from the byte array.
* <p/>
*
* @param data The <code>byte</code> array containing the X509 data
* @return An array of X509 certificates
* @throws WSSecurityException
*/
public X509Certificate[] getCertificatesFromBytes(byte[] data)
throws WSSecurityException {
CertPath path = null;
try (InputStream in = new ByteArrayInputStream(data)) {
path = getCertificateFactory().generateCertPath(in);
} catch (CertificateException | IOException e) {
throw new WSSecurityException(
WSSecurityException.ErrorCode.SECURITY_TOKEN_UNAVAILABLE, e, "parseError"
);
}
List<?> l = path.getCertificates();
X509Certificate[] certs = new X509Certificate[l.size()];
int i = 0;
for (Object cert : l) {
certs[i++] = (X509Certificate) cert;
}
return certs;
}
protected Object createBCX509Name(String s) {
if (BC_509CLASS_CONS != null) {
try {
return BC_509CLASS_CONS.newInstance(s);
} catch (Exception e) { //NOPMD
//ignore
}
}
return new X500Principal(s);
}
/**
* @return true if the certificate's SubjectDN matches the constraints defined in the
* subject DNConstraints; false, otherwise. The certificate subject DN only
* has to match ONE of the subject cert constraints (not all).
*/
protected boolean
matchesSubjectDnPattern(
final X509Certificate cert, final Collection<Pattern> subjectDNPatterns
) {
if (cert == null) {
LOG.debug("The certificate is null so no constraints matching was possible");
return false;
}
String subjectName = cert.getSubjectX500Principal().getName();
if (subjectDNPatterns == null || subjectDNPatterns.isEmpty()) {
LOG.warn("No Subject DN Certificate Constraints were defined. This could be a security issue");
return true;
}
return matchesName(subjectName, subjectDNPatterns);
}
/**
* @return true if the certificate's Issuer DN matches the constraints defined in the
* subject DNConstraints; false, otherwise. The certificate subject DN only
* has to match ONE of the subject cert constraints (not all).
*/
protected boolean
matchesIssuerDnPattern(
final X509Certificate cert, final Collection<Pattern> issuerDNPatterns
) {
if (cert == null) {
LOG.debug("The certificate is null so no constraints matching was possible");
return false;
}
String issuerDn = cert.getIssuerDN().getName();
return matchesName(issuerDn, issuerDNPatterns);
}
/**
* @return true if the provided name matches the constraints defined in the
* subject DNConstraints; false, otherwise. The certificate (subject) DN only
* has to match ONE of the (subject) cert constraints (not all).
*/
protected boolean
matchesName(
final String name, final Collection<Pattern> patterns
) {
if (patterns != null && !patterns.isEmpty()) {
if (name == null || name.isEmpty()) {
LOG.debug("The name is null so no constraints matching was possible");
return false;
}
boolean subjectMatch = false;
for (Pattern subjectDNPattern : patterns) {
final Matcher matcher = subjectDNPattern.matcher(name);
if (matcher.matches()) {
LOG.debug("Name {} matches with pattern {}", name, subjectDNPattern);
subjectMatch = true;
break;
}
}
if (!subjectMatch) {
return false;
}
}
return true;
}
/**
* Extracts the NameConstraints sequence from the certificate.
* Handles the case where the data is encoded directly as {@link DERDecoder#TYPE_SEQUENCE}
* or where the sequence has been encoded as an {@link DERDecoder#TYPE_OCTET_STRING}.
* <p>
* By contract, the values retrieved from calls to {@link X509Certificate#getExtensionValue(String)}
* should always be DER-encoded OCTET strings; however, because of ambiguity in the RFC and
* the potential for a future breaking change to this contract, testing whether the bytes
* returned are tagged as a sequence or an encoded octet string is prudent. Considering the fact
* that it is a single byte comparison, the performance hit is negligible.
*
* @param cert the certificate to extract NameConstraints from
* @return the NameConstraints, or null if not present
* @throws WSSecurityException if a processing error occurs decoding the Octet String
*/
protected byte[] getNameConstraints(final X509Certificate cert) throws WSSecurityException {
byte[] bytes = cert.getExtensionValue(NAME_CONSTRAINTS_OID);
if (bytes == null || bytes.length <= 0) {
return null;
}
switch (bytes[0]) {
case DERDecoder.TYPE_OCTET_STRING:
DERDecoder extVal = new DERDecoder(bytes);
extVal.expect(DERDecoder.TYPE_OCTET_STRING);
int seqLen = extVal.getLength();
return extVal.getBytes(seqLen);
case DERDecoder.TYPE_SEQUENCE:
return bytes;
default:
throw new IllegalArgumentException(
"Invalid type for NameConstraints; must be Sequence or OctetString-encoded Sequence");
}
}
}