Java tutorial
/* * ------------------------------------------------------------------------------ * "THE BEER-WARE LICENSE" (Ramo's Revision): * <ramo@goodvikings.com> wrote this file. As long as you retain this notice on * this and any derivative works you can do whatever you want with this stuff. * If we meet some day, and you think this stuff is worth it, you can buy me a * beer in return - Ramo * ------------------------------------------------------------------------------ */ package com.goodvikings.cryptim.api; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; import java.security.SignatureException; import java.security.spec.InvalidKeySpecException; import java.text.ParseException; import java.util.TreeMap; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPException; import org.jivesoftware.smack.Chat; import org.jivesoftware.smack.Connection; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.Roster; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Presence; /** * The main class to work with. * * @author ramo */ public class Cryptim { private KeyRing kr; private Connection conn; private TreeMap<String, Chat> chats; private CryptimChatManagerListener ccml; /** * Create a new CryptimAPI instance. */ public Cryptim() { Security.insertProviderAt(new BouncyCastleProvider(), 1); kr = new KeyRing(); chats = new TreeMap<>(); } /** * Open a saved keyring, or if it doesn't exist, create a new one. * * @param filename The filename of the keyring to open * @param cbh A CallbackHandler for retrieving the password to open the keyfile. This callback handler need only check for PasswordCallbacks * @param createIfNotExists If the file does not exist, create a new key set. Note, this does not yet create the keyfile. * @return Success * @throws BadPaddingException Error in padding when decrypting the keyfile * @throws CryptimException If you asked not to create a new keyfile, and the one specified by filename doesn't exist * @throws IOException Error in reading the keyfile * @throws IllegalBlockSizeException Error in blocksize when decrypting the keyfile * @throws InvalidAlgorithmParameterException Error in algorithm parameters when decrypting the keyfile * @throws InvalidKeyException Error in the key when decrypting the keyfile * @throws InvalidKeySpecException Error in the key spec when decrypting the keyfile * @throws NoSuchAlgorithmException Algorithm for encryption has no implementation * @throws NoSuchPaddingException Padding has no implementation * @throws NoSuchProviderException Provider doesn't exist, or hasn't been added yet * @throws PGPException Error in PGP-ifying when decrypting the keyfile * @throws ParseException Error in parsing when decrypting the keyfile */ public boolean openKeys(String filename, CallbackHandler cbh, boolean createIfNotExists) throws BadPaddingException, CryptimException, IOException, IllegalBlockSizeException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, PGPException, ParseException { kr.open(filename, cbh, createIfNotExists); return true; } /** * Save the keys to the keyfile. * * @param cbh A CallbackHandler for retrieving the password to save the keyfile. This callback handler need only check for PasswordCallbacks * @return Success * @throws BadPaddingException Error in padding when encrypting the keyfile * @throws IOException Error in saving the keyfile * @throws IllegalBlockSizeException Error in blocksize when decrypting the keyfile * @throws InvalidAlgorithmParameterException Error in algorithm parameters when decrypting the keyfile * @throws InvalidKeyException Error in the key when decrypting the keyfile * @throws InvalidKeySpecException Error in the key spec when decrypting the keyfile * @throws NoSuchAlgorithmException Algorithm for encryption has no implementation * @throws NoSuchPaddingException Padding has no implementation * @throws NoSuchProviderException Provider doesn't exist, or hasn't been added yet * @throws PGPException Error in PGP-ifying when decrypting the keyfile */ public boolean saveKeys(CallbackHandler cbh) throws BadPaddingException, IOException, IllegalBlockSizeException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, PGPException { kr.saveKDB(cbh); return true; } /** * Saves the keys to a new keyfile. * * @param filename The filename to save to * @param cbh A CallbackHandler for retrieving the password to save the keyfile. This callback handler need only check for PasswordCallbacks * @return Success * @throws BadPaddingException Error in padding when encrypting the keyfile * @throws IOException Error in saving the keyfile * @throws IllegalBlockSizeException Error in blocksize when decrypting the keyfile * @throws InvalidAlgorithmParameterException Error in algorithm parameters when decrypting the keyfile * @throws InvalidKeyException Error in the key when decrypting the keyfile * @throws InvalidKeySpecException Error in the key spec when decrypting the keyfile * @throws NoSuchAlgorithmException Algorithm for encryption has no implementation * @throws NoSuchPaddingException Padding has no implementation * @throws NoSuchProviderException Provider doesn't exist, or hasn't been added yet * @throws PGPException Error in PGP-ifying when decrypting the keyfile */ public boolean saveKeysToNewFile(String filename, CallbackHandler cbh) throws BadPaddingException, IOException, IllegalBlockSizeException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, PGPException { kr.saveKDB(filename, cbh); return true; } /** * Connects to the default XMPP server * * @return Success * @throws XMPPException If there is an issue connecting to the XMPP server */ public boolean connectToXMPPServer() throws XMPPException { return connectToXMPPServer(DEFAULTADDRESS, DEFAULTPORT); } /** * Connects to a specified XMPP server * * @param address The address to connect to * @param port The port to connect to * @return Success * @throws XMPPException If there is an issue connecting to the XMPP server */ public boolean connectToXMPPServer(String address, int port) throws XMPPException { ConnectionConfiguration config = new ConnectionConfiguration(address, port); config.setCompressionEnabled(true); config.setSASLAuthenticationEnabled(true); config.setExpiredCertificatesCheckEnabled(true); config.setVerifyChainEnabled(true); conn = new XMPPConnection(config); conn.connect(); return true; } /** * Logs into the XMPP server * * @param resource The resource name * @param cbh A CallbackHandler for retrieving the name and password to connect to the server with. Uses Password and Name callbacks * @return Success * @throws IOException Error reading from the callbacks * @throws UnsupportedCallbackException If a callback was unsupported * @throws XMPPException */ public boolean loginToXMPPServer(String resource, CallbackHandler cbh) throws IOException, UnsupportedCallbackException, XMPPException { NameCallback ncb = new NameCallback("Login name"); PasswordCallback pcb = new PasswordCallback("Password", false); cbh.handle(new Callback[] { ncb, pcb }); conn.login(ncb.getName(), new String(pcb.getPassword()), resource); pcb.clearPassword(); return true; } /** * Adds a CryptimChatManagerListener to the connection. * * @param ccml The CryptimChatManagerListener to add * @throws CryptimException If the listener cannot yet be added */ public void addListener(CryptimChatManagerListener ccml) throws CryptimException { if (conn == null) { throw new CryptimException("Not connected: Connect to the XMPP server before adding listeners."); } ccml.addKeyRing(kr); this.ccml = ccml; conn.getChatManager().addChatListener(this.ccml); } /** * Get a chat with a chat partner. If the chat does not exist already, it is * created. * * @param id JID of the chat partner * @param cml A CryptimMessageListener to use if creating a new chat. * @return */ public Chat getChat(String id, CryptimMessageListener cml) { if (chats.containsKey(id)) { System.out.println("Returning existing chat"); return chats.get(id); } else { System.out.println("Creating new chat"); Chat c = createChat(id, cml); chats.put(c.getParticipant(), c); return c; } } /** * Sends a message through a chat. * * @param chat The chat to send to * @param body The message to send * @throws XMPPException If an XMPP error occurs * @throws CryptimException If a Cryptim exception occurs */ public void sendMessage(Chat chat, String body) throws XMPPException, CryptimException { if (kr.hasKeyForUser(chat.getParticipant())) { sendEncryptedMessage(chat, body); } else { sendPlainMessage(chat, body); } } /** * Gets the XMPP Roster. * * @return The Roster * @throws CryptimException If the connection to the server hasn't been made yet */ public Roster getRoster() throws CryptimException { if (conn == null) { throw new CryptimException("Not connected to a server"); } return conn.getRoster(); } /** * Adds in a callback handler for responding to subscription requests. * * @param cbh The CallbackHandler * @throws CryptimException If the connection to the server is not yet established */ public void addSubcriptionConfirmationCallbackHandler(CallbackHandler cbh) throws CryptimException { if (conn == null) { throw new CryptimException("Not connected to a server"); } conn.addPacketListener(new CryptimSubscriptionPacketListener(cbh, conn.getRoster(), this), new PacketTypeFilter(Presence.class)); } /** * Confirms a received subscription request. * * @param p The subscription packet received */ public void confirmSubscription(Presence p) { Presence sub = new Presence(Presence.Type.subscribed); sub.setTo(p.getFrom()); conn.sendPacket(sub); } /** * Sends a subscription request to a user with the JID specified. * * @param jid The JID to subscribe to */ public void subscribe(String jid) { Presence sub = new Presence(Presence.Type.subscribe); sub.setTo(jid); conn.sendPacket(sub); } private Chat createChat(String id, CryptimMessageListener cml) { return conn.getChatManager().createChat(id, cml); } void addChat(Chat chat) { chats.put(chat.getParticipant(), chat); } private void sendEncryptedMessage(Chat chat, String body) throws XMPPException, CryptimException { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); kr.signEncryptMessage(new ByteArrayInputStream(body.getBytes()), baos, chat.getParticipant()); Message msg = new Message(); msg.setProperty(CryptimUtils.XMPPTAG, CryptimUtils.ENCRYPTEDMESSAGE); msg.setBody(baos.toString()); chat.sendMessage(msg); } catch (IOException e) { throw new CryptimException("IO error", e); } catch (PGPException e) { throw new CryptimException("PGP error", e); } catch (SignatureException e) { throw new CryptimException("Signature error", e); } } private void sendPlainMessage(Chat chat, String body) throws XMPPException { chat.sendMessage(body); } public static final String DEFAULTADDRESS = "ubuntu.test.ramo"; public static final int DEFAULTPORT = 5222; public static final String DEFAULTRESOURCE = "cryptim"; }