diff -r 000000000000 -r ca70ae20a155 src/tools/py2sis/ensymble/cmd_mergesis.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/tools/py2sis/ensymble/cmd_mergesis.py Tue Feb 16 10:07:05 2010 +0530 @@ -0,0 +1,314 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################## +# cmd_mergesis.py - Ensymble command line tool, mergesis command +# Copyright 2006, 2007 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 getopt +import getpass +import locale + +import sisfile +import sisfield +import cryptutil + + +############################################################################## +# Help texts +############################################################################## + +shorthelp = 'Merge several SIS packages into one' +longhelp = '''mergesis + [--cert=mycert.cer] [--privkey=mykey.key] [--passphrase=12345] + [--encoding=terminal,filesystem] [--verbose] + [mergefile]... + +Merge several SIS packages into one and sign the resulting SIS file with +the certificate provided. The first SIS file is used as the base file and +the remaining SIS files are added as unconditional embedded SIS files +into it. Any signatures present in the first SIS file are stripped. + +Options: + infile - Path of the base SIS file + mergefile - Path of SIS file(s) to add to the base SIS file + outfile - Path of the resulting SIS file + cert - Certificate to use for signing (PEM format) + privkey - Private key of the certificate (PEM format) + passphrase - Pass phrase of the private key (insecure, use stdin instead) + encoding - Local character encodings for terminal and filesystem + verbose - Print extra statistics + +Merging SIS files that already contain other SIS files is not supported. +''' + + +############################################################################## +# Parameters +############################################################################## + +MAXPASSPHRASELENGTH = 256 +MAXCERTIFICATELENGTH = 65536 +MAXPRIVATEKEYLENGTH = 65536 +MAXSISFILESIZE = 1024 * 1024 * 8 # Eight megabytes + + +############################################################################## +# Global variables +############################################################################## + +debug = False + + +############################################################################## +# Public module-level functions +############################################################################## + +def run(pgmname, argv): + global debug + + # Determine system character encodings. + try: + # getdefaultlocale() may sometimes return None. + # Fall back to ASCII encoding in that case. + terminalenc = locale.getdefaultlocale()[1] + "" + except TypeError: + # Invalid locale, fall back to ASCII terminal encoding. + terminalenc = "ascii" + + try: + # sys.getfilesystemencoding() was introduced in Python v2.3 and + # it can sometimes return None. Fall back to ASCII if something + # goes wrong. + filesystemenc = sys.getfilesystemencoding() + "" + except (AttributeError, TypeError): + filesystemenc = "ascii" + + try: + gopt = getopt.gnu_getopt + except: + # Python MAXCERTIFICATELENGTH: + raise ValueError("certificate file too large") + + # Read private key file. + f = file(privkey, "rb") + privkeydata = f.read(MAXPRIVATEKEYLENGTH + 1) + f.close() + + if len(privkeydata) > MAXPRIVATEKEYLENGTH: + raise ValueError("private key file too large") + elif cert == None and privkey == None: + # No certificate given, use the Ensymble default certificate. + # defaultcert.py is not imported when not needed. This speeds + # up program start-up a little. + import defaultcert + certdata = defaultcert.cert + privkeydata = defaultcert.privkey + + print ("%s: warning: no certificate given, using " + "insecure built-in one" % pgmname) + else: + raise ValueError("missing certificate or private key") + + # Get pass phrase. Pass phrase remains in terminal encoding. + passphrase = opts.get("--passphrase", opts.get("-p", None)) + if passphrase == None and privkey != None: + # Private key given without "--passphrase" option, ask it. + if sys.stdin.isatty(): + # Standard input is a TTY, ask password interactively. + passphrase = getpass.getpass("Enter private key pass phrase:") + else: + # Not connected to a TTY, read stdin non-interactively instead. + passphrase = sys.stdin.read(MAXPASSPHRASELENGTH + 1) + + if len(passphrase) > MAXPASSPHRASELENGTH: + raise ValueError("pass phrase too long") + + passphrase = passphrase.strip() + + # Determine verbosity. + verbose = False + if "--verbose" in opts.keys() or "-v" in opts.keys(): + verbose = True + + # Determine if debug output is requested. + if "--debug" in opts.keys(): + debug = True + + # Enable debug output for OpenSSL-related functions. + cryptutil.setdebug(True) + + # Ingredients for successful SIS generation: + # + # terminalenc Terminal character encoding (autodetected) + # filesystemenc File system name encoding (autodetected) + # infiles A list of input SIS file names, filesystemenc encoded + # outfile Output SIS file name, filesystemenc encoded + # cert Certificate in PEM format + # privkey Certificate private key in PEM format + # passphrase Pass phrase of priv. key, terminalenc encoded string + # verbose Boolean indicating verbose terminal output + + if verbose: + print + print "Input SIS files %s" % " ".join( + [f.decode(filesystemenc).encode(terminalenc) for f in infiles]) + print "Output SIS file %s" % ( + outfile.decode(filesystemenc).encode(terminalenc)) + print "Certificate %s" % ((cert and + cert.decode(filesystemenc).encode(terminalenc)) or "") + print "Private key %s" % ((privkey and + privkey.decode(filesystemenc).encode(terminalenc)) or "") + print + + insis = [] + for n in xrange(len(infiles)): + # Read input SIS files. + f = file(infiles[n], "rb") + instring = f.read(MAXSISFILESIZE + 1) + f.close() + + if len(instring) > MAXSISFILESIZE: + raise ValueError("%s: input SIS file too large" % infiles[n]) + + if n == 0: + # Store UIDs for later use. + uids = instring[:16] # UID1, UID2, UID3 and UIDCRC + + # Convert input SIS file to SISFields. + sf, rlen = sisfield.SISField(instring[16:], False) + + # Ignore extra bytes after SIS file. + if len(instring) > (rlen + 16): + print ("%s: %s: warning: %d extra bytes after SIS file (ignored)" % + (pgmname, infiles[n], (len(instring) - (rlen + 16)))) + + # Try to release some memory early. + del instring + + # Check that there are no embedded SIS files. + if len(sf.Data.DataUnits) > 1: + raise ValueError("%s: input SIS file contains " + "embedded SIS files" % infiles[n]) + + insis.append(sf) + + # Temporarily remove the SISDataIndex SISField from the first SISController. + ctrlfield = insis[0].Controller.Data + didxfield = ctrlfield.DataIndex + ctrlfield.DataIndex = None + + # Remove old signatures from the first SIS file. + if len(ctrlfield.getsignatures()) > 0: + print ("%s: warning: removing old signatures " + "from the first input SIS file" % pgmname) + ctrlfield.setsignatures([]) + + for n in xrange(1, len(insis)): + # Append SISDataUnit SISFields into SISData array of the first SIS file. + insis[0].Data.DataUnits.append(insis[n].Data.DataUnits[0]) + + # Set data index in SISController SISField. + insis[n].Controller.Data.DataIndex.DataIndex = n + + # Embed SISController into SISInstallBlock of the first SIS file. + ctrlfield.InstallBlock.EmbeddedSISFiles.append(insis[n].Controller.Data) + + # Calculate a signature of the modified SISController. + string = ctrlfield.tostring() + string = sisfield.stripheaderandpadding(string) + signature, algoid = sisfile.signstring(privkeydata, passphrase, string) + + # Create a SISCertificateChain SISField from certificate data. + sf1 = sisfield.SISBlob(Data = cryptutil.certtobinary(certdata)) + sf2 = sisfield.SISCertificateChain(CertificateData = sf1) + + # Create a SISSignature SISField from calculated signature. + sf3 = sisfield.SISString(String = algoid) + sf4 = sisfield.SISSignatureAlgorithm(AlgorithmIdentifier = sf3) + sf5 = sisfield.SISBlob(Data = signature) + sf6 = sisfield.SISSignature(SignatureAlgorithm = sf4, SignatureData = sf5) + + # Create a new SISSignatureCertificateChain SISField. + sa = sisfield.SISArray(SISFields = [sf6]) + sf7 = sisfield.SISSignatureCertificateChain(Signatures = sa, + CertificateChain = sf2) + + # Set certificate, restore data index. + ctrlfield.Signature0 = sf7 + ctrlfield.DataIndex = didxfield + + # Convert SISFields to string. + outstring = insis[0].tostring() + + # Write output SIS file. + f = file(outfile, "wb") + f.write(uids) + f.write(outstring) + f.close()