Skip to content

Commit

Permalink
xroot: switch to xrootd4j-4.6.0
Browse files Browse the repository at this point in the history
New major release with bug fixes and enhancements.

Highlight:
- reload TLS certificate

Changelog for v4.5.0..v4.6.0
    * [2861259b] [maven-release-plugin] prepare for next development iteration
    * [fd22f60c] xrootd4j:  do not release buffer of unconsumed message in AuthenticationHandler
    * [7924276f] xrootd4j: correct regression in NoAuthenticationProvider
    * [f2090745] Motivation:
    * [7d03e489] xrootd4j:  support kXR_prefname in kXR_locate request
    * [c76a766d] build(deps): bump netty-codec-haproxy from 4.1.5.Final to 4.1.86.Final
    * [6551be9a] xrootd4j:  fix bug in goToTLS value
    * [1ac4ee31] build:  update Copyright (C) 2011-2022 to Copyright (C) 2011-2023
    * [88922f05] xrootd4j: fix source TLS check for TPC client
    * [e09debe9] xrootd4j:  turn off TLS and ZTN for pre-5+ clients
    * [555a7500] xrootd4j: fix byte buffer leak in ZTNCredentialUtils
    * [7abf228d] xrootd4j: provide better info on SSLExceptions
    * [b62cb25d] core: don't manually update writeIndex in ReadResponse#writeTo
    * [75ae03b8] standalone: don't use ByteBuffer wrapper for read file chunk
    * [9f27e279] core: don't allocate AuthenticationRequest  buffer from memory pool
    * [422d1a72] tls: update SSLHandlerFactory to use SSLContext supplier
    * [8af2965a] [maven-release-plugin] prepare branch 4.6
    * [99c727fb] pom: fix license year
    * [e84edf92] [maven-release-plugin] prepare release v4.6.0

Ticket: #10562
Acked-by: Lea Morschel
Target: master
Require-book: no
Require-notes: yes
(cherry picked from commit 030d5ce)
Signed-off-by: Tigran Mkrtchyan <tigran.mkrtchyan@desy.de>
  • Loading branch information
kofemann authored and mksahakyan committed Apr 12, 2024
1 parent 3eeefe0 commit 973a063
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 11 deletions.
Expand Up @@ -17,12 +17,15 @@
*/
package org.dcache.xrootd.plugins.tls;

import com.google.common.base.Throwables;
import eu.emi.security.authn.x509.CrlCheckingMode;
import eu.emi.security.authn.x509.NamespaceCheckingMode;
import eu.emi.security.authn.x509.OCSPCheckingMode;
import io.netty.handler.ssl.SslContext;
import java.io.File;
import java.util.Properties;
import java.util.function.Supplier;

import org.dcache.ssl.CanlContextFactory;

/**
Expand All @@ -34,15 +37,15 @@
*/
public class CDCCanlTLSHandlerFactory extends SSLHandlerFactory {

private static final String SERVICE_KEY = "xrootd.security.tls.hostcert.key";
private static final String SERVICE_CERT = "xrootd.security.tls.hostcert.cert";
private static final String SERVICE_CACERTS = "xrootd.security.tls.ca.path";
private static final String NAMESPACE_MODE = "xrootd.security.tls.ca.namespace-mode";
private static final String CRL_MODE = "xrootd.security.tls.ca.crl-mode";
private static final String OCSP_MODE = "xrootd.security.tls.ca.ocsp-mode";
public static final String SERVICE_KEY = "xrootd.security.tls.hostcert.key";
public static final String SERVICE_CERT = "xrootd.security.tls.hostcert.cert";
public static final String SERVICE_CACERTS = "xrootd.security.tls.ca.path";
public static final String NAMESPACE_MODE = "xrootd.security.tls.ca.namespace-mode";
public static final String CRL_MODE = "xrootd.security.tls.ca.crl-mode";
public static final String OCSP_MODE = "xrootd.security.tls.ca.ocsp-mode";

@Override
protected SslContext buildContext(Properties properties) throws Exception {
protected Supplier<SslContext> buildContextSupplier(Properties properties) throws Exception {
File serviceKey = new File(properties.getProperty(SERVICE_KEY));
File serviceCert = new File(properties.getProperty(SERVICE_CERT));
File serviceCaCerts = new File(properties.getProperty(SERVICE_CACERTS));
Expand All @@ -53,7 +56,7 @@ protected SslContext buildContext(Properties properties) throws Exception {
OCSPCheckingMode ocspMode
= OCSPCheckingMode.valueOf(properties.getProperty(OCSP_MODE));

return CanlContextFactory.custom()
var builder = CanlContextFactory.custom()
.withCertificatePath(serviceCert.toPath())
.withKeyPath(serviceKey.toPath())
.withCertificateAuthorityPath(serviceCaCerts.toPath())
Expand All @@ -62,7 +65,15 @@ protected SslContext buildContext(Properties properties) throws Exception {
.withNamespaceMode(namespaceMode)
.withLazy(false)
.startTls(startTls)
.buildWithCaching(SslContext.class)
.call();
.buildWithCaching(SslContext.class);

return () -> {
try {
return builder.call();
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
};
}
}
@@ -0,0 +1,183 @@
package org.dcache.xrootd.plugins.tls;

import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import static org.dcache.xrootd.plugins.tls.CDCCanlTLSHandlerFactory.*;
import static org.dcache.xrootd.plugins.tls.CDCCanlTLSHandlerFactory.OCSP_MODE;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeTrue;
import eu.emi.security.authn.x509.impl.CertificateUtils;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

public class CDCCanlTLSHandlerFactoryTest {


// whatever slow test should run
private static final boolean RUN_SLOW_TESTS = System.getProperty("run.slow.tests") != null;

@BeforeClass
public static void setupClass() {
Security.addProvider(new BouncyCastleProvider());
}
private File keyFile;
private File certFile;
private File ca;

private Properties configProperties;

@Before
public void setUp() throws Exception {
keyFile = File.createTempFile("hostkey-", ".pem");
certFile = File.createTempFile("hostcert-", ".pem");
ca = Files.createTempDirectory("ca-").toFile();

configProperties = new Properties();
configProperties.setProperty(SERVICE_KEY, keyFile.getAbsolutePath());
configProperties.setProperty(SERVICE_CERT, certFile.getAbsolutePath());
configProperties.setProperty(SERVICE_CACERTS, ca.getAbsolutePath());
configProperties.setProperty(NAMESPACE_MODE, "IGNORE");
configProperties.setProperty(OCSP_MODE, "IGNORE");
configProperties.setProperty(CRL_MODE, "IGNORE");
}

@After
public void tearDown() throws Exception {
keyFile.delete();
certFile.delete();
ca.delete();
}

@Test
public void shouldReturnCachedContextIfFilesAreNotChanged() throws Exception {

assumeTrue("Skip slow running test", RUN_SLOW_TESTS);

generateSelfSignedCert();

var contextSupplierFactory = new CDCCanlTLSHandlerFactory();
var contextSupplier = contextSupplierFactory.buildContextSupplier(configProperties);

var c1 = contextSupplier.get();

TimeUnit.SECONDS.sleep(62); // 1 min is hard coded in org.dcache.ssl.CanlContextFactory.Builder.Builder

var c2 = contextSupplier.get();
assertSame(c1, c2);
}

@Test
public void shouldReturnNewContextIfFilesChanged() throws Exception {

assumeTrue("Skip slow running test", RUN_SLOW_TESTS);

generateSelfSignedCert();
var contextSupplierFactory = new CDCCanlTLSHandlerFactory();
var contextSupplier = contextSupplierFactory.buildContextSupplier(configProperties);

var c1 = contextSupplier.get();

TimeUnit.SECONDS.sleep(62); // 1 min is hard coded in org.dcache.ssl.CanlContextFactory.Builder.Builder

generateSelfSignedCert();
var c2 = contextSupplier.get();

assertNotSame(c1, c2);
}

// ensue that files are re-read
@Test//(expected = FileNotFoundException.class)
public void shouldFailToCreateContextIfFilesRemoved() throws Exception {

assumeTrue("Skip slow running test", RUN_SLOW_TESTS);

generateSelfSignedCert();
var contextSupplierFactory = new CDCCanlTLSHandlerFactory();
var contextSupplier = contextSupplierFactory.buildContextSupplier(configProperties);

contextSupplier.get();

TimeUnit.SECONDS.sleep(62); // 1 min is hard coded in org.dcache.ssl.CanlContextFactory.Builder.Builder

keyFile.delete();
certFile.delete();

var t = assertThrows(RuntimeException.class, contextSupplier::get);
assertThat(t.getCause(), new org.hamcrest.core.IsInstanceOf(FileNotFoundException.class));
}

private void generateSelfSignedCert()
throws GeneralSecurityException, OperatorCreationException, IOException {

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(2048, new SecureRandom());
KeyPair keyPair = keyPairGenerator.generateKeyPair();

long notBefore = System.currentTimeMillis();
long notAfter = notBefore + TimeUnit.DAYS.toMillis(1);

X500Name subjectDN = new X500Name("CN=localhost, O=dCache.org");
// explicit self-signed certificate
X500Name issuerDN = subjectDN;

SubjectPublicKeyInfo subjectPublicKeyInfo =
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());

X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(issuerDN,
BigInteger.ONE,
new Date(notBefore),
new Date(notAfter), subjectDN,
subjectPublicKeyInfo);

String signatureAlgorithm = "SHA256WithRSA";

// sign with own key
ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm)
.build(keyPair.getPrivate());

X509CertificateHolder certificateHolder = certificateBuilder.build(contentSigner);
var cert = new JcaX509CertificateConverter().getCertificate(certificateHolder);

try (OutputStream certOut = Files.newOutputStream(
certFile.toPath(), CREATE, TRUNCATE_EXISTING,
WRITE); OutputStream keyOut = Files.newOutputStream(keyFile.toPath(), CREATE,
TRUNCATE_EXISTING, WRITE)) {

CertificateUtils.saveCertificate(certOut, cert, CertificateUtils.Encoding.PEM);
CertificateUtils.savePrivateKey(keyOut, keyPair.getPrivate(), CertificateUtils.Encoding.PEM, null, null);
}
}

}

2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -68,7 +68,7 @@
<version.smc>6.6.0</version.smc>
<version.xerces>2.12.0</version.xerces>
<version.jetty>9.4.51.v20230217</version.jetty>
<version.xrootd4j>4.5.8</version.xrootd4j>
<version.xrootd4j>4.6.0</version.xrootd4j>
<version.jersey>2.28</version.jersey>
<version.dcache-view>2.1.0</version.dcache-view>
<version.netty>4.1.94.Final</version.netty>
Expand Down

0 comments on commit 973a063

Please sign in to comment.