Renamed translator ItoopieTranslator -> Transl.

Changed http into https.
Added support storing ssl certs.
Added a GUI for confirming/denying/overwriting certs.
This commit is contained in:
dev
2011-07-01 06:34:21 +00:00
parent a3d5cefb3d
commit a1546b7027
11 changed files with 365 additions and 103 deletions

View File

@ -13,6 +13,13 @@ import java.util.regex.*;
import javax.net.ssl.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.i2p.itoopie.i2pcontrol.JSONInterface;
import net.i2p.itoopie.security.CertificateHelper;
import net.i2p.itoopie.security.CertificateManager;
import com.thetransactioncompany.jsonrpc2.*;
@ -106,7 +113,11 @@ import com.thetransactioncompany.jsonrpc2.*;
* @version 1.2 (2011-03-29)
*/
public class JSONRPC2Session {
private static final Log _log;
static {
_log = LogFactory.getLog(JSONRPC2Session.class);
}
/**
* The server URL, which protocol must be HTTP or HTTPS.
@ -428,6 +439,7 @@ public class JSONRPC2Session {
public void checkServerTrusted(X509Certificate[] certs, String authType) { }
}
};
try {
SSLContext sc = SSLContext.getInstance("SSL");

View File

@ -6,6 +6,7 @@ package net.i2p.itoopie;
import java.security.Security;
import javax.net.ssl.HttpsURLConnection;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
@ -16,6 +17,7 @@ import org.apache.commons.logging.LogFactory;
import com.thetransactioncompany.jsonrpc2.JSONRPC2Error;
import net.i2p.itoopie.i2pcontrol.JSONInterface;
import net.i2p.itoopie.security.CertificateHelper;
import net.i2p.itoopie.util.ConfigurationManager;
/**
@ -27,6 +29,7 @@ public class Main {
private TrayManager trayManager = null;
private static ConfigurationManager _conf;
private static Log _log;
public static final boolean isDebug = true;
/**
* Start the tray icon code (loads tray icon in the tray area).
@ -62,6 +65,7 @@ public class Main {
System.setProperty("java.awt.headless", "false");
_conf = ConfigurationManager.getInstance();
_log = LogFactory.getLog(Main.class);
HttpsURLConnection.setDefaultHostnameVerifier(CertificateHelper.getHostnameVerifier());
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
@ -75,20 +79,11 @@ public class Main {
//log.log(Log.ERROR, null, ex);
}
_conf.parseConfigStr("server-name=localhost");
_conf.parseConfigStr("server-port=7656");
_conf.parseConfigStr("server-name=127.0.0.1");
_conf.parseConfigStr("server-port=5555");
_conf.parseConfigStr("server-target=jsonrpc");
for (java.security.Provider p : Security.getProviders()){
System.out.println("Provider: " + p.getName());
}
for (String p : Security.getAlgorithms("KeyStore")){
System.out.println("KeyStore algorithm: " + p);
}
for (String p : Security.getAlgorithms("Cipher")){
System.out.println("Cipher algorithm: " + p);
}
String str = null;
try {

View File

@ -11,10 +11,12 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
import net.i2p.itoopie.i18n.ItoopieTranslator;
import net.i2p.itoopie.i18n.Transl;
import net.i2p.itoopie.util.BrowseException;
import net.i2p.itoopie.util.IsJar;
/**
* Manages the tray icon life.
@ -81,14 +83,12 @@ public class TrayManager {
desiredHeight = 512;
}
URL url = getClass().getResource("/resources/images/itoopie-"+desiredHeight+".png");
Image image = Toolkit.getDefaultToolkit().getImage(url);
//Image image = Toolkit.getDefaultToolkit().getImage("resources/images/itoopie-"+desiredHeight+".png");
return image;
}
protected static String _(String s) {
return ItoopieTranslator._(s);
if (IsJar.isRunningJar()){
URL url = getClass().getResource("/resources/images/itoopie-"+desiredHeight+".png");
return Toolkit.getDefaultToolkit().getImage(url);
} else {
return Toolkit.getDefaultToolkit().getImage("resources/images/itoopie-"+desiredHeight+".png");
}
}
@ -99,7 +99,7 @@ public class TrayManager {
public PopupMenu getMainMenu() {
PopupMenu popup = new PopupMenu();
MenuItem browserLauncher = new MenuItem(_("Launch I2P Browser"));
MenuItem browserLauncher = new MenuItem(Transl._("Launch I2P Browser"));
browserLauncher.addActionListener(new ActionListener() {
@Override
@ -119,25 +119,7 @@ public class TrayManager {
}.execute();
}
});
MenuItem restartItem = new MenuItem(_("Restart I2P"));
restartItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
new SwingWorker<Object, Object>() {
@Override
protected Object doInBackground() throws Exception {
//RouterManager.restart();
return null;
}
}.execute();
}
});
MenuItem stopItem = new MenuItem(_("Stop I2P"));
MenuItem stopItem = new MenuItem(Transl._("Exit itoopie"));
stopItem.addActionListener(new ActionListener() {
@Override
@ -146,19 +128,13 @@ public class TrayManager {
@Override
protected Object doInBackground() throws Exception {
//RouterManager.shutDown();
System.exit(0);
return null;
}
}.execute();
}
});
popup.add(browserLauncher);
popup.addSeparator();
popup.add(restartItem);
popup.add(stopItem);
return popup;

View File

@ -0,0 +1,109 @@
package net.i2p.itoopie.gui;
import javax.security.cert.X509Certificate;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import net.i2p.itoopie.i18n.Transl;
import net.i2p.itoopie.security.CertificateHelper;
import net.i2p.itoopie.security.CertificateManager;
public class CertificateGUI {
public static void main(String[] args){
System.out.println("Save new cert: " + saveNewCert(null,null));
System.out.println("Overwrite cert: " + overwriteCert(null,null));
}
public static boolean saveNewCert(String hostname, X509Certificate cert){
JFrame frame = new JFrame();
String title = Transl._("New remote host detected");
String hostString = Transl._("Would you like permanently trust the certificate from the remote host " + hostname + "?");
String certName = "N/A";
String certAlgo = "N/A";
String certSerial = "N/A";
String certThumb = "N/A";
if (cert != null){
certName = cert.getSubjectDN().getName();
String certString = cert.getPublicKey().toString();
certAlgo = certString.substring(0,certString.indexOf("\n"));
certSerial = String.valueOf(cert.getPublicKey().serialVersionUID);
certThumb = CertificateHelper.getThumbPrint(cert);
}
String certInfo = "<html>"+Transl._("Certificate info") + "<br><br>" +
Transl._("Name: ") + certName + "<br>" +
Transl._("Algorithm: ") + certAlgo + "<br>" +
Transl._("Serial: ") + certSerial + "<br>" +
Transl._("SHA-1 ID-hash: ") + certThumb;
String textContent = certInfo + "<br><br>" + hostString;
int n = JOptionPane.showConfirmDialog(
frame,
textContent,
title,
JOptionPane.YES_NO_OPTION,
JOptionPane.INFORMATION_MESSAGE);
if (n == JOptionPane.YES_OPTION){
CertificateManager.forcePutServerCert(hostname, CertificateHelper.convert(cert));
return true;
} else {
return false;
}
}
public static boolean overwriteCert(String hostname, X509Certificate cert){
JFrame frame = new JFrame();
String title = Transl._("Warning, new remote host detected");
String hostString = Transl._("The certificate of " + hostname + " has changed! <br>" +
"Are you sure you like permanently trust the new certificate from the remote host?");
String certName = "N/A";
String certAlgo = "N/A";
String certSerial = "N/A";
String certThumb = "N/A";
if (cert != null){
certName = cert.getSubjectDN().getName();
String certString = cert.getPublicKey().toString();
certAlgo = certString.substring(0,certString.indexOf("\n"));
certSerial = String.valueOf(cert.getPublicKey().serialVersionUID);
certThumb = CertificateHelper.getThumbPrint(cert);
}
String certInfo = "<html>"+Transl._("Certificate info") + "<br><br>" +
Transl._("Name: ") + certName + "<br>" +
Transl._("Algorithm: ") + certAlgo + "<br>" +
Transl._("Serial: ") + certSerial + "<br>" +
Transl._("SHA-1 ID-hash: ") + certThumb;
String textContent = certInfo + "<br><br>" + hostString;
int n = JOptionPane.showConfirmDialog(
frame,
textContent,
title,
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (n == JOptionPane.YES_OPTION){
n = JOptionPane.showConfirmDialog(
frame,
Transl._("Are you sure that you trust the new certificate?"),
Transl._("Is that you final answer?"),
JOptionPane.YES_NO_OPTION,
JOptionPane.ERROR_MESSAGE);
if (n == JOptionPane.YES_OPTION){
CertificateManager.forcePutServerCert(hostname, CertificateHelper.convert(cert));
return true; // Confirmation positive
} else {
return false; // Confirmation negative
}
} else {
return false; // No
}
}
}

View File

@ -10,7 +10,7 @@ import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.SwingWorker;
import net.i2p.itoopie.i18n.ItoopieTranslator;
import net.i2p.itoopie.i18n.Transl;
import net.i2p.itoopie.i2pcontrol.JSONInterface;
import net.i2p.itoopie.util.BrowseException;
@ -50,35 +50,35 @@ public class Main {
frame.getContentPane().setLayout(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton btnStop = new JButton(ItoopieTranslator._("Stop I2P"));
JButton btnStop = new JButton(Transl._("Stop I2P"));
btnStop.setBounds(293, 91, 125, 25);
frame.getContentPane().add(btnStop);
JLabel lblStop = new JLabel(ItoopieTranslator._("Push to stop I2P"));
JLabel lblStop = new JLabel(Transl._("Push to stop I2P"));
lblStop.setBounds(293, 78, 125, 15);
frame.getContentPane().add(lblStop);
JButton btnStart = new JButton(ItoopieTranslator._("Start I2P"));
JButton btnStart = new JButton(Transl._("Start I2P"));
btnStart.setBounds(43, 91, 125, 25);
frame.getContentPane().add(btnStart);
JLabel lblStart = new JLabel(ItoopieTranslator._("Push to start I2P"));
JLabel lblStart = new JLabel(Transl._("Push to start I2P"));
lblStart.setBounds(42, 78, 125, 15);
frame.getContentPane().add(lblStart);
JLabel lblGetRate = new JLabel(ItoopieTranslator._("Get bwSend rate"));
JLabel lblGetRate = new JLabel(Transl._("Get bwSend rate"));
lblGetRate.setBounds(293, 172, 125, 15);
frame.getContentPane().add(lblGetRate);
final JLabel lblDispRate = new JLabel(ItoopieTranslator._("Rate not update yet, push button."));
final JLabel lblDispRate = new JLabel(Transl._("Rate not update yet, push button."));
lblDispRate.setBounds(0,255,450,15);
frame.getContentPane().add(lblDispRate);
JButton btnGetRate = new JButton(ItoopieTranslator._("Update"));
JButton btnGetRate = new JButton(Transl._("Update"));
btnGetRate.setBounds(293, 185, 125, 25);
frame.getContentPane().add(btnGetRate);
JButton btnConnect = new JButton(ItoopieTranslator._("Connect"));
JButton btnConnect = new JButton(Transl._("Connect"));
btnConnect.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
}
@ -86,7 +86,7 @@ public class Main {
btnConnect.setBounds(43, 185, 125, 25);
frame.getContentPane().add(btnConnect);
JLabel lblConnect = new JLabel(ItoopieTranslator._("Connect to I2P"));
JLabel lblConnect = new JLabel(Transl._("Connect to I2P"));
lblConnect.setBounds(43, 172, 125, 15);
frame.getContentPane().add(lblConnect);
@ -120,7 +120,7 @@ public class Main {
@Override
protected Object doInBackground() throws Exception {
double rate = JSONInterface.getRateStat("bw.sendRate", 3600000L);
lblDispRate.setText(ItoopieTranslator._("Current bw.sendRate: " + rate));
lblDispRate.setText(Transl._("Current bw.sendRate: " + rate));
return null;
}
}.execute();

View File

@ -1,6 +1,6 @@
package net.i2p.itoopie.i18n;
public class ItoopieTranslator {
public class Transl {
private static final String BUNDLE_NAME = "net.i2p.itoopie.messages";

View File

@ -48,6 +48,7 @@ public class JSONInterface{
_log.error("Bad URL: https://"+srvHost+":"+srvPort+"/"+srvTarget, e);
}
session = new JSONRPC2Session(srvURL);
session.trustAllCerts(true);
}
@ -98,4 +99,22 @@ public class JSONInterface{
throw resp.getError();
}
}
@SuppressWarnings("unchecked")
public static String getServerCert(String str) throws JSONRPC2Error{
JSONRPC2Request req = new JSONRPC2Request("echo", incrNonce());
@SuppressWarnings("rawtypes")
Map params = new HashMap();
params.put("echo", str);
req.setParams(params);
JSONRPC2Response resp = sendReq(req);
if (resp.indicatesSuccess()){
Map inParams = (HashMap)resp.getResult();
return (String) inParams.get("serverCert");
} else {
throw resp.getError();
}
}
}

View File

@ -1,10 +1,21 @@
package net.i2p.itoopie.security;
import java.io.ByteArrayInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.security.cert.CertificateEncodingException;
import net.i2p.itoopie.gui.CertificateGUI;
import net.i2p.itoopie.i18n.Transl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -31,4 +42,100 @@ public class CertificateHelper {
return null;
}
// Converts to java.security
public static java.security.cert.X509Certificate convert(javax.security.cert.X509Certificate cert) {
try {
byte[] encoded = cert.getEncoded();
ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
java.security.cert.CertificateFactory cf
= java.security.cert.CertificateFactory.getInstance("X.509");
return (java.security.cert.X509Certificate)cf.generateCertificate(bis);
} catch (java.security.cert.CertificateEncodingException e) {
} catch (javax.security.cert.CertificateEncodingException e) {
} catch (java.security.cert.CertificateException e) {
}
return null;
}
// Converts to javax.security
public static javax.security.cert.X509Certificate convert(java.security.cert.X509Certificate cert) {
try {
byte[] encoded = cert.getEncoded();
return javax.security.cert.X509Certificate.getInstance(encoded);
} catch (java.security.cert.CertificateEncodingException e) {
} catch (javax.security.cert.CertificateEncodingException e) {
} catch (javax.security.cert.CertificateException e) {
}
return null;
}
public static String getThumbPrint(javax.security.cert.X509Certificate cert){
try {
return getThumbPrint(convert(cert));
} catch (Exception e){
return Transl._("Unable to create hash of the given cert, ") + cert;
}
}
public static String getThumbPrint(java.security.cert.X509Certificate cert)
throws NoSuchAlgorithmException, CertificateEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] der = null;
try {
der = cert.getEncoded();
} catch (java.security.cert.CertificateEncodingException e) {
e.printStackTrace();
}
md.update(der);
byte[] digest = md.digest();
return hexify(digest);
}
private static String hexify (byte bytes[]) {
char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
StringBuffer buf = new StringBuffer(bytes.length * 2);
for (int i = 0; i < bytes.length; ++i) {
buf.append(hexDigits[(bytes[i] & 0xf0) >> 4]);
buf.append(hexDigits[bytes[i] & 0x0f]);
}
return buf.toString();
}
public static HostnameVerifier getHostnameVerifier(){
return new HostnameVerifier(){
public boolean verify(String urlHostName, SSLSession session) {
String serverHost = session.getPeerHost();
try {
javax.security.cert.X509Certificate[] certs = session.getPeerCertificateChain();
if (CertificateManager.contains(serverHost)){
if (CertificateManager.verifyCert(serverHost, CertificateHelper.convert(certs[0]))){
return true; // Remote host has provided valid certificate that is store locally.
} else {
// Remote host has provided a certificate that != the stored certificate for this host
return CertificateGUI.overwriteCert(serverHost, certs[0]);
}
} else {
// GUI, Add new host! new host
return CertificateGUI.saveNewCert(serverHost, certs[0]);
}
} catch (SSLPeerUnverifiedException e) {
_log.fatal("Remote host could not be verified, possibly due to using not using athentication");
return false;
}
}
};
}
}

View File

@ -2,17 +2,21 @@ package net.i2p.itoopie.security;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.TrustManager;
@ -37,39 +41,16 @@ public class CertificateManager {
_log = LogFactory.getLog(CertificateManager.class);
}
/**
* Export X509Certificate as a file.
*
* @param cert - X509Certificate to export
* @param file - Destination file for certificate
*/
@SuppressWarnings("unused")
private static void export(X509Certificate cert, File file) {
public static boolean verifyCert(String storedCertAlias, X509Certificate cert){
try {
// Get the encoded form which is suitable for exporting
byte[] buf = cert.getEncoded();
FileOutputStream os = new FileOutputStream(file);
// Never write certificate in binary form.
if (false) {
// Write in binary form
os.write(buf);
} else {
// Write in text form
Writer wr = new OutputStreamWriter(os, Charset.forName("UTF-8"));
wr.write("-----BEGIN CERTIFICATE-----\n");
wr.write(new sun.misc.BASE64Encoder().encode(buf));
wr.write("\n-----END CERTIFICATE-----\n");
wr.flush();
}
os.close();
} catch (CertificateEncodingException e) {
_log.error(
"Bad certificate, can't be base64 encoded as a X509Certificate",
e);
} catch (IOException e) {
_log.error("File " + file.getAbsolutePath().toString()
+ " couldn't be written", e);
X509Certificate storedCert = (X509Certificate) getDefaultKeyStore().getCertificate(storedCertAlias);
storedCert.verify(cert.getPublicKey());
return true;
} catch (KeyStoreException e) {
return false; // Was unable to read cert with given alias. Which is fine.
} catch (Exception e) {
return false; // Something is wrong with the provided key.
}
}
@ -91,11 +72,13 @@ public class CertificateManager {
* @return - True if store was successful, false in other cases.
*/
public static boolean putServerCert(String name, X509Certificate cert) {
KeyStore ks = getDefaultKeyStore();
try {
if (getDefaultKeyStore().containsAlias(name)){
if (ks.containsAlias(name)){
return false;
} else {
getDefaultKeyStore().setCertificateEntry(name, cert);
ks.setCertificateEntry(name, cert);
saveKeyStore(ks);
return true;
}
} catch (KeyStoreException e) {
@ -104,6 +87,26 @@ public class CertificateManager {
}
return false;
}
/**
* Force store server X509Certificate under the name provided even if a certificate with the given alias already exists.
*
* @param name - Name of the certificate
* @param cert - X509Certificate to store
* @return - True if store was successful, false in other cases.
*/
public static boolean forcePutServerCert(String name, X509Certificate cert) {
KeyStore ks = getDefaultKeyStore();
try {
ks.setCertificateEntry(name, cert);
saveKeyStore(ks);
return true;
} catch (KeyStoreException e) {
e.printStackTrace();
}
return false;
}
/**
* Overwrite current X509Certificate with this name. Will only work if the
@ -114,11 +117,13 @@ public class CertificateManager {
* @return - True if the overwrite was successful, false in other cases
*/
public static boolean overwriteServerCert(String name, X509Certificate cert){
KeyStore ks = getDefaultKeyStore();
try {
if (getDefaultKeyStore().containsAlias(name)){
if (ks.containsAlias(name)){
return false;
} else {
getDefaultKeyStore().setCertificateEntry(name, cert);
saveKeyStore(ks);
return true;
}
} catch (KeyStoreException e) {
@ -153,13 +158,12 @@ public class CertificateManager {
*/
private static synchronized KeyStore getDefaultKeyStore(){
if (_ks == null){
KeyStore ks = null;
try {
ks = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
_ks = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
if ((new File(DEFAULT_KEYSTORE_LOCATION)).exists()){
InputStream is = new FileInputStream(DEFAULT_KEYSTORE_LOCATION);
ks.load(is, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
return ks;
_ks.load(is, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
return _ks;
} else {
throw new IOException("KeyStore file " + DEFAULT_KEYSTORE_LOCATION + "wasn't readable");
}
@ -167,10 +171,10 @@ public class CertificateManager {
// Ignore. Not an issue. Let's just create a new keystore instead.
}
try {
ks = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
ks.store(new FileOutputStream(DEFAULT_KEYSTORE_LOCATION), DEFAULT_KEYSTORE_PASSWORD.toCharArray());
return ks;
_ks = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
_ks.load(null, DEFAULT_KEYSTORE_PASSWORD.toCharArray());
saveKeyStore(_ks);
return _ks;
} catch (Exception e){
// Log perhaps?
}
@ -179,4 +183,25 @@ public class CertificateManager {
return _ks;
}
}
private static void saveKeyStore(KeyStore ks){
try {
ks.store(new FileOutputStream(DEFAULT_KEYSTORE_LOCATION), DEFAULT_KEYSTORE_PASSWORD.toCharArray());
} catch (KeyStoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,14 @@
package net.i2p.itoopie.util;
public abstract class IsJar {
public static boolean isRunningJar(){
IsJarTester isJar = new IsJarTester();
String className = isJar.getClass().getName().replace('.', '/');
String classJar = isJar.getClass().getResource("/" + className + ".class").toString();
if (classJar.startsWith("jar:"))
return true;
else
return false;
}
}

View File

@ -0,0 +1,5 @@
package net.i2p.itoopie.util;
public class IsJarTester {
}