Just An Application

August 21, 2014

And Another One: Part Eighteen — What Are Android Application Signatures For ? (Continued)

1.0 What Can You Prove Using the compareSignatures Method ?

If you have two Applications A and B and you pass the Signature array of Application A and the Signature array of Application B to the method

   compareSignatures

and it returns

   PackageManager.SIGNATURE_MATCH

then you have proved that Application A and Application B were signed with the same private key(s).

2.0 Proof

Notation

C(puk) the Certificate specifying the public key puk
PrK(kp) the Private Key of the key pair kp
PuK(kp) the Public Key of the key pair kp
S(x) the set of Signatures constructed from the array of Signatures of an Application x
Sig(c) the Signature constructed using the encoding of a Certificate c

Assertion

if an Application X is signed with the private key PrK(KP) then the signature set S(X) must contain Sig(C(PuK(KP))).

Assume we have two signed Applications A and B and a key pair KP.

Let the Application A be signed using PrK(KP).

Let S(A) contain the single Signature Sig(C(PuK(KP))).

Call the compareSignatures method passing it the Signature array of Application A and the Signature array of Application B.

If the call returns PackageManager.SIGNATURE_MATCH then

    S(A) == S(B)

Therefore S(B) must contain the single Signature Sig(C(PuK(KP))).

There is only one Signature in S(B) which is Sig(C(PuK(KP))) so given our assertion Application B must have been signed with PrK(KP).

Alternatively, let S(A) contain Sig(C(PuK(KP))) plus N Signatures constructed from the certificates of the associated certificate chain.

If S(B) is equal to S(A), S(B) must contain the N Signatures that are not Sig(C(PuK(KP))) plus one other Signature.

Since the sets are equal the other Signature must be Sig(C(PuK(KP))).

Note that the converse is not true.

If the compareSignatures method returns PackageManager.SIGNATURE_NO_MATCH it does not prove that the Applications were not signed with the same private key.

3.0 And Exactly Why Is That Useful ?

It is useful because it is precisely what the PackageManagerService wants to know during Application installation, which is an astonishing coincidence, or perhaps not.

Those aspects of the Android Application security model which are embodied by the PackageManagerService are all based on establishing whether two applications are signed with the same private key(s).

3.1 PackageManagerService.grantSignaturePermission

As an example consider the grantSignaturePermission method which is used by the PackageManagerService when installing an Application.

A Signature permission is

A permission that the system grants only if the requesting application is signed with the same certificate as the application that declared the permission. If the certificates match, the system automatically grants the permission without notifying the user or asking for the user’s explicit approval.

Substituting signed with the same private key for the endlessly misleading

signed with the same certificate

formulation, you can see that the compareSignatures method is going to tell you exactly what you need to know.

File

    $(ANDROID_SRC)/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java

Source

    ...
    
    private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
                                          BasePermission bp, HashSet<String> origPermissions) {
        boolean allowed;
        allowed = (compareSignatures(
                bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
                        == PackageManager.SIGNATURE_MATCH)
                || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
                        == PackageManager.SIGNATURE_MATCH);
        if (!allowed && (bp.protectionLevel
                & PermissionInfo.PROTECTION_FLAG_SYSTEM) != 0) {
            if (isSystemApp(pkg)) {
                // For updated system applications, a system permission
                // is granted only if it had been defined by the original application.
                if (isUpdatedSystemApp(pkg)) {
                    final PackageSetting sysPs = mSettings
                            .getDisabledSystemPkgLPr(pkg.packageName);
                    final GrantedPermissions origGp = sysPs.sharedUser != null
                            ? sysPs.sharedUser : sysPs;
                    if (origGp.grantedPermissions.contains(perm)) {
                        allowed = true;
                    } else {
                        // The system apk may have been updated with an older
                        // version of the one on the data partition, but which
                        // granted a new system permission that it didn't have
                        // before.  In this case we do want to allow the app to
                        // now get the new permission, because it is allowed by
                        // the system image.
                        allowed = false;
                        if (sysPs.pkg != null) {
                            for (int j=0;
                                    j<sysPs.pkg.requestedPermissions.size(); j++) {
                                if (perm.equals(
                                        sysPs.pkg.requestedPermissions.get(j))) {
                                    allowed = true;
                                    break;
                                }
                            }
                        }
                    }
                } else {
                    allowed = true;
                }
            }
        }
        if (!allowed && (bp.protectionLevel
                & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
                // For development permissions, a development permission
                // is granted only if it was already granted.
                allowed = origPermissions.contains(perm);
        }
        return allowed;
    }
    
    ...

The grantSignaturePermission method works with both and signature and signatureOrSystem permissions.

The bulk of the method is some special case code for a systemOrSignature permission, but you can see that before that the method is indeed using the compareSignatures method to decide whether to grant a signature permission.

3.2 PackageManager.setInstallerPackageName

The setInstallerPackageName method is the back-end method which implements the functionality accesible via the PackageManager method of the same name.

The documentation for the PackageManager version is as follows

Change the installer associated with a given package. There are limitations
on how the installer package can be changed; in particular:

  • A SecurityException will be thrown if installerPackageName
    is not signed with the same certificate as the calling application.

  • A SecurityException will be thrown if targetPackage already
    has an installer package, and that installer package is not signed with
    the same certificate as the calling application.

The appearance, yet again, of the stock phrase

signed with the same certificate

leads us to expect that the method will use the compareSignatures method and indeed it does.

File

    $(ANDROID_SRC)/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java

Source

    ...
    
    public void setInstallerPackageName(String targetPackage, String installerPackageName) {
        final int uid = Binder.getCallingUid();
        // writer
        synchronized (mPackages) {
            PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage);
            if (targetPackageSetting == null) {
                throw new IllegalArgumentException("Unknown target package: " + targetPackage);
            }
    
            PackageSetting installerPackageSetting;
            if (installerPackageName != null) {
                installerPackageSetting = mSettings.mPackages.get(installerPackageName);
                if (installerPackageSetting == null) {
                    throw new IllegalArgumentException("Unknown installer package: "
                            + installerPackageName);
                }
            } else {
                installerPackageSetting = null;
            }
    
            Signature[] callerSignature;
            Object obj = mSettings.getUserIdLPr(uid);
            if (obj != null) {
                if (obj instanceof SharedUserSetting) {
                    callerSignature = ((SharedUserSetting)obj).signatures.mSignatures;
                } else if (obj instanceof PackageSetting) {
                    callerSignature = ((PackageSetting)obj).signatures.mSignatures;
                } else {
                    throw new SecurityException("Bad object " + obj + " for uid " + uid);
                }
            } else {
                throw new SecurityException("Unknown calling uid " + uid);
            }
    
            // Verify: can't set installerPackageName to a package that is
            // not signed with the same cert as the caller.
            if (installerPackageSetting != null) {
                if (compareSignatures(callerSignature,
                        installerPackageSetting.signatures.mSignatures)
                        != PackageManager.SIGNATURE_MATCH) {
                    throw new SecurityException(
                            "Caller does not have same cert as new installer package "
                            + installerPackageName);
                }
            }
    
            // Verify: if target already has an installer package, it must
            // be signed with the same cert as the caller.
            if (targetPackageSetting.installerPackageName != null) {
                PackageSetting setting = mSettings.mPackages.get(
                        targetPackageSetting.installerPackageName);
                // If the currently set package isn't valid, then it's always
                // okay to change it.
                if (setting != null) {
                    if (compareSignatures(callerSignature,
                            setting.signatures.mSignatures)
                            != PackageManager.SIGNATURE_MATCH) {
                        throw new SecurityException(
                            "Caller does not have same cert as old installer package "
                            + targetPackageSetting.installerPackageName);
                    }
                }
            }
    
            // Okay!
            targetPackageSetting.installerPackageName = installerPackageName;
            scheduleWriteSettingsLocked();
        }
    }
    
    ...

4.0 But What About The Certificate Chain Verification Issue ?

The compareSignatures method is immune to the certificate chain verification issue because it tests for the equality of sets of Signatures.

You cannot construct an Application X such that S(X) is equal to S(A) for some application A if you do not know the private key PrK(KPA) used to sign A.

The failure to verify the certificate chain means that until recently (?) you could, with a little effort, add Application A‘s entire certificate chain to the signed signature file of Application X.

The result would be that

    S(X)

would contain the Signatures corresponding to the certificates in Application A‘s certificate chain.

However, since you do not know

    PrK(KPA)

you must sign it with a different private key

    PrK(KPX)

As a result, as long as the assertion above holds then

    Sig(C(PuK(KPX)))

must be in

    S(X)

but it is not in

    S(A)

therefore the two sets are not equal.

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: