aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl/protocol/sip/net/RFC5922Matcher.java
blob: 262b7174d1f734c9a04fa069308f81b16c4beafa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/*
 * 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 <a
 * href="http://tools.ietf.org/html/rfc5922#section-7.1">RFC5922, Section
 * 7.1</a> 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
     * <a href="http://tools.ietf.org/html/rfc5922#section-7.2">RFC5922 Section
     * 7.2</a>
     */
    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<String> 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<String> 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<String> extractCertIdentities(X509Certificate cert)
    {
        List<String> certIdentities = new ArrayList<String>();
        Collection<List<?>> 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;
    }
}