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