|
|
|
/*
|
|
|
|
kpgpbaseG.cpp
|
|
|
|
|
|
|
|
Copyright (C) 2001,2002 the KPGP authors
|
|
|
|
See file AUTHORS.kpgp for details
|
|
|
|
|
|
|
|
This file is part of KPGP, the KDE PGP/GnuPG support library.
|
|
|
|
|
|
|
|
KPGP is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software Foundation,
|
|
|
|
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include <config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "kpgpbase.h"
|
|
|
|
#include "kpgp.h"
|
|
|
|
|
|
|
|
#include <tdelocale.h>
|
|
|
|
#include <kprocess.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
|
|
#include <tqtextcodec.h>
|
|
|
|
|
|
|
|
#include <string.h> /* strncmp */
|
|
|
|
|
|
|
|
namespace Kpgp {
|
|
|
|
|
|
|
|
BaseG::BaseG()
|
|
|
|
: Base()
|
|
|
|
{
|
|
|
|
// determine the version of gpg (the method is equivalent to gpgme's method)
|
|
|
|
runGpg( "--version", 0 );
|
|
|
|
int eol = output.find( '\n' );
|
|
|
|
if( eol > 0 ) {
|
|
|
|
int pos = output.findRev( ' ', eol - 1 );
|
|
|
|
if( pos != -1 ) {
|
|
|
|
mVersion = output.mid( pos + 1, eol - pos - 1 );
|
|
|
|
kdDebug(5100) << "found GnuPG " << mVersion << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
BaseG::~BaseG()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
BaseG::encrypt( Block& block, const KeyIDList& recipients )
|
|
|
|
{
|
|
|
|
return encsign( block, recipients, 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
BaseG::clearsign( Block& block, const TQString& passphrase )
|
|
|
|
{
|
|
|
|
return encsign( block, KeyIDList(), passphrase );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
BaseG::encsign( Block& block, const KeyIDList& recipients,
|
|
|
|
const TQString& passphrase )
|
|
|
|
{
|
|
|
|
TQCString cmd;
|
|
|
|
int exitStatus = 0;
|
|
|
|
|
|
|
|
if (!recipients.isEmpty() && !passphrase.isNull())
|
|
|
|
cmd = "--batch --armor --sign --encrypt --textmode";
|
|
|
|
else if(!recipients.isEmpty())
|
|
|
|
cmd = "--batch --armor --encrypt --textmode";
|
|
|
|
else if (!passphrase.isNull())
|
|
|
|
cmd = "--batch --escape-from --clearsign";
|
|
|
|
else
|
|
|
|
{
|
|
|
|
kdDebug(5100) << "kpgpbase: Neither recipients nor passphrase specified." << endl;
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!passphrase.isNull())
|
|
|
|
cmd += addUserId();
|
|
|
|
|
|
|
|
if(!recipients.isEmpty())
|
|
|
|
{
|
|
|
|
cmd += " --set-filename stdin";
|
|
|
|
|
|
|
|
TQCString pgpUser = Module::getKpgp()->user();
|
|
|
|
if(Module::getKpgp()->encryptToSelf() && !pgpUser.isEmpty()) {
|
|
|
|
cmd += " -r 0x";
|
|
|
|
cmd += pgpUser;
|
|
|
|
}
|
|
|
|
|
|
|
|
for( KeyIDList::ConstIterator it = recipients.begin();
|
|
|
|
it != recipients.end(); ++it ) {
|
|
|
|
cmd += " -r 0x";
|
|
|
|
cmd += (*it);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
clear();
|
|
|
|
input = block.text();
|
|
|
|
exitStatus = runGpg(cmd.data(), passphrase);
|
|
|
|
if( !output.isEmpty() )
|
|
|
|
block.setProcessedText( output );
|
|
|
|
block.setError( error );
|
|
|
|
|
|
|
|
if( exitStatus != 0 )
|
|
|
|
{
|
|
|
|
// this error message is later hopefully overwritten
|
|
|
|
errMsg = i18n( "Unknown error." );
|
|
|
|
status = ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
// #### FIXME: As we check the keys ourselves the following problems
|
|
|
|
// shouldn't occur. Therefore I don't handle them for now.
|
|
|
|
// IK 01/2002
|
|
|
|
if(!recipients.isEmpty())
|
|
|
|
{
|
|
|
|
int index = 0;
|
|
|
|
bool bad = FALSE;
|
|
|
|
unsigned int num = 0;
|
|
|
|
TQCString badkeys = "";
|
|
|
|
// Examples:
|
|
|
|
// gpg: 0x12345678: skipped: public key not found
|
|
|
|
// gpg: 0x12345678: skipped: public key is disabled
|
|
|
|
// gpg: 0x12345678: skipped: unusable public key
|
|
|
|
// (expired or revoked key)
|
|
|
|
// gpg: 23456789: no info to calculate a trust probability
|
|
|
|
// (untrusted key, 23456789 is the key Id of the encryption sub key)
|
|
|
|
while((index = error.find("skipped: ",index)) != -1)
|
|
|
|
{
|
|
|
|
bad = TRUE;
|
|
|
|
index = error.find('\'',index);
|
|
|
|
int index2 = error.find('\'',index+1);
|
|
|
|
badkeys += error.mid(index, index2-index+1) + ", ";
|
|
|
|
num++;
|
|
|
|
}
|
|
|
|
if(bad)
|
|
|
|
{
|
|
|
|
badkeys.stripWhiteSpace();
|
|
|
|
if(num == recipients.count())
|
|
|
|
errMsg = i18n("Could not find public keys matching the userid(s)\n"
|
|
|
|
"%1;\n"
|
|
|
|
"the message is not encrypted.")
|
|
|
|
.arg( badkeys.data() );
|
|
|
|
else
|
|
|
|
errMsg = i18n("Could not find public keys matching the userid(s)\n"
|
|
|
|
"%1;\n"
|
|
|
|
"these persons will not be able to read the message.")
|
|
|
|
.arg( badkeys.data() );
|
|
|
|
status |= MISSINGKEY;
|
|
|
|
status |= ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (!passphrase.isNull())
|
|
|
|
{
|
|
|
|
// Example 1 (bad passphrase, clearsign only):
|
|
|
|
// gpg: skipped `0x12345678': bad passphrase
|
|
|
|
// gpg: [stdin]: clearsign failed: bad passphrase
|
|
|
|
// Example 2 (bad passphrase, sign & encrypt):
|
|
|
|
// gpg: skipped `0x12345678': bad passphrase
|
|
|
|
// gpg: [stdin]: sign+encrypt failed: bad passphrase
|
|
|
|
// Example 3 (unusable secret key, clearsign only):
|
|
|
|
// gpg: skipped `0x12345678': unusable secret key
|
|
|
|
// gpg: [stdin]: clearsign failed: unusable secret key
|
|
|
|
// Example 4 (unusable secret key, sign & encrypt):
|
|
|
|
// gpg: skipped `0xAC0EB35D': unusable secret key
|
|
|
|
// gpg: [stdin]: sign+encrypt failed: unusable secret key
|
|
|
|
if( error.find("bad passphrase") != -1 )
|
|
|
|
{
|
|
|
|
errMsg = i18n("Signing failed because the passphrase is wrong.");
|
|
|
|
status |= BADPHRASE;
|
|
|
|
status |= ERR_SIGNING;
|
|
|
|
status |= ERROR;
|
|
|
|
}
|
|
|
|
else if( error.find("unusable secret key") != -1 )
|
|
|
|
{
|
|
|
|
errMsg = i18n("Signing failed because your secret key is unusable.");
|
|
|
|
status |= ERR_SIGNING;
|
|
|
|
status |= ERROR;
|
|
|
|
}
|
|
|
|
else if( !( status & ERROR ) )
|
|
|
|
{
|
|
|
|
//kdDebug(5100) << "Base: Good Passphrase!" << endl;
|
|
|
|
status |= SIGNED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//kdDebug(5100) << "status = " << status << endl;
|
|
|
|
block.setStatus( status );
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
BaseG::decrypt( Block& block, const TQString& passphrase )
|
|
|
|
{
|
|
|
|
int index, index2;
|
|
|
|
int exitStatus = 0;
|
|
|
|
|
|
|
|
clear();
|
|
|
|
input = block.text();
|
|
|
|
exitStatus = runGpg("--batch --decrypt", passphrase);
|
|
|
|
if( !output.isEmpty() && ( error.find( "gpg: quoted printable" ) == -1 ) )
|
|
|
|
block.setProcessedText( output );
|
|
|
|
block.setError( error );
|
|
|
|
|
|
|
|
if(exitStatus == -1) {
|
|
|
|
errMsg = i18n("Error running gpg");
|
|
|
|
status = RUN_ERR;
|
|
|
|
block.setStatus( status );
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Example 1 (good passphrase, decryption successful):
|
|
|
|
// gpg: encrypted with 2048-bit ELG-E key, ID 12345678, created 2000-11-11
|
|
|
|
// "Foo Bar <foo@bar.xyz>"
|
|
|
|
//
|
|
|
|
// Example 2 (bad passphrase):
|
|
|
|
// gpg: encrypted with 1024-bit RSA key, ID 12345678, created 1991-01-01
|
|
|
|
// "Foo Bar <foo@bar.xyz>"
|
|
|
|
// gpg: public key decryption failed: bad passphrase
|
|
|
|
// gpg: decryption failed: secret key not available
|
|
|
|
//
|
|
|
|
// Example 3 (no secret key available):
|
|
|
|
// gpg: encrypted with RSA key, ID 12345678
|
|
|
|
// gpg: decryption failed: secret key not available
|
|
|
|
//
|
|
|
|
// Example 4 (good passphrase for second key, decryption successful):
|
|
|
|
// gpg: encrypted with 2048-bit ELG-E key, ID 12345678, created 2000-01-01
|
|
|
|
// "Foo Bar (work) <foo@bar.xyz>"
|
|
|
|
// gpg: public key decryption failed: bad passphrase
|
|
|
|
// gpg: encrypted with 2048-bit ELG-E key, ID 23456789, created 2000-02-02
|
|
|
|
// "Foo Bar (home) <foo@bar.xyz>"
|
|
|
|
if( error.find( "gpg: encrypted with" ) != -1 )
|
|
|
|
{
|
|
|
|
//kdDebug(5100) << "kpgpbase: message is encrypted" << endl;
|
|
|
|
status |= ENCRYPTED;
|
|
|
|
if( error.find( "\ngpg: decryption failed" ) != -1 )
|
|
|
|
{
|
|
|
|
if( ( index = error.find( "bad passphrase" ) ) != -1 )
|
|
|
|
{
|
|
|
|
if (!passphrase.isNull())
|
|
|
|
{
|
|
|
|
errMsg = i18n( "Bad passphrase; could not decrypt." );
|
|
|
|
kdDebug(5100) << "Base: passphrase is bad" << endl;
|
|
|
|
status |= BADPHRASE;
|
|
|
|
status |= ERROR;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Search backwards the user ID of the needed key
|
|
|
|
index2 = error.findRev('"', index) - 1;
|
|
|
|
index = error.findRev(" \"", index2) + 7;
|
|
|
|
// The conversion from UTF8 is necessary because gpg stores and
|
|
|
|
// prints user IDs in UTF8
|
|
|
|
block.setRequiredUserId( TQString::fromUtf8( error.mid( index, index2 - index + 1 ) ) );
|
|
|
|
kdDebug(5100) << "Base: key needed is \"" << block.requiredUserId() << "\"!" << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( error.find( "secret key not available" ) != -1 )
|
|
|
|
{
|
|
|
|
// no secret key fitting this message
|
|
|
|
status |= NO_SEC_KEY;
|
|
|
|
status |= ERROR;
|
|
|
|
errMsg = i18n("You do not have the secret key needed to decrypt this message.");
|
|
|
|
kdDebug(5100) << "Base: no secret key for this message" << endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// check for persons
|
|
|
|
#if 0
|
|
|
|
// ##### FIXME: This information is anyway currently not used
|
|
|
|
// I'll change it to always determine the recipients.
|
|
|
|
index = error.find("can only be read by:");
|
|
|
|
if(index != -1)
|
|
|
|
{
|
|
|
|
index = error.find('\n',index);
|
|
|
|
int end = error.find("\n\n",index);
|
|
|
|
|
|
|
|
mRecipients.clear();
|
|
|
|
while( (index2 = error.find('\n',index+1)) <= end )
|
|
|
|
{
|
|
|
|
TQCString item = error.mid(index+1,index2-index-1);
|
|
|
|
item.stripWhiteSpace();
|
|
|
|
mRecipients.append(item);
|
|
|
|
index = index2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// Example 1 (unknown signature key):
|
|
|
|
// gpg: Signature made Wed 02 Jan 2002 11:26:33 AM CET using DSA key ID 2E250C64
|
|
|
|
// gpg: Can't check signature: public key not found
|
|
|
|
if((index = error.find("Signature made")) != -1)
|
|
|
|
{
|
|
|
|
//kdDebug(5100) << "Base: message is signed" << endl;
|
|
|
|
status |= SIGNED;
|
|
|
|
// get signature date and signature key ID
|
|
|
|
// Example: Signature made Sun 06 May 2001 03:49:27 PM CEST using DSA key ID 12345678
|
|
|
|
index2 = error.find("using", index+15);
|
|
|
|
block.setSignatureDate( error.mid(index+15, index2-(index+15)-1) );
|
|
|
|
kdDebug(5100) << "Message was signed on '" << block.signatureDate() << "'\n";
|
|
|
|
// To handle gnupg > 2.1
|
|
|
|
// gpg: Signature made Thu 05 Apr 2018 10:02:50 PM CEST
|
|
|
|
// gpg: using DSA key A0CF1DC09533E5E87F54DB40F1EEB8CD9FB16A50
|
|
|
|
// gpg: Good signature from "deloptes <deloptes@gmail.com>" [ultimate]
|
|
|
|
// so we need extra check
|
|
|
|
if (error.contains("key ID") > 0) {
|
|
|
|
index2 = error.find("key ID ", index2) + 7;
|
|
|
|
block.setSignatureKeyId( error.mid(index2,8) );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
index2 = error.find("key ", index2) + 4;
|
|
|
|
// handle variable key size
|
|
|
|
// gpg: Signature made Mon 02 Apr 2018 03:15:08 PM CEST
|
|
|
|
// gpg: using DSA key 05C82CF57AD1DA46
|
|
|
|
// gpg: Can't check signature: No public key
|
|
|
|
int end = error.find("\n", index2);
|
|
|
|
block.setSignatureKeyId( error.mid(index2,end-index2) );
|
|
|
|
}
|
|
|
|
kdDebug(5100) << "Message was signed with key '" << block.signatureKeyId() << "'\n";
|
|
|
|
// move index to start of next line
|
|
|
|
index = error.find('\n', index2)+1;
|
|
|
|
|
|
|
|
if ((error.find("Key matching expected", index) != -1)
|
|
|
|
|| (error.find("Can't check signature", index) != -1))
|
|
|
|
{
|
|
|
|
status |= UNKNOWN_SIG;
|
|
|
|
status |= GOODSIG;
|
|
|
|
block.setSignatureUserId( TQString() );
|
|
|
|
}
|
|
|
|
else if( error.find("Good signature", index) != -1 )
|
|
|
|
{
|
|
|
|
status |= GOODSIG;
|
|
|
|
// get the primary user ID of the signer
|
|
|
|
index = error.find('"',index);
|
|
|
|
index2 = error.find('\n',index+1);
|
|
|
|
index2 = error.findRev('"', index2-1);
|
|
|
|
block.setSignatureUserId( TQString::fromLocal8Bit( error.mid( index+1, index2-index-1 ) ) );
|
|
|
|
}
|
|
|
|
else if( error.find("BAD signature", index) != -1 )
|
|
|
|
{
|
|
|
|
//kdDebug(5100) << "BAD signature" << endl;
|
|
|
|
status |= ERROR;
|
|
|
|
// get the primary user ID of the signer
|
|
|
|
index = error.find('"',index);
|
|
|
|
index2 = error.find('\n',index+1);
|
|
|
|
index2 = error.findRev('"', index2-1);
|
|
|
|
block.setSignatureUserId( TQString::fromLocal8Bit( error.mid( index+1, index2-index-1 ) ) );
|
|
|
|
}
|
|
|
|
else if( error.find("Can't find the right public key", index) != -1 )
|
|
|
|
{
|
|
|
|
// #### fix this hack
|
|
|
|
// I think this can't happen anymore because if the pubring is missing
|
|
|
|
// the current GnuPG creates a new empty one.
|
|
|
|
status |= UNKNOWN_SIG;
|
|
|
|
status |= GOODSIG; // this is a hack...
|
|
|
|
block.setSignatureUserId( i18n("??? (file ~/.gnupg/pubring.gpg not found)") );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
status |= ERROR;
|
|
|
|
block.setSignatureUserId( TQString() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//kdDebug(5100) << "status = " << status << endl;
|
|
|
|
block.setStatus( status );
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Key*
|
|
|
|
BaseG::readPublicKey( const KeyID& keyID,
|
|
|
|
const bool readTrust /* = false */,
|
|
|
|
Key* key /* = 0 */ )
|
|
|
|
{
|
|
|
|
int exitStatus = 0;
|
|
|
|
|
|
|
|
status = 0;
|
|
|
|
if( readTrust )
|
|
|
|
exitStatus = runGpg( "--batch --list-public-keys --with-fingerprint --with-colons --fixed-list-mode 0x" + keyID, 0, true );
|
|
|
|
else
|
|
|
|
exitStatus = runGpg( "--batch --list-public-keys --with-fingerprint --with-colons --fixed-list-mode --no-expensive-trust-checks 0x" + keyID, 0, true );
|
|
|
|
|
|
|
|
if(exitStatus != 0) {
|
|
|
|
status = ERROR;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int offset;
|
|
|
|
// search start of key data
|
|
|
|
if( !strncmp( output.data(), "pub:", 4 ) )
|
|
|
|
offset = 0;
|
|
|
|
else {
|
|
|
|
offset = output.find( "\npub:" );
|
|
|
|
if( offset == -1 )
|
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
offset++;
|
|
|
|
}
|
|
|
|
|
|
|
|
key = parseKeyData( output, offset, key );
|
|
|
|
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KeyList
|
|
|
|
BaseG::publicKeys( const TQStringList & patterns )
|
|
|
|
{
|
|
|
|
int exitStatus = 0;
|
|
|
|
|
|
|
|
// the option --with-colons should be used for interprocess communication
|
|
|
|
// with gpg (according to Werner Koch)
|
|
|
|
TQCString cmd = "--batch --list-public-keys --with-fingerprint --with-colons "
|
|
|
|
"--fixed-list-mode --no-expensive-trust-checks";
|
|
|
|
for ( TQStringList::ConstIterator it = patterns.begin();
|
|
|
|
it != patterns.end(); ++it ) {
|
|
|
|
cmd += " ";
|
|
|
|
cmd += TDEProcess::quote( *it ).local8Bit();
|
|
|
|
}
|
|
|
|
status = 0;
|
|
|
|
exitStatus = runGpg( cmd, 0, true );
|
|
|
|
|
|
|
|
if(exitStatus != 0) {
|
|
|
|
status = ERROR;
|
|
|
|
return KeyList();
|
|
|
|
}
|
|
|
|
|
|
|
|
// now we need to parse the output for public keys
|
|
|
|
KeyList publicKeys = parseKeyList(output, false);
|
|
|
|
|
|
|
|
// sort the list of public keys
|
|
|
|
publicKeys.sort();
|
|
|
|
|
|
|
|
return publicKeys;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KeyList
|
|
|
|
BaseG::secretKeys( const TQStringList & patterns )
|
|
|
|
{
|
|
|
|
int exitStatus = 0;
|
|
|
|
|
|
|
|
// the option --with-colons should be used for interprocess communication
|
|
|
|
// with gpg (according to Werner Koch)
|
|
|
|
TQCString cmd = "--batch --list-secret-keys --with-fingerprint --with-colons "
|
|
|
|
"--fixed-list-mode";
|
|
|
|
for ( TQStringList::ConstIterator it = patterns.begin();
|
|
|
|
it != patterns.end(); ++it ) {
|
|
|
|
cmd += " ";
|
|
|
|
cmd += TDEProcess::quote( *it ).local8Bit();
|
|
|
|
}
|
|
|
|
status = 0;
|
|
|
|
exitStatus = runGpg( cmd, 0, true );
|
|
|
|
|
|
|
|
if(exitStatus != 0) {
|
|
|
|
status = ERROR;
|
|
|
|
return KeyList();
|
|
|
|
}
|
|
|
|
|
|
|
|
// now we need to parse the output for secret keys
|
|
|
|
KeyList secretKeys = parseKeyList(output, true);
|
|
|
|
|
|
|
|
// sort the list of secret keys
|
|
|
|
secretKeys.sort();
|
|
|
|
|
|
|
|
return secretKeys;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
BaseG::signKey(const KeyID& keyID, const TQString& passphrase)
|
|
|
|
{
|
|
|
|
TQCString cmd;
|
|
|
|
int exitStatus = 0;
|
|
|
|
|
|
|
|
cmd = "--batch";
|
|
|
|
cmd += addUserId();
|
|
|
|
cmd += " --sign-key 0x";
|
|
|
|
cmd += keyID;
|
|
|
|
|
|
|
|
status = 0;
|
|
|
|
exitStatus = runGpg(cmd.data(), passphrase);
|
|
|
|
|
|
|
|
if (exitStatus != 0)
|
|
|
|
status = ERROR;
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQCString
|
|
|
|
BaseG::getAsciiPublicKey(const KeyID& keyID)
|
|
|
|
{
|
|
|
|
int exitStatus = 0;
|
|
|
|
|
|
|
|
if (keyID.isEmpty())
|
|
|
|
return TQCString();
|
|
|
|
|
|
|
|
status = 0;
|
|
|
|
exitStatus = runGpg("--batch --armor --export 0x" + keyID, 0, true);
|
|
|
|
|
|
|
|
if(exitStatus != 0) {
|
|
|
|
status = ERROR;
|
|
|
|
return TQCString();
|
|
|
|
}
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Key*
|
|
|
|
BaseG::parseKeyData( const TQCString& output, int& offset, Key* key /* = 0 */ )
|
|
|
|
// This function parses the data for a single key which is output by GnuPG
|
|
|
|
// with the following command line arguments:
|
|
|
|
// --batch --list-public-keys --with-fingerprint --with-colons
|
|
|
|
// --fixed-list-mode [--no-expensive-trust-checks]
|
|
|
|
// It expects the key data to start at offset and returns the start of
|
|
|
|
// the next key's data in offset.
|
|
|
|
// Subkeys are currently ignored.
|
|
|
|
{
|
|
|
|
int index = offset;
|
|
|
|
|
|
|
|
if( ( strncmp( output.data() + offset, "pub:", 4 ) != 0 )
|
|
|
|
&& ( strncmp( output.data() + offset, "sec:", 4 ) != 0 ) ) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( key == 0 )
|
|
|
|
key = new Key();
|
|
|
|
else
|
|
|
|
key->clear();
|
|
|
|
|
|
|
|
TQCString keyID;
|
|
|
|
bool firstKey = true;
|
|
|
|
|
|
|
|
while( true )
|
|
|
|
{
|
|
|
|
int eol;
|
|
|
|
// search the end of the current line
|
|
|
|
if( ( eol = output.find( '\n', index ) ) == -1 )
|
|
|
|
break;
|
|
|
|
|
|
|
|
bool bIsPublicKey = false;
|
|
|
|
if( ( bIsPublicKey = !strncmp( output.data() + index, "pub:", 4 ) )
|
|
|
|
|| !strncmp( output.data() + index, "sec:", 4 ) )
|
|
|
|
{ // line contains primary key data
|
|
|
|
// Example: pub:f:1024:17:63CB691DFAEBD5FC:860451781::379:-:::scESC:
|
|
|
|
|
|
|
|
// abort parsing if we found the start of the next key
|
|
|
|
if( !firstKey )
|
|
|
|
break;
|
|
|
|
firstKey = false;
|
|
|
|
|
|
|
|
key->setSecret( !bIsPublicKey );
|
|
|
|
|
|
|
|
Subkey *subkey = new Subkey( TQCString(), !bIsPublicKey );
|
|
|
|
|
|
|
|
int pos = index + 4; // begin of 2nd field
|
|
|
|
int pos2 = output.find( ':', pos );
|
|
|
|
for( int field = 2; field <= 12; field++ )
|
|
|
|
{
|
|
|
|
switch( field )
|
|
|
|
{
|
|
|
|
case 2: // the calculated trust
|
|
|
|
if( pos2 > pos )
|
|
|
|
{
|
|
|
|
switch( output[pos] )
|
|
|
|
{
|
|
|
|
case 'o': // unknown (this key is new to the system)
|
|
|
|
break;
|
|
|
|
case 'i': // the key is invalid, e.g. missing self-signature
|
|
|
|
subkey->setInvalid( true );
|
|
|
|
key->setInvalid( true );
|
|
|
|
break;
|
|
|
|
case 'd': // the key has been disabled
|
|
|
|
subkey->setDisabled( true );
|
|
|
|
key->setDisabled( true );
|
|
|
|
break;
|
|
|
|
case 'r': // the key has been revoked
|
|
|
|
subkey->setRevoked( true );
|
|
|
|
key->setRevoked( true );
|
|
|
|
break;
|
|
|
|
case 'e': // the key has expired
|
|
|
|
subkey->setExpired( true );
|
|
|
|
key->setExpired( true );
|
|
|
|
break;
|
|
|
|
case '-': // undefined (no path leads to the key)
|
|
|
|
case 'q': // undefined (no trusted path leads to the key)
|
|
|
|
case 'n': // don't trust this key at all
|
|
|
|
case 'm': // the key is marginally trusted
|
|
|
|
case 'f': // the key is fully trusted
|
|
|
|
case 'u': // the key is ultimately trusted (secret key available)
|
|
|
|
// These values are ignored since we determine the key trust
|
|
|
|
// from the trust values of the user ids.
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
kdDebug(5100) << "Unknown trust value\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3: // length of key in bits
|
|
|
|
if( pos2 > pos )
|
|
|
|
subkey->setKeyLength( output.mid( pos, pos2-pos ).toUInt() );
|
|
|
|
break;
|
|
|
|
case 4: // the key algorithm
|
|
|
|
if( pos2 > pos )
|
|
|
|
subkey->setKeyAlgorithm( output.mid( pos, pos2-pos ).toUInt() );
|
|
|
|
break;
|
|
|
|
case 5: // the long key id
|
|
|
|
keyID = output.mid( pos, pos2-pos );
|
|
|
|
subkey->setKeyID( keyID );
|
|
|
|
break;
|
|
|
|
case 6: // the creation date (in seconds since 1970-01-01 00:00:00)
|
|
|
|
if( pos2 > pos )
|
|
|
|
subkey->setCreationDate( output.mid( pos, pos2-pos ).toLong() );
|
|
|
|
break;
|
|
|
|
case 7: // the expiration date (in seconds since 1970-01-01 00:00:00)
|
|
|
|
if( pos2 > pos )
|
|
|
|
subkey->setExpirationDate( output.mid( pos, pos2-pos ).toLong() );
|
|
|
|
else
|
|
|
|
subkey->setExpirationDate( -1 ); // key expires never
|
|
|
|
break;
|
|
|
|
case 8: // local ID (ignored)
|
|
|
|
case 9: // Ownertrust (ignored for now)
|
|
|
|
case 10: // User-ID (always empty in --fixed-list-mode)
|
|
|
|
case 11: // signature class (always empty except for key signatures)
|
|
|
|
break;
|
|
|
|
case 12: // key capabilities
|
|
|
|
for( int i=pos; i<pos2; i++ )
|
|
|
|
switch( output[i] )
|
|
|
|
{
|
|
|
|
case 'e':
|
|
|
|
subkey->setCanEncrypt( true );
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
subkey->setCanSign( true );
|
|
|
|
break;
|
|
|
|
case 'c':
|
|
|
|
subkey->setCanCertify( true );
|
|
|
|
break;
|
|
|
|
case 'E':
|
|
|
|
key->setCanEncrypt( true );
|
|
|
|
break;
|
|
|
|
case 'S':
|
|
|
|
key->setCanSign( true );
|
|
|
|
break;
|
|
|
|
case 'C':
|
|
|
|
key->setCanCertify( true );
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
kdDebug(5100) << "Unknown key capability\n";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pos = pos2 + 1;
|
|
|
|
pos2 = output.find( ':', pos );
|
|
|
|
}
|
|
|
|
key->addSubkey( subkey );
|
|
|
|
}
|
|
|
|
else if( !strncmp( output.data() + index, "uid:", 4 ) )
|
|
|
|
{ // line contains a user id
|
|
|
|
// Example: uid:f::::::::Philip R. Zimmermann <prz@pgp.com>:
|
|
|
|
|
|
|
|
UserID *userID = new UserID( "" );
|
|
|
|
|
|
|
|
int pos = index + 4; // begin of 2nd field
|
|
|
|
int pos2 = output.find( ':', pos );
|
|
|
|
for( int field=2; field <= 10; field++ )
|
|
|
|
{
|
|
|
|
switch( field )
|
|
|
|
{
|
|
|
|
case 2: // the calculated trust
|
|
|
|
if( pos2 > pos )
|
|
|
|
{
|
|
|
|
switch( output[pos] )
|
|
|
|
{
|
|
|
|
case 'i': // the user id is invalid, e.g. missing self-signature
|
|
|
|
userID->setInvalid( true );
|
|
|
|
break;
|
|
|
|
case 'r': // the user id has been revoked
|
|
|
|
userID->setRevoked( true );
|
|
|
|
break;
|
|
|
|
case '-': // undefined (no path leads to the key)
|
|
|
|
case 'q': // undefined (no trusted path leads to the key)
|
|
|
|
userID->setValidity( KPGP_VALIDITY_UNDEFINED );
|
|
|
|
break;
|
|
|
|
case 'n': // don't trust this key at all
|
|
|
|
userID->setValidity( KPGP_VALIDITY_NEVER );
|
|
|
|
break;
|
|
|
|
case 'm': // the key is marginally trusted
|
|
|
|
userID->setValidity( KPGP_VALIDITY_MARGINAL );
|
|
|
|
break;
|
|
|
|
case 'f': // the key is fully trusted
|
|
|
|
userID->setValidity( KPGP_VALIDITY_FULL );
|
|
|
|
break;
|
|
|
|
case 'u': // the key is ultimately trusted (secret key available)
|
|
|
|
userID->setValidity( KPGP_VALIDITY_ULTIMATE );
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
kdDebug(5100) << "Unknown trust value\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3: // these fields are empty
|
|
|
|
case 4:
|
|
|
|
case 5:
|
|
|
|
case 6:
|
|
|
|
case 7:
|
|
|
|
case 8:
|
|
|
|
case 9:
|
|
|
|
break;
|
|
|
|
case 10: // User-ID
|
|
|
|
TQCString uid = output.mid( pos, pos2-pos );
|
|
|
|
// replace "\xXX" with the corresponding character;
|
|
|
|
// other escaped characters, i.e. \n, \r etc., are ignored
|
|
|
|
// because they shouldn't appear in user IDs
|
|
|
|
for ( int idx = 0 ; (idx = uid.find( "\\x", idx )) >= 0 ; ++idx ) {
|
|
|
|
char str[2] = "x";
|
|
|
|
str[0] = (char) TQString( uid.mid( idx + 2, 2 ) ).toShort( 0, 16 );
|
|
|
|
uid.replace( idx, 4, str );
|
|
|
|
}
|
|
|
|
TQString uidString = TQString::fromUtf8( uid.data() );
|
|
|
|
// check whether uid was utf-8 encoded
|
|
|
|
bool isUtf8 = true;
|
|
|
|
for ( unsigned int i = 0; i + 1 < uidString.length(); ++i ) {
|
|
|
|
if ( uidString[i].unicode() == 0xdbff &&
|
|
|
|
uidString[i+1].row() == 0xde ) {
|
|
|
|
// we found a non-Unicode character (see TQString::fromUtf8())
|
|
|
|
isUtf8 = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( !isUtf8 ) {
|
|
|
|
// The user id isn't utf-8 encoded. It was most likely
|
|
|
|
// created with PGP which either used latin1 or koi8-r.
|
|
|
|
kdDebug(5100) << "User Id '" << uid
|
|
|
|
<< "' doesn't seem to be utf-8 encoded." << endl;
|
|
|
|
|
|
|
|
// We determine the ratio between non-ASCII and ASCII chars.
|
|
|
|
// A koi8-r user id should have lots of non-ASCII chars.
|
|
|
|
int nonAsciiCount = 0, asciiCount = 0;
|
|
|
|
|
|
|
|
// We only look at the first part of the user id (i. e. everything
|
|
|
|
// before the email address resp. before a comment)
|
|
|
|
for( signed char* ch = (signed char*)uid.data();
|
|
|
|
*ch && ( *ch != '(' ) && ( *ch != '<' );
|
|
|
|
++ch ) {
|
|
|
|
if( ( ( *ch >= 'A' ) && ( *ch <= 'Z' ) )
|
|
|
|
|| ( ( *ch >= 'a' ) && ( *ch <= 'z' ) ) )
|
|
|
|
++asciiCount;
|
|
|
|
else if( *ch < 0 )
|
|
|
|
++nonAsciiCount;
|
|
|
|
}
|
|
|
|
kdDebug(5100) << "ascii-nonAscii ratio : " << asciiCount
|
|
|
|
<< ":" << nonAsciiCount << endl;
|
|
|
|
if( nonAsciiCount > asciiCount ) {
|
|
|
|
// assume koi8-r encoding
|
|
|
|
kdDebug(5100) << "Assume koi8-r encoding." << endl;
|
|
|
|
TQTextCodec *codec = TQTextCodec::codecForName("KOI8-R");
|
|
|
|
uidString = codec->toUnicode( uid.data() );
|
|
|
|
// check the case of the first two characters to find out
|
|
|
|
// whether the user id is probably CP1251 encoded (for some
|
|
|
|
// reason in CP1251 the lower case characters have smaller
|
|
|
|
// codes than the upper case characters, so if the first char
|
|
|
|
// of the koi8-r decoded user id is lower case and the second
|
|
|
|
// char is upper case then it's likely that the user id is
|
|
|
|
// CP1251 encoded)
|
|
|
|
if( ( uidString.length() >= 2 )
|
|
|
|
&& ( uidString[0].lower() == uidString[0] )
|
|
|
|
&& ( uidString[1].upper() == uidString[1] ) ) {
|
|
|
|
// koi8-r decoded user id has inverted case, so assume
|
|
|
|
// CP1251 encoding
|
|
|
|
kdDebug(5100) << "No, it doesn't seem to be koi8-r. "
|
|
|
|
"Use CP 1251 instead." << endl;
|
|
|
|
TQTextCodec *codec = TQTextCodec::codecForName("CP1251");
|
|
|
|
uidString = codec->toUnicode( uid.data() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// assume latin1 encoding
|
|
|
|
kdDebug(5100) << "Assume latin1 encoding." << endl;
|
|
|
|
uidString = TQString::fromLatin1( uid.data() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
userID->setText( uidString );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pos = pos2 + 1;
|
|
|
|
pos2 = output.find( ':', pos );
|
|
|
|
}
|
|
|
|
|
|
|
|
// user IDs are printed in UTF-8 by gpg (if one uses --with-colons)
|
|
|
|
key->addUserID( userID );
|
|
|
|
}
|
|
|
|
else if( !strncmp( output.data() + index, "fpr:", 4 ) )
|
|
|
|
{ // line contains a fingerprint
|
|
|
|
// Example: fpr:::::::::17AFBAAF21064E513F037E6E63CB691DFAEBD5FC:
|
|
|
|
|
|
|
|
if (key == 0) // invalid key data
|
|
|
|
break;
|
|
|
|
|
|
|
|
// search the fingerprint (it's in the 10th field)
|
|
|
|
int pos = index + 4;
|
|
|
|
for( int i = 0; i < 8; i++ )
|
|
|
|
pos = output.find( ':', pos ) + 1;
|
|
|
|
int pos2 = output.find( ':', pos );
|
|
|
|
|
|
|
|
key->setFingerprint( keyID, output.mid( pos, pos2-pos ) );
|
|
|
|
}
|
|
|
|
index = eol + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//kdDebug(5100) << "finished parsing key data\n";
|
|
|
|
|
|
|
|
offset = index;
|
|
|
|
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KeyList
|
|
|
|
BaseG::parseKeyList( const TQCString& output, bool secretKeys )
|
|
|
|
{
|
|
|
|
KeyList keys;
|
|
|
|
Key *key = 0;
|
|
|
|
int offset;
|
|
|
|
|
|
|
|
// search start of key data
|
|
|
|
if( !strncmp( output.data(), "pub:", 4 )
|
|
|
|
|| !strncmp( output.data(), "sec:", 4 ) )
|
|
|
|
offset = 0;
|
|
|
|
else {
|
|
|
|
if( secretKeys )
|
|
|
|
offset = output.find( "\nsec:" );
|
|
|
|
else
|
|
|
|
offset = output.find( "\npub:" );
|
|
|
|
if( offset == -1 )
|
|
|
|
return keys;
|
|
|
|
else
|
|
|
|
offset++;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
key = parseKeyData( output, offset );
|
|
|
|
if( key != 0 )
|
|
|
|
keys.append( key );
|
|
|
|
}
|
|
|
|
while( key != 0 );
|
|
|
|
|
|
|
|
//kdDebug(5100) << "finished parsing keys" << endl;
|
|
|
|
|
|
|
|
return keys;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace Kpgp
|