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 using KeePass.Resources;
26 
27 #if !KeePassUAP
28 using KeePass.Util.Spr;
29 #endif
30 
31 using KeePassLib;
32 using KeePassLib.Cryptography.PasswordGenerator;
33 using KeePassLib.Security;
34 using KeePassLib.Utility;
35 
36 namespace KeePass.Util
37 {
38 	public static class PwGeneratorUtil
39 	{
40 		private static string m_strBuiltInSuffix = null;
41 		internal static string BuiltInSuffix
42 		{
43 			get
44 			{
45 				if(m_strBuiltInSuffix == null)
46 					m_strBuiltInSuffix = " (" + KPRes.BuiltIn + ")";
47 				return m_strBuiltInSuffix;
48 			}
49 		}
50 
51 		private static List<PwProfile> m_lBuiltIn = null;
52 		public static List<PwProfile> BuiltInProfiles
53 		{
54 			get
55 			{
56 				if(m_lBuiltIn == null) AllocStandardProfiles();
57 				return m_lBuiltIn;
58 			}
59 		}
60 
AllocStandardProfiles()61 		private static void AllocStandardProfiles()
62 		{
63 			m_lBuiltIn = new List<PwProfile>();
64 
65 			AddStdPattern(KPRes.RandomMacAddress, @"HH\-HH\-HH\-HH\-HH\-HH");
66 
67 			string strHex = KPRes.HexKeyEx;
68 			AddStdPattern(strHex.Replace(@"{PARAM}", "40"), @"h{10}");
69 			AddStdPattern(strHex.Replace(@"{PARAM}", "128"), @"h{32}");
70 			AddStdPattern(strHex.Replace(@"{PARAM}", "256"), @"h{64}");
71 		}
72 
AddStdPattern(string strName, string strPattern)73 		private static void AddStdPattern(string strName, string strPattern)
74 		{
75 			PwProfile p = new PwProfile();
76 
77 			p.Name = strName + PwGeneratorUtil.BuiltInSuffix;
78 			p.CollectUserEntropy = false;
79 			p.GeneratorType = PasswordGeneratorType.Pattern;
80 			p.Pattern = strPattern;
81 
82 			m_lBuiltIn.Add(p);
83 		}
84 
85 		/// <summary>
86 		/// Get a list of all password generator profiles (built-in
87 		/// and user-defined ones).
88 		/// </summary>
GetAllProfiles(bool bSort)89 		public static List<PwProfile> GetAllProfiles(bool bSort)
90 		{
91 			List<PwProfile> lUser = Program.Config.PasswordGenerator.UserProfiles;
92 
93 			// Sort it in the configuration file
94 			if(bSort) lUser.Sort(PwGeneratorUtil.CompareProfilesByName);
95 
96 			// Remove old built-in profiles by KeePass <= 2.17
97 			for(int i = lUser.Count - 1; i >= 0; --i)
98 			{
99 				if(IsBuiltInProfile(lUser[i].Name)) lUser.RemoveAt(i);
100 			}
101 
102 			List<PwProfile> l = new List<PwProfile>();
103 			l.AddRange(PwGeneratorUtil.BuiltInProfiles);
104 			l.AddRange(lUser);
105 			if(bSort) l.Sort(PwGeneratorUtil.CompareProfilesByName);
106 			return l;
107 		}
108 
IsBuiltInProfile(string strName)109 		public static bool IsBuiltInProfile(string strName)
110 		{
111 			if(strName == null) { Debug.Assert(false); return false; }
112 
113 			string strWithSuffix = strName + PwGeneratorUtil.BuiltInSuffix;
114 			foreach(PwProfile p in PwGeneratorUtil.BuiltInProfiles)
115 			{
116 				if(p.Name.Equals(strName, StrUtil.CaseIgnoreCmp) ||
117 					p.Name.Equals(strWithSuffix, StrUtil.CaseIgnoreCmp))
118 					return true;
119 			}
120 
121 			return false;
122 		}
123 
CompareProfilesByName(PwProfile a, PwProfile b)124 		public static int CompareProfilesByName(PwProfile a, PwProfile b)
125 		{
126 			if(a == b) return 0;
127 			if(a == null) { Debug.Assert(false); return -1; }
128 			if(b == null) { Debug.Assert(false); return 1; }
129 
130 			return StrUtil.CompareNaturally(a.Name, b.Name);
131 		}
132 
133 #if !KeePassUAP
GenerateAcceptable(PwProfile prf, byte[] pbUserEntropy, PwEntry peOptCtx, PwDatabase pdOptCtx, bool bShowErrorUI)134 		internal static ProtectedString GenerateAcceptable(PwProfile prf,
135 			byte[] pbUserEntropy, PwEntry peOptCtx, PwDatabase pdOptCtx,
136 			bool bShowErrorUI)
137 		{
138 			bool bAcceptAlways = false;
139 			string strError;
140 			return GenerateAcceptable(prf, pbUserEntropy, peOptCtx, pdOptCtx,
141 				bShowErrorUI, ref bAcceptAlways, out strError);
142 		}
143 
GenerateAcceptable(PwProfile prf, byte[] pbUserEntropy, PwEntry peOptCtx, PwDatabase pdOptCtx, bool bShowErrorUI, ref bool bAcceptAlways, out string strError)144 		internal static ProtectedString GenerateAcceptable(PwProfile prf,
145 			byte[] pbUserEntropy, PwEntry peOptCtx, PwDatabase pdOptCtx,
146 			bool bShowErrorUI, ref bool bAcceptAlways, out string strError)
147 		{
148 			strError = null;
149 
150 			ProtectedString ps = ProtectedString.Empty;
151 			SprContext ctx = new SprContext(peOptCtx, pdOptCtx,
152 				SprCompileFlags.NonActive, false, false);
153 
154 			while(true)
155 			{
156 				try
157 				{
158 					PwgError e = PwGenerator.Generate(out ps, prf, pbUserEntropy,
159 						Program.PwGeneratorPool);
160 
161 					if(e != PwgError.Success)
162 					{
163 						strError = PwGenerator.ErrorToString(e, true);
164 						break;
165 					}
166 				}
167 				catch(Exception ex)
168 				{
169 					strError = PwGenerator.ErrorToString(ex, true);
170 					break;
171 				}
172 				finally
173 				{
174 					if(ps == null) { Debug.Assert(false); ps = ProtectedString.Empty; }
175 				}
176 
177 				if(bAcceptAlways) break;
178 
179 				string str = ps.ReadString();
180 				string strCmp = SprEngine.Compile(str, ctx);
181 
182 				if(str != strCmp)
183 				{
184 					if(prf.GeneratorType == PasswordGeneratorType.CharSet)
185 						continue; // Silently try again
186 
187 					string strText = str + MessageService.NewParagraph +
188 						KPRes.GenPwSprVariant + MessageService.NewParagraph +
189 						KPRes.GenPwAccept;
190 
191 					if(!MessageService.AskYesNo(strText, null, false))
192 						continue;
193 					bAcceptAlways = true;
194 				}
195 
196 				break;
197 			}
198 
199 			if(!string.IsNullOrEmpty(strError))
200 			{
201 				ps = ProtectedString.Empty;
202 				if(bShowErrorUI) MessageService.ShowWarning(strError);
203 			}
204 
205 			return ps;
206 		}
207 
GenerateAuto(PwEntry pe, PwDatabase pd)208 		internal static void GenerateAuto(PwEntry pe, PwDatabase pd)
209 		{
210 			if(pe == null) { Debug.Assert(false); return; }
211 			if(pd == null) { Debug.Assert(false); return; }
212 
213 			ProtectedString ps = GenerateAcceptable(
214 				Program.Config.PasswordGenerator.AutoGeneratedPasswordsProfile,
215 				null, pe, pd, false);
216 			pe.Strings.Set(PwDefs.PasswordField, ps.WithProtection(
217 				pd.MemoryProtection.ProtectPassword));
218 		}
219 #endif
220 	}
221 }
222