Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc00704d

Browse files
authored
fix: relaxed SNI hostname resolution (#197)
When establishing TLS connections, SNI resolution may fail if theconfigured altHostname contains `_` or any other characters not allowedby domain name standards (i.e. letters, digits and hyphens).This change introduces a relaxed SNI resolution strategy which ignoresthe LDH rules completely. Because this change goes hand in hand withauth. via certificates, I was able to reproduce the issue only via UTs.At this point the official Coder releases supports only auth. via APIkeys.
1 parent7005d1e commitc00704d

File tree

5 files changed

+497
-6
lines changed

5 files changed

+497
-6
lines changed

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
##Unreleased
44

5+
###Fixed
6+
7+
- relaxed SNI hostname resolution
8+
59
##0.6.5 - 2025-09-16
610

711
###Fixed

‎gradle.properties‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
version=0.6.5
1+
version=0.6.6
22
group=com.coder.toolbox
33
name=coder-toolbox

‎src/main/kotlin/com/coder/toolbox/util/TLS.kt‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import com.coder.toolbox.settings.ReadOnlyTLSSettings
44
importokhttp3.internal.tls.OkHostnameVerifier
55
importjava.io.File
66
importjava.io.FileInputStream
7+
importjava.net.IDN
78
importjava.net.InetAddress
89
importjava.net.Socket
10+
importjava.nio.charset.StandardCharsets
911
importjava.security.KeyFactory
1012
importjava.security.KeyStore
1113
importjava.security.cert.CertificateException
@@ -18,11 +20,12 @@ import java.util.Locale
1820
importjavax.net.ssl.HostnameVerifier
1921
importjavax.net.ssl.KeyManager
2022
importjavax.net.ssl.KeyManagerFactory
21-
importjavax.net.ssl.SNIHostName
23+
importjavax.net.ssl.SNIServerName
2224
importjavax.net.ssl.SSLContext
2325
importjavax.net.ssl.SSLSession
2426
importjavax.net.ssl.SSLSocket
2527
importjavax.net.ssl.SSLSocketFactory
28+
importjavax.net.ssl.StandardConstants
2629
importjavax.net.ssl.TrustManager
2730
importjavax.net.ssl.TrustManagerFactory
2831
importjavax.net.ssl.X509TrustManager
@@ -83,11 +86,13 @@ fun sslContextFromPEMs(
8386

8487
funcoderSocketFactory(settings:ReadOnlyTLSSettings):SSLSocketFactory {
8588
val sslContext= sslContextFromPEMs(settings.certPath, settings.keyPath, settings.caPath)
86-
if (settings.altHostname.isNullOrBlank()) {
89+
90+
val altHostname= settings.altHostname
91+
if (altHostname.isNullOrBlank()) {
8792
return sslContext.socketFactory
8893
}
8994

90-
returnAlternateNameSSLSocketFactory(sslContext.socketFactory,settings.altHostname)
95+
returnAlternateNameSSLSocketFactory(sslContext.socketFactory, altHostname)
9196
}
9297

9398
funcoderTrustManagers(tlsCAPath:String?):Array<TrustManager> {
@@ -111,7 +116,7 @@ fun coderTrustManagers(tlsCAPath: String?): Array<TrustManager> {
111116
return trustManagerFactory.trustManagers.map {MergedSystemTrustManger(itasX509TrustManager) }.toTypedArray()
112117
}
113118

114-
classAlternateNameSSLSocketFactory(privatevaldelegate:SSLSocketFactory, privatevalalternateName:String?) :
119+
classAlternateNameSSLSocketFactory(privatevaldelegate:SSLSocketFactory, privatevalalternateName:String) :
115120
SSLSocketFactory() {
116121
overridefungetDefaultCipherSuites():Array<String>= delegate.defaultCipherSuites
117122

@@ -176,12 +181,19 @@ class AlternateNameSSLSocketFactory(private val delegate: SSLSocketFactory, priv
176181

177182
privatefuncustomizeSocket(socket:SSLSocket) {
178183
val params= socket.sslParameters
179-
params.serverNames=listOf(SNIHostName(alternateName))
184+
185+
params.serverNames=listOf(RelaxedSNIHostname(alternateName))
180186
socket.sslParameters= params
181187
}
182188
}
183189

190+
privateclassRelaxedSNIHostname(hostname:String) : SNIServerName(
191+
StandardConstants.SNI_HOST_NAME,
192+
IDN.toASCII(hostname, 0).toByteArray(StandardCharsets.UTF_8)
193+
)
194+
184195
classCoderHostnameVerifier(privatevalalternateName:String?) : HostnameVerifier {
196+
185197
overridefunverify(
186198
host:String,
187199
session:SSLSession,
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
packagecom.coder.toolbox.util
2+
3+
importio.mockk.Runs
4+
importio.mockk.every
5+
importio.mockk.just
6+
importio.mockk.mockk
7+
importio.mockk.verify
8+
importjava.net.InetAddress
9+
importjava.net.Socket
10+
importjavax.net.ssl.SSLParameters
11+
importjavax.net.ssl.SSLSocket
12+
importjavax.net.ssl.SSLSocketFactory
13+
importkotlin.test.Test
14+
importkotlin.test.assertEquals
15+
importkotlin.test.assertNotNull
16+
importkotlin.test.assertSame
17+
18+
19+
classAlternateNameSSLSocketFactoryTest {
20+
21+
@Test
22+
fun`createSocket with no parameters should customize socket with alternate name`() {
23+
// Given
24+
val mockFactory= mockk<SSLSocketFactory>()
25+
val mockSocket= mockk<SSLSocket>(relaxed=true)
26+
val mockParams= mockk<SSLParameters>(relaxed=true)
27+
28+
every { mockFactory.createSocket() } returns mockSocket
29+
every { mockSocket.sslParameters } returns mockParams
30+
every { mockSocket.sslParameters= any() } justRuns
31+
32+
val alternateFactory=AlternateNameSSLSocketFactory(mockFactory,"alternate.example.com")
33+
34+
// When
35+
val result= alternateFactory.createSocket()
36+
37+
// Then
38+
verify { mockSocket.sslParameters= any() }
39+
assertSame(mockSocket, result)
40+
}
41+
42+
@Test
43+
fun`createSocket with host and port should customize socket with alternate name`() {
44+
// Given
45+
val mockFactory= mockk<SSLSocketFactory>()
46+
val mockSocket= mockk<SSLSocket>(relaxed=true)
47+
val mockParams= mockk<SSLParameters>(relaxed=true)
48+
49+
every { mockFactory.createSocket("original.com",443) } returns mockSocket
50+
every { mockSocket.sslParameters } returns mockParams
51+
every { mockSocket.sslParameters= any() } justRuns
52+
53+
val alternateFactory=AlternateNameSSLSocketFactory(mockFactory,"alternate.example.com")
54+
55+
// When
56+
val result= alternateFactory.createSocket("original.com",443)
57+
58+
// Then
59+
verify { mockSocket.sslParameters= any() }
60+
assertSame(mockSocket, result)
61+
}
62+
63+
@Test
64+
fun`createSocket with host port and local address should customize socket`() {
65+
// Given
66+
val mockFactory= mockk<SSLSocketFactory>()
67+
val mockSocket= mockk<SSLSocket>(relaxed=true)
68+
val mockParams= mockk<SSLParameters>(relaxed=true)
69+
val localHost= mockk<InetAddress>()
70+
71+
every { mockFactory.createSocket("original.com",443, localHost,8080) } returns mockSocket
72+
every { mockSocket.sslParameters } returns mockParams
73+
every { mockSocket.sslParameters= any() } justRuns
74+
75+
val alternateFactory=AlternateNameSSLSocketFactory(mockFactory,"alternate.example.com")
76+
77+
// When
78+
val result= alternateFactory.createSocket("original.com",443, localHost,8080)
79+
80+
// Then
81+
verify { mockSocket.sslParameters= any() }
82+
assertSame(mockSocket, result)
83+
}
84+
85+
@Test
86+
fun`createSocket with InetAddress should customize socket with alternate name`() {
87+
// Given
88+
val mockFactory= mockk<SSLSocketFactory>()
89+
val mockSocket= mockk<SSLSocket>(relaxed=true)
90+
val mockParams= mockk<SSLParameters>(relaxed=true)
91+
val address= mockk<InetAddress>()
92+
93+
every { mockFactory.createSocket(address,443) } returns mockSocket
94+
every { mockSocket.sslParameters } returns mockParams
95+
every { mockSocket.sslParameters= any() } justRuns
96+
97+
val alternateFactory=AlternateNameSSLSocketFactory(mockFactory,"alternate.example.com")
98+
99+
// When
100+
val result= alternateFactory.createSocket(address,443)
101+
102+
// Then
103+
verify { mockSocket.sslParameters= any() }
104+
assertSame(mockSocket, result)
105+
}
106+
107+
@Test
108+
fun`createSocket with InetAddress and local address should customize socket`() {
109+
// Given
110+
val mockFactory= mockk<SSLSocketFactory>()
111+
val mockSocket= mockk<SSLSocket>(relaxed=true)
112+
val mockParams= mockk<SSLParameters>(relaxed=true)
113+
val address= mockk<InetAddress>()
114+
val localAddress= mockk<InetAddress>()
115+
116+
every { mockFactory.createSocket(address,443, localAddress,8080) } returns mockSocket
117+
every { mockSocket.sslParameters } returns mockParams
118+
every { mockSocket.sslParameters= any() } justRuns
119+
120+
val alternateFactory=AlternateNameSSLSocketFactory(mockFactory,"alternate.example.com")
121+
122+
// When
123+
val result= alternateFactory.createSocket(address,443, localAddress,8080)
124+
125+
// Then
126+
verify { mockSocket.sslParameters= any() }
127+
assertSame(mockSocket, result)
128+
}
129+
130+
@Test
131+
fun`createSocket with existing socket should customize socket with alternate name`() {
132+
// Given
133+
val mockFactory= mockk<SSLSocketFactory>()
134+
val mockSSLSocket= mockk<SSLSocket>(relaxed=true)
135+
val mockParams= mockk<SSLParameters>(relaxed=true)
136+
val existingSocket= mockk<Socket>()
137+
138+
every { mockFactory.createSocket(existingSocket,"original.com",443,true) } returns mockSSLSocket
139+
every { mockSSLSocket.sslParameters } returns mockParams
140+
every { mockSSLSocket.sslParameters= any() } justRuns
141+
142+
val alternateFactory=AlternateNameSSLSocketFactory(mockFactory,"alternate.example.com")
143+
144+
// When
145+
val result= alternateFactory.createSocket(existingSocket,"original.com",443,true)
146+
147+
// Then
148+
verify { mockSSLSocket.sslParameters= any() }
149+
assertSame(mockSSLSocket, result)
150+
}
151+
152+
@Test
153+
fun`customizeSocket should set SNI hostname to alternate name for valid hostname`() {
154+
// Given
155+
val mockFactory= mockk<SSLSocketFactory>()
156+
val mockSocket= mockk<SSLSocket>(relaxed=true)
157+
val mockParams= mockk<SSLParameters>(relaxed=true)
158+
159+
every { mockFactory.createSocket() } returns mockSocket
160+
every { mockSocket.sslParameters } returns mockParams
161+
every { mockSocket.sslParameters= any() } justRuns
162+
163+
val alternateFactory=AlternateNameSSLSocketFactory(mockFactory,"valid-hostname.example.com")
164+
165+
// When & Then - This should work without throwing an exception
166+
assertNotNull(alternateFactory.createSocket())
167+
verify { mockSocket.sslParameters= any() }
168+
}
169+
170+
@Test
171+
fun`customizeSocket should NOT throw IllegalArgumentException for hostname with underscore`() {
172+
// Given
173+
val mockFactory= mockk<SSLSocketFactory>()
174+
val mockSocket= mockk<SSLSocket>(relaxed=true)
175+
val mockParams= mockk<SSLParameters>(relaxed=true)
176+
177+
every { mockFactory.createSocket() } returns mockSocket
178+
every { mockSocket.sslParameters } returns mockParams
179+
every { mockSocket.sslParameters= any() } justRuns
180+
181+
val alternateFactory=AlternateNameSSLSocketFactory(mockFactory,"non_compliant_hostname.example.com")
182+
183+
// When & Then - This should work without throwing an exception
184+
assertNotNull(alternateFactory.createSocket())
185+
verify { mockSocket.sslParameters= any() }
186+
assertEquals(0, mockSocket.sslParameters.serverNames.size)
187+
}
188+
189+
@Test
190+
fun`createSocket should work with valid international domain names`() {
191+
// Given
192+
val mockFactory= mockk<SSLSocketFactory>()
193+
val mockSocket= mockk<SSLSocket>(relaxed=true)
194+
val mockParams= mockk<SSLParameters>(relaxed=true)
195+
196+
every { mockFactory.createSocket() } returns mockSocket
197+
every { mockSocket.sslParameters } returns mockParams
198+
every { mockSocket.sslParameters= any() } justRuns
199+
200+
val alternateFactory=AlternateNameSSLSocketFactory(mockFactory,"test-server.example.com")
201+
202+
// When & Then - This should work as hyphens are valid
203+
assertNotNull(alternateFactory.createSocket())
204+
verify { mockSocket.sslParameters= any() }
205+
}
206+
207+
privatefuncreateMockSSLSocketFactory():SSLSocketFactory {
208+
val mockFactory= mockk<SSLSocketFactory>()
209+
val mockSocket= mockk<SSLSocket>(relaxed=true)
210+
val mockParams= mockk<SSLParameters>(relaxed=true)
211+
212+
// Setup default behavior
213+
every { mockFactory.defaultCipherSuites } returns arrayOf("TLS_AES_256_GCM_SHA384")
214+
every { mockFactory.supportedCipherSuites } returns arrayOf("TLS_AES_256_GCM_SHA384","TLS_AES_128_GCM_SHA256")
215+
216+
// Make all createSocket methods return our mock socket
217+
every { mockFactory.createSocket() } returns mockSocket
218+
every { mockFactory.createSocket(any<String>(), any<Int>()) } returns mockSocket
219+
every { mockFactory.createSocket(any<String>(), any<Int>(), any<InetAddress>(), any<Int>()) } returns mockSocket
220+
every { mockFactory.createSocket(any<InetAddress>(), any<Int>()) } returns mockSocket
221+
every {
222+
mockFactory.createSocket(
223+
any<InetAddress>(),
224+
any<Int>(),
225+
any<InetAddress>(),
226+
any<Int>()
227+
)
228+
} returns mockSocket
229+
every { mockFactory.createSocket(any<Socket>(), any<String>(), any<Int>(), any<Boolean>()) } returns mockSocket
230+
231+
// Setup SSL parameters
232+
every { mockSocket.sslParameters } returns mockParams
233+
every { mockSocket.sslParameters= any() } justRuns
234+
235+
return mockFactory
236+
}
237+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp