I'm working on a small work order app with a database back end for our Help Desk. Part of it is tracking some basic information about my organization's laptops (Manufacturer, Model, Serial Number, who it's assigned to, etc). I would use a real programming language like C# or Java, but for reasons dictated by people over my head, I'm stuck with using what is available built into Windows 10 Enterprise, so PowerShell with WPF.
Our network has a Windows domain with a large Active Directory forest and smart card authentication. What I would like to do, if possible, is have the user select their smart card certificate (the user using this app will be different than the user who logged into Windows i.e. there will be multiple smart cards inserted) with a UAC prompt or Get-Credential prompt. Entering their pin is not a requirement, though it would be nice to confirm their identity. All I want is to retrieve some basic information from the certificate/card they select, such as display name and email address. I'll be using the email address to query my database for other information such as which laptop(s) they're assigned. I would like to avoid doing an Active Directory lookup if possible, but that option is not completely off the table.
Below are a few things I have found but they all are sort of partial solutions to what I'm trying to do and I'm not sure how to put it all together. Get-Credential prompts the user to pick a smart card and enter their pin, which does what I'm looking for up front, but in the back it returns a PSCredential object that contains a username (coded somehow, but I can't find which encoding is used, or maybe it's a UID) and SecureString password (not validated, the user can leave this blank or enter anything). I don't know what to do with this to get the information I want. Get-ADUser doesn't seem to be able to return a user object using a PSCredential object as identity. Is there something I am missing or not understanding about this? Is what I'm trying to do possible?
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/get-credential?view=powershell-5.1
View All Certificates On Smart Card
https://www.akaplan.com/blog/2013/10/get-users-mailaddress-from-smartcard-with-powershell/
https://blogs.msdn.microsoft.com/alejacma/2010/12/15/how-to-enumerate-all-certificates-on-a-smart-card-powershell/
This last link seems like it would work but I'm not sure how to put it into use. The documentation is very sparse.
If I wanted to work with certificates based on the smart cards inserted at the time I would use certutil.exe to pull all of the smart card info. Then grab the certificate serial numbers from the resultant text and query the CurrentUser\MY certificate store matching the serial numbers. Once I had the certificates I would pass that info to Out-GridView with the -OutputMode Single parameter to allow the user to select a certificate. From there you have the user's info based on the certificate shown.
$SCSerials = certutil -scinfo -silent | Where{$_ -match 'Serial Number: (\S+)'} | ForEach {$Matches[1]}
$SelectedThumb = Get-ChildItem Cert:\CurrentUser\my | Where{$_.SerialNumber -in $SCSerials} | Select Subject,Issuer,NotBefore,NotAfter,Thumbprint | Out-GridView -Title 'Select a smartcard certificate.' -OutputMode Single |% Thumbprint
$UserCert = Get-Item Cert:\CurrentUser\My\$SelectedThumb
Then $UserCert.Subject is the distinguished name of the user and you can use that to query AD or whatever you want.
Related
I want to manage mailbox contacts in Exchange online using EWS 2.2 in PowerShell. I am able to create, delete and modify (most of the properties)
But I can not set any PhoneNumber (BusinessPhone, MobilePhone, HomePhone) to an empty value. Tried $updateItem.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = $null;
and a lot of variations. Always get: "An object within a change description must contain one and only one property to modify."
I saw this post and using the code from there does not create any error message, but the phone number also isn't empty: EWS Delete PhoneNumber Entry on Contact
Any ideas how to set these field empty?
I'm trying to write a powershell command that checks to see if a user is part of an AD Group, however, I don't want to use the RSAT modules, as this may end up being a logon script (and we don't want users having those modules installed). This did lead me to this question, Search AD with PowerShell without using AD module (RSAT), however, I can't figure out how to filter the results check it the value is in there.
For example, the below does return a list of users, in LDAP form, for the group IT, but how do I then check a specific user (with their Username, not display name) is in there?
([System.DirectoryServices.DirectorySearcher]"(&(objectCategory=group)(name=IT))").FindOne().Properties["Member"]
FindOne() despite what it says as well, returns multiple rows; in fact FindAll() and FindOne() both return the same results.
Should I be using a different command to search AD? Specifically I want to either check an AD group contains a user (the current user), or the inverse, check a user (the current user) is a member of a particular AD group.
You can do it that way if you really need to (and I can help you do it that way if you really need) but if you are going to be running this script under the credentials of the user you are interested in, then you can get all the groups from the user's login token. That already contains a recursive list of all security groups that the user is in. (It won't include groups where the 'Group type' is "Distribution")
The login token contains a list of SIDs, so the absolute fastest way is to compare using the SID of the group you are interested in, since it won't have to make any network request at all. That's especially convenient for laptop users who may not be online when they login - your script would still work.
$currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
if ($currentIdentity.Groups.Where({$_.Value -eq "S-1-1-0"}, "First")) { #Is in "Everyone"?
"Yes"
} else {
"No"
}
To find the SID of a group, use this:
(Get-ADGroup "GroupName").SID.Value
Then copy/paste that value into the script.
If you would prefer to use the name of the group in the script, then you can convert it to a WindowsPrincipal and use IsInRole. However, this will need to make a network request to find the group by its name.
$currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
$currentPrincipal = New-Object System.Security.Principal.WindowsPrincipal($currentIdentity)
if ($currentPrincipal.IsInRole("Everyone")) {
"Yes"
} else {
"No"
}
So any one who has used perl dancer knows that to authenticate a user on login you can call authenticate_user
authenticate_user(
params->{username}, params->{password}
);
This is part of the Auth::Extensible plugin.
To me it looks like it encourages the use of storing passwords in plain text! Sure you can hash the password first then make sure the stored password is the same hash but this seems to be more of a work around and i found isn't guaranteed to work. I have only got this to work using sha1 which shouldn't be used. I want to use Bcrypt but the passphrase simply wont match. Possibly odd characters not matching i'm not sure.
The thing is using the dancer Passphrase plugin i can already validate the username and password without even needing to rely on authenticate_user to verify them. But for the dancer framework to consider the user logged in you still have to call authenticate_user which must be passed the password.
I'm completely stuck. I'm curious how other people have managed to use proper password management in dancer2?
Firstly, I'll echo the "you almost certainly don't need to be using authenticate_user()" comments. The plugin can handle all that for you.
However, "it doesn't hash it" is wrong; here's how it works. The
authenticate_user keyword loops through all auth realms configured, and for
each one, asks that provider's authenticate_user() method to see if it accepts
the username and password. The Database provider (and the others) fetch the
record from the DB, and use $self->match_password() (which comes from the
Provider role) to validate it; that code checks if the stored password from
the database starts with {scheme} and if so, uses
Crypt::SaltedHash->validate to validate that the user-supplied password (in
plain text, as it's just come in over the wire) matches the stored, hashed
passsword ($correct in the code below is the stored password):
if ( $correct =~ /^{.+}/ ) {
# Looks like a crypted password starting with the scheme, so try to
# validate it with Crypt::SaltedHash:
return Crypt::SaltedHash->validate( $correct, $given );
}
So, yes, if your stored password in the database is hashed, then it will match
it if the password supplied matches that hash.
For an example of what a stored hashed password should look like, here's
the output of the bundled generate-crypted-password utility:
[davidp#supernova:~]$ generate-crypted-password
Enter plain-text password ?> hunter2
Result: {SSHA}z9llSLkkAXENw8FerEchzRxABeuJ6OPs
See the Crypt::SaltedHash doco for details on which algorhythms are
supported by it, and the format it uses (which "comes from RFC-3112 and
is extended by the use of different digital algorithms").
Do bear in mind that the code behind authenticate_user is exactly what's used
under the hood for you.
For an example of just letting the plugin do the work for you, consider:
get '/secret' => require_login sub {
my $user = logged_in_user();
return "Hi, $user->{username}, let me tell you a secret";
};
... that's it. The require_login means that the plugin will check
if the user is logged in, and if not, redirect them to the login page
to log in. You don't need to call authenticate_user yourself, you
don't need to set any session variables or anything. logged_in_user()
will return a hashref of information about the logged in user (and because
the route code has require_login, there's guaranteed to be one at this
point, so you don't need to check).
If you need to check they have a suitable role, instead of just that they
are logged in, then look at require_role in the documentation instead.
In the documentation for Dancer2::Plugin::Auth::Extensible, the description for authenticate_user() says:
Usually you'll want to let the built-in login handling code deal with authenticating users, but in case you need to do it yourself, this keyword accepts a username and password ...
Which strongly implies to me that you shouldn't be calling this function at all unless you're doing something particularly clever.
I haven't used this module myself, but it seems to me that all the hashing and encryption stuff should be handled by one of the authentication providers and if there's not one that covers the case you use, then you can write one yourself.
Whenever I need to store secure passwords for a Dancer app, I reach for Dancer2::Plugin::Passphrase. I wonder if I should consider writing an Auth::Extensible style authentication provider for it.
I have created a client side app using JavaScript connected to a Firebase database where a user can login and save/edit some data stored Firebase. 'Email and Password' authentication is used as https://www.firebase.com/docs/web/guide/login/password.html
I subsequently wanted to write a Powershell script which would be setup with 'Task Scheduler' to run 1x per day, read each users data and execute some business logic.
I incorrectly expected to be able to whitelist my Server IP to get full access rights to the DB.
If I understood it correctly I need to use 'Custom authentication' using 'JSON Web Tokens (JWTs)', but there are no helper libraries available for Powershell. Had a look at this section https://www.firebase.com/docs/web/guide/login/custom.html#section-tokens-without-helpers but it's not clear to me what needs to be done to get the token.
Can someone give me some pointers or sample code on how to get JWT to work with Firebase/Powershell, or some alternate ways I can get full access to the BD using Powershell?
Thanks in advance
Quintus
I did something that might help you ...
#region TokenGenerator
function TokenGeneretor($secret){
$asm = [Reflection.Assembly]::LoadFile("D:\Firebase\FirebaseTokenGenerator.dll")
$tokenGenerator = [Firebase.TokenGenerator]::new("$secret")
$authPayload = New-Object "System.Collections.Generic.Dictionary``2[System.String,System.Object]"
$authPayload.Add('uid', '1')
$authPayload.Add('some', 'arbitrary')
$authPayload.Add('data', 'here')
$Option = [Firebase.TokenOptions]::new(((Get-Date).AddHours(1)),$null,$true)
$token = $tokenGenerator.CreateToken($authPayload, $Option)
return $token
}
#endregion
TokenGeneretor -secret "123"
The DLL mentioned is the code compiled from https://github.com/firebase/firebase-token-generator-dotNet. Just open the project in Visual Studio and have it compiled. It will play the DLL in the project's DEBUG folder.
Is there a way to successfully bind while leaving out one of the files in the bind search base string? I don't always know what $site is for a user and if I leave it out the binding fails. Can I have something like OU=*,
$ldapSearchBase = "OU=$site,OU=xxxx,OU=xxxx,DC=$globalLocation,DC=companyName,DC=com";
If I leave the site out I get. it works if I put in my correct site
The wrong password was supplied or the SASL credentials could not be processed
LDAP binds require you to have a single unique distinguished name plus the appropriate credentials (user/pass, SSL, etc.). Your bind will always fail if your DN is not unique.
You might want to try splitting your base DN and varying whatever $site is:
my $ldapSearchBase = "OU=user,OU=accounts,DC=$globalLocation,DC=companyName,DC=com";
my $ldapSite = "OU=$site";
my $bindString = $ldapSearch . "," . $ldapSearchBase;
Use the first container as search base:
$ldapSearchBase = "DC=$globalLocation,DC=companyName,DC=com";