Software /
code /
prosody
Comparison
util/x509.lua @ 3733:26571a99f6e6
core.s2smanager, mod_console, mod_saslauth, util.certverification: rename util.certverification to util.x509
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sun, 12 Dec 2010 02:03:32 +0100 |
parent | 3651:util/certverification.lua@337391d34b70 |
child | 3735:40b54c46a14c |
comparison
equal
deleted
inserted
replaced
3732:bf449ecf2144 | 3733:26571a99f6e6 |
---|---|
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; | |
23 local log = require "util.logger".init("certverification"); | |
24 | |
25 module "certverification" | |
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; |