1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 * Copyright (c) 1996, 2016, 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
27package sun.security.pkcs;
28
29// BEGIN Android-added
30import java.io.InputStream;
31import java.io.ByteArrayInputStream;
32// END Android-added
33import java.io.OutputStream;
34import java.io.IOException;
35import java.math.BigInteger;
36import java.security.CryptoPrimitive;
37import java.security.InvalidKeyException;
38import java.security.MessageDigest;
39import java.security.NoSuchAlgorithmException;
40import java.security.Principal;
41import java.security.PublicKey;
42import java.security.Signature;
43import java.security.SignatureException;
44import java.security.Timestamp;
45import java.security.cert.CertificateException;
46import java.security.cert.CertificateFactory;
47import java.security.cert.CertPath;
48import java.security.cert.X509Certificate;
49import java.util.ArrayList;
50import java.util.Arrays;
51import java.util.Collections;
52import java.util.EnumSet;
53import java.util.Set;
54
55import sun.misc.HexDumpEncoder;
56import sun.security.timestamp.TimestampToken;
57import sun.security.util.Debug;
58import sun.security.util.DerEncoder;
59import sun.security.util.DerInputStream;
60import sun.security.util.DerOutputStream;
61import sun.security.util.DerValue;
62import sun.security.util.DisabledAlgorithmConstraints;
63import sun.security.util.ObjectIdentifier;
64import sun.security.x509.AlgorithmId;
65import sun.security.x509.X500Name;
66import sun.security.x509.KeyUsageExtension;
67import sun.security.x509.PKIXExtensions;
68
69/**
70 * A SignerInfo, as defined in PKCS#7's signedData type.
71 *
72 * @author Benjamin Renaud
73 */
74public class SignerInfo implements DerEncoder {
75
76    // Digest and Signature restrictions
77    private static final Set<CryptoPrimitive> DIGEST_PRIMITIVE_SET =
78            Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.MESSAGE_DIGEST));
79
80    private static final Set<CryptoPrimitive> SIG_PRIMITIVE_SET =
81            Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
82
83    private static final DisabledAlgorithmConstraints JAR_DISABLED_CHECK =
84            new DisabledAlgorithmConstraints(
85                    DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS);
86
87    BigInteger version;
88    X500Name issuerName;
89    BigInteger certificateSerialNumber;
90    AlgorithmId digestAlgorithmId;
91    AlgorithmId digestEncryptionAlgorithmId;
92    byte[] encryptedDigest;
93    Timestamp timestamp;
94    private boolean hasTimestamp = true;
95    // Android-removed
96    // private static final Debug debug = Debug.getInstance("jar");
97
98    PKCS9Attributes authenticatedAttributes;
99    PKCS9Attributes unauthenticatedAttributes;
100
101    public SignerInfo(X500Name  issuerName,
102                      BigInteger serial,
103                      AlgorithmId digestAlgorithmId,
104                      AlgorithmId digestEncryptionAlgorithmId,
105                      byte[] encryptedDigest) {
106        this.version = BigInteger.ONE;
107        this.issuerName = issuerName;
108        this.certificateSerialNumber = serial;
109        this.digestAlgorithmId = digestAlgorithmId;
110        this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
111        this.encryptedDigest = encryptedDigest;
112    }
113
114    public SignerInfo(X500Name  issuerName,
115                      BigInteger serial,
116                      AlgorithmId digestAlgorithmId,
117                      PKCS9Attributes authenticatedAttributes,
118                      AlgorithmId digestEncryptionAlgorithmId,
119                      byte[] encryptedDigest,
120                      PKCS9Attributes unauthenticatedAttributes) {
121        this.version = BigInteger.ONE;
122        this.issuerName = issuerName;
123        this.certificateSerialNumber = serial;
124        this.digestAlgorithmId = digestAlgorithmId;
125        this.authenticatedAttributes = authenticatedAttributes;
126        this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
127        this.encryptedDigest = encryptedDigest;
128        this.unauthenticatedAttributes = unauthenticatedAttributes;
129    }
130
131    /**
132     * Parses a PKCS#7 signer info.
133     */
134    public SignerInfo(DerInputStream derin)
135        throws IOException, ParsingException
136    {
137        this(derin, false);
138    }
139
140    /**
141     * Parses a PKCS#7 signer info.
142     *
143     * <p>This constructor is used only for backwards compatibility with
144     * PKCS#7 blocks that were generated using JDK1.1.x.
145     *
146     * @param derin the ASN.1 encoding of the signer info.
147     * @param oldStyle flag indicating whether or not the given signer info
148     * is encoded according to JDK1.1.x.
149     */
150    public SignerInfo(DerInputStream derin, boolean oldStyle)
151        throws IOException, ParsingException
152    {
153        // version
154        version = derin.getBigInteger();
155
156        // issuerAndSerialNumber
157        DerValue[] issuerAndSerialNumber = derin.getSequence(2);
158        byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray();
159        issuerName = new X500Name(new DerValue(DerValue.tag_Sequence,
160                                               issuerBytes));
161        certificateSerialNumber = issuerAndSerialNumber[1].getBigInteger();
162
163        // digestAlgorithmId
164        DerValue tmp = derin.getDerValue();
165
166        digestAlgorithmId = AlgorithmId.parse(tmp);
167
168        // authenticatedAttributes
169        if (oldStyle) {
170            // In JDK1.1.x, the authenticatedAttributes are always present,
171            // encoded as an empty Set (Set of length zero)
172            derin.getSet(0);
173        } else {
174            // check if set of auth attributes (implicit tag) is provided
175            // (auth attributes are OPTIONAL)
176            if ((byte)(derin.peekByte()) == (byte)0xA0) {
177                authenticatedAttributes = new PKCS9Attributes(derin);
178            }
179        }
180
181        // digestEncryptionAlgorithmId - little RSA naming scheme -
182        // signature == encryption...
183        tmp = derin.getDerValue();
184
185        digestEncryptionAlgorithmId = AlgorithmId.parse(tmp);
186
187        // encryptedDigest
188        encryptedDigest = derin.getOctetString();
189
190        // unauthenticatedAttributes
191        if (oldStyle) {
192            // In JDK1.1.x, the unauthenticatedAttributes are always present,
193            // encoded as an empty Set (Set of length zero)
194            derin.getSet(0);
195        } else {
196            // check if set of unauth attributes (implicit tag) is provided
197            // (unauth attributes are OPTIONAL)
198            if (derin.available() != 0
199                && (byte)(derin.peekByte()) == (byte)0xA1) {
200                unauthenticatedAttributes =
201                    new PKCS9Attributes(derin, true);// ignore unsupported attrs
202            }
203        }
204
205        // all done
206        if (derin.available() != 0) {
207            throw new ParsingException("extra data at the end");
208        }
209    }
210
211    public void encode(DerOutputStream out) throws IOException {
212
213        derEncode(out);
214    }
215
216    /**
217     * DER encode this object onto an output stream.
218     * Implements the <code>DerEncoder</code> interface.
219     *
220     * @param out
221     * the output stream on which to write the DER encoding.
222     *
223     * @exception IOException on encoding error.
224     */
225    public void derEncode(OutputStream out) throws IOException {
226        DerOutputStream seq = new DerOutputStream();
227        seq.putInteger(version);
228        DerOutputStream issuerAndSerialNumber = new DerOutputStream();
229        issuerName.encode(issuerAndSerialNumber);
230        issuerAndSerialNumber.putInteger(certificateSerialNumber);
231        seq.write(DerValue.tag_Sequence, issuerAndSerialNumber);
232
233        digestAlgorithmId.encode(seq);
234
235        // encode authenticated attributes if there are any
236        if (authenticatedAttributes != null)
237            authenticatedAttributes.encode((byte)0xA0, seq);
238
239        digestEncryptionAlgorithmId.encode(seq);
240
241        seq.putOctetString(encryptedDigest);
242
243        // encode unauthenticated attributes if there are any
244        if (unauthenticatedAttributes != null)
245            unauthenticatedAttributes.encode((byte)0xA1, seq);
246
247        DerOutputStream tmp = new DerOutputStream();
248        tmp.write(DerValue.tag_Sequence, seq);
249
250        out.write(tmp.toByteArray());
251    }
252
253
254
255    /*
256     * Returns the (user) certificate pertaining to this SignerInfo.
257     */
258    public X509Certificate getCertificate(PKCS7 block)
259        throws IOException
260    {
261        return block.getCertificate(certificateSerialNumber, issuerName);
262    }
263
264    /*
265     * Returns the certificate chain pertaining to this SignerInfo.
266     */
267    public ArrayList<X509Certificate> getCertificateChain(PKCS7 block)
268        throws IOException
269    {
270        X509Certificate userCert;
271        userCert = block.getCertificate(certificateSerialNumber, issuerName);
272        if (userCert == null)
273            return null;
274
275        ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
276        certList.add(userCert);
277
278        X509Certificate[] pkcsCerts = block.getCertificates();
279        if (pkcsCerts == null
280            || userCert.getSubjectDN().equals(userCert.getIssuerDN())) {
281            return certList;
282        }
283
284        Principal issuer = userCert.getIssuerDN();
285        int start = 0;
286        while (true) {
287            boolean match = false;
288            int i = start;
289            while (i < pkcsCerts.length) {
290                if (issuer.equals(pkcsCerts[i].getSubjectDN())) {
291                    // next cert in chain found
292                    certList.add(pkcsCerts[i]);
293                    // if selected cert is self-signed, we're done
294                    // constructing the chain
295                    if (pkcsCerts[i].getSubjectDN().equals(
296                                            pkcsCerts[i].getIssuerDN())) {
297                        start = pkcsCerts.length;
298                    } else {
299                        issuer = pkcsCerts[i].getIssuerDN();
300                        X509Certificate tmpCert = pkcsCerts[start];
301                        pkcsCerts[start] = pkcsCerts[i];
302                        pkcsCerts[i] = tmpCert;
303                        start++;
304                    }
305                    match = true;
306                    break;
307                } else {
308                    i++;
309                }
310            }
311            if (!match)
312                break;
313        }
314
315        return certList;
316    }
317
318    // BEGIN Android-changed
319    // Originally there's no overloading for InputStream.
320    SignerInfo verify(PKCS7 block, byte[] data)
321    throws NoSuchAlgorithmException, SignatureException {
322      try {
323        return verify(block, new ByteArrayInputStream(data));
324      } catch (IOException e) {
325        // Ignore
326        return null;
327      }
328    }
329
330    /* Returns null if verify fails, this signerInfo if
331       verify succeeds. */
332    SignerInfo verify(PKCS7 block, InputStream inputStream)
333    throws NoSuchAlgorithmException, SignatureException, IOException {
334
335       try {
336
337            ContentInfo content = block.getContentInfo();
338            if (inputStream == null) {
339                inputStream = new ByteArrayInputStream(content.getContentBytes());
340            }
341
342            String digestAlgname = getDigestAlgorithmId().getName();
343
344            InputStream dataSigned;
345
346            // if there are authenticate attributes, get the message
347            // digest and compare it with the digest of data
348            if (authenticatedAttributes == null) {
349                dataSigned = inputStream;
350            } else {
351
352                // first, check content type
353                ObjectIdentifier contentType = (ObjectIdentifier)
354                       authenticatedAttributes.getAttributeValue(
355                         PKCS9Attribute.CONTENT_TYPE_OID);
356                if (contentType == null ||
357                    !contentType.equals((Object)content.contentType))
358                    return null;  // contentType does not match, bad SignerInfo
359
360                // now, check message digest
361                byte[] messageDigest = (byte[])
362                    authenticatedAttributes.getAttributeValue(
363                         PKCS9Attribute.MESSAGE_DIGEST_OID);
364
365                if (messageDigest == null) // fail if there is no message digest
366                    return null;
367
368                // check that algorithm is not restricted
369                if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET,
370                        digestAlgname, null)) {
371                    throw new SignatureException("Digest check failed. " +
372                            "Disabled algorithm used: " + digestAlgname);
373                }
374
375                MessageDigest md = MessageDigest.getInstance(digestAlgname);
376
377                byte[] buffer = new byte[4096];
378                int read = 0;
379                while ((read = inputStream.read(buffer)) != -1) {
380                  md.update(buffer, 0 , read);
381                }
382                byte[] computedMessageDigest = md.digest();
383
384                if (messageDigest.length != computedMessageDigest.length)
385                    return null;
386                for (int i = 0; i < messageDigest.length; i++) {
387                    if (messageDigest[i] != computedMessageDigest[i])
388                        return null;
389                }
390
391                // message digest attribute matched
392                // digest of original data
393
394                // the data actually signed is the DER encoding of
395                // the authenticated attributes (tagged with
396                // the "SET OF" tag, not 0xA0).
397                dataSigned = new ByteArrayInputStream(authenticatedAttributes.getDerEncoding());
398            }
399
400            // put together digest algorithm and encryption algorithm
401            // to form signing algorithm
402            String encryptionAlgname =
403                getDigestEncryptionAlgorithmId().getName();
404
405            // Workaround: sometimes the encryptionAlgname is actually
406            // a signature name
407            String tmp = AlgorithmId.getEncAlgFromSigAlg(encryptionAlgname);
408            if (tmp != null) encryptionAlgname = tmp;
409            String algname = AlgorithmId.makeSigAlg(
410                    digestAlgname, encryptionAlgname);
411
412            // check that algorithm is not restricted
413            if (!JAR_DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, algname, null)) {
414                throw new SignatureException("Signature check failed. " +
415                        "Disabled algorithm used: " + algname);
416            }
417
418            X509Certificate cert = getCertificate(block);
419            PublicKey key = cert.getPublicKey();
420            if (cert == null) {
421                return null;
422            }
423
424            // check if the public key is restricted
425            if (!JAR_DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
426                throw new SignatureException("Public key check failed. " +
427                        "Disabled algorithm used: " + key.getAlgorithm());
428            }
429
430            if (cert.hasUnsupportedCriticalExtension()) {
431                throw new SignatureException("Certificate has unsupported "
432                                             + "critical extension(s)");
433            }
434
435            // Make sure that if the usage of the key in the certificate is
436            // restricted, it can be used for digital signatures.
437            // XXX We may want to check for additional extensions in the
438            // future.
439            boolean[] keyUsageBits = cert.getKeyUsage();
440            if (keyUsageBits != null) {
441                KeyUsageExtension keyUsage;
442                try {
443                    // We don't care whether or not this extension was marked
444                    // critical in the certificate.
445                    // We're interested only in its value (i.e., the bits set)
446                    // and treat the extension as critical.
447                    keyUsage = new KeyUsageExtension(keyUsageBits);
448                } catch (IOException ioe) {
449                    throw new SignatureException("Failed to parse keyUsage "
450                                                 + "extension");
451                }
452
453                boolean digSigAllowed = keyUsage.get(
454                        KeyUsageExtension.DIGITAL_SIGNATURE).booleanValue();
455
456                boolean nonRepuAllowed = keyUsage.get(
457                        KeyUsageExtension.NON_REPUDIATION).booleanValue();
458
459                if (!digSigAllowed && !nonRepuAllowed) {
460                    throw new SignatureException("Key usage restricted: "
461                                                 + "cannot be used for "
462                                                 + "digital signatures");
463                }
464            }
465
466            Signature sig = Signature.getInstance(algname);
467            sig.initVerify(key);
468
469            byte[] buffer = new byte[4096];
470            int read = 0;
471            while ((read = dataSigned.read(buffer)) != -1) {
472              sig.update(buffer, 0 , read);
473            }
474            if (sig.verify(encryptedDigest)) {
475                return this;
476            }
477
478        } catch (IOException e) {
479            throw new SignatureException("IO error verifying signature:\n" +
480                                         e.getMessage());
481
482        } catch (InvalidKeyException e) {
483            throw new SignatureException("InvalidKey: " + e.getMessage());
484
485        }
486        return null;
487    }
488    // END Android-changed
489
490    /* Verify the content of the pkcs7 block. */
491    SignerInfo verify(PKCS7 block)
492    throws NoSuchAlgorithmException, SignatureException {
493      // BEGIN Android-changed
494      // Was: return verify(block, null);
495      // As in Android the method is overloaded, we need to disambiguate with a cast
496      return verify(block, (byte[])null);
497      // END Android-changed
498    }
499
500
501    public BigInteger getVersion() {
502            return version;
503    }
504
505    public X500Name getIssuerName() {
506        return issuerName;
507    }
508
509    public BigInteger getCertificateSerialNumber() {
510        return certificateSerialNumber;
511    }
512
513    public AlgorithmId getDigestAlgorithmId() {
514        return digestAlgorithmId;
515    }
516
517    public PKCS9Attributes getAuthenticatedAttributes() {
518        return authenticatedAttributes;
519    }
520
521    public AlgorithmId getDigestEncryptionAlgorithmId() {
522        return digestEncryptionAlgorithmId;
523    }
524
525    public byte[] getEncryptedDigest() {
526        return encryptedDigest;
527    }
528
529    public PKCS9Attributes getUnauthenticatedAttributes() {
530        return unauthenticatedAttributes;
531    }
532
533    /*
534     * Extracts a timestamp from a PKCS7 SignerInfo.
535     *
536     * Examines the signer's unsigned attributes for a
537     * <tt>signatureTimestampToken</tt> attribute. If present,
538     * then it is parsed to extract the date and time at which the
539     * timestamp was generated.
540     *
541     * @param info A signer information element of a PKCS 7 block.
542     *
543     * @return A timestamp token or null if none is present.
544     * @throws IOException if an error is encountered while parsing the
545     *         PKCS7 data.
546     * @throws NoSuchAlgorithmException if an error is encountered while
547     *         verifying the PKCS7 object.
548     * @throws SignatureException if an error is encountered while
549     *         verifying the PKCS7 object.
550     * @throws CertificateException if an error is encountered while generating
551     *         the TSA's certpath.
552     */
553    public Timestamp getTimestamp()
554        throws IOException, NoSuchAlgorithmException, SignatureException,
555               CertificateException
556    {
557
558        if (timestamp != null || !hasTimestamp)
559            return timestamp;
560
561        if (unauthenticatedAttributes == null) {
562            hasTimestamp = false;
563            return null;
564        }
565        PKCS9Attribute tsTokenAttr =
566            unauthenticatedAttributes.getAttribute(
567                PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);
568        if (tsTokenAttr == null) {
569            hasTimestamp = false;
570            return null;
571        }
572
573        PKCS7 tsToken = new PKCS7((byte[])tsTokenAttr.getValue());
574        // Extract the content (an encoded timestamp token info)
575        byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
576        // Extract the signer (the Timestamping Authority)
577        // while verifying the content
578        SignerInfo[] tsa = tsToken.verify(encTsTokenInfo);
579        // Expect only one signer
580        ArrayList<X509Certificate> chain = tsa[0].getCertificateChain(tsToken);
581        CertificateFactory cf = CertificateFactory.getInstance("X.509");
582        CertPath tsaChain = cf.generateCertPath(chain);
583        // Create a timestamp token info object
584        TimestampToken tsTokenInfo = new TimestampToken(encTsTokenInfo);
585        // Check that the signature timestamp applies to this signature
586        verifyTimestamp(tsTokenInfo);
587        // Create a timestamp object
588        timestamp = new Timestamp(tsTokenInfo.getDate(), tsaChain);
589        return timestamp;
590    }
591
592    /*
593     * Check that the signature timestamp applies to this signature.
594     * Match the hash present in the signature timestamp token against the hash
595     * of this signature.
596     */
597    private void verifyTimestamp(TimestampToken token)
598        throws NoSuchAlgorithmException, SignatureException {
599        String digestAlgname = token.getHashAlgorithm().getName();
600        // check that algorithm is not restricted
601        if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET, digestAlgname,
602                null)) {
603            throw new SignatureException("Timestamp token digest check failed. " +
604                    "Disabled algorithm used: " + digestAlgname);
605        }
606
607        MessageDigest md =
608            MessageDigest.getInstance(digestAlgname);
609
610        if (!Arrays.equals(token.getHashedMessage(),
611            md.digest(encryptedDigest))) {
612
613            throw new SignatureException("Signature timestamp (#" +
614                token.getSerialNumber() + ") generated on " + token.getDate() +
615                " is inapplicable");
616        }
617
618        // BEGIN Android-removed
619        /*
620        if (debug != null) {
621            debug.println();
622            debug.println("Detected signature timestamp (#" +
623                token.getSerialNumber() + ") generated on " + token.getDate());
624            debug.println();
625        }
626        */
627        // END Android-removed
628    }
629
630    public String toString() {
631        HexDumpEncoder hexDump = new HexDumpEncoder();
632
633        String out = "";
634
635        out += "Signer Info for (issuer): " + issuerName + "\n";
636        out += "\tversion: " + Debug.toHexString(version) + "\n";
637        out += "\tcertificateSerialNumber: " +
638               Debug.toHexString(certificateSerialNumber) + "\n";
639        out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n";
640        if (authenticatedAttributes != null) {
641            out += "\tauthenticatedAttributes: " + authenticatedAttributes +
642                   "\n";
643        }
644        out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId +
645            "\n";
646
647        out += "\tencryptedDigest: " + "\n" +
648            hexDump.encodeBuffer(encryptedDigest) + "\n";
649        if (unauthenticatedAttributes != null) {
650            out += "\tunauthenticatedAttributes: " +
651                   unauthenticatedAttributes + "\n";
652        }
653        return out;
654    }
655}
656