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 }