Q: When is a validity period not a validity period?
A: When it’s a Certificate Authority after Windows 2000.
Those helpful chaps over at the Windows PKI Blog provided a script (setupca.vbs) to install a Certificate Authority on Windows Server 2008 and R2, and recently we had a request to be able to specify the expiration date of the CA’s own certificate (the default is 5 years).
In deconstructing the script to add this functionality I ran into a couple of issues which I thought it might be useful to share here…
So far, so good – the CASetupProperty typedef shows 2 properties of interest; ENUM_SETUPPROP_VALIDITYPERIOD and ENUM_SETUPPROP_VALIDITYPERIODUNIT.
First “gotcha” – the descriptions posted on MSDN for these 2 properties are backwards:
|ENUM_SETUPPROP_VALIDITYPERIOD A VT_I4 value that specifies the number of units in the validity period as specified by the ENUM_SETUPPROP_VALIDITYPERIODUNIT property type. For a subordinate CA, the validity period is determined by the parent CA. ENUM_SETUPPROP_VALIDITYPERIODUNIT A VT_I4 value that specifies a value of the ENUM_PERIOD enumeration that indicates the time units of the validity period. For a subordinate CA, the validity period time unit is determined by the parent CA.|
There is a reason for this – in Windows Server 2000 this is how it was, but from Windows Server 2003 onwards these got switched around.
If you check out the Ask the Directory Services Team blog you can see the definitions listed down in the CAPolicy.inf section where they talk about the history of RenewalValidityPeriod and RenewalValidityPeriodUnits along the same vein.
The second “gotcha” was due to VBScript not being strongly-typed – when trying to call SetCASetupProperty to specify SETUPPROP_VALIDITYPERIODUNIT was equal to 13 (as a test), the script engine kept falling over with error 0x80071707:
”The actual data type of the property did not match the expected data type of the property”
It seems my 13 wasn’t big enough :)
The original code looked like this:
Call g_oCASetup.SetCASetupProperty(SETUPPROP_VALIDITYPERIODUNIT, iSelectedValidYears)
To make it work, I needed to type-cast the variable to the expected VT_I4 type as defined by MSDN, using CLng():
Call g_oCASetup.SetCASetupProperty(SETUPPROP_VALIDITYPERIODUNIT, CLng( iSelectedValidYears ) )
With those wrinkles ironed out, the script ran happily with my new “/SV” switch to specify the validity period in years – the screenshot below shows the debug spew confirming that I was updating SETUPPROP_VALIDITYPERIODUNIT, and that the certificate produced was indeed valid for 13 years from the current date, using an 8K key length:
To avoid multiple copies of the same script floating around, requiring parallel updates if further changes are made in the future, I won’t post the whole script here, but I will document the bits I added so you can implement them in the setupca.vbs yourself if needed.
For each of the changes below, the lines in blue are from the original script, and the lines in red are the bits I inserted:
1. Add the documentation for the new switch:
|Call OutputLine(ECHOMINIMAL, "/SA <Alg> - Specify Hash algorithm") Call OutputLine(ECHOMINIMAL, "/SV <Validity> - Specify Validity period (in years)") Call OutputLine(ECHOMINIMAL, "/SN <Name> - Specify CA Name")|
2. Add a constant to define SETUPPROP_VALIDITYPERIOD as years (for simplicity we won’t make this a variable):
Const SETUPPROP_WEBCANAME = 16
'CA validity period is an ENUM_PERIOD type, here we only allow YEARS Const ENUM_PERIOD_YEARS = 6
3. Add a variable to hold the (optional) validity period specified at the command line:
'Signing key length Dim iSelectedKeySize iSelectedKeySize = "" ' DEF_SEL_KEY_SIZE
'Validity period for the CA certificate, if specified by the user Dim iSelectedValidYears iSelectedValidYears = ""
'Save request to file, for submitting to offline root Dim strRequestFile strRequestFile = ""
4. Add the debug output to confirm the specified validity period (if it was entered):
|wscript.echo "bWebPages: " & bWebPages wscript.echo "iSelectedKeySize: " & iSelectedKeySize wscript.echo "iSelectedValidYears: " & iSelectedValidYears|
5. Add the case to parse the command line switch “/SV”:
If Not blnGetArg("Key length", iSelectedKeySize, intArgIter) Then intParseCmdLine = CONST_ERROR Exit Function ' intParseCmdLine End If
intArgIter = intArgIter + 1
If Not blnGetArg("Validity Period", iSelectedValidYears, intArgIter) Then intParseCmdLine = CONST_ERROR Exit Function ' intParseCmdLine End If
intArgIter = intArgIter + 1
If Not blnGetArg("Hash algorithm",strSelectedHashAlg, intArgIter) Then intParseCmdLine = CONST_ERROR Exit Function ' intParseCmdLine End If
intArgIter = intArgIter + 1
6. Add the code to set the object properties SETUPPROP_VALIDITYPERIOD and SETUPPROP_VALIDITYPERIODUNIT:
If (bReuseCert = False) Then Dim bProviderSet bProviderSet = SetProvider(g_oCASetup, strSelectedCSP, strSelectedHashAlg, iSelectedKeySize)
If (False = bProviderSet) Then Call OutputLine(ECHOMINIMAL, "InstallAndVerifyCA:unable to set key properties!") Exit Function 'InstallAndVerifyCA End If 'error occurred
If (iSelectedValidYears <> "") Then 'Specify CA validity period (NOTE: this is the unit SIZE!) Call g_oCASetup.SetCASetupProperty(SETUPPROP_VALIDITYPERIOD, CLng(ENUM_PERIOD_YEARS))
If (0 <> Err.Number) Then Call PrintErrorInfo("InstallAndVerifyCA:unable to set SETUPPROP_VALIDITYPERIOD!", Err) Exit Function 'InstallAndVerifyCA End If Call Err.Clear()
'Specify CA validity period unit (NOTE: this is the NUMBER of units!) Call OutputLine(ECHOMINIMAL, "InstallAndVerifyCA: SetCASetupProperty - ValidityPeriodUnit = " & CLng(iSelectedValidYears)) Call g_oCASetup.SetCASetupProperty(SETUPPROP_VALIDITYPERIODUNIT, CLng(iSelectedValidYears))
If (0 <> Err.Number) Then Call PrintErrorInfo("InstallAndVerifyCA:unable to set SETUPPROP_VALIDITYPERIODUNIT!", Err) Exit Function 'InstallAndVerifyCA End If Call Err.Clear() End If End If
The code above is very, very briefly tested on my W2K8R2 lab server and is provided “as-is”, without any liability or support.
There is no error or boundary checking done on the value entered.
Be careful of any inadvertent line-wrapping that may occur when viewed in small browser windows, VBScript is very unforgiving…