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.security.GeneralSecurityException;
20  import java.security.Key;
21  import java.security.PrivateKey;
22  import java.security.PublicKey;
23  import java.security.Signature;
24  import java.util.Arrays;
25  
26  import javax.crypto.Mac;
27  
28  import org.bouncycastle.util.encoders.Hex;
29  import org.opensaml.xml.security.credential.Credential;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /**
34   * A utility class for computing and verifying raw signatures and MAC values.
35   */
36  public final class SigningUtil {
37  
38      /** Constructor. */
39      private SigningUtil() {
40      }
41  
42      /**
43       * Compute the signature or MAC value over the supplied input.
44       * 
45       * It is up to the caller to ensure that the specified algorithm URI is consistent with the type of signing key
46       * supplied in the signing credential.
47       * 
48       * @param signingCredential the credential containing the signing key
49       * @param algorithmURI the algorithm URI to use
50       * @param input the input over which to compute the signature
51       * @return the computed signature or MAC value
52       * @throws SecurityException throw if the computation process results in an error
53       */
54      public static byte[] signWithURI(Credential signingCredential, String algorithmURI, byte[] input)
55              throws SecurityException {
56  
57          String jcaAlgorithmID = SecurityHelper.getAlgorithmIDFromURI(algorithmURI);
58          if (jcaAlgorithmID == null) {
59              throw new SecurityException("Could not derive JCA algorithm identifier from algorithm URI");
60          }
61  
62          boolean isHMAC = SecurityHelper.isHMAC(algorithmURI);
63  
64          return sign(signingCredential, jcaAlgorithmID, isHMAC, input);
65      }
66  
67      /**
68       * Compute the signature or MAC value over the supplied input.
69       * 
70       * It is up to the caller to ensure that the specified algorithm ID and isMAC flag are consistent with the type of
71       * signing key supplied in the signing credential.
72       * 
73       * @param signingCredential the credential containing the signing key
74       * @param jcaAlgorithmID the Java JCA algorithm ID to use
75       * @param isMAC flag indicating whether the operation to be performed is a signature or MAC computation
76       * @param input the input over which to compute the signature
77       * @return the computed signature or MAC value
78       * @throws SecurityException throw if the computation process results in an error
79       */
80      public static byte[] sign(Credential signingCredential, String jcaAlgorithmID, boolean isMAC, byte[] input)
81              throws SecurityException {
82          Logger log = getLogger();
83  
84          Key signingKey = SecurityHelper.extractSigningKey(signingCredential);
85          if (signingKey == null) {
86              log.error("No signing key supplied in signing credential for signature computation");
87              throw new SecurityException("No signing key supplied in signing credential");
88          }
89  
90          if (isMAC) {
91              return signMAC(signingKey, jcaAlgorithmID, input);
92          } else if (signingKey instanceof PrivateKey) {
93              return sign((PrivateKey) signingKey, jcaAlgorithmID, input);
94          } else {
95              log.error("No PrivateKey present in signing credential for signature computation");
96              throw new SecurityException("No PrivateKey supplied for signing");
97          }
98      }
99  
100     /**
101      * Compute the raw signature value over the supplied input.
102      * 
103      * It is up to the caller to ensure that the specified algorithm ID is consistent with the type of signing key
104      * supplied.
105      * 
106      * @param signingKey the private key with which to compute the signature
107      * @param jcaAlgorithmID the Java JCA algorithm ID to use
108      * @param input the input over which to compute the signature
109      * @return the computed signature value
110      * @throws SecurityException thrown if the signature computation results in an error
111      */
112     public static byte[] sign(PrivateKey signingKey, String jcaAlgorithmID, byte[] input) throws SecurityException {
113         Logger log = getLogger();
114         log.debug("Computing signature over input using private key of type {} and JCA algorithm ID {}", signingKey
115                 .getAlgorithm(), jcaAlgorithmID);
116 
117         try {
118             Signature signature = Signature.getInstance(jcaAlgorithmID);
119             signature.initSign(signingKey);
120             signature.update(input);
121             byte[] rawSignature = signature.sign();
122             log.debug("Computed signature: {}", new String(Hex.encode(rawSignature)));
123             return rawSignature;
124         } catch (GeneralSecurityException e) {
125             log.error("Error during signature generation", e);
126             throw new SecurityException("Error during signature generation", e);
127         }
128     }
129 
130     /**
131      * Compute the Message Authentication Code (MAC) value over the supplied input.
132      * 
133      * It is up to the caller to ensure that the specified algorithm ID is consistent with the type of signing key
134      * supplied.
135      * 
136      * @param signingKey the key with which to compute the MAC
137      * @param jcaAlgorithmID the Java JCA algorithm ID to use
138      * @param input the input over which to compute the MAC
139      * @return the computed MAC value
140      * @throws SecurityException thrown if the MAC computation results in an error
141      */
142     public static byte[] signMAC(Key signingKey, String jcaAlgorithmID, byte[] input) throws SecurityException {
143         Logger log = getLogger();
144         log.debug("Computing MAC over input using key of type {} and JCA algorithm ID {}", signingKey.getAlgorithm(),
145                 jcaAlgorithmID);
146 
147         try {
148             Mac mac = Mac.getInstance(jcaAlgorithmID);
149             mac.init(signingKey);
150             mac.update(input);
151             byte[] rawMAC = mac.doFinal();
152             log.debug("Computed MAC: {}", new String(Hex.encode(rawMAC)));
153             return rawMAC;
154         } catch (GeneralSecurityException e) {
155             log.error("Error during MAC generation", e);
156             throw new SecurityException("Error during MAC generation", e);
157         }
158     }
159 
160     /**
161      * Verify the signature value computed over the supplied input against the supplied signature value.
162      * 
163      * It is up to the caller to ensure that the specified algorithm URI are consistent with the type of verification
164      * credential supplied.
165      * 
166      * @param verificationCredential the credential containing the verification key
167      * @param algorithmURI the algorithm URI to use
168      * @param signature the computed signature value received from the signer
169      * @param input the input over which the signature is computed and verified
170      * @return true if the signature value computed over the input using the supplied key and algorithm ID is identical
171      *         to the supplied signature value
172      * @throws SecurityException thrown if the signature computation or verification process results in an error
173      */
174     public static boolean verifyWithURI(Credential verificationCredential, String algorithmURI, byte[] signature,
175             byte[] input) throws SecurityException {
176 
177         String jcaAlgorithmID = SecurityHelper.getAlgorithmIDFromURI(algorithmURI);
178         if (jcaAlgorithmID == null) {
179             throw new SecurityException("Could not derive JCA algorithm identifier from algorithm URI");
180         }
181 
182         boolean isHMAC = SecurityHelper.isHMAC(algorithmURI);
183 
184         return verify(verificationCredential, jcaAlgorithmID, isHMAC, signature, input);
185     }
186 
187     /**
188      * Verify the signature value computed over the supplied input against the supplied signature value.
189      * 
190      * It is up to the caller to ensure that the specified algorithm ID and isMAC flag are consistent with the type of
191      * verification credential supplied.
192      * 
193      * @param verificationCredential the credential containing the verification key
194      * @param jcaAlgorithmID the Java JCA algorithm ID to use
195      * @param isMAC flag indicating whether the operation to be performed is a signature or MAC computation
196      * @param signature the computed signature value received from the signer
197      * @param input the input over which the signature is computed and verified
198      * @return true if the signature value computed over the input using the supplied key and algorithm ID is identical
199      *         to the supplied signature value
200      * @throws SecurityException thrown if the signature computation or verification process results in an error
201      */
202     public static boolean verify(Credential verificationCredential, String jcaAlgorithmID, boolean isMAC,
203             byte[] signature, byte[] input) throws SecurityException {
204         Logger log = getLogger();
205 
206         Key verificationKey = SecurityHelper.extractVerificationKey(verificationCredential);
207         if (verificationKey == null) {
208             log.error("No verification key supplied in verification credential for signature verification");
209             throw new SecurityException("No verification key supplied in verification credential");
210         }
211 
212         if (isMAC) {
213             return verifyMAC(verificationKey, jcaAlgorithmID, signature, input);
214         } else if (verificationKey instanceof PublicKey) {
215             return verify((PublicKey) verificationKey, jcaAlgorithmID, signature, input);
216         } else {
217             log.error("No PublicKey present in verification credential for signature verification");
218             throw new SecurityException("No PublicKey supplied for signature verification");
219         }
220     }
221 
222     /**
223      * Verify the signature value computed over the supplied input against the supplied signature value.
224      * 
225      * It is up to the caller to ensure that the specified algorithm ID is consistent with the type of verification key
226      * supplied.
227      * 
228      * @param verificationKey the key with which to compute and verify the signature
229      * @param jcaAlgorithmID the Java JCA algorithm ID to use
230      * @param signature the computed signature value received from the signer
231      * @param input the input over which the signature is computed and verified
232      * @return true if the signature value computed over the input using the supplied key and algorithm ID is identical
233      *         to the supplied signature value
234      * @throws SecurityException thrown if the signature computation or verification process results in an error
235      */
236     public static boolean verify(PublicKey verificationKey, String jcaAlgorithmID, byte[] signature, byte[] input)
237             throws SecurityException {
238         Logger log = getLogger();
239 
240         log.debug("Verifying signature over input using public key of type {} and JCA algorithm ID {}", verificationKey
241                 .getAlgorithm(), jcaAlgorithmID);
242 
243         try {
244             Signature sig = Signature.getInstance(jcaAlgorithmID);
245             sig.initVerify(verificationKey);
246             sig.update(input);
247             return sig.verify(signature);
248         } catch (GeneralSecurityException e) {
249             log.error("Error during signature verification", e);
250             throw new SecurityException("Error during signature verification", e);
251         }
252     }
253 
254     /**
255      * Verify the Message Authentication Code (MAC) value computed over the supplied input against the supplied MAC
256      * value.
257      * 
258      * It is up to the caller to ensure that the specified algorithm ID is consistent with the type of verification key
259      * supplied.
260      * 
261      * @param verificationKey the key with which to compute and verify the MAC
262      * @param jcaAlgorithmID the Java JCA algorithm ID to use
263      * @param signature the computed MAC value received from the signer
264      * @param input the input over which the MAC is computed and verified
265      * @return true if the MAC value computed over the input using the supplied key and algorithm ID is identical to the
266      *         supplied MAC signature value
267      * @throws SecurityException thrown if the MAC computation or verification process results in an error
268      */
269     public static boolean verifyMAC(Key verificationKey, String jcaAlgorithmID, byte[] signature, byte[] input)
270             throws SecurityException {
271         Logger log = getLogger();
272 
273         log.debug("Verifying MAC over input using key of type {} and JCA algorithm ID {}", verificationKey
274                 .getAlgorithm(), jcaAlgorithmID);
275 
276         // Java JCA/JCE Mac interface doesn't have a verification op,
277         // so have to compute the Mac and compare the byte arrays manually.
278 
279         byte[] computed = signMAC(verificationKey, jcaAlgorithmID, input);
280         return Arrays.equals(computed, signature);
281     }
282     
283     /**
284      * Get an SLF4J Logger.
285      * 
286      * @return a Logger instance
287      */
288     private static Logger getLogger() {
289         return LoggerFactory.getLogger(SigningUtil.class);
290     }
291 }