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/EdDSASignedDataDemo.java 4     12.02.25 17:58 Dbratko $
029// $Revision: 4 $
030//
031
032package demo.cms.ecc;
033
034import iaik.asn1.ObjectID;
035import iaik.asn1.structures.AlgorithmID;
036import iaik.asn1.structures.Attribute;
037import iaik.cms.CMSAlgorithmID;
038import iaik.cms.CMSException;
039import iaik.cms.ContentInfo;
040import iaik.cms.ContentInfoStream;
041import iaik.cms.IssuerAndSerialNumber;
042import iaik.cms.SignedData;
043import iaik.cms.SignedDataStream;
044import iaik.cms.SignerInfo;
045import iaik.cms.attributes.CMSContentType;
046import iaik.cms.attributes.SigningTime;
047import iaik.utils.KeyAndCertificate;
048import iaik.utils.Util;
049import iaik.x509.X509Certificate;
050
051import java.io.ByteArrayInputStream;
052import java.io.ByteArrayOutputStream;
053import java.io.IOException;
054import java.io.InputStream;
055import java.security.NoSuchAlgorithmException;
056import java.security.PrivateKey;
057import java.security.SignatureException;
058
059import demo.DemoUtil;
060import demo.cms.ecc.keystore.CMSEccKeyStore;
061
062
063/**
064 * This class demonstrates the IAIK-CMS SignedData(Stream) implementation
065 * with the EdDSA (Ed25519, Ed448) signature algorithm. 
066 * <p>
067 * Any keys/certificates required for this demo are read from a keystore
068 * file "cmsecc.keystore" located in your current working directory. If
069 * the keystore file does not exist you can create it by running the
070 * {@link demo.cms.ecc.keystore.SetupCMSEccKeyStore SetupCMSEccKeyStore}
071 * program. 
072 * <p>
073 * Additionally to <code>iaik_cms.jar</code> you also must have 
074 * <code>iaik_jce_(full).jar</code> (IAIK-JCE, <a href =
075 * "https://sic.tech/products/core-crypto-toolkits/jca-jce/" target="_blank">
076 * https://sic.tech/products/core-crypto-toolkits/jca-jce/</a>),
077 * and <code>iaik_eccelarate.jar</code> (IAIK-ECCelerate<sup><small>TM</small></sup>, <a href =
078 * "https://sic.tech/products/core-crypto-toolkits/eccelerate/" target="_blank">
079 * https://sic.tech/products/core-crypto-toolkits/eccelerate/</a>)
080 * in your classpath.
081 */
082public class EdDSASignedDataDemo {
083
084  /**
085   * Default Constructor.
086   */
087  public EdDSASignedDataDemo() throws Exception {
088    System.out.println();
089    System.out.println("**********************************************************************************");
090    System.out.println("*                           EdDSASignedData demo                                 *");
091    System.out.println("*      (shows how to use the SignedData(Stream) implementation with EdDSA)       *");
092    System.out.println("**********************************************************************************");
093    System.out.println();
094    
095  }
096  
097  /**
098   * Creates an EdDSA signed CMS <code>SignedDataStream</code> object and wraps it by a
099   * CMS <code>ContentInfoStream</code>.
100   *
101   * @param message the message to be signed, as byte representation
102   * @param mode the transmission mode, either IMPLICIT or EXPLICIT
103   * @param hashAlgorithm the hash algorithm to be used
104   * @param signatureAlgorithm the signature algorithm to be used
105   * @param signerKey the private key of the signer
106   * @param certificates the certificate chain of the signer
107   * 
108   * @return the DER encoding of the <code>ContentInfo</code> object just created
109   * 
110   * @throws CMSException if the <code>SignedData</code>, <code>ContentInfo</code>
111   *            object cannot be created
112   * @throws IOException if an I/O related error occurs
113   */
114  public byte[] createSignedDataStream(byte[] message, 
115                                       int mode,
116                                       AlgorithmID hashAlgorithm,
117                                       AlgorithmID signatureAlgorithm,
118                                       PrivateKey signerKey,
119                                       X509Certificate[] certificates) 
120    throws CMSException, IOException  {
121    
122    System.out.print("Create a new message signed with " + signatureAlgorithm.getName());
123   
124    // we are testing the stream interface
125    ByteArrayInputStream is = new ByteArrayInputStream(message);
126    // create a new SignedData object which includes the data
127    SignedDataStream signed_data = new SignedDataStream(is, mode);
128    
129    // SignedData shall include the certificate chain for verifying
130    signed_data.setCertificates(certificates);
131
132    // cert at index 0 is the user certificate
133    IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(certificates[0]);
134
135    // create a new SignerInfo
136    AlgorithmID eddsaSig = (AlgorithmID)signatureAlgorithm.clone();
137    eddsaSig.encodeAbsentParametersAsNull(false);
138    SignerInfo signer_info = new SignerInfo(issuer, (AlgorithmID)hashAlgorithm.clone(), eddsaSig, signerKey);
139    
140    try {
141      // create some signed attributes
142      // the message digest attribute is automatically added
143      Attribute[] attributes = new Attribute[2];
144      // content type is data
145      CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
146      attributes[0] = new Attribute(contentType);
147      // signing time is now
148      SigningTime signingTime = new SigningTime();
149      attributes[1] = new Attribute(signingTime);
150  
151      // set the attributes
152      signer_info.setSignedAttributes(attributes);
153    } catch (Exception ex) {
154      throw new CMSException("Error adding attributes: " + ex.toString());
155    }
156    
157    // finish the creation of SignerInfo by calling method addSigner
158    try {
159      signed_data.addSignerInfo(signer_info);
160    } catch (NoSuchAlgorithmException ex) {
161      throw new CMSException("No implementation for signature algorithm: "+ex.getMessage());
162    }
163
164    // write the data through SignedData to any out-of-band place
165    if (mode == SignedDataStream.EXPLICIT) {
166      InputStream data_is = signed_data.getInputStream();
167      byte[] buf = new byte[1024];
168      int r;
169      while ((r = data_is.read(buf)) > 0) {
170        ;   // skip data
171      }  
172    }
173
174    signed_data.setBlockSize(2048);
175     // create the ContentInfo
176    ContentInfoStream cis = new ContentInfoStream(signed_data);
177    // return the SignedData as DER encoded byte array with block size 2048
178    ByteArrayOutputStream os = new ByteArrayOutputStream();
179    cis.writeTo(os);
180    return os.toByteArray();
181  }
182  
183  
184  /**
185   * Parses a CMS <code>ContentInfo</code> object holding a <code>SignedData</code> 
186   * object and verifies the signature.
187   *
188   * @param signedData the <code>ContentInfo</code> holding the <code>SignedData</code> 
189   *                   object as BER encoded byte array
190   * @param message the the message which was transmitted out-of-band (explicit signed)
191   * @param certificates the certificate of the signer (used for alternative signature verification)
192   * 
193   * @return the inherent message as byte array
194   * 
195   * @throws CMSException if any signature does not verify
196   * @throws IOException if an I/O related error occurs
197   */
198  public byte[] getSignedDataStream(byte[] signedData, byte[] message, X509Certificate[] certificates) 
199    throws CMSException, IOException {
200
201    // we are testing the stream interface
202    ByteArrayInputStream is = new ByteArrayInputStream(signedData);
203
204    SignedDataStream signed_data = new SignedDataStream(is);
205
206    if (signed_data.getMode() == SignedDataStream.EXPLICIT) {
207      // in explicit mode explicitly supply the content for hash computation
208      signed_data.setInputStream(new ByteArrayInputStream(message));
209    }
210
211    // get an InputStream for reading the signed content and update hash computation
212    InputStream data = signed_data.getInputStream();
213    ByteArrayOutputStream os = new ByteArrayOutputStream();
214    Util.copyStream(data, os, null);
215
216    System.out.println("SignedData contains the following signer information:");
217    SignerInfo[] signer_infos = signed_data.getSignerInfos();
218    
219    int numberOfSignerInfos = signer_infos.length;
220    if (numberOfSignerInfos == 0) {
221      String warning = "Warning: Unsigned message (no SignerInfo included)!";  
222      System.err.println(warning);
223      throw new CMSException(warning);
224    } else {
225      for (int i = 0; i < numberOfSignerInfos; i++) {
226        try {
227          // verify the signed data using the SignerInfo at index i
228          X509Certificate signer_cert = signed_data.verify(i);
229          // if the signature is OK the certificate of the signer is returned
230          System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN());
231          // check for some included attributes
232          SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime);
233          if (signingTime != null) {
234            System.out.println("This message has been signed at " + signingTime.get());
235          } 
236          CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType);
237          if (contentType != null) {
238            System.out.println("The content has CMS content type " + contentType.get().getName());
239          }  
240        } catch (SignatureException ex) {
241          // if the signature is not OK a SignatureException is thrown
242          System.out.println("Signature ERROR from signer: "+signed_data.getCertificate(signer_infos[i].getSignerIdentifier()).getSubjectDN());
243          throw new CMSException(ex.toString());
244        }  
245      }  
246      // now check alternative signature verification
247      System.out.println("Now check the signature assuming that no certs have been included:");
248      try {
249        SignerInfo signer_info = signed_data.verify(certificates[0]);
250        // if the signature is OK the certificate of the signer is returned
251        System.out.println("Signature OK from signer: "+signed_data.getCertificate(signer_info.getSignerIdentifier()).getSubjectDN());
252      } catch (SignatureException ex) {
253        // if the signature is not OK a SignatureException is thrown
254        System.out.println("Signature ERROR from signer: "+certificates[0].getSubjectDN());
255        throw new CMSException(ex.toString());
256      }
257      // in practice we also would validate the signer certificate(s)  
258    }      
259    return os.toByteArray();
260  }
261  
262  
263  /**
264   * Creates an EdDSA signed CMS <code>SignedData</code> object and wraps it by a CMS
265   * <code>ContentInfo</code> object.
266   * <p>
267   *
268   * @param message the message to be signed, as byte representation
269   * @param mode the mode, either SignedData.IMPLICIT or SignedData.EXPLICIT
270   * @param hashAlgorithm the hash algorithm to be used
271   * @param signatureAlgorithm the signature algorithm to be used
272   * @param signerKey the private key of the signer
273   * @param certificates the certificate chain of the signer
274   * 
275   * @return the DER encoded <code>SignedData</code>-<code>ContentInfo</code> object
276   *  
277   * @throws CMSException if the <code>SignedData</code>-<code>ContentInfo</code> object cannot
278   *                          be created
279   * @throws IOException if an I/O related error occurs
280   */
281  public byte[] createSignedData(byte[] message, 
282                                 int mode,
283                                 AlgorithmID hashAlgorithm,
284                                 AlgorithmID signatureAlgorithm,
285                                 PrivateKey signerKey,
286                                 X509Certificate[] certificates) 
287    throws CMSException, IOException  {
288    
289    System.out.println("Create a new message signed with " + signatureAlgorithm.getName());
290  
291    // create a new SignedData object which includes the data
292    SignedData signed_data = new SignedData(message, mode);
293    
294    // SignedData shall include the certificate chain for verifying
295    signed_data.setCertificates(certificates);
296  
297    // cert at index 0 is the user certificate
298    IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(certificates[0]);
299
300    // create a new SignerInfo
301    AlgorithmID eddsaSig = (AlgorithmID)signatureAlgorithm.clone();
302    eddsaSig.encodeAbsentParametersAsNull(false);
303    SignerInfo signer_info = new SignerInfo(issuer, (AlgorithmID)hashAlgorithm.clone(), eddsaSig, signerKey);
304    
305    try {
306      // create some signed attributes
307      // the message digest attribute is automatically added
308      Attribute[] attributes = new Attribute[2];
309      // content type is data
310      CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
311      attributes[0] = new Attribute(contentType);
312      // signing time is now
313      SigningTime signingTime = new SigningTime();
314      attributes[1] = new Attribute(signingTime);
315  
316      // set the attributes
317      signer_info.setSignedAttributes(attributes);
318    } catch (Exception ex) {
319      throw new CMSException("Error adding attributes: " + ex.toString());
320    }
321    
322    // finish the creation of SignerInfo by calling method addSigner
323    try {
324      signed_data.addSignerInfo(signer_info);
325    } catch (NoSuchAlgorithmException ex) {
326      throw new CMSException("No implementation for signature algorithm: "+ex.getMessage());
327    }
328
329    ContentInfo ci = new ContentInfo(signed_data); 
330    return ci.getEncoded();
331  }
332  
333  
334  /**
335   * Parses a CMS <code>ContentInfo</code> holding a <code>SignedData</code> 
336   * object and verifies the signature.
337   *
338   * @param signedData the <code>ContentInfo</code> holding the <code>SignedData</code> 
339   *                   object as DER encoded byte array
340   * @param message the message which was transmitted out-of-band (explicit signed)
341   * @param certificates the certificate of the signer (used for alternative signature verification) 
342   * 
343   * @return the inherent message as byte array
344   * 
345   * @throws CMSException if any signature does not verify
346   * @throws IOException if an I/O related error occurs
347   */
348  public byte[] getSignedData(byte[] signedData, byte[] message, X509Certificate[] certificates) 
349    throws CMSException, IOException {
350    
351    ByteArrayInputStream is = new ByteArrayInputStream(signedData);
352    // create the SignedData object
353    SignedData signed_data = new SignedData(is);
354    
355    if (signed_data.getMode() == SignedData.EXPLICIT) {
356      // in explcit mode explictly supply the content data to do the hash calculation
357      signed_data.setContent(message);
358    }
359    
360    System.out.println("SignedData contains the following signer information:");
361    SignerInfo[] signer_infos = signed_data.getSignerInfos();
362    
363    int numberOfSignerInfos = signer_infos.length;
364    if (numberOfSignerInfos == 0) {
365      String warning = "Warning: Unsigned message (no SignerInfo included)!";  
366      System.err.println(warning);
367      throw new CMSException(warning);
368    } else {
369      for (int i = 0; i < numberOfSignerInfos; i++) {
370        try {
371          // verify the signed data using the SignerInfo at index i
372          X509Certificate signer_cert = signed_data.verify(i);
373          // if the signature is OK the certificate of the signer is returned
374          System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN());
375          // check some attributes
376          SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime);
377          if (signingTime != null) {
378            System.out.println("This message has been signed at " + signingTime.get());
379          } 
380          CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType);
381          if (contentType != null) {
382            System.out.println("The content has CMS content type " + contentType.get().getName());
383          }
384        } catch (SignatureException ex) {
385          // if the signature is not OK a SignatureException is thrown
386          System.out.println("Signature ERROR from signer: "+signed_data.getCertificate(signer_infos[i].getSignerIdentifier()).getSubjectDN());
387          throw new CMSException(ex.toString());
388        } 
389      }      
390    
391      // now check alternative signature verification
392      System.out.println("Now check the signature assuming that no certs have been included:");
393      try {
394        SignerInfo signer_info = signed_data.verify(certificates[0]);
395        // if the signature is OK the certificate of the signer is returned
396        System.out.println("Signature OK from signer: "+signed_data.getCertificate(signer_info.getSignerIdentifier()).getSubjectDN());
397      } catch (SignatureException ex) {
398        // if the signature is not OK a SignatureException is thrown
399        System.out.println("Signature ERROR from signer: "+certificates[0].getSubjectDN());
400        throw new CMSException(ex.toString());
401      }
402      // in practice we also would validate the signer certificate(s)  
403    }      
404    return signed_data.getContent();
405  }
406
407  /**
408   * Runs the signing - verifying demo.
409   * 
410   * @param message the message to be signed
411   * @param hashAlgorithm the hash algorithm to be used
412   * @param signatureAlgorithm the signature algorithm to be used
413   * @param signerKeyAndCert private key and certificate chain of the signer
414   */
415  public void runDemo(byte[] message, 
416                      AlgorithmID hashAlgorithm,
417                      AlgorithmID signatureAlgorithm,
418                      KeyAndCertificate signerKeyAndCert) 
419    throws Exception {
420    
421    PrivateKey signerKey = signerKeyAndCert.getPrivateKey();
422    X509Certificate[] signerCerts = signerKeyAndCert.getCertificateChain();
423    
424    byte[] encodedSignedData;
425    byte[] received_message = null;
426    
427    System.out.println("\nRun demos for " + hashAlgorithm.getName() + " / " + signatureAlgorithm.getName() + "\n");
428      
429    System.out.println("Stream implementation demos");
430    System.out.println("===========================");
431    //
432    // test CMS Implicit SignedDataStream
433    //
434    System.out.println("\nImplicit SignedDataStream demo [create]:\n");
435    encodedSignedData = createSignedDataStream(message, 
436                                               SignedDataStream.IMPLICIT,
437                                               hashAlgorithm,
438                                               signatureAlgorithm,
439                                               signerKey,
440                                               signerCerts);
441    System.out.println();
442    // transmit data
443    System.out.println("\nImplicit SignedDataStream demo [parse]:\n");
444    received_message = getSignedDataStream(encodedSignedData, null, signerCerts);
445    System.out.print("\nSigned content: ");
446    System.out.println(new String(received_message));
447      
448    //
449    // test CMS Explicit SignedDataStream
450    //
451    System.out.println("\nExplicit SignedDataStream demo [create]:\n");
452    encodedSignedData = createSignedDataStream(message,
453                                               SignedDataStream.EXPLICIT,
454                                               hashAlgorithm,
455                                               signatureAlgorithm,
456                                               signerKey,
457                                               signerCerts);
458    // transmit data
459    System.out.println("\nExplicit SignedDataStream demo [parse]:\n");
460    received_message = getSignedDataStream(encodedSignedData, message, signerCerts);
461    System.out.print("\nSigned content: ");
462    System.out.println(new String(received_message));
463      
464    // the non-stream implementation
465    System.out.println("\nNon-stream implementation demos");
466    System.out.println("===============================");
467
468    //
469    // test CMS Implicit SignedData
470    //
471    System.out.println("\nImplicit CMS SignedData demo [create]:\n");
472    encodedSignedData = createSignedData(message, 
473                                         SignedData.IMPLICIT,
474                                         hashAlgorithm,
475                                         signatureAlgorithm,
476                                         signerKey,
477                                         signerCerts);
478    // transmit data
479    System.out.println("\nImplicit CMS SignedData demo [parse]:\n");
480    received_message = getSignedData(encodedSignedData, null, signerCerts);
481    System.out.print("\nSigned content: ");
482    System.out.println(new String(received_message));
483
484    //
485    // test CMS Explicit SignedData
486    //
487    System.out.println("\nExplicit CMS SignedData demo [create]:\n");
488    encodedSignedData = createSignedData(message, 
489                                         SignedData.EXPLICIT,
490                                         hashAlgorithm,
491                                         signatureAlgorithm,
492                                         signerKey,
493                                         signerCerts);
494    // transmit data
495    System.out.println("\nExplicit CMS SignedData demo [parse]:\n");
496    received_message = getSignedData(encodedSignedData, message, signerCerts);
497    System.out.print("\nSigned content: ");
498    System.out.println(new String(received_message));
499        
500  }
501  
502  /**
503   * Tests the CMS SignedData implementation with the ECDSA signature
504   * algorithm and several hash algorithms.
505   */
506  public void start() throws Exception {
507    
508     // the test message
509    String m = "This is the test message.";
510    System.out.println("Test message: \""+m+"\"");
511    System.out.println();
512    byte[] message = m.getBytes();
513
514    AlgorithmID[][] algorithms = new AlgorithmID[][] {
515                                   { CMSAlgorithmID.sha512, CMSAlgorithmID.ed25519},
516                                   { CMSAlgorithmID.shake256Len, CMSAlgorithmID.ed448 },
517                                 };
518
519    // get signer key and certs                                 
520    KeyAndCertificate[] keyAndCerts = {
521      // ed25519  
522      new KeyAndCertificate(CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_ED25519),
523                            CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_ED25519)),
524      // ed448  
525      new KeyAndCertificate(CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_ED448),
526                            CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_ED448)),
527                             
528    };
529                                 
530    final int HASH_ALG = 0;
531    final int SIGNATURE_ALG = 1;
532    for (int i = 0; i < algorithms.length; i++) {
533      runDemo(message, algorithms[i][HASH_ALG], algorithms[i][SIGNATURE_ALG], keyAndCerts[i]);
534    }
535   
536  }  
537  
538  /**
539   * Starts the demo.
540   * 
541   * @throws Exception 
542   *            if an error occurs 
543   */
544  public static void main(String argv[]) throws Exception {
545
546    DemoUtil.initDemos();
547    ECCDemoUtil.installIaikEccelerateProvider();   
548    (new EdDSASignedDataDemo()).start();
549    System.out.println("\nReady!");
550    System.in.read();
551  }
552    
553}