NCBI C++ ToolKit
resource_info.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: resource_info.cpp 79870 2017-10-18 17:48:25Z grichenk $
2  * ===========================================================================
3  *
4  * PUBLIC DOMAIN NOTICE
5  * National Center for Biotechnology Information
6  *
7  * This software/database is a "United States Government Work" under the
8  * terms of the United States Copyright Act. It was written as part of
9  * the author's official duties as a United States Government employee and
10  * thus cannot be copyrighted. This software/database is freely available
11  * to the public for use. The National Library of Medicine and the U.S.
12  * Government have not placed any restriction on its use or reproduction.
13  *
14  * Although all reasonable efforts have been taken to ensure the accuracy
15  * and reliability of the software and data, the NLM and the U.S.
16  * Government do not and cannot warrant the performance or results that
17  * may be obtained by using this software or data. The NLM and the U.S.
18  * Government disclaim all warranties, express or implied, including
19  * warranties of performance, merchantability or fitness for any particular
20  * purpose.
21  *
22  * Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Author: Aleksey Grichenko
27  *
28  * File Description:
29  * NCBI C++ secure resources API
30  *
31  */
32 
33 
34 #include <ncbi_pch.hpp>
35 
36 #include <ncbiconf.h>
37 #include <corelib/ncbi_bswap.hpp>
38 #include <corelib/ncbi_param.hpp>
39 #include <corelib/ncbimtx.hpp>
40 #include <corelib/ncbifile.hpp>
41 #include <corelib/ncbistr.hpp>
43 
44 
46 
47 
48 /////////////////////////////////////////////////////////////////////////////
49 //
50 // Utility finctions
51 //
52 
53 
54 // Forward declarations
55 void CalcMD5(const char* data, size_t len, unsigned char* digest);
56 
57 string x_BlockTEA_Encode(const string& str_key,
58  const string& src,
59  size_t block_size);
60 string x_BlockTEA_Decode(const string& str_key,
61  const string& src,
62  size_t block_size);
63 
64 
65 namespace {
66 
67 inline char Hex(unsigned char c)
68 {
69  if (c < 10) {
70  return char(c + '0');
71  }
72  return char(c - 10 + 'A');
73 }
74 
75 
76 // Convert binary data to a printable hexadecimal string.
77 string BinToHex(const string& data)
78 {
79  string ret;
80  ret.reserve(data.size()*2);
81  ITERATE(string, c, data) {
82  ret += Hex((unsigned char)((unsigned char)(*c) >> 4));
83  ret += Hex((unsigned char)((unsigned char)(*c) & 0x0F));
84  }
85  return ret;
86 }
87 
88 
89 // Convert hexadecimal string to binary data. Throw CNcbiEncryptException
90 // if string format is not valid.
91 string HexToBin(const string& hex)
92 {
93  string ret;
94  _ASSERT(hex.size() % 2 == 0);
95  ret.reserve(hex.size()/2);
96  ITERATE(string, h, hex) {
97  int hc = NStr::HexChar(*h);
98  _ASSERT(hc != -1);
99  char c1 = char(hc);
100  h++;
101  hc = NStr::HexChar(*h);
102  _ASSERT(hc != -1);
103  char c2 = char(hc);
104  if (c1 < 0 || c2 < 0) {
105  NCBI_THROW(CNcbiEncryptException, eBadFormat,
106  "Invalid hexadecimal string format: " + hex);
107  return kEmptyStr;
108  }
109  ret += char((c1 << 4) + c2);
110  }
111  return ret;
112 }
113 
114 
115 // Use 128-bit key
116 const size_t kBlockTEA_KeySize = 4;
117 
118 // Block size is a multiple of key size. The longer the better (hides
119 // the source length).
120 // For historical reasons block sizes for NCBI resources and encryption
121 // API are different.
122 const size_t kResInfo_BlockSize = kBlockTEA_KeySize * sizeof(Int4) * 4;
123 const size_t kEncrypt_BlockSize = kBlockTEA_KeySize * sizeof(Int4);
124 
125 
126 // Helper function converting a seed string to a 128 bit binary key.
127 string GenerateBinaryKey(const string& seed)
128 {
129  const unsigned char kBlockTEA_Salt[] = {
130  0x2A, 0x0C, 0x84, 0x24, 0x5B, 0x0D, 0x85, 0x26,
131  0x72, 0x40, 0xBC, 0x38, 0xD3, 0x43, 0x63, 0x9E,
132  0x8E, 0x56, 0xF9, 0xD7, 0x00
133  };
134  string hash = seed + (char*)kBlockTEA_Salt;
135  int len = (int)hash.size();
136  // Allocate memory for both digest (16) and salt (20+1)
137  char digest[37];
138  memcpy(digest + 16, kBlockTEA_Salt, 21);
139  {{
140  CalcMD5(hash.c_str(), hash.size(), (unsigned char*)digest);
141  }}
142  // On every step calculate new digest from the last one + salt
143  for (int i = 0; i < len; i++) {
144  CalcMD5(digest, 36, (unsigned char*)digest);
145  }
146  return string(digest, kBlockTEA_KeySize*sizeof(Int4)); // 16 bytes
147 }
148 
149 
150 string BlockTEA_Encode(const string& password,
151  const string& src,
152  size_t block_size)
153 {
154  return x_BlockTEA_Encode(GenerateBinaryKey(password), src, block_size);
155 }
156 
157 
158 string BlockTEA_Decode(const string& password,
159  const string& src,
160  size_t block_size)
161 {
162  return x_BlockTEA_Decode(GenerateBinaryKey(password), src, block_size);
163 }
164 
165 
166 // Get encoded and hex-formatted string, to be used for resource-info
167 // only!
168 inline string EncodeString(const string& s, const string& pwd)
169 {
170  return BinToHex(BlockTEA_Encode(pwd, s, kResInfo_BlockSize));
171 }
172 
173 } // namespace
174 
175 
176 /////////////////////////////////////////////////////////////////////////////
177 //
178 // CNcbiResourceInfoFile
179 //
180 
181 
182 static const char* kDefaultResourceInfoPath = "/etc/ncbi/.info";
183 // Separator used in the encrypted file between name and value
184 static const char* kResourceValueSeparator = " ";
185 // Separator used in the encrypted file between main and extra
186 static const char* kResourceExtraSeparator = "&";
187 // Separators used in the source file
188 static const char* kParserSeparators = " \t";
189 
190 
192 {
194 }
195 
196 
198  : m_FileName(filename)
199 {
200  CNcbiIfstream in(m_FileName.c_str());
201  if ( !in.good() ) {
202  return;
203  }
204 
205  string line;
206  while ( !in.eof() ) {
207  getline(in, line);
208  line = NStr::TruncateSpaces(line);
209  // Skip empty lines
210  if ( line.empty() ) continue;
211  string name, value;
213  m_Cache[name].encoded = value;
214  }
215 }
216 
217 
218 void CNcbiResourceInfoFile::SaveFile(const string& new_name)
219 {
220  string fname = new_name.empty() ? m_FileName : new_name;
221 
222  CNcbiOfstream out(fname.c_str());
223  if ( !out.good() ) {
225  "Failed to save encrypted file.");
226  return;
227  }
228 
229  ITERATE(TCache, it, m_Cache) {
230  // Data may be modified, re-encode using saved password
231  string enc = it->second.info ?
232  it->second.info->x_GetEncoded() : it->second.encoded;
233  out << it->first << kResourceValueSeparator << enc << endl;
234  }
235 
236  // If new_name was not empty, remember it on success
237  m_FileName = fname;
238 }
239 
240 
241 const CNcbiResourceInfo&
243  const string& pwd) const
244 {
245  TCache::iterator it = m_Cache.find(EncodeString(res_name, pwd));
246  if (it == m_Cache.end()) {
248  }
249  if ( !it->second.info ) {
250  it->second.info.Reset(new CNcbiResourceInfo(res_name,
251  x_GetDataPassword(pwd, res_name), it->second.encoded));
252  }
253  return *it->second.info;
254 }
255 
256 
259  const string& pwd)
260 {
261  SResInfoCache& res_info = m_Cache[EncodeString(res_name, pwd)];
262  if ( !res_info.info ) {
263  res_info.info.Reset(new CNcbiResourceInfo(res_name,
264  x_GetDataPassword(pwd, res_name), res_info.encoded));
265  }
266  return *res_info.info;
267 }
268 
269 
270 void CNcbiResourceInfoFile::DeleteResourceInfo(const string& res_name,
271  const string& pwd)
272 {
273  TCache::iterator it = m_Cache.find(EncodeString(res_name, pwd));
274  if (it != m_Cache.end()) {
275  m_Cache.erase(it);
276  }
277 }
278 
279 
281 CNcbiResourceInfoFile::AddResourceInfo(const string& plain_text)
282 {
283  string data = NStr::TruncateSpaces(plain_text);
284  // Ignore empty lines
285  if ( data.empty() ) {
287  "Empty source string.");
288  // return CNcbiResourceInfo::GetEmptyResInfo();
289  }
290  list<string> split;
291  list<string>::iterator it;
292  string pwd, res_name, res_value, extra;
293 
294  // Get password for encoding
297  it = split.begin();
298  if ( it == split.end() ) {
299  // No password
301  "Missing password.");
302  // return CNcbiResourceInfo::GetEmptyResInfo();
303  }
304  pwd = NStr::URLDecode(*it);
305  it++;
306  if ( it == split.end() ) {
307  // No resource name
309  "Missing resource name.");
310  // return CNcbiResourceInfo::GetEmptyResInfo();
311  }
312  res_name = NStr::URLDecode(*it);
313  it++;
314  if ( it == split.end() ) {
315  // No main value
317  "Missing main resource value.");
319  }
320  res_value = NStr::URLDecode(*it);
321  it++;
322 
323  CNcbiResourceInfo& info = GetResourceInfo_NC(res_name, pwd);
324  info.SetValue(res_value);
325  if ( it != split.end() ) {
326  info.GetExtraValues_NC().Parse(*it);
327  it++;
328  }
329 
330  if (it != split.end()) {
331  // Too many fields
333  "Unrecognized data found after extra values: " + *it + "...");
334  }
335  return info;
336 }
337 
338 
339 void CNcbiResourceInfoFile::ParsePlainTextFile(const string& filename)
340 {
341  CNcbiIfstream in(filename.c_str());
342  while ( in.good() && !in.eof() ) {
343  string line;
344  getline(in, line);
345  if ( line.empty() ) continue;
346  AddResourceInfo(line);
347  }
348 }
349 
350 
351 string CNcbiResourceInfoFile::x_GetDataPassword(const string& name_pwd,
352  const string& res_name) const
353 {
354  // The user's password is combined with the resource name.
355  // This will produce different encoded data for different
356  // resources even if they use the same password and have the
357  // same value.
358  // Name and password are swapped (name is used as encryption key)
359  // so that the result is not equal to the encoded resource name.
360  return BlockTEA_Encode(res_name, name_pwd, kResInfo_BlockSize);
361 }
362 
363 
364 /////////////////////////////////////////////////////////////////////////////
365 //
366 // CNcbiResourceInfo
367 //
368 
370 {
373 }
374 
375 
377  const string& pwd,
378  const string& enc)
379 {
380  _ASSERT(!res_name.empty());
383 
384  // Decode values only if enc is not empty.
385  // If it's not set, we are creating a new resource info
386  // and values will be set later.
387  if ( !enc.empty() ) {
388  string dec = BlockTEA_Decode(pwd, HexToBin(enc), kResInfo_BlockSize);
389  if ( dec.empty() ) {
390  // Error decoding data
392  "Error decrypting resource info value.");
393  return;
394  }
395  string val, extra;
397  // Main value is URL-encoded, extra is not (but its members are).
399  m_Extra.Parse(extra);
400  }
401  m_Name = res_name;
402  m_Password = pwd;
403 }
404 
405 
407 {
408  static CSafeStatic<CNcbiResourceInfo> sEmptyResInfo;
409  return *sEmptyResInfo;
410 }
411 
412 
414 {
415  if ( x_IsEmpty() ) {
416  return kEmptyStr;
417  }
418  string str = NStr::URLEncode(m_Value) +
420  m_Extra.Merge();
421  return BinToHex(BlockTEA_Encode(m_Password, str, kResInfo_BlockSize));
422 }
423 
424 
425 /////////////////////////////////////////////////////////////////////////////
426 //
427 // CNcbiEncrypt
428 //
429 
430 // The default keys file if present in the user's home directory.
431 const char* kDefaultKeysFile = ".ncbi_keys";
432 const char* kDefaultKeysPath = "/opt/ncbi/config";
433 const char* kKeysDomainPrefix = ".ncbi_keys.";
434 const char* kNcbiEncryptVersion = "2";
435 
436 // Key files to cache in memory.
437 NCBI_PARAM_DECL(string, NCBI_KEY, FILES);
438 NCBI_PARAM_DEF_EX(string, NCBI_KEY, FILES, "", eParam_NoThread,
439  NCBI_KEY_FILES);
440 typedef NCBI_PARAM_TYPE(NCBI_KEY, FILES) TKeyFiles;
441 
442 // Paths to search for domain keys, colon separated. Special value '$$' can be
443 // added to include the default path.
444 NCBI_PARAM_DECL(string, NCBI_KEY, PATHS);
445 NCBI_PARAM_DEF_EX(string, NCBI_KEY, PATHS, "$$", eParam_NoThread,
446  NCBI_KEY_PATHS);
447 typedef NCBI_PARAM_TYPE(NCBI_KEY, PATHS) TKeyPaths;
448 
449 
452 volatile bool CNcbiEncrypt::s_KeysInitialized = false;
453 
454 DEFINE_STATIC_MUTEX(s_EncryptMutex);
455 
456 
457 const char* CNcbiEncrypt::GetVersion(void)
458 {
459  return kNcbiEncryptVersion;
460 }
461 
462 
463 string CNcbiEncrypt::Encrypt(const string& original_string)
464 {
465  sx_InitKeyMap();
466  const string& key = s_DefaultKey.Get();
467  if ( key.empty() ) {
468  NCBI_THROW(CNcbiEncryptException, eMissingKey,
469  "No encryption keys found.");
470  }
471  return x_Encrypt(original_string, key);
472 }
473 
474 
475 string CNcbiEncrypt::Decrypt(const string& encrypted_string)
476 {
477  size_t domain_pos = encrypted_string.find('/');
478  if (domain_pos != NPOS) {
479  return DecryptForDomain(encrypted_string.substr(0, domain_pos),
480  encrypted_string.substr(domain_pos + 1));
481  }
482 
483  sx_InitKeyMap();
484  const TKeyMap& keys = s_KeyMap.Get();
485  if ( keys.empty() ) {
486  NCBI_THROW(CNcbiEncryptException, eMissingKey,
487  "No decryption keys found.");
488  }
489 
490  return x_Decrypt(encrypted_string, keys);
491 }
492 
493 
494 string CNcbiEncrypt::Encrypt(const string& original_string,
495  const string& password)
496 {
497  if ( password.empty() ) {
498  NCBI_THROW(CNcbiEncryptException, eBadPassword,
499  "Encryption password can not be empty.");
500  }
501  return x_Encrypt(original_string, GenerateBinaryKey(password));
502 }
503 
504 
505 string CNcbiEncrypt::Decrypt(const string& encrypted_string,
506  const string& password)
507 {
508  if ( password.empty() ) {
509  NCBI_THROW(CNcbiEncryptException, eBadPassword,
510  "Encryption password can not be empty.");
511  }
512  TKeyMap keys;
513  string key = GenerateBinaryKey(password);
514  char md5[16];
515  CalcMD5(key.c_str(), key.size(), (unsigned char*)md5);
516  keys[string(md5, 16)] =
518  return x_Decrypt(encrypted_string, keys);
519 }
520 
521 
522 string CNcbiEncrypt::EncryptForDomain(const string& original_string,
523  const string& domain)
524 {
525  string key = x_GetDomainKeys(domain, NULL);
526  if ( key.empty() ) {
527  NCBI_THROW(CNcbiEncryptException, eBadDomain,
528  "No encryption keys found for domain " + domain);
529  }
530 
531  return x_Encrypt(original_string, key) + "/" + domain;
532 }
533 
534 
535 string CNcbiEncrypt::DecryptForDomain(const string& encrypted_string,
536  const string& domain)
537 {
538  TKeyMap keys;
539  x_GetDomainKeys(domain, &keys);
540 
541  size_t domain_pos = encrypted_string.find('/');
542  if (domain_pos != NPOS) {
543  // If the data include domain, check it as well.
544  string domain2 = encrypted_string.substr(domain_pos + 1);
545  if (domain2 != domain) {
546  x_GetDomainKeys(domain2, &keys);
547  }
548  }
549 
550  if ( keys.empty() ) {
551  NCBI_THROW(CNcbiEncryptException, eBadDomain,
552  "No decryption keys found for domain " + domain);
553  }
554 
555  return x_Decrypt(encrypted_string.substr(0, domain_pos), keys);
556 }
557 
558 
559 string CNcbiEncrypt::GenerateKey(const string& seed)
560 {
561  // This method is just a helper for storing keys as hexadecimal strings.
562  string key = GenerateBinaryKey(seed);
563  string checksum = x_GetBinKeyChecksum(key);
564  return kNcbiEncryptVersion + checksum + ":" + BinToHex(key);
565 }
566 
567 
568 bool CNcbiEncrypt::IsEncrypted(const string& data)
569 {
570  if ( data.empty() ) {
571  return false;
572  }
573 
574  // Domain, if present, must be not empty.
575  size_t domain_pos = data.find('/');
576  if (domain_pos == data.size() - 1) {
577  return false;
578  }
579 
580  string encr = data.substr(0, domain_pos);
581 
582  if (encr.empty()) {
583  return false;
584  }
585 
586  // Check API version. Currently supported versions are 1 and 2.
587  char version = encr[0];
588  if (version < '1' || version > '2') {
589  return false;
590  }
591 
592  // Check if key checksum is present and the possibly encrypted part
593  // has the correct length (a multiple of kEncrypt_BlockSize).
594  if (encr.size() <= 34 || encr[33] != ':' ||
595  (encr.size() - 34) % kEncrypt_BlockSize) {
596  return false;
597  }
598  for (size_t pos = 1; pos < encr.size(); ++pos) {
599  // Skip colon between checksum and the encrypted data.
600  if (pos == 33) continue;
601  // All other chars must be [0-9a-f].
602  if (NStr::HexChar(encr[pos]) < 0) {
603  return false;
604  }
605  }
606  return true;
607 }
608 
609 
611 {
612  CMutexGuard guard(s_EncryptMutex);
613  s_KeysInitialized = false;
614  TKeyFiles::ResetDefault();
615  TKeyPaths::ResetDefault();
616  s_KeyMap.Get().clear();
617  s_DefaultKey.Get().clear();
618  sx_InitKeyMap();
619 }
620 
621 
623 {
624  if ( !s_KeysInitialized ) {
625  CMutexGuard guard(s_EncryptMutex);
626  if ( !s_KeysInitialized ) {
627  TKeyMap& keys = s_KeyMap.Get();
628  // Load keys from all available files.
629  string files = TKeyFiles::GetDefault();
630  if ( files.empty() ) {
632  }
633  list<string> file_list;
634  NStr::Split(files, ":", file_list,
636  ITERATE(list<string>, it, file_list) {
637  string fname = *it;
638  size_t home_pos = fname.find("$HOME");
639  if (home_pos == 0 && fname.size() > 5 && CDirEntry::IsPathSeparator(fname[5])) {
640  fname = CDir::ConcatPath(CDir::GetHome(), fname.substr(6));
641  }
642  string first_key = x_LoadKeys(fname, &keys);
643  // Set the first available key as the default one.
644  if ( s_DefaultKey->empty() ) {
645  *s_DefaultKey = first_key;
646  }
647  }
648  s_KeysInitialized = true;
649  }
650  }
651 }
652 
653 
655 {
656  char md5[16];
657  CalcMD5(key.c_str(), key.size(), (unsigned char*)md5);
658  return BinToHex(string(md5, 16));
659 }
660 
661 
662 string CNcbiEncrypt::x_LoadKeys(const string& filename, TKeyMap* keys)
663 {
664  string first_key;
665  CNcbiIfstream in(filename.c_str());
666  if ( !in.good() ) return first_key;
667  size_t line = 0;
668  while ( in.good() && !in.eof() ) {
669  line++;
670  string s;
671  getline(in, s);
673  // Ignore empty lines and comments.
674  if (s.empty() || s[0] == '#') continue;
675 
676  char version = s[0];
677  if (version < '1' || version > '2') {
678  NCBI_THROW(CNcbiEncryptException, eBadVersion,
679  "Invalid or unsupported API version in encryption key.");
680  }
681 
682  string checksum, key;
683  if ( !NStr::SplitInTwo(s, ":", checksum, key) ||
684  checksum.size() != 33 ) {
685  ERR_POST(Warning <<
686  "Invalid encryption key format in " << filename << ", line " << line);
687  continue;
688  }
689  checksum = HexToBin(checksum.substr(1));
690 
691  EDiagSev sev = eDiag_Trace;
692  size_t sevpos = key.find('/');
693  if (sevpos != NPOS) {
694  string sevname = key.substr(sevpos + 1);
696  if ( !CNcbiDiag::StrToSeverityLevel(sevname.c_str(), sev) ) {
697  ERR_POST(Warning <<
698  "Invalid key severity in " << filename << ", line " << line);
699  continue;
700  }
701  key.resize(sevpos);
703  }
704  if (key.size() != 32) {
705  ERR_POST(Warning <<
706  "Invalid key format in " << filename << ", line " << line);
707  continue;
708  }
709 
710  key = HexToBin(key);
711  char md5[16];
712  CalcMD5(key.c_str(), key.size(), (unsigned char*)md5);
713  if (string(md5, 16) != checksum) {
714  ERR_POST(Warning << "Invalid key checksum in " << filename << ", line " << line);
715  continue;
716  }
717  // Set the first available key as the default one.
718  if ( first_key.empty() ) {
719  first_key = key;
720  }
721  if ( !keys ) {
722  // Found the first key, no need to parse the rest of the file.
723  break;
724  }
725  (*keys)[checksum] = SEncryptionKeyInfo(key, sev, filename, line, version);
726  }
727  return first_key;
728 }
729 
730 
731 string CNcbiEncrypt::x_GetDomainKeys(const string& domain, TKeyMap* keys)
732 {
733  string first_key;
734  list<string> paths;
735  NStr::Split(TKeyPaths::GetDefault(), ":", paths,
737  ITERATE(list<string>, it, paths) {
738  string path = *it;
739  if (path == "$$") {
740  path = kDefaultKeysPath;
741  }
742  string fname = CDirEntry::MakePath(*it, kKeysDomainPrefix + domain);
743  string res = x_LoadKeys(fname, keys);
744  if ( first_key.empty() ) {
745  first_key = res;
746  }
747  if (!first_key.empty() && !keys) {
748  // Found the first key, no need to check other files.
749  break;
750  }
751  }
752  if ( keys ) {
753  // Reset severity of the first key to Trace so that it's not reported.
754  string first_checksum = x_GetBinKeyChecksum(first_key);
755  (*keys)[first_checksum].m_Severity = eDiag_Trace;
756  }
757  return first_key;
758 }
759 
760 
761 string CNcbiEncrypt::x_Encrypt(const string& data, const string& key)
762 {
763  _ASSERT(!key.empty());
764  string checksum = x_GetBinKeyChecksum(key);
765  return kNcbiEncryptVersion +
766  checksum + ":" + BinToHex(x_BlockTEA_Encode(key,
767  x_AddSalt(data, *kNcbiEncryptVersion), kEncrypt_BlockSize));
768 }
769 
770 
771 string CNcbiEncrypt::x_Decrypt(const string& data, const TKeyMap& keys)
772 {
773  if (data.empty()) {
774  NCBI_THROW(CNcbiEncryptException, eBadFormat,
775  "Trying to decrypt an empty string.");
776  }
777 
778  // API version
779  char version = data[0];
780  if (version < '1' || version > '2') {
781  NCBI_THROW(CNcbiEncryptException, eBadVersion,
782  "Invalid or unsupported API version in the encrypted data.");
783  }
784 
785  // Parse the encrypted string, find key checksum.
786  // The checksum is in the 16 bytes following version (32 hex chars),
787  // separated with a colon.
788  if (data.size() < 34 || data[33] != ':') {
789  NCBI_THROW(CNcbiEncryptException, eBadFormat,
790  "Invalid encrypted string format - missing key checksum.");
791  }
792  string checksum = HexToBin(data.substr(1, 32));
793  TKeyMap::const_iterator key_it = keys.find(checksum);
794  if (key_it == keys.end()) {
795  NCBI_THROW(CNcbiEncryptException, eMissingKey,
796  "No decryption key found for the checksum.");
797  }
798  string key = key_it->second.m_Key;
799  EDiagSev sev = key_it->second.m_Severity;
800  // TRACE severity indicates there's no need to log key usage.
801  if (key != s_DefaultKey.Get() && sev != eDiag_Trace) {
802  ERR_POST_ONCE(Severity(key_it->second.m_Severity) <<
803  "Decryption key accessed: checksum=" << x_GetBinKeyChecksum(key) <<
804  ", location=" << key_it->second.m_File << ":" << key_it->second.m_Line);
805  }
806 
807  // Domain must be parsed by the caller.
808  _ASSERT(data.find('/') == NPOS);
809  _ASSERT(!key.empty());
810  return x_RemoveSalt(
811  x_BlockTEA_Decode(key, HexToBin(data.substr(34)), kEncrypt_BlockSize),
812  version);
813 }
814 
815 
816 const size_t kSaltLength = 16; // sizeof(time_t) + sizeof(long)
817 
818 
819 string CNcbiEncrypt::x_AddSalt(const string& data, char version)
820 {
821  if (version < '2') return data;
822  // Add salt
823  string salt;
824  salt.reserve(kSaltLength);
825  static time_t tt = 0;
826  static long ns = 0;
827  if (tt == 0) {
828  CTime::GetCurrentTimeT(&tt, &ns);
829  }
830  time_t ttmp = tt;
831  for (size_t i = 0; i < sizeof(ttmp) && salt.size() < kSaltLength; ++i) {
832  salt += char(ttmp & 0xFF);
833  ttmp >>= 8;
834  }
835  while (salt.size() < kSaltLength) {
836  long ntmp = ++ns;
837  for (size_t i = 0; i < sizeof(ntmp) && salt.size() < kSaltLength; ++i) {
838  salt += char(ntmp & 0xFF);
839  ntmp >>= 8;
840  }
841  }
842  return salt + data;
843 }
844 
845 
846 string CNcbiEncrypt::x_RemoveSalt(const string& data, char version)
847 {
848  if (version < '2') return data;
849  return data.substr(kSaltLength);
850 }
851 
852 
853 /////////////////////////////////////////////////////////////////////////////
854 //
855 // XXTEA implementation
856 //
857 
858 namespace { // Hide the implementation
859 
860 const Uint4 kBlockTEA_Delta = 0x9e3779b9;
861 
862 typedef Int4 TBlockTEA_Key[kBlockTEA_KeySize];
863 
864 #define TEA_MX ((z >> 5)^(y << 2)) + (((y >> 3)^(z << 4))^(sum^y)) + (key[(p & 3)^e]^z);
865 
866 // Corrected Block TEA encryption
867 void BlockTEA_Encode_In_Place(Int4* data, Int4 n, const TBlockTEA_Key key)
868 {
869  if (n <= 1) return;
870  Uint4 z = data[n - 1];
871  Uint4 y;
872  Uint4 sum = 0;
873  Uint4 e;
874  Int4 p;
875  Int4 q = 6 + 52/n;
876  while (q-- > 0) {
877  sum += kBlockTEA_Delta;
878  e = (sum >> 2) & 3;
879  for (p = 0; p < n - 1; p++) {
880  y = data[p + 1];
881  z = data[p] += TEA_MX;
882  }
883  y = data[0];
884  z = data[n - 1] += TEA_MX;
885  }
886 }
887 
888 
889 // Corrected Block TEA decryption
890 void BlockTEA_Decode_In_Place(Int4* data, Int4 n, const TBlockTEA_Key key)
891 {
892  if (n <= 1) return;
893  Uint4 z;
894  Uint4 y = data[0];
895  Uint4 e;
896  Int4 p;
897  Int4 q = 6 + 52/n;
898  Uint4 sum = q*kBlockTEA_Delta;
899  while (sum != 0) {
900  e = (sum >> 2) & 3;
901  for (p = n - 1; p > 0; p--) {
902  z = data[p - 1];
903  y = data[p] -= TEA_MX;
904  }
905  z = data[n - 1];
906  y = data[0] -= TEA_MX;
907  sum -= kBlockTEA_Delta;
908  }
909 }
910 
911 
912 // Read an integer from a little-endian ordered buffer
913 inline Int4 GetInt4LE(const char* ptr)
914 {
915 #ifdef WORDS_BIGENDIAN
916  return CByteSwap::GetInt4((const unsigned char*)ptr);
917 #else
918  return *(const Int4*)(ptr);
919 #endif
920 }
921 
922 // Put the integer to the buffer in little-endian order
923 inline void PutInt4LE(Int4 i, char* ptr)
924 {
925 #ifdef WORDS_BIGENDIAN
926  CByteSwap::PutInt4((unsigned char*)ptr, i);
927 #else
928  *(Int4*)(ptr) = i;
929 #endif
930 }
931 
932 
933 // Convert string to array of integers assuming bytes in the string
934 // have little-endian order. 'dst' must have enough space to store
935 // the result. Length is given in bytes (chars).
936 void StringToInt4Array(const string& src, Int4* dst)
937 {
938  size_t len = src.size()/sizeof(Int4);
939  const char* p = src.c_str();
940  for (size_t i = 0; i < len; i++, p += sizeof(Int4)) {
941  dst[i] = GetInt4LE(p);
942  }
943 }
944 
945 
946 // Convert array of integers to string with little-endian order of bytes.
947 // Length is given in integers.
948 string Int4ArrayToString(const Int4* src, size_t len)
949 {
950  string ret;
951  ret.reserve(len*sizeof(Int4));
952  char buf[4];
953  for (size_t i = 0; i < len; i++) {
954  PutInt4LE(src[i], buf);
955  ret += string(buf, 4);
956  }
957  return ret;
958 }
959 
960 } // namespace
961 
962 
963 // The string key must contain a 128-bit value.
964 string x_BlockTEA_Encode(const string& str_key,
965  const string& src,
966  size_t block_size)
967 {
968  if ( src.empty() ) {
969  return kEmptyStr;
970  }
971 
972  // Prepare the key
973  _ASSERT(str_key.size() == kBlockTEA_KeySize*sizeof(Int4));
974  TBlockTEA_Key key;
975  StringToInt4Array(str_key, key);
976 
977  // Prepare the source:
978  // Add padding so that the src length is a multiple of key size.
979  // Padding is always present even if the string size is already good -
980  // this is necessary to remove it correctly after decoding.
981  size_t padlen = block_size - (src.size() % block_size);
982  string padded = string(padlen, char(padlen)) + src;
983  _ASSERT(padded.size() % sizeof(Int4) == 0);
984  // Convert source string to array of integers
985  size_t buflen = padded.size() / sizeof(Int4);
986  Int4* buf = new Int4[buflen];
987  StringToInt4Array(padded, buf);
988 
989  // Encode data
990  BlockTEA_Encode_In_Place(buf, (Int4)buflen, key);
991 
992  // Convert encoded buffer back to string
993  string ret = Int4ArrayToString(buf, buflen);
994  delete[] buf;
995  return ret;
996 }
997 
998 
999 // The string key must contain a 128-bit value.
1000 string x_BlockTEA_Decode(const string& str_key,
1001  const string& src,
1002  size_t block_size)
1003 {
1004  if ( src.empty() ) {
1005  return kEmptyStr;
1006  }
1007 
1008  // Prepare the key
1009  _ASSERT(str_key.size() == kBlockTEA_KeySize*sizeof(Int4));
1010  TBlockTEA_Key key;
1011  StringToInt4Array(str_key, key);
1012 
1013  _ASSERT(src.size() % block_size == 0);
1014  // Convert source string to array of integers
1015  size_t buflen = src.size() / sizeof(Int4);
1016  Int4* buf = new Int4[buflen];
1017  StringToInt4Array(src, buf);
1018 
1019  // Decode data
1020  BlockTEA_Decode_In_Place(buf, (Int4)buflen, key);
1021 
1022  // Convert decoded buffer back to string
1023  string ret = Int4ArrayToString(buf, buflen);
1024  delete[] buf;
1025 
1026  // Make sure padding is correct
1027  size_t padlen = (size_t)ret[0];
1028  if (padlen >= ret.size()) {
1029  return kEmptyStr;
1030  }
1031  for (size_t i = 0; i < padlen; i++) {
1032  if ((size_t)ret[i] != padlen) {
1033  return kEmptyStr;
1034  }
1035  }
1036  // Remove padding and return the result
1037  return ret.substr((size_t)ret[0], ret.size());
1038 }
1039 
1040 
1041 /////////////////////////////////////////////////////////////////////////////
1042 //
1043 // Local MD5 implementation
1044 //
1045 
1046 
1047 namespace {
1048  union SUnionLenbuf {
1049  char lenbuf[8];
1050  Uint8 len8;
1051  };
1052 
1053  inline Uint4 leftrotate(Uint4 x, Uint4 c)
1054  {
1055  return (x << c) | (x >> (32 - c));
1056  }
1057 }
1058 
1059 
1060 void CalcMD5(const char* data, size_t len, unsigned char* digest)
1061 {
1062  const Uint4 r[64] = {
1063  7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
1064  5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
1065  4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
1066  6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
1067  };
1068 
1069  const Uint4 k[64] = {
1070  3614090360U, 3905402710U, 606105819U, 3250441966U,
1071  4118548399U, 1200080426U, 2821735955U, 4249261313U,
1072  1770035416U, 2336552879U, 4294925233U, 2304563134U,
1073  1804603682U, 4254626195U, 2792965006U, 1236535329U,
1074  4129170786U, 3225465664U, 643717713U, 3921069994U,
1075  3593408605U, 38016083U, 3634488961U, 3889429448U,
1076  568446438U, 3275163606U, 4107603335U, 1163531501U,
1077  2850285829U, 4243563512U, 1735328473U, 2368359562U,
1078  4294588738U, 2272392833U, 1839030562U, 4259657740U,
1079  2763975236U, 1272893353U, 4139469664U, 3200236656U,
1080  681279174U, 3936430074U, 3572445317U, 76029189U,
1081  3654602809U, 3873151461U, 530742520U, 3299628645U,
1082  4096336452U, 1126891415U, 2878612391U, 4237533241U,
1083  1700485571U, 2399980690U, 4293915773U, 2240044497U,
1084  1873313359U, 4264355552U, 2734768916U, 1309151649U,
1085  4149444226U, 3174756917U, 718787259U, 3951481745U
1086  };
1087 
1088  // Initialize variables:
1089  Uint4 h0 = 0x67452301;
1090  Uint4 h1 = 0xEFCDAB89;
1091  Uint4 h2 = 0x98BADCFE;
1092  Uint4 h3 = 0x10325476;
1093 
1094  // Pre-processing:
1095  // append "1" bit to message
1096  // append "0" bits until message length in bits == 448 (mod 512)
1097  // append bit (not byte) length of unpadded message as 64-bit
1098  // little-endian integer to message
1099  Uint4 padlen = 64 - Uint4(len % 64);
1100  if (padlen < 9) padlen += 64;
1101  string buf(data, len);
1102  buf += char(0x80);
1103  buf.append(string(padlen - 9, 0));
1104  Uint8 len8 = len*8;
1105  SUnionLenbuf lb;
1106 #ifdef WORDS_BIGENDIAN
1107  CByteSwap::PutInt8((unsigned char*)lb.lenbuf, len8);
1108 #else
1109  lb.len8 = len8;
1110 #endif
1111  buf.append(lb.lenbuf, 8);
1112 
1113  const char* buf_start = buf.c_str();
1114  const char* buf_end = buf_start + len + padlen;
1115  // Process the message in successive 512-bit chunks
1116  for (const char* p = buf_start; p < buf_end; p += 64) {
1117  // Break chunk into sixteen 32-bit little-endian words w[i]
1118  Uint4 w[16];
1119  for (int i = 0; i < 16; i++) {
1120  w[i] = (Uint4)GetInt4LE(p + i*4);
1121  }
1122 
1123  // Initialize hash value for this chunk:
1124  Uint4 a = h0;
1125  Uint4 b = h1;
1126  Uint4 c = h2;
1127  Uint4 d = h3;
1128 
1129  Uint4 f, g;
1130 
1131  // Main loop:
1132  for (unsigned int i = 0; i < 64; i++) {
1133  if (i < 16) {
1134  f = (b & c) | ((~b) & d);
1135  g = i;
1136  }
1137  else if (i < 32) {
1138  f = (d & b) | ((~d) & c);
1139  g = (5*i + 1) % 16;
1140  }
1141  else if (i < 48) {
1142  f = b ^ c ^ d;
1143  g = (3*i + 5) % 16;
1144  }
1145  else {
1146  f = c ^ (b | (~d));
1147  g = (7*i) % 16;
1148  }
1149 
1150  Uint4 temp = d;
1151  d = c;
1152  c = b;
1153  b += leftrotate((a + f + k[i] + w[g]), r[i]);
1154  a = temp;
1155  }
1156 
1157  // Add this chunk's hash to result so far:
1158  h0 += a;
1159  h1 += b;
1160  h2 += c;
1161  h3 += d;
1162  }
1163 
1164  PutInt4LE(h0, (char*)digest);
1165  PutInt4LE(h1, (char*)(digest + 4));
1166  PutInt4LE(h2, (char*)(digest + 8));
1167  PutInt4LE(h3, (char*)(digest + 12));
1168 }
1169 
static void PutInt8(unsigned char *ptr, Int8 value)
Definition: ncbi_bswap.hpp:180
static Int4 GetInt4(const unsigned char *ptr)
Definition: ncbi_bswap.hpp:121
static void PutInt4(unsigned char *ptr, Int4 value)
Definition: ncbi_bswap.hpp:138
Exception thrown by encryption classes.
static string x_AddSalt(const string &data, char version)
static string x_LoadKeys(const string &filename, TKeyMap *keys)
static bool IsEncrypted(const string &data)
Check if the string contains valid encrypted data.
static string x_Encrypt(const string &data, const string &key)
static string x_GetDomainKeys(const string &domain, TKeyMap *keys)
static string x_GetBinKeyChecksum(const string &key)
static CSafeStatic< CNcbiEncrypt::TKeyMap > s_KeyMap
static string x_RemoveSalt(const string &data, char version)
static string x_Decrypt(const string &data, const TKeyMap &keys)
static const char * GetVersion(void)
Get encryption API version.
static CSafeStatic< string > s_DefaultKey
static string GenerateKey(const string &seed)
Generate an encryption/decryption key from the seed string.
static void sx_InitKeyMap(void)
static volatile bool s_KeysInitialized
static string DecryptForDomain(const string &encrypted_string, const string &domain)
Decrypt data using domain key.
static string EncryptForDomain(const string &original_string, const string &domain)
Encrypt data using domain key.
static string Encrypt(const string &original_string)
Encrypt a string using key from the 1st line of the 1st NCBI keys file.
static string Decrypt(const string &encrypted_string)
Decrypt a string using the matching key found in the NCBI keys files.
static void Reload(void)
Re-read key file locations and domain paths, reload encryption keys.
Exception thrown by resource info classes.
CNcbiResourceInfo & GetResourceInfo_NC(const string &res_name, const string &pwd)
Get (or create) non-const resource info for the given name.
CNcbiResourceInfoFile(const string &filename)
Load resource-info file.
void DeleteResourceInfo(const string &res_name, const string &pwd)
Delete resource associated with the name.
void SaveFile(const string &new_name=kEmptyStr)
Save data to file.
string x_GetDataPassword(const string &name_pwd, const string &res_name) const
void ParsePlainTextFile(const string &filename)
Parse file containing source plaintext data, add (replace) all resources to the current file.
static string GetDefaultFileName(void)
Get default resource info file location (/etc/ncbi/.info).
const CNcbiResourceInfo & GetResourceInfo(const string &res_name, const string &pwd) const
Get read-only resource info for the given name.
CNcbiResourceInfo & AddResourceInfo(const string &plain_text)
Create new resource info from the string and return reference to the new CNcbiResourceInfo object or ...
Class for storing encrypted resource information.
CNcbiResourceInfo(void)
Create a new empty resource info.
bool x_IsEmpty(void) const
string x_GetEncoded(void) const
static CNcbiResourceInfo & GetEmptyResInfo(void)
TExtraValues m_Extra
CSafeStatic<>::
T & Get(void)
Create the variable if not created yet, return the reference.
URL-decoder for string pairs parser.
Definition: ncbistr.hpp:4565
URL-encoder for string pairs parser.
Definition: ncbistr.hpp:4578
Severity –.
Definition: ncbidiag.hpp:833
void erase(iterator pos)
Definition: map.hpp:167
container_type::const_iterator const_iterator
Definition: map.hpp:53
const_iterator end() const
Definition: map.hpp:152
bool empty() const
Definition: map.hpp:149
const_iterator find(const key_type &key) const
Definition: map.hpp:153
The NCBI C++ standard methods for dealing with std::string.
std::ofstream out("events_result.xml")
main entry point for tests
static const char * str(char *buf, int n)
Definition: stats.c:84
static void md5(const char *src, const char *out)
Definition: challenge.c:77
char data[12]
Definition: iconv.c:80
#define ITERATE(Type, Var, Cont)
ITERATE macro to sequence through container elements.
Definition: ncbimisc.hpp:815
string
Definition: cgiapp.hpp:687
#define NULL
Definition: ncbistd.hpp:225
#define ERR_POST_ONCE(message)
Error posting only once during program execution.
Definition: ncbidiag.hpp:602
#define ERR_POST(message)
Error posting with file, line number information but without error codes.
Definition: ncbidiag.hpp:186
EDiagSev
Severity level for the posted diagnostics.
Definition: ncbidiag.hpp:650
static bool StrToSeverityLevel(const char *str_sev, EDiagSev &sev)
Get severity from string.
Definition: ncbidiag.cpp:7904
@ eDiag_Trace
Trace message.
Definition: ncbidiag.hpp:657
#define NCBI_THROW(exception_class, err_code, message)
Generic macro to throw an exception, given the exception class, error code and message string.
Definition: ncbiexpt.hpp:704
void Warning(CExceptionArgs_Base &args)
Definition: ncbiexpt.hpp:1191
static bool IsPathSeparator(const char c)
Check whether a character "c" is a path separator symbol specific for the current platform.
Definition: ncbifile.cpp:439
static string MakePath(const string &dir=kEmptyStr, const string &base=kEmptyStr, const string &ext=kEmptyStr)
Assemble a path from basic components.
Definition: ncbifile.cpp:413
static string ConcatPath(const string &first, const string &second)
Concatenate two parts of the path for the current OS.
Definition: ncbifile.cpp:776
static string GetHome(void)
Get user "home" directory.
Definition: ncbifile.cpp:3624
void Reset(void)
Reset reference object.
Definition: ncbiobj.hpp:773
@ eParam_NoThread
Do not use per-thread values.
Definition: ncbi_param.hpp:418
int32_t Int4
4-byte (32-bit) signed integer
Definition: ncbitype.h:102
uint32_t Uint4
4-byte (32-bit) unsigned integer
Definition: ncbitype.h:103
uint64_t Uint8
8-byte (64-bit) unsigned integer
Definition: ncbitype.h:105
#define END_NCBI_SCOPE
End previously defined NCBI scope.
Definition: ncbistl.hpp:103
#define BEGIN_NCBI_SCOPE
Define ncbi namespace.
Definition: ncbistl.hpp:100
IO_PREFIX::ofstream CNcbiOfstream
Portable alias for ofstream.
Definition: ncbistre.hpp:500
IO_PREFIX::ifstream CNcbiIfstream
Portable alias for ifstream.
Definition: ncbistre.hpp:439
void Parse(const CTempString str, NStr::EMergeDelims merge_argsep=NStr::eMergeDelims)
Parse the string.
Definition: ncbistr.hpp:4698
string Merge(void) const
Merge name-value pairs into a single string using the currently set separators and the provided encod...
Definition: ncbistr.hpp:4753
#define kEmptyStr
Definition: ncbistr.hpp:123
static list< string > & Split(const CTempString str, const CTempString delim, list< string > &arr, TSplitFlags flags=0, vector< SIZE_TYPE > *token_pos=NULL)
Split a string using specified delimiters.
Definition: ncbistr.cpp:3461
void SetDecoder(IStringDecoder *decoder, EOwnership own=eTakeOwnership)
Set string decoder.
Definition: ncbistr.hpp:4673
#define NPOS
Definition: ncbistr.hpp:133
static string URLDecode(const CTempString str, EUrlDecode flag=eUrlDec_All)
URL-decode string.
Definition: ncbistr.cpp:6214
static void TruncateSpacesInPlace(string &str, ETrunc where=eTrunc_Both)
Truncate spaces in a string (in-place)
Definition: ncbistr.cpp:3201
void SetEncoder(IStringEncoder *encoder, EOwnership own=eTakeOwnership)
Set string encoder.
Definition: ncbistr.hpp:4684
static int HexChar(char ch)
Convert character to integer.
Definition: ncbistr.hpp:5196
static bool SplitInTwo(const CTempString str, const CTempString delim, string &str1, string &str2, TSplitFlags flags=0)
Split a string into two pieces using the specified delimiters.
Definition: ncbistr.cpp:3554
static string URLEncode(const CTempString str, EUrlEncode flag=eUrlEnc_SkipMarkChars)
URL-encode string.
Definition: ncbistr.cpp:6062
static string TruncateSpaces(const string &str, ETrunc where=eTrunc_Both)
Truncate spaces in a string.
Definition: ncbistr.cpp:3186
@ fSplit_Truncate
Definition: ncbistr.hpp:2501
@ fSplit_MergeDelimiters
Merge adjacent delimiters.
Definition: ncbistr.hpp:2498
static void GetCurrentTimeT(time_t *sec, long *nanosec=0)
Get current UTC time in time_t format (with nanoseconds).
Definition: ncbitime.cpp:1672
unsigned int
A callback function used to compare two keys in a database.
Definition: types.hpp:1210
char * buf
int i
yy_size_t n
int len
static void hex(unsigned char c)
Definition: mdb_dump.c:56
static MDB_envinfo info
Definition: mdb_load.c:37
static int version
Definition: mdb_load.c:29
const struct ncbi::grid::netcache::search::fields::KEY key
Arg Hex(short *ptr)
Definition: pcrecpparg.h:154
const GenericPointer< typename T::ValueType > T2 value
Definition: pointer.h:1227
unsigned int a
Definition: ncbi_localip.c:102
Front end for a platform-specific configuration summary.
Defines classes: CDirEntry, CFile, CDir, CSymLink, CMemoryFile, CFileUtil, CFileLock,...
Multi-threading – mutexes; rw-locks; semaphore.
std::istream & in(std::istream &in_, double &x_)
double r(size_t dimension_, const Int4 *score_, const double *prob_, double theta_)
double f(double x_, const double &y_)
Definition: njn_root.hpp:188
void split(std::vector< std::string > *strVec, const std::string &str_, const std::string &split_)
static const char * kResourceValueSeparator
DEFINE_STATIC_MUTEX(s_EncryptMutex)
NCBI_PARAM_DECL(string, NCBI_KEY, FILES)
const char * kKeysDomainPrefix
const char * kDefaultKeysPath
const char * kDefaultKeysFile
static const char * kParserSeparators
string x_BlockTEA_Encode(const string &str_key, const string &src, size_t block_size)
typedef NCBI_PARAM_TYPE(NCBI_KEY, FILES) TKeyFiles
NCBI_PARAM_DEF_EX(string, NCBI_KEY, FILES, "", eParam_NoThread, NCBI_KEY_FILES)
#define TEA_MX
string x_BlockTEA_Decode(const string &str_key, const string &src, size_t block_size)
const char * kNcbiEncryptVersion
const size_t kSaltLength
void CalcMD5(const char *data, size_t len, unsigned char *digest)
static const char * kResourceExtraSeparator
static const char * kDefaultResourceInfoPath
Defines NCBI C++ secure resources API.
CRef< CNcbiResourceInfo > info
Definition: _hash_fun.h:40
#define _ASSERT
static int seed
Definition: test_table.cpp:132
int g(Seg_Gsm *spe, Seq_Mtf *psm, Thd_Gsm *tdg)
Definition: thrddgri.c:44
Modified on Tue May 21 10:55:23 2024 by modify_doxy.py rev. 669887