Just An Application

August 17, 2014

And Another One: Part Seventeen — What Are Android Application Signatures For ?

Working out exactly why the PackageManagerService goes to all the trouble of acquiring an application’s signatures at installation time is easier said than done.

A top-down approach is rendered extremely difficult by the sheer size of the class,

The version of the file

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

which I am currently working with clocks in at

   10979

lines which is insane and makes the class effectively incomprehensible as a whole, not to mention unmaintainable.

How about bottom up ?

A search for the word

   signatures

comes up with a mere

   78

matches, one of which is

   compareSignatures

which looks as though it might be interesting.

Narrowing the search to

   compareSignatures

brings it down to

    12

Of these one is the method definition and the other eleven are calls to it so it looks as though it might be a good place to start at least.

1.0 PackageManagerService.compareSignatures

File

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

Source

    ...
    
    static int compareSignatures(Signature[] s1, Signature[] s2) {
        if (s1 == null) {
            return s2 == null
                    ? PackageManager.SIGNATURE_NEITHER_SIGNED
                    : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
        }
        if (s2 == null) {
            return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
        }
        HashSet<Signature> set1 = new HashSet<Signature>();
        for (Signature sig : s1) {
            set1.add(sig);
        }
        HashSet<Signature> set2 = new HashSet<Signature>();
        for (Signature sig : s2) {
            set2.add(sig);
        }
        // Make sure s2 contains all signatures in s1.
        if (set1.equals(set2)) {
            return PackageManager.SIGNATURE_MATCH;
        }
        return PackageManager.SIGNATURE_NO_MATCH;
    }

    ...

As written the method is pretty opaque.

Re-written like this it is hopefully clearer what is going on.

    ...
    
    static int compareSignatures(Signature[] s1, Signature[] s2)
    {
        if ((s1 == null) && (s2 == null))
        {
            return PackageManager.SIGNATURE_NEITHER_SIGNED;
        }
        else
        if (s1 == null)
        {
            return PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
        }
        else
        if (s2 == null)
        {
             return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
        }
        else
        {
            Set<Signature>   set1 = makeSignatureSet(s1);
            Set<Signature>   set2 = makeSignatureSet(s2);
                
            return
                set1.equals(set2)
                        ?
                        PackageManager.SIGNATURE_MATCH
                        :
                        PackageManager.SIGNATURE_NO_MATCH;
        }
    }
                
    private static Set<Signature> makeSignatureSet(Signature[] theSignatures)
    {
        HashSet<Signature> set = new HashSet<Signature>();
                        
        for (Signature s : theSignatures)
        {
            set.add(s);
        }
        return set;
    }
    
    ...

The fourth case is the interesting one.

The two arrays of Signatures are converted to Sets of Signatures.

Only if the two Sets are equal are the two arrays of Signatures considered to match.

2.0 The Semantics Of The compareSignatures Method

At first glance the semantics of the compareSignatures method may look very straight forward but appearances can be deceptive.

The behaviour of the method is completely determined by the behaviour of the Sets that are constructed and compared.

To be sure what the method is doing it is necessary to understand how Sets of Signatures behave.

2.1 Set Construction

The documentation for the add method of the java.util.Set interface states

Adds the specified object to this set. The set is not modified if it already contains the object.

which means what exactly ?

The documentation for the add method of the java.util.HashSet class manages, if anything, to be even more vague

Adds the specified object to this HashSet if not already present.

so there is nothing for it but to look at the source

File

    $(ANDROID_SRC)/libcore/luni/src/main/java/java/util/HashSet.java

Source

    ...
    
    @Override
    public boolean add(E object) {
        return backingMap.put(object, this) == null;
    }

    ...

Oh great !

The instance variable backingMap is declared like this.

    ...
    
    transient HashMap<E, HashSet<E>> backingMap;
        
    ...

so its off to the HashMap source we go

File

    $(ANDROID_SRC)/libcore/luni/src/main/java/java/util/HashMap.java

Source

    ...
    
    @Override public V put(K key, V value) {
        if (key == null) {
            return putValueForNullKey(value);
        }

        int hash = secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                preModify(e);
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }
        
        // No entry for (non-null) key is present; create one
        modCount++;
        if (size++ > threshold) {
            tab = doubleCapacity();
            index = hash & (tab.length - 1);
        }
        addNewEntry(key, value, hash, index);
        return null;
    }
    
    ...
    
    static int secondaryHash(Object key) {
        int hash = key.hashCode();
        hash ^= (hash >>> 20) ^ (hash >>> 12);
        hash ^= (hash >>> 7) ^ (hash >>> 4);
        return hash;
    }

    ...

On the basis of this we can conclude that an object is considered to be present in a HashSet on the basis of the values returned by that object's equals and hashCode methods.

Whilest this is what you might expect, as we have seen, in this code base what you might expect and what you actually get are not always the same thing.

2.2 Set Equality

The HashSet implementation inherits the equals method from java.util.AbstractSet where it is defined like this

File

    $(ANDROID_SRC)/libcore/luni/src/main/java/java/util/AbstractSet.java

Source

    ...

    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (object instanceof Set) {
            Set<?> s = (Set<?>) object;
    
            try {
                return size() == s.size() && containsAll(s);
            } catch (NullPointerException ignored) {
                return false;
            } catch (ClassCastException ignored) {
                return false;
            }
        }
        return false;
    }
    
    ...

AbstractSet in turn inherits its implementation of the containsAll method from java.util.AbstractCollection where it is defined like this

File

    $(ANDROID_SRC)/libcore/luni/src/main/java/java/util/AbstractCollection.java

Source

    ...
    
    public boolean containsAll(Collection<?> collection) {
        Iterator<?> it = collection.iterator();
        while (it.hasNext()) {
            if (!contains(it.next())) {
                return false;
            }
        }
        return true;
    }
            
    ... 

Neither HashSet nor AbstractSet define the method contains so the AbstractCollection implementation will be used.

It is defined like this

File

    $(ANDROID_SRC)/libcore/luni/src/main/java/java/util/AbstractCollection.java

Source

    ...
    
    public boolean contains(Object object) {
        Iterator<E> it = iterator();
        if (object != null) {
            while (it.hasNext()) {
                if (object.equals(it.next())) {
                    return true;
                }
            }
        } else {
            while (it.hasNext()) {
                if (it.next() == null) {
                    return true;
                }
            }
        }
        return false;
    }
    
    ... 

The contains method calls the equals of its argument if it is not null.

This is what you might expect but now we know for certain.

3.0 The Semantics Of Signatures

It turns out that the behaviour of the compareSignatures method is determined by the implementation, or lack of one, of the equals method for the Signature class.

The question is does the Signature class define an equals method and if so how is it defined.

3.1 Signature.equals

The answer is yes, and like this

File

    $(ANDROID_SRC)/frameworks/base/core/java/android/content/pm/Signature.java

Source

    ...
    
    @Override
    public boolean equals(Object obj) {
        try {
            if (obj != null) {
                Signature other = (Signature)obj;
                return this == other || Arrays.equals(mSignature, other.mSignature);
            }
        } catch (ClassCastException e) {
        }
        return false;
    }

    ...

Two Signatures are equal if their mSignature byte arrays are equal, which is to say, byte for byte identical.

The mSignature byte array is actually a byte encoded certificate so two Signatures are equal if they have been constructed with identical encodings of the same certificate.

3.2 Signature.hashCode

Since we are in the area we might as well have a look at the implementation of the hashCode method.

File

    $(ANDROID_SRC)/frameworks/base/core/java/android/content/pm/Signature.java

Source

3.3 Signature.hashCode

    ...

    @Override
    public int hashCode() {
        if (mHaveHashCode) {
            return mHashCode;
        }
        mHashCode = Arrays.hashCode(mSignature);
        mHaveHashCode = true;
        return mHashCode;
    }
    
    ...

The hash of a Signature is actually that of its encoded certificate.

4.0 The Semantics Of The compareSignatures Method Concluded

And there you have it, after all of that it turns out that the semantics of the compareSignatures method are very simple.

Two arrays of Signatures match, if and only if, after the elimination of duplicate Signatures the resulting sets are identical.

One of the rather strange implications of this is that if you want the signatures of different applications to match you had better make sure to include exactly the same number of certificates in the signed certificate file of each one.

If you include the legitimate cerificate chain

    A B C

where C is the issuer of B which is the issuer of A, and C is not self-signed, in the signed certificate file of one application, but for some reason you include the legitimate certificate chain

    A B C D

where D is the issuer of C, in the signed certificate file of another application, then you end up with two applications whose signatures do not match despite the fact that you actually signed both of them using the same private key, which is a bit odd really.

This applies equally to different versions of the same application.

It also implies that if one of the certificates in the chain you have included with an application expires you are must keep using it despite the fact that it has expired or, again, you end up with non-matching signatures even if the public keys involved do not change.

The simplest thing you can do is to include only the certificate specifying the public key corresponding to the private key with which you signed the signature file, although this does not help in the case of that certificate expiring.


Copyright (c) 2014 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog's author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

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: