ADCSTemplateForPSEncryption.psm1

#requires -Version 5.0 -Modules ActiveDirectory

<##############################################################################
Create Certificate Template in ADCS for PowerShell CMS Encryption
by Ashley McGlone, Microsoft PFE
http://aka.ms/GoateePFE
@GoateePFE

Take parameters
Generate a unique OID for the template
Create the template
Permission the template with Enroll for a specified group(s)
Optionally add AutoEnroll permission as well
Publish the template to CA(s)
Target all operations to the designated DC

Requirements:
-Enterprise AD CS PKI
-Tested on 2012 R2 & 2016
-Enterprise Administrator rights
-ActiveDirectory PowerShell Module

Template generated will have these properties:
-2 year lifetime
-2003 lowest compatibility level
-Private key not exportable
-Not stored in AD
-Document Encryption
-No digital signature

Satisfies the document encryption template requires of these PowerShell 5.x features:
-DSC credential encryption
https://docs.microsoft.com/en-us/powershell/dsc/securemof#certificate-requirements
-CMS message cmdlets
https://docs.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Security/Protect-CmsMessage
##############################################################################>



Function Get-RandomHex {
param ([int]$Length)
    $Hex = '0123456789ABCDEF'
    [string]$Return = $null
    For ($i=1;$i -le $length;$i++) {
        $Return += $Hex.Substring((Get-Random -Minimum 0 -Maximum 16),1)
    }
    Return $Return
}

Function IsUniqueOID {
param ($cn,$TemplateOID,$Server,$ConfigNC)
    $Search = Get-ADObject -Server $Server `
        -SearchBase "CN=OID,CN=Public Key Services,CN=Services,$ConfigNC" `
        -Filter {cn -eq $cn -and msPKI-Cert-Template-OID -eq $TemplateOID}
    If ($Search) {$False} Else {$True}
}

Function New-TemplateOID {
Param($Server,$ConfigNC)
    <#
    OID CN/Name [10000000-99999999].[32 hex characters]
    OID msPKI-Cert-Template-OID [Forest base OID].[1000000-99999999].[10000000-99999999] <--- second number same as first number in OID name
    #>

    do {
        $OID_Part_1 = Get-Random -Minimum 1000000  -Maximum 99999999
        $OID_Part_2 = Get-Random -Minimum 10000000 -Maximum 99999999
        $OID_Part_3 = Get-RandomHex -Length 32
        $OID_Forest = Get-ADObject -Server $Server `
            -Identity "CN=OID,CN=Public Key Services,CN=Services,$ConfigNC" `
            -Properties msPKI-Cert-Template-OID |
            Select-Object -ExpandProperty msPKI-Cert-Template-OID
        $msPKICertTemplateOID = "$OID_Forest.$OID_Part_1.$OID_Part_2"
        $Name = "$OID_Part_2.$OID_Part_3"
    } until (IsUniqueOID -cn $Name -TemplateOID $msPKICertTemplateOID -Server $Server -ConfigNC $ConfigNC)
    Return @{
        TemplateOID  = $msPKICertTemplateOID
        TemplateName = $Name
    }
}


<#
.SYNOPSIS
Creates a new Active Directory Certificate Services template for PowerShell CMS encryption.
.DESCRIPTION
The template can be used for CMS cmdlet encryption and/or DSC credential encryption.
.NOTES
The OID generated does not use the approved API, but it works well. Please report any issues.
.PARAMETER DisplayName
DisplayName for the certificate template.
.PARAMETER Server
Active Directory Domain Controller to target for the operation.
.PARAMETER GroupName
Global group(s) to assign permissions to enroll the template.
Specify in DOMAIN\GROUP naming convention.
Default is Domain Computers.
.PARAMETER AutoEnroll
Switch to also grant AutoEnroll to the group(s).
.PARAMETER Publish
Publish the template to all Certificate Authority issuers.
Default is only Enroll.
.EXAMPLE
New-ADCSTemplateForPSEncryption -DisplayName PowerShellCMS
.EXAMPLE
New-ADCSTemplateForPSEncryption -DisplayName PowerShellCMS -Server dc1.contoso.com -GroupName G_DSCNodes -AutoEnroll -Publish
.EXAMPLE
# From a client configured for AD CS autoenrollment:
$Req = @{
    Template = 'PSEncryption'
    Url = 'ldap:'
    CertStoreLocation = 'Cert:\LocalMachine\My'
}
Get-Certificate @Req
# Note: If you have the Carbon module installed, it conflicts with Get-Certificate native cmdlet.

$DocEncrCert = (dir Cert:\LocalMachine\My -DocumentEncryptionCert | Sort-Object NotBefore)[-1]
Protect-CmsMessage -To $DocEncrCert -Content "Encrypted with my new cert from the new template!"
#>

Function New-ADCSTemplateForPSEncryption {
param(
    [parameter(Mandatory)]
    [string]$DisplayName,
    [string]$Server = (Get-ADDomainController -Discover -ForceDiscover -Writable).HostName[0],
    [string[]]$GroupName = "$((Get-ADDomain).NetBIOSName)\Domain Computers",
    [switch]$AutoEnroll,
    [switch]$Publish
)
    Import-Module ActiveDirectory
    $ConfigNC = $((Get-ADRootDSE -Server $Server).configurationNamingContext)

    #region CREATE OID
    <#
    CN : 14891906.F2AC4390685318BD1D950A66EDB50FF4
    DisplayName : TemplateNameHere
    DistinguishedName : CN=14891906.F2AC4390685318BD1D950A66EDB50FF4,CN=OID,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com
    dSCorePropagationData : {1/1/1601 12:00:00 AM}
    flags : 1
    instanceType : 4
    msPKI-Cert-Template-OID : 1.3.6.1.4.1.311.21.8.11489019.14294623.5588661.594850.12204198.151.6616009.14891906
    Name : 14891906.F2AC4390685318BD1D950A66EDB50FF4
    ObjectCategory : CN=ms-PKI-Enterprise-Oid,CN=Schema,CN=Configuration,DC=contoso,DC=com
    ObjectClass : msPKI-Enterprise-Oid
    #>

    $OID = New-TemplateOID -Server $Server -ConfigNC $ConfigNC
    $TemplateOIDPath = "CN=OID,CN=Public Key Services,CN=Services,$ConfigNC"
    $oa = @{
        'DisplayName' = $DisplayName
        'flags' = [System.Int32]'1'
        'msPKI-Cert-Template-OID' = $OID.TemplateOID
    }
    New-ADObject -Path $TemplateOIDPath -OtherAttributes $oa -Name $OID.TemplateName -Type 'msPKI-Enterprise-Oid' -Server $Server
    #endregion

    #region CREATE TEMPLATE
    # https://docs.microsoft.com/en-us/powershell/dsc/securemof#certificate-requirements
    # https://blogs.technet.microsoft.com/option_explicit/2012/04/09/pki-certificates-and-the-x-509-standard/
    # https://technet.microsoft.com/en-us/library/cc776447(v=ws.10).aspx
    $oa = @{
        'flags' = [System.Int32]'131680'
        'msPKI-Certificate-Application-Policy' = [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]@('1.3.6.1.4.1.311.80.1')
        'msPKI-Certificate-Name-Flag' = [System.Int32]'268435456'
        'msPKI-Enrollment-Flag' = [System.Int32]'32'
        'msPKI-Minimal-Key-Size' = [System.Int32]'2048'
        'msPKI-Private-Key-Flag' = [System.Int32]'16842752'
        'msPKI-Template-Minor-Revision' = [System.Int32]'1'
        'msPKI-Template-Schema-Version' = [System.Int32]'2'
        'msPKI-RA-Signature' = [System.Int32]'0'
        'pKIMaxIssuingDepth' = [System.Int32]'0'
        'ObjectClass' = [System.String]'pKICertificateTemplate'
        'pKICriticalExtensions' = [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]@('2.5.29.15')
        'pKIDefaultCSPs' = [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]@('1,Microsoft RSA SChannel Cryptographic Provider')
        'pKIDefaultKeySpec' = [System.Int32]'1'
        'pKIExpirationPeriod' = [System.Byte[]]@('0','128','114','14','93','194','253','255')
        'pKIExtendedKeyUsage' = [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]@('1.3.6.1.4.1.311.80.1')
        'pKIKeyUsage' = [System.Byte[]]@('32')
        'pKIOverlapPeriod' = [System.Byte[]]@('0','128','166','10','255','222','255','255')
        'revision' = [System.Int32]'100'
        'msPKI-Cert-Template-OID' = $OID.TemplateOID
    }
    $TemplatePath = "CN=Certificate Templates,CN=Public Key Services,CN=Services,$ConfigNC"
    New-ADObject -Path $TemplatePath -OtherAttributes $oa -Name $DisplayName -DisplayName $DisplayName -Type pKICertificateTemplate -Server $Server
    #endregion

    #region PERMISSIONS
    ## Potential issue here that the AD: drive may not be targetting the selected DC in the -SERVER parameter
    $TemplatePath            = "AD:\CN=$DisplayName,CN=Certificate Templates,CN=Public Key Services,CN=Services,$ConfigNC"
    $acl                     = Get-ACL $TemplatePath
    $InheritedObjectType = [GUID]'00000000-0000-0000-0000-000000000000'
    ForEach ($Group in $GroupName) {
        $ObjectType          = [GUID]'0e10c968-78fb-11d2-90d4-00c04f79dc55'
        $account             = New-Object System.Security.Principal.NTAccount($Group)
        $sid                 = $account.Translate([System.Security.Principal.SecurityIdentifier])
        $ace                 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule `
            $sid, 'ExtendedRight', 'Allow', $ObjectType, 'None', $InheritedObjectType
        $acl.AddAccessRule($ace)

        If ($AutoEnroll) {
            $ObjectType      = [GUID]'a05b8cc2-17bc-4802-a710-e7c15ab866a2'
            $ace             = New-Object System.DirectoryServices.ActiveDirectoryAccessRule `
                $sid, 'ExtendedRight', 'Allow', $ObjectType, 'None', $InheritedObjectType
            $acl.AddAccessRule($ace)
        }
    }
    Set-ACL $TemplatePath -AclObject $acl
    #endregion

    #region ISSUE
    If ($Publish) {
        ### WARNING: Issues on all available CAs. Test in your environment.
        $EnrollmentPath = "CN=Enrollment Services,CN=Public Key Services,CN=Services,$ConfigNC"
        $CAs = Get-ADObject -SearchBase $EnrollmentPath -SearchScope OneLevel -Filter * -Server $Server
        ForEach ($CA in $CAs) {
            Set-ADObject -Identity $CA.DistinguishedName -Add @{certificateTemplates=$DisplayName} -Server $Server
        }
    }
    #endregion
}

Export-ModuleMember -Function New-ADCSTemplateForPSEncryption