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