Software /
code /
prosody
Annotate
util/x509.lua @ 12181:783056b4e448 0.11 0.11.12
util.xml: Do not allow doctypes, comments or processing instructions
Yes. This is as bad as it sounds. CVE pending.
In Prosody itself, this only affects mod_websocket, which uses util.xml
to parse the <open/> frame, thus allowing unauthenticated remote DoS
using Billion Laughs. However, third-party modules using util.xml may
also be affected by this.
This commit installs handlers which disallow the use of doctype
declarations and processing instructions without any escape hatch. It,
by default, also introduces such a handler for comments, however, there
is a way to enable comments nontheless.
This is because util.xml is used to parse human-facing data, where
comments are generally a desirable feature, and also because comments
are generally harmless.
author | Jonas Schäfer <jonas@wielicki.name> |
---|---|
date | Mon, 10 Jan 2022 18:23:54 +0100 |
parent | 8555:4f0f5b49bb03 |
child | 9907:54e36a8677bc |
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; | |
6152
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
23 local base64 = require "util.encodings".base64; |
3735
40b54c46a14c
util.x509: "certverification" -> "x509".
Waqas Hussain <waqas20@gmail.com>
parents:
3733
diff
changeset
|
24 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
|
25 local s_format = string.format; |
3651 | 26 |
6777
5de6b93d0190
util.*: Remove use of module() function, make all module functions local and return them in a table at the end
Kim Alvefur <zash@zash.se>
parents:
6708
diff
changeset
|
27 local _ENV = nil; |
8555
4f0f5b49bb03
vairious: Add annotation when an empty environment is set [luacheck]
Kim Alvefur <zash@zash.se>
parents:
6777
diff
changeset
|
28 -- luacheck: std none |
3651 | 29 |
30 local oid_commonname = "2.5.4.3"; -- [LDAP] 2.3 | |
31 local oid_subjectaltname = "2.5.29.17"; -- [PKIX] 4.2.1.6 | |
32 local oid_xmppaddr = "1.3.6.1.5.5.7.8.5"; -- [XMPP-CORE] | |
33 local oid_dnssrv = "1.3.6.1.5.5.7.8.7"; -- [SRV-ID] | |
34 | |
35 -- Compare a hostname (possibly international) with asserted names | |
36 -- extracted from a certificate. | |
37 -- 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
|
38 -- sections 6.4.1 and 6.4.2 of [TLS-CERTS] |
3651 | 39 -- |
40 -- A wildcard ("*") all by itself is allowed only as the left-most label | |
41 local function compare_dnsname(host, asserted_names) | |
42 -- TODO: Sufficient normalization? Review relevant specs. | |
43 local norm_host = idna_to_ascii(host) | |
44 if norm_host == nil then | |
45 log("info", "Host %s failed IDNA ToASCII operation", host) | |
46 return false | |
47 end | |
48 | |
49 norm_host = norm_host:lower() | |
50 | |
51 local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label | |
52 | |
53 for i=1,#asserted_names do | |
54 local name = asserted_names[i] | |
55 if norm_host == name:lower() then | |
56 log("debug", "Cert dNSName %s matched hostname", name); | |
57 return true | |
58 end | |
59 | |
60 -- Allow the left most label to be a "*" | |
61 if name:match("^%*%.") then | |
62 local rest_name = name:gsub("^[^.]+%.", "") | |
63 if host_chopped == rest_name:lower() then | |
64 log("debug", "Cert dNSName %s matched hostname", name); | |
65 return true | |
66 end | |
67 end | |
68 end | |
69 | |
70 return false | |
71 end | |
72 | |
73 -- Compare an XMPP domain name with the asserted id-on-xmppAddr | |
74 -- identities extracted from a certificate. Both are UTF8 strings. | |
75 -- | |
76 -- Per [XMPP-CORE], matches against asserted identities don't include | |
77 -- wildcards, so we just do a normalize on both and then a string comparison | |
78 -- | |
79 -- TODO: Support for full JIDs? | |
80 local function compare_xmppaddr(host, asserted_names) | |
81 local norm_host = nameprep(host) | |
82 | |
83 for i=1,#asserted_names do | |
84 local name = asserted_names[i] | |
85 | |
86 -- We only want to match against bare domains right now, not | |
87 -- those crazy full-er JIDs. | |
88 if name:match("[@/]") then | |
89 log("debug", "Ignoring xmppAddr %s because it's not a bare domain", name) | |
90 else | |
91 local norm_name = nameprep(name) | |
92 if norm_name == nil then | |
93 log("info", "Ignoring xmppAddr %s, failed nameprep!", name) | |
94 else | |
95 if norm_host == norm_name then | |
96 log("debug", "Cert xmppAddr %s matched hostname", name) | |
97 return true | |
98 end | |
99 end | |
100 end | |
101 end | |
102 | |
103 return false | |
104 end | |
105 | |
106 -- Compare a host + service against the asserted id-on-dnsSRV (SRV-ID) | |
107 -- identities extracted from a certificate. | |
108 -- | |
109 -- Per [SRV-ID], the asserted identities will be encoded in ASCII via ToASCII. | |
110 -- Comparison is done case-insensitively, and a wildcard ("*") all by itself | |
111 -- is allowed only as the left-most non-service label. | |
112 local function compare_srvname(host, service, asserted_names) | |
113 local norm_host = idna_to_ascii(host) | |
114 if norm_host == nil then | |
115 log("info", "Host %s failed IDNA ToASCII operation", host); | |
116 return false | |
117 end | |
118 | |
119 -- Service names start with a "_" | |
120 if service:match("^_") == nil then service = "_"..service end | |
121 | |
122 norm_host = norm_host:lower(); | |
123 local host_chopped = norm_host:gsub("^[^.]+%.", "") -- everything after the first label | |
124 | |
125 for i=1,#asserted_names do | |
126 local asserted_service, name = asserted_names[i]:match("^(_[^.]+)%.(.*)"); | |
127 if service == asserted_service then | |
128 if norm_host == name:lower() then | |
129 log("debug", "Cert SRVName %s matched hostname", name); | |
130 return true; | |
131 end | |
132 | |
133 -- Allow the left most label to be a "*" | |
134 if name:match("^%*%.") then | |
135 local rest_name = name:gsub("^[^.]+%.", "") | |
136 if host_chopped == rest_name:lower() then | |
137 log("debug", "Cert SRVName %s matched hostname", name) | |
138 return true | |
139 end | |
140 end | |
141 if norm_host == name:lower() then | |
142 log("debug", "Cert SRVName %s matched hostname", name); | |
143 return true | |
144 end | |
145 end | |
146 end | |
147 | |
148 return false | |
149 end | |
150 | |
6777
5de6b93d0190
util.*: Remove use of module() function, make all module functions local and return them in a table at the end
Kim Alvefur <zash@zash.se>
parents:
6708
diff
changeset
|
151 local function verify_identity(host, service, cert) |
6708
d2beb98ece29
util.x509: Tell LuaSec we want UTF-8 data
Kim Alvefur <zash@zash.se>
parents:
6153
diff
changeset
|
152 if cert.setencode then |
d2beb98ece29
util.x509: Tell LuaSec we want UTF-8 data
Kim Alvefur <zash@zash.se>
parents:
6153
diff
changeset
|
153 cert:setencode("utf8"); |
d2beb98ece29
util.x509: Tell LuaSec we want UTF-8 data
Kim Alvefur <zash@zash.se>
parents:
6153
diff
changeset
|
154 end |
3651 | 155 local ext = cert:extensions() |
156 if ext[oid_subjectaltname] then | |
157 local sans = ext[oid_subjectaltname]; | |
158 | |
4330
520fcb333cba
util.x509: Update references to published RFCs
Paul Aurich <paul@darkrain42.org>
parents:
3735
diff
changeset
|
159 -- Per [TLS-CERTS] 6.3, 6.4.4, "a client MUST NOT seek a match for a |
3651 | 160 -- reference identifier if the presented identifiers include a DNS-ID |
161 -- SRV-ID, URI-ID, or any application-specific identifier types" | |
162 local had_supported_altnames = false | |
163 | |
164 if sans[oid_xmppaddr] then | |
165 had_supported_altnames = true | |
5845
c48f717c2fd6
util.x509: Only compare identity with oid-on-xmppAddr for XMPP services
Kim Alvefur <zash@zash.se>
parents:
4825
diff
changeset
|
166 if service == "_xmpp-client" or service == "_xmpp-server" then |
c48f717c2fd6
util.x509: Only compare identity with oid-on-xmppAddr for XMPP services
Kim Alvefur <zash@zash.se>
parents:
4825
diff
changeset
|
167 if compare_xmppaddr(host, sans[oid_xmppaddr]) then return true end |
c48f717c2fd6
util.x509: Only compare identity with oid-on-xmppAddr for XMPP services
Kim Alvefur <zash@zash.se>
parents:
4825
diff
changeset
|
168 end |
3651 | 169 end |
170 | |
171 if sans[oid_dnssrv] then | |
172 had_supported_altnames = true | |
173 -- Only check srvNames if the caller specified a service | |
174 if service and compare_srvname(host, service, sans[oid_dnssrv]) then return true end | |
175 end | |
176 | |
177 if sans["dNSName"] then | |
178 had_supported_altnames = true | |
179 if compare_dnsname(host, sans["dNSName"]) then return true end | |
180 end | |
181 | |
182 -- We don't need URIs, but [TLS-CERTS] is clear. | |
183 if sans["uniformResourceIdentifier"] then | |
184 had_supported_altnames = true | |
185 end | |
186 | |
187 if had_supported_altnames then return false end | |
188 end | |
189 | |
190 -- Extract a common name from the certificate, and check it as if it were | |
191 -- a dNSName subjectAltName (wildcards may apply for, and receive, | |
192 -- cat treats) | |
193 -- | |
4330
520fcb333cba
util.x509: Update references to published RFCs
Paul Aurich <paul@darkrain42.org>
parents:
3735
diff
changeset
|
194 -- Per [TLS-CERTS] 1.8, a CN-ID is the Common Name from a cert subject |
3651 | 195 -- which has one and only one Common Name |
196 local subject = cert:subject() | |
197 local cn = nil | |
198 for i=1,#subject do | |
199 local dn = subject[i] | |
200 if dn["oid"] == oid_commonname then | |
201 if cn then | |
202 log("info", "Certificate has multiple common names") | |
203 return false | |
204 end | |
205 | |
206 cn = dn["value"]; | |
207 end | |
208 end | |
209 | |
210 if cn then | |
4330
520fcb333cba
util.x509: Update references to published RFCs
Paul Aurich <paul@darkrain42.org>
parents:
3735
diff
changeset
|
211 -- Per [TLS-CERTS] 6.4.4, follow the comparison rules for dNSName SANs. |
3651 | 212 return compare_dnsname(host, { cn }) |
213 end | |
214 | |
215 -- If all else fails, well, why should we be any different? | |
216 return false | |
217 end | |
218 | |
6152
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
219 local pat = "%-%-%-%-%-BEGIN ([A-Z ]+)%-%-%-%-%-\r?\n".. |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
220 "([0-9A-Za-z+/=\r\n]*)\r?\n%-%-%-%-%-END %1%-%-%-%-%-"; |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
221 |
6777
5de6b93d0190
util.*: Remove use of module() function, make all module functions local and return them in a table at the end
Kim Alvefur <zash@zash.se>
parents:
6708
diff
changeset
|
222 local function pem2der(pem) |
6152
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
223 local typ, data = pem:match(pat); |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
224 if typ and data then |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
225 return base64.decode(data), typ; |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
226 end |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
227 end |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
228 |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
229 local wrap = ('.'):rep(64); |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
230 local envelope = "-----BEGIN %s-----\n%s\n-----END %s-----\n" |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
231 |
6777
5de6b93d0190
util.*: Remove use of module() function, make all module functions local and return them in a table at the end
Kim Alvefur <zash@zash.se>
parents:
6708
diff
changeset
|
232 local function der2pem(data, typ) |
6152
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
233 typ = typ and typ:upper() or "CERTIFICATE"; |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
234 data = base64.encode(data); |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
235 return s_format(envelope, typ, data:gsub(wrap, '%0\n', (#data-1)/64), typ); |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
236 end |
fbab74c28e31
util.x509: And functions for converting between DER and PEM
Kim Alvefur <zash@zash.se>
parents:
5845
diff
changeset
|
237 |
6777
5de6b93d0190
util.*: Remove use of module() function, make all module functions local and return them in a table at the end
Kim Alvefur <zash@zash.se>
parents:
6708
diff
changeset
|
238 return { |
5de6b93d0190
util.*: Remove use of module() function, make all module functions local and return them in a table at the end
Kim Alvefur <zash@zash.se>
parents:
6708
diff
changeset
|
239 verify_identity = verify_identity; |
5de6b93d0190
util.*: Remove use of module() function, make all module functions local and return them in a table at the end
Kim Alvefur <zash@zash.se>
parents:
6708
diff
changeset
|
240 pem2der = pem2der; |
5de6b93d0190
util.*: Remove use of module() function, make all module functions local and return them in a table at the end
Kim Alvefur <zash@zash.se>
parents:
6708
diff
changeset
|
241 der2pem = der2pem; |
5de6b93d0190
util.*: Remove use of module() function, make all module functions local and return them in a table at the end
Kim Alvefur <zash@zash.se>
parents:
6708
diff
changeset
|
242 }; |