Revision: 7496
http://mahogany.svn.sourceforge.net/mahogany/?rev=7496&view=rev
Author: vadz
Date: 2008-07-28 22:59:55 +0000 (Mon, 28 Jul 2008)
Log Message:
-----------
implement message signing (not exposed in the UI yet though)
Modified Paths:
--------------
trunk/M/include/SendMessage.h
trunk/M/include/SendMessageCC.h
trunk/M/include/modules/MCrypt.h
trunk/M/src/mail/SendMessageCC.cpp
trunk/M/src/modules/crypt/PGPEngine.cpp
Modified: trunk/M/include/SendMessage.h
===================================================================
--- trunk/M/include/SendMessage.h 2008-07-26 18:48:42 UTC (rev 7495)
+++ trunk/M/include/SendMessage.h 2008-07-28 22:59:55 UTC (rev 7496)
@@ -218,11 +218,22 @@
virtual void AddPart(MimeType::Primary type,
const void *buf, size_t len,
const String &subtype = M_EMPTYSTRING,
- const String &disposition = _T("INLINE"),
+ const String &disposition = "INLINE",
MessageParameterList const *dlist = NULL,
MessageParameterList const *plist = NULL,
wxFontEncoding enc = wxFONTENCODING_SYSTEM) = 0;
+ /**
+ Indicate whether the message should be cryptographically signed.
+
+ By default the message is not signed, this method must be called to try
+ signing it.
+
+ @param user The user name to use for signing, can be empty to use the
+ default one.
+ */
+ virtual void EnableSigning(const String& user = "") = 0;
+
/** Writes the message to a String
@param output string to write to
@return true if ok, false if an error occured
Modified: trunk/M/include/SendMessageCC.h
===================================================================
--- trunk/M/include/SendMessageCC.h 2008-07-26 18:48:42 UTC (rev 7495)
+++ trunk/M/include/SendMessageCC.h 2008-07-28 22:59:55 UTC (rev 7496)
@@ -84,11 +84,13 @@
virtual void AddPart(MimeType::Primary type,
const void *buf, size_t len,
const String &subtype = M_EMPTYSTRING,
- const String &disposition = _T("INLINE"),
+ const String &disposition = "INLINE",
MessageParameterList const *dlist = NULL,
MessageParameterList const *plist = NULL,
wxFontEncoding enc = wxFONTENCODING_SYSTEM);
+ virtual void EnableSigning(const String& user = "");
+
virtual bool WriteToString(String &output);
virtual bool WriteToFile(const String &filename, bool append = true);
@@ -125,9 +127,6 @@
/// init the header and contents from an existing message
void InitFromMsg(const Message *message, const wxArrayInt *partsToOmit);
- /// common part of InitNew() and InitFromMsg()
- void InitBody();
-
//@}
/** Sends the message.
@@ -138,16 +137,25 @@
bool Send(int flags);
/// set sender address fields
- void SetupFromAddresses(void);
+ void SetupFromAddresses();
/**
+ Sign the message cryptographically.
+
+ This is called by Build() if m_sign == true.
+
+ @return false if signing failed, this is a fatal error for this message
+ */
+ bool Sign();
+
+ /**
Builds the message prior to sending or saving it.
- @param forStorage if this is TRUE, store some extra information that is
+ @param forStorage if this is true, store some extra information that is
not supposed to be sent, like BCC header.
@return true if ok or false if we failed to build the message
*/
- bool Build(bool forStorage = FALSE);
+ bool Build(bool forStorage = false);
/// translate the (wxWin) encoding to (MIME) charset
String EncodingToCharset(wxFontEncoding enc);
@@ -184,13 +192,24 @@
/// the envelope
ENVELOPE *m_Envelope;
- /// the body
- BODY *m_Body;
+ /**
+ The top level part.
- /// the next and last body parts
- PART *m_NextPart,
- *m_LastPart;
+ We only used the BODY member of this part but using a PART (which
+ contains a BODY and a pointer to the next PART) allows us to efficiently
+ change the structure of the message by just switching pointers and
+ without copying any data. E.g. the first part added to this message
+ becomes m_partTop but if another part is added later we change m_partTop
+ to be a MULTIPART/MIXED part and set its nested part pointer to the
+ previous value. And if we need to sign the message at the end it's again
+ enough to simply replace m_partTop with a MULTIPART/SIGNED part and move
+ the previous m_partTop value to be its nested part.
+ */
+ PART *m_partTop;
+ /// Return the message BODY structure.
+ BODY *GetBody() const { return &m_partTop->body; }
+
//@}
/// the profile containing our settings
@@ -287,6 +306,19 @@
/// the parent frame (only used for the dialogs)
wxFrame *m_frame;
+
+ /// @name Cryptographic stuff.
+ //@{
+
+ /// If true, we must sign the message. False by default.
+ bool m_sign;
+
+ /// The user name to sign the message as, only used if m_sign and can be
+ /// empty even if m_sign is true.
+ String m_signAsUser;
+
+ //@}
+
// it uses our ctor
friend class SendMessage;
Modified: trunk/M/include/modules/MCrypt.h
===================================================================
--- trunk/M/include/modules/MCrypt.h 2008-07-26 18:48:42 UTC (rev 7495)
+++ trunk/M/include/modules/MCrypt.h 2008-07-28 22:59:55 UTC (rev 7496)
@@ -37,30 +37,20 @@
OK = 0,
CANNOT_EXEC_PROGRAM,
OPERATION_CANCELED_BY_USER,
- NO_PASSPHRASE,
- OUT_OF_ENV_SPACE_ERROR,
- BATCH_MODE_ERROR,
- BAD_ARGUMENT_ERROR,
- PROCESS_INTERRUPTED_ERROR,
- OUT_OF_MEM_ERROR,
- NONEXISTING_KEY_ERROR,
- KEYRING_ADD_ERROR,
- KEYRING_EXTRACT_ERROR,
- KEYRING_EDIT_ERROR,
- KEYRING_VIEW_ERROR,
- KEYRING_CHECK_ERROR,
- KEYRING_SIGNATURE_ERROR,
- SIGNATURE_EXPIRED_ERROR,
- SIGNATURE_UNTRUSTED_WARNING,
- SIGNATURE_ERROR,
- PUBLIC_KEY_ENCRIPTION_ERROR,
- ENCRYPTION_ERROR,
- COMPRESSION_ERROR,
- SIGNATURE_CHECK_ERROR,
- PUBLIC_KEY_DECRIPTION_ERROR,
- DECRYPTION_ERROR,
- DECOMPRESSION_ERROR,
+ BAD_ARGUMENT_ERROR, // bad input data format
+ NONEXISTING_KEY_ERROR, // no public key found
+
+ SIGNATURE_EXPIRED_ERROR, // expired signature in VerifySignature()
+ SIGNATURE_UNTRUSTED_WARNING, // valid signature from untrusted source
+ SIGNATURE_ERROR, // incorrect signature
+ SIGNATURE_CHECK_ERROR, // generic failure to verify signature
+
+ DECRYPTION_ERROR, // generic failure of decryption
NO_DATA_ERROR, // or no signature in VerifySignature()
+
+ SIGN_ERROR, // generic signing error
+ SIGN_UNKNOWN_MICALG, // unknown hash algorithm used in Sign()
+
NOT_IMPLEMENTED_ERROR, // operation not implemented by this engine
MAX_ERROR
};
@@ -117,6 +107,21 @@
Just signs the message.
Takes the user id to use and creates a signed message on output.
+
+ Notice that to create an RFC 3156 compliant message the caller must
+ retrieve the name of the hashing ("Message Integrity Check") algorithm
+ used during signing using MCryptoEngineOutputLog::GetMicAlg().
+
+ @param user
+ The user name to sign the message as, may be empty to use the default
+ user name.
+ @param messageIn
+ The contents of the message to sign, including any MIME headers (see
+ section 6.1 of the RFC 3156 for an example).
+ @param messageOut
+ Contains the input signature on successful return.
+ @param log
+ Optional sink for the messages generated during the operation.
*/
virtual Status Sign(const String& user,
const String& messageIn,
@@ -206,6 +211,19 @@
const String& GetPublicKey() const { return m_pubkey; }
void SetPublicKey(const String& pubkey) { m_pubkey = pubkey; }
+ /**
+ Return the name of the hashing algorithm used for generating the
+ signature.
+
+ This only makes sense to use after calling MCryptoEngine::Sign().
+
+ The name of the method comes from "micalg" parameter of multipart/signed
+ in OpenPGP message format, which in turn is an abbreviation of "Message
+ Integrity Check Algorithm".
+ */
+ const String& GetMicAlg() const { return m_micalg; }
+ void SetMicAlg(const String& micalg) { m_micalg = micalg; }
+
wxWindow *GetParent() const { return m_parent; }
private:
@@ -220,6 +238,9 @@
// the public key mentioned in the PGP output
String m_pubkey;
+
+ // the name of the hash algorithm used for signing
+ String m_micalg;
};
// ----------------------------------------------------------------------------
Modified: trunk/M/src/mail/SendMessageCC.cpp
===================================================================
--- trunk/M/src/mail/SendMessageCC.cpp 2008-07-26 18:48:42 UTC (rev 7495)
+++ trunk/M/src/mail/SendMessageCC.cpp 2008-07-28 22:59:55 UTC (rev 7496)
@@ -57,9 +57,12 @@
#include "XFace.h"
#include "gui/wxMDialogs.h"
+#include "modules/MCrypt.h"
+
#include <wx/file.h>
#include <wx/filename.h>
#include <wx/datetime.h>
+#include <wx/scopeguard.h>
extern bool InitSSL(); // from src/util/ssl.cpp
@@ -124,10 +127,13 @@
static long write_stream_output(void *, char *);
static long write_str_output(void *, char *);
+namespace
+{
+
// test if the header corresponds to one of the address headers
//
// NB: the header name must be in upper case
-static inline
+inline
bool IsAddressHeader(const String& name)
{
return name == "FROM" ||
@@ -139,7 +145,7 @@
// test if we allow this header to be set by user
//
// NB: the header name must be in upper case
-static inline
+inline
bool HeaderCanBeSetByUser(const String& name)
{
return name != "MIME-VERSION" &&
@@ -150,7 +156,7 @@
}
// check if the header name is valid (as defined in 2.2 of RFC 2822)
-static bool IsValidHeaderName(const char *name)
+bool IsValidHeaderName(const char *name)
{
if ( !name )
return false;
@@ -164,6 +170,19 @@
return true;
}
+// create a new BODY parameter and initialize it
+PARAMETER *CreateBodyParameter(const char *name, const char *value)
+{
+ PARAMETER * const par = mail_newbody_parameter();
+ par->attribute = strdup(name);
+ par->value = strdup(value);
+ par->next = NULL;
+
+ return par;
+}
+
+} // anonymous namespace
+
// ----------------------------------------------------------------------------
// private classes
// ----------------------------------------------------------------------------
@@ -295,6 +314,7 @@
m_encHeaders = wxFONTENCODING_SYSTEM;
m_cloneOfExisting = false;
+ m_sign = false;
m_headerNames =
m_headerValues = NULL;
@@ -302,11 +322,8 @@
m_wasBuilt = false;
m_Envelope = mail_newenvelope();
- m_Body = mail_newbody();
+ m_partTop = NULL;
- m_NextPart =
- m_LastPart = NULL;
-
if ( !profile )
{
FAIL_MSG( _T("SendMessageCC::Create() requires profile") );
@@ -410,24 +427,8 @@
}
}
-void SendMessageCC::InitBody()
-{
- // this is some strange code: we start by creating a fake multipart and then
- // flatten it in Build() if we realize that we don't have any other parts
- //
- // it could probably have been done simpler but this code is there since a
- // long time and seems to work, so let's not touch it without reason
- m_Body->type = TYPEMULTIPART;
- m_Body->nested.part = mail_newbody_part();
- m_Body->nested.part->next = NULL;
- m_NextPart = m_Body->nested.part;
- m_LastPart = m_NextPart;
-}
-
void SendMessageCC::InitNew()
{
- InitBody();
-
m_ReplyTo = READ_CONFIG_TEXT(m_profile, MP_REPLY_ADDRESS);
/*
@@ -439,7 +440,7 @@
if ( READ_CONFIG(m_profile, MP_GUESS_SENDER) )
{
m_Sender = READ_CONFIG_TEXT(m_profile, MP_SMTPHOST_LOGIN);
- m_Sender.Trim().Trim(FALSE); // remove all spaces on begin/end
+ m_Sender.Trim().Trim(false); // remove all spaces on begin/end
if ( Address::Compare(m_From, m_Sender) )
{
@@ -512,14 +513,16 @@
// now copy the body: note that we have to use ENC7BIT here to prevent
// c-client from (re)encoding the body
- m_Body->type = TYPETEXT;
- m_Body->encoding = ENC7BIT;
- m_Body->subtype = cpystr("PLAIN");
+ m_partTop = mail_newbody_part();
+ BODY& body = m_partTop->body;
+ body.type = TYPETEXT;
+ body.encoding = ENC7BIT;
+ body.subtype = cpystr("PLAIN");
- // FIXME: we potentially copy a lot of data here!
- String text = message->FetchText();
- m_Body->contents.text.data = (unsigned char *)cpystr(text.To8BitData());
- m_Body->contents.text.size = text.length();
+ // FIXME-OPT: we potentially copy a lot of data here!
+ const wxWX2MBbuf text8bit(message->FetchText().To8BitData());
+ body.contents.text.data = (unsigned char *)cpystr(text8bit);
+ body.contents.text.size = strlen(text8bit);
}
void
@@ -527,8 +530,6 @@
{
m_cloneOfExisting = true;
- InitBody();
-
// set the headers not supported by AddHeaderEntry()
// VZ: I'm not sure at all about what exactly we're trying to do here so
@@ -632,8 +633,8 @@
SendMessageCC::~SendMessageCC()
{
- mail_free_envelope (&m_Envelope);
- mail_free_body (&m_Body);
+ mail_free_envelope(&m_Envelope);
+ mail_free_body_part(&m_partTop);
if(m_headerNames)
{
@@ -1019,6 +1020,90 @@
}
bool
+SendMessageCC::Sign()
+{
+ // get the text to sign
+ BODY * const bodyOrig = GetBody();
+ rfc822_encode_body_7bit(NULL /* env is unused */, bodyOrig);
+
+ String textToSign;
+ char tmp[MAILTMPLEN + 1];
+ RFC822BUFFER
+ buf = { write_str_output, &textToSign, tmp, tmp, tmp + MAILTMPLEN };
+
+ if ( !rfc822_output_body_header(&buf, bodyOrig) ||
+ !rfc822_output_flush(&buf) ||
+ !(textToSign += "\r\n", rfc822_output_text(&buf, bodyOrig)) ||
+ !rfc822_output_flush(&buf) )
+ {
+ ERRORMESSAGE((_("Failed to create the text to sign.")));
+ return false;
+ }
+
+ // according to 5.1.1 of RFC 2046 the EOL before the boundary line in a
+ // multipart MIME message belongs to the boundary, and not the preceding
+ // line (so that it's possible to have content not terminating with EOL)
+ //
+ // as the old body will become the first part of multipart/signed message
+ // this EOL hence shouldn't be taken into account when signing it
+ if ( textToSign.EndsWith("\r\n") )
+ textToSign.erase(textToSign.length() - 2);
+
+
+ // sign it
+ MCryptoEngineFactory * const
+ factory = (MCryptoEngineFactory *)MModule::LoadModule("PGPEngine");
+ CHECK( factory, false, "failed to create PGPEngineFactory" );
+
+ wxON_BLOCK_EXIT_OBJ0(*factory, MCryptoEngineFactory::DecRef);
+
+ MCryptoEngine * const pgpEngine = factory->Get();
+ MCryptoEngineOutputLog log(m_frame);
+
+ String signature;
+ MCryptoEngine::Status status = pgpEngine->Sign
+ (
+ m_signAsUser,
+ textToSign,
+ signature,
+ &log
+ );
+ if ( status != MCryptoEngine::OK )
+ {
+ const size_t n = log.GetMessageCount();
+ String err = n == 0 ? String(_("no error information available"))
+ : log.GetMessage(n - 1);
+
+ ERRORMESSAGE((_("Signing the message failed: %s"), err.c_str()));
+
+ return false;
+ }
+
+ // create the new multipart/signed message
+ PART * const partOrig = m_partTop;
+
+ m_partTop = mail_newbody_part();
+ BODY& body = m_partTop->body;
+ body.type = TYPEMULTIPART;
+ body.subtype = cpystr("SIGNED");
+ body.nested.part = partOrig;
+
+ // fill in required parameters, see RFC 3156 section 5
+ body.parameter = CreateBodyParameter("protocol",
+ "application/pgp-signature");
+ body.parameter->next = CreateBodyParameter("micalg",
+ "pgp-" + log.GetMicAlg());
+
+ const wxWX2MBbuf asciiSig(signature.ToAscii());
+ AddPart(MimeType::APPLICATION,
+ asciiSig, strlen(asciiSig),
+ "PGP-SIGNATURE",
+ "" /* no disposition */);
+
+ return true;
+}
+
+bool
SendMessageCC::Build(bool forStorage)
{
if ( m_wasBuilt )
@@ -1204,17 +1289,14 @@
m_headerNames[h] = NULL;
m_headerValues[h] = NULL;
- mail_free_body_part(&m_LastPart->next);
- m_LastPart->next = NULL;
- // check if there is only one part, then we don't need multipart/mixed
- if(m_LastPart == m_Body->nested.part)
- {
- BODY *oldbody = m_Body;
- m_Body = &(m_LastPart->body);
- oldbody->nested.part = NULL;
- mail_free_body(&oldbody);
- }
+ // after fully constructing everything check if we need to add a
+ // cryptographic signature
+ //
+ // notice that we shouldn't sign (or modify in any other way) messages being
+ // resent/bounced nor those we don't send right now
+ if ( !forStorage && m_sign && !m_Envelope->remail && !Sign() )
+ return false;
return true;
}
@@ -1228,39 +1310,73 @@
MessageParameterList const *plist,
wxFontEncoding enc)
{
- BODY *bdy;
- unsigned char *data;
+ // adjust the input parameters
- // the text must be NUL terminated or it will not be encoded correctly and
- // it won't hurt to add a NUL after the end of data in the other cases as
- // well (note that cclient won't get this last NUL as len will be
- // decremented below)
- len += sizeof(char);
- data = (unsigned char *) fs_get (len);
- len -= sizeof(char);
+ // FIXME-OPT: we're copying a lot of data here, if we could ensure that
+ // buf is already allocated with malloc() and is NUL-terminated
+ // (this is important of encoding it wouldn't work correctly) we
+ // would be able to avoid it
+ unsigned char * const data = (unsigned char *) fs_get(len + sizeof(char));
data[len] = '\0';
memcpy(data, buf, len);
String subtype(subtype_given);
- if( subtype.length() == 0 )
+ if( subtype.empty() )
{
if ( type == TYPETEXT )
- subtype = _T("PLAIN");
+ subtype = "PLAIN";
else if ( type == TYPEAPPLICATION )
- subtype = _T("OCTET-STREAM");
+ subtype = "OCTET-STREAM";
else
{
// shouldn't send message without MIME subtype, but we don't have any
// and can't find the default!
ERRORMESSAGE((_("MIME type specified without subtype and\n"
"no default subtype for this type.")));
- subtype = _T("UNKNOWN");
+ subtype = "UNKNOWN";
}
}
- bdy = &(m_NextPart->body);
+ // create a new MIME part
+
+ // if it's the first one, it [provisionally] becomes the top level one
+ BODY *bdy;
+ if ( !m_partTop )
+ {
+ m_partTop = mail_newbody_part();
+
+ bdy = &m_partTop->body;
+ }
+ else // we already have some part(s)
+ {
+ PART *part = m_partTop->body.nested.part;
+ if ( !part )
+ {
+ // we need to create a new multipart/mixed top part and make the old
+ // part and this one its subparts
+ PART * const partOrig = m_partTop;
+
+ m_partTop = mail_newbody_part();
+ m_partTop->body.type = TYPEMULTIPART;
+ m_partTop->body.subtype = cpystr("MIXED");
+
+ part =
+ m_partTop->body.nested.part = partOrig;
+ }
+ else // we already have a top-level multipart
+ {
+ // add this part after the existing subparts
+ while ( part->next )
+ part = part->next;
+ }
+
+ PART * const partNew = mail_newbody_part();
+ part->next = partNew;
+
+ bdy = &partNew->body;
+ }
+
bdy->type = type;
-
bdy->subtype = cpystr(subtype.c_str());
bdy->contents.text.data = data;
@@ -1323,14 +1439,9 @@
break;
default:
- bdy->encoding = ENCBINARY;
+ bdy->encoding = NeedsToBeEncoded(data) ? ENCBINARY : ENC7BIT;
}
- m_NextPart->next = mail_newbody_part();
- m_LastPart = m_NextPart;
- m_NextPart = m_NextPart->next;
- m_NextPart->next = NULL;
-
PARAMETER *lastpar = NULL,
*par;
@@ -1398,7 +1509,8 @@
}
bdy->parameter = lastpar;
- bdy->disposition.type = strdup(disposition.ToAscii());
+ if ( !disposition.empty() )
+ bdy->disposition.type = strdup(disposition.ToAscii());
if ( dlist )
{
PARAMETER *lastpar = NULL,
@@ -1419,6 +1531,12 @@
}
}
+void SendMessageCC::EnableSigning(const String& user)
+{
+ m_sign = true;
+ m_signAsUser = user; // can be empty, ok
+}
+
// ----------------------------------------------------------------------------
// SendMessageCC sending
// ----------------------------------------------------------------------------
@@ -1508,7 +1626,9 @@
void SendMessageCC::Preview(String *text)
{
String textTmp;
- WriteToString(textTmp);
+ if ( !WriteToString(textTmp) )
+ return;
+
MDialog_ShowText(m_frame, _("Outgoing message text"), textTmp,
"SendPreview");
if ( text )
@@ -1787,13 +1907,13 @@
switch ( m_Protocol )
{
case Prot_SMTP:
- success = smtp_mail (stream,"MAIL",m_Envelope,m_Body) != 0;
+ success = smtp_mail(stream, "MAIL", m_Envelope, GetBody()) != NIL;
reply = wxString::From8BitData(stream->reply);
smtp_close (stream);
break;
case Prot_NNTP:
- success = nntp_mail (stream,m_Envelope,m_Body) != 0;
+ success = nntp_mail(stream, m_Envelope, GetBody()) != NIL;
reply = wxString::From8BitData(stream->reply);
nntp_close (stream);
break;
@@ -1869,7 +1989,8 @@
// install our output routine temporarily
Rfc822OutputRedirector redirect(true /* with bcc */, this);
- return rfc822_output(headers, m_Envelope, m_Body, writer, where, NIL) !=
NIL;
+ return rfc822_output(headers, m_Envelope, GetBody(),
+ writer, where, NIL) != NIL;
}
bool
Modified: trunk/M/src/modules/crypt/PGPEngine.cpp
===================================================================
--- trunk/M/src/modules/crypt/PGPEngine.cpp 2008-07-26 18:48:42 UTC (rev
7495)
+++ trunk/M/src/modules/crypt/PGPEngine.cpp 2008-07-28 22:59:55 UTC (rev
7496)
@@ -52,6 +52,30 @@
extern const MPersMsgBox *M_MSGBOX_REMEMBER_PGP_PASSPHRASE;
// ----------------------------------------------------------------------------
+// miscellaneous helper functions
+// ----------------------------------------------------------------------------
+
+namespace
+{
+
+void SkipSpaces(const wxChar *& pc)
+{
+ while ( *pc && wxIsspace(*pc) )
+ pc++;
+}
+
+String ReadNumber(const wxChar *& pc)
+{
+ const wxChar * const start = pc;
+ while ( *pc && ('0' <= *pc && *pc <= '9') )
+ pc++;
+
+ return String(start, pc - start);
+}
+
+} // anonymous namespace
+
+// ----------------------------------------------------------------------------
// PassphraseManager: this class can be used to remember the passphrases
// instead of asking the user to reenter them again and
// again (and again...)
@@ -130,7 +154,7 @@
/**
Executes PGP/GPG with messageIn on stdin and puts stdout into messageOut.
- @param options are hte PGP/GPG program options
+ @param options are the PGP/GPG program options
@param messageIn will be written to child stdin
@param messageOut will contain child stdout
@param log may contain miscellaneous additional info
@@ -141,12 +165,6 @@
String& messageOut,
MCryptoEngineOutputLog *log);
- /// this is the worker function used by ExecCommand()
- Status DoExecCommand(const String& options,
- const String& messageIn,
- String& messageOut,
- MCryptoEngineOutputLog *log);
-
private:
DECLARE_CRYPTO_ENGINE(PGPEngine)
};
@@ -168,7 +186,7 @@
// ----------------------------------------------------------------------------
/*
- The format of "gnupg --status-fd" output is descrived in the DETAILS file of
+ The format of "gnupg --status-fd" output is described in the DETAILS file of
gnupg distribution, see
http://cvs.gnupg.org/cgi-bin/viewcvs.cgi/gnupg/doc/DETAILS?rev=HEAD
@@ -193,15 +211,11 @@
};
PGPEngine::Status
-PGPEngine::DoExecCommand(const String& options,
- const String& messageIn,
- String& messageOut,
- MCryptoEngineOutputLog *log)
+PGPEngine::ExecCommand(const String& options,
+ const String& messageIn,
+ String& messageOut,
+ MCryptoEngineOutputLog *log)
{
- // First copy from in to out, in case there is a problem and we can't
- // execute the command. At least, the original message will be visible.
- messageOut = messageIn;
-
// check if we have a PGP command: it can be set to nothing to disable pgp
// support
const String pgp = READ_APPCONFIG_TEXT(MP_PGP_COMMAND);
@@ -247,7 +261,6 @@
wxTextInputStream errText(*err);
Status status = MAX_ERROR;
- bool encryptedForSomeoneElse = false;
// the user hint and the passphrase
String user,
@@ -261,13 +274,14 @@
const char *ptrIn = bufIn;
bool outEof = false,
- errEof = false;
+ errEof = false,
+ inEof = false; // set to true when we can safely close child stdin
while ( !process.IsDone() || !outEof || !errEof )
{
wxYieldIfNeeded();
// the order is important here, lest we deadlock: first get everything
- // gpg has for us and only then try to feed it more data
+ // GPG has for us and only then try to feed it more data
if ( out->GetLastError() == wxSTREAM_EOF )
{
outEof = true;
@@ -292,10 +306,12 @@
lenIn -= lenChunk;
ptrIn += lenChunk;
-
- if ( !lenIn )
- process.CloseOutput();
}
+ else if ( inEof && in )
+ {
+ process.CloseOutput();
+ in = NULL;
+ }
if ( err->GetLastError() == wxSTREAM_EOF )
{
@@ -328,23 +344,28 @@
code == _T("SIG_ID") ||
code == _T("DECRYPTION_OKAY") )
{
- if ( status != SIGNATURE_EXPIRED_ERROR ) {
+ if ( status != SIGNATURE_EXPIRED_ERROR )
+ {
+ wxLogStatus(_("Valid signature from \"%s\""),
+ log->GetUserID().c_str());
status = OK;
}
-
}
else if ( code == _T("BADARMOR") )
{
status = BAD_ARGUMENT_ERROR;
wxLogWarning(_("The PGP message is malformed, "
"processing aborted."));
- // If the message is not correctly formatted, GPG does not
+ // If the message is not correctly formatted, GPG does not
// output the resulting message, so we must copy the input
// into the output
messageOut = messageIn;
}
else if ( code == _T("EXPSIG") || code == _T("EXPKEYSIG") )
{
+ wxLogWarning(_("Expired signature from \"%s\""),
+ log->GetUserID().c_str());
+
status = SIGNATURE_EXPIRED_ERROR;
}
else if ( code == _T("BADSIG") )
@@ -366,6 +387,8 @@
code == _T("TRUST_NEVER") )
{
status = SIGNATURE_UNTRUSTED_WARNING;
+ wxLogStatus(_("Valid signature from (invalid) \"%s\""),
+ log->GetUserID().c_str());
}
// else: "_MARGINAL, _FULLY and _ULTIMATE" do not trigger a
warning
}
@@ -375,8 +398,7 @@
while ( *pc && !wxIsspace(*pc) )
pc++;
- if ( *pc )
- pc++;
+ SkipSpaces(pc);
// remember the user
user = pc;
@@ -394,8 +416,6 @@
{
status = OPERATION_CANCELED_BY_USER;
- process.CloseOutput();
-
break;
}
}
@@ -426,10 +446,15 @@
if ( wxStrcmp(pc, _T("passphrase.enter")) == 0 )
{
// we're being asked for a passphrase
- String pass2 = pass;
- pass2 += wxTextFile::GetEOL();
+ const String passNL = pass + wxTextFile::GetEOL();
- in->Write(pass2.c_str(), pass2.length());
+ wxWX2MBbuf buf(passNL.mb_str());
+ const size_t len = strlen(buf);
+ in->Write(buf, len);
+ if ( in->LastWrite() != len )
+ {
+ wxLogSysError("Failed to communicate with PGP: ");
+ }
}
else
{
@@ -466,8 +491,108 @@
}
else if ( code == _T("NO_SECKEY") )
{
- encryptedForSomeoneElse = true;
+ wxLogWarning(_("Secret key needed to decrypt this message is "
+ "not available"));
}
+ else if ( code == _T("BEGIN_SIGNING") )
+ {
+ // we don't need to send anything more to GPG, close its stdin
+ // so it knows that nothing more is coming
+ inEof = true;
+ }
+ else if ( code == _T("SIG_CREATED") )
+ {
+ status = OK;
+
+ // extract hash algorithm, the caller needs it to create OpenPGP
+ // message
+
+ // the format of this line is:
+ //
+ // SIG_CREATED <type> <pkalg> <micalg> <cls> <timestamp> <fp>
+ //
+ // where <type> is one of 'D' (detached), 'C' (clear text) and
+ // 'S' (standard); pkalg is a number (typical value is 17 for
+ // DSA) and the known hash algorithm values are below, see
+ //
http://cvs.gnupg.org/cgi-bin/viewcvs.cgi/trunk/include/cipher.h
+ static const struct
+ {
+ unsigned micalg;
+ const char *name;
+ } micalgNames[] =
+ {
+ { 1, "md5" },
+ { 2, "sha1" },
+ { 3, "ripemd160" },
+ { 8, "sha256" },
+ { 9, "sha384" },
+ { 10, "sha512" },
+ };
+
+ String err;
+ if ( *pc++ != 'D' )
+ {
+ err.Printf(_("unexpected signature type '%c'"), pc[-1]);
+ }
+ else
+ {
+ SkipSpaces(pc);
+
+ const String pkalg(ReadNumber(pc));
+ unsigned long n;
+ if ( !pkalg.ToULong(&n) )
+ {
+ err.Printf(_("unexpected public key algorithm \"%s\""),
+ pkalg.c_str());
+ }
+ else
+ {
+ SkipSpaces(pc);
+
+ const String micalg(ReadNumber(pc));
+ if ( !micalg.ToULong(&n) )
+ {
+ err.Printf(_("unexpected hash algorithm \"%s\""),
+ micalg.c_str());
+ }
+ else
+ {
+ bool found = false;
+ for ( size_t i = 0; i < WXSIZEOF(micalgNames); i++ )
+ {
+ if ( micalgNames[i].micalg == n )
+ {
+ if ( log )
+ log->SetMicAlg(micalgNames[i].name);
+
+ found = true;
+ status = OK;
+ break;
+ }
+ }
+
+ if ( !found )
+ {
+ err.Printf(_("unsupported hash algorithm \"%s\", "
+ "please configure GPG to use a hash "
+ "algorithm compatible with RFC 3156"),
+ micalg.c_str());
+
+ status = SIGN_UNKNOWN_MICALG;
+ }
+ }
+ }
+ }
+
+ if ( !err.empty() )
+ {
+ // don't overwrite a more specific error code if set above
+ if ( status != SIGN_UNKNOWN_MICALG )
+ status = SIGN_ERROR;
+
+ wxLogError(_("Failed to sign message: %s"), err.c_str());
+ }
+ }
else if ( code == _T("ENC_TO") ||
code == _T("BEGIN_DECRYPTION") ||
code == _T("END_DECRYPTION") ||
@@ -488,16 +613,14 @@
line.c_str());
}
// Extract user id used to sign
- if ( log &&
- ( code == _T("GOODSIG") ||
- code == _T("BADSIG") ) )
+ if ( log && (code == _T("GOODSIG") || code == _T("BADSIG")) )
{
String userId = String(pc).AfterFirst(' ');
log->SetUserID(userId);
}
}
-#if defined(NDEBUG) // In non-debug mode, log only free-form output
- else // normal (free form) gpg output
+#ifndef DEBUG // In non-debug mode, log only free-form output
+ else // normal (free form) GPG output
{
// remember in the output log object if we have one
if ( log )
@@ -505,35 +628,13 @@
log->AddMessage(line);
}
}
-#endif
+#endif // release
}
}
- switch (status)
- {
- case OK:
- wxLogStatus(_("Valid signature from \"%s\""),
- log->GetUserID().c_str());
- break;
+ if ( in )
+ process.CloseOutput();
- case SIGNATURE_UNTRUSTED_WARNING:
- wxLogStatus(_("Valid signature from (invalid) \"%s\""),
- log->GetUserID().c_str());
- break;
-
- case SIGNATURE_EXPIRED_ERROR:
- wxLogWarning(_("Expired signature from \"%s\""),
- log->GetUserID().c_str());
- break;
-
- default:
- if ( encryptedForSomeoneElse )
- {
- wxLogWarning(_("Secret key needed to decrypt this message is "
- "not available"));
- }
- }
-
// Removing this assert:
// There is at least one case (signature not detached) where GPG does not
output
// any line beginning with "[GNUPG:] ". So we can't rely on getting one.
@@ -548,15 +649,6 @@
return status;
}
-PGPEngine::Status
-PGPEngine::ExecCommand(const String& options,
- const String& messageIn,
- String& messageOut,
- MCryptoEngineOutputLog *log)
-{
- return DoExecCommand(options, messageIn, messageOut, log);
-}
-
// ----------------------------------------------------------------------------
// PGPEngine: encryption
// ----------------------------------------------------------------------------
@@ -584,6 +676,10 @@
return CANNOT_EXEC_PROGRAM;
}
+ // First copy from in to out, in case there is a problem and we can't
+ // execute the command. At least, the original message will be visible.
+ messageOut = messageIn;
+
return ExecCommand(tmpfname.GetName(), wxEmptyString, messageOut, log);
}
@@ -605,14 +701,30 @@
// ----------------------------------------------------------------------------
PGPEngine::Status
-PGPEngine::Sign(const String& /* user */,
- const String& /* messageIn */,
- String& /* messageOut */,
- MCryptoEngineOutputLog * /* log */)
+PGPEngine::Sign(const String& user,
+ const String& messageIn,
+ String& messageOut,
+ MCryptoEngineOutputLog *log)
{
- FAIL_MSG( _T("TODO") );
+ // as in Decrypt(), using stdin to pass both the input file contents and the
+ // passphrase doesn't work well, so use a temporary file
+ wxFile f;
+ MTempFileName tmpfname(&f);
+ if ( !tmpfname.IsOk() || !f.Write(messageIn) )
+ {
+ wxLogError(_("Can't pass the encrypted data to PGP."));
- return NOT_IMPLEMENTED_ERROR;
+ return CANNOT_EXEC_PROGRAM;
+ }
+
+ f.Close(); // close it before calling gpg, otherwise it fails to open it
+
+ String options("--detach-sign");
+ if ( !user.empty() )
+ options += "--local-user " + user;
+
+ options += ' ' + tmpfname.GetName();
+ return ExecCommand(options, wxEmptyString, messageOut, log);
}
@@ -656,8 +768,8 @@
wxString messageOut;
- return ExecCommand(_T("--verify ") + tmpfileSig.GetName() +
- _T(" ") + tmpfileText.GetName(),
+ return ExecCommand("--verify " + tmpfileSig.GetName() +
+ " " + tmpfileText.GetName(),
wxEmptyString, messageOut, log);
}
@@ -671,11 +783,11 @@
MCryptoEngineOutputLog *log) const
{
String dummyOut;
- Status status = const_cast<PGPEngine *>(this)->DoExecCommand
+ Status status = const_cast<PGPEngine *>(this)->ExecCommand
(
wxString::Format
(
- _T("--keyserver %s --recv-keys %s"),
+ "--keyserver %s --recv-keys %s",
keyserver.c_str(),
pk.c_str()
),
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Mahogany-cvsupdates mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/mahogany-cvsupdates