1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections.Generic;
22 using System.Diagnostics;
23 using System.IO;
24 using System.Reflection;
25 using System.Text;
26 
27 #if !KeePassUAP
28 using System.Security.Cryptography;
29 #endif
30 
31 using KeePassLib.Native;
32 using KeePassLib.Utility;
33 
34 namespace KeePassLib.Cryptography
35 {
36 	public static class CryptoUtil
37 	{
38 		private static bool? g_obProtData = null;
39 		public static bool IsProtectedDataSupported
40 		{
41 			get
42 			{
43 				if(g_obProtData.HasValue) return g_obProtData.Value;
44 
45 				bool b = false;
46 				try
47 				{
48 					Random r = CryptoRandom.NewWeakRandom();
49 
50 					byte[] pbData = new byte[137];
51 					r.NextBytes(pbData);
52 
53 					byte[] pbEnt = new byte[41];
54 					r.NextBytes(pbEnt);
55 
56 					byte[] pbEnc = ProtectedData.Protect(pbData, pbEnt,
57 						DataProtectionScope.CurrentUser);
58 					if((pbEnc != null) && !MemUtil.ArraysEqual(pbEnc, pbData))
59 					{
60 						byte[] pbDec = ProtectedData.Unprotect(pbEnc, pbEnt,
61 							DataProtectionScope.CurrentUser);
62 						if((pbDec != null) && MemUtil.ArraysEqual(pbDec, pbData))
63 							b = true;
64 					}
65 				}
66 				catch(Exception) { Debug.Assert(false); }
67 
68 				Debug.Assert(b); // Should be supported on all systems
69 				g_obProtData = b;
70 				return b;
71 			}
72 		}
73 
HashSha256(byte[] pbData)74 		public static byte[] HashSha256(byte[] pbData)
75 		{
76 			if(pbData == null) throw new ArgumentNullException("pbData");
77 
78 			return HashSha256(pbData, 0, pbData.Length);
79 		}
80 
HashSha256(byte[] pbData, int iOffset, int cbCount)81 		public static byte[] HashSha256(byte[] pbData, int iOffset, int cbCount)
82 		{
83 			if(pbData == null) throw new ArgumentNullException("pbData");
84 
85 #if DEBUG
86 			byte[] pbCopy = new byte[pbData.Length];
87 			Array.Copy(pbData, pbCopy, pbData.Length);
88 #endif
89 
90 			byte[] pbHash;
91 			using(SHA256Managed h = new SHA256Managed())
92 			{
93 				pbHash = h.ComputeHash(pbData, iOffset, cbCount);
94 			}
95 
96 #if DEBUG
97 			// Ensure the data has not been modified
98 			Debug.Assert(MemUtil.ArraysEqual(pbData, pbCopy));
99 
100 			Debug.Assert((pbHash != null) && (pbHash.Length == 32));
101 			byte[] pbZero = new byte[32];
102 			Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero));
103 #endif
104 
105 			return pbHash;
106 		}
107 
HashSha256(string strFilePath)108 		internal static byte[] HashSha256(string strFilePath)
109 		{
110 			byte[] pbHash = null;
111 
112 			using(FileStream fs = new FileStream(strFilePath, FileMode.Open,
113 				FileAccess.Read, FileShare.Read))
114 			{
115 				using(SHA256Managed h = new SHA256Managed())
116 				{
117 					pbHash = h.ComputeHash(fs);
118 				}
119 			}
120 
121 			return pbHash;
122 		}
123 
124 		/// <summary>
125 		/// Create a cryptographic key of length <paramref name="cbOut" />
126 		/// (in bytes) from <paramref name="pbIn" />.
127 		/// </summary>
ResizeKey(byte[] pbIn, int iInOffset, int cbIn, int cbOut)128 		public static byte[] ResizeKey(byte[] pbIn, int iInOffset,
129 			int cbIn, int cbOut)
130 		{
131 			if(pbIn == null) throw new ArgumentNullException("pbIn");
132 			if(cbOut < 0) throw new ArgumentOutOfRangeException("cbOut");
133 
134 			if(cbOut == 0) return MemUtil.EmptyByteArray;
135 
136 			byte[] pbHash;
137 			if(cbOut <= 32) pbHash = HashSha256(pbIn, iInOffset, cbIn);
138 			else
139 			{
140 				using(SHA512Managed h = new SHA512Managed())
141 				{
142 					pbHash = h.ComputeHash(pbIn, iInOffset, cbIn);
143 				}
144 			}
145 
146 			if(cbOut == pbHash.Length) return pbHash;
147 
148 			byte[] pbRet = new byte[cbOut];
149 			if(cbOut < pbHash.Length)
150 				Array.Copy(pbHash, pbRet, cbOut);
151 			else
152 			{
153 				int iPos = 0;
154 				ulong r = 0;
155 				while(iPos < cbOut)
156 				{
157 					Debug.Assert(pbHash.Length == 64);
158 					using(HMACSHA256 h = new HMACSHA256(pbHash))
159 					{
160 						byte[] pbR = MemUtil.UInt64ToBytes(r);
161 						byte[] pbPart = h.ComputeHash(pbR);
162 
163 						int cbCopy = Math.Min(cbOut - iPos, pbPart.Length);
164 						Debug.Assert(cbCopy > 0);
165 
166 						Array.Copy(pbPart, 0, pbRet, iPos, cbCopy);
167 						iPos += cbCopy;
168 						++r;
169 
170 						MemUtil.ZeroByteArray(pbPart);
171 					}
172 				}
173 				Debug.Assert(iPos == cbOut);
174 			}
175 
176 #if DEBUG
177 			byte[] pbZero = new byte[pbHash.Length];
178 			Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero));
179 #endif
180 			MemUtil.ZeroByteArray(pbHash);
181 			return pbRet;
182 		}
183 
184 #if !KeePassUAP
185 		private static bool? g_obAesCsp = null;
CreateAes()186 		internal static SymmetricAlgorithm CreateAes()
187 		{
188 			if(g_obAesCsp.HasValue)
189 				return (g_obAesCsp.Value ? CreateAesCsp() : new RijndaelManaged());
190 
191 			SymmetricAlgorithm a = CreateAesCsp();
192 			g_obAesCsp = (a != null);
193 			return (a ?? new RijndaelManaged());
194 		}
195 
CreateAesCsp()196 		private static SymmetricAlgorithm CreateAesCsp()
197 		{
198 			try
199 			{
200 				// On Windows, the CSP implementation is only minimally
201 				// faster (and for key derivations it's not used anyway,
202 				// as KeePass uses a native implementation based on
203 				// CNG/BCrypt, which is much faster)
204 				if(!NativeLib.IsUnix()) return null;
205 
206 				string strFqn = Assembly.CreateQualifiedName(
207 					"System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
208 					"System.Security.Cryptography.AesCryptoServiceProvider");
209 
210 				Type t = Type.GetType(strFqn);
211 				if(t == null) return null;
212 
213 				return (Activator.CreateInstance(t) as SymmetricAlgorithm);
214 			}
215 			catch(Exception) { Debug.Assert(false); }
216 
217 			return null;
218 		}
219 #endif
220 
ProtectData(byte[] pb, byte[] pbOptEntropy, DataProtectionScope s)221 		public static byte[] ProtectData(byte[] pb, byte[] pbOptEntropy,
222 			DataProtectionScope s)
223 		{
224 			return ProtectDataPriv(pb, true, pbOptEntropy, s);
225 		}
226 
UnprotectData(byte[] pb, byte[] pbOptEntropy, DataProtectionScope s)227 		public static byte[] UnprotectData(byte[] pb, byte[] pbOptEntropy,
228 			DataProtectionScope s)
229 		{
230 			return ProtectDataPriv(pb, false, pbOptEntropy, s);
231 		}
232 
ProtectDataPriv(byte[] pb, bool bProtect, byte[] pbOptEntropy, DataProtectionScope s)233 		private static byte[] ProtectDataPriv(byte[] pb, bool bProtect,
234 			byte[] pbOptEntropy, DataProtectionScope s)
235 		{
236 			if(pb == null) throw new ArgumentNullException("pb");
237 
238 			if((pbOptEntropy != null) && (pbOptEntropy.Length == 0))
239 				pbOptEntropy = null;
240 
241 			if(CryptoUtil.IsProtectedDataSupported)
242 			{
243 				if(bProtect)
244 					return ProtectedData.Protect(pb, pbOptEntropy, s);
245 				return ProtectedData.Unprotect(pb, pbOptEntropy, s);
246 			}
247 
248 			Debug.Assert(false);
249 			byte[] pbCopy = new byte[pb.Length];
250 			Array.Copy(pb, pbCopy, pb.Length);
251 			return pbCopy;
252 		}
253 	}
254 }
255