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