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;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.File;
21  import java.io.IOException;
22  import java.math.BigInteger;
23  import java.security.GeneralSecurityException;
24  import java.security.Key;
25  import java.security.KeyException;
26  import java.security.KeyFactory;
27  import java.security.KeyPair;
28  import java.security.KeyPairGenerator;
29  import java.security.NoSuchAlgorithmException;
30  import java.security.NoSuchProviderException;
31  import java.security.PrivateKey;
32  import java.security.PublicKey;
33  import java.security.cert.CRLException;
34  import java.security.cert.CertificateException;
35  import java.security.cert.CertificateFactory;
36  import java.security.cert.X509Certificate;
37  import java.security.interfaces.DSAParams;
38  import java.security.interfaces.DSAPrivateKey;
39  import java.security.interfaces.DSAPublicKey;
40  import java.security.interfaces.ECPublicKey;
41  import java.security.interfaces.RSAPrivateCrtKey;
42  import java.security.interfaces.RSAPrivateKey;
43  import java.security.interfaces.RSAPublicKey;
44  import java.security.spec.DSAPublicKeySpec;
45  import java.security.spec.InvalidKeySpecException;
46  import java.security.spec.KeySpec;
47  import java.security.spec.RSAPublicKeySpec;
48  import java.security.spec.X509EncodedKeySpec;
49  import java.util.ArrayList;
50  import java.util.HashSet;
51  import java.util.List;
52  import java.util.Set;
53  
54  import javax.crypto.KeyGenerator;
55  import javax.crypto.SecretKey;
56  
57  import org.apache.commons.ssl.PKCS8Key;
58  import org.apache.xml.security.Init;
59  import org.apache.xml.security.algorithms.JCEMapper;
60  import org.opensaml.xml.Configuration;
61  import org.opensaml.xml.encryption.EncryptionParameters;
62  import org.opensaml.xml.encryption.KeyEncryptionParameters;
63  import org.opensaml.xml.security.credential.BasicCredential;
64  import org.opensaml.xml.security.credential.Credential;
65  import org.opensaml.xml.security.keyinfo.BasicProviderKeyInfoCredentialResolver;
66  import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
67  import org.opensaml.xml.security.keyinfo.KeyInfoGenerator;
68  import org.opensaml.xml.security.keyinfo.KeyInfoGeneratorFactory;
69  import org.opensaml.xml.security.keyinfo.KeyInfoProvider;
70  import org.opensaml.xml.security.keyinfo.NamedKeyInfoGeneratorManager;
71  import org.opensaml.xml.security.keyinfo.provider.DSAKeyValueProvider;
72  import org.opensaml.xml.security.keyinfo.provider.InlineX509DataProvider;
73  import org.opensaml.xml.security.keyinfo.provider.RSAKeyValueProvider;
74  import org.opensaml.xml.security.x509.BasicX509Credential;
75  import org.opensaml.xml.signature.KeyInfo;
76  import org.opensaml.xml.signature.Signature;
77  import org.opensaml.xml.signature.SignatureConstants;
78  import org.opensaml.xml.util.Base64;
79  import org.opensaml.xml.util.DatatypeHelper;
80  import org.opensaml.xml.util.LazySet;
81  import org.slf4j.Logger;
82  import org.slf4j.LoggerFactory;
83  
84  /**
85   * Helper methods for security-related requirements.
86   */
87  public final class SecurityHelper {
88  
89      /** Additional algorithm URI's which imply RSA keys. */
90      private static Set<String> rsaAlgorithmURIs;
91  
92      /** Additional algorithm URI's which imply DSA keys. */
93      private static Set<String> dsaAlgorithmURIs;
94  
95      /** Additional algorithm URI's which imply ECDSA keys. */
96      private static Set<String> ecdsaAlgorithmURIs;
97  
98      /** Constructor. */
99      private SecurityHelper() {
100     }
101 
102     /**
103      * Get the Java security JCA/JCE algorithm identifier associated with an algorithm URI.
104      * 
105      * @param algorithmURI the algorithm URI to evaluate
106      * @return the Java algorithm identifier, or null if the mapping is unavailable or indeterminable from the URI
107      */
108     public static String getAlgorithmIDFromURI(String algorithmURI) {
109         return DatatypeHelper.safeTrimOrNullString(JCEMapper.translateURItoJCEID(algorithmURI));
110     }
111 
112     /**
113      * Check whether the signature method algorithm URI indicates HMAC.
114      * 
115      * @param signatureAlgorithm the signature method algorithm URI
116      * @return true if URI indicates HMAC, false otherwise
117      */
118     public static boolean isHMAC(String signatureAlgorithm) {
119         String algoClass = DatatypeHelper.safeTrimOrNullString(JCEMapper.getAlgorithmClassFromURI(signatureAlgorithm));
120         return ApacheXMLSecurityConstants.ALGO_CLASS_MAC.equals(algoClass);
121     }
122 
123     /**
124      * Get the Java security JCA/JCE key algorithm specifier associated with an algorithm URI.
125      * 
126      * @param algorithmURI the algorithm URI to evaluate
127      * @return the Java key algorithm specifier, or null if the mapping is unavailable or indeterminable from the URI
128      */
129     public static String getKeyAlgorithmFromURI(String algorithmURI) {
130         // The default Apache config file currently only includes the key algorithm for
131         // the block ciphers and key wrap URI's. Note: could use a custom config file which contains others.
132         String apacheValue = DatatypeHelper.safeTrimOrNullString(JCEMapper.getJCEKeyAlgorithmFromURI(algorithmURI));
133         if (apacheValue != null) {
134             return apacheValue;
135         }
136 
137         // HMAC uses any symmetric key, so there is no implied specific key algorithm
138         if (isHMAC(algorithmURI)) {
139             return null;
140         }
141 
142         // As a last ditch fallback, check some known common and supported ones.
143         if (rsaAlgorithmURIs.contains(algorithmURI)) {
144             return "RSA";
145         }
146         if (dsaAlgorithmURIs.contains(algorithmURI)) {
147             return "DSA";
148         }
149         if (ecdsaAlgorithmURIs.contains(algorithmURI)) {
150             return "ECDSA";
151         }
152 
153         return null;
154     }
155 
156     /**
157      * Get the length of the key indicated by the algorithm URI, if applicable and available.
158      * 
159      * @param algorithmURI the algorithm URI to evaluate
160      * @return the length of the key indicated by the algorithm URI, or null if the length is either unavailable or
161      *         indeterminable from the URI
162      */
163     public static Integer getKeyLengthFromURI(String algorithmURI) {
164         Logger log = getLogger();
165         String algoClass = DatatypeHelper.safeTrimOrNullString(JCEMapper.getAlgorithmClassFromURI(algorithmURI));
166 
167         if (ApacheXMLSecurityConstants.ALGO_CLASS_BLOCK_ENCRYPTION.equals(algoClass)
168                 || ApacheXMLSecurityConstants.ALGO_CLASS_SYMMETRIC_KEY_WRAP.equals(algoClass)) {
169 
170             try {
171                 int keyLength = JCEMapper.getKeyLengthFromURI(algorithmURI);
172                 return new Integer(keyLength);
173             } catch (NumberFormatException e) {
174                 log.warn("XML Security config contained invalid key length value for algorithm URI: " + algorithmURI);
175             }
176         }
177 
178         log.info("Mapping from algorithm URI {} to key length not available", algorithmURI);
179         return null;
180     }
181 
182     /**
183      * Generates a random Java JCE symmetric Key object from the specified XML Encryption algorithm URI.
184      * 
185      * @param algoURI The XML Encryption algorithm URI
186      * @return a randomly-generated symmetric Key
187      * @throws NoSuchAlgorithmException thrown if the specified algorithm is invalid
188      * @throws KeyException thrown if the length of the key to generate could not be determined
189      */
190     public static SecretKey generateSymmetricKey(String algoURI) throws NoSuchAlgorithmException, KeyException {
191         Logger log = getLogger();
192         String jceAlgorithmName = getKeyAlgorithmFromURI(algoURI);
193         if (DatatypeHelper.isEmpty(jceAlgorithmName)) {
194             log.error("Mapping from algorithm URI '" + algoURI
195                     + "' to key algorithm not available, key generation failed");
196             throw new NoSuchAlgorithmException("Algorithm URI'" + algoURI + "' is invalid for key generation");
197         }
198         Integer keyLength = getKeyLengthFromURI(algoURI);
199         if (keyLength == null) {
200             log.error("Key length could not be determined from algorithm URI, can't generate key");
201             throw new KeyException("Key length not determinable from algorithm URI, could not generate new key");
202         }
203         KeyGenerator keyGenerator = KeyGenerator.getInstance(jceAlgorithmName);
204         keyGenerator.init(keyLength);
205         return keyGenerator.generateKey();
206     }
207 
208     /**
209      * Extract the encryption key from the credential.
210      * 
211      * @param credential the credential containing the encryption key
212      * @return the encryption key (either a public key or a secret (symmetric) key
213      */
214     public static Key extractEncryptionKey(Credential credential) {
215         if (credential == null) {
216             return null;
217         }
218         if (credential.getPublicKey() != null) {
219             return credential.getPublicKey();
220         } else {
221             return credential.getSecretKey();
222         }
223     }
224 
225     /**
226      * Extract the decryption key from the credential.
227      * 
228      * @param credential the credential containing the decryption key
229      * @return the decryption key (either a private key or a secret (symmetric) key
230      */
231     public static Key extractDecryptionKey(Credential credential) {
232         if (credential == null) {
233             return null;
234         }
235         if (credential.getPrivateKey() != null) {
236             return credential.getPrivateKey();
237         } else {
238             return credential.getSecretKey();
239         }
240     }
241 
242     /**
243      * Extract the signing key from the credential.
244      * 
245      * @param credential the credential containing the signing key
246      * @return the signing key (either a private key or a secret (symmetric) key
247      */
248     public static Key extractSigningKey(Credential credential) {
249         if (credential == null) {
250             return null;
251         }
252         if (credential.getPrivateKey() != null) {
253             return credential.getPrivateKey();
254         } else {
255             return credential.getSecretKey();
256         }
257     }
258 
259     /**
260      * Extract the verification key from the credential.
261      * 
262      * @param credential the credential containing the verification key
263      * @return the verification key (either a public key or a secret (symmetric) key
264      */
265     public static Key extractVerificationKey(Credential credential) {
266         if (credential == null) {
267             return null;
268         }
269         if (credential.getPublicKey() != null) {
270             return credential.getPublicKey();
271         } else {
272             return credential.getSecretKey();
273         }
274     }
275 
276     /**
277      * Get the key length in bits of the specified key.
278      * 
279      * @param key the key to evaluate
280      * @return length of the key in bits, or null if the length can not be determined
281      */
282     public static Integer getKeyLength(Key key) {
283         Logger log = getLogger();
284         // TODO investigate techniques (and use cases) to determine length in other cases,
285         // e.g. RSA and DSA keys, and non-RAW format symmetric keys
286         if (key instanceof SecretKey && "RAW".equals(key.getFormat())) {
287             return key.getEncoded().length * 8;
288         }
289         log.debug("Unable to determine length in bits of specified Key instance");
290         return null;
291     }
292 
293     /**
294      * Get a simple, minimal credential containing a secret (symmetric) key.
295      * 
296      * @param secretKey the symmetric key to wrap
297      * @return a credential containing the secret key specified
298      */
299     public static BasicCredential getSimpleCredential(SecretKey secretKey) {
300         if (secretKey == null) {
301             throw new IllegalArgumentException("A secret key is required");
302         }
303         BasicCredential cred = new BasicCredential();
304         cred.setSecretKey(secretKey);
305         return cred;
306     }
307 
308     /**
309      * Get a simple, minimal credential containing a public key, and optionally a private key.
310      * 
311      * @param publicKey the public key to wrap
312      * @param privateKey the private key to wrap, which may be null
313      * @return a credential containing the key(s) specified
314      */
315     public static BasicCredential getSimpleCredential(PublicKey publicKey, PrivateKey privateKey) {
316         if (publicKey == null) {
317             throw new IllegalArgumentException("A public key is required");
318         }
319         BasicCredential cred = new BasicCredential();
320         cred.setPublicKey(publicKey);
321         cred.setPrivateKey(privateKey);
322         return cred;
323     }
324 
325     /**
326      * Get a simple, minimal credential containing an end-entity X.509 certificate, and optionally a private key.
327      * 
328      * @param cert the end-entity certificate to wrap
329      * @param privateKey the private key to wrap, which may be null
330      * @return a credential containing the certificate and key specified
331      */
332     public static BasicX509Credential getSimpleCredential(X509Certificate cert, PrivateKey privateKey) {
333         if (cert == null) {
334             throw new IllegalArgumentException("A certificate is required");
335         }
336         BasicX509Credential cred = new BasicX509Credential();
337         cred.setEntityCertificate(cert);
338         cred.setPrivateKey(privateKey);
339         return cred;
340     }
341 
342     /**
343      * Decodes secret keys in DER and PEM format.
344      * 
345      * This method is not yet implemented.
346      * 
347      * @param key secret key
348      * @param password password if the key is encrypted or null if not
349      * 
350      * @return the decoded key
351      * 
352      * @throws KeyException thrown if the key can not be decoded
353      */
354     public static SecretKey decodeSecretKey(byte[] key, char[] password) throws KeyException {
355         // TODO
356         throw new UnsupportedOperationException("This method is not yet supported");
357     }
358 
359     /**
360      * Decodes RSA/DSA public keys in DER-encoded "SubjectPublicKeyInfo" format.
361      * 
362      * @param key encoded key
363      * @param password password if the key is encrypted or null if not
364      * 
365      * @return decoded key
366      * 
367      * @throws KeyException thrown if the key can not be decoded
368      */
369     public static PublicKey decodePublicKey(byte[] key, char[] password) throws KeyException {
370         X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key);
371         try {
372             return buildKey(keySpec, "RSA");
373         }
374         catch (KeyException ex) {
375         }
376         try {
377             return buildKey(keySpec, "DSA");
378         }
379         catch (KeyException ex) {
380         }
381         try {
382             return buildKey(keySpec, "EC");
383         }
384         catch (KeyException ex) {
385         }
386         throw new KeyException("Unsupported key type.");
387     }
388 
389     /**
390      * Derives the public key from either a DSA or RSA private key.
391      * 
392      * @param key the private key to derive the public key from
393      * 
394      * @return the derived public key
395      * 
396      * @throws KeyException thrown if the given private key is not a DSA or RSA key or there is a problem generating the
397      *             public key
398      */
399     public static PublicKey derivePublicKey(PrivateKey key) throws KeyException {
400         KeyFactory factory;
401         if (key instanceof DSAPrivateKey) {
402             DSAPrivateKey dsaKey = (DSAPrivateKey) key;
403             DSAParams keyParams = dsaKey.getParams();
404             BigInteger y = keyParams.getQ().modPow(dsaKey.getX(), keyParams.getP());
405             DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, keyParams.getP(), keyParams.getQ(), keyParams.getG());
406 
407             try {
408                 factory = KeyFactory.getInstance("DSA");
409                 return factory.generatePublic(pubKeySpec);
410             } catch (GeneralSecurityException e) {
411                 throw new KeyException("Unable to derive public key from DSA private key", e);
412             }
413         } else if (key instanceof RSAPrivateCrtKey) {
414             RSAPrivateCrtKey rsaKey = (RSAPrivateCrtKey) key;
415             RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent());
416 
417             try {
418                 factory = KeyFactory.getInstance("RSA");
419                 return factory.generatePublic(pubKeySpec);
420             } catch (GeneralSecurityException e) {
421                 throw new KeyException("Unable to derive public key from RSA private key", e);
422             }
423         } else {
424             throw new KeyException("Private key was not a DSA or RSA key");
425         }
426     }
427 
428     /**
429      * Decodes RSA/DSA private keys in DER, PEM, or PKCS#8 (encrypted or unencrypted) formats.
430      * 
431      * @param key encoded key
432      * @param password decryption password or null if the key is not encrypted
433      * 
434      * @return deocded private key
435      * 
436      * @throws KeyException thrown if the key can not be decoded
437      */
438     public static PrivateKey decodePrivateKey(File key, char[] password) throws KeyException {
439         if (!key.exists()) {
440             throw new KeyException("Key file " + key.getAbsolutePath() + " does not exist");
441         }
442 
443         if (!key.canRead()) {
444             throw new KeyException("Key file " + key.getAbsolutePath() + " is not readable");
445         }
446 
447         try {
448             return decodePrivateKey(DatatypeHelper.fileToByteArray(key), password);
449         } catch (IOException e) {
450             throw new KeyException("Error reading Key file " + key.getAbsolutePath(), e);
451         }
452     }
453 
454     /**
455      * Decodes RSA/DSA private keys in DER, PEM, or PKCS#8 (encrypted or unencrypted) formats.
456      * 
457      * @param key encoded key
458      * @param password decryption password or null if the key is not encrypted
459      * 
460      * @return deocded private key
461      * 
462      * @throws KeyException thrown if the key can not be decoded
463      */
464     public static PrivateKey decodePrivateKey(byte[] key, char[] password) throws KeyException {
465         try {
466             PKCS8Key deocodedKey = new PKCS8Key(key, password);
467             return deocodedKey.getPrivateKey();
468         } catch (GeneralSecurityException e) {
469             throw new KeyException("Unable to decode private key", e);
470         }
471     }
472 
473     /**
474      * Build Java certificate from base64 encoding.
475      * 
476      * @param base64Cert base64-encoded certificate
477      * @return a native Java X509 certificate
478      * @throws CertificateException thrown if there is an error constructing certificate
479      */
480     public static java.security.cert.X509Certificate buildJavaX509Cert(String base64Cert) throws CertificateException {
481         CertificateFactory  cf = CertificateFactory.getInstance("X.509");
482         ByteArrayInputStream input = new ByteArrayInputStream(Base64.decode(base64Cert));
483         return (java.security.cert.X509Certificate) cf.generateCertificate(input);
484     }
485     
486     /**
487      * Build Java CRL from base64 encoding.
488      * 
489      * @param base64CRL base64-encoded CRL
490      * @return a native Java X509 CRL
491      * @throws CertificateException thrown if there is an error constructing certificate
492      * @throws CRLException  thrown if there is an error constructing CRL
493      */
494     public static java.security.cert.X509CRL buildJavaX509CRL(String base64CRL)
495             throws CertificateException, CRLException {
496         CertificateFactory  cf = CertificateFactory.getInstance("X.509");
497         ByteArrayInputStream input = new ByteArrayInputStream(Base64.decode(base64CRL));
498         return (java.security.cert.X509CRL) cf.generateCRL(input);
499     }
500     
501     /**
502      * Build Java DSA public key from base64 encoding.
503      * 
504      * @param base64EncodedKey base64-encoded DSA public key
505      * @return a native Java DSAPublicKey
506      * @throws KeyException thrown if there is an error constructing key
507      */
508     public static DSAPublicKey buildJavaDSAPublicKey(String base64EncodedKey) throws KeyException {
509         X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(base64EncodedKey));
510         return (DSAPublicKey) buildKey(keySpec, "DSA");
511     }
512     
513     /**
514      * Build Java RSA public key from base64 encoding.
515      * 
516      * @param base64EncodedKey base64-encoded RSA public key
517      * @return a native Java RSAPublicKey
518      * @throws KeyException thrown if there is an error constructing key
519      */
520     public static RSAPublicKey buildJavaRSAPublicKey(String base64EncodedKey) throws KeyException {
521         X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(base64EncodedKey));
522         return (RSAPublicKey) buildKey(keySpec, "RSA");
523     }
524     
525     /**
526      * Build Java EC public key from base64 encoding.
527      * 
528      * @param base64EncodedKey base64-encoded EC public key
529      * @return a native Java ECPublicKey
530      * @throws KeyException thrown if there is an error constructing key
531      */
532     public static ECPublicKey buildJavaECPublicKey(String base64EncodedKey) throws KeyException {
533         X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decode(base64EncodedKey));
534         return (ECPublicKey) buildKey(keySpec, "EC");
535     }
536     
537     /**
538      * Build Java RSA private key from base64 encoding.
539      * 
540      * @param base64EncodedKey base64-encoded RSA private key
541      * @return a native Java RSAPrivateKey
542      * @throws KeyException thrown if there is an error constructing key
543      */
544     public static RSAPrivateKey buildJavaRSAPrivateKey(String base64EncodedKey)  throws KeyException {
545         PrivateKey key =  buildJavaPrivateKey(base64EncodedKey);
546         if (! (key instanceof RSAPrivateKey)) {
547             throw new KeyException("Generated key was not an RSAPrivateKey instance");
548         }
549         return (RSAPrivateKey) key;
550     }
551     
552     /**
553      * Build Java DSA private key from base64 encoding.
554      * 
555      * @param base64EncodedKey base64-encoded DSA private key
556      * @return a native Java DSAPrivateKey
557      * @throws KeyException thrown if there is an error constructing key
558      */
559     public static DSAPrivateKey buildJavaDSAPrivateKey(String base64EncodedKey)  throws KeyException {
560         PrivateKey key =  buildJavaPrivateKey(base64EncodedKey);
561         if (! (key instanceof DSAPrivateKey)) {
562             throw new KeyException("Generated key was not a DSAPrivateKey instance");
563         }
564         return (DSAPrivateKey) key;
565     }
566     
567     /**
568      * Build Java private key from base64 encoding. The key should have no password.
569      * 
570      * @param base64EncodedKey base64-encoded private key
571      * @return a native Java PrivateKey
572      * @throws KeyException thrown if there is an error constructing key
573      */
574     public static PrivateKey buildJavaPrivateKey(String base64EncodedKey)  throws KeyException {
575         return SecurityHelper.decodePrivateKey(Base64.decode(base64EncodedKey), null);
576     }
577     
578     /**
579      * Generates a public key from the given key spec.
580      * 
581      * @param keySpec {@link KeySpec} specification for the key
582      * @param keyAlgorithm key generation algorithm, only DSA and RSA supported
583      * 
584      * @return the generated {@link PublicKey}
585      * 
586      * @throws KeyException thrown if the key algorithm is not supported by the JCE or the key spec does not
587      *             contain valid information
588      */
589     public static PublicKey buildKey(KeySpec keySpec, String keyAlgorithm) throws KeyException {
590         try {
591             KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
592             return keyFactory.generatePublic(keySpec);
593         } catch (NoSuchAlgorithmException e) {
594             throw new KeyException(keyAlgorithm + "algorithm is not supported by the JCE", e);
595         } catch (InvalidKeySpecException e) {
596             throw new KeyException("Invalid key information", e);
597         }
598     }
599     
600     /**
601      * Randomly generates a Java JCE symmetric Key object from the specified XML Encryption algorithm URI.
602      * 
603      * @param algoURI  The XML Encryption algorithm URI
604      * @return a randomly-generated symmteric key
605      * @throws NoSuchProviderException  provider not found
606      * @throws NoSuchAlgorithmException algorithm not found
607      */
608     public static SecretKey generateKeyFromURI(String algoURI) 
609             throws NoSuchAlgorithmException, NoSuchProviderException {
610         String jceAlgorithmName = JCEMapper.getJCEKeyAlgorithmFromURI(algoURI);
611         int keyLength = JCEMapper.getKeyLengthFromURI(algoURI);
612         return generateKey(jceAlgorithmName, keyLength, null);
613     }
614     
615     /**
616      * Randomly generates a Java JCE KeyPair object from the specified XML Encryption algorithm URI.
617      * 
618      * @param algoURI  The XML Encryption algorithm URI
619      * @param keyLength  the length of key to generate
620      * @return a randomly-generated KeyPair
621      * @throws NoSuchProviderException  provider not found
622      * @throws NoSuchAlgorithmException  algorithm not found
623      */
624     public static KeyPair generateKeyPairFromURI(String algoURI, int keyLength) 
625             throws NoSuchAlgorithmException, NoSuchProviderException {
626         String jceAlgorithmName = JCEMapper.getJCEKeyAlgorithmFromURI(algoURI);
627         return generateKeyPair(jceAlgorithmName, keyLength, null);
628     }
629     
630     /**
631      * Generate a random symmetric key.
632      * 
633      * @param algo key algorithm
634      * @param keyLength key length
635      * @param provider JCA provider
636      * @return randomly generated symmetric key
637      * @throws NoSuchAlgorithmException algorithm not found
638      * @throws NoSuchProviderException provider not found
639      */
640     public static SecretKey generateKey(String algo, int keyLength, String provider) 
641             throws NoSuchAlgorithmException, NoSuchProviderException {
642         SecretKey key = null;
643         KeyGenerator keyGenerator = null;
644         if (provider != null) {
645             keyGenerator = KeyGenerator.getInstance(algo, provider);
646         } else {
647             keyGenerator = KeyGenerator.getInstance(algo);
648         }
649         keyGenerator.init(keyLength);
650         key = keyGenerator.generateKey();
651         return key;
652     }
653     
654     /**
655      * Generate a random asymmetric key pair.
656      * 
657      * @param algo key algorithm
658      * @param keyLength key length
659      * @param provider JCA provider
660      * @return randomly generated key
661      * @throws NoSuchAlgorithmException algorithm not found
662      * @throws NoSuchProviderException provider not found
663      */
664     public static KeyPair generateKeyPair(String algo, int keyLength, String provider) 
665             throws NoSuchAlgorithmException, NoSuchProviderException {
666         KeyPairGenerator keyGenerator = null;
667         if (provider != null) {
668             keyGenerator = KeyPairGenerator.getInstance(algo, provider);
669         } else {
670             keyGenerator = KeyPairGenerator.getInstance(algo);
671         }
672         keyGenerator.initialize(keyLength);
673         return keyGenerator.generateKeyPair();
674     }
675     
676     /**
677      * Generate a random symmetric key and return in a BasicCredential.
678      * 
679      * @param algorithmURI The XML Encryption algorithm URI
680      * @return a basic credential containing a randomly generated symmetric key
681      * @throws NoSuchAlgorithmException algorithm not found
682      * @throws NoSuchProviderException provider not found
683      */
684     public static Credential generateKeyAndCredential(String algorithmURI) 
685             throws NoSuchAlgorithmException, NoSuchProviderException {
686         SecretKey key = generateKeyFromURI(algorithmURI);
687         BasicCredential credential = new BasicCredential();
688         credential.setSecretKey(key);
689         return credential;
690     }
691     
692     /**
693      * Generate a random asymmetric key pair and return in a BasicCredential.
694      * 
695      * @param algorithmURI The XML Encryption algorithm URI
696      * @param keyLength key length
697      * @param includePrivate if true, the private key will be included as well
698      * @return a basic credential containing a randomly generated asymmetric key pair
699      * @throws NoSuchAlgorithmException algorithm not found
700      * @throws NoSuchProviderException provider not found
701      */
702     public static Credential generateKeyPairAndCredential(String algorithmURI, int keyLength, boolean includePrivate) 
703             throws NoSuchAlgorithmException, NoSuchProviderException {
704         KeyPair keyPair = generateKeyPairFromURI(algorithmURI, keyLength);
705         BasicCredential credential = new BasicCredential();
706         credential.setPublicKey(keyPair.getPublic());
707         if (includePrivate) {
708             credential.setPrivateKey(keyPair.getPrivate());
709         }
710         return credential;
711     }
712     
713     /**
714      * Get a basic KeyInfo credential resolver which can process standard inline
715      * data - RSAKeyValue, DSAKeyValue, X509Data.
716      * 
717      * @return a new KeyInfoCredentialResolver instance
718      */
719     public static KeyInfoCredentialResolver buildBasicInlineKeyInfoResolver() {
720         List<KeyInfoProvider> providers = new ArrayList<KeyInfoProvider>();
721         providers.add( new RSAKeyValueProvider() );
722         providers.add( new DSAKeyValueProvider() );
723         providers.add( new InlineX509DataProvider() );
724         return new BasicProviderKeyInfoCredentialResolver(providers);
725     }
726     
727     /**
728      * Compare the supplied public and private keys, and determine if they correspond to the same key pair.
729      * 
730      * @param pubKey the public key
731      * @param privKey the private key
732      * @return true if the public and private are from the same key pair, false if not
733      * @throws SecurityException if the keys can not be evaluated, or if the key algorithm is unsupported or unknown
734      */
735     public static boolean matchKeyPair(PublicKey pubKey, PrivateKey privKey) throws SecurityException {
736         Logger log = getLogger();
737         // This approach attempts to match the keys by signing and then validating some known data.
738 
739         if (pubKey == null || privKey == null) {
740             throw new SecurityException("Either public or private key was null");
741         }
742 
743         // Need to dynamically determine the JCA signature algorithm ID to use from the key algorithm.
744         // Don't currently have a direct mapping, so have to map to XML Signature algorithm URI first,
745         // then map that to JCA algorithm ID.
746         SecurityConfiguration secConfig = Configuration.getGlobalSecurityConfiguration();
747         if (secConfig == null) {
748             throw new SecurityException("Global security configuration was null, could not resolve signing algorithm");
749         }
750         String algoURI = secConfig.getSignatureAlgorithmURI(privKey.getAlgorithm());
751         if (algoURI == null) {
752             throw new SecurityException("Can't determine algorithm URI from key algorithm: " + privKey.getAlgorithm());
753         }
754         String jcaAlgoID = getAlgorithmIDFromURI(algoURI);
755         if (jcaAlgoID == null) {
756             throw new SecurityException("Can't determine JCA algorithm ID from algorithm URI: " + algoURI);
757         }
758 
759         if (log.isDebugEnabled()) {
760             log.debug("Attempting to match key pair containing key algorithms public '{}' private '{}', "
761                     + "using JCA signature algorithm '{}'", new Object[] { pubKey.getAlgorithm(),
762                     privKey.getAlgorithm(), jcaAlgoID, });
763         }
764 
765         byte[] data = "This is the data to sign".getBytes();
766         byte[] signature = SigningUtil.sign(privKey, jcaAlgoID, data);
767         return SigningUtil.verify(pubKey, jcaAlgoID, signature, data);
768     }
769 
770     /**
771      * Prepare a {@link Signature} with necessary additional information prior to signing.
772      * 
773      * <p>
774      * <strong>NOTE:</strong>Since this operation modifies the specified Signature object, it should be called
775      * <strong>prior</strong> to marshalling the Signature object.
776      * </p>
777      * 
778      * <p>
779      * The following Signature values will be added:
780      * <ul>
781      * <li>signature algorithm URI</li>
782      * <li>canonicalization algorithm URI</li>
783      * <li>HMAC output length (if applicable and a value is configured)</li>
784      * <li>a {@link KeyInfo} element representing the signing credential</li>
785      * </ul>
786      * </p>
787      * 
788      * <p>
789      * Existing (non-null) values of these parameters on the specified signature will <strong>NOT</strong> be
790      * overwritten, however.
791      * </p>
792      * 
793      * <p>
794      * All values are determined by the specified {@link SecurityConfiguration}. If a security configuration is not
795      * supplied, the global security configuration ({@link Configuration#getGlobalSecurityConfiguration()}) will be
796      * used.
797      * </p>
798      * 
799      * <p>
800      * The signature algorithm URI and optional HMAC output length are derived from the signing credential.
801      * </p>
802      * 
803      * <p>
804      * The KeyInfo to be generated is based on the {@link NamedKeyInfoGeneratorManager} defined in the security
805      * configuration, and is determined by the type of the signing credential and an optional KeyInfo generator manager
806      * name. If the latter is ommited, the default manager ({@link NamedKeyInfoGeneratorManager#getDefaultManager()})
807      * of the security configuration's named generator manager will be used.
808      * </p>
809      * 
810      * @param signature the Signature to be updated
811      * @param signingCredential the credential with which the Signature will be computed
812      * @param config the SecurityConfiguration to use (may be null)
813      * @param keyInfoGenName the named KeyInfoGeneratorManager configuration to use (may be null)
814      * @throws SecurityException thrown if there is an error generating the KeyInfo from the signing credential
815      */
816     public static void prepareSignatureParams(Signature signature, Credential signingCredential,
817             SecurityConfiguration config, String keyInfoGenName) throws SecurityException {
818         Logger log = getLogger();
819 
820         SecurityConfiguration secConfig;
821         if (config != null) {
822             secConfig = config;
823         } else {
824             secConfig = Configuration.getGlobalSecurityConfiguration();
825         }
826 
827         // The algorithm URI is derived from the credential
828         String signAlgo = signature.getSignatureAlgorithm();
829         if (signAlgo == null) {
830             signAlgo = secConfig.getSignatureAlgorithmURI(signingCredential);
831             signature.setSignatureAlgorithm(signAlgo);
832         }
833 
834         // If we're doing HMAC, set the output length
835         if (SecurityHelper.isHMAC(signAlgo)) {
836             if (signature.getHMACOutputLength() == null) {
837                 signature.setHMACOutputLength(secConfig.getSignatureHMACOutputLength());
838             }
839         }
840 
841         if (signature.getCanonicalizationAlgorithm() == null) {
842             signature.setCanonicalizationAlgorithm(secConfig.getSignatureCanonicalizationAlgorithm());
843         }
844 
845         if (signature.getKeyInfo() == null) {
846             KeyInfoGenerator kiGenerator = getKeyInfoGenerator(signingCredential, secConfig, keyInfoGenName);
847             if (kiGenerator != null) {
848                 try {
849                     KeyInfo keyInfo = kiGenerator.generate(signingCredential);
850                     signature.setKeyInfo(keyInfo);
851                 } catch (SecurityException e) {
852                     log.error("Error generating KeyInfo from credential", e);
853                     throw e;
854                 }
855             } else {
856                 log.info("No factory for named KeyInfoGenerator {} was found for credential type {}", keyInfoGenName,
857                         signingCredential.getCredentialType().getName());
858                 log.info("No KeyInfo will be generated for Signature");
859             }
860         }
861     }
862 
863     /**
864      * Build an instance of {@link EncryptionParameters} suitable for passing to an
865      * {@link org.opensaml.xml.encryption.Encrypter}.
866      * 
867      * <p>
868      * The following parameter values will be added:
869      * <ul>
870      * <li>the encryption credential (optional)</li>
871      * <li>encryption algorithm URI</li>
872      * <li>an appropriate {@link KeyInfoGenerator} instance which will be used to generate a {@link KeyInfo} element
873      * from the encryption credential</li>
874      * </ul>
875      * </p>
876      * 
877      * <p>
878      * All values are determined by the specified {@link SecurityConfiguration}. If a security configuration is not
879      * supplied, the global security configuration ({@link Configuration#getGlobalSecurityConfiguration()}) will be
880      * used.
881      * </p>
882      * 
883      * <p>
884      * The encryption algorithm URI is derived from the optional supplied encryption credential. If omitted, the value
885      * of {@link SecurityConfiguration#getAutoGeneratedDataEncryptionKeyAlgorithmURI()} will be used.
886      * </p>
887      * 
888      * <p>
889      * The KeyInfoGenerator to be used is based on the {@link NamedKeyInfoGeneratorManager} defined in the security
890      * configuration, and is determined by the type of the signing credential and an optional KeyInfo generator manager
891      * name. If the latter is ommited, the default manager ({@link NamedKeyInfoGeneratorManager#getDefaultManager()})
892      * of the security configuration's named generator manager will be used.
893      * </p>
894      * 
895      * @param encryptionCredential the credential with which the data will be encrypted (may be null)
896      * @param config the SecurityConfiguration to use (may be null)
897      * @param keyInfoGenName the named KeyInfoGeneratorManager configuration to use (may be null)
898      * @return a new instance of EncryptionParameters
899      */
900     public static EncryptionParameters buildDataEncryptionParams(Credential encryptionCredential,
901             SecurityConfiguration config, String keyInfoGenName) {
902         Logger log = getLogger();
903 
904         SecurityConfiguration secConfig;
905         if (config != null) {
906             secConfig = config;
907         } else {
908             secConfig = Configuration.getGlobalSecurityConfiguration();
909         }
910 
911         EncryptionParameters encParams = new EncryptionParameters();
912         encParams.setEncryptionCredential(encryptionCredential);
913 
914         if (encryptionCredential == null) {
915             encParams.setAlgorithm(secConfig.getAutoGeneratedDataEncryptionKeyAlgorithmURI());
916         } else {
917             encParams.setAlgorithm(secConfig.getDataEncryptionAlgorithmURI(encryptionCredential));
918 
919             KeyInfoGenerator kiGenerator = getKeyInfoGenerator(encryptionCredential, secConfig, keyInfoGenName);
920             if (kiGenerator != null) {
921                 encParams.setKeyInfoGenerator(kiGenerator);
922             } else {
923                 log.info("No factory for named KeyInfoGenerator {} was found for credential type{}", keyInfoGenName,
924                         encryptionCredential.getCredentialType().getName());
925                 log.info("No KeyInfo will be generated for EncryptedData");
926             }
927         }
928 
929         return encParams;
930     }
931 
932     /**
933      * Build an instance of {@link KeyEncryptionParameters} suitable for passing to an
934      * {@link org.opensaml.xml.encryption.Encrypter}.
935      * 
936      * <p>
937      * The following parameter values will be added:
938      * <ul>
939      * <li>the key encryption credential</li>
940      * <li>key transport encryption algorithm URI</li>
941      * <li>an appropriate {@link KeyInfoGenerator} instance which will be used to generate a {@link KeyInfo} element
942      * from the key encryption credential</li>
943      * <li>intended recipient of the resultant encrypted key (optional)</li>
944      * </ul>
945      * </p>
946      * 
947      * <p>
948      * All values are determined by the specified {@link SecurityConfiguration}. If a security configuration is not
949      * supplied, the global security configuration ({@link Configuration#getGlobalSecurityConfiguration()}) will be
950      * used.
951      * </p>
952      * 
953      * <p>
954      * The encryption algorithm URI is derived from the optional supplied encryption credential. If omitted, the value
955      * of {@link SecurityConfiguration#getAutoGeneratedDataEncryptionKeyAlgorithmURI()} will be used.
956      * </p>
957      * 
958      * <p>
959      * The KeyInfoGenerator to be used is based on the {@link NamedKeyInfoGeneratorManager} defined in the security
960      * configuration, and is determined by the type of the signing credential and an optional KeyInfo generator manager
961      * name. If the latter is ommited, the default manager ({@link NamedKeyInfoGeneratorManager#getDefaultManager()})
962      * of the security configuration's named generator manager will be used.
963      * </p>
964      * 
965      * @param encryptionCredential the credential with which the key will be encrypted
966      * @param wrappedKeyAlgorithm the JCA key algorithm name of the key to be encrypted (may be null)
967      * @param config the SecurityConfiguration to use (may be null)
968      * @param keyInfoGenName the named KeyInfoGeneratorManager configuration to use (may be null)
969      * @param recipient the intended recipient of the resultant encrypted key, typically the owner of the key encryption
970      *            key (may be null)
971      * @return a new instance of KeyEncryptionParameters
972      * @throws SecurityException if encryption credential is not supplied
973      * 
974      */
975     public static KeyEncryptionParameters buildKeyEncryptionParams(Credential encryptionCredential,
976             String wrappedKeyAlgorithm, SecurityConfiguration config, String keyInfoGenName, String recipient)
977             throws SecurityException {
978         Logger log = getLogger();
979 
980         SecurityConfiguration secConfig;
981         if (config != null) {
982             secConfig = config;
983         } else {
984             secConfig = Configuration.getGlobalSecurityConfiguration();
985         }
986 
987         KeyEncryptionParameters kekParams = new KeyEncryptionParameters();
988         kekParams.setEncryptionCredential(encryptionCredential);
989 
990         if (encryptionCredential == null) {
991             throw new SecurityException("Key encryption credential may not be null");
992         }
993 
994         kekParams.setAlgorithm(secConfig.getKeyTransportEncryptionAlgorithmURI(encryptionCredential,
995                 wrappedKeyAlgorithm));
996 
997         KeyInfoGenerator kiGenerator = getKeyInfoGenerator(encryptionCredential, secConfig, keyInfoGenName);
998         if (kiGenerator != null) {
999             kekParams.setKeyInfoGenerator(kiGenerator);
1000         } else {
1001             log.info("No factory for named KeyInfoGenerator {} was found for credential type {}", keyInfoGenName,
1002                     encryptionCredential.getCredentialType().getName());
1003             log.info("No KeyInfo will be generated for EncryptedKey");
1004         }
1005 
1006         kekParams.setRecipient(recipient);
1007 
1008         return kekParams;
1009     }
1010 
1011     /**
1012      * Obtains a {@link KeyInfoGenerator} for the specified {@link Credential}.
1013      * 
1014      * <p>
1015      * The KeyInfoGenerator returned is based on the {@link NamedKeyInfoGeneratorManager} defined by the specified
1016      * security configuration via {@link SecurityConfiguration#getKeyInfoGeneratorManager()}, and is determined by the
1017      * type of the signing credential and an optional KeyInfo generator manager name. If the latter is ommited, the
1018      * default manager ({@link NamedKeyInfoGeneratorManager#getDefaultManager()}) of the security configuration's
1019      * named generator manager will be used.
1020      * </p>
1021      * 
1022      * <p>
1023      * The generator is determined by the specified {@link SecurityConfiguration}. If a security configuration is not
1024      * supplied, the global security configuration ({@link Configuration#getGlobalSecurityConfiguration()}) will be
1025      * used.
1026      * </p>
1027      * 
1028      * @param credential the credential for which a generator is desired
1029      * @param config the SecurityConfiguration to use (may be null)
1030      * @param keyInfoGenName the named KeyInfoGeneratorManager configuration to use (may be null)
1031      * @return a KeyInfoGenerator appropriate for the specified credential
1032      */
1033     public static KeyInfoGenerator getKeyInfoGenerator(Credential credential, SecurityConfiguration config,
1034             String keyInfoGenName) {
1035 
1036         SecurityConfiguration secConfig;
1037         if (config != null) {
1038             secConfig = config;
1039         } else {
1040             secConfig = Configuration.getGlobalSecurityConfiguration();
1041         }
1042 
1043         NamedKeyInfoGeneratorManager kiMgr = secConfig.getKeyInfoGeneratorManager();
1044         if (kiMgr != null) {
1045             KeyInfoGeneratorFactory kiFactory = null;
1046             if (DatatypeHelper.isEmpty(keyInfoGenName)) {
1047                 kiFactory = kiMgr.getDefaultManager().getFactory(credential);
1048             } else {
1049                 kiFactory = kiMgr.getFactory(keyInfoGenName, credential);
1050             }
1051             if (kiFactory != null) {
1052                 return kiFactory.newInstance();
1053             }
1054         }
1055         return null;
1056     }
1057     
1058     /**
1059      * Get an SLF4J Logger.
1060      * 
1061      * @return a Logger instance
1062      */
1063     private static Logger getLogger() {
1064         return LoggerFactory.getLogger(SecurityHelper.class);
1065     }
1066 
1067     static {
1068         // We use some Apache XML Security utility functions, so need to make sure library
1069         // is initialized.
1070         if (!Init.isInitialized()) {
1071             Init.init();
1072         }
1073 
1074         // Additonal algorithm URI to JCA key algorithm mappins, beyond what is currently
1075         // supplied in the Apache XML Security mapper config.
1076         dsaAlgorithmURIs = new LazySet<String>();
1077         dsaAlgorithmURIs.add(SignatureConstants.ALGO_ID_SIGNATURE_DSA);
1078 
1079         ecdsaAlgorithmURIs = new LazySet<String>();
1080         ecdsaAlgorithmURIs.add(SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA1);
1081 
1082         rsaAlgorithmURIs = new HashSet<String>(10);
1083         rsaAlgorithmURIs.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
1084         rsaAlgorithmURIs.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
1085         rsaAlgorithmURIs.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA384);
1086         rsaAlgorithmURIs.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512);
1087         rsaAlgorithmURIs.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512);
1088         rsaAlgorithmURIs.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_RIPEMD160);
1089         rsaAlgorithmURIs.add(SignatureConstants.ALGO_ID_SIGNATURE_NOT_RECOMMENDED_RSA_MD5);
1090     }
1091 }