/* * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.] * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.opensaml.security; import jargs.gnu.CmdLineParser; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URL; import java.security.KeyStore; import java.security.cert.Certificate; import org.opensaml.Configuration; import org.opensaml.DefaultBootstrap; import org.opensaml.common.SignableSAMLObject; import org.opensaml.xml.XMLObject; import org.opensaml.xml.XMLObjectBuilder; import org.opensaml.xml.io.Unmarshaller; import org.opensaml.xml.io.UnmarshallingException; import org.opensaml.xml.parse.ParserPool; import org.opensaml.xml.parse.XMLParserException; import org.opensaml.xml.security.SecurityHelper; import org.opensaml.xml.security.credential.Credential; import org.opensaml.xml.signature.Signature; import org.opensaml.xml.signature.SignatureException; import org.opensaml.xml.signature.Signer; import org.opensaml.xml.util.DatatypeHelper; import org.opensaml.xml.util.XMLHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * A tool for retrieving, verifying, and signing metadata. */ public class MetadataTool { /** Class Logger. */ private static Logger log = LoggerFactory.getLogger(MetadataTool.class); private static ParserPool parser; /** * Main entry point to program. * * @param args command line arguments * * @throws Exception thrown if there is a problem running the application */ public static void main(String[] args) throws Exception { DefaultBootstrap.bootstrap(); CmdLineParser parser = CLIParserBuilder.buildParser(); try { parser.parse(args); } catch (CmdLineParser.OptionException e) { errorAndExit(e.getMessage(), e); } Boolean helpEnabled = (Boolean) parser.getOptionValue(CLIParserBuilder.HELP_ARG); if (helpEnabled != null) { printHelp(System.out); System.out.flush(); System.exit(0); } Boolean verify = (Boolean) parser.getOptionValue(CLIParserBuilder.VALIDATE_ARG); String inputFile = (String) parser.getOptionValue(CLIParserBuilder.INPUT_FILE_ARG); SignableSAMLObject metadata = (SignableSAMLObject) fetchMetadata(inputFile, verify); String keystorePath = (String) parser.getOptionValue(CLIParserBuilder.KEYSTORE_ARG); String storeType = (String) parser.getOptionValue(CLIParserBuilder.KEYSTORE_TYPE_ARG); String storePass = (String) parser.getOptionValue(CLIParserBuilder.KEYSTORE_PASS_ARG); String alias = (String) parser.getOptionValue(CLIParserBuilder.ALIAS_ARG); String keyPass = (String) parser.getOptionValue(CLIParserBuilder.KEY_PASS_ARG); Boolean sign = (Boolean) parser.getOptionValue(CLIParserBuilder.SIGN_ARG); if (sign != null && sign.booleanValue()) { KeyStore keystore = getKeyStore(keystorePath, storeType, storePass); Credential signingCredential = getSigningCredential(keystore, alias, keyPass); sign(metadata, signingCredential); } else { if (keystorePath != null) { KeyStore keystore = getKeyStore(keystorePath, storeType, storePass); Credential verificationCredential = getVerificationCredential(keystore, alias); verifySignature(metadata, verificationCredential); } } String outputFile = (String) parser.getOptionValue(CLIParserBuilder.OUTPUT_FILE_ARG); printMetadata(metadata, outputFile); } /** * Fetches metadata from either the given input file or URL. * * @param inputFile the filesystem path to the metadata file. * @param inputURL the URL to the metadata file * * @return the metadata */ private static XMLObject fetchMetadata(String inputFile, Boolean validate) { if (DatatypeHelper.isEmpty(inputFile)) { errorAndExit("No input file was specified.", null); } try { log.debug("Fetching metadata from input " + inputFile); URL inputURL = new URL(inputFile); Document metadatDocument = parser.parse(inputURL.openStream()); // if (validate != null && validate.booleanValue()) { // parser.validate(metadatDocument); // log.info("Metadata document passed validation"); // } Element metadataRoot = metadatDocument.getDocumentElement(); Unmarshaller unmarshaller = Configuration.getUnmarshallerFactory().getUnmarshaller(metadataRoot); return unmarshaller.unmarshall(metadataRoot); } catch (MalformedURLException e) { errorAndExit("Input file/url was not properly formed", e); } catch (XMLParserException e) { errorAndExit("Unable to parse and validate metadata document", e); } catch (IOException e) { errorAndExit("Unable to read input file/url", e); } catch (UnmarshallingException e) { errorAndExit("Unable to unmarshall metadata", e); } return null; } /** * Gets the Java keystore. * * @param keyStore path to the keystore * @param storeType keystore type * @param storePass keystore password * * @return the keystore */ private static KeyStore getKeyStore(String keyStore, String storeType, String storePass) { try { FileInputStream keyStoreIn = new FileInputStream(keyStore); KeyStore ks = KeyStore.getInstance(storeType); storePass = DatatypeHelper.safeTrimOrNullString(storePass); if (storePass != null) { ks.load(keyStoreIn, storePass.toCharArray()); return ks; } else { log.error("No password provided for keystore"); System.exit(1); } } catch (Exception e) { log.error("Unable to load keystore from file " + keyStore, e); System.exit(1); } return null; } /** * Gets the signing credential from the keystore. * * @param keystore keystore to fetch the key from * @param alias the key alias * @param keyPass password for the key * * @return the signing credential or null */ private static Credential getSigningCredential(KeyStore keystore, String alias, String keyPass) { alias = DatatypeHelper.safeTrimOrNullString(alias); if (alias == null) { log.error("Key alias may not be null or empty"); System.exit(1); } keyPass = DatatypeHelper.safeTrimOrNullString(keyPass); if (keyPass == null) { log.error("Private key password may not be null or empty"); System.exit(1); } KeyStore.PasswordProtection keyPassParam = new KeyStore.PasswordProtection(keyPass.toCharArray()); try { KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) keystore.getEntry(alias, keyPassParam); return SecurityHelper.getSimpleCredential(pkEntry.getCertificate().getPublicKey(), pkEntry.getPrivateKey()); } catch (Exception e) { log.error("Unable to retrieve private key " + alias, e); } return null; } /** * Gets a simple credential containing the public key associated with the named certificate. * * @param keystore the keystore from which to get the key * @param alias the name of the certificate from which to get the key * * @return a simple credential containing the public key or null */ private static Credential getVerificationCredential(KeyStore keystore, String alias) { alias = DatatypeHelper.safeTrimOrNullString(alias); if (alias == null) { log.error("Key alias may not be null or empty"); System.exit(1); } try { Certificate cert = keystore.getCertificate(alias); return SecurityHelper.getSimpleCredential(cert.getPublicKey(), null); } catch (Exception e) { log.error("Unable to retrieve certificate " + alias, e); System.exit(1); } return null; } /** * Signs the given metadata document root. * * @param metadata metadata document * @param signingCredential credential used to sign the document */ private static void sign(SignableSAMLObject metadata, Credential signingCredential) { XMLObjectBuilder<Signature> sigBuilder = Configuration.getBuilderFactory().getBuilder( Signature.DEFAULT_ELEMENT_NAME); Signature signature = sigBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME); signature.setSigningCredential(signingCredential); metadata.setSignature(signature); try { Signer.signObject(signature); } catch (SignatureException e) { log.error("Error when attempting to sign object", e); System.exit(1); } } /** * Verifies the signatures of the metadata document. * * @param metadata the metadata document * @param verificationCredential the credential to use to verify it */ private static void verifySignature(SignableSAMLObject metadata, Credential verificationCredential) { // TODO use new trust engine to verify signature } /** * Writes the given metadata out. * * @param metadata the metadata to write * @param outputFile the file to write the metadata to, or null for STDOUT */ private static void printMetadata(XMLObject metadata, String outputFile) { PrintStream out = System.out; if (outputFile != null) { try { out = new PrintStream(new File(outputFile)); } catch (Exception e) { errorAndExit("Unable to open output file for writing", e); } } try { if (!DatatypeHelper.isEmpty(outputFile)) { File outFile = new File(outputFile); outFile.createNewFile(); out = new PrintStream(new File(outputFile)); } } catch (Exception e) { log.error("Unable to write to output file", e); } out.print(XMLHelper.nodeToString(metadata.getDOM())); } /** * Prints a help message to the given output stream. * * @param out output to print the help message to */ private static void printHelp(PrintStream out) { out.println("usage: java org.opensaml.security.MetadataTool"); out.println(); out.println("when retrieving:"); out.println(" --input <fileOrUrl> [--ouput <outfile>]"); out.println("when signing:"); out.println(" --input <fileOrUrl> --sign --keystore <keystore> [--storetype <storetype>] " + "--storepass <password> --alias <alias> [--keypass <password>] [--output <outfile>]"); out.println("when retrieving and verifying signature:"); out.println(" --input <fileOrUrl> --validate --keystore <keystore> [--storetype <storetype>] " + "--storepass <password> --alias <alias> [--output <outfile>]"); out.println(); out.println(); out.println(String.format(" --%-16s %s", CLIParserBuilder.HELP, "print this message")); out.println(String.format(" --%-16s %s", CLIParserBuilder.VALIDATE, "validate the digital signature on the metadata if it is signed")); out.println(String.format(" --%-16s %s", CLIParserBuilder.SIGN, "sign the input file and write out a signed version")); out.println(String.format(" --%-16s %s", CLIParserBuilder.INPUT_FILE, "filesystem path or URL to fetch metadata from")); out.println(String.format(" --%-16s %s", CLIParserBuilder.KEYSTORE, "filesystem path to Java keystore")); out.println(String.format(" --%-16s %s", CLIParserBuilder.KEYSTORE_TYPE, "the keystore type (default: JKS)")); out.println(String.format(" --%-16s %s", CLIParserBuilder.KEYSTORE_PASS, "keystore password")); out.println(String.format(" --%-16s %s", CLIParserBuilder.ALIAS, "alias of signing or verification key")); out.println(String.format(" --%-16s %s", CLIParserBuilder.KEY_PASS, "private key password")); out.println(String.format(" --%-16s %s", CLIParserBuilder.OUTPUT_FILE, "filesystem path where metadata will be written")); out.println(); } /** * Logs, as an error, the error message and exits the program. * * @param errorMessage error message * @param e exception that caused it */ private static void errorAndExit(String errorMessage, Exception e) { if (e == null) { log.error(errorMessage); } else { log.error(errorMessage, e); } printHelp(System.out); System.out.flush(); System.exit(1); } /** * Helper class that creates the command line argument parser. */ private static class CLIParserBuilder { // Command line arguments public static final String HELP = "help"; public static final String SIGN = "sign"; public static final String VALIDATE = "validate"; public static final String INPUT_FILE = "input"; public static final String KEYSTORE = "keystore"; public static final String KEYSTORE_TYPE = "storetype"; public static final String KEYSTORE_PASS = "storepass"; public static final String ALIAS = "alias"; public static final String KEY_PASS = "keypass"; public static final String OUTPUT_FILE = "output"; // Command line parser arguments public static CmdLineParser.Option HELP_ARG; public static CmdLineParser.Option SIGN_ARG; public static CmdLineParser.Option VALIDATE_ARG; public static CmdLineParser.Option INPUT_FILE_ARG; public static CmdLineParser.Option KEYSTORE_ARG; public static CmdLineParser.Option KEYSTORE_TYPE_ARG; public static CmdLineParser.Option KEYSTORE_PASS_ARG; public static CmdLineParser.Option ALIAS_ARG; public static CmdLineParser.Option KEY_PASS_ARG; public static CmdLineParser.Option OUTPUT_FILE_ARG; /** * Create a new command line parser. * * @return command line parser */ public static CmdLineParser buildParser() { CmdLineParser parser = new CmdLineParser(); HELP_ARG = parser.addBooleanOption(HELP); SIGN_ARG = parser.addBooleanOption(SIGN); VALIDATE_ARG = parser.addBooleanOption(VALIDATE); INPUT_FILE_ARG = parser.addStringOption(INPUT_FILE); KEYSTORE_ARG = parser.addStringOption(KEYSTORE); KEYSTORE_TYPE_ARG = parser.addStringOption(KEYSTORE_TYPE); KEYSTORE_PASS_ARG = parser.addStringOption(KEYSTORE_PASS); ALIAS_ARG = parser.addStringOption(ALIAS); KEY_PASS_ARG = parser.addStringOption(KEY_PASS); OUTPUT_FILE_ARG = parser.addStringOption(OUTPUT_FILE); return parser; } } }