Firefox · Security/NSS
CVE-2026-16409
Logic Error in Security/NSS
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
add_signature_testsecurity/manager/ssl/tests/unit/test_signed_apps.js |
modified |
Files Changed
security/manager/ssl/tests/unit/sign_app.pysecurity/manager/ssl/tests/unit/test_signed_apps.jssecurity/manager/ssl/tests/unit/test_signed_apps/moz.buildsecurity/manager/ssl/tests/unit/test_signed_apps/tampered-spki.zipsecurity/manager/tools/pycert.py
Patch
diff --git a/security/manager/ssl/tests/unit/sign_app.py b/security/manager/ssl/tests/unit/sign_app.py
index 66dec481008..599c344db1a 100755
--- a/security/manager/ssl/tests/unit/sign_app.py
+++ b/security/manager/ssl/tests/unit/sign_app.py
@@ -10,8 +10,7 @@ resulting zip file. Mainly for creating test inputs to the
nsIX509CertDB.openSignedAppFileAsync API.
"""
from base64 import b64encode
-from cbor2 import dumps
-from cbor2.types import CBORTag
+from cbor2 import CBORTag, dumps
from hashlib import sha1, sha256
import argparse
from io import StringIO
@@ -133,11 +132,11 @@ def addManifestEntry(filename, hashes, contents, entries):
entries.append(entry)
-def getCert(subject, keyName, issuerName, ee, issuerKey="", validity=""):
+def getCert(subject, keyName, issuerName, ee, issuerKey="", validity="", tamperSpki=False):
"""Helper function to create an X509 cert from a specification.
Takes the subject, the subject key name to use, the issuer name,
a bool whether this is an EE cert or not, and optionally an issuer key
- name."""
+ name, a validity period, and whether or not to tamper with the SPKI."""
certSpecification = (
"issuer:%s\n" % issuerName
+ "subject:"
@@ -156,13 +155,15 @@ def getCert(subject, keyName, issuerName, ee, issuerKey="", validity=""):
certSpecification += "\nissuerKey:%s" % issuerKey
if validity:
certSpecification += "\nvalidity:%s" % validity
+ if tamperSpki:
+ certSpecification += "\ntamperSpki:"
certSpecificationStream = StringIO()
print(certSpecification, file=certSpecificationStream)
certSpecificationStream.seek(0)
return pycert.Certificate(certSpecificationStream)
-def coseAlgorithmToSignatureParams(coseAlgorithm, issuerName, issuerKey, certValidity):
+def coseAlgorithmToSignatureParams(coseAlgorithm, issuerName, issuerKey, certValidity, tamperSpki):
"""Given a COSE algorithm ('ES256', 'ES384', 'ES512'), an issuer
name, the name of the issuer's key, and a validity period, returns a
(algorithm id, pykey.ECCKey, encoded certificate) triplet for use
@@ -188,6 +189,7 @@ def coseAlgorithmToSignatureParams(coseAlgorithm, issuerName, issuerKey, certVal
True,
issuerKey,
certValidity,
+ tamperSpki,
)
return (algId, key, ee.toDER())
@@ -199,6 +201,7 @@ def signZip(
rootName,
rootKey,
certValidity,
+ tamperSpki,
manifestHashes,
signatureHashes,
pkcs7Hashes,
@@ -269,6 +272,7 @@ def signZip(
coseIssuerName,
issuerKey,
certValidity,
+ tamperSpki,
)
for coseAlgorithm in coseAlgorithms
]
@@ -398,6 +402,12 @@ def main(outputFile, appPath, *args):
+ "algorithms (out of ES256, ES384, and ES512)",
default=[],
)
+ parser.add_argument(
+ "-t",
+ "--tamper-spki",
+ action="store_true",
+ help="Whether or not to tamper with the SPKI (only supported for COSE signatures)",
+ )
parser.add_argument(
"-z",
"--pad-headers",
@@ -432,6 +442,7 @@ def main(outputFile, appPath, *args):
parsed.root,
parsed.root_key,
parsed.cert_validity,
+ parsed.tamper_spki,
[hashNameToFunctionAndIdentifier(h) for h in parsed.manifest_hash],
[hashNameToFunctionAndIdentifier(h) for h in parsed.signature_hash],
parsed.pkcs7_hash,
diff --git a/security/manager/ssl/tests/unit/test_signed_apps.js b/security/manager/ssl/tests/unit/test_signed_apps.js
index 1fe15b0bada..c370b6e9b31 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps.js
+++ b/security/manager/ssl/tests/unit/test_signed_apps.js
@@ -534,6 +534,18 @@ add_signature_test(COSEOnly, function () {
);
});
+add_signature_test(COSEOnly, function () {
+ certdb.openSignedAppFileAsync(
+ Ci.nsIX509CertDB.AppXPCShellRoot,
+ original_app_path("tampered-spki"),
+ check_open_result(
+ "tampered-spki",
+ Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
+ []
+ )
+ );
+});
+
// Sanity check to ensure a no-op tampering gives a valid result
add_signature_test(PKCS7WithSHA1OrSHA256, function () {
let tampered = tampered_app_path("identity_tampering");
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/moz.build b/security/manager/ssl/tests/unit/test_signed_apps/moz.build
index 603549de778..5ad469c41fa 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps/moz.build
+++ b/security/manager/ssl/tests/unit/test_signed_apps/moz.build
@@ -71,6 +71,7 @@ def SignedAppFile(name, flags, app_directory="app/"):
# SignedAppFile('validity_expired.zip', ['-c', 'ES256', '-p', 'sha256', '--cert-validity', '19700101-19701212'])
# SignedAppFile('validity_not_yet_valid.zip', ['-c', 'ES256', '-p', 'sha256', '--cert-validity', '99990101-99991212'])
# SignedAppFile('alternate-root.zip', ['-k', 'alternate', '-c', 'ES256', '-p', 'sha256'])
+# SignedAppFile('tampered-spki.zip', ['-c', 'ES256', '-t'])
# To generate a new entry, add SignedAppFile, run mach build and copy from
# objdir/_tests/xpcshell/security/manager/ssl/tests/unit/test_signed_apps/
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/tampered-spki.zip b/security/manager/ssl/tests/unit/test_signed_apps/tampered-spki.zip
new file mode 100644
index 00000000000..7155dc07cbe
Binary files /dev/null and b/security/manager/ssl/tests/unit/test_signed_apps/tampered-spki.zip differ
diff --git a/security/manager/tools/pycert.py b/security/manager/tools/pycert.py
index c2be31d88d5..c4486279e1c 100755
--- a/security/manager/tools/pycert.py
+++ b/security/manager/tools/pycert.py
@@ -16,6 +16,7 @@ subject:<subject distinguished name specification>
[validity:<YYYYMMDD-YYYYMMDD|duration in days>]
[issuerKey:<key specification>]
[subjectKey:<key specification>]
+[tamperSpki:]
[signature:{sha256WithRSAEncryption,sha1WithRSAEncryption,
md5WithRSAEncryption,ecdsaWithSHA256,ecdsaWithSHA384,
ecdsaWithSHA512}]
@@ -391,6 +392,7 @@ class Certificate:
self.savedEmbeddedSCTListData = None
self.subjectKey = pykey.keyFromSpecification("default")
self.issuerKey = pykey.keyFromSpecification("default")
+ self.tamperSpki = False
self.serialNumber = None
self.decodeParams(paramStream)
# If a serial number wasn't specified, generate one based on
@@ -456,6 +458,8 @@ class Certificate:
self.setupKey("issuer", value)
elif param == "subjectKey":
self.setupKey("subject", value)
+ elif param == "tamperSpki":
+ self.tamperSpki = True
elif param == "signature":
self.signature = value
elif param == "serialNumber":
@@ -798,9 +802,20 @@ class Certificate:
tbsCertificate["issuer"] = self.getIssuer()
tbsCertificate["validity"] = self.getValidity()
tbsCertificate["subject"] = self.getSubject()
- tbsCertificate["subjectPublicKeyInfo"] = (
- self.subjectKey.asSubjectPublicKeyInfo()
- )
+ if self.tamperSpki:
+ algorithmIdentifier = rfc5280.AlgorithmIdentifier()
+ algorithmIdentifier["algorithm"] = univ.ObjectIdentifier(
+ "1.3.6.1.4.1.13769.666.666.666.1.500.9.1"
+ )
+ algorithmIdentifier["parameters"] = univ.Null()
+ spki = rfc5280.SubjectPublicKeyInfo()
+ spki["algorithm"] = algorithmIdentifier
+ spki["subjectPublicKey"] = univ.BitString("'0500'H")
+ tbsCertificate["subjectPublicKeyInfo"] = spki
+ else:
+ tbsCertificate["subjectPublicKeyInfo"] = (
+ self.subjectKey.asSubjectPublicKeyInfo()
+ )
if self.extensions:
extensions = rfc5280.Extensions().subtype(
explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/security/manager/ssl/tests/unit/sign_app.py b/security/manager/ssl/tests/unit/sign_app.py
index 66dec481008..599c344db1a 100755
--- a/security/manager/ssl/tests/unit/sign_app.py
+++ b/security/manager/ssl/tests/unit/sign_app.py
@@ -10,8 +10,7 @@ resulting zip file. Mainly for creating test inputs to the
nsIX509CertDB.openSignedAppFileAsync API.
"""
from base64 import b64encode
-from cbor2 import dumps
-from cbor2.types import CBORTag
+from cbor2 import CBORTag, dumps
from hashlib import sha1, sha256
import argparse
from io import StringIO
@@ -133,11 +132,11 @@ def addManifestEntry(filename, hashes, contents, entries):
entries.append(entry)
-def getCert(subject, keyName, issuerName, ee, issuerKey="", validity=""):
+def getCert(subject, keyName, issuerName, ee, issuerKey="", validity="", tamperSpki=False):
"""Helper function to create an X509 cert from a specification.
Takes the subject, the subject key name to use, the issuer name,
a bool whether this is an EE cert or not, and optionally an issuer key
- name."""
+ name, a validity period, and whether or not to tamper with the SPKI."""
certSpecification = (
"issuer:%s\n" % issuerName
+ "subject:"
@@ -156,13 +155,15 @@ def getCert(subject, keyName, issuerName, ee, issuerKey="", validity=""):
certSpecification += "\nissuerKey:%s" % issuerKey
if validity:
certSpecification += "\nvalidity:%s" % validity
+ if tamperSpki:
+ certSpecification += "\ntamperSpki:"
certSpecificationStream = StringIO()
print(certSpecification, file=certSpecificationStream)
certSpecificationStream.seek(0)
return pycert.Certificate(certSpecificationStream)
-def coseAlgorithmToSignatureParams(coseAlgorithm, issuerName, issuerKey, certValidity):
+def coseAlgorithmToSignatureParams(coseAlgorithm, issuerName, issuerKey, certValidity, tamperSpki):
"""Given a COSE algorithm ('ES256', 'ES384', 'ES512'), an issuer
name, the name of the issuer's key, and a validity period, returns a
(algorithm id, pykey.ECCKey, encoded certificate) triplet for use
@@ -188,6 +189,7 @@ def coseAlgorithmToSignatureParams(coseAlgorithm, issuerName, issuerKey, certVal
True,
issuerKey,
certValidity,
+ tamperSpki,
)
return (algId, key, ee.toDER())
@@ -199,6 +201,7 @@ def signZip(
rootName,
rootKey,
certValidity,
+ tamperSpki,
manifestHashes,
signatureHashes,
pkcs7Hashes,
@@ -269,6 +272,7 @@ def signZip(
coseIssuerName,
issuerKey,
certValidity,
+ tamperSpki,
)
for coseAlgorithm in coseAlgorithms
]
@@ -398,6 +402,12 @@ def main(outputFile, appPath, *args):
+ "algorithms (out of ES256, ES384, and ES512)",
default=[],
)
+ parser.add_argument(
+ "-t",
+ "--tamper-spki",
+ action="store_true",
+ help="Whether or not to tamper with the SPKI (only supported for COSE signatures)",
+ )
parser.add_argument(
"-z",
"--pad-headers",
@@ -432,6 +442,7 @@ def main(outputFile, appPath, *args):
parsed.root,
parsed.root_key,
parsed.cert_validity,
+ parsed.tamper_spki,
[hashNameToFunctionAndIdentifier(h) for h in parsed.manifest_hash],
[hashNameToFunctionAndIdentifier(h) for h in parsed.signature_hash],
parsed.pkcs7_hash,
diff --git a/security/manager/ssl/tests/unit/test_signed_apps.js b/security/manager/ssl/tests/unit/test_signed_apps.js
index 1fe15b0bada..c370b6e9b31 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps.js
+++ b/security/manager/ssl/tests/unit/test_signed_apps.js
@@ -534,6 +534,18 @@ add_signature_test(COSEOnly, function () {
);
});
+add_signature_test(COSEOnly, function () {
+ certdb.openSignedAppFileAsync(
+ Ci.nsIX509CertDB.AppXPCShellRoot,
+ original_app_path("tampered-spki"),
+ check_open_result(
+ "tampered-spki",
+ Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
+ []
+ )
+ );
+});
+
// Sanity check to ensure a no-op tampering gives a valid result
add_signature_test(PKCS7WithSHA1OrSHA256, function () {
let tampered = tampered_app_path("identity_tampering");
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/moz.build b/security/manager/ssl/tests/unit/test_signed_apps/moz.build
index 603549de778..5ad469c41fa 100644
--- a/security/manager/ssl/tests/unit/test_signed_apps/moz.build
+++ b/security/manager/ssl/tests/unit/test_signed_apps/moz.build
@@ -71,6 +71,7 @@ def SignedAppFile(name, flags, app_directory="app/"):
# SignedAppFile('validity_expired.zip', ['-c', 'ES256', '-p', 'sha256', '--cert-validity', '19700101-19701212'])
# SignedAppFile('validity_not_yet_valid.zip', ['-c', 'ES256', '-p', 'sha256', '--cert-validity', '99990101-99991212'])
# SignedAppFile('alternate-root.zip', ['-k', 'alternate', '-c', 'ES256', '-p', 'sha256'])
+# SignedAppFile('tampered-spki.zip', ['-c', 'ES256', '-t'])
# To generate a new entry, add SignedAppFile, run mach build and copy from
# objdir/_tests/xpcshell/security/manager/ssl/tests/unit/test_signed_apps/
diff --git a/security/manager/ssl/tests/unit/test_signed_apps/tampered-spki.zip b/security/manager/ssl/tests/unit/test_signed_apps/tampered-spki.zip
new file mode 100644
index 00000000000..7155dc07cbe
Binary files /dev/null and b/security/manager/ssl/tests/unit/test_signed_apps/tampered-spki.zip differ
Loading diff…
References
On This Page