001 // Copyright (C) 2002 IAIK
002 // https://jce.iaik.tugraz.at
003 //
004 // Copyright (C) 2003 - 2025 Stiftung Secure Information and
005 // Communication Technologies SIC
006 // https://sic.tech
007 //
008 // All rights reserved.
009 //
010 // Redistribution and use in source and binary forms, with or without
011 // modification, are permitted provided that the following conditions
012 // are met:
013 // 1. Redistributions of source code must retain the above copyright
014 // notice, this list of conditions and the following disclaimer.
015 // 2. Redistributions in binary form must reproduce the above copyright
016 // notice, this list of conditions and the following disclaimer in the
017 // documentation and/or other materials provided with the distribution.
018 //
019 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
020 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
021 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
023 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
024 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
025 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
027 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
028 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
029 // SUCH DAMAGE.
030
031 // Copyright (C) 2002 IAIK
032 // https://sic.tech/
033 //
034 // Copyright (C) 2003 - 2025 Stiftung Secure Information and
035 // Communication Technologies SIC
036 // https://sic.tech/
037 //
038 // All rights reserved.
039 //
040 // This source is provided for inspection purposes and recompilation only,
041 // unless specified differently in a contract with IAIK. This source has to
042 // be kept in strict confidence and must not be disclosed to any third party
043 // under any circumstances. Redistribution in source and binary forms, with
044 // or without modification, are <not> permitted in any case!
045 //
046 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
047 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
048 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
049 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
050 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
051 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
052 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
053 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
054 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
055 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
056 // SUCH DAMAGE.
057 //
058 // $Header: /IAIK-CMS/current/src/demo/cms/signedData/SignedDataOutputStreamDemo.java 17 12.02.25 17:58 Dbratko $
059 // $Revision: 17 $
060 //
061
062 package demo.cms.signedData;
063
064 import iaik.asn1.ObjectID;
065 import iaik.asn1.structures.AlgorithmID;
066 import iaik.asn1.structures.Attribute;
067 import iaik.asn1.structures.PolicyInformation;
068 import iaik.asn1.structures.PolicyQualifierInfo;
069 import iaik.cms.CMSException;
070 import iaik.cms.ContentInfoOutputStream;
071 import iaik.cms.IssuerAndSerialNumber;
072 import iaik.cms.SignedDataOutputStream;
073 import iaik.cms.SignedDataStream;
074 import iaik.cms.SignerInfo;
075 import iaik.cms.SubjectKeyID;
076 import iaik.cms.attributes.CMSContentType;
077 import iaik.cms.attributes.SigningTime;
078 import iaik.smime.ess.SigningCertificate;
079 import iaik.smime.ess.SigningCertificateV2;
080 import iaik.utils.Util;
081 import iaik.x509.X509Certificate;
082 import iaik.x509.X509ExtensionException;
083 import iaik.x509.attr.AttributeCertificate;
084
085 import java.io.ByteArrayInputStream;
086 import java.io.ByteArrayOutputStream;
087 import java.io.IOException;
088 import java.io.InputStream;
089 import java.security.NoSuchAlgorithmException;
090 import java.security.PrivateKey;
091 import java.security.SignatureException;
092 import java.security.cert.Certificate;
093
094 import demo.DemoUtil;
095 import demo.keystore.CMSKeyStore;
096
097
098 /**
099 * Demonstrates the usage of class {@link iaik.cms.SignedDataOutputStream} and
100 * {@link iaik.cms.SignedDataOutputStream} for signing some data using the CMS type
101 * SignedData.
102 */
103 public class SignedDataOutputStreamDemo {
104
105 // certificate of user 1
106 X509Certificate user1;
107 // private key of user 1
108 PrivateKey user1_pk;
109 // certificate of user 2
110 X509Certificate user2;
111 // private key of user 2
112 PrivateKey user2_pk;
113
114 // a certificate chain containing the user certs + CA
115 Certificate[] certificates;
116 Certificate[] certs;
117 Certificate[] user1Certs;
118
119 // just for attribute certificate testing
120 PrivateKey issuer1_pk;
121
122 /**
123 * Setups the demo certificate chains.
124 *
125 * Keys and certificate are retrieved from the demo KeyStore.
126 *
127 * @throws IOException if an file read error occurs
128 */
129 public SignedDataOutputStreamDemo() throws IOException {
130
131 System.out.println();
132 System.out.println("**********************************************************************************");
133 System.out.println("* SignedDataOutputStream demo *");
134 System.out.println("* (shows the usage of the CMS SignedDataOutputStream implementation) *");
135 System.out.println("**********************************************************************************");
136 System.out.println();
137
138 // add all certificates to the list
139 user1Certs = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
140 user1 = (X509Certificate)user1Certs[0];
141 user1_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
142 user2 = CMSKeyStore.getCertificateChain(CMSKeyStore.DSA, CMSKeyStore.SZ_1024_SIGN)[0];
143 user2_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.DSA, CMSKeyStore.SZ_1024_SIGN);
144
145 certs = user1Certs;
146
147 certificates = new Certificate[certs.length+1];
148 System.arraycopy(certs, 0, certificates, 0, certs.length);
149 certificates[certs.length] = user2;
150 }
151
152 /**
153 * Creates and encodes a CMS <code>SignedData</code> object.
154 * <p>
155 *
156 * @param message the message to be signed, as byte representation
157 * @param mode the transmission mode, either IMPLICIT or EXPLICIT
158 * @return the BER encoding of the <code>SignedData</code> object just created, wrapped into a ContentInfo
159 * @throws CMSException if the <code>SignedData</code> object cannot
160 * be created
161 * @throws IOException if some stream I/O error occurs
162 */
163 public byte[] createSignedDataStream(byte[] message, int mode) throws CMSException, IOException {
164
165 System.out.print("Create a new message signed by user 1 :");
166
167 // a stream from which to read the data
168 ByteArrayInputStream is = new ByteArrayInputStream(message);
169
170 // the stream to which to write the SignedData
171 ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
172
173 // wrap SignedData into a ContentInfo
174 ContentInfoOutputStream contentInfoStream =
175 new ContentInfoOutputStream(ObjectID.cms_signedData, resultStream);
176 SignedDataOutputStream signedData = new SignedDataOutputStream(contentInfoStream, mode);
177
178 // add the certificates
179 signedData.addCertificates(certificates);
180
181 // cert at index 0 is the user certificate
182 IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(user1);
183
184 // create a new SignerInfo
185 SignerInfo signerInfo = new SignerInfo(issuer, (AlgorithmID)AlgorithmID.sha256.clone(), user1_pk);
186 // create some authenticated attributes
187 // the message digest attribute is automatically added
188 Attribute[] attributes = new Attribute[3];
189 try {
190 // content type is data
191 CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
192 attributes[0] = new Attribute(contentType);
193 // signing time is now
194 SigningTime signingTime = new SigningTime();
195 attributes[1] = new Attribute(signingTime);
196 // signing certificate
197 SigningCertificateV2 signingCertificate = new SigningCertificateV2(certs, true);
198 String explicitText = "This certificate only may be used for test purposes";
199 PolicyQualifierInfo policyQualifier = new PolicyQualifierInfo(null, null, explicitText);
200 PolicyInformation[] policyInformations =
201 { new PolicyInformation(new ObjectID("1.3.6.1.4.1.2706.17.0.11.1.1"),
202 new PolicyQualifierInfo[] { policyQualifier }) };
203 signingCertificate.setPolicies(policyInformations);
204 System.out.println("Include signingCertificate attribute:");
205 System.out.println(signingCertificate);
206 attributes[2] = new Attribute(signingCertificate);
207 } catch (Exception ex) {
208 throw new CMSException("Error creating attribute: " + ex.toString());
209 }
210 // set the attributes
211 signerInfo.setSignedAttributes(attributes);
212 // finish the creation of SignerInfo by calling method addSigner
213 try {
214 signedData.addSignerInfo(signerInfo);
215 // another SignerInfo without signed attributes and SHA-256 as hash algorithm
216 signerInfo = new SignerInfo(new SubjectKeyID(user2),
217 (AlgorithmID)AlgorithmID.sha1.clone(),
218 (AlgorithmID)AlgorithmID.dsaWithSHA.clone(),
219 user2_pk);
220
221 // the message digest itself is protected
222 signedData.addSignerInfo(signerInfo);
223
224 } catch (NoSuchAlgorithmException ex) {
225 throw new CMSException(ex.toString());
226 } catch (X509ExtensionException ex) {
227 throw new CMSException("Cannot create SubjectKeyID for user2 : " + ex.getMessage());
228 }
229
230 int blockSize = 4; // in real world we would use a block size like 2048
231 // write in the data to be signed
232 byte[] buffer = new byte[blockSize];
233 int bytesRead;
234 while ((bytesRead = is.read(buffer)) != -1) {
235 signedData.write(buffer, 0, bytesRead);
236 }
237
238 // closing the stream add the signer infos and closes the underlying stream
239 signedData.close();
240 return resultStream.toByteArray();
241 }
242
243
244 /**
245 * Parses a CMS <code>SignedData</code> object and verifies the signatures
246 * for all participated signers.
247 *
248 * @param signedData <code>SignedData</code> object (wrapped into a ContentInfo) as BER encoded byte array
249 * @param message the the message which was transmitted out-of-band (explicit signed)
250 *
251 * @return the inherent message as byte array
252 * @throws CMSException if any signature does not verify
253 * @throws IOException if some stream I/O error occurs
254 */
255 public byte[] getSignedDataStream(byte[] signedData, byte[] message) throws CMSException, IOException {
256
257 // we are testing the stream interface
258 ByteArrayInputStream is = new ByteArrayInputStream(signedData);
259 // create the SignedData object
260 SignedDataStream signed_data = new SignedDataStream(is);
261
262 if (signed_data.getMode() == SignedDataStream.EXPLICIT) {
263 // in explicit mode explicitly supply the content for hash computation
264 signed_data.setInputStream(new ByteArrayInputStream(message));
265 }
266
267 // get an InputStream for reading the signed content
268 InputStream data = signed_data.getInputStream();
269 ByteArrayOutputStream os = new ByteArrayOutputStream();
270 Util.copyStream(data, os, null);
271
272 System.out.println("SignedData contains the following signer information:");
273 SignerInfo[] signer_infos = signed_data.getSignerInfos();
274
275 int numberOfSignerInfos = signer_infos.length;
276 if (numberOfSignerInfos == 0) {
277 String warning = "Warning: Unsigned message (no SignerInfo included)!";
278 System.err.println(warning);
279 throw new CMSException(warning);
280 } else {
281 for (int i = 0; i < numberOfSignerInfos; i++) {
282
283 try {
284 // verify the signed data using the SignerInfo at index i
285 X509Certificate signer_cert = signed_data.verify(i);
286 // if the signature is OK the certificate of the signer is returned
287 System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN());
288 SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime);
289 if (signingTime != null) {
290 System.out.println("This message has been signed at " + signingTime.get());
291 }
292 CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType);
293 if (contentType != null) {
294 System.out.println("The content has CMS content type " + contentType.get().getName());
295 }
296 // check SigningCertificate attribute
297 try {
298 SigningCertificateV2 signingCertificate = signer_infos[i].getSigningCertificateV2Attribute();
299 if (signingCertificate != null) {
300 checkSigningCertificate(signingCertificate, signer_cert, signed_data, i);
301 }
302 } catch (CMSException ex) {
303 throw new CMSException("Error parsing SigningCertificate attribute: " + ex.getMessage());
304 }
305
306 } catch (SignatureException ex) {
307 // if the signature is not OK a SignatureException is thrown
308 System.out.println("Signature ERROR from signer: "+signed_data.getCertificate(signer_infos[i].getSignerIdentifier()).getSubjectDN());
309 throw new CMSException(ex.toString());
310 }
311 }
312
313 // now check alternative signature verification
314 System.out.println("Now check the signature assuming that no certs have been included:");
315 try {
316 SignerInfo signer_info = signed_data.verify(user1);
317 // if the signature is OK the certificate of the signer is returned
318 System.out.println("Signature OK from signer: "+user1.getSubjectDN());
319
320 } catch (SignatureException ex) {
321 // if the signature is not OK a SignatureException is thrown
322 System.out.println("Signature ERROR from signer: "+user1.getSubjectDN());
323 throw new CMSException(ex.toString());
324 }
325
326 System.out.println("Included attribute certificates:");
327 AttributeCertificate[] attributeCerts = signed_data.getAttributeCertificates();
328 if (attributeCerts == null) {
329 System.out.println("No attribute certificates");
330 } else {
331 for (int i = 0; i < attributeCerts.length; i++) {
332 System.out.println(attributeCerts[i].getHolder());
333 }
334 }
335
336 try {
337 SignerInfo signer_info = signed_data.verify(user2);
338 // if the signature is OK the certificate of the signer is returned
339 System.out.println("Signature OK from signer: "+signed_data.getCertificate(signer_info.getSignerIdentifier()).getSubjectDN());
340
341 } catch (SignatureException ex) {
342 // if the signature is not OK a SignatureException is thrown
343 System.out.println("Signature ERROR from signer: "+user2.getSubjectDN());
344 throw new CMSException(ex.toString());
345 }
346 // in practice we also would validate the signer certificate(s)
347 }
348
349 return os.toByteArray();
350 }
351
352
353 /**
354 * Checks the SigningCertificate attribute.
355 *
356 * @param signingCertificate the SigningCertificate attribute
357 * @param signerCert the certificate of the signer
358 * @param signedData the SignedData containing the SignerInfo with the SigningCertificate
359 * attribute to be checked
360 * @param signerInfoIndex the index of the SignerInfo with the SigningCertificate
361 * attribute to be checked
362 *
363 * @throws CMSException if the SigningCertificate check fails
364 */
365 private void checkSigningCertificate(SigningCertificate signingCertificate,
366 X509Certificate signerCert,
367 SignedDataStream signedData,
368 int signerInfoIndex) throws CMSException {
369 if (signedData.getSignerInfos()[signerInfoIndex].isSignerCertificate(signerCert) == false) {
370 throw new CMSException("Cert ERROR!!! The certificate used for signing is not the one " +
371 "identified by the SignerCertificate attribute!");
372 } else {
373 System.out.println("SigningCertificate attribute: Signer cert ok!");
374 }
375 if (signingCertificate != null) {
376 // get the authorization certs for this signerInfo
377 Certificate[] authCerts =
378 signingCertificate.getAuthorizedCertificates(signedData.getCertificates());
379 if (authCerts != null) {
380 System.out.println("SignedData contains the following authorization certs for SignerInfo No " + (signerInfoIndex+1) +":");
381 for (int j = 0; j < authCerts.length; j++) {
382 if (authCerts[j].getType().equalsIgnoreCase("X.509")) {
383 System.out.println("X.509 public key cert: " + ((X509Certificate)authCerts[j]).getSubjectDN());
384 } else {
385 System.out.println("X.509 attribute cert: " + ((AttributeCertificate)authCerts[j]).getHolder());
386 }
387 }
388 }
389 if (signingCertificate.countPolicies() > 0) {
390 // get the certs with PolicyInformations according to the SigningCertificate attribute:
391 Certificate[] policyCerts =
392 signingCertificate.getPolicyInformationCerts(signedData.getCertificates());
393 if (policyCerts != null) {
394 System.out.println("SignedData contains the following certs corresponding to policy informations of SignerInfo No "
395 + (signerInfoIndex+1) +":");
396 for (int j = 0; j < policyCerts.length; j++) {
397 if (policyCerts[j].getType().equalsIgnoreCase("X.509")) {
398 System.out.println("X.509 public key cert: " + ((X509Certificate)policyCerts[j]).getSubjectDN());
399 } else {
400 System.out.println("X.509 attribute cert: " + ((AttributeCertificate)policyCerts[j]).getHolder());
401 }
402 }
403 }
404 }
405 }
406
407 }
408
409
410 /**
411 * Demonstrates the CMS SignedDataOutputStream implementation.
412 */
413 public void start() {
414 // the test message
415 String m = "This is the test message.";
416 System.out.println("Test message: \""+m+"\"");
417 System.out.println();
418 byte[] message = m.getBytes();
419
420 try {
421 byte[] encoding;
422 byte[] received_message = null;
423 System.out.println("Stream implementation demos");
424 System.out.println("===========================");
425 //
426 // test CMS Implicit SignedDataOutputStream
427 //
428 System.out.println("\nImplicit SignedDataOutputStream demo [create]:\n");
429 encoding = createSignedDataStream(message, SignedDataOutputStream.IMPLICIT);
430 // transmit data
431 System.out.println("\nImplicit SignedDataStream demo [parse]:\n");
432 received_message = getSignedDataStream(encoding, null);
433 System.out.print("\nSigned content: ");
434 System.out.println(new String(received_message));
435
436 //
437 // test CMS Explicit SignedDataOutputStream
438 //
439 System.out.println("\nExplicit SignedDataOutputStream demo [create]:\n");
440 encoding = createSignedDataStream(message, SignedDataOutputStream.EXPLICIT);
441 // transmit data
442 System.out.println("\nExplicit SignedDataStream demo [parse]:\n");
443 received_message = getSignedDataStream(encoding, message);
444 System.out.print("\nSigned content: ");
445 System.out.println(new String(received_message));
446
447 } catch (Exception ex) {
448 ex.printStackTrace();
449 throw new RuntimeException(ex.toString());
450 }
451 }
452
453 /**
454 * The main method.
455 *
456 * @throws IOException
457 * if an I/O error occurs when reading required keys
458 * and certificates from files
459 */
460 public static void main(String argv[]) throws Exception {
461
462 DemoUtil.initDemos();
463 (new SignedDataOutputStreamDemo()).start();
464 System.out.println("\nReady!");
465 DemoUtil.waitKey();
466 }
467 }