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  
21  import java.io.UnsupportedEncodingException;
22  import java.security.*;
23  
24  /**
25   * RSA-SHA1 signature method. The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in RFC3447
26   * section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for EMSA-PKCS1-v1_5.
27   *
28   * @author Ryan Heaton
29   */
30  public class RSA_SHA1SignatureMethod implements OAuthSignatureMethod {
31  
32    /**
33     * The name of this RSA-SHA1 signature method ("RSA-SHA1").
34     */
35    public static final String SIGNATURE_NAME = "RSA-SHA1";
36  
37    private final PrivateKey privateKey;
38    private final PublicKey publicKey;
39  
40    /**
41     * Construct a RSA-SHA1 signature method with the given RSA-SHA1 public/private key pair.
42     *
43     * @param privateKey The private key.
44     * @param publicKey  The public key.
45     */
46    public RSA_SHA1SignatureMethod(PrivateKey privateKey, PublicKey publicKey) {
47      this.privateKey = privateKey;
48      this.publicKey = publicKey;
49    }
50  
51    /**
52     * Construct a RSA-SHA1 signature method with the given RSA-SHA1 private key.  This constructor is to be
53     * used by the consumer (who has access to its own private key).
54     *
55     * @param key The key.
56     */
57    public RSA_SHA1SignatureMethod(PrivateKey key) {
58      this(key, null);
59    }
60  
61    /**
62     * Construct a RSA-SHA1 signature method with the given RSA-SHA1 public key.  This constructor is to be
63     * used by the provider (who has access to the public key of the consumer).
64     *
65     * @param key The key.
66     */
67    public RSA_SHA1SignatureMethod(PublicKey key) {
68      this(null, key);
69    }
70  
71    /**
72     * The name of this RSA-SHA1 signature method ("RSA-SHA1").
73     *
74     * @return The name of this RSA-SHA1 signature method.
75     */
76    public String getName() {
77      return SIGNATURE_NAME;
78    }
79  
80    /**
81     * The Signature Base String is signed using the Consumer’s RSA private key per RFC3447 section 8.2.1, where K is the Consumer’s RSA private key,
82     * M the Signature Base String, and S is the result signature octet string:
83     *
84     * {@code S = RSASSA-PKCS1-V1_5-SIGN (K, M) }
85     *
86     * oauth_signature is set to S, first base64-encoded per RFC2045 section 6.8, then URL-encoded per Parameter Encoding.
87     *
88     * @param signatureBaseString The signature base string.
89     * @return The signature.
90     * @throws UnsupportedOperationException If there is no private key.
91     */
92    public String sign(String signatureBaseString) {
93      if (privateKey == null) {
94        throw new UnsupportedOperationException("Cannot sign the base string: no private key supplied.");
95      }
96  
97      try {
98        Signature signer = Signature.getInstance("SHA1withRSA");
99        signer.initSign(privateKey);
100       signer.update(signatureBaseString.getBytes("UTF-8"));
101       byte[] signatureBytes = signer.sign();
102       signatureBytes = Base64.encodeBase64(signatureBytes);
103       return new String(signatureBytes, "UTF-8");
104     }
105     catch (NoSuchAlgorithmException e) {
106       throw new IllegalStateException(e);
107     }
108     catch (InvalidKeyException e) {
109       throw new IllegalStateException(e);
110     }
111     catch (SignatureException e) {
112       throw new IllegalStateException(e);
113     }
114     catch (UnsupportedEncodingException e) {
115       throw new RuntimeException(e);
116     }
117   }
118 
119   /**
120    * Verify the signature of the given signature base string. The signature is verified by generating a new request signature octet string, and comparing it
121    * to the signature provided by the Consumer, first URL-decoded per Parameter Encoding, then base64-decoded per RFC2045 section 6.8. The signature is
122    * generated using the request parameters as provided by the Consumer, and the Consumer Secret and Token Secret as stored by the Service Provider.
123    *
124    * @param signatureBaseString The signature base string.
125    * @param signature           The signature.
126    * @throws InvalidSignatureException
127    *                                       If the signature is invalid for the specified base string.
128    * @throws UnsupportedOperationException If there is no public key.
129    */
130   public void verify(String signatureBaseString, String signature) throws InvalidSignatureException {
131     if (publicKey == null) {
132       throw new UnsupportedOperationException("A public key must be provided to verify signatures.");
133     }
134 
135     try {
136       byte[] signatureBytes = Base64.decodeBase64(signature.getBytes("UTF-8"));
137       Signature verifier = Signature.getInstance("SHA1withRSA");
138       verifier.initVerify(publicKey);
139       verifier.update(signatureBaseString.getBytes("UTF-8"));
140       if (!verifier.verify(signatureBytes)) {
141         throw new InvalidSignatureException("Invalid signature for signature method " + getName());
142       }
143     }
144     catch (UnsupportedEncodingException e) {
145       throw new RuntimeException(e);
146     }
147     catch (NoSuchAlgorithmException e) {
148       throw new IllegalStateException(e);
149     }
150     catch (InvalidKeyException e) {
151       throw new IllegalStateException(e);
152     }
153     catch (SignatureException e) {
154       throw new IllegalStateException(e);
155     }
156   }
157 
158   /**
159    * The private key.
160    *
161    * @return The private key.
162    */
163   public PrivateKey getPrivateKey() {
164     return privateKey;
165   }
166 
167   /**
168    * The private key.
169    *
170    * @return The private key.
171    */
172   public PublicKey getPublicKey() {
173     return publicKey;
174   }
175 }