Merge pull request #13005 from SimonWalton-HiFi/hmac-auth2

Use HMAC authentication for network packets - 2nd attempt
This commit is contained in:
John Conklin II 2018-05-10 14:38:18 -07:00 committed by GitHub
commit bc9e403ab9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 230 additions and 42 deletions

View file

@ -0,0 +1,117 @@
//
// HMACAuth.cpp
// libraries/networking/src
//
// Created by Simon Walton on 3/19/2018.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "HMACAuth.h"
#include <openssl/opensslv.h>
#include <openssl/hmac.h>
#include <QUuid>
#include "NetworkLogging.h"
#include <cassert>
#if OPENSSL_VERSION_NUMBER >= 0x10100000
HMACAuth::HMACAuth(AuthMethod authMethod)
: _hmacContext(HMAC_CTX_new())
, _authMethod(authMethod) { }
HMACAuth::~HMACAuth()
{
HMAC_CTX_free(_hmacContext);
}
#else
HMACAuth::HMACAuth(AuthMethod authMethod)
: _hmacContext(new HMAC_CTX())
, _authMethod(authMethod) {
HMAC_CTX_init(_hmacContext);
}
HMACAuth::~HMACAuth() {
HMAC_CTX_cleanup(_hmacContext);
delete _hmacContext;
}
#endif
bool HMACAuth::setKey(const char* keyValue, int keyLen) {
const EVP_MD* sslStruct = nullptr;
switch (_authMethod) {
case MD5:
sslStruct = EVP_md5();
break;
case SHA1:
sslStruct = EVP_sha1();
break;
case SHA224:
sslStruct = EVP_sha224();
break;
case SHA256:
sslStruct = EVP_sha256();
break;
case RIPEMD160:
sslStruct = EVP_ripemd160();
break;
default:
return false;
}
QMutexLocker lock(&_lock);
return (bool) HMAC_Init_ex(_hmacContext, keyValue, keyLen, sslStruct, nullptr);
}
bool HMACAuth::setKey(const QUuid& uidKey) {
const QByteArray rfcBytes(uidKey.toRfc4122());
return setKey(rfcBytes.constData(), rfcBytes.length());
}
bool HMACAuth::addData(const char* data, int dataLen) {
QMutexLocker lock(&_lock);
return (bool) HMAC_Update(_hmacContext, reinterpret_cast<const unsigned char*>(data), dataLen);
}
HMACAuth::HMACHash HMACAuth::result() {
HMACHash hashValue(EVP_MAX_MD_SIZE);
unsigned int hashLen;
QMutexLocker lock(&_lock);
auto hmacResult = HMAC_Final(_hmacContext, &hashValue[0], &hashLen);
if (hmacResult) {
hashValue.resize((size_t)hashLen);
} else {
// the HMAC_FINAL call failed - should not be possible to get into this state
qCWarning(networking) << "Error occured calling HMAC_Final";
assert(hmacResult);
}
// Clear state for possible reuse.
HMAC_Init_ex(_hmacContext, nullptr, 0, nullptr, nullptr);
return hashValue;
}
bool HMACAuth::calculateHash(HMACHash& hashResult, const char* data, int dataLen) {
QMutexLocker lock(&_lock);
if (!addData(data, dataLen)) {
qCWarning(networking) << "Error occured calling HMACAuth::addData()";
assert(false);
return false;
}
hashResult = result();
return true;
}

View file

@ -0,0 +1,47 @@
//
// HMACAuth.h
// libraries/networking/src
//
// Created by Simon Walton on 3/19/2018.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_HMACAuth_h
#define hifi_HMACAuth_h
#include <vector>
#include <memory>
#include <QtCore/QMutex>
class QUuid;
class HMACAuth {
public:
enum AuthMethod { MD5, SHA1, SHA224, SHA256, RIPEMD160 };
using HMACHash = std::vector<unsigned char>;
explicit HMACAuth(AuthMethod authMethod = MD5);
~HMACAuth();
bool setKey(const char* keyValue, int keyLen);
bool setKey(const QUuid& uidKey);
// Calculate complete hash in one.
bool calculateHash(HMACHash& hashResult, const char* data, int dataLen);
// Append to data to be hashed.
bool addData(const char* data, int dataLen);
// Get the resulting hash from calls to addData().
// Note that only one hash may be calculated at a time for each
// HMACAuth instance if this interface is used.
HMACHash result();
private:
QMutex _lock { QMutex::Recursive };
struct hmac_ctx_st* _hmacContext;
AuthMethod _authMethod;
};
#endif // hifi_HMACAuth_h

View file

@ -36,6 +36,7 @@
#include "HifiSockAddr.h"
#include "NetworkLogging.h"
#include "udt/Packet.h"
#include "HMACAuth.h"
static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0);
@ -332,15 +333,20 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe
if (verifiedPacket && !ignoreVerification) {
QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet);
QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, sourceNode->getConnectionSecret());
QByteArray expectedHash;
auto sourceNodeHMACAuth = sourceNode->getAuthenticateHash();
if (sourceNode->getAuthenticateHash()) {
expectedHash = NLPacket::hashForPacketAndHMAC(packet, *sourceNodeHMACAuth);
}
// check if the md5 hash in the header matches the hash we would expect
if (packetHeaderHash != expectedHash) {
// check if the HMAC-md5 hash in the header matches the hash we would expect
if (!sourceNodeHMACAuth || packetHeaderHash != expectedHash) {
static QMultiMap<QUuid, PacketType> hashDebugSuppressMap;
if (!hashDebugSuppressMap.contains(sourceID, headerType)) {
qCDebug(networking) << packetHeaderHash << expectedHash;
qCDebug(networking) << "Packet hash mismatch on" << headerType << "- Sender" << sourceID;
qCDebug(networking) << "Packet len:" << packet.getDataSize() << "Expected hash:" <<
expectedHash.toHex() << "Actual:" << packetHeaderHash.toHex();
hashDebugSuppressMap.insert(sourceID, headerType);
}
@ -372,15 +378,15 @@ void LimitedNodeList::collectPacketStats(const NLPacket& packet) {
_numCollectedBytes += packet.getDataSize();
}
void LimitedNodeList::fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret) {
void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth) {
if (!PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())) {
packet.writeSourceID(getSessionLocalID());
}
if (!connectionSecret.isNull()
if (hmacAuth
&& !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())
&& !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) {
packet.writeVerificationHashGivenSecret(connectionSecret);
packet.writeVerificationHash(*hmacAuth);
}
}
@ -396,17 +402,17 @@ qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const Node&
emit dataSent(destinationNode.getType(), packet.getDataSize());
destinationNode.recordBytesSent(packet.getDataSize());
return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getConnectionSecret());
return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getAuthenticateHash());
}
qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret) {
HMACAuth* hmacAuth) {
Q_ASSERT(!packet.isPartOfMessage());
Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket",
"Trying to send a reliable packet unreliably.");
collectPacketStats(packet);
fillPacketHeader(packet, connectionSecret);
fillPacketHeader(packet, hmacAuth);
return _nodeSocket.writePacket(packet, sockAddr);
}
@ -419,7 +425,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node&
emit dataSent(destinationNode.getType(), packet->getDataSize());
destinationNode.recordBytesSent(packet->getDataSize());
return sendPacket(std::move(packet), *activeSocket, destinationNode.getConnectionSecret());
return sendPacket(std::move(packet), *activeSocket, destinationNode.getAuthenticateHash());
} else {
qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node" << destinationNode << "- not sending";
return ERROR_SENDING_PACKET_BYTES;
@ -427,18 +433,18 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node&
}
qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret) {
HMACAuth* hmacAuth) {
Q_ASSERT(!packet->isPartOfMessage());
if (packet->isReliable()) {
collectPacketStats(*packet);
fillPacketHeader(*packet, connectionSecret);
fillPacketHeader(*packet, hmacAuth);
auto size = packet->getDataSize();
_nodeSocket.writePacket(std::move(packet), sockAddr);
return size;
} else {
return sendUnreliablePacket(*packet, sockAddr, connectionSecret);
return sendUnreliablePacket(*packet, sockAddr, hmacAuth);
}
}
@ -447,13 +453,14 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi
if (activeSocket) {
qint64 bytesSent = 0;
auto connectionSecret = destinationNode.getConnectionSecret();
auto connectionHash = destinationNode.getAuthenticateHash();
// close the last packet in the list
packetList.closeCurrentPacket();
while (!packetList._packets.empty()) {
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), *activeSocket, connectionSecret);
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), *activeSocket,
connectionHash);
}
emit dataSent(destinationNode.getType(), bytesSent);
@ -466,14 +473,14 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi
}
qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret) {
HMACAuth* hmacAuth) {
qint64 bytesSent = 0;
// close the last packet in the list
packetList.closeCurrentPacket();
while (!packetList._packets.empty()) {
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), sockAddr, connectionSecret);
bytesSent += sendPacket(packetList.takeFront<NLPacket>(), sockAddr, hmacAuth);
}
return bytesSent;
@ -501,7 +508,7 @@ qint64 LimitedNodeList::sendPacketList(std::unique_ptr<NLPacketList> packetList,
for (std::unique_ptr<udt::Packet>& packet : packetList->_packets) {
NLPacket* nlPacket = static_cast<NLPacket*>(packet.get());
collectPacketStats(*nlPacket);
fillPacketHeader(*nlPacket, destinationNode.getConnectionSecret());
fillPacketHeader(*nlPacket, destinationNode.getAuthenticateHash());
}
return _nodeSocket.writePacketList(std::move(packetList), *activeSocket);
@ -524,7 +531,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node&
auto& destinationSockAddr = (overridenSockAddr.isNull()) ? *destinationNode.getActiveSocket()
: overridenSockAddr;
return sendPacket(std::move(packet), destinationSockAddr, destinationNode.getConnectionSecret());
return sendPacket(std::move(packet), destinationSockAddr, destinationNode.getAuthenticateHash());
}
int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {

View file

@ -138,19 +138,17 @@ public:
// use sendUnreliablePacket to send an unreliable packet (that you do not need to move)
// either to a node (via its active socket) or to a manual sockaddr
qint64 sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode);
qint64 sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret = QUuid());
qint64 sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr, HMACAuth* hmacAuth = nullptr);
// use sendPacket to send a moved unreliable or reliable NL packet to a node's active socket or manual sockaddr
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode);
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret = QUuid());
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const HifiSockAddr& sockAddr, HMACAuth* hmacAuth = nullptr);
// use sendUnreliableUnorderedPacketList to unreliably send separate packets from the packet list
// either to a node's active socket or to a manual sockaddr
qint64 sendUnreliableUnorderedPacketList(NLPacketList& packetList, const Node& destinationNode);
qint64 sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret = QUuid());
HMACAuth* hmacAuth = nullptr);
// use sendPacketList to send reliable packet lists (ordered or unordered) to a node's active socket
// or to a manual sock addr
@ -372,7 +370,7 @@ protected:
qint64 writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr,
const QUuid& connectionSecret = QUuid());
void collectPacketStats(const NLPacket& packet);
void fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret = QUuid());
void fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth = nullptr);
void setLocalSocket(const HifiSockAddr& sockAddr);

View file

@ -11,6 +11,8 @@
#include "NLPacket.h"
#include "HMACAuth.h"
int NLPacket::localHeaderSize(PacketType type) {
bool nonSourced = PacketTypeEnum::getNonSourcedPackets().contains(type);
bool nonVerified = PacketTypeEnum::getNonVerifiedPackets().contains(type);
@ -150,18 +152,16 @@ QByteArray NLPacket::verificationHashInHeader(const udt::Packet& packet) {
return QByteArray(packet.getData() + offset, NUM_BYTES_MD5_HASH);
}
QByteArray NLPacket::hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret) {
QCryptographicHash hash(QCryptographicHash::Md5);
QByteArray NLPacket::hashForPacketAndHMAC(const udt::Packet& packet, HMACAuth& hash) {
int offset = Packet::totalHeaderSize(packet.isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion)
+ NUM_BYTES_LOCALID + NUM_BYTES_MD5_HASH;
// add the packet payload and the connection UUID
hash.addData(packet.getData() + offset, packet.getDataSize() - offset);
hash.addData(connectionSecret.toRfc4122());
// return the hash
return hash.result();
HMACAuth::HMACHash hashResult;
if (!hash.calculateHash(hashResult, packet.getData() + offset, packet.getDataSize() - offset)) {
return QByteArray();
}
return QByteArray((const char*) hashResult.data(), (int) hashResult.size());
}
void NLPacket::writeTypeAndVersion() {
@ -214,14 +214,14 @@ void NLPacket::writeSourceID(LocalID sourceID) const {
_sourceID = sourceID;
}
void NLPacket::writeVerificationHashGivenSecret(const QUuid& connectionSecret) const {
void NLPacket::writeVerificationHash(HMACAuth& hmacAuth) const {
Q_ASSERT(!PacketTypeEnum::getNonSourcedPackets().contains(_type) &&
!PacketTypeEnum::getNonVerifiedPackets().contains(_type));
auto offset = Packet::totalHeaderSize(isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion)
+ NUM_BYTES_LOCALID;
QByteArray verificationHash = hashForPacketAndSecret(*this, connectionSecret);
QByteArray verificationHash = hashForPacketAndHMAC(*this, hmacAuth);
memcpy(_packet.get() + offset, verificationHash.data(), verificationHash.size());
}

View file

@ -18,6 +18,8 @@
#include "udt/Packet.h"
class HMACAuth;
class NLPacket : public udt::Packet {
Q_OBJECT
public:
@ -69,7 +71,7 @@ public:
static LocalID sourceIDInHeader(const udt::Packet& packet);
static QByteArray verificationHashInHeader(const udt::Packet& packet);
static QByteArray hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret);
static QByteArray hashForPacketAndHMAC(const udt::Packet& packet, HMACAuth& hash);
PacketType getType() const { return _type; }
void setType(PacketType type);
@ -78,9 +80,9 @@ public:
void setVersion(PacketVersion version);
LocalID getSourceID() const { return _sourceID; }
void writeSourceID(LocalID sourceID) const;
void writeVerificationHashGivenSecret(const QUuid& connectionSecret) const;
void writeVerificationHash(HMACAuth& hmacAuth) const;
protected:

View file

@ -86,7 +86,7 @@ NodeType_t NodeType::fromString(QString type) {
Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket,
const HifiSockAddr& localSocket, QObject* parent) :
const HifiSockAddr& localSocket, QObject* parent) :
NetworkPeer(uuid, publicSocket, localSocket, parent),
_type(type),
_pingMs(-1), // "Uninitialized"
@ -108,6 +108,7 @@ void Node::setType(char type) {
_symmetricSocket.setObjectName(typeString);
}
void Node::updateClockSkewUsec(qint64 clockSkewSample) {
_clockSkewMovingPercentile.updatePercentile(clockSkewSample);
_clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile();
@ -194,3 +195,16 @@ QDebug operator<<(QDebug debug, const Node& node) {
debug.nospace() << node.getPublicSocket() << "/" << node.getLocalSocket();
return debug.nospace();
}
void Node::setConnectionSecret(const QUuid& connectionSecret) {
if (_connectionSecret == connectionSecret) {
return;
}
if (!_authenticateHash) {
_authenticateHash.reset(new HMACAuth());
}
_connectionSecret = connectionSecret;
_authenticateHash->setKey(_connectionSecret);
}

View file

@ -33,6 +33,7 @@
#include "SimpleMovingAverage.h"
#include "MovingPercentile.h"
#include "NodePermissions.h"
#include "HMACAuth.h"
class Node : public NetworkPeer {
Q_OBJECT
@ -55,7 +56,8 @@ public:
void setIsUpstream(bool isUpstream) { _isUpstream = isUpstream; }
const QUuid& getConnectionSecret() const { return _connectionSecret; }
void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; }
void setConnectionSecret(const QUuid& connectionSecret);
HMACAuth* getAuthenticateHash() const { return _authenticateHash.get(); }
NodeData* getLinkedData() const { return _linkedData.get(); }
void setLinkedData(std::unique_ptr<NodeData> linkedData) { _linkedData = std::move(linkedData); }
@ -97,6 +99,7 @@ private:
NodeType_t _type;
QUuid _connectionSecret;
std::unique_ptr<HMACAuth> _authenticateHash { nullptr };
std::unique_ptr<NodeData> _linkedData;
bool _isReplicated { false };
int _pingMs;

View file

@ -92,7 +92,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::AvatarQuery:
return static_cast<PacketVersion>(AvatarQueryVersion::ConicalFrustums);
default:
return 20;
return 21;
}
}