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    }