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/signedData/SignedDataStreamDemoWithAdditionalSignerInfo.java 23 12.02.25 17:58 Dbratko $ 059 // $Revision: 23 $ 060 // 061 062 package demo.cms.signedData; 063 064 import iaik.asn1.ObjectID; 065 import iaik.asn1.structures.AlgorithmID; 066 import iaik.asn1.structures.Attribute; 067 import iaik.cms.CMSException; 068 import iaik.cms.DefaultSDSEncodeListener; 069 import iaik.cms.IssuerAndSerialNumber; 070 import iaik.cms.SignedDataStream; 071 import iaik.cms.SignerInfo; 072 import iaik.cms.attributes.CMSContentType; 073 import iaik.cms.attributes.SigningTime; 074 import iaik.utils.Util; 075 import iaik.x509.X509Certificate; 076 077 import java.io.ByteArrayInputStream; 078 import java.io.ByteArrayOutputStream; 079 import java.io.IOException; 080 import java.io.InputStream; 081 import java.security.NoSuchAlgorithmException; 082 import java.security.PrivateKey; 083 import java.security.SignatureException; 084 import java.security.cert.Certificate; 085 086 import demo.DemoUtil; 087 import demo.keystore.CMSKeyStore; 088 089 /** 090 * This class demonstrates the usage of an SDSEncodeListener to add a new SignerInfo to an 091 * existing, parsed SignedDataStream object. 092 * <p> 093 * A {@link iaik.cms.SDSEncodeListener SDSEncodeListener} allows an application to update 094 * a {@link iaik.cms.SignedDataStream SignedDataStream} during the encoding phase. 095 * <p> 096 * In some situations it may be useful to supply information to a SignedDataStream 097 * actually during encoding is performed. When implementing an SignedDataStream 098 * encode listener an application has the chance to update the SignedDataStream 099 * at two points during the encoding process: AFTER the content data has been 100 * processed and any digest has been calulated (= BEFORE any signature value 101 * is computed) and AFTER the signature values have been calculated. When doing 102 * so an application has to implement two abstract methods: {@link 103 * iaik.cms.SDSEncodeListener#beforeComputeSignature(SignedDataStream) beforeComputeSignature} and 104 * {@link iaik.cms.SDSEncodeListener#afterComputeSignature(SignedDataStream) afterComputeSignature} 105 * (of course, an application may implement any of the two methods in a 106 * way to do actually nothing (if no functionality is required)). 107 * <p> 108 * This demo uses the IAIK-CMS {@link iaik.cms.DefaultSDSEncodeListener DefaultSDSEncodeListener} 109 * for adding a new SignerInfo to an already existing SignedDataStream "on the fly". 110 * 111 * @see iaik.cms.SignedDataStream 112 * @see iaik.cms.SDSEncodeListener 113 */ 114 public class SignedDataStreamDemoWithAdditionalSignerInfo { 115 116 byte[] message; 117 118 // signing certificate of user 1 119 X509Certificate user1_sign; 120 // signing private key of user 1 121 PrivateKey user1_sign_pk; 122 // signing certificate of user 2 123 X509Certificate user2_sign; 124 // signing private key of user 2 125 PrivateKey user2_sign_pk; 126 127 // a certificate chain containing the user certs + CA 128 X509Certificate[] certificates; 129 130 /** 131 * Constructor. 132 * Reads required keys/certs from the demo keystore. 133 */ 134 public SignedDataStreamDemoWithAdditionalSignerInfo() { 135 136 System.out.println(); 137 System.out.println("****************************************************************************************"); 138 System.out.println("* SignedDataStreamDemoWithAdditionalSignerInfo *"); 139 System.out.println("* (shows how to use a SDSEncodeListener for the CMS SignedDataStream implementation) *"); 140 System.out.println("****************************************************************************************"); 141 System.out.println(); 142 143 message = "This is a test of the CMS implementation!".getBytes(); 144 // signing certs 145 certificates = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 146 user1_sign = certificates[0]; 147 user1_sign_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 148 user2_sign = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1)[0]; 149 user2_sign_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 150 } 151 152 /** 153 * Creates a CMS <code>SignedData</code> object. 154 * <p> 155 * 156 * @param message the message to be signed, as byte representation 157 * @param mode the mode indicating whether to include the content 158 * (SignedDataStream.IMPLICIT) or not (SignedDataStream.EXPLICIT) 159 * @return the encoding of the <code>SignedData</code> object just created 160 * @throws CMSException if the <code>SignedData</code> object cannot 161 * be created 162 * @throws IOException if an I/O error occurs 163 */ 164 public byte[] createSignedDataStream(byte[] message, int mode) throws CMSException, IOException { 165 166 System.out.println("Create a new message signed by user 1:"); 167 168 // we are testing the stream interface 169 ByteArrayInputStream is = new ByteArrayInputStream(message); 170 // create a new SignedData object which includes the data 171 SignedDataStream signed_data = new SignedDataStream(is, mode); 172 173 // uses SDSEncodeListener here only to verify sigantures before finishing encoding 174 try { 175 signed_data.setSDSEncodeListener(new DefaultSDSEncodeListener()); 176 } catch (NoSuchAlgorithmException ex) { 177 // ignore here 178 } 179 180 // SignedData shall include the certificate chain for verifying 181 signed_data.setCertificates(certificates); 182 183 // cert at index 0 is the user certificate 184 IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(user1_sign); 185 186 // create a new SignerInfo 187 SignerInfo signer_info = new SignerInfo(issuer, (AlgorithmID)AlgorithmID.sha256.clone(), user1_sign_pk); 188 189 // create some signed attributes 190 // the message digest attribute is automatically added 191 Attribute[] attributes = new Attribute[2]; 192 try { 193 // content type is data 194 CMSContentType contentType = new CMSContentType(ObjectID.cms_data); 195 attributes[0] = new Attribute(contentType); 196 // signing time is now 197 SigningTime signingTime = new SigningTime(); 198 attributes[1] = new Attribute(signingTime); 199 } catch (Exception ex) { 200 throw new CMSException("Error creating attribute: " + ex.toString()); 201 } 202 203 // set the attributes 204 signer_info.setSignedAttributes(attributes); 205 // finish the creation of SignerInfo by calling method addSigner 206 try { 207 signed_data.addSignerInfo(signer_info); 208 209 } catch (NoSuchAlgorithmException ex) { 210 throw new CMSException("No implementation for signature algorithm: "+ex.getMessage()); 211 } 212 // ensure block encoding 213 signed_data.setBlockSize(2048); 214 215 // write the data through SignedData to any out-of-band place 216 if (mode == SignedDataStream.EXPLICIT) { 217 InputStream data_is = signed_data.getInputStream(); 218 byte[] buf = new byte[1024]; 219 int r; 220 while ((r = data_is.read(buf)) > 0) { 221 ; // skip data 222 } 223 } 224 225 // return the SignedData as encoded byte array with block size 2048 226 ByteArrayOutputStream os = new ByteArrayOutputStream(); 227 228 signed_data.writeTo(os); 229 return os.toByteArray(); 230 } 231 232 /** 233 * Parses a CMS <code>SignedData</code> object and verifies the signatures 234 * for all participated signers. 235 * 236 * @param signedData the SignedData, as BER encoded byte array 237 * @param message the the message which was transmitted out-of-band (explicit signed) 238 * @param writeAgain whether to use a SDSEncodeListener to add a SignerInfo 239 * and encode the SignedData again 240 * 241 * @return the inherent message as byte array, or the BER encoded SignedData if 242 * it shall be encoded again 243 * @throws CMSException if any signature does not verify 244 * @throws IOException if an I/O error occurs 245 */ 246 public byte[] getSignedDataStream(byte[] signedData, byte[] message, boolean writeAgain) 247 throws CMSException, IOException, NoSuchAlgorithmException { 248 249 // we are testing the stream interface 250 ByteArrayInputStream is = new ByteArrayInputStream(signedData); 251 252 // the ByteArrayOutputStream to which to write the content 253 ByteArrayOutputStream os = new ByteArrayOutputStream(); 254 255 SignedDataStream signed_data = new SignedDataStream(is); 256 257 if (signed_data.getMode() == SignedDataStream.EXPLICIT) { 258 // in explicit mode explicitly supply the content for hash computation 259 signed_data.setInputStream(new ByteArrayInputStream(message)); 260 } 261 262 if (writeAgain) { 263 // we want to write the SignedData again 264 // create a new SignerInfo 265 SignerInfo signer_info = new SignerInfo(new IssuerAndSerialNumber(user2_sign), 266 (AlgorithmID)AlgorithmID.sha256.clone(), 267 user2_sign_pk); 268 269 // create some signed attributes 270 // the message digest attribute is automatically added 271 Attribute[] attributes = new Attribute[2]; 272 try { 273 // content type is data 274 CMSContentType contentType = new CMSContentType(ObjectID.cms_data); 275 attributes[0] = new Attribute(contentType); 276 // signing time is now 277 SigningTime signingTime = new SigningTime(); 278 attributes[1] = new Attribute(signingTime); 279 } catch (Exception ex) { 280 throw new CMSException("Error creating attribute: " + ex.toString()); 281 } 282 // set the attributes 283 signer_info.setSignedAttributes(attributes); 284 285 // add the SignerInfo via SDSEncodeListener which also verifies the sigantures 286 DefaultSDSEncodeListener dl = new DefaultSDSEncodeListener(); 287 dl.setDigestAlgorithms(new AlgorithmID [] { (AlgorithmID)AlgorithmID.sha256.clone() }); 288 dl.setSignerInfos(new SignerInfo[] { signer_info }); 289 dl.setCertificates(new Certificate[] { user2_sign }); 290 291 292 if (message == null) { 293 // in implicit mode copy data to os 294 dl.setOutputStream(os); 295 signed_data.setSDSEncodeListener(dl); 296 297 } else { 298 signed_data.setSDSEncodeListener(dl); 299 // get an InputStream for reading the signed content 300 InputStream data = signed_data.getInputStream(); 301 Util.copyStream(data, os, null); 302 } 303 304 // ensure block encoding 305 signed_data.setBlockSize(2048); 306 // return the SignedData as encoded byte array with block size 2048 307 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 308 signed_data.writeTo(baos); 309 310 // we read the content 311 byte[] content = os.toByteArray(); 312 System.out.println("Content: " + new String(content)); 313 314 return baos.toByteArray(); 315 316 } else { 317 318 // get an InputStream for reading the signed content 319 InputStream data = signed_data.getInputStream(); 320 os = new ByteArrayOutputStream(); 321 Util.copyStream(data, os, null); 322 323 System.out.println("SignedData contains the following signer information:"); 324 SignerInfo[] signer_infos = signed_data.getSignerInfos(); 325 326 int numberOfSignerInfos = signer_infos.length; 327 if (numberOfSignerInfos == 0) { 328 String warning = "Warning: Unsigned message (no SignerInfo included)!"; 329 System.err.println(warning); 330 throw new CMSException(warning); 331 } else { 332 for (int i = 0; i < numberOfSignerInfos; i++) { 333 try { 334 // verify the signed data using the SignerInfo at index i 335 X509Certificate signer_cert = signed_data.verify(i); 336 // if the signature is OK the certificate of the signer is returned 337 System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN()); 338 // get signed attributes 339 SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime); 340 if (signingTime != null) { 341 System.out.println("This message has been signed at " + signingTime.get()); 342 } 343 CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType); 344 if (contentType != null) { 345 System.out.println("The content has CMS content type " + contentType.get().getName()); 346 } 347 348 } catch (SignatureException ex) { 349 // if the signature is not OK a SignatureException is thrown 350 System.out.println("Signature ERROR from signer: "+signed_data.getCertificate((signer_infos[i].getSignerIdentifier())).getSubjectDN()); 351 throw new CMSException(ex.toString()); 352 } 353 } 354 // now check alternative signature verification 355 System.out.println("Now check the signature assuming that no certs have been included:"); 356 try { 357 SignerInfo signer_info = signed_data.verify(user1_sign); 358 // if the signature is OK the certificate of the signer is returned 359 System.out.println("Signature OK from signer: "+signed_data.getCertificate(signer_info.getSignerIdentifier()).getSubjectDN()); 360 361 } catch (SignatureException ex) { 362 // if the signature is not OK a SignatureException is thrown 363 System.out.println("Signature ERROR from signer: "+user1_sign.getSubjectDN()); 364 throw new CMSException(ex.toString()); 365 } 366 367 try { 368 SignerInfo signer_info = signed_data.verify(user2_sign); 369 // if the signature is OK the certificate of the signer is returned 370 System.out.println("Signature OK from signer: "+signed_data.getCertificate(signer_info.getSignerIdentifier()).getSubjectDN()); 371 372 } catch (SignatureException ex) { 373 // if the signature is not OK a SignatureException is thrown 374 System.out.println("Signature ERROR from signer: "+user2_sign.getSubjectDN()); 375 throw new CMSException(ex.toString()); 376 } 377 // in practice we also would validate the signer certificate(s) 378 } 379 return os.toByteArray(); 380 } 381 382 } 383 384 /** 385 * Starts the test. 386 */ 387 public void start() { 388 389 try { 390 391 byte[] data; 392 byte[] received_message = null; 393 394 395 // 396 // test CMS Implicit SignedDataStream 397 // 398 System.out.println("\nImplicit SignedDataStream demo [create]:\n"); 399 data = createSignedDataStream(message, SignedDataStream.IMPLICIT); 400 // parse and encode again 401 System.out.println("\nImplicit SignedDataStream demo [write again]:\n"); 402 data = getSignedDataStream(data, null, true); 403 // parse 404 System.out.println("\nImplicit SignedDataStream demo [parse]:\n"); 405 received_message = getSignedDataStream(data, null, false); 406 System.out.print("\nSigned content: "); 407 System.out.println(new String(received_message)); 408 409 // 410 // test CMS Explicit SignedDataStream 411 // 412 System.out.println("\nExplicit SignedDataStream demo [create]:\n"); 413 data = createSignedDataStream(message, SignedDataStream.EXPLICIT); 414 // parse and encode again 415 System.out.println("\nExplicit SignedDataStream demo [write again]:\n"); 416 data = getSignedDataStream(data, message, true); 417 418 System.out.println("\nExplicit SignedDataStream demo [parse]:\n"); 419 received_message = getSignedDataStream(data, message, false); 420 System.out.print("\nSigned content: "); 421 System.out.println(new String(received_message)); 422 423 } catch (Exception ex) { 424 ex.printStackTrace(); 425 throw new RuntimeException(ex.toString()); 426 } 427 } 428 429 430 /** 431 * The main method. 432 * 433 * @throws IOException 434 * if an I/O error occurs when reading required keys 435 * and certificates from files 436 */ 437 public static void main(String argv[]) throws IOException { 438 try { 439 DemoUtil.initDemos(); 440 (new SignedDataStreamDemoWithAdditionalSignerInfo()).start(); 441 } catch (Exception ex) { 442 ex.printStackTrace(); 443 } 444 System.out.println("\nReady!"); 445 DemoUtil.waitKey(); 446 } 447 }