Skip to content

Commit 3c32d7e

Browse files
Merge pull request #1097 from rabbitmq/move-rabbit_ssl-functions-to-rabbitmq-common
rabbit_ssl: Move most functions to `rabbit_cert_info` in rabbitmq-common
2 parents 4d90fd7 + 8a5387f commit 3c32d7e

File tree

1 file changed

+9
-219
lines changed

1 file changed

+9
-219
lines changed

src/rabbit_ssl.erl

Lines changed: 9 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@
1111
%% The Original Code is RabbitMQ.
1212
%%
1313
%% 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.
1515
%%
1616

1717
-module(rabbit_ssl).
1818

19-
-include("rabbit.hrl").
20-
2119
-include_lib("public_key/include/public_key.hrl").
2220

2321
-export([peer_cert_issuer/1, peer_cert_subject/1, peer_cert_validity/1]).
@@ -27,54 +25,32 @@
2725

2826
-export_type([certificate/0]).
2927

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().
3929

4030
%%--------------------------------------------------------------------------
4131
%% High-level functions used by reader
4232
%%--------------------------------------------------------------------------
4333

4434
%% Return a string describing the certificate's issuer.
4535
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).
5137

5238
%% Return a string describing the certificate's subject, as per RFC4514.
5339
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).
5941

6042
%% Return the parts of the certificate's subject.
6143
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).
6745

6846
%% Return a string describing the certificate's validity.
6947
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).
7649

7750
%% Extract a username from the certificate
51+
-spec peer_cert_auth_name
52+
(certificate()) -> binary() | 'not_found' | 'unsafe'.
53+
7854
peer_cert_auth_name(Cert) ->
7955
{ok, Mode} = application:get_env(rabbit, ssl_cert_login_from),
8056
peer_cert_auth_name(Mode, Cert).
@@ -106,189 +82,3 @@ auth_config_sane() ->
10682
"disabled, verify=~p~n", [V]),
10783
false
10884
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

Comments
 (0)