CMS(PKCS#7)RecipientInfo

时间:2014-01-22 18:26:24

标签: c encryption openssl pkcs#7 smime

我实际上正在处理一个应该从PKCS7 mime加密消息中提取RecipientInfo的函数。我想这样做的原因是,我希望获取邮件加密的所有邮件地址(或至少是keyids /指纹)。

嗯 - 我尝试了一些东西并创造了这样的东西(indata是* .p7m附件内容,indata_len是indata的strlen):

char *indata;
int indata_len, i;
PKCS7 *p7 = NULL;
BIO *bcont = NULL;
CMS_ContentInfo *cms = NULL;
STACK_OF(CMS_RecipientInfo) *recipients = NULL;
CMS_RecipientInfo *recip = NULL;
BIO *encMessage = BIO_new(BIO_s_mem());
if (encMessage == NULL) {
    goto clean_exit;
}

if(!BIO_write(encMessage, indata, indata_len)) {
    goto clean_exit;
}

cms = SMIME_read_CMS(encMessage,NULL);
if (cms == NULL ) {
    goto clean_exit;
}

recipients = CMS_get0_RecipientInfos(cms);
if (recipients == NULL) {
    goto clean_exit;
}

for (i=0; i< sk_CMS_RecipientInfo_num(recipients); i++) {
    recip = sk_CMS_RecipientInfo_value(recipients, i);
    if( recip == NULL || CMS_RecipientInfo_type(recip) != CMS_RECIPINFO_TRANS ) {
        continue;
    }

    int r;
    ASN1_OCTET_STRING **keyid;
    X509_NAME **issuer;
    ASN1_INTEGER **sno;

    r = CMS_RecipientInfo_ktri_get0_signer_id(recip, keyid, issuer, sno);
    if (!r) {
        continue;
    }

    printf("Key: %s\n", keyid);
}

我没有得到错误(用ERR_get_error()检查)但是keyid,issuer和sno保持“null”,上面代码的输出是:

  

键:( null)

所以我的问题是,是否有可能获得加密信息的信息,或者我的推理中只有错误?

如果有可能获得该数据,有人可以给我一个提示吗? 如果不可能,最新的(最好的)方法是检查用于解密的私钥。由于单个用户可以有多个S / Mime证书/密钥。例如。创建新密钥,因为旧密钥丢失或只是从提供商获得新的证书/密钥组合,... Imho,如果消息非常大,循环遍历所有密钥可能需要一些时间。

祝你好运, 最大

2 个答案:

答案 0 :(得分:0)

那么,如果有多个收件人密钥必须用于解密收到的邮件,那么没有人知道最好的方法吗?

答案 1 :(得分:0)

我不知道如何解决您的代码,但是我有几个openssl命令和一个Python脚本可以解决您的任务:

您可以运行以下命令以获取所有序列号的列表 加密文件MYMAIL中的收件人密钥:

openssl smime -pk7out -inform DER -in MYMAIL \
    | openssl pkcs7 -noout -print \
    | grep serial

这将把序列号打印为所有接收者的十进制数字,即已为其文件MYMAIL加密的证书的序列号。对于给定的证书文件CERTFILE.0,命令

openssl x509 -in CERTFILE.0 -serial -noout

将其序列号打印为十六进制数。现在,您可以将您拥有的证书的序列号与MYMAIL中提到的序列号结合起来。

我已经编写了一个python脚本来执行此操作,并且可以用来替换mutt中的默认smime_decrypt_command,以便在解密电子邮件时,选择正确的私钥进行解密:{{ 3}} 对于网址中断的情况,我在下面粘贴了整个脚本。

#!/usr/bin/env python3
"""
Given an smime encrypted file and some smime certificates,
tell for which of the smime certificates, the encrypted file has been
encrypted for.
"""

import argparse
import os
import re
import subprocess
import sys
import textwrap

class Openssl:
    def __init__(self, openssl_command):
        self.openssl_command = openssl_command

    def get_certificate_serial_number(self, certificate_file):
        """Given a certificate_file filepath, return its serial number as an int"""
        command = [self.openssl_command, 'x509', '-in', certificate_file, '-serial', '-noout']
        proc = subprocess.run(command, stdout=subprocess.PIPE)
        # output should be of the form 'serial=HEXADECIMALNUMBER'
        try:
            return int(proc.stdout.decode().replace('serial=', ''), 16)
        except ValueError:
             print("Can not read file: {}".format(certificate_file), file=sys.stderr)

    def smime_pk7out(self, encrypted_file):
        """run smime -pk7out, return its output"""
        command = [self.openssl_command, 'smime', '-pk7out']
        command += ['-inform', 'DER', '-in', encrypted_file]
        proc = subprocess.run(command, stdout=subprocess.PIPE)
        return proc.stdout.decode()

    def pkcs7_serial_numbers(self, pk7buf):
        """extract all serial numbers via openssl pkcs7 -noout -print"""
        command = [self.openssl_command, 'pkcs7', '-noout', '-print']
        proc = subprocess.run(command, stdout=subprocess.PIPE, text=True, input=pk7buf)
        for match in re.finditer('serial: ([0-9]+)', proc.stdout):
            yield int(match.group(1))

    def list_recipient_serial_numbers(self, encrypted_file):
        """Do essentially:
            openssl smime -pk7out -inform DER -in MYMAIL \
                | openssl pkcs7 -noout -print \
                | grep serial
        """
        pk7out = self.smime_pk7out(encrypted_file)
        return list(self.pkcs7_serial_numbers(pk7out))

    def smime_decrypt(self, private_key, certificate, filepath, passin='stdin'):
        """encrypt the given filepath and print to stdout"""
        command = [self.openssl_command, 'smime', '-decrypt', '-passin', passin]
        command += ['-inform', 'DER', '-in', filepath]
        command += ['-inkey', private_key]
        command += ['-recip', certificate]
        subprocess.run(command)

def main():
    """main"""
    description = "Detect recipients of smime encrypted files"
    epilog = textwrap.dedent(r"""
    E.g. you can decrypt an email with the command that picks the
    private key automatically:

        {} \
            --passin stdin --decrypt \
            --private-key ~/.smime/keys/* \
            -- mymail ~/.smime/certificates/*

    If you use mutt, you can set

    set smime_decrypt_command="\
        ~/path/to/smime-recipient-list.py --passin stdin --decrypt \
        --private-key ~/.smime/keys/* \
        -- %f ~/.smime/certificates/KEYPREFIX.*"

    where KEYPREFIX is the prefix of your key (i.e. without the .0 or .1 suffix).
    """.format(sys.argv[0]))
    parser = argparse.ArgumentParser(
        description=description,
        epilog=epilog,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('encryptedfile', help='the encrypted file')
    parser.add_argument('certificates',
                        nargs='+',
                        help='the smime certificate files')
    parser.add_argument('--openssl', default='openssl', help='openssl command name')
    parser.add_argument('--list-serials', action='store_true',
                        help='list serial numbers of certifacts')
    parser.add_argument('--print-path', action='store_true',
                        help='print path of recipient certificates')
    parser.add_argument('--private-keys', nargs='*', default=[], help='private keys for decrypt')
    parser.add_argument('--decrypt', action='store_true',
                        help='decrypt using one of the private keys passed.\
                              the key must have the same file name as the certificate.')
    parser.add_argument('--passin', default='stdin',
                        help='default openssl -passin parameter for decrypt')
    args = parser.parse_args()
    openssl = Openssl(args.openssl)

    # get the serial number of every smime-certfile:
    serialnum2cert = {}
    for i in args.certificates:
        serialnum2cert[openssl.get_certificate_serial_number(i)] = i
    if args.list_serials:
        for serialnum, keyfile in serialnum2cert.items():
            print("{} --> {}".format(keyfile, serialnum))
    recipients = openssl.list_recipient_serial_numbers(args.encryptedfile)
    if args.print_path or args.decrypt:
        matching_keys = []
        for i in recipients:
            if i in serialnum2cert:
                matching_keys.append(serialnum2cert[i])
    if args.print_path:
        for i in matching_keys:
            print(i)
    if args.decrypt:
        private_keys = {}
        for filepath in args.private_keys:
            private_keys[os.path.basename(filepath)] = filepath
        key_found = None
        for fp in matching_keys:
            if os.path.basename(fp) in private_keys:
                priv_key_path = private_keys[os.path.basename(fp)]
                # print("We can use {} and {}".format(priv_key_path, fp))
                key_found = (priv_key_path, fp)
        if key_found is None:
            print("No matching private key found.", file=sys.stderr)
            sys.exit(1)
        openssl.smime_decrypt(key_found[0], key_found[1],
                              args.encryptedfile, passin=args.passin)

if __name__ == "__main__":
    main()