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/SignedDataOutputStreamDemo.java 17 12.02.25 17:58 Dbratko $ 059 // $Revision: 17 $ 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.asn1.structures.PolicyInformation; 068 import iaik.asn1.structures.PolicyQualifierInfo; 069 import iaik.cms.CMSException; 070 import iaik.cms.ContentInfoOutputStream; 071 import iaik.cms.IssuerAndSerialNumber; 072 import iaik.cms.SignedDataOutputStream; 073 import iaik.cms.SignedDataStream; 074 import iaik.cms.SignerInfo; 075 import iaik.cms.SubjectKeyID; 076 import iaik.cms.attributes.CMSContentType; 077 import iaik.cms.attributes.SigningTime; 078 import iaik.smime.ess.SigningCertificate; 079 import iaik.smime.ess.SigningCertificateV2; 080 import iaik.utils.Util; 081 import iaik.x509.X509Certificate; 082 import iaik.x509.X509ExtensionException; 083 import iaik.x509.attr.AttributeCertificate; 084 085 import java.io.ByteArrayInputStream; 086 import java.io.ByteArrayOutputStream; 087 import java.io.IOException; 088 import java.io.InputStream; 089 import java.security.NoSuchAlgorithmException; 090 import java.security.PrivateKey; 091 import java.security.SignatureException; 092 import java.security.cert.Certificate; 093 094 import demo.DemoUtil; 095 import demo.keystore.CMSKeyStore; 096 097 098 /** 099 * Demonstrates the usage of class {@link iaik.cms.SignedDataOutputStream} and 100 * {@link iaik.cms.SignedDataOutputStream} for signing some data using the CMS type 101 * SignedData. 102 */ 103 public class SignedDataOutputStreamDemo { 104 105 // certificate of user 1 106 X509Certificate user1; 107 // private key of user 1 108 PrivateKey user1_pk; 109 // certificate of user 2 110 X509Certificate user2; 111 // private key of user 2 112 PrivateKey user2_pk; 113 114 // a certificate chain containing the user certs + CA 115 Certificate[] certificates; 116 Certificate[] certs; 117 Certificate[] user1Certs; 118 119 // just for attribute certificate testing 120 PrivateKey issuer1_pk; 121 122 /** 123 * Setups the demo certificate chains. 124 * 125 * Keys and certificate are retrieved from the demo KeyStore. 126 * 127 * @throws IOException if an file read error occurs 128 */ 129 public SignedDataOutputStreamDemo() throws IOException { 130 131 System.out.println(); 132 System.out.println("**********************************************************************************"); 133 System.out.println("* SignedDataOutputStream demo *"); 134 System.out.println("* (shows the usage of the CMS SignedDataOutputStream implementation) *"); 135 System.out.println("**********************************************************************************"); 136 System.out.println(); 137 138 // add all certificates to the list 139 user1Certs = CMSKeyStore.getCertificateChain(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 140 user1 = (X509Certificate)user1Certs[0]; 141 user1_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.RSA, CMSKeyStore.SZ_2048_SIGN_1); 142 user2 = CMSKeyStore.getCertificateChain(CMSKeyStore.DSA, CMSKeyStore.SZ_1024_SIGN)[0]; 143 user2_pk = CMSKeyStore.getPrivateKey(CMSKeyStore.DSA, CMSKeyStore.SZ_1024_SIGN); 144 145 certs = user1Certs; 146 147 certificates = new Certificate[certs.length+1]; 148 System.arraycopy(certs, 0, certificates, 0, certs.length); 149 certificates[certs.length] = user2; 150 } 151 152 /** 153 * Creates and encodes a CMS <code>SignedData</code> object. 154 * <p> 155 * 156 * @param message the message to be signed, as byte representation 157 * @param mode the transmission mode, either IMPLICIT or EXPLICIT 158 * @return the BER encoding of the <code>SignedData</code> object just created, wrapped into a ContentInfo 159 * @throws CMSException if the <code>SignedData</code> object cannot 160 * be created 161 * @throws IOException if some stream I/O error occurs 162 */ 163 public byte[] createSignedDataStream(byte[] message, int mode) throws CMSException, IOException { 164 165 System.out.print("Create a new message signed by user 1 :"); 166 167 // a stream from which to read the data 168 ByteArrayInputStream is = new ByteArrayInputStream(message); 169 170 // the stream to which to write the SignedData 171 ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); 172 173 // wrap SignedData into a ContentInfo 174 ContentInfoOutputStream contentInfoStream = 175 new ContentInfoOutputStream(ObjectID.cms_signedData, resultStream); 176 SignedDataOutputStream signedData = new SignedDataOutputStream(contentInfoStream, mode); 177 178 // add the certificates 179 signedData.addCertificates(certificates); 180 181 // cert at index 0 is the user certificate 182 IssuerAndSerialNumber issuer = new IssuerAndSerialNumber(user1); 183 184 // create a new SignerInfo 185 SignerInfo signerInfo = new SignerInfo(issuer, (AlgorithmID)AlgorithmID.sha256.clone(), user1_pk); 186 // create some authenticated attributes 187 // the message digest attribute is automatically added 188 Attribute[] attributes = new Attribute[3]; 189 try { 190 // content type is data 191 CMSContentType contentType = new CMSContentType(ObjectID.cms_data); 192 attributes[0] = new Attribute(contentType); 193 // signing time is now 194 SigningTime signingTime = new SigningTime(); 195 attributes[1] = new Attribute(signingTime); 196 // signing certificate 197 SigningCertificateV2 signingCertificate = new SigningCertificateV2(certs, true); 198 String explicitText = "This certificate only may be used for test purposes"; 199 PolicyQualifierInfo policyQualifier = new PolicyQualifierInfo(null, null, explicitText); 200 PolicyInformation[] policyInformations = 201 { new PolicyInformation(new ObjectID("1.3.6.1.4.1.2706.17.0.11.1.1"), 202 new PolicyQualifierInfo[] { policyQualifier }) }; 203 signingCertificate.setPolicies(policyInformations); 204 System.out.println("Include signingCertificate attribute:"); 205 System.out.println(signingCertificate); 206 attributes[2] = new Attribute(signingCertificate); 207 } catch (Exception ex) { 208 throw new CMSException("Error creating attribute: " + ex.toString()); 209 } 210 // set the attributes 211 signerInfo.setSignedAttributes(attributes); 212 // finish the creation of SignerInfo by calling method addSigner 213 try { 214 signedData.addSignerInfo(signerInfo); 215 // another SignerInfo without signed attributes and SHA-256 as hash algorithm 216 signerInfo = new SignerInfo(new SubjectKeyID(user2), 217 (AlgorithmID)AlgorithmID.sha1.clone(), 218 (AlgorithmID)AlgorithmID.dsaWithSHA.clone(), 219 user2_pk); 220 221 // the message digest itself is protected 222 signedData.addSignerInfo(signerInfo); 223 224 } catch (NoSuchAlgorithmException ex) { 225 throw new CMSException(ex.toString()); 226 } catch (X509ExtensionException ex) { 227 throw new CMSException("Cannot create SubjectKeyID for user2 : " + ex.getMessage()); 228 } 229 230 int blockSize = 4; // in real world we would use a block size like 2048 231 // write in the data to be signed 232 byte[] buffer = new byte[blockSize]; 233 int bytesRead; 234 while ((bytesRead = is.read(buffer)) != -1) { 235 signedData.write(buffer, 0, bytesRead); 236 } 237 238 // closing the stream add the signer infos and closes the underlying stream 239 signedData.close(); 240 return resultStream.toByteArray(); 241 } 242 243 244 /** 245 * Parses a CMS <code>SignedData</code> object and verifies the signatures 246 * for all participated signers. 247 * 248 * @param signedData <code>SignedData</code> object (wrapped into a ContentInfo) as BER encoded byte array 249 * @param message the the message which was transmitted out-of-band (explicit signed) 250 * 251 * @return the inherent message as byte array 252 * @throws CMSException if any signature does not verify 253 * @throws IOException if some stream I/O error occurs 254 */ 255 public byte[] getSignedDataStream(byte[] signedData, byte[] message) throws CMSException, IOException { 256 257 // we are testing the stream interface 258 ByteArrayInputStream is = new ByteArrayInputStream(signedData); 259 // create the SignedData object 260 SignedDataStream signed_data = new SignedDataStream(is); 261 262 if (signed_data.getMode() == SignedDataStream.EXPLICIT) { 263 // in explicit mode explicitly supply the content for hash computation 264 signed_data.setInputStream(new ByteArrayInputStream(message)); 265 } 266 267 // get an InputStream for reading the signed content 268 InputStream data = signed_data.getInputStream(); 269 ByteArrayOutputStream os = new ByteArrayOutputStream(); 270 Util.copyStream(data, os, null); 271 272 System.out.println("SignedData contains the following signer information:"); 273 SignerInfo[] signer_infos = signed_data.getSignerInfos(); 274 275 int numberOfSignerInfos = signer_infos.length; 276 if (numberOfSignerInfos == 0) { 277 String warning = "Warning: Unsigned message (no SignerInfo included)!"; 278 System.err.println(warning); 279 throw new CMSException(warning); 280 } else { 281 for (int i = 0; i < numberOfSignerInfos; i++) { 282 283 try { 284 // verify the signed data using the SignerInfo at index i 285 X509Certificate signer_cert = signed_data.verify(i); 286 // if the signature is OK the certificate of the signer is returned 287 System.out.println("Signature OK from signer: "+signer_cert.getSubjectDN()); 288 SigningTime signingTime = (SigningTime)signer_infos[i].getSignedAttributeValue(ObjectID.signingTime); 289 if (signingTime != null) { 290 System.out.println("This message has been signed at " + signingTime.get()); 291 } 292 CMSContentType contentType = (CMSContentType)signer_infos[i].getSignedAttributeValue(ObjectID.contentType); 293 if (contentType != null) { 294 System.out.println("The content has CMS content type " + contentType.get().getName()); 295 } 296 // check SigningCertificate attribute 297 try { 298 SigningCertificateV2 signingCertificate = signer_infos[i].getSigningCertificateV2Attribute(); 299 if (signingCertificate != null) { 300 checkSigningCertificate(signingCertificate, signer_cert, signed_data, i); 301 } 302 } catch (CMSException ex) { 303 throw new CMSException("Error parsing SigningCertificate attribute: " + ex.getMessage()); 304 } 305 306 } catch (SignatureException ex) { 307 // if the signature is not OK a SignatureException is thrown 308 System.out.println("Signature ERROR from signer: "+signed_data.getCertificate(signer_infos[i].getSignerIdentifier()).getSubjectDN()); 309 throw new CMSException(ex.toString()); 310 } 311 } 312 313 // now check alternative signature verification 314 System.out.println("Now check the signature assuming that no certs have been included:"); 315 try { 316 SignerInfo signer_info = signed_data.verify(user1); 317 // if the signature is OK the certificate of the signer is returned 318 System.out.println("Signature OK from signer: "+user1.getSubjectDN()); 319 320 } catch (SignatureException ex) { 321 // if the signature is not OK a SignatureException is thrown 322 System.out.println("Signature ERROR from signer: "+user1.getSubjectDN()); 323 throw new CMSException(ex.toString()); 324 } 325 326 System.out.println("Included attribute certificates:"); 327 AttributeCertificate[] attributeCerts = signed_data.getAttributeCertificates(); 328 if (attributeCerts == null) { 329 System.out.println("No attribute certificates"); 330 } else { 331 for (int i = 0; i < attributeCerts.length; i++) { 332 System.out.println(attributeCerts[i].getHolder()); 333 } 334 } 335 336 try { 337 SignerInfo signer_info = signed_data.verify(user2); 338 // if the signature is OK the certificate of the signer is returned 339 System.out.println("Signature OK from signer: "+signed_data.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: "+user2.getSubjectDN()); 344 throw new CMSException(ex.toString()); 345 } 346 // in practice we also would validate the signer certificate(s) 347 } 348 349 return os.toByteArray(); 350 } 351 352 353 /** 354 * Checks the SigningCertificate attribute. 355 * 356 * @param signingCertificate the SigningCertificate attribute 357 * @param signerCert the certificate of the signer 358 * @param signedData the SignedData containing the SignerInfo with the SigningCertificate 359 * attribute to be checked 360 * @param signerInfoIndex the index of the SignerInfo with the SigningCertificate 361 * attribute to be checked 362 * 363 * @throws CMSException if the SigningCertificate check fails 364 */ 365 private void checkSigningCertificate(SigningCertificate signingCertificate, 366 X509Certificate signerCert, 367 SignedDataStream signedData, 368 int signerInfoIndex) throws CMSException { 369 if (signedData.getSignerInfos()[signerInfoIndex].isSignerCertificate(signerCert) == false) { 370 throw new CMSException("Cert ERROR!!! The certificate used for signing is not the one " + 371 "identified by the SignerCertificate attribute!"); 372 } else { 373 System.out.println("SigningCertificate attribute: Signer cert ok!"); 374 } 375 if (signingCertificate != null) { 376 // get the authorization certs for this signerInfo 377 Certificate[] authCerts = 378 signingCertificate.getAuthorizedCertificates(signedData.getCertificates()); 379 if (authCerts != null) { 380 System.out.println("SignedData contains the following authorization certs for SignerInfo No " + (signerInfoIndex+1) +":"); 381 for (int j = 0; j < authCerts.length; j++) { 382 if (authCerts[j].getType().equalsIgnoreCase("X.509")) { 383 System.out.println("X.509 public key cert: " + ((X509Certificate)authCerts[j]).getSubjectDN()); 384 } else { 385 System.out.println("X.509 attribute cert: " + ((AttributeCertificate)authCerts[j]).getHolder()); 386 } 387 } 388 } 389 if (signingCertificate.countPolicies() > 0) { 390 // get the certs with PolicyInformations according to the SigningCertificate attribute: 391 Certificate[] policyCerts = 392 signingCertificate.getPolicyInformationCerts(signedData.getCertificates()); 393 if (policyCerts != null) { 394 System.out.println("SignedData contains the following certs corresponding to policy informations of SignerInfo No " 395 + (signerInfoIndex+1) +":"); 396 for (int j = 0; j < policyCerts.length; j++) { 397 if (policyCerts[j].getType().equalsIgnoreCase("X.509")) { 398 System.out.println("X.509 public key cert: " + ((X509Certificate)policyCerts[j]).getSubjectDN()); 399 } else { 400 System.out.println("X.509 attribute cert: " + ((AttributeCertificate)policyCerts[j]).getHolder()); 401 } 402 } 403 } 404 } 405 } 406 407 } 408 409 410 /** 411 * Demonstrates the CMS SignedDataOutputStream implementation. 412 */ 413 public void start() { 414 // the test message 415 String m = "This is the test message."; 416 System.out.println("Test message: \""+m+"\""); 417 System.out.println(); 418 byte[] message = m.getBytes(); 419 420 try { 421 byte[] encoding; 422 byte[] received_message = null; 423 System.out.println("Stream implementation demos"); 424 System.out.println("==========================="); 425 // 426 // test CMS Implicit SignedDataOutputStream 427 // 428 System.out.println("\nImplicit SignedDataOutputStream demo [create]:\n"); 429 encoding = createSignedDataStream(message, SignedDataOutputStream.IMPLICIT); 430 // transmit data 431 System.out.println("\nImplicit SignedDataStream demo [parse]:\n"); 432 received_message = getSignedDataStream(encoding, null); 433 System.out.print("\nSigned content: "); 434 System.out.println(new String(received_message)); 435 436 // 437 // test CMS Explicit SignedDataOutputStream 438 // 439 System.out.println("\nExplicit SignedDataOutputStream demo [create]:\n"); 440 encoding = createSignedDataStream(message, SignedDataOutputStream.EXPLICIT); 441 // transmit data 442 System.out.println("\nExplicit SignedDataStream demo [parse]:\n"); 443 received_message = getSignedDataStream(encoding, message); 444 System.out.print("\nSigned content: "); 445 System.out.println(new String(received_message)); 446 447 } catch (Exception ex) { 448 ex.printStackTrace(); 449 throw new RuntimeException(ex.toString()); 450 } 451 } 452 453 /** 454 * The main method. 455 * 456 * @throws IOException 457 * if an I/O error occurs when reading required keys 458 * and certificates from files 459 */ 460 public static void main(String argv[]) throws Exception { 461 462 DemoUtil.initDemos(); 463 (new SignedDataOutputStreamDemo()).start(); 464 System.out.println("\nReady!"); 465 DemoUtil.waitKey(); 466 } 467 }