api: implement fernet encryption of pagination tokens
This commit is contained in:
parent
b8c534cd0d
commit
d19a4348df
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@ -30,6 +30,10 @@
|
|||||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||||
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
|
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/fernet/fernet-go",
|
||||||
|
"Rev": "1b2437bc582b3cfbb341ee5a29f8ef5b42912ff2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||||
"Comment": "v1.2-125-gd512f20",
|
"Comment": "v1.2-125-gd512f20",
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"github.com/coreos/clair/config"
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/utils"
|
"github.com/coreos/clair/utils"
|
||||||
)
|
)
|
||||||
@ -59,4 +60,5 @@ func HTTPHandler(handler Handler, ctx *RouteContext) httprouter.Handle {
|
|||||||
|
|
||||||
type RouteContext struct {
|
type RouteContext struct {
|
||||||
Store database.Datastore
|
Store database.Datastore
|
||||||
|
Config *config.APIConfig
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,20 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/clair/database"
|
"github.com/coreos/clair/database"
|
||||||
"github.com/coreos/clair/utils/types"
|
"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 {
|
type Error struct {
|
||||||
Message string `json:"Layer`
|
Message string `json:"Layer`
|
||||||
}
|
}
|
||||||
@ -187,10 +194,9 @@ type Notification struct {
|
|||||||
NextPage string `json:"NextPage,omitempty"`
|
NextPage string `json:"NextPage,omitempty"`
|
||||||
Old *VulnerabilityWithLayers `json:"Old,omitempty"`
|
Old *VulnerabilityWithLayers `json:"Old,omitempty"`
|
||||||
New *VulnerabilityWithLayers `json:"New,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
|
var oldVuln *VulnerabilityWithLayers
|
||||||
if dbNotification.OldVulnerability != nil {
|
if dbNotification.OldVulnerability != nil {
|
||||||
v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability)
|
v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability)
|
||||||
@ -205,7 +211,7 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica
|
|||||||
|
|
||||||
var nextPageStr string
|
var nextPageStr string
|
||||||
if nextPage != database.NoVulnerabilityNotificationPage {
|
if nextPage != database.NoVulnerabilityNotificationPage {
|
||||||
nextPageStr = DBPageNumberToString(nextPage)
|
nextPageStr = pageNumberToToken(nextPage, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
var created, notified, deleted string
|
var created, notified, deleted string
|
||||||
@ -227,7 +233,7 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica
|
|||||||
Notified: notified,
|
Notified: notified,
|
||||||
Deleted: deleted,
|
Deleted: deleted,
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Page: DBPageNumberToString(page),
|
Page: pageNumberToToken(page, key),
|
||||||
NextPage: nextPageStr,
|
NextPage: nextPageStr,
|
||||||
Old: oldVuln,
|
Old: oldVuln,
|
||||||
New: newVuln,
|
New: newVuln,
|
||||||
@ -279,14 +285,30 @@ type FeatureEnvelope struct {
|
|||||||
Error *Error `json:"Error,omitempty"`
|
Error *Error `json:"Error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func pageStringToDBPageNumber(pageStr string) (database.VulnerabilityNotificationPageNumber, error) {
|
func tokenToPageNumber(token, key string) (database.VulnerabilityNotificationPageNumber, error) {
|
||||||
// TODO(jzelinskie): turn pagination into an encrypted token
|
k, _ := fernet.DecodeKey(key)
|
||||||
var old, new int
|
msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k})
|
||||||
_, err := fmt.Sscanf(pageStr, "%d-%d", &old, &new)
|
if msg == nil {
|
||||||
return database.VulnerabilityNotificationPageNumber{old, new}, err
|
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 {
|
func pageNumberToToken(page database.VulnerabilityNotificationPageNumber, key string) string {
|
||||||
// TODO(jzelinskie): turn pagination into an encrypted token
|
var buf bytes.Buffer
|
||||||
return fmt.Sprintf("%d-%d", page.OldVulnerability, page.NewVulnerability)
|
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)
|
||||||
}
|
}
|
||||||
|
@ -321,7 +321,7 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
|
|||||||
page := database.VulnerabilityNotificationFirstPage
|
page := database.VulnerabilityNotificationFirstPage
|
||||||
pageStrs, pageExists := query["page"]
|
pageStrs, pageExists := query["page"]
|
||||||
if pageExists {
|
if pageExists {
|
||||||
page, err = pageStringToDBPageNumber(pageStrs[0])
|
page, err = tokenToPageNumber(pageStrs[0], ctx.Config.PaginationKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeResponse(w, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
|
writeResponse(w, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}})
|
||||||
return getNotificationRoute, http.StatusBadRequest
|
return getNotificationRoute, http.StatusBadRequest
|
||||||
@ -337,7 +337,7 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params
|
|||||||
return getNotificationRoute, http.StatusInternalServerError
|
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})
|
writeResponse(w, http.StatusOK, NotificationEnvelope{Notification: ¬ification})
|
||||||
return getNotificationRoute, http.StatusOK
|
return getNotificationRoute, http.StatusOK
|
||||||
|
4
clair.go
4
clair.go
@ -53,9 +53,9 @@ func Boot(config *config.Config) {
|
|||||||
|
|
||||||
// Start API
|
// Start API
|
||||||
st.Begin()
|
st.Begin()
|
||||||
go api.Run(config.API, &context.RouteContext{db}, st)
|
go api.Run(config.API, &context.RouteContext{db, config.API}, st)
|
||||||
st.Begin()
|
st.Begin()
|
||||||
go api.RunHealth(config.API, &context.RouteContext{db}, st)
|
go api.RunHealth(config.API, &context.RouteContext{db, config.API}, st)
|
||||||
|
|
||||||
// Start updater
|
// Start updater
|
||||||
st.Begin()
|
st.Begin()
|
||||||
|
@ -13,6 +13,8 @@ api:
|
|||||||
healthport: 6061
|
healthport: 6061
|
||||||
# Maximum time that API requests may take before they time-out with a HTTP 503 error.
|
# Maximum time that API requests may take before they time-out with a HTTP 503 error.
|
||||||
timeout: 900s
|
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.
|
# Paths to certificates to secure the main API with TLS and client certificate auth.
|
||||||
cafile:
|
cafile:
|
||||||
keyfile:
|
keyfile:
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fernet/fernet-go"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ type APIConfig struct {
|
|||||||
Port int
|
Port int
|
||||||
HealthPort int
|
HealthPort int
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
PaginationKey string
|
||||||
CertFile, KeyFile, CAFile string
|
CertFile, KeyFile, CAFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,5 +98,22 @@ func Load(path string) (config *Config, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = yaml.Unmarshal(d, config)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
20
vendor/github.com/fernet/fernet-go/License
generated
vendored
Normal file
20
vendor/github.com/fernet/fernet-go/License
generated
vendored
Normal file
@ -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.
|
22
vendor/github.com/fernet/fernet-go/Readme
generated
vendored
Normal file
22
vendor/github.com/fernet/fernet-go/Readme
generated
vendored
Normal file
@ -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.
|
19
vendor/github.com/fernet/fernet-go/cmd/fernet-keygen/main.go
generated
vendored
Normal file
19
vendor/github.com/fernet/fernet-go/cmd/fernet-keygen/main.go
generated
vendored
Normal file
@ -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())
|
||||||
|
}
|
45
vendor/github.com/fernet/fernet-go/cmd/fernet-sign/main.go
generated
vendored
Normal file
45
vendor/github.com/fernet/fernet-go/cmd/fernet-sign/main.go
generated
vendored
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
168
vendor/github.com/fernet/fernet-go/fernet.go
generated
vendored
Normal file
168
vendor/github.com/fernet/fernet-go/fernet.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
9
vendor/github.com/fernet/fernet-go/generate.json
generated
vendored
Normal file
9
vendor/github.com/fernet/fernet-go/generate.json
generated
vendored
Normal file
@ -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="
|
||||||
|
}
|
||||||
|
]
|
58
vendor/github.com/fernet/fernet-go/invalid.json
generated
vendored
Normal file
58
vendor/github.com/fernet/fernet-go/invalid.json
generated
vendored
Normal file
@ -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="
|
||||||
|
}
|
||||||
|
]
|
91
vendor/github.com/fernet/fernet-go/key.go
generated
vendored
Normal file
91
vendor/github.com/fernet/fernet-go/key.go
generated
vendored
Normal file
@ -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
|
||||||
|
}
|
16
vendor/github.com/fernet/fernet-go/verify.json
generated
vendored
Normal file
16
vendor/github.com/fernet/fernet-go/verify.json
generated
vendored
Normal file
@ -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="
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user