Commit c2308294 authored by Dave Cridland's avatar Dave Cridland

Merge pull request #249 from victorqhong-bah/cert-identity-mapping

Allow users to specifiy custom classes to map credentials from certificates
parents c145141e 3bc97e95
...@@ -77,7 +77,7 @@ public class ClearspaceX509TrustManager implements X509TrustManager { ...@@ -77,7 +77,7 @@ public class ClearspaceX509TrustManager implements X509TrustManager {
if (verify) { if (verify) {
int nSize = x509Certificates.length; int nSize = x509Certificates.length;
List<String> peerIdentities = CertificateManager.getPeerIdentities(x509Certificates[0]); List<String> peerIdentities = CertificateManager.getServerIdentities(x509Certificates[0]);
if (getBooleanProperty("clearspace.certificate.verify.chain", true)) { if (getBooleanProperty("clearspace.certificate.verify.chain", true)) {
// Working down the chain, for every certificate in the chain, // Working down the chain, for every certificate in the chain,
......
...@@ -189,7 +189,7 @@ public class ClientTrustManager implements X509TrustManager { ...@@ -189,7 +189,7 @@ public class ClientTrustManager implements X509TrustManager {
if (verify) { if (verify) {
int nSize = x509Certificates.length; int nSize = x509Certificates.length;
List<String> peerIdentities = CertificateManager.getPeerIdentities(x509Certificates[0]); List<String> peerIdentities = CertificateManager.getClientIdentities(x509Certificates[0]);
if (JiveGlobals.getBooleanProperty("xmpp.client.certificate.verify.chain", true)) { if (JiveGlobals.getBooleanProperty("xmpp.client.certificate.verify.chain", true)) {
// Working down the chain, for every certificate in the chain, // Working down the chain, for every certificate in the chain,
......
...@@ -590,7 +590,7 @@ public class SASLAuthentication { ...@@ -590,7 +590,7 @@ public class SASLAuthentication {
authenticationFailed(session, Failure.NOT_AUTHORIZED); authenticationFailed(session, Failure.NOT_AUTHORIZED);
return Status.failed; return Status.failed;
} }
principals.addAll(CertificateManager.getPeerIdentities((X509Certificate)trusted)); principals.addAll(CertificateManager.getClientIdentities((X509Certificate)trusted));
if(principals.size() == 1) { if(principals.size() == 1) {
principal = principals.get(0); principal = principals.get(0);
...@@ -640,7 +640,7 @@ public class SASLAuthentication { ...@@ -640,7 +640,7 @@ public class SASLAuthentication {
} }
public static boolean verifyCertificate(X509Certificate trustedCert, String hostname) { public static boolean verifyCertificate(X509Certificate trustedCert, String hostname) {
for (String identity : CertificateManager.getPeerIdentities(trustedCert)) { for (String identity : CertificateManager.getServerIdentities(trustedCert)) {
// Verify that either the identity is the same as the hostname, or for wildcarded // Verify that either the identity is the same as the hostname, or for wildcarded
// identities that the hostname ends with .domainspecified or -is- domainspecified. // identities that the hostname ends with .domainspecified or -is- domainspecified.
if ((identity.startsWith("*.") if ((identity.startsWith("*.")
......
package org.jivesoftware.util.cert;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Certificate identity mapping that uses the CommonName as the
* identity credentials
*
* @author Victor Hong
*
*/
public class CNCertificateIdentityMapping implements CertificateIdentityMapping {
private static Pattern cnPattern = Pattern.compile("(?i)(cn=)([^,]*)");
/**
* Maps certificate CommonName as identity credentials
*
* @param certificate
* @return
*/
@Override
public List<String> mapIdentity(X509Certificate certificate) {
String name = certificate.getSubjectDN().getName();
Matcher matcher = cnPattern.matcher(name);
// Create an array with the detected identities
List<String> names = new ArrayList<String>();
while (matcher.find()) {
names.add(matcher.group(2));
}
return names;
}
/**
* Returns the short name of mapping
*
* @return The short name of the mapping
*/
@Override
public String name() {
return "Common Name Mapping";
}
}
package org.jivesoftware.util.cert;
import java.security.cert.X509Certificate;
import java.util.List;
/**
* This is the interface used to map identity credentials from certificates.
* Users may implement this class to map authentication credentials (i.e. usernames)
* from certificate data (e.g. CommonName or SubjectAlternativeName)
*
* @author Victor Hong
*
*/
public interface CertificateIdentityMapping {
/**
* Maps identities from X509Certificates
*
* @param certificate The certificate from which to map identities
* @return A list of identities mapped from the certificate
*/
List<String> mapIdentity(X509Certificate certificate);
/**
* Returns the short name of the mapping
*
* @return The short name of the mapping
*/
String name();
}
package org.jivesoftware.util.cert;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.DERUTF8String;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Certificate identity mapping that uses XMPP-OtherName SubjectAlternativeName
* as the identity credentials
*
* @author Victor Hong
*
*/
public class SANCertificateIdentityMapping implements CertificateIdentityMapping {
private static final Logger Log = LoggerFactory.getLogger(SANCertificateIdentityMapping.class);
private static final String OTHERNAME_XMPP_OID = "1.3.6.1.5.5.7.8.5";
/**
* Returns the JID representation of an XMPP entity contained as a SubjectAltName extension
* in the certificate. If none was found then return an empty list.
*
* @param certificate the certificate presented by the remote entity.
* @return the JID representation of an XMPP entity contained as a SubjectAltName extension
* in the certificate. If none was found then return an empty list.
*/
@Override
public List<String> mapIdentity(X509Certificate certificate) {
List<String> identities = new ArrayList<String>();
try {
Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
// Check that the certificate includes the SubjectAltName extension
if (altNames == null) {
return Collections.emptyList();
}
// Use the type OtherName to search for the certified server name
for (List<?> item : altNames) {
Integer type = (Integer) item.get(0);
if (type == 0) {
// Type OtherName found so return the associated value
try {
// Value is encoded using ASN.1 so decode it to get the server's identity
ASN1InputStream decoder = new ASN1InputStream((byte[]) item.get(1));
Object object = decoder.readObject();
ASN1Sequence otherNameSeq = null;
if (object != null && object instanceof ASN1Sequence) {
otherNameSeq = (ASN1Sequence) object;
} else {
continue;
}
// Check the object identifier
ASN1ObjectIdentifier objectId = (ASN1ObjectIdentifier) otherNameSeq.getObjectAt(0);
Log.debug("Parsing otherName for subject alternative names: " + objectId.toString() );
if ( !OTHERNAME_XMPP_OID.equals(objectId.getId())) {
// Not a XMPP otherName
Log.debug("Ignoring non-XMPP otherName, " + objectId.getId());
continue;
}
// Get identity string
try {
final String identity;
ASN1Encodable o = otherNameSeq.getObjectAt(1);
if (o instanceof DERTaggedObject) {
ASN1TaggedObject ato = DERTaggedObject.getInstance(o);
Log.debug("... processing DERTaggedObject: " + ato.toString());
// TODO: there's bound to be a better way...
identity = ato.toString().substring(ato.toString().lastIndexOf(']')+1).trim();
} else {
DERUTF8String derStr = DERUTF8String.getInstance(o);
identity = derStr.getString();
}
if (identity != null && identity.length() > 0) {
// Add the decoded server name to the list of identities
identities.add(identity);
}
decoder.close();
} catch (IllegalArgumentException ex) {
// OF-517: othername formats are extensible. If we don't recognize the format, skip it.
Log.debug("Cannot parse altName, likely because of unknown record format.", ex);
}
}
catch (UnsupportedEncodingException e) {
// Ignore
}
catch (IOException e) {
// Ignore
}
catch (Exception e) {
Log.error("Error decoding subjectAltName", e);
}
}
// Other types are not applicable for XMPP, so silently ignore them
}
}
catch (CertificateParsingException e) {
Log.error("Error parsing SubjectAltName in certificate: " + certificate.getSubjectDN(), e);
}
return identities;
}
/**
* Returns the short name of mapping
*
* @return The short name of the mapping
*/
@Override
public String name() {
return "Subject Alternative Name Mapping";
}
}
...@@ -210,7 +210,7 @@ ...@@ -210,7 +210,7 @@
String a = (String) aliases.nextElement(); String a = (String) aliases.nextElement();
X509Certificate c = (X509Certificate) keyStore.getCertificate(a); X509Certificate c = (X509Certificate) keyStore.getCertificate(a);
StringBuffer identities = new StringBuffer(); StringBuffer identities = new StringBuffer();
for (String identity : CertificateManager.getPeerIdentities(c)) { for (String identity : CertificateManager.getServerIdentities(c)) {
identities.append(identity).append(", "); identities.append(identity).append(", ");
} }
if (identities.length() > 0) { if (identities.length() > 0) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment