18

The problem

  • Create and install temporary certificates to sign code in my development environment.
  • This has to be done with an unattended script (without user interaction).

The legacy script

Right now, I have this script that creates the certificates using the deprecated tool makecert:

makecert -r -pe -n "CN=My CA" -ss CA -sr CurrentUser -a sha256 -cy authority -sky signature -sv MyCA.pvk MyCA.cer
certutil -user -addstore Root MyCA.cer
certutil -addstore Root MyCA.cer
makecert -pe -n "CN=My Company" -a sha256 -cy end -sky signature -ic MyCA.cer -iv MyCA.pvk -sv MySPC.pvk MySPC.cer
pvk2pfx.exe -pvk MySPC.pvk -spc MySPC.cer -pfx MySPC.pfx
certutil -f -user -p "" -importPFX MySPC.pfx

The above script creates 2 certificates:

  1. MyCA.cer: A self-signed root authority certificate.
  2. MySPC.cer: The cerificate to sign my code (signed with MyCA.cer).

This script also opens dialog boxes requesting a user password and user confirmation to install the certificate in the Trusted Root Certification Authorities Store. I need this to be done without user interaction.

The new script

Following this instructions, I rewrited the legacy script with powershell cmdlet New-SelfSignedCertificate. This is what I tried:

# Create a self-signed root authority certificate.
$rootCert = New-SelfSignedCertificate -KeyExportPolicy Exportable -CertStoreLocation cert:\CurrentUser\My -DnsName "Development Root CA" -NotAfter (Get-Date).AddYears(5) -KeyusageProperty All -KeyUsage CertSign,CRLSign,DigitalSignature

# Export the root authority private key.
[System.Security.SecureString] $password = ConvertTo-SecureString -String "passwordx" -Force -AsPlainText
[String] $rootCertPath = Join-Path -Path cert:\CurrentUser\My\ -ChildPath "$($rootcert.Thumbprint)"
Export-PfxCertificate -Cert $rootCertPath -FilePath "MyCA.pfx" -Password $password
Export-Certificate -Cert $rootCertPath -FilePath "MyCA.crt"

# Create a "MySPC" certificate signed by our root authority.
$cert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName "MySPC" -TextExtension @("2.5.29.19={text}false") -KeyLength 2048 -Signer $rootCert -Type CodeSigningCert -KeyUsage None

# Save the signed certificate with private key into a PFX file and just the public key into a CRT file.
[String] $certPath = Join-Path -Path cert:\LocalMachine\My\ -ChildPath "$($cert.Thumbprint)"
Export-PfxCertificate -Cert $certPath -FilePath MySPC.pfx -Password $password
Export-Certificate -Cert $certPath -FilePath "MySPC.crt"

# Add MyCA certificate to the Trusted Root Certification Authorities.
$pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
$pfx.import("MyCA.pfx", $password, "Exportable,PersistKeySet")
$store = new-object System.Security.Cryptography.X509Certificates.X509Store(
    [System.Security.Cryptography.X509Certificates.StoreName]::Root,
    "localmachine"
)
$store.open("MaxAllowed")
$store.add($pfx)
$store.close()

# Import certificate.
Import-PfxCertificate -FilePath MySPC.pfx cert:\CurrentUser\My -Password $password

The new script creates and install MyCA.cer and MySPC.cer without user interaction but these certificates are not the same than the previous ones. For example, When I look at MyCA.cer, the intended purposes are:

Proves your identity to a remote computer
Ensures the identity of a remote computer
All issuance policies

Rather than the expected:

All issuance policies
All application policies

Other problems

  • With makecert the certificate is created with the Basic Constraint: Subject Type=CA, but I cannot create such constraint using New-SelfSignedCertificate.

  • Finally, the MySPC.cer is unable to sign my code, it fails with an error like "not valid for the selected purpose".

The Question

How can I generate the same certificates than the legacy script but in unattended way?

Thanks in advance.

EDIT

With changes proposed by Mötz, I'm able to sign but the error is raised in the validation. These are the commands:

Sign command

signtool.exe sign /v /a c:\git\...\Win32\det.dll

The following certificate was selected:
    Issued to: XXXXXXXXXX
    Issued by: My CA
    Expires:   Fri Dec 20 20:18:26 2019
    SHA1 hash: 0440F2B76E5BBF1F9CB4D24EF5E5AA54F4F4C2E1

Done Adding Additional Store
Successfully signed: c:\git\...\Win32\det.dll

Number of files successfully Signed: 1
Number of warnings: 0
Number of errors: 0

Validation command

signtool.exe verify /pa /v c:\git\...\Win32\det.dll
    
Signature Index: 0 (Primary Signature)
Hash of file (sha1): E4EC8126CC9510610AF4FC72CC8722B81B171AE1

Signing Certificate Chain:
    Issued to: My CA
    Issued by: My CA
    Expires:   Thu Dec 21 01:14:52 2023
    SHA1 hash: DA5B1972016D66294886CA3EDA2D4FEF245D7337

        Issued to: XXXXXXXXX
        Issued by: My CA
        Expires:   Sat Dec 21 01:24:53 2019
        SHA1 hash: 3316486BAF0A53C1C3227F1E522FF776B6F32CC9

File is not timestamped.

SignTool Error: The signing certificate is not valid for the requested usage.

Number of files successfully Verified: 0
Number of warnings: 0
Number of errors: 1

The solution

The accepted solution includes all key things to resolve the problem (a huge thanks to Mötz). I'm including my final script with minor changes just to help others.

#
# This script will create and install two certificates:
#     1. `MyCA.cer`: A self-signed root authority certificate. 
#     2. `MySPC.cer`: The cerificate to sign code in 
#         a development environment (signed with `MyCA.cer`).
# 
# No user interaction is needed (unattended). 
# Powershell 4.0 or higher is required.
#

# Define the expiration date for certificates.
$notAfter = (Get-Date).AddYears(10)

# Create a self-signed root Certificate Authority (CA).
$rootCert = New-SelfSignedCertificate -KeyExportPolicy Exportable -CertStoreLocation Cert:\CurrentUser\My -DnsName "My CA" -NotAfter $notAfter -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}CA=1") -KeyusageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature

# Export the CA private key.
[System.Security.SecureString] $password = ConvertTo-SecureString -String "passwordx" -Force -AsPlainText
[String] $rootCertPath = Join-Path -Path cert:\CurrentUser\My\ -ChildPath "$($rootcert.Thumbprint)"
Export-PfxCertificate -Cert $rootCertPath -FilePath "MyCA.pfx" -Password $password
Export-Certificate -Cert $rootCertPath -FilePath "MyCA.crt"

# Create an end certificate signed by our CA.
$cert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName "My Company Name" -NotAfter $notAfter -Signer $rootCert -Type CodeSigningCert -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}CA=0&pathlength=0")

# Save the signed certificate with private key into a PFX file and just the public key into a CRT file.
[String] $certPath = Join-Path -Path cert:\LocalMachine\My\ -ChildPath "$($cert.Thumbprint)"
Export-PfxCertificate -Cert $certPath -FilePath "MySPC.pfx" -Password $password
Export-Certificate -Cert $certPath -FilePath "MySPC.crt"

# Add MyCA certificate to the Trusted Root Certification Authorities.
$pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
$pfx.import("MyCA.pfx", $password, "Exportable,PersistKeySet")
$store = new-object System.Security.Cryptography.X509Certificates.X509Store(
    [System.Security.Cryptography.X509Certificates.StoreName]::Root,
    "localmachine"
)
$store.open("MaxAllowed")
$store.add($pfx)
$store.close()

# Remove MyCA from CurrentUser to avoid issues when signing with "signtool.exe /a ..."
Remove-Item -Force "cert:\CurrentUser\My\$($rootCert.Thumbprint)"

# Import certificate.
Import-PfxCertificate -FilePath MySPC.pfx cert:\CurrentUser\My -Password $password -Exportable
Community
  • 1
  • 1
Cartucho
  • 3,257
  • 2
  • 30
  • 55
  • Not done it this way myself, but I believe its: New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "Your Local Certificate Authority" -KeyusageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature – Scepticalist Dec 14 '18 at 11:35
  • @Scepticalist Thanks for taking time on this. I tried that but no luck. I've just updated my question to provide more context. Thanks. – Cartucho Dec 14 '18 at 13:52
  • If all you want to do is sign code, you can just create one cert with a command line like this: `New-SelfSignedCertificate -KeyExportPolicy Exportable -CertStoreLocation cert:\CurrentUser\My -DnsName "Development Root CA" -NotAfter (Get-Date).AddYears(5) -Type CodeSigningCert -KeyUsage DigitalSignature` This will get you back a thumbprint. Now, you can just sign using this thumbprint to select the signing certificate, like this: `signtool.exe" sign /sha1 [thumprint goes here] blabla.dll`. you don't need multiple certs, files, exports, password, etc. – Simon Mourier Dec 17 '18 at 11:50
  • Hi @SimonMourier, These scripts are used in a Automation/QA pipelines, so I'm using the self-signed certificates in the Trusted Root Certification Authorities store to create a development environment that more closely simulates the production environment. This is a customer requirement. Thanks for your suggestions, I'll take a look. – Cartucho Dec 17 '18 at 12:09
  • You can make things more complex at will, but it won't change the fact in the end you will use a certificate to sign the code. And it's much safer to use thumbprints than to carry pfx, passwords, files, etc. Plus some real-world code-signing certs just don't allow the "unattended" way easily, by design (can't create a pfx with password): https://stackoverflow.com/questions/17927895/automate-extended-validation-ev-code-signing – Simon Mourier Dec 17 '18 at 12:26
  • @SimonMourier I agree with you regarding make thing easier, but as I said before, this is not my choice, it's a customer requirement. Thanks for your comment, though. – Cartucho Dec 17 '18 at 12:35
  • I'm curious to what would be the exact terms of such a "customer requirement". If it's just about where to get certificates from, my comment is still valid, you can put the cert anywhere and add an `/s` parameter to signtool. – Simon Mourier Dec 17 '18 at 12:50
  • @Cartucho How do use the certificate to sign your development stuff today? Could you share a command example? – Mötz Dec 20 '18 at 12:28
  • @Cartucho I just tested your legacy script and it doesn't work as expected. I believe you should share some more details about the legacy workflow, so we can test that things actually behave as expected. Running the script prompts me for a password and the MySPC is never imported to my Personal cert store. – Mötz Dec 21 '18 at 09:11
  • I got this working! Finally! Works on iOS now. What are all those numbers though? – Simon_Weaver Jan 21 '19 at 08:43

2 Answers2

15

I just tested your code with the signtool.exe coming from my Visual Studio 2017 installation and things seems to work.

So I would really like to see the code / command you use for signing the files. Even more I would like to see the real output from the error that you are seeing. Could you try your signing process manually / by hand at first, so we are sure that we are focusing on the correct issue?

With that said, I spent some time digging around to answer some of the other questions you had.

Solving the first part of you wanting to only see

All issuance policies
All application policies

This is solved with the TextExtension parameter:

-TextExtension @("2.5.29.37={text}1.3.6.1.4.1.311.10.12.1")

Solving the part that you wanted the

Subject Type = CA

This is solved with the TextExtension parameter:

-TextExtension @("2.5.29.19={text}CA=1&pathlength=3")

The path length is used to limit how many levels of children that can use the certificate. Please read more here. The value 3 is just something is used while testing.

We then need to combine those 2 different TextExtensions entries:

-TextExtension @("2.5.29.37={text}1.3.6.1.4.1.311.10.12.1", "2.5.29.19={text}CA=1&pathlength=3")

Which will have us write the updated script like this

$rootCert = New-SelfSignedCertificate -KeyExportPolicy Exportable -CertStoreLocation cert:\CurrentUser\My -DnsName "Development Root CA" -NotAfter (Get-Date).AddYears(5) -TextExtension @("2.5.29.37={text}1.3.6.1.4.1.311.10.12.1", "2.5.29.19={text}CA=1&pathlength=3") -KeyusageProperty All -KeyUsage CertSign,CRLSign,DigitalSignature

# Export the root authority private key.
[System.Security.SecureString] $password = ConvertTo-SecureString -String "passwordx" -Force -AsPlainText
[String] $rootCertPath = Join-Path -Path cert:\CurrentUser\My\ -ChildPath "$($rootcert.Thumbprint)"
Export-PfxCertificate -Cert $rootCertPath -FilePath "MyCA.pfx" -Password $password
Export-Certificate -Cert $rootCertPath -FilePath "MyCA.crt"

# Create a "MySPC" certificate signed by our root authority.
$cert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName "MySPC" -Signer $rootCert -Type CodeSigningCert

# Save the signed certificate with private key into a PFX file and just the public key into a CRT file.
[String] $certPath = Join-Path -Path cert:\LocalMachine\My\ -ChildPath "$($cert.Thumbprint)"
Export-PfxCertificate -Cert $certPath -FilePath MySPC.pfx -Password $password
Export-Certificate -Cert $certPath -FilePath "MySPC.crt"

# Add MyCA certificate to the Trusted Root Certification Authorities.
$pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
$pfx.import("MyCA.pfx", $password, "Exportable,PersistKeySet")
$store = new-object System.Security.Cryptography.X509Certificates.X509Store(
    [System.Security.Cryptography.X509Certificates.StoreName]::Root,
    "localmachine"
)
$store.open("MaxAllowed")
$store.add($pfx)
$store.close()

# Import certificate.
Import-PfxCertificate -FilePath MySPC.pfx cert:\CurrentUser\My -Password $password

But like I stated earlier, your code seems to generate the correct certificates because I was able to use the certificate it generated and sign an .net EXE file with it.

Before signing

Before signing

Signing

SignTool sign /n "MySPC" 2LCS.exe

After signing

After signing

Update based on the new information

You need to specify the /pa switch on your verify command.

https://knowledge.digicert.com/solution/SO21771.html

https://learn.microsoft.com/en-us/windows/desktop/seccrypto/signtool

Question is if you would see the same with the makecert certificates?

Updated with working code

Your focus on the properties of the certificate got me down the wrong road. Based on a discussion from here I learned that we might need to have it created as a Class 3 code signing. I removed the 1.3.6.1.4.1.311.10.12.1 EKU extension and replaced it with 1.3.6.1.5.5.7.3.3. Please see below code example.

$rootCert = New-SelfSignedCertificate -KeyExportPolicy Exportable -CertStoreLocation cert:\CurrentUser\My -DnsName "Development Root CA" -NotAfter (Get-Date).AddYears(5) -TextExtension @("2.5.29.19={text}CA=1&pathlength=3", "2.5.29.37={text}1.3.6.1.5.5.7.3.3") -KeyusageProperty All -KeyUsage CertSign,CRLSign,DigitalSignature #-Type CodeSigningCert

# Export the root authority private key.
[System.Security.SecureString] $password = ConvertTo-SecureString -String "passwordx" -Force -AsPlainText
[String] $rootCertPath = Join-Path -Path cert:\CurrentUser\My\ -ChildPath "$($rootcert.Thumbprint)"
Export-PfxCertificate -Cert $rootCertPath -FilePath "MyCA.pfx" -Password $password
Export-Certificate -Cert $rootCertPath -FilePath "MyCA.crt"

# Create a "MySPC" certificate signed by our root authority.
$cert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName "MySPC" -Signer $rootCert -Type CodeSigningCert

# Save the signed certificate with private key into a PFX file and just the public key into a CRT file.
[String] $certPath = Join-Path -Path cert:\LocalMachine\My\ -ChildPath "$($cert.Thumbprint)"
Export-PfxCertificate -Cert $certPath -FilePath MySPC.pfx -Password $password
Export-Certificate -Cert $certPath -FilePath "MySPC.crt"

# Add MyCA certificate to the Trusted Root Certification Authorities.
$pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
$pfx.import("MyCA.pfx", $password, "Exportable,PersistKeySet")
$store = new-object System.Security.Cryptography.X509Certificates.X509Store(
    [System.Security.Cryptography.X509Certificates.StoreName]::Root,
    "localmachine"
)
$store.open("MaxAllowed")
$store.add($pfx)
$store.close()

# Import certificate.
Import-PfxCertificate -FilePath MySPC.pfx cert:\CurrentUser\My -Password $password

I ran the following signing command:

enter image description here

And after that I ran the verification command:

enter image description here

With that in place I believe that you should have a working solution. Please test it, verify and then extend it to include your timestamp signing as well.

Mötz
  • 1,682
  • 11
  • 17
  • Thanks a lot for such a detailed answer, I really appreciate it. I'll take a look on every suggestion you gave me and will contact you again with some conclusions. Thanks! – Cartucho Dec 20 '18 at 14:32
  • Please consider sharing how you do the signing, so we can replicate things at our end and see what might be your issue. – Mötz Dec 20 '18 at 14:35
  • I've just updated my question. Seems the error is how the certificate is added. – Cartucho Dec 20 '18 at 21:52
  • The problem seems to be in other place, just ignore my last comment. – Cartucho Dec 20 '18 at 22:38
  • From my perspective it seems that you are having issues with the timestamp. Please just sign and validate. Let us know if that works. Then we can focus on the timestamp afterwards. – Mötz Dec 20 '18 at 22:46
  • `signtool sign /n MySPC prog.exe` is OK, but `signtool verify /v /pa prog.exe` fails with "SignTool Error: The signing certificate is not valid for the requested usage." – Cartucho Dec 21 '18 at 02:39
  • @Cartucho Please see my latest update. I believe we have nailed it. – Mötz Dec 22 '18 at 10:00
  • @Cartucho I take it that we finally struck gold?! :) – Mötz Dec 22 '18 at 15:18
  • You really deserve it! Thanks a lot for your support. I also found that `1.3.6.1.5.5.7.3.3` made the trick, made some minor changes and it works. All your comments really help me. Thanks! – Cartucho Dec 22 '18 at 15:23
  • If you could share the entire solution you ended up with, it would be of great value for others when they find this question. Thanks for sticking with me during this! – Mötz Dec 22 '18 at 16:08
0

Installation of certificate on cert store do by Certificate Propagation service.

So you can scan(Scan API) Certificate Propagation service and develop like it.

You can use API Monitor.

VOLVO
  • 541
  • 5
  • 16