~ubuntu-branches/ubuntu/utopic/ensymble/utopic

« back to all changes in this revision

Viewing changes to cmd_mergesis.py

  • Committer: Package Import Robot
  • Author(s): Logan Rosen
  • Date: 2013-05-09 17:13:58 UTC
  • mfrom: (7.1.1 sid)
  • Revision ID: package-import@ubuntu.com-20130509171358-2jblsah6kes5lhub
Tags: 0.29-1ubuntu1
* Merge from Debian unstable. Remaining changes:
  - debian/control X-Python-Version: Use >= 2.6, instead of now gone "all"
    keyword.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
# -*- coding: utf-8 -*-
3
 
 
4
 
##############################################################################
5
 
# cmd_mergesis.py - Ensymble command line tool, mergesis command
6
 
# Copyright 2006, 2007 Jussi Ylänen
7
 
#
8
 
# This file is part of Ensymble developer utilities for Symbian OS(TM).
9
 
#
10
 
# Ensymble is free software; you can redistribute it and/or modify
11
 
# it under the terms of the GNU General Public License as published by
12
 
# the Free Software Foundation; either version 2 of the License, or
13
 
# (at your option) any later version.
14
 
#
15
 
# Ensymble is distributed in the hope that it will be useful,
16
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 
# GNU General Public License for more details.
19
 
#
20
 
# You should have received a copy of the GNU General Public License
21
 
# along with Ensymble; if not, write to the Free Software
22
 
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23
 
##############################################################################
24
 
 
25
 
import sys
26
 
import os
27
 
import getopt
28
 
import getpass
29
 
import locale
30
 
 
31
 
import sisfile
32
 
import sisfield
33
 
import cryptutil
34
 
 
35
 
 
36
 
##############################################################################
37
 
# Help texts
38
 
##############################################################################
39
 
 
40
 
shorthelp = 'Merge several SIS packages into one'
41
 
longhelp  = '''mergesis
42
 
    [--cert=mycert.cer] [--privkey=mykey.key] [--passphrase=12345]
43
 
    [--encoding=terminal,filesystem] [--verbose]
44
 
    <infile> [mergefile]... <outfile>
45
 
 
46
 
Merge several SIS packages into one and sign the resulting SIS file with
47
 
the certificate provided. The first SIS file is used as the base file and
48
 
the remaining SIS files are added as unconditional embedded SIS files
49
 
into it. Any signatures present in the first SIS file are stripped.
50
 
 
51
 
Options:
52
 
    infile      - Path of the base SIS file
53
 
    mergefile   - Path of SIS file(s) to add to the base SIS file
54
 
    outfile     - Path of the resulting SIS file
55
 
    cert        - Certificate to use for signing (PEM format)
56
 
    privkey     - Private key of the certificate (PEM format)
57
 
    passphrase  - Pass phrase of the private key (insecure, use stdin instead)
58
 
    encoding    - Local character encodings for terminal and filesystem
59
 
    verbose     - Print extra statistics
60
 
 
61
 
Merging SIS files that already contain other SIS files is not supported.
62
 
'''
63
 
 
64
 
 
65
 
##############################################################################
66
 
# Parameters
67
 
##############################################################################
68
 
 
69
 
MAXPASSPHRASELENGTH     = 256
70
 
MAXCERTIFICATELENGTH    = 65536
71
 
MAXPRIVATEKEYLENGTH     = 65536
72
 
MAXSISFILESIZE          = 1024 * 1024 * 8   # Eight megabytes
73
 
 
74
 
 
75
 
##############################################################################
76
 
# Global variables
77
 
##############################################################################
78
 
 
79
 
debug = False
80
 
 
81
 
 
82
 
##############################################################################
83
 
# Public module-level functions
84
 
##############################################################################
85
 
 
86
 
def run(pgmname, argv):
87
 
    global debug
88
 
 
89
 
    # Determine system character encodings.
90
 
    try:
91
 
        # getdefaultlocale() may sometimes return None.
92
 
        # Fall back to ASCII encoding in that case.
93
 
        terminalenc = locale.getdefaultlocale()[1] + ""
94
 
    except TypeError:
95
 
        # Invalid locale, fall back to ASCII terminal encoding.
96
 
        terminalenc = "ascii"
97
 
 
98
 
    try:
99
 
        # sys.getfilesystemencoding() was introduced in Python v2.3 and
100
 
        # it can sometimes return None. Fall back to ASCII if something
101
 
        # goes wrong.
102
 
        filesystemenc = sys.getfilesystemencoding() + ""
103
 
    except (AttributeError, TypeError):
104
 
        filesystemenc = "ascii"
105
 
 
106
 
    try:
107
 
        gopt = getopt.gnu_getopt
108
 
    except:
109
 
        # Python <v2.3, GNU-style parameter ordering not supported.
110
 
        gopt = getopt.getopt
111
 
 
112
 
    # Parse command line arguments.
113
 
    short_opts = "a:k:p:e:vh"
114
 
    long_opts = [
115
 
        "cert=", "privkey=", "passphrase=",
116
 
        "encoding=", "verbose", "debug", "help"
117
 
    ]
118
 
    args = gopt(argv, short_opts, long_opts)
119
 
 
120
 
    opts = dict(args[0])
121
 
    pargs = args[1]
122
 
 
123
 
    if len(pargs) < 2:
124
 
        raise ValueError("wrong number of arguments")
125
 
 
126
 
    # Override character encoding of command line and filesystem.
127
 
    encs = opts.get("--encoding", opts.get("-e", "%s,%s" % (terminalenc,
128
 
                                                            filesystemenc)))
129
 
    try:
130
 
        terminalenc, filesystemenc = encs.split(",")
131
 
    except (ValueError, TypeError):
132
 
        raise ValueError("invalid encoding string '%s'" % encs)
133
 
 
134
 
    # Get input SIS file names.
135
 
    infiles = [f.decode(terminalenc).encode(filesystemenc) for f in pargs[:-1]]
136
 
 
137
 
    # Determine output SIS file name.
138
 
    outfile = pargs[-1].decode(terminalenc).encode(filesystemenc)
139
 
    if os.path.isdir(outfile):
140
 
        # Output to directory, use input file name.
141
 
        outfile = os.path.join(outfile, os.path.basename(infiles[0]))
142
 
 
143
 
    # Get certificate and its private key file names.
144
 
    cert = opts.get("--cert", opts.get("-a", None))
145
 
    privkey = opts.get("--privkey", opts.get("-k", None))
146
 
    if cert != None and privkey != None:
147
 
        # Convert file names from terminal encoding to filesystem encoding.
148
 
        cert = cert.decode(terminalenc).encode(filesystemenc)
149
 
        privkey = privkey.decode(terminalenc).encode(filesystemenc)
150
 
 
151
 
        # Read certificate file.
152
 
        f = file(cert, "rb")
153
 
        certdata = f.read(MAXCERTIFICATELENGTH + 1)
154
 
        f.close()
155
 
 
156
 
        if len(certdata) > MAXCERTIFICATELENGTH:
157
 
            raise ValueError("certificate file too large")
158
 
 
159
 
        # Read private key file.
160
 
        f = file(privkey, "rb")
161
 
        privkeydata = f.read(MAXPRIVATEKEYLENGTH + 1)
162
 
        f.close()
163
 
 
164
 
        if len(privkeydata) > MAXPRIVATEKEYLENGTH:
165
 
            raise ValueError("private key file too large")
166
 
    elif cert == None and privkey == None:
167
 
        # No certificate given, use the Ensymble default certificate.
168
 
        # defaultcert.py is not imported when not needed. This speeds
169
 
        # up program start-up a little.
170
 
        import defaultcert
171
 
        certdata = defaultcert.cert
172
 
        privkeydata = defaultcert.privkey
173
 
 
174
 
        print ("%s: warning: no certificate given, using "
175
 
               "insecure built-in one" % pgmname)
176
 
    else:
177
 
        raise ValueError("missing certificate or private key")
178
 
 
179
 
    # Get pass phrase. Pass phrase remains in terminal encoding.
180
 
    passphrase = opts.get("--passphrase", opts.get("-p", None))
181
 
    if passphrase == None and privkey != None:
182
 
        # Private key given without "--passphrase" option, ask it.
183
 
        if sys.stdin.isatty():
184
 
            # Standard input is a TTY, ask password interactively.
185
 
            passphrase = getpass.getpass("Enter private key pass phrase:")
186
 
        else:
187
 
            # Not connected to a TTY, read stdin non-interactively instead.
188
 
            passphrase = sys.stdin.read(MAXPASSPHRASELENGTH + 1)
189
 
 
190
 
            if len(passphrase) > MAXPASSPHRASELENGTH:
191
 
                raise ValueError("pass phrase too long")
192
 
 
193
 
            passphrase = passphrase.strip()
194
 
 
195
 
    # Determine verbosity.
196
 
    verbose = False
197
 
    if "--verbose" in opts.keys() or "-v" in opts.keys():
198
 
        verbose = True
199
 
 
200
 
    # Determine if debug output is requested.
201
 
    if "--debug" in opts.keys():
202
 
        debug = True
203
 
 
204
 
        # Enable debug output for OpenSSL-related functions.
205
 
        cryptutil.setdebug(True)
206
 
 
207
 
    # Ingredients for successful SIS generation:
208
 
    #
209
 
    # terminalenc          Terminal character encoding (autodetected)
210
 
    # filesystemenc        File system name encoding (autodetected)
211
 
    # infiles              A list of input SIS file names, filesystemenc encoded
212
 
    # outfile              Output SIS file name, filesystemenc encoded
213
 
    # cert                 Certificate in PEM format
214
 
    # privkey              Certificate private key in PEM format
215
 
    # passphrase           Pass phrase of priv. key, terminalenc encoded string
216
 
    # verbose              Boolean indicating verbose terminal output
217
 
 
218
 
    if verbose:
219
 
        print
220
 
        print "Input SIS files   %s"        % " ".join(
221
 
            [f.decode(filesystemenc).encode(terminalenc) for f in infiles])
222
 
        print "Output SIS file   %s"        % (
223
 
            outfile.decode(filesystemenc).encode(terminalenc))
224
 
        print "Certificate       %s"        % ((cert and
225
 
            cert.decode(filesystemenc).encode(terminalenc)) or "<default>")
226
 
        print "Private key       %s"        % ((privkey and
227
 
            privkey.decode(filesystemenc).encode(terminalenc)) or "<default>")
228
 
        print
229
 
 
230
 
    insis = []
231
 
    for n in xrange(len(infiles)):
232
 
        # Read input SIS files.
233
 
        f = file(infiles[n], "rb")
234
 
        instring = f.read(MAXSISFILESIZE + 1)
235
 
        f.close()
236
 
 
237
 
        if len(instring) > MAXSISFILESIZE:
238
 
            raise ValueError("%s: input SIS file too large" % infiles[n])
239
 
 
240
 
        if n == 0:
241
 
            # Store UIDs for later use.
242
 
            uids = instring[:16]    # UID1, UID2, UID3 and UIDCRC
243
 
 
244
 
        # Convert input SIS file to SISFields.
245
 
        sf, rlen = sisfield.SISField(instring[16:], False)
246
 
 
247
 
        # Ignore extra bytes after SIS file.
248
 
        if len(instring) > (rlen + 16):
249
 
            print ("%s: %s: warning: %d extra bytes after SIS file (ignored)" %
250
 
                   (pgmname, infiles[n], (len(instring) - (rlen + 16))))
251
 
 
252
 
        # Try to release some memory early.
253
 
        del instring
254
 
 
255
 
        # Check that there are no embedded SIS files.
256
 
        if len(sf.Data.DataUnits) > 1:
257
 
            raise ValueError("%s: input SIS file contains "
258
 
                             "embedded SIS files" % infiles[n])
259
 
 
260
 
        insis.append(sf)
261
 
 
262
 
    # Temporarily remove the SISDataIndex SISField from the first SISController.
263
 
    ctrlfield = insis[0].Controller.Data
264
 
    didxfield = ctrlfield.DataIndex
265
 
    ctrlfield.DataIndex = None
266
 
 
267
 
    # Remove old signatures from the first SIS file.
268
 
    if len(ctrlfield.getsignatures()) > 0:
269
 
        print ("%s: warning: removing old signatures "
270
 
               "from the first input SIS file" % pgmname)
271
 
        ctrlfield.setsignatures([])
272
 
 
273
 
    for n in xrange(1, len(insis)):
274
 
        # Append SISDataUnit SISFields into SISData array of the first SIS file.
275
 
        insis[0].Data.DataUnits.append(insis[n].Data.DataUnits[0])
276
 
 
277
 
        # Set data index in SISController SISField.
278
 
        insis[n].Controller.Data.DataIndex.DataIndex = n
279
 
 
280
 
        # Embed SISController into SISInstallBlock of the first SIS file.
281
 
        ctrlfield.InstallBlock.EmbeddedSISFiles.append(insis[n].Controller.Data)
282
 
 
283
 
    # Calculate a signature of the modified SISController.
284
 
    string = ctrlfield.tostring()
285
 
    string = sisfield.stripheaderandpadding(string)
286
 
    signature, algoid = sisfile.signstring(privkeydata, passphrase, string)
287
 
 
288
 
    # Create a SISCertificateChain SISField from certificate data.
289
 
    sf1 = sisfield.SISBlob(Data = cryptutil.certtobinary(certdata))
290
 
    sf2 = sisfield.SISCertificateChain(CertificateData = sf1)
291
 
 
292
 
    # Create a SISSignature SISField from calculated signature.
293
 
    sf3 = sisfield.SISString(String = algoid)
294
 
    sf4 = sisfield.SISSignatureAlgorithm(AlgorithmIdentifier = sf3)
295
 
    sf5 = sisfield.SISBlob(Data = signature)
296
 
    sf6 = sisfield.SISSignature(SignatureAlgorithm = sf4, SignatureData = sf5)
297
 
 
298
 
    # Create a new SISSignatureCertificateChain SISField.
299
 
    sa  = sisfield.SISArray(SISFields = [sf6])
300
 
    sf7 = sisfield.SISSignatureCertificateChain(Signatures = sa,
301
 
                                                CertificateChain = sf2)
302
 
 
303
 
    # Set certificate, restore data index.
304
 
    ctrlfield.Signature0 = sf7
305
 
    ctrlfield.DataIndex = didxfield
306
 
 
307
 
    # Convert SISFields to string.
308
 
    outstring = insis[0].tostring()
309
 
 
310
 
    # Write output SIS file.
311
 
    f = file(outfile, "wb")
312
 
    f.write(uids)
313
 
    f.write(outstring)
314
 
    f.close()