Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2cadff74 authored by cketti's avatar cketti
Browse files

Extracted CRAM-MD5 computation into separate class.

This gets rid of duplicated code in ImapStore, Pop3Store, and
SmtpTransport.
parent e3818e77
Loading
Loading
Loading
Loading
+85 −0
Original line number Diff line number Diff line
package com.fsck.k9.mail;

import java.security.MessageDigest;

import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.Hex;

public class Authentication {
	private static final String US_ASCII = "US-ASCII";

	/**
	 * Computes the response for CRAM-MD5 authentication mechanism given the user credentials and
	 * the server-provided nonce.
	 *
	 * @param username The username.
	 * @param password The password.
	 * @param b64Nonce The nonce as base64-encoded string.
	 * @return The CRAM-MD5 response.
	 *
	 * @throws AuthenticationFailedException If something went wrong.
	 *
	 * @see Authentication#computeCramMd5Bytes(String, String, byte[])
	 */
	public static String computeCramMd5(String username, String password, String b64Nonce)
	throws AuthenticationFailedException {

        try {
        	byte[] b64NonceBytes = b64Nonce.getBytes(US_ASCII);
			byte[] b64CRAM = computeCramMd5Bytes(username, password, b64NonceBytes);
		    return new String(b64CRAM, US_ASCII);
        } catch (AuthenticationFailedException e) {
        	throw e;
        } catch (Exception e) {
        	throw new AuthenticationFailedException("This shouldn't happen", e);
        }
	}

	/**
	 * Computes the response for CRAM-MD5 authentication mechanism given the user credentials and
	 * the server-provided nonce.
	 *
	 * @param username The username.
	 * @param password The password.
	 * @param b64Nonce The nonce as base64-encoded byte array.
	 * @return The CRAM-MD5 response as byte array.
	 *
	 * @throws AuthenticationFailedException If something went wrong.
	 *
	 * @see <a href="https://tools.ietf.org/html/rfc2195">RFC 2195</a>
	 */
	public static byte[] computeCramMd5Bytes(String username, String password, byte[] b64Nonce)
	throws AuthenticationFailedException {

        try {
			byte[] nonce = Base64.decodeBase64(b64Nonce);

		    byte[] secretBytes = password.getBytes(US_ASCII);
		    MessageDigest md = MessageDigest.getInstance("MD5");
		    if (secretBytes.length > 64) {
		        secretBytes = md.digest(secretBytes);
		    }

			byte[] ipad = new byte[64];
		    byte[] opad = new byte[64];
		    System.arraycopy(secretBytes, 0, ipad, 0, secretBytes.length);
		    System.arraycopy(secretBytes, 0, opad, 0, secretBytes.length);
		    for (int i = 0; i < ipad.length; i++) ipad[i] ^= 0x36;
		    for (int i = 0; i < opad.length; i++) opad[i] ^= 0x5c;

		    md.update(ipad);
		    byte[] firstPass = md.digest(nonce);

		    md.update(opad);
		    byte[] result = md.digest(firstPass);

		    String plainCRAM = username + " " + new String(Hex.encodeHex(result));
		    byte[] b64CRAM = Base64.encodeBase64(plainCRAM.getBytes(US_ASCII));

		    return b64CRAM;

        } catch (Exception e) {
        	throw new AuthenticationFailedException("Something went wrong during CRAM-MD5 computation", e);
        }
	}
}
+11 −39
Original line number Diff line number Diff line
@@ -20,8 +20,6 @@ import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.text.SimpleDateFormat;
@@ -61,6 +59,7 @@ import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mail.Authentication;
import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.CertificateValidationException;
@@ -73,10 +72,8 @@ import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.Pusher;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.filter.FixedLengthInputStream;
import com.fsck.k9.mail.filter.Hex;
import com.fsck.k9.mail.filter.PeekableInputStream;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
@@ -2099,38 +2096,14 @@ public class ImapStore extends Store {
                }
                byte[] b64NonceTrim = new byte[b64NonceLen - 2];
                System.arraycopy(buf, 1, b64NonceTrim, 0, b64NonceLen - 2);
                byte[] nonce = Base64.decodeBase64(b64NonceTrim);
                if (K9.DEBUG) {
                    Log.d(K9.LOG_TAG, "Got nonce: " + new String(b64NonceTrim, "US-ASCII"));
                    Log.d(K9.LOG_TAG, "Plaintext nonce: " + new String(nonce, "US-ASCII"));
                }

                byte[] ipad = new byte[64];
                byte[] opad = new byte[64];
                byte[] secretBytes = mSettings.getPassword().getBytes("US-ASCII");
                MessageDigest md = MessageDigest.getInstance("MD5");
                if (secretBytes.length > 64) {
                    secretBytes = md.digest(secretBytes);
                }
                System.arraycopy(secretBytes, 0, ipad, 0, secretBytes.length);
                System.arraycopy(secretBytes, 0, opad, 0, secretBytes.length);
                for (int i = 0; i < ipad.length; i++) ipad[i] ^= 0x36;
                for (int i = 0; i < opad.length; i++) opad[i] ^= 0x5c;
                md.update(ipad);
                byte[] firstPass = md.digest(nonce);
                md.update(opad);
                byte[] result = md.digest(firstPass);
                String plainCRAM = mSettings.getUsername() + " " + new String(Hex.encodeHex(result));
                byte[] b64CRAM = Base64.encodeBase64(plainCRAM.getBytes("US-ASCII"));
                if (K9.DEBUG) {
                    Log.d(K9.LOG_TAG, "Username == " + mSettings.getUsername());
                    Log.d(K9.LOG_TAG, "plainCRAM: " + plainCRAM);
                    Log.d(K9.LOG_TAG, "b64CRAM: " + new String(b64CRAM, "US-ASCII"));
                }

                byte[] b64CRAM = Authentication.computeCramMd5Bytes(mSettings.getUsername(),
                		mSettings.getPassword(), b64NonceTrim);

                mOut.write(b64CRAM);
                mOut.write(new byte[] { 0x0d, 0x0a });
                mOut.flush();

                int respLen = 0;
                for (int i = 0; i < buf.length; i++) {
                    buf[i] = (byte)mIn.read();
@@ -2139,15 +2112,14 @@ public class ImapStore extends Store {
                        break;
                    }
                }

                String toMatch = tag + " OK";
                String respStr = new String(buf, 0, respLen);
                if (!respStr.startsWith(toMatch)) {
                    throw new AuthenticationFailedException("CRAM-MD5 error: " + respStr);
                }
            } catch (IOException ioe) {
                throw new AuthenticationFailedException("CRAM-MD5 Auth Failed.");
            } catch (NoSuchAlgorithmException nsae) {
                throw new AuthenticationFailedException("MD5 Not Available.");
                throw new AuthenticationFailedException("CRAM-MD5 Auth Failed.", ioe);
            }
        }

+2 −37
Original line number Diff line number Diff line
@@ -9,8 +9,6 @@ import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.Folder.OpenMode;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.Hex;
import com.fsck.k9.mail.internet.MimeMessage;

import javax.net.ssl.SSLContext;
@@ -19,8 +17,6 @@ import javax.net.ssl.TrustManager;
import java.io.*;
import java.net.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Date;
@@ -231,40 +227,9 @@ public class Pop3Store extends Store {
                if (useCramMd5)
                {
                    try {
                        String nonce64, plainCRAM, b64CRAM;
                        MessageDigest md;
                        byte[] ipad = new byte[64];
                        byte[] opad = new byte[64];
                        byte[] nonce, secretBytes, firstPass, result;

                        nonce64 = executeSimpleCommand("AUTH CRAM-MD5").replace("+ ", "");
                        nonce = Base64.decodeBase64(nonce64.getBytes("US-ASCII"));

                        secretBytes = mPassword.getBytes("US-ASCII");
                        try {
                            md = MessageDigest.getInstance("MD5");
                        } catch (NoSuchAlgorithmException nsae) {
                            throw new AuthenticationFailedException("MD5 Not Available.");
                        }

                        if (secretBytes.length > 64) {
                            secretBytes = md.digest(secretBytes);
                        }

                        System.arraycopy(secretBytes, 0, ipad, 0, secretBytes.length);
                        System.arraycopy(secretBytes, 0, opad, 0, secretBytes.length);
                        for (int i = 0; i < ipad.length; i++) ipad[i] ^= 0x36;
                        for (int i = 0; i < opad.length; i++) opad[i] ^= 0x5c;

                        md.update(ipad);
                        firstPass = md.digest(nonce);

                        md.update(opad);
                        result = md.digest(firstPass);

                        plainCRAM = mUsername + " " + new String(Hex.encodeHex(result));
                        b64CRAM = new String(Base64.encodeBase64(plainCRAM.getBytes("US-ASCII")), "US-ASCII");
                        String b64Nonce = executeSimpleCommand("AUTH CRAM-MD5").replace("+ ", "");

                        String b64CRAM = Authentication.computeCramMd5(mUsername, mPassword, b64Nonce);
                        executeSimpleCommand(b64CRAM);

                    } catch (MessagingException me) {
+8 −29
Original line number Diff line number Diff line
@@ -7,7 +7,6 @@ import com.fsck.k9.mail.*;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.filter.Hex;
import com.fsck.k9.mail.filter.LineWrapOutputStream;
import com.fsck.k9.mail.filter.PeekableInputStream;
import com.fsck.k9.mail.filter.SmtpDataStuffing;
@@ -25,8 +24,6 @@ import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import java.util.*;
@@ -522,33 +519,15 @@ public class SmtpTransport extends Transport {

    private void saslAuthCramMD5(String username, String password) throws MessagingException,
        AuthenticationFailedException, IOException {

    	List<String> respList = executeSimpleCommand("AUTH CRAM-MD5");
        if (respList.size() != 1) throw new AuthenticationFailedException("Unable to negotiate CRAM-MD5");
        if (respList.size() != 1) {
        	throw new AuthenticationFailedException("Unable to negotiate CRAM-MD5");
        }

        String b64Nonce = respList.get(0);
        byte[] nonce = Base64.decodeBase64(b64Nonce.getBytes("US-ASCII"));
        byte[] ipad = new byte[64];
        byte[] opad = new byte[64];
        byte[] secretBytes = password.getBytes("US-ASCII");
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsae) {
            throw new AuthenticationFailedException("MD5 Not Available.");
        }
        if (secretBytes.length > 64) {
            secretBytes = md.digest(secretBytes);
        }
        System.arraycopy(secretBytes, 0, ipad, 0, secretBytes.length);
        System.arraycopy(secretBytes, 0, opad, 0, secretBytes.length);
        for (int i = 0; i < ipad.length; i++) ipad[i] ^= 0x36;
        for (int i = 0; i < opad.length; i++) opad[i] ^= 0x5c;
        md.update(ipad);
        byte[] firstPass = md.digest(nonce);
        md.update(opad);
        byte[] result = md.digest(firstPass);
        String plainCRAM = username + " " + new String(Hex.encodeHex(result));
        byte[] b64CRAM = Base64.encodeBase64(plainCRAM.getBytes("US-ASCII"));
        String b64CRAMString = new String(b64CRAM, "US-ASCII");
        String b64CRAMString = Authentication.computeCramMd5(mUsername, mPassword, b64Nonce);

        try {
            executeSimpleCommand(b64CRAMString, true);
        } catch (MessagingException me) {