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/SignedDataDemoWithAdditionalSignerInfo.java 20    12.02.25 17:58 Dbratko $
029// $Revision: 20 $
030//
031
032package demo.cms.signedData;
033
034import iaik.asn1.ObjectID;
035import iaik.asn1.structures.AlgorithmID;
036import iaik.asn1.structures.Attribute;
037import iaik.cms.IssuerAndSerialNumber;
038import iaik.cms.SignedData;
039import iaik.cms.SignerInfo;
040import iaik.cms.attributes.CMSContentType;
041import iaik.cms.attributes.SigningTime;
042import iaik.x509.X509Certificate;
043
044import java.io.ByteArrayInputStream;
045import java.io.ByteArrayOutputStream;
046import java.io.IOException;
047import java.io.InputStream;
048import java.io.OutputStream;
049import java.security.PrivateKey;
050import java.security.cert.Certificate;
051
052import demo.DemoUtil;
053import demo.keystore.CMSKeyStore;
054
055/**
056 * This class shows how to use the non-stream based {@link iaik.cms.SignedData SignedData} 
057 * implementation to add a new SignerInfo to an existing, parsed SignedData object.
058 */
059public class SignedDataDemoWithAdditionalSignerInfo {
060
061  String testMessage;
062
063  public SignedDataDemoWithAdditionalSignerInfo() {
064    
065    System.out.println();
066    System.out.println("**********************************************************************************");
067    System.out.println("*                      SignedDataDemoWithAdditionalSignerInfo                    *");
068    System.out.println("*    (shows how to add a new signer to an already existing SignedData object)    *");
069    System.out.println("**********************************************************************************");
070    System.out.println();
071    
072    testMessage = "This is a test of the CMS implementation!";
073  }
074
075  /**
076   * Verifies a given SignedData object which is read from the given input stream.
077   *
078   * @param is an input stream holding the SignedData object
079   *
080   * @throws Exception if an error occurs when verifying the SignedData
081   */
082  private void verify(InputStream is) throws Exception {
083
084    System.out.println("\nVerify the SignedData...");
085    // read the SignedData from the given input stream
086    SignedData signedData = new SignedData(is);
087    // get the content
088    byte[] content = signedData.getContent();
089    // and show it
090    System.out.println("Content of SignedData: "+new String(content));
091
092    // print the certificates included
093    System.out.println("Certificates included:");
094    Certificate[] certs = signedData.getCertificates();
095    try {
096      for (int i = 0; i < certs.length; i++)
097        System.out.println(certs[i]);
098    } catch (Exception ex) {
099      ex.printStackTrace();
100    }
101                
102    // print the signers included
103    System.out.println("Signers included:");
104    SignerInfo[] signerInfos = signedData.getSignerInfos();
105
106    int numberOfSignerInfos = signerInfos.length;
107    if (numberOfSignerInfos == 0) {
108      String warning = "Warning: Unsigned message (no SignerInfo included)!";  
109      System.err.println(warning);
110      throw new Exception(warning);
111    } else {
112      for (int i = 0; i < numberOfSignerInfos; i++) {
113        X509Certificate cert = signedData.verify(i);
114        System.out.println("Signer: " + cert.getSubjectDN());
115        System.out.println(signerInfos[i].toString(true));
116      }
117      // in practice we also would validate the signer certificate(s)  
118    }
119  }
120    
121  /**
122   * Creates a new SignedData object.
123   *
124   * @param os the output stream where the created object shall be written to
125   * @param message the content for the SignedData
126   * @param size specifies which private key/certificate to use from the KeyStore
127   *
128   * @throws Exception if an error occurs when creating the SignedData object
129   */
130  private void create(OutputStream os, String message, int size) throws Exception {
131
132    System.out.println("\nCreate a new SignedData with content: "+message);
133    // get the certificate chain from teh KeyStore
134    X509Certificate[] certificates = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, size);
135    // create the SignedData
136    SignedData signedData = new SignedData(message.getBytes(), iaik.cms.SignedData.IMPLICIT);
137    // set teh certificates
138    signedData.setCertificates(certificates);
139    // add a signer
140    addSigner(signedData, size);
141    // and write it the given output stream
142    signedData.writeTo(os);
143    os.close();
144  }
145
146  /**
147   * Adds a new Signer to a given SignedData.
148   *
149   * @param is the input stream holding the existing SignedData object
150   * @param size specifies which private key/certificate to use from the KeyStore
151   */
152  private ByteArrayOutputStream add(InputStream is, int size) throws Exception {
153
154    System.out.println("Adding a signature to an existing SignedData...");
155    // read the existing SignedData from the given InputStream
156    SignedData signedData = new SignedData(is);
157    // print the content
158    byte[] content = signedData.getContent();
159    System.out.println("Existing content is: " + new String(content));
160    
161    // add another signer
162    addSigner(signedData, size);
163
164    // create a new output stream and save it
165    ByteArrayOutputStream os = new ByteArrayOutputStream();
166    signedData.writeTo(os);
167
168    is.close();
169    os.close();
170
171    // return the output stream which contains the new SignedData
172    return os;
173  }
174
175  /**
176   * Adds the new signer.
177   *
178   * @param signedData the SignedData where the new signer shall be added
179   * @param size specifies which private key/certificate to use from the KeyStore
180   */
181  private void addSigner(SignedData signedData, int size) throws Exception {
182        
183    // get new certificate and private key
184    X509Certificate cert = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, size)[0];
185    PrivateKey privateKey = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, size);
186        
187    // add to the existing list of certificates
188    Certificate[] certs = signedData.getCertificates();
189    Certificate[] newCerts = new Certificate [certs.length + 1];
190    System.arraycopy(certs, 0, newCerts, 0, certs.length);
191    newCerts[certs.length] = cert;
192    // set the new certificate list
193    signedData.setCertificates(newCerts);
194        
195    // create a new SignerInfo
196    SignerInfo signerInfo = new SignerInfo(new IssuerAndSerialNumber(cert), 
197                                           (AlgorithmID)AlgorithmID.sha256.clone(),
198                                           privateKey);
199    // define some attributes
200    Attribute[] attributes = { 
201      new Attribute(new CMSContentType(ObjectID.cms_data)),
202      new Attribute(new SigningTime())
203    };
204    // set the attributes
205    signerInfo.setSignedAttributes(attributes);
206    // and add the new signer
207    signedData.addSignerInfo(signerInfo);
208  }
209  
210  /**
211   * Starts the test.
212   */
213  public void start() {
214
215    try {
216      // output stream for storing the signed data
217      ByteArrayOutputStream os = new ByteArrayOutputStream();
218      // create a new SignedData
219      create(os, testMessage, CMSKeyStore.SZ_2048_SIGN_1);
220      // verify it      
221      verify(new ByteArrayInputStream(os.toByteArray()));
222      // add another signer
223      os = add(new ByteArrayInputStream(os.toByteArray()), CMSKeyStore.SZ_2048_SIGN_1);
224      // and verify it again
225      verify(new ByteArrayInputStream(os.toByteArray()));
226
227        } catch (Exception ex) {
228          ex.printStackTrace();
229          throw new RuntimeException(ex.toString());
230        }
231  }
232    
233  /**
234   * Main method.
235   * 
236   * @throws IOException 
237   *            if an I/O error occurs when reading required keys
238   *            and certificates from files
239   */
240  public static void main(String argv[]) throws IOException {
241
242    DemoUtil.initDemos();
243    (new SignedDataDemoWithAdditionalSignerInfo()).start();
244       
245    System.out.println("\nReady!");
246    DemoUtil.waitKey();
247  }
248}