Creating self-signed TLS certificate

My rust program uses https to serve a website (with actix_web).
The user can upload a certificate created by a CA, but at least for the first access to the site the program needs to use a self-signed certificate.
(The program is self-hosted.)
I do not know a lot about certificates in general, but was able to create this code to generate a certificate with rcgen:

use rcgen::{generate_simple_self_signed, CertifiedKey};

let subject_alt_names = vec!["localhost".to_string(), hostname().unwrap_or("localhost".to_string())];
let CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names).unwrap();
let _ = fs::create_dir_all(data_path.join("certificates"));
let _ = fs::write(data_path.join("certificates").join("selfsigned.pem"), format!("{}{}", key_pair.serialize_pem(), cert.pem()));
fn hostname() -> Option<String> {
    let r = fs::read_to_string("/etc/hostname");

    match r {
        Ok(v) => {
            return Some(v)
        },
        Err(_e) => {
            return None
        }
    }
}

This results in the following certificate being generated:

I am now wondering, if this is an appropriate way to generate a certificate and if it would be an option to go so far as to install the certificate on the client machine. (Offering a way to install the certificate or provide instructions on how to do so.)

I hope I am not offending anyone for posting several questions already on my first few days here.

I'm not sure what your question is, I guess you just want to support HTTPS requests with a self signed certificate(?). Which HTTP-Server are you using?

EDIT:
pardon me, I read past the part that actix_web serves your app.

You can certainly use a self signed certificate but the browser will not be happy and you should not ask the user to install thr certificate system-wide because that's not safe.

What about allowing the user to use a plain HTTP connection (no SSL) only for the upload and only if no other certificate is present? Given that this is an operation the user only does once and only at initialisation time it is as much safe as a self-signed certificate.

As far as I understand how TLS works self-signed certificate will not prevent man-in-the-middle attacks, but it will prevent uploaded certificate from being leaked in case attacker can only read traffic and not change it.
So I would not say that HTTP is “as much safe as” HTTPS with self-signed certificate.

You are right but from a practical point of view both ways are insecure.

What I would do in this case is generate an asymmetric key on the server and send the public half together with the upload form to the client. With a bit of JS it is possible to encrypt the data with the public key before the form is submitted and make sure only the server can decrypt it even over an unencrypted HTTP connection.

I too would not recommend providing instructions to users on how to install a certificate. But given small time it is expected to be used I think you should settle on the following check for whether or not certificate is OK: just check that users of all browsers you care about can access the website without lowering the security for all websites.
And provide the instructions on how to convince browser to let user access website, which are mostly needed to make it clear that before uploading certificate scary warnings are expected and not a sign of something being broken.

I also heard that if you are using HSTS you should not enable HSTS until after you get proper certificate.

Is not the only difference between this approach and using HTTPS with self-signed certificate is that both you and attacker capable of being man-in-the-middle will have to write some extra code? If it is not targeted attack I would not expect to see something which would just intercept all HTTPS traffic which happens to have self-signed certificates (at least if attacker cares about not being noticed) and if it is targeted attack such scheme would not help you any as attacker sitting in the middle will just receive your key, send his key and reencrypt user certificate, nothing different from receiving your certificate, sending his and reencrypting traffic.

It may make sense to use this scheme if there is some more secure way to send public key though in place of sending it “together with the upload form”: you could not expect users to check self-signed certificate if check can be omitted, but if user can’t send anything without supplying public key first then this variant is fine.

It is a public key and that means that you don't need "some more secure way to send it" because the security is not in the key. Also it is not a certificate, user don't even need to know it is there. It is just a (very) secure way to secure the form data before sending it through an unencrypted channel.

I am wondering whether we are arguing over different things: I see you as trying to communicate that HTTP can be made secure (== essentially as secure as HTTPS with proper certificate) with the scheme and you are instead trying to communicate the point of HTTP actually being “just as secure as HTTPS with self signed certificate”: in this case I will agree, as long as this extra encryption is added it actually is.

I still would suggest using self-signed certificate as it is less coding and thus less opportunities to insert bugs like accidentally making application being able to serve everything over HTTP under some condition.

Thank you for your response. Not asking the user to install the certificate sounds reasonable, so I won't do it. I would ask the user to upload their own certificate when they first log in, but not have the connecting be completly un-encrypted.

As far as I know, a self-signed certifcate does not prevent man in the middle attacks, but when the browser knows the certificate once, it will warn you when the cert changes and is not from a CA. So the upload should be protected, which is one of the key reasons why I choose to use a self-signed cert when the user first views the page.

A self-signed certificate prevents MITM attacks if - and probably only if - the client knows which self-signed certificate to expect, so that it can warn the user if it sees any other certificate. That can be administered in a few ways, but it is definitely more work.

On the other hand, even a certificate issued by a well-known authority can be MITMed if the endpoint trusts the "man in the middle"'s issuer. This is in real use in some corporate environments, to enable content monitoring and filtering for TLS sites: the corporate cert is trusted, and the corporate web proxy MITMs TLS connections using its cert.

TLS is not particularly resistant to MITM attacks categorically, just to MITM attacks by untrusted parties. Self-signed certs make it easier to insert an untrusted party in the middle, for sure, but security is a whole-system property, not a property of a specific certificate or configuration.

Your approach to generating certificates is reasonable enough, but I would recommend getting the hostname via OS APIs, not by reading a file. /etc/hostname, in particular, may contain either a bare hostname or a fully-qualified one, or even a hostname that is only valid on some internal network. The file may also not exist: while it's a widespread convention, /etc/hostname never made it into the Linux Filesystem Hierarchy spec, and isn't required by POSIX to the best of my knowledge.

(You're also asking for trouble with the terminating newline character.)

Getting the (a) DNS-resolvable hostname for the current host is actually a hard problem generally, since the host may not know such a hostname. There are a few alternatives you might want to consider:

  • Using something like nix, hostname, or gethostname to ask the OS directly.
  • Postponing certificate generation until you receive a request, and generating the certificate based on the Server Name Indication field of the TLS handshake, or the Host header of the HTTP request.
  • Always generate one for localhost, and require the initial setup to be done via that hostname specifically.

Regardless of what you pick, you must provide an option for the administrator to specify the hostname(s) to be used, because any of these options will sometimes give a result that's not correct for their deployment. A startup option or config file option that can accept a list of hostnames (or hostnames and IP addresses) is a good idea here. Instructions on generating a self-signed certificate manually and installing it would also work.

Most browsers already present an option to do this for you, so you don't need to (and should not) write your own process for this. You should, however, make it easy for users to check that the certificate they're accepting is the certificate your app generated. Log the fingerprint, serial number, and other key facts about the cert, or put them somewhere the administrator can read them without needing to connect to your app, and instruct them to compare those fields before accepting the certificate.