mirror of https://github.com/M66B/FairEmail.git
Added CRAM-MD5 authentication
This commit is contained in:
parent
e715772ac5
commit
581d02d1ec
4
FAQ.md
4
FAQ.md
|
@ -1522,11 +1522,11 @@ The following authentication methods are supported and used in this order:
|
|||
|
||||
* LOGIN
|
||||
* PLAIN
|
||||
* CRAM-MD5
|
||||
* NTLM (untested)
|
||||
|
||||
SASL authentication methods, like CRAM-MD5, are not supported
|
||||
SASL authentication methods, besides CRAM-MD5, are not supported
|
||||
because [JavaMail for Android](https://javaee.github.io/javamail/Android) does not support SASL authentication.
|
||||
If using secure connections, a must today, there is little value in using CRAM-MD5 anyway.
|
||||
|
||||
If your provider requires an unsupported authentication method, you'll likely get the error message *authentication failed*.
|
||||
|
||||
|
|
|
@ -100,3 +100,6 @@
|
|||
-dontnote com.bugsnag.android.**
|
||||
-dontnote biweekly.io.**
|
||||
|
||||
#SASL
|
||||
-keep class com.sun.mail.imap.protocol.IMAPSaslAuthenticator
|
||||
-keep class com.sun.mail.smtp.SMTPSaslAuthenticator
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
package com.sun.mail.imap.protocol;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FairEmail is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018-2020 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import com.sun.mail.iap.Argument;
|
||||
import com.sun.mail.iap.ProtocolException;
|
||||
import com.sun.mail.iap.Response;
|
||||
import com.sun.mail.util.MailLogger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import eu.faircode.email.Helper;
|
||||
|
||||
// https://github.com/javaee/javamail/blob/master/mail/src/main/java/com/sun/mail/imap/protocol/IMAPSaslAuthenticator.java
|
||||
public class IMAPSaslAuthenticator implements SaslAuthenticator {
|
||||
private IMAPProtocol pr;
|
||||
private String name;
|
||||
private Properties props;
|
||||
private MailLogger logger;
|
||||
private String host;
|
||||
|
||||
public IMAPSaslAuthenticator(
|
||||
IMAPProtocol pr, String name, Properties props,
|
||||
MailLogger logger, String host) {
|
||||
this.pr = pr;
|
||||
this.name = name;
|
||||
this.props = props;
|
||||
this.logger = logger;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(
|
||||
String[] mechs, final String realm,
|
||||
final String authzid, final String u,
|
||||
final String p) throws ProtocolException {
|
||||
|
||||
if (!pr.hasCapability("AUTH=CRAM-MD5"))
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
List<Response> v = new ArrayList<>();
|
||||
String tag = null;
|
||||
Response r = null;
|
||||
boolean done = false;
|
||||
|
||||
try {
|
||||
Argument args = new Argument();
|
||||
args.writeAtom("CRAM-MD5");
|
||||
tag = pr.writeCommand("AUTHENTICATE", args);
|
||||
} catch (Exception ex) {
|
||||
r = Response.byeResponse(ex);
|
||||
done = true;
|
||||
}
|
||||
|
||||
while (!done) {
|
||||
try {
|
||||
r = pr.readResponse();
|
||||
if (r.isContinuation()) {
|
||||
byte[] nonce = Base64.decode(r.getRest(), Base64.NO_WRAP);
|
||||
String hmac = Helper.HMAC("MD5", 64, p.getBytes(), nonce);
|
||||
String hash = Base64.encodeToString((u + " " + hmac).getBytes(), Base64.NO_WRAP) + "\r\n";
|
||||
pr.getIMAPOutputStream().write(hash.getBytes());
|
||||
pr.getIMAPOutputStream().flush();
|
||||
} else if (r.isTagged() && r.getTag().equals(tag))
|
||||
done = true;
|
||||
else if (r.isBYE())
|
||||
done = true;
|
||||
} catch (Exception ex) {
|
||||
r = Response.byeResponse(ex);
|
||||
done = true;
|
||||
}
|
||||
v.add(r);
|
||||
}
|
||||
|
||||
Response[] responses = v.toArray(new Response[0]);
|
||||
pr.handleCapabilityResponse(responses);
|
||||
pr.notifyResponseHandlers(responses);
|
||||
pr.handleLoginResult(r);
|
||||
pr.setCapabilities(r);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package com.sun.mail.smtp;
|
||||
|
||||
/*
|
||||
This file is part of FairEmail.
|
||||
|
||||
FairEmail is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FairEmail is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2018-2020 by Marcel Bokhorst (M66B)
|
||||
*/
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import com.sun.mail.util.MailLogger;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
import eu.faircode.email.Helper;
|
||||
import eu.faircode.email.Log;
|
||||
|
||||
// https://github.com/javaee/javamail/blob/master/mail/src/main/java/com/sun/mail/smtp/SMTPSaslAuthenticator.java
|
||||
public class SMTPSaslAuthenticator implements SaslAuthenticator {
|
||||
private SMTPTransport pr;
|
||||
private String name;
|
||||
private Properties props;
|
||||
private MailLogger logger;
|
||||
private String host;
|
||||
|
||||
public SMTPSaslAuthenticator(
|
||||
SMTPTransport pr, String name,
|
||||
Properties props, MailLogger logger, String host) {
|
||||
this.pr = pr;
|
||||
this.name = name;
|
||||
this.props = props;
|
||||
this.logger = logger;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(
|
||||
String[] mechs, final String realm,
|
||||
final String authzid, final String u,
|
||||
final String p) throws MessagingException {
|
||||
|
||||
if (!pr.supportsAuthentication("CRAM-MD5"))
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
// https://tools.ietf.org/html/rfc4954
|
||||
int resp = simpleCommand(pr, "AUTH CRAM-MD5");
|
||||
if (resp == 530) { // Authentication is required
|
||||
pr.startTLS();
|
||||
resp = simpleCommand(pr, "AUTH CRAM-MD5");
|
||||
}
|
||||
|
||||
if (resp == 235) // Authentication Succeeded
|
||||
return true;
|
||||
|
||||
if (resp != 334) // server challenge
|
||||
return false;
|
||||
|
||||
try {
|
||||
String t = responseText(pr);
|
||||
byte[] nonce = Base64.decode(t, Base64.NO_WRAP);
|
||||
String hmac = Helper.HMAC("MD5", 64, p.getBytes(), nonce);
|
||||
String hash = Base64.encodeToString((u + " " + hmac).getBytes(), Base64.NO_WRAP);
|
||||
resp = simpleCommand(pr, hash);
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new MessagingException("CRAM-MD5", ex);
|
||||
}
|
||||
|
||||
return (resp == 235);
|
||||
}
|
||||
|
||||
private static int simpleCommand(SMTPTransport pr, String command) throws MessagingException {
|
||||
Log.i("SMTP SASL command=" + command);
|
||||
int resp = pr.simpleCommand(command);
|
||||
Log.i("SMTP SASL response=" + pr.getLastServerResponse());
|
||||
return resp;
|
||||
}
|
||||
|
||||
private static String responseText(SMTPTransport pr) {
|
||||
String resp = pr.getLastServerResponse().trim();
|
||||
if (resp.length() > 4)
|
||||
return resp.substring(4);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
}
|
|
@ -978,6 +978,41 @@ public class Helper {
|
|||
void onNothingSelected();
|
||||
}
|
||||
|
||||
public static String HMAC(String algo, int blocksize, byte[] key, byte[] text) throws NoSuchAlgorithmException {
|
||||
MessageDigest md = MessageDigest.getInstance(algo);
|
||||
|
||||
if (key.length > blocksize)
|
||||
key = md.digest(key);
|
||||
|
||||
byte[] ipad = new byte[blocksize];
|
||||
byte[] opad = new byte[blocksize];
|
||||
|
||||
for (int i = 0; i < key.length; i++) {
|
||||
ipad[i] = key[i];
|
||||
opad[i] = key[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < blocksize; i++) {
|
||||
ipad[i] ^= 0x36;
|
||||
opad[i] ^= 0x5c;
|
||||
}
|
||||
|
||||
byte[] digest;
|
||||
|
||||
md.update(ipad);
|
||||
md.update(text);
|
||||
digest = md.digest();
|
||||
|
||||
md.update(opad);
|
||||
md.update(digest);
|
||||
digest = md.digest();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : digest)
|
||||
sb.append(String.format("%02x", b));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
|
||||
static <T> List<List<T>> chunkList(List<T> list, int size) {
|
||||
|
|
|
@ -121,6 +121,8 @@ public class MailService implements AutoCloseable {
|
|||
properties.put("mail.event.scope", "folder");
|
||||
properties.put("mail.event.executor", executor);
|
||||
|
||||
properties.put("mail." + protocol + ".sasl.enable", "true");
|
||||
properties.put("mail." + protocol + ".sasl.mechanisms", "CRAM-MD5");
|
||||
properties.put("mail." + protocol + ".sasl.realm", realm == null ? "" : realm);
|
||||
properties.put("mail." + protocol + ".auth.ntlm.domain", realm == null ? "" : realm);
|
||||
|
||||
|
|
Loading…
Reference in New Issue