1 //
2 // CFB Unit Tests
3 //
4 // Author:
5 //	Sebastien Pouliot  <sebastien@xamarin.com>
6 //
7 // Copyright (C) 2013 Xamarin Inc (http://www.xamarin.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 
29 using System;
30 using System.Collections.Generic;
31 using System.Security.Cryptography;
32 
33 using NUnit.Framework;
34 
35 namespace MonoTests.System.Security.Cryptography {
36 
37 	public abstract class CfbTests {
38 
GetInstance()39 		protected abstract SymmetricAlgorithm GetInstance ();
40 
ProcessBlockSizes(SymmetricAlgorithm algo)41 		protected void ProcessBlockSizes (SymmetricAlgorithm algo)
42 		{
43 			algo.Padding = PaddingMode.None;
44 			foreach (KeySizes bs in algo.LegalBlockSizes) {
45 				for (int blockSize = bs.MinSize; blockSize <= bs.MaxSize; blockSize += bs.SkipSize) {
46 					algo.BlockSize = blockSize;
47 					ProcessKeySizes (algo);
48 					// SkipSize can be 0 (e.g. DES) if only one block size is available
49 					if (blockSize == bs.MaxSize)
50 						break;
51 				}
52 			}
53 		}
54 
ProcessKeySizes(SymmetricAlgorithm algo)55 		protected void ProcessKeySizes (SymmetricAlgorithm algo)
56 		{
57 			foreach (KeySizes ks in algo.LegalKeySizes) {
58 				for (int keySize = ks.MinSize; keySize <= ks.MaxSize; keySize += ks.SkipSize) {
59 					algo.KeySize = keySize;
60 					algo.Key = GetKey (algo);
61 					algo.IV = new byte [algo.BlockSize / 8];
62 					ProcessPadding (algo);
63 					// SkipSize can be 0 (e.g. DES) if only one key size is available
64 					if (keySize == ks.MaxSize)
65 						break;
66 				}
67 			}
68 		}
69 
70 		protected abstract PaddingMode[] PaddingModes { get; }
71 
ProcessPadding(SymmetricAlgorithm algo)72 		protected void ProcessPadding (SymmetricAlgorithm algo)
73 		{
74 			foreach (var padding in PaddingModes) {
75 				algo.Padding = padding;
76 				CFB (algo);
77 			}
78 		}
79 
GetKey(SymmetricAlgorithm algo)80 		protected virtual byte [] GetKey (SymmetricAlgorithm algo)
81 		{
82 			return new byte [algo.KeySize / 8];
83 		}
84 
CFB(SymmetricAlgorithm algo)85 		protected abstract void CFB (SymmetricAlgorithm algo);
86 
GetId(SymmetricAlgorithm algo)87 		protected int GetId (SymmetricAlgorithm algo)
88 		{
89 			return (algo.BlockSize << 24) + (algo.KeySize << 16) + ((int) algo.Padding << 8) + algo.FeedbackSize;
90 		}
91 
GetExpectedResult(SymmetricAlgorithm algo, byte [] encryptedData)92 		protected virtual string GetExpectedResult (SymmetricAlgorithm algo, byte [] encryptedData)
93 		{
94 			int id = GetId (algo);
95 			string expected = BitConverter.ToString (encryptedData);
96 			Console.WriteLine ("// block size: {0}, key size: {1}, padding: {2}, feedback: {3}", algo.BlockSize, algo.KeySize, algo.Padding, algo.FeedbackSize);
97 			Console.WriteLine ("{{ {0}, \"{1}\" }},", id, expected);
98 			return expected;
99 		}
100 
CFB(SymmetricAlgorithm algo, int feedbackSize)101 		protected void CFB (SymmetricAlgorithm algo, int feedbackSize)
102 		{
103 			byte [] data = new byte [feedbackSize >> 3];
104 			for (int i = 0; i < data.Length; i++)
105 				data [i] = (byte) (0xff - i);
106 			byte [] encdata = Encryptor (algo, data);
107 			string expected = GetExpectedResult (algo, encdata);
108 			string actual = null;
109 			if (algo.Padding == PaddingMode.ISO10126) {
110 				// ISO10126 uses random data so we can't compare the last bytes with a test vector
111 				actual = BitConverter.ToString (encdata, 0, data.Length);
112 				expected = expected.Substring (0, actual.Length);
113 			} else {
114 				actual = BitConverter.ToString (encdata);
115 			}
116 			Assert.AreEqual (expected, actual, "encrypted value");
117 			byte [] decdata = Decryptor (algo, encdata);
118 			if (algo.Padding == PaddingMode.Zeros) {
119 				// this requires manually unpadding the decrypted data - but unlike ISO10126
120 				// we know the rest of the data will be 0 (not random) so we check that
121 				byte [] resize = new byte [data.Length];
122 				Array.Copy (decdata, 0, resize, 0, resize.Length);
123 				// all zeros afterward!
124 				for (int i = resize.Length; i < decdata.Length; i++)
125 					Assert.AreEqual (0, decdata [i], "padding zero {0}", i);
126 				decdata = resize;
127 			}
128 			Assert.AreEqual (data, decdata, "Roundtrip {0} {1}", algo.Mode, algo.FeedbackSize);
129 		}
130 
GetTransformBlockSize(SymmetricAlgorithm algo)131 		protected virtual int GetTransformBlockSize (SymmetricAlgorithm algo)
132 		{
133 			return algo.BlockSize / 8;
134 		}
135 
Encryptor(SymmetricAlgorithm algo, byte [] data)136 		byte [] Encryptor (SymmetricAlgorithm algo, byte [] data)
137 		{
138 			using (ICryptoTransform t = algo.CreateEncryptor (algo.Key, algo.IV)) {
139 				int size = GetTransformBlockSize (algo);
140 				Assert.That (t.InputBlockSize == size, "Encryptor InputBlockSize {0} {1}", algo.Mode, algo.FeedbackSize);
141 				Assert.That (t.OutputBlockSize == size, "Encryptor OutputBlockSize {0} {1}", algo.Mode, algo.FeedbackSize);
142 				return t.TransformFinalBlock (data, 0, data.Length);
143 			}
144 		}
145 
Decryptor(SymmetricAlgorithm algo, byte [] encdata)146 		byte [] Decryptor (SymmetricAlgorithm algo, byte [] encdata)
147 		{
148 			using (ICryptoTransform t = algo.CreateDecryptor (algo.Key, algo.IV)) {
149 				int size = GetTransformBlockSize (algo);
150 				Assert.That (t.InputBlockSize == size, "Decryptor InputBlockSize {0} {1}", algo.Mode, algo.FeedbackSize);
151 				Assert.That (t.OutputBlockSize == size, "Decryptor OutputBlockSize {0} {1}", algo.Mode, algo.FeedbackSize);
152 				return t.TransformFinalBlock (encdata, 0, encdata.Length);
153 			}
154 		}
155 	}
156 
157 	// most algorithms are "limited" and only support CFB8
158 	public abstract class LimitedCfbTests : CfbTests {
159 
160 		// all *CryptoServiceProvider implementation refuse Padding.None
161 		static PaddingMode[] csp_padding_modes = new [] { PaddingMode.PKCS7, PaddingMode.Zeros, PaddingMode.ANSIX923, PaddingMode.ISO10126 };
162 
163 		protected override PaddingMode [] PaddingModes {
164 			get { return csp_padding_modes; }
165 		}
166 
167 		[Test]
168 		[ExpectedException (typeof (CryptographicException))]
Cfb_None()169 		public void Cfb_None ()
170 		{
171 			using (var algo = GetInstance ()) {
172 				algo.Padding = PaddingMode.None;
173 				CFB (algo, 8);
174 			}
175 		}
176 
CFB(SymmetricAlgorithm algo)177 		protected override void CFB (SymmetricAlgorithm algo)
178 		{
179 			algo.Mode = CipherMode.CFB;
180 			// System.Security.Cryptography.CryptographicException : Feedback size for the cipher feedback mode (CFB) must be 8 bits.
181 			algo.FeedbackSize = 8;
182 			CFB (algo, algo.FeedbackSize);
183 		}
184 	}
185 
186 	// DES and 3DES won't accept a key with all zero (since it's a weak key for them)
187 	public abstract class WeakKeyCfbTests : LimitedCfbTests {
188 
GetKey(SymmetricAlgorithm algo)189 		protected override byte [] GetKey (SymmetricAlgorithm algo)
190 		{
191 			var key = base.GetKey (algo);
192 			for (byte i = 0; i < key.Length; i++)
193 				key [i] = i;
194 			return key;
195 		}
196 	}
197 }