Web application working on iis7 but not on local machine - vb.net

I have my VB.NET web application successfully built and compiled on ISS7 server but on the local machine when compiling through Visual studion 2013 it is
returning "System.Security.Cryptography.CryptographicException: The system cannot find the file specified." error.
Not sure what is happening but throws an error at Dim cert As New X509Certificate2(certFile, certFilePassword,X509KeyStorageFlags.MachineKeySet)
Below is the code where I am trying to use a certificate that is installed on my local machine.
Sub LoginED(ByVal token As String)
Try
'We need the ED user ID
lblEDUserID.Text = GetEDUserID().ToString()
'Track user login
InsertLogin(txtEDUsername.Text, True)
'Set login cookies for ED users
If lblEDUserID.Text = "0" Then
lblEDLoginStatus.Text = "You have entered an incorrect login. Please try again."
Else
Dim certFile As String = "VIP_Cert.pfx"
Dim certFilePassword As String = "password"
Dim cert As New X509Certificate2(certFile, certFilePassword,X509KeyStorageFlags.MachineKeySet)
Dim vipSoapInterfaceQueryServiceCert As New WebReference.QueryService
'Dim fs As FileStream = File.Open(certFile, FileMode.Open, FileAccess.ReadWrite)
'Dim filesize As Long = fs.Length
'Dim buffer(filesize) As Byte
'fs.Read(buffer, 0, filesize)
'byte[] buffer = new byte[fs.Length];
'int count = fs.Read(buffer, 0, buffer.Length);
'fs.Close()
lblEDLoginStatus.Text = "Activation "
vipSoapInterfaceQueryServiceCert.ClientCertificates.Add(cert)
Dim vipSoapInterfaceService As New WebReference.GetCredentialInfoRequestType()
vipSoapInterfaceService.credentialId = "SMR23324543"
vipSoapInterfaceService.credentialType = WebReference.CredentialTypeEnum.STANDARD_OTP
vipSoapInterfaceService.requestId = "ABCD"
Dim vipSoapInterfaceResponse As New WebReference.GetCredentialInfoResponseType()
vipSoapInterfaceResponse = vipSoapInterfaceQueryServiceCert.getCredentialInfo(vipSoapInterfaceService)
lblEDLoginStatus.Text = "WebService Response: " + vipSoapInterfaceResponse.credentialId + ", Status:" + vipSoapInterfaceResponse.credentialStatus
End If
Finally
What I have tried so far:
1) Tried to specify the complete path of where the certificate file is on the machine.
2) Tried different formats of certificates (.pfx; .p12; .cer)
3) Tried the certificate open, read and sent into a buffer to take the input from a buffer.
(I removed the buffer logic when deploying on IIS7 as I placed the .pfx certificate file in System32 folder and the code could automatically recognise the certificate. But the local would not recognize)
4) I tried removing the "X509KeyStorageFlags.MachineKeySet" parameter. (I use that for the IIS7 compilation)
Any help is appreciated!!

Thanks! I could figure out what needed.
I had to create an App Pool for the Certificate and then provide Full Control permissions to that pool

Related

Replace SSL Certificate on Remote IIS 10 server from VB.NET

I'm attempting to automate the process of renewing my SSL certificates for a few different publicly accessible endpoints. I'm using Certify the Web's Certify SSL/TLS Certificate Management to complete the CSR and SSL generation and validation via Let's Encrypt and Certify DNS. This generates the .pfx files, which are then copied across the network to a location where my main "daily processing" application can access them and try to install them. I've been able to successfully get that application to install the certificates to the remote servers' certificate stores, but I'm unable to get the IIS 10 bindings reconfigured on the sites to use the new certificates.
For reference, here's the code for installing the certificate in the remote server's certificate store, which seems to work perfectly:
Imports System.Security.Cryptography.X509Certificates
Imports System.Security.Permissions
Imports Microsoft.Web.Administration
Private Sub AddCertificateToRemoteStore(ByVal HostName As String, ByVal StoreName As String, ByVal Certificate As X509Certificate2)
If HostName Is Nothing OrElse String.IsNullOrEmpty(HostName) Then
Throw New ArgumentNullException("HostName", "You must specify a server hostname")
ElseIf StoreName Is Nothing OrElse String.IsNullOrEmpty(StoreName) Then
Throw New ArgumentNullException("StoreName", "You must specify a certificate store name")
ElseIf Certificate Is Nothing Then
Throw New ArgumentNullException("Certificate", "A valid X509Certificate2 object is required")
Else
Dim CertStorePath As String = String.Format("\\{0}\{1}", HostName, StoreName)
Dim RootStorePath As String = String.Format("\\{0}\Root", HostName)
Dim IntermediateStorePath As String = String.Format("\\{0}\CA", HostName)
Using CertChain As New X509Chain
If CertChain.Build(Certificate) Then
Dim FindResults As X509Certificate2Collection
Dim StorePermissions As New StorePermission(PermissionState.Unrestricted)
With StorePermissions
.Flags = StorePermissionFlags.OpenStore Or StorePermissionFlags.AddToStore
.Assert()
End With
For C = 0 To CertChain.ChainElements.Count - 1
If C = 0 Then
Using CertificateStore As New X509Store(CertStorePath, StoreLocation.LocalMachine)
With CertificateStore
.Open(OpenFlags.ReadWrite Or OpenFlags.OpenExistingOnly)
FindResults = .Certificates.Find(X509FindType.FindByThumbprint, CertChain.ChainElements(C).Certificate.Thumbprint, False)
If FindResults.Count <= 0 Then
.Add(CertChain.ChainElements(C).Certificate)
End If
FindResults.Clear()
.Close()
End With
End Using
ElseIf C = CertChain.ChainElements.Count - 1 Then
Using RootStore As New X509Store(RootStorePath, StoreLocation.LocalMachine)
With RootStore
.Open(OpenFlags.ReadWrite Or OpenFlags.OpenExistingOnly)
FindResults = .Certificates.Find(X509FindType.FindByThumbprint, CertChain.ChainElements(C).Certificate.Thumbprint, False)
If FindResults.Count <= 0 Then
.Add(CertChain.ChainElements(C).Certificate)
End If
FindResults.Clear()
.Close()
End With
End Using
Else
Using IntermediateStore As New X509Store(IntermediateStorePath, StoreLocation.LocalMachine)
With IntermediateStore
.Open(OpenFlags.ReadWrite Or OpenFlags.OpenExistingOnly)
FindResults = .Certificates.Find(X509FindType.FindByThumbprint, CertChain.ChainElements(C).Certificate.Thumbprint, False)
If FindResults.Count <= 0 Then
.Add(CertChain.ChainElements(C).Certificate)
End If
FindResults.Clear()
.Close()
End With
End Using
End If
Next C
End If
End Using
End If
End Sub
With the certificate successfully added to the store (I've verified that it's there through certlm.msc), the next obvious step is to apply the new certificate to the existing IIS 10 site's bindings so it can actually be used for SSL/TLS communication. Here's what I'm currently using to try to accomplish that with the Microsoft.Web.Administration namespace:
Private Sub ApplyCertificateBinding(ByVal HostName As String, ByVal StoreName As String, ByVal ActiveCertificate As X509Certificate2)
If HostName Is Nothing OrElse String.IsNullOrEmpty(HostName) Then
Throw New ArgumentNullException("HostName", "You must specify a server hostname")
ElseIf StoreName Is Nothing OrElse String.IsNullOrEmpty(StoreName) Then
Throw New ArgumentNullException("StoreName", "You must specify a certificate store name")
ElseIf ActiveCertificate Is Nothing Then
Throw New ArgumentNullException("ActiveCertificate", "A valid X509Certificate2 object is required")
Else
Dim SSLSiteName As String = ActiveCertificate.GetNameInfo(X509NameType.DnsName, False)
Dim HostSites As New List(Of Site)
Using HostManager As ServerManager = ServerManager.OpenRemote(HostName)
For Each Site As Site In HostManager.Sites
If Site.Name = SSLSiteName Then
HostSites.Add(Site)
Else
For Each Binding In Site.Bindings
If Binding.Host = SSLSiteName Then
HostSites.Add(Site)
Exit For
End If
Next Binding
End If
Next Site
For Each Site As Site In HostSites
For Each SiteBinding In Site.Bindings
If SiteBinding.Protocol = "https" Then
Dim NewBinding As Binding = Site.Bindings.CreateElement
NewBinding.CertificateStoreName = StoreName
NewBinding.Protocol = "https"
NewBinding.CertificateHash = ActiveCertificate.GetCertHash
NewBinding.BindingInformation = SiteBinding.BindingInformation
SiteBinding = NewBinding
HostManager.CommitChanges()
End If
Next SiteBinding
Site.Stop()
'PROBABLY A BETTER WAY TO HANDLE THIS
Do While Site.State <> ObjectState.Stopped
Loop
Site.Start()
'AND THIS
Do While Site.State <> ObjectState.Started
Loop
Next Site
End Using
End If
End Sub
This code gets all the way through the process without error, but it doesn't seem to actually make the necessary changes for the site to start using the new certificate. I manually restarted/refreshed the site from the IIS interface on the host, but it still doesn't seem to take effect. I've checked both the binding settings in IIS and the site itself (browser) and confirmed that it's still using the "old" certificate.
I've also tried to directly set the certificate hash of the SiteBinding object to the X509Certificate2.GetCertHash value, as well as assigning the SiteBinding object to the NewBinding object before trying to set the CertificateHash property as above. Unfortunately, both of these methods throw a NotSupportedException stating: The specified operation is not supported when a server name is specified.
Additionally, there are settings from the "live" SiteBinding object that can't be set on the NewBinding object (like the .Host property). All I really want to be able to do is to change the active certificate on that site and not muck around with any of the binding's other properties. The wording of the exception seems to indicate that what I'm trying to do can't be done remotely (at least, not with the Microsoft.Web.Administration API), but I can't imagine that there isn't a way to accomplish this goal. I'm sure I'm simply missing/overlooking something here, but my Google-fu is failing me and I need to get this project functional as soon as possible.
EDIT #1
I added the Site.Stop() and Site.Start() methods to restart the site from code, but it didn't make any difference. Plus, I'm sure there's probably a better way to implement that than what I've added to the code above.
EDIT #2
I've refactored some things to align with the suggestion from Joel Coehoorn in the comments. The code above represents the current state but still produces the same result: No exception occurs, but I cannot get the bindings updated to use the new certificate, even though it's apparently added to the store.
Just to triple-check, I went to the site's bindings in IIS and the new certificate does show up as available to apply to the site. I know there's a lot of blur, but the one highlighted in blue is what I'm trying to apply to the binding:
EDIT #3
After reading Conrado Clark's Developer Log entry titled Adding SSL Binding to a remote website using Microsoft.Web.Administration, I decided to try to add the NewBinding to IIS instead of just updating the existing one:
Site.Bindings.Add(NewBinding)
Site.Bindings.Remove(SiteBinding)
HostManager.CommitChanges()
This produced a different exception: Cannot add duplicate collection entry of type 'binding' with combined key attributes 'protocol, bindingInformation' respectively set to 'https, XXX.XXX.XXX.XXX:443:'
So, I tried removing the existing binding first:
Site.Bindings.Remove(SiteBinding)
Site.Bindings.Add(NewBinding)
HostManager.CommitChanges()
This time it made it through the first two steps (Site.Bindings.Remove() and Site.Bindings.Add()), but when it tried to execute HostManager.CommitChanges(), I got another "new" exception: A specified logon session does not exist. It may already have been terminated. (Exception from HRESULT: 0x80070520). Additionally, it "reset" the binding so there was no certificate installed on the site.
Just to see what would happen, I tried to commit the Site.Bindings.Remove() before trying to add it back.
Site.Bindings.Remove(SiteBinding)
HostManager.CommitChanges()
Site.Bindings.Add(NewBinding)
HostManager.CommitChanges()
The initial commit seemed to work fine (and the binding disappeared completely from IIS), but when it went to add the new binding, I got this: The configuration object is read only, because it has been committed by a call to ServerManager.CommitChanges(). If write access is required, use ServerManager to get a new reference.
I manually recreated the binding (thankfully I had taken a quick screenshot before I started messing with it), but that last error has given me an idea for my next attempt. I'm going to try to break the Add() and Remove() methods out to new methods where I can open new instances of the ServerManager object specifically for this purpose. I'll come back when I've had a chance to write/test that.
EDIT #4
I tried the above and still ended up with an error stating that A specified logon session does not exist. It may already have been terminated. (Exception from HRESULT: 0x80070520). So, just to see if I could determine the cause of the problem, I went to IIS and manually tried to apply the new certificate. I got the SAME EXACT ERROR from IIS! (Yes, I know... I probably should have checked that bit a long time ago, but here we are)
It looks like there's a problem with the certificate in the store. Digging around a little deeper, I found an old reference on the MSDN forums talking about this error being related to a missing private key. This makes it sound like I missed a step in the certificate installation process, so I guess I need to take another step back and figure out what's wrong with that method before proceeding.
a NotSupportedException stating: The specified operation is not supported when a server name is specified.
That's exactly what we should expect. Microsoft didn't develop that API to manage remote server's everything. As proof, you can see that even IIS Manager (built upon the same API) does not support managing server certificates of a remote machine.
If I were you, I will actually use other approaches, such as developing a dedicated small demon app to run on each IIS machines, so that actual communication via Microsoft.Web.Administration happens locally, not remotely.
I've actually gotten this working! As identified in my multiple EDITs to the OP, it seems the main problem actually had to do with the original import of the certificate. Once that issue was resolved, everything else pretty much fell into place. I've provided the full working code at the end if you want the TL;DR version, but here's what I found:
EXPLANATION/TROUBLESHOOTING
After a bunch of further research into the individual errors I encountered in my initial testing - specifically the A specified logon session does not exist. It may already have been terminated. error - I ran across this SO question: IIS 7 Error "A specified logon session does not exist. It may already have been terminated." when using https.
In the linked answer from user naimadswdn, they state that:
I fixed it by running:
certutil.exe -repairstore $CertificateStoreName $CertThumbPrint
where $CertificateStoreName is store name, and $CertThumbPrint is the thumbprint of imported certificate.
Another answer to that same question from user Ed Greaves provides a bit of explanation for the underlying cause of the problem:
This storage provider is a newer CNG provider and is not supported by IIS or .NET. You cannot access the key. Therefore you should use certutil.exe to install certificates in your scripts. Importing using the Certificate Manager MMC snap-in or IIS also works but for scripting, use certutil as follows:
The original question was asked about trying to import the certificate directly in IIS and, since my testing showed that I wasn't able to do that either, I went ahead and tried to repair the certificate from the command line on the server with certutil. I went back to IIS and, this time was able to successfully apply the certificate to the binding without error.
Since that worked, I reset the binding and tried again to set it to the new certificate through my code. No exception was encountered and, when I went to check the binding in IIS, it showed the correct certificate selected. I verified through my browser that it was showing the new certificate and everything seems to be working as expected/intended.
Of course, I want this process automated, so I can't be logging in to the server to repair certificates every 90 days. So, now I have two options:
Keep my existing code for adding the certificate to the store, then repair the certificate (as per naimadswdn's answer), or
Take Ed Greaves' suggestion and use certutil to perform the actual import of the certificate.
Since I already have it importing the certificate into the store without any exception being thrown, I decided to go with the former solution (for now, at least). Of course, I want to do this remotely, so I've chosen to use WMI to execute certutil on the server (see the CertUtilRepairCertificateInStore() method in the full code listing below).
(FINAL) TESTING
I reset the binding on the IIS site and deleted the certificate from the store to test my "new" process:
The certificate was successfully added to the store by the AddCertificateToRemoteStore() method, but I paused execution before allowing it to "repair" the certificate in the store.
While debugging was paused, I tried to manually apply the certificate to the binding in IIS. This resulted in the same logon session does not exist error.
I allowed the CertUtilRepairCertificateInStore() method to run the certutil on the server through WMI. I didn't get any exceptions.
I paused debugging again after the certificate was "repaired" and tried to manually apply the certifiate to the binding in IIS. Now the binding was successfully updated to use the new certificate.
I manually reset the binding in IIS to use the old certificate and allowed my ApplyCertificateBinding() method to execute.
This time, the method completed without throwing any exceptions, so I went into IIS and verified that it does, indeed, have the new certificate applied to the appropriate binding. As one last bit of verification, I went to my browser and checked the certificate from the site itself and it shows the correct new Let's Encrypt certificate. It seems that, along with some other minor tweaks along the way, the certutil -repairstore call was the final solution.
SOLUTION (CODE)
After all of that, I decided to keep my original code as-is and simply add the WMI bit to "repair" the certificate immediately after importing it to ensure it's ready to be applied to the binding. Yes, I could allow that to be handled in a Try/Catch block for the binding, but I'd rather just avoid the issue altogether. Here's a (mostly) complete listing of the functional code I'm using now and, so far, it seems to work exactly as I require/expect.
(quick edit - I moved the HostManager.CommitChanges() execution outside of the For/Next loop through the HostSites list because once the commit is executed, the code can't make any more changes until a new connection is opened.)
Imports System.Security.Cryptography.X509Certificates
Imports System.Security.Permissions
Imports Microsoft.Web.Administration
Friend Async Function InstallSSLCertificate(ByVal PFXFile As IO.FileInfo) As Task(Of X509Certificate2)
If PFXFile Is Nothing Then
Throw New ArgumentNullException("PFXFile", "You must provide a valid PFX certificate file")
ElseIf Not PFXFile.Exists OrElse PFXFile.Length <= 0 Then
Throw New ArgumentException("PFXFile", "You must provide a valid PFX certificate file")
Else
Dim CertPFX As X509Certificate2 = Nothing
Dim CertSubject As String = String.Empty
'THE GetOperationalCredentials() METHOD (not defined here) IS A CUSTOM UTILITY METHOD FOR RETRIEVING A SPECIFIC SET OF CREDENTIALS STORED ELSEWHERE
Dim PFXCredentials As Net.NetworkCredential = GetOperationalCredentials(SecurityOperation.PFX)
Dim UserCredentials As Net.NetworkCredential = GetOperationalCredentials(SecurityOperation.Server)
If Not PFXCredentials Is Nothing Then
'BUILD/EXTRACT THE X509Certificate2 OBJECT INFORMATION FROM THE PFX FILE
'MAKE SURE TO SET THE X509KeyStorageFlags.MachineKeySet AND X509KeyStorageFlags.PersistKeySet FLAGS TO
' ENSURE THE CERTIFICATE PERSISTS IN THE STORE
CertPFX = New X509Certificate2(PFXFile.FullName, PFXCredentials.SecurePassword, X509KeyStorageFlags.MachineKeySet Or X509KeyStorageFlags.PersistKeySet)
CertSubject = CertPFX.GetNameInfo(X509NameType.DnsName, False)
If Not CertSubject Is Nothing AndAlso Not String.IsNullOrEmpty(CertSubject.Trim) Then
If CertSubject.ToLower.Trim.StartsWith("ftps") Then
'THIS CONDITIONAL (not defined here) IS FOR HANDLING CERTIFICATE(S) THAT CANNOT BE INSTALLED FROM A CERTIFICATE STORE
If Not Await InstallSSLCertificateFromPEM(CertSubject, PFXFile, UserCredentials) Then
Return Nothing
End If
Else
If Not InstallCertificateFromPFX(CertPFX, UserCredentials) Then
Return Nothing
End If
End If
Else
Return Nothing
End If
Else
Return Nothing
End If
Return CertPFX
End If
End Function
Private Function InstallCertificateFromPFX(ByVal Certificate As X509Certificate2, ByVal Credentials As Net.NetworkCredential) As Boolean
Dim Subject As String = Certificate.GetNameInfo(X509NameType.DnsName, False)
Dim Hostname As String = String.Empty
Dim StoreName As String = String.Empty
If Subject.ToLower.Trim.StartsWith("www") Then
Hostname = "<WEBSITE_SERVER_NAME>"
StoreName = "WebHosting"
ElseIf Subject.ToLower.Trim.StartsWith("rdp") Then
Hostname = "<REMOTE_DESKTOP_SERVER_NAME>"
StoreName = "My"
End If
Try
AddCertificateToRemoteStore(Hostname, StoreName, Certificate, Credentials)
ApplyCertificateBinding(Hostname, StoreName, Certificate)
'THE CleanUpCertifcateStore() METHOD (not defined here) IS SOMETHING I INTEND TO
' IMPLEMENT TO GET RID OF OLD UNUSED/EXPIRED CERTIFICATES
CleanUpCertifcateStore(Hostname, StoreName, Certificate)
Return True
Catch ex As Exception
MessageBox.Show(ex.Message)
Return False
End Try
End Function
''' <summary>
''' Connect to the remote certificate store and import the details from a valid <see cref="X509Certificate2"/> object
''' </summary>
''' <param name="HostName">The hostname of the server where the certificate store is located</param>
''' <param name="StoreName">The name of the certificate store into which the certificate should be imported</param>
''' <param name="Certificate">A valid <see cref="X509Certificate2"/> object containing the details of the certificate to be imported</param>
''' <param name="Credentials">A valid <see cref="Net.NetworkCredential"/> object for passing to the certificate repair method for establishing a WMI connection</param>
Private Sub AddCertificateToRemoteStore(ByVal HostName As String, ByVal StoreName As String, ByVal Certificate As X509Certificate2, ByVal Credentials As Net.NetworkCredential)
If HostName Is Nothing OrElse String.IsNullOrEmpty(HostName) Then
Throw New ArgumentNullException("HostName", "You must specify a server hostname")
ElseIf StoreName Is Nothing OrElse String.IsNullOrEmpty(StoreName) Then
Throw New ArgumentNullException("StoreName", "You must specify a certificate store name")
ElseIf Certificate Is Nothing Then
Throw New ArgumentNullException("Certificate", "A valid X509Certificate2 object is required")
Else
'SET UP THE PATHS TO THE APPROPRIATE CERTIFICATE STORES
Dim CertStorePath As String = String.Format("\\{0}\{1}", HostName, StoreName)
Dim RootStorePath As String = String.Format("\\{0}\Root", HostName)
Dim IntermediateStorePath As String = String.Format("\\{0}\CA", HostName)
'USE THE X509Chain OBJECT TO MAKE IT EASIER TO IDENTIFY THE APPROPRIATE STORE FOR
' EACH CERTIFICATE EXTRACTED FROM THE PFX
Using CertChain As New X509Chain
If CertChain.Build(Certificate) Then
Dim FindResults As X509Certificate2Collection
Dim StorePermissions As New StorePermission(PermissionState.Unrestricted)
With StorePermissions
.Flags = StorePermissionFlags.OpenStore Or StorePermissionFlags.AddToStore
.Assert()
End With
For C = 0 To CertChain.ChainElements.Count - 1
If C = 0 Then
'FIRST ELEMENT IN THE CHAIN = CERTIFICATE FOR THE SITE
Using CertificateStore As New X509Store(CertStorePath, StoreLocation.LocalMachine)
With CertificateStore
.Open(OpenFlags.ReadWrite Or OpenFlags.OpenExistingOnly)
FindResults = .Certificates.Find(X509FindType.FindByThumbprint, CertChain.ChainElements(C).Certificate.Thumbprint, False)
If FindResults.Count <= 0 Then
.Add(CertChain.ChainElements(C).Certificate)
End If
FindResults.Clear()
.Close()
End With
End Using
'REPAIR THE CERTIFICATE'S PROVIDER/PRIVATE KEY IN THE REMOTE STORE
CertUtilRepairCertificateInStore(HostName, StoreName, CertChain.ChainElements(C).Certificate, Credentials)
ElseIf C = CertChain.ChainElements.Count - 1 Then
'LAST ELEMENT IN THE CHAIN = ROOT CA CERTIFICATE
Using RootStore As New X509Store(RootStorePath, StoreLocation.LocalMachine)
With RootStore
.Open(OpenFlags.ReadWrite Or OpenFlags.OpenExistingOnly)
FindResults = .Certificates.Find(X509FindType.FindByThumbprint, CertChain.ChainElements(C).Certificate.Thumbprint, False)
If FindResults.Count <= 0 Then
.Add(CertChain.ChainElements(C).Certificate)
End If
FindResults.Clear()
.Close()
End With
End Using
Else
'ANY ELEMENT BETWEEN THE FIRST AND LAST IN THE CHAIN = INTERMEDIATE CA CERTIFICATE(S)
Using IntermediateStore As New X509Store(IntermediateStorePath, StoreLocation.LocalMachine)
With IntermediateStore
.Open(OpenFlags.ReadWrite Or OpenFlags.OpenExistingOnly)
FindResults = .Certificates.Find(X509FindType.FindByThumbprint, CertChain.ChainElements(C).Certificate.Thumbprint, False)
If FindResults.Count <= 0 Then
.Add(CertChain.ChainElements(C).Certificate)
End If
FindResults.Clear()
.Close()
End With
End Using
End If
Next C
End If
End Using
End If
End Sub
''' <summary>
''' Use WMI to execute certutil.exe on the remote server to "repair" the certificate and correct issues with the provider/private key
''' </summary>
''' <param name="HostName">The hostname of the server where the certificate store is located</param>
''' <param name="StoreName">The name of the certificate store into which the certificate has been imported</param>
''' <param name="ActiveCertificate">A valid <see cref="X509Certificate2"/> object containing the details of the certificate to be repaired</param>
''' <param name="Credentials">A valid <see cref="Net.NetworkCredential"/> object for establishing the WMI connection</param>
Private Sub CertUtilRepairCertificateInStore(ByVal HostName As String, ByVal StoreName As String, ByVal ActiveCertificate As X509Certificate2, ByVal Credentials As Net.NetworkCredential)
Dim WMIOptions As New Management.ConnectionOptions
Dim WMIScope As Management.ManagementScope
Dim GetOptions As New Management.ObjectGetOptions
Dim WMIProcess As Management.ManagementClass
Dim WMIParameters As Management.ManagementBaseObject
With WMIOptions
.Username = Credentials.Domain & "\" & Credentials.UserName
.Password = Credentials.Password
.Impersonation = Management.ImpersonationLevel.Impersonate
.Authentication = Management.AuthenticationLevel.PacketPrivacy
.EnablePrivileges = True
End With
WMIScope = New Management.ManagementScope(String.Format("\\{0}\root\cimv2", HostName), WMIOptions)
WMIScope.Connect()
WMIProcess = New Management.ManagementClass(WMIScope, New Management.ManagementPath("root\cimv2:Win32_Process"), GetOptions)
WMIParameters = WMIProcess.GetMethodParameters("Create")
WMIParameters("CommandLine") = String.Format("cmd.exe /c C:\Windows\System32\certutil.exe -repairstore {0} {1}", StoreName, ActiveCertificate.Thumbprint)
WMIProcess.InvokeMethod("Create", WMIParameters, Nothing)
End Sub
''' <summary>
''' Connect to IIS on a remote host to apply a new certificate to a site's SSL bindings
''' </summary>
''' <param name="HostName">The hostname of the server where the certificate store is located</param>
''' <param name="StoreName">The name of the certificate store into which the certificate has been imported</param>
''' <param name="ActiveCertificate">A valid <see cref="X509Certificate2"/> object containing the details of the certificate that has been imported</param>
Private Sub ApplyCertificateBinding(ByVal HostName As String, ByVal StoreName As String, ByVal ActiveCertificate As X509Certificate2)
If HostName Is Nothing OrElse String.IsNullOrEmpty(HostName) Then
Throw New ArgumentNullException("HostName", "You must specify a server hostname")
ElseIf StoreName Is Nothing OrElse String.IsNullOrEmpty(StoreName) Then
Throw New ArgumentNullException("StoreName", "You must specify a certificate store name")
ElseIf ActiveCertificate Is Nothing Then
Throw New ArgumentNullException("ActiveCertificate", "A valid X509Certificate2 object is required")
Else
Dim SSLSiteName As String = ActiveCertificate.GetNameInfo(X509NameType.DnsName, False)
Dim HostSites As New List(Of Site)
Using HostManager As ServerManager = ServerManager.OpenRemote(HostName)
'FIND THE SITE(S) IN IIS THAT MATCH(ES) THE DETAILS FROM THE SSL CERTIFICATE
'>>> THIS IS **FAR FROM** BULLET-PROOF, BUT I AM NOT SURE HOW TO MAKE IT BETTER <<<
For Each Site As Site In HostManager.Sites
If Site.Name = SSLSiteName Then
HostSites.Add(Site)
Else
For Each Binding In Site.Bindings
If Binding.Host = SSLSiteName Then
HostSites.Add(Site)
Exit For
End If
Next Binding
End If
Next Site
For Each Site As Site In HostSites
'USE THE .ToList() METHOD TO BASICALLY MAKE AN IN-MEMORY "COPY" OF THE
' BindingCollection OBJECT WHERE CHANGES CAN BE MADE WITHOUT MODIFYING THE
' COLLECTION ITSELF (which would cause the For/Next loop to generate an
' exception whenever any changes are made to the site's bindings)
For Each SiteBinding In Site.Bindings.ToList
If SiteBinding.Protocol = "https" Then
If Not SiteBinding.CertificateHash.SequenceEqual(ActiveCertificate.GetCertHash) Then
'CANNOT JUST EDIT OR "REPLACE" AN EXISTING BINDING ON A REMOTE IIS HOST SO
' CREATE A NEW BINDING TO ADD TO THE SITE AFTER THE EXISTING BINDING HAS
' BEEN REMOVED
Dim NewBinding As Binding = Site.Bindings.CreateElement
NewBinding.CertificateStoreName = StoreName
NewBinding.Protocol = "https"
NewBinding.CertificateHash = ActiveCertificate.GetCertHash
NewBinding.BindingInformation = SiteBinding.BindingInformation
'THIS PROCESS MUST BE COMPLETED IN THIS ORDER
Site.Bindings.Remove(SiteBinding)
Site.Bindings.Add(NewBinding)
End If
End If
Next SiteBinding
'RESTARTING THE SITE IN IIS (there is almost certainly a better way to do this)
Site.Stop()
Do While Site.State <> ObjectState.Stopped
Loop
Site.Start()
Do While Site.State <> ObjectState.Started
Loop
Next Site
HostManager.CommitChanges()
End Using
End If
End Sub
I'm stepping into all of that from a method that looks for "pending" .pfx files that are waiting to be processd:
Private Async Sub CheckForNewSSLCertificates()
Dim PendingSSLFolder As DirectoryInfo = New DirectoryInfo("\\SERVER\Certify\PendingSSL\")
For Each PFXFile As FileInfo In PendingSSLFolder.GetFiles("*.pfx")
Dim CertPFX As Security.Cryptography.X509Certificates.X509Certificate2 = Await InstallSSLCertificate(PFXFile)
If Not CertPFX Is Nothing Then
'[ARCHIVE THE PFX]
End If
Next PFXFile
End Sub
I know the documentation is a bit sparse but, if you find ways to make this more effective/efficient, or if you have any questions about what it's doing here, please feel free to let me know. Eventually I may try to just use certutil to import the PFX and one-shot that process, but for now I just wanted to leave this here for anyone else who's trying to implement some "centralized" automation for SSL certificate management.

PowerShell remote call. Access is denied from webserver

i write a code for remote access for exchange powershell from asp.net application to enable remote mail using vb.net and exchange 2016
the command run successfully from my visual studio debug
but when i put in iis web server it's giving me
Connecting to remote server "" failed with the following error
message : Access is denied
this is is the code
Function enableRemoteEmail(ByVal samaccount As String, ByVal email As String) As String
Dim ServerUri As String = "http://{mailserver}/powershell"
Dim SchemaUri As String = "http://schemas.microsoft.com/powershell/Microsoft.Exchange"
Dim userName As String = AccountOperatorLogon
Dim password As New System.Security.SecureString
For Each x As Char In AccountOperatorPassword
password.AppendChar(x)
Next
Dim PSCredential As New PSCredential(userName, password)
Dim ConnectionInfo As WSManConnectionInfo = New WSManConnectionInfo(New Uri(ServerUri), SchemaUri, PSCredential)
ConnectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic
Dim RemoteRunspace As Runspace = RunspaceFactory.CreateRunspace(ConnectionInfo)
Dim RemotePowerShell As PowerShell = PowerShell.Create
RemotePowerShell.AddCommand("Enable-RemoteMailbox")
RemotePowerShell.AddParameter("Identity", samaccount)
RemotePowerShell.AddParameter("RemoteRoutingAddress",email )
RemotePowerShell.AddParameter("Credential", PSCredential)
' Open the remote runspace on the server.
RemoteRunspace.Open()
' Associate the runspace with the Exchange Management Shell.
RemotePowerShell.Runspace = RemoteRunspace
Dim TheResult As Collection(Of PSObject)
Dim TheResultString As String = ""
TheResult = RemotePowerShell.Invoke
For Each RSLT As PSObject In TheResult
TheResultString += RSLT.ToString() + "<br/>"
Next
RemoteRunspace.Close()
' Invoke the Exchange Management Shell to run the command.
Return TheResultString
End Function
i found the solution for anyone have the same problem
create user as a member of (Recipient Management group)
IIS change (for exchange server) navigate to IIS Manager | Default Website | PowerShell Change the physical path from: C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\PowerShell to: C:\Program Files\Microsoft\Exchange Server\V15\ClientAccess\PowerShell
3.After the change: IISRESET
and it will work fine

DocuSign Embedded RequestRecipientToken - 500 internal Server Error

-Issue Resolved
On Live accounts that Use Embedded Signing, The Account Manager will need to either disable In Session or apply the accounts X.509 Certificate.
There is no way to bypass without the DocuSign Account Managers/Customer Support making updates to non forward facing settings.
-Using SOAP API in a VB.NET application.
I have recently moved our application to live after endless testing on the staging environment. Everything is working as expected except when I get too opening the recipient signature page.
When I make the RequestRecipientToken call I receive the error "One or both of Username and Password are invalid."
The API log give me 00_Internal Server Error_RequestRecipientToken.txt
the log doesn't really give me any info just shows that call.
I know this all works on staging, and I have tried to have all my account settings the same on both environments.
After looking all over I saw that X509 Certificate was recommended, so I added this last line in my DSAPI
-update code:
Protected Overrides Function GetWebRequest(uri As Uri) As WebRequest
IntegratorKey = SettingsHelper.sIntegrationKey
Password = SettingsHelper.sAPIPassword
Username = SettingsHelper.sAPIUserName
Dim r As System.Net.HttpWebRequest = MyBase.GetWebRequest(uri)
r.Headers.Add("X-DocuSign-Authentication", "<DocuSignCredentials><Username>" & Username & "</Username><Password>" & Password & "</Password><IntegratorKey>" & IntegratorKey & "</IntegratorKey></DocuSignCredentials>")
Dim store As X509Store = New X509Store(StoreName.My, StoreLocation.CurrentUser)
store.Open(OpenFlags.ReadOnly)
Dim certs As X509Certificate2Collection = store.Certificates.Find(X509FindType.FindByIssuerDistinguishedName, <{OUR VALUE}>, False)
Dim cert As New X509Certificate2
If certs.Count > 0 Then
cert = certs(0)
Dim securityToken = New X509SecurityToken(cert)
r.ClientCertificates.Add(securityToken.Certificate)
End If
store.Close()
Return r
End Function
This did not help.
Any help would be appreciated.

Cannot upload the file to dropbox when published on server

I'm trying to upload a file to dropbox. The file gets uploaded and code works when i'm running locally. But file never gets uploaded when published on the server. I'm having the following error .
Could not load type 'System.Security.Authentication.SslProtocols' from assembly 'System.Net.Primitives, Version=4.0.0.0, Culture=neutral, PublicKeyToken=*********'.
I'm trying the below code sample to upload it
public sub uploadtodropbox()
Using httpclient As New HttpClient
httpclient.Timeout = TimeSpan.FromMinutes(20)
Dim _DropboxClient = New DropboxClient(ApiKeyDropBox, config)
Dim resultstring As String = UploadToDB(_DropboxClient, dropboxdir, docName, fileBytes)
_DropboxClient.Dispose()
If Not resultstring = "RanToCompletion" Then
ErrorMsg &= "The following error occured: " & resultstring
End If
End Using
End Sub
and this is the funtion that is uploading to Dropbox
Private Function UploadToDB(_DropboxClient As DropboxClient, Directory As String, Filename As String, Content As [Byte]()) As String
Try
Dim result As String = "Unknown"
Dim trycnt As String = 0
Dim tryLimit As String = 20
Dim respnse As Task(Of FileMetadata)
Using _mStream = New MemoryStream(Content)
respnse = _DropboxClient.Files.UploadAsync(Convert.ToString(Directory & Convert.ToString("/")) & Filename, WriteMode.Overwrite.Instance, body:=_mStream)
While Not respnse.IsCompleted And trycnt < tryLimit
Threading.Thread.Sleep(1000)
trycnt += 1
result = respnse.Status.ToString()
End While
UploadToDB = result
End Using
End try
END function.
I have tried without using the httpclient then i am getting this error :
The type initializer for 'Dropbox.Api.DropboxRequestHandlerOptions' threw an exception.
Thank you
I go the answer . I have system.Net.primitives assembly that is creating a problem uploading the file to Dropbox. All i need to do is delete the reference and also the System.Net.Http has versions have been set wrong in the web.config file. Once i set that in the configuration everything works fine.
For the certificate I have set the on post back to validate and allow the certificates generated in the event handler.

FtpWebRequest.GetRequestStream hang up and fails.

I have wrote a web service, in a nutshell it uses openpop to get email messages does stuff with the content to insert into databases and saves attachments which are images. That works fine when i save images locally, it does exactley what it is suppose to. Now an added requirment was to save images to an FTP directory, so i can create my folders dynamically (they are created based upon timestamp) and that works well. My problem comes from when i try to save them to the ftp. Yes my user name and password are correct, otherwise i wouldn't be creating the directory.
Private Sub UploadFile(ByVal fileToSave As FileInfo, ByVal path As String)
Dim UploadRequest As FtpWebRequest = DirectCast(WebRequest.Create("ftp://UserName:Passowrd#999.99.999.9" & path), FtpWebRequest)
UploadRequest.Credentials = New NetworkCredential("PicService", "grean.matching18")
UploadRequest.Method = System.Net.WebRequestMethods.Ftp.UploadFile
UploadRequest.UseBinary = True
UploadRequest.UsePassive = True
' Const BufferSize As Integer = 2048
' Dim content(BufferSize - 1) As Byte, dataRead As Integer
Dim bFile() As Byte = System.IO.File.ReadAllBytes(fileToSave.ToString)
'UploadRequest.ContentLength = content.Length
Using FileStream1 As FileStream = fileToSave.OpenRead()
Try
'open request to send
Using RequestStream As Stream = UploadRequest.GetRequestStream
End Using
Catch ex As Exception
Finally
'ensure file closed
FileStream1.Close()
End Try
End Using
End Sub
I have tried using Passive False and Binary False as well, i did more research on my stack trace.
And found this article but no solution as of yet. Any input would be appreciated, i am also posting another question on windows services for different issue. If you would like to take a shot at it, the other question isnt about ftp but permissions for a service on windows server 2003
This may not be the solution but I've found that the URI string has to be 'just right' and that what is 'just right' varies by the ftp server.
So ftp://server/directory/file works on some servers but needs to be ftp://server//directory/file to work on others (note the double slash after the server name)
Aso, your URI has 'password' spelled incorrectly: ftp://UserName:Passowrd#999.99.999.9 and you are supplying the credentials in a separate code line as well.