diff --git a/src/main/kotlin/com/coder/gateway/CoderGatewayConnectionProvider.kt b/src/main/kotlin/com/coder/gateway/CoderGatewayConnectionProvider.kt index b19e9f7a..3d6080d9 100644 --- a/src/main/kotlin/com/coder/gateway/CoderGatewayConnectionProvider.kt +++ b/src/main/kotlin/com/coder/gateway/CoderGatewayConnectionProvider.kt @@ -140,7 +140,7 @@ class CoderGatewayConnectionProvider : GatewayConnectionProvider { if (token == null) { // User aborted. throw IllegalArgumentException("Unable to connect to $deploymentURL, $TOKEN is missing") } - val client = CoderRestClient(deploymentURL, token.first,null, settings) + val client = CoderRestClient(deploymentURL, token.first, null, settings) return try { Pair(client, client.me().username) } catch (ex: AuthenticationResponseException) { diff --git a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt index 7d84a639..ea149b99 100644 --- a/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt +++ b/src/main/kotlin/com/coder/gateway/sdk/CoderRestClientService.kt @@ -19,6 +19,7 @@ import com.google.gson.Gson import com.google.gson.GsonBuilder import com.intellij.ide.plugins.PluginManagerCore import com.intellij.openapi.components.Service +import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.util.SystemInfo import okhttp3.OkHttpClient @@ -36,7 +37,6 @@ import java.net.URL import java.nio.file.Path import java.security.KeyFactory import java.security.KeyStore -import java.security.PrivateKey import java.security.cert.CertificateException import java.security.cert.CertificateFactory import java.security.cert.X509Certificate @@ -47,6 +47,7 @@ import java.util.Base64 import java.util.Locale import java.util.UUID import javax.net.ssl.HostnameVerifier +import javax.net.ssl.KeyManager import javax.net.ssl.KeyManagerFactory import javax.net.ssl.SNIHostName import javax.net.ssl.SSLContext @@ -251,53 +252,56 @@ class CoderRestClient( } } -fun coderSocketFactory(settings: CoderSettingsState) : SSLSocketFactory { - if (settings.tlsCertPath.isBlank() || settings.tlsKeyPath.isBlank()) { - return SSLSocketFactory.getDefault() as SSLSocketFactory - } +fun SSLContextFromPEMs(certPath: String, keyPath: String, caPath: String) : SSLContext { + var km: Array? = null + if (certPath.isNotBlank() && keyPath.isNotBlank()) { + val certificateFactory = CertificateFactory.getInstance("X.509") + val certInputStream = FileInputStream(expandPath(certPath)) + val certChain = certificateFactory.generateCertificates(certInputStream) + certInputStream.close() + + // ideally we would use something like PemReader from BouncyCastle, but + // BC is used by the IDE. This makes using BC very impractical since + // type casting will mismatch due to the different class loaders. + val privateKeyPem = File(expandPath(keyPath)).readText() + val start: Int = privateKeyPem.indexOf("-----BEGIN PRIVATE KEY-----") + val end: Int = privateKeyPem.indexOf("-----END PRIVATE KEY-----", start) + val pemBytes: ByteArray = Base64.getDecoder().decode( + privateKeyPem.substring(start + "-----BEGIN PRIVATE KEY-----".length, end) + .replace("\\s+".toRegex(), "") + ) + + val privateKey = try { + val kf = KeyFactory.getInstance("RSA") + val keySpec = PKCS8EncodedKeySpec(pemBytes) + kf.generatePrivate(keySpec) + } catch (e: InvalidKeySpecException) { + val kf = KeyFactory.getInstance("EC") + val keySpec = PKCS8EncodedKeySpec(pemBytes) + kf.generatePrivate(keySpec) + } - val certificateFactory = CertificateFactory.getInstance("X.509") - val certInputStream = FileInputStream(expandPath(settings.tlsCertPath)) - val certChain = certificateFactory.generateCertificates(certInputStream) - certInputStream.close() - - // ideally we would use something like PemReader from BouncyCastle, but - // BC is used by the IDE. This makes using BC very impractical since - // type casting will mismatch due to the different class loaders. - val privateKeyPem = File(expandPath(settings.tlsKeyPath)).readText() - val start: Int = privateKeyPem.indexOf("-----BEGIN PRIVATE KEY-----") - val end: Int = privateKeyPem.indexOf("-----END PRIVATE KEY-----", start) - val pemBytes: ByteArray = Base64.getDecoder().decode( - privateKeyPem.substring(start + "-----BEGIN PRIVATE KEY-----".length, end) - .replace("\\s+".toRegex(), "") - ) - - var privateKey : PrivateKey - try { - val kf = KeyFactory.getInstance("RSA") - val keySpec = PKCS8EncodedKeySpec(pemBytes) - privateKey = kf.generatePrivate(keySpec) - } catch (e: InvalidKeySpecException) { - val kf = KeyFactory.getInstance("EC") - val keySpec = PKCS8EncodedKeySpec(pemBytes) - privateKey = kf.generatePrivate(keySpec) - } - - val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()) - keyStore.load(null) - certChain.withIndex().forEach { - keyStore.setCertificateEntry("cert${it.index}", it.value as X509Certificate) - } - keyStore.setKeyEntry("key", privateKey, null, certChain.toTypedArray()) + val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()) + keyStore.load(null) + certChain.withIndex().forEach { + keyStore.setCertificateEntry("cert${it.index}", it.value as X509Certificate) + } + keyStore.setKeyEntry("key", privateKey, null, certChain.toTypedArray()) - val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) - keyManagerFactory.init(keyStore, null) + val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + keyManagerFactory.init(keyStore, null) + km = keyManagerFactory.keyManagers + } val sslContext = SSLContext.getInstance("TLS") - val trustManagers = coderTrustManagers(settings.tlsCAPath) - sslContext.init(keyManagerFactory.keyManagers, trustManagers, null) + val trustManagers = coderTrustManagers(caPath) + sslContext.init(km, trustManagers, null) + return sslContext +} +fun coderSocketFactory(settings: CoderSettingsState) : SSLSocketFactory { + val sslContext = SSLContextFromPEMs(settings.tlsCertPath, settings.tlsKeyPath, settings.tlsCAPath) if (settings.tlsAlternateHostname.isBlank()) { return sslContext.socketFactory } @@ -393,12 +397,11 @@ class AlternateNameSSLSocketFactory(private val delegate: SSLSocketFactory, priv } class CoderHostnameVerifier(private val alternateName: String) : HostnameVerifier { + val logger = Logger.getInstance(CoderRestClientService::class.java.simpleName) override fun verify(host: String, session: SSLSession): Boolean { if (alternateName.isEmpty()) { - println("using default hostname verifier, alternateName is empty") return OkHostnameVerifier.verify(host, session) } - println("Looking for alternate hostname: $alternateName") val certs = session.peerCertificates ?: return false for (cert in certs) { if (cert !is X509Certificate) { @@ -411,13 +414,12 @@ class CoderHostnameVerifier(private val alternateName: String) : HostnameVerifie continue } val hostname = entry[1] as String - println("Found cert hostname: $hostname") + logger.debug("Found cert hostname: $hostname") if (hostname.lowercase(Locale.getDefault()) == alternateName) { return true } } } - println("No matching hostname found") return false } } diff --git a/src/main/resources/messages/CoderGatewayBundle.properties b/src/main/resources/messages/CoderGatewayBundle.properties index 19941750..1dac9df4 100644 --- a/src/main/resources/messages/CoderGatewayBundle.properties +++ b/src/main/resources/messages/CoderGatewayBundle.properties @@ -93,20 +93,20 @@ gateway.connector.settings.header-command.comment=An external command that \ outputs additional HTTP headers added to all requests. The command must \ output each header as `key=value` on its own line. The following \ environment variables will be available to the process: CODER_URL. -gateway.connector.settings.tls-cert-path.title=Cert Path: +gateway.connector.settings.tls-cert-path.title=Cert path: gateway.connector.settings.tls-cert-path.comment=Optionally set this to \ the path of a certificate to use for TLS connections. The certificate \ should be in X.509 PEM format. -gateway.connector.settings.tls-key-path.title=Key Path: +gateway.connector.settings.tls-key-path.title=Key path: gateway.connector.settings.tls-key-path.comment=Optionally set this to \ the path of the private key that corresponds to the above cert path to use \ for TLS connections. The key should be in X.509 PEM format. -gateway.connector.settings.tls-ca-path.title=CA Path: +gateway.connector.settings.tls-ca-path.title=CA path: gateway.connector.settings.tls-ca-path.comment=Optionally set this to \ the path of a file containing certificates for an alternate certificate \ authority used to verify TLS certs returned by the Coder service. \ The file should be in X.509 PEM format. -gateway.connector.settings.tls-alt-name.title=Alt Hostname: +gateway.connector.settings.tls-alt-name.title=Alt hostname: gateway.connector.settings.tls-alt-name.comment=Optionally set this to \ an alternate hostname used for verifying TLS connections. This is useful \ when the hostname used to connect to the Coder service does not match the \ diff --git a/src/test/fixtures/tls/chain-intermediate.crt b/src/test/fixtures/tls/chain-intermediate.crt new file mode 100644 index 00000000..76beb259 --- /dev/null +++ b/src/test/fixtures/tls/chain-intermediate.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICvjCCAaagAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJVEVT +VC1yb290MCAXDTIzMTAzMDAyMzY0M1oYDzIxMjMxMDA2MDIzNjQzWjAcMRowGAYD +VQQDDBFURVNULWludGVybWVkaWF0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMGD9oILmMRcplGkcNdSZMsBR7C2yoPtL9iRp3V2BKpiRZvQuXHSQsdc +S0Tpk6vnIWQTLkCjVRawL9BoOzwK3FZQti9iXRMnHuzl0gQGZGiHJZ2P/efWaVvn +cmH3Cu2oNCVePhgYAMOiipYGQPcjnQ2kUvMLldZ9+WC+EcaD+FA/kaccPX+kOxQg +qQ0MnPQFfno0F8gylOac+ouKOsXya+jlctgK3dxC73/I+Cdq8xrOJ8lXOYxggleB +ZRXNWWUhrzomn4rUP9wNBrQzFCGcqIS+QjlACjlyn0gPU//ZGVRZ8gZXoI8pDYuB +lRyWpt970/ZPFuiyfiasAAAc8gJ3C7cCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zAN +BgkqhkiG9w0BAQsFAAOCAQEAdHRiLqlYAyGNMPj6wzJt3XmwDbU5yEWor4q+GmA9 +fupirWXeSqKiqngDfvHlQNKgDlm10Kuk7LDVUcAP27Xnv/uFmHIUF+4g/eIjxvog +RorUD2I9hi0Wyww7E8th/JfnuDX4YbIQrv1r5P4JaCoc0C2NBd1hO1Er2GdNEoXm +UYoZg6/P5YQkWSLYtLPswb/Hf63DvzG94H6HnFBYlumt/5xYLrfD1Lx8099wZVdR +qWXSi/tYi0HJGGUynZCvjdUu5En7eDoyWclGHz3stOUkBlz0efz01bxpiGsE/rRG +Xr6qJt45N0Zktytk5TphoeDAeFB5ZHRRatZsg9CyZGoaIA== +-----END CERTIFICATE----- diff --git a/src/test/fixtures/tls/chain-intermediate.key b/src/test/fixtures/tls/chain-intermediate.key new file mode 100644 index 00000000..41a6ed92 --- /dev/null +++ b/src/test/fixtures/tls/chain-intermediate.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAwYP2gguYxFymUaRw11JkywFHsLbKg+0v2JGndXYEqmJFm9C5 +cdJCx1xLROmTq+chZBMuQKNVFrAv0Gg7PArcVlC2L2JdEyce7OXSBAZkaIclnY/9 +59ZpW+dyYfcK7ag0JV4+GBgAw6KKlgZA9yOdDaRS8wuV1n35YL4RxoP4UD+Rpxw9 +f6Q7FCCpDQyc9AV+ejQXyDKU5pz6i4o6xfJr6OVy2Ard3ELvf8j4J2rzGs4nyVc5 +jGCCV4FlFc1ZZSGvOiafitQ/3A0GtDMUIZyohL5COUAKOXKfSA9T/9kZVFnyBleg +jykNi4GVHJam33vT9k8W6LJ+JqwAABzyAncLtwIDAQABAoIBAQC+gC43rzrgc2S3 +km4TSmU3AzeT2x5Z6TDkvd5gX6IQKVXlIgCs8BQVNeJTIK3i2FGits8diqzE/QTU +4QcPAJIP1rzCwM5ngGeNRmEM3U4TKJf7GDkX9ZcahimwDwaPFrre3nu6NEbsUCKl +tdpWcJS3TUDrSkhjMvhAKFxPVLMqKvNK3xg81OmubYDHJ7dmmobJDzklmRlrFCNL +RcQSUYnYruIY4pLmpxVvkFShdxy4oM3f6qanp/nxVvO1of+bqL9fQlgLXSYt83eK +qlUKDdZx/IfckS/DU/8s/PnC5KbrAZB/vNcTIN7USsuAZgP/a8XDLrcH121YZEjW +gIwleYUBAoGBAOtkBKYu+DqlB6xsDJ4XiNji1J19Kmkea+rK5YH21RJlAW3Uh9Y+ +tu9J0iQqqQgyIT+v8U0b6uvKjGUoKYbGha7Cl2X93tFL6QGhfewWF/Yqnzr6cBip +IGfHTTWRkZCeNyDII2VEn4B5E+0emCp0p9B8ffr/bUHgFr/wLLD/sDyXAoGBANJ1 +XYLK+ilWvL3iV9pcvbuHMP7igXP/wOJsoOpMNixBVhzSm4FZWk4duHdRvMQysk8f +KFiEx+0EJwYyBpbnBCRemhFzergV+6a8tJ4x4rBaVOQBTdLJLPypU5tcLP0iWX+b +oyp7mRT+1ffQ2RcFZBRN6bOvcFrkwdiEl6lglu3hAoGBALnTLpxmrg3V5FXowpk3 +aRAXGdPuUMHFg1pKrJ5J1vF7jYI/6rBmuBH1jBCDIQfYU0ksw2ilJnLYZrcg2o+M +P1K0ScL5hKJjs+FWtMrgsi/ie+uac030jiF/Q+OLNIgfbtPRS6gRYX2Rl/p0UZoK +l8RN00KHzJ/ZoPwLRazBXUanAoGBAKzVv9bS1MCwP859HILymMpxyvX3lDJsPb51 +UW0462BKw+plt1lxxOzUEZLD6I8Dx1WdE+gmG331ZAr9eFXjII6xtjtQp96YBxO2 +c2pbM3x6oq6gt4W8uxpAAK5c84Fq/S8D5OrVmDEa2yNqO25hegAGwD9Ve6LZrKwg +r+Bkt25hAoGAdfY0dHAbZpBSSBixb+XsnDxAne8I4OwTOpExdSH1V1Q85CdTlYLq +FoLuy4rqdrF9kFnasn66diaqUtVaubdG8GyJXTGh1rpOGTjhAjAWCl27fOcO/Ffv +7MqsNI8qhOwIJYaBZ8PXtROp5rf3reqjHu9KqfMj+a2sADiF4bW7KYg= +-----END RSA PRIVATE KEY----- diff --git a/src/test/fixtures/tls/chain-leaf.crt b/src/test/fixtures/tls/chain-leaf.crt new file mode 100644 index 00000000..f2b52213 --- /dev/null +++ b/src/test/fixtures/tls/chain-leaf.crt @@ -0,0 +1,74 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4096 (0x1000) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=TEST-intermediate + Validity + Not Before: Oct 30 02:36:43 2023 GMT + Not After : Oct 6 02:36:43 2123 GMT + Subject: CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:ca:ff:7b:41:45:d2:46:14:9c:d7:9d:62:79:04: + 05:5c:36:c1:43:7d:da:2b:25:26:d1:64:15:42:fa: + 10:cc:fd:cf:48:17:87:2f:16:a3:84:11:bd:8a:57: + 73:28:24:af:5e:30:a0:57:bb:b9:9d:90:88:41:d3: + c5:6d:20:25:b3:78:6d:1c:96:69:be:ab:52:64:31: + 27:4c:d2:d2:02:e5:2e:c2:b0:2c:2e:6f:38:bc:a7: + 29:9f:e1:8d:a0:e1:3c:00:9f:37:23:7c:d2:a2:64: + 28:fe:97:c1:34:83:1c:29:59:d9:a8:72:c7:bf:22: + 02:d0:b5:99:7e:42:7b:56:19:12:21:a9:a4:d8:f0: + 70:ef:a1:da:1d:cc:9c:37:7c:45:28:ea:42:f9:20: + 1e:6e:87:04:fc:db:0a:80:99:77:0a:38:de:a5:ba: + b0:75:59:3a:cf:76:27:a1:9d:11:08:db:df:05:d1: + 0e:22:62:de:61:df:15:b2:77:39:3a:c8:dc:77:e4: + 20:c4:20:d7:1a:c0:4b:01:6b:06:4f:4c:b4:23:e9: + dc:18:72:b1:9d:42:14:81:4e:7d:f3:c7:15:72:d5: + b7:81:e7:f8:59:b4:b2:f3:f8:32:c3:aa:8d:d5:d4: + b0:90:bd:da:43:2c:ce:dd:b8:18:83:a2:63:be:66: + 99:df + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Subject Alternative Name: + DNS:localhost + Signature Algorithm: sha256WithRSAEncryption + 52:1f:1f:e7:4a:e7:37:be:49:a4:54:ed:f2:c7:da:87:f0:b3: + 3a:01:21:16:2f:c7:05:2b:c7:dc:2e:98:b1:7e:40:06:32:ca: + cc:d3:95:16:bd:d0:76:a9:9f:d5:cb:64:e0:38:3f:fc:12:62: + 08:4b:b1:b0:b9:ce:e0:b5:75:25:d9:83:44:81:db:9c:4d:2f: + 39:3b:1c:da:18:fb:99:5b:59:fc:12:de:88:5c:0f:47:58:b3: + 5b:70:2f:63:6c:57:19:5b:11:47:2a:98:ba:fe:dd:39:93:34: + 9b:c0:7a:3e:4e:6c:ed:e6:ed:e9:9e:92:ab:35:4d:59:57:f8: + 44:4f:c4:33:a3:20:ec:09:21:cf:2f:e8:35:61:9b:bf:11:9c: + 13:90:81:d4:1c:ec:41:83:86:e3:03:c6:65:c0:db:c8:60:ed: + b1:72:61:66:8f:a9:5e:0f:2d:3d:5c:b6:8a:1f:4e:86:e6:e6: + 3d:08:54:c8:41:79:45:3a:92:73:5b:92:34:ba:99:38:f2:9f: + 4d:71:37:a1:b7:8d:1b:02:f1:77:d4:3e:6d:23:81:a3:fc:f4: + 8b:f2:a6:14:bc:3e:94:2a:7f:bd:d8:fe:41:42:85:31:dd:ed: + b9:03:ad:73:7d:2d:9d:f7:a1:c8:9c:d7:1d:67:83:23:14:da: + 61:3c:82:f7 +-----BEGIN CERTIFICATE----- +MIIC8jCCAdqgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwHDEaMBgGA1UEAwwRVEVT +VC1pbnRlcm1lZGlhdGUwIBcNMjMxMDMwMDIzNjQzWhgPMjEyMzEwMDYwMjM2NDNa +MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMr/e0FF0kYUnNedYnkEBVw2wUN92islJtFkFUL6EMz9z0gXhy8Wo4QR +vYpXcygkr14woFe7uZ2QiEHTxW0gJbN4bRyWab6rUmQxJ0zS0gLlLsKwLC5vOLyn +KZ/hjaDhPACfNyN80qJkKP6XwTSDHClZ2ahyx78iAtC1mX5Ce1YZEiGppNjwcO+h +2h3MnDd8RSjqQvkgHm6HBPzbCoCZdwo43qW6sHVZOs92J6GdEQjb3wXRDiJi3mHf +FbJ3OTrI3HfkIMQg1xrASwFrBk9MtCPp3BhysZ1CFIFOffPHFXLVt4Hn+Fm0svP4 +MsOqjdXUsJC92kMszt24GIOiY75mmd8CAwEAAaNEMEIwCwYDVR0PBAQDAgWgMB0G +A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAUBgNVHREEDTALgglsb2NhbGhv +c3QwDQYJKoZIhvcNAQELBQADggEBAFIfH+dK5ze+SaRU7fLH2ofwszoBIRYvxwUr +x9wumLF+QAYyyszTlRa90Hapn9XLZOA4P/wSYghLsbC5zuC1dSXZg0SB25xNLzk7 +HNoY+5lbWfwS3ohcD0dYs1twL2NsVxlbEUcqmLr+3TmTNJvAej5ObO3m7emekqs1 +TVlX+ERPxDOjIOwJIc8v6DVhm78RnBOQgdQc7EGDhuMDxmXA28hg7bFyYWaPqV4P +LT1ctoofTobm5j0IVMhBeUU6knNbkjS6mTjyn01xN6G3jRsC8XfUPm0jgaP89Ivy +phS8PpQqf73Y/kFChTHd7bkDrXN9LZ33ocic1x1ngyMU2mE8gvc= +-----END CERTIFICATE----- diff --git a/src/test/fixtures/tls/chain-leaf.key b/src/test/fixtures/tls/chain-leaf.key new file mode 100644 index 00000000..38171b63 --- /dev/null +++ b/src/test/fixtures/tls/chain-leaf.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDK/3tBRdJGFJzX +nWJ5BAVcNsFDfdorJSbRZBVC+hDM/c9IF4cvFqOEEb2KV3MoJK9eMKBXu7mdkIhB +08VtICWzeG0clmm+q1JkMSdM0tIC5S7CsCwubzi8pymf4Y2g4TwAnzcjfNKiZCj+ +l8E0gxwpWdmocse/IgLQtZl+QntWGRIhqaTY8HDvododzJw3fEUo6kL5IB5uhwT8 +2wqAmXcKON6lurB1WTrPdiehnREI298F0Q4iYt5h3xWydzk6yNx35CDEINcawEsB +awZPTLQj6dwYcrGdQhSBTn3zxxVy1beB5/hZtLLz+DLDqo3V1LCQvdpDLM7duBiD +omO+ZpnfAgMBAAECggEBALkDnv+/tkU/Ni/h9sUbIBOKqBxuUPCv3LBNSn+P0M40 +qb4oC4KkXIXbcWfsCj3VKaxsH0e3BhaQi0+Lxs2N1i67nJ7IjDpGhUJh9lKzdstC +vJqe3LW5kvmGVY6tkVrGzdw3QJbshkGRjjd0cpf8wycBCDrZ2inewrgcO3hy+Vxe +vhkvUKyB2rjI9xpBGm5YxJiBJGOFZbInp8+Lnx2wuBy+9Dl3/6KzPNc7UKNobwbV +E+kPZoe/zVtsf60mhKQmvyac9eVNXB+U+t/2dnOExG59ROLfR1lsJH5kGdGd/CvR +pLZJwnLX0cTmczz/bcL2iQ/tSClKq2iWEBUUbflIRUECgYEA+P/qOvBQNGXCSMRa +SKEPMUBFbqFMDEsxu0VDZFbRVNmxs5/S5Ta/+aPRzGLtu9oe+Pdhl1xyC1LzSMwm +jJSRrXpnFlLlbahZ8rGV+s52yL/sVy9yR5FDt2B/Y2fVjr0OAJU9aGDjwq92pJ4+ +xDhSuIr1SM/DR6YzdBdfiBWkCv8CgYEA0LR8Z5CpBGu4HMfy1wvo6g5TkuqPjkoA +zyCRpEfGa4goJ4ufvldNkni9dZquWFpdCZX0Ips+S6usbc0/BZHj5z40bEQ1Mg+I +WfqzRlKiBIaz9GcJLHORguW4pikT3H9DtzdZmmww4krkHxJDu5TkTLoOBCbDpPj/ +eiETduu50SECgYBybQCR5z+kZKL816b5u3IE2xlNNriA6clH2xOWN8No78WW2zqK +dTeRnDPcbhX7/se+98gUS7po88yzRoXskpXDl/1pp9yhIP185xkaMekqZfBRPI+S +zfHFgoXoA56DQuP9ZpfasLPaEtI94i7L82ooPktsE3YVJg59KgSPwAortwKBgQDN +3UpdSdc+Uhbg5OYH82qC/TC42YBTJXIY3ZJrzpTNWxfoshQXR7xvv4N6ruJMqo3d +N7oCLMnNEIDcKjmBAAAjCDvjk4A5ahLgVqdhtX61Ij391Wi6HSEqUfjKhfheZnZg +EkvjQ9cQUDkm4PhI3rw3ZssOk0Imx6oRSPEPO8QloQKBgBeYMhqy3ueJ0bi5o5R0 +QcqOYt49wn6bB8fjncBD2eA6ZrLRnBEnyAWoX3d+tIdgZ+0d9fLMQwUYVW2Ql7hh +fPJDcdEx6f2oJYQCSHu9oUXrCFSKdu2CeOGAw6vRknIv71GSDzcNPtiXCWNX4BF+ +d13YhhLzFTqfJtaSb1bFSbcu +-----END PRIVATE KEY----- diff --git a/src/test/fixtures/tls/chain-root.crt b/src/test/fixtures/tls/chain-root.crt new file mode 100644 index 00000000..cafe2217 --- /dev/null +++ b/src/test/fixtures/tls/chain-root.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICvTCCAaWgAwIBAgIJALEhrLJNS0biMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCVRFU1Qtcm9vdDAgFw0yMzEwMzAwMjM2NDNaGA8yMTIzMTAwNjAyMzY0M1ow +FDESMBAGA1UEAwwJVEVTVC1yb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAqLTlMD7rNiC/Hyqz/sh1JBydgNv8CVa/cgCVYQQcGtRl7bs5CfdWti7J +5l7ZEGn+cb+ZyVVyDeF+Tap7zGamQuEkM3C8tettcr7INfKLjNFN94GKtB5LemfK +FFgVA5KWECoovYZPRprgnZuV2QEPdolqwzc3XvaVnmYkxyIhzWD1OFq/vZTFv6eq +fr9JjzWYyv9rCOUmHj/EmVxVVoMYS6Ti3XwOb94Y2CdpuSn3GT4ELN7Tz1B9I0xc +DGKrsjdUIVO5+Bd/5pzQyFMD1UAqsvB9MpHwQswTr/KbrtVC0AQ7fW2q3zOiEg1d +jbNukucc0OwUOIM+UBTtLDBgRzWh5wIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4IBAQAQ+7CNlnwdXEx8Q+JAQYw+DOHQEW8BNhi+iurDzgG3 +RBdHUO7WZi83ijbWuQkUvsfUsRqTkzg3N9fgY28SAhyhn0UmpGKUN6Eqf2d3nYWl +c5X/vGJrajKZUJdBfCegqCgP2zWJycuG6qAs6dnQOj3GfOlUOakGI3czBlIfOXQv +cU23PbQw0zlXFW6FZIqsuGG4aPeaWhuAJNo2XEDEe8Mdvk9w7pO2hqfBcDe03WyJ +ucxx6vsMUGXBqHiOm8Q5TRjv/Zrd/Bhg8aGQlGDsru/dsjIlcxhrjiZzRQv3KjAj ++lNZcvU6Dsb/4QPJthVfb3ZT6r7QLcOk4TIAVyTVFdYR +-----END CERTIFICATE----- diff --git a/src/test/fixtures/tls/chain-root.key b/src/test/fixtures/tls/chain-root.key new file mode 100644 index 00000000..c0ee8d58 --- /dev/null +++ b/src/test/fixtures/tls/chain-root.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAqLTlMD7rNiC/Hyqz/sh1JBydgNv8CVa/cgCVYQQcGtRl7bs5 +CfdWti7J5l7ZEGn+cb+ZyVVyDeF+Tap7zGamQuEkM3C8tettcr7INfKLjNFN94GK +tB5LemfKFFgVA5KWECoovYZPRprgnZuV2QEPdolqwzc3XvaVnmYkxyIhzWD1OFq/ +vZTFv6eqfr9JjzWYyv9rCOUmHj/EmVxVVoMYS6Ti3XwOb94Y2CdpuSn3GT4ELN7T +z1B9I0xcDGKrsjdUIVO5+Bd/5pzQyFMD1UAqsvB9MpHwQswTr/KbrtVC0AQ7fW2q +3zOiEg1djbNukucc0OwUOIM+UBTtLDBgRzWh5wIDAQABAoIBABrimROTM1Cw70Q8 +PesAbwqONNtwMz4ZwPCd/zAyw3fTGVtFVtWrwPnPgwVfYCAphA8EhbF8GGz13nbq +EEiGo0BNOMOp16j2F78NgEJ4oJyUTmR/FGeX3Fdpat7LGq4zEg8JaOyrFr8dt2Xm +gX7PmHM/evAZQI21pipUBNBnNBPSeXoESc+lwGRjh0VlSP/T7u1fgVNGj5mJ6iWT +O8s4EZwY5nW9YOzngXYclAoMqX41h/q1PrFf1tACNpYRNGHaGrDOgvS0ZUN7uNGS +3MaYjmrxS43ltMjC/Y4CrOfO+VENR6cTpdb4u1SS6gx4DumziYRUTUsqTUXwfD9W +97PYAaECgYEA121lNUnT6fd5W90Vk/3FWVh9QSKidnSPnXtRuka0XNYZdtJhNMCJ +K2XvIrXs4mrPfKYgM30vA9io0Z7uPX6Gtp90yJC1tYTHbJtfvi3Af5BYZWAJF2Ze +kMCFdv7FchyCe6CFGGJLR2Y00kWpYTGihJQb4iD8ya7h/sqFPNd/e1MCgYEAyHro +5aMe4mWMPod4h3V46A4+vUsnQrsBV9vskeIfAyCTGuTe4OJnTWTu+9gty/VX7M2B +Hdq+7qg/kUixQehtV4dU/z9lE24Qs8H9NxuNqhGjmfNAUymxsfkgmGuHsuT07+Wm +28M9/ZfKESFbcjoWVfYTtcgeBmtUBHJB9S/9AJ0CgYAwCa7l4R6mL48aUwR6yb32 +HGth2O1NaNSVk2g4F4gko4FuI5+VedGcodBfdx3pp1O5QfowQRv4yZlrlPsfL1Wu +54PNLae3YHJv333MFLu2NmPfxzh/xU4VDTk1vb4dognes366X0DWHQ5uTSZmDAFn +evd0x1JXTu4KOPLZDFzbDQKBgQCH+WUxK1vtLfbbCkMjjPd+XPsMpIZyaifVEWL4 +5yclldhwaz8HxEdQZN76jXsyVKtX/2JNf2n0sMS8o1MmYqCWt0FdBgBmF0bYxQAb +emKxMNmHt0avoR3WmiQTfQtCuKuwclCjyV6oO2VgDQHbDa7MiuR/bMWAkRchFOXL +iMrOuQKBgQCDoqSVLbdZh52BsjPvw3yn+Vmib2fzXiUPQPwJati70STFHHUXxQ/f +qexnmHaXl6jeZ7aNZOyJRZmw4dRgABUaAG8A9Fr262m62hxdMW62AMU32yltsYoU +2wJk3wahmbpHKrDC2PBuOnYuIc12LUzLFuo12bsAJLtz5/Hvvznf8g== +-----END RSA PRIVATE KEY----- diff --git a/src/test/fixtures/tls/chain.crt b/src/test/fixtures/tls/chain.crt new file mode 100644 index 00000000..42f48410 --- /dev/null +++ b/src/test/fixtures/tls/chain.crt @@ -0,0 +1,108 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4096 (0x1000) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=TEST-intermediate + Validity + Not Before: Oct 30 02:36:43 2023 GMT + Not After : Oct 6 02:36:43 2123 GMT + Subject: CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:ca:ff:7b:41:45:d2:46:14:9c:d7:9d:62:79:04: + 05:5c:36:c1:43:7d:da:2b:25:26:d1:64:15:42:fa: + 10:cc:fd:cf:48:17:87:2f:16:a3:84:11:bd:8a:57: + 73:28:24:af:5e:30:a0:57:bb:b9:9d:90:88:41:d3: + c5:6d:20:25:b3:78:6d:1c:96:69:be:ab:52:64:31: + 27:4c:d2:d2:02:e5:2e:c2:b0:2c:2e:6f:38:bc:a7: + 29:9f:e1:8d:a0:e1:3c:00:9f:37:23:7c:d2:a2:64: + 28:fe:97:c1:34:83:1c:29:59:d9:a8:72:c7:bf:22: + 02:d0:b5:99:7e:42:7b:56:19:12:21:a9:a4:d8:f0: + 70:ef:a1:da:1d:cc:9c:37:7c:45:28:ea:42:f9:20: + 1e:6e:87:04:fc:db:0a:80:99:77:0a:38:de:a5:ba: + b0:75:59:3a:cf:76:27:a1:9d:11:08:db:df:05:d1: + 0e:22:62:de:61:df:15:b2:77:39:3a:c8:dc:77:e4: + 20:c4:20:d7:1a:c0:4b:01:6b:06:4f:4c:b4:23:e9: + dc:18:72:b1:9d:42:14:81:4e:7d:f3:c7:15:72:d5: + b7:81:e7:f8:59:b4:b2:f3:f8:32:c3:aa:8d:d5:d4: + b0:90:bd:da:43:2c:ce:dd:b8:18:83:a2:63:be:66: + 99:df + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Subject Alternative Name: + DNS:localhost + Signature Algorithm: sha256WithRSAEncryption + 52:1f:1f:e7:4a:e7:37:be:49:a4:54:ed:f2:c7:da:87:f0:b3: + 3a:01:21:16:2f:c7:05:2b:c7:dc:2e:98:b1:7e:40:06:32:ca: + cc:d3:95:16:bd:d0:76:a9:9f:d5:cb:64:e0:38:3f:fc:12:62: + 08:4b:b1:b0:b9:ce:e0:b5:75:25:d9:83:44:81:db:9c:4d:2f: + 39:3b:1c:da:18:fb:99:5b:59:fc:12:de:88:5c:0f:47:58:b3: + 5b:70:2f:63:6c:57:19:5b:11:47:2a:98:ba:fe:dd:39:93:34: + 9b:c0:7a:3e:4e:6c:ed:e6:ed:e9:9e:92:ab:35:4d:59:57:f8: + 44:4f:c4:33:a3:20:ec:09:21:cf:2f:e8:35:61:9b:bf:11:9c: + 13:90:81:d4:1c:ec:41:83:86:e3:03:c6:65:c0:db:c8:60:ed: + b1:72:61:66:8f:a9:5e:0f:2d:3d:5c:b6:8a:1f:4e:86:e6:e6: + 3d:08:54:c8:41:79:45:3a:92:73:5b:92:34:ba:99:38:f2:9f: + 4d:71:37:a1:b7:8d:1b:02:f1:77:d4:3e:6d:23:81:a3:fc:f4: + 8b:f2:a6:14:bc:3e:94:2a:7f:bd:d8:fe:41:42:85:31:dd:ed: + b9:03:ad:73:7d:2d:9d:f7:a1:c8:9c:d7:1d:67:83:23:14:da: + 61:3c:82:f7 +-----BEGIN CERTIFICATE----- +MIIC8jCCAdqgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwHDEaMBgGA1UEAwwRVEVT +VC1pbnRlcm1lZGlhdGUwIBcNMjMxMDMwMDIzNjQzWhgPMjEyMzEwMDYwMjM2NDNa +MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMr/e0FF0kYUnNedYnkEBVw2wUN92islJtFkFUL6EMz9z0gXhy8Wo4QR +vYpXcygkr14woFe7uZ2QiEHTxW0gJbN4bRyWab6rUmQxJ0zS0gLlLsKwLC5vOLyn +KZ/hjaDhPACfNyN80qJkKP6XwTSDHClZ2ahyx78iAtC1mX5Ce1YZEiGppNjwcO+h +2h3MnDd8RSjqQvkgHm6HBPzbCoCZdwo43qW6sHVZOs92J6GdEQjb3wXRDiJi3mHf +FbJ3OTrI3HfkIMQg1xrASwFrBk9MtCPp3BhysZ1CFIFOffPHFXLVt4Hn+Fm0svP4 +MsOqjdXUsJC92kMszt24GIOiY75mmd8CAwEAAaNEMEIwCwYDVR0PBAQDAgWgMB0G +A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAUBgNVHREEDTALgglsb2NhbGhv +c3QwDQYJKoZIhvcNAQELBQADggEBAFIfH+dK5ze+SaRU7fLH2ofwszoBIRYvxwUr +x9wumLF+QAYyyszTlRa90Hapn9XLZOA4P/wSYghLsbC5zuC1dSXZg0SB25xNLzk7 +HNoY+5lbWfwS3ohcD0dYs1twL2NsVxlbEUcqmLr+3TmTNJvAej5ObO3m7emekqs1 +TVlX+ERPxDOjIOwJIc8v6DVhm78RnBOQgdQc7EGDhuMDxmXA28hg7bFyYWaPqV4P +LT1ctoofTobm5j0IVMhBeUU6knNbkjS6mTjyn01xN6G3jRsC8XfUPm0jgaP89Ivy +phS8PpQqf73Y/kFChTHd7bkDrXN9LZ33ocic1x1ngyMU2mE8gvc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICvjCCAaagAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJVEVT +VC1yb290MCAXDTIzMTAzMDAyMzY0M1oYDzIxMjMxMDA2MDIzNjQzWjAcMRowGAYD +VQQDDBFURVNULWludGVybWVkaWF0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAMGD9oILmMRcplGkcNdSZMsBR7C2yoPtL9iRp3V2BKpiRZvQuXHSQsdc +S0Tpk6vnIWQTLkCjVRawL9BoOzwK3FZQti9iXRMnHuzl0gQGZGiHJZ2P/efWaVvn +cmH3Cu2oNCVePhgYAMOiipYGQPcjnQ2kUvMLldZ9+WC+EcaD+FA/kaccPX+kOxQg +qQ0MnPQFfno0F8gylOac+ouKOsXya+jlctgK3dxC73/I+Cdq8xrOJ8lXOYxggleB +ZRXNWWUhrzomn4rUP9wNBrQzFCGcqIS+QjlACjlyn0gPU//ZGVRZ8gZXoI8pDYuB +lRyWpt970/ZPFuiyfiasAAAc8gJ3C7cCAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zAN +BgkqhkiG9w0BAQsFAAOCAQEAdHRiLqlYAyGNMPj6wzJt3XmwDbU5yEWor4q+GmA9 +fupirWXeSqKiqngDfvHlQNKgDlm10Kuk7LDVUcAP27Xnv/uFmHIUF+4g/eIjxvog +RorUD2I9hi0Wyww7E8th/JfnuDX4YbIQrv1r5P4JaCoc0C2NBd1hO1Er2GdNEoXm +UYoZg6/P5YQkWSLYtLPswb/Hf63DvzG94H6HnFBYlumt/5xYLrfD1Lx8099wZVdR +qWXSi/tYi0HJGGUynZCvjdUu5En7eDoyWclGHz3stOUkBlz0efz01bxpiGsE/rRG +Xr6qJt45N0Zktytk5TphoeDAeFB5ZHRRatZsg9CyZGoaIA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICvTCCAaWgAwIBAgIJALEhrLJNS0biMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCVRFU1Qtcm9vdDAgFw0yMzEwMzAwMjM2NDNaGA8yMTIzMTAwNjAyMzY0M1ow +FDESMBAGA1UEAwwJVEVTVC1yb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAqLTlMD7rNiC/Hyqz/sh1JBydgNv8CVa/cgCVYQQcGtRl7bs5CfdWti7J +5l7ZEGn+cb+ZyVVyDeF+Tap7zGamQuEkM3C8tettcr7INfKLjNFN94GKtB5LemfK +FFgVA5KWECoovYZPRprgnZuV2QEPdolqwzc3XvaVnmYkxyIhzWD1OFq/vZTFv6eq +fr9JjzWYyv9rCOUmHj/EmVxVVoMYS6Ti3XwOb94Y2CdpuSn3GT4ELN7Tz1B9I0xc +DGKrsjdUIVO5+Bd/5pzQyFMD1UAqsvB9MpHwQswTr/KbrtVC0AQ7fW2q3zOiEg1d +jbNukucc0OwUOIM+UBTtLDBgRzWh5wIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4IBAQAQ+7CNlnwdXEx8Q+JAQYw+DOHQEW8BNhi+iurDzgG3 +RBdHUO7WZi83ijbWuQkUvsfUsRqTkzg3N9fgY28SAhyhn0UmpGKUN6Eqf2d3nYWl +c5X/vGJrajKZUJdBfCegqCgP2zWJycuG6qAs6dnQOj3GfOlUOakGI3czBlIfOXQv +cU23PbQw0zlXFW6FZIqsuGG4aPeaWhuAJNo2XEDEe8Mdvk9w7pO2hqfBcDe03WyJ +ucxx6vsMUGXBqHiOm8Q5TRjv/Zrd/Bhg8aGQlGDsru/dsjIlcxhrjiZzRQv3KjAj ++lNZcvU6Dsb/4QPJthVfb3ZT6r7QLcOk4TIAVyTVFdYR +-----END CERTIFICATE----- diff --git a/src/test/fixtures/tls/chain.key b/src/test/fixtures/tls/chain.key new file mode 100644 index 00000000..38171b63 --- /dev/null +++ b/src/test/fixtures/tls/chain.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDK/3tBRdJGFJzX +nWJ5BAVcNsFDfdorJSbRZBVC+hDM/c9IF4cvFqOEEb2KV3MoJK9eMKBXu7mdkIhB +08VtICWzeG0clmm+q1JkMSdM0tIC5S7CsCwubzi8pymf4Y2g4TwAnzcjfNKiZCj+ +l8E0gxwpWdmocse/IgLQtZl+QntWGRIhqaTY8HDvododzJw3fEUo6kL5IB5uhwT8 +2wqAmXcKON6lurB1WTrPdiehnREI298F0Q4iYt5h3xWydzk6yNx35CDEINcawEsB +awZPTLQj6dwYcrGdQhSBTn3zxxVy1beB5/hZtLLz+DLDqo3V1LCQvdpDLM7duBiD +omO+ZpnfAgMBAAECggEBALkDnv+/tkU/Ni/h9sUbIBOKqBxuUPCv3LBNSn+P0M40 +qb4oC4KkXIXbcWfsCj3VKaxsH0e3BhaQi0+Lxs2N1i67nJ7IjDpGhUJh9lKzdstC +vJqe3LW5kvmGVY6tkVrGzdw3QJbshkGRjjd0cpf8wycBCDrZ2inewrgcO3hy+Vxe +vhkvUKyB2rjI9xpBGm5YxJiBJGOFZbInp8+Lnx2wuBy+9Dl3/6KzPNc7UKNobwbV +E+kPZoe/zVtsf60mhKQmvyac9eVNXB+U+t/2dnOExG59ROLfR1lsJH5kGdGd/CvR +pLZJwnLX0cTmczz/bcL2iQ/tSClKq2iWEBUUbflIRUECgYEA+P/qOvBQNGXCSMRa +SKEPMUBFbqFMDEsxu0VDZFbRVNmxs5/S5Ta/+aPRzGLtu9oe+Pdhl1xyC1LzSMwm +jJSRrXpnFlLlbahZ8rGV+s52yL/sVy9yR5FDt2B/Y2fVjr0OAJU9aGDjwq92pJ4+ +xDhSuIr1SM/DR6YzdBdfiBWkCv8CgYEA0LR8Z5CpBGu4HMfy1wvo6g5TkuqPjkoA +zyCRpEfGa4goJ4ufvldNkni9dZquWFpdCZX0Ips+S6usbc0/BZHj5z40bEQ1Mg+I +WfqzRlKiBIaz9GcJLHORguW4pikT3H9DtzdZmmww4krkHxJDu5TkTLoOBCbDpPj/ +eiETduu50SECgYBybQCR5z+kZKL816b5u3IE2xlNNriA6clH2xOWN8No78WW2zqK +dTeRnDPcbhX7/se+98gUS7po88yzRoXskpXDl/1pp9yhIP185xkaMekqZfBRPI+S +zfHFgoXoA56DQuP9ZpfasLPaEtI94i7L82ooPktsE3YVJg59KgSPwAortwKBgQDN +3UpdSdc+Uhbg5OYH82qC/TC42YBTJXIY3ZJrzpTNWxfoshQXR7xvv4N6ruJMqo3d +N7oCLMnNEIDcKjmBAAAjCDvjk4A5ahLgVqdhtX61Ij391Wi6HSEqUfjKhfheZnZg +EkvjQ9cQUDkm4PhI3rw3ZssOk0Imx6oRSPEPO8QloQKBgBeYMhqy3ueJ0bi5o5R0 +QcqOYt49wn6bB8fjncBD2eA6ZrLRnBEnyAWoX3d+tIdgZ+0d9fLMQwUYVW2Ql7hh +fPJDcdEx6f2oJYQCSHu9oUXrCFSKdu2CeOGAw6vRknIv71GSDzcNPtiXCWNX4BF+ +d13YhhLzFTqfJtaSb1bFSbcu +-----END PRIVATE KEY----- diff --git a/src/test/fixtures/tls/generate.bash b/src/test/fixtures/tls/generate.bash new file mode 100755 index 00000000..679535b3 --- /dev/null +++ b/src/test/fixtures/tls/generate.bash @@ -0,0 +1,134 @@ +#!/usr/bin/env bash + +set -xeuo pipefail + +function prepare() { + local cwd=$1 + mkdir -p "$cwd"/{certs,crl,newcerts,private} + echo 1000 > "$cwd/serial" + touch "$cwd"/{index.txt,index.txt.attr} + local fwd=$(readlink -f "$cwd") + + echo ' + [ ca ] + default_ca = CA_default + [ CA_default ] + dir = '"$fwd"' + certs = $dir/certs # Where the issued certs are kept + crl_dir = $dir/crl # Where the issued crl are kept + database = $dir/index.txt # database index file. + new_certs_dir = $dir/newcerts # default place for new certs. + certificate = $dir/cacert.pem # The CA certificate + serial = $dir/serial # The current serial number + crl = $dir/crl.pem # The current CRL + private_key = $dir/private/ca.key.pem # The private key + RANDFILE = $dir/.rnd # private random number file + nameopt = default_ca + certopt = default_ca + policy = policy_match + default_days = 36500 + default_md = sha256 + + [ policy_match ] + countryName = optional + stateOrProvinceName = optional + organizationName = optional + organizationalUnitName = optional + commonName = supplied + emailAddress = optional + + [req] + req_extensions = v3_req + distinguished_name = req_distinguished_name + + [req_distinguished_name] + + [v3_req]' > "$cwd/openssl.cnf" + + if [[ $cwd == out ]] ; then + echo "keyUsage = digitalSignature, keyEncipherment" >> "$cwd/openssl.cnf" + echo "extendedKeyUsage = serverAuth, clientAuth" >> "$cwd/openssl.cnf" + echo "subjectAltName = DNS:localhost" >> "$cwd/openssl.cnf" + else + echo "basicConstraints = CA:TRUE" >> "$cwd/openssl.cnf" + fi +} + +# chain generates three certificates in a chain. +function chain() { + rm -rf {root,intermediate,out} + prepare root + prepare intermediate + prepare out + + # Create root certificate and key. + openssl genrsa -out root/private/ca.key 2048 + openssl req -new -x509 -sha256 -days 36500 \ + -config root/openssl.cnf -extensions v3_req \ + -key root/private/ca.key -out root/certs/ca.crt \ + -subj '/CN=TEST-root' + + # Create intermediate key and request. + openssl genrsa -out intermediate/private/intermediate.key 2048 + openssl req -new -sha256 \ + -config intermediate/openssl.cnf -extensions v3_req \ + -key intermediate/private/intermediate.key -out intermediate/certs/intermediate.csr \ + -subj '/CN=TEST-intermediate' + + # Sign intermediate request with root to create a cert. + openssl ca -batch -notext -md sha256 \ + -config intermediate/openssl.cnf -extensions v3_req \ + -keyfile root/private/ca.key -cert root/certs/ca.crt \ + -in intermediate/certs/intermediate.csr \ + -out intermediate/certs/intermediate.crt + + # Create a key and request for an end certificate. + openssl req -new -days 36500 -nodes -newkey rsa:2048 \ + -config out/openssl.cnf -extensions v3_req \ + -keyout out/private/localhost.key -out out/certs/localhost.csr \ + -subj "/CN=localhost" + + # Sign that with the intermediate. + openssl ca -batch \ + -config out/openssl.cnf -extensions v3_req \ + -keyfile intermediate/private/intermediate.key -cert intermediate/certs/intermediate.crt \ + -out out/certs/localhost.crt \ + -infiles out/certs/localhost.csr + + mv out/certs/localhost.crt chain-leaf.crt + mv out/private/localhost.key chain-leaf.key + mv intermediate/certs/intermediate.crt chain-intermediate.crt + mv intermediate/private/intermediate.key chain-intermediate.key + mv root/certs/ca.crt chain-root.crt + mv root/private/ca.key chain-root.key + + rm -r {out,intermediate,root} + + cat chain-leaf.crt chain-intermediate.crt chain-root.crt > chain.crt + cp chain-leaf.key chain.key +} + +# non-signing generates a self-signed certificate that has cert signing +# explicitly omitted. +function non-signing() { + openssl req -x509 -nodes -newkey rsa:2048 -days 36500 \ + -keyout no-signing.key -out no-signing.crt \ + -addext "keyUsage = digitalSignature, keyEncipherment" \ + -addext "subjectAltName=DNS:localhost" \ + -subj "/CN=localhost" +} + +# self-signed generates a certificate without specifying key usage. +function self-signed() { + openssl req -x509 -nodes -newkey rsa:2048 -days 36500 \ + -keyout self-signed.key -out self-signed.crt \ + -addext "subjectAltName=DNS:localhost" \ + -subj "/CN=localhost" +} + +function main() { + local name=$1 ; shift + "$name" "$@" +} + +main "$@" diff --git a/src/test/fixtures/tls/no-signing.crt b/src/test/fixtures/tls/no-signing.crt new file mode 100644 index 00000000..6353bd3d --- /dev/null +++ b/src/test/fixtures/tls/no-signing.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC0jCCAbqgAwIBAgIJAPFDMRRoqxjwMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAgFw0yMzEwMzAwMjM2NTBaGA8yMTIzMTAwNjAyMzY1MFow +FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEArAfqJMvBtyrlVDnNVd3fai8jQT2zDScPig7BTAhDn5dAin9TMvZ1yKay +rgidszVZSfjAegtMBxxOFPbjD3J3z4By9PXLmkTgU/VlXBzb0I/z8+1+Lq/vaM9A +8kxPruj3Z74cu6uZ3/ZEjB9GHMMIfc8mhUDBwLdwRf4/K5GEm40CbIKvlCPrbinJ +o5KZtb+PcKqg/pjRD0/xpFr36B4f9Nq1nE98zMSWYkBDHZ7wHbRm8a4JSa0zj9WB +Owq5bNBnWdixHwGzNeT6Y+/fDe/UKFBvQ54Fgh+BS3EnJon3FZRKwX1u558RYXue +b5OqaFAeOxBtEts2NObAYncFpRINtQIDAQABoyUwIzALBgNVHQ8EBAMCBaAwFAYD +VR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQATTLQbK9jSxYs9 +e2XxSv0KF4cuxJgdyQvcnbZVkQnBs/G4D0W9rZ1dbuWhskDybw0tMsOSosnhgMsO +uVPTzDAiBa17+vvp9AZ3EvAL4/p45EHYzqJcD8UTx1F7RwOyt1BVuSqP3E2yyGZU +oNZvWSnfn987z66g9FgUv/h3isCLLIeZTk188X+rgLh9P8psU9Uz3LdupcwAhklh +oEHHw34wDIA48IHdcEZxnlkNEyLYJSGxJXu1Fwri7hZktSATsuH3nG1WZInSqlBo +Biv/L7n6GBtcysVM0KJ7S7dQIyTGTd3HpPgV1jCWyai/uuINJ/Om7vKrs94GYWtK +wgQb524q +-----END CERTIFICATE----- diff --git a/src/test/fixtures/tls/no-signing.key b/src/test/fixtures/tls/no-signing.key new file mode 100644 index 00000000..fddc6833 --- /dev/null +++ b/src/test/fixtures/tls/no-signing.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsB+oky8G3KuVU +Oc1V3d9qLyNBPbMNJw+KDsFMCEOfl0CKf1My9nXIprKuCJ2zNVlJ+MB6C0wHHE4U +9uMPcnfPgHL09cuaROBT9WVcHNvQj/Pz7X4ur+9oz0DyTE+u6Pdnvhy7q5nf9kSM +H0Ycwwh9zyaFQMHAt3BF/j8rkYSbjQJsgq+UI+tuKcmjkpm1v49wqqD+mNEPT/Gk +WvfoHh/02rWcT3zMxJZiQEMdnvAdtGbxrglJrTOP1YE7Crls0GdZ2LEfAbM15Ppj +798N79QoUG9DngWCH4FLcScmifcVlErBfW7nnxFhe55vk6poUB47EG0S2zY05sBi +dwWlEg21AgMBAAECggEAWXCP+mt5HpsNuhmHOSJumo1BXhUO90KcoKGFO9t8FQgV +RSxnfDKJEDYi5bqTCu4sqvnKUGl5MKU1r06gxJI12ksk+Vilb2Jp4xzNgvN6EVgW +dHbASNOtvCcs1Ax6zSxQHL7Jv4S7LqaiAtvrnt6Dlq1RkKwXT/PPSoSiISu57wip +LHqEneiM2nSo7GWFuX3avyRB9KejrrKq9GdxZDU0n3RKuVq3Iq96Axcj6/XZICbw +Fa8LLxtQiaKLD2bBsy1+KJfuKHSTYrqgncBuuAqD7qkPlNHg/nPEWKLbuw9WrqE+ +j9VjVYRoct7IhFxUCUVrO1OUVHvO0oOWVkWiEBQRbQKBgQDiHOe3DsdNCoF/bx2Z +YRVi7qF0OTMI0WNQgVRifZZh+e1ZqsaOA9I2e7fTUap7YQWleitjQwHnjvklG48h +oGts4PUfbE71sWa43+TiKIZGfiyNWZ2qjKNLlxsNrS6MWw3+LF48BAX4EUPOAsEj +8mtlXp3W9CWwrGzXeNdPFVwrhwKBgQDCxQKbtrUv4khIRgjmONLdLsnltg0OKlts +Qf/+/ORYNTVVxS5CQSWriE7Jhuyzn4nEHUZe2BcuyNsuvFTWtSWb3zJVLPio+0Fp +KRgj3S0OgELdjLxPFeByKxUKDMZPbt+6cOxa66d6xBnix2lA8XIn5PBkL6xLAn8O +ul2WE3wj4wKBgQDah4se7aaK+8taSR63PQ/5VJ4wAJQlQpEUnlna8nuj53OQRK+v +U1wYEgwArR3yLjvRyTgjsAAoNpLuXStBGZSZXvUo0HmjlTetF55TQU081fbjCaiK +y2+Kv9iCqEyjk+D7NRBCOrU2IiGA+kKGJmXLS92KgN3oWUy8FusoYIF7AwKBgGwQ +dxQCWaFJwaUoBoQF/yjtbuPfEHtNkRANxoWptuAiFYeTMclc8BOuO1ihXe+DkyKW +w5aX+rTgiIvzvnaqZ0WGnxyXKRhI39ADFvu/GeKz02WtUkXm83Mk6DV9RQKJl+SQ +BvOjUHdTGrGyxnlb/WSZJ6/Oq5+qsOhxCr/b68LVAoGBAMWnWTkmM8RAsQJunuLI +xp+pLgG1LsZnDw58t5v3dD2cgU3l98MBhoaoSdGBekgHqdR8ZfbL0lSzvq9/Xcl4 +Mq3Q/GmdgrpEotLFE22KMbEsdrlBXHXoHjNMO43JVugZKMlPgOaOxC0e6fSRDX+C +mbo9OuDh2oeR0ceOcE30ZZie +-----END PRIVATE KEY----- diff --git a/src/test/fixtures/tls/self-signed.crt b/src/test/fixtures/tls/self-signed.crt new file mode 100644 index 00000000..fd317b5f --- /dev/null +++ b/src/test/fixtures/tls/self-signed.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICxTCCAa2gAwIBAgIJAOWWrnoRu7tvMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV +BAMMCWxvY2FsaG9zdDAgFw0yMzEwMzAwMjM2NTNaGA8yMTIzMTAwNjAyMzY1M1ow +FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAw5Ys73Qyq+SV/GCDv0sUjZ2/Ay7zycjRthanuiPx5NhxxdkUExNQZQW/ +kkENASk37XfUfEO4YRyCx6+ffEB5I1USP5afresyXFYhRaaHtk8crsddzMES5Eq4 +TWtEnlC4qPFEj5z5LXhZ9J8De9ekfJ6hKurPw9auUl1NrNTLPqmcyQDITmujNVYI +lSj6INjxAe7qFDnmdp4mVW1IPkZbIRpGYbnOTZL56CcxV8R0ZLxtIxUwuuKtqvz9 +jhTY5KnnmdOD+HEIwiAeQL5W63fZdP6Kb14cKahjy6DQBy1ETexAFDNyl+818kww +uLo6dRXMGceQ4BvB1aF3nDX3iuVN5wIDAQABoxgwFjAUBgNVHREEDTALgglsb2Nh +bGhvc3QwDQYJKoZIhvcNAQELBQADggEBAKQ5YwlN5TEorKYuf2yqp3GJAoDEeUNc +qlaGHMmIlrhKKyh9zfdf0nGx/7eA8pFumz1rl84TpuGWNAg/1DLSk/BpRYckx+dj +cn8xFQzAqsmZpxf6HHGA/mmAHYM9ypaV30SRcBHzaNTa+CfXYVbwanO59wBfeLMs +NNsH6ZblEWIKGZXoQosGrssUWB3Ko93wXi7953nBusI7N4IS+Sdrlj6O8i0lcHaS +GLrT6LtkVWhXja3blBmXcN1DNeZiyl/1JwyTHet9/DW1UEt1LXxP1uE1jMV6hDCp +h/y3s5tuOiV+/BTPQ/p0ngSy5Yy8x8M3KTK+KHef9Y3twg92EI//qdI= +-----END CERTIFICATE----- diff --git a/src/test/fixtures/tls/self-signed.key b/src/test/fixtures/tls/self-signed.key new file mode 100644 index 00000000..8be6be69 --- /dev/null +++ b/src/test/fixtures/tls/self-signed.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDlizvdDKr5JX8 +YIO/SxSNnb8DLvPJyNG2Fqe6I/Hk2HHF2RQTE1BlBb+SQQ0BKTftd9R8Q7hhHILH +r598QHkjVRI/lp+t6zJcViFFpoe2Txyux13MwRLkSrhNa0SeULio8USPnPkteFn0 +nwN716R8nqEq6s/D1q5SXU2s1Ms+qZzJAMhOa6M1VgiVKPog2PEB7uoUOeZ2niZV +bUg+RlshGkZhuc5NkvnoJzFXxHRkvG0jFTC64q2q/P2OFNjkqeeZ04P4cQjCIB5A +vlbrd9l0/opvXhwpqGPLoNAHLURN7EAUM3KX7zXyTDC4ujp1FcwZx5DgG8HVoXec +NfeK5U3nAgMBAAECggEAWU0hOTfBxxA4lyHuJZJ/UOW8iBSRBQnnDo+rh2bQFF/r +Gp2x97+yzl1gicOfz27ldUxoPVCiR9y/rbL3S8EYTlSSX2xDfiJMPTKqQGX3wvq+ +KuMmZc2l9YxUOC0JCIvstF5sonHWp7cyw2kzKwFbvfajube6oz1LHJozU/1Yy0PT +kmZlPi7Kd/nZujEey82mtj0maxxknk+vD52HIaRtyn9ua0Z04PijsPlOsT/EaxHY +t4FeewwSxm7EOrIo04DhD9uxWJ7etrzeSL6AANatlP7ttvQqwkOoJgV5tDNu038U +eqkf8LrBzBbUmqAkG1ssHLGKrwdzAE2k509L9WTOqQKBgQDri4HKriV+Z4a9B1YX ++o2GvaioMtMiY0zPYwdWOqwctmBVUi8jz7huDfmdmY9ahuhIe9gm77eFdhjMzOW0 +k/hEDVbVv9lAD7NMQmJisw33Lk9a+W6ZcMxPV/hanZ84ig5fvfCu9Nz37udiT4Y4 +/7RAt+m4jASPvyqSK0VqvUw2/QKBgQDUklbTobplY8xjjR1vLymgN6GC0ktdmkq/ +tmC4Ylj697UDtrjAhJCuy53vxqxY+MRcSQs/H657eI8gyVeysfy5TG+HgXTbwMI+ +o/uh0kq0blwBOGgtSzhWLSGljkOzQt7tx4/ZzhTQGJ9lQzPlkpPuXkAq81rTMc9o +VDfDEDy3swKBgDT6B5MiX+RyPGe/gqmZ/MLVXV2XMM2HL/tk9n16bMN4cWo/NcME +MSLvmbjMlOVzekLzN8ZqHAi0axeE7hUTQr9rkKA6qg4yec0pER/JzdZOYCLB/xIb +wJgH3R/kW69Hvbvi6IMxJ5HL9daytCmVuWDk/Hg5Zb0+7cA6Yz6CnOWxAoGBAJI2 +Tg6nUWRn7rAS4koVsJYJbchkCX7Kn9uaAJES5I1LUHDLf+y7wiDY4TuJ9gYEplur +ylaS3hsDY79zfiTllCWIU7Zq7wwwW+tmM7CsysGsnxAf0lhFQuzTgi8z2ZE1z8zR +1TpFK7+vEARA4zNnTOVKYuyoErLtsfHa67f6NSlNAoGAZ/uRyaQrjt1lC4i8PFru +2eCnE2/kJEcxZ+jxPPIaTsTPGDkenPiMqkPs2LUwZfH4cpZnNmf4sIAgLkYAYh4f +tcBrjjiqyfBzm233XobIsavnmTxO6CIUkLuzJleACDWq6yEKvcf/Uot3uoh0oraM +G4WEbFBcic/oFPDonmBlVD4= +-----END PRIVATE KEY----- diff --git a/src/test/groovy/CoderRestClientTest.groovy b/src/test/groovy/CoderRestClientTest.groovy index 493640df..6ba4bd7a 100644 --- a/src/test/groovy/CoderRestClientTest.groovy +++ b/src/test/groovy/CoderRestClientTest.groovy @@ -1,6 +1,9 @@ package com.coder.gateway.sdk import com.coder.gateway.sdk.convertors.InstantConverter +import com.coder.gateway.sdk.v2.models.Role +import com.coder.gateway.sdk.v2.models.User +import com.coder.gateway.sdk.v2.models.UserStatus import com.coder.gateway.sdk.v2.models.Workspace import com.coder.gateway.sdk.v2.models.WorkspaceResource import com.coder.gateway.sdk.v2.models.WorkspacesResponse @@ -9,12 +12,15 @@ import com.google.gson.GsonBuilder import com.sun.net.httpserver.HttpExchange import com.sun.net.httpserver.HttpHandler import com.sun.net.httpserver.HttpServer +import com.sun.net.httpserver.HttpsConfigurator +import com.sun.net.httpserver.HttpsServer import spock.lang.IgnoreIf import spock.lang.Requires import spock.lang.Specification import spock.lang.Unroll import javax.net.ssl.HttpsURLConnection +import java.nio.file.Path import java.time.Instant @Unroll @@ -28,6 +34,12 @@ class CoderRestClientTest extends Specification { */ def mockServer(List workspaces, List> resources = []) { HttpServer srv = HttpServer.create(new InetSocketAddress(0), 0) + addServerContext(srv, workspaces, resources) + srv.start() + return [srv, "http://localhost:" + srv.address.port] + } + + def addServerContext(HttpServer srv, List workspaces, List> resources = []) { srv.createContext("/", new HttpHandler() { void handle(HttpExchange exchange) { int code = HttpURLConnection.HTTP_NOT_FOUND @@ -44,6 +56,21 @@ class CoderRestClientTest extends Specification { code = HttpsURLConnection.HTTP_OK response = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantConverter()) .create().toJson(new WorkspacesResponse(workspaces, workspaces.size())) + } else if (exchange.requestURI.path == "/api/v2/users/me") { + code = HttpsURLConnection.HTTP_OK + def user = new User( + UUID.randomUUID(), + "tester", + "tester@example.com", + Instant.now(), + Instant.now(), + UserStatus.ACTIVE, + List.of(), + List.of(), + "" + ) + response = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantConverter()) + .create().toJson(user) } } catch (error) { // This will be a developer error. @@ -58,8 +85,18 @@ class CoderRestClientTest extends Specification { exchange.close() } }) + } + + def mockTLSServer(String certName, List workspaces, List> resources = []) { + HttpsServer srv = HttpsServer.create(new InetSocketAddress(0), 0) + def sslContext = CoderRestClientServiceKt.SSLContextFromPEMs( + Path.of("src/test/fixtures/tls", certName + ".crt").toString(), + Path.of("src/test/fixtures/tls", certName + ".key").toString(), + "") + srv.setHttpsConfigurator(new HttpsConfigurator(sslContext)) + addServerContext(srv, workspaces, resources) srv.start() - return [srv, "http://localhost:" + srv.address.port] + return [srv, "https://localhost:" + srv.address.port] } def "gets workspaces"() { @@ -177,4 +214,68 @@ class CoderRestClientTest extends Specification { ] } + + def "valid self-signed cert"() { + given: + def settings = new CoderSettingsState() + settings.tlsCAPath = Path.of("src/test/fixtures/tls", "self-signed.crt").toString() + settings.tlsAlternateHostname = "localhost" + def (srv, url) = mockTLSServer("self-signed", null) + def client = new CoderRestClient(new URL(url), "token", "test", settings) + + expect: + client.me().username == "tester" + + cleanup: + srv.stop(0) + } + + def "wrong hostname for cert"() { + given: + def settings = new CoderSettingsState() + settings.tlsCAPath = Path.of("src/test/fixtures/tls", "self-signed.crt").toString() + settings.tlsAlternateHostname = "fake.example.com" + def (srv, url) = mockTLSServer("self-signed", null) + def client = new CoderRestClient(new URL(url), "token", "test", settings) + + when: + client.me() + + then: + thrown(javax.net.ssl.SSLPeerUnverifiedException) + + cleanup: + srv.stop(0) + } + + def "server cert not trusted"() { + given: + def settings = new CoderSettingsState() + settings.tlsCAPath = Path.of("src/test/fixtures/tls", "self-signed.crt").toString() + def (srv, url) = mockTLSServer("no-signing", null) + def client = new CoderRestClient(new URL(url), "token", "test", settings) + + when: + client.me() + + then: + thrown(javax.net.ssl.SSLHandshakeException) + + cleanup: + srv.stop(0) + } + + def "server using valid chain cert"() { + given: + def settings = new CoderSettingsState() + settings.tlsCAPath = Path.of("src/test/fixtures/tls", "chain-root.crt").toString() + def (srv, url) = mockTLSServer("chain", null) + def client = new CoderRestClient(new URL(url), "token", "test", settings) + + expect: + client.me().username == "tester" + + cleanup: + srv.stop(0) + } }