summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp77
-rw-r--r--src/core/hle/service/nwm/uds_data.cpp278
-rw-r--r--src/core/hle/service/nwm/uds_data.h78
-rw-r--r--src/core/hw/aes/key.h2
5 files changed, 436 insertions, 1 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index b16a89990..ea09819e5 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -144,6 +144,7 @@ set(SRCS
144 hle/service/nwm/nwm_tst.cpp 144 hle/service/nwm/nwm_tst.cpp
145 hle/service/nwm/nwm_uds.cpp 145 hle/service/nwm/nwm_uds.cpp
146 hle/service/nwm/uds_beacon.cpp 146 hle/service/nwm/uds_beacon.cpp
147 hle/service/nwm/uds_data.cpp
147 hle/service/pm_app.cpp 148 hle/service/pm_app.cpp
148 hle/service/ptm/ptm.cpp 149 hle/service/ptm/ptm.cpp
149 hle/service/ptm/ptm_gets.cpp 150 hle/service/ptm/ptm_gets.cpp
@@ -341,6 +342,7 @@ set(HEADERS
341 hle/service/nwm/nwm_tst.h 342 hle/service/nwm/nwm_tst.h
342 hle/service/nwm/nwm_uds.h 343 hle/service/nwm/nwm_uds.h
343 hle/service/nwm/uds_beacon.h 344 hle/service/nwm/uds_beacon.h
345 hle/service/nwm/uds_data.h
344 hle/service/pm_app.h 346 hle/service/pm_app.h
345 hle/service/ptm/ptm.h 347 hle/service/ptm/ptm.h
346 hle/service/ptm/ptm_gets.h 348 hle/service/ptm/ptm_gets.h
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index a7149c9e8..6dbdff044 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -15,6 +15,7 @@
15#include "core/hle/result.h" 15#include "core/hle/result.h"
16#include "core/hle/service/nwm/nwm_uds.h" 16#include "core/hle/service/nwm/nwm_uds.h"
17#include "core/hle/service/nwm/uds_beacon.h" 17#include "core/hle/service/nwm/uds_beacon.h"
18#include "core/hle/service/nwm/uds_data.h"
18#include "core/memory.h" 19#include "core/memory.h"
19 20
20namespace Service { 21namespace Service {
@@ -373,6 +374,80 @@ static void DestroyNetwork(Interface* self) {
373} 374}
374 375
375/** 376/**
377 * NWM_UDS::SendTo service function.
378 * Sends a data frame to the UDS network we're connected to.
379 * Inputs:
380 * 0 : Command header.
381 * 1 : Unknown.
382 * 2 : u16 Destination network node id.
383 * 3 : u8 Data channel.
384 * 4 : Buffer size >> 2
385 * 5 : Data size
386 * 6 : Flags
387 * 7 : Input buffer descriptor
388 * 8 : Input buffer address
389 * Outputs:
390 * 0 : Return header
391 * 1 : Result of function, 0 on success, otherwise error code
392 */
393static void SendTo(Interface* self) {
394 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x17, 6, 2);
395
396 rp.Skip(1, false);
397 u16 dest_node_id = rp.Pop<u16>();
398 u8 data_channel = rp.Pop<u8>();
399 rp.Skip(1, false);
400 u32 data_size = rp.Pop<u32>();
401 u32 flags = rp.Pop<u32>();
402
403 size_t desc_size;
404 const VAddr input_address = rp.PopStaticBuffer(&desc_size, false);
405 ASSERT(desc_size == data_size);
406
407 IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
408
409 if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsClient) &&
410 connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
411 rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS,
412 ErrorSummary::InvalidState, ErrorLevel::Status));
413 return;
414 }
415
416 if (dest_node_id == connection_status.network_node_id) {
417 rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS,
418 ErrorSummary::WrongArgument, ErrorLevel::Status));
419 return;
420 }
421
422 // TODO(Subv): Do something with the flags.
423
424 constexpr size_t MaxSize = 0x5C6;
425 if (data_size > MaxSize) {
426 rb.Push(ResultCode(ErrorDescription::TooLarge, ErrorModule::UDS,
427 ErrorSummary::WrongArgument, ErrorLevel::Usage));
428 return;
429 }
430
431 std::vector<u8> data(data_size);
432 Memory::ReadBlock(input_address, data.data(), data.size());
433
434 // TODO(Subv): Increment the sequence number after each sent packet.
435 u16 sequence_number = 0;
436 std::vector<u8> data_payload = GenerateDataPayload(
437 data, data_channel, dest_node_id, connection_status.network_node_id, sequence_number);
438
439 // TODO(Subv): Retrieve the MAC address of the dest_node_id and our own to encrypt
440 // and encapsulate the payload.
441
442 // TODO(Subv): Send the frame.
443
444 rb.Push(RESULT_SUCCESS);
445
446 LOG_WARNING(Service_NWM, "(STUB) called dest_node_id=%u size=%u flags=%u channel=%u",
447 static_cast<u32>(dest_node_id), data_size, flags, static_cast<u32>(data_channel));
448}
449
450/**
376 * NWM_UDS::GetChannel service function. 451 * NWM_UDS::GetChannel service function.
377 * Returns the WiFi channel in which the network we're connected to is transmitting. 452 * Returns the WiFi channel in which the network we're connected to is transmitting.
378 * Inputs: 453 * Inputs:
@@ -600,7 +675,7 @@ const Interface::FunctionInfo FunctionTable[] = {
600 {0x00130040, nullptr, "Unbind"}, 675 {0x00130040, nullptr, "Unbind"},
601 {0x001400C0, nullptr, "PullPacket"}, 676 {0x001400C0, nullptr, "PullPacket"},
602 {0x00150080, nullptr, "SetMaxSendDelay"}, 677 {0x00150080, nullptr, "SetMaxSendDelay"},
603 {0x00170182, nullptr, "SendTo"}, 678 {0x00170182, SendTo, "SendTo"},
604 {0x001A0000, GetChannel, "GetChannel"}, 679 {0x001A0000, GetChannel, "GetChannel"},
605 {0x001B0302, InitializeWithVersion, "InitializeWithVersion"}, 680 {0x001B0302, InitializeWithVersion, "InitializeWithVersion"},
606 {0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"}, 681 {0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"},
diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp
new file mode 100644
index 000000000..8c6742dba
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_data.cpp
@@ -0,0 +1,278 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <cryptopp/aes.h>
7#include <cryptopp/ccm.h>
8#include <cryptopp/filters.h>
9#include <cryptopp/md5.h>
10#include <cryptopp/modes.h>
11#include "core/hle/service/nwm/nwm_uds.h"
12#include "core/hle/service/nwm/uds_data.h"
13#include "core/hw/aes/key.h"
14
15namespace Service {
16namespace NWM {
17
18using MacAddress = std::array<u8, 6>;
19
20/*
21 * Generates a SNAP-enabled 802.2 LLC header for the specified protocol.
22 * @returns a buffer with the bytes of the generated header.
23 */
24static std::vector<u8> GenerateLLCHeader(EtherType protocol) {
25 LLCHeader header{};
26 header.protocol = static_cast<u16>(protocol);
27
28 std::vector<u8> buffer(sizeof(header));
29 memcpy(buffer.data(), &header, sizeof(header));
30
31 return buffer;
32}
33
34/*
35 * Generates a Nintendo UDS SecureData header with the specified parameters.
36 * @returns a buffer with the bytes of the generated header.
37 */
38static std::vector<u8> GenerateSecureDataHeader(u16 data_size, u8 channel, u16 dest_node_id,
39 u16 src_node_id, u16 sequence_number) {
40 SecureDataHeader header{};
41 header.protocol_size = data_size + sizeof(SecureDataHeader);
42 // Note: This size includes everything except the first 4 bytes of the structure,
43 // reinforcing the hypotheses that the first 4 bytes are actually the header of
44 // another container protocol.
45 header.securedata_size = data_size + sizeof(SecureDataHeader) - 4;
46 // Frames sent by the emulated application are never UDS management frames
47 header.is_management = 0;
48 header.data_channel = channel;
49 header.sequence_number = sequence_number;
50 header.dest_node_id = dest_node_id;
51 header.src_node_id = src_node_id;
52
53 std::vector<u8> buffer(sizeof(header));
54 memcpy(buffer.data(), &header, sizeof(header));
55
56 return buffer;
57}
58
59/*
60 * Calculates the CTR used for the AES-CTR process that calculates
61 * the CCMP crypto key for data frames.
62 * @returns The CTR used for data frames crypto key generation.
63 */
64static std::array<u8, CryptoPP::MD5::DIGESTSIZE> GetDataCryptoCTR(const NetworkInfo& network_info) {
65 DataFrameCryptoCTR data{};
66
67 data.host_mac = network_info.host_mac_address;
68 data.wlan_comm_id = network_info.wlan_comm_id;
69 data.id = network_info.id;
70 data.network_id = network_info.network_id;
71
72 std::array<u8, CryptoPP::MD5::DIGESTSIZE> hash;
73 CryptoPP::MD5().CalculateDigest(hash.data(), reinterpret_cast<u8*>(&data), sizeof(data));
74
75 return hash;
76}
77
78/*
79 * Generates the key used for encrypting the 802.11 data frames generated by UDS.
80 * @returns The key used for data frames crypto.
81 */
82static std::array<u8, CryptoPP::AES::BLOCKSIZE> GenerateDataCCMPKey(
83 const std::vector<u8>& passphrase, const NetworkInfo& network_info) {
84 // Calculate the MD5 hash of the input passphrase.
85 std::array<u8, CryptoPP::MD5::DIGESTSIZE> passphrase_hash;
86 CryptoPP::MD5().CalculateDigest(passphrase_hash.data(), passphrase.data(), passphrase.size());
87
88 std::array<u8, CryptoPP::AES::BLOCKSIZE> ccmp_key;
89
90 // The CCMP key is the result of encrypting the MD5 hash of the passphrase with AES-CTR using
91 // keyslot 0x2D.
92 using CryptoPP::AES;
93 std::array<u8, CryptoPP::MD5::DIGESTSIZE> counter = GetDataCryptoCTR(network_info);
94 std::array<u8, AES::BLOCKSIZE> key = HW::AES::GetNormalKey(HW::AES::KeySlotID::UDSDataKey);
95 CryptoPP::CTR_Mode<AES>::Encryption aes;
96 aes.SetKeyWithIV(key.data(), AES::BLOCKSIZE, counter.data());
97 aes.ProcessData(ccmp_key.data(), passphrase_hash.data(), passphrase_hash.size());
98
99 return ccmp_key;
100}
101
102/*
103 * Generates the Additional Authenticated Data (AAD) for an UDS 802.11 encrypted data frame.
104 * @returns a buffer with the bytes of the AAD.
105 */
106static std::vector<u8> GenerateCCMPAAD(const MacAddress& sender, const MacAddress& receiver,
107 const MacAddress& bssid, u16 frame_control) {
108 // Reference: IEEE 802.11-2007
109
110 // 8.3.3.3.2 Construct AAD (22-30 bytes)
111 // The AAD is constructed from the MPDU header. The AAD does not include the header Duration
112 // field, because the Duration field value can change due to normal IEEE 802.11 operation (e.g.,
113 // a rate change during retransmission). For similar reasons, several subfields in the Frame
114 // Control field are masked to 0.
115 struct {
116 u16_be FC; // MPDU Frame Control field
117 MacAddress A1;
118 MacAddress A2;
119 MacAddress A3;
120 u16_be SC; // MPDU Sequence Control field
121 } aad_struct{};
122
123 constexpr u16 AADFrameControlMask = 0x8FC7;
124 aad_struct.FC = frame_control & AADFrameControlMask;
125 aad_struct.SC = 0;
126
127 bool to_ds = (frame_control & (1 << 0)) != 0;
128 bool from_ds = (frame_control & (1 << 1)) != 0;
129 // In the 802.11 standard, ToDS = 1 and FromDS = 1 is a valid configuration,
130 // however, the 3DS doesn't seem to transmit frames with such combination.
131 ASSERT_MSG(to_ds != from_ds, "Invalid combination");
132
133 // The meaning of the address fields depends on the ToDS and FromDS fields.
134 if (from_ds) {
135 aad_struct.A1 = receiver;
136 aad_struct.A2 = bssid;
137 aad_struct.A3 = sender;
138 }
139
140 if (to_ds) {
141 aad_struct.A1 = bssid;
142 aad_struct.A2 = sender;
143 aad_struct.A3 = receiver;
144 }
145
146 std::vector<u8> aad(sizeof(aad_struct));
147 std::memcpy(aad.data(), &aad_struct, sizeof(aad_struct));
148
149 return aad;
150}
151
152/*
153 * Decrypts the payload of an encrypted 802.11 data frame using the specified key.
154 * @returns The decrypted payload.
155 */
156static std::vector<u8> DecryptDataFrame(const std::vector<u8>& encrypted_payload,
157 const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key,
158 const MacAddress& sender, const MacAddress& receiver,
159 const MacAddress& bssid, u16 sequence_number,
160 u16 frame_control) {
161
162 // Reference: IEEE 802.11-2007
163
164 std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control);
165
166 std::vector<u8> packet_number{0,
167 0,
168 0,
169 0,
170 static_cast<u8>((sequence_number >> 8) & 0xFF),
171 static_cast<u8>(sequence_number & 0xFF)};
172
173 // 8.3.3.3.3 Construct CCM nonce (13 bytes)
174 std::vector<u8> nonce;
175 nonce.push_back(0); // priority
176 nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2
177 nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN
178
179 try {
180 CryptoPP::CCM<CryptoPP::AES, 8>::Decryption d;
181 d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size());
182 d.SpecifyDataLengths(aad.size(), encrypted_payload.size() - 8, 0);
183
184 CryptoPP::AuthenticatedDecryptionFilter df(
185 d, nullptr, CryptoPP::AuthenticatedDecryptionFilter::MAC_AT_END |
186 CryptoPP::AuthenticatedDecryptionFilter::THROW_EXCEPTION);
187 // put aad
188 df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size());
189
190 // put cipher with mac
191 df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, encrypted_payload.data(),
192 encrypted_payload.size() - 8);
193 df.ChannelPut(CryptoPP::DEFAULT_CHANNEL,
194 encrypted_payload.data() + encrypted_payload.size() - 8, 8);
195
196 df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL);
197 df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
198 df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
199
200 int size = df.MaxRetrievable();
201
202 std::vector<u8> pdata(size);
203 df.Get(pdata.data(), size);
204 return pdata;
205 } catch (CryptoPP::Exception&) {
206 LOG_ERROR(Service_NWM, "failed to decrypt");
207 }
208
209 return {};
210}
211
212/*
213 * Encrypts the payload of an 802.11 data frame using the specified key.
214 * @returns The encrypted payload.
215 */
216static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload,
217 const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key,
218 const MacAddress& sender, const MacAddress& receiver,
219 const MacAddress& bssid, u16 sequence_number,
220 u16 frame_control) {
221 // Reference: IEEE 802.11-2007
222
223 std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control);
224
225 std::vector<u8> packet_number{0,
226 0,
227 0,
228 0,
229 static_cast<u8>((sequence_number >> 8) & 0xFF),
230 static_cast<u8>(sequence_number & 0xFF)};
231
232 // 8.3.3.3.3 Construct CCM nonce (13 bytes)
233 std::vector<u8> nonce;
234 nonce.push_back(0); // priority
235 nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2
236 nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN
237
238 try {
239 CryptoPP::CCM<CryptoPP::AES, 8>::Encryption d;
240 d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size());
241 d.SpecifyDataLengths(aad.size(), payload.size(), 0);
242
243 CryptoPP::AuthenticatedEncryptionFilter df(d);
244 // put aad
245 df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size());
246 df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL);
247
248 // put plaintext
249 df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, payload.data(), payload.size());
250 df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
251
252 df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
253
254 int size = df.MaxRetrievable();
255
256 std::vector<u8> cipher(size);
257 df.Get(cipher.data(), size);
258 return cipher;
259 } catch (CryptoPP::Exception&) {
260 LOG_ERROR(Service_NWM, "failed to encrypt");
261 }
262
263 return {};
264}
265
266std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
267 u16 src_node, u16 sequence_number) {
268 std::vector<u8> buffer = GenerateLLCHeader(EtherType::SecureData);
269 std::vector<u8> securedata_header =
270 GenerateSecureDataHeader(data.size(), channel, dest_node, src_node, sequence_number);
271
272 buffer.insert(buffer.end(), securedata_header.begin(), securedata_header.end());
273 buffer.insert(buffer.end(), data.begin(), data.end());
274 return buffer;
275}
276
277} // namespace NWM
278} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h
new file mode 100644
index 000000000..a23520a41
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_data.h
@@ -0,0 +1,78 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <vector>
9#include "common/common_types.h"
10#include "common/swap.h"
11#include "core/hle/service/service.h"
12
13namespace Service {
14namespace NWM {
15
16enum class SAP : u8 { SNAPExtensionUsed = 0xAA };
17
18enum class PDUControl : u8 { UnnumberedInformation = 3 };
19
20enum class EtherType : u16 { SecureData = 0x876D, EAPoL = 0x888E };
21
22/*
23 * 802.2 header, UDS packets always use SNAP for these headers,
24 * which means the dsap and ssap are always SNAPExtensionUsed (0xAA)
25 * and the OUI is always 0.
26 */
27struct LLCHeader {
28 u8 dsap = static_cast<u8>(SAP::SNAPExtensionUsed);
29 u8 ssap = static_cast<u8>(SAP::SNAPExtensionUsed);
30 u8 control = static_cast<u8>(PDUControl::UnnumberedInformation);
31 std::array<u8, 3> OUI = {};
32 u16_be protocol;
33};
34
35static_assert(sizeof(LLCHeader) == 8, "LLCHeader has the wrong size");
36
37/*
38 * Nintendo SecureData header, every UDS packet contains one,
39 * it is used to store metadata about the transmission such as
40 * the source and destination network node ids.
41 */
42struct SecureDataHeader {
43 // TODO(Subv): It is likely that the first 4 bytes of this header are
44 // actually part of another container protocol.
45 u16_be protocol_size;
46 INSERT_PADDING_BYTES(2);
47 u16_be securedata_size;
48 u8 is_management;
49 u8 data_channel;
50 u16_be sequence_number;
51 u16_be dest_node_id;
52 u16_be src_node_id;
53};
54
55static_assert(sizeof(SecureDataHeader) == 14, "SecureDataHeader has the wrong size");
56
57/*
58 * The raw bytes of this structure are the CTR used in the encryption (AES-CTR)
59 * process used to generate the CCMP key for data frame encryption.
60 */
61struct DataFrameCryptoCTR {
62 u32_le wlan_comm_id;
63 u32_le network_id;
64 std::array<u8, 6> host_mac;
65 u16_le id;
66};
67
68static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size");
69
70/**
71 * Generates an unencrypted 802.11 data payload.
72 * @returns The generated frame payload.
73 */
74std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
75 u16 src_node, u16 sequence_number);
76
77} // namespace NWM
78} // namespace Service
diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h
index b01d04f13..c9f1342f4 100644
--- a/src/core/hw/aes/key.h
+++ b/src/core/hw/aes/key.h
@@ -12,6 +12,8 @@ namespace HW {
12namespace AES { 12namespace AES {
13 13
14enum KeySlotID : size_t { 14enum KeySlotID : size_t {
15 // AES Keyslot used to generate the UDS data frame CCMP key.
16 UDSDataKey = 0x2D,
15 APTWrap = 0x31, 17 APTWrap = 0x31,
16 18
17 MaxKeySlotID = 0x40, 19 MaxKeySlotID = 0x40,