I am new to xml signatures and currently I am using xmlsec to generate a signed xml. I do this with some modifications on the sample code:
from lxml import etree
import xmlsec
parser = etree.XMLParser(remove_blank_text=True)
template = etree.parse('unsigned.xml', parser).getroot()
signature_node = xmlsec.tree.find_node(template, xmlsec.constants.NodeSignature)
ctx = xmlsec.SignatureContext()
key = xmlsec.Key.from_file('keys/private_key.pem', xmlsec.constants.KeyDataFormatPem)
ctx.key = key
sig_ = ctx.sign(signature_node)
formated = etree.tostring(template)
with open('signed_test.xml', 'wb') as the_file:
the_file.write(formated)
Now I have signed XML, and from here I am trying to learn where or how the values get generated. I am following this for context. I am able to verify the DigestValue and now I am trying to get the SignatureValue. From the link and some other questions here in stackoverflow, I just need to:
Canonized the whole SignedInfo element
Hash the result
Sign the Hash
In order to get the signature value. I am canonizing the SignedInfo Element using lxml:
from lxml import etree
parser = etree.XMLParser(remove_blank_text=True)
xmlTree = etree.parse('signed_info.xml', parser)
root = xmlTree.getroot()
formated = etree.tostring(root, method='c14n', exclusive=True)
# Write to file
with open('canon_sinfo.xml', 'wb') as the_file:
the_file.write(formated)
For info the following is the resulting SignedInfo:
<SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod><Reference URI="#obj"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#base64"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod><DigestValue>izbIdQ4tSAg6VKGpr1zd6kU9QpVQi/Bcwxjxu/k2oKk=</DigestValue></Reference></SignedInfo>
I am using cryptography to try and generate the SignatureValue however, I cannot get the same result as that of what xmlsec generated.
Here is my code snippet in doing so:
sign_info = '''String of the Sign Info'''
digest_sinfo = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest_sinfo.update(bytes(sign_info, 'utf-8'))
digested_sinfo = digest_sinfo.finalize()
# Load private_key here...
# Sign the message
signature_1v15 = private_key.sign(
digested_sinfo,
padding.PKCS1v15(),
hashes.SHA256()
)
I also tried loading the SignedInfo into a seperate file, however I still get a mismatched SignatureValue. How do I accomplish this?
Your issue is that you do the hashing twice. The sign() function does the hashing for you so you can skip the middle part.
Just call the sign() with your canonized SignedInfo element:
signature_1v15 = private_key.sign(
sign_info,
padding.PKCS1v15(),
hashes.SHA256()
)
Related
I would need to sign a SAML assertion as it's possible to do on this page :
https://www.samltool.com/sign_response.php
From my side, I'm using Python, and my SAML is in a string.
What would you advise ?
Best regards,
Nico.
I finally made it using signxml lib.
Just to remind, I wanted to sign the SAML assertion.
I get the unsigned SAML from a file. Then I place this tag <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder"></ds:Signature> between Issuer tag and Subject tag, as it's recommended by signxml lib. And finally, I sign my SAML and verify the signature. Note that I changed the c14n_algorithm to be compatible with my services.
Here's the code :
import re
from lxml import etree
from signxml import XMLSigner, XMLVerifier
with open('saml_to_sign.xml', 'r') as file :
data_to_sign = file.read()
with open("/vagrant/my_cert.crt", "r") as cert,\
open("/vagrant/my_key.key", "r") as key:
certificate = cert.read()
private_key = key.read()
p = re.search('<Subject>', data_to_sign).start()
tmp_message = data_to_sign[:p]
tmp_message = tmp_message +\
'<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="placeholder"></ds:Signature>'
data_to_sign = tmp_message + data_to_sign[p:]
print(data_to_sign)
saml_root = etree.fromstring(data_to_sign)
signed_saml_root = XMLSigner(c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#")\
.sign(saml_root, key=private_key, cert=certificate)
verified_data = XMLVerifier().verify(signed_saml_root, x509_cert=certificate).signed_xml
signed_saml_root_str = etree.tostring(signed_saml_root, encoding='unicode')
print(signed_saml_root_str)
I can generate a valid CMS EnvelopedData value with AES-CBC using the Kotlin code below:
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.cms.CMSAlgorithm
import org.bouncycastle.cms.CMSEnvelopedDataGenerator
import org.bouncycastle.cms.CMSProcessableByteArray
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator
fun encrypt(plaintext: ByteArray, recipientCertificate: X509CertificateHolder): ByteArray {
val cmsEnvelopedDataGenerator = CMSEnvelopedDataGenerator()
val x509Certificate = JcaX509CertificateConverter()
.getCertificate(recipientCertificate)
val transKeyGen =
JceKeyTransRecipientInfoGenerator(x509Certificate)
cmsEnvelopedDataGenerator.addRecipientInfoGenerator(transKeyGen)
val msg = CMSProcessableByteArray(plaintext)
val encryptor = JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).build()
val bcEnvelopedData = cmsEnvelopedDataGenerator.generate(msg, encryptor)
return bcEnvelopedData.encoded
}
But if I replace CMSAlgorithm.AES128_CBC with CMSAlgorithm.AES128_GCM, JceCMSContentEncryptorBuilder.build() throws the following error:
cannot create key generator: 2.16.840.1.101.3.4.1.6 KeyGenerator not available
That error seems to suggest AES-GCM-128 isn't supported, but the fact the object CMSAlgorithm.AES128_GCM exists suggests to me that can't be it -- I must be doing something wrong. Maybe the IV isn't generated behind the scenes for me and I have to set it explicitly somehow?
I'm using org.bouncycastle:bcpkix-jdk15on:1.64.
UPDATE: I've found a test in the BouncyCastle test suite that uses AES-GCM in EnvelopedData values, and they're also passing CMSAlgorithm.AES128_GCM to JceCMSContentEncryptorBuilder in the same way. The only difference is that they're calling .setProvider("BC") before .build(), but I've just tried that and it didn't make any difference.
Answering my question:
JceCMSContentEncryptorBuilder.setProvider() must be certainly called before .build(), but if the provider isn't registered, you have to pass an instance of org.bouncycastle.jce.provider.BouncyCastleProvider.
I have a hex encoded string which I need to decrypt using RSA_OAEP algorithm.Below is my code snippet:
final_token = decoded_req['token']
print(final_token)
print("Converting actual token into bytes")
## Hex encoded string to bytes
token_to_bytes = bytes.fromhex(final_token)
print(token_to_bytes)
## Read the private key
with open('private.rsa', 'r') as pvt_key:
miPvt = pvt_key.read()
## Decrypt the token using RSA_OAEP
print("Decrypting the token")
rsakey_obj = RSA.importKey(miPvt)
cipher = PKCS1_OAEP.new(rsakey_obj)
dec_token = cipher.decrypt(token_to_bytes)
print(dec_token)
Below is the command line output:
6c79855ca15a3ac0308d17db97a1c576b6f35e4bb630da22867d0d081d55f05e1b1c640d7e7bd8c8de06b6a03e2ef7449e31fa9c0f0c675ebbf838bb880a4c6e309391441e1c41a0c42fbe11eaf6306bb39e7bbbffbe2dbced8c9bb07679ff18cef348b2b7ce08aa05028fda818ac4ce08ad84246c94afbcac405db5258c5333f66148007be8fa5384fa78a0d54ccf26028571180723b2a61fe18ebd0124f7e008902e79b48a53d29953ffde949878b4eb99d376ccf68341b0ec2ceb03b050e7c260f8905dccddb5e50cb331a3bb6bc034fc06dfaf722fd8fad8cb7a311b020be8b1a9115279173b18f46ccc0e28c71f0a6f67e8362a103fa4729057d8924ef0a6fb5a33eeb5b42cdc764fde53b9d6338472fc73e80cf785eb54ffb4ac15a6bf63db19baca586508b8160bf73dbad322221ddb632f5ae8040ef86805a408fc7df6f4a8ee5c7a444a5a13dbdcb8b958c52a8bdc4394e5deaabce203a3ff8675079e6618ac3558cdaf5ce6da77ca667bd5349ba82cc1ad3f71a662a2b7c8b47cc548209264a45a65b3f6f25fedcad8449699a19e2910d24c13b44e3e9445a62f88213346e00048480218996ade7e01151b7e038fbc8a5ff03bfa511126763d4571dec6ce0f5183302a99eee62facabe09211f3a61b8d154a38f0ee9a2647998e2ec1b8ee96b52c443f408cec24140838616c79d82cba4b77171b621f261539e239
Converting actual token into bytes
b'ly\x85\\\xa1Z:\xc00\x8d\x17\xdb\x97\xa1\xc5v\xb6\xf3^K\xb60\xda"\x86}\r\x08\x1dU\xf0^\x1b\x1cd\r~{\xd8\xc8\xde\x06\xb6\xa0>.\xf7D\x9e1\xfa\x9c\x0f\x0cg^\xbb\xf88\xbb\x88\nLn0\x93\x91D\x1e\x1cA\xa0\xc4/\xbe\x11\xea\xf60k\xb3\x9e{\xbb\xff\xbe-\xbc\xed\x8c\x9b\xb0vy\xff\x18\xce\xf3H\xb2\xb7\xce\x08\xaa\x05\x02\x8f\xda\x81\x8a\xc4\xce\x08\xad\x84$l\x94\xaf\xbc\xac#]\xb5%\x8cS3\xf6aH\x00{\xe8\xfaS\x84\xfax\xa0\xd5L\xcf&\x02\x85q\x18\x07#\xb2\xa6\x1f\xe1\x8e\xbd\x01$\xf7\xe0\x08\x90.y\xb4\x8aS\xd2\x99S\xff\xde\x94\x98x\xb4\xeb\x99\xd3v\xcc\xf6\x83A\xb0\xec,\xeb\x03\xb0P\xe7\xc2`\xf8\x90]\xcc\xdd\xb5\xe5\x0c\xb31\xa3\xbbk\xc04\xfc\x06\xdf\xafr/\xd8\xfa\xd8\xcbz1\x1b\x02\x0b\xe8\xb1\xa9\x11Ry\x17;\x18\xf4l\xcc\x0e(\xc7\x1f\nog\xe86*\x10?\xa4r\x90W\xd8\x92N\xf0\xa6\xfbZ3\xee\xb5\xb4,\xdcvO\xdeS\xb9\xd63\x84r\xfcs\xe8\x0c\xf7\x85\xebT\xff\xb4\xac\x15\xa6\xbfc\xdb\x19\xba\xcaXe\x08\xb8\x16\x0b\xf7=\xba\xd3""\x1d\xdbc/Z\xe8\x04\x0e\xf8h\x05\xa4\x08\xfc}\xf6\xf4\xa8\xee\\zDJZ\x13\xdb\xdc\xb8\xb9X\xc5*\x8b\xdcC\x94\xe5\xde\xaa\xbc\xe2\x03\xa3\xff\x86u\x07\x9ef\x18\xac5X\xcd\xaf\\\xe6\xdaw\xcaf{\xd54\x9b\xa8,\xc1\xad?q\xa6b\xa2\xb7\xc8\xb4|\xc5H \x92d\xa4Ze\xb3\xf6\xf2_\xed\xca\xd8D\x96\x99\xa1\x9e)\x10\xd2L\x13\xb4N>\x94E\xa6/\x88!3F\xe0\x00HH\x02\x18\x99j\xde~\x01\x15\x1b~\x03\x8f\xbc\x8a_\xf0;\xfaQ\x11&v=Eq\xde\xc6\xce\x0fQ\x830*\x99\xee\xe6/\xac\xab\xe0\x92\x11\xf3\xa6\x1b\x8d\x15J8\xf0\xee\x9a&G\x99\x8e.\xc1\xb8\xee\x96\xb5,D?#\x8c\xec$\x14\x088aly\xd8,\xbaKw\x17\x1bb\x1f&\x159\xe29'
Decrypting the token
Traceback (most recent call last):
File "script.py", line 80, in <module>
dec_token = cipher.decrypt(token_to_bytes)
File "/home/venv/lib/python3.6/site-packages/Crypto/Cipher/PKCS1_OAEP.py", line 201, in decrypt
raise ValueError("Incorrect decryption.")
ValueError: Incorrect decryption.
Can someone help me in resolving this issue?
I suspect you have not correctly transferred your encrypted token. But there isn't a lot to go on.
I've used you example code though to help me write the below which does seem to work, (thank you for that) or at least it decrypted a value from another implementation.
In my case it was a node JS implementation
var nodeRsa = require("node-rsa");
const key = new nodeRsa( "-----BEGIN PUBLIC KEY-----\n\
.
.
.
-----END PUBLIC KEY-----");
key.encrypt("passwd","base64");
If I took the output of the above and put in a file called 'ciphertext', this following python decrypts the message correctly.
import Crypto.Cipher.PKCS1_OAEP as rsaenc
import Crypto.PublicKey.RSA as RSA
import codecs
## Load the private key and the base 64 ciphertext.
with open("key.pem") as f:
keystring = f.read()
with open("ciphertext","rb") as f:
base64msg = f.read()
# Create the binary ciphertext
binmsg=codecs.decode(base64msg,'base64')
# Setup the RSA objetcs
k = RSA.importKey(keystring)
cipher = rsaenc.new(k)
plaintext = cipher.decrypt(binmsg)
print (plaintext)
I'm using python 3 to write a script that generates a customer report for Solarwinds N-Central. The script uses SOAP to query N-Central and I'm using zeep for this project. While not new to python I am new to SOAP.
When calling the CustomerList fuction I'm getting the TypeError: __init__() got an unexpected keyword argument 'listSOs'
import zeep
wsdl = 'http://' + <server url> + '/dms/services/ServerEI?wsdl'
client = zeep.CachingClient(wsdl=wsdl)
config = {'listSOs': 'true'}
customers = client.service.CustomerList(Username=nc_user, Password=nc_pass, Settings=config)
Per the perameters below 'listSOs' is not only a valid keyword, its the only one accepted.
CustomerList
public com.nable.nobj.ei.Customer[] CustomerList(String username, String password, com.nable.nobj.ei.T_KeyPair[] settings) throws RemoteException
Parameters:
username - MSP N-central username
password - Corresponding MSP N-central password
settings - A list of non default settings stored in a T_KeyPair[]. Below is a list of the acceptable Keys and Values. If not used leave null
(Key) listSOs - (Value) "true" or "false". If true only SOs with be shown, if false only customers and sites will be shown. Default value is false.
I've also tried passing the dictionary as part of a list:
config = []
key = {'listSOs': 'true'}
config += key
TypeError: Any element received object of type 'str', expected lxml.etree._Element or builtins.dict or zeep.objects.T_KeyPair
Omitting the Settings value entirely:
customers = client.service.CustomerList(Username=nc_user, Password=nc_pass)
zeep.exceptions.ValidationError: Missing element Settings (CustomerList.Settings)
And trying zeep's SkipValue:
customers = client.service.CustomerList(Username=nc_user, Password=nc_pass, Settings=zeep.xsd.SkipValue)
zeep.exceptions.Fault: java.lang.NullPointerException
I'm probably missing something simple but I've been banging my head against the wall off and on this for awhile I'm hoping someone can point me in the right direction.
Here's my source code from my getAssets.py script. I did it in Python2.7, easily upgradeable though. Hope it helps someone else, N-central's API documentation is really bad lol.
#pip2.7 install zeep
import zeep, sys, csv, copy
from zeep import helpers
api_username = 'your_ncentral_api_user'
api_password='your_ncentral_api_user_pw'
wsdl = 'https://(yourdomain|tenant)/dms2/services2/ServerEI2?wsdl'
client = zeep.CachingClient(wsdl=wsdl)
response = client.service.deviceList(
username=api_username,
password=api_password,
settings=
{
'key': 'customerId',
'value': 1
}
)
# If you can't tell yet, I code sloppy
devices_list = []
device_dict = {}
dev_inc = 0
max_dict_keys = 0
final_keys = []
for device in response:
# Iterate through all device nodes
for device_properties in device.items:
# Iterate through each device's properties and add it to a dict (keyed array)
device_dict[device_properties.first]=device_properties.second
# Dig further into device properties
device_properties = client.service.devicePropertyList(
username=api_username,
password=api_password,
deviceIDs=device_dict['device.deviceid'],
reverseOrder=False
)
prop_ind = 0 # This is a hacky thing I did to make my CSV writing work
for device_node in device_properties:
for prop_tree in device_node.properties:
for key, value in helpers.serialize_object(prop_tree).items():
prop_ind+=1
device_dict["prop" + str(prop_ind) + "_" + str(key)]=str(value)
# Append the dict to a list (array), giving us a multi dimensional array, you need to do deep copy, as .copy will act like a pointer
devices_list.append(copy.deepcopy(device_dict))
# check to see the amount of keys in the last item
if len(devices_list[-1].keys()) > max_dict_keys:
max_dict_keys = len(devices_list[-1].keys())
final_keys = devices_list[-1].keys()
print "Gathered all the datas of N-central devices count: ",len(devices_list)
# Write the data out to a CSV
with open('output.csv', 'w') as csvfile:
fieldnames = final_keys
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for csv_line in devices_list:
writer.writerow(csv_line)
I have problems loading a pkcs#7 file and ask your help to figure out what I'm doing wrong.
I run M2Crypto-0.21.1 with OpenSSL 0.9.8g (as present in Ubuntu 9.4) and built with SWIG 1.3.36 and python 2.6.2.
"python setup.py test --test-suite=tests.test_smime" runs 15 tests with exit status "OK"; so the installation seems to be ok.
I created a pkcs#7 file in PEM format with a digital signature program and tested it with OpenSSL from the command line:
openssl smime -verify -inform PEM -in mandato-PEM.p7m -noverify
which prints the content contained in the envelope (a text file that I signed) and "Verification successful". So OpenSSL (same version as used by M2Crypto) seems to like my file.
However, when I try the same in M2Crypto, it chocks right in the beginning on:
p7, data = SMIME.smime_load_pkcs7('mandato-PEM.p7m')
I get the following exception:
Traceback (most recent call last):
File "./sign.py", line 110, in <module>
p7, data = SMIME.smime_load_pkcs7('mandato-PEM.p7m')
File "/usr/local/lib/python2.6/dist-packages/M2Crypto-0.21.1-py2.6-linux-i686.egg/M2Crypto/SMIME.py", line 91, in smime_load_pkcs7
p7_ptr, bio_ptr = m2.smime_read_pkcs7(bio)
M2Crypto.SMIME.SMIME_Error: no content type
While I have found information of a problem in Ubuntu (https://lists.ubuntu.com/archives/ubuntu-server-bugs/2010-July/038683.html), it seems to me that this cannot apply here since I built the latest M2Crypto manually and the test suite works fine.
Any help in resolving my problem would be highly appreciated!
many thanks
-bud
After a lot of sweat, here the solution for others who run into the same issue.
I was following the recipe http://code.activestate.com/recipes/285211/ and found many discussions on the web that just "verify(p7)" [method of SMIME] wasn't correct and "verify(p7, data)" was the right thing to do.
This applies only to SMIME documents where the signature is detached. My pkcs#7 file, and all other Italian digitally signed documents, are pkcs#7 envelopes that contain both the signature and the file content (in DER format).
Enveloped p7m files have to be verified as follows:
s=SMIME.SMIME()
st = X509.X509_Store()
st.load_info(trustedCAsPEMfileName)
s.set_x509_store(st)
p7bio = BIO.MemoryBuffer(p7strPEM)
p7 = SMIME.load_pkcs7_bio(p7bio)
certStack = p7.get0_signers(X509.X509_Stack())
s.set_x509_stack(certStack)
try:
docContent = s.verify(p7)
except SMIME.PKCS7_Error, e:
print "An exception occurred!!!!"
print e
To test that this works, I edited the p7m file such that the signature doesn't verify anymore and it correctly prints out "digest failure".
You can also verify a .p7m file (attached DER format) directly but you need to load PKCS #7 object from DER format by m2 direct call to OpenSSL (m2.pkcs7_read_bio_der(input_bio._ptr())) because there is no function for this inside M2Crypto SMIME module. For a proposed patch see Small patch to SMIME.py.
Here a sample code:
import logging
from M2Crypto import SMIME, X509, m2, BIO
certstore_path = "/etc/ssl/certs/ca-certificates.crt"
file_descriptor = open('test_file.p7m', 'rb')
input_bio = BIO.File(file_descriptor)
signer = SMIME.SMIME()
cert_store = X509.X509_Store()
cert_store.load_info(certstore_path)
signer.set_x509_store(cert_store)
try:
p7 = SMIME.PKCS7(m2.pkcs7_read_bio_der(input_bio._ptr()), 1)
except SMIME.SMIME_Error, e:
logging.error('load pkcs7 error: ' + str(e))
sk3 = p7.get0_signers(X509.X509_Stack())
signer.set_x509_stack(sk3)
data_bio = None
try:
v = signer.verify(p7, data_bio)
except SMIME.SMIME_Error, e:
logging.error('smime error: ' + str(e))
except SMIME.PKCS7_Error, e:
logging.error('pkcs7 error: ' + str(e))
Code Source: pysmime core
If you only want to extract the original file from the .p7m one (without verifying it), you need to install M2Crypto with pip install M2Crypto (you must probably run sudo apt-get install libssl-dev before) and then run this Python code:
from M2Crypto import BIO, SMIME, X509
# Load file in memory just to showcase BIO usage
with open('file.p7m', 'rb') as file:
p7m = file.read()
smime = SMIME.SMIME()
smime.set_x509_store(X509.X509_Store())
smime.set_x509_stack(X509.X509_Stack())
original_file_content = smime.verify(
SMIME.load_pkcs7_bio_der(BIO.MemoryBuffer(p7m)),
flags=SMIME.PKCS7_NOVERIFY
)
You can use SMIME.load_pkcs7, SMIME.load_pkcs7_bio, SMIME.load_pkcs7_der instead of SMIME.load_pkcs7_bio_der according to your use case: in-memory (_bio) or on file system .p7m file, and PEM or DER (_der) format.
The best reference I found to sign and unsign is the M2Crypto tests here:
http://svn.osafoundation.org/m2crypto/trunk/tests/test_smime.py
def test_sign(self):
buf = BIO.MemoryBuffer(self.cleartext)
s = SMIME.SMIME()
s.load_key('tests/signer_key.pem', 'tests/signer.pem')
p7 = s.sign(buf, SMIME.PKCS7_DETACHED)
assert len(buf) == 0
assert p7.type() == SMIME.PKCS7_SIGNED, p7.type()
assert isinstance(p7, SMIME.PKCS7), p7
out = BIO.MemoryBuffer()
p7.write(out)
buf = out.read()
assert buf[:len('-----BEGIN PKCS7-----')] == '-----BEGIN PKCS7-----'
buf = buf.strip()
assert buf[-len('-----END PKCS7-----'):] == '-----END PKCS7-----', buf[-len('-----END PKCS7-----'):]
assert len(buf) > len('-----END PKCS7-----') + len('-----BEGIN PKCS7-----')
s.write(out, p7, BIO.MemoryBuffer(self.cleartext))
return out
def test_verify(self):
s = SMIME.SMIME()
x509 = X509.load_cert('tests/signer.pem')
sk = X509.X509_Stack()
sk.push(x509)
s.set_x509_stack(sk)
st = X509.X509_Store()
st.load_info('tests/ca.pem')
s.set_x509_store(st)
p7, data = SMIME.smime_load_pkcs7_bio(self.signed)
assert isinstance(p7, SMIME.PKCS7), p7
v = s.verify(p7, data)
assert v == self.cleartext
t = p7.get0_signers(sk)
assert len(t) == 1
assert t[0].as_pem() == x509.as_pem(), t[0].as_text()
Be carefull with the documentation (http://svn.osafoundation.org/m2crypto/trunk/doc/howto.smime.html) because it is not updated.
See this patch:
https://bugzilla.osafoundation.org/show_bug.cgi?id=13020