首页 >后端开发 >Golang >无法使用自定义 crypto.Signer 实现生成 X.509 证书

无法使用自定义 crypto.Signer 实现生成 X.509 证书

PHPz
PHPz转载
2024-02-10 20:42:07841浏览

无法使用自定义 crypto.Signer 实现生成 X.509 证书

php小编柚子在这里为大家介绍一个关于生成 X.509 证书的问题。有时候在使用自定义 crypto.Signer 实现生成证书的过程中,可能会遇到一个无法使用的问题。这个问题可能会让开发者感到困惑,不知道如何解决。在本文中,我们将探讨这个问题的原因,并提供一些解决方案,以帮助开发者顺利生成自己的 X.509 证书。

问题内容

我正在尝试根据存储在 hsm 中的 rsa 密钥对生成 x.509 证书。我使用此 pkcs #11 实现与我的 hsm 进行通信。

由于我的加密对象存储在后者中,如果我想要执行的操作需要私钥(例如签名),我必须实现 crypto.signer 接口才能“访问私钥” 。这是这个实现。

type rsasigner struct {
    privatekey p11.privatekey
    publickey  *rsa.publickey
}

func (s rsasigner) public() crypto.publickey {
    return s.publickey
}

func (s rsasigner) sign(_ io.reader, digest []byte, _ crypto.signeropts) ([]byte, error) {
    return s.privatekey.sign(pkcs11.mechanism{mechanism: pkcs11.ckm_sha512_rsa_pkcs}, digest)
}

func newrsasigner(privatekey p11.privatekey) (*rsasigner, error) {
    var (
        modulus, publicexponent []byte
        err                     error
    )

    // retrieve modulus n from the private key
    // reminder: n = p * q
    modulus, err = p11.object(privatekey).attribute(pkcs11.cka_modulus)
    if err != nil {
        return nil, err
    }

    // retrieve public exponent (e: "always" 65537) from the private key
    // reminder: φ(n) = (p - 1) * (q - 1), e such that 1 < e < φ(n) and e and φ(n) are co prime
    publicexponent, err = p11.object(privatekey).attribute(pkcs11.cka_public_exponent)
    if err != nil {
        return nil, err
    }

    // public key is (e, n)
    publickey := &rsa.publickey{
        n: new(big.int).setbytes(modulus),
        e: int(big.newint(0).setbytes(publicexponent).uint64()),
    }

    return &rsasigner{privatekey: privatekey, publickey: publickey}, nil
}

这个实现有效。例如,要创建 csr,createcertificaterequest 函数需要私钥来签署 csr(priv any 参数),这是我提供 rsasigner 实例的地方。

createcertificate函数有些类似,参数pub是要生成的证书的公钥,priv是签名者的私钥。

在下面的代码中,我尝试生成自签名的x.509证书,因此根据api,templateparent参数是相同的。

func (t *token) x509(id, objecttype, output string) ([]time.duration, error) {
    startfunction := time.now()

    var (
        keytype            int
        privatekeytemplate []*pkcs11.attribute
        privatekeyobject   p11.object
        err                error
        timings            []time.duration
        signer             *rsasigner
        cert               []byte
        file               *os.file
        writtenbytes       int
    )

    objecttype = strings.tolower(objecttype)

    if objecttype != "rsa" && objecttype != "ec" {
        logger.fatalf("%s: unrecognized type, it can only be equal to rsa or ec", objecttype)
    }

    switch objecttype {
    case "rsa":
        keytype = pkcs11.ckk_rsa
    case "ec":
        keytype = pkcs11.ckk_ec
    }

    // creation of the template to find the private key based on the given id (pkcs #11 attribute cka_id)
    privatekeytemplate = []*pkcs11.attribute{
        pkcs11.newattribute(pkcs11.cka_key_type, keytype),
        pkcs11.newattribute(pkcs11.cka_class, pkcs11.cko_private_key),
        pkcs11.newattribute(pkcs11.cka_id, id),
    }

    startfindobject := time.now()
    privatekeyobject, err = t.session.findobject(privatekeytemplate)
    timings = append(timings, time.since(startfindobject))
    if err != nil {
        return nil, err
    }

    // creation of the x.509 certificate template
    certtemplate := &x509.certificate{
        serialnumber: big.newint(2023),
        subject: pkix.name{
            commonname: "test",
        },
        signaturealgorithm: x509.sha512withrsa,
        notbefore:          time.now(),
        notafter:           time.now().adddate(1, 0, 0),
    }

    // instantiate the rsasigner with the found private key object
    signer, err = newrsasigner(p11.privatekey(privatekeyobject))
    if err != nil {
        return nil, err
    }

    startcreatecert := time.now()
    cert, err = x509.createcertificate(rand.reader, certtemplate, certtemplate, signer.publickey, signer)
    timings = append(timings, time.since(startcreatecert))
    if err != nil {
        return nil, err
    }

    file, err = os.create(output)
    if err != nil {
        return nil, err
    }

    writtenbytes, err = file.write(cert)
    if err != nil {
        return nil, err
    }

    logger.printf("wrote %d bytes in %s", writtenbytes, output)

    return append(timings, time.since(startfunction)), nil
}

无论密钥类型(rsa 或 ec)如何,此函数都会返回以下错误。

FATA[2022-12-22 10:48:50] x509: signature over certificate returned by signer is invalid: crypto/rsa: verification error

如果 crypto.signer 实现未正确完成,则会返回此错误。

我实现了 crypto.signer 来尝试使用椭圆曲线上的密钥对执行相同的操作,但错误是相同的。

我还在 sign 函数中尝试了不同的哈希算法,但它没有改变任何东西。

该错误似乎来自 crypto.signer 的实现,尽管它可以用于生成 csr。

解决方法

尽管我几个月前就已经找到了这个问题的解决方案,但我从未花时间分享答案,但是,现在是时候了。

当我们直接通过 pkcs #11 进行签名时,我们需要通过使用此处引用的 digestinfo 值手动为哈希添加前缀来管理哈希前缀:https://www.rfc-editor.org/rfc/rfc3447#page-43

更准确地说,对于 rsassa-pkcs1-v1_5 签名,实际签名函数的输入是 asn.1 der 编码的结构。 pkcs #11 具有特定于哈希的机制(例如ckm_sha256_rsa_pkcs),它们知道如何生成该结构,但它们都假设数据未经过哈希处理,而加密货币的情况并非如此。 signer 接口,因此我们必须使用通用的 cka_rsa_pkcs 机制,该机制仅执行原始签名操作。这意味着我们必须自己生成 asn.1 结构,只需为我们可能想要使用的所有哈希值提供正确的前缀即可做到这一点。

借助crypto.signeropts类型的opts参数,我们可以在以下情况下检索crypto.hash类型的哈希函数的标识符:调用 sign() 函数,并应用正确的前缀。

type signer struct {
    prikey p11.privatekey
    pubkey *rsa.publickey
}

var hashprefixes = map[crypto.hash][]byte{
    crypto.sha256: {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20},
    crypto.sha384: {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30},
    crypto.sha512: {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40},
}

func (s signer) public() crypto.publickey {
    return s.pubkey
}

func (s signer) sign(_ io.reader, digest []byte, opts crypto.signeropts) ([]byte, error) {
    return s.prikey.sign(*pkcs11.newmechanism(pkcs11.ckm_rsa_pkcs, nil), append(hashprefixes[opts.hashfunc()], digest...))
}

func newsigner(key p11.privatekey) (*signer, error) {
    // retrieve modulus n from the private key
    // reminder: n = p * q
    modulus, err := p11.object(key).attribute(pkcs11.cka_modulus)
    if err != nil {
        return nil, err
    }

    var pubexp []byte
    // retrieve public exponent (e: "always" 65537) from the private key
    // reminder: φ(n) = (p - 1) * (q - 1), e such that 1 < e < φ(n) and e and φ(n) are co prime
    pubexp, err = p11.object(key).attribute(pkcs11.cka_public_exponent)
    if err != nil {
        return nil, err
    }

    // public key is (e, n)
    pubkey := &rsa.publickey{
        n: new(big.int).setbytes(modulus),
        e: int(new(big.int).setbytes(pubexp).uint64()),
    }

    return &signer{prikey: key, pubkey: pubkey}, nil
}

它就像一个魅力。不过,还有更好的事情要做。

ckm_rsa_pkcs机制提供rsassa-pkcs1-v1_5类型的签名。我留给感兴趣的读者自己研究这个旧的签名方案,该方案不应再在新产品/软件中使用。

确实,建议使用ckm_rsa_pkcs_pss机制,它提供rsassa-pss类型的签名。

从这个原则出发,这是我现在使用的实现。

type Signer struct {
    priKey p11.PrivateKey
    pubKey *rsa.PublicKey
}

var sigAlg = map[crypto.Hash]uint{
    crypto.SHA256: pkcs11.CKM_SHA256_RSA_PKCS_PSS,
    crypto.SHA384: pkcs11.CKM_SHA384_RSA_PKCS_PSS,
    crypto.SHA512: pkcs11.CKM_SHA512_RSA_PKCS_PSS,
}

var mgf = map[crypto.Hash]uint{
    crypto.SHA256: pkcs11.CKG_MGF1_SHA256,
    crypto.SHA384: pkcs11.CKG_MGF1_SHA384,
    crypto.SHA512: pkcs11.CKG_MGF1_SHA512,
}

func (s Signer) Public() crypto.PublicKey {
    return s.pubKey
}

func (s Signer) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
    return s.priKey.Sign(*pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_PSS, pkcs11.NewPSSParams(sigAlg[opts.HashFunc()], mgf[opts.HashFunc()], uint(opts.HashFunc().Size()))), digest)
}

func NewSigner(key p11.PrivateKey) (*Signer, error) {
    // Retrieve modulus n from the private key
    // Reminder: n = p * q
    modulus, err := p11.Object(key).Attribute(pkcs11.CKA_MODULUS)
    if err != nil {
        return nil, err
    }

    var pubExp []byte
    // Retrieve public exponent (e: "always" 65537) from the private key
    // Reminder: φ(n) = (p - 1) * (q - 1), e such that 1 < e < φ(n) and e and φ(n) are co prime
    pubExp, err = p11.Object(key).Attribute(pkcs11.CKA_PUBLIC_EXPONENT)
    if err != nil {
        return nil, err
    }

    // Public key is (e, n)
    pubKey := &rsa.PublicKey{
        N: new(big.Int).SetBytes(modulus),
        E: int(new(big.Int).SetBytes(pubExp).Uint64()),
    }

    return &Signer{priKey: key, pubKey: pubKey}, nil
}

因此不再需要前缀,但是需要哈希算法标识符和要使用的签名算法以及要使用的mgf之间的对应关系。

最后,在go中,使用的签名算法不再是x509.sha256withrsa、x509.sha384withrsax509.sha512withrsa,而是 sha256withrsapsssha384withrsapsssha512withrsapss

签约愉快。

以上是无法使用自定义 crypto.Signer 实现生成 X.509 证书的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文转载于:stackoverflow.com。如有侵权,请联系admin@php.cn删除