├── .delivery └── project.toml ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── Berksfile ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── TESTING.md ├── attributes └── default.rb ├── chefignore ├── kitchen.dokken.yml ├── kitchen.yml ├── libraries ├── helpers.rb ├── random_password.rb └── secure_password.rb ├── metadata.rb ├── recipes ├── default.rb └── upgrade.rb ├── resources ├── dhparam.rb ├── ec_private_key.rb ├── ec_public_key.rb ├── rsa_private_key.rb ├── rsa_public_key.rb ├── x509_certificate.rb ├── x509_crl.rb └── x509_request.rb ├── spec ├── spec_helper.rb └── unit │ ├── helpers │ └── helpers_spec.rb │ ├── libraries │ └── random_password_spec.rb │ └── recipes │ ├── resource_spec.rb │ └── upgrade_spec.rb └── test ├── fixtures └── cookbooks │ └── test │ ├── metadata.rb │ └── recipes │ ├── httpd.rb │ ├── resources.rb │ └── upgrade.rb └── integration └── resources └── resources_spec.rb /.delivery/project.toml: -------------------------------------------------------------------------------- 1 | remote_file = "https://raw.githubusercontent.com/chef-cookbooks/community_cookbook_tools/master/delivery/project.toml" 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Cookbook version 2 | [Version of the cookbook where you are encountering the issue] 3 | 4 | ### Chef-client version 5 | [Version of chef-client in your environment] 6 | 7 | ### Platform Details 8 | [Operating system distribution and release version. Cloud provider if running in the cloud] 9 | 10 | ### Scenario: 11 | [What you are trying to achieve and you can't?] 12 | 13 | ### Steps to Reproduce: 14 | [If you are filing an issue what are the things we need to do in order to repro your problem? How are you using this cookbook or any resources it includes?] 15 | 16 | ### Expected Result: 17 | [What are you expecting to happen as the consequence of above reproduction steps?] 18 | 19 | ### Actual Result: 20 | [What actually happens after the reproduction steps? Include the error output or a link to a gist if possible.] 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | [Describe what this change achieves] 4 | 5 | ### Issues Resolved 6 | 7 | [List any existing issues this PR resolves] 8 | 9 | ### Check List 10 | 11 | - [ ] All tests pass. See 12 | - [ ] New functionality includes testing. 13 | - [ ] New functionality has been documented in the README if applicable 14 | - [ ] All commits have been signed for the Developer Certificate of Origin. See 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | .config 3 | coverage 4 | InstalledFiles 5 | lib/bundler/man 6 | pkg 7 | rdoc 8 | spec/reports 9 | test/tmp 10 | test/version_tmp 11 | tmp 12 | _Store 13 | *~ 14 | *# 15 | .#* 16 | \#*# 17 | .*.sw[a-z] 18 | *.un~ 19 | *.tmp 20 | *.bk 21 | *.bkup 22 | 23 | # ruby/bundler files 24 | .ruby-version 25 | .ruby-gemset 26 | .rvmrc 27 | Gemfile.lock 28 | .bundle 29 | *.gem 30 | 31 | # YARD artifacts 32 | .yardoc 33 | _yardoc 34 | doc/ 35 | .idea 36 | 37 | # chef stuff 38 | Berksfile.lock 39 | .kitchen 40 | kitchen.local.yml 41 | vendor/ 42 | .coverage/ 43 | .zero-knife.rb 44 | Policyfile.lock.json 45 | 46 | # vagrant stuff 47 | .vagrant/ 48 | .vagrant.d/ 49 | .kitchen/ 50 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | addons: 4 | apt: 5 | sources: 6 | - chef-current-trusty 7 | packages: 8 | - chefdk 9 | 10 | # Don't `bundle install` which takes about 1.5 mins 11 | install: echo "skip bundle install" 12 | 13 | branches: 14 | only: 15 | - master 16 | 17 | services: docker 18 | 19 | env: 20 | matrix: 21 | - INSTANCE=resources-amazonlinux 22 | - INSTANCE=resources-ubuntu-1604 23 | - INSTANCE=resources-ubuntu-1804 24 | - INSTANCE=resources-debian-8 25 | - INSTANCE=resources-debian-9 26 | - INSTANCE=resources-centos-6 27 | - INSTANCE=resources-centos-7 28 | - INSTANCE=resources-opensuse-leap 29 | - INSTANCE=resources-fedora-latest 30 | - INSTANCE=upgrade-ubuntu-1604 31 | - INSTANCE=upgrade-ubuntu-1804 32 | - INSTANCE=upgrade-debian-8 33 | - INSTANCE=upgrade-debian-9 34 | - INSTANCE=upgrade-centos-6 35 | - INSTANCE=upgrade-centos-7 36 | - INSTANCE=upgrade-opensuse-leap 37 | - INSTANCE=upgrade-fedora-latest 38 | 39 | before_script: 40 | - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) 41 | - eval "$(chef shell-init bash)" 42 | - chef --version 43 | - cookstyle --version 44 | - foodcritic --version 45 | 46 | script: KITCHEN_LOCAL_YAML=kitchen.dokken.yml kitchen verify ${INSTANCE} 47 | 48 | matrix: 49 | include: 50 | - script: 51 | - chef exec delivery local all 52 | env: UNIT_AND_LINT=1 53 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://supermarket.chef.io' 2 | 3 | metadata 4 | 5 | group :integration do 6 | cookbook 'test', path: 'test/fixtures/cookbooks/test' 7 | end 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # openssl Cookbook CHANGELOG 2 | 3 | This file is used to list changes made in each version of the openssl cookbook. 4 | 5 | ## 8.5.5 (2018-09-04) 6 | 7 | All resources in this cookbook are now built into Chef 14.4+. When Chef 15.4 is released (April 2019) the resources will be removed from this cookbook as all users should be running Chef 14.4 or later at that point. 8 | 9 | ## 8.5.4 (2018-08-29) 10 | 11 | - Add missing email documentation for the request property 12 | - Fix x509_crl to work on non-Linux platforms 13 | - Attribute -> Property in the readme 14 | - revokation -> revocation in the readme 15 | - Update group/owner documentation 16 | - Avoid deprecation warnings on Chef 14.3+ 17 | 18 | ## 8.5.3 (2018-08-15) 19 | 20 | - Call ::OpenSSL not OpenSSL to be more defensive in the helpers 21 | 22 | ## 8.5.2 (2018-08-14) 23 | 24 | - Back out mode change in ec_private_key 25 | 26 | ## 8.5.1 (2018-08-14) 27 | 28 | - Add license headers to the resources 29 | - Remove default_action setup from the resources since this is done automatically in custom resources now 30 | - Make sure to use the path name_property when creating the ec public key file 31 | - Make sure we're using openssl and not Chef's Openssl class 32 | - Simplify how we handle user/group properties 33 | 34 | ## 8.5.0 (2018-08-02) 35 | 36 | - Use the system provided owner/group defaults in resources 37 | - Added new openssl_x509_crl resource 38 | - Fix openssl_ec_public_key with documentation & tests 39 | - Few corrections in the documentation 40 | - Fix backward compatibility with chef client 12 41 | 42 | ## 8.4.0 (2018-07-30) 43 | 44 | This release is brought to you by Institut National de l'Audiovisuel, which contributed the following changes: 45 | 46 | - openssl_x509 is renamed to openssl_x509_certificate with backwards compatibility for the old name 47 | - openssl_x509_certificate can now generate a signed certificate with a provided CA cert & key 48 | - openssl_x509_certificate now support x509 extensions 49 | - openssl_x509_certificate now support x509 csr 50 | - openssl_x509_certificate now generate a random serial for the certificate 51 | - openssl_x509_certificate expires has now a default value : 365 52 | - country field is now mandatory in x509_request 53 | - the private key file is not rewrited in x509_request if it already exist 54 | 55 | ## 8.3.0 (2018-07-25) 56 | 57 | - Add resource x509_request 58 | 59 | ## 8.2.0 (2018-07-23) 60 | 61 | - Add ec_private_key & ec_public_key resources 62 | 63 | ## 8.1.2 (2018-02-09) 64 | 65 | - Fix typo in resources that caused failures on Windows. 66 | - Properly reference key_cipher in the readme 67 | 68 | ## 8.1.1 (2018-01-05) 69 | 70 | - Add YARD comments to all the helpers 71 | - Move valid ciphers directly into the equal_to check 72 | - Remove the Chefspec matchers since modern ChefSpec does this automatically 73 | - Fix failures on Windows nodes 74 | 75 | ## 8.1.0 (2017-12-28) 76 | 77 | - Adding x509 support for /ST and /L 78 | - Allow passing private key content to rsa_public_key resource via property 79 | - Fix openssl_rsa_public_key converging on every run 80 | - Fix undefied method "cipher" error in openssl_rsa_private_key resource 81 | 82 | ## 8.0.0 (2017-12-11) 83 | 84 | - Added a new openssl_rsa_public_key resource which generates a public key from a private key 85 | - Rename openssl_rsa_key to openssl_rsa_private_key, while still allowing the old name to function. This resource actually generates private keys, but the previous name didn't make that clear 86 | - Added owner, group, and mode properties to all of the resources so you could control who owned the files you generated 87 | - Set the default modes of generated files to 640 instead of 644 88 | - Set the files to generate using node['root_group'] not 'root' for compatibility on other *nix systems such as FreeBSD and macOS 89 | - Added a new property to openssl_rsa_private_key for specifying the cipher to use 90 | - Converted integration tests to InSpec and moved all resources to a single Kitchen suite for quicker testing 91 | - Added a force property to allow overwriting any existing key that may exist 92 | - Fixed upgrade recipe failures on Debian 9 93 | - Added a new path property which allows you to set the path there instead of in the resource's name 94 | - Improved input validation in some of the helpers 95 | - Added a deprecation message in Opscode::OpenSSL::Password helper "secure_password" and removed readme documentation 96 | - Added a warning in the upgrade recipe if we're on an unsupported platform 97 | - Switched the upgrade recipe to a multipackage upgrade to speed up Chef runs 98 | 99 | ## 7.1.0 (2017-05-30) 100 | 101 | - Add supported platforms to the metdata 102 | - Fix amazon support 103 | - Remove class_eval usage and require Chef 12.7+ 104 | 105 | ## 7.0.1 (2017-03-21) 106 | 107 | - Fix compatibility with Chef 12.5.1 108 | 109 | ## 7.0.0 (2017-03-06) 110 | 111 | - Converted LWRPs to custom resources, increasing the chef-client dependency to 12.5+. This fixes the bus where each resource notified on every run even if it didn't actually update the files on disk. 112 | - Added testing for Chef 13 113 | - Test with Local Delivery instead of Rake 114 | 115 | ## 6.1.1 (2017-01-19) 116 | 117 | - Resolve deprecation warnings in chefspec 118 | - Use proper ::File class and fix ^2 validation of dhparam key length 119 | - Disable .zero? in cookstyle for now 120 | 121 | ## 6.1.0 (2017-01-18) 122 | 123 | - [#37] Support for Subject Alternative Names on generated self-signed certificates 124 | - rubocop 125 | - Cookstyle fixes 126 | 127 | ## 6.0.0 (2016-09-08) 128 | 129 | - Update the minimum chef release to 12.1 130 | 131 | ## 5.0.1 (2016-09-01) 132 | - Update docs from node.normal as node.set has been deprecated 133 | - Testing updates 134 | 135 | ## 5.0.0 (2016-08-27) 136 | 137 | - Remove the need for the chef-sugar cookbook 138 | - Remove the default['openssl']['packages'] attribute in the upgrades recipe and instead use the correct openssl packages based on platform 139 | - Remove support for Debian 6 and Ubuntu 10.04 in the upgrade recipe 140 | - Add support for Fedora and Suse in the upgrade recipe 141 | - Prevent errors with unset variable in error raising within the random password helper 142 | - Add cookstyle and resolve all warnings 143 | - Add testing, contributing, and maintainers documentation 144 | - Add integration testing in Travis CI with kitchen-dokken 145 | - Add issues_url, source_url and chef_version metadata 146 | - Update the requirements section of the README 147 | - Update the Chefspecs to avoid errors and run using caching for faster runs 148 | - Add issues and PR templates for Github 149 | 150 | ## v4.4.0 (2015-08-28) 151 | 152 | - NEW: x509 certificates are now signed via SHA-256 instead of SHA-1 153 | - FIX: gen_dhparam error now correctly fails with TypeError instead of ArgumentError if Generator argument isn't an integer 154 | 155 | ## v4.3.2 (2015-08-01) 156 | 157 | - FIX: Updated changelog 158 | 159 | ## v4.3 (2015-08-01) 160 | 161 | - NEW: Add rsa_key lwrp 162 | - FIX: dhparam lwrp now correctly honors the generator parameter 163 | 164 | ## v4.2 (2015-06-23) 165 | 166 | - NEW: Add dhparam lwrp 167 | - FIX: x509 lwrp now updates resource count correctly 168 | 169 | ## v4.1.2 (2015-06-20) 170 | 171 | - Add Serverspec suite 172 | - Removed update suite from .kitchen.yml 173 | - Add explicit license to test cookbook recipes 174 | - Add Whyrun support to x509 LWRP 175 | - Expand Chefspec tests for x509 LWRP to step_into LWRP 176 | - Add helper library 177 | - Update x509 LWRP to verify existing keys, if specified 178 | 179 | ## v4.1.1 (2015-06-11) 180 | 181 | - README.md fixes 182 | 183 | ## v4.1.0 (2015-06-11) 184 | 185 | - Add new random_password Mixin (Thanks, Seth!) 186 | - Rewritten README.md 187 | - Refactor specs 188 | - Clear Rubocop violations 189 | 190 | ## v4.0.0 (2015-02-19) 191 | 192 | - Reverting to Opscode module namespace 193 | 194 | ## v3.0.2 (2015-12-18) 195 | 196 | - Accidently released 2.0.2 as 3.0.2 197 | - Re-namespaced `Opscode::OpenSSL::Password` module as `Chef::OpenSSL::Password` 198 | 199 | ## v2.0.2 (2014-12-30) 200 | 201 | - Call cert.to_pem before recipe DSL 202 | 203 | ## v2.0.0 (2014-06-11) 204 | 205 | - # 1 - **[COOK-847](https://tickets.chef.io/browse/COOK-847)** - Add LWRP for generating self signed certs 206 | 207 | - # 4 - **[COOK-4715](https://tickets.chef.io/browse/COOK-4715)** - add upgrade recipe and complete test harness 208 | 209 | ## v1.1.0 210 | 211 | ### Improvement 212 | 213 | - **[COOK-3222](https://tickets.chef.io/browse/COOK-3222)** - Allow setting length for `secure_password` 214 | 215 | ## v1.0.2 216 | 217 | - Add name attribute to metadata 218 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please refer to 2 | https://github.com/chef-cookbooks/community_cookbook_documentation/blob/master/CONTRIBUTING.MD 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # This gemfile provides additional gems for testing and releasing this cookbook 2 | # It is meant to be installed on top of ChefDK which provides the majority 3 | # of the necessary gems for testing this cookbook 4 | # 5 | # Run 'chef exec bundle install' to install these dependencies 6 | 7 | source 'https://rubygems.org' 8 | 9 | gem 'community_cookbook_releaser' 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenSSL Cookbook 2 | 3 | [![Build Status](https://travis-ci.org/chef-cookbooks/openssl.svg?branch=master)](http://travis-ci.org/chef-cookbooks/openssl) [![Cookbook Version](https://img.shields.io/cookbook/v/openssl.svg)](https://supermarket.chef.io/cookbooks/openssl) 4 | 5 | This cookbook provides tools for working with the Ruby OpenSSL library. It includes: 6 | 7 | - A library method to generate secure random passwords in recipes, using the Ruby SecureRandom library. 8 | - A resource for generating RSA private keys. 9 | - A resource for generating RSA public keys. 10 | - A resource for generating EC private keys. 11 | - A resource for generating EC public keys. 12 | - A resource for generating x509 certificates. 13 | - A resource for generating x509 requests. 14 | - A resource for generating x509 crl. 15 | - A resource for generating dhparam.pem files. 16 | - An attribute-driven recipe for upgrading OpenSSL packages. 17 | 18 | ## Deprecation 19 | 20 | All resources in this cookbook are now built-into Chef 14.4 and later so this cookbook is no longer necessary to use those resources. All future development of these resources will take place in the chef-client itself and we highly recommend users upgrade to get the latest and greatest functionality. No further development of this cookbook will take place. 21 | 22 | ## Platforms 23 | 24 | - Debian / Ubuntu derivatives 25 | - Fedora 26 | - FreeBSD 27 | - macOS 28 | - openSUSE / SUSE Linux Enterprises 29 | - RHEL/CentOS/Scientific/Amazon/Oracle 30 | - Solaris 31 | 32 | ## Chef 33 | 34 | - Chef 12.7+ 35 | 36 | ## Cookbooks 37 | 38 | - none 39 | 40 | ## Attributes 41 | 42 | - `node['openssl']['restart_services']` - An array of service resources that depend on the openssl packages. This array is empty by default, as Chef has no reasonable way to detect which applications or services are compiled against these packages. _Note_ Each service listed in this array should represent a "`service`" resource specified in the recipes of the node's run list. 43 | 44 | ## Recipes 45 | 46 | ### upgrade 47 | 48 | The upgrade recipe iterates over the list of packages in the `node['openssl']['packages']` attribute, and manages them with the `:upgrade` action. Each package will send a `:restart` notification to service resources named in the `node['openssl']['restart_services']` attribute. 49 | 50 | #### Example Usage 51 | 52 | In this example, assume the node is running the `stats_collector` daemon, which depends on the openssl library. Imagine that a new openssl vulnerability has been disclosed, and the operating system vendor has released an update to openssl to address this vulnerability. In order to protect the node, an administrator crafts this recipe: 53 | 54 | ```ruby 55 | node.default['openssl']['restart_services'] = ['stats_collector'] 56 | 57 | # other recipe code here... 58 | service 'stats_collector' do 59 | action [:enable, :start] 60 | end 61 | 62 | include_recipe 'openssl::upgrade' 63 | ``` 64 | 65 | When executed, this recipe will ensure that openssl is upgraded to the latest version, and that the `stats_collector` service is restarted to pick up the latest security fixes released in the openssl package. 66 | 67 | ## Libraries 68 | 69 | There are two mixins packaged with this cookbook. 70 | 71 | ### random_password (`OpenSSLCookbook::RandomPassword`) 72 | 73 | The `RandomPassword` mixin can be used to generate secure random passwords in Chef cookbooks, usually for assignment to a variable or an attribute. `random_password` uses Ruby's SecureRandom library and is customizable. 74 | 75 | #### Example Usage 76 | 77 | ```ruby 78 | Chef::Recipe.send(:include, OpenSSLCookbook::RandomPassword) 79 | node.normal['my_secure_attribute'] = random_password 80 | node.normal_unless['my_secure_attribute'] = random_password 81 | node.normal['my_secure_attribute'] = random_password(length: 50) 82 | node.normal['my_secure_attribute'] = random_password(length: 50, mode: :base64) 83 | node.normal['my_secure_attribute'] = random_password(length: 50, mode: :base64, encoding: 'ASCII') 84 | ``` 85 | 86 | Note that node attributes are widely accessible. Storing unencrypted passwords in node attributes, as in this example, carries risk. 87 | 88 | ## Resources 89 | 90 | ### openssl_x509_certificate 91 | 92 | This resource generates signed or self-signed, PEM-formatted x509 certificates. If no existing key is specified, the resource will automatically generate a passwordless key with the certificate. If a CA private key and certificate are provided, the certificate will be signed with them. 93 | 94 | Note: This resource was renamed from openssl_x509 to openssl_x509_certificate. The legacy name will continue to function, but cookbook code should be updated for the new resource name. 95 | 96 | #### Properties 97 | 98 | Name | Type | Description 99 | ------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 100 | `path` | String (Optional) | Optional path to write the file to if you'd like to specify it here instead of in the resource name 101 | `common_name` | String (Optional) | Value for the `CN` certificate field. 102 | `org` | String (Optional) | Value for the `O` certificate field. 103 | `org_unit` | String (Optional) | Value for the `OU` certificate field. 104 | `city` | String (Optional) | Value for the `L` certificate field. 105 | `state` | String (Optional) | Value for the `ST` certificate field. 106 | `country` | String (Optional) | Value for the `C` ssl field. 107 | `email` | String (Optional) | Value for the `email` ssl field. 108 | `expire` | Integer (Optional) | Value representing the number of days from _now_ through which the issued certificate cert will remain valid. The certificate will expire after this period. _Default: 365 109 | `extensions` | Hash (Optional) | Hash of X509 Extensions entries, in format `{ 'keyUsage' => { 'values' => %w( keyEncipherment digitalSignature), 'critical' => true } }` _Default: empty_ 110 | `subject_alt_name` | Array (Optional) | Array of _Subject Alternative Name_ entries, in format `DNS:example.com` or `IP:1.2.3.4` _Default: empty_ 111 | `key_file` | String (Optional) | The path to a certificate key file on the filesystem. If the `key_file` property is specified, the resource will attempt to source a key from this location. If no key file is found, the resource will generate a new key file at this location. If the `key_file` property is not specified, the resource will generate a key file in the same directory as the generated certificate, with the same name as the generated certificate. 112 | `key_pass` | String (Optional) | The passphrase for an existing key's passphrase 113 | `key_type` | String (Optional) | The desired type of the generated key (rsa or ec). _Default: rsa_ 114 | `key_length` | Integer (Optional) | The desired Bit Length of the generated key (if key_type is equal to 'rsa'). _Default: 2048_ 115 | `key_curve` | String (Optional) | The desired curve of the generated key (if key_type is equal to 'ec'). Run `openssl ecparam -list_curves` to see available options. _Default: prime256v1_ 116 | `csr_file` | String (Optional) | The path to a X509 Certificate Request (CSR) on the filesystem. If the `csr_file` property is specified, the resource will attempt to source a CSR from this location. If no CSR file is found, the resource will generate a Self-Signed Certificate and the certificate fields must be specified (common_name at last). 117 | `ca_cert_file` | String (Optional) | The path to the CA X509 Certificate on the filesystem. If the `ca_cert_file` property is specified, the `ca_key_file` property must also be specified, the certificate will be signed with them. 118 | `ca_key_file` | String (Optional) | The path to the CA private key on the filesystem. If the `ca_key_file` property is specified, the `ca_cert_file' property must also be specified, the certificate will be signed with them. 119 | `ca_key_pass` | String (Optional) | The passphrase for CA private key's passphrase 120 | `owner` | String (optional) | The owner of all files created by the resource. 121 | `group` | String (optional) | The group of all files created by the resource. 122 | `mode` | String or Integer (Optional) | The permission mode of all files created by the resource. 123 | 124 | #### Example Usage 125 | 126 | In this example, an administrator wishes to create a self-signed x509 certificate for use with a web server. In order to create the certificate, the administrator crafts this recipe: 127 | 128 | ```ruby 129 | openssl_x509 '/etc/httpd/ssl/mycert.pem' do 130 | common_name 'www.f00bar.com' 131 | org 'Foo Bar' 132 | org_unit 'Lab' 133 | country 'US' 134 | end 135 | ``` 136 | 137 | When executed, this recipe will generate a key certificate at `/etc/httpd/ssl/mycert.key`. It will then use that key to generate a new certificate file at `/etc/httpd/ssl/mycert.pem`. 138 | 139 | In this example, an administrator wishes to create a x509 certificate signed with a CA certificate and key. In order to create the certificate, the administrator crafts this recipe: 140 | 141 | ```ruby 142 | openssl_x509_certificate '/etc/ssl_test/my_signed_cert.crt' do 143 | common_name 'www.f00bar.com' 144 | ca_key_file '/etc/ssl_test/my_ca.key' 145 | ca_cert_file '/etc/ssl_test/my_ca.crt' 146 | expire 365 147 | extensions( 148 | 'keyUsage' => { 149 | 'values' => %w( 150 | keyEncipherment 151 | digitalSignature), 152 | 'critical' => true, 153 | }, 154 | 'extendedKeyUsage' => { 155 | 'values' => %w(serverAuth), 156 | 'critical' => false, 157 | } 158 | ) 159 | subject_alt_name ['IP:127.0.0.1', 'DNS:localhost.localdomain'] 160 | end 161 | ``` 162 | 163 | When executed, this recipe will generate a key certificate at `/etc/ssl_test/my_signed_cert.key`. It will then use that key to generate a CSR and signed it with `my_ca.key/my_ca.crt`. A new certificate file at `/etc/ssl_test/my_signed_cert.cert` will be created as a result. 164 | 165 | 166 | ### openssl_x509_request 167 | 168 | This resource generates PEM-formatted x509 certificates requests. If no existing key is specified, the resource will automatically generate a passwordless key with the certificate. 169 | 170 | #### Properties 171 | 172 | Name | Type | Description 173 | --------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- 174 | `path` | String (Optional) | Optional path to write the file to if you'd like to specify it here instead of in the resource name 175 | `common_name` | String (Required) | Value for the `CN` certificate field. 176 | `org` | String (Optional) | Value for the `O` certificate field. 177 | `org_unit` | String (Optional) | Value for the `OU` certificate field. 178 | `city` | String (Optional) | Value for the `L` certificate field. 179 | `state` | String (Optional) | Value for the `ST` certificate field. 180 | `country` | String (Optional) | Value for the `C` ssl field. 181 | `email` | String (Optional) | Value for the `email` ssl field. 182 | `key_file` | String (Optional) | The path to a certificate key file on the filesystem. If the `key_file` property is specified, the resource will attempt to source a key from this location. If no key file is found, the resource will generate a new key file at this location. If the `key_file` property is not specified, the resource will generate a key file in the same directory as the generated certificate, with the same name as the generated certificate. 183 | `key_pass` | String (Optional) | The passphrase for an existing key's passphrase 184 | `key_type` | String (Optional) | The desired type of the generated key (rsa or ec). _Default: ec_ 185 | `key_length` | Integer (Optional) | The desired Bit Length of the generated key (if key_type is equal to 'rsa'). _Default: 2048_ 186 | `key_curve` | String (Optional) | The desired curve of the generated key (if key_type is equal to 'ec'). Run `openssl ecparam -list_curves` to see available options. _Default: prime256v1 187 | `owner` | String (optional) | The owner of all files created by the resource. 188 | `group` | String (optional) | The group of all files created by the resource. 189 | `mode` | String or Integer (Optional) | The permission mode of all files created by the resource. 190 | 191 | #### Example Usage 192 | 193 | In this example, an administrator wishes to create a x509 CRL. In order to create the CRL, the administrator crafts this recipe: 194 | 195 | ```ruby 196 | openssl_x509_request '/etc/ssl_test/my_ec_request.csr' do 197 | common_name 'myecrequest.example.com' 198 | org 'Test Kitchen Example' 199 | org_unit 'Kitchens' 200 | country 'UK' 201 | end 202 | ``` 203 | 204 | When executed, this recipe will generate a key certificate at `/etc/httpd/ssl/my_ec_request.key`. It will then use that key to generate a new csr file at `/etc/ssl_test/my_ec_request.csr`. 205 | 206 | ### openssl_x509_crl 207 | 208 | This resource generates PEM-formatted x509 CRL. 209 | 210 | #### Properties 211 | 212 | Name | Type | Description 213 | --------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- 214 | `path` | String (Optional) | Optional path to write the file to if you'd like to specify it here instead of in the resource name 215 | `serial_to_revoke` | String or Integer(Optional) | Serial of the X509 Certificate to revoke 216 | `revocation_reason` | String or Integer(Optional) | [Reason of the revocation]((https://en.wikipedia.org/wiki/Certificate_revocation_list#Reasons_for_revocation)) _Default: 0_ 217 | `expire` | Integer (Optional) | Value representing the number of days from _now_ through which the issued CRL will remain valid. The CRL will expire after this period. _Default: 8_ 218 | `renewal_threshold` | Integer (Optional) | Number of days before the expiration. It this threshold is reached, the CRL will be renewed _Default: 1_ 219 | `ca_cert_file` | String (Required) | The path to the CA X509 Certificate on the filesystem. If the `ca_cert_file` property is specified, the `ca_key_file` property must also be specified, the CRL will be signed with them. 220 | `ca_key_file` | String (Required) | The path to the CA private key on the filesystem. If the `ca_key_file` property is specified, the `ca_cert_file' property must also be specified, the CRL will be signed with them. 221 | `ca_key_pass` | String (Optional) | The passphrase for CA private key's passphrase 222 | `owner` | String (optional) | The owner of all files created by the resource. 223 | `group` | String (optional) | The group of all files created by the resource. 224 | `mode` | String or Integer (Optional) | The permission mode of all files created by the resource. 225 | 226 | 227 | #### Example Usage 228 | 229 | In this example, an administrator wishes to create an empty X509 CRL. In order to create the CRL, the administrator crafts this recipe: 230 | 231 | ```ruby 232 | openssl_x509_crl '/etc/ssl_test/my_ca.crl' do 233 | ca_cert_file '/etc/ssl_test/my_ca.crt' 234 | ca_key_file '/etc/ssl_test/my_ca.key' 235 | end 236 | ``` 237 | 238 | When executed, this recipe will generate a new CRL file at `/etc/ssl_test/my_ca.crl`. 239 | 240 | In this example, an administrator wishes to revoke a certificate in an existing X509 CRL. 241 | 242 | ```ruby 243 | openssl_x509_crl '/etc/ssl_test/my_ca.crl' do 244 | ca_cert_file '/etc/ssl_test/my_ca.crt' 245 | ca_key_file '/etc/ssl_test/my_ca.key' 246 | serial_to_revoke C7BCB6602A2E4251EF4E2827A228CB52BC0CEA2F 247 | end 248 | ``` 249 | 250 | ### openssl_dhparam 251 | 252 | This resource generates dhparam.pem files. If a valid dhparam.pem file is found at the specified location, no new file will be created. If a file is found at the specified location but it is not a valid dhparam file, it will be overwritten. 253 | 254 | #### Properties 255 | 256 | Name | Type | Description 257 | ------------ | ---------------------------- | --------------------------------------------------------------------------------------------------- 258 | `path` | String (Optional) | Optional path to write the file to if you'd like to specify it here instead of in the resource name 259 | `key_length` | Integer (Optional) | The desired Bit Length of the generated key. _Default: 2048_ 260 | `generator` | Integer (Optional) | The desired Diffie-Hellmann generator. Can be _2_ or _5_. 261 | `owner` | String (optional) | The owner of all files created by the resource. 262 | `group` | String (optional) | The group of all files created by the resource. 263 | `mode` | String or Integer (Optional) | The permission mode of all files created by the resource. _Default: "0640"_ 264 | 265 | #### Example Usage 266 | 267 | In this example, an administrator wishes to create a dhparam.pem file for use with a web server. In order to create the .pem file, the administrator crafts this recipe: 268 | 269 | ```ruby 270 | openssl_dhparam '/etc/httpd/ssl/dhparam.pem' do 271 | key_length 2048 272 | generator 2 273 | end 274 | ``` 275 | 276 | When executed, this recipe will generate a dhparam file at `/etc/httpd/ssl/dhparam.pem`. 277 | 278 | ### openssl_rsa_private_key 279 | 280 | This resource generates rsa private key files. If a valid rsa key file can be opened at the specified location, no new file will be created. If the RSA key file cannot be opened, either because it does not exist or because the password to the RSA key file does not match the password in the recipe, it will be overwritten. 281 | 282 | Note: This resource was renamed from openssl_rsa_key to openssl_rsa_private_key. The legacy name will continue to function, but cookbook code should be updated for the new resource name. 283 | 284 | #### Properties 285 | 286 | Name | Type | Description 287 | ------------ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- 288 | `path` | String (Optional) | Optional path to write the file to if you'd like to specify it here instead of in the resource name 289 | `key_length` | Integer (Optional) | The desired Bit Length of the generated key. _Default: 2048_ 290 | `key_cipher` | String (Optional) | The designed cipher to use when generating your key. Run `openssl list-cipher-algorithms` to see available options. _Default: des3_ 291 | `key_pass` | String (Optional) | The desired passphrase for the key. 292 | `owner` | String (optional) | The owner of all files created by the resource. 293 | `group` | String (optional) | The group of all files created by the resource. 294 | `mode` | String or Integer (Optional) | The permission mode of all files created by the resource. _Default: "0640"_ 295 | `force` | true/false (Optional) | Force creating the key even if the existing key exists. _Default: false_ 296 | 297 | #### Example Usage 298 | 299 | In this example, an administrator wishes to create a new RSA private key file in order to generate other certificates and public keys. In order to create the key file, the administrator crafts this recipe: 300 | 301 | ```ruby 302 | openssl_rsa_private_key '/etc/httpd/ssl/server.key' do 303 | key_length 2048 304 | end 305 | ``` 306 | 307 | When executed, this recipe will generate a passwordless RSA key file at `/etc/httpd/ssl/server.key`. 308 | 309 | ### openssl_rsa_public_key 310 | 311 | This resource generates rsa public key files given a private key. 312 | 313 | #### Properties 314 | 315 | Name | Type | Description 316 | --------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- 317 | `path` | String (Optional) | Optional path to write the file to if you'd like to specify it here instead of in the resource name 318 | `private_key_path` | String (Required unless private_key_content used) | The path to the private key to generate the public key from 319 | `private_key_content` | String (Required unless private_key_path used) | The content of the private key including new lines. Used if you don't want to write a private key to disk and use `private_key_path`. 320 | `private_key_pass` | String (Optional) | The passphrase of the provided private key 321 | `owner` | String (optional) | The owner of all files created by the resource. 322 | `group` | String (optional) | The group of all files created by the resource. 323 | `mode` | String or Integer (Optional) | The permission mode of all files created by the resource. _Default: "0640"_ 324 | 325 | **Note**: To use `private_key_content` the private key string must be properly formatted including new lines. The easiest way to get the right string is to run the following from irb (/opt/chefdk/embedded/bin/irb from ChefDK) 326 | 327 | ```ruby 328 | File.read('/foo/bar/private.pem') 329 | ``` 330 | 331 | #### Example Usage 332 | 333 | ```ruby 334 | openssl_rsa_public_key '/etc/foo/something.pub' do 335 | priv_key_path '/etc/foo/something.pem' 336 | end 337 | ``` 338 | 339 | ### openssl_ec_private_key 340 | 341 | This resource generates ec private key files. If a valid ec key file can be opened at the specified location, no new file will be created. If the EC key file cannot be opened, either because it does not exist or because the password to the EC key file does not match the password in the recipe, it will be overwritten. 342 | 343 | #### Properties 344 | 345 | Name | Type | Description 346 | ------------ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- 347 | `path` | String (Optional) | Optional path to write the file to if you'd like to specify it here instead of in the resource name 348 | `key_curve` | String (Optional) | The desired curve of the generated key. Run `openssl ecparam -list_curves` to see available options. _Default: prime256v1 349 | `key_cipher` | String (Optional) | The designed cipher to use when generating your key. Run `openssl list-cipher-algorithms` to see available options. _Default: des3_ 350 | `key_pass` | String (Optional) | The desired passphrase for the key. 351 | `owner` | String (optional) | The owner of all files created by the resource. 352 | `group` | String (optional) | The group of all files created by the resource. 353 | `mode` | String or Integer (Optional) | The permission mode of all files created by the resource. _Default: "0640"_ 354 | `force` | true/false (Optional) | Force creating the key even if the existing key exists. _Default: false_ 355 | 356 | #### Example Usage 357 | 358 | In this example, an administrator wishes to create a new EC private key file in order to generate other certificates and public keys. In order to create the key file, the administrator crafts this recipe: 359 | 360 | ```ruby 361 | openssl_ec_private_key '/etc/httpd/ssl/server.key' do 362 | key_curve "prime256v1' 363 | end 364 | ``` 365 | 366 | When executed, this recipe will generate a passwordless EC key file at `/etc/httpd/ssl/server.key`. 367 | 368 | ### openssl_ec_public_key 369 | 370 | This resource generates ec public key files given a private key. 371 | 372 | #### Properties 373 | 374 | Name | Type | Description 375 | --------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- 376 | `path` | String (Optional) | Optional path to write the file to if you'd like to specify it here instead of in the resource name 377 | `private_key_path` | String (Required unless private_key_content used) | The path to the private key to generate the public key from 378 | `private_key_content` | String (Required unless private_key_path used) | The content of the private key including new lines. Used if you don't want to write a private key to disk and use `private_key_path`. 379 | `private_key_pass` | String (Optional) | The passphrase of the provided private key 380 | `owner` | String (optional) | The owner of all files created by the resource. _Default: "root"_ 381 | `group` | String (optional) | The group of all files created by the resource. _Default: "root or wheel depending on platform"_ 382 | `mode` | String or Integer (Optional) | The permission mode of all files created by the resource. _Default: "0640"_ 383 | 384 | **Note**: To use `private_key_content` the private key string must be properly formatted including new lines. The easiest way to get the right string is to run the following from irb (/opt/chefdk/embedded/bin/irb from ChefDK) 385 | 386 | ```ruby 387 | File.read('/foo/bar/private.pem') 388 | ``` 389 | 390 | #### Example Usage 391 | 392 | ```ruby 393 | openssl_ec_public_key '/etc/foo/something.pub' do 394 | priv_key_path '/etc/foo/something.pem' 395 | end 396 | ``` 397 | 398 | ## Maintainers 399 | 400 | This cookbook is maintained by Chef's Community Cookbook Engineering team. Our goal is to improve cookbook quality and to aid the community in contributing to cookbooks. To learn more about our team, process, and design goals see our [team documentation](https://github.com/chef-cookbooks/community_cookbook_documentation/blob/master/COOKBOOK_TEAM.MD). To learn more about contributing to cookbooks like this see our [contributing documentation](https://github.com/chef-cookbooks/community_cookbook_documentation/blob/master/CONTRIBUTING.MD), or if you have general questions about this cookbook come chat with us in #cookbok-engineering on the [Chef Community Slack](http://community-slack.chef.io/) 401 | 402 | ## License 403 | 404 | **Copyright:** 2009-2018, Chef Software, Inc. 405 | 406 | ``` 407 | Licensed under the Apache License, Version 2.0 (the "License"); 408 | you may not use this file except in compliance with the License. 409 | You may obtain a copy of the License at 410 | 411 | http://www.apache.org/licenses/LICENSE-2.0 412 | 413 | Unless required by applicable law or agreed to in writing, software 414 | distributed under the License is distributed on an "AS IS" BASIS, 415 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 416 | See the License for the specific language governing permissions and 417 | limitations under the License. 418 | ``` 419 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | Please refer to 2 | https://github.com/chef-cookbooks/community_cookbook_documentation/blob/master/TESTING.MD 3 | -------------------------------------------------------------------------------- /attributes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook:: openssl 3 | # Attributes:: default 4 | # 5 | # Copyright:: 2014-2017, Chef Software, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | default['openssl']['restart_services'] = [] 21 | -------------------------------------------------------------------------------- /chefignore: -------------------------------------------------------------------------------- 1 | # Put files/directories that should be ignored in this file when uploading 2 | # to a chef-server or supermarket. 3 | # Lines that start with '# ' are comments. 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | Icon? 9 | nohup.out 10 | ehthumbs.db 11 | Thumbs.db 12 | 13 | # SASS # 14 | ######## 15 | .sass-cache 16 | 17 | # EDITORS # 18 | ########### 19 | \#* 20 | .#* 21 | *~ 22 | *.sw[a-z] 23 | *.bak 24 | REVISION 25 | TAGS* 26 | tmtags 27 | *_flymake.* 28 | *_flymake 29 | *.tmproj 30 | .project 31 | .settings 32 | mkmf.log 33 | 34 | ## COMPILED ## 35 | ############## 36 | a.out 37 | *.o 38 | *.pyc 39 | *.so 40 | *.com 41 | *.class 42 | *.dll 43 | *.exe 44 | */rdoc/ 45 | 46 | # Testing # 47 | ########### 48 | .watchr 49 | .rspec 50 | spec/* 51 | spec/fixtures/* 52 | test/* 53 | features/* 54 | examples/* 55 | Guardfile 56 | Procfile 57 | kitchen* 58 | .rubocop.yml 59 | spec/* 60 | Rakefile 61 | .travis.yml 62 | .foodcritic 63 | .codeclimate.yml 64 | 65 | # SCM # 66 | ####### 67 | .git 68 | */.git 69 | .gitignore 70 | .gitmodules 71 | .gitconfig 72 | .gitattributes 73 | .svn 74 | */.bzr/* 75 | */.hg/* 76 | */.svn/* 77 | 78 | # Berkshelf # 79 | ############# 80 | Berksfile 81 | Berksfile.lock 82 | cookbooks/* 83 | tmp 84 | 85 | # Policyfile # 86 | ############## 87 | Policyfile.rb 88 | Policyfile.lock.json 89 | 90 | # Cookbooks # 91 | ############# 92 | CONTRIBUTING* 93 | CHANGELOG* 94 | TESTING* 95 | 96 | # Strainer # 97 | ############ 98 | Colanderfile 99 | Strainerfile 100 | .colander 101 | .strainer 102 | 103 | # Vagrant # 104 | ########### 105 | .vagrant 106 | Vagrantfile 107 | -------------------------------------------------------------------------------- /kitchen.dokken.yml: -------------------------------------------------------------------------------- 1 | driver: 2 | name: dokken 3 | privileged: true # because Docker and SystemD/Upstart 4 | chef_version: <%= ENV['CHEF_VERSION'] || 'latest' %> 5 | 6 | transport: 7 | name: dokken 8 | 9 | provisioner: 10 | name: dokken 11 | deprecations_as_errors: true 12 | 13 | verifier: 14 | name: inspec 15 | 16 | platforms: 17 | - name: amazonlinux 18 | driver: 19 | image: dokken/amazonlinux 20 | pid_one_command: /sbin/init 21 | intermediate_instructions: 22 | - RUN /usr/bin/yum -y install diffutils 23 | 24 | - name: debian-8 25 | driver: 26 | image: dokken/debian-8 27 | pid_one_command: /bin/systemd 28 | intermediate_instructions: 29 | - RUN /usr/bin/apt-get update 30 | 31 | - name: debian-9 32 | driver: 33 | image: dokken/debian-9 34 | pid_one_command: /bin/systemd 35 | intermediate_instructions: 36 | - RUN /usr/bin/apt-get update 37 | 38 | - name: centos-6 39 | driver: 40 | image: dokken/centos-6 41 | pid_one_command: /sbin/init 42 | 43 | - name: centos-7 44 | driver: 45 | image: dokken/centos-7 46 | pid_one_command: /usr/lib/systemd/systemd 47 | 48 | - name: fedora-latest 49 | driver: 50 | image: dokken/fedora-latest 51 | pid_one_command: /usr/lib/systemd/systemd 52 | 53 | - name: ubuntu-16.04 54 | driver: 55 | image: dokken/ubuntu-16.04 56 | pid_one_command: /bin/systemd 57 | intermediate_instructions: 58 | - RUN /usr/bin/apt-get update 59 | 60 | - name: ubuntu-18.04 61 | driver: 62 | image: dokken/ubuntu-18.04 63 | pid_one_command: /bin/systemd 64 | intermediate_instructions: 65 | - RUN /usr/bin/apt-get update 66 | 67 | - name: opensuse-leap 68 | driver: 69 | image: dokken/opensuse-leap-42 70 | pid_one_command: /bin/systemd 71 | -------------------------------------------------------------------------------- /kitchen.yml: -------------------------------------------------------------------------------- 1 | driver: 2 | name: vagrant 3 | 4 | provisioner: 5 | name: chef_zero 6 | deprecations_as_errors: true 7 | 8 | verifier: 9 | name: inspec 10 | 11 | platforms: 12 | - name: amazonlinux 13 | driver_config: 14 | box: mvbcoding/awslinux 15 | - name: centos-6 16 | - name: centos-7 17 | - name: debian-8 18 | - name: debian-9 19 | - name: fedora-28 20 | - name: freebsd-11 21 | - name: opensuse-leap-42 22 | - name: sles-11-sp2 23 | driver: 24 | box: chef/sles-11-sp2-x86_64 # private box 25 | - name: sles-12-sp1 26 | driver: 27 | box: chef/sles-12-sp1-x86_64 # private box 28 | - name: solaris-11.3 29 | driver: 30 | box: chef/solaris-11.3 # private box 31 | - name: ubuntu-14.04 32 | - name: ubuntu-16.04 33 | - name: ubuntu-18.04 34 | 35 | suites: 36 | - name: resources 37 | run_list: 38 | - recipe[test::resources] 39 | - name: upgrade 40 | run_list: 41 | - recipe[test::upgrade] 42 | -------------------------------------------------------------------------------- /libraries/helpers.rb: -------------------------------------------------------------------------------- 1 | # 2 | # License:: Apache License, Version 2.0 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | module OpenSSLCookbook 18 | # Helper functions for the OpenSSL cookbook. 19 | module Helpers 20 | def self.included(_base) 21 | require 'openssl' unless defined?(::OpenSSL) 22 | end 23 | 24 | # determine the key filename from the cert filename 25 | # @param [String] cert_filename the path to the certfile 26 | # @return [String] the path to the keyfile 27 | def get_key_filename(cert_filename) 28 | cert_file_path, cert_filename = ::File.split(cert_filename) 29 | cert_filename = ::File.basename(cert_filename, ::File.extname(cert_filename)) 30 | cert_file_path + ::File::SEPARATOR + cert_filename + '.key' 31 | end 32 | 33 | # is the key length a valid key length 34 | # @param [Integer] number 35 | # @return [Boolean] is length valid 36 | def key_length_valid?(number) 37 | number >= 1024 && (number & (number - 1) == 0) 38 | end 39 | 40 | # validate a dhparam file from path 41 | # @param [String] dhparam_pem_path the path to the pem file 42 | # @return [Boolean] is the key valid 43 | def dhparam_pem_valid?(dhparam_pem_path) 44 | # Check if the dhparam.pem file exists 45 | # Verify the dhparam.pem file contains a key 46 | return false unless ::File.exist?(dhparam_pem_path) 47 | dhparam = ::OpenSSL::PKey::DH.new File.read(dhparam_pem_path) 48 | dhparam.params_ok? 49 | end 50 | 51 | # given either a key file path or key file content see if it's actually 52 | # a private key 53 | # @param [String] key_file the path to the keyfile or the key contents 54 | # @param [String] key_password optional password to the keyfile 55 | # @return [Boolean] is the key valid? 56 | def priv_key_file_valid?(key_file, key_password = nil) 57 | # if the file exists try to read the content 58 | # if not assume we were passed the key and set the string to the content 59 | key_content = ::File.exist?(key_file) ? File.read(key_file) : key_file 60 | 61 | begin 62 | key = ::OpenSSL::PKey.read key_content, key_password 63 | rescue ::OpenSSL::PKey::PKeyError, ArgumentError 64 | return false 65 | end 66 | 67 | if key.is_a?(::OpenSSL::PKey::EC) 68 | key.private_key? 69 | else 70 | key.private? 71 | end 72 | end 73 | 74 | # given a crl file path see if it's actually a crl 75 | # @param [String] crl_file the path to the crlfile 76 | # @return [Boolean] is the key valid? 77 | def crl_file_valid?(crl_file) 78 | begin 79 | ::OpenSSL::X509::CRL.new ::File.read(crl_file) 80 | rescue ::OpenSSL::X509::CRLError, Errno::ENOENT 81 | return false 82 | end 83 | true 84 | end 85 | 86 | # check is a serial given is revoked in a crl given 87 | # @param [OpenSSL::X509::CRL] crl X509 CRL to check 88 | # @param [String, Integer] serial X509 Certificate Serial Number 89 | # @return [true, false] 90 | def serial_revoked?(crl, serial) 91 | raise TypeError, 'crl must be a Ruby OpenSSL::X509::CRL object' unless crl.is_a?(::OpenSSL::X509::CRL) 92 | raise TypeError, 'serial must be a Ruby String or Integer object' unless serial.is_a?(String) || serial.is_a?(Integer) 93 | 94 | serial_to_verify = if serial.is_a?(String) 95 | serial.to_i(16) 96 | else 97 | serial 98 | end 99 | status = false 100 | crl.revoked.each do |revoked| 101 | status = true if revoked.serial == serial_to_verify 102 | end 103 | status 104 | end 105 | 106 | # generate a dhparam file 107 | # @param [String] key_length the length of the key 108 | # @param [Integer] generator the dhparam generator to use 109 | # @return [OpenSSL::PKey::DH] 110 | def gen_dhparam(key_length, generator) 111 | raise ArgumentError, 'Key length must be a power of 2 greater than or equal to 1024' unless key_length_valid?(key_length) 112 | raise TypeError, 'Generator must be an integer' unless generator.is_a?(Integer) 113 | 114 | ::OpenSSL::PKey::DH.new(key_length, generator) 115 | end 116 | 117 | # generate an RSA private key given key length 118 | # @param [Integer] key_length the key length of the private key 119 | # @return [OpenSSL::PKey::DH] 120 | def gen_rsa_priv_key(key_length) 121 | raise ArgumentError, 'Key length must be a power of 2 greater than or equal to 1024' unless key_length_valid?(key_length) 122 | 123 | ::OpenSSL::PKey::RSA.new(key_length) 124 | end 125 | 126 | # generate pem format of the public key given a private key 127 | # @param [String] priv_key either the contents of the private key or the path to the file 128 | # @param [String] priv_key_password optional password for the private key 129 | # @return [String] pem format of the public key 130 | def gen_rsa_pub_key(priv_key, priv_key_password = nil) 131 | # if the file exists try to read the content 132 | # if not assume we were passed the key and set the string to the content 133 | key_content = ::File.exist?(priv_key) ? File.read(priv_key) : priv_key 134 | key = ::OpenSSL::PKey::RSA.new key_content, priv_key_password 135 | key.public_key.to_pem 136 | end 137 | 138 | # generate a pem file given a cipher, key, an optional key_password 139 | # @param [OpenSSL::PKey::RSA] rsa_key the private key object 140 | # @param [String] key_password the password for the private key 141 | # @param [String] key_cipher the cipher to use 142 | # @return [String] pem contents 143 | def encrypt_rsa_key(rsa_key, key_password, key_cipher) 144 | raise TypeError, 'rsa_key must be a Ruby OpenSSL::PKey::RSA object' unless rsa_key.is_a?(::OpenSSL::PKey::RSA) 145 | raise TypeError, 'key_password must be a string' unless key_password.is_a?(String) 146 | raise TypeError, 'key_cipher must be a string' unless key_cipher.is_a?(String) 147 | raise ArgumentError, 'Specified key_cipher is not available on this system' unless ::OpenSSL::Cipher.ciphers.include?(key_cipher) 148 | 149 | cipher = ::OpenSSL::Cipher.new(key_cipher) 150 | rsa_key.to_pem(cipher, key_password) 151 | end 152 | 153 | # generate an ec private key given curve type 154 | # @param [String] curve the kind of curve to use 155 | # @return [OpenSSL::PKey::DH] 156 | def gen_ec_priv_key(curve) 157 | raise TypeError, 'curve must be a string' unless curve.is_a?(String) 158 | raise ArgumentError, 'Specified curve is not available on this system' unless curve == 'prime256v1' || curve == 'secp384r1' || curve == 'secp521r1' 159 | ::OpenSSL::PKey::EC.new(curve).generate_key 160 | end 161 | 162 | # generate pem format of the public key given a private key 163 | # @param [String] priv_key either the contents of the private key or the path to the file 164 | # @param [String] priv_key_password optional password for the private key 165 | # @return [String] pem format of the public key 166 | def gen_ec_pub_key(priv_key, priv_key_password = nil) 167 | # if the file exists try to read the content 168 | # if not assume we were passed the key and set the string to the content 169 | key_content = ::File.exist?(priv_key) ? File.read(priv_key) : priv_key 170 | key = ::OpenSSL::PKey::EC.new key_content, priv_key_password 171 | 172 | # Get curve type (prime256v1...) 173 | group = ::OpenSSL::PKey::EC::Group.new(key.group.curve_name) 174 | # Get Generator point & public point (priv * generator) 175 | generator = group.generator 176 | pub_point = generator.mul(key.private_key) 177 | key.public_key = pub_point 178 | 179 | # Public Key in pem 180 | public_key = ::OpenSSL::PKey::EC.new 181 | public_key.group = group 182 | public_key.public_key = pub_point 183 | public_key.to_pem 184 | end 185 | 186 | # generate a pem file given a cipher, key, an optional key_password 187 | # @param [OpenSSL::PKey::EC] ec_key the private key object 188 | # @param [String] key_password the password for the private key 189 | # @param [String] key_cipher the cipher to use 190 | # @return [String] pem contents 191 | def encrypt_ec_key(ec_key, key_password, key_cipher) 192 | raise TypeError, 'ec_key must be a Ruby OpenSSL::PKey::EC object' unless ec_key.is_a?(::OpenSSL::PKey::EC) 193 | raise TypeError, 'key_password must be a string' unless key_password.is_a?(String) 194 | raise TypeError, 'key_cipher must be a string' unless key_cipher.is_a?(String) 195 | raise ArgumentError, 'Specified key_cipher is not available on this system' unless ::OpenSSL::Cipher.ciphers.include?(key_cipher) 196 | 197 | cipher = ::OpenSSL::Cipher.new(key_cipher) 198 | ec_key.to_pem(cipher, key_password) 199 | end 200 | 201 | # generate a csr pem file given a subject and a private key 202 | # @param [OpenSSL::X509::Name] subject the x509 subject object 203 | # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] key the private key object 204 | # @return [OpenSSL::X509::Request] 205 | def gen_x509_request(subject, key) 206 | raise TypeError, 'subject must be a Ruby OpenSSL::X509::Name object' unless subject.is_a?(::OpenSSL::X509::Name) 207 | raise TypeError, 'key must be a Ruby OpenSSL::PKey::EC or a Ruby OpenSSL::PKey::RSA object' unless key.is_a?(::OpenSSL::PKey::EC) || key.is_a?(::OpenSSL::PKey::RSA) 208 | 209 | request = ::OpenSSL::X509::Request.new 210 | request.version = 0 211 | request.subject = subject 212 | request.public_key = key 213 | 214 | # Chef 12 backward compatibility 215 | ::OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?) 216 | 217 | request.sign(key, ::OpenSSL::Digest::SHA256.new) 218 | request 219 | end 220 | 221 | # generate an array of X509 Extensions given a hash of extensions 222 | # @param [Hash] extensions hash of extensions 223 | # @return [Array] 224 | def gen_x509_extensions(extensions) 225 | raise TypeError, 'extensions must be a Ruby Hash object' unless extensions.is_a?(Hash) 226 | 227 | exts = [] 228 | extensions.each do |ext_name, ext_prop| 229 | raise TypeError, "#{ext_name} must contain a Ruby Hash" unless ext_prop.is_a?(Hash) 230 | raise ArgumentError, "keys in #{ext_name} must be 'values' and 'critical'" unless ext_prop.key?('values') && ext_prop.key?('critical') 231 | raise TypeError, "the key 'values' must contain a Ruby Arrays" unless ext_prop['values'].is_a?(Array) 232 | raise TypeError, "the key 'critical' must be a Ruby Boolean true/false" unless ext_prop['critical'].is_a?(TrueClass) || ext_prop['critical'].is_a?(FalseClass) 233 | 234 | exts << ::OpenSSL::X509::ExtensionFactory.new.create_extension(ext_name, ext_prop['values'].join(','), ext_prop['critical']) 235 | end 236 | exts 237 | end 238 | 239 | # generate a random Serial 240 | # @return [Integer] 241 | def gen_serial 242 | ::OpenSSL::BN.generate_prime(160) 243 | end 244 | 245 | # generate a Certificate given a X509 request 246 | # @param [OpenSSL::X509::Request] request X509 Certificate Request 247 | # @param [Array] extension Array of X509 Certificate Extension 248 | # @param [Hash] info issuer & validity 249 | # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] key private key to sign with 250 | # @return [OpenSSL::X509::Certificate] 251 | def gen_x509_cert(request, extension, info, key) 252 | raise TypeError, 'request must be a Ruby OpenSSL::X509::Request' unless request.is_a?(::OpenSSL::X509::Request) 253 | raise TypeError, 'extension must be a Ruby Array' unless extension.is_a?(Array) 254 | raise TypeError, 'info must be a Ruby Hash' unless info.is_a?(Hash) 255 | raise TypeError, 'key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object' unless key.is_a?(::OpenSSL::PKey::EC) || key.is_a?(::OpenSSL::PKey::RSA) 256 | 257 | raise ArgumentError, 'info must contain a validity' unless info.key?('validity') 258 | raise TypeError, 'info[\'validity\'] must be a Ruby Integer object' unless info['validity'].is_a?(Integer) 259 | 260 | cert = ::OpenSSL::X509::Certificate.new 261 | ef = ::OpenSSL::X509::ExtensionFactory.new 262 | 263 | cert.serial = gen_serial() 264 | cert.version = 2 265 | cert.subject = request.subject 266 | cert.public_key = request.public_key 267 | cert.not_before = Time.now 268 | cert.not_after = cert.not_before + info['validity'] * 24 * 60 * 60 269 | 270 | if info['issuer'].nil? 271 | cert.issuer = request.subject 272 | ef.issuer_certificate = cert 273 | extension << ef.create_extension('basicConstraints', 'CA:TRUE', true) 274 | else 275 | raise TypeError, 'info[\'issuer\'] must be a Ruby OpenSSL::X509::Certificate object' unless info['issuer'].is_a?(::OpenSSL::X509::Certificate) 276 | cert.issuer = info['issuer'].subject 277 | ef.issuer_certificate = info['issuer'] 278 | end 279 | ef.subject_certificate = cert 280 | ef.config = ::OpenSSL::Config.load(::OpenSSL::Config::DEFAULT_CONFIG_FILE) 281 | 282 | cert.extensions = extension 283 | cert.add_extension ef.create_extension('subjectKeyIdentifier', 'hash') 284 | cert.add_extension ef.create_extension('authorityKeyIdentifier', 285 | 'keyid:always,issuer:always') 286 | 287 | cert.sign(key, ::OpenSSL::Digest::SHA256.new) 288 | cert 289 | end 290 | 291 | # generate a X509 CRL given a CA 292 | # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] ca_private_key private key from the CA 293 | # @param [Hash] info issuer & validity 294 | # @return [OpenSSL::X509::CRL] 295 | def gen_x509_crl(ca_private_key, info) 296 | raise TypeError, 'ca_private_key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object' unless ca_private_key.is_a?(::OpenSSL::PKey::EC) || ca_private_key.is_a?(::OpenSSL::PKey::RSA) 297 | raise TypeError, 'info must be a Ruby Hash' unless info.is_a?(Hash) 298 | 299 | raise ArgumentError, 'info must contain a issuer and a validity' unless info.key?('issuer') && info.key?('validity') 300 | raise TypeError, 'info[\'issuer\'] must be a Ruby OpenSSL::X509::Certificate object' unless info['issuer'].is_a?(::OpenSSL::X509::Certificate) 301 | raise TypeError, 'info[\'validity\'] must be a Ruby Integer object' unless info['validity'].is_a?(Integer) 302 | 303 | crl = ::OpenSSL::X509::CRL.new 304 | ef = ::OpenSSL::X509::ExtensionFactory.new 305 | 306 | crl.version = 1 307 | crl.issuer = info['issuer'].subject 308 | crl.last_update = Time.now 309 | crl.next_update = Time.now + 3600 * 24 * info['validity'] 310 | 311 | ef.config = ::OpenSSL::Config.load(::OpenSSL::Config::DEFAULT_CONFIG_FILE) 312 | ef.issuer_certificate = info['issuer'] 313 | 314 | crl.add_extension ::OpenSSL::X509::Extension.new('crlNumber', ::OpenSSL::ASN1::Integer(1)) 315 | crl.add_extension ef.create_extension('authorityKeyIdentifier', 316 | 'keyid:always,issuer:always') 317 | crl.sign(ca_private_key, ::OpenSSL::Digest::SHA256.new) 318 | crl 319 | end 320 | 321 | # generate the next CRL number available for a X509 CRL given 322 | # @param [OpenSSL::X509::CRL] crl x509 CRL 323 | # @return [Integer] 324 | def get_next_crl_number(crl) 325 | raise TypeError, 'crl must be a Ruby OpenSSL::X509::CRL object' unless crl.is_a?(::OpenSSL::X509::CRL) 326 | crlnum = 1 327 | crl.extensions.each do |e| 328 | crlnum = e.value if e.oid == 'crlNumber' 329 | end 330 | crlnum.to_i + 1 331 | end 332 | 333 | # add a serial given in the crl given 334 | # @param [Hash] revoke_info serial to revoke & revokation reason 335 | # @param [OpenSSL::X509::CRL] crl X509 CRL 336 | # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] ca_private_key private key from the CA 337 | # @param [Hash] info issuer & validity 338 | # @return [OpenSSL::X509::CRL] 339 | def revoke_x509_crl(revoke_info, crl, ca_private_key, info) 340 | raise TypeError, 'revoke_info must be a Ruby Hash oject' unless revoke_info.is_a?(Hash) 341 | raise TypeError, 'crl must be a Ruby OpenSSL::X509::CRL object' unless crl.is_a?(::OpenSSL::X509::CRL) 342 | raise TypeError, 'ca_private_key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object' unless ca_private_key.is_a?(::OpenSSL::PKey::EC) || ca_private_key.is_a?(::OpenSSL::PKey::RSA) 343 | raise TypeError, 'info must be a Ruby Hash' unless info.is_a?(Hash) 344 | 345 | raise ArgumentError, 'revoke_info must contain a serial and a reason' unless revoke_info.key?('serial') && revoke_info.key?('reason') 346 | raise TypeError, 'revoke_info[\'serial\'] must be a Ruby String or Integer object' unless revoke_info['serial'].is_a?(String) || revoke_info['serial'].is_a?(Integer) 347 | raise TypeError, 'revoke_info[\'reason\'] must be a Ruby Integer object' unless revoke_info['reason'].is_a?(Integer) 348 | 349 | raise ArgumentError, 'info must contain a issuer and a validity' unless info.key?('issuer') && info.key?('validity') 350 | raise TypeError, 'info[\'issuer\'] must be a Ruby OpenSSL::X509::Certificate object' unless info['issuer'].is_a?(::OpenSSL::X509::Certificate) 351 | raise TypeError, 'info[\'validity\'] must be a Ruby Integer object' unless info['validity'].is_a?(Integer) 352 | 353 | revoked = ::OpenSSL::X509::Revoked.new 354 | revoked.serial = if revoke_info['serial'].is_a?(String) 355 | revoke_info['serial'].to_i(16) 356 | else 357 | revoke_info['serial'] 358 | end 359 | revoked.time = Time.now 360 | 361 | ext = ::OpenSSL::X509::Extension.new('CRLReason', 362 | ::OpenSSL::ASN1::Enumerated(revoke_info['reason'])) 363 | revoked.add_extension(ext) 364 | crl.add_revoked(revoked) 365 | 366 | crl = renew_x509_crl(crl, ca_private_key, info) 367 | crl 368 | end 369 | 370 | # renew a X509 crl given 371 | # @param [OpenSSL::X509::CRL] crl CRL to renew 372 | # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] ca_private_key private key from the CA 373 | # @param [Hash] info issuer & validity 374 | # @return [OpenSSL::X509::CRL] 375 | def renew_x509_crl(crl, ca_private_key, info) 376 | raise TypeError, 'crl must be a Ruby OpenSSL::X509::CRL object' unless crl.is_a?(::OpenSSL::X509::CRL) 377 | raise TypeError, 'ca_private_key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object' unless ca_private_key.is_a?(::OpenSSL::PKey::EC) || ca_private_key.is_a?(::OpenSSL::PKey::RSA) 378 | raise TypeError, 'info must be a Ruby Hash' unless info.is_a?(Hash) 379 | 380 | raise ArgumentError, 'info must contain a issuer and a validity' unless info.key?('issuer') && info.key?('validity') 381 | raise TypeError, 'info[\'issuer\'] must be a Ruby OpenSSL::X509::Certificate object' unless info['issuer'].is_a?(::OpenSSL::X509::Certificate) 382 | raise TypeError, 'info[\'validity\'] must be a Ruby Integer object' unless info['validity'].is_a?(Integer) 383 | 384 | crl.last_update = Time.now 385 | crl.next_update = crl.last_update + 3600 * 24 * info['validity'] 386 | 387 | ef = ::OpenSSL::X509::ExtensionFactory.new 388 | ef.config = ::OpenSSL::Config.load(::OpenSSL::Config::DEFAULT_CONFIG_FILE) 389 | ef.issuer_certificate = info['issuer'] 390 | 391 | crl.extensions = [ ::OpenSSL::X509::Extension.new('crlNumber', 392 | ::OpenSSL::ASN1::Integer(get_next_crl_number(crl)))] 393 | crl.add_extension ef.create_extension('authorityKeyIdentifier', 394 | 'keyid:always,issuer:always') 395 | crl.sign(ca_private_key, ::OpenSSL::Digest::SHA256.new) 396 | crl 397 | end 398 | end 399 | end 400 | -------------------------------------------------------------------------------- /libraries/random_password.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook:: openssl 3 | # Library:: random_password 4 | # Author:: Seth Vargo 5 | # 6 | # Copyright:: 2015-2017, Seth Vargo 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # rubocop:disable UnusedMethodArgument, Style/RaiseArgs 20 | 21 | module OpenSSLCookbook 22 | module RandomPassword 23 | # Override the included method to require securerandom if it is not defined. 24 | # This avoids the need to load the class on each Chef run unless the user is 25 | # explicitly requiring it. 26 | def self.included(base) 27 | require 'securerandom' unless defined?(SecureRandom) 28 | end 29 | 30 | class InvalidPasswordMode < StandardError 31 | def initialize(given, _acceptable = nil) 32 | super <<-EOH 33 | The given password mode '#{given}' is not valid. Valid password modes are :hex, 34 | :base64, and :random_bytes! 35 | EOH 36 | end 37 | end 38 | 39 | # 40 | # Generates a random password using {SecureRandom}. 41 | # 42 | # @example Generating a random (hex) password (of 20 characters) 43 | # random_password #=> "1930e99aa035083bdd93d1d8f11cb7ac8f625c9c" 44 | # 45 | # @example Generating a random base64 password that is 50 characters 46 | # random_password(mode: :base64, length: 50) #=> "72o5oVbKHHEVYj1nOgFB2EijnzZfnrbfasVuF+oRH8wMgb0QWoYZF/OkrQricp1ENoI=" 47 | # 48 | # @example Generate a password with a forced encoding 49 | # random_password(encoding: "ASCII") 50 | # 51 | # @param [Hash] options 52 | # @option options [Fixnum] :length 53 | # the number of bits to use in the password 54 | # @option options [Symbol] :mode 55 | # the type of random password to generate - valid values are 56 | # `:hex`, `:base64`, or `:random_bytes` 57 | # @option options [String, Symbol, Constant] :encoding 58 | # the encoding to force (default is "UTF-8") 59 | # 60 | # @return [String] 61 | # 62 | def random_password(options = {}) 63 | length = options[:length] || 20 64 | mode = options[:mode] || :hex 65 | encoding = options[:encoding] || 'UTF-8' 66 | 67 | # Convert to a "proper" length, since the size is actually in bytes 68 | length = case mode 69 | when :hex 70 | length / 2 71 | when :base64 72 | length * 3 / 4 73 | when :random_bytes 74 | length 75 | else 76 | raise InvalidPasswordMode.new(mode) 77 | end 78 | 79 | SecureRandom.send(mode, length).force_encoding(encoding) 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /libraries/secure_password.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook:: openssl 3 | # Library:: secure_password 4 | # Author:: Joshua Timberman 5 | # 6 | # Copyright:: 2009-2017, Chef Software, Inc. 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | 21 | include OpenSSLCookbook::Helpers 22 | 23 | module Opscode 24 | module OpenSSL 25 | # Generate secure passwords with OpenSSL 26 | module Password 27 | def secure_password(length = 20) 28 | Chef::Log.warn('The Opscode::OpenSSL::Password helper "secure_password" has been deprecated. Use the random_password method in OpenSSLCookbook::RandomPassword instead.') 29 | 30 | pw = '' 31 | 32 | while pw.length < length 33 | pw << ::OpenSSL::Random.random_bytes(1).gsub(/\W/, '') 34 | end 35 | 36 | pw 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /metadata.rb: -------------------------------------------------------------------------------- 1 | name 'openssl' 2 | maintainer 'Chef Software, Inc.' 3 | maintainer_email 'cookbooks@chef.io' 4 | license 'Apache-2.0' 5 | description 'Resources and libraries for interacting with certificates, keys, passwords, and dhparam files.' 6 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) 7 | version '8.5.5' 8 | 9 | recipe 'openssl::upgrade', 'Upgrade OpenSSL library and restart dependent services' 10 | 11 | %w(amazon centos debian fedora freebsd opensuse opensuseleap oracle redhat scientific solaris2 suse ubuntu zlinux).each do |os| 12 | supports os 13 | end 14 | 15 | source_url 'https://github.com/chef-cookbooks/openssl' 16 | issues_url 'https://github.com/chef-cookbooks/openssl/issues' 17 | chef_version '>= 12.7' if respond_to?(:chef_version) 18 | -------------------------------------------------------------------------------- /recipes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: openssl 3 | # Recipe:: default 4 | # 5 | # Copyright 2009-2016, Chef Software, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | -------------------------------------------------------------------------------- /recipes/upgrade.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook:: openssl 3 | # Recipe:: upgrade 4 | # 5 | # Copyright:: 2015-2017, Chef Software, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | case node['platform_family'] 21 | when 'debian', 'ubuntu' 22 | packages = if platform?('debian') && node['platform_version'].to_i >= 9 23 | %w(libssl1.0.2 openssl) 24 | else 25 | %w(libssl1.0.0 openssl) 26 | end 27 | when 'rhel', 'fedora', 'suse', 'amazon' 28 | packages = %w(openssl) 29 | else 30 | packages = [] 31 | end 32 | 33 | if packages.empty? 34 | Chef::Log.warn("The openssl::upgrade recipe does not currently support #{node['platform']}. If you believe it could please open a PR at https://github.com/chef-cookbooks/openssl") 35 | else 36 | package packages do 37 | action :upgrade 38 | node['openssl']['restart_services'].each do |ssl_svc| 39 | notifies :restart, "service[#{ssl_svc}]" 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /resources/dhparam.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright:: Copyright 2009-2018, Chef Software Inc. 3 | # License:: Apache License, Version 2.0 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) 19 | resource_name :openssl_dhparam 20 | 21 | include OpenSSLCookbook::Helpers 22 | 23 | property :path, String, name_property: true 24 | property :key_length, equal_to: [1024, 2048, 4096, 8192], default: 2048 25 | property :generator, equal_to: [2, 5], default: 2 26 | property :owner, String 27 | property :group, String 28 | property :mode, [Integer, String], default: '0640' 29 | 30 | action :create do 31 | unless dhparam_pem_valid?(new_resource.path) 32 | converge_by("Create a dhparam file #{new_resource.path}") do 33 | dhparam_content = gen_dhparam(new_resource.key_length, new_resource.generator).to_pem 34 | 35 | log "Generating #{new_resource.key_length} bit "\ 36 | "dhparam file at #{new_resource.path}, this may take some time" 37 | 38 | file new_resource.path do 39 | action :create 40 | owner new_resource.owner unless new_resource.owner.nil? 41 | group new_resource.group unless new_resource.group.nil? 42 | mode new_resource.mode 43 | sensitive true 44 | content dhparam_content 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /resources/ec_private_key.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright:: Copyright 2018, Chef Software Inc. 3 | # License:: Apache License, Version 2.0 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | chef_version_for_provides '< 14.4' if respond_to?(:chef_version_for_provides) 19 | resource_name :openssl_ec_private_key 20 | 21 | include OpenSSLCookbook::Helpers 22 | 23 | property :path, String, name_property: true 24 | property :key_curve, equal_to: %w(secp384r1 secp521r1 prime256v1 secp224r1 secp256k1), default: 'prime256v1' 25 | property :key_pass, String 26 | property :key_cipher, String, default: 'des3', equal_to: ::OpenSSL::Cipher.ciphers 27 | property :owner, String 28 | property :group, String 29 | property :mode, [Integer, String], default: '0640' 30 | property :force, [true, false], default: false 31 | 32 | action :create do 33 | unless new_resource.force || priv_key_file_valid?(new_resource.path, new_resource.key_pass) 34 | converge_by("Create an EC private key #{new_resource.path}") do 35 | log "Generating an #{new_resource.key_curve} "\ 36 | "EC key file at #{new_resource.name}, this may take some time" 37 | 38 | if new_resource.key_pass 39 | unencrypted_ec_key = gen_ec_priv_key(new_resource.key_curve) 40 | ec_key_content = encrypt_ec_key(unencrypted_ec_key, new_resource.key_pass, new_resource.key_cipher) 41 | else 42 | ec_key_content = gen_ec_priv_key(new_resource.key_curve).to_pem 43 | end 44 | 45 | file new_resource.path do 46 | action :create 47 | owner new_resource.owner unless new_resource.owner.nil? 48 | group new_resource.group unless new_resource.group.nil? 49 | mode new_resource.mode 50 | sensitive true 51 | content ec_key_content 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /resources/ec_public_key.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright:: Copyright 2018, Chef Software Inc. 3 | # License:: Apache License, Version 2.0 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | chef_version_for_provides '< 14.4' if respond_to?(:chef_version_for_provides) 19 | resource_name :openssl_ec_public_key 20 | 21 | include OpenSSLCookbook::Helpers 22 | 23 | property :path, String, name_property: true 24 | property :private_key_path, String 25 | property :private_key_content, String 26 | property :private_key_pass, String 27 | property :owner, String 28 | property :group, String 29 | property :mode, [Integer, String], default: '0640' 30 | 31 | action :create do 32 | raise ArgumentError, "You cannot specify both 'private_key_path' and 'private_key_content' properties at the same time." if new_resource.private_key_path && new_resource.private_key_content 33 | raise ArgumentError, "You must specify the private key with either 'private_key_path' or 'private_key_content' properties." unless new_resource.private_key_path || new_resource.private_key_content 34 | raise "#{new_resource.private_key_path} not a valid private EC key or password is invalid" unless priv_key_file_valid?((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass) 35 | 36 | ec_key_content = gen_ec_pub_key((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass) 37 | 38 | file new_resource.path do 39 | action :create 40 | owner new_resource.owner unless new_resource.owner.nil? 41 | group new_resource.group unless new_resource.group.nil? 42 | mode new_resource.mode 43 | content ec_key_content 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /resources/rsa_private_key.rb: -------------------------------------------------------------------------------- 1 | # 2 | # License:: Apache License, Version 2.0 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) 18 | resource_name :openssl_rsa_private_key 19 | provides :openssl_rsa_key # legacy name 20 | 21 | include OpenSSLCookbook::Helpers 22 | 23 | property :path, String, name_property: true 24 | property :key_length, equal_to: [1024, 2048, 4096, 8192], default: 2048 25 | property :key_pass, String 26 | property :key_cipher, String, default: 'des3', equal_to: ::OpenSSL::Cipher.ciphers 27 | property :owner, String 28 | property :group, String 29 | property :mode, [Integer, String], default: '0640' 30 | property :force, [true, false], default: false 31 | 32 | action :create do 33 | unless new_resource.force || priv_key_file_valid?(new_resource.path, new_resource.key_pass) 34 | converge_by("Create an RSA private key #{new_resource.path}") do 35 | log "Generating #{new_resource.key_length} bit "\ 36 | "RSA key file at #{new_resource.path}, this may take some time" 37 | 38 | if new_resource.key_pass 39 | unencrypted_rsa_key = gen_rsa_priv_key(new_resource.key_length) 40 | rsa_key_content = encrypt_rsa_key(unencrypted_rsa_key, new_resource.key_pass, new_resource.key_cipher) 41 | else 42 | rsa_key_content = gen_rsa_priv_key(new_resource.key_length).to_pem 43 | end 44 | 45 | file new_resource.path do 46 | action :create 47 | owner new_resource.owner unless new_resource.owner.nil? 48 | group new_resource.group unless new_resource.group.nil? 49 | mode new_resource.mode 50 | sensitive true 51 | content rsa_key_content 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /resources/rsa_public_key.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright:: Copyright 2018, Chef Software Inc. 3 | # License:: Apache License, Version 2.0 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) 19 | resource_name :openssl_rsa_public_key 20 | 21 | include OpenSSLCookbook::Helpers 22 | 23 | property :path, String, name_property: true 24 | property :private_key_path, String 25 | property :private_key_content, String 26 | property :private_key_pass, String 27 | property :owner, String 28 | property :group, String 29 | property :mode, [Integer, String], default: '0640' 30 | 31 | action :create do 32 | raise ArgumentError, "You cannot specify both 'private_key_path' and 'private_key_content' properties at the same time." if new_resource.private_key_path && new_resource.private_key_content 33 | raise ArgumentError, "You must specify the private key with either 'private_key_path' or 'private_key_content' properties." unless new_resource.private_key_path || new_resource.private_key_content 34 | raise "#{new_resource.private_key_path} not a valid private RSA key or password is invalid" unless priv_key_file_valid?((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass) 35 | 36 | rsa_key_content = gen_rsa_pub_key((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass) 37 | 38 | file new_resource.path do 39 | action :create 40 | owner new_resource.owner unless new_resource.owner.nil? 41 | group new_resource.group unless new_resource.group.nil? 42 | mode new_resource.mode 43 | content rsa_key_content 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /resources/x509_certificate.rb: -------------------------------------------------------------------------------- 1 | # 2 | # License:: Apache License, Version 2.0 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | chef_version_for_provides '< 14.4' if respond_to?(:chef_version_for_provides) 18 | resource_name :openssl_x509_certificate 19 | 20 | provides :openssl_x509 # legacy_name 21 | 22 | include OpenSSLCookbook::Helpers 23 | 24 | property :path, String, name_property: true 25 | property :owner, String 26 | property :group, String 27 | property :expire, Integer, default: 365 28 | property :mode, [Integer, String], default: '0644' 29 | property :country, String 30 | property :state, String 31 | property :city, String 32 | property :org, String 33 | property :org_unit, String 34 | property :common_name, String 35 | property :email, String 36 | property :extensions, Hash, default: {} 37 | property :subject_alt_name, Array, default: [] 38 | property :key_file, String 39 | property :key_pass, String 40 | property :key_type, equal_to: %w(rsa ec), default: 'rsa' 41 | property :key_length, equal_to: [1024, 2048, 4096, 8192], default: 2048 42 | property :key_curve, equal_to: %w(secp384r1 secp521r1 prime256v1), default: 'prime256v1' 43 | property :csr_file, String 44 | property :ca_cert_file, String 45 | property :ca_key_file, String 46 | property :ca_key_pass, String 47 | 48 | action :create do 49 | unless ::File.exist? new_resource.path 50 | converge_by("Create #{@new_resource}") do 51 | file new_resource.path do 52 | action :create_if_missing 53 | mode new_resource.mode 54 | owner new_resource.owner unless new_resource.owner.nil? 55 | group new_resource.group unless new_resource.group.nil? 56 | sensitive true 57 | content cert.to_pem 58 | end 59 | 60 | if new_resource.csr_file.nil? 61 | file new_resource.key_file do 62 | action :create_if_missing 63 | mode new_resource.mode 64 | owner new_resource.owner unless new_resource.owner.nil? 65 | group new_resource.group unless new_resource.group.nil? 66 | sensitive true 67 | content key.to_pem 68 | end 69 | end 70 | end 71 | end 72 | end 73 | 74 | action_class do 75 | def generate_key_file 76 | unless new_resource.key_file 77 | path, file = ::File.split(new_resource.path) 78 | filename = ::File.basename(file, ::File.extname(file)) 79 | new_resource.key_file path + '/' + filename + '.key' 80 | end 81 | new_resource.key_file 82 | end 83 | 84 | def key 85 | @key ||= if priv_key_file_valid?(generate_key_file, new_resource.key_pass) 86 | ::OpenSSL::PKey.read ::File.read(generate_key_file), new_resource.key_pass 87 | elsif new_resource.key_type == 'rsa' 88 | gen_rsa_priv_key(new_resource.key_length) 89 | else 90 | gen_ec_priv_key(new_resource.key_curve) 91 | end 92 | @key 93 | end 94 | 95 | def request 96 | request = if new_resource.csr_file.nil? 97 | gen_x509_request(subject, key) 98 | else 99 | ::OpenSSL::X509::Request.new ::File.read(new_resource.csr_file) 100 | end 101 | request 102 | end 103 | 104 | def subject 105 | subject = ::OpenSSL::X509::Name.new() 106 | subject.add_entry('C', new_resource.country) unless new_resource.country.nil? 107 | subject.add_entry('ST', new_resource.state) unless new_resource.state.nil? 108 | subject.add_entry('L', new_resource.city) unless new_resource.city.nil? 109 | subject.add_entry('O', new_resource.org) unless new_resource.org.nil? 110 | subject.add_entry('OU', new_resource.org_unit) unless new_resource.org_unit.nil? 111 | subject.add_entry('CN', new_resource.common_name) 112 | subject.add_entry('emailAddress', new_resource.email) unless new_resource.email.nil? 113 | subject 114 | end 115 | 116 | def ca_private_key 117 | ca_private_key = if new_resource.csr_file.nil? 118 | key 119 | else 120 | ::OpenSSL::PKey.read ::File.read(new_resource.ca_key_file), new_resource.ca_key_pass 121 | end 122 | ca_private_key 123 | end 124 | 125 | def ca_info 126 | # Will contain issuer (if any) & expiration 127 | ca_info = {} 128 | 129 | unless new_resource.ca_cert_file.nil? 130 | ca_info['issuer'] = ::OpenSSL::X509::Certificate.new ::File.read(new_resource.ca_cert_file) 131 | end 132 | ca_info['validity'] = new_resource.expire 133 | 134 | ca_info 135 | end 136 | 137 | def extensions 138 | extensions = gen_x509_extensions(new_resource.extensions) 139 | 140 | unless new_resource.subject_alt_name.empty? 141 | extensions += gen_x509_extensions('subjectAltName' => { 'values' => new_resource.subject_alt_name, 'critical' => false }) 142 | end 143 | 144 | extensions 145 | end 146 | 147 | def cert 148 | cert = gen_x509_cert(request, extensions, ca_info, ca_private_key) 149 | cert 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /resources/x509_crl.rb: -------------------------------------------------------------------------------- 1 | # 2 | # License:: Apache License, Version 2.0 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | chef_version_for_provides '< 14.4' if respond_to?(:chef_version_for_provides) 18 | resource_name :openssl_x509_crl 19 | 20 | include OpenSSLCookbook::Helpers 21 | 22 | property :path, String, name_property: true 23 | property :serial_to_revoke, [Integer, String] 24 | property :revocation_reason, Integer, default: 0 25 | property :expire, Integer, default: 8 26 | property :renewal_threshold, Integer, default: 1 27 | property :ca_cert_file, String, required: true 28 | property :ca_key_file, String, required: true 29 | property :ca_key_pass, String 30 | property :owner, String 31 | property :group, String 32 | property :mode, String 33 | 34 | action :create do 35 | file new_resource.path do 36 | owner new_resource.owner unless new_resource.owner.nil? 37 | group new_resource.group unless new_resource.group.nil? 38 | mode new_resource.mode unless new_resource.mode.nil? 39 | content crl.to_pem 40 | action :create 41 | end 42 | end 43 | 44 | action_class do 45 | def crl_info 46 | # Will contain issuer & expiration 47 | crl_info = {} 48 | 49 | crl_info['issuer'] = ::OpenSSL::X509::Certificate.new ::File.read(new_resource.ca_cert_file) 50 | crl_info['validity'] = new_resource.expire 51 | 52 | crl_info 53 | end 54 | 55 | def revoke_info 56 | # Will contain Serial to revoke & reason 57 | revoke_info = {} 58 | 59 | revoke_info['serial'] = new_resource.serial_to_revoke 60 | revoke_info['reason'] = new_resource.revocation_reason 61 | 62 | revoke_info 63 | end 64 | 65 | def ca_private_key 66 | ca_private_key = ::OpenSSL::PKey.read ::File.read(new_resource.ca_key_file), new_resource.ca_key_pass 67 | ca_private_key 68 | end 69 | 70 | def crl 71 | if crl_file_valid?(new_resource.path) 72 | crl = ::OpenSSL::X509::CRL.new ::File.read(new_resource.path) 73 | else 74 | log "Creating a CRL #{new_resource.path} for CA #{new_resource.ca_cert_file}" 75 | crl = gen_x509_crl(ca_private_key, crl_info) 76 | end 77 | 78 | if !new_resource.serial_to_revoke.nil? && serial_revoked?(crl, new_resource.serial_to_revoke) == false 79 | log "Revoking serial #{new_resource.serial_to_revoke} in CRL #{new_resource.path}" 80 | crl = revoke_x509_crl(revoke_info, crl, ca_private_key, crl_info) 81 | elsif crl.next_update <= Time.now + 3600 * 24 * new_resource.renewal_threshold 82 | log "Renewing CRL for CA #{new_resource.ca_cert_file}" 83 | crl = renew_x509_crl(crl, ca_private_key, crl_info) 84 | end 85 | 86 | crl 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /resources/x509_request.rb: -------------------------------------------------------------------------------- 1 | # 2 | # License:: Apache License, Version 2.0 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | chef_version_for_provides '< 14.4' if respond_to?(:chef_version_for_provides) 18 | resource_name :openssl_x509_request 19 | 20 | include OpenSSLCookbook::Helpers 21 | 22 | property :path, String, name_property: true 23 | property :owner, String 24 | property :group, String 25 | property :mode, [Integer, String], default: '0644' 26 | property :country, String 27 | property :state, String 28 | property :city, String 29 | property :org, String 30 | property :org_unit, String 31 | property :common_name, String, required: true 32 | property :email, String 33 | property :key_file, String 34 | property :key_pass, String 35 | property :key_type, equal_to: %w(rsa ec), default: 'ec' 36 | property :key_length, equal_to: [1024, 2048, 4096, 8192], default: 2048 37 | property :key_curve, equal_to: %w(secp384r1 secp521r1 prime256v1), default: 'prime256v1' 38 | 39 | action :create do 40 | unless ::File.exist? new_resource.path 41 | converge_by("Create CSR #{@new_resource}") do 42 | file new_resource.name do 43 | owner new_resource.owner unless new_resource.owner.nil? 44 | group new_resource.group unless new_resource.group.nil? 45 | mode new_resource.mode 46 | content csr.to_pem 47 | action :create 48 | end 49 | 50 | file new_resource.key_file do 51 | mode new_resource.mode 52 | owner new_resource.owner unless new_resource.owner.nil? 53 | group new_resource.group unless new_resource.group.nil? 54 | content key.to_pem 55 | sensitive true 56 | action :create_if_missing 57 | end 58 | end 59 | end 60 | end 61 | 62 | action_class do 63 | def generate_key_file 64 | unless new_resource.key_file 65 | path, file = ::File.split(new_resource.path) 66 | filename = ::File.basename(file, ::File.extname(file)) 67 | new_resource.key_file path + '/' + filename + '.key' 68 | end 69 | new_resource.key_file 70 | end 71 | 72 | def key 73 | @key ||= if priv_key_file_valid?(generate_key_file, new_resource.key_pass) 74 | ::OpenSSL::PKey.read ::File.read(generate_key_file), new_resource.key_pass 75 | elsif new_resource.key_type == 'rsa' 76 | gen_rsa_priv_key(new_resource.key_length) 77 | else 78 | gen_ec_priv_key(new_resource.key_curve) 79 | end 80 | @key 81 | end 82 | 83 | def subject 84 | csr_subject = ::OpenSSL::X509::Name.new() 85 | csr_subject.add_entry('C', new_resource.country) unless new_resource.country.nil? 86 | csr_subject.add_entry('ST', new_resource.state) unless new_resource.state.nil? 87 | csr_subject.add_entry('L', new_resource.city) unless new_resource.city.nil? 88 | csr_subject.add_entry('O', new_resource.org) unless new_resource.org.nil? 89 | csr_subject.add_entry('OU', new_resource.org_unit) unless new_resource.org_unit.nil? 90 | csr_subject.add_entry('CN', new_resource.common_name) 91 | csr_subject.add_entry('emailAddress', new_resource.email) unless new_resource.email.nil? 92 | csr_subject 93 | end 94 | 95 | def csr 96 | gen_x509_request(subject, key) 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'chefspec' 2 | require 'chefspec/berkshelf' 3 | 4 | RSpec.configure do |config| 5 | config.color = true # Use color in STDOUT 6 | config.formatter = :documentation # Use the specified formatter 7 | config.log_level = :error # Avoid deprecation notice SPAM 8 | end 9 | -------------------------------------------------------------------------------- /spec/unit/helpers/helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../../libraries/helpers' 2 | 3 | describe OpenSSLCookbook::Helpers do 4 | let(:instance) do 5 | Class.new { include OpenSSLCookbook::Helpers }.new 6 | end 7 | 8 | describe '.included' do 9 | it 'requires openssl' do 10 | instance 11 | expect(defined?(OpenSSL)).to_not be(false) 12 | end 13 | end 14 | 15 | # Path helpers 16 | describe '#get_key_filename' do 17 | context 'When the input is not a string' do 18 | it 'Throws a TypeError' do 19 | expect do 20 | instance.get_key_filename(33) 21 | end.to raise_error(TypeError) 22 | end 23 | end 24 | 25 | context 'when the input is a string' do 26 | it 'Generates valid keyfile names' do 27 | expect(instance.get_key_filename('/etc/temp.crt')).to match('/etc/temp.key') 28 | end 29 | end 30 | end 31 | 32 | # Validation helpers 33 | describe '#key_length_valid?' do 34 | context 'When the number is less than 1024' do 35 | it 'returns false' do 36 | expect(instance.key_length_valid?(1023)).to be_falsey 37 | expect(instance.key_length_valid?(2)).to be_falsey 38 | expect(instance.key_length_valid?(64)).to be_falsey 39 | expect(instance.key_length_valid?(512)).to be_falsey 40 | end 41 | end 42 | 43 | context 'When the number is greater than 1024 but is not a power of 2' do 44 | it 'returns false' do 45 | expect(instance.key_length_valid?(1025)).to be_falsey 46 | expect(instance.key_length_valid?(6666)).to be_falsey 47 | expect(instance.key_length_valid?(8191)).to be_falsey 48 | end 49 | end 50 | 51 | context 'When the number is a power of 2, equal to or greater than 1024' do 52 | it 'returns true' do 53 | expect(instance.key_length_valid?(1024)).to be_truthy 54 | expect(instance.key_length_valid?(2048)).to be_truthy 55 | expect(instance.key_length_valid?(4096)).to be_truthy 56 | expect(instance.key_length_valid?(8192)).to be_truthy 57 | end 58 | end 59 | end 60 | 61 | describe '#dhparam_pem_valid?' do 62 | require 'tempfile' 63 | 64 | before(:each) do 65 | @dhparam_file = Tempfile.new('dhparam') 66 | end 67 | 68 | context 'When the dhparam.pem file does not exist' do 69 | it 'returns false' do 70 | expect(instance.dhparam_pem_valid?('/tmp/bad_filename')).to be_falsey 71 | end 72 | end 73 | 74 | context 'When the dhparam.pem file does exist, but does not contain a valid dhparam key' do 75 | it 'Throws an OpenSSL::PKey::DHError exception' do 76 | expect do 77 | @dhparam_file.puts('I_am_not_a_key_I_am_a_free_man') 78 | @dhparam_file.close 79 | instance.dhparam_pem_valid?(@dhparam_file.path) 80 | end.to raise_error(OpenSSL::PKey::DHError) 81 | end 82 | end 83 | 84 | context 'When the dhparam.pem file does exist, and does contain a vaild dhparam key' do 85 | it 'returns true' do 86 | @dhparam_file.puts(OpenSSL::PKey::DH.new(1024).to_pem) 87 | @dhparam_file.close 88 | expect(instance.dhparam_pem_valid?(@dhparam_file.path)).to be_truthy 89 | end 90 | end 91 | 92 | after(:each) do 93 | @dhparam_file.unlink 94 | end 95 | end 96 | 97 | describe '#priv_key_file_valid?' do 98 | require 'tempfile' 99 | require 'openssl' unless defined?(OpenSSL) 100 | 101 | cipher = OpenSSL::Cipher.new('des3') 102 | 103 | before(:each) do 104 | @keyfile = Tempfile.new('keyfile') 105 | end 106 | 107 | context 'When the key file does not exist' do 108 | it 'returns false' do 109 | expect(instance.priv_key_file_valid?('/tmp/bad_filename')).to be_falsey 110 | end 111 | end 112 | 113 | context 'When the key file does exist, but does not contain a valid rsa/ec private key' do 114 | it 'Throws an OpenSSL::PKey::PKeyError exception' do 115 | @keyfile.write('I_am_not_a_key_I_am_a_free_man') 116 | @keyfile.close 117 | expect(instance.priv_key_file_valid?(@keyfile.path)).to be_falsey 118 | end 119 | end 120 | 121 | context 'When the rsa key file does exist, and does contain a vaild rsa private key' do 122 | it 'returns true' do 123 | @keyfile.write(OpenSSL::PKey::RSA.new(1024).to_pem) 124 | @keyfile.close 125 | expect(instance.priv_key_file_valid?(@keyfile.path)).to be_truthy 126 | end 127 | end 128 | 129 | context 'When the ec key file does exist, and does contain a vaild ec private key' do 130 | it 'returns true' do 131 | @keyfile.write(OpenSSL::PKey::EC.generate('prime256v1').to_pem) 132 | @keyfile.close 133 | expect(instance.priv_key_file_valid?(@keyfile.path)).to be_truthy 134 | end 135 | end 136 | 137 | context 'When a valid rsa keyfile requires a passphrase, and an invalid passphrase is supplied' do 138 | it 'returns false' do 139 | @keyfile.write(OpenSSL::PKey::RSA.new(1024).to_pem(cipher, 'oink')) 140 | @keyfile.close 141 | expect(instance.priv_key_file_valid?(@keyfile.path, 'poml')).to be_falsey 142 | end 143 | end 144 | 145 | context 'When a valid ec keyfile requires a passphrase, and an invalid passphrase is supplied' do 146 | it 'returns false' do 147 | @keyfile.write(OpenSSL::PKey::EC.generate('prime256v1').to_pem(cipher, 'oink')) 148 | @keyfile.close 149 | expect(instance.priv_key_file_valid?(@keyfile.path, 'poml')).to be_falsey 150 | end 151 | end 152 | 153 | context 'When a valid rsa keyfile requires a passphrase, and a valid passphrase is supplied' do 154 | it 'returns true' do 155 | @keyfile.write(OpenSSL::PKey::RSA.new(1024).to_pem(cipher, 'oink')) 156 | @keyfile.close 157 | expect(instance.priv_key_file_valid?(@keyfile.path, 'oink')).to be_truthy 158 | end 159 | end 160 | 161 | context 'When a valid ec keyfile requires a passphrase, and a valid passphrase is supplied' do 162 | it 'returns true' do 163 | @keyfile.write(OpenSSL::PKey::EC.generate('prime256v1').to_pem(cipher, 'oink')) 164 | @keyfile.close 165 | expect(instance.priv_key_file_valid?(@keyfile.path, 'oink')).to be_truthy 166 | end 167 | end 168 | 169 | after(:each) do 170 | @keyfile.unlink 171 | end 172 | end 173 | 174 | describe '#crl_file_valid?' do 175 | require 'tempfile' 176 | 177 | before(:each) do 178 | @crlfile = Tempfile.new('crlfile') 179 | end 180 | 181 | context 'When the crl file doesnt not exist' do 182 | it 'returns false' do 183 | expect(instance.crl_file_valid?('/tmp/bad_filename')).to be_falsey 184 | end 185 | end 186 | 187 | context 'When the crl file does exist, but does not contain a valid CRL' do 188 | it 'returns false' do 189 | @crlfile.write('I_am_not_a_crl_I_am_a_free_man') 190 | @crlfile.close 191 | expect(instance.crl_file_valid?(@crlfile.path)).to be_falsey 192 | end 193 | end 194 | 195 | context 'When the crl file does exist, and does contain a vaild CRL' do 196 | it 'returns true' do 197 | @crlfile.write("-----BEGIN X509 CRL-----\nMIIBbjCB0QIBATAKBggqhkjOPQQDAjAOMQwwCgYDVQQDDANDQTIXDTE4MDgwMTE3\nMjg1NVoXDTE4MDgwOTE3Mjg1NVowNjA0AhUAx7y2YCouQlHvTignoijLUrwM6i8X\nDTE4MDgwMTE3Mjg1NVowDDAKBgNVHRUEAwoBAKBaMFgwCgYDVR0UBAMCAQQwSgYD\nVR0jBEMwQYAUCqE8XxFIFys0LTVPvsO1UtmrlyOhEqQQMA4xDDAKBgNVBAMMA0NB\nMoIVAPneTuAa1LzrK0wiZrxE8/1lSTp3MAoGCCqGSM49BAMCA4GLADCBhwJBct+Z\nZV3IZkPNevQv2S8lZ6kAMudN8R4QSzIQfM354Uk880RyQStP2S5Mb4gW3aFzwAy2\n/+rbx0bn2WmwoQv17I8CQgDtbvhf9chyPgMwAGCF7al04fve90fU1zRNH0zX1j9H\niDA2q1uBX+3TcTWcN+xgNimeRpvJFJ3uOB6w7jtwqGf1YQ==\n-----END X509 CRL-----\n") 198 | @crlfile.close 199 | expect(instance.crl_file_valid?(@crlfile.path)).to be_truthy 200 | end 201 | end 202 | 203 | after(:each) do 204 | @crlfile.unlink 205 | end 206 | end 207 | 208 | # Generators 209 | describe '#gen_dhparam' do 210 | context 'When given an invalid key length' do 211 | it 'Throws an ArgumentError' do 212 | expect do 213 | instance.gen_dhparam(2046, 2) 214 | end.to raise_error(ArgumentError) 215 | end 216 | end 217 | 218 | context 'When given an invalid generator id' do 219 | it 'Throws a TypeError' do 220 | expect do 221 | instance.gen_dhparam(2048, 'bob') 222 | end.to raise_error(TypeError) 223 | end 224 | end 225 | 226 | context 'When a proper key length and generator id are given' do 227 | it 'Generates a dhparam object' do 228 | expect(instance.gen_dhparam(1024, 2)).to be_kind_of(OpenSSL::PKey::DH) 229 | end 230 | end 231 | end 232 | 233 | describe '#gen_rsa_priv_key' do 234 | context 'When given an invalid key length' do 235 | it 'Throws an ArgumentError' do 236 | expect do 237 | instance.gen_rsa_priv_key(4093) 238 | end.to raise_error(ArgumentError) 239 | end 240 | end 241 | 242 | context 'When a proper key length is given' do 243 | it 'Generates an RSA key object' do 244 | expect(instance.gen_rsa_priv_key(1024)).to be_kind_of(OpenSSL::PKey::RSA) 245 | end 246 | end 247 | end 248 | 249 | describe '#encrypt_rsa_key' do 250 | before(:all) do 251 | @rsa_key = OpenSSL::PKey::RSA.new(1024) 252 | end 253 | 254 | context 'When given anything other than an RSA key object to encrypt' do 255 | it 'Raises a TypeError' do 256 | expect do 257 | instance.encrypt_rsa_key('abcd', 'efgh', 'des3') 258 | end.to raise_error(TypeError) 259 | end 260 | end 261 | 262 | context 'When given anything other than a string as the passphrase' do 263 | it 'Raises a TypeError' do 264 | expect do 265 | instance.encrypt_rsa_key(@rsa_key, 1234, 'des3') 266 | end.to raise_error(TypeError) 267 | end 268 | end 269 | 270 | context 'When given anything other than a string as the cipher' do 271 | it 'Raises a TypeError' do 272 | expect do 273 | instance.encrypt_rsa_key(@rsa_key, '1234', 1234) 274 | end.to raise_error(TypeError) 275 | end 276 | end 277 | 278 | context 'When given an invalid cipher string' do 279 | it 'Raises an ArgumentError' do 280 | expect do 281 | instance.encrypt_rsa_key(@rsa_key, '1234', 'des3_bogus') 282 | end.to raise_error(ArgumentError) 283 | end 284 | end 285 | 286 | context 'When given a valid RSA key and a valid passphrase string' do 287 | it 'Generates a valid encrypted PEM' do 288 | @encrypted_key = instance.encrypt_rsa_key(@rsa_key, 'oink', 'des3') 289 | expect(@encrypted_key).to be_kind_of(String) 290 | expect(OpenSSL::PKey::RSA.new(@encrypted_key, 'oink').private?).to be_truthy 291 | end 292 | end 293 | end 294 | 295 | describe '#gen_ec_priv_key' do 296 | context 'When given an invalid curve' do 297 | it 'Raises a TypeError' do 298 | expect do 299 | instance.gen_ec_priv_key(2048) 300 | end.to raise_error(TypeError) 301 | end 302 | 303 | it 'Throws an ArgumentError' do 304 | expect do 305 | instance.gen_ec_priv_key('primeFromTheFuture') 306 | end.to raise_error(ArgumentError) 307 | end 308 | end 309 | 310 | context 'When a proper curve is given' do 311 | it 'Generates an ec key object' do 312 | expect(instance.gen_ec_priv_key('prime256v1')).to be_kind_of(OpenSSL::PKey::EC) 313 | end 314 | end 315 | end 316 | 317 | describe '#encrypt_ec_key' do 318 | before(:all) do 319 | @ec_key = OpenSSL::PKey::EC.generate('prime256v1') 320 | end 321 | 322 | context 'When given anything other than an EC key object to encrypt' do 323 | it 'Raises a TypeError' do 324 | expect do 325 | instance.encrypt_ec_key('abcd', 'efgh', 'des3') 326 | end.to raise_error(TypeError) 327 | end 328 | end 329 | 330 | context 'When given anything other than a string as the passphrase' do 331 | it 'Raises a TypeError' do 332 | expect do 333 | instance.encrypt_ec_key(@ec_key, 1234, 'des3') 334 | end.to raise_error(TypeError) 335 | end 336 | end 337 | 338 | context 'When given anything other than a string as the cipher' do 339 | it 'Raises a TypeError' do 340 | expect do 341 | instance.encrypt_ec_key(@ec_key, '1234', 1234) 342 | end.to raise_error(TypeError) 343 | end 344 | end 345 | 346 | context 'When given an invalid cipher string' do 347 | it 'Raises an ArgumentError' do 348 | expect do 349 | instance.encrypt_ec_key(@ec_key, '1234', 'des3_bogus') 350 | end.to raise_error(ArgumentError) 351 | end 352 | end 353 | 354 | context 'When given a valid ec key and a valid passphrase string' do 355 | it 'Generates a valid encrypted PEM' do 356 | @encrypted_key = instance.encrypt_ec_key(@ec_key, 'oink', 'des3') 357 | expect(@encrypted_key).to be_kind_of(String) 358 | expect(OpenSSL::PKey::EC.new(@encrypted_key, 'oink').private?).to be_truthy 359 | end 360 | end 361 | end 362 | 363 | describe '#gen_x509_request' do 364 | before(:all) do 365 | @subject = OpenSSL::X509::Name.new [%w(CN x509request)] 366 | @ec_key = OpenSSL::PKey::EC.generate('prime256v1') 367 | @rsa_key = OpenSSL::PKey::RSA.new(2048) 368 | end 369 | 370 | context 'When given anything other than an RSA/EC key object' do 371 | it 'Raises a TypeError' do 372 | expect do 373 | instance.gen_x509_request(@subject, 'abc') 374 | end.to raise_error(TypeError) 375 | end 376 | end 377 | 378 | context 'When given anything other than an X509 Name object' do 379 | it 'Raises a TypeError' do 380 | expect do 381 | instance.gen_x509_request('abc', @key) 382 | end.to raise_error(TypeError) 383 | end 384 | end 385 | 386 | context 'When given a valid EC key and a valid subject' do 387 | it 'Generates a valid x509 request PEM' do 388 | @x509_request = instance.gen_x509_request(@subject, @ec_key) 389 | expect(@x509_request).to be_kind_of(OpenSSL::X509::Request) 390 | expect(OpenSSL::X509::Request.new(@x509_request).verify(@ec_key)).to be_truthy 391 | end 392 | end 393 | 394 | context 'When given a valid RSA key and a valid subject' do 395 | it 'Generates a valid x509 request PEM' do 396 | @x509_request = instance.gen_x509_request(@subject, @rsa_key) 397 | expect(@x509_request).to be_kind_of(OpenSSL::X509::Request) 398 | expect(OpenSSL::X509::Request.new(@x509_request).verify(@rsa_key)).to be_truthy 399 | end 400 | end 401 | end 402 | 403 | describe '#gen_x509_extensions' do 404 | context 'When given anything other than an Ruby Hash object' do 405 | it 'Raises a TypeError' do 406 | expect do 407 | instance.gen_x509_extensions('abc') 408 | end.to raise_error(TypeError) 409 | end 410 | end 411 | 412 | context 'When a misformatted ruby Hash is given' do 413 | it 'Raises a TypeError' do 414 | expect do 415 | instance.gen_x509_extensions('pouet' => 'plop') 416 | end.to raise_error(TypeError) 417 | end 418 | 419 | it 'Raises a ArgumentError' do 420 | expect do 421 | instance.gen_x509_extensions('pouet' => { 'values' => [ 'keyCertSign' ], 'wrong_key' => true }) 422 | end.to raise_error(ArgumentError) 423 | end 424 | 425 | it 'Raises a TypeError' do 426 | expect do 427 | instance.gen_x509_extensions('keyUsage' => { 'values' => 'keyCertSign', 'critical' => true }) 428 | end.to raise_error(TypeError) 429 | end 430 | 431 | it 'Raises a TypeError' do 432 | expect do 433 | instance.gen_x509_extensions('keyUsage' => { 'values' => [ 'keyCertSign' ], 'critical' => 'yes' }) 434 | end.to raise_error(TypeError) 435 | end 436 | end 437 | 438 | context 'When given a well formatted ruby Hash' do 439 | it 'Generates a valid Array of X509 Extensions' do 440 | @x509_extension = instance.gen_x509_extensions('keyUsage' => { 'values' => [ 'keyCertSign' ], 'critical' => true }) 441 | expect(@x509_extension).to be_kind_of(Array) 442 | @x509_extension.each { |e| expect(e).to be_kind_of(OpenSSL::X509::Extension) } 443 | end 444 | end 445 | end 446 | 447 | describe '#gen_x509_cert' do 448 | include OpenSSLCookbook::Helpers 449 | before(:all) do 450 | @rsa_key = OpenSSL::PKey::RSA.new(2048) 451 | @ec_key = OpenSSL::PKey::EC.generate('prime256v1') 452 | 453 | @rsa_request = gen_x509_request(OpenSSL::X509::Name.new([%w(CN RSACert)]), @rsa_key) 454 | @ec_request = gen_x509_request(OpenSSL::X509::Name.new([%w(CN ECCert)]), @ec_key) 455 | 456 | @x509_extension = gen_x509_extensions('keyUsage' => { 'values' => [ 'keyCertSign' ], 'critical' => true }) 457 | 458 | # Generating CA 459 | @ca_key = OpenSSL::PKey::RSA.new(2048) 460 | @ca_cert = OpenSSL::X509::Certificate.new 461 | @ca_cert.version = 2 462 | @ca_cert.serial = 1 463 | @ca_cert.subject = OpenSSL::X509::Name.new [%w(CN TestCA)] 464 | @ca_cert.issuer = @ca_cert.subject 465 | @ca_cert.public_key = @ca_key.public_key 466 | @ca_cert.not_before = Time.now 467 | @ca_cert.not_after = @ca_cert.not_before + 365 * 24 * 60 * 60 468 | ef = OpenSSL::X509::ExtensionFactory.new 469 | ef.subject_certificate = @ca_cert 470 | ef.issuer_certificate = @ca_cert 471 | @ca_cert.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true)) 472 | @ca_cert.add_extension(ef.create_extension('keyUsage', 'keyCertSign, cRLSign', true)) 473 | @ca_cert.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false)) 474 | @ca_cert.add_extension(ef.create_extension('authorityKeyIdentifier', 'keyid:always', false)) 475 | @ca_cert.sign(@ca_key, OpenSSL::Digest::SHA256.new) 476 | 477 | @info_with_issuer = { 'validity' => 365, 'issuer' => @ca_cert } 478 | @info_without_issuer = { 'validity' => 365 } 479 | end 480 | 481 | context 'When the request given is anything other then a Ruby OpenSSL::X509::Request' do 482 | it 'Raises a TypeError' do 483 | expect do 484 | instance.gen_x509_cert('abc', @x509_extension, @info_without_issuer, @rsa_key) 485 | end.to raise_error(TypeError) 486 | end 487 | end 488 | 489 | context 'When the extension given is anything other then a Ruby Array' do 490 | it 'Raises a TypeError' do 491 | expect do 492 | instance.gen_x509_cert(@rsa_request, 'abc', @info_without_issuer, @rsa_key) 493 | end.to raise_error(TypeError) 494 | end 495 | end 496 | 497 | context 'When the info given is anything other then a Ruby Hash' do 498 | it 'Raises a TypeError' do 499 | expect do 500 | instance.gen_x509_cert(@rsa_request, @x509_extension, 'abc', @rsa_key) 501 | end.to raise_error(TypeError) 502 | end 503 | end 504 | 505 | context 'When the key given is anything other then a Ruby OpenSSL::Pkey::EC or OpenSSL::Pkey::RSA object' do 506 | it 'Raises a TypeError' do 507 | expect do 508 | instance.gen_x509_cert(@rsa_request, @x509_extension, @info_without_issuer, 'abc') 509 | end.to raise_error(TypeError) 510 | end 511 | end 512 | 513 | context 'When given valid parameters to generate a self signed certificate' do 514 | it 'Generates a valid x509 Certificate' do 515 | @x509_certificate = instance.gen_x509_cert(@rsa_request, @x509_extension, @info_without_issuer, @rsa_key) 516 | expect(@x509_certificate).to be_kind_of(OpenSSL::X509::Certificate) 517 | expect(OpenSSL::X509::Certificate.new(@x509_certificate).verify(@rsa_key)).to be_truthy 518 | end 519 | end 520 | 521 | context 'When given valid parameters to generate a CA signed certificate' do 522 | it 'Generates a valid x509 Certificate' do 523 | @x509_certificate = instance.gen_x509_cert(@ec_request, @x509_extension, @info_with_issuer, @ca_key) 524 | expect(@x509_certificate).to be_kind_of(OpenSSL::X509::Certificate) 525 | expect(OpenSSL::X509::Certificate.new(@x509_certificate).verify(@ca_key)).to be_truthy 526 | end 527 | end 528 | end 529 | 530 | describe '#get_next_crl_number' do 531 | include OpenSSLCookbook::Helpers 532 | before(:all) do 533 | @crl = OpenSSL::X509::CRL. new "-----BEGIN X509 CRL-----\nMIIBbTCB0QIBATAKBggqhkjOPQQDAjAOMQwwCgYDVQQDDANDQTIXDTE4MDgwMjA5\nMzc0OFoXDTE4MDgxMDA5Mzc0OFowNjA0AhUAx7y2YCouQlHvTignoijLUrwM6i8X\nDTE4MDgwMjA5Mzc0OFowDDAKBgNVHRUEAwoBAKBaMFgwCgYDVR0UBAMCAQQwSgYD\nVR0jBEMwQYAUxRlLNQUIOeWVaYm6HS0qFIbNCs2hEqQQMA4xDDAKBgNVBAMMA0NB\nMoIVAN1nyw8cj7IbhRLBu2CfS9Q8ILmDMAoGCCqGSM49BAMCA4GKADCBhgJBNR3o\njo/PzFwFGJKxIMa09pU+jprLG2CWehpZ4tGDjwiDCfZBztkg3H15eu+hyWmDp0U9\neAP5iJHVb12/3KZP0YUCQSgmaoLF68+Gh7ha+hcDjwFhzqdgmh/UlGPaxFBJ1BiQ\nQq9uBn0IT4o7v1Tv2WRZNDk7oiuRaZG+R9IodiZPsGKv\n-----END X509 CRL-----\n" 534 | end 535 | 536 | context 'When the CRL given is anything other then a Ruby OpenSSL::X509::CRL object' do 537 | it 'Raises a TypeError' do 538 | expect do 539 | instance.get_next_crl_number('abc') 540 | end.to raise_error(TypeError) 541 | end 542 | end 543 | 544 | context 'When given valid parameter to get the next crlNumber' do 545 | it 'Get 5' do 546 | @next_crl = instance.get_next_crl_number(@crl) 547 | expect(@next_crl).to be_kind_of(Integer) 548 | expect(@next_crl == 5).to be_truthy 549 | end 550 | end 551 | end 552 | 553 | describe '#serial_revoked?' do 554 | include OpenSSLCookbook::Helpers 555 | before(:all) do 556 | @crl = OpenSSL::X509::CRL. new "-----BEGIN X509 CRL-----\nMIIBbTCB0QIBATAKBggqhkjOPQQDAjAOMQwwCgYDVQQDDANDQTIXDTE4MDgwMjA5\nMzc0OFoXDTE4MDgxMDA5Mzc0OFowNjA0AhUAx7y2YCouQlHvTignoijLUrwM6i8X\nDTE4MDgwMjA5Mzc0OFowDDAKBgNVHRUEAwoBAKBaMFgwCgYDVR0UBAMCAQQwSgYD\nVR0jBEMwQYAUxRlLNQUIOeWVaYm6HS0qFIbNCs2hEqQQMA4xDDAKBgNVBAMMA0NB\nMoIVAN1nyw8cj7IbhRLBu2CfS9Q8ILmDMAoGCCqGSM49BAMCA4GKADCBhgJBNR3o\njo/PzFwFGJKxIMa09pU+jprLG2CWehpZ4tGDjwiDCfZBztkg3H15eu+hyWmDp0U9\neAP5iJHVb12/3KZP0YUCQSgmaoLF68+Gh7ha+hcDjwFhzqdgmh/UlGPaxFBJ1BiQ\nQq9uBn0IT4o7v1Tv2WRZNDk7oiuRaZG+R9IodiZPsGKv\n-----END X509 CRL-----\n" 557 | end 558 | 559 | context 'When the CRL given is anything other then a Ruby OpenSSL::X509::CRL object' do 560 | it 'Raises a TypeError' do 561 | expect do 562 | instance.serial_revoked?('abc', 'C7BCB6602A2E4251EF4E2827A228CB52BC0CEA2F') 563 | end.to raise_error(TypeError) 564 | end 565 | end 566 | 567 | context 'When the serial given is anything other then a Ruby String or Integer object' do 568 | it 'Raises a TypeError' do 569 | expect do 570 | instance.serial_revoked?(@crl, []) 571 | end.to raise_error(TypeError) 572 | end 573 | end 574 | 575 | context 'When given valid parameters to know if the serial is revoked' do 576 | it 'get true' do 577 | @serial_revoked = instance.serial_revoked?(@crl, 'C7BCB6602A2E4251EF4E2827A228CB52BC0CEA2F') 578 | expect(@serial_revoked).to be_kind_of(TrueClass) 579 | end 580 | end 581 | end 582 | 583 | describe '#gen_x509_crl' do 584 | include OpenSSLCookbook::Helpers 585 | before(:all) do 586 | # Generating CA 587 | @ca_key = OpenSSL::PKey::RSA.new(2048) 588 | @ca_cert = OpenSSL::X509::Certificate.new 589 | @ca_cert.version = 2 590 | @ca_cert.serial = 1 591 | @ca_cert.subject = OpenSSL::X509::Name.new [%w(CN TestCA)] 592 | @ca_cert.issuer = @ca_cert.subject 593 | @ca_cert.public_key = @ca_key.public_key 594 | @ca_cert.not_before = Time.now 595 | @ca_cert.not_after = @ca_cert.not_before + 365 * 24 * 60 * 60 596 | ef = OpenSSL::X509::ExtensionFactory.new 597 | ef.subject_certificate = @ca_cert 598 | ef.issuer_certificate = @ca_cert 599 | @ca_cert.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true)) 600 | @ca_cert.add_extension(ef.create_extension('keyUsage', 'keyCertSign, cRLSign', true)) 601 | @ca_cert.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false)) 602 | @ca_cert.add_extension(ef.create_extension('authorityKeyIdentifier', 'keyid:always', false)) 603 | @ca_cert.sign(@ca_key, OpenSSL::Digest::SHA256.new) 604 | 605 | @info = { 'validity' => 8, 'issuer' => @ca_cert } 606 | end 607 | 608 | context 'When the CA private key given is anything other then a Ruby OpenSSL::PKey::EC object or a OpenSSL::PKey::RSA object' do 609 | it 'Raises a TypeError' do 610 | expect do 611 | instance.gen_x509_crl('abc', @info) 612 | end.to raise_error(TypeError) 613 | end 614 | end 615 | 616 | context 'When the info given is anything other then a Ruby Hash' do 617 | it 'Raises a TypeError' do 618 | expect do 619 | instance.gen_x509_crl(@ca_key, 'abc') 620 | end.to raise_error(TypeError) 621 | end 622 | end 623 | 624 | context 'When a misformatted info Ruby Hash is given' do 625 | it 'Raises a ArgumentError' do 626 | expect do 627 | instance.gen_x509_crl(@ca_key, 'abc' => 'def', 'validity' => 8) 628 | end.to raise_error(ArgumentError) 629 | end 630 | 631 | it 'Raises a TypeError' do 632 | expect do 633 | instance.gen_x509_crl(@ca_key, 'issuer' => 'abc', 'validity' => 8) 634 | end.to raise_error(TypeError) 635 | end 636 | 637 | it 'Raises a TypeError' do 638 | expect do 639 | instance.gen_x509_crl(@ca_key, 'issuer' => @ca_cert, 'validity' => 'abc') 640 | end.to raise_error(TypeError) 641 | end 642 | end 643 | 644 | context 'When given valid parameters to generate a CRL' do 645 | it 'Generates a valid x509 CRL' do 646 | @x509_crl = instance.gen_x509_crl(@ca_key, @info) 647 | expect(@x509_crl).to be_kind_of(OpenSSL::X509::CRL) 648 | expect(OpenSSL::X509::CRL.new(@x509_crl).verify(@ca_key)).to be_truthy 649 | end 650 | end 651 | end 652 | 653 | describe '#renew_x509_crl' do 654 | include OpenSSLCookbook::Helpers 655 | before(:all) do 656 | # Generating CA 657 | @ca_key = OpenSSL::PKey::RSA.new(2048) 658 | @ca_cert = OpenSSL::X509::Certificate.new 659 | @ca_cert.version = 2 660 | @ca_cert.serial = 1 661 | @ca_cert.subject = OpenSSL::X509::Name.new [%w(CN TestCA)] 662 | @ca_cert.issuer = @ca_cert.subject 663 | @ca_cert.public_key = @ca_key.public_key 664 | @ca_cert.not_before = Time.now 665 | @ca_cert.not_after = @ca_cert.not_before + 365 * 24 * 60 * 60 666 | ef = OpenSSL::X509::ExtensionFactory.new 667 | ef.subject_certificate = @ca_cert 668 | ef.issuer_certificate = @ca_cert 669 | @ca_cert.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true)) 670 | @ca_cert.add_extension(ef.create_extension('keyUsage', 'keyCertSign, cRLSign', true)) 671 | @ca_cert.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false)) 672 | @ca_cert.add_extension(ef.create_extension('authorityKeyIdentifier', 'keyid:always', false)) 673 | @ca_cert.sign(@ca_key, OpenSSL::Digest::SHA256.new) 674 | 675 | @info = { 'validity' => 8, 'issuer' => @ca_cert } 676 | 677 | @crl = gen_x509_crl(@ca_key, @info) 678 | end 679 | 680 | context 'When the CRL given is anything other then a Ruby OpenSSL::X509::CRL object' do 681 | it 'Raises a TypeError' do 682 | expect do 683 | instance.renew_x509_crl('abc', @ca_key, @info) 684 | end.to raise_error(TypeError) 685 | end 686 | end 687 | 688 | context 'When the CA private key given is anything other then a Ruby OpenSSL::PKey::EC object or a OpenSSL::PKey::RSA object' do 689 | it 'Raises a TypeError' do 690 | expect do 691 | instance.renew_x509_crl(@crl, 'abc', @info) 692 | end.to raise_error(TypeError) 693 | end 694 | end 695 | 696 | context 'When the info given is anything other then a Ruby Hash' do 697 | it 'Raises a TypeError' do 698 | expect do 699 | instance.renew_x509_crl(@crl, @ca_key, 'abc') 700 | end.to raise_error(TypeError) 701 | end 702 | end 703 | 704 | context 'When a misformatted info Ruby Hash is given' do 705 | it 'Raises a ArgumentError' do 706 | expect do 707 | instance.renew_x509_crl(@crl, @ca_key, 'abc' => 'def', 'validity' => 8) 708 | end.to raise_error(ArgumentError) 709 | end 710 | 711 | it 'Raises a TypeError' do 712 | expect do 713 | instance.renew_x509_crl(@crl, @ca_key, 'issuer' => 'abc', 'validity' => 8) 714 | end.to raise_error(TypeError) 715 | end 716 | 717 | it 'Raises a TypeError' do 718 | expect do 719 | instance.renew_x509_crl(@crl, @ca_key, 'issuer' => @ca_cert, 'validity' => 'abc') 720 | end.to raise_error(TypeError) 721 | end 722 | end 723 | 724 | context 'When given valid parameters to renew a CRL' do 725 | it 'Renew a valid x509 CRL' do 726 | @renewed_crl = instance.renew_x509_crl(@crl, @ca_key, @info) 727 | expect(@renewed_crl).to be_kind_of(OpenSSL::X509::CRL) 728 | expect(OpenSSL::X509::CRL.new(@renewed_crl).verify(@ca_key)).to be_truthy 729 | end 730 | end 731 | end 732 | 733 | describe '#revoke_x509_crl' do 734 | include OpenSSLCookbook::Helpers 735 | before(:all) do 736 | # Generating CA 737 | @ca_key = OpenSSL::PKey::RSA.new(2048) 738 | @ca_cert = OpenSSL::X509::Certificate.new 739 | @ca_cert.version = 2 740 | @ca_cert.serial = 1 741 | @ca_cert.subject = OpenSSL::X509::Name.new [%w(CN TestCA)] 742 | @ca_cert.issuer = @ca_cert.subject 743 | @ca_cert.public_key = @ca_key.public_key 744 | @ca_cert.not_before = Time.now 745 | @ca_cert.not_after = @ca_cert.not_before + 365 * 24 * 60 * 60 746 | ef = OpenSSL::X509::ExtensionFactory.new 747 | ef.subject_certificate = @ca_cert 748 | ef.issuer_certificate = @ca_cert 749 | @ca_cert.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true)) 750 | @ca_cert.add_extension(ef.create_extension('keyUsage', 'keyCertSign, cRLSign', true)) 751 | @ca_cert.add_extension(ef.create_extension('subjectKeyIdentifier', 'hash', false)) 752 | @ca_cert.add_extension(ef.create_extension('authorityKeyIdentifier', 'keyid:always', false)) 753 | @ca_cert.sign(@ca_key, OpenSSL::Digest::SHA256.new) 754 | 755 | @info = { 'validity' => 8, 'issuer' => @ca_cert } 756 | 757 | @crl = gen_x509_crl(@ca_key, @info) 758 | @revoke_info = { 'serial' => 1, 'reason' => 0 } 759 | end 760 | 761 | context 'When the revoke_info given is anything other then a Ruby Hash' do 762 | it 'Raises a TypeError' do 763 | expect do 764 | instance.revoke_x509_crl('abc', @crl, @ca_key, @info) 765 | end.to raise_error(TypeError) 766 | end 767 | end 768 | 769 | context 'When the CRL given is anything other then a Ruby OpenSSL::X509::CRL object' do 770 | it 'Raises a TypeError' do 771 | expect do 772 | instance.revoke_x509_crl(@revoke_info, 'abc', @ca_key, @info) 773 | end.to raise_error(TypeError) 774 | end 775 | end 776 | 777 | context 'When the CA private key given is anything other then a Ruby OpenSSL::PKey::EC object or a OpenSSL::PKey::RSA object' do 778 | it 'Raises a TypeError' do 779 | expect do 780 | instance.revoke_x509_crl(@revoke_info, @crl, 'abc', @info) 781 | end.to raise_error(TypeError) 782 | end 783 | end 784 | 785 | context 'When the info given is anything other then a Ruby Hash' do 786 | it 'Raises a TypeError' do 787 | expect do 788 | instance.revoke_x509_crl(@revoke_info, @crl, @ca_key, 'abc') 789 | end.to raise_error(TypeError) 790 | end 791 | end 792 | 793 | context 'When a misformatted revoke_info Ruby Hash is given' do 794 | it 'Raises a ArgumentError' do 795 | expect do 796 | instance.revoke_x509_crl({ 'abc' => 'def', 'ghi' => 'jkl' }, @crl, @ca_key, @info) 797 | end.to raise_error(ArgumentError) 798 | end 799 | 800 | it 'Raises a TypeError' do 801 | expect do 802 | instance.revoke_x509_crl({ 'serial' => [], 'reason' => 0 }, @crl, @ca_key, @info) 803 | end.to raise_error(TypeError) 804 | end 805 | 806 | it 'Raises a TypeError' do 807 | expect do 808 | instance.revoke_x509_crl({ 'serial' => 1, 'reason' => 'abc' }, @crl, @ca_key, @info) 809 | end.to raise_error(TypeError) 810 | end 811 | end 812 | 813 | context 'When a misformatted info Ruby Hash is given' do 814 | it 'Raises a ArgumentError' do 815 | expect do 816 | instance.revoke_x509_crl(@revoke_info, @crl, @ca_key, 'abc' => 'def', 'validity' => 8) 817 | end.to raise_error(ArgumentError) 818 | end 819 | 820 | it 'Raises a TypeError' do 821 | expect do 822 | instance.revoke_x509_crl(@revoke_info, @crl, @ca_key, 'issuer' => 'abc', 'validity' => 8) 823 | end.to raise_error(TypeError) 824 | end 825 | 826 | it 'Raises a TypeError' do 827 | expect do 828 | instance.revoke_x509_crl(@revoke_info, @crl, @ca_key, 'issuer' => @ca_cert, 'validity' => 'abc') 829 | end.to raise_error(TypeError) 830 | end 831 | end 832 | 833 | context 'When given valid parameters to revoke a Serial in a CRL' do 834 | it 'Revoke a Serial in a CRL' do 835 | @crl_with_revoked_serial = instance.revoke_x509_crl(@revoke_info, @crl, @ca_key, @info) 836 | expect(@crl_with_revoked_serial).to be_kind_of(OpenSSL::X509::CRL) 837 | expect(OpenSSL::X509::CRL.new(@crl_with_revoked_serial).verify(@ca_key)).to be_truthy 838 | expect(serial_revoked?(@crl_with_revoked_serial, 1)).to be_truthy 839 | end 840 | end 841 | end 842 | end 843 | -------------------------------------------------------------------------------- /spec/unit/libraries/random_password_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require_relative '../../../libraries/random_password' 3 | 4 | describe OpenSSLCookbook::RandomPassword do 5 | let(:instance) do 6 | Class.new { include OpenSSLCookbook::RandomPassword }.new 7 | end 8 | 9 | describe '.included' do 10 | it 'requires securerandom' do 11 | instance 12 | expect(defined?(SecureRandom)).to_not be(false) 13 | end 14 | end 15 | 16 | describe '#random_password' do 17 | context 'with no options' do 18 | it 'returns a random hex password' do 19 | expect(instance.random_password).to match(/[a-z0-9]{20}/) 20 | end 21 | end 22 | 23 | context 'with the :length option' do 24 | it 'returns a password with the given length' do 25 | expect(instance.random_password(length: 50).size).to eq(50) 26 | end 27 | end 28 | 29 | context 'with the :mode option' do 30 | it 'returns a :hex password with :hex' do 31 | expect(instance.random_password(mode: :hex)).to match(/[a-z0-9]{20}/) 32 | end 33 | 34 | it 'returns a :base64 password with :base64' do 35 | expect(instance.random_password(mode: :base64).size).to eq(20) 36 | end 37 | 38 | it 'returns a :random_bytes password with :random_bytes' do 39 | # There's nothing to really assert here, since the length can vary 40 | # depending on the encoding and whatnot... 41 | expect { instance.random_password(mode: :random_bytes) }.to_not raise_error 42 | end 43 | end 44 | 45 | context 'with the :encoding option' do 46 | it 'returns a password with the forced encoding' do 47 | expect(instance.random_password(encoding: 'ASCII').encoding.to_s).to eq('US-ASCII') 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/unit/recipes/resource_spec.rb: -------------------------------------------------------------------------------- 1 | # This Chefspec test was created by Chef generate 2 | # 3 | # Cookbook:: openssl 4 | # Spec:: resource-dhparam 5 | # 6 | # Author:: Charles Johnson () 7 | # 8 | # Copyright:: 2015-2017, Chef Software, Inc. 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | # 22 | 23 | # Chefspec examples can be found at 24 | # https://github.com/sethvargo/chefspec/tree/master/examples 25 | 26 | require 'spec_helper' 27 | 28 | describe 'test::resources' do 29 | context 'the openssl_dhparam resource:' do 30 | cached(:chef_run) do 31 | runner = ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '16.04', step_into: ['openssl_dhparam']) 32 | runner.converge(described_recipe) 33 | end 34 | 35 | it 'adds a directory resource \'/etc/ssl_test\' with action create' do 36 | expect(chef_run).to create_directory('/etc/ssl_test') 37 | end 38 | 39 | it 'The resource adds a file resource \'/etc/ssl_test/dhparam.pem\' with action create' do 40 | expect(chef_run).to create_file('/etc/ssl_test/dhparam.pem') 41 | end 42 | end 43 | 44 | context 'the openssl_x509_certificate resource:' do 45 | cached(:chef_run) do 46 | runner = ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '16.04', step_into: ['openssl_x509']) 47 | runner.converge(described_recipe) 48 | end 49 | 50 | it 'adds a directory resource \'/etc/ssl_test\' with action create' do 51 | expect(chef_run).to create_directory('/etc/ssl_test') 52 | end 53 | 54 | it 'The resource adds a file resource \'/etc/ssl_test/mycert.crt\' with action create_if_missing' do 55 | expect(chef_run).to create_file_if_missing('/etc/ssl_test/mycert.crt') 56 | end 57 | 58 | it 'The resource adds a file resource \'/etc/ssl_test/mycert.key\' with action create_if_missing' do 59 | expect(chef_run).to create_file_if_missing('/etc/ssl_test/mycert.key') 60 | end 61 | 62 | it 'The resource adds a file resource \'/etc/ssl_test/mycert2.crt\' with action create_if_missing' do 63 | expect(chef_run).to create_file_if_missing('/etc/ssl_test/mycert2.crt') 64 | end 65 | 66 | it 'The resource adds a file resource \'/etc/ssl_test/my_ca.crt\' with action create_if_missing' do 67 | expect(chef_run).to create_file_if_missing('/etc/ssl_test/my_ca.crt') 68 | end 69 | 70 | it 'The resource adds a file resource \'/etc/ssl_test/my_ca.key\' with action create_if_missing' do 71 | expect(chef_run).to create_file_if_missing('/etc/ssl_test/my_ca.crt') 72 | end 73 | end 74 | 75 | context 'the openssl_x509_request resource:' do 76 | cached(:chef_run) do 77 | runner = ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '16.04', step_into: ['openssl_x509_request']) 78 | runner.converge(described_recipe) 79 | end 80 | 81 | it 'adds a directory resource \'/etc/ssl_test\' with action create' do 82 | expect(chef_run).to create_directory('/etc/ssl_test') 83 | end 84 | 85 | it 'The resource adds a file resource \'/etc/ssl_test/my_ec_request.csr\' with action create' do 86 | expect(chef_run).to create_file('/etc/ssl_test/my_ec_request.csr') 87 | end 88 | 89 | it 'The resource adds a file resource \'/etc/ssl_test/my_ec_request.key\' with action create' do 90 | expect(chef_run).to create_file_if_missing('/etc/ssl_test/my_ec_request.key') 91 | end 92 | 93 | it 'The resource adds a file resource \'/etc/ssl_test/my_ec_request2.csr\' with action create' do 94 | expect(chef_run).to create_file('/etc/ssl_test/my_ec_request2.csr') 95 | end 96 | 97 | it 'The resource adds a file resource \'/etc/ssl_test/my_rsa_request.csr\' with action create' do 98 | expect(chef_run).to create_file('/etc/ssl_test/my_rsa_request.csr') 99 | end 100 | 101 | it 'The resource adds a file resource \'/etc/ssl_test/my_rsa_request.key\' with action create' do 102 | expect(chef_run).to create_file_if_missing('/etc/ssl_test/my_rsa_request.key') 103 | end 104 | 105 | it 'The resource adds a file resource \'/etc/ssl_test/my_rsa_request2.csr\' with action create' do 106 | expect(chef_run).to create_file('/etc/ssl_test/my_rsa_request2.csr') 107 | end 108 | end 109 | 110 | context 'the openssl_rsa_private_key resource:' do 111 | cached(:chef_run) do 112 | runner = ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '16.04', step_into: ['openssl_rsa_private_key']) 113 | runner.converge(described_recipe) 114 | end 115 | 116 | it 'adds a directory resource \'/etc/ssl_test\' with action create' do 117 | expect(chef_run).to create_directory('/etc/ssl_test') 118 | end 119 | 120 | it 'The resource adds a file resource \'/etc/ssl_test/rsakey_des3.pem\' with action create' do 121 | expect(chef_run).to create_file('/etc/ssl_test/rsakey_des3.pem') 122 | end 123 | end 124 | 125 | # This does not work at the moment due to the private key not existing 126 | # context 'the openssl_rsa_public_key resource:' do 127 | # cached(:chef_run) do 128 | # runner = ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '16.04', step_into: ['openssl_rsa_public_key']) 129 | # runner.converge(described_recipe) 130 | # end 131 | # 132 | # it 'The resource adds a file resource \'/etc/ssl_test/rsakey_des3.pub\' with action create' do 133 | # expect(chef_run).to create_file('/etc/ssl_test/rsakey_des3.pub') 134 | # end 135 | # end 136 | 137 | context 'the openssl_ec_private_key resource:' do 138 | cached(:chef_run) do 139 | runner = ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '16.04', step_into: ['openssl_ec_private_key']) 140 | runner.converge(described_recipe) 141 | end 142 | 143 | it 'adds a directory resource \'/etc/ssl_test\' with action create' do 144 | expect(chef_run).to create_directory('/etc/ssl_test') 145 | end 146 | 147 | it 'The resource adds a file resource \'/etc/ssl_test/eckey_prime256v1_des3.pem\' with action create' do 148 | expect(chef_run).to create_file('/etc/ssl_test/eckey_prime256v1_des3.pem') 149 | end 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /spec/unit/recipes/upgrade_spec.rb: -------------------------------------------------------------------------------- 1 | # This Chefspec test was created by Chef generate 2 | # 3 | # Cookbook:: openssl 4 | # Spec:: upgrade_spec 5 | # 6 | # Author:: Joshua Timberman () 7 | # Author:: Charles Johnson () 8 | # 9 | # Copyright:: 2015-2017, Chef Software, Inc. 10 | # 11 | # Licensed under the Apache License, Version 2.0 (the "License"); 12 | # you may not use this file except in compliance with the License. 13 | # You may obtain a copy of the License at 14 | # 15 | # http://www.apache.org/licenses/LICENSE-2.0 16 | # 17 | # Unless required by applicable law or agreed to in writing, software 18 | # distributed under the License is distributed on an "AS IS" BASIS, 19 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | # See the License for the specific language governing permissions and 21 | # limitations under the License. 22 | # 23 | 24 | require 'spec_helper' 25 | 26 | describe 'openssl::upgrade' do 27 | context 'When all attributes are default, on Ubuntu 16.04 the recipe:' do 28 | cached(:chef_run) do 29 | runner = ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '16.04') 30 | runner.converge(described_recipe) 31 | end 32 | 33 | it 'installs the correct packages' do 34 | expect(chef_run).to upgrade_package(['libssl1.0.0', 'openssl']) 35 | end 36 | end 37 | 38 | context 'When all attributes are default, on Debian 8 the recipe:' do 39 | cached(:chef_run) do 40 | runner = ChefSpec::ServerRunner.new(platform: 'debian', version: '8.9') 41 | runner.converge(described_recipe) 42 | end 43 | 44 | it 'installs the correct packages' do 45 | expect(chef_run).to upgrade_package(['libssl1.0.0', 'openssl']) 46 | end 47 | end 48 | 49 | context 'When all attributes are default, on Debian 9 the recipe:' do 50 | cached(:chef_run) do 51 | runner = ChefSpec::ServerRunner.new(platform: 'debian', version: '9.1') 52 | runner.converge(described_recipe) 53 | end 54 | 55 | it 'installs the correct packages' do 56 | expect(chef_run).to upgrade_package(['libssl1.0.2', 'openssl']) 57 | end 58 | end 59 | 60 | context 'When all attributes are default, on CentOS, the recipe:' do 61 | cached(:chef_run) do 62 | runner = ChefSpec::ServerRunner.new(platform: 'centos', version: '6.9') 63 | runner.converge(described_recipe) 64 | end 65 | 66 | it 'installs the correct packages' do 67 | expect(chef_run).to upgrade_package(['openssl']) 68 | end 69 | end 70 | 71 | context 'When all attributes are default, on openSUSE, the recipe:' do 72 | cached(:chef_run) do 73 | runner = ChefSpec::ServerRunner.new(platform: 'opensuse', version: '42.3') 74 | runner.converge(described_recipe) 75 | end 76 | 77 | it 'installs the correct packages' do 78 | expect(chef_run).to upgrade_package('openssl') 79 | end 80 | end 81 | 82 | context 'When the [\'openssl\'][\'restart_services\'] array is set to [\'httpd\'], the recipe:' do 83 | cached(:chef_run) do 84 | runner = ChefSpec::ServerRunner.new(platform: 'centos', version: '6.9') 85 | runner.node.normal['openssl']['restart_services'] = ['httpd'] 86 | runner.converge('test::httpd', described_recipe) 87 | end 88 | 89 | it 'The created package resource to upgrade openssl notifies the httpd service resource to restart' do 90 | expect(chef_run.package('openssl')).to notify('service[httpd]').to(:restart) 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/test/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'test' 2 | license 'Apache-2.0' 3 | description 'Installs/Configures test' 4 | version '0.1.0' 5 | depends 'openssl' 6 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/test/recipes/httpd.rb: -------------------------------------------------------------------------------- 1 | # This is used by chefspec only 2 | 3 | service 'httpd' do 4 | action :nothing 5 | end 6 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/test/recipes/resources.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook:: test 3 | # Recipe:: resource_dhparam 4 | # 5 | # Copyright:: 2015-2017, Chef Software, Inc. 6 | # License:: Apache License, Version 2.0 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | 21 | %w( 22 | /etc/ssl_test/rsakey_des3.pem 23 | /etc/ssl_test/rsakey_aes128cbc.pem 24 | /etc/ssl_test/private_key.pem 25 | /etc/ssl_test/rsakey_des3.pub 26 | /etc/ssl_test/rsakey_2.pub 27 | /etc/ssl_test/eckey_prime256v1_des3.pem 28 | /etc/ssl_test/eckey_prime256v1_des3.pub 29 | /etc/ssl_test/eckey_prime256v1_des3_2.pub 30 | /etc/ssl_test/dhparam.pem 31 | /etc/ssl_test/mycert.crt 32 | /etc/ssl_test/mycert.key 33 | /etc/ssl_test/mycert2.crt 34 | /etc/ssl_test/my_ca.crt 35 | /etc/ssl_test/my_ca.key 36 | /etc/ssl_test/my_signed_cert.crt 37 | /etc/ssl_test/my_signed_cert.key 38 | /etc/ssl_test/my_ca2.key 39 | /etc/ssl_test/my_ca2.csr 40 | /etc/ssl_test/my_ca2.crt 41 | /etc/ssl_test/my_ca2.crl 42 | /etc/ssl_test/my_signed_cert2.key 43 | /etc/ssl_test/my_signed_cert2.csr 44 | /etc/ssl_test/my_signed_cert2.crt 45 | /etc/ssl_test/my_ec_request.csr 46 | /etc/ssl_test/my_ec_request.key 47 | /etc/ssl_test/my_ec_request2.csr 48 | /etc/ssl_test/my_rsa_request.csr 49 | /etc/ssl_test/my_rsa_request.key 50 | /etc/ssl_test/my_rsa_request2.csr 51 | ).each do |f| 52 | file "delete existing test file #{f}" do 53 | path f 54 | action :delete 55 | end 56 | end 57 | 58 | # Create directory if not already present 59 | directory '/etc/ssl_test' do 60 | recursive true 61 | end 62 | 63 | # 64 | # DHPARAM HERE 65 | # 66 | 67 | # Generate new key and certificate 68 | openssl_dhparam '/etc/ssl_test/dhparam.pem' do 69 | key_length 1024 70 | action :create 71 | end 72 | 73 | # 74 | # RSA KEYS HERE 75 | # 76 | 77 | # Generate new key with des3 cipher using the new resource name 78 | openssl_rsa_private_key '/etc/ssl_test/rsakey_des3.pem' do 79 | key_length 2048 80 | action :create 81 | end 82 | 83 | # Generate new key with aes-128-cbc cipher with the old resource name 84 | openssl_rsa_key '/etc/ssl_test/rsakey_aes128cbc.pem' do 85 | key_length 1024 86 | key_cipher 'aes-128-cbc' 87 | action :create 88 | end 89 | 90 | # we need to do this with a file resource so that chefspec stepping 91 | # into openssl_rsa_public_key can function. It's :( 92 | file '/etc/ssl_test/private_key.pem' do 93 | content '-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,1F2FDA436115C4EE W24gBmtq/Eik2FkSdBh3hF3th3gFq2lMZqSLbho/JVbHFpAQynDbcS9qH5x1fRkt Y7o4A/Sh7noy9kzC1eVIPaQpKFJu5da+uf3t1KxpVMqibzeIE33P9WI+5PzzOm5W xs9shvv/0anU6UMsqBqI+0cmQQ8lw3myTTpO9yWKav2FdTnx7svd+P6BmFknGQaM DYomD0qiB/JzjXbYHLgFspPQXHdyQGhe/YFMlvmjKE0Nut18XJsNwUTWjBA4nRj4 JdlE8XOkWrzIsWKfrBhuhx9bTD0ZVvgssYl2QEh26mv0P0nxx4V/zYx+9U5j0L7q tV4FXfQTgFyctKySuBNi8IT1HFqG9LQps14p8q0XeRigFsRUOVuR0S3eHqg7xiiW QVdF+LgYPpdVNX2mHOSFnHMpFdKLHs8VCNjcGwMNK7avKbne/TJ2NRcL4uhgpsX/ 4tg1kQlwIwtp8MlMqkcinHJ3fjIhWGgjNBVe85NJPVogRDy+c80SqBenaJSavwVA ytmiQOCeon4zhZdscESki+KmsyOWkPB9/zQK76E4ni2IVOL6ZYBMJNTkP47WmA9d Etv7UMxQMI6EYMEH43czvbe4bNCC+hlYotJUM2B52Al7I79W9sSy8cmYi3YZEl0G xtKgY7XwstUBD2XjMuaNyUT0EDjcoa0GhLJSCQkvgn8//BGKaLEyb+Lr+dmHGvxM phCnUKLkfZn9hAFempSJuW4iSaeBKIU3KgYOkBooTuYhXqbN2McoxH6Ec/gnAM5e TIaLiDaHY8IPI4Et5l0sr7v+YF3ZGKC1fL6k4eInNRlhy8oWsFMe79jKkh5wRflt WifTbEdy3D53pVH5lbXyJwpBIOjKJ0OqGWGegu02P5JTsAsniKD+jxNUS8iSOAXL gtpMe4jtqj38hb9D7pBir85Hm+uDqeEuwUqSXAiI+P2F/Jf4ep3h+ek8dcgZtkJQ 3iz92ic2g3M7HW+EE0JcBX+KBwU7yI+UJbWvNQmTXUAYbpoQOLIVm/TrFdGzZ6e9 t0T5wmkE2cS9C3QYiEc7D81nTcTadZChZJDURzUk2REwRGjnunQggHUsj/JKVWqO EPZbpgyDhCaIAkkloWK/SgKny4irMZClhVdeq+v55vDf9nbKR9bgHUb2ZNwp6DQc CPs1BteYthiLtILYzzasMKhlfdoUjEaYziYLGkAQca5XwvwEp0qWg0sMCUUL9pbW 9WzFELBvqNQ1WyIcjb4clcvM0fJdGZ2nKbCAw6zbeSQcGd50NzvTra0xE/J2q6Jo 0V6AGr1Zmu4bJ+tGZCdAIteEO2TosNfS6nrFy15DAe4M4+77ZUGJ8rcwOBopa9qI w7aAyPlfAhrtdSrbOLLp0kRP9EwzSIjSoqc/YJINaNMN8WM7JgnfklmPToT2AqPc 6MOX/Uktag6AXzjcQDtIZSQox326emX1o/huw+7z3/lSXgTdxm3brew/is+9iaQh 5katqPtbec+K/4qydINZSRRFPaoVkg27+6OXvd1AbVS7jmUGHL20xyzA0A9c1csN dm460w4eqbjJEUtDucyIhLPhtYJwPODoRitRmIrzF5DSPrgmSiG93TPiDpRfVPPU -----END RSA PRIVATE KEY-----' 94 | end 95 | 96 | openssl_rsa_public_key '/etc/ssl_test/rsakey_des3.pub' do 97 | private_key_path '/etc/ssl_test/rsakey_des3.pem' 98 | private_key_pass 'something' 99 | action :create 100 | end 101 | 102 | openssl_rsa_public_key '/etc/ssl_test/rsakey_2.pub' do 103 | private_key_pass 'something' 104 | private_key_content "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,5EE0AE9A5FE3342E\n\nyb930kj5/4/nd738dPx6XdbDrMCvqkldaz0rHNw8xsWvwARrl/QSPwROG3WY7ROl\nEUttVlLaeVaqRPfQbmTUfzGI8kTMmDWKjw52gJUx2YJTYRgMHAB0dzYIRjeZAaeS\nypXnEfouVav+jKTmmehr1WuVKbzRhQDBSalzeUwsPi2+fb3Bfuo1dRW6xt8yFuc4\nAkv1hCglymPzPHE2L0nSGjcgA2DZu+/S8/wZ4E63442NHPzO4VlLvpNvJrYpEWq9\nB5mJzcdXPeOTjqd13olNTlOZMaKxu9QShu50GreCTVsl8VRkK8NtwbWuPGBZlIFa\njzlS/RaLuzNzfajaKMkcIYco9t7gN2DwnsACHKqEYT8248Ii3NQ+9/M5YcmpywQj\nWGr0UFCSAdCky1lRjwT+zGQKohr+dVR1GaLem+rSZH94df4YBxDYw4rjsKoEhvXB\nv2Vlx+G7Vl2NFiZzxUKh3MvQLr/NDElpG1pYWDiE0DIG13UqEG++cS870mcEyfFh\nSF2SXYHLWyAhDK0viRDChJyFMduC4E7a2P9DJhL3ZvM0KZ1SLMwROc1XuZ704GwO\nYUqtCX5OOIsTti1Z74jQm9uWFikhgWByhVtu6sYL1YTqtiPJDMFhA560zp/k/qLO\nFKiM4eUWV8AI8AVwT6A4o45N2Ru8S48NQyvh/ADFNrgJbVSeDoYE23+DYKpzbaW9\n00BD/EmUQqaQMc670vmI+CIdcdE7L1zqD6MZN7wtPaRIjx4FJBGsFoeDShr+LoTD\nrwbadwrbc2Rf4DWlvFwLJ4pvNvdtY3wtBu79UCOol0+t8DVVSPVASsh+tp8XncDE\nKRljj88WwBjX7/YlRWvQpe5y2UrsHI0pNy8TA1Xkf6GPr6aS2TvQD5gOrAVReSse\n/kktCzZQotjmY1odvo90Zi6A9NCzkI4ZLgAuhiKDPhxZg61IeLppnfFw0v3H4331\nV9SMYgr1Ftov0++x7q9hFPIHwZp6NHHOhdHNI80XkHqtY/hEvsh7MhFMYCgSY1pa\nK/gMcZ/5Wdg9LwOK6nYRmtPtg6fuqj+jB3Rue5/p9dt4kfom4etCSeJPdvP1Mx2I\neNmyQ/7JN9N87FsfZsIj5OK9OB0fPdj0N0m1mlHM/mFt5UM5x39u13QkCt7skEF+\nyOptXcL629/xwm8eg4EXnKFk330WcYSw+sYmAQ9ZTsBxpCMkz0K4PBTPWWXx63XS\nc4J0r88kbCkMCNv41of8ceeGzFrC74dG7i3IUqZzMzRP8cFeps8auhweUHD2hULs\nXwwtII0YQ6/Fw4hgGQ5//0ASdvAicvH0l1jOQScHzXC2QWNg3GttueB/kmhMeGGm\nsHOJ1rXQ4oEckFvBHOvzjP3kuRHSWFYDx35RjWLAwLCG9odQUApHjLBgFNg9yOR0\njW9a2SGxRvBAfdjTa9ZBBrbjlaF57hq7mXws90P88RpAL+xxCAZUElqeW2Rb2rQ6\nCbz4/AtPekV1CYVodGkPutOsew2zjNqlNH+M8XzfonA60UAH20TEqAgLKwgfgr+a\nc+rXp1AupBxat4EHYJiwXBB9XcVwyp5Z+/dXsYmLXzoMOnp8OFyQ9H8R7y9Y0PEu\n-----END RSA PRIVATE KEY-----\n" 105 | action :create 106 | end 107 | 108 | # 109 | # EC KEYS HERE 110 | # 111 | 112 | # Generate a new ec key with key_curve prime256v1 and des3 cipher 113 | openssl_ec_private_key '/etc/ssl_test/eckey_prime256v1_des3.pem' do 114 | key_curve 'prime256v1' 115 | key_pass 'something' 116 | action :create 117 | end 118 | 119 | openssl_ec_public_key '/etc/ssl_test/eckey_prime256v1_des3.pub' do 120 | private_key_path '/etc/ssl_test/eckey_prime256v1_des3.pem' 121 | private_key_pass 'something' 122 | action :create 123 | end 124 | 125 | openssl_ec_public_key '/etc/ssl_test/eckey_prime256v1_des3_2.pub' do 126 | private_key_content "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEII2VAU9re44mAUzYPWCg+qqwdmP8CplsEg0b/DYPXLg2oAoGCCqGSM49\nAwEHoUQDQgAEKkpMCbIQ2C6Qlp/B+Odp1a9Y06Sm8yqPvCVIkWYP7M8PX5+RmoIv\njGBVf/+mVBx77ji3NpTilMUt2KPZ87lZ3w==\n-----END EC PRIVATE KEY-----\n" 127 | action :create 128 | end 129 | 130 | # 131 | # X509_CERTIFICATE HERE 132 | # 133 | 134 | # Generate new key and certificate 135 | openssl_x509 '/etc/ssl_test/mycert.crt' do 136 | common_name 'mycert.example.com' 137 | org 'Test Kitchen Example' 138 | org_unit 'Kitchens' 139 | country 'UK' 140 | subject_alt_name ['IP:127.0.0.1', 'DNS:localhost.localdomain'] 141 | end 142 | 143 | # Generate a new certificate from an existing key 144 | openssl_x509 '/etc/ssl_test/mycert2.crt' do 145 | common_name 'mycert2.example.com' 146 | org 'Test Kitchen Example' 147 | org_unit 'Kitchens' 148 | country 'UK' 149 | key_file '/etc/ssl_test/mycert.key' 150 | end 151 | 152 | # Generate a new CA certificate 153 | openssl_x509 '/etc/ssl_test/my_ca.crt' do 154 | common_name 'CA' 155 | expire 3650 156 | extensions( 157 | 'keyUsage' => { 158 | 'values' => %w( 159 | keyCertSign 160 | keyEncipherment 161 | digitalSignature 162 | cRLSign), 163 | 'critical' => true, 164 | } 165 | ) 166 | end 167 | 168 | # Generate and sign a certificate with the CA 169 | openssl_x509_certificate '/etc/ssl_test/my_signed_cert.crt' do 170 | common_name 'mysignedcert.example.com' 171 | ca_key_file '/etc/ssl_test/my_ca.key' 172 | ca_cert_file '/etc/ssl_test/my_ca.crt' 173 | expire 365 174 | extensions( 175 | 'keyUsage' => { 176 | 'values' => %w( 177 | keyEncipherment 178 | digitalSignature), 179 | 'critical' => true, 180 | }, 181 | 'extendedKeyUsage' => { 182 | 'values' => %w(serverAuth), 183 | 'critical' => false, 184 | } 185 | ) 186 | subject_alt_name ['IP:127.0.0.1', 'DNS:localhost.localdomain'] 187 | end 188 | 189 | # Generate CA with CSR and EC key 190 | openssl_ec_private_key '/etc/ssl_test/my_ca2.key' do 191 | mode '0400' 192 | key_curve 'secp521r1' 193 | end 194 | 195 | openssl_x509_request '/etc/ssl_test/my_ca2.csr' do 196 | common_name 'CA2' 197 | key_file '/etc/ssl_test/my_ca2.key' 198 | action :create 199 | end 200 | 201 | openssl_x509_certificate '/etc/ssl_test/my_ca2.crt' do 202 | csr_file '/etc/ssl_test/my_ca2.csr' 203 | ca_key_file '/etc/ssl_test/my_ca2.key' 204 | expire 3650 205 | extensions( 206 | 'keyUsage' => { 207 | 'values' => %w( 208 | keyCertSign 209 | keyEncipherment 210 | digitalSignature 211 | cRLSign), 212 | 'critical' => true, 213 | } 214 | ) 215 | end 216 | 217 | # Generate key, csr & sign it with CA 218 | openssl_ec_private_key '/etc/ssl_test/my_signed_cert2.key' 219 | 220 | openssl_x509_request '/etc/ssl_test/my_signed_cert2.csr' do 221 | common_name 'mysignedcert2.example.com' 222 | org 'Test Kitchen Example' 223 | org_unit 'Kitchens' 224 | country 'UK' 225 | key_file '/etc/ssl_test/my_signed_cert2.key' 226 | end 227 | 228 | openssl_x509_certificate '/etc/ssl_test/my_signed_cert2.crt' do 229 | csr_file '/etc/ssl_test/my_signed_cert2.csr' 230 | ca_key_file '/etc/ssl_test/my_ca2.key' 231 | ca_cert_file '/etc/ssl_test/my_ca2.crt' 232 | expire 365 233 | extensions( 234 | 'keyUsage' => { 235 | 'values' => %w( 236 | keyEncipherment 237 | digitalSignature), 238 | 'critical' => true, 239 | }, 240 | 'extendedKeyUsage' => { 241 | 'values' => %w(serverAuth), 242 | 'critical' => false, 243 | } 244 | ) 245 | subject_alt_name ['IP:127.0.0.1', 'DNS:localhost.localdomain'] 246 | end 247 | 248 | # 249 | # X509_CRL HERE 250 | # 251 | 252 | openssl_x509_crl '/etc/ssl_test/my_ca2.crl' do 253 | ca_cert_file '/etc/ssl_test/my_ca2.crt' 254 | ca_key_file '/etc/ssl_test/my_ca2.key' 255 | expire 1 256 | end 257 | 258 | openssl_x509_crl '/etc/ssl_test/my_ca2.crl' do 259 | ca_cert_file '/etc/ssl_test/my_ca2.crt' 260 | ca_key_file '/etc/ssl_test/my_ca2.key' 261 | renewal_threshold 2 262 | end 263 | 264 | openssl_x509_crl '/etc/ssl_test/my_ca2.crl' do 265 | ca_cert_file '/etc/ssl_test/my_ca2.crt' 266 | ca_key_file '/etc/ssl_test/my_ca2.key' 267 | serial_to_revoke 'C7BCB6602A2E4251EF4E2827A228CB52BC0CEA2F' 268 | end 269 | 270 | # 271 | # X509_REQUEST HERE 272 | # 273 | 274 | # Generate new ec key and csr 275 | openssl_x509_request '/etc/ssl_test/my_ec_request.csr' do 276 | common_name 'myecrequest.example.com' 277 | org 'Test Kitchen Example' 278 | org_unit 'Kitchens' 279 | country 'UK' 280 | end 281 | 282 | # Generate a new csr from an existing ec key 283 | openssl_x509_request '/etc/ssl_test/my_ec_request2.csr' do 284 | common_name 'myecrequest2.example.com' 285 | org 'Test Kitchen Example' 286 | org_unit 'Kitchens' 287 | country 'UK' 288 | key_file '/etc/ssl_test/my_ec_request.key' 289 | end 290 | 291 | # Generate new rsa key and csr 292 | openssl_x509_request '/etc/ssl_test/my_rsa_request.csr' do 293 | common_name 'myrsarequest.example.com' 294 | org 'Test Kitchen Example' 295 | org_unit 'Kitchens' 296 | country 'UK' 297 | key_type 'rsa' 298 | end 299 | 300 | # Generate a new certificate from an existing rsa key 301 | openssl_x509_request '/etc/ssl_test/my_rsa_request2.csr' do 302 | common_name 'myrsarequest2.example.com' 303 | org 'Test Kitchen Example' 304 | org_unit 'Kitchens' 305 | country 'UK' 306 | key_file '/etc/ssl_test/my_rsa_request.key' 307 | end 308 | -------------------------------------------------------------------------------- /test/fixtures/cookbooks/test/recipes/upgrade.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook:: test 3 | # Recipe:: upgrade 4 | # 5 | # Copyright:: 2015-2017, Chef Software, Inc. 6 | # License:: Apache License, Version 2.0 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | 21 | apt_update 'update' 22 | 23 | include_recipe 'openssl::upgrade' 24 | -------------------------------------------------------------------------------- /test/integration/resources/resources_spec.rb: -------------------------------------------------------------------------------- 1 | # PRIVATE/PUBLIC KEYS 2 | 3 | describe key_rsa('/etc/ssl_test/rsakey_des3.pem') do 4 | it { should be_private } 5 | its('key_length') { should eq 2048 } 6 | end 7 | 8 | describe key_rsa('/etc/ssl_test/rsakey_aes128cbc.pem') do 9 | it { should be_private } 10 | its('key_length') { should eq 1024 } 11 | end 12 | 13 | describe command('openssl ec -in /etc/ssl_test/eckey_prime256v1_des3.pem -text -noout -passin pass:"something"') do 14 | its('exit_status') { should eq 0 } 15 | its('stdout') { should match /prime256v1/ } 16 | end 17 | 18 | describe command('openssl ec -in /etc/ssl_test/eckey_prime256v1_des3.pem -passin pass:"something" -pubout -out /tmp/ec_pub && diff /etc/ssl_test/eckey_prime256v1_des3.pub /tmp/ec_pub') do 19 | its('exit_status') { should eq 0 } 20 | end 21 | 22 | describe command('openssl dhparam -in /etc/ssl_test/dhparam.pem -check -noout') do 23 | its('exit_status') { should eq 0 } 24 | end 25 | 26 | describe command('openssl rsa -in /etc/ssl_test/mycert.key -check -noout') do 27 | its('exit_status') { should eq 0 } 28 | end 29 | 30 | # X509 CERTIFICATES 31 | 32 | # mycert.example.com private key 33 | 34 | describe file('/etc/ssl_test/mycert.key') do 35 | it { should exist } 36 | its('mode') { should cmp '0644' } 37 | its('group') { should eq 'root' } 38 | its('owner') { should eq 'root' } 39 | end 40 | 41 | describe command('openssl rsa -in /etc/ssl_test/mycert.key -check -noout') do 42 | its('exit_status') { should eq 0 } 43 | end 44 | 45 | # mycert.example.com certificate 46 | 47 | describe file('/etc/ssl_test/mycert.crt') do 48 | it { should exist } 49 | its('mode') { should cmp '0644' } 50 | its('group') { should eq 'root' } 51 | its('owner') { should eq 'root' } 52 | end 53 | 54 | describe x509_certificate('/etc/ssl_test/mycert.crt') do 55 | its('subject_dn') { should match '/C=UK/O=Test Kitchen Example/OU=Kitchens/CN=mycert.example.com' } 56 | its('issuer_dn') { should eq '/C=UK/O=Test Kitchen Example/OU=Kitchens/CN=mycert.example.com' } 57 | its('signature_algorithm') { should match /sha256WithRSAEncryption/ } 58 | its('validity_in_days') { should be <= 365 } 59 | its('validity_in_days') { should be >= 364 } 60 | its('version') { should eq 2 } 61 | its('extensions.basicConstraints') { should match ['critical', 'CA:TRUE'] } 62 | its('extensions.subjectAltName') { should include 'DNS:localhost.localdomain' } 63 | its('extensions.subjectAltName') { should include 'IP Address:127.0.0.1' } 64 | end 65 | 66 | # mycert2.example.com certificate 67 | 68 | describe file('/etc/ssl_test/mycert2.crt') do 69 | it { should exist } 70 | its('mode') { should cmp '0644' } 71 | its('group') { should eq 'root' } 72 | its('owner') { should eq 'root' } 73 | end 74 | 75 | describe x509_certificate('/etc/ssl_test/mycert2.crt') do 76 | its('subject_dn') { should match '/C=UK/O=Test Kitchen Example/OU=Kitchens/CN=mycert2.example.com' } 77 | its('issuer_dn') { should eq '/C=UK/O=Test Kitchen Example/OU=Kitchens/CN=mycert2.example.com' } 78 | its('signature_algorithm') { should match /sha256WithRSAEncryption/ } 79 | its('validity_in_days') { should be <= 365 } 80 | its('validity_in_days') { should be >= 364 } 81 | its('version') { should eq 2 } 82 | its('extensions.basicConstraints') { should match ['critical', 'CA:TRUE'] } 83 | end 84 | 85 | # CA private key 86 | 87 | describe file('/etc/ssl_test/my_ca.key') do 88 | it { should exist } 89 | its('mode') { should cmp '0644' } 90 | its('group') { should eq 'root' } 91 | its('owner') { should eq 'root' } 92 | end 93 | 94 | describe command('openssl rsa -in /etc/ssl_test/mycert.key -check -noout') do 95 | its('exit_status') { should eq 0 } 96 | end 97 | 98 | # CA certificate 99 | 100 | describe file('/etc/ssl_test/my_ca.crt') do 101 | it { should exist } 102 | its('mode') { should cmp '0644' } 103 | its('group') { should eq 'root' } 104 | its('owner') { should eq 'root' } 105 | end 106 | 107 | describe x509_certificate('/etc/ssl_test/my_ca.crt') do 108 | its('subject_dn') { should match '/CN=CA' } 109 | its('issuer_dn') { should eq '/CN=CA' } 110 | its('signature_algorithm') { should match /sha256WithRSAEncryption/ } 111 | its('validity_in_days') { should be <= 3650 } 112 | its('validity_in_days') { should be >= 3649 } 113 | its('version') { should eq 2 } 114 | its('extensions.basicConstraints') { should match ['critical', 'CA:TRUE'] } 115 | its('extensions.keyUsage') { should match ['critical', 'Digital Signature', 'Key Encipherment', 'Certificate Sign', 'CRL Sign'] } 116 | end 117 | 118 | # mysignedcert.example.com private key 119 | 120 | describe file('/etc/ssl_test/my_signed_cert.key') do 121 | it { should exist } 122 | its('mode') { should cmp '0644' } 123 | its('group') { should eq 'root' } 124 | its('owner') { should eq 'root' } 125 | end 126 | 127 | describe command('openssl rsa -in /etc/ssl_test/my_signed_cert.key -noout -check') do 128 | its('exit_status') { should eq 0 } 129 | end 130 | 131 | # mysignedcert.example.com cert 132 | 133 | describe file('/etc/ssl_test/my_signed_cert.crt') do 134 | it { should exist } 135 | its('mode') { should cmp '0644' } 136 | its('group') { should eq 'root' } 137 | its('owner') { should eq 'root' } 138 | end 139 | 140 | describe x509_certificate('/etc/ssl_test/my_signed_cert.crt') do 141 | its('subject_dn') { should match 'CN=mysignedcert.example.com' } 142 | its('issuer_dn') { should eq '/CN=CA' } 143 | its('signature_algorithm') { should match /sha256WithRSAEncryption/ } 144 | its('validity_in_days') { should be <= 365 } 145 | its('validity_in_days') { should be >= 364 } 146 | its('version') { should eq 2 } 147 | its('extensions.extendedKeyUsage') { should match ['TLS Web Server Authentication'] } 148 | its('extensions.keyUsage') { should match ['critical', 'Digital Signature', 'Key Encipherment'] } 149 | its('extensions.subjectAltName') { should include 'DNS:localhost.localdomain' } 150 | its('extensions.subjectAltName') { should include 'IP Address:127.0.0.1' } 151 | end 152 | 153 | # CA2 private key 154 | 155 | describe file('/etc/ssl_test/my_ca2.key') do 156 | it { should exist } 157 | its('mode') { should cmp '0400' } 158 | its('group') { should eq 'root' } 159 | its('owner') { should eq 'root' } 160 | end 161 | 162 | describe command('openssl ec -in /etc/ssl_test/my_ca2.key -noout -text') do 163 | its('exit_status') { should eq 0 } 164 | its('stdout') { should match /secp521r1/ } 165 | end 166 | 167 | # CA2 certificate 168 | 169 | describe file('/etc/ssl_test/my_ca2.crt') do 170 | it { should exist } 171 | its('mode') { should cmp '0644' } 172 | its('group') { should eq 'root' } 173 | its('owner') { should eq 'root' } 174 | end 175 | 176 | describe x509_certificate('/etc/ssl_test/my_ca2.crt') do 177 | its('subject_dn') { should match 'CN=CA2' } 178 | its('issuer_dn') { should eq '/CN=CA2' } 179 | its('signature_algorithm') { should match /ecdsa-with-SHA256/ } 180 | its('validity_in_days') { should be <= 3650 } 181 | its('validity_in_days') { should be >= 3649 } 182 | its('version') { should eq 2 } 183 | its('extensions.keyUsage') { should match ['critical', 'Digital Signature', 'Key Encipherment', 'Certificate Sign', 'CRL Sign'] } 184 | its('extensions.basicConstraints') { should match ['critical', 'CA:TRUE'] } 185 | end 186 | 187 | # mysignedcert2.example.com private key 188 | 189 | describe file('/etc/ssl_test/my_signed_cert2.key') do 190 | it { should exist } 191 | its('mode') { should cmp '0640' } 192 | its('group') { should eq 'root' } 193 | its('owner') { should eq 'root' } 194 | end 195 | 196 | describe command('openssl ec -in /etc/ssl_test/my_signed_cert2.key -noout -text') do 197 | its('exit_status') { should eq 0 } 198 | its('stdout') { should match /prime256v1/ } 199 | end 200 | 201 | # mysignedcert2.example.com certificate 202 | 203 | describe file('/etc/ssl_test/my_signed_cert2.crt') do 204 | it { should exist } 205 | its('mode') { should cmp '0644' } 206 | its('group') { should eq 'root' } 207 | its('owner') { should eq 'root' } 208 | end 209 | 210 | describe x509_certificate('/etc/ssl_test/my_signed_cert2.crt') do 211 | its('subject_dn') { should match 'C=UK/O=Test Kitchen Example/OU=Kitchens/CN=mysignedcert2.example.com' } 212 | its('issuer_dn') { should eq '/CN=CA2' } 213 | its('signature_algorithm') { should match /ecdsa-with-SHA256/ } 214 | its('validity_in_days') { should be <= 365 } 215 | its('validity_in_days') { should be >= 364 } 216 | its('version') { should eq 2 } 217 | its('extensions.extendedKeyUsage') { should match ['TLS Web Server Authentication'] } 218 | its('extensions.keyUsage') { should match ['critical', 'Digital Signature', 'Key Encipherment'] } 219 | its('extensions.subjectAltName') { should include 'DNS:localhost.localdomain' } 220 | its('extensions.subjectAltName') { should include 'IP Address:127.0.0.1' } 221 | end 222 | 223 | # X509 CRL 224 | describe command('openssl crl -in /etc/ssl_test/my_ca2.crl -text -noout | grep Serial') do 225 | its('exit_status') { should eq 0 } 226 | its('stdout') { should match /C7BCB6602A2E4251EF4E2827A228CB52BC0CEA2F/ } 227 | end 228 | 229 | # X509 REQUESTS 230 | 231 | describe command('openssl ec -in /etc/ssl_test/my_ec_request.key -text -noout') do 232 | its('exit_status') { should eq 0 } 233 | end 234 | 235 | describe command('openssl rsa -in /etc/ssl_test/my_rsa_request.key -check -noout') do 236 | its('exit_status') { should eq 0 } 237 | end 238 | 239 | describe command('openssl req -text -noout -verify -in /etc/ssl_test/my_ec_request.csr') do 240 | its('exit_status') { should eq 0 } 241 | end 242 | 243 | describe command('openssl req -text -noout -verify -in /etc/ssl_test/my_ec_request2.csr') do 244 | its('exit_status') { should eq 0 } 245 | end 246 | 247 | describe command('openssl req -text -noout -verify -in /etc/ssl_test/my_rsa_request.csr') do 248 | its('exit_status') { should eq 0 } 249 | end 250 | 251 | describe command('openssl req -text -noout -verify -in /etc/ssl_test/my_rsa_request2.csr') do 252 | its('exit_status') { should eq 0 } 253 | end 254 | --------------------------------------------------------------------------------