Explorar o código

Refactor SoftwareUpdate to make .nfo parse and signature check code easily reusable so it can be used from the Qt GUI code.

Adam Ierymenko %!s(int64=11) %!d(string=hai) anos
pai
achega
a19c19c58c
Modificáronse 4 ficheiros con 148 adicións e 33 borrados
  1. 32 5
      ZeroTierUI/installdialog.cpp
  2. 11 0
      ZeroTierUI/installdialog.h
  3. 59 28
      node/SoftwareUpdater.cpp
  4. 46 0
      node/SoftwareUpdater.hpp

+ 32 - 5
ZeroTierUI/installdialog.cpp

@@ -3,6 +3,7 @@
 #include "ui_installdialog.h"
 
 #include "../node/Defaults.hpp"
+#include "../node/SoftwareUpdater.hpp"
 
 #include <QMainWindow>
 #include <QMessageBox>
@@ -12,7 +13,8 @@
 InstallDialog::InstallDialog(QWidget *parent) :
 	QDialog(parent),
 	ui(new Ui::InstallDialog),
-	nam(new QNetworkAccessManager(this))
+	nam(new QNetworkAccessManager(this)),
+	phase(FETCHING_NFO)
 {
 	ui->setupUi(this);
 	QObject::connect(nam,SIGNAL(finished(QNetworkReply*)),this,SLOT(on_networkReply(QNetworkReply*)));
@@ -40,16 +42,41 @@ void InstallDialog::on_networkReply(QNetworkReply *reply)
 	if (reply->error() != QNetworkReply::NoError) {
 		QMessageBox::critical(this,"Download Failed",QString("Download failed: ") + reply->errorString(),QMessageBox::Ok,QMessageBox::NoButton);
 		QApplication::exit(1);
-		return;
 	} else {
 		if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
 			QByteArray installerData(reply->readAll());
-			installerData.append((char)0);
-			printf("%s\n",installerData.data());
+
+			switch(phase) {
+				case FETCHING_NFO: {
+					unsigned int vMajor = 0,vMinor = 0,vRevision = 0;
+					installerData.append((char)0);
+					const char *err = ZeroTier::SoftwareUpdater::parseNfo(installerData.data(),vMajor,vMinor,vRevision,signedBy,signature,url);
+
+					if (err) {
+						QMessageBox::critical(this,"Download Failed","Download failed: there is a problem with the software update web site.\nTry agian later. (invalid .nfo file)",QMessageBox::Ok,QMessageBox::NoButton);
+						QApplication::exit(1);
+						return;
+					}
+
+					phase = FETCHING_INSTALLER;
+					reply = nam->get(QNetworkRequest(QUrl(url.c_str())));
+					QObject::connect(reply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(on_downloadProgress(qint64,qint64)));
+				}	break;
+				case FETCHING_INSTALLER: {
+					if (!ZeroTier::SoftwareUpdater::validateUpdate(installerData.data(),installerData.length(),signedBy,signature)) {
+						QMessageBox::critical(this,"Download Failed","Download failed: there is a problem with the software update web site.\nTry agian later. (failed signature check)",QMessageBox::Ok,QMessageBox::NoButton);
+						QApplication::exit(1);
+						return;
+					}
+				}	break;
+			}
+
+			ui->progressBar->setMinimum(0);
+			ui->progressBar->setMaximum(100);
+			ui->progressBar->setValue(0);
 		} else {
 			QMessageBox::critical(this,"Download Failed",QString("Download failed: HTTP status code ") + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString(),QMessageBox::Ok,QMessageBox::NoButton);
 			QApplication::exit(1);
-			return;
 		}
 	}
 }

+ 11 - 0
ZeroTierUI/installdialog.h

@@ -7,6 +7,10 @@
 #include <QNetworkRequest>
 #include <QNetworkReply>
 
+#include <string>
+
+#include "../node/Address.hpp"
+
 namespace Ui {
 class InstallDialog;
 }
@@ -28,6 +32,13 @@ private slots:
 private:
 	Ui::InstallDialog *ui;
 	QNetworkAccessManager *nam;
+	enum {
+		FETCHING_NFO,
+		FETCHING_INSTALLER
+	} phase;
+
+	ZeroTier::Address signedBy;
+	std::string url,signature;
 };
 
 #endif // INSTALLDIALOG_H

+ 59 - 28
node/SoftwareUpdater.cpp

@@ -29,6 +29,8 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <stdexcept>
+
 #include "../version.h"
 
 #include "SoftwareUpdater.hpp"
@@ -72,6 +74,48 @@ SoftwareUpdater::~SoftwareUpdater()
 	}
 }
 
+const char *SoftwareUpdater::parseNfo(
+	const char *nfoText,
+	unsigned int &vMajor,
+	unsigned int &vMinor,
+	unsigned int &vRevision,
+	Address &signedBy,
+	std::string &signature,
+	std::string &url)
+{
+	try {
+		Dictionary nfo(nfoText);
+
+		vMajor = Utils::strToUInt(nfo.get("vMajor").c_str());
+		vMinor = Utils::strToUInt(nfo.get("vMinor").c_str());
+		vRevision = Utils::strToUInt(nfo.get("vRevision").c_str());
+		signedBy = nfo.get("signedBy");
+		signature = Utils::unhex(nfo.get("ed25519"));
+		url = nfo.get("url");
+
+		if (signature.length() != ZT_C25519_SIGNATURE_LEN)
+			return "bad ed25519 signature, invalid length";
+		if ((url.length() <= 7)||(url.substr(0,7) != "http://"))
+			return "invalid URL, must begin with http://";
+
+		return (const char *)0;
+	} catch ( ... ) {
+		return "invalid NFO file format or one or more required fields missing";
+	}
+}
+
+bool SoftwareUpdater::validateUpdate(
+	const void *data,
+	unsigned int len,
+	const Address &signedBy,
+	const std::string &signature)
+{
+	std::map< Address,Identity >::const_iterator updateAuthority = ZT_DEFAULTS.updateAuthorities.find(signedBy);
+	if (updateAuthority == ZT_DEFAULTS.updateAuthorities.end())
+		return false;
+	return updateAuthority->second.verify(data,len,signature.data(),signature.length());
+}
+
 void SoftwareUpdater::_cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,bool onDisk,const std::string &body)
 {
 	SoftwareUpdater *upd = (SoftwareUpdater *)arg;
@@ -90,35 +134,30 @@ void SoftwareUpdater::_cbHandleGetLatestVersionInfo(void *arg,int code,const std
 	}
 
 	try {
-		Dictionary nfo(body);
-		const unsigned int vMajor = Utils::strToUInt(nfo.get("vMajor").c_str());
-		const unsigned int vMinor = Utils::strToUInt(nfo.get("vMinor").c_str());
-		const unsigned int vRevision = Utils::strToUInt(nfo.get("vRevision").c_str());
-		const Address signedBy(nfo.get("signedBy"));
-		const std::string signature(Utils::unhex(nfo.get("ed25519")));
-		const std::string &url = nfo.get("url");
-
-		if (signature.length() != ZT_C25519_SIGNATURE_LEN) {
-			LOG("software update aborted: .nfo file invalid: bad ed25519 ECC signature field");
+		unsigned int vMajor = 0,vMinor = 0,vRevision = 0;
+		Address signedBy;
+		std::string signature,url;
+
+		const char *err = parseNfo(body.c_str(),vMajor,vMinor,vRevision,signedBy,signature,url);
+
+		if (err) {
+			LOG("software update aborted: .nfo file error: %s",err);
 			upd->_status = UPDATE_STATUS_IDLE;
 			return;
 		}
-		if ((url.length() <= 7)||(url.substr(0,7) != "http://")) {
-			LOG("software update aborted: .nfo file invalid: update URL must begin with http://");
+		if (!ZT_DEFAULTS.updateAuthorities.count(signedBy)) {
+			LOG("software update aborted: .nfo file specifies unknown signing authority");
 			upd->_status = UPDATE_STATUS_IDLE;
 			return;
 		}
+
+#ifndef ZT_ALWAYS_UPDATE /* for testing */
 		if (packVersion(vMajor,vMinor,vRevision) <= upd->_myVersion) {
 			LOG("software update aborted: .nfo file invalid: version on web site <= my version");
 			upd->_status = UPDATE_STATUS_IDLE;
 			return;
 		}
-
-		if (!ZT_DEFAULTS.updateAuthorities.count(signedBy)) {
-			LOG("software update aborted: .nfo file specifies unknown signing authority");
-			upd->_status = UPDATE_STATUS_IDLE;
-			return;
-		}
+#endif
 
 		upd->_status = UPDATE_STATUS_GETTING_FILE;
 		upd->_signedBy = signedBy;
@@ -137,16 +176,8 @@ void SoftwareUpdater::_cbHandleGetLatestVersionBinary(void *arg,int code,const s
 	const RuntimeEnvironment *_r = (const RuntimeEnvironment *)upd->_r;
 	Mutex::Lock _l(upd->_lock);
 
-	std::map< Address,Identity >::const_iterator updateAuthority = ZT_DEFAULTS.updateAuthorities.find(upd->_signedBy);
-	if (updateAuthority == ZT_DEFAULTS.updateAuthorities.end()) { // sanity check, shouldn't happen
-		LOG("software update aborted: .nfo file specifies unknown signing authority");
-		upd->_status = UPDATE_STATUS_IDLE;
-		return;
-	}
-
-	// The all-important authenticity check... :)
-	if (!updateAuthority->second.verify(body.data(),body.length(),upd->_signature.data(),upd->_signature.length())) {
-		LOG("software update aborted: update fetched from '%s' failed certificate check against signer %s",url.c_str(),updateAuthority->first.toString().c_str());
+	if (!validateUpdate(body.data(),body.length(),upd->_signedBy,upd->_signature)) {
+		LOG("software update aborted: update fetched from '%s' failed signature check (got %u bytes)",url.c_str(),(unsigned int)body.length());
 		upd->_status = UPDATE_STATUS_IDLE;
 		return;
 	}

+ 46 - 0
node/SoftwareUpdater.hpp

@@ -30,11 +30,14 @@
 
 #include <stdint.h>
 
+#include <string>
+
 #include "Constants.hpp"
 #include "Mutex.hpp"
 #include "Utils.hpp"
 #include "HttpClient.hpp"
 #include "Defaults.hpp"
+#include "Address.hpp"
 
 namespace ZeroTier {
 
@@ -115,6 +118,49 @@ public:
 		return ( ((uint64_t)(vmaj & 0xffff) << 32) | ((uint64_t)(vmin & 0xffff) << 16) | (uint64_t)(rev & 0xffff) );
 	}
 
+	/**
+	 * Parse NFO data from .nfo file on software update site
+	 *
+	 * The first argument is the NFO data, and all the remaining arguments are
+	 * result parameters to be filled with results. If an error is returned the
+	 * results in the parameters should be considered undefined.
+	 *
+	 * @param nfo NFO data
+	 * @param vMajor Result: major version
+	 * @param vMinor Result: minor version
+	 * @param vRevision Result: revision number
+	 * @param signedBy Result: signing identity
+	 * @param signature Result: Ed25519 signature data
+	 * @param url Result: URL of update binary
+	 * @return NULL on success or error message on failure
+	 */
+	static const char *parseNfo(
+		const char *nfoText,
+		unsigned int &vMajor,
+		unsigned int &vMinor,
+		unsigned int &vRevision,
+		Address &signedBy,
+		std::string &signature,
+		std::string &url);
+
+	/**
+	 * Validate an update once downloaded
+	 *
+	 * This obtains the identity corresponding to the address from the compiled-in
+	 * list of valid signing identities.
+	 *
+	 * @param data Update data
+	 * @param len Length of update data
+	 * @param signedBy Signing authority address
+	 * @param signature Signing authority signature
+	 * @return True on validation success, false if rejected
+	 */
+	static bool validateUpdate(
+		const void *data,
+		unsigned int len,
+		const Address &signedBy,
+		const std::string &signature);
+
 private:
 	static void _cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,bool onDisk,const std::string &body);
 	static void _cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,bool onDisk,const std::string &body);