You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					364 lines
				
				14 KiB
			
		
		
			
		
	
	
					364 lines
				
				14 KiB
			| 
											3 years ago
										 | # Abstract
 | ||
|  | 
 | ||
|  | This document describes a way to add origin authentication, message integrity, | ||
|  | and replay resistance to HTTP REST requests.  It is intended to be used over | ||
|  | the HTTPS protocol. | ||
|  | 
 | ||
|  | # Copyright Notice
 | ||
|  | 
 | ||
|  | Copyright (c) 2011 Joyent, Inc. and the persons identified as document authors. | ||
|  | All rights reserved. | ||
|  | 
 | ||
|  | Code Components extracted from this document must include MIT License text. | ||
|  | 
 | ||
|  | # Introduction
 | ||
|  | 
 | ||
|  | This protocol is intended to provide a standard way for clients to sign HTTP | ||
|  | requests.  RFC2617 (HTTP Authentication) defines Basic and Digest authentication | ||
|  | mechanisms, and RFC5246 (TLS 1.2) defines client-auth, both of which are widely | ||
|  | employed on the Internet today.  However, it is common place that the burdens of | ||
|  | PKI prevent web service operators from deploying that methodology, and so many | ||
|  | fall back to Basic authentication, which has poor security characteristics. | ||
|  | 
 | ||
|  | Additionally, OAuth provides a fully-specified alternative for authorization | ||
|  | of web service requests, but is not (always) ideal for machine to machine | ||
|  | communication, as the key acquisition steps (generally) imply a fixed | ||
|  | infrastructure that may not make sense to a service provider (e.g., symmetric | ||
|  | keys). | ||
|  | 
 | ||
|  | Several web service providers have invented their own schemes for signing | ||
|  | HTTP requests, but to date, none have been placed in the public domain as a | ||
|  | standard.  This document serves that purpose.  There are no techniques in this | ||
|  | proposal that are novel beyond previous art, however, this aims to be a simple | ||
|  | mechanism for signing these requests. | ||
|  | 
 | ||
|  | # Signature Authentication Scheme
 | ||
|  | 
 | ||
|  | The "signature" authentication scheme is based on the model that the client must | ||
|  | authenticate itself with a digital signature produced by either a private | ||
|  | asymmetric key (e.g., RSA) or a shared symmetric key (e.g., HMAC).  The scheme | ||
|  | is parameterized enough such that it is not bound to any particular key type or | ||
|  | signing algorithm.  However, it does explicitly assume that clients can send an | ||
|  | HTTP `Date` header. | ||
|  | 
 | ||
|  | ## Authorization Header
 | ||
|  | 
 | ||
|  | The client is expected to send an Authorization header (as defined in RFC 2617) | ||
|  | with the following parameterization: | ||
|  | 
 | ||
|  |     credentials := "Signature" params | ||
|  |     params := 1#(keyId | algorithm | [headers] | [ext] | signature) | ||
|  |     digitalSignature := plain-string | ||
|  | 
 | ||
|  |     keyId := "keyId" "=" <"> plain-string <"> | ||
|  |     algorithm := "algorithm" "=" <"> plain-string <"> | ||
|  |     headers := "headers" "=" <"> 1#headers-value <"> | ||
|  |     ext := "ext" "=" <"> plain-string <"> | ||
|  |     signature := "signature" "=" <"> plain-string <"> | ||
|  | 
 | ||
|  |     headers-value := plain-string | ||
|  |     plain-string   = 1*( %x20-21 / %x23-5B / %x5D-7E ) | ||
|  | 
 | ||
|  | ### Signature Parameters
 | ||
|  | 
 | ||
|  | #### keyId
 | ||
|  | 
 | ||
|  | REQUIRED.  The `keyId` field is an opaque string that the server can use to look | ||
|  | up the component they need to validate the signature.  It could be an SSH key | ||
|  | fingerprint, an LDAP DN, etc.  Management of keys and assignment of `keyId` is | ||
|  | out of scope for this document. | ||
|  | 
 | ||
|  | #### algorithm
 | ||
|  | 
 | ||
|  | REQUIRED. The `algorithm` parameter is used if the client and server agree on a | ||
|  | non-standard digital signature algorithm.  The full list of supported signature | ||
|  | mechanisms is listed below. | ||
|  | 
 | ||
|  | #### headers
 | ||
|  | 
 | ||
|  | OPTIONAL.  The `headers` parameter is used to specify the list of HTTP headers | ||
|  | used to sign the request.  If specified, it should be a quoted list of HTTP | ||
|  | header names, separated by a single space character.  By default, only one | ||
|  | HTTP header is signed, which is the `Date` header.  Note that the list MUST be | ||
|  | specified in the order the values are concatenated together during signing. To | ||
|  | include the HTTP request line in the signature calculation, use the special | ||
|  | `request-line` value.  While this is overloading the definition of `headers` in | ||
|  | HTTP linguism, the request-line is defined in RFC 2616, and as the outlier from | ||
|  | headers in useful signature calculation, it is deemed simpler to simply use | ||
|  | `request-line` than to add a separate parameter for it. | ||
|  | 
 | ||
|  | #### extensions
 | ||
|  | 
 | ||
|  | OPTIONAL.  The `extensions` parameter is used to include additional information | ||
|  | which is covered by the request.  The content and format of the string is out of | ||
|  | scope for this document, and expected to be specified by implementors. | ||
|  | 
 | ||
|  | #### signature
 | ||
|  | 
 | ||
|  | REQUIRED.  The `signature` parameter is a `Base64` encoded digital signature | ||
|  | generated by the client. The client uses the `algorithm` and `headers` request | ||
|  | parameters to form a canonicalized `signing string`.  This `signing string` is | ||
|  | then signed with the key associated with `keyId` and the algorithm | ||
|  | corresponding to `algorithm`.  The `signature` parameter is then set to the | ||
|  | `Base64` encoding of the signature. | ||
|  | 
 | ||
|  | ### Signing String Composition
 | ||
|  | 
 | ||
|  | In order to generate the string that is signed with a key, the client MUST take | ||
|  | the values of each HTTP header specified by `headers` in the order they appear. | ||
|  | 
 | ||
|  | 1. If the header name is not `request-line` then append the lowercased header | ||
|  |    name followed with an ASCII colon `:` and an ASCII space ` `. | ||
|  | 2. If the header name is `request-line` then append the HTTP request line, | ||
|  |    otherwise append the header value. | ||
|  | 3. If value is not the last value then append an ASCII newline `\n`. The string | ||
|  |    MUST NOT include a trailing ASCII newline. | ||
|  | 
 | ||
|  | # Example Requests
 | ||
|  | 
 | ||
|  | All requests refer to the following request (body omitted): | ||
|  | 
 | ||
|  |     POST /foo HTTP/1.1 | ||
|  |     Host: example.org | ||
|  |     Date: Tue, 07 Jun 2014 20:51:35 GMT | ||
|  |     Content-Type: application/json | ||
|  |     Digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE= | ||
|  |     Content-Length: 18 | ||
|  | 
 | ||
|  | The "rsa-key-1" keyId refers to a private key known to the client and a public | ||
|  | key known to the server. The "hmac-key-1" keyId refers to key known to the | ||
|  | client and server. | ||
|  | 
 | ||
|  | ## Default parameterization
 | ||
|  | 
 | ||
|  | The authorization header and signature would be generated as: | ||
|  | 
 | ||
|  |     Authorization: Signature keyId="rsa-key-1",algorithm="rsa-sha256",signature="Base64(RSA-SHA256(signing string))" | ||
|  | 
 | ||
|  | The client would compose the signing string as: | ||
|  | 
 | ||
|  |     date: Tue, 07 Jun 2014 20:51:35 GMT | ||
|  | 
 | ||
|  | ## Header List
 | ||
|  | 
 | ||
|  | The authorization header and signature would be generated as: | ||
|  | 
 | ||
|  |     Authorization: Signature keyId="rsa-key-1",algorithm="rsa-sha256",headers="(request-target) date content-type digest",signature="Base64(RSA-SHA256(signing string))" | ||
|  | 
 | ||
|  | The client would compose the signing string as (`+ "\n"` inserted for | ||
|  | readability): | ||
|  | 
 | ||
|  |     (request-target) post /foo + "\n" | ||
|  |     date: Tue, 07 Jun 2011 20:51:35 GMT + "\n" | ||
|  |     content-type: application/json + "\n" | ||
|  |     digest: SHA-256=Base64(SHA256(Body)) | ||
|  | 
 | ||
|  | ## Algorithm
 | ||
|  | 
 | ||
|  | The authorization header and signature would be generated as: | ||
|  | 
 | ||
|  |     Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))" | ||
|  | 
 | ||
|  | The client would compose the signing string as: | ||
|  | 
 | ||
|  |     date: Tue, 07 Jun 2011 20:51:35 GMT | ||
|  | 
 | ||
|  | # Signing Algorithms
 | ||
|  | 
 | ||
|  | Currently supported algorithm names are: | ||
|  | 
 | ||
|  | * rsa-sha1 | ||
|  | * rsa-sha256 | ||
|  | * rsa-sha512 | ||
|  | * dsa-sha1 | ||
|  | * hmac-sha1 | ||
|  | * hmac-sha256 | ||
|  | * hmac-sha512 | ||
|  | 
 | ||
|  | # Security Considerations
 | ||
|  | 
 | ||
|  | ## Default Parameters
 | ||
|  | 
 | ||
|  | Note the default parameterization of the `Signature` scheme is only safe if all | ||
|  | requests are carried over a secure transport (i.e., TLS).  Sending the default | ||
|  | scheme over a non-secure transport will leave the request vulnerable to | ||
|  | spoofing, tampering, replay/repudiation, and integrity violations (if using the | ||
|  | STRIDE threat-modeling methodology). | ||
|  | 
 | ||
|  | ## Insecure Transports
 | ||
|  | 
 | ||
|  | If sending the request over plain HTTP, service providers SHOULD require clients | ||
|  | to sign ALL HTTP headers, and the `request-line`.  Additionally, service | ||
|  | providers SHOULD require `Content-MD5` calculations to be performed to ensure | ||
|  | against any tampering from clients. | ||
|  | 
 | ||
|  | ## Nonces
 | ||
|  | 
 | ||
|  | Nonces are out of scope for this document simply because many service providers | ||
|  | fail to implement them correctly, or do not adopt security specifications | ||
|  | because of the infrastructure complexity.  Given the `header` parameterization, | ||
|  | a service provider is fully enabled to add nonce semantics into this scheme by | ||
|  | using something like an `x-request-nonce` header, and ensuring it is signed | ||
|  | with the `Date` header. | ||
|  | 
 | ||
|  | ## Clock Skew
 | ||
|  | 
 | ||
|  | As the default scheme is to sign the `Date` header, service providers SHOULD | ||
|  | protect against logged replay attacks by enforcing a clock skew.  The server | ||
|  | SHOULD be synchronized with NTP, and the recommendation in this specification | ||
|  | is to allow 300s of clock skew (in either direction). | ||
|  | 
 | ||
|  | ## Required Headers to Sign
 | ||
|  | 
 | ||
|  | It is out of scope for this document to dictate what headers a service provider | ||
|  | will want to enforce, but service providers SHOULD at minimum include the | ||
|  | `Date` header. | ||
|  | 
 | ||
|  | # References
 | ||
|  | 
 | ||
|  | ## Normative References
 | ||
|  | 
 | ||
|  | * [RFC2616] Hypertext Transfer Protocol -- HTTP/1.1 | ||
|  | * [RFC2617] HTTP Authentication: Basic and Digest Access Authentication | ||
|  | * [RFC5246] The Transport Layer Security (TLS) Protocol Version 1.2 | ||
|  | 
 | ||
|  | ## Informative References
 | ||
|  | 
 | ||
|  |     Name: Mark Cavage (editor) | ||
|  |     Company: Joyent, Inc. | ||
|  |     Email: mark.cavage@joyent.com | ||
|  |     URI: http://www.joyent.com | ||
|  | 
 | ||
|  | # Appendix A - Test Values
 | ||
|  | 
 | ||
|  | The following test data uses the RSA (1024b) keys, which we will refer | ||
|  | to as `keyId=Test` in the following samples: | ||
|  | 
 | ||
|  |     -----BEGIN PUBLIC KEY----- | ||
|  |     MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3 | ||
|  |     6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6 | ||
|  |     Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw | ||
|  |     oYi+1hqp1fIekaxsyQIDAQAB | ||
|  |     -----END PUBLIC KEY----- | ||
|  | 
 | ||
|  |     -----BEGIN RSA PRIVATE KEY----- | ||
|  |     MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF | ||
|  |     NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F | ||
|  |     UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB | ||
|  |     AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA | ||
|  |     QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK | ||
|  |     kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg | ||
|  |     f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u | ||
|  |     412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc | ||
|  |     mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7 | ||
|  |     kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA | ||
|  |     gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW | ||
|  |     G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI | ||
|  |     7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA== | ||
|  |     -----END RSA PRIVATE KEY----- | ||
|  | 
 | ||
|  | And all examples use this request: | ||
|  | 
 | ||
|  | <!-- httpreq --> | ||
|  | 
 | ||
|  |     POST /foo?param=value&pet=dog HTTP/1.1 | ||
|  |     Host: example.com | ||
|  |     Date: Thu, 05 Jan 2014 21:31:40 GMT | ||
|  |     Content-Type: application/json | ||
|  |     Digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE= | ||
|  |     Content-Length: 18 | ||
|  | 
 | ||
|  |     {"hello": "world"} | ||
|  | 
 | ||
|  | <!-- /httpreq --> | ||
|  | 
 | ||
|  | ### Default
 | ||
|  | 
 | ||
|  | The string to sign would be: | ||
|  | 
 | ||
|  | <!-- sign {"name": "Default", "options": {"keyId":"Test", "algorithm": "rsa-sha256"}} --> | ||
|  | <!-- signstring --> | ||
|  | 
 | ||
|  |     date: Thu, 05 Jan 2014 21:31:40 GMT | ||
|  | 
 | ||
|  | <!-- /signstring --> | ||
|  | 
 | ||
|  | The Authorization header would be: | ||
|  | 
 | ||
|  | <!-- authz --> | ||
|  | 
 | ||
|  |     Authorization: Signature keyId="Test",algorithm="rsa-sha256",headers="date",signature="jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w=" | ||
|  | 
 | ||
|  | <!-- /authz --> | ||
|  | 
 | ||
|  | ### All Headers
 | ||
|  | 
 | ||
|  | Parameterized to include all headers, the string to sign would be (`+ "\n"` | ||
|  | inserted for readability): | ||
|  | 
 | ||
|  | <!-- sign {"name": "All Headers", "options": {"keyId":"Test", "algorithm": "rsa-sha256", "headers": ["(request-target)", "host", "date", "content-type", "digest", "content-length"]}} --> | ||
|  | <!-- signstring --> | ||
|  | 
 | ||
|  |     (request-target): post /foo?param=value&pet=dog | ||
|  |     host: example.com | ||
|  |     date: Thu, 05 Jan 2014 21:31:40 GMT | ||
|  |     content-type: application/json | ||
|  |     digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE= | ||
|  |     content-length: 18 | ||
|  | 
 | ||
|  | <!-- /signstring --> | ||
|  | 
 | ||
|  | The Authorization header would be: | ||
|  | 
 | ||
|  | <!-- authz --> | ||
|  | 
 | ||
|  |     Authorization: Signature keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="Ef7MlxLXoBovhil3AlyjtBwAL9g4TN3tibLj7uuNB3CROat/9KaeQ4hW2NiJ+pZ6HQEOx9vYZAyi+7cmIkmJszJCut5kQLAwuX+Ms/mUFvpKlSo9StS2bMXDBNjOh4Auj774GFj4gwjS+3NhFeoqyr/MuN6HsEnkvn6zdgfE2i0=" | ||
|  | 
 | ||
|  | <!-- /authz --> | ||
|  | 
 | ||
|  | ## Generating and verifying signatures using `openssl`
 | ||
|  | 
 | ||
|  | The `openssl` commandline tool can be used to generate or verify the signatures listed above. | ||
|  | 
 | ||
|  | Compose the signing string as usual, and pipe it into the the `openssl dgst` command, then into `openssl enc -base64`, as follows: | ||
|  | 
 | ||
|  |     $ printf 'date: Thu, 05 Jan 2014 21:31:40 GMT' | \ | ||
|  |       openssl dgst -binary -sign /path/to/private.pem -sha256 | \ | ||
|  |       openssl enc -base64 | ||
|  |     jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9Hp... | ||
|  |     $ | ||
|  | 
 | ||
|  | The `-sha256` option is necessary to produce an `rsa-sha256` signature. You can select other hash algorithms such as `sha1` by changing this argument. | ||
|  | 
 | ||
|  | To verify a signature, first save the signature data, Base64-decoded, into a file, then use `openssl dgst` again with the `-verify` option: | ||
|  | 
 | ||
|  |     $ echo 'jKyvPcxB4JbmYY4mByy...' | openssl enc -A -d -base64 > signature | ||
|  |     $ printf 'date: Thu, 05 Jan 2014 21:31:40 GMT' | \ | ||
|  |       openssl dgst -sha256 -verify /path/to/public.pem -signature ./signature | ||
|  |     Verified OK | ||
|  |     $ | ||
|  | 
 | ||
|  | ## Generating and verifying signatures using `sshpk-sign`
 | ||
|  | 
 | ||
|  | You can also generate and check signatures using the `sshpk-sign` tool which is | ||
|  | included with the `sshpk` package in `npm`. | ||
|  | 
 | ||
|  | Compose the signing string as above, and pipe it into `sshpk-sign` as follows: | ||
|  | 
 | ||
|  |     $ printf 'date: Thu, 05 Jan 2014 21:31:40 GMT' | \ | ||
|  |       sshpk-sign -i /path/to/private.pem | ||
|  |     jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9Hp... | ||
|  |     $ | ||
|  | 
 | ||
|  | This will produce an `rsa-sha256` signature by default, as you can see using | ||
|  | the `-v` option: | ||
|  | 
 | ||
|  |     sshpk-sign: using rsa-sha256 with a 1024 bit key | ||
|  | 
 | ||
|  | You can also use `sshpk-verify` in a similar manner: | ||
|  | 
 | ||
|  |     $ printf 'date: Thu, 05 Jan 2014 21:31:40 GMT' | \ | ||
|  |       sshpk-verify -i ./public.pem -s 'jKyvPcxB4JbmYY...' | ||
|  |     OK | ||
|  |     $ |