次の方法で共有


Accessing Azure Mobile Services from Android 2.2 (Froyo) devices

If you try to access a Windows Azure Mobile Service using the Android SDK from a device running Android version 2.2 (also known as Froyo), you’ll get an error in all requests. The issue is that that version of Android doesn’t accept the SSL certificate used in Azure Mobile Services, and you end up getting the following exception:

Error: com.microsoft.windowsazure.mobileservices.MobileServiceException: Error while processing request.
Cause: javax.net.ssl.SSLException: Not trusted server certificate
Cause: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: TrustAnchor for CertPath not found.
Cause: java.security.cert.CertPathValidatorException: TrustAnchor for CertPath not found.

The number of Froyo Android devices is rapidly declining (2.2% in the date I wrote this post, according to the Android dashboard), but there are scenarios where you need to target up to that platform. This post will show one way to work around that limitation, created by Pablo Zaidenvoren and committed to the test project for the Android platform in the Android Mobile Services GitHub repository. The solution is to register a new SSL socket factory to be used by the mobile service client with a key store which accepts the certificate used by the Azure Mobile Service.

Scenario

In a TDD-like fashion, I’ll first write a program which will fail, then walk through the steps required to make it pass. Here’s the onCreate method in the main activity, whose layout has nothing but a button (btnClickMe) and a text view (txtResult). If you’re going to copy this code, remember to replace the application URL / key with yours, since I’ll delete this mobile service which I created right before I publish this post.

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3.     super.onCreate(savedInstanceState);
  4.     setContentView(R.layout.activity_main);
  5.  
  6.     try {
  7.         mClient = new MobileServiceClient(
  8.                   "https://blog20131019.azure-mobile.net/", // Replace with your
  9.                   "umtzEajPoOBsnvufFUVbLFTtjQrPPd37",       // own URL / key
  10.                   this
  11.             );
  12.     } catch (MalformedURLException e) {
  13.         e.printStackTrace();
  14.     }
  15.  
  16.     Button btn = (Button)findViewById(R.id.btnClickMe);
  17.     btn.setOnClickListener(new View.OnClickListener() {
  18.         @Override
  19.         public void onClick(View view) {
  20.             final StringBuilder sb = new StringBuilder();
  21.             final TextView txtResult = (TextView)findViewById(R.id.txtResult);
  22.             MobileServiceJsonTable table = mClient.getTable("TodoItem");
  23.             sb.append("Created the table object, inserting item");
  24.             txtResult.setText(sb.toString());
  25.             JsonObject item = new JsonObject();
  26.             item.addProperty("text", "Buy bread");
  27.             item.addProperty("complete", false);
  28.             table.insert(item, new TableJsonOperationCallback() {
  29.                 @Override
  30.                 public void onCompleted(JsonObject inserted, Exception error,
  31.                         ServiceFilterResponse response) {
  32.                     if (error != null) {
  33.                         sb.append("\nError: " + error.toString());
  34.                         Throwable cause = error.getCause();
  35.                         while (cause != null) {
  36.                             sb.append("\n  Cause: " + cause.toString());
  37.                             cause = cause.getCause();
  38.                         }
  39.                     } else {
  40.                         sb.append("\nInserted: id=" + inserted.get("id").getAsString());
  41.                     }
  42.                     
  43.                     String txt = sb.toString();
  44.                     txtResult.setText(txt);
  45.                 }
  46.             });
  47.         }
  48.     });
  49. }

If you run this code in a Froyo emulator (or a device), you’ll see printed out the error shown before.

On to the workaround

First, add a new folder under the res (resources) folder for the solution, by right-clicking it, and selecting New –> Folder:

01-AddNewFolder

Let’s call it ‘raw’, and click Finish:

02-NewFolderResRaw

Now, download the mobile service store file from https://github.com/WindowsAzure/azure-mobile-services/blob/dev/test/Android/ZumoE2ETestApp/res/raw/mobileservicestore.bks, and save it into the <project>/res/raw folder which you just created (in the GitHub page, clicking “View Raw” should prompt you to save the file). Back in Eclipse, select the ‘raw’ folder and hit F5 (Refresh), and you should see the file under the folder.

03-FileInEclipse

That file will need to be read in the new SSL socket factory, and to read that resource we’ll need a reference to the main activity. So we’ll add a static method (along with a new static field) which returns the instance of the main activity. And in the onCreate method, we set the (global) instance.

  1. private static Activity mInstance;
  2. public static Activity getInstance() {
  3.     return mInstance;
  4. }
  5.  
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8.     super.onCreate(savedInstanceState);
  9.     setContentView(R.layout.activity_main);
  10.  
  11.     mInstance = this;
  12.  
  13.     try {
  14.         mClient = new MobileServiceClient(

Now we can start by adding the Froyo support to the application. The snippet below shows only the beginning of the class – it doesn’t show the entire SSL factory with the additional certificate store (for Azure Mobile Services). You can find the whole code (may need to update the imports for the main activity and the resources) in the GitHub project.

  1. public class FroyoSupport {
  2.     /**
  3.      * Fixes an AndroidHttpClient instance to accept MobileServices SSL certificate
  4.      * @param client AndroidHttpClient to fix
  5.      */
  6.     public static void fixAndroidHttpClientForCertificateValidation(AndroidHttpClient client) {
  7.         
  8.         final SchemeRegistry schemeRegistry = new SchemeRegistry();
  9.         schemeRegistry.register(new Scheme("https",
  10.                 createAdditionalCertsSSLSocketFactory(), 443));
  11.         client.getConnectionManager().getSchemeRegistry().unregister("https");
  12.         
  13.         client.getConnectionManager().getSchemeRegistry().register(new Scheme("https",
  14.                 createAdditionalCertsSSLSocketFactory(), 443));
  15.     }
  16.  
  17.     private static SSLSocketFactory createAdditionalCertsSSLSocketFactory() {
  18.         try {
  19.             final KeyStore ks = KeyStore.getInstance("BKS");
  20.  
  21.             Activity mainActivity = MainActivity.getInstance();
  22.             final InputStream in = mainActivity.getResources().openRawResource(R.raw.mobileservicestore);
  23.             try {
  24.                 ks.load(in, "mobileservices".toCharArray());
  25.             } finally {
  26.                 in.close();
  27.             }
  28.  
  29.             return new AdditionalKeyStoresSSLSocketFactory(ks);
  30.  
  31.         } catch (Exception e) {
  32.             throw new RuntimeException(e);
  33.         }
  34.     }
  35.  
  36.     private static class AdditionalKeyStoresSSLSocketFactory extends SSLSocketFactory {

Now that we have that support, we need to create a new instance of the factory used to create the AndroidHttpClient instances – which is defined in the Azure Mobile Services Android SDK (version 1.0.1 and later). The SDK already includes a default implementation of the class, so extending it to use the fix support is fairly straightforward. Add a new class (I’ll call it FroyoAndroidHttpClientFactory) which extends that default implementation:

  1. package com.example.froyoandwams;
  2.  
  3. import android.net.http.AndroidHttpClient;
  4.  
  5. import com.microsoft.windowsazure.mobileservices.AndroidHttpClientFactoryImpl;
  6.  
  7. public class FroyoAndroidHttpClientFactory
  8.     extends AndroidHttpClientFactoryImpl {
  9.  
  10.     @Override
  11.     public AndroidHttpClient createAndroidHttpClient() {
  12.         AndroidHttpClient client = super.createAndroidHttpClient();
  13.         FroyoSupport.fixAndroidHttpClientForCertificateValidation(client);
  14.         return client;
  15.     }
  16. }

Now all we need to do is to hook up that AndroidHttpClientFactory with the MobileServiceClient, which can be done with the setAndroidHttpClientFactory method, as shown below.

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3.     super.onCreate(savedInstanceState);
  4.     setContentView(R.layout.activity_main);
  5.  
  6.     mInstance = this;
  7.  
  8.     try {
  9.         mClient = new MobileServiceClient(
  10.                   "https://blog20131019.azure-mobile.net/", // Replace with your
  11.                   "umtzEajPoOBsnvufFUVbLFTtjQrPPd37",       // own URL / key
  12.                   this
  13.             );
  14.     } catch (MalformedURLException e) {
  15.         e.printStackTrace();
  16.     }
  17.     
  18.     mClient.setAndroidHttpClientFactory(new FroyoAndroidHttpClientFactory());

And that’s it. If you execute the application again and click the button, the item should be correctly inserted (and other operations should work as well).

If you want to download the code for this example, you can find it in my GitHub repository at https://github.com/carlosfigueira/blogsamples/tree/master/AzureMobileServices/FroyoAndWAMS.

Comments

  • Anonymous
    December 15, 2014
    After that i now get "IOEXception Wrong version of key store."
  • Anonymous
    November 07, 2015
    Don't right-click and "save as" from GIT, download the raw file otherwise you get all the GIT HTML code too which obviously isn't the file you need.