/* dCache - http://www.dcache.org/
*
* Copyright (C) 2014-2015 Deutsches Elektronen-Synchrotron
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.dcache.gsi;
import com.google.common.io.ByteSource;
import eu.emi.security.authn.x509.X509Credential;
import eu.emi.security.authn.x509.proxy.ProxyGenerator;
import eu.emi.security.authn.x509.proxy.ProxyRequestOptions;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import static com.google.common.base.Preconditions.checkArgument;
/**
* Wrapper for SSLEngine that implements GSI delegation. Only the client side of GSI is
* supported.
*/
public class ClientGsiEngine extends InterceptingSSLEngine
{
public static final char DELEGATION_CHAR = 'D';
public static final char NO_DELEGATION_CHAR = '0';
private final boolean isDelegationLimited;
private final X509Credential credential;
public ClientGsiEngine(SSLEngine delegate, X509Credential credential, boolean isDelegationEnabled, boolean isDelegationLimited)
{
super(delegate);
this.isDelegationLimited = isDelegationLimited;
this.credential = credential;
if (isDelegationEnabled) {
sendThenReceive(ByteBuffer.wrap(new byte[] { DELEGATION_CHAR }), new GotCsr());
} else {
send(ByteBuffer.wrap(new byte[]{NO_DELEGATION_CHAR}));
}
}
@Override
public void setUseClientMode(boolean isClientMode)
{
checkArgument(isClientMode, "Only the client side of GSI is supported by this engine.");
super.setUseClientMode(isClientMode);
}
private class GotCsr implements Callback
{
private ByteSource data;
@Override
public void call(ByteBuffer buffer) throws SSLException
{
// read csr
ByteSource chunk = ByteSource.wrap(buffer.array()).slice(buffer.arrayOffset(), buffer.position());
ByteSource source = (data == null) ? chunk : ByteSource.concat(data, chunk);
try {
PKCS10CertificationRequest csr = new PKCS10CertificationRequest(source.read());
// generate proxy
ProxyRequestOptions options = new ProxyRequestOptions(credential.getCertificateChain(), csr);
options.setLimited(isDelegationLimited);
X509Certificate[] chain = ProxyGenerator.generate(options, credential.getKey());
// send to server
send(ByteBuffer.wrap(chain[0].getEncoded()));
} catch (EOFException f) {
try {
/* Incomplete - read another frame. */
ByteSource copy = ByteSource.wrap(chunk.read());
data = (data == null) ? copy : ByteSource.concat(data, copy);
receive(this);
} catch (IOException e) {
f.addSuppressed(e);
}
} catch (CertificateParsingException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException | CertificateEncodingException | InvalidKeyException | IOException e) {
throw new SSLException("GSI delegation failed: " + e.toString(), e);
}
}
}
}