commit 983a0a54341cb83645dcae94c85d9698d7ac35da parent b7f898100ef2f03defd8e3e1f5b3bb72b8f686eb Author: Dennis Jackson <git@dennis-jackson.uk> Date: Thu, 16 Oct 2025 15:51:39 +0000 Bug 1992446 - upgrade NSS to f9041cc46f7495257b639e7e36fa8f2f0d50faa0. r=nss-reviewers,jschanck UPGRADE_NSS_RELEASE Differential Revision: https://phabricator.services.mozilla.com/D268682 Diffstat:
60 files changed, 1562 insertions(+), 315 deletions(-)
diff --git a/security/nss/automation/abi-check/expected-report-libnss3.so.txt b/security/nss/automation/abi-check/expected-report-libnss3.so.txt @@ -1,10 +1,15 @@ -6 Added functions: - - 'function const SECItem* PK11_GetPublicValueFromPublicKey(const SECKEYPublicKey*)' {PK11_GetPublicValueFromPublicKey@@NSS_3.117} - 'function SECKEYPrivateKey* PK11_UnwrapPrivKeyByKeyType(PK11SlotInfo*, PK11SymKey*, CK_MECHANISM_TYPE, SECItem*, SECItem*, SECItem*, const SECItem*, PRBool, PRBool, KeyType, unsigned int, void*)' {PK11_UnwrapPrivKeyByKeyType@@NSS_3.117} - 'function const char* SECKEY_GetKeyTypeString(KeyType)' {SECKEY_GetKeyTypeString@@NSS_3.117} - 'function SECStatus SEC_CreateSignatureAlgorithmID(PLArenaPool*, SECAlgorithmID*, SECOidTag, SECOidTag, const SECItem*, const SECKEYPrivateKey*, const SECKEYPublicKey*)' {SEC_CreateSignatureAlgorithmID@@NSS_3.117} - 'function SECItem* SEC_CreateVerifyAlgorithmParameters(PLArenaPool*, SECItem*, SECOidTag, SECOidTag, const SECItem*, const SECKEYPublicKey*)' {SEC_CreateVerifyAlgorithmParameters@@NSS_3.117} - 'function SECOidTag SEC_GetSignatureAlgorithmOidTagByKey(const SECKEYPrivateKey*, const SECKEYPublicKey*, SECOidTag)' {SEC_GetSignatureAlgorithmOidTagByKey@@NSS_3.117} +1 function with some indirect sub-type change: + + [C]'function SECStatus CERT_AddOCSPAcceptableResponses(CERTOCSPRequest*, SECOidTag, ...)' at ocsp.c:2202:1 has some indirect sub-type changes: + parameter 2 of type 'typedef SECOidTag' has sub-type changes: + underlying type 'enum __anonymous_enum__' at secoidt.h:34:1 changed: + type size hasn't changed + 1 enumerator insertion: + '__anonymous_enum__::SEC_OID_SECP256R1MLKEM768' value '394' + + 1 enumerator change: + '__anonymous_enum__::SEC_OID_TOTAL' from value '394' to '395' at secoidt.h:34:1 + + diff --git a/security/nss/automation/abi-check/expected-report-libnssutil3.so.txt b/security/nss/automation/abi-check/expected-report-libnssutil3.so.txt @@ -1,5 +1,15 @@ -1 Added function: +1 function with some indirect sub-type change: + + [C]'function SECOidTag HASH_GetHMACOidTagByHashOidTag_Util(SECOidTag)' at nsshash.c:149:1 has some indirect sub-type changes: + return type changed: + underlying type 'enum __anonymous_enum__' at secoidt.h:34:1 changed: + type size hasn't changed + 1 enumerator insertion: + '__anonymous_enum__::SEC_OID_SECP256R1MLKEM768' value '394' + + 1 enumerator change: + '__anonymous_enum__::SEC_OID_TOTAL' from value '394' to '395' at secoidt.h:34:1 + - 'function SECOidTag SECOID_FindOIDTagFromDescripton(const char*, size_t, PRBool)' {SECOID_FindOIDTagFromDescripton@@NSSUTIL_3.117} diff --git a/security/nss/automation/abi-check/expected-report-libsmime3.so.txt b/security/nss/automation/abi-check/expected-report-libsmime3.so.txt @@ -0,0 +1,44 @@ + +1 function with some indirect sub-type change: + + [C]'function PK11SymKey* NSS_CMSContentInfo_GetBulkKey(NSSCMSContentInfo*)' at cmscinfo.c:426:1 has some indirect sub-type changes: + parameter 1 of type 'NSSCMSContentInfo*' has sub-type changes: + in pointed to type 'typedef NSSCMSContentInfo' at cmst.h:54:1: + underlying type 'struct NSSCMSContentInfoStr' at cmst.h:126:1 changed: + type size hasn't changed + 1 data member changes (2 filtered): + type of 'NSSCMSContent NSSCMSContentInfoStr::content' changed: + underlying type 'union NSSCMSContentUnion' at cmst.h:113:1 changed: + type size hasn't changed + 1 data member changes (3 filtered): + type of 'NSSCMSEncryptedData* NSSCMSContentUnion::encryptedData' changed: + in pointed to type 'typedef NSSCMSEncryptedData' at cmst.h:65:1: + underlying type 'struct NSSCMSEncryptedDataStr' at cmst.h:470:1 changed: + type size hasn't changed + 1 data member changes (1 filtered): + type of 'NSSCMSAttribute** NSSCMSEncryptedDataStr::unprotectedAttr' changed: + in pointed to type 'NSSCMSAttribute*': + in pointed to type 'typedef NSSCMSAttribute' at cmst.h:69:1: + underlying type 'struct NSSCMSAttributeStr' at cmst.h:489:1 changed: + type size hasn't changed + 1 data member change: + type of 'SECOidData* NSSCMSAttributeStr::typeTag' changed: + in pointed to type 'typedef SECOidData' at secoidt.h:16:1: + underlying type 'struct SECOidDataStr' at secoidt.h:567:1 changed: + type size hasn't changed + 1 data member change: + type of 'SECOidTag SECOidDataStr::offset' changed: + underlying type 'enum __anonymous_enum__' at secoidt.h:34:1 changed: + type size hasn't changed + 1 enumerator insertion: + '__anonymous_enum__::SEC_OID_SECP256R1MLKEM768' value '394' + + 1 enumerator change: + '__anonymous_enum__::SEC_OID_TOTAL' from value '394' to '395' at secoidt.h:34:1 + + + + + + + diff --git a/security/nss/automation/abi-check/expected-report-libssl3.so.txt b/security/nss/automation/abi-check/expected-report-libssl3.so.txt @@ -0,0 +1,30 @@ + +1 function with some indirect sub-type change: + + [C]'function SECStatus SSL_SetSessionTicketKeyPair(SECKEYPublicKey*, SECKEYPrivateKey*)' at sslsnce.c:1723:1 has some indirect sub-type changes: + parameter 1 of type 'SECKEYPublicKey*' has sub-type changes: + in pointed to type 'typedef SECKEYPublicKey' at keythi.h:221:1: + underlying type 'struct SECKEYPublicKeyStr' at keythi.h:205:1 changed: + type size hasn't changed + 1 data member change: + type of 'union {SECKEYRSAPublicKey rsa; SECKEYDSAPublicKey dsa; SECKEYDHPublicKey dh; SECKEYKEAPublicKey kea; SECKEYFortezzaPublicKey fortezza; SECKEYECPublicKey ec; SECKEYKyberPublicKey kyber; SECKEYMLDSAPublicKey mldsa;} SECKEYPublicKeyStr::u' changed: + type size hasn't changed + 1 data member change: + type of 'SECKEYMLDSAPublicKey mldsa' changed: + underlying type 'struct SECKEYMLDSAPublicKeyStr' at keythi.h:196:1 changed: + type size hasn't changed + 1 data member change: + type of 'SECOidTag SECKEYMLDSAPublicKeyStr::paramSet' changed: + underlying type 'enum __anonymous_enum__' at secoidt.h:34:1 changed: + type size hasn't changed + 1 enumerator insertion: + '__anonymous_enum__::SEC_OID_SECP256R1MLKEM768' value '394' + + 1 enumerator change: + '__anonymous_enum__::SEC_OID_TOTAL' from value '394' to '395' at secoidt.h:34:1 + + + + + + diff --git a/security/nss/automation/abi-check/previous-nss-release b/security/nss/automation/abi-check/previous-nss-release @@ -1 +1 @@ -NSS_3_116_BRANCH +NSS_3_117_BRANCH diff --git a/security/nss/automation/release/nss-release-helper.py b/security/nss/automation/release/nss-release-helper.py @@ -38,6 +38,10 @@ def check_call_noisy(cmd, *args, **kwargs): check_call(cmd, *args, **kwargs) +def print_separator(): + print("=" * 70) + + def exit_with_failure(what): print("failure: {}".format(what)) sys.exit(2) @@ -127,7 +131,9 @@ def remove_beta_status(): print("--- removing beta flags. Existing versions were:") print_beta_versions() toggle_beta_status(False) + print("=" * 70) print("--- finished modifications, new versions are:") + print("=" * 70) print_beta_versions() @@ -151,8 +157,8 @@ def print_root_ca_version(): check_call_noisy(["grep", "define *NSS_BUILTINS_LIBRARY_VERSION", nssckbi_h]) -def ensure_arguments_after_action(how_many, usage): - if (len(sys.argv) != (2 + how_many)): +def ensure_arguments_count(args, how_many, usage): + if (len(args) != how_many): exit_with_failure("incorrect number of arguments, expected parameters are:\n" + usage) @@ -201,10 +207,10 @@ def set_full_lib_versions(version): repl=r'\g<1>{}\g<3>'.format(version))]) -def set_root_ca_version(): - ensure_arguments_after_action(2, "major_version minor_version") - major = args[1].strip() - minor = args[2].strip() +def set_root_ca_version(args): + ensure_arguments_count(args, 2, "major_version minor_version") + major = args[0].strip() + minor = args[1].strip() version = major + '.' + minor inplace_replace(filename=nssckbi_h, replacements=[ @@ -244,47 +250,518 @@ def set_all_lib_versions(version, major, minor, patch, build): set_build_versions(build) -def set_version_to_minor_release(): - ensure_arguments_after_action(2, "major_version minor_version") - major = args[1].strip() - minor = args[2].strip() +def set_version_to_minor_release(args): + ensure_arguments_count(args, 2, "major_version minor_version") + major = args[0].strip() + minor = args[1].strip() version = major + '.' + minor patch = "0" build = "0" set_all_lib_versions(version, major, minor, patch, build) -def set_version_to_patch_release(): - ensure_arguments_after_action(3, "major_version minor_version patch_release") - major = args[1].strip() - minor = args[2].strip() - patch = args[3].strip() +def set_version_to_patch_release(args): + ensure_arguments_count(args, 3, "major_version minor_version patch_release") + major = args[0].strip() + minor = args[1].strip() + patch = args[2].strip() version = major + '.' + minor + '.' + patch build = "0" set_all_lib_versions(version, major, minor, patch, build) -def set_release_candidate_number(): - ensure_arguments_after_action(1, "release_candidate_number") - build = args[1].strip() +def set_release_candidate_number(args): + ensure_arguments_count(args, 1, "release_candidate_number") + build = args[0].strip() set_build_versions(build) -def set_4_digit_release_number(): - ensure_arguments_after_action(4, "major_version minor_version patch_release 4th_digit_release_number") - major = args[1].strip() - minor = args[2].strip() - patch = args[3].strip() - build = args[4].strip() +def set_4_digit_release_number(args): + ensure_arguments_count(args, 4, "major_version minor_version patch_release 4th_digit_release_number") + major = args[0].strip() + minor = args[1].strip() + patch = args[2].strip() + build = args[3].strip() version = major + '.' + minor + '.' + patch + '.' + build set_all_lib_versions(version, major, minor, patch, build) -def create_nss_release_archive(): - ensure_arguments_after_action(3, "nss_release_version nss_hg_release_tag path_to_stage_directory") - nssrel = args[1].strip() # e.g. 3.19.3 - nssreltag = args[2].strip() # e.g. NSS_3_19_3_RTM - stagedir = args[3].strip() # e.g. ../stage +def make_release_branch(args): + ensure_arguments_count(args, 2, "version_string remote") + version_string = args[0].strip() + remote = args[1].strip() + + major, minor, patch = parse_version_string(version_string) + if patch is not None: + exit_with_failure("make_release_branch expects a minor version (e.g., '3.117'), not a patch version.") + + version = f"{major}.{minor}" + branch_name = f"NSS_{major}_{minor}_BRANCH" + tag_name = f"NSS_{major}_{minor}_BETA1" + + print_separator() + print("MAKE RELEASE BRANCH") + print_separator() + print(f"Version: {version}") + print(f"Remote: {remote}") + print_separator() + + response = input('Are these parameters correct? [yN]: ') + if 'y' not in response.lower(): + print("Aborted.") + sys.exit(0) + print_separator() + + # Step 1: Update local repo + print("Step 1: Updating local repository...") + check_call_noisy(["hg", "pull"]) + check_call_noisy(["hg", "checkout", "default"]) + print_separator() + + print("Step 2: Checking working directory is clean") + hg_status = check_output(["hg", "status"]).decode('utf-8').strip() + if hg_status: + print() + print("ERROR: Working directory is not clean") + print(hg_status) + print() + exit_with_failure("Please commit or revert changes then run this command again. You can reset your working directory with 'hg update -C' and 'hg purge if you want to discard all local changes.") + + branches = check_output(["hg", "branches"]).decode('utf-8').strip() + if branch_name in branches: + exit_with_failure(f"Branch {branch_name} already exists.") + print_separator() + + # Step 2: Verify version numbers are correct + print("Step 2: Verifying version numbers are correct...") + set_version_to_minor_release([major, minor]) + print("=" * 70) + set_beta_status() + print("=" * 70) + # Check if there are any uncommitted changes + hg_status = check_output(["hg", "status"]).decode('utf-8').strip() + if hg_status: + print() + print("ERROR: Version numbers are not correctly set") + print() + print() + exit_with_failure("Please check the correct version to freeze, or update the version numbers then run this command again.") + + print("Version numbers verified - no changes needed.") + print_separator() + + # Step 3: Create branch + print(f"Step 3: Creating branch {branch_name}...") + check_call_noisy(["hg", "branch", branch_name]) + print_separator() + + # Step 4: Create tag + print(f"Step 4: Creating tag {tag_name}...") + check_call_noisy(["hg", "tag", tag_name]) + print_separator() + + # Step 5: Show outgoing changes + response = input('Display outgoing changes? [yN]: ') + if 'y' in response.lower(): + print() + check_call_noisy(["hg", "outgoing", "-p", remote]) + print_separator() + + # Step 6: Prompt user and push if confirmed + response = input('Push this branch and tag to the NSS repository? [yN]: ') + if 'y' in response.lower(): + print("Pushing branch and tag...") + check_call_noisy(["hg", "push", "--new-branch", remote]) + print_separator() + print("SUCCESS: Branch and tag have been pushed!") + print_separator() + print() + print("NEXT STEPS:") + print(f"1. Wait for the changes to sync to Github: https://github.com/nss-dev/nss/tree/{branch_name}") + print("2. In your mozilla-unified repository, run:") + print(f" ./mach nss-uplift {tag_name}") + print() + else: + print("Branch and tag have NOT been pushed to the repository.") + print("The local branch and tag remain in your working directory.") + print_separator() + + +def parse_version_string(version_string): + """Parse a version string like '3.117' or '3.117.1' and return (major, minor, patch) + + For versions like '3.117', patch will be None. + Returns: tuple of (major, minor, patch) where patch can be None + """ + parts = version_string.split('.') + if len(parts) < 2: + exit_with_failure(f"Invalid version string '{version_string}'. Expected format: 'major.minor' or 'major.minor.patch'") + + major = parts[0].strip() + minor = parts[1].strip() + patch = parts[2].strip() if len(parts) >= 3 else None + + # Validate that they're numbers + try: + int(major) + int(minor) + if patch is not None: + int(patch) + except ValueError: + exit_with_failure(f"Invalid version string '{version_string}'. Version components must be numbers.") + + return major, minor, patch + + +def version_string_to_RTM_tag(version_string): + parts = version_string.split('.') + return "NSS_" + "_".join(parts) + "_RTM" + +def version_string_to_underscore(version_string): + return version_string.replace('.', '_') + + +def generate_release_note(args): + ensure_arguments_count(args, 3, "this_release_version_string revision_or_tag previous_release_version_string ") + + version = args[0].strip() + this_tag = args[1].strip() # Typically going to be . + version_underscore = version_string_to_underscore(version) + prev_tag = version_string_to_RTM_tag(args[2].strip()) + + # Get the NSPR version + nspr_version = check_output(['hg', 'cat', '-r', this_tag, 'automation/release/nspr-version.txt']).decode('utf-8').split("\n")[0].strip() + + # Get the current date + from datetime import datetime + current_date = datetime.now().strftime("%-d %B %Y") + + # Get the list of bugs from hg log + # Get log entries between previous tag and current HEAD + command = ["hg", "log", "-r", f"{prev_tag}:{this_tag}", "--template", "{desc|firstline}\\n"] + log_output = check_output(command).decode('utf-8') + + # Extract bug numbers and descriptions + bug_lines = [] + for line in reversed(log_output.split('\n')): + if 'Bug' in line or 'bug' in line: + line = line.strip() + line = line.split("r=")[0].strip() + + # Match patterns like "Bug 1234567 Something" and convert to "Bug 1234567 - Something" + line = re.sub(r'(Bug\s+\d+)\s+([^-])', r'\1 - \2', line, flags=re.IGNORECASE) + + # Add a full stop at the end if there isn't one + if line: + line = line.rstrip(',') + + if line and not line.endswith('.'): + line = line + '.' + + if line and line not in bug_lines: + bug_lines.append(line) + + changes_text = "\n".join([f" - {line}" for line in bug_lines]) + + # Create the release notes content + rst_content = f""".. _mozilla_projects_nss_nss_{version_underscore}_release_notes: + +NSS {version} release notes +======================== + +`Introduction <#introduction>`__ +-------------------------------- + +.. container:: + + Network Security Services (NSS) {version} was released on *{current_date}**. + +`Distribution Information <#distribution_information>`__ +-------------------------------------------------------- + +.. container:: + + The HG tag is NSS_{version_underscore}_RTM. NSS {version} requires NSPR {nspr_version} or newer. + + NSS {version} source distributions are available on ftp.mozilla.org for secure HTTPS download: + + - Source tarballs: + https://ftp.mozilla.org/pub/mozilla.org/security/nss/releases/NSS_{version_underscore}_RTM/src/ + + Other releases are available :ref:`mozilla_projects_nss_releases`. + +.. _changes_in_nss_{version}: + +`Changes in NSS {version} <#changes_in_nss_{version}>`__ +------------------------------------------------------------------ + +.. container:: + +{changes_text} + +""" + return rst_content + + +def generate_release_notes_index(args): + ensure_arguments_count(args, 2, "latest_release_version latest_esr_version") + latest_version = args[0].strip() # e.g. 3.116 + esr_version = args[1].strip() # e.g. 3.112.1 + + latest_underscore = version_string_to_underscore(latest_version) + esr_underscore = version_string_to_underscore(esr_version) + + # Read all release note files from doc/rst/releases/ + release_dir = "doc/rst/releases" + if not os.path.exists(release_dir): + exit_with_failure(f"Release notes directory not found: {release_dir}") + + # Get all nss_*.rst files (excluding index.rst) + release_files = [] + for filename in os.listdir(release_dir): + if filename.startswith("nss_") and filename.endswith(".rst") and filename != "index.rst": + release_files.append(filename) + + # Sort release files in reverse order (newest first) + # Extract version numbers for proper sorting + def version_key(filename): + # Extract version parts from filename like nss_3_116.rst + parts = filename.replace("nss_", "").replace(".rst", "").split("_") + # Convert to integers for proper numerical sorting + return [int(p) for p in parts] + + release_files.sort(key=version_key, reverse=True) + + # Build the toctree content + toctree_lines = "\n".join([f" {f}" for f in release_files]) + + # Create the index.rst content + index_content = f""".. _mozilla_projects_nss_releases: + +Release Notes +============= + +.. toctree:: + :maxdepth: 0 + :glob: + :hidden: + +{toctree_lines} + +.. note:: + + **NSS {latest_version}** is the latest version of NSS. + Complete release notes are available here: :ref:`mozilla_projects_nss_nss_{latest_underscore}_release_notes` + + **NSS {esr_version} (ESR)** is the latest ESR version of NSS. + Complete release notes are available here: :ref:`mozilla_projects_nss_nss_{esr_underscore}_release_notes` + +""" + + index_file = os.path.join(release_dir, "index.rst") + with open(index_file, "w") as f: + f.write(index_content) + + print(f"Generated {index_file}") + print() + print("=" * 70) + print("Content:") + print("=" * 70) + print(index_content) + + +def release_nss(args): + ensure_arguments_count(args, 4, "version_string previous_version esr_version remote") + version_string = args[0].strip() + previous_version = args[1].strip() + esr_version = args[2].strip() + remote = args[3].strip() + + major, minor, patch = parse_version_string(version_string) + + # Build version string and related names + version = version_string + version_underscore = version_string_to_underscore(version_string) + branch_name = f"NSS_{major}_{minor}_BRANCH" + rtm_tag = f"NSS_{version_underscore}_RTM" + release_note_file = f"doc/rst/releases/nss_{version_underscore}.rst" + + print_separator() + print("RELEASE NSS") + print_separator() + print(f"Release version: {version}") + print(f"Previous version: {previous_version}") + print(f"ESR version: {esr_version}") + print(f"Remote: {remote}") + print_separator() + + response = input('Are these parameters correct? [yN]: ') + if 'y' not in response.lower(): + print("Aborted.") + sys.exit(0) + print_separator() + + print("=" * 70) + print(f"Starting NSS {version} release process") + print("=" * 70) + print() + + # Step 1: Update local repo + print("Step 1: Updating local repository...") + check_call_noisy(["hg", "pull"]) + print_separator() + + # Step 2: Checking working directory is clean + print("Step 2: Checking working directory is clean...") + hg_status = check_output(["hg", "status"]).decode('utf-8').strip() + if hg_status: + print() + print("ERROR: Working directory is not clean") + print(hg_status) + print() + exit_with_failure("Please commit or revert changes then run this command again. You can reset your working directory with 'hg update -C' and 'hg purge if you want to discard all local changes.") + print_separator() + + # Step 3: Make sure we're on the appropriate branch + print(f"Step 3: Checking out branch {branch_name}...") + try: + check_call_noisy(["hg", "checkout", branch_name]) + except Exception as e: + exit_with_failure(f"Failed to checkout branch {branch_name}. Does it exist?") + print_separator() + + # Step 4: Check for any existing commits or tags + print("Step 4: Checking for existing release commits or tags...") + + # Check if RTM tag already exists + tags_output = check_output(["hg", "tags"]).decode('utf-8') + if rtm_tag in tags_output: + exit_with_failure(f"Tag {rtm_tag} already exists. Has this release already been made?") + + # Check for recent commits with the same commit messages we're about to make + version_commit_message = f"Set version numbers to {version} final" + release_notes_commit_message = f"Release notes for NSS {version}" + + recent_log = check_output(["hg", "log", "-l", "5", "--template", "{desc|firstline}\\n"]).decode('utf-8') + + if version_commit_message in recent_log: + exit_with_failure(f"Found recent commit with message '{version_commit_message}'. Has this release already been started?") + + if release_notes_commit_message in recent_log: + exit_with_failure(f"Found recent commit with message '{release_notes_commit_message}'. Has this release already been started?") + + print("No existing release commits or tags found.") + print_separator() + + # Step 5: Update the NSS version numbers (remove beta) + print("Step 5: Removing beta status from version numbers...") + if patch: + set_version_to_patch_release([major, minor, patch]) + else: + set_version_to_minor_release([major, minor]) + remove_beta_status() + + print_separator() + + + + # Step 6: Commit the change + print("Step 6: Committing version number changes...") + check_call_noisy(["hg", "commit", "-m", version_commit_message]) + print_separator() + + # Step 7: Generate release note + print("Step 7: Generating release notes...") + release_note_content = generate_release_note([version, ".", previous_version]) + + # Write release note to file + with open(release_note_file, "w") as f: + f.write(release_note_content) + print(f"Release note written to {release_note_file}") + print_separator() + + # Step 8: Generate new release note index + print("Step 8: Generating release notes index...") + generate_release_notes_index([version, esr_version]) + print_separator() + + input("Are you making an ESR release? If so, please manually edit doc/rst/releases/index.rst to adjust the ESR / main version note. Press enter when done.") + + # Step 9: Commit the release notes + print("Step 9: Committing release notes...") + check_call_noisy(["hg", "add", release_note_file]) + check_call_noisy(["hg", "commit", "-m", release_notes_commit_message]) + + # Get the commit hash + docs_commit = check_output(["hg", "log", "-r", ".", "--template", "{node|short}"]).decode('utf-8').strip() + print(f"Release notes committed. Commit hash: {docs_commit}") + print_separator() + + # Step 10: Tag the release version + print(f"Step 10: Tagging release version {rtm_tag}...") + check_call_noisy(["hg", "tag", rtm_tag]) + print_separator() + + # Step 11: Switch to default branch and graft the release notes + print("Step 11: Switching to default branch and grafting release notes...") + check_call_noisy(["hg", "checkout", "default"]) + check_call_noisy(["hg", "graft", "-r", docs_commit]) + print_separator() + + response = input('Display the outgoing changes? [yN]: ') + if 'y' in response.lower(): + check_call_noisy(["hg", "outgoing", "--graph", "-b", "default", "-b", branch_name, remote]) + print_separator() + + # Step 12: Push changes + response = input('Push these changes to the NSS repository? [yN]: ') + if 'y' in response.lower(): + print("Pushing changes to default branch...") + check_call_noisy(["hg", "push", "-b", "default", remote]) + print(f"Pushing changes to {branch_name} branch...") + check_call_noisy(["hg", "push", "-b", branch_name, remote]) + print_separator() + print("SUCCESS: NSS release process completed!") + print_separator() + print() + print("NEXT STEPS:") + print(f"1. Wait for the changes to sync to Github") + print("2. In your mozilla-unified repository, run:") + print(f" ./mach nss-uplift {rtm_tag}") + print() + else: + print("Changes have NOT been pushed to the repository.") + print("The local commits remain in your working directory.") + print_separator() + + +def create_nss_release_archive(args): + ensure_arguments_count(args, 2, "nss_release_version path_to_stage_directory") + nssrel = args[0].strip() # e.g. 3.19.3 + stagedir = args[1].strip() # e.g. ../stage + + # Determine which tar command to use (prefer gtar if available) + tar_cmd = "gtar" + try: + check_call(["which", "gtar"], stdout=open(os.devnull, 'w'), stderr=open(os.devnull, 'w')) + except: + tar_cmd = "tar" + + # Generate the release tag from the version + nssreltag = version_string_to_RTM_tag(nssrel) + + print_separator() + print("CREATE NSS RELEASE ARCHIVE") + print_separator() + print(f"NSS release version: {nssrel}") + print(f"Stage directory: {stagedir}") + print_separator() + + response = input('Are these parameters correct? [yN]: ') + if 'y' not in response.lower(): + print("Aborted.") + sys.exit(0) + print_separator() with open('automation/release/nspr-version.txt') as nspr_version_file: nsprrel = next(nspr_version_file).strip() @@ -311,16 +788,16 @@ def create_nss_release_archive(): check_call_noisy(["mkdir", "-p", nss_stagedir]) check_call_noisy(["hg", "archive", "-r", nssreltag, "--prefix=nss-" + nssrel + "/nss", stagedir + "/" + nssreltag + "/src/" + nss_tar, "-X", ".hgtags"]) - check_call_noisy(["gtar", "-xz", "-C", nss_stagedir, "-f", nsprtar_with_path]) + check_call_noisy([tar_cmd, "-xz", "-C", nss_stagedir, "-f", nsprtar_with_path]) print("changing to directory " + nss_stagedir) os.chdir(nss_stagedir) - check_call_noisy(["gtar", "-xz", "-f", nss_tar]) + check_call_noisy([tar_cmd, "-xz", "-f", nss_tar]) check_call_noisy(["mv", "-i", "nspr-" + nsprrel + "/nspr", "nss-" + nssrel + "/"]) check_call_noisy(["rmdir", "nspr-" + nsprrel]) nss_nspr_tar = "nss-" + nssrel + "-with-nspr-" + nsprrel + ".tar.gz" - check_call_noisy(["gtar", "-cz", "--remove-files", "-f", nss_nspr_tar, "nss-" + nssrel]) + check_call_noisy([tar_cmd, "-cz", "--remove-files", "-f", nss_nspr_tar, "nss-" + nssrel]) check_call("sha1sum " + nss_tar + " " + nss_nspr_tar + " > SHA1SUMS", shell=True) check_call("sha256sum " + nss_tar + " " + nss_nspr_tar + " > SHA256SUMS", shell=True) print("created directory " + nss_stagedir + " with files:") @@ -346,17 +823,22 @@ def create_nss_release_archive(): f"gs://{gcp_proj}-productdelivery/pub/security/nss/releases/", ] ) + print_separator() + print(f"Release tarballs have been uploaded to Google Cloud Storage. You can find them at https://ftp.mozilla.org/pub/security/nss/releases/{nssreltag}/") + print_separator() o = OptionParser(usage="client.py [options] " + " | ".join([ "remove_beta", "set_beta", "print_library_versions", "print_root_ca_version", "set_root_ca_version", "set_version_to_minor_release", "set_version_to_patch_release", "set_release_candidate_number", - "set_4_digit_release_number", "create_nss_release_archive"])) + "set_4_digit_release_number", "make_release_branch", "create_nss_release_archive", + "generate_release_note", "generate_release_notes_index"])) try: options, args = o.parse_args() action = args[0] + action_args = args[1:] # Get all arguments after the action except IndexError: o.print_help() sys.exit(2) @@ -374,29 +856,44 @@ elif action in ('print_root_ca_version'): print_root_ca_version() elif action in ('set_root_ca_version'): - set_root_ca_version() + set_root_ca_version(action_args) # x.y version number - 2 parameters elif action in ('set_version_to_minor_release'): - set_version_to_minor_release() + set_version_to_minor_release(action_args) # x.y.z version number - 3 parameters elif action in ('set_version_to_patch_release'): - set_version_to_patch_release() + set_version_to_patch_release(action_args) # change the release candidate number, usually increased by one, # usually if previous release candiate had a bug # 1 parameter elif action in ('set_release_candidate_number'): - set_release_candidate_number() + set_release_candidate_number(action_args) # use the build/release candiate number in the identifying version number # 4 parameters elif action in ('set_4_digit_release_number'): - set_4_digit_release_number() + set_4_digit_release_number(action_args) + +# create a freeze branch and beta tag for a new release +# 2 parameters +elif action in ('make_release_branch'): + make_release_branch(action_args) elif action in ('create_nss_release_archive'): - create_nss_release_archive() + create_nss_release_archive(action_args) + +elif action in ('generate_release_note'): + print(generate_release_note(action_args)) + +elif action in ('generate_release_notes_index'): + generate_release_notes_index(action_args) + + +elif action in ('release_nss'): + release_nss(action_args) else: o.print_help() diff --git a/security/nss/cmd/lib/secutil.c b/security/nss/cmd/lib/secutil.c @@ -4245,6 +4245,9 @@ static const struct SSLNamedGroupString { #ifndef NSS_DISABLE_KYBER { NAME_AND_LEN("xyber76800"), ssl_grp_kem_xyber768d00 }, #endif + { NAME_AND_LEN("x25519mlkem768"), ssl_grp_kem_mlkem768x25519 }, + { NAME_AND_LEN("secp256r1mlkem768"), ssl_grp_kem_secp256r1mlkem768 }, + // keep for compatibility { NAME_AND_LEN("mlkem768x25519"), ssl_grp_kem_mlkem768x25519 }, }; diff --git a/security/nss/doc/rst/community.rst b/security/nss/doc/rst/community.rst @@ -46,7 +46,7 @@ following mailing list, Google group and Element/Matrix channel: .. container:: - See our section on :ref:`mozilla_projects_nss_nss_sources_building_testing` to get started + See our section on :ref:`mozilla_projects_nss_building` to get started making your patch. When you're satisfied with it, you'll need code review. .. _code_review: diff --git a/security/nss/doc/rst/releases/index.rst b/security/nss/doc/rst/releases/index.rst @@ -15,6 +15,7 @@ Release Notes nss_3_114_1.rst nss_3_114.rst nss_3_113.rst + nss_3_112_2.rst nss_3_112_1.rst nss_3_112.rst nss_3_111.rst @@ -97,5 +98,6 @@ Release Notes **NSS 3.117** is the latest version of NSS. Complete release notes are available here: :ref:`mozilla_projects_nss_nss_3_117_release_notes` - **NSS 3.112.1 (ESR)** is the latest ESR version of NSS. + **NSS 3.112.2 (ESR)** is the latest ESR version of NSS. Complete release notes are available here: :ref:`mozilla_projects_nss_nss_3_112_1_release_notes` + diff --git a/security/nss/doc/rst/releases/nss_3_112_2.rst b/security/nss/doc/rst/releases/nss_3_112_2.rst @@ -0,0 +1,72 @@ +.. _mozilla_projects_nss_nss_3_112_2_release_notes: + +NSS 3.112.2 release notes +========================= + +`Introduction <#introduction>`__ +-------------------------------- + +.. container:: + + Network Security Services (NSS) 3.112.2 was released on *3 October 2025**. + +`Distribution Information <#distribution_information>`__ +-------------------------------------------------------- + +.. container:: + + The HG tag is NSS_3_112_2_RTM. NSS 3.112.2 requires NSPR 4.36 or newer. + + NSS 3.112.2 source distributions are available on ftp.mozilla.org for secure HTTPS download: + + - Source tarballs: + https://ftp.mozilla.org/pub/mozilla.org/security/nss/releases/NSS_3_112_2_RTM/src/ + + Other releases are available :ref:`mozilla_projects_nss_releases`. + +.. _changes_in_nss_3.112.2: + +`Changes in NSS 3.112.2 <#changes_in_nss_3.112.2>`__ +------------------------------------------------------------------ + +.. container:: + + - Bug 1970079 - Prevent leaks during pkcs12 decoding. + - Bug 1988046 - SEC_ASN1Decode* should ensure it has read as many bytes as each length field indicates. + - Bug 1992218 - fix memory leak in secasn1decode_unittest.cc. + - Bug 1988913 - Add OISTE roots. + - Bug 1976051 - Add runbook for certdata.txt changes. + - Bug 1991666 - dbtool: close databases before shutdown. + - Bug 1956754 - don't flush base64 when buffer is null. + - Bug 1989541 - Set `use_pkcs5_pbkd2_params2_only=1` for fuzzing builds. + - Bug 1989480 - mozilla::pkix: recognize the qcStatements extension for QWACs. + - Bug 1980465 - Fix a big-endian-problematic cast in zlib calls. + - Bug 1962321 - Revert removing out/ directory after ossfuzz build. + - Bug 1988524 - Add Cryptofuzz to OSS-Fuzz build. + - Bug 1984704 - Add PKCS#11 trust tests. + - Bug 1983308 - final disable dsa patch cert.sh. + - Bug 1983320 - ml-dsa: move tls 1.3 to use streaming signatures. + - Bug 1983320 - ml-dsa: Prep Create a FindOidTagByString function. + - Bug 1983320 - ml-dsa: softoken changes. + - Bug 1983320 - ml-dsa: der key decode. + - Bug 1983320 - ml-dsa: Prep colapse the overuse of keyType outside of pk11wrap and cryptohi. + - Bug 1983320 - ml-dsa: Prep Create a CreateSignatureAlgorithmID function. + - Bug 1983308 - disable DSA in NSS script tests. + - Bug 1983308 - Disabling of some algorithms: generic cert.sh. + - Bug 1981046 - Need to update to new mechanisms. + - Bug 1983320 - Add ML-DSA public key printing support in NSS command-line utilities. + - Bug 1986802 - note embedded scts before revocation checks are performed. + - Bug 1983320 - Add support for ML-DSA keys and mechanisms in PKCS#11 interface. + - Bug 1983320 - Add support for ML-DSA key type and public key structure. + - Bug 1983320 - Enable ML-DSA integration via OIDs support and SECMOD flag. + - Bug 1983308 - disable kyber. + - Bug 1965329 - Implement PKCS #11 v3.2 PQ functions (use verify signature). + - Bug 1983308 - Disable dsa - gtests. + - Bug 1983313 - make group and scheme support in test tools generic. + - Bug 1983770 - Create GH workflow to automatically close PRs. + - Bug 1983308 - Disable dsa - base code. + - Bug 1983308 - Disabling of some algorithms: remove dsa from pk11_mode. + - Bug 1983308 - Disable seed and RC2 bug fixes. + - Bug 1982742 - restore support for finding certificates by decoded serial number. + - Bug 1984165 - avoid CKR_BUFFER_TO_SMALL error in trust lookups. + diff --git a/security/nss/doc/rst/releases/nss_3_64.rst b/security/nss/doc/rst/releases/nss_3_64.rst @@ -24,7 +24,7 @@ NSS 3.64 release notes - Source tarballs: https://ftp.mozilla.org/pub/mozilla.org/security/nss/releases/NSS_3_64_RTM/src/ - Other releases are available :ref:`mozilla_projects_nss_nss_releases`. + Other releases are available :ref:`mozilla_projects_nss_releases`. .. _bugs_fixed_in_nss_3.64: diff --git a/security/nss/doc/rst/runbooks/index.rst b/security/nss/doc/rst/runbooks/index.rst @@ -8,4 +8,5 @@ Runbooks :glob: :hidden: - releasing.rst -\ No newline at end of file + releasing.rst + rootstore.rst +\ No newline at end of file diff --git a/security/nss/doc/rst/runbooks/releasing.rst b/security/nss/doc/rst/runbooks/releasing.rst @@ -10,23 +10,25 @@ Releasing NSS * Normal development. This runs from the day after a Firefox merge until 2 weeks before the next Firefox merge. During this time, the version of NSS in mozilla-central and on NSS's development branch are kept in sync by `Updatebot <https://github.com/mozilla-services/updatebot>`_. * Freezing for release. This starts 2 weeks before the next Firefox merge. During this time, mozilla-central tracks a release branch. Commits can still land on NSS's development branch but they won't be uplifted to mozilla-central. -Freezing a version for release ------------------------------- +Make a release branch +--------------------- In the week prior to a NSS release, the version in mozilla-unified will be frozen. This is to ensure that new NSS versions have adequate testing in Firefox Nightly before making their way to Beta and Release. -The NSS Release owner will: +The NSS Release owner will run the ``make_release_branch`` script: -1. Make sure your local repo is up to date with ``hg pull`` and ``hg checkout default``. -2. Make a branch for this NSS release. ``hg branch NSS_3_XXX_BRANCH`` -3. Tag a beta for this NSS release. ``hg tag NSS_3_XXX_BETA1`` -4. Inspect the outgoing changes with ``hg outgoing`` and verify they are correct. -5. Push this branch and tag to the NSS repository. ``hg push --new-branch`` -6. Wait for the changes to sync to Github (~15 minutes). -7. Manually uplift this version into mozilla-unified by running ``./mach vendor security/nss/moz.yaml -r NSS_3_XXX_BETA1`` in mozilla-unified. + python3 automation/release/nss-release-helper.py make_release_branch <3.XXX> <remote> + +This creates a new branch for the release and tags the first beta release as ``NSS_3_XXX_BETA1``. This can then be uplifted into mozilla-unified via: + + ./mach nss-uplift {tag_name} + +You may need to wait a few minutes for Github to sync the new branch. If issues are discovered with this build, you can manually graft patches onto this branch and tag new beta versions, which then need to be uplifted. + +The equivalent manual process is described below. .. warning:: @@ -35,27 +37,25 @@ The NSS Release owner will: .. warning:: - After this point, further submissions by Updatebot SHOULD be ignored to ensure that the frozen branch is not overwritten by + After this point, automated submissions by UpdateBot SHOULD be ignored to ensure that the frozen branch is not overwritten by further changes to the development branch. -Releasing NSS into Firefox --------------------------- +Tagging NSS for Release +----------------------- -The NSS Release Owner will: +The NSS Release Owner will run the release script: -1. Make sure you're on the appropriate branch (``hg checkout NSS_3_XXX_BRANCH``). -2. Update the NSS version numbers: ``python3 automation/release/nss-release-helper.py remove_beta`` -3. Commit the change: ``hg commit -m "Set version numbers to 3.XXX final`` -4. Add a release note named ``nss_3_XXX.rst`` to ``doc/rst/releases`` and update ``index.rst`` in the release branch. -5. Commit the release notes: ``hg commit -m "Release notes for NSS 3.XXX"``. -6. Tag the release version: ``hg tag NSS_3_XXX_RTM`` -7. Push the changes to the NSS repository. ``hg push`` -8. Switch the default branch and graft the release notes onto this branch: ``hg graft -r {DOCS_COMMIT}``. -9. Manually uplift this version by running ``./mach vendor security/nss/moz.yaml -r NSS_3_XXX_RTM`` in mozilla-unified. + python3 automation/release/nss-release-helper.py release_nss <3.XXX or 3.XXX.YYY> <previous_version> <esr_version> <remote> + +``<previous_version>`` is the previous release version (e.g. ``3.YYY``) and ``<esr_version>`` is the current NSS ESR version (e.g. ``3.ZZZ.X``). + +Note that if you're making an ESR or patch release, you'll need to manually update ``index.rst`` when prompted by the script. You may also be asked to merge the changes to this file. + +This will update the version numbers, generate release notes and tag the release as ``NSS_3_XXX_RTM``. The release notes will be placed in ``doc/rst/releases/nss_3_XXX.rst`` and the index of release notes will be updated. After it syncs to Github, you can manually uplift the tagged release into mozilla-unified via ``./mach nss-uplift {tag_name}``. .. warning:: - ./mach vendor does not currently update the root CA telemetry. This must be done manually. + The nss-uplift script does not currently update the root CA telemetry. This must be done manually. Releasing NSS to downstream @@ -63,7 +63,7 @@ Releasing NSS to downstream You will need the ``gcloud`` tool installed from https://cloud.google.com/sdk/docs/install. -1. Create the release archives with ``python automation/release/nss-release-helper.py create_nss_release_archive 3.XXX NSS_3_XXX_RTM ../stage`` +1. Create the release archives with ``python automation/release/nss-release-helper.py create_nss_release_archive 3.XXX ../stage`` 2. Announce the release on `dev-tech-crypto <https://groups.google.com/a/mozilla.org/g/dev-tech-crypto>`_. Preparing for the next release @@ -91,4 +91,39 @@ Please now copy the checklist below and fill it out in the NSS release bug and c Updating NSPR ------------- -NSPR releases are infrequent, but require changing the NSPR version is listed in ``automation/release/nspr-version.txt`` -\ No newline at end of file +NSPR releases are infrequent, but require changing the NSPR version is listed in ``automation/release/nspr-version.txt`` + + +Making an ESR release +--------------------- + +For an ESR release, there will already be a release branch. You will need to manually graft any patches you're backporting from the main release branch onto the ESR branch. You can then run the release_nss and create_nss_release_archive commands with the usual parameters. Afterwards, you'll need to request them for uplift to mozilla-unified via the ESR option. + +Manually freezing a version for release +--------------------------------------- + +1. Make sure your local repo is up to date with ``hg pull`` and ``hg checkout default``. +2. Make a branch for this NSS release. ``hg branch NSS_3_XXX_BRANCH`` +3. Tag a beta for this NSS release. ``hg tag NSS_3_XXX_BETA1`` +4. Inspect the outgoing changes with ``hg outgoing`` and verify they are correct. +5. Push this branch and tag to the NSS repository. ``hg push --new-branch`` +6. Wait for the changes to sync to Github (~15 minutes). +7. Manually uplift this version into mozilla-unified by running ``./mach vendor security/nss/moz.yaml -r NSS_3_XXX_BETA1`` in mozilla-unified. + +Manually tagging NSS for release +-------------------------------- + +1. Make sure you're on the appropriate branch (``hg checkout NSS_3_XXX_BRANCH``). +2. Update the NSS version numbers: ``python3 automation/release/nss-release-helper.py remove_beta`` +3. Commit the change: ``hg commit -m "Set version numbers to 3.XXX final`` +4. Generate a release note by running ``python3 automation/release/nss-release-helper.py generate_release_note 3.XXX 3.YYY > doc/rst/releases/nss_3_XXX.rst`` where ``3.YYY`` is the previous version. +5. Generate a new release note index by running ``python3 automation/release/nss-release-helper.py generate_release_notes_index <latest_release> <latest_esr_release>``. +6. Commit the release notes: ``hg commit -m "Release notes for NSS 3.XXX"`` The commit hash of this change will be needed later, so make a note of it (we'll refer to it as ``{DOCS_COMMIT}``). +7. Tag the release version: ``hg tag NSS_3_XXX_RTM`` +8. Switch the default branch and graft the release notes onto this branch: ``hg graft -r {DOCS_COMMIT}``. +9. Push the changes on both branches. + +Manually uplifting a release into mozilla-unified +------------------------------------------------- + +``./mach nss-uplift {tag_name}`` is calling ``./mach vendor security/nss/moz.yaml -r NSS_3_XXX_BETA1`` behind the scenes and performing a few other tweaks. It relies on UpdateBot's tooling. +\ No newline at end of file diff --git a/security/nss/doc/rst/runbooks/rootstore.rst b/security/nss/doc/rst/runbooks/rootstore.rst @@ -0,0 +1,108 @@ +.. _mozilla_projects_nss_runbooks_rootstore: + +Updating NSS's Root Store +========================= + +.. container:: + + The authoritative source for NSS's root store is `certdata.txt <https://hg.mozilla.org/projects/nss/file/tip/lib/ckfw/builtins/certdata.txt>`_. + + certdata.txt contains a list of "certificate blocks" and "trust blocks". A "root" is a certificate (in a certificate block) and its trust bits (in a trust block). + Blocks are delimited by a blank line. Comments start with a ``#``. + Certificate blocks include a line that says ``CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE``. + Trust blocks include a line that says ``CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST``. + The other lines in a block describe attributes of a PKCS#11 object. + +Adding a root +============= + +1. Make sure that the NSS ``atob`` and ``addbuiltin`` binaries are in your ``PATH`` and the necessary libraries (``libnsp4``, ``libnssutil3``, etc.) are available. + +2. If the certificate is PEM encoded, convert it to DER using the ``atob`` tool:: + + atob -i cert.pem -o cert.der + +3. Add the certificate to the ``certdata.txt`` file using the ``addbuiltin`` tool:: + + addbuiltin -t <trust_bits> -n "<friendly name>" -i <cert.der> >> certdata.txt + + ``<trust_bits>`` is a string of trust bits: + + - ``C,,`` for website trust + - ``,C,`` for email (SMIME) trust + - There are a number of more obscure settings; see `CERT_DecodeTrustString <https://searchfox.org/mozilla-central/source/security/nss/lib/certdb/certdb.c#2319>`_ for further details. + +This can also be done as a one liner: + + atob -i cert.pem | addbuiltin -t <trust_bits> -n "<friendly name>" >> certdata.txt + +Removing a root +--------------- + +Simply remove both the certificate block and the trust block for the root. Use the nickname and SHA256 fingerprint listed in the bug report to ensure that you are removing the correct blocks. Leave a blank line to delimit adjacent blocks. + +Setting a distrust-after date +----------------------------- + +Setting a distrust-after date for a root means that any certificates issued by that root after the specified date will no longer be trusted, but certificates issued before that date will still be trusted. + +1. Create the timestamp for the desired distrust date. An easy and practical way to do this is using the date command:: + + date -d "2019-07-01 00:00:00 UTC" +%s + + The result should be something like: ``1561939200`` + +2. Then, run ``addbuiltin -d`` to verify the timestamp and do the right conversions. The ``-d`` option takes the timestamp as an argument, which is interpreted as seconds since Unix epoch. The addbuiltin command will show the result in stdout, as it should be inserted in certdata.txt:: + + addbuiltin -d 1561939200 + + The result should be something like this:: + + The timestamp represents this date: Mon Jul 01 00:00:00 2019 + Locate the entry of the desired certificate in certdata.txt + Erase the CKA_NSS_[SERVER|EMAIL]_DISTRUST_AFTER CK_BBOOL CK_FALSE + And override with the following respective entry: + # For Server Distrust After: Mon Jul 01 00:00:00 2019 + CKA_NSS_SERVER_DISTRUST_AFTER MULTILINE_OCTAL + \061\071\060\067\060\061\060\060\060\060\060\060\132 + END + # For Email Distrust After: Mon Jul 01 00:00:00 2019 + CKA_NSS_EMAIL_DISTRUST_AFTER MULTILINE_OCTAL + \061\071\060\067\060\061\060\060\060\060\060\060\132 + END + +Incrementing the root store version +----------------------------------- + +After making a change to the root store, you must increment the version number in `nssckbi.h <https://searchfox.org/mozilla-central/source/security/nss/lib/ckfw/builtins/nssckbi.h>`_: + +1. Bump ``NSS_BUILTINS_LIBRARY_VERSION_MINOR`` to the next even number (odd numbers are used for fixes). +2. Set ``NSS_BUILTINS_LIBRARY_VERSION`` to match. + +Checking your work +------------------ + +1. Check the SHA256 hashes in the output blocks in certdata.txt +2. If the certificate should be trusted for websites, check the ``CKA_TRUST_SERVER_AUTH`` includes ``CKT_NSS_TRUSTED_DELEGATOR``, otherwise it should include ``CKT_NSS_MUST_VERIFY_TRUST``. +3. If the certificate should be trusted for email, check the ``CKA_TRUST_EMAIL_PROTECTION`` line the same way. +4. The ``CKA_TRUST_CODE_SIGNING`` line should always include ``CKT_NSS_MUST_VERIFY_TRUST``. + +If making a change to the roots trusted for website authentication, you can confirm your work by rebuilding NSS and running ``vfyserv <hostname>`` which will output either ``PROBLEM WITH CERT CHAIN`` or ``SERVER CONFIGURED CORRECTLY``. All roots trusted for website authentication should have a test site listed in CCADB. + +You can also dump certificates from a copy of ``libnssckbi.so``:: + + $ mkdir tmp; cd tmp + $ certutil -N -d . + $ modutil -add builtins -dbdir . -libfile /path/to/libnssckbi.so + $ certutil -L -h builtins # list all certificates in the builtins module + $ certutil -L -n "<friendly name>" -d . # pretty print one cert + +Root Store Consumers +-------------------- + +certdata.txt is consumed by various tools to generate root store formats suitable for different libraries and languages. + +NSS itself uses a `perl script <https://hg.mozilla.org/projects/nss/file/tip/lib/ckfw/builtins/certdata.perl>`_ to generate a C source file which builds a PKCS#11 module containing the root store, called libnssckbi. +Firefox used to depend on libnssckbi directly, but now uses its own pure Rust implementation which builds directly from certdata.txt (`build.rs <https://searchfox.org/mozilla-central/source/security/manager/ssl/trust_anchors/build.rs>`_, `output <https://searchfox.org/mozilla-central/source/__GENERATED__/__RUST_BUILD_SCRIPT__/trust-anchors/builtins.rs>`_). + +certdata.txt is known to be consumed by several external projects, including: Curl's `mk-ca-bundle <https://curl.se/docs/mk-ca-bundle.html>`_, `Certifi <https://certifi.io/>`_, and the ca-certificates package used by Debian, Ubuntu, Gentoo, Fedora and Arch (`ca-certificates <https://packages.debian.org/stable/ca-certificates>`_). diff --git a/security/nss/gtests/ssl_gtest/ssl_0rtt_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_0rtt_unittest.cc @@ -198,7 +198,7 @@ TEST_P(TlsConnectTls13, ZeroRttOptionsSetLate) { ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); Connect(); SendReceive(); // Need to read so that we absorb the session ticket. - CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); + CheckKeys(); Reset(); StartConnect(); // Now turn on 0-RTT but too late for the ticket. diff --git a/security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc @@ -55,8 +55,7 @@ TEST_P(TlsConnectTls12Plus, ServerAuthRsaPss) { server_->SetSignatureSchemes(kSignatureSchemePss, PR_ARRAY_SIZE(kSignatureSchemePss)); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_pss, - ssl_sig_rsa_pss_pss_sha256); + CheckKeys(ssl_auth_rsa_pss, ssl_sig_rsa_pss_pss_sha256); } // PSS doesn't work with TLS 1.0 or 1.1 because we can't signal it. @@ -85,8 +84,7 @@ TEST_P(TlsConnectTls12Plus, ServerAuthRsaPssNoParameters) { server_->SetSignatureSchemes(kSignatureSchemePss, PR_ARRAY_SIZE(kSignatureSchemePss)); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_pss, - ssl_sig_rsa_pss_pss_sha256); + CheckKeys(ssl_auth_rsa_pss, ssl_sig_rsa_pss_pss_sha256); } TEST_P(TlsConnectGeneric, ServerAuthRsaPssChain) { @@ -947,7 +945,7 @@ TEST_P(TlsConnectClientAuth, ClientAuthEcdsa) { client_->SetupClientAuth(std::get<2>(GetParam()), true); server_->RequestClientAuth(true); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa); + CheckKeys(ssl_auth_ecdsa); } TEST_P(TlsConnectClientAuth, ClientAuthWithEch) { @@ -960,7 +958,7 @@ TEST_P(TlsConnectClientAuth, ClientAuthWithEch) { client_->SetupClientAuth(std::get<2>(GetParam()), true); server_->RequestClientAuth(true); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa); + CheckKeys(ssl_auth_ecdsa); } TEST_P(TlsConnectClientAuth, ClientAuthBigRsa) { @@ -1304,14 +1302,14 @@ static const SSLSignatureScheme kSignatureSchemeRsaSha384[] = { static const SSLSignatureScheme kSignatureSchemeRsaSha256[] = { ssl_sig_rsa_pkcs1_sha256}; -static SSLNamedGroup NamedGroupForEcdsa384(uint16_t version) { +static SSLNamedGroup NamedGroupForEcdsa384(const TlsConnectTestBase* ctbase) { // NSS tries to match the group size to the symmetric cipher. In TLS 1.1 and // 1.0, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA is the highest priority suite, so // we use P-384. With TLS 1.2 on we pick AES-128 GCM so use x25519. - if (version <= SSL_LIBRARY_VERSION_TLS_1_1) { + if (ctbase->GetVersion() <= SSL_LIBRARY_VERSION_TLS_1_1) { return ssl_grp_ec_secp384r1; } - return ssl_grp_ec_curve25519; + return ctbase->GetDefaultGroupFromKEA(ctbase->GetDefaultKEA()); } // When signature algorithms match up, this should connect successfully; even @@ -1323,7 +1321,7 @@ TEST_P(TlsConnectGeneric, SignatureAlgorithmServerAuth) { server_->SetSignatureSchemes(kSignatureSchemeEcdsaSha384, PR_ARRAY_SIZE(kSignatureSchemeEcdsaSha384)); Connect(); - CheckKeys(ssl_kea_ecdh, NamedGroupForEcdsa384(version_), ssl_auth_ecdsa, + CheckKeys(GetDefaultKEA(), NamedGroupForEcdsa384(this), ssl_auth_ecdsa, ssl_sig_ecdsa_secp384r1_sha384); } @@ -1342,7 +1340,7 @@ TEST_P(TlsConnectGeneric, SignatureAlgorithmClientOnly) { SSL_SignaturePrefSet(client_->ssl_fd(), clientAlgorithms, PR_ARRAY_SIZE(clientAlgorithms))); Connect(); - CheckKeys(ssl_kea_ecdh, NamedGroupForEcdsa384(version_), ssl_auth_ecdsa, + CheckKeys(GetDefaultKEA(), NamedGroupForEcdsa384(this), ssl_auth_ecdsa, ssl_sig_ecdsa_secp384r1_sha384); } @@ -1353,7 +1351,7 @@ TEST_P(TlsConnectGeneric, SignatureAlgorithmServerOnly) { server_->SetSignatureSchemes(kSignatureSchemeEcdsaSha384, PR_ARRAY_SIZE(kSignatureSchemeEcdsaSha384)); Connect(); - CheckKeys(ssl_kea_ecdh, NamedGroupForEcdsa384(version_), ssl_auth_ecdsa, + CheckKeys(GetDefaultKEA(), NamedGroupForEcdsa384(this), ssl_auth_ecdsa, ssl_sig_ecdsa_secp384r1_sha384); } @@ -1606,6 +1604,8 @@ TEST_F(TlsConnectDatagram13, AuthCompleteBeforeFinished) { MakeTlsFilter<BeforeFinished13>(server_, client_, [this]() { EXPECT_EQ(SECSuccess, SSL_AuthCertificateComplete(client_->ssl_fd(), 0)); }); + // filters only work with particular groups + client_->ConfigNamedGroups(kNonPQDHEGroups); Connect(); } @@ -2036,8 +2036,7 @@ class TlsSignatureSchemeConfiguration EnsureTlsSetup(); configPeer->SetSignatureSchemes(&signature_scheme_, 1); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, auth_type_, - signature_scheme_); + CheckKeys(auth_type_, signature_scheme_); } std::string certificate_; @@ -2071,7 +2070,7 @@ TEST_P(TlsSignatureSchemeConfiguration, SignatureSchemeConfigBoth) { client_->SetSignatureSchemes(&signature_scheme_, 1); server_->SetSignatureSchemes(&signature_scheme_, 1); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, auth_type_, signature_scheme_); + CheckKeys(auth_type_, signature_scheme_); } class Tls12CertificateRequestReplacer : public TlsHandshakeFilter { diff --git a/security/nss/gtests/ssl_gtest/ssl_dhe_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_dhe_unittest.cc @@ -41,7 +41,7 @@ TEST_P(TlsConnectTls13, SharesForBothEcdheAndDhe) { Connect(); - CheckKeys(); + CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); bool ec, dh; auto track_group_type = [&ec, &dh](SSLNamedGroup group) { diff --git a/security/nss/gtests/ssl_gtest/ssl_drop_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_drop_unittest.cc @@ -201,6 +201,8 @@ class TlsDropDatagram13 : public TlsConnectDatagram13, // ACKs TEST_P(TlsDropDatagram13, DropClientFirstFlightOnce) { client_filters_.drop_->Reset({0}); + // filters only work with particular groups + client_->ConfigNamedGroups(kNonPQDHEGroups); StartConnect(); client_->Handshake(); server_->Handshake(); @@ -210,6 +212,9 @@ TEST_P(TlsDropDatagram13, DropClientFirstFlightOnce) { TEST_P(TlsDropDatagram13, DropServerFirstFlightOnce) { server_filters_.drop_->Reset(0xff); + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); + client_->ConfigNamedGroups(kNonPQDHEGroups); StartConnect(); client_->Handshake(); // Send the first flight, all dropped. @@ -224,6 +229,9 @@ TEST_P(TlsDropDatagram13, DropServerFirstFlightOnce) { // TODO(ekr@rtfm.com): We should generate an empty ACK. TEST_P(TlsDropDatagram13, DropServerFirstRecordOnce) { server_filters_.drop_->Reset({0}); + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); + client_->ConfigNamedGroups(kNonPQDHEGroups); StartConnect(); client_->Handshake(); server_->Handshake(); @@ -236,6 +244,8 @@ TEST_P(TlsDropDatagram13, DropServerFirstRecordOnce) { // produce an ACK. TEST_P(TlsDropDatagram13, DropServerSecondRecordOnce) { server_filters_.drop_->Reset({1}); + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); StartConnect(); client_->Handshake(); server_->Handshake(); @@ -299,6 +309,9 @@ TEST_P(TlsDropDatagram13, DropClientCertVerify) { // Shrink the MTU down so that certs get split and drop the first piece. TEST_P(TlsDropDatagram13, DropFirstHalfOfServerCertificate) { server_filters_.drop_->Reset({2}); + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); + client_->ConfigNamedGroups(kNonPQDHEGroups); StartConnect(); ShrinkPostServerHelloMtu(); client_->Handshake(); @@ -326,6 +339,9 @@ TEST_P(TlsDropDatagram13, DropFirstHalfOfServerCertificate) { // Shrink the MTU down so that certs get split and drop the second piece. TEST_P(TlsDropDatagram13, DropSecondHalfOfServerCertificate) { server_filters_.drop_->Reset({3}); + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); + client_->ConfigNamedGroups(kNonPQDHEGroups); StartConnect(); ShrinkPostServerHelloMtu(); client_->Handshake(); @@ -414,6 +430,9 @@ class TlsFragmentationAndRecoveryTest : public TlsDropDatagram13 { private: void FirstFlightDropCertificate() { + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); + client_->ConfigNamedGroups(kNonPQDHEGroups); StartConnect(); client_->Handshake(); @@ -561,6 +580,9 @@ TEST_P(TlsDropDatagram13, NoDropsDuringZeroRtt) { TEST_P(TlsDropDatagram13, DropEEDuringZeroRtt) { SetupForZeroRtt(); + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); + client_->ConfigNamedGroups(kNonPQDHEGroups); SetFilters(); std::cerr << "Starting second handshake" << std::endl; client_->Set0RttEnabled(true); @@ -606,6 +628,9 @@ class TlsReorderDatagram13 : public TlsDropDatagram13 { // of the flight and will still produce an ACK. TEST_P(TlsDropDatagram13, ReorderServerEE) { server_filters_.drop_->Reset({1}); + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); + client_->ConfigNamedGroups(kNonPQDHEGroups); StartConnect(); client_->Handshake(); server_->Handshake(); @@ -684,6 +709,9 @@ TEST_F(TlsConnectDatagram13, SendOutOfOrderHsNonsenseWithHandshakeKey) { // Shrink the MTU down so that certs get split and then swap the first and // second pieces of the server certificate. TEST_P(TlsReorderDatagram13, ReorderServerCertificate) { + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); + client_->ConfigNamedGroups(kNonPQDHEGroups); StartConnect(); ShrinkPostServerHelloMtu(); client_->Handshake(); diff --git a/security/nss/gtests/ssl_gtest/ssl_ecdh_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_ecdh_unittest.cc @@ -490,7 +490,7 @@ TEST_P(TlsKeyExchangeTest13, EqualPriority13) { Connect(); - CheckKeys(); + CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); const std::vector<SSLNamedGroup> shares = {ssl_grp_ec_curve25519}; CheckKEXDetails(client_groups, shares); } diff --git a/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc @@ -1314,6 +1314,9 @@ TEST_P(TlsDisallowedUnadvertisedExtensionTest13, TEST_P(TlsConnectStream, IncludePadding) { EnsureTlsSetup(); + // filters only work with particular groups + client_->ConfigNamedGroups(kNonPQDHEGroups); + SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_FALSE); // Don't GREASE // This needs to be long enough to push a TLS 1.0 ClientHello over 255, but @@ -1372,7 +1375,7 @@ TEST_F(TlsConnectStreamTls13, ClientHelloExtensionPermutationWithPSK) { PR_TRUE) == SECSuccess); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); } /* This test checks that the ClientHello extension order is actually permuted diff --git a/security/nss/gtests/ssl_gtest/ssl_hrr_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_hrr_unittest.cc @@ -107,6 +107,8 @@ TEST_P(TlsConnectTls13, SecondClientHelloRejectEarlyDataXtn) { auto orig_client = std::make_shared<TlsAgent>(client_->name(), TlsAgent::CLIENT, variant_); client_.swap(orig_client); + // filters only work with particular groups + client_->ConfigNamedGroups(kNonPQDHEGroups); client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_1, SSL_LIBRARY_VERSION_TLS_1_3); client_->ConfigureSessionCache(RESUME_BOTH); @@ -950,7 +952,7 @@ TEST_P(TlsKeyExchange13, ConnectEcdhePreferenceMismatchHrr) { client_->ConfigNamedGroups(client_groups); server_->ConfigNamedGroups(server_groups); Connect(); - CheckKeys(); + CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519); static const std::vector<SSLNamedGroup> expectedShares = { ssl_grp_ec_secp384r1}; CheckKEXDetails(client_groups, expectedShares, ssl_grp_ec_curve25519); @@ -997,7 +999,7 @@ TEST_P(TlsKeyExchange13, ConnectEcdhePreferenceMismatchHrrExtraShares) { EXPECT_EQ(SECSuccess, SSL_SendAdditionalKeyShares(client_->ssl_fd(), 1)); Connect(); - CheckKeys(); + CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519); CheckKEXDetails(client_groups, client_groups); } @@ -1043,7 +1045,7 @@ TEST_P(TlsKeyExchange13, EXPECT_EQ(2U, cb_called); EXPECT_TRUE(shares_capture2_->captured()) << "client should send shares"; - CheckKeys(); + CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519); static const std::vector<SSLNamedGroup> client_shares( client_groups.begin(), client_groups.begin() + 2); CheckKEXDetails(client_groups, client_shares, server_groups[0]); diff --git a/security/nss/gtests/ssl_gtest/ssl_loopback_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_loopback_unittest.cc @@ -37,7 +37,7 @@ TEST_P(TlsConnectGeneric, ConnectEcdsa) { SetExpectedVersion(std::get<1>(GetParam())); Reset(TlsAgent::kServerEcdsa256); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa); + CheckKeys(ssl_auth_ecdsa); } TEST_P(TlsConnectGeneric, CipherSuiteMismatch) { diff --git a/security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc @@ -275,6 +275,8 @@ TEST_F(TlsConnectStreamTls13, ClientHelloF5Padding) { auto filter = MakeTlsFilter<TlsHandshakeRecorder>(client_, kTlsHandshakeClientHello); + // filters are tied to a particular group, switch back to that group + client_->ConfigNamedGroups(kNonPQDHEGroups); // Add PSK with label long enough to push CH length into [256, 511]. std::vector<uint8_t> label(100); EXPECT_EQ(SECSuccess, @@ -723,4 +725,4 @@ TEST_P(TlsConnectGeneric, RecordSizeLimitLong) { } } -} // namespace nss_test -\ No newline at end of file +} // namespace nss_test diff --git a/security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_resumption_unittest.cc @@ -453,7 +453,7 @@ TEST_P(TlsConnectGeneric, ServerSNICertTypeSwitch) { Connect(); ScopedCERTCertificate cert2(SSL_PeerCertificate(client_->ssl_fd())); ASSERT_NE(nullptr, cert2.get()); - CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa); + CheckKeys(ssl_auth_ecdsa); EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert)); } @@ -613,7 +613,7 @@ TEST_P(TlsConnectGenericResumption, ResumeClientIncompatibleCipher) { client_->EnableSingleCipher(ChooseOneCipher(version_)); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); + CheckKeys(); Reset(); ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); @@ -628,7 +628,7 @@ TEST_P(TlsConnectGenericResumption, ResumeClientIncompatibleCipher) { auto ticket_capture = MakeTlsFilter<TlsExtensionCapture>(client_, ticket_extension); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); + CheckKeys(); EXPECT_EQ(0U, ticket_capture->extension().len()); } @@ -655,7 +655,7 @@ TEST_P(TlsConnectStream, ResumptionOverrideCipher) { server_->EnableSingleCipher(ChooseOneCipher(version_)); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); + CheckKeys(); Reset(); ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); @@ -759,7 +759,7 @@ TEST_P(TlsConnectGenericPre13, TestResumptionOverrideVersion) { // Need to use a cipher that is plausible for the lower version. server_->EnableSingleCipher(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); + CheckKeys(); Reset(); ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET); @@ -793,8 +793,7 @@ TEST_F(TlsConnectTest, TestTls13ResumptionTwice) { MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_pre_shared_key_xtn); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_sign, - ssl_sig_rsa_pss_rsae_sha256); + CheckKeys(ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); // The filter will go away when we reset, so save the captured extension. DataBuffer initialTicket(c1->extension()); ASSERT_LT(0U, initialTicket.len()); @@ -811,8 +810,7 @@ TEST_F(TlsConnectTest, TestTls13ResumptionTwice) { ExpectResumption(RESUME_TICKET); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_sign, - ssl_sig_rsa_pss_rsae_sha256); + CheckKeys(ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); ASSERT_LT(0U, c2->extension().len()); ScopedCERTCertificate cert2(SSL_PeerCertificate(client_->ssl_fd())); @@ -1089,7 +1087,7 @@ TEST_F(TlsConnectTest, TestTls13ResumptionDowngrade) { Handshake(); SendReceive(); - CheckKeys(); + CheckKeys(ssl_kea_ecdh); } TEST_F(TlsConnectTest, TestTls13ResumptionForcedDowngrade) { @@ -1144,15 +1142,15 @@ TEST_P(TlsConnectGenericResumption, ReConnectTicket) { server_->EnableSingleCipher(ChooseOneCipher(version_)); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_sign, - ssl_sig_rsa_pss_rsae_sha256); + CheckKeys(ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); // Resume Reset(); ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); ExpectResumption(RESUME_TICKET); Connect(); // Only the client knows this. - CheckKeysResumption(ssl_kea_ecdh, ssl_grp_none, ssl_grp_ec_curve25519, + CheckKeysResumption(GetDefaultKEA(), ssl_grp_none, + GetDefaultGroupFromKEA(GetDefaultKEA()), ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); } @@ -1161,13 +1159,13 @@ TEST_P(TlsConnectGenericPre13, ReConnectCache) { server_->EnableSingleCipher(ChooseOneCipher(version_)); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_sign, - ssl_sig_rsa_pss_rsae_sha256); + CheckKeys(ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); // Resume Reset(); ExpectResumption(RESUME_SESSIONID); Connect(); - CheckKeysResumption(ssl_kea_ecdh, ssl_grp_none, ssl_grp_ec_curve25519, + CheckKeysResumption(GetDefaultKEA(), ssl_grp_none, + GetDefaultGroupFromKEA(GetDefaultKEA()), ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); } @@ -1176,15 +1174,15 @@ TEST_P(TlsConnectGenericResumption, ReConnectAgainTicket) { server_->EnableSingleCipher(ChooseOneCipher(version_)); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_rsa_sign, - ssl_sig_rsa_pss_rsae_sha256); + CheckKeys(ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); // Resume Reset(); ConfigureSessionCache(RESUME_BOTH, RESUME_BOTH); ExpectResumption(RESUME_TICKET); Connect(); // Only the client knows this. - CheckKeysResumption(ssl_kea_ecdh, ssl_grp_none, ssl_grp_ec_curve25519, + CheckKeysResumption(GetDefaultKEA(), ssl_grp_none, + GetDefaultGroupFromKEA(GetDefaultKEA()), ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); // Resume connection again Reset(); @@ -1192,7 +1190,8 @@ TEST_P(TlsConnectGenericResumption, ReConnectAgainTicket) { ExpectResumption(RESUME_TICKET, 2); Connect(); // Only the client knows this. - CheckKeysResumption(ssl_kea_ecdh, ssl_grp_none, ssl_grp_ec_curve25519, + CheckKeysResumption(GetDefaultKEA(), ssl_grp_none, + GetDefaultGroupFromKEA(GetDefaultKEA()), ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); } diff --git a/security/nss/gtests/ssl_gtest/ssl_skip_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_skip_unittest.cc @@ -114,6 +114,9 @@ class Tls13SkipTest : public TlsConnectTestBase, void SetUp() override { TlsConnectTestBase::SetUp(); EnsureTlsSetup(); + // until we can fix filters to work with MLKEM + client_->ConfigNamedGroups(kNonPQDHEGroups); + server_->ConfigNamedGroups(kNonPQDHEGroups); } void ServerSkipTest(std::shared_ptr<TlsRecordFilter> filter, int32_t error) { diff --git a/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc @@ -482,6 +482,9 @@ TEST_F(TlsConnectDatagram13, CompatModeDtlsClient) { client_->SetOption(SSL_ENABLE_TLS13_COMPAT_MODE, PR_TRUE); auto client_records = MakeTlsFilter<TlsRecordRecorder>(client_); auto server_records = MakeTlsFilter<TlsRecordRecorder>(server_); + // filters only work with particular groups + client_->ConfigNamedGroups(kNonPQDHEGroups); + server_->ConfigNamedGroups(kNonPQDHEGroups); Connect(); ASSERT_EQ(2U, client_records->count()); // CH, Fin @@ -531,6 +534,9 @@ TEST_F(TlsConnectDatagram13, CompatModeDtlsServer) { auto server_records = std::make_shared<TlsRecordRecorder>(server_); server_->SetFilter(std::make_shared<ChainedPacketFilter>( ChainedPacketFilterInit({server_records, server_hello}))); + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); + client_->ConfigNamedGroups(kNonPQDHEGroups); StartConnect(); client_->Handshake(); server_->Handshake(); diff --git a/security/nss/gtests/ssl_gtest/tls_agent.cc b/security/nss/gtests/ssl_gtest/tls_agent.cc @@ -519,13 +519,21 @@ void TlsAgent::DisableAllCiphers() { // Not actually all groups, just the ones that we are actually willing // to use. const std::vector<SSLNamedGroup> kAllDHEGroups = { - ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, ssl_grp_ec_secp384r1, - ssl_grp_ec_secp521r1, ssl_grp_ffdhe_2048, ssl_grp_ffdhe_3072, - ssl_grp_ffdhe_4096, ssl_grp_ffdhe_6144, ssl_grp_ffdhe_8192, + ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, + ssl_grp_ec_secp384r1, ssl_grp_ec_secp521r1, + ssl_grp_ffdhe_2048, ssl_grp_ffdhe_3072, + ssl_grp_ffdhe_4096, ssl_grp_ffdhe_6144, + ssl_grp_ffdhe_8192, #ifndef NSS_DISABLE_KYBER ssl_grp_kem_xyber768d00, #endif - ssl_grp_kem_mlkem768x25519, + ssl_grp_kem_mlkem768x25519, ssl_grp_kem_secp256r1mlkem768, +}; + +const std::vector<SSLNamedGroup> kNonPQDHEGroups = { + ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, ssl_grp_ec_secp384r1, + ssl_grp_ec_secp521r1, ssl_grp_ffdhe_2048, ssl_grp_ffdhe_3072, + ssl_grp_ffdhe_4096, ssl_grp_ffdhe_6144, ssl_grp_ffdhe_8192, }; const std::vector<SSLNamedGroup> kECDHEGroups = { @@ -534,7 +542,7 @@ const std::vector<SSLNamedGroup> kECDHEGroups = { #ifndef NSS_DISABLE_KYBER ssl_grp_kem_xyber768d00, #endif - ssl_grp_kem_mlkem768x25519, + ssl_grp_kem_mlkem768x25519, ssl_grp_kem_secp256r1mlkem768, }; const std::vector<SSLNamedGroup> kFFDHEGroups = { @@ -543,12 +551,13 @@ const std::vector<SSLNamedGroup> kFFDHEGroups = { // Defined because the big DHE groups are ridiculously slow. const std::vector<SSLNamedGroup> kFasterDHEGroups = { - ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, ssl_grp_ec_secp384r1, - ssl_grp_ffdhe_2048, ssl_grp_ffdhe_3072, + ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, + ssl_grp_ec_secp384r1, ssl_grp_ffdhe_2048, + ssl_grp_ffdhe_3072, #ifndef NSS_DISABLE_KYBER ssl_grp_kem_xyber768d00, #endif - ssl_grp_kem_mlkem768x25519, + ssl_grp_kem_mlkem768x25519, ssl_grp_kem_secp256r1mlkem768, }; const std::vector<SSLNamedGroup> kEcdhHybridGroups = { @@ -556,6 +565,7 @@ const std::vector<SSLNamedGroup> kEcdhHybridGroups = { ssl_grp_kem_xyber768d00, #endif ssl_grp_kem_mlkem768x25519, + ssl_grp_kem_secp256r1mlkem768, }; void TlsAgent::EnableCiphersByKeyExchange(SSLKEAType kea) { @@ -727,6 +737,7 @@ void TlsAgent::CheckKEA(SSLKEAType kea, SSLNamedGroup kea_group, case ssl_grp_kem_mlkem768x25519: kea_size = 255; break; + case ssl_grp_kem_secp256r1mlkem768: case ssl_grp_ec_secp256r1: kea_size = 256; break; diff --git a/security/nss/gtests/ssl_gtest/tls_agent.h b/security/nss/gtests/ssl_gtest/tls_agent.h @@ -52,6 +52,7 @@ class TlsCipherSpec; struct TlsRecord; const extern std::vector<SSLNamedGroup> kAllDHEGroups; +const extern std::vector<SSLNamedGroup> kNonPQDHEGroups; const extern std::vector<SSLNamedGroup> kECDHEGroups; const extern std::vector<SSLNamedGroup> kFFDHEGroups; const extern std::vector<SSLNamedGroup> kFasterDHEGroups; diff --git a/security/nss/gtests/ssl_gtest/tls_connect.cc b/security/nss/gtests/ssl_gtest/tls_connect.cc @@ -505,21 +505,24 @@ void TlsConnectTestBase::CheckEarlyDataLimit( EXPECT_EQ(expected_size, static_cast<size_t>(preinfo.maxEarlyDataSize)); } -void TlsConnectTestBase::CheckKeys(SSLKEAType kea_type, SSLNamedGroup kea_group, - SSLAuthType auth_type, - SSLSignatureScheme sig_scheme) const { - if (kea_group != ssl_grp_none) { - client_->CheckKEA(kea_type, kea_group); - server_->CheckKEA(kea_type, kea_group); +SSLKEAType TlsConnectTestBase::GetDefaultKEA(void) const { + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + return ssl_kea_ecdh_hybrid; } - server_->CheckAuthType(auth_type, sig_scheme); - client_->CheckAuthType(auth_type, sig_scheme); + return ssl_kea_ecdh; } -void TlsConnectTestBase::CheckKeys(SSLKEAType kea_type, - SSLAuthType auth_type) const { +SSLAuthType TlsConnectTestBase::GetDefaultAuth(void) const { + return ssl_auth_rsa_sign; +} + +SSLNamedGroup TlsConnectTestBase::GetDefaultGroupFromKEA( + SSLKEAType kea_type) const { SSLNamedGroup group; switch (kea_type) { + case ssl_kea_ecdh_hybrid: + group = ssl_grp_kem_mlkem768x25519; + break; case ssl_kea_ecdh: group = ssl_grp_ec_curve25519; break; @@ -534,7 +537,11 @@ void TlsConnectTestBase::CheckKeys(SSLKEAType kea_type, group = ssl_grp_none; break; } + return group; +} +SSLSignatureScheme TlsConnectTestBase::GetDefaultSchemeFromAuth( + SSLAuthType auth_type) const { SSLSignatureScheme scheme; switch (auth_type) { case ssl_auth_rsa_decrypt: @@ -561,11 +568,56 @@ void TlsConnectTestBase::CheckKeys(SSLKEAType kea_type, scheme = static_cast<SSLSignatureScheme>(0x0100); break; } + return scheme; +} + +void TlsConnectTestBase::CheckKeys(SSLKEAType kea_type, SSLNamedGroup kea_group, + SSLAuthType auth_type, + SSLSignatureScheme sig_scheme) const { + if (kea_group != ssl_grp_none) { + client_->CheckKEA(kea_type, kea_group); + server_->CheckKEA(kea_type, kea_group); + } + server_->CheckAuthType(auth_type, sig_scheme); + client_->CheckAuthType(auth_type, sig_scheme); +} + +void TlsConnectTestBase::CheckKeys(SSLKEAType kea_type, + SSLNamedGroup kea_group) const { + SSLAuthType auth_type = GetDefaultAuth(); + SSLSignatureScheme scheme = GetDefaultSchemeFromAuth(auth_type); + CheckKeys(kea_type, kea_group, auth_type, scheme); +} + +void TlsConnectTestBase::CheckKeys(SSLAuthType auth_type, + SSLSignatureScheme sig_scheme) const { + SSLKEAType kea_type = GetDefaultKEA(); + SSLNamedGroup group = GetDefaultGroupFromKEA(kea_type); + CheckKeys(kea_type, group, auth_type, sig_scheme); +} + +void TlsConnectTestBase::CheckKeys(SSLKEAType kea_type, + SSLAuthType auth_type) const { + SSLNamedGroup group = GetDefaultGroupFromKEA(kea_type); + SSLSignatureScheme scheme = GetDefaultSchemeFromAuth(auth_type); + CheckKeys(kea_type, group, auth_type, scheme); } +void TlsConnectTestBase::CheckKeys(SSLKEAType kea_type) const { + SSLAuthType auth_type = GetDefaultAuth(); + CheckKeys(kea_type, auth_type); +} + +void TlsConnectTestBase::CheckKeys(SSLAuthType auth_type) const { + SSLKEAType kea_type = GetDefaultKEA(); + CheckKeys(kea_type, auth_type); +} + void TlsConnectTestBase::CheckKeys() const { - CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); + SSLKEAType kea_type = GetDefaultKEA(); + SSLAuthType auth_type = GetDefaultAuth(); + CheckKeys(kea_type, auth_type); } void TlsConnectTestBase::CheckKeysResumption(SSLKEAType kea_type, diff --git a/security/nss/gtests/ssl_gtest/tls_connect.h b/security/nss/gtests/ssl_gtest/tls_connect.h @@ -83,12 +83,24 @@ class TlsConnectTestBase : public ::testing::Test { void ConnectWithCipherSuite(uint16_t cipher_suite); void CheckEarlyDataLimit(const std::shared_ptr<TlsAgent>& agent, size_t expected_size); + // Get the default KEA for our tls version + SSLKEAType GetDefaultKEA(void) const; + // Get the default auth for our tls version + SSLAuthType GetDefaultAuth(void) const; + // Find the default group for a given KEA + SSLNamedGroup GetDefaultGroupFromKEA(SSLKEAType kea_type) const; + // Find the default scheam for a given auth + SSLSignatureScheme GetDefaultSchemeFromAuth(SSLAuthType auth_type) const; + // Check that the keys used in the handshake match expectations. void CheckKeys(SSLKEAType kea_type, SSLNamedGroup kea_group, SSLAuthType auth_type, SSLSignatureScheme sig_scheme) const; - // This version guesses some of the values. + // These version guesses some of the values based on defaults + void CheckKeys(SSLKEAType kea_type, SSLNamedGroup kea_group) const; + void CheckKeys(SSLAuthType auth_type, SSLSignatureScheme sig_scheme) const; void CheckKeys(SSLKEAType kea_type, SSLAuthType auth_type) const; - // This version assumes defaults. + void CheckKeys(SSLKEAType kea_type) const; + void CheckKeys(SSLAuthType auth_type) const; void CheckKeys() const; // Check that keys on resumed sessions. void CheckKeysResumption(SSLKEAType kea_type, SSLNamedGroup kea_group, @@ -103,6 +115,7 @@ class TlsConnectTestBase : public ::testing::Test { void ConfigureVersion(uint16_t version); void SetExpectedVersion(uint16_t version); + uint16_t GetVersion(void) const { return version_; }; // Expect resumption of a particular type. void ExpectResumption(SessionResumptionMode expected, uint8_t num_resumed = 1); diff --git a/security/nss/gtests/ssl_gtest/tls_ech_unittest.cc b/security/nss/gtests/ssl_gtest/tls_ech_unittest.cc @@ -1106,7 +1106,7 @@ TEST_F(TlsConnectStreamTls13, EchAcceptWithExternalPsk) { Handshake(); CheckConnected(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); // The PSK extension is present in CHOuter. ASSERT_TRUE(filter->captured()); diff --git a/security/nss/gtests/ssl_gtest/tls_grease_unittest.cc b/security/nss/gtests/ssl_gtest/tls_grease_unittest.cc @@ -131,6 +131,9 @@ TEST_P(GreasePresenceAbsenceTestAllVersions, ClientGreaseKeyShare) { auto ch1 = MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_key_share_xtn); + // filters only work with particular groups + server_->ConfigNamedGroups(kNonPQDHEGroups); + client_->ConfigNamedGroups(kNonPQDHEGroups); Connect(); EXPECT_TRUE((version_ >= SSL_LIBRARY_VERSION_TLS_1_3) == ch1->captured()); @@ -412,7 +415,7 @@ TEST_F(TlsConnectStreamTls13, GreasePsk) { Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); } // Test that ECH and GREASE work together successfully diff --git a/security/nss/gtests/ssl_gtest/tls_mlkem_unittest.cc b/security/nss/gtests/ssl_gtest/tls_mlkem_unittest.cc @@ -30,6 +30,15 @@ TEST_P(TlsKeyExchangeTest13, Mlkem768x25519Supported) { ssl_sig_rsa_pss_rsae_sha256); } +TEST_P(TlsKeyExchangeTest13, Secp256r1Mlkem768Supported) { + EnsureKeyShareSetup(); + ConfigNamedGroups({ssl_grp_kem_secp256r1mlkem768}); + + Connect(); + CheckKeys(ssl_kea_ecdh_hybrid, ssl_grp_kem_secp256r1mlkem768, + ssl_auth_rsa_sign, ssl_sig_rsa_pss_rsae_sha256); +} + TEST_P(TlsKeyExchangeTest, Tls12ClientMlkem768x25519NotSupported) { EnsureKeyShareSetup(); client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, @@ -47,6 +56,7 @@ TEST_P(TlsKeyExchangeTest, Tls12ClientMlkem768x25519NotSupported) { std::vector<SSLNamedGroup> groups = GetGroupDetails(groups_capture_); for (auto group : groups) { EXPECT_NE(group, ssl_grp_kem_mlkem768x25519); + EXPECT_NE(group, ssl_grp_kem_secp256r1mlkem768); } } @@ -75,6 +85,31 @@ TEST_P(TlsKeyExchangeTest13, Tls12ServerMlkem768x25519NotSupported) { ssl_sig_rsa_pss_rsae_sha256); } +TEST_P(TlsKeyExchangeTest13, Tls12ServerSecp256r1Mlkem768NotSupported) { + EnsureKeyShareSetup(); + + client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_3); + server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2, + SSL_LIBRARY_VERSION_TLS_1_2); + + client_->DisableAllCiphers(); + client_->EnableCiphersByKeyExchange(ssl_kea_ecdh); + client_->EnableCiphersByKeyExchange(ssl_kea_ecdh_hybrid); + client_->ConfigNamedGroups( + {ssl_grp_kem_secp256r1mlkem768, ssl_grp_ec_secp256r1}); + EXPECT_EQ(SECSuccess, SSL_SendAdditionalKeyShares(client_->ssl_fd(), 1)); + + server_->EnableCiphersByKeyExchange(ssl_kea_ecdh); + server_->EnableCiphersByKeyExchange(ssl_kea_ecdh_hybrid); + server_->ConfigNamedGroups( + {ssl_grp_kem_secp256r1mlkem768, ssl_grp_ec_secp256r1}); + + Connect(); + CheckKeys(ssl_kea_ecdh, ssl_grp_ec_secp256r1, ssl_auth_rsa_sign, + ssl_sig_rsa_pss_rsae_sha256); +} + TEST_P(TlsKeyExchangeTest13, Mlkem768x25519ClientDisabledByPolicy) { EnsureKeyShareSetup(); client_->SetPolicy(SEC_OID_MLKEM768X25519, 0, NSS_USE_ALG_IN_SSL_KX); @@ -94,12 +129,37 @@ TEST_P(TlsKeyExchangeTest13, Mlkem768x25519ServerDisabledByPolicy) { {ssl_grp_kem_mlkem768x25519}, ssl_grp_ec_secp256r1); } +TEST_P(TlsKeyExchangeTest13, Secp256r1Mlkem768ClientDisabledByPolicy) { + EnsureKeyShareSetup(); + client_->SetPolicy(SEC_OID_SECP256R1MLKEM768, 0, NSS_USE_ALG_IN_SSL_KX); + ConfigNamedGroups({ssl_grp_kem_secp256r1mlkem768, ssl_grp_ec_secp256r1}); + + Connect(); + CheckKEXDetails({ssl_grp_ec_secp256r1}, {ssl_grp_ec_secp256r1}); +} + +TEST_P(TlsKeyExchangeTest13, Secp256r1Mlkem768ServerDisabledByPolicy) { + EnsureKeyShareSetup(); + server_->SetPolicy(SEC_OID_SECP256R1MLKEM768, 0, NSS_USE_ALG_IN_SSL_KX); + ConfigNamedGroups({ssl_grp_kem_secp256r1mlkem768, ssl_grp_ec_secp256r1}); + + Connect(); + CheckKEXDetails({ssl_grp_kem_secp256r1mlkem768, ssl_grp_ec_secp256r1}, + {ssl_grp_kem_secp256r1mlkem768}, ssl_grp_ec_secp256r1); +} + static void CheckECDHShareReuse( const std::shared_ptr<TlsExtensionCapture>& capture) { EXPECT_TRUE(capture->captured()); const DataBuffer& ext = capture->extension(); - DataBuffer hybrid_share; - DataBuffer x25519_share; + DataBuffer hybrid_share[4]; + DataBuffer ecdh_share[4]; + int hybrid_offset[4]; + SSLNamedGroup hybrid_ec_type[4]; + SSLNamedGroup ec_type[4]; + int ecdh_index[4]; + int nextHybrid = 0; + int nextECDH = 0; size_t offset = 0; uint32_t ext_len; @@ -112,11 +172,26 @@ static void CheckECDHShareReuse( ext.Read(offset, 2, &named_group); ext.Read(offset + 2, 2, &named_group_len); while (offset < ext.len()) { - if (named_group == ssl_grp_kem_mlkem768x25519) { - hybrid_share = DataBuffer(ext.data() + offset + 2 + 2, named_group_len); - } - if (named_group == ssl_grp_ec_curve25519) { - x25519_share = DataBuffer(ext.data() + offset + 2 + 2, named_group_len); + switch (named_group) { + case ssl_grp_kem_mlkem768x25519: + hybrid_share[nextHybrid] = + DataBuffer(ext.data() + offset + 2 + 2, named_group_len); + hybrid_offset[nextHybrid] = KYBER768_PUBLIC_KEY_BYTES; + hybrid_ec_type[nextHybrid] = ssl_grp_ec_curve25519; + nextHybrid++; + break; + case ssl_grp_kem_secp256r1mlkem768: + hybrid_share[nextHybrid] = + DataBuffer(ext.data() + offset + 2 + 2, named_group_len); + hybrid_offset[nextHybrid] = 0; + hybrid_ec_type[nextHybrid] = ssl_grp_ec_secp256r1; + nextHybrid++; + case ssl_grp_ec_curve25519: + case ssl_grp_ec_secp256r1: + ecdh_share[nextECDH] = + DataBuffer(ext.data() + offset + 2 + 2, named_group_len); + ec_type[nextECDH] = (SSLNamedGroup)named_group; + nextECDH++; } offset += 2 + 2 + named_group_len; ext.Read(offset, 2, &named_group); @@ -124,11 +199,29 @@ static void CheckECDHShareReuse( } EXPECT_EQ(offset, ext.len()); - ASSERT_TRUE(hybrid_share.data()); - ASSERT_TRUE(x25519_share.data()); - ASSERT_GT(hybrid_share.len(), x25519_share.len()); - EXPECT_EQ(0, memcmp(hybrid_share.data() + KYBER768_PUBLIC_KEY_BYTES, - x25519_share.data(), x25519_share.len())); + ASSERT_TRUE(nextECDH > 0); + ASSERT_TRUE(nextHybrid > 0); + /* setup the hybrid ecdh indeces */ + for (int i = 0; i < nextHybrid; i++) { + ecdh_index[i] = -1; + for (int j = 0; j < nextECDH; j++) { + if (hybrid_ec_type[i] == ec_type[j]) { + ecdh_index[i] = j; + break; + } + } + ASSERT_TRUE(ecdh_index[i] != -1); + } + for (int i = 0; i < nextECDH; i++) { + ASSERT_TRUE(ecdh_share[i].data()); + } + for (int i = 0; i < nextHybrid; i++) { + int j = ecdh_index[i]; + ASSERT_TRUE(hybrid_share[i].data()); + ASSERT_GT(hybrid_share[i].len(), ecdh_share[j].len()); + EXPECT_EQ(0, memcmp(hybrid_share[i].data() + hybrid_offset[i], + ecdh_share[j].data(), ecdh_share[j].len())); + } } TEST_P(TlsKeyExchangeTest13, Mlkem768x25519ShareReuseFirst) { @@ -155,6 +248,30 @@ TEST_P(TlsKeyExchangeTest13, Mlkem768x25519ShareReuseSecond) { CheckECDHShareReuse(shares_capture_); } +TEST_P(TlsKeyExchangeTest13, Secp256r1Mlkem768ShareReuseFirst) { + EnsureKeyShareSetup(); + ConfigNamedGroups({ssl_grp_kem_secp256r1mlkem768, ssl_grp_ec_secp256r1}); + EXPECT_EQ(SECSuccess, SSL_SendAdditionalKeyShares(client_->ssl_fd(), 1)); + + Connect(); + + CheckKEXDetails({ssl_grp_kem_secp256r1mlkem768, ssl_grp_ec_secp256r1}, + {ssl_grp_kem_secp256r1mlkem768, ssl_grp_ec_secp256r1}); + CheckECDHShareReuse(shares_capture_); +} + +TEST_P(TlsKeyExchangeTest13, Secp256r1Mlkem768ShareReuseSecond) { + EnsureKeyShareSetup(); + ConfigNamedGroups({ssl_grp_ec_secp256r1, ssl_grp_kem_secp256r1mlkem768}); + EXPECT_EQ(SECSuccess, SSL_SendAdditionalKeyShares(client_->ssl_fd(), 1)); + + Connect(); + + CheckKEXDetails({ssl_grp_ec_secp256r1, ssl_grp_kem_secp256r1mlkem768}, + {ssl_grp_ec_secp256r1, ssl_grp_kem_secp256r1mlkem768}); + CheckECDHShareReuse(shares_capture_); +} + class Mlkem768x25519ShareDamager : public TlsExtensionFilter { public: typedef enum { diff --git a/security/nss/gtests/ssl_gtest/tls_psk_unittest.cc b/security/nss/gtests/ssl_gtest/tls_psk_unittest.cc @@ -68,7 +68,7 @@ TEST_P(Tls13PskTest, NormalExternal) { AddPsk(scoped_psk_, kPskDummyLabel_, kPskHash_); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); client_->RemovePsk(kPskDummyLabel_); server_->RemovePsk(kPskDummyLabel_); @@ -91,7 +91,7 @@ TEST_P(Tls13PskTest, KeyTooLarge) { AddPsk(scoped_psk_, kPskDummyLabel_, kPskHash_); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); } // Attempt to use a PSK with the wrong PRF hash. @@ -117,7 +117,7 @@ TEST_P(Tls13PskTest, LabelMismatch) { client_->AddPsk(scoped_psk_, std::string("foo"), kPskHash_); server_->AddPsk(scoped_psk_, std::string("bar"), kPskHash_); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); + CheckKeys(); } SSLHelloRetryRequestAction RetryFirstHello( @@ -148,7 +148,7 @@ TEST_P(Tls13PskTest, ResPskRetryStateless) { Handshake(); CheckConnected(); EXPECT_EQ(2U, cb_called); - CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); + CheckKeys(); SendReceive(); } @@ -174,7 +174,7 @@ TEST_P(Tls13PskTest, ExtPskRetryStateless) { Handshake(); CheckConnected(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); } // Server not configured with PSK and sends a certificate instead of @@ -182,7 +182,7 @@ TEST_P(Tls13PskTest, ExtPskRetryStateless) { TEST_P(Tls13PskTest, ClientOnly) { client_->AddPsk(scoped_psk_, kPskDummyLabel_, kPskHash_); Connect(); - CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); + CheckKeys(); } // Set a PSK, remove psk_key_exchange_modes. @@ -247,7 +247,7 @@ TEST_P(Tls13PskTest, PreferEpsk) { Handshake(); CheckConnected(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); } // Enable resumption, but connect (initially) with an EPSK. @@ -260,7 +260,7 @@ TEST_P(Tls13PskTest, SuppressNewSessionTicket) { nst_capture->EnableDecryption(); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); EXPECT_EQ(SECFailure, SSL_SendSessionTicket(server_->ssl_fd(), nullptr, 0)); EXPECT_EQ(0U, nst_capture->buffer().len()); if (variant_ == ssl_variant_stream) { @@ -275,7 +275,7 @@ TEST_P(Tls13PskTest, SuppressNewSessionTicket) { ExpectResumption(RESUME_NONE); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); } TEST_P(Tls13PskTest, BadConfigValues) { @@ -314,7 +314,7 @@ TEST_P(Tls13PskTest, FallbackUnsupportedCiphersuite) { client_->EnableSingleCipher(TLS_AES_128_GCM_SHA256); Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_auth_rsa_sign); + CheckKeys(); } // That fallback should not occur if there is no cipher overlap. @@ -342,7 +342,7 @@ TEST_P(Tls13PskTest, SuppressHandshakeCertReq) { Connect(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); EXPECT_EQ(0U, cr_cert_capture->buffer().len()); } @@ -422,7 +422,7 @@ TEST_P(Tls13PskTestWithCiphers, 0RttCiphers) { ExpectEarlyDataAccepted(true); CheckConnected(); SendReceive(); - CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none); + CheckKeys(ssl_auth_psk, ssl_sig_none); } TEST_P(Tls13PskTestWithCiphers, 0RttMaxEarlyData) { diff --git a/security/nss/lib/certdb/certdb.c b/security/nss/lib/certdb/certdb.c @@ -1231,6 +1231,7 @@ CERT_CheckKeyUsage(CERTCertificate *cert, unsigned int requiredUsage) break; case rsaPssKey: case dsaKey: + case mldsaKey: requiredUsage |= KU_DIGITAL_SIGNATURE; break; case dhKey: diff --git a/security/nss/lib/certhigh/certvfy.c b/security/nss/lib/certhigh/certvfy.c @@ -157,6 +157,17 @@ checkKeyParams(const SECAlgorithmID *sigAlgorithm, const SECKEYPublicKey *key) } return SECSuccess; + case SEC_OID_ML_DSA_44: + case SEC_OID_ML_DSA_65: + case SEC_OID_ML_DSA_87: + if (key->keyType != mldsaKey) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return SECFailure; + } + if (key->u.mldsa.paramSet != sigAlg) { + PORT_SetError(SEC_ERROR_INVALID_ALGORITHM); + return SECFailure; + } default: return SECSuccess; } diff --git a/security/nss/lib/cryptohi/keyi.h b/security/nss/lib/cryptohi/keyi.h @@ -54,6 +54,8 @@ SECStatus sec_DecodeRSAPSSParamsToMechanism(PLArenaPool *arena, CK_RSA_PKCS_PSS_PARAMS *mech, SECOidTag *hashAlg); +/* get the parameter set, converted to a key oid, only for new keys like mldsa, mlkem, and shldsa */ +SECOidTag seckey_GetParameterSet(const SECKEYPrivateKey *key); SEC_END_PROTOS #endif /* _KEYHI_H_ */ diff --git a/security/nss/lib/cryptohi/seckey.c b/security/nss/lib/cryptohi/seckey.c @@ -690,6 +690,26 @@ SECKEY_MLDSAOidParamsToLen(SECOidTag oid, SECKEYSizeType type) return 0; } +/* make this function generic. multiple key types will be able to use + * it (ml-kem, ml=dsa, shl-dsa, fn-dsa, etc. ) */ +SECOidTag +seckey_GetParameterSet(const SECKEYPrivateKey *key) +{ + CK_ULONG paramSet = PK11_ReadULongAttribute(key->pkcs11Slot, + key->pkcs11ID, + CKA_PARAMETER_SET); + if (paramSet == CK_UNAVAILABLE_INFORMATION) { + return SEC_OID_UNKNOWN; + } + switch (key->keyType) { + case mldsaKey: + return SECKEY_GetMLDSAOidTagByPkcs11ParamSet(paramSet); + default: + break; + } + return SEC_OID_UNKNOWN; +} + SECOidTag SECKEY_MLDSAOidParamsFromLen(unsigned int len, SECKEYSizeType type) { @@ -1291,8 +1311,7 @@ SECKEY_PrivateKeyStrengthInBits(const SECKEYPrivateKey *privk) unsigned bitSize = 0; SECItem params = { siBuffer, NULL, 0 }; SECStatus rv; - SECOidTag mlDsaOidTag; - CK_ML_DSA_PARAMETER_SET_TYPE mlDsaPkcs11ParamSet; + SECOidTag paramSetOid; if (!privk) { PORT_SetError(SEC_ERROR_INVALID_KEY); @@ -1343,17 +1362,11 @@ SECKEY_PrivateKeyStrengthInBits(const SECKEYPrivateKey *privk) PORT_Free(params.data); return bitSize; case mldsaKey: - mlDsaPkcs11ParamSet = PK11_ReadULongAttribute(privk->pkcs11Slot, - privk->pkcs11ID, - CKA_PARAMETER_SET); - if (mlDsaPkcs11ParamSet == CK_UNAVAILABLE_INFORMATION) { - break; - } - mlDsaOidTag = SECKEY_GetMLDSAOidTagByPkcs11ParamSet(mlDsaPkcs11ParamSet); - if (mlDsaOidTag == SEC_OID_UNKNOWN) { + paramSetOid = seckey_GetParameterSet(privk); + if (paramSetOid == SEC_OID_UNKNOWN) { break; } - bitSize = SECKEY_MLDSAOidParamsToLen(mlDsaOidTag, + bitSize = SECKEY_MLDSAOidParamsToLen(paramSetOid, SECKEYPrivKeyType) * 8; break; @@ -1390,7 +1403,7 @@ SECKEY_SignatureLen(const SECKEYPublicKey *pubk) &pubk->u.ec.DEREncodedParams); return ((size + 7) / 8) * 2; case mldsaKey: - size = SECKEY_MLDSAOidParamsToLen(pubk->u.mldsa.paramSet, + return SECKEY_MLDSAOidParamsToLen(pubk->u.mldsa.paramSet, SECKEYSignatureType); break; default: @@ -1644,8 +1657,6 @@ SECKEY_ConvertToPublicKey(SECKEYPrivateKey *privk) SECStatus rv; CK_OBJECT_HANDLE pubKeyHandle; SECItem decodedPoint; - CK_ML_DSA_PARAMETER_SET_TYPE mlDsaPkcs11ParamSet; - SECOidTag mlDsaOidTag; /* * First try to look up the cert. @@ -1784,17 +1795,10 @@ SECKEY_ConvertToPublicKey(SECKEYPrivateKey *privk) if (pubKeyHandle == CK_INVALID_HANDLE) { break; } - mlDsaPkcs11ParamSet = PK11_ReadULongAttribute(privk->pkcs11Slot, - privk->pkcs11ID, - CKA_PARAMETER_SET); - if (mlDsaPkcs11ParamSet == CK_UNAVAILABLE_INFORMATION) { - break; - } - mlDsaOidTag = SECKEY_GetMLDSAOidTagByPkcs11ParamSet(mlDsaPkcs11ParamSet); - if (mlDsaOidTag == SEC_OID_UNKNOWN) { + pubk->u.mldsa.paramSet = seckey_GetParameterSet(privk); + if (pubk->u.mldsa.paramSet == SEC_OID_UNKNOWN) { break; } - pubk->u.mldsa.paramSet = mlDsaOidTag; rv = PK11_ReadAttribute(privk->pkcs11Slot, pubKeyHandle, CKA_VALUE, arena, &pubk->u.mldsa.publicValue); if (rv != SECSuccess) { diff --git a/security/nss/lib/cryptohi/secsign.c b/security/nss/lib/cryptohi/secsign.c @@ -521,11 +521,18 @@ sec_DerSignData(PLArenaPool *arena, SECItem *result, case ecKey: algID = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; break; + case mldsaKey: + algID = seckey_GetParameterSet(pk); + break; default: - PORT_SetError(SEC_ERROR_INVALID_KEY); - return SECFailure; + algID = SEC_OID_UNKNOWN; + break; } } + if (algID == SEC_OID_UNKNOWN) { + PORT_SetError(SEC_ERROR_INVALID_KEY); + return SECFailure; + } /* Sign input buffer */ rv = sec_SignData(&it, buf, len, pk, algID, params); @@ -600,6 +607,14 @@ SGN_Digest(SECKEYPrivateKey *privKey, PORT_SetError(SEC_ERROR_SIGNATURE_ALGORITHM_DISABLED); return SECFailure; } + + if (privKey->keyType == mldsaKey) { + /* don't allow digest sign for mldsa. May be possible if mu + * is enabled, in that case the hash must be the special mldsa + * hash, which we don't have defined yet */ + PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG); + return SECFailure; + } /* check the policy on the encryption algorithm */ enctag = sec_GetEncAlgFromSigAlg( SEC_GetSignatureAlgorithmOidTagByKey(privKey, NULL, algtag)); @@ -722,6 +737,19 @@ SEC_GetSignatureAlgorithmOidTag(KeyType keyType, SECOidTag hashAlgTag) break; } break; + case mldsaKey: + switch (hashAlgTag) { + case SEC_OID_ML_DSA_44: + case SEC_OID_ML_DSA_65: + case SEC_OID_ML_DSA_87: + /* only accept known mldsa values. For mldsa, the hash must + * match the underlying signature algorithm */ + sigTag = hashAlgTag; + break; + default: + break; + } + break; case ecKey: switch (hashAlgTag) { case SEC_OID_SHA1: @@ -760,18 +788,23 @@ SEC_GetSignatureAlgorithmOidTagByKey(const SECKEYPrivateKey *privKey, const SECK } /* make sure we have only one key */ if (privKey) { - if (pubKey) { - PORT_SetError(SEC_ERROR_INVALID_ARGS); - return SEC_OID_UNKNOWN; - } keyType = privKey->keyType; + /* for mldsa, the hash has to match the paramset anyway, so + * pass in the param set as the hash */ + if (keyType == mldsaKey) { + hashAlgTag = seckey_GetParameterSet(privKey); + } } else { /* the logic above should guarentee the following assert. */ PORT_Assert(pubKey != NULL); PORT_Assert(privKey == NULL); keyType = pubKey->keyType; + /* for mldsa, the hash has to match the paramset anyway, so + * pass in the param set as the hash */ + if (keyType == mldsaKey) { + hashAlgTag = pubKey->u.mldsa.paramSet; + } } - /* for future, we can look at other part of the key to determine the algorithm */ return SEC_GetSignatureAlgorithmOidTag(keyType, hashAlgTag); } diff --git a/security/nss/lib/cryptohi/secvfy.c b/security/nss/lib/cryptohi/secvfy.c @@ -18,7 +18,6 @@ #include "keyi.h" #include "nss.h" #include "prenv.h" - /* ** Recover the DigestInfo from an RSA PKCS#1 signature. ** @@ -190,6 +189,9 @@ checkedSignatureLen(const SECKEYPublicKey *pubk) case ecKey: maxSigLen = 2 * MAX_ECKEY_LEN; break; + case mldsaKey: + maxSigLen = MAX_ML_DSA_SIGNATURE_LEN; + break; default: PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG); return 0; @@ -294,6 +296,10 @@ sec_GetEncAlgFromSigAlg(SECOidTag sigAlg) case SEC_OID_ANSIX962_ECDSA_SIGNATURE_RECOMMENDED_DIGEST: case SEC_OID_ANSIX962_ECDSA_SIGNATURE_SPECIFIED_DIGEST: return SEC_OID_ANSIX962_EC_PUBLIC_KEY; + case SEC_OID_ML_DSA_44: + case SEC_OID_ML_DSA_65: + case SEC_OID_ML_DSA_87: + return sigAlg; /* we don't implement MD4 hashes */ case SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION: default: @@ -397,6 +403,14 @@ sec_GetCombinedMech(SECOidTag encalg, SECOidTag hashalg) return sec_ECDSAGetCombinedMech(hashalg); case SEC_OID_ANSIX9_DSA_SIGNATURE: return sec_DSAGetCombinedMech(hashalg); + case SEC_OID_ML_DSA_44: + case SEC_OID_ML_DSA_65: + case SEC_OID_ML_DSA_87: + /* make sure the hashAlg is rational */ + if ((hashalg == SEC_OID_UNKNOWN) || (hashalg == encalg)) { + return CKM_ML_DSA; + } + break; default: break; } @@ -430,6 +444,7 @@ sec_DecodeSigAlg(const SECKEYPublicKey *key, SECOidTag sigAlg, SECStatus rv; SECItem oid; SECOidTag encalg; + PRBool comboRequired = PR_FALSE; char *evp; PR_ASSERT(hashalg != NULL); @@ -588,6 +603,13 @@ sec_DecodeSigAlg(const SECKEYPublicKey *key, SECOidTag sigAlg, } *mechp = sec_ECDSAGetCombinedMech(*hashalg); break; + case SEC_OID_ML_DSA_44: + case SEC_OID_ML_DSA_65: + case SEC_OID_ML_DSA_87: + comboRequired = PR_TRUE; + *hashalg = sigAlg; + /* decode params to get the sign context and set (mechparamsp) */ + break; /* we don't implement MD4 hashes */ case SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION: default: @@ -611,7 +633,7 @@ sec_DecodeSigAlg(const SECKEYPublicKey *key, SECOidTag sigAlg, * We know if we are signing or verifying based on the value of 'key'. * Since key is a public key, then it's set to NULL for signing */ evp = PR_GetEnvSecure("NSS_COMBO_SIGNATURES"); - if (evp) { + if (evp && !comboRequired) { if (PORT_Strcasecmp(evp, "none") == 0) { *mechp = CKM_INVALID_MECHANISM; } else if (key && (PORT_Strcasecmp(evp, "signonly") == 0)) { @@ -621,6 +643,11 @@ sec_DecodeSigAlg(const SECKEYPublicKey *key, SECOidTag sigAlg, } /* anything else we take as use combo, which is the default */ } + /* now enforce the combo requires requirement */ + if (comboRequired && (*mechp == CKM_INVALID_MECHANISM)) { + SECITEM_FreeItem(mechparamsp, PR_FALSE); + return SECFailure; + } return SECSuccess; } @@ -810,7 +837,9 @@ vfy_CreateContext(const SECKEYPublicKey *key, const SECItem *sig, } /* check hash alg again, RSA may have changed it.*/ - if (HASH_GetHashTypeByOidTag(cx->hashAlg) == HASH_AlgNULL) { + /* combo mechs set the hash to the sigAlg */ + if ((cx->hashAlg != cx->encAlg) && + (HASH_GetHashTypeByOidTag(cx->hashAlg) == HASH_AlgNULL)) { /* error set by HASH_GetHashTypeByOidTag */ goto loser; } @@ -1104,7 +1133,9 @@ vfy_VerifyDigest(const SECItem *digest, const SECKEYPublicKey *key, PORT_SetError(SEC_ERROR_BAD_SIGNATURE); } break; + case mldsaKey: default: + PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG); break; } VFY_DestroyContext(cx, PR_TRUE); diff --git a/security/nss/lib/nss/nss.h b/security/nss/lib/nss/nss.h @@ -22,12 +22,12 @@ * The format of the version string should be * "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]" */ -#define NSS_VERSION "3.117" _NSS_CUSTOMIZED +#define NSS_VERSION "3.118" _NSS_CUSTOMIZED " Beta" #define NSS_VMAJOR 3 -#define NSS_VMINOR 117 +#define NSS_VMINOR 118 #define NSS_VPATCH 0 #define NSS_VBUILD 0 -#define NSS_BETA PR_FALSE +#define NSS_BETA PR_TRUE #ifndef RC_INVOKED diff --git a/security/nss/lib/pk11wrap/pk11cert.c b/security/nss/lib/pk11wrap/pk11cert.c @@ -1110,28 +1110,11 @@ PK11_GetPubIndexKeyID(CERTCertificate *cert) if (pubk == NULL) return NULL; - switch (pubk->keyType) { - case rsaKey: - newItem = SECITEM_DupItem(&pubk->u.rsa.modulus); - break; - case dsaKey: - newItem = SECITEM_DupItem(&pubk->u.dsa.publicValue); - break; - case dhKey: - newItem = SECITEM_DupItem(&pubk->u.dh.publicValue); - break; - case ecKey: - case edKey: - case ecMontKey: - newItem = SECITEM_DupItem(&pubk->u.ec.publicValue); - break; - case mldsaKey: - newItem = SECITEM_DupItem(&pubk->u.mldsa.publicValue); - break; - case fortezzaKey: - default: - newItem = NULL; /* Fortezza Fix later... */ + const SECItem *oldItem = PK11_GetPublicValueFromPublicKey(pubk); + if (oldItem) { + newItem = SECITEM_DupItem(oldItem); } + SECKEY_DestroyPublicKey(pubk); /* make hash of it */ return newItem; diff --git a/security/nss/lib/pk11wrap/pk11obj.c b/security/nss/lib/pk11wrap/pk11obj.c @@ -16,6 +16,7 @@ #include "pkcs11t.h" #include "pk11func.h" #include "keyhi.h" +#include "keyi.h" #include "secitem.h" #include "secerr.h" #include "sslerr.h" @@ -552,8 +553,7 @@ PK11_SignatureLen(SECKEYPrivateKey *key) SECItem attributeItem = { siBuffer, NULL, 0 }; SECStatus rv; int length; - SECOidTag paramOid; - CK_ULONG paramSet; + SECOidTag paramSet; switch (key->keyType) { case rsaKey: @@ -593,17 +593,12 @@ PK11_SignatureLen(SECKEYPrivateKey *key) } return pk11_backupGetSignLength(key); case mldsaKey: - paramSet = PK11_ReadULongAttribute(key->pkcs11Slot, key->pkcs11ID, - CKA_PARAMETER_SET); - if (paramSet == CK_UNAVAILABLE_INFORMATION) { - break; - } - paramOid = SECKEY_GetMLDSAOidTagByPkcs11ParamSet(paramSet); - if (paramOid == SEC_OID_UNKNOWN) { + paramSet = seckey_GetParameterSet(key); + if (paramSet == SEC_OID_UNKNOWN) { break; } - return SECKEY_MLDSAOidParamsToLen(paramOid, SECKEYSignatureType); + return SECKEY_MLDSAOidParamsToLen(paramSet, SECKEYSignatureType); default: break; } diff --git a/security/nss/lib/pk11wrap/pk11pars.c b/security/nss/lib/pk11wrap/pk11pars.c @@ -245,8 +245,19 @@ static const oidValDef curveOptList[] = { NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_CERT_SIGNATURE }, { CIPHER_NAME("CURVE25519"), SEC_OID_CURVE25519, NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_CERT_SIGNATURE }, - { CIPHER_NAME("XYBER768D00"), SEC_OID_XYBER768D00, 0 }, - { CIPHER_NAME("MLKEM768X25519"), SEC_OID_MLKEM768X25519, 0 }, + /* NOTE, don't use '0' to indicate default off. Setting '0' + * makes this entry unmanagable by the policy code (including + * turning the entry off. If you want an entry off by default + * simply explictly flip the bits in SECOID_Init() + * (util/secoid.c) */ + { CIPHER_NAME("XYBER768D00"), SEC_OID_XYBER768D00, + NSS_USE_ALG_IN_SSL_KX }, + { CIPHER_NAME("X25519MLKEM768"), SEC_OID_MLKEM768X25519, + NSS_USE_ALG_IN_SSL_KX }, + { CIPHER_NAME("SECP256R1MLKEM768"), SEC_OID_SECP256R1MLKEM768, + NSS_USE_ALG_IN_SSL_KX }, + { CIPHER_NAME("MLKEM768X25519"), SEC_OID_MLKEM768X25519, + NSS_USE_ALG_IN_SSL_KX }, /* ANSI X9.62 named elliptic curves (characteristic two field) */ { CIPHER_NAME("C2PNB163V1"), SEC_OID_ANSIX962_EC_C2PNB163V1, NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_CERT_SIGNATURE }, @@ -461,6 +472,12 @@ static const oidValDef signOptList[] = { NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_SIGNATURE }, { CIPHER_NAME("ED25519"), SEC_OID_ED25519_PUBLIC_KEY, NSS_USE_ALG_IN_SIGNATURE }, + { CIPHER_NAME("ML-DSA-44"), SEC_OID_ML_DSA_44, + NSS_USE_ALG_IN_SIGNATURE }, + { CIPHER_NAME("ML-DSA-65"), SEC_OID_ML_DSA_65, + NSS_USE_ALG_IN_SIGNATURE }, + { CIPHER_NAME("ML-DSA-87"), SEC_OID_ML_DSA_87, + NSS_USE_ALG_IN_SIGNATURE }, }; typedef struct { diff --git a/security/nss/lib/softoken/fipstokn.c b/security/nss/lib/softoken/fipstokn.c @@ -325,7 +325,7 @@ static CK_INTERFACE fips_interfaces[] = { { (CK_UTF8CHAR_PTR) "Vendor NSS FIPS Interface", &sftk_fips_funcList, NSS_INTERFACE_FLAGS } }; /* must match the count of interfaces in fips_interfaces above*/ -#define FIPS_INTERFACE_COUNT 4 +#define FIPS_INTERFACE_COUNT PR_ARRAY_SIZE(fips_interfaces) /* CKO_NOT_A_KEY can be any object class that's not a key object. */ #define CKO_NOT_A_KEY CKO_DATA diff --git a/security/nss/lib/softoken/pkcs11.c b/security/nss/lib/softoken/pkcs11.c @@ -1451,7 +1451,11 @@ sftk_handlePrivateKeyObject(SFTKSession *session, SFTKObject *object, CK_KEY_TYP return CKR_TEMPLATE_INCOMPLETE; } } - encrypt = sign = recover = wrap = CK_FALSE; + derive = CK_FALSE; + sign = CK_FALSE; + encrypt = CK_FALSE; + recover = CK_FALSE; + wrap = CK_FALSE; decapsulate = CK_TRUE; break; case CKK_ML_DSA: diff --git a/security/nss/lib/softoken/sftkpwd.c b/security/nss/lib/softoken/sftkpwd.c @@ -93,35 +93,40 @@ static SECStatus sftkdb_passwordToKey(SFTKDBHandle *keydb, SECItem *salt, const char *pw, SECItem *key) { - SHA1Context *cx = NULL; + HASH_HashType hType; + const SECHashObject *hashObj; + void *ctx = NULL; SECStatus rv = SECFailure; + hType = salt->len == SHA384_LENGTH ? HASH_AlgSHA384 : HASH_AlgSHA1; + hashObj = HASH_GetRawHashObject(hType); + if (!pw) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } - key->data = PORT_Alloc(SHA1_LENGTH); + key->data = PORT_Alloc(hashObj->length); if (key->data == NULL) { goto loser; } - key->len = SHA1_LENGTH; + key->len = hashObj->length; - cx = SHA1_NewContext(); - if (cx == NULL) { + ctx = hashObj->create(); + if (ctx == NULL) { goto loser; } - SHA1_Begin(cx); + hashObj->begin(ctx); if (salt && salt->data) { - SHA1_Update(cx, salt->data, salt->len); + hashObj->update(ctx, salt->data, salt->len); } - SHA1_Update(cx, (unsigned char *)pw, PORT_Strlen(pw)); - SHA1_End(cx, key->data, &key->len, key->len); + hashObj->update(ctx, (unsigned char *)pw, PORT_Strlen(pw)); + hashObj->end(ctx, key->data, &key->len, key->len); rv = SECSuccess; loser: - if (cx) { - SHA1_DestroyContext(cx, PR_TRUE); + if (ctx) { + hashObj->destroy(ctx, PR_TRUE); } if (rv != SECSuccess) { if (key->data != NULL) { @@ -1362,6 +1367,7 @@ sftkdb_ChangePassword(SFTKDBHandle *keydb, unsigned char saltData[SDB_MAX_META_DATA_LEN]; unsigned char valueData[SDB_MAX_META_DATA_LEN]; int iterationCount = getPBEIterationCount(); + int preferred_salt_length; CK_RV crv; SDB *db; @@ -1393,7 +1399,18 @@ sftkdb_ChangePassword(SFTKDBHandle *keydb, goto loser; } } else { - salt.len = SHA1_LENGTH; + salt.len = 0; + } + + preferred_salt_length = SHA384_LENGTH; + + /* Prefer SHA-1 if the password is NULL */ + if (!newPin || *newPin == 0) { + preferred_salt_length = SHA1_LENGTH; + } + + if (salt.len != preferred_salt_length) { + salt.len = preferred_salt_length; RNG_GenerateGlobalRandomBytes(salt.data, salt.len); } diff --git a/security/nss/lib/softoken/softkver.h b/security/nss/lib/softoken/softkver.h @@ -17,11 +17,11 @@ * The format of the version string should be * "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]" */ -#define SOFTOKEN_VERSION "3.117" SOFTOKEN_ECC_STRING +#define SOFTOKEN_VERSION "3.118" SOFTOKEN_ECC_STRING " Beta" #define SOFTOKEN_VMAJOR 3 -#define SOFTOKEN_VMINOR 117 +#define SOFTOKEN_VMINOR 118 #define SOFTOKEN_VPATCH 0 #define SOFTOKEN_VBUILD 0 -#define SOFTOKEN_BETA PR_FALSE +#define SOFTOKEN_BETA PR_TRUE #endif /* _SOFTKVER_H_ */ diff --git a/security/nss/lib/ssl/sslimpl.h b/security/nss/lib/ssl/sslimpl.h @@ -132,7 +132,7 @@ typedef enum { SSLAppOpRead = 0, #define DTLS_RETRANSMIT_FINISHED_MS 30000 /* default number of entries in namedGroupPreferences */ -#define SSL_NAMED_GROUP_COUNT 33 +#define SSL_NAMED_GROUP_COUNT 34 /* The maximum DH and RSA bit-length supported. */ #define SSL_MAX_DH_KEY_BITS 8192 diff --git a/security/nss/lib/ssl/sslsock.c b/security/nss/lib/ssl/sslsock.c @@ -158,16 +158,22 @@ static const PRUint16 srtpCiphers[] = { ssl_grp_ffdhe_##size, size, ssl_kea_dh, \ SEC_OID_TLS_FFDHE_##size, PR_TRUE \ } +#define HYGROUP(first, second, size, first_oid, second_oid, assumeSupported) \ + { \ + ssl_grp_kem_##first##second, size, ssl_kea_ecdh_hybrid, \ + SEC_OID_##first_oid##second_oid, assumeSupported \ + } const sslNamedGroupDef ssl_named_groups[] = { - /* Note that 256 for 25519 is a lie, but we only use it for checking bit - * security and expect 256 bits there (not 255). */ + /* Note that 256 for 25519 and x25519mlkem786 is a lie, but we only use it + * for checking bit security and expect 256 bits there (not 255). */ + HYGROUP(mlkem768, x25519, 256, MLKEM768, X25519, PR_TRUE), { ssl_grp_ec_curve25519, 256, ssl_kea_ecdh, SEC_OID_CURVE25519, PR_TRUE }, ECGROUP(secp256r1, 256, SECP256R1, PR_TRUE), ECGROUP(secp384r1, 384, SECP384R1, PR_TRUE), ECGROUP(secp521r1, 521, SECP521R1, PR_TRUE), + HYGROUP(secp256r1, mlkem768, 256, SECP256R1, MLKEM768, PR_TRUE), { ssl_grp_kem_xyber768d00, 256, ssl_kea_ecdh_hybrid, SEC_OID_XYBER768D00, PR_FALSE }, - { ssl_grp_kem_mlkem768x25519, 256, ssl_kea_ecdh_hybrid, SEC_OID_MLKEM768X25519, PR_TRUE }, FFGROUP(2048), FFGROUP(3072), FFGROUP(4096), diff --git a/security/nss/lib/ssl/sslt.h b/security/nss/lib/ssl/sslt.h @@ -260,6 +260,7 @@ typedef enum { ssl_grp_ffdhe_4096 = 258, ssl_grp_ffdhe_6144 = 259, ssl_grp_ffdhe_8192 = 260, + ssl_grp_kem_secp256r1mlkem768 = 4587, ssl_grp_kem_mlkem768x25519 = 4588, ssl_grp_kem_xyber768d00 = 25497, /* draft-tls-westerbaan-xyber768d00-02 */ ssl_grp_none = 65537, /* special value */ diff --git a/security/nss/lib/ssl/tls13con.c b/security/nss/lib/ssl/tls13con.c @@ -387,6 +387,7 @@ tls13_CreateKEMKeyPair(sslSocket *ss, const sslNamedGroupDef *groupDef, paramSet = CKP_NSS_KYBER_768_ROUND3; break; case ssl_grp_kem_mlkem768x25519: + case ssl_grp_kem_secp256r1mlkem768: mechanism = CKM_ML_KEM_KEY_PAIR_GEN; paramSet = CKP_ML_KEM_768; break; @@ -463,6 +464,57 @@ loser: return SECFailure; } +/* only copy the ECDH component of an ephemeral KeyPair */ +sslEphemeralKeyPair * +tls13_CopyECDHKeyFromHybrid(sslEphemeralKeyPair *copyKeyPair, + const sslNamedGroupDef *groupDef) +{ + /* We could use ssl_CopyEphemeralKeyPair here, but we would need to free + * the KEM components. So we only copy the ECDH keys */ + sslEphemeralKeyPair *keyPair = PORT_ZNew(sslEphemeralKeyPair); + if (!keyPair) { + return NULL; + } + PR_INIT_CLIST(&keyPair->link); + keyPair->group = groupDef; + keyPair->keys = ssl_GetKeyPairRef(copyKeyPair->keys); + return keyPair; +} + +/* + * find a hybrid key Pair they might contain the same ecdh key so we + * can reuse them. Each ec group can map to more than one hybrid Pair + */ +sslEphemeralKeyPair * +tls13_FindHybridKeyPair(sslSocket *ss, const sslNamedGroupDef *groupDef) +{ + sslEphemeralKeyPair *hybridPair = NULL; + switch (groupDef->name) { + case ssl_grp_ec_secp256r1: + /* future, this may be a loop to check multiple named groups */ + hybridPair = ssl_LookupEphemeralKeyPair(ss, + ssl_LookupNamedGroup(ssl_grp_kem_secp256r1mlkem768)); + break; + case ssl_grp_ec_curve25519: { + /* a loop to check multiple named groups */ + SSLNamedGroup gnames[] = { ssl_grp_kem_xyber768d00, + ssl_grp_kem_mlkem768x25519 }; + for (int i = 0; i < PR_ARRAY_SIZE(gnames); i++) { + hybridPair = ssl_LookupEphemeralKeyPair(ss, + ssl_LookupNamedGroup(gnames[i])); + if (hybridPair != NULL) { + break; + } + } + break; + } + default: + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return NULL; + } + return hybridPair; +} + SECStatus tls13_CreateKeyShare(sslSocket *ss, const sslNamedGroupDef *groupDef, sslEphemeralKeyPair **outKeyPair) @@ -470,21 +522,33 @@ tls13_CreateKeyShare(sslSocket *ss, const sslNamedGroupDef *groupDef, SECStatus rv; const ssl3DHParams *params; sslEphemeralKeyPair *keyPair = NULL; + const sslNamedGroupDef *ecGroup = NULL; PORT_Assert(groupDef); switch (groupDef->keaType) { case ssl_kea_ecdh_hybrid: - if (groupDef->name != ssl_grp_kem_xyber768d00 && groupDef->name != ssl_grp_kem_mlkem768x25519) { + switch (groupDef->name) { + case ssl_grp_kem_secp256r1mlkem768: + ecGroup = ssl_LookupNamedGroup(ssl_grp_ec_secp256r1); + break; + case ssl_grp_kem_xyber768d00: + case ssl_grp_kem_mlkem768x25519: + ecGroup = ssl_LookupNamedGroup(ssl_grp_ec_curve25519); + break; + default: + PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); + return SECFailure; + } + if (ecGroup == NULL) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; } - const sslNamedGroupDef *x25519 = ssl_LookupNamedGroup(ssl_grp_ec_curve25519); - sslEphemeralKeyPair *x25519Pair = ssl_LookupEphemeralKeyPair(ss, x25519); - if (x25519Pair) { - keyPair = ssl_CopyEphemeralKeyPair(x25519Pair); + keyPair = ssl_LookupEphemeralKeyPair(ss, ecGroup); + if (keyPair) { + keyPair = ssl_CopyEphemeralKeyPair(keyPair); } if (!keyPair) { - rv = ssl_CreateECDHEphemeralKeyPair(ss, x25519, &keyPair); + rv = ssl_CreateECDHEphemeralKeyPair(ss, ecGroup, &keyPair); if (rv != SECSuccess) { return SECFailure; } @@ -492,23 +556,9 @@ tls13_CreateKeyShare(sslSocket *ss, const sslNamedGroupDef *groupDef, keyPair->group = groupDef; break; case ssl_kea_ecdh: - if (groupDef->name == ssl_grp_ec_curve25519) { - sslEphemeralKeyPair *hybridPair = ssl_LookupEphemeralKeyPair(ss, ssl_LookupNamedGroup(ssl_grp_kem_mlkem768x25519)); - if (!hybridPair) { - hybridPair = ssl_LookupEphemeralKeyPair(ss, ssl_LookupNamedGroup(ssl_grp_kem_xyber768d00)); - } - if (hybridPair) { - // We could use ssl_CopyEphemeralKeyPair here, but we would need to free - // the KEM components. We should pull this out into a utility function when - // we refactor to support multiple hybrid mechanisms. - keyPair = PORT_ZNew(sslEphemeralKeyPair); - if (!keyPair) { - return SECFailure; - } - PR_INIT_CLIST(&keyPair->link); - keyPair->group = groupDef; - keyPair->keys = ssl_GetKeyPairRef(hybridPair->keys); - } + keyPair = tls13_FindHybridKeyPair(ss, groupDef); + if (keyPair) { + keyPair = tls13_CopyECDHKeyFromHybrid(keyPair, groupDef); } if (!keyPair) { rv = ssl_CreateECDHEphemeralKeyPair(ss, groupDef, &keyPair); @@ -728,6 +778,9 @@ tls13_ImportKEMKeyShare(SECKEYPublicKey *peerKey, TLS13KeyShareEntry *entry) case ssl_grp_kem_mlkem768x25519: expected_len = X25519_PUBLIC_KEY_BYTES + KYBER768_PUBLIC_KEY_BYTES; break; + case ssl_grp_kem_secp256r1mlkem768: + expected_len = SECP256_PUBLIC_KEY_BYTES + KYBER768_PUBLIC_KEY_BYTES; + break; default: PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG); return SECFailure; @@ -753,6 +806,13 @@ tls13_ImportKEMKeyShare(SECKEYPublicKey *peerKey, TLS13KeyShareEntry *entry) pk.data = entry->key_exchange.data; pk.len = KYBER768_PUBLIC_KEY_BYTES; break; + case ssl_grp_kem_secp256r1mlkem768: + peerKey->keyType = kyberKey; + peerKey->u.kyber.params = params_ml_kem768; + /* key_exchange.data is `secp256 || mlkem768` */ + pk.data = entry->key_exchange.data + SECP256_PUBLIC_KEY_BYTES; + pk.len = KYBER768_PUBLIC_KEY_BYTES; + break; default: PORT_Assert(0); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); @@ -791,6 +851,14 @@ tls13_HandleKEMCiphertext(sslSocket *ss, TLS13KeyShareEntry *entry, sslKeyPair * ct.data = entry->key_exchange.data; ct.len = KYBER768_CIPHERTEXT_BYTES; break; + case ssl_grp_kem_secp256r1mlkem768: + if (entry->key_exchange.len != SECP256_PUBLIC_KEY_BYTES + KYBER768_CIPHERTEXT_BYTES) { + ssl_MapLowLevelError(SSL_ERROR_RX_MALFORMED_HYBRID_KEY_SHARE); + return SECFailure; + } + ct.data = entry->key_exchange.data + SECP256_PUBLIC_KEY_BYTES; + ct.len = KYBER768_CIPHERTEXT_BYTES; + break; default: PORT_Assert(0); ssl_MapLowLevelError(SEC_ERROR_LIBRARY_FAILURE); @@ -883,6 +951,8 @@ tls13_HandleKeyShare(sslSocket *ss, unsigned char *ec_data; SECStatus rv; int keySize = 0; + const sslNamedGroupDef *ecGroup = NULL; + int ec_len = 0; PORT_InitCheapArena(&arena, DER_DEFAULT_CHUNKSIZE); peerKey = PORT_ArenaZNew(&arena.arena, SECKEYPublicKey); @@ -897,16 +967,28 @@ tls13_HandleKeyShare(sslSocket *ss, case ssl_kea_ecdh_hybrid: switch (entry->group->name) { case ssl_grp_kem_xyber768d00: + ec_len = X25519_PUBLIC_KEY_BYTES; // x25519 share is at the beginning - ec_data = entry->key_exchange.len < X25519_PUBLIC_KEY_BYTES + ec_data = entry->key_exchange.len < ec_len ? NULL : entry->key_exchange.data; + ecGroup = ssl_LookupNamedGroup(ssl_grp_ec_curve25519); break; case ssl_grp_kem_mlkem768x25519: + ec_len = X25519_PUBLIC_KEY_BYTES; // x25519 share is at the end - ec_data = entry->key_exchange.len < X25519_PUBLIC_KEY_BYTES + ec_data = entry->key_exchange.len < ec_len ? NULL - : entry->key_exchange.data + entry->key_exchange.len - X25519_PUBLIC_KEY_BYTES; + : entry->key_exchange.data + entry->key_exchange.len - ec_len; + ecGroup = ssl_LookupNamedGroup(ssl_grp_ec_curve25519); + break; + case ssl_grp_kem_secp256r1mlkem768: + ec_len = SECP256_PUBLIC_KEY_BYTES; + /* secp256 share is at the beginning */ + ec_data = entry->key_exchange.len < ec_len + ? NULL + : entry->key_exchange.data; + ecGroup = ssl_LookupNamedGroup(ssl_grp_ec_secp256r1); break; default: ec_data = NULL; @@ -916,10 +998,7 @@ tls13_HandleKeyShare(sslSocket *ss, PORT_SetError(SSL_ERROR_RX_MALFORMED_HYBRID_KEY_SHARE); goto loser; } - rv = ssl_ImportECDHKeyShare(peerKey, - ec_data, - X25519_PUBLIC_KEY_BYTES, - ssl_LookupNamedGroup(ssl_grp_ec_curve25519)); + rv = ssl_ImportECDHKeyShare(peerKey, ec_data, ec_len, ecGroup); mechanism = CKM_ECDH1_DERIVE; break; case ssl_kea_ecdh: @@ -2790,6 +2869,7 @@ tls13_HandleClientKeyShare(sslSocket *ss, TLS13KeyShareEntry *peerShare) goto loser; /* Error set by tls13_HandleKEMKey */ } switch (peerShare->group->name) { + case ssl_grp_kem_secp256r1mlkem768: case ssl_grp_kem_xyber768d00: ss->ssl3.hs.dheSecret = PK11_ConcatSymKeys(dheSecret, kemSecret, CKM_HKDF_DERIVE, CKA_DERIVE); break; @@ -3649,6 +3729,7 @@ tls13_HandleServerKeyShare(sslSocket *ss) goto loser; /* Error set by tls13_HandleKEMCiphertext */ } switch (entry->group->name) { + case ssl_grp_kem_secp256r1mlkem768: case ssl_grp_kem_xyber768d00: ss->ssl3.hs.dheSecret = PK11_ConcatSymKeys(dheSecret, kemSecret, CKM_HKDF_DERIVE, CKA_DERIVE); break; diff --git a/security/nss/lib/ssl/tls13exthandle.c b/security/nss/lib/ssl/tls13exthandle.c @@ -80,13 +80,17 @@ tls13_SizeOfKeyShareEntry(const sslEphemeralKeyPair *keyPair) if (keyPair->kemKeys) { PORT_Assert(!keyPair->kemCt); - PORT_Assert(keyPair->group->name == ssl_grp_kem_xyber768d00 || keyPair->group->name == ssl_grp_kem_mlkem768x25519); + PORT_Assert(keyPair->group->name == ssl_grp_kem_xyber768d00 || + keyPair->group->name == ssl_grp_kem_mlkem768x25519 || + keyPair->group->name == ssl_grp_kem_secp256r1mlkem768); pubKey = keyPair->kemKeys->pubKey; size += pubKey->u.kyber.publicValue.len; } if (keyPair->kemCt) { PORT_Assert(!keyPair->kemKeys); - PORT_Assert(keyPair->group->name == ssl_grp_kem_xyber768d00 || keyPair->group->name == ssl_grp_kem_mlkem768x25519); + PORT_Assert(keyPair->group->name == ssl_grp_kem_xyber768d00 || + keyPair->group->name == ssl_grp_kem_mlkem768x25519 || + keyPair->group->name == ssl_grp_kem_secp256r1mlkem768); size += keyPair->kemCt->len; } @@ -94,12 +98,13 @@ tls13_SizeOfKeyShareEntry(const sslEphemeralKeyPair *keyPair) } static SECStatus -tls13_WriteXyber768D00KeyExchangeInfo(sslBuffer *buf, sslEphemeralKeyPair *keyPair) +tls13_WriteHybridECCKeyFirst(sslBuffer *buf, sslEphemeralKeyPair *keyPair) { - PORT_Assert(keyPair->group->name == ssl_grp_kem_xyber768d00); + PORT_Assert(keyPair->group->name == ssl_grp_kem_xyber768d00 || + keyPair->group->name == ssl_grp_kem_secp256r1mlkem768); PORT_Assert(keyPair->keys->pubKey->keyType == ecKey); - // Encode the X25519 share first, then the Kyber768 key or ciphertext. + // Encode the ecc share first, then the MLKEM key or ciphertext. SECStatus rv; rv = sslBuffer_Append(buf, keyPair->keys->pubKey->u.ec.publicValue.data, keyPair->keys->pubKey->u.ec.publicValue.len); @@ -121,7 +126,7 @@ tls13_WriteXyber768D00KeyExchangeInfo(sslBuffer *buf, sslEphemeralKeyPair *keyPa } static SECStatus -tls13_WriteMLKEM768X25519KeyExchangeInfo(sslBuffer *buf, sslEphemeralKeyPair *keyPair) +tls13_WriteHybridHybridKeyFirst(sslBuffer *buf, sslEphemeralKeyPair *keyPair) { PORT_Assert(keyPair->group->name == ssl_grp_kem_mlkem768x25519); PORT_Assert(keyPair->keys->pubKey->keyType == ecKey); @@ -188,10 +193,11 @@ tls13_EncodeKeyShareEntry(sslBuffer *buf, sslEphemeralKeyPair *keyPair) switch (keyPair->group->name) { case ssl_grp_kem_mlkem768x25519: - rv = tls13_WriteMLKEM768X25519KeyExchangeInfo(buf, keyPair); + rv = tls13_WriteHybridHybridKeyFirst(buf, keyPair); break; + case ssl_grp_kem_secp256r1mlkem768: case ssl_grp_kem_xyber768d00: - rv = tls13_WriteXyber768D00KeyExchangeInfo(buf, keyPair); + rv = tls13_WriteHybridECCKeyFirst(buf, keyPair); break; default: rv = tls13_WriteKeyExchangeInfo(buf, keyPair); diff --git a/security/nss/lib/util/eccutil.h b/security/nss/lib/util/eccutil.h @@ -6,6 +6,7 @@ #define _FREEBL_H_ #define X25519_PUBLIC_KEY_BYTES 32U +#define SECP256_PUBLIC_KEY_BYTES 65U /* deprecated */ typedef enum { diff --git a/security/nss/lib/util/nssutil.h b/security/nss/lib/util/nssutil.h @@ -19,12 +19,12 @@ * The format of the version string should be * "<major version>.<minor version>[.<patch level>[.<build number>]][ <Beta>]" */ -#define NSSUTIL_VERSION "3.117" +#define NSSUTIL_VERSION "3.118 Beta" #define NSSUTIL_VMAJOR 3 -#define NSSUTIL_VMINOR 117 +#define NSSUTIL_VMINOR 118 #define NSSUTIL_VPATCH 0 #define NSSUTIL_VBUILD 0 -#define NSSUTIL_BETA PR_FALSE +#define NSSUTIL_BETA PR_TRUE SEC_BEGIN_PROTOS diff --git a/security/nss/lib/util/secoid.c b/security/nss/lib/util/secoid.c @@ -1905,13 +1905,16 @@ const static SECOidData oids[SEC_OID_TOTAL] = { "X25519 key exchange", CKM_EC_MONTGOMERY_KEY_PAIR_GEN, INVALID_CERT_EXTENSION), ODE(SEC_OID_MLKEM768X25519, - "ML-KEM-768+X25519 key exchange", CKM_INVALID_MECHANISM, INVALID_CERT_EXTENSION), + "X25519+ML-KEM-768 Hybrid key exchange", CKM_INVALID_MECHANISM, INVALID_CERT_EXTENSION), ODE(SEC_OID_TLS_REQUIRE_EMS, "TLS Require EMS", CKM_INVALID_MECHANISM, INVALID_CERT_EXTENSION), OD(mlDsa44, SEC_OID_ML_DSA_44, "ML-DSA-44", CKM_ML_DSA, INVALID_CERT_EXTENSION), OD(mlDsa65, SEC_OID_ML_DSA_65, "ML-DSA-65", CKM_ML_DSA, INVALID_CERT_EXTENSION), OD(mlDsa87, SEC_OID_ML_DSA_87, "ML-DSA-87", CKM_ML_DSA, INVALID_CERT_EXTENSION), + ODE(SEC_OID_SECP256R1MLKEM768, + "SECP256R1+ML-KEM-768 Hybrid key exchange", CKM_INVALID_MECHANISM, INVALID_CERT_EXTENSION), + }; /* PRIVATE EXTENDED SECOID Table diff --git a/security/nss/lib/util/secoidt.h b/security/nss/lib/util/secoidt.h @@ -531,7 +531,6 @@ typedef enum { SEC_OID_RC2_128_CBC = 386, SEC_OID_ECDH_KEA = 387, SEC_OID_X25519 = 388, - SEC_OID_MLKEM768X25519 = 389, SEC_OID_TLS_REQUIRE_EMS = 390, @@ -540,6 +539,8 @@ typedef enum { SEC_OID_ML_DSA_65 = 392, SEC_OID_ML_DSA_87 = 393, + SEC_OID_SECP256R1MLKEM768 = 394, + SEC_OID_TOTAL } SECOidTag; diff --git a/security/nss/moz.yaml b/security/nss/moz.yaml @@ -9,8 +9,8 @@ origin: description: nss url: https://hg-edge.mozilla.org/projects/nss - release: 11dc5f9349ad12af08b792a3f705166056547950 (2025-10-03T11:12:45Z). - revision: 11dc5f9349ad12af08b792a3f705166056547950 + release: f9041cc46f7495257b639e7e36fa8f2f0d50faa0 (2025-10-13T23:31:48Z). + revision: f9041cc46f7495257b639e7e36fa8f2f0d50faa0 license: MPL-2.0 license-file: COPYING diff --git a/security/nss/tests/ssl/ssl.sh b/security/nss/tests/ssl/ssl.sh @@ -124,9 +124,9 @@ ssl_init() # in fips mode, turn off curve25519 until it's NIST approved FIPS_OPTIONS="" # in fips mode, turn off curve25519 until it's NIST approved - ALL_GROUPS="P256,P384,P521,x25519,FF2048,FF3072,FF4096,FF6144,FF8192,xyber768d00,mlkem768x25519" + ALL_GROUPS="P256,P384,P521,x25519,FF2048,FF3072,FF4096,FF6144,FF8192,xyber768d00,x25519mlkem768,secp256r1mlkem768" NON_PQ_GROUPS="P256,P384,P521,x25519,FF2048,FF3072,FF4096,FF6144,FF8192" - FIPS_GROUPS="P256,P384,P521,FF2048,FF3072,FF4096,FF6144,FF8192,mlkem768x25519" + FIPS_GROUPS="P256,P384,P521,FF2048,FF3072,FF4096,FF6144,FF8192,mx25519mlkem768,secp256r1mlkem768" FIPS_NON_PQ_GROUPS="P256,P384,P521,FF2048,FF3072,FF4096,FF6144,FF8192" @@ -387,7 +387,9 @@ ssl_cov() if [ "$ectype" = "XYBER" ]; then TLS_GROUPS="xyber768d00" elif [ "$ectype" = "MLKEM219" ]; then - TLS_GROUPS="mlkem768x25519" + TLS_GROUPS="x25519mlkem768" + elif [ "$ectype" = "MLKEM256" ]; then + TLS_GROUPS="secp256r1mlkem768" fi echo "tstclnt -4 -p ${PORT} -h ${HOSTADDR} -c ${param} -I \"${TLS_GROUPS}\" -V ${VMIN}:${VMAX} ${CLIENT_OPTIONS} \\" diff --git a/security/nss/tests/ssl/sslcov.txt b/security/nss/tests/ssl/sslcov.txt @@ -153,9 +153,12 @@ ECC TLS13 :1301 ECC TLS13_ECDHE_WITH_AES_128_GCM_SHA256 ECC TLS13 :1302 ECC TLS13_ECDHE_WITH_AES_256_GCM_SHA384 ECC TLS13 :1303 ECC TLS13_ECDHE_WITH_CHACHA20_POLY1305_SHA256 -MLKEM219 TLS13 :1301 ECC TLS13_MLKEM768X25519_WITH_AES_128_GCM_SHA256 -MLKEM219 TLS13 :1302 ECC TLS13_MLKEM768X25519_WITH_AES_256_GCM_SHA384 -MLKEM219 TLS13 :1303 ECC TLS13_MLKEM768X25519_WITH_CHACHA20_POLY1305_SHA256 +MLKEM219 TLS13 :1301 ECC TLS13_X25519MLKEM768_WITH_AES_128_GCM_SHA256 +MLKEM219 TLS13 :1302 ECC TLS13_X25519MLKEM768_WITH_AES_256_GCM_SHA384 +MLKEM219 TLS13 :1303 ECC TLS13_X25519MLKEM768_WITH_CHACHA20_POLY1305_SHA256 +MLKEM256 TLS13 :1301 ECC TLS13_SECP256R1MLKEM768_WITH_AES_128_GCM_SHA256 +MLKEM256 TLS13 :1302 ECC TLS13_SECP256R1MLKEM768_WITH_AES_256_GCM_SHA384 +MLKEM256 TLS13 :1303 ECC TLS13_SECP256R1MLKEM768_WITH_CHACHA20_POLY1305_SHA256 # need to turn on policy in selfserv/tstclnt to make these work #XYBER TLS13 :1301 ECC TLS13_XYBER768D00_WITH_AES_128_GCM_SHA256 #XYBER TLS13 :1302 ECC TLS13_XYBER768D00_WITH_AES_256_GCM_SHA384