Built in pluggable for Trust XML
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / XMLTrustImpl.java
1 /*
2  * XMLTrustImpl.java
3  * 
4  * Trust provider plugin for XML <Trust> configuration data
5  * 
6  * Logic based on XMLTrust.cpp
7  * 
8  * A signed SAMLAssertion arrives. It may carry a certificate
9  * or not. If it does carry a certificate, are we willing to 
10  * belive it? Trust answers that question. The Signature logic
11  * in the XML Security jar will validate the SAML Assertion
12  * against a key for us, but we have to belive that the key is
13  * correct. Whether to trust the key is outside the scope of 
14  * XML, Signature, SAML, and the Shibboleth protocol per se.
15  * It is an implementation feature.
16  * 
17  * A pluggable trust element in a Shibboleth configuration
18  * file builds or gains access to a collection of keys and/or
19  * certificates (that contain keys). Each key/certificate is 
20  * associated with one or more subject names that represent
21  * Shibboleth services at a particular institution (Entity). 
22  * In this case, the keys and certificates are embedded as
23  * bin64 data in an XML file, but they could more generally 
24  * be in any key storage.
25  * 
26  * The function of Trust is to determine the Subject name
27  * from the SAMLAssertion, look up the key/certificate for
28  * that Subject, apply a wildcard where appropriate, and then
29  * ask OpenSAML to ask XML Security to validate the assertion
30  * given the key. In some cases, the Assertion may be 
31  * accompanied by a certificate signed by an authority, in 
32  * which case Trust may use its key store to validate the 
33  * authority and certificate, and then feed back the offered
34  * key from the transmitted certificate back for validation.
35  * 
36  * The XML Trust file has already been parsed into a DOM by the 
37  * ServiceProviderConfig. This class is a logical extension of the
38  * ServiceProviderConfig.XMLTrustProviderImpl class that holds that
39  * parsed information.
40  * 
41  * However, while the ServiceProviderConfig class knows how to parse
42  * a trust file into its elements, it is up to this class to know
43  * what to do with that information. Generally, it creates 
44  * java.security objects representing keys and certificates and
45  * matches them to names.
46  * 
47  * Ultimately, the keys and certificates have to be used by the
48  * XML Signature classes from Apache. However, the actual interface
49  * to Signature services is the responsibility of OpenSAML. This
50  * module determines which keys to use or certificates to trust, but
51  * then passes the keys on to the OpenSAML layer.
52  * 
53  * Note: SAML 2.0 Metadata includes a KeyDescriptor for EntityDescriptor
54  * and roles, but while there is a placeholder for it in existing code
55  * it is not implemented. The current logic manages key information 
56  * embedded in a (typically separate) XML configuration file.
57  * 
58  * Warning: Trust is a very popular class name. There is an obsolete
59  * Trust tag in the shibboleth-targetconfig-1.0.xsd, a current tag in
60  * shibboleth-trust-1.0.xsd, plus an interface and so on. As a result,
61  * there are a bunch of related objects with slightly different names
62  * that represent different views of the same information from different
63  * layer of the processing.
64  * 
65  * --------------------
66  * Copyright 2002, 2004 
67  * University Corporation for Advanced Internet Development, Inc. 
68  * All rights reserved
69  * [Thats all we have to say to protect ourselves]
70  * Your permission to use this code is governed by "The Shibboleth License".
71  * A copy may be found at http://shibboleth.internet2.edu/license.html
72  * [Nothing in copyright law requires license text in every file.]
73  */
74 package edu.internet2.middleware.shibboleth.serviceprovider;
75
76 import java.io.ByteArrayInputStream;
77 import java.io.FileInputStream;
78 import java.io.FileNotFoundException;
79 import java.math.BigInteger;
80 import java.security.KeyFactory;
81 import java.security.NoSuchAlgorithmException;
82 import java.security.PublicKey;
83 import java.security.cert.Certificate;
84 import java.security.cert.CertificateException;
85 import java.security.cert.CertificateFactory;
86 import java.security.spec.DSAPublicKeySpec;
87 import java.security.spec.InvalidKeySpecException;
88 import java.security.spec.RSAPublicKeySpec;
89 import java.util.ArrayList;
90 import java.util.HashMap;
91 import java.util.Iterator;
92 import java.util.Map;
93 import org.apache.log4j.Logger;
94 import org.apache.xml.security.exceptions.XMLSecurityException;
95 import org.apache.xmlbeans.XmlException;
96 import org.opensaml.SAMLAssertion;
97 import org.opensaml.SAMLAttributeQuery;
98 import org.opensaml.SAMLObject;
99 import org.opensaml.SAMLQuery;
100 import org.opensaml.SAMLRequest;
101 import org.opensaml.SAMLResponse;
102 import org.opensaml.SAMLSubjectStatement;
103 import org.w3.x2000.x09.xmldsig.DSAKeyValueType;
104 import org.w3.x2000.x09.xmldsig.KeyInfoType;
105 import org.w3.x2000.x09.xmldsig.KeyValueType;
106 import org.w3.x2000.x09.xmldsig.RSAKeyValueType;
107 import org.w3.x2000.x09.xmldsig.RetrievalMethodType;
108 import org.w3.x2000.x09.xmldsig.X509DataType;
109 import org.w3c.dom.Element;
110 import org.w3c.dom.Node;
111
112 import x0.maceShibbolethTrust1.KeyAuthorityType;
113 import x0.maceShibbolethTrust1.TrustDocument;
114 import x0.maceShibbolethTrust1.TrustDocument.Trust;
115
116 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
117 import edu.internet2.middleware.shibboleth.common.XML;
118 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
119 import edu.internet2.middleware.shibboleth.metadata.EntityLocator;
120 import edu.internet2.middleware.shibboleth.metadata.ProviderRole;
121
122
123 /**
124  * An XMLTrustImpl object is created from an external Trust XML file
125  * or inline data under the TrustProvider element of an Application
126  * 
127  * @author Howard Gilbert
128  */
129 public class XMLTrustImpl 
130         implements ITrust,
131         PluggableConfigurationComponent {
132         
133         private static Logger log = Logger.getLogger(XMLTrustImpl.class);
134         
135         // Data extracted from the KeyAuthority elements of the Trust
136         private ArrayList/*<KeyAuthorityInfo>*/ keyauths = new ArrayList();
137         private Map/*<Subject-String,KeyAuthorityInfo>*/ authMap = new HashMap();
138         private KeyAuthorityInfo wildcardKeyAuthorityInfo;
139         
140         // Data extracted from the KeyInfo elements of the Trust
141         private Map/*<Subject-String,KeyInfoInfo>*/ trustKeyMap = new HashMap();
142         
143
144         public void initialize(Node dom) 
145                 throws XmlException,
146                 ShibbolethConfigurationException {
147             
148             TrustDocument docbean = TrustDocument.Factory.parse(dom);
149             Trust trustbean = docbean.getTrust();
150                 CertificateFactory certFactory = null;
151                 try {
152                         certFactory = CertificateFactory.getInstance("X.509");
153                 } catch (CertificateException e) {
154                         // Get a modern version of Java, stupid.
155                         log.error("Unable to process X.509 Certificates.");
156                         throw new ShibbolethConfigurationException("Unable to process X.509 Certificates.");
157                 }               
158                 
159                 /*
160                  * Convert KeyAuthority configuration elements into KeyAuthorityInfo objects.
161                  * A KeyAuthority has only X509 certificates (no naked keys). For each
162                  * KeyAuthorityInfo, create a collection of subject names and a collection
163                  * of certificates. Then index the KeyAuthorityInfo (and all its certificates)
164                  * by each subject name.
165                  * 
166                  * Note: Using a Map to index by subject restricts every subject name to a single
167                  * KeyAuthority. If a subject name might be validated by multiple authorities, then
168                  * we would need to use a different collection mechanism.
169                  */
170                 
171                 KeyAuthorityType[] keyAuthorityArray = trustbean.getKeyAuthorityArray();
172                 for (int i=0;i<keyAuthorityArray.length;i++){
173                         KeyAuthorityType keyAuthority = keyAuthorityArray[i];
174                         /*
175                          * While I was walking to Saint Ives, I met a file with 7 KeyAuthorities
176                          * Each KeyAuthority had 7 Names, but only one KeyInfo
177                          * Each KeyInfo had 7 X509Datas
178                          * Each X509Datas had 7 X509Certificates
179                          * Authorities, Infos, Datas, Certs ...
180                          * How many were going to Saint Ives?
181                          */
182                         KeyAuthorityInfo ka = new KeyAuthorityInfo(); 
183                         KeyInfoType keyInfo = keyAuthority.getKeyInfo();
184                         ka.subjects = keyAuthority.getKeyNameArray();
185                         ka.depth = keyAuthority.getVerifyDepth();
186                         
187                         X509DataType[] x509DataArray = keyInfo.getX509DataArray();
188                         for (int idata=0;idata<x509DataArray.length;idata++) {
189                                 X509DataType x509Data = x509DataArray[idata];
190                                 byte[][] certificateArray = x509Data.getX509CertificateArray();
191                                 for (int icert=0;icert<certificateArray.length;icert++) {
192                                         byte [] certbytes = certificateArray[icert];
193                                         try {
194                                                 Certificate certificate = certFactory.generateCertificate(new ByteArrayInputStream(certbytes));
195                                                 ka.certs.add(certificate);
196                                         } catch (CertificateException e1) {
197                                                 log.error("Invalid X.509 certificate in Trust list.");
198                                                 throw new ShibbolethConfigurationException("Invalid X.509 certificate in Trust list.");
199                                         }
200                                 }
201                         }
202                         
203                         // Externally referenced objects
204                         RetrievalMethodType[] retrievalMethodArray = keyInfo.getRetrievalMethodArray();
205                         for (int iretrieval=0;iretrieval<retrievalMethodArray.length;iretrieval++) {
206                                 RetrievalMethodType method = retrievalMethodArray[iretrieval];
207                                 String type = method.getType();
208                                 String fname = method.getURI();
209                                 if (type.equals(XML.XMLSIG_RETMETHOD_RAWX509)) {
210                                         try {
211                                                 Certificate certificate = certFactory.generateCertificate(new FileInputStream(fname));
212                                                 ka.certs.add(certificate);
213                                         } catch (FileNotFoundException e1) {
214                                                 log.error("RetrievalMethod referenced an unknown file: "+fname );
215                                         } catch (CertificateException e) {
216                                                 log.error("RetrievalMethod referenced an certificate file with bad format: "+fname);
217                                         }
218                                 } else if (type.equals(XML.SHIB_RETMETHOD_PEMX509)) {
219                                         // not sure
220                                         try {
221                                                 Certificate certificate = certFactory.generateCertificate(new FileInputStream(fname));
222                                                 ka.certs.add(certificate);
223                                         } catch (FileNotFoundException e1) {
224                                                 log.error("RetrievalMethod referenced an unknown file: "+fname );
225                                         } catch (CertificateException e) {
226                                                 log.error("RetrievalMethod referenced an certificate file with bad format: "+fname);
227                                         }
228                                 }
229                         }
230                         
231                         /*
232                          * Index all these certificates by all the subject names provided 
233                          */
234                         for (int isubject=0;isubject<ka.subjects.length;isubject++) {
235                                 String subject = ka.subjects[isubject];
236                                 authMap.put(subject,ka);
237                         }
238                         if (ka.subjects.length==0) {
239                                 log.warn("found a wildcard KeyAuthority element, make sure this is what you intend");
240                                 wildcardKeyAuthorityInfo=ka;
241                         }
242                         keyauths.add(ka);
243                 }
244                 
245                 /*
246                  * KeyInfo trust elements can have either X509 certificates or public keys.
247                  * Drill down collecting the information and index it by subject name.
248                  */
249                 KeyInfoType[] keyInfoArray = trustbean.getKeyInfoArray();
250                 
251                 for (int i=0;i<keyInfoArray.length;i++) {
252                         KeyInfoType keyInfo = keyInfoArray[i];
253                         
254                         KeyInfoInfo keyii = new KeyInfoInfo();
255                         
256                         /*
257                          * If the KeyInfo has a certificate, then everything is simple. However,
258                          * if it has keys, then the XML format for DER or RSA keys is daunting.
259                          * The good news is that Apache XML Security already has classes for 
260                          * handling KeyInfo XML elements, and maybe that is a simpler path
261                          * than trying to parse them ourselves. So on spec, create an 
262                          * apache object just to have it around. 
263                          */
264                         Element ele = (Element) keyInfo.newDomNode().getFirstChild();
265                         org.apache.xml.security.keys.KeyInfo apacheKeyInfo = null;
266                         try {
267                                 apacheKeyInfo = 
268                                         new org.apache.xml.security.keys.KeyInfo(ele,"file:///opt/shibboleth/");
269                         } catch (XMLSecurityException e) {
270                                 // Just a test
271                         }
272                         
273                         String[] subjects = keyInfo.getKeyNameArray();
274                         
275                         X509DataType[] dataArray = keyInfo.getX509DataArray();
276                         if (dataArray.length>0) {  // process certificates
277                                 X509DataType x509Data = dataArray[0];
278                                 if (subjects==null)
279                                         subjects = x509Data.getX509SubjectNameArray();
280                                 if (subjects==null) {
281                                         log.error("Ignoring KeyInfo element with no Subject names");
282                                         continue;
283                                 }
284                                 byte[][] certificateArray = x509Data.getX509CertificateArray();
285                                 if (certificateArray.length==0) {
286                                         log.error("Ignoring KeyInfo element with neither keys nor certificates");
287                                         continue;
288                                 }
289                                 
290                                 // Question: can there be more than one certificate?
291                                 byte[] certbytes = certificateArray[0];
292                                 Certificate certificate=null;
293                                 try {
294                                         certificate = certFactory.generateCertificate(new ByteArrayInputStream(certbytes));
295                                 } catch (CertificateException e1) {
296                                         log.error("Invalid X.509 certificate in Trust list.");
297                                         throw new ShibbolethConfigurationException("Invalid X.509 certificate in Trust list.");
298                                 }
299                                 keyii.cert=certificate;
300                                 keyii.key=certificate.getPublicKey();
301                                 // for (subject:subjects)
302                                 for (int isubject=0;isubject<subjects.length;isubject++) {
303                                         String subject=subjects[isubject];
304                                         
305                                         trustKeyMap.put(subject,keyii);
306                                 }
307                         } else {
308                                 /*
309                                  * Tutorial Time
310                                  * 
311                                  * A "Key" isn't a single value, but rather a collection of parameters.
312                                  * In the KeyInfo XML element, a KeyValue can have either a DSA or RSA key.
313                                  * Each key is specified as a set of fields of type ds:CryptoBinary
314                                  *  (a version of bin64 binary encoding).
315                                  * Now there are two ways to proceed, and for the moment both are coded
316                                  * below until we decide which to use.
317                                  * 
318                                  * If you continue to use XBeans, then you extract each parameter for the
319                                  * specific type of key as the byte[] that XBeans converts bin64 into. 
320                                  * That byte[] can then be converted to a Java BigInteger which can be
321                                  * passed as an argument to build a Java KeySpec.
322                                  * A KeySpec Object is a Java object that represents the key as its 
323                                  * external separate parameters. The last step is to get a KeyFactor
324                                  * of type "DSA" or "RSA" to convert the KeySpec into a PublicKey object.
325                                  * 
326                                  * Alternately, you can abandon XMLBeans and go back to the DOM.
327                                  * The KeyInfo element can be directly converted into a corresponding
328                                  * org.apache object of the XML Security packages. Then the apache
329                                  * classes do all the work of extracting parameters, converting
330                                  * formats, and they spit out a public key.
331                                  * 
332                                  * Both approaches have been coded below to demonstrate. The
333                                  * preferred solution will be determined by developer discussion.
334                                  */
335                                 
336                                 
337                                 KeyValueType[] keyValueArray = keyInfo.getKeyValueArray();
338                                 if (keyValueArray.length==0) {
339                                         log.error("Ignoring an empty KeyInfo (with neither keys nor certificates) in the Trust configuration");
340                                         log.error(keyInfo.xmlText());
341                                         continue;
342                                 }
343                                 
344                                 KeyValueType keyValue = keyValueArray[0];
345                                 
346                                 if (keyValue.isSetDSAKeyValue()) {
347                                         DSAKeyValueType dsavalue = keyValue.getDSAKeyValue();
348                                         BigInteger g = new BigInteger(dsavalue.getG());
349                                         BigInteger p = new BigInteger(dsavalue.getP());
350                                         BigInteger q = new BigInteger(dsavalue.getQ());
351                                         BigInteger y = new BigInteger(dsavalue.getY());
352                                         DSAPublicKeySpec dsakeyspec = new DSAPublicKeySpec(y,p,q,g);
353                                         try {
354                                                 PublicKey pubkey = KeyFactory.getInstance("DSA").generatePublic(dsakeyspec);
355                                                 keyii.key= pubkey;
356                                         } catch (NoSuchAlgorithmException e) {
357                                                 log.error("Java crypto library is broken");
358                                                 continue;
359                                         } catch (InvalidKeySpecException e) {
360                                                 log.error("Invalid key values in KeyInfo");
361                                                 continue;
362                                         }
363                                         
364                                 } else if (keyValue.isSetRSAKeyValue()) {
365                                         RSAKeyValueType rsavalue = keyValue.getRSAKeyValue();
366                                         BigInteger modulus = new BigInteger(rsavalue.getModulus());
367                                         BigInteger pubexp  = new BigInteger(rsavalue.getExponent());
368                                         RSAPublicKeySpec rsakeyspec = new RSAPublicKeySpec(modulus,pubexp);
369                                         try {
370                                                 PublicKey pubkey = KeyFactory.getInstance("RSA").generatePublic(rsakeyspec);
371                                                 keyii.key= pubkey;
372                                         } catch (NoSuchAlgorithmException e) {
373                                                 log.error("Java crypto library is broken");
374                                                 continue;
375                                         } catch (InvalidKeySpecException e) {
376                                                 log.error("Invalid key values in KeyInfo");
377                                                 continue;
378                                         }
379                                         
380                                 }
381                                         
382                                 PublicKey pubkey = null;
383                                 try {
384                                         pubkey= apacheKeyInfo.getPublicKey();
385                                         // Uncomment the following line to replace XMLBean with Apache logic
386                                         // keyii.key= pubkey;
387                                 } catch (org.apache.xml.security.keys.keyresolver.KeyResolverException kre) {
388                                         log.error("Bad key in KeyInfo in Trust");
389                                 }
390                         }
391                         if (keyii.cert!=null || keyii.key!=null) {
392                                 // for (subject:subjects)
393                                 for (int isubject=0;isubject<subjects.length;isubject++) {
394                                         String subject = subjects[isubject];
395                                         trustKeyMap.put(subject,keyii);
396                                 }
397                         }
398                 }
399         }
400
401         /**
402          * Validate a signed SAML object from Trust configuration data
403          * 
404          * <p>Implements a method of the ITrust interface.</p>
405          * 
406          * <p>If the caller supplies a Role, then the object is validated against
407          * the keys associated with that remote Entity. Otherwise, the remote
408          * Entity is located from fields within the token object itself.
409          * 
410          * @param token        The SAMLObject to be validated
411          * @param ProviderRole null or the Role object for the source
412          * @param locator      interface for ApplicationInfo.getEntityDescriptor()
413          * @param revocations  null or revocation provider
414          */
415         public boolean validate(
416                         Iterator revocations, 
417                         ProviderRole role,
418                         SAMLObject token, 
419                         EntityLocator locator
420                                 ) {
421                 
422                 EntityDescriptor entityDescriptor = null;
423                 
424                 // Did the caller designate the remote Entity
425                 if (role!=null)
426                         entityDescriptor = (EntityDescriptor) role.getProvider();
427                 
428                 // If not, then search through the SAMLObject for the remote Entity Id
429                 if (entityDescriptor==null) {
430                         if (token instanceof SAMLResponse) {
431                                 Iterator assertions = ((SAMLResponse) token).getAssertions();
432                                 while (entityDescriptor==null && assertions.hasNext()) {
433                                         SAMLAssertion assertion = (SAMLAssertion) assertions.next();
434                                         entityDescriptor = getEntityFromAssertion(locator, assertion);
435                                         
436                                 }
437                         } else if(token instanceof SAMLAssertion){
438                                 SAMLAssertion assertion = (SAMLAssertion) token;
439                                 entityDescriptor = getEntityFromAssertion(locator, assertion);
440                         } else if (token instanceof SAMLRequest) {
441                                 SAMLRequest request = (SAMLRequest) token;
442                                 SAMLQuery query = request.getQuery();
443                                 if (query!=null && query instanceof SAMLAttributeQuery) {
444                                         String name = ((SAMLAttributeQuery) query).getResource();
445                                         entityDescriptor=locator.getEntityDescriptor(name);
446                                 }
447                         }
448                 }
449                 
450                 if (entityDescriptor==null) {
451                         // May need to use wildcard
452                 }
453                 
454                 // The C++ code now checks the KeyDescriptors in the Roles, but that logic
455                 // only makes sense in SAML 2.0. There are no KeyDescriptors in the current
456                 // configuration file, and the current implementation of the Role interfaces
457                 // dummy that call out. So for now I am going to skip it.
458                 
459                 
460                 
461                 return true;
462         }
463
464         /**
465          * Find the metadata EntityDescriptor for the remote Entity from data in the SAMLAssertion.
466          * 
467          * <p>Try the Issuer first, then look through the name qualifiers.
468          * 
469          * @param locator    Metadata Entity finder method from the configuration ApplicationInfo object
470          * @param assertion  SAMLAssertion to be verified
471          * @return           First ntityDescriptor mapped from assertion data fields. 
472          */
473         private EntityDescriptor getEntityFromAssertion(EntityLocator locator, SAMLAssertion assertion) {
474                 EntityDescriptor entityDescriptor = null;
475                 entityDescriptor=locator.getEntityDescriptor(assertion.getIssuer());
476                 if (entityDescriptor!=null) 
477                         return entityDescriptor;
478                 Iterator statements = assertion.getStatements();
479                 while (entityDescriptor==null && statements.hasNext()) {
480                         SAMLSubjectStatement statement = (SAMLSubjectStatement) statements.next();
481                         String qname = statement.getSubject().getName().getNameQualifier();
482                         entityDescriptor=locator.getEntityDescriptor(qname);
483                 }
484                 return entityDescriptor;
485         }
486
487         public boolean attach(
488                         Iterator revocations, 
489                         ProviderRole role
490                                 ) {
491                 return false;
492         }
493
494         /**
495          * Represent the information extracted from one KeyAuthority Element
496          */
497         static private class KeyAuthorityInfo {
498                 short depth = 1;
499                 String[] subjects;
500                 ArrayList/*<Certificate>*/ certs=new ArrayList();
501         }
502         
503         /**
504          * Represent the information extracted from a simple KeyInfo Element
505          */
506         static private class KeyInfoInfo {
507                 PublicKey key;
508                 Certificate cert;
509         }
510
511     /**
512      * @return
513      */
514     public String getSchemaPathname() {
515         return null;
516     }
517 }