无法使用自定义 crypto.Signer 进行 X.509 证书生成
来源:stackoverflow
时间:2024-02-06 09:27:21 260浏览 收藏
Golang不知道大家是否熟悉?今天我将给大家介绍《无法使用自定义 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,template和parent参数是相同的。
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.sha384withrsa 或 x509.sha512withrsa,而是 sha256withrsapss、sha384withrsapss 和sha512withrsapss。
签约愉快。
文中关于的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《无法使用自定义 crypto.Signer 进行 X.509 证书生成》文章吧,也可关注golang学习网公众号了解相关技术文章。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
139 收藏
-
204 收藏
-
325 收藏
-
478 收藏
-
486 收藏
-
439 收藏
-
357 收藏
-
352 收藏
-
101 收藏
-
440 收藏
-
212 收藏
-
143 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习