mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-30 13:12:25 -05:00
Extracted CRAM-MD5 computation into separate class.
This gets rid of duplicated code in ImapStore, Pop3Store, and SmtpTransport.
This commit is contained in:
parent
e3818e7739
commit
2cadff74b1
85
src/com/fsck/k9/mail/Authentication.java
Normal file
85
src/com/fsck/k9/mail/Authentication.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,8 +20,6 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.text.SimpleDateFormat;
|
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.Utility;
|
||||||
import com.fsck.k9.helper.power.TracingPowerManager;
|
import com.fsck.k9.helper.power.TracingPowerManager;
|
||||||
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
|
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.AuthenticationFailedException;
|
||||||
import com.fsck.k9.mail.Body;
|
import com.fsck.k9.mail.Body;
|
||||||
import com.fsck.k9.mail.CertificateValidationException;
|
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.PushReceiver;
|
||||||
import com.fsck.k9.mail.Pusher;
|
import com.fsck.k9.mail.Pusher;
|
||||||
import com.fsck.k9.mail.Store;
|
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.EOLConvertingOutputStream;
|
||||||
import com.fsck.k9.mail.filter.FixedLengthInputStream;
|
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.filter.PeekableInputStream;
|
||||||
import com.fsck.k9.mail.internet.MimeBodyPart;
|
import com.fsck.k9.mail.internet.MimeBodyPart;
|
||||||
import com.fsck.k9.mail.internet.MimeHeader;
|
import com.fsck.k9.mail.internet.MimeHeader;
|
||||||
@ -2099,38 +2096,14 @@ public class ImapStore extends Store {
|
|||||||
}
|
}
|
||||||
byte[] b64NonceTrim = new byte[b64NonceLen - 2];
|
byte[] b64NonceTrim = new byte[b64NonceLen - 2];
|
||||||
System.arraycopy(buf, 1, b64NonceTrim, 0, 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[] b64CRAM = Authentication.computeCramMd5Bytes(mSettings.getUsername(),
|
||||||
byte[] opad = new byte[64];
|
mSettings.getPassword(), b64NonceTrim);
|
||||||
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
mOut.write(b64CRAM);
|
mOut.write(b64CRAM);
|
||||||
mOut.write(new byte[] { 0x0d, 0x0a });
|
mOut.write(new byte[] { 0x0d, 0x0a });
|
||||||
mOut.flush();
|
mOut.flush();
|
||||||
|
|
||||||
int respLen = 0;
|
int respLen = 0;
|
||||||
for (int i = 0; i < buf.length; i++) {
|
for (int i = 0; i < buf.length; i++) {
|
||||||
buf[i] = (byte)mIn.read();
|
buf[i] = (byte)mIn.read();
|
||||||
@ -2139,15 +2112,14 @@ public class ImapStore extends Store {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String toMatch = tag + " OK";
|
String toMatch = tag + " OK";
|
||||||
String respStr = new String(buf, 0, respLen);
|
String respStr = new String(buf, 0, respLen);
|
||||||
if (!respStr.startsWith(toMatch)) {
|
if (!respStr.startsWith(toMatch)) {
|
||||||
throw new AuthenticationFailedException("CRAM-MD5 error: " + respStr);
|
throw new AuthenticationFailedException("CRAM-MD5 error: " + respStr);
|
||||||
}
|
}
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
throw new AuthenticationFailedException("CRAM-MD5 Auth Failed.");
|
throw new AuthenticationFailedException("CRAM-MD5 Auth Failed.", ioe);
|
||||||
} catch (NoSuchAlgorithmException nsae) {
|
|
||||||
throw new AuthenticationFailedException("MD5 Not Available.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,8 +9,6 @@ import com.fsck.k9.controller.MessageRetrievalListener;
|
|||||||
import com.fsck.k9.helper.Utility;
|
import com.fsck.k9.helper.Utility;
|
||||||
import com.fsck.k9.mail.*;
|
import com.fsck.k9.mail.*;
|
||||||
import com.fsck.k9.mail.Folder.OpenMode;
|
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 com.fsck.k9.mail.internet.MimeMessage;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
@ -19,8 +17,6 @@ import javax.net.ssl.TrustManager;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -231,40 +227,9 @@ public class Pop3Store extends Store {
|
|||||||
if (useCramMd5)
|
if (useCramMd5)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
String nonce64, plainCRAM, b64CRAM;
|
String b64Nonce = executeSimpleCommand("AUTH CRAM-MD5").replace("+ ", "");
|
||||||
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 b64CRAM = Authentication.computeCramMd5(mUsername, mPassword, b64Nonce);
|
||||||
executeSimpleCommand(b64CRAM);
|
executeSimpleCommand(b64CRAM);
|
||||||
|
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
|
@ -7,7 +7,6 @@ import com.fsck.k9.mail.*;
|
|||||||
import com.fsck.k9.mail.Message.RecipientType;
|
import com.fsck.k9.mail.Message.RecipientType;
|
||||||
import com.fsck.k9.mail.filter.Base64;
|
import com.fsck.k9.mail.filter.Base64;
|
||||||
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
|
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.LineWrapOutputStream;
|
||||||
import com.fsck.k9.mail.filter.PeekableInputStream;
|
import com.fsck.k9.mail.filter.PeekableInputStream;
|
||||||
import com.fsck.k9.mail.filter.SmtpDataStuffing;
|
import com.fsck.k9.mail.filter.SmtpDataStuffing;
|
||||||
@ -25,8 +24,6 @@ import java.io.OutputStream;
|
|||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -522,33 +519,15 @@ public class SmtpTransport extends Transport {
|
|||||||
|
|
||||||
private void saslAuthCramMD5(String username, String password) throws MessagingException,
|
private void saslAuthCramMD5(String username, String password) throws MessagingException,
|
||||||
AuthenticationFailedException, IOException {
|
AuthenticationFailedException, IOException {
|
||||||
|
|
||||||
List<String> respList = executeSimpleCommand("AUTH CRAM-MD5");
|
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);
|
String b64Nonce = respList.get(0);
|
||||||
byte[] nonce = Base64.decodeBase64(b64Nonce.getBytes("US-ASCII"));
|
String b64CRAMString = Authentication.computeCramMd5(mUsername, mPassword, b64Nonce);
|
||||||
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");
|
|
||||||
try {
|
try {
|
||||||
executeSimpleCommand(b64CRAMString, true);
|
executeSimpleCommand(b64CRAMString, true);
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
|
Loading…
Reference in New Issue
Block a user