Skip to content

Commit

Permalink
xrootd4j: Port to CANL
Browse files Browse the repository at this point in the history
Uses CANL rather than JGlobus to implement GSI authentication.

Acked-by: Tigran Mkrtchyan <[email protected]>
Patch: https://rb.dcache.org/r/8657/
Target: master
  • Loading branch information
gbehrmann committed Oct 16, 2015
1 parent 7105b0d commit 9fa1645
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 218 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@
<version>1.8</version>
</dependency>
<dependency>
<groupId>org.jglobus</groupId>
<artifactId>ssl-proxies</artifactId>
<version>2.0.6</version>
<groupId>eu.eu-emi.security</groupId>
<artifactId>canl</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>org.dcache</groupId>
Expand Down
10 changes: 2 additions & 8 deletions xrootd4j-gsi/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,8 @@
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.jglobus</groupId>
<artifactId>ssl-proxies</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging-api</artifactId>
</exclusion>
</exclusions>
<groupId>eu.eu-emi.security</groupId>
<artifactId>canl</artifactId>
</dependency>
<dependency>
<groupId>org.dcache</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,22 @@
*/
package org.dcache.xrootd.plugins.authn.gsi;

import com.google.common.base.Throwables;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils;
import org.bouncycastle.openssl.PEMWriter;

import javax.security.auth.x500.X500Principal;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.security.GeneralSecurityException;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.X509Certificate;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static org.globus.gsi.CertUtil.readCertificate;

/**
*
* CertUtil - convenience methods for certificate processing
Expand Down Expand Up @@ -86,14 +82,20 @@ public static byte[] fromPEM(String pem, String header, String footer)
/**
* Encodes to PEM format with default X.509 certificate
* header/footer
* @param der the content to be encoded
* @param certificate the certificate to be encoded
* @return the PEM-encoded String
*/
public static String certToPEM(byte [] der)
public static String certToPEM(X509Certificate certificate)
{
return toPEM(der,
"-----BEGIN CERTIFICATE-----",
"-----END CERTIFICATE-----");
try {
StringWriter output = new StringWriter();
PEMWriter writer = new PEMWriter(output);
writer.writeObject(certificate);
writer.flush();
return output.toString();
} catch (IOException e) {
throw Throwables.propagate(e);
}
}

/**
Expand Down Expand Up @@ -217,35 +219,4 @@ private static StringBuilder removeChar(StringBuilder sb, char c)
return sb;
}

/**
* Parses a sequence of certificates from an input source and returns it
* as a list. The cert list usually represents a 'certificate path', used
* to validate the chain of trust.
*
* @param in the input source
* @return a list of x509 certificates
* @throws IOException if an parse error occurs
* @throws GeneralSecurityException if not certificates are found
*/
public static List<X509Certificate> parseCerts(Reader in)
throws IOException, GeneralSecurityException
{
if (in == null) {
throw new IllegalArgumentException("no inputstream given");
}

List<X509Certificate> list = new LinkedList<>();
X509Certificate cert;
try (BufferedReader reader = new BufferedReader(in)) {
while ((cert = readCertificate(reader)) != null) {
list.add(cert);
}
}

if (list.isEmpty()) {
throw new GeneralSecurityException("no certificates found");
}

return list;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
*/
package org.dcache.xrootd.plugins.authn.gsi;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.pkcs.DHParameter;
import org.globus.gsi.bc.BouncyCastleUtil;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
Expand All @@ -34,6 +34,7 @@
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
Expand Down Expand Up @@ -221,8 +222,9 @@ private String removeCharFromString(String s, char c)
*/
private DHParameterSpec fromDER(byte[] der) throws IOException
{
DHParameter dhparam = new DHParameter((ASN1Sequence) BouncyCastleUtil
.toDERObject(der));
ByteArrayInputStream inStream = new ByteArrayInputStream(der);
ASN1InputStream derInputStream = new ASN1InputStream(inStream);
DHParameter dhparam = new DHParameter((ASN1Sequence) derInputStream.readObject());
return new DHParameterSpec(dhparam.getP(), dhparam.getG());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,23 @@
*/
package org.dcache.xrootd.plugins.authn.gsi;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.globus.gsi.CertificateRevocationLists;
import org.globus.gsi.TrustedCertificates;
import org.globus.gsi.proxy.ProxyPathValidator;
import org.globus.gsi.proxy.ProxyPathValidatorException;
import eu.emi.security.authn.x509.CrlCheckingMode;
import eu.emi.security.authn.x509.NamespaceCheckingMode;
import eu.emi.security.authn.x509.OCSPCheckingMode;
import eu.emi.security.authn.x509.OCSPParametes;
import eu.emi.security.authn.x509.ProxySupport;
import eu.emi.security.authn.x509.RevocationParameters;
import eu.emi.security.authn.x509.X509CertChainValidator;
import eu.emi.security.authn.x509.impl.OpensslCertChainValidator;
import eu.emi.security.authn.x509.impl.PEMCredential;
import eu.emi.security.authn.x509.impl.ValidatorParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.KeyStoreException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -69,23 +62,15 @@ public class GSIAuthenticationFactory implements AuthenticationFactory
private final String _hostCertificatePath;
private final String _hostKeyPath;
private final String _caCertificatePath;

private X509Certificate _hostCertificate;
private PrivateKey _hostKey;
private TrustedCertificates _trustedCerts;
private final X509CertChainValidator _validator;

private final long _hostCertRefreshInterval;
private final long _trustAnchorRefreshInterval;
private long _hostCertRefreshTimestamp = 0;
private long _trustAnchorRefreshTimestamp = 0;

private final ProxyPathValidator _proxyValidator = new ProxyPathValidator();
private final boolean _verifyHostCertificate;

static
{
Security.addProvider(new BouncyCastleProvider());
}
private PEMCredential _hostCredential;

public GSIAuthenticationFactory(Properties properties)
{
Expand All @@ -103,21 +88,25 @@ public GSIAuthenticationFactory(Properties properties)
_trustAnchorRefreshInterval =
TimeUnit.valueOf(properties.getProperty("xrootd.gsi.ca.refresh.unit"))
.toMillis(Integer.parseInt(properties.getProperty("xrootd.gsi.ca.refresh")));
NamespaceCheckingMode namespaceMode =
NamespaceCheckingMode.valueOf(properties.getProperty("xrootd.gsi.ca.namespace-mode"));
CrlCheckingMode crlCheckingMode =
CrlCheckingMode.valueOf(properties.getProperty("xrootd.gsi.ca.crl-mode"));
OCSPCheckingMode ocspCheckingMode =
OCSPCheckingMode.valueOf(properties.getProperty("xrootd.gsi.ca.ocsp-mode"));
ValidatorParams validatorParams = new ValidatorParams(
new RevocationParameters(crlCheckingMode, new OCSPParametes(ocspCheckingMode)), ProxySupport.ALLOW);
_validator =
new OpensslCertChainValidator(_caCertificatePath, false, namespaceMode,
_trustAnchorRefreshInterval, validatorParams, false);
}

@Override
public AuthenticationHandler createHandler()
throws InvalidHandlerConfigurationException
{
CertificateRevocationLists crls =
CertificateRevocationLists.getDefaultCertificateRevocationLists();

try {
loadTrustAnchors();
loadServerCredentials(crls);
} catch (ProxyPathValidatorException ppvex) {
String msg = "Could not verify server certificate chain";
throw new InvalidHandlerConfigurationException(msg, ppvex);
loadServerCredentials();
} catch (GeneralSecurityException gssex) {
String msg = "Could not load certificates/key due to security error";
throw new InvalidHandlerConfigurationException(msg, gssex);
Expand All @@ -126,88 +115,23 @@ public AuthenticationHandler createHandler()
throw new InvalidHandlerConfigurationException(msg, ioex);
}

return new GSIAuthenticationHandler(_hostCertificate,
_hostKey,
_trustedCerts,
crls);
}

/**
* Reload the trusted certificates from the position specified in
* caCertDir
*/
private synchronized void loadTrustAnchors()
{
long timeSinceLastTrustAnchorRefresh = (System.currentTimeMillis() -
_trustAnchorRefreshTimestamp);

if (_trustedCerts == null ||
(timeSinceLastTrustAnchorRefresh >= _trustAnchorRefreshInterval)) {
_logger.info("CA certificate directory: {}", _caCertificatePath);
_trustedCerts = TrustedCertificates.load(_caCertificatePath);

_trustAnchorRefreshTimestamp = System.currentTimeMillis();
}
return new GSIAuthenticationHandler(_hostCredential, _validator);
}

private synchronized void loadServerCredentials(CertificateRevocationLists crls)
throws CertificateException, IOException, NoSuchAlgorithmException,
InvalidKeySpecException, ProxyPathValidatorException, NoSuchProviderException
private synchronized void loadServerCredentials() throws CertificateException, KeyStoreException, IOException
{
long timeSinceLastServerRefresh =
(System.currentTimeMillis() - _hostCertRefreshTimestamp);

if (_hostCertificate == null || _hostKey == null ||
(timeSinceLastServerRefresh >= _hostCertRefreshInterval)) {
_logger.info("Time since last server cert refresh {}",
timeSinceLastServerRefresh);
_logger.info("Loading server certificates. Current refresh " +
"interval: {} ms",
long timeSinceLastServerRefresh = (System.currentTimeMillis() - _hostCertRefreshTimestamp);
if (_hostCredential == null || timeSinceLastServerRefresh >= _hostCertRefreshInterval) {
_logger.info("Time since last server cert refresh {}", timeSinceLastServerRefresh);
_logger.info("Loading server certificates. Current refresh interval: {} ms",
_hostCertRefreshInterval);
loadHostCertificate();
loadHostKey();

PEMCredential credential = new PEMCredential(_hostKeyPath, _hostCertificatePath, null);
if (_verifyHostCertificate) {
_logger.info("Verifying host certificate");
verifyHostCertificate(crls);
_validator.validate(credential.getCertificateChain());
}

_hostCredential = credential;
_hostCertRefreshTimestamp = System.currentTimeMillis();
}
}

private void loadHostCertificate()
throws CertificateException, IOException, NoSuchProviderException
{
try (InputStream fis = new FileInputStream(_hostCertificatePath)) {
CertificateFactory cf =
CertificateFactory.getInstance("X.509", "BC");
_hostCertificate = (X509Certificate) cf.generateCertificate(fis);
}
}

private void loadHostKey()
throws NoSuchAlgorithmException, IOException, InvalidKeySpecException
{
/* java's RSA KeyFactory needs keys in PKCS8 encoding. Use
* BouncyCastle instead, as jGlobus does.
*/
BufferedReader br = new BufferedReader(new FileReader(_hostKeyPath));
KeyPair kp = (KeyPair) new PEMReader(br).readObject();
_hostKey = kp.getPrivate();
}

/**
* Check whether host certificate's certificate chain is trusted according
* to jGlobus' proxy validation check
* @throws ProxyPathValidatorException
*/
private void verifyHostCertificate(CertificateRevocationLists crls)
throws ProxyPathValidatorException
{
_proxyValidator.validate(new X509Certificate[] { _hostCertificate },
_trustedCerts.getCertificates(),
crls,
_trustedCerts.getSigningPolicies());
}
}
Loading

0 comments on commit 9fa1645

Please sign in to comment.