From b2a415daae23bfdab30d0f9c6f1c0b8e6d66362d Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Tue, 9 Jan 2018 05:44:24 +1000 Subject: [PATCH] win_certificate_store: added new module (#33980) * win_certificate_store: added new module * added warning about become or credssp for pfx --- .../modules/windows/win_certificate_store.ps1 | 252 ++++++ .../modules/windows/win_certificate_store.py | 195 +++++ .../targets/win_certificate_store/aliases | 1 + .../win_certificate_store/defaults/main.yml | 4 + .../win_certificate_store/files/root-cert.pem | 20 + .../win_certificate_store/files/root-key.pem | 28 + .../win_certificate_store/files/subj-cert.pem | 19 + .../win_certificate_store/files/subj-key.pem | 28 + .../win_certificate_store/meta/main.yml | 2 + .../win_certificate_store/tasks/main.yml | 121 +++ .../win_certificate_store/tasks/test.yml | 801 ++++++++++++++++++ 11 files changed, 1471 insertions(+) create mode 100644 lib/ansible/modules/windows/win_certificate_store.ps1 create mode 100644 lib/ansible/modules/windows/win_certificate_store.py create mode 100644 test/integration/targets/win_certificate_store/aliases create mode 100644 test/integration/targets/win_certificate_store/defaults/main.yml create mode 100644 test/integration/targets/win_certificate_store/files/root-cert.pem create mode 100644 test/integration/targets/win_certificate_store/files/root-key.pem create mode 100644 test/integration/targets/win_certificate_store/files/subj-cert.pem create mode 100644 test/integration/targets/win_certificate_store/files/subj-key.pem create mode 100644 test/integration/targets/win_certificate_store/meta/main.yml create mode 100644 test/integration/targets/win_certificate_store/tasks/main.yml create mode 100644 test/integration/targets/win_certificate_store/tasks/test.yml diff --git a/lib/ansible/modules/windows/win_certificate_store.ps1 b/lib/ansible/modules/windows/win_certificate_store.ps1 new file mode 100644 index 0000000000..d567d5a12f --- /dev/null +++ b/lib/ansible/modules/windows/win_certificate_store.ps1 @@ -0,0 +1,252 @@ +#!powershell +# This file is part of Ansible + +# Copyright: (c) 2017, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#Requires -Module Ansible.ModuleUtils.Legacy + +$ErrorActionPreference = "Stop" + +$store_name_values = ([System.Security.Cryptography.X509Certificates.StoreName]).GetEnumValues() +$store_location_values = ([System.Security.Cryptography.X509Certificates.StoreLocation]).GetEnumValues() + +$params = Parse-Args $args -supports_check_mode $true +$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false + +$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "absent", "exported", "present" +$path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty ($state -eq "present" -or $state -eq "exported") +$thumbprint = Get-AnsibleParam -obj $params -name "thumbprint" -type "str" -failifempty ($state -eq "exported") +$store_name = Get-AnsibleParam -obj $params -name "store_name" -type "str" -default "My" -validateset $store_name_values +$store_location = Get-AnsibleParam -obj $params -name "store_location" -type "str" -default "LocalMachine" -validateset $store_location_values +$password = Get-AnsibleParam -obj $params -name "password" -type "str" +$key_exportable = Get-AnsibleParam -obj $params -name "key_exportable" -type "bool" -default $true +$key_storage = Get-AnsibleParam -obj $param -name "key_storage" -type "str" -default "default" -validateset "default", "machine", "user" +$file_type = Get-AnsibleParam -obj $params -name "file_type" -type "str" -default "der" -validateset "der", "pem", "pkcs12" + +$result = @{ + changed = $false + thumbprints = @() +} + +Function Get-CertFile($path, $password, $key_exportable, $key_storage) { + # parses a certificate file and returns X509Certificate2Collection + if (-not (Test-Path -Path $path -PathType Leaf)) { + Fail-Json -obj $result -message "File at '$path' either does not exist or is not a file" + } + + # must set at least the PersistKeySet flag so that the PrivateKey + # is stored in a permanent container and not deleted once the handle + # is gone. + $store_flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet + + $key_storage = $key_storage.substring(0,1).ToUpper() + $key_storage.substring(1).ToLower() + $store_flags = $store_flags -bor [Enum]::Parse([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags], "$($key_storage)KeySet") + if ($key_exportable) { + $store_flags = $store_flags -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable + } + + # TODO: If I'm feeling adventurours, write code to parse PKCS#12 PEM encoded + # file as .NET does not have an easy way to import this + $certs = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection + + try { + $certs.Import($path, $password, $store_flags) + } catch { + Fail-Json -obj $result -message "Failed to load cert from file: $($_.Exception.Message)" + } + + return $certs +} + +Function New-CertFile($cert, $path, $type, $password) { + $content_type = switch ($type) { + "pem" { [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert } + "der" { [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert } + "pkcs12" { [System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12 } + } + if ($type -eq "pkcs12") { + $missing_key = $false + if ($cert.PrivateKey -eq $null) { + $missing_key = $true + } elseif ($cert.PrivateKey.CspKeyContainerInfo.Exportable -eq $false) { + $missing_key = $true + } + if ($missing_key) { + Fail-Json -obj $result -message "Cannot export cert with key as PKCS12 when the key is not marked as exportable or not accesible by the current user" + } + } + + if (Test-Path -Path $path) { + Remove-Item -Path $path -Force + $result.changed = $true + } + try { + $cert_bytes = $cert.Export($content_type, $password) + } catch { + Fail-Json -obj $result -message "Failed to export certificate as bytes: $($_.Exception.Message)" + } + + # Need to manually handle a PEM file + if ($type -eq "pem") { + $cert_content = "-----BEGIN CERTIFICATE-----`r`n" + $base64_string = [System.Convert]::ToBase64String($cert_bytes, [System.Base64FormattingOptions]::InsertLineBreaks) + $cert_content += $base64_string + $cert_content += "`r`n-----END CERTIFICATE-----" + $file_encoding = [System.Text.Encoding]::ASCII + $cert_bytes = $file_encoding.GetBytes($cert_content) + } elseif ($type -eq "pkcs12") { + $result.key_exported = $false + if ($cert.PrivateKey -ne $null) { + $result.key_exportable = $cert.PrivateKey.CspKeyContainerInfo.Exportable + } + } + + if (-not $check_mode) { + try { + [System.IO.File]::WriteAllBytes($path, $cert_bytes) + } catch [System.ArgumentNullException] { + Fail-Json -obj $result -message "Failed to write cert to file, cert was null: $($_.Exception.Message)" + } catch [System.IO.IOException] { + Fail-Json -obj $result -message "Failed to write cert to file due to IO exception: $($_.Exception.Message)" + } catch [System.UnauthorizedAccessException, System>Security.SecurityException] { + Fail-Json -obj $result -message "Failed to write cert to file due to permission: $($_.Exception.Message)" + } catch { + Fail-Json -obj $result -message "Failed to write cert to file: $($_.Exception.Message)" + } + } + $result.changed = $true +} + +Function Get-CertFileType($path, $password) { + $certs = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection + try { + $certs.Import($path, $password, 0) + } catch [System.Security.Cryptography.CryptographicException] { + # the file is a pkcs12 we just had the wrong password + return "pkcs12" + } catch { + return "unknown" + } + + $file_contents = Get-Content -Path $path -Raw + if ($file_contents.StartsWith("-----BEGIN CERTIFICATE-----")) { + return "pem" + } elseif ($file_contents.StartsWith("-----BEGIN PKCS7-----")) { + return "pkcs7-ascii" + } elseif ($certs.Count -gt 1) { + # multiple certs must be pkcs7 + return "pkcs7-binary" + } elseif ($certs[0].HasPrivateKey) { + return "pkcs12" + } elseif ($path.EndsWith(".pfx") -or $path.EndsWith(".p12")) { + # no way to differenciate a pfx with a der file so we must rely on the + # extension + return "pkcs12" + } else { + return "der" + } +} + +$store_name = [System.Security.Cryptography.X509Certificates.StoreName]::$store_name +$store_location = [System.Security.Cryptography.X509Certificates.Storelocation]::$store_location +$store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $store_name, $store_location +try { + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) +} catch [System.Security.Cryptography.CryptographicException] { + Fail-Json -obj $result -message "Unable to open the store as it is not readable: $($_.Exception.Message)" +} catch [System.Security.SecurityException] { + Fail-Json -obj $result -message "Unable to open the store with the current permissions: $($_.Exception.Message)" +} catch { + Fail-Json -obj $result -message "Unable to open the store: $($_.Exception.Message)" +} +$store_certificates = $store.Certificates + +try { + if ($state -eq "absent") { + $cert_thumbprints = @() + + if ($path -ne $null) { + $certs = Get-CertFile -path $path -password $password -key_exportable $key_exportable -key_storage $key_storage + foreach ($cert in $certs) { + $cert_thumbprints += $cert.Thumbprint + } + } elseif ($thumbprint -ne $null) { + $cert_thumbprints += $thumbprint + } else { + Fail-Json -obj $result -message "Either path or thumbprint must be set when state=absent" + } + + foreach ($cert_thumbprint in $cert_thumbprints) { + $result.thumbprints += $cert_thumbprint + $found_certs = $store_certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $cert_thumbprint, $false) + if ($found_certs.Count -gt 0) { + foreach ($found_cert in $found_certs) { + try { + if (-not $check_mode) { + $store.Remove($found_cert) + } + } catch [System.Security.SecurityException] { + Fail-Json -obj $result -message "Unable to remove cert with thumbprint '$cert_thumbprint' with the current permissions: $($_.Exception.Message)" + } catch { + Fail-Json -obj $result -message "Unable to remove cert with thumbprint '$cert_thumbprint': $($_.Exception.Message)" + } + $result.changed = $true + } + } + } + } elseif ($state -eq "exported") { + # TODO: Add support for PKCS7 and exporting a cert chain + $result.thumbprints += $thumbprint + $export = $true + if (Test-Path -Path $path -PathType Container) { + Fail-Json -obj $result -message "Cannot export cert to path '$path' as it is a directory" + } elseif (Test-Path -Path $path -PathType Leaf) { + $actual_cert_type = Get-CertFileType -path $path -password $password + if ($actual_cert_type -eq $file_type) { + try { + $certs = Get-CertFile -path $path -password $password -key_exportable $key_exportable -key_storage $key_storage + } catch { + # failed to load the file so we set the thumbprint to something + # that will fail validation + $certs = @{Thumbprint = $null} + } + + if ($certs.Thumbprint -eq $thumbprint) { + $export = $false + } + } + } + + if ($export) { + $found_certs = $store_certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $thumbprint, $false) + if ($found_certs.Count -ne 1) { + Fail-Json -obj $result -message "Found $($found_certs.Count) certs when only expecting 1" + } + + New-CertFile -cert $found_certs -path $path -type $file_type -password $password + } + } else { + $certs = Get-CertFile -path $path -password $password -key_exportable $key_exportable -key_storage $key_storage + foreach ($cert in $certs) { + $result.thumbprints += $cert.Thumbprint + $found_certs = $store_certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $cert.Thumbprint, $false) + if ($found_certs.Count -eq 0) { + try { + if (-not $check_mode) { + $store.Add($cert) + } + } catch [System.Security.Cryptography.CryptographicException] { + Fail-Json -obj $result -message "Unable to import certificate with thumbprint '$($cert.Thumbprint)' with the current permissions: $($_.Exception.Message)" + } catch { + Fail-Json -obj $result -message "Unable to import certificate with thumbprint '$($cert.Thumbprint)': $($_.Exception.Message)" + } + $result.changed = $true + } + } + } +} finally { + $store.Close() +} + +Exit-Json -obj $result diff --git a/lib/ansible/modules/windows/win_certificate_store.py b/lib/ansible/modules/windows/win_certificate_store.py new file mode 100644 index 0000000000..d713723d1d --- /dev/null +++ b/lib/ansible/modules/windows/win_certificate_store.py @@ -0,0 +1,195 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# This file is part of Ansible + +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = r''' +--- +module: win_certificate_store +version_added: '2.5' +short_description: Manages the certificate store +description: +- Used to import/export and remove certificates and keys from the local + certificate store. +- This module is not used to create certificates and will only manage existing + certs as a file or in the store. +- It can be used to import PEM, DER, P7B, PKCS12 (PFX) certificates and export + PEM, DER and PKCS12 certificates. +options: + state: + description: + - If C(present), will ensure that the certificate at I(path) is imported + into the certificate store specified. + - If C(absent), will ensure that the certificate specified by I(thumbprint) + or the thumbprint of the cert at I(path) is removed from the store + specified. + - If C(exported), will ensure the file at I(path) is a certificate + specified by I(thumbprint). + - When exporting a certificate, if I(path) is a directory then the module + will fail, otherwise the file will be replaced if needed. + default: present + choices: + - present + - absent + - exported + path: + description: + - The path to a certificate file. + - This is required when I(state) is C(present) or C(exported). + - When I(state) is C(absent) and I(thumbprint) is not specified, the + thumbprint is derived from the certificate at this path. + thumbprint: + description: + - The thumbprint as a hex string to either export or remove. + - See the examples for how to specify the thumbprint. + store_name: + description: + - The store name to use when importing a certificate or searching for a + certificate. + default: My + choices: + - AddressBook + - AuthRoot + - CertificateAuthority + - Disallowed + - My + - Root + - TrustedPeople + - TrustedPublisher + store_location: + description: + - The store location to use when importing a certificate or searching for a + certificate. + default: LocalMachine + choices: + - CurrentUser + - LocalMachine + password: + description: + - The password of the pkcs12 certificate key. + - This is used when reading a pkcs12 certificate file or the password to + set when C(state=exported) and C(file_type=pkcs12). + - If the pkcs12 file has no password set or no password should be set on + the exported file, do not set this option. + key_exportable: + description: + - Whether to allow the private key to be exported. + - If C(no), then this module and other process will only be able to export + the certificate and the private key cannot be exported. + - Used when C(state=present) only. + type: bool + default: 'yes' + key_storage: + description: + - Specifies where Windows will store the private key when it is imported. + - When set to C(default), the default option as set by Windows is used. + - When set to C(machine), the key is stored in a path accessible by various + users. + - When set to C(user), the key is stored in a path only accessible by the + current user. + - Used when C(state=present) only and cannot be changed once imported. + - See U(https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509keystorageflags.aspx) + for more details. + choices: + - default + - machine + - user + default: default + file_type: + description: + - The file type to export the certificate as when C(state=exported). + - C(der) is a binary ASN.1 encoded file. + - C(pem) is a base64 encoded file of a der file in the OpenSSL form. + - C(pkcs12) (also known as pfx) is a binary container that contains both + the certificate and private key unlike the other options. + - When C(pkcs12) is set and the private key is not exportable or accessible + by the current user, it will throw an exception. + choices: + - der + - pem + - pkcs12 + default: der +notes: +- Some actions on PKCS12 certificates and keys may fail with the error + C(the specified network password is not correct), either use CredSSP or + Kerberos with credential delegation, or use C(become) to bypass these + restrictions. +- The certificates must be located on the Windows host to be set with I(path). +author: +- Jordan Borean (@jborean93) +''' + +EXAMPLES = r''' +- name: import a certificate + win_certificate_store: + path: C:\temp\cert.pem + state: present + +- name: import pfx certificate that is password protected + win_certificate_store: + path: C:\temp\cert.pfx + state: present + password: VeryStrongPasswordHere! + become: yes + become_method: runas + +- name: import pfx certificate without password and set private key as un-exportable + win_certificate_store: + path: C:\temp\cert.pfx + state: present + key_exportable: no + # usually you don't set this here but it is for illustrative purposes + vars: + ansible_winrm_transport: credssp + +- name: remove a certificate based on file thumbprint + win_certificate_store: + path: C:\temp\cert.pem + state: absent + +- name: remove a certificate based on thumbprint + win_certificate_store: + thumbprint: BD7AF104CF1872BDB518D95C9534EA941665FD27 + state: absent + +- name: remove certificate based on thumbprint is CurrentUser/TrustedPublishers store + win_certificate_store: + thumbprint: BD7AF104CF1872BDB518D95C9534EA941665FD27 + state: absent + store_location: CurrentUser + store_name: TrustedPublisher + +- name: export certificate as der encoded file + win_certificate_store: + path: C:\temp\cert.cer + state: exported + file_type: der + +- name: export certificate and key as pfx encoded file + win_certificate_store: + path: C:\temp\cert.pfx + state: exported + file_type: pkcs12 + password: AnotherStrongPass! + become: yes + become_method: runas + become_user: SYSTEM +''' + +RETURN = r''' +thumbprints: + description: A list of certificate thumbprints that were touched by the + module. + returned: success + type: list + sample: ["BC05633694E675449136679A658281F17A191087"] +''' diff --git a/test/integration/targets/win_certificate_store/aliases b/test/integration/targets/win_certificate_store/aliases new file mode 100644 index 0000000000..c6d6198167 --- /dev/null +++ b/test/integration/targets/win_certificate_store/aliases @@ -0,0 +1 @@ +windows/ci/group3 diff --git a/test/integration/targets/win_certificate_store/defaults/main.yml b/test/integration/targets/win_certificate_store/defaults/main.yml new file mode 100644 index 0000000000..076b65d639 --- /dev/null +++ b/test/integration/targets/win_certificate_store/defaults/main.yml @@ -0,0 +1,4 @@ +win_cert_dir: '{{win_output_dir}}\win_certificate' +key_password: password +subj_thumbprint: 'BD7AF104CF1872BDB518D95C9534EA941665FD27' +root_thumbprint: 'BC05633694E675449136679A658281F17A191087' diff --git a/test/integration/targets/win_certificate_store/files/root-cert.pem b/test/integration/targets/win_certificate_store/files/root-cert.pem new file mode 100644 index 0000000000..edbe6b8684 --- /dev/null +++ b/test/integration/targets/win_certificate_store/files/root-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDKDCCAhCgAwIBAgIJAP1vIdGgMJv/MA0GCSqGSIb3DQEBCwUAMCgxGTAXBgNV +BAMMEHJvb3QuYW5zaWJsZS5jb20xCzAJBgNVBAYTAlVTMCAXDTE3MTIxNTA4Mzkz +MloYDzIwODYwMTAyMDgzOTMyWjAoMRkwFwYDVQQDDBByb290LmFuc2libGUuY29t +MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMmq +YT8eZY6rFQKnmScUGnnUH1tLQ+3WQpfKiWygCUSb1CNqO3J1u3pGMEqYM58LK4Kr +Mpskv7K1tCV/EMZqGTqXAIfSLy9umlb/9C3AhL9thBPn5I9dam/EmrIZktI9/w5Y +wBXn4toe+OopA3QkMQh9BUjUCPb9fdOI+ir7OGFZMmxXmiM64+BEeywM2oSGsdZ9 +5hU378UBu2IX4+OAV8Fbr2l6VW+Fxg/tKIOo6Bs46Pa4EZgtemOqs3kxYBOltBTb +vFcLsLa4KYVu5Ge5YfB0Axfaem7PoP8IlMs8gxyojZ/r0o5hzxUcYlL/h8GeeoLW +PFFdiAS+UgxWINOqNXMCAwEAAaNTMFEwHQYDVR0OBBYEFLp9k4LmOnAR4ROrqhb+ +CFdbk2+oMB8GA1UdIwQYMBaAFLp9k4LmOnAR4ROrqhb+CFdbk2+oMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGksycHsjGbXfWfuhQh+CvXk/A2v +MoNgiHtNMTGliVNgoVp1B1rj4x9xyZ8YrO8GAmv8jaCwCShd0B5Ul4aZVk1wglVv +lFAwb4IAZN9jv9+fw5BRzQ2tLhkVWIEwx6pZkhGhhjBvMaplLN5JwBtsdZorFbm7 +wuKiUKcFAM28acoOhCmOhgyNNBZpZn5wXaQDY43AthJOhitAV7vph4MPUkwIJnOh +MA5GJXEqS58TE9z9pkhQnn9598G8tmOXyA2erAoM9JAXM3EYHxVpoHBb9QRj6WAw +XVBo6qRXkwjNEM5CbnD4hVIBsdkOGsDrgd4Q5izQZ3x+jFNkdL/zPsXjJFw= +-----END CERTIFICATE----- + diff --git a/test/integration/targets/win_certificate_store/files/root-key.pem b/test/integration/targets/win_certificate_store/files/root-key.pem new file mode 100644 index 0000000000..c2d641d449 --- /dev/null +++ b/test/integration/targets/win_certificate_store/files/root-key.pem @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyaphPx5ljqsVAqeZJxQaedQfW0tD7dZCl8qJbKAJRJvUI2o7 +cnW7ekYwSpgznwsrgqsymyS/srW0JX8QxmoZOpcAh9IvL26aVv/0LcCEv22EE+fk +j11qb8SashmS0j3/DljAFefi2h746ikDdCQxCH0FSNQI9v1904j6Kvs4YVkybFea +Izrj4ER7LAzahIax1n3mFTfvxQG7Yhfj44BXwVuvaXpVb4XGD+0og6joGzjo9rgR +mC16Y6qzeTFgE6W0FNu8VwuwtrgphW7kZ7lh8HQDF9p6bs+g/wiUyzyDHKiNn+vS +jmHPFRxiUv+HwZ56gtY8UV2IBL5SDFYg06o1cwIDAQABAoIBAFRpZNsutgPJyLmb +vZeF6q8kAxwLnRtom+c9d9hoBHkbYOiSBuAaN6cuyffvTWw9GLFRR5V5BGSheg5X +6YWj03uayTYQ3H9WJHRWHrcn5mjaRnaukhUQXQT7nmT+H16xZJl0vLJupZ33aOla +0X9DxuJusk+RsU7xPEHXDCABl8/m7v3cFttUBughGBG5oDuzKlFbhXPwA8/yeJ1v +qdXKxENi9HO4X5fH1l0vFNIhEqvUVKjw/AzapYtr+bv1wssoNAzvhT7CFa2GjPQ0 +Ibcq3+RxyAN4iQVITy86Yl4LW1jLx63wbg9q1WG/ca9K/OEAuT7ebJNeMYmM+kf7 +sf6A8wECgYEA+nnLJ4QtANtAs6nmDC106DTx1cOf3Yq9JOAvmGLomF/8SrUzZbXM +F+JcZcttXuuFIFcZD0K7fFP9sx2ITH//BS5V0B0x7Z2olWexVjR6/5pOVFPu19ow +tyDCNi5BlTPbvSr/fAxjmO9SgVTb8oG66i4mi0Xn5bp1E441KdvNsHECgYEAzhz/ ++SjFJlJcGNvMmgfAbfv6McUv7TKrPIvVkA++Gi5QdqJjkuzL1uTfgWIY/9iDByMd +W36rFTkYrw6LTMF2dkMjul72Kkco3UExSzOmF4lFmCt3DZW6a6CExKpwk4kF2RnX +GRD0FoZZown3RbPHi9rsWxjyVy/yKGwnvXYndiMCgYEA6rnIUDfllK/jansFQtQ2 +goVbPGAfKJYjurL852mJX4JUBA7bI63CnX9b52lEDXfZQf1dVpfK6zAqx/gdCtPI +QSqy8FzrtSnSGnEaFxcHTRFl5lDhuxaWIIdqeSvP+eqnOhdZZP6XN3LPdrP3isNY +Tq0BIfNY5khd/v19hMSfdYECgYBQ8h6tMY/LrwiwUpIV4/l0uELYDQL3erC5RImI +3EXiblH3ZWsJpqmfKZ+FZos+3z8GLIo5BpQV76h8B5A5grkNVOzRIr42eF/aFOJR +EGWoVKbaTiehVC40WoQJ4I35wxRi4L0TAQ97USQe3akY3LP/fujYFgIGr7PAoEkz +JRX2VQKBgQDir8/a3FZVo6nYI8zIhBz8xqZJIgvlYQqiQFFwADu5eNPMvNIaVy+6 +7HKibGM2jPkuS2KHdc8WUp8IrRRMui04qE7kRxVu41QXEBfPiDvrvAQf8SfJe631 +XvYeZr7HKY4NI5J0ENcb54d7DLQ8a1/wL/GeLVrfUWG35Ra5MW57Og== +-----END RSA PRIVATE KEY----- + diff --git a/test/integration/targets/win_certificate_store/files/subj-cert.pem b/test/integration/targets/win_certificate_store/files/subj-cert.pem new file mode 100644 index 0000000000..6d9ec39c73 --- /dev/null +++ b/test/integration/targets/win_certificate_store/files/subj-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIC0TCCAbkCCQC/MtOBa1UDpzANBgkqhkiG9w0BAQsFADAoMRkwFwYDVQQDDBBy +b290LmFuc2libGUuY29tMQswCQYDVQQGEwJVUzAgFw0xNzEyMTUwODU2MzBaGA8y +MDg2MDEwMjA4NTYzMFowKzEcMBoGA1UEAwwTc3ViamVjdC5hbnNpYmxlLmNvbTEL +MAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDszqdF +So3GlVP1xUnN4bSPrFRFiOl/Mqup0Zn5UJJUR9wLnRD+OLcq7kKin6hYqozSu7cC ++BnWQoq7vGSSNVqv7BqFMwzGJt9IBUQv0UqIQkA/duUdKdAiMn2PQRsNDnkWEbTj +4xsitItVNv84cDG0lkZBYyTgfyZlZLZWplkpUQkrZhoFCekZRJ+ODrqNW3W560rr +OUIh+HiQeBqocat6OdxgICBqpUh8EVo1iha3DXjGN08q5utg6gmbIl2VBaVJjfyd +wnUSqHylJwh6WCIEh+HXsn4ndfNWSN/fDqvi5I10V1j6Zos7yqQf8qAezUAm6eSq +hLgZz0odq9DsO4HHAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFK5mVIJ2D+kI0kk +sxnW4ibWFjzlYFYPYrZg+2JFIVTbKBg1YzyhuIKm0uztqRxQq5iLn/C/uponHoqF +7KDQI37KAJIQdgSva+mEuO9bZAXg/eegail2hN6np7HjOKlPu23s40dAbFrbcOWP +VbsBEPDP0HLv6OgbQWzNlE9HO1b7pX6ozk3q4ULO7IR85P6OHYsBBThL+qsOTzg/ +gVknuB9+n9hgNqZcAcXBLDetOM9aEmYJCGk0enYP5UGLYpseE+rTXFbRuHTPr1o6 +e8BetiSWS/wcrV4ZF5qr9NiYt5eD6JzTB5Rn5awxxj0FwMtrBu003lLQUWxsuTzz +35/RLY4= +-----END CERTIFICATE----- + diff --git a/test/integration/targets/win_certificate_store/files/subj-key.pem b/test/integration/targets/win_certificate_store/files/subj-key.pem new file mode 100644 index 0000000000..51a150afe6 --- /dev/null +++ b/test/integration/targets/win_certificate_store/files/subj-key.pem @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA7M6nRUqNxpVT9cVJzeG0j6xURYjpfzKrqdGZ+VCSVEfcC50Q +/ji3Ku5Cop+oWKqM0ru3AvgZ1kKKu7xkkjVar+wahTMMxibfSAVEL9FKiEJAP3bl +HSnQIjJ9j0EbDQ55FhG04+MbIrSLVTb/OHAxtJZGQWMk4H8mZWS2VqZZKVEJK2Ya +BQnpGUSfjg66jVt1uetK6zlCIfh4kHgaqHGrejncYCAgaqVIfBFaNYoWtw14xjdP +KubrYOoJmyJdlQWlSY38ncJ1Eqh8pScIelgiBIfh17J+J3XzVkjf3w6r4uSNdFdY ++maLO8qkH/KgHs1AJunkqoS4Gc9KHavQ7DuBxwIDAQABAoIBAQDfjqBfS+jYZrUi +uqPYV5IMaNYN5xj4Wi+xXA0OT0A1jLlxxU/7kDNrtg72U9+sBSZ483nstag+nAc5 +ALu5Q+FfX3gR84XFs4DrDv22XtEMHe9leqsFgynYfu4GRaJyCw3JBeJNmWNOuj8n +rYn4EAL8xzmAFUcFIURwSEnTN6vI0cS09nQukz+9CIBuGr7TPMET8YlATDJcH+Ua +EGZ9MAFXdKF6adC2nrCVBDNr8mUEpK1XdQcPH2bvcTuZ3Jj5AF2rOrcHq4FZUm97 +8PaMH6Sarxhwl+ycwrKbU5aEzUYTk67k0V6m9lyvH9z3O3Y84Tr3cZZ5WxdnG6Ri +72MFlfgRAoGBAP8wA+KWJ/ttmEXAoSX4J2fPl7X1RhR+1zPNdLY7iX0uNstL8IFH +vUN9JHi1Tr7llav+2bUTOu2EMDVmDWZH0s/qKOn+GmqIQLp0441fVAiamTcgwGKE +Wwsu4dg10IJ9akHIIbrILT0CvRcIRf67EYLBj3ZwfR+wF1ncefbsxWA9AoGBAO2P +qGMn+yrIi5DZF23x6iD2Y7bIdlUmqIqwb99XhW+3YJmRuh1EuN6XP2bIveRa9xvm +Q7bbcQM0Yv2c7eTyxpzz2I4bmnccVbs6M1VhtkyQEy5+X5yOl9wnitaaUrbWFy/w +kDPuISjLl3xDlxd6dbjf70fkG5oogx5c5toEyWZTAoGAK1CHGErMdozfr9dGgx9f +8Or3oVcEki4FcTGKgfQRHkJd4pv9MrRul6oCKsr7lsN5aDxVz7p34iDx3d54n8fJ +LKleUHllGngOJJf6l+B6bwtuvkC85vv4SCmpA/3+amfHRWsm7oFTzGtOlT4+Q0KV +clBQfZYSZvKIxCP8P8ForzECgYEAjDOad1qjOy68X7Ifx71cJjQDyV4pqDt2gNN8 +Ut1+XN5m3ntI0fk6+fNdcbXLjDe7WvXcxNBhtDh4q6CwLcyyNvMavVPBJ8bLOgIx +RZSzWCA3kdr3ZpgpO78Ci4DsjAdyC9L36A4D9+Wf87CYPT0CuSdAOrd/Ks36BDNj +8wucKQ0CgYAaRwQ18nkemrpQ/+EQgEWnWfqgB+6T4ygZ4ZTym0FAtG7CdLxvCi8V +toyn+zi+yFTRFXHDmvg9HLIIMK/hRQjgc8Ns5nDwgQlGwCZTvjVbD4anCr1IWuky +owvxKWsHseNilKrnAk2maQxrrrpSk8QWrp2CFw04LsWGTxtFvstBmg== +-----END RSA PRIVATE KEY----- + diff --git a/test/integration/targets/win_certificate_store/meta/main.yml b/test/integration/targets/win_certificate_store/meta/main.yml new file mode 100644 index 0000000000..bdea853d75 --- /dev/null +++ b/test/integration/targets/win_certificate_store/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: +- prepare_win_tests diff --git a/test/integration/targets/win_certificate_store/tasks/main.yml b/test/integration/targets/win_certificate_store/tasks/main.yml new file mode 100644 index 0000000000..86bf3baedd --- /dev/null +++ b/test/integration/targets/win_certificate_store/tasks/main.yml @@ -0,0 +1,121 @@ +### keys in files/ have been generated with +# generate root private key +# openssl genrsa -aes256 -out enckey.pem 2048 +# openssl rsa -in envkey.pem -out root-key.pem +# +# generate root certificate +# openssl req -x509 -key root-key.pem -days 24855 -out root-vert.pem -subj "/CN=root.ansible.com/C=US" +# +# generate subject private key +# openssl genrsa -aes256 -out enckey.pem 2048 +# openssl rsa -in enckey.pem -out subj-key.pem +# +# generate subject certificate +# openssl req -new -key subj-key.pem -out cert.csr -subj "/CN=subject.ansible.com/C=US" +# openssl x509 -req -in cert.csr -CA root-cert.pem -CAkey root-key.pem -CAcreateserial -out subj-cert.pem -days 24855 +### +--- +- name: ensure test dir is present + win_file: + path: '{{win_cert_dir}}\exported' + state: directory + +- name: ensure certificates are removed from store before test + win_certificate_store: + thumbprint: '{{item}}' + state: absent + with_items: + - '{{subj_thumbprint}}' + - '{{root_thumbprint}}' + +- name: ensure certificates are removed from custom store before test + win_certificate_store: + thumbprint: '{{item}}' + state: absent + store_name: TrustedPeople + store_location: CurrentUser + with_items: + - '{{subj_thumbprint}}' + - '{{root_thumbprint}}' + +# these files are created on the fly so we don't store binary in the git repo +- name: create PKCS12 without password + command: 'openssl pkcs12 -export -out subj-cert-without-pass.pfx -inkey subj-key.pem -in subj-cert.pem -passout pass:' + args: + chdir: '{{role_path}}/files' + delegate_to: localhost + run_once: yes + +- name: create PKCS12 with password + command: 'openssl pkcs12 -export -out subj-cert-with-pass.pfx -inkey subj-key.pem -in subj-cert.pem -passout pass:{{key_password}}' + args: + chdir: '{{role_path}}/files' + delegate_to: localhost + run_once: yes + +- name: create DER encoded cert + command: openssl x509 -outform der -in subj-cert.pem -out subj-cert.cer + args: + chdir: '{{role_path}}/files' + delegate_to: localhost + run_once: yes + +- name: create PEM encoded PKCS7 file + command: openssl crl2pkcs7 -nocrl -certfile subj-cert.pem -certfile root-cert.pem -out chain.pem + args: + chdir: '{{role_path}}/files' + delegate_to: localhost + run_once: yes + +- name: create DER encoded PKCS7 file + command: openssl crl2pkcs7 -nocrl -certfile subj-cert.pem -certfile root-cert.pem -out chain.p7b -outform der + args: + chdir: '{{role_path}}/files' + delegate_to: localhost + run_once: yes + +- name: copy across test cert files + win_copy: + src: files/ + dest: '{{win_cert_dir}}' + +- block: + - name: run tests + include_tasks: test.yml + + always: + - name: ensure generated keys are deleted + file: + path: '{{role_path}}/files/{{item}}' + state: absent + delegate_to: localhost + run_once: yes + with_items: + - subj-cert-with-pass.pfx + - subj-cert-without-pass.pfx + - subj-cert.cer + - chain.pem + - chain.p7b + + - name: ensure certificates are removed from store after test + win_certificate_store: + thumbprint: '{{item}}' + state: absent + with_items: + - '{{subj_thumbprint}}' + - '{{root_thumbprint}}' + + - name: ensure certificates are removed from custom store after test + win_certificate_store: + thumbprint: '{{item}}' + state: absent + store_name: TrustedPeople + store_location: CurrentUser + with_items: + - '{{subj_thumbprint}}' + - '{{root_thumbprint}}' + + - name: ensure test dir is deleted + win_file: + path: '{{win_cert_dir}}' + state: absent diff --git a/test/integration/targets/win_certificate_store/tasks/test.yml b/test/integration/targets/win_certificate_store/tasks/test.yml new file mode 100644 index 0000000000..eb2ac5ad85 --- /dev/null +++ b/test/integration/targets/win_certificate_store/tasks/test.yml @@ -0,0 +1,801 @@ +--- +- name: fail with invalid store location + win_certificate_store: + state: present + path: '{{win_cert_dir}}\subj-cert.pem' + store_location: FakeLocation + register: fail_fake_location + failed_when: "fail_fake_location.msg != 'Get-AnsibleParam: Argument store_location needs to be one of CurrentUser,LocalMachine but was FakeLocation.'" + +- name: fail with invalid store name + win_certificate_store: + state: present + path: '{{win_cert_dir}}\subj-cert.pem' + store_name: FakeName + register: fail_fake_name + failed_when: "fail_fake_name.msg != 'Get-AnsibleParam: Argument store_name needs to be one of AddressBook,AuthRoot,CertificateAuthority,Disallowed,My,Root,TrustedPeople,TrustedPublisher but was FakeName.'" + +- name: fail when state=present and no path is set + win_certificate_store: + state: present + register: fail_present_no_path + failed_when: "fail_present_no_path.msg != 'Get-AnsibleParam: Missing required argument: path'" + +- name: fail when state=exported and no path is set + win_certificate_store: + state: exported + thumbprint: ABC + register: fail_export_no_path + failed_when: "fail_export_no_path.msg != 'Get-AnsibleParam: Missing required argument: path'" + +- name: fail when state=exported and no thumbprint is set + win_certificate_store: + state: exported + path: '{{win_cert_dir}}' + register: fail_export_no_thumbprint + failed_when: "fail_export_no_thumbprint.msg != 'Get-AnsibleParam: Missing required argument: thumbprint'" + +- name: fail to export thumbprint when path is a dir + win_certificate_store: + state: exported + thumbprint: '{{subj_thumbprint}}' + path: '{{win_cert_dir}}' + register: fail_export_path_is_dir + failed_when: fail_export_path_is_dir.msg != "Cannot export cert to path '" + win_cert_dir + "' as it is a directory" + +- name: fail when state=absent and not path or thumbprint is set + win_certificate_store: + state: absent + register: fail_absent_no_path_or_thumbprint + failed_when: fail_absent_no_path_or_thumbprint.msg != 'Either path or thumbprint must be set when state=absent' + +- name: import pem certificate (check) + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert.pem' + state: present + register: import_pem_check + check_mode: yes + +- name: get result of import pem certificate (check) + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: import_pem_result_check + +- name: assert results of import pem certificate (check) + assert: + that: + - import_pem_check is changed + - import_pem_check.thumbprints == [subj_thumbprint] + - import_pem_result_check.stdout_lines[0] == "False" + +- name: import pem certificate + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert.pem' + state: present + register: import_pem + +- name: get result of import pem certificate + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: import_pem_result + +- name: assert results of import pem certificate + assert: + that: + - import_pem is changed + - import_pem.thumbprints == [subj_thumbprint] + - import_pem_result.stdout_lines[0] == "True" + +- name: import pem certificate (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert.pem' + state: present + register: import_pem_again + +- name: assert results of import pem certificate (idempotent) + assert: + that: + - not import_pem_again is changed + +- name: remove certificate based on thumbprint (check) + win_certificate_store: + thumbprint: '{{subj_thumbprint}}' + state: absent + register: remove_thumbprint_check + check_mode: yes + +- name: get result of remove certificate based on thumbprint (check) + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: remove_thumbprint_result_check + +- name: assert results of remove certificate based on thumbprint (check) + assert: + that: + - remove_thumbprint_check is changed + - remove_thumbprint_check.thumbprints == [subj_thumbprint] + - remove_thumbprint_result_check.stdout_lines[0] == "True" + +- name: remove certificate based on thumbprint + win_certificate_store: + thumbprint: '{{subj_thumbprint}}' + state: absent + register: remove_thumbprint + +- name: get result of remove certificate based on thumbprint + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: remove_thumbprint_result + +- name: assert results of remove certificate based on thumbprint + assert: + that: + - remove_thumbprint is changed + - remove_thumbprint.thumbprints == [subj_thumbprint] + - remove_thumbprint_result.stdout_lines[0] == "False" + +- name: remove certificate based on thumbprint (idempotent) + win_certificate_store: + thumbprint: '{{subj_thumbprint}}' + state: absent + register: remove_thumbprint_again + +- name: assert results of remove certificate based on thumbprint (idempotent) + assert: + that: + - not remove_thumbprint_again is changed + +- name: import der certificate (check) + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert.cer' + state: present + register: import_der_check + check_mode: yes + +- name: get result of import der certificate (check) + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: import_der_result_check + +- name: assert results of import der certificate (check) + assert: + that: + - import_der_check is changed + - import_der_check.thumbprints == [subj_thumbprint] + - import_der_result_check.stdout_lines[0] == "False" + +- name: import der certificate + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert.cer' + state: present + register: import_der + +- name: get result of import der certificate + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: import_der_result + +- name: assert results of import der certificate + assert: + that: + - import_der is changed + - import_der.thumbprints == [subj_thumbprint] + - import_der_result.stdout_lines[0] == "True" + +- name: import der certificate (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert.cer' + state: present + register: import_der_again + +- name: assert results of import der certificate (idempotent) + assert: + that: + - not import_der_again is changed + +- name: remove certificate based on path (check) + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert.cer' + state: absent + register: remove_path_check + check_mode: yes + +- name: get result of remove certificate based on path (check) + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: remove_path_result_check + +- name: assert results of remove certificate based on path (check) + assert: + that: + - remove_path_check is changed + - remove_path_check.thumbprints == [subj_thumbprint] + - remove_path_result_check.stdout_lines[0] == "True" + +- name: remove certificate based on path + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert.cer' + state: absent + register: remove_path + +- name: get result of remove certificate based on path + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: remove_path_result + +- name: assert results of remove certificate based on path + assert: + that: + - remove_path is changed + - remove_path.thumbprints == [subj_thumbprint] + - remove_path_result.stdout_lines[0] == "False" + +- name: remove certificate based on path (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert.cer' + state: absent + register: remove_path_again + +- name: assert results of remove certificate based on path (idempotent) + assert: + that: + - not remove_path_again is changed + +- name: import PEM encoded p7b chain (check) + win_certificate_store: + path: '{{win_cert_dir}}\chain.pem' + state: present + register: import_pem_p7b_check + check_mode: yes + +- name: get result of subj in p7b chain (check) + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: import_pem_p7b_subj_result_check + +- name: get result of root in p7b chain (check) + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{root_thumbprint}}" }) { $true } else { $false } + register: import_pem_p7b_root_result_check + +- name: assert results of import PEM encoded p7b chain (check) + assert: + that: + - import_pem_p7b_check is changed + - import_pem_p7b_check.thumbprints|count == 2 + - subj_thumbprint in import_pem_p7b_check.thumbprints + - root_thumbprint in import_pem_p7b_check.thumbprints + - import_pem_p7b_subj_result_check.stdout_lines[0] == "False" + - import_pem_p7b_root_result_check.stdout_lines[0] == "False" + +- name: import PEM encoded p7b chain + win_certificate_store: + path: '{{win_cert_dir}}\chain.pem' + state: present + register: import_pem_p7b + +- name: get result of subj in p7b chain + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: import_pem_p7b_subj_result + +- name: get result of root in p7b chain + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{root_thumbprint}}" }) { $true } else { $false } + register: import_pem_p7b_root_result + +- name: assert results of import PEM encoded p7b chain + assert: + that: + - import_pem_p7b is changed + - import_pem_p7b.thumbprints|count == 2 + - subj_thumbprint in import_pem_p7b.thumbprints + - root_thumbprint in import_pem_p7b.thumbprints + - import_pem_p7b_subj_result.stdout_lines[0] == "True" + - import_pem_p7b_root_result.stdout_lines[0] == "True" + +- name: import PEM encoded p7b chain (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\chain.pem' + state: present + register: import_pem_p7b_again + +- name: assert results of import PEM encoded p7b chain (idempotent) + assert: + that: + - not import_pem_p7b_again is changed + +- name: remove p7b chain certs + win_certificate_store: + thumbprint: '{{item}}' + state: absent + with_items: + - '{{subj_thumbprint}}' + - '{{root_thumbprint}}' + +- name: import DER encoded p7b chain into custom store (check) + win_certificate_store: + path: '{{win_cert_dir}}\chain.p7b' + state: present + store_name: TrustedPeople + store_location: CurrentUser + register: import_der_p7b_check + check_mode: yes + +- name: get result of subj in p7b chain in custom store (check) + win_shell: if (Get-ChildItem -Path Cert:\CurrentUser\TrustedPeople | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: import_der_p7b_subj_result_check + +- name: get result of root in p7b chain in custom store (check) + win_shell: if (Get-ChildItem -Path Cert:\CurrentUser\TrustedPeople | Where-Object { $_.Thumbprint -eq "{{root_thumbprint}}" }) { $true } else { $false } + register: import_der_p7b_root_result_check + +- name: assert results of import DER encoded p7b chain into custom store (check) + assert: + that: + - import_der_p7b_check is changed + - import_der_p7b_check.thumbprints|count == 2 + - subj_thumbprint in import_der_p7b_check.thumbprints + - root_thumbprint in import_der_p7b_check.thumbprints + - import_der_p7b_subj_result_check.stdout_lines[0] == "False" + - import_der_p7b_root_result_check.stdout_lines[0] == "False" + +- name: import DER encoded p7b chain into custom store + win_certificate_store: + path: '{{win_cert_dir}}\chain.p7b' + state: present + store_name: TrustedPeople + store_location: CurrentUser + register: import_der_p7b + +- name: get result of subj in p7b chain in custom store + win_shell: if (Get-ChildItem -Path Cert:\CurrentUser\TrustedPeople | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: import_der_p7b_subj_result + +- name: get result of root in p7b chain in custom store + win_shell: if (Get-ChildItem -Path Cert:\CurrentUser\TrustedPeople | Where-Object { $_.Thumbprint -eq "{{root_thumbprint}}" }) { $true } else { $false } + register: import_der_p7b_root_result + +- name: assert results of import DER encoded p7b chain into custom store + assert: + that: + - import_der_p7b is changed + - import_der_p7b.thumbprints|count == 2 + - subj_thumbprint in import_der_p7b.thumbprints + - root_thumbprint in import_der_p7b.thumbprints + - import_der_p7b_root_result.stdout_lines[0] == "True" + - import_der_p7b_root_result.stdout_lines[0] == "True" + +- name: import DER encoded p7b chain into custom store (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\chain.p7b' + state: present + store_name: TrustedPeople + store_location: CurrentUser + register: import_der_p7b_again + +- name: assert results of import DER encoded p7b chain into custom store (idempotent) + assert: + that: + - not import_der_p7b_again is changed + +- name: remove p7b chain certs from custom store + win_certificate_store: + thumbprint: '{{item}}' + state: absent + store_name: TrustedPeople + store_location: CurrentUser + with_items: + - '{{subj_thumbprint}}' + - '{{root_thumbprint}}' + +- name: import pfx without password and non exportable (check) + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert-without-pass.pfx' + state: present + key_exportable: no + vars: &become_vars + ansible_become: yes + ansible_become_method: runas + ansible_become_user: '{{ansible_user}}' + ansible_become_pass: '{{ansible_password}}' + register: import_pfx_without_pass_check + check_mode: yes + +- name: get results of import pfx without password and non exportable (check) + win_shell: if (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: import_pfx_without_pass_result_check + +- name: assert results of import pfx without password and non exportable (check) + assert: + that: + - import_pfx_without_pass_check is changed + - import_pfx_without_pass_check.thumbprints == [subj_thumbprint] + - import_pfx_without_pass_result_check.stdout_lines[0] == "False" + +- name: import pfx without password and non exportable + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert-without-pass.pfx' + state: present + key_exportable: no + vars: *become_vars + register: import_pfx_without_pass + +- name: get results of import pfx without password and non exportable + win_shell: (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }).PrivateKey.CspKeyContainerInfo.Exportable + vars: *become_vars + register: import_pfx_without_pass_result + +- name: assert results of import pfx without password and non exportable + assert: + that: + - import_pfx_without_pass is changed + - import_pfx_without_pass.thumbprints == [subj_thumbprint] + - import_pfx_without_pass_result.stdout_lines[0] == "False" + +- name: import pfx without password and non exportable (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert-without-pass.pfx' + state: present + key_exportable: no + vars: *become_vars + register: import_pfx_without_pass_again + +- name: assert results of import pfx without password and non exportable (idempotent) + assert: + that: + - not import_pfx_without_pass_again is changed + +- name: fail import pfx with password and none set + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert-with-pass.pfx' + state: present + store_location: CurrentUser + store_name: TrustedPeople + register: fail_import_pfx_with_password + failed_when: "'Failed to load cert from file' not in fail_import_pfx_with_password.msg and 'The specified network password is not correct' not in fail_import_pfx_with_password.msg" + +- name: import pfx with password (check) + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert-with-pass.pfx' + state: present + password: '{{key_password}}' + store_location: CurrentUser + store_name: TrustedPeople + register: import_pfx_with_pass_check + vars: *become_vars + check_mode: yes + +- name: get results of import pfx with password (check) + win_shell: if (Get-ChildItem -Path Cert:\CurrentUser\TrustedPeople | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }) { $true } else { $false } + register: import_pfx_with_pass_result_check + +- name: assert results of import pfx with password (check) + assert: + that: + - import_pfx_with_pass_check is changed + - import_pfx_with_pass_check.thumbprints == [subj_thumbprint] + - import_pfx_with_pass_result_check.stdout_lines[0] == "False" + +- name: import pfx with password + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert-with-pass.pfx' + state: present + password: '{{key_password}}' + store_location: CurrentUser + store_name: TrustedPeople + vars: *become_vars + register: import_pfx_with_pass + +- name: get results of import pfx with password + win_shell: (Get-ChildItem -Path Cert:\CurrentUser\TrustedPeople | Where-Object { $_.Thumbprint -eq "{{subj_thumbprint}}" }).PrivateKey.CspKeyContainerInfo.Exportable + vars: *become_vars + register: import_pfx_with_pass_result + +- name: assert results of import pfx with password + assert: + that: + - import_pfx_with_pass is changed + - import_pfx_with_pass.thumbprints == [subj_thumbprint] + - import_pfx_with_pass_result.stdout_lines[0] == "True" + +- name: import pfx with password (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\subj-cert-with-pass.pfx' + state: present + password: '{{key_password}}' + store_location: CurrentUser + store_name: TrustedPeople + vars: *become_vars + register: import_pfx_with_pass_again + +- name: assert results of import pfx with password (idempotent) + assert: + that: + - not import_pfx_with_pass_again is changed + +- name: import root cert for export tests + win_certificate_store: + path: '{{win_cert_dir}}\root-cert.pem' + state: present + +- name: export cert as pem (check) + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert.pem' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pem + register: export_pem_check + check_mode: yes + +- name: get result of export cert as pem (check) + win_stat: + path: '{{win_cert_dir}}\exported\cert.pem' + register: export_pem_result_check + +- name: assert results of export cert as pem (check) + assert: + that: + - export_pem_check is changed + - export_pem_check.thumbprints == [subj_thumbprint] + - export_pem_result_check.stat.exists == False + +- name: export cert as pem + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert.pem' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pem + register: export_pem + +- name: get result of export cert as pem + win_stat: + path: '{{win_cert_dir}}\exported\cert.pem' + register: export_pem_result + +- name: assert results of export cert as pem + assert: + that: + - export_pem is changed + - export_pem.thumbprints == [subj_thumbprint] + - export_pem_result.stat.checksum == '1ebf5467d18230e9f611940a74d12f1d0bc819b7' + +- name: export cert as pem (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert.pem' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pem + register: export_pem_again + +- name: assert results of export cert as pem + assert: + that: + - not export_pem_again is changed + +- name: export cert as der (check) + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert.cer' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: der + register: export_der_check + check_mode: yes + +- name: get result of export cert as der (check) + win_stat: + path: '{{win_cert_dir}}\exported\cert.cer' + register: export_der_result_check + +- name: assert results of export cert as der (check) + assert: + that: + - export_der_check is changed + - export_der_check.thumbprints == [subj_thumbprint] + - export_der_result_check.stat.exists == False + +- name: export cert as der + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert.cer' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: der + register: export_der + +- name: get result of export cert as der + win_stat: + path: '{{win_cert_dir}}\exported\cert.cer' + register: export_der_result + +- name: assert results of export cert as der + assert: + that: + - export_der is changed + - export_der.thumbprints == [subj_thumbprint] + - export_der_result.stat.checksum == 'bd7af104cf1872bdb518d95c9534ea941665fd27' + +- name: export cert as der (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert.cer' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: der + register: export_der_again + +- name: assert results of export cert as der + assert: + that: + - not export_der_again is changed + +- name: export cert as der replacing pem + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert.pem' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: der + register: export_der_over_pem + +- name: get result of export cert as der replacing pem + win_stat: + path: '{{win_cert_dir}}\exported\cert.pem' + register: export_der_over_pem_result + +- name: assert results of export cert as der replacing pem + assert: + that: + - export_der_over_pem is changed + - export_der_over_pem.thumbprints == [subj_thumbprint] + - export_der_over_pem_result.stat.checksum == 'bd7af104cf1872bdb518d95c9534ea941665fd27' + +- name: export cert as pem replacing der + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert.cer' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pem + register: export_pem_over_der + +- name: get result of export cert as pem replacing der + win_stat: + path: '{{win_cert_dir}}\exported\cert.cer' + register: export_pem_over_der_result + +- name: assert results of export cert as pem replacing der + assert: + that: + - export_pem_over_der is changed + - export_pem_over_der.thumbprints == [subj_thumbprint] + - export_pem_over_der_result.stat.checksum == '1ebf5467d18230e9f611940a74d12f1d0bc819b7' + +- name: export cert with key and password as pfx (check) + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert-pass.pfx' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pkcs12 + store_location: CurrentUser + store_name: TrustedPeople + password: '{{key_password}}' + register: export_pfx_with_pass_check + vars: *become_vars + check_mode: yes + +- name: get result of export cert with key and password as pfx (check) + win_stat: + path: '{{win_cert_dir}}\exported\cert-pass.pfx' + register: export_pfx_with_pass_result_check + +- name: assert results of export cert with key and password as pfx (check) + assert: + that: + - export_pfx_with_pass_check is changed + - export_pfx_with_pass_check.thumbprints == [subj_thumbprint] + - export_pfx_with_pass_result_check.stat.exists == False + +- name: export cert with key and password as pfx + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert-pass.pfx' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pkcs12 + store_location: CurrentUser + store_name: TrustedPeople + password: '{{key_password}}' + vars: *become_vars + register: export_pfx_with_pass + +- name: get result of export cert with key and password as pfx + win_shell: | + $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 + $cert.Import("{{win_cert_dir}}\exported\cert-pass.pfx", "{{key_password}}", 0) + $cert.HasPrivateKey + vars: *become_vars + register: export_pfx_with_pass_result + +- name: assert results of export cert with key and password as pfx + assert: + that: + - export_pfx_with_pass is changed + - export_pfx_with_pass.thumbprints == [subj_thumbprint] + - export_pfx_with_pass_result.stdout_lines[0] == "True" + +- name: export cert with key and password as pfx (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert-pass.pfx' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pkcs12 + store_location: CurrentUser + store_name: TrustedPeople + password: '{{key_password}}' + vars: *become_vars + register: export_pfx_with_pass_again + +- name: assert results of export cert with key and password as pfx (idempotent) + assert: + that: + - not export_pfx_with_pass_again is changed + +- name: export cert with key without password as pfx (check) + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert-without-pass.pfx' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pkcs12 + store_location: CurrentUser + store_name: TrustedPeople + vars: *become_vars + register: export_pfx_without_pass_check + check_mode: yes + +- name: get result of export cert with key without password as pfx (check) + win_stat: + path: '{{win_cert_dir}}\exported\cert-without-pass.pfx' + register: export_pfx_without_pass_result_check + +- name: assert results of export cert with key without password as pfx (check) + assert: + that: + - export_pfx_without_pass_check is changed + - export_pfx_without_pass_check.thumbprints == [subj_thumbprint] + - export_pfx_without_pass_result_check.stat.exists == False + +- name: export cert with key without password as pfx + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert-without-pass.pfx' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pkcs12 + store_location: CurrentUser + store_name: TrustedPeople + vars: *become_vars + register: export_pfx_without_pass + +- name: get result of export cert with key without password as pfx + win_shell: | + $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 + $cert.Import("{{win_cert_dir}}\exported\cert-without-pass.pfx", $null, 0) + $cert.HasPrivateKey + vars: *become_vars + register: export_pfx_without_pass_result + +- name: assert results of export cert with key without password as pfx + assert: + that: + - export_pfx_without_pass is changed + - export_pfx_without_pass.thumbprints == [subj_thumbprint] + - export_pfx_without_pass_result.stdout_lines[0] == "True" + +- name: export cert with key without password as pfx (idempotent) + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert-without-pass.pfx' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pkcs12 + store_location: CurrentUser + store_name: TrustedPeople + vars: *become_vars + register: export_pfx_without_pass_again + +- name: assert results of export cert with key without password as pfx (idempotent) + assert: + that: + - not export_pfx_without_pass_again is changed + +- name: fail to export cert with key as pfx when not marked as exportable + win_certificate_store: + path: '{{win_cert_dir}}\exported\cert-fail.pfx' + thumbprint: '{{subj_thumbprint}}' + state: exported + file_type: pkcs12 + vars: *become_vars + register: fail_export_non_exportable + failed_when: fail_export_non_exportable.msg != 'Cannot export cert with key as PKCS12 when the key is not marked as exportable or not accesible by the current user'