Android content provider protection level & different keys
Asked Answered
C

1

4

I have an app available in Pro (paid) & Free (add supported) versions. Both versions are signed with the same key store, but each one with its own key alias.

Now, I'd like to develop a plugin compatible with both versions, offering data through a content provider. The data is sensitive, so I need my content provider to be accessible only from my app (both Pro & Free versions).

Using a permission with the android:protectionLevel="signature" is not working, because the Free & Pro versions do not have the same signature :/ . I guess I should have signed my two versions with the same key, but I thought each app on the Play Store needed to be signed with its own key.

So, does someone knows a solution ? Is there a way to gently ask Google to change the keys I'm using (I can prove my identity, as I did not loose my keys), or am I stuck??

EDIT : I could choose to un-publish the Pro version (as I have very few downloads at the moment) and re-upload it with the same certificate than the one used for the free version. If I do so, will I need to change its package?

Thanks in advance

Cannes answered 24/8, 2015 at 15:36 Comment(6)
"Is there a way to gently ask Google to change the keys I'm using" -- Google isn't the limiting factor. Android itself will fail to replace an existing installed app with an update signed by a different signing key. As an alternative, see what getCallingUid() returns from inside some API method of your provider (e.g., query()). You might be able to use that to validate the caller and return empty results (and ignore insert/update/delete ops) if the caller is not one of your apps.Nakasuji
Thanks for your comment. Indeed asking Google won't help. By using the Uid, you mean use it to retrieve the caller's package name, and then check if it is correct? But what if someone develop an app with the right package name in order to access to my provider?Cannes
"By using the Uid, you mean use it to retrieve the caller's package name, and then check if it is correct?" -- I'd check package name and signature. See my SignatureUtils class: github.com/commonsguy/cwac-security/#usage-signatureutilsNakasuji
Great, I'll give it a try tomorrow, thanks!!Cannes
Seems to be working great, you can add it as a solution, I'll accept it. Thanks !Cannes
I think we can use developer.android.com/reference/android/content/… to verify the third party application / application with different signature. Yes, checking signature on top it would be much betterChange
N
6

signature-level permissions are great, but fairly inflexible: the apps have to be signed by the same signing key. There are many cases in which we will want to check to see if another app is signed by the expected key, but that key is not our key. In your case, you have two keys. In other cases, one might be checking the signature of some partner app — confirming, for example, that the PayPal app you are about to send the user to is really the PayPal app and not malware that has replaced the PayPal app.

To validate the signature of another app, you can use PackageManager. For example, here is the now-current edition of my SignatureUtils class from my CWAC-Security library:

/***
  Copyright (c) 2014 CommonsWare, LLC

  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.commonsware.cwac.security;

import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class SignatureUtils {
  public static String getOwnSignatureHash(Context ctxt)
                                                        throws NameNotFoundException,
                                                        NoSuchAlgorithmException {
    return(getSignatureHash(ctxt, ctxt.getPackageName()));
  }

  public static String getSignatureHash(Context ctxt, String packageName)
                                                                         throws NameNotFoundException,
                                                                         NoSuchAlgorithmException {
    MessageDigest md=MessageDigest.getInstance("SHA-256");
    Signature sig=
        ctxt.getPackageManager()
            .getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures[0];

    return(toHexStringWithColons(md.digest(sig.toByteArray())));
  }

  // based on https://mcmap.net/q/24240/-in-java-how-do-i-convert-a-byte-array-to-a-string-of-hex-digits-while-keeping-leading-zeros-duplicate

  public static String toHexStringWithColons(byte[] bytes) {
    char[] hexArray=
        { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
            'C', 'D', 'E', 'F' };
    char[] hexChars=new char[(bytes.length * 3) - 1];
    int v;

    for (int j=0; j < bytes.length; j++) {
      v=bytes[j] & 0xFF;
      hexChars[j * 3]=hexArray[v / 16];
      hexChars[j * 3 + 1]=hexArray[v % 16];

      if (j < bytes.length - 1) {
        hexChars[j * 3 + 2]=':';
      }
    }

    return new String(hexChars);
  }
}

I use PackageManager to get the Signature for the given package, given its application ID. Despite the name, Signature is really the public key of the keypair used to sign the app. The output of getSignatureHash() is a colon-delimited set of hex character pairs, representing the SHA256 hash of the public key... the same value you get from using the Java 7+ keytool command.

In your case, you are trying to determine, on the fly, whether an incoming operation is from the desired app, and if that desired app is the right one (versus repackaged malware, for example). In your case, Binder.getCallingUid() will give you the Linux UID of the app that triggered the IPC that triggered your code. PackageManager can give you the application ID of the app for that UID (ignoring android:sharedUserId scenarios). You would then see if that application ID is the expected value, and if it is, check to see if the hashed signing key is the expected value. If either test fails, in the words of an arm-flailing robot, "Danger, Will Robinson! Danger!".

One significant caveat is that some developers will publish apps through channels where those apps get re-signed. Most notable here is the Amazon AppStore for Android, where Amazon intentionally wraps your app in their own quasi-DRM and signs that app using a key that they generate on your behalf. That's not the same key that you would necessarily be using elsewhere, so there may be multiple valid signature hashes that you would need to compare.

Nakasuji answered 31/8, 2015 at 12:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.