Home >Backend Development >Golang >When signing a certificate, the authorization key identifier is copied to the SKID
php editor Strawberry pointed out when introducing the signing certificate that the authorization key identifier (SKID) plays an important role in the signing process. When a certificate is signed, the SKID is copied into the certificate and identifies the certificate's authorized key. The existence of this identifier can help ensure the authenticity and legality of the certificate, and also facilitate subsequent certificate verification and management. Copying the SKID is a necessary step when signing a certificate, and it plays an important role in the use and maintenance of the certificate.
I am trying to sign a certificate using csr and the spacemonkeygo/openssl
wrapper.
The console openssl command for signing the certificate works as expected and I get a valid certificate.
openssl x509 -req -days 365 -in cert_client.csr -ca ca/root.crt -cakey ca/root.key -set_serial 10101 -out cert_client.crt -extfile ca/extensions.cnf
As can be seen from the screenshot, the keyid of skid and issuer are different.
However, my code in go provides the wrong certificate, where skid contains the exact value of the keyid that issued the certificate. This causes an invalid value for "Issuer" to be copied in "Authority Key Identifier": since the skid is the same as the issuer's keyid, it "thinks" the certificate is self-issued.
package main import ( "github.com/spacemonkeygo/openssl" "math/big" "os" "time" ) func main() { crtfilepath := filepath("ca/root.crt") keyfilepath := filepath("ca/root.key") certca, privatekeyca, err := getrootca(pathcert(crtfilepath), pathkey(keyfilepath)) if err != nil { panic(err) } serialnumber := big.newint(10101) country := "ru" organization := "some organization" commonname := "commonname" expirationdate := time.now().adddate(1, 0, 0) certinfo := &openssl.certificateinfo{ serial: serialnumber, expires: expirationdate.sub(time.now()), commonname: commonname, // will fail if these are empty or not initialized country: country, organization: organization, } // just for example. publickey is received from csr privatekeycert, err := openssl.generatersakey(2048) if err != nil { panic(err) } newcert, err := openssl.newcertificate(certinfo, openssl.publickey(privatekeycert)) if err != nil { panic(err) } err = newcert.setversion(openssl.x509_v3) if err != nil { panic(err) } // (?) must be called before adding extensions err = newcert.setissuer(certca) if err != nil { panic(err) } err = newcert.addextension(openssl.nid_basic_constraints, "critical,ca:false") if err != nil { panic(err) } err = newcert.addextension(openssl.nid_subject_key_identifier, "hash") if err != nil { panic(err) } err = newcert.addextension(openssl.nid_authority_key_identifier, "keyid:always,issuer:always") if err != nil { panic(err) } err = newcert.sign(privatekeyca, openssl.evp_sha256) if err != nil { panic(err) } pembytes, err := newcert.marshalpem() if err != nil { panic(err) } err = os.writefile("generated.crt", pembytes, os.filemode(0644)) if err != nil { panic(err) } print("done") } type filepath string type pathcert string type pathkey string func getrootca(pathcert pathcert, pathkey pathkey) (*openssl.certificate, openssl.privatekey, error) { capublickeyfile, err := os.readfile(string(pathcert)) if err != nil { return nil, nil, err } certca, err := openssl.loadcertificatefrompem(capublickeyfile) if err != nil { return nil, nil, err } caprivatekeyfile, err := os.readfile(string(pathkey)) if err != nil { return nil, nil, err } privatekeyca, err := openssl.loadprivatekeyfrompem(caprivatekeyfile) if err != nil { return nil, nil, err } return certca, privatekeyca, nil }
(The generated one is correct)
If I don't call setissuer
, the skid is newly generated, but the generated certificate still shows as "invalid".
What am I doing wrong in my code?
renew:
I compared the implementation adding extensions for 2 wrappers: spacemonkey/go
and pyopenssl
.
go:
// add an extension to a certificate. // extension constants are nid_* as found in openssl. func (c *certificate) addextension(nid nid, value string) error { issuer := c if c.issuer != nil { issuer = c.issuer } var ctx c.x509v3_ctx c.x509v3_set_ctx(&ctx, c.x, issuer.x, nil, nil, 0) ex := c.x509v3_ext_conf_nid(nil, &ctx, c.int(nid), c.cstring(value)) if ex == nil { return errors.new("failed to create x509v3 extension") } defer c.x509_extension_free(ex) if c.x509_add_ext(c.x, ex, -1) <= 0 { return errors.new("failed to add x509v3 extension") } return nil }
python (omitting some comments):
# X509Extension::__init__ def __init__( self, type_name: bytes, critical: bool, value: bytes, subject: Optional["X509"] = None, issuer: Optional["X509"] = None, ) -> None: ctx = _ffi.new("X509V3_CTX*") # A context is necessary for any extension which uses the r2i # conversion method. That is, X509V3_EXT_nconf may segfault if passed # a NULL ctx. Start off by initializing most of the fields to NULL. _lib.X509V3_set_ctx(ctx, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL, 0) # We have no configuration database - but perhaps we should (some # extensions may require it). _lib.X509V3_set_ctx_nodb(ctx) # Initialize the subject and issuer, if appropriate. ctx is a local, # and as far as I can tell none of the X509V3_* APIs invoked here steal # any references, so no need to mess with reference counts or # duplicates. if issuer is not None: if not isinstance(issuer, X509): raise TypeError("issuer must be an X509 instance") ctx.issuer_cert = issuer._x509 if subject is not None: if not isinstance(subject, X509): raise TypeError("subject must be an X509 instance") ctx.subject_cert = subject._x509 if critical: # There are other OpenSSL APIs which would let us pass in critical # separately, but they're harder to use, and since value is already # a pile of crappy junk smuggling a ton of utterly important # structured data, what's the point of trying to avoid nasty stuff # with strings? (However, X509V3_EXT_i2d in particular seems like # it would be a better API to invoke. I do not know where to get # the ext_struc it desires for its last parameter, though.) value = b"critical," + value extension = _lib.X509V3_EXT_nconf(_ffi.NULL, ctx, type_name, value) if extension == _ffi.NULL: _raise_current_error() self._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free)
The obvious difference is the API: the python version accepts subject
and issuer
as parameters for overloading. The go version does not.
The implementation differences are as follows:
x509v3_ext_nconf
x509v3_ext_conf_nid
Called in go
Both functions can be found on github. I think it is not possible to add the skid extension when using openspacemonkey/go-openssl with ca signing.
It seems the only way is to manually use the c bindings and "do it like python".
I implemented a clever workaround to add skid and authoritykeyidentifier. The generated certificate is valid. However, since the x *c.x509
members of the certificate
structure are not exported, the only way to access them is through unsafe pointers and casts.
This is not a recommended approach, but is a way to go until spacemonkey/go
is updated (which I suspect will happen soon).
func addAuthorityKeyIdentifier(c *openssl.Certificate) error { var ctx C.X509V3_CTX C.X509V3_set_ctx(&ctx, nil, nil, nil, nil, 0) // this is ugly and very unsafe! cx509 := *(**C.X509)(unsafe.Pointer(c)) cx509Issuer := cx509 if c.Issuer != nil { cx509Issuer = *(**C.X509)(unsafe.Pointer(c.Issuer)) } ctx.issuer_cert = cx509Issuer cExtName := C.CString("authorityKeyIdentifier") defer C.free(unsafe.Pointer(cExtName)) cExtValue := C.CString("keyid:always,issuer:always") defer C.free(unsafe.Pointer(cExtValue)) extension := C.X509V3_EXT_nconf(nil, &ctx, cExtName, cExtValue) if extension == nil { return errors.New("failed to set 'authorityKeyIdentifier' extension") } defer C.X509_EXTENSION_free(extension) addResult := C.X509_add_ext(cx509, extension, -1) if addResult == 0 { return errors.New("failed to set 'authorityKeyIdentifier' extension") } return nil } func addSKIDExtension(c *openssl.Certificate) error { var ctx C.X509V3_CTX C.X509V3_set_ctx(&ctx, nil, nil, nil, nil, 0) // this is ugly and very unsafe! cx509 := *(**C.X509)(unsafe.Pointer(c)) _ = cx509 ctx.subject_cert = cx509 _ = ctx cExtName := C.CString("subjectKeyIdentifier") defer C.free(unsafe.Pointer(cExtName)) cExtValue := C.CString("hash") defer C.free(unsafe.Pointer(cExtValue)) extension := C.X509V3_EXT_nconf(nil, &ctx, cExtName, cExtValue) if extension == nil { return errors.New("failed to set 'subjectKeyIdentifier' extension") } defer C.X509_EXTENSION_free(extension) // adding itself as a subject addResult := C.X509_add_ext(cx509, extension, -1) if addResult == 0 { return errors.New("failed to set 'subjectKeyIdentifier' extension") } return nil }
The above is the detailed content of When signing a certificate, the authorization key identifier is copied to the SKID. For more information, please follow other related articles on the PHP Chinese website!