From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001
From: "Matt A. Tobin" <mattatobin@localhost.localdomain>
Date: Fri, 2 Feb 2018 04:16:08 -0500
Subject: Add m-esr52 at 52.6.0

---
 .../unit/test_signed_apps/gentestfiles/README.md   |  18 ++
 .../gentestfiles/create_test_files.sh              | 213 +++++++++++++++++++++
 .../test_signed_apps/gentestfiles/nss_ctypes.py    | 129 +++++++++++++
 .../test_signed_apps/gentestfiles/sign_b2g_app.py  | 174 +++++++++++++++++
 .../gentestfiles/unsigned_app_1/icon-128.png       | Bin 0 -> 1633 bytes
 .../gentestfiles/unsigned_app_1/index.html         |   6 +
 .../gentestfiles/unsigned_app_1/manifest.webapp    |   8 +
 .../gentestfiles/unsigned_app_origin/icon-128.png  | Bin 0 -> 1633 bytes
 .../gentestfiles/unsigned_app_origin/index.html    |   6 +
 .../unsigned_app_origin/manifest.webapp            |  10 +
 .../unsigned_app_origin_toolkit_webapps/index.html |  10 +
 .../manifest.webapp                                |   9 +
 .../test_signed_apps/privileged-app-test-1.0.zip   | Bin 0 -> 23169 bytes
 .../test-privileged-app-test-1.0.zip               | Bin 0 -> 22750 bytes
 .../tests/unit/test_signed_apps/trusted_ca1.der    | Bin 0 -> 898 bytes
 .../unit/test_signed_apps/unknown_issuer_app_1.zip | Bin 0 -> 4220 bytes
 .../tests/unit/test_signed_apps/unsigned_app_1.zip | Bin 0 -> 2282 bytes
 .../tests/unit/test_signed_apps/valid_app_1.zip    | Bin 0 -> 4222 bytes
 18 files changed, 583 insertions(+)
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/README.md
 create mode 100755 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/create_test_files.sh
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/nss_ctypes.py
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/sign_b2g_app.py
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/icon-128.png
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/index.html
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/manifest.webapp
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/icon-128.png
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/index.html
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/manifest.webapp
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/index.html
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/manifest.webapp
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/privileged-app-test-1.0.zip
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/test-privileged-app-test-1.0.zip
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/trusted_ca1.der
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/unknown_issuer_app_1.zip
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/unsigned_app_1.zip
 create mode 100644 security/manager/ssl/tests/unit/test_signed_apps/valid_app_1.zip

(limited to 'security/manager/ssl/tests/unit/test_signed_apps')

diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/README.md b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/README.md
new file mode 100644
index 000000000..5c6201534
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/README.md
@@ -0,0 +1,18 @@
+This file contains the scripts and binary files needed to regenerate the signed
+files used on the signed apps test.
+
+Prerequisites:
+
+* NSS 3.4 or higher.
+* Python 2.7 (should work with 2.6 also)
+* Bash
+
+Usage:
+
+Run
+
+./create_test_files.sh
+
+The new test files will be created at the ./testApps directory. Just copy the
+contents of this directory to the test directory (dom/apps/tests/signed and
+security/manager/ssl/tests/unit) to use the new files.
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/create_test_files.sh b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/create_test_files.sh
new file mode 100755
index 000000000..e211c3685
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/create_test_files.sh
@@ -0,0 +1,213 @@
+#!/bin/bash
+
+export NSS_DEFAULT_DB_TYPE=sql
+
+export BASE_PATH=`dirname $0`
+export SIGN_SCR_LOC=.
+export APPS_TEST_LOC=../../../../../../../dom/apps/tests/signed
+export TOOLKIT_WEBAPPS_TEST_LOC=../../../../../../../toolkit/webapps/tests/data/
+
+# Creates the entry zip files (unsigned apps) from the source directories
+packageApps() {
+APPS="unsigned_app_1 unsigned_app_origin unsigned_app_origin_toolkit_webapps"
+OLD_PWD=`pwd`
+cd ${BASE_PATH}
+for i in $APPS
+do
+  echo "Creating $i.zip"
+  cd $i && zip -r ../$i.zip . && cd ..
+done
+cd ${OLD_PWD}
+}
+
+
+# Function to create a signing database
+# Parameters:
+# $1: Output directory (where the DB will be created)
+createDb() {
+
+  db=$1
+
+  mkdir -p $db
+
+  # Insecure by design, so... please don't use this for anything serious
+  passwordfile=$db/passwordfile
+
+  echo insecurepassword > $passwordfile
+  certutil -d $db -N -f $passwordfile 2>&1 >/dev/null
+
+}
+
+# Add a CA cert and a signing cert to the database
+# Arguments:
+#   $1: DB directory
+#   $2: CA CN (don't include the CN=, just the value)
+#   $3: Signing Cert CN (don't include the CN=, just the value)
+#   $4: CA short name (don't use spaces!)
+#   $5: Signing Cert short name (don't use spaces!)
+addCerts() {
+  org="O=Examplla Corporation,L=Mountain View,ST=CA,C=US"
+  ca_subj="CN=${2},${org}"
+  ee_subj="CN=${3},${org}"
+
+  noisefile=/tmp/noise.$$
+  head -c 32 /dev/urandom > $noisefile
+
+  ca_responses=/tmp/caresponses.$$
+  ee_responses=/tmp/earesponses
+
+  echo y >  $ca_responses # Is this a CA?
+  echo >>   $ca_responses # Accept default path length constraint (no constraint)
+  echo y >> $ca_responses # Is this a critical constraint?
+  echo n >  $ee_responses # Is this a CA?
+  echo >>   $ee_responses # Accept default path length constraint (no constraint)
+  echo y >> $ee_responses # Is this a critical constraint?
+
+  make_cert="certutil -d $db -f $passwordfile -S -g 2048 -Z SHA256 \
+                    -z $noisefile -y 3 -2 --extKeyUsage critical,codeSigning"
+  $make_cert -v 480 -n ${4}        -m 1 -s "$ca_subj" \
+      --keyUsage critical,certSigning      -t ",,CTu" -x < $ca_responses 2>&1 >/dev/null
+  $make_cert -v 240 -n ${5} -c ${4} -m 2 -s "$ee_subj" \
+      --keyUsage critical,digitalSignature -t ",,,"      < $ee_responses 2>&1 >/dev/null
+
+  # In case we want to inspect the generated certs
+
+  # Also, we'll need this one later on
+  certutil -d $db -L -n ${4} -r -o $db/${4}.der
+  certutil -d $db -L -n ${5} -r -o $db/${5}.der
+
+  rm -f $noisefile $ee_responses $ca_responses
+}
+
+
+# Signs an app
+# Parameters:
+# $1: Database directory
+# $2: Unsigned ZIP file path
+# $3: Signed ZIP file path
+# $4: Store ID for the signed App
+# $5: Version of the signed App
+# $6: Nickname of the signing certificate
+signApp() {
+
+  db=$1
+
+  # Once again, this is INSECURE. It doesn't matter here but
+  # DON'T use this for anything production related
+  passwordfile=$db/passwordfile
+
+  python ${BASE_PATH}/${SIGN_SCR_LOC}/sign_b2g_app.py -d $db -f $passwordfile \
+         -k ${6} -i ${2} -o ${3} -S ${4} -V ${5}
+}
+
+DB_PATH=${BASE_PATH}/signingDB
+TEST_APP_PATH=${BASE_PATH}/testApps
+
+echo "Warning! The directories ${DB_PATH} and ${TEST_APP_PATH} will be erased!"
+echo "Do you want to proceed anyway?"
+select answer in "Yes" "No"
+do
+  case $answer in
+    Yes) break;;
+    No) exit 1;;
+  esac
+done
+
+rm -rf ${DB_PATH} ${TEST_APP_PATH}
+
+TRUSTED_EE=trusted_ee1
+UNTRUSTED_EE=untrusted_ee1
+TRUSTED_CA=trusted_ca1
+UNTRUSTED_CA=untrusted_ca1
+
+# First, we'll create a new couple of signing DBs
+createDb $DB_PATH
+addCerts $DB_PATH "Valid CA" "Store Cert" trusted_ca1 ${TRUSTED_EE}
+addCerts $DB_PATH "Invalid CA" "Invalid Cert" ${UNTRUSTED_CA} ${UNTRUSTED_EE}
+
+# Then we'll create the unsigned apps
+echo "Creating unsigned apps"
+packageApps
+
+# And then we'll create all the test apps...
+mkdir -p ${TEST_APP_PATH}
+
+# We need:
+# A valid signed file, with two different versions:
+#    valid_app_1.zip
+#    valid_app_2.zip
+VALID_UID=`uuidgen`
+signApp $DB_PATH ${BASE_PATH}/unsigned_app_1.zip \
+        $TEST_APP_PATH/valid_app_1.zip \
+        $VALID_UID 1 ${TRUSTED_EE}
+signApp $DB_PATH ${BASE_PATH}/unsigned_app_1.zip \
+        $TEST_APP_PATH/valid_app_2.zip \
+        $VALID_UID 2 ${TRUSTED_EE}
+
+
+# A corrupt_package:
+#    corrupt_app_1.zip
+# A corrupt package is a package with a entry modified, for example...
+CURDIR=`pwd`
+export TEMP_DIR=$TEST_APP_PATH/aux_unzip_$$
+mkdir -p $TEMP_DIR
+cd  $TEMP_DIR
+unzip ../valid_app_1.zip 2>&1 >/dev/null
+echo " - " >> index.html
+zip -r ../corrupt_app_1.zip * 2>&1 >/dev/null
+cd $CURDIR
+rm -rf $TEMP_DIR
+
+# A file signed by a unknown issuer
+#    unknown_issuer_app_1.zip
+INVALID_UID=`uuidgen`
+signApp $DB_PATH ${BASE_PATH}/unsigned_app_1.zip \
+        $TEST_APP_PATH/unknown_issuer_app_1.zip \
+        $INVALID_UID 1 ${UNTRUSTED_EE}
+
+# And finally a priviledged signed file that includes the origin on the manifest
+# to avoid that reverting again
+PRIV_UID=`uuidgen`
+signApp $DB_PATH ${BASE_PATH}/unsigned_app_origin.zip \
+        $TEST_APP_PATH/origin_app_1.zip \
+        $PRIV_UID 1 ${TRUSTED_EE}
+
+# A privileged signed app needed for a toolkit/webapps test
+PRIV_TOOLKIT_UID=`uuidgen`
+signApp $DB_PATH ${BASE_PATH}/unsigned_app_origin_toolkit_webapps.zip \
+        $TEST_APP_PATH/custom_origin.zip \
+        $PRIV_TOOLKIT_UID 1 ${TRUSTED_EE}
+
+# Now let's copy the trusted cert to the app directory so we have everything
+# on the same place...
+cp ${DB_PATH}/${TRUSTED_CA}.der ${TEST_APP_PATH}
+
+cat <<EOF
+
+All done. The new test files are in ${TEST_APP_PATH}. You should copy the
+contents of that directory to the dom/apps/tests/signed directory and to
+the security/manager/ssl/tests/unit/test_signed_apps (which should be the
+parent of this directory) to install them.
+
+EOF
+
+echo "Do you wish me to do that for you now?"
+select answer in "Yes" "No"
+do
+  case $answer in
+    Yes) break;;
+    No) echo "Ok, not installing the new files"
+        echo "You should run: "
+        echo cp ${TEST_APP_PATH}/* ${BASE_PATH}/${APPS_TEST_LOC}
+        echo cp ${TEST_APP_PATH}/* ${TEST_APP_PATH}/../unsigned_app_1.zip ${BASE_PATH}/..
+        echo cp ${TEST_APP_PATH}/* ${BASE_PATH}/${TOOLKIT_WEBAPPS_TEST_LOC}
+        echo "to install them"
+        exit 0;;
+  esac
+done
+
+cp ${TEST_APP_PATH}/* ${BASE_PATH}/${APPS_TEST_LOC}
+cp ${TEST_APP_PATH}/* ${TEST_APP_PATH}/../unsigned_app_1.zip ${BASE_PATH}/..
+cp ${TEST_APP_PATH}/* ${BASE_PATH}/${TOOLKIT_WEBAPPS_TEST_LOC}
+
+echo "Done!"
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/nss_ctypes.py b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/nss_ctypes.py
new file mode 100644
index 000000000..35ca92959
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/nss_ctypes.py
@@ -0,0 +1,129 @@
+from ctypes import *
+import os
+import sys
+
+if sys.platform == 'darwin':
+  libprefix = "lib"
+  libsuffix = ".dylib"
+elif os.name == 'posix':
+  libprefix = "lib"
+  libsuffix = ".so"
+else: # assume windows
+  libprefix = ""
+  libsuffix = ".dll"
+
+plc   = cdll.LoadLibrary(libprefix + "plc4"   + libsuffix)
+nspr  = cdll.LoadLibrary(libprefix + "nspr4"  + libsuffix)
+nss   = cdll.LoadLibrary(libprefix + "nss3"   + libsuffix)
+smime = cdll.LoadLibrary(libprefix + "smime3" + libsuffix)
+
+nspr.PR_GetError.argtypes = []
+nspr.PR_GetError.restype = c_int32
+nspr.PR_ErrorToName.argtypes = [c_int32]
+nspr.PR_ErrorToName.restype = c_char_p
+
+def raise_if_not_SECSuccess(rv):
+  SECSuccess = 0
+  if (rv != SECSuccess):
+    raise ValueError(nspr.PR_ErrorToName(nspr.PR_GetError()))
+
+def raise_if_NULL(p):
+  if not p:
+    raise ValueError(nspr.PR_ErrorToName(nspr.PR_GetError()))
+  return p
+
+PRBool = c_int
+SECStatus = c_int
+
+# from secoidt.h
+SEC_OID_SHA1 = 4
+
+# from certt.h
+certUsageObjectSigner = 6
+
+class SECItem(Structure):
+  _fields_ = [("type", c_int),
+              ("data", c_char_p),
+              ("len", c_uint)]
+
+nss.NSS_Init.argtypes = [c_char_p]
+nss.NSS_Init.restype = SECStatus
+def NSS_Init(db_dir):
+  nss.NSS_Init.argtypes = [c_char_p]
+  nss.NSS_Init.restype = SECStatus
+  raise_if_not_SECSuccess(nss.NSS_Init(db_dir))
+
+nss.NSS_Shutdown.argtypes = []
+nss.NSS_Shutdown.restype = SECStatus
+def NSS_Shutdown():
+  raise_if_not_SECSuccess(nss.NSS_Shutdown())
+
+PK11PasswordFunc = CFUNCTYPE(c_char_p, c_void_p, PRBool, c_char_p)
+
+# pass the result of this as the wincx parameter when a wincx is required
+nss.PK11_SetPasswordFunc.argtypes = [PK11PasswordFunc]
+nss.PK11_SetPasswordFunc.restype = None
+
+# Set the return type as *void so Python doesn't touch it
+plc.PL_strdup.argtypes = [c_char_p]
+plc.PL_strdup.restype = c_void_p
+def SetPasswordContext(password):
+  def callback(slot, retry, arg):
+    return plc.PL_strdup(password)
+  wincx = PK11PasswordFunc(callback)
+  nss.PK11_SetPasswordFunc(wincx)
+  return wincx
+
+nss.CERT_GetDefaultCertDB.argtypes = []
+nss.CERT_GetDefaultCertDB.restype = c_void_p
+def CERT_GetDefaultCertDB():
+  return raise_if_NULL(nss.CERT_GetDefaultCertDB())
+
+nss.PK11_FindCertFromNickname.argtypes = [c_char_p, c_void_p]
+nss.PK11_FindCertFromNickname.restype = c_void_p
+def PK11_FindCertFromNickname(nickname, wincx):
+  return raise_if_NULL(nss.PK11_FindCertFromNickname(nickname, wincx))
+
+nss.CERT_DestroyCertificate.argtypes = [c_void_p]
+nss.CERT_DestroyCertificate.restype = None
+def CERT_DestroyCertificate(cert):
+  nss.CERT_DestroyCertificate(cert)
+
+smime.SEC_PKCS7CreateSignedData.argtypes = [c_void_p, c_int, c_void_p,
+                                            c_int, c_void_p,
+                                            c_void_p, c_void_p]
+smime.SEC_PKCS7CreateSignedData.restype = c_void_p
+def SEC_PKCS7CreateSignedData(cert, certusage, certdb, digestalg, digest, wincx):
+  item = SECItem(0,  c_char_p(digest), len(digest))
+  return raise_if_NULL(smime.SEC_PKCS7CreateSignedData(cert, certusage, certdb,
+                                                       digestalg,
+                                                       pointer(item),
+                                                       None, wincx))
+
+smime.SEC_PKCS7AddSigningTime.argtypes = [c_void_p]
+smime.SEC_PKCS7AddSigningTime.restype = SECStatus
+def SEC_PKCS7AddSigningTime(p7):
+  raise_if_not_SECSuccess(smime.SEC_PKCS7AddSigningTime(p7))
+
+smime.SEC_PKCS7IncludeCertChain.argtypes = [c_void_p, c_void_p]
+smime.SEC_PKCS7IncludeCertChain.restype = SECStatus
+def SEC_PKCS7IncludeCertChain(p7, wincx):
+  raise_if_not_SECSuccess(smime.SEC_PKCS7IncludeCertChain(p7, wincx))
+
+SEC_PKCS7EncoderOutputCallback = CFUNCTYPE(None, c_void_p, c_void_p, c_long)
+smime.SEC_PKCS7Encode.argtypes = [c_void_p, SEC_PKCS7EncoderOutputCallback,
+                                  c_void_p, c_void_p, c_void_p, c_void_p]
+smime.SEC_PKCS7Encode.restype = SECStatus
+def SEC_PKCS7Encode(p7, bulkkey, wincx):
+  outputChunks = []
+  def callback(chunks, data, len):
+    outputChunks.append(string_at(data, len))
+  callbackWrapper = SEC_PKCS7EncoderOutputCallback(callback)
+  raise_if_not_SECSuccess(smime.SEC_PKCS7Encode(p7, callbackWrapper,
+                                                None, None, None, wincx))
+  return "".join(outputChunks)
+
+smime.SEC_PKCS7DestroyContentInfo.argtypes = [c_void_p]
+smime.SEC_PKCS7DestroyContentInfo.restype = None
+def SEC_PKCS7DestroyContentInfo(p7):
+  smime.SEC_PKCS7DestroyContentInfo(p7)
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/sign_b2g_app.py b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/sign_b2g_app.py
new file mode 100644
index 000000000..89b385a9b
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/sign_b2g_app.py
@@ -0,0 +1,174 @@
+import argparse
+from base64 import b64encode
+from hashlib import sha1
+import sys
+import zipfile
+import ctypes
+
+import nss_ctypes
+
+# Change the limits in JarSignatureVerification.cpp when you change the limits
+# here.
+max_entry_uncompressed_len = 100 * 1024 * 1024
+max_total_uncompressed_len = 500 * 1024 * 1024
+max_entry_count = 100 * 1000
+max_entry_filename_len = 1024
+max_mf_len = max_entry_count * 50
+max_sf_len = 1024
+
+
+
+def nss_load_cert(nss_db_dir, nss_password, cert_nickname):
+  nss_ctypes.NSS_Init(nss_db_dir)
+  try:
+    wincx = nss_ctypes.SetPasswordContext(nss_password)
+    cert = nss_ctypes.PK11_FindCertFromNickname(cert_nickname, wincx)
+    return (wincx, cert)
+  except:
+    nss_ctypes.NSS_Shutdown()
+    raise
+
+def nss_create_detached_signature(cert, dataToSign, wincx):
+  certdb = nss_ctypes.CERT_GetDefaultCertDB()
+  p7 = nss_ctypes.SEC_PKCS7CreateSignedData(cert,
+                                            nss_ctypes.certUsageObjectSigner,
+                                            certdb,
+                                            nss_ctypes.SEC_OID_SHA1,
+                                            sha1(dataToSign).digest(),
+                                            wincx       )
+  try:
+    nss_ctypes.SEC_PKCS7AddSigningTime(p7)
+    nss_ctypes.SEC_PKCS7IncludeCertChain(p7, wincx)
+    return nss_ctypes.SEC_PKCS7Encode(p7, None, wincx)
+  finally:
+    nss_ctypes.SEC_PKCS7DestroyContentInfo(p7)
+
+# We receive a ids_json string for the toBeSigned app
+def sign_zip(in_zipfile_name, out_zipfile_name, cert, wincx, ids_json):
+  mf_entries = []
+  seen_entries = set()
+
+  total_uncompressed_len = 0
+  entry_count = 0
+  with zipfile.ZipFile(out_zipfile_name, 'w') as out_zip:
+    with zipfile.ZipFile(in_zipfile_name, 'r') as in_zip:
+      for entry_info in in_zip.infolist():
+        name = entry_info.filename
+
+        # Check for reserved and/or insane (potentially malicious) names
+        if name.endswith("/"):
+          pass
+          # Do nothing; we don't copy directory entries since they are just a
+          # waste of space.
+        elif name.lower().startswith("meta-inf/"):
+          # META-INF/* is reserved for our use
+          raise ValueError("META-INF entries are not allowed: %s" % (name))
+        elif len(name) > max_entry_filename_len:
+          raise ValueError("Entry's filename is too long: %s" % (name))
+        # TODO: elif name has invalid characters...
+        elif name in seen_entries:
+          # It is possible for a zipfile to have duplicate entries (with the exact
+          # same filenames). Python's zipfile module accepts them, but our zip
+          # reader in Gecko cannot do anything useful with them, and there's no
+          # sane reason for duplicate entries to exist, so reject them.
+          raise ValueError("Duplicate entry in input file: %s" % (name))
+        else:
+          entry_count += 1
+          if entry_count > max_entry_count:
+            raise ValueError("Too many entries in input archive")
+
+          seen_entries.add(name)
+
+          # Read in the input entry, but be careful to avoid going over the
+          # various limits we have, to minimize the likelihood that we'll run
+          # out of memory. Note that we can't use the length from entry_info
+          # because that might not be accurate if the input zip file is
+          # maliciously crafted to contain misleading metadata.
+          with in_zip.open(name, 'r') as entry_file:
+            contents = entry_file.read(max_entry_uncompressed_len + 1)
+          if len(contents) > max_entry_uncompressed_len:
+            raise ValueError("Entry is too large: %s" % (name))
+          total_uncompressed_len += len(contents)
+          if total_uncompressed_len > max_total_uncompressed_len:
+            raise ValueError("Input archive is too large")
+
+          # Copy the entry, using the same compression as used in the input file
+          out_zip.writestr(entry_info, contents)
+
+          # Add the entry to the manifest we're building
+          mf_entries.append('Name: %s\nSHA1-Digest: %s\n'
+                                % (name, b64encode(sha1(contents).digest())))
+    if (ids_json):
+      mf_entries.append('Name: %s\nSHA1-Digest: %s\n'
+                        % ("META-INF/ids.json", b64encode(sha1(ids_json).digest())))
+
+    mf_contents = 'Manifest-Version: 1.0\n\n' + '\n'.join(mf_entries)
+    if len(mf_contents) > max_mf_len:
+      raise ValueError("Generated MANIFEST.MF is too large: %d" % (len(mf_contents)))
+
+    sf_contents = ('Signature-Version: 1.0\nSHA1-Digest-Manifest: %s\n'
+                                % (b64encode(sha1(mf_contents).digest())))
+    if len(sf_contents) > max_sf_len:
+      raise ValueError("Generated SIGNATURE.SF is too large: %d"
+                          % (len(mf_contents)))
+
+    p7 = nss_create_detached_signature(cert, sf_contents, wincx)
+
+    # write the signature, SF, and MF
+    out_zip.writestr("META-INF/A.RSA", p7, zipfile.ZIP_DEFLATED)
+    out_zip.writestr("META-INF/A.SF",  sf_contents, zipfile.ZIP_DEFLATED)
+    out_zip.writestr("META-INF/MANIFEST.MF", mf_contents, zipfile.ZIP_DEFLATED)
+    if (ids_json):
+        out_zip.writestr("META-INF/ids.json", ids_json, zipfile.ZIP_DEFLATED)
+
+def main():
+  parser = argparse.ArgumentParser(description='Sign a B2G app.')
+  parser.add_argument('-d', action='store',
+                            required=True, help='NSS database directory')
+  parser.add_argument('-f', action='store',
+                            type=argparse.FileType('rb'),
+                            required=True, help='password file')
+  parser.add_argument('-k', action='store',
+                            required=True, help="nickname of signing cert.")
+  parser.add_argument('-i', action='store', type=argparse.FileType('rb'),
+                            required=True, help="input JAR file (unsigned)")
+  parser.add_argument('-o', action='store', type=argparse.FileType('wb'),
+                            required=True, help="output JAR file (signed)")
+  parser.add_argument('-I', '--ids-file', action='store', type=argparse.FileType('rb'),
+                     help="Path to the ids.json file", dest='I')
+  parser.add_argument('-S', '--storeId', action='store',
+                      help="Store Id for the package", dest='S')
+  parser.add_argument('-V', '--storeVersion', action='store', type=int,
+                      help="Package Version", dest='V')
+  args = parser.parse_args()
+
+  # Sadly nested groups and neccesarily inclusive groups (http://bugs.python.org/issue11588)
+  # are not implemented. Note that this means the automatic help is slighty incorrect
+  if not((not args.I and args.V and args.S) or (args.I and not args.V and not args.S)):
+      raise ValueError("Either -I or -S and -V must be specified")
+
+  if (args.I):
+    ids_contents = args.I.read(max_entry_uncompressed_len+1)
+  else:
+    ids_contents = '''{
+  "id": "%(id)s",
+  "version": %(version)d
+}
+''' % {"id": args.S, "version": args.V}
+  if len(ids_contents) > max_entry_uncompressed_len:
+    raise ValueError("Entry is too large: %s" % (name))
+
+  db_dir = args.d
+  password = args.f.readline().strip()
+  cert_nickname = args.k
+
+  (wincx, cert) = nss_load_cert(db_dir, password, cert_nickname)
+  try:
+    sign_zip(args.i, args.o, cert, wincx, ids_contents)
+    return 0
+  finally:
+    nss_ctypes.CERT_DestroyCertificate(cert)
+    nss_ctypes.NSS_Shutdown()
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/icon-128.png b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/icon-128.png
new file mode 100644
index 000000000..d6fd07a41
Binary files /dev/null and b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/icon-128.png differ
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/index.html b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/index.html
new file mode 100644
index 000000000..2acf635e8
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/index.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<html lang=en>
+<head><meta charset=utf-8><title>Simple App</title></head>
+<body><p>This is a Simple App.</body>
+</html>
+
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/manifest.webapp b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/manifest.webapp
new file mode 100644
index 000000000..447cd1e71
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_1/manifest.webapp
@@ -0,0 +1,8 @@
+{ "name": "Simple App"
+, "description": "A Simple Open Web App"
+, "launch_path": "tests/dom/apps/tests/signed_app.sjs"
+, "icons": { "128" : "icon-128.png" }
+, "installs_allowed_from": [ "https://marketplace.mozilla.com", "http://mochi.test:8888" ]
+, "version": 1
+, "default_locale": "en-US"
+}
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/icon-128.png b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/icon-128.png
new file mode 100644
index 000000000..d6fd07a41
Binary files /dev/null and b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/icon-128.png differ
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/index.html b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/index.html
new file mode 100644
index 000000000..9f2adf57b
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/index.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<html lang=en>
+<head><meta charset=utf-8><title>Simple App</title></head>
+<body><p>This is a Simple App.</body>
+</html>
+
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/manifest.webapp b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/manifest.webapp
new file mode 100644
index 000000000..d2393e3c3
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin/manifest.webapp
@@ -0,0 +1,10 @@
+{ "name": "Simple App",
+  "description": "A Simple Open Web App",
+  "type": "privileged",
+  "origin": "app://test.origin.privileged.app",
+  "launch_path": "tests/dom/apps/tests/signed_app.sjs",
+  "icons": { "128" : "icon-128.png" },
+  "installs_allowed_from": [ "https://marketplace.mozilla.com", "http://mochi.test:8888" ],
+  "version": 1,
+  "default_locale": "en-US"
+}
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/index.html b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/index.html
new file mode 100644
index 000000000..12c6881c2
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Test app</title>
+</head>
+<body>
+Test app:
+<iframe src="http://127.0.0.1:8888/chrome/toolkit/webapps/tests/app.sjs?appreq"></iframe>
+</body>
+</html>
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/manifest.webapp b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/manifest.webapp
new file mode 100644
index 000000000..55103ca5a
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_signed_apps/gentestfiles/unsigned_app_origin_toolkit_webapps/manifest.webapp
@@ -0,0 +1,9 @@
+{
+  "name": "Custom Origin Test",
+  "version": 1,
+  "size": 777,
+  "package_path": "custom_origin.zip",
+  "launch_path": "/index.html",
+  "origin": "app://test.origin.privileged.app",
+  "type": "privileged"
+}
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/privileged-app-test-1.0.zip b/security/manager/ssl/tests/unit/test_signed_apps/privileged-app-test-1.0.zip
new file mode 100644
index 000000000..3a12106c8
Binary files /dev/null and b/security/manager/ssl/tests/unit/test_signed_apps/privileged-app-test-1.0.zip differ
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/test-privileged-app-test-1.0.zip b/security/manager/ssl/tests/unit/test_signed_apps/test-privileged-app-test-1.0.zip
new file mode 100644
index 000000000..ec137ea14
Binary files /dev/null and b/security/manager/ssl/tests/unit/test_signed_apps/test-privileged-app-test-1.0.zip differ
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/trusted_ca1.der b/security/manager/ssl/tests/unit/test_signed_apps/trusted_ca1.der
new file mode 100644
index 000000000..0200a7f35
Binary files /dev/null and b/security/manager/ssl/tests/unit/test_signed_apps/trusted_ca1.der differ
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/unknown_issuer_app_1.zip b/security/manager/ssl/tests/unit/test_signed_apps/unknown_issuer_app_1.zip
new file mode 100644
index 000000000..374e45f6f
Binary files /dev/null and b/security/manager/ssl/tests/unit/test_signed_apps/unknown_issuer_app_1.zip differ
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/unsigned_app_1.zip b/security/manager/ssl/tests/unit/test_signed_apps/unsigned_app_1.zip
new file mode 100644
index 000000000..731e050ab
Binary files /dev/null and b/security/manager/ssl/tests/unit/test_signed_apps/unsigned_app_1.zip differ
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/valid_app_1.zip b/security/manager/ssl/tests/unit/test_signed_apps/valid_app_1.zip
new file mode 100644
index 000000000..74a35a420
Binary files /dev/null and b/security/manager/ssl/tests/unit/test_signed_apps/valid_app_1.zip differ
-- 
cgit v1.2.3