mandag den 25. februar 2013

ASP.NET and/or MVC websites and Claims based Authentication

Implementing WIF on a website is pretty straight forward. There are ton’s of guide out there on how to do it …. By doing a bit of magic in web.config you can make any website support claims based authentication. ( even Exchange 2010 and Citrix  )

If you are a developer, implementing WIF on your website is just as easy. You install WIF SDK and run FedUtil.exe who will handle configuring your web.config for you …

But what if you want absolute control over the login process. You often end up, with a mix of stuff in web.config and some in code. So I decided to figure out, what it would take, to support getting a user signed on, without ever touching web.config.

You will need to handle 4 actions. Requesting login, Handle login, Handle sign-out and handling Cleanup. So lets start with requesting login. But before we can do that, we need to have a basic setup of WIF. WIF normally do most of it’s magic though 3-4 modules, that get’s its configuration though web.config, and is saved in a static (shared) variable you can access simply by typing

Dim fam As Microsoft.IdentityModel.Web.WSFederationAuthenticationModule = Microsoft.IdentityModel.Web.FederatedAuthentication.WSFederationAuthenticationModule

But we want to control. To make things simple, we function like this

Public Function GetAuthenticationModule() As WSFederationAuthenticationModule
Dim fam As New WSFederationAuthenticationModule
fam.ServiceConfiguration = New Microsoft.IdentityModel.Configuration.ServiceConfiguration()
' use this, to allow any realm
' fam.ServiceConfiguration.AudienceRestriction.AudienceMode = IdentityModel.Selectors.AudienceUriMode.Never
' Add known Audience's ( Realms )
' This would proberly come from a database or what ever
For Each Realm in my.settings.Realms
Dim AllowedUri = fam.ServiceConfiguration.AudienceRestriction.AllowedAudienceUris.Where(Function(x) x.AbsoluteUri = IDP.TargetRealm).FirstOrDefault()
If AllowedUri Is Nothing Then
fam.ServiceConfiguration.AudienceRestriction.AllowedAudienceUris.Add(New Uri(IDP.TargetRealm))
End If
Next
fam.ServiceConfiguration.IssuerNameRegistry = New CustomTrustedIssuerNameRegistry()

Dim certificate As System.Security.Cryptography.X509Certificates.X509Certificate2 = CustomSecurityTokenServiceConfiguration.Current.EncryptingCertificate
AttachCert(fam.ServiceConfiguration, certificate)
' If you later decide to let WIF set the identity and save a FedAuth cookie, use thise to allow it to work incase you got
' your website hosted in Azure. The reason is Azure's Loadbalencing are using true round robin and doesnt support Sticky sessions.
' So in order to make all websites able to read the cookie, you need to a "common" ground for reading the encrypted token
'Dim sessionTransforms As New List(Of CookieTransform)(New CookieTransform() {New DeflateCookieTransform(), New RsaEncryptionCookieTransform(certificate),
' New RsaSignatureCookieTransform(certificate)})
'Dim sessionHandler As New Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler(sessionTransforms.AsReadOnly())
'fam.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(sessionHandler)
Return fam
End Function

Private Function InlineAssignHelper(Of T)(ByRef target As T, value As T) As T
target = value
Return value
End Function
Sub AttachCert(configuration As Microsoft.IdentityModel.Configuration.ServiceConfiguration, certificate As System.Security.Cryptography.X509Certificates.X509Certificate2)
configuration.ServiceCertificate = certificate
Dim certificates = New List(Of System.IdentityModel.Tokens.SecurityToken)() From { _
New System.IdentityModel.Tokens.X509SecurityToken(Configuration.ServiceCertificate) _
}
Dim encryptedSecurityTokenHandler = TryCast((From handler In Configuration.SecurityTokenHandlers Where TypeOf handler Is Microsoft.IdentityModel.Tokens.EncryptedSecurityTokenHandler).First(), Microsoft.IdentityModel.Tokens.EncryptedSecurityTokenHandler)
configuration.ServiceTokenResolver = InlineAssignHelper(encryptedSecurityTokenHandler.Configuration.ServiceTokenResolver, System.IdentityModel.Selectors.SecurityTokenResolver.CreateDefaultSecurityTokenResolver(certificates.AsReadOnly(), False))
End Sub

We need to tell WIF the realm(s) we are running under right now, called Audience Uri.
We need to tell WIF what signing certificate's to allow tokens from, called Issuer Name Registry.
CustomTrustedIssuerNameRegistry is a class that will get “fed” the signing certificates when WIF is validating the tokens, and should return a name (any name) if the certificate was allowed. Code for that is further down


Perfect, so now we are ready to create a sign request ( what happens when a user clicks the “login” button or what ever)

Public Function CreateSignInRequestMessage(Issuer As String, Realm As String, Reply As String) As SignInRequestMessage
Dim fam = GetAuthenticationModule()
fam.Issuer = Issuer
fam.Realm = Realm
Dim signInRequest = New SignInRequestMessage(New Uri(fam.Issuer), fam.Realm) With { _
.AuthenticationType = fam.AuthenticationType, _
.Freshness = fam.Freshness, .Reply = Reply
}
Return signInRequest
End Function

So in the code for your “login button” you do something down the line of this


Dim STSLoginPage as string = "https://adfs.wingu.dk/adfs/ls/"
' This webpage, must be in the AllowedAudienceUris
Dim TargetRealm as string = "https://domain.com/MyAwsomeWebapp"
Dim returnURL as string = "https://domain.com/MyAwsomeWebapp/login.aspx"
' or if using MVC
Dim returnURL as string = "https://domain.com/MyAwsomeWebapp/login/signin/"

Dim signInRequest = WIFHelper.CreateSignInRequestMessage(STSLoginPage, TargetRealm, returnURL)
Dim redirURL As String = signInRequest.WriteQueryString()
Response.Redirect(redirURL)

That will kick the user over to the identity provider, and if all goes well, he will then return with a POST to the return URL, where we can now pickup he’s SAML token.
So on the login page (or MVC controller) its now time to read the token, we add a function to our module, than handles that part


Private Function GetSignInResponseMessage() As SignInResponseMessage
Dim ctx = System.Web.HttpContext.Current
Dim req = ctx.Request
Dim message As SignInResponseMessage = WSFederationMessage.CreateFromFormPost(req) 'as SignInResponseMessage
Return message
End Function

Public Function GetWIFPrincipal(ByRef Issuer As String) As Microsoft.IdentityModel.Claims.ClaimsPrincipal 'System.Security.Principal.IPrincipal
Dim fam = GetAuthenticationModule()
Dim message As SignInResponseMessage = GetSignInResponseMessage()
'Dim token As System.IdentityModel.Tokens.SamlSecurityToken = fam.GetSecurityToken(message)
Dim token = fam.GetSecurityToken(message)
If token IsNot Nothing Then
Dim claims As Microsoft.IdentityModel.Claims.ClaimsIdentityCollection = fam.ServiceConfiguration.SecurityTokenHandlers.ValidateToken(token)
Dim principal As System.Security.Principal.IPrincipal = New Microsoft.IdentityModel.Claims.ClaimsPrincipal(claims)
If TypeOf token Is System.IdentityModel.Tokens.SamlSecurityToken Then
Dim t As System.IdentityModel.Tokens.SamlSecurityToken = token
Issuer = t.Assertion.Issuer
ElseIf TypeOf token Is Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken Then
Dim t As Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken = token
Issuer = t.Assertion.Issuer.Value
Else
Throw New Exception("Failed resolving SecurityToken to a known type '" & token.GetType.Name & "'")
End If
Return principal
End If
Return Nothing
End Function

And in the login page/controller you, can now either set the current principal, or parse the claims and sign in the user with formsbased authentication or what ever makes you tick.

Dim Issuer As String = Nothing
Dim Principal As Microsoft.IdentityModel.Claims.ClaimsPrincipal = GetWIFPrincipal(Issuer)
If Principal Is Nothing Then Throw New Exception("Failed parsing sign message to new Principal")
Dim Identity As Microsoft.IdentityModel.Claims.ClaimsIdentity = Principal.Identity
If Identity Is Nothing Then Throw New Exception("Failed parsing sign message to new Principal")
Issuer = Issuer.ToLower

Lastly, to make everything work correctly, we need to make our “login” page support at least 3 of the WS-Federation commands ( I guess that makes “login” page a wrong name then ? ) . So to wrap it all up, you will get something like this

Dim action as string = Request.QueryString(WSFederationConstants.Parameters.Action)
Select Case action
Case WSFederationConstants.Actions.SignIn
' Send user to the identity provider
Dim STSLoginPage As String = "https://adfs.wingu.dk/adfs/ls/"
' This webpage, must be in the AllowedAudienceUris
Dim TargetRealm As String = "https://domain.com/MyAwsomeWebapp"
Dim returnURL As String = "https://domain.com/MyAwsomeWebapp/login.aspx"
' or if using MVC
returnURL = "https://domain.com/MyAwsomeWebapp/login/signin/"
Case WSFederationConstants.Actions.SignOut
Dim requestMessage As SignOutRequestMessage = Nothing
Try
' This will fail, if a "sharepoint" login message
requestMessage = WSFederationMessage.CreateFromUri(Request.Url)
Catch ex As Exception
' ignore
End Try
' Return user to front page
model.RedirectURL = Request.Url.Scheme & "://" & Request.Url.Host & Request.ApplicationPath
If Not requestMessage Is Nothing Then
If Not String.IsNullOrEmpty(requestMessage.Reply) Then
' unless they logged out from another site, then send them back there
model.RedirectURL = requestMessage.Reply
End If
End If
FormsAuthentication.SignOut()
Session.Abandon()

' clear authentication cookie
Dim cookie1 As HttpCookie = New HttpCookie(FormsAuthentication.FormsCookieName, "")
cookie1.Expires = DateTime.Now.AddYears(-1)
Response.Cookies.Add(cookie1)

' clear session cookie
Dim cookie2 As HttpCookie = New HttpCookie("ASP.NET_SessionId", "")
cookie2.Expires = DateTime.Now.AddYears(-1)
Response.Cookies.Add(cookie2)
Case WSFederationConstants.Actions.SignOutCleanup
' Do the same as above, but send an "ok" image back
End Select
And lastly, we need to create class that validates singing certificates. 
Imports Microsoft.IdentityModel.Tokens
Imports System.IdentityModel.Tokens

Public Class CustomTrustedIssuerNameRegistry
Inherits IssuerNameRegistry
'Inherits Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry

Public Sub New()
End Sub
'Public Sub New(customConfiguration As System.Xml.XmlNodeList)
' MyBase.New(customConfiguration)
'End Sub

Public Overrides Function GetWindowsIssuerName() As String
Return MyBase.GetWindowsIssuerName()
End Function

Public Overloads Overrides Function GetIssuerName(securityToken As IdentityModel.Tokens.SecurityToken) As String
Dim x509Token As X509SecurityToken = TryCast(securityToken, X509SecurityToken)
If x509Token IsNot Nothing Then

For Each Thumbprint in My.Settings.AllowedSigningThumbprints
If [String].Equals(x509Token.Certificate.Thumbprint, Thumbprint) Then
Return x509Token.Certificate.SubjectName.Name
End If
Next
End If
Throw New SecurityTokenException("Untrusted issuer.")
End Function

End Class

lørdag den 23. februar 2013

SharePoint/CRM/Exchange and other Claims based websites with Social Sign in

The last week I have been working on a project, regarding combining Claims based authentication and login using Social Providers. In the cloud control panel that I normal work on, I have implemented support for signing in using Office 365, Azure AD, pass-through using Azure ACS, and the mandatory Open ID, Google ID, Windows Live ID, LinkedIn, Facebook, and Twitter. But for reasons I don’t want go into, a need came up, to create a simple STS (Secure Token Service), than would “tie” together one or more identities from social network, and offer those to websites and applications supporting WIF/Claims based authentication.

Why a new STS
If you implemented Social Network, either directly, or indirectly though ACS you probably noticed that if a user sign in using Google ID they will not be the same user, if they later decide to sign in using Facebook. That is relatively easy to “handle” if you wrote the application your self, and Microsoft did a decent job, tying it all up, using the membership provider together with DotNetOpenAuth in MVC 4, but if you don’t want to mess with wrapping up your own customer member ship provider, or if your facilitating WIF in Exchange/CRM/SharePoint/Navision or what ever, wouldn’t it just be awesome if you could just get it all “out of the box” some where. And what if that Service would also allow you to setup your own Identity Providers, so you could implement sign in using Office 365/Azure AD, your own ADFS server, or any other Secure Token Service you might have. You can now

The basic setup
We are still figuring out license models and such, but for now were thinking either a hosted solution, and one where you get the website to run for your self, lets image a hosted setup, and after that you get billed a small amount for each 100k users or something like that. So let’s imaging you want to implement this with Microsoft CRM 2011.

  • First signup for an account, you then get access to a Manage menu. But before you go there, head to CRM and start the “Configure Claims-Based Authentication” wizard
    image
    Type in the URL of the Secure Token Service
    image
    Select a certificate to use, when signing the Claims inside the tokens (this certificate needs to exists on all your CRM servers if you got more than one. It can be a self signed, from your internal CA, or one you bought from a third party
    image
    Go though the rest. Go though the “Configure Internet-Facing Deployment” if you haven't don’t so already, and at the end note the URL to the CRM servers metadata.xml file
  • Go to the STS, click Relays, past in the URL from the “Create new relying part” field, and press Create. By default all Social connectors will be allowed, but if you only want for instance Live ID, select that under Identity Providers, and hit Save.

And that is that. SharePoint how ever doesn’t have these cool metadata.xml files, so configuring that, will take a tiny bit more work, but not to worry, that is why we got PowerShell.

  • Open a PowerShell prompt locally on any of the servers in your farm. And run the following PowerShell Script . ( there is a link on the Relaying Party page too ) Make sure you modify the variables at the top to fit your installation
  • Head to the STS, click management, type a meaningful name into “Create new relying part” field, and press Create
    If you went with the defaults in the script, SharePoint should be sending a reply URL, so we can leave PassiveRequestorURI blank.
    SharePoint (default) doesn’t want the claims to be encrypted so we don’t need to add a certificate.
    Under “Audience Uris” add each of the URN’s that was mapped for each of the websites, show in the end of the script.

And you are all done.

Adding Azure AD, office 365, Azure ACS or an ADFS server
That’s all nice, but what about all your local users, or all your Office 365 users ?
Not a problem, head to the “Providers” page, and type in the URL to the metadata file.

If you are using Office 365 or Azure AD the URL will look something like this
https://accounts.accesscontrol.windows.net/<tenant>.onmicrosoft.com/FederationMetadata/2007-06/FederationMetadata.xml

If your using Microsoft ACS, your URL will look something like this
https://<yourname>.accesscontrol.windows.net/FederationMetadata/2007-06/FederationMetadata.xml
(note, someone at Microsoft fucked this up, so it doesn’t sent a Signing Certificate, but it does doing Office 365 … it’s the same certificate thou, so is easy to grab )

If your using ADFS, the URL will look something like
https://adfs.wingu.dk/FederationMetadata/2007-06/federationmetadata.xml

If your using ACS or ADFS, you don’t need to do anything else than going to the ADFS Server/Azure ACS/Azure AD website and add https://STS/FederationMetadata.xml and it will all configure it self. If how ever your using Office 365, you need to add a ServicePrincipal representing the STS. At the top of the Provider screen is a link to a powershell scrip that will set this up. Once the script is run, past in the “spn:<guid>” from the output of the script into the Target Realm field of the new Provider.

Lastly, go back to your Relying Party, and you can now also select your new Office 365 or what ever Provider you added, and let your users sign in using that

SharePoint 2010 and claims based authentication made a little bit easier

updated 05/03-2013: Added support for extracting root cert from signing certificate
So you have ADFS 2.0 installed. Or your reading some guide about how to configure SharePoint to allow login in using Office 365 or Azure AD or any other kind of Secure Token Service. And 99,9% of them will tell you to copy 1 or more certificates, and run a lot of weird PowerShell command, and it all ends up with you being able to setup some awesome new login page here

image

Well, fear not. If your STS support generating a FederationMetadata.xml URL, you can now use this PowerShell Script to quickly setup your SPTrustedIdentityTokenIssuer . The script can also setup Uri mappings and configure the websites, but I do not recommend using that part in other than development/test environments. Make sure to modify the variables at the top.

I’ve updated the script. If your STS was using a self issued certificate, or a certificate from an internal CA, it wouldn’t be trusted by SharePoint, so added a bit of code to traverse the certificate chain, and then add the root certificate to the Trusted store and also add the root to the list of trusted certificates inside SharePoint

# URL for ADFS / ACS / Office 365 / SimpleSTS / Wingu Cloud ControlPanel / what ever

# $metadataurl = 'https://adfs.wingu.dk/FederationMetadata/2007-06/federationmetadata.xml' # ADFS
# $metadataurl = 'https://accounts.accesscontrol.windows.net/skadefro.onmicrosoft.com/FederationMetadata/2007-06/FederationMetadata.xml' # Azure AD
# $metadataurl = 'https://skadefro.accesscontrol.windows.net/FederationMetadata/2007-06/FederationMetadata.xml' # Azure ACS - Wont work, apperently the guy who made this, forgot to add the signing certificat .. :-(
# $metadataurl = 'https://simplests.wingu.dk/FederationMetadata.xml' # Simple STS
# $metadataurl = 'http://admin.wingu.dk/sts2/FederationMetadata/2007-06/federationmetadata.xml' # Wingu Cloud Control Panel
$metadataurl = 'https://simplests.wingu.dk/FederationMetadata.xml'


# This should really be NameID, but often people will use emails or UPN. choose what fits your needs
$IdentifierClaim = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"
# What claims do you want mapped. All will be exposed in the ClaimsToken, but what will show up in the people picker ?
$mapClaims = @('UPN', 'Role')

# Default realm. If you only have 1 website, you can add that here, but more correctly would be adding
# Each URI accessible on your farm, mapping it to a URN
# If your using Wingu Cloud Control Panal or SimpleSTS, urn:sps will automaticly be converte to a SharePoint signin URI
# urn:sps:http://spfarm01.cloudapp.net becomes http://spfarm01.cloudapp.net/_trust/
# if not, just use the full uri, like http://spfarm01.cloudapp.net/_trust/
# if you want several sharepoint sites to allow the user access without prompting for login each time,
# use one shared urn (not URI) for all those sites. There are certain restrictions, on how/where you can do that
$defaultrealm = 'urn:sps:https://sharepoint.domain.com'

# Be carefull with this. Not all STS' understand/support's this, and often the above is a simpler solution
# set this to true, will go though the list of sites in the farm and add an "urn:sps:URI" entry for each site
$autoGenerateProviderRealms = $false

# Dont, Dont, DONT ... D-O-N-T, use this
# This will add Windows and ALL identityProviders to all WebApplication's (except Central Admin) for the "default" zone.
$autoAddSTS = $false

$stsname = 'SimpleSTS'
$name = $stsname
$realm = $null
$stsd = $null
$asd = $null

$snapin = Get-PSSnapin | where {$_.name -eq 'Microsoft.SharePoint.PowerShell'}
if($snapin -eq $null){ Add-PSSnapin Microsoft.SharePoint.PowerShell }


function Get-medadata ($metadataurl) {
[void] [Reflection.Assembly]::LoadWithPartialName("Microsoft.IdentityModel")
$STSReader = New-Object Microsoft.IdentityModel.Protocols.WSFederation.Metadata.MetadataSerializer
$req = [System.Net.WebRequest]::Create($metadataurl)
$xmlreader = [System.Xml.XmlReader]::Create($req.GetResponse().GetResponseStream())
$metadata = $STSReader.ReadMetadata([System.Xml.XmlReader]$xmlreader)
$metadata
}

function Get-STS ($metadata) {
foreach($RoleDescriptor in $metadata.RoleDescriptors){
if($RoleDescriptor.GetType().FullName -eq 'Microsoft.IdentityModel.Protocols.WSFederation.Metadata.SecurityTokenServiceDescriptor'){
# metadata have a STS
$stsd = $RoleDescriptor
} elseif($RoleDescriptor.GetType().FullName -eq 'Microsoft.IdentityModel.Protocols.WSFederation.Metadata.ApplicationServiceDescriptor'){
# metadata have a Relying Party
$asd = $RoleDescriptor
}
}
$stsd
}
function Get-RelyingParty ($metadata) {
foreach($RoleDescriptor in $metadata.RoleDescriptors){
if($RoleDescriptor.GetType().FullName -eq 'Microsoft.IdentityModel.Protocols.WSFederation.Metadata.SecurityTokenServiceDescriptor'){
# metadata have a STS
$stsd = $RoleDescriptor
} elseif($RoleDescriptor.GetType().FullName -eq 'Microsoft.IdentityModel.Protocols.WSFederation.Metadata.ApplicationServiceDescriptor'){
# metadata have a Relying Party
$asd = $RoleDescriptor
}
}
$asd
}

function Get-GetCertificate ($stsd, $certificatetype) {
if( ($certificatetype -ne 'Signing') -and ($certificatetype -ne 'Encryption')){
Write-Error "CertificateType should be 'Signing' or 'Encryption'"
return
}
$has40 = [Reflection.Assembly]::LoadWithPartialName("System.IdentityModel")

$SigningCertificate = $null
foreach($key in $stsd.Keys){
if($key.Use -eq $certificatetype){
if($has40.GetName().Version.Major -eq 4){
#if .net 4.0 is install we can just do this, when System.IdentityModel v4.0.30319 is loaded
$bytes = $key.Keyinfo.GetX509RawData()
} else {
#but default, sharepoint 2010 only have version v2 loaded so, lets do it, the ugly way
$str = $key.Keyinfo.ToString()
$str = $str.Substring( $str.IndexOf('RawData = ') + 10)
$str = $str.Substring(0, $str.IndexOf(')'))

$bytes = ([system.Text.Encoding]::UTF8).GetBytes($str)
}
$SigningCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]([byte[]]$bytes)
}
}
$SigningCertificate
}

Function Ensure-SPProvider($name, $defaultrealm, $SigningCertificate, $SigninURL, $claims, $IdentifierClaim, $mapClaims){
$certificatename = "$($name) Signing Certificate"
$cert = get-SPTrustedRootAuthority | ?{($_.Certificate.Thumbprint -eq $SigningCertificate.Thumbprint) -or ($_.Name -eq $certificatename)}
if(!$cert){
Write-Host "Adding $($SigningCertificate.Thumbprint) as a Trust Certificate"
$cert = New-SPTrustedRootAuthority -name $certificatename -Certificate $SigningCertificate
} else {
$cert | Set-SPTrustedRootAuthority -Certificate $SigningCertificate
}
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.Build($SigningCertificate)
$SigningCertificateRoot = $chain.ChainElements[$chain.ChainElements.Count -1].Certificate

# Ensure Root Cert has been added to Trusted Store
$store = get-item cert:\LocalMachine\Root
$store.Open("ReadWrite")
$store.Add($SigningCertificateRoot )
$store.Close()

# And now, add it to SharePoint too
$rootcert = get-SPTrustedRootAuthority | ?{($_.Certificate.Thumbprint -eq $SigningCertificateRoot.Thumbprint)}
if(!$rootcert){
Write-Host "Adding $($SigningCertificateRoot.Thumbprint) as a Trust Certificate"
$rootcert = New-SPTrustedRootAuthority -name $SigningCertificateRoot.Subject -Certificate $SigningCertificateRoot
}

$IDClaim = $null
$claimsmappings = @()
foreach($claim in $claims) {
# http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name is reserved inside SharePoint (??? Why ?... really, WTF !!!!)
# http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid is only used in SharePoint 2013 ( not tested yet )
# its SharePoints internal STS job to handle the requiered fields
# http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod
# http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant

if($claim.ClaimType -eq 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'){
$claimsmappings += New-SPClaimTypeMapping $claim.ClaimType -IncomingClaimTypeDisplayName $claim.DisplayTag -LocalClaimType 'http://simplests.wingu.dk/identity/claims/name'
} elseif($claim.ClaimType -eq 'http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod'){
# Skip
} elseif($claim.ClaimType -eq 'http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant'){
# Skip
} elseif($claim.ClaimType -eq 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'){
$claimsmappings += New-SPClaimTypeMapping $claim.ClaimType -IncomingClaimTypeDisplayName $claim.DisplayTag -LocalClaimType 'http://simplests.wingu.dk/identity/claims/nameidentifier'
} else {
$claimsmappings += New-SPClaimTypeMapping -IncomingClaimType $claim.ClaimType -IncomingClaimTypeDisplayName $claim.DisplayTag -SameAsIncoming
}
if($IdentifierClaim -eq $claim.ClaimType){
$IDClaim = $claim.ClaimType
}
}

$ap = Get-SPTrustedIdentityTokenIssuer | ?{$_.name -eq $name}
if(!$ap){
$ap = New-SPTrustedIdentityTokenIssuer -Name $name -Description $name -Realm $defaultrealm -ImportTrustCertificate $SigningCertificate -ClaimsMappings $claimsmappings -SignInUrl $SigninURL -IdentifierClaim $IDClaim
}
# Ensure all claim mappins are there
#foreach($claim in $claims) {
# $hasClaim = $ap.ClaimTypes | ?{$_ -eq $claim.ClaimType}
# if(!$hasClaim) {
# Write-Host "adding ClaimType $($claim.DisplayTag) to $name"
# $ap.ClaimTypes.Add($claim.ClaimType)
# }
#}

#$ap.Update()
#Add-SPClaimTypeMapping -Identity $claim -TrustedIdentityTokenIssuer $ap
#$ap = Get-SPTrustedIdentityTokenIssuer | ?{$_.name -eq $name}

# if you only have 1 sharepoint site, and dont want to "bother" setting up
# Reply URL on your STS ( adfs - AD FS 2.0 Management -> Trust Releationships ->
# Relying Party Trusts -> Properties on RP Go to "Endpoints" tab -> WS-Federation Passive Endpoint
# Or SimpleSTS -> Management -> Relying Party -> Manage -> PassiveRequestorURI
# you can just override the $ap.DefaultProviderRealm
# but the correct way would be to list each unique URI with an associated URN in $ap.ProviderRealms
$ap.DefaultProviderRealm = $defaultrealm

$ap.ProviderUri = $SigninURL
#$ap.Name = $name
#$ap.DisplayName = $name


# If WReply is not set, sharepoint will not add return URL when sending user to login page
# if DefaultProviderRealm/$ap.ProviderRealms URN is not listed with a Return URL in the STS
# the STS will not know where to send the user. What's "correct" ? dont know.
# if your paranoid, leacing WReply $false, using URN's all the places, and keeping Passive Federation
# URL's in you STS would proberly be best, but let's ensure we can have both unique URI's, prober return URI's
# and unique'nes by using $ap.ProviderRealms
$ap.UseWReplyParameter = $true

# HomeRealm tells the STS how the user want to to login instead of prompting.
# ADFS will ask and save a cookie, for 3 months, most custom solutions, will prompt each time
# for instance SimpleSTS will allow you to choose each time from a list of all allowed providers
# Sharepoint will NEVER know what those homerealms could be.
# SharePoint's internet STS will normaly "strip" off HomeRealm ( WHR= ) but with the Juni RU this new
# parameter was added to allow adding HomeRealm in custom code (modules or custom login page) without loosing the WHR parameter
# http://blogs.msdn.com/b/chriskeyser/archive/2011/10/02/sharepoint-2010-claims-and-home-realm-discover-passing-whr-on-the-url-to-sharepoint.aspx
try {
$ap.UseWHomeRealmParameter = $true
} catch {
Write-Warning "SharePoint is not update, and wont allow setting UseWHomeRealmParameter, make sure you use uri:sp: as realms/AudienceURIs, or set PassiveRequestorURI to [SharePointURI]/_trust/"
$Error.Clear()
}
$ap.Update()

}
$metadata = Get-medadata $metadataurl
$stsrealm = $metadata.EntityId.Id
$stsd = Get-STS $metadata
#$asd = Get-RelyingParty $metadata
$SigningCertificate = Get-GetCertificate $stsd 'Signing'
#$encryptionCertificate = Get-GetCertificate $asd 'Encryption'
$claims = $stsd.ClaimTypesOffered

foreach($PassiveRequestorEndpoint in $stsd.PassiveRequestorEndpoints){
$SigninURL = $PassiveRequestorEndpoint.Uri.AbsoluteUri
}

Ensure-SPProvider $stsname $defaultrealm $SigningCertificate $SigninURL $claims $IdentifierClaim $mapClaims

if($autoGenerateProviderRealms -eq $true){
# We could use Get-SPWebApplication, and then Get-SPAlternateURL on each site
# we could also filter out only "external" sites, etc ....

$ap = Get-SPTrustedIdentityTokenIssuer | ?{$_.name -eq $stsname}
#$ap.ProviderRealms.Clear()
foreach($wa in Get-SPWebApplication){
$pr = $ap.ProviderRealms.keys | where {$_.AbsoluteUri -eq $wa.Url}
if(!$pr){ $pr = $ap.ProviderRealms.Add([uri]($wa.Url), "urn:sps:$($wa.Url)") }
}
$ap.Update()
}
if($autoAddSTS -eq $true){
$windowsap = New-SPAuthenticationProvider
#$aps = Get-SPAuthenticationProvider -Zone "Default" -WebApplication $wa
$aps = @()
$aps += $windowsap
foreach($ap in Get-SPTrustedIdentityTokenIssuer){
$aps += $ap
}
foreach($wa in Get-SPWebApplication){
Set-SPWebApplication -Identity $wa -Zone "Default" -AuthenticationProvider $aps # -Force
}
}

$ap = Get-SPTrustedIdentityTokenIssuer | ?{$_.name -eq $stsname}
Write-Host "ensure '$($defaultrealm) and each 'Value' is added to the Relying Party in the STS"
$ap.ProviderRealms

onsdag den 6. februar 2013

Requesting email when using Windows LiveID OAuth2

didn’t take long to figure out, but just in case. If you are using DotNetOpenAuth and are using the ApplicationBlock.WindowsLiveGraph to get the identity of the user, you will not get the email address of the user. You can how ever request it, by sending wl.emails in the Scope:

Dim WindowsLiveIDTokenManager As New SQLTokenManager(WindowsLiveIDConsumerKey, WindowsLiveIDConsumerSecret)
Dim WindowsLiveClient As New DotNetOpenAuth.ApplicationBlock.WindowsLiveClient() With { _
    .ClientIdentifier = WindowsLiveIDConsumerKey, _
    .ClientSecret = WindowsLiveIDConsumerSecret _
}
Dim authUrl = New Uri(Request.Url.Scheme + "://" + Request.Url.Authority + "/sts2/login.aspx")
WindowsLiveClient.RequestUserAuthorization(scope:=New String() {ApplicationBlock.WindowsLiveClient.Scopes.Basic, "wl.emails"}, returnTo:=authUrl)

Next, when validating the access token, don’t use the built in JSON Deserialize function, but use your own

 

If authorization IsNot Nothing Then
    Dim request = System.Net.WebRequest.Create("https://apis.live.net/v5.0/me?access_token=" + Uri.EscapeDataString(authorization.AccessToken))
    Using response = request.GetResponse()
        Dim JSON As String = ""
        Using responseStream = response.GetResponseStream()
            Dim reader As New IO.StreamReader(responseStream, Encoding.UTF8)
            JSON = reader.ReadToEnd()
        End Using

        Dim jss As New Script.Serialization.JavaScriptSerializer
        Dim LiveIDUser As LiveIDGraph = jss.Deserialize(JSON, GetType(LiveIDGraph))    End Using
End If

 

And this is how your LiveIDGraph would look like

Imports System.Runtime.Serialization

<DataContract(Namespace:="")>
Public Class LiveIDGraph

    <DataMember> Public id As String
    <DataMember> Public name As String
    <DataMember> Public first_name As String
    <DataMember> Public last_name As String
    <DataMember> Public link As String
    <DataMember> Public gender As String
    <DataMember> Public emails As LiveIDEmails
    <DataMember> Public locale As String
    <DataMember> Public updated_time As DateTime

End Class

<DataContract(Namespace:="")>
Public Class LiveIDEmails
    <DataMember> Public preferred As String
    <DataMember> Public account As String
    <DataMember> Public personal As String
    <DataMember> Public business As String
End Class

tirsdag den 5. februar 2013

Fully automated deployment of SharePoint farms in Microsoft Azure

There a plenty of blogs out there about scripting SharePoint, so I wont fill up more space about all the troubles and joys regarding that. I will how ever point you in the direction of AutoSpInstaller who have an awesome script for slip streaming SharePoint installations and better yet. Automating the deployment of SharePoint 2010 and 2013.

I’ve seen a few blog post about installing SharePoint in Microsoft Azure, and I wanted to add my two cent about that. I just finished up doing a Proof of Concept showing a FULLY automated installation of multiple both single and multiple server, SharePoint farm, inside Microsoft Azure. This is how I did it.

I’m developing a Provisioning Engine that supports running in many sites and distribute servers and services across these sites seamlessly. I order to start facilitating Azure we need a subscription and some Persistent VMs deployed inside that subscription. I originally wanted to do everything “offsite” from each subscription, so a new customer could start from scratch, but after a while it became clear I needed some more granulated control doing deployment and needed to deploy my provisioning engine inside the subscription. Microsoft does not have an API for creating subscriptions (that I know off) so we manually sign up for a new Azure Subscription. Next we sign up for a Trial Account in our Provisioning Site. Create a certificate and upload it to Azure and Add Subscription ID and certificate to my Provisioning API in order to make the provisioning engine able to facilitate Azure for that subscription.

We do that by adding a Provisioning Site,

image

Next, we need to make sure all servers can see each other. We can install everything on one server. We can add all servers to the same Azure Hosted Service. We can “link” different roles to a Azure Hosted Service (I have not tested this) or we can create a Virtual Network. The latter is most appealing, so we add a Workspace

image

( at the time of writing, I have not automated the creating of the Virtual Network inside Azure, so we need to add it manually inside Azure and fill out the values in the Workspace plan )

image

Once this is done, manually Add a server to Azure, and deploy our provisioning engine. (this could later be a custom image, or pushed from another site, but for proof of concept, doing this manually  is ok). We link the server to the provisioning site, so the new server have access to all services, and we are now ready to automated everything else.

Next we add an Microsoft SQL server. This can also be done while adding the SharePoint Farm
image

Let customize the SharePoint installation so we control Server names etc. Create a Hosted Service for our new SharePoint Farm

image

image

Next add 1 or more servers depending on how big you want the farm to be

image

Make sure to update relation to point to your new Azure Hosted Service if you got more than one. Lastly we need to add a SharePoint farm on top of the Hosted Service.
image
Fill out desired SMTP/Email settings if needed, and hit deploy. This will automatically add a new Hosted Service and add an SQL Server inside that Service. It will add a Plan for each of the (up to) 22 (default is 16) databases  needed to run the farm, once all have been setup it will deploy SharePoint to each of the servers you added in the above steps, it will create a new Farm, a default SharePoint site and My Site. Configure most of the normal features of a SharePoint farm ( but any features supported by AutoSpInstaller is supported here too)

So what happens behind the scene? Well, AutoSpInstaller supports deploying SharePoint remotely on one or more servers, but it uses PS Exec from PS Tools to do it. ( if you look in the PowerShell scripts it looks like the author also tried making it work using WSMan ( invoke-computer / new-pssession )) but a new blank server doesn’t allow connect with either, so we need a way to get in contact with the server in the first place. If you deploy the standard Windows 2003 image, it allows you to talk with it, using WMI but not WSMan, so you could fire off a WMI command to run the command to configure WSMan ( winrm quickconfig ) but if you deploy a Windows 2012 server, your just out of luck, neither WMI or WSMan will work. You can how ever, tell Windows Azure to automatically join new servers to a domain, and when we are talking about SharePoint you need the servers to be domain joined. Once a Windows server 2012 is part of a domain, you can connect using WSMan and execute PowerShell on the machine (but not WMI, hence PS Exec will not work )

Sure, you could just fire up all servers to be part of a domain, create a group policy to configure the firewall (and maybe there is a Policy for doing something like a winrm quickconfig  too?) but I wanted something generic. Granted, I never figured out a way to “cheat” a fresh Windows 2012 that is not a domain member, I guess I will need to create a custom Image for those later, but for now, having them domain joined was good enough.

On all servers I deploy, I automatically I push out a small windows service. It handles certificates, joining/disjoining domains, installing software, remote management and is also used on Remote Desktop and Citrix XenApp servers to manage user sessions. Its perfect for handling the SharePoint installation and with above I can now push out this service though WMI and/or WSMan and kick of the installation. So the service handles installing an SQL server. well tested and Known technology and easy to automate, next we can now kick off the SharePoint installation.

AutoSpInstaller uses Start-Transcript doing installation, and I haven't figured out how to “support” that in a custom hosted PowerShell host, so first I had to strip that out. Next it does an insane amount of “am waiting lets do a write a “.” dot each second” thing, and is annoying as hell in the console logs, so had to strip those out too. Lastly you need patience … A LOT of it … So now when looking at the Provisioning Request Log is more clean.

imageimage

For some reason the first server you install, that will also handle setting up the farm, and configuring services, will take from 2½ to 4 hours depending on the amount of services you choose. The following servers can take up to 30 minutes to install binaries, and 20 to 40 minutes joining the farm and configuring services. Not sure why really (both CPU/memory and Disk queue length is way below any warning signs on all servers doing the installation)

Once AutoSpInstaller is done, its easy and well documented how to connect to one of the servers and configuring new SharePoint sites/Site Collections and adding/removing users and groups, as part of my provisioning engine.

When using AutoSpInstaller it worth taking 2 minute extra reading the comments in the AutoSPInstallerInput.xml file. Stuff like how database prefix's, limits on characters' in password, or knowing that what worked on your “single” server install will not work in farms ( using the default http://localhost as WebApp name’s or not limiting the QueryServer to a single server will take a lot longer to troubleshoot that just reading the damn documentation Winking smile )

mandag den 19. november 2012

Requesting Certificate from different Windows OS’

long ago I created an application that could create a certificate request, submit it to a Web Service that would submit it to an internal Enterprise CA, send the signed certificate back, and then accept the certificate on the client. Once done, the application would the (if needed) install SCOM (System Center Operation Manager Client) and configure it for Certificate Authentication.

We needed this application to work on everything from Windows 2000 and up, and that turned out to be one hell of a lot of “messy” code, since Microsoft changed the API for handling Certificates around Server 2008 (and now again in Windows 8/ Server 2012)

So I had to do an update to the application and instantly I started getting a ton of errors. After fixing those, suddenly the application didn’t work on old client’s (2003 servers) After spending several hour’s I just had a flip and decided there had to be something smarter you could do. I came across this blog post and though to my self, that seemed simple and elegant, and if Bouncy Castle could help create a PFX file, it would be simple to import the certificate, on all versions of operation systems. I cleaned up the code, removed a few bits and pieces and added links to where I got the difference pieces of code from.

Imports Org.BouncyCastle.Crypto.Generators
Imports Org.BouncyCastle.Crypto

Imports System.Security.Cryptography.X509Certificates

Public Class CertificateRequest

#Region "Internals"

Private _CSR As String
Public ReadOnly Property CSR() As String
Get
Return _CSR
End Get
End Property

Private PrivateKeyPem As String
Private ackp As AsymmetricCipherKeyPair

#End Region

#Region "Constants"
Const CRYPT_EXPORTABLE = 1
Const AT_KEYEXCHANGE = 1
Const CERT_SYSTEM_STORE_LOCAL_MACHINE = &H20000

Const OID_MSTEMPLATE_v1 As String = "1.3.6.1.4.1.311.20.2"
Const OID_MSTEMPLATE_v2 As String = "1.3.6.1.4.1.311.20.7"

Private Const CC_DEFAULTCONFIG As Integer = 0
Private Const CC_UIPICKCONFIG As Integer = &H1
Private Const CR_IN_BASE64 As Integer = &H1
Private Const CR_IN_FORMATANY As Integer = 0
Private Const CR_IN_PKCS10 As Integer = &H100
Private Const CR_DISP_ISSUED As Integer = &H3
Private Const CR_DISP_UNDER_SUBMISSION As Integer = &H5
Private Const CR_OUT_BASE64 As Integer = &H1
Private Const CR_OUT_CHAIN As Integer = &H100

#End Region

''' <summary>
''' Create a new Certificate Request, with a 2048 bits key pair
''' </summary>
''' <param name="FQDN">Fully qualified domain name</param>
''' <returns>An instance of CertificateRequest containing private key and CSR</returns>
''' <remarks></remarks>
Shared Function CreateRequest(FQDN As String) As CertificateRequest
Return CreateRequest(FQDN, 2048)
End Function

''' <summary>
''' Create a new Certificate Request
''' </summary>
''' <param name="FQDN">Fully qualified domain name</param>
''' <param name="Strength">Strength of key in bits</param>
''' <returns>An instance of CertificateRequest containing private key and CSR</returns>
''' <remarks></remarks>
Shared Function CreateRequest(FQDN As String, Strength As Integer) As CertificateRequest
' http://stackoverflow.com/questions/949727/bouncycastle-rsaprivatekey-to-net-rsaprivatekey
'Key generation
Dim rkpg As New RsaKeyPairGenerator()
rkpg.Init(New KeyGenerationParameters(New Org.BouncyCastle.Security.SecureRandom(), Strength))
Dim ackp As AsymmetricCipherKeyPair = rkpg.GenerateKeyPair()

'Requested Certificate Name
Dim name As New Org.BouncyCastle.Asn1.X509.X509Name("CN=" & FQDN)
Dim csr As New Org.BouncyCastle.Pkcs.Pkcs10CertificationRequest("SHA1WITHRSA", name, ackp.[Public], Nothing, ackp.[Private])

'Convert BouncyCastle CSR to .PEM file.
Dim CSRPem As New System.Text.StringBuilder()
Dim CSRPemWriter As New Org.BouncyCastle.OpenSsl.PemWriter(New System.IO.StringWriter(CSRPem))
CSRPemWriter.WriteObject(csr)
CSRPemWriter.Writer.Flush()

'Convert BouncyCastle Private Key to .PEM file.
Dim PrivateKeyPem As New System.Text.StringBuilder()
Dim PrivateKeyPemWriter As New Org.BouncyCastle.OpenSsl.PemWriter(New System.IO.StringWriter(PrivateKeyPem))
PrivateKeyPemWriter.WriteObject(ackp.[Private])
CSRPemWriter.Writer.Flush()

'Push the CSR Text to a Label on a Page
'Dim PrivateKeyLabel As String = PrivateKeyPem.ToString()
Return New CertificateRequest With {.ackp = ackp, ._CSR = CSRPem.ToString(), .PrivateKeyPem = PrivateKeyPem.ToString()}
End Function

''' <summary>
''' Submit request to CA and get signed certificate back
''' </summary>
''' <param name="CA">CA to use, leaving blank will open default UI to select an CA</param>
''' <param name="Template">Certificate Template to use, can be blank if standalone CA, or template specefied in CSR. If specefied, will override what is specefied in CSR</param>
''' <param name="base64">CSR as Base54</param>
''' <returns>Signed Certificate as Base64</returns>
''' <remarks></remarks>
Public Function submitRequest(ByVal CA As String, Template As String, ByVal base64 As String) As String
' Create all the objects that will be required
Dim objCertConfig As CERTCLIENTLib.CCertConfig = New CERTCLIENTLib.CCertConfig()
Dim objCertRequest As CERTCLIENTLib.CCertRequest = New CERTCLIENTLib.CCertRequest()
Dim strCAConfig As String
Dim iDisposition As Integer
Dim strDisposition As String
Dim strCert As String

Try
If CA = "" Then
' Get CA config from UI
'strCAConfig = objCertConfig.GetConfig(CC_DEFAULTCONFIG);
strCAConfig = objCertConfig.GetConfig(CC_UIPICKCONFIG)
Else
strCAConfig = CA ' "AD01.int.wingu.dk\int-AD01-CA"
End If

Dim strAttributes As String = Nothing
If Not String.IsNullOrEmpty(Template) Then strAttributes = "CertificateTemplate: " & Template

' Submit the request
iDisposition = objCertRequest.Submit(CR_IN_BASE64 Or CR_IN_FORMATANY, base64, strAttributes, strCAConfig)

' Check the submission status
If CR_DISP_ISSUED <> iDisposition Then
' Not enrolled
strDisposition = objCertRequest.GetDispositionMessage()

If CR_DISP_UNDER_SUBMISSION = iDisposition Then
' Pending
Console.WriteLine("The submission is pending: " & strDisposition)
Return ""
Else
Throw New Exception("The submission failed: " & strDisposition & vbCrLf & "Last status: " & objCertRequest.GetLastStatus().ToString())
' Failed
Console.WriteLine("The submission failed: " & strDisposition)
Console.WriteLine("Last status: " & objCertRequest.GetLastStatus().ToString())
Return ""
End If
End If

' Get the certificate
strCert = objCertRequest.GetCertificate(CR_OUT_BASE64 Or CR_OUT_CHAIN)

Return strCert
Catch ex As Exception
Throw ex
Console.WriteLine(ex.Message)
End Try
End Function

''' <summary>
''' Pair Private Key with signed certificate and save to local machine store
''' </summary>
''' <param name="CertResponse"></param>
''' <param name="allowExport"></param>
''' <remarks></remarks>
Sub AcceptRequest(ByVal CertResponse As String, allowExport As Boolean)
Dim s As String = CertResponse
' Response from Microsoft CA will not contain filetype specefication, to add it to make BouncyCastle happy
If Not s.Contains("-----BEGIN CERTIFICATE-----") Then
s = "-----BEGIN CERTIFICATE-----" & vbCrLf & s & vbCrLf & "-----END CERTIFICATE-----"
End If
' Load signed Certificate as a BouncyCastle Certificate
Dim pr As New Org.BouncyCastle.OpenSsl.PemReader(New System.IO.StringReader(s))
Dim prObj = pr.ReadObject()
Dim cert As Org.BouncyCastle.X509.X509Certificate = DirectCast(prObj, Org.BouncyCastle.X509.X509Certificate)

' Convert certificate to a "microsoft" Certificate
Dim _netcert As System.Security.Cryptography.X509Certificates.X509Certificate = Org.BouncyCastle.Security.DotNetUtilities.ToX509Certificate(cert)
Dim netcert As New System.Security.Cryptography.X509Certificates.X509Certificate2(_netcert)
' Add Private Key to the Certificate
Dim rcsp As New System.Security.Cryptography.RSACryptoServiceProvider()

'And the privateKeyParameters
Dim parms As New System.Security.Cryptography.RSAParameters()
'Translate ackp.PrivateKey to parms;
Dim BCKeyParms As Parameters.RsaPrivateCrtKeyParameters = DirectCast(ackp.[Private], Parameters.RsaPrivateCrtKeyParameters)
parms.Modulus = BCKeyParms.Modulus.ToByteArrayUnsigned()
parms.P = BCKeyParms.P.ToByteArrayUnsigned()
parms.Q = BCKeyParms.Q.ToByteArrayUnsigned()
parms.DP = BCKeyParms.DP.ToByteArrayUnsigned()
parms.DQ = BCKeyParms.DQ.ToByteArrayUnsigned()
parms.InverseQ = BCKeyParms.QInv.ToByteArrayUnsigned()
parms.D = BCKeyParms.Exponent.ToByteArrayUnsigned()
parms.Exponent = BCKeyParms.PublicExponent.ToByteArrayUnsigned()

'import the RSAParameters into the RSACryptoServiceProvider
rcsp.ImportParameters(parms)
netcert.PrivateKey = rcsp

' I'm sure there is a smarter way, but this works for now
' If you add the certificate now, it will be lacking the MachineKeySet/PersistKeySet and Exportable
' Parameter, so we export the certificate and load it again, with Cryptography.CspParameters set correctly
' http://stackoverflow.com/questions/9810887/export-x509certificate2-to-byte-array-with-the-private-key

Dim certBytes As Byte() = netcert.Export(X509ContentType.Pkcs12, "Password1!")
'System.IO.File.WriteAllBytes("c:\certificate.pfx", certBytes)
Dim certToImport As System.Security.Cryptography.X509Certificates.X509Certificate2
If allowExport Then
certToImport = New System.Security.Cryptography.X509Certificates.X509Certificate2(certBytes, "Password1!", X509KeyStorageFlags.MachineKeySet Or X509KeyStorageFlags.Exportable Or X509KeyStorageFlags.PersistKeySet)
Else
certToImport = New System.Security.Cryptography.X509Certificates.X509Certificate2(certBytes, "Password1!", X509KeyStorageFlags.MachineKeySet Or X509KeyStorageFlags.PersistKeySet)
End If
If Not certToImport.HasPrivateKey Then Throw New Exception("Certificate failed to load with Private key")

'Test that we can encrypt and decrypt with the certificate
'Dim PlainString As String = "Encrypt this string"
'Dim EncryptedString As String = CertUtilities.GetEncryptedText(certToImport, PlainString)
'Dim result As String = CertUtilities.GetDecryptedText(certToImport, EncryptedString)
'If PlainString <> result Then Throw New Exception("Certificate failed the encryption/decryption test")

' Finally, add the certificate to the LocalMachine store
Dim store As New Security.Cryptography.X509Certificates.X509Store(Security.Cryptography.X509Certificates.StoreName.My, Security.Cryptography.X509Certificates.StoreLocation.LocalMachine)
store.Open(OpenFlags.ReadWrite)
store.Add(certToImport)
store.Close()

End Sub

End Class

And a few tools for testing

    ' http://blogs.msdn.com/b/cagatay/archive/2009/02/08/removing-acls-from-csp-key-containers.aspx
Sub RemoveEveryoneFromPrivateKey(Cert As System.Security.Cryptography.X509Certificates.X509Certificate2)
Dim rsa As Cryptography.RSACryptoServiceProvider = Cert.PrivateKey

Dim id As New Principal.SecurityIdentifier(Principal.WellKnownSidType.WorldSid, Nothing) ' Indicates a SID that matches everyone.
Dim cspParams As New Cryptography.CspParameters(rsa.CspKeyContainerInfo.ProviderType, rsa.CspKeyContainerInfo.ProviderName, rsa.CspKeyContainerInfo.KeyContainerName)
cspParams.Flags = Cryptography.CspProviderFlags.UseMachineKeyStore
Dim container As New Cryptography.CspKeyContainerInfo(cspParams)

'get the original acls first
cspParams.CryptoKeySecurity = container.CryptoKeySecurity

'Search for the account given to us and remove it from accessrules
'For Each rule As CryptoKeyAccessRule In cspParams.CryptoKeySecurity.GetAccessRules(True, False, GetType(Principal.NTAccount))
For Each rule As CryptoKeyAccessRule In cspParams.CryptoKeySecurity.GetAccessRules(True, False, GetType(Principal.SecurityIdentifier))
If rule.IdentityReference.Equals(id) Then
cspParams.CryptoKeySecurity.RemoveAccessRule(rule)
End If
Next
'persist accessrules on key container.
Dim cryptoServiceProvider As New Cryptography.RSACryptoServiceProvider(cspParams)
End Sub

'http://social.msdn.microsoft.com/Forums/hu/vbgeneral/thread/7e0f513f-cd09-492a-8748-a4aea024cff0
'http://stackoverflow.com/questions/425688/how-to-set-read-permission-on-the-private-key-file-of-x-509-certificate-from-ne
Sub AddEveryoneToPrivateKey(Cert As System.Security.Cryptography.X509Certificates.X509Certificate2)
Dim id As New Principal.SecurityIdentifier(Principal.WellKnownSidType.WorldSid, Nothing) ' Indicates a SID that matches everyone.
Dim rsa As Cryptography.RSACryptoServiceProvider = Cert.PrivateKey

If Cert.HasPrivateKey Then
Dim cspParams = New Cryptography.CspParameters(rsa.CspKeyContainerInfo.ProviderType, _
rsa.CspKeyContainerInfo.ProviderName, _
rsa.CspKeyContainerInfo.KeyContainerName) _
With {.Flags = Cryptography.CspProviderFlags.UseExistingKey Or Cryptography.CspProviderFlags.UseMachineKeyStore, _
.CryptoKeySecurity = rsa.CspKeyContainerInfo.CryptoKeySecurity}
cspParams.CryptoKeySecurity.AddAccessRule(
New CryptoKeyAccessRule(id, CryptoKeyRights.FullControl, AccessControlType.Allow))

' Once we create a new RSACryptoServiceProvider, we override the existing one.
Dim rsa2 As Cryptography.RSACryptoServiceProvider = New Cryptography.RSACryptoServiceProvider(cspParams)
End If
End Sub

Function GetEncryptedText(cert As System.Security.Cryptography.X509Certificates.X509Certificate2, PlainStringToEncrypt As String) As String
Dim cipherbytes As Byte() = Text.ASCIIEncoding.ASCII.GetBytes(PlainStringToEncrypt)
Dim rsa As Security.Cryptography.RSACryptoServiceProvider = cert.PublicKey.Key
Dim cipher As Byte() = rsa.Encrypt(cipherbytes, False)
Return Convert.ToBase64String(cipher)
End Function

Function GetDecryptedText(cert As System.Security.Cryptography.X509Certificates.X509Certificate2, EncryptedStringToDecrypt As String) As String
Dim cipherbytes As Byte() = Convert.FromBase64String(EncryptedStringToDecrypt)
If cert.HasPrivateKey Then
Dim rsa As Security.Cryptography.RSACryptoServiceProvider = cert.PrivateKey
Dim plainbytes As Byte() = rsa.Decrypt(cipherbytes, False)
Dim enc As System.Text.ASCIIEncoding = New System.Text.ASCIIEncoding()
Return enc.GetString(plainbytes)
Else
Throw New Exception("Certificate used for has no private key.")
End If
End Function