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,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。
簽約愉快。






