• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

emqx / esockd / 527

16 Sep 2025 07:26AM UTC coverage: 67.052% (+1.0%) from 66.039%
527

push

github

web-flow
Merge pull request #211 from JimMoen/fix-rate-limit-pause

fix: the next check start time should be `Now + Pasue`

2 of 2 new or added lines in 1 file covered. (100.0%)

228 existing lines in 13 files now uncovered.

696 of 1038 relevant lines covered (67.05%)

106.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.62
/src/esockd_ssl.erl
1
%%--------------------------------------------------------------------
2
%% Copyright (c) 2020 EMQ Technologies Co., Ltd. All Rights Reserved.
3
%%
4
%% Licensed under the Apache License, Version 2.0 (the "License");
5
%% you may not use this file except in compliance with the License.
6
%% You may obtain a copy of the License at
7
%%
8
%%     http://www.apache.org/licenses/LICENSE-2.0
9
%%
10
%% Unless required by applicable law or agreed to in writing, software
11
%% distributed under the License is distributed on an "AS IS" BASIS,
12
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
%% See the License for the specific language governing permissions and
14
%% limitations under the License.
15
%%
16
%% The Original Code is RabbitMQ.
17
%%
18
%% The Initial Developer of the Original Code is GoPivotal, Inc.
19
%% Copyright (c) 2007-2016 Pivotal Software, Inc.  All rights reserved.
20
%%--------------------------------------------------------------------
21

22
-module(esockd_ssl).
23

24
-include_lib("public_key/include/public_key.hrl").
25

26
-export([ peer_cert_issuer/1
27
        , peer_cert_subject/1
28
        , peer_cert_common_name/1
29
        , peer_cert_subject_items/2
30
        , peer_cert_validity/1
31
        ]).
32

33
-type(certificate() :: binary()).
34
-export_type([certificate/0]).
35

36
%% Return a string describing the certificate's issuer.
37
-spec(peer_cert_issuer(certificate()) -> binary()).
38
peer_cert_issuer(Cert) ->
39
    cert_info(fun(#'OTPCertificate' {
3✔
40
                     tbsCertificate = #'OTPTBSCertificate' {
41
                       issuer = Issuer }}) ->
42
                      format_rdn_sequence(Issuer)
3✔
43
              end, Cert).
44

45
%% Return a string describing the certificate's subject, as per RFC4514.
46
-spec(peer_cert_subject(certificate()) -> binary()).
47
peer_cert_subject(Cert) ->
48
    cert_info(fun(#'OTPCertificate' {
6✔
49
                     tbsCertificate = #'OTPTBSCertificate' {
50
                       subject = Subject }}) ->
51
                      format_rdn_sequence(Subject)
6✔
52
              end, Cert).
53

54
-spec(peer_cert_common_name(certificate()) -> binary() | undefined).
55
peer_cert_common_name(Cert) ->
56
    case peer_cert_subject_items(Cert, ?'id-at-commonName') of
6✔
57
        undefined -> undefined;
×
58
        CNs       -> iolist_to_binary(string:join(CNs, ","))
6✔
59
     end.
60

61
%% Return the parts of the certificate's subject.
62
-spec(peer_cert_subject_items(certificate(), tuple()) -> [string()] | undefined).
63
peer_cert_subject_items(Cert, Type) ->
64
    cert_info(fun(#'OTPCertificate' {
9✔
65
                     tbsCertificate = #'OTPTBSCertificate' {
66
                       subject = Subject }}) ->
67
                      find_by_type(Type, Subject)
9✔
68
              end, Cert).
69

70
%% Return a string describing the certificate's validity.
71
-spec(peer_cert_validity(certificate()) -> binary()).
72
peer_cert_validity(Cert) ->
73
    cert_info(fun(#'OTPCertificate' {
3✔
74
                     tbsCertificate = #'OTPTBSCertificate' {
75
                       validity = {'Validity', Start, End} }}) ->
76
                      iolist_to_binary(
3✔
77
                        format("~s - ~s", [format_asn1_value(Start),
78
                                           format_asn1_value(End)]))
79
              end, Cert).
80

81
cert_info(F, Cert) ->
82
    F(public_key:pkix_decode_cert(Cert, otp)).
21✔
83

84
find_by_type(Type, {rdnSequence, RDNs}) ->
85
    case [V || #'AttributeTypeAndValue'{type = T, value = V}
9✔
86
                   <- lists:flatten(RDNs),
9✔
87
               T == Type] of
63✔
88
        [] -> undefined;
3✔
89
        L  -> [format_asn1_value(V) || V <- L]
6✔
90
    end.
91

92
%%--------------------------------------------------------------------
93
%% Formatting functions
94
%%--------------------------------------------------------------------
95

96
%% Format and rdnSequence as a RFC4514 subject string.
97
format_rdn_sequence({rdnSequence, Seq}) ->
98
    iolist_to_binary(string:join(lists:reverse([format_complex_rdn(RDN) || RDN <- Seq]), ",")).
9✔
99

100
%% Format an RDN set.
101
format_complex_rdn(RDNs) ->
102
    string:join([format_rdn(RDN) || RDN <- RDNs], "+").
63✔
103

104
%% Format an RDN.  If the type name is unknown, use the dotted decimal
105
%% representation.  See RFC4514, section 2.3.
106
format_rdn(#'AttributeTypeAndValue'{type = T, value = V}) ->
107
    FV = escape_rdn_value(format_asn1_value(V)),
63✔
108
    Fmts = [{?'id-at-surname'                , "SN"},
63✔
109
            {?'id-at-givenName'              , "GIVENNAME"},
110
            {?'id-at-initials'               , "INITIALS"},
111
            {?'id-at-generationQualifier'    , "GENERATIONQUALIFIER"},
112
            {?'id-at-commonName'             , "CN"},
113
            {?'id-at-localityName'           , "L"},
114
            {?'id-at-stateOrProvinceName'    , "ST"},
115
            {?'id-at-organizationName'       , "O"},
116
            {?'id-at-organizationalUnitName' , "OU"},
117
            {?'id-at-title'                  , "TITLE"},
118
            {?'id-at-countryName'            , "C"},
119
            {?'id-at-serialNumber'           , "SERIALNUMBER"},
120
            {?'id-at-pseudonym'              , "PSEUDONYM"},
121
            {?'id-domainComponent'           , "DC"},
122
            {?'id-emailAddress'              , "EMAILADDRESS"},
123
            {?'street-address'               , "STREET"},
124
            {{0,9,2342,19200300,100,1,1}     , "UID"}], %% Not in public_key.hrl
125
    case proplists:lookup(T, Fmts) of
63✔
126
        {_, Fmt} ->
127
            format(Fmt ++ "=~s", [FV]);
63✔
128
        none when is_tuple(T) ->
129
            TypeL = [format("~w", [X]) || X <- tuple_to_list(T)],
×
130
            format("~s=~s", [string:join(TypeL, "."), FV]);
×
131
        none ->
132
            format("~p=~s", [T, FV])
×
133
    end.
134

135
%% Escape a string as per RFC4514.
136
escape_rdn_value(V) ->
137
    escape_rdn_value(V, start).
63✔
138

139
escape_rdn_value([], _) ->
140
    [];
63✔
141
escape_rdn_value([C | S], start) when C =:= $ ; C =:= $# ->
142
    [$\\, C | escape_rdn_value(S, middle)];
×
143
escape_rdn_value(S, start) ->
144
    escape_rdn_value(S, middle);
63✔
145
escape_rdn_value([$ ], middle) ->
146
    [$\\, $ ];
×
147
escape_rdn_value([C | S], middle) when C =:= $"; C =:= $+; C =:= $,; C =:= $;;
148
                                       C =:= $<; C =:= $>; C =:= $\\ ->
149
    [$\\, C | escape_rdn_value(S, middle)];
×
150
escape_rdn_value([C | S], middle) when C < 32 ; C >= 126 ->
151
    %% Of ASCII characters only U+0000 needs escaping, but for display
152
    %% purposes it's handy to escape all non-printable chars. All non-ASCII
153
    %% characters get converted to UTF-8 sequences and then escaped. We've
154
    %% already got a UTF-8 sequence here, so just escape it.
155
    lists:flatten(io_lib:format("\\~2.16.0B", [C]) ++ escape_rdn_value(S, middle));
×
156
escape_rdn_value([C | S], middle) ->
157
    [C | escape_rdn_value(S, middle)].
693✔
158

159
%% Get the string representation of an OTPCertificate field.
160
format_asn1_value({ST, S}) when ST =:= teletexString; ST =:= printableString;
161
                                ST =:= universalString; ST =:= utf8String;
162
                                ST =:= bmpString ->
163
    format_directory_string(ST, S);
51✔
164
format_asn1_value({utcTime, [Y1, Y2, M1, M2, D1, D2, H1, H2,
165
                             Min1, Min2, S1, S2, $Z]}) ->
166
    format("20~c~c-~c~c-~c~cT~c~c:~c~c:~c~cZ",
6✔
167
           [Y1, Y2, M1, M2, D1, D2, H1, H2, Min1, Min2, S1, S2]);
168
%% We appear to get an untagged value back for an ia5string
169
%% (e.g. domainComponent).
170
format_asn1_value(V) when is_list(V) ->
171
    V;
18✔
172
format_asn1_value(V) when is_binary(V) ->
173
    %% OTP does not decode some values when combined with an unknown
174
    %% type. That's probably wrong, so as a last ditch effort let's
175
    %% try manually decoding. 'DirectoryString' is semi-arbitrary -
176
    %% but it is the type which covers the various string types we
177
    %% handle below.
178
    try
×
179
        {ST, S} = public_key:der_decode('DirectoryString', V),
×
180
        format_directory_string(ST, S)
×
181
    catch _:_ ->
182
            format("~p", [V])
×
183
    end;
184
format_asn1_value(V) ->
185
    format("~p", [V]).
×
186

187
%% DirectoryString { INTEGER : maxSize } ::= CHOICE {
188
%%     teletexString     TeletexString (SIZE (1..maxSize)),
189
%%     printableString   PrintableString (SIZE (1..maxSize)),
190
%%     bmpString         BMPString (SIZE (1..maxSize)),
191
%%     universalString   UniversalString (SIZE (1..maxSize)),
192
%%     uTF8String        UTF8String (SIZE (1..maxSize)) }
193
%%
194
%% Precise definitions of printable / teletexString are hard to come
195
%% by. This is what I reconstructed:
196
%%
197
%% printableString:
198
%% "intended to represent the limited character sets available to
199
%% mainframe input terminals"
200
%% A-Z a-z 0-9 ' ( ) + , - . / : = ? [space]
201
%% http://msdn.microsoft.com/en-us/library/bb540814(v=vs.85).aspx
202
%%
203
%% teletexString:
204
%% "a sizable volume of software in the world treats TeletexString
205
%% (T61String) as a simple 8-bit string with mostly Windows Latin 1
206
%% (superset of iso-8859-1) encoding"
207
%% http://www.mail-archive.com/asn1@asn1.org/msg00460.html
208
%%
209
%% (However according to that link X.680 actually defines
210
%% TeletexString in some much more involved and crazy way. I suggest
211
%% we treat it as ISO-8859-1 since Erlang does not support Windows
212
%% Latin 1).
213
%%
214
%% bmpString:
215
%% UCS-2 according to RFC 3641. Hence cannot represent Unicode
216
%% characters above 65535 (outside the "Basic Multilingual Plane").
217
%%
218
%% universalString:
219
%% UCS-4 according to RFC 3641.
220
%%
221
%% utf8String:
222
%% UTF-8 according to RFC 3641.
223
%%
224
%% Within Rabbit we assume UTF-8 encoding. Since printableString is a
225
%% subset of ASCII it is also a subset of UTF-8. The others need
226
%% converting. Fortunately since the Erlang SSL library does the
227
%% decoding for us (albeit into a weird format, see below), we just
228
%% need to handle encoding into UTF-8. Note also that utf8Strings come
229
%% back as binary.
230
%%
231
%% Note for testing: the default Ubuntu configuration for openssl will
232
%% only create printableString or teletexString types no matter what
233
%% you do. Edit string_mask in the [req] section of
234
%% /etc/ssl/openssl.cnf to change this (see comments there). You
235
%% probably also need to set utf8 = yes to get it to accept UTF-8 on
236
%% the command line. Also note I could not get openssl to generate a
237
%% universalString.
238

239
format_directory_string(printableString, S) -> S;
51✔
240
format_directory_string(teletexString,   S) -> utf8_list_from(S);
×
241
format_directory_string(bmpString,       S) -> utf8_list_from(S);
×
242
format_directory_string(universalString, S) -> utf8_list_from(S);
×
UNCOV
243
format_directory_string(utf8String,      S) -> binary_to_list(S).
×
244

245
utf8_list_from(S) ->
246
    binary_to_list(
×
247
          unicode:characters_to_binary(flatten_ssl_list(S), utf32, utf8)).
248

249
%% The Erlang SSL implementation invents its own representation for
250
%% non-ascii strings - looking like [97,{0,0,3,187}] (that's LATIN
251
%% SMALL LETTER A followed by GREEK SMALL LETTER LAMDA). We convert
252
%% this into a list of unicode characters, which we can tell
253
%% unicode:characters_to_binary is utf32.
254

255
flatten_ssl_list(L) -> [flatten_ssl_list_item(I) || I <- L].
×
256

257
flatten_ssl_list_item({A, B, C, D}) ->
258
    A * (1 bsl 24) + B * (1 bsl 16) + C * (1 bsl 8) + D;
×
259
flatten_ssl_list_item(N) when is_number (N) ->
260
    N.
×
261

262
format(Fmt, Args) ->
263
    lists:flatten(io_lib:format(Fmt, Args)).
72✔
264

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc