Skip to content
Permalink
Browse files

Multiple key/salt pairs support

  • Loading branch information...
DarthSim committed Nov 15, 2018
1 parent a537e05 commit 9114f28c756d3d8d99556680c9b47fd45bf63993
Showing with 77 additions and 39 deletions.
  1. +44 −26 config.go
  2. +10 −6 crypt.go
  3. +16 −2 crypt_test.go
  4. +3 −1 docs/configuration.md
  5. +4 −4 processing_options_test.go
@@ -2,11 +2,9 @@ package main

import (
"bufio"
"bytes"
"encoding/hex"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"runtime"
@@ -45,17 +43,25 @@ func boolEnvConfig(b *bool, name string) {
}
}

func hexEnvConfig(b *[]byte, name string) {
func hexEnvConfig(b *[]securityKey, name string) {
var err error

if env := os.Getenv(name); len(env) > 0 {
if *b, err = hex.DecodeString(env); err != nil {
log.Fatalf("%s expected to be hex-encoded string\n", name)
parts := strings.Split(env, ",")

keys := make([]securityKey, len(parts))

for i, part := range parts {
if keys[i], err = hex.DecodeString(part); err != nil {
log.Fatalf("%s expected to be hex-encoded strings. Invalid: %s\n", name, part)
}
}

*b = keys
}
}

func hexFileConfig(b *[]byte, filepath string) {
func hexFileConfig(b *[]securityKey, filepath string) {
if len(filepath) == 0 {
return
}
@@ -65,20 +71,28 @@ func hexFileConfig(b *[]byte, filepath string) {
log.Fatalf("Can't open file %s\n", filepath)
}

src, err := ioutil.ReadAll(f)
if err != nil {
log.Fatalln(err)
}
keys := []securityKey{}

src = bytes.TrimSpace(src)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
part := scanner.Text()

dst := make([]byte, hex.DecodedLen(len(src)))
n, err := hex.Decode(dst, src)
if err != nil {
log.Fatalf("%s expected to contain hex-encoded string\n", filepath)
if len(part) == 0 {
continue
}

if key, err := hex.DecodeString(part); err == nil {
keys = append(keys, key)
} else {
log.Fatalf("%s expected to contain hex-encoded strings. Invalid: %s\n", filepath, part)
}
}

*b = dst[:n]
if err := scanner.Err(); err != nil {
log.Fatalf("Failed to read file %s: %s", filepath, err)
}

*b = keys
}

func presetEnvConfig(p presets, name string) {
@@ -137,8 +151,8 @@ type config struct {
EnforceWebp bool
EnableClientHints bool

Key []byte
Salt []byte
Keys []securityKey
Salts []securityKey
AllowInsecure bool
SignatureSize int

@@ -237,12 +251,12 @@ func init() {
boolEnvConfig(&conf.EnforceWebp, "IMGPROXY_ENFORCE_WEBP")
boolEnvConfig(&conf.EnableClientHints, "IMGPROXY_ENABLE_CLIENT_HINTS")

hexEnvConfig(&conf.Key, "IMGPROXY_KEY")
hexEnvConfig(&conf.Salt, "IMGPROXY_SALT")
hexEnvConfig(&conf.Keys, "IMGPROXY_KEY")
hexEnvConfig(&conf.Salts, "IMGPROXY_SALT")
intEnvConfig(&conf.SignatureSize, "IMGPROXY_SIGNATURE_SIZE")

hexFileConfig(&conf.Key, *keyPath)
hexFileConfig(&conf.Salt, *saltPath)
hexFileConfig(&conf.Keys, *keyPath)
hexFileConfig(&conf.Salts, *saltPath)

strEnvConfig(&conf.Secret, "IMGPROXY_SECRET")

@@ -283,14 +297,18 @@ func init() {
strEnvConfig(&conf.HoneybadgerKey, "IMGPROXY_HONEYBADGER_KEY")
strEnvConfig(&conf.HoneybadgerEnv, "IMGPROXY_HONEYBADGER_ENV")

if len(conf.Key) == 0 {
warning("Key is not defined, so signature checking is disabled")
if len(conf.Keys) != len(conf.Salts) {
log.Fatalf("Number of keys and number of salts should be equal. Keys: %d, salts: %d", len(conf.Keys), len(conf.Salts))
}
if len(conf.Keys) == 0 {
warning("No keys defined, so signature checking is disabled")
conf.AllowInsecure = true
}
if len(conf.Salt) == 0 {
warning("Salt is not defined, so signature checking is disabled")
if len(conf.Salts) == 0 {
warning("No salts defined, so signature checking is disabled")
conf.AllowInsecure = true
}

if conf.SignatureSize < 1 || conf.SignatureSize > 32 {
log.Fatalf("Signature size should be within 1 and 32, now - %d\n", conf.SignatureSize)
}
@@ -12,22 +12,26 @@ var (
errInvalidTokenEncoding = errors.New("Invalid token encoding")
)

type securityKey []byte

func validatePath(token, path string) error {
messageMAC, err := base64.RawURLEncoding.DecodeString(token)
if err != nil {
return errInvalidTokenEncoding
}

if !hmac.Equal(messageMAC, signatureFor(path)) {
return errInvalidToken
for i := 0; i < len(conf.Keys); i++ {
if hmac.Equal(messageMAC, signatureFor(path, i)) {
return nil
}
}

return nil
return errInvalidToken
}

func signatureFor(str string) []byte {
mac := hmac.New(sha256.New, conf.Key)
mac.Write(conf.Salt)
func signatureFor(str string, pairInd int) []byte {
mac := hmac.New(sha256.New, conf.Keys[pairInd])
mac.Write(conf.Salts[pairInd])
mac.Write([]byte(str))
expectedMAC := mac.Sum(nil)
if conf.SignatureSize < 32 {
@@ -12,8 +12,8 @@ type CryptTestSuite struct{ MainTestSuite }
func (s *CryptTestSuite) SetupTest() {
s.MainTestSuite.SetupTest()

conf.Key = []byte("test-key")
conf.Salt = []byte("test-salt")
conf.Keys = []securityKey{securityKey("test-key")}
conf.Salts = []securityKey{securityKey("test-salt")}
}

func (s *CryptTestSuite) TestValidatePath() {
@@ -33,6 +33,20 @@ func (s *CryptTestSuite) TestValidatePathInvalid() {
assert.Error(s.T(), err)
}

func (s *CryptTestSuite) TestValidatePathMultiplePairs() {
conf.Keys = append(conf.Keys, securityKey("test-key2"))
conf.Salts = append(conf.Salts, securityKey("test-salt2"))

err := validatePath("dtLwhdnPPiu_epMl1LrzheLpvHas-4mwvY6L3Z8WwlY", "asd")
assert.Nil(s.T(), err)

err = validatePath("jbDffNPt1-XBgDccsaE-XJB9lx8JIJqdeYIZKgOqZpg", "asd")
assert.Nil(s.T(), err)

err = validatePath("dtLwhdnPPis", "asd")
assert.Error(s.T(), err)
}

func TestCrypt(t *testing.T) {
suite.Run(t, new(CryptTestSuite))
}
@@ -10,7 +10,9 @@ imgproxy allows URLs to be signed with a key and salt. This feature is disabled
* `IMGPROXY_SALT`: hex-encoded salt;
* `IMGPROXY_SIGNATURE_SIZE`: number of bytes to use for signature before encoding to Base64. Default: 32;

You can also specify paths to files with a hex-encoded key and salt (useful in a development environment):
You can specify multiple key/salt pairs by dividing keys and salts with comma (`,`). imgproxy will check URL signatures with each pair. Useful when you need to change key/salt pair in your application with zero downtime.

You can also specify paths to files with a hex-encoded keys and salts, one by line (useful in a development environment):

```bash
$ imgproxy -keypath /path/to/file/with/key -saltpath /path/to/file/with/salt
@@ -533,8 +533,8 @@ func (s *ProcessingOptionsTestSuite) TestParsePathDprHeaderDisabled() {
}

func (s *ProcessingOptionsTestSuite) TestParsePathSigned() {
conf.Key = []byte("test-key")
conf.Salt = []byte("test-salt")
conf.Keys = []securityKey{securityKey("test-key")}
conf.Salts = []securityKey{securityKey("test-salt")}
conf.AllowInsecure = false

req := s.getRequest("http://example.com/HcvNognEV1bW6f8zRqxNYuOkV0IUf1xloRb57CzbT4g/width:150/plain/http://images.dev/lorem/ipsum.jpg@png")
@@ -544,8 +544,8 @@ func (s *ProcessingOptionsTestSuite) TestParsePathSigned() {
}

func (s *ProcessingOptionsTestSuite) TestParsePathSignedInvalid() {
conf.Key = []byte("test-key")
conf.Salt = []byte("test-salt")
conf.Keys = []securityKey{securityKey("test-key")}
conf.Salts = []securityKey{securityKey("test-salt")}
conf.AllowInsecure = false

req := s.getRequest("http://example.com/unsafe/width:150/plain/http://images.dev/lorem/ipsum.jpg@png")

0 comments on commit 9114f28

Please sign in to comment.
You can’t perform that action at this time.