Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Tidak dapat menjana sijil X.509 menggunakan pelaksanaan crypto.Signer tersuai

Tidak dapat menjana sijil X.509 menggunakan pelaksanaan crypto.Signer tersuai

PHPz
PHPzke hadapan
2024-02-10 20:42:07793semak imbas

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

editor php Youzi di sini untuk memperkenalkan kepada anda masalah tentang penjanaan sijil X.509. Kadangkala apabila menggunakan pelaksanaan crypto.Signer tersuai untuk menjana sijil, anda mungkin menghadapi masalah yang tidak boleh digunakan. Isu ini boleh menyebabkan pembangun keliru tentang cara menyelesaikannya. Dalam artikel ini, kami akan meneroka punca masalah ini dan menyediakan beberapa penyelesaian untuk membantu pembangun berjaya menjana sijil X.509 mereka sendiri.

Kandungan soalan

Saya cuba menjana sijil x.509 berdasarkan pasangan kunci rsa yang disimpan dalam hsm. Saya menggunakan pelaksanaan pkcs #11 ini untuk berkomunikasi dengan hsm saya.

Memandangkan objek crypto saya disimpan dalam yang kedua, jika operasi yang saya ingin lakukan memerlukan kunci peribadi (seperti menandatangani), saya perlu melaksanakan antara muka crypto.signer untuk "mengakses kunci peribadi". Inilah pelaksanaannya.

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
}

Pelaksanaan ini berfungsi. Contohnya, untuk mencipta csr, fungsi createcertificaterequest memerlukan kunci peribadi untuk menandatangani csr (di mana contoh priv any 参数),这是我提供 rsasigner berada.

Fungsi

createcertificate agak serupa, parameter pub是要生成的证书的公钥,priv ialah kunci peribadi penandatangan.

Dalam kod di bawah, saya cuba menjana sijil x.509 yang ditandatangani sendiri, jadi templateparentparameter adalah sama mengikut api.

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
}

Tidak kira jenis kunci (rsa atau ec), fungsi ini mengembalikan ralat berikut.

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

Ralat ini akan dikembalikan jika pelaksanaan crypto.signer tidak diselesaikan dengan betul.

Saya melaksanakan crypto.signer untuk mencuba melakukan perkara yang sama menggunakan pasangan kunci pada lengkung elips, tetapi ralatnya adalah sama.

Saya juga mencuba algoritma pencincangan yang berbeza dalam fungsi sign tetapi ia tidak mengubah apa-apa.

Ralat nampaknya datang dari pelaksanaan crypto.signer walaupun ia boleh digunakan untuk menjana csr.

Penyelesaian

Walaupun saya telah menemui penyelesaian untuk masalah ini beberapa bulan yang lalu, saya tidak pernah meluangkan masa untuk berkongsi jawapannya, namun, inilah masanya.

Apabila kami menandatangani terus melalui pkcs #11, kami perlu mengurus awalan cincang dengan memberi awalan cincang secara manual menggunakan nilai digestinfo yang dirujuk di sini: https://www.rfc-editor.org/rfc /rfc3447#page-43 .

Lebih tepat lagi, untuk tandatangan rsassa-pkcs1-v1_5, input kepada fungsi tandatangan sebenar ialah struktur yang dikodkan asn.1 der. pkcs #11 mempunyai mekanisme khusus cincang (cth. ckm_sha256_rsa_pkcs) yang tahu cara menjana struktur, tetapi mereka semua menganggap bahawa data tidak dicincang, yang tidak berlaku dengan mata wang kripto. antara muka penandatangan, jadi kita perlu menggunakan mekanisme cka_rsa_pkcs generik, yang hanya menjalankan operasi tandatangan mentah. Ini bermakna kita perlu menjana sendiri struktur asn.1, yang boleh kita lakukan dengan hanya menyediakan awalan yang betul untuk semua cincang yang mungkin ingin kita gunakan.

Dengan bantuan parameter opts jenis crypto.signeropts kita boleh mendapatkan semula pengecam fungsi cincang jenis crypto.hash apabila fungsi sign() dipanggil, dengan awalan yang betul digunakan.

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
}

Ia berfungsi seperti azimat. Terdapat perkara yang lebih baik untuk dilakukan, walaupun.

Mekanisme

ckm_rsa_pkcs menyediakan tandatangan jenis rsassa-pkcs1-v1_5. Saya serahkan kepada pembaca yang berminat untuk menyiasat sendiri skim tandatangan lama ini, yang sepatutnya tidak lagi digunakan dalam produk/perisian baharu.

Sememangnya, adalah disyorkan untuk menggunakan mekanisme ckm_rsa_pkcs_pss, yang menyediakan tandatangan jenis rsassa-pss.

Bermula dari prinsip ini, inilah perlaksanaan yang saya gunakan sekarang.

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
}

Jadi awalan tidak lagi diperlukan, tetapi korespondensi antara pengecam algoritma cincang dan algoritma tandatangan yang akan digunakan dan mgf yang akan digunakan diperlukan.

Akhirnya, dalam proses, algoritma tandatangan yang digunakan bukan lagi x509.sha256withrsa, x509.sha384withrsa atau x509.sha512withrsasha25, tetapi .8pssha2 s dan sha512withrsapss.

Selamat menandatangani.

Atas ialah kandungan terperinci Tidak dapat menjana sijil X.509 menggunakan pelaksanaan crypto.Signer tersuai. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:stackoverflow.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam