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