Sat, 18 Dec 2010 23:15:58 +0000
configmanager: Switch back to returning 'ok' to signal config load success - fixes config errors not being displayed
3649 | 1 | -- Prosody IM |
2 | -- Copyright (C) 2010 Matthew Wild | |
3 | -- Copyright (C) 2010 Paul Aurich | |
4 | -- | |
5 | -- This project is MIT/X11 licensed. Please see the | |
6 | -- COPYING file in the source package for more information. | |
7 | -- | |
8 | ||
9 | -- TODO: I feel a fair amount of this logic should be integrated into Luasec, | |
10 | -- so that everyone isn't re-inventing the wheel. Dependencies on | |
11 | -- IDN libraries complicate that. | |
12 | ||
13 | ||
14 | -- [TLS-CERTS] - http://tools.ietf.org/html/draft-saintandre-tls-server-id-check-10 | |
15 | -- [XMPP-CORE] - http://tools.ietf.org/html/draft-ietf-xmpp-3920bis-18 | |
16 | -- [SRV-ID] - http://tools.ietf.org/html/rfc4985 | |
17 | -- [IDNA] - http://tools.ietf.org/html/rfc5890 | |
18 | -- [LDAP] - http://tools.ietf.org/html/rfc4519 | |
19 | -- [PKIX] - http://tools.ietf.org/html/rfc5280 | |
20 | ||
21 | local nameprep = require "util.encodings".stringprep.nameprep; | |
22 | local idna_to_ascii = require "util.encodings".idna.to_ascii; | |
3735
40b54c46a14c
util.x509: "certverification" -> "x509".
Waqas Hussain <waqas20@gmail.com>
parents:
3733
diff
changeset
|
23 | local log = require "util.logger".init("x509"); |
3649 | 24 | |
3735
40b54c46a14c
util.x509: "certverification" -> "x509".
Waqas Hussain <waqas20@gmail.com>
parents:
3733
diff
changeset
|
25 | module "x509" |
3649 | 26 | |
27 | local oid_commonname = "2.5.4.3"; -- [LDAP] 2.3 | |
28 | local oid_subjectaltname = "2.5.29.17"; -- [PKIX] 4.2.1.6 | |
29 | local oid_xmppaddr = "1.3.6.1.5.5.7.8.5"; -- [XMPP-CORE] | |
30 | local oid_dnssrv = "1.3.6.1.5.5.7.8.7"; -- [SRV-ID] | |
31 | ||
32 | -- Compare a hostname (possibly international) with asserted names | |
33 | -- extracted from a certificate. | |
34 | -- This function follows the rules laid out in | |
35 | -- sections 4.4.1 and 4.4.2 of [TLS-CERTS] | |
36 | -- | |
37 | -- A wildcard ("*") all by itself is allowed only as the left-most label | |
38 | local function compare_dnsname(host, asserted_names) | |
39 | -- TODO: Sufficient normalization? Review relevant specs. | |
40 | local norm_host = idna_to_ascii(host) | |
41 | if norm_host == nil then | |
42 | log("info", "Host %s failed IDNA ToASCII operation", host) | |
43 | return false | |
44 | end | |
45 | ||
46 | norm_host = norm_host:lower() | |
47 | ||
48 | local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label | |
49 | ||
50 | for i=1,#asserted_names do | |
51 | local name = asserted_names[i] | |
52 | if norm_host == name:lower() then | |
53 | log("debug", "Cert dNSName %s matched hostname", name); | |
54 | return true | |
55 | end | |
56 | ||
57 | -- Allow the left most label to be a "*" | |
58 | if name:match("^%*%.") then | |
59 | local rest_name = name:gsub("^[^.]+%.", "") | |
60 | if host_chopped == rest_name:lower() then | |
61 | log("debug", "Cert dNSName %s matched hostname", name); | |
62 | return true | |
63 | end | |
64 | end | |
65 | end | |
66 | ||
67 | return false | |
68 | end | |
69 | ||
70 | -- Compare an XMPP domain name with the asserted id-on-xmppAddr | |
71 | -- identities extracted from a certificate. Both are UTF8 strings. | |
72 | -- | |
73 | -- Per [XMPP-CORE], matches against asserted identities don't include | |
74 | -- wildcards, so we just do a normalize on both and then a string comparison | |
75 | -- | |
76 | -- TODO: Support for full JIDs? | |
77 | local function compare_xmppaddr(host, asserted_names) | |
78 | local norm_host = nameprep(host) | |
79 | ||
80 | for i=1,#asserted_names do | |
81 | local name = asserted_names[i] | |
82 | ||
83 | -- We only want to match against bare domains right now, not | |
84 | -- those crazy full-er JIDs. | |
85 | if name:match("[@/]") then | |
86 | log("debug", "Ignoring xmppAddr %s because it's not a bare domain", name) | |
87 | else | |
88 | local norm_name = nameprep(name) | |
89 | if norm_name == nil then | |
90 | log("info", "Ignoring xmppAddr %s, failed nameprep!", name) | |
91 | else | |
92 | if norm_host == norm_name then | |
93 | log("debug", "Cert xmppAddr %s matched hostname", name) | |
94 | return true | |
95 | end | |
96 | end | |
97 | end | |
98 | end | |
99 | ||
100 | return false | |
101 | end | |
102 | ||
103 | -- Compare a host + service against the asserted id-on-dnsSRV (SRV-ID) | |
104 | -- identities extracted from a certificate. | |
105 | -- | |
106 | -- Per [SRV-ID], the asserted identities will be encoded in ASCII via ToASCII. | |
107 | -- Comparison is done case-insensitively, and a wildcard ("*") all by itself | |
108 | -- is allowed only as the left-most non-service label. | |
109 | local function compare_srvname(host, service, asserted_names) | |
110 | local norm_host = idna_to_ascii(host) | |
111 | if norm_host == nil then | |
112 | log("info", "Host %s failed IDNA ToASCII operation", host); | |
113 | return false | |
114 | end | |
115 | ||
116 | -- Service names start with a "_" | |
117 | if service:match("^_") == nil then service = "_"..service end | |
118 | ||
119 | norm_host = norm_host:lower(); | |
120 | local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label | |
121 | ||
122 | for i=1,#asserted_names do | |
123 | local asserted_service, name = asserted_names[i]:match("^(_[^.]+)%.(.*)"); | |
124 | if service == asserted_service then | |
125 | if norm_host == name:lower() then | |
126 | log("debug", "Cert SRVName %s matched hostname", name); | |
127 | return true; | |
128 | end | |
129 | ||
130 | -- Allow the left most label to be a "*" | |
131 | if name:match("^%*%.") then | |
132 | local rest_name = name:gsub("^[^.]+%.", "") | |
133 | if host_chopped == rest_name:lower() then | |
134 | log("debug", "Cert SRVName %s matched hostname", name) | |
135 | return true | |
136 | end | |
137 | end | |
138 | if norm_host == name:lower() then | |
139 | log("debug", "Cert SRVName %s matched hostname", name); | |
140 | return true | |
141 | end | |
142 | end | |
143 | end | |
144 | ||
145 | return false | |
146 | end | |
147 | ||
148 | function verify_identity(host, service, cert) | |
149 | local ext = cert:extensions() | |
150 | if ext[oid_subjectaltname] then | |
151 | local sans = ext[oid_subjectaltname]; | |
152 | ||
153 | -- Per [TLS-CERTS] 4.3, 4.4.4, "a client MUST NOT seek a match for a | |
154 | -- reference identifier if the presented identifiers include a DNS-ID | |
155 | -- SRV-ID, URI-ID, or any application-specific identifier types" | |
156 | local had_supported_altnames = false | |
157 | ||
158 | if sans[oid_xmppaddr] then | |
159 | had_supported_altnames = true | |
160 | if compare_xmppaddr(host, sans[oid_xmppaddr]) then return true end | |
161 | end | |
162 | ||
163 | if sans[oid_dnssrv] then | |
164 | had_supported_altnames = true | |
165 | -- Only check srvNames if the caller specified a service | |
166 | if service and compare_srvname(host, service, sans[oid_dnssrv]) then return true end | |
167 | end | |
168 | ||
169 | if sans["dNSName"] then | |
170 | had_supported_altnames = true | |
171 | if compare_dnsname(host, sans["dNSName"]) then return true end | |
172 | end | |
173 | ||
174 | -- We don't need URIs, but [TLS-CERTS] is clear. | |
175 | if sans["uniformResourceIdentifier"] then | |
176 | had_supported_altnames = true | |
177 | end | |
178 | ||
179 | if had_supported_altnames then return false end | |
180 | end | |
181 | ||
182 | -- Extract a common name from the certificate, and check it as if it were | |
183 | -- a dNSName subjectAltName (wildcards may apply for, and receive, | |
184 | -- cat treats) | |
185 | -- | |
186 | -- Per [TLS-CERTS] 1.5, a CN-ID is the Common Name from a cert subject | |
187 | -- which has one and only one Common Name | |
188 | local subject = cert:subject() | |
189 | local cn = nil | |
190 | for i=1,#subject do | |
191 | local dn = subject[i] | |
192 | if dn["oid"] == oid_commonname then | |
193 | if cn then | |
194 | log("info", "Certificate has multiple common names") | |
195 | return false | |
196 | end | |
197 | ||
198 | cn = dn["value"]; | |
199 | end | |
200 | end | |
201 | ||
202 | if cn then | |
203 | -- Per [TLS-CERTS] 4.4.4, follow the comparison rules for dNSName SANs. | |
204 | return compare_dnsname(host, { cn }) | |
205 | end | |
206 | ||
207 | -- If all else fails, well, why should we be any different? | |
208 | return false | |
209 | end | |
210 | ||
211 | return _M; |