Just An Application

August 12, 2014

And Another One: Part Ten — JarUtils.verifySignature

File

    $(ANDROID_SRC)/libcore/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java

Source

    ...
    
    public static Certificate[] verifySignature(InputStream signature, InputStream
            signatureBlock) throws IOException, GeneralSecurityException {
    
        BerInputStream bis = new BerInputStream(signatureBlock);
        ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
        SignedData signedData = info.getSignedData();
        if (signedData == null) {
            throw new IOException("No SignedData found");
        }
        Collection<org.apache.harmony.security.x509.Certificate> encCerts
                = signedData.getCertificates();
        if (encCerts.isEmpty()) {
            return null;
        }
        X509Certificate[] certs = new X509Certificate[encCerts.size()];
        int i = 0;
        for (org.apache.harmony.security.x509.Certificate encCert : encCerts) {
            certs[i++] = new X509CertImpl(encCert);
        }
        
        List<SignerInfo> sigInfos = signedData.getSignerInfos();
        SignerInfo sigInfo;
        if (!sigInfos.isEmpty()) {
            sigInfo = sigInfos.get(0);
        } else {
            return null;
        }
            
        // Issuer
        X500Principal issuer = sigInfo.getIssuer();
            
        // Certificate serial number
        BigInteger snum = sigInfo.getSerialNumber();
            
        // Locate the certificate
        int issuerSertIndex = 0;
        for (i = 0; i < certs.length; i++) {
            if (issuer.equals(certs[i].getIssuerDN()) &&
                    snum.equals(certs[i].getSerialNumber())) {
                issuerSertIndex = i;
                break;
            }
        }
        if (i == certs.length) { // No issuer certificate found
            return null;
        }
                
        if (certs[issuerSertIndex].hasUnsupportedCriticalExtension()) {
            throw new SecurityException("Can not recognize a critical extension");
        }
                
        // Get Signature instance
        final String daOid = sigInfo.getDigestAlgorithm();
        final String daName = sigInfo.getDigestAlgorithmName();
        final String deaOid = sigInfo.getDigestEncryptionAlgorithm();
                
        String alg = null;
        Signature sig = null;
                
        if (daOid != null && deaOid != null) {
            alg = daOid + "with" + deaOid;
            try {
                sig = Signature.getInstance(alg);
            } catch (NoSuchAlgorithmException e) {
            }
                
            // Try to convert to names instead of OID.
            if (sig == null) {
                final String deaName = sigInfo.getDigestEncryptionAlgorithmName();
                alg = daName + "with" + deaName;
                try {
                    sig = Signature.getInstance(alg);
                } catch (NoSuchAlgorithmException e) {
                }
            }
        }
                
        /*
         * TODO figure out the case in which we'd only use digestAlgorithm and
         * add a test for it.
         */
        if (sig == null && daOid != null) {
            alg = daOid;
            try {
                sig = Signature.getInstance(alg);
            } catch (NoSuchAlgorithmException e) {
            }
                
            if (sig == null && daName != null) {
                alg = daName;
                try {
                    sig = Signature.getInstance(alg);
                } catch (NoSuchAlgorithmException e) {
                }
            }
        }
                
        // We couldn't find a valid Signature type.
        if (sig == null) {
            return null;
        }
                
        sig.initVerify(certs[issuerSertIndex]);
                
        // If the authenticatedAttributes field of SignerInfo contains more than zero attributes,
        // compute the message digest on the ASN.1 DER encoding of the Attributes value.
        // Otherwise, compute the message digest on the data.
        List<AttributeTypeAndValue> atr = sigInfo.getAuthenticatedAttributes();
                
        byte[] sfBytes = new byte[signature.available()];
        signature.read(sfBytes);
                
        if (atr == null) {
            sig.update(sfBytes);
        } else {
            sig.update(sigInfo.getEncodedAuthenticatedAttributes());
                
            // If the authenticatedAttributes field contains the message-digest attribute,
            // verify that it equals the computed digest of the signature file
            byte[] existingDigest = null;
            for (AttributeTypeAndValue a : atr) {
                if (Arrays.equals(a.getType().getOid(), MESSAGE_DIGEST_OID)) {
                    if (existingDigest != null) {
                        throw new SecurityException("Too many MessageDigest attributes");
                    }
                    Collection<?> entries = a.getValue().getValues(ASN1OctetString.getInstance());
                    if (entries.size() != 1) {
                        throw new SecurityException("Too many values for MessageDigest attribute");
                    }
                    existingDigest = (byte[]) entries.iterator().next();
                }
            }
                    
            // RFC 3852 section 9.2: it authAttrs is present, it must have a
            // message digest entry.
            if (existingDigest == null) {
                throw new SecurityException("Missing MessageDigest in Authenticated Attributes");
            }
                    
            MessageDigest md = null;
            if (daOid != null) {
                md = MessageDigest.getInstance(daOid);
            }
            if (md == null && daName != null) {
                md = MessageDigest.getInstance(daName);
            }
            if (md == null) {
                return null;
            }
                    
            byte[] computedDigest = md.digest(sfBytes);
            if (!Arrays.equals(existingDigest, computedDigest)) {
                throw new SecurityException("Incorrect MD");
            }
        }
                    
        if (!sig.verify(sigInfo.getEncryptedDigest())) {
            throw new SecurityException("Incorrect signature");
        }
                    
        return createChain(certs[issuerSertIndex], certs);
    }

    ...

Typographically and conceptually the verifySignature method is a bit of a mess but what it is trying to do is quite simple.

The method has been passed the contents of a signature file and the contents of a signed signature file and it is trying to verify that the digital signature in the signed signature file is that of the accompanying signature file.

The signed signature file is in CMS/PKCS#7 format and contains a set of certificates, the identity of the signer, and the digital signature.

The method begins by decoding the signed signature file.

It then gets the list of certificates.

    ...
    
        Collection<org.apache.harmony.security.x509.Certificate> encCerts
               = signedData.getCertificates();
        if (encCerts.isEmpty()) {
            return null;
        }
        X509Certificate[] certs = new X509Certificate[encCerts.size()];
        int i = 0;
        for (org.apache.harmony.security.x509.Certificate encCert : encCerts) {
            certs[i++] = new X509CertImpl(encCert);
        }
        
        ...

It then gets the identity of the signer

    ...
    
        List<SignerInfo> sigInfos = signedData.getSignerInfos();
        SignerInfo sigInfo;
        if (!sigInfos.isEmpty()) {
            sigInfo = sigInfos.get(0);
        } else {
            return null;
        }
        
        // Issuer
        X500Principal issuer = sigInfo.getIssuer();
        
        // Certificate serial number
        BigInteger snum = sigInfo.getSerialNumber();
            
    ...

It now has the name of the issuer of a certificate (issuer) and the serial number of a certificate (snum).

Together, these should uniquely identity the certificate of the signer of the signature file.

At this point it gets a bit confusing.

    ...
    
        // Locate the certificate
        int issuerSertIndex = 0;
        for (i = 0; i < certs.length; i++) {
            if (issuer.equals(certs[i].getIssuerDN()) &&
                    snum.equals(certs[i].getSerialNumber())) {
                issuerSertIndex = i;
                break;
            }
        }
        if (i == certs.length) { // No issuer certificate found
            return null;
        }
        
    ...

Even ignoring the typo (?) the variable name

    issuerSertIndex

is plain wrong.

The class org.apache.harmony.security.pkcs7.SignerInfo is written against the RFC 2315 version of CMS/PKCS#7[1], at which point the definition of SignerInfo looked like this

    SignerInfo ::= SEQUENCE {
        version                                 Version,
        issuerAndSerialNumber                   IssuerAndSerialNumber,
        digestAlgorithm                         DigestAlgorithmIdentifier,
        authenticatedAttributes    [0] IMPLICIT Attributes OPTIONAL,
        digestEncryptionAlgorithm               DigestEncryptionAlgorithmIdentifier,
        encryptedDigest                         EncryptedDigest,
        unauthenticatedAttributes  [1] IMPLICIT Attributes OPTIONAL
    }

and the issuerAndSerialNumber field is described as follows

issuerAndSerialNumber specifies the signer’s certificate (and thereby the signer’s distinguished name and public key) by issuer distinguished name and issuer-specific serial number.

So the method is looking for the signer’s certificate NOT the issuer’s, there is after all no point in looking for the issuer’s certificate when you are trying to verify the signature.

If the signer’s certificate is found then the method attempts to verify the digital signature in the signed signature file using the public key in the signer’s certificate.

If this succeeds the method returns the result of calling the method collectCertificates passing it the signer’s certificate and set of certificates from the signed signature file.

    ...
    
        return createChain(certs[issuerSertIndex], certs);
    
    ...

Notes

  1. The documentation comment for this method refers to RFC 2315, but elsewhere in the method there is a comment[2] with a reference to section 9.2 of RFC 3852 which is a later version of CMS/PKCS#7 so amongst other things it is not clear which version of the specification this method has actually been written against.

    This is significant because amongst other things the definition of SignerInfo changed between the two versions.

    Given that the implementation of SignerInfo is incapable of dealing with the RFC 3852 version of SignerInfo correctly it is unclear why the later version of the specification is being referred to at all.

  2. The comment reads

            // RFC 3852 section 9.2: it authAttrs is present, it must have a
            // message digest entry.
    

    Section 9.2 of RFC 3852 pertains to AuthenticatedData not SignedData.

    The authAttrs field being referred to is a field of the AuthenticatedData ASN.1 type.

    The code containing the comment is working with the authenticatedAttributes field of the ASN.1 SignedInfo
    type (see above).

    To make things more interesting the authenticatedAttributes field was renamed to signedAttrs in RFC 3852.

    If it is going to refer to anything at all the comment ought to refer to section 9.3 of RFC 2315.


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. […] Hashtable will contain, for the key "DIGESTS.SF", the array of certificates returned by the JarUtils.verifySignature […]

    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: