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>
73 #include <QFile>
74 #include <QString>
75 #include <QCryptographicHash>
76 #include <QNetworkInterface>
77 #include <QTime>
78 
79 #include <limits>
80 
81 
90 QByteArray decodeString(const QString& _string ,bool _utf8){
91 
92  if (_utf8)
93  return _string.toUtf8();
94  else
95  return _string.toLatin1();
96 
97 }
98 
99 LicenseManager::~LicenseManager()
100 {
101  exit(0);
102 }
103 
104 LicenseManager::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
115 void LicenseManager::blockSignals( bool _state) {
116 
117  if ( !authenticated() ) {
118  QObject::blockSignals( true );
119  } else {
120  QObject::blockSignals(_state);
121  }
122 
123 }
124 
125 
126 bool 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 
580 void 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 
bool authenticated_
This flag is true if authentication was successful.
virtual QString name()=0
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
QString authstring_
License information string.
virtual QString pluginFileName()
#define TOSTRING(x)
QSettings object containing all program settings of OpenFlipper.
void setValue(const QString &key, const QVariant &value)
Wrapper function which makes it possible to enable Debugging output with -DOPENFLIPPER_SETTINGS_DEBUG...
DLLEXPORT OpenFlipperQSettings & OpenFlipperSettings()
QSettings object containing all program settings of OpenFlipper.
void blockSignals(bool _state)
void connectNotify(const char *signal)