All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jivesoftware.util.cert.SANCertificateIdentityMapping Maven / Gradle / Ivy

The newest version!
package org.jivesoftware.util.cert;

import org.bouncycastle.asn1.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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;

/**
 * Certificate identity mapping that uses SubjectAlternativeName as the identity credentials.
 * This implementation returns all subjectAltName entries that are a:
 * 
    *
  • GeneralName of type otherName with the "id-on-xmppAddr" Object Identifier
  • *
  • GeneralName of type otherName with the "id-on-dnsSRV" Object Identifier
  • *
  • GeneralName of type DNSName
  • *
  • GeneralName of type UniformResourceIdentifier
  • *
* * @author Victor Hong * @author Guus der Kinderen, [email protected] */ public class SANCertificateIdentityMapping implements CertificateIdentityMapping { private static final Logger Log = LoggerFactory.getLogger( SANCertificateIdentityMapping.class ); /** * id-on-xmppAddr Object Identifier. * * @see RFC 4985 */ public static final String OTHERNAME_SRV_OID = "1.3.6.1.5.5.7.8.7"; /** * User Principal Name (UPN) Object Identifier. * * @see User Principal Name (UPN) */ public static final String OTHERNAME_UPN_OID = "1.3.6.1.4.1.311.20.2.3"; /** * 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 mapIdentity( X509Certificate certificate ) { List identities = new ArrayList<>(); try { Collection> altNames = certificate.getSubjectAlternativeNames(); // Check that the certificate includes the SubjectAltName extension if ( altNames == null ) { return Collections.emptyList(); } for ( List item : altNames ) { final Integer type = (Integer) item.get( 0 ); final Object value = item.get( 1 ); // this is either a string, or a byte-array that represents the ASN.1 DER encoded form. final String result; switch ( type ) { case 0: // OtherName: search for "id-on-xmppAddr" or 'sRVName' or 'userPrincipalName' result = parseOtherName( (byte[]) value ); break; case 2: // DNS result = (String) value; break; case 6: // URI result = (String) value; break; default: // Not applicable to XMPP, so silently ignore them result = null; break; } if ( result != null ) { identities.add( result ); } } } 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 (never null). */ @Override public String name() { return "Subject Alternative Name Mapping"; } /** * Parses the byte-array representation of a subjectAltName 'otherName' entry. *

* The provided 'OtherName' is expected to have this format: *

{@code
     * OtherName ::= SEQUENCE {
     * type-id    OBJECT IDENTIFIER,
     * value      [0] EXPLICIT ANY DEFINED BY type-id }
     * }
* * @param item A byte array representation of a subjectAltName 'otherName' entry (cannot be null). * @return an xmpp address, or null when the otherName entry does not relate to XMPP (or fails to parse). */ public String parseOtherName( byte[] item ) { if ( item == null || item.length == 0 ) { return null; } try ( final ASN1InputStream decoder = new ASN1InputStream( item ) ) { // By specification, OtherName instances must always be an ASN.1 Sequence. final ASN1Primitive object = decoder.readObject(); final ASN1Sequence otherNameSeq = (ASN1Sequence) object; // By specification, an OtherName instance consists of: // - the type-id (which is an Object Identifier), followed by: // - a tagged value, of which the tag number is 0 (zero) and the value is defined by the type-id. final ASN1ObjectIdentifier typeId = (ASN1ObjectIdentifier) otherNameSeq.getObjectAt( 0 ); final ASN1TaggedObject taggedValue = (ASN1TaggedObject) otherNameSeq.getObjectAt( 1 ); final int tagNo = taggedValue.getTagNo(); if ( tagNo != 0 ) { throw new IllegalArgumentException( "subjectAltName 'otherName' sequence's second object is expected to be a tagged value of which the tag number is 0. The tag number that was detected: " + tagNo ); } final ASN1Primitive value = taggedValue.getObject(); switch ( typeId.getId() ) { case OTHERNAME_SRV_OID: return parseOtherNameDnsSrv( value ); case OTHERNAME_XMPP_OID: return parseOtherNameXmppAddr( value ); case OTHERNAME_UPN_OID: return parseOtherNameUpn( value ); default: String otherName = parseOtherName(typeId, value); if (otherName != null) { return otherName; } Log.debug( "Ignoring subjectAltName 'otherName' type-id '{}' that's neither id-on-xmppAddr nor id-on-dnsSRV.", typeId.getId() ); return null; } } catch ( Exception e ) { Log.warn( "Unable to parse a byte array (of length {}) as a subjectAltName 'otherName'. It is ignored.", item.length, e ); return null; } } /** * Allow sub-class to support additional OID values, possibly taking typeId into account * * @param typeId The ASN.1 object identifier (cannot be null). * @param value The ASN.1 representation of the value (cannot be null). * @return The parsed otherName String value. */ protected String parseOtherName(ASN1ObjectIdentifier typeId, ASN1Primitive value) { return null; } /** * Parses a SRVName value as specified by RFC 4985. * * This method parses the argument value as a DNS SRV Resource Record. Only when the parsed value refers to an XMPP * related service, the corresponding DNS domain name is returned (minus the service name). * * @param srvName The ASN.1 representation of the srvName value (cannot be null). * @return an XMPP address value, or null when the record does not relate to XMPP. */ protected String parseOtherNameDnsSrv( ASN1Primitive srvName ) { // RFC 4985 says that this should be a IA5 String. Lets be tolerant and allow all text-based values. final String value = ( (ASN1String) srvName ).getString(); if ( value.toLowerCase().startsWith( "_xmpp-server." ) ) { return value.substring( "_xmpp-server.".length() ); } else if ( value.toLowerCase().startsWith( "_xmpp-client." ) ) { return value.substring( "_xmpp-client.".length() ); } else { // Not applicable to XMPP. Ignore. Log.debug( "srvName value '{}' of id-on-dnsSRV record is neither _xmpp-server nor _xmpp-client. It is being ignored.", value ); return null; } } /** * Parse a XmppAddr value as specified in RFC 6120. * * @param xmppAddr The ASN.1 representation of the xmppAddr value (cannot be null). * @return The parsed xmppAddr value. */ protected String parseOtherNameXmppAddr( ASN1Primitive xmppAddr ) { // RFC 6120 says that this should be a UTF8String. Lets be tolerant and allow all text-based values. return ( (ASN1String) xmppAddr ).getString(); } /** * Parse a UPN value * * @param value The ASN.1 representation of the UPN (cannot be null). * @return The parsed UPN value. */ protected String parseOtherNameUpn( ASN1Primitive value ) { String otherName = null; if (value instanceof ASN1TaggedObject) { ASN1TaggedObject taggedObject = (ASN1TaggedObject) value; ASN1Primitive objectPrimitive = taggedObject.getObject(); if (objectPrimitive instanceof ASN1String) { otherName = ((ASN1String)objectPrimitive).getString(); } } if (otherName == null) { Log.warn("UPN type unexpected, UPN extraction failed: " + value.getClass().getName() + ":" + value.toString()); } else { Log.debug("UPN from certificate has value of: " + otherName ); } return otherName; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy