• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2007 Netflix, 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 net.oauth.signature;
18 
19 import java.io.IOException;
20 import java.net.URI;
21 import java.net.URISyntaxException;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.concurrent.ConcurrentHashMap;
28 import net.oauth.OAuth;
29 import net.oauth.OAuthAccessor;
30 import net.oauth.OAuthConsumer;
31 import net.oauth.OAuthException;
32 import net.oauth.OAuthMessage;
33 import net.oauth.OAuthProblemException;
34 // BEGIN android-changed
35 // import org.apache.commons.codec.binary.Base64;
36 import android.util.Base64;
37 // END android-changed
38 
39 /**
40  * A pair of algorithms for computing and verifying an OAuth digital signature.
41  *
42  * @author John Kristian
43  * @hide
44  */
45 public abstract class OAuthSignatureMethod {
46 
47     /** Add a signature to the message.
48      * @throws URISyntaxException
49      * @throws IOException */
sign(OAuthMessage message)50     public void sign(OAuthMessage message)
51     throws OAuthException, IOException, URISyntaxException {
52         message.addParameter(new OAuth.Parameter("oauth_signature",
53                 getSignature(message)));
54     }
55 
56     /**
57      * Check whether the message has a valid signature.
58      * @throws URISyntaxException
59      *
60      * @throws OAuthProblemException
61      *             the signature is invalid
62      */
validate(OAuthMessage message)63     public void validate(OAuthMessage message)
64     throws IOException, OAuthException, URISyntaxException {
65         message.requireParameters("oauth_signature");
66         String signature = message.getSignature();
67         String baseString = getBaseString(message);
68         if (!isValid(signature, baseString)) {
69             OAuthProblemException problem = new OAuthProblemException(
70                     "signature_invalid");
71             problem.setParameter("oauth_signature", signature);
72             problem.setParameter("oauth_signature_base_string", baseString);
73             problem.setParameter("oauth_signature_method", message
74                     .getSignatureMethod());
75             throw problem;
76         }
77     }
78 
getSignature(OAuthMessage message)79     protected String getSignature(OAuthMessage message)
80     throws OAuthException, IOException, URISyntaxException {
81         String baseString = getBaseString(message);
82         String signature = getSignature(baseString);
83         // Logger log = Logger.getLogger(getClass().getName());
84         // if (log.isLoggable(Level.FINE)) {
85         // log.fine(signature + "=getSignature(" + baseString + ")");
86         // }
87         return signature;
88     }
89 
initialize(String name, OAuthAccessor accessor)90     protected void initialize(String name, OAuthAccessor accessor)
91             throws OAuthException {
92         String secret = accessor.consumer.consumerSecret;
93         if (name.endsWith(_ACCESSOR)) {
94             // This code supports the 'Accessor Secret' extensions
95             // described in http://oauth.pbwiki.com/AccessorSecret
96             final String key = OAuthConsumer.ACCESSOR_SECRET;
97             Object accessorSecret = accessor.getProperty(key);
98             if (accessorSecret == null) {
99                 accessorSecret = accessor.consumer.getProperty(key);
100             }
101             if (accessorSecret != null) {
102                 secret = accessorSecret.toString();
103             }
104         }
105         if (secret == null) {
106             secret = "";
107         }
108         setConsumerSecret(secret);
109     }
110 
111     public static final String _ACCESSOR = "-Accessor";
112 
113     /** Compute the signature for the given base string. */
getSignature(String baseString)114     protected abstract String getSignature(String baseString) throws OAuthException;
115 
116     /** Decide whether the signature is valid. */
isValid(String signature, String baseString)117     protected abstract boolean isValid(String signature, String baseString)
118             throws OAuthException;
119 
120     private String consumerSecret;
121 
122     private String tokenSecret;
123 
getConsumerSecret()124     protected String getConsumerSecret() {
125         return consumerSecret;
126     }
127 
setConsumerSecret(String consumerSecret)128     protected void setConsumerSecret(String consumerSecret) {
129         this.consumerSecret = consumerSecret;
130     }
131 
getTokenSecret()132     public String getTokenSecret() {
133         return tokenSecret;
134     }
135 
setTokenSecret(String tokenSecret)136     public void setTokenSecret(String tokenSecret) {
137         this.tokenSecret = tokenSecret;
138     }
139 
getBaseString(OAuthMessage message)140     public static String getBaseString(OAuthMessage message)
141             throws IOException, URISyntaxException {
142         List<Map.Entry<String, String>> parameters;
143         String url = message.URL;
144         int q = url.indexOf('?');
145         if (q < 0) {
146             parameters = message.getParameters();
147         } else {
148             // Combine the URL query string with the other parameters:
149             parameters = new ArrayList<Map.Entry<String, String>>();
150             parameters.addAll(OAuth.decodeForm(message.URL.substring(q + 1)));
151             parameters.addAll(message.getParameters());
152             url = url.substring(0, q);
153         }
154         return OAuth.percentEncode(message.method.toUpperCase()) + '&'
155                 + OAuth.percentEncode(normalizeUrl(url)) + '&'
156                 + OAuth.percentEncode(normalizeParameters(parameters));
157     }
158 
normalizeUrl(String url)159     protected static String normalizeUrl(String url) throws URISyntaxException {
160         URI uri = new URI(url);
161         String scheme = uri.getScheme().toLowerCase();
162         String authority = uri.getAuthority().toLowerCase();
163         boolean dropPort = (scheme.equals("http") && uri.getPort() == 80)
164                            || (scheme.equals("https") && uri.getPort() == 443);
165         if (dropPort) {
166             // find the last : in the authority
167             int index = authority.lastIndexOf(":");
168             if (index >= 0) {
169                 authority = authority.substring(0, index);
170             }
171         }
172         String path = uri.getRawPath();
173         if (path == null || path.length() <= 0) {
174             path = "/"; // conforms to RFC 2616 section 3.2.2
175         }
176         // we know that there is no query and no fragment here.
177         return scheme + "://" + authority + path;
178     }
179 
normalizeParameters( Collection<? extends Map.Entry> parameters)180     protected static String normalizeParameters(
181             Collection<? extends Map.Entry> parameters) throws IOException {
182         if (parameters == null) {
183             return "";
184         }
185         List<ComparableParameter> p = new ArrayList<ComparableParameter>(
186                 parameters.size());
187         for (Map.Entry parameter : parameters) {
188             if (!"oauth_signature".equals(parameter.getKey())) {
189                 p.add(new ComparableParameter(parameter));
190             }
191         }
192         Collections.sort(p);
193         return OAuth.formEncode(getParameters(p));
194     }
195 
196     // BEGIN android-changed
decodeBase64(String s)197     public static byte[] decodeBase64(String s) {
198         return Base64.decode(s, Base64.DEFAULT);
199     }
200 
base64Encode(byte[] b)201     public static String base64Encode(byte[] b) {
202         return Base64.encodeToString(b, Base64.DEFAULT);
203     }
204     // END android-changed
205 
newSigner(OAuthMessage message, OAuthAccessor accessor)206     public static OAuthSignatureMethod newSigner(OAuthMessage message,
207             OAuthAccessor accessor) throws IOException, OAuthException {
208         message.requireParameters(OAuth.OAUTH_SIGNATURE_METHOD);
209         OAuthSignatureMethod signer = newMethod(message.getSignatureMethod(),
210                 accessor);
211         signer.setTokenSecret(accessor.tokenSecret);
212         return signer;
213     }
214 
215     /** The factory for signature methods. */
newMethod(String name, OAuthAccessor accessor)216     public static OAuthSignatureMethod newMethod(String name,
217             OAuthAccessor accessor) throws OAuthException {
218         try {
219             Class methodClass = NAME_TO_CLASS.get(name);
220             if (methodClass != null) {
221                 OAuthSignatureMethod method = (OAuthSignatureMethod) methodClass
222                 .newInstance();
223                 method.initialize(name, accessor);
224                 return method;
225             }
226             OAuthProblemException problem = new OAuthProblemException(
227             "signature_method_rejected");
228             String acceptable = OAuth.percentEncode(NAME_TO_CLASS.keySet());
229             if (acceptable.length() > 0) {
230                 problem.setParameter("oauth_acceptable_signature_methods",
231                         acceptable.toString());
232             }
233             throw problem;
234         } catch (InstantiationException e) {
235             throw new OAuthException(e);
236         } catch (IllegalAccessException e) {
237             throw new OAuthException(e);
238         }
239     }
240 
241     /**
242      * Subsequently, newMethod(name) will attempt to instantiate the given
243      * class, with no constructor parameters.
244      */
registerMethodClass(String name, Class clazz)245     public static void registerMethodClass(String name, Class clazz) {
246         NAME_TO_CLASS.put(name, clazz);
247     }
248 
249     private static final Map<String, Class> NAME_TO_CLASS = new ConcurrentHashMap<String, Class>();
250     static {
251         registerMethodClass("HMAC-SHA1", HMAC_SHA1.class);
252         registerMethodClass("PLAINTEXT", PLAINTEXT.class);
253         registerMethodClass("RSA-SHA1", RSA_SHA1.class);
254         registerMethodClass("HMAC-SHA1" + _ACCESSOR, HMAC_SHA1.class);
255         registerMethodClass("PLAINTEXT" + _ACCESSOR, PLAINTEXT.class);
256     }
257 
258     /** An efficiently sortable wrapper around a parameter. */
259     private static class ComparableParameter implements
260             Comparable<ComparableParameter> {
261 
ComparableParameter(Map.Entry value)262         ComparableParameter(Map.Entry value) {
263             this.value = value;
264             String n = toString(value.getKey());
265             String v = toString(value.getValue());
266             this.key = OAuth.percentEncode(n) + ' ' + OAuth.percentEncode(v);
267             // ' ' is used because it comes before any character
268             // that can appear in a percentEncoded string.
269         }
270 
271         final Map.Entry value;
272 
273         private final String key;
274 
toString(Object from)275         private static String toString(Object from) {
276             return (from == null) ? null : from.toString();
277         }
278 
compareTo(ComparableParameter that)279         public int compareTo(ComparableParameter that) {
280             return this.key.compareTo(that.key);
281         }
282 
283         @Override
toString()284         public String toString() {
285             return key;
286         }
287 
288     }
289 
290     /** Retrieve the original parameters from a sorted collection. */
getParameters( Collection<ComparableParameter> parameters)291     private static List<Map.Entry> getParameters(
292             Collection<ComparableParameter> parameters) {
293         if (parameters == null) {
294             return null;
295         }
296         List<Map.Entry> list = new ArrayList<Map.Entry>(parameters.size());
297         for (ComparableParameter parameter : parameters) {
298             list.add(parameter.value);
299         }
300         return list;
301     }
302 
303 }
304