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