Initial implementation of EAS security

* This first implementation integrates with early CLs for
  Email app integration with device security
* Check for policies added to validation process
* If the server has no policy requirements, there is no change
  to the existing process
* We automatically declare a set of policies to be unsupported
  if any are known to the sync adapter to be unsupported
  (e.g. no attachments or password history)
* We call isSupported (PolicySet) to determine whether other
  policies use values that the OS can support (e.g. password length,
  inactivity time, etc.)
* Depending on whether the server's policies are unsupported or
  supported, we throw the proper exception back to the caller

Change-Id: I704cb2151dd87f54c83c2aa23976a8ac8e2c501a
This commit is contained in:
Marc Blank 2009-12-18 08:54:05 -08:00
parent 3d2b3b3b35
commit 72a5e7d7c1
3 changed files with 294 additions and 15 deletions

View File

@ -40,6 +40,7 @@ import com.android.exchange.adapter.EmailSyncAdapter;
import com.android.exchange.adapter.FolderSyncParser;
import com.android.exchange.adapter.MeetingResponseParser;
import com.android.exchange.adapter.PingParser;
import com.android.exchange.adapter.ProvisionParser;
import com.android.exchange.adapter.Serializer;
import com.android.exchange.adapter.Tags;
import com.android.exchange.adapter.Parser.EasParserException;
@ -142,6 +143,9 @@ public class EasSyncService extends AbstractSyncService {
static private final int PING_FALLBACK_INBOX = 5;
static private final int PING_FALLBACK_PIM = 25;
// MSFT's custom HTTP result code indicating the need to provision
static private final int HTTP_NEED_PROVISIONING = 449;
// Reasonable default
public String mProtocolVersion = DEFAULT_PROTOCOL_VERSION;
public Double mProtocolVersionDouble;
@ -224,6 +228,19 @@ public class EasSyncService extends AbstractSyncService {
return (code == HttpStatus.SC_UNAUTHORIZED) || (code == HttpStatus.SC_FORBIDDEN);
}
private void setupProtocolVersion(EasSyncService service, Header versionHeader) {
String versions = versionHeader.getValue();
if (versions != null) {
if (versions.contains("12.0")) {
service.mProtocolVersion = "12.0";
}
service.mProtocolVersionDouble = Double.parseDouble(service.mProtocolVersion);
if (service.mAccount != null) {
service.mAccount.mProtocolVersion = service.mProtocolVersion;
}
}
}
@Override
public void validateAccount(String hostAddress, String userName, String password, int port,
boolean ssl, boolean trustCertificates, Context context) throws MessagingException {
@ -251,6 +268,9 @@ public class EasSyncService extends AbstractSyncService {
throw new MessagingException(MessagingException.IOERROR);
}
// Make sure we've got the right protocol version set up
setupProtocolVersion(svc, versions);
// Run second test here for provisioning failures...
Serializer s = new Serializer();
userLog("Try folder sync");
@ -258,20 +278,17 @@ public class EasSyncService extends AbstractSyncService {
.end().end().done();
resp = svc.sendHttpClientPost("FolderSync", s.toByteArray());
code = resp.getStatusLine().getStatusCode();
if (code == HttpStatus.SC_FORBIDDEN) {
throw new MessagingException(MessagingException.SECURITY_POLICIES_UNSUPPORTED);
// We'll get one of the following responses if policies are required by the server
if (code == HttpStatus.SC_FORBIDDEN || code == HTTP_NEED_PROVISIONING) {
// Get the policies and see if we are able to support them
if (svc.canProvision()) {
// If so, send the advisory Exception (the account may be created later)
throw new MessagingException(MessagingException.SECURITY_POLICIES_REQUIRED);
} else
// If not, send the unsupported Exception (the account won't be created)
throw new MessagingException(
MessagingException.SECURITY_POLICIES_UNSUPPORTED);
}
// PLACEHOLDER: Replace the above simple check with a more sophisticated
// check of server-mandated security policy support. There are three outcomes.
// 1. As below, if no policies required, simply return here as-is.
// 2. As above, if policies are required that we do not support, throw
// MessagingException.SECURITY_POLICIES_UNSUPPORTED. This is a validation
// failure.
// 3. New code: If policies are required that we *do* support, throw
// MessagingException.SECURITY_POLICIES_REQUIRED. This is an advisory to the
// UI that new policies will be required in order to use this account.
// See also: isSupported(PolicySet policies)
userLog("Validation successful");
return;
}
@ -882,6 +899,29 @@ public class EasSyncService extends AbstractSyncService {
}
}
// TODO This is Exchange 2007 only at this point
private boolean canProvision() throws IOException {
Serializer s = new Serializer();
s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES);
s.start(Tags.PROVISION_POLICY).data(Tags.PROVISION_POLICY_TYPE, "MS-EAS-Provisioning-WBXML")
.end().end().end().done();
HttpResponse resp = sendHttpClientPost("Provision", s.toByteArray());
int code = resp.getStatusLine().getStatusCode();
if (code == HttpStatus.SC_OK) {
InputStream is = resp.getEntity().getContent();
ProvisionParser pp = new ProvisionParser(is, this);
if (pp.parse()) {
// If true, we received policies from the server
// Retrieve them and write them to the framework
PolicySet ps = pp.getPolicySet();
if (SecurityPolicy.getInstance(mContext).isSupported(ps)) {
return true;
}
}
}
return false;
}
/**
* Performs FolderSync
*

View File

@ -0,0 +1,178 @@
/* Copyright (C) 2010 The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.exchange.adapter;
import com.android.email.SecurityPolicy;
import com.android.email.SecurityPolicy.PolicySet;
import com.android.exchange.EasSyncService;
import java.io.IOException;
import java.io.InputStream;
/**
* Parse the result of the Provision command
*
* Assuming a successful parse, we store the PolicySet and the policy key
*/
public class ProvisionParser extends Parser {
private EasSyncService mService;
PolicySet mPolicySet = null;
String mPolicyKey = null;
public ProvisionParser(InputStream in, EasSyncService service) throws IOException {
super(in);
mService = service;
setDebug(true);
}
public PolicySet getPolicySet() {
return mPolicySet;
}
public String getPolicyKey() {
return mPolicyKey;
}
public void parseProvisionDoc() throws IOException {
int minPasswordLength = 0;
int passwordMode = PolicySet.PASSWORD_MODE_NONE;
int maxPasswordFails = 0;
int maxScreenLockTime = 0;
boolean canSupport = false;
while (nextTag(Tags.PROVISION_EAS_PROVISION_DOC) != END) {
switch (tag) {
case Tags.PROVISION_DEVICE_PASSWORD_ENABLED:
if (getValueInt() == 1) {
if (passwordMode == PolicySet.PASSWORD_MODE_NONE) {
passwordMode = PolicySet.PASSWORD_MODE_SIMPLE;
}
}
break;
case Tags.PROVISION_MIN_DEVICE_PASSWORD_LENGTH:
minPasswordLength = getValueInt();
break;
case Tags.PROVISION_ALPHA_DEVICE_PASSWORD_ENABLED:
if (getValueInt() == 1) {
passwordMode = PolicySet.PASSWORD_MODE_STRONG;
}
break;
case Tags.PROVISION_MAX_INACTIVITY_TIME_DEVICE_LOCK:
// EAS gives us seconds, which is, happily, what the PolicySet requires
maxScreenLockTime = getValueInt();
break;
case Tags.PROVISION_MAX_DEVICE_PASSWORD_FAILED_ATTEMPTS:
maxPasswordFails = getValueInt();
break;
case Tags.PROVISION_ALLOW_SIMPLE_DEVICE_PASSWORD:
// Ignore this unless there's any MSFT documentation for what this means
// Hint: I haven't seen any that's more specific than "simple"
getValue();
break;
// The following policy, if false, can't be supported at the moment
case Tags.PROVISION_ATTACHMENTS_ENABLED:
if (getValueInt() == 0) {
canSupport = false;
}
break;
// The following policies, if true, can't be supported at the moment
case Tags.PROVISION_DEVICE_ENCRYPTION_ENABLED:
case Tags.PROVISION_PASSWORD_RECOVERY_ENABLED:
case Tags.PROVISION_DEVICE_PASSWORD_EXPIRATION:
case Tags.PROVISION_DEVICE_PASSWORD_HISTORY:
case Tags.PROVISION_MAX_ATTACHMENT_SIZE:
if (getValueInt() == 1) {
canSupport = false;
}
break;
default:
skipTag();
}
}
if (canSupport) {
mPolicySet = new SecurityPolicy.PolicySet(minPasswordLength, passwordMode,
maxPasswordFails, maxScreenLockTime, true);
}
}
public void parseProvisionData() throws IOException {
while (nextTag(Tags.PROVISION_DATA) != END) {
if (tag == Tags.PROVISION_EAS_PROVISION_DOC) {
parseProvisionDoc();
} else {
skipTag();
}
}
}
public void parsePolicy() throws IOException {
while (nextTag(Tags.PROVISION_POLICY) != END) {
switch (tag) {
case Tags.PROVISION_POLICIES:
parsePolicies();
break;
case Tags.PROVISION_POLICY_TYPE:
mService.userLog("Policy type: ", getValue());
break;
case Tags.PROVISION_POLICY_KEY:
mPolicyKey = getValue();
break;
case Tags.PROVISION_STATUS:
mService.userLog("Policy status: ", getValue());
break;
case Tags.PROVISION_DATA:
parseProvisionData();
break;
default:
skipTag();
}
}
}
public void parsePolicies() throws IOException {
while (nextTag(Tags.PROVISION_POLICIES) != END) {
if (tag == Tags.PROVISION_POLICY) {
parsePolicy();
} else {
skipTag();
}
}
}
@Override
public boolean parse() throws IOException {
boolean res = false;
if (nextTag(START_DOCUMENT) != Tags.PROVISION_PROVISION) {
throw new IOException();
}
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
switch (tag) {
case Tags.PROVISION_STATUS:
int status = getValueInt();
mService.userLog("Provision status: ", status);
break;
case Tags.PROVISION_POLICIES:
parsePolicies();
return (mPolicySet != null);
default:
skipTag();
}
}
return res;
}
}

View File

@ -43,6 +43,7 @@ public class Tags {
public static final int TASK = 0x09;
public static final int CONTACTS2 = 0x0C;
public static final int PING = 0x0D;
public static final int PROVISION = 0x0E;
public static final int GAL = 0x10;
public static final int BASE = 0x11;
@ -345,7 +346,6 @@ public class Tags {
public static final int CONTACTS2_NICKNAME = CONTACTS2_PAGE + 0xD;
public static final int CONTACTS2_MMS = CONTACTS2_PAGE + 0xE;
// The Ping constants are used by EasSyncService, and need to be public
public static final int PING_PAGE = PING << PAGE_SHIFT;
public static final int PING_PING = PING_PAGE + 5;
public static final int PING_AUTD_STATE = PING_PAGE + 6;
@ -357,6 +357,66 @@ public class Tags {
public static final int PING_CLASS = PING_PAGE + 0xC;
public static final int PING_MAX_FOLDERS = PING_PAGE + 0xD;
public static final int PROVISION_PAGE = PROVISION << PAGE_SHIFT;
// EAS 2.5
public static final int PROVISION_PROVISION = PROVISION_PAGE + 5;
public static final int PROVISION_POLICIES = PROVISION_PAGE + 6;
public static final int PROVISION_POLICY = PROVISION_PAGE + 7;
public static final int PROVISION_POLICY_TYPE = PROVISION_PAGE + 8;
public static final int PROVISION_POLICY_KEY = PROVISION_PAGE + 9;
public static final int PROVISION_DATA = PROVISION_PAGE + 0xA;
public static final int PROVISION_STATUS = PROVISION_PAGE + 0xB;
public static final int PROVISION_REMOTE_WIPE = PROVISION_PAGE + 0xC;
// EAS 12.0
public static final int PROVISION_EAS_PROVISION_DOC = PROVISION_PAGE + 0xD;
public static final int PROVISION_DEVICE_PASSWORD_ENABLED = PROVISION_PAGE + 0xE;
public static final int PROVISION_ALPHA_DEVICE_PASSWORD_ENABLED = PROVISION_PAGE + 0xF;
public static final int PROVISION_DEVICE_ENCRYPTION_ENABLED = PROVISION_PAGE + 0x10;
public static final int PROVISION_PASSWORD_RECOVERY_ENABLED = PROVISION_PAGE + 0x11;
public static final int PROVISION_ATTACHMENTS_ENABLED = PROVISION_PAGE + 0x13;
public static final int PROVISION_MIN_DEVICE_PASSWORD_LENGTH = PROVISION_PAGE + 0x14;
public static final int PROVISION_MAX_INACTIVITY_TIME_DEVICE_LOCK = PROVISION_PAGE + 0x15;
public static final int PROVISION_MAX_DEVICE_PASSWORD_FAILED_ATTEMPTS = PROVISION_PAGE + 0x16;
public static final int PROVISION_MAX_ATTACHMENT_SIZE = PROVISION_PAGE + 0x17;
public static final int PROVISION_ALLOW_SIMPLE_DEVICE_PASSWORD = PROVISION_PAGE + 0x18;
public static final int PROVISION_DEVICE_PASSWORD_EXPIRATION = PROVISION_PAGE + 0x19;
public static final int PROVISION_DEVICE_PASSWORD_HISTORY = PROVISION_PAGE + 0x1A;
public static final int PROVISION_MAX_SUPPORTED_TAG = PROVISION_DEVICE_PASSWORD_HISTORY;
// EAS 12.1
public static final int PROVISION_ALLOW_STORAGE_CARD = PROVISION_PAGE + 0x1B;
public static final int PROVISION_ALLOW_CAMERA = PROVISION_PAGE + 0x1C;
public static final int PROVISION_REQUIRE_DEVICE_ENCRYPTION = PROVISION_PAGE + 0x1D;
public static final int PROVISION_ALLOW_UNSIGNED_APPLICATIONS = PROVISION_PAGE + 0x1E;
public static final int PROVISION_ALLOW_UNSIGNED_INSTALLATION_PACKAGES = PROVISION_PAGE + 0x1F;
public static final int PROVISION_MIN_DEVICE_PASSWORD_COMPLEX_CHARS = PROVISION_PAGE + 0x20;
public static final int PROVISION_ALLOW_WIFI = PROVISION_PAGE + 0x21;
public static final int PROVISION_ALLOW_TEXT_MESSAGING = PROVISION_PAGE + 0x22;
public static final int PROVISION_ALLOW_POP_IMAP_EMAIL = PROVISION_PAGE + 0x23;
public static final int PROVISION_ALLOW_BLUETOOTH = PROVISION_PAGE + 0x24;
public static final int PROVISION_ALLOW_IRDA = PROVISION_PAGE + 0x25;
public static final int PROVISION_REQUIRE_MANUAL_SYNC_WHEN_ROAMING = PROVISION_PAGE + 0x26;
public static final int PROVISION_ALLOW_DESKTOP_SYNC = PROVISION_PAGE + 0x27;
public static final int PROVISION_MAX_CALENDAR_AGE_FILTER = PROVISION_PAGE + 0x28;
public static final int PROVISION_ALLOW_HTML_EMAIL = PROVISION_PAGE + 0x29;
public static final int PROVISION_MAX_EMAIL_AGE_FILTER = PROVISION_PAGE + 0x2A;
public static final int PROVISION_MAX_EMAIL_BODY_TRUNCATION_SIZE = PROVISION_PAGE + 0x2B;
public static final int PROVISION_MAX_EMAIL_HTML_BODY_TRUNCATION_SIZE = PROVISION_PAGE + 0x2C;
public static final int PROVISION_REQUIRE_SIGNED_SMIME_MESSAGES = PROVISION_PAGE + 0x2D;
public static final int PROVISION_REQUIRE_ENCRYPTED_SMIME_MESSAGES = PROVISION_PAGE + 0x2E;
public static final int PROVISION_REQUIRE_SIGNED_SMIME_ALGORITHM = PROVISION_PAGE + 0x2F;
public static final int PROVISION_REQUIRE_ENCRYPTION_SMIME_ALGORITHM = PROVISION_PAGE + 0x30;
public static final int PROVISION_ALLOW_SMIME_ENCRYPTION_NEGOTIATION = PROVISION_PAGE + 0x31;
public static final int PROVISION_ALLOW_SMIME_SOFT_CERTS = PROVISION_PAGE + 0x32;
public static final int PROVISION_ALLOW_BROWSER = PROVISION_PAGE + 0x33;
public static final int PROVISION_ALLOW_CONSUMER_EMAIL = PROVISION_PAGE + 0x34;
public static final int PROVISION_ALLOW_REMOTE_DESKTOP = PROVISION_PAGE + 0x35;
public static final int PROVISION_ALLOW_INTERNET_SHARING = PROVISION_PAGE + 0x36;
public static final int PROVISION_UNAPPROVED_IN_ROM_APPLICATION_LIST = PROVISION_PAGE + 0x37;
public static final int PROVISION_APPLICATION_NAME = PROVISION_PAGE + 0x38;
public static final int PROVISION_APPROVED_APPLICATION_LIST = PROVISION_PAGE + 0x39;
public static final int PROVISION_HASH = PROVISION_PAGE + 0x3A;
public static final int BASE_PAGE = BASE << PAGE_SHIFT;
public static final int BASE_BODY_PREFERENCE = BASE_PAGE + 5;
public static final int BASE_TYPE = BASE_PAGE + 6;
@ -487,7 +547,8 @@ public class Tags {
"Provision", "Policies", "Policy", "PolicyType", "PolicyKey", "Data", "ProvisionStatus",
"RemoteWipe", "EASProvidionDoc", "DevicePasswordEnabled",
"AlphanumericDevicePasswordRequired",
"DeviceEncryptionEnabled", "-unused-", "AttachmentsEnabled", "MinDevicePasswordLength",
"DeviceEncryptionEnabled", "PasswordRecoveryEnabled", "-unused-", "AttachmentsEnabled",
"MinDevicePasswordLength",
"MaxInactivityTimeDeviceLock", "MaxDevicePasswordFailedAttempts", "MaxAttachmentSize",
"AllowSimpleDevicePassword", "DevicePasswordExpiration", "DevicePasswordHistory",
"AllowStorageCard", "AllowCamera", "RequireDeviceEncryption",