Home > OS >  When signing a certificate, Authority Key Identifier is copied to SKID
When signing a certificate, Authority Key Identifier is copied to SKID

Time:12-01

I am trying to sign a certificate with CSR and spacemonkeygo/openssl wrapper.
The console openssl command to sign a 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

enter image description here
As can be seen from the sceenshot, SKID and Issuer's keyid are different.

However, my code in Go provides an erroneous certificate, where SKID contains the exact value of Issuing certificate's keyid. It results in copying invalid values for "issuer" in "Authority Key Identifier": since SKID is the same as KeyId of the Issuer, it "considers" the certificate to be 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
}

enter image description here
(Generated is the right one)

If I don't invoke SetIssuer, SKID is newly generated, but the resulting certificate is still shown as "invalid".

What am I doing wrong in the code?


UPDATE: I have compared implementations for 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 (omitted 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 in API: python's version accepts subject and issuer for overloading as arguments. Go's version does not.
Difference in implemenmtation is the following:

  • X509V3_EXT_nconf called in Python
  • X509V3_EXT_conf_nid called in Go Both of the functions can be found on github.

I guess it is impossible to add SKID extension when signing with CA using openspacemonkey/go-openssl.
It seems that the only way would be to use C bindings manually and "do as Python does".

CodePudding user response:

I've implemented a hacky workaround to add SKID and authorityKeyIdentifier. The resulting certificate is valid. However, since x *C.X509 members of Certificate struct are unexported, the only way to access them is via unsafe pointers and casting.
This is not a recommended way, but a way to go until spacemonkey/go is updated (I doubt it 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
}
  • Related