Java tutorial
/* * Copyright (c) 2008-2012, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.common.tools; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.security.AlgorithmParameters; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.PrivateKey; import java.security.cert.CertSelector; import java.security.cert.CertificateParsingException; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.List; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import jline.ConsoleReader; import mitm.common.mail.MailUtils; import mitm.common.security.SecurityFactory; import mitm.common.security.SecurityFactoryFactory; import mitm.common.security.asn1.ASN1Utils; import mitm.common.security.bouncycastle.InitializeBouncycastle; import mitm.common.security.certificate.CertificateUtils; import mitm.common.security.certificate.X509CertificateInspector; import mitm.common.security.cms.CryptoMessageSyntaxException; import mitm.common.security.cms.KeyNotFoundException; import mitm.common.security.cms.RecipientInfo; import mitm.common.security.cms.SignerInfo; import mitm.common.security.cms.SignerInfoException; import mitm.common.security.keystore.BasicKeyStore; import mitm.common.security.keystore.KeyStoreKeyProvider; import mitm.common.security.smime.SMIMEBuilder; import mitm.common.security.smime.SMIMEBuilderImpl; import mitm.common.security.smime.SMIMECapabilitiesInspector; import mitm.common.security.smime.SMIMECapabilityInfo; import mitm.common.security.smime.SMIMECompressedInspector; import mitm.common.security.smime.SMIMEEncryptionAlgorithm; import mitm.common.security.smime.SMIMEEnvelopedInspector; import mitm.common.security.smime.SMIMEInspector; import mitm.common.security.smime.SMIMEInspectorImpl; import mitm.common.security.smime.SMIMESignMode; import mitm.common.security.smime.SMIMESignedInspector; import mitm.common.security.smime.SMIMESigningAlgorithm; import mitm.common.security.smime.SMIMEType; import mitm.common.util.MiscStringUtils; import org.apache.commons.cli.BasicParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.MissingArgumentException; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.PropertyConfigurator; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.mail.smime.SMIMEException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SMIME { private static final Logger logger = LoggerFactory.getLogger(SMIME.class); private static SecurityFactory securityFactory; @SuppressWarnings("static-access") private static Options createCommandLineOptions() { Options options = new Options(); Option inOption = OptionBuilder.withArgName("in").hasArg().withDescription("input file").create("in"); inOption.setRequired(false); options.addOption(inOption); Option keyStoreOption = OptionBuilder.withArgName("p12").hasArg().withDescription("keystore (.p12/.pfx)") .create("p12"); keyStoreOption.setRequired(false); options.addOption(keyStoreOption); Option passwordOption = OptionBuilder.withArgName("password").hasArg().withDescription("password") .create("password"); passwordOption.setRequired(false); options.addOption(passwordOption); Option passwordPromptOption = OptionBuilder.withDescription("ask for password").create("pwd"); passwordPromptOption.setRequired(false); options.addOption(passwordPromptOption); Option helpOption = OptionBuilder.withDescription("Show help").create("help"); helpOption.setRequired(false); options.addOption(helpOption); Option p7mOption = OptionBuilder.withDescription("file is a p7m file").create("p7m"); p7mOption.setRequired(false); options.addOption(p7mOption); Option p7mOutputOption = OptionBuilder.withArgName("p7m output file").hasArg() .withDescription("save the p7m as S/MIME to p7mOut").create("p7mOut"); p7mOutputOption.setRequired(false); options.addOption(p7mOutputOption); Option binaryOption = OptionBuilder.withDescription("file is binary").create("binary"); binaryOption.setRequired(false); options.addOption(binaryOption); Option cerOutputOption = OptionBuilder.withArgName("cer output file").hasArg() .withDescription("saves the certificates from the signed blob to this file in p7b format.") .create("cerOut"); cerOutputOption.setRequired(false); options.addOption(cerOutputOption); Option readOption = OptionBuilder.withDescription("read S/MIME file").create("r"); readOption.setRequired(false); options.addOption(readOption); Option signOption = OptionBuilder.withDescription("sign file").create("sign"); signOption.setRequired(false); options.addOption(signOption); Option keyAliasOption = OptionBuilder.withArgName("alias").hasArg().withDescription("key alias") .create("alias"); keyAliasOption.setRequired(false); options.addOption(keyAliasOption); Option printAliasesOption = OptionBuilder.withDescription("print key aliases").create("printAliases"); printAliasesOption.setRequired(false); options.addOption(printAliasesOption); Option digestOption = OptionBuilder.withDescription("hash algorithm").hasArg() .withDescription("digest hash algorithm").create("digest"); digestOption.setRequired(false); options.addOption(digestOption); Option outOption = OptionBuilder.withDescription("output file").hasArg().withDescription("output file") .create("out"); outOption.setRequired(false); options.addOption(outOption); return options; } private static MimeMessage loadMessage(String filename) throws FileNotFoundException, MessagingException { File mail = new File(filename); mail = mail.getAbsoluteFile(); MimeMessage message = MailUtils.loadMessage(mail); return message; } private static MimeMessage loadp7m(String filename, boolean binary) throws Exception { File p7mFile = new File(filename); p7mFile = p7mFile.getAbsoluteFile(); FileInputStream fis = new FileInputStream(p7mFile); ByteArrayOutputStream bos = new ByteArrayOutputStream(); OutputStreamWriter sw = new OutputStreamWriter(bos); sw.append("Content-Type: application/pkcs7-mime; name=\"smime.p7m\"\r\n"); sw.append("Content-Transfer-Encoding: base64\r\n"); sw.append("\r\n\r\n"); byte[] content = IOUtils.toByteArray(fis); if (binary) { content = Base64.encodeBase64Chunked(content); } String base64Content = MiscStringUtils.toAsciiString(content); sw.append(base64Content); sw.flush(); MimeMessage message = MailUtils.byteArrayToMessage(bos.toByteArray()); return message; } private static KeyStore loadKeyStore(String keyFile, String password) throws Exception { File file = new File(keyFile); file = file.getAbsoluteFile(); KeyStore keyStore = securityFactory.createKeyStore("PKCS12"); /* initialize key store */ keyStore.load(new FileInputStream(file), password != null ? password.toCharArray() : null); return keyStore; } private static void inspectEnveloped(SMIMEEnvelopedInspector inspector) throws Exception { System.err.println("=============================="); System.err.println("Encrypted message"); System.err.println(); String algorithmName = inspector.getEncryptionAlgorithmOID(); SMIMEEncryptionAlgorithm encryptionAlg = SMIMEEncryptionAlgorithm.fromOID(algorithmName); if (encryptionAlg != null) { algorithmName = encryptionAlg.toString(); } System.err.print("Encryption Algorithm: " + algorithmName); if (encryptionAlg != null) { AlgorithmParameters parameters = inspector.getEncryptionAlgorithmParameters(); System.err.print(", Key size: " + SMIMEEncryptionAlgorithm.getKeySize(encryptionAlg, parameters)); } System.err.println(); System.err.println(); List<RecipientInfo> recipients = inspector.getRecipients(); for (int i = 0; i < recipients.size(); i++) { RecipientInfo recipient = recipients.get(i); System.err.println("*** Recipient " + i); System.err.println(); System.err.println(recipient.toString()); System.err.println(); } System.err.println(); System.err.println("Unprotected attributes:"); System.err.println(ASN1Utils.dump(inspector.getUnprotectedAttributes())); System.err.println("*** Decrypted message:"); System.err.println(); try { inspector.getContentAsMimeMessage().writeTo(System.out); } catch (KeyNotFoundException e) { System.err.println("Decryption key could not be found."); } } private static void dumpSMIMECapabilities(AttributeTable attributes) { List<SMIMECapabilityInfo> capabilities = SMIMECapabilitiesInspector.inspect(attributes); System.err.println("SMIME capabilities"); System.err.println(); for (SMIMECapabilityInfo capabilityInfo : capabilities) { System.err.println(capabilityInfo); } System.err.println(); } private static void inspectSigned(SMIMESignedInspector inspector, String cerOut) throws Exception { System.err.println("=============================="); System.err.println("Signed message"); System.err.println(); System.err.println("CMSVersion: " + inspector.getVersion()); System.err.println(); List<SignerInfo> signers = inspector.getSigners(); List<X509Certificate> certificates = inspector.getCertificates(); for (int i = 0; i < signers.size(); i++) { SignerInfo signer = signers.get(i); System.err.println("*** [Signer " + i + "] ***"); System.err.println(); System.err.println(signer); System.err.println(); dumpSMIMECapabilities(signer.getSignedAttributes()); CertSelector selector = signer.getSignerId().getSelector(); List<X509Certificate> signingCerts = CertificateUtils.getMatchingCertificates(certificates, selector); if (signingCerts.size() > 0) { /* there could be more certificates but get the first one */ X509Certificate certificate = signingCerts.get(0); try { if (signer.verify(certificate.getPublicKey())) { System.err.println("Verification OK."); } } catch (SignerInfoException e) { System.err.println("* WARNING: verification failed. Message: " + e.getMessage()); } } else { System.err.println("* WARNING: Signing certificate not found so unable to verify signature *"); } } System.err.println("=============================="); System.err.println("Certificates:"); System.err.println(); for (int i = 0; i < certificates.size(); i++) { X509Certificate certificate = certificates.get(i); System.err.println("*** Certificate " + i); System.err.println(); System.err.println(certificate); System.err.println(); try { System.err.println("Extra information:"); System.err.println(); X509CertificateInspector certInspector = new X509CertificateInspector(certificate); System.err.println("SubjectKeyIdentifier: " + certInspector.getSubjectKeyIdentifierHex()); System.err.println("Email: " + certInspector.getEmail()); System.err.println(); } catch (CertificateParsingException e) { logger.error("Error while parsing the certificate", e); } } if (cerOut != null) { File outfile = new File(cerOut); CertificateUtils.writeCertificates(certificates, new FileOutputStream(outfile)); } List<X509CRL> crls = inspector.getCRLs(); System.err.println("=============================="); System.err.println("CRLs:"); System.err.println(); for (int i = 0; i < crls.size(); i++) { X509CRL crl = crls.get(i); System.err.println("*** CRL " + i); System.err.println(); System.err.println(crl); System.err.println(); } System.err.println("*** Unsigned message:"); System.err.println(); inspector.getContentAsMimeMessage().writeTo(System.out); } private static void inspectCompressed(SMIMECompressedInspector inspector) throws Exception { System.err.println("=============================="); System.err.println("Compressed message"); System.err.println(); System.err.println("Decompressed message:"); System.err.println(); inspector.getContentAsMimeMessage().writeTo(System.out); } private static void inspectMessage(MimeMessage message, BasicKeyStore basicKeyStore, String cerOut) throws Exception { SMIMEInspector inspector = new SMIMEInspectorImpl(message, basicKeyStore, securityFactory.getNonSensitiveProvider(), securityFactory.getSensitiveProvider()); if (inspector.getSMIMEType() == SMIMEType.NONE) { System.err.println("Message is not a S/MIME message."); return; } switch (inspector.getSMIMEType()) { case ENCRYPTED: inspectEnveloped(inspector.getEnvelopedInspector()); break; case SIGNED: inspectSigned(inspector.getSignedInspector(), cerOut); break; case COMPRESSED: inspectCompressed(inspector.getCompressedInspector()); break; default: break; } return; } private static void printKeystoreAliases(KeyStore keyStore) throws KeyStoreException { Enumeration<String> aliases = keyStore.aliases(); System.out.println("**** BEGIN KEY ALIASES ***"); while (aliases.hasMoreElements()) { System.out.println(aliases.nextElement()); } System.out.println("**** END KEY ALIASES ***"); } private static void sign(MimeMessage source, KeyStore keyStore, String alias, String password, String digestAlgo, String outFile) throws Exception { if (StringUtils.isEmpty(alias)) { throw new MissingArgumentException("alias is missing."); } KeyStore.Entry entry = keyStore.getEntry(alias, new KeyStore.PasswordProtection(password.toCharArray())); if (!(entry instanceof KeyStore.PrivateKeyEntry)) { throw new KeyStoreException("Key is not a PrivateKeyEntry."); } KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry; X509Certificate certificate = (X509Certificate) privateKeyEntry.getCertificate(); PrivateKey key = privateKeyEntry.getPrivateKey(); if (certificate == null) { throw new KeyStoreException("Entry does not have a certificate."); } if (key == null) { throw new KeyStoreException("Entry does not have a private key."); } SMIMESigningAlgorithm signingAlgorithm; if (StringUtils.isNotEmpty(digestAlgo)) { signingAlgorithm = SMIMESigningAlgorithm.fromName(digestAlgo); if (signingAlgorithm == null) { throw new IllegalArgumentException(digestAlgo + " is not a valid digest."); } } else { signingAlgorithm = SMIMESigningAlgorithm.SHA1WITHRSA; } SMIMEBuilder builder = new SMIMEBuilderImpl(source); builder.addCertificates(certificate); builder.addSigner(key, certificate, signingAlgorithm); builder.sign(SMIMESignMode.CLEAR); MimeMessage signed = builder.buildMessage(); if (signed == null) { throw new SMIMEException("Message could not be signed"); } MailUtils.writeMessage(signed, new File(outFile)); } /** * @param args * @throws CryptoMessageSyntaxException */ public static void main(String[] args) { try { PropertyConfigurator.configure("conf/tools.log4j.properties"); InitializeBouncycastle.initialize(); securityFactory = SecurityFactoryFactory.getSecurityFactory(); CommandLineParser parser = new BasicParser(); Options options = createCommandLineOptions(); HelpFormatter formatter = new HelpFormatter(); CommandLine commandLine; try { commandLine = parser.parse(options, args); } catch (ParseException e) { formatter.printHelp("SMIME", options, true); throw e; } String inFile = commandLine.getOptionValue("in"); String keyFile = commandLine.getOptionValue("p12"); String password = commandLine.getOptionValue("password"); boolean binary = commandLine.hasOption("binary"); String p7mOut = commandLine.getOptionValue("p7mOut"); String cerOut = commandLine.getOptionValue("cerOut"); String alias = commandLine.getOptionValue("alias"); String digest = commandLine.getOptionValue("digest"); String outFile = commandLine.getOptionValue("out"); if (commandLine.hasOption("help") || args == null || args.length == 0) { formatter.printHelp("SMIME", options, true); return; } if (commandLine.hasOption("pwd")) { System.err.println("Please enter your password: "); /* * We will redirect the output to err so we do not get any * chars on the output. */ ConsoleReader consoleReader = new ConsoleReader(new FileInputStream(FileDescriptor.in), new PrintWriter(System.err)); password = consoleReader.readLine(new Character('*')); } KeyStore keyStore = null; if (keyFile != null) { keyStore = loadKeyStore(keyFile, password); } if (commandLine.hasOption("printAliases")) { if (keyStore == null) { throw new MissingArgumentException("p12 file is missing."); } printKeystoreAliases(keyStore); } MimeMessage message; if (commandLine.hasOption("r")) { if (commandLine.hasOption("p7m")) { message = loadp7m(inFile, binary); if (commandLine.hasOption("p7mOut")) { MailUtils.writeMessage(message, new File(p7mOut)); } } else { message = loadMessage(inFile); } KeyStoreKeyProvider basicKeyStore = null; if (keyStore != null) { basicKeyStore = new KeyStoreKeyProvider(keyStore, "test"); basicKeyStore.setUseOL2010Workaround(true); } if (message == null) { throw new MissingArgumentException("in file is not specified"); } inspectMessage(message, basicKeyStore, cerOut); } else if (commandLine.hasOption("sign")) { message = loadMessage(inFile); if (message == null) { throw new MissingArgumentException("in file is not specified"); } if (StringUtils.isEmpty(outFile)) { throw new MissingArgumentException("out file is not specified"); } sign(message, keyStore, alias, password, digest, outFile); } } catch (MissingArgumentException e) { System.err.println("Not all required parameters are specified. " + e); } catch (ParseException e) { System.err.println("Command line parsing error. " + e); } catch (Exception e) { logger.error("Some error ocurred", e); } } }