001: /*
002: * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.security.pkcs;
027:
028: import java.io.OutputStream;
029: import java.io.IOException;
030: import java.math.BigInteger;
031: import java.security.cert.X509Certificate;
032: import java.security.*;
033: import java.util.ArrayList;
034:
035: import sun.security.util.*;
036: import sun.security.x509.AlgorithmId;
037: import sun.security.x509.X500Name;
038: import sun.security.x509.KeyUsageExtension;
039: import sun.security.x509.PKIXExtensions;
040: import sun.misc.HexDumpEncoder;
041:
042: /**
043: * A SignerInfo, as defined in PKCS#7's signedData type.
044: *
045: * @author Benjamin Renaud
046: * @version 1.55 05/05/07
047: */
048: public class SignerInfo implements DerEncoder {
049:
050: BigInteger version;
051: X500Name issuerName;
052: BigInteger certificateSerialNumber;
053: AlgorithmId digestAlgorithmId;
054: AlgorithmId digestEncryptionAlgorithmId;
055: byte[] encryptedDigest;
056:
057: PKCS9Attributes authenticatedAttributes;
058: PKCS9Attributes unauthenticatedAttributes;
059:
060: public SignerInfo(X500Name issuerName, BigInteger serial,
061: AlgorithmId digestAlgorithmId,
062: AlgorithmId digestEncryptionAlgorithmId,
063: byte[] encryptedDigest) {
064: this .version = BigInteger.ONE;
065: this .issuerName = issuerName;
066: this .certificateSerialNumber = serial;
067: this .digestAlgorithmId = digestAlgorithmId;
068: this .digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
069: this .encryptedDigest = encryptedDigest;
070: }
071:
072: public SignerInfo(X500Name issuerName, BigInteger serial,
073: AlgorithmId digestAlgorithmId,
074: PKCS9Attributes authenticatedAttributes,
075: AlgorithmId digestEncryptionAlgorithmId,
076: byte[] encryptedDigest,
077: PKCS9Attributes unauthenticatedAttributes) {
078: this .version = BigInteger.ONE;
079: this .issuerName = issuerName;
080: this .certificateSerialNumber = serial;
081: this .digestAlgorithmId = digestAlgorithmId;
082: this .authenticatedAttributes = authenticatedAttributes;
083: this .digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
084: this .encryptedDigest = encryptedDigest;
085: this .unauthenticatedAttributes = unauthenticatedAttributes;
086: }
087:
088: /**
089: * Parses a PKCS#7 signer info.
090: */
091: public SignerInfo(DerInputStream derin) throws IOException,
092: ParsingException {
093: this (derin, false);
094: }
095:
096: /**
097: * Parses a PKCS#7 signer info.
098: *
099: * <p>This constructor is used only for backwards compatibility with
100: * PKCS#7 blocks that were generated using JDK1.1.x.
101: *
102: * @param derin the ASN.1 encoding of the signer info.
103: * @param oldStyle flag indicating whether or not the given signer info
104: * is encoded according to JDK1.1.x.
105: */
106: public SignerInfo(DerInputStream derin, boolean oldStyle)
107: throws IOException, ParsingException {
108: // version
109: version = derin.getBigInteger();
110:
111: // issuerAndSerialNumber
112: DerValue[] issuerAndSerialNumber = derin.getSequence(2);
113: byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray();
114: issuerName = new X500Name(new DerValue(DerValue.tag_Sequence,
115: issuerBytes));
116: certificateSerialNumber = issuerAndSerialNumber[1]
117: .getBigInteger();
118:
119: // digestAlgorithmId
120: DerValue tmp = derin.getDerValue();
121:
122: digestAlgorithmId = AlgorithmId.parse(tmp);
123:
124: // authenticatedAttributes
125: if (oldStyle) {
126: // In JDK1.1.x, the authenticatedAttributes are always present,
127: // encoded as an empty Set (Set of length zero)
128: derin.getSet(0);
129: } else {
130: // check if set of auth attributes (implicit tag) is provided
131: // (auth attributes are OPTIONAL)
132: if ((byte) (derin.peekByte()) == (byte) 0xA0) {
133: authenticatedAttributes = new PKCS9Attributes(derin);
134: }
135: }
136:
137: // digestEncryptionAlgorithmId - little RSA naming scheme -
138: // signature == encryption...
139: tmp = derin.getDerValue();
140:
141: digestEncryptionAlgorithmId = AlgorithmId.parse(tmp);
142:
143: // encryptedDigest
144: encryptedDigest = derin.getOctetString();
145:
146: // unauthenticatedAttributes
147: if (oldStyle) {
148: // In JDK1.1.x, the unauthenticatedAttributes are always present,
149: // encoded as an empty Set (Set of length zero)
150: derin.getSet(0);
151: } else {
152: // check if set of unauth attributes (implicit tag) is provided
153: // (unauth attributes are OPTIONAL)
154: if (derin.available() != 0
155: && (byte) (derin.peekByte()) == (byte) 0xA1) {
156: unauthenticatedAttributes = new PKCS9Attributes(derin,
157: true);// ignore unsupported attrs
158: }
159: }
160:
161: // all done
162: if (derin.available() != 0) {
163: throw new ParsingException("extra data at the end");
164: }
165: }
166:
167: public void encode(DerOutputStream out) throws IOException {
168:
169: derEncode(out);
170: }
171:
172: /**
173: * DER encode this object onto an output stream.
174: * Implements the <code>DerEncoder</code> interface.
175: *
176: * @param out
177: * the output stream on which to write the DER encoding.
178: *
179: * @exception IOException on encoding error.
180: */
181: public void derEncode(OutputStream out) throws IOException {
182: DerOutputStream seq = new DerOutputStream();
183: seq.putInteger(version);
184: DerOutputStream issuerAndSerialNumber = new DerOutputStream();
185: issuerName.encode(issuerAndSerialNumber);
186: issuerAndSerialNumber.putInteger(certificateSerialNumber);
187: seq.write(DerValue.tag_Sequence, issuerAndSerialNumber);
188:
189: digestAlgorithmId.encode(seq);
190:
191: // encode authenticated attributes if there are any
192: if (authenticatedAttributes != null)
193: authenticatedAttributes.encode((byte) 0xA0, seq);
194:
195: digestEncryptionAlgorithmId.encode(seq);
196:
197: seq.putOctetString(encryptedDigest);
198:
199: // encode unauthenticated attributes if there are any
200: if (unauthenticatedAttributes != null)
201: unauthenticatedAttributes.encode((byte) 0xA1, seq);
202:
203: DerOutputStream tmp = new DerOutputStream();
204: tmp.write(DerValue.tag_Sequence, seq);
205:
206: out.write(tmp.toByteArray());
207: }
208:
209: /*
210: * Returns the (user) certificate pertaining to this SignerInfo.
211: */
212: public X509Certificate getCertificate(PKCS7 block)
213: throws IOException {
214: return block
215: .getCertificate(certificateSerialNumber, issuerName);
216: }
217:
218: /*
219: * Returns the certificate chain pertaining to this SignerInfo.
220: */
221: public ArrayList<X509Certificate> getCertificateChain(PKCS7 block)
222: throws IOException {
223: X509Certificate userCert;
224: userCert = block.getCertificate(certificateSerialNumber,
225: issuerName);
226: if (userCert == null)
227: return null;
228:
229: ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
230: certList.add(userCert);
231:
232: X509Certificate[] pkcsCerts = block.getCertificates();
233: if (pkcsCerts == null
234: || userCert.getSubjectDN().equals(
235: userCert.getIssuerDN())) {
236: return certList;
237: }
238:
239: Principal issuer = userCert.getIssuerDN();
240: int start = 0;
241: while (true) {
242: boolean match = false;
243: int i = start;
244: while (i < pkcsCerts.length) {
245: if (issuer.equals(pkcsCerts[i].getSubjectDN())) {
246: // next cert in chain found
247: certList.add(pkcsCerts[i]);
248: // if selected cert is self-signed, we're done
249: // constructing the chain
250: if (pkcsCerts[i].getSubjectDN().equals(
251: pkcsCerts[i].getIssuerDN())) {
252: start = pkcsCerts.length;
253: } else {
254: issuer = pkcsCerts[i].getIssuerDN();
255: X509Certificate tmpCert = pkcsCerts[start];
256: pkcsCerts[start] = pkcsCerts[i];
257: pkcsCerts[i] = tmpCert;
258: start++;
259: }
260: match = true;
261: break;
262: } else {
263: i++;
264: }
265: }
266: if (!match)
267: break;
268: }
269:
270: return certList;
271: }
272:
273: /* Returns null if verify fails, this signerInfo if
274: verify succeeds. */
275: SignerInfo verify(PKCS7 block, byte[] data)
276: throws NoSuchAlgorithmException, SignatureException {
277:
278: try {
279:
280: ContentInfo content = block.getContentInfo();
281: if (data == null) {
282: data = content.getContentBytes();
283: }
284:
285: String digestAlgname = getDigestAlgorithmId().getName();
286: if (digestAlgname.equalsIgnoreCase("SHA"))
287: digestAlgname = "SHA1";
288:
289: byte[] dataSigned;
290:
291: // if there are authenticate attributes, get the message
292: // digest and compare it with the digest of data
293: if (authenticatedAttributes == null) {
294: dataSigned = data;
295: } else {
296:
297: // first, check content type
298: ObjectIdentifier contentType = (ObjectIdentifier) authenticatedAttributes
299: .getAttributeValue(PKCS9Attribute.CONTENT_TYPE_OID);
300: if (contentType == null
301: || !contentType.equals(content.contentType))
302: return null; // contentType does not match, bad SignerInfo
303:
304: // now, check message digest
305: byte[] messageDigest = (byte[]) authenticatedAttributes
306: .getAttributeValue(PKCS9Attribute.MESSAGE_DIGEST_OID);
307:
308: if (messageDigest == null) // fail if there is no message digest
309: return null;
310:
311: MessageDigest md = MessageDigest
312: .getInstance(digestAlgname);
313: byte[] computedMessageDigest = md.digest(data);
314:
315: if (messageDigest.length != computedMessageDigest.length)
316: return null;
317: for (int i = 0; i < messageDigest.length; i++) {
318: if (messageDigest[i] != computedMessageDigest[i])
319: return null;
320: }
321:
322: // message digest attribute matched
323: // digest of original data
324:
325: // the data actually signed is the DER encoding of
326: // the authenticated attributes (tagged with
327: // the "SET OF" tag, not 0xA0).
328: dataSigned = authenticatedAttributes.getDerEncoding();
329: }
330:
331: // put together digest algorithm and encryption algorithm
332: // to form signing algorithm
333: String encryptionAlgname = getDigestEncryptionAlgorithmId()
334: .getName();
335:
336: if (encryptionAlgname.equalsIgnoreCase("SHA1withDSA"))
337: encryptionAlgname = "DSA";
338: String algname = digestAlgname + "with" + encryptionAlgname;
339:
340: Signature sig = Signature.getInstance(algname);
341: X509Certificate cert = getCertificate(block);
342:
343: if (cert == null) {
344: return null;
345: }
346: if (cert.hasUnsupportedCriticalExtension()) {
347: throw new SignatureException(
348: "Certificate has unsupported "
349: + "critical extension(s)");
350: }
351:
352: // Make sure that if the usage of the key in the certificate is
353: // restricted, it can be used for digital signatures.
354: // XXX We may want to check for additional extensions in the
355: // future.
356: boolean[] keyUsageBits = cert.getKeyUsage();
357: if (keyUsageBits != null) {
358: KeyUsageExtension keyUsage;
359: try {
360: // We don't care whether or not this extension was marked
361: // critical in the certificate.
362: // We're interested only in its value (i.e., the bits set)
363: // and treat the extension as critical.
364: keyUsage = new KeyUsageExtension(keyUsageBits);
365: } catch (IOException ioe) {
366: throw new SignatureException(
367: "Failed to parse keyUsage " + "extension");
368: }
369:
370: boolean digSigAllowed = ((Boolean) keyUsage
371: .get(KeyUsageExtension.DIGITAL_SIGNATURE))
372: .booleanValue();
373:
374: boolean nonRepuAllowed = ((Boolean) keyUsage
375: .get(KeyUsageExtension.NON_REPUDIATION))
376: .booleanValue();
377:
378: if (!digSigAllowed && !nonRepuAllowed) {
379: throw new SignatureException(
380: "Key usage restricted: "
381: + "cannot be used for "
382: + "digital signatures");
383: }
384: }
385:
386: PublicKey key = cert.getPublicKey();
387: sig.initVerify(key);
388:
389: sig.update(dataSigned);
390:
391: if (sig.verify(encryptedDigest)) {
392: return this ;
393: }
394:
395: } catch (IOException e) {
396: throw new SignatureException(
397: "IO error verifying signature:\n" + e.getMessage());
398:
399: } catch (InvalidKeyException e) {
400: throw new SignatureException("InvalidKey: "
401: + e.getMessage());
402:
403: }
404: return null;
405: }
406:
407: /* Verify the content of the pkcs7 block. */
408: SignerInfo verify(PKCS7 block) throws NoSuchAlgorithmException,
409: SignatureException {
410: return verify(block, null);
411: }
412:
413: public BigInteger getVersion() {
414: return version;
415: }
416:
417: public X500Name getIssuerName() {
418: return issuerName;
419: }
420:
421: public BigInteger getCertificateSerialNumber() {
422: return certificateSerialNumber;
423: }
424:
425: public AlgorithmId getDigestAlgorithmId() {
426: return digestAlgorithmId;
427: }
428:
429: public PKCS9Attributes getAuthenticatedAttributes() {
430: return authenticatedAttributes;
431: }
432:
433: public AlgorithmId getDigestEncryptionAlgorithmId() {
434: return digestEncryptionAlgorithmId;
435: }
436:
437: public byte[] getEncryptedDigest() {
438: return encryptedDigest;
439: }
440:
441: public PKCS9Attributes getUnauthenticatedAttributes() {
442: return unauthenticatedAttributes;
443: }
444:
445: public String toString() {
446: HexDumpEncoder hexDump = new HexDumpEncoder();
447:
448: String out = "";
449:
450: out += "Signer Info for (issuer): " + issuerName + "\n";
451: out += "\tversion: " + Debug.toHexString(version) + "\n";
452: out += "\tcertificateSerialNumber: "
453: + Debug.toHexString(certificateSerialNumber) + "\n";
454: out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n";
455: if (authenticatedAttributes != null) {
456: out += "\tauthenticatedAttributes: "
457: + authenticatedAttributes + "\n";
458: }
459: out += "\tdigestEncryptionAlgorithmId: "
460: + digestEncryptionAlgorithmId + "\n";
461:
462: out += "\tencryptedDigest: " + "\n"
463: + hexDump.encodeBuffer(encryptedDigest) + "\n";
464: if (unauthenticatedAttributes != null) {
465: out += "\tunauthenticatedAttributes: "
466: + unauthenticatedAttributes + "\n";
467: }
468: return out;
469: }
470:
471: }
|