StringCertCreation.psm1

Function New-StringCertificate()
{
    [CmdletBinding(PositionalBinding=$false, DefaultParameterSetName = "None")]
    [Alias("New-StringCert", "nsc")]
    [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])]
    param
    (
        [parameter(Mandatory=$false, Position=0)]
        [string] $Subject = "$env:COMPUTERNAME - StringCert",

        [parameter(Mandatory=$false)]
        [AllowEmptyString()]
        [string] $FriendlyName = [string]::Empty,

        [parameter(Mandatory=$false)]
        [datetime] $ValidUntil = [datetime]::Now.AddYears(2),

        [parameter(Mandatory=$false)]
        [ValidateSet("SHA256", "SHA384", "SHA512")]
        [string] $Algorithm = "SHA256",

        [parameter(Mandatory=$false)]
        [ValidateSet("2048", "4096", "8192", "16384")]
        [int] $KeyLength = 2048,

        [parameter(Mandatory=$false)]
        [ValidateSet("CurrentUser", "LocalMachine")]
        [string] $InstallAsTrusted,

        [parameter(Mandatory=$true, ParameterSetName='ExportPfx')]
        [ValidateScript({
            $_ -match '\.pfx$'
        })]
        [string] $ToPfxFile,

        [parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true,
            ParameterSetName='ExportPfx')]
        [alias('Password')]
        [securestring] $PfxPassword
    )
    Process
    {
        if ($PSBoundParameters.ContainsKey("InstallAsTrusted") -and $InstallAsTrusted -eq "LocalMachine")
        {
            # Make sure PowerShell is elevated
            $myWinId = [System.Security.Principal.WindowsIdentity]::GetCurrent();
            $myPrinId = New-Object System.Security.Principal.WindowsPrincipal($myWinId);
            $adm = [System.Security.Principal.WindowsBuiltInRole]::Administrator;
            if (-not $myPrinId.IsInRole($adm))
            {
                throw "To install the certificate in the trusted machine store, PowerShell must be running as an Administrator!";
            }
        }

        if ($PSBoundParameters.ContainsKey("Subject") -and $Subject.StartsWith("CN=", $true, [cultureinfo]::CurrentCulture))
        {
            $Subject = $Subject.Substring(3);
        }

        $extsToAdd = New-Object 'System.Collections.Generic.List[object]';
        $extsToAdd.Add((setKU));
        $extsToAdd.Add((setEKU));
        $extsToAdd.Add((setBC));

        # Set Subject Name
        $subj = setSubjectName -CN $Subject;

        # Make private key
        $privKey = newPrivateKey -Length $KeyLength;

        # Create request
        $certReq = newCertRequest `
            -CertSubject $subj `
            -Validity $ValidUntil `
            -PrivateKey $privKey `
            -HashAlgorithm $Algorithm `
            -Extensions $extsToAdd;

        [byte[]]$newCertBytes = completeRequest -Request $certReq -FriendlyName $FriendlyName;

        $newCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($newCertBytes);
        Write-Output -InputObject $newCert;

        if ($PSBoundParameters.ContainsKey("InstallAsTrusted"))
        {
            $store = New-Object System.Security.Cryptography.X509Certificates.X509Store(
                [System.Security.Cryptography.X509Certificates.StoreName]::Root,
                [System.Security.Cryptography.X509Certificates.StoreLocation]"$InstallAsTrusted"
            );
            $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::MaxAllowed);
            $store.Add($newCert);
            $store.Close();
            $store.Dispose();
        }

        if ($PSBoundParameters.ContainsKey("ToPfxFile"))
        {
            [byte[]]$pfxData = $newCert.Export(
                [System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx,
                $PfxPassword
            );
            [System.IO.File]::WriteAllBytes($ToPfxFile, $pfxData);
        }
    }
}

#region KEY USAGE
Function setKU()
{
    [CmdletBinding()]
    param()

    $private:ku = New-Object -ComObject 'X509Enrollment.CX509ExtensionKeyUsage.1';
    $private:ku.InitializeEncode(48);    # "DataEncipherment & KeyEncipherment"
    $private:ku.Critical = $false;
    return $private:ku;
}

#endregion

#region ENHANCED KEY USAGE
Function setEKU()
{
    [CmdletBinding()]
    param()

    $private:ekuOids = New-Object -ComObject 'X509Enrollment.CObjectIds.1';
    $private:serverAuthOid = New-Object -ComObject 'X509Enrollment.CObjectId.1';
    $private:clientAuthOid = New-Object -ComObject 'X509Enrollment.CObjectId.1';
    $private:sa = [System.Security.Cryptography.Oid]::FromFriendlyName("Server Authentication", [System.Security.Cryptography.OidGroup]::EnhancedKeyUsage);
    $private:ca = [System.Security.Cryptography.Oid]::FromFriendlyName("Client Authentication", [System.Security.Cryptography.OidGroup]::EnhancedKeyUsage);
    $private:serverAuthOid.InitializeFromValue($private:sa.Value);
    $private:clientAuthOid.InitializeFromValue($private:ca.Value);
    $private:ekuOids.Add($private:serverAuthOid);
    $private:ekuOids.Add($private:clientAuthOid);

    $private:ekuExt = New-Object -ComObject 'X509Enrollment.CX509ExtensionEnhancedKeyUsage.1';
    $private:ekuExt.InitializeEncode($private:ekuOids);
    return $private:ekuExt;
}

#endregion

#region BASIC CONSTRAINTS
Function setBC()
{
    [CmdletBinding()]
    param()

    $private:bc = New-Object -ComObject 'X509Enrollment.CX509ExtensionBasicConstraints.1';
    $private:bc.InitializeEncode($false, -1);
    $private:bc.Critical = $true;
    return $private:bc;
}

#endregion

#region SUBJECT NAME
Function setSubjectName([string] $CN)
{
    $private:name = New-Object -ComObject 'X509Enrollment.CX500DistinguishedName.1';
    $private:name.Encode("CN=$($CN)", 0);
    return $private:name;
}

#endregion

#region PRIVATE KEY
Function newPrivateKey([int]$Length)
{
    $private:key = New-Object -ComObject 'X509Enrollment.CX509PrivateKey.1';
    $private:algId = New-Object -ComObject 'X509Enrollment.CObjectId.1';
    $private:algVal = [System.Security.Cryptography.Oid]::FromFriendlyName("RSA", [System.Security.Cryptography.OidGroup]::PublicKeyAlgorithm);
    $private:algId.InitializeFromValue($private:algVal.Value);
    $private:key.ProviderName = 'Microsoft RSA SChannel Cryptographic Provider';
    $private:key.Algorithm = $private:algId;
    $private:key.KeySpec = 1;   # 1 = 'XCN_AT_KEYEXCHANGE'
    $private:key.Length = $Length;
    $private:key.ExportPolicy = 1;  # 1 = 'XCN_NCRYPT_ALLOW_EXPORT_FLAG' -- (i.e. - You can export it :P )
    $private:key.MachineContext = $false;

    $private:key.Create();
    return $private:key;
}

#endregion

#region NEW CERT REQUEST
Function newCertRequest()
{
    param
    (
        [object] $CertSubject,
        [datetime] $Validity,
        [object] $PrivateKey,
        [string] $HashAlgorithm,
        [System.Collections.Generic.List[object]] $Extensions
    )

    $private:req = New-Object -ComObject 'X509Enrollment.CX509CertificateRequestCertificate.1';
    $private:req.InitializeFromPrivateKey(1, $PrivateKey, [string]::Empty);     # 1 = 'X509CertificateEnrollmentContext.CurrentUser'
    $private:req.Subject = $CertSubject;
    $private:req.Issuer = $private:req.Subject;
    $private:req.NotBefore = [datetime]::Now;
    $private:req.NotAfter = $Validity;
    foreach ($ext in $Extensions)
    {
        $private:req.X509Extensions.Add($ext);
    }
    $private:sigId = New-Object -ComObject 'X509Enrollment.CObjectId.1';
    $private:hash = [System.Security.Cryptography.Oid]::FromFriendlyName($HashAlgorithm, [System.Security.Cryptography.OidGroup]::HashAlgorithm);
    $private:sigId.InitializeFromValue($private:hash.Value);

    $private:req.SignatureInformation.HashAlgorithm = $private:sigId;
    $private:req.Encode();
    return $private:req;
}

#endregion

#region COMPLETE CERT REQUEST
Function completeRequest([object]$Request, [string]$FriendlyName)
{
    $private:enroll = New-Object -ComObject 'X509Enrollment.CX509Enrollment.1';
    if (-not [string]::IsNullOrEmpty($FriendlyName))
    {
        $private:enroll.CertificateFriendlyName = $FriendlyName;
    }
    $private:enroll.InitializeFromRequest($Request);

    $private:endCert = $private:enroll.CreateRequest(1);    # 1 = 'EncodingType.XCN_CRYPT_STRING_BASE64'
    $private:enroll.InstallResponse(2, $private:endCert, 1, [string]::Empty);
        # 2 = 'InstallResponseRestrictionFlags.AllowUntrustedCertificate'
        # 1 = 'EncodingType.XCN_CRYPT_STRING_BASE64'

    [byte[]]$private:certBytes = [System.Convert]::FromBase64String($private:endCert);
    Write-Output -InputObject $private:certBytes -NoEnumerate;
}

#endregion