/* dCache - http://www.dcache.org/ * * Copyright (C) 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.dss; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.security.auth.Subject; import java.io.EOFException; import java.io.IOException; import java.nio.ByteBuffer; import java.security.Principal; import java.security.cert.CertPath; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.Collections; import static com.google.common.base.Preconditions.checkState; import static java.util.Arrays.asList; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; public class SslEngineDssContext implements DssContext { private static final ByteBuffer EMPTY = ByteBuffer.wrap(new byte[0]); private final SSLEngine engine; private final CertificateFactory cf; private boolean isClientModeSet; /** Token data received from the peer. */ private ByteBuffer inToken; /** Token data to be delivered to the peer. */ private ByteBuffer outToken; /** Application data received from the peer. */ private ByteBuffer data; private Subject subject; public SslEngineDssContext(SSLEngine engine, CertificateFactory cf) { this.engine = engine; this.cf = cf; data = ByteBuffer.allocate(engine.getSession().getApplicationBufferSize()); outToken = ByteBuffer.allocate(engine.getSession().getPacketBufferSize()); } private void addInToken(byte[] token) { if (inToken == null || inToken.remaining() == 0) { inToken = ByteBuffer.wrap(token); } else if (inToken.capacity() - inToken.remaining() >= token.length) { inToken.compact(); inToken.put(token); inToken.flip(); } else { ByteBuffer buffer = ByteBuffer.allocate(inToken.remaining() + token.length); buffer.put(inToken); buffer.put(token); buffer.flip(); inToken = buffer; } } private byte[] getOutToken() { return getBytes(outToken); } private void handshake() throws IOException { while (!isEstablished()) { switch (engine.getHandshakeStatus()) { case NOT_HANDSHAKING: case FINISHED: throw new IllegalStateException("Not handshaking"); case NEED_TASK: Runnable task = engine.getDelegatedTask(); if (task != null) { task.run(); } break; case NEED_WRAP: wrap(EMPTY); break; case NEED_UNWRAP: if (!unwrap()) { return; } break; } } } private void wrap(ByteBuffer data) throws IOException { SSLEngineResult result; do { result = engine.wrap(data, outToken); switch (result.getStatus()) { case BUFFER_UNDERFLOW: throw new RuntimeException(); case BUFFER_OVERFLOW: ByteBuffer buffer = ByteBuffer.allocate(outToken.capacity() + engine.getSession().getPacketBufferSize()); outToken.flip(); buffer.put(outToken); outToken = buffer; break; case OK: if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) { subject = createSubject(); } break; case CLOSED: throw new EOFException(); } } while (result.getStatus() != SSLEngineResult.Status.OK); if (data.hasRemaining()) { throw new RuntimeException("SSLEngine did not wrap all data."); } } private boolean unwrap() throws IOException { while (true) { SSLEngineResult result = engine.unwrap(inToken, data); switch (result.getStatus()) { case BUFFER_UNDERFLOW: return false; case BUFFER_OVERFLOW: ByteBuffer buffer = ByteBuffer.allocate( outToken.capacity() + engine.getSession().getApplicationBufferSize()); data.flip(); buffer.put(data); data = buffer; break; case OK: if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) { subject = createSubject(); } return true; case CLOSED: throw new EOFException(); } } } private byte[] getData() { return getBytes(data); } private static byte[] getBytes(ByteBuffer buffer) { byte[] bytes; buffer.flip(); if (!buffer.hasRemaining()) { bytes = null; } else { bytes = new byte[buffer.remaining()]; buffer.get(bytes); } buffer.clear(); return bytes; } @Override public byte[] init(byte[] token) throws IOException { checkState(!isEstablished()); if (!isClientModeSet) { engine.setUseClientMode(true); isClientModeSet = true; } addInToken(token); handshake(); return getOutToken(); } @Override public byte[] accept(byte[] token) throws IOException { checkState(!isEstablished()); if (!isClientModeSet) { engine.setUseClientMode(false); isClientModeSet = true; } addInToken(token); handshake(); return getOutToken(); } @Override public byte[] wrap(byte[] data, int offset, int len) throws IOException { checkState(isEstablished()); wrap(ByteBuffer.wrap(data, offset, len)); return getOutToken(); } @Override public byte[] unwrap(byte[] token) throws IOException { checkState(isEstablished()); addInToken(token); boolean underflow; do { underflow = !unwrap(); } while (!underflow && inToken.hasRemaining()); return getData(); } @Override public boolean isEstablished() { return subject != null; } @Override public Subject getSubject() { return subject; } @Override public String getPeerName() { try { return engine.getSession().getPeerPrincipal().getName(); } catch (SSLPeerUnverifiedException e) { return null; } } public SSLSession getSSLSession() { return engine.getSession(); } private Subject createSubject() throws IOException { try { Certificate[] chain = engine.getSession().getPeerCertificates(); CertPath certPath = cf.generateCertPath(asList(chain)); return new Subject(false, Collections.<Principal>emptySet(), singleton(certPath), emptySet()); } catch (SSLPeerUnverifiedException e) { throw new IOException("Failed to establish identity of SSL peer: " + e.getMessage(), e); } catch (CertificateException e) { throw new IOException("Certificate failure: " + e.getMessage(), e); } } }