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