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

travelping / capwap / 189

pending completion
189

Pull #25

travis-ci-com

web-flow
Add HTTP API v2 with getting stations and delete stations
Pull Request #25: Add HTTP API with managing stations

131 of 131 new or added lines in 7 files covered. (100.0%)

1271 of 3311 relevant lines covered (38.39%)

904.34 hits per line

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

20.86
/src/eapol.erl
1
%% Copyright (C) 2017, Travelping GmbH <info@travelping.com>
2

3
%% This program is free software: you can redistribute it and/or modify
4
%% it under the terms of the GNU Affero General Public License as published by
5
%% the Free Software Foundation, either version 3 of the License, or
6
%% (at your option) any later version.
7

8
%% This program is distributed in the hope that it will be useful,
9
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
10
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
%% GNU Affero General Public License for more details.
12

13
%% You should have received a copy of the GNU Affero General Public License
14
%% along with this program.  If not, see <http://www.gnu.org/licenses/>.
15

16
-module(eapol).
17

18
-export([encode_802_11/3, packet/1, key/3, request/3, decode/1, validate_mic/2]).
19
-export([phrase2psk/2, prf/5, kdf/5, pmk2ptk/6, ft_msk2ptk/11, aes_key_wrap/2, key_len/1]).
20

21
-include("eapol.hrl").
22

23
-define(is_mac(MAC),(is_binary(MAC) andalso byte_size(MAC) == 6)).
24

25
-define('802_1X_VERSION', 2).
26

27
-define(EAPOL_PACKET_TYPE_PACKET, 0).
28
-define(EAPOL_PACKET_TYPE_START, 1).
29
-define(EAPOL_PACKET_TYPE_LOGOFF, 2).
30
-define(EAPOL_PACKET_TYPE_KEY, 3).
31
-define(EAPOL_PACKET_TYPE_ENCAPS_ASF_ALERT, 4).
32

33
-define(EAPOL_KEY_RC4, 1).
34
-define(EAPOL_KEY_802_11, 2).
35

36
-define(EAPOL_CODE_REQUEST, 1).
37
-define(EAPOL_CODE_RESPONSE, 2).
38

39
eap_type(identity) -> 1;
×
40
eap_type(1) -> identity;
×
41
eap_type(X) when is_integer(X) -> X.
×
42

43
key_len(#ccmp{})    -> 16;
×
44
key_len('CCMP')     -> 16;
×
45
key_len('AES-CMAC') -> 16.
×
46

47
mic_len('HMAC-SHA1-128') -> 16;
×
48
mic_len('AES-128-CMAC')  -> 16;
×
49
mic_len('HMAC-SHA256')   -> 16;
×
50
mic_len('HMAC-SHA384')   -> 24.
×
51

52
keyinfo(group, Info) ->
53
    Info;
×
54
keyinfo(pairwise, Info) ->
55
    Info bor 16#0008;
×
56
keyinfo(install, Info) ->
57
    Info bor 16#0040;
×
58
keyinfo(ack, Info) ->
59
    Info bor 16#0080;
×
60
keyinfo(mic, Info) ->
61
    Info bor 16#0100;
×
62
keyinfo(secure, Info) ->
63
    Info bor 16#0200;
×
64
keyinfo(enc, Info) ->
65
    Info bor 16#1000;
×
66
keyinfo(_, Info) ->
67
    Info.
×
68

69
dec_keyinfo(16#0008, 0, Flags) ->
70
    [group | Flags];
×
71
dec_keyinfo(16#0008, 1, Flags) ->
72
    [pairwise | Flags];
×
73
dec_keyinfo(16#0040, 1, Flags) ->
74
    [install | Flags];
×
75
dec_keyinfo(16#0080, 1, Flags) ->
76
    [ack | Flags];
×
77
dec_keyinfo(16#0100, 1, Flags) ->
78
    [mic | Flags];
×
79
dec_keyinfo(16#0200, 1, Flags) ->
80
    [secure | Flags];
×
81
dec_keyinfo(16#1000, 1, Flags) ->
82
    [enc | Flags];
×
83
dec_keyinfo(Cnt, 1, Flags) ->
84
    [Cnt | Flags];
×
85
dec_keyinfo(_Cnt, 0, Flags) ->
86
    Flags.
×
87

88
keyinfo(0, _, Flags) ->
89
    Flags;
×
90
keyinfo(V, Cnt, Flags0) ->
91
    Flags = dec_keyinfo(Cnt, V band 1, Flags0),
×
92
    keyinfo(V bsr 1, Cnt bsl 1, Flags).
×
93

94
keyinfo(Info) ->
95
    keyinfo(Info bsr 3, 16#08, []).
×
96

97
mic_algo('HMAC-SHA1-128') -> 2;
×
98
mic_algo('AES-128-CMAC') -> 3;
×
99
mic_algo(X) when is_atom(X) -> 0;
×
100

101
mic_algo(2) -> 'HMAC-SHA1-128';
×
102
mic_algo(3) -> 'AES-128-CMAC';
×
103
mic_algo(X) when is_integer(X) -> unknown.
×
104

105
calc_hmac(#ccmp{mic_algo = 'HMAC-SHA1-128', kck = KCK}, EAPOL, Data, MICLen) ->
106
    C1 = crypto:hmac_init(sha, KCK),
×
107
    C2 = crypto:hmac_update(C1, EAPOL),
×
108
    C3 = crypto:hmac_update(C2, binary:copy(<<0>>, MICLen)),
×
109
    C4 = crypto:hmac_update(C3, Data),
×
110
    crypto:hmac_final_n(C4, MICLen);
×
111

112
calc_hmac(#ccmp{mic_algo = 'AES-128-CMAC', kck = KCK}, EAPOL, Data, MICLen) ->
113
    aes_cmac:aes_cmac(KCK, <<EAPOL/binary, 0:(MICLen * 8), Data/binary>>).
×
114

115
packet(Data) ->
116
    DataLen = size(Data),
×
117
    <<?'802_1X_VERSION', ?EAPOL_PACKET_TYPE_PACKET, DataLen:16, Data/binary>>.
×
118

119
request(Id, Type, Data) ->
120
    EAPOLData = <<(eap_type(Type)):8, Data/binary>>,
×
121
    DataLen = size(EAPOLData) + 4,
×
122
    <<?EAPOL_CODE_REQUEST, Id:8, DataLen:16, EAPOLData/binary>>.
×
123

124
%%
125
%% EAPOL Key frames are defined in IEEE 802.11-2012, Sect. 11.6.2
126
%%
127
key(Flags, KeyData, #ccmp{mic_algo = MICAlgo,
128
                          replay_counter = ReplayCounter,
129
                          nonce = Nonce} = CCMP) ->
130
    lager:info("CCMP KEY: ~p", [lager:pr(CCMP, ?MODULE)]),
×
131
    KeyInfo = lists:foldl(fun keyinfo/2, 0, Flags)
×
132
        bor mic_algo(MICAlgo),
133
    KeyLen = key_len(CCMP),
×
134
    MICLen = mic_len(MICAlgo),
×
135
    EAPOLData = <<?EAPOL_KEY_802_11, KeyInfo:16, KeyLen:16, ReplayCounter:64,
×
136
                  Nonce:32/bytes,                %% Key Nounce
137
                  0:128,                        %% EAPOL Key IV
138
                  0:64,                                %% Key RSC, see RFC 5416, Sect. 9.1 !!!!
139
                  0:64>>,                        %% reserved
140
    KeyDataLen = byte_size(KeyData),
×
141
    KeyData1 = <<KeyDataLen:16, KeyData/binary>>,
×
142
    DataLen = byte_size(EAPOLData) + MICLen + 2 + KeyDataLen,
×
143
    EAPOL = <<?'802_1X_VERSION', ?EAPOL_PACKET_TYPE_KEY, DataLen:16, EAPOLData/binary>>,
×
144

145
    MIC = case proplists:get_bool(mic, Flags) of
×
146
              true ->
147
                  calc_hmac(CCMP, EAPOL, KeyData1, MICLen);
×
148
              _ ->
149
                  binary:copy(<<0>>, MICLen)
×
150
          end,
151
    iolist_to_binary([EAPOL, MIC, KeyData1]).
×
152

153
validate_mic(Crypto, {Head, MIC, Tail}) ->
154
    case calc_hmac(Crypto, Head, Tail, byte_size(MIC)) of
×
155
        MIC -> ok;
×
156
        V   ->
157
            lager:debug("Algo: ~p", [Crypto#ccmp.mic_algo]),
×
158
            lager:debug("Head: ~s", [pbkdf2:to_hex(Head)]),
×
159
            lager:debug("MIC: ~s", [pbkdf2:to_hex(MIC)]),
×
160
            lager:debug("Tail: ~s", [pbkdf2:to_hex(Tail)]),
×
161
            lager:debug("invalid MIC: expected: ~s, got: ~s", [pbkdf2:to_hex(MIC), pbkdf2:to_hex(V)]),
×
162
            {error, invalid}
×
163
    end.
164

165
decode(<<Version:8, ?EAPOL_PACKET_TYPE_START, DataLen:16, Data:DataLen/binary>>)
166
  when Version == 1; Version == 2 ->
167
    {start, Data};
×
168

169
decode(Packet = <<Version:8, ?EAPOL_PACKET_TYPE_PACKET, DataLen:16, EAPOLData:DataLen/binary>>)
170
  when Version == 1; Version == 2 ->
171
    try EAPOLData of
×
172
        <<?EAPOL_CODE_RESPONSE, Id:8, DataLen:16, 1:8, Identity/bytes>>
173
          when size(Identity) == DataLen - 5 ->
174
            {response, Id, EAPOLData, {identity, Identity}};
×
175
        <<?EAPOL_CODE_RESPONSE, Id:8, DataLen:16, Data/bytes>>
176
          when size(Data) == DataLen - 4 ->
177
            {response, Id, EAPOLData, Data};
×
178
        _ ->
179
            {unknown, Packet}
×
180
    catch
181
        _:_ -> {invalid, Packet}
×
182
    end;
183

184
decode(Data = <<Version:8, ?EAPOL_PACKET_TYPE_KEY, DataLen:16, EAPOLData:DataLen/binary>>)
185
  when Version == 1; Version == 2 ->
186
    try
×
187
        <<?EAPOL_KEY_802_11, KeyInfo:16, _/binary>> = EAPOLData,
×
188
        MICAlgo = mic_algo(KeyInfo band 16#07),
×
189
        Flags = keyinfo(KeyInfo),
×
190
        lager:debug("KeyInfo: ~p", [Flags]),
×
191
        MICLen = mic_len(MICAlgo),
×
192

193
        << ?EAPOL_KEY_802_11, _:16, _KeyLen:16, ReplayCounter:64,
194
           Nonce:32/bytes, 0:128, 0:64, 0:64, _:MICLen/bytes,
195
           KeyDataLen:16, KeyData:KeyDataLen/bytes>> = EAPOLData,
×
196

197
        << Head:81/bytes, MIC:MICLen/bytes, Tail/bytes>> = Data,
×
198

199
        {key, Flags, MICAlgo, ReplayCounter, Nonce, KeyData, {Head, MIC, Tail}}
×
200
    catch
201
        _:_ -> {invalid, Data}
×
202
    end;
203
decode(Data) ->
204
    {unknown, Data}.
×
205

206

207
encode_802_11(DA, BSS, KeyData)
208
  when ?is_mac(DA), ?is_mac(BSS) ->
209

210
    LLCHdr = <<?LLC_DSAP_SNAP, ?LLC_SSAP_SNAP, ?LLC_CNTL_SNAP, ?SNAP_ORG_ETHERNET, ?ETH_P_PAE:16>>,
×
211
    Frame = <<LLCHdr/binary, KeyData/binary>>,
×
212

213
    {Type, SubType} = ieee80211_station:frame_type('QoS Data'),
×
214
    FrameControl = <<SubType:4, Type:2, 0:2, 0:6, 1:1, 0:1>>,
×
215
    Duration = 0,
×
216
    SequenceControl = 0,
×
217
    QoS = <<7, 0>>,
×
218
    <<FrameControl/binary,
×
219
      Duration:16/integer-little,
220
      DA:6/bytes, BSS:6/bytes, BSS:6/bytes,
221
      SequenceControl:16,
222
      QoS/binary,
223
      Frame/binary>>.
224

225
%% IEEE 802.11-2012, Sect. 11.6.1.2, PRF
226
prf(Type, Key, Label, Data, WantedLength) ->
227
    prf(Type, Key, Label, Data, WantedLength, 0, []).
10✔
228

229
prf(_Type, _Key, _Label, _Data, WantedLength, _N, [Last | Acc])
230
  when WantedLength =< 0 ->
231
    Keep = bit_size(Last) + WantedLength,
10✔
232
    lager:debug("Size: ~p, Wanted: ~p, Keep: ~p", [bit_size(Last), WantedLength, Keep]),
10✔
233
    <<B:Keep/bits, _/bits>> = Last,
10✔
234
    list_to_binary(lists:reverse(Acc, [B]));
10✔
235

236
prf(Type, Key, Label, Data, WantedLength, N, Acc) ->
237
    Bin = crypto:hmac(Type, Key, [Label, 0, Data, N]),
36✔
238
    prf(Type, Key, Label, Data, WantedLength - bit_size(Bin), N + 1, [Bin|Acc]).
36✔
239

240
%% IEEE 802.11-2012, Sect. 11.6.1.7.2, KDF
241
kdf(Type, Key, Label, Data, WantedLength) ->
242
    kdf(Type, Key, Label, [Data, <<WantedLength:16/little>>], WantedLength, 1, []).
2✔
243

244
kdf(_Type, _Key, _Label, _Data, WantedLength, _N, [Last | Acc])
245
  when WantedLength =< 0 ->
246
    Keep = bit_size(Last) + WantedLength,
2✔
247
    lager:debug("Size: ~p, Wanted: ~p, Keep: ~p", [bit_size(Last), WantedLength, Keep]),
2✔
248
    <<B:Keep/bits, _/bits>> = Last,
2✔
249
    list_to_binary(lists:reverse(Acc, [B]));
2✔
250

251
kdf(Type, Key, Label, Data, WantedLength, N, Acc) ->
252
    Bin = crypto:hmac(Type, Key, [<<N:16/little>>, Label, Data]),
4✔
253
    kdf(Type, Key, Label, Data, WantedLength - bit_size(Bin), N + 1, [Bin|Acc]).
4✔
254

255

256
phrase2psk(Phrase, SSID)
257
  when is_binary(Phrase), is_binary(SSID) ->
258
    pbkdf2:pbkdf2(sha, Phrase, SSID, 4096, 32);
6✔
259
phrase2psk(Phrase, SSID)
260
  when is_list(Phrase) ->
261
    phrase2psk(iolist_to_binary(Phrase), SSID);
6✔
262
phrase2psk(Phrase, SSID)
263
  when is_list(SSID) ->
264
    phrase2psk(Phrase, iolist_to_binary(SSID)).
6✔
265

266
pmk2ptk(PMK, AA, SPA, ANonce, SNonce, PRFLen) ->
267
    <<KCK:16/bytes, KEK:16/bytes, TK/binary>> =
2✔
268
        prf(sha, PMK, "Pairwise key expansion", [min(AA, SPA), max(AA, SPA),
269
                                                  min(ANonce, SNonce), max(SNonce, ANonce)], PRFLen),
270
    {KCK, KEK, TK}.
2✔
271

272
ft_msk2ptk(MSK, SNonce, ANonce, BSS, StationMAC, SSID, MDomain, R0KH, R1KH, S0KH, S1KH) ->
273
    <<_:256/bits, XXKey:256/bits>> = MSK,
×
274

275
    %% R0-Key-Data = KDF-384(XXKey, "FT-R0", SSIDlength || SSID ||
276
    %%                        MDID || R0KHlength || R0KH-ID || S0KH-ID)
277
    %% PMK-R0 = L(R0-Key-Data, 0, 256)
278
    %% PMK-R0Name-Salt = L(R0-Key-Data, 256, 128)
279
    %% PPMKR0Name = Truncate-128(SHA-256("FT-R0N" || PMK-R0Name-Salt))
280

281
    lager:debug("FT XXKey: ~p", [pbkdf2:to_hex(XXKey)]),
×
282
    lager:debug("FT: R0KH-Id: ~p", [pbkdf2:to_hex(R0KH)]),
×
283

284
    <<PMKR0:256/bits, PMKR0NameSalt:128/bits>> =
×
285
        kdf(sha256, XXKey, "FT-R0", [byte_size(SSID), SSID, <<MDomain:16>>,
286
                                           byte_size(R0KH), R0KH, S0KH], 384),
287
    <<PMKR0Name:128/bits, _/binary>> = crypto:hash(sha256, ["FT-R0N", PMKR0NameSalt]),
×
288

289
    lager:debug("FT PMK-R0: ~p", [pbkdf2:to_hex(PMKR0)]),
×
290
    lager:debug("FT PMK-R0Name: ~p", [pbkdf2:to_hex(PMKR0Name)]),
×
291

292
    %% PMK-R1 = KDF-256(PMK-R0, "FT-R1", R1KH-ID || S1KH-ID)
293
    %% PMKR1Name = Truncate-128(SHA-256(“FT-R1N” || PMKR0Name || R1KH-ID || S1KH-ID))
294

295
    PMKR1 =
×
296
        kdf(sha256, PMKR0, "FT-R1", [R1KH, S1KH], 256),
297
    <<PMKR1Name:128/bits, _/binary>> = crypto:hash(sha256, ["FT-R1N", PMKR0Name, BSS, StationMAC]),
×
298

299
    lager:debug("FT PMK-R1: ~p", [pbkdf2:to_hex(PMKR1)]),
×
300
    lager:debug("FT PMK-R1Name: ~p", [pbkdf2:to_hex(PMKR1Name)]),
×
301

302
    %%PTK = KDF-PTKLen(PMK-R1, "FT-PTK", SNonce || ANonce || BSSID || STA-ADDR)
303
    <<KCK:128/bits, KEK:128/bits, TK:128/bits>> =
×
304
        kdf(sha256, PMKR1, "FT-PTK", [SNonce, ANonce, BSS, StationMAC], 384),
305
    lager:debug("KCK: ~p", [pbkdf2:to_hex(KCK)]),
×
306
    lager:debug("KEK: ~p", [pbkdf2:to_hex(KEK)]),
×
307
    lager:debug("TK: ~p", [pbkdf2:to_hex(TK)]),
×
308
    {KCK, KEK, TK, PMKR0Name, PMKR1Name}.
×
309

310
   %% Inputs:  Plaintext, n 64-bit values {P1, P2, ..., Pn}, and
311
   %%          Key, K (the KEK).
312
   %% Outputs: Ciphertext, (n+1) 64-bit values {C0, C1, ..., Cn}.
313

314
   %% 1) Initialize variables.
315

316
   %%     Set A = IV, an initial value (see 2.2.3)
317
   %%     For i = 1 to n
318
   %%         R[i] = P[i]
319

320
   %% 2) Calculate intermediate values.
321

322
   %%     For j = 0 to 5
323
   %%         For i=1 to n
324
   %%             B = AES(K, A | R[i])
325
   %%             A = MSB(64, B) ^ t where t = (n*j)+i
326
   %%             R[i] = LSB(64, B)
327

328
   %% 3) Output the results.
329

330
   %%     Set C[0] = A
331
   %%     For i = 1 to n
332
   %%         C[i] = R[i]
333

334
aes_key_wrap(KEK, PlainText) ->
335
    IV = binary:copy(<<16#A6>>, 8),
8✔
336
    Text = [X || <<X:8/bytes>> <= PlainText],
8✔
337
    aes_key_wrap(KEK, IV, Text, 1, 0).
8✔
338

339
aes_key_wrap(_KEK, IV, Text, _Cnt, 6) ->
340
    iolist_to_binary([IV | Text]);
8✔
341
aes_key_wrap(KEK, IV0, Text0, Cnt0, Round) ->
342
    {IV1, Text1, Cnt1} = aes_key_wrap0(KEK, IV0, Text0, [], Cnt0),
48✔
343
    aes_key_wrap(KEK, IV1, Text1, Cnt1, Round + 1).
48✔
344

345
aes_key_wrap0(_KEK, IV, [], Wrapped, Cnt) ->
346
    {IV, lists:reverse(Wrapped), Cnt};
48✔
347
aes_key_wrap0(KEK, IV0, [Text | Next], Wrapped, Cnt) ->
348
    <<MSB:8/bytes, LSB:8/bytes, _/binary>> = crypto:block_encrypt(aes_ecb, KEK, [IV0, Text]),
132✔
349
    IV1 = crypto:exor(MSB, <<Cnt:64>>),
132✔
350
    aes_key_wrap0(KEK, IV1, Next, [LSB | Wrapped], Cnt + 1).
132✔
351

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

© 2024 Coveralls, Inc