2015-10-15 15:08:38 +00:00
package eu.siacs.conversations.crypto ;
2017-07-17 21:13:55 +00:00
import android.os.Build ;
2015-10-15 15:08:38 +00:00
import android.util.Log ;
2015-10-15 18:05:55 +00:00
import android.util.Pair ;
2015-10-15 15:08:38 +00:00
import org.bouncycastle.asn1.ASN1Primitive ;
import org.bouncycastle.asn1.DERIA5String ;
import org.bouncycastle.asn1.DERTaggedObject ;
import org.bouncycastle.asn1.DERUTF8String ;
import org.bouncycastle.asn1.DLSequence ;
import org.bouncycastle.asn1.x500.RDN ;
import org.bouncycastle.asn1.x500.X500Name ;
import org.bouncycastle.asn1.x500.style.BCStyle ;
import org.bouncycastle.asn1.x500.style.IETFUtils ;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder ;
import java.io.IOException ;
2015-10-15 16:06:26 +00:00
import java.security.cert.Certificate ;
2017-07-16 09:05:19 +00:00
import java.security.cert.CertificateEncodingException ;
2015-10-15 15:08:38 +00:00
import java.security.cert.X509Certificate ;
import java.util.ArrayList ;
import java.util.Collection ;
import java.util.List ;
import javax.net.ssl.SSLSession ;
2017-06-17 17:54:09 +00:00
import de.duenndns.ssl.DomainHostnameVerifier ;
public class XmppDomainVerifier implements DomainHostnameVerifier {
2015-10-15 15:08:38 +00:00
2015-10-15 18:05:55 +00:00
private static final String LOGTAG = " XmppDomainVerifier " ;
2015-10-15 15:08:38 +00:00
2017-07-17 19:11:15 +00:00
private static final String SRV_NAME = " 1.3.6.1.5.5.7.8.7 " ;
private static final String XMPP_ADDR = " 1.3.6.1.5.5.7.8.5 " ;
2015-10-15 17:14:41 +00:00
2015-10-15 15:08:38 +00:00
@Override
2017-06-17 17:54:09 +00:00
public boolean verify ( String domain , String hostname , SSLSession sslSession ) {
2015-10-15 15:08:38 +00:00
try {
2015-10-15 16:06:26 +00:00
Certificate [ ] chain = sslSession . getPeerCertificates ( ) ;
if ( chain . length = = 0 | | ! ( chain [ 0 ] instanceof X509Certificate ) ) {
return false ;
}
X509Certificate certificate = ( X509Certificate ) chain [ 0 ] ;
2017-07-17 19:11:15 +00:00
final List < String > commonNames = getCommonNames ( certificate ) ;
2017-07-17 21:13:55 +00:00
final boolean isSelfSignedCertificate = isSelfSigned ( certificate ) ;
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . KITKAT | | isSelfSignedCertificate ) {
2017-07-17 19:11:15 +00:00
if ( commonNames . size ( ) = = 1 & & commonNames . get ( 0 ) . equals ( domain ) ) {
2017-07-17 21:13:55 +00:00
Log . d ( LOGTAG , " accepted CN in cert as work around for " + domain + " isSelfSigned= " + Boolean . toString ( isSelfSignedCertificate ) + " , sdkInt= " + Build . VERSION . SDK_INT ) ;
2017-07-16 09:05:19 +00:00
return true ;
}
}
2015-10-15 16:06:26 +00:00
Collection < List < ? > > alternativeNames = certificate . getSubjectAlternativeNames ( ) ;
2015-10-15 15:08:38 +00:00
List < String > xmppAddrs = new ArrayList < > ( ) ;
List < String > srvNames = new ArrayList < > ( ) ;
List < String > domains = new ArrayList < > ( ) ;
if ( alternativeNames ! = null ) {
2015-10-15 18:05:55 +00:00
for ( List < ? > san : alternativeNames ) {
2015-10-15 15:08:38 +00:00
Integer type = ( Integer ) san . get ( 0 ) ;
if ( type = = 0 ) {
2015-10-15 18:05:55 +00:00
Pair < String , String > otherName = parseOtherName ( ( byte [ ] ) san . get ( 1 ) ) ;
if ( otherName ! = null ) {
switch ( otherName . first ) {
2017-07-17 19:11:15 +00:00
case SRV_NAME :
2015-10-15 18:05:55 +00:00
srvNames . add ( otherName . second ) ;
break ;
2017-07-17 19:11:15 +00:00
case XMPP_ADDR :
2015-10-15 18:05:55 +00:00
xmppAddrs . add ( otherName . second ) ;
break ;
default :
Log . d ( LOGTAG , " oid: " + otherName . first + " value: " + otherName . second ) ;
2015-10-15 15:08:38 +00:00
}
}
} else if ( type = = 2 ) {
Object value = san . get ( 1 ) ;
if ( value instanceof String ) {
domains . add ( ( String ) value ) ;
}
}
}
}
if ( srvNames . size ( ) = = 0 & & xmppAddrs . size ( ) = = 0 & & domains . size ( ) = = 0 ) {
2017-07-17 19:11:15 +00:00
domains . addAll ( commonNames ) ;
2015-10-15 15:08:38 +00:00
}
Log . d ( LOGTAG , " searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains: " + domains ) ;
2017-06-17 17:54:09 +00:00
if ( hostname ! = null ) {
Log . d ( LOGTAG , " also trying to verify hostname " + hostname ) ;
}
return xmppAddrs . contains ( domain )
| | srvNames . contains ( " _xmpp-client. " + domain )
| | matchDomain ( domain , domains )
| | ( hostname ! = null & & matchDomain ( hostname , domains ) ) ;
2015-10-15 15:08:38 +00:00
} catch ( Exception e ) {
return false ;
}
}
2017-07-16 09:05:19 +00:00
private static List < String > getCommonNames ( X509Certificate certificate ) {
List < String > domains = new ArrayList < > ( ) ;
try {
X500Name x500name = new JcaX509CertificateHolder ( certificate ) . getSubject ( ) ;
RDN [ ] rdns = x500name . getRDNs ( BCStyle . CN ) ;
for ( int i = 0 ; i < rdns . length ; + + i ) {
domains . add ( IETFUtils . valueToString ( x500name . getRDNs ( BCStyle . CN ) [ i ] . getFirst ( ) . getValue ( ) ) ) ;
}
return domains ;
} catch ( CertificateEncodingException e ) {
return domains ;
}
}
2015-10-15 18:05:55 +00:00
private static Pair < String , String > parseOtherName ( byte [ ] otherName ) {
try {
ASN1Primitive asn1Primitive = ASN1Primitive . fromByteArray ( otherName ) ;
if ( asn1Primitive instanceof DERTaggedObject ) {
ASN1Primitive inner = ( ( DERTaggedObject ) asn1Primitive ) . getObject ( ) ;
if ( inner instanceof DLSequence ) {
DLSequence sequence = ( DLSequence ) inner ;
if ( sequence . size ( ) > = 2 & & sequence . getObjectAt ( 1 ) instanceof DERTaggedObject ) {
String oid = sequence . getObjectAt ( 0 ) . toString ( ) ;
ASN1Primitive value = ( ( DERTaggedObject ) sequence . getObjectAt ( 1 ) ) . getObject ( ) ;
if ( value instanceof DERUTF8String ) {
return new Pair < > ( oid , ( ( DERUTF8String ) value ) . getString ( ) ) ;
} else if ( value instanceof DERIA5String ) {
return new Pair < > ( oid , ( ( DERIA5String ) value ) . getString ( ) ) ;
}
}
}
}
return null ;
} catch ( IOException e ) {
return null ;
}
}
private static boolean matchDomain ( String needle , List < String > haystack ) {
for ( String entry : haystack ) {
2015-10-15 15:08:38 +00:00
if ( entry . startsWith ( " *. " ) ) {
int i = needle . indexOf ( '.' ) ;
2015-10-15 18:05:55 +00:00
Log . d ( LOGTAG , " comparing " + needle . substring ( i ) + " and " + entry . substring ( 1 ) ) ;
2015-10-15 16:06:26 +00:00
if ( i ! = - 1 & & needle . substring ( i ) . equals ( entry . substring ( 1 ) ) ) {
2015-10-15 18:05:55 +00:00
Log . d ( LOGTAG , " domain " + needle + " matched " + entry ) ;
2015-10-15 15:08:38 +00:00
return true ;
}
} else {
if ( entry . equals ( needle ) ) {
2015-10-15 18:05:55 +00:00
Log . d ( LOGTAG , " domain " + needle + " matched " + entry ) ;
2015-10-15 15:08:38 +00:00
return true ;
}
}
}
return false ;
}
2017-06-17 17:54:09 +00:00
2017-07-16 09:05:19 +00:00
private boolean isSelfSigned ( X509Certificate certificate ) {
try {
certificate . verify ( certificate . getPublicKey ( ) ) ;
return true ;
} catch ( Exception e ) {
return false ;
}
}
2017-06-17 17:54:09 +00:00
@Override
public boolean verify ( String domain , SSLSession sslSession ) {
return verify ( domain , null , sslSession ) ;
}
2015-10-15 15:08:38 +00:00
}