|
11 | 11 | %% The Original Code is RabbitMQ.
|
12 | 12 | %%
|
13 | 13 | %% The Initial Developer of the Original Code is GoPivotal, Inc.
|
14 |
| -%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved. |
| 14 | +%% Copyright (c) 2007-2017 Pivotal Software, Inc. All rights reserved. |
15 | 15 | %%
|
16 | 16 |
|
17 | 17 | -module(rabbit_ssl).
|
18 | 18 |
|
19 |
| --include("rabbit.hrl"). |
20 |
| - |
21 | 19 | -include_lib("public_key/include/public_key.hrl").
|
22 | 20 |
|
23 | 21 | -export([peer_cert_issuer/1, peer_cert_subject/1, peer_cert_validity/1]).
|
|
27 | 25 |
|
28 | 26 | -export_type([certificate/0]).
|
29 | 27 |
|
30 |
| --type certificate() :: binary(). |
31 |
| - |
32 |
| --spec peer_cert_issuer(certificate()) -> string(). |
33 |
| --spec peer_cert_subject(certificate()) -> string(). |
34 |
| --spec peer_cert_validity(certificate()) -> string(). |
35 |
| --spec peer_cert_subject_items |
36 |
| - (certificate(), tuple()) -> [string()] | 'not_found'. |
37 |
| --spec peer_cert_auth_name |
38 |
| - (certificate()) -> binary() | 'not_found' | 'unsafe'. |
| 28 | +-type certificate() :: rabbit_cert_info:certificate(). |
39 | 29 |
|
40 | 30 | %%--------------------------------------------------------------------------
|
41 | 31 | %% High-level functions used by reader
|
42 | 32 | %%--------------------------------------------------------------------------
|
43 | 33 |
|
44 | 34 | %% Return a string describing the certificate's issuer.
|
45 | 35 | peer_cert_issuer(Cert) ->
|
46 |
| - cert_info(fun(#'OTPCertificate' { |
47 |
| - tbsCertificate = #'OTPTBSCertificate' { |
48 |
| - issuer = Issuer }}) -> |
49 |
| - format_rdn_sequence(Issuer) |
50 |
| - end, Cert). |
| 36 | + rabbit_cert_info:issuer(Cert). |
51 | 37 |
|
52 | 38 | %% Return a string describing the certificate's subject, as per RFC4514.
|
53 | 39 | peer_cert_subject(Cert) ->
|
54 |
| - cert_info(fun(#'OTPCertificate' { |
55 |
| - tbsCertificate = #'OTPTBSCertificate' { |
56 |
| - subject = Subject }}) -> |
57 |
| - format_rdn_sequence(Subject) |
58 |
| - end, Cert). |
| 40 | + rabbit_cert_info:subject(Cert). |
59 | 41 |
|
60 | 42 | %% Return the parts of the certificate's subject.
|
61 | 43 | peer_cert_subject_items(Cert, Type) ->
|
62 |
| - cert_info(fun(#'OTPCertificate' { |
63 |
| - tbsCertificate = #'OTPTBSCertificate' { |
64 |
| - subject = Subject }}) -> |
65 |
| - find_by_type(Type, Subject) |
66 |
| - end, Cert). |
| 44 | + rabbit_cert_info:subject_items(Cert, Type). |
67 | 45 |
|
68 | 46 | %% Return a string describing the certificate's validity.
|
69 | 47 | peer_cert_validity(Cert) ->
|
70 |
| - cert_info(fun(#'OTPCertificate' { |
71 |
| - tbsCertificate = #'OTPTBSCertificate' { |
72 |
| - validity = {'Validity', Start, End} }}) -> |
73 |
| - rabbit_misc:format("~s - ~s", [format_asn1_value(Start), |
74 |
| - format_asn1_value(End)]) |
75 |
| - end, Cert). |
| 48 | + rabbit_cert_info:validity(Cert). |
76 | 49 |
|
77 | 50 | %% Extract a username from the certificate
|
| 51 | +-spec peer_cert_auth_name |
| 52 | + (certificate()) -> binary() | 'not_found' | 'unsafe'. |
| 53 | + |
78 | 54 | peer_cert_auth_name(Cert) ->
|
79 | 55 | {ok, Mode} = application:get_env(rabbit, ssl_cert_login_from),
|
80 | 56 | peer_cert_auth_name(Mode, Cert).
|
@@ -106,189 +82,3 @@ auth_config_sane() ->
|
106 | 82 | "disabled, verify=~p~n", [V]),
|
107 | 83 | false
|
108 | 84 | end.
|
109 |
| - |
110 |
| -%%-------------------------------------------------------------------------- |
111 |
| - |
112 |
| -cert_info(F, Cert) -> |
113 |
| - F(case public_key:pkix_decode_cert(Cert, otp) of |
114 |
| - {ok, DecCert} -> DecCert; %%pre R14B |
115 |
| - DecCert -> DecCert %%R14B onwards |
116 |
| - end). |
117 |
| - |
118 |
| -find_by_type(Type, {rdnSequence, RDNs}) -> |
119 |
| - case [V || #'AttributeTypeAndValue'{type = T, value = V} |
120 |
| - <- lists:flatten(RDNs), |
121 |
| - T == Type] of |
122 |
| - [] -> not_found; |
123 |
| - L -> [format_asn1_value(V) || V <- L] |
124 |
| - end. |
125 |
| - |
126 |
| -%%-------------------------------------------------------------------------- |
127 |
| -%% Formatting functions |
128 |
| -%%-------------------------------------------------------------------------- |
129 |
| - |
130 |
| -%% Format and rdnSequence as a RFC4514 subject string. |
131 |
| -format_rdn_sequence({rdnSequence, Seq}) -> |
132 |
| - string:join(lists:reverse([format_complex_rdn(RDN) || RDN <- Seq]), ","). |
133 |
| - |
134 |
| -%% Format an RDN set. |
135 |
| -format_complex_rdn(RDNs) -> |
136 |
| - string:join([format_rdn(RDN) || RDN <- RDNs], "+"). |
137 |
| - |
138 |
| -%% Format an RDN. If the type name is unknown, use the dotted decimal |
139 |
| -%% representation. See RFC4514, section 2.3. |
140 |
| -format_rdn(#'AttributeTypeAndValue'{type = T, value = V}) -> |
141 |
| - FV = escape_rdn_value(format_asn1_value(V)), |
142 |
| - Fmts = [{?'id-at-surname' , "SN"}, |
143 |
| - {?'id-at-givenName' , "GIVENNAME"}, |
144 |
| - {?'id-at-initials' , "INITIALS"}, |
145 |
| - {?'id-at-generationQualifier' , "GENERATIONQUALIFIER"}, |
146 |
| - {?'id-at-commonName' , "CN"}, |
147 |
| - {?'id-at-localityName' , "L"}, |
148 |
| - {?'id-at-stateOrProvinceName' , "ST"}, |
149 |
| - {?'id-at-organizationName' , "O"}, |
150 |
| - {?'id-at-organizationalUnitName' , "OU"}, |
151 |
| - {?'id-at-title' , "TITLE"}, |
152 |
| - {?'id-at-countryName' , "C"}, |
153 |
| - {?'id-at-serialNumber' , "SERIALNUMBER"}, |
154 |
| - {?'id-at-pseudonym' , "PSEUDONYM"}, |
155 |
| - {?'id-domainComponent' , "DC"}, |
156 |
| - {?'id-emailAddress' , "EMAILADDRESS"}, |
157 |
| - {?'street-address' , "STREET"}, |
158 |
| - {{0,9,2342,19200300,100,1,1} , "UID"}], %% Not in public_key.hrl |
159 |
| - case proplists:lookup(T, Fmts) of |
160 |
| - {_, Fmt} -> |
161 |
| - rabbit_misc:format(Fmt ++ "=~s", [FV]); |
162 |
| - none when is_tuple(T) -> |
163 |
| - TypeL = [rabbit_misc:format("~w", [X]) || X <- tuple_to_list(T)], |
164 |
| - rabbit_misc:format("~s=~s", [string:join(TypeL, "."), FV]); |
165 |
| - none -> |
166 |
| - rabbit_misc:format("~p=~s", [T, FV]) |
167 |
| - end. |
168 |
| - |
169 |
| -%% Escape a string as per RFC4514. |
170 |
| -escape_rdn_value(V) -> |
171 |
| - escape_rdn_value(V, start). |
172 |
| - |
173 |
| -escape_rdn_value([], _) -> |
174 |
| - []; |
175 |
| -escape_rdn_value([C | S], start) when C =:= $ ; C =:= $# -> |
176 |
| - [$\\, C | escape_rdn_value(S, middle)]; |
177 |
| -escape_rdn_value(S, start) -> |
178 |
| - escape_rdn_value(S, middle); |
179 |
| -escape_rdn_value([$ ], middle) -> |
180 |
| - [$\\, $ ]; |
181 |
| -escape_rdn_value([C | S], middle) when C =:= $"; C =:= $+; C =:= $,; C =:= $;; |
182 |
| - C =:= $<; C =:= $>; C =:= $\\ -> |
183 |
| - [$\\, C | escape_rdn_value(S, middle)]; |
184 |
| -escape_rdn_value([C | S], middle) when C < 32 ; C >= 126 -> |
185 |
| - %% Of ASCII characters only U+0000 needs escaping, but for display |
186 |
| - %% purposes it's handy to escape all non-printable chars. All non-ASCII |
187 |
| - %% characters get converted to UTF-8 sequences and then escaped. We've |
188 |
| - %% already got a UTF-8 sequence here, so just escape it. |
189 |
| - rabbit_misc:format("\\~2.16.0B", [C]) ++ escape_rdn_value(S, middle); |
190 |
| -escape_rdn_value([C | S], middle) -> |
191 |
| - [C | escape_rdn_value(S, middle)]. |
192 |
| - |
193 |
| -%% Get the string representation of an OTPCertificate field. |
194 |
| -format_asn1_value({ST, S}) when ST =:= teletexString; ST =:= printableString; |
195 |
| - ST =:= universalString; ST =:= utf8String; |
196 |
| - ST =:= bmpString -> |
197 |
| - format_directory_string(ST, S); |
198 |
| -format_asn1_value({utcTime, [Y1, Y2, M1, M2, D1, D2, H1, H2, |
199 |
| - Min1, Min2, S1, S2, $Z]}) -> |
200 |
| - rabbit_misc:format("20~c~c-~c~c-~c~cT~c~c:~c~c:~c~cZ", |
201 |
| - [Y1, Y2, M1, M2, D1, D2, H1, H2, Min1, Min2, S1, S2]); |
202 |
| -%% We appear to get an untagged value back for an ia5string |
203 |
| -%% (e.g. domainComponent). |
204 |
| -format_asn1_value(V) when is_list(V) -> |
205 |
| - V; |
206 |
| -format_asn1_value(V) when is_binary(V) -> |
207 |
| - %% OTP does not decode some values when combined with an unknown |
208 |
| - %% type. That's probably wrong, so as a last ditch effort let's |
209 |
| - %% try manually decoding. 'DirectoryString' is semi-arbitrary - |
210 |
| - %% but it is the type which covers the various string types we |
211 |
| - %% handle below. |
212 |
| - try |
213 |
| - {ST, S} = public_key:der_decode('DirectoryString', V), |
214 |
| - format_directory_string(ST, S) |
215 |
| - catch _:_ -> |
216 |
| - rabbit_misc:format("~p", [V]) |
217 |
| - end; |
218 |
| -format_asn1_value(V) -> |
219 |
| - rabbit_misc:format("~p", [V]). |
220 |
| - |
221 |
| -%% DirectoryString { INTEGER : maxSize } ::= CHOICE { |
222 |
| -%% teletexString TeletexString (SIZE (1..maxSize)), |
223 |
| -%% printableString PrintableString (SIZE (1..maxSize)), |
224 |
| -%% bmpString BMPString (SIZE (1..maxSize)), |
225 |
| -%% universalString UniversalString (SIZE (1..maxSize)), |
226 |
| -%% uTF8String UTF8String (SIZE (1..maxSize)) } |
227 |
| -%% |
228 |
| -%% Precise definitions of printable / teletexString are hard to come |
229 |
| -%% by. This is what I reconstructed: |
230 |
| -%% |
231 |
| -%% printableString: |
232 |
| -%% "intended to represent the limited character sets available to |
233 |
| -%% mainframe input terminals" |
234 |
| -%% A-Z a-z 0-9 ' ( ) + , - . / : = ? [space] |
235 |
| -%% http://msdn.microsoft.com/en-us/library/bb540814(v=vs.85).aspx |
236 |
| -%% |
237 |
| -%% teletexString: |
238 |
| -%% "a sizable volume of software in the world treats TeletexString |
239 |
| -%% (T61String) as a simple 8-bit string with mostly Windows Latin 1 |
240 |
| -%% (superset of iso-8859-1) encoding" |
241 |
| -%% http://www.mail-archive.com/[email protected]/msg00460.html |
242 |
| -%% |
243 |
| -%% (However according to that link X.680 actually defines |
244 |
| -%% TeletexString in some much more involved and crazy way. I suggest |
245 |
| -%% we treat it as ISO-8859-1 since Erlang does not support Windows |
246 |
| -%% Latin 1). |
247 |
| -%% |
248 |
| -%% bmpString: |
249 |
| -%% UCS-2 according to RFC 3641. Hence cannot represent Unicode |
250 |
| -%% characters above 65535 (outside the "Basic Multilingual Plane"). |
251 |
| -%% |
252 |
| -%% universalString: |
253 |
| -%% UCS-4 according to RFC 3641. |
254 |
| -%% |
255 |
| -%% utf8String: |
256 |
| -%% UTF-8 according to RFC 3641. |
257 |
| -%% |
258 |
| -%% Within Rabbit we assume UTF-8 encoding. Since printableString is a |
259 |
| -%% subset of ASCII it is also a subset of UTF-8. The others need |
260 |
| -%% converting. Fortunately since the Erlang SSL library does the |
261 |
| -%% decoding for us (albeit into a weird format, see below), we just |
262 |
| -%% need to handle encoding into UTF-8. Note also that utf8Strings come |
263 |
| -%% back as binary. |
264 |
| -%% |
265 |
| -%% Note for testing: the default Ubuntu configuration for openssl will |
266 |
| -%% only create printableString or teletexString types no matter what |
267 |
| -%% you do. Edit string_mask in the [req] section of |
268 |
| -%% /etc/ssl/openssl.cnf to change this (see comments there). You |
269 |
| -%% probably also need to set utf8 = yes to get it to accept UTF-8 on |
270 |
| -%% the command line. Also note I could not get openssl to generate a |
271 |
| -%% universalString. |
272 |
| - |
273 |
| -format_directory_string(printableString, S) -> S; |
274 |
| -format_directory_string(teletexString, S) -> utf8_list_from(S); |
275 |
| -format_directory_string(bmpString, S) -> utf8_list_from(S); |
276 |
| -format_directory_string(universalString, S) -> utf8_list_from(S); |
277 |
| -format_directory_string(utf8String, S) -> binary_to_list(S). |
278 |
| - |
279 |
| -utf8_list_from(S) -> |
280 |
| - binary_to_list( |
281 |
| - unicode:characters_to_binary(flatten_ssl_list(S), utf32, utf8)). |
282 |
| - |
283 |
| -%% The Erlang SSL implementation invents its own representation for |
284 |
| -%% non-ascii strings - looking like [97,{0,0,3,187}] (that's LATIN |
285 |
| -%% SMALL LETTER A followed by GREEK SMALL LETTER LAMDA). We convert |
286 |
| -%% this into a list of unicode characters, which we can tell |
287 |
| -%% unicode:characters_to_binary is utf32. |
288 |
| - |
289 |
| -flatten_ssl_list(L) -> [flatten_ssl_list_item(I) || I <- L]. |
290 |
| - |
291 |
| -flatten_ssl_list_item({A, B, C, D}) -> |
292 |
| - A * (1 bsl 24) + B * (1 bsl 16) + C * (1 bsl 8) + D; |
293 |
| -flatten_ssl_list_item(N) when is_number (N) -> |
294 |
| - N. |
0 commit comments