Camel.CipherContext

CamelCipherContext is used to access encryption and signature engines which can be used to encrypt, sign, decrypt, or verify Evolution/Camel.DataWrapper#Camel.MimePart message parts and content.

Base class

The base class includes some information needed by it, and by implementors.

 typedef struct _CamelCipherContext {
        CamelObject parent_object;
        
        struct _CamelCipherContextPrivate *priv;
        
        CamelSession *session;
        
        const char *sign_protocol;
        const char *encrypt_protocol;
        const char *key_protocol;
 } CamelCipherContext;

sign_protocol, encrypt_protcol, and key_protocol must all be initialised by the implementing class to values suitable for the various MIME header fields used in the encrypted and signed parts. e.g. S/MIME will assign sign_protocol = "application/x-pkcs7-signature";

 void camel_cipher_context_construct(CamelCipherContext *context, CamelSession *session);

Implementations need to call the construct method for the root class to initialise itself.

Hash name functions

Because CamelCipherContext tries to wrap any type of cipher backend, it tries to define all capabilities, and then let the implementing class control the variants. The cipher_id functions take the micalg (message integrity check algorithm) description for that protocol, and convert it to/from the internal CIPHER_HASH.

 typedef enum {
        CAMEL_CIPHER_HASH_DEFAULT,
        CAMEL_CIPHER_HASH_MD2,
        CAMEL_CIPHER_HASH_MD5,
        CAMEL_CIPHER_HASH_SHA1,
        CAMEL_CIPHER_HASH_RIPEMD160,
        CAMEL_CIPHER_HASH_TIGER192,
        CAMEL_CIPHER_HASH_HAVAL5160
 } CamelCipherHash;
 
 CamelCipherHash camel_cipher_id_to_hash(CamelCipherContext *context, const char *id);
 const char *camel_cipher_hash_to_id(CamelCipherContext *context, CamelCipherHash hash);

These functions do not appear to be used anywhere outside of the specific context implementations themselves; they should probably be removed.

Validity functions

Because a given part may include multiple levels of encryption and signing, a complex structure is required to describe the content for display in a modern mail viewer. The CamelCipherValidity structure can be used to create a tree describing the status of each encrypted or signed part.

Again, this code is trying to be generic enough to cover both GPG and S/MIME data; some information required to properly implement features like tracking specific certificates used is missing.

First, the signature status.

 typedef enum _camel_cipher_validity_sign_t {
        CAMEL_CIPHER_VALIDITY_SIGN_NONE,
        CAMEL_CIPHER_VALIDITY_SIGN_GOOD,
        CAMEL_CIPHER_VALIDITY_SIGN_BAD,
        CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN,
 } camel_cipher_validity_sign_t;

Then, the encryption status.

 typedef enum _camel_cipher_validity_encrypt_t {
        CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE,
        CAMEL_CIPHER_VALIDITY_ENCRYPT_WEAK,
        CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED,
        CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG,
 } camel_cipher_validity_encrypt_t;

The type of validity for this specific validity instance. This is used when merging validities.

 typedef enum _camel_cipher_validity_mode_t {
        CAMEL_CIPHER_VALIDITY_SIGN,
        CAMEL_CIPHER_VALIDITY_ENCRYPT,
 } camel_cipher_validity_mode_t;

For signers and encryptors, some information to identify the certificate. Note that this is a generic structure so doesn't include information required for specific backends. See the Evolution/#Notes section for discussions about this.

 struct _CamelCipherCertInfo {
        struct _CamelCipherCertInfo *next;
        struct _CamelCipherCertInfo *prev;
 
        char *name;             /* common name */
        char *email;
 };

And finally we have the validity object itself, from which a tree of information about the part may be derived. A part may have multiple levels of signing, encrypting, using different algorithms, wrapped in arbitrary layers, so this complex structure is required to describe it.

 struct _CamelCipherValidity {
        struct _CamelCipherValidity *next;
        struct _CamelCipherValidity *prev;
        EDList children;
 
        struct {
                enum _camel_cipher_validity_sign_t status;
                char *description;
                EDList signers; /* CamelCipherCertInfo's */
        } sign;
        struct {
                enum _camel_cipher_validity_encrypt_t status;
                char *description;
                EDList encrypters;      /* CamelCipherCertInfo's */
        } encrypt;
 };

And then we have a whole gaggle of methods for interacting with the validity structures. Some of these walk the tree to calculate their results, let you build validity structures and merge them together. These are used by both CipherContent implementations and client code during mail display.

 CamelCipherValidity *camel_cipher_validity_new(void);
 void camel_cipher_validity_init(CamelCipherValidity *validity);
 gboolean camel_cipher_validity_get_valid(CamelCipherValidity *validity);
 void camel_cipher_validity_set_valid(CamelCipherValidity *validity, gboolean valid);
 char *camel_cipher_validity_get_description(CamelCipherValidity *validity);
 void camel_cipher_validity_set_description(CamelCipherValidity *validity, const char *description);
 void camel_cipher_validity_clear(CamelCipherValidity *validity);
 CamelCipherValidity *camel_cipher_validity_clone(CamelCipherValidity *vin);
 void camel_cipher_validity_add_certinfo(CamelCipherValidity *vin, camel_cipher_validity_mode_t mode, const char *name, const char *email);
 void camel_cipher_validity_envelope(CamelCipherValidity *valid, CamelCipherValidity *outer);
 void camel_cipher_validity_free(CamelCipherValidity *validity);

It's a pretty messy api, but it's solving a messy problem.

Key routines

These are designed to import or export keys from signature content or to attachments. See the API documentation.

These should be written to take CamelMimePart objects as with the sign/encyrpt functions, as it is implementation dependent on exactly which stream is required to get the information from. And that logic needn't be exposed to client code.

 int camel_cipher_import_keys(CamelCipherContext *context, struct _CamelStream *istream, CamelException *ex);
 int camel_cipher_export_keys(CamelCipherContext *context, GPtrArray *keys, struct _CamelStream *ostream, CamelException *ex);

I don't think these are actually used anywhere in the client code - we rely on other external utilities to perform these tasks.

Cipher routines

The sign and verify functions are straightforward. You simply supply the entire CamelMimePart which contains the information in a raw format, and it will spit out a new CamelMimePart which contains whatever objects and content is required to represent it. This is then used in place of the original content object in building up the message to be sent.

 int camel_cipher_sign(CamelCipherContext *context, const char *userid, CamelCipherHash hash,

 CamelCipherValidity *camel_cipher_verify(CamelCipherContext *context, struct _CamelMimePart *ipart, CamelException *ex);

Directly specifying the hash seems a little strange, as there is no discovery mechanism to find out what a given implementation supports.

Agian, encryption and decryption works in much the same way.

 int camel_cipher_encrypt(CamelCipherContext *context, const char *userid, GPtrArray *recipients, struct _CamelMimePart *ipart, struct _CamelMimePart *opart, CamelException *ex);
 CamelCipherValidity *camel_cipher_decrypt(CamelCipherContext *context, struct _CamelMimePart *ipart, struct _CamelMimePart *opart, CamelException *ex);

And finally a simple helper function that can help properly canoncalise content for signing or encryption. This may change the content-transfer-encoding of parts within the object if any specified might lead to ambigousous canonicalisation at the receiving end.

Unfortunately, due to the brain-dead RFC-1847, there is almost no way to guarantee any canonicalisation is really canonical.

 int camel_cipher_canonical_to_stream(CamelMimePart *part, guint32 flags, CamelStream *ostream);

CamelGPGContext

This is a GPG based PGP cipher context. It implements both the PGP-MIME multipart/signed and the BrokenPGP text/plain 'inline pgp' rubbish.

 CamelCipherContext *camel_gpg_context_new(CamelSession *session);
 
 void camel_gpg_context_set_always_trust(CamelGpgContext *ctx, gboolean trust);

It has one option, to always-trust keys. All other options are provided by the gpg program's configuration file.

CamelSMIMEContext

This implements S/MIME encrypt/decrypt, sign and verify using Mozilla's NSS library.

 CamelCipherContext *camel_smime_context_new(CamelSession *session);

Then there are a couple of options.

 void camel_smime_context_set_encrypt_key(CamelSMIMEContext *context, gboolean use, const char *key);

This specifies the nickname to use for the SMIMEEncKeyPrefs attribute. This indicates to the receiving party which encryption key should be used to encrypt data to the sending party. The key will be added to the data as well.

 typedef enum _camel_smime_sign_t {
        CAMEL_SMIME_SIGN_CLEARSIGN,
        CAMEL_SMIME_SIGN_ENVELOPED
 } camel_smime_sign_t;
 
 void camel_smime_context_set_sign_mode(CamelSMIMEContext *context, camel_smime_sign_t type);

S/MIME signed data may be either clear-signed, using attachments, or enveloped. That is the content is in one part, and the signature is in a separate part of a multipart/signed part. If enveloped, the content and signature is wrapped in a binary object and stored in a signle application/x-pkcs7-mime object.

The former method allows the content to be read, even if the receiving party cannot check the signature. However, it suffers from the same brain-dead shortcommings that PGP-MIME does. Using enveloped signing is the only way to guarantee the sending party can verify verifiable content.

 typedef enum _camel_smime_describe_t {
        CAMEL_SMIME_SIGNED = 1<<0,
        CAMEL_SMIME_ENCRYPTED = 1<<1,
        CAMEL_SMIME_CERTS = 1<<2,
        CAMEL_SMIME_CRLS = 1<<3,
 } camel_smime_describe_t;
 
 guint32 camel_smime_context_describe_part(CamelSMIMEContext *, struct _CamelMimePart *);

This can be used to find out what is inside an S/MIME part.

Passwords

Any pass phrases required are queried through the Mozilla/NSS callback mechanism, for which only one callback can be registered for a given application. Use PK11_SetPasswordFunc() to set the password callback.

Camel used to do this itself, and use the Evolution/Camel.Session password interface, but this interfered with the rest of the application.

Example: Encrypting an entire message

This will create a new message and new part, and then encrypt the part into the message. Note that the destination part in this case is the message itself, so the new encrypted content just goes straight into the content-object of the message.

        CamelCipherContext *smime;
        CamelMimeMessage *msg;
        CamelMimePart *part;
        GPtrArray *recipients;
        CamelInternetAddress *cia;
 
        msg = camel_mime_message_new();
 
        addr = camel_internet_address_new();
        camel_internet_address_add(addr, "NotZed" "notzed@somewhere.else.com");
        camel_mime_message_set_to(msg, addr);
 
        part = camel_mime_part_new();
        text = "This is encrypted content\n";
        camel_mime_part_set_content(part, text, strlen(text), "text/plain");
 
        recipients = g_ptr_array_new();
        g_ptr_array_add(recipients, "notzed@somewhere.else.com");
 
        smime = caml_smime_context_new(session);
        camel_cipher_encrypt(smime, NULL, recipients, part, (CamelMimePart *)msg, ex);
        camel_object_unref(smime);
 
        g_ptr_array_free(recipients, TRUE);
        camel_object_unref(part);
        camel_object_unref(addr);

Note: This example is untested

Signing would be done in much the same way.

See evolution/composer/e-msg-composer.c:build_message for a more complete example, which also demonstrates multi-level operations.

Example: verifying a multipart/signed part

This example assumes that the incoming part is a part of type multipart/signed.

 static void dump_infos(const char *desc, EDList *list)
 {
        CamelCipherCertInfo *info = (CamelCipherCertInfo *)list->head;
 
        printf(" %s\n signed by:\n", desc);
        while (info->next) {
                printf(" cn: %s <%s>\n", info->name, info->email);
                info = info->next;
        }
 }
 
 static const char *enc_desc[] = { "none", "weak", "encrypted", "strong" };
 static const char *sign_desc[] = { "none", "good", "bad", "unknown" };
 
 static void validate(CamelMimePart *part)
 {
        CamelMultipartSigned *mps;
        CamelCipherContext *cipher;
  
        mps = (CamelMultipartSigned *)camel_medium_get_content_object((CamelMedium *)part);
        if (!CAMEL_IS_MULTIPART_SIGNED(mps))
                return;
 
        if (mps->protocol) {
                if (g_ascii_strcasecmp("application/x-pkcs7-signature", mps->protocol) == 0
                    || g_ascii_strcasecmp("application/pkcs7-signature", mps->protocol) == 0)
                        cipher = camel_smime_context_new(emf->session);
                else
                        if (g_ascii_strcasecmp("application/pgp-signature", mps->protocol) == 0)
                                cipher = camel_gpg_context_new(emf->session);
        }
 
        if (cipher) {
                CamelException ex = { 0 };
                CamelCipherValidity *valid;
 
                valid = camel_cipher_verify(cipher, part, &ex);
                if (valid == NULL) {
                        printf(ex.desc?_("Error verifying signature: %s"):_("Unknown error verifying signature"), ex.desc);
                } else {
                        printf("Encrypted? %s\n", _(enc_desc[valid->encrypt.status]));
                        if (valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE)
                                dump_infos(valid->encrypt.description, &valid->encrypt.encrypters);
                        printf("Signed? %s\n", _(sign_desc[valid->sign.status]));
                        if (valid->sign.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE)
                                dump_infos(valid->sign.description, &valid->sign.signers);
                        camel_cipher_validity_free(valid);
                }
 
                camel_exception_clear(&ex);
                camel_object_unref(cipher);
        }
 }

Note: this example is untested

See evolution/mail/em-format.c:emf_multipart_signed for a more complete example, which also merges validation for multiple levels.

Note that the client code must still peek inside the part for various details; it should be possible to add a factory method to CamelCipherContext to perform these checks itself. It could even lead to a proper plugin interface.

Notes

Although this mostly works, there are a few problems with the APIs at present.

  • The key routines need to take raw parts, as the sign/verify/encrypt/decrypt ones do.
  • The CipherValidity stuff probably requires some virtual methods so that implementations can provide additional context information, particularly for the CamelCipherCertInfo structure.

** One problem with this is that the merge functions may get more complex.

  • The way hashes are used needs some work. Perhaps discovery mechanisms. Perhaps per-implementation hash codes rather than trying to abstract them.
  • The id_to_hash and hash_to_id methods should be dropped.

  • Various other options probably do not belong in the base class; not everything needs to be abstracted.
  • A helper function or initialisation option should be added to CamelSMIMEContext for handling password queries.
  • Should the recipients list for signing or encrypting just be a CamelInternetAddress?

RFCs

  • RFC-1847 The brain-damaged: Security Multiparts for MIME: Multipart/Signed

  • RFC-3156 MIME Security with OpenPGP (PGP-MIME)

  • RFC-2440 OpenPGP Message Format (inline pgp)

  • RFC-2015 MIME Security with Pretty Good Privacy (PGP)

  • RFC-2634 Enhanced Security Services for S/MIME

  • RFC-2633 S/MIME Version 3 Message Specification

  • RFC-2632 S/MIME Version 3 Certificate Handling

Apps/Evolution/Camel.CipherContext (last edited 2013-08-08 22:50:00 by WilliamJonMcCann)