--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tools/py2sis/ensymble/cryptutil.py Tue Feb 16 10:07:05 2010 +0530
@@ -0,0 +1,414 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+##############################################################################
+# cryptutil.py - OpenSSL command line utility wrappers for Ensymble
+# Copyright 2006, 2007, 2008 Jussi Ylänen
+#
+# This file is part of Ensymble developer utilities for Symbian OS(TM).
+#
+# Ensymble is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Ensymble is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ensymble; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+##############################################################################
+
+import sys
+import os
+import errno
+import tempfile
+import random
+
+
+opensslcommand = None # Path to OpenSSL command line tool
+openssldebug = False # True for extra debug output
+
+
+##############################################################################
+# Public module-level functions
+##############################################################################
+
+def setdebug(active):
+ '''
+ Activate or deactivate debug output.
+
+ setdebug(...) -> None
+
+ active Debug output enabled / disabled, a boolean value
+
+ Debug output consists of OpenSSL binary command line and
+ any output produced to the standard error stream by OpenSSL.
+ '''
+
+ global openssldebug
+ openssldebug = not not active # Convert to boolean.
+
+def signstring(privkey, passphrase, string):
+ '''
+ Sign a binary string using a given private key and its pass phrase.
+
+ signstring(...) -> (signature, keytype)
+
+ privkey RSA or DSA private key, a string in PEM (base-64) format
+ passphrase pass phrase for the private key, a non-Unicode string or None
+ string a binary string to sign
+
+ signature signature, an ASN.1 encoded binary string
+ keytype detected key type, string, "RSA" or "DSA"
+
+ NOTE: On platforms with poor file system security, decrypted version
+ of the private key may be grabbed from the temporary directory!
+ '''
+
+ if passphrase == None or len(passphrase) == 0:
+ # OpenSSL does not like empty stdin while reading a passphrase from it.
+ passphrase = "\n"
+
+ # Create a temporary directory for OpenSSL to work in.
+ tempdir = mkdtemp("ensymble-XXXXXX")
+
+ keyfilename = os.path.join(tempdir, "privkey.pem")
+ sigfilename = os.path.join(tempdir, "signature.dat")
+ stringfilename = os.path.join(tempdir, "string.dat")
+
+ try:
+ # If the private key is in PKCS#8 format, it needs to be converted.
+ privkey = convertpkcs8key(tempdir, privkey, passphrase)
+
+ # Decrypt the private key. Older versions of OpenSSL do not
+ # accept the "-passin" parameter for the "dgst" command.
+ privkey, keytype = decryptkey(tempdir, privkey, passphrase)
+
+ if keytype == "DSA":
+ signcmd = "-dss1"
+ elif keytype == "RSA":
+ signcmd = "-sha1"
+ else:
+ raise ValueError("unknown private key type %s" % keytype)
+
+ # Write decrypted PEM format private key to file.
+ keyfile = file(keyfilename, "wb")
+ keyfile.write(privkey)
+ keyfile.close()
+
+ # Write binary string to a file. On some systems, stdin is
+ # always in text mode and thus unsuitable for binary data.
+ stringfile = file(stringfilename, "wb")
+ stringfile.write(string)
+ stringfile.close()
+
+ # Sign binary string using the decrypted private key.
+ command = ("dgst %s -binary -sign %s "
+ "-out %s %s") % (signcmd, quote(keyfilename),
+ quote(sigfilename), quote(stringfilename))
+ runopenssl(command)
+
+ signature = ""
+ if os.path.isfile(sigfilename):
+ # Read signature from file.
+ sigfile = file(sigfilename, "rb")
+ signature = sigfile.read()
+ sigfile.close()
+
+ if signature.strip() == "":
+ # OpenSSL did not create output, something went wrong.
+ raise ValueError("unspecified error during signing")
+ finally:
+ # Delete temporary files.
+ for fname in (keyfilename, sigfilename, stringfilename):
+ try:
+ os.remove(fname)
+ except OSError:
+ pass
+
+ # Remove temporary directory.
+ os.rmdir(tempdir)
+
+ return (signature, keytype)
+
+
+def certtobinary(pemcert):
+ '''
+ Convert X.509 certificates from PEM (base-64) format to DER (binary).
+
+ certtobinary(...) -> dercert
+
+ pemcert One or more X.509 certificates in PEM (base-64) format, a string
+
+ dercert X.509 certificate(s), an ASN.1 encoded binary string
+ '''
+
+ # Find base-64 encoded data between header and footer.
+ header = "-----BEGIN CERTIFICATE-----"
+ footer = "-----END CERTIFICATE-----"
+ endoffset = 0
+ certs = []
+ while True:
+ # First find a header.
+ startoffset = pemcert.find(header, endoffset)
+ if startoffset < 0:
+ # No header found, stop search.
+ break
+
+ startoffset += len(header)
+
+ # Next find a footer.
+ endoffset = pemcert.find(footer, startoffset)
+ if endoffset < 0:
+ # No footer found.
+ raise ValueError("missing PEM certificate footer")
+
+ # Extract the base-64 encoded certificate and decode it.
+ try:
+ cert = pemcert[startoffset:endoffset].decode("base-64")
+ except:
+ # Base-64 decoding error.
+ raise ValueError("invalid PEM format certificate")
+
+ certs.append(cert)
+
+ endoffset += len(footer)
+
+ if len(certs) == 0:
+ raise ValueError("not a PEM format certificate")
+
+ # DER certificates are simply raw binary versions
+ # of the base-64 encoded PEM certificates.
+ return "".join(certs)
+
+
+##############################################################################
+# Module-level functions which are normally only used by this module
+##############################################################################
+
+def convertpkcs8key(tempdir, privkey, passphrase):
+ '''
+ Convert a PKCS#8-format RSA or DSA private key to an older
+ SSLeay-compatible format.
+
+ convertpkcs8key(...) -> privkeyout
+
+ tempdir Path to pre-existing temporary directory with read/write access
+ privkey RSA or DSA private key, a string in PEM (base-64) format
+ passphrase pass phrase for the private key, a non-Unicode string or None
+
+ privkeyout decrypted private key in PEM (base-64) format
+ '''
+
+ # Determine PKCS#8 private key type.
+ if privkey.find("-----BEGIN PRIVATE KEY-----") >= 0:
+ # Unencrypted PKCS#8 private key
+ encryptcmd = "-nocrypt"
+ elif privkey.find("-----BEGIN ENCRYPTED PRIVATE KEY-----") >= 0:
+ # Encrypted PKCS#8 private key
+ encryptcmd = ""
+ else:
+ # Not a PKCS#8 private key, nothing to do.
+ return privkey
+
+ keyinfilename = os.path.join(tempdir, "keyin.pem")
+ keyoutfilename = os.path.join(tempdir, "keyout.pem")
+
+ try:
+ # Write PEM format private key to file.
+ keyinfile = file(keyinfilename, "wb")
+ keyinfile.write(privkey)
+ keyinfile.close()
+
+ # Convert a PKCS#8 private key to older SSLeay-compatible format.
+ # Keep pass phrase as-is.
+ runopenssl("pkcs8 -in %s -out %s -passin stdin -passout stdin %s" %
+ (quote(keyinfilename), quote(keyoutfilename), encryptcmd),
+ "%s\n%s\n" % (passphrase, passphrase))
+
+ privkey = ""
+ if os.path.isfile(keyoutfilename):
+ # Read converted private key back.
+ keyoutfile = file(keyoutfilename, "rb")
+ privkey = keyoutfile.read()
+ keyoutfile.close()
+
+ if privkey.strip() == "":
+ # OpenSSL did not create output. Probably a wrong pass phrase.
+ raise ValueError("wrong pass phrase or invalid PKCS#8 private key")
+ finally:
+ # Delete temporary files.
+ for fname in (keyinfilename, keyoutfilename):
+ try:
+ os.remove(fname)
+ except OSError:
+ pass
+
+ return privkey
+
+def decryptkey(tempdir, privkey, passphrase):
+ '''
+ decryptkey(...) -> (privkeyout, keytype)
+
+ tempdir Path to pre-existing temporary directory with read/write access
+ privkey RSA or DSA private key, a string in PEM (base-64) format
+ passphrase pass phrase for the private key, a non-Unicode string or None
+ string a binary string to sign
+
+ keytype detected key type, string, "RSA" or "DSA"
+ privkeyout decrypted private key in PEM (base-64) format
+
+ NOTE: On platforms with poor file system security, decrypted version
+ of the private key may be grabbed from the temporary directory!
+ '''
+
+ # Determine private key type.
+ if privkey.find("-----BEGIN DSA PRIVATE KEY-----") >= 0:
+ keytype = "DSA"
+ convcmd = "dsa"
+ elif privkey.find("-----BEGIN RSA PRIVATE KEY-----") >= 0:
+ keytype = "RSA"
+ convcmd = "rsa"
+ else:
+ raise ValueError("not an RSA or DSA private key in PEM format")
+
+ keyinfilename = os.path.join(tempdir, "keyin.pem")
+ keyoutfilename = os.path.join(tempdir, "keyout.pem")
+
+ try:
+ # Write PEM format private key to file.
+ keyinfile = file(keyinfilename, "wb")
+ keyinfile.write(privkey)
+ keyinfile.close()
+
+ # Decrypt the private key. Older versions of OpenSSL do not
+ # accept the "-passin" parameter for the "dgst" command.
+ runopenssl("%s -in %s -out %s -passin stdin" %
+ (convcmd, quote(keyinfilename),
+ quote(keyoutfilename)), passphrase)
+
+ privkey = ""
+ if os.path.isfile(keyoutfilename):
+ # Read decrypted private key back.
+ keyoutfile = file(keyoutfilename, "rb")
+ privkey = keyoutfile.read()
+ keyoutfile.close()
+
+ if privkey.strip() == "":
+ # OpenSSL did not create output. Probably a wrong pass phrase.
+ raise ValueError("wrong pass phrase or invalid private key")
+ finally:
+ # Delete temporary files.
+ for fname in (keyinfilename, keyoutfilename):
+ try:
+ os.remove(fname)
+ except OSError:
+ pass
+
+ return (privkey, keytype)
+
+def mkdtemp(template):
+ '''
+ Create a unique temporary directory.
+
+ tempfile.mkdtemp() was introduced in Python v2.3. This is for
+ backward compatibility.
+ '''
+
+ # Cross-platform way to determine a suitable location for temporary files.
+ systemp = tempfile.gettempdir()
+
+ if not template.endswith("XXXXXX"):
+ raise ValueError("invalid template for mkdtemp(): %s" % template)
+
+ for n in xrange(10000):
+ randchars = []
+ for m in xrange(6):
+ randchars.append(random.choice("abcdefghijklmnopqrstuvwxyz"))
+
+ tempdir = os.path.join(systemp, template[: -6]) + "".join(randchars)
+
+ try:
+ os.mkdir(tempdir, 0700)
+ return tempdir
+ except OSError:
+ pass
+ else:
+ # All unique names in use, raise an error.
+ raise OSError(errno.EEXIST, os.strerror(errno.EEXIST),
+ os.path.join(systemp, template))
+
+def quote(filename):
+ '''Quote a filename if it has spaces in it.'''
+ if " " in filename:
+ filename = '"%s"' % filename
+ return filename
+
+def runopenssl(command, datain = ""):
+ '''Run the OpenSSL command line tool with the given parameters and data.'''
+
+ global opensslcommand
+
+ if opensslcommand == None:
+ # Find path to the OpenSSL command.
+ findopenssl()
+
+ # Construct a command line for os.popen3().
+ cmdline = '%s %s' % (opensslcommand, command)
+
+ if openssldebug:
+ # Print command line.
+ print "DEBUG: os.popen3(%s)" % repr(cmdline)
+
+ # Run command. Use os.popen3() to capture stdout and stderr.
+ pipein, pipeout, pipeerr = os.popen3(cmdline)
+ pipein.write(datain)
+ pipein.close()
+ dataout = pipeout.read()
+ pipeout.close()
+ errout = pipeerr.read()
+ pipeerr.close()
+
+ if openssldebug:
+ # Print standard error output.
+ print "DEBUG: pipeerr.read() = %s" % repr(errout)
+
+ return (dataout, errout)
+
+def findopenssl():
+ '''Find the OpenSSL command line tool.'''
+
+ global opensslcommand
+
+ # Get PATH and split it to a list of paths.
+ paths = os.environ["PATH"].split(os.pathsep)
+
+ # Insert script path in front of others.
+ # On Windows, this is where openssl.exe resides by default.
+ if sys.path[0] != "":
+ paths.insert(0, sys.path[0])
+
+ for path in paths:
+ cmd = os.path.join(path, "openssl")
+ try:
+ # Try to query OpenSSL version.
+ pin, pout = os.popen4('"%s" version' % cmd)
+ pin.close()
+ verstr = pout.read()
+ pout.close()
+ except OSError:
+ # Could not run command, skip to the next path candidate.
+ continue
+
+ if verstr.split()[0] == "OpenSSL":
+ # Command found, stop searching.
+ break
+ else:
+ raise IOError("no valid OpenSSL command line tool found in PATH")
+
+ # Add quotes around command in case of embedded whitespace on path.
+ opensslcommand = quote(cmd)