Just An Application

August 12, 2014

And Another One: Part Nine — JarVerifier.verifyCertificate

File

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

Source

    ...
    
    private void verifyCertificate(String certFile) {
        // Found Digital Sig, .SF should already have been read
        String signatureFile = certFile.substring(0, certFile.lastIndexOf('.'))
                + ".SF";
        byte[] sfBytes = metaEntries.get(signatureFile);
        if (sfBytes == null) {
            return;
        }
    
        byte[] manifest = metaEntries.get(JarFile.MANIFEST_NAME);
        // Manifest entry is required for any verifications.
        if (manifest == null) {
            return;
        }
    
        byte[] sBlockBytes = metaEntries.get(certFile);
        try {
            Certificate[] signerCertChain = JarUtils.verifySignature(
                    new ByteArrayInputStream(sfBytes),
                    new ByteArrayInputStream(sBlockBytes));
            /*
             * Recursive call in loading security provider related class which
             * is in a signed JAR.
             */
            if (metaEntries == null) {
                return;
            }
            if (signerCertChain != null) {
                certificates.put(signatureFile, signerCertChain);
            }
        } catch (IOException e) {
            return;
        } catch (GeneralSecurityException e) {
            throw failedVerification(jarName, signatureFile);
        }
    
        // Verify manifest hash in .sf file
        Attributes attributes = new Attributes();
        HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
        try {
            InitManifest im = new InitManifest(sfBytes, attributes);
            im.initEntries(entries, null);
        } catch (IOException e) {
            return;
        }
        
        // Do we actually have any signatures to look at?
        if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
            return;
        }
        
        boolean createdBySigntool = false;
        String createdBy = attributes.getValue("Created-By");
        if (createdBy != null) {
            createdBySigntool = createdBy.indexOf("signtool") != -1;
        }
        
        // Use .SF to verify the mainAttributes of the manifest
        // If there is no -Digest-Manifest-Main-Attributes entry in .SF
        // file, such as those created before java 1.5, then we ignore
        // such verification.
        if (mainAttributesEnd > 0 && !createdBySigntool) {
            String digestAttribute = "-Digest-Manifest-Main-Attributes";
            if (!verify(attributes, digestAttribute, manifest, 0, mainAttributesEnd, false, true)) {
                throw failedVerification(jarName, signatureFile);
            }
        }
        
        // Use .SF to verify the whole manifest.
        String digestAttribute = createdBySigntool ? "-Digest"
                : "-Digest-Manifest";
        if (!verify(attributes, digestAttribute, manifest, 0, manifest.length,
                false, false)) {
            Iterator<Map.Entry<String, Attributes>> it = entries.entrySet()
                        .iterator();
            while (it.hasNext()) {
                Map.Entry<String, Attributes> entry = it.next();
                Manifest.Chunk chunk = man.getChunk(entry.getKey());
                if (chunk == null) {
                    return;
                }
                if (!verify(entry.getValue(), "-Digest", manifest,
                        chunk.start, chunk.end, createdBySigntool, false)) {
                    throw invalidDigest(signatureFile, entry.getKey(), jarName);
                }
            }
        }
        metaEntries.put(signatureFile, null);
        signatures.put(signatureFile, entries);
    }
                
    ...

The interesting thing about the verifyCertificate method is that it doesn’t, verify a certificate that is, or not so you would notice anyhow.

What it does do mostly is verify various digests, but it does call the method JarUtils.verifySignature first

    ...
    
            Certificate[] signerCertChain = JarUtils.verifySignature(
                    new ByteArrayInputStream(sfBytes),
                    new ByteArrayInputStream(sBlockBytes));
               
    ...

passing it the contents of the signature file (sfBytes) and the contents of the corresponding signed signature file (sBlockBytes).

The result of the call is assigned to the local variable signerCertChain implying that what it references is the certificate chain for the certificate of the signer of the signature file.

If signerCertChain is not null then the certificate chain is added to the Hashtable referenced by the instance variable certificates with the name of the signature file as the key.

If the call to JarUtils.verifySignature succeeds and irrespective of the return value the method continues.

    ...
    
    Attributes attributes = new Attributes();
    HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
    try {
        InitManifest im = new InitManifest(sfBytes, attributes);
        im.initEntries(entries, null);
    } catch (IOException e) {
        return;
    }
    
    ...

To make things interesting it creates an instance of the class InitManifest passing the constructor the contents of the signature file, which technically speaking isn’t a Manifest although it is in the same format as a Manifest, and the newly created Attributes instance.

The InitManifest constructor adds the main attributes specified in the sigature file to the Attributes instance it is passed.

The subsequent call to the InitManifest initEntries methods adds the per JAR entry attributes as specified by the signature file to the HashMap it is passed.

Given this signature file for example

    Signature-Version: 1.0
    SHA1-Digest-Manifest: Wwf3t6iV/0bkjCeC2UNNjeE1IKY=
    
    Name: res/drawable-xhdpi/ic_launcher.png
    SHA1-Digest: cIga++hy5wqjHl9IHSfbg8tqCug=
    
    Name: res/menu/main.xml
    SHA1-Digest: 4zwSAYv23t3kqpzCDB/SFXeI+fE=
    
    Name: AndroidManifest.xml
    SHA1-Digest: 8G+uHfdKM/lfP/Q+Zh70BrmkE+I=
    
    Name: res/drawable-mdpi/ic_launcher.png
    SHA1-Digest: VY7kOF8E3rn8EUTvQC/DcBEN6kQ=
    
    Name: res/drawable-hdpi/ic_launcher.png
    SHA1-Digest: stS7pUucSY0GgAVoESyO3Y7SanU=
    
    Name: res/layout/activity_main.xml
    SHA1-Digest: bl4WBWN5ooqlzio7tNRqDWt3oWM=
    
    Name: resources.arsc
    SHA1-Digest: 3fNn3pNM8CuP+AWqaqtOPm6hmE8=
    
    Name: classes.dex
    SHA1-Digest: X2Ae/+26mSKFTNACfTP/lcfXCow=
    
    Name: res/drawable-xxhdpi/ic_launcher.png
    SHA1-Digest: KKqaLh/DVvFp+v1KoaDw7xETvrI=

then the Attributes instance referenced by the attributes local variable would contain two attributes

    Attributes(
    {
        Name("Signature-Version")     =>  "1.0"
        Name("SHA1-Digest-Manifest")  =>  "Wwf3t6iV/0bkjCeC2UNNjeE1IKY="
    })

and the HashMap referenced by the local variable entries would look like this

    {
    
        "res/drawable-xhdpi/ic_launcher.png"    => Attributes({ Name("SHA1-Digest")   =>  "cIga++hy5wqjHl9IHSfbg8tqCug=" })
    
        "res/menu/main.xml"                     => Attributes({ Name("SHA1-Digest")   =>  "4zwSAYv23t3kqpzCDB/SFXeI+fE=" })
    
        "AndroidManifest.xml"                   => Attributes({ Name("SHA1-Digest")   =>  "8G+uHfdKM/lfP/Q+Zh70BrmkE+I=" })
    
        "res/drawable-mdpi/ic_launcher.png"     => Attributes({ Name("SHA1-Digest")   =>  "VY7kOF8E3rn8EUTvQC/DcBEN6kQ=" })
    
        "res/drawable-hdpi/ic_launcher.png"     => Attributes({ Name("SHA1-Digest")   =>  "stS7pUucSY0GgAVoESyO3Y7SanU=" })
    
        "res/layout/activity_main.xml"          => Attributes({ Name("SHA1-Digest")   =>  "bl4WBWN5ooqlzio7tNRqDWt3oWM=" })
    
        "resources.arsc"                        => Attributes({ Name("SHA1-Digest")   =>  "3fNn3pNM8CuP+AWqaqtOPm6hmE8=" })
    
        "classes.dex"                           => Attributes({ Name("SHA1-Digest")   =>  "X2Ae/+26mSKFTNACfTP/lcfXCow=" })
    
        "res/drawable-xxhdpi/ic_launcher.png"   => Attributes({ Name("SHA1-Digest")   =>  "KKqaLh/DVvFp+v1KoaDw7xETvrI=" })
    
    }

Once the method has parsed the signature file it is in a position to attempt to verify the digest of the entire Manifest and if that fails to attempt to verify individual sections of the Manifest.

If it is not possible to verify the digest of the entire Manifest and it is not possible to verify the all individual sections of the Manifest for which the signature file specifies a digest a SecurityException is thrown.

Otherwise, the mapping for the signature file in the metaEntries instance variable is removed, which is possibly unwise because as we have seen the readCertificates method which invoked this method is iterating over the key set of metaEntries.

Then the digests of the per JAR entry sections of the Manifest as specified by the signature file are added to the Hashtable referenced by the instance variable signatures, with the name of the signature file as the key.


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

1 Comment »

  1. […] for the key "DIGESTS.SF", the HashMap of per member attributes created during the call to the verifyCertificate method […]

    Pingback by And Another One: Part Twelve — JarVerifier.initEntry | Just An Application — August 14, 2014 @ 8:51 am


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: