diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 035f6282..0a10c91e 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -30,6 +30,10 @@ "ImportPath": "github.com/davecgh/go-spew/spew", "Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d" }, + { + "ImportPath": "github.com/fernet/fernet-go", + "Rev": "1b2437bc582b3cfbb341ee5a29f8ef5b42912ff2" + }, { "ImportPath": "github.com/go-sql-driver/mysql", "Comment": "v1.2-125-gd512f20", diff --git a/api/context/context.go b/api/context/context.go index fb4e4552..e4f2953d 100644 --- a/api/context/context.go +++ b/api/context/context.go @@ -23,6 +23,7 @@ import ( "github.com/julienschmidt/httprouter" "github.com/prometheus/client_golang/prometheus" + "github.com/coreos/clair/config" "github.com/coreos/clair/database" "github.com/coreos/clair/utils" ) @@ -58,5 +59,6 @@ func HTTPHandler(handler Handler, ctx *RouteContext) httprouter.Handle { } type RouteContext struct { - Store database.Datastore + Store database.Datastore + Config *config.APIConfig } diff --git a/api/v1/models.go b/api/v1/models.go index 4f4fdda0..4c6461d1 100644 --- a/api/v1/models.go +++ b/api/v1/models.go @@ -15,13 +15,20 @@ package v1 import ( + "bytes" + "encoding/json" "errors" "fmt" + "time" "github.com/coreos/clair/database" "github.com/coreos/clair/utils/types" + "github.com/coreos/pkg/capnslog" + "github.com/fernet/fernet-go" ) +var log = capnslog.NewPackageLogger("github.com/coreos/clair", "v1") + type Error struct { Message string `json:"Layer` } @@ -187,10 +194,9 @@ type Notification struct { NextPage string `json:"NextPage,omitempty"` Old *VulnerabilityWithLayers `json:"Old,omitempty"` New *VulnerabilityWithLayers `json:"New,omitempty"` - Changed []string `json:"Changed,omitempty"` } -func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, page, nextPage database.VulnerabilityNotificationPageNumber) Notification { +func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, page, nextPage database.VulnerabilityNotificationPageNumber, key string) Notification { var oldVuln *VulnerabilityWithLayers if dbNotification.OldVulnerability != nil { v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability) @@ -205,7 +211,7 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica var nextPageStr string if nextPage != database.NoVulnerabilityNotificationPage { - nextPageStr = DBPageNumberToString(nextPage) + nextPageStr = pageNumberToToken(nextPage, key) } var created, notified, deleted string @@ -227,7 +233,7 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica Notified: notified, Deleted: deleted, Limit: limit, - Page: DBPageNumberToString(page), + Page: pageNumberToToken(page, key), NextPage: nextPageStr, Old: oldVuln, New: newVuln, @@ -279,14 +285,30 @@ type FeatureEnvelope struct { Error *Error `json:"Error,omitempty"` } -func pageStringToDBPageNumber(pageStr string) (database.VulnerabilityNotificationPageNumber, error) { - // TODO(jzelinskie): turn pagination into an encrypted token - var old, new int - _, err := fmt.Sscanf(pageStr, "%d-%d", &old, &new) - return database.VulnerabilityNotificationPageNumber{old, new}, err +func tokenToPageNumber(token, key string) (database.VulnerabilityNotificationPageNumber, error) { + k, _ := fernet.DecodeKey(key) + msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k}) + if msg == nil { + return database.VulnerabilityNotificationPageNumber{}, errors.New("invalid or expired pagination token") + } + + page := database.VulnerabilityNotificationPageNumber{} + err := json.NewDecoder(bytes.NewBuffer(msg)).Decode(&page) + return page, err } -func DBPageNumberToString(page database.VulnerabilityNotificationPageNumber) string { - // TODO(jzelinskie): turn pagination into an encrypted token - return fmt.Sprintf("%d-%d", page.OldVulnerability, page.NewVulnerability) +func pageNumberToToken(page database.VulnerabilityNotificationPageNumber, key string) string { + var buf bytes.Buffer + err := json.NewEncoder(&buf).Encode(page) + if err != nil { + log.Fatal("failed to encode VulnerabilityNotificationPageNumber") + } + + k, _ := fernet.DecodeKey(key) + tokenBytes, err := fernet.EncryptAndSign(buf.Bytes(), k) + if err != nil { + log.Fatal("failed to encrypt VulnerabilityNotificationpageNumber") + } + + return string(tokenBytes) } diff --git a/api/v1/routes.go b/api/v1/routes.go index 1e4a2cb5..673a562a 100644 --- a/api/v1/routes.go +++ b/api/v1/routes.go @@ -321,7 +321,7 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params page := database.VulnerabilityNotificationFirstPage pageStrs, pageExists := query["page"] if pageExists { - page, err = pageStringToDBPageNumber(pageStrs[0]) + page, err = tokenToPageNumber(pageStrs[0], ctx.Config.PaginationKey) if err != nil { writeResponse(w, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}}) return getNotificationRoute, http.StatusBadRequest @@ -337,7 +337,7 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params return getNotificationRoute, http.StatusInternalServerError } - notification := NotificationFromDatabaseModel(dbNotification, limit, page, nextPage) + notification := NotificationFromDatabaseModel(dbNotification, limit, page, nextPage, ctx.Config.PaginationKey) writeResponse(w, http.StatusOK, NotificationEnvelope{Notification: ¬ification}) return getNotificationRoute, http.StatusOK diff --git a/clair.go b/clair.go index 64e2acc0..a445f797 100644 --- a/clair.go +++ b/clair.go @@ -53,9 +53,9 @@ func Boot(config *config.Config) { // Start API st.Begin() - go api.Run(config.API, &context.RouteContext{db}, st) + go api.Run(config.API, &context.RouteContext{db, config.API}, st) st.Begin() - go api.RunHealth(config.API, &context.RouteContext{db}, st) + go api.RunHealth(config.API, &context.RouteContext{db, config.API}, st) // Start updater st.Begin() diff --git a/config.example.yaml b/config.example.yaml index c2117a65..f51db061 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -4,7 +4,7 @@ database: # PostgreSQL Connection string. # Reference: http://www.postgresql.org/docs/9.4/static/libpq-connect.html - source: + source: # Number of elements to keep in the cache. cacheSize: 16384 api: @@ -13,6 +13,8 @@ api: healthport: 6061 # Maximum time that API requests may take before they time-out with a HTTP 503 error. timeout: 900s + # 32-bit key used to encrypt pagination tokens + paginationKey: "2E9IgrgWLNb4gjuU0WbiBIudLH8xolz_qxFn--vxJP8=" # Paths to certificates to secure the main API with TLS and client certificate auth. cafile: keyfile: diff --git a/config/config.go b/config/config.go index d6c0934f..1fecf6a3 100644 --- a/config/config.go +++ b/config/config.go @@ -19,6 +19,7 @@ import ( "os" "time" + "github.com/fernet/fernet-go" "gopkg.in/yaml.v2" ) @@ -44,9 +45,9 @@ type UpdaterConfig struct { // NotifierConfig is the configuration for the Notifier service and its registered notifiers. type NotifierConfig struct { - Attempts int - RenotifyInterval time.Duration - Params map[string]interface{} `yaml:",inline"` + Attempts int + RenotifyInterval time.Duration + Params map[string]interface{} `yaml:",inline"` } // APIConfig is the configuration for the API service. @@ -54,6 +55,7 @@ type APIConfig struct { Port int HealthPort int Timeout time.Duration + PaginationKey string CertFile, KeyFile, CAFile string } @@ -71,8 +73,8 @@ var DefaultConfig = Config{ Timeout: 900 * time.Second, }, Notifier: &NotifierConfig{ - Attempts: 5, - RenotifyInterval: 2 * time.Hour, + Attempts: 5, + RenotifyInterval: 2 * time.Hour, }, } @@ -96,5 +98,22 @@ func Load(path string) (config *Config, err error) { } err = yaml.Unmarshal(d, config) + if err != nil { + return + } + + if config.API.PaginationKey == "" { + var key fernet.Key + if err = key.Generate(); err != nil { + return + } + config.API.PaginationKey = key.Encode() + } else { + _, err = fernet.DecodeKey(config.API.PaginationKey) + if err != nil { + return + } + } + return } diff --git a/vendor/github.com/fernet/fernet-go/License b/vendor/github.com/fernet/fernet-go/License new file mode 100644 index 00000000..14214fbe --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/License @@ -0,0 +1,20 @@ +Copyright © 2013 Keith Rarick + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/fernet/fernet-go/Readme b/vendor/github.com/fernet/fernet-go/Readme new file mode 100644 index 00000000..50cc26cf --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/Readme @@ -0,0 +1,22 @@ +Fernet takes a user-provided *message* (an arbitrary sequence of +bytes), a *key* (256 bits), and the current time, and produces a +*token*, which contains the message in a form that can't be read +or altered without the key. + +This package is compatible with the other implementations at +https://github.com/fernet. They can exchange tokens freely among +each other. + +Documentation: http://godoc.org/github.com/fernet/fernet-go + + +INSTALL + + $ go get github.com/fernet/fernet-go + + +For more information and background, see the Fernet spec at +https://github.com/fernet/spec. + +Fernet is distributed under the terms of the MIT license. +See the License file for details. diff --git a/vendor/github.com/fernet/fernet-go/cmd/fernet-keygen/main.go b/vendor/github.com/fernet/fernet-go/cmd/fernet-keygen/main.go new file mode 100644 index 00000000..eb6d1976 --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/cmd/fernet-keygen/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "log" + + "github.com/fernet/fernet-go" +) + +func main() { + log.SetFlags(0) + log.SetPrefix("fernet: ") + + var key fernet.Key + if err := key.Generate(); err != nil { + log.Fatal(err) + } + fmt.Println(key.Encode()) +} diff --git a/vendor/github.com/fernet/fernet-go/cmd/fernet-sign/main.go b/vendor/github.com/fernet/fernet-go/cmd/fernet-sign/main.go new file mode 100644 index 00000000..67a11be1 --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/cmd/fernet-sign/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + + "github.com/fernet/fernet-go" +) + +const Usage = `Usage: fernet-sign ENV + +fernet-sign encrypts and signs its input and prints the resulting token. +It uses the key in environment variable ENV.` + +func main() { + log.SetFlags(0) + log.SetPrefix("fernet: ") + + if len(os.Args) != 2 { + fmt.Fprintln(os.Stderr, Usage) + os.Exit(2) + } + + key, err := fernet.DecodeKey(os.Getenv(os.Args[1])) + if err != nil { + log.Fatalln(err) + } + + b, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatalln(err) + } + + t, err := fernet.EncryptAndSign(b, key) + if err != nil { + log.Fatalln(err) + } + + _, err = os.Stdout.Write(append(t, '\n')) + if err != nil { + log.Fatalln(err) + } +} diff --git a/vendor/github.com/fernet/fernet-go/fernet.go b/vendor/github.com/fernet/fernet-go/fernet.go new file mode 100644 index 00000000..8549e69c --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/fernet.go @@ -0,0 +1,168 @@ +// Package fernet takes a user-provided message (an arbitrary +// sequence of bytes), a key (256 bits), and the current time, +// and produces a token, which contains the message in a form +// that can't be read or altered without the key. +// +// For more information and background, see the Fernet spec +// at https://github.com/fernet/spec. +// +// Subdirectories in this package provide command-line tools +// for working with Fernet keys and tokens. +package fernet + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "encoding/binary" + "io" + "time" +) + +const ( + version byte = 0x80 + tsOffset = 1 + ivOffset = tsOffset + 8 + payOffset = ivOffset + aes.BlockSize + overhead = 1 + 8 + aes.BlockSize + sha256.Size // ver + ts + iv + hmac + maxClockSkew = 60 * time.Second +) + +var encoding = base64.URLEncoding + +// generates a token from msg, writes it into tok, and returns the +// number of bytes generated, which is encodedLen(msg). +// len(tok) must be >= encodedLen(len(msg)) +func gen(tok, msg, iv []byte, ts time.Time, k *Key) int { + tok[0] = version + binary.BigEndian.PutUint64(tok[tsOffset:], uint64(ts.Unix())) + copy(tok[ivOffset:], iv) + p := tok[payOffset:] + n := pad(p, msg, aes.BlockSize) + bc, _ := aes.NewCipher(k.cryptBytes()) + cipher.NewCBCEncrypter(bc, iv).CryptBlocks(p[:n], p[:n]) + genhmac(p[n:n], tok[:payOffset+n], k.signBytes()) + return payOffset + n + sha256.Size +} + +// token length for input msg of length n, not including base64 +func encodedLen(n int) int { + const k = aes.BlockSize + return n/k*k + k + overhead +} + +// max msg length for tok of length n, for binary token (no base64) +// upper bound; not exact +func decodedLen(n int) int { + return n - overhead +} + +// if msg is nil, decrypts in place and returns a slice of tok. +func verify(msg, tok []byte, ttl time.Duration, now time.Time, k *Key) []byte { + if len(tok) < 1 || tok[0] != version { + return nil + } + ts := time.Unix(int64(binary.BigEndian.Uint64(tok[1:])), 0) + if ttl >= 0 && (now.After(ts.Add(ttl)) || ts.After(now.Add(maxClockSkew))) { + return nil + } + n := len(tok) - sha256.Size + var hmac [sha256.Size]byte + genhmac(hmac[:0], tok[:n], k.signBytes()) + if subtle.ConstantTimeCompare(tok[n:], hmac[:]) != 1 { + return nil + } + pay := tok[payOffset : len(tok)-sha256.Size] + if len(pay)%aes.BlockSize != 0 { + return nil + } + if msg != nil { + copy(msg, pay) + pay = msg + } + bc, _ := aes.NewCipher(k.cryptBytes()) + iv := tok[9:][:aes.BlockSize] + cipher.NewCBCDecrypter(bc, iv).CryptBlocks(pay, pay) + return unpad(pay) +} + +// Pads p to a multiple of k using PKCS #7 standard block padding. +// See http://tools.ietf.org/html/rfc5652#section-6.3. +func pad(q, p []byte, k int) int { + n := len(p)/k*k + k + copy(q, p) + c := byte(n - len(p)) + for i := len(p); i < n; i++ { + q[i] = c + } + return n +} + +// Removes PKCS #7 standard block padding from p. +// See http://tools.ietf.org/html/rfc5652#section-6.3. +// This function is the inverse of pad. +// If the padding is not well-formed, unpad returns nil. +func unpad(p []byte) []byte { + c := p[len(p)-1] + for i := len(p) - int(c); i < len(p); i++ { + if i < 0 || p[i] != c { + return nil + } + } + return p[:len(p)-int(c)] +} + +func b64enc(src []byte) []byte { + dst := make([]byte, encoding.EncodedLen(len(src))) + encoding.Encode(dst, src) + return dst +} + +func b64dec(src []byte) []byte { + dst := make([]byte, encoding.DecodedLen(len(src))) + n, err := encoding.Decode(dst, src) + if err != nil { + return nil + } + return dst[:n] +} + +func genhmac(q, p, k []byte) { + h := hmac.New(sha256.New, k) + h.Write(p) + h.Sum(q) +} + +// EncryptAndSign encrypts and signs msg with key k and returns the resulting +// fernet token. If msg contains text, the text should be encoded +// with UTF-8 to follow fernet convention. +func EncryptAndSign(msg []byte, k *Key) (tok []byte, err error) { + iv := make([]byte, aes.BlockSize) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + b := make([]byte, encodedLen(len(msg))) + n := gen(b, msg, iv, time.Now(), k) + tok = make([]byte, encoding.EncodedLen(n)) + encoding.Encode(tok, b[:n]) + return tok, nil +} + +// VerifyAndDecrypt verifies that tok is a valid fernet token that was signed +// with a key in k at most ttl time ago only if ttl is greater than zero. +// Returns the message contained in tok if tok is valid, otherwise nil. +func VerifyAndDecrypt(tok []byte, ttl time.Duration, k []*Key) (msg []byte) { + b := make([]byte, encoding.DecodedLen(len(tok))) + n, _ := encoding.Decode(b, tok) + for _, k1 := range k { + msg = verify(nil, b[:n], ttl, time.Now(), k1) + if msg != nil { + return msg + } + } + return nil +} diff --git a/vendor/github.com/fernet/fernet-go/generate.json b/vendor/github.com/fernet/fernet-go/generate.json new file mode 100644 index 00000000..d1f3e083 --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/generate.json @@ -0,0 +1,9 @@ +[ + { + "token": "gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==", + "now": "1985-10-26T01:20:00-07:00", + "iv": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + "src": "hello", + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + } +] diff --git a/vendor/github.com/fernet/fernet-go/invalid.json b/vendor/github.com/fernet/fernet-go/invalid.json new file mode 100644 index 00000000..d80e7b4a --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/invalid.json @@ -0,0 +1,58 @@ +[ + { + "desc": "incorrect mac", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAl1-szkFVzXTuGb4hR8AKtwcaX1YdykQUFBQUFBQUFBQQ==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "too short", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPA==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "invalid base64", + "token": "%%%%%%%%%%%%%AECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAl1-szkFVzXTuGb4hR8AKtwcaX1YdykRtfsH-p1YsUD2Q==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "payload size not multiple of block size", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPOm73QeoCk9uGib28Xe5vz6oxq5nmxbx_v7mrfyudzUm", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "payload padding error", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0ODz4LEpdELGQAad7aNEHbf-JkLPIpuiYRLQ3RtXatOYREu2FWke6CnJNYIbkuKNqOhw==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "far-future TS (unacceptable clock skew)", + "token": "gAAAAAAdwStRAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAnja1xKYyhd-Y6mSkTOyTGJmw2Xc2a6kBd-iX9b_qXQcw==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "expired TTL", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAl1-szkFVzXTuGb4hR8AKtwcaX1YdykRtfsH-p1YsUD2Q==", + "now": "1985-10-26T01:21:31-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "incorrect IV (causes padding error)", + "token": "gAAAAAAdwJ6xBQECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAkLhFLHpGtDBRLRTZeUfWgHSv49TF2AUEZ1TIvcZjK1zQ==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + } +] diff --git a/vendor/github.com/fernet/fernet-go/key.go b/vendor/github.com/fernet/fernet-go/key.go new file mode 100644 index 00000000..595217ed --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/key.go @@ -0,0 +1,91 @@ +package fernet + +import ( + "crypto/rand" + "encoding/base64" + "encoding/hex" + "errors" + "io" +) + +var ( + errKeyLen = errors.New("fernet: key decodes to wrong size") + errNoKeys = errors.New("fernet: no keys provided") +) + +// Key represents a key. +type Key [32]byte + +func (k *Key) cryptBytes() []byte { + return k[len(k)/2:] +} + +func (k *Key) signBytes() []byte { + return k[:len(k)/2] +} + +// Generate initializes k with pseudorandom data from package crypto/rand. +func (k *Key) Generate() error { + _, err := io.ReadFull(rand.Reader, k[:]) + return err +} + +// Encode returns the URL-safe base64 encoding of k. +func (k *Key) Encode() string { + return encoding.EncodeToString(k[:]) +} + +// DecodeKey decodes a key from s and returns it. The key can be in +// hexadecimal, standard base64, or URL-safe base64. +func DecodeKey(s string) (*Key, error) { + var b []byte + var err error + if s == "" { + return nil, errors.New("empty key") + } + if len(s) == hex.EncodedLen(len(Key{})) { + b, err = hex.DecodeString(s) + } else { + b, err = base64.StdEncoding.DecodeString(s) + if err != nil { + b, err = base64.URLEncoding.DecodeString(s) + } + } + if err != nil { + return nil, err + } + if len(b) != len(Key{}) { + return nil, errKeyLen + } + k := new(Key) + copy(k[:], b) + return k, nil +} + +// DecodeKeys decodes each element of a using DecodeKey and returns the +// resulting keys. Requires at least one key. +func DecodeKeys(a ...string) ([]*Key, error) { + if len(a) == 0 { + return nil, errNoKeys + } + var err error + ks := make([]*Key, len(a)) + for i, s := range a { + ks[i], err = DecodeKey(s) + if err != nil { + return nil, err + } + } + return ks, nil +} + +// MustDecodeKeys is like DecodeKeys, but panics if an error occurs. +// It simplifies safe initialization of global variables holding +// keys. +func MustDecodeKeys(a ...string) []*Key { + k, err := DecodeKeys(a...) + if err != nil { + panic(err) + } + return k +} diff --git a/vendor/github.com/fernet/fernet-go/verify.json b/vendor/github.com/fernet/fernet-go/verify.json new file mode 100644 index 00000000..a4878794 --- /dev/null +++ b/vendor/github.com/fernet/fernet-go/verify.json @@ -0,0 +1,16 @@ +[ + { + "token": "gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "src": "hello", + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "token": "gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": -1, + "src": "hello", + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + } +]