Oops. Forgot to update the usage message.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / utils / MetadataTool.java
1 /* 
2  * The Shibboleth License, Version 1. 
3  * Copyright (c) 2002 
4  * University Corporation for Advanced Internet Development, Inc. 
5  * All rights reserved
6  * 
7  * 
8  * Redistribution and use in source and binary forms, with or without 
9  * modification, are permitted provided that the following conditions are met:
10  * 
11  * Redistributions of source code must retain the above copyright notice, this 
12  * list of conditions and the following disclaimer.
13  * 
14  * Redistributions in binary form must reproduce the above copyright notice, 
15  * this list of conditions and the following disclaimer in the documentation 
16  * and/or other materials provided with the distribution, if any, must include 
17  * the following acknowledgment: "This product includes software developed by 
18  * the University Corporation for Advanced Internet Development 
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement 
20  * may appear in the software itself, if and wherever such third-party 
21  * acknowledgments normally appear.
22  * 
23  * Neither the name of Shibboleth nor the names of its contributors, nor 
24  * Internet2, nor the University Corporation for Advanced Internet Development, 
25  * Inc., nor UCAID may be used to endorse or promote products derived from this 
26  * software without specific prior written permission. For written permission, 
27  * please contact shibboleth@shibboleth.org
28  * 
29  * Products derived from this software may not be called Shibboleth, Internet2, 
30  * UCAID, or the University Corporation for Advanced Internet Development, nor 
31  * may Shibboleth appear in their name, without prior written permission of the 
32  * University Corporation for Advanced Internet Development.
33  * 
34  * 
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK 
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. 
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY 
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, 
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 package edu.internet2.middleware.shibboleth.utils;
51
52 import jargs.gnu.CmdLineParser;
53
54 import java.io.*;
55 import java.security.*;
56 import java.security.cert.Certificate;
57 import java.security.cert.*;
58
59 import org.apache.log4j.ConsoleAppender;
60 import org.apache.log4j.Level;
61 import org.apache.log4j.Logger;
62 import org.apache.log4j.PatternLayout;
63 import org.apache.xml.security.c14n.*;
64 import org.apache.xml.security.signature.*;
65 import org.apache.xml.security.transforms.*;
66 import org.w3c.dom.*;
67
68 import edu.internet2.middleware.shibboleth.common.XML;
69
70 /**
71  *  Signs/verifies/maintains Shibboleth metadata files
72  *
73  * @author     Scott Cantor
74  * @created    June 11, 2002
75  */
76 public class MetadataTool
77 {
78     /**
79      *  Signs/verifies/maintains Shibboleth metadata files
80      *
81      * @param  argv           The command line arguments
82      * @exception  Exception  One of about fifty different kinds of possible errors
83      */
84     public static void main(String args[]) throws Exception {
85         // Process the command line.
86         CmdLineParser parser = new CmdLineParser();
87         CmdLineParser.Option helpOption = parser.addBooleanOption('h', "help");
88         CmdLineParser.Option signOption = parser.addBooleanOption('s', "sign");
89         CmdLineParser.Option noverifyOption = parser.addBooleanOption('N', "noverify");
90         CmdLineParser.Option inOption = parser.addStringOption('i', "in");
91         CmdLineParser.Option outOption = parser.addStringOption('o', "out");
92         CmdLineParser.Option keystoreOption = parser.addStringOption('k', "keystore");
93         CmdLineParser.Option aliasOption = parser.addStringOption('a', "alias");
94         CmdLineParser.Option pwOption = parser.addStringOption('p', "password");
95         CmdLineParser.Option nsOption = parser.addStringOption('x', "ns");
96         CmdLineParser.Option nameOption = parser.addStringOption('n', "name");
97         CmdLineParser.Option debugOption = parser.addBooleanOption('d', "debug");
98                 
99                 Boolean debugEnabled = ((Boolean) parser.getOptionValue(debugOption));
100                 boolean debug = false;
101                 if (debugEnabled != null) {
102                         debug = debugEnabled.booleanValue();
103                 }
104         configureLogging(debug);
105         
106         try {
107             parser.parse(args);
108         }
109         catch (CmdLineParser.OptionException e) {
110             System.err.println(e.getMessage());
111             try {
112                 Thread.sleep(100); //silliness to get error to print first
113             }
114             catch (InterruptedException ie) {
115                 //doesn't matter
116             }
117             printUsage(System.out);
118             System.exit(-1);
119         }
120
121         Boolean helpEnabled = (Boolean)parser.getOptionValue(helpOption);
122         if (helpEnabled != null && helpEnabled.booleanValue()) {
123             printUsage(System.out);
124             System.exit(0);
125         }
126         
127         Boolean sign = (Boolean)parser.getOptionValue(signOption);
128         Boolean noverify = (Boolean)parser.getOptionValue(noverifyOption);
129         String keystore = (String)parser.getOptionValue(keystoreOption);
130         String pw = (String)parser.getOptionValue(pwOption);
131         String alias = (String)parser.getOptionValue(aliasOption);
132         String infile = (String)parser.getOptionValue(inOption);
133         String outfile = (String)parser.getOptionValue(outOption);
134         String ns = (String)parser.getOptionValue(nsOption);
135         String name = (String)parser.getOptionValue(nameOption);
136
137         if (infile == null || infile.length() == 0) {
138             printUsage(System.out);
139             System.exit(1);
140         }
141         
142         if (keystore != null && keystore.length() > 0) {
143             if (alias == null || alias.length() == 0) {
144                 printUsage(System.out);
145                 System.exit(1);
146             }
147         }
148
149         PrivateKey privateKey = null;
150         Certificate chain[] = null;
151         X509Certificate cert = null;
152         
153         if (sign != null && sign.booleanValue()) {
154             if (keystore == null || keystore.length() == 0 || pw == null || pw.length() == 0) {
155                 printUsage(System.out);
156                 System.exit(1);
157             }
158             KeyStore ks = KeyStore.getInstance("JKS");
159             FileInputStream fis = new FileInputStream(keystore);
160             ks.load(fis, pw.toCharArray());
161             privateKey = (PrivateKey)ks.getKey(alias, pw.toCharArray());
162             chain = ks.getCertificateChain(alias);
163             if (privateKey == null || chain == null) {
164                 System.err.println("error: couldn't load key or certificate chain from keystore");
165                 System.exit(1);
166             }
167         }
168         else if (keystore != null && keystore.length() > 0){
169             KeyStore ks = KeyStore.getInstance("JKS");
170             FileInputStream fis = new FileInputStream(keystore);
171             ks.load(fis, null);
172             cert = (X509Certificate)ks.getCertificate(alias);
173             if (cert == null) {
174                 System.err.println("error: couldn't load certificate from keystore");
175                 System.exit(1);
176             }
177         }
178         else if (noverify == null || !noverify.booleanValue()) {
179             printUsage(System.out);
180             System.exit(1);
181         }
182         
183         org.opensaml.XML.parserPool.registerSchema(XML.SHIB_NS, XML.SHIB_SCHEMA_ID, new XML.SchemaResolver());
184         org.opensaml.XML.parserPool.registerSchema(XML.TRUST_NS, XML.TRUST_SCHEMA_ID, new XML.SchemaResolver());
185         
186         // Parse file and verify root element.
187         Document doc = org.opensaml.XML.parserPool.parse(infile);
188         Element e = doc.getDocumentElement();
189         if (ns != null && name != null && !org.opensaml.XML.isElementNamed(e,ns,name)) {
190             System.err.println("error: root element did not match ns and name parameters");
191             System.exit(1);
192         }
193         else if (!org.opensaml.XML.isElementNamed(e,XML.SHIB_NS,"SiteGroup") &&
194                             !org.opensaml.XML.isElementNamed(e,XML.SHIB_NS,"Trust") &&
195                                         !org.opensaml.XML.isElementNamed(e,XML.TRUST_NS,"Trust")) {
196             System.err.println("error: root element must be shib:SiteGroup, shib:Trust, or trust:Trust");
197             System.exit(1);
198         }
199
200         if (sign != null && sign.booleanValue()) {
201             // Remove any existing signature.
202             Element old = org.opensaml.XML.getLastChildElement(e, org.opensaml.XML.XMLSIG_NS, "Signature");
203             if (old != null)
204                 e.removeChild(old);
205
206             // Create new signature.
207             XMLSignature sig = new XMLSignature(doc, null, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1);
208             Transforms transforms = new Transforms(doc);
209             transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
210             transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
211             sig.addDocument("", transforms, org.apache.xml.security.utils.Constants.ALGO_ID_DIGEST_SHA1);
212             for (int i=0; i < chain.length; i++)
213                 sig.addKeyInfo((X509Certificate)chain[i]);
214             e.appendChild(sig.getElement());
215             sig.sign(privateKey);
216         }
217         else {
218             Element sigElement = org.opensaml.XML.getLastChildElement(e, org.opensaml.XML.XMLSIG_NS, "Signature");
219             boolean v = (noverify == null || !noverify.booleanValue());
220             if (v) {
221                 if (sigElement == null) {
222                     System.err.println("error: file is not signed");
223                     System.exit(1);
224                 }
225                 XMLSignature sig = new XMLSignature(sigElement, null);
226                 if (!sig.checkSignatureValue(cert)) {
227                     System.err.println("error: signature on file did not verify");
228                     System.exit(1);
229                 }
230             }
231             else if (sigElement != null) {
232                 XMLSignature sig = new XMLSignature(sigElement, null);
233                 System.err.println("verification of signer disabled, make sure you trust the source of this file!");
234                 if (!sig.checkSignatureValue(sig.getKeyInfo().getPublicKey())) {
235                     System.err.println("error: signature on file did not verify");
236                     System.exit(1);
237                 }
238             }
239             else {
240                 System.err.println("verification disabled, and file is unsigned!");
241             }
242         }
243         
244         OutputStream out = null;
245         Canonicalizer c = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_WITH_COMMENTS);
246         if (outfile != null && outfile.length() > 0) {
247             out = new FileOutputStream(outfile);
248             out.write(c.canonicalizeSubtree(doc));
249             out.close();
250         }
251         else {
252                 System.out.write(c.canonicalizeSubtree(doc));
253         }
254     }
255
256     private static void printUsage(PrintStream out)
257     {
258
259         out.println("usage: java edu.internet2.middleware.shibboleth.utils.MetadataTool");
260         out.println();
261         out.println("when signing:   -i <uri> -s -k <keystore> -a <alias> -p <pass> [-o <outfile>]");
262         out.println("when updating:  -i <uri> [-k <keystore> -a <alias> OR -N ] [-o <outfile>]");
263         out.println("  -i,--in              input file or url");
264         out.println("  -k,--keystore        pathname of Java keystore file");
265         out.println("  -a,--alias           alias of signing or verification key");
266         out.println("  -p,--password        keystore/key password");
267         out.println("  -o,--outfile         write signed copy to this file instead of stdout");
268         out.println("  -s,--sign            sign the input file and write out a signed version");
269         out.println("  -N,--noverify        allows update of file without signature check");
270         out.println("  -h,--help            print this message");
271         out.println("  -x,--ns              XML namespace of root element");
272         out.println("  -n,--name            name of root element");
273         out.println("  -d, --debug          run in debug mode");
274         out.println();
275         System.exit(1);
276     }
277     
278         private static void configureLogging(boolean debugEnabled) 
279         {
280                 ConsoleAppender rootAppender = new ConsoleAppender();
281                 rootAppender.setWriter(new PrintWriter(System.out));
282                 rootAppender.setName("stdout");
283                 Logger.getRootLogger().addAppender(rootAppender);
284
285                 if (debugEnabled) {
286                         Logger.getRootLogger().setLevel(Level.DEBUG);
287                         rootAppender.setLayout(new PatternLayout("%-5p %-41X{serviceId} %d{ISO8601} (%c:%L) - %m%n")); 
288                 } else {
289                         Logger.getRootLogger().setLevel(Level.INFO);
290                         Logger.getLogger("edu.internet2.middleware.shibboleth.aa.attrresolv").setLevel(Level.WARN);
291                         rootAppender.setLayout(new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)); 
292                 }
293                 Logger.getLogger("org.apache.xml.security").setLevel(Level.OFF);
294         }
295 }
296