Home | History | Annotate | Download | only in pkcs
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
      4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      5  *
      6  * This code is free software; you can redistribute it and/or modify it
      7  * under the terms of the GNU General Public License version 2 only, as
      8  * published by the Free Software Foundation.  Oracle designates this
      9  * particular file as subject to the "Classpath" exception as provided
     10  * by Oracle in the LICENSE file that accompanied this code.
     11  *
     12  * This code is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     15  * version 2 for more details (a copy is included in the LICENSE file that
     16  * accompanied this code).
     17  *
     18  * You should have received a copy of the GNU General Public License version
     19  * 2 along with this work; if not, write to the Free Software Foundation,
     20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     21  *
     22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     23  * or visit www.oracle.com if you need additional information or have any
     24  * questions.
     25  */
     26 
     27 package sun.security.pkcs;
     28 
     29 import java.io.InputStream;
     30 import java.io.ByteArrayInputStream;
     31 import java.io.OutputStream;
     32 import java.io.IOException;
     33 import java.math.BigInteger;
     34 import java.security.cert.X509Certificate;
     35 import java.security.*;
     36 import java.util.ArrayList;
     37 
     38 import sun.security.util.*;
     39 import sun.security.x509.AlgorithmId;
     40 import sun.security.x509.X500Name;
     41 import sun.security.x509.KeyUsageExtension;
     42 import sun.security.x509.PKIXExtensions;
     43 import sun.misc.HexDumpEncoder;
     44 
     45 /**
     46  * A SignerInfo, as defined in PKCS#7's signedData type.
     47  *
     48  * @author Benjamin Renaud
     49  */
     50 public class SignerInfo implements DerEncoder {
     51 
     52     BigInteger version;
     53     X500Name issuerName;
     54     BigInteger certificateSerialNumber;
     55     AlgorithmId digestAlgorithmId;
     56     AlgorithmId digestEncryptionAlgorithmId;
     57     byte[] encryptedDigest;
     58 
     59     PKCS9Attributes authenticatedAttributes;
     60     PKCS9Attributes unauthenticatedAttributes;
     61 
     62     public SignerInfo(X500Name  issuerName,
     63                       BigInteger serial,
     64                       AlgorithmId digestAlgorithmId,
     65                       AlgorithmId digestEncryptionAlgorithmId,
     66                       byte[] encryptedDigest) {
     67         this.version = BigInteger.ONE;
     68         this.issuerName = issuerName;
     69         this.certificateSerialNumber = serial;
     70         this.digestAlgorithmId = digestAlgorithmId;
     71         this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
     72         this.encryptedDigest = encryptedDigest;
     73     }
     74 
     75     public SignerInfo(X500Name  issuerName,
     76                       BigInteger serial,
     77                       AlgorithmId digestAlgorithmId,
     78                       PKCS9Attributes authenticatedAttributes,
     79                       AlgorithmId digestEncryptionAlgorithmId,
     80                       byte[] encryptedDigest,
     81                       PKCS9Attributes unauthenticatedAttributes) {
     82         this.version = BigInteger.ONE;
     83         this.issuerName = issuerName;
     84         this.certificateSerialNumber = serial;
     85         this.digestAlgorithmId = digestAlgorithmId;
     86         this.authenticatedAttributes = authenticatedAttributes;
     87         this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
     88         this.encryptedDigest = encryptedDigest;
     89         this.unauthenticatedAttributes = unauthenticatedAttributes;
     90     }
     91 
     92     /**
     93      * Parses a PKCS#7 signer info.
     94      */
     95     public SignerInfo(DerInputStream derin)
     96         throws IOException, ParsingException
     97     {
     98         this(derin, false);
     99     }
    100 
    101     /**
    102      * Parses a PKCS#7 signer info.
    103      *
    104      * <p>This constructor is used only for backwards compatibility with
    105      * PKCS#7 blocks that were generated using JDK1.1.x.
    106      *
    107      * @param derin the ASN.1 encoding of the signer info.
    108      * @param oldStyle flag indicating whether or not the given signer info
    109      * is encoded according to JDK1.1.x.
    110      */
    111     public SignerInfo(DerInputStream derin, boolean oldStyle)
    112         throws IOException, ParsingException
    113     {
    114         // version
    115         version = derin.getBigInteger();
    116 
    117         // issuerAndSerialNumber
    118         DerValue[] issuerAndSerialNumber = derin.getSequence(2);
    119         byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray();
    120         issuerName = new X500Name(new DerValue(DerValue.tag_Sequence,
    121                                                issuerBytes));
    122         certificateSerialNumber = issuerAndSerialNumber[1].getBigInteger();
    123 
    124         // digestAlgorithmId
    125         DerValue tmp = derin.getDerValue();
    126 
    127         digestAlgorithmId = AlgorithmId.parse(tmp);
    128 
    129         // authenticatedAttributes
    130         if (oldStyle) {
    131             // In JDK1.1.x, the authenticatedAttributes are always present,
    132             // encoded as an empty Set (Set of length zero)
    133             derin.getSet(0);
    134         } else {
    135             // check if set of auth attributes (implicit tag) is provided
    136             // (auth attributes are OPTIONAL)
    137             if ((byte)(derin.peekByte()) == (byte)0xA0) {
    138                 authenticatedAttributes = new PKCS9Attributes(derin);
    139             }
    140         }
    141 
    142         // digestEncryptionAlgorithmId - little RSA naming scheme -
    143         // signature == encryption...
    144         tmp = derin.getDerValue();
    145 
    146         digestEncryptionAlgorithmId = AlgorithmId.parse(tmp);
    147 
    148         // encryptedDigest
    149         encryptedDigest = derin.getOctetString();
    150 
    151         // unauthenticatedAttributes
    152         if (oldStyle) {
    153             // In JDK1.1.x, the unauthenticatedAttributes are always present,
    154             // encoded as an empty Set (Set of length zero)
    155             derin.getSet(0);
    156         } else {
    157             // check if set of unauth attributes (implicit tag) is provided
    158             // (unauth attributes are OPTIONAL)
    159             if (derin.available() != 0
    160                 && (byte)(derin.peekByte()) == (byte)0xA1) {
    161                 unauthenticatedAttributes =
    162                     new PKCS9Attributes(derin, true);// ignore unsupported attrs
    163             }
    164         }
    165 
    166         // all done
    167         if (derin.available() != 0) {
    168             throw new ParsingException("extra data at the end");
    169         }
    170     }
    171 
    172     public void encode(DerOutputStream out) throws IOException {
    173 
    174         derEncode(out);
    175     }
    176 
    177     /**
    178      * DER encode this object onto an output stream.
    179      * Implements the <code>DerEncoder</code> interface.
    180      *
    181      * @param out
    182      * the output stream on which to write the DER encoding.
    183      *
    184      * @exception IOException on encoding error.
    185      */
    186     public void derEncode(OutputStream out) throws IOException {
    187         DerOutputStream seq = new DerOutputStream();
    188         seq.putInteger(version);
    189         DerOutputStream issuerAndSerialNumber = new DerOutputStream();
    190         issuerName.encode(issuerAndSerialNumber);
    191         issuerAndSerialNumber.putInteger(certificateSerialNumber);
    192         seq.write(DerValue.tag_Sequence, issuerAndSerialNumber);
    193 
    194         digestAlgorithmId.encode(seq);
    195 
    196         // encode authenticated attributes if there are any
    197         if (authenticatedAttributes != null)
    198             authenticatedAttributes.encode((byte)0xA0, seq);
    199 
    200         digestEncryptionAlgorithmId.encode(seq);
    201 
    202         seq.putOctetString(encryptedDigest);
    203 
    204         // encode unauthenticated attributes if there are any
    205         if (unauthenticatedAttributes != null)
    206             unauthenticatedAttributes.encode((byte)0xA1, seq);
    207 
    208         DerOutputStream tmp = new DerOutputStream();
    209         tmp.write(DerValue.tag_Sequence, seq);
    210 
    211         out.write(tmp.toByteArray());
    212     }
    213 
    214 
    215 
    216     /*
    217      * Returns the (user) certificate pertaining to this SignerInfo.
    218      */
    219     public X509Certificate getCertificate(PKCS7 block)
    220         throws IOException
    221     {
    222         return block.getCertificate(certificateSerialNumber, issuerName);
    223     }
    224 
    225     /*
    226      * Returns the certificate chain pertaining to this SignerInfo.
    227      */
    228     public ArrayList<X509Certificate> getCertificateChain(PKCS7 block)
    229         throws IOException
    230     {
    231         X509Certificate userCert;
    232         userCert = block.getCertificate(certificateSerialNumber, issuerName);
    233         if (userCert == null)
    234             return null;
    235 
    236         ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
    237         certList.add(userCert);
    238 
    239         X509Certificate[] pkcsCerts = block.getCertificates();
    240         if (pkcsCerts == null
    241             || userCert.getSubjectDN().equals(userCert.getIssuerDN())) {
    242             return certList;
    243         }
    244 
    245         Principal issuer = userCert.getIssuerDN();
    246         int start = 0;
    247         while (true) {
    248             boolean match = false;
    249             int i = start;
    250             while (i < pkcsCerts.length) {
    251                 if (issuer.equals(pkcsCerts[i].getSubjectDN())) {
    252                     // next cert in chain found
    253                     certList.add(pkcsCerts[i]);
    254                     // if selected cert is self-signed, we're done
    255                     // constructing the chain
    256                     if (pkcsCerts[i].getSubjectDN().equals(
    257                                             pkcsCerts[i].getIssuerDN())) {
    258                         start = pkcsCerts.length;
    259                     } else {
    260                         issuer = pkcsCerts[i].getIssuerDN();
    261                         X509Certificate tmpCert = pkcsCerts[start];
    262                         pkcsCerts[start] = pkcsCerts[i];
    263                         pkcsCerts[i] = tmpCert;
    264                         start++;
    265                     }
    266                     match = true;
    267                     break;
    268                 } else {
    269                     i++;
    270                 }
    271             }
    272             if (!match)
    273                 break;
    274         }
    275 
    276         return certList;
    277     }
    278 
    279     // Copied from com.sun.crypto.provider.OAEPParameters.
    280     private static String convertToStandardName(String internalName) {
    281         if (internalName.equals("SHA")) {
    282             return "SHA-1";
    283         } else if (internalName.equals("SHA224")) {
    284             return "SHA-224";
    285         } else if (internalName.equals("SHA256")) {
    286             return "SHA-256";
    287         } else if (internalName.equals("SHA384")) {
    288             return "SHA-384";
    289         } else if (internalName.equals("SHA512")) {
    290             return "SHA-512";
    291         } else {
    292             return internalName;
    293         }
    294     }
    295 
    296 
    297     SignerInfo verify(PKCS7 block, byte[] data)
    298     throws NoSuchAlgorithmException, SignatureException {
    299       try {
    300         return verify(block, new ByteArrayInputStream(data));
    301       } catch (IOException e) {
    302         // Ignore
    303         return null;
    304       }
    305     }
    306 
    307     /* Returns null if verify fails, this signerInfo if
    308        verify succeeds. */
    309     SignerInfo verify(PKCS7 block, InputStream inputStream)
    310     throws NoSuchAlgorithmException, SignatureException, IOException {
    311 
    312        try {
    313 
    314             ContentInfo content = block.getContentInfo();
    315             if (inputStream == null) {
    316                 inputStream = new ByteArrayInputStream(content.getContentBytes());
    317             }
    318 
    319             String digestAlgname = getDigestAlgorithmId().getName();
    320 
    321             InputStream dataSigned;
    322 
    323             // if there are authenticate attributes, get the message
    324             // digest and compare it with the digest of data
    325             if (authenticatedAttributes == null) {
    326                 dataSigned = inputStream;
    327             } else {
    328 
    329                 // first, check content type
    330                 ObjectIdentifier contentType = (ObjectIdentifier)
    331                        authenticatedAttributes.getAttributeValue(
    332                          PKCS9Attribute.CONTENT_TYPE_OID);
    333                 if (contentType == null ||
    334                     !contentType.equals(content.contentType))
    335                     return null;  // contentType does not match, bad SignerInfo
    336 
    337                 // now, check message digest
    338                 byte[] messageDigest = (byte[])
    339                     authenticatedAttributes.getAttributeValue(
    340                          PKCS9Attribute.MESSAGE_DIGEST_OID);
    341 
    342                 if (messageDigest == null) // fail if there is no message digest
    343                     return null;
    344 
    345                 MessageDigest md = MessageDigest.getInstance(
    346                         convertToStandardName(digestAlgname));
    347 
    348                 byte[] buffer = new byte[4096];
    349                 int read = 0;
    350                 while ((read = inputStream.read(buffer)) != -1) {
    351                   md.update(buffer, 0 , read);
    352                 }
    353                 byte[] computedMessageDigest = md.digest();
    354 
    355                 if (messageDigest.length != computedMessageDigest.length)
    356                     return null;
    357                 for (int i = 0; i < messageDigest.length; i++) {
    358                     if (messageDigest[i] != computedMessageDigest[i])
    359                         return null;
    360                 }
    361 
    362                 // message digest attribute matched
    363                 // digest of original data
    364 
    365                 // the data actually signed is the DER encoding of
    366                 // the authenticated attributes (tagged with
    367                 // the "SET OF" tag, not 0xA0).
    368                 dataSigned = new ByteArrayInputStream(authenticatedAttributes.getDerEncoding());
    369             }
    370 
    371             // put together digest algorithm and encryption algorithm
    372             // to form signing algorithm
    373             String encryptionAlgname =
    374                 getDigestEncryptionAlgorithmId().getName();
    375 
    376             // Workaround: sometimes the encryptionAlgname is actually
    377             // a signature name
    378             String tmp = AlgorithmId.getEncAlgFromSigAlg(encryptionAlgname);
    379             if (tmp != null) encryptionAlgname = tmp;
    380             String algname = AlgorithmId.makeSigAlg(
    381                     digestAlgname, encryptionAlgname);
    382 
    383             Signature sig = Signature.getInstance(algname);
    384             X509Certificate cert = getCertificate(block);
    385 
    386             if (cert == null) {
    387                 return null;
    388             }
    389             if (cert.hasUnsupportedCriticalExtension()) {
    390                 throw new SignatureException("Certificate has unsupported "
    391                                              + "critical extension(s)");
    392             }
    393 
    394             // Make sure that if the usage of the key in the certificate is
    395             // restricted, it can be used for digital signatures.
    396             // XXX We may want to check for additional extensions in the
    397             // future.
    398             boolean[] keyUsageBits = cert.getKeyUsage();
    399             if (keyUsageBits != null) {
    400                 KeyUsageExtension keyUsage;
    401                 try {
    402                     // We don't care whether or not this extension was marked
    403                     // critical in the certificate.
    404                     // We're interested only in its value (i.e., the bits set)
    405                     // and treat the extension as critical.
    406                     keyUsage = new KeyUsageExtension(keyUsageBits);
    407                 } catch (IOException ioe) {
    408                     throw new SignatureException("Failed to parse keyUsage "
    409                                                  + "extension");
    410                 }
    411 
    412                 boolean digSigAllowed = ((Boolean)keyUsage.get(
    413                         KeyUsageExtension.DIGITAL_SIGNATURE)).booleanValue();
    414 
    415                 boolean nonRepuAllowed = ((Boolean)keyUsage.get(
    416                         KeyUsageExtension.NON_REPUDIATION)).booleanValue();
    417 
    418                 if (!digSigAllowed && !nonRepuAllowed) {
    419                     throw new SignatureException("Key usage restricted: "
    420                                                  + "cannot be used for "
    421                                                  + "digital signatures");
    422                 }
    423             }
    424 
    425             PublicKey key = cert.getPublicKey();
    426             sig.initVerify(key);
    427 
    428             byte[] buffer = new byte[4096];
    429             int read = 0;
    430             while ((read = dataSigned.read(buffer)) != -1) {
    431               sig.update(buffer, 0 , read);
    432             }
    433             if (sig.verify(encryptedDigest)) {
    434                 return this;
    435             }
    436 
    437         } catch (IOException e) {
    438             throw new SignatureException("IO error verifying signature:\n" +
    439                                          e.getMessage());
    440 
    441         } catch (InvalidKeyException e) {
    442             throw new SignatureException("InvalidKey: " + e.getMessage());
    443 
    444         }
    445         return null;
    446     }
    447 
    448     /* Verify the content of the pkcs7 block. */
    449     SignerInfo verify(PKCS7 block)
    450     throws NoSuchAlgorithmException, SignatureException {
    451       return verify(block, (byte[])null);
    452     }
    453 
    454 
    455     public BigInteger getVersion() {
    456             return version;
    457     }
    458 
    459     public X500Name getIssuerName() {
    460         return issuerName;
    461     }
    462 
    463     public BigInteger getCertificateSerialNumber() {
    464         return certificateSerialNumber;
    465     }
    466 
    467     public AlgorithmId getDigestAlgorithmId() {
    468         return digestAlgorithmId;
    469     }
    470 
    471     public PKCS9Attributes getAuthenticatedAttributes() {
    472         return authenticatedAttributes;
    473     }
    474 
    475     public AlgorithmId getDigestEncryptionAlgorithmId() {
    476         return digestEncryptionAlgorithmId;
    477     }
    478 
    479     public byte[] getEncryptedDigest() {
    480         return encryptedDigest;
    481     }
    482 
    483     public PKCS9Attributes getUnauthenticatedAttributes() {
    484         return unauthenticatedAttributes;
    485     }
    486 
    487     public String toString() {
    488         HexDumpEncoder hexDump = new HexDumpEncoder();
    489 
    490         String out = "";
    491 
    492         out += "Signer Info for (issuer): " + issuerName + "\n";
    493         out += "\tversion: " + Debug.toHexString(version) + "\n";
    494         out += "\tcertificateSerialNumber: " +
    495                Debug.toHexString(certificateSerialNumber) + "\n";
    496         out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n";
    497         if (authenticatedAttributes != null) {
    498             out += "\tauthenticatedAttributes: " + authenticatedAttributes +
    499                    "\n";
    500         }
    501         out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId +
    502             "\n";
    503 
    504         out += "\tencryptedDigest: " + "\n" +
    505             hexDump.encodeBuffer(encryptedDigest) + "\n";
    506         if (unauthenticatedAttributes != null) {
    507             out += "\tunauthenticatedAttributes: " +
    508                    unauthenticatedAttributes + "\n";
    509         }
    510         return out;
    511     }
    512 
    513 }
    514