Forum Discussion

Jonesey's avatar
Jonesey
Icon for Neophyte rankNeophyte
7 months ago

CRL Monitoring...is it possible?

Hi All

I am new to LM and have a question re monitoring CRL expiration dates. We currently monitor for expiring SSL certs (both internal and external) and need to know if it is possible to monitor CRLs too.

We manage multiple customers, and an outage was caused recently due to an expired CRL. I would like to avoid these in the future :)

Any help is appreciated.

Regards

  • Anonymous's avatar
    Anonymous

    I don't know anything out of the box that does it, but certificates has always been a dark area in my knowledge. Is it possible, likely yes. You'd have to answer these two questions: 1) is monitoring it important enough for me to write a logicmodule (which may involve no-code options) and 2) how would I monitor this today if it were my day job? If all I had to do was monitor this, would i interpret the output of a powershell command? Would I go to a website and manually parse through some data? Understanding how the data is obtained manually is the first step towards writing a module that can do that for you.

  • ColP's avatar
    ColP
    Icon for LM Champion rankLM Champion

    This can be done, i used the following as an uploaded PS script to run and then used a guage to alert on the output. There should be a way of not doing it as a uploaded script but see the code below as your starting point.

    Hope this helps.

    
    Function Get-CRLFreshness {
      <#
      .SYNOPSIS
      Checks the Validity/Freshness of a CRL from the provided CRL Distribution Point (CDP) provided.
      .DESCRIPTION
      Downloads and examines the CRL from the provided CDP. Sends e-mail alert if CRL is expired or expiring (past next publish or less than an hour value provided)
      .EXAMPLE
      Get-CRLFreshness -CDP http://crl.domain.com/IssuingCA.crl
      Performs a CRL Freshness check against the CDP URI provided. 
      .EXAMPLE
      Get-CRLFreshness -CDP http://crl.domain.com/IssuingCA.crl -ServerIP 123.123.123.123
      Performs a CRL Freshness check against the CDP URI provided with an override server IP. Direct IP connection, will not use DNS for server lookup. Useful to directly check a web server behind a load balancer.
      .EXAMPLE
      Get-CRLFreshness -CDP http://crl.domain.com/IssuingCA.crl -WarningHours 1
      Performs a CRL Freshness check against the CDP URI provided and an override warning period. Overrides the NextCRLPublish extension if present. 
      .EXAMPLE
      Get-CRLFreshness -CDP http://crl.domain.com/IssuingCA.crl -ServerIP 123.123.123.123 -WarningHours 5
      Performs a CRL Freshness check against the CDP URI provided with an override server IP and override warning period.
      .EXAMPLE
      Get-CRLFreshness -Batch -InputFile C:\Admin\CRLChecks.csv
      Loads an input CSV file and performs a batch CRL freshness check. All options above available in batch mode. Warning, No input error checking performed.
      .EXAMPLE
       ---- Batch CSV Inputfile Example ------
      CASubjectCN,CDP,ServerIP,WarningHours
      Baltimore CyberTrust Root,http://cdp1.public-trust.com/CRL/Omniroot2025.crl,117.18.237.191,120
      
      .PARAMETER ServerIP
      The IP of the Server to be checked. Allows the bypass of load balanced virtual IP's to directly query the Web Server
      .PARAMETER WarningHours
      The number of hours to give a warning if "Next CRL Publish" is not available or you wish to override it.
      .PARAMETER LogFile
      Name of the log file to log to. Defaults to .\CRLFreshness.log.
      .PARAMETER TempFile
      Name of the temporary CRL file that is downloaded.
      .PARAMETER MailFrom
      Sender Email Address for Mail Notifications
      .PARAMETER MailTo
      Receipients Email Address for Mail Notificaitons
      .PARAMETER MailServer
      Mail Server to submit mail to (assumes no relay required)
    
      #>
      # Configure the Commandlet
      [CmdletBinding()]
        Param (
            [Parameter(Mandatory=$true,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True,Position=0,ParameterSetName = "Single")][ValidateNotNullOrEmpty()][String]$CDP,
            [Parameter(Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True,Position=1,ParameterSetName = "Single")][ValidatePattern("\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b")][string]$ServerIP = $Null,        
            [parameter(Mandatory=$false,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True,Position=2,ParameterSetName = "Single")][Int]$WarningHours = 0,
            [parameter(Mandatory=$true,ParameterSetName = "Batch")][Switch]$Batch,
            [parameter(Mandatory=$true,ParameterSetName = "Batch")][String]$InputFile,
            [string]$Logfile = ".\CRLFreshCheck.Log",
            [string]$TempFile = ".\TempCRL.crl",
            [string]$MailFrom = "CRL Freshness Check Script <crlfreshcheck@yourdomain.com>",
    		[string]$MailTo = "Receiver Address <receipient@youromdain.com>",
    		[string]$MailServer = "your-smtp.server.com")
    
    # Begin the Commandlet
    Begin {
      	$ReturnObject = @()
    	$LogTime = Get-Date -Format "yyyy.MM.dd hh:mm"
    	$Now = (Get-Date).ToLocalTime()
    	#$Now = Get-Date "5/4/2016 9:00:00 PM"		# Uncomment to manually set the date and time to confirm email and reporting behaviour 
    
        Add-Content $LogFile "$LogTime`tInfo:   `t----- Begin Processing -----"
        If ($Batch) {
                $CDPPaths = Import-CSV $InputFile
                $Count = $CDPPaths.Count
    			Add-Content $LogFile "$LogTime`tInfo:   `tInput Mode: Batch $Count Paths"}
    	Else {	Add-Content $LogFile "$LogTime`tInfo:   `tInput Mode: Single"}
        }
    
    # Main Program
      Process {
    	   
        # If we are in Batch mode, loop through each CDP entry otherwise just check the passed CRL.
        If ($Batch) {
                $Counter=-1
                ForEach ($Entry in $CDPPaths) {
                        $Counter++
                        Add-Content $LogFile "$LogTime`tInfo:   `t--- CDP $Counter ---"
    					$CDP = $Entry.CDP
    					Write-Progress -activity "Processing CDP Paths" -status "Processing $CDP`: " -percentComplete (($Counter / $Count)  * 100)
    					$ReturnObject += Get-CRLFile $Entry.CDP $Entry.ServerIP $Entry.WarningHours $Counter}
    	} #End If
        Else {$ReturnObject = Get-CRLFile $CDP $ServerIP $WarningHours 0}
      	
    	} # End Process
    
     # Tidy Up
     End {
    	Add-Content $LogFile "$LogTime`tInfo:   `t--- Finished Processing ---"
        Return $ReturnObject
    	} # End End
    } # End of Get-CRLFreshness Function
    
    # ---- The Primary Get-CRLFile Function ----
    
    Function Get-CRLFile ($CDP,$ServerIP,$WarningHours,$Counter){
    
        # Start with some logging    
        Add-Content $LogFile "$LogTime`tInfo:   `t[$Counter] Begin CRL Check of $CDP"
        
        # Build our Custom Status Object to Return
    	$Result = "" | Select-Object "CDP","Status","Description","HoursTilExpiry","Issuer","AKI","ServerIP","DownloadOK","ValidFrom","ValidTo","NextCRLPublish","CurrentDate","BaseCRL","HashAlgorithm","CRLNumber"	
    	$Result.CDP = $CDP
    	 
        # Grab the CDP Host Header from the CDP Variable
    	$HostHeader = ([System.Uri]$CDP).Host
        
    	# If we recieved a ServerIP Address, Extract the Host header and update the CDP Path. This will override DNS lookup so we can query load balanced web servers. Log how we are performing the server lookup
    	If($ServerIP){
                $CDP = $CDP -Replace ($HostHeader,$ServerIP)
                $Result.ServerIP = $ServerIP
                Add-Content $LogFile "$LogTime`tInfo:   `t[$Counter] CRL Download Direct from IP $ServerIP"}
        Else {
               $Result.ServerIP = "Via DNS Lookup"
               Add-Content $LogFile "$LogTime`tInfo:   `t[$Counter] CRL Download via DNS Lookup"
        	   }
       	
        # Attempt to download the file from the server
    	Try { Invoke-WebRequest $CDP -Headers @{Host = $HostHeader} -OutFile $TempFile}	
    	Catch { 
    		Add-Content $LogFile "$LogTime`tError:    `t[$Counter] CRL Download Failed from $CDP. Unable to check freshness"
    		$Result.Status = "NoDownload"
    		$Result.Description = "NoDownload - Failed to download CRL"
    		$Result.DownloadOK = $False
    		}
    	If ($Result.Status -ne "NoDownload"){
    
    		Add-Content $LogFile "$LogTime`tInfo:   `t[$Counter] CRL Downloaded Complete"
    		$Result.DownloadOK = $True
    
    		# Open the CRL as A Byte file and then convert to Base64
    		$CRLContents = [System.Convert]::ToBase64String((Get-Content $TempFile -Encoding Byte))
    			
    		# Create a X509 CRL Object and Intiliaze all of the CRL data into It
    		$CRL = New-Object -ComObject "X509Enrollment.CX509CertificateRevocationList"
    		$CRL.InitializeDecode($CRLContents,1) 									# 1 = XCN_CRYPT_STRING_BASE64
    			
    		# Grab the current ValidFrom/ValidTo Extensions
    		$ThisUpdate = ($CRL.ThisUpdate).ToLocalTime()
    		$NextUpdate = ($CRL.NextUpdate).ToLocalTime()
    
    		# Attempt to grab the Next CRL Publish Date Extension
    		Try {
    			$NextPublishExtension = ($CRL.X509Extensions | Where-Object {$_.ObjectID.Value -eq '1.3.6.1.4.1.311.21.4'})
    			$NextPublishData = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($NextPublishExtension.RawData("1")))
    			$NextPublishData  = $NextPublishData.Remove(0,2) 					# Strip the first 2 bytes (type and length)
    			$NextCRLPublish = [DateTime]::ParseExact($NextPublishData,"yyMMddHHmmss\Z",$null)
    			$NextCRLPublish = $NextCRLPublish.ToLocalTime()
    			}
    		Catch { $NextCRLPublish = "Not Available"}
    			
    		# Attempt to grab the CRL Number Extension
    		Try {
    			$CRLNumberExtension = ($CRL.X509Extensions | Where-Object {$_.ObjectID.Value -eq '2.5.29.20'})
    			$CRLNumberExtensionData = $CRLNumberExtension.RawData("4")
    			$CRLNumberExtensionData = $CRLNumberExtensionData -Replace(" ","")
    			$CRLNumber = $CRLNumberExtensionData.Remove(0,4) 					# Strip the first 2 bytes (type and length)
    			}
    		Catch { $CRLNumber = "Not Available"}
    
    		# Attempt to grab the AKI Extension
    		Try {
    			$AKIExtension = ($CRL.X509Extensions | Where-Object {$_.ObjectID.Value -eq '2.5.29.35'})
    			$AKIExtensionData = $AKIExtension.RawData("4")
    			$AKIExtensionData = $AKIExtensionData -Replace(" ","")
    			$AKIExtensionData = $AKIExtensionData -Replace("`n","")
    			$AKI = $AKIExtensionData.Remove(0,8) 					# Strip the first 4 bytes
    			}
    		Catch {$AKI = "Not Available"}
    
    		# Update our Log
    		Add-Content $LogFile "$LogTime`tInfo:   `t[$Counter] Beginning Freshness Checks"
    		If ($WarningHours -ne 0) {Add-Content $LogFile "$LogTime`tInfo:   `t[$Counter] Warning Hours Override = $WarningHours"}
    
    		# Compare against Next CRL Publish if it's present or generic 3/2/1 days unless WarningHours Override specified.
    		$HoursTilExpiry = [Decimal]::Round((New-TimeSpan $Now $NextUpdate).TotalHours)
    			
    		# First check if We have simply expired
    		If ($Now -gt $NextUpdate) {
    			$Status = "Expired"
    			$Description = "Expired - CRL Expired by $HoursTilExpiry hours"
    			}
    		ElseIf ($WarningHours -eq 0) {				# If there is No Override, use NextCRLPublish
    			If ($NextCRLPublish -ne "Not Available") {			# If there is No Override and we have a NextCRL value, compare it with a 30 minute skew
    				If($Now -gt $NextCRLPublish.AddMinutes(30)) {$Status = "Expiring";$Description = "Expiring - Expires in $HoursTilExpiry hours"}
    				Else {$Status = "Fresh";$Description = "Fresh - Expires in $HoursTilExpiry hours"}
    			}
    			Else{							# If there is No Override and we don't have a NextCRL, compare it against 4,3,2 and 1 Day.
    				Add-Content $LogFile "$LogTime`tWarning:   `t[$Counter] NextCRLPublish and WarningHours both missing. Performing 4,3,2 and 1 day comparison"
    				If($Now -gt $NextUpdate.AddHours(-24)) {$Status = "Expiring";$Description = "Expiring - Less than 1 day validity"}
    					ElseIf($Now -gt $NextUpdate.AddHours(-48)) {$Status = "Expiring";$Description = "Expiring - Less than 2 days validity"}
    					ElseIf($Now -gt $NextUpdate.AddHours(-72)) {$Status = "Expiring";$Description = "Expiring - Less than 3 days validity"}
    					ElseIf($Now -gt $NextUpdate.AddHours(-96)) {$Status = "Expiring";$Description = "Expiring - Less than 4 days validity"}
    					Else {$Status = "Fresh";$Description = "Fresh - Expires in $HoursTilExpiry hours"
    				}
    			}	
    		}
    		Else{								# If we have a Override Warning hours, use it
    			If($HoursTilExpiry -lt $WarningHours){$Status = "Expiring";$Description = "Expiring - Expires in $HoursTilExpiry hours (override value is $WarningHours hours)"}
    			Else {$Status = "Fresh";$Description = "Fresh - Expires in $HoursTilExpiry hours (Overridden to alert at less than $WarningHours hours remaining)"}
    		}	
    		
    		# Populate the Return Object with more details
    		$Result.Status = $Status
    		$Result.Description = $Description
    		$Result.HoursTilExpiry = $HoursTilExpiry
    		$Result.ValidFrom = $ThisUpdate
    		$Result.ValidTo = $NextUpdate
    		$Result.NextCRLPublish = $NextCRLPublish
    		$Result.CurrentDate = $Now
    		$Result.Issuer = (($CRL.Issuer.Name).Split(",")[0]).Remove(0,3)
    		$Result.BaseCRL = $CRL.BaseCRL
    		$Result.HashAlgorithm = ($CRL.HashAlgorithm.FriendlyName).ToUpper()
    		$Result.CRLNumber = $CRLNumber
    		$Result.AKI = $AKI
    			
    		# Update our Log
    		If ($Status -ne "Fresh") {
    			Write-Host "Attempting to send alert e-mail to $MailTo"
    			$MailSubject = "$Status`: CRL Freshness Check Failed for the CRL at $CDP"
    			$MailBody = ($Result | Format-List | out-string)
    			Try{Send-mailmessage -from $MailFrom -to $MailTo -subject $MailSubject -body $MailBody -priority High -dno never -smtpServer $MailServer -ErrorAction Stop}
    			Catch{Add-Content $LogFile "$LogTime`tError:    `t[$Counter]$Error Failed to to send alert e-mail to $MailTo"}
    			Add-Content $LogFile "$LogTime`tError:    `t[$Counter] $Description"
    		}
    		Else {Add-Content $LogFile "$LogTime`tSuccess:`t[$Counter] $Description"}
    	} # End of CRL Download If
    
    	# Close the log entry off
    	Add-Content $LogFile "$LogTime`tInfo:   `t[$Counter] Finish CRL Check of $CDP"
    
    	# Return the Results
    	Return $Result
    }
    # --- End of Get-CRL Function ---
    
    $currentDate = Get-Date
    $ValidToDate = (Get-CRLFreshness -CDP "http://pki.YOUR_DOMAIN.com/pki/CRL_FILENAME.crl").ValidTo.AddHours(-6)
    $currentDate -gt $ValidToDate

    IIRC the original code came from a MS support engineer