From 4eae2e621944541a0f1aac5a38de1b6f1a22f813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix-Antoine=20Fortin?= Date: Tue, 30 Apr 2024 20:09:53 -0400 Subject: [PATCH 1/7] Execute test action on all branches --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 67eeb08..94b8447 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: pull_request: {} push: branches: - - master + - '*' env: BUNDLE_WITHOUT: release From 331cd2f74ffa8d442e00227e3b4b60f5f4a35f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix-Antoine=20Fortin?= Date: Wed, 1 May 2024 08:07:38 -0400 Subject: [PATCH 2/7] Remove Ruby 2.6 from tests Every combination including Ruby 2.6 was excluded already. --- .github/workflows/test.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94b8447..cad69db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,6 @@ jobs: fail-fast: false matrix: ruby: - - "2.6" - "2.7" - "3.0" - "3.1" @@ -30,18 +29,10 @@ jobs: puppet: "~> 8.0" - ruby: "2.7" puppet: "~> 8.0" - - ruby: "2.6" - puppet: "~> 8.0" - - - ruby: "2.6" - puppet: "~> 7.24" - - ruby: "3.0" puppet: "https://github.com/puppetlabs/puppet.git#main" - ruby: "2.7" puppet: "https://github.com/puppetlabs/puppet.git#main" - - ruby: "2.6" - puppet: "https://github.com/puppetlabs/puppet.git#main" env: PUPPET_VERSION: ${{ matrix.puppet }} COVERAGE: ${{ matrix.coverage }} From 9c599aab49cf7740df43661fec9fb4402b55831c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix-Antoine=20Fortin?= Date: Mon, 29 Apr 2024 10:38:52 -0400 Subject: [PATCH 3/7] Make public key opt in decrypt when openssl gem >= 2.2.0 In PKCS7 RFC, the recipient certificate is not mandatory when decrypting. This is also how it is implemented in OpenSSL PKCS7_decrypt(). However, it is only since version 2.2.0 of ruby-openssl that it is possible to call OpenSSL::PKCS7#decrypt with only the private key. Ref: https://github.com/ruby/openssl/pull/183 The issue of hiera-eyaml requiring the public key when decrypting has been brought before in #137, but ruby-openssl was yet patched. --- lib/hiera/backend/eyaml/encryptors/pkcs7.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/hiera/backend/eyaml/encryptors/pkcs7.rb b/lib/hiera/backend/eyaml/encryptors/pkcs7.rb index 9e4f65d..f163ede 100644 --- a/lib/hiera/backend/eyaml/encryptors/pkcs7.rb +++ b/lib/hiera/backend/eyaml/encryptors/pkcs7.rb @@ -50,8 +50,15 @@ def self.decrypt(ciphertext) private_key_pem = self.load_private_key_pem() private_key_rsa = OpenSSL::PKey::RSA.new(private_key_pem) - public_key_pem = self.load_public_key_pem() - public_key_x509 = OpenSSL::X509::Certificate.new(public_key_pem) + # Since ruby-openssl 2.2.0, it is possible to call OpenSSL::PKCS7#decrypt + # with the private key only. Reference: + # https://github.com/ruby/openssl/pull/183 + if Gem::Version::new(OpenSSL::VERSION) >= Gem::Version::new('2.2.0') + public_key_x509 = nil + else + public_key_pem = self.load_public_key_pem() + public_key_x509 = OpenSSL::X509::Certificate.new(public_key_pem) + end pkcs7 = OpenSSL::PKCS7.new(ciphertext) pkcs7.decrypt(private_key_rsa, public_key_x509) From 123169a87a3282f739229d9ca82359b6031378fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix-Antoine=20Fortin?= Date: Mon, 29 Apr 2024 16:01:40 -0400 Subject: [PATCH 4/7] Make public key file an RSA public key instead of a certificate OpenSSL PKCS7 encrypt function needs a list of X509 certs to encrypt and sign data. However, in the context of hiera-eyaml, the certificate serves no purpose. For the user, it is easier to understand that eyaml deals with a keypair. Internally, we wrap the public key in a X509 dummy certificate to call PKCS7 encrypt but the user does not have to be exposed to it. --- lib/hiera/backend/eyaml/encryptors/pkcs7.rb | 58 ++++++--------------- 1 file changed, 17 insertions(+), 41 deletions(-) diff --git a/lib/hiera/backend/eyaml/encryptors/pkcs7.rb b/lib/hiera/backend/eyaml/encryptors/pkcs7.rb index f163ede..b4b780e 100644 --- a/lib/hiera/backend/eyaml/encryptors/pkcs7.rb +++ b/lib/hiera/backend/eyaml/encryptors/pkcs7.rb @@ -20,25 +20,26 @@ class Pkcs7 < Encryptor type: :string, }, public_key_env_var: { desc: 'Name of environment variable to read public key from', type: :string, }, - subject: { desc: 'Subject to use for certificate when creating keys', - type: :string, - default: '/', }, keysize: { desc: 'Key size used for encryption', type: :integer, default: 2048, }, - digest: { desc: 'Hash function used for PKCS7', - type: :string, - default: 'SHA256', }, } self.tag = 'PKCS7' - + # The public certificate serial could be any number, + # but the tests encrypted data were signed with a certificate with the + # serial number 0. It was later changed to 1 in f9fde79, + # but tests data were not re-generated. + X509_SERIAL_NUMBER = 0 def self.encrypt(plaintext) LoggingHelper.trace 'PKCS7 encrypt' public_key_pem = self.load_public_key_pem() - public_key_x509 = OpenSSL::X509::Certificate.new(public_key_pem) + public_key_rsa = OpenSSL::PKey::RSA.new(public_key_pem) + public_key_x509 = OpenSSL::X509::Certificate.new + public_key_x509.serial = Pkcs7::X509_SERIAL_NUMBER + public_key_x509.public_key = public_key_rsa.public_key cipher = OpenSSL::Cipher.new('aes-256-cbc') OpenSSL::PKCS7.encrypt([public_key_x509], plaintext, cipher, OpenSSL::PKCS7::BINARY).to_der @@ -56,8 +57,9 @@ def self.decrypt(ciphertext) if Gem::Version::new(OpenSSL::VERSION) >= Gem::Version::new('2.2.0') public_key_x509 = nil else - public_key_pem = self.load_public_key_pem() - public_key_x509 = OpenSSL::X509::Certificate.new(public_key_pem) + public_key_x509 = OpenSSL::X509::Certificate.new + public_key_x509.serial = Pkcs7::X509_SERIAL_NUMBER + public_key_x509.public_key = private_key_rsa.public_key end pkcs7 = OpenSSL::PKCS7.new(ciphertext) @@ -65,45 +67,19 @@ def self.decrypt(ciphertext) end def self.create_keys - # Try to do equivalent of: - # openssl req -x509 -nodes -days 100000 -newkey rsa:2048 -keyout privatekey.pem -out publickey.pem -subj '/' - - public_key = option :public_key + # Equivalent of: + # openssl genrsa -out private_key.pem 2048 + # openssl rsa -in private_key.pem -pubout -out public_key.pem private_key = option :private_key - subject = option :subject + public_key = option :public_key keysize = option :keysize - digest = option :digest key = OpenSSL::PKey::RSA.new(keysize) EncryptHelper.ensure_key_dir_exists private_key EncryptHelper.write_important_file filename: private_key, content: key.to_pem, mode: 0o600 - cert = OpenSSL::X509::Certificate.new - cert.subject = OpenSSL::X509::Name.parse(subject) - cert.serial = 1 - cert.version = 2 - cert.not_before = Time.now - cert.not_after = if 1.size == 8 # 64bit - Time.now + (50 * 365 * 24 * 60 * 60) - else # 32bit - Time.at(0x7fffffff) - end - cert.public_key = key.public_key - - ef = OpenSSL::X509::ExtensionFactory.new - ef.subject_certificate = cert - ef.issuer_certificate = cert - cert.extensions = [ - ef.create_extension('basicConstraints', 'CA:TRUE', true), - ef.create_extension('subjectKeyIdentifier', 'hash'), - ] - cert.add_extension ef.create_extension('authorityKeyIdentifier', - 'keyid:always,issuer:always') - - cert.sign key, OpenSSL::Digest.new(digest) - EncryptHelper.ensure_key_dir_exists public_key - EncryptHelper.write_important_file filename: public_key, content: cert.to_pem + EncryptHelper.write_important_file filename: public_key, content: key.public_key.to_pem LoggingHelper.info 'Keys created OK' end From 57cdc1b35897db73196dbfc486e0505ab31e9b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix-Antoine=20Fortin?= Date: Thu, 2 May 2024 11:38:47 -0400 Subject: [PATCH 5/7] Retrieve certificate serial number for data when decrypting --- lib/hiera/backend/eyaml/encryptors/pkcs7.rb | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/hiera/backend/eyaml/encryptors/pkcs7.rb b/lib/hiera/backend/eyaml/encryptors/pkcs7.rb index b4b780e..f9a20af 100644 --- a/lib/hiera/backend/eyaml/encryptors/pkcs7.rb +++ b/lib/hiera/backend/eyaml/encryptors/pkcs7.rb @@ -26,11 +26,6 @@ class Pkcs7 < Encryptor } self.tag = 'PKCS7' - # The public certificate serial could be any number, - # but the tests encrypted data were signed with a certificate with the - # serial number 0. It was later changed to 1 in f9fde79, - # but tests data were not re-generated. - X509_SERIAL_NUMBER = 0 def self.encrypt(plaintext) LoggingHelper.trace 'PKCS7 encrypt' @@ -38,7 +33,6 @@ def self.encrypt(plaintext) public_key_pem = self.load_public_key_pem() public_key_rsa = OpenSSL::PKey::RSA.new(public_key_pem) public_key_x509 = OpenSSL::X509::Certificate.new - public_key_x509.serial = Pkcs7::X509_SERIAL_NUMBER public_key_x509.public_key = public_key_rsa.public_key cipher = OpenSSL::Cipher.new('aes-256-cbc') @@ -51,6 +45,8 @@ def self.decrypt(ciphertext) private_key_pem = self.load_private_key_pem() private_key_rsa = OpenSSL::PKey::RSA.new(private_key_pem) + pkcs7 = OpenSSL::PKCS7.new(ciphertext) + # Since ruby-openssl 2.2.0, it is possible to call OpenSSL::PKCS7#decrypt # with the private key only. Reference: # https://github.com/ruby/openssl/pull/183 @@ -58,11 +54,10 @@ def self.decrypt(ciphertext) public_key_x509 = nil else public_key_x509 = OpenSSL::X509::Certificate.new - public_key_x509.serial = Pkcs7::X509_SERIAL_NUMBER + public_key_x509.serial = pkcs7.recipients[0].serial public_key_x509.public_key = private_key_rsa.public_key end - pkcs7 = OpenSSL::PKCS7.new(ciphertext) pkcs7.decrypt(private_key_rsa, public_key_x509) end From 224d91e80d82c4aea9296003d7afd57849889398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix-Antoine=20Fortin?= Date: Thu, 2 May 2024 11:57:23 -0400 Subject: [PATCH 6/7] Add support for X509 and RSA public in encrypt --- lib/hiera/backend/eyaml/encryptors/pkcs7.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/hiera/backend/eyaml/encryptors/pkcs7.rb b/lib/hiera/backend/eyaml/encryptors/pkcs7.rb index f9a20af..b5dbf6e 100644 --- a/lib/hiera/backend/eyaml/encryptors/pkcs7.rb +++ b/lib/hiera/backend/eyaml/encryptors/pkcs7.rb @@ -31,9 +31,13 @@ def self.encrypt(plaintext) LoggingHelper.trace 'PKCS7 encrypt' public_key_pem = self.load_public_key_pem() - public_key_rsa = OpenSSL::PKey::RSA.new(public_key_pem) - public_key_x509 = OpenSSL::X509::Certificate.new - public_key_x509.public_key = public_key_rsa.public_key + if /BEGIN CERTIFICATE/.match(public_key_pem) != nil + public_key_x509 = OpenSSL::X509::Certificate.new(public_key_pem) + elsif /BEGIN PUBLIC KEY/.match(public_key_pem) != nil + public_key_rsa = OpenSSL::PKey::RSA.new(public_key_pem) + public_key_x509 = OpenSSL::X509::Certificate.new + public_key_x509.public_key = public_key_rsa.public_key + end cipher = OpenSSL::Cipher.new('aes-256-cbc') OpenSSL::PKCS7.encrypt([public_key_x509], plaintext, cipher, OpenSSL::PKCS7::BINARY).to_der From 28a68ca4bf783528b208a167ab743db9c03b308a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix-Antoine=20Fortin?= Date: Thu, 2 May 2024 12:05:35 -0400 Subject: [PATCH 7/7] Remove branching on openssl version no longer required --- lib/hiera/backend/eyaml/encryptors/pkcs7.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/hiera/backend/eyaml/encryptors/pkcs7.rb b/lib/hiera/backend/eyaml/encryptors/pkcs7.rb index b5dbf6e..db2a08e 100644 --- a/lib/hiera/backend/eyaml/encryptors/pkcs7.rb +++ b/lib/hiera/backend/eyaml/encryptors/pkcs7.rb @@ -51,16 +51,9 @@ def self.decrypt(ciphertext) pkcs7 = OpenSSL::PKCS7.new(ciphertext) - # Since ruby-openssl 2.2.0, it is possible to call OpenSSL::PKCS7#decrypt - # with the private key only. Reference: - # https://github.com/ruby/openssl/pull/183 - if Gem::Version::new(OpenSSL::VERSION) >= Gem::Version::new('2.2.0') - public_key_x509 = nil - else - public_key_x509 = OpenSSL::X509::Certificate.new - public_key_x509.serial = pkcs7.recipients[0].serial - public_key_x509.public_key = private_key_rsa.public_key - end + public_key_x509 = OpenSSL::X509::Certificate.new + public_key_x509.serial = pkcs7.recipients[0].serial + public_key_x509.public_key = private_key_rsa.public_key pkcs7.decrypt(private_key_rsa, public_key_x509) end