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    }