# encoding: ascii
# api: powershell
# title: Test-Certificate
# description: Tests specified certificate for certificate chain and revocation
# version: 0.9
# type: function
# author: vpodans
# license: CC0
# function: Test-Certificate
# x-poshcode-id: 1634
# x-derived-from-id: 1644
# x-archived: 2010-10-15T20:35:22
#
#
#####################################################################
# Test-Certificate.ps1
# Version 0.9
#
# Tests specified certificate for certificate chain and revocation
#
# Vadims Podans (c) 2009
# http://www.sysadmins.lv/
#####################################################################
#requires -Version 2.0

function Test-Certificate {
<#
.Synopsis
	Tests specified certificate for certificate chain and revocation
.Description
	Tests specified certificate for certificate chain and revocation status for each certificate in chain
	exluding Root certificates
.Parameter Certificate
	Specifies the certificate to test certificate chain. This parameter may accept
	X509Certificate, X509Certificate2 objects or physical file path. this paramter accept
	pipeline input
.Parameter Password
	Specifies PFX file password. Password must be passed as SecureString.
.Parameter CRLMode
	Sets revocation check mode. May contain on of the following values:
	
	Online - perform revocation check downloading CRL from CDP extension ignoring cached CRLs. Default value
	Offline - perform revocation check using cached CRLs if they are already downloaded
	NoCheck - specified certificate will not checked for revocation status (not recommended)
.Parameter CRLFlag
	Sets revocation flags for chain elements. May contain one of the following values:
	
	ExcludeRoot - perform revocation check for each certificate in chain exluding root. Default value
	EntireChain - perform revocation check for each certificate in chain including root. (not recommended)
	EndCertificateOnly - perform revocation check for specified certificate only.
.Parameter VerificationFlags
	Sets verification checks that will bypassed performed during certificate chaining engine
	check. You may specify one of the following values:
	
	NoFlag - No flags pertaining to verification are included (default).
	IgnoreNotTimeValid - Ignore certificates in the chain that are not valid either because they have expired or they
		are not yet	in effect when determining certificate validity.
	IgnoreCtlNotTimeValid - Ignore that the certificate trust list (CTL) is not valid, for reasons such as the CTL
		has expired, when determining certificate verification.
	IgnoreNotTimeNested - Ignore that the CA (certificate authority) certificate and the issued certificate have
		validity periods that are not nested when verifying the certificate. For example, the CA cert can be valid
		from January 1 to December 1 and the issued certificate from January 2 to December 2, which would mean the
		validity periods are not nested.
	IgnoreInvalidBasicConstraints - Ignore that the basic constraints are not valid when determining certificate
		verification.
	AllowUnknownCertificateAuthority - Ignore that the chain cannot be verified due to an unknown certificate
		authority (CA).
	IgnoreWrongUsage - Ignore that the certificate was not issued for the current use when determining
		certificate verification.
	IgnoreInvalidName - Ignore that the certificate has an invalid name when determining certificate verification.
	IgnoreInvalidPolicy - Ignore that the certificate has invalid policy when determining certificate verification.
	IgnoreEndRevocationUnknown - Ignore that the end certificate (the user certificate) revocation is unknown when
		determining	certificate verification.
	IgnoreCtlSignerRevocationUnknown - Ignore that the certificate trust list (CTL) signer revocation is unknown
		when determining certificate verification.
	IgnoreCertificateAuthorityRevocationUnknown - Ignore that the certificate authority revocation is unknown 
		when determining certificate verification.
	IgnoreRootRevocationUnknown - Ignore that the root revocation is unknown when determining certificate verification.
	AllFlags - All flags pertaining to verification are included.	
.Example
	Get-ChilItem cert:\CurrentUser\My | Test-Certificate -CRLMode "NoCheck"
	
	Will check certificate chain for each certificate in current user Personal container.
	Certificates will not checked for revocation status.
.Example
	Test-Certificate C:\Certs\certificate.cer -CRLFlag "EndCertificateOnly"
	
	Will check certificate chain for certificate that is located in C:\Certs and named
	as Certificate.cer and revocation checking will be performed for specified certificate only
.Outputs
	This script return general info about certificate chain status
#>
[CmdletBinding()]
	param(
		[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
		$Certificate,
		[System.Security.SecureString]$Password,
		[System.Security.Cryptography.X509Certificates.X509RevocationMode]$CRLMode = "Online",
		[System.Security.Cryptography.X509Certificates.X509RevocationFlag]$CRLFlag = "ExcludeRoot",
		[System.Security.Cryptography.X509Certificates.X509VerificationFlags]$VerificationFlags = "NoFlag"
	)
	
	begin {
		$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
		$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
		$chain.ChainPolicy.RevocationFlag = $CRLFlag
		$chain.ChainPolicy.RevocationMode = $CRLMode
		$chain.ChainPolicy.VerificationFlags = $VerificationFlags
		function _getstatus_ ($status, $chain, $cert) {
			if ($status) {
				Write-Host Current certificate $cert.SerialNumber chain and revocation status is valid -ForegroundColor Green
			} else {
				Write-Warning "Current certificate $($cert.SerialNumber) chain is invalid due of the following errors:"
				$chain.ChainStatus | %{Write-Host $_.StatusInformation.trim() -ForegroundColor Red}
			}
		}
	}
	
	process {
		if ($_ -is [System.Security.Cryptography.X509Certificates.X509Certificate2]) {
			$status = $chain.Build($_)
			_getstatus_ $status $chain $_
		} else {
			if (!(Test-Path $Certificate)) {Write-Warning "Specified path is invalid"; return}
			else {
				if ((Resolve-Path $Certificate).Provider.Name -ne "FileSystem") {
					Write-Warning "Spicifed path is not recognized as filesystem path. Try again"; return
				} else {
					$Certificate = gi $(Resolve-Path $Certificate)
					switch -regex ($Certificate.Extension) {
					"\.CER|\.DER|\.CRT" {$cert.Import($Certificate.FullName)}
					"\.PFX" {
						if (!$Password) {$Password = Read-Host "Enter password for PFX file $certificate" -AsSecureString}
							$cert.Import($Certificate.FullName, $password, "UserKeySet")
						}
					"\.P7B|\.SST" {
						$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
						$cert.Import([System.IO.File]::ReadAllBytes($Certificate.FullName))
						}
					default {Write-Warning "Looks like your specified file is not a certificate file"; return}
					}
					$cert | %{
						$status = $chain.Build($_)
						_getstatus_ $status $chain $_
					}
					$cert.Reset()
					$chain.Reset()
				}
			}
		}
	}
}
