http_signing.md (13849B)
1 # Abstract 2 3 This document describes a way to add origin authentication, message integrity, 4 and replay resistance to HTTP REST requests. It is intended to be used over 5 the HTTPS protocol. 6 7 # Copyright Notice 8 9 Copyright (c) 2011 Joyent, Inc. and the persons identified as document authors. 10 All rights reserved. 11 12 Code Components extracted from this document must include MIT License text. 13 14 # Introduction 15 16 This protocol is intended to provide a standard way for clients to sign HTTP 17 requests. RFC2617 (HTTP Authentication) defines Basic and Digest authentication 18 mechanisms, and RFC5246 (TLS 1.2) defines client-auth, both of which are widely 19 employed on the Internet today. However, it is common place that the burdens of 20 PKI prevent web service operators from deploying that methodology, and so many 21 fall back to Basic authentication, which has poor security characteristics. 22 23 Additionally, OAuth provides a fully-specified alternative for authorization 24 of web service requests, but is not (always) ideal for machine to machine 25 communication, as the key acquisition steps (generally) imply a fixed 26 infrastructure that may not make sense to a service provider (e.g., symmetric 27 keys). 28 29 Several web service providers have invented their own schemes for signing 30 HTTP requests, but to date, none have been placed in the public domain as a 31 standard. This document serves that purpose. There are no techniques in this 32 proposal that are novel beyond previous art, however, this aims to be a simple 33 mechanism for signing these requests. 34 35 # Signature Authentication Scheme 36 37 The "signature" authentication scheme is based on the model that the client must 38 authenticate itself with a digital signature produced by either a private 39 asymmetric key (e.g., RSA) or a shared symmetric key (e.g., HMAC). The scheme 40 is parameterized enough such that it is not bound to any particular key type or 41 signing algorithm. However, it does explicitly assume that clients can send an 42 HTTP `Date` header. 43 44 ## Authorization Header 45 46 The client is expected to send an Authorization header (as defined in RFC 2617) 47 with the following parameterization: 48 49 credentials := "Signature" params 50 params := 1#(keyId | algorithm | [headers] | [ext] | signature) 51 digitalSignature := plain-string 52 53 keyId := "keyId" "=" <"> plain-string <"> 54 algorithm := "algorithm" "=" <"> plain-string <"> 55 headers := "headers" "=" <"> 1#headers-value <"> 56 ext := "ext" "=" <"> plain-string <"> 57 signature := "signature" "=" <"> plain-string <"> 58 59 headers-value := plain-string 60 plain-string = 1*( %x20-21 / %x23-5B / %x5D-7E ) 61 62 ### Signature Parameters 63 64 #### keyId 65 66 REQUIRED. The `keyId` field is an opaque string that the server can use to look 67 up the component they need to validate the signature. It could be an SSH key 68 fingerprint, an LDAP DN, etc. Management of keys and assignment of `keyId` is 69 out of scope for this document. 70 71 #### algorithm 72 73 REQUIRED. The `algorithm` parameter is used if the client and server agree on a 74 non-standard digital signature algorithm. The full list of supported signature 75 mechanisms is listed below. 76 77 #### headers 78 79 OPTIONAL. The `headers` parameter is used to specify the list of HTTP headers 80 used to sign the request. If specified, it should be a quoted list of HTTP 81 header names, separated by a single space character. By default, only one 82 HTTP header is signed, which is the `Date` header. Note that the list MUST be 83 specified in the order the values are concatenated together during signing. To 84 include the HTTP request line in the signature calculation, use the special 85 `request-line` value. While this is overloading the definition of `headers` in 86 HTTP linguism, the request-line is defined in RFC 2616, and as the outlier from 87 headers in useful signature calculation, it is deemed simpler to simply use 88 `request-line` than to add a separate parameter for it. 89 90 #### extensions 91 92 OPTIONAL. The `extensions` parameter is used to include additional information 93 which is covered by the request. The content and format of the string is out of 94 scope for this document, and expected to be specified by implementors. 95 96 #### signature 97 98 REQUIRED. The `signature` parameter is a `Base64` encoded digital signature 99 generated by the client. The client uses the `algorithm` and `headers` request 100 parameters to form a canonicalized `signing string`. This `signing string` is 101 then signed with the key associated with `keyId` and the algorithm 102 corresponding to `algorithm`. The `signature` parameter is then set to the 103 `Base64` encoding of the signature. 104 105 ### Signing String Composition 106 107 In order to generate the string that is signed with a key, the client MUST take 108 the values of each HTTP header specified by `headers` in the order they appear. 109 110 1. If the header name is not `request-line` then append the lowercased header 111 name followed with an ASCII colon `:` and an ASCII space ` `. 112 2. If the header name is `request-line` then append the HTTP request line, 113 otherwise append the header value. 114 3. If value is not the last value then append an ASCII newline `\n`. The string 115 MUST NOT include a trailing ASCII newline. 116 117 # Example Requests 118 119 All requests refer to the following request (body omitted): 120 121 POST /foo HTTP/1.1 122 Host: example.org 123 Date: Tue, 07 Jun 2014 20:51:35 GMT 124 Content-Type: application/json 125 Digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE= 126 Content-Length: 18 127 128 The "rsa-key-1" keyId refers to a private key known to the client and a public 129 key known to the server. The "hmac-key-1" keyId refers to key known to the 130 client and server. 131 132 ## Default parameterization 133 134 The authorization header and signature would be generated as: 135 136 Authorization: Signature keyId="rsa-key-1",algorithm="rsa-sha256",signature="Base64(RSA-SHA256(signing string))" 137 138 The client would compose the signing string as: 139 140 date: Tue, 07 Jun 2014 20:51:35 GMT 141 142 ## Header List 143 144 The authorization header and signature would be generated as: 145 146 Authorization: Signature keyId="rsa-key-1",algorithm="rsa-sha256",headers="(request-target) date content-type digest",signature="Base64(RSA-SHA256(signing string))" 147 148 The client would compose the signing string as (`+ "\n"` inserted for 149 readability): 150 151 (request-target) post /foo + "\n" 152 date: Tue, 07 Jun 2011 20:51:35 GMT + "\n" 153 content-type: application/json + "\n" 154 digest: SHA-256=Base64(SHA256(Body)) 155 156 ## Algorithm 157 158 The authorization header and signature would be generated as: 159 160 Authorization: Signature keyId="hmac-key-1",algorithm="hmac-sha1",signature="Base64(HMAC-SHA1(signing string))" 161 162 The client would compose the signing string as: 163 164 date: Tue, 07 Jun 2011 20:51:35 GMT 165 166 # Signing Algorithms 167 168 Currently supported algorithm names are: 169 170 * rsa-sha1 171 * rsa-sha256 172 * rsa-sha512 173 * dsa-sha1 174 * hmac-sha1 175 * hmac-sha256 176 * hmac-sha512 177 178 # Security Considerations 179 180 ## Default Parameters 181 182 Note the default parameterization of the `Signature` scheme is only safe if all 183 requests are carried over a secure transport (i.e., TLS). Sending the default 184 scheme over a non-secure transport will leave the request vulnerable to 185 spoofing, tampering, replay/repudiation, and integrity violations (if using the 186 STRIDE threat-modeling methodology). 187 188 ## Insecure Transports 189 190 If sending the request over plain HTTP, service providers SHOULD require clients 191 to sign ALL HTTP headers, and the `request-line`. Additionally, service 192 providers SHOULD require `Content-MD5` calculations to be performed to ensure 193 against any tampering from clients. 194 195 ## Nonces 196 197 Nonces are out of scope for this document simply because many service providers 198 fail to implement them correctly, or do not adopt security specifications 199 because of the infrastructure complexity. Given the `header` parameterization, 200 a service provider is fully enabled to add nonce semantics into this scheme by 201 using something like an `x-request-nonce` header, and ensuring it is signed 202 with the `Date` header. 203 204 ## Clock Skew 205 206 As the default scheme is to sign the `Date` header, service providers SHOULD 207 protect against logged replay attacks by enforcing a clock skew. The server 208 SHOULD be synchronized with NTP, and the recommendation in this specification 209 is to allow 300s of clock skew (in either direction). 210 211 ## Required Headers to Sign 212 213 It is out of scope for this document to dictate what headers a service provider 214 will want to enforce, but service providers SHOULD at minimum include the 215 `Date` header. 216 217 # References 218 219 ## Normative References 220 221 * [RFC2616] Hypertext Transfer Protocol -- HTTP/1.1 222 * [RFC2617] HTTP Authentication: Basic and Digest Access Authentication 223 * [RFC5246] The Transport Layer Security (TLS) Protocol Version 1.2 224 225 ## Informative References 226 227 Name: Mark Cavage (editor) 228 Company: Joyent, Inc. 229 Email: mark.cavage@joyent.com 230 URI: http://www.joyent.com 231 232 # Appendix A - Test Values 233 234 The following test data uses the RSA (1024b) keys, which we will refer 235 to as `keyId=Test` in the following samples: 236 237 -----BEGIN PUBLIC KEY----- 238 MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3 239 6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6 240 Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw 241 oYi+1hqp1fIekaxsyQIDAQAB 242 -----END PUBLIC KEY----- 243 244 -----BEGIN RSA PRIVATE KEY----- 245 MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF 246 NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F 247 UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB 248 AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA 249 QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK 250 kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg 251 f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u 252 412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc 253 mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7 254 kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA 255 gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW 256 G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI 257 7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA== 258 -----END RSA PRIVATE KEY----- 259 260 And all examples use this request: 261 262 <!-- httpreq --> 263 264 POST /foo?param=value&pet=dog HTTP/1.1 265 Host: example.com 266 Date: Thu, 05 Jan 2014 21:31:40 GMT 267 Content-Type: application/json 268 Digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE= 269 Content-Length: 18 270 271 {"hello": "world"} 272 273 <!-- /httpreq --> 274 275 ### Default 276 277 The string to sign would be: 278 279 <!-- sign {"name": "Default", "options": {"keyId":"Test", "algorithm": "rsa-sha256"}} --> 280 <!-- signstring --> 281 282 date: Thu, 05 Jan 2014 21:31:40 GMT 283 284 <!-- /signstring --> 285 286 The Authorization header would be: 287 288 <!-- authz --> 289 290 Authorization: Signature keyId="Test",algorithm="rsa-sha256",headers="date",signature="jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w=" 291 292 <!-- /authz --> 293 294 ### All Headers 295 296 Parameterized to include all headers, the string to sign would be (`+ "\n"` 297 inserted for readability): 298 299 <!-- sign {"name": "All Headers", "options": {"keyId":"Test", "algorithm": "rsa-sha256", "headers": ["(request-target)", "host", "date", "content-type", "digest", "content-length"]}} --> 300 <!-- signstring --> 301 302 (request-target): post /foo?param=value&pet=dog 303 host: example.com 304 date: Thu, 05 Jan 2014 21:31:40 GMT 305 content-type: application/json 306 digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE= 307 content-length: 18 308 309 <!-- /signstring --> 310 311 The Authorization header would be: 312 313 <!-- authz --> 314 315 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=" 316 317 <!-- /authz --> 318 319 ## Generating and verifying signatures using `openssl` 320 321 The `openssl` commandline tool can be used to generate or verify the signatures listed above. 322 323 Compose the signing string as usual, and pipe it into the the `openssl dgst` command, then into `openssl enc -base64`, as follows: 324 325 $ printf 'date: Thu, 05 Jan 2014 21:31:40 GMT' | \ 326 openssl dgst -binary -sign /path/to/private.pem -sha256 | \ 327 openssl enc -base64 328 jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9Hp... 329 $ 330 331 The `-sha256` option is necessary to produce an `rsa-sha256` signature. You can select other hash algorithms such as `sha1` by changing this argument. 332 333 To verify a signature, first save the signature data, Base64-decoded, into a file, then use `openssl dgst` again with the `-verify` option: 334 335 $ echo 'jKyvPcxB4JbmYY4mByy...' | openssl enc -A -d -base64 > signature 336 $ printf 'date: Thu, 05 Jan 2014 21:31:40 GMT' | \ 337 openssl dgst -sha256 -verify /path/to/public.pem -signature ./signature 338 Verified OK 339 $ 340 341 ## Generating and verifying signatures using `sshpk-sign` 342 343 You can also generate and check signatures using the `sshpk-sign` tool which is 344 included with the `sshpk` package in `npm`. 345 346 Compose the signing string as above, and pipe it into `sshpk-sign` as follows: 347 348 $ printf 'date: Thu, 05 Jan 2014 21:31:40 GMT' | \ 349 sshpk-sign -i /path/to/private.pem 350 jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9Hp... 351 $ 352 353 This will produce an `rsa-sha256` signature by default, as you can see using 354 the `-v` option: 355 356 sshpk-sign: using rsa-sha256 with a 1024 bit key 357 358 You can also use `sshpk-verify` in a similar manner: 359 360 $ printf 'date: Thu, 05 Jan 2014 21:31:40 GMT' | \ 361 sshpk-verify -i ./public.pem -s 'jKyvPcxB4JbmYY...' 362 OK 363 $