Skip to content

Instantly share code, notes, and snippets.

@fntlnz
Last active May 17, 2024 07:59
Show Gist options
  • Save fntlnz/cf14feb5a46b2eda428e000157447309 to your computer and use it in GitHub Desktop.
Save fntlnz/cf14feb5a46b2eda428e000157447309 to your computer and use it in GitHub Desktop.
Self Signed Certificate with Custom Root CA

Create Root CA (Done once)

Create Root Key

Attention: this is the key used to sign the certificate requests, anyone holding this can sign certificates on your behalf. So keep it in a safe place!

openssl genrsa -des3 -out rootCA.key 4096

If you want a non password protected key just remove the -des3 option

Create and self sign the Root Certificate

openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt

Here we used our root key to create the root certificate that needs to be distributed in all the computers that have to trust us.

Create a certificate (Done for each server)

This procedure needs to be followed for each server/appliance that needs a trusted certificate from our CA

Create the certificate key

openssl genrsa -out mydomain.com.key 2048

Create the signing (csr)

The certificate signing request is where you specify the details for the certificate you want to generate. This request will be processed by the owner of the Root key (you in this case since you create it earlier) to generate the certificate.

Important: Please mind that while creating the signign request is important to specify the Common Name providing the IP address or domain name for the service, otherwise the certificate cannot be verified.

I will describe here two ways to gener

Method A (Interactive)

If you generate the csr in this way, openssl will ask you questions about the certificate to generate like the organization details and the Common Name (CN) that is the web address you are creating the certificate for, e.g mydomain.com.

openssl req -new -key mydomain.com.key -out mydomain.com.csr

Method B (One Liner)

This method generates the same output as Method A but it's suitable for use in your automation :) .

openssl req -new -sha256 -key mydomain.com.key -subj "/C=US/ST=CA/O=MyOrg, Inc./CN=mydomain.com" -out mydomain.com.csr

If you need to pass additional config you can use the -config parameter, here for example I want to add alternative names to my certificate.

openssl req -new -sha256 \
    -key mydomain.com.key \
    -subj "/C=US/ST=CA/O=MyOrg, Inc./CN=mydomain.com" \
    -reqexts SAN \
    -config <(cat /etc/ssl/openssl.cnf \
        <(printf "\n[SAN]\nsubjectAltName=DNS:mydomain.com,DNS:www.mydomain.com")) \
    -out mydomain.com.csr

Verify the csr's content

openssl req -in mydomain.com.csr -noout -text

Generate the certificate using the mydomain csr and key along with the CA Root key

openssl x509 -req -in mydomain.com.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out mydomain.com.crt -days 500 -sha256

Verify the certificate's content

openssl x509 -in mydomain.com.crt -text -noout
@perseon
Copy link

perseon commented Jun 2, 2020

I had to amend the last command because on ubuntu the certificate was missing the SAN section.
what i did was to create ans supply a config file
openssl x509 -req -in 192.168.10.100.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out 192.168.10.100.crt -days 3650 -sha256 -extfile certificate.conf -extensions req_ext

and the config file contains:

[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[dn]
C = RO
ST = Bucharest
L = Bucharest
O = ACME, INC
OU = OU
emailAddress = some@email.com
CN = address.local
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = address.local
IP.1 = 192.168.10.100

@AlexeyNovikov
Copy link

A small note. If you want to use this in your local network you have to add IP addresses your server runs on as well as localhost to subjectAltName, e.g.:

[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = development.personal #real domain
DNS.2 = localhost
IP.1 = 127.0.0.1 # ip for localhost
IP.2 = 192.168.10.10 # local network address
IP.3 = 94.181.174.85 # external address

@ykfq
Copy link

ykfq commented Jul 16, 2020

openssl genrsa -out apiserver.key 2048
openssl req -new -key apiserver.key -out apiserver.csr -subj "/CN=kube-apiserver"
openssl x509 -req -in apiserver.csr -CA /etc/kubernetes/ssl/ca.crt -CAkey /etc/kubernetes/ssl/ca.key \
             -CAcreateserial -out apiserver.crt -days 3650 -extensions SAN \
             -extfile <(printf "[SAN]\nsubjectAltName=DNS:localhost,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local,DNS:lb-apiserver.kubernetes.local,IP:127.0.0.1,IP:192.168.10.21,IP:192.168.10.21")

@edbshubham
Copy link

The security team is just providing the rootCA.crt but not rootCA.key, is their any way to sign the server level certificate using the only rootCA.crt?

@xB-2048
Copy link

xB-2048 commented Sep 21, 2020

@edbshubham Usually security teams won't share rootCA private key. Please send the CSR to your security team and ask them to sign it In response you should receive the signed cert and the rootCA.crt.

@jimmychu0807
Copy link

This is very convenience. Thank you!

@cgm-aw
Copy link

cgm-aw commented Nov 4, 2020

The problem mentioned by @chainhead also appears in Java when you use the generated root CA certificate in your truststore. You will see this exception:

javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

You must make sure that you use the v3_ca extension when creating the root CA:
openssl req -x509 -new -nodes -extensions v3_ca -key rootCA.key -sha256 -days 1024 -out rootCA.crt

Or you just disable this check with a VM parameter: -Djdk.security.allowNonCaAnchor=true
Of course this is not recommended :)

Otherwise an extremely helpful overview, thanks!

@minhphungbk03
Copy link

I followed all the steps and configured nginx to use the generated certificate:

    ssl_certificate         /root/mydomain.com.crt;
    ssl_certificate_key     /root/mydomain.com.key;
    ssl_client_certificate  /root/rootCA.crt;

    ssl_verify_client on;
    ssl_verify_depth 2;

however, when trying it with curl it doens't work: curl --cacert rootCA.crt https://localhost:9090

Any idea?

Hi, I face with the same issue. Does anyone has solution yet? Thanks!
https://gist.github.com/fntlnz/cf14feb5a46b2eda428e000157447309#gistcomment-3095363

@EmmanuelKasper
Copy link

@fntlnz
not sure how long you want to maintain this gist, but it lacks the command where the CA is signing a CSR containing at subjectAltName entry. You're documenting how generate a CSR containing a subjectAltName, but not how the CA should sign it :)
It should be
openssl x509 -req -extfile <(printf "subjectAltName=DNS:mydomain.com") -days 120 -in mydomain.com.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out mydomain.com.crt -sha256

as pointed out by @chrismacp

@pplmx
Copy link

pplmx commented Sep 17, 2021

Help: how to create a authentication tls in golang

Could anyone give me some help? Thanks a lot.

Error info

I can create root CA and the server CA successfully.
However, I cannot use localhost.key.pem and localhost.crt.pem to create a TLS connection in golang 1.17.
Client always failed to connect to the server, which threw the following

2021/09/17 14:27:21 x509: certificate signed by unknown authority
exit status 1

Server start

# server can be started normally
go run server.go 12345
# client cannot connect to the server.
go run client.go localhost:12345

CA creation

Here are my reproduced steps:

openssl req -x509 -nodes -sha256 \
	-newkey rsa:4096 \
	-days 10240 \
	-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg, Inc./OU=Software Dept/CN=localhost" \
	-keyout root.key.pem \
	-out root.crt.pem

# OPENSSL_CONF="/etc/ssl/openssl.cnf"
OPENSSL_CONF="/System/Library/OpenSSL/openssl.cnf"
openssl req -new -nodes \
    -newkey rsa:4096 \
    -subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg, Inc./OU=Software Dept/CN=localhost" \
	-reqexts SAN \
    -config <(cat "${OPENSSL_CONF}" \
        <(printf "\n[SAN]\nsubjectAltName=DNS:localhost")) \
    -keyout localhost.key.pem \
    -out localhost.csr

openssl x509 -req -sha256 -CAcreateserial -days 365 \
	-CA root.crt.pem \
	-CAkey root.key.pem \
	-extfile <(printf "subjectAltName=DNS:localhost") \
	-in localhost.csr \
	-out localhost.crt.pem

Golang code

Here is my golang code.
Server

package main

import (
    "bufio"
    "crypto/rand"
    "crypto/tls"
    "fmt"
    "log"
    "net"
    "os"
    "strconv"
    "strings"
    "time"
)

var count = 0
var ServerCertFile = "../certificate/localhost.crt.pem"
var ServerKeyFile = "../certificate/localhost.key.pem"

func handleConnection(c net.Conn) {
    fmt.Println("Connection from ", c.RemoteAddr().String())
    for {
        netData, err := bufio.NewReader(c).ReadString('\n')
        if err != nil {
            fmt.Println(err)
            return
        }

        temp := strings.TrimSpace(netData)
        if temp == "STOP" {
            break
        }
        fmt.Println(temp)
        counter := strconv.Itoa(count) + "\n"
        c.Write([]byte(counter))
    }
    defer func(c net.Conn) {
        err := c.Close()
        if err != nil {
            fmt.Println("Failed to Close Connection from ", c.RemoteAddr().String())
        }
    }(c)
}

func main() {

    arguments := os.Args
    if len(arguments) == 1 {
        fmt.Println("Please provide a port number!")
        return
    }
    PORT := ":" + arguments[1]

    // load certificate
    crt, err := tls.LoadX509KeyPair(ServerCertFile, ServerKeyFile)
    if err != nil {
        log.Fatalln(err.Error())
        return
    }
    tlsConfig := &tls.Config{}
    tlsConfig.Certificates = []tls.Certificate{crt}
    tlsConfig.Time = time.Now
    tlsConfig.Rand = rand.Reader

    l, err := tls.Listen("tcp", PORT, tlsConfig)
    if err != nil {
        log.Fatalln(err.Error())
        return
    }
    defer l.Close()

    for {
        conn, err := l.Accept()
        if err != nil {
            fmt.Println(err.Error())
            continue
        }
        go handleConnection(conn)
        count++
    }
}

Client

package main

import (
    "bufio"
    "crypto/tls"
    "fmt"
    "log"
    "os"
    "strings"
)

func main() {
    arguments := os.Args
    if len(arguments) == 1 {
        fmt.Println("Please provide host:port.")
        return
    }

    CONNECT := arguments[1]
    c, err := tls.Dial("tcp", CONNECT, nil)
    if err != nil {
        log.Fatalln(err.Error())
        return
    }

    for {
        reader := bufio.NewReader(os.Stdin)
        fmt.Print(">> ")
        text, _ := reader.ReadString('\n')
        fmt.Fprintf(c, text+"\n")

        message, _ := bufio.NewReader(c).ReadString('\n')
        fmt.Print("->: " + message)
        if strings.TrimSpace(string(text)) == "STOP" {
            fmt.Println("TCP client exiting...")
            return
        }
    }
}

@cspiel1
Copy link

cspiel1 commented Nov 4, 2022

Option -copy_extensions copy is missing for openssl x509 -req command because of the example with the Subject alternative name.

@shreeve
Copy link

shreeve commented Dec 17, 2022

#!/bin/bash

# =============================================================================
# ssl-certs.sh - Self signing SSL certificates
#
# Author: Steve Shreeve <steve.shreeve@gmail.com>
#   Date: Dec 17, 2022
# =============================================================================

# Use https://gist.github.com/shreeve/3358901a26a21d4ddee0e1342be7749d
# See https://gist.github.com/fntlnz/cf14feb5a46b2eda428e000157447309

# variables
name="ACME, Inc."
base="acme.com"
root="root"
myip="$(ifconfig | awk '/inet / { print $2 }' | grep -v -E "^127\." | head -1)"

# create root key and certificate
openssl genrsa -out "${root}.key" 3072
openssl req -x509 -nodes -sha256 -new -key "${root}.key" -out "${root}.crt" -days 731 \
  -subj "/CN=Custom Root" \
  -addext "keyUsage = critical, keyCertSign" \
  -addext "basicConstraints = critical, CA:TRUE, pathlen:0" \
  -addext "subjectKeyIdentifier = hash"

# create our key and certificate signing request
openssl genrsa -out "${base}.key" 2048
openssl req -sha256 -new -key "${base}.key" -out "${base}.csr" \
  -subj "/CN=*.${base}/O=${name}/OU=$(whoami)@$(hostname) ($(/usr/bin/id -F))" \
  -reqexts SAN -config <(echo "[SAN]\nsubjectAltName=DNS:${base},DNS:*.${base},IP:127.0.0.1,IP:${myip}\n")

# create our final certificate and sign it
openssl x509 -req -sha256 -in "${base}.csr" -out "${base}.crt" -days 731 \
  -CAkey "${root}.key" -CA "${root}.crt" -CAcreateserial -extfile <(cat <<END
    subjectAltName = DNS:${base},DNS:*.${base},IP:127.0.0.1,IP:${myip}
    keyUsage = critical, digitalSignature, keyEncipherment
    extendedKeyUsage = serverAuth
    basicConstraints = CA:FALSE
    authorityKeyIdentifier = keyid:always
    subjectKeyIdentifier = none
END
)

# update the macOS trust store (TODO: add other operating systems)
sudo /usr/bin/security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${root}.crt"

# review files
echo "--"; openssl x509 -in "${root}.crt" -noout -text
echo "--"; openssl req  -in "${base}.csr" -noout -text
echo "--"; openssl x509 -in "${base}.crt" -noout -text
echo "--";

@yura833
Copy link

yura833 commented Oct 13, 2023

Hi i am encouraging error like this:

So i am using reverse proxy for my web server and want ssl certificate to be included in my nginx config file so i followed this
guide here that creates ssl certificate and did these steps to create csr:
openssl genrsa -des3 -out rootCA.key 4096

openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt

openssl genrsa -out mydomain.com.key 2048

openssl req -new -sha256
-key SERVER.key
-subj "/C=US/ST=North Carolina/O=ORG/OU=ORG_UNIT/CN=mydomain.com"
-reqexts SAN
-config <(cat /etc/ssl/openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:mydomain.com"))
-out SERVER.csr
then i used this for signing certifiacte for my web server:
openssl x509 -req -extfile <(printf "subjectAltName=DNS:YOUR_DOMAIN_NAME") -days 120 -in SERVER.csr -CA rootCA.crt -CAkey root_rsa.key -CAcreateserial -out SERVER.crt -sha256

and everything looked fine btw i am using swarm, and when i try to connect to server i get 502 bad gateway and after using curl https://mydomain.com i get this error:

curl: (35) OpenSSL/3.1.3: error:0A00010B:SSL routines::wrong version number

inside my container logs it shows this:
failed (SSL: error:0A00010B:SSL routines::wrong version number) while SSL handshaking to upstream, client: 10.0.0.2, server: , request: "GET / HTTP/1.1", upstream

does anyone have idea what can i do here ?

@sourabhsarkar
Copy link

I followed your guide but cannot verify created certificate with root CA

openssl verify -CAfile rootCA.crt mydomain.com.crt C = AU, ST = Some-State, O = Internet Widgits Pty Ltd error 18 at 0 depth lookup: self signed certificate error mydomain.com.crt: verification failed

@notariuss you should use: openssl verify -CAfile C:\ca-cert.pem C:\mycert.pem

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment