Unable to generate X.509 certificate using custom crypto.Signer implementation

PHPz
Release: 2024-02-10 20:42:07
forward
793 people have browsed it

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

php editor Youzi is here to introduce to you a problem about generating X.509 certificates. Sometimes when using a custom crypto.Signer implementation to generate a certificate, you may encounter an unusable problem. This issue can leave developers confused as to how to resolve it. In this article, we'll explore the cause of this problem and provide some solutions to help developers successfully generate their own X.509 certificates.

Question content

I am trying to generate an x.509 certificate based on an rsa key pair stored in hsm. I use this pkcs #11 implementation to communicate with my hsm.

Since my crypto objects are stored in the latter, if the operation I want to perform requires the private key (such as signing), I have to implement the crypto.signer interface to "access the private key". This is the implementation.

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
}
Copy after login

This implementation works. For example, to create a csr, the createcertificaterequest function requires the private key to sign the csr (priv any parameter), which is where I provide the rsasigner instance.

The

createcertificate function is somewhat similar. The parameter pub is the public key of the certificate to be generated, and priv is the private key of the signer.

In the code below, I try to generate a self-signed x.509 certificate, so the template and parent parameters are the same according to the 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
}
Copy after login

Regardless of the key type (rsa or ec), this function returns the following error.

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

This error will be returned if the crypto.signer implementation is not completed correctly.

I implemented crypto.signer to try to do the same thing using a key pair on an elliptic curve, but the error is the same.

I also tried different hashing algorithms in the sign function but it didn't change anything.

The error seems to come from the implementation of crypto.signer, although it can be used to generate csr.

SOLUTION

Although I found the solution to this problem months ago, I never took the time to share the answer, however, now is the time.

When we sign directly via pkcs #11 we need to manage the hash prefix by manually prefixing the hash with the digestinfo value referenced here: https://www .rfc-editor.org/rfc/rfc3447#page-43.

More precisely, for the rsassa-pkcs1-v1_5 signature, the input to the actual signing function is an asn.1 der encoded structure. pkcs #11 have hash-specific mechanisms (e.g. ckm_sha256_rsa_pkcs) that know how to generate the structure, but they all assume that the data is not hashed, which is not the case with cryptocurrencies. signer interface, so we must use the generic cka_rsa_pkcs mechanism, which only performs raw signing operations. This means we have to generate the asn.1 structure ourselves, which we can do by simply providing the correct prefix for all the hashes we might want to use.

With the help of the opts parameter of type crypto.signeropts we can retrieve the identifier of a hash function of type crypto.hash when: calling sign() function and apply the correct prefix.

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
}
Copy after login

It works like a charm. There are better things to do, though.

ckm_rsa_pkcs mechanism provides rsassa-pkcs1-v1_5 type of signature. I leave it to interested readers to investigate this old signature scheme on their own, which should no longer be used in new products/software.

Indeed, it is recommended to use the ckm_rsa_pkcs_pss mechanism, which provides rsassa-pss type signatures.

Starting from this principle, this is the implementation I use now.

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
}
Copy after login

So the prefix is ​​no longer needed, but the correspondence between the hash algorithm identifier and the signature algorithm to be used and the mgf to be used are required.

Finally, in go, the signature algorithm used is no longer x509.sha256withrsa, x509.sha384withrsa or x509.sha512withrsa, but sha256withrsapss, sha384withrsapss and sha512withrsapss.

Happy signing.

The above is the detailed content of Unable to generate X.509 certificate using custom crypto.Signer implementation. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:stackoverflow.com
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!