public/Get-BimiRecord.ps1
|
<#> HelpInfoURI 'https://github.com/T13nn3s/Invoke-SpfDkimDmarc/blob/main/public/CmdletHelp/Get-BIMIRecord.md' #> # Load private functions Get-ChildItem -Path $PSScriptRoot\..\private\*.ps1 | ForEach-Object { . $_.FullName } # Load public functions $PublicFolder = Join-Path -Path $PSScriptRoot -ChildPath 'public' if (Test-Path -Path $PublicFolder) { Get-ChildItem -Path $PublicFolder -Filter "*.ps1" -File | ForEach-Object { . $_.FullName } } function Get-BIMIRecord { [CmdletBinding()] param ( [Parameter( Mandatory, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True, HelpMessage = "Specifies the domain for resolving the BIMI-record.")] [string[]]$Name, [Parameter(Mandatory = $false, Helpmessage = "Specify BIMI selector to query.")] [string]$Selector, [Parameter(Mandatory = $false, HelpMessage = "DNS Server to use.")] [string]$Server ) begin { Write-Verbose "Starting $($MyInvocation.MyCommand)" $PSBoundParameters | Out-String | Write-Verbose # Determine OS platform try { Write-Verbose "Determining OS platform" $OsPlatform = (Get-OsPlatform).Platform } catch { Write-Verbose "Failed to determine OS platform, defaulting to Windows" $OsPlatform = "Windows" } # Linux or macOS: Check if dnsutils is installed if ($OsPlatform -eq "Linux" -or $OsPlatform -eq "macOS") { Test-DnsUtilsInstalled } if ($PSBoundParameters.ContainsKey('Server')) { $SplatParameters = @{ 'Server' = $Server 'ErrorAction' = 'SilentlyContinue' } } Else { $SplatParameters = @{ 'ErrorAction' = 'SilentlyContinue' } } $BimiObject = New-Object System.Collections.Generic.List[System.Object] } process { foreach ($domain in $Name) { Write-Verbose "Resolving BIMI record for domain: $domain" if ($PSBoundParameters.ContainsKey('Selector')) { Write-Verbose "Using BIMI selector: $($Selector)" if ($OsPlatform -eq "Windows") { $bimiRecord = Resolve-DnsName -Type TXT -Name "$($Selector)._bimi.$($domain)" @SplatParameters | Select-Object -ExpandProperty strings -ErrorAction SilentlyContinue } elseif ($OsPlatform -eq "macOS" -or $OsPlatform -eq "Linux") { $bimiRecord = $(dig TXT "$($Selector)._bimi.$($domain)" +short | Out-String).Trim() } elseif ($OsPlatform -eq "macOS" -or $OsPlatform -eq "Linux" -and $Server) { $bimiRecord = $(dig TXT "$($Selector)._bimi.$($domain)" +short NS $PSBoundParameters.Server | Out-String).Trim() } } else { Write-Verbose "Using default BIMI selector." if ($OsPlatform -eq "Windows") { $bimiRecord = Resolve-DnsName -Type TXT -Name "default._bimi.$($domain)" @SplatParameters | Select-Object -ExpandProperty strings -ErrorAction SilentlyContinue } elseif ($OsPlatform -eq "macOS" -or $OsPlatform -eq "Linux") { $bimiRecord = $(dig TXT "default._bimi.$($domain)" +short | Out-String).Trim() } elseif ($OsPlatform -eq "macOS" -or $OsPlatform -eq "Linux" -and $Server) { $bimiRecord = $(dig TXT "default._bimi.$($domain)" +short NS $PSBoundParameters.Server | Out-String).Trim() } if ($null -eq $bimiRecord -or $bimiRecord -eq "") { Write-Verbose "No BIMI record found for $domain" $BimiAdvisory += "No BIMI record found for domain." $BimiRecord = "We couldn't find a BIMI record associated with your domain." } } if ($null -ne $bimiRecord -and $bimiRecord -ne "") { Write-Verbose "BIMI record found for $domain" $BimiAdvisory += "BIMI record found." $BimiRecord = $bimiRecord # DMARC policy must be configured to at 'quarantine' or 'reject' for BIMI to function # DMARC quarantine policies MUST NOT have a pct less than 'pct=100'. # See: https://datatracker.ietf.org/doc/html/draft-brand-indicators-for-message-identification-12#name-introduction try { Write-Verbose "Checking DMARC record for domain: $domain" $DmarcPolicy = Get-DMARCRecord -Name $domain @SplatParameters } Catch { Write-Verbose "No DMARC record found for domain: $domain" $null = $DmarcPolicy } if ($null -eq $DmarcPolicy) { Write-Verbose "No DMARC record found for $domain" $BimiAdvisory = "Does not have a DMARC record. To use BIMI, this domain must have a DMARC record with a policy of at least p=quarantine and pct=100." } else { switch -Regex ($DmarcPolicy) { ('p=none') { $BimiAdvisory = "DMARC policy is set to p=none. BIMI requires a DMARC policy of at least p=quarantine to function." } ('p=quarantine') { $BimiAdvisory = "DMARC policy is set to p=quarantine. While BIMI can function with this policy, it is recommended to use p=reject for better protection." } ('p=reject') { $BimiAdvisory = "DMARC policy is set to p=reject, which is the best policy for BIMI to function." } ('pct=(100|\d{1,2})') { $pctValue = [int]$Matches[1] if ($pctValue -lt 100) { $BimiAdvisory += " DMARC policy pct is set to less than 100%, BIMI requires a DMARC policy with pct=100 to function." } } } } # Check whether the BIMI record contains the 'a=' tag # The 'a=' tag Verified Mark Certificate (VMC) is optional. # See: https://datatracker.ietf.org/doc/html/draft-brand-indicators-for-message-identification-12#section-4.3 if ($bimiRecord -match "a=") { Write-Verbose "Validating 'a=' (VMC) tag in BIMI record." $VMC = $bimiRecord.Split("a=")[1].Split(";")[0].Trim() # Check if the 'a=' tag contains a valid HTTPS URL if ($VMC -match "^https://") { Write-Verbose "'a=' (VMC) tag contains a valid HTTPS URL." $BimiAdvisory += " 'a=' (VMC) tag contains a valid HTTPS URL." Write-Verbose "Validate date of VMC certificate" # Download the VMC certificate # Split multiple certificates if present in the certificate chain try { Write-Verbose "Downloading VMC certificate from URL: $VMC" $VmcCertificate = (Invoke-WebRequest -Uri $VMC -UseBasicParsing).Content $certificates = $VmcCertificate -split '(?=-----BEGIN CERTIFICATE-----)' | Where-Object { $_ -match 'BEGIN CERTIFICATE' } } catch { Write-Verbose "Failed to download VMC certificate from URL: $VMC" $BimiAdvisory += " Failed to download VMC certificate from URL: $VMC. Please ensure the URL is correct and accessible." continue } foreach ($pem in $certificates) { Write-Verbose "Processing VMC certificate." $base64 = $pem -replace '-----BEGIN CERTIFICATE-----', '' -replace '-----END CERTIFICATE-----', '' -replace '\\s', '' $certBytes = [System.Convert]::FromBase64String($base64) $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($certBytes) Write-Verbose "Loaded certificate: $($cert.Subject)" # Output the expiration date of the certificate (skip CA) $expiration = [datetime]$cert.NotAfter if ($expiration -lt (Get-Date)) { Write-Verbose "VMC certificate is expired, expiration date: $($expiration)" $BimiAdvisory += " VMC certificate is expired, expiration date: $($expiration). Please renew the certificate." } else { Write-Verbose "VMC certificate is valid, expiration date: $($expiration)." $BimiAdvisory += " VMC certificate is valid, expiration date: $($expiration)." } break # only evaluate the first certificate in the chain } } else { Write-Verbose "'a=' (VMC) tag does not contain a valid HTTPS URL." $BimiAdvisory += " 'a=' (VMC) tag does not contain a valid HTTPS URL, it should start with 'https://'. It's recommended to use a valid HTTPS URL for VMC to prevent that scammers misuse your logo." } } else { Write-Verbose "No 'a=' (VMC) tag found in BIMI record." $BimiAdvisory += " No 'a=' (VMC) tag found, it's recommended to include a VMC certificate." } } $BimiReturnValues = New-Object psobject $BimiReturnValues | Add-Member NoteProperty "Name" $domain $BimiReturnValues | Add-Member NoteProperty "BimiRecord" $BimiRecord $BimiReturnValues | Add-Member NoteProperty "BimiAdvisory" $BimiAdvisory $BimiObject.Add($BimiReturnValues) $BimiReturnValues } } end { Write-Verbose "Completed $($MyInvocation.MyCommand)" } } Set-Alias -Name gbimi -Value Get-BimiRecord |