Running your own Certificate Authority is truly the best option if you're managing a large number of systems which need to have secured, authenticated communication flowing between them. While it is a fair amount of work to setup and maintain, it is much cheaper than signing each of your leaf certificates with an external Certificate Authority, and much more secure than using self-signed certificates.
It is recommended that you read these other blog posts before starting, as they will cover some of the more general knowledge of X.509 which will help us proceed with setting up a CA.
Also, I recommend checking out Jamie Nguyen's guide on setting up an OpenSSL Certificate Authority which is focused on creating an RSA based certificate authority. This guide will create both RSA and ECDSA based certificate authorities.
Because of it's size and performance properties, ECDSA is likely to become the dominate cryptosystem, however it isn't nearly as widely supported as RSA is. As well, ECDSA and RSA have different performance properties under different operations, so in some circumstances, RSA is superior in performance to ECDSA, and will likely remain so.
We will prefer to use our ECDSA chain of trust, with the RSA chain being used as a backup, and both being used where a service supports using both. Since most of the effort of running a Certificate Authority is in securely distributing the root certificates in the first place, why not just distribute two keys instead of one?
Step 0: Have a Plan
Most of the work of running a Certificate Authority comes down to management and compartmentalization. Spending a few minutes to plan this out can really help. Before we go too much further, lets take a quick look at possible ways to configure a Certificate Authority.
Single Key Certificate Authority
A single key, or a root-leaf certificate authority uses a single self-signed long-lived key-pair (root) generated on a secured (and preferably air-gaped) system that directly signs and revokes service (leaf) keys. This is the simplest form of Certificate Authority, but unfortunately also the least secure and least flexible.
Since leaf CSRs are signed directly by the root certificate, each CSR must be transferred to and each Certificate must be transferred from the air-gapped root key system, which could prove inconvenient. If your security methodologies fail and the root certificate private key is compromised, then the entire trust network fails (as is the case with the compromise of any anchoring key).
- Simple to administer
- Only requires a dedicated system for the Root Certificate
- Relatively inconvenient
- Potentially insecure
- Setup a root key-pair
- Fulfill leaf CSRs with the root key
Delegated Key Certificate Authority
A delegate key, intermediate key, or a root-branch-leaf certificate authority uses a single self-signed long-lived key-pair (root) generated on a secured (and preferably air-gaped) system that signs medium-lived intermediate (branch) keys, authorizing them to sign and revoke short-lived service (leaf) keys. This is the recommended approach, and is only slightly more complicated than the above method, but allows for much more robust fulfillment of CSRs, and overall quite flexible.
In this approach, leaf CSRs are signed by branch keys, which can be kept much closer to the action, in their own dedicated limited-access VM for instance. The branch certificates are in turn created by the root key, which can still be kept on an air-gaped system and used only to issue new branch certificates.
- Somewhat more flexible than a single key
- Not quite as simple as a single key
- Setup a root key-pair
- Setup a branch key-pair
- Fulfill leaf CSRs with the branch key
We will be using the delegated key approach for our Certificate Authority.
Cross-Signed Certificate Authority
There are certain conditions where the flexibility provided by the linear delegated key certificate authority strategy isn't enough:
- A new public Certificate Authority is beginning operation and doesn't yet have a widely distributed root certificate. This is the case with the Lets Encrypt public certificate authority, which has cross-signed one of their intermediate certificates. Really, this is the only valid use of cross-signing.
Two private Certificate Authorities wish to use the PKI to authorize (or revoke authorization for) certificates from each other. In such a case, intermediate certificates from each certificate authority could be signed by the other to authorize them. While this may seem like an awesome idea, it's an excessive amount of work, and mainly included here for reference purposes.
- The most flexible configuration
- By far the most complicated
- Signing branch keys and providing bundles for leaf keys is not simple
- The overall management cost is prohibitive
The major complexity here is that certificates need to be issues from multiple branch or root keys, and must be presented as a combined bundle to the end recipient. This isn't really that much more complicated than the Delegated Key Certificate Authority described above, but there is really very little point in planning something like this, as this is usually develops temporarily as a new CA is coming online.
Our root key-pair will serve as the backbone of trust within our Certificate Authority. This is our planned schedule for the naming and use of our root key-pair:
|Name||Deployed On||Valid From||Valid To||Key-size|
|Cospix Root 2040 ECC||2016-02-29||2016-01-01||2040-01-01||384-bit|
|Cospix Root 2040 RSA||2016-02-29||2016-01-01||2040-01-01||3072-bit|
|Cospix Root 2060 ECC||2036-01-01||2036-01-01||2060-01-01||unknown|
According to the appropriate key size section in our previous article, an RSA key of approximately 1536-bit could be compromised by 2040. A minimum RSA key-size of 3072-bit is recommended to ensure adequate security through 2040. Due to the relative immaturity of attacks on ECDSA cryptosystems as well as its performance characteristics, we opt for using a 384-bit ECDSA root key-pair (instead of the 3072-bit RSA comparable 256-bit key-pair).
These root certificates have the job of anchoring the entire trust network, but the root keys only have three jobs:
- Issue Branch Key Certificates
- Issue Certificate Revocation Lists
- Stay Available and Secure
I can't stress that last one enough. You need to ensure that your root keys will be secure, hence our triple redundancy and heavy use of physical safes and encryption. Persistence in the LiveCD environment is attained by using Removable USB flash drives.
We are also planning for the replacement of our 2040 key with a 2060 key in 2036. This is done so that we have four years of overlap to distribute our 2060 key.
The certificate from this key-pair is included on our internal zone images.
Our branch key-pairs act as the delegated agents of the root, signing leaf keys for the windows they are marked as active:
|Name||Valid From||Valid To||Key-size|
|Cospix Branch 2020 ECC||2016-01-01||2020-01-01||256-bit|
|Cospix Branch 2020 RSA||2016-01-01||2020-01-01||2048-bit|
|Cospix Branch 2022 ECC||2018-01-01||2022-01-01||256-bit|
|Cospix Branch 2024 ECC||2020-01-01||2024-01-01||256-bit|
The Branch Keys are ECDSA keys with a 4 year lifespan. Additionally, they are replaced every 2 years with new Branch Certificates. This is so that certificates signed with the old keys remain valid. Additionally, this gives us the option of upgrading the strength of our branch keys if there are indications that a given key-size is no longer secure.
These keys reside on dedicated VMs (specifically a SmartOS Zone) with highly restricted (but still online) access; they are the primary certificate signing keys in the Certificate Authority.
These keys are normally restrained to a path length of one, so that they can only be used to issue service certificates, and not certificates that could be used in an additional CA. The root key is NOT restrained like this though, so if we need to perform cross-signing, we can generate a new cross-signing key (with a constrained path length of 2) for another organization or sub-organization if need be.
Some thoughts about security
There are three basic attacks that you need to be concerned with when running a Certificate Authority:
- Having your private key read by an unauthorized party: If an unauthorized party gains read access to a private key, they can issue unauthorized certificates and revoke authorized certificates under that key. While this is definitely a problem on a branch key, it's an absolute disaster on a root key, where the recourse of revoking and re-issuing is MUCH more complicated. This is why we insist on encrypting our keys wherever possible, as any attacker must first defeat the encryption on any key that they have managed to acquire before making use of it. Brute-forcing technologies have made that easier today than ever before.
- Having your private key deleted by an unauthorized party: While not quite as bad as our first point, if a private key is deleted anywhere along the chain, the area under that point is effectively immutable by your organization. This means that no additional keys or revocation lists can be issued. While this isn't a horrible deal for a branch key, it's a big problem for a root key.
- Having your root public key intercepted by an unauthorized party: This attack is the easiest to detect and guard against, but shouldn't be discounted because of this ease. If an attacker can add additional public keys into your root key distributions, they can gain the same trust that you have. This is easy to detect because there will either be additional keys, or certificates issued under your own chain of trust will fail to validate.
We will discuss these more later on as the methods of combating them become relevant.
Step 1: Setup the Branch Keys & CSRs
While this may seem like it's being done out of order, we actually want to initialize our branch key-pair before we setup our root key-pair. This is because we'll be able to create and transfer the branch CSR and the OpenSSL configuration in to our air-gapped laptop on a USB drive, perform our operations, and then transfer our newly issued certificates back out via the same USB drive.
We will be running our CA's branch key-pair from a minimally exposed SmartOS Zone hosted on a secure hypervisor. For the rest of this example, we'll be using a fresh base-64-lts instance with a limit of 128 MB of memory and 1-2GB quota. Networking is optional, and depends on your context.
Notice: The global zone this runs under should be physically and logically secured, since anyone with access to the global zone can read, alter, or destroy any records within that zone. Additionally, you will want to be careful about backing this zone up, since anyone with access to the backups could potentially gain access to your private key.
It's probably best practice to spin up a new Zone per generation of branch key-pair. In our case, we're going to use one Zone to host both RSA and ECDSA key-pairs. Once your zone has been initialized, login and continue
Create File & Directory Structure
Since this zone will be dedicated to our CA, we're going to setup shop in the system OpenSSL directory, located at
/opt/local/etc/openssl/. Since the only purpose of this zone is handling this branch authority, we might as well symlink from the root home directory.
# ln -sf /opt/local/etc/openssl ~ # cd ~/openssl # mkdir -p ecc/certs rsa/certs # chown 700 ecc rsa # touch ecc/index.txt rsa/index.txt # echo 00000001 > ecc/serial # echo 00000001 > rsa/serial # echo 00000001 > ecc/crlnumber # echo 00000001 > rsa/crlnumber
~/openssl/openssl.cnf with the following configuration file. Be sure to change your Distinguished Name defaults as appropriate:
Unless you will be automating the production of certificates at this branch, these keys should be encrypted for storage. While the highest performing 256-bit ECDSA curve is
secp256k1, we will be using the
prime256v1 curve instead.
# openssl ecparam -name prime256v1 -genkey | openssl ec -aes256 -out ./ecc/private.key
Note: The use of a pipe here means the unencrypted key is never written to disk. Since ZFS is a copy-on-write filesystem, that could leave an unencrypted copy available for a through attacker. While this is a serious concern, it can only be exploited by someone with administrator or physical access to your hypervisor. That's beyond the scope of adversary we're protecting against with this specific certificate.
We will also create the RSA key-pair.
# openssl genrsa -aes256 -out ./rsa/private.key 2048
We will now generate some CSRs to pass to our root key.
# openssl req -new -key ecc/private.key -subj "/C=US/O=Cospix LLC/CN=Cospix Branch 2020 ECC"
Do the same with the RSA key-pair
# openssl req -new -key rsa/private.key -subj "/C=US/O=Cospix LLC/CN=Cospix Branch 2020 RSA"
Copy these Certificate Requests (I'm calling mine cospixbranchecc.csr and cospixbranchrsa.csr) and a copy of openssl.cnf to a USB drive you're going to use to transfer CSRs and Certificates between your air-gapped laptop and your branch CA as described in the next section.
Note: While nothing sensitive is being transfered with this drive, we should be mindful that no third-party alters any of the CSRs before they're transfered to your air-gapped laptop, as that could potentially lead to your root key signing an unauthorized third-party key. The easiest way to do this is to visually compare the CSR data between your terminal screens.
If you saved your CSRs into files, you could also run the following commands to compare the public key data. You are specifically concerned about the section titled Subject Public Key Info:
# openssl req -in cospix_branch_ecc.csr -noout -text # openssl req -in cospix_branch_rsa.csr -noout -text
Step 2: Signing with your Root key
All operations involving your root key should be done on an unplugged, fully air-gapped laptop (that means all wireless functionality disabled) booted onto a LiveCD that supports ZFS and with USB drives that are normally kept physically secured that will store your root key-pair. Mine are labeled and normally kept in safe deposit boxes when not in use.
Turn on the air-gapped laptop but do not insert CA drives into the laptop yet. Wait for it to boot up before continuing.
Initialize CA Pool
Note: If you have already initialized your CA pool, you can skip this sub-section.
Insert all your CA drives into your laptop. Determine their identifiers using
rmformat (mine were
c8t0d0.) If OpenIndiana mounted them for you, unmount them before continuing.
Run the following command (with your drive identifiers):
# zpool create -O checksum=edonr -O compression=lz4 -O copies=3 ca \ mirror c5t0d0 c6t0d0 c7t0d0 c8t0d0
While the Edonr checksum is incredibly high performance and quite resistant to attack, it's not cryptographically secure, which is okay, since we're going to be signing a cryptographic manifest file with our private key later on anyways. I prefer explicitly specifying lz4 as my compression algorithm, but you can use 'on' just as easily here. By configuring a four-way mirror with
copies=3, we establish 12 total copies of all of our data, making this a very robust and secure way of storing small volumes of data (Our root CAs shouldn't take much space at all, especially stored on this pool).
Mounting CA Pool
Note: Only follow this section if you skipped the previous section.
ZFS will update all drives in a mirror if it sees any of them out of sync (ie, lagging behind of changes seen in the others). This means that if an attacker compromises one of your USB drives by mounting and changing anything within the filesystem, those compromises will be pushed to ALL of your USB drives.
This becomes a non-issue once ZFS encryption comes to Illumos, but in the mean time we'll employ the following strategy:
If at any point something fails, run
zpool export ca, remove the drive and place it in our 'failed' pile. Skip to the next good drive and return to step 1.
- Insert one of your good USB CA storage drives.
- Import your CA pool using
zpool import ca. It should bring the pool up in a degraded state (since only one vdev will be available).
- Scrub the pool using
zpool scrub ca. This should only take a few moments and verify if there are any innocent data corruption issues. Verify using
zpool status ca.
- The following steps should be done once per organization using this CA pool. Switch directories into each organization's dataset and perform the following actions:
- Verify the fingerprint of the root certificate, comparing with your hand-written copy you should have made before.
- Verify the signature file against the sum file.
- Verify the signature file against the dataset.
Commands are as follows:
# openssl x509 -in ecc/public.crt -noout -fingerprint # openssl x509 -pubkey -in ecc/public.crt > /tmp/public.key # openssl dgst -sha512 -verify /tmp/public.key -signature ca.sig ca.sum # sha512sum -c ca.sum
If you find that all stored root CAs check out, you can safely plug all USB drives back in again. ZFS will resilver any accidental or malicious corruption away.
Mount Transfer Drive
I find that OpenIndiana takes a while, but finally mounts my transfer drive. If OpenIndiana doesn't automatically mount your drive when you plug it in, you'll want to use
rmformat to identify it and the following commands to manually mount it:
# mkdir /media/tmp # mount -F pcfs /dev/dsk/identifier /media/tmp
You may need to try p1 instead of p0.
Initialize Root Certificate Authority
Note: Only follow this section if you need to create a new root CA.
For organization, I prefer configuring a few datasets below the pool root. This lets us easily manage multiple CA records if we need to, (note the structure of organization/certificate_name):
# zfs create -p ca/cospix/root_2040
We can now start creating the required directory structure:
# cd /ca/cospix/root_2040 # chmod 700 . # mkdir -p ecc/certs rsa/certs # touch ecc/index.txt rsa/index.txt # echo 0001 > ecc/serial # echo 0001 > rsa/serial # echo 0001 > ecc/crlnumber # echo 0001 > rsa/crlnumber
Copy the OpenSSL configuration file from the ferry drive into our working directory, we will be changing the following directives (update both instances):
dir = /opt/local/etc/openssl/ecc dir = /opt/local/etc/openssl/rsa unique_subject = no
Will change to:
dir = ./ecc dir = ./rsa unique_subject = yes
The keys we create here should always be encrypted for storage. First, we will always be performing this step with manual intervention. Second, we cannot guarantee that our media won't fall into adversarial hands. This situation is perfect for the two-factor approach of needing to possess the USB drives while also knowing the password to the keys.
Note: ZFS is a copy-on-write filesystem. You should NEVER write an an unencrypted private key to this dataset, because there's no easy guaranteed way to overwrite it. Always use a pipe when generating ECC keys.
The highest performing 384-bit ECDSA curve is
secp384r1, which is the curve we'll use here.
# openssl ecparam -name secp384r1 -genkey | openssl ec -aes256 -out ecc/private.key
We will also create the RSA key-pair.
# openssl genrsa -aes256 -out rsa/private.key 3072
Your root key-pairs will now issue CSRs that will be self-signed via
openssl ca. We will be using
ca instead of
x509 so that we can specify an absolute start date and absolute end date via command line, something we don't have the option to do with the alternative sub-commands.
# openssl req -config openssl.cnf -new -key ecc/private.key -out \ /tmp/cospix_root_ecc.csr -subj "/C=US/O=Cospix LLC/CN=Cospix Global Root 2040 ECC" Enter pass phrase for ecc/private.key:
And now run that csr through the
openssl ca command.
# openssl ca -config openssl.cnf -name ecc -selfsign -notext -extensions v3ext_root \ -in /tmp/cospix_root_ecc.csr -out ecc/public.crt \ -startdate 20160101000000Z -enddate 20400101000000Z Using configuration from openssl.cnf Enter pass phrase for ./ecc/private.key: Check that the request matches the signature Signature ok Certificate Details: Serial Number: 4096 (0x1000) Validity Not Before: Jan 1 00:00:00 2016 GMT Not After : Jan 1 00:00:00 2040 GMT Subject: countryName = US organizationName = Cospix LLC commonName = Cospix Global Root 2040 ECC X509v3 extensions: X509v3 Subject Key Identifier: E4:0A:1A:5B:64:5F:DF:26:EA:F3:C8:DD:E1:86:28:2E:C0:90:FC:3E X509v3 Authority Key Identifier: keyid:E4:0A:1A:5B:64:5F:DF:26:EA:F3:C8:DD:E1:86:28:2E:C0:90:FC:3E X509v3 Basic Constraints: critical CA:TRUE X509v3 Key Usage: critical Digital Signature, Certificate Sign, CRL Sign Certificate is to be certified until Jan 1 00:00:00 2040 GMT (8627 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
Perform the same set of actions for our RSA key-pair.
# openssl req -config openssl.cnf -new -key rsa/private.key -out \ /tmp/cospix_root_rsa.csr -subj "/C=US/O=Cospix LLC/CN=Cospix Global Root 2040 RSA" Enter pass phrase for rsa/private.key:
# openssl ca -config openssl.cnf -name rsa -selfsign -notext -extensions v3ext_root \ -in /tmp/cospix_root_rsa.csr -out rsa/public.crt \ -startdate 20160101000000Z -enddate 20400101000000Z
At this point, you should have public certificates in
rsa/public.crt. Copy both of these files to your transfer drive (as
cospix_root_rsa.crt respectively). They will serve as your global trust anchors for your entire Certificate Authority.
Record (by hand) the certificate fingerprints for verification purposes:
# openssl x509 -in ecc/public.crt -noout -fingerprint SHA1 Fingerprint=E4:0A:1A:5B:64:5F:DF:26:EA:F3:C8:DD:E1:86:28:2E:C0:90:FC:3E # openssl x509 -in rsa/public.crt -noout -fingerprint SHA1 Fingerprint=F5:D7:69:DF:EE:1F:99:AF:EE:DC:2E:EA:EC:FC:2F:BC:3D:82:E7:C4
It's good to maintain paper records of this number, ensuring that nobody has snuck a forged root certificate into anywhere it doesn't belong (that'd be anywhere).
Issue Branch Certificate
First, you should confirm that the public key info of the CSRs as read from your transfer drive on your air-gapped laptop match those of your branch certificate environment. Run the following commands and compare their output to your branch certificate terminal.
# openssl req -noout -text -in /media/Drive/cospix_branch_ecc.csr # openssl req -noout -text -in /media/Drive/cospix_branch_rsa.csr
Assuming they match, issue the following command to read the CSR directly from the USB transfer drive, and issue a certificate right back onto it.
# openssl ca -config openssl.cnf -name ecc -notext -extensions v3ext_branch \ -in /media/tmp/cospix_branch_ecc.csr -out /media/tmp/cospix_branch_ecc.crt \ -startdate 20160101000000Z -enddate 20200101000000Z Using configuration from openssl.cnf Enter pass phrase for ./ecc/private.key: Check that the request matches the signature Signature ok Certificate Details: Serial Number: 4097 (0x1001) Validity Not Before: Jan 1 00:00:00 2016 GMT Not After : Jan 1 00:00:00 2020 GMT Subject: countryName = US organizationName = Cospix LLC commonName = Cospix Global Branch 2020 ECC X509v3 extensions: X509v3 Subject Key Identifier: 44:8B:3D:DD:65:B3:31:EB:77:D9:80:DE:AF:D8:B7:54:0A:5B:AA:2C X509v3 Authority Key Identifier: keyid:E4:0A:1A:5B:64:5F:DF:26:EA:F3:C8:DD:E1:86:28:2E:C0:90:FC:3E X509v3 Basic Constraints: critical CA:TRUE, pathlen:0 X509v3 Key Usage: critical Digital Signature, Certificate Sign, CRL Sign Certificate is to be certified until Jan 1 00:00:00 2020 GMT (1322 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
And do the same for the RSA certificate.
# openssl ca -config openssl.cnf -name rsa -notext -extensions v3ext_branch \ -in /media/tmp/cospix_branch_rsa.csr -out /media/tmp/cospix_branch_rsa.crt \ -startdate 20160101000000Z -enddate 20200101000000Z
If all of your work on this Certificate Authority root is complete, generate or update the
ca.sum file. This file will allow you to verify file integrity when next working on these files.
# sha512sum openssl.cnf */* */*/* > ca.sum
Next, sign it with the root private key to ensure that none of the CA files are either accidentally or maliciously compromised:
# openssl dgst -sha512 -sign ecc/private.key -out ca.sig ca.sum
Finally, snapshot the dataset with the current date.
# zfs snapshot ca/cospix/root_2040@r20160226
If you are done using your USB drives, be sure to zfs scrub them and ensure it completes successfully before exporting the ca pool.
# zfs scrub ca # zfs status ca pool: ca state: ONLINE scan: scrub repaired 0 in 0h0m with 0 errors on ... # zfs export ca
You may now shutdown your laptop and physically secure your storage media.
Installing Branch Certificates
cospix_branch_ecc.crt file on your USB drive to
~/openssl/ecc/public.crt on your CA zone and the
cospix_branch_rsa.crt file on your USB drive to
~/openssl/rsa/public.crt on your CA zone. You can additionally copy the root certificates as well, as
Use the following command to verify your certificate chain:
# openssl verify -CAfile ecc/root.crt ecc/public.crt ecc/public.crt: OK # openssl verify -CAfile rsa/root.crt rsa/public.crt rsa/public.crt: OK
We can also fingerprint our root certificates to ensure they're match the fingerprint we wrote down earlier:
# openssl x509 -in ecc/root.crt -noout -fingerprint SHA1 Fingerprint=E4:0A:1A:5B:64:5F:DF:26:EA:F3:C8:DD:E1:86:28:2E:C0:90:FC:3E # openssl x509 -in rsa/root.crt -noout -fingerprint SHA1 Fingerprint=F5:D7:69:DF:EE:1F:99:AF:EE:DC:2E:EA:EC:FC:2F:BC:3D:82:E7:C4
Step 3: Fulfilling leaf CSRs
I'm going to assume that you have used my previous guide to produce a CSR in the path ~/csr.pem.
First we should read the CSR to see if there are any requested extensions:
# openssl req -text -noout -in ./csr.pem
OpenSSL will not transfer Requested Extensions between Certificate Signing Requests and Certificates, so we will have to manually establish our own. If you'd like to declare extensions, I find that the
-extfile parameter is the easiest way to do this with OpenSSL. While this does mean you will need to setup an extension file for every certificate (or certificate pair) you sign, it's a whole lot easier to follow than alternatives I've seen.
basicConstraints = critical, CA:false keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, keyAgreement extendedKeyUsage = serverAuth, timeStamping subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always, issuer:always subjectAltName = DNS:one.example.net,DNS:two.example.net
Copy this template to another filename and edit it for your needs.
Notice: As of summer 2017, using the Common Name in the subject to identify a server resource is now depreciated. You will now always want to specify one or more server hostnames in subjectAltName.
To use, run the following command:
# openssl ca -notext -in csr.pem -out cert.pem -extfile filename.ext
This command will (optionally) ask you for a password to decrypt the branch private key, and prompt you to actually sign and file the certificate.
If there are no extensions, I've also included some generic extensions in the above linked openssl.cnf file:
v3ext_user: Useful for user or email authentication
v3ext_server: Useful for server authentication
To sign a server certificate (placed into cert.pem), run the following command:
# openssl ca -notext -in csr.pem -out cert.pem -extensions v3ext_server
If you want to issue an RSA certificate, include
-name rsa in the above commands.
You will need to include the public branch certificate along with the newly issued certificate when returning them to the requesting party. They can be concatenated together.
$ cat ~/openssl/(ecc or rsa)/public.crt >> cert.pem