made TLS client certificate request from the server configurable
All checks were successful
CI / build (push) Successful in 4m2s

This commit is contained in:
2025-01-27 13:32:04 +08:00
parent 90a5834f5f
commit 45458761f3
10 changed files with 82 additions and 23 deletions

View File

@@ -91,11 +91,14 @@ public class Configuration {
boolean verifyClients; boolean verifyClients;
} }
public enum ClientCertificate {
REQUIRED, OPTIONAL
}
@Value @Value
public static class Tls { public static class Tls {
KeyStore keyStore; KeyStore keyStore;
TrustStore trustStore; TrustStore trustStore;
boolean verifyClients;
} }
@Value @Value
@@ -111,6 +114,7 @@ public class Configuration {
Path file; Path file;
String password; String password;
boolean checkCertificateStatus; boolean checkCertificateStatus;
boolean requireClientCertificate;
} }
@Value @Value

View File

@@ -208,15 +208,15 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
.map { it as X509Certificate } .map { it as X509Certificate }
.toArray { size -> Array<X509Certificate?>(size) { null } } .toArray { size -> Array<X509Certificate?>(size) { null } }
SslContextBuilder.forServer(serverKey, *serverCert).apply { SslContextBuilder.forServer(serverKey, *serverCert).apply {
if (tls.isVerifyClients) { val clientAuth = tls.trustStore?.let { trustStore ->
clientAuth(ClientAuth.OPTIONAL) val ts = loadKeystore(trustStore.file, trustStore.password)
tls.trustStore?.let { trustStore -> trustManager(
val ts = loadKeystore(trustStore.file, trustStore.password) ClientCertificateValidator.getTrustManager(ts, trustStore.isCheckCertificateStatus)
trustManager( )
ClientCertificateValidator.getTrustManager(ts, trustStore.isCheckCertificateStatus) if(trustStore.isRequireClientCertificate) ClientAuth.REQUIRE
) else ClientAuth.OPTIONAL
} } ?: ClientAuth.NONE
} clientAuth(clientAuth)
}.build() }.build()
} }
} }

View File

@@ -142,10 +142,9 @@ object Parser {
} }
"tls" -> { "tls" -> {
val verifyClients = child.renderAttribute("verify-clients")
?.let(String::toBoolean) ?: false
var keyStore: KeyStore? = null var keyStore: KeyStore? = null
var trustStore: TrustStore? = null var trustStore: TrustStore? = null
for (granChild in child.asIterable()) { for (granChild in child.asIterable()) {
when (granChild.localName) { when (granChild.localName) {
"keystore" -> { "keystore" -> {
@@ -167,15 +166,19 @@ object Parser {
val checkCertificateStatus = granChild.renderAttribute("check-certificate-status") val checkCertificateStatus = granChild.renderAttribute("check-certificate-status")
?.let(String::toBoolean) ?.let(String::toBoolean)
?: false ?: false
val requireClientCertificate = child.renderAttribute("require-client-certificate")
?.let(String::toBoolean) ?: false
trustStore = TrustStore( trustStore = TrustStore(
trustStoreFile, trustStoreFile,
trustStorePassword, trustStorePassword,
checkCertificateStatus checkCertificateStatus,
requireClientCertificate
) )
} }
} }
} }
tls = Tls(keyStore, trustStore, verifyClients) tls = Tls(keyStore, trustStore)
} }
} }
} }

View File

@@ -154,9 +154,6 @@ object Serializer {
conf.tls?.let { tlsConfiguration -> conf.tls?.let { tlsConfiguration ->
node("tls") { node("tls") {
if(tlsConfiguration.isVerifyClients) {
attr("verify-clients", "true")
}
tlsConfiguration.keyStore?.let { keyStore -> tlsConfiguration.keyStore?.let { keyStore ->
node("keystore") { node("keystore") {
attr("file", keyStore.file.toString()) attr("file", keyStore.file.toString())
@@ -177,6 +174,7 @@ object Serializer {
attr("password", password) attr("password", password)
} }
attr("check-certificate-status", trustStore.isCheckCertificateStatus.toString()) attr("check-certificate-status", trustStore.isCheckCertificateStatus.toString())
attr("require-client-certificate", trustStore.isRequireClientCertificate.toString())
} }
} }
} }

View File

@@ -183,7 +183,6 @@
<xs:element name="keystore" type="gbcs:keyStoreType" /> <xs:element name="keystore" type="gbcs:keyStoreType" />
<xs:element name="truststore" type="gbcs:trustStoreType" minOccurs="0"/> <xs:element name="truststore" type="gbcs:trustStoreType" minOccurs="0"/>
</xs:all> </xs:all>
<xs:attribute name="verify-clients" type="xs:boolean" use="optional" default="false"/>
</xs:complexType> </xs:complexType>
<xs:complexType name="keyStoreType"> <xs:complexType name="keyStoreType">
@@ -197,6 +196,7 @@
<xs:attribute name="file" type="xs:string" use="required"/> <xs:attribute name="file" type="xs:string" use="required"/>
<xs:attribute name="password" type="xs:string"/> <xs:attribute name="password" type="xs:string"/>
<xs:attribute name="check-certificate-status" type="xs:boolean"/> <xs:attribute name="check-certificate-status" type="xs:boolean"/>
<xs:attribute name="require-client-certificate" type="xs:boolean" use="optional" default="false"/>
</xs:complexType> </xs:complexType>
<xs:complexType name="propertiesType"> <xs:complexType name="propertiesType">

View File

@@ -171,9 +171,8 @@ abstract class AbstractTlsServerTest : AbstractServerTest() {
), ),
Configuration.Tls( Configuration.Tls(
Configuration.KeyStore(this.serverKeyStoreFile, null, SERVER_CERTIFICATE_ENTRY, PASSWORD), Configuration.KeyStore(this.serverKeyStoreFile, null, SERVER_CERTIFICATE_ENTRY, PASSWORD),
Configuration.TrustStore(this.trustStoreFile, null, false), Configuration.TrustStore(this.trustStoreFile, null, false, false),
true )
),
) )
Xml.write(Serializer.serialize(cfg), System.out) Xml.write(Serializer.serialize(cfg), System.out)
} }

View File

@@ -20,6 +20,7 @@ class ConfigurationTest {
"classpath:net/woggioni/gbcs/server/test/valid/gbcs-default.xml", "classpath:net/woggioni/gbcs/server/test/valid/gbcs-default.xml",
"classpath:net/woggioni/gbcs/server/test/valid/gbcs-memcached.xml", "classpath:net/woggioni/gbcs/server/test/valid/gbcs-memcached.xml",
"classpath:net/woggioni/gbcs/server/test/valid/gbcs-tls.xml", "classpath:net/woggioni/gbcs/server/test/valid/gbcs-tls.xml",
"classpath:net/woggioni/gbcs/server/test/valid/gbcs-memcached-tls.xml",
] ]
) )
@ParameterizedTest @ParameterizedTest

View File

@@ -7,6 +7,7 @@ import org.bouncycastle.asn1.x500.X500Name
import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Order import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.params.provider.ArgumentsSource
import java.net.http.HttpClient import java.net.http.HttpClient
import java.net.http.HttpRequest import java.net.http.HttpRequest
import java.net.http.HttpResponse import java.net.http.HttpResponse

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<gbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gbcs="urn:net.woggioni.gbcs.server"
xmlns:gbcs-memcached="urn:net.woggioni.gbcs.server.memcached"
xs:schemaLocation="urn:net.woggioni.gbcs.server.memcached jpms://net.woggioni.gbcs.server.memcached/net/woggioni/gbcs/server/memcached/schema/gbcs-memcached.xsd urn:net.woggioni.gbcs.server jpms://net.woggioni.gbcs.server/net/woggioni/gbcs/server/schema/gbcs.xsd"
>
<bind host="0.0.0.0" port="8443" incoming-connections-backlog-size="4096"/>
<connection
max-request-size="67108864"
idle-timeout="PT30S"
read-idle-timeout="PT60S"
write-idle-timeout="PT60S"
read-timeout="PT5M"
write-timeout="PT5M"/>
<event-executor use-virtual-threads="true"/>
<cache xs:type="gbcs-memcached:memcachedCacheType" max-age="P7D" max-size="16777216" compression-mode="zip">
<server host="memcached" port="11211"/>
</cache>
<authorization>
<users>
<user name="woggioni">
<quota calls="1000" period="PT1S"/>
</user>
<user name="gitea">
<quota calls="10" period="PT1S" initial-available-calls="100" max-available-calls="100"/>
</user>
<anonymous>
<quota calls="2" period="PT5S"/>
</anonymous>
</users>
<groups>
<group name="writers">
<users>
<user ref="woggioni"/>
<user ref="gitea"/>
</users>
<roles>
<reader/>
<writer/>
</roles>
</group>
</groups>
</authorization>
<authentication>
<client-certificate>
<user-extractor attribute-name="CN" pattern="(.*)"/>
</client-certificate>
</authentication>
<tls>
<keystore file="/home/luser/ssl/gbcs.woggioni.net.pfx" key-alias="gbcs.woggioni.net" password="KEYSTORE_PASSWOR" key-password="KEY_PASSWORD"/>
<truststore file="/home/luser/ssl/woggioni.net.pfx" check-certificate-status="false" password="TRUSTSTORE_PASSWORD"/>
</tls>
</gbcs:server>

View File

@@ -60,8 +60,8 @@
<user-extractor pattern="user-pattern" attribute-name="CN"/> <user-extractor pattern="user-pattern" attribute-name="CN"/>
</client-certificate> </client-certificate>
</authentication> </authentication>
<tls verify-clients="true"> <tls>
<keystore file="keystore.pfx" key-alias="key1" password="password" key-password="key-password"/> <keystore file="keystore.pfx" key-alias="key1" password="password" key-password="key-password"/>
<truststore file="truststore.pfx" password="password" check-certificate-status="true" /> <truststore file="truststore.pfx" password="password" check-certificate-status="true" require-client-certificate="true"/>
</tls> </tls>
</gbcs:server> </gbcs:server>