-
Notifications
You must be signed in to change notification settings - Fork 43
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
Minor improvements to HTTPS handling in Agama's web server #1228
Changes from all commits
4a91eb1
0b5b27e
a36daa6
0d59669
fbd5a73
ab63189
21db741
6f3e2dd
f82e247
271184d
6336dbe
22600a7
70a2a39
d70a618
3e5fcea
39858c4
f40236c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,84 +1,128 @@ | ||
use anyhow; | ||
use gethostname::gethostname; | ||
use openssl::asn1::Asn1Time; | ||
use openssl::bn::{BigNum, MsbOption}; | ||
use openssl::error::ErrorStack; | ||
use openssl::hash::MessageDigest; | ||
use openssl::pkey::{PKey, Private}; | ||
use openssl::rsa::Rsa; | ||
use openssl::x509::extension::{BasicConstraints, SubjectAlternativeName, SubjectKeyIdentifier}; | ||
use openssl::x509::{X509NameBuilder, X509}; | ||
use std::{ | ||
fs, | ||
io::{self, Write}, | ||
os::unix::fs::OpenOptionsExt, | ||
path::Path, | ||
}; | ||
|
||
// TODO: move the certificate related functions into a struct | ||
// | ||
// struct Certificate { | ||
// certificate: X509, | ||
// key: PKey<Private>, | ||
// } | ||
// | ||
// impl Certificate { | ||
// // read from file, support some default location | ||
// // (like /etc/agama.d/ssl/{certificate,key}.pem ?) | ||
// pub read(cert: &str, key: &str) -> Result<Self>; | ||
// // generate a self-signed certificate | ||
// pub new() -> Self | ||
// // dump to file | ||
// pub write(...) | ||
// } | ||
|
||
/// Generates a self-signed SSL certificate | ||
/// see https://github.com/sfackler/rust-openssl/blob/master/openssl/examples/mk_certs.rs | ||
pub fn create_certificate() -> Result<(X509, PKey<Private>), ErrorStack> { | ||
let rsa = Rsa::generate(2048)?; | ||
let key = PKey::from_rsa(rsa)?; | ||
|
||
let mut x509_name = X509NameBuilder::new()?; | ||
x509_name.append_entry_by_text("O", "Agama")?; | ||
x509_name.append_entry_by_text("CN", "localhost")?; | ||
let x509_name = x509_name.build(); | ||
|
||
let mut builder = X509::builder()?; | ||
builder.set_version(2)?; | ||
let serial_number = { | ||
let mut serial = BigNum::new()?; | ||
serial.rand(159, MsbOption::MAYBE_ZERO, false)?; | ||
serial.to_asn1_integer()? | ||
}; | ||
builder.set_serial_number(&serial_number)?; | ||
builder.set_subject_name(&x509_name)?; | ||
builder.set_issuer_name(&x509_name)?; | ||
builder.set_pubkey(&key)?; | ||
|
||
let not_before = Asn1Time::days_from_now(0)?; | ||
builder.set_not_before(¬_before)?; | ||
let not_after = Asn1Time::days_from_now(365)?; | ||
builder.set_not_after(¬_after)?; | ||
|
||
builder.append_extension(BasicConstraints::new().critical().ca().build()?)?; | ||
|
||
builder.append_extension( | ||
SubjectAlternativeName::new() | ||
// use the default Agama host name | ||
// TODO: use the gethostname crate and use the current real hostname | ||
.dns("agama") | ||
// use the default name for the mDNS/Avahi | ||
// TODO: check which name is actually used by mDNS, to avoid | ||
// conflicts it might actually use something like agama-2.local | ||
.dns("agama.local") | ||
.build(&builder.x509v3_context(None, None))?, | ||
)?; | ||
|
||
let subject_key_identifier = | ||
SubjectKeyIdentifier::new().build(&builder.x509v3_context(None, None))?; | ||
builder.append_extension(subject_key_identifier)?; | ||
|
||
builder.sign(&key, MessageDigest::sha256())?; | ||
let cert = builder.build(); | ||
|
||
// for debugging you might dump the certificate to a file: | ||
// use std::io::Write; | ||
// let mut cert_file = std::fs::File::create("agama_cert.pem").unwrap(); | ||
// let mut key_file = std::fs::File::create("agama_key.pem").unwrap(); | ||
// cert_file.write_all(cert.to_pem().unwrap().as_ref()).unwrap(); | ||
// key_file.write_all(key.private_key_to_pem_pkcs8().unwrap().as_ref()).unwrap(); | ||
|
||
Ok((cert, key)) | ||
const DEFAULT_CERT_DIR: &str = "/etc/agama.d/ssl"; | ||
|
||
/// Structure to handle and store certificate and private key which is later | ||
/// used for establishing HTTPS connection | ||
pub struct Certificate { | ||
pub cert: X509, | ||
pub key: PKey<Private>, | ||
} | ||
|
||
impl Certificate { | ||
/// Writes cert, key to (for now well known) location(s) | ||
pub fn write(&self) -> anyhow::Result<()> { | ||
// check and create default dir if needed | ||
if !Path::new(DEFAULT_CERT_DIR).is_dir() { | ||
std::fs::create_dir_all(DEFAULT_CERT_DIR)?; | ||
} | ||
|
||
if let Ok(bytes) = self.cert.to_pem() { | ||
write_and_restrict(Path::new(DEFAULT_CERT_DIR).join("cert.pem"), &bytes)?; | ||
} | ||
if let Ok(bytes) = self.key.private_key_to_pem_pkcs8() { | ||
mchf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
write_and_restrict(Path::new(DEFAULT_CERT_DIR).join("key.pem"), &bytes)?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Reads certificate and corresponding private key from given paths | ||
pub fn read<T: AsRef<Path>>(cert: T, key: T) -> anyhow::Result<Self> { | ||
let cert_bytes = std::fs::read(cert)?; | ||
let key_bytes = std::fs::read(key)?; | ||
|
||
let cert = X509::from_pem(&cert_bytes.as_slice()); | ||
let key = PKey::private_key_from_pem(&key_bytes.as_slice()); | ||
|
||
match (cert, key) { | ||
(Ok(c), Ok(k)) => Ok(Certificate { cert: c, key: k }), | ||
_ => Err(anyhow::anyhow!("Failed to read certificate")), | ||
} | ||
} | ||
|
||
/// Creates a self-signed certificate | ||
pub fn new() -> anyhow::Result<Self> { | ||
let rsa = Rsa::generate(2048)?; | ||
let key = PKey::from_rsa(rsa)?; | ||
|
||
let hostname = gethostname() | ||
.into_string() | ||
.unwrap_or(String::from("localhost")); | ||
let mut x509_name = X509NameBuilder::new()?; | ||
x509_name.append_entry_by_text("O", "Agama")?; | ||
mchf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
x509_name.append_entry_by_text("CN", hostname.as_str())?; | ||
let x509_name = x509_name.build(); | ||
|
||
let mut builder = X509::builder()?; | ||
builder.set_version(2)?; | ||
let serial_number = { | ||
let mut serial = BigNum::new()?; | ||
serial.rand(159, MsbOption::MAYBE_ZERO, false)?; | ||
serial.to_asn1_integer()? | ||
}; | ||
builder.set_serial_number(&serial_number)?; | ||
builder.set_subject_name(&x509_name)?; | ||
builder.set_issuer_name(&x509_name)?; | ||
builder.set_pubkey(&key)?; | ||
|
||
let not_before = Asn1Time::days_from_now(0)?; | ||
builder.set_not_before(¬_before)?; | ||
let not_after = Asn1Time::days_from_now(365)?; | ||
builder.set_not_after(¬_after)?; | ||
|
||
builder.append_extension(BasicConstraints::new().critical().ca().build()?)?; | ||
|
||
builder.append_extension( | ||
SubjectAlternativeName::new() | ||
// use the default Agama host name | ||
// TODO: use the gethostname crate and use the current real hostname | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. something to fix? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well it partly is : https://github.com/openSUSE/agama/pull/1228/files#diff-280a7d38feb9a8c0c477b0903c149026ea053d231fa7ebe1326b4fd76eec07ecR62 may be @lslezak can say something about this ? |
||
.dns("agama") | ||
// use the default name for the mDNS/Avahi | ||
// TODO: check which name is actually used by mDNS, to avoid | ||
// conflicts it might actually use something like agama-2.local | ||
.dns("agama.local") | ||
.build(&builder.x509v3_context(None, None))?, | ||
)?; | ||
|
||
let subject_key_identifier = | ||
SubjectKeyIdentifier::new().build(&builder.x509v3_context(None, None))?; | ||
builder.append_extension(subject_key_identifier)?; | ||
|
||
builder.sign(&key, MessageDigest::sha256())?; | ||
let cert = builder.build(); | ||
|
||
Ok(Certificate { | ||
cert: cert, | ||
key: key, | ||
}) | ||
} | ||
} | ||
|
||
/// Writes buf into a file at path and sets the file permissions for the root only access | ||
fn write_and_restrict<T: AsRef<Path>>(path: T, buf: &[u8]) -> io::Result<()> { | ||
let mut file = fs::OpenOptions::new() | ||
.create(true) | ||
.truncate(true) | ||
.write(true) | ||
.mode(0o400) | ||
.open(path)?; | ||
|
||
file.write_all(buf)?; | ||
|
||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,13 @@ | ||
------------------------------------------------------------------- | ||
Fri Jun 7 05:58:48 UTC 2024 - Michal Filka <[email protected]> | ||
|
||
- Improvements in HTTPS setup | ||
mchf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- self-signed certificate contains hostname | ||
- self-signed certificate is stored into default location | ||
- before creating new self-signed certificate a default location | ||
(/etc/agama.d/ssl) is checked for a certificate | ||
- gh#openSUSE/agama#1228 | ||
|
||
------------------------------------------------------------------- | ||
Wed Jun 5 13:53:59 UTC 2024 - José Iván López González <[email protected]> | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say this is a good opportunity to use let-else: