github.com/emmansun/gmsm@v0.29.1/cipher/ccm.go (about)

     1  // Package cipher provides several extra chipher modes.
     2  package cipher
     3  
     4  import (
     5  	goCipher "crypto/cipher"
     6  	goSubtle "crypto/subtle"
     7  	"encoding/binary"
     8  	"math"
     9  
    10  	"errors"
    11  
    12  	"github.com/emmansun/gmsm/internal/alias"
    13  	"github.com/emmansun/gmsm/internal/subtle"
    14  )
    15  
    16  const (
    17  	ccmBlockSize         = 16
    18  	ccmTagSize           = 16
    19  	ccmMinimumTagSize    = 4
    20  	ccmStandardNonceSize = 12
    21  )
    22  
    23  // ccmAble is an interface implemented by ciphers that have a specific optimized
    24  // implementation of CCM.
    25  type ccmAble interface {
    26  	NewCCM(nonceSize, tagSize int) (goCipher.AEAD, error)
    27  }
    28  
    29  type ccm struct {
    30  	cipher    goCipher.Block
    31  	nonceSize int
    32  	tagSize   int
    33  }
    34  
    35  func (c *ccm) NonceSize() int {
    36  	return c.nonceSize
    37  }
    38  
    39  func (c *ccm) Overhead() int {
    40  	return c.tagSize
    41  }
    42  
    43  func (c *ccm) MaxLength() int {
    44  	return maxlen(15-c.NonceSize(), c.Overhead())
    45  }
    46  
    47  func maxlen(L, tagsize int) int {
    48  	max := (uint64(1) << (8 * L)) - 1
    49  	if m64 := uint64(math.MaxInt64) - uint64(tagsize); L > 8 || max > m64 {
    50  		max = m64 // The maximum lentgh on a 64bit arch
    51  	}
    52  	if max != uint64(int(max)) {
    53  		return math.MaxInt32 - tagsize // We have only 32bit int's
    54  	}
    55  	return int(max)
    56  }
    57  
    58  // NewCCM returns the given 128-bit, block cipher wrapped in CCM
    59  // with the standard nonce length.
    60  func NewCCM(cipher goCipher.Block) (goCipher.AEAD, error) {
    61  	return NewCCMWithNonceAndTagSize(cipher, ccmStandardNonceSize, ccmTagSize)
    62  }
    63  
    64  // NewCCMWithNonceSize returns the given 128-bit, block cipher wrapped in CCM,
    65  // which accepts nonces of the given length. The length must not
    66  // be zero.
    67  func NewCCMWithNonceSize(cipher goCipher.Block, size int) (goCipher.AEAD, error) {
    68  	return NewCCMWithNonceAndTagSize(cipher, size, ccmTagSize)
    69  }
    70  
    71  // NewCCMWithTagSize returns the given 128-bit, block cipher wrapped in CCM,
    72  // which generates tags with the given length.
    73  //
    74  // Tag sizes between 8 and 16 bytes are allowed.
    75  //
    76  func NewCCMWithTagSize(cipher goCipher.Block, tagSize int) (goCipher.AEAD, error) {
    77  	return NewCCMWithNonceAndTagSize(cipher, ccmStandardNonceSize, tagSize)
    78  }
    79  
    80  // https://tools.ietf.org/html/rfc3610
    81  func NewCCMWithNonceAndTagSize(cipher goCipher.Block, nonceSize, tagSize int) (goCipher.AEAD, error) {
    82  	if tagSize < ccmMinimumTagSize || tagSize > ccmBlockSize || tagSize&1 != 0 {
    83  		return nil, errors.New("cipher: incorrect tag size given to CCM")
    84  	}
    85  
    86  	if nonceSize <= 0 {
    87  		return nil, errors.New("cipher: the nonce can't have zero length, or the security of the key will be immediately compromised")
    88  	}
    89  
    90  	lenSize := 15 - nonceSize
    91  	if lenSize < 2 || lenSize > 8 {
    92  		return nil, errors.New("cipher: invalid ccm nounce size, should be in [7,13]")
    93  	}
    94  
    95  	if cipher, ok := cipher.(ccmAble); ok {
    96  		return cipher.NewCCM(nonceSize, tagSize)
    97  	}
    98  
    99  	if cipher.BlockSize() != ccmBlockSize {
   100  		return nil, errors.New("cipher: NewCCM requires 128-bit block cipher")
   101  	}
   102  
   103  	c := &ccm{cipher: cipher, nonceSize: nonceSize, tagSize: tagSize}
   104  
   105  	return c, nil
   106  }
   107  
   108  // https://tools.ietf.org/html/rfc3610
   109  func (c *ccm) deriveCounter(counter *[ccmBlockSize]byte, nonce []byte) {
   110  	counter[0] = byte(14 - c.nonceSize)
   111  	copy(counter[1:], nonce)
   112  }
   113  
   114  func (c *ccm) cmac(out, data []byte) {
   115  	for len(data) >= ccmBlockSize {
   116  		subtle.XORBytes(out, out, data)
   117  		c.cipher.Encrypt(out, out)
   118  		data = data[ccmBlockSize:]
   119  	}
   120  	if len(data) > 0 {
   121  		var block [ccmBlockSize]byte
   122  		copy(block[:], data)
   123  		subtle.XORBytes(out, out, data)
   124  		c.cipher.Encrypt(out, out)
   125  	}
   126  }
   127  
   128  // https://tools.ietf.org/html/rfc3610 2.2. Authentication
   129  func (c *ccm) auth(nonce, plaintext, additionalData []byte, tagMask *[ccmBlockSize]byte) []byte {
   130  	var out [ccmTagSize]byte
   131  	if len(additionalData) > 0 {
   132  		out[0] = 1 << 6 // 64*Adata
   133  	}
   134  	out[0] |= byte(c.tagSize-2) << 2 // M' = ((tagSize - 2) / 2)*8
   135  	out[0] |= byte(14 - c.nonceSize) // L'
   136  	binary.BigEndian.PutUint64(out[ccmBlockSize-8:], uint64(len(plaintext)))
   137  	copy(out[1:], nonce)
   138  	// B0
   139  	c.cipher.Encrypt(out[:], out[:])
   140  
   141  	var block [ccmBlockSize]byte
   142  	if n := uint64(len(additionalData)); n > 0 {
   143  		// First adata block includes adata length
   144  		i := 2
   145  		if n <= 0xfeff { // l(a) < (2^16 - 2^8)
   146  			binary.BigEndian.PutUint16(block[:i], uint16(n))
   147  		} else {
   148  			block[0] = 0xff
   149  			// If (2^16 - 2^8) <= l(a) < 2^32, then the length field is encoded as
   150  			// six octets consisting of the octets 0xff, 0xfe, and four octets
   151  			// encoding l(a) in most-significant-byte-first order.
   152  			if n < uint64(1<<32) {
   153  				block[1] = 0xfe
   154  				i = 2 + 4
   155  				binary.BigEndian.PutUint32(block[2:i], uint32(n))
   156  			} else {
   157  				block[1] = 0xff
   158  				// If 2^32 <= l(a) < 2^64, then the length field is encoded as ten
   159  				// octets consisting of the octets 0xff, 0xff, and eight octets encoding
   160  				// l(a) in most-significant-byte-first order.
   161  				i = 2 + 8
   162  				binary.BigEndian.PutUint64(block[2:i], uint64(n))
   163  			}
   164  		}
   165  		i = copy(block[i:], additionalData) // first block start with additional data length
   166  		c.cmac(out[:], block[:])
   167  		c.cmac(out[:], additionalData[i:])
   168  	}
   169  	if len(plaintext) > 0 {
   170  		c.cmac(out[:], plaintext)
   171  	}
   172  	subtle.XORBytes(out[:], out[:], tagMask[:])
   173  	return out[:c.tagSize]
   174  }
   175  
   176  func (c *ccm) Seal(dst, nonce, plaintext, data []byte) []byte {
   177  	if len(nonce) != c.nonceSize {
   178  		panic("cipher: incorrect nonce length given to CCM")
   179  	}
   180  	if uint64(len(plaintext)) > uint64(c.MaxLength()) {
   181  		panic("cipher: message too large for CCM")
   182  	}
   183  	ret, out := alias.SliceForAppend(dst, len(plaintext)+c.tagSize)
   184  	if alias.InexactOverlap(out, plaintext) {
   185  		panic("cipher: invalid buffer overlap")
   186  	}
   187  
   188  	var counter, tagMask [ccmBlockSize]byte
   189  	c.deriveCounter(&counter, nonce)
   190  	c.cipher.Encrypt(tagMask[:], counter[:])
   191  
   192  	counter[len(counter)-1] |= 1
   193  	ctr := goCipher.NewCTR(c.cipher, counter[:])
   194  	ctr.XORKeyStream(out, plaintext)
   195  
   196  	tag := c.auth(nonce, plaintext, data, &tagMask)
   197  	copy(out[len(plaintext):], tag)
   198  
   199  	return ret
   200  }
   201  
   202  var errOpen = errors.New("cipher: message authentication failed")
   203  
   204  func (c *ccm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
   205  	if len(nonce) != c.nonceSize {
   206  		panic("cipher: incorrect nonce length given to CCM")
   207  	}
   208  	// Sanity check to prevent the authentication from always succeeding if an implementation
   209  	// leaves tagSize uninitialized, for example.
   210  	if c.tagSize < ccmMinimumTagSize {
   211  		panic("cipher: incorrect CCM tag size")
   212  	}
   213  
   214  	if len(ciphertext) < c.tagSize {
   215  		return nil, errOpen
   216  	}
   217  
   218  	if len(ciphertext) > c.MaxLength()+c.Overhead() {
   219  		return nil, errOpen
   220  	}
   221  
   222  	tag := ciphertext[len(ciphertext)-c.tagSize:]
   223  	ciphertext = ciphertext[:len(ciphertext)-c.tagSize]
   224  
   225  	var counter, tagMask [ccmBlockSize]byte
   226  	c.deriveCounter(&counter, nonce)
   227  	c.cipher.Encrypt(tagMask[:], counter[:])
   228  
   229  	ret, out := alias.SliceForAppend(dst, len(ciphertext))
   230  	if alias.InexactOverlap(out, ciphertext) {
   231  		panic("cipher: invalid buffer overlap")
   232  	}
   233  
   234  	counter[len(counter)-1] |= 1
   235  	ctr := goCipher.NewCTR(c.cipher, counter[:])
   236  	ctr.XORKeyStream(out, ciphertext)
   237  	expectedTag := c.auth(nonce, out, data, &tagMask)
   238  	if goSubtle.ConstantTimeCompare(expectedTag, tag) != 1 {
   239  		// The AESNI code decrypts and authenticates concurrently, and
   240  		// so overwrites dst in the event of a tag mismatch. That
   241  		// behavior is mimicked here in order to be consistent across
   242  		// platforms.
   243  		for i := range out {
   244  			out[i] = 0
   245  		}
   246  		return nil, errOpen
   247  	}
   248  	return ret, nil
   249  }