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

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

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

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

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

1
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' - название создаваемого файла с сертификатом центра сертификации.

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

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

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

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

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

Где:

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

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

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

1
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), заполнять его необходимо очень внимательно.

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

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

1
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 является контейнером, в котором содержится приватный ключ сертификата и сам сертификат.

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

Где:

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

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

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

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

1
openssl x509 -in 'client.crt' -text

Где:

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

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

Терминал
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

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

1
openssl req -in 'client.csr' -text

Где:

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

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

Терминал
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!/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. Создать ключ и сертификат центра сертификации:
Терминал
1
bash bash.openssl.ca.sh ca
  1. Создать клиентские ключи и сертификаты:
Терминал
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)

1
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)

1
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/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
Системный путь
Тип
Статистика
Количество слов
Время чтения
мин.