英文:
How to add DirName and serial to X509v3 Authority Key Identifier
问题
我正在尝试使用OpenSSL和Go代码生成客户端证书。我有一个使用OpenSSL生成带有所需扩展的证书的脚本,并且我希望使用Go代码实现相同的结果。
使用OpenSSL
options.ext
OpenSSL使用的options.ext文件包含以下扩展:
basicConstraints=CA:FALSE
authorityKeyIdentifier=keyid,issuer
subjectKeyIdentifier=hash
keyUsage=digitalSignature
extendedKeyUsage=clientAuth
generate-client-cert.sh
我目前拥有的OpenSSL脚本如下:
openssl req \
-newkey rsa:2048 \
-keyout cert.crt \
-out cert.csr \
-nodes \
-sha256
openssl x509 \
-req \
-CA ca.crt \
-CAkey ca.key \
-in cert.csr \
-out cert.crt \
-days 365 \
-CAcreateserial \
-extfile options.ext \
-sha256
生成证书后,可以使用以下命令查看其详细信息:
openssl x509 -in cert.crt -text -noout
生成的证书具有以下结构:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
xx:xx:xx:xx:xx:xx:xx:xx
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=xxx
Validity
Not Before: Jan 1 00:00:00 2023 GMT
Not After : Jan 1 00:00:00 2024 GMT
Subject: CN=xxx
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
...
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Authority Key Identifier:
DirName:CN=xxx
serial:xx:xx:xx:xx:xx:xx:xx:xx
X509v3 Subject Key Identifier:
...
X509v3 Key Usage:
Digital Signature
X509v3 Extended Key Usage:
TLS Web Client Authentication
Signature Algorithm: sha256WithRSAEncryption
它应该是这样的:
X509v3 Authority Key Identifier:
DirName:CN=xxx
serial:xx:xx:xx:xx:xx:xx:xx:xx
Go代码
在我的Go代码中,我使用x509包生成证书。但是,我不确定如何设置X509v3 Authority Key Identifier扩展。以下是我Go代码的相关部分:
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"os"
"time"
)
...
var caCertificate *x509.Certificate
var caPrivateKey *rsa.PrivateKey
var authorityKeyIdentifierValue []byte // 如何编写这个?
template := &x509.Certificate{
Subject: pkix.Name{
CommonName: "xxx",
},
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{2, 5, 29, 35},
Value: authorityKeyIdentifierValue,
},
},
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(0, 0, 365),
IsCA: false,
BasicConstraintsValid: true,
}
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
// 错误处理
}
certificateBytes, err := x509.CreateCertificate(rand.Reader, template, caCertificate, &privateKey.PublicKey, caPrivateKey)
if err != nil {
// 错误处理
}
// 输出
如何将DirName和serial添加到X509v3 Authority Key Identifier中?
相关链接
当我尝试这样做时:
var caPublicKeyBytes []byte
publicKeyHash := (sha1.Sum(caPublicKeyBytes))[:]
var dirName string
authorityKeyIdentifierValue := []byte{0x30, len(publicKeyHash)}
authorityKeyIdentifierValue = append(authorityKeyIdentifierValue, publicKeyHash...)
authorityKeyIdentifierValue = append(authorityKeyIdentifierValue, 0x80, len(dirName))
authorityKeyIdentifierValue = append(authorityKeyIdentifierValue, []byte(dirName)...)
...
结果是:
X509v3 Authority Key Identifier:
0....0...<....).!.r[..F.....".hCN=xxx.....$...D
英文:
I'm trying to generate a client certificate using OpenSSL and Go code. I have an OpenSSL script that generates the certificate with the required extensions, and I want to achieve the same result using Go code.
With OpenSSL
options.ext
The options.ext file used by OpenSSL contains the following extensions:
basicConstraints=CA:FALSE
authorityKeyIdentifier=keyid,issuer
subjectKeyIdentifier=hash
keyUsage=digitalSignature
extendedKeyUsage=clientAuth
generate-client-cert.sh
The OpenSSL script I currently have is as follows:
openssl req \
-newkey rsa:2048 \
-keyout cert.crt \
-out cert.csr \
-nodes \
-sha256
openssl x509 \
-req \
-CA ca.crt \
-CAkey ca.key \
-in cert.csr \
-out cert.crt \
-days 365 \
-CAcreateserial \
-extfile options.ext \
-sha256
After generating the certificate, I can use the following command to view its details:
openssl x509 -in cert.crt -text -noout
The resulting certificate has the following structure:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
xx:xx:xx:xx:xx:xx:xx:xx
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=xxx
Validity
Not Before: Jan 1 00:00:00 2023 GMT
Not After : Jan 1 00:00:00 2024 GMT
Subject: CN=xxx
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
...
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Authority Key Identifier:
DirName:CN=xxx
serial:xx:xx:xx:xx:xx:xx:xx:xx
X509v3 Subject Key Identifier:
...
X509v3 Key Usage:
Digital Signature
X509v3 Extended Key Usage:
TLS Web Client Authentication
Signature Algorithm: sha256WithRSAEncryption
it should look like this:
X509v3 Authority Key Identifier:
DirName:CN=xxx
serial:xx:xx:xx:xx:xx:xx:xx:xx
Go code
In my Go code, I'm using the x509 package to generate the certificate. However, I'm unsure how to set the X509v3 Authority Key Identifier extension. Here's the relevant part of my Go code:
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"os"
"time"
)
...
var caCertificate *x509.Certificate
var caPrivateKey *rsa.PrivateKey
var authorityKeyIdentifierValue []byte // how to write this?
template := &x509.Certificate{
Subject: pkix.Name{
CommonName: "xxx",
},
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{2, 5, 29, 35},
Value: authorityKeyIdentifierValue,
},
},
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(0, 0, 365),
IsCA: false,
BasicConstraintsValid: true,
}
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
// err
}
certificateBytes, err := x509.CreateCertificate(rand.Reader, template, caCertificate, &privateKey.PublicKey, caPrivateKey)
if err != nil {
// err
}
// out
How to add DirName and serial to X509v3 Authority Key Identifier?
Related
- http://oid-info.com/get/2.5.29.35
- https://github.com/golang/go/issues/47096 (the original question)
When I tried this:
var caPublicKeyBytes []byte
publicKeyHash := (sha1.Sum(caPublicKeyBytes))[:]
var dirName string
authorityKeyIdentifierValue := []byte{0x30, len(publicKeyHash)}
authorityKeyIdentifierValue = append(authorityKeyIdentifierValue, publicKeyHash...)
authorityKeyIdentifierValue = append(authorityKeyIdentifierValue, 0x80, len(dirName))
authorityKeyIdentifierValue = append(authorityKeyIdentifierValue, []byte(dirName)...)
...
The result was:
X509v3 Authority Key Identifier:
0....0...<....).!.r[..F.....".hCN=xxx.....$...D
答案1
得分: 1
authorityKeyIdentifierValue
可以使用asn1.Marshal生成。下面的示例根据RFC 5280定义了authKeyId
结构体,并使用该结构体生成了值:
package main
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"fmt"
"math/big"
)
// RFC 5280, A.2. Implicitly Tagged Module, 1988 Syntax
//
// AuthorityKeyIdentifier ::= SEQUENCE {
// keyIdentifier [0] KeyIdentifier OPTIONAL,
// authorityCertIssuer [1] GeneralNames OPTIONAL,
// authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
// -- authorityCertIssuer and authorityCertSerialNumber MUST both
// -- be present or both be absent
type authKeyId struct {
KeyIdentifier []byte `asn1:"optional,tag:0"`
AuthorityCertIssuer generalNames `asn1:"optional,tag:1"`
AuthorityCertSerialNumber *big.Int `asn1:"optional,tag:2"`
}
// RFC 5280, A.2. Implicitly Tagged Module, 1988 Syntax
//
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
//
// GeneralName ::= CHOICE {
// otherName [0] AnotherName,
// rfc822Name [1] IA5String,
// dNSName [2] IA5String,
// x400Address [3] ORAddress,
// directoryName [4] Name,
// ediPartyName [5] EDIPartyName,
// uniformResourceIdentifier [6] IA5String,
// iPAddress [7] OCTET STRING,
// registeredID [8] OBJECT IDENTIFIER }
type generalNames struct {
Name []pkix.RDNSequence `asn1:"tag:4"`
}
func gen(issuer *x509.Certificate) ([]byte, error) {
return asn1.Marshal(authKeyId{
KeyIdentifier: issuer.SubjectKeyId,
AuthorityCertIssuer: generalNames{Name: []pkix.RDNSequence{issuer.Issuer.ToRDNSequence()}},
AuthorityCertSerialNumber: issuer.SerialNumber,
})
}
func main() {
caCert := `-----BEGIN CERTIFICATE-----
MIIBoTCCAUegAwIBAgIQGoCjDJN1Y6rGWEbXW8V8MDAKBggqhkjOPQQDAjAmMQ8w
DQYDVQQKEwZNeSBPcmcxEzARBgNVBAMTCk15IFJvb3QgQ0EwHhcNMjMwNTE2MTQy
NTUwWhcNMjMwNTE3MTUyNTUwWjAmMQ8wDQYDVQQKEwZNeSBPcmcxEzARBgNVBAMT
Ck15IFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARZQz2Ka7Fi6w9/
32SJHTAjrkE+VqYx7hFNmtX1INPBAJNfvONF2SIlh5nQmS50JpNVGIvEhTbFL0A0
dcuruFHno1cwVTAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5Y48DJ96LQWVh3S/aNJ/6SGy/j4w
CgYIKoZIzj0EAwIDSAAwRQIhANQDh6SGZ014wVFdH0ZHbEGhdb2TqXZUJxA7YMo3
80UnAiApZp4wlzqlB+J4fIPnep+Txru01JgFaKsml2yHv3mEWg==
-----END CERTIFICATE-----`
b, _ := pem.Decode([]byte(caCert))
if b == nil {
panic("couldn't decode test certificate")
}
issuer, err := x509.ParseCertificate(b.Bytes)
if err != nil {
panic(err)
}
authorityKeyIdentifierValue, err := gen(issuer)
if err != nil {
panic(err)
}
fmt.Println(hex.EncodeToString(authorityKeyIdentifierValue))
}
十六进制编码的值为:
30548014e58e3c0c9f7a2d05958774bf68d27fe921b2fe3ea12aa4283026310f300d060355040a13064d79204f7267311330110603550403130a4d7920526f6f7420434182101a80a30c937563aac65846d75bc57c30
可以使用诸如ASN.1 JavaScript decoder之类的工具解码十六进制字符串。
以下是相同结果的C#示例:
using System.Security.Cryptography.X509Certificates;
using System.Text;
internal class Program
{
private static void Main(string[] args)
{
var certBytes = Encoding.ASCII.GetBytes(@"-----BEGIN CERTIFICATE-----
MIIBoTCCAUegAwIBAgIQGoCjDJN1Y6rGWEbXW8V8MDAKBggqhkjOPQQDAjAmMQ8w
DQYDVQQKEwZNeSBPcmcxEzARBgNVBAMTCk15IFJvb3QgQ0EwHhcNMjMwNTE2MTQy
NTUwWhcNMjMwNTE3MTUyNTUwWjAmMQ8wDQYDVQQKEwZNeSBPcmcxEzARBgNVBAMT
Ck15IFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARZQz2Ka7Fi6w9/
32SJHTAjrkE+VqYx7hFNmtX1INPBAJNfvONF2SIlh5nQmS50JpNVGIvEhTbFL0A0
dcuruFHno1cwVTAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5Y48DJ96LQWVh3S/aNJ/6SGy/j4w
CgYIKoZIzj0EAwIDSAAwRQIhANQDh6SGZ014wVFdH0ZHbEGhdb2TqXZUJxA7YMo3
80UnAiApZp4wlzqlB+J4fIPnep+Txru01JgFaKsml2yHv3mEWg==
-----END CERTIFICATE-----");
using var issuer = new X509Certificate2(certBytes);
var e = X509AuthorityKeyIdentifierExtension.CreateFromCertificate(issuer, true, true);
Console.WriteLine(ByteArrayToHex(e.RawData));
}
private static string ByteArrayToHex(byte[] bytes)
{
var builder = new StringBuilder(bytes.Length * 2);
for (int i = 0; i < bytes.Length; i++)
{
builder.Append($"{bytes[i]:x2}");
}
return builder.ToString();
}
}
更新:
以下是更新后的gen
函数,包括电子邮件地址:
func gen(issuer *x509.Certificate) ([]byte, error) {
rdnSequence := issuer.Issuer.ToRDNSequence()
if len(issuer.EmailAddresses) > 0 {
oidEmail := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}
emails := make([]pkix.AttributeTypeAndValue, len(issuer.EmailAddresses))
for i, value := range issuer.EmailAddresses {
emails[i].Type = oidEmail
emails[i].Value = value
}
rdnSequence = append(rdnSequence, emails)
}
return asn1.Marshal(authKeyId{
KeyIdentifier: issuer.SubjectKeyId,
AuthorityCertIssuer: generalNames{Name: []pkix.RDNSequence{rdnSequence}},
AuthorityCertSerialNumber: issuer.SerialNumber,
})
}
请注意,该OID已被弃用(参见http://oid-info.com/get/1.2.840.113549.1.9.1)。.NET也不包含它。
英文:
The authorityKeyIdentifierValue
can be generated with asn1.Marshal. The demo below defines the struct authKeyId
according to RFC 5280 and generates the value using this struct:
package main
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/hex"
"encoding/pem"
"fmt"
"math/big"
)
// RFC 5280, A.2. Implicitly Tagged Module, 1988 Syntax
//
// AuthorityKeyIdentifier ::= SEQUENCE {
// keyIdentifier [0] KeyIdentifier OPTIONAL,
// authorityCertIssuer [1] GeneralNames OPTIONAL,
// authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
// -- authorityCertIssuer and authorityCertSerialNumber MUST both
// -- be present or both be absent
type authKeyId struct {
KeyIdentifier []byte `asn1:"optional,tag:0"`
AuthorityCertIssuer generalNames `asn1:"optional,tag:1"`
AuthorityCertSerialNumber *big.Int `asn1:"optional,tag:2"`
}
// RFC 5280, A.2. Implicitly Tagged Module, 1988 Syntax
//
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
//
// GeneralName ::= CHOICE {
// otherName [0] AnotherName,
// rfc822Name [1] IA5String,
// dNSName [2] IA5String,
// x400Address [3] ORAddress,
// directoryName [4] Name,
// ediPartyName [5] EDIPartyName,
// uniformResourceIdentifier [6] IA5String,
// iPAddress [7] OCTET STRING,
// registeredID [8] OBJECT IDENTIFIER }
type generalNames struct {
Name []pkix.RDNSequence `asn1:"tag:4"`
}
func gen(issuer *x509.Certificate) ([]byte, error) {
return asn1.Marshal(authKeyId{
KeyIdentifier: issuer.SubjectKeyId,
AuthorityCertIssuer: generalNames{Name: []pkix.RDNSequence{issuer.Issuer.ToRDNSequence()}},
AuthorityCertSerialNumber: issuer.SerialNumber,
})
}
func main() {
caCert := `-----BEGIN CERTIFICATE-----
MIIBoTCCAUegAwIBAgIQGoCjDJN1Y6rGWEbXW8V8MDAKBggqhkjOPQQDAjAmMQ8w
DQYDVQQKEwZNeSBPcmcxEzARBgNVBAMTCk15IFJvb3QgQ0EwHhcNMjMwNTE2MTQy
NTUwWhcNMjMwNTE3MTUyNTUwWjAmMQ8wDQYDVQQKEwZNeSBPcmcxEzARBgNVBAMT
Ck15IFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARZQz2Ka7Fi6w9/
32SJHTAjrkE+VqYx7hFNmtX1INPBAJNfvONF2SIlh5nQmS50JpNVGIvEhTbFL0A0
dcuruFHno1cwVTAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5Y48DJ96LQWVh3S/aNJ/6SGy/j4w
CgYIKoZIzj0EAwIDSAAwRQIhANQDh6SGZ014wVFdH0ZHbEGhdb2TqXZUJxA7YMo3
80UnAiApZp4wlzqlB+J4fIPnep+Txru01JgFaKsml2yHv3mEWg==
-----END CERTIFICATE-----`
b, _ := pem.Decode([]byte(caCert))
if b == nil {
panic("couldn't decode test certificate")
}
issuer, err := x509.ParseCertificate(b.Bytes)
if err != nil {
panic(err)
}
authorityKeyIdentifierValue, err := gen(issuer)
if err != nil {
panic(err)
}
fmt.Println(hex.EncodeToString(authorityKeyIdentifierValue))
}
The hex encoded value is:
30548014e58e3c0c9f7a2d05958774bf68d27fe921b2fe3ea12aa4283026310f300d060355040a13064d79204f7267311330110603550403130a4d7920526f6f7420434182101a80a30c937563aac65846d75bc57c30
The hex string can be decoded with tools such as ASN.1 JavaScript decoder:
The following C# demo gives the same result:
using System.Security.Cryptography.X509Certificates;
using System.Text;
internal class Program
{
private static void Main(string[] args)
{
var certBytes = Encoding.ASCII.GetBytes(@"-----BEGIN CERTIFICATE-----
MIIBoTCCAUegAwIBAgIQGoCjDJN1Y6rGWEbXW8V8MDAKBggqhkjOPQQDAjAmMQ8w
DQYDVQQKEwZNeSBPcmcxEzARBgNVBAMTCk15IFJvb3QgQ0EwHhcNMjMwNTE2MTQy
NTUwWhcNMjMwNTE3MTUyNTUwWjAmMQ8wDQYDVQQKEwZNeSBPcmcxEzARBgNVBAMT
Ck15IFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARZQz2Ka7Fi6w9/
32SJHTAjrkE+VqYx7hFNmtX1INPBAJNfvONF2SIlh5nQmS50JpNVGIvEhTbFL0A0
dcuruFHno1cwVTAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5Y48DJ96LQWVh3S/aNJ/6SGy/j4w
CgYIKoZIzj0EAwIDSAAwRQIhANQDh6SGZ014wVFdH0ZHbEGhdb2TqXZUJxA7YMo3
80UnAiApZp4wlzqlB+J4fIPnep+Txru01JgFaKsml2yHv3mEWg==
-----END CERTIFICATE-----");
using var issuer = new X509Certificate2(certBytes);
var e = X509AuthorityKeyIdentifierExtension.CreateFromCertificate(issuer, true, true);
Console.WriteLine(ByteArrayToHex(e.RawData));
}
private static string ByteArrayToHex(byte[] bytes)
{
var builder = new StringBuilder(bytes.Length * 2);
for (int i = 0; i < bytes.Length; i++)
{
builder.Append($"{bytes[i]:x2}");
}
return builder.ToString();
}
}
Update:
Here is the updated version of gen
to include email address:
func gen(issuer *x509.Certificate) ([]byte, error) {
rdnSequence := issuer.Issuer.ToRDNSequence()
if len(issuer.EmailAddresses) > 0 {
oidEmail := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}
emails := make([]pkix.AttributeTypeAndValue, len(issuer.EmailAddresses))
for i, value := range issuer.EmailAddresses {
emails[i].Type = oidEmail
emails[i].Value = value
}
rdnSequence = append(rdnSequence, emails)
}
return asn1.Marshal(authKeyId{
KeyIdentifier: issuer.SubjectKeyId,
AuthorityCertIssuer: generalNames{Name: []pkix.RDNSequence{rdnSequence}},
AuthorityCertSerialNumber: issuer.SerialNumber,
})
}
Please note that the OID is deprecated (see http://oid-info.com/get/1.2.840.113549.1.9.1). And .NET does not include it too.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论