...

Source file src/github.com/lestrrat-go/jwx/jwe/internal/aescbc/aescbc.go

Documentation: github.com/lestrrat-go/jwx/jwe/internal/aescbc

     1  package aescbc
     2  
     3  import (
     4  	"crypto/cipher"
     5  	"crypto/hmac"
     6  	"crypto/sha256"
     7  	"crypto/sha512"
     8  	"crypto/subtle"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"hash"
    12  
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  const (
    17  	NonceSize = 16
    18  )
    19  
    20  func pad(buf []byte, n int) []byte {
    21  	rem := n - len(buf)%n
    22  	if rem == 0 {
    23  		return buf
    24  	}
    25  
    26  	newbuf := make([]byte, len(buf)+rem)
    27  	copy(newbuf, buf)
    28  
    29  	for i := len(buf); i < len(newbuf); i++ {
    30  		newbuf[i] = byte(rem)
    31  	}
    32  	return newbuf
    33  }
    34  
    35  func unpad(buf []byte, n int) ([]byte, error) {
    36  	lbuf := len(buf)
    37  	rem := lbuf % n
    38  
    39  	// First, `buf` must be a multiple of `n`
    40  	if rem != 0 {
    41  		return nil, errors.Errorf("input buffer must be multiple of block size %d", n)
    42  	}
    43  
    44  	// Find the last byte, which is the encoded padding
    45  	// i.e. 0x1 == 1 byte worth of padding
    46  	last := buf[lbuf-1]
    47  
    48  	// This is the number of padding bytes that we expect
    49  	expected := int(last)
    50  
    51  	if expected == 0 || /* we _have_ to have padding here. therefore, 0x0 is not an option */
    52  		expected > n || /* we also must make sure that we don't go over the block size (n) */
    53  		expected > lbuf /* finally, it can't be more than the buffer itself. unlikely, but could happen */ {
    54  		return nil, fmt.Errorf(`invalid padding byte at the end of buffer`)
    55  	}
    56  
    57  	// start i = 1 because we have already established that expected == int(last) where
    58  	// last = buf[lbuf-1].
    59  	//
    60  	// we also don't check against lbuf-i in range, because we have established expected <= lbuf
    61  	for i := 1; i < expected; i++ {
    62  		if buf[lbuf-i] != last {
    63  			return nil, errors.New(`invalid padding`)
    64  		}
    65  	}
    66  
    67  	return buf[:lbuf-expected], nil
    68  }
    69  
    70  type Hmac struct {
    71  	blockCipher  cipher.Block
    72  	hash         func() hash.Hash
    73  	keysize      int
    74  	tagsize      int
    75  	integrityKey []byte
    76  }
    77  
    78  type BlockCipherFunc func([]byte) (cipher.Block, error)
    79  
    80  func New(key []byte, f BlockCipherFunc) (hmac *Hmac, err error) {
    81  	keysize := len(key) / 2
    82  	ikey := key[:keysize]
    83  	ekey := key[keysize:]
    84  
    85  	bc, ciphererr := f(ekey)
    86  	if ciphererr != nil {
    87  		err = errors.Wrap(ciphererr, `failed to execute block cipher function`)
    88  		return
    89  	}
    90  
    91  	var hfunc func() hash.Hash
    92  	switch keysize {
    93  	case 16:
    94  		hfunc = sha256.New
    95  	case 24:
    96  		hfunc = sha512.New384
    97  	case 32:
    98  		hfunc = sha512.New
    99  	default:
   100  		return nil, errors.Errorf("unsupported key size %d", keysize)
   101  	}
   102  
   103  	return &Hmac{
   104  		blockCipher:  bc,
   105  		hash:         hfunc,
   106  		integrityKey: ikey,
   107  		keysize:      keysize,
   108  		tagsize:      keysize, // NonceSize,
   109  		// While investigating GH #207, I stumbled upon another problem where
   110  		// the computed tags don't match on decrypt. After poking through the
   111  		// code using a bunch of debug statements, I've finally found out that
   112  		// tagsize = keysize makes the whole thing work.
   113  	}, nil
   114  }
   115  
   116  // NonceSize fulfills the crypto.AEAD interface
   117  func (c Hmac) NonceSize() int {
   118  	return NonceSize
   119  }
   120  
   121  // Overhead fulfills the crypto.AEAD interface
   122  func (c Hmac) Overhead() int {
   123  	return c.blockCipher.BlockSize() + c.tagsize
   124  }
   125  
   126  func (c Hmac) ComputeAuthTag(aad, nonce, ciphertext []byte) ([]byte, error) {
   127  	buf := make([]byte, len(aad)+len(nonce)+len(ciphertext)+8)
   128  	n := 0
   129  	n += copy(buf, aad)
   130  	n += copy(buf[n:], nonce)
   131  	n += copy(buf[n:], ciphertext)
   132  	binary.BigEndian.PutUint64(buf[n:], uint64(len(aad)*8))
   133  
   134  	h := hmac.New(c.hash, c.integrityKey)
   135  	if _, err := h.Write(buf); err != nil {
   136  		return nil, errors.Wrap(err, "failed to write ComputeAuthTag using Hmac")
   137  	}
   138  	s := h.Sum(nil)
   139  	return s[:c.tagsize], nil
   140  }
   141  
   142  func ensureSize(dst []byte, n int) []byte {
   143  	// if the dst buffer has enough length just copy the relevant parts to it.
   144  	// Otherwise create a new slice that's big enough, and operate on that
   145  	// Note: I think go-jose has a bug in that it checks for cap(), but not len().
   146  	ret := dst
   147  	if diff := n - len(dst); diff > 0 {
   148  		// dst is not big enough
   149  		ret = make([]byte, n)
   150  		copy(ret, dst)
   151  	}
   152  	return ret
   153  }
   154  
   155  // Seal fulfills the crypto.AEAD interface
   156  func (c Hmac) Seal(dst, nonce, plaintext, data []byte) []byte {
   157  	ctlen := len(plaintext)
   158  	ciphertext := make([]byte, ctlen+c.Overhead())[:ctlen]
   159  	copy(ciphertext, plaintext)
   160  	ciphertext = pad(ciphertext, c.blockCipher.BlockSize())
   161  
   162  	cbc := cipher.NewCBCEncrypter(c.blockCipher, nonce)
   163  	cbc.CryptBlocks(ciphertext, ciphertext)
   164  
   165  	authtag, err := c.ComputeAuthTag(data, nonce, ciphertext)
   166  	if err != nil {
   167  		// Hmac implements cipher.AEAD interface. Seal can't return error.
   168  		// But currently it never reach here because of Hmac.ComputeAuthTag doesn't return error.
   169  		panic(fmt.Errorf("failed to seal on hmac: %v", err))
   170  	}
   171  
   172  	retlen := len(dst) + len(ciphertext) + len(authtag)
   173  
   174  	ret := ensureSize(dst, retlen)
   175  	out := ret[len(dst):]
   176  	n := copy(out, ciphertext)
   177  	copy(out[n:], authtag)
   178  
   179  	return ret
   180  }
   181  
   182  // Open fulfills the crypto.AEAD interface
   183  func (c Hmac) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
   184  	if len(ciphertext) < c.keysize {
   185  		return nil, errors.New("invalid ciphertext (too short)")
   186  	}
   187  
   188  	tagOffset := len(ciphertext) - c.tagsize
   189  	if tagOffset%c.blockCipher.BlockSize() != 0 {
   190  		return nil, fmt.Errorf(
   191  			"invalid ciphertext (invalid length: %d %% %d != 0)",
   192  			tagOffset,
   193  			c.blockCipher.BlockSize(),
   194  		)
   195  	}
   196  	tag := ciphertext[tagOffset:]
   197  	ciphertext = ciphertext[:tagOffset]
   198  
   199  	expectedTag, err := c.ComputeAuthTag(data, nonce, ciphertext[:tagOffset])
   200  	if err != nil {
   201  		return nil, errors.Wrap(err, `failed to compute auth tag`)
   202  	}
   203  
   204  	if subtle.ConstantTimeCompare(expectedTag, tag) != 1 {
   205  		return nil, errors.New("invalid ciphertext (tag mismatch)")
   206  	}
   207  
   208  	cbc := cipher.NewCBCDecrypter(c.blockCipher, nonce)
   209  	buf := make([]byte, tagOffset)
   210  	cbc.CryptBlocks(buf, ciphertext)
   211  
   212  	plaintext, err := unpad(buf, c.blockCipher.BlockSize())
   213  	if err != nil {
   214  		return nil, errors.Wrap(err, `failed to generate plaintext from decrypted blocks`)
   215  	}
   216  	ret := ensureSize(dst, len(plaintext))
   217  	out := ret[len(dst):]
   218  	copy(out, plaintext)
   219  	return ret, nil
   220  }
   221  

View as plain text