/*******************************************************************************
* Copyright (c) 2009, 2012 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.ssl;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.security.KeyFactory;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509TrustManager;
import org.eclipse.tcf.Activator;
import org.eclipse.tcf.core.Base64;
import org.eclipse.tcf.protocol.Protocol;
/**
* This class implements keys and certificates management for secure TCF channels.
*/
public class TCFSecurityManager {
public static File getCertificatesDirectory() throws IOException {
File certs;
try {
certs = Activator.getDefault().getStateLocation().append("certificates").toFile(); //$NON-NLS-1$
}
catch (IllegalStateException e) {
// An RCP workspace-less environment (-data @none)
certs = new File(System.getProperty("user.home"), ".tcf");
certs = new File(certs, "certificates");
}
if (!certs.exists() && !certs.mkdirs()) throw new IOException("Cannot create directory: " + certs);
return certs;
}
public static File getSysCertificatesDirectory() {
File file = null;
String osname = System.getProperty("os.name", "");
if (osname.startsWith("Windows")) {
Process prs = null;
BufferedReader inp = null;
try {
String sys_root = "SystemRoot";
prs = Runtime.getRuntime().exec(new String[]{ "cmd", "/c", "set", sys_root }, null);
inp = new BufferedReader(new InputStreamReader(prs.getInputStream()));
for (;;) {
String s = inp.readLine();
if (s == null) break;
int i = s.indexOf('=');
if (i > 0) {
String name = s.substring(0, i);
if (name.equalsIgnoreCase(sys_root)) {
File root = new File(s.substring(i + 1));
if (root.exists()) file = new File(root, "TCF/ssl");
}
}
}
try {
prs.getErrorStream().close();
prs.getOutputStream().close();
inp.close();
}
catch (IOException x) {
Protocol.log("Cannot close child process I/O streams", x); //$NON-NLS-1$
}
prs.waitFor();
}
catch (Throwable x) {
Protocol.log("Cannot get system directory name", x); //$NON-NLS-1$
try {
if (prs != null) {
prs.getErrorStream().close();
prs.getOutputStream().close();
}
if (inp != null) inp.close();
}
catch (IOException y) {
}
}
}
else {
file = new File("/etc/tcf/ssl");
}
if (file == null) return null;
if (!file.exists()) return null;
if (!file.isDirectory()) return null;
return file;
}
public static SSLContext createSSLContext() {
try {
final File usr_certs = getCertificatesDirectory();
final File sys_certs = getSysCertificatesDirectory();
if (!usr_certs.exists() && !usr_certs.mkdirs()) throw new Exception("Cannot create directory: " + usr_certs);
final CertificateFactory cf = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
SSLContext context = SSLContext.getInstance("TLS"); //$NON-NLS-1$
X509ExtendedKeyManager km = new X509ExtendedKeyManager() {
public X509Certificate[] getCertificateChain(String alias) {
File f = new File(usr_certs, "local.cert"); //$NON-NLS-1$
if (!f.exists() && sys_certs != null) f = new File(sys_certs, "local.cert"); //$NON-NLS-1$
InputStream inp = null;
try {
inp = new BufferedInputStream(new FileInputStream(f));
X509Certificate cert = (X509Certificate)cf.generateCertificate(inp);
inp.close();
return new X509Certificate[] { cert };
}
catch (Throwable x) {
Protocol.log("Cannot read certificate: " + f, x); //$NON-NLS-1$
try {
if (inp != null) inp.close();
}
catch (IOException e) {
Protocol.log("Cannot close certificate file: " + f, x); //$NON-NLS-1$
}
return null;
}
}
public PrivateKey getPrivateKey(String alias) {
File f = new File(usr_certs, "local.priv"); //$NON-NLS-1$
if (!f.exists() && sys_certs != null) f = new File(sys_certs, "local.priv"); //$NON-NLS-1$
BufferedReader r = null;
try {
r = new BufferedReader(new InputStreamReader(new FileInputStream(f), "ASCII")); //$NON-NLS-1$
StringBuffer bf = new StringBuffer();
boolean app = false;
for (;;) {
String s = r.readLine();
if (s == null) throw new Exception("Invalid format"); //$NON-NLS-1$
else if (s.indexOf("-----BEGIN ") == 0) app = true; //$NON-NLS-1$
else if (s.indexOf("-----END ") == 0) break; //$NON-NLS-1$
else if (app) bf.append(s);
}
KeyFactory kf = KeyFactory.getInstance("RSA"); //$NON-NLS-1$
byte[] bytes = Base64.toByteArray(bf.toString().toCharArray());
return kf.generatePrivate(new PKCS8EncodedKeySpec(bytes));
}
catch (Exception x) {
Protocol.log("Cannot read private key: " + f, x); //$NON-NLS-1$
try {
if (r != null) r.close();
}
catch (IOException e) {
Protocol.log("Cannot close private key file: " + f, x); //$NON-NLS-1$
}
return null;
} finally {
if (r != null) try { r.close(); } catch (IOException e) {}
}
}
public String[] getClientAliases(String keyType, Principal[] issuers) {
return new String[] { "TCF" }; //$NON-NLS-1$
}
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return "TCF"; //$NON-NLS-1$
}
public String[] getServerAliases(String keyType, Principal[] issuers) {
return new String[] { "TCF" }; //$NON-NLS-1$
}
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return "TCF"; //$NON-NLS-1$
}
};
X509TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String auth_type) throws CertificateException {
if ("RSA".equals(auth_type) && chain != null && chain.length == 1) { //$NON-NLS-1$
for (X509Certificate cert : getAcceptedIssuers()) {
if (cert.equals(chain[0])) return;
}
}
throw new CertificateException("Client certificate validation failed"); //$NON-NLS-1$
}
public void checkServerTrusted(X509Certificate[] chain, String auth_type) throws CertificateException {
if ("RSA".equals(auth_type) && chain != null && chain.length == 1) { //$NON-NLS-1$
for (X509Certificate cert : getAcceptedIssuers()) {
if (cert.equals(chain[0])) return;
}
}
throw new CertificateException("Server certificate validation failed"); //$NON-NLS-1$
}
public X509Certificate[] getAcceptedIssuers() {
ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
for (String fnm : usr_certs.list()) {
if (!fnm.endsWith(".cert")) continue; //$NON-NLS-1$
InputStream inp = null;
try {
inp = new BufferedInputStream(new FileInputStream(new File(usr_certs, fnm)));
X509Certificate cert = (X509Certificate)cf.generateCertificate(inp);
inp.close();
list.add(cert);
}
catch (Throwable x) {
Protocol.log("Cannot load certificate: " + fnm, x); //$NON-NLS-1$
try {
if (inp != null) inp.close();
}
catch (IOException e) {
Protocol.log("Cannot close certificate file: " + fnm, x); //$NON-NLS-1$
}
}
}
if (sys_certs != null) {
String[] arr = sys_certs.list();
if (arr != null) {
for (String fnm : arr) {
if (!fnm.endsWith(".cert")) continue; //$NON-NLS-1$
InputStream inp = null;
try {
inp = new BufferedInputStream(new FileInputStream(new File(sys_certs, fnm)));
X509Certificate cert = (X509Certificate)cf.generateCertificate(inp);
inp.close();
list.add(cert);
}
catch (Throwable x) {
Protocol.log("Cannot load certificate: " + fnm, x); //$NON-NLS-1$
try {
if (inp != null) inp.close();
}
catch (IOException e) {
Protocol.log("Cannot close certificate file: " + fnm, x); //$NON-NLS-1$
}
}
}
}
}
return list.toArray(new X509Certificate[list.size()]);
}
};
context.init(new KeyManager[] { km }, new TrustManager[] { tm }, null);
return context;
}
catch (Throwable x) {
Protocol.log("Cannot initialize SSL context", x); //$NON-NLS-1$
return null;
}
}
}