Home >Backend Development >Golang >Unable to generate X.509 certificate using custom crypto.Signer implementation
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.
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 }
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.
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 }
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
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.
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 }
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 }
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!