View Javadoc
1   /*
2    * Copyright 2008 Web Cohesion
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    *   https://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.springframework.security.oauth.common.signature;
18  
19  import org.apache.commons.codec.binary.Base64;
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  
23  import javax.crypto.Mac;
24  import javax.crypto.SecretKey;
25  import java.io.UnsupportedEncodingException;
26  import java.security.InvalidKeyException;
27  import java.security.NoSuchAlgorithmException;
28  
29  /**
30   * HMAC-SHA1 signature method.
31   *
32   * @author Ryan Heaton
33   */
34  public class HMAC_SHA1SignatureMethod implements OAuthSignatureMethod {
35  
36    private static final Log LOG = LogFactory.getLog(HMAC_SHA1SignatureMethod.class);
37  
38    /**
39     * The name of this HMAC-SHA1 signature method ("HMAC-SHA1").
40     */
41    public static final String SIGNATURE_NAME = "HMAC-SHA1";
42  
43    /**
44     * The MAC name (for interfacing with javax.crypto.*).  "HmacSHA1".
45     */
46    public static final String MAC_NAME = "HmacSHA1";
47  
48    private final SecretKey key;
49  
50    /**
51     * Construct a HMAC-SHA1 signature method with the given HMAC-SHA1 key.
52     *
53     * @param key The key.
54     */
55    public HMAC_SHA1SignatureMethod(SecretKey key) {
56      this.key = key;
57    }
58  
59    /**
60     * The name of this HMAC-SHA1 signature method ("HMAC-SHA1").
61     *
62     * @return The name of this HMAC-SHA1 signature method.
63     */
64    public String getName() {
65      return SIGNATURE_NAME;
66    }
67  
68    /**
69     * Sign the signature base string. The signature is the digest octet string, first base64-encoded per RFC2045, section 6.8, then URL-encoded per
70     * OAuth Parameter Encoding.
71     *
72     * @param signatureBaseString The signature base string.
73     * @return The signature.
74     */
75    public String sign(String signatureBaseString) {
76      try {
77        Mac mac = Mac.getInstance(MAC_NAME);
78        mac.init(key);
79        byte[] text = signatureBaseString.getBytes("UTF-8");
80        byte[] signatureBytes = mac.doFinal(text);
81        signatureBytes = Base64.encodeBase64(signatureBytes);
82        String signature = new String(signatureBytes, "UTF-8");
83  
84        if (LOG.isDebugEnabled()) {
85          LOG.debug("signature base: " + signatureBaseString);
86          LOG.debug("signature: " + signature);
87        }
88  
89        return signature;
90      }
91      catch (NoSuchAlgorithmException e) {
92        throw new IllegalStateException(e);
93      }
94      catch (InvalidKeyException e) {
95        throw new IllegalStateException(e);
96      }
97      catch (UnsupportedEncodingException e) {
98        throw new RuntimeException(e);
99      }
100   }
101 
102   /**
103    * Verify the signature of the given signature base string. The signature is verified by generating a new request signature octet string, and comparing it
104    * to the signature provided by the Consumer, first URL-decoded per Parameter Encoding, then base64-decoded per RFC2045 section 6.8. The signature is
105    * generated using the request parameters as provided by the Consumer, and the Consumer Secret and Token Secret as stored by the Service Provider.
106    *
107    * @param signatureBaseString The signature base string.
108    * @param signature           The signature.
109    * @throws InvalidSignatureException If the signature is invalid for the specified base string.
110    */
111   public void verify(String signatureBaseString, String signature) throws InvalidSignatureException {
112     try {
113       if (LOG.isDebugEnabled()) {
114         LOG.debug("signature base: " + signatureBaseString);
115         LOG.debug("signature: " + signature);
116       }
117 
118       byte[] signatureBytes = Base64.decodeBase64(signature.getBytes("UTF-8"));
119 
120       Mac mac = Mac.getInstance(MAC_NAME);
121       mac.init(key);
122       byte[] text = signatureBaseString.getBytes("UTF-8");
123       byte[] calculatedBytes = mac.doFinal(text);
124       if (!safeArrayEquals(calculatedBytes, signatureBytes)) {
125         throw new InvalidSignatureException("Invalid signature for signature method " + getName());
126       }
127     }
128     catch (NoSuchAlgorithmException e) {
129       throw new IllegalStateException(e);
130     }
131     catch (InvalidKeyException e) {
132       throw new IllegalStateException(e);
133     }
134     catch (UnsupportedEncodingException e) {
135       throw new RuntimeException(e);
136     }
137   }
138 
139   boolean safeArrayEquals(byte[] a1, byte[] a2) {
140     if (a1 == null || a2 == null) {
141       return (a1 == a2);
142     }
143 
144     if (a1.length != a2.length) {
145       return false;
146     }
147 
148     byte result = 0;
149     for (int i = 0; i < a1.length; i++) {
150       result |= a1[i] ^ a2[i];
151     }
152     
153     return (result == 0);
154   }
155 
156   /**
157    * The secret key.
158    *
159    * @return The secret key.
160    */
161   public SecretKey getSecretKey() {
162     return key;
163   }
164 }