2019-10-25 20:16:43 +02:00
|
|
|
/*
|
|
|
|
|
* File: GpgMEWorker.cpp
|
2019-10-28 14:12:50 +01:00
|
|
|
* Author: SET - nmset@yandex.com
|
2019-10-25 20:16:43 +02:00
|
|
|
* License : LGPL v2.1
|
|
|
|
|
* Copyright SET - © 2019
|
|
|
|
|
*
|
|
|
|
|
* Created on 11 octobre 2019, 16:34
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "GpgMEWorker.h"
|
|
|
|
|
#include <gpgme++/keylistresult.h>
|
|
|
|
|
#include <gpgme++/importresult.h>
|
2020-11-11 14:47:05 +01:00
|
|
|
#include <gpgme++/keygenerationresult.h>
|
2019-10-25 20:16:43 +02:00
|
|
|
#include <locale>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <gpgme++/data.h>
|
|
|
|
|
|
2020-11-11 14:47:05 +01:00
|
|
|
using namespace std;
|
|
|
|
|
|
2020-11-16 14:39:57 +01:00
|
|
|
#define SPACE string(" ")
|
|
|
|
|
#define LESSTHAN string("<")
|
|
|
|
|
#define MORETHAN string(">")
|
|
|
|
|
#define LEFT_PARENTHESIS string("(")
|
|
|
|
|
#define RIGHT_PARENTHESIS string(")")
|
2020-11-11 14:47:05 +01:00
|
|
|
// From gpgme.h (C API), don't want to include it here for one const.
|
|
|
|
|
#define _CREATE_NOEXPIRE (1 << 13)
|
|
|
|
|
|
2019-10-25 20:16:43 +02:00
|
|
|
GpgMEWorker::GpgMEWorker()
|
|
|
|
|
{
|
|
|
|
|
m_ctx = Context::createForProtocol(Protocol::OpenPGP);
|
2020-11-07 12:21:49 +01:00
|
|
|
// Allow to list key certifications
|
|
|
|
|
m_ctx->setKeyListMode(GpgME::KeyListMode::Signatures
|
2020-11-07 22:17:44 +01:00
|
|
|
| GpgME::KeyListMode::Validate);
|
2020-11-03 11:06:25 +01:00
|
|
|
m_ppp = NULL;
|
2019-10-25 20:16:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GpgMEWorker::~GpgMEWorker()
|
|
|
|
|
{
|
2020-11-03 11:06:25 +01:00
|
|
|
delete m_ppp;
|
2019-10-25 20:16:43 +02:00
|
|
|
delete m_ctx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
vector<GpgME::Key> GpgMEWorker::FindKeys(const char * pattern, bool hasSecret, Error& e) const
|
|
|
|
|
{
|
|
|
|
|
vector<Key> klst;
|
|
|
|
|
e = m_ctx->startKeyListing(pattern, hasSecret);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return klst;
|
|
|
|
|
KeyListResult klr = m_ctx->keyListResult();
|
|
|
|
|
if (klr.error().code() != 0)
|
|
|
|
|
{
|
|
|
|
|
e = klr.error();
|
|
|
|
|
return klst;
|
|
|
|
|
}
|
|
|
|
|
while (e.code() == 0)
|
|
|
|
|
{
|
|
|
|
|
Key k = m_ctx->nextKey(e);
|
|
|
|
|
if (!k.isNull())
|
|
|
|
|
{ // What is that null key alongside ?
|
|
|
|
|
klst.push_back(k);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// e.code() != 0 here
|
|
|
|
|
klr = m_ctx->endKeyListing();
|
|
|
|
|
e = klr.error();
|
|
|
|
|
return klst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GpgME::Key GpgMEWorker::FindKey(const char * anyFullId, Error& e, bool secret) const
|
|
|
|
|
{
|
|
|
|
|
return m_ctx->key(anyFullId, e, secret);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const string GpgMEWorker::ImportKey(const char * filePath, Error& e)
|
|
|
|
|
{
|
|
|
|
|
// Should we use a mutex here ?
|
|
|
|
|
FILE * kFp = fopen(filePath, "rb");
|
|
|
|
|
GpgME::Data dKey(kFp);
|
|
|
|
|
vector<GpgME::Key> keys = dKey.toKeys();
|
|
|
|
|
if (keys.size() == 0)
|
|
|
|
|
{
|
|
|
|
|
fclose(kFp);
|
|
|
|
|
return "";
|
|
|
|
|
}
|
2020-11-11 21:09:43 +01:00
|
|
|
const string fpr = string(dKey.toKeys().at(0).primaryFingerprint()); // Must be done before import
|
2019-10-25 20:16:43 +02:00
|
|
|
ImportResult rImportKey = m_ctx->importKeys(dKey);
|
|
|
|
|
e = rImportKey.error();
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
{
|
|
|
|
|
fclose(kFp);
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fclose(kFp);
|
2020-11-11 21:09:43 +01:00
|
|
|
return fpr;
|
2020-10-25 17:28:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Error GpgMEWorker::EditOwnerTrust(const char* anyFullId, GpgME::Key::OwnerTrust trustLevel)
|
|
|
|
|
{
|
|
|
|
|
Error e;
|
|
|
|
|
Key k = FindKey(anyFullId, e, false);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
|
|
|
|
SetOwnerTrustEditInteractor * interactor = new SetOwnerTrustEditInteractor(trustLevel);
|
|
|
|
|
GpgME::Data d; // Internal processing data
|
|
|
|
|
return m_ctx->edit(k, std::unique_ptr<SetOwnerTrustEditInteractor> (interactor), d);
|
|
|
|
|
}
|
2020-11-03 11:06:25 +01:00
|
|
|
|
|
|
|
|
const Error GpgMEWorker::CertifyKey(const char* fprSigningKey,
|
|
|
|
|
const char * fprKeyToSign,
|
|
|
|
|
vector<uint>& userIDsToSign, int options,
|
|
|
|
|
const string& passphrase)
|
|
|
|
|
{
|
|
|
|
|
Error e;
|
|
|
|
|
Key signingKey = FindKey(fprSigningKey, e, true);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
|
|
|
|
e = m_ctx->addSigningKey(signingKey); // +++
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
|
|
|
|
Key keyToSign = FindKey(fprKeyToSign, e, false);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
2020-11-07 22:17:44 +01:00
|
|
|
|
2020-11-03 11:06:25 +01:00
|
|
|
// GPG engine will fetch for passphrase in the custom provider.
|
|
|
|
|
m_ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback);
|
|
|
|
|
if (m_ppp == NULL)
|
|
|
|
|
m_ppp = new LoopbackPassphraseProvider();
|
|
|
|
|
m_ppp->SetPassphrase(passphrase);
|
|
|
|
|
m_ctx->setPassphraseProvider(m_ppp);
|
2020-11-07 22:17:44 +01:00
|
|
|
|
2020-11-03 11:06:25 +01:00
|
|
|
SetSignKeyEditInteractor * interactor = new SetSignKeyEditInteractor();
|
|
|
|
|
interactor->setKey(keyToSign);
|
|
|
|
|
interactor->setUserIDsToSign(userIDsToSign);
|
|
|
|
|
interactor->setSigningOptions(options);
|
|
|
|
|
// What's that check level ?
|
|
|
|
|
// interactor->setCheckLevel(2);
|
|
|
|
|
GpgME::Data d;
|
|
|
|
|
e = m_ctx->edit(keyToSign, std::unique_ptr<SetSignKeyEditInteractor> (interactor), d);
|
|
|
|
|
m_ctx->clearSigningKeys();
|
|
|
|
|
/*
|
|
|
|
|
* On error, always : code = 1024 | asString = User defined error code 1
|
|
|
|
|
* Can't distinguish between bad password or whatever cause.
|
|
|
|
|
*/
|
|
|
|
|
return e;
|
|
|
|
|
}
|
2020-11-07 22:17:44 +01:00
|
|
|
|
2020-11-21 20:53:01 +01:00
|
|
|
const Error GpgMEWorker::SetSubkeyExpiryTime(const char* keyFpr,
|
|
|
|
|
const char* subkeyFpr,
|
|
|
|
|
const string& passphrase,
|
|
|
|
|
ulong expires)
|
|
|
|
|
{
|
|
|
|
|
Error e;
|
|
|
|
|
Key k = FindKey(keyFpr, e, true);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
|
|
|
|
e = m_ctx->addSigningKey(k); // +++
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
|
|
|
|
|
|
|
|
|
vector<GpgME::Subkey> subkey;
|
|
|
|
|
for (uint i = 0; i < k.subkeys().size(); i++)
|
|
|
|
|
{
|
|
|
|
|
GpgME::Subkey sk = k.subkey(i);
|
|
|
|
|
if (string(sk.fingerprint()) == string(subkeyFpr))
|
|
|
|
|
{
|
|
|
|
|
subkey.push_back(sk);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// There should always be at least one subkey (the key itself).
|
|
|
|
|
|
|
|
|
|
m_ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback);
|
|
|
|
|
if (m_ppp == NULL)
|
|
|
|
|
m_ppp = new LoopbackPassphraseProvider();
|
|
|
|
|
m_ppp->SetPassphrase(passphrase);
|
|
|
|
|
m_ctx->setPassphraseProvider(m_ppp);
|
|
|
|
|
|
|
|
|
|
e = m_ctx->setExpire(k, expires, subkey);
|
|
|
|
|
|
|
|
|
|
return e;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-07 22:17:44 +01:00
|
|
|
const Error GpgMEWorker::SetExpiryTime(const char * keyFpr,
|
|
|
|
|
const string& passphrase,
|
|
|
|
|
const string& timeString)
|
|
|
|
|
{
|
|
|
|
|
Error e;
|
|
|
|
|
Key k = FindKey(keyFpr, e, true);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
|
|
|
|
e = m_ctx->addSigningKey(k); // +++
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
2020-11-11 14:47:05 +01:00
|
|
|
|
2020-11-07 22:17:44 +01:00
|
|
|
m_ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback);
|
|
|
|
|
if (m_ppp == NULL)
|
|
|
|
|
m_ppp = new LoopbackPassphraseProvider();
|
|
|
|
|
m_ppp->SetPassphrase(passphrase);
|
|
|
|
|
m_ctx->setPassphraseProvider(m_ppp);
|
2020-11-11 14:47:05 +01:00
|
|
|
|
2020-11-07 22:17:44 +01:00
|
|
|
SetExpiryTimeEditInteractor * interactor
|
|
|
|
|
= new SetExpiryTimeEditInteractor(timeString);
|
|
|
|
|
GpgME::Data d;
|
|
|
|
|
e = m_ctx->edit(k, std::unique_ptr<SetExpiryTimeEditInteractor> (interactor), d);
|
|
|
|
|
m_ctx->clearSigningKeys();
|
2020-11-21 20:53:01 +01:00
|
|
|
// NB : with a wrong passphrase, e.code() is 0 !
|
2020-11-11 14:47:05 +01:00
|
|
|
return e;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-16 15:58:50 +01:00
|
|
|
const Error GpgMEWorker::AddUserID(const char* keyFpr, const string& passphrase,
|
|
|
|
|
const string& name, const string& email,
|
|
|
|
|
const string& comment)
|
|
|
|
|
{
|
|
|
|
|
Error e;
|
|
|
|
|
Key k = FindKey(keyFpr, e, true);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
|
|
|
|
e = m_ctx->addSigningKey(k);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
|
|
|
|
|
|
|
|
|
m_ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback);
|
|
|
|
|
if (m_ppp == NULL)
|
|
|
|
|
m_ppp = new LoopbackPassphraseProvider();
|
|
|
|
|
m_ppp->SetPassphrase(passphrase);
|
|
|
|
|
m_ctx->setPassphraseProvider(m_ppp);
|
|
|
|
|
|
|
|
|
|
AddUserIDEditInteractor * interactor = new AddUserIDEditInteractor();
|
|
|
|
|
interactor->setNameUtf8(name);
|
|
|
|
|
interactor->setEmailUtf8(email);
|
|
|
|
|
interactor->setCommentUtf8(comment);
|
|
|
|
|
GpgME::Data d;
|
|
|
|
|
e = m_ctx->edit(k, std::unique_ptr<AddUserIDEditInteractor> (interactor), d);
|
|
|
|
|
m_ctx->clearSigningKeys();
|
|
|
|
|
|
|
|
|
|
return e;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-18 20:48:15 +01:00
|
|
|
const Error GpgMEWorker::RevokeUserID(const char* keyFpr,
|
|
|
|
|
const string& passphrase,
|
|
|
|
|
const string& name, const string& email,
|
|
|
|
|
const string& comment)
|
|
|
|
|
{
|
|
|
|
|
Error e;
|
|
|
|
|
Key k = FindKey(keyFpr, e, true);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
|
|
|
|
e = m_ctx->addSigningKey(k);
|
|
|
|
|
if (e.code() != 0)
|
|
|
|
|
return e;
|
|
|
|
|
|
|
|
|
|
m_ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback);
|
|
|
|
|
if (m_ppp == NULL)
|
|
|
|
|
m_ppp = new LoopbackPassphraseProvider();
|
|
|
|
|
m_ppp->SetPassphrase(passphrase);
|
|
|
|
|
m_ctx->setPassphraseProvider(m_ppp);
|
|
|
|
|
|
|
|
|
|
const string uid = MakeUidString(name, email, comment);
|
|
|
|
|
e = m_ctx->revUid(k, uid.c_str());
|
|
|
|
|
k.update();
|
|
|
|
|
m_ctx->clearSigningKeys();
|
|
|
|
|
|
|
|
|
|
return e;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-11 14:47:05 +01:00
|
|
|
/*
|
|
|
|
|
* Using a temporary context for key creation. It is altered after secret key
|
|
|
|
|
* creation, and subkey creation fails thereafter. This is observational.
|
|
|
|
|
*/
|
|
|
|
|
const Error GpgMEWorker::CreateKeyWithEngineDefaultAlgo(GpgME::Key& k,
|
|
|
|
|
const string& name,
|
|
|
|
|
const string& email,
|
|
|
|
|
const string& comment,
|
|
|
|
|
const string& passphrase,
|
|
|
|
|
ulong expires)
|
|
|
|
|
{
|
|
|
|
|
Error e;
|
|
|
|
|
Context * ctx = Context::createForProtocol(Protocol::OpenPGP);
|
|
|
|
|
LoopbackPassphraseProvider * ppp = new LoopbackPassphraseProvider(passphrase);
|
|
|
|
|
ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback);
|
|
|
|
|
ctx->setPassphraseProvider(ppp);
|
|
|
|
|
|
2020-11-16 14:39:57 +01:00
|
|
|
const string uid =MakeUidString(name, email, comment);
|
2020-11-11 14:47:05 +01:00
|
|
|
uint flags = expires
|
|
|
|
|
? 0 : _CREATE_NOEXPIRE;
|
|
|
|
|
|
|
|
|
|
KeyGenerationResult kgr = ctx->createKeyEx(uid.c_str(), "default",
|
2020-11-12 22:25:14 +01:00
|
|
|
0, expires, k, flags);
|
|
|
|
|
delete ppp;
|
|
|
|
|
delete ctx;
|
|
|
|
|
if (kgr.error().code() == 0)
|
|
|
|
|
// Why is k not assigned the newly created key ?!
|
|
|
|
|
k = FindKey(kgr.fingerprint(), e, true);
|
2020-11-11 14:47:05 +01:00
|
|
|
return kgr.error();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Error GpgMEWorker::CreateKey(GpgME::Key& k,
|
|
|
|
|
const string& name,
|
|
|
|
|
const string& email,
|
|
|
|
|
const string& comment,
|
|
|
|
|
const char* algo,
|
|
|
|
|
const string& passphrase,
|
|
|
|
|
ulong expires)
|
|
|
|
|
{
|
|
|
|
|
Error e;
|
|
|
|
|
Context * ctx = Context::createForProtocol(Protocol::OpenPGP);
|
|
|
|
|
LoopbackPassphraseProvider * ppp = new LoopbackPassphraseProvider(passphrase);
|
|
|
|
|
ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback);
|
|
|
|
|
ctx->setPassphraseProvider(ppp);
|
|
|
|
|
|
2020-11-16 14:39:57 +01:00
|
|
|
const string uid = MakeUidString(name, email, comment);
|
2020-11-11 14:47:05 +01:00
|
|
|
uint flags = expires
|
|
|
|
|
? 0 : _CREATE_NOEXPIRE;
|
|
|
|
|
|
|
|
|
|
KeyGenerationResult kgr = ctx->createKeyEx(uid.c_str(), algo,
|
|
|
|
|
0, expires, k, flags);
|
|
|
|
|
delete ppp;
|
|
|
|
|
delete ctx;
|
2020-11-12 22:25:14 +01:00
|
|
|
if (kgr.error().code() == 0)
|
|
|
|
|
// Why is k not assigned the newly created key ?!
|
|
|
|
|
k = FindKey(kgr.fingerprint(), e, true);
|
2020-11-11 14:47:05 +01:00
|
|
|
return kgr.error();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Error GpgMEWorker::CreateSubKey(GpgME::Key& k,
|
|
|
|
|
const char* algo,
|
|
|
|
|
const string& passphrase,
|
|
|
|
|
ulong expires)
|
|
|
|
|
{
|
|
|
|
|
Error e;
|
|
|
|
|
Context * ctx = Context::createForProtocol(Protocol::OpenPGP);
|
|
|
|
|
LoopbackPassphraseProvider * ppp = new LoopbackPassphraseProvider(passphrase);
|
|
|
|
|
ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback);
|
|
|
|
|
ctx->setPassphraseProvider(ppp);
|
|
|
|
|
|
|
|
|
|
uint flags = expires
|
|
|
|
|
? 0 : _CREATE_NOEXPIRE;
|
|
|
|
|
|
|
|
|
|
e = ctx->createSubkey(k, algo, 0, expires, flags);
|
|
|
|
|
k.update();
|
|
|
|
|
delete ppp;
|
|
|
|
|
delete ctx;
|
2020-11-07 22:17:44 +01:00
|
|
|
return e;
|
|
|
|
|
}
|
2020-11-15 11:47:30 +01:00
|
|
|
|
|
|
|
|
#ifdef DEVTIME
|
|
|
|
|
|
|
|
|
|
const Error GpgMEWorker::ExportPrivateKey(const char * pattern, string& buffer,
|
|
|
|
|
const string& passphrase)
|
|
|
|
|
{
|
|
|
|
|
GpgME::Data kData;
|
|
|
|
|
Context * ctx = Context::createForProtocol(Protocol::OpenPGP);
|
|
|
|
|
LoopbackPassphraseProvider * ppp = new LoopbackPassphraseProvider();
|
|
|
|
|
ppp->SetPassphrase(passphrase);
|
|
|
|
|
ctx->setPinentryMode(Context::PinentryMode::PinentryLoopback);
|
|
|
|
|
ctx->setPassphraseProvider(ppp);
|
|
|
|
|
|
|
|
|
|
ctx->setArmor(true);
|
|
|
|
|
uint flags = Context::ExportSecret;
|
|
|
|
|
|
|
|
|
|
Error e = ctx->exportPublicKeys(pattern, kData, flags);
|
|
|
|
|
buffer = kData.toString(); // Empty
|
2020-11-15 11:58:07 +01:00
|
|
|
|
2020-11-15 11:47:30 +01:00
|
|
|
delete ppp;
|
|
|
|
|
delete ctx;
|
|
|
|
|
|
|
|
|
|
return e;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2020-11-15 11:58:07 +01:00
|
|
|
|
|
|
|
|
const Error GpgMEWorker::ExportPublicKey(const char* pattern, string& buffer)
|
|
|
|
|
{
|
|
|
|
|
GpgME::Data kData;
|
|
|
|
|
Context * ctx = Context::createForProtocol(Protocol::OpenPGP);
|
|
|
|
|
ctx->setArmor(true);
|
|
|
|
|
uint flags = Context::ExportDefault;
|
|
|
|
|
|
|
|
|
|
Error e = ctx->exportPublicKeys(pattern, kData, flags);
|
|
|
|
|
buffer = kData.toString();
|
|
|
|
|
|
|
|
|
|
delete ctx;
|
|
|
|
|
|
|
|
|
|
return e;
|
|
|
|
|
}
|
2020-11-16 14:39:57 +01:00
|
|
|
|
|
|
|
|
string GpgMEWorker::MakeUidString(const string& name, const string& email, const string& comment)
|
|
|
|
|
{
|
2020-11-18 20:40:16 +01:00
|
|
|
string uid = name;
|
2020-11-16 14:39:57 +01:00
|
|
|
if (!comment.empty())
|
|
|
|
|
uid += SPACE + LEFT_PARENTHESIS + comment + RIGHT_PARENTHESIS;
|
2020-11-18 20:40:16 +01:00
|
|
|
uid += SPACE
|
|
|
|
|
+ LESSTHAN + email + MORETHAN;
|
2020-11-16 14:39:57 +01:00
|
|
|
return uid;
|
|
|
|
|
}
|