3

I need to be able to generate, save and read CMS / PKCS#7 data in Python. It seems that it can be done using asn1crypto library, but I am having a hard time finding functions that would allow me to save the data to the disk (in PEM/DER format). There is a testbench at asn1crypto/tests/test_cms.py, but it only shows how to read CMS / PKCS#7 data from the files and store it into corresponding asn1crypto.cms objects. I was not able to find a manual or even a list of asn1crypto.cms functions (methods).

For now, I am able to generate all the necessary pieces such as a signature, encrypted data, symmetric key, etc, so what I need to do is to find a way to fuse them together into CMS / PKCS#7 compatible file formats. Basically, I am looking for an equivalent Python flow for the shell's openssl cms and openssl engine capabilities. A simple Python example showing how to create and save a CMS object (e.g., SignedData, EnvelopedData, etc) would go a long way.

Proto Ukr
  • 492
  • 4
  • 13
  • 1
    Saving to disk is the last step; for smaller messages at least it makes more sense to create them in memory. For larger swaths of data, you might try and stream directly to disk. Note that programming languages such as Python generally operate on high level, and may not offer such low level constructions in all API calls. – Maarten Bodewes Nov 02 '19 at 19:46
  • @Maarten, I did a thorough review of cms.py that is part of `asn1crypto` package, and my understanding is that I can generate pieces and bits (certificate, secret key, signature, etc) with `pkcs11` and `OpenSSL` and then assign them to the corresponding fields of an appropriate object (e.g., SignedData). There might be some methods inherited from asn1 class, but it's hard to track them. Would you say it's the right way to go? – Proto Ukr Nov 03 '19 at 00:44
  • Yeah, look into SignedData and SignedInfo classes, possibly there is also a singer object (SignatureGenerator or something similar). Unfortunately I'm not familiar with the Python API's. – Maarten Bodewes Nov 03 '19 at 02:31

1 Answers1

13

So, after many days of playing around with asn1crypto and pkcs11 packages, I was able to create a signed data file. For signing I used the PIV signing slot in my Yubikey 5. Below is an excerpt from my script showing the essence of it (pardon the large code):

from asn1crypto import cms, util, algos, x509, core, pem
import pkcs11
from pkcs11 import Attribute, ObjectClass, KeyType

data = b'Just a test'

# Creating a SignedData object from cms
sd = cms.SignedData()

# Populating some of its fields
sd['version']='v1'
sd['encap_content_info']=util.OrderedDict([
        ('content_type', 'data'),
        ('content', data)])
sd['digest_algorithms']=[ util.OrderedDict([
        ('algorithm', 'sha256'),
        ('parameters', None) ])

# Initiating my Yubikey smart card
lib = pkcs11.lib('.../onepin-opensc-pkcs11.so')
token = lib.get_token(token_label='PIV Card Holder pin (PIV_II)')
session = token.open(user_pin='123456')

# Getting the private key and certificate objects using pkcs11
privateKey = next(session.get_objects({
        Attribute.CLASS: ObjectClass.PRIVATE_KEY,
        Attribute.LABEL: "SIGN key" })
certObj = next(session.get_objects({
        Attribute.CLASS: ObjectClass.CERTIFICATE,
        Attribute.LABEL: 'Certificate for Digital Signature' })

# Getting the raw value (DER) of certificate and storing it in x509
cert = x509.Certificate.load(certObj[Attribute.VALUE])

# Adding this certificate to SignedData object
sd['certificates'] = [cert]

# Setting signer info section
signer_info = cms.SignerInfo()
signer_info['version']=cms_version
signer_info['digest_algorithm']=util.OrderedDict([
                ('algorithm', 'sha256'),
                ('parameters', None) ])
signer_info['signature_algorithm']=util.OrderedDict([
                ('algorithm', 'sha256_rsa'),
                ('parameters', None) ])

# Creating a signature using a private key object from pkcs11
signer_info['signature'] = privateKey.sign(
        data, 
        mechanism=pkcs11.mechanisms.Mechanism.SHA256_RSA_PKCS )

# Finding subject_key_identifier from certificate (asn1crypto.x509 object)
key_id = cert.key_identifier_value.native
signer_info['sid'] = cms.SignerIdentifier({
        'subject_key_identifier': key_id })

# Adding SignerInfo object to SignedData object
sd['signer_infos'] = [ signer_info ]

# Writing everything into ASN.1 object
asn1obj = cms.ContentInfo()
asn1obj['content_type'] = 'signed_data'
asn1obj['content'] = sd

# This asn1obj can be dumped to a disk using dump() method (DER format)
with open('signed_data.der','wb+') as fout:
    fout.write(asn1obj.dump())

I then verified the signature using openssl cms -verify -in signed_data.der -inform DER -CAfile rootCertificate.pem and it worked!

MarSoft
  • 3,555
  • 1
  • 33
  • 38
Proto Ukr
  • 492
  • 4
  • 13