How to add DirName and serial to X509v3 Authority Key Identifier

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

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 (
	&quot;crypto/rand&quot;
	&quot;crypto/rsa&quot;
	&quot;crypto/sha1&quot;
	&quot;crypto/x509&quot;
	&quot;crypto/x509/pkix&quot;
	&quot;encoding/asn1&quot;
	&quot;os&quot;
	&quot;time&quot;
)

...

var caCertificate *x509.Certificate
var caPrivateKey *rsa.PrivateKey

var authorityKeyIdentifierValue []byte // how to write this?

template := &amp;x509.Certificate{
	Subject: pkix.Name{
		CommonName: &quot;xxx&quot;,
	},
	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, &amp;privateKey.PublicKey, caPrivateKey)
if err != nil {
	// err
}

// out

How to add DirName and serial to X509v3 Authority Key Identifier?

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...&lt;....).!.r[..F.....&quot;.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 (
	&quot;crypto/x509&quot;
	&quot;crypto/x509/pkix&quot;
	&quot;encoding/asn1&quot;
	&quot;encoding/hex&quot;
	&quot;encoding/pem&quot;
	&quot;fmt&quot;
	&quot;math/big&quot;
)

// 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:&quot;optional,tag:0&quot;`
	AuthorityCertIssuer       generalNames `asn1:&quot;optional,tag:1&quot;`
	AuthorityCertSerialNumber *big.Int     `asn1:&quot;optional,tag:2&quot;`
}

// 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:&quot;tag:4&quot;`
}

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(&quot;couldn&#39;t decode test certificate&quot;)
	}
	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:

How to add DirName and serial to X509v3 Authority Key Identifier


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(@&quot;-----BEGIN CERTIFICATE-----
MIIBoTCCAUegAwIBAgIQGoCjDJN1Y6rGWEbXW8V8MDAKBggqhkjOPQQDAjAmMQ8w
DQYDVQQKEwZNeSBPcmcxEzARBgNVBAMTCk15IFJvb3QgQ0EwHhcNMjMwNTE2MTQy
NTUwWhcNMjMwNTE3MTUyNTUwWjAmMQ8wDQYDVQQKEwZNeSBPcmcxEzARBgNVBAMT
Ck15IFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARZQz2Ka7Fi6w9/
32SJHTAjrkE+VqYx7hFNmtX1INPBAJNfvONF2SIlh5nQmS50JpNVGIvEhTbFL0A0
dcuruFHno1cwVTAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU5Y48DJ96LQWVh3S/aNJ/6SGy/j4w
CgYIKoZIzj0EAwIDSAAwRQIhANQDh6SGZ014wVFdH0ZHbEGhdb2TqXZUJxA7YMo3
80UnAiApZp4wlzqlB+J4fIPnep+Txru01JgFaKsml2yHv3mEWg==
-----END CERTIFICATE-----&quot;);

        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 &lt; bytes.Length; i++)
        {
            builder.Append($&quot;{bytes[i]:x2}&quot;);
        }

        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) &gt; 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.

huangapple
  • 本文由 发表于 2023年5月16日 14:17:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76259889.html
匿名

发表评论

匿名网友

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

确定