From 2a0a5eed19628889318549def8dc7a6158ee7676 Mon Sep 17 00:00:00 2001
From: Thomas Pornin <pornin@bolet.org>
Date: Sat, 28 Jul 2018 22:35:08 +0200
Subject: [PATCH] Added CCM and CCM_8 cipher suites.

---
 Crypto/BlockCipherCore.cs  |  80 +++++++++++++++++--
 Crypto/IBlockCipher.cs     |  54 +++++++++++++
 SSLTLS/RecordDecryptCCM.cs | 135 ++++++++++++++++++++++++++++++++
 SSLTLS/RecordEncryptCCM.cs | 153 +++++++++++++++++++++++++++++++++++++
 SSLTLS/SSL.cs              |  58 ++++++++++++++
 SSLTLS/SSLEngine.cs        |  68 +++++++++++++++++
 conf/bearssl.json          |   8 ++
 7 files changed, 550 insertions(+), 6 deletions(-)
 create mode 100644 SSLTLS/RecordDecryptCCM.cs
 create mode 100644 SSLTLS/RecordEncryptCCM.cs

diff --git a/Crypto/BlockCipherCore.cs b/Crypto/BlockCipherCore.cs
index 2f1b761..ca5cc08 100644
--- a/Crypto/BlockCipherCore.cs
+++ b/Crypto/BlockCipherCore.cs
@@ -43,13 +43,17 @@ namespace Crypto {
  *    void CBCDecrypt(byte[] iv, byte[] data, int off, int len)
  *    uint CTRRun(byte[] iv, uint cc, byte[] data)
  *    uint CTRRun(byte[] iv, uint cc, byte[] data, int off, int len)
+ *    void CTRCBCRun(byte[] ctr, byte[] cbcmac, bool encrypt, byte[] data)
+ *    void CTRCBCRun(byte[] ctr, byte[] cbcmac, bool encrypt,
+ *                   byte[] data, int off, int len)
  * Note that CBCEncrypt(byte[],byte[]) (respectively
- * CBCDecrypt(byte[],byte[]) and CTRRun(byte[],uint,byte[])) simply
- * calls CBCEncrypt(byte[],byte[],int,int) (respectively
- * CBCDecrypt(byte[],byte[],int,int) and
- * CTRRun(byte[],uint,byte[],int,int)) so implementations who wish to
- * override these methods may content themselves with overriding the two
- * methods with the "off" and "len" extra parameters.
+ * CBCDecrypt(byte[],byte[]), CTRRun(byte[],uint,byte[]) and
+ * CTRCBCRun(byte[],byte[],bool,byte[])) simply calls
+ * CBCEncrypt(byte[],byte[],int,int) (respectively
+ * CBCDecrypt(byte[],byte[],int,int), CTRRun(byte[],uint,byte[],int,int)
+ * and CTRCBCRun(byte[],byte[],bool,byte[],int,int)) so implementations
+ * who wish to override these methods may content themselves with
+ * overriding the four methods with the "off" and "len" extra parameters.
  */
 
 public abstract class BlockCipherCore : IBlockCipher {
@@ -215,6 +219,70 @@ public abstract class BlockCipherCore : IBlockCipher {
 		return cc;
 	}
 
+	/*
+	 * This method is implemented by calling
+	 * CTRCBCRun(byte[],byte[],bool,byte[],int,int).
+	 */
+	public virtual void CTRCBCRun(byte[] ctr, byte[] cbcmac,
+		bool encrypt, byte[] data)
+	{
+		CTRCBCRun(ctr, cbcmac, encrypt, data, 0, data.Length);
+	}
+
+	/* see IBlockCipher */
+	public virtual void CTRCBCRun(byte[] ctr, byte[] cbcmac,
+		bool encrypt, byte[] data, int off, int len)
+	{
+		if (!encrypt) {
+			CBCMac(cbcmac, data, off, len);
+		}
+		DoCTRFull(ctr, data, off, len);
+		if (encrypt) {
+			CBCMac(cbcmac, data, off, len);
+		}
+	}
+
+	void DoCTRFull(byte[] ctr, byte[] data, int off, int len)
+	{
+		int blen = BlockSize;
+		if (ctr.Length != blen) {
+			throw new CryptoException("wrong counter length");
+		}
+		while (len > 0) {
+			Array.Copy(ctr, 0, tmp, 0, blen);
+			uint cc = 1;
+			for (int i = blen - 1; i >= 0; i --) {
+				uint x = ctr[i] + cc;
+				ctr[i] = (byte)x;
+				cc = x >> 8;
+			}
+			BlockEncrypt(tmp, 0);
+			int clen = Math.Min(blen, len);
+			for (int i = 0; i < clen; i ++) {
+				data[off + i] ^= tmp[i];
+			}
+			off += clen;
+			len -= clen;
+		}
+	}
+
+	/* see IBlockCipher */
+	public void CBCMac(byte[] cbcmac, byte[] data, int off, int len)
+	{
+		int blen = BlockSize;
+		if (cbcmac.Length != blen) {
+			throw new CryptoException("wrong MAC length");
+		}
+		while (len > 0) {
+			for (int i = 0; i < blen; i ++) {
+				cbcmac[i] ^= data[off + i];
+			}
+			BlockEncrypt(cbcmac, 0);
+			off += blen;
+			len -= blen;
+		}
+	}
+
 	/* see IBlockCipher */
 	public abstract IBlockCipher Dup();
 }
diff --git a/Crypto/IBlockCipher.cs b/Crypto/IBlockCipher.cs
index 6a51b99..0a9f469 100644
--- a/Crypto/IBlockCipher.cs
+++ b/Crypto/IBlockCipher.cs
@@ -138,6 +138,60 @@ public interface IBlockCipher {
 	 */
 	uint CTRRun(byte[] iv, uint cc, byte[] data, int off, int len);
 
+	/*
+	 * Do combined CTR encryption/decryption and CBC-MAC. The CTR
+	 * mode uses full-block increments (counter value is the
+	 * big-endian interpretation of the complete block); the ctr[]
+	 * array contains the initial value for the counter (used to
+	 * encrypt or decrypt the full block) and it is updated by
+	 * this method as blocks are processed.
+	 *
+	 * The cbcmac[] array has full block width and contains the
+	 * running value for CBC-MAC, computed over the _encrypted_ data.
+	 *
+	 * The flag 'encrypt' is true when encrypting, false when
+	 * decrypting. Note that CTR encryption and decryption are
+	 * identical; thus, the only effect of this flag is to decide
+	 * whether CBC-MAC should be applied on the blocks before or
+	 * after CTR encryption/decryption.
+	 *
+	 * The data is provided in the data[] buffer, and is
+	 * encrypted/decrypted in place. Its length MUST be a multiple
+	 * of the block size.
+	 */
+	void CTRCBCRun(byte[] ctr, byte[] cbcmac, bool encrypt, byte[] data);
+
+	/*
+	 * Do combined CTR encryption/decryption and CBC-MAC. The CTR
+	 * mode uses full-block increments (counter value is the
+	 * big-endian interpretation of the complete block); the ctr[]
+	 * array contains the initial value for the counter (used to
+	 * encrypt or decrypt the full block) and it is updated by
+	 * this method as blocks are processed.
+	 *
+	 * The cbcmac[] array has full block width and contains the
+	 * running value for CBC-MAC, computed over the _encrypted_ data.
+	 *
+	 * The flag 'encrypt' is true when encrypting, false when
+	 * decrypting. Note that CTR encryption and decryption are
+	 * identical; thus, the only effect of this flag is to decide
+	 * whether CBC-MAC should be applied on the blocks before or
+	 * after CTR encryption/decryption.
+	 *
+	 * The data is provided in the data[] buffer, and is
+	 * encrypted/decrypted in place. Its length MUST be a multiple
+	 * of the block size.
+	 */
+	void CTRCBCRun(byte[] ctr, byte[] cbcmac, bool encrypt,
+		byte[] data, int off, int len);
+
+	/*
+	 * Perform CBC-MAC: the cbcmac[] block is updated with the
+	 * CBC-MAC of the data. Data length must be a multiple of the
+	 * block size.
+	 */
+	void CBCMac(byte[] cbcmac, byte[] data, int off, int len);
+
 	/*
 	 * Duplicate this engine. This creates a new, independent
 	 * instance that implements the same function, and starts with
diff --git a/SSLTLS/RecordDecryptCCM.cs b/SSLTLS/RecordDecryptCCM.cs
new file mode 100644
index 0000000..4a60e8d
--- /dev/null
+++ b/SSLTLS/RecordDecryptCCM.cs
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordDecryptCCM : RecordDecrypt {
+
+	IBlockCipher bc;
+	byte[] iv;
+	ulong seq;
+	byte[] tmp, tag, ctr, cbcmac;
+	bool ccm8;
+
+	internal RecordDecryptCCM(IBlockCipher bc, byte[] iv, bool ccm8)
+	{
+		this.bc = bc;
+		this.iv = new byte[12];
+		Array.Copy(iv, 0, this.iv, 0, 4);
+		seq = 0;
+		tag = new byte[16];
+		tmp = new byte[32];
+		ctr = new byte[16];
+		cbcmac = new byte[16];
+		this.ccm8 = ccm8;
+	}
+
+	internal override bool CheckLength(int len)
+	{
+		int tagLen = ccm8 ? 8 : 16;
+		return len >= (8 + tagLen) && len <= (16384 + 8 + tagLen);
+	}
+
+	internal override bool Decrypt(int recordType, int version,
+		byte[] data, ref int off, ref int len)
+	{
+		Array.Copy(data, off, iv, 4, 8);
+		off += 8;
+		len -= ccm8 ? 16 : 24;
+
+		/*
+		 * Assemble block B0 and AAD.
+		 */
+		tmp[0] = (byte)(0x40 | ((ccm8 ? 6 : 14) << 2) | 2);
+		Array.Copy(iv, 0, tmp, 1, 12);
+		tmp[13] = 0;
+		IO.Enc16be(len, tmp, 14);
+
+		tmp[16] = 0;
+		tmp[17] = 13;
+		IO.Enc64be(seq, tmp, 18);
+		IO.WriteHeader(recordType, version, len, tmp, 26);
+		tmp[31] = 0;
+		seq ++;
+
+		for (int i = 0; i < cbcmac.Length; i ++) {
+			cbcmac[i] = 0;
+		}
+		bc.CBCMac(cbcmac, tmp, 0, 32);
+
+		/*
+		 * Make initial counter value, and compute tag mask.
+		 * Since the counter least significant byte has value 0,
+		 * getting it to the next value is simple and requires
+		 * no carry propagation.
+		 */
+		ctr[0] = 2;
+		Array.Copy(iv, 0, ctr, 1, 12);
+		for (int i = 13; i < 16; i ++) {
+			ctr[i] = 0;
+		}
+		Array.Copy(ctr, 0, tag, 0, 16);
+		bc.BlockEncrypt(tag);
+		ctr[15] = 1;
+
+		/*
+		 * Perform CTR decryption, and compute CBC-MAC. Since
+		 * CBC-MAC requires full blocks, we have to do the
+		 * processing of the last partial block in a temporary
+		 * buffer. The CBC-MAC is computed on the plaintext,
+		 * padded with zeros in the last block.
+		 */
+		int len1 = len & ~15;
+		int len2 = len - len1;
+		bc.CTRCBCRun(ctr, cbcmac, true, data, off, len1);
+		if (len2 > 0) {
+			bc.BlockEncrypt(ctr);
+			for (int i = 0; i < len2; i ++) {
+				data[off + len1 + i] ^= ctr[i];
+			}
+			Array.Copy(data, off + len1, tmp, 0, len2);
+			for (int i = len2; i < 16; i ++) {
+				tmp[i] = 0;
+			}
+			bc.CBCMac(cbcmac, tmp, 0, 16);
+		}
+
+		/*
+		 * Check that the record MAC matches the expected value
+		 * (taking into account the tag mask).
+		 */
+		int z = 0;
+		for (int i = 0; i < (ccm8 ? 8 : 16); i ++) {
+			z |= cbcmac[i] ^ tag[i] ^ data[off + len + i];
+		}
+		return z == 0;
+	}
+}
+
+}
diff --git a/SSLTLS/RecordEncryptCCM.cs b/SSLTLS/RecordEncryptCCM.cs
new file mode 100644
index 0000000..05b61b8
--- /dev/null
+++ b/SSLTLS/RecordEncryptCCM.cs
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2018 Thomas Pornin <pornin@bolet.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining 
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be 
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+using System;
+using System.Text;
+
+using Crypto;
+
+namespace SSLTLS {
+
+internal class RecordEncryptCCM : RecordEncrypt {
+
+	IBlockCipher bc;
+	byte[] iv;
+	ulong seq;
+	byte[] tmp, tag, ctr, cbcmac;
+	bool ccm8;
+
+	internal RecordEncryptCCM(IBlockCipher bc, byte[] iv, bool ccm8)
+	{
+		this.bc = bc;
+		this.iv = new byte[4];
+		Array.Copy(iv, 0, this.iv, 0, 4);
+		seq = 0;
+		tag = new byte[16];
+		tmp = new byte[32];
+		ctr = new byte[16];
+		cbcmac = new byte[16];
+		this.ccm8 = ccm8;
+	}
+
+	internal override void GetMaxPlaintext(ref int start, ref int end)
+	{
+		/*
+		 * We need room at the start for the record header (5 bytes)
+		 * and the explicit nonce (8 bytes). We need room at the end
+		 * for the MAC (16 bytes).
+		 */
+		start += 13;
+		end -= ccm8 ? 8 : 16;
+		int len = Math.Min(end - start, 16384);
+		end = start + len;
+	}
+
+	internal override void Encrypt(int recordType, int version,
+		byte[] data, ref int off, ref int len)
+	{
+		/*
+		 * CBC-MAC starts with block B0, that encodes the
+		 * nonce, tag length, and data length.
+		 * It is then followed by the AAD:
+		 *  - AAD header (length, over 2 bytes in our case)
+		 *  - TLS sequence number (8 bytes)
+		 *  - plain record header
+		 */
+		tmp[0] = (byte)(0x40 | ((ccm8 ? 6 : 14) << 2) | 2);
+		Array.Copy(iv, 0, tmp, 1, 4);
+		IO.Enc64be(seq, tmp, 5);
+		tmp[13] = 0;
+		IO.Enc16be(len, tmp, 14);
+
+		tmp[16] = 0;
+		tmp[17] = 13;
+		IO.Enc64be(seq, tmp, 18);
+		IO.WriteHeader(recordType, version, len, tmp, 26);
+		tmp[31] = 0;
+
+		for (int i = 0; i < cbcmac.Length; i ++) {
+			cbcmac[i] = 0;
+		}
+		bc.CBCMac(cbcmac, tmp, 0, 32);
+
+		/*
+		 * Make initial counter value, and compute tag mask.
+		 * Since the counter least significant byte has value 0,
+		 * getting it to the next value is simple and requires
+		 * no carry propagation.
+		 */
+		ctr[0] = 2;
+		Array.Copy(tmp, 1, ctr, 1, 12);
+		for (int i = 13; i < 16; i ++) {
+			ctr[i] = 0;
+		}
+		Array.Copy(ctr, 0, tag, 0, 16);
+		bc.BlockEncrypt(tag);
+		ctr[15] = 1;
+
+		/*
+		 * Perform CTR encryption and CBC-MAC. CCM is defined
+		 * to use CBC-MAC on the plaintext, not the ciphertext,
+		 * thus we need to set the 'encrypt' flag to false.
+		 *
+		 * When the last block is partial, then we must pad
+		 * the plaintext with zeros, and compute the CBC-MAC
+		 * on that plaintext.
+		 */
+		int len1 = len & ~15;
+		int len2 = len - len1;
+		bc.CTRCBCRun(ctr, cbcmac, false, data, off, len1);
+		if (len2 > 0) {
+			Array.Copy(data, off + len1, tmp, 0, len2);
+			for (int i = len2; i < 16; i ++) {
+				tmp[i] = 0;
+			}
+			bc.CBCMac(cbcmac, tmp, 0, 16);
+			bc.BlockEncrypt(ctr);
+			for (int i = 0; i < len2; i ++) {
+				data[off + len1 + i] ^= ctr[i];
+			}
+		}
+
+		/*
+		 * XOR the CBC-MAC output with the tag mask.
+		 */
+		for (int i = 0; i < (ccm8 ? 8 : 16); i ++) {
+			data[off + len + i] = (byte)(tag[i] ^ cbcmac[i]);
+		}
+
+		/*
+		 * Encode the header, and adjust offset / length.
+		 */
+		off -= 13;
+		len += ccm8 ? 16 : 24;
+		IO.WriteHeader(recordType, version, len, data, off);
+		IO.Enc64be(seq, data, off + 5);
+		len += 5;
+
+		seq ++;
+	}
+}
+
+}
diff --git a/SSLTLS/SSL.cs b/SSLTLS/SSL.cs
index 4059194..0b0948f 100644
--- a/SSLTLS/SSL.cs
+++ b/SSLTLS/SSL.cs
@@ -204,6 +204,16 @@ public sealed class SSL {
 	public const int ECDH_RSA_WITH_AES_128_GCM_SHA256       = 0xC031;
 	public const int ECDH_RSA_WITH_AES_256_GCM_SHA384       = 0xC032;
 
+	/* From RFC 6655 and 7251 */
+	public const int RSA_WITH_AES_128_CCM                   = 0xC09C;
+	public const int RSA_WITH_AES_256_CCM                   = 0xC09D;
+	public const int RSA_WITH_AES_128_CCM_8                 = 0xC0A0;
+	public const int RSA_WITH_AES_256_CCM_8                 = 0xC0A1;
+	public const int ECDHE_ECDSA_WITH_AES_128_CCM           = 0xC0AC;
+	public const int ECDHE_ECDSA_WITH_AES_256_CCM           = 0xC0AD;
+	public const int ECDHE_ECDSA_WITH_AES_128_CCM_8         = 0xC0AE;
+	public const int ECDHE_ECDSA_WITH_AES_256_CCM_8         = 0xC0AF;
+
 	/* From RFC 7905 */
 	public const int ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256      = 0xCCA8;
 	public const int ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256    = 0xCCA9;
@@ -504,6 +514,22 @@ public sealed class SSL {
 			return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
 		case ECDH_RSA_WITH_AES_256_GCM_SHA384:
 			return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+		case RSA_WITH_AES_128_CCM:
+			return "RSA_WITH_AES_128_CCM";
+		case RSA_WITH_AES_256_CCM:
+			return "RSA_WITH_AES_256_CCM";
+		case RSA_WITH_AES_128_CCM_8:
+			return "RSA_WITH_AES_128_CCM_8";
+		case RSA_WITH_AES_256_CCM_8:
+			return "RSA_WITH_AES_256_CCM_8";
+		case ECDHE_ECDSA_WITH_AES_128_CCM:
+			return "ECDHE_ECDSA_WITH_AES_128_CCM";
+		case ECDHE_ECDSA_WITH_AES_256_CCM:
+			return "ECDHE_ECDSA_WITH_AES_256_CCM";
+		case ECDHE_ECDSA_WITH_AES_128_CCM_8:
+			return "ECDHE_ECDSA_WITH_AES_128_CCM_8";
+		case ECDHE_ECDSA_WITH_AES_256_CCM_8:
+			return "ECDHE_ECDSA_WITH_AES_256_CCM_8";
 		case ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
 			return "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256";
 		case ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
@@ -718,6 +744,22 @@ public sealed class SSL {
 			return ECDH_RSA_WITH_AES_128_GCM_SHA256;
 		case "ECDHRSAWITHAES256GCMSHA384":
 			return ECDH_RSA_WITH_AES_256_GCM_SHA384;
+		case "RSAWITHAES128CCM":
+			return RSA_WITH_AES_128_CCM;
+		case "RSAWITHAES256CCM":
+			return RSA_WITH_AES_256_CCM;
+		case "RSAWITHAES128CCM8":
+			return RSA_WITH_AES_128_CCM_8;
+		case "RSAWITHAES256CCM8":
+			return RSA_WITH_AES_256_CCM_8;
+		case "ECDHEECDSAWITHAES128CCM":
+			return ECDHE_ECDSA_WITH_AES_128_CCM;
+		case "ECDHEECDSAWITHAES256CCM":
+			return ECDHE_ECDSA_WITH_AES_256_CCM;
+		case "ECDHEECDSAWITHAES128CCM8":
+			return ECDHE_ECDSA_WITH_AES_128_CCM_8;
+		case "ECDHEECDSAWITHAES256CCM8":
+			return ECDHE_ECDSA_WITH_AES_256_CCM_8;
 		case "ECDHERSAWITHCHACHA20POLY1305SHA256":
 			return ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
 		case "ECDHEECDSAWITHCHACHA20POLY1305SHA256":
@@ -867,6 +909,10 @@ public sealed class SSL {
 		case RSA_WITH_AES_256_CBC_SHA256:
 		case RSA_WITH_AES_128_GCM_SHA256:
 		case RSA_WITH_AES_256_GCM_SHA384:
+		case RSA_WITH_AES_128_CCM:
+		case RSA_WITH_AES_256_CCM:
+		case RSA_WITH_AES_128_CCM_8:
+		case RSA_WITH_AES_256_CCM_8:
 			return true;
 		default:
 			return false;
@@ -996,6 +1042,10 @@ public sealed class SSL {
 		case ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
 		case ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
 		case ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+		case ECDHE_ECDSA_WITH_AES_128_CCM:
+		case ECDHE_ECDSA_WITH_AES_256_CCM:
+		case ECDHE_ECDSA_WITH_AES_128_CCM_8:
+		case ECDHE_ECDSA_WITH_AES_256_CCM_8:
 		case ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
 			return true;
 		default:
@@ -1093,6 +1143,14 @@ public sealed class SSL {
 		case ECDHE_RSA_WITH_AES_256_GCM_SHA384:
 		case ECDH_RSA_WITH_AES_128_GCM_SHA256:
 		case ECDH_RSA_WITH_AES_256_GCM_SHA384:
+		case RSA_WITH_AES_128_CCM:
+		case RSA_WITH_AES_256_CCM:
+		case RSA_WITH_AES_128_CCM_8:
+		case RSA_WITH_AES_256_CCM_8:
+		case ECDHE_ECDSA_WITH_AES_128_CCM:
+		case ECDHE_ECDSA_WITH_AES_256_CCM:
+		case ECDHE_ECDSA_WITH_AES_128_CCM_8:
+		case ECDHE_ECDSA_WITH_AES_256_CCM_8:
 		case ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
 		case ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
 		case DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
diff --git a/SSLTLS/SSLEngine.cs b/SSLTLS/SSLEngine.cs
index e4ebf95..ac951cf 100644
--- a/SSLTLS/SSLEngine.cs
+++ b/SSLTLS/SSLEngine.cs
@@ -79,6 +79,10 @@ public abstract class SSLEngine : Stream {
 		SSL.ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 		SSL.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
 		SSL.ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+		SSL.ECDHE_ECDSA_WITH_AES_128_CCM,
+		SSL.ECDHE_ECDSA_WITH_AES_256_CCM,
+		SSL.ECDHE_ECDSA_WITH_AES_128_CCM_8,
+		SSL.ECDHE_ECDSA_WITH_AES_256_CCM_8,
 		SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
 		SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA256,
 		SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
@@ -103,6 +107,10 @@ public abstract class SSLEngine : Stream {
 
 		SSL.RSA_WITH_AES_128_GCM_SHA256,
 		SSL.RSA_WITH_AES_256_GCM_SHA384,
+		SSL.RSA_WITH_AES_128_CCM,
+		SSL.RSA_WITH_AES_256_CCM,
+		SSL.RSA_WITH_AES_128_CCM_8,
+		SSL.RSA_WITH_AES_256_CCM_8,
 		SSL.RSA_WITH_AES_128_CBC_SHA256,
 		SSL.RSA_WITH_AES_256_CBC_SHA256,
 		SSL.RSA_WITH_AES_128_CBC_SHA,
@@ -1347,6 +1355,8 @@ public abstract class SSLEngine : Stream {
 		IBlockCipher block = null;
 		IDigest hash = null;
 		Poly1305 pp = null;
+		bool isCCM = false;
+		bool isCCM8 = false;
 		switch (CipherSuite) {
 		case SSL.RSA_WITH_3DES_EDE_CBC_SHA:
 		case SSL.DH_DSS_WITH_3DES_EDE_CBC_SHA:
@@ -1475,6 +1485,42 @@ public abstract class SSLEngine : Stream {
 			block = new AES();
 			break;
 
+		case SSL.RSA_WITH_AES_128_CCM:
+		case SSL.ECDHE_ECDSA_WITH_AES_128_CCM:
+			macLen = 0;
+			encLen = 16;
+			ivLen = 4;
+			block = new AES();
+			isCCM = true;
+			break;
+
+		case SSL.RSA_WITH_AES_256_CCM:
+		case SSL.ECDHE_ECDSA_WITH_AES_256_CCM:
+			macLen = 0;
+			encLen = 32;
+			ivLen = 4;
+			block = new AES();
+			isCCM = true;
+			break;
+
+		case SSL.RSA_WITH_AES_128_CCM_8:
+		case SSL.ECDHE_ECDSA_WITH_AES_128_CCM_8:
+			macLen = 0;
+			encLen = 16;
+			ivLen = 4;
+			block = new AES();
+			isCCM8 = true;
+			break;
+
+		case SSL.RSA_WITH_AES_256_CCM_8:
+		case SSL.ECDHE_ECDSA_WITH_AES_256_CCM_8:
+			macLen = 0;
+			encLen = 32;
+			ivLen = 4;
+			block = new AES();
+			isCCM8 = true;
+			break;
+
 		case SSL.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
 		case SSL.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
 		case SSL.DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
@@ -1539,6 +1585,28 @@ public abstract class SSLEngine : Stream {
 				inRec.SetDecryption(
 					new RecordDecryptCBC(block, hm, iv));
 			}
+		} else if (isCCM) {
+			/*
+			 * CCM cipher suite.
+			 */
+			if (write) {
+				outRec.SetEncryption(
+					new RecordEncryptCCM(block, iv, false));
+			} else {
+				inRec.SetDecryption(
+					new RecordDecryptCCM(block, iv, false));
+			}
+		} else if (isCCM8) {
+			/*
+			 * CCM cipher suite with truncated MAC value.
+			 */
+			if (write) {
+				outRec.SetEncryption(
+					new RecordEncryptCCM(block, iv, true));
+			} else {
+				inRec.SetDecryption(
+					new RecordDecryptCCM(block, iv, true));
+			}
 		} else if (block != null) {
 			/*
 			 * GCM cipher suite.
diff --git a/conf/bearssl.json b/conf/bearssl.json
index 97f213a..ce2f9bf 100644
--- a/conf/bearssl.json
+++ b/conf/bearssl.json
@@ -16,6 +16,10 @@
     "ECDHE_RSA_WITH_AES_128_GCM_SHA256",
     "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
     "ECDHE_RSA_WITH_AES_256_GCM_SHA384",
+    "ECDHE_ECDSA_WITH_AES_128_CCM",
+    "ECDHE_ECDSA_WITH_AES_256_CCM",
+    "ECDHE_ECDSA_WITH_AES_128_CCM_8",
+    "ECDHE_ECDSA_WITH_AES_256_CCM_8",
     "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
     "ECDHE_RSA_WITH_AES_128_CBC_SHA256",
     "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
@@ -34,6 +38,10 @@
 
     "RSA_WITH_AES_128_GCM_SHA256",
     "RSA_WITH_AES_256_GCM_SHA384",
+    "RSA_WITH_AES_128_CCM",
+    "RSA_WITH_AES_256_CCM",
+    "RSA_WITH_AES_128_CCM_8",
+    "RSA_WITH_AES_256_CCM_8",
     "RSA_WITH_AES_128_CBC_SHA256",
     "RSA_WITH_AES_256_CBC_SHA256",
     "RSA_WITH_AES_128_CBC_SHA",
-- 
2.17.1