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