Commit c9850548 authored by Guus der Kinderen's avatar Guus der Kinderen

OF-1100: Allow for subjectAltNames of type 'DNS'

In addition to subjectAltNames of type otherName with an ASN.1 Object Identifier of
"id-on-xmppAddr", subjectAltNames of type DNS should also be evaluated when processing
identities from a certificate.
parent 6f9f814c
package org.jivesoftware.util.cert; package org.jivesoftware.util.cert;
import java.io.IOException;
import java.security.cert.CertificateParsingException; import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -19,9 +18,10 @@ import org.slf4j.Logger; ...@@ -19,9 +18,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* Certificate identity mapping that uses XMPP-OtherName SubjectAlternativeName * Certificate identity mapping that uses SubjectAlternativeName as the identity credentials. This implementation
* as the identity credentials * combines subjectAltName entries of type otherName with an ASN.1 Object Identifier of "id-on-xmppAddr" with entries
* * of type DNS.
*
* @author Victor Hong * @author Victor Hong
* *
*/ */
...@@ -48,59 +48,28 @@ public class SANCertificateIdentityMapping implements CertificateIdentityMapping ...@@ -48,59 +48,28 @@ public class SANCertificateIdentityMapping implements CertificateIdentityMapping
if (altNames == null) { if (altNames == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
// Use the type OtherName to search for the certified server name
for (List<?> item : altNames) { for (List<?> item : altNames) {
Integer type = (Integer) item.get(0); final Integer type = (Integer) item.get(0);
if (type == 0) { final Object value = item.get(1);
// Type OtherName found so return the associated value final String result;
try (ASN1InputStream decoder = new ASN1InputStream((byte[]) item.get(1))) { switch ( type ) {
// Value is encoded using ASN.1 so decode it to get the server's identity case 0:
Object object = decoder.readObject(); // OtherName: search for "id-on-xmppAddr"
ASN1Sequence otherNameSeq = null; result = parseOtherName( (byte[]) value );
if (object != null && object instanceof ASN1Sequence) { break;
otherNameSeq = (ASN1Sequence) object; case 2:
} else { // DNS
continue; result = (String) value;
} break;
// Check the object identifier default:
ASN1ObjectIdentifier objectId = (ASN1ObjectIdentifier) otherNameSeq.getObjectAt(0); // Other types are not applicable for XMPP, so silently ignore them
Log.debug("Parsing otherName for subject alternative names: " + objectId.toString() ); result = null;
break;
if ( !OTHERNAME_XMPP_OID.equals(objectId.getId())) { }
// Not a XMPP otherName
Log.debug("Ignoring non-XMPP otherName, " + objectId.getId());
continue;
}
// Get identity string if ( result != null ) {
try { identities.add( result );
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);
}
} 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 (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) { catch (CertificateParsingException e) {
...@@ -110,13 +79,69 @@ public class SANCertificateIdentityMapping implements CertificateIdentityMapping ...@@ -110,13 +79,69 @@ public class SANCertificateIdentityMapping implements CertificateIdentityMapping
} }
/** /**
* Returns the short name of mapping * Returns the short name of mapping.
* *
* @return The short name of the mapping * @return The short name of the mapping (never null).
*/ */
@Override @Override
public String name() { public String name() {
return "Subject Alternative Name Mapping"; return "Subject Alternative Name Mapping";
} }
/**
* Parses the byte-array representation of a subjectAltName 'otherName' entry, returning the "id-on-xmppAddr" value
* when that is in the entry.
*
* @param item A byte array representation of a subjectAltName 'otherName' entry (cannot be null).
* @return an "id-on-xmppAddr" value (which is expected to be a JID), or null.
*/
public static String parseOtherName( byte[] item ) {
// Type OtherName found so return the associated value
try (ASN1InputStream decoder = new ASN1InputStream(item)) {
// Value is encoded using ASN.1 so decode it to get the server's identity
Object object = decoder.readObject();
ASN1Sequence otherNameSeq = null;
if (object != null && object instanceof ASN1Sequence) {
otherNameSeq = (ASN1Sequence) object;
} else {
return null;
}
// 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());
return null;
}
// 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
return identity;
}
} 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 (Exception e) {
Log.error("Error decoding subjectAltName", e);
}
return null;
}
} }
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