3

I am actually working on a function which should extract RecipientInfo from PKCS7 mime encrypted message. The reason why I want to do this is, that I want to get all mail addresses (or at least the keyids/fingerprints) the message is encrypted for.

Well - I tried something out and created something like this (indata is a *.p7m attachment content, indata_len the strlen of indata):

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);
}

I get no error (checked with ERR_get_error()) but keyid, issuer and sno stay "null", output of above code is:

Key: (null)

So my question is, is it even possible to get that information of an encrypted message or is there just an error in reasoning on my side?

If it is possible to get that data, can someone give me a hint? If it is not possible, whats the default (best) way to check which private key to use for decryption. Since there can be more than one S/Mime certificate/key for a single user. E.g. creating new key since the old one is lost or just get a new cert/key combination from provider, ... Imho, looping through all keys could take some time if the message is really big.

Best regards, Max

4

1 回答 1

2

我不知道如何修复你的代码,但我有几个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 对于网址中断的情况,我将整个脚本粘贴在下面。

#!/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()
于 2019-04-24T14:16:34.833 回答