# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

using namespace System.Security.Cryptography.X509Certificates
using namespace System.Security.Cryptography

function New-CmsRecipient {
    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])]
    param([String]$Name, [Switch]$Invalid, [String]$OutPfxFile)
    $hash = [HashAlgorithmName]::SHA256
    $pad = [RSASignaturePadding]::Pkcs1
    $oids = [OidCollection]::new()
    $oids.Add("1.3.6.1.4.1.311.80.1") | Out-Null
    $ext1 = [X509KeyUsageExtension]::new([X509KeyUsageFlags]::DataEncipherment, $false)
    $ext2 = [X509EnhancedKeyUsageExtension]::new($oids, $false)
    $req = ([CertificateRequest]::new("CN=$Name", ([RSA]::Create(2048)), $hash, $pad))
    if (!$Invalid) { ($ext1, $ext2).ForEach( { $req.CertificateExtensions.Add($_) }) }
    $certTmp = $req.CreateSelfSigned([datetime]::Now.AddDays(-1), [datetime]::Now.AddDays(365))
    $certBytes = $certTmp.Export([X509ContentType]::Pfx, "tmp")
    [X509KeyStorageFlags[]]$flags = "PersistKeySet", "Exportable"
    $cert = [X509Certificate2]::new($certBytes, "tmp", $flags)
    if ($OutPfxFile) {
        $outfile = New-Item $OutPfxFile -Force
        [System.IO.File]::WriteAllBytes($outfile.FullName, $cert.Export([X509ContentType]::Pfx))
    }
    return $cert
}

Describe "CmsMessage cmdlets using X509 cert" -Tags "CI" {

    BeforeAll {
        Setup -Dir "certDir"
        Setup -File "vc1.pfx"
        Setup -File "vc2.pfx"
        Setup -File "certDir/vc3.pfx"
        Setup -File "message.txt" -Content "test"
        $file1 = "TestDrive:\vc1.pfx"
        $file2 = "TestDrive:\vc2.pfx"
        $messageFile = "TestDrive:\message.txt"
        $cipherFile = "TestDrive:\cipher.txt"
        $vc1 = New-CmsRecipient "ValidCms1" -OutPfxFile $file1
        $vc2 = New-CmsRecipient "ValidCms2" -OutPfxFile $file2
        $vc3 = New-CmsRecipient "ValidCms22" -OutPfxFile "TestDrive:\certDir\vc3.pfx"
        $ic = New-CmsRecipient "InvalidCms" -Invalid -OutPfxFile "TestDrive:\ic.pfx"
        $store = [X509Store]::new("My", [StoreLocation]::CurrentUser)
        $store.Open("ReadWrite")
        if (!$IsMacOS) {
            $store.Add($vc1)
            $store.Add($vc2)
            $store.Add($vc3)
        }
        $certContent = "
            -----BEGIN CERTIFICATE-----
            MIIDXTCCAkWgAwIBAgIQRTsRwsx0LZBHrx9z5Dag2zANBgkqhkiG9w0BAQUFADAh
            MR8wHQYDVQQDDBZNeURhdGFFbmNpcGhlcm1lbnRDZXJ0MCAXDTE0MDcyNTIyMjkz
            OVoYDzMwMTQwNzI1MjIzOTM5WjAhMR8wHQYDVQQDDBZNeURhdGFFbmNpcGhlcm1l
            bnRDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx3SuShUvnRqn
            tYOIouJdP3wPZ5rtDi2KYPurpngGNZjM0EGDTrnhmEAI8DL4Kp6n/zz1mYVoX73+
            6uCpZX/13VDXg1neebJ261XpBX6FzxtclIQr8ywdUtrEgCnUAhgqgvO1Wwm4ogNR
            tWGCGkmlnqyaoV1j/V4KSn4WvKqSUIOZm0umGCTtNAJ6VtdpYO+uxxnRAapPUCY+
            qQ7DFzTUECIo1lMlBcuMiXj6NSFr4/D7ltkZ27jCdsZmzI7ZvRnDlfSYTPQnAO/E
            0uYn9uyKY/xfngWkUX/pe+j+10Lm1ypbASrj2Ezgf0KeZRXBwqKUOLhKheEmBJ18
            rLV27qwHeQIDAQABo4GOMIGLMA4GA1UdDwEB/wQEAwIEMDAUBgNVHSUEDTALBgkr
            BgEEAYI3UAEwRAYJKoZIhvcNAQkPBDcwNTAOBggqhkiG9w0DAgICAIAwDgYIKoZI
            hvcNAwQCAgCAMAcGBSsOAwIHMAoGCCqGSIb3DQMHMB0GA1UdDgQWBBRIyIzwInLJ
            3B+FajVUFMACf1hrxjANBgkqhkiG9w0BAQUFAAOCAQEAfFt4rmUmWfCbbwi2mCrZ
            Osq0lfVNUiZ+iLlEKga4VAI3sJZRtErnVM70eXUt7XpRaOdIfxjuXFpsgc37KyLi
            ByCORLuRC0itZVs3aba48opfMDXivxBy0ngqCPPLQsyaN9K7WnpvYV1QxiudYwwU
            8U5rFmzlwNLvc3XiyoGWaVZluk2DIJawQ5QYAU9/NMBBCbPHjTG7k0l4cpcEC+Ex
            od3RlO6/MOYuK2WB4VTxKsV80EdA3ljlu7Td8P4movnrbB4rG4wpCpk05eREkg/5
            Y54Ilo9m5OSAWtdx4yfS779eebLgUs3P+dk6EKwovXMokVveZA8cenIp3QkqSpeT
            cQ==
            -----END CERTIFICATE-----
            "
    }

    It "Cert Store: Encrypt/Decrypt using Subject" {
        "test" | Protect-CmsMessage -To $vc1.Subject | Unprotect-CmsMessage | Should -BeExactly "test"
        "test" | Protect-CmsMessage -To $vc1.Subject, $vc2.Subject | Unprotect-CmsMessage | Should -BeExactly "test"
    }

    It "Cert Store: Subject with wildcard (returns single cert)" {
        "test" | Protect-CmsMessage -To "*dCms1" | Unprotect-CmsMessage | Should -BeExactly "test"
    }

    It "Cert Store: Subject with wrong wildcard (returns multiple certs)" {
        { "test" | Protect-CmsMessage -To "*ValidCms*" -ErrorAction Stop } | Should -Throw -ErrorId 'IdentifierMustReferenceSingleCertificate'
    }

    It "Cert Store: Encrypt/Decrypt using Thumbprint" {
        "test" | Protect-CmsMessage -To $vc1.Thumbprint | Unprotect-CmsMessage | Should -BeExactly "test"
        "test" | Protect-CmsMessage -To $vc1.Thumbprint, $vc2.Thumbprint | Unprotect-CmsMessage | Should -BeExactly "test"
    }

    It "Cert Store: Encrypt/Decrypt subject and thumbprint" {
        "test" | Protect-CmsMessage -To $vc1.Thumbprint, $vc2.Subject | Unprotect-CmsMessage | Should -BeExactly "test"
    }

    It "Cert Store: removing test certificates" {
        $store.Remove($vc1)
        $store.Remove($vc2)
        $store.Remove($vc3)
        if ($IsMacOS) {
            $store.Remove($ic)
        }

        $store.Certificates.Find("FindByThumbprint", $vc1.Thumbprint, $false).Count | Should -BeExactly 0
        $store.Certificates.Find("FindByThumbprint", $vc2.Thumbprint, $false).Count | Should -BeExactly 0
        $store.Certificates.Find("FindByThumbprint", $vc3.Thumbprint, $false).Count | Should -BeExactly 0
        $store.Certificates.Find("FindByThumbprint", $ic.Thumbprint, $false).Count | Should -BeExactly 0
    }

    It "Encrypting with X509Cert" {
        "test" | Protect-CmsMessage -To $vc1 | Should -BeLike '-----BEGIN CMS*'
    }

    It "Encrypting with base64 string" {
        "test" | Protect-CmsMessage -To $certContent | Should -BeLike '-----BEGIN CMS*'
    }

    It "Encrypting with multiple X509Cert" {
        "test" | Protect-CmsMessage -To $vc1, $vc2 | Should -BeLike '-----BEGIN CMS*'
    }

    It "Decrypt with X509Cert" {
        "test" | Protect-CmsMessage -To $vc1 | Unprotect-CmsMessage -To $vc1 | Should -BeExactly "test"
    }

    It "Decrypt with multiple X509Cert" {
        "test" | Protect-CmsMessage -To $vc1, $vc2 | Unprotect-CmsMessage -To $vc1, $vc2 | Should -BeExactly "test"
    }

    It "Encrypt with invalid cert" {
        { "test" | Protect-CmsMessage -To $ic -ErrorAction Stop } | Should -Throw -ErrorId 'CertificateCannotBeUsedForEncryption'
    }

    It "Encrypt with valid and invalid" {
        { "test" | Protect-CmsMessage -To $vc1, $vc2, $ic -ErrorAction Stop } | Should -Throw -ErrorId 'CertificateCannotBeUsedForEncryption'
    }

    It "Encrypt/Decrypt from file" {
        Protect-CmsMessage -Path $messageFile -To $vc1 -OutFile $cipherFile
        $msg = Unprotect-CmsMessage -To $vc1 -Path $cipherFile
        $msg | Should -BeExactly "test"
    }

    It "Get-CmsMessage from content" {
        ("test" | Protect-CmsMessage -To $vc1 | Get-CmsMessage).Content | Should -BeLike '-----BEGIN CMS*'
    }

    It "Get-CmsMessage from file" {
        (Get-CmsMessage -Path $cipherFile).Content | Should -BeLike '-----BEGIN CMS*'
    }

    It "Encrypt With Single File" {
        "test" | Protect-CmsMessage -To $file1 | Unprotect-CmsMessage -To $file1 | Should -BeExactly "test"
    }

    It "Encrypt With Multiple Files" {
        $msg = "test" | Protect-CmsMessage -To $file1, $file2
        ($msg | Unprotect-CmsMessage -To $file1) | Should -BeExactly "test"
        ($msg | Unprotect-CmsMessage -To $file2) | Should -BeExactly "test"
    }

    It "Encrypt/Decrypt with Directory" {
        "test" | Protect-CmsMessage -To "TestDrive:\certDir" | Unprotect-CmsMessage -To "TestDrive:\certDir" | Should -BeExactly "test"
    }

    It "Decrypt with multiple files" {
        "test" | Protect-CmsMessage -To $vc1 | Unprotect-CmsMessage -To $file1, $file2 | Should -BeExactly "test"
    }

    AfterAll {
        $store.Dispose()
    }
}
