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