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/ecc/ECDSASignedDataOutputStreamDemo.java 5     12.02.25 17:58 Dbratko $
059    // $Revision: 5 $
060    //
061    
062    package demo.cms.ecc;
063    
064    import java.io.ByteArrayInputStream;
065    import java.io.ByteArrayOutputStream;
066    import java.io.IOException;
067    import java.io.InputStream;
068    import java.security.NoSuchAlgorithmException;
069    import java.security.PrivateKey;
070    import java.security.SignatureException;
071    
072    import demo.DemoUtil;
073    import demo.cms.ecc.keystore.CMSEccKeyStore;
074    import iaik.asn1.ObjectID;
075    import iaik.asn1.structures.AlgorithmID;
076    import iaik.asn1.structures.Attribute;
077    import iaik.cms.CMSAlgorithmID;
078    import iaik.cms.CMSException;
079    import iaik.cms.ContentInfoOutputStream;
080    import iaik.cms.IssuerAndSerialNumber;
081    import iaik.cms.SignedDataOutputStream;
082    import iaik.cms.SignedDataStream;
083    import iaik.cms.SignerInfo;
084    import iaik.cms.attributes.CMSContentType;
085    import iaik.cms.attributes.SigningTime;
086    import iaik.utils.KeyAndCertificate;
087    import iaik.utils.Util;
088    import iaik.x509.X509Certificate;
089    
090    
091    /**
092     * This class demonstrates the IAIK-CMS SignedDataOutputStream implementation
093     * with the ECDSA (with SHA1, SHA224, SHA256, SHA384, SHA512, RIPEMD160) signature algorithm. 
094     * <p>
095     * Any keys/certificates required for this demo are read from a keystore
096     * file "cmsecc.keystore" located in your current working directory. If
097     * the keystore file does not exist you can create it by running the
098     * {@link demo.cms.ecc.keystore.SetupCMSEccKeyStore SetupCMSEccKeyStore}
099     * program. 
100     * <p>
101     * Additionally to <code>iaik_cms.jar</code> you also must have 
102     * <code>iaik_jce_(full).jar</code> (IAIK-JCE, <a href =
103     * "https://sic.tech/products/core-crypto-toolkits/jca-jce/" target="_blank">
104     * https://sic.tech/products/core-crypto-toolkits/jca-jce/</a>),
105     * and <code>iaik_eccelarate.jar</code> (IAIK-ECCelerate<sup><small>TM</small></sup>, <a href =
106     * "https://sic.tech/products/core-crypto-toolkits/eccelerate/" target="_blank">
107     * https://sic.tech/products/core-crypto-toolkits/eccelerate/</a>)
108     * in your classpath.
109     */
110    public class ECDSASignedDataOutputStreamDemo {
111    
112      /**
113       * Default Constructor.
114       */
115      public ECDSASignedDataOutputStreamDemo() throws Exception {
116        System.out.println();
117        System.out.println("**********************************************************************************");
118        System.out.println("*                           ECDSASignedData demo                                 *");
119        System.out.println("*      (shows how to use the SignedData(Stream) implementation with ECDSA)       *");
120        System.out.println("**********************************************************************************");
121        System.out.println();
122        
123      }
124      
125      /**
126       * Creates an ECDSA signed CMS <code>SignedDataStream</code> object and wraps it by a
127       * CMS <code>ContentInfoStream</code>.
128       *
129       * @param message the message to be signed, as byte representation
130       * @param mode the transmission mode, either IMPLICIT or EXPLICIT
131       * @param hashAlgorithm the hash algorithm to be used
132       * @param signatureAlgorithm the signature algorithm to be used
133       * @param signerKey the private key of the signer
134       * @param certificates the certificate chain of the signer
135       * 
136       * @return the DER encoding of the <code>ContentInfo</code> object just created
137       * 
138       * @throws CMSException if the <code>SignedData</code>, <code>ContentInfo</code>
139       *            object cannot be created
140       * @throws IOException if an I/O related error occurs
141       */
142      public byte[] createSignedDataStream(byte[] message, 
143                                           int mode,
144                                           AlgorithmID hashAlgorithm,
145                                           AlgorithmID signatureAlgorithm,
146                                           PrivateKey signerKey,
147                                           X509Certificate[] certificates) 
148        throws CMSException, IOException  {
149        
150        System.out.print("Create a new message signed with " + signatureAlgorithm.getName());
151       
152        // we are testing the stream interface
153        ByteArrayInputStream is = new ByteArrayInputStream(message);
154        
155        // the stream to which to write the SignedData
156        ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
157        
158        //  wrap SignedData into a ContentInfo 
159        ContentInfoOutputStream contentInfoStream = 
160          new ContentInfoOutputStream(ObjectID.cms_signedData, resultStream);
161        SignedDataOutputStream signedData = new SignedDataOutputStream(contentInfoStream, mode);
162    
163        // add the certificates
164        signedData.addCertificates(certificates);
165    
166        // cert at index 0 is the user certificate
167        IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(certificates[0]);
168    
169        // create a new SignerInfo
170        AlgorithmID ecdsaSig = (AlgorithmID)signatureAlgorithm.clone();
171        // CMS-ECDSA requires to encode the parameter field as NULL (see RFC 3278)
172        ecdsaSig.encodeAbsentParametersAsNull(true);
173        SignerInfo signer_info = new SignerInfo(issuer, (AlgorithmID)hashAlgorithm.clone(), ecdsaSig, signerKey);
174        
175        try {
176          // create some signed attributes
177          // the message digest attribute is automatically added
178          Attribute[] attributes = new Attribute[2];
179          // content type is data
180          CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
181          attributes[0] = new Attribute(contentType);
182          // signing time is now
183          SigningTime signingTime = new SigningTime();
184          attributes[1] = new Attribute(signingTime);
185      
186          // set the attributes
187          signer_info.setSignedAttributes(attributes);
188        } catch (Exception ex) {
189          throw new CMSException("Error adding attributes: " + ex.toString());
190        }
191        
192        // finish the creation of SignerInfo by calling method addSigner
193        try {
194          signedData.addSignerInfo(signer_info);
195        } catch (NoSuchAlgorithmException ex) {
196          throw new CMSException("No implementation for signature algorithm: "+ex.getMessage());
197        }
198    
199        int blockSize = 4; // in real world we would use a block size like 2048
200        //  write in the data to be signed
201        byte[] buffer = new byte[blockSize];
202        int bytesRead;
203        while ((bytesRead = is.read(buffer)) != -1) {
204          signedData.write(buffer, 0, bytesRead);
205        }
206        
207        // closing the stream add the signer infos and closes the underlying stream
208        signedData.close();
209        return resultStream.toByteArray();
210      }
211      
212      
213      /**
214       * Parses a CMS <code>ContentInfo</code> object holding a <code>SignedData</code> 
215       * object and verifies the signature.
216       *
217       * @param signedData the <code>ContentInfo</code> holding the <code>SignedData</code> 
218       *                   object as BER encoded byte array
219       * @param message the the message which was transmitted out-of-band (explicit signed)
220       * @param certificates the certificate of the signer (used for alternative signature verification)
221       * 
222       * @return the inherent message as byte array
223       * 
224       * @throws CMSException if any signature does not verify
225       * @throws IOException if an I/O related error occurs
226       */
227      public byte[] getSignedDataStream(byte[] signedData, byte[] message, X509Certificate[] certificates) 
228        throws CMSException, IOException {
229    
230        // we are testing the stream interface
231        ByteArrayInputStream is = new ByteArrayInputStream(signedData);
232    
233        SignedDataStream signed_data = new SignedDataStream(is);
234    
235        if (signed_data.getMode() == SignedDataStream.EXPLICIT) {
236          // in explicit mode explicitly supply the content for hash computation
237          signed_data.setInputStream(new ByteArrayInputStream(message));
238        }
239    
240        // get an InputStream for reading the signed content and update hash computation
241        InputStream data = signed_data.getInputStream();
242        ByteArrayOutputStream os = new ByteArrayOutputStream();
243        Util.copyStream(data, os, null);
244    
245        System.out.println("SignedData contains the following signer information:");
246        SignerInfo[] signer_infos = signed_data.getSignerInfos();
247        
248        int numberOfSignerInfos = signer_infos.length;
249        if (numberOfSignerInfos == 0) {
250          String warning = "Warning: Unsigned message (no SignerInfo included)!";  
251          System.err.println(warning);
252          throw new CMSException(warning);
253        } else {
254          for (int i = 0; i < numberOfSignerInfos; i++) {
255            try {
256              // verify the signed data using the SignerInfo at index i
257              X509Certificate signer_cert = signed_data.verify(i);
258              // if the signature is OK the certificate of the signer is returned
259              System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN());
260              // check for some included attributes
261              SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime);
262              if (signingTime != null) {
263                System.out.println("This message has been signed at " + signingTime.get());
264              } 
265              CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType);
266              if (contentType != null) {
267                System.out.println("The content has CMS content type " + contentType.get().getName());
268              }  
269            } catch (SignatureException ex) {
270              // if the signature is not OK a SignatureException is thrown
271              System.out.println("Signature ERROR from signer: "+signed_data.getCertificate(signer_infos[i].getSignerIdentifier()).getSubjectDN());
272              throw new CMSException(ex.toString());
273            }  
274          }  
275          // now check alternative signature verification
276          System.out.println("Now check the signature assuming that no certs have been included:");
277          try {
278            SignerInfo signer_info = signed_data.verify(certificates[0]);
279            // if the signature is OK the certificate of the signer is returned
280            System.out.println("Signature OK from signer: "+signed_data.getCertificate(signer_info.getSignerIdentifier()).getSubjectDN());
281          } catch (SignatureException ex) {
282            // if the signature is not OK a SignatureException is thrown
283            System.out.println("Signature ERROR from signer: "+certificates[0].getSubjectDN());
284            throw new CMSException(ex.toString());
285          }
286          // in practice we also would validate the signer certificate(s)  
287        }      
288        return os.toByteArray();
289      }
290      
291      
292     
293      /**
294       * Runs the signing - verifying demo.
295       * 
296       * @param message the message to be signed
297       * @param hashAlgorithm the hash algorithm to be used
298       * @param signatureAlgorithm the signature algorithm to be used
299       * @param signerKeyAndCert private key and certificate chain of the signer
300       */
301      public void runDemo(byte[] message, 
302                          AlgorithmID hashAlgorithm,
303                          AlgorithmID signatureAlgorithm,
304                          KeyAndCertificate signerKeyAndCert) 
305        throws Exception {
306        
307        PrivateKey signerKey = signerKeyAndCert.getPrivateKey();
308        X509Certificate[] signerCerts = signerKeyAndCert.getCertificateChain();
309        
310        byte[] encodedSignedData;
311        byte[] received_message = null;
312        
313        System.out.println("\nRun demos for " + hashAlgorithm.getName() + " / " + signatureAlgorithm.getName() + "\n");
314          
315        System.out.println("Stream implementation demos");
316        System.out.println("===========================");
317        //
318        // test CMS Implicit SignedDataStream
319        //
320        System.out.println("\nImplicit SignedDataStream demo [create]:\n");
321        encodedSignedData = createSignedDataStream(message, 
322                                                   SignedDataStream.IMPLICIT,
323                                                   hashAlgorithm,
324                                                   signatureAlgorithm,
325                                                   signerKey,
326                                                   signerCerts);
327        System.out.println();
328       
329        // transmit data
330        System.out.println("\nImplicit SignedDataStream demo [parse]:\n");
331        received_message = getSignedDataStream(encodedSignedData, null, signerCerts);
332        System.out.print("\nSigned content: ");
333        System.out.println(new String(received_message));
334          
335        //
336        // test CMS Explicit SignedDataStream
337        //
338        System.out.println("\nExplicit SignedDataStream demo [create]:\n");
339        encodedSignedData = createSignedDataStream(message,
340                                                   SignedDataStream.EXPLICIT,
341                                                   hashAlgorithm,
342                                                   signatureAlgorithm,
343                                                   signerKey,
344                                                   signerCerts);
345        // transmit data
346        System.out.println("\nExplicit SignedDataStream demo [parse]:\n");
347        received_message = getSignedDataStream(encodedSignedData, message, signerCerts);
348        System.out.print("\nSigned content: ");
349        System.out.println(new String(received_message));
350          
351        // the non-stream implementation
352        System.out.println("\nNon-stream implementation demos");
353        System.out.println("===============================");
354    
355       
356      }
357      
358      /**
359       * Tests the CMS SignedData implementation with the ECDSA signature
360       * algorithm and several hash algorithms.
361       */
362      public void start() throws Exception {
363        
364         // the test message
365        String m = "This is the test message.";
366        System.out.println("Test message: \""+m+"\"");
367        System.out.println();
368        byte[] message = m.getBytes();
369    
370        AlgorithmID[][] algorithms = new AlgorithmID[][] {
371                                       { CMSAlgorithmID.sha1, CMSAlgorithmID.ecdsa_With_SHA1 },
372                                       { CMSAlgorithmID.sha224, CMSAlgorithmID.ecdsa_With_SHA224 },
373                                       { CMSAlgorithmID.sha256, CMSAlgorithmID.ecdsa_With_SHA256 },
374                                       { CMSAlgorithmID.sha384, CMSAlgorithmID.ecdsa_With_SHA384 },
375                                       { CMSAlgorithmID.sha512, CMSAlgorithmID.ecdsa_With_SHA512 },
376                                       // ECDSA with RIPEMD-160 in plain format (BSI)
377                                       { CMSAlgorithmID.ripeMd160, CMSAlgorithmID.ecdsa_plain_With_RIPEMD160 },
378                                     };
379    
380        // get signer key and certs                                 
381        KeyAndCertificate[] keyAndCerts = {
382          // P-192  
383          new KeyAndCertificate(CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_192_SIGN),
384                                CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_192_SIGN)),
385          // P-224  
386          new KeyAndCertificate(CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_224_SIGN),
387                                CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_224_SIGN)),
388          // P-256  
389          new KeyAndCertificate(CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_256_SIGN),
390                                CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_256_SIGN)),
391          // P-384                      
392          new KeyAndCertificate(CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_384_SIGN),
393                                CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_384_SIGN)),
394          // P-521                            
395          new KeyAndCertificate(CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_521_SIGN),
396                                CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_521_SIGN)),                                    
397          // P-192 (for ECDSA with RIPEMD-160 in plain format (BSI)  
398          new KeyAndCertificate(CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_192_SIGN),
399                                CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_192_SIGN)),
400                                
401                                 
402        };
403                                     
404        final int HASH_ALG = 0;
405        final int SIGNATURE_ALG = 1;
406        for (int i = 0; i < algorithms.length; i++) {
407          runDemo(message, algorithms[i][HASH_ALG], algorithms[i][SIGNATURE_ALG], keyAndCerts[i]);
408        }
409       
410      }  
411      
412      /**
413       * Starts the demo.
414       * 
415       * @throws Exception 
416       *            if an error occurs 
417       */
418      public static void main(String argv[]) throws Exception {
419    
420        DemoUtil.initDemos();
421        ECCDemoUtil.installIaikEccProvider();   
422        (new ECDSASignedDataOutputStreamDemo()).start();
423        System.out.println("\nReady!");
424        System.in.read();
425      }
426        
427    }