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.Text;
24 
25 #if !KeePassUAP
26 using System.Security.Cryptography;
27 #endif
28 
29 using KeePassLib.Resources;
30 using KeePassLib.Security;
31 using KeePassLib.Utility;
32 
33 namespace KeePassLib.Cryptography.PasswordGenerator
34 {
35 	public enum PwgError
36 	{
37 		Success = 0,
38 		Unknown = 1,
39 		TooFewCharacters = 2,
40 		UnknownAlgorithm = 3,
41 		InvalidCharSet = 4,
42 		InvalidPattern = 5
43 	}
44 
45 	/// <summary>
46 	/// Password generator.
47 	/// </summary>
48 	public static class PwGenerator
49 	{
Generate(out ProtectedString psOut, PwProfile pwProfile, byte[] pbUserEntropy, CustomPwGeneratorPool pwAlgorithmPool)50 		public static PwgError Generate(out ProtectedString psOut,
51 			PwProfile pwProfile, byte[] pbUserEntropy,
52 			CustomPwGeneratorPool pwAlgorithmPool)
53 		{
54 			Debug.Assert(pwProfile != null);
55 			if(pwProfile == null) throw new ArgumentNullException("pwProfile");
56 
57 			PwgError e = PwgError.Unknown;
58 			CryptoRandomStream crs = null;
59 			byte[] pbKey = null;
60 			try
61 			{
62 				crs = CreateRandomStream(pbUserEntropy, out pbKey);
63 
64 				if(pwProfile.GeneratorType == PasswordGeneratorType.CharSet)
65 					e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs);
66 				else if(pwProfile.GeneratorType == PasswordGeneratorType.Pattern)
67 					e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs);
68 				else if(pwProfile.GeneratorType == PasswordGeneratorType.Custom)
69 					e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool);
70 				else { Debug.Assert(false); psOut = ProtectedString.Empty; }
71 			}
72 			finally
73 			{
74 				if(crs != null) crs.Dispose();
75 				if(pbKey != null) MemUtil.ZeroByteArray(pbKey);
76 			}
77 
78 			return e;
79 		}
80 
CreateRandomStream(byte[] pbAdditionalEntropy, out byte[] pbKey)81 		private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy,
82 			out byte[] pbKey)
83 		{
84 			pbKey = CryptoRandom.Instance.GetRandomBytes(128);
85 
86 			// Mix in additional entropy
87 			Debug.Assert(pbKey.Length >= 64);
88 			if((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0))
89 			{
90 				using(SHA512Managed h = new SHA512Managed())
91 				{
92 					byte[] pbHash = h.ComputeHash(pbAdditionalEntropy);
93 					MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length);
94 				}
95 			}
96 
97 			return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey);
98 		}
99 
GenerateCharacter(PwCharSet pwCharSet, CryptoRandomStream crsRandomSource)100 		internal static char GenerateCharacter(PwCharSet pwCharSet,
101 			CryptoRandomStream crsRandomSource)
102 		{
103 			uint cc = pwCharSet.Size;
104 			if(cc == 0) return char.MinValue;
105 
106 			uint i = (uint)crsRandomSource.GetRandomUInt64(cc);
107 			return pwCharSet[i];
108 		}
109 
PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)110 		internal static bool PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile)
111 		{
112 			uint cc = pwCharSet.Size;
113 			for(uint i = 0; i < cc; ++i)
114 			{
115 				char ch = pwCharSet[i];
116 				if((ch == char.MinValue) || (ch == '\t') || (ch == '\r') ||
117 					(ch == '\n') || char.IsSurrogate(ch))
118 					return false;
119 			}
120 
121 			if(pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike);
122 
123 			if(!string.IsNullOrEmpty(pwProfile.ExcludeCharacters))
124 				pwCharSet.Remove(pwProfile.ExcludeCharacters);
125 
126 			return true;
127 		}
128 
Shuffle(char[] v, CryptoRandomStream crsRandomSource)129 		internal static void Shuffle(char[] v, CryptoRandomStream crsRandomSource)
130 		{
131 			if(v == null) { Debug.Assert(false); return; }
132 			if(crsRandomSource == null) { Debug.Assert(false); return; }
133 
134 			for(int i = v.Length - 1; i >= 1; --i)
135 			{
136 				int j = (int)crsRandomSource.GetRandomUInt64((ulong)(i + 1));
137 
138 				char t = v[i];
139 				v[i] = v[j];
140 				v[j] = t;
141 			}
142 		}
143 
GenerateCustom(out ProtectedString psOut, PwProfile pwProfile, CryptoRandomStream crs, CustomPwGeneratorPool pwAlgorithmPool)144 		private static PwgError GenerateCustom(out ProtectedString psOut,
145 			PwProfile pwProfile, CryptoRandomStream crs,
146 			CustomPwGeneratorPool pwAlgorithmPool)
147 		{
148 			psOut = ProtectedString.Empty;
149 
150 			Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom);
151 			if(pwAlgorithmPool == null) return PwgError.UnknownAlgorithm;
152 
153 			string strID = pwProfile.CustomAlgorithmUuid;
154 			if(string.IsNullOrEmpty(strID)) return PwgError.UnknownAlgorithm;
155 
156 			byte[] pbUuid = Convert.FromBase64String(strID);
157 			PwUuid uuid = new PwUuid(pbUuid);
158 			CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid);
159 			if(pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; }
160 
161 			ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs);
162 			if(pwd == null) return PwgError.Unknown;
163 
164 			psOut = pwd;
165 			return PwgError.Success;
166 		}
167 
ErrorToString(PwgError e, bool bHeader)168 		internal static string ErrorToString(PwgError e, bool bHeader)
169 		{
170 			if(e == PwgError.Success) { Debug.Assert(false); return string.Empty; }
171 			if((e == PwgError.Unknown) && bHeader) return KLRes.PwGenFailed;
172 
173 			string str = KLRes.UnknownError;
174 			switch(e)
175 			{
176 				// case PwgError.Success:
177 				//	break;
178 
179 				case PwgError.Unknown:
180 					break;
181 
182 				case PwgError.TooFewCharacters:
183 					str = KLRes.CharSetTooFewChars;
184 					break;
185 
186 				case PwgError.UnknownAlgorithm:
187 					str = KLRes.AlgorithmUnknown;
188 					break;
189 
190 				case PwgError.InvalidCharSet:
191 					str = KLRes.CharSetInvalid;
192 					break;
193 
194 				case PwgError.InvalidPattern:
195 					str = KLRes.PatternInvalid;
196 					break;
197 
198 				default:
199 					Debug.Assert(false);
200 					break;
201 			}
202 
203 			if(bHeader)
204 				str = KLRes.PwGenFailed + MessageService.NewParagraph + str;
205 
206 			return str;
207 		}
208 
ErrorToString(Exception ex, bool bHeader)209 		internal static string ErrorToString(Exception ex, bool bHeader)
210 		{
211 			string str = KLRes.UnknownError;
212 			if((ex != null) && !string.IsNullOrEmpty(ex.Message))
213 				str = ex.Message;
214 
215 			if(bHeader)
216 				str = KLRes.PwGenFailed + MessageService.NewParagraph + str;
217 
218 			return str;
219 		}
220 	}
221 }
222