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/pkcs11/SignedDataStreamDemo.java 16    12.02.25 17:58 Dbratko $
029// $Revision: 16 $
030//
031
032package demo.cms.pkcs11;
033
034import java.io.ByteArrayInputStream;
035import java.io.ByteArrayOutputStream;
036import java.io.IOException;
037import java.io.InputStream;
038import java.security.GeneralSecurityException;
039import java.security.Key;
040import java.security.NoSuchAlgorithmException;
041import java.security.PrivateKey;
042import java.security.SignatureException;
043import java.security.cert.Certificate;
044import java.security.cert.X509Certificate;
045import java.util.Enumeration;
046
047import demo.DemoUtil;
048// class and interface imports
049import iaik.asn1.ObjectID;
050import iaik.asn1.structures.AlgorithmID;
051import iaik.asn1.structures.Attribute;
052import iaik.cms.CMSException;
053import iaik.cms.ContentInfoStream;
054import iaik.cms.IssuerAndSerialNumber;
055import iaik.cms.SignedDataStream;
056import iaik.cms.SignerInfo;
057import iaik.cms.attributes.CMSContentType;
058import iaik.cms.attributes.SigningTime;
059
060
061/**
062 * Base class of SignedDataStream demos using PKCS#11 for
063 * accessing the signer key on a smart card.
064 */
065public abstract class SignedDataStreamDemo extends PKCS11Demo {
066  
067   /**
068   * The private key of the signer. In this case only a proxy object, but the
069   * application cannot see this.
070   */
071  protected PrivateKey signerKey_;
072
073  /**
074   * This is the certificate used for verifying the signature. In contrast to the
075   * private signer key, the certificate holds the actual public keying material.
076   */
077  protected X509Certificate signerCertificate_;
078
079  /**
080   * Creates a SignedDataStreamDemo object that has to be explicitly 
081   * {@link PKCS11Demo#init(String, char[]) initialized} with a module name.
082   */
083  protected SignedDataStreamDemo() {
084    // install provider in super class    
085    super();
086  }
087  
088  /**
089   * This method gets the key stores of all inserted (compatible) smart
090   * cards and simply takes the first key-entry. From this key entry it
091   * takes the private key and the certificate to retrieve the public key
092   * from. The keys are stored in the member variables <code>signerKey_
093   * </code> and <code>signerCertificate_</code>.
094   *
095   * @throws GeneralSecurityException If anything with the provider fails.
096   * @throws IOException If loading the key store fails.
097   */
098  protected void getSignatureKey() throws GeneralSecurityException, IOException
099  {
100    getSignatureKey(null);
101  }
102
103  /**
104   * This method gets the key stores of all inserted (compatible) smart
105   * cards and simply takes the first key-entry. From this key entry it
106   * takes the private key and the certificate to retrieve the public key
107   * from. The keys are stored in the member variables <code>signerKey_
108   * </code> and <code>signerCertificate_</code>.
109   * <br>
110   * If <code>algorithm</code> is not <code>null</code> only those keys
111   * are considered that match the given algorithm.
112   * 
113   * @param algorithm the key algorithm; maybe <code>null</code> to take
114   *                  the first signing key regardless of its algorithm
115   *
116   * @throws GeneralSecurityException If anything with the provider fails.
117   * @throws IOException If loading the key store fails.
118   */
119  protected void getSignatureKey(String algorithm) throws GeneralSecurityException, IOException
120  {
121    // we simply take the first keystore, if there are serveral
122    Enumeration aliases = tokenKeyStore_.aliases();
123
124    // and we take the first signature (private) key for simplicity
125    while (aliases.hasMoreElements()) {
126      String keyAlias = aliases.nextElement().toString();
127      Key key = null;
128      try {
129        key = tokenKeyStore_.getKey(keyAlias, null);
130      } catch (NoSuchAlgorithmException ex) {
131        throw new GeneralSecurityException(ex.toString());
132      }
133
134      if (key instanceof PrivateKey) {
135        if ((algorithm != null) && (!algorithm.equals(key.getAlgorithm()))) {
136          continue;
137        }
138        Certificate[] certificateChain = tokenKeyStore_.getCertificateChain(keyAlias);
139        if ((certificateChain != null) && (certificateChain.length > 0)) {
140          X509Certificate signerCertificate = (X509Certificate) certificateChain[0];
141          boolean[] keyUsage = signerCertificate.getKeyUsage();
142          if ((keyUsage == null) || keyUsage[0] || keyUsage[1]) { // check for digital signature or non-repudiation, but also accept if none set
143            System.out.println("##########");
144            System.out.println("The signer key is: " + key );
145            System.out.println("##########");
146            // get the corresponding certificate for this signer key
147            System.out.println("##########");
148            System.out.println("The signer certificate is:");
149            System.out.println(signerCertificate.toString());
150            System.out.println("##########");
151            signerKey_ = (PrivateKey) key;
152            signerCertificate_ = signerCertificate;
153            break;
154          }
155        }  
156      }
157    }
158
159    if (signerKey_ == null) {
160      System.out.println("Found no signature key. Ensure that a valid card is inserted and contains a key that is suitable for signing.");
161      System.exit(0);
162    }
163  }
164  
165  /**
166   * This method creates a SignerInfo for the given signer certificate.
167   *
168   * @param signerCertificate the certificate of the signer
169   * 
170   * @return the SignerInfo
171   */
172  protected SignerInfo createSignerInfo(iaik.x509.X509Certificate signerCertificate) 
173  {
174    IssuerAndSerialNumber issuerAndSerialNumber = new IssuerAndSerialNumber(signerCertificate);
175    return new SignerInfo(issuerAndSerialNumber,
176                          (AlgorithmID)AlgorithmID.sha256.clone(), 
177                          signerKey_);
178  }
179
180  /**
181   * This method signs the data in the byte array <code>DATA</code> with
182   * <code>signatureKey_</code>. Normally the data would be read from file.
183   * The created signature is stored in <code>signature_</code>.
184   * 
185   * @param data the data to be signed
186   * @param implicit whether to include the data (implicit mode) 
187   *                 or  to not include it (explicit mode)
188   * 
189   * @return the encoded SignedData
190   *
191   * @throws GeneralSecurityException
192   *     If anything with the provider fails.
193   * @throws IOException
194   *     If the data file could not be found or writing to it failed.
195   * @throws CMSException 
196   *     If an error occurs when creating/encoding the SignedData     
197   */
198  public byte[] sign(byte[] data, boolean implicit)
199      throws GeneralSecurityException, IOException, CMSException
200  {    
201    System.out.println("##########");
202    System.out.println("Signing data... ");
203    
204    InputStream dataStream = new ByteArrayInputStream(data); // the raw data supplying input stream
205    int mode = (implicit == true) ? SignedDataStream.IMPLICIT : SignedDataStream.EXPLICIT;
206    SignedDataStream signedData = new SignedDataStream(dataStream, mode);
207    iaik.x509.X509Certificate iaikSignerCertificate = (signerCertificate_ instanceof iaik.x509.X509Certificate) 
208                                                       ? (iaik.x509.X509Certificate) signerCertificate_
209                                                       : new iaik.x509.X509Certificate(signerCertificate_.getEncoded());
210    signedData.setCertificates(new iaik.x509.X509Certificate[] { iaikSignerCertificate } );
211    SignerInfo signerInfo = createSignerInfo(iaikSignerCertificate);
212    System.out.println("Digest algorithm: " + signerInfo.getDigestAlgorithm());
213    System.out.println("Signature algorithm: " + signerInfo.getSignatureAlgorithm());
214    
215    // create some signed attributes
216    // the message digest attribute is automatically added
217    Attribute[] attributes = new Attribute[2];
218    try {
219      // content type is data
220      CMSContentType contentType = new CMSContentType(ObjectID.cms_data);
221      attributes[0] = new Attribute(contentType);
222      // signing time is now
223      SigningTime signingTime = new SigningTime();
224      attributes[1] = new Attribute(signingTime);
225    } catch (Exception ex) {
226      throw new CMSException("Error creating attribute: " + ex.toString());   
227    }    
228    
229    // set the attributes
230    signerInfo.setSignedAttributes(attributes);
231    
232    try {
233      signedData.addSignerInfo(signerInfo);
234    } catch (NoSuchAlgorithmException ex) {
235      throw new GeneralSecurityException(ex.toString());
236    }
237    
238    if (implicit == false) {
239      // in explicit mode read "away" content data (to be transmitted out-of-band)
240      InputStream contentIs = signedData.getInputStream();
241      byte[] buffer = new byte[2048];
242      int bytesRead;
243      while ((bytesRead = contentIs.read(buffer)) >= 0) {
244        ;  // skip data
245      }
246    }
247    
248    ByteArrayOutputStream baos = new ByteArrayOutputStream();
249    ContentInfoStream cos = new ContentInfoStream(signedData);
250    cos.writeTo(baos);
251    
252    return baos.toByteArray();
253  }
254
255  /**
256   * This method verifies the signature stored in <code>signatureKey_
257   * </code>. The verification key used is <code>verificationKey_</code>.
258   * The implementation for the signature algorithm is taken from an
259   * other provider. Here IAIK is used, IAIK is pure software.
260   * 
261   * @param encodedSignedData the encoded SignedData object
262   * @param contentData the contentData (in explicit mode required for signature verification)
263   * 
264   * @return the content data
265   *
266   * @throws GeneralSecurityException
267   *     If anything with the provider fails.
268   * @throws IOException
269   *     If reading the CMS file fails.
270   * @throws CMSException
271   *     If handling the CMS structure fails.
272   * @throws SignatureException
273   *     If the signature verification fails    
274   */
275  public byte[] verify(byte[] encodedSignedData, byte[] contentData)
276      throws GeneralSecurityException, CMSException, IOException, SignatureException
277  {
278    System.out.println("##########");
279    System.out.println("Verifying signature");
280    
281    InputStream inputStream = new ByteArrayInputStream(encodedSignedData); 
282    SignedDataStream signedData = new SignedDataStream(inputStream);
283    
284    if (signedData.getMode() == SignedDataStream.EXPLICIT) {
285      // explicitly set the data received by other means
286      signedData.setInputStream(new ByteArrayInputStream(contentData));
287    }
288    
289    // read data
290    InputStream signedDataInputStream = signedData.getInputStream();
291
292    ByteArrayOutputStream contentOs = new ByteArrayOutputStream();
293    byte[] buffer = new byte[2048];
294    int bytesRead;
295    while ((bytesRead = signedDataInputStream.read(buffer)) >= 0) {
296      contentOs.write(buffer, 0, bytesRead);
297    }
298    
299    // get the signer infos
300    SignerInfo[] signerInfos = signedData.getSignerInfos();
301    // verify the signatures
302    int numberOfSignerInfos = signerInfos.length;
303    if (numberOfSignerInfos == 0) {
304      String warning = "Warning: Unsigned message (no SignerInfo included)!";  
305      System.err.println(warning);
306      throw new CMSException(warning);
307    } else {
308      for (int i = 0; i < numberOfSignerInfos; i++) {
309        try {
310          // verify the signature for SignerInfo at index i
311          X509Certificate signerCertificate = signedData.verify(i);
312          // if the signature is OK the certificate of the signer is returned
313          System.out.println("Signature OK from signer: "+ signerCertificate.getSubjectDN());
314        } catch (SignatureException ex) {
315          // if the signature is not OK a SignatureException is thrown
316          throw new SignatureException("Signature ERROR: " + ex.getMessage());
317        }
318        // in practice we also would validate the signer certificate(s)  
319      }        
320    }
321    System.out.println("##########");
322    // return the content
323    return contentOs.toByteArray();
324  }
325  
326  /**
327   * Starts the demo.
328   * 
329   * @param implicit whether the implicit or explicit mode is used (data included in signature or not) 
330   */
331  public void start(boolean implicit) {
332    try {
333      byte[] testMessage = "This is the test message to be signed!".getBytes("ASCII");
334      getKeyStore();
335      getSignatureKey();
336      byte[] signedData = sign(testMessage, implicit);
337      // verify
338      byte[] content = verify(signedData, implicit ? null : testMessage);
339      System.out.println("##########");
340      // we know that we had a text content, thus we can convert into a String
341      System.out.println("Content: " + new String(content, "ASCII"));
342      System.out.println("##########");
343      System.out.println("\nReady!");
344    } catch (Throwable ex) {
345      ex.printStackTrace();
346      throw new RuntimeException(ex.toString());
347    }
348  }
349  
350  /**
351   * This method starts the demo based on the given command line arguments.
352   *
353   * @param args These are the command line arguments.
354   */
355  public void init(String[] args) {
356
357    if (args.length == 0) {
358      System.out.println("Missing pkcs11 module name.\n");
359      printUsage();
360    }
361    
362    String moduleName = args[0];
363    char[] userPin = (args.length == 2) ? args[1].toCharArray() : null;
364    
365    if (args.length > 2) {
366      System.out.println("Too many arguments.\n");
367      printUsage();
368    }
369    
370    init(moduleName, userPin);
371    
372    DemoUtil.initDemos();
373    
374  }
375  
376  /**
377   * Print usage information.
378   */
379  private final void printUsage() {
380    String demo = getClass().getName();
381    System.out.println("Usage:\n");
382    System.out.println("java " + demo + " <pkcs11 module name>\n");
383    System.out.println("e.g.:");
384    System.out.println("java " + demo + " aetpkss1.dll");
385    System.out.println("java "  + demo + " aetpkss1.so");
386    DemoUtil.waitKey();
387    System.exit(0);
388  }
389
390}