--- a/js/xpconnect/src/xpc.msg
+++ b/js/xpconnect/src/xpc.msg
@@ -198,16 +198,17 @@ XPC_MSG_DEF(NS_ERROR_ILLEGAL_INPUT
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_NOT_SIGNED , "The JAR is not signed.")
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY , "An entry in the JAR has been modified after the JAR was signed.")
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY , "An entry in the JAR has not been signed.")
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_MISSING , "An entry is missing from the JAR file.")
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE , "The JAR's signature is wrong.")
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE , "An entry in the JAR is too large.")
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_INVALID , "An entry in the JAR is invalid.")
XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MANIFEST_INVALID , "The JAR's manifest or signature file is invalid.")
+XPC_MSG_DEF(NS_ERROR_CMS_VERIFY_NOT_SIGNED , "The PKCS#7 information is not signed.")
/* Codes related to signed manifests */
XPC_MSG_DEF(NS_ERROR_SIGNED_APP_MANIFEST_INVALID , "The signed app manifest or signature file is invalid.")
/* Codes for printing-related errors. */
XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE , "No printers available.")
XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND , "The selected printer could not be found.")
XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_COULD_NOT_OPEN_FILE , "Failed to open output file for print to file.")
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -136,18 +136,18 @@ ReadStream(const nsCOMPtr<nsIInputStream
}
buf.data[buf.len - 1] = 0; // null-terminate
return NS_OK;
}
// Finds exactly one (signature metadata) JAR entry that matches the given
-// search pattern, and then load it. Fails if there are no matches or if
-// there is more than one match. If bugDigest is not null then on success
+// search pattern, and then loads it. Fails if there are no matches or if
+// there is more than one match. If bufDigest is not null then on success
// bufDigest will contain the digeset of the entry using the given digest
// algorithm.
nsresult
FindAndLoadOneEntry(nsIZipReader* zip,
const nsACString& searchPattern,
/*out*/ nsACString& filename,
/*out*/ SECItem& buf,
/*optional, in*/ SECOidTag digestAlgorithm = SEC_OID_SHA1,
@@ -462,101 +462,98 @@ CheckManifestVersion(const char* & nextL
if (!curLine.Equals(expectedHeader)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
return NS_OK;
}
// Parses a signature file (SF) based on the JDK 8 JAR Specification.
//
-// The SF file must contain a SHA1-Digest-Manifest (or, preferrably,
-// SHA256-Digest-Manifest) attribute in the main section. All other sections are
-// ignored. This means that this will NOT parse old-style signature files that
-// have separate digests per entry.
+// The SF file must contain a SHA*-Digest-Manifest attribute in the main
+// section (where the * is either 1 or 256, depending on the given digest
+// algorithm). All other sections are ignored. This means that this will NOT
+// parse old-style signature files that have separate digests per entry.
// The JDK8 x-Digest-Manifest variant is better because:
//
// (1) It allows us to follow the principle that we should minimize the
// processing of data that we do before we verify its signature. In
// particular, with the x-Digest-Manifest style, we can verify the digest
// of MANIFEST.MF before we parse it, which prevents malicious JARs
// exploiting our MANIFEST.MF parser.
// (2) It is more time-efficient and space-efficient to have one
// x-Digest-Manifest instead of multiple x-Digest values.
//
// filebuf must be null-terminated. On output, mfDigest will contain the
-// decoded value of SHA1-Digest-Manifest or SHA256-Digest-Manifest, if found,
-// as well as an identifier indicating which algorithm was found.
+// decoded value of the appropriate SHA*-DigestManifest, if found.
nsresult
-ParseSF(const char* filebuf, /*out*/ DigestWithAlgorithm& mfDigest)
+ParseSF(const char* filebuf, SECOidTag digestAlgorithm,
+ /*out*/ nsAutoCString& mfDigest)
{
+ const char* digestNameToFind = nullptr;
+ switch (digestAlgorithm) {
+ case SEC_OID_SHA256:
+ digestNameToFind = "sha256-digest-manifest";
+ break;
+ case SEC_OID_SHA1:
+ digestNameToFind = "sha1-digest-manifest";
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("bad argument to ParseSF");
+ return NS_ERROR_FAILURE;
+ }
+
const char* nextLineStart = filebuf;
nsresult rv = CheckManifestVersion(nextLineStart,
NS_LITERAL_CSTRING(JAR_SF_HEADER));
if (NS_FAILED(rv)) {
return rv;
}
- nsAutoCString savedSHA1Digest;
- // Search for SHA256-Digest-Manifest and SHA1-Digest-Manifest. Prefer the
- // former.
for (;;) {
nsAutoCString curLine;
rv = ReadLine(nextLineStart, curLine);
if (NS_FAILED(rv)) {
return rv;
}
if (curLine.Length() == 0) {
- // End of main section (blank line or end-of-file). We didn't find
- // SHA256-Digest-Manifest, but maybe we found SHA1-Digest-Manifest.
- if (!savedSHA1Digest.IsEmpty()) {
- mfDigest.mDigest.Assign(savedSHA1Digest);
- mfDigest.mAlgorithm = SEC_OID_SHA1;
- return NS_OK;
- }
+ // End of main section (blank line or end-of-file). We didn't find the
+ // SHA*-Digest-Manifest we were looking for.
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
nsAutoCString attrName;
nsAutoCString attrValue;
rv = ParseAttribute(curLine, attrName, attrValue);
if (NS_FAILED(rv)) {
return rv;
}
- if (attrName.LowerCaseEqualsLiteral("sha256-digest-manifest")) {
- rv = Base64Decode(attrValue, mfDigest.mDigest);
+ if (attrName.EqualsIgnoreCase(digestNameToFind)) {
+ rv = Base64Decode(attrValue, mfDigest);
if (NS_FAILED(rv)) {
return rv;
}
- mfDigest.mAlgorithm = SEC_OID_SHA256;
// There could be multiple SHA*-Digest-Manifest attributes, which
// would be an error, but it's better to just skip any erroneous
// duplicate entries rather than trying to detect them, because:
//
// (1) It's simpler, and simpler generally means more secure
// (2) An attacker can't make us accept a JAR we would otherwise
// reject just by adding additional SHA*-Digest-Manifest
// attributes.
return NS_OK;
}
- if (attrName.LowerCaseEqualsLiteral("sha1-digest-manifest") &&
- savedSHA1Digest.IsEmpty()) {
- rv = Base64Decode(attrValue, savedSHA1Digest);
- if (NS_FAILED(rv)) {
- return rv;
- }
- }
-
// ignore unrecognized attributes
}
- return NS_OK;
+ MOZ_ASSERT_UNREACHABLE("somehow exited loop in ParseSF without returning");
+ return NS_ERROR_FAILURE;
}
// Parses MANIFEST.MF. The filenames of all entries will be returned in
// mfItems. buf must be a pre-allocated scratch buffer that is used for doing
// I/O. Each file's contents are verified against the entry in the manifest with
// the digest algorithm that matches the given one. This algorithm comes from
// the signature file. If the signature file has a SHA-256 digest, then SHA-256
// entries must be present in the manifest file. If the signature file only has
@@ -753,26 +750,66 @@ VerifyCertificate(CERTCertificate* signe
}
if (result != Success) {
return mozilla::psm::GetXPCOMFromNSSError(MapResultToPRErrorCode(result));
}
return NS_OK;
}
+// Given a SECOidTag representing a digest algorithm (either SEC_OID_SHA1 or
+// SEC_OID_SHA256), returns the first signerInfo in the given signedData that
+// purports to have been created using that digest algorithm, or nullptr if
+// there is none.
+// The returned signerInfo is owned by signedData, so the caller must ensure
+// that the lifetime of the signerInfo is contained by the lifetime of the
+// signedData.
+NSSCMSSignerInfo*
+GetSignerInfoForDigestAlgorithm(NSSCMSSignedData* signedData,
+ SECOidTag digestAlgorithm)
+{
+ MOZ_ASSERT(digestAlgorithm == SEC_OID_SHA1 ||
+ digestAlgorithm == SEC_OID_SHA256);
+ if (digestAlgorithm != SEC_OID_SHA1 && digestAlgorithm != SEC_OID_SHA256) {
+ return nullptr;
+ }
+
+ int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
+ if (numSigners < 1) {
+ return nullptr;
+ }
+ for (int i = 0; i < numSigners; i++) {
+ NSSCMSSignerInfo* signerInfo =
+ NSS_CMSSignedData_GetSignerInfo(signedData, i);
+ // NSS_CMSSignerInfo_GetDigestAlgTag isn't exported from NSS.
+ SECOidData* digestAlgOID = SECOID_FindOID(&signerInfo->digestAlg.algorithm);
+ if (!digestAlgOID) {
+ continue;
+ }
+ if (digestAlgorithm == digestAlgOID->offset) {
+ return signerInfo;
+ }
+ }
+ return nullptr;
+}
+
nsresult
VerifySignature(AppTrustedRoot trustedRoot, const SECItem& buffer,
- const SECItem& detachedDigest,
+ const SECItem& detachedSHA1Digest,
+ const SECItem& detachedSHA256Digest,
+ /*out*/ SECOidTag& digestAlgorithm,
/*out*/ UniqueCERTCertList& builtChain)
{
// Currently, this function is only called within the CalculateResult() method
// of CryptoTasks. As such, NSS should not be shut down at this point and the
// CryptoTask implementation should already hold a nsNSSShutDownPreventionLock.
- if (NS_WARN_IF(!buffer.data && buffer.len > 0) ||
- NS_WARN_IF(!detachedDigest.data && detachedDigest.len > 0)) {
+
+ if (NS_WARN_IF(!buffer.data || buffer.len == 0 || !detachedSHA1Digest.data ||
+ detachedSHA1Digest.len == 0 || !detachedSHA256Digest.data ||
+ detachedSHA256Digest.len == 0)) {
return NS_ERROR_INVALID_ARG;
}
UniqueNSSCMSMessage
cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr));
if (!cmsMsg) {
@@ -796,22 +833,16 @@ VerifySignature(AppTrustedRoot trustedRo
// signedData is non-owning
NSSCMSSignedData* signedData =
static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
if (!signedData) {
return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
}
- // Set digest value.
- if (NSS_CMSSignedData_SetDigestValue(signedData, SEC_OID_SHA1,
- const_cast<SECItem*>(&detachedDigest))) {
- return NS_ERROR_CMS_VERIFY_BAD_DIGEST;
- }
-
// Parse the certificates into CERTCertificate objects held in memory so
// verifyCertificate will be able to find them during path building.
UniqueCERTCertList certs(CERT_NewCertList());
if (!certs) {
return NS_ERROR_OUT_OF_MEMORY;
}
if (signedData->rawCerts) {
for (size_t i = 0; signedData->rawCerts[i]; ++i) {
@@ -827,47 +858,53 @@ VerifySignature(AppTrustedRoot trustedRo
if (CERT_AddCertToListTail(certs.get(), cert.get()) != SECSuccess) {
return NS_ERROR_OUT_OF_MEMORY;
}
Unused << cert.release(); // Ownership transferred to the cert list.
}
}
- // Get the end-entity certificate.
- int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
- if (NS_WARN_IF(numSigners != 1)) {
- return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
+ NSSCMSSignerInfo* signerInfo =
+ GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA256);
+ const SECItem* detachedDigest = &detachedSHA256Digest;
+ digestAlgorithm = SEC_OID_SHA256;
+ if (!signerInfo) {
+ signerInfo = GetSignerInfoForDigestAlgorithm(signedData, SEC_OID_SHA1);
+ if (!signerInfo) {
+ return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
+ }
+ detachedDigest = &detachedSHA1Digest;
+ digestAlgorithm = SEC_OID_SHA1;
}
- // signer is non-owning.
- NSSCMSSignerInfo* signer = NSS_CMSSignedData_GetSignerInfo(signedData, 0);
- if (NS_WARN_IF(!signer)) {
- return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
- }
+
+ // Get the end-entity certificate.
CERTCertificate* signerCert =
- NSS_CMSSignerInfo_GetSigningCertificate(signer, CERT_GetDefaultCertDB());
+ NSS_CMSSignerInfo_GetSigningCertificate(signerInfo,
+ CERT_GetDefaultCertDB());
if (!signerCert) {
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
}
nsresult rv = VerifyCertificate(signerCert, trustedRoot, builtChain);
- if (NS_WARN_IF(NS_FAILED(rv))) {
+ if (NS_FAILED(rv)) {
return rv;
}
- // See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
- SECOidData* contentTypeOidData =
- SECOID_FindOID(&signedData->contentInfo.contentType);
- if (!contentTypeOidData) {
+ // Ensure that the PKCS#7 data OID is present as the PKCS#9 contentType.
+ const char* pkcs7DataOidString = "1.2.840.113549.1.7.1";
+ ScopedAutoSECItem pkcs7DataOid;
+ if (SEC_StringToOID(nullptr, &pkcs7DataOid, pkcs7DataOidString, 0)
+ != SECSuccess) {
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
}
- return MapSECStatus(NSS_CMSSignerInfo_Verify(signer,
- const_cast<SECItem*>(&detachedDigest),
- &contentTypeOidData->oid));
+ return MapSECStatus(
+ NSS_CMSSignerInfo_Verify(signerInfo, const_cast<SECItem*>(detachedDigest),
+ &pkcs7DataOid));
}
nsresult
OpenSignedAppFile(AppTrustedRoot aTrustedRoot, nsIFile* aJarFile,
/*out, optional */ nsIZipReader** aZipReader,
/*out, optional */ nsIX509Cert** aSignerCert)
{
NS_ENSURE_ARG_POINTER(aJarFile);
@@ -896,64 +933,79 @@ OpenSignedAppFile(AppTrustedRoot aTruste
sigFilename, sigBuffer);
if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
}
// Signature (SF) file
nsAutoCString sfFilename;
ScopedAutoSECItem sfBuffer;
- Digest sfCalculatedDigest;
rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_SF_SEARCH_STRING),
- sfFilename, sfBuffer, SEC_OID_SHA1,
- &sfCalculatedDigest);
+ sfFilename, sfBuffer);
if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
- sigBuffer.type = siBuffer;
- UniqueCERTCertList builtChain;
- rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
- builtChain);
+ // Calculate both the SHA-1 and SHA-256 hashes of the signature file - we
+ // don't know what algorithm the PKCS#7 signature used.
+ Digest sfCalculatedSHA1Digest;
+ rv = sfCalculatedSHA1Digest.DigestBuf(SEC_OID_SHA1, sfBuffer.data,
+ sfBuffer.len - 1);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ Digest sfCalculatedSHA256Digest;
+ rv = sfCalculatedSHA256Digest.DigestBuf(SEC_OID_SHA256, sfBuffer.data,
+ sfBuffer.len - 1);
if (NS_FAILED(rv)) {
return rv;
}
- DigestWithAlgorithm mfDigest;
- rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
+ sigBuffer.type = siBuffer;
+ UniqueCERTCertList builtChain;
+ SECOidTag digestToUse;
+ rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedSHA1Digest.get(),
+ sfCalculatedSHA256Digest.get(), digestToUse, builtChain);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoCString mfDigest;
+ rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse,
+ mfDigest);
if (NS_FAILED(rv)) {
return rv;
}
// Manifest (MF) file
nsAutoCString mfFilename;
ScopedAutoSECItem manifestBuffer;
Digest mfCalculatedDigest;
rv = FindAndLoadOneEntry(zip, NS_LITERAL_CSTRING(JAR_MF_SEARCH_STRING),
- mfFilename, manifestBuffer, mfDigest.mAlgorithm,
+ mfFilename, manifestBuffer, digestToUse,
&mfCalculatedDigest);
if (NS_FAILED(rv)) {
return rv;
}
nsDependentCSubstring calculatedDigest(
DigestToDependentString(mfCalculatedDigest));
- if (!mfDigest.mDigest.Equals(calculatedDigest)) {
+ if (!mfDigest.Equals(calculatedDigest)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
// Allocate the I/O buffer only once per JAR, instead of once per entry, in
// order to minimize malloc/free calls and in order to avoid fragmenting
// memory.
ScopedAutoSECItem buf(128 * 1024);
nsTHashtable<nsCStringHashKey> items;
rv = ParseMF(BitwiseCast<char*, unsigned char*>(manifestBuffer.data), zip,
- mfDigest.mAlgorithm, items, buf);
+ digestToUse, items, buf);
if (NS_FAILED(rv)) {
return rv;
}
// Verify every entry in the file.
nsCOMPtr<nsIUTF8StringEnumerator> entries;
rv = zip->FindEntries(EmptyCString(), getter_AddRefs(entries));
if (NS_SUCCEEDED(rv) && !entries) {
@@ -1471,66 +1523,81 @@ VerifySignedDirectory(AppTrustedRoot aTr
// Load the signature (SF) file and verify the signature.
// The .sf and .rsa files must have the same name apart from the extension.
nsAutoString sfFilename(Substring(sigFilename, 0, sigFilename.Length() - 3)
+ NS_LITERAL_STRING("sf"));
ScopedAutoSECItem sfBuffer;
- Digest sfCalculatedDigest;
- rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, SEC_OID_SHA1,
- &sfCalculatedDigest);
+ rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer);
if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
+ // Calculate both the SHA-1 and SHA-256 hashes of the signature file - we
+ // don't know what algorithm the PKCS#7 signature used.
+ Digest sfCalculatedSHA1Digest;
+ rv = sfCalculatedSHA1Digest.DigestBuf(SEC_OID_SHA1, sfBuffer.data,
+ sfBuffer.len - 1);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ Digest sfCalculatedSHA256Digest;
+ rv = sfCalculatedSHA256Digest.DigestBuf(SEC_OID_SHA256, sfBuffer.data,
+ sfBuffer.len - 1);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
sigBuffer.type = siBuffer;
UniqueCERTCertList builtChain;
- rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
- builtChain);
+ SECOidTag digestToUse;
+ rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedSHA1Digest.get(),
+ sfCalculatedSHA256Digest.get(), digestToUse, builtChain);
if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
// Get the expected manifest hash from the signed .sf file
- DigestWithAlgorithm mfDigest;
- rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), mfDigest);
+ nsAutoCString mfDigest;
+ rv = ParseSF(BitwiseCast<char*, unsigned char*>(sfBuffer.data), digestToUse,
+ mfDigest);
if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
// Load manifest (MF) file and verify signature
nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf"));
ScopedAutoSECItem manifestBuffer;
Digest mfCalculatedDigest;
- rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, mfDigest.mAlgorithm,
+ rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, digestToUse,
&mfCalculatedDigest);
if (NS_FAILED(rv)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
nsDependentCSubstring calculatedDigest(
DigestToDependentString(mfCalculatedDigest));
- if (!mfDigest.mDigest.Equals(calculatedDigest)) {
+ if (!mfDigest.Equals(calculatedDigest)) {
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
}
// Parse manifest and verify signed hash of all listed files
// Allocate the I/O buffer only once per JAR, instead of once per entry, in
// order to minimize malloc/free calls and in order to avoid fragmenting
// memory.
ScopedAutoSECItem buf(128 * 1024);
nsTHashtable<nsStringHashKey> items;
rv = ParseMFUnpacked(BitwiseCast<char*, unsigned char*>(manifestBuffer.data),
- aDirectory, mfDigest.mAlgorithm, items, buf);
+ aDirectory, digestToUse, items, buf);
if (NS_FAILED(rv)){
return rv;
}
// We've checked that everything listed in the manifest exists and is signed
// correctly. Now check on disk for extra (unsigned) files.
// Deletes found entries from items as it goes.
rv = CheckDirForUnsignedFiles(aDirectory, EmptyString(), items,
--- a/security/manager/ssl/tests/unit/pycms.py
+++ b/security/manager/ssl/tests/unit/pycms.py
@@ -5,24 +5,30 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Reads a specification from stdin and outputs a PKCS7 (CMS) message with
the desired properties.
The specification format is as follows:
-hash:<hex string>
+sha1:<hex string>
+sha256:<hex string>
signer:
<pycert specification>
-hash is the value that will be put in the messageDigest attribute in
-each SignerInfo of the signerInfos field of the SignedData.
+Eith or both of sha1 and sha256 may be specified. The value of
+each hash directive is what will be put in the messageDigest
+attribute of the SignerInfo that corresponds to the signature
+algorithm defined by the hash algorithm and key type of the
+default key. Together, these comprise the signerInfos field of
+the SignedData. If neither hash is specified, the signerInfos
+will be an empty SET (i.e. there will be no actual signature
+information).
The certificate specification must come last.
-Currently only SHA-1 is supported.
"""
from pyasn1.codec.der import decoder
from pyasn1.codec.der import encoder
from pyasn1.type import tag, univ
from pyasn1_modules import rfc2315, rfc2459
import StringIO
import base64
@@ -47,26 +53,29 @@ class UnknownDirectiveError(Error):
return 'Unknown directive %s' % repr(self.directive)
class CMS(object):
"""Utility class for reading a CMS specification and
generating a CMS message"""
def __init__(self, paramStream):
- self.hash = ''
+ self.sha1 = ''
+ self.sha256 = ''
signerSpecification = StringIO.StringIO()
readingSignerSpecification = False
for line in paramStream.readlines():
if readingSignerSpecification:
print >>signerSpecification, line.strip()
elif line.strip() == 'signer:':
readingSignerSpecification = True
- elif line.startswith('hash:'):
- self.hash = line.strip()[len('hash:'):]
+ elif line.startswith('sha1:'):
+ self.sha1 = line.strip()[len('sha1:'):]
+ elif line.startswith('sha256:'):
+ self.sha256 = line.strip()[len('sha256:'):]
else:
raise UnknownDirectiveError(line.strip())
signerSpecification.seek(0)
self.signer = pycert.Certificate(signerSpecification)
self.signingKey = pykey.keyFromSpecification('default')
def buildAuthenticatedAttributes(self, value, implicitTag=None):
"""Utility function to build a pyasn1 AuthenticatedAttributes
@@ -92,21 +101,25 @@ class CMS(object):
hashAttribute['values'][0] = univ.OctetString(hexValue=value)
authenticatedAttributes[1] = hashAttribute
return authenticatedAttributes
def pykeyHashToDigestAlgorithm(self, pykeyHash):
"""Given a pykey hash algorithm identifier, builds an
AlgorithmIdentifier for use with pyasn1."""
if pykeyHash == pykey.HASH_SHA1:
- algorithmIdentifier = rfc2459.AlgorithmIdentifier()
- algorithmIdentifier['algorithm'] = univ.ObjectIdentifier('1.3.14.3.2.26')
- algorithmIdentifier['parameters'] = univ.Null()
- return algorithmIdentifier
- raise pykey.UnknownHashAlgorithmError(pykeyHash)
+ oidString = '1.3.14.3.2.26'
+ elif pykeyHash == pykey.HASH_SHA256:
+ oidString = '2.16.840.1.101.3.4.2.1'
+ else:
+ raise pykey.UnknownHashAlgorithmError(pykeyHash)
+ algorithmIdentifier = rfc2459.AlgorithmIdentifier()
+ algorithmIdentifier['algorithm'] = univ.ObjectIdentifier(oidString)
+ algorithmIdentifier['parameters'] = univ.Null()
+ return algorithmIdentifier
def buildSignerInfo(self, certificate, pykeyHash, digestValue):
"""Given a pyasn1 certificate, a pykey hash identifier
and a hash value, creates a SignerInfo with the
appropriate values."""
signerInfo = rfc2315.SignerInfo()
signerInfo['version'] = 1
issuerAndSerialNumber = rfc2315.IssuerAndSerialNumber()
@@ -152,17 +165,22 @@ class CMS(object):
certificate = decoder.decode(self.signer.toDER(),
asn1Spec=rfc2459.Certificate())[0]
extendedCertificateOrCertificate['certificate'] = certificate
certificates[0] = extendedCertificateOrCertificate
signedData['certificates'] = certificates
signerInfos = rfc2315.SignerInfos()
- signerInfos[0] = self.buildSignerInfo(certificate, pykey.HASH_SHA1, self.hash)
+ if len(self.sha1) > 0:
+ signerInfos[len(signerInfos)] = self.buildSignerInfo(certificate,
+ pykey.HASH_SHA1, self.sha1)
+ if len(self.sha256) > 0:
+ signerInfos[len(signerInfos)] = self.buildSignerInfo(certificate,
+ pykey.HASH_SHA256, self.sha256)
signedData['signerInfos'] = signerInfos
encoded = encoder.encode(signedData)
anyTag = univ.Any(encoded).subtype(
explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))
contentInfo['content'] = anyTag
return encoder.encode(contentInfo)
--- a/security/manager/ssl/tests/unit/sign_app.py
+++ b/security/manager/ssl/tests/unit/sign_app.py
@@ -28,24 +28,25 @@ def walkDirectory(directory):
for path, dirs, files in os.walk(directory):
for f in files:
fullPath = os.path.join(path, f)
internalPath = re.sub(r'^/', '', fullPath.replace(directory, ''))
paths.append((fullPath, internalPath))
return paths
def signZip(appDirectory, outputFile, issuerName, manifestHashes,
- signatureHashes, doSign):
+ signatureHashes, pkcs7Hashes, doSign):
"""Given a directory containing the files to package up,
an output filename to write to, the name of the issuer of
the signing certificate, a list of hash algorithms to use in
the manifest file, a similar list for the signature file,
- and whether or not to actually sign the resulting package,
- packages up the files in the directory and creates the
- output as appropriate."""
+ a similar list for the pkcs#7 signature, and whether or not
+ to actually sign the resulting package, packages up the
+ files in the directory and creates the output as
+ appropriate."""
mfEntries = []
with zipfile.ZipFile(outputFile, 'w') as outZip:
for (fullPath, internalPath) in walkDirectory(appDirectory):
with open(fullPath) as inputFile:
contents = inputFile.read()
outZip.writestr(internalPath, contents)
@@ -61,17 +62,22 @@ def signZip(appDirectory, outputFile, is
return
mfContents = 'Manifest-Version: 1.0\n\n' + '\n'.join(mfEntries)
sfContents = 'Signature-Version: 1.0\n'
for (hashFunc, name) in signatureHashes:
base64hash = b64encode(hashFunc(mfContents).digest())
sfContents += '%s-Digest-Manifest: %s\n' % (name, base64hash)
- cmsSpecification = 'hash:%s\nsigner:\n' % sha1(sfContents).hexdigest() + \
+ cmsSpecification = ''
+ for name in pkcs7Hashes:
+ hashFunc, _ = hashNameToFunctionAndIdentifier(name)
+ cmsSpecification += '%s:%s\n' % (name,
+ hashFunc(sfContents).hexdigest())
+ cmsSpecification += 'signer:\n' + \
'issuer:%s\n' % issuerName + \
'subject:xpcshell signed app test signer\n' + \
'extension:keyUsage:digitalSignature'
cmsSpecificationStream = StringIO.StringIO()
print >>cmsSpecificationStream, cmsSpecification
cmsSpecificationStream.seek(0)
cms = pycms.CMS(cmsSpecificationStream)
p7 = cms.toDER()
@@ -113,17 +119,25 @@ def main(outputFile, appPath, *args):
parser.add_argument('-i', '--issuer', action='store', help='Issuer name',
default='xpcshell signed apps test root')
parser.add_argument('-m', '--manifest-hash', action='append',
help='Hash algorithms to use in manifest',
default=[])
parser.add_argument('-s', '--signature-hash', action='append',
help='Hash algorithms to use in signature file',
default=[])
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument('-p', '--pkcs7-hash', action='append',
+ help='Hash algorithms to use in PKCS#7 signature',
+ default=[])
+ group.add_argument('-e', '--empty-signerInfos', action='store_true',
+ help='Emit pkcs#7 SignedData with empty signerInfos')
parsed = parser.parse_args(args)
if len(parsed.manifest_hash) == 0:
parsed.manifest_hash.append('sha256')
if len(parsed.signature_hash) == 0:
parsed.signature_hash.append('sha256')
+ if len(parsed.pkcs7_hash) == 0 and not parsed.empty_signerInfos:
+ parsed.pkcs7_hash.append('sha256')
signZip(appPath, outputFile, parsed.issuer,
map(hashNameToFunctionAndIdentifier, parsed.manifest_hash),
map(hashNameToFunctionAndIdentifier, parsed.signature_hash),
- not parsed.no_sign)
+ parsed.pkcs7_hash, not parsed.no_sign)
--- a/security/manager/ssl/tests/unit/test_signed_apps.js
+++ b/security/manager/ssl/tests/unit/test_signed_apps.js
@@ -138,24 +138,25 @@ add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app"),
check_open_result("valid", Cr.NS_OK));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("signed_app_sha256"),
- check_open_result("valid with sha256 hashes in manifest", Cr.NS_OK));
+ check_open_result("valid with sha256 everywhere", Cr.NS_OK));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("signed_app_sha1_and_sha256"),
- check_open_result("valid with sha1 and sha256 hashes in manifest", Cr.NS_OK));
+ check_open_result("valid with sha1 and sha256 hashes everywhere",
+ Cr.NS_OK));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("signed_app_sha256_manifest"),
check_open_result("sha256 hashes in manifest, but only a sha1 hash in the signature file",
Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
@@ -177,33 +178,41 @@ add_test(function () {
Cr.NS_OK));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("sha1_and_sha256_manifest_sha256_signature_file"),
check_open_result("sha1 and sha256 hashes in the manifest, but only sha256 hash in the signature file",
- Cr.NS_OK));
+ Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("sha1_manifest_sha1_and_sha256_signature_file"),
check_open_result("only sha1 in the manifest, sha1 and sha256 hashes in the signature file",
- Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
+ Cr.NS_OK));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot,
original_app_path("sha256_manifest_sha1_and_sha256_signature_file"),
check_open_result("only sha256 in the manifest, sha1 and sha256 hashes in the signature file",
- Cr.NS_OK));
+ Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID));
+});
+
+add_test(function () {
+ certdb.openSignedAppFileAsync(
+ Ci.nsIX509CertDB.AppXPCShellRoot,
+ original_app_path("empty_signerInfos"),
+ check_open_result("the signerInfos in the PKCS#7 signature is empty",
+ Cr.NS_ERROR_CMS_VERIFY_NOT_SIGNED));
});
add_test(function () {
certdb.openSignedAppFileAsync(
Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unsigned_app"),
check_open_result("unsigned", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED));
});
new file mode 100644
index 0000000000000000000000000000000000000000..94261ff0c5ee298142dd7ed6efcc4cab210f6395
GIT binary patch
literal 2458
zc$^FHW@Zs#0D-A~(cV%q8k_upY!K!J;@rf%%(T?v61}YA{Jd%|1qCITNPJmpQE_H|
zo|2V<kq$^AFEKY2$WaObN-DTkl%(c?rNMFtwMqth26~1{T(w*QC>DS7Fx1fovO$;)
zh=W`mU3^_bGBS%5fT$!RRiP*~F(o%uAuYcM%q;;~lM1s&56!sQO_uCpObiSl%m)-p
zNi0d!&&*9sPt_~POYaQubLZuf;sSZn)59eQNKXS{4v@_@jD?RF7#Qz*x;TbZ%z1lm
zBPWA_z>y8N>tCK1Vr(v47<c>Kr2iir=8U2ti+bb|3Y4n$$q4TOr58_EKbLh*2~DUe
zXi5LQ33|*73?K|jLBPc5sO#zHrthd16zpiw#H_`}snzDu_MMlJooPW6voughoQaXq
zklTQhja8eEnMsP3!GIf~h;cy^(-(s#rVj>9jJFmrGchtTiHOdrT=tneXzjbh57<7=
z@Ch!|{r(k64<omMrlGolDjRbs3p0;gML}|LMruxuLUCq#UTTU$VnIPMBp(#z=a(2L
zN$?vP7@8Uy8d(^CK@?cP!obMD6e?h#WvF4G1~E<^i*XRs!2BYhGnEW@5pH2*WngY%
z<Yxeib1^kBGBWJy2wdUPCw9&8Z{D^JHZuWR8{wO;o|t-VdGj?RJV&Ma;BnOz4{pfs
z-8hGBcAiw*r|d`Tqe`>0QVKb$mPRdiZT~KFRb>uS-C5^9a+wz_6f`HVRygeW<n~UV
zL)kfeZ$+E<Kh~|yl-hWi$*Nn+@!j%*2mHLff77_NH>gZnQoFMwv2w!3@Cmk;tdBm}
z;UT}m{Bno$dv&oJq1)4<R@E_0(6_1<Xkb%0Ba<_I?X<+`?T@Aj9Vj(i%ro)smQ&yV
ztd&*0%=vz&IN$P&hnuVQmGUzmZ|RzvDEm(Bn(Exu6FL0MPRppU=<FBk)_?l3f2O$M
zj?Mo(m46il9x=Siyylh!m+9MHCT2zk#>M;wyucV^4wdC+VPR%sZ$ODYW>7epSzEbF
zFV<q1$zAb<@6`Rw{DmDk_O5*f?XjZyD%}=O%b!e=Q(5`gT~7D3WPrk*mVzas7w3Kb
zf3sSgtKDewu}3y}_dk7<Ogiy=OV+_Og}UH*j906c-aq|yv5(8%nbZCpnU$#h<JPA?
zZ_AHlxNh4mZ+6l0<I#(0&paoa9G`e`>zVjOJ57<q{ahTIRK0FZJJ`VcxViZMo_y~$
z?na(kf@Nl}1P=3El5l#f_G^2N!}Vt&ypt=`mWxz|&Era(9{zszD{0Hqr@t-P`7-|d
z4}tkzWs*xa-dgZbK6~fnB~ncnrmlQ7^ZM8GwU<|Z4ZgIaq<Y55VAuBGgU&P4w+OJE
z<FSkIGGH`hK+RcU8k|lsKz0nYjzP*<!EV98w3k>?T9m3A2B|Zw6b$tYxPm<#jZDpS
zT{6>w2~^h?S_fGv_*)oxr$v^zxE1GQXE{5U_+=Lv<eT_L7PzHHRAprp86}lxR#sS8
zgytpNqE!*ExMwT{#vlU-iy%A0*U`_@%{4eg&({rR5vpUj{D4)Rl>$oLi0VQs1*eca
z=YkMJQxiWUv!JM4i=>Pc7Zc;~h!lg8EYqZPv-~t)LrXs&PXm8fTbO~6IuG5Xq)3At
z%ZfsGuSn;D!pKxF18=iJ6PNJNz`|04Tt6o_UlR|L)ZC1$Os{mfNvPEwx{0m<S=oM3
z<&}w+5!wYsr9L?o?%HmS&OYVt&L$QSp(eqW`BDCk203A2fuKNRWD;S<-9mtY21W+-
z_5!+A^g14)wGv+Aqw7Jh$Ps!@U{veqdeMt;gkCOY^a34SFM1+H=uKfmOS0%%(GwOz
Z>uWab$t=K|6&xOHK$rs5rN9B=0RRLSa9#ia
--- a/security/manager/ssl/tests/unit/test_signed_apps/moz.build
+++ b/security/manager/ssl/tests/unit/test_signed_apps/moz.build
@@ -20,23 +20,24 @@ def SignedAppFile(name, flags):
# TEST_HARNESS_FILES.xpcshell.security.manager.ssl.tests.unit.test_signed_apps.
files = TEST_HARNESS_FILES.xpcshell
for part in RELATIVEDIR.split('/'):
files = files[part]
files += ['!%s' % name]
# Temporarily disabled. See bug 1256495.
#signed_app_files = (
-# ['signed_app.zip', '-m', 'sha1', '-s', 'sha1'],
-# ['unknown_issuer_app.zip', '-i', 'unknown issuer', '-m', 'sha1', '-s', 'sha1'],
+# ['signed_app.zip', '-m', 'sha1', '-s', 'sha1', '-p', 'sha1'],
+# ['unknown_issuer_app.zip', '-i', 'unknown issuer', '-m', 'sha1', '-s', 'sha1', '-p', 'sha1'],
# ['unsigned_app.zip', '-n'],
# ['signed_app_sha256.zip'],
-# ['signed_app_sha1_and_sha256.zip', '-m', 'sha256', '-m', 'sha1', '-s', 'sha256', '-s', 'sha1'],
-# ['signed_app_sha256_manifest.zip', '-s', 'sha1'],
-# ['signed_app_sha256_signature_file.zip', '-m', 'sha1'],
-# ['sha1_and_sha256_manifest_sha1_signature_file.zip', '-m', 'sha256', '-m', 'sha1', '-s', 'sha1'],
-# ['sha1_and_sha256_manifest_sha256_signature_file.zip', '-m', 'sha256', '-m', 'sha1'],
-# ['sha1_manifest_sha1_and_sha256_signature_file.zip', '-m', 'sha1', '-s', 'sha1', '-s', 'sha256'],
-# ['sha256_manifest_sha1_and_sha256_signature_file.zip', '-s', 'sha1', '-s', 'sha256'],
+# ['signed_app_sha1_and_sha256.zip', '-m', 'sha256', '-m', 'sha1', '-s', 'sha256', '-s', 'sha1', '-p', 'sha1', '-p', 'sha256'],
+# ['signed_app_sha256_manifest.zip', '-s', 'sha1', '-p', 'sha1'],
+# ['signed_app_sha256_signature_file.zip', '-m', 'sha1', '-p', 'sha1'],
+# ['sha1_and_sha256_manifest_sha1_signature_file.zip', '-m', 'sha256', '-m', 'sha1', '-s', 'sha1', '-p', 'sha1'],
+# ['sha1_and_sha256_manifest_sha256_signature_file.zip', '-m', 'sha256', '-m', 'sha1', '-p', 'sha1'],
+# ['sha1_manifest_sha1_and_sha256_signature_file.zip', '-m', 'sha1', '-s', 'sha1', '-s', 'sha256', '-p', 'sha1'],
+# ['sha256_manifest_sha1_and_sha256_signature_file.zip', '-s', 'sha1', '-s', 'sha256', '-p', 'sha1'],
+# ['empty_signerInfos.zip', '-e'],
#)
#
#for signed_app_file_params in signed_app_files:
# SignedAppFile(signed_app_file_params[0], signed_app_file_params[1:])
index 5770318d0dd764eb641fa32fbc3f32b6bdeea107..bd861664cc99a7672935f465cbcd079d67d431cb
GIT binary patch
literal 2993
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
index 2c6e371c1fcb06361d7ca682039736151eb44fd7..1d2973196823842bf3ceb7a85f536e938f342dd6
GIT binary patch
literal 3011
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
index 4a3c13debcbb5e21f760c7f692f0d754a92acde1..29b92f4b9497fceaf37e65019f546154c0164625
GIT binary patch
literal 2882
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
index d556c06c84987aaf6cab1cdb2e766488a6cf3c87..66ef1037a7fc437f530579a32c96f87632d18662
GIT binary patch
literal 2936
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
index 6fe760615ac4f5816a4cb9772f717b166e4f2eae..b1b71768463dbf8d16f71fffb66125dd6d7da934
GIT binary patch
literal 2813
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
index 947616ebbeac123e45db4dfd617a184f37dcab81..6ac41ad7b3f8cbd521b59313ec02d5ddd57e1e8e
GIT binary patch
literal 3503
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
index c2ad381b16ebc6838cc9534abf20b891ff4c146d..b5cc693c3bce90fb8a994bf39884db9d10720c4e
GIT binary patch
literal 2901
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
Zt;;yDC$j)=R&aQ*0bvSI*KJM^4*;yvIotpM
index e88095e6eeed65f94c50d63d6e941c15ade58c1a..803e43098954ecbddcc73d9fcf4bc95bf54dd3ce
GIT binary patch
literal 2867
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
index 945930f81c5aeaccaea7e87062286dadd3aa58e6..4d1a4d384340559282f19dcdab4d7ef0224e2380
GIT binary patch
literal 2831
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
GARYijNEWUD
index cccaa9993e2c10a513da6b6bed3918ec939a44e6..fe74ed666c6632eb1f63eca3cc85c6f92b759565
GIT binary patch
literal 2781
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
index f1cbb1d751edf9b2b4696c843a1f8df2f1a4b482..8d1de1552a45ad30d65699b8a19484270886fc54
GIT binary patch
literal 971
zc$^FHW@Zs#0D&of(cV%q8k_upY!K!J;@rf%%(T?v61}YA{Jd%|1qCITNPJmpQE_H|
zo|2V<kq$^AFEKY2$WaObN-DTkl%(c?rNMFtwMqth26~1{T(w*QC>DS7Fx1fovO$;)
zh=W`mU3^_bGBS%5fT$!RRiP*~F(o%uAuYcM%q;;~lM1s&56!sQO_uCpObiSl%m)-p
zNi0d!&&*9sPt_~POYaQubLZuf;sSZn)59eQNKXS{4v@_@jD?RF7#Qz*x;TbZ%z1lm
zBPWA_z>y8N>tCK1Vr(v47<c>Kr2iir=8U2ti+bb|3Y4n$$q4TOr58_EKbLh*2~7ds
zj7%cTxbp%GG%zxt=L~eM=&2c@wGy7T(e<DwS%jVw7zsGQn-y#<GZ3x@(p=0S9sqVk
Bf_(r0