在使用pgopenssltypes扩展时,我意识到我还没有讨论如何使用OpenSSL库对数字证书进行签名。 (至少我不记得这样做了–我可能在博客的早期就对此进行了讨论。我很确定我已经讨论过使用BouncyCastle(java)库对数字证书进行签名。)
我的pgopenssltypes扩展将具有签名数字证书以进行测试的功能,但实际工作将在可能的pgca扩展中完成。 成为CA不仅需要签署简单证书的能力。
签署证书的基本代码很简单。
BIGNUM *serialNumber;
X509_NAME *issuerName;
X509_NAME *subjectName;
time_t notBefore;
time_t notAfter;
EVP_PKEY *issuerKey; // private key
EVP_PKEY *subjectKey; // public key
// allocate memory for a cert.
X509 cert = X509_new();
// this is standard
X509_set_version(cert, 3);
// set the the mandatory fields.
X509_set_serialNumber(cert, BN_to_ASN1_INTEGER(serialNumber, NULL));
X509_set_issuer_name(cert, issuerName);
X509_set_subject_name(cert, subjectName);
X509_set_notBefore(cert, ASN1_TIME_set(NULL, notBefore));
X509_set_notAfter(cert, ASN1_TIME_set(NULL, notAfter));
X509_set_pubkey(cert, subjectKey);
// sign the certificate. In this case I'm using SHA256 for the hash.
if ((r = X509_sign(cert, issuerKey, EVP_sha256())) <= 0) {
fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
X509_free(cert);
return NULL;
}
// release the memory
PKCS8_PRIV_KEY_INFO_free(p8);
EVP_PKEY_free(pkey);
// write certificate to file
// FILE *fp = fopen(...)
// PEM_write_X509(fp, cert);
return cert;
有一个使用X509_REQ而不是X509对象的变体,但这是不重要的更改。 (我记得X509_REQ是特殊的自签名X509证书,主要由完整的证书颁发机构(CA)使用。)
发行方通常与自己的数字证书无限制地关联。 这形成了“证书链”,其中每个证书的颁发者是下一个证书的主题。 链以“受信任”证书(由用户决定什么是“受信任”)或自签名证书(由用户决定哪个主题/发布者是多少)结束“受信任”)。
证书本身不是跟踪公钥的便捷方法,没有任何价值。 证书链只能在每个中间签名者都可以信任的范围内用于身份验证。
最后,上面的代码将很乐意允许使用任何**来签署证书。 在行为良好的应用程序中,将检查颁发者证书的“基本约束” –该数字指定可以签名多少个附加级别,并且每一代应至少丢弃一个级别。
资料类型
该代码包含几种不熟悉的数据类型。
BIGNUM是任意长度整数的OpenSSL实现。 某些CA使用128或160位***,而不是同时使用MD5或SHA1哈希值的长度。 但是,这只是一个惯例,没有理由为什么攻击者无法使用10k字节的***来尝试在编写不良的代码尝试读取缓冲区时导致缓冲区溢出。
有一个宽松的约定,即***较高的证书比***较低的证书发行晚。 许多CA不遵循此约定。
一些CA担心简单的序列会泄漏敏感信息,即已颁发了多少证书。 常见的对策是使用基于日期的***,例如,十六进制值可以是0x20141230NNNNNN,其中NNNNNN是时间,伪随机数或某种组合。
最后,谨慎的CA可能会在***本身中编码信息。 例如,它可能会悄悄地确保***始终为3 mod 17,或者类似的值反映证书中其他位置存在的信息。 有权访问CA私钥的攻击者可能没有意识到这些检查,而在野外发现错误的***可能是破坏的第一个迹象。
EVP_PKEY是通用私钥或公钥。 它可以包含RSA,ECC,DSA或DH**。 我认为在创建数字证书(可能是带有低功耗设备的ECC)时,我们只关心RSA**。
我们可以使用以下方法将RSA**转换为EVP_PKEY:
RSA *rsa;
EVP_PKEY *pkey;
pkey = EVP_PKEY_new();
if (EVP_PKEY_set1_RSA(pkey, rsa) <= 0) {
fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
EVP_PKEY_free(pkey);
return NULL;
}
return pkey;
将.p8(PKCS8)**转换为EVP_PKEY也很容易:
PKCS8_PRIV_KEY_INFO *p8;
EVP_PKEY *pkey;
pkey = EVP_PKCS82PKEY(p8);
if (pkey == null) {
fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
return NULL;
}
return pkey;
X509_NAME与LDAP专有名称相同。 从字面上看-它们来自相同的X.500标准。 (因此X509。)许多体系结构都利用了这一点–这是将用户的数字证书与企业目录服务绑定的好方法。
X509_NAME是X509_NAME_ENTRY值的堆栈。 每个条目都是一个键值对。 例如,“ CN = Bear Giles,C = US,ST = Colorado”将具有三个X509_NAME_ENTRY值-(“ CN”,“ Bear Giles”),(“ C”,“ US”),(“ ST”,“科罗拉多”。 有几十个标准键,其中一些可以重复。 (例如,域组件的“ DC”。invariantproperties.com上的服务器将为“ DC = invariantproperties,DC = com”。)有关更多详细信息,请参见RFC 4519属性类型。
(旁注:LDAP通常具有上面显示的格式。OpenSSL倾向于用斜杠代替逗号,例如“ CN = Bear Giles / C = US / ST = Colorado”。)
您可以使用以下命令打印X509_NAME:
X509_NAME *name;
char buf[BUF_LEN];
X509_NAME_oneline(name, buf, BUF_LEN);
要么:
BIO *bio; // can point to memory buffer, file, etc.
X509_NAME *name;
int obase; // indentation on multi-line output.
X509_NAME_print(bio, name, obase);
创建X509_NAME对象要复杂一些。
char *name; // e.g., "C" for Country
unsigned char *value; // e.g., "US"
int type = MBSTRING_ASC; // or MBSTRING_UTF8
X509_NAME *name;
// value is null-terminated string.
int len = -1;
// these values add entry to end of X509_NAME.
int loc = -1;
int set = 0;
name = X509_NAME_new();
if (X509_NAME_add_entry_by_txt(name, key, type, value, len, loc, set) <= 0) {
fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
return NULL;
}
扩展名
X509v3证书的真正功能是可以使用任意信息进行扩展。 有十几种广泛使用的扩展名,但是任何人都可以获取新的ASN1 OID并将自己的信息添加到数字证书中。 (必须明确地请求并注册一个新的OID –如果将相同的OID用于不同的用途,则会引起问题。)仅必须注册根OID,才能*创建子OID。
有关标准扩展的详细讨论超出了本文的范围。 需要满足的条件是它们可以提供有关**用法的信息(例如,应使用证书对电子邮件进行加密,但不能在服务器上使用),证书的其他别名,甚至是对此证书签名的证书的限制。 (例如,它必须用于“ example.com”域下的服务器)。
非标准扩展名可能包括内部用户ID,进一步验证用户身份的生物特征,照片等。
翻译自: https://www.javacodegeeks.com/2015/01/signing-digital-certificates-with-openssl-library.html