Software /
code /
prosody
Annotate
util/x509.lua @ 5915:e6fed1d80116
Back out 1b0ac7950129, as SSLv3 appears to still be in moderate use on the network. Also, although obsolete, SSLv3 isn't documented to have any weaknesses that TLS 1.0 (the most common version used today) doesn't also have. Get your act together clients!
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Tue, 12 Nov 2013 02:13:01 +0000 |
parent | 4825:5fdc36bd866c |
child | 5845:c48f717c2fd6 |
rev | line source |
---|---|
3651 | 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 | |
4330
520fcb333cba
util.x509: Update references to published RFCs
Paul Aurich <paul@darkrain42.org>
parents:
3735
diff
changeset
|
14 -- [TLS-CERTS] - http://tools.ietf.org/html/rfc6125 |
520fcb333cba
util.x509: Update references to published RFCs
Paul Aurich <paul@darkrain42.org>
parents:
3735
diff
changeset
|
15 -- [XMPP-CORE] - http://tools.ietf.org/html/rfc6120 |
3651 | 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"); |
4486
f04db5e7e90d
user.x509: Add some utility functions for generating OpenSSL configs
Kim Alvefur <zash@zash.se>
parents:
4330
diff
changeset
|
24 local pairs, ipairs = pairs, ipairs; |
f04db5e7e90d
user.x509: Add some utility functions for generating OpenSSL configs
Kim Alvefur <zash@zash.se>
parents:
4330
diff
changeset
|
25 local s_format = string.format; |
f04db5e7e90d
user.x509: Add some utility functions for generating OpenSSL configs
Kim Alvefur <zash@zash.se>
parents:
4330
diff
changeset
|
26 local t_insert = table.insert; |
f04db5e7e90d
user.x509: Add some utility functions for generating OpenSSL configs
Kim Alvefur <zash@zash.se>
parents:
4330
diff
changeset
|
27 local t_concat = table.concat; |
3651 | 28 |
3735
40b54c46a14c
util.x509: "certverification" -> "x509".
Waqas Hussain <waqas20@gmail.com>
parents:
3733
diff
changeset
|
29 module "x509" |
3651 | 30 |
31 local oid_commonname = "2.5.4.3"; -- [LDAP] 2.3 | |
32 local oid_subjectaltname = "2.5.29.17"; -- [PKIX] 4.2.1.6 | |
33 local oid_xmppaddr = "1.3.6.1.5.5.7.8.5"; -- [XMPP-CORE] | |
34 local oid_dnssrv = "1.3.6.1.5.5.7.8.7"; -- [SRV-ID] | |
35 | |
36 -- Compare a hostname (possibly international) with asserted names | |
37 -- extracted from a certificate. | |
38 -- This function follows the rules laid out in | |
4330
520fcb333cba
util.x509: Update references to published RFCs
Paul Aurich <paul@darkrain42.org>
parents:
3735
diff
changeset
|
39 -- sections 6.4.1 and 6.4.2 of [TLS-CERTS] |
3651 | 40 -- |
41 -- A wildcard ("*") all by itself is allowed only as the left-most label | |
42 local function compare_dnsname(host, asserted_names) | |
43 -- TODO: Sufficient normalization? Review relevant specs. | |
44 local norm_host = idna_to_ascii(host) | |
45 if norm_host == nil then | |
46 log("info", "Host %s failed IDNA ToASCII operation", host) | |
47 return false | |
48 end | |
49 | |
50 norm_host = norm_host:lower() | |
51 | |
52 local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label | |
53 | |
54 for i=1,#asserted_names do | |
55 local name = asserted_names[i] | |
56 if norm_host == name:lower() then | |
57 log("debug", "Cert dNSName %s matched hostname", name); | |
58 return true | |
59 end | |
60 | |
61 -- Allow the left most label to be a "*" | |
62 if name:match("^%*%.") then | |
63 local rest_name = name:gsub("^[^.]+%.", "") | |
64 if host_chopped == rest_name:lower() then | |
65 log("debug", "Cert dNSName %s matched hostname", name); | |
66 return true | |
67 end | |
68 end | |
69 end | |
70 | |
71 return false | |
72 end | |
73 | |
74 -- Compare an XMPP domain name with the asserted id-on-xmppAddr | |
75 -- identities extracted from a certificate. Both are UTF8 strings. | |
76 -- | |
77 -- Per [XMPP-CORE], matches against asserted identities don't include | |
78 -- wildcards, so we just do a normalize on both and then a string comparison | |
79 -- | |
80 -- TODO: Support for full JIDs? | |
81 local function compare_xmppaddr(host, asserted_names) | |
82 local norm_host = nameprep(host) | |
83 | |
84 for i=1,#asserted_names do | |
85 local name = asserted_names[i] | |
86 | |
87 -- We only want to match against bare domains right now, not | |
88 -- those crazy full-er JIDs. | |
89 if name:match("[@/]") then | |
90 log("debug", "Ignoring xmppAddr %s because it's not a bare domain", name) | |
91 else | |
92 local norm_name = nameprep(name) | |
93 if norm_name == nil then | |
94 log("info", "Ignoring xmppAddr %s, failed nameprep!", name) | |
95 else | |
96 if norm_host == norm_name then | |
97 log("debug", "Cert xmppAddr %s matched hostname", name) | |
98 return true | |
99 end | |
100 end | |
101 end | |
102 end | |
103 | |
104 return false | |
105 end | |
106 | |
107 -- Compare a host + service against the asserted id-on-dnsSRV (SRV-ID) | |
108 -- identities extracted from a certificate. | |
109 -- | |
110 -- Per [SRV-ID], the asserted identities will be encoded in ASCII via ToASCII. | |
111 -- Comparison is done case-insensitively, and a wildcard ("*") all by itself | |
112 -- is allowed only as the left-most non-service label. | |
113 local function compare_srvname(host, service, asserted_names) | |
114 local norm_host = idna_to_ascii(host) | |
115 if norm_host == nil then | |
116 log("info", "Host %s failed IDNA ToASCII operation", host); | |
117 return false | |
118 end | |
119 | |
120 -- Service names start with a "_" | |
121 if service:match("^_") == nil then service = "_"..service end | |
122 | |
123 norm_host = norm_host:lower(); | |
124 local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label | |
125 | |
126 for i=1,#asserted_names do | |
127 local asserted_service, name = asserted_names[i]:match("^(_[^.]+)%.(.*)"); | |
128 if service == asserted_service then | |
129 if norm_host == name:lower() then | |
130 log("debug", "Cert SRVName %s matched hostname", name); | |
131 return true; | |
132 end | |
133 | |
134 -- Allow the left most label to be a "*" | |
135 if name:match("^%*%.") then | |
136 local rest_name = name:gsub("^[^.]+%.", "") | |
137 if host_chopped == rest_name:lower() then | |
138 log("debug", "Cert SRVName %s matched hostname", name) | |
139 return true | |
140 end | |
141 end | |
142 if norm_host == name:lower() then | |
143 log("debug", "Cert SRVName %s matched hostname", name); | |
144 return true | |
145 end | |
146 end | |
147 end | |
148 | |
149 return false | |
150 end | |
151 | |
152 function verify_identity(host, service, cert) | |
153 local ext = cert:extensions() | |
154 if ext[oid_subjectaltname] then | |
155 local sans = ext[oid_subjectaltname]; | |
156 | |
4330
520fcb333cba
util.x509: Update references to published RFCs
Paul Aurich <paul@darkrain42.org>
parents:
3735
diff
changeset
|
157 -- Per [TLS-CERTS] 6.3, 6.4.4, "a client MUST NOT seek a match for a |
3651 | 158 -- reference identifier if the presented identifiers include a DNS-ID |
159 -- SRV-ID, URI-ID, or any application-specific identifier types" | |
160 local had_supported_altnames = false | |
161 | |
162 if sans[oid_xmppaddr] then | |
163 had_supported_altnames = true | |
164 if compare_xmppaddr(host, sans[oid_xmppaddr]) then return true end | |
165 end | |
166 | |
167 if sans[oid_dnssrv] then | |
168 had_supported_altnames = true | |
169 -- Only check srvNames if the caller specified a service | |
170 if service and compare_srvname(host, service, sans[oid_dnssrv]) then return true end | |
171 end | |
172 | |
173 if sans["dNSName"] then | |
174 had_supported_altnames = true | |
175 if compare_dnsname(host, sans["dNSName"]) then return true end | |
176 end | |
177 | |
178 -- We don't need URIs, but [TLS-CERTS] is clear. | |
179 if sans["uniformResourceIdentifier"] then | |
180 had_supported_altnames = true | |
181 end | |
182 | |
183 if had_supported_altnames then return false end | |
184 end | |
185 | |
186 -- Extract a common name from the certificate, and check it as if it were | |
187 -- a dNSName subjectAltName (wildcards may apply for, and receive, | |
188 -- cat treats) | |
189 -- | |
4330
520fcb333cba
util.x509: Update references to published RFCs
Paul Aurich <paul@darkrain42.org>
parents:
3735
diff
changeset
|
190 -- Per [TLS-CERTS] 1.8, a CN-ID is the Common Name from a cert subject |
3651 | 191 -- which has one and only one Common Name |
192 local subject = cert:subject() | |
193 local cn = nil | |
194 for i=1,#subject do | |
195 local dn = subject[i] | |
196 if dn["oid"] == oid_commonname then | |
197 if cn then | |
198 log("info", "Certificate has multiple common names") | |
199 return false | |
200 end | |
201 | |
202 cn = dn["value"]; | |
203 end | |
204 end | |
205 | |
206 if cn then | |
4330
520fcb333cba
util.x509: Update references to published RFCs
Paul Aurich <paul@darkrain42.org>
parents:
3735
diff
changeset
|
207 -- Per [TLS-CERTS] 6.4.4, follow the comparison rules for dNSName SANs. |
3651 | 208 return compare_dnsname(host, { cn }) |
209 end | |
210 | |
211 -- If all else fails, well, why should we be any different? | |
212 return false | |
213 end | |
214 | |
215 return _M; |