Bug 1531165: Deprecate old signing formats, and add support for sha2signcode-v2 r=aki
authorChris AtLee <catlee@mozilla.com>
Thu, 28 Mar 2019 10:05:02 +0000
changeset 8470 e7cfca9a2b6e
parent 8469 8b59e64ffde0
child 8471 a41a482b7876
push id6193
push usercatlee@mozilla.com
push dateMon, 01 Apr 2019 17:12:41 +0000
reviewersaki
bugs1531165
Bug 1531165: Deprecate old signing formats, and add support for sha2signcode-v2 r=aki Differential Revision: https://phabricator.services.mozilla.com/D23593
lib/python/signing/utils.py
release/signing/signing.ini.template
release/signing/signscript.py
--- a/lib/python/signing/utils.py
+++ b/lib/python/signing/utils.py
@@ -14,91 +14,52 @@ from release.info import fileInfo
 import logging
 log = logging.getLogger(__name__)
 
 MAC_DESIGNATED_REQUIREMENTS = """\
 =designated => ( (anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9] ) or (anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] and certificate leaf[field.1.2.840.113635.100.6.1.13] and certificate leaf[subject.OU] = "%(subject_ou)s"))
 """
 
 
-def signfile(filename, keydir, fake=False, passphrase=None, timestamp=True):
-    """Perform authenticode signing on the given file with keys in keydir.
+def osslsigncode_signfile(inputfile, outputfile, keydir, fake=False,
+                          passphrase=None, timestamp='rfc3161', digest='sha2',
+                          includedummycert=False):
+    """
+    Perform Authenticode signing on "inputfile", writing a signed version to
+    "outputfile". includedummycert controls the inclusion of the extra cert
+    from bug 1261140, intended for stub installers only.
 
     If passphrase is set, it will be sent as stdin to the process.
 
     If fake is True, then don't actually sign anything, just sleep for a
     second to simulate signing time.
 
-    If timestamp is True, then a signed timestamp will be included with the
+    If timestamp is 'rfc3161', then an RFC3161 timestamp will be included with
+    the signature.  If timestamp is True, then an old style timestamp will be
+    included with the signature.
+
+    digest should be set to 'sha1' or 'sha2', and defaults to 'sha2'.
     signature."""
     if fake:
         time.sleep(1)
         return
-    basename = os.path.basename(filename)
-    dirname = os.path.dirname(filename)
-    stdout = tempfile.TemporaryFile()
-    command = ['signcode',
-               '-spc', '%s/MozAuthenticode.spc' % keydir,
-               '-v', '%s/MozAuthenticode.pvk' % keydir,
-               ]
-    if timestamp:
-        command.extend(
-            ['-t', 'http://timestamp.verisign.com/scripts/timestamp.dll'])
-    command.extend([
-        '-i', 'http://www.mozilla.com',
-        '-a', 'sha1',
-        # Try 5 times, and wait 60 seconds between tries
-        '-tr', '5',
-        '-tw', '60',
-        basename])
-    try:
-        log.debug("Running %s", command)
-        proc = Popen(
-            command, cwd=dirname, stdout=stdout, stderr=STDOUT, stdin=PIPE)
-        if passphrase:
-            proc.stdin.write(passphrase)
-        proc.stdin.close()
-        if proc.wait() != 0:
-            raise ValueError("signcode didn't return with 0")
-        stdout.seek(0)
-        data = stdout.read()
-        # Make sure that the command output "Succeeded".  Sometimes signcode
-        # returns with 0, but doesn't output "Succeeded", which in the past has
-        # meant that the file has been signed, but is missing a timestmap.
-        if data.strip() != "Succeeded" and "Success" not in data:
-            raise ValueError("signcode didn't report success")
-    except:
-        stdout.seek(0)
-        data = stdout.read()
-        log.exception(data)
-        raise
-
-
-def osslsigncode_signfile(inputfile, outputfile, keydir, fake=False, passphrase=None,
-                          timestamp=None, includedummycert=False):
-    """Perform Authenticode signing on "inputfile", writing a signed version
-    to "outputfile". includedummycert controls the inclusion of the extra cert from bug
-    1261140, intended for stub installers only. See signfile() for a description
-    of other arguments.
-    """
-    if fake:
-        time.sleep(1)
-        return
 
     stdout = tempfile.TemporaryFile()
     args = [
         '-certs', '%s/MozAuthenticode.spc' % keydir,
         '-key', '%s/MozAuthenticode.pvk' % keydir,
         '-i', 'https://www.mozilla.com',
-        '-h', 'sha2',
+        '-h', digest,
         '-in', inputfile,
         '-out', outputfile,
     ]
-    if timestamp:
+    if timestamp == 'rfc3161':
         args.extend(['-ts', 'http://timestamp.digicert.com'])
+    elif timestamp:
+        args.extend(['-t', 'http://timestamp.verisign.com/scripts/timestamp.dll'])
     # requires osslsigncode >= 1.6
     if includedummycert:
         args.extend(['-ac', '%s/StubDummy.cert' % keydir])
 
     try:
         import pexpect
         proc = pexpect.spawn('osslsigncode', args)
         # We use logfile_read because we only want stdout/stderr, _not_ stdin.
@@ -149,51 +110,16 @@ I am ur signature!
         data = stdout.read()
     except:
         stdout.seek(0)
         data = stdout.read()
         log.exception(data)
         raise
 
 
-def emevoucher_signfile(inputfile, outputfile, key, chain, fake=False, passphrase=None):
-    """Perform SMIME signing on "inputfile", writing a signed version
-    to "outputfile", using passed in "key". This is necessary for the EME voucher.
-
-    If fake is True, generate a fake signature and sleep for a bit.
-
-    If passphrase is set, it will be passed to gpg on stdin
-    """
-    if fake:
-        time.sleep(1)
-        return
-
-    stdout = tempfile.TemporaryFile()
-    args = ['smime', '-sign', '-in', inputfile,
-            '-out', outputfile, '-signer', key,
-            '-certfile', chain,
-            '-md', 'sha256', '-binary', '-nodetach',
-            '-outform', 'DER']
-
-    try:
-        import pexpect
-        proc = pexpect.spawn("openssl", args)
-        # We use logfile_read because we only want stdout/stderr, _not_ stdin.
-        proc.logfile_read = stdout
-        proc.expect('Enter pass phrase')
-        proc.sendline(passphrase)
-        if proc.wait() != 0:
-            raise ValueError("openssl didn't return 0")
-    except:
-        stdout.seek(0)
-        data = stdout.read()
-        log.exception(data)
-        raise
-
-
 def mar_signfile(inputfile, outputfile, mar_cmd, fake=False, passphrase=None):
     # Now sign it
     if isinstance(mar_cmd, basestring):
         mar_cmd = shlex.split(mar_cmd)
     else:
         mar_cmd = mar_cmd[:]
     command = mar_cmd + [inputfile, outputfile]
     log.info('Running %s', command)
--- a/release/signing/signing.ini.template
+++ b/release/signing/signing.ini.template
@@ -21,22 +21,20 @@ allowed_ips = 0.0.0.0/0
 # of regular expressions
 allowed_filenames = .*
 # Minimum filesize that we'll sign
 min_filesize = 10
 # Maximum filesize, per format. 52428800 = 50MB, 524288000 = 500MB
 max_filesize_gpg = 524288000
 max_filesize_dmg = 52428800
 max_filesize_mar = 52428800
-max_filesize_mar_sha384 = 52428800
-max_filesize_signcode = 52428800
-max_filesize_osslsigncode = 52428800
 max_filesize_sha2signcode = 52428800
 max_filesize_sha2signcodestub = 52428800
-max_filesize_emevoucher = 52428800
+max_filesize_sha2signcode-v2 = 52428800
+max_filesize_sha2signcodestub-v2 = 52428800
 max_filesize_widevine = 52428800
 max_filesize_widevine_blessed = 52428800
 # Secret for signing tokens. This should be kept private!
 # It should also be the same on all equivalent signing servers.
 token_secret = secretstring
 # Any key starting with 'token_secret' is also valid, to allow supporting
 # multiple token secrets at the same time (to make it possible to transitioning
 # to new secrets without downtime). New tokens are generated with the
@@ -54,53 +52,37 @@ max_token_age = 3600
 [paths]
 # Where we store signed files
 signed_dir = signed-files
 # Where we store unsigned files
 unsigned_dir = unsigned-files
 
 [signing]
 # What signing formats we support
-formats = mar,mar_sha384,gpg,sha2signcode,sha2signcodestub,signcode,osslsigncode,emevoucher,widevine,widevine_blessed,jar,focus-jar
+formats = mar,gpg,sha2signcode,sha2signcodestub,sha2signcode-v2,sha2signcodestub-v2,widevine,widevine_blessed
 # Which script to run to sign files
 signscript = python ./signscript.py -c signing.ini
 # How many files to sign at once
 concurrency = 4
 # Test files for the various signing formats
 # signscript will be run on each of these on startup to test that passphrases
 # have been entered correctly
-testfile_signcode = test.exe
-testfile_osslsigncode = test64.exe
 testfile_sha2signcode = test.exe
 testfile_sha2signcodestub = test.exe
+testfile_sha2signcode-v2 = test.exe
+testfile_sha2signcodestub-v2 = test.exe
 testfile_mar = test.mar
-testfile_mar_sha384 = test.mar
 testfile_gpg = test.mar
-testfile_emevoucher = test.bin
 testfile_widevine = test.tar.gz
 testfile_widevine_blessed = test.exe
 
 [signscript]
 # Various settings for signscript. signing-server.py doesn't look in here
 # Where are MozAuthenticode.{pvk,spc} located
-signcode_keydir = /path/to/keys
-osslsigncode_keydir = /path/to/keys
 sha2signcode_keydir = /path/to/keys
 # Where is the gpg directory with our private key
 gpg_homedir = /path/to/.gpg
-jar_keystore = /path/to/jar/keystore
-jar_keyname = some-name
-jar_digestalg = SHA1
-jar_sigalg = SHA1withRSA
-focus_jar_keystore = /path/to/jar/keystore
-focus_jar_keyname = some-name
-focus_jar_digestalg = SHA-256
-focus_jar_sigalg = SHA256withRSA
-# Where is the eme voucher private key
-emevoucher_key = /path/to/cert.pem
-emevoucher_chain = /path/to/chain.pem
 # How to run mar
 mar_cmd = /path/to/signmar -d /path/to/nsscerts -n keyname -s
-mar_sha384_cmd = /path/to/signmar-sha384 -d /path/to/nsscerts -n keyname -s
 # widevine info
 widevine_key = /path/to/key.pem
 widevine_cert = /path/to/cert.der
 widevine_cmd = python /path/to/script --private_key %(widevine_key)s --certificate %(widevine_cert)s --input %(input)s --output_file %(output)s --flags %(blessed)s --prompt_passphrase
--- a/release/signing/signscript.py
+++ b/release/signing/signscript.py
@@ -7,42 +7,31 @@ import site
 site.addsitedir(os.path.join(os.path.dirname(__file__), "../../lib/python"))
 
 import logging
 import sys
 
 from util.file import copyfile, safe_unlink
 from signing.utils import shouldSign, signfile, osslsigncode_signfile
 from signing.utils import gpg_signfile, mar_signfile, dmg_signpackage
-from signing.utils import jar_signfile, emevoucher_signfile, widevine_signfile
+from signing.utils import widevine_signfile
 
 if __name__ == '__main__':
     from optparse import OptionParser
     from ConfigParser import RawConfigParser
 
     parser = OptionParser(__doc__)
     parser.set_defaults(
         fake=False,
         signcode_keydir=None,
         gpg_homedir=None,
         loglevel=logging.INFO,
         configfile=None,
         mar_cmd=None,
-        mar_sha384_cmd=None,
         signcode_timestamp=None,
-        jar_keystore=None,
-        jar_keyname=None,
-        jar_sigalg=None,
-        jar_digestalg=None,
-        focus_jar_keystore=None,
-        focus_jar_keyname=None,
-        focus_jar_sigalg=None,
-        focus_jar_digestalg=None,
-        emevoucher_key=None,
-        emevoucher_chain=None,
         widevine_key=None,
         widevine_cert=None,
         widevine_cmd=None,
     )
     parser.add_option("--keydir", dest="signcode_keydir",
                       help="where MozAuthenticode.spc, MozAuthenticode.spk can be found")
     parser.add_option("--gpgdir", dest="gpg_homedir",
                       help="where the gpg keyrings are")
@@ -51,36 +40,16 @@ if __name__ == '__main__':
     parser.add_option("--dmgkeychain", dest="dmg_keychain",
                       help="the mac signing keydir")
     parser.add_option("--fake", dest="fake", action="store_true",
                       help="do fake signing")
     parser.add_option("-c", "--config", dest="configfile",
                       help="config file to use")
     parser.add_option("--signcode_disable_timestamp",
                       dest="signcode_timestamp", action="store_false")
-    parser.add_option("--jar_keystore", dest="jar_keystore",
-                      help="keystore for signing jar_")
-    parser.add_option("--jar_keyname", dest="jar_keyname",
-                      help="which key to use from jar_keystore")
-    parser.add_option("--jar_digestalg", dest="jar_digestalg",
-                      help="which digest algorithm to use for signing jar files")
-    parser.add_option("--jar_sigalg", dest="jar_sigalg",
-                      help="which signature algorithm to use for signing jar files")
-    parser.add_option("--focus_jar_keystore", dest="focus_jar_keystore",
-                      help="keystore for signing Firefox Focus")
-    parser.add_option("--focus_jar_keyname", dest="focus_jar_keyname",
-                      help="which key to use from focus_jar_keystore")
-    parser.add_option("--focus_jar_digestalg", dest="focus_jar_digestalg",
-                      help="which digest algorithm to use for signing Firefox Focus")
-    parser.add_option("--focus_jar_sigalg", dest="focus_jar_sigalg",
-                      help="which signature algorithm to use for signing Firefox Focus")
-    parser.add_option("--emevoucher_key", dest="emevoucher_key",
-                      help="The certificate to use for signing the eme voucher")
-    parser.add_option("--emevoucher_chain", dest="emevoucher_chain",
-                      help="Certificate chain to include in EME voucher signatures")
     parser.add_option("--widevine_key", dest="widevine_key",
                       help="The key to use for signing widevine files")
     parser.add_option("--widevine_cert", dest="widevine_cert",
                       help="Certificate to use for signing widevine files")
     parser.add_option("--widevine_cmd", dest="widevine_cmd",
                       help="Command to use for signing widevine files")
     parser.add_option(
         "-v", action="store_const", dest="loglevel", const=logging.DEBUG)
@@ -108,100 +77,59 @@ if __name__ == '__main__':
     format_, inputfile, destfile, filename = args
 
     tmpfile = destfile + ".tmp"
 
     passphrase = sys.stdin.read().strip()
     if passphrase == '':
         passphrase = None
 
-    if format_ == "signcode":
-        if not options.signcode_keydir:
-            parser.error("keydir required when format is signcode")
-        copyfile(inputfile, tmpfile)
-        if shouldSign(filename):
-            signfile(tmpfile, options.signcode_keydir, options.fake,
-                     passphrase, timestamp=options.signcode_timestamp)
-        else:
-            parser.error("Invalid file for signing: %s" % filename)
-            sys.exit(1)
-    elif format_ == "osslsigncode":
-        safe_unlink(tmpfile)
-        if not options.signcode_keydir:
-            parser.error("keydir required when format is osslsigncode")
-        if shouldSign(filename):
-            osslsigncode_signfile(inputfile, tmpfile, options.signcode_keydir, options.fake,
-                     passphrase, timestamp=options.signcode_timestamp)
-        else:
-            parser.error("Invalid file for signing: %s" % filename)
-            sys.exit(1)
-    elif format_ in ("sha2signcode", "sha2signcodestub"):
+    if format_.startswith('sha2signcode'):
         safe_unlink(tmpfile)
         # add zipfile support
         if not options.sha2signcode_keydir:
             parser.error("sha2signcode_keydir required when format is sha2signcode")
         includedummycert = False
-        if format_ == "sha2signcodestub":
+        if format_.startswith("sha2signcodestub"):
             includedummycert = True
+        if format_.endswith('-v2'):
+            timestamp = 'rfc3161' if options.signcode_timestamp else False
+            digest = 'sha2'
+        else:
+            timestamp = True if options.signcode_timestamp else False
+            digest = 'sha1'
         if shouldSign(filename):
-            # osslsigncode_signfile is used here because "sha2 signing" isn't
-            # a different signing process, it's just signing using the same
-            # tools/process with a different cert.
-            osslsigncode_signfile(inputfile, tmpfile, options.sha2signcode_keydir, options.fake,
-                     passphrase, timestamp=options.signcode_timestamp, includedummycert=includedummycert)
+            osslsigncode_signfile(inputfile, tmpfile,
+                                  options.sha2signcode_keydir, options.fake,
+                                  passphrase,
+                                  timestamp=timestamp,
+                                  includedummycert=includedummycert,
+                                  digest=digest)
         else:
             parser.error("Invalid file for signing: %s" % filename)
             sys.exit(1)
     elif format_ == "gpg":
         if not options.gpg_homedir:
             parser.error("gpgdir required when format is gpg")
         safe_unlink(tmpfile)
         gpg_signfile(
             inputfile, tmpfile, options.gpg_homedir, options.fake, passphrase)
-    elif format_ == "emevoucher":
-        safe_unlink(tmpfile)
-        emevoucher_signfile(
-            inputfile, tmpfile, options.emevoucher_key,
-            options.emevoucher_chain, options.fake, passphrase)
     elif format_ == "mar":
         if not options.mar_cmd:
             parser.error("mar_cmd is required when format is mar")
         safe_unlink(tmpfile)
         mar_signfile(
             inputfile, tmpfile, options.mar_cmd, options.fake, passphrase)
-    elif format_ == "mar_sha384":
-        if not options.mar_sha384_cmd:
-            parser.error("mar_sha384_cmd is required when format is mar_sha384")
-        safe_unlink(tmpfile)
-        mar_signfile(
-            inputfile, tmpfile, options.mar_sha384_cmd, options.fake, passphrase)
     elif format_ == "dmg":
         if not options.dmg_keychain:
             parser.error("dmg_keychain required when format is dmg")
         if not options.mac_id:
             parser.error("mac_id required when format is dmg")
         safe_unlink(tmpfile)
         dmg_signpackage(inputfile, tmpfile, options.dmg_keychain, options.mac_id, options.mac_cert_subject_ou, options.fake, passphrase)
-    elif format_ in ("jar", "focus-jar"):
-        if format_ == "jar":
-            keystore, keystore_config_name, keyname, keyname_config_name, digestalg, sigalg = (
-                options.jar_keystore, "jar_keystore", options.jar_keyname, "jar_keyname",
-                options.jar_digestalg, options.jar_sigalg
-            )
-        else:
-            keystore, keystore_config_name, keyname, keyname_config_name, digestalg, sigalg = (
-                options.focus_jar_keystore, "focus_jar_keystore", options.focus_jar_keyname, "focus_jar_keystore",
-                options.focus_jar_digestalg, options.focus_jar_sigalg
-            )
-        if not keystore:
-            parser.error("%s required when format is %s" % (keystore_config_name, format_))
-        if not keyname:
-            parser.error("%s required when format is %s" % (keyname_config_name, format_))
-        copyfile(inputfile, tmpfile)
-        jar_signfile(tmpfile, keystore, keyname, digestalg, sigalg, options.fake, passphrase)
     elif format_ in ("widevine", "widevine_blessed"):
         safe_unlink(tmpfile)
         if not options.widevine_key:
             parser.error("widevine_key required when format is %s" % format_)
         blessed = "0"
         if format_ == "widevine_blessed":
             blessed = "1"
         widevine_signfile(