Home  >  Article  >  Backend Development  >  When signing a certificate, the authorization key identifier is copied to the SKID

When signing a certificate, the authorization key identifier is copied to the SKID

PHPz
PHPzforward
2024-02-09 13:27:32869browse

签署证书时,授权密钥标识符被复制到 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.

Question content

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:

  • Calling in pythonx509v3_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".

Workaround

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!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete