diff --git a/FAQ.md b/FAQ.md index 531434e1ca..7f20657ee9 100644 --- a/FAQ.md +++ b/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*. diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 40c9810559..2044b1fd8c 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -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 diff --git a/app/src/main/java/com.sun.mail.imap.protocol/IMAPSaslAuthenticator.java b/app/src/main/java/com.sun.mail.imap.protocol/IMAPSaslAuthenticator.java new file mode 100644 index 0000000000..a1a79fff0f --- /dev/null +++ b/app/src/main/java/com.sun.mail.imap.protocol/IMAPSaslAuthenticator.java @@ -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 . + + 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 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; + } +} diff --git a/app/src/main/java/com.sun.mail.smtp/SMTPSaslAuthenticator.java b/app/src/main/java/com.sun.mail.smtp/SMTPSaslAuthenticator.java new file mode 100644 index 0000000000..a76ce2a2db --- /dev/null +++ b/app/src/main/java/com.sun.mail.smtp/SMTPSaslAuthenticator.java @@ -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 . + + 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 ""; + } +} diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index cd6167f395..8cc61c53d6 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -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 List> chunkList(List list, int size) { diff --git a/app/src/main/java/eu/faircode/email/MailService.java b/app/src/main/java/eu/faircode/email/MailService.java index 9c484b4810..8412845da7 100644 --- a/app/src/main/java/eu/faircode/email/MailService.java +++ b/app/src/main/java/eu/faircode/email/MailService.java @@ -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);