/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed 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 net.java.sip.communicator.impl.protocol.sip.net; import java.security.cert.*; import java.text.*; import java.util.*; import java.util.regex.*; import javax.sip.address.*; import net.java.sip.communicator.impl.protocol.sip.*; import net.java.sip.communicator.service.certificate.*; import net.java.sip.communicator.util.*; /** * Matcher that extracts certificate identities according to RFC5922, Section * 7.1 and compares them with the rules from Section 7.2 and 7.3. * @see #PNAME_STRICT_RFC5922 for wildcard handling; the default is false * * @author Ingo Bauersachs */ public class RFC5922Matcher implements CertificateMatcher { /** * When set to true, enables strict validation of the hostname according to * RFC5922 Section * 7.2 */ public final static String PNAME_STRICT_RFC5922 = "net.java.sip.communicator.sip.tls.STRICT_RFC5922"; private ProtocolProviderServiceSipImpl provider; /** * Creates a new instance of this class. * @param provider The SIP Provider to which this matcher belongs. */ public RFC5922Matcher(ProtocolProviderServiceSipImpl provider) { this.provider = provider; } /** Our class logger. */ private static final Logger logger = Logger .getLogger(CertificateMatcher.class); /* * (non-Javadoc) * * @see * net.java.sip.communicator.service.certificate.CertificateMatcher#verify * (java.lang.Iterable, java.security.cert.X509Certificate) */ public void verify(Iterable identitiesToTest, X509Certificate cert) throws CertificateException { boolean strict = SipActivator.getConfigurationService() .getBoolean(PNAME_STRICT_RFC5922, false); // if any of the identities is contained in the certificate we're good boolean oneMatched = false; Iterable certIdentities = extractCertIdentities(cert); for (String identity : identitiesToTest) { // check if the intended hostname is contained in one of the // hostnames of the certificate according to // http://tools.ietf.org/html/rfc5922#section-7.2 for(String dnsName : certIdentities) { try { if(NetworkUtils.compareDnsNames(dnsName, identity) == 0) { // one of the hostnames matched, we're good to go return; } if(!strict // is a wildcard name && dnsName.startsWith("*.") // contains at least two dots (*.example.com) && identity.indexOf(".") < identity.lastIndexOf(".") // compare *.example.com stripped to example.com with // - foo.example.com stripped to example.com // - foo.bar.example.com to bar.example.com && NetworkUtils.compareDnsNames( dnsName.substring(2), identity.substring(identity.indexOf(".")+1)) == 0) { // the wildcard matched, we're good to go return; } } catch (ParseException e) {} // we don't care - this hostname did not match } } if (!oneMatched) throw new CertificateException("None of <" + identitiesToTest + "> matched by the rules of RFC5922 to the cert with CN=" + cert.getSubjectDN()); } private Iterable extractCertIdentities(X509Certificate cert) { List certIdentities = new ArrayList(); Collection> subjAltNames = null; try { subjAltNames = cert.getSubjectAlternativeNames(); } catch (CertificateParsingException ex) { logger.error("Error parsing TLS certificate", ex); } // subjAltName types are defined in rfc2459 final Integer dnsNameType = 2; final Integer uriNameType = 6; if (subjAltNames != null) { if (logger.isDebugEnabled()) logger.debug("found subjAltNames: " + subjAltNames); // First look for a URI in the subjectAltName field for (List altName : subjAltNames) { // 0th position is the alt name type // 1st position is the alt name data if (altName.get(0).equals(uriNameType)) { SipURI altNameUri; try { altNameUri = provider.getAddressFactory().createSipURI( (String) altName.get(1)); // only sip URIs are allowed if (!"sip".equals(altNameUri.getScheme())) continue; // user certificates are not allowed if (altNameUri.getUser() != null) continue; String altHostName = altNameUri.getHost(); if (logger.isDebugEnabled()) { logger.debug("found uri " + altName.get(1) + ", hostName " + altHostName); } certIdentities.add(altHostName); } catch (ParseException e) { logger.error("certificate contains invalid uri: " + altName.get(1)); } } } // DNS An implementation MUST accept a domain name system // identifier as a SIP domain identity if and only if no other // identity is found that matches the "sip" URI type described // above. if (certIdentities.isEmpty()) { for (List altName : subjAltNames) { if (altName.get(0).equals(dnsNameType)) { if (logger.isDebugEnabled()) logger.debug("found dns " + altName.get(1)); certIdentities.add(altName.get(1).toString()); } } } } else { // If and only if the subjectAltName does not appear in the // certificate, the implementation MAY examine the CN field of the // certificate. If a valid DNS name is found there, the // implementation MAY accept this value as a SIP domain identity. String dname = cert.getSubjectDN().getName(); String cname = ""; try { Pattern EXTRACT_CN = Pattern.compile(".*CN\\s*=\\s*([\\w*\\.]+).*"); Matcher matcher = EXTRACT_CN.matcher(dname); if (matcher.matches()) { cname = matcher.group(1); if (logger.isDebugEnabled()) { logger.debug("found CN: " + cname + " from DN: " + dname); } certIdentities.add(cname); } } catch (Exception ex) { logger.error("exception while extracting CN", ex); } } return certIdentities; } }