]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
Implement OpenSSL PKCS#7 signed-data parsing and verification
authorMartin Willi <martin@revosec.ch>
Wed, 28 Nov 2012 17:45:30 +0000 (18:45 +0100)
committerMartin Willi <martin@revosec.ch>
Wed, 19 Dec 2012 09:32:08 +0000 (10:32 +0100)
src/libstrongswan/plugins/openssl/openssl_pkcs7.c

index b53e1c4a7347ee2e9cea129a47d9cb4576b64835..95bb93ec67b041d47b74899fa618daeeabc87c3d 100644 (file)
  */
 
 #include "openssl_pkcs7.h"
+#include "openssl_util.h"
 
 #include <library.h>
+#include <utils/debug.h>
+#include <asn1/oid.h>
+
+#include <openssl/cms.h>
 
 typedef struct private_openssl_pkcs7_t private_openssl_pkcs7_t;
 
@@ -28,20 +33,243 @@ struct private_openssl_pkcs7_t {
         * Public pkcs7_t interface.
         */
        pkcs7_t public;
+
+       /**
+        * Type of this container
+        */
+       container_type_t type;
+
+       /**
+        * OpenSSL CMS structure
+        */
+       CMS_ContentInfo *cms;
 };
 
-METHOD(container_t, get_type, container_type_t,
-       private_openssl_pkcs7_t *this)
+/**
+ * OpenSSL does not allow us to read the signature to verify it with our own
+ * crypto API. We define the internal CMS_SignerInfo structure here to get it.
+ */
+struct CMS_SignerInfo_st {
+       long version;
+       void *sid;
+       X509_ALGOR *digestAlgorithm;
+       STACK_OF(X509_ATTRIBUTE) *signedAttrs;
+       X509_ALGOR *signatureAlgorithm;
+       ASN1_OCTET_STRING *signature;
+};
+
+/**
+ * We can't include asn1.h, declare function prototype directly
+ */
+chunk_t asn1_wrap(int, const char *mode, ...);
+
+/**
+ * Enumerator for signatures
+ */
+typedef struct {
+       /** implements enumerator_t */
+       enumerator_t public;
+       /** Stack of signerinfos */
+       STACK_OF(CMS_SignerInfo) *signers;
+       /** current enumerator position in signers */
+       int i;
+       /** currently enumerating auth config */
+       auth_cfg_t *auth;
+       /** full CMS */
+       CMS_ContentInfo *cms;
+} signature_enumerator_t;
+
+/**
+ * Verify signerInfo signature
+ */
+static auth_cfg_t *verify_signature(CMS_SignerInfo *si, int hash_oid)
 {
-       return CONTAINER_PKCS7_DATA;
+       enumerator_t *enumerator;
+       public_key_t *key;
+       certificate_t *cert;
+       auth_cfg_t *auth, *found = NULL;
+       identification_t *issuer, *serial;
+       chunk_t attrs = chunk_empty, sig, attr;
+       X509_NAME *name;
+       ASN1_INTEGER *snr;
+       int i;
+
+       if (CMS_SignerInfo_get0_signer_id(si, NULL, &name, &snr) != 1)
+       {
+               return NULL;
+       }
+       issuer = openssl_x509_name2id(name);
+       if (!issuer)
+       {
+               return NULL;
+       }
+       serial = identification_create_from_encoding(
+                                                                       ID_KEY_ID, openssl_asn1_str2chunk(snr));
+
+       /* reconstruct DER encoded attributes to verify signature */
+       for (i = 0; i < CMS_signed_get_attr_count(si); i++)
+       {
+               attr = openssl_i2chunk(X509_ATTRIBUTE, CMS_signed_get_attr(si, i));
+               attrs = chunk_cat("mm", attrs, attr);
+       }
+       /* wrap in a ASN1_SET */
+       attrs = asn1_wrap(0x31, "m", attrs);
+
+       /* TODO: find a better way to access and verify the signature */
+       sig = openssl_asn1_str2chunk(si->signature);
+       enumerator = lib->credmgr->create_trusted_enumerator(lib->credmgr,
+                                                                                                               KEY_RSA, serial, FALSE);
+       while (enumerator->enumerate(enumerator, &cert, &auth))
+       {
+               if (issuer->equals(issuer, cert->get_issuer(cert)))
+               {
+                       key = cert->get_public_key(cert);
+                       if (key)
+                       {
+                               if (key->verify(key, signature_scheme_from_oid(hash_oid),
+                                                               attrs, sig))
+                               {
+                                       found = auth->clone(auth);
+                                       key->destroy(key);
+                                       break;
+                               }
+                               key->destroy(key);
+                       }
+               }
+       }
+       enumerator->destroy(enumerator);
+       issuer->destroy(issuer);
+       serial->destroy(serial);
+       free(attrs.ptr);
+
+       return found;
+}
+
+/**
+ * Verify the message digest in the signerInfo attributes
+ */
+static bool verify_digest(CMS_ContentInfo *cms, CMS_SignerInfo *si, int hash_oid)
+{
+       ASN1_OCTET_STRING *os, **osp;
+       hash_algorithm_t hash_alg;
+       chunk_t digest, content, hash;
+       hasher_t *hasher;
+
+       os = CMS_signed_get0_data_by_OBJ(si,
+                               OBJ_nid2obj(NID_pkcs9_messageDigest), -3, V_ASN1_OCTET_STRING);
+       if (!os)
+       {
+               return FALSE;
+       }
+       digest = openssl_asn1_str2chunk(os);
+       osp = CMS_get0_content(cms);
+       if (!osp)
+       {
+               return FALSE;
+       }
+       content = openssl_asn1_str2chunk(*osp);
+
+       hash_alg = hasher_algorithm_from_oid(hash_oid);
+       hasher = lib->crypto->create_hasher(lib->crypto, hash_alg);
+       if (!hasher)
+       {
+               DBG1(DBG_LIB, "hash algorithm %N not supported",
+                        hash_algorithm_names, hash_alg);
+               return FALSE;
+       }
+       if (!hasher->allocate_hash(hasher, content, &hash))
+       {
+               hasher->destroy(hasher);
+               return FALSE;
+       }
+       hasher->destroy(hasher);
+
+       if (!chunk_equals(digest, hash))
+       {
+               free(hash.ptr);
+               DBG1(DBG_LIB, "invalid messageDigest");
+               return FALSE;
+       }
+       free(hash.ptr);
+       return TRUE;
+}
+
+METHOD(enumerator_t, signature_enumerate, bool,
+       signature_enumerator_t *this, auth_cfg_t **out)
+{
+       if (!this->signers)
+       {
+               return FALSE;
+       }
+       while (this->i < sk_CMS_SignerInfo_num(this->signers))
+       {
+               CMS_SignerInfo *si;
+               X509_ALGOR *digest, *sig;
+               int hash_oid;
+
+               /* clean up previous round */
+               DESTROY_IF(this->auth);
+               this->auth = NULL;
+
+               si = sk_CMS_SignerInfo_value(this->signers, this->i++);
+
+               CMS_SignerInfo_get0_algs(si, NULL, NULL, &digest, &sig);
+               hash_oid = openssl_asn1_known_oid(digest->algorithm);
+               if (openssl_asn1_known_oid(sig->algorithm) != OID_RSA_ENCRYPTION)
+               {
+                       DBG1(DBG_LIB, "only RSA digest encryption supported");
+                       continue;
+               }
+               this->auth = verify_signature(si, hash_oid);
+               if (!this->auth)
+               {
+                       DBG1(DBG_LIB, "unable to verify pkcs7 attributes signature");
+                       continue;
+               }
+               if (!verify_digest(this->cms, si, hash_oid))
+               {
+                       continue;
+               }
+               *out = this->auth;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+METHOD(enumerator_t, signature_destroy, void,
+       signature_enumerator_t *this)
+{
+       DESTROY_IF(this->auth);
+       free(this);
 }
 
 METHOD(container_t, create_signature_enumerator, enumerator_t*,
        private_openssl_pkcs7_t *this)
 {
+       signature_enumerator_t *enumerator;
+
+       if (this->type == CONTAINER_PKCS7_SIGNED_DATA)
+       {
+               INIT(enumerator,
+                       .public = {
+                               .enumerate = (void*)_signature_enumerate,
+                               .destroy = _signature_destroy,
+                       },
+                       .cms = this->cms,
+                       .signers = CMS_get0_SignerInfos(this->cms),
+               );
+               return &enumerator->public;
+       }
        return enumerator_create_empty();
 }
 
+
+METHOD(container_t, get_type, container_type_t,
+       private_openssl_pkcs7_t *this)
+{
+       return this->type;
+}
+
 METHOD(pkcs7_t, get_attribute, bool,
        private_openssl_pkcs7_t *this, int oid,
        enumerator_t *enumerator, chunk_t *value)
@@ -58,6 +286,24 @@ METHOD(pkcs7_t, create_cert_enumerator, enumerator_t*,
 METHOD(container_t, get_data, bool,
        private_openssl_pkcs7_t *this, chunk_t *data)
 {
+       ASN1_OCTET_STRING **os;
+
+       switch (this->type)
+       {
+               case CONTAINER_PKCS7_DATA:
+               case CONTAINER_PKCS7_SIGNED_DATA:
+                       os = CMS_get0_content(this->cms);
+                       if (os)
+                       {
+                               *data = chunk_clone(openssl_asn1_str2chunk(*os));
+                               return TRUE;
+                       }
+                       break;
+               case CONTAINER_PKCS7_ENVELOPED_DATA:
+                       /* TODO: decrypt */
+               default:
+                       break;
+       }
        return FALSE;
 }
 
@@ -70,6 +316,7 @@ METHOD(container_t, get_encoding, bool,
 METHOD(container_t, destroy, void,
        private_openssl_pkcs7_t *this)
 {
+       CMS_ContentInfo_free(this->cms);
        free(this);
 }
 
@@ -97,6 +344,39 @@ static private_openssl_pkcs7_t* create_empty()
        return this;
 }
 
+/**
+ * Parse a PKCS#7 container
+ */
+static bool parse(private_openssl_pkcs7_t *this, chunk_t blob)
+{
+       BIO *bio;
+
+       bio = BIO_new_mem_buf(blob.ptr, blob.len);
+       this->cms = d2i_CMS_bio(bio, NULL);
+       BIO_free(bio);
+
+       if (!this->cms)
+       {
+               return FALSE;
+       }
+       switch (openssl_asn1_known_oid((ASN1_OBJECT*)CMS_get0_type(this->cms)))
+       {
+               case OID_PKCS7_DATA:
+                       this->type = CONTAINER_PKCS7_DATA;
+                       break;
+               case OID_PKCS7_SIGNED_DATA:
+                       this->type = CONTAINER_PKCS7_SIGNED_DATA;
+                       break;
+               case OID_PKCS7_ENVELOPED_DATA:
+                       this->type = CONTAINER_PKCS7_ENVELOPED_DATA;
+                       break;
+               default:
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
 /**
  * See header
  */
@@ -122,7 +402,10 @@ pkcs7_t *openssl_pkcs7_load(container_type_t type, va_list args)
        if (blob.len)
        {
                this = create_empty();
-               /* TODO: parse blob */
+               if (parse(this, blob))
+               {
+                       return &this->public;
+               }
                destroy(this);
        }
        return NULL;