com.goodvikings.cryptim.api.Cryptim.java Source code

Java tutorial

Introduction

Here is the source code for com.goodvikings.cryptim.api.Cryptim.java

Source

/*
 * ------------------------------------------------------------------------------
 * "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";
}