当签署证书时,将授权密钥标识符复制到SKID。

huangapple go评论69阅读模式
英文:

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

问题

我正在尝试使用CSR和spacemonkeygo/openssl包来签发证书。

控制台中的openssl命令用于签发证书,结果符合预期,我得到了有效的证书。

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

从屏幕截图中可以看出,SKID和颁发者的keyid是不同的。

然而,我的Go代码提供了一个错误的证书,其中SKID包含了颁发证书的keyid的确切值。这导致在“Authority Key Identifier”中将无效的值复制给“issuer”:因为SKID与颁发者的KeyId相同,它“认为”证书是自签发的。

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
}

(生成的是正确的)

如果我不调用SetIssuer,SKID会被新生成,但生成的证书仍然被显示为“无效”。

在代码中我做错了什么?


更新:
我比较了两个包的扩展添加实现:spacemonkey/goPyOpenSSL

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(省略了一些注释):

# 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)

明显的区别在于API:Python版本接受subjectissuer作为参数进行重载,而Go版本则不接受。

实现上的区别如下:

  • Go中调用了X509V3_EXT_conf_nid
  • Python中调用了X509V3_EXT_nconf

这两个函数都可以在GitHub上找到。

**我猜在使用openspacemonkey/go-openssl签名CA时,无法添加SKID扩展。**似乎唯一的方法是手动使用C绑定并“按照Python的方式”进行操作。

英文:

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

当签署证书时,将授权密钥标识符复制到SKID。
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 (
&quot;github.com/spacemonkeygo/openssl&quot;
&quot;math/big&quot;
&quot;os&quot;
&quot;time&quot;
)
func main() {
crtFilePath := FilePath(&quot;ca/root.crt&quot;)
keyFilePath := FilePath(&quot;ca/root.key&quot;)
certCA, privateKeyCA, err := getRootCA(PathCert(crtFilePath), PathKey(keyFilePath))
if err != nil {
panic(err)
}
serialNumber := big.NewInt(10101)
country := &quot;RU&quot;
organization := &quot;Some Organization&quot;
commonName := &quot;CommonName&quot;
expirationDate := time.Now().AddDate(1, 0, 0)
certInfo := &amp;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,
&quot;critical,CA:FALSE&quot;)
if err != nil {
panic(err)
}
err = newCert.AddExtension(openssl.NID_subject_key_identifier,
&quot;hash&quot;)
if err != nil {
panic(err)
}
err = newCert.AddExtension(openssl.NID_authority_key_identifier,
&quot;keyid:always,issuer:always&quot;)
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(&quot;generated.crt&quot;, pemBytes, os.FileMode(0644))
if err != nil {
panic(err)
}
print(&quot;Done&quot;)
}
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
}

当签署证书时,将授权密钥标识符复制到SKID。
(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(&amp;ctx, c.x, issuer.x, nil, nil, 0)
ex := C.X509V3_EXT_conf_nid(nil, &amp;ctx, C.int(nid), C.CString(value))
if ex == nil {
return errors.New(&quot;failed to create x509v3 extension&quot;)
}
defer C.X509_EXTENSION_free(ex)
if C.X509_add_ext(c.x, ex, -1) &lt;= 0 {
return errors.New(&quot;failed to add x509v3 extension&quot;)
}
return nil
}

Python (omitted some comments):

# X509Extension::__init__
def __init__(
self,
type_name: bytes,
critical: bool,
value: bytes,
subject: Optional[&quot;X509&quot;] = None,
issuer: Optional[&quot;X509&quot;] = None,
) -&gt; None:
ctx = _ffi.new(&quot;X509V3_CTX*&quot;)
# 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(&quot;issuer must be an X509 instance&quot;)
ctx.issuer_cert = issuer._x509
if subject is not None:
if not isinstance(subject, X509):
raise TypeError(&quot;subject must be an X509 instance&quot;)
ctx.subject_cert = subject._x509
if critical:
# There are other OpenSSL APIs which would let us pass in critical
# separately, but they&#39;re harder to use, and since value is already
# a pile of crappy junk smuggling a ton of utterly important
# structured data, what&#39;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&quot;critical,&quot; + 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".

答案1

得分: 0

我已经实现了一个笨拙的解决方法来添加SKID和authorityKeyIdentifier。生成的证书是有效的。然而,由于x *C.X509结构体的Certificate成员是未导出的,唯一访问它们的方式是通过不安全指针和类型转换。

这不是推荐的方式,但在spacemonkey/go更新之前是一种可行的方式(我怀疑更新会很快发生)。

func addAuthorityKeyIdentifier(c *openssl.Certificate) error {
	var ctx C.X509V3_CTX
	C.X509V3_set_ctx(&ctx, nil, nil, nil, nil, 0)

	// 这很丑陋且非常不安全!
	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)

	// 这很丑陋且非常不安全!
	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)

	// 将自身添加为主题
	addResult := C.X509_add_ext(cx509, extension, -1)
	if addResult == 0 {
		return errors.New("failed to set 'subjectKeyIdentifier' extension")
	}

	return nil
}

希望对你有帮助!

英文:

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(&amp;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(&quot;authorityKeyIdentifier&quot;)
defer C.free(unsafe.Pointer(cExtName))
cExtValue := C.CString(&quot;keyid:always,issuer:always&quot;)
defer C.free(unsafe.Pointer(cExtValue))
extension := C.X509V3_EXT_nconf(nil, &amp;ctx, cExtName, cExtValue)
if extension == nil {
return errors.New(&quot;failed to set &#39;authorityKeyIdentifier&#39; extension&quot;)
}
defer C.X509_EXTENSION_free(extension)
addResult := C.X509_add_ext(cx509, extension, -1)
if addResult == 0 {
return errors.New(&quot;failed to set &#39;authorityKeyIdentifier&#39; extension&quot;)
}
return nil
}
func addSKIDExtension(c *openssl.Certificate) error {
var ctx C.X509V3_CTX
C.X509V3_set_ctx(&amp;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(&quot;subjectKeyIdentifier&quot;)
defer C.free(unsafe.Pointer(cExtName))
cExtValue := C.CString(&quot;hash&quot;)
defer C.free(unsafe.Pointer(cExtValue))
extension := C.X509V3_EXT_nconf(nil, &amp;ctx, cExtName, cExtValue)
if extension == nil {
return errors.New(&quot;failed to set &#39;subjectKeyIdentifier&#39; extension&quot;)
}
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(&quot;failed to set &#39;subjectKeyIdentifier&#39; extension&quot;)
}
return nil
}

huangapple
  • 本文由 发表于 2022年11月28日 19:12:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/74599727.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定