001: package org.bouncycastle.cms;
002:
003: import org.bouncycastle.asn1.ASN1InputStream;
004: import org.bouncycastle.asn1.ASN1Null;
005: import org.bouncycastle.asn1.ASN1OctetString;
006: import org.bouncycastle.asn1.ASN1OutputStream;
007: import org.bouncycastle.asn1.ASN1Sequence;
008: import org.bouncycastle.asn1.ASN1Set;
009: import org.bouncycastle.asn1.DEREncodable;
010: import org.bouncycastle.asn1.DERNull;
011: import org.bouncycastle.asn1.DERObject;
012: import org.bouncycastle.asn1.DERObjectIdentifier;
013: import org.bouncycastle.asn1.DEROutputStream;
014: import org.bouncycastle.asn1.DERSet;
015: import org.bouncycastle.asn1.DERTags;
016: import org.bouncycastle.asn1.cms.Attribute;
017: import org.bouncycastle.asn1.cms.AttributeTable;
018: import org.bouncycastle.asn1.cms.CMSAttributes;
019: import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
020: import org.bouncycastle.asn1.cms.SignerIdentifier;
021: import org.bouncycastle.asn1.cms.SignerInfo;
022: import org.bouncycastle.asn1.cms.Time;
023: import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
024: import org.bouncycastle.asn1.x509.DigestInfo;
025:
026: import javax.crypto.Cipher;
027: import java.io.ByteArrayOutputStream;
028: import java.io.IOException;
029: import java.security.GeneralSecurityException;
030: import java.security.InvalidKeyException;
031: import java.security.MessageDigest;
032: import java.security.NoSuchAlgorithmException;
033: import java.security.NoSuchProviderException;
034: import java.security.PublicKey;
035: import java.security.Signature;
036: import java.security.SignatureException;
037: import java.security.cert.CertificateExpiredException;
038: import java.security.cert.CertificateNotYetValidException;
039: import java.security.cert.X509Certificate;
040:
041: /**
042: * an expanded SignerInfo block from a CMS Signed message
043: */
044: public class SignerInformation {
045: private SignerId sid;
046: private SignerInfo info;
047: private AlgorithmIdentifier digestAlgorithm;
048: private AlgorithmIdentifier encryptionAlgorithm;
049: private ASN1Set signedAttributes;
050: private ASN1Set unsignedAttributes;
051: private CMSProcessable content;
052: private byte[] signature;
053: private DERObjectIdentifier contentType;
054: private byte[] _digest;
055: private byte[] _resultDigest;
056:
057: SignerInformation(SignerInfo info, DERObjectIdentifier contentType,
058: CMSProcessable content, byte[] digest) {
059: this .info = info;
060: this .sid = new SignerId();
061: this .contentType = contentType;
062:
063: try {
064: SignerIdentifier s = info.getSID();
065:
066: if (s.isTagged()) {
067: ASN1OctetString octs = ASN1OctetString.getInstance(s
068: .getId());
069:
070: sid.setSubjectKeyIdentifier(octs.getOctets());
071: } else {
072: IssuerAndSerialNumber iAnds = IssuerAndSerialNumber
073: .getInstance(s.getId());
074:
075: ByteArrayOutputStream bOut = new ByteArrayOutputStream();
076: ASN1OutputStream aOut = new ASN1OutputStream(bOut);
077:
078: aOut.writeObject(iAnds.getName());
079:
080: sid.setIssuer(bOut.toByteArray());
081: sid.setSerialNumber(iAnds.getSerialNumber().getValue());
082: }
083: } catch (IOException e) {
084: throw new IllegalArgumentException(
085: "invalid sid in SignerInfo");
086: }
087:
088: this .digestAlgorithm = info.getDigestAlgorithm();
089: this .signedAttributes = info.getAuthenticatedAttributes();
090: this .unsignedAttributes = info.getUnauthenticatedAttributes();
091: this .encryptionAlgorithm = info.getDigestEncryptionAlgorithm();
092: this .signature = info.getEncryptedDigest().getOctets();
093:
094: this .content = content;
095: _digest = digest;
096: }
097:
098: private byte[] encodeObj(DEREncodable obj) throws IOException {
099: if (obj != null) {
100: ByteArrayOutputStream bOut = new ByteArrayOutputStream();
101: ASN1OutputStream aOut = new ASN1OutputStream(bOut);
102:
103: aOut.writeObject(obj);
104:
105: return bOut.toByteArray();
106: }
107:
108: return null;
109: }
110:
111: public SignerId getSID() {
112: return sid;
113: }
114:
115: /**
116: * return the version number for this objects underlying SignerInfo structure.
117: */
118: public int getVersion() {
119: return info.getVersion().getValue().intValue();
120: }
121:
122: /**
123: * return the object identifier for the signature.
124: */
125: public String getDigestAlgOID() {
126: return digestAlgorithm.getObjectId().getId();
127: }
128:
129: /**
130: * return the signature parameters, or null if there aren't any.
131: */
132: public byte[] getDigestAlgParams() {
133: try {
134: return encodeObj(digestAlgorithm.getParameters());
135: } catch (Exception e) {
136: throw new RuntimeException(
137: "exception getting digest parameters " + e);
138: }
139: }
140:
141: /**
142: * return the content digest that was calculated during verification.
143: */
144: public byte[] getContentDigest() {
145: if (_resultDigest == null) {
146: throw new IllegalStateException(
147: "method can only be called after verify.");
148: }
149:
150: return (byte[]) _resultDigest.clone();
151: }
152:
153: /**
154: * return the object identifier for the signature.
155: */
156: public String getEncryptionAlgOID() {
157: return encryptionAlgorithm.getObjectId().getId();
158: }
159:
160: /**
161: * return the signature/encyrption algorithm parameters, or null if
162: * there aren't any.
163: */
164: public byte[] getEncryptionAlgParams() {
165: try {
166: return encodeObj(encryptionAlgorithm.getParameters());
167: } catch (Exception e) {
168: throw new RuntimeException(
169: "exception getting encryption parameters " + e);
170: }
171: }
172:
173: /**
174: * return a table of the signed attributes - indexed by
175: * the OID of the attribute.
176: */
177: public AttributeTable getSignedAttributes() {
178: if (signedAttributes == null) {
179: return null;
180: }
181:
182: return new AttributeTable(signedAttributes);
183: }
184:
185: /**
186: * return a table of the unsigned attributes indexed by
187: * the OID of the attribute.
188: */
189: public AttributeTable getUnsignedAttributes() {
190: if (unsignedAttributes == null) {
191: return null;
192: }
193:
194: return new AttributeTable(unsignedAttributes);
195: }
196:
197: /**
198: * return the encoded signature
199: */
200: public byte[] getSignature() {
201: return (byte[]) signature.clone();
202: }
203:
204: /**
205: * return the DER encoding of the signed attributes.
206: * @throws IOException if an encoding error occurs.
207: */
208: public byte[] getEncodedSignedAttributes() throws IOException {
209: if (signedAttributes != null) {
210: ByteArrayOutputStream bOut = new ByteArrayOutputStream();
211: DEROutputStream aOut = new DEROutputStream(bOut);
212:
213: aOut.writeObject(signedAttributes);
214:
215: return bOut.toByteArray();
216: }
217:
218: return null;
219: }
220:
221: private boolean doVerify(PublicKey key,
222: AttributeTable signedAttrTable, String sigProvider)
223: throws CMSException, NoSuchAlgorithmException,
224: NoSuchProviderException {
225: String digestName = CMSSignedHelper.INSTANCE
226: .getDigestAlgName(this .getDigestAlgOID());
227: String signatureName = digestName
228: + "with"
229: + CMSSignedHelper.INSTANCE.getEncryptionAlgName(this
230: .getEncryptionAlgOID());
231: Signature sig = CMSSignedHelper.INSTANCE.getSignatureInstance(
232: signatureName, sigProvider);
233: MessageDigest digest = CMSSignedHelper.INSTANCE
234: .getDigestInstance(digestName, sigProvider);
235:
236: try {
237: sig.initVerify(key);
238:
239: if (signedAttributes == null) {
240: if (content != null) {
241: content
242: .write(new CMSSignedDataGenerator.SigOutputStream(
243: sig));
244: content
245: .write(new CMSSignedDataGenerator.DigOutputStream(
246: digest));
247:
248: _resultDigest = digest.digest();
249: } else {
250: _resultDigest = _digest;
251:
252: // need to decrypt signature and check message bytes
253: return verifyDigest(_digest, key, this
254: .getSignature(), sigProvider);
255: }
256: } else {
257: byte[] hash;
258:
259: if (content != null) {
260: content
261: .write(new CMSSignedDataGenerator.DigOutputStream(
262: digest));
263:
264: hash = digest.digest();
265: } else {
266: hash = _digest;
267: }
268:
269: _resultDigest = hash;
270:
271: Attribute dig = signedAttrTable
272: .get(CMSAttributes.messageDigest);
273: Attribute type = signedAttrTable
274: .get(CMSAttributes.contentType);
275:
276: if (dig == null) {
277: throw new SignatureException(
278: "no hash for content found in signed attributes");
279: }
280:
281: if (type == null) {
282: throw new SignatureException(
283: "no content type id found in signed attributes");
284: }
285:
286: DERObject hashObj = dig.getAttrValues().getObjectAt(0)
287: .getDERObject();
288:
289: if (hashObj instanceof ASN1OctetString) {
290: byte[] signedHash = ((ASN1OctetString) hashObj)
291: .getOctets();
292:
293: if (!MessageDigest.isEqual(hash, signedHash)) {
294: throw new SignatureException(
295: "content hash found in signed attributes different");
296: }
297: } else if (hashObj instanceof DERNull) {
298: if (hash != null) {
299: throw new SignatureException(
300: "NULL hash found in signed attributes when one expected");
301: }
302: }
303:
304: DERObjectIdentifier typeOID = (DERObjectIdentifier) type
305: .getAttrValues().getObjectAt(0);
306:
307: if (!typeOID.equals(contentType)) {
308: throw new SignatureException(
309: "contentType in signed attributes different");
310: }
311:
312: sig.update(this .getEncodedSignedAttributes());
313: }
314:
315: return sig.verify(this .getSignature());
316: } catch (InvalidKeyException e) {
317: throw new CMSException(
318: "key not appropriate to signature in message.", e);
319: } catch (IOException e) {
320: throw new CMSException(
321: "can't process mime object to create signature.", e);
322: } catch (SignatureException e) {
323: throw new CMSException(
324: "invalid signature format in message: "
325: + e.getMessage(), e);
326: }
327: }
328:
329: private boolean isNull(DEREncodable o) {
330: return (o instanceof ASN1Null) || (o == null);
331: }
332:
333: private DigestInfo derDecode(byte[] encoding) throws IOException {
334: if (encoding[0] != (DERTags.CONSTRUCTED | DERTags.SEQUENCE)) {
335: throw new IOException("not a digest info object");
336: }
337:
338: ASN1InputStream aIn = new ASN1InputStream(encoding);
339:
340: return new DigestInfo((ASN1Sequence) aIn.readObject());
341: }
342:
343: private boolean verifyDigest(byte[] digest, PublicKey key,
344: byte[] signature, String sigProvider)
345: throws NoSuchAlgorithmException, NoSuchProviderException,
346: CMSException {
347: String algorithm = CMSSignedHelper.INSTANCE
348: .getEncryptionAlgName(this .getEncryptionAlgOID());
349:
350: try {
351: if (algorithm.equals("RSA")) {
352: Cipher c;
353: if (sigProvider != null) {
354: c = Cipher.getInstance("RSA/ECB/PKCS1Padding",
355: sigProvider);
356: } else {
357: c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
358: }
359:
360: c.init(Cipher.DECRYPT_MODE, key);
361:
362: DigestInfo digInfo = derDecode(c.doFinal(signature));
363:
364: if (!digInfo.getAlgorithmId().getObjectId().equals(
365: digestAlgorithm.getObjectId())) {
366: return false;
367: }
368:
369: if (!isNull(digInfo.getAlgorithmId().getParameters())) {
370: return false;
371: }
372:
373: byte[] sigHash = digInfo.getDigest();
374:
375: return MessageDigest.isEqual(digest, sigHash);
376: } else if (algorithm.equals("DSA")) {
377: Signature sig;
378: if (sigProvider != null) {
379: sig = Signature.getInstance("NONEwithDSA",
380: sigProvider);
381: } else {
382: sig = Signature.getInstance("NONEwithDSA",
383: sigProvider);
384: }
385:
386: sig.initVerify(key);
387:
388: sig.update(digest);
389:
390: return sig.verify(signature);
391: } else {
392: throw new CMSException("algorithm: " + algorithm
393: + " not supported in base signatures.");
394: }
395: } catch (NoSuchAlgorithmException e) {
396: throw e;
397: } catch (NoSuchProviderException e) {
398: throw e;
399: } catch (GeneralSecurityException e) {
400: throw new CMSException("Exception processing signature: "
401: + e, e);
402: } catch (IOException e) {
403: throw new CMSException(
404: "Exception decoding signature: " + e, e);
405: }
406: }
407:
408: /**
409: * verify that the given public key succesfully handles and confirms the
410: * signature associated with this signer.
411: */
412: public boolean verify(PublicKey key, String sigProvider)
413: throws NoSuchAlgorithmException, NoSuchProviderException,
414: CMSException {
415: return doVerify(key, this .getSignedAttributes(), sigProvider);
416: }
417:
418: /**
419: * verify that the given certificate succesfully handles and confirms
420: * the signature associated with this signer and, if a signingTime
421: * attribute is available, that the certificate was valid at the time the
422: * signature was generated.
423: */
424: public boolean verify(X509Certificate cert, String sigProvider)
425: throws NoSuchAlgorithmException, NoSuchProviderException,
426: CertificateExpiredException,
427: CertificateNotYetValidException, CMSException {
428: AttributeTable attr = this .getSignedAttributes();
429:
430: if (attr != null) {
431: Attribute t = attr.get(CMSAttributes.signingTime);
432:
433: if (t != null) {
434: Time time = Time.getInstance(t.getAttrValues()
435: .getObjectAt(0).getDERObject());
436:
437: cert.checkValidity(time.getDate());
438: }
439: }
440:
441: return doVerify(cert.getPublicKey(), attr, sigProvider);
442: }
443:
444: /**
445: * Return the base ASN.1 CMS structure that this object contains.
446: *
447: * @return an object containing a CMS SignerInfo structure.
448: */
449: public SignerInfo toSignerInfo() {
450: return info;
451: }
452:
453: /**
454: * Return a signer information object with the passed in unsigned
455: * attributes replacing the ones that are current associated with
456: * the object passed in.
457: *
458: * @param signerInformation the signerInfo to be used as the basis.
459: * @param unsignedAttributes the unsigned attributes to add.
460: * @return a copy of the original SignerInformationObject with the changed attributes.
461: */
462: public static SignerInformation replaceUnsignedAttributes(
463: SignerInformation signerInformation,
464: AttributeTable unsignedAttributes) {
465: SignerInfo sInfo = signerInformation.info;
466: ASN1Set unsignedAttr = null;
467:
468: if (unsignedAttributes != null) {
469: unsignedAttr = new DERSet(unsignedAttributes
470: .toASN1EncodableVector());
471: }
472:
473: return new SignerInformation(new SignerInfo(sInfo.getSID(),
474: sInfo.getDigestAlgorithm(), sInfo
475: .getAuthenticatedAttributes(), sInfo
476: .getDigestEncryptionAlgorithm(), sInfo
477: .getEncryptedDigest(), unsignedAttr),
478: signerInformation.contentType,
479: signerInformation.content, null);
480: }
481: }
|