Browse Source

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

Adam Ierymenko 11 years ago
parent
commit
a19c19c58c
4 changed files with 148 additions and 33 deletions
  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 "ui_installdialog.h"
 
 
 #include "../node/Defaults.hpp"
 #include "../node/Defaults.hpp"
+#include "../node/SoftwareUpdater.hpp"
 
 
 #include <QMainWindow>
 #include <QMainWindow>
 #include <QMessageBox>
 #include <QMessageBox>
@@ -12,7 +13,8 @@
 InstallDialog::InstallDialog(QWidget *parent) :
 InstallDialog::InstallDialog(QWidget *parent) :
 	QDialog(parent),
 	QDialog(parent),
 	ui(new Ui::InstallDialog),
 	ui(new Ui::InstallDialog),
-	nam(new QNetworkAccessManager(this))
+	nam(new QNetworkAccessManager(this)),
+	phase(FETCHING_NFO)
 {
 {
 	ui->setupUi(this);
 	ui->setupUi(this);
 	QObject::connect(nam,SIGNAL(finished(QNetworkReply*)),this,SLOT(on_networkReply(QNetworkReply*)));
 	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) {
 	if (reply->error() != QNetworkReply::NoError) {
 		QMessageBox::critical(this,"Download Failed",QString("Download failed: ") + reply->errorString(),QMessageBox::Ok,QMessageBox::NoButton);
 		QMessageBox::critical(this,"Download Failed",QString("Download failed: ") + reply->errorString(),QMessageBox::Ok,QMessageBox::NoButton);
 		QApplication::exit(1);
 		QApplication::exit(1);
-		return;
 	} else {
 	} else {
 		if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
 		if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) {
 			QByteArray installerData(reply->readAll());
 			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 {
 		} else {
 			QMessageBox::critical(this,"Download Failed",QString("Download failed: HTTP status code ") + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString(),QMessageBox::Ok,QMessageBox::NoButton);
 			QMessageBox::critical(this,"Download Failed",QString("Download failed: HTTP status code ") + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString(),QMessageBox::Ok,QMessageBox::NoButton);
 			QApplication::exit(1);
 			QApplication::exit(1);
-			return;
 		}
 		}
 	}
 	}
 }
 }

+ 11 - 0
ZeroTierUI/installdialog.h

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

+ 59 - 28
node/SoftwareUpdater.cpp

@@ -29,6 +29,8 @@
 #include <stdlib.h>
 #include <stdlib.h>
 #include <string.h>
 #include <string.h>
 
 
+#include <stdexcept>
+
 #include "../version.h"
 #include "../version.h"
 
 
 #include "SoftwareUpdater.hpp"
 #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)
 void SoftwareUpdater::_cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,bool onDisk,const std::string &body)
 {
 {
 	SoftwareUpdater *upd = (SoftwareUpdater *)arg;
 	SoftwareUpdater *upd = (SoftwareUpdater *)arg;
@@ -90,35 +134,30 @@ void SoftwareUpdater::_cbHandleGetLatestVersionInfo(void *arg,int code,const std
 	}
 	}
 
 
 	try {
 	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;
 			upd->_status = UPDATE_STATUS_IDLE;
 			return;
 			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;
 			upd->_status = UPDATE_STATUS_IDLE;
 			return;
 			return;
 		}
 		}
+
+#ifndef ZT_ALWAYS_UPDATE /* for testing */
 		if (packVersion(vMajor,vMinor,vRevision) <= upd->_myVersion) {
 		if (packVersion(vMajor,vMinor,vRevision) <= upd->_myVersion) {
 			LOG("software update aborted: .nfo file invalid: version on web site <= my version");
 			LOG("software update aborted: .nfo file invalid: version on web site <= my version");
 			upd->_status = UPDATE_STATUS_IDLE;
 			upd->_status = UPDATE_STATUS_IDLE;
 			return;
 			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->_status = UPDATE_STATUS_GETTING_FILE;
 		upd->_signedBy = signedBy;
 		upd->_signedBy = signedBy;
@@ -137,16 +176,8 @@ void SoftwareUpdater::_cbHandleGetLatestVersionBinary(void *arg,int code,const s
 	const RuntimeEnvironment *_r = (const RuntimeEnvironment *)upd->_r;
 	const RuntimeEnvironment *_r = (const RuntimeEnvironment *)upd->_r;
 	Mutex::Lock _l(upd->_lock);
 	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;
 		upd->_status = UPDATE_STATUS_IDLE;
 		return;
 		return;
 	}
 	}

+ 46 - 0
node/SoftwareUpdater.hpp

@@ -30,11 +30,14 @@
 
 
 #include <stdint.h>
 #include <stdint.h>
 
 
+#include <string>
+
 #include "Constants.hpp"
 #include "Constants.hpp"
 #include "Mutex.hpp"
 #include "Mutex.hpp"
 #include "Utils.hpp"
 #include "Utils.hpp"
 #include "HttpClient.hpp"
 #include "HttpClient.hpp"
 #include "Defaults.hpp"
 #include "Defaults.hpp"
+#include "Address.hpp"
 
 
 namespace ZeroTier {
 namespace ZeroTier {
 
 
@@ -115,6 +118,49 @@ public:
 		return ( ((uint64_t)(vmaj & 0xffff) << 32) | ((uint64_t)(vmin & 0xffff) << 16) | (uint64_t)(rev & 0xffff) );
 		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:
 private:
 	static void _cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,bool onDisk,const std::string &body);
 	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);
 	static void _cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,bool onDisk,const std::string &body);