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

Support Ed25519 keys/certificates #8869

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions lib/puppet/ssl/ssl_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def create_system_context(cacerts:, path: Puppet[:ssl_trust_store], include_clie
#
# @param cacerts [Array<OpenSSL::X509::Certificate>] Array of trusted CA certs
# @param crls [Array<OpenSSL::X509::CRL>] 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions lib/puppet/x509/cert_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion spec/lib/puppet/test_ca.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
26 changes: 26 additions & 0 deletions spec/unit/x509/cert_provider_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 16 additions & 10 deletions tasks/generate_cert_fixtures.rake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading