| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.conscrypt; |
| |
| import static org.conscrypt.Preconditions.checkNotNull; |
| |
| import java.security.Principal; |
| import java.security.cert.Certificate; |
| import java.security.cert.CertificateException; |
| import java.security.cert.X509Certificate; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import javax.net.ssl.SSLPeerUnverifiedException; |
| import javax.net.ssl.SSLSessionBindingEvent; |
| import javax.net.ssl.SSLSessionBindingListener; |
| import javax.net.ssl.SSLSessionContext; |
| |
| /** |
| * A session that is dedicated a single connection and operates directly on the underlying |
| * {@code SSL}. |
| */ |
| final class ActiveSession implements ConscryptSession { |
| private final NativeSsl ssl; |
| private AbstractSessionContext sessionContext; |
| private byte[] id; |
| private long creationTime; |
| private String protocol; |
| private String peerHost; |
| private int peerPort = -1; |
| private long lastAccessedTime = 0; |
| private volatile javax.security.cert.X509Certificate[] peerCertificateChain; |
| private X509Certificate[] localCertificates; |
| private X509Certificate[] peerCertificates; |
| private byte[] peerCertificateOcspData; |
| private byte[] peerTlsSctData; |
| |
| // lazy init for memory reasons |
| private Map<String, Object> values; |
| |
| ActiveSession(NativeSsl ssl, AbstractSessionContext sessionContext) { |
| this.ssl = checkNotNull(ssl, "ssl"); |
| this.sessionContext = checkNotNull(sessionContext, "sessionContext"); |
| } |
| |
| @Override |
| public byte[] getId() { |
| if (id == null) { |
| synchronized (ssl) { |
| id = ssl.getSessionId(); |
| } |
| } |
| return id != null ? id.clone() : EmptyArray.BYTE; |
| } |
| |
| /** |
| * Indicates that this session's ID may have changed and should be re-cached. |
| */ |
| void resetId() { |
| id = null; |
| } |
| |
| @Override |
| public SSLSessionContext getSessionContext() { |
| return isValid() ? sessionContext : null; |
| } |
| |
| @Override |
| public long getCreationTime() { |
| if (creationTime == 0) { |
| synchronized (ssl) { |
| creationTime = ssl.getTime(); |
| } |
| } |
| return creationTime; |
| } |
| |
| /** |
| * Returns the last time this SSL session was accessed. Accessing |
| * here is to mean that a new connection with the same SSL context data was |
| * established. |
| * |
| * @return the session's last access time in milliseconds since the epoch |
| */ |
| // TODO(nathanmittler): Does lastAccessedTime need to account for session reuse? |
| @Override |
| public long getLastAccessedTime() { |
| return lastAccessedTime == 0 ? getCreationTime() : lastAccessedTime; |
| } |
| |
| void setLastAccessedTime(long accessTimeMillis) { |
| lastAccessedTime = accessTimeMillis; |
| } |
| |
| /** |
| * Returns the OCSP stapled response. Returns a copy of the internal arrays. |
| * |
| * The method signature matches |
| * <a |
| * href="http://download.java.net/java/jdk9/docs/api/javax/net/ssl/ExtendedSSLSession.html#getStatusResponses--">Java |
| * 9</a>. |
| * |
| * @see <a href="https://tools.ietf.org/html/rfc6066">RFC 6066</a> |
| * @see <a href="https://tools.ietf.org/html/rfc6961">RFC 6961</a> |
| */ |
| @Override |
| public List<byte[]> getStatusResponses() { |
| if (peerCertificateOcspData == null) { |
| return Collections.<byte[]>emptyList(); |
| } |
| |
| return Collections.singletonList(peerCertificateOcspData.clone()); |
| } |
| |
| /** |
| * Returns the signed certificate timestamp (SCT) received from the peer. Returns a |
| * copy of the internal array. |
| * |
| * @see <a href="https://tools.ietf.org/html/rfc6962">RFC 6962</a> |
| */ |
| @Override |
| public byte[] getPeerSignedCertificateTimestamp() { |
| if (peerTlsSctData == null) { |
| return null; |
| } |
| return peerTlsSctData.clone(); |
| } |
| |
| @Override |
| public String getRequestedServerName() { |
| synchronized (ssl) { |
| return ssl.getRequestedServerName(); |
| } |
| } |
| |
| @Override |
| public void invalidate() { |
| synchronized (ssl) { |
| ssl.setTimeout(0L); |
| } |
| } |
| |
| @Override |
| public boolean isValid() { |
| synchronized (ssl) { |
| long creationTimeMillis = ssl.getTime(); |
| long timeoutMillis = ssl.getTimeout(); |
| return (System.currentTimeMillis() - timeoutMillis) < creationTimeMillis; |
| } |
| } |
| |
| @Override |
| public void putValue(String name, Object value) { |
| if (name == null) { |
| throw new NullPointerException("name"); |
| } |
| if (value == null) { |
| throw new NullPointerException("value"); |
| } |
| Map<String, Object> values = this.values; |
| if (values == null) { |
| // Use size of 2 to keep the memory overhead small |
| values = this.values = new HashMap<String, Object>(2); |
| } |
| Object old = values.put(name, value); |
| if (value instanceof SSLSessionBindingListener) { |
| ((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name)); |
| } |
| if (old instanceof SSLSessionBindingListener) { |
| ((SSLSessionBindingListener) old).valueUnbound(new SSLSessionBindingEvent(this, name)); |
| } |
| notifyUnbound(old, name); |
| } |
| |
| @Override |
| public Object getValue(String name) { |
| if (name == null) { |
| throw new NullPointerException("name"); |
| } |
| if (values == null) { |
| return null; |
| } |
| return values.get(name); |
| } |
| |
| @Override |
| public void removeValue(String name) { |
| if (name == null) { |
| throw new NullPointerException("name"); |
| } |
| Map<String, Object> values = this.values; |
| if (values == null) { |
| return; |
| } |
| Object old = values.remove(name); |
| notifyUnbound(old, name); |
| } |
| |
| @Override |
| public String[] getValueNames() { |
| Map<String, Object> values = this.values; |
| if (values == null || values.isEmpty()) { |
| return EmptyArray.STRING; |
| } |
| return values.keySet().toArray(new String[values.size()]); |
| } |
| |
| @Override |
| public X509Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { |
| checkPeerCertificatesPresent(); |
| return peerCertificates.clone(); |
| } |
| |
| @Override |
| public Certificate[] getLocalCertificates() { |
| return localCertificates == null ? null : localCertificates.clone(); |
| } |
| |
| /** |
| * Returns the certificate(s) of the peer in this SSL session |
| * used in the handshaking phase of the connection. |
| * Please notice hat this method is superseded by |
| * <code>getPeerCertificates()</code>. |
| * @return an array of X509 certificates (the peer's one first and then |
| * eventually that of the certification authority) or null if no |
| * certificate were used during the SSL connection. |
| * @throws SSLPeerUnverifiedException if either a non-X.509 certificate |
| * was used (i.e. Kerberos certificates) or the peer could not |
| * be verified. |
| */ |
| @Override |
| public javax.security.cert.X509Certificate[] getPeerCertificateChain() |
| throws SSLPeerUnverifiedException { |
| checkPeerCertificatesPresent(); |
| // TODO(nathanmittler): Should we clone? |
| javax.security.cert.X509Certificate[] result = peerCertificateChain; |
| if (result == null) { |
| // single-check idiom |
| peerCertificateChain = result = SSLUtils.toCertificateChain(peerCertificates); |
| } |
| return result; |
| } |
| |
| @Override |
| public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { |
| checkPeerCertificatesPresent(); |
| return peerCertificates[0].getSubjectX500Principal(); |
| } |
| |
| @Override |
| public Principal getLocalPrincipal() { |
| if (localCertificates != null && localCertificates.length > 0) { |
| return localCertificates[0].getSubjectX500Principal(); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public String getCipherSuite() { |
| // Always get the Cipher from the SSL directly since it may have changed during a |
| // renegotiation. |
| String cipher; |
| synchronized (ssl) { |
| cipher = ssl.getCipherSuite(); |
| } |
| return cipher == null ? SSLNullSession.INVALID_CIPHER : cipher; |
| } |
| |
| @Override |
| public String getProtocol() { |
| String protocol = this.protocol; |
| if (protocol == null) { |
| synchronized (ssl) { |
| protocol = ssl.getVersion(); |
| } |
| this.protocol = protocol; |
| } |
| return protocol; |
| } |
| |
| @Override |
| public String getPeerHost() { |
| return peerHost; |
| } |
| |
| @Override |
| public int getPeerPort() { |
| return peerPort; |
| } |
| |
| @Override |
| public int getPacketBufferSize() { |
| return NativeConstants.SSL3_RT_MAX_PACKET_SIZE; |
| } |
| |
| @Override |
| public int getApplicationBufferSize() { |
| return NativeConstants.SSL3_RT_MAX_PLAIN_LENGTH; |
| } |
| |
| /** |
| * Configures the peer information once it has been received by the handshake. |
| */ |
| void onPeerCertificatesReceived( |
| String peerHost, int peerPort, X509Certificate[] peerCertificates) { |
| configurePeer(peerHost, peerPort, peerCertificates); |
| } |
| |
| private void configurePeer(String peerHost, int peerPort, X509Certificate[] peerCertificates) { |
| this.peerHost = peerHost; |
| this.peerPort = peerPort; |
| this.peerCertificates = peerCertificates; |
| synchronized (ssl) { |
| this.peerCertificateOcspData = ssl.getPeerCertificateOcspData(); |
| this.peerTlsSctData = ssl.getPeerTlsSctData(); |
| } |
| } |
| |
| /** |
| * Updates the cached peer certificate after the handshake has completed |
| * (or entered False Start). |
| */ |
| void onPeerCertificateAvailable(String peerHost, int peerPort) throws CertificateException { |
| synchronized (ssl) { |
| id = null; |
| this.localCertificates = ssl.getLocalCertificates(); |
| if (this.peerCertificates == null) { |
| // When resuming a session, the cert_verify_callback (which calls |
| // onPeerCertificatesReceived) isn't called by BoringSSL during the handshake |
| // because it presumes the certs were verified in the previous connection on that |
| // session, leaving us without the peer certificates. If that happens, fetch them |
| // explicitly. |
| configurePeer(peerHost, peerPort, ssl.getPeerCertificates()); |
| } |
| } |
| } |
| |
| /** |
| * Throw SSLPeerUnverifiedException on null or empty peerCertificates array |
| */ |
| private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException { |
| if (peerCertificates == null || peerCertificates.length == 0) { |
| throw new SSLPeerUnverifiedException("No peer certificates"); |
| } |
| } |
| |
| private void notifyUnbound(Object value, String name) { |
| if (value instanceof SSLSessionBindingListener) { |
| ((SSLSessionBindingListener) value) |
| .valueUnbound(new SSLSessionBindingEvent(this, name)); |
| } |
| } |
| } |