【问题标题】:how to modify signature of PEM formatted certificate in python如何在python中修改PEM格式证书的签名
【发布时间】:2020-01-22 00:23:43
【问题描述】:

有没有办法用 python 修改 PEM 编码的 x509 证书的签名?

我尝试过使用密码学 python 模块,但似乎 x509 签名属性不可设置。我只能得到它的价值。是否有另一个 Python 模块可以更好地实现此目的?

密码学 python 模块文档在这里: https://cryptography.io/en/latest/x509/reference/#x-509-certificate-object

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import Encoding
import os

#change the signature of the cert
def change_cert_sig(pem_data):
    cert = x509.load_pem_x509_certificate(pem_data, default_backend())
    #the line below works
    print(cert.signature)
    #the line below causes an exception "AttributeError: can't set attribute"
    cert.set_signature(os.urandom(len(cert.signature))) #set signature to random bytes
    return cert.public_bytes(Encoding.PEM)

【问题讨论】:

    标签: python certificate x509 signature pem


    【解决方案1】:

    有没有办法用 python 修改 PEM 编码的 x509 证书的签名?

    是的。

    我不是 Python 人,所以我只能描述要做什么......将证书读入内存。这是来自RFC 5280, Appendix A, p. 116 (and friends)的证书的ASN.1结构:

    Certificate  ::=  SEQUENCE  {
         tbsCertificate       TBSCertificate,
         signatureAlgorithm   AlgorithmIdentifier,
         signature            BIT STRING  }
    

    tbsCertificate 是颁发者(或证书颁发机构)签名的内容。 “TBS” 表示“待签名”。 signature 是发行人在tbsCertificate 上的签名。而signatureAlgorithm 描述了使用的签名算法,如sha256WithRSAEncryption

    跳到第三个属性,即signature BIT STRING。前 4 个八位字节是 BIT_STRING 的 ASN.1 编码的一部分。八位字节 5-37 是签名字节。获取签名字节之一并对其进行篡改。比如Byte b = data[6]b ^= 0x01,然后是data[6] = b

    由于signature 是证书中的最后一件事,您应该能够篡改文件中最后 32 个字节中的任何一个。最后 32 个字节是签名。 (32 个字节假定使用 SHA-256 进行签名)。

    【讨论】:

    • 与答案无关,如果我太好奇了,很抱歉,但你是怎么进入罚球区的?
    【解决方案2】:

    这不是微不足道的。

    您正在调用cert.public_bytes(Encoding.PEM),其源代码如下:

    def public_bytes(self, encoding):
        bio = self._backend._create_mem_bio_gc()
        if encoding is serialization.Encoding.PEM:
            res = self._backend._lib.PEM_write_bio_X509(bio, self._x509)
        elif encoding is serialization.Encoding.DER:
            res = self._backend._lib.i2d_X509_bio(bio, self._x509)
        else:
            raise TypeError("encoding must be an item from the Encoding enum")
    
        self._backend.openssl_assert(res == 1)
        return self._backend._read_mem_bio(bio)
    

    转到PEM_write_bio_X509,它实际上是用 C 语言实现并通过 CFFI 访问的。

    传递给它的参数,._x509 只是一个数据块:

    In [25]: c._x509
    Out[25]: <cdata 'X509 *' 0x7f8608d08890>
    

    这意味着要修改现有对象中的签名,您必须找到内存位置并覆盖该内存位置...

    这样就大错特错了。

    您看,cryptography 是底层库(如 OpenSSL)上的薄垫片。默认情况下,它不会向您提供所有详细信息。正确的方法是将证书的 X.509 表示分解为一堆字段,然后添加、删除、修改这些字段,然后将其回滚到 X.509。

    但是,由于这太难了,请注意:

    pd  # PEM data as a string
    '-----BEGIN CERTIFICATE-----\nMIIC [snip] 1Q=\n-----END CERTIFICATE-----\n'
    
    b64 = "".join([bit for bit in pd.split() if "---" not in bit])
    pemdata = binascii.a2b_base64(b64)
    
    cert.signature in pemdata
    True
    

    所以,恰好证书签名在 cert blob 中占用了一个字节范围,所以,让我们尝试更改它:

    pemdata.find(cert.signature)
    423
    len(cert.signature)
    128
    fakesig = b"\x42" * len(cert.signature)
    fakecertdata = pemdata[:423] + fakesig + pemdata[423 + 128:]
    with open("fake.pem", "wb") as fout:
        fout.write(b"-----BEGIN CERTIFICATE-----\n")
        fout.write(base64.encodebytes(fakecertdata))
        fout.write(b"-----END CERTIFICATE-----\n")
    

    你瞧!

    > openssl x509 -in fake.pem -text -noout
    Certificate:
        Data:
            Version: 1 (0x0)
            Serial Number: [snip]
        Signature Algorithm: sha1WithRSAEncryption
            Issuer: [snip]
            Validity [snip]
            Subject: [snip]
            Subject Public Key Info:
                Public Key Algorithm: rsaEncryption
                    Public-Key: (1024 bit)
                    Modulus:
                        00: [snip] :55
                    Exponent: 65537 (0x10001)
        Signature Algorithm: sha1WithRSAEncryption
             42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:
             42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:
             42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:
             42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:
             42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:
             42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:
             42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:42:
             42:42
    

    【讨论】:

    • 签名不是ASN.1编码的吗?我认为,根据实际的签名数据,还需要做一些进一步的修改。
    • YMMV,我认为签名是字节的整数,至少对于常用算法而言,ASN.1的DER编码将字节编码为字节。因此,如果签名算法保持不变,则不需要更改算法代码;签名长度保持不变,然后替换那个字节序列应该没问题。 PEM 的 Base64 部分需要解包和重新打包,ofc。见en.wikipedia.org/wiki/X.690#DER_encoding
    • 对于 P-256 椭圆曲线,它有点混乱,因为签名包含 ASN.1 整数,可能会根据它们的值进行修剪,也可能不会修剪。更多信息请参见 Atmel 的 Compressed Certificate Definition 第 3.2 节。
    【解决方案3】:

    根据签名算法,@Dima Tisnek 的答案可能并不总是有效。例如,椭圆曲线 P-256 签名包含 ASN.1 整数,可能会或可能不会根据其值进行修剪。

    这是一个修改后的版本,调整了ASN.1结构的位串和序列长度:

    cert_der = cert.public_bytes(encoding=serialization.Encoding.DER)
    
    assert(cert.signature in cert_der)
    
    # see 3.2 Signature Reconstruction in Compressed Certificate Definition
    sig_offset = cert_der.find(cert.signature)  
    cert_der = bytearray(cert_der[:sig_offset] + signature)
    cert_der[2:4] = (len(cert_der) - 4).to_bytes(2, 'big')
    cert_der[sig_offset-2] = len(signature) + 1
    cert = x509.load_der_x509_certificate(cert_der)
    

    Microchip/Atmel 的Compressed Certificate Definition 的第 3.2 节对所需修改进行了详细说明。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-10-26
      • 1970-01-01
      • 2014-07-27
      • 1970-01-01
      • 2013-06-13
      • 2021-02-12
      • 1970-01-01
      相关资源
      最近更新 更多