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