/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine 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. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
/*
* Copyright(C) 2006 Cameron Rich
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* A wrapper around the unmanaged interface to give a semi-decent Java API
*/
package totalcross.net.ssl;
import java.io.*;
import java.security.cert.*;
import javax.net.ssl.*;
import javax.security.auth.x500.*;
import sun.security.validator.*;
import totalcross.crypto.*;
import totalcross.io.IOException;
import totalcross.net.*;
import totalcross.util.*;
/**
* @defgroup java_api Java API.
*
* Ensure that the appropriate dispose() methods are called when finished with
* various objects - otherwise memory leaks will result.
*/
/**
* A representation of an SSL connection.
*/
public class SSL
{
Object ssl;
Socket socket;
TrustManager[] _trustMgrs; // skip consistency check
int status = Constants.SSL_NOT_OK;
Exception lastException;
static private final Hashtable cache = new Hashtable(17);
static void cachePutSSL(Socket s, SSL ssl)
{
synchronized(cache)
{
if (ssl == null)
cache.remove(s);
else
cache.put(s, ssl);
}
}
static SSL cacheGetSSL(Socket s)
{
synchronized(cache)
{
return (SSL)cache.get(s);
}
}
/**
* Store the reference to an SSL context.
* @param ssl A reference to an SSL object.
*/
protected SSL(Object ssl, Socket socket)
{
this.ssl = ssl;
this.socket = socket;
((javax.net.ssl.SSLSocket)ssl).addHandshakeCompletedListener(new HandshakeCompletedListener() {
public void handshakeCompleted(HandshakeCompletedEvent arg)
{
status = Constants.SSL_OK;
}
});
cachePutSSL(socket, this);
}
/**
* Free any used resources on this connection.
*
* A "Close Notify" message is sent on this connection (if possible). It
* is up to the application to close the socket.
* @throws IOException
*/
final public void dispose() throws IOException
{
try
{
((javax.net.ssl.SSLSocket)ssl).close();
cachePutSSL(socket, null);
}
catch (java.io.IOException e)
{
throw new IOException(e.getMessage());
}
}
/**
* Return the result of a handshake.
* @return SSL_OK if the handshake is complete and ok.
*/
final public int handshakeStatus()
{
return status;
}
/**
* Return the SSL cipher id.
* @return The cipher id which is one of:
* - TLS_RSA_WITH_AES_128_CBC_SHA (0x2f)
* - TLS_RSA_WITH_AES_256_CBC_SHA (0x35)
* - TLS_RSA_WITH_RC4_128_SHA (0x05)
* - TLS_RSA_WITH_RC4_128_MD5 (0x04)
*/
final public byte getCipherId()
{
if (ssl != null)
{
String cs = ((javax.net.ssl.SSLSocket)ssl).getSession().getCipherSuite();
if (cs.equals("TLS_RSA_WITH_AES_128_CBC_SHA")) return Constants.TLS_RSA_WITH_AES_128_CBC_SHA;
else if (cs.equals("TLS_RSA_WITH_AES_256_CBC_SHA")) return Constants.TLS_RSA_WITH_AES_256_CBC_SHA;
else if (cs.equals("TLS_RSA_WITH_RC4_128_SHA")) return Constants.TLS_RSA_WITH_RC4_128_SHA;
else if (cs.equals("TLS_RSA_WITH_RC4_128_MD5")) return Constants.TLS_RSA_WITH_RC4_128_MD5;
}
return -1;
}
/**
* Get the session id for a handshake.
*
* This will be a 32 byte sequence and is available after the first
* handshaking messages are sent.
* A SSLv23 handshake may have only 16 valid bytes.
* @return The session id as a 32 byte sequence.
*/
final public byte[] getSessionId()
{
if (ssl != null)
return ((javax.net.ssl.SSLSocket)ssl).getSession().getId();
return null;
}
/**
* Retrieve an X.509 distinguished name component.
*
* When a handshake is complete and a certificate has been exchanged, then
* the details of the remote certificate can be retrieved.
*
* This will usually be used by a client to check that the server's common
* name matches the URL.
*
* A full handshake needs to occur for this call to work.
*
* @param component [in] one of:
* - SSL_X509_CERT_COMMON_NAME
* - SSL_X509_CERT_ORGANIZATION
* - SSL_X509_CERT_ORGANIZATIONAL_NAME
* - SSL_X509_CA_CERT_COMMON_NAME
* - SSL_X509_CA_CERT_ORGANIZATION
* - SSL_X509_CA_CERT_ORGANIZATIONAL_NAME
* @return The appropriate string (or null if not defined)
* @throws CryptoException
*/
final public String getCertificateDN(int component) throws CryptoException
{
if (ssl != null)
{
SSLSession session = ((javax.net.ssl.SSLSocket) ssl).getSession();
java.security.cert.Certificate chain[];
try
{
chain = session.getPeerCertificates();
}
catch (javax.net.ssl.SSLPeerUnverifiedException e)
{
throw new CryptoException(e.getMessage());
}
if (chain == null || chain.length == 0 || chain[0] == null)
return null;
X500Principal principal = null;
switch (component)
{
// issuer X500
case Constants.SSL_X509_CA_CERT_COMMON_NAME:
case Constants.SSL_X509_CA_CERT_ORGANIZATION:
case Constants.SSL_X509_CA_CERT_ORGANIZATIONAL_NAME:
principal = ((java.security.cert.X509Certificate) chain[0]).getIssuerX500Principal();
break;
// subject X500
case Constants.SSL_X509_CERT_COMMON_NAME:
case Constants.SSL_X509_CERT_ORGANIZATION:
case Constants.SSL_X509_CERT_ORGANIZATIONAL_NAME:
principal = ((java.security.cert.X509Certificate) chain[0]).getSubjectX500Principal();
break;
}
if (principal == null)
return null;
String x500 = principal.getName(X500Principal.RFC2253);
if (x500 == null || x500.length() == 0)
return null;
int s;
switch (component)
{
case Constants.SSL_X509_CA_CERT_COMMON_NAME:
case Constants.SSL_X509_CERT_COMMON_NAME:
s = x500.indexOf("CN=");
if (s >= 0)
return x500.substring(s + 3, x500.indexOf(',', s));
break;
case Constants.SSL_X509_CA_CERT_ORGANIZATION:
case Constants.SSL_X509_CERT_ORGANIZATION:
s = x500.indexOf("O=");
if (s >= 0)
{
int lc = x500.indexOf(',', s);
s += 2;
if (lc >= s)
return x500.substring(s, lc);
else
return x500.substring(s);
}
break;
case Constants.SSL_X509_CA_CERT_ORGANIZATIONAL_NAME:
case Constants.SSL_X509_CERT_ORGANIZATIONAL_NAME:
s = x500.indexOf("OU=");
if (s >= 0)
return x500.substring(s + 3, x500.indexOf(',', s));
break;
}
}
return null;
}
/**
* Read the SSL data stream.
* Use rh before doing any successive ssl calls.
* @param rh [out] After a successful read, the decrypted data can be
* retrieved with rh.getData(). It will be null otherwise.
* @return The number of decrypted bytes:
* - if > 0, then the handshaking is complete and we are returning the
* number of decrypted bytes.
* - SSL_OK if the handshaking stage is successful (but not yet complete).
* - < 0 if an error.
* @throws SocketTimeoutException
* @throws IOException
*/
final public int read(SSLReadHolder rh) throws SocketTimeoutException, IOException
{
if (ssl != null)
{
try
{
javax.net.ssl.SSLSocket sslSocket = (javax.net.ssl.SSLSocket) ssl;
sslSocket.setSoTimeout(socket.readTimeout);
InputStream is = sslSocket.getInputStream();
int r = is.read(); // first, read one byte using the timeout
if (r != -1)
{
int count = is.available();
byte[] buf = rh.m_buf = new byte[count + 1];
buf[0] = (byte) r;
if (count > 0)
is.read(buf, 1, count);
return count + 1;
}
}
catch (java.io.IOException e)
{
throw new IOException(e.getMessage());
}
}
return -1;
}
/**
* Write to the SSL data stream.
* @param out_data [in] The data to be written
* @return The number of bytes sent, or if < 0 if an error.
* @throws IOException
*/
final public int write(byte[] out_data) throws IOException
{
return write(out_data, out_data.length);
}
/**
* Write to the SSL data stream.
* @param out_data [in] The data to be written
* @param out_len [in] The number of bytes to be written
* @return The number of bytes sent, or if < 0 if an error.
* @throws IOException
*/
final public int write(byte[] out_data, int out_len) throws IOException
{
if (ssl != null)
{
try
{
((javax.net.ssl.SSLSocket)ssl).getOutputStream().write(out_data, 0, out_len);
return out_len;
}
catch (java.io.IOException e)
{
throw new IOException(e.getMessage());
}
}
return -1;
}
/**
* Authenticate a received certificate.
* This call is usually made by a client after a handshake is complete
* and the context is in SSL_SERVER_VERIFY_LATER mode.
* @return SSL_OK if the certificate is verified.
* @throws CryptoException
*/
final public int verifyCertificate() throws CryptoException
{
for (int i = 0; i < _trustMgrs.length; i++)
{
try
{
Certificate[] certs = ((javax.net.ssl.SSLSocket)ssl).getSession().getPeerCertificates();
java.security.cert.X509Certificate[] _certs = new java.security.cert.X509Certificate[certs.length];
for (int c = 0; c < certs.length; c++)
_certs[c] = (java.security.cert.X509Certificate)certs[c];
javax.net.ssl.X509TrustManager x509_tm = (javax.net.ssl.X509TrustManager)_trustMgrs[i];
x509_tm.checkServerTrusted(_certs, "RSA");
// found one trust manager that could verify the peer certificate chain
return Constants.SSL_OK;
}
catch (SSLPeerUnverifiedException ex) { /* no remote certificate */ }
catch (ValidatorException ex) { /* remote certificate is not trusted! */ }
catch (CertificateException ex)
{
throw new CryptoException(ex.getMessage());
}
}
return Constants.X509_VFY_ERROR_NO_TRUSTED_CERT;
}
/**
* Force the client to perform its handshake again.
* For a client this involves sending another "client hello" message.
* For the server is means sending a "hello request" message.
* This is a blocking call on the client (until the handshake completes).
* @return SSL_OK if renegotiation instantiation was ok
* @throws IOException
*/
final public int renegotiate() throws IOException
{
if (ssl != null)
{
try
{
status = Constants.SSL_HANDSHAKE_IN_PROGRESS;
((javax.net.ssl.SSLSocket)ssl).startHandshake();
}
catch (java.io.IOException e)
{
throw new IOException(e.getMessage());
}
}
return Constants.SSL_OK;
}
/**
* @return the last exception occurred in this SSL connection.
* @since TotalCross 1.20
*/
final public Exception getLastException()
{
return lastException;
}
}