【问题标题】:CMS (PKCS#7) RecipientInfoCMS (PKCS#7) 收件人信息
【发布时间】:2014-02-12 23:39:40
【问题描述】:

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

嗯 - 我尝试了一些东西并创建了这样的东西(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”,上面代码的输出是:

键:(空)

所以我的问题是,是否有可能获得加密消息的信息,还是我这边的推理有误?

如果有可能获得该数据,有人可以给我一个提示吗? 如果不可能,检查使用哪个私钥进行解密的默认(最佳)方法是什么。因为单个用户可以有多个 S/Mime 证书/密钥。例如。创建新密钥,因为旧密钥丢失或只是从提供商那里获得新的证书/密钥组合,... 恕我直言,如果消息真的很大,遍历所有键可能需要一些时间。

最好的问候, 最大

【问题讨论】:

    标签: c encryption openssl pkcs#7 smime


    【解决方案1】:

    我不知道如何修复您的代码,但我有几个 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,这样当解密电子邮件时,选择正确的私钥进行解密:https://github.com/t-wissmann/dotfiles/blob/master/utils/smime-recipient-list.py 对于 url 中断的情况,我将整个脚本粘贴在下面。

    #!/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()
    

    【讨论】:

      猜你喜欢
      • 2021-03-28
      • 2013-07-09
      • 2022-08-03
      • 1970-01-01
      • 2020-03-19
      • 2018-05-03
      • 1970-01-01
      • 2020-02-28
      • 1970-01-01
      相关资源
      最近更新 更多