Coverage Report

Created: 2026-07-24 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pdns/pdns/dnsdistdist/dnsdist-crypto.cc
Line
Count
Source
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22
#include <iostream>
23
#include <arpa/inet.h>
24
25
#include "dnsdist-crypto.hh"
26
27
#include "namespaces.hh"
28
#include "noinitvector.hh"
29
#include "misc.hh"
30
#include "base64.hh"
31
32
namespace dnsdist::crypto::authenticated
33
{
34
#ifdef HAVE_LIBSODIUM
35
string newKey(bool base64Encoded)
36
{
37
  std::string key;
38
  key.resize(crypto_secretbox_KEYBYTES);
39
40
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
41
  randombytes_buf(reinterpret_cast<unsigned char*>(key.data()), key.size());
42
43
  if (!base64Encoded) {
44
    return key;
45
  }
46
  return "\"" + Base64Encode(key) + "\"";
47
}
48
49
bool isValidKey(const std::string& key)
50
{
51
  return key.size() == crypto_secretbox_KEYBYTES;
52
}
53
54
std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
55
{
56
  if (!isValidKey(key)) {
57
    throw std::runtime_error("Invalid encryption key of size " + std::to_string(key.size()) + " (" + std::to_string(crypto_secretbox_KEYBYTES) + " expected), use setKey() to set a valid key");
58
  }
59
60
  std::string ciphertext;
61
  ciphertext.resize(msg.length() + crypto_secretbox_MACBYTES);
62
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
63
  crypto_secretbox_easy(reinterpret_cast<unsigned char*>(ciphertext.data()),
64
                        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
65
                        reinterpret_cast<const unsigned char*>(msg.data()),
66
                        msg.length(),
67
                        nonce.value.data(),
68
                        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
69
                        reinterpret_cast<const unsigned char*>(key.data()));
70
71
  if (incrementNonce) {
72
    nonce.increment();
73
  }
74
75
  return ciphertext;
76
}
77
78
std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
79
{
80
  std::string decrypted;
81
82
  if (msg.length() < crypto_secretbox_MACBYTES) {
83
    throw std::runtime_error("Could not decrypt message of size " + std::to_string(msg.length()));
84
  }
85
86
  if (!isValidKey(key)) {
87
    throw std::runtime_error("Invalid decryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
88
  }
89
90
  decrypted.resize(msg.length() - crypto_secretbox_MACBYTES);
91
92
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
93
  if (crypto_secretbox_open_easy(reinterpret_cast<unsigned char*>(decrypted.data()),
94
                                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
95
                                 reinterpret_cast<const unsigned char*>(msg.data()),
96
                                 msg.length(),
97
                                 nonce.value.data(),
98
                                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
99
                                 reinterpret_cast<const unsigned char*>(key.data()))
100
      != 0) {
101
    throw std::runtime_error("Could not decrypt message, please check that the key configured with setKey() is correct");
102
  }
103
104
  if (incrementNonce) {
105
    nonce.increment();
106
  }
107
108
  return decrypted;
109
}
110
111
void Nonce::init()
112
{
113
  randombytes_buf(value.data(), value.size());
114
}
115
116
#elif defined(HAVE_LIBCRYPTO)
117
#include <openssl/evp.h>
118
#include <openssl/rand.h>
119
120
static constexpr size_t s_CHACHA20_POLY1305_KEY_SIZE = 32U;
121
static constexpr size_t s_POLY1305_BLOCK_SIZE = 16U;
122
123
string newKey(bool base64Encoded)
124
0
{
125
0
  std::string key;
126
0
  key.resize(s_CHACHA20_POLY1305_KEY_SIZE);
127
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
128
0
  if (RAND_priv_bytes(reinterpret_cast<unsigned char*>(key.data()), key.size()) != 1) {
129
0
    throw std::runtime_error("Could not initialize random number generator for cryptographic functions");
130
0
  }
131
0
  if (!base64Encoded) {
132
0
    return key;
133
0
  }
134
0
  return "\"" + Base64Encode(key) + "\"";
135
0
}
136
137
bool isValidKey(const std::string& key)
138
0
{
139
0
  return key.size() == s_CHACHA20_POLY1305_KEY_SIZE;
140
0
}
141
142
std::string encryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
143
0
{
144
0
  if (!isValidKey(key)) {
145
0
    throw std::runtime_error("Invalid encryption key of size " + std::to_string(key.size()) + " (" + std::to_string(s_CHACHA20_POLY1305_KEY_SIZE) + " expected), use setKey() to set a valid key");
146
0
  }
147
148
  // Each thread gets its own cipher context
149
0
  static thread_local auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(nullptr, EVP_CIPHER_CTX_free);
150
151
0
  if (!ctx) {
152
0
    ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
153
0
    if (!ctx) {
154
0
      throw std::runtime_error("encryptSym: EVP_CIPHER_CTX_new() could not initialize cipher context");
155
0
    }
156
157
0
    if (EVP_EncryptInit_ex(ctx.get(), EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) != 1) {
158
0
      throw std::runtime_error("encryptSym: EVP_EncryptInit_ex() could not initialize encryption operation");
159
0
    }
160
0
  }
161
162
0
  std::string ciphertext;
163
  /* plus one so we can access the last byte in EncryptFinal which does nothing for this algo */
164
0
  ciphertext.resize(s_POLY1305_BLOCK_SIZE + msg.length() + 1);
165
0
  int outLength{0};
166
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
167
0
  if (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char*>(key.c_str()), nonce.value.data()) != 1) {
168
0
    throw std::runtime_error("encryptSym: EVP_EncryptInit_ex() could not initialize encryption key and IV");
169
0
  }
170
171
0
  if (!msg.empty()) {
172
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
173
0
    if (EVP_EncryptUpdate(ctx.get(),
174
0
                          reinterpret_cast<unsigned char*>(&ciphertext.at(s_POLY1305_BLOCK_SIZE)), &outLength,
175
0
                          reinterpret_cast<const unsigned char*>(msg.data()), msg.length())
176
0
        != 1) {
177
0
      throw std::runtime_error("encryptSym: EVP_EncryptUpdate() could not encrypt message");
178
0
    }
179
0
  }
180
181
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
182
0
  if (EVP_EncryptFinal_ex(ctx.get(), reinterpret_cast<unsigned char*>(&ciphertext.at(s_POLY1305_BLOCK_SIZE + outLength)), &outLength) != 1) {
183
0
    throw std::runtime_error("encryptSym: EVP_EncryptFinal_ex() could finalize message encryption");
184
0
    ;
185
0
  }
186
187
  /* Get the tag */
188
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
189
0
  if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, s_POLY1305_BLOCK_SIZE, ciphertext.data()) != 1) {
190
0
    throw std::runtime_error("encryptSym: EVP_CIPHER_CTX_ctrl() could not get tag");
191
0
  }
192
193
0
  if (incrementNonce) {
194
0
    nonce.increment();
195
0
  }
196
197
0
  ciphertext.resize(ciphertext.size() - 1);
198
0
  return ciphertext;
199
0
}
200
201
std::string decryptSym(const std::string_view& msg, const std::string& key, Nonce& nonce, bool incrementNonce)
202
0
{
203
0
  if (msg.length() < s_POLY1305_BLOCK_SIZE) {
204
0
    throw std::runtime_error("Could not decrypt message of size " + std::to_string(msg.length()));
205
0
  }
206
207
0
  if (!isValidKey(key)) {
208
0
    throw std::runtime_error("Invalid decryption key of size " + std::to_string(key.size()) + ", use setKey() to set a valid key");
209
0
  }
210
211
0
  if (msg.length() == s_POLY1305_BLOCK_SIZE) {
212
0
    if (incrementNonce) {
213
0
      nonce.increment();
214
0
    }
215
0
    return std::string();
216
0
  }
217
218
  // Each thread gets its own cipher context
219
0
  static thread_local auto ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(nullptr, EVP_CIPHER_CTX_free);
220
0
  if (!ctx) {
221
0
    ctx = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
222
0
    if (!ctx) {
223
0
      throw std::runtime_error("decryptSym: EVP_CIPHER_CTX_new() could not initialize cipher context");
224
0
    }
225
226
0
    if (EVP_DecryptInit_ex(ctx.get(), EVP_chacha20_poly1305(), nullptr, nullptr, nullptr) != 1) {
227
0
      throw std::runtime_error("decryptSym: EVP_DecryptInit_ex() could not initialize decryption operation");
228
0
    }
229
0
  }
230
231
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
232
0
  if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char*>(key.c_str()), nonce.value.data()) != 1) {
233
0
    throw std::runtime_error("decryptSym: EVP_DecryptInit_ex() could not initialize decryption key and IV");
234
0
  }
235
236
0
  const auto tag = msg.substr(0, s_POLY1305_BLOCK_SIZE);
237
0
  std::string decrypted;
238
  /* plus one so we can access the last byte in DecryptFinal, which does nothing */
239
0
  decrypted.resize(msg.length() - s_POLY1305_BLOCK_SIZE + 1);
240
0
  int outLength{0};
241
0
  if (msg.size() > s_POLY1305_BLOCK_SIZE) {
242
0
    if (!EVP_DecryptUpdate(ctx.get(),
243
                           // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
244
0
                           reinterpret_cast<unsigned char*>(decrypted.data()), &outLength,
245
                           // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
246
0
                           reinterpret_cast<const unsigned char*>(&msg.at(s_POLY1305_BLOCK_SIZE)), msg.size() - s_POLY1305_BLOCK_SIZE)) {
247
0
      throw std::runtime_error("Could not decrypt message (update failed), please check that the key configured with setKey() is correct");
248
0
    }
249
0
  }
250
251
  /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
252
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): sorry, OpenSSL's API is terrible
253
0
  if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG, s_POLY1305_BLOCK_SIZE, const_cast<char*>(tag.data()))) {
254
0
    throw std::runtime_error("Could not decrypt message (invalid tag), please check that the key configured with setKey() is correct");
255
0
  }
256
257
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
258
0
  if (!EVP_DecryptFinal_ex(ctx.get(), reinterpret_cast<unsigned char*>(&decrypted.at(outLength)), &outLength)) {
259
0
    throw std::runtime_error("Could not decrypt message (final failed), please check that the key configured with setKey() is correct");
260
0
  }
261
262
0
  if (incrementNonce) {
263
0
    nonce.increment();
264
0
  }
265
266
0
  decrypted.resize(decrypted.size() - 1);
267
0
  return decrypted;
268
0
}
269
270
void Nonce::init()
271
0
{
272
0
  if (RAND_priv_bytes(value.data(), value.size()) != 1) {
273
0
    throw std::runtime_error("Could not initialize random number generator for cryptographic functions");
274
0
  }
275
0
}
276
#endif
277
278
#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
279
void Nonce::merge(const Nonce& lower, const Nonce& higher)
280
0
{
281
0
  constexpr size_t halfSize = std::tuple_size<decltype(value)>{} / 2;
282
0
  memcpy(value.data(), lower.value.data(), halfSize);
283
0
  memcpy(value.data() + halfSize, higher.value.data() + halfSize, halfSize);
284
0
}
285
286
void Nonce::increment()
287
0
{
288
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
289
0
  auto* ptr = reinterpret_cast<uint32_t*>(value.data());
290
0
  uint32_t count = htonl(*ptr) + 1;
291
0
  *ptr = ntohl(count);
292
0
}
293
294
#else
295
void Nonce::init()
296
{
297
}
298
299
void Nonce::merge(const Nonce&, const Nonce&)
300
{
301
}
302
303
void Nonce::increment()
304
{
305
}
306
307
std::string encryptSym(const std::string_view& msg, const std::string&, Nonce&, bool)
308
{
309
  return std::string(msg);
310
}
311
std::string decryptSym(const std::string_view& msg, const std::string&, Nonce&, bool)
312
{
313
  return std::string(msg);
314
}
315
316
string newKey(bool)
317
{
318
  return "\"plaintext\"";
319
}
320
321
bool isValidKey(const std::string&)
322
{
323
  return true;
324
}
325
326
#endif
327
}
328
329
#include <cinttypes>
330
331
namespace anonpdns
332
{
333
static char B64Decode1(char cInChar)
334
0
{
335
  // The incoming character will be A-Z, a-z, 0-9, +, /, or =.
336
  // The idea is to quickly determine which grouping the
337
  // letter belongs to and return the associated value
338
  // without having to search the global encoding string
339
  // (the value we're looking for would be the resulting
340
  // index into that string).
341
  //
342
  // To do that, we'll play some tricks...
343
0
  unsigned char iIndex = '\0';
344
0
  switch (cInChar) {
345
0
  case '+':
346
0
    iIndex = 62;
347
0
    break;
348
349
0
  case '/':
350
0
    iIndex = 63;
351
0
    break;
352
353
0
  case '=':
354
0
    iIndex = 0;
355
0
    break;
356
357
0
  default:
358
    // Must be 'A'-'Z', 'a'-'z', '0'-'9', or an error...
359
    //
360
    // Numerically, small letters are "greater" in value than
361
    // capital letters and numerals (ASCII value), and capital
362
    // letters are "greater" than numerals (again, ASCII value),
363
    // so we check for numerals first, then capital letters,
364
    // and finally small letters.
365
0
    iIndex = '9' - cInChar;
366
0
    if (iIndex > 0x3F) {
367
      // Not from '0' to '9'...
368
0
      iIndex = 'Z' - cInChar;
369
0
      if (iIndex > 0x3F) {
370
        // Not from 'A' to 'Z'...
371
0
        iIndex = 'z' - cInChar;
372
0
        if (iIndex > 0x3F) {
373
          // Invalid character...cannot
374
          // decode!
375
0
          iIndex = 0x80; // set the high bit
376
0
        } // if
377
0
        else {
378
          // From 'a' to 'z'
379
0
          iIndex = (('z' - iIndex) - 'a') + 26;
380
0
        } // else
381
0
      } // if
382
0
      else {
383
        // From 'A' to 'Z'
384
0
        iIndex = ('Z' - iIndex) - 'A';
385
0
      } // else
386
0
    } // if
387
0
    else {
388
      // Adjust the index...
389
0
      iIndex = (('9' - iIndex) - '0') + 52;
390
0
    } // else
391
0
    break;
392
393
0
  } // switch
394
395
0
  return static_cast<char>(iIndex);
396
0
}
397
398
static inline char B64Encode1(unsigned char input)
399
0
{
400
0
  if (input < 26) {
401
0
    return static_cast<char>('A' + input);
402
0
  }
403
0
  if (input < 52) {
404
0
    return static_cast<char>('a' + (input - 26));
405
0
  }
406
0
  if (input < 62) {
407
0
    return static_cast<char>('0' + (input - 52));
408
0
  }
409
0
  if (input == 62) {
410
0
    return '+';
411
0
  }
412
0
  return '/';
413
0
};
414
415
}
416
using namespace anonpdns;
417
418
template <typename Container>
419
int B64Decode(const std::string& strInput, Container& strOutput)
420
0
{
421
  // Set up a decoding buffer
422
0
  long cBuf = 0;
423
  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
424
0
  char* pBuf = reinterpret_cast<char*>(&cBuf);
425
426
  // Decoding management...
427
0
  int iBitGroup = 0;
428
0
  int iInNum = 0;
429
430
  // While there are characters to process...
431
  //
432
  // We'll decode characters in blocks of 4, as
433
  // there are 4 groups of 6 bits in 3 bytes. The
434
  // incoming Base64 character is first decoded, and
435
  // then it is inserted into the decode buffer
436
  // (with any relevant shifting, as required).
437
  // Later, after all 3 bytes have been reconstituted,
438
  // we assign them to the output string, ultimately
439
  // to be returned as the original message.
440
0
  int iInSize = static_cast<int>(strInput.size());
441
0
  unsigned char cChar = '\0';
442
0
  uint8_t pad = 0;
443
0
  while (iInNum < iInSize) {
444
    // Fill the decode buffer with 4 groups of 6 bits
445
0
    cBuf = 0; // clear
446
0
    pad = 0;
447
0
    for (iBitGroup = 0; iBitGroup < 4; ++iBitGroup) {
448
0
      if (iInNum < iInSize) {
449
        // Decode a character
450
0
        if (strInput.at(iInNum) == '=') {
451
0
          pad++;
452
0
        }
453
0
        while (isspace(strInput.at(iInNum))) {
454
0
          iInNum++;
455
0
          if (iInNum >= iInSize) {
456
0
            return -1;
457
0
          }
458
0
        }
459
0
        cChar = B64Decode1(strInput.at(iInNum++));
460
461
0
      } // if
462
0
      else {
463
        // Decode a padded zero
464
0
        cChar = '\0';
465
0
      } // else
466
467
      // Check for valid decode
468
0
      if (cChar > 0x7F) {
469
0
        return -1;
470
0
      }
471
472
      // Adjust the bits
473
0
      switch (iBitGroup) {
474
0
      case 0:
475
        // The first group is copied into
476
        // the least significant 6 bits of
477
        // the decode buffer...these 6 bits
478
        // will eventually shift over to be
479
        // the most significant bits of the
480
        // third byte.
481
0
        cBuf = cBuf | cChar;
482
0
        break;
483
484
0
      default:
485
        // For groupings 1-3, simply shift
486
        // the bits in the decode buffer over
487
        // by 6 and insert the 6 from the
488
        // current decode character.
489
0
        cBuf = (cBuf << 6) | cChar;
490
0
        break;
491
492
0
      } // switch
493
0
    } // for
494
495
    // Interpret the resulting 3 bytes...note there
496
    // may have been padding, so those padded bytes
497
    // are actually ignored.
498
#if BYTE_ORDER == BIG_ENDIAN
499
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
500
    strOutput.push_back(pBuf[sizeof(long) - 3]);
501
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
502
    strOutput.push_back(pBuf[sizeof(long) - 2]);
503
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
504
    strOutput.push_back(pBuf[sizeof(long) - 1]);
505
#else
506
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
507
0
    strOutput.push_back(pBuf[2]);
508
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
509
0
    strOutput.push_back(pBuf[1]);
510
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
511
0
    strOutput.push_back(pBuf[0]);
512
0
#endif
513
0
  } // while
514
0
  if (pad) {
515
0
    if (pad > strOutput.size()) {
516
0
      return -1; // padding-only (or otherwise invalid) Base64
517
0
    }
518
0
    strOutput.resize(strOutput.size() - pad);
519
0
  }
520
521
0
  return 1;
522
0
}
Unexecuted instantiation: int B64Decode<std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > >(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<unsigned char, std::__1::allocator<unsigned char> >&)
Unexecuted instantiation: int B64Decode<std::__1::vector<unsigned char, noinit_adaptor<std::__1::allocator<unsigned char> > > >(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<unsigned char, noinit_adaptor<std::__1::allocator<unsigned char> > >&)
523
524
template int B64Decode<std::vector<uint8_t>>(const std::string& strInput, std::vector<uint8_t>& strOutput);
525
template int B64Decode<PacketBuffer>(const std::string& strInput, PacketBuffer& strOutput);
526
template int B64Decode<std::string>(const std::string& strInput, std::string& strOutput);
527
528
/*
529
www.kbcafe.com
530
Copyright 2001-2002 Randy Charles Morin
531
The Encode static method takes an array of 8-bit values and returns a base-64 stream.
532
*/
533
534
std::string Base64Encode(const std::string& src)
535
0
{
536
0
  std::string retval;
537
0
  if (src.empty()) {
538
0
    return retval;
539
0
  }
540
0
  for (unsigned int i = 0; i < src.size(); i += 3) {
541
0
    unsigned char by1 = 0;
542
0
    unsigned char by2 = 0;
543
0
    unsigned char by3 = 0;
544
0
    by1 = src[i];
545
0
    if (i + 1 < src.size()) {
546
0
      by2 = src[i + 1];
547
0
    };
548
0
    if (i + 2 < src.size()) {
549
0
      by3 = src[i + 2];
550
0
    }
551
0
    unsigned char by4 = 0;
552
0
    unsigned char by5 = 0;
553
0
    unsigned char by6 = 0;
554
0
    unsigned char by7 = 0;
555
0
    by4 = by1 >> 2;
556
0
    by5 = ((by1 & 0x3) << 4) | (by2 >> 4);
557
0
    by6 = ((by2 & 0xf) << 2) | (by3 >> 6);
558
0
    by7 = by3 & 0x3f;
559
0
    retval += B64Encode1(by4);
560
0
    retval += B64Encode1(by5);
561
0
    if (i + 1 < src.size()) {
562
0
      retval += B64Encode1(by6);
563
0
    }
564
0
    else {
565
0
      retval += "=";
566
0
    };
567
0
    if (i + 2 < src.size()) {
568
0
      retval += B64Encode1(by7);
569
0
    }
570
0
    else {
571
0
      retval += "=";
572
0
    };
573
    /*      if ((i % (76 / 4 * 3)) == 0)
574
      {
575
        retval += "\r\n";
576
        }*/
577
0
  };
578
0
  return retval;
579
0
};