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