Generating good modern self-signed TLS certificates in today's world

April 12, 2017

Once upon a time, generating decently good self-signed certificates for a host with OpenSSL was reasonably straightforward, especially if you didn't know about some relevant nominal standards. The certificate's Subject name field is a standard field with standard components, so OpenSSL would prompt you for all of them, including the Common Name (CN) that you'd put the hostname in. Then things changed and in modern TLS, you really want to put the hostname in the Subject Alternative Name field. SubjectAltName is an extension, and because it's an extension 'openssl req' will not prompt you to fill it in.

(The other thing is that you need to remember to specify -sha256 as one of the arguments; otherwise 'openssl req' will use SHA1 and various things will be unhappy with your certificate. Not all examples you can find on the Internet use '-sha256', so watch out.)

You can get 'openssl req' to create a self-signed certificate with a SAN, but since OpenSSL won't prompt for this you must use an OpenSSL configuration file to specify everything about the certificate, including the hostname(s). This is somewhat intricate, even if it turns out to be possibly to do this more or less through the command line with suitably complicated incantations. I particularly admire the use of the shell's usually obscure '<(...)' idiom.

Given how painful this is, what we really need is a better tool to create self-signed certificates and fortunately for me, it turns out that there is just what I need sitting around in the Go source code as generate_cert.go. Grab this file, copy it to a directory, then:

$ go build generate_cert.go
$ ./generate_cert --host --duration 17520h
2017/04/11 23:51:21 written cert.pem
2017/04/11 23:51:21 written key.pem

This generates exactly the sort of modern self-signed certificate that I want; it uses SHA256, it has a 2048-bit RSA key (by default), and it's got SubjectAltName(s). You can use it to generate ECDSA based certificates if you're feeling bold.

Note that this generates a certificate without a CN. Since there are real CN-less certificates out there in the wild issued by real Certificate Authorities (including the one for this site), not having a CN should work fine with web browsers and most software, but you may run into some software that is unhappy with this. If so, it's only a small modification to add a CN value.

(You could make a rather more elaborate version of generate_cert.go with various additional useful options, and perhaps someone has already done so. I have so far resisted the temptation to start changing it myself.)

A rather more elaborate but more complete looking alternative is Cloudflare's CFSSL toolkit. CFSSL can generate self-signed certificates, good modern CSRs, and sign certificates with your own private CA certificate, which covers everything I can think of. But it has the drawback that you need to feed it JSON (even if you generate the JSON on the fly) and then turn its JSON output into regular .pem files with one of its included programs.

For basic, more or less junk self-signed certificates, generate_cert is the simple way to go. For instance my sinkhole SMTP server now uses one of these certs; SMTP senders don't care about details like good O values in your SMTP certificates, and even if they did in general spammers probably don't. If I was generating more proper self-signed certificates, one where people might see them in a browser or something, I would probably use CFSSL.

(Although if I only needed certificates with a constant Subject name, the lazy way to go would be to hardcode everything in a version of generate_cert and then just crank out a mass of self-signed certificates without having to deal with JSON.)

PS: We might someday want self-signed certificates with relatively proper O values and so on, for purely internal hosts that live in our own internal DNS zones. Updated TLS certificates for IPMI web interfaces are one potential case that comes to mind.

PPS: It's entirely possible that there's a better command line tool for this out there that I haven't stumbled over yet. Certainly this feels like a wheel that people must have reinvented several times; I almost started writing something myself before finding generate_cert.

Comments on this page:

By Alexey at 2017-04-12 03:26:30:

"Grab this file, copy it to a directory, then: $ go build generate_cert.go"

You may want to add a short note about GOPATH, for non-Go programmers. Recently I've wasted an hour trying while learning Go, because my source code wasn't sitting somewhere inside $GOPATH/src tree.

By Alexey at 2017-04-12 03:33:50:

And no, this is not enough:

mkdir ~/go/src; ln -s ~/src/myproj ~/go/src/myproj; cd ~/src/myproj; GOPATH=~/go go build main.go

Because Go doesn't want you to symlink to the insides of GOPATH.

By cks at 2017-04-12 08:54:45:

In this case you don't actually need a $GOPATH as far as I can tell, because generate_cert.go only uses packages from the standard library that comes with Go itself. Provided that you have Go installed you can just build without further fiddling.

(And in Go 1.8, a standard $GOPATH setting of $HOME/go is assumed if $GOPATH is unset.)

By James (trs80) at 2017-04-12 11:25:03:

My go-to tool is OpenVPN's EasyRSA. Admittedly that creates a CA which you can then sign certificates with, but for your internal hosts it would mean you could install said CA into your browser and then trust them all.

By James (trs80) at 2017-04-12 12:17:20:

Oh, and macOS Sierra doesn't like certs without a CN which is a change in behaviour from the previous version. If it doesn't get changed back then iOS 11 will probably behave the same.

By cks at 2017-04-12 12:36:10:

There's a difference between a certificate without a CN but with other elements of the Subject Name and a certificate that has nothing in the Subject Name at all. Fortunately the reports seem to be about certificates with a completely empty Subject Name, so if your certificates have eg an O field you're probably fine.

While CAs are issuing certificates without CNs, I don't think they're issuing them without any Subject Name fields at all. Let's Encrypt probably comes closest, since all they have in the Subject Name is a CN (and there have been proposals to drop that, which probably won't happen now).

By cks at 2017-04-12 21:42:49:

James: my view is that local CAs are quite dangerous if you add them to your browser's trust roots. If you're just going to use a CA management tool as a convenient way to spit out decently put together certificates and certificate chains, I think that's relatively harmless. I don't know if browsers or other software treats self-signed certificates differently from certificates chained from an unknown root, though.

(For things like SMTP clients, they probably don't. My impression is that most SMTP sending software just uses the server's TLS certificate as part of a way to bring up encryption, and will accept any old garbage. If they're doing authenticated SMTP, MUAs like Thunderbird are hopefully more cautious, but for that you're generally going to want a real valid certificate.)

Debian/Ubuntu have a handy script to make self-signed certs with the host's FQDN in the CN (though no SANs by default):

RSA 2048, SHA2, and ten year expiration. Theoretically they can hook into debconf to create SANs, but that does seem to work properly (DebBug 832036).

By Walex at 2017-04-14 18:11:05:

I use a somewhat subtle GNU Makefile and templates here with OpenSSL:

The main advantage is that the templates allow generating unusual but quite useful certificates. As some of then comments or defaults show it is quite old, but still works well.

By Walex at 2017-04-14 20:02:48:
> it is quite old, but still works well

I looked at it again and some details were too old, so slightly updated.

Written on 12 April 2017.
« How TLS certificates specify the hosts they're for
On today's web, a local Certificate Authority is fairly dangerous »

Page tools: View Source, View Normal, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Wed Apr 12 00:23:59 2017
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.