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/SignedDataOutputStreamDemo.java 17    12.02.25 17:58 Dbratko $
029// $Revision: 17 $
030//
031
032package demo.cms.signedData;
033
034import iaik.asn1.ObjectID;
035import iaik.asn1.structures.AlgorithmID;
036import iaik.asn1.structures.Attribute;
037import iaik.asn1.structures.PolicyInformation;
038import iaik.asn1.structures.PolicyQualifierInfo;
039import iaik.cms.CMSException;
040import iaik.cms.ContentInfoOutputStream;
041import iaik.cms.IssuerAndSerialNumber;
042import iaik.cms.SignedDataOutputStream;
043import iaik.cms.SignedDataStream;
044import iaik.cms.SignerInfo;
045import iaik.cms.SubjectKeyID;
046import iaik.cms.attributes.CMSContentType;
047import iaik.cms.attributes.SigningTime;
048import iaik.smime.ess.SigningCertificate;
049import iaik.smime.ess.SigningCertificateV2;
050import iaik.utils.Util;
051import iaik.x509.X509Certificate;
052import iaik.x509.X509ExtensionException;
053import iaik.x509.attr.AttributeCertificate;
054
055import java.io.ByteArrayInputStream;
056import java.io.ByteArrayOutputStream;
057import java.io.IOException;
058import java.io.InputStream;
059import java.security.NoSuchAlgorithmException;
060import java.security.PrivateKey;
061import java.security.SignatureException;
062import java.security.cert.Certificate;
063
064import demo.DemoUtil;
065import demo.keystore.CMSKeyStore;
066
067
068/**
069 * Demonstrates the usage of class {@link iaik.cms.SignedDataOutputStream} and
070 * {@link iaik.cms.SignedDataOutputStream} for signing some data using the CMS type
071 * SignedData.
072 */
073public class SignedDataOutputStreamDemo {
074
075  // certificate of user 1
076  X509Certificate user1;
077  // private key of user 1
078  PrivateKey user1_pk;
079  // certificate of user 2
080  X509Certificate user2;
081  // private key of user 2
082  PrivateKey user2_pk;
083
084  // a certificate chain containing the user certs + CA
085  Certificate[] certificates;
086  Certificate[] certs;
087  Certificate[] user1Certs;
088  
089  // just for attribute certificate testing
090  PrivateKey issuer1_pk;
091
092  /**
093   * Setups the demo certificate chains.
094   * 
095   * Keys and certificate are retrieved from the demo KeyStore.
096   * 
097   * @throws IOException if an file read error occurs
098   */
099  public SignedDataOutputStreamDemo() throws IOException {
100    
101    System.out.println();
102    System.out.println("**********************************************************************************");
103    System.out.println("*                         SignedDataOutputStream demo                            *");
104    System.out.println("*       (shows the usage of the CMS SignedDataOutputStream implementation)       *");
105    System.out.println("**********************************************************************************");
106    System.out.println();
107    
108    // add all certificates to the list
109    user1Certs = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
110    user1 = (X509Certificate)user1Certs[0];
111    user1_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1);
112    user2 = CMSKeyStore.getCertificateChain(CMSKeyStore.DSA, CMSKeyStore.SZ_1024_SIGN)[0];
113    user2_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.DSA, CMSKeyStore.SZ_1024_SIGN);
114    
115    certs = user1Certs;
116   
117    certificates = new Certificate[certs.length+1];
118    System.arraycopy(certs, 0, certificates, 0, certs.length);
119    certificates[certs.length] = user2;
120  }
121  
122  /**
123   * Creates and encodes a CMS <code>SignedData</code> object.
124   * <p>
125   *
126   * @param message the message to be signed, as byte representation
127   * @param mode the transmission mode, either IMPLICIT or EXPLICIT
128   * @return the BER encoding of the <code>SignedData</code> object just created, wrapped into a ContentInfo
129   * @throws CMSException if the <code>SignedData</code> object cannot
130   *                          be created
131   * @throws IOException if some stream I/O error occurs
132   */
133  public byte[] createSignedDataStream(byte[] message, int mode) throws CMSException, IOException  {
134    
135    System.out.print("Create a new message signed by user 1 :");
136    
137    // a stream from which to read the data
138    ByteArrayInputStream is = new ByteArrayInputStream(message);
139    
140    // the stream to which to write the SignedData
141    ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
142    
143    //  wrap SignedData into a ContentInfo 
144    ContentInfoOutputStream contentInfoStream = 
145      new ContentInfoOutputStream(ObjectID.cms_signedData, resultStream);
146    SignedDataOutputStream signedData = new SignedDataOutputStream(contentInfoStream, mode);
147    
148    // add the certificates
149    signedData.addCertificates(certificates);
150    
151    // cert at index 0 is the user certificate
152    IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(user1);
153
154    // create a new SignerInfo
155    SignerInfo signerInfo = new SignerInfo(issuer, (AlgorithmID)AlgorithmID.sha256.clone(), user1_pk);
156    // create some authenticated attributes
157    // the message digest attribute is automatically added
158    Attribute[] attributes = new Attribute[3];
159    try {
160      // content type is data
161      CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
162      attributes[0] = new Attribute(contentType);
163      // signing time is now
164      SigningTime signingTime = new SigningTime();
165      attributes[1] = new Attribute(signingTime);
166      // signing certificate
167      SigningCertificateV2 signingCertificate = new SigningCertificateV2(certs, true);
168      String explicitText = "This certificate only may be used for test purposes";
169      PolicyQualifierInfo policyQualifier = new PolicyQualifierInfo(null, null, explicitText);
170      PolicyInformation[] policyInformations = 
171        { new PolicyInformation(new ObjectID("1.3.6.1.4.1.2706.17.0.11.1.1"),
172                              new PolicyQualifierInfo[] { policyQualifier }) };
173      signingCertificate.setPolicies(policyInformations);                        
174      System.out.println("Include signingCertificate attribute:");
175      System.out.println(signingCertificate);
176      attributes[2] = new Attribute(signingCertificate);
177    } catch (Exception ex) {
178      throw new CMSException("Error creating attribute: " + ex.toString());   
179    }    
180    // set the attributes
181    signerInfo.setSignedAttributes(attributes);
182    // finish the creation of SignerInfo by calling method addSigner
183    try {
184      signedData.addSignerInfo(signerInfo);
185      // another SignerInfo without signed attributes and SHA-256 as hash algorithm
186      signerInfo = new SignerInfo(new SubjectKeyID(user2), 
187                                  (AlgorithmID)AlgorithmID.sha1.clone(), 
188                                  (AlgorithmID)AlgorithmID.dsaWithSHA.clone(),
189                                  user2_pk);
190      
191      // the message digest itself is protected
192      signedData.addSignerInfo(signerInfo);
193
194    } catch (NoSuchAlgorithmException ex) {
195      throw new CMSException(ex.toString());
196    } catch (X509ExtensionException ex) {
197      throw new CMSException("Cannot create SubjectKeyID for user2 : " + ex.getMessage());
198    }
199    
200    int blockSize = 4; // in real world we would use a block size like 2048
201    //  write in the data to be signed
202    byte[] buffer = new byte[blockSize];
203    int bytesRead;
204    while ((bytesRead = is.read(buffer)) != -1) {
205      signedData.write(buffer, 0, bytesRead);
206    }
207    
208    // closing the stream add the signer infos and closes the underlying stream
209    signedData.close();
210    return resultStream.toByteArray();
211  }
212  
213
214  /**
215   * Parses a CMS <code>SignedData</code> object and verifies the signatures
216   * for all participated signers.
217   *
218   * @param signedData <code>SignedData</code> object (wrapped into a ContentInfo) as BER encoded byte array
219   * @param message the the message which was transmitted out-of-band (explicit signed)
220   *
221   * @return the inherent message as byte array
222   * @throws CMSException if any signature does not verify
223   * @throws IOException if some stream I/O error occurs
224   */
225  public byte[] getSignedDataStream(byte[] signedData, byte[] message) throws CMSException, IOException {
226
227    // we are testing the stream interface
228    ByteArrayInputStream is = new ByteArrayInputStream(signedData);
229    // create the SignedData object
230    SignedDataStream signed_data = new SignedDataStream(is);
231    
232    if (signed_data.getMode() == SignedDataStream.EXPLICIT) {
233      // in explicit mode explicitly supply the content for hash computation  
234      signed_data.setInputStream(new ByteArrayInputStream(message));
235    }
236
237    // get an InputStream for reading the signed content
238    InputStream data = signed_data.getInputStream();
239    ByteArrayOutputStream os = new ByteArrayOutputStream();
240    Util.copyStream(data, os, null);
241    
242    System.out.println("SignedData contains the following signer information:");
243    SignerInfo[] signer_infos = signed_data.getSignerInfos();
244    
245    int numberOfSignerInfos = signer_infos.length;
246    if (numberOfSignerInfos == 0) {
247      String warning = "Warning: Unsigned message (no SignerInfo included)!";  
248      System.err.println(warning);
249      throw new CMSException(warning);
250    } else {
251      for (int i = 0; i < numberOfSignerInfos; i++) {
252        
253        try {
254          // verify the signed data using the SignerInfo at index i
255          X509Certificate signer_cert = signed_data.verify(i);
256          // if the signature is OK the certificate of the signer is returned
257          System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN());
258          SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime);
259          if (signingTime != null) {
260            System.out.println("This message has been signed at " + signingTime.get());
261          } 
262          CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType);
263          if (contentType != null) {
264            System.out.println("The content has CMS content type " + contentType.get().getName());
265          }
266          // check SigningCertificate attribute
267          try {
268            SigningCertificateV2 signingCertificate = signer_infos[i].getSigningCertificateV2Attribute();
269            if (signingCertificate != null) {
270              checkSigningCertificate(signingCertificate, signer_cert, signed_data, i);  
271            }  
272          } catch (CMSException ex) {
273            throw new CMSException("Error parsing SigningCertificate attribute: " + ex.getMessage());   
274          }    
275         
276        } catch (SignatureException ex) {
277          // if the signature is not OK a SignatureException is thrown
278          System.out.println("Signature ERROR from signer: "+signed_data.getCertificate(signer_infos[i].getSignerIdentifier()).getSubjectDN());
279          throw new CMSException(ex.toString());
280        }  
281      }  
282    
283      // now check alternative signature verification
284      System.out.println("Now check the signature assuming that no certs have been included:");
285      try {
286         SignerInfo signer_info = signed_data.verify(user1);
287          // if the signature is OK the certificate of the signer is returned
288          System.out.println("Signature OK from signer: "+user1.getSubjectDN());
289          
290      } catch (SignatureException ex) {
291          // if the signature is not OK a SignatureException is thrown
292          System.out.println("Signature ERROR from signer: "+user1.getSubjectDN());
293          throw new CMSException(ex.toString());
294      }
295    
296      System.out.println("Included attribute certificates:");
297      AttributeCertificate[] attributeCerts = signed_data.getAttributeCertificates();
298      if (attributeCerts == null) {
299        System.out.println("No attribute certificates");   
300      } else {   
301        for (int i = 0; i < attributeCerts.length; i++) {
302          System.out.println(attributeCerts[i].getHolder());   
303        } 
304      } 
305     
306      try {
307        SignerInfo signer_info = signed_data.verify(user2);
308        // if the signature is OK the certificate of the signer is returned
309        System.out.println("Signature OK from signer: "+signed_data.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: "+user2.getSubjectDN());
314        throw new CMSException(ex.toString());
315      }
316      // in practice we also would validate the signer certificate(s)  
317    }
318        
319    return os.toByteArray();
320  }
321  
322  
323  /**
324   * Checks the SigningCertificate attribute.
325   *
326   * @param signingCertificate the SigningCertificate attribute
327   * @param signerCert the certificate of the signer
328   * @param signedData the SignedData containing the SignerInfo with the SigningCertificate
329   *                   attribute to be checked
330   * @param signerInfoIndex the index of the SignerInfo with the SigningCertificate
331   *                   attribute to be checked
332   *
333   * @throws CMSException if the SigningCertificate check fails
334   */
335  private void checkSigningCertificate(SigningCertificate signingCertificate,
336                                       X509Certificate signerCert,
337                                       SignedDataStream signedData,
338                                       int signerInfoIndex) throws CMSException {
339      if (signedData.getSignerInfos()[signerInfoIndex].isSignerCertificate(signerCert) == false) {
340        throw new CMSException("Cert ERROR!!! The certificate used for signing is not the one " +
341                               "identified by the SignerCertificate attribute!");
342      } else {
343        System.out.println("SigningCertificate attribute: Signer cert ok!");   
344      } 
345      if (signingCertificate != null) {
346        // get the authorization certs for this signerInfo
347        Certificate[] authCerts = 
348          signingCertificate.getAuthorizedCertificates(signedData.getCertificates());
349        if (authCerts != null) {
350          System.out.println("SignedData contains the following authorization certs for SignerInfo No " + (signerInfoIndex+1) +":");   
351          for (int j = 0; j < authCerts.length; j++) {
352            if (authCerts[j].getType().equalsIgnoreCase("X.509")) {
353              System.out.println("X.509 public key cert: " + ((X509Certificate)authCerts[j]).getSubjectDN());
354            } else {
355              System.out.println("X.509 attribute cert: " + ((AttributeCertificate)authCerts[j]).getHolder());  
356            }     
357          }  
358        } 
359        if (signingCertificate.countPolicies() > 0) {
360          // get the certs with PolicyInformations according to the SigningCertificate attribute:
361          Certificate[] policyCerts = 
362            signingCertificate.getPolicyInformationCerts(signedData.getCertificates());
363          if (policyCerts != null) {
364            System.out.println("SignedData contains the following certs corresponding to policy informations of SignerInfo No "
365                               + (signerInfoIndex+1) +":");   
366            for (int j = 0; j < policyCerts.length; j++) {
367              if (policyCerts[j].getType().equalsIgnoreCase("X.509")) {
368                System.out.println("X.509 public key cert: " + ((X509Certificate)policyCerts[j]).getSubjectDN());
369              } else {
370                System.out.println("X.509 attribute cert: " + ((AttributeCertificate)policyCerts[j]).getHolder());  
371              }     
372            }  
373          }
374        }  
375      }  
376    
377  }      
378  
379  
380  /**
381   * Demonstrates the CMS SignedDataOutputStream implementation.
382   */
383  public void start() {
384     // the test message
385    String m = "This is the test message.";
386    System.out.println("Test message: \""+m+"\"");
387    System.out.println();
388    byte[] message = m.getBytes();
389   
390    try {
391      byte[] encoding;
392      byte[] received_message = null;
393      System.out.println("Stream implementation demos");
394      System.out.println("===========================");
395      //
396      // test CMS Implicit SignedDataOutputStream
397      //
398      System.out.println("\nImplicit SignedDataOutputStream demo [create]:\n");
399      encoding = createSignedDataStream(message, SignedDataOutputStream.IMPLICIT);
400      // transmit data
401      System.out.println("\nImplicit SignedDataStream demo [parse]:\n");
402      received_message = getSignedDataStream(encoding, null);
403      System.out.print("\nSigned content: ");
404      System.out.println(new String(received_message));
405      
406      //
407      // test CMS Explicit SignedDataOutputStream
408      //
409      System.out.println("\nExplicit SignedDataOutputStream demo [create]:\n");
410      encoding = createSignedDataStream(message, SignedDataOutputStream.EXPLICIT);
411      // transmit data
412      System.out.println("\nExplicit SignedDataStream demo [parse]:\n");
413      received_message = getSignedDataStream(encoding, message);
414      System.out.print("\nSigned content: ");
415      System.out.println(new String(received_message));
416      
417        } catch (Exception ex) {
418          ex.printStackTrace();
419          throw new RuntimeException(ex.toString());
420        }
421  }
422  
423  /**
424   * The main method.
425   * 
426   * @throws IOException 
427   *            if an I/O error occurs when reading required keys
428   *            and certificates from files
429   */
430  public static void main(String argv[]) throws Exception {
431
432        DemoUtil.initDemos();
433    (new SignedDataOutputStreamDemo()).start();
434    System.out.println("\nReady!");
435    DemoUtil.waitKey();
436  }
437}