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