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
相关推荐
Lsetea2 天前
OpenSSL的checkhost显示不匹配却退出0:名称检查与证书验收
linux·https·shell·ssl证书·openssl
Lsetea3 天前
OpenSSL的showcerts没有根证书正常吗:服务端发链与show_chain构链排查
https·ssl证书·openssl·tls·证书链
Lsetea4 天前
SSL证书指纹相同但文件哈希不同:PEM、DER与公钥摘要怎么比
运维·https·ssl证书·openssl·证书链
Lsetea5 天前
OpenSSL指定-cipher仍协商TLS1.3:证书排查中的套件与协议边界
运维·https·ssl证书·openssl·tls
Lsetea7 天前
curl设置SSL_CERT_FILE仍报60:CURL_CA_BUNDLE与CA路径优先级排查
linux·https·ssl证书·openssl·curl
Lsetea8 天前
OpenSSL报certificate rejected:error 28与CA辅助信任属性排查
linux·https·ssl证书·openssl·证书链
Lsetea9 天前
OpenSSL报certificate revoked:error 23与CRL吊销层级排查
linux·https·ssl证书·openssl·crl
Lsetea9 天前
OpenSSL报certificate chain too long:error 22与verify_depth深度排查
linux·https·ssl证书·openssl·证书链
Lsetea11 天前
OpenSSL读取加密私钥报bad decrypt:passin与口令来源排查
linux·https·ssl证书·openssl·私钥