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