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/SignedDataStreamDemoWithAdditionalSignerInfo.java 23 12.02.25 17:58 Dbratko $
059 // $Revision: 23 $
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.cms.CMSException;
068 import iaik.cms.DefaultSDSEncodeListener;
069 import iaik.cms.IssuerAndSerialNumber;
070 import iaik.cms.SignedDataStream;
071 import iaik.cms.SignerInfo;
072 import iaik.cms.attributes.CMSContentType;
073 import iaik.cms.attributes.SigningTime;
074 import iaik.utils.Util;
075 import iaik.x509.X509Certificate;
076
077 import java.io.ByteArrayInputStream;
078 import java.io.ByteArrayOutputStream;
079 import java.io.IOException;
080 import java.io.InputStream;
081 import java.security.NoSuchAlgorithmException;
082 import java.security.PrivateKey;
083 import java.security.SignatureException;
084 import java.security.cert.Certificate;
085
086 import demo.DemoUtil;
087 import demo.keystore.CMSKeyStore;
088
089 /**
090 * This class demonstrates the usage of an SDSEncodeListener to add a new SignerInfo to an
091 * existing, parsed SignedDataStream object.
092 * <p>
093 * A {@link iaik.cms.SDSEncodeListener SDSEncodeListener} allows an application to update
094 * a {@link iaik.cms.SignedDataStream SignedDataStream} during the encoding phase.
095 * <p>
096 * In some situations it may be useful to supply information to a SignedDataStream
097 * actually during encoding is performed. When implementing an SignedDataStream
098 * encode listener an application has the chance to update the SignedDataStream
099 * at two points during the encoding process: AFTER the content data has been
100 * processed and any digest has been calulated (= BEFORE any signature value
101 * is computed) and AFTER the signature values have been calculated. When doing
102 * so an application has to implement two abstract methods: {@link
103 * iaik.cms.SDSEncodeListener#beforeComputeSignature(SignedDataStream) beforeComputeSignature} and
104 * {@link iaik.cms.SDSEncodeListener#afterComputeSignature(SignedDataStream) afterComputeSignature}
105 * (of course, an application may implement any of the two methods in a
106 * way to do actually nothing (if no functionality is required)).
107 * <p>
108 * This demo uses the IAIK-CMS {@link iaik.cms.DefaultSDSEncodeListener DefaultSDSEncodeListener}
109 * for adding a new SignerInfo to an already existing SignedDataStream "on the fly".
110 *
111 * @see iaik.cms.SignedDataStream
112 * @see iaik.cms.SDSEncodeListener
113 */
114 public class SignedDataStreamDemoWithAdditionalSignerInfo {
115
116 byte[] message;
117
118 // signing certificate of user 1
119 X509Certificate user1_sign;
120 // signing private key of user 1
121 PrivateKey user1_sign_pk;
122 // signing certificate of user 2
123 X509Certificate user2_sign;
124 // signing private key of user 2
125 PrivateKey user2_sign_pk;
126
127 // a certificate chain containing the user certs + CA
128 X509Certificate[] certificates;
129
130 /**
131 * Constructor.
132 * Reads required keys/certs from the demo keystore.
133 */
134 public SignedDataStreamDemoWithAdditionalSignerInfo() {
135
136 System.out.println();
137 System.out.println("****************************************************************************************");
138 System.out.println("* SignedDataStreamDemoWithAdditionalSignerInfo *");
139 System.out.println("* (shows how to use a SDSEncodeListener for the CMS SignedDataStream implementation) *");
140 System.out.println("****************************************************************************************");
141 System.out.println();
142
143 message = "This is a test of the CMS implementation!".getBytes();
144 // signing certs
145 certificates = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
146 user1_sign = certificates[0];
147 user1_sign_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
148 user2_sign = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1)[0];
149 user2_sign_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
150 }
151
152 /**
153 * Creates a CMS <code>SignedData</code> object.
154 * <p>
155 *
156 * @param message the message to be signed, as byte representation
157 * @param mode the mode indicating whether to include the content
158 * (SignedDataStream.IMPLICIT) or not (SignedDataStream.EXPLICIT)
159 * @return the encoding of the <code>SignedData</code> object just created
160 * @throws CMSException if the <code>SignedData</code> object cannot
161 * be created
162 * @throws IOException if an I/O error occurs
163 */
164 public byte[] createSignedDataStream(byte[] message, int mode) throws CMSException, IOException {
165
166 System.out.println("Create a new message signed by user 1:");
167
168 // we are testing the stream interface
169 ByteArrayInputStream is = new ByteArrayInputStream(message);
170 // create a new SignedData object which includes the data
171 SignedDataStream signed_data = new SignedDataStream(is, mode);
172
173 // uses SDSEncodeListener here only to verify sigantures before finishing encoding
174 try {
175 signed_data.setSDSEncodeListener(new DefaultSDSEncodeListener());
176 } catch (NoSuchAlgorithmException ex) {
177 // ignore here
178 }
179
180 // SignedData shall include the certificate chain for verifying
181 signed_data.setCertificates(certificates);
182
183 // cert at index 0 is the user certificate
184 IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(user1_sign);
185
186 // create a new SignerInfo
187 SignerInfo signer_info = new SignerInfo(issuer, (AlgorithmID)AlgorithmID.sha256.clone(), user1_sign_pk);
188
189 // create some signed attributes
190 // the message digest attribute is automatically added
191 Attribute[] attributes = new Attribute[2];
192 try {
193 // content type is data
194 CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
195 attributes[0] = new Attribute(contentType);
196 // signing time is now
197 SigningTime signingTime = new SigningTime();
198 attributes[1] = new Attribute(signingTime);
199 } catch (Exception ex) {
200 throw new CMSException("Error creating attribute: " + ex.toString());
201 }
202
203 // set the attributes
204 signer_info.setSignedAttributes(attributes);
205 // finish the creation of SignerInfo by calling method addSigner
206 try {
207 signed_data.addSignerInfo(signer_info);
208
209 } catch (NoSuchAlgorithmException ex) {
210 throw new CMSException("No implementation for signature algorithm: "+ex.getMessage());
211 }
212 // ensure block encoding
213 signed_data.setBlockSize(2048);
214
215 // write the data through SignedData to any out-of-band place
216 if (mode == SignedDataStream.EXPLICIT) {
217 InputStream data_is = signed_data.getInputStream();
218 byte[] buf = new byte[1024];
219 int r;
220 while ((r = data_is.read(buf)) > 0) {
221 ; // skip data
222 }
223 }
224
225 // return the SignedData as encoded byte array with block size 2048
226 ByteArrayOutputStream os = new ByteArrayOutputStream();
227
228 signed_data.writeTo(os);
229 return os.toByteArray();
230 }
231
232 /**
233 * Parses a CMS <code>SignedData</code> object and verifies the signatures
234 * for all participated signers.
235 *
236 * @param signedData the SignedData, as BER encoded byte array
237 * @param message the the message which was transmitted out-of-band (explicit signed)
238 * @param writeAgain whether to use a SDSEncodeListener to add a SignerInfo
239 * and encode the SignedData again
240 *
241 * @return the inherent message as byte array, or the BER encoded SignedData if
242 * it shall be encoded again
243 * @throws CMSException if any signature does not verify
244 * @throws IOException if an I/O error occurs
245 */
246 public byte[] getSignedDataStream(byte[] signedData, byte[] message, boolean writeAgain)
247 throws CMSException, IOException, NoSuchAlgorithmException {
248
249 // we are testing the stream interface
250 ByteArrayInputStream is = new ByteArrayInputStream(signedData);
251
252 // the ByteArrayOutputStream to which to write the content
253 ByteArrayOutputStream os = new ByteArrayOutputStream();
254
255 SignedDataStream signed_data = new SignedDataStream(is);
256
257 if (signed_data.getMode() == SignedDataStream.EXPLICIT) {
258 // in explicit mode explicitly supply the content for hash computation
259 signed_data.setInputStream(new ByteArrayInputStream(message));
260 }
261
262 if (writeAgain) {
263 // we want to write the SignedData again
264 // create a new SignerInfo
265 SignerInfo signer_info = new SignerInfo(new IssuerAndSerialNumber(user2_sign),
266 (AlgorithmID)AlgorithmID.sha256.clone(),
267 user2_sign_pk);
268
269 // create some signed attributes
270 // the message digest attribute is automatically added
271 Attribute[] attributes = new Attribute[2];
272 try {
273 // content type is data
274 CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
275 attributes[0] = new Attribute(contentType);
276 // signing time is now
277 SigningTime signingTime = new SigningTime();
278 attributes[1] = new Attribute(signingTime);
279 } catch (Exception ex) {
280 throw new CMSException("Error creating attribute: " + ex.toString());
281 }
282 // set the attributes
283 signer_info.setSignedAttributes(attributes);
284
285 // add the SignerInfo via SDSEncodeListener which also verifies the sigantures
286 DefaultSDSEncodeListener dl = new DefaultSDSEncodeListener();
287 dl.setDigestAlgorithms(new AlgorithmID [] { (AlgorithmID)AlgorithmID.sha256.clone() });
288 dl.setSignerInfos(new SignerInfo[] { signer_info });
289 dl.setCertificates(new Certificate[] { user2_sign });
290
291
292 if (message == null) {
293 // in implicit mode copy data to os
294 dl.setOutputStream(os);
295 signed_data.setSDSEncodeListener(dl);
296
297 } else {
298 signed_data.setSDSEncodeListener(dl);
299 // get an InputStream for reading the signed content
300 InputStream data = signed_data.getInputStream();
301 Util.copyStream(data, os, null);
302 }
303
304 // ensure block encoding
305 signed_data.setBlockSize(2048);
306 // return the SignedData as encoded byte array with block size 2048
307 ByteArrayOutputStream baos = new ByteArrayOutputStream();
308 signed_data.writeTo(baos);
309
310 // we read the content
311 byte[] content = os.toByteArray();
312 System.out.println("Content: " + new String(content));
313
314 return baos.toByteArray();
315
316 } else {
317
318 // get an InputStream for reading the signed content
319 InputStream data = signed_data.getInputStream();
320 os = new ByteArrayOutputStream();
321 Util.copyStream(data, os, null);
322
323 System.out.println("SignedData contains the following signer information:");
324 SignerInfo[] signer_infos = signed_data.getSignerInfos();
325
326 int numberOfSignerInfos = signer_infos.length;
327 if (numberOfSignerInfos == 0) {
328 String warning = "Warning: Unsigned message (no SignerInfo included)!";
329 System.err.println(warning);
330 throw new CMSException(warning);
331 } else {
332 for (int i = 0; i < numberOfSignerInfos; i++) {
333 try {
334 // verify the signed data using the SignerInfo at index i
335 X509Certificate signer_cert = signed_data.verify(i);
336 // if the signature is OK the certificate of the signer is returned
337 System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN());
338 // get signed attributes
339 SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime);
340 if (signingTime != null) {
341 System.out.println("This message has been signed at " + signingTime.get());
342 }
343 CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType);
344 if (contentType != null) {
345 System.out.println("The content has CMS content type " + contentType.get().getName());
346 }
347
348 } catch (SignatureException ex) {
349 // if the signature is not OK a SignatureException is thrown
350 System.out.println("Signature ERROR from signer: "+signed_data.getCertificate((signer_infos[i].getSignerIdentifier())).getSubjectDN());
351 throw new CMSException(ex.toString());
352 }
353 }
354 // now check alternative signature verification
355 System.out.println("Now check the signature assuming that no certs have been included:");
356 try {
357 SignerInfo signer_info = signed_data.verify(user1_sign);
358 // if the signature is OK the certificate of the signer is returned
359 System.out.println("Signature OK from signer: "+signed_data.getCertificate(signer_info.getSignerIdentifier()).getSubjectDN());
360
361 } catch (SignatureException ex) {
362 // if the signature is not OK a SignatureException is thrown
363 System.out.println("Signature ERROR from signer: "+user1_sign.getSubjectDN());
364 throw new CMSException(ex.toString());
365 }
366
367 try {
368 SignerInfo signer_info = signed_data.verify(user2_sign);
369 // if the signature is OK the certificate of the signer is returned
370 System.out.println("Signature OK from signer: "+signed_data.getCertificate(signer_info.getSignerIdentifier()).getSubjectDN());
371
372 } catch (SignatureException ex) {
373 // if the signature is not OK a SignatureException is thrown
374 System.out.println("Signature ERROR from signer: "+user2_sign.getSubjectDN());
375 throw new CMSException(ex.toString());
376 }
377 // in practice we also would validate the signer certificate(s)
378 }
379 return os.toByteArray();
380 }
381
382 }
383
384 /**
385 * Starts the test.
386 */
387 public void start() {
388
389 try {
390
391 byte[] data;
392 byte[] received_message = null;
393
394
395 //
396 // test CMS Implicit SignedDataStream
397 //
398 System.out.println("\nImplicit SignedDataStream demo [create]:\n");
399 data = createSignedDataStream(message, SignedDataStream.IMPLICIT);
400 // parse and encode again
401 System.out.println("\nImplicit SignedDataStream demo [write again]:\n");
402 data = getSignedDataStream(data, null, true);
403 // parse
404 System.out.println("\nImplicit SignedDataStream demo [parse]:\n");
405 received_message = getSignedDataStream(data, null, false);
406 System.out.print("\nSigned content: ");
407 System.out.println(new String(received_message));
408
409 //
410 // test CMS Explicit SignedDataStream
411 //
412 System.out.println("\nExplicit SignedDataStream demo [create]:\n");
413 data = createSignedDataStream(message, SignedDataStream.EXPLICIT);
414 // parse and encode again
415 System.out.println("\nExplicit SignedDataStream demo [write again]:\n");
416 data = getSignedDataStream(data, message, true);
417
418 System.out.println("\nExplicit SignedDataStream demo [parse]:\n");
419 received_message = getSignedDataStream(data, message, false);
420 System.out.print("\nSigned content: ");
421 System.out.println(new String(received_message));
422
423 } catch (Exception ex) {
424 ex.printStackTrace();
425 throw new RuntimeException(ex.toString());
426 }
427 }
428
429
430 /**
431 * The main method.
432 *
433 * @throws IOException
434 * if an I/O error occurs when reading required keys
435 * and certificates from files
436 */
437 public static void main(String argv[]) throws IOException {
438 try {
439 DemoUtil.initDemos();
440 (new SignedDataStreamDemoWithAdditionalSignerInfo()).start();
441 } catch (Exception ex) {
442 ex.printStackTrace();
443 }
444 System.out.println("\nReady!");
445 DemoUtil.waitKey();
446 }
447 }