Windows Giriş Ekranında C# Form Açmak
Can Dogu
Merhaba arkadaşlar, bu yazıda windows giriş ekranına nasıl müdahale edilir, windows giriş ekranında kullanıcı adı ve parola bilgisi nasıl alınır / işlenir, özelleştirilmiş çoklu kimlik doğrulama (custom multi factor authentication) gibi harici doğrulama yöntemleri nasıl uygulanır gibi konular hakkında kısa bir bilgi vereceğim.
Yazıya herhangi bir kod veya kod bloğu eklemeyeceğim. Olayı teorik olarak anlatacağım. Kodlama ile ilgili sorularınız olursa metnin başındaki mail adresimden veya Linkedin üzerinden bana erişebilirsiniz. Öncelikle, şunu belirteyim, kimlik bilgisi sağlayıcı yapıları, genelde C++ ile kullanılmak üzere geliştirilmiştir. Ne kadar iyi C# bilirsek bilelim, bir yerden sonra C++ kodu geliştirmemiz ve bu kodu projemizde kullanmamız (DllImport) gerekecektir.
Böyle bir projeyi hayata geçirebilmeniz için genel olarak iyi seviyede C# bilgisine sahip olmanız, memory manipulation bilmeniz, windows form uygulamaları geliştiriyor olmanız, geliştirdiğiniz iş mantığınıza bağlı olarak gerekli olması durumunda windows servis geliştirmeniz ve bu yapılar arasındaki iletişim yöntemlerini biliyor olmanız, C#’ın yetmediği alanlar için C++’ta DLL kütüphanesi oluşturup bu kütüphaneyi C# içinden kullanabiliyor olmanız gerekecek. Bunları zaten biliyorsanız yazıyı okumaya devam edebilirsiniz. Bilmiyorsanız, ilerleyen zamanda uygulamalar arası iletişim (interprocess communication) hakkında da bir yazı yazmayı düşünüyorum.
Windows Vista öncesinde, windows’a giriş (logon) süreci, Microsoft’un geliştirmiş olduğu, windows’un temel parçalarından olan MsGina.dll adında bir kütüphane üzerinden gerçekleşiyordu. Windows Vista sürümü ile birlikte “kimlik bilgisi sağlayıcı (credential provider)“ yapıları devreye girdi. MsGina artık desteklenmeyen Windows sürümlerini ilgilendirdiği için bu yazıda MsGina.dll’e değinilmeyecektir.
Nedir bu “kimlik bilgisi sağlayıcı (credential provider)”?
Zamanla oluşan farklı kimlik doğrulama ihtiyaçlarına yönelik Windows işletim sistemine eklenen ve özelleştirilebilen bir kimlik doğrulama yapısıdır. Aslında kullanıcıdan giriş bilgilerini almayı doğrulamayı değil “özelleştirilmiş (custom)” hale getirmemizi sağlayan bir yapıdır. Siz sadece kimlik bilgilerini almayı özelleştirebilirsiniz. Doğrulamayı gene windows (winlogon) yapacaktır burası önemli. Microsoft tarafından belirlenen bazı kalıplar dahilinde, kullanıcıdan kullanıcı adı ve parolasını alarak ve istediğimiz ara operasyonları işleyerek kişiyi sisteme login ettirmemizi sağlar. Çok yaygın olan ihtiyaca yönelik bir örnek vereyim; kurumunuzdaki çalışanların iş bilgisayarına çok faktörlü kimlik doğrulama ile girmesini istiyorsunuz ve kullanıcının cep telefonuna tek kullanımlık bir 6 haneli kod gönderip bu kodu doğrulamasını isteyeceksiniz veya herhangi bir “doğrulayıcı (authenticator)” uygulamasını kullanmasını isteyeceksiniz. Kullanıcı bu kodu doğru girmezse, Windows’a giriş yapamasın istiyorsunuz. Bu durumda özelleştirilmiş bir kimlik bilgisi sağlayıcı geliştirmeniz işleri çok kolaylaştıracaktır.
Teorik Olarak Nasıl Uygulanır?
Öncelikle, yapacağınız tüm testleri ve denemeleri bir sanal bilgisayar üzerinde yapmanızı tavsiye ediyorum. Çünkü bu DLL’de oluşan bir hata windows’a girişinizi engelleyebilir. Bir kimlik bilgisi sağlayıcı oluşturabilmeniz için COM DLL geliştirmeniz gerekiyor. Geliştireceğiniz bu COM DLL Windows SDK içinden üreteceğiniz “CredentialProvider.Interop” adında bir DLL’i referans olarak almalıdır. DLL’i referans aldıktan sonra, 2 tane temel sınıf oluşturacaksınız ve referans olarak aldığınız DLL’den gelen iki adet Interface’i (arayüz) implement edeceksiniz. Bunlardan biri ICredentialProvider diğeri ICredentialProviderCredential. Bu arayüzleri implement ettiğinizde, sınıflariniz içine implement edilen metodlar gelecek. Bu metodların sıralamasını ve ne işe yaradıklarını aşağıda anlatacağım. ICredentialProvider’ı implement ettiğiniz sınıf, Windows giriş ekranı ilk açıldığında sizden alacağı bilgileri ayarlayacağınız sınıf olacak. Yani, yazdığınız bu kimlik bilgisi sağlayıcı hangi senaryolarda kullanıcıya gösterilecek gibi. Örneğin; windows logon senaryosunda kimlik bilgisi sağlayıcınızın kullanıcıya gösterilmesini istiyorsunuz fakat windows unlock senaryosunda gösterilmesini istemiyorsunuz. İşte bu ayarlamaları bu sınıf içinde yapacaksınız. LogonUI ekranında sol alt kısımda görünen kutucuklar Credential Tile olarak adlandırılır ve credential tile’lardan herhangi biri seçildiğinde, yukarıda belirttiğim ikinci implement ettiğimiz ICredentialProviderCredential sınıf implementasyonu devreye girer. Bu sınıf içinde, giriş ekranında windows’un izin verdiği kadarıyla kullanabileceğimiz bazı built-in input alanları mevcut. Bu alanlar haricinde ek bir alan mevcut değil. Ek alanlar için kendi özelleştirilmiş formunuzu tasarlamanız ve aşağıda anlatacağım şekilde o formu kullanıcıya göstermeniz gerekecek.
Windows’un built-in alanları nelerdir bir bakalım;
CPFT_SMALL_TEXT: Size içine statik yazı yazıp kullanıcıya gösterebileceğiniz, izin verilen iki yazı boyutundan küçük olanına denk gelen bir label sağlar.
CPFT_LARGE_TEXT: Size içine statik yazı yazıp kullanıcıya gösterebileceğiniz, izin verilen iki yazı boyutundan büyük olanına denk gelen bir label sağlar.
CPFT_SUBMIT_BUTTON: Windows’a, kullanıcı kimlik bilgilerini verdiğimiz butondur. Web projelerindeki form post gibi düşünebilirsiniz.
CPFT_COMMAND_LINK: Bir link buton eklemek için kullanılır. Aslında form’da ikinci bir action button gibi kullanabilirsiniz. Bir event tetikler. Buna en yaygın örnek “Parolamı unuttum” olabilir.
CPFT_EDIT_TEXT: Kullanıcının giriş yapabileceği bir yazı alanı eklemek için kullanılır. Örneğin kullanıcı adı almak için kullanılabilir.
CPFT_COMBOBOX: Çoklu seçim alanı “drop down list” eklemek için kullanılır.
CPFT_CHECKBOX: Form’a checkbox eklemek için kullanılır. Sonucu true / false olan seçim alanınız için kullanabilirsiniz.
CPFT_PASSWORD_TEXT: Kullanıcıdan parola almak için eklenmesi iyi olur. Yazılan yazı dışarıdan okunamaz şekilde yazılır. Harici kişiler yazılan parolayı okuyamaz.
CPFT_TILE_IMAGE: Kimlik bilgisi sağlayıcınıza bir “Bitmap” image ayarlayabilirsiniz. Hazırladığınız formun üstünde gözükecektir. Buraya koyacağınız resim *.bmp (bitmap) formatında olmalıdır.
Yukarıda belirttiğim alanlar CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR adında bir structure yapısında barındırılacaktır ve her alanın benzersiz uint cinsinden bir ID’si, ihtiyaca göre string cinsinden bir başlık yazısı (label) ve Guid cinsinden bir GUID belirteci olacaktır. Bu alanları ayarladıktan sonra, görünüm olarak kimlik bilgisi sağlayıcımız görüntüde hazır olur.
Şunu iyi anlamak gerekiyor, implement ettiğiniz interface, windows sizin implemented metodlarınızı ihtiyaç anında çağırabilsin diyedir. Buradaki çağırılma sırasını ve çağırılma durumlarını değiştiremezsiniz.
Sadece daha anlaşılır olması açısından, ICredentialProvider implementasyonundaki bence önemli olan bir iki metoda değineceğim;
SetUsageScenario: Bizim kimlik bilgisi sağlayıcımızın, hangi senaryolarda kullanıcıya gösterileceğini belirttiğimiz metoddur. LogonUI bu metodu çağırdığında, burada senaryoyu ayarlarız.
GetCredentialAt: Burada ICredentialProviderCredential implementasyonu olan sınıfımızdan bir instance oluşturup, windows’a döneriz. Bu bizim oluşturacağımız (form) ikinci sınıftır ve kullanıcı bilgileri bu sınıf içinden toplanacaktır.
Bunların haricinde birkaç metod daha var fakat akışı oluşturabileceğiniz veya değiştirebileceğiniz en önemli ikisi bunlar.
ICredentialProviderCredential implementasyonundaki gene önemli birkaç metod;
GetFieldState: Bu metod, LogonUI’da oluşturacağınız tüm user interface komponentleri için bir kez çalışacaktır. Burada ilgili komponentin durumunu ayarlayacağız. Örneğin; focused, disabled, hidden, readonly gibi…
GetBitmapValue: Bu metod, LogonUI’a koyduğumuz CPFT_TILE_IMAGE komponentinin içine resim koymak için kullanılacaktır. Burada .bmp dosyamızı doğru şekilde memory’e yüklememiz ve geri dönmemiz gerekecektir.
SetStringValue: Bu metod, bir CPFT_EDIT_TEXT veya CPFT_PASSWORD_TEXT içinde klavye tuşlarına bastığımızda, basılan her karakter için LogonUI tarafından çağrılacak olan metoddur. LogonUI’da bir text box içindeki değeri doğrudan okuyamazsınız. Bu değerleri her tuş basıldığında kendi yöntemimiz ile memory’de oluşturmamız gerekecektir.
CommandLinkClicked: Bu metod, eğer oluşturduğunuz formda bir CPFT_COMMAND_LINK varsa, basıldığında LogonUI tarafından çağrılacak metoddur. Aslında ek bir action butonu olarak düşünebiliriz. Genelde bir web sayfası açar ve açılan sayfadan parola değişikliği yapılabilir (Şifremi unuttum senaryosu)
GetSerialization: Bu metod sistemdeki en önemli ve son nokta metoddur. Tüm olay burada sonlanır ve kullanıcıdan topladığımız tüm veriler memory’de bir alana gerekli metodlar aracılığı ile konulur. Bu işlem Credential Pack Authentication Buffer olarak adlandırılabilir. Winlogon memory’deki bu alanı okuyarak kullanıcıyı doğrular ve windows session’ını açar. İşte yazacağımız C++ kütüphaneyi kullanacağımız, ayrıca çoklu kimlik doğrulama için araya girmemiz gereken yer tam da burasıdır.
Neden C++ Kütüphane Geliştirmemiz Gerekiyor?
LogonUI’da kullanıcıdan aldığımız giriş bilgilerini memory’e koyarken C# içinden DllImport ile kullanmamız gereken “CredPackAuthenticationBuffer” isimli metod sadece “InteractiveLogon” ticket’ı vermektedir. Bilindiği üzere Interactive logon veya unlock, kullanıcının bilgisayarın başında olup giriş yaptığı senaryodur. Yani eğer geliştirdiğimiz kimlik bilgisi sağlayıcıyı remote session’lar için de kullanmak istiyorsak bu metod işe yaramayacaktır. İşletim sisteminden alacağımız ticket structure’ı yapısındaki “MessageType” alanı her zaman “InteractiveLogon” olacaktır. Bu durumda remote unlock veya logon senaryoları desteklenemeyecektir.
Çözüm olarak, Microsoft’un GitHub sayfasında bulunan “Windows-classic-samples” altındaki “CredentialProvider” projesinden ihtiyacımız olan iki adet metodu ayırıp, ihtiyacımız olan değişiklikleri yaptıktan sonra DLL exportable olacak şekilde ayrı bir C++ projesinde derleyip kullanmamız gerekecektir. Bu iki ana metodun isimleri “KerbInteractiveUnlockLogonInit” ve “KerbInteractiveUnlockLogonPack” . Çıkarttığımız DLL’i C# içinden DllImport ile alıp kullanmamız gerekecektir. Bu metodları kullanırken tip dönüşümlerine dikkat etmemiz gerekecektir. Bazı durumlarda metodlar hata vermeyecek fakat logon ticketi de gerçerli olmayacaktır.
LogonUI Üzerinde Bir C# Form Nasıl Açabiliriz?
Bir windows form uygulaması, bu uygulamayı çalıştıran kişinin contextinde ve o kişinin yetki scope’u içinde çalışır. Aktif bir windows oturumu gerektirir kısacası. LogonUI senaryosuna baktığımızda, henüz windows oturumu açılmamış, yani arka tarafta aktif bir kullanıcı oturumu mevcut değil. Fakat, C#’ın bize sunmuş olduğu ve form açarken verebileceğimiz bir IWin32Window owner parametresi bulunmaktadır. Bunun anlamı, yeni açılacak olan form, verilen IWin32Window nesnesi contexti içinde yaşasın anlamına gelir. Bunu yapabilmemiz için öncelikle LogonUI process’inin handle bilgisine ihtiyacımız olacak ki LogonUI process context’i içinde yaşayan bir form açabilelim. Bu değeri ICredentialProvider implementasyonu yapan sınıfımız içindeki bir metod yardımı ile alabiliriz. Aldığımız değeri de açacağımız form’un owner’ı olarak atadığımızda form LogonUI ekranı önünde açılacaktır. Açtığımız form network’e ve İnternete erişim sağlayabilir. Bundan sonrası yaratıcılığınıza kalmış...
Sonuç olarak
Bu yapıları hiç bilmeyen yazılım geliştiriciler için çok zor görünen bu konuyu en azından bir fikir vermesi için kendi uyguladığım kadarı ve basit hatları ile çok detaya girmeden anlatmaya çalıştım. Evet biraz karmaşık ve anlaması zor bir konu fakat mümkün. Bu bir ARGE projesi. Sürekli geliştirilebilecek, yeni öğrenilecek özellikleri mevcut. Bazı örnek ekranları aşağıda bulabilirsiniz.
Last updated