Mercurial > prosody-hg
comparison spec/util_tlsref_spec.lua @ 14218:926f25af2ffe 13.0
util.tlsref: New util library to encapsulate the Mozilla/TLSRef recommendations
Previously we embedded this data directly into core.certmanager. This splits
it out, for various benefits:
- Removes data from the business logic (the config parsing is complex enough
as it is)
- Allows easier testing and tracking of the data (this commit adds various
consistency checks, so that we can have more confidence in future updates
to the data not breaking stuff)
This library also supports multiple versions of the recommendations.
Previously we picked a single version, and put that into certmanager. But this
meant we had to make choices for our users, and one of the choices is between
advancing security standards and ensuring we don't break connectivity for
people doing minor upgrades (deterring people from performing minor upgrades
would have a security impact too).
This library supports the concept of a "default" and a "latest" version, so we
are now free to add new versions into stable releases, and admins can pick
between compatibility and security.
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Fri, 12 Jun 2026 13:01:56 +0100 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 14217:7eb0e47418ab | 14218:926f25af2ffe |
|---|---|
| 1 local tlsref = require "prosody.util.tlsref"; | |
| 2 | |
| 3 describe("util.tlsref", function() | |
| 4 local version_compare = require "prosody.util.human.io".simple_version_compare; | |
| 5 | |
| 6 describe("default version", function() | |
| 7 it("is not greater than latest version", function () | |
| 8 local default_version = tlsref.get_default_version(); | |
| 9 local latest_version = tlsref.get_latest_version(); | |
| 10 assert.equal(false, version_compare(latest_version, default_version)); | |
| 11 end); | |
| 12 | |
| 13 it("is a known version", function () | |
| 14 assert.is_table(tlsref.get_profile(tlsref.get_default_version())); | |
| 15 end); | |
| 16 end); | |
| 17 | |
| 18 describe("latest version", function () | |
| 19 it("is a known version", function () | |
| 20 assert.is_table(tlsref.get_profile("latest")); | |
| 21 end); | |
| 22 | |
| 23 it("is the first entry of get_valid_versions()", function () | |
| 24 local versions = tlsref.get_valid_versions(); | |
| 25 assert.equal(tlsref.get_latest_version(), versions[1]); | |
| 26 end); | |
| 27 end); | |
| 28 | |
| 29 describe("get_valid_versions()", function () | |
| 30 it("returns versions in descending order", function () | |
| 31 local versions = tlsref.get_valid_versions(); | |
| 32 assert.is_true(#versions >= 1); | |
| 33 for i = 1, #versions-1 do | |
| 34 -- versions[i] must sort before versions[i+1] | |
| 35 assert.equal(false, version_compare(versions[i], versions[i+1])); | |
| 36 end | |
| 37 end); | |
| 38 | |
| 39 it("contains the default version", function () | |
| 40 local found = false; | |
| 41 for _, v in ipairs(tlsref.get_valid_versions()) do | |
| 42 if v == tlsref.get_default_version() then found = true; end | |
| 43 end | |
| 44 assert.is_true(found); | |
| 45 end); | |
| 46 end); | |
| 47 | |
| 48 describe("get_valid_profile_names()", function () | |
| 49 it("returns an error for unknown versions", function () | |
| 50 local names, err = tlsref.get_valid_profile_names("0.0"); | |
| 51 assert.is_nil(names); | |
| 52 assert.equal("unknown-version", err); | |
| 53 end); | |
| 54 | |
| 55 it("uses the default version when none is given", function () | |
| 56 assert.same( | |
| 57 tlsref.get_valid_profile_names(tlsref.get_default_version()), | |
| 58 tlsref.get_valid_profile_names() | |
| 59 ); | |
| 60 end); | |
| 61 | |
| 62 it("accepts 'latest' as a version", function () | |
| 63 assert.same( | |
| 64 tlsref.get_valid_profile_names(tlsref.get_latest_version()), | |
| 65 tlsref.get_valid_profile_names("latest") | |
| 66 ); | |
| 67 end); | |
| 68 | |
| 69 it("lists the most-preferred profile first", function () | |
| 70 for _, version in ipairs(tlsref.get_valid_versions()) do | |
| 71 local names = tlsref.get_valid_profile_names(version); | |
| 72 assert.equal("modern", names[1], | |
| 73 ("most-preferred profile should be first for version %s"):format(version)); | |
| 74 end | |
| 75 end); | |
| 76 end); | |
| 77 | |
| 78 describe("get_profile()", function () | |
| 79 it("returns the default profile of the default version when called with no arguments", function () | |
| 80 assert.equal( | |
| 81 tlsref.get_profile(tlsref.get_default_version(), tlsref.get_default_profile_name()), | |
| 82 tlsref.get_profile() | |
| 83 ); | |
| 84 end); | |
| 85 | |
| 86 it("resolves 'latest' to the latest version", function () | |
| 87 assert.equal( | |
| 88 tlsref.get_profile(tlsref.get_latest_version()), | |
| 89 tlsref.get_profile("latest") | |
| 90 ); | |
| 91 end); | |
| 92 | |
| 93 it("returns an error for unknown versions", function () | |
| 94 local profile, err = tlsref.get_profile("0.0"); | |
| 95 assert.is_nil(profile); | |
| 96 assert.equal("unknown-version", err); | |
| 97 end); | |
| 98 | |
| 99 it("returns an error for unknown profile names", function () | |
| 100 local profile, err = tlsref.get_profile(nil, "no-such-profile"); | |
| 101 assert.is_nil(profile); | |
| 102 assert.equal("unknown-profile", err); | |
| 103 end); | |
| 104 | |
| 105 it("returns an error for profiles absent from a given version", function () | |
| 106 -- 'old' was dropped from the 6.0 guidelines | |
| 107 local profile, err = tlsref.get_profile("6.0", "old"); | |
| 108 assert.is_nil(profile); | |
| 109 assert.equal("unknown-profile", err); | |
| 110 end); | |
| 111 | |
| 112 it("resolves every advertised version/profile combination", function () | |
| 113 for _, version in ipairs(tlsref.get_valid_versions()) do | |
| 114 for _, name in ipairs(tlsref.get_valid_profile_names(version)) do | |
| 115 local profile, err = tlsref.get_profile(version, name); | |
| 116 assert.is_table(profile, | |
| 117 ("get_profile(%q, %q) failed: %s"):format(version, name, err)); | |
| 118 end | |
| 119 end | |
| 120 end); | |
| 121 end); | |
| 122 | |
| 123 describe("get_default_profile_name()", function () | |
| 124 it("returns an error for unknown versions", function () | |
| 125 local name, err = tlsref.get_default_profile_name("0.0"); | |
| 126 assert.is_nil(name); | |
| 127 assert.equal("unknown-version", err); | |
| 128 end); | |
| 129 | |
| 130 it("accepts 'latest' as a version", function () | |
| 131 assert.is_string(tlsref.get_default_profile_name("latest")); | |
| 132 end); | |
| 133 | |
| 134 it("returns a profile name valid for every version", function () | |
| 135 for _, version in ipairs(tlsref.get_valid_versions()) do | |
| 136 local name = tlsref.get_default_profile_name(version); | |
| 137 assert.is_string(name); | |
| 138 assert.is_table(tlsref.get_profile(version, name), | |
| 139 ("default profile %q missing from version %s"):format(name, version)); | |
| 140 end | |
| 141 end); | |
| 142 end); | |
| 143 | |
| 144 describe("profile contents", function () | |
| 145 it("every profile specifies a TLS protocol version", function () | |
| 146 for _, version in ipairs(tlsref.get_valid_versions()) do | |
| 147 for _, name in ipairs(tlsref.get_valid_profile_names(version)) do | |
| 148 local profile = tlsref.get_profile(version, name); | |
| 149 assert.is_string(profile.protocol); | |
| 150 assert.truthy(profile.protocol:match("^tlsv1"), | |
| 151 ("unexpected protocol %q in %s/%s"):format(profile.protocol, version, name)); | |
| 152 end | |
| 153 end | |
| 154 end); | |
| 155 | |
| 156 it("every profile specifies TLS 1.3 ciphersuites and curves", function () | |
| 157 for _, version in ipairs(tlsref.get_valid_versions()) do | |
| 158 for _, name in ipairs(tlsref.get_valid_profile_names(version)) do | |
| 159 local profile = tlsref.get_profile(version, name); | |
| 160 assert.is_table(profile.ciphersuites); | |
| 161 assert.is_true(#profile.ciphersuites > 0); | |
| 162 assert.is_table(profile.curveslist); | |
| 163 assert.is_true(#profile.curveslist > 0); | |
| 164 end | |
| 165 end | |
| 166 end); | |
| 167 end); | |
| 168 end); |
