If you’re like me—writing internal apps for a small company—you create your own self-signed certificates when publishing a ClickOnce application. Visual Studio will create these certificates for you automatically on the first publish, but they’ll only be good for a couple of years. That’s when you find yourself under pressure, updating a malfunctioning application, and suddenly you’re getting an error when trying to push out your fixes.
A great tool to renew your certificates is RenewCert, written by Cliff Stanford. Using this tool, you can quickly extend the expiration date of your signing certificate to 5 years from the current date. Much thanks are due to Cliff for figuring this all out! This stuff isn’t really documented anywhere.
But I found a few limitations:
- I couldn’t run it in a setting without VC++ installed. The author acknowledges this issue, but with walking the dependencies I couldn’t manage to find them all. I had to run this on a remote computer so it was a deal-breaker.
- I couldn’t get the thing to compile on my system. I’ve rooted around C++ apps before and I could probably figure it out, but I just didn’t want to.
- Because I couldn’t compile I couldn’t extend the expiration date beyond 5 years, like 25 years for example.
- I wanted to use it as part of a tool to move a ClickOnce app from one location to another. I needed a way to determine if a certificate was out of date by running a command line tool and checking the [generic]errorlevel[/generic] output.
So I set about rewriting the app in C#. The hardest part was tracking down all the P/Invoke declarations. The [generic]CertCreateSelfSignCertificate[/generic] API was particularly finicky. The rewrite works the same way as the original with a hard-coded year, but you can always hard-code your own or add support for an additional argument.
New features include:
- If you don’t supply a CN, it will look up the original and use it rather than a placeholder.
- You can use a “/e” argument to see if the given certificate has expired.
- All cleanups are wrapped in a [csharp]try..finally[/csharp] so you’re less likely to destabilize the system (which I did a couple of times and had to restart).
Here’s a complete example:
[shell linenumbers=”false”]
set certFile=C:\My Project\MyCert.pfx
renewcert “%certFile%” /e
if %errorlevel% equ 0 echo Certificate is not expired. & goto SkipCertRenew
echo Certificate is out of date and must be renewed!
renewcert “%certFile%” “%certFile%”
if %errorlevel% neq 0 echo Error %errorlevel% renewing certificate.
:SkipCertRenew
pause
[/shell]
Like the original author, I got it working and stopped, so it’s not the prettiest thing in the world—mea culpa. But this should be easier to extend to suit your needs.
Download: RenewCert.zip (for VS 2010)
Thank for this it has got me out of trouble a couple of times, when Microsoft took project deployment out of VS I thought the replacement ClickOnce was a great idea and it revolutionised the way I gave updates to my clients until applications were more than a year old
Well, I tried this by compiling (also in debug) in VS 2015 and on Windows 7 machine and it threw AccessViolation exception always at the CertNameToStr call (line 261 in main). I guess I’ll just
I gave it a try in VS2017 on Win10 64bit and also got an AccessViolation at the CertNameToStr call. Looks like a bug but the code is certainly “finickity” !
The fix is to change the signature of CertNameToStr in Crypt.cs from
[DllImport(“crypt32.dll”, CharSet = CharSet.Auto)]
public static extern int CertNameToStr(X509Encoding dwCertEncodingType, ref CRYPT_DATA_BLOB pName, CertNameType dwStrType, ref string psz, int csz);
to
[DllImport("crypt32.dll", CharSet = CharSet.Auto)]
public static extern int CertNameToStr(X509Encoding dwCertEncodingType, ref CRYPT_DATA_BLOB pName, CertNameType dwStrType, string psz, int csz);
then just change the method call line in Program.cs from
if ((d = Crypt.CertNameToStr(Crypt.X509Encoding.ASN_Encodings, ref certNameBlob, Crypt.CertNameType.CERT_X500_NAME_STR, ref buffer, 1024 * sizeof(char))) != 0)
To
if ((d = Crypt.CertNameToStr(Crypt.X509Encoding.ASN_Encodings, ref certNameBlob, Crypt.CertNameType.CERT_X500_NAME_STR, buffer, 1024 * sizeof(char))) != 0)
ie the output buffer string is passed by value not reference.
After that it worked fine for me.
Worked like a charm. Thanks.. I have added this code to Github and extended the expiry date to 105 years!! The project can be found here:
https://github.com/OceanAirdrop/ExtendClickOnceCertificate