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

无法使用自定义 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。如有侵权,请联系admin@php.cn删除
去编码/二进制包:实践示例去编码/二进制包:实践示例May 10, 2025 am 12:16 AM

theEncoding/binarypackageingoisessential forhandlingbinarydata,offeringFunctionStoreadAndWritedAtainBig-Endianandlittle-endianFormats.1)IT'SidealFornetwork-work-workprotocels,enableSeringSeringSerializationalializationalialization andDeSerialization andDeSerializationOfStructuredDatalizedDataliakePackackEtheadErloth

GO BYTES软件包:您需要了解字节片的基本功能GO BYTES软件包:您需要了解字节片的基本功能May 10, 2025 am 12:11 AM

theessentionfunctionsingo'sbytespackageThatyOuneedToknoware:1)字节.indexforsearchingwithinbyteslices,2)bytes.splitforparsing数据,3)字节。joinforConcatenatingslices,4)bytes.containsforcheckingsubslicepresence和5)bytes.replaceallfordatatatatransformatio

GO中'字符串”包的替代方案是什么?GO中'字符串”包的替代方案是什么?May 10, 2025 am 12:09 AM

Gooffersalternativestothestringspackageforstringmanipulation:1)Theregexppackageforcomplexpatternmatching,2)Thestrconvpackagefornumericconversions,and3)Externallibrarieslikestrutilforspecializedoperations.Theseoptionscatertodifferentneeds,enhancingyou

GO编码/二进制软件包:处理不同的数据类型GO编码/二进制软件包:处理不同的数据类型May 10, 2025 am 12:09 AM

效率地使用/binarypackageforhandlingvariousdatatypes,lofterTheSesteps:1)指定bytedorder(例如,binary.littleendian)for -compatibility.2)Usepututuint32/uint322222222222forintegerSandfloat32bits/floatt32bits/floatth322222222frollombitsss.3222frollombitss.3)

掌握go bytes:深入研究'字节”软件包掌握go bytes:深入研究'字节”软件包May 10, 2025 am 12:09 AM

掌握bytes包的原因是它能显着提高处理字节切片的效率和性能。 1)bytes包提供了强大的工具,如bytes.Contains用于搜索字节序列,2)bytes.Buffer类型适用于增量构建字节切片,3)了解bytes包的使用陷阱和性能优化策略,如重用bytes.Buffer实例,可以避免常见错误并提升效率。

学习GO String操纵:使用'字符串”软件包学习GO String操纵:使用'字符串”软件包May 09, 2025 am 12:07 AM

Go的"strings"包提供了丰富的功能,使字符串操作高效且简单。1)使用strings.Contains()检查子串。2)strings.Split()可用于解析数据,但需谨慎使用以避免性能问题。3)strings.Join()适用于格式化字符串,但对小数据集,循环使用 =更有效。4)对于大字符串,使用strings.Builder构建字符串更高效。

GO:使用标准'字符串”包的字符串操纵GO:使用标准'字符串”包的字符串操纵May 09, 2025 am 12:07 AM

Go语言使用"strings"包进行字符串操作。1)拼接字符串使用strings.Join函数。2)查找子串使用strings.Contains函数。3)替换字符串使用strings.Replace函数,这些函数高效且易用,适用于各种字符串处理任务。

使用GO的'字节”软件包掌握字节切片操作:实用指南使用GO的'字节”软件包掌握字节切片操作:实用指南May 09, 2025 am 12:02 AM

资助bytespackageingoisesential foreffited byteSemanipulation,uperingFunctionsLikeContains,index,andReplaceForsearchingangingAndModifyingBinaryData.itenHancesperformanceNandCoderAceAnibility,MakeitiTavitalToolToolToolToolToolToolToolToolToolForhandLingBinaryData,networkProtocols,networkProtocoLss,networkProtocols,andetFilei

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境