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