首頁 >後端開發 >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刪除