From 9fa1645d8a402a4ec36d1265c16e26d09eb23d54 Mon Sep 17 00:00:00 2001 From: Gerd Behrmann Date: Thu, 15 Oct 2015 13:37:45 +0200 Subject: [PATCH] xrootd4j: Port to CANL Uses CANL rather than JGlobus to implement GSI authentication. Acked-by: Tigran Mkrtchyan Patch: https://rb.dcache.org/r/8657/ Target: master --- pom.xml | 6 +- xrootd4j-gsi/pom.xml | 10 +- .../xrootd/plugins/authn/gsi/CertUtil.java | 57 ++----- .../xrootd/plugins/authn/gsi/DHSession.java | 8 +- .../authn/gsi/GSIAuthenticationFactory.java | 144 +++++------------- .../authn/gsi/GSIAuthenticationHandler.java | 70 +++------ .../src/main/resources/default.properties | 3 + 7 files changed, 80 insertions(+), 218 deletions(-) diff --git a/pom.xml b/pom.xml index 05c8f31e..e690e2bb 100644 --- a/pom.xml +++ b/pom.xml @@ -164,9 +164,9 @@ 1.8 - org.jglobus - ssl-proxies - 2.0.6 + eu.eu-emi.security + canl + 1.3.3 org.dcache diff --git a/xrootd4j-gsi/pom.xml b/xrootd4j-gsi/pom.xml index 6ece22a6..8523b1ea 100644 --- a/xrootd4j-gsi/pom.xml +++ b/xrootd4j-gsi/pom.xml @@ -47,14 +47,8 @@ commons-codec - org.jglobus - ssl-proxies - - - commons-logging - commons-logging-api - - + eu.eu-emi.security + canl org.dcache diff --git a/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/CertUtil.java b/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/CertUtil.java index bdf65928..4a0742fb 100644 --- a/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/CertUtil.java +++ b/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/CertUtil.java @@ -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 @@ -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); + } } /** @@ -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 parseCerts(Reader in) - throws IOException, GeneralSecurityException - { - if (in == null) { - throw new IllegalArgumentException("no inputstream given"); - } - - List 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; - } } diff --git a/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/DHSession.java b/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/DHSession.java index 05ab6110..c7f87d48 100644 --- a/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/DHSession.java +++ b/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/DHSession.java @@ -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; @@ -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; @@ -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()); } diff --git a/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/GSIAuthenticationFactory.java b/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/GSIAuthenticationFactory.java index 511670c4..2440b449 100644 --- a/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/GSIAuthenticationFactory.java +++ b/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/GSIAuthenticationFactory.java @@ -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; @@ -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) { @@ -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); @@ -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()); - } } diff --git a/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/GSIAuthenticationHandler.java b/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/GSIAuthenticationHandler.java index aa0d83a6..5acb2742 100644 --- a/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/GSIAuthenticationHandler.java +++ b/xrootd4j-gsi/src/main/java/org/dcache/xrootd/plugins/authn/gsi/GSIAuthenticationHandler.java @@ -18,22 +18,20 @@ */ package org.dcache.xrootd.plugins.authn.gsi; +import eu.emi.security.authn.x509.X509CertChainValidator; +import eu.emi.security.authn.x509.X509Credential; +import eu.emi.security.authn.x509.impl.CertificateUtils; import io.netty.buffer.ByteBuf; -import org.globus.gsi.CertificateRevocationLists; -import org.globus.gsi.TrustedCertificates; -import org.globus.gsi.proxy.ProxyPathValidator; -import org.globus.gsi.proxy.ProxyPathValidatorException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.crypto.Cipher; import javax.security.auth.Subject; +import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.StringReader; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; -import java.security.PrivateKey; import java.security.SecureRandom; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; @@ -99,15 +97,10 @@ public class GSIAuthenticationHandler implements AuthenticationHandler /** cryptographic helper classes */ private static final SecureRandom _random = new SecureRandom(); - private static final ProxyPathValidator _proxyValidator = - new ProxyPathValidator(); - /** certificates/keys/trust-anchors */ - private final TrustedCertificates _trustedCerts; - private final X509Certificate _hostCertificate; - private final PrivateKey _hostKey; - private final CertificateRevocationLists _crls; + private final X509Credential _hostCredential; + private final X509CertChainValidator _validator; private String _challenge = ""; private Cipher _challengeCipher; @@ -121,16 +114,9 @@ public class GSIAuthenticationHandler implements AuthenticationHandler private boolean _finished = false; - public GSIAuthenticationHandler(X509Certificate hostCertificate, - PrivateKey privateKey, - TrustedCertificates trustedCerts, - CertificateRevocationLists crls) { - - _hostCertificate = hostCertificate; - _hostKey = privateKey; - _trustedCerts = trustedCerts; - _crls = crls; - + public GSIAuthenticationHandler(X509Credential hostCredential, X509CertChainValidator validator) { + _hostCredential = hostCredential; + _validator = validator; _subject = new Subject(); } @@ -218,10 +204,8 @@ public XrootdResponse authenticate(AuthenticationRequest throws XrootdException { try { - _challengeCipher = Cipher.getInstance(SERVER_ASYNC_CIPHER_MODE, - "BC"); - - _challengeCipher.init(Cipher.ENCRYPT_MODE, _hostKey); + _challengeCipher = Cipher.getInstance(SERVER_ASYNC_CIPHER_MODE, "BC"); + _challengeCipher.init(Cipher.ENCRYPT_MODE, _hostCredential.getKey()); Map buckets = request.getBuckets(); NestedBucketBuffer buffer = @@ -239,8 +223,10 @@ public XrootdResponse authenticate(AuthenticationRequest /* send DH params */ byte[] puk = _dhSession.getEncodedDHMaterial().getBytes(); /* send host certificate */ + _hostCredential.getCertificate().getEncoded(); + String hostCertificateString = - CertUtil.certToPEM(_hostCertificate.getEncoded()); + CertUtil.certToPEM(_hostCredential.getCertificate()); XrootdBucketContainer responseBuckets = buildCertReqResponse(signedRtag, @@ -328,31 +314,19 @@ public XrootdResponse authenticate(AuthenticationRequest ((StringBucket) clientX509Bucket).getContent(); /* now it's time to verify the client's X509 certificate */ - List clientCerts = - CertUtil.parseCerts(new StringReader(clientX509)); - - X509Certificate proxyCert; - - if (clientCerts.size() > 1) { - proxyCert = clientCerts.get(0); - } else { + X509Certificate[] proxyCertChain = CertificateUtils.loadCertificateChain(new ByteArrayInputStream(clientX509.getBytes(US_ASCII)), CertificateUtils.Encoding.PEM); + if (proxyCertChain.length == 0) { throw new IllegalArgumentException("Could not parse user " + "certificate from input stream!"); } - + X509Certificate proxyCert = proxyCertChain[0]; _logger.info("The proxy-cert has the subject {} and the issuer {}", proxyCert.getSubjectDN(), proxyCert.getIssuerDN()); - X509Certificate[] proxyCertChain = - clientCerts.toArray(new X509Certificate[clientCerts.size()]); + _validator.validate(proxyCertChain); _subject.getPublicCredentials().add(proxyCertChain); - _proxyValidator.validate(proxyCertChain, - _trustedCerts.getCertificates(), - _crls, - _trustedCerts.getSigningPolicies()); - _challengeCipher.init(Cipher.DECRYPT_MODE, proxyCert.getPublicKey()); XrootdBucket signedRTagBucket = @@ -393,12 +367,6 @@ public XrootdResponse authenticate(AuthenticationRequest throw new XrootdException(kXR_IOError, "Could not decrypt encrypted " + "client message."); - } catch (ProxyPathValidatorException ppvex) { - _logger.error("Could not validate certificate path of client " + - "certificate: {}", ppvex); - throw new XrootdException(kXR_NotAuthorized, - "Your certificate's issuer is " + - "not trusted."); } catch (GeneralSecurityException gssex) { _logger.error("Error during decrypting/server-side key exchange: {}", gssex); @@ -508,7 +476,7 @@ private String generateChallengeString() { public String getProtocol() { /* hashed principals are cached in CertUtil */ String subjectHash = - CertUtil.computeMD5Hash(_hostCertificate.getIssuerX500Principal()); + CertUtil.computeMD5Hash(_hostCredential.getCertificate().getIssuerX500Principal()); return "&P=" + PROTOCOL + "," + "v:" + PROTOCOL_VERSION + "," + diff --git a/xrootd4j-gsi/src/main/resources/default.properties b/xrootd4j-gsi/src/main/resources/default.properties index 821bd2fd..2b93d158 100644 --- a/xrootd4j-gsi/src/main/resources/default.properties +++ b/xrootd4j-gsi/src/main/resources/default.properties @@ -25,3 +25,6 @@ xrootd.gsi.hostcert.verify=true xrootd.gsi.ca.path=/etc/grid-security/certificates xrootd.gsi.ca.refresh=43200 xrootd.gsi.ca.refresh.unit=SECONDS +xrootd.gsi.ca.namespace-mode=EUGRIDPMA_AND_GLOBUS_REQUIRE +xrootd.gsi.ca.crl-mode=REQUIRE +xrootd.gsi.ca.ocsp-mode=IF_AVAILABLE