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