219

During my search, I found several ways of signing a SSL Certificate Signing Request:

  1. Using the x509 module:

    openssl x509 -req -days 360 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
    
  2. Using the ca module:

    openssl ca -cert ca.crt -keyfile ca.key -in server.csr -out server.crt
    

Note: I am unsure of the use of the right parameters for this one. Please advise correct usage if I am to use it.

What way should one use to sign certificate requests with your Certification Authority? Is one method better than the other (for example, one being deprecated)?

Bernard Rosset
  • 3,753
  • 5
  • 25
  • 28
  • 1
    possible duplicate of [How to build a self-signed certificate with openssl?](http://stackoverflow.com/questions/10175812/how-to-build-a-self-signed-certificate-with-openssl) – jww Feb 07 '14 at 07:42
  • From what I can see, `ca` is for cases when you're more serious about becoming a CA. – x-yuri Mar 29 '18 at 08:08
  • You might find [my answer](https://stackoverflow.com/a/49553299/52499) interesting. – x-yuri Mar 29 '18 at 10:39
  • Stack Overflow is a site for programming and development questions. This question appears to be off-topic because it is not about programming or development. See [What topics can I ask about here](http://stackoverflow.com/help/on-topic) in the Help Center. Perhaps [Super User](http://superuser.com/) or [Unix & Linux Stack Exchange](http://unix.stackexchange.com/) would be a better place to ask. – jww May 20 '18 at 04:01
  • The accepted answer is great, also check my little addition to it https://stackoverflow.com/a/47970330/5457916 – humble_wolf Jun 21 '19 at 11:31

2 Answers2

506
1. Using the x509 module
openssl x509 ...
...

2 Using the ca module
openssl ca ...
...

You are missing the prelude to those commands.

This is a two-step process. First you set up your CA, and then you sign an end entity certificate (a.k.a server or user). Both of the two commands elide the two steps into one. And both assume you have a an OpenSSL configuration file already setup for both CAs and Server (end entity) certificates.


First, create a basic configuration file:

$ touch openssl-ca.cnf

Then, add the following to it:

HOME            = .
RANDFILE        = $ENV::HOME/.rnd

####################################################################
[ ca ]
default_ca    = CA_default      # The default ca section

[ CA_default ]

default_days     = 1000         # How long to certify for
default_crl_days = 30           # How long before next CRL
default_md       = sha256       # Use public key default MD
preserve         = no           # Keep passed DN ordering

x509_extensions = ca_extensions # The extensions to add to the cert

email_in_dn     = no            # Don't concat the email in the DN
copy_extensions = copy          # Required to copy SANs from CSR to cert

####################################################################
[ req ]
default_bits       = 4096
default_keyfile    = cakey.pem
distinguished_name = ca_distinguished_name
x509_extensions    = ca_extensions
string_mask        = utf8only

####################################################################
[ ca_distinguished_name ]
countryName         = Country Name (2 letter code)
countryName_default = US

stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = Maryland

localityName                = Locality Name (eg, city)
localityName_default        = Baltimore

organizationName            = Organization Name (eg, company)
organizationName_default    = Test CA, Limited

organizationalUnitName         = Organizational Unit (eg, division)
organizationalUnitName_default = Server Research Department

commonName         = Common Name (e.g. server FQDN or YOUR name)
commonName_default = Test CA

emailAddress         = Email Address
emailAddress_default = test@example.com

####################################################################
[ ca_extensions ]

subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints       = critical, CA:true
keyUsage               = keyCertSign, cRLSign

The fields above are taken from a more complex openssl.cnf (you can find it in /usr/lib/openssl.cnf), but I think they are the essentials for creating the CA certificate and private key.

Tweak the fields above to suit your taste. The defaults save you the time from entering the same information while experimenting with configuration file and command options.

I omitted the CRL-relevant stuff, but your CA operations should have them. See openssl.cnf and the related crl_ext section.

Then, execute the following. The -nodes omits the password or passphrase so you can examine the certificate. It's a really bad idea to omit the password or passphrase.

$ openssl req -x509 -config openssl-ca.cnf -newkey rsa:4096 -sha256 -nodes -out cacert.pem -outform PEM

After the command executes, cacert.pem will be your certificate for CA operations, and cakey.pem will be the private key. Recall the private key does not have a password or passphrase.

You can dump the certificate with the following.

$ openssl x509 -in cacert.pem -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 11485830970703032316 (0x9f65de69ceef2ffc)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, ST=MD, L=Baltimore, CN=Test CA/emailAddress=test@example.com
        Validity
            Not Before: Jan 24 14:24:11 2014 GMT
            Not After : Feb 23 14:24:11 2014 GMT
        Subject: C=US, ST=MD, L=Baltimore, CN=Test CA/emailAddress=test@example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
                Modulus:
                    00:b1:7f:29:be:78:02:b8:56:54:2d:2c:ec:ff:6d:
                    ...
                    39:f9:1e:52:cb:8e:bf:8b:9e:a6:93:e1:22:09:8b:
                    59:05:9f
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                4A:9A:F3:10:9E:D7:CF:54:79:DE:46:75:7A:B0:D0:C1:0F:CF:C1:8A
            X509v3 Authority Key Identifier:
                keyid:4A:9A:F3:10:9E:D7:CF:54:79:DE:46:75:7A:B0:D0:C1:0F:CF:C1:8A

            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Key Usage:
                Certificate Sign, CRL Sign
    Signature Algorithm: sha256WithRSAEncryption
         4a:6f:1f:ac:fd:fb:1e:a4:6d:08:eb:f5:af:f6:1e:48:a5:c7:
         ...
         cd:c6:ac:30:f9:15:83:41:c1:d1:20:fa:85:e7:4f:35:8f:b5:
         38:ff:fd:55:68:2c:3e:37

And test its purpose with the following (don't worry about the Any Purpose: Yes; see "critical,CA:FALSE" but "Any Purpose CA : Yes").

$ openssl x509 -purpose -in cacert.pem -inform PEM
Certificate purposes:
SSL client : No
SSL client CA : Yes
SSL server : No
SSL server CA : Yes
Netscape SSL server : No
Netscape SSL server CA : Yes
S/MIME signing : No
S/MIME signing CA : Yes
S/MIME encryption : No
S/MIME encryption CA : Yes
CRL signing : Yes
CRL signing CA : Yes
Any Purpose : Yes
Any Purpose CA : Yes
OCSP helper : Yes
OCSP helper CA : Yes
Time Stamp signing : No
Time Stamp signing CA : Yes
-----BEGIN CERTIFICATE-----
MIIFpTCCA42gAwIBAgIJAJ9l3mnO7y/8MA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV
...
aQUtFrV4hpmJUaQZ7ySr/RjCb4KYkQpTkOtKJOU1Ic3GrDD5FYNBwdEg+oXnTzWP
tTj//VVoLD43
-----END CERTIFICATE-----

For part two, I'm going to create another configuration file that's easily digestible. First, touch the openssl-server.cnf (you can make one of these for user certificates also).

$ touch openssl-server.cnf

Then open it, and add the following.

HOME            = .
RANDFILE        = $ENV::HOME/.rnd

####################################################################
[ req ]
default_bits       = 2048
default_keyfile    = serverkey.pem
distinguished_name = server_distinguished_name
req_extensions     = server_req_extensions
string_mask        = utf8only

####################################################################
[ server_distinguished_name ]
countryName         = Country Name (2 letter code)
countryName_default = US

stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = MD

localityName         = Locality Name (eg, city)
localityName_default = Baltimore

organizationName            = Organization Name (eg, company)
organizationName_default    = Test Server, Limited

commonName           = Common Name (e.g. server FQDN or YOUR name)
commonName_default   = Test Server

emailAddress         = Email Address
emailAddress_default = test@example.com

####################################################################
[ server_req_extensions ]

subjectKeyIdentifier = hash
basicConstraints     = CA:FALSE
keyUsage             = digitalSignature, keyEncipherment
subjectAltName       = @alternate_names
nsComment            = "OpenSSL Generated Certificate"

####################################################################
[ alternate_names ]

DNS.1  = example.com
DNS.2  = www.example.com
DNS.3  = mail.example.com
DNS.4  = ftp.example.com

If you are developing and need to use your workstation as a server, then you may need to do the following for Chrome. Otherwise Chrome may complain a Common Name is invalid (ERR_CERT_COMMON_NAME_INVALID). I'm not sure what the relationship is between an IP address in the SAN and a CN in this instance.

# IPv4 localhost
IP.1     = 127.0.0.1

# IPv6 localhost
IP.2     = ::1

Then, create the server certificate request. Be sure to omit -x509*. Adding -x509 will create a certificate, and not a request.

$ openssl req -config openssl-server.cnf -newkey rsa:2048 -sha256 -nodes -out servercert.csr -outform PEM

After this command executes, you will have a request in servercert.csr and a private key in serverkey.pem.

And you can inspect it again.

$ openssl req -text -noout -verify -in servercert.csr
Certificate:
    verify OK
    Certificate Request:
        Version: 0 (0x0)
        Subject: C=US, ST=MD, L=Baltimore, CN=Test Server/emailAddress=test@example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:ce:3d:58:7f:a0:59:92:aa:7c:a0:82:dc:c9:6d:
                    ...
                    f9:5e:0c:ba:84:eb:27:0d:d9:e7:22:5d:fe:e5:51:
                    86:e1
                Exponent: 65537 (0x10001)
        Attributes:
        Requested Extensions:
            X509v3 Subject Key Identifier:
                1F:09:EF:79:9A:73:36:C1:80:52:60:2D:03:53:C7:B6:BD:63:3B:61
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Key Usage:
                Digital Signature, Key Encipherment
            X509v3 Subject Alternative Name:
                DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com
            Netscape Comment:
                OpenSSL Generated Certificate
    Signature Algorithm: sha256WithRSAEncryption
         6d:e8:d3:85:b3:88:d4:1a:80:9e:67:0d:37:46:db:4d:9a:81:
         ...
         76:6a:22:0a:41:45:1f:e2:d6:e4:8f:a1:ca:de:e5:69:98:88:
         a9:63:d0:a7

Next, you have to sign it with your CA.


You are almost ready to sign the server's certificate by your CA. The CA's openssl-ca.cnf needs two more sections before issuing the command.

First, open openssl-ca.cnf and add the following two sections.

####################################################################
[ signing_policy ]
countryName            = optional
stateOrProvinceName    = optional
localityName           = optional
organizationName       = optional
organizationalUnitName = optional
commonName             = supplied
emailAddress           = optional

####################################################################
[ signing_req ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints       = CA:FALSE
keyUsage               = digitalSignature, keyEncipherment

Second, add the following to the [ CA_default ] section of openssl-ca.cnf. I left them out earlier, because they can complicate things (they were unused at the time). Now you'll see how they are used, so hopefully they will make sense.

base_dir      = .
certificate   = $base_dir/cacert.pem   # The CA certifcate
private_key   = $base_dir/cakey.pem    # The CA private key
new_certs_dir = $base_dir              # Location for new certs after signing
database      = $base_dir/index.txt    # Database index file
serial        = $base_dir/serial.txt   # The current serial number

unique_subject = no  # Set to 'no' to allow creation of
                     # several certificates with same subject.

Third, touch index.txt and serial.txt:

$ touch index.txt
$ echo '01' > serial.txt

Then, perform the following:

$ openssl ca -config openssl-ca.cnf -policy signing_policy -extensions signing_req -out servercert.pem -infiles servercert.csr

You should see similar to the following:

Using configuration from openssl-ca.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName           :PRINTABLE:'US'
stateOrProvinceName   :ASN.1 12:'MD'
localityName          :ASN.1 12:'Baltimore'
commonName            :ASN.1 12:'Test CA'
emailAddress          :IA5STRING:'test@example.com'
Certificate is to be certified until Oct 20 16:12:39 2016 GMT (1000 days)
Sign the certificate? [y/n]:Y

1 out of 1 certificate requests certified, commit? [y/n]Y
Write out database with 1 new entries
Data Base Updated

After the command executes, you will have a freshly minted server certificate in servercert.pem. The private key was created earlier and is available in serverkey.pem.

Finally, you can inspect your freshly minted certificate with the following:

$ openssl x509 -in servercert.pem -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 9 (0x9)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, ST=MD, L=Baltimore, CN=Test CA/emailAddress=test@example.com
        Validity
            Not Before: Jan 24 19:07:36 2014 GMT
            Not After : Oct 20 19:07:36 2016 GMT
        Subject: C=US, ST=MD, L=Baltimore, CN=Test Server
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:ce:3d:58:7f:a0:59:92:aa:7c:a0:82:dc:c9:6d:
                    ...
                    f9:5e:0c:ba:84:eb:27:0d:d9:e7:22:5d:fe:e5:51:
                    86:e1
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                1F:09:EF:79:9A:73:36:C1:80:52:60:2D:03:53:C7:B6:BD:63:3B:61
            X509v3 Authority Key Identifier:
                keyid:42:15:F2:CA:9C:B1:BB:F5:4C:2C:66:27:DA:6D:2E:5F:BA:0F:C5:9E

            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Key Usage:
                Digital Signature, Key Encipherment
            X509v3 Subject Alternative Name:
                DNS:example.com, DNS:www.example.com, DNS:mail.example.com, DNS:ftp.example.com
            Netscape Comment:
                OpenSSL Generated Certificate
    Signature Algorithm: sha256WithRSAEncryption
         b1:40:f6:34:f4:38:c8:57:d4:b6:08:f7:e2:71:12:6b:0e:4a:
         ...
         45:71:06:a9:86:b6:0f:6d:8d:e1:c5:97:8d:fd:59:43:e9:3c:
         56:a5:eb:c8:7e:9f:6b:7a

Earlier, you added the following to CA_default: copy_extensions = copy. This copies extension provided by the person making the request.

If you omit copy_extensions = copy, then your server certificate will lack the Subject Alternate Names (SANs) like www.example.com and mail.example.com.

If you use copy_extensions = copy, but don't look over the request, then the requester might be able to trick you into signing something like a subordinate root (rather than a server or user certificate). Which means he/she will be able to mint certificates that chain back to your trusted root. Be sure to verify the request with openssl req -verify before signing.


If you omit unique_subject or set it to yes, then you will only be allowed to create one certificate under the subject's distinguished name.

unique_subject = yes            # Set to 'no' to allow creation of
                                # several ctificates with same subject.

Trying to create a second certificate while experimenting will result in the following when signing your server's certificate with the CA's private key:

Sign the certificate? [y/n]:Y
failed to update database
TXT_DB error number 2

So unique_subject = no is perfect for testing.


If you want to ensure the Organizational Name is consistent between self-signed CAs, Subordinate CA and End-Entity certificates, then add the following to your CA configuration files:

[ policy_match ]
organizationName = match

If you want to allow the Organizational Name to change, then use:

[ policy_match ]
organizationName = supplied

There are other rules concerning the handling of DNS names in X.509/PKIX certificates. Refer to these documents for the rules:

RFC 6797 and RFC 7469 are listed, because they are more restrictive than the other RFCs and CA/B documents. RFC's 6797 and 7469 do not allow an IP address, either.

Lelo
  • 734
  • 9
  • 23
jww
  • 83,594
  • 69
  • 338
  • 732
  • 5
    Thanks for that extensive answer... However, I am kind of lost here. What I understood from what you wrote: `openssl req` is used to generate CSR, `openssl req -x509` is used to generate CA certificate (I saw in some other place you could create self-signed certificate too), `openssl ca` is used to sign a CSR with a CA certificate. Right? What confuses me too is that the same parts of the openssl.cnf file are used with different values depending on the command... I think I am totally lost now. – Bernard Rosset Jan 25 '14 at 00:53
  • 31
    First, `openssl req -x509` is used to create the CA. Second, `openssl req` is used to create the server's CSR. Third, `openssl ca` is used to create the server certificate and certify it with the CA's signature. – jww Jan 25 '14 at 00:58
  • 2
    "What confuses me too is that the same parts of the openssl.cnf..." - Right. That's why I broke them out for you into `openssl-ca.cnf` and `openssl-server.cnf`. After you get used to them and how the sections are invoked, you can combine them into a monstrosity like `openssl.cnf`. – jww Jan 25 '14 at 00:59
  • Please validate or correct the 3 assertions I made in my previous comment to sse if I understood you answer. Thanks for the detailed help! – Bernard Rosset Jan 25 '14 at 05:00
  • If I were to add `DNS.5 = *.example.com` and `DNS.6 = *.*.*.*.example.com` would these wildcards work as expected, for domains `www.example.com` and `www.test.com.dev.example.com` respectively? – LeonardChallis Aug 12 '15 at 07:47
  • @LeonardChallis - I believe `*.example.com` will work under all user agents and issuing policies. `*.*.*.*.example.com` will fail under browsers because the CA/B forums only allow a wildcard in the leftmost label. The behavior of other user agents will vary. (Even more interesting, you can add DNS names like `*.com`, `*.net`, `www.*.com` and `www.*.com` and non-browser user agents will match them. Dooh....) – jww Sep 03 '15 at 23:30
  • Thanks @jww. I've used several of your posts extensively to get things working for our dev server with many (sub)-sub-domains :) – LeonardChallis Sep 04 '15 at 09:08
  • I have another question. What does the `default_days` refer to? And in the CA the `Validity` seems to always default to a month. Does this reflect on how long self-signed certs will last? – LeonardChallis Dec 08 '15 at 13:47
  • why do you have `x509_extensions = ca_extensions` in both the `[ CA_default ]` and `[ req ]` sections? – Jeff Puckett Jul 26 '16 at 20:38
  • 1
    @JeffPuckettII - Its a common section. Its used by both the CA utility and the Req utility. They should be v3 extensions. – jww Jul 26 '16 at 20:43
  • I pretty much follow all the steps but I am still getting the error `The certificate is not trusted in all web browsers. You may need to install an Intermediate/chain certificate to link it to a trusted root certificate` and end up having to buy from a ssl provider to get pass this problem – user3667089 Jan 05 '17 at 21:30
  • 1
    Thanks @jww, nice description. One thing I dont follow is the duration of the CA certificate. openssl-ca.cnf says default_days 1000 but the validity of the cert is just over a month. Where does the 30 days come from? – ahnkle Feb 13 '17 at 14:24
  • 5
    @ahnkle Use -days option for anything different from the default 30 days. [OpenSSL docs](https://www.openssl.org/docs/manmaster/man1/req.html) – george Apr 27 '17 at 12:21
  • Thanks for the explanations @jww! How would you do for Docker images, like 192.168.99.100 ? Would you be able to add the IP in the SNA section ? – Johan Chouquet Sep 19 '17 at 14:52
  • Can the signing of certificate using CA be done using GSKIT instead of openssl? I have my CA, have created the key and the csr. Need to sign it using gskit. – Hitesh Bajaj Dec 16 '17 at 11:32
  • "-sha256" is not listed as an option for "openssl req" or "openssl genrsa" in either openssl version 1.0.2 nor 1.1.0. ( https://www.openssl.org/docs/ ) – Craig Hicks Apr 15 '18 at 06:49
  • This is like the best answer I've ever seen on stack overflow. You solved a 6 hour headache with this. Thanks! – Jim Jul 08 '18 at 10:29
  • See my answer below, it adds a little to this great answer. – humble_wolf Jul 23 '18 at 08:37
  • @jww Great answer... but one hiccup: I think I've followed your steps correctly (minor tweaks for names etc.), but when I come to sign the request, I only get the message about using the `.cnf` file and asking for a passphrase (I omitted -nodes). I don't get `Check that the request matches the signature` or anything after it. (The pass-phrase is correct, because it complains if I deliberately enter it wrong). Any obvious things that might cause that? (OpenSSL=v1.1.1d) – TripeHound Nov 11 '19 at 15:55
  • 1
    @jww I found the problem: PBCAC... specifically creating the "index.txt" As I was on Windows, and didn't have a `touch` command to hand, I (stupidly) just created an "empty" text file (`CR-LF`). This causes `openssl` to give-up somewhere after checking the private key but – annoyingly – with no indication of why. Creating a true zero-byte file for the index has made it spring into life! – TripeHound Nov 12 '19 at 10:58
  • Great answer. Thanks for that. For anybody looking for including UPN in the client certificate (above answer is for server certificate, but configurations and procedure for client certificate also will be in the same lines), it can be done by `subjectAltName=otherName:1.3.6.1.4.1.311.20.2.3;UTF8:USER@EXAMPLE.COM` under _req_extensions_ section, OR with `otherName=1.3.6.1.4.1.311.20.2.3;UTF8:USER@EXAMPLE.COM` under _alternate_names_ section of _openssl-client.cnf_ file (file name inspired by openssl-server.cnf) – ramtech Mar 05 '20 at 05:42
  • Very informative. In addition, for those who want to understand more about `preserve` and `copy_extensions` options, check this out: https://stackoverflow.com/questions/48672935/openssl-ca-vs-openssl-x509-the-openssl-ca-command-doesnt-register-the-same-on – Kenna Mar 05 '20 at 18:27
18

In addition to answer of @jww, I would like to say that the configuration in openssl-ca.cnf,

default_days     = 1000         # How long to certify for

defines the default number of days the certificate signed by this root-ca will be valid. To set the validity of root-ca itself you should use '-days n' option in:

openssl req -x509 -days 3000 -config openssl-ca.cnf -newkey rsa:4096 -sha256 -nodes -out cacert.pem -outform PEM

Failing to do so, your root-ca will be valid for only the default one month and any certificate signed by this root CA will also have validity of one month.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
humble_wolf
  • 952
  • 11
  • 20
  • 2
    To anyone doing iOS or macOS development be aware that certificates issued for 1,000 days will not be trusted in iOS 13+ (extendedKeyUsage is also required). I was able to finally sign my own trusted certs using both these answers in addition to the answer here: https://apple.stackexchange.com/questions/371725/why-does-ios-13-not-trust-my-own-root-ca – Jeff Camera Oct 29 '20 at 20:11