[6a3a178] | 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 | $
|
---|