cpp: Add ostream operators for key and uid
[gpgme.git] / lang / cpp / src / key.cpp
1 /*
2   key.cpp - wraps a gpgme key
3   Copyright (C) 2003, 2005 Klarälvdalens Datakonsult AB
4
5   This file is part of GPGME++.
6
7   GPGME++ is free software; you can redistribute it and/or
8   modify it under the terms of the GNU Library General Public
9   License as published by the Free Software Foundation; either
10   version 2 of the License, or (at your option) any later version.
11
12   GPGME++ is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU Library General Public License for more details.
16
17   You should have received a copy of the GNU Library General Public License
18   along with GPGME++; see the file COPYING.LIB.  If not, write to the
19   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20   Boston, MA 02110-1301, USA.
21 */
22
23 #include <key.h>
24
25 #include "util.h"
26 #include "tofuinfo.h"
27
28 #include <gpgme.h>
29
30 #include <string.h>
31 #include <istream>
32 #include <iterator>
33
34 const GpgME::Key::Null GpgME::Key::null;
35
36 namespace GpgME
37 {
38
39 Key::Key() : key() {}
40
41 Key::Key(const Null &) : key() {}
42
43 Key::Key(const shared_gpgme_key_t &k) : key(k) {}
44
45 Key::Key(gpgme_key_t k, bool ref)
46     : key(k
47           ? shared_gpgme_key_t(k, &gpgme_key_unref)
48           : shared_gpgme_key_t())
49 {
50     if (ref && impl()) {
51         gpgme_key_ref(impl());
52     }
53 }
54
55 UserID Key::userID(unsigned int index) const
56 {
57     return UserID(key, index);
58 }
59
60 Subkey Key::subkey(unsigned int index) const
61 {
62     return Subkey(key, index);
63 }
64
65 unsigned int Key::numUserIDs() const
66 {
67     if (!key) {
68         return 0;
69     }
70     unsigned int count = 0;
71     for (gpgme_user_id_t uid = key->uids ; uid ; uid = uid->next) {
72         ++count;
73     }
74     return count;
75 }
76
77 unsigned int Key::numSubkeys() const
78 {
79     if (!key) {
80         return 0;
81     }
82     unsigned int count = 0;
83     for (gpgme_sub_key_t subkey = key->subkeys ; subkey ; subkey = subkey->next) {
84         ++count;
85     }
86     return count;
87 }
88
89 std::vector<UserID> Key::userIDs() const
90 {
91     if (!key) {
92         return std::vector<UserID>();
93     }
94
95     std::vector<UserID> v;
96     v.reserve(numUserIDs());
97     for (gpgme_user_id_t uid = key->uids ; uid ; uid = uid->next) {
98         v.push_back(UserID(key, uid));
99     }
100     return v;
101 }
102
103 std::vector<Subkey> Key::subkeys() const
104 {
105     if (!key) {
106         return std::vector<Subkey>();
107     }
108
109     std::vector<Subkey> v;
110     v.reserve(numSubkeys());
111     for (gpgme_sub_key_t subkey = key->subkeys ; subkey ; subkey = subkey->next) {
112         v.push_back(Subkey(key, subkey));
113     }
114     return v;
115 }
116
117 Key::OwnerTrust Key::ownerTrust() const
118 {
119     if (!key) {
120         return Unknown;
121     }
122     switch (key->owner_trust) {
123     default:
124     case GPGME_VALIDITY_UNKNOWN:   return Unknown;
125     case GPGME_VALIDITY_UNDEFINED: return Undefined;
126     case GPGME_VALIDITY_NEVER:     return Never;
127     case GPGME_VALIDITY_MARGINAL:  return Marginal;
128     case GPGME_VALIDITY_FULL:     return Full;
129     case GPGME_VALIDITY_ULTIMATE: return Ultimate;
130     }
131 }
132 char Key::ownerTrustAsString() const
133 {
134     if (!key) {
135         return '?';
136     }
137     switch (key->owner_trust) {
138     default:
139     case GPGME_VALIDITY_UNKNOWN:   return '?';
140     case GPGME_VALIDITY_UNDEFINED: return 'q';
141     case GPGME_VALIDITY_NEVER:     return 'n';
142     case GPGME_VALIDITY_MARGINAL:  return 'm';
143     case GPGME_VALIDITY_FULL:     return 'f';
144     case GPGME_VALIDITY_ULTIMATE: return 'u';
145     }
146 }
147
148 Protocol Key::protocol() const
149 {
150     if (!key) {
151         return UnknownProtocol;
152     }
153     switch (key->protocol) {
154     case GPGME_PROTOCOL_CMS:     return CMS;
155     case GPGME_PROTOCOL_OpenPGP: return OpenPGP;
156     default:                     return UnknownProtocol;
157     }
158 }
159
160 const char *Key::protocolAsString() const
161 {
162     return key ? gpgme_get_protocol_name(key->protocol) : 0 ;
163 }
164
165 bool Key::isRevoked() const
166 {
167     return key && key->revoked;
168 }
169
170 bool Key::isExpired() const
171 {
172     return key && key->expired;
173 }
174
175 bool Key::isDisabled() const
176 {
177     return key && key->disabled;
178 }
179
180 bool Key::isInvalid() const
181 {
182     return key && key->invalid;
183 }
184
185 bool Key::hasSecret() const
186 {
187     return key && key->secret;
188 }
189
190 bool Key::isRoot() const
191 {
192     return key && key->subkeys && key->subkeys->fpr && key->chain_id &&
193            strcasecmp(key->subkeys->fpr, key->chain_id) == 0;
194 }
195
196 bool Key::canEncrypt() const
197 {
198     return key && key->can_encrypt;
199 }
200
201 bool Key::canSign() const
202 {
203 #ifndef GPGME_CAN_SIGN_ON_SECRET_OPENPGP_KEYLISTING_NOT_BROKEN
204     if (key && key->protocol == GPGME_PROTOCOL_OpenPGP) {
205         return true;
206     }
207 #endif
208     return canReallySign();
209 }
210
211 bool Key::canReallySign() const
212 {
213     return key && key->can_sign;
214 }
215
216 bool Key::canCertify() const
217 {
218     return key && key->can_certify;
219 }
220
221 bool Key::canAuthenticate() const
222 {
223     return key && key->can_authenticate;
224 }
225
226 bool Key::isQualified() const
227 {
228     return key && key->is_qualified;
229 }
230
231 const char *Key::issuerSerial() const
232 {
233     return key ? key->issuer_serial : 0 ;
234 }
235 const char *Key::issuerName() const
236 {
237     return key ? key->issuer_name : 0 ;
238 }
239 const char *Key::chainID() const
240 {
241     return key ? key->chain_id : 0 ;
242 }
243
244 const char *Key::keyID() const
245 {
246     return key && key->subkeys ? key->subkeys->keyid : 0 ;
247 }
248
249 const char *Key::shortKeyID() const
250 {
251     if (!key || !key->subkeys || !key->subkeys->keyid) {
252         return 0;
253     }
254     const int len = strlen(key->subkeys->keyid);
255     if (len > 8) {
256         return key->subkeys->keyid + len - 8; // return the last 8 bytes (in hex notation)
257     } else {
258         return key->subkeys->keyid;
259     }
260 }
261
262 const char *Key::primaryFingerprint() const
263 {
264     if (!key) {
265         return nullptr;
266     }
267     if (key->fpr) {
268         /* Return what gpgme thinks is the primary fingerprint */
269         return key->fpr;
270     }
271     if (key->subkeys) {
272         /* Return the first subkeys fingerprint */
273         return key->subkeys->fpr;
274     }
275 }
276
277 unsigned int Key::keyListMode() const
278 {
279     return key ? convert_from_gpgme_keylist_mode_t(key->keylist_mode) : 0 ;
280 }
281
282 const Key &Key::mergeWith(const Key &other)
283 {
284     // ### incomplete. Just merges has* and can*, nothing else atm
285     // ### detach also missing
286
287     if (!this->primaryFingerprint() ||
288             !other.primaryFingerprint() ||
289             strcasecmp(this->primaryFingerprint(), other.primaryFingerprint()) != 0) {
290         return *this; // only merge the Key object which describe the same key
291     }
292
293     const gpgme_key_t me = impl();
294     const gpgme_key_t him = other.impl();
295
296     if (!me || !him) {
297         return *this;
298     }
299
300     me->revoked          |= him->revoked;
301     me->expired          |= him->expired;
302     me->disabled         |= him->disabled;
303     me->invalid          |= him->invalid;
304     me->can_encrypt      |= him->can_encrypt;
305     me->can_sign         |= him->can_sign;
306     me->can_certify      |= him->can_certify;
307     me->secret           |= him->secret;
308     me->can_authenticate |= him->can_authenticate;
309     me->is_qualified     |= him->is_qualified;
310     me->keylist_mode     |= him->keylist_mode;
311
312     // make sure the gpgme_sub_key_t::is_cardkey flag isn't lost:
313     for (gpgme_sub_key_t mysk = me->subkeys ; mysk ; mysk = mysk->next) {
314         for (gpgme_sub_key_t hissk = him->subkeys ; hissk ; hissk = hissk->next) {
315             if (strcmp(mysk->fpr, hissk->fpr) == 0) {
316                 mysk->is_cardkey |= hissk->is_cardkey;
317                 break;
318             }
319         }
320     }
321
322     return *this;
323 }
324
325 //
326 //
327 // class Subkey
328 //
329 //
330
331 gpgme_sub_key_t find_subkey(const shared_gpgme_key_t &key, unsigned int idx)
332 {
333     if (key) {
334         for (gpgme_sub_key_t s = key->subkeys ; s ; s = s->next, --idx) {
335             if (idx == 0) {
336                 return s;
337             }
338         }
339     }
340     return 0;
341 }
342
343 gpgme_sub_key_t verify_subkey(const shared_gpgme_key_t &key, gpgme_sub_key_t subkey)
344 {
345     if (key) {
346         for (gpgme_sub_key_t s = key->subkeys ; s ; s = s->next) {
347             if (s == subkey) {
348                 return subkey;
349             }
350         }
351     }
352     return 0;
353 }
354
355 Subkey::Subkey() : key(), subkey(0) {}
356
357 Subkey::Subkey(const shared_gpgme_key_t &k, unsigned int idx)
358     : key(k), subkey(find_subkey(k, idx))
359 {
360
361 }
362
363 Subkey::Subkey(const shared_gpgme_key_t &k, gpgme_sub_key_t sk)
364     : key(k), subkey(verify_subkey(k, sk))
365 {
366
367 }
368
369 Key Subkey::parent() const
370 {
371     return Key(key);
372 }
373
374 const char *Subkey::keyID() const
375 {
376     return subkey ? subkey->keyid : 0 ;
377 }
378
379 const char *Subkey::fingerprint() const
380 {
381     return subkey ? subkey->fpr : 0 ;
382 }
383
384 Subkey::PubkeyAlgo Subkey::publicKeyAlgorithm() const
385 {
386     return subkey ? static_cast<PubkeyAlgo>(subkey->pubkey_algo) : AlgoUnknown;
387 }
388
389 const char *Subkey::publicKeyAlgorithmAsString() const
390 {
391     return gpgme_pubkey_algo_name(subkey ? subkey->pubkey_algo : (gpgme_pubkey_algo_t)0);
392 }
393
394 /* static */
395 const char *Subkey::publicKeyAlgorithmAsString(PubkeyAlgo algo)
396 {
397     if (algo == AlgoUnknown) {
398         return NULL;
399     }
400     return gpgme_pubkey_algo_name(static_cast<gpgme_pubkey_algo_t>(algo));
401 }
402
403 std::string Subkey::algoName() const
404 {
405     char *gpgmeStr;
406     if (subkey && (gpgmeStr = gpgme_pubkey_algo_string(subkey))) {
407         std::string ret = std::string(gpgmeStr);
408         gpgme_free(gpgmeStr);
409         return ret;
410     }
411     return std::string();
412 }
413
414 bool Subkey::canEncrypt() const
415 {
416     return subkey && subkey->can_encrypt;
417 }
418
419 bool Subkey::canSign() const
420 {
421     return subkey && subkey->can_sign;
422 }
423
424 bool Subkey::canCertify() const
425 {
426     return subkey && subkey->can_certify;
427 }
428
429 bool Subkey::canAuthenticate() const
430 {
431     return subkey && subkey->can_authenticate;
432 }
433
434 bool Subkey::isQualified() const
435 {
436     return subkey && subkey->is_qualified;
437 }
438
439 bool Subkey::isCardKey() const
440 {
441     return subkey && subkey->is_cardkey;
442 }
443
444 const char *Subkey::cardSerialNumber() const
445 {
446     return subkey ? subkey->card_number : 0 ;
447 }
448
449 bool Subkey::isSecret() const
450 {
451     return subkey && subkey->secret;
452 }
453
454 unsigned int Subkey::length() const
455 {
456     return subkey ? subkey->length : 0 ;
457 }
458
459 time_t Subkey::creationTime() const
460 {
461     return static_cast<time_t>(subkey ? subkey->timestamp : 0);
462 }
463
464 time_t Subkey::expirationTime() const
465 {
466     return static_cast<time_t>(subkey ? subkey->expires : 0);
467 }
468
469 bool Subkey::neverExpires() const
470 {
471     return expirationTime() == time_t(0);
472 }
473
474 bool Subkey::isRevoked() const
475 {
476     return subkey && subkey->revoked;
477 }
478
479 bool Subkey::isInvalid() const
480 {
481     return subkey && subkey->invalid;
482 }
483
484 bool Subkey::isExpired() const
485 {
486     return subkey && subkey->expired;
487 }
488
489 bool Subkey::isDisabled() const
490 {
491     return subkey && subkey->disabled;
492 }
493
494 //
495 //
496 // class UserID
497 //
498 //
499
500 gpgme_user_id_t find_uid(const shared_gpgme_key_t &key, unsigned int idx)
501 {
502     if (key) {
503         for (gpgme_user_id_t u = key->uids ; u ; u = u->next, --idx) {
504             if (idx == 0) {
505                 return u;
506             }
507         }
508     }
509     return 0;
510 }
511
512 gpgme_user_id_t verify_uid(const shared_gpgme_key_t &key, gpgme_user_id_t uid)
513 {
514     if (key) {
515         for (gpgme_user_id_t u = key->uids ; u ; u = u->next) {
516             if (u == uid) {
517                 return uid;
518             }
519         }
520     }
521     return 0;
522 }
523
524 UserID::UserID() : key(), uid(0) {}
525
526 UserID::UserID(const shared_gpgme_key_t &k, gpgme_user_id_t u)
527     : key(k), uid(verify_uid(k, u))
528 {
529
530 }
531
532 UserID::UserID(const shared_gpgme_key_t &k, unsigned int idx)
533     : key(k), uid(find_uid(k, idx))
534 {
535
536 }
537
538 Key UserID::parent() const
539 {
540     return Key(key);
541 }
542
543 UserID::Signature UserID::signature(unsigned int index) const
544 {
545     return Signature(key, uid, index);
546 }
547
548 unsigned int UserID::numSignatures() const
549 {
550     if (!uid) {
551         return 0;
552     }
553     unsigned int count = 0;
554     for (gpgme_key_sig_t sig = uid->signatures ; sig ; sig = sig->next) {
555         ++count;
556     }
557     return count;
558 }
559
560 std::vector<UserID::Signature> UserID::signatures() const
561 {
562     if (!uid) {
563         return std::vector<Signature>();
564     }
565
566     std::vector<Signature> v;
567     v.reserve(numSignatures());
568     for (gpgme_key_sig_t sig = uid->signatures ; sig ; sig = sig->next) {
569         v.push_back(Signature(key, uid, sig));
570     }
571     return v;
572 }
573
574 const char *UserID::id() const
575 {
576     return uid ? uid->uid : 0 ;
577 }
578
579 const char *UserID::name() const
580 {
581     return uid ? uid->name : 0 ;
582 }
583
584 const char *UserID::email() const
585 {
586     return uid ? uid->email : 0 ;
587 }
588
589 const char *UserID::comment() const
590 {
591     return uid ? uid->comment : 0 ;
592 }
593
594 UserID::Validity UserID::validity() const
595 {
596     if (!uid) {
597         return Unknown;
598     }
599     switch (uid->validity) {
600     default:
601     case GPGME_VALIDITY_UNKNOWN:   return Unknown;
602     case GPGME_VALIDITY_UNDEFINED: return Undefined;
603     case GPGME_VALIDITY_NEVER:     return Never;
604     case GPGME_VALIDITY_MARGINAL:  return Marginal;
605     case GPGME_VALIDITY_FULL:      return Full;
606     case GPGME_VALIDITY_ULTIMATE:  return Ultimate;
607     }
608 }
609
610 char UserID::validityAsString() const
611 {
612     if (!uid) {
613         return '?';
614     }
615     switch (uid->validity) {
616     default:
617     case GPGME_VALIDITY_UNKNOWN:   return '?';
618     case GPGME_VALIDITY_UNDEFINED: return 'q';
619     case GPGME_VALIDITY_NEVER:     return 'n';
620     case GPGME_VALIDITY_MARGINAL:  return 'm';
621     case GPGME_VALIDITY_FULL:      return 'f';
622     case GPGME_VALIDITY_ULTIMATE:  return 'u';
623     }
624 }
625
626 bool UserID::isRevoked() const
627 {
628     return uid && uid->revoked;
629 }
630
631 bool UserID::isInvalid() const
632 {
633     return uid && uid->invalid;
634 }
635
636 TofuInfo UserID::tofuInfo() const
637 {
638     if (!uid) {
639         return TofuInfo();
640     }
641     return TofuInfo(uid->tofu);
642 }
643
644 //
645 //
646 // class Signature
647 //
648 //
649
650 gpgme_key_sig_t find_signature(gpgme_user_id_t uid, unsigned int idx)
651 {
652     if (uid) {
653         for (gpgme_key_sig_t s = uid->signatures ; s ; s = s->next, --idx) {
654             if (idx == 0) {
655                 return s;
656             }
657         }
658     }
659     return 0;
660 }
661
662 gpgme_key_sig_t verify_signature(gpgme_user_id_t uid, gpgme_key_sig_t sig)
663 {
664     if (uid) {
665         for (gpgme_key_sig_t s = uid->signatures ; s ; s = s->next) {
666             if (s == sig) {
667                 return sig;
668             }
669         }
670     }
671     return 0;
672 }
673
674 UserID::Signature::Signature() : key(), uid(0), sig(0) {}
675
676 UserID::Signature::Signature(const shared_gpgme_key_t &k, gpgme_user_id_t u, unsigned int idx)
677     : key(k), uid(verify_uid(k, u)), sig(find_signature(uid, idx))
678 {
679
680 }
681
682 UserID::Signature::Signature(const shared_gpgme_key_t &k, gpgme_user_id_t u, gpgme_key_sig_t s)
683     : key(k), uid(verify_uid(k, u)), sig(verify_signature(uid, s))
684 {
685
686 }
687
688 UserID UserID::Signature::parent() const
689 {
690     return UserID(key, uid);
691 }
692
693 const char *UserID::Signature::signerKeyID() const
694 {
695     return sig ? sig->keyid : 0 ;
696 }
697
698 const char *UserID::Signature::algorithmAsString() const
699 {
700     return gpgme_pubkey_algo_name(sig ? sig->pubkey_algo : (gpgme_pubkey_algo_t)0);
701 }
702
703 unsigned int UserID::Signature::algorithm() const
704 {
705     return sig ? sig->pubkey_algo : 0 ;
706 }
707
708 time_t UserID::Signature::creationTime() const
709 {
710     return static_cast<time_t>(sig ? sig->timestamp : 0);
711 }
712
713 time_t UserID::Signature::expirationTime() const
714 {
715     return static_cast<time_t>(sig ? sig->expires : 0);
716 }
717
718 bool UserID::Signature::neverExpires() const
719 {
720     return expirationTime() == time_t(0);
721 }
722
723 bool UserID::Signature::isRevokation() const
724 {
725     return sig && sig->revoked;
726 }
727
728 bool UserID::Signature::isInvalid() const
729 {
730     return sig && sig->invalid;
731 }
732
733 bool UserID::Signature::isExpired() const
734 {
735     return sig && sig->expired;
736 }
737
738 bool UserID::Signature::isExportable() const
739 {
740     return sig && sig->exportable;
741 }
742
743 const char *UserID::Signature::signerUserID() const
744 {
745     return sig ? sig->uid : 0 ;
746 }
747
748 const char *UserID::Signature::signerName() const
749 {
750     return sig ? sig->name : 0 ;
751 }
752
753 const char *UserID::Signature::signerEmail() const
754 {
755     return sig ? sig->email : 0 ;
756 }
757
758 const char *UserID::Signature::signerComment() const
759 {
760     return sig ? sig->comment : 0 ;
761 }
762
763 unsigned int UserID::Signature::certClass() const
764 {
765     return sig ? sig->sig_class : 0 ;
766 }
767
768 UserID::Signature::Status UserID::Signature::status() const
769 {
770     if (!sig) {
771         return GeneralError;
772     }
773
774     switch (gpgme_err_code(sig->status)) {
775     case GPG_ERR_NO_ERROR:      return NoError;
776     case GPG_ERR_SIG_EXPIRED:   return SigExpired;
777     case GPG_ERR_KEY_EXPIRED:   return KeyExpired;
778     case GPG_ERR_BAD_SIGNATURE: return BadSignature;
779     case GPG_ERR_NO_PUBKEY:     return NoPublicKey;
780     default:
781     case GPG_ERR_GENERAL:       return GeneralError;
782     }
783 }
784
785 std::string UserID::Signature::statusAsString() const
786 {
787     if (!sig) {
788         return std::string();
789     }
790     char buf[ 1024 ];
791     gpgme_strerror_r(sig->status, buf, sizeof buf);
792     buf[ sizeof buf - 1 ] = '\0';
793     return std::string(buf);
794 }
795
796 GpgME::Notation UserID::Signature::notation(unsigned int idx) const
797 {
798     if (!sig) {
799         return GpgME::Notation();
800     }
801     for (gpgme_sig_notation_t nota = sig->notations ; nota ; nota = nota->next) {
802         if (nota->name) {
803             if (idx-- == 0) {
804                 return GpgME::Notation(nota);
805             }
806         }
807     }
808     return GpgME::Notation();
809 }
810
811 unsigned int UserID::Signature::numNotations() const
812 {
813     if (!sig) {
814         return 0;
815     }
816     unsigned int count = 0;
817     for (gpgme_sig_notation_t nota = sig->notations ; nota ; nota = nota->next) {
818         if (nota->name) {
819             ++count; // others are policy URLs...
820         }
821     }
822     return count;
823 }
824
825 std::vector<Notation> UserID::Signature::notations() const
826 {
827     if (!sig) {
828         return std::vector<GpgME::Notation>();
829     }
830     std::vector<GpgME::Notation> v;
831     v.reserve(numNotations());
832     for (gpgme_sig_notation_t nota = sig->notations ; nota ; nota = nota->next) {
833         if (nota->name) {
834             v.push_back(GpgME::Notation(nota));
835         }
836     }
837     return v;
838 }
839
840 const char *UserID::Signature::policyURL() const
841 {
842     if (!sig) {
843         return 0;
844     }
845     for (gpgme_sig_notation_t nota = sig->notations ; nota ; nota = nota->next) {
846         if (!nota->name) {
847             return nota->value;
848         }
849     }
850     return 0;
851 }
852
853 std::ostream &operator<<(std::ostream &os, const UserID &uid)
854 {
855     os << "GpgME::UserID(";
856     if (!uid.isNull()) {
857         os << "\n name:      " << protect(uid.name())
858            << "\n email:     " << protect(uid.email())
859            << "\n comment:   " << protect(uid.comment())
860            << "\n validity:  " << uid.validityAsString()
861            << "\n revoked:   " << uid.isRevoked()
862            << "\n invalid:   " << uid.isInvalid()
863            << "\n numsigs:   " << uid.numSignatures()
864            << "\n tofuinfo:\n" << uid.tofuInfo();
865     }
866     return os << ')';
867 }
868
869 std::ostream &operator<<(std::ostream &os, const Key &key)
870 {
871     os << "GpgME::Key(";
872     if (!key.isNull()) {
873         os << "\n protocol:   " << protect(key.protocolAsString())
874            << "\n ownertrust: " << key.ownerTrustAsString()
875            << "\n issuer:     " << protect(key.issuerName())
876            << "\n fingerprint:" << protect(key.primaryFingerprint())
877            << "\n listmode:   " << key.keyListMode()
878            << "\n canSign:    " << key.canReallySign()
879            << "\n canEncrypt: " << key.canEncrypt()
880            << "\n canCertify: " << key.canCertify()
881            << "\n canAuth:    " << key.canAuthenticate()
882            << "\n uids:\n";
883         const std::vector<UserID> uids = key.userIDs();
884         std::copy(uids.begin(), uids.end(),
885                   std::ostream_iterator<UserID>(os, "\n"));
886     }
887     return os << ')';
888 }
889
890 } // namespace GpgME