1# -*- coding: utf-8 -*- 2""" 3oauthlib.oauth1.rfc5849.signature 4~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 6This module represents a direct implementation of `section 3.4`_ of the spec. 7 8Terminology: 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 12 13Steps for signing a request: 14 151. Collect parameters from the uri query, auth header, & body 162. Normalize those parameters 173. Normalize the uri 184. Pass the normalized uri, normalized parameters, and http method to 19 construct the base string 205. Pass the base string and any keys needed to a signing function 21 22.. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 23""" 24from __future__ import absolute_import, unicode_literals 25 26import binascii 27import hashlib 28import hmac 29try: 30 import urlparse 31except ImportError: 32 import urllib.parse as urlparse 33from . import utils 34from oauthlib.common import urldecode, extract_params, safe_string_equals 35from oauthlib.common import bytes_type, unicode_type 36 37 38def 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. 42 43 For example, the HTTP request:: 44 45 POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 46 Host: example.com 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" 55 56 c2&a3=2+q 57 58 is represented by the following signature base string (line breaks 59 are for display purposes only):: 60 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 65 9d7dh3k39sjv7 66 67 .. _`section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1 68 """ 69 70 # The signature base string is constructed by concatenating together, 71 # in order, the following HTTP request elements: 72 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`_). 76 # 77 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 78 base_string = utils.escape(http_method.upper()) 79 80 # 2. An "&" character (ASCII code 38). 81 base_string += '&' 82 83 # 3. The base string URI from `Section 3.4.1.2`_, after being encoded 84 # (`Section 3.6`_). 85 # 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) 89 90 # 4. An "&" character (ASCII code 38). 91 base_string += '&' 92 93 # 5. The request parameters as normalized in `Section 3.4.1.3.2`_, after 94 # being encoded (`Section 3.6`). 95 # 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) 99 100 return base_string 101 102 103def normalize_base_string_uri(uri, host=None): 104 """**Base String URI** 105 Per `section 3.4.1.2`_ of the spec. 106 107 For example, the HTTP request:: 108 109 GET /r%20v/X?id=123 HTTP/1.1 110 Host: EXAMPLE.COM:80 111 112 is represented by the base string URI: "http://example.com/r%20v/X". 113 114 In another example, the HTTPS request:: 115 116 GET /?q=1 HTTP/1.1 117 Host: www.example.net:8080 118 119 is represented by the base string URI: "https://www.example.net:8080/". 120 121 .. _`section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2 122 123 The host argument overrides the netloc part of the uri argument. 124 """ 125 if not isinstance(uri, unicode_type): 126 raise ValueError('uri must be a unicode object.') 127 128 # FIXME: urlparse does not support unicode 129 scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri) 130 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: 134 # 135 # .. _`RFC3986`: http://tools.ietf.org/html/rfc3986 136 137 if not scheme or not netloc: 138 raise ValueError('uri must include a scheme and netloc') 139 140 # Per `RFC 2616 section 5.1.2`_: 141 # 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). 144 # 145 # .. _`RFC 2616 section 5.1.2`: http://tools.ietf.org/html/rfc2616#section-5.1.2 146 if not path: 147 path = '/' 148 149 # 1. The scheme and host MUST be in lowercase. 150 scheme = scheme.lower() 151 netloc = netloc.lower() 152 153 # 2. The host and port values MUST match the content of the HTTP 154 # request "Host" header field. 155 if host is not None: 156 netloc = host.lower() 157 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. 163 # 164 # .. _`RFC2616`: http://tools.ietf.org/html/rfc2616 165 # .. _`RFC2818`: http://tools.ietf.org/html/rfc2818 166 default_ports = ( 167 ('http', '80'), 168 ('https', '443'), 169 ) 170 if ':' in netloc: 171 host, port = netloc.split(':', 1) 172 if (scheme, port) in default_ports: 173 netloc = host 174 175 return urlparse.urlunparse((scheme, netloc, path, params, '', '')) 176 177 178# ** Request Parameters ** 179# 180# Per `section 3.4.1.3`_ of the spec. 181# 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. 187# 188# .. _`section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3 189 190def collect_parameters(uri_query='', body=[], headers=None, 191 exclude_oauth_signature=True, with_realm=False): 192 """**Parameter Sources** 193 194 Parameters starting with `oauth_` will be unescaped. 195 196 Body parameters must be supplied as a dict, a list of 2-tuples, or a 197 formencoded query string. 198 199 Headers must be supplied as a dict. 200 201 Per `section 3.4.1.3.1`_ of the spec. 202 203 For example, the HTTP request:: 204 205 POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 206 Host: example.com 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" 215 216 c2&a3=2+q 217 218 contains the following (fully decoded) parameters used in the 219 signature base sting:: 220 221 +------------------------+------------------+ 222 | Name | Value | 223 +------------------------+------------------+ 224 | b5 | =%3D | 225 | a3 | a | 226 | c@ | | 227 | a2 | r b | 228 | oauth_consumer_key | 9djdj82h48djs9d2 | 229 | oauth_token | kkk9d7dh3k39sjv7 | 230 | oauth_signature_method | HMAC-SHA1 | 231 | oauth_timestamp | 137131201 | 232 | oauth_nonce | 7d8f3e4a | 233 | c2 | | 234 | a3 | 2 q | 235 +------------------------+------------------+ 236 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 245 request). 246 247 .. _`section 3.4.1.3.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 248 """ 249 headers = headers or {} 250 params = [] 251 252 # The parameters from the following sources are collected into a single 253 # list of name/value pairs: 254 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. 261 # 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 264 if uri_query: 265 params.extend(urldecode(uri_query)) 266 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`_. 271 # 272 # .. _`Section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1 273 if headers: 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']) 279 280 # * The HTTP request entity-body, but only if all of the following 281 # conditions are met: 282 # * The entity-body is single-part. 283 # 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`_. 287 288 # * The HTTP request entity-header includes the "Content-Type" 289 # header field set to "application/x-www-form-urlencoded". 290 # 291 # .._`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424 292 293 # TODO: enforce header param inclusion conditions 294 bodyparams = extract_params(body) or [] 295 params.extend(bodyparams) 296 297 # ensure all oauth params are unescaped 298 unescaped_params = [] 299 for k, v in params: 300 if k.startswith('oauth_'): 301 v = utils.unescape(v) 302 unescaped_params.append((k, v)) 303 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', 308 unescaped_params)) 309 310 return unescaped_params 311 312 313def normalize_parameters(params): 314 """**Parameters Normalization** 315 Per `section 3.4.1.3.2`_ of the spec. 316 317 For example, the list of parameters from the previous section would 318 be normalized as follows: 319 320 Encoded:: 321 322 +------------------------+------------------+ 323 | Name | Value | 324 +------------------------+------------------+ 325 | b5 | %3D%253D | 326 | a3 | a | 327 | c%40 | | 328 | a2 | r%20b | 329 | oauth_consumer_key | 9djdj82h48djs9d2 | 330 | oauth_token | kkk9d7dh3k39sjv7 | 331 | oauth_signature_method | HMAC-SHA1 | 332 | oauth_timestamp | 137131201 | 333 | oauth_nonce | 7d8f3e4a | 334 | c2 | | 335 | a3 | 2%20q | 336 +------------------------+------------------+ 337 338 Sorted:: 339 340 +------------------------+------------------+ 341 | Name | Value | 342 +------------------------+------------------+ 343 | a2 | r%20b | 344 | a3 | 2%20q | 345 | a3 | a | 346 | b5 | %3D%253D | 347 | c%40 | | 348 | c2 | | 349 | oauth_consumer_key | 9djdj82h48djs9d2 | 350 | oauth_nonce | 7d8f3e4a | 351 | oauth_signature_method | HMAC-SHA1 | 352 | oauth_timestamp | 137131201 | 353 | oauth_token | kkk9d7dh3k39sjv7 | 354 +------------------------+------------------+ 355 356 Concatenated Pairs:: 357 358 +-------------------------------------+ 359 | Name=Value | 360 +-------------------------------------+ 361 | a2=r%20b | 362 | a3=2%20q | 363 | a3=a | 364 | b5=%3D%253D | 365 | c%40= | 366 | c2= | 367 | oauth_consumer_key=9djdj82h48djs9d2 | 368 | oauth_nonce=7d8f3e4a | 369 | oauth_signature_method=HMAC-SHA1 | 370 | oauth_timestamp=137131201 | 371 | oauth_token=kkk9d7dh3k39sjv7 | 372 +-------------------------------------+ 373 374 and concatenated together into a single string (line breaks are for 375 display purposes only):: 376 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 380 381 .. _`section 3.4.1.3.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2 382 """ 383 384 # The parameters collected in `Section 3.4.1.3`_ are normalized into a 385 # single string as follows: 386 # 387 # .. _`Section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3 388 389 # 1. First, the name and value of each parameter are encoded 390 # (`Section 3.6`_). 391 # 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] 394 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. 398 key_values.sort() 399 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] 404 405 # 4. The sorted name/value pairs are concatenated together into a 406 # single string by using an "&" character (ASCII code 38) as 407 # separator. 408 return '&'.join(parameter_parts) 409 410 411def sign_hmac_sha1_with_client(base_string, client): 412 return sign_hmac_sha1(base_string, 413 client.client_secret, 414 client.resource_owner_secret 415 ) 416 417 418def sign_hmac_sha1(base_string, client_secret, resource_owner_secret): 419 """**HMAC-SHA1** 420 421 The "HMAC-SHA1" signature method uses the HMAC-SHA1 signature 422 algorithm as defined in `RFC2104`_:: 423 424 digest = HMAC-SHA1 (key, text) 425 426 Per `section 3.4.2`_ of the spec. 427 428 .. _`RFC2104`: http://tools.ietf.org/html/rfc2104 429 .. _`section 3.4.2`: http://tools.ietf.org/html/rfc5849#section-3.4.2 430 """ 431 432 # The HMAC-SHA1 function variables are used in following way: 433 434 # text is set to the value of the signature base string from 435 # `Section 3.4.1.1`_. 436 # 437 # .. _`Section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1 438 text = base_string 439 440 # key is set to the concatenated values of: 441 # 1. The client shared-secret, after being encoded (`Section 3.6`_). 442 # 443 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 444 key = utils.escape(client_secret or '') 445 446 # 2. An "&" character (ASCII code 38), which MUST be included 447 # even when either secret is empty. 448 key += '&' 449 450 # 3. The token shared-secret, after being encoded (`Section 3.6`_). 451 # 452 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 453 key += utils.escape(resource_owner_secret or '') 454 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) 459 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`. 463 # 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') 466 467_jwtrs1 = None 468 469#jwt has some nice pycrypto/cryptography abstractions 470def _jwt_rs1_signing_algorithm(): 471 global _jwtrs1 472 if _jwtrs1 is None: 473 import jwt.algorithms as jwtalgo 474 _jwtrs1 = jwtalgo.RSAAlgorithm(jwtalgo.hashes.SHA1) 475 return _jwtrs1 476 477def sign_rsa_sha1(base_string, rsa_private_key): 478 """**RSA-SHA1** 479 480 Per `section 3.4.3`_ of the spec. 481 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). 488 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 491 492 """ 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') 500 501 502def sign_rsa_sha1_with_client(base_string, client): 503 if not client.rsa_key: 504 raise ValueError('rsa_key is required when using RSA signature method.') 505 return sign_rsa_sha1(base_string, client.rsa_key) 506 507 508def sign_plaintext(client_secret, resource_owner_secret): 509 """Sign a request using plaintext. 510 511 Per `section 3.4.4`_ of the spec. 512 513 The "PLAINTEXT" method does not employ a signature algorithm. It 514 MUST be used with a transport-layer mechanism such as TLS or SSL (or 515 sent over a secure channel with equivalent protections). It does not 516 utilize the signature base string or the "oauth_timestamp" and 517 "oauth_nonce" parameters. 518 519 .. _`section 3.4.4`: http://tools.ietf.org/html/rfc5849#section-3.4.4 520 521 """ 522 523 # The "oauth_signature" protocol parameter is set to the concatenated 524 # value of: 525 526 # 1. The client shared-secret, after being encoded (`Section 3.6`_). 527 # 528 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 529 signature = utils.escape(client_secret or '') 530 531 # 2. An "&" character (ASCII code 38), which MUST be included even 532 # when either secret is empty. 533 signature += '&' 534 535 # 3. The token shared-secret, after being encoded (`Section 3.6`_). 536 # 537 # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 538 signature += utils.escape(resource_owner_secret or '') 539 540 return signature 541 542 543def sign_plaintext_with_client(base_string, client): 544 return sign_plaintext(client.client_secret, client.resource_owner_secret) 545 546 547def verify_hmac_sha1(request, client_secret=None, 548 resource_owner_secret=None): 549 """Verify a HMAC-SHA1 signature. 550 551 Per `section 3.4`_ of the spec. 552 553 .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 554 555 To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri 556 attribute MUST be an absolute URI whose netloc part identifies the 557 origin server or gateway on which the resource resides. Any Host 558 item of the request argument's headers dict attribute will be 559 ignored. 560 561 .. _`RFC2616 section 5.2`: http://tools.ietf.org/html/rfc2616#section-5.2 562 563 """ 564 norm_params = normalize_parameters(request.params) 565 uri = normalize_base_string_uri(request.uri) 566 base_string = construct_base_string(request.http_method, uri, norm_params) 567 signature = sign_hmac_sha1(base_string, client_secret, 568 resource_owner_secret) 569 return safe_string_equals(signature, request.signature) 570 571def _prepare_key_plus(alg, keystr): 572 if isinstance(keystr, bytes_type): 573 keystr = keystr.decode('utf-8') 574 return alg.prepare_key(keystr) 575 576def verify_rsa_sha1(request, rsa_public_key): 577 """Verify a RSASSA-PKCS #1 v1.5 base64 encoded signature. 578 579 Per `section 3.4.3`_ of the spec. 580 581 Note this method requires the jwt and cryptography libraries. 582 583 .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3 584 585 To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri 586 attribute MUST be an absolute URI whose netloc part identifies the 587 origin server or gateway on which the resource resides. Any Host 588 item of the request argument's headers dict attribute will be 589 ignored. 590 591 .. _`RFC2616 section 5.2`: http://tools.ietf.org/html/rfc2616#section-5.2 592 """ 593 norm_params = normalize_parameters(request.params) 594 uri = normalize_base_string_uri(request.uri) 595 message = construct_base_string(request.http_method, uri, norm_params).encode('utf-8') 596 sig = binascii.a2b_base64(request.signature.encode('utf-8')) 597 598 alg = _jwt_rs1_signing_algorithm() 599 key = _prepare_key_plus(alg, rsa_public_key) 600 return alg.verify(message, key, sig) 601 602 603def verify_plaintext(request, client_secret=None, resource_owner_secret=None): 604 """Verify a PLAINTEXT signature. 605 606 Per `section 3.4`_ of the spec. 607 608 .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 609 """ 610 signature = sign_plaintext(client_secret, resource_owner_secret) 611 return safe_string_equals(signature, request.signature) 612