Merge pull request #11882 from howard-stearns/the-lost-ECDSA

The lost ecdsa
This commit is contained in:
Zach Fox 2017-11-29 15:46:22 -08:00 committed by GitHub
commit 7490744fb7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 171 additions and 212 deletions

View file

@ -27,11 +27,11 @@
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/ecdsa.h>
// I know, right? But per https://www.openssl.org/docs/faq.html
// this avoids OPENSSL_Uplink(00007FF847238000,08): no OPENSSL_Applink
@ -78,18 +78,19 @@ int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
}
}
RSA* readKeys(const char* filename) {
EC_KEY* readKeys(const char* filename) {
FILE* fp;
RSA* key = NULL;
EC_KEY *key = NULL;
if ((fp = fopen(filename, "rt"))) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) {
if ((key = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL))) {
// now read private key
qCDebug(commerce) << "read public key";
if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) {
if ((key = PEM_read_ECPrivateKey(fp, &key, passwordCallback, NULL))) {
qCDebug(commerce) << "read private key";
fclose(fp);
return key;
@ -137,18 +138,18 @@ bool Wallet::writeBackupInstructions() {
return retval;
}
bool writeKeys(const char* filename, RSA* keys) {
bool writeKeys(const char* filename, EC_KEY* keys) {
FILE* fp;
bool retval = false;
if ((fp = fopen(filename, "wt"))) {
if (!PEM_write_RSAPublicKey(fp, keys)) {
if (!PEM_write_EC_PUBKEY(fp, keys)) {
fclose(fp);
qCDebug(commerce) << "failed to write public key";
QFile(QString(filename)).remove();
return retval;
}
if (!PEM_write_RSAPrivateKey(fp, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
if (!PEM_write_ECPrivateKey(fp, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
fclose(fp);
qCDebug(commerce) << "failed to write private key";
QFile(QString(filename)).remove();
@ -164,50 +165,29 @@ bool writeKeys(const char* filename, RSA* keys) {
return retval;
}
// copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp.
// We will have a different implementation in practice, but this gives us a start for now
//
// TODO: we don't really use the private keys returned - we can see how this evolves, but probably
// we should just return a list of public keys?
// or perhaps return the RSA* instead?
QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
QPair<QByteArray*, QByteArray*> generateECKeypair() {
RSA* keyPair = RSA_new();
BIGNUM* exponent = BN_new();
EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1);
QPair<QByteArray*, QByteArray*> retval;
const unsigned long RSA_KEY_EXPONENT = 65537;
BN_set_word(exponent, RSA_KEY_EXPONENT);
// seed the random number generator before we call RSA_generate_key_ex
srand(time(NULL));
const int RSA_KEY_BITS = 2048;
if (!RSA_generate_key_ex(keyPair, RSA_KEY_BITS, exponent, NULL)) {
qCDebug(commerce) << "Error generating 2048-bit RSA Keypair -" << ERR_get_error();
// we're going to bust out of here but first we cleanup the BIGNUM
BN_free(exponent);
EC_KEY_set_asn1_flag(keyPair, OPENSSL_EC_NAMED_CURVE);
if (!EC_KEY_generate_key(keyPair)) {
qCDebug(commerce) << "Error generating EC Keypair -" << ERR_get_error();
return retval;
}
// we don't need the BIGNUM anymore so clean that up
BN_free(exponent);
// grab the public key and private key from the file
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_RSAPublicKey(keyPair, &publicKeyDER);
int publicKeyLength = i2d_EC_PUBKEY(keyPair, &publicKeyDER);
unsigned char* privateKeyDER = NULL;
int privateKeyLength = i2d_RSAPrivateKey(keyPair, &privateKeyDER);
int privateKeyLength = i2d_ECPrivateKey(keyPair, &privateKeyDER);
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
qCDebug(commerce) << "Error getting DER public or private key from RSA struct -" << ERR_get_error();
qCDebug(commerce) << "Error getting DER public or private key from EC struct -" << ERR_get_error();
// cleanup the RSA struct
RSA_free(keyPair);
// cleanup the EC struct
EC_KEY_free(keyPair);
// cleanup the public and private key DER data, if required
if (publicKeyLength > 0) {
@ -227,13 +207,13 @@ QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
return retval;
}
RSA_free(keyPair);
EC_KEY_free(keyPair);
// prepare the return values. TODO: Fix this - we probably don't really even want the
// private key at all (better to read it when we need it?). Or maybe we do, when we have
// multiple keys?
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength ),
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength );
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
// cleanup the publicKeyDER and publicKeyDER data
OPENSSL_free(publicKeyDER);
@ -245,18 +225,18 @@ QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
// the public key can just go into a byte array
QByteArray readPublicKey(const char* filename) {
FILE* fp;
RSA* key = NULL;
EC_KEY* key = NULL;
if ((fp = fopen(filename, "r"))) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) {
if ((key = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL))) {
// file read successfully
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_RSAPublicKey(key, &publicKeyDER);
int publicKeyLength = i2d_EC_PUBKEY(key, &publicKeyDER);
// TODO: check for 0 length?
// cleanup
RSA_free(key);
EC_KEY_free(key);
fclose(fp);
qCDebug(commerce) << "parsed public key file successfully";
@ -274,15 +254,15 @@ QByteArray readPublicKey(const char* filename) {
return QByteArray();
}
// the private key should be read/copied into heap memory. For now, we need the RSA struct
// so I'll return that. Note we need to RSA_free(key) later!!!
RSA* readPrivateKey(const char* filename) {
// the private key should be read/copied into heap memory. For now, we need the EC_KEY struct
// so I'll return that.
EC_KEY* readPrivateKey(const char* filename) {
FILE* fp;
RSA* key = NULL;
EC_KEY* key = NULL;
if ((fp = fopen(filename, "r"))) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) {
if ((key = PEM_read_ECPrivateKey(fp, &key, passwordCallback, NULL))) {
qCDebug(commerce) << "parsed private key file successfully";
} else {
@ -509,7 +489,7 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
if (publicKey.size() > 0) {
if (auto key = readPrivateKey(keyFilePath().toStdString().c_str())) {
RSA_free(key);
EC_KEY_free(key);
// be sure to add the public key so we don't do this over and over
_publicKeys.push_back(publicKey.toBase64());
@ -525,7 +505,7 @@ bool Wallet::generateKeyPair() {
initialize();
qCInfo(commerce) << "Generating keypair.";
auto keyPair = generateRSAKeypair();
auto keyPair = generateECKeypair();
writeBackupInstructions();
@ -557,25 +537,25 @@ QStringList Wallet::listPublicKeys() {
// the horror of code pages and so on (changing the bytes) by just returning a base64
// encoded string representing the signature (suitable for http, etc...)
QString Wallet::signWithKey(const QByteArray& text, const QString& key) {
qCInfo(commerce) << "Signing text.";
RSA* rsaPrivateKey = NULL;
if ((rsaPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) {
QByteArray signature(RSA_size(rsaPrivateKey), 0);
qCInfo(commerce) << "Signing text" << text << "with key" << key;
EC_KEY* ecPrivateKey = NULL;
if ((ecPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) {
unsigned char* sig = new unsigned char[ECDSA_size(ecPrivateKey)];
unsigned int signatureBytes = 0;
QByteArray hashedPlaintext = QCryptographicHash::hash(text, QCryptographicHash::Sha256);
int encryptReturn = RSA_sign(NID_sha256,
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
hashedPlaintext.size(),
reinterpret_cast<unsigned char*>(signature.data()),
&signatureBytes,
rsaPrivateKey);
// free the private key RSA struct now that we are done with it
RSA_free(rsaPrivateKey);
int retrn = ECDSA_sign(0,
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
hashedPlaintext.size(),
sig,
&signatureBytes, ecPrivateKey);
if (encryptReturn != -1) {
EC_KEY_free(ecPrivateKey);
QByteArray signature(reinterpret_cast<const char*>(sig), signatureBytes);
if (retrn != -1) {
return signature.toBase64();
}
}
@ -674,7 +654,7 @@ void Wallet::reset() {
keyFile.remove();
}
bool Wallet::writeWallet(const QString& newPassphrase) {
RSA* keys = readKeys(keyFilePath().toStdString().c_str());
EC_KEY* keys = readKeys(keyFilePath().toStdString().c_str());
if (keys) {
// we read successfully, so now write to a new temp file
QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp"));
@ -720,82 +700,86 @@ bool Wallet::changePassphrase(const QString& newPassphrase) {
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
auto nodeList = DependencyManager::get<NodeList>();
// With EC keys, we receive a nonce from the metaverse server, which is signed
// here with the private key and returned. Verification is done at server.
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
unsigned char decryptedText[64];
int status;
int certIDByteArraySize;
int encryptedTextByteArraySize;
int textByteArraySize;
int challengingNodeUUIDByteArraySize;
packet->readPrimitive(&certIDByteArraySize);
packet->readPrimitive(&encryptedTextByteArraySize);
packet->readPrimitive(&textByteArraySize); // returns a cast char*, size
if (challengeOriginatedFromClient) {
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
}
// "encryptedText" is now a series of random bytes, a nonce
QByteArray certID = packet->read(certIDByteArraySize);
QByteArray encryptedText = packet->read(encryptedTextByteArraySize);
QByteArray text = packet->read(textByteArraySize);
QByteArray challengingNodeUUID;
if (challengeOriginatedFromClient) {
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
}
RSA* rsa = readKeys(keyFilePath().toStdString().c_str());
int decryptionStatus = -1;
EC_KEY* ec = readKeys(keyFilePath().toStdString().c_str());
QString sig;
if (rsa) {
if (ec) {
ERR_clear_error();
decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize,
reinterpret_cast<const unsigned char*>(encryptedText.constData()),
decryptedText,
rsa,
RSA_PKCS1_OAEP_PADDING);
RSA_free(rsa);
sig = signWithKey(text, ""); // base64 signature, QByteArray cast (on return) to QString FIXME should pass ec as string so we can tell which key to sign with
status = 1;
} else {
qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed.";
qCDebug(commerce) << "During entity ownership challenge, creating the EC-signed nonce failed.";
status = -1;
}
QByteArray decryptedTextByteArray;
if (decryptionStatus > -1) {
decryptedTextByteArray = QByteArray(reinterpret_cast<const char*>(decryptedText), decryptionStatus);
EC_KEY_free(ec);
QByteArray ba = sig.toLocal8Bit();
const char *sigChar = ba.data();
QByteArray textByteArray;
if (status > -1) {
textByteArray = QByteArray(sigChar, (int) strlen(sigChar));
}
int decryptedTextByteArraySize = decryptedTextByteArray.size();
textByteArraySize = textByteArray.size();
int certIDSize = certID.size();
// setup the packet
if (challengeOriginatedFromClient) {
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
certIDSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
true);
decryptedTextPacket->writePrimitive(certIDSize);
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize);
decryptedTextPacket->write(certID);
decryptedTextPacket->write(decryptedTextByteArray);
decryptedTextPacket->write(challengingNodeUUID);
textPacket->writePrimitive(certIDSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->writePrimitive(challengingNodeUUIDByteArraySize);
textPacket->write(certID);
textPacket->write(textByteArray);
textPacket->write(challengingNodeUUID);
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for CertID" << certID;
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
nodeList->sendPacket(std::move(textPacket), *sendingNode);
} else {
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true);
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + textByteArraySize + 2 * sizeof(int), true);
decryptedTextPacket->writePrimitive(certIDSize);
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
decryptedTextPacket->write(certID);
decryptedTextPacket->write(decryptedTextByteArray);
textPacket->writePrimitive(certIDSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->write(certID);
textPacket->write(textByteArray);
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for CertID" << certID;
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
nodeList->sendPacket(std::move(textPacket), *sendingNode);
}
if (decryptionStatus == -1) {
qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed.";
if (status == -1) {
qCDebug(commerce) << "During entity ownership challenge, signing the text failed.";
long error = ERR_get_error();
if (error != 0) {
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "RSA error:" << error_str;
qCWarning(entities) << "EC error:" << error_str;
}
}
}

View file

@ -326,21 +326,21 @@ void ContextOverlayInterface::openInspectionCertificate() {
QString ownerKey = jsonObject["transfer_recipient_key"].toString();
QByteArray certID = entityProperties.getCertificateID().toUtf8();
QByteArray encryptedText = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeEncryptedNonce(certID, ownerKey);
QByteArray text = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeNonce(certID, ownerKey);
QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122();
int certIDByteArraySize = certID.length();
int encryptedTextByteArraySize = encryptedText.length();
int textByteArraySize = text.length();
int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
certIDByteArraySize + encryptedTextByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize);
challengeOwnershipPacket->writePrimitive(textByteArraySize);
challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize);
challengeOwnershipPacket->write(certID);
challengeOwnershipPacket->write(encryptedText);
challengeOwnershipPacket->write(text);
challengeOwnershipPacket->write(nodeToChallengeByteArray);
nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer);
@ -421,16 +421,16 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer
_challengeOwnershipTimeoutTimer.stop();
int certIDByteArraySize;
int decryptedTextByteArraySize;
int textByteArraySize;
packet->readPrimitive(&certIDByteArraySize);
packet->readPrimitive(&decryptedTextByteArraySize);
packet->readPrimitive(&textByteArraySize);
QString certID(packet->read(certIDByteArraySize));
QString decryptedText(packet->read(decryptedTextByteArraySize));
QString text(packet->read(textByteArraySize));
EntityItemID id;
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyDecryptedNonce(certID, decryptedText, id);
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyNonce(certID, text, id);
if (verificationSuccess) {
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));

View file

@ -13,21 +13,18 @@
#include <QHash>
#include <QObject>
#include <QtCore/QJsonDocument>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/ecdsa.h>
#include <NetworkingConstants.h>
#include <NetworkAccessManager.h>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <ByteCountCoding.h>
#include <GLMHelpers.h>
#include <RegisteredMetaTypes.h>
#include <Extents.h>
#include "EntitiesLogging.h"
#include "EntityItem.h"
#include "EntityItemProperties.h"
@ -2508,48 +2505,47 @@ QByteArray EntityItemProperties::getStaticCertificateHash() const {
return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256);
}
bool EntityItemProperties::verifyStaticCertificateProperties() {
// True IIF a non-empty certificateID matches the static certificate json.
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
// FIXME: This is largely copied from EntityItemProperties::verifyStaticCertificateProperties, which should be refactored to use this.
// I also don't like the nested-if style, but for this step I'm deliberately preserving the similarity.
bool EntityItemProperties::verifySignature(const QString& publicKey, const QByteArray& digestByteArray, const QByteArray& signatureByteArray) {
if (getCertificateID().isEmpty()) {
if (digestByteArray.isEmpty()) {
return false;
}
const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8();
const unsigned char* marketplacePublicKey = reinterpret_cast<const unsigned char*>(marketplacePublicKeyByteArray.constData());
int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length();
const unsigned char* key = reinterpret_cast<const unsigned char*>(publicKey.toUtf8().constData());
int keyLength = publicKey.length();
BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength);
BIO *bio = BIO_new_mem_buf((void*)key, keyLength);
EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
if (evp_key) {
RSA* rsa = EVP_PKEY_get1_RSA(evp_key);
if (rsa) {
const QByteArray digestByteArray = getStaticCertificateHash();
EC_KEY* ec = EVP_PKEY_get1_EC_KEY(evp_key);
if (ec) {
const unsigned char* digest = reinterpret_cast<const unsigned char*>(digestByteArray.constData());
int digestLength = digestByteArray.length();
const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8());
const unsigned char* signature = reinterpret_cast<const unsigned char*>(signatureByteArray.constData());
int signatureLength = signatureByteArray.length();
ERR_clear_error();
bool answer = RSA_verify(NID_sha256,
// ECSDA verification prototype: note that type is currently ignored
// int ECDSA_verify(int type, const unsigned char *dgst, int dgstlen,
// const unsigned char *sig, int siglen, EC_KEY *eckey);
bool answer = ECDSA_verify(0,
digest,
digestLength,
signature,
signatureLength,
rsa);
ec);
long error = ERR_get_error();
if (error != 0) {
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str
<< "\nStatic Cert JSON:" << getStaticCertificateJSON()
<< "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength
qCWarning(entities) << "ERROR while verifying signature! EC error:" << error_str
<< "\nKey:" << publicKey << "\nutf8 Key Length:" << keyLength
<< "\nDigest:" << digest << "\nDigest Length:" << digestLength
<< "\nSignature:" << signature << "\nSignature Length:" << signatureLength;
}
RSA_free(rsa);
EC_KEY_free(ec);
if (bio) {
BIO_free(bio);
}
@ -2566,7 +2562,7 @@ bool EntityItemProperties::verifyStaticCertificateProperties() {
}
long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
qCWarning(entities) << "Failed to verify signature! key" << publicKey << " EC key error:" << error_str;
return false;
}
} else {
@ -2575,7 +2571,13 @@ bool EntityItemProperties::verifyStaticCertificateProperties() {
}
long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
qCWarning(entities) << "Failed to verify signature! key" << publicKey << " EC PEM error:" << error_str;
return false;
}
}
bool EntityItemProperties::verifyStaticCertificateProperties() {
// True IFF a non-empty certificateID matches the static certificate json.
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
return verifySignature(EntityItem::_marketplacePublicKey, getStaticCertificateHash(), QByteArray::fromBase64(getCertificateID().toUtf8()));
}

View file

@ -339,6 +339,7 @@ public:
QByteArray getStaticCertificateJSON() const;
QByteArray getStaticCertificateHash() const;
bool verifyStaticCertificateProperties();
static bool verifySignature(const QString& key, const QByteArray& text, const QByteArray& signature);
protected:
QString getCollisionMaskAsString() const;

View file

@ -12,9 +12,7 @@
#include "EntityTree.h"
#include <QtCore/QDateTime>
#include <QtCore/QQueue>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <NetworkingConstants.h>
@ -1167,63 +1165,37 @@ void EntityTree::startPendingTransferStatusTimer(const QString& certID, const En
transferStatusRetryTimer->start(90000);
}
QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QString ownerKey) {
QString ownerKeyWithHeaders = ("-----BEGIN RSA PUBLIC KEY-----\n" + ownerKey + "\n-----END RSA PUBLIC KEY-----");
BIO* bio = BIO_new_mem_buf((void*)ownerKeyWithHeaders.toUtf8().constData(), -1);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); // NO NEWLINE
RSA* rsa = PEM_read_bio_RSAPublicKey(bio, NULL, NULL, NULL);
QByteArray EntityTree::computeNonce(const QString& certID, const QString ownerKey) {
QUuid nonce = QUuid::createUuid(); //random, 5-hex value, separated by "-"
QByteArray nonceBytes = nonce.toByteArray();
if (rsa) {
QUuid nonce = QUuid::createUuid();
const unsigned int textLength = nonce.toString().length();
QByteArray encryptedText(RSA_size(rsa), 0);
const int encryptStatus = RSA_public_encrypt(textLength,
reinterpret_cast<const unsigned char*>(qPrintable(nonce.toString())),
reinterpret_cast<unsigned char*>(encryptedText.data()),
rsa,
RSA_PKCS1_OAEP_PADDING);
if (bio) {
BIO_free(bio);
}
RSA_free(rsa);
if (encryptStatus == -1) {
long error = ERR_get_error();
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "Unable to compute encrypted nonce for" << certID << "\nRSA error:" << error_str;
return "";
}
QWriteLocker locker(&_certNonceMapLock);
_certNonceMap.insert(certID, QPair<QUuid, QString>(nonce, ownerKey));
QWriteLocker locker(&_certNonceMapLock);
_certNonceMap.insert(certID, nonce);
return encryptedText;
} else {
if (bio) {
BIO_free(bio);
}
return "";
}
return nonceBytes;
}
bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id) {
bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id) {
{
QReadLocker certIdMapLocker(&_entityCertificateIDMapLock);
id = _entityCertificateIDMap.value(certID);
}
QString actualNonce;
QString actualNonce, key;
{
QWriteLocker locker(&_certNonceMapLock);
actualNonce = _certNonceMap.take(certID).toString();
QPair<QUuid, QString> sent = _certNonceMap.take(certID);
actualNonce = sent.first.toString();
key = sent.second;
}
bool verificationSuccess = (actualNonce == decryptedNonce);
QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----";
bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), actualNonce.toUtf8(), nonce.toUtf8());
if (verificationSuccess) {
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded.";
} else {
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed."
<< "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce;
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed for nonce" << actualNonce << "key" << key << "signature" << nonce;
}
return verificationSuccess;
@ -1231,67 +1203,67 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr
void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
int certIDByteArraySize;
int encryptedTextByteArraySize;
int textByteArraySize;
int nodeToChallengeByteArraySize;
message.readPrimitive(&certIDByteArraySize);
message.readPrimitive(&encryptedTextByteArraySize);
message.readPrimitive(&textByteArraySize);
message.readPrimitive(&nodeToChallengeByteArraySize);
QByteArray certID(message.read(certIDByteArraySize));
QByteArray encryptedText(message.read(encryptedTextByteArraySize));
QByteArray text(message.read(textByteArraySize));
QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize));
sendChallengeOwnershipRequestPacket(certID, encryptedText, nodeToChallenge, sourceNode);
sendChallengeOwnershipRequestPacket(certID, text, nodeToChallenge, sourceNode);
}
void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
auto nodeList = DependencyManager::get<NodeList>();
int certIDByteArraySize;
int decryptedTextByteArraySize;
int textByteArraySize;
int challengingNodeUUIDByteArraySize;
message.readPrimitive(&certIDByteArraySize);
message.readPrimitive(&decryptedTextByteArraySize);
message.readPrimitive(&textByteArraySize);
message.readPrimitive(&challengingNodeUUIDByteArraySize);
QByteArray certID(message.read(certIDByteArraySize));
QByteArray decryptedText(message.read(decryptedTextByteArraySize));
QByteArray text(message.read(textByteArraySize));
QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize));
auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
certIDByteArraySize + decryptedText.length() + 2 * sizeof(int),
certIDByteArraySize + text.length() + 2 * sizeof(int),
true);
challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipReplyPacket->writePrimitive(decryptedText.length());
challengeOwnershipReplyPacket->writePrimitive(text.length());
challengeOwnershipReplyPacket->write(certID);
challengeOwnershipReplyPacket->write(decryptedText);
challengeOwnershipReplyPacket->write(text);
nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode)));
}
void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) {
// 1. Encrypt a nonce with the owner's public key
// 1. Obtain a nonce
auto nodeList = DependencyManager::get<NodeList>();
QByteArray encryptedText = computeEncryptedNonce(certID, ownerKey);
QByteArray text = computeNonce(certID, ownerKey);
if (encryptedText == "") {
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity...";
if (text == "") {
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute nonce. Deleting entity...";
deleteEntity(entityItemID, true);
} else {
qCDebug(entities) << "Challenging ownership of Cert ID" << certID;
// 2. Send the encrypted text to the rezzing avatar's node
// 2. Send the nonce to the rezzing avatar's node
QByteArray certIDByteArray = certID.toUtf8();
int certIDByteArraySize = certIDByteArray.size();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
certIDByteArraySize + encryptedText.length() + 2 * sizeof(int),
certIDByteArraySize + text.length() + 2 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(encryptedText.length());
challengeOwnershipPacket->writePrimitive(text.length());
challengeOwnershipPacket->write(certIDByteArray);
challengeOwnershipPacket->write(encryptedText);
challengeOwnershipPacket->write(text);
nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode);
// 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time
@ -1304,7 +1276,7 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri
}
}
void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) {
void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) {
auto nodeList = DependencyManager::get<NodeList>();
// In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants
@ -1312,17 +1284,17 @@ void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, c
QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122();
int certIDByteArraySize = certID.length();
int encryptedTextByteArraySize = encryptedText.length();
int TextByteArraySize = text.length();
int senderNodeUUIDSize = senderNodeUUID.length();
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
certIDByteArraySize + encryptedTextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int),
certIDByteArraySize + TextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int),
true);
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize);
challengeOwnershipPacket->writePrimitive(TextByteArraySize);
challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize);
challengeOwnershipPacket->write(certID);
challengeOwnershipPacket->write(encryptedText);
challengeOwnershipPacket->write(text);
challengeOwnershipPacket->write(senderNodeUUID);
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(QUuid::fromRfc4122(nodeToChallenge))));
@ -1391,18 +1363,18 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt
void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
int certIDByteArraySize;
int decryptedTextByteArraySize;
int textByteArraySize;
message.readPrimitive(&certIDByteArraySize);
message.readPrimitive(&decryptedTextByteArraySize);
message.readPrimitive(&textByteArraySize);
QString certID(message.read(certIDByteArraySize));
QString decryptedText(message.read(decryptedTextByteArraySize));
QString text(message.read(textByteArraySize));
emit killChallengeOwnershipTimeoutTimer(certID);
EntityItemID id;
if (!verifyDecryptedNonce(certID, decryptedText, id)) {
if (!verifyNonce(certID, text, id)) {
if (!id.isNull()) {
deleteEntity(id, true);
}

View file

@ -275,8 +275,8 @@ public:
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey);
bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id);
QByteArray computeNonce(const QString& certID, const QString ownerKey);
bool verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id);
void setMyAvatar(std::shared_ptr<AvatarData> myAvatar) { _myAvatar = myAvatar; }
@ -334,7 +334,7 @@ protected:
QHash<QString, EntityItemID> _entityCertificateIDMap;
mutable QReadWriteLock _certNonceMapLock;
QHash<QString, QUuid> _certNonceMap;
QHash<QString, QPair<QUuid, QString>> _certNonceMap;
EntitySimulationPointer _simulation;
@ -383,7 +383,7 @@ protected:
private:
void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode);
void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode);
void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation);
std::shared_ptr<AvatarData> _myAvatar{ nullptr };