Just An Application

August 21, 2014

And Another One: Part Twenty — Abstraction Failure Or Why Does The Class android.content.pm.Signature Exist ?

I have already quoted the class documentation comment for the android.content.pm.Signature class before but here it is again.

Opaque, immutable representation of a signature associated with an application package.

It is the comment which appeared in the first version of the class and despite subsequent changes it has never been updated.

Even in the original version it was not true as the class defined this method

    ...
    
    /**
     * @return the contents of this signature as a byte array.
     */
    public byte[] toByteArray() {
        byte[] bytes = new byte[mSignature.length];
        System.arraycopy(mSignature, 0, bytes, 0, mSignature.length);
        return bytes;
    }
    
    ...

Why should it be possible to turn the contents of something supposedly opaque into a byte array ?

It looks fairly innocuous but its a major abstraction failure all on its own so lets take the documentation at its word and get rid of it, and while we are at it we will get rid of

    public char[] toChars()

as well as

    public char[] toChars(char[] existingArray, int[] outLen)

not to mention

    public String toCharsString()

just to be on the safe side.

For an opaque object it defines a remarkable number of ways of finding out whats in it, and we’re not finished yet because its

    Parcelable

and you never know, if somebody got really desperate they might write it to a Parcel then go and read it out themselves as a byte array or something.

Best not to put temptation in their way, so we’ll get rid of all of that as well.

Now what can you do with a Signature as originally defined which is genuinely opaque and immutable ?

Not much to be honest.

What’s left are the equals and hashCode methods and as we have already seen all they do is sub-contract the heavy-lifting to the java.util.Arrays class.

To be fair in its modified form it hides the fact that its really just a wrapper around byte array pretty well but nothing about it realls shouts out Signature ! does it ?

Still its an improvement on its original form where it completely fails to hide the fact that its really just a wrapper around a byte array by giving you access to it.

So, to return to the original question, why does it exist at all in its current form, or to put it another why isn’t just a wrapper a Set of encoded certificates ?

The Past Is A Foreign Country, They Do Things Differently There

One possible explanation is that once upon a time Application signatures used to be defined in a different way and that the Signature class has been re-purposed or something.

Looking for the contemporary version of the PackageManegerService class we discover it in the package.

    com.android.server

That’s right, there was a time when the PackageManegerService class was not sufficiently important to warrant its own package ! Hard to believe isn’t it ?

Now lets see how they used to compare Signatures in those days.

Well they didn’t use compareSignatures thats for sure because its not defined.

So how did grantSignaturePermission work if there was no compareSignatures method ? Answer it didn’t because its not defined.

So where are signature permissions granted then ?

A ha, grantPermissionsLP and its calling checkSignaturesLP, should have guessed, and here it is

    ...
    
    int checkSignaturesLP(PackageParser.Package p1, PackageParser.Package p2) {
        if (p1.mSignatures == null) {
            return p2.mSignatures == null
                    ? PackageManager.SIGNATURE_NEITHER_SIGNED
                    : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
        }
        if (p2.mSignatures == null) {
            return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
        }
        final int N1 = p1.mSignatures.length;
        final int N2 = p2.mSignatures.length;
        for (int i=0; i<N1; i++) {
            boolean match = false;
            for (int j=0; j<N2; j++) {
                if (p1.mSignatures[i].equals(p2.mSignatures[j])) {
                    match = true;
                    break;
                }
            }
            if (!match) {
                return PackageManager.SIGNATURE_NO_MATCH;
            }
        }
        return PackageManager.SIGNATURE_MATCH;
    }

    ...

This looks familiar.

Thats all almost exactly the code structure same as the definition of the compareSignatures method now except that the semantics of Application signatures are completely different !

No Sets there, that’s a ‘member in common’ test !

Whoah, scary ! To be honest I wasn’t expecting that !

(Enough with the exclamation marks.)

Presumably Android Application Signature arrays were constructed somewhat differently in those days or that test does not make a whole lot of sense.

Nope, PackageParser.loadCertificates looks to be nigh on identical.

OK, so at least they used to verify the certificate chains properly surely. No they didn’t do that either. JarUtils.createChain is identical.

Oh dear.

The full implications of that are left as an exercise for the reader but bear in mind that quite possibly there is something somewhere doing something which means it is not as bad as it looks.

Code Rot

Given the way it seems Signature instances were used originally it turns out that the Signature class is not quite as pointless as it appears to be now.

It doesn’t explain why the abstraction was completely broken from day one but it does explain why, for example, the class represents a single encoded certificate rather than a set of them.

The combination of the class being public and the abstraction failure presumably meant that it was felt that it was not possible to substantially modify its behaviour and what it represented.

However, given the major change in semantics that occurred, changing the class substantively so that any code that relied on it broke would have had the benefit of revealing what if anything was relying on the assumption that it contained a certificate and that that certificate could be accessed.

Having said that it does not explain why at least internally a class could not have been defined that simply encapulated all the details of what constitutes an Application’s signature, how one is constructed and how they are compared.

Afterword

When I started writing this although I thought it possible that something about how Android Application signatures worked might have changed between the point at which the Signature class was originally defined and now I didn’t really didn’t know one way or another. It was just a possible explanation for the existence of a fairly pointless class.

The earliest version of the Android source I have immediately accessible is the version I changed to get running standalone, that is, on a standard JVM rather than Dalvik, which dates from late 2008.

Although I clearly changed the PackageManagerService class for some reason at that time I have no recollection of looking at this area at all, so I was genuinely surprised to discover

  1. that it really had changed a lot, and

  2. that at that point it seems it was completely broken

The earliest version of the PackageManagerService class I have locally that constructs sets of Signatures in order to perform a comparison is from March 2011 where it is being done by the checkSignaturesLP method.

At that point there is still no compareSignatures method defined.


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.

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.

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.

Create a free website or blog at WordPress.com.

%d bloggers like this: