PowerShell Core: Get cert object from PFX file - linux

Using PowerShell on Linux, how do I get an X509Certificate2 object from a PKCS#12 file (aka PFX file) ?
On Windows, it's done like this:
$cert = (Get-PfxData -FilePath "mycert.p12" -Password $secstr).EndEntityCertificates[0]
# Pass cert object to some cmdlet, in this case MS Graph API:
Connect-MgGraph -Certificate $cert .....
The problem is that the PKI module is not available for Linux. I tried to dig into the background for all of this mess, and I think it is a consequence of the shortcuts that .NET Core is taking when it claims to be "cross-platform". (hint: it ain't true cross-platform as Microsoft has decided to rely on OS specific libraries for a lot of the security related stuff, unlike for example the route Java is taking). When the background .NET features are not there then consequently it is not possible for Microsoft to create the parity on PowerShell either.
Apart from my rant, can you answer my question? How would I do it?
Update (solution)
As per Toni's answer this will work on Linux:
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("myfile.p12",$secstr)
(not aware that there's any PowerShell binding for this, calling directly into .Net from PowerShell is not my favorite, but it does the job)
The PKCS#12 format allows a file to include an unlimited number of certificates. I'm assuming that the above will only work when the PKCS#12 file only has one certificate (chain) in it or that the behaviour in case of multiple certificate chains is un-specified. But one certificate (chain) is most typically the case. Or at least the case in my scenario.
The above works for me using Linux and PowerShell 7.2. To be certain I've tested with a .p12 file not created by PowerShell.

On Windows if the certificate is stored in the certificate store you can access the cert by using get-childitem, e.g.:
$cert = Get-ChildItem -Path 'Cert:\LocalMachine\MY' | ?{$_.Thumbprint -eq $thumbprint}
If the certificate is stored plain on the disk I think its the same for Linux and Windows:
#Without Password
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2([path])
#With Password
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2([path],[password])
X509Certificate2 Class is part of the .NET Core, so it works on Linux too.
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2?view=netcore-3.1
Alternatively you can also use a password as secret to connect to Azure by using a ServicePrincipal, e.g.:
$tenandId = #fill the Id of your Tenant here
$clientId = #fill in the Id of the ServicePrincipal here
$secret = #fill in the password of the ServicePrincipal
#HashTable used for splatting
$paramsHt = #{}
$body = #{
Grant_Type = 'client_credentials'
Scope = 'https://graph.microsoft.com/.default'
Client_Id = $ClientID
Client_Secret = $Secret
}
#Establish connection and obtain Bearer Token
$con = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantid/oauth2/v2.0/token" -Method 'Post' -Body $body
#Get Token issued
$token = $con.access_token
#Add token to HashTable
$paramsHT.add('AccessToken',$token)
#Connect to GraphAPI
Connect-MgGraph #paramsHT
But by doing so you have to handle the token refresh by yourself.

Related

How to retrieve Azure Service Principal's secret in Powershell 7

I recently started using PowerShell 7.x and I've came around some problems. I am not able to retrieve the secret of my service principal when I create it through PowerShell 7. The return body does not give the "Secret" property. I used to work with PowerShell 5.x and I used to get a "Secret" property in the return object after creating a service principal. I've added the screenshots of creating service principal through both PowerShell 7.x and PowerShell 5.x.
As you can see while working with Powershell 5 I could just use an object and save the returned object in it and access the secret like:
$sp = New-AzADServicePrincipal -DisplayName "xyz"
$secret = $sp.Secret
$plainSecret = convertFromSecureString $secret
convertFromSecrureString is just a basic function which converts the secret to plain text.
But I cannot use the same approach with PowerShell 7. How can I retrieve the secret?
$sp = New-AzADServicePrincipal -DisplayName "xyz" $secret = $sp.Secret
$plainSecret = convertFromSecureString $secret
We have tested the above shared cmdlets in our local environment which has PowerShell running with different versions 5.1 & 7.2.
Using those cmdlets we are able to create the service principal & able to see the same properties in the output in either of both versions.
Here is the sample output screenshot for reference :
New-AzADServicePrincipal returns the IMicrosoftGraphServicePrincipal structure, which didn't match the example code.
IMicrosoftGraphServicePrincipal Interface (latest PS version)
Here is the code that works for me:
Connect-AzAccount -Tenant 'TENANT_ID' -Subscription 'SUBSCRIPTION_ID'
$sp = New-AzADServicePrincipal -DisplayName $Name
$clientsec = [System.Net.NetworkCredential]::new("", $sp.passwordCredentials.secretText).Password
$jsonresp =
#{clientId=$sp.appId
clientSecret=$clientsec
}
$jsonresp | ConvertTo-Json

Azure - Access token request with a certificate - postman

i want to test access to key vault using certificate
Scenario
Second case: Access token request with a certificate
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
I am struggling to supply
client_assertion
An assertion (a JSON web token) that you need to create and sign with the certificate you registered as credentials for your application. Read about certificate credentials to learn how to register your certificate and the format of the assertion.
I dont know why powershell has to bes used, and I dont have pfx, so cant use
https://blogs.aaddevsup.xyz/2020/10/how-to-use-postman-to-perform-a-client-credentials-grant-flow-with-a-certificate/
Is it possible to generate signed JWT using postman?
Note: Certificates in postman added. so that part is taken care
I don't think you can generate client_assertion directly in postman, please use the script below to create a self-assigned certificate, then you can use the script you mentioned to get the token.
$certroopath = "C:\Users\Administrator\Desktop"
$certname = "mycert1"
$certpassword = "P#ssw0rd1234"
$cert = New-SelfSignedCertificate -DnsName "$certname" -CertStoreLocation cert:\CurrentUser\My
$pwd = ConvertTo-SecureString -String $certpassword -Force -AsPlainText
$certwithThumb = "cert:\CurrentUser\my\"+$cert.Thumbprint
$filepath = "$certroopath\$certname.pfx"
Export-PfxCertificate -cert $certwithThumb -FilePath $filepath -Password $pwd

How to obtain certificate issuer thumbprint?

I'm currently attempting to deploy a Service Fabric cluster using the instructions provided here. I've successfully created a certificate using Let's Encrypt and am able to successfully handle all the instructions except for obtaining the certificateIssuerThumbprint value, as indicated as required in the parameters file at the top of this link.
Looking at the certificate details in my Certificate Manager, I don't see a field providing this value. I read through the Chain of Trust page for Let's Encrypt on which I'd expect to find such a value, but I'm not seeing it.
How would I go about looking up what this certificate issuer thumbprint value is?
Thank you!
$secret = Get-AzKeyVaultSecret -VaultName $vault -SecretName $secretName -Version $version
$certBytes = [Convert]::FromBase64String($secret.SecretValueText)
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certBytes, $null, 'Exportable')
$certChain = [System.Security.Cryptography.X509Certificates.X509Chain]::new()
$certChain.Build($cert)
$certificateIssuerThumbprint = ($certChain.ChainElements.Certificate | Where-Object {$_.Subject -eq $cert.Issuer}).Thumbprint
I'm far from an expert on certificates, but here's what I think needs to be done:
In certificate manager, if you simply double click and open the certificate > Certification Path tab, you should see your certificate at the bottom (as a leaf node), and in the Details tab you will see your certificate's thumbprint
There should be a certificate above your certificate - I believe this is the issuer certificate. If you double click that issuer certificate > Details, I think that's the thumbprint you need
#mperian's answer is absolutely spot-on if the certificate lives in an Azure Key Vault.
In my case, the certificates are installed to the stores on the various machines. Building on their excellent answer, here's the Powershell to do the same locally without use of Azure Key Vault.
$thumbprint = ""
$store = "My"
$cert = Get-ChildItem -Path "Cert:\LocalMachine\$store" | Where-Object {$_.Thumbprint -match $thumbprint}
$certChain = [System.Security.Cryptography.X509Certificates.X509Chain]::new()
$certChain.Build($cert)
$certIssuerThumbprint = ($certChain.ChainElements.Certificate | Where-Object {$_.Subject -eq $cert.Issuer}).Thumbprint
You should be able to see that value after you upload it to Keyvault using the script in the link you provided
https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-create-cluster-using-cert-cn#upload-the-certificate-to-a-key-vault
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser -Force
$SubscriptionId = "<subscription ID>"
# Sign in to your Azure account and select your subscription
Login-AzAccount -SubscriptionId $SubscriptionId
$region = "southcentralus"
$KeyVaultResourceGroupName = "mykeyvaultgroup"
$VaultName = "mykeyvault"
$certFilename = "C:\users\sfuser\myclustercert.pfx"
$certname = "myclustercert"
$Password = "P#ssw0rd!123"
# Create new Resource Group
New-AzResourceGroup -Name $KeyVaultResourceGroupName -Location $region
# Create the new key vault
$newKeyVault = New-AzKeyVault -VaultName $VaultName -ResourceGroupName $KeyVaultResourceGroupName -Location $region -EnabledForDeployment
$resourceId = $newKeyVault.ResourceId
# Add the certificate to the key vault.
$PasswordSec = ConvertTo-SecureString -String $Password -AsPlainText -Force
$KVSecret = Import-AzureKeyVaultCertificate -VaultName $vaultName -Name $certName -FilePath $certFilename -Password $PasswordSec
$CertificateThumbprint = $KVSecret.Thumbprint
$CertificateURL = $KVSecret.SecretId
$SourceVault = $resourceId
$CommName = $KVSecret.Certificate.SubjectName.Name
Write-Host "CertificateThumbprint :" $CertificateThumbprint
Write-Host "CertificateURL :" $CertificateURL
Write-Host "SourceVault :" $SourceVault
Write-Host "Common Name :" $CommName
From Create an SF cluster using certificates declared by CN:
"Note
The 'certificateIssuerThumbprint' field allows specifying the expected issuers of certificates with a given subject common name. This field accepts a comma-separated enumeration of SHA1 thumbprints. Note this is a strengthening of the certificate validation - in the case when the issuer is not specified or empty, the certificate will be accepted for authentication if its chain can be built, and ends up in a root trusted by the validator. If the issuer is specified, the certificate will be accepted if the thumbprint of its direct issuer matches any of the values specified in this field - irrespective of whether the root is trusted or not. Please note that a PKI may use different certification authorities to issue certificates for the same subject, and so it is important to specify all expected issuer thumbprints for a given subject.
Specifying the issuer is considered a best practice; while omitting it will continue to work - for certificates chaining up to a trusted root - this behavior has limitations and may be phased out in the near future. Also note that clusters deployed in Azure, and secured with X509 certificates issued by a private PKI and declared by subject may not be able to be validated by the Azure Service Fabric service (for cluster-to-service communication), if the PKI's Certificate Policy is not discoverable, available and accessible."
Note that upon renewing (or re-keying) a certificate, the issuer may well change. A PKI would typically publish all its active issuers in a Certification Practice Statement (CPS) (here is LetsEncrypt's - unfortunately it doesn't seem to list the issuer certificates.)
If you're internal to Microsoft, you would probably know which API to use to retrieve authorized issuers; if you aren't, please contact the PKI of your choice for guidance.

How do I access the GRAPH API to get all users WITHOUT having to login ?

What I want is simple but I have not found a clear answer.
I have a simple console app and all I want to do is get all the users in my Azure AD using the new GRAPH API. All the examples I have require the program login (OAuth?). I don't want that. I want to give the code the user/pw and simply start calling the methods.
Whenever you have a user physically sitting at the device, your best bet, by far, is to invoke the full sign-in flow. Not only does keep an admin's credentials from being exposed, but it also allows the user to change password if needed, invoke multi-factor authentication, etc.
However, there are some scenarios where you want an entirely unsupervised service running on a completely secure and trusted machine. (Known as a "confidential client" in OAuth 2.0.) This can be achieved with the OAuth 2.0 Client Credentials Grant flow, which uses only the application's credentials to authenticate. This is illustrated in Service to Service Calls Using Client Credentials.
Using ADAL, this flow is invoked by using either AuthenticationContext.AcquireToken(String, ClientCredential) (where your credential is an password credential--a string), or AuthenticationContext.AcquireToken(String, ClientAssertionCertificate) (where your credential is a certificate that you use to sign an assertion). There is a .NET (C#) sample for each of these on the Azure AD samples for daemon applications:
Calling web APIs in a daemon or long-running process
Authenticating to Azure AD in daemon apps with certificates
Using PowerShell and certificate authentication, it would look something like this:
$appId = "<app client ID>"
$resource = "https://graph.windows.net" # (or other resource URI)
$tenantId = "<domain name or ID>"
$certThumbprint = "<certificate thumbprint>"
# Get locally-installed cert by thumbprint
$x509cert = Get-ChildItem "Cert:\LocalMachine\My" | ? { $_.Thumbprint -eq $certThumbprint } | Select-Object -First 1
# Get access token using ClientAssertionCertificate
$authority = "https://login.microsoftonline.com/$tenantId"
$creds = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate $appId, $x509cert
$authContext = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext $authority
$authResult = $authContext.AcquireToken($resource, $creds)
# Make Graph API request to list all users
$header = #{
"Authorization" = "Bearer $($authResult.AccessToken)"
"Content-Type" = "application/json"
}
$result = Invoke-RestMethod -Method Get -Headers $header -Uri "https://graph.windows.net/$tenantId/users?api-version=1.6"
($result.Content | ConvertFrom-Json).value
You will need to ensure your application is registered in Azure AD, and has the minimum required application permissions for what you're trying to do (and not more than that, to limit your exposure if the app's credentials were to be compromised). For example, if your application only needs to read directory data (e.g. to find a user by email address), you would set the permissions like this:
Here is the PowerShell script I wrote.
# Adding the AD library to your PowerShell Session.
Add-Type -Path 'C:\Program Files\Microsoft Azure Active Directory Connect\Microsoft.IdentityModel.Clients.ActiveDirectory.dll'
# This is the tenant id of you Azure AD. You can use tenant name instead if you want.
$tenantID = "<the tenant id of Azure AD>"
$authString = "https://login.microsoftonline.com/$tenantID"
# Here, the username must be MFA disabled user Admin at least, and must not be a live id.
$username = "<the username of the AD's Admin>"
$password = "<the password of the above user>"
# The resource URI for your token.
$resource = "https://graph.windows.net/"
# This is the common client id.
$client_id = "1950a258-227b-4e31-a9cf-717495945fc2"
# Create a client credential with the above common client id, username and password.
$creds = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential" `
-ArgumentList $username,$password
# Create a authentication context with the above authentication string.
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" `
-ArgumentList $authString
# Acquire access token from server.
$authenticationResult = $authContext.AcquireToken($resource,$client_id,$creds)
# Use the access token to setup headers for your http request.
$authHeader = $authenticationResult.AccessTokenType + " " + $authenticationResult.AccessToken
$headers = #{"Authorization"=$authHeader; "Content-Type"="application/json"}
# Get the users.
Invoke-RestMethod -Method GET -Uri "https://graph.windows.net/$tenantID/users?api-version=1.6"
If you are using C#, it would be really similar, because my script is actually translated from C# code. For other Programing language, there are similar APIs in the corresponding Azure SDK. If not, you might consider using OAuth2.

How to add application to Azure AD programmatically?

I want to automate the creation of my application in Azure AD and get back the client id generated by Azure AD.
Are there PowerShell commandlets to do this? Is there some other means, like an API of doing this besides the management console?
Can you point me to an example?
Thanks!
There are a number of ways you can create an application in AAD Programatically. I will briefly cover two different ways you can go about doing this: PowerShell CMDLETs and the Graph API. In general, I would strongly reccommend using the Graph API for this.
PowerShell:
There are a few different modules running around that have the ability to create AAD Applications/Service Principals. If you need to create a new application object in your tenant, you can use Azure PowerShell to make the following call:
https://msdn.microsoft.com/en-us/library/mt603747.aspx
PS C:\> New-AzureRmADApplication -DisplayName "NewApplication" -HomePage "http://www.Contoso.com" -IdentifierUris "http://NewApplication"
If you need to create a service principal for your application in your tenant you can use Azure AD PowerShell:
https://msdn.microsoft.com/en-us/library/azure/jj151815.aspx
https://msdn.microsoft.com/en-us/library/azure/dn194119.aspx
New-MsolServicePrincipal -ServicePrincipalNames #("MyApp/Contoso.com") -DisplayName "My Application"
Graph API:
(recommended)
You can also create applications by making a POST to our Graph API:
https://msdn.microsoft.com/Library/Azure/Ad/Graph/api/entity-and-complex-type-reference#ApplicationEntity
We have samples that show how you can register and create an applicatoin to target the Graph API, and use the Graph Client Library to assist you in making the correct calls to the API:
https://github.com/AzureADSamples/WebApp-GraphAPI-DotNet
I hope this helps!
I'm a little late to the party - but I recently encountered this challenge too. Here are the relevant excerpts from my solution...
First you need to get the authentication token. For this you can use this handy function.
function GetAuthToken
{
param
(
[Parameter(Mandatory=$true)]
$TenantName
)
$adal = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
$adalforms = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll"
[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null
$clientId = "1950a258-227b-4e31-a9cf-717495945fc2"
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
$resourceAppIdURI = "https://graph.windows.net"
$authority = "https://login.windows.net/$TenantName"
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId,$redirectUri, "Auto")
return $authResult
}
(borrowed from Paulo Marques https://blogs.technet.microsoft.com/paulomarques/2016/03/21/working-with-azure-active-directory-graph-api-from-powershell/)
You can then submit a POST request to the Azure Active Directory Graph API in order to create your application. However there is a little setup required.
# The name of this AAD instance
$global:tenant = "mycompany.onmicorosft.com"
$global:aadSecretGuid = New-Guid
$global:aadDisplayName = "azure-ad-displayname"
$global:aadIdentifierUris = #("https://contoso.com")
$guidBytes = [System.Text.Encoding]::UTF8.GetBytes($global:aadSecretGuid)
$global:aadSecret = #{
'type'='Symmetric';
'usage'='Verify';
'endDate'=[DateTime]::UtcNow.AddDays(365).ToString('u').Replace(' ', 'T');
'keyId'=$global:aadSecretGuid;
'startDate'=[DateTime]::UtcNow.AddDays(-1).ToString('u').Replace(' ', 'T');
'value'=[System.Convert]::ToBase64String($guidBytes);
}
# ADAL JSON token - necessary for making requests to Graph API
$global:token = GetAuthToken -TenantName $global:tenant
# REST API header with auth token
$global:authHeader = #{
'Content-Type'='application/json';
'Authorization'=$global:token.CreateAuthorizationHeader()
}
Now you can hit the Graph API.
$resource = "applications"
$payload = #{
'displayName'=$global:aadDisplayName;
'homepage'='https://www.contoso.com';
'identifierUris'= $global:aadIdentifierUris;
'keyCredentials'=#($global:aadSecret)
}
$payload = ConvertTo-Json -InputObject $payload
$uri = "https://graph.windows.net/$($global:tenant)/$($resource)?api-version=1.6"
$result = (Invoke-RestMethod -Uri $uri -Headers $global:authHeader -Body $payload -Method POST -Verbose).value
Once the response comes back, you can extract the configuration values you need.
# Extract configuration values
$keyObject = foreach($i in $result.keyCredentials) { $i }
# Tenant ID
$global:aadTenantId = Get-AzureRmSubscription | Select-Object -ExpandProperty TenantId
# Application object ID
$global:aadApplicationObjectId = $result | Select-Object -ExpandProperty objectId
# App ID / Client ID
$global:aadClientId = $result | Select-Object -ExpandProperty appId
# Application Secret/Key
$global:aadAppSecret = $keyObject | Select-Object -ExpandProperty keyId
I hope this helps somebody!
Microsoft has released a couple of additional PowerShell cmdlets to register an app and set credentials:
New-AzureRmADApplication
New-AzureRmADServicePrincipal
New-AzureRmRoleAssignment
Add-AzureADApplicationCredential
Please review their documentation:
https://learn.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal
I've written some powershell scripts which will
Create AAD applications (thanks mainly to Matt's answer)
Create a Key Vault in Azure
Create a key in the Key Vault
Assign permissions to the key vault for the AAD applications
I know this is more than what you're asking for, but if, like me, you're interested in getting back the secret (aka key) from the application (the same one you add in the portal which you have to copy before never seeing it again), then the second script will allow you to explicitly send that in as part of the payload in a call to the Graph API. The script will save that to a file for you to refer to later.
The other scripts are not really what you're asking about, but you may still find them useful if you ever need to set up SQL Server to work with Azure Key Vault for TDE or column-level encryption.

Resources