View Javadoc

1   /*
2    * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.opensaml.xml.security.credential;
18  
19  import java.security.GeneralSecurityException;
20  import java.security.KeyStore;
21  import java.security.KeyStoreException;
22  import java.security.UnrecoverableEntryException;
23  import java.security.KeyStore.SecretKeyEntry;
24  import java.security.cert.X509Certificate;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collections;
28  import java.util.Map;
29  
30  import org.opensaml.xml.security.CriteriaSet;
31  import org.opensaml.xml.security.SecurityException;
32  import org.opensaml.xml.security.criteria.EntityIDCriteria;
33  import org.opensaml.xml.security.criteria.UsageCriteria;
34  import org.opensaml.xml.security.x509.BasicX509Credential;
35  import org.opensaml.xml.security.x509.X509Credential;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * A {@link CredentialResolver} that extracts {@link Credential}'s from a key store.
41   * 
42   * If no key usage type is presented at construction time this resolver will return the key, if available, regardless of
43   * the usage type provided to its resolve method.
44   */
45  public class KeyStoreCredentialResolver extends AbstractCriteriaFilteringCredentialResolver {
46  
47      /** Class logger. */
48      private final Logger log = LoggerFactory.getLogger(KeyStoreCredentialResolver.class);
49  
50      /** Key store credentials are retrieved from. */
51      private KeyStore keyStore;
52  
53      /** Passwords for keys. The key must be the entity ID, the value the password. */
54      private Map<String, String> keyPasswords;
55  
56      /** Usage type of all keys in the store. */
57      private UsageType keystoreUsage;
58  
59      /**
60       * Constructor.
61       * 
62       * @param store key store credentials are retrieved from
63       * @param passwords for key entries, map key is the entity id, map value is the password
64       * 
65       * @throws IllegalArgumentException thrown if the given keystore is null
66       */
67      public KeyStoreCredentialResolver(KeyStore store, Map<String, String> passwords) throws IllegalArgumentException {
68          this(store, passwords, null);
69      }
70  
71      /**
72       * Constructor.
73       * 
74       * @param store key store credentials are retrieved from
75       * @param passwords for key entries, map key is the entity id, map value is the password
76       * @param usage usage type of all keys in the store
77       * 
78       * @throws IllegalArgumentException thrown if the given keystore is null
79       */
80      public KeyStoreCredentialResolver(KeyStore store, Map<String, String> passwords, UsageType usage)
81              throws IllegalArgumentException {
82          super();
83  
84          if (store == null) {
85              throw new IllegalArgumentException("Provided key store may not be null.");
86          }
87  
88          try {
89              store.size();
90          } catch (KeyStoreException e) {
91              throw new IllegalArgumentException("Keystore has not been initialized.");
92          }
93  
94          keyStore = store;
95  
96          if (usage != null) {
97              keystoreUsage = usage;
98          } else {
99              keystoreUsage = UsageType.UNSPECIFIED;
100         }
101 
102         keyPasswords = passwords;
103     }
104 
105     /** {@inheritDoc} */
106     protected Iterable<Credential> resolveFromSource(CriteriaSet criteriaSet) throws SecurityException {
107 
108         checkCriteriaRequirements(criteriaSet);
109 
110         String entityID = criteriaSet.get(EntityIDCriteria.class).getEntityID();
111         UsageCriteria usageCriteria = criteriaSet.get(UsageCriteria.class);
112         UsageType usage;
113         if (usageCriteria != null) {
114             usage = usageCriteria.getUsage();
115         } else {
116             usage = UsageType.UNSPECIFIED;
117         }
118         if (!matchUsage(keystoreUsage, usage)) {
119             log.debug("Specified usage criteria {} does not match keystore usage {}", usage, keystoreUsage);
120             log.debug("Can not resolve credentials from this keystore");
121             return Collections.emptySet();
122         }
123 
124         KeyStore.PasswordProtection keyPassword = null;
125         if (keyPasswords.containsKey(entityID)) {
126             keyPassword = new KeyStore.PasswordProtection(keyPasswords.get(entityID).toCharArray());
127         }
128 
129         KeyStore.Entry keyStoreEntry = null;
130         try {
131             keyStoreEntry = keyStore.getEntry(entityID, keyPassword);
132         } catch (UnrecoverableEntryException e) {
133             log.error("Unable to retrieve keystore entry for entityID (keystore alias): " + entityID);
134             log.error("Check for invalid keystore entityID/alias entry password");
135             throw new SecurityException("Could not retrieve entry from keystore", e);
136         } catch (GeneralSecurityException e) {
137             log.error("Unable to retrieve keystore entry for entityID (keystore alias): " + entityID, e);
138             throw new SecurityException("Could not retrieve entry from keystore", e);
139         }
140 
141         if (keyStoreEntry == null) {
142             log.debug("Keystore entry for entity ID (keystore alias) {} does not exist", entityID);
143             return Collections.emptySet();
144         }
145 
146         Credential credential = buildCredential(keyStoreEntry, entityID, keystoreUsage);
147         return Collections.singleton(credential);
148     }
149 
150     /**
151      * Check that required credential criteria are available.
152      * 
153      * @param criteriaSet the credential criteria set to evaluate
154      */
155     protected void checkCriteriaRequirements(CriteriaSet criteriaSet) {
156         EntityIDCriteria entityCriteria = criteriaSet.get(EntityIDCriteria.class);
157         if (entityCriteria == null) {
158             log.error("EntityIDCriteria was not specified in the criteria set, resolution can not be attempted");
159             throw new IllegalArgumentException("No EntityIDCriteria was available in criteria set");
160         }
161     }
162 
163     /**
164      * Match usage enum type values from keystore configured usage and from credential criteria.
165      * 
166      * @param keyStoreUsage the usage type configured for the keystore
167      * @param criteriaUsage the value from credential criteria
168      * @return true if the two usage specifiers match for purposes of resolving credentials, false otherwise
169      */
170     protected boolean matchUsage(UsageType keyStoreUsage, UsageType criteriaUsage) {
171         if (keyStoreUsage == UsageType.UNSPECIFIED || criteriaUsage == UsageType.UNSPECIFIED) {
172             return true;
173         }
174         return keyStoreUsage == criteriaUsage;
175     }
176 
177     /**
178      * Build a credential instance from the key store entry.
179      * 
180      * @param keyStoreEntry the key store entry to process
181      * @param entityID the entityID to include in the credential
182      * @param usage the usage type to include in the credential
183      * @return the new credential instance, appropriate to the type of key store entry being processed
184      * @throws SecurityException throw if there is a problem building a credential from the key store entry
185      */
186     protected Credential buildCredential(KeyStore.Entry keyStoreEntry, String entityID, UsageType usage)
187             throws SecurityException {
188 
189         log.debug("Building credential from keystore entry for entityID {}, usage type {}", entityID, usage);
190 
191         Credential credential = null;
192         if (keyStoreEntry instanceof KeyStore.PrivateKeyEntry) {
193             credential = processPrivateKeyEntry((KeyStore.PrivateKeyEntry) keyStoreEntry, entityID, keystoreUsage);
194         } else if (keyStoreEntry instanceof KeyStore.TrustedCertificateEntry) {
195             credential = processTrustedCertificateEntry((KeyStore.TrustedCertificateEntry) keyStoreEntry, entityID,
196                     keystoreUsage);
197         } else if (keyStoreEntry instanceof KeyStore.SecretKeyEntry) {
198             credential = processSecretKeyEntry((KeyStore.SecretKeyEntry) keyStoreEntry, entityID, keystoreUsage);
199         } else {
200             throw new SecurityException("KeyStore entry was of an unsupported type: "
201                     + keyStoreEntry.getClass().getName());
202         }
203         return credential;
204     }
205 
206     /**
207      * Build an X509Credential from a keystore trusted certificate entry.
208      * 
209      * @param trustedCertEntry the entry being processed
210      * @param entityID the entityID to set
211      * @param usage the usage type to set
212      * @return new X509Credential instance
213      */
214     protected X509Credential processTrustedCertificateEntry(KeyStore.TrustedCertificateEntry trustedCertEntry,
215             String entityID, UsageType usage) {
216 
217         log.debug("Processing TrustedCertificateEntry from keystore");
218 
219         BasicX509Credential credential = new BasicX509Credential();
220         credential.setEntityId(entityID);
221         credential.setUsageType(usage);
222 
223         X509Certificate cert = (X509Certificate) trustedCertEntry.getTrustedCertificate();
224 
225         credential.setEntityCertificate(cert);
226 
227         ArrayList<X509Certificate> certChain = new ArrayList<X509Certificate>();
228         certChain.add(cert);
229         credential.setEntityCertificateChain(certChain);
230 
231         return credential;
232     }
233 
234     /**
235      * Build an X509Credential from a keystore private key entry.
236      * 
237      * @param privateKeyEntry the entry being processed
238      * @param entityID the entityID to set
239      * @param usage the usage type to set
240      * @return new X509Credential instance
241      */
242     protected X509Credential processPrivateKeyEntry(KeyStore.PrivateKeyEntry privateKeyEntry, String entityID,
243             UsageType usage) {
244 
245         log.debug("Processing PrivateKeyEntry from keystore");
246 
247         BasicX509Credential credential = new BasicX509Credential();
248         credential.setEntityId(entityID);
249         credential.setUsageType(usage);
250 
251         credential.setPrivateKey(privateKeyEntry.getPrivateKey());
252 
253         credential.setEntityCertificate((X509Certificate) privateKeyEntry.getCertificate());
254         credential.setEntityCertificateChain(Arrays.asList((X509Certificate[]) privateKeyEntry.getCertificateChain()));
255 
256         return credential;
257     }
258 
259     /**
260      * Build a Credential from a keystore secret key entry.
261      * 
262      * @param secretKeyEntry the entry being processed
263      * @param entityID the entityID to set
264      * @param usage the usage type to set
265      * @return new Credential instance
266      */
267     protected Credential processSecretKeyEntry(SecretKeyEntry secretKeyEntry, String entityID, UsageType usage) {
268         log.debug("Processing SecretKeyEntry from keystore");
269 
270         BasicCredential credential = new BasicCredential();
271         credential.setEntityId(entityID);
272         credential.setUsageType(usage);
273 
274         credential.setSecretKey(secretKeyEntry.getSecretKey());
275 
276         return credential;
277     }
278 }