【问题标题】:Linux Kernel RSA Signature Verification crypto_akcipher_verify() outputLinux 内核 RSA 签名验证 crypto_akcipher_verify() 输出
【发布时间】:2018-04-05 00:58:10
【问题描述】:

我目前正在开发一个内核模块,我正在执行 RSA 签名验证。我的模块是针对 4.4 内核的,所以我决定使用较低级别的akcipher API。我一直使用当前的implementationpublic_key_verify_signature 作为指导。我的做法是:

  1. 分配一个 crypto_akcipher 结构:*tfm = crypto_alloc_akcipher("rsa", 0, 0);
  2. 分配一个 akcipher_request 结构:req = akcipher_request_alloc(*tfm, GFP_KERNEL);
  3. 为请求设置公钥:err = crypto_akcipher_set_pub_key(*tfm, data, len);
  4. 将接收到的签名放入分散列表并将其设置为 akcipher_request 的参数:akcipher_request_set_crypt(req, &src, &dst, sig->s_size, MAX_OUT);
  5. 最后调用crypto_akcipher_verify(req),它应该会计算出预期的摘要
  6. 将预期摘要与收到的摘要进行比较以验证签名

我目前处于我认为我正确使用 API 但crypto_akcipher_verify 的输出不符合它在较新的public_key_verify_signature 示例中使用的方式的地步。这让我很困惑,因为它似乎输出了正确摘要的一部分。

例如,当收到正确签名的请求时,我会得到以下结果:

Expected Digest:
e52bed356dcbf8e4b3c1458ac3e4cb49e77512e6

Computated outbuf:
01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003015300906052b0e03021a05000408e52bed356dcbf8e4

计算出的outbuf 的最后 8 个字节是预期的 20 字节摘要的前 8 个字节。但是outbuf 的其余部分似乎是垃圾。 (虽然每次都是一致的,但它总是0x01,后面跟着很多0xffs,最后是003015300906052b0e03021a05000408,最后8个字节)。这是负责调用crypto_akcipher_verify(req)的代码块:

// Init completion
init_completion(&(res.completion));

// Put the data into our request structure
memcpy(inbuf, sig->s, sig->s_size);
sg_init_one(&src, inbuf, sig->s_size);
sg_init_one(&dst, outbuf, MAX_OUT);
akcipher_request_set_crypt(req, &src, &dst, sig->s_size, MAX_OUT);

// Set the completion routine callback
// results from the verify routine will be stored in &res
akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
                  CRYPTO_TFM_REQ_MAY_SLEEP, op_complete, &res);

// Compute the expected digest
err = wait_async_op(&res, crypto_akcipher_verify(req));

if(err) {
    printk(KERN_INFO "[!] Digest computation failed %d\n", err);
    kfree(inbuf);
    kfree(outbuf);
    return err;
}

printk(KERN_INFO "\nComputation:\n");
hexdump(outbuf, req->dst_len);

/* Do the actual verification step. */
if (req->dst_len != sig->digest_size ||
    memcmp(sig->digest, outbuf, sig->digest_size) != 0) {
    printk(KERN_INFO "[!] Signature verification failed - Key Rejected: %d\n", -EKEYREJECTED);
    printk(KERN_INFO "[!] Sig len: %d   Computed len: %d\n", sig->digest_size, req->dst_len);
    kfree(inbuf);
    kfree(outbuf);
    return -EKEYREJECTED;
}

任何帮助或指出正确的方向将不胜感激。对不起,如果这篇文章不是非常简洁。

【问题讨论】:

    标签: c linux-kernel cryptography rsa


    【解决方案1】:

    正如@Maarten 上面所建议的那样。我看到的是 PKCS1 v1.5 编码。从RFC 来看,填充看起来像:

    EM = 0x00 || 0x01 || PS || 0x00 || T.
    

    PS 在哪里:

    “PS 由 emLen - tLen - 3 个八位字节组成,十六进制值为 0xff。PS 的长度至少为 8 个八位字节。”

    SHA1 末尾的 DER 编码“T”是(我将按照上面的建议切换到 SHA256):

    SHA-1:   (0x)30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 || H.
    

    在较新的内核中解析此填充的最佳方法是在调用 crypto_alloc_akcipher 时使用 "pkcs1pad(rsa,SHA256)" 类型。然后rsa_verify 将为您解析填充。不幸的是,由于我试图将它移植到几个内核版本中,我不得不查看一个较旧的方法和referenced the old rsa_verify routine

    最后我的 SHA256 EMSA PKCS#1 v1.5 解析代码如下所示:

    static const u8 RSA_digest_info_SHA256[] = {
        0x30, 0x31, 0x30, 0x0d, 0x06, 0x09,
        0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
        0x05, 0x00, 0x04, 0x20
    };
    
    typedef struct RSA_ASN1_template {
        const u8 * data;
        size_t size;
    } RSA_ASN1_template;
    
    RSA_ASN1_template sha256_template;
    
    // Derived from https://github.com/torvalds/linux/blob/db6c43bd2132dc2dd63d73a6d1ed601cffd0ae06/crypto/asymmetric_keys/rsa.c#L101
    // and https://www.rfc-editor.org/rfc/rfc8017#section-9.2
    // thanks to Maarten Bodewes for answering the question on Stackoverflow
    // https://stackoverflow.com/questions/49662595/linux-kernel-rsa-signature-verification-crypto-akcipher-verify-output
    static char *pkcs_1_v1_5_decode_emsa(unsigned char * EM,
                            unsigned long  EMlen,
                            const u8 * asn1_template,
                            size_t asn1_size,
                            size_t hash_size) {
    
        unsigned int t_offset, ps_end, ps_start, i;
    
        if (EMlen < 2 + 1 + asn1_size + hash_size)
            return NULL;    
    
    /* Decode the EMSA-PKCS1-v1_5
     * note: leading zeros are stripped by the RSA implementation in older kernels
     * so   EM = 0x00 || 0x01 || PS || 0x00 || T
     * will become EM = 0x01 || PS || 0x00 || T.
     */
    #if LINUX_VERSION_CODE < KERNEL_VERSION(4,8,0)
        ps_start = 1;
        if (EM[0] != 0x01) {
            printk(" = -EBADMSG [EM[0] == %02u]", EM[0]);
            return NULL;
        }
    #else
        ps_start = 2;
        if (EM[0] != 0x00 || EM[1] != 0x01) {
            printk(" = -EBADMSG [EM[0] == %02u] [EM[1] == %02u]", EM[0], EM[1]);
            return NULL;
        }
    #endif
    
        // Calculate offsets
        t_offset = EMlen - (asn1_size + hash_size);
        ps_end = t_offset - 1;
    
        // Check if there's a 0x00 seperator between PS and T
        if (EM[ps_end] != 0x00) {
            printk(" = -EBADMSG [EM[T-1] == %02u]", EM[ps_end]);
            return NULL;
        }
    
        // Check the PS 0xff padding 
        for (i = ps_start; i < ps_end; i++) {
            if (EM[i] != 0xff) {
                printk(" = -EBADMSG [EM[PS%x] == %02u]", i - 2, EM[i]);
                return NULL;
            }
        }
    
        // Compare the DER encoding T of the DigestInfo value
        if (crypto_memneq(asn1_template, EM + t_offset, asn1_size) != 0) {
            printk(" = -EBADMSG [EM[T] ASN.1 mismatch]");
            return NULL;
        }
    
        return EM + t_offset + asn1_size;
    
    }
    

    以及调用它的验证函数:

    // Verify a recieved signature
    int verify_sig_rsa(akcipher_request * req, pkey_signature * sig) {
    
        int err;
        void *inbuf, *outbuf, *result = NULL;
        op_result res;
        struct scatterlist src, dst;
        crypto_akcipher *tfm = crypto_akcipher_reqtfm(req);
        int MAX_OUT = crypto_akcipher_maxsize(tfm);
    
    
        inbuf = kzalloc(PAGE_SIZE, GFP_KERNEL);
    
        err = -ENOMEM;
        if(!inbuf) {
            return err;
        }
    
        outbuf = kzalloc(MAX_OUT, GFP_KERNEL);
    
        if(!outbuf) {
            kfree(inbuf);
            return err;
        } 
    
        // Init completion
        init_completion(&(res.completion));
    
        // Put the data into our request structure
        memcpy(inbuf, sig->s, sig->s_size);
        sg_init_one(&src, inbuf, sig->s_size);
        sg_init_one(&dst, outbuf, MAX_OUT);
        akcipher_request_set_crypt(req, &src, &dst, sig->s_size, MAX_OUT);
    
        // Set the completion routine callback
        // results from the verify routine will be stored in &res
        akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
                      CRYPTO_TFM_REQ_MAY_SLEEP, op_complete, &res);
    
        // Compute the expected digest
        err = wait_async_op(&res, crypto_akcipher_verify(req));
    
        if(err) {
            printk(KERN_INFO "[!] Digest computation failed %d\n", err);
            kfree(inbuf);
            kfree(outbuf);
            kfree(result);
            return err;
        }
    
        // Decode the PKCS#1 v1.5 encoding
        sha256_template.data = RSA_digest_info_SHA256;
        sha256_template.size = ARRAY_SIZE(RSA_digest_info_SHA256);
        result = pkcs_1_v1_5_decode_emsa(outbuf, req->dst_len, 
                 sha256_template.data, sha256_template.size, 32);
    
        err = -EINVAL;
        if(!result) {
            printk(KERN_INFO "[!] EMSA PKCS#1 v1.5 decode failed\n");
            kfree(inbuf);
            kfree(outbuf);
            return err;
        }
    
        printk(KERN_INFO "\nComputation:\n");
        hexdump(result, 20); 
    
        /* Do the actual verification step. */
        if (crypto_memneq(sig->digest, result, sig->digest_size) != 0) {
            printk(KERN_INFO "[!] Signature verification failed - Key Rejected: %d\n", -EKEYREJECTED);
            kfree(inbuf);
            kfree(outbuf);
            return -EKEYREJECTED;
        }
        
        printk(KERN_INFO "[+] RSA signature verification passed\n");
        kfree(inbuf);
        kfree(outbuf);
        return 0;
    }
    

    如果有人想参考完整的代码,can be found here

    【讨论】:

      【解决方案2】:

      您已执行原始或教科书 RSA 解密。您正在查看的是散列 + 一个称为 PKCS#1 v1.5 的填充。这也在 PKCS#1 v2.0 及更高版本中定义,以实现向后兼容性(它仍然被认为是安全的,即使它是确定性的)。

      所以请看PKCS#1 v2.2, 8.2.2 步骤 3,其中解释说您必须自己构建相同的结构,然后进行比较。请注意,9.2 包含一些有助于创建结构的快捷方式在注释中

      最后:SHA-1 对于签名生成/验证不再被认为是安全的。尽快升级!

      【讨论】:

      • 对不起,由于某种原因错过了这个问题。我对内核级开发不太了解,这可能会让我跳过这个问题......
      • @Marteen 谢谢!让我看看这个。我知道我遗漏了一些东西,因为十六进制转储是一致的,并且看起来像是某种结构。让我看一看!当我弄清楚时,我会发回我的代码
      • 别忘了检查那里是否已经有东西可以执行取消填充,填充是相当标准的:)
      • 再次感谢!不确定如果没有你我会不会意识到这一点,感谢所有帮助!
      猜你喜欢
      • 2016-01-15
      • 1970-01-01
      • 2019-11-24
      • 1970-01-01
      • 2012-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-06
      相关资源
      最近更新 更多