Skip to content

Instantly share code, notes, and snippets.

@larsw
Created December 20, 2013 11:34
Show Gist options
  • Select an option

  • Save larsw/8053599 to your computer and use it in GitHub Desktop.

Select an option

Save larsw/8053599 to your computer and use it in GitHub Desktop.
Gaaah!
<HTML>
<Head>
<Meta HTTP-Equiv="Content-Type" Content="text/html; charset=UTF-8">
<Meta HTTP-Equiv="X-UA-Compatible" Content="IE=7">
<Title>Microsoft Active Directory Certificate Services</Title>
</Head>
<Body BgColor=#FFFFFF Link=#0000FF VLink=#0000FF ALink=#0000FF OnLoad="postLoad();" ><Font ID=locPageFont Face="Arial">
<Table Border=0 CellSpacing=0 CellPadding=4 Width=100% BgColor=#008080>
<TR>
<TD><Font Color=#FFFFFF><LocID ID=locMSCertSrv><Font Face="Arial" Size=-1><B><I>Microsoft</I></B> Active Directory Certificate Services &nbsp;--&nbsp; wilhelmsen-CA &nbsp;</Font></LocID></Font></TD>
<TD ID=locHomeAlign Align=Right><A Href="/certsrv"><Font Color=#FFFFFF><LocID ID=locHomeLink><Font Face="Arial" Size=-1><B>Home</B></Font></LocID></Font></A></TD>
</TR>
</Table>
<Form Name=UIForm OnSubmit="goNext();return false;" Method=Post>
<Input Type=Hidden Name=SourcePage Value="certrqbi">
<P ID=locPageTitle> <B> User Certificate - Identifying Information </B>
<!-- Green HR --><Table Border=0 CellSpacing=0 CellPadding=0 Width=100%><TR><TD BgColor=#008080><Img Src="certspc.gif" Alt="" Height=2 Width=1></TD></TR></Table>
<Span ID=spnFixTxt Style="display:none">
<Table Border=0 CellSpacing=0 CellPadding=4 Style="Color:#FF0000"><TR><TD><LocID ID=locBadCharError>
<I>Please correct the fields marked in <B>RED</B>.</I>
The name field may not be blank.
The country/region field must be a two letter ISO 3166 country/region code.
</LocID></TD></TR></Table>
</Span>
<Span ID=spnErrorTxt Style="display:none">
<Table Border=0 CellSpacing=0 CellPadding=4 Style="Color:#FF0000">
<TR><TD><LocID ID=locErrMsgBasic>
<B>An error occurred</B> while creating the certificate request.
Please verify that you selected the correct CSP, or contact
an administrator for assistance.
</LocID></TD></TR><TR><TD><Span ID=spnErrorDetailsBtn>
<Table Border=0 CellSpacing=0 CellPadding=0>
<TR> <TD Width=20></TD><TD>
<Input ID=locBtnDetails Type=Button Value="Details &gt;&gt;" OnClick="showErrorDetails();blur();">
</TD></TR>
</Table>
</Span></TD></TR><TR><TD><Span ID=spnErrorDetails1 Style="display:none">
<LocID ID=locErrorCause><B>Suggested cause:</B></LocID><BR>
<Span ID=spnErrorMsg></Span>
</Span></TD></TR><TR>
<TD><Span ID=spnErrorDetails2 Style="display:none"><LocID ID=locErrorNumber><Font Size=-2>Error: <Span ID=spnErrorNum></Span></Font></LocID></Span></TD>
</TR>
</Table>
</Span>
<P>
<Table Border=0 CellSpacing=0 CellPadding=0>
<TR> <!-- establish column widths. -->
<TD Height=4 Width=100></TD> <!-- label column, top border -->
<TD RowSpan=50 Width=4></TD> <!-- label spacing column -->
<TD></TD> <!-- field column -->
</TR>
<!-- <TR><TD ColSpan=3 Height=15></TD></TR>-->
<TR>
<TD ID=locReadyToGo ColSpan=3><Font Face="Arial">
No further identifying information is required.
<LocID ID=locReadyToGo2>To complete your certificate, press submit.</LocID></Font></TD>
</TR>
<TR ID=trMoreOptHide><TD Height=12></TD><TD></TD></TR>
<TR ID=trMoreOptHide>
<TD><Font Size=-1><Span ID=spnShowMoreOptions tabindex=0 Style="cursor:hand; color:#0000FF; text-decoration:underline;"
OnContextMenu="return false;"
OnMouseOver="window.status='Click to show more options.'; return true;"
OnMouseOut="window.status=''; return true;"
OnKeyDown="if (13==event.keyCode) {showMoreOptions();return false;} else if (9==event.keyCode) {return true;};return false;"
OnClick="showMoreOptions();return false;">
<LocID ID=locMoreOpt>More Options &gt;&gt;</LocID></Span></Font>
</TD>
<TD></TD>
</TR>
<!-- More options -->
<TR ID=trMoreOptShow Style="display:none">
<TD ID=locMoreOptHead ColSpan=3><Font Size=-1><BR><B>More Options:</B></Font></TD>
</TR>
<TR ID=trMoreOptShow Style="display:none"><TD ColSpan=3 Height=2 BgColor=#008080></TD></TR>
<TR ID=trMoreOptShow Style="display:none"><TD ColSpan=3 Height=3></TD></TR>
<TR ID=trMoreOptShow Style="display:none">
<TD ColSpan=3><Font Face="Arial"><Label For=lbCSPID><LocID ID=locCSPInstr>
Select a Cryptographic Service Provider:</LocID><Label></Font></TD>
</TR>
<TR ID=trMoreOptShow Style="display:none"><TD Height=4></TD> <TD></TD></TR>
<TR ID=trMoreOptShow Style="display:none">
<TD ID=locCSPLabel Align=Right><Font Size=-1>CSP:</Font></TD>
<TD><Select Name=lbCSP ID=lbCSPID>
<Option ID=locLoading>Loading...</Option>
</Select>
</TD>
</TR>
<TR ID=trMoreOptShow Style="display:none"><TD Height=8></TD> <TD></TD></TR>
<TR ID=trMoreOptShow Style="display:none">
<TD></TD>
<TD>
<Table Border=0 CellSpacing=0 CellPadding=0><TR>
<TD><Input Type=Checkbox ID=cbStrongKey Name=cbStrongKey></TD>
<TD><Font Size=-1><Label For=cbStrongKey ID=locStrongKeyLabel>Enable strong private key protection</Label></Font></TD>
</TR></Table>
</TD>
</TR>
<TR ID=trMoreOptShow Style="display:none"><TD Height=8></TD> <TD></TD></TR>
<TR ID=trMoreOptShow Style="display:none">
<TD ID=locRequestFormatLabel Align=Right><LocID ID=locRequestFormat><Font Size=-1>Request Format:</Font></LocID></TD>
<TD>
<Input Type=Radio ID=rbFormatPKCS10 Name=rbRequestFormat Value="0" Checked><Label For=rbFormatPKCS10 ID=locFormatPKCS10Label>CMC</Label>
<LocID ID=locSpc5>&nbsp;&nbsp;&nbsp;<LocID>
<Input Type=Radio ID=rbFormatCMC Name=rbRequestFormat Value="1"><Label For=rbFormatCMC ID=locFormatCMCLabel>PKCS10</Label>
</TD>
</TR>
<TR ID=trMoreOptShow Style="display:none">
<TD ColSpan=3><LocID ID=locAdvancedLink><Font Face="Arial" Size=-1><BR>
If you need to use an advanced option that is not listed here,
<A Href="certrqma.asp">use the Advanced Certificate Request form</A>.</Font></LocID></TD>
</TR>
<!-- end More options -->
<TR><TD ColSpan=3><Font Size=-1><BR></Font></TD></TR>
<TR><TD ColSpan=3 Height=2 BgColor=#008080><Img Src="certspc.gif" Alt="" Height=2 Width=1></TD></TR>
<TR><TD ColSpan=3 Height=3></TD></TR>
<TR><TD></TD>
<TD ID=locSubmitAlign Align=Right>
<Input ID=locBtnSubmit Type=Submit Name=btnSubmit Value="Submit &gt;" Style="width:.75in">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</TD></TR>
<TR><TD ColSpan=3 Height=40></TD></TR>
</Table>
<!-- Green HR --><Table Border=0 CellSpacing=0 CellPadding=0 Width=100%><TR><TD BgColor=#008080><Img Src="certspc.gif" Alt="" Height=2 Width=1></TD></TR></Table>
<!-- White HR --><Table Border=0 CellSpacing=0 CellPadding=0 Width=100%><TR><TD BgColor=#FFFFFF><Img Src="certspc.gif" Alt="" Height=5 Width=1></TD></TR></Table>
</Form>
</Font>
<!-- ############################################################ -->
<!-- End of standard text. Scripts follow -->
<Script Language="VBSCRIPT">
Const ContextUser = 1
Const ContextMachine = 2
Const CR_OUT_BASE64HEADER = &H00000000
Const CR_OUT_BASE64 = &H00000001
Const CR_OUT_BINARY = &H00000002
Const CRYPT_STRING_ANY = &H00000007
Const CR_OUT_CHAIN = &H00000100
Const FR_PROP_FULLRESPONSE = &H00000001
Const PROPTYPE_BINARY = &H00000003
Const CERT_DATA_ENCIPHERMENT_KEY_USAGE = &H10
Const CERT_KEY_ENCIPHERMENT_KEY_USAGE = &H20
Const CERT_NON_REPUDIATION_KEY_USAGE = &H40
Const CERT_DIGITAL_SIGNATURE_KEY_USAGE = &H80
Const CERT_SYSTEM_STORE_LOCAL_MACHINE = &H20000
Const CRYPT_MACHINE_KEYSET = &H20
Const XECT_EXTENSION_V1 = 1
Const XCN_CRYPT_UNKNOWN_INTERFACE = 0
Const XCN_NCRYPT_ALLOW_EXPORT_FLAG = 1
Const XCN_CRYPT_HASH_INTERFACE = 2
Const XCN_CRYPT_SECRET_AGREEMENT_INTERFACE = 4
Const XCN_CRYPT_SIGNATURE_INTERFACE = 5
Const XCN_NCRYPT_UI_NO_PROTECTION_FLAG = 0
Const XCN_NCRYPT_UI_PROTECT_KEY_FLAG = 1
Const szOID_PKIX_KP_CODE_SIGNING = "1.3.6.1.5.5.7.3.3"
Const SPC_INDIVIDUAL_SP_KEY_PURPOSE_OBJID = "1.3.6.1.4.1.311.2.1.21"
Const SPC_COMMERCIAL_SP_KEY_PURPOSE_OBJID = "1.3.6.1.4.1.311.2.1.22"
Const CERTENROLL_INDEX_BASE = 0
Const PROV_SSL = 6
Const PROV_RSA_SCHANNEL = 12
Const AllowNone = 0
Const AlgorithmFlagsNone = 0
</SCRIPT>
<Script Language="JavaScript">
//
// Implement the GenKeyFlags routine in Javascript
// because its easier to perform bitwise manipulation
//
function XEp_SetGenKeyFlags(objPrivateKey, nGenKeyFlags)
{
// some constants defined in wincrypt.h:
var CRYPT_EXPORTABLE=1;
var CRYPT_USER_PROTECTED=2;
objPrivateKey.KeyProtection = (0 != (CRYPT_USER_PROTECTED & nGenKeyFlags)) ? XCN_NCRYPT_UI_PROTECT_KEY_FLAG : XCN_NCRYPT_UI_NO_PROTECTION_FLAG;
objPrivateKey.ExportPolicy = (0 != (CRYPT_EXPORTABLE & nGenKeyFlags)) ? XCN_NCRYPT_ALLOW_EXPORT_FLAG : 0;
objPrivateKey.Length = nGenKeyFlags >> 16;
}
</script>
<Script Language="VBSCRIPT">
'----------------------------------------------------------------------
'
' ENROLLMENT OBJECT PROXIES
'
' Routines which operate on either
'
' a) an instance of the IX509Enrollment interface (Longhorn+)
' or b) xenroll (downlevels)
'
'----------------------------------------------------------------------
Function XE_Enroll_AcceptResponse(objEnroll, sPKCS7, bMachine)
Dim EnrollmentContext
If True = bMachine Then
EnrollmentContext = ContextMachine
Else
EnrollmentContext = ContextUser
End If
Call objEnroll.Initialize(EnrollmentContext)
Call objEnroll.InstallResponse(AllowNone, sPKCS7, CRYPT_STRING_ANY, "")
' True=bLH
End Function
Function XE_Enroll_addFriendlyNameToRequest(objEnroll, sName)
objEnroll.CertificateFriendlyName = sName
End Function
Function XE_Enroll_CreateRequest(objEnroll, lFlags, sDistinguishedName, sCertUsage)
Const CRYPT_STRING_BASE64REQUESTHEADER = 3
XE_Enroll_CreateRequest = objEnroll.CreateRequest(CRYPT_STRING_BASE64REQUESTHEADER)
End Function
Function XE_Enroll_InstallPKCS7Ex(objEnroll, sPKCS7)
objEnroll.Initialize(ContextUser)
XE_Enroll_InstallPKCS7Ex = objEnroll.InstallResponse(AllowNone, sPKCS7, CRYPT_STRING_ANY, "")
End Function
'----------------------------------------------------------------------
'
' PRIVATEKEY OBJECT PROXIES
'
' Routines which operate on either
'
' a) an instance of the IX509PrivateKey interface (Longhorn+)
' or b) xenroll (downlevels)
'
'----------------------------------------------------------------------
Function XE_PrivateKey_GetKeyLenEx(objPrivateKey, nSizeSpec, nKeySpec)
Dim Algorithm, CspInformation, CspStatus
Set CspInformation = g_objCSPInformations.ItemByName(objPrivateKey.ProviderName)
Set CspStatus = g_objCSPInformations.GetCspStatusFromProviderName(objPrivateKey.ProviderName, nKeySpec)
Set Algorithm = CspStatus.CspAlgorithm
If XEKL_KEYSIZE_INC = nSizeSpec Then
XE_PrivateKey_GetKeyLenEx = Algorithm.IncrementLength
ElseIf XEKL_KEYSIZE_MIN=nSizeSpec Then
XE_PrivateKey_GetKeyLenEx = Algorithm.MinLength
ElseIf XEKL_KEYSIZE_MAX=nSizeSpec Then
XE_PrivateKey_GetKeyLenEx = Algorithm.MaxLength
Else 'assume XEKL_KEYSIZE_DEFAULT=nSizeSpec
XE_PrivateKey_GetKeyLenEx = Algorithm.DefaultLength
End If
End Function
Function XE_PrivateKey_SetProviderNameAndType( _
objPrivateKey, _
sProviderName, _
nProviderType _
)
objPrivateKey.ProviderName = sProviderName
objPrivateKey.ProviderType = sProviderType
' True=bLH
End Function
'----------------------------------------------------------------------
'
' REQUEST OBJECT PROXIES
'
' Routines which operate on either
'
' a) an instance of the IX509CertificateRequestPKCS10 interface (Longhorn+)
' or b) xenroll (downlevels)
'
'----------------------------------------------------------------------
Function XE_Request_addCertTypeToRequestEx( _
objRequest, _
nType, _
sOIDOrName, _
nMajorVersion, _
bMinorVersion, _
nMinorVersion _
)
If XECT_EXTENSION_V1=nType Then
Dim X509ExtensionTemplateName
Set X509ExtensionTemplateName = g_objClassFactory.CreateObject("X509Enrollment.CX509ExtensionTemplateName")
Call X509ExtensionTemplateName.InitializeEncode(sOIDOrName)
Call objRequest.X509Extensions.Add(X509ExtensionTemplateName)
Else
Dim ObjectId
Dim X509ExtensionTemplateV2
Set ObjectId = g_objClassFactory.CreateObject("X509Enrollment.CObjectId")
Set X509ExtensionTemplateV2 = g_objClassFactory.CreateObject("X509Enrollment.CX509ExtensionTemplate")
Call ObjectId.InitializeFromValue(sOIDOrName)
Call X509ExtensionTemplateV2.InitializeEncode(ObjectId, nMajorVersion, nMinorVersion)
Call objRequest.X509Extensions.Add(X509ExtensionTemplateV2)
End If
End Function
Function XE_Request_AddDistinguishedName(objRequest, sDN)
Const XCN_CERT_NAME_STR_ENABLE_PUNYCODE_FLAG = 2097152
Dim X500DistinguishedName
Set X500DistinguishedName = g_objClassFactory.CreateObject("X509Enrollment.CX500DistinguishedName")
Call X500DistinguishedName.Encode(sDN, XCN_CERT_NAME_STR_ENABLE_PUNYCODE_FLAG)
objRequest.Subject = X500DistinguishedName
End Function
Function XE_Request_AddEKUToRequest(objRequest, sCertUsage)
Dim bAddedCodeSignEKU
Dim bMustAddCodeSignEKU
Dim nLen
Dim ObjectId
Dim ObjectIds
Dim X509ExtensionEnhancedKeyUsage
bAddedCodeSignEKU = False
bMustAddCodeSignEKU = False
nLen = Len(sCertUsage)
Set ObjectIds = g_objClassFactory.CreateObject("X509Enrollment.CObjectIds")
While nLen > 0
' Strip whitespace and commas from the current
If Left(sCertUsage, 1) = "," Or Left(sCertUsage, 1) = " " Then
sCertUsage = Right(sCertUsage, nLen - 1)
nLen = nLen - 1
' We have another EKU to parse
Else
Dim nCommaIndex
Dim nEndIndex
Dim nSpaceIndex
Dim sEKU
nCommaIndex = InStr(sCertUsage, ",")
nSpaceIndex = InStr(sCertUsage, " ")
If Not nCommaIndex = nSpaceIndex Then
' This is not the last EKU in the list
If 0 = nCommaIndex Or 0 = nSpaceIndex Then
If 0 = nCommaIndex Then
nEndIndex = nSpaceIndex
Else
nEndIndex = nCommaIndex
End If
Else
If nCommaIndex > nSpaceIndex Then
nEndIndex = nSpaceIndex
Else
nEndIndex = nCommaIndex
End If
End If
sEKU = Left(sCertUsage, nEndIndex - 1)
Else
sEKU = sCertUsage
End If
'
' Check if we have EKUs that imply codesigning
'
If 0 = StrComp(sEKU, SPC_INDIVIDUAL_SP_KEY_PURPOSE_OBJID) Or _
0 = StrComp(sEKU, SPC_COMMERCIAL_SP_KEY_PURPOSE_OBJID) Then
bMustAddCodeSignEKU = True
End If
If 0 = StrComp(sEKU, szOID_PKIX_KP_CODE_SIGNING) Then
bAddedCodeSignEKU = True
End If
'
' Add this EKU to our collection
'
Set ObjectId = g_objClassFactory.CreateObject("X509Enrollment.CObjectId")
ObjectId.InitializeFromValue(sEKU)
ObjectIds.Add(ObjectId)
sCertUsage = Right(sCertUsage, nLen - Len(sEKU))
nLen = nLen - Len(sEKU)
End If
Wend
If True = bMustAddCodeSignEKU And False = bAddedCodeSignEKU Then
' We require the codesigning EKU and have not yet added it.
' Add it to the request now.
Set ObjectId = g_objClassFactory.CreateObject("X509Enrollment.CObjectId")
ObjectId.InitializeFromValue(szOID_PKIX_KP_CODE_SIGNING)
ObjectIds.Add(ObjectId)
End If
'
' Create the EKU extension
'
Set X509ExtensionEnhancedKeyUsage = g_objClassFactory.CreateObject("X509Enrollment.CX509ExtensionEnhancedKeyUsage")
X509ExtensionEnhancedKeyUsage.InitializeEncode(ObjectIds)
'
' Add the EKU extension to the request
'
objRequest.X509Extensions.Add(X509ExtensionEnhancedKeyUsage)
End Function
Function XE_Request_EnableSMIMECapabilities(objRequest, bEnable)
objRequest.SmimeCapabilities = bEnable
End Function
Function XE_Request_GetSupportedKeySpec(objPrivateKey)
Dim CspInformation
Set CspInformation = g_objCSPInformations.ItemByName(objPrivateKey.ProviderName)
XE_Request_GetSupportedKeySpec = CspInformation.KeySpec
' True=bLH
End Function
Function XE_Request_InitializeCspInformation( _
objRequest, _
objPrivateKey, _
bMachine, _
nKeySpec, _
nGenKeyFlags, _
sProviderName, _
nProviderType, _
sContainerName, _
bReuseKey _
)
Dim nContext
If bReuseKey Then
objPrivateKey.ContainerName = sContainerName
objPrivateKey.Existing = True
Else
objPrivateKey.ProviderName = sProviderName
objPrivateKey.ProviderType = nProviderType
If Not 0=nKeySpec Then
objPrivateKey.KeySpec = nKeySpec
End If
If Not ""=sContainerName Then
objPrivateKey.ContainerName = sContainerName
End If
' Call JavaScript to set the GenKeyFlags, as
' it is more cumbersome to parse the GenKeyFlags in VBScript
Call XEp_SetGenKeyFlags(objPrivateKey, nGenKeyFlags)
End If
If True = bMachine Then
nContext = ContextMachine
Else
nContext = ContextUser
End If
objPrivateKey.MachineContext = bMachine
On Error Resume Next
Call objRequest.InitializeFromPrivateKey(nContext, objPrivateKey, "")
' True=bLH
If 0<>Err.Number Then
XE_Request_InitializeCspInformation = Err.Number
Exit Function
Else
XE_Request_InitializeCspInformation = 0
End If
End Function
Function XE_Request_LimitExchangeKeyToEncipherment(objRequest, bLimitToEncipherment, nKeySpec)
Dim KeyUsageExtension
Set KeyUsageExtension = g_objClassFactory.CreateObject("X509Enrollment.CX509ExtensionKeyUsage")
If AT_SIGNATURE = nKeySpec Then
KeyUsageExtension.InitializeEncode(CERT_DIGITAL_SIGNATURE_KEY_USAGE Or CERT_NON_REPUDIATION_KEY_USAGE)
Else
If True = bLimitToEncipherment Then
KeyUsageExtension.InitializeEncode(CERT_KEY_ENCIPHERMENT_KEY_USAGE Or CERT_DATA_ENCIPHERMENT_KEY_USAGE)
Else
KeyUsageExtension.InitializeEncode(CERT_KEY_ENCIPHERMENT_KEY_USAGE Or CERT_DATA_ENCIPHERMENT_KEY_USAGE Or CERT_DIGITAL_SIGNATURE_KEY_USAGE Or CERT_NON_REPUDIATION_KEY_USAGE)
End If
End If
objRequest.X509Extensions.Add(KeyUsageExtension)
End Function
Function XE_Request_SetHashAlgorithm(objRequest, vHashAlgorithm)
Dim ObjectId
Set ObjectId = g_objClassFactory.CreateObject("X509Enrollment.CObjectId")
ObjectId.InitializeFromValue(vHashAlgorithm)
objRequest.HashAlgorithm = ObjectId
End Function
'----------------------------------------------------------------------
'
' CMC OBJECT PROXIES
'
' Routines which operate on either
'
' a) an instance of the IX509CertificateRequestCMC interface (Longhorn+)
' or b) xenroll (downlevels)
'
'----------------------------------------------------------------------
Function XE_CMC_SetArchivalCertificate(objCMC, sCAExchangeCert)
objCMC.KeyArchivalCertificate = sCAExchangeCert
XE_CMC_SetArchivalCertificate = Err.Number
End Function
'----------------------------------------------------------------------
'
' GENERAL UTILITY ROUTINES
'
'----------------------------------------------------------------------
Function XE_GetProviderType(sProviderName)
Dim CspInformation
Set CspInformation = g_objClassFactory.CreateObject("X509Enrollment.CCspInformation")
CspInformation.InitializeFromName(sProviderName)
XE_GetProviderType = CspInformation.Type
' True=bLH
End Function
Function XE_InitializeEnrollObject(objRequest)
g_objEnroll.InitializeFromRequest(objRequest)
End Function
Function XE_InitializeCmcObject()
g_objRequestCmc.InitializeFromInnerRequest(g_objRequest)
End Function
Function XE_LegacyCsp(sProviderName)
Dim CspInformation
Set CspInformation = g_objClassFactory.CreateObject("X509Enrollment.CCspInformation")
CspInformation.InitializeFromName(sProviderName)
XE_LegacyCsp = CspInformation.LegacyCsp
End Function
Function XE_reset()
Set g_objEnroll = g_objClassFactory.CreateObject("X509Enrollment.CX509Enrollment")
Set g_objPrivateKey = g_objClassFactory.CreateObject("X509Enrollment.CX509PrivateKey")
Set g_objRequest = g_objClassFactory.CreateObject("X509Enrollment.CX509CertificateRequestPkcs10")
Set g_objRequestCMC = g_objClassFactory.CreateObject("X509Enrollment.CX509CertificateRequestCmc")
End Function
Function XE_ReuseHardwareKeyIfUnableToGenNew(bReuse)
' Nothing to do here -- this is not configurable in LH
End Function
</Script>
<Script Language="JavaScript">
//----------------------------------------------------------------
// convert a (signed) number into a (unsigned) hex string
function toHex(number) {
var sRight=(number&0x0FFFFFFF).toString(16).toUpperCase();
sRight="0000000".substring(0, 7-sRight.length)+sRight;
return ((number>>28)&0x0000000F).toString(16).toUpperCase()+sRight;
}
</Script>
<script language="JScript">
//----------------------------------------------------------------
// IE SPECIFIC:
// disable all the controls on this page so the user can't do anything
function disableAllControls() {
// some pages do not have any controls
if (null==document.UIForm) {
return;
}
// disable every control on the page
var nCount=document.UIForm.elements.length;
var nIndex;
for (nIndex=0; nIndex<nCount; nIndex++) { //>
document.UIForm.elements(nIndex).disabled=true;
}
}
</script>
<Span ID=spnCertEnroll Style="display:none">
<OBJECT id='g_objClassFactory' CLASSID='clsid:884e2049-217d-11da-b2a4-000e7bbb2b09'>
</object>
</Span>
<Script Language="VBScript">
Option Explicit
Dim g_objEnroll
Dim g_objPrivateKey
Dim g_objRequest
Dim g_objRequestCMC
Dim g_certEnrollLoadError
Dim g_objCSPInformations
g_certEnrollLoadError = 0
On Error resume next
Set g_objEnroll = g_objClassFactory.CreateObject("X509Enrollment.CX509Enrollment")
Set g_objPrivateKey = g_objClassFactory.CreateObject("X509Enrollment.CX509PrivateKey")
Set g_objRequest = g_objClassFactory.CreateObject("X509Enrollment.CX509CertificateRequestPkcs10")
Set g_objRequestCMC = g_objClassFactory.CreateObject("X509Enrollment.CX509CertificateRequestCmc")
Set g_objCSPInformations = g_objClassFactory.CreateObject("X509Enrollment.CCspInformations")
If 0<>Err.Number Then
g_certEnrollLoadError = Err.Number
Else
g_objCSPInformations.AddAvailableCsps
End If
</Script>
<!-- IE SPECIFIC: -->
<Span ID=spnXEnroll Style="display:none">
<!-- XEnroll will be inserted here -->
</Span>
<!-- A DHTML alert box, for the transient message routines -->
<Table Border=0 CellSpacing=0 CellPadding=0 ID=tblWorkingMsg Style="display:none; position:absolute;">
<TR>
<TD BgColor=#000040 Height=3 ColSpan=3></TD>
</TR> <TR>
<TD BgColor=#000040 Width=3></TD>
<TD BgColor=#008080><Font Color=#FFFFFF><B><BR>&nbsp;&nbsp;&nbsp;&nbsp;<Span ID=spnWorkingMsg></Span>&nbsp;&nbsp;&nbsp;&nbsp;<BR><BR></B></Font></TD>
<TD BgColor=#000040 Width=3></TD>
</TR> <TR>
<TD BgColor=#000040 Height=3 ColSpan=3></TD>
</TR>
</Table>
<Script Language="JavaScript">
//----------------------------------------------------------------
// Show the message in the status bar and in the middle of the screen (DHTML only)
function ShowTransientMessage(sMessage) {
window.status=sMessage;
spnWorkingMsg.innerText=sMessage;
tblWorkingMsg.style.display='';
tblWorkingMsg.style.pixelTop=
(document.body.clientHeight/2)-(tblWorkingMsg.offsetHeight/2)+(document.body.scrollTop);
tblWorkingMsg.style.pixelLeft=
(document.body.clientWidth/2)-(tblWorkingMsg.offsetWidth/2)+(document.body.scrollLeft);
}
//----------------------------------------------------------------
// hide the message box
function HideTransientMessage() {
window.status="";
tblWorkingMsg.style.display='none';
}
</Script>
<Script Language="VBScript">
Option Explicit
'-----------------------------------------------------------------
' Strings to be localized
Const L_NoCSPs_ErrorMessage="(No CSPs found!)"
Const EnhancedCSPString="Microsoft Enhanced Cryptographic Provider"
Const BaseCSPString="Microsoft Base Cryptographic Provider"
Function GetCSPList()
On Error Resume Next
Dim nProvType, nDefaultCSP, nBaseCSP, nCSPIndex
nDefaultCSP=-1
nBaseCSP=-1
Dim CspInformations
Dim CspInformation
Set CspInformations = g_objCSPInformations
' if there are no CSPs, we're kinda stuck
If 0=CspInformations.Count Then
Set oElement=document.createElement("Option")
oElement.text=L_NoCSPs_ErrorMessage
document.UIForm.lbCSP.Options.Add oElement
Else
For nCSPIndex=CERTENROLL_INDEX_BASE To CspInformations.Count-1+CERTENROLL_INDEX_BASE
Dim sProviderName
Dim oOption
Set CspInformation = CspInformations.ItemByIndex(nCSPIndex)
If True = CspInformation.LegacyCsp Then
sProviderName=CspInformation.Name
nProvType=CspInformation.Type
Set oOption=document.createElement("Option")
oOption.text=sProviderName
oOption.Value=nProvType
document.UIForm.lbCSP.add(oOption)
If InStr(sProviderName, EnhancedCSPString) <> 0 Then
oOption.selected=True
nDefaultCSP=nCSPIndex
End If
If InStr(sProviderName, BaseCSPString) <> 0 Then
'just remember the base csp index
nBaseCSP=nCSPIndex
End If
End If
Next
End If ' if 0 == CspInformations.Count
' remove the 'loading' text
document.UIForm.lbCSP.remove(0)
' select the default provider
If -1 = nDefaultCSP Then
'no enhanced csp, how about base
If -1 <> nBaseCSP Then
'ok, take base csp
nDefaultCSP=nBaseCSP
End If
End If
If -1<>nDefaultCSP Then
document.UIForm.lbCSP.selectedIndex=nDefaultCSP
End If
If -1 = nDefaultCSP Then
If 0 < nCSPIndex Then
'well, best bet is the 1st available one
document.UIForm.lbCSP.selectedIndex=0
End If
End If
' set the return value and exit
If 0<>Err.Number Then
GetCSPList=Err.Number
ElseIf 0=nCSPIndex Then
' signal no elements with -1
GetCSPList=-1
Else
GetCSPList=0
End If
End Function
Function AddCSPToList(sCSP)
On Error Resume Next
Dim oOption
Dim nProviderType
If True = XE_LegacyCsp(sCSP) Then
nProviderType = XE_GetProviderType(sCSP)
If 0=Err.Number Then
'csp available on the machine
Set oOption=document.createElement("Option")
oOption.text=sCSP
oOption.Value=nProviderType
document.UIForm.lbCSP.add(oOption)
If InStr(sCSP, EnhancedCSPString) <> 0 Then
oOption.selected=True
End If
End If
End If
If 0<>Err.Number Then
AddCSPToList=Err.Number
End If
End Function
</Script>
<Script Language="JavaScript">
var CTINFO_INDEX_OFFLINE =0;
var CTINFO_INDEX_REALNAME =1;
var CTINFO_INDEX_KEYSPEC =2;
var CTINFO_INDEX_KEYFLAG =3;
var CTINFO_INDEX_ENROLLFLAG =4;
var CTINFO_INDEX_PRIVATEKEYFLAG =5;
var CTINFO_INDEX_SUBJECTFLAG =6;
var CTINFO_INDEX_RASIGNATURE =7;
var CTINFO_INDEX_CSPLIST =8;
var CTINFO_INDEX_EXTOID =9;
var CTINFO_INDEX_EXTMAJ =10;
var CTINFO_INDEX_EXTFMIN =11;
var CTINFO_INDEX_EXTMIN =12;
var CTINFO_INDEX_FRIENDLYNAME=13;
function getTemplateStringInfo(nIndex, sInTemplate)
{
//extract sub string from template string in a format
//of "substr1;substr2;substr3;substr4;..."
//";" is the separator, index starts from 0
var nTemplateIndex, sTemplate;
if (null == sInTemplate)
{
nTemplateIndex=document.UIForm.lbCertTemplate.selectedIndex;
sTemplate=document.UIForm.lbCertTemplate.options[nTemplateIndex].value;
}
else
{
sTemplate = sInTemplate;
}
var sTemp = sTemplate;
var n, m, nEnd;
var fFound = true;
//find sub-string start location
for (n = 0; n < nIndex; ++n)
{
m = sTemp.indexOf(";");
if (-1 == m)
{
fFound = false;
break;
}
sTemp = sTemp.substr(m+1);
}
if (fFound)
{
//sTemp starts from the substring, find end index
nEnd = sTemp.indexOf(";");
if (-1 != nEnd)
{
sTemp = sTemp.substring(0, nEnd);
}
}
else
{
sTemp = "";
}
return sTemp;
}
function updateCSPListFromStrings(sCSPList)
{
var n, m, nCSP;
var L_NotTrustedSite_ErrorMessage="In order to complete certificate enrollment, the Web site for the CA must be configured to use HTTPS authentication.";
var L_NoDesiredCSPInstalledMsg = "You need to install the following CSPs before the enrollment, ";
var L_AndMsg = "and ";
var sSupportedCSPs = "";
var nResult = 0;
//remove the current csp list
var nCSP = document.UIForm.lbCSP.length;
//note, strange reasons, can't nCSP-1
for (n = 0; n < nCSP; ++n)
{
document.UIForm.lbCSP.remove(0);
}
//add to the list
while (-1 != (m = sCSPList.indexOf("?")))
{
//get csp from the list
sCSP = sCSPList.substring(0, m);
nResult = AddCSPToList(sCSP);
if ("" == sSupportedCSPs)
{
sSupportedCSPs = sCSP;
}
else
{
sSupportedCSPs = sSupportedCSPs + ", ";
}
//move to the next csp
sCSPList = sCSPList.substring(m+1);
}
if ("" != sCSPList)
{
//add the last csp
nResult = AddCSPToList(sCSPList);
if ("" == sSupportedCSPs)
{
sSupportedCSPs = sCSPList + ".";
}
else
{
sSupportedCSPs = sSupportedCSPs + ", " + L_AndMsg + sCSPList + ".";
}
}
if (0 == document.UIForm.lbCSP.length)
{
if(0!=g_certEnrollLoadError)
{
alert(L_NotTrustedSite_ErrorMessage);
}
else
{
alert(L_NoDesiredCSPInstalledMsg + sSupportedCSPs);
}
//no csp, disable submit button
document.UIForm.btnSubmit.disabled = true;
disableAllControls();
}
}
</Script>
<Script Language="JavaScript">
//helper to decide downlevel browsers
function isClientAbleToCreateCMC()
{
var sUserAgent = navigator.userAgent;
var index;
//check if W2K or newer
index = sUserAgent.indexOf("Windows NT");
if (-1 != index)
{
if (4 < parseInt(sUserAgent.substring(index+11, index+12)))
{
//either w2k or newer
return true;
}
}
if (-1 != sUserAgent.indexOf("Windows 98; Win 9x"))
{
//win ME
return true;
}
return false;
}
</Script>
<!-- IE SPECIFIC: This form we fill in and submit 'by hand'. NN does it differently. -->
<Form Name=SubmittedData Action="certfnsh.asp" Method=Post>
<Input Type=Hidden Name=Mode> <!-- used in request ('newreq'|'chkpnd') -->
<Input Type=Hidden Name=CertRequest> <!-- used in request -->
<Input Type=Hidden Name=CertAttrib> <!-- used in request -->
<Input Type=Hidden Name=FriendlyType> <!-- used on pending -->
<!--
NOTE:
ENTERPRISE PENDING/AUTOENRL SUPPORT IS DISABLED BY DEFAULT.
UN-COMMENT THE BELOW LINE TO RE-ENABLE
<!-- <Input Type=Hidden Name=ThumbPrint> <!-- used on pending -->
<Input Type=Hidden Name=TargetStoreFlags> <!-- used on install ('0'|CSSLM)-->
<Input Type=Hidden Name=SaveCert> <!-- used on install ('no'|'yes')-->
</FORM>
<Script Language="JavaScript">
//================================================================
// PAGE GLOBAL VARIABLES
//----------------------------------------------------------------
// Strings to be localized
var L_StillLoading_ErrorMessage="This page has not finished loading yet. Please wait a few seconds and try again.";
var L_Generating_Message="Generating request...";
var L_NotTrustedSite_ErrorMessage="In order to complete certificate enrollment, the Web site for the CA must be configured to use HTTPS authentication.";
;
var L_CspLoadErrNoneFound_ErrorMessage="An unexpected error occurred while getting the CSP list:\nNo CSPs could be found!";
var L_CspLoadErrUnexpected_ErrorMessage="\"An unexpected error (\"+sErrorNumber+\") occurred while getting the CSP list.\"";
var L_Waiting_Message="Waiting for server response...";
var L_ErrNameUnknown_ErrorMessage="(unknown)";
var L_SugCauseNone_ErrorMessage="No suggestion.";
var L_SugCauseBadCSP_ErrorMessage="The CSP you chose was unable to process the request. Try a different CSP.";
var L_SugCauseKeysetFull_ErrorMessage="The security token does not have storage space available for an additional container.";
var L_SugCauseBadSetting_ErrorMessage="The CSP you chose does not support one or more of the settings you have made. Try using different settings or a different CSP.";
var L_SugCauseBadChar_ErrorMessage="You entered an invalid character. Report a bug, because this should have been caught in validation.";
var L_SugCauseNoProfile_ErrorMessage="The profile for the user is a temporary profile.";
var L_SugCauseCancelled_ErrorMessage="The operation was canceled by the user.";
// IE is not ready until XEnroll has been loaded
var g_bOkToSubmit=false;
var g_bSubmitPending=false;
//================================================================
// INITIALIZATION ROUTINES
function postLoad() {
// No need to load xenroll. Call the postLoad directly:
postLoadPhase2();
handleCMCFormat();
}
function postLoadPhase2() {
// continued from above
var nResult;
var sCSPList ="Microsoft Enhanced Cryptographic Provider v1.0?Microsoft Base Cryptographic Provider v1.0";
var sUserAgent=navigator.userAgent;
if (-1 == sUserAgent.indexOf("Windows NT 5.1"))
{
var sCSPList ="Microsoft Base Cryptographic Provider v1.0?Microsoft Enhanced Cryptographic Provider v1.0";
}
if ("" != sCSPList)
{
// get csp from template
updateCSPListFromStrings(sCSPList);
nResult = 0;
}
else
{
// get the CSP list from local xenroll
nResult=GetCSPList();
}
if (0!=nResult) {
if(0!=g_certEnrollLoadError)
{
alert(L_NotTrustedSite_ErrorMessage);
} else {
handleLoadError(nResult, L_CspLoadErrNoneFound_ErrorMessage, L_CspLoadErrUnexpected_ErrorMessage);
}
//disable submit button
document.UIForm.btnSubmit.disabled = true;
disableAllControls();
return;
}
// Now we're ready to go
g_bOkToSubmit=true;
}
//----------------------------------------------------------------
// IE SPECIFIC: handle errors from GetCSPList() and GetTemplateList()
function handleLoadError(nResult, sNoneFound, sUnexpected) {
if (-1==nResult) {
alert(sNoneFound);
} else {
var sErrorNumber="0x"+toHex(nResult);
alert(eval(sUnexpected));
}
}
//================================================================
// PAGE MANAGEMENT ROUTINES
//----------------------------------------------------------------
// IE SPECIFIC: morph method for the error details drop-down
function showErrorDetails() {
spnErrorDetailsBtn.style.display='none';
spnErrorDetails1.style.display='';
spnErrorDetails2.style.display='';
}
//----------------------------------------------------------------
// IE SPECIFIC: morph method for the "more options" drop down
function showMoreOptions() {
var nIndex;
for (nIndex=0; nIndex<trMoreOptHide.length; nIndex++) { //>
trMoreOptHide[nIndex].style.display='none';
}
for (nIndex=0; nIndex<trMoreOptShow.length; nIndex++) { //>
trMoreOptShow[nIndex].style.display='';
}
}
//----------------------------------------------------------------
// handle CMC Format
function handleCMCFormat() {
if (!isClientAbleToCreateCMC())
{
//no cmc, disable it, only pkcs10
document.UIForm.rbRequestFormat[0].disabled=true;
document.UIForm.rbRequestFormat[1].disabled=true;
document.UIForm.rbRequestFormat[1].checked=true;
}
}
//================================================================
// SUBMIT ROUTINES
//----------------------------------------------------------------
// determine what to do when the submit button is pressed
function goNext() {
if (false==g_bOkToSubmit) {
alert(L_StillLoading_ErrorMessage);
return false;
} else if (true==g_bSubmitPending) {
// ignore this, as there is UI already.
return false;
} else {
return SubmitRequest();
}
}
//----------------------------------------------------------------
// IE SPECIFIC:
function SubmitRequest() {
g_bSubmitPending=true;
spnErrorTxt.style.display='none';
spnFixTxt.style.display='none';
// show a nice message since request creation can take a while
ShowTransientMessage(L_Generating_Message);
// Make the message show up on the screen,
// then continue with 'SubmitRequest':
// Pause 1 mS before executing phase 2,
// so screen will have time to repaint.
setTimeout("SubmitRequestPhase2();", 10);
}
function SubmitRequestPhase2() {
// continued from above
// some constants defined in wincrypt.h: (line ~234)
var CRYPT_EXPORTABLE=1;
var CRYPT_USER_PROTECTED=2;
var AT_KEYEXCHANGE=1;
var AT_SIGNATURE=2;
var PROV_DSS=3;
var PROV_DSS_DH=13;
var XECR_PKCS10_V2_0=1;
var XECR_CMC=3;
var bCmcInitialized = false;
var nResult = 0;
// the distinguished name is not used for enterprise CAs
var sDistinguishedName="";
// set defaults for values we need on install
document.SubmittedData.CertAttrib.value="UserAgent:Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)\r\n";
document.SubmittedData.TargetStoreFlags.value=0; // 0=Use default (=user store)
document.SubmittedData.SaveCert.value="no";
document.SubmittedData.Mode.value="newreq";
document.SubmittedData.FriendlyType.value="User Certificate";
// append the local date to the type
document.SubmittedData.FriendlyType.value+=" ("+(new Date()).toLocaleString()+")";
// set the CSP
var nCSPIndex=document.UIForm.lbCSP.selectedIndex;
var nProvType=document.UIForm.lbCSP.options[nCSPIndex].value;
var nKeySpec;
var nGenKeyFlags=0;
// default to exchange keys, unless we're doing DSS which only does sig.
if (PROV_DSS==nProvType || PROV_DSS_DH==nProvType) {
nKeySpec = AT_SIGNATURE;
} else {
nKeySpec = AT_KEYEXCHANGE;
}
// set 'Strong private key protection'
if (document.UIForm.cbStrongKey.checked) {
nGenKeyFlags |= CRYPT_USER_PROTECTED;
}
if ("True"=="True")
{
nGenKeyFlags |= CRYPT_EXPORTABLE;
}
nResult = XE_Request_InitializeCspInformation(g_objRequest, g_objPrivateKey, false, nKeySpec, nGenKeyFlags, document.UIForm.lbCSP.options[nCSPIndex].text, nProvType, "", false);
// deal with an error if there was one
if (0!=nResult) {
// hide the message box
HideTransientMessage();
handleError(nResult);
g_bSubmitPending=false;
return;
}
// set the cert template, we know this is v1 template
var XECT_EXTENSION_V1=1;
XE_Request_addCertTypeToRequestEx(g_objRequest, XECT_EXTENSION_V1, "User", 0, false, 0);
var sCertUsage=""; // ignored
//
// We're done configuring the request object.
// Configure the enrollment object now.
//
if (document.UIForm.rbRequestFormat[1].checked)
{
XE_InitializeEnrollObject(g_objRequest)
}
else
{
if (!bCmcInitialized)
{
XE_InitializeCmcObject();
bCmcInitialized = true;
}
XE_InitializeEnrollObject(g_objRequestCmc)
}
// set request format
lRequestFlag=XECR_CMC;
if (document.UIForm.rbRequestFormat[1].checked) {
lRequestFlag=XECR_PKCS10_V2_0;
}
//--------------------------------------------------------
// NOTE:
//
// ENTERPRISE PENDING/AUTOENRL SUPPORT IS DISABLED BY DEFAULT.
// UN-COMMENT THE BELOW LINES TO RE-ENABLE
//
//--------------------------------------------------------
// if (0 == nResult)
// {
// //always get thumbprint in case of pending
// document.SubmittedData.ThumbPrint.value=g_objEnroll.ThumbPrint;
// }
// build the certificate request
nResult=CreateRequest(lRequestFlag, sDistinguishedName, sCertUsage); // ask VB to do it, since it can handle errors
// hide the message box
HideTransientMessage();
//see if it was cancelled
if (document.UIForm.cbStrongKey.checked && (0==(0x8010006e^nResult)))
{
//ERROR_CANCELLED, likely from dialog, out
g_bSubmitPending=false;
XE_reset();
return;
}
// deal with an error if there was one
if (0!=nResult) {
handleError(nResult);
g_bSubmitPending=false;
return;
}
// put up a new wait message
ShowTransientMessage(L_Waiting_Message);
// Submit the cert request and move forward in the wizard
document.SubmittedData.submit();
}
//----------------------------------------------------------------
// IE SPECIFIC:
function handleError(nResult) {
var sSugCause=L_SugCauseNone_ErrorMessage;
var sErrorName=L_ErrNameUnknown_ErrorMessage;
// analyze the error - funny use of XOR ('^') because obvious choice '==' doesn't work
if (0==(0x80090008^nResult)) {
sErrorName="NTE_BAD_ALGID";
sSugCause=L_SugCauseBadCSP_ErrorMessage;
} else if (0==(0x80090016^nResult)) {
sErrorName="NTE_BAD_KEYSET";
sSugCause=L_SugCauseBadCSP_ErrorMessage;
} else if (0==(0x80090019^nResult)) {
sErrorName="NTE_KEYSET_NOT_DEF";
sSugCause=L_SugCauseBadCSP_ErrorMessage;
} else if (0==(0x80090020^nResult)) {
sErrorName="NTE_FAIL";
sSugCause=L_SugCauseBadCSP_ErrorMessage;
} else if (0==(0x80090023^nResult)) {
sErrorName="NTE_TOKEN_KEYSET_STORAGE_FULL";
sSugCause=L_SugCauseKeysetFull_ErrorMessage;
} else if (0==(0x80090009^nResult)) {
sErrorName="NTE_BAD_FLAGS";
sSugCause=L_SugCauseBadSetting_ErrorMessage;
} else if (0==(0x80092002^nResult)) {
sErrorName="CRYPT_E_BAD_ENCODE";
//sSugCause="";
} else if (0==(0x80092022^nResult)) {
sErrorName="CRYPT_E_INVALID_IA5_STRING";
sSugCause=L_SugCauseBadChar_ErrorMessage;
} else if (0==(0x80092023^nResult)) {
sErrorName="CRYPT_E_INVALID_X500_STRING";
sSugCause=L_SugCauseBadChar_ErrorMessage;
} else if (0==(0x80090024^nResult)) {
sErrorName = "NTE_TEMPORARY_PROFILE";
sSugCause = L_SugCauseNoProfile_ErrorMessage;
} else if (0==(0x800704C7^nResult)) {
sErrorName = "ERROR_CANCELLED";
sSugCause = L_SugCauseCancelled_ErrorMessage;
} else if (0==(0x8000FFFF^nResult)) {
sErrorName="E_UNEXPECTED";
}
var sErrorNum="0x"+toHex(nResult)+" - "+sErrorName;
// modify the document text and appearance to show the error message
spnErrorNum.innerText=sErrorNum;
spnErrorMsg.innerText=sSugCause;
spnFixTxt.style.display='none';
spnErrorTxt.style.display='';
// back to the top so the messages show
window.scrollTo(0,0);
// reset XEnroll so the user can select a different CSP, etc.
XE_reset();
}
</Script>
<Script Language="VBSCRIPT">
'-----------------------------------------------------------------
' IE SPECIFIC:
' call XEnroll to create a request, since javascript has no error handling
Function CreateRequest(lFlags, sDistinguishedName, sCertUsage)
On Error Resume Next
Call XE_ReuseHardwareKeyIfUnableToGenNew(False)
document.SubmittedData.CertRequest.value= _
XE_Enroll_CreateRequest(g_objEnroll, lFlags, sDistinguishedName, sCertUsage)
CreateRequest=Err.Number
End Function
</Script>
</Body>
</HTML>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment