diff --git a/lib/puppet/ssl/ssl_provider.rb b/lib/puppet/ssl/ssl_provider.rb index 8b2d7a71b00..6e562e5ecac 100644 --- a/lib/puppet/ssl/ssl_provider.rb +++ b/lib/puppet/ssl/ssl_provider.rb @@ -134,7 +134,7 @@ def create_system_context(cacerts:, path: Puppet[:ssl_trust_store], include_clie # # @param cacerts [Array] Array of trusted CA certs # @param crls [Array] Array of CRLs - # @param private_key [OpenSSL::PKey::RSA, OpenSSL::PKey::EC] client's private key + # @param private_key [OpenSSL::PKey::PKey] client's private key # @param client_cert [OpenSSL::X509::Certificate] client's cert whose public # key matches the `private_key` # @param revocation [:chain, :leaf, false] revocation mode @@ -199,7 +199,7 @@ def load_context(certname: Puppet[:certname], revocation: Puppet[:certificate_re # of the private key, and that it hasn't been tampered with since. # # @param csr [OpenSSL::X509::Request] certificate signing request - # @param public_key [OpenSSL::PKey::RSA, OpenSSL::PKey::EC] public key + # @param public_key [OpenSSL::PKey::PKey] public key # @raise [Puppet::SSL:SSLError] The private_key for the given `public_key` was # not used to sign the CSR. # @api private @@ -281,7 +281,9 @@ def revocation_mode(mode) def resolve_client_chain(store, client_cert, private_key) client_chain = verify_cert_with_store(store, client_cert) - if !private_key.is_a?(OpenSSL::PKey::RSA) && !private_key.is_a?(OpenSSL::PKey::EC) + if !private_key.is_a?(OpenSSL::PKey::RSA) && \ + !private_key.is_a?(OpenSSL::PKey::EC) && \ + !(private_key.is_a?(OpenSSL::PKey::PKey) && private_key.respond_to?(:oid) && private_key.oid == 'ED25519') raise Puppet::SSL::SSLError, _("Unsupported key '%{type}'") % { type: private_key.class.name } end diff --git a/lib/puppet/x509/cert_provider.rb b/lib/puppet/x509/cert_provider.rb index 0a1c794baa0..3788c91dfd1 100644 --- a/lib/puppet/x509/cert_provider.rb +++ b/lib/puppet/x509/cert_provider.rb @@ -176,7 +176,7 @@ def ca_last_update=(time) # historical reasons, names are case insensitive. # # @param name [String] The private key identity - # @param key [OpenSSL::PKey::RSA] private key + # @param key [OpenSSL::PKey::PKey] private key # @param password [String, nil] If non-nil, derive an encryption key # from the password, and use that to encrypt the private key. If nil, # save the private key unencrypted. @@ -227,7 +227,7 @@ def load_private_key(name, required: false, password: nil) # @param password [String, nil] If the private key is encrypted, decrypt # it using the password. If the key is encrypted, but a password is # not specified, then the key cannot be loaded. - # @return [OpenSSL::PKey::RSA, OpenSSL::PKey::EC] The private key + # @return [OpenSSL::PKey::PKey] The private key # @raise [OpenSSL::PKey::PKeyError] The `pem` text does not contain a valid key # # @api private @@ -299,7 +299,7 @@ def load_client_cert_from_pem(pem) # Create a certificate signing request (CSR). # # @param name [String] the request identity - # @param private_key [OpenSSL::PKey::RSA] private key + # @param private_key [OpenSSL::PKey::PKey] private key # @return [Puppet::X509::Request] The request # # @api private diff --git a/spec/lib/puppet/test_ca.rb b/spec/lib/puppet/test_ca.rb index c1c8e489111..b263b2da973 100644 --- a/spec/lib/puppet/test_ca.rb +++ b/spec/lib/puppet/test_ca.rb @@ -129,7 +129,9 @@ def generate(name, opts) private def build_cert(name, issuer, opts = {}) - key = if opts[:key_type] == :ec + key = if opts[:key_type] == :ed25519 + key = OpenSSL::PKey.generate('ed25519') + elsif opts[:key_type] == :ec key = OpenSSL::PKey::EC.generate('prime256v1') elsif opts[:reuse_key] key = opts[:reuse_key] diff --git a/spec/unit/x509/cert_provider_spec.rb b/spec/unit/x509/cert_provider_spec.rb index 7ad2dea0eb5..7b90781e7a1 100644 --- a/spec/unit/x509/cert_provider_spec.rb +++ b/spec/unit/x509/cert_provider_spec.rb @@ -318,6 +318,32 @@ def expects_private_file(path) }.to raise_error(OpenSSL::PKey::PKeyError, /(unknown|invalid) curve name|Could not parse PKey: (no start line|bad decrypt)/) end end + + context 'using Ed25519', if: RUBY_VERSION.to_f >= 3 && OpenSSL::OPENSSL_VERSION_NUMBER > 0x10101000 do + it 'returns a generic key' do + expect(provider.load_private_key('ed25519-key')).to be_a(OpenSSL::PKey::PKey) + end + + it 'returns a generic key from PKCS#8 format' do + expect(provider.load_private_key('ed25519-key-pk8')).to be_a(OpenSSL::PKey::PKey) + end + + it 'returns a generic key from openssl format' do + expect(provider.load_private_key('ed25519-key-openssl')).to be_a(OpenSSL::PKey::PKey) + end + + it 'decrypts a generic key using the password' do + pkey = provider.load_private_key('encrypted-ed25519-key', password: password) + expect(pkey).to be_a(OpenSSL::PKey::PKey) + end + + it 'raises without a password' do + # password is 74695716c8b6 + expect { + provider.load_private_key('encrypted-ed25519-key') + }.to raise_error(OpenSSL::PKey::PKeyError, /(unknown|invalid) curve name|Could not parse PKey: no start line/) + end + end end context 'certs' do diff --git a/tasks/generate_cert_fixtures.rake b/tasks/generate_cert_fixtures.rake index b0fb13d8eb9..3cbfe9fd08f 100644 --- a/tasks/generate_cert_fixtures.rake +++ b/tasks/generate_cert_fixtures.rake @@ -22,6 +22,19 @@ task(:gen_cert_fixtures) do end end + def generate(type, inter) + # Create an EC key and cert, issued by "Test CA Subauthority" + cert = ca.create_cert(type, inter[:cert], inter[:private_key], key_type: :type) + save(dir, "#{type}.pem", cert[:cert]) + save(dir, "#{type}-key.pem", cert[:private_key]) + + # Create an encrypted version of the above private key. + save(dir, "encrypted-#{type}-key.pem", cert[:private_key]) do |x509| + # private key password was chosen at random + x509.to_pem(OpenSSL::Cipher::AES.new(128, :CBC), '74695716c8b6') + end + end + # This task generates a PKI consisting of a root CA, intermediate CA and # several leaf certs. A CRL is generated for each CA. The root CA CRL is # empty, while the intermediate CA CRL contains the revoked cert's serial @@ -125,16 +138,9 @@ task(:gen_cert_fixtures) do save(dir, 'revoked.pem', revoked[:cert]) save(dir, 'revoked-key.pem', revoked[:private_key]) - # Create an EC key and cert, issued by "Test CA Subauthority" - ec = ca.create_cert('ec', inter[:cert], inter[:private_key], key_type: :ec) - save(dir, 'ec.pem', ec[:cert]) - save(dir, 'ec-key.pem', ec[:private_key]) - - # Create an encrypted version of the above private key for host "ec" - save(dir, 'encrypted-ec-key.pem', ec[:private_key]) do |x509| - # private key password was chosen at random - x509.to_pem(OpenSSL::Cipher::AES.new(128, :CBC), '74695716c8b6') - end + # Generate certificate and key sets for various algorithms. + generate('ec', inter) + generate('ed25519', inter) # Update intermediate CRL now that we've revoked save(dir, 'intermediate-crl.pem', inter_crl)