Те, кто в своей инфраструктуре использует Microsoft CA, могли часто встречаться с проблемой выпуска сертификатов для linux машин\cервисов.

Все время приходилось вручную генерировать сертификат с помощью openssl, а далее идти в веб интерфейс MS CA,запрашивать, потом скачивать сертификат и заливать его на машину.

К счастью, нашелся модуль python, который облегачет получение сертификатов в Linux- certsrv

Документация к модулю

Пример получения сертификата

При установке модуля по умолчанию используется basic-аутентификация, мы будем рассматривать аутентификацию с помощью NTLM, поэтому установим модуль с добавлением [ntlm] к названию.

Установим модуль certsrv c поддержкой NTLM

1pip install certsrv[ntlm]

С certsrv должен установиться модуль requests_ntlm, если этого не произошло - установите его вручную и повторите установку certsrv.

В документации есть пример генерации сертификата c использованием шаблона WebServer,попробуем запустить его, предварительно установив модуль cryptography :

Установка модуля cryptography:

1pip install cryptography

Создадим файл скрипта для генерации:

 1from certsrv import Certsrv
 2
 3from cryptography.hazmat.backends import default_backend
 4from cryptography.hazmat.primitives import serialization
 5from cryptography.hazmat.primitives.asymmetric import rsa
 6from cryptography import x509
 7from cryptography.x509.oid import NameOID
 8from cryptography.hazmat.primitives import hashes
 9
10# Generate a key
11key = rsa.generate_private_key(
12    public_exponent=65537,
13    key_size=2048,
14    backend=default_backend()
15)
16
17# Generate a CSR
18csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
19    x509.NameAttribute(NameOID.COMMON_NAME, u"{FQDN}"),
20])).add_extension(
21    x509.SubjectAlternativeName([
22        x509.DNSName(u"{FQDN}"),
23    ]),
24    critical=False,
25).sign(key, hashes.SHA256(), default_backend())
26
27# Get the cert from the ADCS server
28pem_req = csr.public_bytes(serialization.Encoding.PEM)
29
30ca_server = Certsrv("{MS_CA_URL}", "{USER}", "{PASSWORD}")
31pem_cert = ca_server.get_cert(pem_req, "{CERT_TYPE}")
32
33# Print the key and the cert
34pem_key = key.private_bytes(
35            encoding=serialization.Encoding.PEM,
36            format=serialization.PrivateFormat.TraditionalOpenSSL,
37            encryption_algorithm=serialization.NoEncryption(),
38)
39
40print("Cert:\n{}".format(pem_cert.decode()))
41print("Key:\n{}".format(pem_key.decode()))

Не забываем указать:

  • {CERT_TYPE} - шаблон сертификата[^1] ;

  • {MS_CA_URL} - ссылка на страницу получения сертификатов MS CA;

  • {USER} - доменный пользователь с правами выдачи сертификатов;

  • {PASSWORD} - пароль пользоваетеля;

  • {FQDN} - полное доменное имя сайта\сервера;

После запуска скрипта он должен выдать в STDOUT содержимое файлов сертификата и ключа. Далее их нужно скопировать и создать соответсвтующие файлы:

  • Файл сертификата с расширением pem;
  • Файл ключа с расширением key;

Известные проблемы


ValueError: unsupported hash type md4

При использовании NTLM-аутентификации может встретиться ошибка:

1ValueError: unsupported hash type md4

Решение: Добавить в /usr/lib/openssl.cnf следующие строки:

1[provider_sect]
2default = default_sect
3legacy = legacy_sect
4
5[default_sect]
6activate = 1
7
8[legacy_sect]
9activate = 1

Примечания:

Пример наименования некоторых стандартных шаблонов для выпуска сертификатов

  • WebServerfor10years
  • WebServer - (на 1 год)
  • User
  • EFS
  • Administrator
  • EFSRecovery
  • CodeSigning
  • CodeSignin10years
  • SubCA

Дополнительные аттрибуты для запроса сертификата

При формировании запроса можно добавить дополнительные атрибуты (подробнее в документации модуля cryptography), например:

 1csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
 2    x509.NameAttribute(NameOID.COMMON_NAME, 0xbbeer.ru),
 3    x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
 4    x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"Alabama"),
 5    x509.NameAttribute(NameOID.LOCALITY_NAME, u"SweetHomeAlabama"),
 6    x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"SuperORG"),
 7])).add_extension(
 8    x509.SubjectAlternativeName([
 9        x509.DNSName('0xbbeer.ru'),
10        x509.IPAddress('192.168.0.1'),
11    ]),
12    critical=False,
13).sign(key, hashes.SHA256(), default_backend())