Создание сертификатов при помощи OpenSSL

Создание сертификатов при помощи OpenSSL

#OpenSSL является библиотекой по работе с криптографией, но в этой статье я затрону только часть функций этого инструмента, а именно создание само-подписанных сертификатов.

Создание центра сертификации

При помощи корневого сертификата, мы будем подписывать сертификаты клиентские. Создаём сертификат центра сертификации…

openssl ecparam -genkey -name 'secp384r1' | openssl ec -aes256 -out "_CA.key" && openssl req -new -sha384 -key "_CA.key" -out "_CA.csr" && openssl x509 -req -sha384 -days 3650 -key "_CA.key" -in "_CA.csr" -out "_CA.crt"

Где:

  • -days '3650' - количество дней, по прошествии которого сертификат центра сертификации станет недействительным.
  • '_CA.key' - название создаваемого файла с ключом центра сертификации.
  • '_CA.crt' - название создаваемого файла с сертификатом центра сертификации.

Создание клиентского сертификата

Как только будет готов корневой сертификат, можно приступать к выпуску клиентских сертификатов.

Создание приватного ключа

Для начала создаём приватный ключ.

openssl ecparam -genkey -name 'prime256v1' | openssl ec -out 'client.key'

Где:

  • -out 'client.key' - название создаваемого файла с клиентским приватным ключом.

Создание запроса на подпись сертификата

Выполняем запрос на сертификат.

openssl req -new -key 'client.key' -out 'client.csr'

Где:

  • -key 'client.key' - файл с приватным ключом клиентского сертификата.
  • -out 'client.csr' - название создаваемого файла с запросом на подпись клиентского сертификата.

При выполнении запроса будет предложено ввести актуальные данные для будущего сертификата:

  • Country Name (2 letter code) - двухбуквенный код страны, в которой юридически находится ваша организация.
  • State or Province Name (full name) - штат или провинция, в которой юридически находится ваша организация.
  • Locality Name (e.g., city) - город, в котором юридически находится ваша организация.
  • Organization Name (e.g., company) - юридически зарегистрированное название вашей организации.
  • Organizational Unit Name (e.g., section) - название вашего отдела в организации (опционально).
  • Common Name (e.g., server FQDN) - полное доменное имя (FQDN) (например, www.example.com).
  • Email Address - ваш адрес email (опционально).
  • A challenge password - пароль (опционально).
  • An optional company name - необязательное название компании (опционально).

Самое главное поле это Common Name (e.g., server FQDN) (CN), заполнять его необходимо очень внимательно.

Создание и подпись сертификата

В заключительной части остаётся только создать сам сертификат и подписать его.

openssl x509 -req -days 3650 -in 'client.csr' -CA '_CA.crt' -CAkey '_CA.key' -out 'client.crt'

Где:

  • -in 'client.csr' - файл с запросом клиентского сертификата.
  • -CA '_CA.crt' - файл с сертификатом центра сертификации.
  • -CAkey '_CA.key' - файл с ключом центра сертификации.
  • -days '3650' - количество дней, по прошествии которого клиентский сертификат станет недействительным.
  • -out 'client.crt' - название создаваемого файла с клиентским сертификатом.

Экспорт сертификата

Для того, чтобы импортировать сертификат на клиентские устройства, его необходимо экспортировать в формат P12. P12 является контейнером, в котором содержится приватный ключ сертификата и сам сертификат.

openssl pkcs12 -export -inkey 'client.key' -in 'client.crt' -out 'client.p12'

Где:

  • -inkey 'client.key' - файл с приватным клиентским ключом.
  • -in 'client.crt' - файл с клиентским сертификатом.
  • -out 'client.p12' - название создаваемого файла-контейнера с приватным ключом и сертификатом.

Верификация сертификата

openssl verify -CAfile '_CA.crt' 'client.crt'
  • -CAfile '_CA.crt' - файл с сертификатом центра сертификации.
  • 'client.crt' - файл с клиентским сертификатом.

Просмотр сертификата

openssl x509 -in 'client.crt' -text

Где:

  • -in 'client.crt' - файл с клиентским сертификатом.

При выполнении команды, терминал покажет информацию со всеми основными сведениями о сертификате (пример ниже).

Терминал
openssl x509 -in 'client.crt' -text

Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            49:14:b8:78:17:a1:ca:c4:6a:41:1b:23:f3:8a:8d:36:e0:9e:69:81
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = CA
        Validity
            Not Before: Oct 18 21:31:53 2023 GMT
            Not After : Oct 15 21:31:53 2033 GMT
        Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = CN-FQDN, emailAddress = mail@example.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:9f:22:91:cc:14:b3:75:ad:44:cc:0d:96:0d:19:
                    04:c4:20:79:fd:b7:f5:65:64:dd:65:a1:f0:b8:2d:
                    e4:02:75:62:41:03:7c:6e:2f:48:e4:11:38:dc:ed:
                    08:1f:4c:fa:5f:2f:a2:f5:5d:51:2e:8e:de:de:70:
                    33:aa:e5:3b:95
                ASN1 OID: prime256v1
                NIST CURVE: P-256
    Signature Algorithm: ecdsa-with-SHA256
         30:64:02:30:0d:ff:b1:dc:8b:a6:c6:9c:ad:65:8a:7c:01:41:
         9f:91:ca:24:4c:0b:28:5a:5c:f6:35:2a:b2:d7:58:ca:39:da:
         6c:bf:cf:8b:23:20:ce:11:45:13:61:36:e2:23:4a:e9:02:30:
         54:dc:45:ed:ef:21:58:ea:c7:b6:63:db:a2:d9:71:fe:3d:b3:
         d6:1e:15:82:7b:c8:e8:08:33:d5:2f:d5:f2:8f:3b:41:ea:53:
         1e:2d:a9:1e:9e:25:9c:fb:a7:12:f9:ec

Просмотр запроса на подпись сертификата

openssl req -in 'client.csr' -text

Где:

  • -in 'client.csr' - файл с запросом на подпись клиентского сертификата.

Команда выдаст информацию по запросу на подпись клиентского сертификата (пример ниже).

Терминал
openssl req -in 'client.csr' -text

Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = CN-FQDN, emailAddress = mail@example.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:9f:22:91:cc:14:b3:75:ad:44:cc:0d:96:0d:19:
                    04:c4:20:79:fd:b7:f5:65:64:dd:65:a1:f0:b8:2d:
                    e4:02:75:62:41:03:7c:6e:2f:48:e4:11:38:dc:ed:
                    08:1f:4c:fa:5f:2f:a2:f5:5d:51:2e:8e:de:de:70:
                    33:aa:e5:3b:95
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        Attributes:
            a0:00
        Requested Extensions:
    Signature Algorithm: ecdsa-with-SHA256
         30:45:02:20:51:9c:a0:b9:b2:61:7f:fa:64:ea:34:1f:65:15:
         37:ae:f4:89:30:47:11:89:db:c4:1b:1d:9b:82:b9:64:ea:c7:
         02:21:00:f8:00:70:4e:e6:db:99:78:cf:25:22:c0:8d:c6:b1:
         f3:f0:d8:68:3b:c2:51:21:a5:b3:fa:97:f9:85:c6:9c:49

Автоматизация

Команд много. Поэтому я как обычно, решил всё автоматизировать и загнать в скрипт.

bash.openssl.ca.sh
#!/usr/bin/env -S bash -e
#
# OpenSSL certificate generator with CA.
#
# @package    Bash
# @author     Kai Kimera <mail@kai.kim>
# @copyright  2023 Library Online
# @license    MIT
# @version    0.1.2
# @link       https://lib.onl/ru/articles/2023/10/6733cb51-62a0-5ed9-b421-8f08c4e0cb18/
# -------------------------------------------------------------------------------------------------------------------- #

(( EUID == 0 )) && { echo >&2 'This script should not be run as root!'; exit 1; }

# -------------------------------------------------------------------------------------------------------------------- #
# CONFIGURATION.
# -------------------------------------------------------------------------------------------------------------------- #

# Get 'cat' command.
cat="$( command -v cat )"

# Get 'date' command.
date="$( command -v date )"

# Get 'mkdir' command.
mkdir="$( command -v mkdir )"

# Get 'openssl' command.
ossl="$( command -v openssl )"

# Get 'shuf' command.
shuf="$( command -v shuf )"

# CA file names.
ca='_CA'

# Specifies the number of days to make a certificate valid for.
# Default is 10 years.
days='3650'

# The two-letter country code where your company is legally located.
country='RU'

# The state/province where your company is legally located.
state='Russia'

# The city where your company is legally located.
city='Moscow'

# Your company's legally registered name (e.g., YourCompany, Inc.).
org='YourCompany'

# Timestamp.
ts="$( ${date} -u '+%s' )"

# Suffix.
sfx=$( ${shuf} -i '1000-9999' -n 1 --random-source='/dev/random' )

# -------------------------------------------------------------------------------------------------------------------- #
# -----------------------------------------------------< SCRIPT >----------------------------------------------------- #
# -------------------------------------------------------------------------------------------------------------------- #

# -------------------------------------------------------------------------------------------------------------------- #
# CERTIFICATE AUTHORITY.
# -------------------------------------------------------------------------------------------------------------------- #

ca() {
  ! [[ -x "${ossl}" ]] && { echo >&2 "'openssl' is not installed!"; exit 1; }

  local name="${1:-example.com}"
  local email="${2:-mail@example.com}"
  local v3ext_ca='_CA.v3ext'

  cat > "${v3ext_ca}" <<EOF
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:TRUE
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
EOF

  echo '' && echo "--- [SSL-CA] CREATING A CA CERTIFICATE" && echo ''
  ${ossl} ecparam -genkey -name 'secp384r1' | ${ossl} ec -aes256 -out "${ca}.key" \
    && ${ossl} req -new -sha384 -key "${ca}.key" -out "${ca}.csr" \
    -subj "/C=${country}/ST=${state}/L=${city}/O=${org}/emailAddress=${email}/CN=${name}" \
    && ${ossl} x509 -req -extfile "${v3ext_ca}" -sha384 -days ${days} -key "${ca}.key" -in "${ca}.csr" -out "${ca}.crt"

    _info "${ca}.crt"
}

# -------------------------------------------------------------------------------------------------------------------- #
# CLIENT CERTIFICATE.
# -------------------------------------------------------------------------------------------------------------------- #

cert() {
  for i in "${mkdir}" "${ossl}" "${shuf}"; do
    ! [[ -x "${i}" ]] && { echo >&2 "'${i}' is not installed!"; exit 1; }
  done

  local name="${1:-example.com}"
  local email="${2:-mail@example.com}"
  local type="${3:-server}"; [[ "${name}" = *' '* ]] && type='client'
  local dir="${type}/${name// /_}"; ${mkdir} -p "${dir}"
  local file="${dir}/${email}.${ts}.${sfx}"

  local v3ext
  if [[ "${type}" = 'client' ]]; then
    v3ext=$( _v3ext_client "${file}.v3ext" )
  else
    v3ext=$( _v3ext_server "${file}.v3ext" "${name}" )
  fi

  echo '' && echo "--- [SSL] CREATING A ${type^^} CERTIFICATE" && echo ''
  ${ossl} ecparam -genkey -name 'prime256v1' | ${ossl} ec -out "${file}.key" \
    && ${ossl} req -new -key "${file}.key" -out "${file}.csr" \
    -subj "/C=${country}/ST=${state}/L=${city}/O=${org}/emailAddress=${email}/CN=${name}" \
    && ${ossl} x509 -req -extfile "${v3ext}" -days ${days} -in "${file}.csr" \
    -CA "${ca}.crt" -CAkey "${ca}.key" -CAcreateserial -CAserial "${file}.srl" -out "${file}.crt" \
    && ${cat} "${file}.key" "${file}.crt" "${ca}.crt" > "${file}.crt.chain"

  _verify "${ca}.crt" "${file}.crt" && _info "${file}.crt" && _export "${file}.key" "${file}.crt" "${file}.p12"
}

_verify() {
  echo '' && echo "--- [SSL] CERTIFICATE VERIFICATION" && echo ''
  for i in "${1}" "${2}"; do [[ ! -f "${i}" ]] && { echo >&2 "'${i}' not found!"; exit 1; }; done
  ${ossl} verify -CAfile "${1}" "${2}"
}

_info() {
  echo '' && echo "--- [SSL] CERTIFICATE DETAILS" && echo ''
  [[ ! -f "${1}" ]] && { echo >&2 "'${i}' not found!"; exit 1; }
  ${ossl} x509 -in "${1}" -text -noout
}

_export() {
  echo '' && echo "--- [SSL] EXPORTING A CERTIFICATE" && echo ''
  for i in "${1}" "${2}"; do [[ ! -f "${i}" ]] && { echo >&2 "'${i}' not found!"; exit 1; }; done
  ${ossl} pkcs12 -export -inkey "${1}" -in "${2}" -out "${3}"
}

_v3ext_client() {
  cat > "${1}" <<EOF
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
extendedKeyUsage = clientAuth, emailProtection
keyUsage = critical, digitalSignature, keyEncipherment, nonRepudiation
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
EOF
  echo -n "${1}"
}

_v3ext_server() {
  cat > "${1}" <<EOF
authorityKeyIdentifier = keyid,issuer:always
basicConstraints = CA:FALSE
extendedKeyUsage = serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectAltName = @alt_names

[alt_names]
DNS.1 = ${2}
DNS.2 = *.${2}
EOF
  echo -n "${1}"
}

# -------------------------------------------------------------------------------------------------------------------- #
# -------------------------------------------------< RUNNING SCRIPT >------------------------------------------------- #
# -------------------------------------------------------------------------------------------------------------------- #

"$@"

Скрипт содержит две функции, вызывать которых можно по отдельности. Функция ca() позволяет сгенерировать ключ и сертификат центра сертификации, а функция cert(), соответственно, генерирует клиентские сертификаты.

Использование

  1. Создать ключ и сертификат центра сертификации:
Терминал
bash bash.openssl.ca.sh ca
  1. Создать клиентские ключи и сертификаты:
Терминал
bash bash.openssl.ca.sh cert

Немного расскажу про алгоритм генерации сертификатов.

  1. У сертификата есть поле Serial Number, которое содержит серийный номер сертификата. Серийный номер сертификата это уникальный номер, выданный центром сертификации. В моём скрипте при генерации клиентского сертификата указаны опции -CAcreateserial и -CAserial "${srl}", которые позволяют OpenSSL создавать рядом с клиентскими сертификатами файлы формата .slr, содержащие в себе серийные номера сгенерированных сертификатов. Таким образом можно создавать небольшую локальную базу данных выданных сертификатов.
  2. Файл каждого клиентского сертификата генерируется с названием вида [TS].[SFX].[EXE], где [TS] - это временная метка в микросекундах, [SFX] - числовой суффикс, а [EXE] - расширение файлов.

На этом всё. Не исключаю факта присутствия ошибок или неточностей. Если что, жду предложения в электронную почту. 😄

Быстрое создание само-подписанного сертификата

Для быстрого создания само-подписанного сертификата, я написал пару небольших команд.

ECC (Elliptic Curve Cryptography)

ossl_days='3650'; ossl_country='RU'; ossl_state='Russia'; ossl_city='Moscow'; ossl_org='RiK'; ossl_host='example.com'; openssl ecparam -genkey -name 'prime256v1' -out "${ossl_host}.key" && openssl req -new -sha256 -key "${ossl_host}.key" -out "${ossl_host}.csr" -subj "/C=${ossl_country}/ST=${ossl_state}/L=${ossl_city}/O=${ossl_org}/CN=${ossl_host}" -addext "subjectAltName=DNS:${ossl_host},DNS:*.${ossl_host}" && openssl req -x509 -sha256 -days ${ossl_days} -key "${ossl_host}.key" -in "${ossl_host}.csr" -out "${ossl_host}.crt" && openssl x509 -in "${ossl_host}.crt" -text -noout

Где:

  • ossl_days - количество дней валидности сертификата.
  • ossl_country - страна.
  • ossl_state - штат.
  • ossl_city - город.
  • ossl_org - организация.
  • ossl_host - хост.

RSA (Rivest-Shamir-Adleman)

ossl_days='3650'; ossl_country='RU'; ossl_state='Russia'; ossl_city='Moscow'; ossl_org='RiK'; ossl_host='example.com'; openssl genrsa -out "${ossl_host}.key" 2048 && openssl req -new -sha256 -key "${ossl_host}.key" -out "${ossl_host}.csr" -subj "/C=${ossl_country}/ST=${ossl_state}/L=${ossl_city}/O=${ossl_org}/CN=${ossl_host}" -addext "subjectAltName=DNS:${ossl_host},DNS:*.${ossl_host}" && openssl req -x509 -sha256 -days ${ossl_days} -key "${ossl_host}.key" -in "${ossl_host}.csr" -out "${ossl_host}.crt" && openssl x509 -in "${ossl_host}.crt" -text -noout

Где:

  • ossl_days - количество дней валидности сертификата.
  • ossl_country - страна.
  • ossl_state - штат.
  • ossl_city - город.
  • ossl_org - организация.
  • ossl_host - хост.

Скрипт

bash.openssl.ssc.sh
#!/usr/bin/env -S bash -e
#
# OpenSSL self signed certificate generator.
#
# @package    Bash
# @author     Kai Kimera <mail@kai.kim>
# @copyright  2024 Library Online
# @license    MIT
# @version    0.1.0
# @link       https://lib.onl/ru/articles/2023/10/6733cb51-62a0-5ed9-b421-8f08c4e0cb18/
# -------------------------------------------------------------------------------------------------------------------- #

(( EUID == 0 )) && { echo >&2 'This script should not be run as root!'; exit 1; }

# -------------------------------------------------------------------------------------------------------------------- #
# CONFIGURATION.
# -------------------------------------------------------------------------------------------------------------------- #

# Get 'openssl' command.
ossl="$( command -v openssl )"

# Specifies the number of days to make a certificate valid for.
# Default is 10 years.
days='3650'

# The two-letter country code where your company is legally located.
country='RU'

# The state/province where your company is legally located.
state='Russia'

# The city where your company is legally located.
city='Moscow'

# Your company's legally registered name (e.g., YourCompany, Inc.).
org='YourCompany'

# The fully-qualified domain name (FQDN) (e.g., www.example.com).
host="${1:-example.com}"

# Your email address.
email="mail@${host}"

# -------------------------------------------------------------------------------------------------------------------- #
# -----------------------------------------------------< SCRIPT >----------------------------------------------------- #
# -------------------------------------------------------------------------------------------------------------------- #

! [[ -x "${ossl}" ]] && { echo >&2 "'openssl' is not installed!"; exit 1; }

echo "" && echo "--- [SSL] Self Signed Certificate: '${host}'" && echo ""

if [[ "${2}" = 'rsa' ]]; then
  ${ossl} genrsa -out "${host}.key" 2048
else
  ${ossl} ecparam -genkey -name 'prime256v1' -out "${host}.key"
fi

if [[ -f "${host}.key" ]]; then
  ${ossl} req -new -sha256 -key "${host}.key" -out "${host}.csr" \
    -subj "/C=${country}/ST=${state}/L=${city}/O=${org}/emailAddress=${email}/CN=${host}" \
    -addext "subjectAltName=DNS:${host},DNS:*.${host}" \
    && ${ossl} req -x509 -sha256 -days ${days} \
      -key "${host}.key" -in "${host}.csr" -out "${host}.crt" \
    && ${ossl} x509 -in "${host}.crt" -text -noout
else
  echo "'${host}.key' not found!" && exit 1
fi

exit 0

2023-11-08

  • bash.openssh.ca.sh: добавление проверки на запуск скрипта под root’ом.
  • bash.openssh.ca.sh: добавление комментариев.

2024-04-07

  • Добавлены команды для быстрой генерации сертификата без создания CA.
  • Добавлен скрипт для быстрой генерации сертификата без создания CA.
Авторы
Мета
Лицензия
ID файла
UUID
Системный путь
Тип
Статистика
Количество слов
Время чтения
мин.