001// Copyright (C) 2002 IAIK
002// https://sic.tech/
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// This source is provided for inspection purposes and recompilation only,
011// unless specified differently in a contract with IAIK. This source has to
012// be kept in strict confidence and must not be disclosed to any third party
013// under any circumstances. Redistribution in source and binary forms, with
014// or without modification, are <not> permitted in any case!
015//
016// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
017// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
018// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
019// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
020// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
021// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
022// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
023// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
024// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
025// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
026// SUCH DAMAGE.
027//
028// $Header: /IAIK-CMS/current/src/demo/cms/signedData/SignedDataStreamDemoWithAdditionalSignerInfo.java 23    12.02.25 17:58 Dbratko $
029// $Revision: 23 $
030//
031
032package demo.cms.signedData;
033
034import iaik.asn1.ObjectID;
035import iaik.asn1.structures.AlgorithmID;
036import iaik.asn1.structures.Attribute;
037import iaik.cms.CMSException;
038import iaik.cms.DefaultSDSEncodeListener;
039import iaik.cms.IssuerAndSerialNumber;
040import iaik.cms.SignedDataStream;
041import iaik.cms.SignerInfo;
042import iaik.cms.attributes.CMSContentType;
043import iaik.cms.attributes.SigningTime;
044import iaik.utils.Util;
045import iaik.x509.X509Certificate;
046
047import java.io.ByteArrayInputStream;
048import java.io.ByteArrayOutputStream;
049import java.io.IOException;
050import java.io.InputStream;
051import java.security.NoSuchAlgorithmException;
052import java.security.PrivateKey;
053import java.security.SignatureException;
054import java.security.cert.Certificate;
055
056import demo.DemoUtil;
057import demo.keystore.CMSKeyStore;
058
059/**
060 * This class demonstrates the usage of an SDSEncodeListener to add a new SignerInfo to an
061 * existing, parsed SignedDataStream object.
062 * <p>
063 * A {@link iaik.cms.SDSEncodeListener SDSEncodeListener} allows an application to update 
064 * a {@link iaik.cms.SignedDataStream SignedDataStream} during the encoding phase.
065 * <p>
066 * In some situations it may be useful to supply information to a SignedDataStream
067 * actually during encoding is performed. When implementing an SignedDataStream
068 * encode listener an application has the chance to update the SignedDataStream
069 * at two points during the encoding process: AFTER the content data has been
070 * processed and any digest has been calulated (= BEFORE any signature value
071 * is computed) and AFTER the signature values have been calculated. When doing
072 * so an application has to implement two abstract methods: {@link 
073 * iaik.cms.SDSEncodeListener#beforeComputeSignature(SignedDataStream) beforeComputeSignature} and 
074 * {@link iaik.cms.SDSEncodeListener#afterComputeSignature(SignedDataStream) afterComputeSignature}
075 * (of course, an application may implement any of the two methods in a 
076 * way to do actually nothing (if no functionality is required)).
077 * <p>
078 * This demo uses the IAIK-CMS {@link iaik.cms.DefaultSDSEncodeListener DefaultSDSEncodeListener}
079 * for adding a new SignerInfo to an already existing SignedDataStream "on the fly".
080 *
081 * @see iaik.cms.SignedDataStream
082 * @see iaik.cms.SDSEncodeListener
083 */
084public class SignedDataStreamDemoWithAdditionalSignerInfo {
085
086  byte[] message;
087  
088  // signing certificate of user 1
089  X509Certificate user1_sign;
090  // signing private key of user 1
091  PrivateKey user1_sign_pk;
092  // signing certificate of user 2
093  X509Certificate user2_sign;
094  // signing private key of user 2
095  PrivateKey user2_sign_pk;
096  
097  // a certificate chain containing the user certs + CA
098  X509Certificate[] certificates;
099
100  /**
101   * Constructor.
102   * Reads required keys/certs from the demo keystore.
103   */
104  public SignedDataStreamDemoWithAdditionalSignerInfo() {
105    
106    System.out.println();
107    System.out.println("****************************************************************************************");
108    System.out.println("*                       SignedDataStreamDemoWithAdditionalSignerInfo                   *");
109    System.out.println("*  (shows how to use a SDSEncodeListener for the CMS SignedDataStream implementation)  *");
110    System.out.println("****************************************************************************************");
111    System.out.println();
112    
113    message = "This is a test of the CMS implementation!".getBytes();
114    // signing certs
115    certificates = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
116    user1_sign = certificates[0];
117    user1_sign_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
118    user2_sign = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1)[0];
119    user2_sign_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
120  }
121  
122  /**
123   * Creates a CMS <code>SignedData</code> object.
124   * <p>
125   *
126   * @param message the message to be signed, as byte representation
127   * @param mode the mode indicating whether to include the content 
128   *        (SignedDataStream.IMPLICIT) or not (SignedDataStream.EXPLICIT)
129   * @return the encoding of the <code>SignedData</code> object just created
130   * @throws CMSException if the <code>SignedData</code> object cannot
131   *                          be created
132   * @throws IOException if an I/O error occurs
133   */
134  public byte[] createSignedDataStream(byte[] message, int mode) throws CMSException, IOException  {
135
136    System.out.println("Create a new message signed by user 1:");
137
138    // we are testing the stream interface
139    ByteArrayInputStream is = new ByteArrayInputStream(message);
140    // create a new SignedData object which includes the data
141    SignedDataStream signed_data = new SignedDataStream(is, mode);
142    
143    // uses SDSEncodeListener here only to verify sigantures before finishing encoding
144    try {
145      signed_data.setSDSEncodeListener(new DefaultSDSEncodeListener());
146    } catch (NoSuchAlgorithmException ex) {
147      // ignore here  
148    }
149    
150    // SignedData shall include the certificate chain for verifying
151    signed_data.setCertificates(certificates);
152
153    // cert at index 0 is the user certificate
154    IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(user1_sign);
155
156    // create a new SignerInfo
157    SignerInfo signer_info = new SignerInfo(issuer, (AlgorithmID)AlgorithmID.sha256.clone(), user1_sign_pk);
158    
159    // create some signed attributes
160    // the message digest attribute is automatically added
161    Attribute[] attributes = new Attribute[2];
162    try {
163      // content type is data
164      CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
165      attributes[0] = new Attribute(contentType);
166      // signing time is now
167      SigningTime signingTime = new SigningTime();
168      attributes[1] = new Attribute(signingTime);
169    } catch (Exception ex) {
170      throw new CMSException("Error creating attribute: " + ex.toString());   
171    }
172    
173    // set the attributes
174    signer_info.setSignedAttributes(attributes);
175    // finish the creation of SignerInfo by calling method addSigner
176    try {
177      signed_data.addSignerInfo(signer_info);
178
179    } catch (NoSuchAlgorithmException ex) {
180      throw new CMSException("No implementation for signature algorithm: "+ex.getMessage());
181    }
182    // ensure block encoding
183    signed_data.setBlockSize(2048);
184
185    // write the data through SignedData to any out-of-band place
186    if (mode == SignedDataStream.EXPLICIT) {
187      InputStream data_is = signed_data.getInputStream();
188      byte[] buf = new byte[1024];
189      int r;
190      while ((r = data_is.read(buf)) > 0) {
191        ;   // skip data
192      }  
193    }
194
195    // return the SignedData as encoded byte array with block size 2048
196    ByteArrayOutputStream os = new ByteArrayOutputStream();
197
198    signed_data.writeTo(os);
199    return os.toByteArray();
200  }
201
202  /**
203   * Parses a CMS <code>SignedData</code> object and verifies the signatures
204   * for all participated signers. 
205   *
206   * @param signedData the SignedData, as BER encoded byte array
207   * @param message the the message which was transmitted out-of-band (explicit signed)
208   * @param writeAgain whether to use a SDSEncodeListener to add a SignerInfo
209   *        and encode the SignedData again
210   *
211   * @return the inherent message as byte array, or the BER encoded SignedData if
212   *         it shall be encoded again
213   * @throws CMSException if any signature does not verify
214   * @throws IOException if an I/O error occurs
215   */
216  public byte[] getSignedDataStream(byte[] signedData, byte[] message, boolean writeAgain) 
217    throws CMSException, IOException, NoSuchAlgorithmException {
218
219    // we are testing the stream interface
220    ByteArrayInputStream is = new ByteArrayInputStream(signedData);
221    
222    // the ByteArrayOutputStream to which to write the content
223    ByteArrayOutputStream os = new ByteArrayOutputStream();
224        
225    SignedDataStream signed_data = new SignedDataStream(is);
226    
227    if (signed_data.getMode() == SignedDataStream.EXPLICIT) {
228      // in explicit mode explicitly supply the content for hash computation  
229      signed_data.setInputStream(new ByteArrayInputStream(message));
230    }
231    
232    if (writeAgain) {
233      // we want to write the SignedData again   
234      // create a new SignerInfo
235      SignerInfo signer_info = new SignerInfo(new IssuerAndSerialNumber(user2_sign),
236                                              (AlgorithmID)AlgorithmID.sha256.clone(),
237                                              user2_sign_pk);
238     
239      // create some signed attributes
240      // the message digest attribute is automatically added
241      Attribute[] attributes = new Attribute[2];
242      try {
243        // content type is data
244        CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
245        attributes[0] = new Attribute(contentType);
246        // signing time is now
247        SigningTime signingTime = new SigningTime();
248        attributes[1] = new Attribute(signingTime);
249      } catch (Exception ex) {
250        throw new CMSException("Error creating attribute: " + ex.toString());   
251      }
252      // set the attributes
253      signer_info.setSignedAttributes(attributes);
254      
255      // add the SignerInfo via SDSEncodeListener which also verifies the sigantures
256      DefaultSDSEncodeListener dl = new DefaultSDSEncodeListener();
257      dl.setDigestAlgorithms(new AlgorithmID [] { (AlgorithmID)AlgorithmID.sha256.clone() });
258      dl.setSignerInfos(new SignerInfo[] { signer_info });
259      dl.setCertificates(new Certificate[] { user2_sign });
260      
261      
262      if (message == null) {
263        // in implicit mode copy data to os
264        dl.setOutputStream(os);
265        signed_data.setSDSEncodeListener(dl);     
266
267      } else {
268        signed_data.setSDSEncodeListener(dl);
269        // get an InputStream for reading the signed content
270        InputStream data = signed_data.getInputStream();
271        Util.copyStream(data, os, null);
272      } 
273       
274      // ensure block encoding
275      signed_data.setBlockSize(2048);
276      // return the SignedData as encoded byte array with block size 2048
277      ByteArrayOutputStream baos = new ByteArrayOutputStream();
278      signed_data.writeTo(baos);
279      
280      // we read the content
281      byte[] content = os.toByteArray();
282      System.out.println("Content: " + new String(content));
283      
284      return baos.toByteArray();
285
286    } else {  
287
288      // get an InputStream for reading the signed content
289      InputStream data = signed_data.getInputStream();
290      os = new ByteArrayOutputStream();
291      Util.copyStream(data, os, null);
292
293      System.out.println("SignedData contains the following signer information:");
294      SignerInfo[] signer_infos = signed_data.getSignerInfos();
295
296      int numberOfSignerInfos = signer_infos.length;
297      if (numberOfSignerInfos == 0) {
298        String warning = "Warning: Unsigned message (no SignerInfo included)!";  
299        System.err.println(warning);
300        throw new CMSException(warning);
301      } else {
302        for (int i = 0; i < numberOfSignerInfos; i++) {
303          try {
304            // verify the signed data using the SignerInfo at index i
305            X509Certificate signer_cert = signed_data.verify(i);
306            // if the signature is OK the certificate of the signer is returned
307            System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN());
308            // get signed attributes
309            SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime);
310            if (signingTime != null) {
311              System.out.println("This message has been signed at " + signingTime.get());
312            } 
313            CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType);
314            if (contentType != null) {
315              System.out.println("The content has CMS content type " + contentType.get().getName());
316            }
317  
318          } catch (SignatureException ex) {
319            // if the signature is not OK a SignatureException is thrown
320            System.out.println("Signature ERROR from signer: "+signed_data.getCertificate((signer_infos[i].getSignerIdentifier())).getSubjectDN());
321            throw new CMSException(ex.toString());
322          } 
323        }
324        // now check alternative signature verification
325        System.out.println("Now check the signature assuming that no certs have been included:");
326        try {
327          SignerInfo signer_info = signed_data.verify(user1_sign);
328          // if the signature is OK the certificate of the signer is returned
329          System.out.println("Signature OK from signer: "+signed_data.getCertificate(signer_info.getSignerIdentifier()).getSubjectDN());
330  
331        } catch (SignatureException ex) {
332          // if the signature is not OK a SignatureException is thrown
333          System.out.println("Signature ERROR from signer: "+user1_sign.getSubjectDN());
334          throw new CMSException(ex.toString());
335        }
336  
337        try {
338          SignerInfo signer_info = signed_data.verify(user2_sign);
339          // if the signature is OK the certificate of the signer is returned
340          System.out.println("Signature OK from signer: "+signed_data.getCertificate(signer_info.getSignerIdentifier()).getSubjectDN());
341  
342        } catch (SignatureException ex) {
343          // if the signature is not OK a SignatureException is thrown
344          System.out.println("Signature ERROR from signer: "+user2_sign.getSubjectDN());
345          throw new CMSException(ex.toString());
346        }
347        // in practice we also would validate the signer certificate(s)  
348      }
349      return os.toByteArray();
350    }
351    
352  }
353
354  /** 
355   * Starts the test.
356   */
357  public void start() {
358
359    try {
360        
361      byte[] data;
362      byte[] received_message = null;  
363      
364      
365      //
366      // test CMS Implicit SignedDataStream
367      //
368      System.out.println("\nImplicit SignedDataStream demo [create]:\n");
369      data = createSignedDataStream(message, SignedDataStream.IMPLICIT);
370      // parse and encode again
371      System.out.println("\nImplicit SignedDataStream demo [write again]:\n");
372      data = getSignedDataStream(data, null, true);
373      // parse
374      System.out.println("\nImplicit SignedDataStream demo [parse]:\n");
375      received_message = getSignedDataStream(data, null, false);
376      System.out.print("\nSigned content: ");
377      System.out.println(new String(received_message));
378
379      //
380      // test CMS Explicit SignedDataStream
381      //
382      System.out.println("\nExplicit SignedDataStream demo [create]:\n");
383      data = createSignedDataStream(message, SignedDataStream.EXPLICIT);
384      // parse and encode again
385      System.out.println("\nExplicit SignedDataStream demo [write again]:\n");
386      data = getSignedDataStream(data, message, true);
387      
388      System.out.println("\nExplicit SignedDataStream demo [parse]:\n");
389      received_message = getSignedDataStream(data, message, false);
390      System.out.print("\nSigned content: ");
391      System.out.println(new String(received_message));
392
393        } catch (Exception ex) {
394          ex.printStackTrace();
395          throw new RuntimeException(ex.toString());
396        }
397        }
398
399    
400  /**
401   * The main method.
402   * 
403   * @throws IOException 
404   *            if an I/O error occurs when reading required keys
405   *            and certificates from files
406   */
407  public static void main(String argv[]) throws IOException {
408    try {
409      DemoUtil.initDemos();
410      (new SignedDataStreamDemoWithAdditionalSignerInfo()).start();
411    } catch (Exception ex) {    
412      ex.printStackTrace();
413    }
414    System.out.println("\nReady!");
415    DemoUtil.waitKey();
416  }
417}