# HG changeset patch # User Joshua Cranmer # Date 1402015551 18000 # Thu Jun 05 19:45:51 2014 -0500 # Node ID 4ba38f875a0958bd1916c758df457914e4a71af8 # Parent 3dfe0c40ba28093a2c4638126fd1df3bb204561d imported patch test-smime MozReview-Commit-ID: 95cyxTTEoyv diff --git a/mailnews/mime/test/unit/head_mime.js b/mailnews/mime/test/unit/head_mime.js --- a/mailnews/mime/test/unit/head_mime.js +++ b/mailnews/mime/test/unit/head_mime.js @@ -1,18 +1,101 @@ Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource:///modules/mailServices.js"); Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://testing-common/mailnews/mailTestUtils.js"); +Components.utils.import("resource://testing-common/mailnews/PromiseTestUtils.jsm"); var Cc = Components.classes; var Ci = Components.interfaces; var Cr = Components.results; var CC = Components.Constructor; // Ensure the profile directory is set up do_get_profile(); var gDEPTH = "../../../../"; do_register_cleanup(function() { load(gDEPTH + "mailnews/resources/mailShutdown.js"); }); + +class DummyMsgHeader { + constructor() { + this.mProperties = {}; + this.messageSize = 0; + this.recipients = null; + this.from = null; + this.subject = ""; + this.ccList = null, + this.messageId = null, + this.listPost = null, + this.date = 0, + this.accountKey = "", + this.flags = 0, + this.folder = null + } + getStringProperty(aProperty) { return this.mProperties[aProperty]; } + setStringProperty(aProperty, aVal) { this.mProperties[aProperty] = aVal; } + getUint32Property(aProperty) { + if (aProperty in this.mProperties) + return parseInt(this.mProperties[aProperty]); + return 0; + } + setUint32Property(aProperty, aVal) { + this.mProperties[aProperty] = aVal.toString(); + } + markHasAttachments(hasAttachments) { } + get mime2DecodedSubject() { return this.subject; } +} + +function apply_mime_conversion(msgUri, headerSink={}) { + let stubHeaderSink = { + processHeaders(aHeaderNames, aHeaderValues, dontCollectAddress) {}, + handleAttachment(contentType, url, displayName, uri, aNotDownloaded) {}, + addAttachmentField(field, value) {}, + onEndAllAttachments() {}, + onEndMsgHeaders(url) {}, + onEndMsgDownload(url) {}, + securityInfo: null, + onMsgHasRemoteContent(aMsgHdr, aContentURI) {}, + dummyMsgHeader: new DummyMsgHeader(), + get properties() { return null; }, + resetProperties() {}, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgHeaderSink]) + }; + + // Copy the descriptors from headerSink to stubHeaderSink. + let fullHeaderSink = Object.create(headerSink); + for (let name of Object.getOwnPropertyNames(stubHeaderSink)) { + if (!(name in headerSink)) { + Object.defineProperty(fullHeaderSink, name, + Object.getOwnPropertyDescriptor(stubHeaderSink, name)); + } + } + + var msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"] + .createInstance(Ci.nsIMsgWindow); + msgWindow.msgHeaderSink = fullHeaderSink; + + let messenger = Cc["@mozilla.org/messenger;1"] + .createInstance(Ci.nsIMessenger); + let service = messenger.messageServiceFromURI(msgUri); + + // This is what we listen on in the end. + let listener = new PromiseTestUtils.PromiseStreamListener(); + + // Make the underlying channel--we need this for the converter parameter. + let url = {}; + service.GetUrlForUri(msgUri, url, msgWindow); + let channel = Services.io.newChannelFromURIWithLoadInfo(url.value, null); + + // Make the MIME converter, using the listener we first set up. + let converter = Cc["@mozilla.org/streamConverters;1"] + .getService(Ci.nsIStreamConverterService) + .asyncConvertData("message/rfc822", "text/html", + listener, channel); + + // Now load the message, run it through the converter, and wait for all the + // data to stream through. + channel.asyncOpen(converter, null); + return listener.promise; +} diff --git a/mailnews/mime/test/unit/test_smime_decrypt.js b/mailnews/mime/test/unit/test_smime_decrypt.js new file mode 100644 --- /dev/null +++ b/mailnews/mime/test/unit/test_smime_decrypt.js @@ -0,0 +1,105 @@ +Components.utils.import("resource:///modules/mailServices.js"); +Components.utils.import("resource:///modules/PromiseUtils.jsm"); +Components.utils.import("resource://testing-common/mailnews/localAccountUtils.js"); +Components.utils.import("resource://testing-common/mailnews/smimeUtils.js"); + +var gInbox; + +let smimeHeaderSink = { + expectResults(maxLen) { + dump("Restarting for next test\n"); + this._deferred = PromiseUtils.defer(); + this._expectedLen = maxLen; + this._results = []; + return this._deferred.promise; + }, + maxWantedNesting() { return 1; }, + signedStatus(aNestingLevel, aSignedStatus, aSignerCert) { + dump("Signed message\n"); + do_check_eq(aNestingLevel, 1); + this._results.push({ + type: "signed", + status: aSignedStatus, + certificate: aSignerCert + }); + if (this._results.length == this._expectedLen) + this._deferred.resolve(this._results); + }, + encryptionStatus(aNestingLevel, aEncryptedStatus, aRecipientCert) { + dump("Encrypted message\n"); + do_check_eq(aNestingLevel, 1); + this._results.push({ + type: "encrypted", + status: aEncryptedStatus, + certificate: aRecipientCert + }); + if (this._results.length == this._expectedLen) + this._deferred.resolve(this._results); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgSMIMEHeaderSink]) +}; + +function add_message_to_inbox(relpath) { + let file = do_get_file("../../../test/data/" + relpath); + let listener = new PromiseTestUtils.PromiseCopyListener(); + gInbox.copyFileMessage(file, null, false, Ci.nsMsgMessageFlags.New, + "", null, listener); + // Synchronous, so it never calls this. Grrr... + listener.OnStopCopy(Cr.NS_OK); + return listener.promise.then( + msgs => gInbox.generateMessageURI(msgs.messageKeys[0])); +} + +add_task(function *check_signed_status() { + let promise = smimeHeaderSink.expectResults(1); + let signed_uri = yield add_message_to_inbox("smime-certs/simple-signed.eml"); + let contents = yield apply_mime_conversion(signed_uri, {securityInfo: smimeHeaderSink}); + // Check that the plaintext body is in there. + do_check_true(contents.includes("This data should be within the signed content.")); + // Check that we're also using the display output. + do_check_true(contents.includes("")); + + // Check the signature details. This happens asynchronously. + let signer = yield promise; + do_check_eq(signer.length, 1); + do_check_eq(signer[0].type, "signed"); + do_check_eq(signer[0].status, 0); + do_check_eq(signer[0].certificate.emailAddress, "tinderbox@test.invalid"); +}); + + +add_task(function *check_encrypted_status() { + let promise = smimeHeaderSink.expectResults(1); + let encrypted_uri = yield add_message_to_inbox("smime-certs/simple-encrypted.eml"); + let contents = yield apply_mime_conversion(encrypted_uri, {securityInfo: smimeHeaderSink}); + // Check that the plaintext body is in there. + do_check_true(contents.includes("This data should be within the encrypted content.")); + // Check that we're also using the display output. + do_check_true(contents.includes("")); + + // Check the signature details. This happens asynchronously. + let encrypted = yield promise; + do_check_eq(encrypted.length, 1); + do_check_eq(encrypted[0].type, "encrypted"); + do_check_eq(encrypted[0].status, 0); + do_check_eq(encrypted[0].certificate, null); +}); + +add_task(function *cleanup() { + gInbox = null; + localAccountUtils.clearAll(); +}); + +function run_test() { + localAccountUtils.loadLocalMailAccount(); + gInbox = localAccountUtils.inboxFolder; + SmimeUtils.ensureNSS(); + + let certDB = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); + SmimeUtils.loadPEMCertificate(do_get_file("../../../test/data/smime-certs/base-ca.pem"), Ci.nsIX509Cert.CA_CERT); + SmimeUtils.loadCertificateAndKey(do_get_file("../../../test/data/smime-certs/tinderbox-smime.p12")); + + do_check_neq(certDB.findCertByEmailAddress("tinderbox@test.invalid"), null); + run_next_test(); +} diff --git a/mailnews/mime/test/unit/xpcshell.ini b/mailnews/mime/test/unit/xpcshell.ini --- a/mailnews/mime/test/unit/xpcshell.ini +++ b/mailnews/mime/test/unit/xpcshell.ini @@ -16,10 +16,11 @@ support-files = custom_header.js [test_nsIMsgHeaderParser1.js] [test_nsIMsgHeaderParser2.js] [test_nsIMsgHeaderParser3.js] [test_nsIMsgHeaderParser4.js] [test_nsIMsgHeaderParser5.js] [test_parseHeadersWithArray.js] [test_parser.js] [test_rfc822_body.js] +[test_smime_decrypt.js] [test_structured_headers.js] [test_text_attachment.js] diff --git a/mailnews/moz.build b/mailnews/moz.build --- a/mailnews/moz.build +++ b/mailnews/moz.build @@ -36,16 +36,17 @@ if CONFIG['OS_ARCH'] == 'WINNT': DIRS += ['import/outlook/src'] if not CONFIG['GNU_CC']: DIRS += [ 'import/winlivemail', ] TEST_DIRS += [ + 'test/data/smime-certs', 'imap/test', 'import/test', 'local/test', ] if CONFIG['MOZ_MAPI_SUPPORT']: DIRS += [ 'mapi/mapiDLL', @@ -70,16 +71,17 @@ TESTING_JS_MODULES.mailnews += [ 'test/fakeserver/pop3d.js', 'test/fakeserver/smtpd.js', 'test/resources/IMAPpump.js', 'test/resources/localAccountUtils.js', 'test/resources/mailTestUtils.js', 'test/resources/MockFactory.js', 'test/resources/NetworkTestUtils.jsm', 'test/resources/PromiseTestUtils.jsm', + 'test/resources/smimeUtils.js', ] TEST_HARNESS_FILES.xpcshell.mailnews.data += [ '/mailnews/test/data/**', ] TEST_HARNESS_FILES.xpcshell.mailnews.resources += [ '/mailnews/test/resources/**', diff --git a/mailnews/test/data/smime-certs/README.md b/mailnews/test/data/smime-certs/README.md new file mode 100644 --- /dev/null +++ b/mailnews/test/data/smime-certs/README.md @@ -0,0 +1,35 @@ +S/MIME certificate generation for Mailnews tests +================================================ + +This directory implements test frameworks for adding new certificates and +sample mails for S/MIME tests. The basic framework for these tests originate +with the certificate validation test framework defined at +`mozilla/security/manager/ssl/tests/unit/`. Certificates and private keys are +defined on the basis of templated `.certspec` and `.keyspec` files, +respetively. These are transformed to exported files that can be imported into +the key and certificate stores (see `smimeUtils.js` for instructions on how to +do that). + +In addition to generating keys and certificates, it is possible to generate +S/MIME messages using a templated email format. Generating these messages +currently requires the use of OpenSSL for access as a simple command-line tool. + + +S/MIME templates +---------------- + +Emails are generated using `pysmime.py`. The input template file is a +nearly-valid message/rfc822 message. The only invalid part of the message is +the `Date` header, which may be a simple expression involving `$now` and time +deltas. For example, the header `$now + delta(days=100)` will be replaced with +an appropriate time that is 100 days from the "now" epoch. The "now" epoch is +not exactly the current time, but it is synchronized with the mozilla-central +certificate generation logic so that 100 days is usually 100 days from the +start date validity of the certificate. + +In addition to allowing for a generated Date header, the content-type of +`message/x-generated` is treated specially. Such parts are converted to S/MIME. +The body of this MIME type is treated as the MIME part of an enclosed data type +(so, for example, it should have a `Content-Type: text/plain` header or +similar). Parameters on the `Content-Type` are used to control features of the +S/MIME generation. diff --git a/mailnews/test/data/smime-certs/base-ca.pem.certspec b/mailnews/test/data/smime-certs/base-ca.pem.certspec new file mode 100644 --- /dev/null +++ b/mailnews/test/data/smime-certs/base-ca.pem.certspec @@ -0,0 +1,3 @@ +issuer:base-ca +subject:base-ca +extension:basicConstraints:cA, diff --git a/mailnews/test/data/smime-certs/moz.build b/mailnews/test/data/smime-certs/moz.build new file mode 100644 --- /dev/null +++ b/mailnews/test/data/smime-certs/moz.build @@ -0,0 +1,97 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +@template +def CCGeneratedTestCertificate(name): + if not CONFIG['COMPILE_ENVIRONMENT']: + return + + GENERATED_FILES += [name] + props = GENERATED_FILES[name] + props.script = '/mozilla/security/manager/ssl/tests/unit/pycert.py' + props.inputs = ['%s.certspec' % name] + # Turn RELATIVEDIR into list entry: like + # 'security/manager/ssl/tests/unit/bad_certs' -> + # TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.bad_certs. + files = TEST_HARNESS_FILES.xpcshell + for part in RELATIVEDIR.split('/'): + files = files[part] + files += ['!%s' % name] + +@template +def CCGeneratedTestKey(name): + if not CONFIG['COMPILE_ENVIRONMENT']: + return + + GENERATED_FILES += [name] + props = GENERATED_FILES[name] + props.script = '/mozilla/security/manager/ssl/tests/unit/pykey.py' + props.inputs = ['%s.keyspec' % name] + # Turn RELATIVEDIR into list entry: like + # 'security/manager/ssl/tests/unit/bad_certs' -> + # TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.bad_certs. + files = TEST_HARNESS_FILES.xpcshell + for part in RELATIVEDIR.split('/'): + files = files[part] + files += ['!%s' % name] + +@template +def GeneratedSecMail(name): + if not CONFIG['COMPILE_ENVIRONMENT']: + return + + GENERATED_FILES += [name] + props = GENERATED_FILES[name] + props.script = '/mailnews/test/data/smime-certs/pysmime.py' + props.inputs = ['%s.tmpl' % name] + # Turn RELATIVEDIR into list entry: like + # 'security/manager/ssl/tests/unit/bad_certs' -> + # TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.bad_certs. + files = TEST_HARNESS_FILES.xpcshell + for part in RELATIVEDIR.split('/'): + files = files[part] + files += ['!%s' % name] + +@template +def GeneratedTestPKCS12(name): + if not CONFIG['COMPILE_ENVIRONMENT']: + return + + basename = name.split('.')[0] + GENERATED_FILES += [name] + props = GENERATED_FILES[name] + props.script = '/mailnews/test/data/smime-certs/pypkcs12.py' + props.inputs = ['!%s.key' % basename, '!%s.pem' % basename] + # Turn RELATIVEDIR into list entry: like + # 'security/manager/ssl/tests/unit/bad_certs' -> + # TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.bad_certs. + files = TEST_HARNESS_FILES.xpcshell + for part in RELATIVEDIR.split('/'): + files = files[part] + files += ['!%s' % name] + +test_certificates = ( + 'base-ca.pem', +) +test_keys = ( + 'tester-smime', + 'tinderbox-smime', +) +test_mails = ( + 'sign-and-encrypt.eml', + 'simple-encrypted.eml', + 'simple-signed.eml', +) + +for test_certificate in test_certificates: + CCGeneratedTestCertificate(test_certificate) + +for test_key in test_keys: + CCGeneratedTestCertificate(test_key + '.pem') + CCGeneratedTestKey(test_key + '.key') + GeneratedTestPKCS12(test_key + '.p12') + +for test_mail in test_mails: + GeneratedSecMail(test_mail) diff --git a/mailnews/test/data/smime-certs/pypkcs12.py b/mailnews/test/data/smime-certs/pypkcs12.py new file mode 100755 --- /dev/null +++ b/mailnews/test/data/smime-certs/pypkcs12.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Generate PKCS12 blobs for the input key files. + +import base64 +import sys +import os +import subprocess + +import pyasn1 +from pyasn1.codec.der import decoder, encoder +from pyasn1.type import univ, namedtype, namedval, tag, constraint, useful +from pyasn1_modules import rfc2315, rfc3852, rfc5208, rfc5652 + +class PFXVersion(univ.Integer): pass +PFXVersion.namedValues = namedval.NamedValues( + ('v3', 3), +) + +class MacData(univ.Sequence): pass +MacData.componentType = namedtype.NamedTypes( + namedtype.NamedType('mac', rfc2315.DigestInfo()), + namedtype.NamedType('macSalt', univ.OctetString()), + namedtype.NamedType('iterations', univ.Integer()) +) + +class PFX(univ.Sequence): pass +PFX.componentType = namedtype.NamedTypes( + namedtype.NamedType('version', PFXVersion()), + namedtype.NamedType('authSafe', rfc5652.ContentInfo()), + namedtype.OptionalNamedType('macData', MacData()) +) + +class AuthenticatedSafe(univ.SequenceOf): pass +AuthenticatedSafe.componentType = rfc5652.ContentInfo() + +KeyBagOID = univ.ObjectIdentifier(value=(1,2,840,113549,1,12,10,1,1)) +ShroudedKeyBagOID = univ.ObjectIdentifier(value=(1,2,840,113549,1,12,10,1,2)) +CertBagOID = univ.ObjectIdentifier(value=(1,2,840,113549,1,12,10,1,3)) + +def load_pem_file(path): + data = '' + with open(path) as fd: + for line in fd: + if line.startswith('----'): + continue + data += base64.b64decode(line) + return data + +def make_content_info(data, oid): + data = encoder.encode(univ.OctetString(value=data)) + contentInfo = rfc5652.ContentInfo() + contentInfo.setComponentByName('contentType', oid) + contentInfo.setComponentByName('content', data) + return contentInfo + +def main(output, keypath, certpath): + openssl = subprocess.Popen([ + 'openssl', 'pkcs12', '-export', '-inkey', keypath, '-in', certpath, + '-password', 'pass:'], stdout=subprocess.PIPE) + results, _ = openssl.communicate() + output.write(results) + return + + key = load_pem_file(keypath) + cert = load_pem_file(certpath) + + # Make the authenticated safes + safe = AuthenticatedSafe() + safe.setComponentByPosition(0, make_content_info(key, KeyBagOID)) + safe.setComponentByPosition(1, make_content_info(cert, CertBagOID)) + auth_safe = make_content_info(encoder.encode(safe), rfc5652.id_data) + + pk12_contents = PFX() + pk12_contents.setComponentByName('version', 3) + pk12_contents.setComponentByName('authSafe', auth_safe) + output.write(encoder.encode(pk12_contents)) + + with open('/src/build/trunk/mail/mailnews/test/data/smime-certs/test.out', 'rb') as fd: + data = fd.read() + #pyasn1.debug.setLogger(pyasn1.debug.Debug('all')) + (val, rest) = decoder.decode(data, asn1Spec=PFX()) + contents = decoder.decode(val['authSafe']['content'], univ.OctetString())[0] + print decoder.decode(contents, asn1Spec=AuthenticatedSafe())[0].prettyPrint() + +if __name__ == '__main__': + main(sys.stdout, *sys.argv[1:]) diff --git a/mailnews/test/data/smime-certs/pysmime.py b/mailnews/test/data/smime-certs/pysmime.py new file mode 100755 --- /dev/null +++ b/mailnews/test/data/smime-certs/pysmime.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys +sys.path += map(lambda p: '/src/trunk/comm-central/mozilla/third_party/python/' + p, + ['pyasn1-modules/pyasn1_modules', 'pyasn1/pyasn1']) + +import datetime +import email +import email.generator +import time +import os +import subprocess + +def write_message(message, output): + '''Write an email.message.Message object to the stream at output. Since + Python doesn't enforce normalized CRLF bodies in its email library, we need + to manage this ourselves. So what we do is basically hook the output to run + s/\r?\n/\r\n/g.''' + class CRLFEnforcer(object): + def __init__(self, outfd): + self.output = outfd + + def write(self, s): + # Convert CRLF to LF first, so that CRLF goes to CRLF not CRCRLF + s = s.replace('\r\n', '\n') + s = s.replace('\n', '\r\n') + self.output.write(s) + generator = email.generator.Generator(CRLFEnforcer(output), False, 0) + generator.flatten(message) + +def parse_template(emailfd): + msg = email.message_from_file(emailfd) + # This is a funky definition of "now", but it's meant to match pycert's + # definition. It's basically the start of the current year but in the local + # timezone. + now = datetime.datetime.strptime(str(datetime.datetime.utcnow().year), '%Y') + + for part in msg.walk(): + # Implement the simple arithmetic for handling date/times in the + # messages. This is basically $now + delta(days=1) for something a day + # in the 'future' (see above for the funky definition of now). + if 'Date' in part and "$now" in part['Date']: + code = part['Date'].replace('$now', 'now') + real = eval(code, {'delta': datetime.timedelta}, {'now': now}) + part.replace_header('Date', + email.utils.formatdate(time.mktime(real.timetuple()))) + if part.get_content_type() == "message/x-generated": + generate_smime_blob(part) + + return msg + +CERT_DIR=os.path.abspath(os.curdir) + +def generate_smime_blob(part): + assert part.is_multipart() + # Load the Content-Type parameters into a dict. + parameters = dict(part.get_params()[1:]) + raw_data = part.get_payload(0) + + openssl_call = ['openssl', 'smime'] + if 'signer' in parameters: + sign_cert = os.path.join(CERT_DIR, parameters['signer'] + '.pem') + sign_key = os.path.join(CERT_DIR, parameters['signer'] + '.key') + openssl_call += ['-sign', '-signer', sign_cert, '-inkey', sign_key] + elif 'decryptor' in parameters: + # Choose the encryption cipher to use. + openssl_call += ['-' + parameters.get('cipher', 'aes-128-cbc')] + encrypt_cert = os.path.join(CERT_DIR, parameters['decryptor'] + '.pem') + + # Put the encryption certificates last. + openssl_call += ['-encrypt', encrypt_cert] + + # Call OpenSSL, passing the original message as the input and parsing the + # result into a new Message object. + openssl = subprocess.Popen(openssl_call, stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + write_message(raw_data, openssl.stdin) + (crypto_blob, _) = openssl.communicate() + crypt_msg = email.message_from_string(crypto_blob) + del crypt_msg['MIME-Version'] # OpenSSL adds this bit. + + # Replace the current part with the newly-minted message + for header, value in crypt_msg.items(): + assert header.startswith('Content-') + try: + part.replace_header(header, value) + except KeyError: + part.add_header(header, value) + part.set_payload(crypt_msg.get_payload()) + +def main(output, origpath): + with open(origpath) as origfd: + msg = parse_template(origfd) + write_message(msg, output) + +if __name__ == '__main__': + main(sys.stdout, *sys.argv[1:]) diff --git a/mailnews/test/data/smime-certs/sign-and-encrypt.eml.tmpl b/mailnews/test/data/smime-certs/sign-and-encrypt.eml.tmpl new file mode 100644 --- /dev/null +++ b/mailnews/test/data/smime-certs/sign-and-encrypt.eml.tmpl @@ -0,0 +1,12 @@ +From: Tester +To: Tinderbox +Date: $now + delta(days=100) +MIME-Version: 1.0 +Subject: This is a message that is signed and then encrypted. +Content-Type: message/x-generated; decryptor=tinderbox-smime + +Content-Type: message/x-generated; signer=tester-smime + +Content-Type: text/plain + +This is the actual message that is so secret. diff --git a/mailnews/test/data/smime-certs/simple-encrypted.eml.tmpl b/mailnews/test/data/smime-certs/simple-encrypted.eml.tmpl new file mode 100644 --- /dev/null +++ b/mailnews/test/data/smime-certs/simple-encrypted.eml.tmpl @@ -0,0 +1,10 @@ +From: Tester +To: Tinderbox +Date: $now + delta(days=100) +MIME-Version: 1.0 +Subject: This should be a simple encrypted email. +Content-Type: message/x-generated; decryptor=tinderbox-smime + +Content-Type: text/plain + +This data should be within the encrypted content. diff --git a/mailnews/test/data/smime-certs/simple-signed.eml.tmpl b/mailnews/test/data/smime-certs/simple-signed.eml.tmpl new file mode 100644 --- /dev/null +++ b/mailnews/test/data/smime-certs/simple-signed.eml.tmpl @@ -0,0 +1,10 @@ +From: Tinderbox +To: Tester +Date: $now + delta(days=100) +MIME-Version: 1.0 +Subject: This should be a simple signed email with multipart/signed. +Content-Type: message/x-generated; signer=tinderbox-smime + +Content-Type: text/plain + +This data should be within the signed content. diff --git a/mailnews/test/data/smime-certs/tester-smime.key.keyspec b/mailnews/test/data/smime-certs/tester-smime.key.keyspec new file mode 100644 --- /dev/null +++ b/mailnews/test/data/smime-certs/tester-smime.key.keyspec @@ -0,0 +1,1 @@ +alternate diff --git a/mailnews/test/data/smime-certs/tester-smime.pem.certspec b/mailnews/test/data/smime-certs/tester-smime.pem.certspec new file mode 100644 --- /dev/null +++ b/mailnews/test/data/smime-certs/tester-smime.pem.certspec @@ -0,0 +1,5 @@ +issuer:base-ca +subject:Tester +extension:keyUsage:nonRepudiation,digitalSignature,keyEncipherment +extension:extKeyUsage:emailProtection +extension:subjectAlternativeName:email:tester@test.invalid diff --git a/mailnews/test/data/smime-certs/tinderbox-smime.key.keyspec b/mailnews/test/data/smime-certs/tinderbox-smime.key.keyspec new file mode 100644 --- /dev/null +++ b/mailnews/test/data/smime-certs/tinderbox-smime.key.keyspec @@ -0,0 +1,1 @@ +default diff --git a/mailnews/test/data/smime-certs/tinderbox-smime.pem.certspec b/mailnews/test/data/smime-certs/tinderbox-smime.pem.certspec new file mode 100644 --- /dev/null +++ b/mailnews/test/data/smime-certs/tinderbox-smime.pem.certspec @@ -0,0 +1,5 @@ +issuer:base-ca +subject:Tinderbox +extension:keyUsage:nonRepudiation,digitalSignature,keyEncipherment +extension:extKeyUsage:emailProtection +extension:subjectAlternativeName:email:tinderbox@test.invalid diff --git a/mailnews/test/resources/smimeUtils.js b/mailnews/test/resources/smimeUtils.js new file mode 100644 --- /dev/null +++ b/mailnews/test/resources/smimeUtils.js @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This file provides some utilities for helping run S/MIME tests. + */ + +var EXPORTED_SYMBOLS = [ + 'SmimeUtils' +]; + +Components.utils.import("resource://gre/modules/Task.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://testing-common/mailnews/MockFactory.js"); + +var Cc = Components.classes; +var Ci = Components.interfaces; + +const gCertDialogs = { + confirmDownloadCACert: (ctx, cert, trust) => { + dump("Requesting certificate download\n"); + trust.value = Ci.nsIX509CertDB.TRUSTED_EMAIL; + return true; + }, + setPKCS12FilePassword: (ctx, password) => { + throw new Error("Not implemented"); + }, + getPKCS12FilePassword: (ctx, password) => { + password.value = ""; + return true; + }, + viewCert: (ctx, cert) => { + throw new Error("Not implemented"); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsICertificateDialogs]) +}; + +// Implements nsIInterfaceRequestor. Mostly serves to mock nsIPrompt. +const gInterfaceRequestor = { + alert: (title, text) => { + // We don't test anything that calls this method yet. + ok(false, `alert() should not have been called: ${text}`); + }, + + getInterface: iid => { + if (iid.equals(Ci.nsIPrompt)) { + return this; + } + + dump('Tried to getInterface(' + iid + ')\n'); + throw new Error(Cr.NS_ERROR_NO_INTERFACE); + } +}; + +var SmimeUtils = { + ensureNSS: function () { + // Ensure NSS is initialized. + Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); + + // Set up the internal key token so that subsequent code doesn't fail. If + // this isn't done, we'll fail to work if the NSS databases didn't already + // exist. + let keydb = Cc["@mozilla.org/security/pk11tokendb;1"] + .getService(Ci.nsIPK11TokenDB); + try { + keydb.getInternalKeyToken().initPassword(""); + } catch (e) { + // In this scenario, the key token already had its password initialized. + // Therefore, we don't need to do anything (assuming its password is + // empty). + } + + MockFactory.register("@mozilla.org/nsCertificateDialogs;1", gCertDialogs); + }, + + loadPEMCertificate: function (file, certType, loadKey=false) { + dump("Loading certificate from " + file.path + "\n"); + let certDB = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); + certDB.importCertsFromFile(file, certType); + }, + + loadCertificateAndKey: function (file) { + dump("Loading key from " + file.path + "\n"); + let certDB = Cc["@mozilla.org/security/x509certdb;1"] + .getService(Ci.nsIX509CertDB); + certDB.importPKCS12File(file); + }, +};