diff --git a/docs/url-signing.md b/docs/url-signing.md index 19bcaa1..fcbbdf5 100644 --- a/docs/url-signing.md +++ b/docs/url-signing.md @@ -1,6 +1,71 @@ # How to generate signed requests -## Go +Signing requests allows an imageproxy instance to proxy images from arbitrary +remote hosts, but without opening the service up for potential abuse. When +appropriately configured, the imageproxy instance will only serve requests that +are for allowed hosts, or which have a valid signature. + +Signatures can be calculated in two ways: + +1. they can be calculated solely on the remote image URL, in which case any + transformations of the image can be requested without changes to the + signature value. This used to be the only way to sign requests, but is no + longer recommended since it still leaves the imageproxy instance open to + potential abuse. + +2. they can be calculated based on the combination of the remote image URL and + the requested transformation options. + +In both cases, the signature is calculated using HMAC-SHA256 and a secret key +which is provided to imageproxy on startup. The message to be signed is the +remote URL, with the transformation options optionally set as the URL fragment, +[as documented below](#Signing-options). The signature is url-safe base64 +encoded, and [provided as an option][s-option] in the imageproxy request. + +imageproxy will accept signatures for URLs with or without options +transparently. It's up to the publisher of the signed URLs to decide which +method they use to generate the URL. + +[s-option]: https://godoc.org/willnorris.com/go/imageproxy#hdr-Signature + +## Signing options + +Transformation options for a proxied URL are [specified as a comma separated +string][ParseOptions] of individual options, which can be supplied in any +order. When calculating a signature, options should be put in their canonical +form, sorted in lexigraphical order (omitting the signature option itself), and +appended to the remote URL as the URL fragment. + +Currently, only [size option][] has a canonical form, which is +`{width}x{height}` with the number `0` used when no value is specified. For +example, a request that does not request any size option would still have a +canonical size value of `0x0`, indicating that no size transformation is being +performed. If only a height of 500px is requested, the canonical form would be +`0x500`. + +For example, requesting the remote URL of `http://example.com/image.jpg`, +resized to 100 pixels square, rotated 90 degrees, and converted to 75% quality +might produce an imageproxy URL similar to: + + http://localhost:8080/100,r90,q75/http://example.com/image.jpg + +When calculating a signature for this request including transformation options, +the signed value would be: + + http://example.com/image.jpg#100x100,q75,r90 + +The `100` size option was put in its canonical form of `100x100`, and the +options are sorted, moving `q75` before `r90`. + +[ParseOptions]: https://godoc.org/willnorris.com/go/imageproxy#ParseOptions +[size option]: https://godoc.org/willnorris.com/go/imageproxy#hdr-Size_and_Cropping + +## Language Examples + +Here are examples of calculating signatures in a variety of languages. These +demonstrate the HMAC-SHA256 bits, but not the option canonicalization. + +### Go main.go: ```go @@ -27,14 +92,14 @@ $ go run main.go "test" "https://www.google.fr/images/srpr/logo11w.png" result: RYifAJRfbhsitJeOrDNxWURCCkPsVR4ihCPXNv-ePbA= ``` -## OpenSSL +### OpenSSL ```shell $ echo -n "https://www.google.fr/images/srpr/logo11w.png" | openssl dgst -sha256 -hmac "test" -binary|base64| tr '/+' '_-' RYifAJRfbhsitJeOrDNxWURCCkPsVR4ihCPXNv-ePbA= ``` -## Java +### Java ```java import org.apache.commons.codec.binary.Base64; @@ -63,7 +128,7 @@ $ java -cp commons-codec-1.10.jar:. EncodeUrl test https://www.google.fr/images/ RYifAJRfbhsitJeOrDNxWURCCkPsVR4ihCPXNv-ePbA ``` -## Ruby +### Ruby ```ruby require 'openssl' @@ -79,7 +144,7 @@ puts Base64.urlsafe_encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), RYifAJRfbhsitJeOrDNxWURCCkPsVR4ihCPXNv-ePbA= ``` -## Python +### Python ```python import hmac @@ -91,7 +156,7 @@ data = 'https://octodex.github.com/images/codercat.jpg' print base64.urlsafe_b64encode(hmac.new(key, msg=data, digestmod=hashlib.sha256).digest()) ``` -## JavaScript +### JavaScript ```javascript import crypto from 'crypto'; @@ -102,7 +167,7 @@ let data = 'https://octodex.github.com/images/codercat.jpg'; console.log(URLSafeBase64.encode(crypto.createHmac('sha256', key).update(data).digest())); ``` -## PHP +### PHP ````php