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/EdDSASignedDataOutputStreamDemo.java 6     12.02.25 17:58 Dbratko $
029// $Revision: 6 $
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 EdDSA (Ed25519, Ed448) 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 EdDSASignedDataOutputStreamDemo {
081
082  /**
083   * Default Constructor.
084   */
085  public EdDSASignedDataOutputStreamDemo() throws Exception {
086    System.out.println();
087    System.out.println("**********************************************************************************");
088    System.out.println("*                           EdDSASignedData demo                                 *");
089    System.out.println("*      (shows how to use the SignedData(Stream) implementation with EdDSA)       *");
090    System.out.println("**********************************************************************************");
091    System.out.println();
092    
093  }
094  
095  /**
096   * Creates an EdDSA signed CMS <code>SignedDataOutputStream</code> object and wraps it by a
097   * CMS <code>ContentInfoOutputStream</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 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
137    // cert at index 0 is the user certificate
138    IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(certificates[0]);
139
140    // create a new SignerInfo
141    AlgorithmID eddsaSig = (AlgorithmID)signatureAlgorithm.clone();
142    eddsaSig.encodeAbsentParametersAsNull(false);
143    SignerInfo signer_info = new SignerInfo(issuer, (AlgorithmID)hashAlgorithm.clone(), eddsaSig, 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   * Tests the CMS SignedData implementation with the ECDSA signature
329   * algorithm and several hash algorithms.
330   */
331  public void start() throws Exception {
332    
333     // the test message
334    String m = "This is the test message.";
335    System.out.println("Test message: \""+m+"\"");
336    System.out.println();
337    byte[] message = m.getBytes();
338
339    AlgorithmID[][] algorithms = new AlgorithmID[][] {
340                                   { CMSAlgorithmID.sha512, CMSAlgorithmID.ed25519},
341                                   { CMSAlgorithmID.shake256Len, CMSAlgorithmID.ed448 },
342                                 };
343
344    // get signer key and certs                                 
345    KeyAndCertificate[] keyAndCerts = {
346      // ed25519  
347      new KeyAndCertificate(CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_ED25519),
348                            CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_ED25519)),
349      // ed448  
350      new KeyAndCertificate(CMSEccKeyStore.getPrivateKey(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_ED448),
351                            CMSEccKeyStore.getCertificateChain(CMSEccKeyStore.ECDSA, CMSEccKeyStore.SZ_ED448)),
352                             
353    };
354                                 
355    final int HASH_ALG = 0;
356    final int SIGNATURE_ALG = 1;
357    for (int i = 0; i < algorithms.length; i++) {
358      runDemo(message, algorithms[i][HASH_ALG], algorithms[i][SIGNATURE_ALG], keyAndCerts[i]);
359    }
360   
361  }  
362  
363  /**
364   * Starts the demo.
365   * 
366   * @throws Exception 
367   *            if an error occurs 
368   */
369  public static void main(String argv[]) throws Exception {
370
371    DemoUtil.initDemos();
372    ECCDemoUtil.installIaikEccelerateProvider();   
373    (new EdDSASignedDataOutputStreamDemo()).start();
374    System.out.println("\nReady!");
375    System.in.read();
376  }
377    
378}