Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make public_key.pkcs7.pem a RSA public key instead of a X509 certificate #367

Closed
wants to merge 7 commits into from

Conversation

cmd-ntrf
Copy link
Contributor

@cmd-ntrf cmd-ntrf commented May 1, 2024

OpenSSL PKCS7 encrypt function needs a list of X509 certs to encrypt and sign data.

In hiera-eyaml, the certificate is only a vehicle to wrap the public key in order to use PKCS7 encrypt/decrypt functions. It has no signature checking function since the certificate serial number is harcoded to 1. All other attributes configured for the certificate are not involved in the decryption i.e: subject, start date, end date, digest, extensions.

In this PR, we propose to replace the format of the public key file from an X509 certificate with serial number 1, to an RSA public key file. Internally, we wrap the public key in a dummy certificate with an hardcoded serial number.

For the hiera-eyaml user, it is easier to understand that eyaml deals with a keypair. This is also what is documented. It is also make it easier for users to generate a keypair with openssl CLI.

We moved from serial number 1 to serial number 0 to make all tests pass. The test encrypted data were originally signed with a certificate serial number 0. The harcoded serial number was changed in commit f9fde79, but the encrypted data and the key files were not regenerated. Since it's been more than 10 years where the serial number has been 1, if this PR is accepted, we would updated the test data to make it sign with serial number 1 instead.

To upgrade, If the data has been encrypted with certificate serial number 1 (in the last 10 years), a user would only have to only have to extract the extracting the public key from the private key file:

openssl rsa -in private_key.pkcs7.pem -pubout -out public_key.pkcs7.pem

Every combination including Ruby 2.6 was excluded already.
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: ruby/openssl#183

The issue of hiera-eyaml requiring the public key when decrypting has
been brought before in voxpupuli#137, but ruby-openssl was yet patched.
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.
@Sharpie
Copy link

Sharpie commented May 2, 2024

I'm 👎 on breaking changes unless they deliver an exceptionally strong benefit or prevent a significant harm. After looking through this patch, it appears that hiera-eyaml will no longer be able to load existing .pem files that are in wide use today in production systems. It also looks like the code calls OpenSSL::X509::Certificate.new and ends up creating a certificate anyway --- so there is no actual change in how PCKS7 is being used.

So, I'm with Linus on this one: "don't break userland"

@bastelfreak
Copy link
Member

Hi!

This is also what is documented

@cmd-ntrf which documentation do you have in mind?

It is also make it easier for users to generate a keypair with openssl CLI.

Yes. But I think that's the reason why you can generate everything via eyaml createkeys?

I don't mind the additional support for plain private + public key, if the diff is easy to review and doesn't break existing setups.

@Sharpie
Copy link

Sharpie commented May 2, 2024

A tool like cfssl can also be used to generate self-signed certificates with much less hassle than openssl, if you're looking for something that has fewer dependencies than a Ruby gem:

$ echo '{"CN": "hiera-eyaml", "key": {"algo": "rsa", "size": 4096}}' \
     | cfssl selfsign 'hiera-eyaml' - \
     | jq -r '.cert,.key'
     
2024/05/02 10:06:01 [INFO] generate received request
2024/05/02 10:06:01 [INFO] received CSR
2024/05/02 10:06:01 [INFO] generating key: rsa-4096
2024/05/02 10:06:03 [INFO] encoded CSR
*** WARNING ***

Self-signed certificates are dangerous. Use this self-signed
certificate at your own risk.

It is strongly recommended that these certificates NOT be used
in production.

*** WARNING ***

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

@cmd-ntrf
Copy link
Contributor Author

cmd-ntrf commented May 2, 2024

First, thank you for taking the time to review this properly. I appreciate it. I understand it is a breaking change. Let me try to answer your interrogations the best I can and I am open to discussion on how we could me make it break less things. My work on this started when asking myself why does hiera-eyaml requires the "public key" to decrypt. I found a partial solution to this and made PR #364. Then, I kept digging and realized the public key was not only a public key but a certificate.

I also found issue #137 where @gtmtech said this about public key being required:

Yes, when I implemented this, I noted that the public key and the private key are necessary to decrypt, and only the public key is necessary to encrypt. If you know otherwise, please let me know and will investigate.

So 9 years later, I decided to investigate and give a shot at a remedy.

@bastelfreak : > which documentation do you have in mind?
Starting with the README.md - Generate keys. It is mentioned that the command generate a private and a public key. No mention of certificate. In fact, the word certificate is never mentioned in the entire README. Users expecting a public key file will be surprised to find a certificate.

@Sharpie: > it appears that hiera-eyaml will no longer be able to load existing .pem files that are in wide use today in production systems
@bastelfreak: > I don't mind the additional support for plain private + public key, if the diff is easy to review and doesn't break existing setups.

Correct me I understand this improperly, but if we were to introduce backward compatibility with previous the previous public key format, it would make this PR acceptable? If so, I am confident that can be arranged.

@cmd-ntrf
Copy link
Contributor Author

cmd-ntrf commented May 2, 2024

Quick update: I think it is possible to entirely remove that serial number constant by retrieving the certificate serial number value from the PKCS7 data object. This means decrypt would no longer require the public key, and therefore its format would no longer matter. There is a breaking in the test for Ruby 2.7, but I am confident I can fix it.

As for the encrypt function, it would be possible to support both the X509 certificate and the RSA public key format.

This would no longer make this a breaking change PR, but a feature PR to support RSA public key for encrypting.

@Sharpie
Copy link

Sharpie commented May 2, 2024

Preserving backwards compatibility would be a requirement. However, I'm generally not a fan of complicating cryptographic workflows with branching logic --- especially when there are ways to generate the required input.

@cmd-ntrf
Copy link
Contributor Author

cmd-ntrf commented May 2, 2024

The PR is now backward compatible.

  • The encrypt function supports both public key file format X509 and RSA public key.
  • I have removed the commit changing the test public key file format.
  • I have removed the check on the ruby-openssl gem version. It is no longer required as we fetch the certificate serial number from the PKCS7 object.

@smortex
Copy link
Member

smortex commented May 2, 2024

It looks like 2 years ago I also got confused by the behavior of this backend, but I finally just did a copy of the code and adjusted it to use bare RSA keys: https://github.com/smortex/hiera-eyaml-rsa

At that time I was thinking that this module would make a lot of sense if it was able to handle a bunch of public keys (e.g. 1 for the puppet sever and 1 for each sysadmin) and when encrypting a secret allow to choose who will be able to decrypt it (e.g. the puppet server, and a selected group of admins). In a bolt context where multiple small teams might need to have access to some secrets that should not be available to other teams, this could be handy.

@cmd-ntrf
Copy link
Contributor Author

cmd-ntrf commented May 2, 2024

@smortex : I started going down a similar route before stopping when I realized that there is a limit to the number of bytes that can be encrypted with an RSA key. It depends on the keysize, but for the default 2048 bits, a RSA only backend could only encrypt 214 bytes data. While the PKCS7 backend does support encrypting files of arbitrary size.

@cmd-ntrf
Copy link
Contributor Author

cmd-ntrf commented May 6, 2024

To help with review and avoid confusion, I am splitting this PR in three independent components and separate PR:

  1. Decrypt public key requirement removal - PR Remove public key requirement to decrypt #378
  2. Adding support for RSA public key in encrypt - PR Add support to encrypt with an RSA public key #379
  3. Simplifying createkeys and remove unnecessary certificate attributes - PR Remove non-essential public certificate attributes #380

@cmd-ntrf cmd-ntrf closed this May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants