Skip to content

Commit bbcc0ac

Browse files
hope51607marty1885
andauthored
Optimization SSL name matching (#215)
Co-authored-by: marty1885 <marty188586@gmail.com>
1 parent 46d9b44 commit bbcc0ac

File tree

6 files changed

+181
-49
lines changed

6 files changed

+181
-49
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ cmake-build-debug
44
.vscode
55
.vs
66
CMakeSettings.json
7+
.cache

trantor/net/inner/TcpConnectionImpl.cc

Lines changed: 5 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@
3434
#include <openssl/x509v3.h>
3535
#include <openssl/err.h>
3636
#endif
37-
#include <regex>
38-
3937
using namespace trantor;
4038

4139
#ifdef _WIN32
@@ -88,49 +86,6 @@ inline bool loadWindowsSystemCert(X509_STORE *store)
8886
}
8987
#endif
9088

91-
inline std::string certNameToRegex(const std::string &certName)
92-
{
93-
std::string result;
94-
result.reserve(certName.size() + 11);
95-
96-
bool isStar = false;
97-
bool isLeadingStar = true;
98-
for (char ch : certName)
99-
{
100-
if (isStar == false)
101-
{
102-
if (ch == '*')
103-
isStar = true;
104-
else if (ch == '.')
105-
{
106-
result += "\\.";
107-
isLeadingStar = false;
108-
}
109-
else
110-
{
111-
result.push_back(ch);
112-
isLeadingStar = false;
113-
}
114-
}
115-
else
116-
{
117-
if (ch == '.' && isLeadingStar)
118-
result += "([^.]*\\.|)?";
119-
else
120-
result += std::string("[^.]*") + ch;
121-
isStar = false;
122-
}
123-
}
124-
assert(isStar == false);
125-
return result;
126-
}
127-
128-
inline bool verifyName(const std::string &certName, const std::string &hostname)
129-
{
130-
std::regex re(certNameToRegex(certName));
131-
return std::regex_match(hostname, re);
132-
}
133-
13489
inline bool verifyCommonName(X509 *cert, const std::string &hostname)
13590
{
13691
X509_NAME *subjectName = X509_get_subject_name(cert);
@@ -145,8 +100,9 @@ inline bool verifyCommonName(X509 *cert, const std::string &hostname)
145100
if (length == -1)
146101
return false;
147102

148-
return verifyName(std::string(name.begin(), name.begin() + length),
149-
hostname);
103+
return utils::verifySslName(std::string(name.begin(),
104+
name.begin() + length),
105+
hostname);
150106
}
151107

152108
return false;
@@ -177,7 +133,8 @@ inline bool verifyAltName(X509 *cert, const std::string &hostname)
177133
auto name = (const char *)ASN1_STRING_data(val->d.ia5);
178134
#endif
179135
auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
180-
good = verifyName(std::string(name, name + name_len), hostname);
136+
good = utils::verifySslName(std::string(name, name + name_len),
137+
hostname);
181138
}
182139
}
183140

trantor/unittests/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ add_executable(inetaddress_unittest InetAddressUnittest.cc)
44
add_executable(date_unittest DateUnittest.cc)
55
add_executable(split_string_unittest splitStringUnittest.cc)
66
add_executable(string_encoding_unittest stringEncodingUnittest.cc)
7+
add_executable(ssl_name_verify_unittest sslNameVerifyUnittest.cc)
78
set(UNITTEST_TARGETS
89
msgbuffer_unittest
910
inetaddress_unittest
1011
date_unittest
1112
split_string_unittest
12-
string_encoding_unittest)
13+
string_encoding_unittest
14+
ssl_name_verify_unittest)
1315
set_property(TARGET ${UNITTEST_TARGETS} PROPERTY CXX_STANDARD 14)
1416
set_property(TARGET ${UNITTEST_TARGETS} PROPERTY CXX_STANDARD_REQUIRED ON)
1517
set_property(TARGET ${UNITTEST_TARGETS} PROPERTY CXX_EXTENSIONS OFF)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include <trantor/utils/Utilities.h>
2+
#include <gtest/gtest.h>
3+
#include <iostream>
4+
using namespace trantor;
5+
using namespace trantor::utils;
6+
7+
TEST(sslNameCheck, baseCases)
8+
{
9+
EXPECT_EQ(verifySslName("example.com", "example.com"), true);
10+
EXPECT_EQ(verifySslName("example.com", "example.org"), false);
11+
EXPECT_EQ(verifySslName("example.com", "www.example.com"), false);
12+
}
13+
14+
TEST(sslNameCheck, rfc6125Examples)
15+
{
16+
EXPECT_EQ(verifySslName("*.example.com", "foo.example.com"), true);
17+
EXPECT_EQ(verifySslName("*.example.com", "foo.bar.example.com"), false);
18+
EXPECT_EQ(verifySslName("*.example.com", "example.com"), false);
19+
EXPECT_EQ(verifySslName("*bar.example.com", "foobar.example.com"), true);
20+
EXPECT_EQ(verifySslName("baz*.example.com", "baz1.example.com"), true);
21+
EXPECT_EQ(verifySslName("b*z.example.com", "buzz.example.com"), true);
22+
}
23+
24+
TEST(sslNameCheck, rfcCounterExamples)
25+
{
26+
EXPECT_EQ(verifySslName("buz*.example.com", "buaz.example.com"), false);
27+
EXPECT_EQ(verifySslName("*bar.example.com", "aaasdasbaz.example.com"),
28+
false);
29+
EXPECT_EQ(verifySslName("b*z.example.com", "baaaaaa.example.com"), false);
30+
}
31+
32+
TEST(sslNameCheck, wildExamples)
33+
{
34+
EXPECT_EQ(verifySslName("datatracker.ietf.org", "datatracker.ietf.org"),
35+
true);
36+
EXPECT_EQ(verifySslName("*.nsysu.edu.tw", "nsysu.edu.tw"), false);
37+
EXPECT_EQ(verifySslName("nsysu.edu.tw", "nsysu.edu.tw"), true);
38+
}
39+
40+
TEST(sslNameCheck, edgeCase)
41+
{
42+
EXPECT_EQ(verifySslName(".example.com", "example.com"), false);
43+
EXPECT_EQ(verifySslName("example.com.", "example.com."), true);
44+
}
45+
46+
int main(int argc, char **argv)
47+
{
48+
testing::InitGoogleTest(&argc, argv);
49+
return RUN_ALL_TESTS();
50+
}

trantor/utils/Utilities.cc

100755100644
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,118 @@ std::string fromWidePath(const std::wstring &wstrPath)
132132
return toUtf8(srcPath);
133133
}
134134

135+
bool verifySslName(const std::string &certName, const std::string &hostname)
136+
{
137+
if (certName.find('*') == std::string::npos)
138+
{
139+
return certName == hostname;
140+
}
141+
142+
size_t firstDot = certName.find('.');
143+
size_t hostFirstDot = hostname.find('.');
144+
size_t pos, len, hostPos, hostLen;
145+
146+
if (firstDot != std::string::npos)
147+
{
148+
pos = firstDot + 1;
149+
}
150+
else
151+
{
152+
firstDot = pos = certName.size();
153+
}
154+
155+
len = certName.size() - pos;
156+
157+
if (hostFirstDot != std::string::npos)
158+
{
159+
hostPos = hostFirstDot + 1;
160+
}
161+
else
162+
{
163+
hostFirstDot = hostPos = hostname.size();
164+
}
165+
166+
hostLen = hostname.size() - hostPos;
167+
168+
// *. in the beginning of the cert name
169+
if (certName.compare(0, firstDot, "*") == 0)
170+
{
171+
return certName.compare(pos, len, hostname, hostPos, hostLen) == 0;
172+
}
173+
// * in the left most. but other chars in the right
174+
else if (certName[0] == '*')
175+
{
176+
// compare if `hostname` ends with `certName` but without the leftmost
177+
// should be fine as domain names can't be that long
178+
intmax_t hostnameIdx = hostname.size() - 1;
179+
intmax_t certNameIdx = certName.size() - 1;
180+
while (hostnameIdx >= 0 && certNameIdx != 0)
181+
{
182+
if (hostname[hostnameIdx] != certName[certNameIdx])
183+
{
184+
return false;
185+
}
186+
hostnameIdx--;
187+
certNameIdx--;
188+
}
189+
if (certNameIdx != 0)
190+
{
191+
return false;
192+
}
193+
return true;
194+
}
195+
// * in the right of the first dot
196+
else if (firstDot != 0 && certName[firstDot - 1] == '*')
197+
{
198+
if (certName.compare(pos, len, hostname, hostPos, hostLen) != 0)
199+
{
200+
return false;
201+
}
202+
for (size_t i = 0;
203+
i < hostFirstDot && i < firstDot && certName[i] != '*';
204+
i++)
205+
{
206+
if (hostname[i] != certName[i])
207+
{
208+
return false;
209+
}
210+
}
211+
return true;
212+
}
213+
// else there's a * in the middle
214+
else
215+
{
216+
if (certName.compare(pos, len, hostname, hostPos, hostLen) != 0)
217+
{
218+
return false;
219+
}
220+
for (size_t i = 0;
221+
i < hostFirstDot && i < firstDot && certName[i] != '*';
222+
i++)
223+
{
224+
if (hostname[i] != certName[i])
225+
{
226+
return false;
227+
}
228+
}
229+
intmax_t hostnameIdx = hostFirstDot - 1;
230+
intmax_t certNameIdx = firstDot - 1;
231+
while (hostnameIdx >= 0 && certNameIdx >= 0 &&
232+
certName[certNameIdx] != '*')
233+
{
234+
if (hostname[hostnameIdx] != certName[certNameIdx])
235+
{
236+
return false;
237+
}
238+
hostnameIdx--;
239+
certNameIdx--;
240+
}
241+
return true;
242+
}
243+
244+
// should not reach
245+
return certName == hostname;
246+
}
247+
135248
} // namespace utils
136249
} // namespace trantor

trantor/utils/Utilities.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ inline std::string fromNativePath(const std::wstring &strPath)
171171
return fromWidePath(strPath);
172172
}
173173

174+
/**
175+
* @brief Check if the name supplied by the SSL Cert matchs a FQDN
176+
* @param certName The name supplied by the SSL Cert
177+
* @param hostName The FQDN to match
178+
*
179+
* @return true if matches. false otherwise
180+
*/
181+
bool verifySslName(const std::string &certName, const std::string &hostname);
182+
174183
} // namespace utils
175184

176185
} // namespace trantor

0 commit comments

Comments
 (0)