1 # -*- coding: utf-8 -*-
3 oauthlib.oauth1.rfc5849.signature
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 This module represents a direct implementation of `section 3.4`_ of the spec.
9 * Client: software interfacing with an OAuth API
10 * Server: the API provider
11 * Resource Owner: the user who is granting authorization to the client
13 Steps for signing a request:
15 1. Collect parameters from the uri query, auth header, & body
16 2. Normalize those parameters
18 4. Pass the normalized uri, normalized parameters, and http method to
19 construct the base string
20 5. Pass the base string and any keys needed to a signing function
22 .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4
24 from __future__ import absolute_import, unicode_literals
32 import urllib.parse as urlparse
34 from oauthlib.common import urldecode, extract_params, safe_string_equals
35 from oauthlib.common import bytes_type, unicode_type
38 def construct_base_string(http_method, base_string_uri,
39 normalized_encoded_request_parameters):
40 """**String Construction**
41 Per `section 3.4.1.1`_ of the spec.
43 For example, the HTTP request::
45 POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1
47 Content-Type: application/x-www-form-urlencoded
48 Authorization: OAuth realm="Example",
49 oauth_consumer_key="9djdj82h48djs9d2",
50 oauth_token="kkk9d7dh3k39sjv7",
51 oauth_signature_method="HMAC-SHA1",
52 oauth_timestamp="137131201",
53 oauth_nonce="7d8f3e4a",
54 oauth_signature="bYT5CMsGcbgUdFHObYMEfcx6bsw%3D"
58 is represented by the following signature base string (line breaks
59 are for display purposes only)::
61 POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q
62 %26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_
63 key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m
64 ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk
67 .. _`section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1
70 # The signature base string is constructed by concatenating together,
71 # in order, the following HTTP request elements:
73 # 1. The HTTP request method in uppercase. For example: "HEAD",
74 # "GET", "POST", etc. If the request uses a custom HTTP method, it
75 # MUST be encoded (`Section 3.6`_).
77 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
78 base_string = utils.escape(http_method.upper())
80 # 2. An "&" character (ASCII code 38).
83 # 3. The base string URI from `Section 3.4.1.2`_, after being encoded
86 # .. _`Section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2
87 # .. _`Section 3.4.6`: http://tools.ietf.org/html/rfc5849#section-3.4.6
88 base_string += utils.escape(base_string_uri)
90 # 4. An "&" character (ASCII code 38).
93 # 5. The request parameters as normalized in `Section 3.4.1.3.2`_, after
94 # being encoded (`Section 3.6`).
96 # .. _`Section 3.4.1.3.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
97 # .. _`Section 3.4.6`: http://tools.ietf.org/html/rfc5849#section-3.4.6
98 base_string += utils.escape(normalized_encoded_request_parameters)
103 def normalize_base_string_uri(uri, host=None):
104 """**Base String URI**
105 Per `section 3.4.1.2`_ of the spec.
107 For example, the HTTP request::
109 GET /r%20v/X?id=123 HTTP/1.1
112 is represented by the base string URI: "http://example.com/r%20v/X".
114 In another example, the HTTPS request::
117 Host: www.example.net:8080
119 is represented by the base string URI: "https://www.example.net:8080/".
121 .. _`section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2
123 The host argument overrides the netloc part of the uri argument.
125 if not isinstance(uri, unicode_type):
126 raise ValueError('uri must be a unicode object.')
128 # FIXME: urlparse does not support unicode
129 scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
131 # The scheme, authority, and path of the request resource URI `RFC3986`
132 # are included by constructing an "http" or "https" URI representing
133 # the request resource (without the query or fragment) as follows:
135 # .. _`RFC3986`: http://tools.ietf.org/html/rfc3986
137 if not scheme or not netloc:
138 raise ValueError('uri must include a scheme and netloc')
140 # Per `RFC 2616 section 5.1.2`_:
142 # Note that the absolute path cannot be empty; if none is present in
143 # the original URI, it MUST be given as "/" (the server root).
145 # .. _`RFC 2616 section 5.1.2`: http://tools.ietf.org/html/rfc2616#section-5.1.2
149 # 1. The scheme and host MUST be in lowercase.
150 scheme = scheme.lower()
151 netloc = netloc.lower()
153 # 2. The host and port values MUST match the content of the HTTP
154 # request "Host" header field.
156 netloc = host.lower()
158 # 3. The port MUST be included if it is not the default port for the
159 # scheme, and MUST be excluded if it is the default. Specifically,
160 # the port MUST be excluded when making an HTTP request `RFC2616`_
161 # to port 80 or when making an HTTPS request `RFC2818`_ to port 443.
162 # All other non-default port numbers MUST be included.
164 # .. _`RFC2616`: http://tools.ietf.org/html/rfc2616
165 # .. _`RFC2818`: http://tools.ietf.org/html/rfc2818
171 host, port = netloc.split(':', 1)
172 if (scheme, port) in default_ports:
175 return urlparse.urlunparse((scheme, netloc, path, params, '', ''))
178 # ** Request Parameters **
180 # Per `section 3.4.1.3`_ of the spec.
182 # In order to guarantee a consistent and reproducible representation of
183 # the request parameters, the parameters are collected and decoded to
184 # their original decoded form. They are then sorted and encoded in a
185 # particular manner that is often different from their original
186 # encoding scheme, and concatenated into a single string.
188 # .. _`section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3
190 def collect_parameters(uri_query='', body=[], headers=None,
191 exclude_oauth_signature=True, with_realm=False):
192 """**Parameter Sources**
194 Parameters starting with `oauth_` will be unescaped.
196 Body parameters must be supplied as a dict, a list of 2-tuples, or a
197 formencoded query string.
199 Headers must be supplied as a dict.
201 Per `section 3.4.1.3.1`_ of the spec.
203 For example, the HTTP request::
205 POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1
207 Content-Type: application/x-www-form-urlencoded
208 Authorization: OAuth realm="Example",
209 oauth_consumer_key="9djdj82h48djs9d2",
210 oauth_token="kkk9d7dh3k39sjv7",
211 oauth_signature_method="HMAC-SHA1",
212 oauth_timestamp="137131201",
213 oauth_nonce="7d8f3e4a",
214 oauth_signature="djosJKDKJSD8743243%2Fjdk33klY%3D"
218 contains the following (fully decoded) parameters used in the
219 signature base sting::
221 +------------------------+------------------+
223 +------------------------+------------------+
228 | oauth_consumer_key | 9djdj82h48djs9d2 |
229 | oauth_token | kkk9d7dh3k39sjv7 |
230 | oauth_signature_method | HMAC-SHA1 |
231 | oauth_timestamp | 137131201 |
232 | oauth_nonce | 7d8f3e4a |
235 +------------------------+------------------+
237 Note that the value of "b5" is "=%3D" and not "==". Both "c@" and
238 "c2" have empty values. While the encoding rules specified in this
239 specification for the purpose of constructing the signature base
240 string exclude the use of a "+" character (ASCII code 43) to
241 represent an encoded space character (ASCII code 32), this practice
242 is widely used in "application/x-www-form-urlencoded" encoded values,
243 and MUST be properly decoded, as demonstrated by one of the "a3"
244 parameter instances (the "a3" parameter is used twice in this
247 .. _`section 3.4.1.3.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1
249 headers = headers or {}
252 # The parameters from the following sources are collected into a single
253 # list of name/value pairs:
255 # * The query component of the HTTP request URI as defined by
256 # `RFC3986, Section 3.4`_. The query component is parsed into a list
257 # of name/value pairs by treating it as an
258 # "application/x-www-form-urlencoded" string, separating the names
259 # and values and decoding them as defined by
260 # `W3C.REC-html40-19980424`_, Section 17.13.4.
262 # .. _`RFC3986, Section 3.4`: http://tools.ietf.org/html/rfc3986#section-3.4
263 # .. _`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424
265 params.extend(urldecode(uri_query))
267 # * The OAuth HTTP "Authorization" header field (`Section 3.5.1`_) if
268 # present. The header's content is parsed into a list of name/value
269 # pairs excluding the "realm" parameter if present. The parameter
270 # values are decoded as defined by `Section 3.5.1`_.
272 # .. _`Section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1
274 headers_lower = dict((k.lower(), v) for k, v in headers.items())
275 authorization_header = headers_lower.get('authorization')
276 if authorization_header is not None:
277 params.extend([i for i in utils.parse_authorization_header(
278 authorization_header) if with_realm or i[0] != 'realm'])
280 # * The HTTP request entity-body, but only if all of the following
281 # conditions are met:
282 # * The entity-body is single-part.
284 # * The entity-body follows the encoding requirements of the
285 # "application/x-www-form-urlencoded" content-type as defined by
286 # `W3C.REC-html40-19980424`_.
288 # * The HTTP request entity-header includes the "Content-Type"
289 # header field set to "application/x-www-form-urlencoded".
291 # .._`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424
293 # TODO: enforce header param inclusion conditions
294 bodyparams = extract_params(body) or []
295 params.extend(bodyparams)
297 # ensure all oauth params are unescaped
298 unescaped_params = []
300 if k.startswith('oauth_'):
301 v = utils.unescape(v)
302 unescaped_params.append((k, v))
304 # The "oauth_signature" parameter MUST be excluded from the signature
305 # base string if present.
306 if exclude_oauth_signature:
307 unescaped_params = list(filter(lambda i: i[0] != 'oauth_signature',
310 return unescaped_params
313 def normalize_parameters(params):
314 """**Parameters Normalization**
315 Per `section 3.4.1.3.2`_ of the spec.
317 For example, the list of parameters from the previous section would
318 be normalized as follows:
322 +------------------------+------------------+
324 +------------------------+------------------+
329 | oauth_consumer_key | 9djdj82h48djs9d2 |
330 | oauth_token | kkk9d7dh3k39sjv7 |
331 | oauth_signature_method | HMAC-SHA1 |
332 | oauth_timestamp | 137131201 |
333 | oauth_nonce | 7d8f3e4a |
336 +------------------------+------------------+
340 +------------------------+------------------+
342 +------------------------+------------------+
349 | oauth_consumer_key | 9djdj82h48djs9d2 |
350 | oauth_nonce | 7d8f3e4a |
351 | oauth_signature_method | HMAC-SHA1 |
352 | oauth_timestamp | 137131201 |
353 | oauth_token | kkk9d7dh3k39sjv7 |
354 +------------------------+------------------+
358 +-------------------------------------+
360 +-------------------------------------+
367 | oauth_consumer_key=9djdj82h48djs9d2 |
368 | oauth_nonce=7d8f3e4a |
369 | oauth_signature_method=HMAC-SHA1 |
370 | oauth_timestamp=137131201 |
371 | oauth_token=kkk9d7dh3k39sjv7 |
372 +-------------------------------------+
374 and concatenated together into a single string (line breaks are for
375 display purposes only)::
377 a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9dj
378 dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1
379 &oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7
381 .. _`section 3.4.1.3.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
384 # The parameters collected in `Section 3.4.1.3`_ are normalized into a
385 # single string as follows:
387 # .. _`Section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3
389 # 1. First, the name and value of each parameter are encoded
392 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
393 key_values = [(utils.escape(k), utils.escape(v)) for k, v in params]
395 # 2. The parameters are sorted by name, using ascending byte value
396 # ordering. If two or more parameters share the same name, they
397 # are sorted by their value.
400 # 3. The name of each parameter is concatenated to its corresponding
401 # value using an "=" character (ASCII code 61) as a separator, even
402 # if the value is empty.
403 parameter_parts = ['{0}={1}'.format(k, v) for k, v in key_values]
405 # 4. The sorted name/value pairs are concatenated together into a
406 # single string by using an "&" character (ASCII code 38) as
408 return '&'.join(parameter_parts)
411 def sign_hmac_sha1_with_client(base_string, client):
412 return sign_hmac_sha1(base_string,
413 client.client_secret,
414 client.resource_owner_secret
418 def sign_hmac_sha1(base_string, client_secret, resource_owner_secret):
421 The "HMAC-SHA1" signature method uses the HMAC-SHA1 signature
422 algorithm as defined in `RFC2104`_::
424 digest = HMAC-SHA1 (key, text)
426 Per `section 3.4.2`_ of the spec.
428 .. _`RFC2104`: http://tools.ietf.org/html/rfc2104
429 .. _`section 3.4.2`: http://tools.ietf.org/html/rfc5849#section-3.4.2
432 # The HMAC-SHA1 function variables are used in following way:
434 # text is set to the value of the signature base string from
435 # `Section 3.4.1.1`_.
437 # .. _`Section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1
440 # key is set to the concatenated values of:
441 # 1. The client shared-secret, after being encoded (`Section 3.6`_).
443 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
444 key = utils.escape(client_secret or '')
446 # 2. An "&" character (ASCII code 38), which MUST be included
447 # even when either secret is empty.
450 # 3. The token shared-secret, after being encoded (`Section 3.6`_).
452 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
453 key += utils.escape(resource_owner_secret or '')
455 # FIXME: HMAC does not support unicode!
456 key_utf8 = key.encode('utf-8')
457 text_utf8 = text.encode('utf-8')
458 signature = hmac.new(key_utf8, text_utf8, hashlib.sha1)
460 # digest is used to set the value of the "oauth_signature" protocol
461 # parameter, after the result octet string is base64-encoded
462 # per `RFC2045, Section 6.8`.
464 # .. _`RFC2045, Section 6.8`: http://tools.ietf.org/html/rfc2045#section-6.8
465 return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8')
469 #jwt has some nice pycrypto/cryptography abstractions
470 def _jwt_rs1_signing_algorithm():
473 import jwt.algorithms as jwtalgo
474 _jwtrs1 = jwtalgo.RSAAlgorithm(jwtalgo.hashes.SHA1)
477 def sign_rsa_sha1(base_string, rsa_private_key):
480 Per `section 3.4.3`_ of the spec.
482 The "RSA-SHA1" signature method uses the RSASSA-PKCS1-v1_5 signature
483 algorithm as defined in `RFC3447, Section 8.2`_ (also known as
484 PKCS#1), using SHA-1 as the hash function for EMSA-PKCS1-v1_5. To
485 use this method, the client MUST have established client credentials
486 with the server that included its RSA public key (in a manner that is
487 beyond the scope of this specification).
489 .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3
490 .. _`RFC3447, Section 8.2`: http://tools.ietf.org/html/rfc3447#section-8.2
493 if isinstance(base_string, unicode_type):
494 base_string = base_string.encode('utf-8')
495 # TODO: finish RSA documentation
496 alg = _jwt_rs1_signing_algorithm()
497 key = _prepare_key_plus(alg, rsa_private_key)
498 s=alg.sign(base_string, key)
499 return binascii.b2a_base64(s)[:-1].decode('utf-8')
502 def sign_rsa_sha1_with_client(base_string, client):
503 return sign_rsa_sha1(base_string, client.rsa_key)
506 def sign_plaintext(client_secret, resource_owner_secret):
507 """Sign a request using plaintext.
509 Per `section 3.4.4`_ of the spec.
511 The "PLAINTEXT" method does not employ a signature algorithm. It
512 MUST be used with a transport-layer mechanism such as TLS or SSL (or
513 sent over a secure channel with equivalent protections). It does not
514 utilize the signature base string or the "oauth_timestamp" and
515 "oauth_nonce" parameters.
517 .. _`section 3.4.4`: http://tools.ietf.org/html/rfc5849#section-3.4.4
521 # The "oauth_signature" protocol parameter is set to the concatenated
524 # 1. The client shared-secret, after being encoded (`Section 3.6`_).
526 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
527 signature = utils.escape(client_secret or '')
529 # 2. An "&" character (ASCII code 38), which MUST be included even
530 # when either secret is empty.
533 # 3. The token shared-secret, after being encoded (`Section 3.6`_).
535 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6
536 signature += utils.escape(resource_owner_secret or '')
541 def sign_plaintext_with_client(base_string, client):
542 return sign_plaintext(client.client_secret, client.resource_owner_secret)
545 def verify_hmac_sha1(request, client_secret=None,
546 resource_owner_secret=None):
547 """Verify a HMAC-SHA1 signature.
549 Per `section 3.4`_ of the spec.
551 .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4
553 To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
554 attribute MUST be an absolute URI whose netloc part identifies the
555 origin server or gateway on which the resource resides. Any Host
556 item of the request argument's headers dict attribute will be
559 .. _`RFC2616 section 5.2`: http://tools.ietf.org/html/rfc2616#section-5.2
562 norm_params = normalize_parameters(request.params)
563 uri = normalize_base_string_uri(request.uri)
564 base_string = construct_base_string(request.http_method, uri, norm_params)
565 signature = sign_hmac_sha1(base_string, client_secret,
566 resource_owner_secret)
567 return safe_string_equals(signature, request.signature)
569 def _prepare_key_plus(alg, keystr):
570 if isinstance(keystr, bytes_type):
571 keystr = keystr.decode('utf-8')
572 return alg.prepare_key(keystr)
574 def verify_rsa_sha1(request, rsa_public_key):
575 """Verify a RSASSA-PKCS #1 v1.5 base64 encoded signature.
577 Per `section 3.4.3`_ of the spec.
579 Note this method requires the jwt and cryptography libraries.
581 .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3
583 To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
584 attribute MUST be an absolute URI whose netloc part identifies the
585 origin server or gateway on which the resource resides. Any Host
586 item of the request argument's headers dict attribute will be
589 .. _`RFC2616 section 5.2`: http://tools.ietf.org/html/rfc2616#section-5.2
591 norm_params = normalize_parameters(request.params)
592 uri = normalize_base_string_uri(request.uri)
593 message = construct_base_string(request.http_method, uri, norm_params).encode('utf-8')
594 sig = binascii.a2b_base64(request.signature.encode('utf-8'))
596 alg = _jwt_rs1_signing_algorithm()
597 key = _prepare_key_plus(alg, rsa_public_key)
598 return alg.verify(message, key, sig)
601 def verify_plaintext(request, client_secret=None, resource_owner_secret=None):
602 """Verify a PLAINTEXT signature.
604 Per `section 3.4`_ of the spec.
606 .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4
608 signature = sign_plaintext(client_secret, resource_owner_secret)
609 return safe_string_equals(signature, request.signature)