Bug 1059208 - Add scripts for signing manifest files of Trusted Hosted Apps r=dkeeler

This commit is contained in:
Robin Thunell 2014-09-22 07:58:59 -07:00
parent b743822978
commit 3b04dbe2e6
10 changed files with 446 additions and 1 deletions

View File

@ -4,6 +4,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
GEN_CERT_HEADER = $(srcdir)/gen_cert_header.py
TEST_SSL_PATH = $(srcdir)/../manager/ssl/tests/unit/test_signed_manifest/
marketplace-prod-public.inc: marketplace-prod-public.crt $(GEN_CERT_HEADER)
$(PYTHON) $(GEN_CERT_HEADER) marketplaceProdPublicRoot $< > $@
@ -20,6 +21,18 @@ marketplace-dev-reviewers.inc: marketplace-dev-reviewers.crt $(GEN_CERT_HEADER)
marketplace-stage.inc: marketplace-stage.crt $(GEN_CERT_HEADER)
$(PYTHON) $(GEN_CERT_HEADER) marketplaceStageRoot $< > $@
ifeq ($(shell test -s trusted-app-public.der; echo $$?),0)
TRUSTED_APP_PUBLIC=trusted-app-public.der
else
TRUSTED_APP_PUBLIC=
endif
manifest-signing-root.inc: $(TRUSTED_APP_PUBLIC) $(GEN_CERT_HEADER)
$(PYTHON) $(GEN_CERT_HEADER) trustedAppPublicRoot $(TRUSTED_APP_PUBLIC) > $@
manifest-signing-test-root.inc: $(TEST_SSL_PATH)trusted_ca1.der $(GEN_CERT_HEADER)
$(PYTHON) $(GEN_CERT_HEADER) trustedAppTestRoot $< > $@
xpcshell.inc: $(srcdir)/../manager/ssl/tests/unit/test_signed_apps/trusted_ca1.der $(GEN_CERT_HEADER)
$(PYTHON) $(GEN_CERT_HEADER) xpcshellRoot $< > $@
@ -29,5 +42,7 @@ export:: \
marketplace-dev-public.inc \
marketplace-dev-reviewers.inc \
marketplace-stage.inc \
manifest-signing-root.inc \
manifest-signing-test-root.inc \
xpcshell.inc \
$(NULL)

View File

@ -22,8 +22,18 @@ def create_header(array_name, in_filename):
print "};"
return 0
def create_empty_header(array_name):
# mfbt/ArrayUtils.h will not be able to pick up the
# correct specialization for ArrayLength(const array[0])
# so add a value of 0 which will fail cert verification
# just the same as an empty array
print "const uint8_t " + array_name + "[] = { 0x0 };"
return 0
if __name__ == '__main__':
if len(sys.argv) < 3:
if len(sys.argv) < 2:
print 'ERROR: usage: gen_cert_header.py array_name in_filename'
sys.exit(1);
if len(sys.argv) == 2:
sys.exit(create_empty_header(sys.argv[1]))
sys.exit(create_header(sys.argv[1], sys.argv[2]))

View File

@ -0,0 +1,17 @@
This folder contains the scripts needed to generate signed manifest files
to verify the Trusted Hosted Apps concept.
Prerequisites:
* NSS 3.4 or higher.
* Python 2.7 (should work with 2.6 also)
* Bash
* OpenSSL
Usage:
Run
I) For usage info execute ./create_test_files.sh --help
II) Upload the signed manifest.webapp and manifest.sig to the
application hosting server.

View File

@ -0,0 +1,181 @@
#!/bin/bash
#
# Mode: shell-script; sh-indentation:2;
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
export BASE_PATH=`dirname $0`
echo $BASE_PATH
# location of the 'sign_b2g_manifest.py' script
export SIGN_SCR_PATH=.
DB_PATH=${BASE_PATH}/signingDB
PASSWORD_FILE=${DB_PATH}/passwordfile
VALID_MANIFEST_PATH=${BASE_PATH}/testValidSignedManifest
INVALID_MANIFEST_PATH=${BASE_PATH}/testInvalidSignedManifest
TRUSTED_EE=trusted_ee1
UNTRUSTED_EE=untrusted_ee1
TRUSTED_CA=trusted_ca1
UNTRUSTED_CA=untrusted_ca1
# Print usage info
usage() {
echo
echo
tput bold
echo "NAME"
tput sgr0
echo " create_test_files.sh - Signing a manifest for Trusted Hosted Apps."
echo
tput bold
echo "SYNOPSIS"
tput sgr0
echo " create_test_files.sh"
echo " create_test_files.sh [--regenerate-test-certs]"
echo " create_test_files.sh [--help]"
echo
tput bold
echo "DESCRIPTION"
tput sgr0
echo " The script signs a manifest for Trusted Hosted Apps if no parameter"
echo " is given and if the manifest file and a certificate database directory"
echo " is present in the current directory."
echo " Two directories ./testValidSignedManifest and ./testInvalidSignedManifest"
echo " are generated containing a manifest signature file each, signed with valid"
echo " and invalid certificates respectively."
echo " If the --regenerate-test-certs parameter is given, a new certificate database"
echo " directory is generated before the signing takes place."
echo " If the certificate database is not present and the --regenerate-test-certs"
echo " parameter is not given the script exits whithout any operations."
echo
tput bold
echo "OPTIONS"
echo " --regenerate-test-certs,"
tput sgr0
echo " Generates a test certificate database and then signs the manifest.webapp"
echo " file in the current directory."
echo
tput bold
echo " --help,"
tput sgr0
echo " Show this usage information."
echo
}
# Function to create a signing database
# Parameters:
# $1: Output directory (where the DB will be created)
# $2: Password file
createDB() {
local db_path=${1}
local password_file=${2}
mkdir -p ${db_path}
echo insecurepassword > ${password_file}
certutil -d ${db_path} -N -f ${password_file} 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!)
# $6: Password file
addCerts() {
local db_path=${1}
local password_file=${6}
org="O=Example Trusted 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_path} -f ${password_file} -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
certutil -d ${db_path} -L -n ${4} -r -o ${SIGN_SCR_PATH}/${4}.der
rm -f ${noisefile} ${ee_responses} ${ca_responses}
}
# Signs a manifest
# Parameters:
# $1: Database directory
# $2: Unsigned manifest file path
# $3: Signed manifest file path
# $4: Nickname of the signing certificate
# $5: Password file
signManifest() {
local db_path=${1}
local password_file=${5}
python ${BASE_PATH}/${SIGN_SCR_PATH}/sign_b2g_manifest.py -d ${db_path} \
-f ${password_file} -k ${4} -i ${2} -o ${3}
}
# Generate the necessary files to be used for the signing
generate_files() {
# First create a new couple of signing DBs
rm -rf ${DB_PATH} ${VALID_MANIFEST_PATH} ${INVALID_MANIFEST_PATH}
createDB ${DB_PATH} ${PASSWORD_FILE}
addCerts ${DB_PATH} "Trusted Valid CA" "Trusted Corp Cert" ${TRUSTED_CA} ${TRUSTED_EE} ${PASSWORD_FILE}
addCerts ${DB_PATH} "Trusted Invalid CA" "Trusted Invalid Cert" ${UNTRUSTED_CA} ${UNTRUSTED_EE} ${PASSWORD_FILE}
}
#Start of execution
if [ ${1} ] && [ "${1}" == '--regenerate-test-certs' ]; then
generate_files
elif [ "${1}" == '--help' ]; then
usage
exit 1
else
if [ -d ${DB_PATH} ]; then
rm -rf ${VALID_MANIFEST_PATH} ${INVALID_MANIFEST_PATH}
else
echo "Error! The directory ${DB_PATH} does not exist!"
echo "New certificate database must be created!"
usage $0
exit 1
fi
fi
# Create all the test manifests
mkdir -p ${VALID_MANIFEST_PATH}
mkdir -p ${INVALID_MANIFEST_PATH}
CURDIR=`pwd`
cd $CURDIR
# Sign a manifest file with a known issuer
signManifest ${DB_PATH} ${BASE_PATH}/manifest.webapp \
${VALID_MANIFEST_PATH}/manifest.sig \
${TRUSTED_EE} ${PASSWORD_FILE}
# Sign a manifest file with a unknown issuer
signManifest ${DB_PATH} ${BASE_PATH}/manifest.webapp \
${INVALID_MANIFEST_PATH}/manifest.sig \
${UNTRUSTED_EE} ${PASSWORD_FILE}
echo "Done!"

View File

@ -0,0 +1,10 @@
{ "name": "Trusted App Example",
"description": "A Manifest for a Trusted Hosted Application",
"type": "trusted",
"launch_path": "/index.html",
"icons": { "128" : "icon-128.png" },
"version": 1,
"csp" : "script-src https://www.example.com; style-src https://www.example.com",
"permissions": { "device-storage:videos":{ "access": "readonly" }, "device-storage:pictures":{ "access": "readwrite" } },
"default_locale": "en-US"
}

View File

@ -0,0 +1,136 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
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)

View File

@ -0,0 +1,76 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import argparse
from base64 import b64encode
from hashlib import sha1
import sys
import ctypes
import nss_ctypes
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)
# Sign a manifest file
def sign_file(in_file, out_file, cert, wincx):
contents = in_file.read()
in_file.close()
# generate base64 encoded string of the sha1 digest of the input file
in_file_hash = b64encode(sha1(contents).digest())
# sign the base64 encoded string with the given certificate
in_file_signature = nss_create_detached_signature(cert, in_file_hash, wincx)
# write the content of the output file
out_file.write(in_file_signature)
out_file.close()
def main():
parser = argparse.ArgumentParser(description='Sign a B2G app manifest.')
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 manifest file (unsigned)")
parser.add_argument('-o', action='store', type=argparse.FileType('wb'),
required=True, help="output manifest file (signed)")
args = parser.parse_args()
db_dir = args.d
password = args.f.readline().strip()
cert_nickname = args.k
cert = None
try:
nss_ctypes.NSS_Init(db_dir)
wincx = nss_ctypes.SetPasswordContext(password)
cert = nss_ctypes.PK11_FindCertFromNickname(cert_nickname, wincx)
sign_file(args.i, args.o, cert, wincx)
return 0
finally:
nss_ctypes.CERT_DestroyCertificate(cert)
nss_ctypes.NSS_Shutdown()
if __name__ == "__main__":
sys.exit(main())