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      /** Class logger. */
39      private static Logger log = LoggerFactory.getLogger(SigningUtil.class);
40  
41      /** Constructor. */
42      private SigningUtil() {
43      }
44  
45      /**
46       * Compute the signature or MAC value over the supplied input.
47       * 
48       * It is up to the caller to ensure that the specified algorithm URI is consistent with the type of signing key
49       * supplied in the signing credential.
50       * 
51       * @param signingCredential the credential containing the signing key
52       * @param algorithmURI the algorithm URI to use
53       * @param input the input over which to compute the signature
54       * @return the computed signature or MAC value
55       * @throws SecurityException throw if the computation process results in an error
56       */
57      public static byte[] signWithURI(Credential signingCredential, String algorithmURI, byte[] input)
58              throws SecurityException {
59  
60          String jcaAlgorithmID = SecurityHelper.getAlgorithmIDFromURI(algorithmURI);
61          if (jcaAlgorithmID == null) {
62              throw new SecurityException("Could not derive JCA algorithm identifier from algorithm URI");
63          }
64  
65          boolean isHMAC = SecurityHelper.isHMAC(algorithmURI);
66  
67          return sign(signingCredential, jcaAlgorithmID, isHMAC, input);
68      }
69  
70      /**
71       * Compute the signature or MAC value over the supplied input.
72       * 
73       * It is up to the caller to ensure that the specified algorithm ID and isMAC flag are consistent with the type of
74       * signing key supplied in the signing credential.
75       * 
76       * @param signingCredential the credential containing the signing key
77       * @param jcaAlgorithmID the Java JCA algorithm ID to use
78       * @param isMAC flag indicating whether the operation to be performed is a signature or MAC computation
79       * @param input the input over which to compute the signature
80       * @return the computed signature or MAC value
81       * @throws SecurityException throw if the computation process results in an error
82       */
83      public static byte[] sign(Credential signingCredential, String jcaAlgorithmID, boolean isMAC, byte[] input)
84              throws SecurityException {
85  
86          Key signingKey = SecurityHelper.extractSigningKey(signingCredential);
87          if (signingKey == null) {
88              log.error("No signing key supplied in signing credential for signature computation");
89              throw new SecurityException("No signing key supplied in signing credential");
90          }
91  
92          if (isMAC) {
93              return signMAC(signingKey, jcaAlgorithmID, input);
94          } else if (signingKey instanceof PrivateKey) {
95              return sign((PrivateKey) signingKey, jcaAlgorithmID, input);
96          } else {
97              log.error("No PrivateKey present in signing credential for signature computation");
98              throw new SecurityException("No PrivateKey supplied for signing");
99          }
100     }
101 
102     /**
103      * Compute the raw signature value over the supplied input.
104      * 
105      * It is up to the caller to ensure that the specified algorithm ID is consistent with the type of signing key
106      * supplied.
107      * 
108      * @param signingKey the private key with which to compute the signature
109      * @param jcaAlgorithmID the Java JCA algorithm ID to use
110      * @param input the input over which to compute the signature
111      * @return the computed signature value
112      * @throws SecurityException thrown if the signature computation results in an error
113      */
114     public static byte[] sign(PrivateKey signingKey, String jcaAlgorithmID, byte[] input) throws SecurityException {
115         log.debug("Computing signature over input using private key of type {} and JCA algorithm ID {}", signingKey
116                 .getAlgorithm(), jcaAlgorithmID);
117 
118         try {
119             Signature signature = Signature.getInstance(jcaAlgorithmID);
120             signature.initSign(signingKey);
121             signature.update(input);
122             byte[] rawSignature = signature.sign();
123             log.debug("Computed signature: {}", new String(Hex.encode(rawSignature)));
124             return rawSignature;
125         } catch (GeneralSecurityException e) {
126             log.error("Error during signature generation", e);
127             throw new SecurityException("Error during signature generation", e);
128         }
129     }
130 
131     /**
132      * Compute the Message Authentication Code (MAC) value over the supplied input.
133      * 
134      * It is up to the caller to ensure that the specified algorithm ID is consistent with the type of signing key
135      * supplied.
136      * 
137      * @param signingKey the key with which to compute the MAC
138      * @param jcaAlgorithmID the Java JCA algorithm ID to use
139      * @param input the input over which to compute the MAC
140      * @return the computed MAC value
141      * @throws SecurityException thrown if the MAC computation results in an error
142      */
143     public static byte[] signMAC(Key signingKey, String jcaAlgorithmID, byte[] input) throws SecurityException {
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 
205         Key verificationKey = SecurityHelper.extractVerificationKey(verificationCredential);
206         if (verificationKey == null) {
207             log.error("No verification key supplied in verification credential for signature verification");
208             throw new SecurityException("No verification key supplied in verification credential");
209         }
210 
211         if (isMAC) {
212             return verifyMAC(verificationKey, jcaAlgorithmID, signature, input);
213         } else if (verificationKey instanceof PublicKey) {
214             return verify((PublicKey) verificationKey, jcaAlgorithmID, signature, input);
215         } else {
216             log.error("No PublicKey present in verification credential for signature verification");
217             throw new SecurityException("No PublicKey supplied for signature verification");
218         }
219     }
220 
221     /**
222      * Verify the signature value computed over the supplied input against the supplied signature value.
223      * 
224      * It is up to the caller to ensure that the specified algorithm ID is consistent with the type of verification key
225      * supplied.
226      * 
227      * @param verificationKey the key with which to compute and verify the signature
228      * @param jcaAlgorithmID the Java JCA algorithm ID to use
229      * @param signature the computed signature value received from the signer
230      * @param input the input over which the signature is computed and verified
231      * @return true if the signature value computed over the input using the supplied key and algorithm ID is identical
232      *         to the supplied signature value
233      * @throws SecurityException thrown if the signature computation or verification process results in an error
234      */
235     public static boolean verify(PublicKey verificationKey, String jcaAlgorithmID, byte[] signature, byte[] input)
236             throws SecurityException {
237 
238         log.debug("Verifying signature over input using public key of type {} and JCA algorithm ID {}", verificationKey
239                 .getAlgorithm(), jcaAlgorithmID);
240 
241         try {
242             Signature sig = Signature.getInstance(jcaAlgorithmID);
243             sig.initVerify(verificationKey);
244             sig.update(input);
245             return sig.verify(signature);
246         } catch (GeneralSecurityException e) {
247             log.error("Error during signature verification", e);
248             throw new SecurityException("Error during signature verification", e);
249         }
250     }
251 
252     /**
253      * Verify the Message Authentication Code (MAC) value computed over the supplied input against the supplied MAC
254      * value.
255      * 
256      * It is up to the caller to ensure that the specified algorithm ID is consistent with the type of verification key
257      * supplied.
258      * 
259      * @param verificationKey the key with which to compute and verify the MAC
260      * @param jcaAlgorithmID the Java JCA algorithm ID to use
261      * @param signature the computed MAC value received from the signer
262      * @param input the input over which the MAC is computed and verified
263      * @return true if the MAC value computed over the input using the supplied key and algorithm ID is identical to the
264      *         supplied MAC signature value
265      * @throws SecurityException thrown if the MAC computation or verification process results in an error
266      */
267     public static boolean verifyMAC(Key verificationKey, String jcaAlgorithmID, byte[] signature, byte[] input)
268             throws SecurityException {
269 
270         log.debug("Verifying MAC over input using key of type {} and JCA algorithm ID {}", verificationKey
271                 .getAlgorithm(), jcaAlgorithmID);
272 
273         // Java JCA/JCE Mac interface doesn't have a verification op,
274         // so have to compute the Mac and compare the byte arrays manually.
275 
276         byte[] computed = signMAC(verificationKey, jcaAlgorithmID, input);
277         return Arrays.equals(computed, signature);
278     }
279 }