diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | fdb2704414a9ed92394ada0d1395e4db86889465 (patch) | |
tree | 9b591a4a50054274a197f02b3ccb51313681879f /x-net/src/main | |
download | libcore-fdb2704414a9ed92394ada0d1395e4db86889465.zip libcore-fdb2704414a9ed92394ada0d1395e4db86889465.tar.gz libcore-fdb2704414a9ed92394ada0d1395e4db86889465.tar.bz2 |
Initial Contribution
Diffstat (limited to 'x-net/src/main')
112 files changed, 20123 insertions, 0 deletions
diff --git a/x-net/src/main/java/javax/net/DefaultServerSocketFactory.java b/x-net/src/main/java/javax/net/DefaultServerSocketFactory.java new file mode 100644 index 0000000..2bf3a22 --- /dev/null +++ b/x-net/src/main/java/javax/net/DefaultServerSocketFactory.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; + +/** + * Default implementation of javax.net.ServerSocketFactory + * + */ +class DefaultServerSocketFactory extends ServerSocketFactory { + + public ServerSocket createServerSocket(int port) throws IOException { + return new ServerSocket(port); + } + + public ServerSocket createServerSocket(int port, int backlog) + throws IOException { + return new ServerSocket(port, backlog); + } + + public ServerSocket createServerSocket(int port, int backlog, + InetAddress iAddress) throws IOException { + return new ServerSocket(port, backlog, iAddress); + } + +} diff --git a/x-net/src/main/java/javax/net/DefaultSocketFactory.java b/x-net/src/main/java/javax/net/DefaultSocketFactory.java new file mode 100644 index 0000000..ead9651 --- /dev/null +++ b/x-net/src/main/java/javax/net/DefaultSocketFactory.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + * Default implementation of javax.net.SocketFactory + * + */ +class DefaultSocketFactory extends SocketFactory { + + public Socket createSocket() throws IOException { + return new Socket(); + } + + public Socket createSocket(String host, int port) throws IOException, + UnknownHostException { + return new Socket(host, port); + } + + public Socket createSocket(String host, int port, InetAddress localHost, + int localPort) throws IOException, UnknownHostException { + return new Socket(host, port, localHost, localPort); + } + + public Socket createSocket(InetAddress host, int port) throws IOException { + return new Socket(host, port); + } + + public Socket createSocket(InetAddress address, int port, + InetAddress localAddress, int localPort) throws IOException { + return new Socket(address, port, localAddress, localPort); + } +} diff --git a/x-net/src/main/java/javax/net/ServerSocketFactory.java b/x-net/src/main/java/javax/net/ServerSocketFactory.java new file mode 100644 index 0000000..c2b8bfd --- /dev/null +++ b/x-net/src/main/java/javax/net/ServerSocketFactory.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.SocketException; + +/** + * @com.intel.drl.spec_ref + * + */ + +public abstract class ServerSocketFactory { + static ServerSocketFactory defaultFactory; + protected ServerSocketFactory() { + } + + public static synchronized ServerSocketFactory getDefault() { + if (defaultFactory == null) { + defaultFactory = new DefaultServerSocketFactory(); + } + return defaultFactory; + } + + public ServerSocket createServerSocket() throws IOException { + // follow RI's behavior + throw new SocketException("Unbound server sockets not implemented"); + } + + public abstract ServerSocket createServerSocket(int port) + throws IOException; + + public abstract ServerSocket createServerSocket(int port, int backlog) + throws IOException; + + public abstract ServerSocket createServerSocket(int port, int backlog, + InetAddress iAddress) throws IOException; + +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/SocketFactory.java b/x-net/src/main/java/javax/net/SocketFactory.java new file mode 100644 index 0000000..1f8fa87 --- /dev/null +++ b/x-net/src/main/java/javax/net/SocketFactory.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; + +/** + * @com.intel.drl.spec_ref + * + */ +public abstract class SocketFactory { + + static SocketFactory defaultFactory; + + protected SocketFactory() { + } + + public static synchronized SocketFactory getDefault() { + if (defaultFactory == null) { + defaultFactory = new DefaultSocketFactory(); + } + return defaultFactory; + } + + public Socket createSocket() throws IOException { + // follow RI's behavior + throw new SocketException("Unconnected sockets not implemented"); + } + + public abstract Socket createSocket(String host, int port) + throws IOException, UnknownHostException; + + public abstract Socket createSocket(String host, int port, + InetAddress localHost, int localPort) throws IOException, + UnknownHostException; + + public abstract Socket createSocket(InetAddress host, int port) + throws IOException; + + public abstract Socket createSocket(InetAddress address, int port, + InetAddress localAddress, int localPort) throws IOException; +} diff --git a/x-net/src/main/java/javax/net/ssl/CertPathTrustManagerParameters.java b/x-net/src/main/java/javax/net/ssl/CertPathTrustManagerParameters.java new file mode 100644 index 0000000..9a37270 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/CertPathTrustManagerParameters.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.cert.CertPathParameters; + +/** + * @com.intel.drl.spec_ref + * + */ +public class CertPathTrustManagerParameters implements ManagerFactoryParameters { + + private CertPathParameters param; + + public CertPathTrustManagerParameters(CertPathParameters parameters) { + param = (CertPathParameters) parameters.clone(); + } + + public CertPathParameters getParameters() { + return (CertPathParameters) param.clone(); + } + +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/ContextImpl.java b/x-net/src/main/java/javax/net/ssl/ContextImpl.java new file mode 100644 index 0000000..5787266 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/ContextImpl.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris V. Kuznetsov + * @version $Revision$ + */ + +package javax.net.ssl; + +import java.security.Provider; + +/** + * Support class for this package. + * + */ + +class ContextImpl extends SSLContext { + public ContextImpl(SSLContextSpi contextSpi, Provider provider, + String protocol) { + super(contextSpi, provider, protocol); + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java b/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java new file mode 100644 index 0000000..373e792 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +/** + * Default implementation of javax.net.ssl.HostnameVerifier + * + */ +class DefaultHostnameVerifier implements HostnameVerifier { + + /** + * DefaultHostnameVerifier assumes the connection should not be permitted + * + * @param hostname + * @param session + */ + public boolean verify(String hostname, SSLSession session) { + return false; + } +} diff --git a/x-net/src/main/java/javax/net/ssl/DefaultSSLContext.java b/x-net/src/main/java/javax/net/ssl/DefaultSSLContext.java new file mode 100644 index 0000000..cc029f4 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/DefaultSSLContext.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris V. Kuznetsov + * @version $Revision$ + */ + +package javax.net.ssl; + +import java.io.FileInputStream; +import java.security.AccessController; +import java.security.Provider; +import java.security.Security; +import java.security.KeyStore; +import java.util.Iterator; + +import org.apache.harmony.security.fortress.Engine; +import org.apache.harmony.security.fortress.Services; + + +/** + * Support class for this package. + * + */ + +class DefaultSSLContext { + private static SSLContext defaultSSLContext; + + public static SSLContext getContext() { + if (defaultSSLContext == null) { + defaultSSLContext = AccessController + .doPrivileged(new java.security.PrivilegedAction<SSLContext>() { + public SSLContext run() { + return findDefault(); + } + }); + } + return defaultSSLContext; + } + + private static SSLContext findDefault() { + // FIXME EXPORT CONTROL + Provider.Service service; + for (Iterator it1 = Services.getProvidersList().iterator(); it1 + .hasNext();) { + service = Engine.door.getService((Provider) it1.next(), + "SSLContext"); + if (service != null) { + try { + SSLContext con = new ContextImpl( + (SSLContextSpi) service.newInstance(null), + service.getProvider(), + service.getAlgorithm()); + + //TODO javax.net.ssl.keyStoreProvider, javax.net.ssl.trustStoreProvider system property + // find KeyStore, KeyManagers + KeyManager[] keyManagers = null; + KeyStore ks = KeyStore.getInstance(KeyStore + .getDefaultType()); + String keystore = System + .getProperty("javax.net.ssl.keyStore"); + String keystorepwd = System + .getProperty("javax.net.ssl.keyStorePassword"); + char[] pwd = null; + if (keystorepwd != null) { + pwd = keystorepwd.toCharArray(); + } + if (keystore != null) { + FileInputStream fis = new java.io.FileInputStream( + keystore); + ks.load(fis, pwd); + fis.close(); + + KeyManagerFactory kmf; + String kmfAlg = Security + .getProperty("ssl.KeyManagerFactory.algorithm"); + if (kmfAlg == null) { + kmfAlg = "SunX509"; + } + kmf = KeyManagerFactory.getInstance(kmfAlg); + kmf.init(ks, pwd); + keyManagers = kmf.getKeyManagers(); + } + + // find TrustStore, TrustManagers + TrustManager[] trustManagers = null; + keystore = System.getProperty("javax.net.ssl.trustStore"); + keystorepwd = System + .getProperty("javax.net.ssl.trustStorePassword"); + pwd = null; + if (keystorepwd != null) { + pwd = keystorepwd.toCharArray(); + } + //TODO Defaults: jssecacerts; cacerts + if (keystore != null) { + FileInputStream fis = new java.io.FileInputStream( + keystore); + ks.load(fis, pwd); + fis.close(); + TrustManagerFactory tmf; + String tmfAlg = Security + .getProperty("ssl.TrustManagerFactory.algorithm"); + if (tmfAlg == null) { + tmfAlg = "PKIX"; + } + tmf = TrustManagerFactory.getInstance(tmfAlg); + tmf.init(ks); + trustManagers = tmf.getTrustManagers(); + } + + con.init(keyManagers, trustManagers, null); + return con; + } catch (Exception e) { + // e.printStackTrace(); + // ignore and try another + } + } + } + return null; + } +} diff --git a/x-net/src/main/java/javax/net/ssl/DefaultSSLServerSocketFactory.java b/x-net/src/main/java/javax/net/ssl/DefaultSSLServerSocketFactory.java new file mode 100644 index 0000000..f382072 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/DefaultSSLServerSocketFactory.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.SocketException; + +/** + * Default inoperative implementation of javax.net.ssl.SSLServerSocketFactory + * + */ +class DefaultSSLServerSocketFactory extends SSLServerSocketFactory { + + private String errMessage; + + public String[] getDefaultCipherSuites() { + return new String[0]; + } + + public String[] getSupportedCipherSuites() { + return new String[0]; + } + + public ServerSocket createServerSocket(int port) throws IOException { + throw new SocketException(errMessage); + } + + + public ServerSocket createServerSocket(int port, int backlog) + throws IOException { + throw new SocketException(errMessage); + } + + public ServerSocket createServerSocket(int port, int backlog, + InetAddress iAddress) throws IOException { + throw new SocketException(errMessage); + } + + DefaultSSLServerSocketFactory(String mes) { + errMessage = mes; + } + +} diff --git a/x-net/src/main/java/javax/net/ssl/DefaultSSLSocketFactory.java b/x-net/src/main/java/javax/net/ssl/DefaultSSLSocketFactory.java new file mode 100644 index 0000000..bda575e --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/DefaultSSLSocketFactory.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; + +/** + * Default inoperative implementation of javax.net.ssl.SSLSocketFactory + * + */ +class DefaultSSLSocketFactory extends SSLSocketFactory { + + private String errMessage; + + public String[] getDefaultCipherSuites() { + return new String[0]; + } + + public String[] getSupportedCipherSuites() { + return new String[0]; + } + + /** + * @see javax.net.ssl.SSLSocketFactory#createSocket(java.net.Socket, java.lang.String, int, boolean) + */ + public Socket createSocket(Socket s, String host, int port, + boolean autoClose) throws IOException { + throw new SocketException(errMessage); + } + + /** + * @see javax.net.SocketFactory#createSocket(java.lang.String, int) + */ + public Socket createSocket(String host, int port) throws IOException, + UnknownHostException { + throw new SocketException(errMessage); + } + + /** + * @see javax.net.SocketFactory#createSocket(java.lang.String, int, java.net.InetAddress, int) + */ + public Socket createSocket(String host, int port, InetAddress localHost, + int localPort) throws IOException, UnknownHostException { + throw new SocketException(errMessage); + } + + /** + * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int) + */ + public Socket createSocket(InetAddress host, int port) throws IOException { + throw new SocketException(errMessage); + } + + /** + * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int, java.net.InetAddress, int) + */ + public Socket createSocket(InetAddress address, int port, + InetAddress localAddress, int localPort) throws IOException { + throw new SocketException(errMessage); + } + + DefaultSSLSocketFactory(String mes) { + errMessage = mes; + } + +} diff --git a/x-net/src/main/java/javax/net/ssl/HandshakeCompletedEvent.java b/x-net/src/main/java/javax/net/ssl/HandshakeCompletedEvent.java new file mode 100644 index 0000000..a783a30 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/HandshakeCompletedEvent.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.io.Serializable; +import java.security.Principal; +import java.security.cert.Certificate; +import javax.security.cert.X509Certificate; +import java.util.EventObject; + +/** + * @com.intel.drl.spec_ref + * + */ +public class HandshakeCompletedEvent extends EventObject implements + Serializable { + + /** + * @serial + * The 5.0 spec. doesn't declare this serialVersionUID field + * In order to be compatible it is explicitly declared here + */ + private static final long serialVersionUID = 7914963744257769778L; + + private transient SSLSession session; + + public HandshakeCompletedEvent(SSLSocket sock, SSLSession s) { + super(sock); + session = s; + } + + public SSLSession getSession() { + return session; + } + + public String getCipherSuite() { + return session.getCipherSuite(); + } + + public Certificate[] getLocalCertificates() { + return session.getLocalCertificates(); + } + + public Certificate[] getPeerCertificates() + throws SSLPeerUnverifiedException { + return session.getPeerCertificates(); + } + + public X509Certificate[] getPeerCertificateChain() + throws SSLPeerUnverifiedException { + return session.getPeerCertificateChain(); + } + + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + return session.getPeerPrincipal(); + } + + public Principal getLocalPrincipal() { + return session.getLocalPrincipal(); + } + + public SSLSocket getSocket() { + return (SSLSocket)this.source; + } + +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/HandshakeCompletedListener.java b/x-net/src/main/java/javax/net/ssl/HandshakeCompletedListener.java new file mode 100644 index 0000000..55b8894 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/HandshakeCompletedListener.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.util.EventListener; + +/** + * @com.intel.drl.spec_ref + * + */ +public interface HandshakeCompletedListener extends EventListener { + /** + * @com.intel.drl.spec_ref + */ + public void handshakeCompleted(HandshakeCompletedEvent event); +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/HostnameVerifier.java b/x-net/src/main/java/javax/net/ssl/HostnameVerifier.java new file mode 100644 index 0000000..609c861 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/HostnameVerifier.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +/** + * @com.intel.drl.spec_ref + * + */ +public interface HostnameVerifier { + + /** + * @com.intel.drl.spec_ref + */ + public boolean verify(String hostname, SSLSession session); +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/HttpsURLConnection.java b/x-net/src/main/java/javax/net/ssl/HttpsURLConnection.java new file mode 100644 index 0000000..5d983b6 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/HttpsURLConnection.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.Principal; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +/** + * @com.intel.drl.spec_ref + * + */ +public abstract class HttpsURLConnection extends HttpURLConnection { + + private static HostnameVerifier defaultHostnameVerifier = new DefaultHostnameVerifier(); + + private static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory) SSLSocketFactory + .getDefault(); + + protected HostnameVerifier hostnameVerifier; + + private static SSLSocketFactory socketFactory; + + protected HttpsURLConnection(URL url) { + super(url); + hostnameVerifier = defaultHostnameVerifier; + socketFactory = defaultSSLSocketFactory; + } + + public abstract String getCipherSuite(); + + public abstract Certificate[] getLocalCertificates(); + + public abstract Certificate[] getServerCertificates() + throws SSLPeerUnverifiedException; + + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + Certificate[] certs = getServerCertificates(); + if (certs == null || certs.length == 0 || + (!(certs[0] instanceof X509Certificate))) { + throw new SSLPeerUnverifiedException( + "No server's end-entity certificate"); + } + return ((X509Certificate) certs[0]).getSubjectX500Principal(); + } + + public Principal getLocalPrincipal() { + Certificate[] certs = getLocalCertificates(); + if (certs == null || certs.length == 0 + || (!(certs[0] instanceof X509Certificate))) { + return null; + } + return ((X509Certificate) certs[0]).getSubjectX500Principal(); + } + + public static void setDefaultHostnameVerifier(HostnameVerifier v) { + if (v == null) { + throw new IllegalArgumentException("HostnameVerifier is null"); + } + defaultHostnameVerifier = v; + } + + public static HostnameVerifier getDefaultHostnameVerifier() { + return defaultHostnameVerifier; + } + + public void setHostnameVerifier(HostnameVerifier v) { + if (v == null) { + throw new IllegalArgumentException("HostnameVerifier is null"); + } + hostnameVerifier = v; + } + + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + + public static void setDefaultSSLSocketFactory(SSLSocketFactory sf) { + if (sf == null) { + throw new IllegalArgumentException("SSLSocketFactory is null"); + } + defaultSSLSocketFactory = sf; + } + + public static SSLSocketFactory getDefaultSSLSocketFactory() { + return defaultSSLSocketFactory; + } + + public void setSSLSocketFactory(SSLSocketFactory sf) { + if (sf == null) { + throw new IllegalArgumentException("SSLSocketFactory is null"); + } + socketFactory = sf; + } + + public SSLSocketFactory getSSLSocketFactory() { + return socketFactory; + } + +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/KeyManager.java b/x-net/src/main/java/javax/net/ssl/KeyManager.java new file mode 100644 index 0000000..6a8bcab --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/KeyManager.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +/** + * @com.intel.drl.spec_ref + * + */ +public interface KeyManager { +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/KeyManagerFactory.java b/x-net/src/main/java/javax/net/ssl/KeyManagerFactory.java new file mode 100644 index 0000000..9c0fddd --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/KeyManagerFactory.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.AccessController; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.Security; +import java.security.UnrecoverableKeyException; + +import org.apache.harmony.security.fortress.Engine; + + +/** + * @com.intel.drl.spec_ref + * + */ + +public class KeyManagerFactory { + // Store KeyManagerFactory service name + private static final String SERVICE = "KeyManagerFactory"; + + // Used to access common engine functionality + private static Engine engine = new Engine(SERVICE); + + // Store default property name + private static final String PROPERTY_NAME = "ssl.KeyManagerFactory.algorithm"; + + // Store used provider + private final Provider provider; + + // Store used KeyManagerFactorySpi implementation + private final KeyManagerFactorySpi spiImpl; + + // Store used algorithm + private final String algorithm; + + /** + * @com.intel.drl.spec_ref + * + */ + protected KeyManagerFactory(KeyManagerFactorySpi factorySpi, + Provider provider, String algorithm) { + this.provider = provider; + this.algorithm = algorithm; + this.spiImpl = factorySpi; + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final String getAlgorithm() { + return algorithm; + } + + /** + * @com.intel.drl.spec_ref + * + * throws NullPointerException if algorithm is null (instead of + * NoSuchAlgorithmException as in 1.4 release) + */ + public static final KeyManagerFactory getInstance(String algorithm) + throws NoSuchAlgorithmException { + if (algorithm == null) { + throw new NullPointerException("algorith is null"); + } + synchronized (engine) { + engine.getInstance(algorithm, null); + return new KeyManagerFactory((KeyManagerFactorySpi) engine.spi, + engine.provider, algorithm); + } + } + + /** + * @com.intel.drl.spec_ref + * + * throws NullPointerException if algorithm is null (instead of + * NoSuchAlgorithmException as in 1.4 release) + */ + public static final KeyManagerFactory getInstance(String algorithm, + String provider) throws NoSuchAlgorithmException, + NoSuchProviderException { + if ((provider == null) || (provider.length() == 0)) { + throw new IllegalArgumentException("Provider is null or empty"); + } + Provider impProvider = Security.getProvider(provider); + if (impProvider == null) { + throw new NoSuchProviderException(provider); + } + return getInstance(algorithm, impProvider); + } + + /** + * @com.intel.drl.spec_ref + * + * throws NullPointerException if algorithm is null (instead of + * NoSuchAlgorithmException as in 1.4 release) + */ + public static final KeyManagerFactory getInstance(String algorithm, + Provider provider) throws NoSuchAlgorithmException { + if (provider == null) { + throw new IllegalArgumentException("Provider is null"); + } + if (algorithm == null) { + throw new NullPointerException("algorith is null"); + } + synchronized (engine) { + engine.getInstance(algorithm, provider, null); + return new KeyManagerFactory((KeyManagerFactorySpi) engine.spi, + provider, algorithm); + } + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final Provider getProvider() { + return provider; + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final void init(KeyStore ks, char[] password) + throws KeyStoreException, NoSuchAlgorithmException, + UnrecoverableKeyException { + spiImpl.engineInit(ks, password); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final void init(ManagerFactoryParameters spec) + throws InvalidAlgorithmParameterException { + spiImpl.engineInit(spec); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final KeyManager[] getKeyManagers() { + return spiImpl.engineGetKeyManagers(); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public static final String getDefaultAlgorithm() { + return AccessController + .doPrivileged(new java.security.PrivilegedAction<String>() { + public String run() { + return Security.getProperty(PROPERTY_NAME); + } + }); + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/KeyManagerFactorySpi.java b/x-net/src/main/java/javax/net/ssl/KeyManagerFactorySpi.java new file mode 100644 index 0000000..ae6a10c --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/KeyManagerFactorySpi.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +/** + * @com.intel.drl.spec_ref + * + */ + +public abstract class KeyManagerFactorySpi { + /** + * @com.intel.drl.spec_ref + * + */ + public KeyManagerFactorySpi() { + } + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract void engineInit(KeyStore ks, char[] password) + throws KeyStoreException, NoSuchAlgorithmException, + UnrecoverableKeyException; + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract void engineInit(ManagerFactoryParameters spec) + throws InvalidAlgorithmParameterException; + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract KeyManager[] engineGetKeyManagers(); +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/KeyStoreBuilderParameters.java b/x-net/src/main/java/javax/net/ssl/KeyStoreBuilderParameters.java new file mode 100644 index 0000000..dc6d05a --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/KeyStoreBuilderParameters.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 javax.net.ssl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.security.KeyStore; + +/** + * @com.intel.drl.spec_ref + * + */ +public class KeyStoreBuilderParameters implements ManagerFactoryParameters { + + private List ksbuilders; + + public KeyStoreBuilderParameters(KeyStore.Builder builder) { + ksbuilders = new ArrayList(); + if (builder != null) { + ksbuilders.add(builder); + } + } + + public KeyStoreBuilderParameters(List parameters) { + if (parameters == null) { + throw new NullPointerException("Builders list is null"); + } + if (parameters.isEmpty()) { + throw new IllegalArgumentException("Builders list is empty"); + } + ksbuilders = new ArrayList(parameters); + } + + public List getParameters() { + return Collections.unmodifiableList(ksbuilders); + } +} diff --git a/x-net/src/main/java/javax/net/ssl/ManagerFactoryParameters.java b/x-net/src/main/java/javax/net/ssl/ManagerFactoryParameters.java new file mode 100644 index 0000000..6c6121a --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/ManagerFactoryParameters.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +/** + * @com.intel.drl.spec_ref + * + */ +public interface ManagerFactoryParameters { +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLContext.java b/x-net/src/main/java/javax/net/ssl/SSLContext.java new file mode 100644 index 0000000..2edcfb0 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLContext.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.Security; + +import org.apache.harmony.security.fortress.Engine; + + +/** + * @com.intel.drl.spec_ref + * + */ + +public class SSLContext { + // StoreSSLContext service name + private static final String SERVICE = "SSLContext"; + + // Used to access common engine functionality + private static Engine engine = new Engine(SERVICE); + + // Storeused provider + private final Provider provider; + + // Storeused SSLContextSpi implementation + private final SSLContextSpi spiImpl; + + // Storeused protocol + private final String protocol; + + /* + * @com.intel.drl.spec_ref + * + */ + protected SSLContext(SSLContextSpi contextSpi, Provider provider, + String protocol) { + this.provider = provider; + this.protocol = protocol; + this.spiImpl = contextSpi; + } + + /** + * @com.intel.drl.spec_ref + * + * throws NullPointerException if protocol is null (instead of + * NoSuchAlgorithmException as in 1.4 release) + */ + public static SSLContext getInstance(String protocol) + throws NoSuchAlgorithmException { + if (protocol == null) { + throw new NullPointerException("protocol is null"); + } + synchronized (engine) { + engine.getInstance(protocol, null); + return new SSLContext((SSLContextSpi) engine.spi, engine.provider, + protocol); + } + } + + /** + * @com.intel.drl.spec_ref + * + * throws NullPointerException if protocol is null (instead of + * NoSuchAlgorithmException as in 1.4 release) + */ + public static SSLContext getInstance(String protocol, String provider) + throws NoSuchAlgorithmException, NoSuchProviderException { + if (provider == null) { + throw new IllegalArgumentException("Provider is null"); + } + if (provider.length() == 0) { + throw new IllegalArgumentException("Provider is empty"); + } + Provider impProvider = Security.getProvider(provider); + if (impProvider == null) { + throw new NoSuchProviderException(provider); + } + return getInstance(protocol, impProvider); + } + + /** + * @com.intel.drl.spec_ref + * + * throws NullPointerException if protocol is null (instead of + * NoSuchAlgorithmException as in 1.4 release) + */ + public static SSLContext getInstance(String protocol, Provider provider) + throws NoSuchAlgorithmException { + if (provider == null) { + throw new IllegalArgumentException("provider is null"); + } + if (protocol == null) { + throw new NullPointerException("protocol is null"); + } + synchronized (engine) { + engine.getInstance(protocol, provider, null); + return new SSLContext((SSLContextSpi) engine.spi, provider, protocol); + } + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final String getProtocol() { + return protocol; + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final Provider getProvider() { + return provider; + } + + /** + * @com.intel.drl.spec_ref + * + * FIXME: check what exception will be thrown when parameters are null + */ + public final void init(KeyManager[] km, TrustManager[] tm, SecureRandom sr) + throws KeyManagementException { + spiImpl.engineInit(km, tm, sr); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final SSLSocketFactory getSocketFactory() { + return spiImpl.engineGetSocketFactory(); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final SSLServerSocketFactory getServerSocketFactory() { + return spiImpl.engineGetServerSocketFactory(); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final SSLEngine createSSLEngine() { + return spiImpl.engineCreateSSLEngine(); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final SSLEngine createSSLEngine(String peerHost, int peerPort) { + return spiImpl.engineCreateSSLEngine(peerHost, peerPort); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final SSLSessionContext getServerSessionContext() { + return spiImpl.engineGetServerSessionContext(); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final SSLSessionContext getClientSessionContext() { + return spiImpl.engineGetClientSessionContext(); + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLContextSpi.java b/x-net/src/main/java/javax/net/ssl/SSLContextSpi.java new file mode 100644 index 0000000..fdbd336 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLContextSpi.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.KeyManagementException; +import java.security.SecureRandom; + +/** + * @com.intel.drl.spec_ref + * + */ + +public abstract class SSLContextSpi { + /** + * @com.intel.drl.spec_ref + * + */ + public SSLContextSpi() { + } + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract void engineInit(KeyManager[] km, TrustManager[] tm, + SecureRandom sr) throws KeyManagementException; + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract SSLSocketFactory engineGetSocketFactory(); + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract SSLServerSocketFactory engineGetServerSocketFactory(); + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract SSLEngine engineCreateSSLEngine(String host, int port); + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract SSLEngine engineCreateSSLEngine(); + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract SSLSessionContext engineGetServerSessionContext(); + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract SSLSessionContext engineGetClientSessionContext(); + +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLEngine.java b/x-net/src/main/java/javax/net/ssl/SSLEngine.java new file mode 100644 index 0000000..af635c6 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLEngine.java @@ -0,0 +1,312 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; + +/** + * + * @com.intel.drl.spec_ref + * + * + */ +public abstract class SSLEngine { + // Store host value + private final String host; + + // Store port value + private final int port; + + /** + * @com.intel.drl.spec_ref + * + */ + protected SSLEngine() { + host = null; + port = -1; + } + + /** + * @com.intel.drl.spec_ref + * + */ + protected SSLEngine(String host, int port) { + this.host = host; + this.port = port; + } + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract void beginHandshake() throws SSLException; + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract void closeInbound() throws SSLException; + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract void closeOutbound(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract Runnable getDelegatedTask(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract String[] getEnabledCipherSuites(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract String[] getEnabledProtocols(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract boolean getEnableSessionCreation(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract SSLEngineResult.HandshakeStatus getHandshakeStatus(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract boolean getNeedClientAuth(); + + /** + * @com.intel.drl.spec_ref + * + */ + public String getPeerHost() { + return host; + } + + /** + * @com.intel.drl.spec_ref + * + */ + public int getPeerPort() { + return port; + } + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract SSLSession getSession(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract String[] getSupportedCipherSuites(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract String[] getSupportedProtocols(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract boolean getUseClientMode(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract boolean getWantClientAuth(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract boolean isInboundDone(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract boolean isOutboundDone(); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract void setEnabledCipherSuites(String[] suites); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract void setEnabledProtocols(String[] protocols); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract void setEnableSessionCreation(boolean flag); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract void setNeedClientAuth(boolean need); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract void setUseClientMode(boolean mode); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract void setWantClientAuth(boolean want); + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, + int offset, int length) throws SSLException; + + /** + * @com.intel.drl.spec_ref + * + */ + public abstract SSLEngineResult wrap(ByteBuffer[] srcs, int offset, + int length, ByteBuffer dst) throws SSLException; + + /** + * implementation behavior follows RI: + * jdk 1.5 does not throw IllegalArgumentException when parameters are null + * and does not throw ReadOnlyBufferException if dst is read only byte buffer + * + */ + public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) + throws SSLException { +// if (src == null) { +// throw new IllegalArgumentException("Byte buffer src is null"); +// } +// if (dst == null) { +// throw new IllegalArgumentException("Byte buffer dst is null"); +// } +// if (dst.isReadOnly()) { +// throw new ReadOnlyBufferException(); +// } + return unwrap(src, new ByteBuffer[] { dst }, 0, 1); + } + + /** + * implementation behavior follows RI: + * jdk 1.5 does not throw IllegalArgumentException when src is null or if + * dsts contains null elements + * It does not throw ReadOnlyBufferException when dsts contains read only elements + */ + public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts) + throws SSLException { +// if (src == null) { +// throw new IllegalArgumentException("Byte buffer src is null"); +// } + if (dsts == null) { + throw new IllegalArgumentException("Byte buffer array dsts is null"); + } +// for (int i = 0; i < dsts.length; i++) { +// if (dsts[i] == null) { +// throw new IllegalArgumentException("Byte buffer dsts[" + i +// + "] is null"); +// } +// if (dsts[i].isReadOnly()) { +// throw new ReadOnlyBufferException(); +// } +// } + return unwrap(src, dsts, 0, dsts.length); + } + + /** + * implementation behavior follows RI: jdk 1.5 does not throw + * IllegalArgumentException when dst is null or if srcs contains null + * elements It does not throw ReadOnlyBufferException for read only dst + * + */ + public SSLEngineResult wrap(ByteBuffer[] srcs, ByteBuffer dst) + throws SSLException { + if (srcs == null) { + throw new IllegalArgumentException("Byte buffer array srcs is null"); + } +// for (int i = 0; i < srcs.length; i++) { +// if (srcs[i] == null) { +// throw new IllegalArgumentException("Byte buffer srcs[" + i +// + "] is null"); +// } +// } +// if (dst == null) { +// throw new IllegalArgumentException("Byte buffer array dst is null"); +// } +// if (dst.isReadOnly()) { +// throw new ReadOnlyBufferException(); +// } + return wrap(srcs, 0, srcs.length, dst); + } + + /** + * implementation behavior follows RI: + * jdk 1.5 does not throw IllegalArgumentException when parameters are null + * and does not throw ReadOnlyBufferException if dst is read only byte buffer + * + */ + public SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) + throws SSLException { +// if (src == null) { +// throw new IllegalArgumentException("Byte buffer src is null"); +// } +// if (dst == null) { +// throw new IllegalArgumentException("Byte buffer dst is null"); +// } +// if (dst.isReadOnly()) { +// throw new ReadOnlyBufferException(); +// } + return wrap(new ByteBuffer[] { src }, 0, 1, dst); + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLEngineResult.java b/x-net/src/main/java/javax/net/ssl/SSLEngineResult.java new file mode 100644 index 0000000..5cfba1e --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLEngineResult.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +/** + * @com.intel.drl.spec_ref + * + */ +public class SSLEngineResult { + + // Store Status object + private final SSLEngineResult.Status status; + + // Store HandshakeStatus object + private final SSLEngineResult.HandshakeStatus handshakeStatus; + + // Store bytesConsumed + private final int bytesConsumed; + + // Store bytesProduced + private final int bytesProduced; + + /** + * @com.intel.drl.spec_ref + */ + public SSLEngineResult(SSLEngineResult.Status status, + SSLEngineResult.HandshakeStatus handshakeStatus, int bytesConsumed, + int bytesProduced) { + if (status == null) { + throw new IllegalArgumentException("status is null"); + } + if (handshakeStatus == null) { + throw new IllegalArgumentException("handshakeStatus is null"); + } + if (bytesConsumed < 0) { + throw new IllegalArgumentException("bytesConsumed is negative"); + } + if (bytesProduced < 0) { + throw new IllegalArgumentException("bytesProduced is negative"); + } + this.status = status; + this.handshakeStatus = handshakeStatus; + this.bytesConsumed = bytesConsumed; + this.bytesProduced = bytesProduced; + } + + /** + * @com.intel.drl.spec_ref + */ + public final Status getStatus() { + return status; + } + + /** + * @com.intel.drl.spec_ref + */ + public final HandshakeStatus getHandshakeStatus() { + return handshakeStatus; + } + + /** + * @com.intel.drl.spec_ref + */ + public final int bytesConsumed() { + return bytesConsumed; + } + + /** + * @com.intel.drl.spec_ref + */ + public final int bytesProduced() { + return bytesProduced; + } + + /** + * @com.intel.drl.spec_ref + */ + public String toString() { + StringBuffer sb = new StringBuffer("SSLEngineReport: Status = "); + sb.append(status.toString()); + sb.append(" HandshakeStatus = "); + sb.append(handshakeStatus.toString()); + sb.append("\n bytesConsumed = "); + sb.append(Integer.toString(bytesConsumed)); + sb.append(" bytesProduced = "); + sb.append(Integer.toString(bytesProduced)); + return sb.toString(); + } + + /** + * @com.intel.drl.spec_ref + */ + public enum HandshakeStatus { + NOT_HANDSHAKING, + FINISHED, + NEED_TASK, + NEED_WRAP, + NEED_UNWRAP + } + + /** + * @com.intel.drl.spec_ref + */ + public static enum Status { + BUFFER_OVERFLOW, + BUFFER_UNDERFLOW, + CLOSED, + OK + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLException.java b/x-net/src/main/java/javax/net/ssl/SSLException.java new file mode 100644 index 0000000..87f5caa --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLException.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.io.IOException; + +/** + * @com.intel.drl.spec_ref + * + */ +public class SSLException extends IOException { + /** + * @com.intel.drl.spec_ref + * @serial + */ + private static final long serialVersionUID = 4511006460650708967L; + + /** + * @com.intel.drl.spec_ref + * + */ + public SSLException(String reason) { + super(reason); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public SSLException(String message, Throwable cause) { + super(message); + super.initCause(cause); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public SSLException(Throwable cause) { + super(cause == null ? null : cause.toString()); + super.initCause(cause); + } +} diff --git a/x-net/src/main/java/javax/net/ssl/SSLHandshakeException.java b/x-net/src/main/java/javax/net/ssl/SSLHandshakeException.java new file mode 100644 index 0000000..a137e47 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLHandshakeException.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +/** + * @com.intel.drl.spec_ref + * + */ +public class SSLHandshakeException extends SSLException { + + private static final long serialVersionUID = -5045881315018326890L; + + /** + * @com.intel.drl.spec_ref + * + */ + public SSLHandshakeException(String reason) { + super(reason); + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLKeyException.java b/x-net/src/main/java/javax/net/ssl/SSLKeyException.java new file mode 100644 index 0000000..e00ae0a --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLKeyException.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +/** + * @com.intel.drl.spec_ref + * + */ +public class SSLKeyException extends SSLException { + + private static final long serialVersionUID = -8071664081941937874L; + + /** + * @com.intel.drl.spec_ref + * + */ + public SSLKeyException(String reason) { + super(reason); + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLPeerUnverifiedException.java b/x-net/src/main/java/javax/net/ssl/SSLPeerUnverifiedException.java new file mode 100644 index 0000000..c112454 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLPeerUnverifiedException.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +/** + * @com.intel.drl.spec_ref + * + */ +public class SSLPeerUnverifiedException extends SSLException { + + private static final long serialVersionUID = -8919512675000600547L; + + /** + * @com.intel.drl.spec_ref + * + */ + public SSLPeerUnverifiedException(String reason) { + super(reason); + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLPermission.java b/x-net/src/main/java/javax/net/ssl/SSLPermission.java new file mode 100644 index 0000000..eeea71e --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLPermission.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.BasicPermission; + +/** + * @com.intel.drl.spec_ref + * + */ +public final class SSLPermission extends BasicPermission { + + private static final long serialVersionUID = -3456898025505876775L; + + public SSLPermission(String name) { + super(name); + } + + public SSLPermission(String name, String actions) { + super(name, actions); + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLProtocolException.java b/x-net/src/main/java/javax/net/ssl/SSLProtocolException.java new file mode 100644 index 0000000..1ec9827 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLProtocolException.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +/** + * @com.intel.drl.spec_ref + * + */ +public class SSLProtocolException extends SSLException { + + private static final long serialVersionUID = 5445067063799134928L; + + /** + * @com.intel.drl.spec_ref + * + */ + public SSLProtocolException(String reason) { + super(reason); + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLServerSocket.java b/x-net/src/main/java/javax/net/ssl/SSLServerSocket.java new file mode 100644 index 0000000..b1b6f7a --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLServerSocket.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; + +/** + * @com.intel.drl.spec_ref + * + */ +public abstract class SSLServerSocket extends ServerSocket { + protected SSLServerSocket() throws IOException { + super(); + } + + protected SSLServerSocket(int port) throws IOException { + super(port); + } + + protected SSLServerSocket(int port, int backlog) throws IOException { + super(port, backlog); + } + + protected SSLServerSocket(int port, int backlog, InetAddress address) + throws IOException { + super(port, backlog, address); + } + + public abstract String[] getEnabledCipherSuites(); + public abstract void setEnabledCipherSuites(String[] suites); + public abstract String[] getSupportedCipherSuites(); + public abstract String[] getSupportedProtocols(); + public abstract String[] getEnabledProtocols(); + public abstract void setEnabledProtocols(String[] protocols); + public abstract void setNeedClientAuth(boolean need); + public abstract boolean getNeedClientAuth(); + public abstract void setWantClientAuth(boolean want); + public abstract boolean getWantClientAuth(); + public abstract void setUseClientMode(boolean mode); + public abstract boolean getUseClientMode(); + public abstract void setEnableSessionCreation(boolean flag); + public abstract boolean getEnableSessionCreation(); +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLServerSocketFactory.java b/x-net/src/main/java/javax/net/ssl/SSLServerSocketFactory.java new file mode 100644 index 0000000..1445794 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLServerSocketFactory.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.AccessController; +import java.security.Security; + +import javax.net.ServerSocketFactory; + +/** + * @com.intel.drl.spec_ref + * + */ +public abstract class SSLServerSocketFactory extends ServerSocketFactory { +// TODO EXPORT CONTROL + + // The default SSL socket factory + private static ServerSocketFactory defaultServerSocketFactory; + + private static String defaultName; + + protected SSLServerSocketFactory() { + super(); + } + + public static ServerSocketFactory getDefault() { + if (defaultServerSocketFactory != null) { + return defaultServerSocketFactory; + } + if (defaultName == null) { + AccessController.doPrivileged(new java.security.PrivilegedAction(){ + public Object run() { + defaultName = Security.getProperty("ssl.ServerSocketFactory.provider"); + if (defaultName != null) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = ClassLoader.getSystemClassLoader(); + } + try { + defaultServerSocketFactory = (ServerSocketFactory) Class + .forName(defaultName, true, cl) + .newInstance(); + } catch (Exception e) { + return e; + } + } + return null; + } + }); + } + if (defaultServerSocketFactory == null) { + // Try to find in providers + SSLContext context = DefaultSSLContext.getContext(); + if (context != null) { + defaultServerSocketFactory = context.getServerSocketFactory(); + } + } + if (defaultServerSocketFactory == null) { + // Use internal dummy implementation + defaultServerSocketFactory = new DefaultSSLServerSocketFactory("No ServerSocketFactory installed"); + } + return defaultServerSocketFactory; + } + + public abstract String[] getDefaultCipherSuites(); + public abstract String[] getSupportedCipherSuites(); + +} diff --git a/x-net/src/main/java/javax/net/ssl/SSLSession.java b/x-net/src/main/java/javax/net/ssl/SSLSession.java new file mode 100644 index 0000000..36a2528 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLSession.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.Principal; +import java.security.cert.Certificate; +import javax.security.cert.X509Certificate; + +/** + * @com.intel.drl.spec_ref + * + */ +public interface SSLSession { + + /** + * @com.intel.drl.spec_ref + * + */ + public int getApplicationBufferSize(); + + /** + * @com.intel.drl.spec_ref + * + */ + public String getCipherSuite(); + + /** + * @com.intel.drl.spec_ref + * + */ + public long getCreationTime(); + + /** + * @com.intel.drl.spec_ref + * + */ + public byte[] getId(); + + /** + * @com.intel.drl.spec_ref + * + */ + public long getLastAccessedTime(); + + /** + * @com.intel.drl.spec_ref + * + */ + public Certificate[] getLocalCertificates(); + + /** + * @com.intel.drl.spec_ref + * + */ + public Principal getLocalPrincipal(); + + /** + * @com.intel.drl.spec_ref + * + */ + public int getPacketBufferSize(); + + /** + * @com.intel.drl.spec_ref + * + */ + public X509Certificate[] getPeerCertificateChain() + throws SSLPeerUnverifiedException; + + /** + * @com.intel.drl.spec_ref + * + */ + public Certificate[] getPeerCertificates() + throws SSLPeerUnverifiedException; + + /** + * @com.intel.drl.spec_ref + * + */ + public String getPeerHost(); + + /** + * @com.intel.drl.spec_ref + * + */ + public int getPeerPort(); + + /** + * @com.intel.drl.spec_ref + * + */ + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException; + + /** + * @com.intel.drl.spec_ref + * + */ + public String getProtocol(); + + /** + * @com.intel.drl.spec_ref + * + */ + public SSLSessionContext getSessionContext(); + + /** + * @com.intel.drl.spec_ref + * + */ + public Object getValue(String name); + + /** + * @com.intel.drl.spec_ref + * + */ + public String[] getValueNames(); + + /** + * @com.intel.drl.spec_ref + * + */ + public void invalidate(); + + /** + * @com.intel.drl.spec_ref + * + */ + public boolean isValid(); + + /** + * @com.intel.drl.spec_ref + * + */ + public void putValue(String name, Object value); + + /** + * @com.intel.drl.spec_ref + * + */ + public void removeValue(String name); +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLSessionBindingEvent.java b/x-net/src/main/java/javax/net/ssl/SSLSessionBindingEvent.java new file mode 100644 index 0000000..6d30534 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLSessionBindingEvent.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.io.Serializable; +import java.util.EventObject; + +/** + * @com.intel.drl.spec_ref + * + */ +public class SSLSessionBindingEvent extends EventObject implements Serializable { + + /** + * @serial + * The 5.0 spec. doesn't declare this serialVersionUID field + * In order to be compatible it is explicitly declared here + */ + private static final long serialVersionUID = 3989172637106345L; + + private String name; + + public SSLSessionBindingEvent(SSLSession session, String name) { + super(session); + this.name = name; + } + public String getName() { + return name; + } + public SSLSession getSession() { + return (SSLSession)this.source; + } + + + +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLSessionBindingListener.java b/x-net/src/main/java/javax/net/ssl/SSLSessionBindingListener.java new file mode 100644 index 0000000..4e3723d --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLSessionBindingListener.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.util.EventListener; + +/** + * @com.intel.drl.spec_ref + * + */ +public interface SSLSessionBindingListener extends EventListener { + + /** + * @com.intel.drl.spec_ref + * + */ + public void valueBound(SSLSessionBindingEvent event); + + /** + * @com.intel.drl.spec_ref + * + */ + public void valueUnbound(SSLSessionBindingEvent event); + +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLSessionContext.java b/x-net/src/main/java/javax/net/ssl/SSLSessionContext.java new file mode 100644 index 0000000..dd32486 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLSessionContext.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.util.Enumeration; + +/** + * @com.intel.drl.spec_ref + * + */ +public interface SSLSessionContext { + /** + * @com.intel.drl.spec_ref + * + */ + public Enumeration getIds(); + + /** + * @com.intel.drl.spec_ref + * + */ + public SSLSession getSession(byte[] sessionId); + + /** + * @com.intel.drl.spec_ref + * + */ + public int getSessionCacheSize(); + + /** + * @com.intel.drl.spec_ref + * + */ + public int getSessionTimeout(); + + /** + * @com.intel.drl.spec_ref + * + */ + public void setSessionCacheSize(int size) throws IllegalArgumentException; + + /** + * @com.intel.drl.spec_ref + * + */ + public void setSessionTimeout(int seconds) throws IllegalArgumentException; + +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLSocket.java b/x-net/src/main/java/javax/net/ssl/SSLSocket.java new file mode 100644 index 0000000..f11410c --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLSocket.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; + +/** + * @com.intel.drl.spec_ref + * + */ +public abstract class SSLSocket extends Socket { + protected SSLSocket() { + super(); + } + + protected SSLSocket(String host, int port) throws IOException, + UnknownHostException { + super(host, port); + } + + protected SSLSocket(InetAddress address, int port) throws IOException { + super(address, port); + } + + protected SSLSocket(String host, int port, InetAddress clientAddress, + int clientPort) throws IOException, UnknownHostException { + super(host, port, clientAddress, clientPort); + } + + protected SSLSocket(InetAddress address, int port, + InetAddress clientAddress, int clientPort) throws IOException { + super(address, port, clientAddress, clientPort); + } + + public abstract String[] getSupportedCipherSuites(); + public abstract String[] getEnabledCipherSuites(); + public abstract void setEnabledCipherSuites(String[] suites); + public abstract String[] getSupportedProtocols(); + public abstract String[] getEnabledProtocols(); + public abstract void setEnabledProtocols(String[] protocols); + public abstract SSLSession getSession(); + public abstract void addHandshakeCompletedListener(HandshakeCompletedListener listener); + public abstract void removeHandshakeCompletedListener(HandshakeCompletedListener listener); + public abstract void startHandshake() throws IOException; + public abstract void setUseClientMode(boolean mode); + public abstract boolean getUseClientMode(); + public abstract void setNeedClientAuth(boolean need); + public abstract boolean getNeedClientAuth(); + public abstract void setWantClientAuth(boolean want); + public abstract boolean getWantClientAuth(); + public abstract void setEnableSessionCreation(boolean flag); + public abstract boolean getEnableSessionCreation(); + +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java b/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java new file mode 100644 index 0000000..34a221e --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.Socket; +import java.security.AccessController; +import java.security.Security; + +import javax.net.SocketFactory; + +/** + * @com.intel.drl.spec_ref + * + */ +public abstract class SSLSocketFactory extends SocketFactory { + // FIXME EXPORT CONTROL + + // The default SSL socket factory + private static SocketFactory defaultSocketFactory; + + private static String defaultName; + + public SSLSocketFactory() { + super(); + } + + public static SocketFactory getDefault() { + if (defaultSocketFactory != null) { + log("SSLSocketFactory", "Using factory " + defaultSocketFactory); + return defaultSocketFactory; + } + if (defaultName == null) { + AccessController.doPrivileged(new java.security.PrivilegedAction(){ + public Object run() { + defaultName = Security.getProperty("ssl.SocketFactory.provider"); + if (defaultName != null) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = ClassLoader.getSystemClassLoader(); + } + try { + defaultSocketFactory = (SocketFactory) Class.forName( + defaultName, true, cl).newInstance(); + } catch (Exception e) { + return e; + } + } + return null; + } + }); + } + + if (defaultSocketFactory == null) { + // Try to find in providers + SSLContext context = DefaultSSLContext.getContext(); + if (context != null) { + defaultSocketFactory = context.getSocketFactory(); + } + } + if (defaultSocketFactory == null) { + defaultSocketFactory = new DefaultSSLSocketFactory("No SSLSocketFactory installed"); + } + + log("SSLSocketFactory", "Using factory " + defaultSocketFactory); + return defaultSocketFactory; + } + + @SuppressWarnings("unchecked") + private static void log(String tag, String msg) { + try { + Class clazz = Class.forName("android.util.Log"); + Method method = clazz.getMethod("d", new Class[] { String.class, String.class }); + method.invoke(null, new Object[] { tag, msg }); + } catch (Exception ex) { + // Silently ignore. + } + } + + public abstract String[] getDefaultCipherSuites(); + + public abstract String[] getSupportedCipherSuites(); + + public abstract Socket createSocket(Socket s, String host, int port, + boolean autoClose) throws IOException; + +} diff --git a/x-net/src/main/java/javax/net/ssl/TrustManager.java b/x-net/src/main/java/javax/net/ssl/TrustManager.java new file mode 100644 index 0000000..14b75e5 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/TrustManager.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +/** + * @com.intel.drl.spec_ref + * + */ +public interface TrustManager { +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/TrustManagerFactory.java b/x-net/src/main/java/javax/net/ssl/TrustManagerFactory.java new file mode 100644 index 0000000..43919eb --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/TrustManagerFactory.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.AccessController; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.Security; + +import org.apache.harmony.security.fortress.Engine; + + +/** + * @com.intel.drl.spec_ref + * + */ + +public class TrustManagerFactory { + // Store TrustManager service name + private static final String SERVICE = "TrustManagerFactory"; + + // Used to access common engine functionality + private static Engine engine = new Engine(SERVICE); + + // Store default property name + private static final String PROPERTYNAME = "ssl.TrustManagerFactory.algorithm"; + + // Store used provider + private final Provider provider; + + // Storeused TrustManagerFactorySpi implementation + private final TrustManagerFactorySpi spiImpl; + + // Store used algorithm + private final String algorithm; + + /** + * @com.intel.drl.spec_ref + * + */ + protected TrustManagerFactory(TrustManagerFactorySpi factorySpi, + Provider provider, String algorithm) { + this.provider = provider; + this.algorithm = algorithm; + this.spiImpl = factorySpi; + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final String getAlgorithm() { + return algorithm; + } + + /** + * @com.intel.drl.spec_ref + * + * throws NullPointerException if algorithm is null (instead of + * NoSuchAlgorithmException as in 1.4 release) + */ + public static final TrustManagerFactory getInstance(String algorithm) + throws NoSuchAlgorithmException { + if (algorithm == null) { + throw new NullPointerException("algorithm is null"); + } + synchronized (engine) { + engine.getInstance(algorithm, null); + return new TrustManagerFactory((TrustManagerFactorySpi) engine.spi, + engine.provider, algorithm); + } + } + + /** + * @com.intel.drl.spec_ref + * + * throws NullPointerException if algorithm is null (instead of + * NoSuchAlgorithmException as in 1.4 release) + */ + public static final TrustManagerFactory getInstance(String algorithm, + String provider) throws NoSuchAlgorithmException, + NoSuchProviderException { + if ((provider == null) || (provider.length() == 0)) { + throw new IllegalArgumentException("Provider is null oe empty"); + } + Provider impProvider = Security.getProvider(provider); + if (impProvider == null) { + throw new NoSuchProviderException(provider); + } + return getInstance(algorithm, impProvider); + } + + /** + * @com.intel.drl.spec_ref + * + * throws NullPointerException if algorithm is null (instead of + * NoSuchAlgorithmException as in 1.4 release) + */ + public static final TrustManagerFactory getInstance(String algorithm, + Provider provider) throws NoSuchAlgorithmException { + if (provider == null) { + throw new IllegalArgumentException("Provider is null"); + } + if (algorithm == null) { + throw new NullPointerException("algorithm is null"); + } + synchronized (engine) { + engine.getInstance(algorithm, provider, null); + return new TrustManagerFactory((TrustManagerFactorySpi) engine.spi, + provider, algorithm); + } + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final Provider getProvider() { + return provider; + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final void init(KeyStore ks) throws KeyStoreException { + spiImpl.engineInit(ks); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final void init(ManagerFactoryParameters spec) + throws InvalidAlgorithmParameterException { + spiImpl.engineInit(spec); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public final TrustManager[] getTrustManagers() { + return spiImpl.engineGetTrustManagers(); + } + + /** + * @com.intel.drl.spec_ref + * + */ + public static final String getDefaultAlgorithm() { + return AccessController + .doPrivileged(new java.security.PrivilegedAction<String>() { + public String run() { + return Security.getProperty(PROPERTYNAME); + } + }); + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/TrustManagerFactorySpi.java b/x-net/src/main/java/javax/net/ssl/TrustManagerFactorySpi.java new file mode 100644 index 0000000..84b986d --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/TrustManagerFactorySpi.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; + +/** + * @com.intel.drl.spec_ref + * + */ + +public abstract class TrustManagerFactorySpi { + /** + * @com.intel.drl.spec_ref + * + */ + public TrustManagerFactorySpi() { + } + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract void engineInit(KeyStore ks) throws KeyStoreException; + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract void engineInit(ManagerFactoryParameters spec) + throws InvalidAlgorithmParameterException; + + /** + * @com.intel.drl.spec_ref + * + */ + protected abstract TrustManager[] engineGetTrustManagers(); +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/X509ExtendedKeyManager.java b/x-net/src/main/java/javax/net/ssl/X509ExtendedKeyManager.java new file mode 100644 index 0000000..6c47fa9 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/X509ExtendedKeyManager.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris V. Kuznetsov +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.Principal; + +/** + * @com.intel.drl.spec_ref + * + */ +public abstract class X509ExtendedKeyManager implements X509KeyManager { + protected X509ExtendedKeyManager() { + super(); + } + + public String chooseEngineClientAlias(String[] keyType, + Principal[] issuers, SSLEngine engine) { + return null; + } + + public String chooseEngineServerAlias(String keyType, Principal[] issuers, + SSLEngine engine) { + return null; + } + +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/X509KeyManager.java b/x-net/src/main/java/javax/net/ssl/X509KeyManager.java new file mode 100644 index 0000000..075e2fc --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/X509KeyManager.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.net.Socket; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +/** + * @com.intel.drl.spec_ref + * + */ +public interface X509KeyManager extends KeyManager { + + /** + * @com.intel.drl.spec_ref + * + */ + public String chooseClientAlias(String[] keyType, Principal[] issuers, + Socket socket); + + /** + * @com.intel.drl.spec_ref + * + */ + public String chooseServerAlias(String keyType, Principal[] issuers, + Socket socket); + + /** + * @com.intel.drl.spec_ref + * + */ + public X509Certificate[] getCertificateChain(String alias); + + /** + * @com.intel.drl.spec_ref + * + */ + public String[] getClientAliases(String keyType, Principal[] issuers); + + /** + * @com.intel.drl.spec_ref + * + */ + public String[] getServerAliases(String keyType, Principal[] issuers); + + /** + * @com.intel.drl.spec_ref + * + */ + public PrivateKey getPrivateKey(String alias); +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/X509TrustManager.java b/x-net/src/main/java/javax/net/ssl/X509TrustManager.java new file mode 100644 index 0000000..ed5a221 --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/X509TrustManager.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Vera Y. Petrashkova +* @version $Revision$ +*/ + +package javax.net.ssl; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * @com.intel.drl.spec_ref + * + */ +public interface X509TrustManager extends TrustManager { + + /** + * @com.intel.drl.spec_ref + * + */ + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException; + + /** + * @com.intel.drl.spec_ref + * + */ + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException; + + /** + * @com.intel.drl.spec_ref + * + */ + public X509Certificate[] getAcceptedIssuers(); +}
\ No newline at end of file diff --git a/x-net/src/main/java/javax/net/ssl/package.html b/x-net/src/main/java/javax/net/ssl/package.html new file mode 100644 index 0000000..732b05f --- /dev/null +++ b/x-net/src/main/java/javax/net/ssl/package.html @@ -0,0 +1,24 @@ +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=us-ascii"> +</head> +<html> +<body> +<p> +This package provides all the classes and interfaces needed to implemenet and program the Secure Socket +abstraction based on the SSL protocol SSSLv3.0 or TLSv1.2 +All the details of the SSL handshake protocol ar card for, and the cipher set with which +a client or a server work can be specified. + +X.509 certificates are verified and if desired either the client or the server can walk through +the whole certificates' chain until the root CA is reached. + +Notice that the Android javax.net.ssl package uses the OpenSSL Library to implement the low level +SSL functionality. All the relevant OpenSSl write(...) and read(...) functions are hidden within two +JNI files. The signatures of all the Java SSL methods are compliant with that of the Java 5.0 +specification. + +The provider for all SSL cryptological tools is The Legion of Bouncy Castle (http://www.bouncycastle.org). +</p> +</body> +</html>
\ No newline at end of file diff --git a/x-net/src/main/java/org/apache/harmony/xnet/internal/nls/Messages.java b/x-net/src/main/java/org/apache/harmony/xnet/internal/nls/Messages.java new file mode 100644 index 0000000..efad845 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/internal/nls/Messages.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/* + * THE FILE HAS BEEN AUTOGENERATED BY MSGTOOL TOOL. + * All changes made to this file manually will be overwritten + * if this tool runs again. Better make changes in the template file. + */ + +package org.apache.harmony.xnet.internal.nls; + +import org.apache.harmony.luni.util.MsgHelp; + +/** + * This class retrieves strings from a resource bundle and returns them, + * formatting them with MessageFormat when required. + * <p> + * It is used by the system classes to provide national language support, by + * looking up messages in the <code> + * org.apache.harmony.xnet.internal.nls.messages + * </code> + * resource bundle. Note that if this file is not available, or an invalid key + * is looked up, or resource bundle support is not available, the key itself + * will be returned as the associated message. This means that the <em>KEY</em> + * should a reasonable human-readable (english) string. + * + */ +public class Messages { + + private static final String sResource = + "org.apache.harmony.xnet.internal.nls.messages"; //$NON-NLS-1$ + + /** + * Retrieves a message which has no arguments. + * + * @param msg + * String the key to look up. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg) { + return MsgHelp.getString(sResource, msg); + } + + /** + * Retrieves a message which takes 1 argument. + * + * @param msg + * String the key to look up. + * @param arg + * Object the object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg) { + return getString(msg, new Object[] { arg }); + } + + /** + * Retrieves a message which takes 1 integer argument. + * + * @param msg + * String the key to look up. + * @param arg + * int the integer to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, int arg) { + return getString(msg, new Object[] { Integer.toString(arg) }); + } + + /** + * Retrieves a message which takes 1 character argument. + * + * @param msg + * String the key to look up. + * @param arg + * char the character to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, char arg) { + return getString(msg, new Object[] { String.valueOf(arg) }); + } + + /** + * Retrieves a message which takes 2 arguments. + * + * @param msg + * String the key to look up. + * @param arg1 + * Object an object to insert in the formatted output. + * @param arg2 + * Object another object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg1, Object arg2) { + return getString(msg, new Object[] { arg1, arg2 }); + } + + /** + * Retrieves a message which takes several arguments. + * + * @param msg + * String the key to look up. + * @param args + * Object[] the objects to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object[] args) { + return MsgHelp.getString(sResource, msg, args); + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/internal/nls/messages.properties b/x-net/src/main/java/org/apache/harmony/xnet/internal/nls/messages.properties new file mode 100644 index 0000000..229ca61 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/internal/nls/messages.properties @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +# messages for EN locale
\ No newline at end of file diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AlertException.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AlertException.java new file mode 100644 index 0000000..edf7638 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AlertException.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import javax.net.ssl.SSLException; + +/** + * This exception is used to signalize the fatal alert + * occured during the work of protocol. + */ +public class AlertException extends RuntimeException { + + // SSLException to be thrown to application side + private final SSLException reason; + // alert description code + private final byte description; + + /** + * Constructs the instance. + * @param description: The alert description code. + * @see org.apache.harmony.xnet.provider.jsse.AlertProtocol + * @param reason: The SSLException to be thrown to application + * side after alert processing (sending the record with alert, + * shoutdown work, etc). + */ + protected AlertException(byte description, SSLException reason) { + super(reason); + this.reason = reason; + this.description = description; + } + + /** + * Returns the reason of alert. This reason should be rethrown + * after alert protcessin. + * @return the reason of alert. + */ + protected SSLException getReason() { + return reason; + } + + /** + * Returns alert's description code. + * @return byte value describing the occured alert. + * @see org.apache.harmony.xnet.provider.jsse.AlertProtocol for more information about possible + * reason codes. + */ + protected byte getDescriptionCode() { + return description; + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AlertProtocol.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AlertProtocol.java new file mode 100644 index 0000000..8f10875 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AlertProtocol.java @@ -0,0 +1,293 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.SSLRecordProtocol; +import org.apache.harmony.xnet.provider.jsse.Logger; +import org.apache.harmony.xnet.provider.jsse.ContentType; + +/** + * This class encapsulates the functionality of Alert Protocol. + * Constant values are taken according to the TLS v1 specification + * (http://www.ietf.org/rfc/rfc2246.txt), p 7.2. + */ +public class AlertProtocol { + + // ------------------------ AlertLevel codes -------------------------- + /** + * Defines the severity of alert as warning + */ + protected static final byte WARNING = 1; + /** + * Defines the severity of alert as fatal + */ + protected static final byte FATAL = 2; + + // --------------------- AlertDescription codes ----------------------- + /** + * Defines the description code of the close_notify alert + */ + protected static final byte CLOSE_NOTIFY = 0; + /** + * Defines the description code of the unexpected_message alert + */ + protected static final byte UNEXPECTED_MESSAGE = 10; + /** + * Defines the description code of the bad_record_mac alert + */ + protected static final byte BAD_RECORD_MAC = 20; + /** + * Defines the description code of the decryption_failed alert + */ + protected static final byte DECRYPTION_FAILED = 21; + /** + * Defines the description code of the record_overflow alert + */ + protected static final byte RECORD_OVERFLOW = 22; + /** + * Defines the description code of the decompression_failure alert + */ + protected static final byte DECOMPRESSION_FAILURE = 30; + /** + * Defines the description code of the handshake_failure alert + */ + protected static final byte HANDSHAKE_FAILURE = 40; + /** + * Defines the description code of the bad_certificate alert + */ + protected static final byte BAD_CERTIFICATE = 42; + /** + * Defines the description code of the unsupported_certificate alert + */ + protected static final byte UNSUPPORTED_CERTIFICATE = 43; + /** + * Defines the description code of the certificate_revoked alert + */ + protected static final byte CERTIFICATE_REVOKED = 44; + /** + * Defines the description code of the certificate_expired alert + */ + protected static final byte CERTIFICATE_EXPIRED = 45; + /** + * Defines the description code of the certificate_unknown alert + */ + protected static final byte CERTIFICATE_UNKNOWN = 46; + /** + * Defines the description code of the illegal_parameter alert + */ + protected static final byte ILLEGAL_PARAMETER = 47; + /** + * Defines the description code of the unknown_ca alert + */ + protected static final byte UNKNOWN_CA = 48; + /** + * Defines the description code of the access_denied alert + */ + protected static final byte ACCESS_DENIED = 49; + /** + * Defines the description code of the decode_error alert + */ + protected static final byte DECODE_ERROR = 50; + /** + * Defines the description code of the decrypt_error alert + */ + protected static final byte DECRYPT_ERROR = 51; + /** + * Defines the description code of the export_restriction alert + */ + protected static final byte EXPORT_RESTRICTION = 60; + /** + * Defines the description code of the protocol_version alert + */ + protected static final byte PROTOCOL_VERSION = 70; + /** + * Defines the description code of the insufficient_security alert + */ + protected static final byte INSUFFICIENT_SECURITY = 71; + /** + * Defines the description code of the internal_error alert + */ + protected static final byte INTERNAL_ERROR = 80; + /** + * Defines the description code of the user_canceled alert + */ + protected static final byte USER_CANCELED = 90; + /** + * Defines the description code of the no_renegotiation alert + */ + protected static final byte NO_RENEGOTIATION = 100; + + + // holds level and description codes + private final byte[] alert = new byte[2]; + // record protocol to be used to wrap the alerts + private SSLRecordProtocol recordProtocol; + + private Logger.Stream logger = Logger.getStream("alert"); + + /** + * Creates the instance of AlertProtocol. + * Note that class is not ready to work without providing of + * record protocol + * @see #setRecordProtocol + */ + protected AlertProtocol() {} + + /** + * Sets up the record protocol to be used by this allert protocol. + */ + protected void setRecordProtocol(SSLRecordProtocol recordProtocol) { + this.recordProtocol = recordProtocol; + } + + /** + * Reports an alert to be sent/received by transport. + * This method is usually called during processing + * of the income TSL record: if it contains alert message from another + * peer, or if warning alert occured during the processing of the + * message and this warning should be sent to another peer. + * @param level: alert level code + * @param description: alert description code + * @return + */ + protected void alert(byte level, byte description) { + if (logger != null) { + logger.println("Alert.alert: "+level+" "+description); + } + this.alert[0] = level; + this.alert[1] = description; + } + + /** + * Returns the description code of alert or -100 if there + * is no alert. + */ + protected byte getDescriptionCode() { + return (alert[0] != 0) ? alert[1] : -100; + } + + /** + * Resets the protocol to be in "no alert" state. + * This method shoud be called after processing of the reported alert. + */ + protected void setProcessed() { + // free the info about alert + if (logger != null) { + logger.println("Alert.setProcessed"); + } + this.alert[0] = 0; + } + + /** + * Checks if any alert has occured. + */ + protected boolean hasAlert() { + return (alert[0] != 0); + } + + /** + * Checks if occured alert is fatal alert. + */ + protected boolean isFatalAlert() { + return (alert[0] == 2); + } + + /** + * Returns the string representation of occured alert. + * If no alert has occured null is returned. + */ + protected String getAlertDescription() { + switch (alert[1]) { + case CLOSE_NOTIFY: + return "close_notify"; + case UNEXPECTED_MESSAGE: + return "unexpected_message"; + case BAD_RECORD_MAC: + return "bad_record_mac"; + case DECRYPTION_FAILED: + return "decryption_failed"; + case RECORD_OVERFLOW: + return "record_overflow"; + case DECOMPRESSION_FAILURE: + return "decompression_failure"; + case HANDSHAKE_FAILURE: + return "handshake_failure"; + case BAD_CERTIFICATE: + return "bad_certificate"; + case UNSUPPORTED_CERTIFICATE: + return "unsupported_certificate"; + case CERTIFICATE_REVOKED: + return "certificate_revoked"; + case CERTIFICATE_EXPIRED: + return "certificate_expired"; + case CERTIFICATE_UNKNOWN: + return "certificate_unknown"; + case ILLEGAL_PARAMETER: + return "illegal_parameter"; + case UNKNOWN_CA: + return "unknown_ca"; + case ACCESS_DENIED: + return "access_denied"; + case DECODE_ERROR: + return "decode_error"; + case DECRYPT_ERROR: + return "decrypt_error"; + case EXPORT_RESTRICTION: + return "export_restriction"; + case PROTOCOL_VERSION: + return "protocol_version"; + case INSUFFICIENT_SECURITY: + return "insufficient_security"; + case INTERNAL_ERROR: + return "internal_error"; + case USER_CANCELED: + return "user_canceled"; + case NO_RENEGOTIATION: + return "no_renegotiation"; + } + return null; + } + + /** + * Returns the record with reported alert message. + * The returned array of bytes is ready to be sent to another peer. + * Note, that this method does not automatically set the state of allert + * protocol in "no alert" state, so after wrapping the method setProcessed + * should be called. + */ + protected byte[] wrap() { + byte[] res = recordProtocol.wrap(ContentType.ALERT, alert, 0, 2); + return res; + } + + /** + * Shutdownes the protocol. It will be impossiblke to use the instance + * after the calling of this method. + */ + protected void shutdown() { + alert[0] = 0; + alert[1] = 0; + recordProtocol = null; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Appendable.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Appendable.java new file mode 100644 index 0000000..1485bae --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Appendable.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +/** + * This interface represents the ability of the input stream related + * classes to provide additianal data to be read. + */ +public interface Appendable { + + /** + * Provides the additional data to be read. + * @param src: the source data to be appended. + */ + public void append(byte[] src); + +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateMessage.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateMessage.java new file mode 100644 index 0000000..6aac128 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateMessage.java @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.Message; +import org.apache.harmony.xnet.provider.jsse.Handshake; +import org.apache.harmony.xnet.provider.jsse.HandshakeIODataStream; +import org.apache.harmony.xnet.provider.jsse.AlertProtocol; + +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Vector; + +/** + * + * Represents server/client certificate message + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS + * 1.0 spec., 7.4.2. Server certificate; 7.4.6. Client certificate</a> + * + */ +public class CertificateMessage extends Message { + + /** + * Certificates + */ + X509Certificate[] certs; + + /** + * Certificates in encoded form + */ + byte[][] encoded_certs; + + /** + * Creates inbound message + * + * @param in + * @param length + * @throws IOException + */ + public CertificateMessage(HandshakeIODataStream in, int length) + throws IOException { + int l = in.readUint24(); // total_length + if (l == 0) { // message contais no certificates + if (length != 3) { // no more bytes after total_length + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect CertificateMessage"); + } + certs = new X509Certificate[0]; + encoded_certs = new byte[0][0]; + this.length = 3; + return; + } + CertificateFactory cf; + try { + cf = CertificateFactory.getInstance("X509"); + } catch (CertificateException e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e); + return; + } + Vector certs_vector = new Vector(); + int size = 0; + int enc_size = 0; + while (l > 0) { + size = in.readUint24(); + l -= 3; + try { + certs_vector.add(cf.generateCertificate(in)); + } catch (CertificateException e) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR", e); + } + l -= size; + enc_size += size; + } + certs = new X509Certificate[certs_vector.size()]; + for (int i = 0; i < certs.length; i++) { + certs[i] = (X509Certificate) certs_vector.elementAt(i); + } + this.length = 3 + 3 * certs.length + enc_size; + if (this.length != length) { + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect CertificateMessage"); + } + + } + + /** + * Creates outbound message + * + * @param certs + */ + public CertificateMessage(X509Certificate[] certs) { + if (certs == null) { + this.certs = new X509Certificate[0]; + encoded_certs = new byte[0][0]; + length = 3; + return; + } + this.certs = certs; + if (encoded_certs == null) { + encoded_certs = new byte[certs.length][]; + for (int i = 0; i < certs.length; i++) { + try { + encoded_certs[i] = certs[i].getEncoded(); + } catch (CertificateEncodingException e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", + e); + } + } + } + length = 3 + 3 * encoded_certs.length; + for (int i = 0; i < encoded_certs.length; i++) { + length += encoded_certs[i].length; + } + } + + /** + * Sends message + * + * @param out + */ + public void send(HandshakeIODataStream out) { + + int total_length = 0; + if (encoded_certs == null) { + encoded_certs = new byte[certs.length][]; + for (int i = 0; i < certs.length; i++) { + try { + encoded_certs[i] = certs[i].getEncoded(); + } catch (CertificateEncodingException e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", + e); + } + } + } + total_length = 3 * encoded_certs.length; + for (int i = 0; i < encoded_certs.length; i++) { + total_length += encoded_certs[i].length; + } + out.writeUint24(total_length); + for (int i = 0; i < encoded_certs.length; i++) { + out.writeUint24(encoded_certs[i].length); + out.write(encoded_certs[i]); + } + + } + + /** + * Returns message type + * + * @return + */ + public int getType() { + return Handshake.CERTIFICATE; + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateRequest.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateRequest.java new file mode 100644 index 0000000..8bedccd --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateRequest.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.Message; +import org.apache.harmony.xnet.provider.jsse.Handshake; +import org.apache.harmony.xnet.provider.jsse.HandshakeIODataStream; +import org.apache.harmony.xnet.provider.jsse.AlertProtocol; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.Vector; + +import javax.security.auth.x500.X500Principal; + +/** + * + * Represents certificate request message + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.4. + * Certificate request</a> + */ +public class CertificateRequest extends Message { + + /** + * Client certificate types as defined in + * TLS 1.0 spec., 7.4.4. Certificate request + */ + public static final byte RSA_SIGN = 1; + public static final byte DSS_SIGN = 2; + public static final byte RSA_FIXED_DH = 3; + public static final byte DSS_FIXED_DH = 4; + + /** + * Requested certificate types + */ + final byte[] certificate_types; + + /** + * Certificate authorities + */ + X500Principal[] certificate_authorities; + + //Requested certificate types as Strings + // ("RSA", "DSA", "DH_RSA" or "DH_DSA") + private String[] types; + + // Encoded form of certificate authorities + private byte[][] encoded_principals; + + /** + * Creates outbound message + * + * @param certificate_types + * @param accepted - array of certificate authority certificates + */ + public CertificateRequest(byte[] certificate_types, + X509Certificate[] accepted) { + + if (accepted == null) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, + "CertificateRequest: array of certificate authority certificates is null"); + } + this.certificate_types = certificate_types; + + int totalPrincipalsLength = 0; + certificate_authorities = new X500Principal[accepted.length]; + encoded_principals = new byte[accepted.length][]; + for (int i = 0; i < accepted.length; i++) { + certificate_authorities[i] = accepted[i].getIssuerX500Principal(); + encoded_principals[i] = certificate_authorities[i].getEncoded(); + totalPrincipalsLength += encoded_principals[i].length + 2; + } + + length = 3 + certificate_types.length + totalPrincipalsLength; + } + + /** + * Creates inbound message + * + * @param in + * @param length + * @throws IOException + */ + public CertificateRequest(HandshakeIODataStream in, int length) + throws IOException { + int size = in.readUint8(); + certificate_types = new byte[size]; + in.read(certificate_types, 0, size); + size = in.readUint16(); + certificate_authorities = new X500Principal[size]; + int totalPrincipalsLength = 0; + int principalLength = 0; + Vector principals = new Vector(); + while (totalPrincipalsLength < size) { + principalLength = in.readUint16(); // encoded X500Principal size + principals.add(new X500Principal(in)); + totalPrincipalsLength += 2; + totalPrincipalsLength += principalLength; + } + certificate_authorities = new X500Principal[principals.size()]; + for (int i = 0; i < certificate_authorities.length; i++) { + certificate_authorities[i] = (X500Principal) principals.elementAt(i); + } + this.length = 3 + certificate_types.length + totalPrincipalsLength; + if (this.length != length) { + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect CertificateRequest"); + } + + } + + /** + * Sends message + * + * @param out + */ + public void send(HandshakeIODataStream out) { + + out.writeUint8(certificate_types.length); + for (int i = 0; i < certificate_types.length; i++) { + out.write(certificate_types[i]); + } + int authoritiesLength = 0; + for (int i = 0; i < certificate_authorities.length; i++) { + authoritiesLength += encoded_principals[i].length +2; + } + out.writeUint16(authoritiesLength); + for (int i = 0; i < certificate_authorities.length; i++) { + out.writeUint16(encoded_principals[i].length); + out.write(encoded_principals[i]); + } + } + + /** + * Returns message type + * + * @return + */ + public int getType() { + return Handshake.CERTIFICATE_REQUEST; + } + + /** + * Returns requested certificate types as array of strings + */ + public String[] getTypesAsString() { + if (types == null) { + types = new String[certificate_types.length]; + for (int i = 0; i < types.length; i++) { + switch (certificate_types[i]) { + case 1: + types[i] = "RSA"; + break; + case 2: + types[i] = "DSA"; + break; + case 3: + types[i] = "DH_RSA"; + break; + case 4: + types[i] = "DH_DSA"; + break; + default: + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect CertificateRequest"); + } + } + } + return types; + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateVerify.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateVerify.java new file mode 100644 index 0000000..183b8aa --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateVerify.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.Message; +import org.apache.harmony.xnet.provider.jsse.Handshake; +import org.apache.harmony.xnet.provider.jsse.HandshakeIODataStream; +import org.apache.harmony.xnet.provider.jsse.AlertProtocol; + +import java.io.IOException; + +/** + * + * Represents certificate verify message + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.8. + * Certificate verify</a> + */ +public class CertificateVerify extends Message { + + /** + * Signature + */ + byte[] signedHash; + + /** + * Creates outbound message + * + * @param hash + */ + public CertificateVerify(byte[] hash) { + if (hash == null || hash.length == 0) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, + "INTERNAL ERROR: incorrect certificate verify hash"); + } + this.signedHash = hash; + length = hash.length + 2; + } + + /** + * Creates inbound message + * + * @param in + * @param length + * @throws IOException + */ + public CertificateVerify(HandshakeIODataStream in, int length) + throws IOException { + if (length == 0) { + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect CertificateVerify"); + } else { + if (in.readUint16() != length - 2) { + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect CertificateVerify"); + } + signedHash = in.read(length -2); + } + this.length = length; + } + + /** + * Sends message + * + * @param out + */ + public void send(HandshakeIODataStream out) { + if (signedHash.length != 0) { + out.writeUint16(signedHash.length); + out.write(signedHash); + } + } + + /** + * Returns message type + * + * @return + */ + public int getType() { + return Handshake.CERTIFICATE_VERIFY; + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CipherSuite.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CipherSuite.java new file mode 100644 index 0000000..8352386 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CipherSuite.java @@ -0,0 +1,612 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.security.GeneralSecurityException; +import java.util.Hashtable; + +import javax.crypto.Cipher; + +/** + * Represents Cipher Suite as defined in TLS 1.0 spec., + * A.5. The CipherSuite; + * C. CipherSuite definitions. + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec.</a> + * + */ +public class CipherSuite { + + /** + * true if this cipher suite is supported + */ + boolean supported = true; + + /** + * cipher suite key exchange + */ + final int keyExchange; + + /** + * cipher + */ + final String cipherName; + + /** + * Cipher information + */ + final int keyMaterial; + final int expandedKeyMaterial; + final int effectiveKeyBytes; + final int IVSize; + final private int blockSize; + + // cipher suite code + private final byte[] cipherSuiteCode; + + // cipher suite name + private final String name; + + // true if cipher suite is exportable + private final boolean isExportable; + + // Hash algorithm + final private String hashName; + + // MAC algorithm + final private String hmacName; + + // Hash size + final private int hashSize; + + /** + * key exchange values + */ + static int KeyExchange_RSA = 1; + static int KeyExchange_RSA_EXPORT = 2; + static int KeyExchange_DHE_DSS = 3; + static int KeyExchange_DHE_DSS_EXPORT = 4; + static int KeyExchange_DHE_RSA = 5; + static int KeyExchange_DHE_RSA_EXPORT = 6; + static int KeyExchange_DH_DSS = 7; + static int KeyExchange_DH_RSA = 8; + static int KeyExchange_DH_anon = 9; + static int KeyExchange_DH_anon_EXPORT = 10; + static int KeyExchange_DH_DSS_EXPORT = 11; + static int KeyExchange_DH_RSA_EXPORT = 12; + + /** + * TLS cipher suite codes + */ + static byte[] code_TLS_NULL_WITH_NULL_NULL = { 0x00, 0x00 }; + static byte[] code_TLS_RSA_WITH_NULL_MD5 = { 0x00, 0x01 }; + static byte[] code_TLS_RSA_WITH_NULL_SHA = { 0x00, 0x02 }; + static byte[] code_TLS_RSA_EXPORT_WITH_RC4_40_MD5 = { 0x00, 0x03 }; + static byte[] code_TLS_RSA_WITH_RC4_128_MD5 = { 0x00, 0x04 }; + static byte[] code_TLS_RSA_WITH_RC4_128_SHA = { 0x00, 0x05 }; + static byte[] code_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = { 0x00, 0x06 }; + static byte[] code_TLS_RSA_WITH_IDEA_CBC_SHA = { 0x00, 0x07 }; + static byte[] code_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x08 }; + static byte[] code_TLS_RSA_WITH_DES_CBC_SHA = { 0x00, 0x09 }; + static byte[] code_TLS_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x0A }; + static byte[] code_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x0B }; + static byte[] code_TLS_DH_DSS_WITH_DES_CBC_SHA = { 0x00, 0x0C }; + static byte[] code_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x0D }; + static byte[] code_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x0E }; + static byte[] code_TLS_DH_RSA_WITH_DES_CBC_SHA = { 0x00, 0x0F }; + static byte[] code_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x10 }; + static byte[] code_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x11 }; + static byte[] code_TLS_DHE_DSS_WITH_DES_CBC_SHA = { 0x00, 0x12 }; + static byte[] code_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x13 }; + static byte[] code_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x14 }; + static byte[] code_TLS_DHE_RSA_WITH_DES_CBC_SHA = { 0x00, 0x15 }; + static byte[] code_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x16 }; + static byte[] code_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = { 0x00, 0x17 }; + static byte[] code_TLS_DH_anon_WITH_RC4_128_MD5 = { 0x00, 0x18 }; + static byte[] code_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x19 }; + static byte[] code_TLS_DH_anon_WITH_DES_CBC_SHA = { 0x00, 0x1A }; + static byte[] code_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x1B }; + + static CipherSuite TLS_NULL_WITH_NULL_NULL = new CipherSuite( + "TLS_NULL_WITH_NULL_NULL", true, 0, null, null, + code_TLS_NULL_WITH_NULL_NULL); + + static CipherSuite TLS_RSA_WITH_NULL_MD5 = new CipherSuite( + "TLS_RSA_WITH_NULL_MD5", true, KeyExchange_RSA, null, "MD5", + code_TLS_RSA_WITH_NULL_MD5); + + static CipherSuite TLS_RSA_WITH_NULL_SHA = new CipherSuite( + "TLS_RSA_WITH_NULL_SHA", true, KeyExchange_RSA, null, "SHA", + code_TLS_RSA_WITH_NULL_SHA); + + static CipherSuite TLS_RSA_EXPORT_WITH_RC4_40_MD5 = new CipherSuite( + "TLS_RSA_EXPORT_WITH_RC4_40_MD5", true, KeyExchange_RSA_EXPORT, + "RC4_40", "MD5", code_TLS_RSA_EXPORT_WITH_RC4_40_MD5); + + static CipherSuite TLS_RSA_WITH_RC4_128_MD5 = new CipherSuite( + "TLS_RSA_WITH_RC4_128_MD5", false, KeyExchange_RSA, "RC4_128", + "MD5", code_TLS_RSA_WITH_RC4_128_MD5); + + static CipherSuite TLS_RSA_WITH_RC4_128_SHA = new CipherSuite( + "TLS_RSA_WITH_RC4_128_SHA", false, KeyExchange_RSA, "RC4_128", + "SHA", code_TLS_RSA_WITH_RC4_128_SHA); + + static CipherSuite TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = new CipherSuite( + "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5", true, KeyExchange_RSA_EXPORT, + "RC2_CBC_40", "MD5", code_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5); + + static CipherSuite TLS_RSA_WITH_IDEA_CBC_SHA = new CipherSuite( + "TLS_RSA_WITH_IDEA_CBC_SHA", false, KeyExchange_RSA, "IDEA_CBC", + "SHA", code_TLS_RSA_WITH_IDEA_CBC_SHA); + + static CipherSuite TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite( + "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", true, KeyExchange_RSA_EXPORT, + "DES40_CBC", "SHA", code_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA); + + static CipherSuite TLS_RSA_WITH_DES_CBC_SHA = new CipherSuite( + "TLS_RSA_WITH_DES_CBC_SHA", false, KeyExchange_RSA, "DES_CBC", + "SHA", code_TLS_RSA_WITH_DES_CBC_SHA); + + static CipherSuite TLS_RSA_WITH_3DES_EDE_CBC_SHA = new CipherSuite( + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", false, KeyExchange_RSA, + "3DES_EDE_CBC", "SHA", code_TLS_RSA_WITH_3DES_EDE_CBC_SHA); + + static CipherSuite TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite( + "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", true, + KeyExchange_DH_DSS_EXPORT, "DES40_CBC", "SHA", + code_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA); + + static CipherSuite TLS_DH_DSS_WITH_DES_CBC_SHA = new CipherSuite( + "TLS_DH_DSS_WITH_DES_CBC_SHA", false, KeyExchange_DH_DSS, + "DES_CBC", "SHA", code_TLS_DH_DSS_WITH_DES_CBC_SHA); + + static CipherSuite TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = new CipherSuite( + "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", false, KeyExchange_DH_DSS, + "3DES_EDE_CBC", "SHA", code_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA); + + static CipherSuite TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite( + "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", true, + KeyExchange_DH_RSA_EXPORT, "DES40_CBC", "SHA", + code_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA); + + static CipherSuite TLS_DH_RSA_WITH_DES_CBC_SHA = new CipherSuite( + "TLS_DH_RSA_WITH_DES_CBC_SHA", false, KeyExchange_DH_RSA, + "DES_CBC", "SHA", code_TLS_DH_RSA_WITH_DES_CBC_SHA); + + static CipherSuite TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = new CipherSuite( + "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", false, KeyExchange_DH_RSA, + "3DES_EDE_CBC", "SHA", code_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA); + + static CipherSuite TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite( + "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", true, + KeyExchange_DHE_DSS_EXPORT, "DES40_CBC", "SHA", + code_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA); + + static CipherSuite TLS_DHE_DSS_WITH_DES_CBC_SHA = new CipherSuite( + "TLS_DHE_DSS_WITH_DES_CBC_SHA", false, KeyExchange_DHE_DSS, + "DES_CBC", "SHA", code_TLS_DHE_DSS_WITH_DES_CBC_SHA); + + static CipherSuite TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = new CipherSuite( + "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", false, KeyExchange_DHE_DSS, + "3DES_EDE_CBC", "SHA", code_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA); + + static CipherSuite TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite( + "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", true, + KeyExchange_DHE_RSA_EXPORT, "DES40_CBC", "SHA", + code_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA); + + static CipherSuite TLS_DHE_RSA_WITH_DES_CBC_SHA = new CipherSuite( + "TLS_DHE_RSA_WITH_DES_CBC_SHA", false, KeyExchange_DHE_RSA, + "DES_CBC", "SHA", code_TLS_DHE_RSA_WITH_DES_CBC_SHA); + + static CipherSuite TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = new CipherSuite( + "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", false, KeyExchange_DHE_RSA, + "3DES_EDE_CBC", "SHA", code_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA); + + static CipherSuite TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = new CipherSuite( + "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", true, + KeyExchange_DH_anon_EXPORT, "RC4_40", "MD5", + code_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5); + + static CipherSuite TLS_DH_anon_WITH_RC4_128_MD5 = new CipherSuite( + "TLS_DH_anon_WITH_RC4_128_MD5", false, KeyExchange_DH_anon, + "RC4_128", "MD5", code_TLS_DH_anon_WITH_RC4_128_MD5); + + static CipherSuite TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite( + "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", true, + KeyExchange_DH_anon_EXPORT, "DES40_CBC", "SHA", + code_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA); + + static CipherSuite TLS_DH_anon_WITH_DES_CBC_SHA = new CipherSuite( + "TLS_DH_anon_WITH_DES_CBC_SHA", false, KeyExchange_DH_anon, + "DES_CBC", "SHA", code_TLS_DH_anon_WITH_DES_CBC_SHA); + + static CipherSuite TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = new CipherSuite( + "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", false, KeyExchange_DH_anon, + "3DES_EDE_CBC", "SHA", code_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA); + + // array for quick access to cipher suite by code + private static CipherSuite[] cuitesByCode = { + TLS_NULL_WITH_NULL_NULL, + TLS_RSA_WITH_NULL_MD5, + TLS_RSA_WITH_NULL_SHA, + TLS_RSA_EXPORT_WITH_RC4_40_MD5, + TLS_RSA_WITH_RC4_128_MD5, + TLS_RSA_WITH_RC4_128_SHA, + TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5, + TLS_RSA_WITH_IDEA_CBC_SHA, + TLS_RSA_EXPORT_WITH_DES40_CBC_SHA, + TLS_RSA_WITH_DES_CBC_SHA, + TLS_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA, + TLS_DH_DSS_WITH_DES_CBC_SHA, + TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA, + TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA, + TLS_DH_RSA_WITH_DES_CBC_SHA, + TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, + TLS_DHE_DSS_WITH_DES_CBC_SHA, + TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, + TLS_DHE_RSA_WITH_DES_CBC_SHA, + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_DH_anon_EXPORT_WITH_RC4_40_MD5, + TLS_DH_anon_WITH_RC4_128_MD5, + TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA, + TLS_DH_anon_WITH_DES_CBC_SHA, + TLS_DH_anon_WITH_3DES_EDE_CBC_SHA + }; + + // hash for quick access to cipher suite by name + private static Hashtable cuitesByName; + + /** + * array of supported sipher suites. + * Set of supported suites is defined at the moment provider's start + */ +// TODO Dinamical supported suites: new providers may be dynamically +// added/removed and the set of supportes suites may be changed + static CipherSuite[] supportedCipherSuites; + + /** + * array of supported sipher suites names + */ + static String[] supportedCipherSuiteNames; + + /** + * default sipher suites + */ + static CipherSuite[] defaultCipherSuites; + + static { + int count = 0; + cuitesByName = new Hashtable(); + for (int i = 0; i < cuitesByCode.length; i++) { + cuitesByName.put(cuitesByCode[i].getName(), cuitesByCode[i]); + if (cuitesByCode[i].supported) { + count++; + } + } + supportedCipherSuites = new CipherSuite[count]; + supportedCipherSuiteNames = new String[count]; + count = 0; + for (int i = 0; i < cuitesByCode.length; i++) { + if (cuitesByCode[i].supported) { + supportedCipherSuites[count] = cuitesByCode[i]; + supportedCipherSuiteNames[count] = supportedCipherSuites[count].getName(); + count++; + } + } + + CipherSuite[] defaultPretendent = { + TLS_RSA_WITH_RC4_128_MD5, + TLS_RSA_WITH_RC4_128_SHA, + // TLS_RSA_WITH_AES_128_CBC_SHA, + // TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + // LS_DHE_DSS_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_DES_CBC_SHA, + TLS_DHE_RSA_WITH_DES_CBC_SHA, TLS_DHE_DSS_WITH_DES_CBC_SHA, + TLS_RSA_EXPORT_WITH_RC4_40_MD5, + TLS_RSA_EXPORT_WITH_DES40_CBC_SHA, + TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, + TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA + }; + count = 0; + for (int i = 0; i < defaultPretendent.length; i++) { + if (defaultPretendent[i].supported) { + count++; + } + } + defaultCipherSuites = new CipherSuite[count]; + count = 0; + for (int i = 0; i < defaultPretendent.length; i++) { + if (defaultPretendent[i].supported) { + defaultCipherSuites[count++] = defaultPretendent[i]; + } + } + } + + /** + * Returns CipherSuite by name + * @param name + * @return + */ + public static CipherSuite getByName(String name) { + return (CipherSuite) cuitesByName.get(name); + } + + /** + * Returns CipherSuite based on TLS CipherSuite code + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., A.5. The CipherSuite</a> + * @param b1 + * @param b2 + * @return + */ + public static CipherSuite getByCode(byte b1, byte b2) { + if (b1 != 0 || b2 > cuitesByCode.length) { + // Unknoun + return new CipherSuite("UNKNOUN_" + b1 + "_" + b2, false, 0, "", + "", new byte[] { b1, b2 }); + } + return cuitesByCode[b2]; + } + + /** + * Returns CipherSuite based on V2CipherSpec code + * as described in TLS 1.0 spec., E. Backward Compatibility With SSL + * + * @param b1 + * @param b2 + * @param b3 + * @return CipherSuite + */ + public static CipherSuite getByCode(byte b1, byte b2, byte b3) { + if (b1 == 0 && b2 == 0) { + if (b3 <= cuitesByCode.length) { + return cuitesByCode[b3]; + } + } + // as TLSv1 equivalent of V2CipherSpec should be included in + // V2ClientHello, ignore V2CipherSpec + return new CipherSuite("UNKNOUN_" + b1 + "_" + b2 + "_" + b3, false, 0, + "", "", new byte[] { b1, b2, b3 }); + } + + /** + * Creates CipherSuite + * @param name + * @param isExportable + * @param keyExchange + * @param cipherName + * @param hash + * @param code + */ + public CipherSuite(String name, boolean isExportable, int keyExchange, + String cipherName, String hash, byte[] code) { + this.name = name; + this.keyExchange = keyExchange; + this.isExportable = isExportable; + if (cipherName == null) { + this.cipherName = null; + keyMaterial = 0; + expandedKeyMaterial = 0; + effectiveKeyBytes = 0; + IVSize = 0; + blockSize = 0; + } else if ("IDEA_CBC".equals(cipherName)) { + this.cipherName = "IDEA/CBC/NoPadding"; + keyMaterial = 16; + expandedKeyMaterial = 16; + effectiveKeyBytes = 16; + IVSize = 8; + blockSize = 8; + } else if ("RC2_CBC_40".equals(cipherName)) { + this.cipherName = "RC2/CBC/NoPadding"; + keyMaterial = 5; + expandedKeyMaterial = 16; + effectiveKeyBytes = 5; + IVSize = 8; + blockSize = 8; + } else if ("RC4_40".equals(cipherName)) { + this.cipherName = "RC4"; + keyMaterial = 5; + expandedKeyMaterial = 16; + effectiveKeyBytes = 5; + IVSize = 0; + blockSize = 0; + } else if ("RC4_128".equals(cipherName)) { + this.cipherName = "RC4"; + keyMaterial = 16; + expandedKeyMaterial = 16; + effectiveKeyBytes = 16; + IVSize = 0; + blockSize = 0; + } else if ("DES40_CBC".equals(cipherName)) { + this.cipherName = "DES/CBC/NoPadding"; + keyMaterial = 5; + expandedKeyMaterial = 8; + effectiveKeyBytes = 5; + IVSize = 8; + blockSize = 8; + } else if ("DES_CBC".equals(cipherName)) { + this.cipherName = "DES/CBC/NoPadding"; + keyMaterial = 8; + expandedKeyMaterial = 8; + effectiveKeyBytes = 7; + IVSize = 8; + blockSize = 8; + } else if ("3DES_EDE_CBC".equals(cipherName)) { + this.cipherName = "DESede/CBC/NoPadding"; + keyMaterial = 24; + expandedKeyMaterial = 24; + effectiveKeyBytes = 24; + IVSize = 8; + blockSize = 8; + } else { + this.cipherName = cipherName; + keyMaterial = 0; + expandedKeyMaterial = 0; + effectiveKeyBytes = 0; + IVSize = 0; + blockSize = 0; + } + + if ("MD5".equals(hash)) { + this.hmacName = "HmacMD5"; + this.hashName = "MD5"; + hashSize = 16; + } else if ("SHA".equals(hash)) { + this.hmacName = "HmacSHA1"; + this.hashName = "SHA-1"; + hashSize = 20; + } else { + this.hmacName = null; + this.hashName = null; + hashSize = 0; + } + + cipherSuiteCode = code; + + if (this.cipherName != null) { + try { + Cipher.getInstance(this.cipherName); + } catch (GeneralSecurityException e) { + supported = false; + } + } + + } + + /** + * Returns true if cipher suite is anonymous + * @return + */ + public boolean isAnonymous() { + if (keyExchange == KeyExchange_DH_anon + || keyExchange == KeyExchange_DH_anon_EXPORT) { + return true; + } + return false; + } + + /** + * Returns array of supported CipherSuites + * @return + */ + public static CipherSuite[] getSupported() { + return supportedCipherSuites; + } + + /** + * Returns array of supported cipher suites names + * @return + */ + public static String[] getSupportedCipherSuiteNames() { + return (String[]) supportedCipherSuiteNames.clone(); + } + + /** + * Returns cipher suite name + * @return + */ + public String getName() { + return name; + } + + /** + * Returns cipher suite code as byte array + * @return + */ + public byte[] toBytes() { + return cipherSuiteCode; + } + + /** + * Returns cipher suite description + */ + public String toString() { + return name + ": " + cipherSuiteCode[0] + " " + cipherSuiteCode[1]; + } + + /** + * Compares this cipher suite to the specified object. + */ + public boolean equals(Object obj) { + if (obj instanceof CipherSuite + && this.cipherSuiteCode[0] == ((CipherSuite) obj).cipherSuiteCode[0] + && this.cipherSuiteCode[1] == ((CipherSuite) obj).cipherSuiteCode[1]) { + return true; + } + return false; + } + + /** + * Returns cipher algorithm name + * @return + */ + public String getBulkEncryptionAlgorithm() { + return cipherName; + } + + /** + * Returns cipher block size + * @return + */ + public int getBlockSize() { + return blockSize; + } + + /** + * Returns MAC algorithm name + * @return + */ + public String getHmacName() { + return hmacName; + } + + /** + * Returns hash algorithm name + * @return + */ + public String getHashName() { + return hashName; + } + + /** + * Returns hash size + * @return + */ + public int getMACLength() { + return hashSize; + } + + /** + * Indicates whether this cipher suite is exportable + * @return + */ + public boolean isExportable() { + return isExportable; + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java new file mode 100644 index 0000000..90ba0a9 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHandshakeImpl.java @@ -0,0 +1,649 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.io.IOException; +import java.security.AccessController; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PrivilegedExceptionAction; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Enumeration; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; +import javax.crypto.interfaces.DHKey; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.DHPublicKeySpec; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.X509ExtendedKeyManager; + +/** + * Client side handshake protocol implementation. + * Handshake protocol operates on top of the Record Protocol. + * It is responsible for session negotiating. + * + * The implementation proceses inbound server handshake messages, + * creates and sends respond messages. Outbound messages are supplied + * to Record Protocol. Detected errors are reported to the Alert protocol. + * + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7. The + * TLS Handshake Protocol</a> + * + */ +public class ClientHandshakeImpl extends HandshakeProtocol { + + /** + * Creates Client Handshake Implementation + * + * @param owner + */ + ClientHandshakeImpl(Object owner) { + super(owner); + } + + /** + * Starts handshake + * + */ + public void start() { + if (session == null) { // initial handshake + session = findSessionToResume(); + } else { // start session renegotiation + if (clientHello != null && this.status != FINISHED) { + // current negotiation has not completed + return; // ignore + } + if (!session.isValid()) { + session = null; + } + } + if (session != null) { + isResuming = true; + } else if (parameters.getEnableSessionCreation()){ + isResuming = false; + session = new SSLSessionImpl(parameters.getSecureRandom()); + session.protocol = ProtocolVersion.getLatestVersion(parameters + .getEnabledProtocols()); + recordProtocol.setVersion(session.protocol.version); + } else { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "SSL Session may not be created "); + } + startSession(); + } + + /** + * Starts renegotiation on a new session + * + */ + private void renegotiateNewSession() { + if (parameters.getEnableSessionCreation()){ + isResuming = false; + session = new SSLSessionImpl(parameters.getSecureRandom()); + session.protocol = ProtocolVersion.getLatestVersion(parameters + .getEnabledProtocols()); + recordProtocol.setVersion(session.protocol.version); + startSession(); + } else { + status = NOT_HANDSHAKING; + sendWarningAlert(AlertProtocol.NO_RENEGOTIATION); + } + } + + /* + * Starts/resumes session + */ + private void startSession() { + CipherSuite[] cipher_suites; + if (isResuming) { + cipher_suites = new CipherSuite[] { session.cipherSuite }; + } else { + // BEGIN android-removed + // cipher_suites = parameters.enabledCipherSuites; + // END android-removed + // BEGIN android-added + cipher_suites = parameters.getEnabledCipherSuitesMember(); + // END android-added + } + clientHello = new ClientHello(parameters.getSecureRandom(), + session.protocol.version, session.id, cipher_suites); + session.clientRandom = clientHello.random; + send(clientHello); + status = NEED_UNWRAP; + } + + /** + * Proceses inbound handshake messages + * @param bytes + */ + public void unwrap(byte[] bytes) { + if (this.delegatedTaskErr != null) { + Exception e = this.delegatedTaskErr; + this.delegatedTaskErr = null; + this.fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Error in delegated task", e); + } + int handshakeType; + io_stream.append(bytes); + while (io_stream.available() > 0) { + io_stream.mark(); + int length; + try { + handshakeType = io_stream.read(); + length = io_stream.readUint24(); + if (io_stream.available() < length) { + io_stream.reset(); + return; + } + switch (handshakeType) { + case 0: // HELLO_REQUEST + // we don't need to take this message into account + // during FINISH message verification, so remove it + io_stream.removeFromMarkedPosition(); + if (clientHello != null + && (clientFinished == null || serverFinished == null)) { + //currently negotiating - ignore + break; + } + // renegotiate + if (session.isValid()) { + session = (SSLSessionImpl) session.clone(); + isResuming = true; + startSession(); + } else { + // if SSLSession is invalidated (e.g. timeout limit is + // exceeded) connection can't resume the session. + renegotiateNewSession(); + } + break; + case 2: // SERVER_HELLO + if (clientHello == null || serverHello != null) { + unexpectedMessage(); + return; + } + serverHello = new ServerHello(io_stream, length); + + //check protocol version + ProtocolVersion servProt = ProtocolVersion + .getByVersion(serverHello.server_version); + String[] enabled = parameters.getEnabledProtocols(); + find: { + for (int i = 0; i < enabled.length; i++) { + if (servProt.equals(ProtocolVersion + .getByName(enabled[i]))) { + break find; + } + } + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Bad server hello protocol version"); + } + + // check compression method + if (serverHello.compression_method != 0) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Bad server hello compression method"); + } + + //check cipher_suite + // BEGIN android-removed + // CipherSuite[] enabledSuites = parameters.enabledCipherSuites; + // END android-removed + // BEGIN android-added + CipherSuite[] enabledSuites = parameters.getEnabledCipherSuitesMember(); + // END android-added + find: { + for (int i = 0; i < enabledSuites.length; i++) { + if (serverHello.cipher_suite + .equals(enabledSuites[i])) { + break find; + } + } + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Bad server hello cipher suite"); + } + + if (isResuming) { + if (serverHello.session_id.length == 0) { + // server is not willing to establish the new connection + // using specified session + isResuming = false; + } else if (!Arrays.equals(serverHello.session_id, clientHello.session_id)) { + isResuming = false; + } else if (!session.protocol.equals(servProt)) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Bad server hello protocol version"); + } else if (!session.cipherSuite + .equals(serverHello.cipher_suite)) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Bad server hello cipher suite"); + } + if (serverHello.server_version[1] == 1) { + computerReferenceVerifyDataTLS("server finished"); + } else { + computerReferenceVerifyDataSSLv3(SSLv3Constants.server); + } + } + session.protocol = servProt; + recordProtocol.setVersion(session.protocol.version); + session.cipherSuite = serverHello.cipher_suite; + session.id = (byte[]) serverHello.session_id.clone(); + session.serverRandom = serverHello.random; + break; + case 11: // CERTIFICATE + if (serverHello == null || serverKeyExchange != null + || serverCert != null || isResuming) { + unexpectedMessage(); + return; + } + serverCert = new CertificateMessage(io_stream, length); + break; + case 12: // SERVER_KEY_EXCHANGE + if (serverHello == null || serverKeyExchange != null + || isResuming) { + unexpectedMessage(); + return; + } + serverKeyExchange = new ServerKeyExchange(io_stream, + length, session.cipherSuite.keyExchange); + break; + case 13: // CERTIFICATE_REQUEST + if (serverCert == null || certificateRequest != null + || session.cipherSuite.isAnonymous() || isResuming) { + unexpectedMessage(); + return; + } + certificateRequest = new CertificateRequest(io_stream, + length); + break; + case 14: // SERVER_HELLO_DONE + if (serverHello == null || serverHelloDone != null + || isResuming) { + unexpectedMessage(); + return; + } + serverHelloDone = new ServerHelloDone(io_stream, length); + if (this.nonBlocking) { + delegatedTasks.add(new DelegatedTask( + new PrivilegedExceptionAction(){ + public Object run() throws Exception { + processServerHelloDone(); + return null; + } + }, + this, + AccessController.getContext())); + return; + } + processServerHelloDone(); + break; + case 20: // FINISHED + if (!changeCipherSpecReceived) { + unexpectedMessage(); + return; + } + serverFinished = new Finished(io_stream, length); + verifyFinished(serverFinished.getData()); + session.lastAccessedTime = System.currentTimeMillis(); + // BEGIN android-added + session.context = parameters.getClientSessionContext(); + // END android-added + parameters.getClientSessionContext().putSession(session); + if (isResuming) { + sendChangeCipherSpec(); + } else { + session.lastAccessedTime = System.currentTimeMillis(); + status = FINISHED; + } + // XXX there is no cleanup work + break; + default: + unexpectedMessage(); + return; + } + } catch (IOException e) { + // io stream dosn't contain complete handshake message + io_stream.reset(); + return; + } + } + + } + + /** + * Processes SSLv2 Hello message. + * SSLv2 client hello message message is an unexpected message + * for client side of handshake protocol. + * @ see TLS 1.0 spec., E.1. Version 2 client hello + * @param bytes + */ + public void unwrapSSLv2(byte[] bytes) { + unexpectedMessage(); + } + + /** + * Creates and sends Finished message + */ + protected void makeFinished() { + byte[] verify_data; + if (serverHello.server_version[1] == 1) { + verify_data = new byte[12]; + computerVerifyDataTLS("client finished", verify_data); + } else { + verify_data = new byte[36]; + computerVerifyDataSSLv3(SSLv3Constants.client, verify_data); + } + clientFinished = new Finished(verify_data); + send(clientFinished); + if (isResuming) { + session.lastAccessedTime = System.currentTimeMillis(); + status = FINISHED; + } else { + if (serverHello.server_version[1] == 1) { + computerReferenceVerifyDataTLS("server finished"); + } else { + computerReferenceVerifyDataSSLv3(SSLv3Constants.server); + } + status = NEED_UNWRAP; + } + } + + /** + * Processes ServerHelloDone: makes verification of the server messages; sends + * client messages, computers masterSecret, sends ChangeCipherSpec + */ + void processServerHelloDone() { + PrivateKey clientKey = null; + + if (serverCert != null) { + if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) { + unexpectedMessage(); + return; + } + verifyServerCert(); + } else { + if (session.cipherSuite.keyExchange != CipherSuite.KeyExchange_DH_anon + && session.cipherSuite.keyExchange != CipherSuite.KeyExchange_DH_anon_EXPORT) { + unexpectedMessage(); + return; + } + } + + // Client certificate + if (certificateRequest != null) { + X509Certificate[] certs = null; + String clientAlias = ((X509ExtendedKeyManager) parameters + .getKeyManager()).chooseClientAlias(certificateRequest + .getTypesAsString(), + certificateRequest.certificate_authorities, null); + if (clientAlias != null) { + X509ExtendedKeyManager km = (X509ExtendedKeyManager) parameters + .getKeyManager(); + certs = km.getCertificateChain((clientAlias)); + clientKey = km.getPrivateKey(clientAlias); + } + session.localCertificates = certs; + clientCert = new CertificateMessage(certs); + send(clientCert); + } + // Client key exchange + if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT) { + // RSA encrypted premaster secret message + Cipher c; + try { + c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + if (serverKeyExchange != null) { + c.init(Cipher.ENCRYPT_MODE, serverKeyExchange + .getRSAPublicKey()); + } else { + c.init(Cipher.ENCRYPT_MODE, serverCert.certs[0]); + } + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, + "Unexpected exception", e); + return; + } + preMasterSecret = new byte[48]; + parameters.getSecureRandom().nextBytes(preMasterSecret); + System.arraycopy(clientHello.client_version, 0, preMasterSecret, 0, + 2); + try { + clientKeyExchange = new ClientKeyExchange(c + .doFinal(preMasterSecret), + serverHello.server_version[1] == 1); + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, + "Unexpected exception", e); + return; + } + } else { + PublicKey serverPublic; + KeyAgreement agreement = null; + DHParameterSpec spec; + try { + KeyFactory kf = null; + try { + kf = KeyFactory.getInstance("DH"); + } catch (NoSuchAlgorithmException e) { + kf = KeyFactory.getInstance("DiffieHellman"); + } + + try { + agreement = KeyAgreement.getInstance("DH"); + } catch (NoSuchAlgorithmException ee) { + agreement = KeyAgreement.getInstance("DiffieHellman"); + } + + KeyPairGenerator kpg = null; + try { + kpg = KeyPairGenerator.getInstance("DH"); + } catch (NoSuchAlgorithmException e) { + kpg = KeyPairGenerator.getInstance("DiffieHellman"); + } + if (serverKeyExchange != null) { + serverPublic = kf.generatePublic(new DHPublicKeySpec( + serverKeyExchange.par3, serverKeyExchange.par1, + serverKeyExchange.par2)); + spec = new DHParameterSpec(serverKeyExchange.par1, + serverKeyExchange.par2); + } else { + serverPublic = serverCert.certs[0].getPublicKey(); + spec = ((DHPublicKey) serverPublic).getParams(); + } + kpg.initialize(spec); + + KeyPair kp = kpg.generateKeyPair(); + Key key = kp.getPublic(); + if (clientCert != null + && serverCert != null + && (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS)) { + PublicKey client_pk = clientCert.certs[0].getPublicKey(); + PublicKey server_pk = serverCert.certs[0].getPublicKey(); + if (client_pk instanceof DHKey + && server_pk instanceof DHKey) { + if (((DHKey) client_pk).getParams().getG().equals( + ((DHKey) server_pk).getParams().getG()) + && ((DHKey) client_pk).getParams().getP() + .equals(((DHKey) server_pk).getParams().getG())) { + // client cert message DH public key parameters + // matched those specified by the + // server in its certificate, + clientKeyExchange = new ClientKeyExchange(); // empty + } + } + } else { + clientKeyExchange = new ClientKeyExchange( + ((DHPublicKey) key).getY()); + } + key = kp.getPrivate(); + agreement.init(key); + agreement.doPhase(serverPublic, true); + preMasterSecret = agreement.generateSecret(); + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, + "Unexpected exception", e); + return; + } + } + if (clientKeyExchange != null) { + send(clientKeyExchange); + } + + computerMasterSecret(); + + // send certificate verify for all certificates except those containing + // fixed DH parameters + if (clientCert != null && !clientKeyExchange.isEmpty()) { + // Certificate verify + DigitalSignature ds = new DigitalSignature( + session.cipherSuite.keyExchange); + ds.init(clientKey); + + if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA_EXPORT) { + ds.setMD5(io_stream.getDigestMD5()); + ds.setSHA(io_stream.getDigestSHA()); + } else if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS_EXPORT) { + ds.setSHA(io_stream.getDigestSHA()); + // The Signature should be empty in case of anonimous signature algorithm: + // } else if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon || + // session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) { + } + certificateVerify = new CertificateVerify(ds.sign()); + send(certificateVerify); + } + + sendChangeCipherSpec(); + } + + /* + * Verifies certificate path + */ + private void verifyServerCert() { + String authType = null; + switch (session.cipherSuite.keyExchange) { + case 1: // KeyExchange_RSA + authType = "RSA"; + break; + case 2: // KeyExchange_RSA_EXPORT + if (serverKeyExchange != null ) { + // ephemeral RSA key is used + authType = "RSA_EXPORT"; + } else { + authType = "RSA"; + } + break; + case 3: // KeyExchange_DHE_DSS + case 4: // KeyExchange_DHE_DSS_EXPORT + authType = "DHE_DSS"; + break; + case 5: // KeyExchange_DHE_RSA + case 6: // KeyExchange_DHE_RSA_EXPORT + authType = "DHE_RSA"; + break; + case 7: // KeyExchange_DH_DSS + case 11: // KeyExchange_DH_DSS_EXPORT + authType = "DH_DSS"; + break; + case 8: // KeyExchange_DH_RSA + case 12: // KeyExchange_DH_RSA_EXPORT + authType = "DH_RSA"; + break; + case 9: // KeyExchange_DH_anon + case 10: // KeyExchange_DH_anon_EXPORT + return; + } + try { + parameters.getTrustManager().checkServerTrusted(serverCert.certs, + authType); + } catch (CertificateException e) { + fatalAlert(AlertProtocol.BAD_CERTIFICATE, "Not trusted server certificate", e); + return; + } + session.peerCertificates = serverCert.certs; + } + + /** + * Proceses ChangeCipherSpec message + */ + public void receiveChangeCipherSpec() { + if (isResuming) { + if (serverHello == null) { + unexpectedMessage(); + } + } else if (clientFinished == null) { + unexpectedMessage(); + } + changeCipherSpecReceived = true; + } + + // Find session to resume in client session context + private SSLSessionImpl findSessionToResume() { + // BEGIN android-removed + // String host; + // int port; + // END android-removed + // BEGIN android-added + String host = null; + int port = -1; + // END android-added + if (engineOwner != null) { + host = engineOwner.getPeerHost(); + port = engineOwner.getPeerPort(); + // BEGIN android-removed + // } else { + // host = socketOwner.getInetAddress().getHostName(); + // port = socketOwner.getPort(); + // END android-removed + } + if (host == null || port == -1) { + return null; // starts new session + } + + byte[] id; + SSLSession ses; + SSLSessionContext context = parameters.getClientSessionContext(); + for (Enumeration en = context.getIds(); en.hasMoreElements();) { + id = (byte[])en.nextElement(); + ses = context.getSession(id); + if (host.equals(ses.getPeerHost()) && port == ses.getPeerPort()) { + return (SSLSessionImpl)((SSLSessionImpl)ses).clone(); // resume + } + } + return null; // starts new session + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHello.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHello.java new file mode 100644 index 0000000..aa811fb --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientHello.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ + +package org.apache.harmony.xnet.provider.jsse; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Arrays; + +/** + * + * Represents Client Hello message + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.1.2. + * Client hello</a> + * + */ +public class ClientHello extends Message { + + /** + * Client version + */ + final byte[] client_version; + + /** + * Random bytes + */ + final byte[] random = new byte[32]; + + /** + * Session id + */ + final byte[] session_id; + + /** + * Cipher suites supported by the client + */ + final CipherSuite[] cipher_suites; + + /** + * Compression methods supported by the client + */ + final byte[] compression_methods; + + /** + * Creates outbound message + * @param sr + * @param version + * @param ses_id + * @param cipher_suite + */ + public ClientHello(SecureRandom sr, byte[] version, byte[] ses_id, + CipherSuite[] cipher_suite) { + client_version = version; + long gmt_unix_time = System.currentTimeMillis()/1000; + sr.nextBytes(random); + random[0] = (byte) (gmt_unix_time & 0xFF000000 >>> 24); + random[1] = (byte) (gmt_unix_time & 0xFF0000 >>> 16); + random[2] = (byte) (gmt_unix_time & 0xFF00 >>> 8); + random[3] = (byte) (gmt_unix_time & 0xFF); + session_id = ses_id; + this.cipher_suites = cipher_suite; + compression_methods = new byte[] { 0 }; // CompressionMethod.null + length = 38 + session_id.length + (this.cipher_suites.length << 1) + + compression_methods.length; + } + + /** + * Creates inbound message + * @param in + * @param length + * @throws IOException + */ + public ClientHello(HandshakeIODataStream in, int length) throws IOException { + client_version = new byte[2]; + client_version[0] = (byte) in.readUint8(); + client_version[1] = (byte) in.readUint8(); + in.read(random, 0, 32); + int size = in.read(); + session_id = new byte[size]; + in.read(session_id, 0, size); + int l = in.readUint16(); + if ((l & 0x01) == 0x01) { // cipher suites length must be an even number + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect ClientHello"); + } + size = l >> 1; + cipher_suites = new CipherSuite[size]; + for (int i = 0; i < size; i++) { + byte b0 = (byte) in.read(); + byte b1 = (byte) in.read(); + cipher_suites[i] = CipherSuite.getByCode(b0, b1); + } + size = in.read(); + compression_methods = new byte[size]; + in.read(compression_methods, 0, size); + this.length = 38 + session_id.length + (cipher_suites.length << 1) + + compression_methods.length; + if (this.length > length) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ClientHello"); + } + // for forward compatibility, extra data is permitted; + // must be ignored + if (this.length < length) { + in.skip(length - this.length); + this.length = length; + } + } + /** + * Parse V2ClientHello + * @param in + * @throws IOException + */ + public ClientHello(HandshakeIODataStream in) throws IOException { + if (in.readUint8() != 1) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello"); + } + client_version = new byte[2]; + client_version[0] = (byte) in.readUint8(); + client_version[1] = (byte) in.readUint8(); + int cipher_spec_length = in.readUint16(); + if (in.readUint16() != 0) { // session_id_length + // as client already knows the protocol known to a server it should + // initiate the connection in that native protocol + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect V2ClientHello, cannot be used for resuming"); + } + int challenge_length = in.readUint16(); + if (challenge_length < 16) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello, short challenge data"); + } + session_id = new byte[0]; + cipher_suites = new CipherSuite[cipher_spec_length/3]; + for (int i = 0; i < cipher_suites.length; i++) { + byte b0 = (byte) in.read(); + byte b1 = (byte) in.read(); + byte b2 = (byte) in.read(); + cipher_suites[i] = CipherSuite.getByCode(b0, b1, b2); + } + compression_methods = new byte[] { 0 }; // CompressionMethod.null + + if (challenge_length < 32) { + Arrays.fill(random, 0, 32 - challenge_length, (byte)0); + System.arraycopy(in.read(challenge_length), 0, random, 32 - challenge_length, challenge_length); + } else if (challenge_length == 32) { + System.arraycopy(in.read(32), 0, random, 0, 32); + } else { + System.arraycopy(in.read(challenge_length), challenge_length - 32, random, 0, 32); + } + if (in.available() > 0) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello, extra data"); + } + this.length = 38 + session_id.length + (cipher_suites.length << 1) + + compression_methods.length; + } + + /** + * Sends message + * @param out + */ + public void send(HandshakeIODataStream out) { + out.write(client_version); + out.write(random); + out.writeUint8(session_id.length); + out.write(session_id); + int size = cipher_suites.length << 1; + out.writeUint16(size); + for (int i = 0; i < cipher_suites.length; i++) { + out.write(cipher_suites[i].toBytes()); + } + out.writeUint8(compression_methods.length); + for (int i = 0; i < compression_methods.length; i++) { + out.write(compression_methods[i]); + } + } + + /** + * Returns client random + * @return client random + */ + public byte[] getRandom() { + return random; + } + + /** + * Returns message type + * @return + */ + public int getType() { + return Handshake.CLIENT_HELLO; + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientKeyExchange.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientKeyExchange.java new file mode 100644 index 0000000..a208456 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientKeyExchange.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.Message; +import org.apache.harmony.xnet.provider.jsse.Handshake; +import org.apache.harmony.xnet.provider.jsse.HandshakeIODataStream; + +import java.io.IOException; +import java.math.BigInteger; + +/** + * + * Represents client key exchange message + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.7. + * Client key exchange message</a> + * + */ +public class ClientKeyExchange extends Message { + + /** + * Exchange keys + */ + final byte[] exchange_keys; + + /** + * Equals true if TLS1.0 protocol is used + */ + boolean isTLS; + + /** + * Equals true if key exchange algorithm is RSA + */ + final boolean isRSA; + + /** + * Creates outbound message + * @param encrypted_pre_master_secret + * @param isTLS + */ + public ClientKeyExchange(byte[] encrypted_pre_master_secret, boolean isTLS) { + this.exchange_keys = encrypted_pre_master_secret; + length = this.exchange_keys.length; + if (isTLS) { + length += 2; + } + this.isTLS = isTLS; + isRSA = true; + } + + /** + * Creates outbound message + * @param dh_Yc + */ + public ClientKeyExchange(BigInteger dh_Yc) { + byte[] bb = dh_Yc.toByteArray(); + if (bb[0] == 0) { + exchange_keys = new byte[bb.length-1]; + System.arraycopy(bb, 1, exchange_keys, 0, exchange_keys.length); + } else { + exchange_keys = bb; + } + length = exchange_keys.length +2; + isRSA = false; + } + + /** + * Creates empty message + * + */ + public ClientKeyExchange() { + exchange_keys = new byte[0]; + length = 0; + isRSA = false; + } + + /** + * Creates inbound message + * @param length + * @param isTLS + * @param isRSA + * @throws IOException + */ + public ClientKeyExchange(HandshakeIODataStream in, int length, boolean isTLS, boolean isRSA) + throws IOException { + this.isTLS = isTLS; + this.isRSA = isRSA; + if (length == 0) { + this.length = 0; + exchange_keys = new byte[0]; + } else { + int size; + if (isRSA && !isTLS) {// SSL3.0 RSA + size = length; + this.length = size; + } else { // DH or TLSv1 RSA + size = in.readUint16(); + this.length = 2 + size; + } + exchange_keys = new byte[size]; + in.read(exchange_keys, 0, size); + if (this.length != length) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ClientKeyExchange"); + } + } + } + + /** + * Sends message + * @param out + */ + public void send(HandshakeIODataStream out) { + if (exchange_keys.length != 0) { + if (!isRSA || isTLS) {// DH or TLSv1 RSA + out.writeUint16(exchange_keys.length); + } + out.write(exchange_keys); + } + } + + /** + * Returns message type + * @return + */ + public int getType() { + return Handshake.CLIENT_KEY_EXCHANGE; + } + + /** + * Returns true if the message is empty (in case of implicit DH Yc) + * @return + */ + public boolean isEmpty() { + return (exchange_keys.length == 0); + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionState.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionState.java new file mode 100644 index 0000000..63bad5d --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionState.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.Logger; + +import javax.crypto.Cipher; + +/** + * This abstract class is a base for Record Protocol operating environmet + * of different SSL protocol versions. + */ +public abstract class ConnectionState { + + /** + * The cipher used for encode operations + */ + protected Cipher encCipher; + + /** + * The cipher used for decode operations + */ + protected Cipher decCipher; + + /** + * The cipher type + */ + protected boolean is_block_cipher; + + /** + * The size of MAC used under this connection state + */ + protected int hash_size; + + /** + * Write sequence number which is incremented after each + * encrypt call + */ + protected final byte[] write_seq_num = {0, 0, 0, 0, 0, 0, 0, 0}; + + /** + * Read sequence number which is incremented after each + * decrypt call + */ + protected final byte[] read_seq_num = {0, 0, 0, 0, 0, 0, 0, 0}; + + protected Logger.Stream logger = Logger.getStream("conn_state"); + + /** + * Returns the minimal possible size of the + * Generic[Stream|Generic]Cipher structure under this + * connection state. + */ + protected int getMinFragmentSize() { + // block ciphers return value with padding included + return encCipher.getOutputSize(1+hash_size); // 1 byte for data + } + + /** + * Returns the size of the Generic[Stream|Generic]Cipher structure + * corresponding to the content data of specified size. + */ + protected int getFragmentSize(int content_size) { + return encCipher.getOutputSize(content_size+hash_size); + } + + /** + * Returns the minimal upper bound of the content size enclosed + * into the Generic[Stream|Generic]Cipher structure of specified size. + * For stream ciphers the returned value will be exact value. + */ + protected int getContentSize(int generic_cipher_size) { + //it does not take the padding of block ciphered structures + //into account (so returned value can be greater than actual) + return decCipher.getOutputSize(generic_cipher_size)-hash_size; + } + + /** + * Creates the GenericStreamCipher or GenericBlockCipher + * data structure for specified data of specified type. + * @param type - the ContentType of the provided data + * @param fragment - the byte array containing the + * data to be encrypted under the current connection state. + */ + protected byte[] encrypt(byte type, byte[] fragment) { + return encrypt(type, fragment, 0, fragment.length); + } + + /** + * Creates the GenericStreamCipher or GenericBlockCipher + * data structure for specified data of specified type. + * @param type - the ContentType of the provided data + * @param fragment - the byte array containing the + * data to be encrypted under the current connection state. + * @param offset - the offset from which the data begins with. + * @param len - the length of the data. + */ + protected abstract byte[] encrypt + (byte type, byte[] fragment, int offset, int len); + + /** + * Retrieves the fragment of the Plaintext structure of + * the specified type from the provided data. + * @param type - the ContentType of the data to be decrypted. + * @param fragment - the byte array containing the + * data to be encrypted under the current connection state. + */ + protected byte[] decrypt(byte type, byte[] fragment) { + return decrypt(type, fragment, 0, fragment.length); + } + + /** + * Retrieves the fragment of the Plaintext structure of + * the specified type from the provided data. + * @param type - the ContentType of the data to be decrypted. + * @param fragment - the byte array containing the + * data to be encrypted under the current connection state. + * @param offset - the offset from which the data begins with. + * @param len - the length of the data. + */ + protected abstract byte[] decrypt + (byte type, byte[] fragment, int offset, int len); + + /** + * Increments the sequence number. + */ + protected static void incSequenceNumber(byte[] seq_num) { + int octet = 7; + while (octet >= 0) { + seq_num[octet] ++; + if (seq_num[octet] == 0) { + // characteristic overflow, so + // carrying a number in adding + octet --; + } else { + return; + } + } + } + + /** + * Shutdownes the protocol. It will be impossiblke to use the instance + * after the calling of this method. + */ + protected void shutdown() { + encCipher = null; + decCipher = null; + for (int i=0; i<write_seq_num.length; i++) { + write_seq_num[i] = 0; + read_seq_num[i] = 0; + } + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateSSLv3.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateSSLv3.java new file mode 100644 index 0000000..078bf58 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateSSLv3.java @@ -0,0 +1,356 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.util.Arrays; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.net.ssl.SSLProtocolException; + +/** + * This class incapsulates the operating environment of the SSL v3 + * (http://wp.netscape.com/eng/ssl3) Record Protocol and provides + * relating encryption/decryption functionality. + * The work functionality is based on the security + * parameters negotiated during the handshake. + */ +public class ConnectionStateSSLv3 extends ConnectionState { + + // digest to create and check the message integrity info + private final MessageDigest messageDigest; + private final byte[] mac_write_secret; + private final byte[] mac_read_secret; + + // paddings + private final byte[] pad_1; + private final byte[] pad_2; + // array will hold the part of the MAC material: + // length of 3 == 1(SSLCompressed.type) + 2(SSLCompressed.length) + // (more on SSLv3 MAC computation and payload protection see + // SSL v3 specification, p. 5.2.3) + private final byte[] mac_material_part = new byte[3]; + + /** + * Creates the instance of SSL v3 Connection State. All of the + * security parameters are provided by session object. + * @param session: the sessin object which incapsulates + * all of the security parameters established by handshake protocol. + * The key calculation for the state is done according + * to the SSL v3 Protocol specification. + * (http://www.mozilla.org/projects/security/pki/nss/ssl/draft302.txt) + */ + protected ConnectionStateSSLv3(SSLSessionImpl session) { + try { + CipherSuite cipherSuite = session.cipherSuite; + + boolean is_exportabe = cipherSuite.isExportable(); + hash_size = cipherSuite.getMACLength(); + int key_size = (is_exportabe) + ? cipherSuite.keyMaterial + : cipherSuite.expandedKeyMaterial; + int iv_size = cipherSuite.getBlockSize(); + + String algName = cipherSuite.getBulkEncryptionAlgorithm(); + String hashName = cipherSuite.getHashName(); + if (logger != null) { + logger.println("ConnectionStateSSLv3.create:"); + logger.println(" cipher suite name: " + + session.getCipherSuite()); + logger.println(" encryption alg name: " + algName); + logger.println(" hash alg name: " + hashName); + logger.println(" hash size: " + hash_size); + logger.println(" block size: " + iv_size); + logger.println(" IV size (== block size):" + iv_size); + logger.println(" key size: " + key_size); + } + + byte[] clientRandom = session.clientRandom; + byte[] serverRandom = session.serverRandom; + // so we need PRF value of size of + // 2*hash_size + 2*key_size + 2*iv_size + byte[] key_block = new byte[2*hash_size + 2*key_size + 2*iv_size]; + byte[] seed = new byte[clientRandom.length + serverRandom.length]; + System.arraycopy(serverRandom, 0, seed, 0, serverRandom.length); + System.arraycopy(clientRandom, 0, seed, serverRandom.length, + clientRandom.length); + + PRF.computePRF_SSLv3(key_block, session.master_secret, seed); + + byte[] client_mac_secret = new byte[hash_size]; + byte[] server_mac_secret = new byte[hash_size]; + byte[] client_key = new byte[key_size]; + byte[] server_key = new byte[key_size]; + + boolean is_client = !session.isServer; + + is_block_cipher = (iv_size > 0); + + System.arraycopy(key_block, 0, client_mac_secret, 0, hash_size); + System.arraycopy(key_block, hash_size, + server_mac_secret, 0, hash_size); + System.arraycopy(key_block, 2*hash_size, client_key, 0, key_size); + System.arraycopy(key_block, 2*hash_size+key_size, + server_key, 0, key_size); + + IvParameterSpec clientIV = null; + IvParameterSpec serverIV = null; + + if (is_exportabe) { + if (logger != null) { + logger.println("ConnectionStateSSLv3: is_exportable"); + } + + MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(client_key); + md5.update(clientRandom); + md5.update(serverRandom); + client_key = md5.digest(); + + md5.update(server_key); + md5.update(serverRandom); + md5.update(clientRandom); + server_key = md5.digest(); + + key_size = cipherSuite.expandedKeyMaterial; + + if (is_block_cipher) { + md5.update(clientRandom); + md5.update(serverRandom); + clientIV = new IvParameterSpec(md5.digest(), 0, iv_size); + md5.update(serverRandom); + md5.update(clientRandom); + serverIV = new IvParameterSpec(md5.digest(), 0, iv_size); + } + } else if (is_block_cipher) { + clientIV = new IvParameterSpec(key_block, + 2*hash_size+2*key_size, iv_size); + serverIV = new IvParameterSpec(key_block, + 2*hash_size+2*key_size+iv_size, iv_size); + } + + if (logger != null) { + logger.println("is exportable: "+is_exportabe); + logger.println("master_secret"); + logger.print(session.master_secret); + logger.println("client_random"); + logger.print(clientRandom); + logger.println("server_random"); + logger.print(serverRandom); + //logger.println("key_block"); + //logger.print(key_block); + logger.println("client_mac_secret"); + logger.print(client_mac_secret); + logger.println("server_mac_secret"); + logger.print(server_mac_secret); + logger.println("client_key"); + logger.print(client_key, 0, key_size); + logger.println("server_key"); + logger.print(server_key, 0, key_size); + if (clientIV != null) { + logger.println("client_iv"); + logger.print(clientIV.getIV()); + logger.println("server_iv"); + logger.print(serverIV.getIV()); + } else { + logger.println("no IV."); + } + } + encCipher = Cipher.getInstance(algName); + decCipher = Cipher.getInstance(algName); + messageDigest = MessageDigest.getInstance(hashName); + if (is_client) { // client side + encCipher.init(Cipher.ENCRYPT_MODE, + new SecretKeySpec(client_key, 0, key_size, algName), + clientIV); + decCipher.init(Cipher.DECRYPT_MODE, + new SecretKeySpec(server_key, 0, key_size, algName), + serverIV); + mac_write_secret = client_mac_secret; + mac_read_secret = server_mac_secret; + } else { // server side + encCipher.init(Cipher.ENCRYPT_MODE, + new SecretKeySpec(server_key, 0, key_size, algName), + serverIV); + decCipher.init(Cipher.DECRYPT_MODE, + new SecretKeySpec(client_key, 0, key_size, algName), + clientIV); + mac_write_secret = server_mac_secret; + mac_read_secret = client_mac_secret; + } + if (hashName.equals("MD5")) { + pad_1 = SSLv3Constants.MD5pad1; + pad_2 = SSLv3Constants.MD5pad2; + } else { + pad_1 = SSLv3Constants.SHApad1; + pad_2 = SSLv3Constants.SHApad2; + } + } catch (Exception e) { + e.printStackTrace(); + throw new AlertException(AlertProtocol.INTERNAL_ERROR, + new SSLProtocolException( + "Error during computation of security parameters")); + } + } + + /** + * Creates the GenericStreamCipher or GenericBlockCipher + * data structure for specified data of specified type. + * @throws org.apache.harmony.xnet.provider.jsse.AlertException if alert was occured. + */ + protected byte[] encrypt(byte type, byte[] fragment, int offset, int len) { + try { + int content_mac_length = len + hash_size; + int padding_length = is_block_cipher + ? padding_length = + ((8 - (++content_mac_length & 0x07)) & 0x07) + : 0; + byte[] res = new byte[content_mac_length + padding_length]; + System.arraycopy(fragment, offset, res, 0, len); + + mac_material_part[0] = type; + mac_material_part[1] = (byte) ((0x00FF00 & len) >> 8); + mac_material_part[2] = (byte) (0x0000FF & len); + + messageDigest.update(mac_write_secret); + messageDigest.update(pad_1); + messageDigest.update(write_seq_num); + messageDigest.update(mac_material_part); + messageDigest.update(fragment, offset, len); + byte[] digest = messageDigest.digest(); + messageDigest.update(mac_write_secret); + messageDigest.update(pad_2); + messageDigest.update(digest); + digest = messageDigest.digest(); + System.arraycopy(digest, 0, res, len, hash_size); + + //if (logger != null) { + // logger.println("MAC Material:"); + // logger.print(write_seq_num); + // logger.print(mac_material_header); + // logger.print(fragment, offset, len); + //} + + if (is_block_cipher) { + // do padding: + Arrays.fill(res, content_mac_length-1, + res.length, (byte) (padding_length)); + } + if (logger != null) { + logger.println("SSLRecordProtocol.encrypt: " + + (is_block_cipher + ? "GenericBlockCipher with padding[" + +padding_length+"]:" + : "GenericStreamCipher:")); + logger.print(res); + } + byte[] rez = new byte[encCipher.getOutputSize(res.length)]; + encCipher.update(res, 0, res.length, rez); + incSequenceNumber(write_seq_num); + return rez; + } catch (GeneralSecurityException e) { + e.printStackTrace(); + throw new AlertException(AlertProtocol.INTERNAL_ERROR, + new SSLProtocolException("Error during the encryption")); + } + } + + /** + * Retrieves the fragment of the Plaintext structure of + * the specified type from the provided data. + * @throws AlertException if alert was occured. + */ + protected byte[] decrypt(byte type, byte[] fragment, + int offset, int len) { + // plain data of the Generic[Stream|Block]Cipher structure + byte[] data = decCipher.update(fragment, offset, len); + // the 'content' part of the structure + byte[] content; + if (is_block_cipher) { + // check padding + int padding_length = data[data.length-1]; + for (int i=0; i<padding_length; i++) { + if (data[data.length-2-i] != padding_length) { + throw new AlertException( + AlertProtocol.DECRYPTION_FAILED, + new SSLProtocolException( + "Received message has bad padding")); + } + } + content = new byte[data.length - hash_size - padding_length - 1]; + } else { + content = new byte[data.length - hash_size]; + } + + byte[] mac_value; + + mac_material_part[0] = type; + mac_material_part[1] = (byte) ((0x00FF00 & content.length) >> 8); + mac_material_part[2] = (byte) (0x0000FF & content.length); + + messageDigest.update(mac_read_secret); + messageDigest.update(pad_1); + messageDigest.update(read_seq_num); + messageDigest.update(mac_material_part); + messageDigest.update(data, 0, content.length); + mac_value = messageDigest.digest(); + messageDigest.update(mac_read_secret); + messageDigest.update(pad_2); + messageDigest.update(mac_value); + mac_value = messageDigest.digest(); + + if (logger != null) { + logger.println("Decrypted:"); + logger.print(data); + //logger.println("MAC Material:"); + //logger.print(read_seq_num); + //logger.print(mac_material_header); + //logger.print(data, 0, content.length); + logger.println("Expected mac value:"); + logger.print(mac_value); + } + // checking the mac value + for (int i=0; i<hash_size; i++) { + if (mac_value[i] != data[i+content.length]) { + throw new AlertException(AlertProtocol.BAD_RECORD_MAC, + new SSLProtocolException("Bad record MAC")); + } + } + System.arraycopy(data, 0, content, 0, content.length); + incSequenceNumber(read_seq_num); + return content; + } + + /** + * Shutdownes the protocol. It will be impossiblke to use the instance + * after the calling of this method. + */ + protected void shutdown() { + Arrays.fill(mac_write_secret, (byte) 0); + Arrays.fill(mac_read_secret, (byte) 0); + super.shutdown(); + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateTLS.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateTLS.java new file mode 100644 index 0000000..1b21b17 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ConnectionStateTLS.java @@ -0,0 +1,355 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.AlertException; +import org.apache.harmony.xnet.provider.jsse.SSLSessionImpl; +import org.apache.harmony.xnet.provider.jsse.PRF; +import org.apache.harmony.xnet.provider.jsse.ConnectionState; + +import java.security.GeneralSecurityException; +import java.util.Arrays; +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.net.ssl.SSLProtocolException; + +/** + * This class incapsulates the operating environment of the TLS v1 + * (http://www.ietf.org/rfc/rfc2246.txt) Record Protocol and provides + * relating encryption/decryption functionality. + * The work functionality is based on the security + * parameters negotiated during the handshake. + */ +public class ConnectionStateTLS extends ConnectionState { + + // Precomputed prf label values: + // "key expansion".getBytes() + private static byte[] KEY_EXPANSION_LABEL = { + (byte) 0x6B, (byte) 0x65, (byte) 0x79, (byte) 0x20, (byte) 0x65, + (byte) 0x78, (byte) 0x70, (byte) 0x61, (byte) 0x6E, (byte) 0x73, + (byte) 0x69, (byte) 0x6F, (byte) 0x6E }; + + // "client write key".getBytes() + private static byte[] CLIENT_WRITE_KEY_LABEL = { + (byte) 0x63, (byte) 0x6C, (byte) 0x69, (byte) 0x65, (byte) 0x6E, + (byte) 0x74, (byte) 0x20, (byte) 0x77, (byte) 0x72, (byte) 0x69, + (byte) 0x74, (byte) 0x65, (byte) 0x20, (byte) 0x6B, (byte) 0x65, + (byte) 0x79 }; + + // "server write key".getBytes() + private static byte[] SERVER_WRITE_KEY_LABEL = { + (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65, + (byte) 0x72, (byte) 0x20, (byte) 0x77, (byte) 0x72, (byte) 0x69, + (byte) 0x74, (byte) 0x65, (byte) 0x20, (byte) 0x6B, (byte) 0x65, + (byte) 0x79 }; + + // "IV block".getBytes() + private static byte[] IV_BLOCK_LABEL = { + (byte) 0x49, (byte) 0x56, (byte) 0x20, (byte) 0x62, (byte) 0x6C, + (byte) 0x6F, (byte) 0x63, (byte) 0x6B }; + + // MACs to create and check the message integrity info + private final Mac encMac; + private final Mac decMac; + + // Once created permanently used array: + // is used to create the header of the MAC material value: + // 5 == 1(TLSCompressed.type) + 2(TLSCompressed.version) + + // 2(TLSCompressed.length) + private final byte[] mac_material_header = new byte[] {0, 3, 1, 0, 0}; + + /** + * Creates the instance of TLS v1 Connection State. All of the + * security parameters are provided by session object. + * @param session: the sessin object which incapsulates + * all of the security parameters established by handshake protocol. + * The key calculation for the state is done according + * to the TLS v 1.0 Protocol specification. + * (http://www.ietf.org/rfc/rfc2246.txt) + */ + protected ConnectionStateTLS(SSLSessionImpl session) { + try { + CipherSuite cipherSuite = session.cipherSuite; + + hash_size = cipherSuite.getMACLength(); + boolean is_exportabe = cipherSuite.isExportable(); + int key_size = (is_exportabe) + ? cipherSuite.keyMaterial + : cipherSuite.expandedKeyMaterial; + int iv_size = cipherSuite.getBlockSize(); + + String algName = cipherSuite.getBulkEncryptionAlgorithm(); + String macName = cipherSuite.getHmacName(); + if (logger != null) { + logger.println("ConnectionStateTLS.create:"); + logger.println(" cipher suite name: " + + cipherSuite.getName()); + logger.println(" encryption alg name: " + algName); + logger.println(" mac alg name: " + macName); + logger.println(" hash size: " + hash_size); + logger.println(" block size: " + iv_size); + logger.println(" IV size (== block size):" + iv_size); + logger.println(" key size: " + key_size); + } + + byte[] clientRandom = session.clientRandom; + byte[] serverRandom = session.serverRandom; + // so we need PRF value of size of + // 2*hash_size + 2*key_size + 2*iv_size + byte[] key_block = new byte[2*hash_size + 2*key_size + 2*iv_size]; + byte[] seed = new byte[clientRandom.length + serverRandom.length]; + System.arraycopy(serverRandom, 0, seed, 0, serverRandom.length); + System.arraycopy(clientRandom, 0, seed, serverRandom.length, + clientRandom.length); + + PRF.computePRF(key_block, session.master_secret, + KEY_EXPANSION_LABEL, seed); + + byte[] client_mac_secret = new byte[hash_size]; + byte[] server_mac_secret = new byte[hash_size]; + byte[] client_key = new byte[key_size]; + byte[] server_key = new byte[key_size]; + + boolean is_client = !session.isServer; + + is_block_cipher = (iv_size > 0); + // do not count, as block_size is always 8 + // block_size = iv_size; + + System.arraycopy(key_block, 0, client_mac_secret, 0, hash_size); + System.arraycopy(key_block, hash_size, + server_mac_secret, 0, hash_size); + System.arraycopy(key_block, 2*hash_size, client_key, 0, key_size); + System.arraycopy(key_block, 2*hash_size+key_size, + server_key, 0, key_size); + + IvParameterSpec clientIV = null; + IvParameterSpec serverIV = null; + + if (is_exportabe) { + System.arraycopy(clientRandom, 0, + seed, 0, clientRandom.length); + System.arraycopy(serverRandom, 0, + seed, clientRandom.length, serverRandom.length); + byte[] final_client_key = + new byte[cipherSuite.expandedKeyMaterial]; + byte[] final_server_key = + new byte[cipherSuite.expandedKeyMaterial]; + PRF.computePRF(final_client_key, client_key, + CLIENT_WRITE_KEY_LABEL, seed); + PRF.computePRF(final_server_key, server_key, + SERVER_WRITE_KEY_LABEL, seed); + client_key = final_client_key; + server_key = final_server_key; + if (is_block_cipher) { + byte[] iv_block = new byte[2*iv_size]; + PRF.computePRF(iv_block, null, IV_BLOCK_LABEL, seed); + clientIV = new IvParameterSpec(iv_block, 0, iv_size); + serverIV = new IvParameterSpec(iv_block, iv_size, iv_size); + } + } else if (is_block_cipher) { + clientIV = new IvParameterSpec(key_block, + 2*(hash_size+key_size), iv_size); + serverIV = new IvParameterSpec(key_block, + 2*(hash_size+key_size)+iv_size, iv_size); + } + + if (logger != null) { + logger.println("is exportable: "+is_exportabe); + logger.println("master_secret"); + logger.print(session.master_secret); + logger.println("client_random"); + logger.print(clientRandom); + logger.println("server_random"); + logger.print(serverRandom); + //logger.println("key_block"); + //logger.print(key_block); + logger.println("client_mac_secret"); + logger.print(client_mac_secret); + logger.println("server_mac_secret"); + logger.print(server_mac_secret); + logger.println("client_key"); + logger.print(client_key); + logger.println("server_key"); + logger.print(server_key); + if (clientIV == null) { + logger.println("no IV."); + } else { + logger.println("client_iv"); + logger.print(clientIV.getIV()); + logger.println("server_iv"); + logger.print(serverIV.getIV()); + } + } + + encCipher = Cipher.getInstance(algName); + decCipher = Cipher.getInstance(algName); + encMac = Mac.getInstance(macName); + decMac = Mac.getInstance(macName); + + if (is_client) { // client side + encCipher.init(Cipher.ENCRYPT_MODE, + new SecretKeySpec(client_key, algName), clientIV); + decCipher.init(Cipher.DECRYPT_MODE, + new SecretKeySpec(server_key, algName), serverIV); + encMac.init(new SecretKeySpec(client_mac_secret, macName)); + decMac.init(new SecretKeySpec(server_mac_secret, macName)); + } else { // server side + encCipher.init(Cipher.ENCRYPT_MODE, + new SecretKeySpec(server_key, algName), serverIV); + decCipher.init(Cipher.DECRYPT_MODE, + new SecretKeySpec(client_key, algName), clientIV); + encMac.init(new SecretKeySpec(server_mac_secret, macName)); + decMac.init(new SecretKeySpec(client_mac_secret, macName)); + } + } catch (Exception e) { + e.printStackTrace(); + throw new AlertException(AlertProtocol.INTERNAL_ERROR, + new SSLProtocolException( + "Error during computation of security parameters")); + } + } + + /** + * Creates the GenericStreamCipher or GenericBlockCipher + * data structure for specified data of specified type. + * @throws org.apache.harmony.xnet.provider.jsse.AlertException if alert was occured. + */ + protected byte[] encrypt(byte type, byte[] fragment, int offset, int len) { + try { + int content_mac_length = len + hash_size; + int padding_length = is_block_cipher + ? ((8 - (++content_mac_length & 0x07)) & 0x07) + : 0; + byte[] res = new byte[content_mac_length + padding_length]; + System.arraycopy(fragment, offset, res, 0, len); + + mac_material_header[0] = type; + mac_material_header[3] = (byte) ((0x00FF00 & len) >> 8); + mac_material_header[4] = (byte) (0x0000FF & len); + + encMac.update(write_seq_num); + encMac.update(mac_material_header); + encMac.update(fragment, offset, len); + encMac.doFinal(res, len); + + //if (logger != null) { + // logger.println("MAC Material:"); + // logger.print(write_seq_num); + // logger.print(mac_material_header); + // logger.print(fragment, offset, len); + //} + + if (is_block_cipher) { + // do padding: + Arrays.fill(res, content_mac_length-1, + res.length, (byte) (padding_length)); + } + if (logger != null) { + logger.println("SSLRecordProtocol.do_encryption: Generic" + + (is_block_cipher + ? "BlockCipher with padding["+padding_length+"]:" + : "StreamCipher:")); + logger.print(res); + } + byte[] rez = new byte[encCipher.getOutputSize(res.length)]; + // We should not call just doFinal because it reinitialize + // the cipher, but as says rfc 2246: + // "For stream ciphers that do not use a synchronization + // vector (such as RC4), the stream cipher state from the end + // of one record is simply used on the subsequent packet." + // and for block ciphers: + // "The IV for subsequent records is the last ciphertext block from + // the previous record." + // i.e. we should keep the cipher state. + encCipher.update(res, 0, res.length, rez); + incSequenceNumber(write_seq_num); + return rez; + } catch (GeneralSecurityException e) { + e.printStackTrace(); + throw new AlertException(AlertProtocol.INTERNAL_ERROR, + new SSLProtocolException("Error during the encryption")); + } + } + + /** + * Retrieves the fragment of the Plaintext structure of + * the specified type from the provided data representing + * the Generic[Stream|Block]Cipher structure. + * @throws org.apache.harmony.xnet.provider.jsse.AlertException if alert was occured. + */ + protected byte[] decrypt(byte type, byte[] fragment, + int offset, int len) { + // plain data of the Generic[Stream|Block]Cipher structure + byte[] data = decCipher.update(fragment, offset, len); + // the 'content' part of the structure + byte[] content; + if (is_block_cipher) { + // check padding + int padding_length = data[data.length-1]; + for (int i=0; i<padding_length; i++) { + if (data[data.length-2-i] != padding_length) { + throw new AlertException( + AlertProtocol.DECRYPTION_FAILED, + new SSLProtocolException( + "Received message has bad padding")); + } + } + content = new byte[data.length - hash_size - padding_length - 1]; + } else { + content = new byte[data.length - hash_size]; + } + + mac_material_header[0] = type; + mac_material_header[3] = (byte) ((0x00FF00 & content.length) >> 8); + mac_material_header[4] = (byte) (0x0000FF & content.length); + + decMac.update(read_seq_num); + decMac.update(mac_material_header); + decMac.update(data, 0, content.length); // mac.update(fragment); + byte[] mac_value = decMac.doFinal(); + if (logger != null) { + logger.println("Decrypted:"); + logger.print(data); + //logger.println("MAC Material:"); + //logger.print(read_seq_num); + //logger.print(mac_material_header); + //logger.print(data, 0, content.length); + logger.println("Expected mac value:"); + logger.print(mac_value); + } + // checking the mac value + for (int i=0; i<hash_size; i++) { + if (mac_value[i] != data[i+content.length]) { + throw new AlertException(AlertProtocol.BAD_RECORD_MAC, + new SSLProtocolException("Bad record MAC")); + } + } + System.arraycopy(data, 0, content, 0, content.length); + incSequenceNumber(read_seq_num); + return content; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ContentType.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ContentType.java new file mode 100644 index 0000000..dedfe64 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ContentType.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +/** + * This class incapsulates the constants determining the + * types of SSL/TLS record's content data. + * Constant values are taken according to the TLS v1 specification + * (http://www.ietf.org/rfc/rfc2246.txt). + */ +public class ContentType { + + /** + * Identifies change cipher spec message + */ + protected static final byte CHANGE_CIPHER_SPEC = 20; + + /** + * Identifies alert message + */ + protected static final byte ALERT = 21; + + /** + * Identifies handshake message + */ + protected static final byte HANDSHAKE = 22; + + /** + * Identifies application data message + */ + protected static final byte APPLICATION_DATA = 23; + +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DHParameters.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DHParameters.java new file mode 100644 index 0000000..1a441a5 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DHParameters.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ +package org.apache.harmony.xnet.provider.jsse; + +/** + * This class contains well-known primes + */ +public class DHParameters { + + // Well-known 512 bit prime + // http://news.hping.org/sci.crypt.archive/2370.html + private static byte[] prime512 = new byte[] { (byte) 0xF5, (byte) 0x2A, (byte) 0xFF, + (byte) 0x3C, (byte) 0xE1, (byte) 0xB1, (byte) 0x29, (byte) 0x40, + (byte) 0x18, (byte) 0x11, (byte) 0x8D, (byte) 0x7C, (byte) 0x84, + (byte) 0xA7, (byte) 0x0A, (byte) 0x72, (byte) 0xD6, (byte) 0x86, + (byte) 0xC4, (byte) 0x03, (byte) 0x19, (byte) 0xC8, (byte) 0x07, + (byte) 0x29, (byte) 0x7A, (byte) 0xCA, (byte) 0x95, (byte) 0x0C, + (byte) 0xD9, (byte) 0x96, (byte) 0x9F, (byte) 0xAB, (byte) 0xD0, + (byte) 0x0A, (byte) 0x50, (byte) 0x9B, (byte) 0x02, (byte) 0x46, + (byte) 0xD3, (byte) 0x08, (byte) 0x3D, (byte) 0x66, (byte) 0xA4, + (byte) 0x5D, (byte) 0x41, (byte) 0x9F, (byte) 0x9C, (byte) 0x7C, + (byte) 0xBD, (byte) 0x89, (byte) 0x4B, (byte) 0x22, (byte) 0x19, + (byte) 0x26, (byte) 0xBA, (byte) 0xAB, (byte) 0xA2, (byte) 0x5E, + (byte) 0xC3, (byte) 0x55, (byte) 0xE9, (byte) 0x2A, (byte) 0x05, + (byte) 0x5F }; + + // Well-Known Group 1: A 768 bit prime rfc 2539 + // (http://www.ietf.org/rfc/rfc2539.txt?number=2539) + private static byte[] primeGroup1 = { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xC9, + (byte) 0x0F, (byte) 0xDA, (byte) 0xA2, (byte) 0x21, (byte) 0x68, + (byte) 0xC2, (byte) 0x34, (byte) 0xC4, (byte) 0xC6, (byte) 0x62, + (byte) 0x8B, (byte) 0x80, (byte) 0xDC, (byte) 0x1C, (byte) 0xD1, + (byte) 0x29, (byte) 0x02, (byte) 0x4E, (byte) 0x08, (byte) 0x8A, + (byte) 0x67, (byte) 0xCC, (byte) 0x74, (byte) 0x02, (byte) 0x0B, + (byte) 0xBE, (byte) 0xA6, (byte) 0x3B, (byte) 0x13, (byte) 0x9B, + (byte) 0x22, (byte) 0x51, (byte) 0x4A, (byte) 0x08, (byte) 0x79, + (byte) 0x8E, (byte) 0x34, (byte) 0x04, (byte) 0xDD, (byte) 0xEF, + (byte) 0x95, (byte) 0x19, (byte) 0xB3, (byte) 0xCD, (byte) 0x3A, + (byte) 0x43, (byte) 0x1B, (byte) 0x30, (byte) 0x2B, (byte) 0x0A, + (byte) 0x6D, (byte) 0xF2, (byte) 0x5F, (byte) 0x14, (byte) 0x37, + (byte) 0x4F, (byte) 0xE1, (byte) 0x35, (byte) 0x6D, (byte) 0x6D, + (byte) 0x51, (byte) 0xC2, (byte) 0x45, (byte) 0xE4, (byte) 0x85, + (byte) 0xB5, (byte) 0x76, (byte) 0x62, (byte) 0x5E, (byte) 0x7E, + (byte) 0xC6, (byte) 0xF4, (byte) 0x4C, (byte) 0x42, (byte) 0xE9, + (byte) 0xA6, (byte) 0x3A, (byte) 0x36, (byte) 0x20, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF }; + + // Well-Known Group 2: A 1024 bit prime rfc 2539 + // (http://www.ietf.org/rfc/rfc2539.txt?number=2539) + private static byte[] primeGroup2 = { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xC9, + (byte) 0x0F, (byte) 0xDA, (byte) 0xA2, (byte) 0x21, (byte) 0x68, + (byte) 0xC2, (byte) 0x34, (byte) 0xC4, (byte) 0xC6, (byte) 0x62, + (byte) 0x8B, (byte) 0x80, (byte) 0xDC, (byte) 0x1C, (byte) 0xD1, + (byte) 0x29, (byte) 0x02, (byte) 0x4E, (byte) 0x08, (byte) 0x8A, + (byte) 0x67, (byte) 0xCC, (byte) 0x74, (byte) 0x02, (byte) 0x0B, + (byte) 0xBE, (byte) 0xA6, (byte) 0x3B, (byte) 0x13, (byte) 0x9B, + (byte) 0x22, (byte) 0x51, (byte) 0x4A, (byte) 0x08, (byte) 0x79, + (byte) 0x8E, (byte) 0x34, (byte) 0x04, (byte) 0xDD, (byte) 0xEF, + (byte) 0x95, (byte) 0x19, (byte) 0xB3, (byte) 0xCD, (byte) 0x3A, + (byte) 0x43, (byte) 0x1B, (byte) 0x30, (byte) 0x2B, (byte) 0x0A, + (byte) 0x6D, (byte) 0xF2, (byte) 0x5F, (byte) 0x14, (byte) 0x37, + (byte) 0x4F, (byte) 0xE1, (byte) 0x35, (byte) 0x6D, (byte) 0x6D, + (byte) 0x51, (byte) 0xC2, (byte) 0x45, (byte) 0xE4, (byte) 0x85, + (byte) 0xB5, (byte) 0x76, (byte) 0x62, (byte) 0x5E, (byte) 0x7E, + (byte) 0xC6, (byte) 0xF4, (byte) 0x4C, (byte) 0x42, (byte) 0xE9, + (byte) 0xA6, (byte) 0x37, (byte) 0xED, (byte) 0x6B, (byte) 0x0B, + (byte) 0xFF, (byte) 0x5C, (byte) 0xB6, (byte) 0xF4, (byte) 0x06, + (byte) 0xB7, (byte) 0xED, (byte) 0xEE, (byte) 0x38, (byte) 0x6B, + (byte) 0xFB, (byte) 0x5A, (byte) 0x89, (byte) 0x9F, (byte) 0xA5, + (byte) 0xAE, (byte) 0x9F, (byte) 0x24, (byte) 0x11, (byte) 0x7C, + (byte) 0x4B, (byte) 0x1F, (byte) 0xE6, (byte) 0x49, (byte) 0x28, + (byte) 0x66, (byte) 0x51, (byte) 0xEC, (byte) 0xE6, (byte) 0x53, + (byte) 0x81, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF + }; + + private static byte[] prime; + + static { +//TODO set prime depand on some system or security property + prime = prime512; + } + + /** + * Returns prime bytes + * @return + */ + public static byte[] getPrime() { + return prime; + } +}
\ No newline at end of file diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DataStream.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DataStream.java new file mode 100644 index 0000000..b52b838 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DataStream.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +/** + * This interface represents the ability of the + * classes to provide the chunks of data. + */ +public interface DataStream { + + /** + * Checks if there is data to be read. + * @return true if there is the input data in the stream, + * false otherwise + */ + public boolean hasData(); + + /** + * Retrieves the data of specified length from the stream. + * If the data size in the stream is less than specified length, + * method returns all the data contained in the stream. + * @return byte array containing the demanded data. + */ + public byte[] getData(int length); + +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DelegatedTask.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DelegatedTask.java new file mode 100644 index 0000000..ea6ba78 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DelegatedTask.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.HandshakeProtocol; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; + +/** + * Delegated Runnable task for SSLEngine + */ +public class DelegatedTask implements Runnable { + + private final HandshakeProtocol handshaker; + private final PrivilegedExceptionAction action; + private final AccessControlContext context; + + /** + * Creates DelegatedTask + * @param action + * @param handshaker + * @param context + */ + public DelegatedTask(PrivilegedExceptionAction action, HandshakeProtocol handshaker, AccessControlContext context) { + this.action = action; + this.handshaker = handshaker; + this.context = context; + } + + /** + * Executes DelegatedTask + */ + public void run() { + synchronized (handshaker) { + try { + AccessController.doPrivileged(action, context); + } catch (PrivilegedActionException e) { + // pass exception to HandshakeProtocol + handshaker.delegatedTaskErr = e.getException(); + } catch (RuntimeException e) { + // pass exception to HandshakeProtocol + handshaker.delegatedTaskErr = e; + } + } + + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DigitalSignature.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DigitalSignature.java new file mode 100644 index 0000000..a8794df --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/DigitalSignature.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.AlertException; + +import java.security.MessageDigest; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.cert.Certificate; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.net.ssl.SSLException; + +/** + * This class represents Signature type, as descrybed in TLS v 1.0 Protocol + * specification, 7.4.3. It allow to init, update and sign hash. Hash algorithm + * depends on SignatureAlgorithm. + * + * select (SignatureAlgorithm) + * { case anonymous: struct { }; + * case rsa: + * digitally-signed struct { + * opaque md5_hash[16]; + * opaque sha_hash[20]; + * }; + * case dsa: + * digitally-signed struct { + * opaque sha_hash[20]; + * }; + * } Signature; + * + * Digital signing description see in TLS spec., 4.7. + * (http://www.ietf.org/rfc/rfc2246.txt) + * + */ +public class DigitalSignature { + + private MessageDigest md5 = null; + private MessageDigest sha = null; + private Signature signature = null; + private Cipher cipher = null; + + private byte[] md5_hash; + private byte[] sha_hash; + + /** + * Create Signature type + * @param keyExchange + */ + public DigitalSignature(int keyExchange) { + try { + if (keyExchange == CipherSuite.KeyExchange_RSA_EXPORT || + keyExchange == CipherSuite.KeyExchange_RSA || + keyExchange == CipherSuite.KeyExchange_DHE_RSA || + keyExchange == CipherSuite.KeyExchange_DHE_RSA_EXPORT) { + // SignatureAlgorithm is rsa + md5 = MessageDigest.getInstance("MD5"); + sha = MessageDigest.getInstance("SHA-1"); + cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + } else if (keyExchange == CipherSuite.KeyExchange_DHE_DSS || + keyExchange == CipherSuite.KeyExchange_DHE_DSS_EXPORT ) { + // SignatureAlgorithm is dsa + sha = MessageDigest.getInstance("SHA-1"); + signature = Signature.getInstance("NONEwithDSA"); +// The Signature should be empty in case of anonimous signature algorithm: +// } else if (keyExchange == CipherSuite.KeyExchange_DH_anon || +// keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) { +// + } + } catch (Exception e) { + throw new AlertException( + AlertProtocol.INTERNAL_ERROR, + new SSLException( + "INTERNAL ERROR: Unexpected exception on digital signature", + e)); + } + + } + + /** + * Initiate Signature type by private key + * @param key + */ + public void init(PrivateKey key) { + try { + if (signature != null) { + signature.initSign(key); + } else if (cipher != null) { + cipher.init(Cipher.ENCRYPT_MODE, key); + } + } catch (Exception e){ + e.printStackTrace(); + } + } + + /** + * Initiate Signature type by certificate + * @param cert + */ + public void init(Certificate cert) { + try { + if (signature != null) { + signature.initVerify(cert); + } else if (cipher != null) { + cipher.init(Cipher.DECRYPT_MODE, cert); + } + } catch (Exception e){ + e.printStackTrace(); + } + } + + /** + * Update Signature hash + * @param data + */ + public void update(byte[] data) { + try { + if (sha != null) { + sha.update(data); + } + if (md5 != null) { + md5.update(data); + } + } catch (Exception e){ + e.printStackTrace(); + } + } + + /** + * Sets MD5 hash + * @param data + */ + public void setMD5(byte[] data) { + md5_hash = data; + } + + /** + * Sets SHA hash + * @param data + */ + public void setSHA(byte[] data) { + sha_hash = data; + } + + /** + * Sign hash + * @return Signature bytes + */ + public byte[] sign() { + try { + if (md5 != null && md5_hash == null) { + md5_hash = new byte[16]; + md5.digest(md5_hash, 0, md5_hash.length); + } + if (md5_hash != null) { + if (signature != null) { + signature.update(md5_hash); + } else if (cipher != null) { + cipher.update(md5_hash); + } + } + if (sha != null && sha_hash == null) { + sha_hash = new byte[20]; + sha.digest(sha_hash, 0, sha_hash.length); + } + if (sha_hash != null) { + if (signature != null) { + signature.update(sha_hash); + } else if (cipher != null) { + cipher.update(sha_hash); + } + } + if (signature != null) { + return signature.sign(); + } else if (cipher != null) { + return cipher.doFinal(); + } + return new byte[0]; + } catch (Exception e){ + e.printStackTrace(); + return new byte[0]; + } + } + + /** + * Verifies the signature data. + * @param data - the signature bytes + * @return true if verified + */ + public boolean verifySignature(byte[] data) { + try { + if (signature != null) { + return signature.verify(data); + } else if (cipher != null) { + byte[] decrypt = cipher.doFinal(data); + byte[] md5_sha; + if (md5_hash != null && sha_hash != null) { + md5_sha = new byte[md5_hash.length + sha_hash.length]; + System.arraycopy(md5_hash, 0, md5_sha, 0, md5_hash.length); + System.arraycopy(sha_hash, 0, md5_sha, md5_hash.length, sha_hash.length); + } else if (md5_hash != null) { + md5_sha = md5_hash; + } else { + md5_sha = sha_hash; + } + if (Arrays.equals(decrypt, md5_sha)) { + return true; + } else { + return false; + } + } else if (data == null || data.length == 0) { + return true; + } else { + return false; + } + } catch (Exception e){ + e.printStackTrace(); + return false; + } + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/EndOfBufferException.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/EndOfBufferException.java new file mode 100644 index 0000000..b2bcafe --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/EndOfBufferException.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.io.IOException; + +/** + * This class represents the exception signalizing that + * data could not be read from the stream because + * underlying input stream reached its end. + */ +public class EndOfBufferException extends IOException { + + /** + * Constructor + */ + public EndOfBufferException() { + super(); + } + +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/EndOfSourceException.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/EndOfSourceException.java new file mode 100644 index 0000000..fbc1eaf --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/EndOfSourceException.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.io.IOException; + +/** + * This class represents the exception signalizing that + * data could not be read from the buffered stream because + * underlying data buffer was exhausted. + */ +public class EndOfSourceException extends IOException { + + /** + * Constructor + */ + public EndOfSourceException() { + super(); + } + +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Finished.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Finished.java new file mode 100644 index 0000000..d0f1fe1 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Finished.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.Message; + +import java.io.IOException; + +/** + * + * Represents Finished message + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.9. + * Finished</a> + * + */ +public class Finished extends Message { + + // verify data + private byte[] data; + + /** + * Creates outbound message + * @param bytes + */ + public Finished(byte[] bytes) { + data = bytes; + length = data.length; + } + + /** + * Creates inbound message + * @param in + * @param length + * @throws IOException + */ + public Finished(HandshakeIODataStream in, int length) + throws IOException { + if (length == 12 || length == 36) { + data = in.read(length); + length = data.length; + } else { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect Finished"); + } + } + + public void send(HandshakeIODataStream out) { + out.write(data); + } + + /** + * Returns message type + * @return + */ + public int getType() { + return Handshake.FINISHED; + } + + /** + * Returns verify data + * @return + */ + public byte[] getData() { + return data; + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Handshake.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Handshake.java new file mode 100644 index 0000000..4668b8c --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Handshake.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +/** + * + * This class incapsulates the constants determining the types of handshake + * messages as defined in TLS 1.0 spec., 7.4. Handshake protocol. + * (http://www.ietf.org/rfc/rfc2246.txt) + * + */ +public class Handshake { + + /** + * + * hello_request handshake type + */ + public static final byte HELLO_REQUEST = 0; + + /** + * + * client_hello handshake type + */ + public static final byte CLIENT_HELLO = 1; + + /** + * + * server_hello handshake type + */ + public static final byte SERVER_HELLO = 2; + + /** + * + * certificate handshake type + */ + public static final byte CERTIFICATE = 11; + + /** + * + * server_key_exchange handshake type + */ + public static final byte SERVER_KEY_EXCHANGE = 12; + + /** + * + * certificate_request handshake type + */ + public static final byte CERTIFICATE_REQUEST = 13; + + /** + * + * server_hello_done handshake type + */ + public static final byte SERVER_HELLO_DONE = 14; + + /** + * + * certificate_verify handshake type + */ + public static final byte CERTIFICATE_VERIFY = 15; + + /** + * + * client_key_exchange handshake type + */ + public static final byte CLIENT_KEY_EXCHANGE = 16; + + /** + * + * finished handshake type + */ + public static final byte FINISHED = 20; + +}
\ No newline at end of file diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/HandshakeIODataStream.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/HandshakeIODataStream.java new file mode 100644 index 0000000..b5c4553 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/HandshakeIODataStream.java @@ -0,0 +1,462 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.AlertException; +import org.apache.harmony.xnet.provider.jsse.SSLInputStream; + +import java.io.IOException; +import java.io.PrintStream; +import java.security.MessageDigest; +import java.util.Arrays; +import javax.net.ssl.SSLHandshakeException; + +/** + * This class provides Input/Output data functionality + * for handshake layer. It provides read and write operations + * and accumulates all sent/received handshake's data. + * This class can be presented as a combination of 2 data pipes. + * The first data pipe is a pipe of income data: append method + * places the data at the beginning of the pipe, and read methods + * consume the data from the pipe. The second pipe is an outcoming + * data pipe: write operations plases the data into the pipe, + * and getData methods consume the data. + * It is important to note that work with pipe cound not be + * started if there is unconsumed data in another pipe. It is + * reasoned by the following: handshake protocol performs read + * and write operations consecuently. I.e. it first reads all + * income data and only than produces the responce and places it + * into the stream. + * The read operations of the stream presented by the methods + * of SSLInputStream which in its turn is an extension of InputStream. + * So this stream can be used as an InputStream parameter for + * certificate generation. + * Also input stream functionality supports marks. The marks + * help to reset the position of the stream in case of incompleate + * handshake records. Note that in case of exhausting + * of income data the EndOfBufferException is thown which implies + * the following: + * 1. the stream contains scrappy handshake record, + * 2. the read position should be reseted to marked, + * 3. and more income data is expected. + * The throwing of the exception (instead of returning of -1 value + * or incompleate filling of destination buffer) + * helps to speed up the process of scrappy data recognition and + * processing. + * For more information about TLS handshake process see + * TLS v 1 specification at http://www.ietf.org/rfc/rfc2246.txt. + */ +public class HandshakeIODataStream + extends SSLInputStream implements org.apache.harmony.xnet.provider.jsse.Appendable, DataStream { + + // Objects are used to compute digests of data passed + // during the handshake phase + private static final MessageDigest md5; + private static final MessageDigest sha; + + static { + try { + md5 = MessageDigest.getInstance("MD5"); + sha = MessageDigest.getInstance("SHA-1"); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException( + "Could not initialize the Digest Algorithms."); + } + } + + public HandshakeIODataStream() {} + + // buffer is used to keep the handshaking data; + private int buff_size = 1024; + private int inc_buff_size = 1024; + private byte[] buffer = new byte[buff_size]; + + + // ---------------- Input related functionality ----------------- + + // position of the next byte to read + private int read_pos; + private int marked_pos; + // position of the last byte to read + 1 + private int read_pos_end; + + public int available() { + return read_pos_end - read_pos; + } + + public boolean markSupported() { + return true; + } + + public void mark(int limit) { + marked_pos = read_pos; + } + + public void mark() { + marked_pos = read_pos; + } + + public void reset() { + read_pos = marked_pos; + } + + /** + * Removes the data from the marked position to + * the current read position. The method is usefull when it is needed + * to delete one message from the internal buffer. + */ + protected void removeFromMarkedPosition() { + System.arraycopy(buffer, read_pos, + buffer, marked_pos, read_pos_end - read_pos); + read_pos_end -= (read_pos - marked_pos); + read_pos = marked_pos; + } + + /** + * read an opaque value; + * @param byte: byte + * @return + */ + public int read() throws IOException { + if (read_pos == read_pos_end) { + //return -1; + throw new EndOfBufferException(); + } + return buffer[read_pos++] & 0xFF; + } + + /** + * reads vector of opaque values + * @param new: long + * @return + */ + public byte[] read(int length) throws IOException { + if (length > available()) { + throw new EndOfBufferException(); + } + byte[] res = new byte[length]; + System.arraycopy(buffer, read_pos, res, 0, length); + read_pos = read_pos + length; + return res; + } + + public int read(byte[] dest, int offset, int length) throws IOException { + if (length > available()) { + throw new EndOfBufferException(); + } + System.arraycopy(buffer, read_pos, dest, offset, length); + read_pos = read_pos + length; + return length; + } + + // ------------------- Extending of the input data --------------------- + + /** + * Appends the income data to be read by handshake protocol. + * The attempts to overflow the buffer by meens of this methos + * seem to be futile because of: + * 1. The SSL protocol specifies the maximum size of the record + * and record protocol does not pass huge messages. + * (see TLS v1 specification http://www.ietf.org/rfc/rfc2246.txt , + * p 6.2) + * 2. After each call of this method, handshake protocol should + * start (and starts) the operations on received data and recognize + * the fake data if such was provided (to check the size of certificate + * for example). + */ + public void append(byte[] src) { + append(src, 0, src.length); + } + + private void append(byte[] src, int from, int length) { + if (read_pos == read_pos_end) { + // start reading state after writing + if (write_pos_beg != write_pos) { + // error: outboud handshake data was not sent, + // but inbound handshake data has been received. + throw new AlertException( + AlertProtocol.UNEXPECTED_MESSAGE, + new SSLHandshakeException( + "Handshake message has been received before " + + "the last oubound message had been sent.")); + } + if (read_pos < write_pos) { + read_pos = write_pos; + read_pos_end = read_pos; + } + } + if (read_pos_end + length > buff_size) { + enlargeBuffer(read_pos_end+length-buff_size); + } + System.arraycopy(src, from, buffer, read_pos_end, length); + read_pos_end += length; + } + + private void enlargeBuffer(int size) { + buff_size = (size < inc_buff_size) + ? buff_size + inc_buff_size + : buff_size + size; + byte[] new_buff = new byte[buff_size]; + System.arraycopy(buffer, 0, new_buff, 0, buffer.length); + buffer = new_buff; + } + + protected void clearBuffer() { + read_pos = 0; + marked_pos = 0; + read_pos_end = 0; + write_pos = 0; + write_pos_beg = 0; + Arrays.fill(buffer, (byte) 0); + } + + // ------------------- Output related functionality -------------------- + + // position in the buffer available for write + private int write_pos; + // position in the buffer where the last write session has begun + private int write_pos_beg; + + // checks if the data can be written in the buffer + private void check(int length) { + // (write_pos == write_pos_beg) iff: + // 1. there were not write operations yet + // 2. all written data was demanded by getData methods + if (write_pos == write_pos_beg) { + // just started to write after the reading + if (read_pos != read_pos_end) { + // error: attempt to write outbound data into the stream before + // all the inbound handshake data had been read + throw new AlertException( + AlertProtocol.INTERNAL_ERROR, + new SSLHandshakeException("Data was not fully read: " + + read_pos + " " + read_pos_end)); + } + // set up the write positions + if (write_pos_beg < read_pos_end) { + write_pos_beg = read_pos_end; + write_pos = write_pos_beg; + } + } + // if there is not enought free space in the buffer - enlarge it: + if (write_pos + length >= buff_size) { + enlargeBuffer(length); + } + } + + /** + * Writes an opaque value + * @param byte: byte + */ + public void write(byte b) { + check(1); + buffer[write_pos++] = b; + } + + /** + * Writes Uint8 value + * @param long: the value to be written (last byte) + */ + public void writeUint8(long n) { + check(1); + buffer[write_pos++] = (byte) (n & 0x00ff); + } + + /** + * Writes Uint16 value + * @param long: the value to be written (last 2 bytes) + */ + public void writeUint16(long n) { + check(2); + buffer[write_pos++] = (byte) ((n & 0x00ff00) >> 8); + buffer[write_pos++] = (byte) (n & 0x00ff); + } + + /** + * Writes Uint24 value + * @param long: the value to be written (last 3 bytes) + */ + public void writeUint24(long n) { + check(3); + buffer[write_pos++] = (byte) ((n & 0x00ff0000) >> 16); + buffer[write_pos++] = (byte) ((n & 0x00ff00) >> 8); + buffer[write_pos++] = (byte) (n & 0x00ff); + } + + /** + * Writes Uint32 value + * @param long: the value to be written (last 4 bytes) + */ + public void writeUint32(long n) { + check(4); + buffer[write_pos++] = (byte) ((n & 0x00ff000000) >> 24); + buffer[write_pos++] = (byte) ((n & 0x00ff0000) >> 16); + buffer[write_pos++] = (byte) ((n & 0x00ff00) >> 8); + buffer[write_pos++] = (byte) (n & 0x00ff); + } + + /** + * Writes Uint64 value + * @param long: the value to be written + */ + public void writeUint64(long n) { + check(8); + buffer[write_pos++] = (byte) ((n & 0x00ff00000000000000L) >> 56); + buffer[write_pos++] = (byte) ((n & 0x00ff000000000000L) >> 48); + buffer[write_pos++] = (byte) ((n & 0x00ff0000000000L) >> 40); + buffer[write_pos++] = (byte) ((n & 0x00ff00000000L) >> 32); + buffer[write_pos++] = (byte) ((n & 0x00ff000000) >> 24); + buffer[write_pos++] = (byte) ((n & 0x00ff0000) >> 16); + buffer[write_pos++] = (byte) ((n & 0x00ff00) >> 8); + buffer[write_pos++] = (byte) (n & 0x00ff); + } + + /** + * writes vector of opaque values + * @param vector the vector to be written + */ + public void write(byte[] vector) { + check(vector.length); + System.arraycopy(vector, 0, buffer, write_pos, vector.length); + write_pos += vector.length; + } + + // ------------------- Retrieve the written bytes ---------------------- + + public boolean hasData() { + return (write_pos > write_pos_beg); + } + + /** + * returns the chunk of stored data with the length no more than specified. + * @param length: int + * @return + */ + public byte[] getData(int length) { + byte[] res; + if (write_pos - write_pos_beg < length) { + res = new byte[write_pos - write_pos_beg]; + System.arraycopy(buffer, write_pos_beg, + res, 0, write_pos-write_pos_beg); + write_pos_beg = write_pos; + } else { + res = new byte[length]; + System.arraycopy(buffer, write_pos_beg, res, 0, length); + write_pos_beg += length; + } + return res; + } + + // ---------------------- Debud functionality ------------------------- + + protected void printContent(PrintStream outstream) { + int perLine = 20; + String prefix = " "; + String delimiter = ""; + + for (int i=write_pos_beg; i<write_pos; i++) { + String tail = Integer.toHexString( + 0x00ff & buffer[i]).toUpperCase(); + if (tail.length() == 1) { + tail = "0" + tail; + } + outstream.print(prefix + tail + delimiter); + + if (((i-write_pos_beg+1)%10) == 0) { + outstream.print(" "); + } + + if (((i-write_pos_beg+1)%perLine) == 0) { + outstream.println(); + } + } + outstream.println(); + } + + // ---------------------- Message Digest Functionality ---------------- + + /** + * Returns the MD5 digest of the data passed throught the stream + * @return MD5 digest + */ + protected byte[] getDigestMD5() { + synchronized (md5) { + int len = (read_pos_end > write_pos) + ? read_pos_end + : write_pos; + md5.update(buffer, 0, len); + return md5.digest(); + } + } + + /** + * Returns the SHA-1 digest of the data passed throught the stream + * @return SHA-1 digest + */ + protected byte[] getDigestSHA() { + synchronized (sha) { + int len = (read_pos_end > write_pos) + ? read_pos_end + : write_pos; + sha.update(buffer, 0, len); + return sha.digest(); + } + } + + /** + * Returns the MD5 digest of the data passed throught the stream + * except last message + * @return MD5 digest + */ + protected byte[] getDigestMD5withoutLast() { + synchronized (md5) { + md5.update(buffer, 0, marked_pos); + return md5.digest(); + } + } + + /** + * Returns the SHA-1 digest of the data passed throught the stream + * except last message + * @return SHA-1 digest + */ + protected byte[] getDigestSHAwithoutLast() { + synchronized (sha) { + sha.update(buffer, 0, marked_pos); + return sha.digest(); + } + } + + /** + * Returns all the data passed throught the stream + * @return all the data passed throught the stream at the moment + */ + protected byte[] getMessages() { + int len = (read_pos_end > write_pos) ? read_pos_end : write_pos; + byte[] res = new byte[len]; + System.arraycopy(buffer, 0, res, 0, len); + return res; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/HandshakeProtocol.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/HandshakeProtocol.java new file mode 100644 index 0000000..14c8ae5 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/HandshakeProtocol.java @@ -0,0 +1,544 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.interfaces.RSAKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.Arrays; +import java.util.Vector; + +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; + +/** + * Base class for ClientHandshakeImpl and ServerHandshakeImpl classes. + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4. + * Handshake protocol</a> + * + */ +public abstract class HandshakeProtocol { + + /** + * Handshake status NEED_UNWRAP - HandshakeProtocol needs to receive data + */ + public final static int NEED_UNWRAP = 1; + + /** + * Handshake status NOT_HANDSHAKING - is not currently handshaking + */ + public final static int NOT_HANDSHAKING = 2; + + /** + * Handshake status FINISHED - HandshakeProtocol has just finished + */ + public final static int FINISHED = 3; + + /** + * Handshake status NEED_TASK - HandshakeProtocol needs the results of delegated task + */ + public final static int NEED_TASK = 4; + + /** + * Current handshake status + */ + protected int status = NOT_HANDSHAKING; + + /** + * IO stream for income/outcome handshake data + */ + protected HandshakeIODataStream io_stream = new HandshakeIODataStream(); + + /** + * SSL Record Protocol implementation. + */ + protected SSLRecordProtocol recordProtocol; + + /** + * SSLParameters suplied by SSLSocket or SSLEngine + */ + protected SSLParameters parameters; + + /** + * Delegated tasks for this handshake implementation + */ + protected Vector delegatedTasks = new Vector(); + + /** + * Indicates non-blocking handshake + */ + protected boolean nonBlocking; + + /** + * Pending session + */ + protected SSLSessionImpl session; + + /** + * Sended and received handshake messages + */ + protected ClientHello clientHello; + protected ServerHello serverHello; + protected CertificateMessage serverCert; + protected ServerKeyExchange serverKeyExchange; + protected CertificateRequest certificateRequest; + protected ServerHelloDone serverHelloDone; + protected CertificateMessage clientCert; + protected ClientKeyExchange clientKeyExchange; + protected CertificateVerify certificateVerify; + protected Finished clientFinished; + protected Finished serverFinished; + + /** + * Indicates that change cipher spec message has been received + */ + protected boolean changeCipherSpecReceived = false; + + /** + * Indicates previous session resuming + */ + protected boolean isResuming = false; + + /** + * Premaster secret + */ + protected byte[] preMasterSecret; + + /** + * Exception occured in delegated task + */ + protected Exception delegatedTaskErr; + + // reference verify_data used to verify finished message + private byte[] verify_data = new byte[12]; + + // Encoding of "master secret" string: "master secret".getBytes() + private byte[] master_secret_bytes = + {109, 97, 115, 116, 101, 114, 32, 115, 101, 99, 114, 101, 116 }; + + // indicates whether protocol needs to send change cipher spec message + private boolean needSendCCSpec = false; + + // indicates whether protocol needs to send change cipher spec message + protected boolean needSendHelloRequest = false; + + /** + * SSLEngine owning this HandshakeProtocol + */ + public SSLEngineImpl engineOwner; + + /** + * SSLSocket owning this HandshakeProtocol + */ + // BEGIN android-removed + // public SSLSocketImpl socketOwner; + // END android-removed + + /** + * Creates HandshakeProtocol instance + * @param owner + */ + protected HandshakeProtocol(Object owner) { + if (owner instanceof SSLEngineImpl) { + engineOwner = (SSLEngineImpl) owner; + nonBlocking = true; + this.parameters = (SSLParameters) engineOwner.sslParameters; + // BEGIN android-removed + // } else if (owner instanceof SSLSocketImpl) { + // socketOwner = (SSLSocketImpl) owner; + // nonBlocking = false; + // this.parameters = (SSLParameters) socketOwner.sslParameters; + // } + // END android-removed + // BEGIN android-added + } + // END android-added + } + + /** + * Sets SSL Record Protocol + * @param recordProtocol + */ + public void setRecordProtocol(SSLRecordProtocol recordProtocol) { + this.recordProtocol = recordProtocol; + } + + /** + * Start session negotiation + * @param session + */ + public abstract void start(); + + /** + * Stops the current session renegotiation process. + * Such functionality is needed when it is session renegotiation + * process and no_renegotiation alert message is received + * from another peer. + * @param session + */ + protected void stop() { + clearMessages(); + status = NOT_HANDSHAKING; + } + + /** + * Returns handshake status + * @return + */ + public SSLEngineResult.HandshakeStatus getStatus() { + if (io_stream.hasData() || needSendCCSpec || + needSendHelloRequest || delegatedTaskErr != null) { + return SSLEngineResult.HandshakeStatus.NEED_WRAP; + } + if (!delegatedTasks.isEmpty()) { + return SSLEngineResult.HandshakeStatus.NEED_TASK; + } + + switch (status) { + case HandshakeProtocol.NEED_UNWRAP: + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + case HandshakeProtocol.FINISHED: + status = NOT_HANDSHAKING; + clearMessages(); + return SSLEngineResult.HandshakeStatus.FINISHED; + default: // HandshakeProtocol.NOT_HANDSHAKING: + return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + } + } + + /** + * Returns pending session + * @return session + */ + public SSLSessionImpl getSession() { + return session; + } + + protected void sendChangeCipherSpec() { + needSendCCSpec = true; + } + + protected void sendHelloRequest() { + needSendHelloRequest = true; + } + + /** + * Proceses inbound ChangeCipherSpec message + */ + abstract void receiveChangeCipherSpec(); + + /** + * Creates and sends finished message + */ + abstract void makeFinished(); + + /** + * Proceses inbound handshake messages + * @param bytes + */ + public abstract void unwrap(byte[] bytes); + + /** + * Processes SSLv2 Hello message + * @param bytes + */ + public abstract void unwrapSSLv2(byte[] bytes); + + /** + * Proceses outbound handshake messages + * @return + */ + public byte[] wrap() { + if (delegatedTaskErr != null) { + // process error occured in delegated task + Exception e = delegatedTaskErr; + delegatedTaskErr = null; + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "Error occured in delegated task:" + e.getMessage(), e); + } + if (io_stream.hasData()) { + return recordProtocol.wrap(ContentType.HANDSHAKE, io_stream); + } else if (needSendCCSpec) { + makeFinished(); + needSendCCSpec = false; + return recordProtocol.getChangeCipherSpecMesage(getSession()); + } else if (needSendHelloRequest) { + needSendHelloRequest = false; + return recordProtocol.wrap(ContentType.HANDSHAKE, + // hello request message + // (see TLS v 1 specification: + // http://www.ietf.org/rfc/rfc2246.txt) + new byte[] {0, 0, 0, 0}, 0, 4); + } else { + return null; // nothing to send; + } + } + + /** + * Sends fatal alert, breaks execution + * + * @param description + */ + protected void sendWarningAlert(byte description) { + recordProtocol.alert(AlertProtocol.WARNING, description); + } + + /** + * Sends fatal alert, breaks execution + * + * @param description + * @param reason + */ + protected void fatalAlert(byte description, String reason) { + throw new AlertException(description, new SSLHandshakeException(reason)); + } + + /** + * Sends fatal alert, breaks execution + * + * @param description + * @param reason + * @param cause + */ + protected void fatalAlert(byte description, String reason, Exception cause) { + throw new AlertException(description, new SSLException(reason, cause)); + } + + /** + * Sends fatal alert, breaks execution + * + * @param description + * @param cause + */ + protected void fatalAlert(byte description, SSLException cause) { + throw new AlertException(description, cause); + } + + /** + * Computers reference TLS verify_data that is used to verify finished message + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS spec. 7.4.9. Finished</a> + * @param label + */ + protected void computerReferenceVerifyDataTLS(String label) { + computerVerifyDataTLS(label, verify_data); + } + + /** + * Computer TLS verify_data + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS spec. 7.4.9. Finished</a> + * @param label + * @param buf + */ + protected void computerVerifyDataTLS(String label, byte[] buf) { + byte[] md5_digest = io_stream.getDigestMD5(); + byte[] sha_digest = io_stream.getDigestSHA(); + + byte[] digest = new byte[md5_digest.length + sha_digest.length]; + System.arraycopy(md5_digest, 0, digest, 0, md5_digest.length); + System.arraycopy(sha_digest, 0, digest, md5_digest.length, + sha_digest.length); + try { + PRF.computePRF(buf, session.master_secret, + label.getBytes(), digest); + } catch (GeneralSecurityException e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, "PRF error", e); + } + } + + /** + * Computer reference SSLv3 verify_data that is used to verify finished message + * @see "SSLv3 spec. 7.6.9. Finished" + * @param label + */ + protected void computerReferenceVerifyDataSSLv3(byte[] sender) { + verify_data = new byte[36]; + computerVerifyDataSSLv3(sender, verify_data); + } + + /** + * Computer SSLv3 verify_data + * @see "SSLv3 spec. 7.6.9. Finished" + * @param label + * @param buf + */ + protected void computerVerifyDataSSLv3(byte[] sender, byte[] buf) { + MessageDigest md5; + MessageDigest sha; + try { + md5 = MessageDigest.getInstance("MD5"); + sha = MessageDigest.getInstance("SHA-1"); + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, "Could not initialize the Digest Algorithms.", e); + return; + } + try { + byte[] hanshake_messages = io_stream.getMessages(); + md5.update(hanshake_messages); + md5.update(sender); + md5.update(session.master_secret); + byte[] b = md5.digest(SSLv3Constants.MD5pad1); + md5.update(session.master_secret); + md5.update(SSLv3Constants.MD5pad2); + System.arraycopy(md5.digest(b), 0, buf, 0, 16); + + sha.update(hanshake_messages); + sha.update(sender); + sha.update(session.master_secret); + b = sha.digest(SSLv3Constants.SHApad1); + sha.update(session.master_secret); + sha.update(SSLv3Constants.SHApad2); + System.arraycopy(sha.digest(b), 0, buf, 16, 20); + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e); + + } + } + + /** + * Verifies finished data + * + * @param data + * @param isServer + */ + protected void verifyFinished(byte[] data) { + if (!Arrays.equals(verify_data, data)) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Incorrect FINISED"); + } + } + + /** + * Sends fatal alert "UNEXPECTED MESSAGE" + * + */ + protected void unexpectedMessage() { + fatalAlert(AlertProtocol.UNEXPECTED_MESSAGE, "UNEXPECTED MESSAGE"); + } + + /** + * Writes message to HandshakeIODataStream + * + * @param message + */ + public void send(Message message) { + io_stream.writeUint8(message.getType()); + io_stream.writeUint24(message.length()); + message.send(io_stream); + } + + /** + * Computers master secret + * + */ + public void computerMasterSecret() { + byte[] seed = new byte[64]; + System.arraycopy(clientHello.getRandom(), 0, seed, 0, 32); + System.arraycopy(serverHello.getRandom(), 0, seed, 32, 32); + session.master_secret = new byte[48]; + if (serverHello.server_version[1] == 1) { // TLSv1 + try { + PRF.computePRF(session.master_secret, preMasterSecret, + master_secret_bytes, seed); + } catch (GeneralSecurityException e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, "PRF error", e); + } + } else { // SSL3.0 + PRF.computePRF_SSLv3(session.master_secret, preMasterSecret, seed); + } + + //delete preMasterSecret from memory + Arrays.fill(preMasterSecret, (byte)0); + preMasterSecret = null; + } + + /** + * Returns a delegated task. + * @return Delegated task or null + */ + public Runnable getTask() { + if (delegatedTasks.isEmpty()) { + return null; + } else { + Runnable task = (Runnable)delegatedTasks.firstElement(); + delegatedTasks.remove(0); + return task; + } + } + + /** + * + * Clears previously sended and received handshake messages + */ + protected void clearMessages() { + io_stream.clearBuffer(); + clientHello = null; + serverHello = null; + serverCert = null; + serverKeyExchange = null; + certificateRequest = null; + serverHelloDone = null; + clientCert = null; + clientKeyExchange = null; + certificateVerify = null; + clientFinished = null; + serverFinished = null; + } + + /** + * Returns RSA key length + * @param pk + * @return + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + */ + protected static int getRSAKeyLength(PublicKey pk) + throws NoSuchAlgorithmException, InvalidKeySpecException { + + BigInteger mod; + if (pk instanceof RSAKey) { + mod = ((RSAKey) pk).getModulus(); + } else { + KeyFactory kf = KeyFactory.getInstance("RSA"); + mod = ((RSAPublicKeySpec) kf.getKeySpec(pk, RSAPublicKeySpec.class)) + .getModulus(); + } + return mod.bitLength(); + } + + /** + * Shutdownes the protocol. It will be impossiblke to use the instance + * after the calling of this method. + */ + protected void shutdown() { + clearMessages(); + session = null; + preMasterSecret = null; + delegatedTasks.clear(); + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/HelloRequest.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/HelloRequest.java new file mode 100644 index 0000000..2ce4061 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/HelloRequest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.Message; +import org.apache.harmony.xnet.provider.jsse.Handshake; +import org.apache.harmony.xnet.provider.jsse.HandshakeIODataStream; + +import java.io.IOException; + +/** + * + * Represents Hello Request message + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.1.1. + * Hello request</a> + * + */ +public class HelloRequest extends Message { + + /** + * Creates outbound message + * + */ + public HelloRequest() { + } + + /** + * Creates inbound message + * @param in + * @param length + * @throws IOException + */ + public HelloRequest(HandshakeIODataStream in, int length) + throws IOException { + if (length != 0) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect HelloRequest"); + } + } + + /** + * Sends message + * @param out + */ + public void send(HandshakeIODataStream out) { + } + + public int length() { + return 0; + } + + /** + * Returns message type + * @return + */ + public int getType() { + return Handshake.HELLO_REQUEST; + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java new file mode 100644 index 0000000..25b027e --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.security.AccessController; +import java.security.Provider; + +/** + * JSSE Provider implementation. + * + * This implementation is based on TLS v 1.0 and SSL v3 protocol specifications. + * + * <ul> + * <li><a href="http://www.ietf.org/rfc/rfc2246.txt">TLS v 1.0 Protocol + * specification</a></li> + * <li><a href="http://wp.netscape.com/eng/ssl3">SSL v3 Protocol + * specification</a></li> + * </ul> + * + * Provider implementation supports the following cipher suites: + * TLS_NULL_WITH_NULL_NULL + * TLS_RSA_WITH_NULL_MD5 + * TLS_RSA_WITH_NULL_SHA + * TLS_RSA_EXPORT_WITH_RC4_40_MD5 + * TLS_RSA_WITH_RC4_128_MD5 + * TLS_RSA_WITH_RC4_128_SHA + * TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 + * TLS_RSA_WITH_IDEA_CBC_SHA + * TLS_RSA_EXPORT_WITH_DES40_CBC_SHA + * TLS_RSA_WITH_DES_CBC_SHA + * TLS_RSA_WITH_3DES_EDE_CBC_SHA + * TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA + * TLS_DH_DSS_WITH_DES_CBC_SHA + * TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA + * TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA + * TLS_DH_RSA_WITH_DES_CBC_SHA + * TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA + * TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA + * TLS_DHE_DSS_WITH_DES_CBC_SHA + * TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA + * TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA + * TLS_DHE_RSA_WITH_DES_CBC_SHA + * TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA + * TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 + * TLS_DH_anon_WITH_RC4_128_MD5 + * TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA + * TLS_DH_anon_WITH_DES_CBC_SHA + * TLS_DH_anon_WITH_3DES_EDE_CBC_SHA + * + * The real set of availible cipher suites depends on set of availible + * crypto algorithms. These algorithms must be provided by some crypto + * provider. + * + * The following cipher algorithms are used by different cipher suites: + * IDEA/CBC/NoPadding + * RC2/CBC/NoPadding + * RC4 + * DES/CBC/NoPadding + * DES/CBC/NoPadding + * DESede/CBC/NoPadding + * + * Also the current JSSE provider implementation uses the following + * crypto algorithms: + * + * Algorithms that MUST be provided by crypto provider: + * Mac HmacMD5 + * Mac HmacSHA1 + * MessageDigest MD5 + * MessageDigest SHA-1 + * CertificateFactory X509 + * + * The cipher suites with RSA key exchange may also require: + * Cipher RSA + * KeyPairGenerator RSA + * KeyFactory RSA + * + * The cipher suites with DH key exchange may also require: + * Signature NONEwithDSA + * KeyPairGenerator DiffieHellman or DH + * KeyFactory DiffieHellman or DH + * KeyAgreement DiffieHellman or DH + * KeyPairGenerator DiffieHellman or DH + * + * Trust manager implementation requires: + * CertPathValidator PKIX + * CertificateFactory X509 + * + */ +public final class JSSEProvider extends Provider { + + public JSSEProvider() { + super("HarmonyJSSE", 1.0, "Harmony JSSE Provider"); + AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() { + public Void run() { + put("SSLContext.TLS", + "org.apache.harmony.xnet.provider.jsse.SSLContextImpl"); + put("Alg.Alias.SSLContext.TLSv1", "TLS"); + put("KeyManagerFactory.X509", + "org.apache.harmony.xnet.provider.jsse.KeyManagerFactoryImpl"); + put("TrustManagerFactory.X509", + "org.apache.harmony.xnet.provider.jsse.TrustManagerFactoryImpl"); + return null; + } + }); + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/KeyManagerFactoryImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/KeyManagerFactoryImpl.java new file mode 100644 index 0000000..1daf80c --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/KeyManagerFactoryImpl.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ +package org.apache.harmony.xnet.provider.jsse; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.AccessController; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactorySpi; +import javax.net.ssl.ManagerFactoryParameters; + +/** + * KeyManagerFactory implementation. + * @see javax.net.ssl.KeyManagerFactorySpi + */ +public class KeyManagerFactoryImpl extends KeyManagerFactorySpi { + + // source of key material + private KeyStore keyStore; + + //password + private char[] pwd; + + /** + * @see javax.net.ssl.KeyManagerFactorySpi#engineInit(KeyStore ks, char[] + * password) + */ + public void engineInit(KeyStore ks, char[] password) + throws KeyStoreException, NoSuchAlgorithmException, + UnrecoverableKeyException { + if (ks != null) { + keyStore = ks; + if (password != null) { + pwd = (char[]) password.clone(); + } else { + pwd = new char[0]; + } + } else { + keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + String keyStoreName = AccessController + .doPrivileged(new java.security.PrivilegedAction<String>() { + public String run() { + return System.getProperty("javax.net.ssl.keyStore"); + } + }); + String keyStorePwd = null; + if (keyStoreName == null || keyStoreName.equalsIgnoreCase("NONE") + || keyStoreName.length() == 0) { + try { + keyStore.load(null, null); + } catch (IOException e) { + throw new KeyStoreException(e); + } catch (CertificateException e) { + throw new KeyStoreException(e); + } + } else { + keyStorePwd = AccessController + .doPrivileged(new java.security.PrivilegedAction<String>() { + public String run() { + return System + .getProperty("javax.net.ssl.keyStorePassword"); + } + }); + if (keyStorePwd == null) { + pwd = new char[0]; + } else { + pwd = keyStorePwd.toCharArray(); + } + try { + keyStore.load(new FileInputStream(new File(keyStoreName)), + pwd); + + } catch (FileNotFoundException e) { + throw new KeyStoreException(e); + } catch (IOException e) { + throw new KeyStoreException(e); + } catch (CertificateException e) { + throw new KeyStoreException(e); + } + } + + } + + } + + /** + * @see javax.net.ssl.KeyManagerFactorySpi#engineInit(ManagerFactoryParameters + * spec) + */ + public void engineInit(ManagerFactoryParameters spec) + throws InvalidAlgorithmParameterException { + throw new InvalidAlgorithmParameterException( + "ManagerFactoryParameters not supported"); + + } + + /** + * @see javax.net.ssl.KeyManagerFactorySpi#engineGetKeyManagers() + */ + public KeyManager[] engineGetKeyManagers() { + if (keyStore == null) { + throw new IllegalStateException("KeyManagerFactory is not initialized"); + } + return new KeyManager[] { new KeyManagerImpl(keyStore, pwd) }; + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/KeyManagerImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/KeyManagerImpl.java new file mode 100644 index 0000000..092d92d --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/KeyManagerImpl.java @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ +package org.apache.harmony.xnet.provider.jsse; + +import java.net.Socket; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.UnrecoverableEntryException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.security.auth.x500.X500Principal; + +/** + * KeyManager implementation. + * This implementation uses hashed key store information. + * It works faster than retrieving all of the data from the key store. + * Any key store changes, that happen after key manager was created, have no effect. + * The implementation does not use peer information (host, port) + * that may be obtained from socket or engine. + * + * @see javax.net.ssl.KeyManager + * + */ +public class KeyManagerImpl extends X509ExtendedKeyManager { + + // hashed key store information + private final Hashtable hash = new Hashtable(); + + /** + * Creates Key manager + * + * @param keyStore + * @param pwd + */ + public KeyManagerImpl(KeyStore keyStore, char[] pwd) { + String alias; + KeyStore.PrivateKeyEntry entry; + Enumeration aliases; + try { + aliases = keyStore.aliases(); + } catch (KeyStoreException e) { + return; + } + for (; aliases.hasMoreElements();) { + alias = (String) aliases.nextElement(); + try { + if (keyStore.entryInstanceOf(alias, + KeyStore.PrivateKeyEntry.class)) { + entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, + new KeyStore.PasswordProtection(pwd)); + hash.put(alias, entry); + } + } catch (KeyStoreException e) { + continue; + } catch (UnrecoverableEntryException e) { + continue; + } catch (NoSuchAlgorithmException e) { + continue; + } + } + + } + + /** + * @see javax.net.ssl.X509ExtendedKeyManager#chooseClientAlias(String[] + * keyType, Principal[] issuers, Socket socket) + */ + public String chooseClientAlias(String[] keyType, Principal[] issuers, + Socket socket) { + String[] al = chooseAlias(keyType, issuers); + if (al != null) { + return al[0]; + } else { + return null; + } + } + + /** + * @see javax.net.ssl.X509ExtendedKeyManager#chooseServerAlias(String + * keyType, Principal[] issuers, Socket socket) + */ + public String chooseServerAlias(String keyType, Principal[] issuers, + Socket socket) { + String[] al = chooseAlias(new String[] { keyType }, issuers); + if (al != null) { + return al[0]; + } else { + return null; + } + } + + /** + * @see javax.net.ssl.X509ExtendedKeyManager#getCertificateChain(String + * alias) + */ + public X509Certificate[] getCertificateChain(String alias) { + if (hash.containsKey(alias)) { + Certificate[] certs = ((KeyStore.PrivateKeyEntry) hash.get(alias)) + .getCertificateChain(); + if (certs[0] instanceof X509Certificate) { + X509Certificate[] xcerts = new X509Certificate[certs.length]; + for (int i = 0; i < certs.length; i++) { + xcerts[i] = (X509Certificate) certs[i]; + } + return xcerts; + } + } + return null; + + } + + /** + * @see javax.net.ssl.X509ExtendedKeyManager#getClientAliases(String + * keyType, Principal[] issuers) + */ + public String[] getClientAliases(String keyType, Principal[] issuers) { + return chooseAlias(new String[] { keyType }, issuers); + } + + /** + * @see javax.net.ssl.X509ExtendedKeyManager#getServerAliases(String + * keyType, Principal[] issuers) + */ + public String[] getServerAliases(String keyType, Principal[] issuers) { + return chooseAlias(new String[] { keyType }, issuers); + } + + /** + * @see javax.net.ssl.X509ExtendedKeyManager#getPrivateKey(String alias) + */ + public PrivateKey getPrivateKey(String alias) { + if (hash.containsKey(alias)) { + return ((KeyStore.PrivateKeyEntry) hash.get(alias)).getPrivateKey(); + } + return null; + } + + /** + * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineClientAlias(String[] + * keyType, Principal[] issuers, SSLEngine engine) + */ + public String chooseEngineClientAlias(String[] keyType, + Principal[] issuers, SSLEngine engine) { + String[] al = chooseAlias(keyType, issuers); + if (al != null) { + return al[0]; + } else { + return null; + } + } + + /** + * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineServerAlias(String + * keyType, Principal[] issuers, SSLEngine engine) + */ + public String chooseEngineServerAlias(String keyType, Principal[] issuers, + SSLEngine engine) { + String[] al = chooseAlias(new String[] { keyType }, issuers); + if (al != null) { + return al[0]; + } else { + return null; + } + } + + private String[] chooseAlias(String[] keyType, Principal[] issuers) { + String alias; + KeyStore.PrivateKeyEntry entry; + + if (keyType == null || keyType.length == 0) { + return null; + } + Vector found = new Vector(); + int count = 0; + for (Enumeration aliases = hash.keys(); aliases.hasMoreElements();) { + alias = (String) aliases.nextElement(); + entry = (KeyStore.PrivateKeyEntry) hash.get(alias); + Certificate[] certs = entry.getCertificateChain(); + String alg = certs[0].getPublicKey().getAlgorithm(); + for (int i = 0; i < keyType.length; i++) { + if (alg.equals(keyType[i])) { + if (issuers != null && issuers.length != 0) { + // check that certificate was issued by specified issuer + loop: for (int ii = 0; ii < certs.length; ii++) { + if (certs[ii] instanceof X509Certificate) { + X500Principal issuer = ((X509Certificate) certs[ii]) + .getIssuerX500Principal(); + for (int iii = 0; iii < issuers.length; iii++) { + if (issuer.equals(issuers[iii])) { + found.add(alias); + count++; + break loop; + } + } + } + + } + } else { + found.add(alias); + count++; + } + } + } + } + if (count > 0) { + String[] result = new String[count]; + found.toArray(result); + return result; + } else { + return null; + } + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Logger.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Logger.java new file mode 100644 index 0000000..5b7ba2c --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Logger.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.io.PrintStream; +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * This class provides debug logging for JSSE provider implementation + * TODO: Use java.util.logging + */ +public class Logger { + + public static class Stream extends PrintStream { + private final String prefix; + private static int indent = 0; + + public Stream(String name) { + super(System.err); + prefix = name + "["+Thread.currentThread().getName()+"] "; + } + + public void print(String msg) { + for (int i=0; i<indent; i++) { + super.print(" "); + } + super.print(msg); + } + + public void newIndent() { + indent ++; + } + + public void endIndent() { + indent --; + } + + public void println(String msg) { + print(prefix); + super.println(msg); + } + + public void print(byte[] data) { + printAsHex(16, " ", "", data, 0, data.length); + } + + public void print(byte[] data, int offset, int len) { + printAsHex(16, " ", "", data, offset, len); + } + + public void printAsHex(int perLine, + String prefix, + String delimiter, + byte[] data) { + printAsHex(perLine, prefix, delimiter, data, 0, data.length); + } + + public void printAsHex(int perLine, + String prefix, + String delimiter, + byte[] data, int offset, int len) { + String line = ""; + for (int i=0; i<len; i++) { + String tail = + Integer.toHexString(0x00ff & data[i+offset]).toUpperCase(); + if (tail.length() == 1) { + tail = "0" + tail; + } + line += prefix + tail + delimiter; + + if (((i+1)%perLine) == 0) { + super.println(line); + line = ""; + } + } + super.println(line); + } + } + + private static String[] names; + + static { + try { + names = AccessController + .doPrivileged(new PrivilegedAction<String[]>() { + public String[] run() { + return System.getProperty("jsse", "").split(","); + } + }); + } catch (Exception e) { + names = new String[0]; + } + } + + public static Stream getStream(String name) { + for (int i=0; i<names.length; i++) { + if (names[i].equals(name)) { + return new Stream(name); + } + } + return null; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Message.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Message.java new file mode 100644 index 0000000..cf99d6e --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/Message.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.AlertException; + +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; + +/** + * + * Base class for handshake messages + */ +public abstract class Message { + + /* + * Message length + */ + protected int length; + + /** + * Returns message type + * @return + */ + abstract int getType(); + + /** + * Returns message length + * @return + */ + public int length() { + return length; + } + + /** + * Sends message + * @param out + */ + abstract void send(HandshakeIODataStream out); + + /** + * Sends fatal alert + * @param description + * @param reason + */ + protected void fatalAlert(byte description, String reason) { + throw new AlertException(description, new SSLHandshakeException(reason)); + } + + /** + * Sends fatal alert + * @param description + * @param reason + * @param cause + */ + protected void fatalAlert(byte description, String reason, Throwable cause) { + throw new AlertException(description, new SSLException(reason, cause)); + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java new file mode 100644 index 0000000..508df3a --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2008 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.apache.harmony.xnet.provider.jsse; + +/** + * Provides the Java side of our JNI glue for OpenSSL. Currently only hashing + * and verifying are covered. Is expected to grow over time. Also needs to move + * into libcore/openssl at some point. + */ +public class NativeCrypto { + + static { + // Need to ensure that OpenSSL initialization is done exactly once. + // This can be cleaned up later, when all OpenSSL glue moves into its + // own libcore module. Make it run, make it nice. + OpenSSLSocketImpl.class.getClass(); + } + + // --- DSA/RSA public/private key handling functions ----------------------- + + public static native int EVP_PKEY_new_DSA(byte[] p, byte[] q, byte[] g, byte[] priv_key, byte[] pub_key); + + public static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q); + + public static native void EVP_PKEY_free(int pkey); + + // --- RSA public/private key handling functions --------------------------- + +// public static native int rsaCreatePublicKey(byte[] n, byte[] e); +// +// public static native int rsaCreatePrivateKey(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q); +// +// public static native void rsaDestroyKey(int rsa); + + // --- DSA public/private key handling functions --------------------------- + +// public static native int dsaCreatePublicKey(byte[] p, byte[] q, byte[] g, byte[] pub_key); +// +// public static native int dsaCreatePrivateKey(byte[] p, byte[] q, byte[] g, byte[] priv_key, byte[] pub_key); +// +// public static native void dsaDestroyKey(int dsa); + + // --- RSA public/private key handling functions --------------------------- + +// public static native int rsaCreatePublicKey(byte[] n, byte[] e); +// +// public static native int rsaCreatePrivateKey(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q); +// +// public static native void rsaDestroyKey(int rsa); + + // --- General context handling functions (despite the names) -------------- + + public static native int EVP_new(); + + public static native void EVP_free(int ctx); + + // --- Digest handling functions ------------------------------------------- + + public static native void EVP_DigestInit(int ctx, String algorithm); + + public static native void EVP_DigestUpdate(int ctx, byte[] buffer, int offset, int length); + + public static native int EVP_DigestFinal(int ctx, byte[] hash, int offset); + + public static native int EVP_DigestSize(int ctx); + + public static native int EVP_DigestBlockSize(int ctx); + + // --- Signature handling functions ---------------------------------------- + + public static native void EVP_VerifyInit(int ctx, String algorithm); + + public static native void EVP_VerifyUpdate(int ctx, byte[] buffer, int offset, int length); + + public static native int EVP_VerifyFinal(int ctx, byte[] signature, int offset, int length, int key); + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMessageDigest.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMessageDigest.java new file mode 100644 index 0000000..f8067df --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMessageDigest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2008 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.apache.harmony.xnet.provider.jsse; + +import org.bouncycastle.crypto.ExtendedDigest; + +/** + * Implements the BouncyCastle Digest interface using OpenSSL's EVP API. + */ +public class OpenSSLMessageDigest implements ExtendedDigest { + + /** + * Holds the name of the hashing algorithm, e.g. "SHA-1"; + */ + private String algorithm; + + /** + * Holds a pointer to the native message digest context. + */ + private int ctx; + + /** + * Holds a dummy buffer for writing single bytes to the digest. + */ + private byte[] singleByte = new byte[1]; + + /** + * Creates a new OpenSSLMessageDigest instance for the given algorithm + * name. + * + * @param algorithm The name of the algorithm, e.g. "SHA1". + * + * @return The new OpenSSLMessageDigest instance. + * + * @throws RuntimeException In case of problems. + */ + public static OpenSSLMessageDigest getInstance(String algorithm) { + return new OpenSSLMessageDigest(algorithm); + } + + /** + * Creates a new OpenSSLMessageDigest instance for the given algorithm + * name. + * + * @param algorithm The name of the algorithm, e.g. "SHA1". + */ + private OpenSSLMessageDigest(String algorithm) { + this.algorithm = algorithm; + ctx = NativeCrypto.EVP_new(); + try { + NativeCrypto.EVP_DigestInit(ctx, algorithm.replace("-", "").toLowerCase()); + } catch (Exception ex) { + throw new RuntimeException(ex.getMessage() + " (" + algorithm + ")"); + } + } + + public int doFinal(byte[] out, int outOff) { + int i = NativeCrypto.EVP_DigestFinal(ctx, out, outOff); + reset(); + return i; + } + + public String getAlgorithmName() { + return algorithm; + } + + public int getDigestSize() { + return NativeCrypto.EVP_DigestSize(ctx); + } + + public int getByteLength() { + return NativeCrypto.EVP_DigestBlockSize(ctx); + } + + public void reset() { + NativeCrypto.EVP_DigestInit(ctx, algorithm.replace("-", "").toLowerCase()); + } + + public void update(byte in) { + singleByte[0] = in; + NativeCrypto.EVP_DigestUpdate(ctx, singleByte, 0, 1); + } + + public void update(byte[] in, int inOff, int len) { + NativeCrypto.EVP_DigestUpdate(ctx, in, inOff, len); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + NativeCrypto.EVP_free(ctx); + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMessageDigestJDK.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMessageDigestJDK.java new file mode 100644 index 0000000..b00c5dd --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLMessageDigestJDK.java @@ -0,0 +1,82 @@ +package org.apache.harmony.xnet.provider.jsse; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Implements the JDK MessageDigest interface using OpenSSL's EVP API. + */ +public class OpenSSLMessageDigestJDK extends MessageDigest { + + /** + * Holds a pointer to the native message digest context. + */ + private int ctx; + + /** + * Holds a dummy buffer for writing single bytes to the digest. + */ + private byte[] singleByte = new byte[1]; + + /** + * Creates a new OpenSSLMessageDigestJDK instance for the given algorithm + * name. + * + * @param algorithm The name of the algorithm, e.g. "SHA1". + * + * @return The new OpenSSLMessageDigestJDK instance. + * + * @throws RuntimeException In case of problems. + */ + public static OpenSSLMessageDigestJDK getInstance(String algorithm) throws NoSuchAlgorithmException{ + return new OpenSSLMessageDigestJDK(algorithm); + } + + /** + * Creates a new OpenSSLMessageDigest instance for the given algorithm + * name. + * + * @param algorithm The name of the algorithm, e.g. "SHA1". + */ + private OpenSSLMessageDigestJDK(String algorithm) throws NoSuchAlgorithmException { + super(algorithm); + + ctx = NativeCrypto.EVP_new(); + try { + NativeCrypto.EVP_DigestInit(ctx, getAlgorithm().replace("-", "").toLowerCase()); + } catch (Exception ex) { + throw new NoSuchAlgorithmException(ex.getMessage() + " (" + algorithm + ")"); + } + } + + @Override + protected byte[] engineDigest() { + byte[] result = new byte[NativeCrypto.EVP_DigestSize(ctx)]; + NativeCrypto.EVP_DigestFinal(ctx, result, 0); + engineReset(); + return result; + } + + @Override + protected void engineReset() { + NativeCrypto.EVP_DigestInit(ctx, getAlgorithm().replace("-", "").toLowerCase()); + } + + @Override + protected void engineUpdate(byte input) { + singleByte[0] = input; + engineUpdate(singleByte, 0, 1); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + NativeCrypto.EVP_DigestUpdate(ctx, input, offset, len); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + NativeCrypto.EVP_free(ctx); + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketFactoryImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketFactoryImpl.java new file mode 100644 index 0000000..970f5dc --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketFactoryImpl.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2007 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.apache.harmony.xnet.provider.jsse; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.security.KeyManagementException; + +public class OpenSSLServerSocketFactoryImpl extends javax.net.ssl.SSLServerSocketFactory { + + private SSLParameters sslParameters; + private IOException instantiationException; + + public OpenSSLServerSocketFactoryImpl() { + super(); + try { + this.sslParameters = SSLParameters.getDefault(); + this.sslParameters.setUseClientMode(false); + } catch (KeyManagementException e) { + instantiationException = + new IOException("Delayed instantiation exception:"); + instantiationException.initCause(e); + } + } + + public OpenSSLServerSocketFactoryImpl(SSLParameters sslParameters) { + this.sslParameters = sslParameters; + } + + public String[] getDefaultCipherSuites() { + // TODO There might be a better way to implement this... + return OpenSSLServerSocketImpl.nativegetsupportedciphersuites(); + } + + public String[] getSupportedCipherSuites() { + return OpenSSLServerSocketImpl.nativegetsupportedciphersuites(); + } + + public ServerSocket createServerSocket() throws IOException { + return new OpenSSLServerSocketImpl((SSLParameters) sslParameters.clone()); + } + + public ServerSocket createServerSocket(int port) throws IOException { + return new OpenSSLServerSocketImpl(port, (SSLParameters) sslParameters.clone()); + } + + public ServerSocket createServerSocket(int port, int backlog) + throws IOException { + return new OpenSSLServerSocketImpl(port, backlog, (SSLParameters) sslParameters.clone()); + } + + public ServerSocket createServerSocket(int port, int backlog, + InetAddress iAddress) throws IOException { + return new OpenSSLServerSocketImpl(port, backlog, iAddress, (SSLParameters) sslParameters.clone()); + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java new file mode 100644 index 0000000..4fc6e99 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2007 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.apache.harmony.xnet.provider.jsse; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; + +import org.bouncycastle.openssl.PEMWriter; + +/** + * OpenSSL-based implementation of server sockets. + * + * This class only supports SSLv3 and TLSv1. This should be documented elsewhere + * later, for example in the package.html or a separate reference document. + */ +public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket { + private int ssl_ctx; + private boolean client_mode = true; + private long ssl_op_no = 0x00000000L; + private SSLParameters sslParameters; + private static final String[] supportedProtocols = new String[] { + "SSLv3", + "TLSv1" + }; + + private native static void nativeinitstatic(); + + static { + nativeinitstatic(); + } + + private native void nativeinit(String privatekey, String certificate, byte[] seed); + + /** + * Initialize the SSL server socket and set the certificates for the + * future handshaking. + */ + private void init() throws IOException { + String alias = sslParameters.getKeyManager().chooseServerAlias("RSA", null, null); + if (alias == null) { + throw new IOException("No suitable certificates found"); + } + + PrivateKey privateKey = sslParameters.getKeyManager().getPrivateKey(alias); + X509Certificate[] certificates = sslParameters.getKeyManager().getCertificateChain(alias); + + ByteArrayOutputStream privateKeyOS = new ByteArrayOutputStream(); + PEMWriter privateKeyPEMWriter = new PEMWriter(new OutputStreamWriter(privateKeyOS)); + privateKeyPEMWriter.writeObject(privateKey); + privateKeyPEMWriter.close(); + + ByteArrayOutputStream certificateOS = new ByteArrayOutputStream(); + PEMWriter certificateWriter = new PEMWriter(new OutputStreamWriter(certificateOS)); + + for (int i = 0; i < certificates.length; i++) { + certificateWriter.writeObject(certificates[i]); + } + certificateWriter.close(); + + nativeinit(privateKeyOS.toString(), certificateOS.toString(), + sslParameters.getSecureRandomMember() != null ? + sslParameters.getSecureRandomMember().generateSeed(1024) : null); + } + + protected OpenSSLServerSocketImpl(SSLParameters sslParameters) + throws IOException { + super(); + this.sslParameters = sslParameters; + init(); + } + + protected OpenSSLServerSocketImpl(int port, SSLParameters sslParameters) + throws IOException { + super(port); + this.sslParameters = sslParameters; + init(); + } + + protected OpenSSLServerSocketImpl(int port, int backlog, SSLParameters sslParameters) + throws IOException { + super(port, backlog); + this.sslParameters = sslParameters; + init(); + } + + protected OpenSSLServerSocketImpl(int port, int backlog, InetAddress iAddress, SSLParameters sslParameters) + throws IOException { + super(port, backlog, iAddress); + this.sslParameters = sslParameters; + init(); + } + + @Override + public boolean getEnableSessionCreation() { + return sslParameters.getEnableSessionCreation(); + } + + @Override + public void setEnableSessionCreation(boolean flag) { + sslParameters.setEnableSessionCreation(flag); + } + + /** + * The names of the protocols' versions that may be used on this SSL + * connection. + * @return an array of protocols names + */ + @Override + public String[] getSupportedProtocols() { + return supportedProtocols.clone(); + } + + /** + * See the OpenSSL ssl.h header file for more information. + */ + static private long SSL_OP_NO_SSLv3 = 0x02000000L; + static private long SSL_OP_NO_TLSv1 = 0x04000000L; + + /** + * The names of the protocols' versions that in use on this SSL connection. + * + * @return an array of protocols names + */ + @Override + public String[] getEnabledProtocols() { + ArrayList<String> array = new ArrayList<String>(); + + if ((ssl_op_no & SSL_OP_NO_SSLv3) == 0x00000000L) { + array.add(supportedProtocols[1]); + } + if ((ssl_op_no & SSL_OP_NO_TLSv1) == 0x00000000L) { + array.add(supportedProtocols[2]); + } + return array.toArray(new String[array.size()]); + } + + private native void nativesetenabledprotocols(long l); + + /** + * This method enables the protocols' versions listed by + * getSupportedProtocols(). + * + * @param protocols names of all the protocols to enable. + * + * @throws IllegalArgumentException when one or more of the names in the + * array are not supported, or when the array is null. + */ + @Override + public void setEnabledProtocols(String[] protocols) { + if (protocols == null) { + throw new IllegalArgumentException("Provided parameter is null"); + } + + ssl_op_no = SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1; + + for (int i = 0; i < protocols.length; i++) { + if (protocols[i].equals("SSLv3")) + ssl_op_no ^= SSL_OP_NO_SSLv3; + else if (protocols[i].equals("TLSv1")) + ssl_op_no ^= SSL_OP_NO_TLSv1; + else throw new IllegalArgumentException("Protocol " + protocols[i] + + " is not supported."); + } + + nativesetenabledprotocols(ssl_op_no); + } + + /** + * Gets all available ciphers from the current OpenSSL library. + * Needed by OpenSSLServerSocketFactory too. + */ + static native String[] nativegetsupportedciphersuites(); + + @Override + public String[] getSupportedCipherSuites() { + return nativegetsupportedciphersuites(); + } + + /** + * Calls native OpenSSL functions to get the enabled ciphers. + */ + private native String[] nativegetenabledciphersuites(); + + @Override + public String[] getEnabledCipherSuites() { + return nativegetenabledciphersuites(); + } + + /** + * Calls the SSL_CTX_set_cipher_list(...) OpenSSL function with the passed + * char array. + */ + private native void nativesetenabledciphersuites(String controlString); + + private boolean findSuite(String suite) { + String[] supportedCipherSuites = nativegetsupportedciphersuites(); + for(int i = 0; i < supportedCipherSuites.length; i++) + if (supportedCipherSuites[i].equals(suite)) return true; + throw new IllegalArgumentException("Protocol " + suite + + " is not supported."); + } + + /** + * This method enables the cipher suites listed by + * getSupportedCipherSuites(). + * + * @param suites the names of all the cipher suites to enable + * @throws IllegalArgumentException when one or more of the ciphers in array + * suites are not supported, or when the array is null. + */ + @Override + public void setEnabledCipherSuites(String[] suites) { + if (suites == null) { + throw new IllegalArgumentException("Provided parameter is null"); + } + String controlString = ""; + for (int i = 0; i < suites.length; i++) { + findSuite(suites[i]); + if (i == 0) controlString = suites[i]; + else controlString += ":" + suites[i]; + } + nativesetenabledciphersuites(controlString); + } + + /** + * See the OpenSSL ssl.h header file for more information. + */ + static private int SSL_VERIFY_NONE = 0x00; + static private int SSL_VERIFY_PEER = 0x01; + static private int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 0x02; + static private int SSL_VERIFY_CLIENT_ONCE = 0x04; + + /** + * Calls the SSL_CTX_set_verify(...) OpenSSL function with the passed int + * value. + */ + private native void nativesetclientauth(int value); + + private void setClientAuth() { + int value = SSL_VERIFY_NONE; + + if (sslParameters.getNeedClientAuth()) { + value |= SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT|SSL_VERIFY_CLIENT_ONCE; + } else if (sslParameters.getWantClientAuth()) { + value |= SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE; + } + + nativesetclientauth(value); + } + + @Override + public boolean getWantClientAuth() { + return sslParameters.getWantClientAuth(); + } + + @Override + public void setWantClientAuth(boolean want) { + sslParameters.setWantClientAuth(want); + setClientAuth(); + } + + @Override + public boolean getNeedClientAuth() { + return sslParameters.getNeedClientAuth(); + } + + @Override + public void setNeedClientAuth(boolean need) { + sslParameters.setNeedClientAuth(need); + setClientAuth(); + } + + @Override + public void setUseClientMode(boolean mode) { + sslParameters.setUseClientMode(mode); + } + + @Override + public boolean getUseClientMode() { + return sslParameters.getUseClientMode(); + } + + @Override + public Socket accept() throws IOException { + OpenSSLSocketImpl socket = new OpenSSLSocketImpl(sslParameters, ssl_op_no); + implAccept(socket); + socket.accept(ssl_ctx, client_mode); + + return socket; + } + + /** + * Removes OpenSSL objects from memory. + */ + private native void nativefree(); + + /** + * Unbinds the port if the socket is open. + */ + @Override + protected void finalize() throws Throwable { + if (!isClosed()) close(); + } + + @Override + public synchronized void close() throws IOException { + nativefree(); + super.close(); + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java new file mode 100644 index 0000000..475d388 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2007 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.apache.harmony.xnet.provider.jsse; + +import java.io.IOException; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.Principal; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Iterator; +import java.util.Vector; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLPermission; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionBindingEvent; +import javax.net.ssl.SSLSessionBindingListener; +import javax.net.ssl.SSLSessionContext; +import javax.security.cert.CertificateEncodingException; + +import org.apache.harmony.luni.util.TwoKeyHashMap; +import org.apache.harmony.security.provider.cert.X509CertImpl; + +/** + * Implementation of the class OpenSSLSessionImpl + * based on OpenSSL. The JNI native interface for some methods + * of this this class are defined in the file: + * org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp + */ +public class OpenSSLSessionImpl implements SSLSession { + + long lastAccessedTime = 0; + X509Certificate[] localCertificates; + X509Certificate[] peerCertificates; + + private boolean isValid = true; + private TwoKeyHashMap values = new TwoKeyHashMap(); + private javax.security.cert.X509Certificate[] peerCertificateChain; + protected int session; + private SSLParameters sslParameters; + private String peerHost; + private int peerPort; + + /** + * Class constructor creates an SSL session context given the appropriate + * SSL parameters. + * @param sslParameters the SSL parameters like ciphers' suites etc. + * @param ssl the Identifier for SSL session + */ + protected OpenSSLSessionImpl(int session, SSLParameters sslParameters, String peerHost, int peerPort) { + this.session = session; + this.sslParameters = sslParameters; + this.peerHost = peerHost; + this.peerPort = peerPort; + } + + /** + * Returns the identifier of the actual OpenSSL session. + */ + private native byte[] nativegetid(); + + /** + * Gets the identifier of the actual SSL session + * @return array of sessions' identifiers. + */ + public byte[] getId() { + return nativegetid(); + } + + /** + * Gets the creation time of the OpenSSL session. + * @return the session's creation time in milli seconds since January + * 1st, 1970 + */ + private native long nativegetcreationtime(); + + /** + * Gets the creation time of the SSL session. + * @return the session's creation time in milli seconds since 12.00 PM, + * January 1st, 1970 + */ + public long getCreationTime(){ + return nativegetcreationtime(); + } + + /** + * Gives the last time this concrete 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 accessing time in milli seconds since 12.00 PM, + * January 1st, 1970 + */ + public long getLastAccessedTime() { + if (lastAccessedTime == 0) + return nativegetcreationtime(); + else + return lastAccessedTime; + } + + /** + * Gives the largest buffer size for the application's data bound to this + * concrete SSL session. + * @return the largest buffer size + */ + public int getApplicationBufferSize() { + return SSLRecordProtocol.MAX_DATA_LENGTH; + } + + /** + * Gives the largest SSL/TLS packet size one can expect for this concrete + * SSL session. + * @return the largest packet size + */ + public int getPacketBufferSize() { + return SSLRecordProtocol.MAX_SSL_PACKET_SIZE; + } + + /** + * Gives the principal (subject) of this concrete SSL session used in the + * handshaking phase of the connection. + * @return a X509 certificate or null if no principal was defined + */ + public Principal getLocalPrincipal() { + if (localCertificates != null && localCertificates.length > 0) { + return localCertificates[0].getSubjectX500Principal(); + } else { + return null; + } + } + + /** + * Gives the certificate(s) of the principal (subject) of this concrete SSL + * session used in the handshaking phase of the connection. The OpenSSL + * native method supports only RSA certificates. + * @return an array of certificates (the local one first and then eventually + * that of the certification authority) or null if no certificate + * were used during the handshaking phase. + */ + public Certificate[] getLocalCertificates() { + X509Certificate[] localCertificates = null; + // This implementation only supports RSA certificates. + String alias = sslParameters.getKeyManager().chooseClientAlias(new String[] { "RSA" }, null, null); + if (alias != null) { + localCertificates = sslParameters.getKeyManager().getCertificateChain(alias); + } + return localCertificates; + } + + /** + * Returns the X509 certificates of the peer in the PEM format. + */ + private native byte[][] nativegetpeercertificates(); + + /** + * Gives 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 <code>SSLPeerUnverifiedcertificateException</code> if either a + * not X509 certificate was used (i.e. Kerberos certificates) or the + * peer could not be verified. + */ + public javax.security.cert.X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { + if (peerCertificateChain == null) { + try { + byte[][] bytes = nativegetpeercertificates(); + if (bytes == null) throw new SSLPeerUnverifiedException("No certificate available"); + + peerCertificateChain = new javax.security.cert.X509Certificate[bytes.length]; + + for(int i = 0; i < bytes.length; i++) { + peerCertificateChain[i] = javax.security.cert.X509Certificate.getInstance(bytes[i]); + } + + return peerCertificateChain; + } catch (javax.security.cert.CertificateException e) { + throw new SSLPeerUnverifiedException(e.getMessage()); + } + } else { + return peerCertificateChain; + } + } + + /** + * Gives the identitity of the peer in this SSL session + * determined via certificate(s). + * @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 <code>SSLPeerUnverifiedException</code> if either a not X509 + * certificate was used (i.e. Kerberos certificates) or the peer + * could not be verified. + */ + public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { + if (peerCertificates == null) { + if (peerCertificateChain == null) getPeerCertificateChain(); + try { + if (peerCertificateChain.length == 0) return new X509Certificate[]{}; + + peerCertificates = new X509CertImpl[peerCertificateChain.length]; + for(int i = 0; i < peerCertificates.length; i++) { + peerCertificates[i] = new X509CertImpl(peerCertificateChain[i].getEncoded()); + } + return peerCertificates; + } catch (SSLPeerUnverifiedException e) { + return new X509Certificate[]{}; + } catch (IOException e) { + return new X509Certificate[]{}; + } catch (CertificateEncodingException e) { + return new X509Certificate[]{}; + } + } else { + return peerCertificates; + } + } + + /** + * The identity of the principal that was used by the peer during the SSL + * handshake phase is returned by this method. + * @return a X500Principal of the last certificate for X509-based + * cipher suites. If no principal was sent, then null is returned. + * @throws <code>SSLPeerUnverifiedException</code> if either a not X509 + * certificate was used (i.e. Kerberos certificates) or the + * peer does not exist. + * + */ + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + if (peerCertificates == null) { + throw new SSLPeerUnverifiedException("No peer certificate"); + } + return peerCertificates[0].getSubjectX500Principal(); + } + + /** + * Returns via OpenSSL call the actual peer host name. + */ + private native String nativegetpeerhost(); + + /** + * The peer's host name used in this SSL session is returned. It is the host + * name of the client for the server; and that of the server for the client. + * It is not a reliable way to get a fully qualified host name: it is mainly + * used internally to implement links for a temporary cache of SSL sessions. + * + * @return the host name of the peer, or null if no information is + * available. + * + */ + public String getPeerHost() { + return peerHost; + //return nativegetpeerhost(); + } + + /** + * Returns via OpenSSL call the actual peer port number. + */ + private native String nativegetpeerport(); + + /** + * Gives the peer's port number for the actual SSL session. It is the port + * number of the client for the server; and that of the server for the + * client. It is not a reliable way to get a peer's port number: it is + * mainly used internally to implement links for a temporary cache of SSL + * sessions. + * @return the peer's port number, or -1 if no one is available. + * + */ + public int getPeerPort() { + return peerPort; + //return Integer.parseInt(nativegetpeerport()); + } + + /** + * Returns via OpenSSL call the actual cipher suite in use. + */ + private native String nativegetciphersuite(); + + /** + * Gives back a string identifier of the crypto tools used in the actual SSL + * session. For example AES_256_WITH_MD5. + * + * @return an identifier for all the cryptographic algorithms used in the + * actual SSL session. + */ + public String getCipherSuite() { + return nativegetciphersuite(); + } + + /** + * Returns via OpenSSL call the actual version of the SSL protocol. + */ + private native String nativegetprotocol(); + + /** + * Gives back the standard version name of the SSL protocol used in all + * connections pertaining to this SSL session. + * + * @return the standard version name of the SSL protocol used in all + * connections pertaining to this SSL session. + * + */ + public String getProtocol() { + return nativegetprotocol(); + } + + /** + * Gives back the context to which the actual SSL session is bound. A SSL + * context consists of (1) a possible delegate, (2) a provider and (3) a + * protocol. If the security manager is activated and one tries to access + * the SSL context an exception may be thrown if a + * <code>SSLPermission("getSSLSessionContext")</code> + * permission is not set. + * @return the SSL context used for this session, or null if it is + * unavailable. + */ + public SSLSessionContext getSessionContext() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new SSLPermission("getSSLSessionContext")); + } + return sslParameters.getClientSessionContext(); + } + + /** + * Gives back a boolean flag signaling whether a SSL session is valid and + * available + * for resuming or joining or not. + * @return true if this session may be resumed. + */ + public boolean isValid() { + SSLSessionContextImpl context = sslParameters.getClientSessionContext(); + if (isValid + && context != null + && context.getSessionTimeout() != 0 + && lastAccessedTime + context.getSessionTimeout() > System + .currentTimeMillis()) { + isValid = false; + } + return isValid; + } + + /** + * It invalidates a SSL session forbidding any resumption. + */ + public void invalidate() { + isValid = false; + } + + /** + * Gives back the object which is bound to the the input parameter name. + * This name is a sort of link to the data of the SSL session's application + * layer, if any exists. The search for this link is monitored, as a matter + * of security, by the full machinery of the <code>AccessController</code> + * class. + * + * @param <code>String name</code> the name of the binding to find. + * @return the value bound to that name, or null if the binding does not + * exist. + * @throws <code>IllegalArgumentException</code> if the argument is null. + */ + public Object getValue(String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter is null"); + } + return values.get(name, AccessController.getContext()); + } + + /** + * Gives back an array with the names (sort of links) of all the data + * objects of the application layer bound into the SSL session. The search + * for this link is monitored, as a matter of security, by the full + * machinery of the <code>AccessController</code> class. + * + * @return a non-null (possibly empty) array of names of the data objects + * bound to this SSL session. + */ + public String[] getValueNames() { + Vector v = new Vector(); + AccessControlContext current = AccessController.getContext(); + AccessControlContext cont; + for (Iterator it = values.entrySet().iterator(); it.hasNext();) { + TwoKeyHashMap.Entry entry = (TwoKeyHashMap.Entry) it.next(); + cont = (AccessControlContext) entry.getKey2(); + if ((current == null && cont == null) + || (current != null && current.equals(cont))) { + v.add(entry.getKey1()); + } + } + return (String[]) v.toArray(new String[0]); + } + + /** + * A link (name) with the specified value object of the SSL session's + * application layer data is created or replaced. If the new (or existing) + * value object implements the <code>SSLSessionBindingListener</code> + * interface, that object will be notified in due course. These links-to + * -data bounds are monitored, as a matter of security, by the full + * machinery of the <code>AccessController</code> class. + * + * @param <code>String name</code> the name of the link (no null are + * accepted!) + * @param <code>Object value</code> data object that shall be bound to + * name. + * @throws <code>IllegalArgumentException</code> if one or both + * argument(s) is null. + */ + public void putValue(String name, Object value) { + if (name == null || value == null) { + throw new IllegalArgumentException("Parameter is null"); + } + Object old = values.put(name, AccessController.getContext(), value); + if (value instanceof SSLSessionBindingListener) { + ((SSLSessionBindingListener) value) + .valueBound(new SSLSessionBindingEvent(this, name)); + } + if (old != null && old instanceof SSLSessionBindingListener) { + ((SSLSessionBindingListener) old) + .valueUnbound(new SSLSessionBindingEvent(this, name)); + } + } + + /** + * Removes a link (name) with the specified value object of the SSL + * session's application layer data. These links-to -data bounds are + * monitored, as a matter of security, by the full machinery of the + * <code>AccessController</code> class. + * + * @param <code>String name</code> the name of the link (no null are + * accepted!) + * @throws <code>IllegalArgumentException</code> if the argument is null. + */ + public void removeValue(String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter is null"); + } + values.remove(name, AccessController.getContext()); + } + + private native void nativefree(int session); + + /** + * Frees the OpenSSL session in the memory. + */ + protected void finalize() { + nativefree(session); + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignature.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignature.java new file mode 100644 index 0000000..472c9df --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSignature.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2008 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.apache.harmony.xnet.provider.jsse; + +import java.lang.reflect.Method; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.RSAPublicKey; + +/** + * Implements the JDK MessageDigest interface using OpenSSL's EVP API. + */ +public class OpenSSLSignature extends Signature { + + /** + * Holds a pointer to the native message digest context. + */ + private int ctx; + + /** + * Holds a pointer to the native DSA key. + */ + private int dsa; + + /** + * Holds a pointer to the native RSA key. + */ + private int rsa; + + /** + * Holds the OpenSSL name of the algorithm (lower case, no dashes). + */ + private String evpAlgorithm; + + /** + * Holds a dummy buffer for writing single bytes to the digest. + */ + private byte[] singleByte = new byte[1]; + + /** + * Creates a new OpenSSLSignature instance for the given algorithm name. + * + * @param algorithm The name of the algorithm, e.g. "SHA1". + * + * @return The new OpenSSLSignature instance. + * + * @throws RuntimeException In case of problems. + */ + public static OpenSSLSignature getInstance(String algorithm) throws NoSuchAlgorithmException { + //log("OpenSSLSignature", "getInstance() invoked with " + algorithm); + return new OpenSSLSignature(algorithm); + } + + /** + * Creates a new OpenSSLSignature instance for the given algorithm name. + * + * @param algorithm The name of the algorithm, e.g. "SHA1". + */ + private OpenSSLSignature(String algorithm) throws NoSuchAlgorithmException { + super(algorithm); + + int i = algorithm.indexOf("with"); + if (i == -1) { + throw new NoSuchAlgorithmException(algorithm); + } + + // For the special combination of DSA and SHA1, we need to pass the + // algorithm name as a pair consisting of crypto algorithm and hash + // algorithm. For all other (RSA) cases, passing the hash algorithm + // alone is not only sufficient, but actually necessary. OpenSSL + // doesn't accept something like RSA-SHA1. + if ("1.3.14.3.2.26with1.2.840.10040.4.1".equals(algorithm) + || "SHA1withDSA".equals(algorithm) + || "SHAwithDSA".equals(algorithm)) { + evpAlgorithm = "DSA-SHA"; + } else { + evpAlgorithm = algorithm.substring(0, i).replace("-", "").toUpperCase(); + } + + ctx = NativeCrypto.EVP_new(); + } + + @Override + protected void engineUpdate(byte input) { + singleByte[0] = input; + engineUpdate(singleByte, 0, 1); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + if (state == SIGN) { + throw new UnsupportedOperationException(); + } else { + NativeCrypto.EVP_VerifyUpdate(ctx, input, offset, len); + } + } + + @Override + protected Object engineGetParameter(String param) throws InvalidParameterException { + return null; + } + + @Override + protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { + throw new UnsupportedOperationException(); + } + + @Override + protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { + //log("OpenSSLSignature", "engineInitVerify() invoked with " + publicKey.getClass().getCanonicalName()); + + if (publicKey instanceof DSAPublicKey) { + try { + DSAPublicKey dsaPublicKey = (DSAPublicKey)publicKey; + DSAParams dsaParams = dsaPublicKey.getParams(); + dsa = NativeCrypto.EVP_PKEY_new_DSA(dsaParams.getP().toByteArray(), + dsaParams.getQ().toByteArray(), dsaParams.getG().toByteArray(), + dsaPublicKey.getY().toByteArray(), null); + + } catch (Exception ex) { + throw new InvalidKeyException(ex.toString()); + } + } else if (publicKey instanceof RSAPublicKey) { + try { + RSAPublicKey rsaPublicKey = (RSAPublicKey)publicKey; + rsa = NativeCrypto.EVP_PKEY_new_RSA(rsaPublicKey.getModulus().toByteArray(), + rsaPublicKey.getPublicExponent().toByteArray(), null, null, null); + + } catch (Exception ex) { + throw new InvalidKeyException(ex.toString()); + } + } else { + throw new InvalidKeyException("Need DSA or RSA public key"); + } + + try { + NativeCrypto.EVP_VerifyInit(ctx, evpAlgorithm); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + protected void engineSetParameter(String param, Object value) throws InvalidParameterException { + } + + @Override + protected byte[] engineSign() throws SignatureException { + throw new UnsupportedOperationException(); + } + + @Override + protected boolean engineVerify(byte[] sigBytes) throws SignatureException { + int handle = (rsa != 0) ? rsa : dsa; + + if (handle == 0) { + // This can't actually happen, but you never know... + throw new SignatureException("Need DSA or RSA public key"); + } + + try { + int result = NativeCrypto.EVP_VerifyFinal(ctx, sigBytes, 0, sigBytes.length, handle); + return result == 1; + } catch (Exception ex) { + throw new SignatureException(ex); + } + + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + + if (dsa != 0) { + NativeCrypto.EVP_PKEY_free(dsa); + } + + if (rsa != 0) { + NativeCrypto.EVP_PKEY_free(rsa); + } + + if (ctx != 0) { + NativeCrypto.EVP_free(ctx); + } + } + + // TODO Just for debugging purposes, remove later. + private static void log(String tag, String msg) { + try { + Class clazz = Class.forName("android.util.Log"); + Method method = clazz.getMethod("d", new Class[] { + String.class, String.class + }); + method.invoke(null, new Object[] { + tag, msg + }); + } catch (Exception ex) { + // Silently ignore. + } + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketFactoryImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketFactoryImpl.java new file mode 100644 index 0000000..aeb23b6 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketFactoryImpl.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007 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.apache.harmony.xnet.provider.jsse; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; + +import org.apache.harmony.xnet.provider.jsse.SSLParameters; + +public class OpenSSLSocketFactoryImpl extends javax.net.ssl.SSLSocketFactory { + + private SSLParameters sslParameters; + private IOException instantiationException; + + public OpenSSLSocketFactoryImpl() { + super(); + try { + sslParameters = SSLParameters.getDefault(); + } catch (KeyManagementException e) { + instantiationException = + new IOException("Delayed instantiation exception:"); + instantiationException.initCause(e); + } + } + + public OpenSSLSocketFactoryImpl(SSLParameters sslParameters) { + super(); + this.sslParameters = sslParameters; + } + + public String[] getDefaultCipherSuites() { + // TODO There might be a better implementation for this... + return OpenSSLSocketImpl.nativegetsupportedciphersuites(); + } + + public String[] getSupportedCipherSuites() { + return OpenSSLSocketImpl.nativegetsupportedciphersuites(); + } + + public Socket createSocket() throws IOException { + if (instantiationException != null) { + throw instantiationException; + } + return new OpenSSLSocketImpl((SSLParameters) sslParameters.clone()); + } + + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return new OpenSSLSocketImpl(host, port, (SSLParameters) sslParameters.clone()); + } + + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) + throws IOException, UnknownHostException { + return new OpenSSLSocketImpl(host, port, localHost, localPort, (SSLParameters) sslParameters.clone()); + } + + public Socket createSocket(InetAddress host, int port) throws IOException { + return new OpenSSLSocketImpl(host, port, (SSLParameters) sslParameters.clone()); + } + + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) + throws IOException { + return new OpenSSLSocketImpl(address, port, localAddress, localPort, (SSLParameters) sslParameters.clone()); + } + + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return new OpenSSLSocketImplWrapper(s, host, port, autoClose, (SSLParameters) sslParameters.clone()); + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java new file mode 100644 index 0000000..1d38ca9 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java @@ -0,0 +1,1037 @@ +/* + * Copyright (C) 2007 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.apache.harmony.xnet.provider.jsse; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.net.ssl.HandshakeCompletedEvent; +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSession; + +import org.apache.harmony.security.provider.cert.X509CertImpl; +import org.bouncycastle.openssl.PEMWriter; + +/** + * Implementation of the class OpenSSLSocketImpl + * based on OpenSSL. The JNI native interface for some methods + * of this this class are defined in the file: + * org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp + * + * This class only supports SSLv3 and TLSv1. This should be documented elsewhere + * later, for example in the package.html or a separate reference document. + */ +public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket { + private int ssl_ctx; + private int ssl; + private InputStream is; + private OutputStream os; + private Object handshakeLock = new Object(); + private Object readLock = new Object(); + private Object writeLock = new Object(); + private SSLParameters sslParameters; + private OpenSSLSessionImpl sslSession; + private Socket socket; + private boolean autoClose; + private boolean handshakeStarted = false; + private ArrayList listeners; + private long ssl_op_no = 0x00000000L; + private int timeout = 0; + private InetSocketAddress address; + + private static final String[] supportedProtocols = new String[] { + "SSLv3", + "TLSv1" + }; + + private static int instanceCount = 0; + + public static int getInstanceCount() { + synchronized (OpenSSLSocketImpl.class) { + return instanceCount; + } + } + + private static void updateInstanceCount(int amount) { + synchronized (OpenSSLSocketImpl.class) { + instanceCount += amount; + } + } + + /** + * Initialize OpenSSL library. + */ + private native static void nativeinitstatic(); + + static { + nativeinitstatic(); + } + + private native void nativeinit(String privatekey, String certificate, byte[] seed); + + /** + * Initialize the SSL socket and set the certificates for the + * future handshaking. + */ + private void init() throws IOException { + String alias = sslParameters.getKeyManager().chooseClientAlias(new String[] { "RSA" }, null, null); + if (alias != null) { + PrivateKey privateKey = sslParameters.getKeyManager().getPrivateKey(alias); + X509Certificate[] certificates = sslParameters.getKeyManager().getCertificateChain(alias); + + ByteArrayOutputStream privateKeyOS = new ByteArrayOutputStream(); + PEMWriter privateKeyPEMWriter = new PEMWriter(new OutputStreamWriter(privateKeyOS)); + privateKeyPEMWriter.writeObject(privateKey); + privateKeyPEMWriter.close(); + + ByteArrayOutputStream certificateOS = new ByteArrayOutputStream(); + PEMWriter certificateWriter = new PEMWriter(new OutputStreamWriter(certificateOS)); + + for (int i = 0; i < certificates.length; i++) { + certificateWriter.writeObject(certificates[i]); + } + certificateWriter.close(); + + nativeinit(privateKeyOS.toString(), certificateOS.toString(), + sslParameters.getSecureRandomMember() != null ? + sslParameters.getSecureRandomMember().generateSeed(1024) : null); + } else { + nativeinit(null, null, + sslParameters.getSecureRandomMember() != null ? + sslParameters.getSecureRandomMember().generateSeed(1024) : null); + } + } + + /** + * Class constructor with 2 parameters + * + * @param <code>SSLParameters sslParameters</code> Parameters for the SSL + * context + * @param <code>long ssl_op_no</code> Parameter to set the enabled + * protocols + * @throws <code>IOException</code> if network fails + */ + protected OpenSSLSocketImpl(SSLParameters sslParameters, long ssl_op_no) throws IOException { + super(); + this.sslParameters = sslParameters; + this.ssl_op_no = ssl_op_no; + updateInstanceCount(1); + } + + /** + * Class constructor with 1 parameter + * + * @param <code>SSLParameters sslParameters</code> Parameters for the SSL + * context + * @throws <code>IOException</code> if network fails + */ + protected OpenSSLSocketImpl(SSLParameters sslParameters) throws IOException { + super(); + this.sslParameters = sslParameters; + init(); + updateInstanceCount(1); + } + + /** + * Class constructor with 3 parameters + * + * @param <code> String host</code> + * @param <code>int port</code> + * @param <code>SSLParameters sslParameters</code> + * @throws <code>IOException</code> if network fails + * @throws <code>UnknownHostException</code> host not defined + */ + protected OpenSSLSocketImpl(String host, int port, + SSLParameters sslParameters) + throws IOException { + super(host, port); + this.sslParameters = sslParameters; + init(); + updateInstanceCount(1); + } + + + /** + * Class constructor with 3 parameters: 1st is InetAddress + * + * @param <code>InetAddress address</code> + * @param <code>int port</code> + * @param <code>SSLParameters sslParameters</code> + * @throws <code>IOException</code> if network fails + * @throws <code>UnknownHostException</code> host not defined + */ + protected OpenSSLSocketImpl(InetAddress address, int port, + SSLParameters sslParameters) + throws IOException { + super(address, port); + this.sslParameters = sslParameters; + init(); + updateInstanceCount(1); + } + + + /** + * Class constructor with 5 parameters: 1st is host + * + * @param <code>String host</code> + * @param <code>int port</code> + * @param <code>InetAddress localHost</code> + * @param <code>int localPort</code> + * @param <code>SSLParameters sslParameters</code> + * @throws <code>IOException</code> if network fails + * @throws <code>UnknownHostException</code> host not defined + */ + protected OpenSSLSocketImpl(String host, int port, InetAddress clientAddress, + int clientPort, SSLParameters sslParameters) + throws IOException { + super(host, port, clientAddress, clientPort); + this.sslParameters = sslParameters; + init(); + updateInstanceCount(1); + } + + /** + * Class constructor with 5 parameters: 1st is InetAddress + * + * @param <code>InetAddress address</code> + * @param <code>int port</code> + * @param <code>InetAddress localAddress</code> + * @param <code>int localPort</code> + * @param <code>SSLParameters sslParameters</code> + * @throws <code>IOException</code> if network fails + * @throws <code>UnknownHostException</code> host not defined + */ + protected OpenSSLSocketImpl(InetAddress address, int port, + InetAddress clientAddress, int clientPort, SSLParameters sslParameters) + throws IOException { + super(address, port, clientAddress, clientPort); + this.sslParameters = sslParameters; + init(); + updateInstanceCount(1); + } + + /** + * Constructor with 5 parameters: 1st is socket. Enhances an existing socket + * with SSL functionality. + * + * @param <code>Socket socket</code> + * @param <code>String host</code> + * @param <code>int port</code> + * @param <code>boolean autoClose</code> + * @param <code>SSLParameters sslParameters</code> + * @throws <code>IOException</code> if network fails + */ + protected OpenSSLSocketImpl(Socket socket, String host, int port, + boolean autoClose, SSLParameters sslParameters) throws IOException { + super(); + this.socket = socket; + this.timeout = socket.getSoTimeout(); + this.address = new InetSocketAddress(host, port); + this.autoClose = autoClose; + this.sslParameters = sslParameters; + init(); + updateInstanceCount(1); + } + + /** + * Adds OpenSSL functionality to the existing socket and starts the SSL + * handshaking. + */ + private native boolean nativeconnect(int ctx, Socket sock, boolean client_mode, int sslsession) throws IOException; + private native int nativegetsslsession(int ssl); + private native String nativecipherauthenticationmethod(); + + /** + * Gets the suitable session reference from the session cache container. + * + * @return OpenSSLSessionImpl + */ + private OpenSSLSessionImpl getOpenSSLSessionImpl() { + try { + byte[] id; + SSLSession ses; + for (Enumeration<byte[]> en = sslParameters.getClientSessionContext().getIds(); en.hasMoreElements();) { + id = en.nextElement(); + ses = sslParameters.getClientSessionContext().getSession(id); + if (ses instanceof OpenSSLSessionImpl && ses.isValid() && + super.getInetAddress() != null && + super.getInetAddress().getHostAddress() != null && + super.getInetAddress().getHostName().equals(ses.getPeerHost()) && + super.getPort() == ses.getPeerPort()) { + return (OpenSSLSessionImpl) ses; + } + } + } catch (Exception ex) { + // It's not clear to me under what circumstances the above code + // might fail. I also can't reproduce it. + } + return null; + } + + /** + * Starts a TLS/SSL handshake on this connection using some native methods + * from the OpenSSL library. It can negotiate new encryption keys, change + * cipher suites, or initiate a new session. The certificate chain is + * verified if the correspondent property in java.Security is set. All + * listensers are notified at the end of the TLS/SSL handshake. + * + * @throws <code>IOException</code> if network fails + */ + public synchronized void startHandshake() throws IOException { + synchronized (handshakeLock) { + if (!handshakeStarted) { + handshakeStarted = true; + } else { + return; + } + } + + { + // Debug + int size = 0; + for (Enumeration<byte[]> en = sslParameters.getClientSessionContext().getIds(); + en.hasMoreElements(); en.nextElement()) { size++; }; + } + OpenSSLSessionImpl session = getOpenSSLSessionImpl(); + + // Check if it's allowed to create a new session (default is true) + if (!sslParameters.getEnableSessionCreation() && session == null) { + throw new SSLHandshakeException("SSL Session may not be created"); + } else { + if (nativeconnect(ssl_ctx, this.socket != null ? + this.socket : this, sslParameters.getUseClientMode(), session != null ? session.session : 0)) { + session.lastAccessedTime = System.currentTimeMillis(); + sslSession = session; + } else { + if (address == null) sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl), + sslParameters, super.getInetAddress().getHostName(), super.getPort()); + else sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl), + sslParameters, address.getHostName(), address.getPort()); + try { + X509Certificate[] peerCertificates = (X509Certificate[]) sslSession.getPeerCertificates(); + + if (peerCertificates == null || peerCertificates.length == 0) { + throw new SSLException("Server sends no certificate"); + } + + sslParameters.getTrustManager().checkServerTrusted(peerCertificates, + nativecipherauthenticationmethod()); + sslParameters.getClientSessionContext().putSession(sslSession); + } catch (CertificateException e) { + throw new SSLException("Not trusted server certificate", e); + } + } + } + + if (listeners != null) { + // notify the listeners + HandshakeCompletedEvent event = + new HandshakeCompletedEvent(this, sslSession); + int size = listeners.size(); + for (int i=0; i<size; i++) { + ((HandshakeCompletedListener)listeners.get(i)) + .handshakeCompleted(event); + } + } + } + + // To be synchronized because of the verify_callback + native synchronized void nativeaccept(Socket socketObject, int m_ctx, boolean client_mode); + + /** + * Performs the first part of a SSL/TLS handshaking process with a given + * 'host' connection and initializes the SSLSession. + */ + protected void accept(int m_ctx, boolean client_mode) throws IOException { + // Must be set because no handshaking is necessary + // in this situation + handshakeStarted = true; + + nativeaccept(this, m_ctx, client_mode); + + sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl), + sslParameters, super.getInetAddress().getHostName(), super.getPort()); + sslSession.lastAccessedTime = System.currentTimeMillis(); + } + + /** + * Callback methode for the OpenSSL native certificate verification process. + * + * @param <code>byte[][] bytes</code> Byte array containing the cert's + * information. + * @return 0 if the certificate verification fails or 1 if OK + */ + @SuppressWarnings("unused") + private int verify_callback(byte[][] bytes) { + try { + X509Certificate[] peerCertificateChain = new X509Certificate[bytes.length]; + for(int i = 0; i < bytes.length; i++) { + peerCertificateChain[i] = + new X509CertImpl(javax.security.cert.X509Certificate.getInstance(bytes[i]).getEncoded()); + } + + try { + // TODO "null" String + sslParameters.getTrustManager().checkClientTrusted(peerCertificateChain, "null"); + } catch (CertificateException e) { + throw new AlertException(AlertProtocol.BAD_CERTIFICATE, + new SSLException("Not trusted server certificate", e)); + } + } catch (javax.security.cert.CertificateException e) { + return 0; + } catch (IOException e) { + return 0; + } + return 1; + } + + /** + * Returns an input stream for this SSL socket using native calls to the + * OpenSSL library. + * + * @return: an input stream for reading bytes from this socket. + * @throws: <code>IOException</code> if an I/O error occurs when creating + * the input stream, the socket is closed, the socket is not + * connected, or the socket input has been shutdown. + */ + public InputStream getInputStream() throws IOException { + synchronized(this) { + if (is == null) { + is = new SSLInputStream(); + } + + return is; + } + } + + /** + * Returns an output stream for this SSL socket using native calls to the + * OpenSSL library. + * + * @return an output stream for writing bytes to this socket. + * @throws <code>IOException</code> if an I/O error occurs when creating + * the output stream, or no connection to the socket exists. + */ + public OutputStream getOutputStream() throws IOException { + synchronized(this) { + if (os == null) { + os = new SSLOutputStream(); + } + + return os; + } + } + + /** + * This method is not supported for this SSLSocket implementation. + */ + public void shutdownInput() throws IOException { + throw new UnsupportedOperationException( + "Method shutdownInput() is not supported."); + } + + /** + * This method is not supported for this SSLSocket implementation. + */ + public void shutdownOutput() throws IOException { + throw new UnsupportedOperationException( + "Method shutdownOutput() is not supported."); + } + + /** + * Reads with the native SSL_read function from the encrypted data stream + * @return -1 if error or the end of the stream is reached. + */ + private native int nativeread(int timeout) throws IOException; + private native int nativeread(byte[] b, int off, int len, int timeout) throws IOException; + + /** + * This inner class provides input data stream functionality + * for the OpenSSL native implementation. It is used to + * read data received via SSL protocol. + */ + private class SSLInputStream extends InputStream { + SSLInputStream() throws IOException { + /** + /* Note: When startHandshake() throws an exception, no + * SSLInputStream object will be created. + */ + OpenSSLSocketImpl.this.startHandshake(); + } + + /** + * Reads one byte. If there is no data in the underlying buffer, + * this operation can block until the data will be + * available. + * @return read value. + * @throws <code>IOException</code> + */ + public int read() throws IOException { + synchronized(readLock) { + return OpenSSLSocketImpl.this.nativeread(timeout); + } + } + + /** + * Method acts as described in spec for superclass. + * @see java.io.InputStream#read(byte[],int,int) + */ + public int read(byte[] b, int off, int len) throws IOException { + synchronized(readLock) { + return OpenSSLSocketImpl.this.nativeread(b, off, len, timeout); + } + } + } + + /** + * Writes with the native SSL_write function to the encrypted data stream. + */ + private native void nativewrite(int b) throws IOException; + private native void nativewrite(byte[] b, int off, int len) throws IOException; + + /** + * This inner class provides output data stream functionality + * for the OpenSSL native implementation. It is used to + * write data according to the encryption parameters given in SSL context. + */ + private class SSLOutputStream extends OutputStream { + SSLOutputStream() throws IOException { + /** + /* Note: When startHandshake() throws an exception, no + * SSLInputStream object will be created. + */ + OpenSSLSocketImpl.this.startHandshake(); + } + + /** + * Method acts as described in spec for superclass. + * @see java.io.OutputStream#write(int) + */ + public void write(int b) throws IOException { + synchronized(writeLock) { + OpenSSLSocketImpl.this.nativewrite(b); + } + } + + /** + * Method acts as described in spec for superclass. + * @see java.io.OutputStream#write(byte[],int,int) + */ + public void write(byte[] b, int start, int len) throws IOException { + synchronized(writeLock) { + OpenSSLSocketImpl.this.nativewrite(b, start, len); + } + } + } + + + /** + * The SSL session used by this connection is returned. The SSL session + * determines which cipher suite should be used by all connections within + * that session and which identities have the session's client and server. + * This method starts the SSL handshake. + * @return the SSLSession. + * @throws <code>IOException</code> if the handshake fails + */ + public SSLSession getSession() { + try { + startHandshake(); + } catch (IOException e) { + Logger.getLogger(getClass().getName()).log(Level.WARNING, + "Error negotiating SSL connection.", e); + + // return an invalid session with + // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL" + return SSLSessionImpl.NULL_SESSION; + } + return sslSession; + } + + /** + * Registers a listener to be notified that a SSL handshake + * was successfully completed on this connection. + * @param <code>HandShakeCompletedListener listener</code> + * @throws <code>IllegalArgumentException</code> if listener is null. + */ + public void addHandshakeCompletedListener( + HandshakeCompletedListener listener) { + if (listener == null) { + throw new IllegalArgumentException("Provided listener is null"); + } + if (listeners == null) { + listeners = new ArrayList(); + } + listeners.add(listener); + } + + /** + * The method removes a registered listener. + * @param <code>HandShakeCompletedListener listener</code> + * @throws IllegalArgumentException if listener is null or not registered + */ + public void removeHandshakeCompletedListener( + HandshakeCompletedListener listener) { + if (listener == null) { + throw new IllegalArgumentException("Provided listener is null"); + } + if (listeners == null) { + throw new IllegalArgumentException( + "Provided listener is not registered"); + } + if (!listeners.remove(listener)) { + throw new IllegalArgumentException( + "Provided listener is not registered"); + } + } + + /** + * Returns true if new SSL sessions may be established by this socket. + * + * @return true if the session may be created; false if a session already + * exists and must be resumed. + */ + public boolean getEnableSessionCreation() { + return sslParameters.getEnableSessionCreation(); + } + + /** + * Set a flag for the socket to inhibit or to allow the creation of a new + * SSL sessions. If the flag is set to false, and there are no actual + * sessions to resume, then there will be no successful handshaking. + * + * @param <code>boolean flag</code> true if session may be created; false + * if a session already exists and must be resumed. + */ + public void setEnableSessionCreation(boolean flag) { + sslParameters.setEnableSessionCreation(flag); + } + + /** + * Gets all available ciphers from the current OpenSSL library. + * Needed by OpenSSLSocketFactory too. + */ + static native String[] nativegetsupportedciphersuites(); + + /** + * The names of the cipher suites which could be used by the SSL connection + * are returned. + * @return an array of cipher suite names + */ + public String[] getSupportedCipherSuites() { + return nativegetsupportedciphersuites(); + } + + private native String[] nativegetenabledciphersuites(); + + /** + * The names of the cipher suites that are in use in the actual the SSL + * connection are returned. + * + * @return an array of cipher suite names + */ + public String[] getEnabledCipherSuites() { + return nativegetenabledciphersuites(); + } + + /** + * Calls the SSL_CTX_set_cipher_list(...) OpenSSL function with the passed + * char array. + */ + private native void nativesetenabledciphersuites(String controlString); + + private boolean findSuite(String suite) { + String[] supportedCipherSuites = nativegetsupportedciphersuites(); + for(int i = 0; i < supportedCipherSuites.length; i++) + if (supportedCipherSuites[i].equals(suite)) return true; + throw new IllegalArgumentException("Protocol " + suite + + " is not supported."); + } + + /** + * This method enables the cipher suites listed by + * getSupportedCipherSuites(). + * + * @param <code> String[] suites</code> names of all the cipher suites to + * put on use + * @throws <code>IllegalArgumentException</code> when one or more of the + * ciphers in array suites are not supported, or when the array + * is null. + */ + public void setEnabledCipherSuites(String[] suites) { + if (suites == null) { + throw new IllegalArgumentException("Provided parameter is null"); + } + String controlString = ""; + for(int i = 0; i < suites.length; i++) { + findSuite(suites[i]); + if (i == 0) controlString = suites[i]; + else controlString += ":" + suites[i]; + } + nativesetenabledciphersuites(controlString); + } + + /** + * The names of the protocols' versions that may be used on this SSL + * connection. + * @return an array of protocols names + */ + public String[] getSupportedProtocols() { + return supportedProtocols.clone(); + } + + /** + * SSL mode of operation with or without back compatibility. See the OpenSSL + * ssl.h header file for more information. + */ + static private long SSL_OP_NO_SSLv3 = 0x02000000L; + static private long SSL_OP_NO_TLSv1 = 0x04000000L; + + /** + * The names of the protocols' versions that are in use on this SSL + * connection. + * + * @return an array of protocols names + */ + @Override + public String[] getEnabledProtocols() { + ArrayList<String> array = new ArrayList<String>(); + + if ((ssl_op_no & SSL_OP_NO_SSLv3) == 0x00000000L) { + array.add(supportedProtocols[1]); + } + if ((ssl_op_no & SSL_OP_NO_TLSv1) == 0x00000000L) { + array.add(supportedProtocols[2]); + } + return array.toArray(new String[array.size()]); + } + + private native void nativesetenabledprotocols(long l); + + /** + * This method enables the protocols' versions listed by + * getSupportedProtocols(). + * + * @param protocols The names of all the protocols to put on use + * + * @throws IllegalArgumentException when one or more of the names in the + * array are not supported, or when the array is null. + */ + @Override + public synchronized void setEnabledProtocols(String[] protocols) { + + if (protocols == null) { + throw new IllegalArgumentException("Provided parameter is null"); + } + + ssl_op_no = SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1; + + for(int i = 0; i < protocols.length; i++) { + if (protocols[i].equals("SSLv3")) + ssl_op_no ^= SSL_OP_NO_SSLv3; + else if (protocols[i].equals("TLSv1")) + ssl_op_no ^= SSL_OP_NO_TLSv1; + else throw new IllegalArgumentException("Protocol " + protocols[i] + + " is not supported."); + } + + nativesetenabledprotocols(ssl_op_no); + } + + /** + * This method gives true back if the SSL socket is set to client mode. + * + * @return true if the socket should do the handshaking as client. + */ + public boolean getUseClientMode() { + return sslParameters.getUseClientMode(); + } + + /** + * This method set the actual SSL socket to client mode. + * + * @param <code>boolean mode</code> true if the socket starts in client + * mode + * @throws <code>IllegalArgumentException</code> if mode changes during + * handshake. + */ + public synchronized void setUseClientMode(boolean mode) { + if (handshakeStarted) { + throw new IllegalArgumentException( + "Could not change the mode after the initial handshake has begun."); + } + sslParameters.setUseClientMode(mode); + } + + /** + * Returns true if the SSL socket requests client's authentication. Relevant + * only for server sockets! + * + * @return true if client authentication is desired, false if not. + */ + public boolean getWantClientAuth() { + return sslParameters.getWantClientAuth(); + } + + /** + * Returns true if the SSL socket needs client's authentication. Relevant + * only for server sockets! + * + * @return true if client authentication is desired, false if not. + */ + public boolean getNeedClientAuth() { + return sslParameters.getNeedClientAuth(); + } + + /** + * Sets the SSL socket to use client's authentication. Relevant only for + * server sockets! + * + * @param <code>boolean need</code> true if client authentication is + * desired, false if not. + */ + public void setNeedClientAuth(boolean need) { + sslParameters.setNeedClientAuth(need); + } + + /** + * Sets the SSL socket to use client's authentication. Relevant only for + * server sockets! Notice that in contrast to setNeedClientAuth(..) this + * method will continue the negotiation if the client decide not to send + * authentication credentials. + * + * @param <code>boolean want</code> true if client authentication is + * desired, false if not. + */ + public void setWantClientAuth(boolean want) { + sslParameters.setWantClientAuth(want); + } + + /** + * This method is not supported for SSLSocket implementation. + */ + public void sendUrgentData(int data) throws IOException { + throw new SocketException( + "Method sendUrgentData() is not supported."); + } + + /** + * This method is not supported for SSLSocket implementation. + */ + public void setOOBInline(boolean on) throws SocketException { + throw new SocketException( + "Methods sendUrgentData, setOOBInline are not supported."); + } + + /** + * Set the read timeout on this socket. The SO_TIMEOUT option, is specified + * in milliseconds. The read operation will block indefinitely for a zero + * value. + * + * @param timeout the read timeout value + * @throws SocketException if an error occurs setting the option + */ + public synchronized void setSoTimeout(int timeout) throws SocketException { + super.setSoTimeout(timeout); + this.timeout = timeout; + } + + private native void nativeinterrupt() throws IOException; + private native void nativeclose() throws IOException; + + /** + * Closes the SSL socket. Once closed, a socket is not available for further + * use anymore under any circumstance. A new socket must be created. + * + * @throws <code>IOException</code> if an I/O error happens during the + * socket's closure. + */ + public void close() throws IOException { + synchronized (handshakeLock) { + if (!handshakeStarted) { + handshakeStarted = true; + + synchronized (this) { + nativefree(); + + if (socket != null) { + if (autoClose && !socket.isClosed()) socket.close(); + } else { + if (!super.isClosed()) super.close(); + } + } + + return; + } + } + + nativeinterrupt(); + + synchronized (this) { + synchronized (writeLock) { + synchronized (readLock) { + + IOException pendingException = null; + + // Shut down the SSL connection, per se. + try { + if (handshakeStarted) { + nativeclose(); + } + } catch (IOException ex) { + /* + * Note the exception at this point, but try to continue + * to clean the rest of this all up before rethrowing. + */ + pendingException = ex; + } + + /* + * Even if the above call failed, it is still safe to free + * the native structs, and we need to do so lest we leak + * memory. + */ + nativefree(); + + if (socket != null) { + if (autoClose && !socket.isClosed()) + socket.close(); + } else { + if (!super.isClosed()) + super.close(); + } + + if (pendingException != null) { + throw pendingException; + } + } + } + } + } + + private native void nativefree(); + + protected void finalize() throws IOException { + updateInstanceCount(-1); + + if (ssl == 0) { + /* + * It's already been closed, so there's no need to do anything + * more at this point. + */ + return; + } + + // Note the underlying socket up-front, for possible later use. + Socket underlyingSocket = socket; + + // Fire up a thread to (hopefully) do all the real work. + Finalizer f = new Finalizer(); + f.setDaemon(true); + f.start(); + + /* + * Give the finalizer thread one second to run. If it fails to + * terminate in that time, interrupt it (which may help if it + * is blocked on an interruptible I/O operation), make a note + * in the log, and go ahead and close the underlying socket if + * possible. + */ + try { + f.join(1000); + } catch (InterruptedException ex) { + // Reassert interrupted status. + Thread.currentThread().interrupt(); + } + + if (f.isAlive()) { + f.interrupt(); + Logger.global.log(Level.SEVERE, + "Slow finalization of SSL socket (" + this + ", for " + + underlyingSocket + ")"); + if ((underlyingSocket != null) && !underlyingSocket.isClosed()) { + underlyingSocket.close(); + } + } + } + + /** + * Helper class for a thread that knows how to call {@link #close} on behalf + * of instances being finalized, since that call can take arbitrarily long + * (e.g., due to a slow network), and an overly long-running finalizer will + * cause the process to be totally aborted. + */ + private class Finalizer extends Thread { + public void run() { + Socket underlyingSocket = socket; // for error reporting + try { + close(); + } catch (IOException ex) { + /* + * Clear interrupted status, so that the Logger call + * immediately below won't get spuriously interrupted. + */ + Thread.interrupted(); + + Logger.global.log(Level.SEVERE, + "Trouble finalizing SSL socket (" + + OpenSSLSocketImpl.this + ", for " + underlyingSocket + + ")", + ex); + } + } + } + + /** + * Verifies an RSA signature. Conceptually, this method doesn't really + * belong here, but due to its native code being closely tied to OpenSSL + * (just like the rest of this class), we put it here for the time being. + * This also solves potential problems with native library initialization. + * + * @param message The message to verify + * @param signature The signature to verify + * @param algorithm The hash/sign algorithm to use, i.e. "RSA-SHA1" + * @param key The RSA public key to use + * @return true if the verification succeeds, false otherwise + */ + public static boolean verifySignature(byte[] message, byte[] signature, String algorithm, RSAPublicKey key) { + byte[] modulus = key.getModulus().toByteArray(); + byte[] exponent = key.getPublicExponent().toByteArray(); + + return nativeverifysignature(message, signature, algorithm, modulus, exponent) == 1; + } + + private static native int nativeverifysignature(byte[] message, byte[] signature, + String algorithm, byte[] modulus, byte[] exponent); +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImplWrapper.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImplWrapper.java new file mode 100644 index 0000000..e3451aa --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImplWrapper.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2008 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.apache.harmony.xnet.provider.jsse; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; + +/** + * This class wraps the SSL functionality over an existing conneted socket. + */ +public class OpenSSLSocketImplWrapper extends OpenSSLSocketImpl { + + private Socket socket; + + protected OpenSSLSocketImplWrapper(Socket socket, String host, int port, + boolean autoClose, SSLParameters sslParameters) throws IOException { + super(socket, host, port, autoClose, sslParameters); + if (!socket.isConnected()) { + throw new SocketException("Socket is not connected."); + } + this.socket = socket; + } + + public void connect(SocketAddress sockaddr, int timeout) + throws IOException { + throw new IOException("Underlying socket is already connected."); + } + + public void connect(SocketAddress sockaddr) throws IOException { + throw new IOException("Underlying socket is already connected."); + } + + public void bind(SocketAddress sockaddr) throws IOException { + throw new IOException("Underlying socket is already connected."); + } + + public SocketAddress getRemoteSocketAddress() { + return socket.getRemoteSocketAddress(); + } + + public SocketAddress getLocalSocketAddress() { + return socket.getLocalSocketAddress(); + } + + public InetAddress getLocalAddress() { + return socket.getLocalAddress(); + } + + public InetAddress getInetAddress() { + return socket.getInetAddress(); + } + + public String toString() { + return "SSL socket over " + socket.toString(); + } + + public void setSoLinger(boolean on, int linger) throws SocketException { + socket.setSoLinger(on, linger); + } + + public void setTcpNoDelay(boolean on) throws SocketException { + socket.setTcpNoDelay(on); + } + + public void setReuseAddress(boolean on) throws SocketException { + socket.setReuseAddress(on); + } + + public void setKeepAlive(boolean on) throws SocketException { + socket.setKeepAlive(on); + } + + public void setTrafficClass(int tos) throws SocketException { + socket.setTrafficClass(tos); + } + + public void setSoTimeout(int to) throws SocketException { + socket.setSoTimeout(to); + super.setSoTimeout(to); + } + + public void setSendBufferSize(int size) throws SocketException { + socket.setSendBufferSize(size); + } + + public void setReceiveBufferSize(int size) throws SocketException { + socket.setReceiveBufferSize(size); + } + + public boolean getTcpNoDelay() throws SocketException { + return socket.getTcpNoDelay(); + } + + public boolean getReuseAddress() throws SocketException { + return socket.getReuseAddress(); + } + + public boolean getOOBInline() throws SocketException { + return socket.getOOBInline(); + } + + public boolean getKeepAlive() throws SocketException { + return socket.getKeepAlive(); + } + + public int getTrafficClass() throws SocketException { + return socket.getTrafficClass(); + } + + public int getSoTimeout() throws SocketException { + return socket.getSoTimeout(); + } + + public int getSoLinger() throws SocketException { + return socket.getSoLinger(); + } + + public int getSendBufferSize() throws SocketException { + return socket.getSendBufferSize(); + } + + public int getReceiveBufferSize() throws SocketException { + return socket.getReceiveBufferSize(); + } + + public boolean isConnected() { + return socket.isConnected(); + } + + public boolean isClosed() { + return socket.isClosed(); + } + + public boolean isBound() { + return socket.isBound(); + } + + public boolean isOutputShutdown() { + return socket.isOutputShutdown(); + } + + public boolean isInputShutdown() { + return socket.isInputShutdown(); + } + + public int getPort() { + return socket.getPort(); + } + + public int getLocalPort() { + return socket.getLocalPort(); + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/PRF.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/PRF.java new file mode 100644 index 0000000..3ed9b2a --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/PRF.java @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.AlertException; +import org.apache.harmony.xnet.provider.jsse.Logger; + +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import javax.net.ssl.SSLException; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +/** + * This class provides functionality for computation + * of PRF values for TLS (http://www.ietf.org/rfc/rfc2246.txt) + * and SSL v3 (http://wp.netscape.com/eng/ssl3) protocols. + */ +public class PRF { + private static Logger.Stream logger = Logger.getStream("prf"); + + private static Mac md5_mac; + private static Mac sha_mac; + protected static MessageDigest md5; + protected static MessageDigest sha; + private static int md5_mac_length; + private static int sha_mac_length; + + static private void init() { + try { + md5_mac = Mac.getInstance("HmacMD5"); + sha_mac = Mac.getInstance("HmacSHA1"); + } catch (NoSuchAlgorithmException e) { + throw new AlertException(AlertProtocol.INTERNAL_ERROR, + new SSLException( + "There is no provider of HmacSHA1 or HmacMD5 " + + "algorithms installed in the system")); + } + md5_mac_length = md5_mac.getMacLength(); + sha_mac_length = sha_mac.getMacLength(); + try { + md5 = MessageDigest.getInstance("MD5"); + sha = MessageDigest.getInstance("SHA-1"); + } catch (Exception e) { + throw new AlertException(AlertProtocol.INTERNAL_ERROR, + new SSLException( + "Could not initialize the Digest Algorithms.")); + } + } + + /** + * Computes the value of SSLv3 pseudo random function. + * @param out: the buffer to fill up with the value of the function. + * @param secret: the buffer containing the secret value to generate prf. + * @param seed: the seed to be used. + */ + static synchronized void computePRF_SSLv3(byte[] out, byte[] secret, byte[] seed) { + if (sha == null) { + init(); + } + int pos = 0; + int iteration = 1; + byte[] digest; + while (pos < out.length) { + byte[] pref = new byte[iteration]; + Arrays.fill(pref, (byte) (64 + iteration++)); + sha.update(pref); + sha.update(secret); + sha.update(seed); + md5.update(secret); + md5.update(sha.digest()); + digest = md5.digest(); // length == 16 + if (pos + 16 > out.length) { + System.arraycopy(digest, 0, out, pos, out.length - pos); + pos = out.length; + } else { + System.arraycopy(digest, 0, out, pos, 16); + pos += 16; + } + } + } + + /** + * Computes the value of TLS pseudo random function. + * @param out: the buffer to fill up with the value of the function. + * @param secret: the buffer containing the secret value to generate prf. + * @param str_bytes: the label bytes to be used. + * @param seed: the seed to be used. + */ + synchronized static void computePRF(byte[] out, byte[] secret, + byte[] str_byts, byte[] seed) throws GeneralSecurityException { + if (sha_mac == null) { + init(); + } + // Do concatenation of the label with the seed: + // (metterings show that is is faster to concatenate the arrays + // and to call HMAC.update on cancatenation, than twice call for + // each of the part, i.e.: + // time(HMAC.update(label+seed)) + // < time(HMAC.update(label)) + time(HMAC.update(seed)) + // but it takes more memmory (approximaty on 4%) + /* + byte[] tmp_seed = new byte[seed.length + str_byts.length]; + System.arraycopy(str_byts, 0, tmp_seed, 0, str_byts.length); + System.arraycopy(seed, 0, tmp_seed, str_byts.length, seed.length); + seed = tmp_seed; + */ + SecretKeySpec keyMd5; + SecretKeySpec keySha1; + if ((secret == null) || (secret.length == 0)) { + secret = new byte[8]; + keyMd5 = new SecretKeySpec(secret, "HmacMD5"); + keySha1 = new SecretKeySpec(secret, "HmacSHA1"); + } else { + int length = secret.length >> 1; // division by 2 + int offset = secret.length & 1; // remainder + keyMd5 = new SecretKeySpec(secret, 0, length + offset, + "HmacMD5"); + keySha1 = new SecretKeySpec(secret, length, length + + offset, "HmacSHA1"); + } + + //byte[] str_byts = label.getBytes(); + + if (logger != null) { + logger.println("secret["+secret.length+"]: "); + logger.printAsHex(16, "", " ", secret); + logger.println("label["+str_byts.length+"]: "); + logger.printAsHex(16, "", " ", str_byts); + logger.println("seed["+seed.length+"]: "); + logger.printAsHex(16, "", " ", seed); + logger.println("MD5 key:"); + logger.printAsHex(16, "", " ", keyMd5.getEncoded()); + logger.println("SHA1 key:"); + logger.printAsHex(16, "", " ", keySha1.getEncoded()); + } + + md5_mac.init(keyMd5); + sha_mac.init(keySha1); + + int pos = 0; + md5_mac.update(str_byts); + byte[] hash = md5_mac.doFinal(seed); // A(1) + while (pos < out.length) { + md5_mac.update(hash); + md5_mac.update(str_byts); + md5_mac.update(seed); + if (pos + md5_mac_length < out.length) { + md5_mac.doFinal(out, pos); + pos += md5_mac_length; + } else { + System.arraycopy(md5_mac.doFinal(), 0, out, + pos, out.length - pos); + break; + } + // make A(i) + hash = md5_mac.doFinal(hash); + } + if (logger != null) { + logger.println("P_MD5:"); + logger.printAsHex(md5_mac_length, "", " ", out); + } + + pos = 0; + sha_mac.update(str_byts); + hash = sha_mac.doFinal(seed); // A(1) + byte[] sha1hash; + while (pos < out.length) { + sha_mac.update(hash); + sha_mac.update(str_byts); + sha1hash = sha_mac.doFinal(seed); + for (int i = 0; (i < sha_mac_length) & (pos < out.length); i++) { + out[pos++] ^= sha1hash[i]; + } + // make A(i) + hash = sha_mac.doFinal(hash); + } + + if (logger != null) { + logger.println("PRF:"); + logger.printAsHex(sha_mac_length, "", " ", out); + } + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ProtocolVersion.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ProtocolVersion.java new file mode 100644 index 0000000..1343c3b --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ProtocolVersion.java @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ +package org.apache.harmony.xnet.provider.jsse; + +import java.util.Hashtable; + +/** + * + * Represents Protocol Version + */ +public class ProtocolVersion { + + /** + * Protocol name + */ + public final String name; + + /** + * Protocol version as byte array + */ + public final byte[] version; + + /** + * Protocols supported by this provider implementaton + */ + public static final String[] supportedProtocols = new String[] { "TLSv1", + "SSLv3" }; + + private static Hashtable protocolsByName = new Hashtable(4); + + private ProtocolVersion(String name, byte[] version) { + this.name = name; + this.version = version; + } + + /** + * Compares this ProtocolVersion to the specified object. + */ + public boolean equals(Object o) { + if (o instanceof ProtocolVersion + && this.version[0] == ((ProtocolVersion) o).version[0] + && this.version[1] == ((ProtocolVersion) o).version[1]) { + return true; + } + return false; + } + + /** + * + * Returns true if protocol version is supported + * + * @param version + */ + public static boolean isSupported(byte[] version) { + if (version[0] != 3 || (version[1] != 0 && version[1] != 1)) { + return false; + } + return true; + } + + /** + * Returns ProtocolVersion + * + * @param version + * @return + */ + public static ProtocolVersion getByVersion(byte[] version) { + if (version[0] == 3) { + if (version[1] == 1) { + return TLSv1; + } + if (version[1] == 0) { + return SSLv3; + } + } + return null; + } + + /** + * Returns true if provider supports protocol version + * + * @param name + * @return + */ + public static boolean isSupported(String name) { + return protocolsByName.containsKey(name); + } + + /** + * Returns ProtocolVersion + * + * @param name + * @return + */ + public static ProtocolVersion getByName(String name) { + return (ProtocolVersion) protocolsByName.get(name); + } + + /** + * Highest protocol version supported by provider implementation + * + * @param protocols + * @return + */ + public static ProtocolVersion getLatestVersion(String[] protocols) { + if (protocols == null || protocols.length == 0) { + return null; + } + ProtocolVersion latest = getByName(protocols[0]); + ProtocolVersion current; + for (int i = 1; i < protocols.length; i++) { + current = getByName(protocols[i]); + if (current == null) { + continue; + } + if ((latest == null) + || (latest.version[0] < current.version[0]) + || (latest.version[0] == current.version[0] && latest.version[1] < current.version[1])) { + latest = current; + } + } + return latest; + + } + + /** + * SSL 3.0 protocol version + */ + public static ProtocolVersion SSLv3 = new ProtocolVersion("SSLv3", + new byte[] { 3, 0 }); + + /** + * TLS 1.0 protocol version + */ + public static ProtocolVersion TLSv1 = new ProtocolVersion("TLSv1", + new byte[] { 3, 1 }); + + static { + protocolsByName.put(SSLv3.name, SSLv3); + protocolsByName.put(TLSv1.name, TLSv1); + protocolsByName.put("SSL", SSLv3); + protocolsByName.put("TLS", TLSv1); + } + +}
\ No newline at end of file diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java new file mode 100644 index 0000000..44009b9 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.SSLInputStream; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * This is a wrapper input stream for ByteBuffer data source. + * Among with the read functionality it provides info + * about number of cunsumed bytes from the source ByteBuffer. + * The source ByteBuffer object can be reseted. + * So one instance of this wrapper can be reused for several + * ByteBuffer data sources. + */ +public class SSLBufferedInput extends SSLInputStream { + + private ByteBuffer in; + private int bytik; + private int consumed = 0; + + /** + * Constructor + */ + protected SSLBufferedInput() {} + + /** + * Sets the buffer as a data source + */ + protected void setSourceBuffer(ByteBuffer in) { + consumed = 0; + this.in = in; + } + + /** + * Returns the number of bytes available for reading. + */ + public int available() throws IOException { + // in assumption that the buffer has been set + return in.remaining(); + } + + /** + * Returns the number of consumed bytes. + */ + protected int consumed() { + return consumed; + } + + /** + * Reads the following byte value. If there are no bytes in the source + * buffer, method throws java.nio.BufferUnderflowException. + */ + public int read() throws IOException { + // TODO: implement optimized read(int) + // and read(byte[], int, int) methods + bytik = in.get() & 0x00FF; + consumed ++; + return bytik; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java new file mode 100644 index 0000000..0d3a07c --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLContextImpl.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +// BEGIN android-removed +// import org.apache.harmony.xnet.provider.jsse.SSLSocketFactoryImpl; +// END android-removed +import org.apache.harmony.xnet.provider.jsse.SSLEngineImpl; +import org.apache.harmony.xnet.provider.jsse.SSLParameters; +// BEGIN android-removed +// import org.apache.harmony.xnet.provider.jsse.SSLServerSocketFactoryImpl; +// END android-removed + +import java.security.KeyManagementException; +import java.security.SecureRandom; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContextSpi; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +/** + * Implementation of SSLContext service provider interface. + */ +public class SSLContextImpl extends SSLContextSpi { + + // client session context contains the set of reusable + // client-side SSL sessions + private SSLSessionContextImpl clientSessionContext = + new SSLSessionContextImpl(); + // server session context contains the set of reusable + // server-side SSL sessions + private SSLSessionContextImpl serverSessionContext = + new SSLSessionContextImpl(); + + protected SSLParameters sslParameters; + + public SSLContextImpl() { + super(); + } + + public void engineInit(KeyManager[] kms, TrustManager[] tms, + SecureRandom sr) throws KeyManagementException { + sslParameters = new SSLParameters(kms, tms, sr, clientSessionContext, + serverSessionContext); + } + + public SSLSocketFactory engineGetSocketFactory() { + if (sslParameters == null) { + throw new IllegalStateException("SSLContext is not initiallized."); + } + return new OpenSSLSocketFactoryImpl(sslParameters); + } + + public SSLServerSocketFactory engineGetServerSocketFactory() { + if (sslParameters == null) { + throw new IllegalStateException("SSLContext is not initiallized."); + } + return new OpenSSLServerSocketFactoryImpl(sslParameters); + } + + public SSLEngine engineCreateSSLEngine(String host, int port) { + if (sslParameters == null) { + throw new IllegalStateException("SSLContext is not initiallized."); + } + return new SSLEngineImpl(host, port, + (SSLParameters) sslParameters.clone()); + } + + public SSLEngine engineCreateSSLEngine() { + if (sslParameters == null) { + throw new IllegalStateException("SSLContext is not initiallized."); + } + return new SSLEngineImpl((SSLParameters) sslParameters.clone()); + } + + public SSLSessionContext engineGetServerSessionContext() { + return serverSessionContext; + } + + public SSLSessionContext engineGetClientSessionContext() { + return clientSessionContext; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLEngineAppData.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLEngineAppData.java new file mode 100644 index 0000000..698723b --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLEngineAppData.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.AlertException; + +import java.nio.ByteBuffer; +import javax.net.ssl.SSLException; + +/** + * This class is used to retrieve the application data + * arrived for the SSLEngine. + */ +public class SSLEngineAppData implements org.apache.harmony.xnet.provider.jsse.Appendable { + + /** + * Buffer containing received application data. + */ + byte[] buffer; + + /** + * Constructor + */ + protected SSLEngineAppData() {} + + /** + * Stores received data. The source data is not cloned, + * just the array reference is remembered into the buffer field. + */ + public void append(byte[] src) { + if (buffer != null) { + throw new AlertException( + AlertProtocol.INTERNAL_ERROR, + new SSLException("Attempt to override the data")); + } + buffer = src; + } + + /** + * Places the data from the buffer into the array of destination + * ByteBuffer objects. + */ + protected int placeTo(ByteBuffer[] dsts, int offset, int length) { + if (buffer == null) { + return 0; + } + int pos = 0; + int len = buffer.length; + int rem; + // write data to the buffers + for (int i=offset; i<offset+length; i++) { + rem = dsts[i].remaining(); + // TODO: optimization work - use hasArray, array(), arraycopy + if (len - pos < rem) { + // can fully write remaining data into buffer + dsts[i].put(buffer, pos, len - pos); + pos = len; + // data was written, exit + break; + } else { + // write chunk of data + dsts[i].put(buffer, pos, rem); + pos += rem; + } + } + if (pos != len) { + // The data did not feet into the buffers, + // it should not happen, because the destination buffers + // had been checked for the space before record unwrapping. + // But if it so, we should allert about internal error. + throw new AlertException( + AlertProtocol.INTERNAL_ERROR, + new SSLException( + "The received application data could not be fully written" + + "into the destination buffers")); + } + buffer = null; + return len; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLEngineDataStream.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLEngineDataStream.java new file mode 100644 index 0000000..bc13577 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLEngineDataStream.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.nio.ByteBuffer; + +/** + * This class provides the DataStream functionality + * implemented over the array of ByteBuffer instances. + * Among with the data chunks read functionality + * it provides the info about amount of consumed data. + * The source ByteBuffer objects can be replaced by other. + * So one instance of this wrapper can be reused for several + * data sources. + */ +public class SSLEngineDataStream implements DataStream { + + private ByteBuffer[] srcs; + private int offset; + private int limit; + + private int available; + private int consumed; + + protected SSLEngineDataStream() {} + + protected void setSourceBuffers(ByteBuffer[] srcs, int offset, int length) { + this.srcs = srcs; + this.offset = offset; + this.limit = offset+length; + this.consumed = 0; + this.available = 0; + for (int i=offset; i<limit; i++) { + if (srcs[i] == null) { + throw new IllegalStateException( + "Some of the input parameters are null"); + } + available += srcs[i].remaining(); + } + } + + public int available() { + return available; + } + + public boolean hasData() { + return available > 0; + } + + public byte[] getData(int length) { + // TODO: optimization work: + // use ByteBuffer.get(byte[],int,int) + // and ByteBuffer.hasArray() methods + int len = (length < available) ? length : available; + available -= len; + consumed += len; + byte[] res = new byte[len]; + int pos = 0; + loop: + for (; offset<limit; offset++) { + while (srcs[offset].hasRemaining()) { + res[pos++] = srcs[offset].get(); + len --; + if (len == 0) { + break loop; + } + } + } + return res; + } + + protected int consumed() { + return consumed; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLEngineImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLEngineImpl.java new file mode 100644 index 0000000..383e146 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLEngineImpl.java @@ -0,0 +1,751 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.AlertException; +import org.apache.harmony.xnet.provider.jsse.SSLSessionImpl; +import org.apache.harmony.xnet.provider.jsse.SSLEngineDataStream; + +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ReadOnlyBufferException; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; + +/** + * Implementation of SSLEngine. + * @see javax.net.ssl.SSLEngine class documentation for more information. + */ +public class SSLEngineImpl extends SSLEngine { + + // indicates if peer mode was set + private boolean peer_mode_was_set = false; + // indicates if handshake has been started + private boolean handshake_started = false; + // indicates if inbound operations finished + private boolean isInboundDone = false; + // indicates if outbound operations finished + private boolean isOutboundDone = false; + // indicates if close_notify alert had been sent to another peer + private boolean close_notify_was_sent = false; + // indicates if close_notify alert had been received from another peer + private boolean close_notify_was_received = false; + // indicates if engine was closed (it means that + // all the works on it are done, except (probably) some finalizing work) + private boolean engine_was_closed = false; + // indicates if engine was shutted down (it means that + // all cleaning work had been done and the engine is not operable) + private boolean engine_was_shutteddown = false; + + // record protocol to be used + protected SSLRecordProtocol recordProtocol; + // input stream for record protocol + private SSLBufferedInput recProtIS; + // handshake protocol to be used + private HandshakeProtocol handshakeProtocol; + // alert protocol to be used + private AlertProtocol alertProtocol; + // place where application data will be stored + private SSLEngineAppData appData; + // outcoming application data stream + private SSLEngineDataStream dataStream = new SSLEngineDataStream(); + // active session object + private SSLSessionImpl session; + + // peer configuration parameters + protected SSLParameters sslParameters; + + // in case of emergency situations when data could not be + // placed in destination buffers it will be stored in this + // fields + private byte[] remaining_wrapped_data = null; + private byte[] remaining_hsh_data = null; + + // logger + private Logger.Stream logger = Logger.getStream("engine"); + + /** + * Ctor + * @param sslParameters: SSLParameters + */ + protected SSLEngineImpl(SSLParameters sslParameters) { + super(); + this.sslParameters = sslParameters; + } + + /** + * Ctor + * @param host: String + * @param port: int + * @param sslParameters: SSLParameters + */ + protected SSLEngineImpl(String host, int port, SSLParameters sslParameters) { + super(host, port); + this.sslParameters = sslParameters; + } + + /** + * Starts the handshake. + * @throws SSLException + * @see javax.net.ssl.SSLEngine#beginHandshake() method documentation + * for more information + */ + public void beginHandshake() throws SSLException { + if (engine_was_closed) { + throw new SSLException("Engine has already been closed."); + } + if (!peer_mode_was_set) { + throw new IllegalStateException("Client/Server mode was not set"); + } + if (!handshake_started) { + handshake_started = true; + if (getUseClientMode()) { + handshakeProtocol = new ClientHandshakeImpl(this); + } else { + handshakeProtocol = new ServerHandshakeImpl(this); + } + appData = new SSLEngineAppData(); + alertProtocol = new AlertProtocol(); + recProtIS = new SSLBufferedInput(); + recordProtocol = new SSLRecordProtocol(handshakeProtocol, + alertProtocol, recProtIS, appData); + } + handshakeProtocol.start(); + } + + /** + * Closes inbound operations of this engine + * @throws SSLException + * @see javax.net.ssl.SSLEngine#closeInbound() method documentation + * for more information + */ + public void closeInbound() throws SSLException { + if (logger != null) { + logger.println("closeInbound() "+isInboundDone); + } + if (isInboundDone) { + return; + } + isInboundDone = true; + engine_was_closed = true; + if (handshake_started) { + if (!close_notify_was_received) { + if (session != null) { + session.invalidate(); + } + alertProtocol.alert(AlertProtocol.FATAL, + AlertProtocol.INTERNAL_ERROR); + throw new SSLException("Inbound is closed before close_notify " + + "alert has been received."); + } + } else { + // engine is closing before initial handshake has been made + shutdown(); + } + } + + /** + * Closes outbound operations of this engine + * @see javax.net.ssl.SSLEngine#closeOutbound() method documentation + * for more information + */ + public void closeOutbound() { + if (logger != null) { + logger.println("closeOutbound() "+isOutboundDone); + } + if (isOutboundDone) { + return; + } + isOutboundDone = true; + if (handshake_started) { + // initial handshake had been started + alertProtocol.alert(AlertProtocol.WARNING, + AlertProtocol.CLOSE_NOTIFY); + close_notify_was_sent = true; + } else { + // engine is closing before initial handshake has been made + shutdown(); + } + engine_was_closed = true; + } + + /** + * Returns handshake's delegated tasks to be run + * @return the delegated task to be executed. + * @see javax.net.ssl.SSLEngine#getDelegatedTask() method documentation + * for more information + */ + public Runnable getDelegatedTask() { + return handshakeProtocol.getTask(); + } + + /** + * Returns names of supported cipher suites. + * @return array of strings containing the names of supported cipher suites + * @see javax.net.ssl.SSLEngine#getSupportedCipherSuites() method + * documentation for more information + */ + public String[] getSupportedCipherSuites() { + return CipherSuite.getSupportedCipherSuiteNames(); + } + + // --------------- SSLParameters based methods --------------------- + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#getEnabledCipherSuites() method + * documentation for more information + */ + public String[] getEnabledCipherSuites() { + return sslParameters.getEnabledCipherSuites(); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#setEnabledCipherSuites(String[]) method + * documentation for more information + */ + public void setEnabledCipherSuites(String[] suites) { + sslParameters.setEnabledCipherSuites(suites); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#getSupportedProtocols() method + * documentation for more information + */ + public String[] getSupportedProtocols() { + return (String[]) ProtocolVersion.supportedProtocols.clone(); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#getEnabledProtocols() method + * documentation for more information + */ + public String[] getEnabledProtocols() { + return sslParameters.getEnabledProtocols(); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#setEnabledProtocols(String[]) method + * documentation for more information + */ + public void setEnabledProtocols(String[] protocols) { + sslParameters.setEnabledProtocols(protocols); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#setUseClientMode(boolean) method + * documentation for more information + */ + public void setUseClientMode(boolean mode) { + if (handshake_started) { + throw new IllegalArgumentException( + "Could not change the mode after the initial handshake has begun."); + } + sslParameters.setUseClientMode(mode); + peer_mode_was_set = true; + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#getUseClientMode() method + * documentation for more information + */ + public boolean getUseClientMode() { + return sslParameters.getUseClientMode(); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#setNeedClientAuth(boolean) method + * documentation for more information + */ + public void setNeedClientAuth(boolean need) { + sslParameters.setNeedClientAuth(need); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#getNeedClientAuth() method + * documentation for more information + */ + public boolean getNeedClientAuth() { + return sslParameters.getNeedClientAuth(); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#setWantClientAuth(boolean) method + * documentation for more information + */ + public void setWantClientAuth(boolean want) { + sslParameters.setWantClientAuth(want); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#getWantClientAuth() method + * documentation for more information + */ + public boolean getWantClientAuth() { + return sslParameters.getWantClientAuth(); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#setEnableSessionCreation(boolean) method + * documentation for more information + */ + public void setEnableSessionCreation(boolean flag) { + sslParameters.setEnableSessionCreation(flag); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#getEnableSessionCreation() method + * documentation for more information + */ + public boolean getEnableSessionCreation() { + return sslParameters.getEnableSessionCreation(); + } + + // ----------------------------------------------------------------- + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#getHandshakeStatus() method + * documentation for more information + */ + public SSLEngineResult.HandshakeStatus getHandshakeStatus() { + if (!handshake_started || engine_was_shutteddown) { + // initial handshake has not been started yet + return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + } + if (alertProtocol.hasAlert()) { + // need to send an alert + return SSLEngineResult.HandshakeStatus.NEED_WRAP; + } + if (close_notify_was_sent && !close_notify_was_received) { + // waiting for "close_notify" response + return SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + return handshakeProtocol.getStatus(); + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#getSession() method + * documentation for more information + */ + public SSLSession getSession() { + if (session != null) { + return session; + } else { + return SSLSessionImpl.NULL_SESSION; + } + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#isInboundDone() method + * documentation for more information + */ + public boolean isInboundDone() { + return isInboundDone || engine_was_closed; + } + + /** + * This method works according to the specification of implemented class. + * @see javax.net.ssl.SSLEngine#isOutboundDone() method + * documentation for more information + */ + public boolean isOutboundDone() { + return isOutboundDone; + } + + /** + * Decodes one complete SSL/TLS record provided in the source buffer. + * If decoded record contained application data, this data will + * be placed in the destination buffers. + * For more information about TLS record fragmentation see + * TLS v 1 specification (http://www.ietf.org/rfc/rfc2246.txt) p 6.2. + * @param src source buffer containing SSL/TLS record. + * @param dsts destination buffers to place received application data. + * @see javax.net.ssl.SSLEngine#unwrap(ByteBuffer,ByteBuffer[],int,int) + * method documentation for more information + */ + public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, + int offset, int length) throws SSLException { + if (engine_was_shutteddown) { + return new SSLEngineResult(SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, 0); + } + if ((src == null) || (dsts == null)) { + throw new IllegalStateException( + "Some of the input parameters are null"); + } + + if (!handshake_started) { + beginHandshake(); + } + + SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus(); + // If is is initial handshake or connection closure stage, + // check if this call was made in spite of handshake status + if ((session == null || engine_was_closed) && ( + handshakeStatus.equals( + SSLEngineResult.HandshakeStatus.NEED_WRAP) || + handshakeStatus.equals( + SSLEngineResult.HandshakeStatus.NEED_TASK))) { + return new SSLEngineResult( + getEngineStatus(), handshakeStatus, 0, 0); + } + + if (src.remaining() < recordProtocol.getMinRecordSize()) { + return new SSLEngineResult( + SSLEngineResult.Status.BUFFER_UNDERFLOW, + getHandshakeStatus(), 0, 0); + } + + try { + src.mark(); + // check the destination buffers and count their capacity + int capacity = 0; + for (int i=offset; i<offset+length; i++) { + if (dsts[i] == null) { + throw new IllegalStateException( + "Some of the input parameters are null"); + } + if (dsts[i].isReadOnly()) { + throw new ReadOnlyBufferException(); + } + capacity += dsts[i].remaining(); + } + if (capacity < recordProtocol.getDataSize(src.remaining())) { + return new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + getHandshakeStatus(), 0, 0); + } + recProtIS.setSourceBuffer(src); + // unwrap the record contained in source buffer, pass it + // to appropriate client protocol (alert, handshake, or app) + // and retrieve the type of unwrapped data + int type = recordProtocol.unwrap(); + // process the data and return the result + switch (type) { + case ContentType.HANDSHAKE: + case ContentType.CHANGE_CIPHER_SPEC: + if (handshakeProtocol.getStatus().equals( + SSLEngineResult.HandshakeStatus.FINISHED)) { + session = recordProtocol.getSession(); + } + break; + case ContentType.APPLICATION_DATA: + break; + case ContentType.ALERT: + if (alertProtocol.isFatalAlert()) { + alertProtocol.setProcessed(); + if (session != null) { + session.invalidate(); + } + String description = "Fatal alert received " + + alertProtocol.getAlertDescription(); + shutdown(); + throw new SSLException(description); + } else { + if (logger != null) { + logger.println("Warning allert has been received: " + + alertProtocol.getAlertDescription()); + } + switch(alertProtocol.getDescriptionCode()) { + case AlertProtocol.CLOSE_NOTIFY: + alertProtocol.setProcessed(); + close_notify_was_received = true; + if (!close_notify_was_sent) { + closeOutbound(); + closeInbound(); + } else { + closeInbound(); + shutdown(); + } + break; + case AlertProtocol.NO_RENEGOTIATION: + alertProtocol.setProcessed(); + if (session == null) { + // message received during the initial + // handshake + throw new AlertException( + AlertProtocol.HANDSHAKE_FAILURE, + new SSLHandshakeException( + "Received no_renegotiation " + + "during the initial handshake")); + } else { + // just stop the handshake + handshakeProtocol.stop(); + } + break; + default: + alertProtocol.setProcessed(); + } + } + break; + } + return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), + recProtIS.consumed(), + // place the app. data (if any) into the dest. buffers + // and get the number of produced bytes: + appData.placeTo(dsts, offset, length)); + } catch (BufferUnderflowException e) { + // there was not enought data ource buffer to make complete packet + src.reset(); + return new SSLEngineResult(SSLEngineResult.Status.BUFFER_UNDERFLOW, + getHandshakeStatus(), 0, 0); + } catch (AlertException e) { + // fatal alert occured + alertProtocol.alert(AlertProtocol.FATAL, e.getDescriptionCode()); + engine_was_closed = true; + src.reset(); + if (session != null) { + session.invalidate(); + } + // shutdown work will be made after the alert will be sent + // to another peer (by wrap method) + throw e.getReason(); + } catch (SSLException e) { + throw e; + } catch (IOException e) { + alertProtocol.alert(AlertProtocol.FATAL, + AlertProtocol.INTERNAL_ERROR); + engine_was_closed = true; + // shutdown work will be made after the alert will be sent + // to another peer (by wrap method) + throw new SSLException(e.getMessage()); + } + } + + /** + * Encodes the application data into SSL/TLS record. If handshake status + * of the engine differs from NOT_HANDSHAKING the operation can work + * without consuming of the source data. + * For more information about TLS record fragmentation see + * TLS v 1 specification (http://www.ietf.org/rfc/rfc2246.txt) p 6.2. + * @param srcs the source buffers with application data to be encoded + * into SSL/TLS record. + * @param offset the offset in the destination buffers array pointing to + * the first buffer with the source data. + * @param len specifies the maximum number of buffers to be procesed. + * @param dst the destination buffer where encoded data will be placed. + * @see javax.net.ssl.SSLEngine#wrap(ByteBuffer[],int,int,ByteBuffer) method + * documentation for more information + */ + public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, + int len, ByteBuffer dst) throws SSLException { + if (engine_was_shutteddown) { + return new SSLEngineResult(SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, 0); + } + if ((srcs == null) || (dst == null)) { + throw new IllegalStateException( + "Some of the input parameters are null"); + } + if (dst.isReadOnly()) { + throw new ReadOnlyBufferException(); + } + + if (!handshake_started) { + beginHandshake(); + } + + SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus(); + // If it is an initial handshake or connection closure stage, + // check if this call was made in spite of handshake status + if ((session == null || engine_was_closed) && ( + handshakeStatus.equals( + SSLEngineResult.HandshakeStatus.NEED_UNWRAP) || + handshakeStatus.equals( + SSLEngineResult.HandshakeStatus.NEED_TASK))) { + return new SSLEngineResult( + getEngineStatus(), handshakeStatus, 0, 0); + } + + int capacity = dst.remaining(); + int produced = 0; + + if (alertProtocol.hasAlert()) { + // we have an alert to be sent + if (capacity < recordProtocol.getRecordSize(2)) { + return new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + handshakeStatus, 0, 0); + } + byte[] alert_data = alertProtocol.wrap(); + // place the alert record into destination + dst.put(alert_data); + if (alertProtocol.isFatalAlert()) { + alertProtocol.setProcessed(); + if (session != null) { + session.invalidate(); + } + // fatal alert has been sent, so shut down the engine + shutdown(); + return new SSLEngineResult( + SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, alert_data.length); + } else { + alertProtocol.setProcessed(); + // check if the works on this engine have been done + if (close_notify_was_sent && close_notify_was_received) { + shutdown(); + return new SSLEngineResult(SSLEngineResult.Status.CLOSED, + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, + 0, alert_data.length); + } + return new SSLEngineResult( + getEngineStatus(), + getHandshakeStatus(), + 0, alert_data.length); + } + } + + if (capacity < recordProtocol.getMinRecordSize()) { + if (logger != null) { + logger.println("Capacity of the destination(" + +capacity+") < MIN_PACKET_SIZE(" + +recordProtocol.getMinRecordSize()+")"); + } + return new SSLEngineResult(SSLEngineResult.Status.BUFFER_OVERFLOW, + handshakeStatus, 0, 0); + } + + try { + if (!handshakeStatus.equals( + SSLEngineResult.HandshakeStatus.NEED_WRAP)) { + // so we wraps application data + dataStream.setSourceBuffers(srcs, offset, len); + if ((capacity < SSLRecordProtocol.MAX_SSL_PACKET_SIZE) && + (capacity < recordProtocol.getRecordSize( + dataStream.available()))) { + if (logger != null) { + logger.println("The destination buffer(" + +capacity+") can not take the resulting packet(" + + recordProtocol.getRecordSize( + dataStream.available())+")"); + } + return new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + handshakeStatus, 0, 0); + } + if (remaining_wrapped_data == null) { + remaining_wrapped_data = + recordProtocol.wrap(ContentType.APPLICATION_DATA, + dataStream); + } + if (capacity < remaining_wrapped_data.length) { + // It should newer happen because we checked the destination + // buffer size, but there is a possibility + // (if dest buffer was filled outside) + // so we just remember the data into remaining_wrapped_data + // and will enclose it during the the next call + return new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + handshakeStatus, dataStream.consumed(), 0); + } else { + dst.put(remaining_wrapped_data); + produced = remaining_wrapped_data.length; + remaining_wrapped_data = null; + return new SSLEngineResult(getEngineStatus(), + handshakeStatus, dataStream.consumed(), produced); + } + } else { + if (remaining_hsh_data == null) { + remaining_hsh_data = handshakeProtocol.wrap(); + } + if (capacity < remaining_hsh_data.length) { + // It should newer happen because we checked the destination + // buffer size, but there is a possibility + // (if dest buffer was filled outside) + // so we just remember the data into remaining_hsh_data + // and will enclose it during the the next call + return new SSLEngineResult( + SSLEngineResult.Status.BUFFER_OVERFLOW, + handshakeStatus, 0, 0); + } else { + dst.put(remaining_hsh_data); + produced = remaining_hsh_data.length; + remaining_hsh_data = null; + + handshakeStatus = handshakeProtocol.getStatus(); + if (handshakeStatus.equals( + SSLEngineResult.HandshakeStatus.FINISHED)) { + session = recordProtocol.getSession(); + } + } + return new SSLEngineResult( + getEngineStatus(), getHandshakeStatus(), 0, produced); + } + } catch (AlertException e) { + // fatal alert occured + alertProtocol.alert(AlertProtocol.FATAL, e.getDescriptionCode()); + engine_was_closed = true; + if (session != null) { + session.invalidate(); + } + // shutdown work will be made after the alert will be sent + // to another peer (by wrap method) + throw e.getReason(); + } + } + + // Shutdownes the engine and makes all cleanup work. + private void shutdown() { + engine_was_closed = true; + engine_was_shutteddown = true; + isOutboundDone = true; + isInboundDone = true; + if (handshake_started) { + alertProtocol.shutdown(); + alertProtocol = null; + handshakeProtocol.shutdown(); + handshakeProtocol = null; + recordProtocol.shutdown(); + recordProtocol = null; + } + } + + + private SSLEngineResult.Status getEngineStatus() { + return (engine_was_closed) + ? SSLEngineResult.Status.CLOSED + : SSLEngineResult.Status.OK; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java new file mode 100644 index 0000000..bd9b6cf --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.io.IOException; +import java.io.InputStream; + +/** + * This class is a base for all input stream classes used + * in protocol implementation. It extends an InputStream with + * some additional read methods allowing to read TLS specific + * data types such as uint8, uint32 etc (see TLS v 1 specification + * at http://www.ietf.org/rfc/rfc2246.txt). + */ +public abstract class SSLInputStream extends InputStream { + + /** + * @see java.io.InputStream#available() + */ + public abstract int available() throws IOException; + + /** + * Reads the following byte value. Note that in the case of + * reaching of the end of the data this methods throws the + * exception, not return -1. The type of exception depends + * on implementation. It was done for simplifying and speeding + * up of processing of such cases. + * @see org.apache.harmony.xnet.provider.jsse.SSLStreamedInput#read() + * @see org.apache.harmony.xnet.provider.jsse.SSLBufferedInput#read() + * @see org.apache.harmony.xnet.provider.jsse.HandshakeIODataStream#read() + */ + public abstract int read() throws IOException; + + /** + * @see java.io.InputStream#skip(long) + */ + public long skip(long n) throws IOException { + long skept = n; + while (n > 0) { + read(); + n--; + } + return skept; + } + + /** + * Reads and returns uint8 value. + */ + public int readUint8() throws IOException { + return read() & 0x00FF; + } + + /** + * Reads and returns uint16 value. + */ + public int readUint16() throws IOException { + return (read() << 8) | (read() & 0x00FF); + } + + /** + * Reads and returns uint24 value. + */ + public int readUint24() throws IOException { + return (read() << 16) | (read() << 8) | (read() & 0x00FF); + } + + /** + * Reads and returns uint32 value. + */ + public long readUint32() throws IOException { + return (read() << 24) | (read() << 16) + | (read() << 8) | (read() & 0x00FF); + } + + /** + * Reads and returns uint64 value. + */ + public long readUint64() throws IOException { + return (read() << 56) | (read() << 48) + | (read() << 40) | (read() << 32) + | (read() << 24) | (read() << 16) + | (read() << 8) | (read() & 0x00FF); + } + + /** + * Returns the vector of opaque values of specified length; + * @param length - the length of the vector to be read. + * @return the read data + * @throws IOException if read operation could not be finished. + */ + public byte[] read(int length) throws IOException { + byte[] res = new byte[length]; + for (int i=0; i<length; i++) { + res[i] = (byte) read(); + } + return res; + } + + /** + * @see java.io.InputStream#read(byte[],int,int) + */ + public int read(byte[] b, int off, int len) throws IOException { + int read_b; + int i = 0; + do { + if ((read_b = read()) == -1) { + return (i == 0) ? -1 : i; + } + b[off+i] = (byte) read_b; + i++; + } while ((available() != 0) && (i<len)); + return i; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java new file mode 100644 index 0000000..0607e17 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java @@ -0,0 +1,447 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.SSLSessionContextImpl; + +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; + +/** + * The instances of this class incapsulate all the info + * about enabled cipher suites and protocols, + * as well as the information about client/server mode of + * ssl socket, whether it require/want client authentication or not, + * and controls whether new SSL sessions may be established by this + * socket or not. + */ +public class SSLParameters { + + // default source of authentication keys + private static X509KeyManager defaultKeyManager; + // default source of authentication trust decisions + private static X509TrustManager defaultTrustManager; + // default source of random numbers + private static SecureRandom defaultSecureRandom; + // default SSL parameters + private static SSLParameters defaultParameters; + + // client session context contains the set of reusable + // client-side SSL sessions + private SSLSessionContextImpl clientSessionContext; + // server session context contains the set of reusable + // server-side SSL sessions + private SSLSessionContextImpl serverSessionContext; + // source of authentication keys + private X509KeyManager keyManager; + // source of authentication trust decisions + private X509TrustManager trustManager; + // source of random numbers + private SecureRandom secureRandom; + + // cipher suites available for SSL connection + // BEGIN android-removed + // protected CipherSuite[] enabledCipherSuites; + // END android-removed + // BEGIN android-added + private CipherSuite[] enabledCipherSuites; + // END android-added + // string representations of available cipher suites + private String[] enabledCipherSuiteNames = null; + + // protocols available for SSL connection + private String[] enabledProtocols = ProtocolVersion.supportedProtocols; + + // if the peer with this parameters tuned to work in client mode + private boolean client_mode = true; + // if the peer with this parameters tuned to require client authentication + private boolean need_client_auth = false; + // if the peer with this parameters tuned to request client authentication + private boolean want_client_auth = false; + // if the peer with this parameters allowed to cteate new SSL session + private boolean enable_session_creation = true; + + /** + * Creates an instance of SSLParameters. + */ + private SSLParameters() { + // BEGIN android-removed + // this.enabledCipherSuites = CipherSuite.defaultCipherSuites; + // END android-removed + } + + // BEGIN android-added + protected CipherSuite[] getEnabledCipherSuitesMember() { + if (enabledCipherSuites == null) this.enabledCipherSuites = CipherSuite.defaultCipherSuites; + return enabledCipherSuites; + } + + /** + * Holds a pointer to our native SSL context. + */ + private int ssl_ctx = 0; + + /** + * Initializes our native SSL context. + */ + private native int nativeinitsslctx(); + + /** + * Returns the native SSL context, creating it on-the-fly, if necessary. + */ + protected synchronized int getSSLCTX() { + if (ssl_ctx == 0) ssl_ctx = nativeinitsslctx(); + return ssl_ctx; + } + // END android-added + + /** + * Initializes the parameters. Naturally this constructor is used + * in SSLContextImpl.engineInit method which dirrectly passes its + * parameters. In other words this constructor holds all + * the functionality provided by SSLContext.init method. + * See {@link javax.net.ssl.SSLContext#init(KeyManager[],TrustManager[],SecureRandom)} + * for more information + */ + protected SSLParameters(KeyManager[] kms, TrustManager[] tms, + SecureRandom sr, SSLSessionContextImpl clientSessionContext, + SSLSessionContextImpl serverSessionContext) + throws KeyManagementException { + this(); + this.serverSessionContext = serverSessionContext; + this.clientSessionContext = clientSessionContext; + try { + // initialize key manager + boolean initialize_default = false; + // It's not described by the spec of SSLContext what should happen + // if the arrays of length 0 are specified. This implementation + // behave as for null arrays (i.e. use installed security providers) + if ((kms == null) || (kms.length == 0)) { + if (defaultKeyManager == null) { + KeyManagerFactory kmf = KeyManagerFactory.getInstance( + KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(null, null); + kms = kmf.getKeyManagers(); + // tell that we are trying to initialize defaultKeyManager + initialize_default = true; + } else { + keyManager = defaultKeyManager; + } + } + if (keyManager == null) { // was not initialized by default + for (int i = 0; i < kms.length; i++) { + if (kms[i] instanceof X509KeyManager) { + keyManager = (X509KeyManager)kms[i]; + break; + } + } + if (keyManager == null) { + throw new KeyManagementException("No X509KeyManager found"); + } + if (initialize_default) { + // found keyManager is default key manager + defaultKeyManager = keyManager; + } + } + + // initialize trust manager + initialize_default = false; + if ((tms == null) || (tms.length == 0)) { + if (defaultTrustManager == null) { + TrustManagerFactory tmf = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore)null); + tms = tmf.getTrustManagers(); + initialize_default = true; + } else { + trustManager = defaultTrustManager; + } + } + if (trustManager == null) { // was not initialized by default + for (int i = 0; i < tms.length; i++) { + if (tms[i] instanceof X509TrustManager) { + trustManager = (X509TrustManager)tms[i]; + break; + } + } + if (trustManager == null) { + throw new KeyManagementException("No X509TrustManager found"); + } + if (initialize_default) { + // found trustManager is default trust manager + defaultTrustManager = trustManager; + } + } + } catch (NoSuchAlgorithmException e) { + throw new KeyManagementException(e); + } catch (KeyStoreException e) { + throw new KeyManagementException(e); + } catch (UnrecoverableKeyException e) { + throw new KeyManagementException(e); + } + // initialize secure random + // BEGIN android-removed + // if (sr == null) { + // if (defaultSecureRandom == null) { + // defaultSecureRandom = new SecureRandom(); + // } + // secureRandom = defaultSecureRandom; + // } else { + // secureRandom = sr; + // } + // END android-removed + // BEGIN android-added + // We simply use the SecureRandom passed in by the caller. If it's + // null, we don't replace it by a new instance. The native code below + // then directly accesses /dev/urandom. Not the most elegant solution, + // but faster that going through the SecureRandom object. + secureRandom = sr; + // END android-added + } + + protected static SSLParameters getDefault() throws KeyManagementException { + if (defaultParameters == null) { + defaultParameters = new SSLParameters(null, null, null, + new SSLSessionContextImpl(), new SSLSessionContextImpl()); + } + return (SSLParameters) defaultParameters.clone(); + } + + /** + * @return server session context + */ + protected SSLSessionContextImpl getServerSessionContext() { + return serverSessionContext; + } + + /** + * @return client session context + */ + protected SSLSessionContextImpl getClientSessionContext() { + return clientSessionContext; + } + + /** + * @return key manager + */ + protected X509KeyManager getKeyManager() { + return keyManager; + } + + /** + * @return trust manager + */ + protected X509TrustManager getTrustManager() { + return trustManager; + } + + /** + * @return secure random + */ + protected SecureRandom getSecureRandom() { + // BEGIN android-removed + // return secureRandom; + // END android-removed + // BEGIN android-added + if (secureRandom != null) return secureRandom; + if (defaultSecureRandom == null) + { + defaultSecureRandom = new SecureRandom(); + } + secureRandom = defaultSecureRandom; + return secureRandom; + // END android-added + } + + // BEGIN android-added + /** + * @return the secure random member reference, even it is null + */ + protected SecureRandom getSecureRandomMember() { + return secureRandom; + } + // END android-added + + /** + * @return the names of enabled cipher suites + */ + protected String[] getEnabledCipherSuites() { + if (enabledCipherSuiteNames == null) { + // BEGIN android-added + CipherSuite[] enabledCipherSuites = getEnabledCipherSuitesMember(); + // END android-added + enabledCipherSuiteNames = new String[enabledCipherSuites.length]; + for (int i = 0; i< enabledCipherSuites.length; i++) { + enabledCipherSuiteNames[i] = enabledCipherSuites[i].getName(); + } + } + return (String[]) enabledCipherSuiteNames.clone(); + } + + /** + * Sets the set of available cipher suites for use in SSL connection. + * @param suites: String[] + * @return + */ + protected void setEnabledCipherSuites(String[] suites) { + if (suites == null) { + throw new IllegalArgumentException("Provided parameter is null"); + } + CipherSuite[] cipherSuites = new CipherSuite[suites.length]; + for (int i=0; i<suites.length; i++) { + cipherSuites[i] = CipherSuite.getByName(suites[i]); + if (cipherSuites[i] == null || !cipherSuites[i].supported) { + throw new IllegalArgumentException(suites[i] + + " is not supported."); + } + } + enabledCipherSuites = cipherSuites; + enabledCipherSuiteNames = suites; + } + + /** + * @return the set of enabled protocols + */ + protected String[] getEnabledProtocols() { + return (String[]) enabledProtocols.clone(); + } + + /** + * Sets the set of available protocols for use in SSL connection. + * @param suites: String[] + */ + protected void setEnabledProtocols(String[] protocols) { + if (protocols == null) { + throw new IllegalArgumentException("Provided parameter is null"); + } + for (int i=0; i<protocols.length; i++) { + if (!ProtocolVersion.isSupported(protocols[i])) { + throw new IllegalArgumentException("Protocol " + protocols[i] + + " is not supported."); + } + } + enabledProtocols = protocols; + } + + /** + * Tunes the peer holding this parameters to work in client mode. + * @param mode if the peer is configured to work in client mode + */ + protected void setUseClientMode(boolean mode) { + client_mode = mode; + } + + /** + * Returns the value indicating if the parameters configured to work + * in client mode. + */ + protected boolean getUseClientMode() { + return client_mode; + } + + /** + * Tunes the peer holding this parameters to require client authentication + */ + protected void setNeedClientAuth(boolean need) { + need_client_auth = need; + // reset the want_client_auth setting + want_client_auth = false; + } + + /** + * Returns the value indicating if the peer with this parameters tuned + * to require client authentication + */ + protected boolean getNeedClientAuth() { + return need_client_auth; + } + + /** + * Tunes the peer holding this parameters to request client authentication + */ + protected void setWantClientAuth(boolean want) { + want_client_auth = want; + // reset the need_client_auth setting + need_client_auth = false; + } + + /** + * Returns the value indicating if the peer with this parameters + * tuned to request client authentication + * @return + */ + protected boolean getWantClientAuth() { + return want_client_auth; + } + + /** + * Allows/disallows the peer holding this parameters to + * create new SSL session + */ + protected void setEnableSessionCreation(boolean flag) { + enable_session_creation = flag; + } + + /** + * Returns the value indicating if the peer with this parameters + * allowed to cteate new SSL session + */ + protected boolean getEnableSessionCreation() { + return enable_session_creation; + } + + /** + * Returns the clone of this object. + * @return the clone. + */ + protected Object clone() { + SSLParameters parameters = new SSLParameters(); + + parameters.clientSessionContext = clientSessionContext; + parameters.serverSessionContext = serverSessionContext; + parameters.keyManager = keyManager; + parameters.trustManager = trustManager; + parameters.secureRandom = secureRandom; + + parameters.enabledCipherSuites = enabledCipherSuites; + parameters.enabledProtocols = enabledProtocols; + + parameters.client_mode = client_mode; + parameters.need_client_auth = need_client_auth; + parameters.want_client_auth = want_client_auth; + parameters.enable_session_creation = enable_session_creation; + + return parameters; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLRecordProtocol.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLRecordProtocol.java new file mode 100644 index 0000000..4428820 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLRecordProtocol.java @@ -0,0 +1,488 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.AlertException; +import org.apache.harmony.xnet.provider.jsse.SSLSessionImpl; +import org.apache.harmony.xnet.provider.jsse.SSLInputStream; + +import java.io.IOException; +import javax.net.ssl.SSLProtocolException; + +/** + * This class performs functionality dedicated to SSL record layer. + * It unpacks and routes income data to the appropriate + * client protocol (handshake, alert, application data protocols) + * and paketizes outcome data into SSL/TLS records. + * Initially created object has null connection state and does not + * perform any cryptography computations over the income/outcome data. + * After handshake protocol agreed upon security parameters they are placed + * into SSLSessionImpl object and available for record protocol as + * pending session. The order of setting up of the pending session + * as an active session differs for client and server modes. + * So for client mode the parameters are provided by handshake protocol + * during retrieving of change_cipher_spec message to be sent (by calling of + * getChangeCipherSpecMesage method). + * For server side mode record protocol retrieves the parameters from + * handshake protocol after receiving of client's change_cipher_spec message. + * After the pending session has been setted up as a curent session, + * new connectin state object is created and used for encryption/decryption + * of the messages. + * Among with base functionality this class provides the information about + * constrains on the data length, and information about correspondance + * of plain and encrypted data lengths. + * For more information on TLS v1 see http://www.ietf.org/rfc/rfc2246.txt, + * on SSL v3 see http://wp.netscape.com/eng/ssl3, + * on SSL v2 see http://wp.netscape.com/eng/security/SSL_2.html. + */ +public class SSLRecordProtocol { + + /** + * Maximum length of allowed plain data fragment + * as specified by TLS specification. + */ + protected static int MAX_DATA_LENGTH = 16384; // 2^14 + /** + * Maximum length of allowed compressed data fragment + * as specified by TLS specification. + */ + protected static int MAX_COMPRESSED_DATA_LENGTH + = MAX_DATA_LENGTH + 1024; + /** + * Maximum length of allowed ciphered data fragment + * as specified by TLS specification. + */ + protected static int MAX_CIPHERED_DATA_LENGTH + = MAX_COMPRESSED_DATA_LENGTH + 1024; + /** + * Maximum length of ssl record. It is counted as: + * type(1) + version(2) + length(2) + MAX_CIPHERED_DATA_LENGTH + */ + protected static int MAX_SSL_PACKET_SIZE + = MAX_CIPHERED_DATA_LENGTH + 5; + // the SSL session used for connection + private SSLSessionImpl session; + // protocol version of the connection + private byte[] version; + // input stream of record protocol + private SSLInputStream in; + // handshake protocol object to which handshaking data will be transmitted + private HandshakeProtocol handshakeProtocol; + // alert protocol to indicate alerts occured/received + private AlertProtocol alertProtocol; + // application data object to which application data will be transmitted + private org.apache.harmony.xnet.provider.jsse.Appendable appData; + // connection state holding object + private ConnectionState + activeReadState, activeWriteState, pendingConnectionState; + + // logger + private Logger.Stream logger = Logger.getStream("record"); + + // flag indicating if session object has been changed after + // handshake phase (to distinguish session pending state) + private boolean sessionWasChanged = false; + + // change cipher spec message content + private static final byte[] change_cipher_spec_byte = new byte[] {1}; + + /** + * Creates an instance of record protocol and tunes + * up the client protocols to use ut. + * @param handshakeProtocol: HandshakeProtocol + * @param alertProtocol: AlertProtocol + * @param in: SSLInputStream + * @param appData: Appendable + */ + protected SSLRecordProtocol(HandshakeProtocol handshakeProtocol, + AlertProtocol alertProtocol, + SSLInputStream in, + Appendable appData) { + this.handshakeProtocol = handshakeProtocol; + this.handshakeProtocol.setRecordProtocol(this); + this.alertProtocol = alertProtocol; + this.alertProtocol.setRecordProtocol(this); + this.in = in; + this.appData = appData; + } + + /** + * Returns the session obtained during the handshake negotiation. + * If the handshake process was not compleated, method returns null. + * @return the session in effect. + */ + protected SSLSessionImpl getSession() { + return session; + } + + /** + * Returns the minimum possible length of the SSL record. + * @return + */ + protected int getMinRecordSize() { + return (activeReadState == null) + ? 6 // type + version + length + 1 byte of data + : 5 + activeReadState.getMinFragmentSize(); + } + + /** + * Returns the record length for the specified incoming data length. + * If actual resulting record length is greater than + * MAX_CIPHERED_DATA_LENGTH, MAX_CIPHERED_DATA_LENGTH is returned. + */ + protected int getRecordSize(int data_size) { + if (activeWriteState == null) { + return 5+data_size; // type + version + length + data_size + } else { + int res = 5 + activeWriteState.getFragmentSize(data_size); + return (res > MAX_CIPHERED_DATA_LENGTH) + ? MAX_CIPHERED_DATA_LENGTH // so the source data should be + // splitted into several packets + : res; + } + } + + /** + * Returns the upper bound of length of data containing in the record with + * specified length. + * If the provided record_size is greater or equal to + * MAX_CIPHERED_DATA_LENGTH the returned value will be + * MAX_DATA_LENGTH + * counted as for data with + * MAX_CIPHERED_DATA_LENGTH length. + */ + protected int getDataSize(int record_size) { + record_size -= 5; // - (type + version + length + data_size) + if (record_size > MAX_CIPHERED_DATA_LENGTH) { + // the data of such size consists of the several packets + return MAX_DATA_LENGTH; + } + if (activeReadState == null) { + return record_size; + } else { + return activeReadState.getContentSize(record_size); + } + } + + /** + * Depending on the Connection State (Session) encrypts and compress + * the provided data, and packs it into TLSCiphertext structute. + * @param content_type: int + * @param fragment: byte[] + * @return ssl packet created over the current connection state + */ + protected byte[] wrap(byte content_type, DataStream dataStream) { + byte[] fragment = dataStream.getData(MAX_DATA_LENGTH); + return wrap(content_type, fragment, 0, fragment.length); + } + + /** + * Depending on the Connection State (Session) encrypts and compress + * the provided data, and packs it into TLSCiphertext structute. + * @param content_type: int + * @param fragment: byte[] + * @return ssl packet created over the current connection state + */ + protected byte[] wrap(byte content_type, + byte[] fragment, int offset, int len) { + if (logger != null) { + logger.println("SSLRecordProtocol.wrap: TLSPlaintext.fragment[" + +len+"]:"); + logger.print(fragment, offset, len); + } + if (len > MAX_DATA_LENGTH) { + throw new AlertException( + AlertProtocol.INTERNAL_ERROR, + new SSLProtocolException( + "The provided chunk of data is too big: " + len + + " > MAX_DATA_LENGTH == "+MAX_DATA_LENGTH)); + } + byte[] ciphered_fragment = fragment; + if (activeWriteState != null) { + ciphered_fragment = + activeWriteState.encrypt(content_type, fragment, offset, len); + if (ciphered_fragment.length > MAX_CIPHERED_DATA_LENGTH) { + throw new AlertException( + AlertProtocol.INTERNAL_ERROR, + new SSLProtocolException( + "The ciphered data increased more than on 1024 bytes")); + } + if (logger != null) { + logger.println("SSLRecordProtocol.wrap: TLSCiphertext.fragment[" + +ciphered_fragment.length+"]:"); + logger.print(ciphered_fragment); + } + } + return packetize(content_type, version, ciphered_fragment); + } + + private byte[] packetize(byte type, byte[] version, byte[] fragment) { + byte[] buff = new byte[5+fragment.length]; + buff[0] = type; + if (version != null) { + buff[1] = version[0]; + buff[2] = version[1]; + } else { + buff[1] = 3; + buff[2] = 1; + } + buff[3] = (byte) ((0x00FF00 & fragment.length) >> 8); + buff[4] = (byte) (0x0000FF & fragment.length); + System.arraycopy(fragment, 0, buff, 5, fragment.length); + return buff; + } + + /** + * Set the ssl session to be used after sending the changeCipherSpec message + * @param session: SSLSessionImpl + */ + private void setSession(SSLSessionImpl session) { + if (!sessionWasChanged) { + // session was not changed for current handshake process + if (logger != null) { + logger.println("SSLRecordProtocol.setSession: Set pending session"); + logger.println(" cipher name: " + session.getCipherSuite()); + } + this.session = session; + // create new connection state + pendingConnectionState = ((version == null) || (version[1] == 1)) + ? (ConnectionState) new ConnectionStateTLS(getSession()) + : (ConnectionState) new ConnectionStateSSLv3(getSession()); + sessionWasChanged = true; + } else { + // wait for rehandshaking's session + sessionWasChanged = false; + } + } + + /** + * Returns the change cipher spec message to be sent to another peer. + * The pending connection state will be built on the base of provided + * session object + * The calling of this method triggers pending write connection state to + * be active. + * @return ssl record containing the "change cipher spec" message. + */ + protected byte[] getChangeCipherSpecMesage(SSLSessionImpl session) { + // make change_cipher_spec_message: + byte[] change_cipher_spec_message; + if (activeWriteState == null) { + change_cipher_spec_message = new byte[] { + ContentType.CHANGE_CIPHER_SPEC, version[0], + version[1], 0, 1, 1 + }; + } else { + change_cipher_spec_message = + packetize(ContentType.CHANGE_CIPHER_SPEC, version, + activeWriteState.encrypt(ContentType.CHANGE_CIPHER_SPEC, + change_cipher_spec_byte, 0, 1)); + } + setSession(session); + activeWriteState = pendingConnectionState; + if (logger != null) { + logger.println("SSLRecordProtocol.getChangeCipherSpecMesage"); + logger.println("activeWriteState = pendingConnectionState"); + logger.print(change_cipher_spec_message); + } + return change_cipher_spec_message; + } + + /** + * Retrieves the fragment field of TLSCiphertext, and than + * depending on the established Connection State + * decrypts and decompresses it. The following structure is expected + * on the input at the moment of the call: + * + * struct { + * ContentType type; + * ProtocolVersion version; + * uint16 length; + * select (CipherSpec.cipher_type) { + * case stream: GenericStreamCipher; + * case block: GenericBlockCipher; + * } fragment; + * } TLSCiphertext; + * + * (as specified by RFC 2246, TLS v1 Protocol specification) + * + * In addition this method can recognize SSLv2 hello message which + * are often used to establish the SSL/TLS session. + * + * @throws IOException if some io errors have been occured + * @throws EndOfSourceException if underlying input stream + * has ran out of data. + * @throws EndOfBufferException if there was not enought data + * to build complete ssl packet. + * @return the type of unwrapped message. + */ + protected int unwrap() throws IOException { + if (logger != null) { + logger.println("SSLRecordProtocol.unwrap: BEGIN ["); + } + int type = in.readUint8(); + if ((type < ContentType.CHANGE_CIPHER_SPEC) + || (type > ContentType.APPLICATION_DATA)) { + if (logger != null) { + logger.println("Non v3.1 message type:" + type); + } + if (type >= 0x80) { + // it is probably SSL v2 client_hello message + // (see SSL v2 spec at: + // http://wp.netscape.com/eng/security/SSL_2.html) + int length = (type & 0x7f) << 8 | in.read(); + byte[] fragment = in.read(length); + handshakeProtocol.unwrapSSLv2(fragment); + if (logger != null) { + logger.println( + "SSLRecordProtocol:unwrap ] END, SSLv2 type"); + } + return ContentType.HANDSHAKE; + } + throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE, + new SSLProtocolException( + "Unexpected message type has been received: "+type)); + } + if (logger != null) { + logger.println("Got the message of type: " + type); + } + if (version != null) { + if ((in.read() != version[0]) + || (in.read() != version[1])) { + throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE, + new SSLProtocolException( + "Unexpected message type has been received: " + + type)); + } + } else { + in.skip((long) 2); // just skip the version number + } + int length = in.readUint16(); + if (logger != null) { + logger.println("TLSCiphertext.fragment["+length+"]: ..."); + } + if (length > MAX_CIPHERED_DATA_LENGTH) { + throw new AlertException(AlertProtocol.RECORD_OVERFLOW, + new SSLProtocolException( + "Received message is too big.")); + } + byte[] fragment = in.read(length); + if (logger != null) { + logger.print(fragment); + } + if (activeReadState != null) { + fragment = activeReadState.decrypt((byte) type, fragment); + if (logger != null) { + logger.println("TLSPlaintext.fragment:"); + logger.print(fragment); + } + } + if (fragment.length > MAX_DATA_LENGTH) { + throw new AlertException(AlertProtocol.DECOMPRESSION_FAILURE, + new SSLProtocolException( + "Decompressed plain data is too big.")); + } + switch (type) { + case ContentType.CHANGE_CIPHER_SPEC: + // notify handshake protocol: + handshakeProtocol.receiveChangeCipherSpec(); + setSession(handshakeProtocol.getSession()); + // change cipher spec message has been received, so: + if (logger != null) { + logger.println("activeReadState = pendingConnectionState"); + } + activeReadState = pendingConnectionState; + break; + case ContentType.ALERT: + alert(fragment[0], fragment[1]); + break; + case ContentType.HANDSHAKE: + handshakeProtocol.unwrap(fragment); + break; + case ContentType.APPLICATION_DATA: + if (logger != null) { + logger.println( + "TLSCiphertext.unwrap: APP DATA["+length+"]:"); + logger.println(new String(fragment)); + } + appData.append(fragment); + break; + default: + throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE, + new SSLProtocolException( + "Unexpected message type has been received: " + + type)); + } + if (logger != null) { + logger.println("SSLRecordProtocol:unwrap ] END, type: " + type); + } + return type; + } + + /** + * Passes the alert information to the alert protocol. + * @param level: byte + * @param description: byte + */ + protected void alert(byte level, byte description) { + if (logger != null) { + logger.println("SSLRecordProtocol.allert: "+level+" "+description); + } + alertProtocol.alert(level, description); + } + + /** + * Sets up the SSL version used in this connection. + * This method is calling from the hanshake protocol after + * it becomes known witch protocol version will be used. + * @param ver: byte[] + * @return + */ + protected void setVersion(byte[] ver) { + this.version = ver; + } + + /** + * Shutdownes the protocol. It will be impossiblke to use the instance + * after the calling of this method. + */ + protected void shutdown() { + session = null; + version = null; + in = null; + handshakeProtocol = null; + alertProtocol = null; + appData = null; + if (pendingConnectionState != null) { + pendingConnectionState.shutdown(); + } + pendingConnectionState = null; + if (activeReadState != null) { + activeReadState.shutdown(); + } + activeReadState = null; + if (activeReadState != null) { + activeReadState.shutdown(); + } + activeWriteState = null; + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionContextImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionContextImpl.java new file mode 100644 index 0000000..7ae4c73 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionContextImpl.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ +package org.apache.harmony.xnet.provider.jsse; + +//BEGIN android-changed +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionContext; + +/** + * SSLSessionContext implementation + * + * @see javax.net.ssl.SSLSessionContext + */ +public class SSLSessionContextImpl implements SSLSessionContext { + + private int cacheSize = 20; + private long timeout = 0; + private final LinkedHashMap<byte[], SSLSession> sessions = + new LinkedHashMap<byte[],SSLSession>(cacheSize, 0.75f, true) { + public boolean removeEldestEntry(Map.Entry eldest) { + return cacheSize > 0 && this.size() > cacheSize; + } + }; + private volatile LinkedHashMap<byte[], SSLSession> clone = new LinkedHashMap<byte[], SSLSession>(); + + /** + * @see javax.net.ssl.SSLSessionContext#getIds() + */ + public Enumeration<byte[]> getIds() { + return new Enumeration<byte[]>() { + Iterator<byte[]> iterator = clone.keySet().iterator(); + public boolean hasMoreElements() { + return iterator.hasNext(); + } + public byte[] nextElement() { + return iterator.next(); + } + }; + } + + /** + * @see javax.net.ssl.SSLSessionContext#getSession(byte[] sessionId) + */ + public SSLSession getSession(byte[] sessionId) { + synchronized (sessions) { + return (SSLSession) sessions.get(sessionId); + } + } + + /** + * @see javax.net.ssl.SSLSessionContext#getSessionCacheSize() + */ + public int getSessionCacheSize() { + return cacheSize; + } + + /** + * @see javax.net.ssl.SSLSessionContext#getSessionTimeout() + */ + public int getSessionTimeout() { + return (int) (timeout/1000); + } + + /** + * @see javax.net.ssl.SSLSessionContext#setSessionCacheSize(int size) + */ + public void setSessionCacheSize(int size) throws IllegalArgumentException { + if (size < 0) { + throw new IllegalArgumentException("size < 0"); + } + synchronized (sessions) { + cacheSize = size; + Set<byte[]> set = sessions.keySet(); + if (cacheSize > 0 && cacheSize < set.size()) { + // Resize the cache to the maximum + Iterator<byte[]> iterator = set.iterator(); + for (int i = 0; iterator.hasNext(); i++) { + iterator.next(); + if (i >= cacheSize) { + iterator.remove(); + } + } + } + clone = (LinkedHashMap<byte[], SSLSession>) sessions.clone(); + } + } + + /** + * @see javax.net.ssl.SSLSessionContext#setSessionTimeout(int seconds) + */ + public void setSessionTimeout(int seconds) throws IllegalArgumentException { + if (seconds < 0) { + throw new IllegalArgumentException("seconds < 0"); + } + + synchronized (sessions) { + timeout = seconds*1000; + // Check timeouts and remove expired sessions + SSLSession ses; + for (Iterator<byte[]> iterator = sessions.keySet().iterator(); iterator.hasNext();) { + ses = (SSLSession)(sessions.get(iterator.next())); + if (!ses.isValid()) { + iterator.remove(); + } + } + clone = (LinkedHashMap<byte[], SSLSession>) sessions.clone(); + } + } + + /** + * Adds session to the session cache + * @param ses + */ + void putSession(SSLSession ses) { + synchronized (sessions) { + sessions.put(ses.getId(), ses); + clone = (LinkedHashMap<byte[], SSLSession>) sessions.clone(); + } + } +} +// END android-changed
\ No newline at end of file diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java new file mode 100644 index 0000000..df46094 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java @@ -0,0 +1,410 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.Principal; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Iterator; +import java.util.Vector; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLPermission; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSessionBindingEvent; +import javax.net.ssl.SSLSessionBindingListener; +import javax.net.ssl.SSLSessionContext; + +import org.apache.harmony.luni.util.TwoKeyHashMap; +import org.apache.harmony.xnet.provider.jsse.SSLSessionContextImpl; + +/** + * + * SSLSession implementation + * + * @see javax.net.ssl.SSLSession + */ +public class SSLSessionImpl implements SSLSession { + + /** + * Session object reporting an invalid cipher suite of + * "SSL_NULL_WITH_NULL_NULL" + */ + public static final SSLSessionImpl NULL_SESSION = new SSLSessionImpl(null); + + private long creationTime; + private boolean isValid = true; + private TwoKeyHashMap values = new TwoKeyHashMap(); + + /** + * ID of the session + */ + byte[] id; + + /** + * Last time the session was accessed + */ + long lastAccessedTime; + + /** + * Protocol used in the session + */ + ProtocolVersion protocol; + + /** + * CipherSuite used in the session + */ + CipherSuite cipherSuite; + + /** + * Context of the session + */ + SSLSessionContextImpl context; + + + /** + * certificates were sent to the peer + */ + X509Certificate[] localCertificates; + + /** + * Peer certificates + */ + X509Certificate[] peerCertificates; + + /** + * Peer host name + */ + String peerHost; + + /** + * Peer port number + */ + int peerPort = -1; + + /** + * Master secret + */ + byte[] master_secret; + + + /** + * clientRandom + */ + byte[] clientRandom; + + /** + * serverRandom + */ + byte[] serverRandom; + + /** + * True if this entity is considered the server + */ + boolean isServer = false; + + /** + * Creates SSLSession implementation + * @param cipher_suite + * @param sr + */ + public SSLSessionImpl(CipherSuite cipher_suite, SecureRandom sr) { + creationTime = System.currentTimeMillis(); + lastAccessedTime = creationTime; + if (cipher_suite == null) { + this.cipherSuite = CipherSuite.TLS_NULL_WITH_NULL_NULL; + id = new byte[0]; + isServer = false; + } else { + this.cipherSuite = cipher_suite; + id = new byte[32]; + sr.nextBytes(id); + long time = new java.util.Date().getTime() / 1000; + id[28] = (byte) ((time & 0xFF000000) >>> 24); + id[29] = (byte) ((time & 0xFF0000) >>> 16); + id[30] = (byte) ((time & 0xFF00) >>> 8); + id[31] = (byte) (time & 0xFF); + isServer = true; + } + + } + + /** + * Creates SSLSession implementation + * @param sr + */ + public SSLSessionImpl(SecureRandom sr) { + this(null, sr); + } + + private SSLSessionImpl() { + } + + /** + * @see javax.net.ssl.SSLSession#getApplicationBufferSize() + */ + public int getApplicationBufferSize() { + return SSLRecordProtocol.MAX_DATA_LENGTH; + } + + /** + * @see javax.net.ssl.SSLSession#getCipherSuite() + */ + public String getCipherSuite() { + return cipherSuite.getName(); + } + + /** + * @see javax.net.ssl.SSLSession#getCreationTime() + */ + public long getCreationTime() { + return creationTime; + } + + /** + * @see javax.net.ssl.SSLSession#getId() + */ + public byte[] getId() { + return id; + } + + /** + * @see javax.net.ssl.SSLSession#getLastAccessedTime() + */ + public long getLastAccessedTime() { + return lastAccessedTime; + } + + /** + * @see javax.net.ssl.SSLSession#getLocalCertificates() + */ + public Certificate[] getLocalCertificates() { + return localCertificates; + } + + /** + * @see javax.net.ssl.SSLSession#getLocalPrincipal() + */ + public Principal getLocalPrincipal() { + if (localCertificates != null && localCertificates.length > 0) { + return localCertificates[0].getSubjectX500Principal(); + } else { + return null; + } + } + + /** + * @see javax.net.ssl.SSLSession#getPacketBufferSize() + */ + public int getPacketBufferSize() { + return SSLRecordProtocol.MAX_SSL_PACKET_SIZE; + } + + /** + * @see javax.net.ssl.SSLSession#getPeerCertificateChain() + */ + public javax.security.cert.X509Certificate[] getPeerCertificateChain() + throws SSLPeerUnverifiedException { + if (peerCertificates == null) { + throw new SSLPeerUnverifiedException("No peer certificate"); + } + javax.security.cert.X509Certificate[] certs = new javax.security.cert.X509Certificate[peerCertificates.length]; + for (int i = 0; i < certs.length; i++) { + try { + certs[i] = javax.security.cert.X509Certificate + .getInstance(peerCertificates[i].getEncoded()); + } catch (javax.security.cert.CertificateException e) { + } catch (CertificateEncodingException e) { + } + } + return certs; + } + + /** + * @see javax.net.ssl.SSLSession#getPeerCertificates() + */ + public Certificate[] getPeerCertificates() + throws SSLPeerUnverifiedException { + if (peerCertificates == null) { + throw new SSLPeerUnverifiedException("No peer certificate"); + } + return peerCertificates; + } + + /** + * @see javax.net.ssl.SSLSession#getPeerHost() + */ + public String getPeerHost() { + return peerHost; + } + + /** + * @see javax.net.ssl.SSLSession#getPeerPort() + */ + public int getPeerPort() { + return peerPort; + } + + /** + * @see javax.net.ssl.SSLSession#getPeerPrincipal() + */ + public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + if (peerCertificates == null) { + throw new SSLPeerUnverifiedException("No peer certificate"); + } + return peerCertificates[0].getSubjectX500Principal(); + } + + /** + * @see javax.net.ssl.SSLSession#getProtocol() + */ + public String getProtocol() { + return protocol.name; + } + + /** + * @see javax.net.ssl.SSLSession#getSessionContext() + */ + public SSLSessionContext getSessionContext() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new SSLPermission("getSSLSessionContext")); + } + return context; + } + + /** + * @see javax.net.ssl.SSLSession#getValue(String name) + */ + public Object getValue(String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter is null"); + } + return values.get(name, AccessController.getContext()); + } + + /** + * @see javax.net.ssl.SSLSession#getValueNames() + */ + public String[] getValueNames() { + Vector v = new Vector(); + AccessControlContext current = AccessController.getContext(); + AccessControlContext cont; + for (Iterator it = values.entrySet().iterator(); it.hasNext();) { + TwoKeyHashMap.Entry entry = (TwoKeyHashMap.Entry) it.next(); + cont = (AccessControlContext) entry.getKey2(); + if ((current == null && cont == null) + || (current != null && current.equals(cont))) { + v.add(entry.getKey1()); + } + } + return (String[]) v.toArray(new String[0]); + } + + /** + * @see javax.net.ssl.SSLSession#invalidate() + */ + public void invalidate() { + isValid = false; + } + + /** + * @see javax.net.ssl.SSLSession#isValid() + */ + public boolean isValid() { + if (isValid + && context != null + && context.getSessionTimeout() != 0 + && lastAccessedTime + context.getSessionTimeout() > System + .currentTimeMillis()) { + isValid = false; + } + return isValid; + } + + /** + * @see javax.net.ssl.SSLSession#putValue(String name, Object value) + */ + public void putValue(String name, Object value) { + if (name == null || value == null) { + throw new IllegalArgumentException("Parameter is null"); + } + Object old = values.put(name, AccessController.getContext(), value); + if (value instanceof SSLSessionBindingListener) { + ((SSLSessionBindingListener) value) + .valueBound(new SSLSessionBindingEvent(this, name)); + } + if (old != null && old instanceof SSLSessionBindingListener) { + ((SSLSessionBindingListener) old) + .valueUnbound(new SSLSessionBindingEvent(this, name)); + } + + } + + /** + * @see javax.net.ssl.SSLSession#removeValue(String name) + */ + public void removeValue(String name) { + if (name == null) { + throw new IllegalArgumentException("Parameter is null"); + } + values.remove(name, AccessController.getContext()); + + } + + public Object clone() { + SSLSessionImpl ses = new SSLSessionImpl(); + ses.id = this.id; + ses.creationTime = this.creationTime; + ses.lastAccessedTime = this.lastAccessedTime; + ses.isValid = this.isValid; + ses.cipherSuite = this.cipherSuite; + ses.localCertificates = this.localCertificates; + ses.peerCertificates = this.peerCertificates; + ses.master_secret = this.master_secret; + ses.clientRandom = this.clientRandom; + ses.serverRandom = this.serverRandom; + ses.peerHost = this.peerHost; + ses.peerPort = this.peerPort; + ses.isServer = this.isServer; + ses.context = this.context; + ses.protocol = this.protocol; + ses.values = this.values; + return ses; + } + + + /** + * Sets the address of the peer + * @param peerHost + * @param peerPort + */ + void setPeer(String peerHost, int peerPort) { + this.peerHost = peerHost; + this.peerPort = peerPort; + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLStreamedInput.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLStreamedInput.java new file mode 100644 index 0000000..efabef8 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLStreamedInput.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Alexander Y. Kleymenov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.io.IOException; +import java.io.InputStream; + +/** + * This class acts like a filtered input stream: it takes + * the bytes from another InputStream. + */ +public class SSLStreamedInput extends SSLInputStream { + + private InputStream in; + + public SSLStreamedInput(InputStream in) { + this.in = in; + } + + public int available() throws IOException { + return in.available(); + } + + /** + * Read an opaque value from the stream. + * @return the value read from the underlying stream. + * @throws IOException if the data could not be read from + * the underlying stream + * @throws org.apache.harmony.xnet.provider.jsse.EndOfSourceException if the end of the underlying + * stream has been reached. + */ + public int read() throws IOException { + int res = in.read(); + if (res < 0) { + throw new EndOfSourceException(); + } + return res; + } +} + diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLv3Constants.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLv3Constants.java new file mode 100644 index 0000000..1a03f7f --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLv3Constants.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +/** + * + * Contains SSL 3.0 constants + * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec.</a> + */ +public class SSLv3Constants { + + /** + * Client is a sender. Used in hash calculating for finished message. + * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.6.9 + * Finished</a> + */ + static final byte[] client = new byte[] { 0x43, 0x4C, 0x4E, 0x54 }; + + /** + * Server is a sender. Used in hash calculating for finished message. + * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.6.9 + * Finished</a> + */ + static final byte[] server = new byte[] { 0x53, 0x52, 0x56, 0x52 }; + + /** + * pad_1 for MD5 + * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.2.3.1 + * Null or standard stream cipher</a> + */ + static final byte[] MD5pad1 = new byte[] { 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36 }; + + /** + * pad_1 for SHA + * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.2.3.1 + * Null or standard stream cipher</a> + */ + static final byte[] SHApad1 = new byte[] { 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36 }; + + /** + * pad_2 for MD5 + * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.2.3.1 + * Null or standard stream cipher</a> + */ + static final byte[] MD5pad2 = new byte[] { 0x5C, 0x5C, 0x5C, 0x5C, + 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, + 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, + 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, + 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C }; + + /** + * pad_2 for SHA + * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.2.3.1 + * Null or standard stream cipher</a> + */ + static final byte[] SHApad2 = new byte[] { 0x5C, 0x5C, 0x5C, 0x5C, + 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, + 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, + 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, + 0x5C, 0x5C, 0x5C }; +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java new file mode 100644 index 0000000..84a663c --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHandshakeImpl.java @@ -0,0 +1,738 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.SSLv3Constants; +import org.apache.harmony.xnet.provider.jsse.SSLSessionImpl; +import org.apache.harmony.xnet.provider.jsse.ProtocolVersion; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.AccessController; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PrivilegedExceptionAction; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPublicKey; + +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; +import javax.crypto.interfaces.DHPublicKey; +import javax.crypto.spec.DHParameterSpec; +import javax.crypto.spec.DHPublicKeySpec; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509KeyManager; +import javax.net.ssl.X509TrustManager; + +/** + * Server side handshake protocol implementation. + * Handshake protocol operates on top of the Record Protocol. + * It responsible for negotiating a session. + * + * The implementation proceses inbound client handshake messages, + * creates and sends respond messages. Outbound messages are supplied + * to Record Protocol. Detected errors are reported to the Alert protocol. + * + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4. + * Handshake protocol.</a> + * + */ +public class ServerHandshakeImpl extends HandshakeProtocol { + + // private key used in key exchange + private PrivateKey privKey; + + /** + * Creates Server Handshake Implementation + * + * @param owner + */ + public ServerHandshakeImpl(Object owner) { + super(owner); + status = NEED_UNWRAP; + } + + /** + * Start session negotiation + * @param session + */ + public void start() { + if (session == null) { // initial handshake + status = NEED_UNWRAP; + return; // wait client hello + } + if (clientHello != null && this.status != FINISHED) { + // current negotiation has not completed + return; // ignore + } + + // renegotiation + sendHelloRequest(); + status = NEED_UNWRAP; + } + + /** + * Proceses inbound handshake messages + * @param bytes + */ + public void unwrap(byte[] bytes) { + + io_stream.append(bytes); + while (io_stream.available() > 0) { + int handshakeType; + int length; + io_stream.mark(); + try { + handshakeType = io_stream.read(); + length = io_stream.readUint24(); + if (io_stream.available() < length) { + io_stream.reset(); + return; + } + + switch (handshakeType) { + case 1: // CLIENT_HELLO + if (clientHello != null && this.status != FINISHED) { + // Client hello has been received during handshake + unexpectedMessage(); + return; + } + // if protocol planed to send Hello Request message + // - cancel this demand. + needSendHelloRequest = false; + clientHello = new ClientHello(io_stream, length); + if (nonBlocking) { + delegatedTasks.add(new DelegatedTask( + new PrivilegedExceptionAction(){ + public Object run() throws Exception { + processClientHello(); + return null; + } + }, + this, + AccessController.getContext())); + return; + } + processClientHello(); + break; + + case 11: // CLIENT CERTIFICATE + if (isResuming || certificateRequest == null + || serverHelloDone == null || clientCert != null) { + unexpectedMessage(); + return; + } + clientCert = new CertificateMessage(io_stream, length); + if (clientCert.certs.length == 0) { + if (parameters.getNeedClientAuth()) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "HANDSHAKE FAILURE: no client certificate recived"); + } + } else { + String authType = clientCert.certs[0].getPublicKey() + .getAlgorithm(); + try { + parameters.getTrustManager().checkClientTrusted( + clientCert.certs, authType); + } catch (CertificateException e) { + fatalAlert(AlertProtocol.BAD_CERTIFICATE, + "Untrusted Client Certificate ", e); + } + session.peerCertificates = clientCert.certs; + } + break; + + case 15: // CERTIFICATE_VERIFY + if (isResuming + || clientKeyExchange == null + || clientCert == null + || clientKeyExchange.isEmpty() //client certificate + // contains fixed DH + // parameters + || certificateVerify != null + || changeCipherSpecReceived) { + unexpectedMessage(); + return; + } + certificateVerify = new CertificateVerify(io_stream, length); + + DigitalSignature ds = new DigitalSignature(session.cipherSuite.keyExchange); + ds.init(serverCert.certs[0]); + byte[] md5_hash = null; + byte[] sha_hash = null; + + if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_RSA_EXPORT) { + md5_hash = io_stream.getDigestMD5withoutLast(); + sha_hash = io_stream.getDigestSHAwithoutLast(); + } else if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DHE_DSS_EXPORT) { + sha_hash = io_stream.getDigestSHAwithoutLast(); + } else if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) { + } + ds.setMD5(md5_hash); + ds.setSHA(sha_hash); + if (!ds.verifySignature(certificateVerify.signedHash)) { + fatalAlert(AlertProtocol.DECRYPT_ERROR, + "DECRYPT ERROR: CERTIFICATE_VERIFY incorrect signature"); + } + break; + case 16: // CLIENT_KEY_EXCHANGE + if (isResuming + || serverHelloDone == null + || clientKeyExchange != null + || (clientCert == null && parameters + .getNeedClientAuth())) { + unexpectedMessage(); + return; + } + if (session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA + || session.cipherSuite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT) { + clientKeyExchange = new ClientKeyExchange(io_stream, + length, serverHello.server_version[1] == 1, + true); + Cipher c = null; + try { + c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + c.init(Cipher.DECRYPT_MODE, privKey); + preMasterSecret = c + .doFinal(clientKeyExchange.exchange_keys); + // check preMasterSecret: + if (preMasterSecret.length != 48 + || preMasterSecret[0] != clientHello.client_version[0] + || preMasterSecret[1] != clientHello.client_version[1]) { + // incorrect preMasterSecret + // prevent an attack (see TLS 1.0 spec., 7.4.7.1.) + preMasterSecret = new byte[48]; + parameters.getSecureRandom().nextBytes( + preMasterSecret); + } + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, + "INTERNAL ERROR", e); + } + } else { // diffie hellman key exchange + clientKeyExchange = new ClientKeyExchange(io_stream, + length, serverHello.server_version[1] == 1, + false); + if (clientKeyExchange.isEmpty()) { + // TODO check that client cert. DH params + // matched server cert. DH params + + // client cert. contains fixed DH parameters + preMasterSecret = ((DHPublicKey) clientCert.certs[0] + .getPublicKey()).getY().toByteArray(); + } else { + PublicKey clientPublic; + KeyAgreement agreement; + try { + KeyFactory kf = null; + try { + kf = KeyFactory.getInstance("DH"); + } catch (NoSuchAlgorithmException ee) { + kf = KeyFactory + .getInstance("DiffieHellman"); + } + try { + agreement = KeyAgreement.getInstance("DH"); + } catch (NoSuchAlgorithmException ee) { + agreement = KeyAgreement + .getInstance("DiffieHellman"); + } + clientPublic = kf + .generatePublic(new DHPublicKeySpec( + new BigInteger( + 1, + clientKeyExchange.exchange_keys), + serverKeyExchange.par1, + serverKeyExchange.par2)); + agreement.init(privKey); + agreement.doPhase(clientPublic, true); + preMasterSecret = agreement.generateSecret(); + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, + "INTERNAL ERROR", e); + return; + } + } + } + + computerMasterSecret(); + break; + + case 20: // FINISHED + if (!isResuming && !changeCipherSpecReceived) { + unexpectedMessage(); + return; + } + + clientFinished = new Finished(io_stream, length); + verifyFinished(clientFinished.getData()); + // BEGIN android-added + session.context = parameters.getClientSessionContext(); + // END android-added + parameters.getServerSessionContext().putSession(session); + if (!isResuming) { + sendChangeCipherSpec(); + } else { + session.lastAccessedTime = System.currentTimeMillis(); + status = FINISHED; + } + break; + default: + unexpectedMessage(); + return; + } + } catch (IOException e) { + // io stream dosn't contain complete handshake message + io_stream.reset(); + return; + } + } + } + /** + * Processes SSLv2 Hello message + * @ see TLS 1.0 spec., E.1. Version 2 client hello + * @param bytes + */ + public void unwrapSSLv2(byte[] bytes) { + try { + io_stream.append(bytes); + io_stream.mark(); + try { + clientHello = new ClientHello(io_stream); + } catch (IOException e) { + io_stream.reset(); + return; + } + if (nonBlocking) { + delegatedTasks.add(new DelegatedTask( + new PrivilegedExceptionAction(){ + public Object run() throws Exception { + processClientHello(); + return null; + } + }, + this, + AccessController.getContext())); + return; + } + processClientHello(); + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e); + } + } + + /** + * + * Processes Client Hello message. + * Server responds to client hello message with server hello + * and (if necessary) server certificate, server key exchange, + * certificate request, and server hello done messages. + */ + void processClientHello() { + CipherSuite cipher_suite; + + // check that clientHello contains CompressionMethod.null + checkCompression: { + for (int i = 0; i < clientHello.compression_methods.length; i++) { + if (clientHello.compression_methods[i] == 0) { + break checkCompression; + } + } + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "HANDSHAKE FAILURE. Incorrect client hello message"); + } + + if (!ProtocolVersion.isSupported(clientHello.client_version)) { + fatalAlert(AlertProtocol.PROTOCOL_VERSION, + "PROTOCOL VERSION. Unsupported client version " + + clientHello.client_version[0] + + clientHello.client_version[1]); + } + + isResuming = false; + FIND: if (clientHello.session_id.length != 0) { + // client wishes to reuse session + + SSLSessionImpl sessionToResume; + boolean reuseCurrent = false; + + // reuse current session + if (session != null + && Arrays.equals(session.id, clientHello.session_id)) { + if (session.isValid()) { + isResuming = true; + break FIND; + } + reuseCurrent = true; + } + + // find session in cash + sessionToResume = findSessionToResume(clientHello.session_id); + if (sessionToResume == null || !sessionToResume.isValid()) { + if (!parameters.getEnableSessionCreation()) { + if (reuseCurrent) { + // we can continue current session + sendWarningAlert(AlertProtocol.NO_RENEGOTIATION); + status = NOT_HANDSHAKING; + clearMessages(); + return; + } else { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "SSL Session may not be created"); + } + } + session = null; + } else { + session = (SSLSessionImpl)sessionToResume.clone(); + isResuming = true; + } + } + + if (isResuming) { + cipher_suite = session.cipherSuite; + // clientHello.cipher_suites must include at least cipher_suite from the session + checkCipherSuite: { + for (int i = 0; i < clientHello.cipher_suites.length; i++) { + if (cipher_suite.equals(clientHello.cipher_suites[i])) { + break checkCipherSuite; + } + } + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "HANDSHAKE FAILURE. Incorrect client hello message"); + } + } else { + cipher_suite = selectSuite(clientHello.cipher_suites); + if (cipher_suite == null) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "HANDSHAKE FAILURE. NO COMMON SUITE"); + } + if (!parameters.getEnableSessionCreation()) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, + "SSL Session may not be created"); + } + session = new SSLSessionImpl(cipher_suite, parameters + .getSecureRandom()); + } + + recordProtocol.setVersion(clientHello.client_version); + session.protocol = ProtocolVersion.getByVersion(clientHello.client_version); + session.clientRandom = clientHello.random; + + // create server hello message + serverHello = new ServerHello(parameters.getSecureRandom(), + clientHello.client_version, + session.getId(), cipher_suite, (byte) 0); //CompressionMethod.null + session.serverRandom = serverHello.random; + send(serverHello); + if (isResuming) { + sendChangeCipherSpec(); + return; + } + + // create and send server certificate message if needed + if (!cipher_suite.isAnonymous()) { // need to send server certificate + X509Certificate[] certs = null; + String certType = null; + if (cipher_suite.keyExchange == CipherSuite.KeyExchange_RSA + || cipher_suite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT + || cipher_suite.keyExchange == CipherSuite.KeyExchange_DHE_RSA + || cipher_suite.keyExchange == CipherSuite.KeyExchange_DHE_RSA_EXPORT) { + certType = "RSA"; + } else if (cipher_suite.keyExchange == CipherSuite.KeyExchange_DHE_DSS + || cipher_suite.keyExchange == CipherSuite.KeyExchange_DHE_DSS_EXPORT) { + certType = "DSA"; + } else if (cipher_suite.keyExchange == CipherSuite.KeyExchange_DH_DSS) { + certType = "DH_DSA"; + } else if (cipher_suite.keyExchange == CipherSuite.KeyExchange_DH_RSA) { + certType = "DH_RSA"; + } + // obtain certificates from key manager + String alias = null; + X509KeyManager km = parameters.getKeyManager(); + if (km instanceof X509ExtendedKeyManager) { + X509ExtendedKeyManager ekm = (X509ExtendedKeyManager)km; + // BEGIN android-removed + // if (this.socketOwner != null) { + // alias = ekm.chooseServerAlias(certType, null, + // this.socketOwner); + // } else { + // END android-removed + alias = ekm.chooseEngineServerAlias(certType, null, + this.engineOwner); + // BEGIN android-removed + // } + // END android-removed + if (alias != null) { + certs = ekm.getCertificateChain(alias); + } + } else { + // BEGIN android-removed + // alias = km.chooseServerAlias(certType, null, this.socketOwner); + // if (alias != null) { + // END android-removed + certs = km.getCertificateChain(alias); + // BEGIN android-removed + // } + // END android-removed + } + + if (certs == null) { + fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "NO SERVER CERTIFICATE FOUND"); + return; + } + session.localCertificates = certs; + serverCert = new CertificateMessage(certs); + privKey = parameters.getKeyManager().getPrivateKey(alias); + send(serverCert); + } + + // create and send server key exchange message if needed + RSAPublicKey rsakey = null; + DHPublicKeySpec dhkeySpec = null; + byte[] hash = null; + BigInteger p = null; + BigInteger g = null; + + KeyPairGenerator kpg = null; + + try { + if (cipher_suite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT) { + PublicKey pk = serverCert.certs[0].getPublicKey(); + if (getRSAKeyLength(pk) > 512) { + // key is longer than 512 bits + kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(512); + } + } else if (cipher_suite.keyExchange == CipherSuite.KeyExchange_DHE_DSS + || cipher_suite.keyExchange == CipherSuite.KeyExchange_DHE_DSS_EXPORT + || cipher_suite.keyExchange == CipherSuite.KeyExchange_DHE_RSA + || cipher_suite.keyExchange == CipherSuite.KeyExchange_DHE_RSA_EXPORT + || cipher_suite.keyExchange == CipherSuite.KeyExchange_DH_anon + || cipher_suite.keyExchange == CipherSuite.KeyExchange_DH_anon_EXPORT) { + try { + kpg = KeyPairGenerator.getInstance("DH"); + } catch (NoSuchAlgorithmException ee) { + kpg = KeyPairGenerator.getInstance("DiffieHellman"); + } + p = new BigInteger(1, DHParameters.getPrime()); + g = new BigInteger("2"); + DHParameterSpec spec = new DHParameterSpec(p, g); + kpg.initialize(spec); + } + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e); + } + + if (kpg != null) { + // need to send server key exchange message + DigitalSignature ds = new DigitalSignature(cipher_suite.keyExchange); + KeyPair kp = null; + try { + kp = kpg.genKeyPair(); + if (cipher_suite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT) { + rsakey = (RSAPublicKey) kp.getPublic(); + } else { + DHPublicKey dhkey = (DHPublicKey) kp.getPublic(); + KeyFactory kf = null; + try { + kf = KeyFactory.getInstance("DH"); + } catch (NoSuchAlgorithmException e) { + kf = KeyFactory.getInstance("DiffieHellman"); + } + dhkeySpec = (DHPublicKeySpec) kf.getKeySpec(dhkey, + DHPublicKeySpec.class); + } + if (!cipher_suite.isAnonymous()) { // calculate signed_params + + // init by private key which correspond to + // server certificate + ds.init(privKey); + + // use emphemeral key for key exchange + privKey = kp.getPrivate(); + ds.update(clientHello.getRandom()); + ds.update(serverHello.getRandom()); + + byte[] tmp; + byte[] tmpLength = new byte[2]; +//FIXME 1_byte==0x00 + if (cipher_suite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT) { + tmp = rsakey.getModulus().toByteArray(); + tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8); + tmpLength[1] = (byte) (tmp.length & 0xFF); + ds.update(tmpLength); + ds.update(tmp); + tmp = rsakey.getPublicExponent().toByteArray(); + tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8); + tmpLength[1] = (byte) (tmp.length & 0xFF); + ds.update(tmp); + } else { + tmp = dhkeySpec.getP().toByteArray(); + tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8); + tmpLength[1] = (byte) (tmp.length & 0xFF); + ds.update(tmp); + tmp = dhkeySpec.getG().toByteArray(); + tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8); + tmpLength[1] = (byte) (tmp.length & 0xFF); + ds.update(tmp); + tmp = dhkeySpec.getY().toByteArray(); + tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8); + tmpLength[1] = (byte) (tmp.length & 0xFF); + ds.update(tmp); + } + hash = ds.sign(); + } else { + privKey = kp.getPrivate(); // use emphemeral key for key exchange + } + } catch (Exception e) { + fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e); + } + + if (cipher_suite.keyExchange == CipherSuite.KeyExchange_RSA_EXPORT) { + serverKeyExchange = new ServerKeyExchange(rsakey.getModulus(), + rsakey.getPublicExponent(), null, hash); + } else { + serverKeyExchange = new ServerKeyExchange(p, + g, dhkeySpec.getY(), hash); + } + send(serverKeyExchange); + } + + // CERTIFICATE_REQUEST + certRequest: if (parameters.getWantClientAuth() + || parameters.getNeedClientAuth()) { + X509Certificate[] accepted; + try { + X509TrustManager tm = parameters.getTrustManager(); + accepted = tm.getAcceptedIssuers(); + } catch (ClassCastException e) { + // don't send certificateRequest + break certRequest; + } + byte[] requestedClientCertTypes = {1, 2}; // rsa sign, dsa sign + certificateRequest = new CertificateRequest( + requestedClientCertTypes, accepted); + send(certificateRequest); + } + + // SERVER_HELLO_DONE + serverHelloDone = new ServerHelloDone(); + send(serverHelloDone); + status = NEED_UNWRAP; + } + + /** + * Creates and sends finished message + */ + protected void makeFinished() { + byte[] verify_data; + boolean isTLS = (serverHello.server_version[1] == 1); // TLS 1.0 protocol + if (isTLS) { + verify_data = new byte[12]; + computerVerifyDataTLS("server finished", verify_data); + } else { // SSL 3.0 protocol (http://wp.netscape.com/eng/ssl3) + verify_data = new byte[36]; + computerVerifyDataSSLv3(SSLv3Constants.server, verify_data); + } + serverFinished = new Finished(verify_data); + send(serverFinished); + if (isResuming) { + if (isTLS) { + computerReferenceVerifyDataTLS("client finished"); + } else { + computerReferenceVerifyDataSSLv3(SSLv3Constants.client); + } + status = NEED_UNWRAP; + } else { + session.lastAccessedTime = System.currentTimeMillis(); + status = FINISHED; + } + } + + // find sesssion in the session hash + private SSLSessionImpl findSessionToResume(byte[] session_id) { + return (SSLSessionImpl)parameters.getServerSessionContext().getSession(session_id); + } + + // find appropriate cipher_suite in the client suites + private CipherSuite selectSuite(CipherSuite[] client_suites) { + for (int i = 0; i < client_suites.length; i++) { + if (!client_suites[i].supported) { + continue; + } + // BEGIN android-removed + // for (int j = 0; j < parameters.enabledCipherSuites.length; j++) { + // if (client_suites[i].equals(parameters.enabledCipherSuites[j])) { + // return client_suites[i]; + // } + // } + // END android-removed + // BEGIN android-added + for (int j = 0; j < parameters.getEnabledCipherSuitesMember().length; j++) { + if (client_suites[i].equals(parameters.getEnabledCipherSuitesMember()[j])) { + return client_suites[i]; + } + } + // END android-added + } + return null; + } + + /** + * Proceses inbound ChangeCipherSpec message + */ + public void receiveChangeCipherSpec() { + if (isResuming) { + if (serverFinished == null) { + unexpectedMessage(); + } else { + changeCipherSpecReceived = true; + } + } else { + if ((parameters.getNeedClientAuth() && clientCert == null) + || clientKeyExchange == null + || (clientCert != null && !clientKeyExchange.isEmpty() && certificateVerify == null)) { + unexpectedMessage(); + } else { + changeCipherSpecReceived = true; + } + if (serverHello.server_version[1] == 1) { + computerReferenceVerifyDataTLS("client finished"); + } else { + computerReferenceVerifyDataSSLv3(SSLv3Constants.client); + } + } + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHello.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHello.java new file mode 100644 index 0000000..0365288 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHello.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.Message; + +import java.io.IOException; +import java.security.SecureRandom; + +/** + * + * Represents server hello message. + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.1.3. + * Server hello.</a> + */ +public class ServerHello extends Message { + + /** + * Server version + */ + byte[] server_version = new byte[2]; + + /** + * Random bytes + */ + byte[] random = new byte[32]; + + /** + * Session id + */ + byte[] session_id; + + /** + * Selected cipher suite + */ + CipherSuite cipher_suite; + + /** + * Selected compression method + */ + byte compression_method; + + /** + * Creates outbound message + * @param sr + * @param server_version + * @param session_id + * @param cipher_suite + * @param compression_method + */ + public ServerHello(SecureRandom sr, byte[] server_version, + byte[] session_id, CipherSuite cipher_suite, byte compression_method) { + long gmt_unix_time = new java.util.Date().getTime() / 1000; + sr.nextBytes(random); + random[0] = (byte) ((gmt_unix_time & 0xFF000000) >>> 24); + random[1] = (byte) ((gmt_unix_time & 0xFF0000) >>> 16); + random[2] = (byte) ((gmt_unix_time & 0xFF00) >>> 8); + random[3] = (byte) (gmt_unix_time & 0xFF); + this.session_id = session_id; + this.cipher_suite = cipher_suite; + this.compression_method = compression_method; + this.server_version = server_version; + length = 38 + session_id.length; + } + + /** + * Creates inbound message + * @param in + * @param length + * @throws IOException + */ + public ServerHello(HandshakeIODataStream in, int length) throws IOException { + + server_version[0] = (byte) in.read(); + server_version[1] = (byte) in.read(); + in.read(random, 0, 32); + int size = in.readUint8(); + session_id = new byte[size]; + in.read(session_id, 0, size); + byte b0 = (byte) in.read(); + byte b1 = (byte) in.read(); + cipher_suite = CipherSuite.getByCode(b0, b1); + compression_method = (byte) in.read(); + this.length = 38 + session_id.length; + if (this.length != length) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ServerHello"); + } + + } + + /** + * Sends message + * @param out + */ + public void send(HandshakeIODataStream out) { + out.write(server_version); + out.write(random); + out.writeUint8(session_id.length); + out.write(session_id); + out.write(cipher_suite.toBytes()); + out.write(compression_method); + length = 38 + session_id.length; + } + + /** + * Returns server random + * @return + */ + public byte[] getRandom() { + return random; + } + + /** + * Returns message type + * @return + */ + public int getType() { + return Handshake.SERVER_HELLO; + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHelloDone.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHelloDone.java new file mode 100644 index 0000000..e794ed9 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerHelloDone.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.Message; + +import java.io.IOException; + +/** + * + * Represents server hello done message + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.5. + * Server hello done</a> + * + */ +public class ServerHelloDone extends Message { + + /** + * Creates outbound message + * + */ + public ServerHelloDone() { + } + + /** + * Creates inbound message + * @param in + * @param length + * @throws IOException + */ + public ServerHelloDone(HandshakeIODataStream in, int length) + throws IOException { + if (length != 0) { + fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ServerHelloDone"); + } + } + + /** + * Sends message + * @param out + */ + public void send(HandshakeIODataStream out) { + } + + /** + * Returns message length + * @return + */ + public int length() { + return 0; + } + + /** + * Returns message type + * @return + */ + public int getType() { + return Handshake.SERVER_HELLO_DONE; + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerKeyExchange.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerKeyExchange.java new file mode 100644 index 0000000..1d93ece --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerKeyExchange.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** +* @author Boris Kuznetsov +* @version $Revision$ +*/ + +package org.apache.harmony.xnet.provider.jsse; + +import org.apache.harmony.xnet.provider.jsse.Message; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAPublicKeySpec; + +/** + * + * Represents server key exchange message. + * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.3. + * Server key exchange message.</a> + * + */ +public class ServerKeyExchange extends Message { + + // ServerRSAParams ServerDHParams + final BigInteger par1; // rsa_modulus dh_p + final byte[] bytes1; + + final BigInteger par2; // rsa_exponent dh_g + final byte[] bytes2; + + final BigInteger par3; // dh_Ys + final byte[] bytes3; + + /** + * Signature + */ + final byte[] hash; + + private RSAPublicKey key; + + /** + * Creates outbound message + * @param par1 rsa_modulus or dh_p + * @param par2 rsa_exponent or dh_g + * @param par3 dh_Ys for ServerDHParams; should be null for ServerRSAParams + * @param hash should be null for anonymous SignatureAlgorithm + */ + public ServerKeyExchange(BigInteger par1, BigInteger par2, BigInteger par3, + byte[] hash) { + this.par1 = par1; + this.par2 = par2; + this.par3 = par3; + this.hash = hash; + + byte[] bb = this.par1.toByteArray(); + if (bb[0] == 0) { +// XXX check for par1 == 0 or bb.length > 1 + bytes1 = new byte[bb.length - 1]; + System.arraycopy(bb, 1, bytes1, 0, bytes1.length); + } else { + bytes1 = bb; + } + + bb = this.par2.toByteArray(); + if (bb[0] == 0) { + bytes2 = new byte[bb.length - 1]; + System.arraycopy(bb, 1, bytes2, 0, bytes2.length); + } else { + bytes2 = bb; + } + + length = 4 + bytes1.length + bytes2.length; + if (hash != null) { + length += 2 + hash.length; + } + if (par3 == null) { + bytes3 = null; + return; + } + bb = this.par3.toByteArray(); + if (bb[0] == 0) { + bytes3 = new byte[bb.length - 1]; + System.arraycopy(bb, 1, bytes3, 0, bytes3.length); + } else { + bytes3 = bb; + } + length += 2 + bytes3.length; + } + + /** + * Creates inbound message + * @param in + * @param length + * @param keyExchange + * @throws IOException + */ + public ServerKeyExchange(HandshakeIODataStream in, int length, + int keyExchange) throws IOException { + + int size = in.readUint16(); + bytes1 = in.read(size); + par1 = new BigInteger(1, bytes1); + this.length = 2 + bytes1.length; + size = in.readUint16(); + bytes2 = in.read(size); + par2 = new BigInteger(1, bytes2); + this.length += 2 + bytes2.length; + if (keyExchange != CipherSuite.KeyExchange_RSA_EXPORT) { + size = in.readUint16(); + bytes3 = in.read(size); + par3 = new BigInteger(1, bytes3); + this.length += 2 + bytes3.length; + } else { + par3 = null; + bytes3 = null; + } + if (keyExchange != CipherSuite.KeyExchange_DH_anon_EXPORT + && keyExchange != CipherSuite.KeyExchange_DH_anon) { + size = in.readUint16(); + hash = in.read(size); + this.length += 2 + hash.length; + } else { + hash = null; + } + if (this.length != length) { + fatalAlert(AlertProtocol.DECODE_ERROR, + "DECODE ERROR: incorrect ServerKeyExchange"); + } + } + + /** + * Sends message + * @param out + */ + public void send(HandshakeIODataStream out) { + out.writeUint16(bytes1.length); + out.write(bytes1); + out.writeUint16(bytes2.length); + out.write(bytes2); + if (bytes3 != null) { + out.writeUint16(bytes3.length); + out.write(bytes3); + } + if (hash != null) { + out.writeUint16(hash.length); + out.write(hash); + } + } + + /** + * Returns RSAPublicKey generated using ServerRSAParams + * (rsa_modulus and rsa_exponent). + * + * @return + */ + public RSAPublicKey getRSAPublicKey() { + if (key != null) { + return key; + } + try { + KeyFactory kf = KeyFactory.getInstance("RSA"); + key = (RSAPublicKey) kf.generatePublic(new RSAPublicKeySpec(par1, + par2)); + } catch (Exception e) { + return null; + } + return key; + } + + /** + * Returns message type + * @return + */ + public int getType() { + return Handshake.SERVER_KEY_EXCHANGE; + } + +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerFactoryImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerFactoryImpl.java new file mode 100644 index 0000000..b96d1ab --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerFactoryImpl.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.AccessController; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactorySpi; + +/** + * + * TrustManagerFactory service provider interface implementation. + * + * @see javax.net.ssl.TrustManagerFactorySpi + */ +public class TrustManagerFactoryImpl extends TrustManagerFactorySpi { + + private KeyStore keyStore; + + /** + * @see javax.net.ssl.TrustManagerFactorySpi#engineInit(KeyStore) + */ + public void engineInit(KeyStore ks) throws KeyStoreException { + if (ks != null) { + keyStore = ks; + } else { + // BEGIN android-added + if (System.getProperty("javax.net.ssl.trustStore") == null) { + String file = System.getProperty("java.home") + + java.io.File.separator + "etc" + java.io.File.separator + + "security" + java.io.File.separator + + "cacerts.bks"; + + System.setProperty("javax.net.ssl.trustStore", file); + } + // END android-added + keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + String keyStoreName = AccessController + .doPrivileged(new java.security.PrivilegedAction<String>() { + public String run() { + return System + .getProperty("javax.net.ssl.trustStore"); + } + }); + String keyStorePwd = null; + if (keyStoreName == null || keyStoreName.equalsIgnoreCase("NONE") + || keyStoreName.length() == 0) { + try { + keyStore.load(null, null); + } catch (IOException e) { + throw new KeyStoreException(e); + } catch (CertificateException e) { + throw new KeyStoreException(e); + } catch (NoSuchAlgorithmException e) { + throw new KeyStoreException(e); + } + } else { + keyStorePwd = AccessController + .doPrivileged(new java.security.PrivilegedAction<String>() { + public String run() { + return System + .getProperty("javax.net.ssl.trustStorePassword"); + } + }); + char[] pwd; + if (keyStorePwd == null) { + pwd = new char[0]; + } else { + pwd = keyStorePwd.toCharArray(); + } + try { + keyStore.load(new FileInputStream(new File(keyStoreName)), pwd); + } catch (FileNotFoundException e) { + throw new KeyStoreException(e); + } catch (IOException e) { + throw new KeyStoreException(e); + } catch (CertificateException e) { + throw new KeyStoreException(e); + } catch (NoSuchAlgorithmException e) { + throw new KeyStoreException(e); + } + } + } + + } + + /** + * @see javax.net.ssl#engineInit(ManagerFactoryParameters) + */ + public void engineInit(ManagerFactoryParameters spec) + throws InvalidAlgorithmParameterException { + throw new InvalidAlgorithmParameterException( + "ManagerFactoryParameters not supported"); + } + + /** + * @see javax.net.ssl#engineGetTrustManagers() + */ + public TrustManager[] engineGetTrustManagers() { + if (keyStore == null) { + throw new IllegalStateException( + "TrustManagerFactory is not initialized"); + } + return new TrustManager[] { new TrustManagerImpl(keyStore) }; + } +} diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java new file mode 100644 index 0000000..1c45fb8 --- /dev/null +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java @@ -0,0 +1,208 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * @author Boris Kuznetsov + * @version $Revision$ + */ + +package org.apache.harmony.xnet.provider.jsse; + +import java.lang.reflect.Method; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.cert.CertPath; +import java.security.cert.CertPathValidator; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.PKIXParameters; +import java.security.cert.TrustAnchor; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javax.net.ssl.X509TrustManager; + +/** + * + * TrustManager implementation. The implementation is based on CertPathValidator + * PKIX and CertificateFactory X509 implementations. This implementations should + * be provided by some certification provider. + * + * @see javax.net.ssl.X509TrustManager + */ +public class TrustManagerImpl implements X509TrustManager { + + private CertPathValidator validator; + + private PKIXParameters params; + + private Exception err = null; + + private CertificateFactory factory; + + /** + * Creates trust manager implementation + * + * @param ks + */ + public TrustManagerImpl(KeyStore ks) { + try { + validator = CertPathValidator.getInstance("PKIX"); + factory = CertificateFactory.getInstance("X509"); + String alias; + X509Certificate cert; + byte[] nameConstrains = null; + Set trusted = new HashSet(); + for (Enumeration en = ks.aliases(); en.hasMoreElements();) { + alias = (String) en.nextElement(); + cert = (X509Certificate) ks.getCertificate(alias); + if (cert != null) { + trusted.add(new TrustAnchor(cert, nameConstrains)); + } + } + params = new PKIXParameters(trusted); + params.setRevocationEnabled(false); + } catch (Exception e) { + err = e; + } + } + + /** + * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], + * String) + */ + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + if (chain == null || chain.length == 0 || authType == null + || authType.length() == 0) { + throw new IllegalArgumentException("null or zero-length parameter"); + } + if (err != null) { + throw new CertificateException(err); + } + + // Cater for degenerate special case where we can't + // establish an actual certificate chain the usual way, + // but have the peer certificate in our trust store. + if (isDirectlyTrustedCert(chain)) { + return; + } + + try { + CertPath certPath = factory.generateCertPath(Arrays.asList(chain)); + if (!Arrays.equals(chain[0].getEncoded(), + ((X509Certificate)certPath.getCertificates().get(0)).getEncoded())) { + // sanity check failed (shouldn't ever happen, but we are using pretty remote code) + throw new CertificateException("Certificate chain error"); + } + validator.validate(certPath, params); + } catch (InvalidAlgorithmParameterException e) { + throw new CertificateException(e); + } catch (CertPathValidatorException e) { + throw new CertificateException(e); + } + } + + /** + * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], + * String) + */ + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + if (chain == null || chain.length == 0 || authType == null + || authType.length() == 0) { + throw new IllegalArgumentException("null or zero-length parameter"); + } + if (err != null) { + throw new CertificateException(err); + } + + // Cater for degenerate special case where we can't + // establish an actual certificate chain the usual way, + // but have the peer certificate in our trust store. + if (isDirectlyTrustedCert(chain)) { + return; + } + + try { + CertPath certPath = factory.generateCertPath(Arrays.asList(chain)); + if (!Arrays.equals(chain[0].getEncoded(), + ((X509Certificate)certPath.getCertificates().get(0)).getEncoded())) { + // sanity check failed (shouldn't ever happen, but we are using pretty remote code) + throw new CertificateException("Certificate chain error"); + } + validator.validate(certPath, params); + } catch (InvalidAlgorithmParameterException e) { + throw new CertificateException(e); + } catch (CertPathValidatorException e) { + throw new CertificateException(e); + } + } + + /** + * Checks whether the given chain is just a certificate + * that we have in our trust store. + * + * @param chain The certificate chain. + * + * @return True if the certificate is in our trust store, false otherwise. + */ + private boolean isDirectlyTrustedCert(X509Certificate[] chain) { + byte[] questionable; + + if (chain.length == 1) { + try { + questionable = chain[0].getEncoded(); + Set<TrustAnchor> anchors = params.getTrustAnchors(); + + for (TrustAnchor trustAnchor : anchors) { + byte[] trusted = trustAnchor.getTrustedCert().getEncoded(); + + if (Arrays.equals(questionable, trusted)) { + return true; + } + } + } catch (CertificateEncodingException e) { + // Ignore. + } + } + + return false; + } + + /** + * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() + */ + public X509Certificate[] getAcceptedIssuers() { + if (params == null) { + return new X509Certificate[0]; + } + Set anchors = params.getTrustAnchors(); + X509Certificate[] certs = new X509Certificate[anchors.size()]; + int i = 0; + for (Iterator it = anchors.iterator(); it.hasNext();) { + certs[i++] = ((TrustAnchor) it.next()).getTrustedCert(); + } + return certs; + } +} diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp new file mode 100644 index 0000000..59859ba --- /dev/null +++ b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2008 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. + */ + +/** + * Native glue for Java class org.apache.harmony.xnet.provider.jsse.NativeCrypto + */ + +#define LOG_TAG "NativeCrypto" + +#include <jni.h> +#include <JNIHelp.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/dsa.h> +#include <openssl/rsa.h> + +/** + * Frees the SSL error state. + * + * OpenSSL keeps an "error stack" per thread, and given that this code + * can be called from arbitrary threads that we don't keep track of, + * we err on the side of freeing the error state promptly (instead of, + * say, at thread death). + */ +static void freeSslErrorState(void) { + ERR_clear_error(); + ERR_remove_state(0); +} + +/** + * Throws a NullPointerException without any message. + */ +static void throwNullPointerException(JNIEnv* env) { + jniThrowException(env, "java/lang/NullPointerException", NULL); +} + +/** + * Throws a RuntimeException with a human-readable error message. + */ +static void throwRuntimeException(JNIEnv* env, const char* message) { + jniThrowException(env, "java/lang/RuntimeException", message); +} + +/* + * Checks this thread's OpenSSL error queue and throws a RuntimeException if + * necessary. + * + * @return 1 if an exception was thrown, 0 if not. + */ +static int throwExceptionIfNecessary(JNIEnv* env) { + int error = ERR_get_error(); + int result = 0; + + if (error != 0) { + char message[50]; + ERR_error_string_n(error, message, sizeof(message)); + LOGD("OpenSSL error %d: %s", error, message); + throwRuntimeException(env, message); + result = 1; + } + + freeSslErrorState(); + return result; +} + +/** + * Converts a Java byte[] to an OpenSSL BIGNUM, allocating the BIGNUM on the + * fly. + */ +static BIGNUM* arrayToBignum(JNIEnv* env, jbyteArray source) { + // LOGD("Entering arrayToBignum()"); + + jbyte* sourceBytes = env->GetByteArrayElements(source, NULL); + int sourceLength = env->GetArrayLength(source); + BIGNUM* bignum = BN_bin2bn((unsigned char*) sourceBytes, sourceLength, NULL); + env->ReleaseByteArrayElements(source, sourceBytes, JNI_ABORT); + return bignum; +} + +static void rsaDestroyKey(JNIEnv* env, jclass clazz, RSA* rsa); + +/** + * private static native int rsaCreatePublicKey(byte[] n, byte[] e); + */ +static RSA* rsaCreatePublicKey(JNIEnv* env, jclass clazz, jbyteArray n, jbyteArray e) { + // LOGD("Entering rsaCreatePublicKey()"); + + RSA* rsa = RSA_new(); + + rsa->n = arrayToBignum(env, n); + rsa->e = arrayToBignum(env, e); + + if (rsa->n == NULL || rsa->e == NULL) { + rsaDestroyKey(env, clazz, rsa); + throwRuntimeException(env, "Unable to convert BigInteger to BIGNUM"); + return NULL; + } + + return rsa; +} + +/** + * private static native int rsaCreatePrivateKey(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q); + */ +static RSA* rsaCreatePrivateKey(JNIEnv* env, jclass clazz, jbyteArray n, jbyteArray e, jbyteArray d, jbyteArray p, jbyteArray q) { + // LOGD("Entering rsaCreatePrivateKey()"); + + RSA* rsa = RSA_new(); + + rsa->n = arrayToBignum(env, n); + rsa->e = arrayToBignum(env, e); + rsa->d = arrayToBignum(env, d); + rsa->p = arrayToBignum(env, p); + rsa->q = arrayToBignum(env, q); + + int check = RSA_check_key(rsa); + LOGI("RSA_check_key returns %d", check); + + if (rsa->n == NULL || rsa->e == NULL || rsa->d == NULL || rsa->p == NULL || rsa->q == NULL) { + rsaDestroyKey(env, clazz, rsa); + throwRuntimeException(env, "Unable to convert BigInteger to BIGNUM"); + return NULL; + } + + return rsa; +} + +/** + * private static native void rsaDestroyKey(int rsa); + */ +static void rsaDestroyKey(JNIEnv* env, jclass clazz, RSA* rsa) { + // LOGD("Entering rsaDestroyKey()"); + + if (rsa != NULL) { + RSA_free(rsa); + } +} + +/** + * private static native int EVP_PKEY_new_DSA(byte[] p, byte[] q, byte[] g, byte[] pub_key, byte[] priv_key); + */ +static EVP_PKEY* NativeCrypto_EVP_PKEY_new_DSA(JNIEnv* env, jclass clazz, jbyteArray p, jbyteArray q, jbyteArray g, jbyteArray pub_key, jbyteArray priv_key) { + // LOGD("Entering EVP_PKEY_new_DSA()"); + + DSA* dsa = DSA_new(); + + dsa->p = arrayToBignum(env, p); + dsa->q = arrayToBignum(env, q); + dsa->g = arrayToBignum(env, g); + dsa->pub_key = arrayToBignum(env, pub_key); + + if (priv_key != NULL) { + dsa->priv_key = arrayToBignum(env, priv_key); + } + + if (dsa->p == NULL || dsa->q == NULL || dsa->g == NULL || dsa->pub_key == NULL) { + DSA_free(dsa); + throwRuntimeException(env, "Unable to convert BigInteger to BIGNUM"); + return NULL; + } + + EVP_PKEY* pkey = EVP_PKEY_new(); + EVP_PKEY_assign_DSA(pkey, dsa); + + return pkey; +} + +/** + * private static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q); + */ +static EVP_PKEY* NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass clazz, jbyteArray n, jbyteArray e, jbyteArray d, jbyteArray p, jbyteArray q) { + // LOGD("Entering EVP_PKEY_new_RSA()"); + + RSA* rsa = RSA_new(); + + rsa->n = arrayToBignum(env, n); + rsa->e = arrayToBignum(env, e); + + if (d != NULL) { + rsa->d = arrayToBignum(env, d); + } + + if (p != NULL) { + rsa->p = arrayToBignum(env, p); + } + + if (q != NULL) { + rsa->q = arrayToBignum(env, q); + } + + // int check = RSA_check_key(rsa); + // LOGI("RSA_check_key returns %d", check); + + if (rsa->n == NULL || rsa->e == NULL) { + RSA_free(rsa); + throwRuntimeException(env, "Unable to convert BigInteger to BIGNUM"); + return NULL; + } + + EVP_PKEY* pkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(pkey, rsa); + + return pkey; +} + +/** + * private static native void EVP_PKEY_free(int pkey); + */ +static void NativeCrypto_EVP_PKEY_free(JNIEnv* env, jclass clazz, EVP_PKEY* pkey) { + // LOGD("Entering EVP_PKEY_free()"); + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } +} + +/* + * public static native int EVP_new() + */ +static jint NativeCrypto_EVP_new(JNIEnv* env, jclass clazz) { + // LOGI("NativeCrypto_EVP_DigestNew"); + + return (jint)EVP_MD_CTX_create(); +} + +/* + * public static native void EVP_free(int) + */ +static void NativeCrypto_EVP_free(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx) { + // LOGI("NativeCrypto_EVP_DigestFree"); + + if (ctx != NULL) { + EVP_MD_CTX_destroy(ctx); + } +} + +/* + * public static native int EVP_DigestFinal(int, byte[], int) + */ +static jint NativeCrypto_EVP_DigestFinal(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray hash, jint offset) { + // LOGI("NativeCrypto_EVP_DigestFinal%x, %x, %d, %d", ctx, hash, offset); + + if (ctx == NULL || hash == NULL) { + throwNullPointerException(env); + return -1; + } + + int result = -1; + + jbyte* hashBytes = env->GetByteArrayElements(hash, NULL); + EVP_DigestFinal(ctx, (unsigned char*) (hashBytes + offset), (unsigned int*)&result); + env->ReleaseByteArrayElements(hash, hashBytes, 0); + + throwExceptionIfNecessary(env); + + return result; +} + +/* + * public static native void EVP_DigestInit(int, java.lang.String) + */ +static void NativeCrypto_EVP_DigestInit(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jstring algorithm) { + // LOGI("NativeCrypto_EVP_DigestInit"); + + if (ctx == NULL || algorithm == NULL) { + throwNullPointerException(env); + return; + } + + const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL); + + const EVP_MD *digest = EVP_get_digestbynid(OBJ_txt2nid(algorithmChars)); + env->ReleaseStringUTFChars(algorithm, algorithmChars); + + if (digest == NULL) { + throwRuntimeException(env, "Hash algorithm not found"); + return; + } + + EVP_DigestInit(ctx, digest); + + throwExceptionIfNecessary(env); +} + +/* + * public static native void EVP_DigestReset(int) + */ +static jint NativeCrypto_EVP_DigestSize(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx) { + // LOGI("NativeCrypto_EVP_DigestSize"); + + if (ctx == NULL) { + throwNullPointerException(env); + return -1; + } + + int result = EVP_MD_CTX_size(ctx); + + throwExceptionIfNecessary(env); + + return result; +} + +/* + * public static native void EVP_DigestReset(int) + */ +static jint NativeCrypto_EVP_DigestBlockSize(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx) { + // LOGI("NativeCrypto_EVP_DigestBlockSize"); + + if (ctx == NULL) { + throwNullPointerException(env); + return -1; + } + + int result = EVP_MD_CTX_block_size(ctx); + + throwExceptionIfNecessary(env); + + return result; +} + +/* + * public static native void EVP_DigestUpdate(int, byte[], int, int) + */ +static void NativeCrypto_EVP_DigestUpdate(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length) { + // LOGI("NativeCrypto_EVP_DigestUpdate %x, %x, %d, %d", ctx, buffer, offset, length); + + if (ctx == NULL || buffer == NULL) { + throwNullPointerException(env); + return; + } + + jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL); + EVP_DigestUpdate(ctx, (unsigned char*) (bufferBytes + offset), length); + env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT); + + throwExceptionIfNecessary(env); +} + +/* + * public static native void EVP_VerifyInit(int, java.lang.String) + */ +static void NativeCrypto_EVP_VerifyInit(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jstring algorithm) { + // LOGI("NativeCrypto_EVP_VerifyInit"); + + if (ctx == NULL || algorithm == NULL) { + throwNullPointerException(env); + return; + } + + const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL); + + const EVP_MD *digest = EVP_get_digestbynid(OBJ_txt2nid(algorithmChars)); + env->ReleaseStringUTFChars(algorithm, algorithmChars); + + if (digest == NULL) { + throwRuntimeException(env, "Hash algorithm not found"); + return; + } + + EVP_VerifyInit(ctx, digest); + + throwExceptionIfNecessary(env); +} + +/* + * public static native void EVP_VerifyUpdate(int, byte[], int, int) + */ +static void NativeCrypto_EVP_VerifyUpdate(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length) { + // LOGI("NativeCrypto_EVP_VerifyUpdate %x, %x, %d, %d", ctx, buffer, offset, length); + + if (ctx == NULL || buffer == NULL) { + throwNullPointerException(env); + return; + } + + jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL); + EVP_VerifyUpdate(ctx, (unsigned char*) (bufferBytes + offset), length); + env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT); + + throwExceptionIfNecessary(env); +} + +/* + * public static native void EVP_VerifyFinal(int, byte[], int, int, int) + */ +static int NativeCrypto_EVP_VerifyFinal(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length, EVP_PKEY* pkey) { + // LOGI("NativeCrypto_EVP_VerifyFinal %x, %x, %d, %d %x", ctx, buffer, offset, length, pkey); + + if (ctx == NULL || buffer == NULL || pkey == NULL) { + throwNullPointerException(env); + return -1; + } + + jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL); + int result = EVP_VerifyFinal(ctx, (unsigned char*) (bufferBytes + offset), length, pkey); + env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT); + + throwExceptionIfNecessary(env); + + return result; +} + +/* + * Defines the mapping from Java methods and their signatures + * to native functions. Order is (1) Java name, (2) signature, + * (3) pointer to C function. + */ +static JNINativeMethod methods[] = { +/* + { "dsaCreatePublicKey", "([B[B[B[B)I", (void*)dsaCreatePublicKey }, + { "dsaCreatePrivateKey", "([B[B[B[B[B)I", (void*)dsaCreatePrivateKey }, + { "dsaDestroyKey", "(I)V", (void*)dsaDestroyKey }, + { "rsaCreatePublicKey", "([B[B)I", (void*)rsaCreatePublicKey }, + { "rsaCreatePrivateKey", "([B[B[B[B[B)I", (void*)rsaCreatePrivateKey }, + { "rsaDestroyKey", "(I)V", (void*)rsaDestroyKey }, +*/ + { "EVP_PKEY_new_DSA", "([B[B[B[B[B)I", (void*)NativeCrypto_EVP_PKEY_new_DSA }, + { "EVP_PKEY_new_RSA", "([B[B[B[B[B)I", (void*)NativeCrypto_EVP_PKEY_new_RSA }, + { "EVP_PKEY_free", "(I)V", (void*)NativeCrypto_EVP_PKEY_free }, + { "EVP_new", "()I", (void*)NativeCrypto_EVP_new }, + { "EVP_free", "(I)V", (void*)NativeCrypto_EVP_free }, + { "EVP_DigestFinal", "(I[BI)I", (void*)NativeCrypto_EVP_DigestFinal }, + { "EVP_DigestInit", "(ILjava/lang/String;)V", (void*)NativeCrypto_EVP_DigestInit }, + { "EVP_DigestBlockSize", "(I)I", (void*)NativeCrypto_EVP_DigestBlockSize }, + { "EVP_DigestSize", "(I)I", (void*)NativeCrypto_EVP_DigestSize }, + { "EVP_DigestUpdate", "(I[BII)V", (void*)NativeCrypto_EVP_DigestUpdate }, + { "EVP_VerifyInit", "(ILjava/lang/String;)V", (void*)NativeCrypto_EVP_VerifyInit }, + { "EVP_VerifyUpdate", "(I[BII)V", (void*)NativeCrypto_EVP_VerifyUpdate }, + { "EVP_VerifyFinal", "(I[BIII)I", (void*)NativeCrypto_EVP_VerifyFinal } +}; + +/* + * Peforms the actual registration of the native methods. + * Also looks up the fields that belong to the class (if + * any) and stores the field IDs. Simply remove what you + * don't need. + */ +extern "C" int register_org_apache_harmony_xnet_provider_jsse_NativeCrypto(JNIEnv* env) { + int result; + result = jniRegisterNativeMethods(env, "org/apache/harmony/xnet/provider/jsse/NativeCrypto", methods, NELEM(methods)); + if (result == -1) { + return -1; + } + + jclass clazz; + clazz = env->FindClass("org/apache/harmony/xnet/provider/jsse/NativeCrypto"); + if (clazz == NULL) { + return -1; + } + + return 0; +} diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl.cpp new file mode 100644 index 0000000..13a1e61 --- /dev/null +++ b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2007 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. + */ + +#define LOG_TAG "OpenSSLServerSocketImpl" + +#include <jni.h> +#include <JNIHelp.h> + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> + +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/ssl.h> + +#include "org_apache_harmony_xnet_provider_jsse_common.h" + +/** + * Module scope variables initialized during JNI registration. + */ +static jfieldID field_ssl_ctx; + +/** + * Throws java.io.IOexception with the provided message. + */ +static void throwIOExceptionStr(JNIEnv* env, const char* message) +{ + jclass exClass = env->FindClass("java/io/IOException"); + + if (exClass == NULL) + { + LOGE("Unable to find class java/io/IOException"); + } + else + { + env->ThrowNew(exClass, message); + } +} + +/** + * Initialization phase of OpenSSL: Loads the Error strings, the crypto algorithms and reset the OpenSSL library + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_initstatic(JNIEnv* env, jobject obj) +{ + SSL_load_error_strings(); + ERR_load_crypto_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); +} + +/** + * Initialization phase for a server socket with OpenSSL. The server's private key and X509 certificate are read and + * the Linux /dev/random file is loaded as RNG for the session keys. + * + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_init(JNIEnv* env, jobject object, + jstring privatekey, jstring certificates, jbyteArray seed) +{ + SSL_CTX *ssl_ctx; + const char *privatekeychar; + const char *certificateschar; + EVP_PKEY * privatekeyevp; + + BIO *privatekeybio; + BIO *certificatesbio; + + // 'seed == null' when no SecureRandom Object is set + // in the SSLContext. + if (seed != NULL) { + jboolean iscopy = JNI_FALSE; + jbyte* randseed = env->GetByteArrayElements(seed, &iscopy); + RAND_seed((unsigned char*) randseed, 1024); + } else { + RAND_load_file("/dev/urandom", 1024); + } + + ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); + + privatekeychar = env->GetStringUTFChars((jstring)privatekey, NULL); + privatekeybio = BIO_new_mem_buf((void*)privatekeychar, -1); + + privatekeyevp = PEM_read_bio_PrivateKey(privatekeybio, NULL, 0, NULL); + env->ReleaseStringUTFChars(privatekey, privatekeychar); + + if (privatekeyevp == NULL) { + LOGE(ERR_error_string(ERR_get_error(), NULL)); + throwIOExceptionStr(env, "Error parsing the private key"); + return; + } + + certificateschar = env->GetStringUTFChars((jstring)certificates, NULL); + certificatesbio = BIO_new_mem_buf((void*)certificateschar, -1); + + X509 * certificatesx509 = PEM_read_bio_X509(certificatesbio, NULL, 0, NULL); + env->ReleaseStringUTFChars(certificates, certificateschar); + + if (certificatesx509 == NULL) { + LOGE(ERR_error_string(ERR_get_error(), NULL)); + throwIOExceptionStr(env, "Error parsing the certificates"); + return; + } + + if (!SSL_CTX_use_certificate(ssl_ctx, certificatesx509)) { + LOGE(ERR_error_string(ERR_get_error(), NULL)); + throwIOExceptionStr(env, "Error setting the certificates"); + return; + } + + if (!SSL_CTX_use_PrivateKey(ssl_ctx, privatekeyevp)) { + LOGE(ERR_error_string(ERR_get_error(), NULL)); + throwIOExceptionStr(env, "Error setting the private key"); + return; + } + + if (!SSL_CTX_check_private_key(ssl_ctx)) { + LOGE(ERR_error_string(ERR_get_error(), NULL)); + throwIOExceptionStr(env, "Error checking private key"); + return; + } + + env->SetIntField(object, field_ssl_ctx, (int)ssl_ctx); +} + +/** + * Loads the desired protocol for the OpenSSL server and enables it. + * For example SSL_OP_NO_TLSv1 means do not use TLS v. 1. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_setenabledprotocols(JNIEnv* env, + jobject object, jlong protocol) +{ + if (protocol != 0x00000000L) { + if (protocol & SSL_OP_NO_SSLv3) + LOGD("SSL_OP_NO_SSLv3 is set"); + if (protocol & SSL_OP_NO_TLSv1) + LOGD("SSL_OP_NO_TLSv1 is set"); + + SSL_CTX* ctx = (SSL_CTX*)env->GetIntField(object, field_ssl_ctx); + SSL_CTX_set_options((SSL_CTX*)ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2|(long)protocol); + } +} + +/** + * Loads the ciphers suites that are supported by the OpenSSL server + * and returns them in a string array. + */ +static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_getsupportedciphersuites(JNIEnv* env, + jobject object) +{ + SSL_CTX* ctx; + SSL* ssl; + STACK_OF(SSL_CIPHER) *sk; + jobjectArray ret; + int i; + const char *c; + + ctx = SSL_CTX_new(SSLv23_server_method()); + ssl = SSL_new(ctx); + sk=SSL_get_ciphers(ssl); + + ret= (jobjectArray)env->NewObjectArray(5, + env->FindClass("java/lang/String"), + env->NewStringUTF("")); + + i = 0; + while (SSL_get_cipher_list(ssl,i) != NULL) { + i++; + } + + ret = (jobjectArray)env->NewObjectArray(i, + env->FindClass("java/lang/String"), + env->NewStringUTF("")); + + for (i=0; ; i++) { + c=SSL_get_cipher_list(ssl,i); + if (c == NULL) break; + + env->SetObjectArrayElement(ret,i,env->NewStringUTF(c)); + } + + return ret; +} + +/** + * Loads the ciphers suites that are enabled in the OpenSSL server + * and returns them in a string array. + */ +static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_getenabledciphersuites(JNIEnv* env, + jobject object) +{ + SSL_CTX* ctx; + SSL* ssl; + jobjectArray ret; + int i; + const char *c; + + ctx = (SSL_CTX*)env->GetIntField(object, field_ssl_ctx); + ssl = SSL_new(ctx); + + i = 0; + while (SSL_get_cipher_list(ssl,i) != NULL) { + i++; + } + + ret = (jobjectArray)env->NewObjectArray(i, + env->FindClass("java/lang/String"), + env->NewStringUTF("")); + + for (i = 0; ; i++) { + c = SSL_get_cipher_list(ssl,i); + if (c == NULL) break; + + env->SetObjectArrayElement(ret,i,env->NewStringUTF(c)); + } + + return ret; +} + +/** + * Sets the ciphers suites that are enabled in the OpenSSL server. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_setenabledciphersuites(JNIEnv* env, + jobject object, jstring controlstring) +{ + SSL_CTX* ctx; + const char *str; + int ret; + + ctx = (SSL_CTX*)env->GetIntField(object, field_ssl_ctx); + str = env->GetStringUTFChars(controlstring, 0); + ret = SSL_CTX_set_cipher_list(ctx, str); + + if(ret == 0) { + jclass exClass = env->FindClass("java/lang/IllegalArgumentException"); + env->ThrowNew(exClass, "Illegal cipher suite strings."); + } +} + +/** + * Sets the client's credentials and the depth of theirs verification. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth(JNIEnv* env, + jobject object, jint value) +{ + SSL_CTX *ssl_ctx = (SSL_CTX *)env->GetIntField(object, field_ssl_ctx); + SSL_CTX_set_verify(ssl_ctx, (int)value, verify_callback); +} + +/** + * The actual SSL context is reset. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativefree(JNIEnv* env, jobject object) +{ + SSL_CTX *ctx = (SSL_CTX *)env->GetIntField(object, field_ssl_ctx); + SSL_CTX_free(ctx); + env->SetIntField(object, field_ssl_ctx, 0); +} + +/** + * The actual JNI methods' mapping table for the class OpenSSLServerSocketImpl. + */ +static JNINativeMethod sMethods[] = +{ + {"nativeinitstatic", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_initstatic}, + {"nativeinit", "(Ljava/lang/String;Ljava/lang/String;[B)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_init}, + {"nativesetenabledprotocols", "(J)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_setenabledprotocols}, + {"nativegetsupportedciphersuites", "()[Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_getsupportedciphersuites}, + {"nativegetenabledciphersuites", "()[Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_getenabledciphersuites}, + {"nativesetenabledciphersuites", "(Ljava/lang/String;)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_setenabledciphersuites}, + {"nativesetclientauth", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth}, + {"nativefree", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativefree} +}; + +/** + * Register the native methods with JNI for the class OpenSSLServerSocketImpl. + */ +extern "C" int register_org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl(JNIEnv* env) +{ + int ret; + jclass clazz; + + clazz = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl"); + + if (clazz == NULL) { + LOGE("Can't find org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl"); + return -1; + } + + ret = jniRegisterNativeMethods(env, "org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl", + sMethods, NELEM(sMethods)); + + if (ret >= 0) { + // Note: do these after the registration of native methods, because + // there is a static method "initstatic" that's called when the + // OpenSSLServerSocketImpl class is first loaded, and that required + // a native method to be associated with it. + field_ssl_ctx = env->GetFieldID(clazz, "ssl_ctx", "I"); + if (field_ssl_ctx == NULL) { + LOGE("Can't find OpenSSLServerSocketImpl.ssl_ctx"); + return -1; + } + } + return ret; +} diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp new file mode 100644 index 0000000..42c4e77 --- /dev/null +++ b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2007 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. + */ + +#define LOG_TAG "OpenSSLSessionImpl" + +#include <jni.h> +#include <JNIHelp.h> + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> + +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/ssl.h> + +#include "org_apache_harmony_xnet_provider_jsse_common.h" + +/** + * Module scope variables initialized during JNI registration. + */ +static jfieldID field_session; + +static SSL_SESSION *getSslSessionPointer(JNIEnv* env, jobject object) { + SSL_SESSION* session = (SSL_SESSION *)env->GetIntField(object, field_session); + + return session; +} + +/** + * Throws java.io.IOexception with the provided message. + */ +static void throwIOExceptionStr(JNIEnv* env, const char* message) +{ + jclass exClass = env->FindClass("java/io/IOException"); + + if (exClass == NULL) { + LOGE("Unable to find class java/io/IOException"); + } else { + env->ThrowNew(exClass, message); + } +} + +/** + * Gets the peer certificate in the chain and fills a byte array with the + * information therein. + */ +static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeercertificates(JNIEnv* env, + jobject object, jint jssl) +{ + SSL_SESSION *ssl_session; + SSL_CTX* ssl_ctx; + SSL* ssl; + STACK_OF(X509) *chain; + jobjectArray objectArray; + + ssl_session = getSslSessionPointer(env, object); + + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + ssl = SSL_new(ssl_ctx); + + SSL_set_session(ssl, ssl_session); + + chain = SSL_get_peer_cert_chain(ssl); + + objectArray = getcertificatebytes(env, chain); + + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + + return objectArray; +} + +/** + * Gets and returns in a byte array the ID of the actual SSL session. + */ +static jbyteArray org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getid(JNIEnv* env, jobject object) +{ + SSL_SESSION * ssl_session; + jbyteArray bytes; + jbyte *tmp; + + ssl_session = getSslSessionPointer(env, object); + + bytes = env->NewByteArray(ssl_session->session_id_length); + if (bytes != NULL) { + tmp = env->GetByteArrayElements(bytes, NULL); + memcpy(tmp, ssl_session->session_id, ssl_session->session_id_length); + env->ReleaseByteArrayElements(bytes, tmp, 0); + } + + return bytes; +} + +/** + * Gets and returns in a long integer the creation's time of the + * actual SSL session. + */ +static jlong org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getcreationtime(JNIEnv* env, jobject object) +{ + SSL_SESSION * ssl_session; + + ssl_session = getSslSessionPointer(env, object); + + // convert the creation time from seconds to milliseconds + return (jlong)(1000L * ssl_session->time); +} + +/** + * Gets and returns in a string the peer's host's name. + */ +static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerhost(JNIEnv* env, jobject object) +{ + SSL_CTX *ssl_ctx; + SSL *ssl; + SSL_SESSION *ssl_session; + BIO *bio; + char* hostname; + jstring result; + + ssl_session = getSslSessionPointer(env, object); + + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + ssl = SSL_new(ssl_ctx); + + SSL_set_session(ssl, ssl_session); + + bio = SSL_get_rbio(ssl); + + hostname = BIO_get_conn_hostname(bio); + + /* Notice: hostname can be NULL */ + result = env->NewStringUTF(hostname); + + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + + return result; +} + +/** + * Gets and returns in a string the peer's port name (https, ftp, etc.). + */ +static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerport(JNIEnv* env, jobject object) +{ + SSL_CTX *ssl_ctx; + SSL *ssl; + SSL_SESSION *ssl_session; + BIO *bio; + char *port; + jstring result; + + ssl_session = getSslSessionPointer(env, object); + + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + ssl = SSL_new(ssl_ctx); + + SSL_set_session(ssl, ssl_session); + + bio = SSL_get_rbio(ssl); + port = BIO_get_conn_port(bio); + + /* Notice: port name can be NULL */ + result = env->NewStringUTF(port); + + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + + return result; +} + +/** + * Gets and returns in a string the version of the SSL protocol. If it + * returns the string "unknown" it means that no connection is established. + */ +static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getprotocol(JNIEnv* env, jobject object) +{ + SSL_CTX *ssl_ctx; + SSL *ssl; + SSL_SESSION *ssl_session; + const char* protocol; + jstring result; + + ssl_session = getSslSessionPointer(env, object); + + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + ssl = SSL_new(ssl_ctx); + + SSL_set_session(ssl, ssl_session); + + protocol = SSL_get_version(ssl); + + result = env->NewStringUTF(protocol); + + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + + return result; +} + +/** + * Gets and returns in a string the set of ciphers the actual SSL session uses. + */ +static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getciphersuite(JNIEnv* env, jobject object) +{ + SSL_CTX *ssl_ctx; + SSL *ssl; + SSL_SESSION *ssl_session; + + ssl_session = getSslSessionPointer(env, object); + + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + ssl = SSL_new(ssl_ctx); + + SSL_set_session(ssl, ssl_session); + + SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); + jstring result = env->NewStringUTF(SSL_CIPHER_get_name(cipher)); + + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); +} + +/** + * Frees the SSL session. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_free(JNIEnv* env, jobject object, jint session) +{ + LOGD("Freeing OpenSSL session"); + SSL_SESSION* ssl_session; + ssl_session = (SSL_SESSION*) session; + SSL_SESSION_free(ssl_session); +} + +/** + * The actual JNI methods' mapping table for the class OpenSSLSessionImpl. + */ +static JNINativeMethod sMethods[] = +{ + {"nativegetid", "()[B", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getid}, + {"nativegetcreationtime", "()J", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getcreationtime}, + {"nativegetpeerhost", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerhost}, + {"nativegetpeerport", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeerport}, + {"nativegetprotocol", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getprotocol}, + {"nativegetpeercertificates", "()[[B", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_getpeercertificates}, + {"nativefree", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl_free} +}; + +/** + * Register the native methods with JNI for the class OpenSSLSessionImpl. + */ +extern "C" int register_org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl(JNIEnv* env) +{ + int ret; + jclass clazz; + + clazz = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl"); + + if (clazz == NULL) { + LOGE("Can't find org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl"); + return -1; + } + + field_session = env->GetFieldID(clazz, "session", "I"); + + ret = jniRegisterNativeMethods(env, "org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl", + sMethods, NELEM(sMethods)); + + return ret; +} diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp new file mode 100644 index 0000000..b1ce1bc --- /dev/null +++ b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp @@ -0,0 +1,1929 @@ +/* + * Copyright (C) 2007 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. + */ + +#define LOG_TAG "OpenSSLSocketImpl" + +#include <cutils/log.h> +#include <jni.h> +#include <JNIHelp.h> + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/select.h> + +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/ssl.h> + +#include <utils/LogSocket.h> + +#include "org_apache_harmony_xnet_provider_jsse_common.h" + +/** + * Global variable used in module org_apache_harmony_xnet_provider_jsse_common.h. + * It is correctly updated in the function accept(). + */ +int verify_callback_mydata_index = 0; + +/** + * Module scope variables initialized during JNI registration. + */ +static jfieldID field_ssl_ctx; +static jfieldID field_ssl; +static jfieldID field_descriptor; +static jfieldID field_mImpl; +static jfieldID field_mFD; +static jfieldID field_timeout; + +/** + * Gets the chars of a String object as a '\0'-terminated UTF-8 string, + * stored in a freshly-allocated BIO memory buffer. + */ +static BIO *stringToMemBuf(JNIEnv* env, jstring string) { + BIO *result = BIO_new(BIO_s_mem()); + jsize length = env->GetStringUTFLength(string); + char buf[length + 1]; + + env->GetStringUTFRegion(string, 0, env->GetStringLength(string), buf); + buf[length] = '\0'; + + BIO_puts(result, buf); + return result; +} + +/** + * Throws a NullPointerException without any message. + */ +static void throwNullPointerException(JNIEnv* env) { + if (jniThrowException(env, "java/lang/NullPointerException", NULL)) { + LOGE("Unable to throw"); + } +} + +/** + * Throws a RuntimeException with a human-readable error message. + */ +static void throwRuntimeException(JNIEnv* env, const char* message) { + if (jniThrowException(env, "java/lang/RuntimeException", message)) { + LOGE("Unable to throw"); + } +} + +/** + * Throws an SocketTimeoutException with the given string as a message. + */ +static void throwSocketTimeoutException(JNIEnv* env, const char* message) { + if (jniThrowException(env, "java/net/SocketTimeoutException", message)) { + LOGE("Unable to throw"); + } +} + +/** + * Throws an IOException with the given string as a message. + */ +static void throwIOExceptionStr(JNIEnv* env, const char* message) { + if (jniThrowException(env, "java/io/IOException", message)) { + LOGE("Unable to throw"); + } +} + +/** + * Frees the SSL error state. + * + * OpenSSL keeps an "error stack" per thread, and given that this code + * can be called from arbitrary threads that we don't keep track of, + * we err on the side of freeing the error state promptly (instead of, + * say, at thread death). + */ +static void freeSslErrorState(void) { + ERR_clear_error(); + ERR_remove_state(0); +} + +/** + * Throws an IOException with a message constructed from the current + * SSL errors. This will also log the errors. + * + * @param env the JNI environment + * @param sslReturnCode return code from failing SSL function + * @param sslErrorCode error code returned from SSL_get_error() + * @param message null-ok; general error message + */ +static void throwIOExceptionWithSslErrors(JNIEnv* env, int sslReturnCode, + int sslErrorCode, const char* message) { + const char* messageStr = NULL; + char* str; + int ret; + + // First consult the SSL error code for the general message. + switch (sslErrorCode) { + case SSL_ERROR_NONE: + messageStr = "Ok"; + break; + case SSL_ERROR_SSL: + messageStr = "Failure in SSL library, usually a protocol error"; + break; + case SSL_ERROR_WANT_READ: + messageStr = "SSL_ERROR_WANT_READ occured. You should never see this."; + break; + case SSL_ERROR_WANT_WRITE: + messageStr = "SSL_ERROR_WANT_WRITE occured. You should never see this."; + break; + case SSL_ERROR_WANT_X509_LOOKUP: + messageStr = "SSL_ERROR_WANT_X509_LOOKUP occured. You should never see this."; + break; + case SSL_ERROR_SYSCALL: + messageStr = "I/O error during system call"; + break; + case SSL_ERROR_ZERO_RETURN: + messageStr = "SSL_ERROR_ZERO_RETURN occured. You should never see this."; + break; + case SSL_ERROR_WANT_CONNECT: + messageStr = "SSL_ERROR_WANT_CONNECT occured. You should never see this."; + break; + case SSL_ERROR_WANT_ACCEPT: + messageStr = "SSL_ERROR_WANT_ACCEPT occured. You should never see this."; + break; + default: + messageStr = "Unknown SSL error"; + } + + // Prepend either our explicit message or a default one. + if (asprintf(&str, "%s: %s", + (message != NULL) ? message : "SSL error", messageStr) == 0) { + throwIOExceptionStr(env, messageStr); + LOGV("%s", messageStr); + freeSslErrorState(); + return; + } + + char* allocStr = str; + + // For SSL protocol errors, SSL might have more information. + if (sslErrorCode == SSL_ERROR_SSL) { + // Append each error as an additional line to the message. + for (;;) { + char errStr[256]; + const char* file; + int line; + const char* data; + int flags; + unsigned long err = + ERR_get_error_line_data(&file, &line, &data, &flags); + + if (err == 0) { + break; + } + + ERR_error_string_n(err, errStr, sizeof(errStr)); + + ret = asprintf(&str, "%s\n%s (%s:%d %p:0x%08x)", + (allocStr == NULL) ? "" : allocStr, + errStr, + file, + line, + data, + flags); + + if (ret < 0) { + break; + } + + free(allocStr); + allocStr = str; + } + // For errors during system calls, errno might be our friend. + } else if (sslErrorCode == SSL_ERROR_SYSCALL) { + if (asprintf(&str, "%s, %s", allocStr, strerror(errno)) >= 0) { + free(allocStr); + allocStr = str; + } + // If the error code is invalid, print it. + } else if (sslErrorCode > SSL_ERROR_WANT_ACCEPT) { + if (asprintf(&str, ", error code is %d", sslErrorCode) >= 0) { + free(allocStr); + allocStr = str; + } + } + + throwIOExceptionStr(env, allocStr); + + LOGV("%s", allocStr); + free(allocStr); + freeSslErrorState(); +} + +/** + * Helper function that grabs the ssl pointer out of the given object. + * If this function returns NULL and <code>throwIfNull</code> is + * passed as <code>true</code>, then this function will call + * <code>throwIOExceptionStr</code> before returning, so in this case of + * NULL, a caller of this function should simply return and allow JNI + * to do its thing. + * + * @param env non-null; the JNI environment + * @param obj non-null; socket object + * @param throwIfNull whether to throw if the SSL pointer is NULL + * @returns the pointer, which may be NULL + */ +static SSL *getSslPointer(JNIEnv* env, jobject obj, bool throwIfNull) { + SSL *ssl = (SSL *)env->GetIntField(obj, field_ssl); + + if ((ssl == NULL) && throwIfNull) { + throwIOExceptionStr(env, "null SSL pointer"); + } + + return ssl; +} + +// ============================================================================ +// === OpenSSL-related helper stuff begins here. ============================== +// ============================================================================ + +/** + * OpenSSL locking support. Taken from the O'Reilly book by Viega et al., but I + * suppose there are not many other ways to do this on a Linux system (modulo + * isomorphism). + */ +#define MUTEX_TYPE pthread_mutex_t +#define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL) +#define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x)) +#define MUTEX_LOCK(x) pthread_mutex_lock(&(x)) +#define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x)) +#define THREAD_ID pthread_self() +#define THROW_EXCEPTION (-2) +#define THROW_SOCKETTIMEOUTEXCEPTION (-3) + +static MUTEX_TYPE *mutex_buf = NULL; + +static void locking_function(int mode, int n, const char * file, int line) { + if (mode & CRYPTO_LOCK) { + MUTEX_LOCK(mutex_buf[n]); + } else { + MUTEX_UNLOCK(mutex_buf[n]); + } +} + +static unsigned long id_function(void) { + return ((unsigned long)THREAD_ID); +} + +int THREAD_setup(void) { + int i; + + mutex_buf = (MUTEX_TYPE *)malloc(CRYPTO_num_locks( ) * sizeof(MUTEX_TYPE)); + + if(!mutex_buf) { + return 0; + } + + for (i = 0; i < CRYPTO_num_locks( ); i++) { + MUTEX_SETUP(mutex_buf[i]); + } + + CRYPTO_set_id_callback(id_function); + CRYPTO_set_locking_callback(locking_function); + + return 1; +} + +int THREAD_cleanup(void) { + int i; + + if (!mutex_buf) { + return 0; + } + + CRYPTO_set_id_callback(NULL); + CRYPTO_set_locking_callback(NULL); + + for (i = 0; i < CRYPTO_num_locks( ); i++) { + MUTEX_CLEANUP(mutex_buf[i]); + } + + free(mutex_buf); + mutex_buf = NULL; + + return 1; +} + +int get_socket_timeout(int type, int sd) { + struct timeval tv; + socklen_t len = sizeof(tv); + if (getsockopt(sd, SOL_SOCKET, type, &tv, &len) < 0) { + LOGE("getsockopt(%d, SOL_SOCKET): %s (%d)", + sd, + strerror(errno), + errno); + return 0; + } + // LOGI("Current socket timeout (%d(s), %d(us))!", + // (int)tv.tv_sec, (int)tv.tv_usec); + int timeout = tv.tv_sec * 1000 + tv.tv_usec / 1000; + return timeout; +} + +#ifdef TIMEOUT_DEBUG_SSL + +void print_socket_timeout(const char* name, int type, int sd) { + struct timeval tv; + int len = sizeof(tv); + if (getsockopt(sd, SOL_SOCKET, type, &tv, &len) < 0) { + LOGE("getsockopt(%d, SOL_SOCKET, %s): %s (%d)", + sd, + name, + strerror(errno), + errno); + } + LOGI("Current socket %s is (%d(s), %d(us))!", + name, (int)tv.tv_sec, (int)tv.tv_usec); +} + +void print_timeout(const char* method, SSL* ssl) { + LOGI("SSL_get_default_timeout %d in %s", SSL_get_default_timeout(ssl), method); + int fd = SSL_get_fd(ssl); + print_socket_timeout("SO_RCVTIMEO", SO_RCVTIMEO, fd); + print_socket_timeout("SO_SNDTIMEO", SO_SNDTIMEO, fd); +} + +#endif + +/** + * Our additional application data needed for getting synchronization right. + * This maybe warrants a bit of lengthy prose: + * + * (1) We use a flag to reflect whether we consider the SSL connection alive. + * Any read or write attempt loops will be cancelled once this flag becomes 0. + * + * (2) We use an int to count the number of threads that are blocked by the + * underlying socket. This may be at most two (one reader and one writer), since + * the Java layer ensures that no more threads will enter the native code at the + * same time. + * + * (3) The pipe is used primarily as a means of cancelling a blocking select() + * when we want to close the connection (aka "emergency button"). It is also + * necessary for dealing with a possible race condition situation: There might + * be cases where both threads see an SSL_ERROR_WANT_READ or + * SSL_ERROR_WANT_WRITE. Both will enter a select() with the proper argument. + * If one leaves the select() successfully before the other enters it, the + * "success" event is already consumed and the second thread will be blocked, + * possibly forever (depending on network conditions). + * + * The idea for solving the problem looks like this: Whenever a thread is + * successful in moving around data on the network, and it knows there is + * another thread stuck in a select(), it will write a byte to the pipe, waking + * up the other thread. A thread that returned from select(), on the other hand, + * knows whether it's been woken up by the pipe. If so, it will consume the + * byte, and the original state of affairs has been restored. + * + * The pipe may seem like a bit of overhead, but it fits in nicely with the + * other file descriptors of the select(), so there's only one condition to wait + * for. + * + * (4) Finally, a mutex is needed to make sure that at most one thread is in + * either SSL_read() or SSL_write() at any given time. This is an OpenSSL + * requirement. We use the same mutex to guard the field for counting the + * waiting threads. + * + * Note: The current implementation assumes that we don't have to deal with + * problems induced by multiple cores or processors and their respective + * memory caches. One possible problem is that of inconsistent views on the + * "aliveAndKicking" field. This could be worked around by also enclosing all + * accesses to that field inside a lock/unlock sequence of our mutex, but + * currently this seems a bit like overkill. + */ +typedef struct app_data { + int aliveAndKicking; + int waitingThreads; + int fdsEmergency[2]; + MUTEX_TYPE mutex; +} APP_DATA; + +/** + * Creates our application data and attaches it to a given SSL connection. + * + * @param ssl The SSL connection to attach the data to. + * @return 0 on success, -1 on failure. + */ +static int sslCreateAppData(SSL* ssl) { + APP_DATA* data = (APP_DATA*) malloc(sizeof(APP_DATA)); + + memset(data, sizeof(APP_DATA), 0); + + data->aliveAndKicking = 1; + data->waitingThreads = 0; + data->fdsEmergency[0] = -1; + data->fdsEmergency[1] = -1; + + if (pipe(data->fdsEmergency) == -1) { + return -1; + } + + if (MUTEX_SETUP(data->mutex) == -1) { + return -1; + } + + SSL_set_app_data(ssl, (char*) data); + + return 0; +} + +/** + * Destroys our application data, cleaning up everything in the process. + * + * @param ssl The SSL connection to take the data from. + */ +static void sslDestroyAppData(SSL* ssl) { + APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + + if (data != NULL) { + SSL_set_app_data(ssl, NULL); + + data -> aliveAndKicking = 0; + + if (data->fdsEmergency[0] != -1) { + close(data->fdsEmergency[0]); + } + + if (data->fdsEmergency[1] != -1) { + close(data->fdsEmergency[1]); + } + + MUTEX_CLEANUP(data->mutex); + + free(data); + } +} + + +/** + * Frees the SSL_CTX struct for the given instance. + */ +static void free_ssl_ctx(JNIEnv* env, jobject object) { + /* + * Preserve and restore the exception state around this call, so + * that GetIntField and SetIntField will operate without complaint. + */ + jthrowable exception = env->ExceptionOccurred(); + + if (exception != NULL) { + env->ExceptionClear(); + } + + SSL_CTX *ctx = (SSL_CTX *)env->GetIntField(object, field_ssl_ctx); + + if (ctx != NULL) { + SSL_CTX_free(ctx); + env->SetIntField(object, field_ssl_ctx, (int) NULL); + } + + if (exception != NULL) { + env->Throw(exception); + } +} + +/** + * Frees the SSL struct for the given instance. + */ +static void free_ssl(JNIEnv* env, jobject object) { + /* + * Preserve and restore the exception state around this call, so + * that GetIntField and SetIntField will operate without complaint. + */ + jthrowable exception = env->ExceptionOccurred(); + + if (exception != NULL) { + env->ExceptionClear(); + } + + SSL *ssl = (SSL *)env->GetIntField(object, field_ssl); + + if (ssl != NULL) { + sslDestroyAppData(ssl); + SSL_free(ssl); + env->SetIntField(object, field_ssl, (int) NULL); + } + + if (exception != NULL) { + env->Throw(exception); + } +} + +/** + * Constructs the SSL struct for the given instance, replacing one + * that was already made, if any. + */ +static SSL* create_ssl(JNIEnv* env, jobject object, SSL_CTX* ssl_ctx) { + free_ssl(env, object); + + SSL *ssl = SSL_new(ssl_ctx); + env->SetIntField(object, field_ssl, (int) ssl); + return ssl; +} + +/** + * Dark magic helper function that checks, for a given SSL session, whether it + * can SSL_read() or SSL_write() without blocking. Takes into account any + * concurrent attempts to close the SSL session from the Java side. This is + * needed to get rid of the hangs that occur when thread #1 closes the SSLSocket + * while thread #2 is sitting in a blocking read or write. The type argument + * specifies whether we are waiting for readability or writability. It expects + * to be passed either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, since we + * only need to wait in case one of these problems occurs. + * + * @param type Either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE + * @param fd The file descriptor to wait for (the underlying socket) + * @param data The application data structure with mutex info etc. + * @param timeout The timeout value for select call, with the special value + * 0 meaning no timeout at all (wait indefinitely). Note: This is + * the Java semantics of the timeout value, not the usual + * select() semantics. + * @return The result of the inner select() call, -1 on additional errors + */ +static int sslSelect(int type, int fd, APP_DATA *data, int timeout) { + fd_set rfds; + fd_set wfds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + if (type == SSL_ERROR_WANT_READ) { + FD_SET(fd, &rfds); + } else { + FD_SET(fd, &wfds); + } + + FD_SET(data->fdsEmergency[0], &rfds); + + int max = fd > data->fdsEmergency[0] ? fd : data->fdsEmergency[0]; + + // Build a struct for the timeout data if we actually want a timeout. + struct timeval tv; + struct timeval *ptv; + if (timeout > 0) { + tv.tv_sec = timeout / 1000; + tv.tv_usec = 0; + ptv = &tv; + } else { + ptv = NULL; + } + + // LOGD("Doing select() for SSL_ERROR_WANT_%s...", type == SSL_ERROR_WANT_READ ? "READ" : "WRITE"); + int result = select(max + 1, &rfds, &wfds, NULL, ptv); + // LOGD("Returned from select(), result is %d", result); + + // Lock + if (MUTEX_LOCK(data->mutex) == -1) { + return -1; + } + + // If we have been woken up by the emergency pipe, there must be a token in + // it. Thus we can safely read it (even in a blocking way). + if (FD_ISSET(data->fdsEmergency[0], &rfds)) { + char token; + do { + read(data->fdsEmergency[0], &token, 1); + } while (errno == EINTR); + } + + // Tell the world that there is now one thread less waiting for the + // underlying network. + data->waitingThreads--; + + // Unlock + MUTEX_UNLOCK(data->mutex); + // LOGD("leave sslSelect"); + return result; +} + +/** + * Helper function that wakes up a thread blocked in select(), in case there is + * one. Is being called by sslRead() and sslWrite() as well as by JNI glue + * before closing the connection. + * + * @param data The application data structure with mutex info etc. + */ +static void sslNotify(APP_DATA *data) { + // Write a byte to the emergency pipe, so a concurrent select() can return. + // Note we have to restore the errno of the original system call, since the + // caller relies on it for generating error messages. + int errnoBackup = errno; + char token = '*'; + do { + errno = 0; + write(data->fdsEmergency[1], &token, 1); + } while (errno == EINTR); + errno = errnoBackup; +} + +/** + * Helper function which does the actual reading. The Java layer guarantees that + * at most one thread will enter this function at any given time. + * + * @param ssl non-null; the SSL context + * @param buf non-null; buffer to read into + * @param len length of the buffer, in bytes + * @param sslReturnCode original SSL return code + * @param sslErrorCode filled in with the SSL error code in case of error + * @return number of bytes read on success, -1 if the connection was + * cleanly shut down, or THROW_EXCEPTION if an exception should be thrown. + */ +static int sslRead(SSL* ssl, char* buf, jint len, int* sslReturnCode, + int* sslErrorCode, int timeout) { + + // LOGD("Entering sslRead, caller requests to read %d bytes...", len); + + if (len == 0) { + // Don't bother doing anything in this case. + return 0; + } + + int fd = SSL_get_fd(ssl); + BIO *bio = SSL_get_rbio(ssl); + + APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + + while (data->aliveAndKicking) { + errno = 0; + + // Lock + if (MUTEX_LOCK(data->mutex) == -1) { + return -1; + } + + unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio); + + // LOGD("Doing SSL_Read()"); + int result = SSL_read(ssl, buf, len); + int error = SSL_get_error(ssl, result); + freeSslErrorState(); + // LOGD("Returned from SSL_Read() with result %d, error code %d", result, error); + + // If we have been successful in moving data around, check whether it + // might make sense to wake up other blocked threads, so they can give + // it a try, too. + if (BIO_number_read(bio) + BIO_number_written(bio) != bytesMoved && data->waitingThreads > 0) { + sslNotify(data); + } + + // If we are blocked by the underlying socket, tell the world that + // there will be one more waiting thread now. + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { + data->waitingThreads++; + } + + // Unlock + MUTEX_UNLOCK(data->mutex); + + switch (error) { + // Sucessfully read at least one byte. + case SSL_ERROR_NONE: { + add_recv_stats(fd, result); + return result; + } + + // Read zero bytes. End of stream reached. + case SSL_ERROR_ZERO_RETURN: { + return -1; + } + + // Need to wait for availability of underlying layer, then retry. + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: { + int selectResult = sslSelect(error, fd, data, timeout); + if (selectResult == -1) { + *sslReturnCode = -1; + *sslErrorCode = error; + return THROW_EXCEPTION; + } else if (selectResult == 0) { + return THROW_SOCKETTIMEOUTEXCEPTION; + } + + break; + } + + // A problem occured during a system call, but this is not + // necessarily an error. + case SSL_ERROR_SYSCALL: { + // Connection closed without proper shutdown. Tell caller we + // have reached end-of-stream. + if (result == 0) { + return -1; + } + + // System call has been interrupted. Simply retry. + if (errno == EINTR) { + break; + } + + // Note that for all other system call errors we fall through + // to the default case, which results in an Exception. + } + + // Everything else is basically an error. + default: { + *sslReturnCode = result; + *sslErrorCode = error; + return THROW_EXCEPTION; + } + } + } + + return -1; +} + +/** + * Helper function which does the actual writing. The Java layer guarantees that + * at most one thread will enter this function at any given time. + * + * @param ssl non-null; the SSL context + * @param buf non-null; buffer to write + * @param len length of the buffer, in bytes + * @param sslReturnCode original SSL return code + * @param sslErrorCode filled in with the SSL error code in case of error + * @return number of bytes read on success, -1 if the connection was + * cleanly shut down, or THROW_EXCEPTION if an exception should be thrown. + */ +static int sslWrite(SSL* ssl, const char* buf, jint len, int* sslReturnCode, + int* sslErrorCode) { + + // LOGD("Entering sslWrite(), caller requests to write %d bytes...", len); + + if (len == 0) { + // Don't bother doing anything in this case. + return 0; + } + + int fd = SSL_get_fd(ssl); + BIO *bio = SSL_get_wbio(ssl); + + APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + + int count = len; + + while(data->aliveAndKicking && len > 0) { + errno = 0; + if (MUTEX_LOCK(data->mutex) == -1) { + return -1; + } + + unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio); + + // LOGD("Doing SSL_write() with %d bytes to go", len); + int result = SSL_write(ssl, buf, len); + int error = SSL_get_error(ssl, result); + freeSslErrorState(); + // LOGD("Returned from SSL_write() with result %d, error code %d", result, error); + + // If we have been successful in moving data around, check whether it + // might make sense to wake up other blocked threads, so they can give + // it a try, too. + if (BIO_number_read(bio) + BIO_number_written(bio) != bytesMoved && data->waitingThreads > 0) { + sslNotify(data); + } + + // If we are blocked by the underlying socket, tell the world that + // there will be one more waiting thread now. + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { + data->waitingThreads++; + } + + MUTEX_UNLOCK(data->mutex); + + switch (error) { + // Sucessfully write at least one byte. + case SSL_ERROR_NONE: { + buf += result; + len -= result; + break; + } + + // Wrote zero bytes. End of stream reached. + case SSL_ERROR_ZERO_RETURN: { + return -1; + } + + // Need to wait for availability of underlying layer, then retry. + // The concept of a write timeout doesn't really make sense, and + // it's also not standard Java behavior, so we wait forever here. + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: { + int selectResult = sslSelect(error, fd, data, 0); + if (selectResult == -1) { + *sslReturnCode = -1; + *sslErrorCode = error; + return THROW_EXCEPTION; + } else if (selectResult == 0) { + return THROW_SOCKETTIMEOUTEXCEPTION; + } + + break; + } + + // An problem occured during a system call, but this is not + // necessarily an error. + case SSL_ERROR_SYSCALL: { + // Connection closed without proper shutdown. Tell caller we + // have reached end-of-stream. + if (result == 0) { + return -1; + } + + // System call has been interrupted. Simply retry. + if (errno == EINTR) { + break; + } + + // Note that for all other system call errors we fall through + // to the default case, which results in an Exception. + } + + // Everything else is basically an error. + default: { + *sslReturnCode = result; + *sslErrorCode = error; + return THROW_EXCEPTION; + } + } + } + add_send_stats(fd, count); + // LOGD("Successfully wrote %d bytes", count); + + return count; +} + +/** + * Helper function that creates an RSA public key from two buffers containing + * the big-endian bit representation of the modulus and the public exponent. + * + * @param mod The data of the modulus + * @param modLen The length of the modulus data + * @param exp The data of the exponent + * @param expLen The length of the exponent data + * + * @return A pointer to the new RSA structure, or NULL on error + */ +static RSA* rsaCreateKey(unsigned char* mod, int modLen, unsigned char* exp, int expLen) { + // LOGD("Entering rsaCreateKey()"); + + RSA* rsa = RSA_new(); + + rsa->n = BN_bin2bn((unsigned char*) mod, modLen, NULL); + rsa->e = BN_bin2bn((unsigned char*) exp, expLen, NULL); + + if (rsa->n == NULL || rsa->e == NULL) { + RSA_free(rsa); + return NULL; + } + + return rsa; +} + +/** + * Helper function that frees an RSA key. Just calls the corresponding OpenSSL + * function. + * + * @param rsa The pointer to the new RSA structure to free. + */ +static void rsaFreeKey(RSA* rsa) { + // LOGD("Entering rsaFreeKey()"); + + if (rsa != NULL) { + RSA_free(rsa); + } +} + +/** + * Helper function that verifies a given RSA signature for a given message. + * + * @param msg The message to verify + * @param msgLen The length of the message + * @param sig The signature to verify + * @param sigLen The length of the signature + * @param algorithm The name of the hash/sign algorithm to use, e.g. "RSA-SHA1" + * @param rsa The RSA public key to use + * + * @return 1 on success, 0 on failure, -1 on error (check SSL errors then) + * + */ +static int rsaVerify(unsigned char* msg, unsigned int msgLen, unsigned char* sig, + unsigned int sigLen, char* algorithm, RSA* rsa) { + + // LOGD("Entering rsaVerify(%x, %d, %x, %d, %s, %x)", msg, msgLen, sig, sigLen, algorithm, rsa); + + int result = -1; + + EVP_PKEY* key = EVP_PKEY_new(); + EVP_PKEY_set1_RSA(key, rsa); + + const EVP_MD *type = EVP_get_digestbyname(algorithm); + if (type == NULL) { + goto cleanup; + } + + EVP_MD_CTX ctx; + + EVP_MD_CTX_init(&ctx); + if (EVP_VerifyInit_ex(&ctx, type, NULL) == 0) { + goto cleanup; + } + + EVP_VerifyUpdate(&ctx, msg, msgLen); + result = EVP_VerifyFinal(&ctx, sig, sigLen, key); + EVP_MD_CTX_cleanup(&ctx); + + cleanup: + + if (key != NULL) { + EVP_PKEY_free(key); + } + + return result; +} + +// ============================================================================ +// === OpenSSL-related helper stuff ends here. JNI glue follows. ============== +// ============================================================================ + +/** + * Initialization phase for every OpenSSL job: Loads the Error strings, the + * crypto algorithms and reset the OpenSSL library + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_initstatic(JNIEnv* env, jobject obj) +{ + SSL_load_error_strings(); + ERR_load_crypto_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + THREAD_setup(); +} + +/** + * Initialization phase for a socket with OpenSSL. The server's private key + * and X509 certificate are read and the Linux /dev/urandom file is loaded + * as RNG for the session keys. + * + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_init(JNIEnv* env, jobject object, + jstring privatekey, jstring certificates, jbyteArray seed) +{ + SSL_CTX* ssl_ctx; + + // 'seed == null' when no SecureRandom Object is set + // in the SSLContext. + if (seed != NULL) { + jboolean iscopy = JNI_FALSE; + jbyte* randseed = env->GetByteArrayElements(seed, &iscopy); + RAND_seed((unsigned char*) randseed, 1024); + } else { + RAND_load_file("/dev/urandom", 1024); + } + + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + + // Note: We explicitly do not allow SSLv2 to be used. It + SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); + + /* Java code in class OpenSSLSocketImpl does the verification. Meaning of + * SSL_VERIFY_NONE flag in client mode: if not using an anonymous cipher + * (by default disabled), the server will send a certificate which will + * be checked. The result of the certificate verification process can be + * checked after the TLS/SSL handshake using the SSL_get_verify_result(3) + * function. The handshake will be continued regardless of the + * verification result. + */ + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); + + if (privatekey != NULL) { + BIO* privatekeybio = stringToMemBuf(env, (jstring) privatekey); + EVP_PKEY* privatekeyevp = + PEM_read_bio_PrivateKey(privatekeybio, NULL, 0, NULL); + BIO_free(privatekeybio); + + if (privatekeyevp == NULL) { + throwIOExceptionWithSslErrors(env, 0, 0, + "Error parsing the private key"); + SSL_CTX_free(ssl_ctx); + return; + } + + BIO* certificatesbio = stringToMemBuf(env, (jstring) certificates); + X509* certificatesx509 = + PEM_read_bio_X509(certificatesbio, NULL, 0, NULL); + BIO_free(certificatesbio); + + if (certificatesx509 == NULL) { + throwIOExceptionWithSslErrors(env, 0, 0, + "Error parsing the certificates"); + EVP_PKEY_free(privatekeyevp); + SSL_CTX_free(ssl_ctx); + return; + } + + int ret = SSL_CTX_use_certificate(ssl_ctx, certificatesx509); + if (ret != 1) { + throwIOExceptionWithSslErrors(env, ret, 0, + "Error setting the certificates"); + X509_free(certificatesx509); + EVP_PKEY_free(privatekeyevp); + SSL_CTX_free(ssl_ctx); + return; + } + + ret = SSL_CTX_use_PrivateKey(ssl_ctx, privatekeyevp); + if (ret != 1) { + throwIOExceptionWithSslErrors(env, ret, 0, + "Error setting the private key"); + X509_free(certificatesx509); + EVP_PKEY_free(privatekeyevp); + SSL_CTX_free(ssl_ctx); + return; + } + + ret = SSL_CTX_check_private_key(ssl_ctx); + if (ret != 1) { + throwIOExceptionWithSslErrors(env, ret, 0, + "Error checking the private key"); + X509_free(certificatesx509); + EVP_PKEY_free(privatekeyevp); + SSL_CTX_free(ssl_ctx); + return; + } + } + + env->SetIntField(object, field_ssl_ctx, (int)ssl_ctx); +} + +/** + * A connection within an OpenSSL context is established. (1) A new socket is + * constructed, (2) the TLS/SSL handshake with a server is initiated. + */ +static jboolean org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_connect(JNIEnv* env, jobject object, + jint ctx, jobject socketObject, jboolean client_mode, jint session) +{ + // LOGD("ENTER connect"); + int ret, fd; + SSL_CTX* ssl_ctx; + SSL* ssl; + SSL_SESSION* ssl_session; + + ssl_ctx = (SSL_CTX*)env->GetIntField(object, field_ssl_ctx); + + ssl = create_ssl(env, object, ssl_ctx); + if (ssl == NULL) { + throwIOExceptionWithSslErrors(env, 0, 0, + "Unable to create SSL structure"); + free_ssl_ctx(env, object); + return (jboolean) false; + } + + jobject socketImplObject = env->GetObjectField(socketObject, field_mImpl); + if (socketImplObject == NULL) { + free_ssl(env, object); + free_ssl_ctx(env, object); + throwIOExceptionStr(env, + "couldn't get the socket impl from the socket"); + return (jboolean) false; + } + + jobject fdObject = env->GetObjectField(socketImplObject, field_mFD); + if (fdObject == NULL) { + free_ssl(env, object); + free_ssl_ctx(env, object); + throwIOExceptionStr(env, + "couldn't get the file descriptor from the socket impl"); + return (jboolean) false; + } + + fd = jniGetFDFromFileDescriptor(env, fdObject); + + /* + * Turn on "partial write" mode. This means that SSL_write() will + * behave like Posix write() and possibly return after only + * writing a partial buffer. Note: The alternative, perhaps + * surprisingly, is not that SSL_write() always does full writes + * but that it will force you to retry write calls having + * preserved the full state of the original call. (This is icky + * and undesirable.) + */ + SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); + + ssl_session = (SSL_SESSION *) session; + + ret = SSL_set_fd(ssl, fd); + + if (ret != 1) { + throwIOExceptionWithSslErrors(env, ret, 0, + "Error setting the file descriptor"); + free_ssl(env, object); + free_ssl_ctx(env, object); + return (jboolean) false; + } + + if (ssl_session != NULL) { + ret = SSL_set_session(ssl, ssl_session); + + if (ret != 1) { + /* + * Translate the error, and throw if it turns out to be a real + * problem. + */ + int sslErrorCode = SSL_get_error(ssl, ret); + if (sslErrorCode != SSL_ERROR_ZERO_RETURN) { + throwIOExceptionWithSslErrors(env, ret, sslErrorCode, + "SSL session set"); + free_ssl(env, object); + free_ssl_ctx(env, object); + return (jboolean) false; + } + } + } + + /* + * Make socket non-blocking, so SSL_connect SSL_read() and SSL_write() don't hang + * forever and we can use select() to find out if the socket is ready. + */ + int mode = fcntl(fd, F_GETFL); + if (mode == -1 || fcntl(fd, F_SETFL, mode | O_NONBLOCK) == -1) { + throwIOExceptionStr(env, "Unable to make socket non blocking"); + free_ssl(env, object); + free_ssl_ctx(env, object); + return (jboolean) false; + } + + /* + * Create our special application data. + */ + if (sslCreateAppData(ssl) == -1) { + throwIOExceptionStr(env, "Unable to create application data"); + free_ssl(env, object); + free_ssl_ctx(env, object); + // TODO + return (jboolean) false; + } + + APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + env->SetIntField(object, field_ssl, (int)ssl); + + int timeout = (int)env->GetIntField(object, field_timeout); + + while (data->aliveAndKicking) { + errno = 0; + ret = SSL_connect(ssl); + if (ret == 1) { + break; + } else if (errno == EINTR) { + continue; + } else { + // LOGD("SSL_connect: result %d, errno %d, timeout %d", ret, errno, timeout); + int error = SSL_get_error(ssl, ret); + + /* + * If SSL_connect doesn't succeed due to the socket being + * either unreadable or unwritable, we use sslSelect to + * wait for it to become ready. If that doesn't happen + * before the specified timeout or an error occurs, we + * cancel the handshake. Otherwise we try the SSL_connect + * again. + */ + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { + data->waitingThreads++; + int selectResult = sslSelect(error, fd, data, timeout); + + if (selectResult == -1) { + throwIOExceptionWithSslErrors(env, -1, error, + "Connect error"); + free_ssl(env, object); + free_ssl_ctx(env, object); + return (jboolean) false; + } else if (selectResult == 0) { + throwSocketTimeoutException(env, "SSL handshake timed out"); + freeSslErrorState(); + free_ssl(env, object); + free_ssl_ctx(env, object); + return (jboolean) false; + } + } else { + LOGE("Unknown error %d during connect", error); + break; + } + } + } + + if (ret != 1) { + /* + * Translate the error, and throw if it turns out to be a real + * problem. + */ + int sslErrorCode = SSL_get_error(ssl, ret); + if (sslErrorCode != SSL_ERROR_ZERO_RETURN) { + throwIOExceptionWithSslErrors(env, ret, sslErrorCode, + "SSL handshake failure"); + free_ssl(env, object); + free_ssl_ctx(env, object); + return (jboolean) false; + } + } + + if (ssl_session != NULL) { + ret = SSL_session_reused(ssl); + // if (ret == 1) LOGD("A session was reused"); + // else LOGD("A new session was negotiated"); + return (jboolean) ret; + } else { + // LOGD("A new session was negotiated"); + return (jboolean) 0; + } + // LOGD("LEAVE connect"); +} + +static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession(JNIEnv* env, jobject object, + jint jssl) +{ + return (jint) SSL_get1_session((SSL *) jssl); +} + +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept(JNIEnv* env, jobject object, + jobject socketObject, jint jssl_ctx, jboolean client_mode) +{ + int sd, ret; + BIO *bio; + SSL *ssl; + SSL_CTX *ssl_ctx; + mydata_t mydata; + + char name[] = "mydata index"; + + ssl_ctx = (SSL_CTX *)jssl_ctx; + + ssl = create_ssl(env, object, ssl_ctx); + if (ssl == NULL) { + throwIOExceptionWithSslErrors(env, 0, 0, + "Unable to create SSL structure"); + return; + } + + jobject socketImplObject = env->GetObjectField(socketObject, field_mImpl); + if (socketImplObject == NULL) { + free_ssl(env, object); + throwIOExceptionStr(env, "couldn't get the socket impl from the socket"); + return; + } + + jobject fdObject = env->GetObjectField(socketImplObject, field_mFD); + if (fdObject == NULL) { + free_ssl(env, object); + throwIOExceptionStr(env, "couldn't get the file descriptor from the socket impl"); + return; + } + + + sd = jniGetFDFromFileDescriptor(env, fdObject); + + bio = BIO_new_socket(sd, BIO_NOCLOSE); + + /* The parameter client_mode must be 1 */ + if (client_mode != 0) + client_mode = 1; + BIO_set_ssl_mode(bio, client_mode); + + SSL_set_bio(ssl, bio, bio); + + /* Call to "register" some new application specific data. It takes three + * optional function pointers which are called when the parent structure + * (in this case an RSA structure) is initially created, when it is copied + * and when it is freed up. If any or all of these function pointer + * arguments are not used they should be set to NULL. Here we simply + * register a dummy callback application with the index 0. + */ + verify_callback_mydata_index = SSL_get_ex_new_index(0, name, NULL, NULL, NULL); + + /* Fill in the mydata structure */ + mydata.env = env; + mydata.object = object; + SSL_set_ex_data(ssl, verify_callback_mydata_index, &mydata); + + ret = SSL_accept(ssl); + + if (ret < 1) { + /* + * Translate the error, and throw if it turns out to be a real + * problem. + */ + int sslErrorCode = SSL_get_error(ssl, ret); + if (sslErrorCode != SSL_ERROR_ZERO_RETURN) { + throwIOExceptionWithSslErrors(env, ret, sslErrorCode, + "Trouble accepting connection"); + free_ssl(env, object); + } + } + + /* + * Make socket non-blocking, so SSL_read() and SSL_write() don't hang + * forever and we can use select() to find out if the socket is ready. + */ + int fd = SSL_get_fd(ssl); + int mode = fcntl(fd, F_GETFL); + if (mode == -1 || fcntl(fd, F_SETFL, mode | O_NONBLOCK) == -1) { + throwIOExceptionStr(env, "Unable to make socket non blocking"); + free_ssl(env, object); + return; + } + + /* + * Create our special application data. + */ + if (sslCreateAppData(ssl) == -1) { + throwIOExceptionStr(env, "Unable to create application data"); + free_ssl(env, object); + return; + } +} + +/** + * Loads the desired protocol for the OpenSSL client and enables it. + * For example SSL_OP_NO_TLSv1 means do not use TLS v. 1. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_setenabledprotocols(JNIEnv* env, jobject object, + jlong protocol) +{ + if (protocol != 0x00000000L) { + if (protocol & SSL_OP_NO_SSLv3) + LOGD("SSL_OP_NO_SSLv3 is set"); + if (protocol & SSL_OP_NO_TLSv1) + LOGD("SSL_OP_NO_TLSv1 is set"); + + SSL_CTX* ctx = (SSL_CTX*)env->GetIntField(object, field_ssl_ctx); + int options = SSL_CTX_get_options(ctx); + options |= protocol; // Note: SSLv2 disabled earlier. + SSL_CTX_set_options(ctx, options); + } +} + +/** + * Loads the ciphers suites that are supported by the OpenSSL client + * and returns them in a string array. + */ +static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsupportedciphersuites(JNIEnv* env, + jobject object) +{ + SSL_CTX* ssl_ctx; + SSL* ssl; + jobjectArray ret; + int i; + const char *c; + + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + + if (ssl_ctx == NULL) { + return NULL; + } + + ssl = SSL_new(ssl_ctx); + + if (ssl == NULL) { + SSL_CTX_free(ssl_ctx); + return NULL; + } + + i = 0; + while (SSL_get_cipher_list(ssl,i) != NULL) { + i++; + } + + ret = (jobjectArray)env->NewObjectArray(i, + env->FindClass("java/lang/String"), + env->NewStringUTF("")); + + for (i=0; ; i++) { + c=SSL_get_cipher_list(ssl,i); + if (c == NULL) break; + + env->SetObjectArrayElement(ret,i,env->NewStringUTF(c)); + } + + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + + return ret; +} + +/** + * Loads the ciphers suites that are enabled in the OpenSSL client + * and returns them in a string array. + */ +static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getenabledciphersuites(JNIEnv* env, + jobject object) +{ + SSL_CTX* ssl_ctx; + SSL* ssl; + jobjectArray ret; + int i; + const char *c; + + ssl = getSslPointer(env, object, false); + if (ssl == NULL) { + ssl_ctx = (SSL_CTX*)env->GetIntField(object, field_ssl_ctx); + ssl = SSL_new(ssl_ctx); + env->SetIntField(object, field_ssl, (int)ssl); + } + + i = 0; + while (SSL_get_cipher_list(ssl,i) != NULL) { + i++; + } + + ret = (jobjectArray)env->NewObjectArray(i, + env->FindClass("java/lang/String"), + env->NewStringUTF("")); + + for (i = 0; ; i++) { + c = SSL_get_cipher_list(ssl,i); + if (c == NULL) break; + + env->SetObjectArrayElement(ret,i,env->NewStringUTF(c)); + } + + return ret; +} + +/** + * Sets the ciphers suites that are enabled in the OpenSSL client. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_setenabledciphersuites(JNIEnv* env, jobject object, + jstring controlstring) +{ + SSL_CTX* ctx; + const char *str; + int ret; + + ctx = (SSL_CTX*)env->GetIntField(object, field_ssl_ctx); + str = env->GetStringUTFChars(controlstring, 0); + ret = SSL_CTX_set_cipher_list(ctx, str); + + if (ret == 0) { + freeSslErrorState(); + jclass exClass = env->FindClass("java/lang/IllegalArgumentException"); + env->ThrowNew(exClass, "Illegal cipher suite strings."); + } +} + +#define SSL_AUTH_MASK 0x00007F00L +#define SSL_aRSA 0x00000100L /* Authenticate with RSA */ +#define SSL_aDSS 0x00000200L /* Authenticate with DSS */ +#define SSL_DSS SSL_aDSS +#define SSL_aFZA 0x00000400L +#define SSL_aNULL 0x00000800L /* no Authenticate, ADH */ +#define SSL_aDH 0x00001000L /* no Authenticate, ADH */ +#define SSL_aKRB5 0x00002000L /* Authenticate with KRB5 */ +#define SSL_aECDSA 0x00004000L /* Authenticate with ECDSA */ + +/** + * Sets the client's crypto algorithms and authentication methods. + */ +static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod(JNIEnv* env, + jobject object) +{ + SSL* ssl; + SSL_CIPHER *cipher; + jstring ret; + char buf[512]; + unsigned long alg; + const char *au; + + ssl = getSslPointer(env, object, true); + if (ssl == NULL) { + return NULL; + } + + cipher = SSL_get_current_cipher(ssl); + + alg = cipher->algorithms; + + switch (alg&SSL_AUTH_MASK) { + case SSL_aRSA: + au="RSA"; + break; + case SSL_aDSS: + au="DSS"; + break; + case SSL_aDH: + au="DH"; + break; + case SSL_aFZA: + au = "FZA"; + break; + case SSL_aNULL: + au="None"; + break; + case SSL_aECDSA: + au="ECDSA"; + break; + default: + au="unknown"; + break; + } + + ret = env->NewStringUTF(au); + + return ret; +} + +/** + * OpenSSL read function (1): only one chunk is read (returned as jint). + */ +static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read(JNIEnv* env, jobject object, jint timeout) +{ + SSL *ssl = getSslPointer(env, object, true); + if (ssl == NULL) { + return 0; + } + + unsigned char byteRead; + int returnCode = 0; + int errorCode = 0; + + int ret = sslRead(ssl, (char *) &byteRead, 1, &returnCode, &errorCode, timeout); + + switch (ret) { + case THROW_EXCEPTION: + // See sslRead() regarding improper failure to handle normal cases. + throwIOExceptionWithSslErrors(env, returnCode, errorCode, + "Read error"); + return -1; + case THROW_SOCKETTIMEOUTEXCEPTION: + throwSocketTimeoutException(env, "Read timed out"); + return -1; + case -1: + // Propagate EOF upwards. + return -1; + default: + // Return the actual char read, make sure it stays 8 bits wide. + return ((jint) byteRead) & 0xFF; + } +} + +/** + * OpenSSL read function (2): read into buffer at offset n chunks. + * Returns 1 (success) or value <= 0 (failure). + */ +static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba(JNIEnv* env, jobject obj, jbyteArray dest, jint offset, jint len, jint timeout) +{ + SSL *ssl = getSslPointer(env, obj, true); + if (ssl == NULL) { + return 0; + } + + jbyte* bytes = env->GetByteArrayElements(dest, NULL); + int returnCode = 0; + int errorCode = 0; + + int ret = + sslRead(ssl, (char*) (bytes + offset), len, &returnCode, &errorCode, timeout); + + env->ReleaseByteArrayElements(dest, bytes, 0); + + if (ret == THROW_EXCEPTION) { + // See sslRead() regarding improper failure to handle normal cases. + throwIOExceptionWithSslErrors(env, returnCode, errorCode, + "Read error"); + return -1; + } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { + throwSocketTimeoutException(env, "Read timed out"); + return -1; + } + + return ret; +} + +/** + * OpenSSL write function (1): only one chunk is written. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write(JNIEnv* env, jobject object, jint b) +{ + SSL *ssl = getSslPointer(env, object, true); + if (ssl == NULL) { + return; + } + + int returnCode = 0; + int errorCode = 0; + char buf[1] = { (char) b }; + int ret = sslWrite(ssl, buf, 1, &returnCode, &errorCode); + + if (ret == THROW_EXCEPTION) { + // See sslWrite() regarding improper failure to handle normal cases. + throwIOExceptionWithSslErrors(env, returnCode, errorCode, + "Write error"); + } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { + throwSocketTimeoutException(env, "Write timed out"); + } +} + +/** + * OpenSSL write function (2): write into buffer at offset n chunks. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba(JNIEnv* env, jobject obj, + jbyteArray dest, jint offset, jint len) +{ + SSL *ssl = getSslPointer(env, obj, true); + if (ssl == NULL) { + return; + } + + jbyte* bytes = env->GetByteArrayElements(dest, NULL); + int returnCode = 0; + int errorCode = 0; + int timeout = (int)env->GetIntField(obj, field_timeout); + int ret = sslWrite(ssl, (const char *) (bytes + offset), len, + &returnCode, &errorCode); + + env->ReleaseByteArrayElements(dest, bytes, 0); + + if (ret == THROW_EXCEPTION) { + // See sslWrite() regarding improper failure to handle normal cases. + throwIOExceptionWithSslErrors(env, returnCode, errorCode, + "Write error"); + } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { + throwSocketTimeoutException(env, "Write timed out"); + } +} + +/** + * Interrupt any pending IO before closing the socket. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt( + JNIEnv* env, jobject object) { + SSL *ssl = getSslPointer(env, object, false); + if (ssl == NULL) { + return; + } + + /* + * Mark the connection as quasi-dead, then send something to the emergency + * file descriptor, so any blocking select() calls are woken up. + */ + APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + if (data != NULL) { + data->aliveAndKicking = 0; + + // At most two threads can be waiting. + sslNotify(data); + sslNotify(data); + } +} + +/** + * OpenSSL close SSL socket function. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close( + JNIEnv* env, jobject object) { + SSL *ssl = getSslPointer(env, object, false); + if (ssl == NULL) { + return; + } + + /* + * Try to make socket blocking again. OpenSSL literature recommends this. + */ + int fd = SSL_get_fd(ssl); + if (fd != -1) { + int mode = fcntl(fd, F_GETFL); + if (mode == -1 || fcntl(fd, F_SETFL, mode & ~O_NONBLOCK) == -1) { +// throwIOExceptionStr(env, "Unable to make socket blocking again"); +// LOGW("Unable to make socket blocking again"); + } + } + + int ret = SSL_shutdown(ssl); + switch (ret) { + case 0: + /* + * Shutdown was not successful (yet), but there also + * is no error. Since we can't know whether the remote + * server is actually still there, and we don't want to + * get stuck forever in a second SSL_shutdown() call, we + * simply return. This is not security a problem as long + * as we close the underlying socket, which we actually + * do, because that's where we are just coming from. + */ + break; + case 1: + /* + * Shutdown was sucessful. We can safely return. Hooray! + */ + break; + default: + /* + * Everything else is a real error condition. We should + * let the Java layer know about this by throwing an + * exception. + */ + throwIOExceptionWithSslErrors(env, ret, 0, "SSL shutdown failed."); + break; + } + + freeSslErrorState(); + free_ssl(env, object); + free_ssl_ctx(env, object); +} + +/** + * OpenSSL free SSL socket function. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_free(JNIEnv* env, jobject object) +{ + free_ssl(env, object); + free_ssl_ctx(env, object); +} + +/** + * Verifies an RSA signature. + */ +static int org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_verifysignature(JNIEnv* env, jclass clazz, + jbyteArray msg, jbyteArray sig, jstring algorithm, jbyteArray mod, jbyteArray exp) { + + // LOGD("Entering verifysignature()"); + + if (msg == NULL || sig == NULL || algorithm == NULL || mod == NULL || exp == NULL) { + throwNullPointerException(env); + return -1; + } + + int result = -1; + + jbyte* msgBytes = env->GetByteArrayElements(msg, NULL); + jint msgLength = env->GetArrayLength(msg); + + jbyte* sigBytes = env->GetByteArrayElements(sig, NULL); + jint sigLength = env->GetArrayLength(sig); + + jbyte* modBytes = env->GetByteArrayElements(mod, NULL); + jint modLength = env->GetArrayLength(mod); + + jbyte* expBytes = env->GetByteArrayElements(exp, NULL); + jint expLength = env->GetArrayLength(exp); + + const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL); + + RSA* rsa = rsaCreateKey((unsigned char*) modBytes, modLength, (unsigned char*) expBytes, expLength); + if (rsa != NULL) { + result = rsaVerify((unsigned char*) msgBytes, msgLength, (unsigned char*) sigBytes, sigLength, + (char*) algorithmChars, rsa); + rsaFreeKey(rsa); + } + + env->ReleaseStringUTFChars(algorithm, algorithmChars); + + env->ReleaseByteArrayElements(exp, expBytes, JNI_ABORT); + env->ReleaseByteArrayElements(mod, modBytes, JNI_ABORT); + env->ReleaseByteArrayElements(sig, sigBytes, JNI_ABORT); + env->ReleaseByteArrayElements(msg, msgBytes, JNI_ABORT); + + if (result == -1) { + int error = ERR_get_error(); + if (error != 0) { + char message[50]; + ERR_error_string_n(error, message, sizeof(message)); + throwRuntimeException(env, message); + } else { + throwRuntimeException(env, "Internal error during verification"); + } + freeSslErrorState(); + } + + return result; +} + +/** + * The actual JNI methods' mapping table for the class OpenSSLSocketImpl. + */ +static JNINativeMethod sMethods[] = +{ + {"nativeinitstatic", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_initstatic}, + {"nativeinit", "(Ljava/lang/String;Ljava/lang/String;[B)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_init}, + {"nativeconnect", "(ILjava/net/Socket;ZI)Z", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_connect}, + {"nativegetsslsession", "(I)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession}, + {"nativeread", "(I)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read}, + {"nativeread", "([BIII)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba}, + {"nativewrite", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write}, + {"nativewrite", "([BII)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba}, + {"nativeaccept", "(Ljava/net/Socket;IZ)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept}, + {"nativesetenabledprotocols", "(J)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_setenabledprotocols}, + {"nativegetsupportedciphersuites", "()[Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsupportedciphersuites}, + {"nativegetenabledciphersuites", "()[Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getenabledciphersuites}, + {"nativesetenabledciphersuites", "(Ljava/lang/String;)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_setenabledciphersuites}, + {"nativecipherauthenticationmethod", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod}, + {"nativeinterrupt", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt}, + {"nativeclose", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close}, + {"nativefree", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_free}, + {"nativeverifysignature", "([B[BLjava/lang/String;[B[B)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_verifysignature}, +}; + +/** + * Register the native methods with JNI for the class OpenSSLSocketImpl. + */ +extern "C" int register_org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl(JNIEnv* env) +{ + int ret; + jclass clazz; + + clazz = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl"); + + if (clazz == NULL) { + LOGE("Can't find org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl"); + return -1; + } + + jclass socketClass = env->FindClass("java/net/Socket"); + + if (socketClass == NULL) { + LOGE("Can't find class java.net.Socket"); + return -1; + } + + field_mImpl = env->GetFieldID(socketClass, "impl", "Ljava/net/SocketImpl;"); + + if (field_mImpl == NULL) { + LOGE("Can't find field impl in class java.net.Socket"); + return -1; + } + + jclass socketImplClass = env->FindClass("java/net/SocketImpl"); + + if(socketImplClass == NULL) { + LOGE("Can't find class java.net.SocketImpl"); + return -1; + } + + field_mFD = env->GetFieldID(socketImplClass, "fd", "Ljava/io/FileDescriptor;"); + + if (field_mFD == NULL) { + LOGE("Can't find field fd in java.net.SocketImpl"); + return -1; + } + + jclass fdclazz = env->FindClass("java/io/FileDescriptor"); + + if (fdclazz == NULL) + { + LOGE("Can't find java/io/FileDescriptor"); + return -1; + } + + field_descriptor = env->GetFieldID(fdclazz, "descriptor", "I"); + + if (field_descriptor == NULL) { + LOGE("Can't find FileDescriptor.descriptor"); + return -1; + } + + ret = jniRegisterNativeMethods(env, "org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl", + sMethods, NELEM(sMethods)); + + if (ret >= 0) { + // Note: do these after the registration of native methods, because + // there is a static method "initstatic" that's called when the + // OpenSSLSocketImpl class is first loaded, and that required + // a native method to be associated with it. + field_ssl_ctx = env->GetFieldID(clazz, "ssl_ctx", "I"); + if (field_ssl_ctx == NULL) { + LOGE("Can't find OpenSSLSocketImpl.ssl_ctx"); + return -1; + } + + field_ssl = env->GetFieldID(clazz, "ssl", "I"); + if (field_ssl == NULL) { + LOGE("Can't find OpenSSLSocketImpl.ssl"); + return -1; + } + + field_timeout = env->GetFieldID(clazz, "timeout", "I"); + if (field_timeout == NULL) { + LOGE("Can't find OpenSSLSocketImpl.timeout"); + return -1; + } + } + return ret; +} diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_common.h b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_common.h new file mode 100644 index 0000000..16ecf9d --- /dev/null +++ b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_common.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef org_apache_harmony_xnet_provider_jsse_common_h +#define org_apache_harmony_xnet_provider_jsse_common_h + +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/ssl.h> + +#include <stdio.h> + +/** + * Structure to hold together useful JNI variables. + */ +typedef struct { + JNIEnv* env; + jobject object; +} mydata_t; + +/** + * Gives an array back containing all the X509 certificate's bytes. + */ +static jobjectArray getcertificatebytes(JNIEnv* env, + const STACK_OF(X509) *chain) +{ + BUF_MEM *bptr; + int count, i; + jbyteArray bytes; + jobjectArray joa; + + if (chain == NULL) { + // Chain can be NULL if the associated cipher doesn't do certs. + return NULL; + } + + count = sk_X509_num(chain); + + if (count > 0) { + joa = env->NewObjectArray(count, env->FindClass("[B"), NULL); + + if (joa == NULL) { + return NULL; + } + + BIO *bio = BIO_new(BIO_s_mem()); + + // LOGD("Start fetching the certificates"); + for (i = 0; i < count; i++) { + X509 *cert = sk_X509_value(chain, i); + + BIO_reset(bio); + PEM_write_bio_X509(bio, cert); + + BIO_get_mem_ptr(bio, &bptr); + bytes = env->NewByteArray(bptr->length); + + if (bytes == NULL) { + /* + * Indicate an error by resetting joa to NULL. It will + * eventually get gc'ed. + */ + joa = NULL; + break; + } else { + jbyte *tmp = env->GetByteArrayElements(bytes, NULL); + memcpy(tmp, bptr->data, bptr->length); + env->ReleaseByteArrayElements(bytes, tmp, 0); + env->SetObjectArrayElement(joa, i, bytes); + } + } + + // LOGD("Certificate fetching complete"); + BIO_free(bio); + return joa; + } else { + return NULL; + } +} + +extern int verify_callback_mydata_index; + +/** + * Verify the X509 certificate. + */ +static int verify_callback(int preverify_ok, X509_STORE_CTX *x509_store_ctx) +{ + SSL *ssl; + mydata_t *mydata; + jclass cls; + + jobjectArray objectArray; + + /* Get the correct index to the SSLobject stored into X509_STORE_CTX. */ + ssl = (SSL*)X509_STORE_CTX_get_ex_data(x509_store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + + mydata = (mydata_t*)SSL_get_ex_data(ssl, verify_callback_mydata_index); + + cls = mydata->env->GetObjectClass(mydata->object); + + jmethodID methodID = mydata->env->GetMethodID(cls, "verify_callback", "([[B)I"); + + objectArray = getcertificatebytes(mydata->env, x509_store_ctx->untrusted); + + mydata->env->CallIntMethod(mydata->object, methodID, objectArray); + + return 1; +} + +#endif diff --git a/x-net/src/main/native/sub.mk b/x-net/src/main/native/sub.mk new file mode 100644 index 0000000..4aeb41e --- /dev/null +++ b/x-net/src/main/native/sub.mk @@ -0,0 +1,24 @@ +# This file is included by the top-level libcore Android.mk. +# It's not a normal makefile, so we don't include CLEAR_VARS +# or BUILD_*_LIBRARY. + +LOCAL_SRC_FILES := \ + org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp \ + org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp \ + org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl.cpp \ + org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp + +LOCAL_C_INCLUDES += \ + external/openssl/include + + +# Any shared/static libs that are listed here must also +# be listed in libs/nativehelper/Android.mk. +# TODO: fix this requirement + +LOCAL_SHARED_LIBRARIES += \ + libcrypto \ + libssl \ + libutils + +LOCAL_STATIC_LIBRARIES += |