libkpgp

kpgpbase5.cpp
1/*
2 kpgpbase5.cpp
3
4 Copyright (C) 2001,2002 the KPGP authors
5 See file AUTHORS.kpgp for details
6
7 This file is part of KPGP, the KDE PGP/GnuPG support library.
8
9 KPGP is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19#ifdef HAVE_CONFIG_H
20#include <config.h>
21#endif
22
23#include "kpgpbase.h"
24#include "kpgp.h"
25
26#include <string.h> /* strncmp */
27#include <assert.h>
28
29#include <tqregexp.h>
30#include <tqdatetime.h>
31
32#include <tdelocale.h>
33#include <tdeprocess.h>
34#include <kdebug.h>
35
36
37namespace Kpgp {
38
39Base5::Base5()
40 : Base()
41{
42}
43
44
45Base5::~Base5()
46{
47}
48
49
50int
51Base5::encrypt( Block& block, const KeyIDList& recipients )
52{
53 return encsign( block, recipients, 0 );
54}
55
56
57int
58Base5::clearsign( Block& block, const TQString &passphrase )
59{
60 return encsign( block, KeyIDList(), passphrase );
61}
62
63
64int
65Base5::encsign( Block& block, const KeyIDList& recipients,
66 const TQString &passphrase )
67{
68 TQCString cmd;
69 int exitStatus = 0;
70 int index;
71 // used to work around a bug in pgp5. pgp5 treats files
72 // with non ascii chars (umlauts, etc...) as binary files, but
73 // we want a clear signature
74 bool signonly = false;
75
76 if (!recipients.isEmpty() && !passphrase.isEmpty())
77 cmd = "pgpe +batchmode -afts ";
78 else if(!recipients.isEmpty())
79 cmd = "pgpe +batchmode -aft ";
80 else if (!passphrase.isEmpty())
81 {
82 cmd = "pgps +batchmode -abft ";
83 signonly = true;
84 }
85 else
86 {
87 errMsg = i18n("Neither recipients nor passphrase specified.");
88 return OK;
89 }
90
91 if (!passphrase.isEmpty())
92 cmd += addUserId();
93
94 if(!recipients.isEmpty())
95 {
96 if(Module::getKpgp()->encryptToSelf())
97 {
98 cmd += " -r 0x";
99 cmd += Module::getKpgp()->user();
100 }
101
102 for( KeyIDList::ConstIterator it = recipients.begin();
103 it != recipients.end(); ++it ) {
104 cmd += " -r 0x";
105 cmd += (*it);
106 }
107 }
108
109 clear();
110 input = block.text();
111
112 if (signonly)
113 {
114 input.append("\n");
115 input.replace(TQRegExp("[ \t]+\n"), "\n"); //strip trailing whitespace
116 }
117 //We have to do this otherwise it's all in vain
118
119 exitStatus = run(cmd.data(), passphrase);
120 block.setError( error );
121
122 if(exitStatus != 0)
123 status = ERROR;
124
125 // now parse the returned info
126 if(error.find("Cannot unlock private key") != -1)
127 {
128 errMsg = i18n("The passphrase you entered is invalid.");
129 status |= ERROR;
130 status |= BADPHRASE;
131 }
132//if(!ignoreUntrusted)
133//{
134 TQCString aStr;
135 index = -1;
136 while((index = error.find("WARNING: The above key",index+1)) != -1)
137 {
138 int index2 = error.find("But you previously",index);
139 int index3 = error.find("WARNING: The above key",index+1);
140 if(index2 == -1 || (index2 > index3 && index3 != -1))
141 {
142 // the key wasn't valid, no encryption to this person
143 // extract the person
144 index2 = error.find('\n',index);
145 index3 = error.find('\n',index2+1);
146 aStr += error.mid(index2+1, index3-index2-1);
147 aStr += ", ";
148 }
149 }
150 if(!aStr.isEmpty())
151 {
152 aStr.truncate(aStr.length()-2);
153 if(error.find("No valid keys found") != -1)
154 errMsg = i18n("The key(s) you want to encrypt your message "
155 "to are not trusted. No encryption done.");
156 else
157 errMsg = i18n("The following key(s) are not trusted:\n%1\n"
158 "Their owner(s) will not be able to decrypt the message.")
159 .arg(TQString(aStr));
160 status |= ERROR;
161 status |= BADKEYS;
162 }
163//}
164 if((index = error.find("No encryption keys found for")) != -1)
165 {
166 index = error.find(':',index);
167 int index2 = error.find('\n',index);
168
169 errMsg = i18n("Missing encryption key(s) for:\n%1")
170 .arg(TQString(error.mid(index,index2-index)));
171// errMsg = TQString("Missing encryption key(s) for: %1")
172// .arg(error.mid(index,index2-index));
173 status |= ERROR;
174 status |= MISSINGKEY;
175 }
176
177 if(signonly) {
178 // dash-escape the input
179 if (input[0] == '-')
180 input = "- " + input;
181 for ( int idx = 0 ; (idx = input.find("\n-", idx)) >= 0 ; idx += 4 )
182 input.replace(idx, 2, "\n- -");
183
184 output = "-----BEGIN PGP SIGNED MESSAGE-----\n\n" + input + "\n" + output;
185 }
186
187 block.setProcessedText( output );
188 block.setStatus( status );
189 return status;
190}
191
192
193int
194Base5::decrypt( Block& block, const TQString &passphrase )
195{
196 int exitStatus = 0;
197
198 clear();
199 input = block.text();
200 exitStatus = run("pgpv -f +batchmode=1", passphrase);
201 if( !output.isEmpty() )
202 block.setProcessedText( output );
203 block.setError( error );
204
205 if(exitStatus == -1) {
206 errMsg = i18n("Error running PGP");
207 status = RUN_ERR;
208 block.setStatus( status );
209 return status;
210 }
211
212 // lets parse the returned information.
213 int index;
214
215 index = error.find("Cannot decrypt message");
216 if(index != -1)
217 {
218 //kdDebug(5100) << "message is encrypted" << endl;
219 status |= ENCRYPTED;
220
221 // ok. we have an encrypted message. Is the passphrase bad,
222 // or do we not have the secret key?
223 if(error.find("Need a pass phrase") != -1)
224 {
225 if (!passphrase.isEmpty())
226 {
227 errMsg = i18n("Bad passphrase; could not decrypt.");
228 kdDebug(5100) << "Base: passphrase is bad" << endl;
229 status |= BADPHRASE;
230 status |= ERROR;
231 }
232 }
233 else
234 {
235 // we don't have the secret key
236 status |= NO_SEC_KEY;
237 status |= ERROR;
238 errMsg = i18n("You do not have the secret key needed to decrypt this message.");
239 kdDebug(5100) << "Base: no secret key for this message" << endl;
240 }
241 // check for persons
242#if 0
243 // ##### FIXME: This information is anyway currently not used
244 // I'll change it to always determine the recipients.
245 index = error.find("can only be decrypted by:");
246 if(index != -1)
247 {
248 index = error.find('\n',index);
249 int end = error.find("\n\n",index);
250
251 mRecipients.clear();
252 int index2;
253 while( (index2 = error.find('\n',index+1)) <= end )
254 {
255 TQCString item = error.mid(index+1,index2-index-1);
256 item.stripWhiteSpace();
257 mRecipients.append(item);
258 index = index2;
259 }
260 }
261#endif
262 }
263 index = error.find("Good signature");
264 if(index != -1)
265 {
266 //kdDebug(5100) << "good signature" << endl;
267 status |= SIGNED;
268 status |= GOODSIG;
269
270 // get key ID of signer
271 index = error.find("Key ID ", index) + 7;
272 block.setSignatureKeyId( error.mid(index, 8) );
273
274 // get signer
275 index = error.find('"',index) + 1;
276 int index2 = error.find('"', index);
277 block.setSignatureUserId( error.mid(index, index2-index) );
278
280 block.setSignatureDate( "" );
281 }
282 index = error.find("BAD signature");
283 if(index != -1)
284 {
285 //kdDebug(5100) << "BAD signature" << endl;
286 status |= SIGNED;
287 status |= ERROR;
288
289 // get key ID of signer
290 index = error.find("Key ID ", index) + 7;
291 block.setSignatureKeyId( error.mid(index, 8) );
292
293 // get signer
294 index = error.find('"',index) + 1;
295 int index2 = error.find('"', index);
296 block.setSignatureUserId( error.mid(index, index2-index) );
297
299 block.setSignatureDate( "" );
300 }
301 index = error.find("Signature by unknown key");
302 if(index != -1)
303 {
304 index = error.find("keyid: 0x",index) + 9;
305 block.setSignatureKeyId( error.mid(index, 8) );
306 block.setSignatureUserId( TQString() );
307 // FIXME: not a very good solution...
308 status |= SIGNED;
309 status |= GOODSIG;
310
312 block.setSignatureDate( "" );
313 }
314
315 //kdDebug(5100) << "status = " << status << endl;
316 block.setStatus( status );
317 return status;
318}
319
320
321Key*
322Base5::readPublicKey( const KeyID& keyId, const bool readTrust, Key* key )
323{
324 int exitStatus = 0;
325
326 status = 0;
327 exitStatus = run( "pgpk -ll 0x" + keyId, 0, true );
328
329 if(exitStatus != 0) {
330 status = ERROR;
331 return 0;
332 }
333
334 key = parseSingleKey( output, key );
335
336 if( key == 0 )
337 {
338 return 0;
339 }
340
341 if( readTrust )
342 {
343 exitStatus = run( "pgpk -c 0x" + keyId, 0, true );
344
345 if(exitStatus != 0) {
346 status = ERROR;
347 return 0;
348 }
349
350 parseTrustDataForKey( key, output );
351 }
352
353 return key;
354}
355
356
357KeyList
358Base5::publicKeys( const TQStringList & patterns )
359{
360 int exitStatus = 0;
361
362 TQCString cmd = "pgpk -ll";
363 for ( TQStringList::ConstIterator it = patterns.begin();
364 it != patterns.end(); ++it ) {
365 cmd += " ";
366 cmd += TDEProcess::quote( *it ).local8Bit();
367 }
368 status = 0;
369 exitStatus = run( cmd, 0, true );
370
371 if(exitStatus != 0) {
372 status = ERROR;
373 return KeyList();
374 }
375
376 // now we need to parse the output for public keys
377 KeyList keys = parseKeyList( output, false );
378
379 // sort the list of public keys
380 keys.sort();
381
382 return keys;
383}
384
385
386KeyList
387Base5::secretKeys( const TQStringList & patterns )
388{
389 int exitStatus = 0;
390
391 status = 0;
392 TQCString cmd = "pgpk -ll";
393 for ( TQStringList::ConstIterator it = patterns.begin();
394 it != patterns.end(); ++it ) {
395 cmd += " ";
396 cmd += TDEProcess::quote( *it ).local8Bit();
397 }
398 status = 0;
399 exitStatus = run( cmd, 0, true );
400
401 if(exitStatus != 0) {
402 status = ERROR;
403 return KeyList();
404 }
405
406 // now we need to parse the output for secret keys
407 KeyList keys = parseKeyList( output, true );
408
409 // sort the list of public keys
410 keys.sort();
411
412 return keys;
413}
414
415
416TQCString Base5::getAsciiPublicKey(const KeyID& keyID)
417{
418 int exitStatus = 0;
419
420 if (keyID.isEmpty())
421 return TQCString();
422
423 status = 0;
424 exitStatus = run( "pgpk -xa 0x" + keyID, 0, true );
425
426 if(exitStatus != 0) {
427 status = ERROR;
428 return TQCString();
429 }
430
431 return output;
432}
433
434
435int
436Base5::signKey(const KeyID& keyID, const TQString &passphrase)
437{
438 TQCString cmd;
439 int exitStatus = 0;
440
441 if (passphrase.isEmpty()) return false;
442
443 cmd = "pgpk -s -f +batchmode=1 0x";
444 cmd += keyID;
445 cmd += addUserId();
446
447 status = 0;
448 exitStatus = run(cmd.data(), passphrase);
449
450 if (exitStatus != 0)
451 status = ERROR;
452
453 return status;
454}
455
456//-- private functions --------------------------------------------------------
457
458Key*
459Base5::parseKeyData( const TQCString& output, int& offset, Key* key /* = 0 */ )
460// This function parses the data for a single key which is output by PGP 5
461// with the following command line:
462// pgpk -ll
463// It expects the key data to start at offset and returns the start of
464// the next key's data in offset.
465{
466 if( ( strncmp( output.data() + offset, "pub", 3 ) != 0 ) &&
467 ( strncmp( output.data() + offset, "sec", 3 ) != 0 ) )
468 {
469 kdDebug(5100) << "Unknown key type or corrupt key data.\n";
470 return 0;
471 }
472
473 if( key == 0 )
474 key = new Key();
475 else
476 key->clear();
477
478 Subkey *subkey = 0;
479 bool primaryKey = true;
480
481 while( true )
482 {
483 int eol;
484
485 // search the end of the current line
486 eol = output.find( '\n', offset );
487 if( ( eol == -1 ) || ( eol == offset ) )
488 break;
489
490 //kdDebug(5100) << "Parsing: " << output.mid(offset, eol-offset) << endl;
491
492 if( !strncmp( output.data() + offset, "pub", 3 ) ||
493 !strncmp( output.data() + offset, "sec", 3 ) ||
494 !strncmp( output.data() + offset, "sub", 3 ) )
495 { // line contains key data
496 //kdDebug(5100)<<"Key data:\n";
497 int pos, pos2;
498
499 subkey = new Subkey( "", false );
500 key->addSubkey( subkey );
501
502 // Key Flags
503 /* From the PGP 5 manual page for pgpk:
504 Following this column is a single character which
505 describes other attributes of the object:
506
507 @ The object is disabled
508 + The object is axiomatically trusted (i.e., it's
509 your key)
510 */
511 switch( output[offset+3] )
512 {
513 case ' ': // nothing special
514 break;
515 case '@': // disabled key
516 subkey->setDisabled( true );
517 key->setDisabled( true );
518 break;
519 default: // all other flags are ignored
520 //kdDebug(5100) << "Unknown key flag.\n";
521 ;
522 }
523
524 // Key Length
525 pos = offset + 4;
526 while( output[pos] == ' ' )
527 pos++;
528 pos2 = output.find( ' ', pos );
529 subkey->setKeyLength( output.mid( pos, pos2-pos ).toUInt() );
530 //kdDebug(5100) << "Key Length: "<<subkey->keyLength()<<endl;
531
532 // Key ID
533 pos = pos2 + 1;
534 while( output[pos] == ' ' )
535 pos++;
536 pos += 2; // skip the '0x'
537 pos2 = output.find( ' ', pos );
538 subkey->setKeyID( output.mid( pos, pos2-pos ) );
539 //kdDebug(5100) << "Key ID: "<<subkey->keyID()<<endl;
540
541 // Creation Date
542 pos = pos2 + 1;
543 while( output[pos] == ' ' )
544 pos++;
545 pos2 = output.find( ' ', pos );
546 int year = output.mid( pos, 4 ).toInt();
547 int month = output.mid( pos+5, 2 ).toInt();
548 int day = output.mid( pos+8, 2 ).toInt();
549 TQDateTime dt( TQDate( year, month, day ), TQTime( 00, 00 ) );
550 TQDateTime epoch( TQDate( 1970, 01, 01 ), TQTime( 00, 00 ) );
551 // The calculated creation date isn't exactly correct because TQDateTime
552 // doesn't know anything about timezones and always assumes local time
553 // although epoch is of course UTC. But as PGP 5 anyway doesn't print
554 // the time this doesn't matter too much.
555 subkey->setCreationDate( epoch.secsTo( dt ) );
556
557 // Expiration Date
558 // if the primary key has been revoked the expiration date is not printed
559 if( primaryKey || !key->revoked() )
560 {
561 pos = pos2 + 1;
562 while( output[pos] == ' ' )
563 pos++;
564 pos2 = output.find( ' ', pos );
565 if( output[pos] == '-' )
566 { // key doesn't expire
567 subkey->setExpirationDate( -1 );
568 }
569 else if( !strncmp( output.data() + pos, "*REVOKED*", 9 ) )
570 { // key has been revoked
571 subkey->setRevoked( true );
572 key->setRevoked( true );
573 }
574 else
575 {
576 int year = output.mid( pos, 4 ).toInt();
577 int month = output.mid( pos+5, 2 ).toInt();
578 int day = output.mid( pos+8, 2 ).toInt();
579 TQDateTime dt( TQDate( year, month, day ), TQTime( 00, 00 ) );
580 subkey->setCreationDate( epoch.secsTo( dt ) );
581 // has the key already expired?
582 if( TQDateTime::currentDateTime() >= dt )
583 {
584 subkey->setExpired( true );
585 key->setExpired( true );
586 }
587 }
588 }
589 else if( key->revoked() )
590 subkey->setRevoked( true );
591
592 // Key algorithm (RSA, DSS, Diffie-Hellman)
593 bool sign = false;
594 bool encr = false;
595 pos = pos2 + 1;
596 while( output[pos] == ' ' )
597 pos++;
598 pos2 = output.find( ' ', pos );
599 if( !strncmp( output.data() + pos, "RSA", 3 ) )
600 {
601 sign = true;
602 encr = true;
603 }
604 else if( !strncmp( output.data() + pos, "DSS", 3 ) )
605 sign = true;
606 else if( !strncmp( output.data() + pos, "Diffie-Hellman", 14 ) )
607 encr = true;
608 else
609 kdDebug(5100)<<"Unknown key algorithm\n";
610
611 // set key capabilities of the subkey
612 subkey->setCanEncrypt( encr );
613 subkey->setCanSign( sign );
614 subkey->setCanCertify( sign );
615
616 if( primaryKey )
617 {
618 // Global key capabilities
619 bool canSign = false;
620 bool canEncr = false;
621 pos = pos2 + 1;
622 while( output[pos] == ' ' )
623 pos++;
624 pos2 = eol;
625 if( !strncmp( output.data() + pos, "Sign & Encrypt", 14 ) )
626 {
627 canSign = true;
628 canEncr = true;
629 }
630 else if( !strncmp( output.data() + pos, "Sign only", 9 ) )
631 canSign = true;
632 else if( !strncmp( output.data() + pos, "Encrypt only", 12 ) )
633 canEncr = true;
634 else
635 kdDebug(5100)<<"Unknown key capability\n";
636
637 // set the global key capabilities
638 if( !key->expired() && !key->revoked() )
639 {
640 key->setCanEncrypt( canEncr );
641 key->setCanSign( canSign );
642 key->setCanCertify( canSign );
643 }
644 //kdDebug(5100)<<"Key capabilities: "<<(key->canEncrypt()?"E":"")<<(key->canSign()?"SC":"")<<endl;
645 primaryKey = false;
646 }
647 }
648 else if( !strncmp( output.data() + offset, "f16", 3 ) ||
649 !strncmp( output.data() + offset, "f20", 3 ) )
650 { // line contains a fingerprint
651 /* Examples:
652 f16 Fingerprint16 = DE 2A 77 08 78 64 7C 42 72 75 B1 A7 3E 42 3F 79
653 f20 Fingerprint20 = 226F 4B63 6DA2 7389 91D1 2A49 D58A 3EC1 5214 181E
654
655 */
656 int pos = output.find( '=', offset+3 ) + 2;
657 TQCString fingerprint = output.mid( pos, eol-pos );
658 // remove white space from the fingerprint
659 for ( int idx = 0 ; (idx = fingerprint.find(' ', idx)) >= 0 ; )
660 fingerprint.replace( idx, 1, "" );
661 assert( subkey != 0 );
662 subkey->setFingerprint( fingerprint );
663 //kdDebug(5100)<<"Fingerprint: "<<fingerprint<<endl;
664 }
665 else if( !strncmp( output.data() + offset, "uid", 3 ) )
666 { // line contains a uid
667 int pos = offset+5;
668 TQCString uid = output.mid( pos, eol-pos );
669 key->addUserID( uid );
670 // displaying of uids which contain non-ASCII characters is broken in
671 // PGP 5.0i; it shows these characters as \ooo and truncates the uid
672 // because it doesn't take the 3 extra characters per non-ASCII char
673 // into account. Example (with an UTF-8 encoded &ouml;):
674 // uid Ingo Kl\303\266cker <ingo.kloecker@epo
675 // because of this and because we anyway don't know which charset was
676 // used to encode the uid we don't try to decode it
677 }
678 else if ( !strncmp( output.data() + offset, "sig", 3 ) ||
679 !strncmp( output.data() + offset, "SIG", 3 ) ||
680 !strncmp( output.data() + offset, "ret", 3 ) )
681 { // line contains a signature
682 // SIG = sig with own key; ret = sig with revoked key
683 // we ignore it for now
684 }
685
686 offset = eol + 1;
687 }
688
689 return key;
690}
691
692
693Key*
694Base5::parseSingleKey( const TQCString& output, Key* key /* = 0 */ )
695{
696 int offset;
697
698 // search start of header line
699 if( !strncmp( output.data(), "Type Bits", 9 ) )
700 offset = 0;
701 else
702 {
703 offset = output.find( "\nType Bits" ) + 1;
704 if( offset == 0 )
705 return 0;
706 }
707
708 // key data begins in the next line
709 offset = output.find( '\n', offset ) + 1;
710 if( offset == -1 )
711 return 0;
712
713 key = parseKeyData( output, offset, key );
714
715 //kdDebug(5100) << "finished parsing keys" << endl;
716
717 return key;
718}
719
720
721KeyList
722Base5::parseKeyList( const TQCString& output, bool onlySecretKeys )
723{
724 KeyList keys;
725 Key *key = 0;
726 int offset;
727
728 // search start of header line
729 if( !strncmp( output.data(), "Type Bits", 9 ) )
730 offset = 0;
731 else
732 {
733 offset = output.find( "\nType Bits" ) + 1;
734 if( offset == 0 )
735 return keys;
736 }
737
738 // key data begins in the next line
739 offset = output.find( '\n', offset ) + 1;
740 if( offset == -1 )
741 return keys;
742
743 do
744 {
745 key = parseKeyData( output, offset );
746 if( key != 0 )
747 {
748 // if only secret keys should be read test if the key is secret
749 if( !onlySecretKeys || !key->secret() )
750 keys.append( key );
751 // skip the blank line which separates the keys
752 offset++;
753 }
754 }
755 while( key != 0 );
756
757 //kdDebug(5100) << "finished parsing keys" << endl;
758
759 return keys;
760}
761
762
763void
764Base5::parseTrustDataForKey( Key* key, const TQCString& str )
765{
766 if( ( key == 0 ) || str.isEmpty() )
767 return;
768
769 TQCString keyID = "0x" + key->primaryKeyID();
770 UserIDList userIDs = key->userIDs();
771
772 // search the start of the trust data
773 int offset = str.find( "\n\n KeyID" ) + 9;
774 if( offset == -1 + 9 )
775 return;
776
777 offset = str.find( '\n', offset ) + 1;
778 if( offset == -1 + 1 )
779 return;
780
781 bool ultimateTrust = false;
782 if( !strncmp( str.data() + offset+13, "ultimate", 8 ) )
783 ultimateTrust = true;
784
785 while( true )
786 { // loop over all trust information about this key
787
788 int eol;
789
790 // search the end of the current line
791 if( ( eol = str.find( '\n', offset ) ) == -1 )
792 break;
793
794 if( str[offset+23] != ' ' )
795 { // line contains a validity value for a user ID
796
797 // determine the validity
798 Validity validity = KPGP_VALIDITY_UNKNOWN;
799 if( !strncmp( str.data() + offset+23, "complete", 8 ) )
800 if( ultimateTrust )
801 validity = KPGP_VALIDITY_ULTIMATE;
802 else
803 validity = KPGP_VALIDITY_FULL;
804 else if( !strncmp( str.data() + offset+23, "marginal", 8 ) )
805 validity = KPGP_VALIDITY_MARGINAL;
806 else if( !strncmp( str.data() + offset+23, "invalid", 7 ) )
807 validity = KPGP_VALIDITY_UNDEFINED;
808
809 // determine the user ID
810 int pos = offset + 33;
811 TQString uid = str.mid( pos, eol-pos );
812
813 // set the validity of the corresponding user ID
814 for( UserIDListIterator it( userIDs ); it.current(); ++it )
815 if( (*it)->text() == uid )
816 {
817 kdDebug(5100)<<"Setting the validity of "<<uid<<" to "<<validity<<endl;
818 (*it)->setValidity( validity );
819 break;
820 }
821 }
822
823 offset = eol + 1;
824 }
825}
826
827
828} // namespace Kpgp