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