Service Bus and Custom Self Signed Certificates with a High Availability/Multiple Computing Nodes in the Farm

Wow.  I had an adventure with certificates with an On Premise Service Bus installation.  At first, I was using the “Auto Generate” option.  However, I ran into an issue where I uninstalled Service Bus (but I left the certificates in the store), and tried to reinstall Service Bus (this time using the certificates that were already in the store.  (I was basically pseudo testing disaster recovery).

(Side note : Disaster recovery is kinda talked about here : http://sharepoint-community.net/profiles/blogs/workflow-manager-disaster-recovery )

I have posted something about the error (reinstalling by reusing the same certificates) here: https://social.msdn.microsoft.com/Forums/azure/en-US/6ae07eb9-121a-4187-8181-e198490a530d/using-the-auto-generated-certificates-causes-bad-key-issue-if-the-certificates-are-used-again-on?forum=servbus

Basically, I was getting a “Bad Key” error.

So let’s review a little bit.  The “Auto-Generate” option created certificates like this:

Certificate : IssuedTo: *Machine1.fullyqualified.domainname.com* Issued By: AppServerGeneratedSBCA Intended Purposes : Server Authentication

——–
Certificate : IssuedTo: AppServerGeneratedSBCA Issued By: AppServerGeneratedSBCA Intended Purposes : <All>

The most interesting part of the Auto-Generate option is the name of the “Service Authentication” certificate. *Machine1.fullyqualified.domainname.com* which reflects the first machine on which Service Bus was installed.

Then I documented where Microsoft (via the Service Bus installer) put these certficates:

Auto Created Certificates

Certificate : IssuedTo: *Machine1.fullyqualified.domainname.com* Issued By: AppServerGeneratedSBCA Intended Purposes : Server Authentication
On the “Server” aka “Machine1” (where you first installed Service Bus)
Cert Stores : (Personal) (This has the private key)

On “MachineN” (The ServiceBus “Add Farm” will auto-voodoo put the certs on MachineN)
Cert Stores : (Personal) (This has the private key)

On a “ClientMachine” (You must manually put the certs on this machine)
Cert Stores : (Personal) (No private key, aka public key)
——–
Certificate : IssuedTo: AppServerGeneratedSBCA Issued By: AppServerGeneratedSBCA Intended Purposes : <All>
On the “Server” aka “Machine1” (where you first installed Service Bus)
Cert Stores : (Personal, Trusted Root Certificate Authorities, Intermediate Certificate Authorities)….they are the same thumbprint) (This has the private key)

On “MachineN” (The ServiceBus “Add Farm” will auto-voodoo put the certs on MachineN)
Cert Stores : (Trusted Root Certificate Authorities) (This has the private key)

On a “ClientMachine” (You must manually put the certs on this machine)
Cert Stores : (Trusted Root Certificate Authorities) (No private key, aka public key)

So I started down the road of generating the certificates myself.

Using help I got from this article ( http://www.22bugs.co/post/sb-farm-errors-and-their-solutions/ ), I came up with this “.bat” file code to create the necessary certificates.  (If you are quick-reading this blog entry, then the code below will NOT work for multiple-computing-nodes in the farm.  Aka, don’t use this below code)

REM https://blogs.technet.microsoft.com/jhoward/2005/02/02/how-to-use-makecert-for-trusted-root-certification-authority-and-ssl-certificate-issuance/#comment-34025

set __rootDirectory=C:\LetsMakeSomeCerts\MakeCert\SelfSignedWithTrustedRootAuth\Output\
set __makecertExe=C:\LetsMakeSomeCerts\MakeCert\makecert.exe
set __pvk2pfxExe=C:\LetsMakeSomeCerts\MakeCert\PVK2PFX.exe

set __trustRootAuthorityName=MeAndMyselfTrustedRootAuthority

MD “%__rootDirectory%”

@ECHO OFF

ECHO Need to run As-Administrator to install certs in cert-store

FOR /f “tokens=2,* delims= ” %%a in (‘IPCONFIG ^/ALL ^| FINDSTR “Primary Dns”‘) do set tempsuffix=%%b
FOR /f “tokens=1,2 delims=:” %%a in (‘echo %tempsuffix%’) do set dnssuffix=%%b
SET __FQDN=%COMPUTERNAME%.%DNSSUFFIX:~1%

ECHO Server FQDN: %__FQDN%
%__makecertExe% -pe -r -n “CN=%__trustRootAuthorityName%” -ss my -sr LocalMachine -a sha1 -sky signature -b 10/01/2016 -e 12/31/9998 -sv “%__rootDirectory%%__trustRootAuthorityName%PrivateKeyfile.pvk” “%__rootDirectory%%__trustRootAuthorityName%.cer”

%__makecertExe% -pe -n “CN=%__FQDN%” -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -in “%__trustRootAuthorityName%” -is MY -ir LocalMachine -sp “Microsoft RSA SChannel Cryptographic Provider” -sy 12 -b 10/01/2016 -e 12/31/9998 -sv “%__rootDirectory%%__FQDN%.pvk” “%__rootDirectory%%__FQDN%.cer
set __rootDirectory=
set __makecertExe=
set __pvk2pfxExe=
SET __FQDN=
set __trustRootAuthorityName=

pause

And that was working well, until I tried to add more computing-nodes to the farm.  One reason you add more computing-nodes to the farm is so that if one node fails, the others will pick it up.  Aka, High Availability.

So the setup looked like this:

First Machine with Service Bus : Machine1.fullyqualified.domainname.com
Second Machine with Service Bus : Machine2.fullyqualified.domain.com

Well, then I “took down” the first machine, the “client” should be able to continue by communicating with Machine2.fullyqualified.domain.com.  Mine was failing.😦

So after looking at the certificates that Auto-Generate created, I discovered that the “Subject Alternate Name” was set to
DNS Name=*.fullyqualified.domain.com
and that was the secret that allowed the client to talk to the second (or the third, or the fourth or the fifth) machine in the farm.

But “makecert.exe” doesn’t support setting the “Subject Alternate Name”.

Side note, you can read about the “Subject Alternate Name” here :
https://www.digicert.com/subject-alternative-name.htm

Gaaaaaaaaaaaaaaaaaaaaaaaa!  (That’s my version of a Homer Simpson “D’oh”)

So I couldn’t use Auto-Generate and “makecert.exe” couldn’t set (all of) the properties correctly.

So I tried to find some ways to create the certificates that supported “Subject Alternate Name”.

At first, I tried Mono.Security.  And that looked promising.  But I hit an issue that I logged here: ( https://stackoverflow.com/questions/40287336/mono-security-wont-set-multiple-keyusages )

So then I went to Bouncy Castle.  And I was able to create some “rough code” to get the certificates that I needed.

So I am posting that code here, in the hopes it may help someone.

The code will mimic the certificates : “Machine1.fullyqualified.domainname.com” and “AppServerGeneratedSBCA”.  Here the “AppServerGeneratedSBCA” is replaced by “BouncyCastleTrustedRootCertAuthority”.  (You can call it whatever name you choose).

The Machine1.fullyqualified.domainname.com certficate will be of the “1.3.6.1.5.5.7.3.1” variety.  I’ve also tried to mimic all of the key-usages that were in the original Microsoft “auto generated” certificates.

Which were:
(for “Machine1.fullyqualified.domainname.com”) : X509Extension.X509KeyUsageExtension.KeyUsages=’KeyEncipherment, DigitalSignature

and

(for “AppServerGeneratedSBCA”) (which will be “BouncyCastleTrustedRootCertAuthority”) :
X509Extension.X509KeyUsageExtension.KeyUsages=’CrlSign, KeyCertSign

And finally, here is the C# code.

using System;
using System.Collections.Generic;
using System.IO;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;

namespace ServiceBusCertificateMaker.BAL
{
/*
<?xml version=”1.0″ encoding=”utf-8″?>
<packages>
<package id=”BouncyCastle” version=”1.8.1″ targetFramework=”net45″ />
</packages>
*/

/* Notes, because this code actually places certificates IN YOUR CERTIFICATE STORE, it needs to be run “As Administrator” */

public class BouncyCastleMaker
{
public const string DefaultIssuer = “BouncyCastleTrustedRootCertAuthority”;

public void MakeItSo(string certificateName, List<string> subjectAlternateNames, string certificateFileName, string privateKeyFilePassword, string rootsigningCertFileName)
{
string issuerCnName = string.Format(“CN={0}”, DefaultIssuer);
AsymmetricKeyParameter caPrivKey = GenerateCACertificate(issuerCnName, privateKeyFilePassword, rootsigningCertFileName);
System.Security.Cryptography.X509Certificates.X509Certificate2 cert = GenerateSelfSignedCertificate(certificateName, subjectAlternateNames, certificateFileName, privateKeyFilePassword, issuerCnName, caPrivKey);
AddCertToStore(cert, System.Security.Cryptography.X509Certificates.StoreName.My, System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine);
ServiceBusSample.Shared.Showers.SecurityShower.ShowCertAndChain(cert);
}

public System.Security.Cryptography.X509Certificates.X509Certificate2 GenerateSelfSignedCertificate(string certificateName, List<string> subjectAlternateNames, string certificateFileName, string privateKeyFilePassword, string issuerName, AsymmetricKeyParameter issuerPrivKey)
{
return GenerateSelfSignedCertificate(certificateName, subjectAlternateNames, certificateFileName, privateKeyFilePassword, issuerName, issuerPrivKey, 2048);
}

public System.Security.Cryptography.X509Certificates.X509Certificate2 GenerateSelfSignedCertificate(string certificateName, List<string> subjectAlternateNames, string certificateFileName, string privateKeyFilePassword, string issuerName, AsymmetricKeyParameter issuerPrivKey, int keyStrength)
{
string subjectName = string.Format(“CN={0}”, certificateName);

// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);

// The Certificate Generator
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();

// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);

// Signature Algorithm
const string SignatureAlgorithm = “SHA256WithRSA”;
certificateGenerator.SetSignatureAlgorithm(SignatureAlgorithm);

// Issuer and Subject Name
var subjectDN = new X509Name(subjectName);

// original code var issuerDN = issuerName;
var issuerDN = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDN);
certificateGenerator.SetSubjectDN(subjectDN);

// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(2);

certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);

KeyUsage keyUsage = new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyEncipherment);
certificateGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsage);

// Add the “Extended Key Usage” attribute, specifying “server authentication”.
var usages = new[] { KeyPurposeID.IdKPServerAuth };
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage.Id,
false,
new ExtendedKeyUsage(usages));

/* DNS Name=*.full.domainname.com */
if (subjectAlternateNames.Count <= 1)
{
/* the <=1 is for the simple reason of showing an alternate syntax .. */
foreach (string subjectAlternateName in subjectAlternateNames)
{
GeneralName altName = new GeneralName(GeneralName.DnsName, subjectAlternateName);
GeneralNames subjectAltName = new GeneralNames(altName);
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, subjectAltName);
}
}
else
{
//Asn1Encodable[] ansiEncodeSubjectAlternativeNames = new Asn1Encodable[]
// {
// //new GeneralName(GeneralName.DnsName, “*.full.domainname.com”),
// new GeneralName(GeneralName.DnsName, “*.full.domainname.com”)
// };

List<Asn1Encodable> asn1EncodableList = new List<Asn1Encodable>();
foreach (string subjectAlternateName in subjectAlternateNames)
{
asn1EncodableList.Add(new GeneralName(GeneralName.DnsName, subjectAlternateName));
}

DerSequence subjectAlternativeNamesExtension = new DerSequence(asn1EncodableList.ToArray());
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
}

// Subject Public Key
AsymmetricCipherKeyPair subjectKeyPair;
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
subjectKeyPair = keyPairGenerator.GenerateKeyPair();

certificateGenerator.SetPublicKey(subjectKeyPair.Public);

// Generating the Certificate
var issuerKeyPair = subjectKeyPair;

// selfsign certificate
var certificate = certificateGenerator.Generate(issuerPrivKey, random);

// correcponding private key
PrivateKeyInfo pinfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);

// merge into X509Certificate2
var x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate.GetEncoded());

var seq = (Asn1Sequence)Asn1Object.FromByteArray(pinfo.PrivateKey.GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException(“malformed sequence in RSA private key”);
}

var rsa = new RsaPrivateKeyStructure(seq);
RsaPrivateCrtKeyParameters rsaparams = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);

x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams);

File.WriteAllBytes(certificateFileName.Replace(“.pfx”, “.cer”), x509.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Cert));

// Export Certificate with private key
File.WriteAllBytes(certificateFileName, x509.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, privateKeyFilePassword));

return x509;
}

public AsymmetricKeyParameter GenerateCACertificate(string subjectName, string privateKeyFilePassword, string rootsigningCertFileName, int keyStrength = 2048)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);

// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();

// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);

// Signature Algorithm
const string SignatureAlgorithm = “SHA256WithRSA”;
certificateGenerator.SetSignatureAlgorithm(SignatureAlgorithm);

// Issuer and Subject Name
var subjectDN = new X509Name(subjectName);
var issuerDN = subjectDN;
certificateGenerator.SetIssuerDN(issuerDN);
certificateGenerator.SetSubjectDN(subjectDN);

// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.AddYears(2);

certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);

KeyUsage keyUsage = new KeyUsage(KeyUsage.KeyCertSign | KeyUsage.CrlSign);
certificateGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsage);

// Subject Public Key
AsymmetricCipherKeyPair subjectKeyPair;
var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
subjectKeyPair = keyPairGenerator.GenerateKeyPair();

certificateGenerator.SetPublicKey(subjectKeyPair.Public);

// Generating the Certificate
var issuerKeyPair = subjectKeyPair;

// selfsign certificate
Org.BouncyCastle.X509.X509Certificate certificate = certificateGenerator.Generate(issuerKeyPair.Private, random);
System.Security.Cryptography.X509Certificates.X509Certificate2 x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate.GetEncoded());

#region Private Key

// correcponding private key
PrivateKeyInfo pinfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);

var seq = (Asn1Sequence)Asn1Object.FromByteArray(pinfo.PrivateKey.GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException(“malformed sequence in RSA private key”);
}

var rsa = new RsaPrivateKeyStructure(seq);
RsaPrivateCrtKeyParameters rsaparams = new RsaPrivateCrtKeyParameters(
rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);

x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
#endregion

// Add CA certificate to Root store
AddCertToStore(x509, System.Security.Cryptography.X509Certificates.StoreName.Root, System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine);

File.WriteAllBytes(rootsigningCertFileName.Replace(“.pfx”, “.cer”), x509.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Cert));

// Export Certificate with private key
File.WriteAllBytes(rootsigningCertFileName, x509.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, privateKeyFilePassword));

return issuerKeyPair.Private;
}

public bool AddCertToStore(System.Security.Cryptography.X509Certificates.X509Certificate2 cert, System.Security.Cryptography.X509Certificates.StoreName st, System.Security.Cryptography.X509Certificates.StoreLocation sl)
{
bool bRet = false;

try
{
System.Security.Cryptography.X509Certificates.X509Store store = new System.Security.Cryptography.X509Certificates.X509Store(st, sl);
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadWrite);
store.Add(cert);

store.Close();
}
catch
{
throw;
}

return bRet;
}
}
}

And then the code to call it, with specific parameter names.

private static void RunBouncyCastleMakerStuff()
{
string fullyQualifiedName = “StarDot.fullyqualified.domainname.com”;

List<string> subjectAlternateNames = new List<string>();
/* you can either add by wildcard */
//subjectAlternateNames.Add(“*.fullyqualified.domainname.com”); /* see https://www.digicert.com/subject-alternative-name.htm */

/* or you can add by machine names on the farm */
subjectAlternateNames.Add(“Machine1.fullyqualified.domainname.com”);
subjectAlternateNames.Add(“Machine2.fullyqualified.domainname.com”);
subjectAlternateNames.Add(“Machine3.fullyqualified.domainname.com”);

string privateKeyFilePassword = “MyPrivateKeyPa$$word”;

string rootFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(“N”));

string certFileName = Path.Combine(rootFolder, fullyQualifiedName + “.pfx”);
string rootsigningCertFileName = Path.Combine(rootFolder, BouncyCastleMaker.DefaultIssuer + “.pfx”);

if(!Directory.Exists(rootFolder))
{
Directory.CreateDirectory(rootFolder);
}

if (System.IO.File.Exists(certFileName))
{
System.IO.File.Delete(certFileName);
}

if (System.IO.File.Exists(rootsigningCertFileName))
{
System.IO.File.Delete(rootsigningCertFileName);
}

new BouncyCastleMaker().MakeItSo(fullyQualifiedName, subjectAlternateNames, certFileName, privateKeyFilePassword, rootsigningCertFileName);

Process.Start(“explorer.exe”, rootFolder);
}

This will create the certificates:

Certificate : IssuedTo: *StarDot.fullyqualified.domainname.com* Issued By: BouncyCastleTrustedRootCertAuthority Intended Purposes : Server Authentication


 

Then you need to put the certificates in the stores of Machine1, Machine2-N, and “The Client”.

NOT Auto Created Certificates (Aka Custom Certificates)

Same as above “Auto Created Certificates” except

First: Manually Add all certificates listed above in “Auto Generate”.

ADDITIONALLY :

——–
Certificate : IssuedTo: BouncyCastleTrustedRootCertAuthority  Issued By: BouncyCastleTrustedRootCertAuthority  Intended Purposes : <All>

On “MachineN”
Cert Stores : Manually add (private-key-version) to Personal.

And there ya go.

I have tested installations, re-installs (pseudo testing disaster recovery), and that my client will keep processing, even if Machine1.fullyqualified.domainname.com “goes down”.  (You can test this by stopping the Windows-Service “Service Bus Gateway” on Machine1.fullyqualified.domainname.com (or Machine2.fullyqualified.domainname.com or MachineN.fullyqualified.domainname.com, but stopping the service on “Machine1.fullyqualified.domainname.com” is the test that proves the certificate still work for the other computing-nodes)

While I haven’t shown the “client” code, the client-code is main-stream code using this package:

<?xml version=”1.0″ encoding=”utf-8″?>
<packages>
<package id=”Microsoft.WindowsAzure.ConfigurationManager” version=”2.0.1.0″ targetFramework=”net45″ />
<package id=”WindowsAzure.ServiceBus” version=”2.1.4.0″ targetFramework=”net45″ />
</packages>

with objects such as MessagingFactory, QueueClient.

Oh yeah, here is my “Certificate-Show-er” code.  (Not shower, like bathing… 8) )

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace ServiceBusSample.Shared.Showers
{
public static class SecurityShower
{
public static void ShowHttpWebRequest(System.Net.HttpWebRequest hwr)
{
StringBuilder sb = new StringBuilder();
if (null != hwr)
{
sb.Append(“———————————————–HttpWebRequest” + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.Address.AbsolutePath='{0}'”, hwr.Address.AbsolutePath) + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.Address.AbsoluteUri='{0}'”, hwr.Address.AbsoluteUri) + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.Address='{0}'”, hwr.Address) + System.Environment.NewLine);

sb.Append(string.Format(“HttpWebRequest.RequestUri.AbsolutePath='{0}'”, hwr.RequestUri.AbsolutePath) + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.RequestUri.AbsoluteUri='{0}'”, hwr.RequestUri.AbsoluteUri) + System.Environment.NewLine);
sb.Append(string.Format(“HttpWebRequest.RequestUri='{0}'”, hwr.RequestUri) + System.Environment.NewLine);

foreach (X509Certificate cert in hwr.ClientCertificates)
{
ShowX509Certificate(sb, cert);
}
}

string result = sb.ToString();
Console.WriteLine(result);
}

public static void ShowCertAndChain(X509Certificate2 cert)
{
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
chain.ChainPolicy.RevocationMode = X509RevocationMode.Offline;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;

chain.Build(cert);

ShowCertAndChain(cert, chain);
}

public static void ShowCertAndChain(X509Certificate cert, X509Chain chain)
{
StringBuilder sb = new StringBuilder();
if (null != cert)
{
ShowX509Certificate(sb, cert);
}

if (null != chain)
{
sb.Append(“-X509Chain(Start)-” + System.Environment.NewLine);
////sb.Append(string.Format(“Cert.ChainStatus='{0}'”, string.Join(“,”, chain.ChainStatus.ToList())) + System.Environment.NewLine);

foreach (X509ChainStatus cstat in chain.ChainStatus)
{
sb.Append(string.Format(“X509ChainStatus::'{0}’-‘{1}'”, cstat.Status.ToString(), cstat.StatusInformation) + System.Environment.NewLine);
}

X509ChainElementCollection ces = chain.ChainElements;
ShowX509ChainElementCollection(sb, ces);
sb.Append(“-X509Chain(End)-” + System.Environment.NewLine);
}

string result = sb.ToString();
Console.WriteLine(result);
}

private static void ShowX509Extension(StringBuilder sb, int x509ExtensionCount, X509Extension ext)
{
sb.Append(string.Empty + System.Environment.NewLine);
sb.Append(string.Format(“——–X509ExtensionNumber(Start):{0}”, x509ExtensionCount) + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.Critical='{0}'”, ext.Critical) + System.Environment.NewLine);

AsnEncodedData asndata = new AsnEncodedData(ext.Oid, ext.RawData);
sb.Append(string.Format(“Extension type: {0}”, ext.Oid.FriendlyName) + System.Environment.NewLine);
sb.Append(string.Format(“Oid value: {0}”, asndata.Oid.Value) + System.Environment.NewLine);
sb.Append(string.Format(“Raw data length: {0} {1}”, asndata.RawData.Length, Environment.NewLine) + System.Environment.NewLine);
sb.Append(asndata.Format(true) + System.Environment.NewLine);

X509BasicConstraintsExtension basicEx = ext as X509BasicConstraintsExtension;
if (null != basicEx)
{
sb.Append(“-X509BasicConstraintsExtension-” + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.X509BasicConstraintsExtension.CertificateAuthority='{0}'”, basicEx.CertificateAuthority) + System.Environment.NewLine);
}

X509EnhancedKeyUsageExtension keyEx = ext as X509EnhancedKeyUsageExtension;
if (null != keyEx)
{
sb.Append(“-X509EnhancedKeyUsageExtension-” + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.X509EnhancedKeyUsageExtension.EnhancedKeyUsages='{0}'”, keyEx.EnhancedKeyUsages) + System.Environment.NewLine);
foreach (Oid oi in keyEx.EnhancedKeyUsages)
{
sb.Append(string.Format(“————EnhancedKeyUsages.Oid.FriendlyName='{0}'”, oi.FriendlyName) + System.Environment.NewLine);
sb.Append(string.Format(“————EnhancedKeyUsages.Oid.Value='{0}'”, oi.Value) + System.Environment.NewLine);
}
}

X509KeyUsageExtension usageEx = ext as X509KeyUsageExtension;
if (null != usageEx)
{
sb.Append(“-X509KeyUsageExtension-” + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.X509KeyUsageExtension.KeyUsages='{0}'”, usageEx.KeyUsages) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.CrlSign='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.CrlSign) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.DataEncipherment='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.DataEncipherment) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.DecipherOnly='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.DecipherOnly) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.DigitalSignature='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.DigitalSignature) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.EncipherOnly='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.EncipherOnly) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.KeyAgreement='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.KeyAgreement) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.KeyCertSign='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.KeyCertSign) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.KeyEncipherment='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.KeyEncipherment) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.None='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.None) != 0) + System.Environment.NewLine);
sb.Append(string.Format(“X509KeyUsageExtension.KeyUsages.X509KeyUsageFlags.NonRepudiation='{0}'”, (usageEx.KeyUsages & X509KeyUsageFlags.NonRepudiation) != 0) + System.Environment.NewLine);
}

X509SubjectKeyIdentifierExtension skIdEx = ext as X509SubjectKeyIdentifierExtension;
if (null != skIdEx)
{
sb.Append(“-X509SubjectKeyIdentifierExtension-” + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.X509SubjectKeyIdentifierExtension.Oid='{0}'”, skIdEx.Oid) + System.Environment.NewLine);
sb.Append(string.Format(“X509Extension.X509SubjectKeyIdentifierExtension.SubjectKeyIdentifier='{0}'”, skIdEx.SubjectKeyIdentifier) + System.Environment.NewLine);
}

sb.Append(string.Format(“——–X509ExtensionNumber(End):{0}”, x509ExtensionCount) + System.Environment.NewLine);
}

private static void ShowX509Extensions(StringBuilder sb, string cert2SubjectName, X509ExtensionCollection extColl)
{
int x509ExtensionCount = 0;
sb.Append(string.Format(“——–ShowX509Extensions(Start):for:{0}”, cert2SubjectName) + System.Environment.NewLine);
foreach (X509Extension ext in extColl)
{
ShowX509Extension(sb, ++x509ExtensionCount, ext);
}

sb.Append(string.Format(“——–ShowX509Extensions(End):for:{0}”, cert2SubjectName) + System.Environment.NewLine);
}

private static void ShowX509Certificate2(StringBuilder sb, X509Certificate2 cert2)
{
if (null != cert2)
{
sb.Append(string.Format(“X509Certificate2.SubjectName.Name='{0}'”, cert2.SubjectName.Name) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.Subject='{0}'”, cert2.Subject) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.Thumbprint='{0}'”, cert2.Thumbprint) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.HasPrivateKey='{0}'”, cert2.HasPrivateKey) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.Version='{0}'”, cert2.Version) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.NotBefore='{0}'”, cert2.NotBefore) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.NotAfter='{0}'”, cert2.NotAfter) + System.Environment.NewLine);
sb.Append(string.Format(“X509Certificate2.PublicKey.Key.KeySize='{0}'”, cert2.PublicKey.Key.KeySize) + System.Environment.NewLine);

////List<X509KeyUsageExtension> keyUsageExtensions = cert2.Extensions.OfType<X509KeyUsageExtension>().ToList();
////List<X509Extension> extensions = cert2.Extensions.OfType<X509Extension>().ToList();

ShowX509Extensions(sb, cert2.Subject, cert2.Extensions);
}
}

private static void ShowX509ChainElementCollection(StringBuilder sb, X509ChainElementCollection ces)
{
int x509ChainElementCount = 0;
foreach (X509ChainElement ce in ces)
{
sb.Append(string.Empty + System.Environment.NewLine);
sb.Append(string.Format(“—-X509ChainElementNumber:{0}”, ++x509ChainElementCount) + System.Environment.NewLine);
sb.Append(string.Format(“X509ChainElement.Cert.SubjectName.Name='{0}'”, ce.Certificate.SubjectName.Name) + System.Environment.NewLine);
sb.Append(string.Format(“X509ChainElement.Cert.Issuer='{0}'”, ce.Certificate.Issuer) + System.Environment.NewLine);
sb.Append(string.Format(“X509ChainElement.Cert.Thumbprint='{0}'”, ce.Certificate.Thumbprint) + System.Environment.NewLine);
sb.Append(string.Format(“X509ChainElement.Cert.HasPrivateKey='{0}'”, ce.Certificate.HasPrivateKey) + System.Environment.NewLine);

X509Certificate2 cert2 = ce.Certificate as X509Certificate2;
ShowX509Certificate2(sb, cert2);

ShowX509Extensions(sb, cert2.Subject, ce.Certificate.Extensions);
}
}

private static void ShowX509Certificate(StringBuilder sb, X509Certificate cert)
{
sb.Append(“———————————————–” + System.Environment.NewLine);
sb.Append(string.Format(“Cert.Subject='{0}'”, cert.Subject) + System.Environment.NewLine);
sb.Append(string.Format(“Cert.Issuer='{0}'”, cert.Issuer) + System.Environment.NewLine);

sb.Append(string.Format(“Cert.GetPublicKey().Length='{0}'”, cert.GetPublicKey().Length) + System.Environment.NewLine);

X509Certificate2 cert2 = cert as X509Certificate2;
ShowX509Certificate2(sb, cert2);
}
}
}

Last note:  If you’re having certificate issues on “the client”, you can hook into this event below to see what is happening.  This is how I originally figured out all the settings on the Auto-Generated certificates.

/* Use the below to debug failed verification. Remember that the Certificate ALTERNATE subject name comes into play in a High Availability scenario */
ServicePointManager.ServerCertificateValidationCallback =
new System.Net.Security.RemoteCertificateValidationCallback((
sender,
cert,
chain,
ssl) =>
{
Console.WriteLine(“ServerCertificateValidationCallback for Cert.Subject : ‘{0}'”, cert.Subject);
System.Net.HttpWebRequest hwr = sender as System.Net.HttpWebRequest;
if (null != hwr)
{
SecurityShower.ShowHttpWebRequest(hwr);
}

SecurityShower.ShowCertAndChain(cert, chain);
return true; /* return true here is ONLY FOR DEBUGGING */
});

Posted in Uncategorized | Leave a comment

Custom MSBuild Task and capturing Command Line Output

I recently had a need to “capture the output” of a command line tool, but within a MSBuild Custom Task (context).

While I know there are “msbuild’ish” ways to accomplish the below example (getting a list of directories from a “dir” command), the below is an ~~example~~ of how to capture the output of a command line call.

What am I talking about?

Well, for example, if run the command (from a command line prompt):

dir “c:\”

You would see (in the command line window) something like the below:

C:\>dir “c:\”

Volume in drive C has no label.
Volume Serial Number is JER-33_3

Directory of c:\

10/15/2012 01:20 PM <DIR> Program Files
11/08/2012 02:29 PM <DIR> Program Files (x86)
08/16/2012 02:23 PM <DIR> Users
11/08/2012 02:08 PM <DIR> Windows

0 File(s) 0 bytes
4 Dir(s) 33,490,378,752 bytes free

C:\>

So how can I “capture” the output while inside of a custom MSBuild Task?

First.  I didn’t figure this out on my own.  I googled and bing searched by rumpus off.  And then I came across an example.

https://msbuildextensionpack.svn.codeplex.com/svn/Solutions/Main3.5/Framework/Framework/CommandLine.cs

namespace MSBuild.ExtensionPack.Framework
{
[HelpUrl(“http://www.msbuildextensionpack.com/help/3.5.12.0/html/324b0e31-5ff0-baac-40ae-bf26297e5821.htm&#8221;)]
public class CommandLine : Task
{
//not seen stuff here
}
}

And I totally ripped their code.

But what I did do is package this up in a smaller (more digestable?) example as seen below.
And I remember when I first started with writing my own custom MSBuild tasks that what is obvious to me now, was not obvious when I first started.

So, in a nutshell:
“CollectedOutput” will be the property that has all the contents for the ‘dir “c:\”‘.

The CSharp code will need to be put into a “Class Library” DotNet csproj of course.
If you want the example to run “out of the box”, then name the new csproj with the name “GranadaCoder.Framework.CrossDomain.MSBuild” (If you don’t do this, you’ll need to adjust the name of the .dll mentioned in the .msbuild file).
After compiling, place the .dll in the same folder as the .msbuild and .bat file.

//START CSharp Code//

namespace GranadaCoder.Framework.CrossDomain.MSBuild.Tasks.Temp
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.ComponentModel;
using System.Reflection;

using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using System.Diagnostics;

public class CommandLineDirectoryTask : ToolTask
{

private static readonly string WINDIR_ENVIRONMENT_VARIABLE = “windir”;
private static readonly string CMD_RUNTIME_EXE = “cmd.exe”;
private static readonly string COMMAND_LINE_SWITCH_C_DIR = “/c dir”;

public CommandLineDirectoryTask()
{
base.ToolExe = CMD_RUNTIME_EXE; // see http://msdn.microsoft.com/en-us/library/microsoft.build.utilities.tooltask.toolexe.aspx
this.ToolPath = this.ReadSystem32EnvironmentVariable();
}

[Required]
public string DirectoryNameToList { get; set; }

/// <summary>
/// Gets or sets the collected output from the command-line.
/// </summary>
///
[Output]
public string CollectedOutput { get; set; }

/// <summary>
/// Gets or sets the StdErr stream encoding. Specifies the encoding of the captured task standard error stream.
/// The default is the current console output encoding.
/// </summary>
/// <remarks>Exec Equivalent: StdErrEncoding</remarks>
[Output]
public string StdErrEncoding { get; set; }

/// <summary>
/// Gets or sets the StdOut stream encoding. Specifies the encoding of the captured task standard output stream.
/// The default is the current console output encoding.
/// </summary>
/// <remarks>Exec Equivalent: StdOutEncoding</remarks>
[Output]
public string StdOutEncoding { get; set; }

/// <summary>
/// Gets the name of the tool.
/// </summary>
/// <value>The name of the tool.</value>
protected override string ToolName
{
get { return base.ToolExe; }
}

/// <summary>
/// Generates the full path to tool.
/// </summary>
/// <returns></returns>
protected override string GenerateFullPathToTool()
{
return Path.Combine(ToolPath, ToolName);
}

/// <summary>
/// Executes this instance.
/// </summary>
/// <returns></returns>
public override bool Execute()
{
//return base.Execute();
return InternalExecute();
}

private bool InternalExecute()
{

string command = this.GenerateFullPathToTool();
string commandLineArguments = GenerateSvnExportCommandLineArguments();

this.Log.LogMessage(“Execute: {0} {1}”, command, commandLineArguments);
ProcessStartInfo startInfo = this.GetCommandLine(command, commandLineArguments, this.DirectoryNameToList, this.StdErrEncoding, this.StdOutEncoding);
using (Process process = Process.Start(startInfo))
{
//this.Log.LogMessage(“Collect Standard Output Stream”);

while (!process.StandardOutput.EndOfStream || !process.HasExited)
{
this.CollectOutputLine(process.StandardOutput.ReadLine());
}

//this.Log.LogMessage(“Collect Standard Error Stream”);
while (!process.StandardError.EndOfStream)
{
this.CollectOutputLine(process.StandardError.ReadLine());
}

}

return true;

}

/// <summary>
/// Gets a command process object with the command specified.
/// </summary>
/// <param name=”command”>The command to execute</param>
/// <param name=”workingDirectory”>The command working directory</param>
/// <param name=”standardErrorEncoding”>The standard error stream encoding</param>
/// <param name=”standardOutputEncoding”>The standard output stream encoding</param>
/// <returns>Returns a command prompt start information that is ready to start</returns>
/// <remarks>StdErr and StdOut are always redirected.</remarks>
private ProcessStartInfo GetCommandLine(string command, string commandLineArguments, string workingDirectory, string standardErrorEncoding, string standardOutputEncoding)
{
var process = new ProcessStartInfo
{
FileName = command,
Arguments = commandLineArguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
ErrorDialog = false
};

if (!string.IsNullOrEmpty(standardErrorEncoding))
{
try
{
process.StandardErrorEncoding = Encoding.GetEncoding(standardErrorEncoding);
}
catch (ArgumentException ex)
{
this.Log.LogMessage(“Non-fatal exception caught: invalid encoding specified for standard error stream: {0}”, standardErrorEncoding);
this.Log.LogWarningFromException(ex);
}
}

if (!string.IsNullOrEmpty(standardOutputEncoding))
{
try
{
process.StandardOutputEncoding = Encoding.GetEncoding(standardOutputEncoding);
}
catch (ArgumentException ex)
{
this.Log.LogMessage(“Non-fatal exception caught: invalid encoding specified for standard output stream: {0}”, standardOutputEncoding);
this.Log.LogWarningFromException(ex);
}
}

if (!string.IsNullOrEmpty(workingDirectory))
{
if (Directory.Exists(workingDirectory))
{
process.WorkingDirectory = workingDirectory;
}
else
{
this.Log.LogWarning(“Non-fatal input error: provided working directory does not exist: {0}”, workingDirectory);
}
}

return process;
}

/// <summary>
/// Generates the command line commands.
/// </summary>
/// <returns></returns>
protected string GenerateSvnExportCommandLineArguments()
{
StringBuilder builder = new StringBuilder();
AppendIfPresent(builder, CommandLineDirectoryTask.COMMAND_LINE_SWITCH_C_DIR, QuoteItUp(this.DirectoryNameToList));
Log.LogCommandLine(builder.ToString());
return builder.ToString();
}

/// <summary>
/// Collects a line of output.
/// </summary>
/// <param name=”text”>The text line</param>
private string CollectOutputLine(string text)
{
text = string.IsNullOrEmpty(text) ? null : text.Trim();
if (!string.IsNullOrEmpty(text))
{
this.CollectedOutput += Environment.NewLine + text;
this.Log.LogMessage(text);
}

return text;
}

/// <summary>
/// Append the command and argument only if the argument exists. Repped from the internet at http://www.zorched.net/2009/01/08/msbuild-task-for-partcover/
/// </summary>
/// <param name=”builder”>The containing string builder.</param>
/// <param name=”cmdArg”>The command argument switch.</param>
/// <param name=”value”>The value of the command argument.</param>
protected static void AppendIfPresent(StringBuilder builder, string cmdArg, string value)
{
if (!String.IsNullOrEmpty(value))
{
builder.AppendFormat(“{0} {1} “, cmdArg, value);
}
}

/// <summary>
/// Wrap quotes around a string and escape charcters if needed.
/// </summary>
/// <param name=”builder”>The containing string builder.</param>
protected static string QuoteItUp(string args)
{
if (String.IsNullOrEmpty(args))
{
return args;
}

bool alreadyHasQuoteBookEnds = false;

if (args.StartsWith(“\””) && args.EndsWith(“\””))
{
alreadyHasQuoteBookEnds = true;
}

// Escape internal quotes if any
if (args.Contains(“\””))
{
args = args.Replace(“\””, “\\\””);
}

if (!alreadyHasQuoteBookEnds)
{
// quote string
args = String.Format(“\”{0}\””, args);
}
return args;
}

private string ReadSystem32EnvironmentVariable()
{
string returnValue = string.Empty;

if (!String.IsNullOrEmpty(System.Environment.GetEnvironmentVariable(WINDIR_ENVIRONMENT_VARIABLE)))
{
returnValue = System.Environment.GetEnvironmentVariable(WINDIR_ENVIRONMENT_VARIABLE);
returnValue = Path.Combine(returnValue, “System32”);
}
else
{
base.Log.LogWarning(string.Format(“The ‘{0}’ Environment Variable was not available. You will need to manually set the ToolPath value”, WINDIR_ENVIRONMENT_VARIABLE));
}

return returnValue;
}
}
}

//END CSHARP Code

—————————————

//Start file “CommandLineDirectoryTaskTest.msbuild”

<?xml version=”1.0″ encoding=”utf-8″?>
<Project DefaultTargets=”AllTargetsWrapper” xmlns=”http://schemas.microsoft.com/developer/msbuild/2003″&gt;
<!– –>
<PropertyGroup>
<WorkingDirectory>.</WorkingDirectory>
</PropertyGroup>
<!– –>
<PropertyGroup>
<GranadaCoderMSBuildFileName Condition=”Exists(‘$(WorkingDirectory)\MSBuildHelpers\GranadaCoder.Framework.CrossDomain.MSBuild.dll’)”>$(WorkingDirectory)\MSBuildHelpers\GranadaCoder.Framework.CrossDomain.MSBuild.dll</GranadaCoderMSBuildFileName>
<GranadaCoderMSBuildFileName Condition=”Exists(‘$(WorkingDirectory)\GranadaCoder.Framework.CrossDomain.MSBuild.dll’)”>$(WorkingDirectory)\GranadaCoder.Framework.CrossDomain.MSBuild.dll</GranadaCoderMSBuildFileName>
<GranadaCoderMSBuildFileName Condition=”$(GranadaCoderMSBuildFileName)==””>CannotFind_GranadaCoder.Framework.CrossDomain.MSBuild.dll</GranadaCoderMSBuildFileName>
</PropertyGroup>
<!– –>
<!– –>
<UsingTask AssemblyFile=”$(GranadaCoderMSBuildFileName)” TaskName=”CommandLineDirectoryTask”/>
<!– –>
<!– –>
<Target Name=”AllTargetsWrapper”>
<CallTarget Targets=”CommandLineDirectoryTask1″ />
</Target>
<!– –>
<!– –>
<Target Name=”CommandLineDirectoryTask1″>

<CommandLineDirectoryTask DirectoryNameToList=”C:\wutemp\”>
<Output TaskParameter=”CollectedOutput” PropertyName=”MyCollectedOutput1″/>
</CommandLineDirectoryTask>

<Message Text=”The CollectedOutput1 is $(MyCollectedOutput1)”/>
<Message Text=” “/>
<Message Text=” “/>
<Message Text=” “/>
<Message Text=” “/>

<CommandLineDirectoryTask ContinueOnError=”true” DirectoryNameToList=”C:\work3\” >
<Output TaskParameter=”ExitCode” PropertyName=”MyErrorCode”/>
<Output TaskParameter=”CollectedOutput” PropertyName=”MyCollectedOutput2″/>
</CommandLineDirectoryTask>

<Message Text=”The CollectedOutput2 is $(MyCollectedOutput1)”/>
<Message Text=” “/>
<Message Text=” “/>
<Message Text=”The exit code (MyErrorCode) is $(MyErrorCode)”/>

</Target>

</Project>

//End file “CommandLineDirectoryTaskTest.msbuild”

—————————————

//Start CommandLineDirectoryTaskTest.bat file
set msBuildDir=%WINDIR%\Microsoft.NET\Framework\v3.5
set msBuildDir=%WINDIR%\Microsoft.NET\Framework\v2.0.50727
set msBuildDir=%WINDIR%\Microsoft.NET\Framework\v3.5

call %msBuildDir%\msbuild /target:AllTargetsWrapper “CommandLineDirectoryTaskTest.msbuild” /p:Configuration=Release;FavoriteFood=Popeyes /l:FileLogger,Microsoft.Build.Engine;logfile=CommandLineDirectoryTaskTest_AllTargetsWrapped.log

set msBuildDir=

//End CommandLineDirectoryTaskTest.bat file

Posted in Software Development | Leave a comment

How to find References in a C# Project File (.csproj) using LINQ (Xml)

I was asked to “Generate a report of all our c# projects and their dependencies.

:<

So instead of going through 333+ csprojs manually, it was time to write a helper routine to look through the .csproj xml.

And instead of old-school XPath and SelectNodes() (boooo), it was time to write some Linqy-Dinq.

The below code will query a csproj file and report back all all project references. 

It will report back:

(1) If the reference is an already compiled dll.  Not in the GAC.
(2) If the reference is an already compiled dll.  In the GAC.
(3) If the reference is a reference “by project”.

Feel free to post any suggestions and/or improvements.

The biggest trick was adding the namespace to the conditional clauses. ( ns + “SomeElementName” ).
The other trick was figuring out how to ignore Xml-Element(s) if there was no value.
Oh, it is so simple when it is done.  But took a while to figure out the nuances.

//using System.Xml.Linq;

string fileName = @”C:\MyFolder\MyProjectFile.csproj”;

XDocument xDoc = XDocument.Load(fileName);

XNamespace ns = XNamespace.Get(http://schemas.microsoft.com/developer/msbuild/2003&#8221;);


//References “By DLL (file)”

var list1 = from list in xDoc.Descendants(ns + “ItemGroup”)
from item in list.Elements(ns + “Reference”)

/* where item.Element(ns + “HintPath”) != null */ /* optional */

select new
{
CsProjFileName = fileName,
ReferenceInclude = item.Attribute(“Include”).Value,
RefType = (item.Element(ns + “HintPath”) == null) ? “CompiledDLLInGac” : “CompiledDLL”,
 HintPath = (item.Element(ns + “HintPath”) == null) ? string.Empty : item.Element(ns + “HintPath”).Value};

foreach  (var v in list1)
{
    Console.WriteLine(v.ToString());
}

//References “By Project”
var list2 = from list in xDoc.Descendants(ns + “ItemGroup”)
from item in list.Elements(ns + “ProjectReference”)

where
item.Element(ns + “Project”) != null

select new
{
CsProjFileName = fileName,
ReferenceInclude = item.Attribute(“Include”).Value,
RefType = “ProjectReference”,
ProjectGuid = item.Element(ns + “Project”).Value
};

foreach (var v in list2)
{
    Console.WriteLine(v.ToString());
}

Posts that helped me:

http://stackoverflow.com/questions/2338512/understanding-linq-to-xml-descendants-return-no-results

http://stackoverflow.com/questions/2630192/c-sharp-check-an-element-exists-while-using-linq-to-xml

Bonus Code:

string startFolder = @”C:\DotNetCodeBase\”;

System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(startFolder);

IEnumerable<System.IO.FileInfo> fileList = dir.GetFiles(“*.csproj”, System.IO.SearchOption.AllDirectories);

IEnumerable<System.IO.FileInfo> fileQuery = from file in fileList
    where file.Extension == “.csproj”
    orderby file.Name
select file;

 

Posted in Software Development | 3 Comments

Create a COM+ Application with Powershell

$comAdmin = New-Object -comobject COMAdmin.COMAdminCatalog
$apps = $comAdmin.GetCollection(“Applications”)
$apps.Populate();

$newComPackageName = “MyFirstCOMPackage”

$appExistCheckApp = $apps | Where-Object {$_.Name -eq $newComPackageName}

if($appExistCheckApp)
{
$appExistCheckAppName = $appExistCheckApp.Value(“Name”)
“This COM+ Application already exists : $appExistCheckAppName”
}
Else
{
$newApp1 = $apps.Add()
$newApp1.Value(“Name”) = $newComPackageName
$newApp1.Value(“ApplicationAccessChecksEnabled”) = 0 <# Security Tab, Authorization Panel, “Enforce access checks for this application #>

<# See http://msdn.microsoft.com/en-us/library/ms686107(v=VS.85).aspx#identity for full documentation. #>

<# Optional (to set to a specific Identify) #>
$newApp1.Value(“Identity”) = “MyDomain\myUserName”
$newApp1.Value(“Password”) = “myPassword”

$saveChangesResult = $apps.SaveChanges()
“Results of the SaveChanges operation : $saveChangesResult”
}

Full documentation of the properties here.

http://msdn.microsoft.com/en-us/library/ms686107(v=VS.85).aspx#applicationaccesschecksenabled

I’m a C# developer.  But Powershell rocks the suburbs for alot of tasks!

Posted in Software Development | Leave a comment

Hello world!

Welcome to WordPress.com. This is your first post. Edit or delete it and start blogging!

Posted in Uncategorized | 1 Comment

Bug in Documentation : Microsoft Access Database Engine 2010 Redistributable

Microsoft Access
Database Engine 2010 Redistributable

http://www.microsoft.com/downloads/en/details.aspx?familyid=C06B8369-60DD-4B64-A44B-84B371EDE16D&displaylang=en

There is a bug in the documentation at the download page.

The documentation says:
1.If you are the user of an application, consult your application
documentation for details on how to use the appropriate driver.

2.If
you are an application developer using OLEDB, set the Provider argument
of the ConnectionString property to “Microsoft.ACE.OLEDB.12.0”
    If
you are connecting to Microsoft Office Excel data, add “Excel 14.0” to
the Extended Properties of the OLEDB connection string.

The "Excel 14.0" is the issue.

It should be "Excel 12.0".

Her are a few connection strings to provide full context.

// Old School Jet, been around for a while
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=’C:\MyOldSchoolFile.xls’;Extended Properties=’Excel 8.0;HDR=NO;IMEX=1;’;"

//Newer version with xslx
"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=’C:\MyXlsXFile.xlsx’;Extended Properties=’Excel 12.0 Xml;HDR=NO;IMEX=1;’;"

//Newer version, any xls file
"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=’C:\AlmostAnyExcelVersionFileRunningUnder64BitOS.xls’;Extended Properties=’Excel 12.0;HDR=NO;IMEX=1;’;"

Don’t take the above as absolute truth.  But it should draw attention to the issue (if you’re experiencing it), and help with some more google (errr. bing) searches.

Here are some other phrases which might lead you here.

The OLE DB provider "Microsoft.Jet.OLEDB.4.0" has not been registered
    ( Trying to read an excel file on a 64 bit O/S?  The purple string above should work for you under a 64 bit O/S. )

"Could not find installable ISAM"     
    ( This might show up because of the "14.0" vs "12.0" bug mentioned above )

See also:
http://stackoverflow.com/questions/2075054/upload-an-excel-file-in-classic-asp-on-windows-2003-x64-using-office-2010-drivers/

http://social.msdn.microsoft.com/Forums/en/adodotnetdataproviders/thread/686d8ebb-0da3-4f0c-bf16-9c650f8dcb32

http://www.connectionstrings.com/excel-2007

Posted in Software Development | 6 Comments

RANT : Hard Coded Security Roles

:::::::::::sigh:::::::::::::

If I come across one more hard coded security roles brownfield application, I think I’m gonna throw my chair out the window.

Today, I came across an application that does type-checking to determine security.

The code goes something like this:

AbstractUser user = SomeGetUserMethod(“myname”, “mypassword”);
/* the above method returns 1 of a few concrete classes which implement AbstractUser */

// type-checking time!!
if (user.GetType() == typeof(BAL.Domain.AdminUser))
{
MyWebPage.btnDeleteAllEmployees.Visible = true;
MyWebPage.btnUpdateMyOwnProfile.Visible = true;
MyWebPage.btnLogOut.Visible = true;
}

if (user.GetType() ==
typeof(BAL.Domain.NormalUser))

{

MyWebPage.btnDeleteAllEmployees.Visible = false;

MyWebPage.btnUpdateMyOwnProfile.Visible =
true;

MyWebPage.btnLogOut.Visible = true;

}

or maybe you have seen (bool representing 1 of a few hard coded roles)

bool isAdmin = SomeMethodToFigureOutHardCodedRoles();
bool isNormalUser =
SomeMethodToFigureOutHardCodedRoles();
if (isAdmin)

{

MyWebPage.btnDeleteAllEmployees.Visible =
true;

MyWebPage.btnUpdateMyOwnProfile.Visible =
true;

MyWebPage.btnLogOut.Visible = true;

}
if (isNormalUser)

{

MyWebPage.btnDeleteAllEmployees.Visible =false ; // NormalUser cannot do this! So hide the button.

MyWebPage.btnUpdateMyOwnProfile.Visible =
true;

MyWebPage.btnLogOut.Visible = true;

}

You know the drill.

And the pre-project-starts-to-be-constructed discussion goes something like this:
“Today, we have 3 roles, lets base all our security off those 3 (hard coded) roles.  Those roles will ~~never~~ change.

And my thoughts on a finite set of hard coded Role(s).
That’s fine for your kid’s soccer club fan page.
That is NOT fine for a professional DotNet developer creating a business application.

I’ve written an example on how NOT do it (so you can see the pattern clearly).

The problem will always end up being that the 3 (N) number of roles will never be sufficient.
A DAY WILL ARRIVE WHEN THE BUSINESS OWNERS WANT A SLIGHTLY DIFFERENT ROLE.
They’ll say that want to “tweak” an existing role, but what they really mean is that they want a new role that is very close to an existing role.
But “a tad bit different” is still different.
But you (or your “architect” if you want to blame someone else) did not account for this at the beginning of the project.

You can check out:
http://www.lhotka.net/weblog/CommentView,guid,9efcafc7-68a2-4f8f-bc64-66174453adfd.aspx
for a discussion.
I can’t blame just the developer(s).  Microsoft and its “super easy p-easy” “.IsInRole()” method helped propagate this ugliness.
And then this kind of code:
[PrincipalPermission(SecurityAction.Demand,Role=”Teller”)]

And I completely agree with the article above and its assertion “At runtime, when the user is actually using the application, the roles are entirely meaningless“.
Listen people (aka all you developers) ..  software cares about permissions (or “rights”).  Stop coding to roles, start coding to permissions (or rights).

The article above tackles the issue using the existing (available) objects in DotNet.

Here is my custom IPrincipal solution, if the above workaround rubs you the wrong way.
While I have “Role” methods, I never use them ***, except for the AllRoles collection, which I use to show humans what role they are in.
However, I never show just the Roles, I show the Permissions/Rights, because that is more important.

public interface IRolesAndRightsPrincipal : System.Security.Principal.IPrincipal
{

bool IsInRole(System.Guid role);

bool IsInAnyRole(System.Guid[] roles);

bool IsInAllRoles(System.Guid[] roles);

bool HasRight(System.Guid right);

bool HasAnyRight(System.Guid[] rights);

bool HasAllRights(System.Guid[] rights);

ISecurityRoleCollection AllRoles
{
get;
}

ISecurityRightCollection AllRights
{
get;
}
}

(*** The one place I might use them (though I never have) is backwards compatibility when refactoring an existing application.  If the current application is “all roled up” then that would be a stepping stone to getting to permissions/rights based security.)

You might be saying “What is the HasAnyRight method all about?”

Well, take for instance that you have a menu link called “Manage Employees”.
This link will take you to a separate page that allows you to ADDNEW, UPDATE, and/or DELETE an employee.
(These are 3 distinct permission(s)/rights(s)).

So how do you decide if you should show this menu link called “Manage Employees”, since its not based on a single permission/right.

And there ya go:  Use the HasAnyRight() method
menuItemManageEmployees.Visible = customPrinc.HasAnyRight ( /* throw the Guids here which represent ADDNEW, UPDATE, DELETE */ );
You show the link if the use has one (any) of the 3 permissions/rights.
Then when you get to that new page, you show buttons/links/etc based on the individual permissions/rights.

Side note, my concrete IRolesAndRightsPrincipal ( adaptly named “RolesAndRightsPrincipal” takes all your roles,rights in its constructor, and then becomes a look-up holder from that point on.  Obviously, if you do on the fly permissions/rights changes, you’ll have to refresh it.
Currently, I just go with a “You gotta re-login to get your fresh permissions/rights”, since the project (almost) never takes away any permissions/rights and seldom changes them.  But you’ll have to conquer that design decision on your own.

:::::::::Call to all Developers::::::::::::

If you’re developing a new project, and starting out with N number of hard coded roles, please stop.  Please stop.  I’m begging you, please stop.

If you’re in a brownfield application, then ask your manager for some time to re-factor the security.
If your manager is fair, then he/she will find some time for you that does not involve late nights or weekends.
If your manager thinks re-factoring (without scope change) is a waste of time since “it works, so its OK”, then find a new job.

This oldie-but-goodie article has the basic meat of a good solution.
http://www.codeproject.com/KB/security/cgsecurity.aspx
I would take his DDL, update it to your standards, and “framework up” his “Managers”(.cs code).

It is OK to make a Role, but ONLY in the sense to logically group a set of permissions/rights.
You should be checking permission(s)/right(s) when you actually are interested in the question
“Can this IIdentity perform this certain thing?”

Please stop coding hard-coded-roles into your application(s).
The developers who have to maintain your code after you’ve left will thank you.
..

 

//End Rant

 

2016 Update

You will now want to code to “Claims”.

You’ll create (at least) one System.Security.Claims.ClaimsIdentity.

You will add 1 or more System.Security.Claims.Claim ‘s to this Identity.

Then you will inject (or more) ClaimsIdentity’s to the ClaimsPrincipal.

The (final) ClaimsPrincipal will consolidate all of the Claims into one master collection.

 

https://msdn.microsoft.com/en-us/library/system.security.claims.claimsidentity(v=vs.110).aspx

https://msdn.microsoft.com/en-us/library/system.security.claims.claim(v=vs.110).aspx

https://msdn.microsoft.com/en-us/library/system.security.claims.claimsprincipal(v=vs.110).aspx

 

Posted in Software Development | 2 Comments