| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- // 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
- }
|