Developer Documentation
LicenseManagerActive.cc
1/*===========================================================================*\
2* *
3* OpenFlipper *
4 * Copyright (c) 2001-2015, RWTH-Aachen University *
5 * Department of Computer Graphics and Multimedia *
6 * All rights reserved. *
7 * www.openflipper.org *
8 * *
9 *---------------------------------------------------------------------------*
10 * This file is part of OpenFlipper. *
11 *---------------------------------------------------------------------------*
12 * *
13 * Redistribution and use in source and binary forms, with or without *
14 * modification, are permitted provided that the following conditions *
15 * are met: *
16 * *
17 * 1. Redistributions of source code must retain the above copyright notice, *
18 * this list of conditions and the following disclaimer. *
19 * *
20 * 2. Redistributions in binary form must reproduce the above copyright *
21 * notice, this list of conditions and the following disclaimer in the *
22 * documentation and/or other materials provided with the distribution. *
23 * *
24 * 3. Neither the name of the copyright holder nor the names of its *
25 * contributors may be used to endorse or promote products derived from *
26 * this software without specific prior written permission. *
27 * *
28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
29 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
30 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
31 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER *
32 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
33 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
34 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
35 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
36 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
37 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
38 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
39* *
40\*===========================================================================*/
41
42
43/*
44License File format:
45
460: Signature over all other entries
471: Expiry date
482: Plugin filename
493: coreHash
504: pluginHash
515: cpuHash
526: windowsProductId (Windows only otherwise filled with "-" before hashing)
537..?: mac Address hashes
54
55*/
56
57
58// Windows required to get network address infos
59#ifdef WIN32
60 #include <winsock2.h>
61 #include <iphlpapi.h>
62 #pragma comment(lib, "IPHLPAPI.lib")
63#endif
64
65#ifdef ARCH_DARWIN
66 #include <sys/types.h>
67 #include <sys/sysctl.h>
68#endif
69
70
71#include <OpenFlipper/LicenseManager/LicenseManagerActive.hh>
73#include <QFile>
74#include <QString>
75#include <QCryptographicHash>
76#include <QNetworkInterface>
77#include <QTime>
78
79#include <limits>
80
81
90QByteArray decodeString(const QString& _string ,bool _utf8){
91
92 if (_utf8)
93 return _string.toUtf8();
94 else
95 return _string.toLatin1();
96
97}
98
99LicenseManager::~LicenseManager()
100{
101 exit(0);
102}
103
104LicenseManager::LicenseManager()
105{
106
107 authenticated_ = false;
108
109 // On startup, block all signals by default until the plugin is authenticated!
110 QObject::blockSignals( true );
111}
112
113// Override default block signals. Transparent if authenticated, otherwise
114// the function will always block the signals automatically
116
117 if ( !authenticated() ) {
118 QObject::blockSignals( true );
119 } else {
120 QObject::blockSignals(_state);
121 }
122
123}
124
125
126bool LicenseManager::timestampOk() {
127
128 bool notExpired = false;
129 bool gotTimestampEntry = false;
130
131 quint64 timestamp = QDateTime::currentMSecsSinceEpoch();
132 quint64 lastTimestamp = timestamp;
133 quint64 timestampEntry = 0;
134 quint64 lastTimestampEntryNum = 0;
135
136
137 // ===============================================================================================
138 // Read last Timestemp
139 // ===============================================================================================
140
141 const QString title = "Timestamp/"+pluginFileName();
142
143 QString lastTimestampEntry = OpenFlipperSettings().value(title,"empty").toString();
144
145 if( lastTimestampEntry==QString("empty") ){
146 notExpired = true;
147 } else {
148 lastTimestampEntryNum = lastTimestampEntry.toULongLong(&gotTimestampEntry,16);
149 }
150
151 // ===============================================================================================
152 // Decrypt last Timestamp
153 // ===============================================================================================
154
155 const unsigned int factor = 30000;
156 const std::string name = pluginFileName().toStdString();
157 const unsigned int moduloFactor = 72; //<100
158 const int nameSize = pluginFileName().size();
159 const int nameEncr = ( name[0] ) + ( name[nameSize-1] ) + (name[(nameSize-1) / 2] );
160
161 bool moduloOK = (lastTimestampEntryNum -( 100 * (lastTimestampEntryNum / 100) ) == (lastTimestampEntryNum/100) % moduloFactor );
162
163 lastTimestampEntryNum = lastTimestampEntryNum / 100;
164 lastTimestampEntryNum = lastTimestampEntryNum - nameEncr;
165 lastTimestampEntryNum = lastTimestampEntryNum * factor;
166
167 lastTimestamp = lastTimestampEntryNum;
168
169 // ===============================================================================================
170 // Check last Timestemp
171 // ===============================================================================================
172
173 if (notExpired||(timestamp>(lastTimestamp-360000) && moduloOK)) {
174 notExpired = true;
175 } else {
176 timestamp = std::numeric_limits<quint64>::max();
177 }
178
179
180 // ===============================================================================================
181 // Encrypt new Timestamp
182 // ===============================================================================================
183
184 timestampEntry = timestamp;
185
186 timestampEntry = timestampEntry / factor;
187 timestampEntry = timestampEntry + nameEncr;
188 timestampEntry = (timestampEntry * 100) + (timestampEntry % moduloFactor);
189
190
191 // ===============================================================================================
192 // Write new Timestemp
193 // ===============================================================================================
194
195 OpenFlipperSettings().setValue ( title, QString::number(timestampEntry,16) );
196
197 return notExpired;
198
199}
200
201
202// Plugin authentication function.
204
205 // Construct license string (will be cleaned up if license valid)
206 authstring_ = "==\n";
207 authstring_ += "PluginName: " + pluginFileName() + "\n";
208
209 // ===============================================================================================
210 // Read License file, if exists
211 // ===============================================================================================
212 QString saltPre;
213 ADD_SALT_PRE(saltPre);
214
215 QString saltPost;
216 ADD_SALT_POST(saltPost);
217
218 QString licenseFileName = OpenFlipper::Options::licenseDirStr() + QDir::separator() + pluginFileName() + ".lic";
219 QFile file( licenseFileName );
220 QStringList elements; //has no elements, if file is invalid or was not found
221 bool signatureOk = false;
222
223 bool utf8Encoded = true;
224
225 if (file.open(QIODevice::ReadOnly|QIODevice::Text))
226 {
227 QString licenseContents = file.readAll();
228 elements = licenseContents.split('\n',QString::SkipEmptyParts);
229 bool fileOk = !elements.empty() && elements[0] != "ERROR";
230
231 if (fileOk)
232 {
233 // simplify license file entries
234 for ( int i = 0 ; i < elements.size(); ++i )
235 elements[i] = elements[i].simplified();
236
237 // Check the signature of the file (excluding first element as this is the signature itself)
238 QString license = saltPre;
239 for ( int i = 1 ; i < elements.size(); ++i )
240 license += elements[i];
241 license += saltPost;
242 QString licenseHash = QCryptographicHash::hash ( license.toUtf8() , QCryptographicHash::Sha1 ).toHex();
243 signatureOk = licenseHash == elements[0];
244
245 if (signatureOk)
246 utf8Encoded = true;
247 else
248 {
249 licenseHash = QCryptographicHash::hash ( license.toLatin1() , QCryptographicHash::Sha1 ).toHex();
250 signatureOk = licenseHash == elements[0];
251 if (signatureOk)
252 utf8Encoded = false;
253 }
254
255 }
256 else
257 elements = QStringList();
258 }
259
260 // ===============================================================================================
261 // Compute hash value of Core application binary
262 // ===============================================================================================
263
264 #ifdef WIN32
265 QFile coreApp(OpenFlipper::Options::applicationDirStr() + QDir::separator() + "OpenFlipper.exe");
266 #elif defined ARCH_DARWIN
267 QFile coreApp(OpenFlipper::Options::applicationDirStr() + QDir::separator() + ".." +
268 QDir::separator() + "MacOS"+
269 QDir::separator() + "OpenFlipper");
270 #else
271 QFile coreApp(OpenFlipper::Options::applicationDirStr() + QDir::separator() + "bin" + QDir::separator() + TOSTRING(PRODUCT_STRING));
272 #endif
273
274 if ( ! coreApp.exists() ) {
275 std::cerr << "Error finding core application for security check! : " << coreApp.fileName().toStdString() << std::endl;
276 return false;
277 }
278
279 coreApp.open(QIODevice::ReadOnly);
280 QCryptographicHash sha1sumCore( QCryptographicHash::Sha1 );
281 sha1sumCore.addData(coreApp.readAll() );
282 coreApp.close();
283
284 QString coreHash = QString(sha1sumCore.result().toHex());
285
286
287 // ===============================================================================================
288 // Compute hash of Plugin binary
289 // ===============================================================================================
290
291 #ifdef WIN32
292 QFile pluginFile(OpenFlipper::Options::pluginDirStr() + QDir::separator() + pluginFileName() + ".dll");
293 #elif defined ARCH_DARWIN
294 QFile pluginFile(OpenFlipper::Options::pluginDirStr() + QDir::separator() + "lib" + pluginFileName() + ".so");
295 #else
296 QFile pluginFile(OpenFlipper::Options::pluginDirStr() + QDir::separator() + "lib" + pluginFileName() + ".so");
297 #endif
298
299 if ( ! pluginFile.exists() ) {
300 std::cerr << "Error finding plugin file for security check!" << std::endl;
301 std::cerr << "Path: " << pluginFile.fileName().toStdString() << std::endl;
302 return false;
303 }
304
305 pluginFile.open(QIODevice::ReadOnly);
306 QCryptographicHash sha1sumPlugin( QCryptographicHash::Sha1 );
307 sha1sumPlugin.addData(pluginFile.readAll());
308 pluginFile.close();
309
310 QString pluginHash = QString(sha1sumPlugin.result().toHex());
311
312 // ===============================================================================================
313 // Compute hash of network interfaces
314 // ===============================================================================================
315
316 QStringList macHashes;
317
318#ifdef WIN32
319
320 #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
321 #define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
322
323 // Pointer for iterating over adapters
324 PIP_ADAPTER_ADDRESSES pAddresses = NULL;
325
326 // Length of the buffer to get information
327 ULONG outBufLen = 0;
328
329 // Allocate enough for one info struct
330 outBufLen = sizeof (IP_ADAPTER_ADDRESSES);
331 pAddresses = (IP_ADAPTER_ADDRESSES *) MALLOC(outBufLen);
332
333 // default to unspecified address family ( get all interfaces .. 4 and 6)
334 ULONG family = AF_UNSPEC;
335
336 // Set the flags to pass to GetAdaptersAddresses
337 ULONG flags = GAA_FLAG_INCLUDE_PREFIX;
338
339 // Make an initial call to GetAdaptersAddresses to get the
340 // size needed into the outBufLen variable
341 if (GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) {
342 FREE(pAddresses);
343 pAddresses = (IP_ADAPTER_ADDRESSES *) MALLOC(outBufLen);
344 }
345
346 // If we allocated the required memory
347 if (pAddresses != NULL) {
348
349 // Get the required info
350 DWORD dwRetVal = GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen);
351
352 if (dwRetVal == NO_ERROR) {
353
354 // pointer to iterate over all available structs .. initialize to first one
355 PIP_ADAPTER_ADDRESSES pCurrAddresses = pAddresses;
356
357 while (pCurrAddresses) {
358
359 // Check if this device contains a mac
360 if (pCurrAddresses->PhysicalAddressLength != 0) {
361 QString currentMac = "";
362
363 for (uint i = 0; i < pCurrAddresses->PhysicalAddressLength; i++) {
364
365 currentMac += QString("%1").arg( (int) pCurrAddresses->PhysicalAddress[i] , 2 ,16,QChar('0'));
366
367 if (i != (pCurrAddresses->PhysicalAddressLength - 1))
368 currentMac +=":";
369 }
370
371 // Ignore non ethernet macs with more than 5 blocks
372 // Only check ethernet and wireless interfaces
373 if ( (currentMac.count(":") == 5) &&
374 ( pCurrAddresses->IfType == IF_TYPE_IEEE80211 || pCurrAddresses->IfType == IF_TYPE_ETHERNET_CSMACD ) ) {
375 // Cleanup and remember mac adress
376
377 currentMac = (decodeString(currentMac,utf8Encoded)).toUpper();
378 currentMac = currentMac.remove(":");
379 macHashes.push_back(currentMac);
380 }
381
382 }
383
384 // Next interface
385 pCurrAddresses = pCurrAddresses->Next;
386 }
387
388 }
389
390 }
391
392 FREE(pAddresses);
393
394#else
395
396 // Get all Network Interfaces
397 QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
398 foreach ( QNetworkInterface netInterface, interfaces ) {
399
400 // Ignore loopback interfaces
401 if ( ( netInterface.flags() & QNetworkInterface::IsLoopBack ) ) {
402 continue;
403 }
404
405 // Ignore non ethernet macs
406 if ( netInterface.hardwareAddress().count(":") != 5 ) {
407 continue;
408 }
409
410 // Cleanup mac adress
411 QString currentMac = (decodeString(netInterface.hardwareAddress(),utf8Encoded)).toUpper();
412 currentMac = currentMac.remove(":");
413
414 macHashes.push_back(currentMac);
415 }
416
417#endif
418
419
420 // cleanup the list from duplicates (virtual interfaces on windows connected to an existing device ... )
421 macHashes.removeDuplicates();
422
423 // generate hashes
424 for (int i = 0 ; i < macHashes.size(); ++i )
425 // Cleanup mac adress
426 macHashes[i] = QCryptographicHash::hash ( decodeString(macHashes[i],utf8Encoded) , QCryptographicHash::Sha1 ).toHex();
427
428 // ===============================================================================================
429 // Compute hash of processor information
430 // ===============================================================================================
431
432 QString processor("Unknown");
433
434 #ifdef WIN32
435 QSettings registryCPU("HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor", QSettings::NativeFormat);
436
437 QStringList cpus = registryCPU.childGroups();
438 if ( cpus.size() != 0 ) {
439 processor = registryCPU.value( cpus[0]+"/ProcessorNameString", "Unknown" ).toString();
440 }
441
442 #elif defined ARCH_DARWIN
443
444 size_t lenCPU;
445 char *pCPU;
446
447 // First call to get required size
448 sysctlbyname("machdep.cpu.brand_string", NULL, &lenCPU, NULL, 0);
449
450 // allocate
451 pCPU = (char * )malloc(lenCPU);
452
453 // Second call to get data
454 sysctlbyname("machdep.cpu.brand_string", pCPU, &lenCPU, NULL, 0);
455
456 // Output
457 processor = QString(pCPU);
458
459 // free memory
460 free(pCPU);
461
462 #else
463 QFile cpuinfo("/proc/cpuinfo");
464 if ( cpuinfo.exists() ) {
465 cpuinfo.open(QFile::ReadOnly);
466 QTextStream stream(&cpuinfo);
467 QStringList splitted = stream.readAll().split("\n",QString::SkipEmptyParts);
468
469 int position = splitted.indexOf ( QRegExp("^model name.*") );
470 if ( position != -1 ){
471 QString cpuModel = splitted[position].section(':', -1).simplified();
472 processor = cpuModel;
473 }
474 }
475 #endif
476
477 QString cpuHash = QCryptographicHash::hash ( decodeString(processor,utf8Encoded) , QCryptographicHash::Sha1 ).toHex();
478
479 // ===============================================================================================
480 // Get windows product id
481 // ===============================================================================================
482
483
484
485 #ifdef WIN32
486 QSettings registryProduct("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", QSettings::NativeFormat);
487 QString productId = registryProduct.value( "ProductId", "Unknown" ).toString();
488 #else
489 QString productId = "-";
490 #endif
491
492 QString productHash = QCryptographicHash::hash ( decodeString(productId,utf8Encoded) , QCryptographicHash::Sha1 ).toHex();
493
494 // ===============================================================================================
495 // Check License or generate request
496 // ===============================================================================================
497
498 if (!elements.empty()) //valid file was found
499 {
500
501 // Check expiry date
502 QDate currentDate = QDate::currentDate();
503 QDate expiryDate = QDate::fromString(elements[1],Qt::ISODate);
504 bool expired = (currentDate > expiryDate);
505 // Get number of available mac adresses
506 QStringList licensedMacs;
507 for ( int i = 7 ; i < elements.size(); ++i ) {
508 licensedMacs.push_back(elements[i]);
509 }
510
511 bool macFound = false;
512 for ( int i = 0; i < macHashes.size(); ++i ) {
513 if ( licensedMacs.contains(macHashes[i]) )
514 macFound = true;
515 }
516
517 if ( !signatureOk ) {
518 authstring_ += tr("License Error: The license file signature for Plugin \"") + name() + tr("\" is invalid!\n\n");
519 } else if ( expired ) {
520 authstring_ += tr("License Error: The license for plugin \"") + name() + tr("\" has expired on ") + elements[1] + "!\n\n";
521 } else if ( !timestampOk() ) {
522 authstring_ += tr("License Error: System time has been reset. The license for plugin \"") + name() + tr("\" has been expired!\n\n");
523 } else if ( elements[2] != pluginFileName() ) {
524 authstring_ += tr("License Error: The license file contains plugin name\"") + elements[2] + tr("\" but this is plugin \"") + name() + "\"!\n\n";
525 } else if ( elements[3] != coreHash ) {
526 authstring_ += tr("License Error: The license file for plugin \"") + name() + tr("\" is invalid for the currently running OpenFlipper Core!\n\n");
527 } else if ( elements[4] != pluginHash ) {
528 authstring_ += tr("License Error: The plugin \"") + name() + tr("\" is a different version than specified in license file!\n\n");
529 } else if ( elements[5] != cpuHash ) {
530 authstring_ += "License Error: The plugin \"" + name() + "\" is not allowed to run on the current system (Changed CPU?)!\n\n";
531 } else if ( elements[6] != productHash ) {
532 authstring_ += "License Error: The plugin \"" + name() + "\" is not allowed to run on the current system (Changed OS?)!\n\n";
533 } else if ( !macFound ) {
534 authstring_ += "License Error: The plugin \"" + name() + "\" is not allowed to run on the current system (Changed Network?)!\n\n";
535 } else {
536 authenticated_ = true;
537 }
538
539 // Clean it on success
540 if ( authenticated_ )
541 authstring_ = "";
542
543 }
544
545 if ( authenticated_ ) {
546 blockSignals(false);
547 } else {
548 authstring_ += tr("Message: License check for plugin failed.\n");
549 authstring_ += tr("Message: Please get a valid License!\n");
550 authstring_ += tr("Message: Send the following Information to \n");
551 authstring_ += tr("Contact mail: ") + CONTACTMAIL + "\n\n";
552 authstring_ += pluginFileName() +"\n";
553 authstring_ += coreHash +"\n";
554 authstring_ += pluginHash +"\n";
555 authstring_ += cpuHash +"\n";
556 authstring_ += productHash +"\n";
557
558 for ( int i = 0 ; i < macHashes.size(); ++i )
559 authstring_ += macHashes[i] +"\n";
560
561 QString keyRequest = saltPre + pluginFileName() + coreHash + pluginHash + cpuHash + productHash + macHashes.join("") + saltPost;
562 QString requestSig = QCryptographicHash::hash ( keyRequest.toUtf8() , QCryptographicHash::Sha1 ).toHex();
563 authstring_ += requestSig + "\n";
564
565 authenticated_ = false;
566 }
567
568 return authenticated_;
569}
570
572 return authstring_;
573}
574
576 // Function to check if the plugin is authenticated
577 return authenticated_;
578}
579
580void LicenseManager::connectNotify ( const char * /*signal*/ ) {
581
582 // if the plugin is not authenticated and something wants to connect, we block all signals and force a direct disconnect
583 // here, rendering all signal/slot connections useless.
584 if ( !authenticated() ) {
585 blockSignals(true);
586 disconnect();
587 }
588
589}
590
592 // FileName of the plugin. has to be set via the salt file
593 QString pluginFileName;
594 ADD_PLUGIN_FILENAME(pluginFileName);
595 return pluginFileName;
596}
597
DLLEXPORT OpenFlipperQSettings & OpenFlipperSettings()
QSettings object containing all program settings of OpenFlipper.
#define TOSTRING(x)
QSettings object containing all program settings of OpenFlipper.
virtual QString pluginFileName()
void blockSignals(bool _state)
virtual QString name()=0
QString authstring_
License information string.
bool authenticated_
This flag is true if authentication was successful.
void connectNotify(const char *signal)
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
void setValue(const QString &key, const QVariant &value)
Wrapper function which makes it possible to enable Debugging output with -DOPENFLIPPER_SETTINGS_DEBUG...