openssl签名报错

在调用RSA_private_encrypt函数时遇到如下报错。

复制代码
0:error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error:crypto/asn1/tasn_dec.c:309:Type=X509
0:error:2406C06E:random number generator:RAND_DRBG_instantiate:error retrieving entropy:crypto/rand/drbg_lib.c:335:
0:error:2406B072:random number generator:RAND_DRBG_generate:in error state:crypto/rand/drbg_lib.c:588:
0:error:2406C06E:random number generator:RAND_DRBG_instantiate:error retrieving entropy:crypto/rand/drbg_lib.c:335:
0:error:2406B072:random number generator:RAND_DRBG_generate:in error state:crypto/rand/drbg_lib.c:588:
0:error:04088003:rsa routines:RSA_setup_blinding:BN lib:crypto/rsa/rsa_crpt.c:155:
0:error:04066044:rsa routines:rsa_ossl_private_encrypt:internal error:crypto/rsa/rsa_ossl.c:297:

openssl库版本为1.1.1Q。

复制代码
$ openssl version
OpenSSL 1.1.1q 

查看openssl源码,随机数生成器(DRBG)在获取熵时遇到了错误,错误码RAND_R_ERROR_RETRIEVING_ENTROPY。

复制代码
int RAND_DRBG_instantiate(RAND_DRBG *drbg,
                          const unsigned char *pers, size_t perslen)
{
    if (drbg->get_entropy != NULL)
        entropylen = drbg->get_entropy(drbg, &entropy, min_entropy,   min_entropylen, max_entropylen, 0);
    if (entropylen < min_entropylen || entropylen > max_entropylen) {
        RANDerr(RAND_F_RAND_DRBG_INSTANTIATE, RAND_R_ERROR_RETRIEVING_ENTROPY);
        goto end;
    }

将程序与openssl库做静态链接,放到其它机器上运行一切正常,排除了openssl库的问题,应当是系统问题。

复制代码
# gcc rsasign.c -L/lib/x86_64-linux-gnu/ libssl.a  libcrypto.a -ldl -lpthread 

使用strace调试,发现问题出在getrandom函数,出错的机器上提示函数未实现。

复制代码
$ strace ./rsasign
...
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
getrandom(0x7f0366004398, 8, GRND_NONBLOCK) = -1 ENOSYS (Function not implemented)
brk(NULL)                               = 0x7f036633b000
brk(0x7f036635c000)                     = 0x7f036635c000

单独写一个getrandom.c测试程序:

复制代码
#include<stdio.h>
#include <errno.h>
#include <string.h>
#define _GNU_SOURCE        /* See feature_test_macros(7) */
#include <unistd.h>
#include <sys/syscall.h>   /* For SYS_xxx definitions */
#define __NR_getrandom 318

int main(int argc, void *argv[])
{
    unsigned char buf[32];
    ssize_t result = syscall(__NR_getrandom, buf, sizeof(buf), 0);

    if (result == -1) {
        printf("getrandom failed:%s\n", strerror(errno));
        return 1;
    }
    for (int i = 0; i < sizeof(buf); i++)
        printf("%02x ", buf[i]);
    printf("\n");
    return 0;
}

运行测试程序,同样有如下报错:

复制代码
$ gcc getrandom.c -o rand
$ ./rand
getrandom failed:Function not implemented

其它正常运行的设备上,如下信息,可以正常获取到随机数:

复制代码
$ ./rand
32 69 78 58 4b c8 8b a4 3d c5 11 95 b6 de 00 29 4f 6f 9e 02 0b 66 28 78 f9 70 e4 53 62 1b 64 e6 

openssl文件crypto/rand/rand_unix.c中函数syscall_random代码如下,linux内核在3.17版本之后开始支持getrandom系统调用。出问题的系统内核为3.10,还不能支持getrandom。

复制代码
/* syscall_random(): Try to get random data using a system call
 * returns the number of bytes returned in buf, or < 0 on error.
 */
static ssize_t syscall_random(void *buf, size_t buflen)
{
    /* Linux supports this since version 3.17 */
#  if defined(__linux) && defined(__NR_getrandom)
    return syscall(__NR_getrandom, buf, buflen, 0);

另外,对于linux系统,随机数还可以从以下设备文件中获取。

复制代码
#  define DEVRANDOM "/dev/urandom", "/dev/random", "/dev/hwrng", "/dev/srandom"

在由设备文件读取随机数之前,函数wait_random_seeded等待设备有足够的种子(seed)。

复制代码
size_t rand_pool_acquire_entropy(RAND_POOL *pool)
{
#   if defined(OPENSSL_RAND_SEED_DEVRANDOM)
    if (wait_random_seeded()) {
        size_t bytes_needed;
        unsigned char *buffer;
        
        bytes_needed = rand_pool_bytes_needed(pool, 1 /*entropy_factor*/);
        for (i = 0; bytes_needed > 0 && i < OSSL_NELEM(random_device_paths); i++) {
            ssize_t bytes = 0; 
            int attempts = 3;    /* Maximum number of consecutive unsuccessful attempts */
            const int fd = get_random_device(i);
            if (fd == -1) continue;
            
            while (bytes_needed != 0 && attempts-- > 0) {
                buffer = rand_pool_add_begin(pool, bytes_needed);
                bytes = read(fd, buffer, bytes_needed);

从内核4.8(DEVRANDOM_SAFE_KERNEL)开始,当/dev/random(DEVRANDOM_WAIT)可读时,不确保设备/dev/urandom被正确的Seed。这里使用uname获取系统的内核版本,如果版本号大于4.8,返回0。

否则,检查/dev/random是否可读,来判断/dev/urandom是否正确的Seed。

复制代码
static int wait_random_seeded(void)
{
    static int seeded = OPENSSL_RAND_SEED_DEVRANDOM_SHM_ID < 0;
    static const int kernel_version[] = { DEVRANDOM_SAFE_KERNEL };

    if (!seeded) {     /* See if anything has created the global seeded indication */
        if ((shm_id = shmget(OPENSSL_RAND_SEED_DEVRANDOM_SHM_ID, 1, 0)) == -1) {
            /*
             * Check the kernel's version and fail if it is too recent.
             * Linux kernels from 4.8 onwards do not guarantee that
             * /dev/urandom is properly seeded when /dev/random becomes
             * readable.  However, such kernels support the getentropy(2)
             * system call and this should always succeed which renders
             * this alternative but essentially identical source moot.
             */
            if (uname(&un) == 0) {
                kernel[0] = atoi(un.release);
                p = strchr(un.release, '.');
                kernel[1] = p == NULL ? 0 : atoi(p + 1);
                if (kernel[0] > kernel_version[0] || (kernel[0] == kernel_version[0] && kernel[1] >= kernel_version[1]))
                    return 0;
            }
            /* Open /dev/random and wait for it to be readable */
            if ((fd = open(DEVRANDOM_WAIT, O_RDONLY)) != -1) {
                if (DEVRANDM_WAIT_USE_SELECT && fd < FD_SETSIZE) {
                    FD_ZERO(&fds);
                    FD_SET(fd, &fds);
                    while ((r = select(fd + 1, &fds, NULL, NULL, NULL)) < 0 && errno == EINTR);
                } else {
                    while ((r = read(fd, &c, 1)) < 0 && errno == EINTR);

出现问题的系统使用的内核为3.10,但是uname系统调用获取到的内核版本为5.16,导致了问题的产生。修复uname的问题,获取内核版本号为实际的版本号3.10之后,问题得到解决。

命令strace跟踪,不再调用getrandom,而是使用了urandom来获取随机数。

复制代码
$ strace ./rsasign
open("/dev/urandom", O_RDONLY|O_NOCTTY|O_NONBLOCK) = 3
相关推荐
Lsetea1 天前
证书没到期却报certificate has expired:OpenSSL定位中间证书与系统时间
运维·https·ssl证书·openssl·证书链
Lsetea2 天前
Nginx报No required SSL certificate was sent:mTLS客户端证书排查
运维·nginx·https·ssl·openssl
Lsetea3 天前
部署证书后提示“证书名称与输入不匹配”:Safari 报错的三层排查
nginx·https·safari·ssl证书·openssl
Lsetea4 天前
OpenSSL快速生成域名证书:含SAN的自签命令、验证与排错
nginx·https·ssl证书·openssl·自签证书
Lsetea8 天前
CDN换了SSL证书仍显示旧证书?按边缘节点、SNI和缓存逐层排查
https·cdn·ssl证书·openssl·tls
Lsetea9 天前
Nginx HTTPS证书域名不匹配:用SAN与SNI定位 ERR_CERT_COMMON_NAME_INVALID
nginx·https·ssl证书·openssl·tls
bosins13 天前
Windows IIS 配置本地HTTPS 全流程指南:自签名证书 + 端口绑定 + 避坑详解
powershell·openssl
Lsetea17 天前
Nginx报SSL_CTX_use_PrivateKey failed:证书与私钥不匹配怎么排查
nginx·https·ssl证书·openssl·私钥
Lsetea19 天前
SSL证书快过期怎么自动检查:用OpenSSL checkend做7天预警与线上证书验收
https·ssl证书·openssl·systemd·证书监控
蜡台1 个月前
本机 局域网 搭建本地 HTTPS 域名完整方案
网络协议·http·https·openssl