-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Need to enhance the OpenSSL::SSL::Context loads Certificate/PrivateKey from Memory #7897
Comments
Please, if possible, please consider giving priority to this feature, |
A pull request will likely be welcomed :) |
@ysbaddaden Okay. |
Update@ysbaddaden Hi, I have implemented this feature, But I think some methods need to be renamed.
And some methods of ssl/context.cr should also be renamed.
Or better method name. Enhance
lib LibCrypto
alias PasswordCallback = (UInt8*, Int, Int, Void*) -> Int
alias EVP_PKEY = Void*
fun pem_read_bio_x509 = PEM_read_bio_X509(bio : Bio*, x509 : X509*, cb : PasswordCallback, user_data : Void*) : X509
fun bio_s_mem = BIO_s_mem : BioMethod*
fun bio_new = BIO_new(type : BioMethod*) : Bio*
fun bio_write = BIO_write(bio : Bio*, data : UInt8*, len : Int) : Int
fun bio_free = BIO_free(Bio*) : Int
fun pem_read_bio_privatekey = PEM_read_bio_PrivateKey(bio : Bio*, pk : EVP_PKEY*, cb : PasswordCallback, user_data : Void*) : EVP_PKEY
fun evp_pkey_free = EVP_PKEY_free(pkey : EVP_PKEY)
end
lib LibSSL
fun ssl_ctx_use_certificate = SSL_CTX_use_certificate(ctx : SSLContext, x509 : LibCrypto::X509) : Int
fun ssl_ctx_use_privatekey = SSL_CTX_use_PrivateKey(ctx : SSLContext, pkey : LibCrypto::EVP_PKEY) : Int
end
abstract class OpenSSL::SSL::Context
# Set the CA certificate by string, in PEM format, used to
# validate the peers certificate.
def ca_certificate_text=(certificate : String)
bio = LibCrypto.bio_new LibCrypto.bio_s_mem
LibCrypto.bio_write bio, certificate, certificate.size
x509 = LibCrypto.pem_read_bio_x509 bio, nil, nil, nil
ret = LibSSL.ssl_ctx_use_certificate @handle, x509
LibCrypto.bio_free bio ensure LibCrypto.x509_free x509
raise OpenSSL::Error.new("SSL_CTX_use_certificate") unless ret == 1_i32
end
# Set the private key by string, The key must in PEM format.
def private_key_text=(private_key : String)
bio = LibCrypto.bio_new LibCrypto.bio_s_mem
LibCrypto.bio_write bio, private_key, private_key.size
pkey = LibCrypto.pem_read_bio_privatekey bio, nil, nil, nil
ret = LibSSL.ssl_ctx_use_privatekey @handle, pkey
LibCrypto.bio_free bio ensure LibCrypto.evp_pkey_free pkey
raise OpenSSL::Error.new("SSL_CTX_use_PrivateKey") unless ret == 1_i32
end
end Usagerequire "openssl"
context = OpenSSL::SSL::Context::Server.new
context.ca_certificate_text = File.read "/Users/User/Certificate/server.pem"
context.private_key_text = File.read "/Users/User/Certificate/localhost.key" References
Finally
|
@ysbaddaden @asterite I have implemented this feature, If anyone is interested in this, please |
UpdateOther classes will use similar features, so I redesigned them. Better implementationlib LibCrypto
alias PasswordCallback = (UInt8*, Int, Int, Void*) -> Int
alias EVP_PKEY = Void*
alias RSA = Void*
NID_rsaEncryption = 6_i32
BIO_CTRL_RESET = 1_i32
fun bio_s_mem = BIO_s_mem : BioMethod*
fun bio_new = BIO_new(type : BioMethod*) : Bio*
fun bio_read = BIO_read(bio : Bio*, data : UInt8*, len : Int) : Int
fun bio_write = BIO_write(bio : Bio*, data : UInt8*, len : Int) : Int
fun bio_free = BIO_free(Bio*) : Int
fun bio_free_all = BIO_free_all(bio : Bio*) : Int
fun bio_ctrl = BIO_ctrl(bio : Bio*, cmd : Int, larg : Int, parg : Void*) : Int
fun evp_pkey_assign = EVP_PKEY_assign(pkey : EVP_PKEY, type : Int, key : Void*) : Int
fun evp_pkey_new = EVP_PKEY_new : EVP_PKEY
fun evp_pkey_free = EVP_PKEY_free(pkey : EVP_PKEY)
fun evp_pkey_get1_rsa = EVP_PKEY_get1_RSA(pk : EVP_PKEY) : RSA
fun rsa_generate_key = RSA_generate_key(bits : Int, e : UInt, cb : (Int32, Int32, Void*) ->, ud : Void*) : RSA
fun rsa_free = RSA_free(rsa : RSA) : Int
fun rsapublickey_dup = RSAPublicKey_dup(rsa : RSA) : RSA
fun rsaprivateKey_dup = RSAPrivateKey_dup(rsa : RSA) : RSA
fun pem_read_bio_privatekey = PEM_read_bio_PrivateKey(bio : Bio*, pk : EVP_PKEY, cb : PasswordCallback, user_data : Void*) : EVP_PKEY
fun pem_read_bio_x509 = PEM_read_bio_X509(bio : Bio*, x509 : X509*, cb : PasswordCallback, user_data : Void*) : X509
fun pem_write_bio_rsaprivatekey = PEM_write_bio_RSAPrivateKey(bio : Bio*, rsa : RSA, enc : EVP_CIPHER, kstr : UInt8*, klen : Int, cb : PasswordCallback, user_data : Void*) : Int
fun pem_write_bio_rsa_pubkey = PEM_write_bio_RSA_PUBKEY(bio : Bio*, rsa : RSA) : Int
fun x509_get_pubkey = X509_get_pubkey(x509 : X509) : EVP_PKEY
end lib LibSSL
fun ssl_ctx_use_certificate = SSL_CTX_use_certificate(ctx : SSLContext, x509 : LibCrypto::X509) : Int
fun ssl_ctx_use_privatekey = SSL_CTX_use_PrivateKey(ctx : SSLContext, pkey : LibCrypto::EVP_PKEY) : Int
end
class OpenSSL::MemBIO < IO
class Error < OpenSSL::Error
end
def initialize(@bio : LibCrypto::Bio*)
raise Error.new "Invalid handle" unless @bio
end
def initialize
initialize LibCrypto.bio_new(LibCrypto.bio_s_mem)
end
def read(data : Slice(UInt8))
LibCrypto.bio_read self, data, data.size
end
def write(data : String)
write data.to_slice
end
def write(data : Slice(UInt8))
LibCrypto.bio_write self, data, data.size
end
def reset
LibCrypto.bio_ctrl self, LibCrypto::BIO_CTRL_RESET, 0_i64, nil
end
def finalize
LibCrypto.bio_free_all self
end
def to_io(io : IO)
IO.copy self, io
end
def to_s
io = IO::Memory.new
to_io io
io.to_s ensure io.close
end
def to_unsafe
@bio
end
end
module OpenSSL
class PKey
class Error < OpenSSL::Error
end
enum KeyType
PrivateKey
PublicKey
end
def initialize(@pkey : LibCrypto::EVP_PKEY, @keyType = KeyType::PublicKey)
raise Error.new "Invalid EVP_PKEY" unless @pkey
end
def initialize(keyType : KeyType)
initialize LibCrypto.evp_pkey_new, keyType
end
def self.from_text(private_key : String, password = nil)
bio = MemBIO.new
bio.write private_key
pkey = LibCrypto.pem_read_bio_privatekey bio, nil, nil, nil
bio.finalize
new pkey, KeyType::PrivateKey
end
def private_key?
KeyType::PrivateKey == keyType
end
def public_key?
KeyType::PublicKey == keyType
end
def to_rsa
OpenSSL::PKey::RSA.new LibCrypto.evp_pkey_get1_rsa(self), @keyType
end
def to_unsafe
@pkey
end
def finalize
LibCrypto.evp_pkey_free @pkey
end
end
end
module OpenSSL
class PKey::RSA
alias KeyType = OpenSSL::PKey::KeyType
class Error < PKey::Error
end
def initialize(@rsa : LibCrypto::RSA, @keyType = KeyType::PublicKey)
raise Error.new "Invalid EVP_PKEY" unless @rsa
end
def self.new(size : Int32)
generate size
end
def self.generate(size : Int32, exponent = 65537_u32)
new LibCrypto.rsa_generate_key size, exponent, nil, nil
end
def to_io(io : IO, keyType : KeyType)
bio = MemBIO.new
case keyType
when KeyType::PrivateKey
LibCrypto.pem_write_bio_rsaprivatekey bio, self, nil, nil, 0, nil, nil
when KeyType::PublicKey
LibCrypto.pem_write_bio_rsa_pubkey bio, self
end ensure bio.to_io io
end
def to_s(keyType : KeyType)
io = IO::Memory.new
to_io io, keyType
io.to_s ensure io.close
end
def to_s
to_s @keyType
end
def to_unsafe
@rsa
end
def finalize
LibCrypto.rsa_free self
end
def self.free(rsa : LibCrypto::RSA)
LibCrypto.rsa_free rsa
end
def private_key
private_rsa = LibCrypto.rsaprivateKey_dup self
raise Error.new "Could not get PrivateKey from RSA" unless private_rsa
private_rsa
end
def public_key
public_rsa = LibCrypto.rsapublickey_dup self
raise Error.new "Could not get PublicKey from RSA" unless public_rsa
public_rsa
end
end
end module OpenSSL::X509
class Certificate
def self.from_text(certificate : String)
bio = MemBIO.new
bio.write certificate
x509 = LibCrypto.pem_read_bio_x509 bio, nil, nil, nil
bio.finalize
new x509
end
def public_key
OpenSSL::PKey.new LibCrypto.x509_get_pubkey(self),
OpenSSL::PKey::KeyType::PublicKey
end
end
end abstract class OpenSSL::SSL::Context
# Set the CA certificate by string, in PEM format, used to
# validate the peers certificate.
def ca_certificate_text=(certificate : String)
x509 = OpenSSL::X509::Certificate.from_text certificate
ret = LibSSL.ssl_ctx_use_certificate @handle, x509.to_unsafe
x509.finalize
raise OpenSSL::Error.new("SSL_CTX_use_certificate") unless ret == 1_i32
end
# Set the private key by string, The key must in PEM format.
def private_key_text=(private_key : String)
pkey = OpenSSL::PKey.from_text private_key
ret = LibSSL.ssl_ctx_use_privatekey @handle, pkey.to_unsafe
pkey.finalize
raise OpenSSL::Error.new("SSL_CTX_use_PrivateKey") unless ret == 1_i32
end
end Usage# 1 - Load Certificate from String | Extract the RSA public key data in the X509 certificate
x509 = OpenSSL::X509::Certificate.from_text File.read "/Users/User/Certificate/server.pem"
puts [x509.subject.to_a] # => [[{"CN", "CustomName"}]]
rsa_key = x509.public_key.to_rsa
puts [rsa_key.to_s] ensure rsa_key.finalize # => RSA PublicKey...
# 2 - Load PrivateKey from String | Extract the RSA private key data in the PKey certificate
pkey = OpenSSL::PKey.from_text File.read "/Users/User/Certificate/localhost.key"
rsa_key = pkey.to_rsa
puts [rsa_key.to_s] ensure rsa_key.finalize # => RSA PrivateKey...
x509.finalize ensure pkey.finalize
# 3 - Load string from `OpenSSL::SSL::Context::Server` (Certificate/PrivateKey)
context = OpenSSL::SSL::Context::Server.new
context.ca_certificate_text = File.read "/Users/User/Certificate/server.pem"
context.private_key_text = File.read "/Users/User/Certificate/localhost.key"
# 4 - Test `OpenSSL::MemBIO` IO read and write features.
bio = OpenSSL::MemBIO.new
bio.write File.read "/Users/User/Certificate/server.pem"
bio_memory = IO::Memory.new
bio.to_io bio_memory
puts [bio_memory] # => [#<IO::Memory:0x108acaae0 ...]
bio_memory.close ensure bio.finalize
# 5 - Test `OpenSSL::PKey::RSA` KeyPair generation
rsa_key = OpenSSL::PKey::RSA.new 4096
private_key = OpenSSL::PKey::RSA.new rsa_key.private_key, OpenSSL::PKey::KeyType::PrivateKey
public_key = OpenSSL::PKey::RSA.new rsa_key.public_key, OpenSSL::PKey::KeyType::PublicKey
puts str = {private_key: private_key.to_s, public_key: public_key.to_s}
private_key.finalize ensure public_key.finalize ensure rsa_key.finalize Last Modified
|
If anyone wants to take this over and go through a proper pull request cycle, taking care of any codereview feedback, please do :) |
Does this still need to be made into a pr? |
Summary
I saw the Crystal API documentation, it can only be loaded from the file path.
In Golang it can be loaded using
X509KeyPair
? (probably?),But I don't seem to see similar features in Crystal.
In Golang
References
Golang - Modifying net/http server.go, using decrypted TLS certificates from memory
Golang - Practical embedding in GoLang
Golang - TLS server with in-memory self-signed certificate
The text was updated successfully, but these errors were encountered: