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 
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.
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 
571  return authstring_;
572 }
573 
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 
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 
DLLEXPORT OpenFlipperQSettings & OpenFlipperSettings()
QSettings object containing all program settings of OpenFlipper.
void connectNotify(const char *signal)
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
#define TOSTRING(x)
QSettings object containing all program settings of OpenFlipper.
bool authenticated_
This flag is true if authentication was successful.
void blockSignals(bool _state)
QString authstring_
License information string.
virtual QString name()=0