diff --git a/include/tins/crypto.h b/include/tins/crypto.h index f047ac0..6e7dfce 100644 --- a/include/tins/crypto.h +++ b/include/tins/crypto.h @@ -47,26 +47,79 @@ class Dot11; class Dot11Data; namespace Crypto { - /** - * \cond - */ struct RC4Key; #ifdef HAVE_WPA2_DECRYPTION namespace WPA2 { - class invalid_handshake : public std::exception { - public: - const char *what() const throw() { - return "invalid handshake"; - } - }; + /** + * \brief Class that represents the keys used to decrypt a session. + */ class SessionKeys { public: - typedef Internals::byte_array<80> ptk_type; - typedef Internals::byte_array<32> pmk_type; + /** + * The size of the Pairwise Master Key. + */ + static const size_t PMK_SIZE; + + /** + * The size of the Pairwise Transient Key. + */ + static const size_t PTK_SIZE; + + /** + * The type used to hold the PTK (this has to be PTK_SIZE bytes long). + */ + typedef std::vector ptk_type; + + /** + * The type used to hold the PMK (this has to be PMK_SIZE bytes long). + */ + typedef std::vector pmk_type; + /** + * Default constructs a SessionKeys object. + */ SessionKeys(); + + /** + * \brief Constructs an instance using the provided PTK and a flag + * indicating whether it should use ccmp. + * + * \param ptk The PTK to use. + * \param is_ccmp Indicates whether to use CCMP to decrypt this traffic. + */ + SessionKeys(const ptk_type& ptk, bool is_ccmp); + + /** + * \brief Constructs an instance using a handshake and a PMK. + * + * This will internally construct the PTK from the input parameters. + * + * \param hs The handshake to use. + * \param pmk The PMK to use. + */ SessionKeys(const RSNHandshake &hs, const pmk_type &pmk); + + /** + * \brief Decrypts a unicast packet. + * + * \param dot11 The encrypted packet to decrypt. + * \param raw The raw layer on the packet to decrypt. + * \return A SNAP layer containing the decrypted traffic or a null pointer + * if decryption failed. + */ SNAP *decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) const; + + /** + * \brief Gets the PTK for this session keys. + * \return The Pairwise Transcient Key. + */ + const ptk_type& get_ptk() const; + + /** + * \brief Indicates whether CCMP is used to decrypt packets + * /return true iff CCMP is used. + */ + bool uses_ccmp() const; private: SNAP *ccmp_decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) const; SNAP *tkip_decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) const; @@ -75,7 +128,10 @@ namespace Crypto { ptk_type ptk; bool is_ccmp; }; - + + /** + * \cond + */ class SupplicantData { public: typedef HWAddress<6> address_type; @@ -87,7 +143,7 @@ namespace Crypto { private: pmk_type pmk_; }; - } + } // WPA2 #endif // HAVE_WPA2_DECRYPTION /** * \endcond @@ -180,6 +236,27 @@ namespace Crypto { */ typedef HWAddress<6> address_type; + /** + * \brief Represents a pair of mac addresses. + * + * This is used to identify a host and the access point to which + * it is connected. The first element in the pair will always de + * lower or equal than the second one, so that given any host and + * the access point it's connected to, we can uniquely identify + * it with an address pair. + */ + typedef std::pair addr_pair; + + /** + * \brief Maps an address pair to the session keys. + * + * This type associates an address pair (host, access point) with the + * session keys, as generated using the packets seen on a handshake. + * + * \sa addr_pair + */ + typedef std::map keys_map; + /** * \brief Adds an access points's information. * @@ -216,6 +293,28 @@ namespace Crypto { */ void add_ap_data(const std::string &psk, const std::string &ssid, const address_type &addr); + /** + * \brief Explicitly add decryption keys. + * + * This method associates a pair (host, access point) with the given decryption keys. + * All encrypted packets sent between the given addresses will be decrypted using the + * provided keys. + * + * This method shouldn't normally be required. The WPA2Decrypter will be waiting for + * handshakes and will automatically extract the session keys, decrypting all + * encrypted packets with them. You should only use this method if for some reason + * you know the actual keys being used (because you checked and stored the keys_map + * somewhere). + * + * The actual order of the addresses doesn't matter, this method will make sure + * they're sorted. + * + * \param addresses The address pair (host, access point) to associate. + * \param session_keys The keys to use when decrypting messages sent between the + * given addresses. + */ + void add_decryption_keys(const addr_pair& addresses, const WPA2::SessionKeys& session_keys); + /** * \brief Decrypts the provided PDU. * @@ -232,11 +331,19 @@ namespace Crypto { * failed, true otherwise. */ bool decrypt(PDU &pdu); + + /** + * \brief Getter for the keys on this decrypter + * + * The returned map will be populated every time a new, complete, handshake + * is captured. + * + * \return The WPA2Decrypter keys map. + */ + const keys_map& get_keys() const; private: typedef std::map pmks_map; typedef std::map bssids_map; - typedef std::pair addr_pair; - typedef std::map keys_map; void try_add_keys(const Dot11Data &dot11, const RSNHandshake &hs); addr_pair make_addr_pair(const address_type &addr1, const address_type &addr2) { diff --git a/include/tins/exceptions.h b/include/tins/exceptions.h index 46230ce..1b726b6 100644 --- a/include/tins/exceptions.h +++ b/include/tins/exceptions.h @@ -191,6 +191,20 @@ public: } }; +namespace Crypto { +namespace WPA2 { + /** + * \brief Exception thrown when an invalid WPA2 handshake is found. + */ + class invalid_handshake : public std::exception { + public: + const char *what() const throw() { + return "Invalid WPA2 handshake"; + } + }; +} // WPA2 +} // Crypto + } // Tins #endif // TINS_EXCEPTIONS_H diff --git a/src/crypto.cpp b/src/crypto.cpp index 86e024d..356ed75 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -38,6 +38,7 @@ #endif // HAVE_WPA2_DECRYPTION #include "dot11/dot11_data.h" #include "dot11/dot11_beacon.h" +#include "exceptions.h" namespace Tins { namespace Crypto { @@ -115,6 +116,8 @@ PDU *WEPDecrypter::decrypt(RawPDU &raw, const std::string &password) { #ifdef HAVE_WPA2_DECRYPTION // WPA2Decrypter +using WPA2::SessionKeys; + const HWAddress<6> &min(const HWAddress<6>& lhs, const HWAddress<6>& rhs) { return lhs < rhs ? lhs : rhs; } @@ -232,11 +235,26 @@ HWAddress<6> get_bssid(const Dot11Data &dot11) { namespace WPA2 { +const size_t SessionKeys::PTK_SIZE = 80; +const size_t SessionKeys::PMK_SIZE = 32; + SessionKeys::SessionKeys() { } -SessionKeys::SessionKeys(const RSNHandshake &hs, const pmk_type &pmk) { +SessionKeys::SessionKeys(const ptk_type& ptk, bool is_ccmp) +: ptk(ptk), is_ccmp(is_ccmp) { + if (ptk.size() != PTK_SIZE) { + throw invalid_handshake(); + } +} + +SessionKeys::SessionKeys(const RSNHandshake &hs, const pmk_type &pmk) +: ptk(PTK_SIZE) { + if (pmk.size() != PMK_SIZE) { + throw invalid_handshake(); + } + uint8_t PKE[100] = "Pairwise key expansion"; uint8_t MIC[16]; min(hs.client_address(), hs.supplicant_address()).copy(PKE + 23); @@ -253,14 +271,14 @@ SessionKeys::SessionKeys(const RSNHandshake &hs, const pmk_type &pmk) { } for(int i(0); i < 4; ++i) { PKE[99] = i; - HMAC(EVP_sha1(), pmk.begin(), pmk.size(), PKE, 100, ptk.begin() + i * 20, 0); + HMAC(EVP_sha1(), &pmk[0], pmk.size(), PKE, 100, &ptk[0] + i * 20, 0); } PDU::serialization_type buffer = const_cast(hs.handshake()[3]).serialize(); std::fill(buffer.begin() + 81, buffer.begin() + 81 + 16, 0); if(hs.handshake()[3].key_descriptor() == 2) - HMAC(EVP_sha1(), ptk.begin(), 16, &buffer[0], buffer.size(), MIC, 0); + HMAC(EVP_sha1(), &ptk[0], 16, &buffer[0], buffer.size(), MIC, 0); else - HMAC(EVP_md5(), ptk.begin(), 16, &buffer[0], buffer.size(), MIC, 0); + HMAC(EVP_md5(), &ptk[0], 16, &buffer[0], buffer.size(), MIC, 0); if(!std::equal(MIC, MIC + sizeof(MIC), hs.handshake()[3].mic())) throw invalid_handshake(); @@ -298,7 +316,7 @@ SNAP *SessionKeys::ccmp_decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) con dot11.addr4().copy(AAD + 24); AES_KEY ctx; - AES_set_encrypt_key(ptk.begin() + 32, 128, &ctx); + AES_set_encrypt_key(&ptk[0] + 32, 128, &ctx); uint8_t crypted_block[16]; size_t total_sz = raw.payload_size() - 16, offset = 8, blocks = (total_sz + 15) / 16; @@ -343,7 +361,7 @@ SNAP *SessionKeys::ccmp_decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) con RC4Key SessionKeys::generate_rc4_key(const Dot11Data &dot11, const RawPDU &raw) const { const RawPDU::payload_type &pload = raw.payload(); - const uint8_t *tk = ptk.begin() + 32; + const uint8_t *tk = &ptk[0] + 32; Internals::byte_array<16> rc4_key; uint16_t ppk[6]; const Dot11::address_type addr = dot11.addr2(); @@ -429,9 +447,18 @@ SNAP *SessionKeys::decrypt_unicast(const Dot11Data &dot11, RawPDU &raw) const { tkip_decrypt_unicast(dot11, raw); } +const SessionKeys::ptk_type& SessionKeys::get_ptk() const { + return ptk; +} + +bool SessionKeys::uses_ccmp() const { + return is_ccmp; +} + // supplicant_data -SupplicantData::SupplicantData(const std::string &psk, const std::string &ssid) { +SupplicantData::SupplicantData(const std::string &psk, const std::string &ssid) +: pmk_(SessionKeys::PMK_SIZE) { PKCS5_PBKDF2_HMAC_SHA1( psk.c_str(), psk.size(), @@ -439,7 +466,7 @@ SupplicantData::SupplicantData(const std::string &psk, const std::string &ssid) ssid.size(), 4096, pmk_.size(), - pmk_.begin() + &pmk_[0] ); } @@ -466,18 +493,27 @@ void WPA2Decrypter::add_access_point(const std::string &ssid, const address_type aps.insert(std::make_pair(addr, it->second)); } +void WPA2Decrypter::add_decryption_keys(const addr_pair& addresses, const SessionKeys& session_keys) { + addr_pair sorted_pair = make_addr_pair(addresses.first, addresses.second); + keys[sorted_pair] = session_keys; +} + void WPA2Decrypter::try_add_keys(const Dot11Data &dot11, const RSNHandshake &hs) { bssids_map::const_iterator it = find_ap(dot11); if(it != aps.end()) { addr_pair addr_p = extract_addr_pair(dot11); try { - WPA2::SessionKeys session(hs, it->second.pmk()); + SessionKeys session(hs, it->second.pmk()); keys[addr_p] = session; } catch(WPA2::invalid_handshake&) { } } } +const WPA2Decrypter::keys_map& WPA2Decrypter::get_keys() const { + return keys; +} + WPA2Decrypter::addr_pair WPA2Decrypter::extract_addr_pair(const Dot11Data &dot11) { if(dot11.from_ds() && !dot11.to_ds()) return make_addr_pair(dot11.addr2(), dot11.addr3()); diff --git a/tests/src/wpa2_decrypt.cpp b/tests/src/wpa2_decrypt.cpp index fcb34db..ef80e1e 100644 --- a/tests/src/wpa2_decrypt.cpp +++ b/tests/src/wpa2_decrypt.cpp @@ -127,6 +127,37 @@ TEST_F(WPA2DecryptTest, DecryptCCMPWithoutUsingBeacon) { } } +TEST_F(WPA2DecryptTest, DecryptCCMPUsingKey) { + Crypto::WPA2Decrypter::addr_pair addresses; + Crypto::WPA2::SessionKeys session_keys; + + { + Crypto::WPA2Decrypter decrypter; + decrypter.add_ap_data("Induction", "Coherer", "00:0c:41:82:b2:55"); + for(size_t i = 1; i < 5; ++i) { + RadioTap radio(ccmp_packets[i], ccmp_packets_size[i]); + ASSERT_FALSE(decrypter.decrypt(radio)); + } + const Crypto::WPA2Decrypter::keys_map& keys = decrypter.get_keys(); + ASSERT_EQ(1, keys.size()); + addresses = keys.begin()->first; + session_keys = keys.begin()->second; + } + + Crypto::WPA2Decrypter decrypter; + decrypter.add_decryption_keys(addresses, session_keys); + for(size_t i = 5; i < 7; ++i) { + RadioTap radio(ccmp_packets[i], ccmp_packets_size[i]); + ASSERT_TRUE(decrypter.decrypt(radio)); + if(i == 5) + check_ccmp_packet5(radio); + else + check_ccmp_packet6(radio); + } + + EXPECT_TRUE(session_keys.uses_ccmp()); +} + TEST_F(WPA2DecryptTest, DecryptTKIPUsingBeacon) { Crypto::WPA2Decrypter decrypter; decrypter.add_ap_data("libtinstest", "NODO"); @@ -161,6 +192,37 @@ TEST_F(WPA2DecryptTest, DecryptTKIPWithoutUsingBeacon) { } } +TEST_F(WPA2DecryptTest, DecryptTKIPUsingKey) { + Crypto::WPA2Decrypter::addr_pair addresses; + Crypto::WPA2::SessionKeys session_keys; + + { + Crypto::WPA2Decrypter decrypter; + decrypter.add_ap_data("libtinstest", "NODO", "00:1b:11:d2:1b:eb"); + for(size_t i = 1; i < 5; ++i) { + RadioTap radio(tkip_packets[i], tkip_packets_size[i]); + ASSERT_FALSE(decrypter.decrypt(radio)); + } + const Crypto::WPA2Decrypter::keys_map& keys = decrypter.get_keys(); + ASSERT_EQ(1, keys.size()); + addresses = keys.begin()->first; + session_keys = keys.begin()->second; + } + + Crypto::WPA2Decrypter decrypter; + decrypter.add_decryption_keys(addresses, session_keys); + for(size_t i = 5; i < 7; ++i) { + RadioTap radio(tkip_packets[i], tkip_packets_size[i]); + ASSERT_TRUE(decrypter.decrypt(radio)); + if(i == 5) + check_tkip_packet5(radio); + else + check_tkip_packet6(radio); + } + + EXPECT_FALSE(session_keys.uses_ccmp()); +} + TEST_F(WPA2DecryptTest, DecryptCCMPAndTKIPUsingBeacon) { Crypto::WPA2Decrypter decrypter; decrypter.add_ap_data("libtinstest", "NODO");