123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- /*-
- * Copyright 2014 Square Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package josecipher
- import (
- "bytes"
- "crypto/cipher"
- "crypto/hmac"
- "crypto/sha256"
- "crypto/sha512"
- "crypto/subtle"
- "encoding/binary"
- "errors"
- "hash"
- )
- const (
- nonceBytes = 16
- )
- // NewCBCHMAC instantiates a new AEAD based on CBC+HMAC.
- func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (cipher.AEAD, error) {
- keySize := len(key) / 2
- integrityKey := key[:keySize]
- encryptionKey := key[keySize:]
- blockCipher, err := newBlockCipher(encryptionKey)
- if err != nil {
- return nil, err
- }
- var hash func() hash.Hash
- switch keySize {
- case 16:
- hash = sha256.New
- case 24:
- hash = sha512.New384
- case 32:
- hash = sha512.New
- }
- return &cbcAEAD{
- hash: hash,
- blockCipher: blockCipher,
- authtagBytes: keySize,
- integrityKey: integrityKey,
- }, nil
- }
- // An AEAD based on CBC+HMAC
- type cbcAEAD struct {
- hash func() hash.Hash
- authtagBytes int
- integrityKey []byte
- blockCipher cipher.Block
- }
- func (ctx *cbcAEAD) NonceSize() int {
- return nonceBytes
- }
- func (ctx *cbcAEAD) Overhead() int {
- // Maximum overhead is block size (for padding) plus auth tag length, where
- // the length of the auth tag is equivalent to the key size.
- return ctx.blockCipher.BlockSize() + ctx.authtagBytes
- }
- // Seal encrypts and authenticates the plaintext.
- func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
- // Output buffer -- must take care not to mangle plaintext input.
- ciphertext := make([]byte, uint64(len(plaintext))+uint64(ctx.Overhead()))[:len(plaintext)]
- copy(ciphertext, plaintext)
- ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize())
- cbc := cipher.NewCBCEncrypter(ctx.blockCipher, nonce)
- cbc.CryptBlocks(ciphertext, ciphertext)
- authtag := ctx.computeAuthTag(data, nonce, ciphertext)
- ret, out := resize(dst, uint64(len(dst))+uint64(len(ciphertext))+uint64(len(authtag)))
- copy(out, ciphertext)
- copy(out[len(ciphertext):], authtag)
- return ret
- }
- // Open decrypts and authenticates the ciphertext.
- func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
- if len(ciphertext) < ctx.authtagBytes {
- return nil, errors.New("square/go-jose: invalid ciphertext (too short)")
- }
- offset := len(ciphertext) - ctx.authtagBytes
- expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset])
- match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:])
- if match != 1 {
- return nil, errors.New("square/go-jose: invalid ciphertext (auth tag mismatch)")
- }
- cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce)
- // Make copy of ciphertext buffer, don't want to modify in place
- buffer := append([]byte{}, []byte(ciphertext[:offset])...)
- if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
- return nil, errors.New("square/go-jose: invalid ciphertext (invalid length)")
- }
- cbc.CryptBlocks(buffer, buffer)
- // Remove padding
- plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize())
- if err != nil {
- return nil, err
- }
- ret, out := resize(dst, uint64(len(dst))+uint64(len(plaintext)))
- copy(out, plaintext)
- return ret, nil
- }
- // Compute an authentication tag
- func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
- buffer := make([]byte, uint64(len(aad))+uint64(len(nonce))+uint64(len(ciphertext))+8)
- n := 0
- n += copy(buffer, aad)
- n += copy(buffer[n:], nonce)
- n += copy(buffer[n:], ciphertext)
- binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad))*8)
- // According to documentation, Write() on hash.Hash never fails.
- hmac := hmac.New(ctx.hash, ctx.integrityKey)
- _, _ = hmac.Write(buffer)
- return hmac.Sum(nil)[:ctx.authtagBytes]
- }
- // resize ensures the the given slice has a capacity of at least n bytes.
- // If the capacity of the slice is less than n, a new slice is allocated
- // and the existing data will be copied.
- func resize(in []byte, n uint64) (head, tail []byte) {
- if uint64(cap(in)) >= n {
- head = in[:n]
- } else {
- head = make([]byte, n)
- copy(head, in)
- }
- tail = head[len(in):]
- return
- }
- // Apply padding
- func padBuffer(buffer []byte, blockSize int) []byte {
- missing := blockSize - (len(buffer) % blockSize)
- ret, out := resize(buffer, uint64(len(buffer))+uint64(missing))
- padding := bytes.Repeat([]byte{byte(missing)}, missing)
- copy(out, padding)
- return ret
- }
- // Remove padding
- func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
- if len(buffer)%blockSize != 0 {
- return nil, errors.New("square/go-jose: invalid padding")
- }
- last := buffer[len(buffer)-1]
- count := int(last)
- if count == 0 || count > blockSize || count > len(buffer) {
- return nil, errors.New("square/go-jose: invalid padding")
- }
- padding := bytes.Repeat([]byte{last}, count)
- if !bytes.HasSuffix(buffer, padding) {
- return nil, errors.New("square/go-jose: invalid padding")
- }
- return buffer[:len(buffer)-count], nil
- }
|