class OpenSSL::X509::Certificate
Реализация сертификата X.509 в соответствии со спецификацией RFC 5280. Предоставляет доступ к атрибутам сертификата и позволяет считывать сертификаты из строки, а также создавать новые сертификаты с нуля.
Чтение сертификата из файла
Certificate умеет обрабатывать сертификаты в кодировке DER и сертификаты в формате PEM OpenSSL.
raw = File.binread "cert.cer" # DER- or PEM-encoded certificate = OpenSSL::X509::Certificate.new raw
Сохранение сертификата в файл
Сертификат можно закодировать в формате DER
cert = ...
File.open("cert.cer", "wb") { |f| f.print cert.to_der }
или в формате PEM
cert = ...
File.open("cert.pem", "wb") { |f| f.print cert.to_pem }
Сертификаты X.509 связаны с парой закрытого и открытого ключей, обычно ключом RSA, DSA или ECC (см. также OpenSSL::PKey::RSA, OpenSSL::PKey::DSA и OpenSSL::PKey::EC); сам открытый ключ хранится в сертификате и доступен в виде OpenSSL::PKey. Сертификаты обычно используются для связывания некоторой формы идентичности с парой ключей. Например, веб-серверы, обслуживающие страницы по HTTPS, используют сертификаты для подтверждения своей подлинности пользователю.
Модель инфраструктуры открытых ключей (PKI) опирается на доверенные центры сертификации («корневые центры сертификации»), выдающие эти сертификаты. Благодаря этому конечным пользователям достаточно доверять лишь нескольким выбранным центрам, которые, в свою очередь, поручаются за подчинённые центры сертификации, выдающие сертификаты конечным пользователям.
Модуль OpenSSL::X509 предоставляет инструменты для настройки независимой PKI, например в случаях, когда для выдачи сертификатов в частной PKI используется интерфейс командной строки «openssl».
Создание сертификата корневого центра сертификации и сертификата конечного субъекта
Сначала нужно создать самоподписанный корневой сертификат. Для этого сперва необходимо сгенерировать ключ. Обратите внимание: использование «1» в качестве серийного номера считается уязвимостью для реальных сертификатов. Безопасные варианты — целые числа в диапазоне двухбайтовых значений, в идеале не последовательные, а случайные криптографически стойкие числа. Эти шаги опущены здесь для краткости примера.
root_key = OpenSSL::PKey::RSA.new 2048 # the CA's public/private key
root_ca = OpenSSL::X509::Certificate.new
root_ca.version = 2 # cf. RFC 5280 - to make it a "v3" certificate
root_ca.serial = 1
root_ca.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby CA"
root_ca.issuer = root_ca.subject # root CA's are "self-signed"
root_ca.public_key = root_key.public_key
root_ca.not_before = Time.now
root_ca.not_after = root_ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = root_ca
ef.issuer_certificate = root_ca
root_ca.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true))
root_ca.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true))
root_ca.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
root_ca.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false))
root_ca.sign(root_key, OpenSSL::Digest.new('SHA256'))
Следующий шаг — создать сертификат конечного субъекта с помощью сертификата корневого центра сертификации.
key = OpenSSL::PKey::RSA.new 2048
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 2
cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby certificate"
cert.issuer = root_ca.subject # root CA is the issuer
cert.public_key = key.public_key
cert.not_before = Time.now
cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60 # 1 years validity
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = root_ca
cert.add_extension(ef.create_extension("keyUsage","digitalSignature", true))
cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
cert.sign(root_key, OpenSSL::Digest.new('SHA256'))
Публичные методы класса
static VALUE
ossl_x509_load(VALUE klass, VALUE buffer)
{
BIO *in = ossl_obj2bio(&buffer);
return rb_ensure(load_chained_certificates, (VALUE)in, load_chained_certificates_ensure, (VALUE)in);
} Читает цепочку сертификатов из заданных входных данных. Поддерживаются сертификаты в кодировках PEM и DER.
PEM — текстовый формат, поддерживающий несколько сертификатов.
DER — двоичный формат, поддерживающий только один сертификат.
Если файл пуст или содержит только несвязанные данные, будет вызвано исключение OpenSSL::X509::CertificateError.
# File ext/openssl/lib/openssl/x509.rb, line 369 def self.load_file(path) load(File.binread(path)) end
static VALUE
ossl_x509_initialize(int argc, VALUE *argv, VALUE self)
{
BIO *in;
X509 *x509, *x509_orig = RTYPEDDATA_DATA(self);
VALUE arg;
rb_check_frozen(self);
if (rb_scan_args(argc, argv, "01", &arg) == 0) {
/* create just empty X509Cert */
return self;
}
arg = ossl_to_der_if_possible(arg);
in = ossl_obj2bio(&arg);
x509 = d2i_X509_bio(in, NULL);
if (!x509) {
OSSL_BIO_reset(in);
x509 = PEM_read_bio_X509(in, NULL, NULL, NULL);
}
BIO_free(in);
if (!x509)
ossl_raise(eX509CertError, "PEM_read_bio_X509");
RTYPEDDATA_DATA(self) = x509;
X509_free(x509_orig);
return self;
} Публичные методы экземпляра
static VALUE
ossl_x509_eq(VALUE self, VALUE other)
{
X509 *a, *b;
GetX509(self, a);
if (!rb_obj_is_kind_of(other, cX509Cert))
return Qfalse;
GetX509(other, b);
return !X509_cmp(a, b) ? Qtrue : Qfalse;
} Сравнивает два сертификата. Обратите внимание, что учитываются все поля, а не только имя издателя и серийный номер.
Этот метод использует X509_cmp() из OpenSSL, который сравнивает сертификаты на основе их кэшированных DER-кодировок. Если сертификат неполон, сравнение может быть ненадёжным.
См. также справочную страницу X509_cmp(3).
static VALUE
ossl_x509_add_extension(VALUE self, VALUE extension)
{
X509 *x509;
X509_EXTENSION *ext;
GetX509(self, x509);
ext = GetX509ExtPtr(extension);
if (!X509_add_ext(x509, ext, -1)) { /* DUPs ext - FREE it */
ossl_raise(eX509CertError, NULL);
}
return extension;
} static VALUE
ossl_x509_check_private_key(VALUE self, VALUE key)
{
X509 *x509;
EVP_PKEY *pkey;
/* not needed private key, but should be */
pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */
GetX509(self, x509);
if (!X509_check_private_key(x509, pkey)) {
ossl_clear_error();
return Qfalse;
}
return Qtrue;
} Возвращает true, если key — соответствующий закрытый ключ для информации об открытом ключе субъекта, и false в противном случае.
static VALUE
ossl_x509_get_extensions(VALUE self)
{
X509 *x509;
int count, i;
X509_EXTENSION *ext;
VALUE ary;
GetX509(self, x509);
count = X509_get_ext_count(x509);
ary = rb_ary_new_capa(count);
for (i=0; i<count; i++) {
ext = X509_get_ext(x509, i); /* NO DUP - don't free! */
rb_ary_push(ary, ossl_x509ext_new(ext));
}
return ary;
} static VALUE
ossl_x509_set_extensions(VALUE self, VALUE ary)
{
X509 *x509;
X509_EXTENSION *ext;
long i;
Check_Type(ary, T_ARRAY);
/* All ary's members should be X509Extension */
for (i=0; i<RARRAY_LEN(ary); i++) {
OSSL_Check_Kind(RARRAY_AREF(ary, i), cX509Ext);
}
GetX509(self, x509);
for (i = X509_get_ext_count(x509); i > 0; i--)
X509_EXTENSION_free(X509_delete_ext(x509, 0));
for (i=0; i<RARRAY_LEN(ary); i++) {
ext = GetX509ExtPtr(RARRAY_AREF(ary, i));
if (!X509_add_ext(x509, ext, -1)) { /* DUPs ext */
ossl_raise(eX509CertError, "X509_add_ext");
}
}
return ary;
} # File ext/openssl/lib/openssl/x509.rb, line 349
def inspect
"#<#{self.class}: " \
"subject=#{subject.inspect}, " \
"issuer=#{issuer.inspect}, " \
"serial=#{serial.inspect}, " \
"not_before=#{not_before.inspect rescue "(error)"}, " \
"not_after=#{not_after.inspect rescue "(error)"}>"
end static VALUE
ossl_x509_get_issuer(VALUE self)
{
X509 *x509;
X509_NAME *name;
GetX509(self, x509);
if(!(name = X509_get_issuer_name(x509))) { /* NO DUP - don't free! */
ossl_raise(eX509CertError, NULL);
}
return ossl_x509name_new(name);
} static VALUE
ossl_x509_set_issuer(VALUE self, VALUE issuer)
{
X509 *x509;
GetX509(self, x509);
if (!X509_set_issuer_name(x509, GetX509NamePtr(issuer))) { /* DUPs name */
ossl_raise(eX509CertError, NULL);
}
return issuer;
} static VALUE
ossl_x509_get_not_after(VALUE self)
{
X509 *x509;
const ASN1_TIME *asn1time;
GetX509(self, x509);
if (!(asn1time = X509_get0_notAfter(x509))) {
ossl_raise(eX509CertError, NULL);
}
return asn1time_to_time(asn1time);
} static VALUE
ossl_x509_set_not_after(VALUE self, VALUE time)
{
X509 *x509;
ASN1_TIME *asn1time;
GetX509(self, x509);
asn1time = ossl_x509_time_adjust(NULL, time);
if (!X509_set1_notAfter(x509, asn1time)) {
ASN1_TIME_free(asn1time);
ossl_raise(eX509CertError, "X509_set_notAfter");
}
ASN1_TIME_free(asn1time);
return time;
} static VALUE
ossl_x509_get_not_before(VALUE self)
{
X509 *x509;
const ASN1_TIME *asn1time;
GetX509(self, x509);
if (!(asn1time = X509_get0_notBefore(x509))) {
ossl_raise(eX509CertError, NULL);
}
return asn1time_to_time(asn1time);
} static VALUE
ossl_x509_set_not_before(VALUE self, VALUE time)
{
X509 *x509;
ASN1_TIME *asn1time;
GetX509(self, x509);
asn1time = ossl_x509_time_adjust(NULL, time);
if (!X509_set1_notBefore(x509, asn1time)) {
ASN1_TIME_free(asn1time);
ossl_raise(eX509CertError, "X509_set_notBefore");
}
ASN1_TIME_free(asn1time);
return time;
} # File ext/openssl/lib/openssl/x509.rb, line 358
def pretty_print(q)
q.object_group(self) {
q.breakable
q.text 'subject='; q.pp self.subject; q.text ','; q.breakable
q.text 'issuer='; q.pp self.issuer; q.text ','; q.breakable
q.text 'serial='; q.pp self.serial; q.text ','; q.breakable
q.text 'not_before='; q.pp self.not_before; q.text ','; q.breakable
q.text 'not_after='; q.pp self.not_after
}
end static VALUE
ossl_x509_get_public_key(VALUE self)
{
X509 *x509;
EVP_PKEY *pkey;
GetX509(self, x509);
if (!(pkey = X509_get_pubkey(x509))) { /* adds an reference */
ossl_raise(eX509CertError, NULL);
}
return ossl_pkey_wrap(pkey);
} static VALUE
ossl_x509_set_public_key(VALUE self, VALUE key)
{
X509 *x509;
EVP_PKEY *pkey;
GetX509(self, x509);
pkey = GetPKeyPtr(key);
ossl_pkey_check_public_key(pkey);
if (!X509_set_pubkey(x509, pkey))
ossl_raise(eX509CertError, "X509_set_pubkey");
return key;
} static VALUE
ossl_x509_get_serial(VALUE self)
{
X509 *x509;
GetX509(self, x509);
return asn1integer_to_num(X509_get_serialNumber(x509));
} static VALUE
ossl_x509_set_serial(VALUE self, VALUE num)
{
X509 *x509;
GetX509(self, x509);
X509_set_serialNumber(x509, num_to_asn1integer(num, X509_get_serialNumber(x509)));
return num;
} static VALUE
ossl_x509_sign(VALUE self, VALUE key, VALUE digest)
{
X509 *x509;
EVP_PKEY *pkey;
const EVP_MD *md;
VALUE md_holder;
pkey = GetPrivPKeyPtr(key); /* NO NEED TO DUP */
/* NULL needed for some key types, e.g. Ed25519 */
md = NIL_P(digest) ? NULL : ossl_evp_md_fetch(digest, &md_holder);
GetX509(self, x509);
if (!X509_sign(x509, pkey, md))
ossl_raise(eX509CertError, "X509_sign");
return self;
} static VALUE
ossl_x509_get_signature_algorithm(VALUE self)
{
X509 *x509;
const ASN1_OBJECT *obj;
GetX509(self, x509);
X509_ALGOR_get0(&obj, NULL, NULL, X509_get0_tbs_sigalg(x509));
return ossl_asn1obj_to_string_long_name(obj);
} Возвращает алгоритм подписи, использованный для подписания этого сертификата. Возвращается имя алгоритма, указанное в структуре TBSCertificate, а не во внешней структуре Certificate.
Возвращает полное имя алгоритма подписи или его десятичное представление с точками, если OpenSSL не определяет для него полное имя.
static VALUE
ossl_x509_get_subject(VALUE self)
{
X509 *x509;
X509_NAME *name;
GetX509(self, x509);
if (!(name = X509_get_subject_name(x509))) { /* NO DUP - don't free! */
ossl_raise(eX509CertError, NULL);
}
return ossl_x509name_new(name);
} static VALUE
ossl_x509_set_subject(VALUE self, VALUE subject)
{
X509 *x509;
GetX509(self, x509);
if (!X509_set_subject_name(x509, GetX509NamePtr(subject))) { /* DUPs name */
ossl_raise(eX509CertError, NULL);
}
return subject;
} static VALUE
ossl_x509_tbs_bytes(VALUE self)
{
X509 *x509;
int len;
unsigned char *p0;
VALUE str;
GetX509(self, x509);
len = i2d_re_X509_tbs(x509, NULL);
if (len <= 0) {
ossl_raise(eX509CertError, "i2d_re_X509_tbs");
}
str = rb_str_new(NULL, len);
p0 = (unsigned char *)RSTRING_PTR(str);
if (i2d_re_X509_tbs(x509, &p0) <= 0) {
ossl_raise(eX509CertError, "i2d_re_X509_tbs");
}
ossl_str_adjust(str, p0);
return str;
} Возвращает байты сертификата, подлежащего подписанию, в кодировке DER. Это полезно главным образом для проверки встроенных подписей прозрачности сертификатов.
static VALUE
ossl_x509_to_der(VALUE self)
{
X509 *x509;
VALUE str;
long len;
unsigned char *p;
GetX509(self, x509);
if ((len = i2d_X509(x509, NULL)) <= 0)
ossl_raise(eX509CertError, NULL);
str = rb_str_new(0, len);
p = (unsigned char *)RSTRING_PTR(str);
if (i2d_X509(x509, &p) <= 0)
ossl_raise(eX509CertError, NULL);
ossl_str_adjust(str, p);
return str;
} static VALUE
ossl_x509_to_pem(VALUE self)
{
X509 *x509;
BIO *out;
VALUE str;
GetX509(self, x509);
out = BIO_new(BIO_s_mem());
if (!out) ossl_raise(eX509CertError, NULL);
if (!PEM_write_bio_X509(out, x509)) {
BIO_free(out);
ossl_raise(eX509CertError, NULL);
}
str = ossl_membio2str(out);
return str;
} static VALUE
ossl_x509_to_text(VALUE self)
{
X509 *x509;
BIO *out;
VALUE str;
GetX509(self, x509);
out = BIO_new(BIO_s_mem());
if (!out) ossl_raise(eX509CertError, NULL);
if (!X509_print(out, x509)) {
BIO_free(out);
ossl_raise(eX509CertError, NULL);
}
str = ossl_membio2str(out);
return str;
} static VALUE
ossl_x509_verify(VALUE self, VALUE key)
{
X509 *x509;
EVP_PKEY *pkey;
GetX509(self, x509);
pkey = GetPKeyPtr(key);
ossl_pkey_check_public_key(pkey);
switch (X509_verify(x509, pkey)) {
case 1:
return Qtrue;
case 0:
ossl_clear_error();
return Qfalse;
default:
ossl_raise(eX509CertError, NULL);
}
} Проверяет подпись сертификата с помощью открытого ключа key. key должен быть экземпляром OpenSSL::PKey.
static VALUE
ossl_x509_get_version(VALUE self)
{
X509 *x509;
GetX509(self, x509);
return LONG2NUM(X509_get_version(x509));
} static VALUE
ossl_x509_set_version(VALUE self, VALUE version)
{
X509 *x509;
long ver;
if ((ver = NUM2LONG(version)) < 0) {
ossl_raise(eX509CertError, "version must be >= 0!");
}
GetX509(self, x509);
if (!X509_set_version(x509, ver)) {
ossl_raise(eX509CertError, NULL);
}
return version;
}
Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.