001: package org.bouncycastle.mail.smime.validator;
002:
003: import org.bouncycastle.asn1.ASN1InputStream;
004: import org.bouncycastle.asn1.ASN1OctetString;
005: import org.bouncycastle.asn1.ASN1TaggedObject;
006: import org.bouncycastle.asn1.DERIA5String;
007: import org.bouncycastle.asn1.DERObject;
008: import org.bouncycastle.asn1.DEROctetString;
009: import org.bouncycastle.asn1.DERSequence;
010: import org.bouncycastle.asn1.cms.Attribute;
011: import org.bouncycastle.asn1.cms.AttributeTable;
012: import org.bouncycastle.asn1.cms.CMSAttributes;
013: import org.bouncycastle.asn1.cms.Time;
014: import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
015: import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
016: import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
017: import org.bouncycastle.asn1.x509.KeyPurposeId;
018: import org.bouncycastle.asn1.x509.X509Extensions;
019: import org.bouncycastle.cms.SignerInformation;
020: import org.bouncycastle.cms.SignerInformationStore;
021: import org.bouncycastle.i18n.ErrorBundle;
022: import org.bouncycastle.i18n.filter.TrustedInput;
023: import org.bouncycastle.i18n.filter.UntrustedInput;
024: import org.bouncycastle.jce.PrincipalUtil;
025: import org.bouncycastle.jce.X509Principal;
026: import org.bouncycastle.mail.smime.SMIMESigned;
027: import org.bouncycastle.x509.CertPathReviewerException;
028: import org.bouncycastle.x509.PKIXCertPathReviewer;
029:
030: import javax.mail.Address;
031: import javax.mail.internet.InternetAddress;
032: import javax.mail.internet.MimeMessage;
033: import javax.mail.internet.MimeMultipart;
034: import java.io.IOException;
035: import java.security.GeneralSecurityException;
036: import java.security.PublicKey;
037: import java.security.cert.CertPath;
038: import java.security.cert.CertStore;
039: import java.security.cert.CertStoreException;
040: import java.security.cert.CertificateEncodingException;
041: import java.security.cert.CertificateExpiredException;
042: import java.security.cert.CertificateFactory;
043: import java.security.cert.CertificateNotYetValidException;
044: import java.security.cert.PKIXParameters;
045: import java.security.cert.TrustAnchor;
046: import java.security.cert.X509CertSelector;
047: import java.security.cert.X509Certificate;
048: import java.security.interfaces.DSAPublicKey;
049: import java.security.interfaces.RSAPublicKey;
050: import java.util.ArrayList;
051: import java.util.Arrays;
052: import java.util.Collection;
053: import java.util.Date;
054: import java.util.HashMap;
055: import java.util.HashSet;
056: import java.util.Iterator;
057: import java.util.LinkedHashSet;
058: import java.util.List;
059: import java.util.Map;
060: import java.util.Set;
061: import java.util.Vector;
062:
063: public class SignedMailValidator {
064: private static final String RESOURCE_NAME = "org.bouncycastle.mail.smime.validator.SignedMailValidatorMessages";
065:
066: private static final Class DEFAULT_CERT_PATH_REVIEWER = PKIXCertPathReviewer.class;
067:
068: private static final String EXT_KEY_USAGE = X509Extensions.ExtendedKeyUsage
069: .getId();
070:
071: private static final String SUBJECT_ALTERNATIVE_NAME = X509Extensions.SubjectAlternativeName
072: .getId();
073:
074: private static final int shortKeyLength = 512;
075:
076: // (365.25*30)*24*3600*1000
077: private static final long THIRTY_YEARS_IN_MILLI_SEC = 21915l * 12l * 3600l * 1000l;
078:
079: private CertStore certs;
080:
081: private SignerInformationStore signers;
082:
083: private Map results;
084:
085: private String[] fromAddresses;
086:
087: private Class certPathReviewerClass;
088:
089: /**
090: * Validates the signed {@link MimeMessage} message. The
091: * {@link PKIXParameters} from param are used for the certificate path
092: * validation. The actual PKIXParameters used for the certificate path
093: * validation is a copy of param with the followin changes: <br> - The
094: * validation date is changed to the signature time <br> - A CertStore with
095: * certificates and crls from the mail message is added to the CertStores.<br>
096: * <br>
097: * In <code>param</code> it's also possible to add additional CertStores
098: * with intermediate Certificates and/or CRLs which then are also used for
099: * the validation.
100: *
101: * @param message
102: * the signed MimeMessage
103: * @param param
104: * the parameters for the certificate path validation
105: * @throws SignedMailValidatorException
106: * if the message is no signed message or if an exception occurs
107: * reading the message
108: */
109: public SignedMailValidator(MimeMessage message, PKIXParameters param)
110: throws SignedMailValidatorException {
111: this (message, param, DEFAULT_CERT_PATH_REVIEWER);
112: }
113:
114: /**
115: * Validates the signed {@link MimeMessage} message. The
116: * {@link PKIXParameters} from param are used for the certificate path
117: * validation. The actual PKIXParameters used for the certificate path
118: * validation is a copy of param with the followin changes: <br> - The
119: * validation date is changed to the signature time <br> - A CertStore with
120: * certificates and crls from the mail message is added to the CertStores.<br>
121: * <br>
122: * In <code>param</code> it's also possible to add additional CertStores
123: * with intermediate Certificates and/or CRLs which then are also used for
124: * the validation.
125: *
126: * @param message
127: * the signed MimeMessage
128: * @param param
129: * the parameters for the certificate path validation
130: * @param certPathReviewerClass
131: * a subclass of {@link PKIXCertPathReviewer}. The SignedMailValidator
132: * uses objects of this type for the cert path vailidation. The class must
133: * have an empty constructor.
134: * @throws SignedMailValidatorException
135: * if the message is no signed message or if an exception occurs
136: * reading the message
137: * @throws IllegalArgumentException if the certPathReviewerClass is not a
138: * subclass of {@link PKIXCertPathReviewer} or objects of
139: * certPathReviewerClass can not be instantiated
140: */
141: public SignedMailValidator(MimeMessage message,
142: PKIXParameters param, Class certPathReviewerClass)
143: throws SignedMailValidatorException {
144: this .certPathReviewerClass = certPathReviewerClass;
145: try {
146: certPathReviewerClass
147: .asSubclass(DEFAULT_CERT_PATH_REVIEWER);
148: } catch (ClassCastException e) {
149: throw new IllegalArgumentException(
150: "certPathReviewerClass is not a subclass of "
151: + DEFAULT_CERT_PATH_REVIEWER.getName());
152: }
153:
154: SMIMESigned s;
155:
156: try {
157: // check if message is multipart signed
158: if (message.isMimeType("multipart/signed")) {
159: MimeMultipart mimemp = (MimeMultipart) message
160: .getContent();
161: s = new SMIMESigned(mimemp);
162: } else if (message.isMimeType("application/pkcs7-mime")
163: || message.isMimeType("application/x-pkcs7-mime")) {
164: s = new SMIMESigned(message);
165: } else {
166: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
167: "SignedMailValidator.noSignedMessage");
168: throw new SignedMailValidatorException(msg);
169: }
170:
171: // save certstore and signerInformationStore
172: certs = s.getCertificatesAndCRLs("Collection", "BC");
173: signers = s.getSignerInfos();
174:
175: // save "from" addresses from message
176: Address[] froms = message.getFrom();
177: fromAddresses = new String[froms.length];
178: for (int i = 0; i < froms.length; i++) {
179: InternetAddress inetAddr = (InternetAddress) froms[i];
180: fromAddresses[i] = inetAddr.getAddress();
181: }
182:
183: // initialize results
184: results = new HashMap();
185: } catch (Exception e) {
186: if (e instanceof SignedMailValidatorException) {
187: throw (SignedMailValidatorException) e;
188: }
189: // exception reading message
190: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
191: "SignedMailValidator.exceptionReadingMessage",
192: new Object[] { e.getMessage(), e,
193: e.getClass().getName() });
194: throw new SignedMailValidatorException(msg, e);
195: }
196:
197: // validate signatues
198: validateSignatures(param);
199: }
200:
201: protected void validateSignatures(PKIXParameters pkixParam) {
202: PKIXParameters usedParameters = (PKIXParameters) pkixParam
203: .clone();
204:
205: // add crls and certs from mail
206: usedParameters.addCertStore(certs);
207:
208: Collection c = signers.getSigners();
209: Iterator it = c.iterator();
210:
211: // check each signer
212: while (it.hasNext()) {
213: List errors = new ArrayList();
214: List notifications = new ArrayList();
215:
216: SignerInformation signer = (SignerInformation) it.next();
217: // signer certificate
218: X509Certificate cert = null;
219:
220: try {
221: Collection certCollection = findCerts(usedParameters
222: .getCertStores(), signer.getSID());
223:
224: Iterator certIt = certCollection.iterator();
225: if (certIt.hasNext()) {
226: cert = (X509Certificate) certIt.next();
227: }
228: } catch (CertStoreException cse) {
229: ErrorBundle msg = new ErrorBundle(
230: RESOURCE_NAME,
231: "SignedMailValidator.exceptionRetrievingSignerCert",
232: new Object[] { cse.getMessage(), cse,
233: cse.getClass().getName() });
234: errors.add(msg);
235: }
236:
237: if (cert != null) {
238: // check signature
239: boolean validSignature = false;
240: try {
241: validSignature = signer.verify(cert.getPublicKey(),
242: "BC");
243: if (!validSignature) {
244: ErrorBundle msg = new ErrorBundle(
245: RESOURCE_NAME,
246: "SignedMailValidator.signatureNotVerified");
247: errors.add(msg);
248: }
249: } catch (Exception e) {
250: ErrorBundle msg = new ErrorBundle(
251: RESOURCE_NAME,
252: "SignedMailValidator.exceptionVerifyingSignature",
253: new Object[] { e.getMessage(), e,
254: e.getClass().getName() });
255: errors.add(msg);
256: }
257:
258: // check signer certificate (mail address, key usage, etc)
259: checkSignerCert(cert, errors, notifications);
260:
261: // notify if a signed receip request is in the message
262: AttributeTable atab = signer.getSignedAttributes();
263: if (atab != null) {
264: Attribute attr = atab
265: .get(PKCSObjectIdentifiers.id_aa_receiptRequest);
266: if (attr != null) {
267: ErrorBundle msg = new ErrorBundle(
268: RESOURCE_NAME,
269: "SignedMailValidator.signedReceiptRequest");
270: notifications.add(msg);
271: }
272: }
273:
274: // check certificate path
275:
276: // get signing time if possible, otherwise use current time as
277: // signing time
278: Date signTime = getSignatureTime(signer);
279: if (signTime == null) // no signing time was found
280: {
281: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
282: "SignedMailValidator.noSigningTime");
283: errors.add(msg);
284: signTime = new Date();
285: } else {
286: // check if certificate was valid at signing time
287: try {
288: cert.checkValidity(signTime);
289: } catch (CertificateExpiredException e) {
290: ErrorBundle msg = new ErrorBundle(
291: RESOURCE_NAME,
292: "SignedMailValidator.certExpired",
293: new Object[] {
294: new TrustedInput(signTime),
295: new TrustedInput(cert
296: .getNotAfter()) });
297: errors.add(msg);
298: } catch (CertificateNotYetValidException e) {
299: ErrorBundle msg = new ErrorBundle(
300: RESOURCE_NAME,
301: "SignedMailValidator.certNotYetValid",
302: new Object[] {
303: new TrustedInput(signTime),
304: new TrustedInput(cert
305: .getNotBefore()) });
306: errors.add(msg);
307: }
308: }
309: usedParameters.setDate(signTime);
310:
311: try {
312: // construct cert chain
313: CertPath certPath;
314: List userProvidedList;
315:
316: List userCertStores = new ArrayList();
317: userCertStores.add(certs);
318: Object[] cpres = createCertPath(cert,
319: usedParameters.getTrustAnchors(), pkixParam
320: .getCertStores(), userCertStores);
321: certPath = (CertPath) cpres[0];
322: userProvidedList = (List) cpres[1];
323:
324: // validate cert chain
325: PKIXCertPathReviewer review;
326: try {
327: review = (PKIXCertPathReviewer) certPathReviewerClass
328: .newInstance();
329: } catch (IllegalAccessException e) {
330: throw new IllegalArgumentException(
331: "Cannot instantiate object of type "
332: + certPathReviewerClass
333: .getName() + ": "
334: + e.getMessage(), e);
335: } catch (InstantiationException e) {
336: throw new IllegalArgumentException(
337: "Cannot instantiate object of type "
338: + certPathReviewerClass
339: .getName() + ": "
340: + e.getMessage(), e);
341: }
342: review.init(certPath, usedParameters);
343: if (!review.isValidCertPath()) {
344: ErrorBundle msg = new ErrorBundle(
345: RESOURCE_NAME,
346: "SignedMailValidator.certPathInvalid");
347: errors.add(msg);
348: }
349: results.put(signer, new ValidationResult(review,
350: validSignature, errors, notifications,
351: userProvidedList));
352: } catch (GeneralSecurityException gse) {
353: // cannot create cert path
354: ErrorBundle msg = new ErrorBundle(
355: RESOURCE_NAME,
356: "SignedMailValidator.exceptionCreateCertPath",
357: new Object[] { gse.getMessage(), gse,
358: gse.getClass().getName() });
359: errors.add(msg);
360: results.put(signer,
361: new ValidationResult(null, validSignature,
362: errors, notifications, null));
363: } catch (CertPathReviewerException cpre) {
364: // cannot initialize certpathreviewer - wrong parameters
365: errors.add(cpre.getErrorMessage());
366: results.put(signer,
367: new ValidationResult(null, validSignature,
368: errors, notifications, null));
369: }
370: } else
371: // no signer certificate found
372: {
373: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
374: "SignedMailValidator.noSignerCert");
375: errors.add(msg);
376: results.put(signer, new ValidationResult(null, false,
377: errors, notifications, null));
378: }
379: }
380: }
381:
382: public static Set getEmailAddresses(X509Certificate cert)
383: throws IOException, CertificateEncodingException {
384: Set addresses = new HashSet();
385:
386: X509Principal name = PrincipalUtil
387: .getSubjectX509Principal(cert);
388: Vector oids = name.getOIDs();
389: Vector names = name.getValues();
390: for (int i = 0; i < oids.size(); i++) {
391: if (oids.get(i).equals(X509Principal.EmailAddress)) {
392: String email = ((String) names.get(i)).toLowerCase();
393: addresses.add(email);
394: break;
395: }
396: }
397:
398: byte[] ext = cert.getExtensionValue(SUBJECT_ALTERNATIVE_NAME);
399: if (ext != null) {
400: DERSequence altNames = (DERSequence) getObject(ext);
401: for (int j = 0; j < altNames.size(); j++) {
402: ASN1TaggedObject o = (ASN1TaggedObject) altNames
403: .getObjectAt(j);
404:
405: if (o.getTagNo() == 1) {
406: String email = DERIA5String.getInstance(o, true)
407: .getString().toLowerCase();
408: addresses.add(email);
409: }
410: }
411: }
412:
413: return addresses;
414: }
415:
416: private static DERObject getObject(byte[] ext) throws IOException {
417: ASN1InputStream aIn = new ASN1InputStream(ext);
418: ASN1OctetString octs = (ASN1OctetString) aIn.readObject();
419:
420: aIn = new ASN1InputStream(octs.getOctets());
421: return aIn.readObject();
422: }
423:
424: protected void checkSignerCert(X509Certificate cert, List errors,
425: List notifications) {
426: // get key length
427: PublicKey key = cert.getPublicKey();
428: int keyLenght = -1;
429: if (key instanceof RSAPublicKey) {
430: keyLenght = ((RSAPublicKey) key).getModulus().bitLength();
431: } else if (key instanceof DSAPublicKey) {
432: keyLenght = ((DSAPublicKey) key).getParams().getP()
433: .bitLength();
434: }
435: if (keyLenght != -1 && keyLenght <= shortKeyLength) {
436: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
437: "SignedMailValidator.shortSigningKey",
438: new Object[] { new Integer(keyLenght) });
439: notifications.add(msg);
440: }
441:
442: // warn if certificate has very long validity period
443: long validityPeriod = cert.getNotAfter().getTime()
444: - cert.getNotBefore().getTime();
445: if (validityPeriod > THIRTY_YEARS_IN_MILLI_SEC) {
446: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
447: "SignedMailValidator.longValidity", new Object[] {
448: new TrustedInput(cert.getNotBefore()),
449: new TrustedInput(cert.getNotAfter()) });
450: notifications.add(msg);
451: }
452:
453: // check key usage if digitalSignature or nonRepudiation is set
454: boolean[] keyUsage = cert.getKeyUsage();
455: if (keyUsage != null && !keyUsage[0] && !keyUsage[1]) {
456: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
457: "SignedMailValidator.signingNotPermitted");
458: errors.add(msg);
459: }
460:
461: // check extended key usage
462: try {
463: byte[] ext = cert.getExtensionValue(EXT_KEY_USAGE);
464: if (ext != null) {
465: ExtendedKeyUsage extKeyUsage = ExtendedKeyUsage
466: .getInstance(getObject(ext));
467: if (!extKeyUsage
468: .hasKeyPurposeId(KeyPurposeId.anyExtendedKeyUsage)
469: && !extKeyUsage
470: .hasKeyPurposeId(KeyPurposeId.id_kp_emailProtection)) {
471: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
472: "SignedMailValidator.extKeyUsageNotPermitted");
473: errors.add(msg);
474: }
475: }
476: } catch (Exception e) {
477: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
478: "SignedMailValidator.extKeyUsageError",
479: new Object[] { e.getMessage(), e,
480: e.getClass().getName() });
481: errors.add(msg);
482: }
483:
484: // cert has an email address
485: try {
486: Set certEmails = getEmailAddresses(cert);
487: if (certEmails.isEmpty()) {
488: // error no email address in signing certificate
489: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
490: "SignedMailValidator.noEmailInCert");
491: errors.add(msg);
492: } else {
493: // check if email in cert is equal to the from address in the
494: // message
495: boolean equalsFrom = false;
496: for (int i = 0; i < fromAddresses.length; i++) {
497: if (certEmails.contains(fromAddresses[i]
498: .toLowerCase())) {
499: equalsFrom = true;
500: break;
501: }
502: }
503: if (!equalsFrom) {
504: ErrorBundle msg = new ErrorBundle(
505: RESOURCE_NAME,
506: "SignedMailValidator.emailFromCertMismatch",
507: new Object[] {
508: new UntrustedInput(Arrays
509: .toString(fromAddresses)),
510: new UntrustedInput(certEmails) });
511: errors.add(msg);
512: }
513: }
514: } catch (Exception e) {
515: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
516: "SignedMailValidator.certGetEmailError",
517: new Object[] { e.getMessage(), e,
518: e.getClass().getName() });
519: errors.add(msg);
520: }
521: }
522:
523: public static Date getSignatureTime(SignerInformation signer) {
524: AttributeTable atab = signer.getSignedAttributes();
525: Date result = null;
526: if (atab != null) {
527: Attribute attr = atab.get(CMSAttributes.signingTime);
528: if (attr != null) {
529: Time t = Time.getInstance(attr.getAttrValues()
530: .getObjectAt(0).getDERObject());
531: result = t.getDate();
532: }
533: }
534: return result;
535: }
536:
537: private static List findCerts(List certStores,
538: X509CertSelector selector) throws CertStoreException {
539: List result = new ArrayList();
540: Iterator it = certStores.iterator();
541: while (it.hasNext()) {
542: CertStore store = (CertStore) it.next();
543: Collection coll = store.getCertificates(selector);
544: result.addAll(coll);
545: }
546: return result;
547: }
548:
549: private static X509Certificate findNextCert(List certStores,
550: X509CertSelector selector, Set certSet)
551: throws CertStoreException {
552: Iterator certIt = findCerts(certStores, selector).iterator();
553:
554: boolean certFound = false;
555: X509Certificate nextCert = null;
556: while (certIt.hasNext()) {
557: nextCert = (X509Certificate) certIt.next();
558: if (!certSet.contains(nextCert)) {
559: certFound = true;
560: break;
561: }
562: }
563:
564: return certFound ? nextCert : null;
565: }
566:
567: /**
568: *
569: * @param signerCert the end of the path
570: * @param trustanchors trust anchors for the path
571: * @param certStores
572: * @return the resulting certificate path.
573: * @throws GeneralSecurityException
574: */
575: public static CertPath createCertPath(X509Certificate signerCert,
576: Set trustanchors, List certStores)
577: throws GeneralSecurityException {
578: Object[] results = createCertPath(signerCert, trustanchors,
579: certStores, null);
580: return (CertPath) results[0];
581: }
582:
583: /**
584: * Returns an Object array containing a CertPath and a List of Booleans. The list contains the value <code>true</code>
585: * if the corresponding certificate in the CertPath was taken from the user provided CertStores.
586: * @param signerCert the end of the path
587: * @param trustanchors trust anchors for the path
588: * @param systemCertStores list of {@link CertStore} provided by the system
589: * @param userCertStores list of {@link CertStore} provided by the user
590: * @return a CertPath and a List of booleans.
591: * @throws GeneralSecurityException
592: */
593: public static Object[] createCertPath(X509Certificate signerCert,
594: Set trustanchors, List systemCertStores, List userCertStores)
595: throws GeneralSecurityException {
596: Set certSet = new LinkedHashSet();
597: List userProvidedList = new ArrayList();
598:
599: // add signer certificate
600:
601: X509Certificate cert = signerCert;
602: certSet.add(cert);
603: userProvidedList.add(new Boolean(true));
604:
605: boolean trustAnchorFound = false;
606:
607: X509Certificate taCert = null;
608:
609: // add other certs to the cert path
610: while (cert != null && !trustAnchorFound) {
611: // check if cert Issuer is Trustanchor
612: Iterator trustIt = trustanchors.iterator();
613: while (trustIt.hasNext()) {
614: TrustAnchor anchor = (TrustAnchor) trustIt.next();
615: X509Certificate anchorCert = anchor.getTrustedCert();
616: if (anchorCert != null) {
617: if (anchorCert.getSubjectX500Principal().equals(
618: cert.getIssuerX500Principal())) {
619: try {
620: cert
621: .verify(anchorCert.getPublicKey(),
622: "BC");
623: trustAnchorFound = true;
624: taCert = anchorCert;
625: break;
626: } catch (Exception e) {
627: // trustanchor not found
628: }
629: }
630: } else {
631: if (anchor.getCAName().equals(
632: cert.getIssuerX500Principal().getName())) {
633: try {
634: cert.verify(anchor.getCAPublicKey(), "BC");
635: trustAnchorFound = true;
636: break;
637: } catch (Exception e) {
638: // trustanchor not found
639: }
640: }
641: }
642: }
643:
644: if (!trustAnchorFound) {
645: // add next cert to path
646: X509CertSelector select = new X509CertSelector();
647: select.setSubject(cert.getIssuerX500Principal());
648: byte[] authKeyIdentBytes = cert
649: .getExtensionValue(X509Extensions.AuthorityKeyIdentifier
650: .getId());
651: if (authKeyIdentBytes != null) {
652: try {
653: AuthorityKeyIdentifier kid = AuthorityKeyIdentifier
654: .getInstance(getObject(authKeyIdentBytes));
655: if (kid.getKeyIdentifier() != null) {
656: select
657: .setSubjectKeyIdentifier(new DEROctetString(
658: kid.getKeyIdentifier())
659: .getDEREncoded());
660: }
661: } catch (IOException ioe) {
662: // ignore
663: }
664: }
665: boolean userProvided = false;
666:
667: cert = findNextCert(systemCertStores, select, certSet);
668: if (cert == null && userCertStores != null) {
669: userProvided = true;
670: cert = findNextCert(userCertStores, select, certSet);
671: }
672:
673: if (cert != null) {
674: // cert found
675: certSet.add(cert);
676: userProvidedList.add(new Boolean(userProvided));
677: }
678: }
679: }
680:
681: // if a trustanchor was found - try to find a selfsigned certificate of
682: // the trustanchor
683: if (trustAnchorFound) {
684: if (taCert != null
685: && taCert.getSubjectX500Principal().equals(
686: taCert.getIssuerX500Principal())) {
687: certSet.add(taCert);
688: userProvidedList.add(new Boolean(false));
689: } else {
690: X509CertSelector select = new X509CertSelector();
691: select.setSubject(cert.getIssuerX500Principal());
692: select.setIssuer(cert.getIssuerX500Principal());
693:
694: boolean userProvided = false;
695:
696: taCert = findNextCert(systemCertStores, select, certSet);
697: if (taCert == null && userCertStores != null) {
698: userProvided = true;
699: taCert = findNextCert(userCertStores, select,
700: certSet);
701: }
702: if (taCert != null) {
703: try {
704: cert.verify(taCert.getPublicKey(), "BC");
705: certSet.add(taCert);
706: userProvidedList.add(new Boolean(userProvided));
707: } catch (GeneralSecurityException gse) {
708: // wrong cert
709: }
710: }
711: }
712: }
713:
714: CertPath certPath = CertificateFactory.getInstance("X.509",
715: "BC").generateCertPath(new ArrayList(certSet));
716: return new Object[] { certPath, userProvidedList };
717: }
718:
719: public CertStore getCertsAndCRLs() {
720: return certs;
721: }
722:
723: public SignerInformationStore getSignerInformationStore() {
724: return signers;
725: }
726:
727: public ValidationResult getValidationResult(SignerInformation signer)
728: throws SignedMailValidatorException {
729: if (signers.getSigners(signer.getSID()).isEmpty()) {
730: // the signer is not part of the SignerInformationStore
731: // he has not signed the message
732: ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
733: "SignedMailValidator.wrongSigner");
734: throw new SignedMailValidatorException(msg);
735: } else {
736: return (ValidationResult) results.get(signer);
737: }
738: }
739:
740: public class ValidationResult {
741:
742: private PKIXCertPathReviewer review;
743:
744: private List errors;
745:
746: private List notifications;
747:
748: private List userProvidedCerts;
749:
750: private boolean signVerified;
751:
752: ValidationResult(PKIXCertPathReviewer review, boolean verified,
753: List errors, List notifications, List userProvidedCerts) {
754: this .review = review;
755: this .errors = errors;
756: this .notifications = notifications;
757: signVerified = verified;
758: this .userProvidedCerts = userProvidedCerts;
759: }
760:
761: /**
762: * Returns a list of error messages of type {@link ErrorBundle}.
763: *
764: * @return List of error messages
765: */
766: public List getErrors() {
767: return errors;
768: }
769:
770: /**
771: * Returns a list of notification messages of type {@link ErrorBundle}.
772: *
773: * @return List of notification messages
774: */
775: public List getNotifications() {
776: return notifications;
777: }
778:
779: /**
780: *
781: * @return the PKIXCertPathReviewer for the CertPath of this signature
782: * or null if an Exception occured.
783: */
784: public PKIXCertPathReviewer getCertPathReview() {
785: return review;
786: }
787:
788: /**
789: *
790: * @return the CertPath for this signature
791: * or null if an Exception occured.
792: */
793: public CertPath getCertPath() {
794: return review != null ? review.getCertPath() : null;
795: }
796:
797: /**
798: *
799: * @return a List of Booleans that are true if the corresponding certificate in the CertPath was taken from
800: * the CertStore of the SMIME message
801: */
802: public List getUserProvidedCerts() {
803: return userProvidedCerts;
804: }
805:
806: /**
807: *
808: * @return true if the signature corresponds to the public key of the
809: * signer
810: */
811: public boolean isVerifiedSignature() {
812: return signVerified;
813: }
814:
815: /**
816: *
817: * @return true if the signature is valid (ie. if it corresponds to the
818: * public key of the signer and the cert path for the signers
819: * certificate is also valid)
820: */
821: public boolean isValidSignature() {
822: if (review != null) {
823: return signVerified && review.isValidCertPath()
824: && errors.isEmpty();
825: } else {
826: return false;
827: }
828: }
829:
830: }
831: }
|