用新比利时ID卡(EID中间件和ITEXT)签名PDF

发布于 2025-01-21 13:34:00 字数 1944 浏览 3 评论 0 原文

我试图用比利时身份证签署一些PDF。 为了获得这一点,我正在使用比利时开斋节中间件签名数据和ITEXT7,用签名将PDF盖章。

我使用pdfsigner(itext),并且已经实现了一个iexternalsignature来调用EID中间件来签署消息。

所有人都适合使用加密RSA和Hash SHA256的比利时ID卡1.7

。由Adobe Reader(或其他阅读器)。 “该文件已被更改或损坏”。

在哈希(Hashing)或...我搜索几天的某个地方似乎是不匹配的

,但是我不再想知道……

有人对出了什么问题有所了解吗?

事先感谢您的帮助。

这里有一些其他信息。

外部签名类别:

    internal sealed class BeIDSignature : IExternalSignature
    {
        public string GetEncryptionAlgorithm()
        {
            return eidWrapper.Instance.GetEncryptionAlgorithm().ToString();
        }

        public string GetHashAlgorithm()
        {
            switch (EidWrapper.Instance.GetEncryptionAlgorithm())
            {
                case EidWrapper.EncryptionAmgorithm.RSA:
                    return DigestAlgorithms.SHA256;
                case EidWrapper.EncryptionAmgorithm.ECDSA:
                    return DigestAlgorithms.SHA384;
                default:
                    return null;
            }
        }
    
        public byte[] Sign(byte[] message)
        {
            return EidWrapper.Instance.SignData(message);
        }
    }

getEncryptionalgorithm将根据芯片的返回RSA或ECDSA。 符号方法将使用eid-mw packege生成签名。

EidWrapper的符号方法的一小部分代码:

    if (key.KeyType.KeyType == CKK.EC)
    {
        session.SignInit(new Mechanism(CKM.ECDSA_SHA384), key);
        return session.Sign(data);
    }
    else if (key.KeyType.KeyType == CKK.RSA)
    {
        session.SignInit(new Mechanism(CKM.SHA256_RSA_PKCS), key);
        return session.Sign(data);
    }

您可以在这里找到一个带有3个pdf文件的邮政编码:

  • 原始文件
  • 直接用Adobe签名(可以是Siganture)(可以)
  • 一个用eid-mw签名的邮政编码(siganture是可以的)(签名不正常)。但是请记住 对于RSA/SHA256 SIGANTURE而言。

https://easyupload.io/yzscsu

再次感谢您的时间。

I try to sign some pdf's with a Belgian id card.
To acheive that, I'm using the belgium eid middleware to sign the data and itext7 to stamp the pdf with the signature.

I use a PdfSigner (itext) and I have implement a IExternalSignature to call the eid middleware to sign the message.

All work well for the Belgian id card 1.7 with encryption RSA and hash SHA256.

But when I try to sign with the new Belgian id card 1.8 with encryption ECDSA and hash SHA384, the signature can't be validated by adobe reader (or other reader).
"The document has been altered or corrupted".

It seems to be a mismatch somewhere in the hashing or ...

I search for some days but I have no more idea to fix that ...

Someone have an idea about what is going wrong?

Thanks in advance for your help.

Here some additional informations.

The external signature class:

    internal sealed class BeIDSignature : IExternalSignature
    {
        public string GetEncryptionAlgorithm()
        {
            return eidWrapper.Instance.GetEncryptionAlgorithm().ToString();
        }

        public string GetHashAlgorithm()
        {
            switch (EidWrapper.Instance.GetEncryptionAlgorithm())
            {
                case EidWrapper.EncryptionAmgorithm.RSA:
                    return DigestAlgorithms.SHA256;
                case EidWrapper.EncryptionAmgorithm.ECDSA:
                    return DigestAlgorithms.SHA384;
                default:
                    return null;
            }
        }
    
        public byte[] Sign(byte[] message)
        {
            return EidWrapper.Instance.SignData(message);
        }
    }

GetEncryptionAlgorithm will return RSA or ECDSA depending of the chip.
The sign method will use the eid-mw packege to generate the signature.

A little piece of code of the sign method of the EidWrapper:

    if (key.KeyType.KeyType == CKK.EC)
    {
        session.SignInit(new Mechanism(CKM.ECDSA_SHA384), key);
        return session.Sign(data);
    }
    else if (key.KeyType.KeyType == CKK.RSA)
    {
        session.SignInit(new Mechanism(CKM.SHA256_RSA_PKCS), key);
        return session.Sign(data);
    }

You can find here a zip with 3 pdf files:

  • The original file
  • One signed directly with adobe (siganture is ok)
  • One signed with eid-mw and itext (signature is NOT ok). But remember
    that is working for RSA/SHA256 siganture.

https://easyupload.io/yzscsu

Thanks again for your time.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

羁拥 2025-01-28 13:34:00

这是比利时EID SmartCard的外部容器的样本。

该代码尚未完全实施,但是您可以正确地在ECDSA/SHA384中制作Siganture。

希望这会帮助某人:)


internal sealed class BeIdExternalSignatureContainer : IExternalSignatureContainer
    {
        private IX509Store _crls = null;
        private readonly IHttpClientFactory _httpClientFactory;
        private int _crlsSize = 0;

        public BeIdExternalSignatureContainer(IHttpClientFactory httpClientFactory)
        {
            this._httpClientFactory = httpClientFactory;
        }

        public void ModifySigningDictionary(PdfDictionary signDic)
        {
            signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
            signDic.Put(PdfName.SubFilter, PdfName.ETSI_CAdES_DETACHED);
        }

        public int ComputeEstimateSize()
        {
            // Base on itext estimation
            if (this._crlsSize == 0)
            {
                this.InitializeCrls();
            }

            return (8192 + this._crlsSize + 4192) * 2 + 2;
        }

        public byte[] Sign(Stream data)
        {
            IReadOnlyDictionary<string, byte[]> certificatesBinaries = EidWrapper.Instance.GetCertificateFiles(new string[] {
                        Constants.SIGNATURE_CERTIFICATE_NAME,
                        Constants.CA_CERTIFICATE_NAME,
                        Constants.ROOT_CERTIFICATE_NAME
                    });

            X509Certificate signCertificate = new(X509CertificateStructure.GetInstance(certificatesBinaries[Constants.SIGNATURE_CERTIFICATE_NAME]));
            EidWrapper.EncryptionAlgorithms encryptionAlgorithm = EidWrapper.Instance.GetEncryptionAlgorithm();

            SignerInfoGenerator signerGenerator = new SignerInfoGeneratorBuilder()
                .WithSignedAttributeGenerator(new PadesSignedAttributeGenerator { SigningCertificate = signCertificate })
                .WithUnsignedAttributeGenerator(new PadesUnsignedAttributeGenerator(this._httpClientFactory))
                .Build(new BeIdSignatureFactory(encryptionAlgorithm), signCertificate);

            CmsSignedDataGenerator gen = new();
            gen.AddSignerInfoGenerator(signerGenerator);

            IX509Store x509CertStore = X509StoreFactory.Create(
                "Certificate/Collection",
                new X509CollectionStoreParameters(certificatesBinaries.Values.Select(b => new X509Certificate(X509CertificateStructure.GetInstance(b))).ToList()));

            gen.AddCertificates(x509CertStore);
            gen.AddCrls(this.Crls);

            CmsProcessableInputStream cmsProcessableInputStream = new(data);
            CmsSignedData signedData = gen.Generate(cmsProcessableInputStream, false);
            return signedData.GetEncoded();
        }

        private IX509Store Crls
        {
            get
            {
                if (this._crls == null)
                {
                    this.InitializeCrls();
                }

                return this._crls;
            }
        }

        private void InitializeCrls()
        {
            this._crlsSize = 0;
            List<X509Crl> crls = new();
            X509CrlParser crlParser = new();
            HttpClient httpClient = this._httpClientFactory.CreateClient();
            foreach (var crlUrl in BeIdSignatureConstants.CRL_URL_COLLECTION)
            {
                using HttpResponseMessage response = httpClient.Send(new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(crlUrl) });
                if (response.IsSuccessStatusCode)
                {
                    using MemoryStream ms = new MemoryStream();
                    response.Content.ReadAsStream().CopyTo(ms);
                    byte[] crlBytes = ms.ToArray();
                    this._crlsSize += crlBytes.Length;
                    crls.Add(crlParser.ReadCrl(crlBytes));
                }
            }

            this._crls = X509StoreFactory.Create(
                "CRL/Collection",
                new X509CollectionStoreParameters(crls));
        }
    }

internal class BeIdSignatureFactory : ISignatureFactory
    {
        private readonly EidWrapper.EncryptionAlgorithms _encryptionAlgorithm;
        public BeIdSignatureFactory(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
        {
            this._encryptionAlgorithm = encryptionAlgorithm;
        }

        public IStreamCalculator CreateCalculator()
        {
            BeIdSigner signer = new(this._encryptionAlgorithm);
            signer.Init(true, null);
            return new DefaultSignatureCalculator(signer);
        }

        public object AlgorithmDetails
        {
            get
            {
                return MapAlgorithm(this._encryptionAlgorithm);
            }
        }

        public static AlgorithmIdentifier MapAlgorithm(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
        {
            switch (encryptionAlgorithm)
            {
                case EidWrapper.EncryptionAlgorithms.RSA:
                    return new AlgorithmIdentifier(PkcsObjectIdentifiers.Sha256WithRsaEncryption, DerNull.Instance);
                case EidWrapper.EncryptionAlgorithms.ECDSA:
                    return new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha384, DerNull.Instance);
                default:
                    throw new ArgumentException($"Unsupported encryption algorithm: {encryptionAlgorithm}");
            }
        }
    }

internal class BeIdSigner : ISigner
    {
        private byte[] _input;
        private readonly EidWrapper.EncryptionAlgorithms _encryptionAlgorithm;

        public BeIdSigner(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
        {
            this._encryptionAlgorithm = encryptionAlgorithm;
        }

        public void Init(bool forSigning, ICipherParameters parameters)
        {
            this.Reset();
        }

        public void Update(byte input)
        {
            throw new NotImplementedException("The \"ISigner.Update\" method is not implemented for the \"BeIdSigner\" class.");
        }

        public void BlockUpdate(byte[] input, int inOff, int length)
        {
            this._input = input.Skip(inOff).Take(length).ToArray();
        }

        public byte[] GenerateSignature()
        {
            return this._encryptionAlgorithm == EidWrapper.EncryptionAlgorithms.ECDSA
                ? PlainToDer(EidWrapper.Instance.SignData(this._input))
                : EidWrapper.Instance.SignData(this._input);
        }

        public bool VerifySignature(byte[] signature)
        {
            throw new NotImplementedException("The \"ISigner.VerifySignature\" method is not implemented for the \"BeIdSigner\" class.");
        }

        public void Reset()
        {
            this._input = null;
        }

        public string AlgorithmName => throw new NotImplementedException("The \"ISigner.AlgorithmName\" property is not implemented for the \"BeIdSigner\" class.");

        private static byte[] PlainToDer(byte[] plain)
        {
            int valueLength = plain.Length / 2;
            BigInteger r = new(1, plain, 0, valueLength);
            BigInteger s = new(1, plain, valueLength, valueLength);
            return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
        }
    }

Here is a sample of an external container for the belgian eid smartcard.

The code is not fully implemented but you have a base to make a siganture in ECDSA/SHA384 correctly.

Hope that will help someone :)


internal sealed class BeIdExternalSignatureContainer : IExternalSignatureContainer
    {
        private IX509Store _crls = null;
        private readonly IHttpClientFactory _httpClientFactory;
        private int _crlsSize = 0;

        public BeIdExternalSignatureContainer(IHttpClientFactory httpClientFactory)
        {
            this._httpClientFactory = httpClientFactory;
        }

        public void ModifySigningDictionary(PdfDictionary signDic)
        {
            signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
            signDic.Put(PdfName.SubFilter, PdfName.ETSI_CAdES_DETACHED);
        }

        public int ComputeEstimateSize()
        {
            // Base on itext estimation
            if (this._crlsSize == 0)
            {
                this.InitializeCrls();
            }

            return (8192 + this._crlsSize + 4192) * 2 + 2;
        }

        public byte[] Sign(Stream data)
        {
            IReadOnlyDictionary<string, byte[]> certificatesBinaries = EidWrapper.Instance.GetCertificateFiles(new string[] {
                        Constants.SIGNATURE_CERTIFICATE_NAME,
                        Constants.CA_CERTIFICATE_NAME,
                        Constants.ROOT_CERTIFICATE_NAME
                    });

            X509Certificate signCertificate = new(X509CertificateStructure.GetInstance(certificatesBinaries[Constants.SIGNATURE_CERTIFICATE_NAME]));
            EidWrapper.EncryptionAlgorithms encryptionAlgorithm = EidWrapper.Instance.GetEncryptionAlgorithm();

            SignerInfoGenerator signerGenerator = new SignerInfoGeneratorBuilder()
                .WithSignedAttributeGenerator(new PadesSignedAttributeGenerator { SigningCertificate = signCertificate })
                .WithUnsignedAttributeGenerator(new PadesUnsignedAttributeGenerator(this._httpClientFactory))
                .Build(new BeIdSignatureFactory(encryptionAlgorithm), signCertificate);

            CmsSignedDataGenerator gen = new();
            gen.AddSignerInfoGenerator(signerGenerator);

            IX509Store x509CertStore = X509StoreFactory.Create(
                "Certificate/Collection",
                new X509CollectionStoreParameters(certificatesBinaries.Values.Select(b => new X509Certificate(X509CertificateStructure.GetInstance(b))).ToList()));

            gen.AddCertificates(x509CertStore);
            gen.AddCrls(this.Crls);

            CmsProcessableInputStream cmsProcessableInputStream = new(data);
            CmsSignedData signedData = gen.Generate(cmsProcessableInputStream, false);
            return signedData.GetEncoded();
        }

        private IX509Store Crls
        {
            get
            {
                if (this._crls == null)
                {
                    this.InitializeCrls();
                }

                return this._crls;
            }
        }

        private void InitializeCrls()
        {
            this._crlsSize = 0;
            List<X509Crl> crls = new();
            X509CrlParser crlParser = new();
            HttpClient httpClient = this._httpClientFactory.CreateClient();
            foreach (var crlUrl in BeIdSignatureConstants.CRL_URL_COLLECTION)
            {
                using HttpResponseMessage response = httpClient.Send(new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(crlUrl) });
                if (response.IsSuccessStatusCode)
                {
                    using MemoryStream ms = new MemoryStream();
                    response.Content.ReadAsStream().CopyTo(ms);
                    byte[] crlBytes = ms.ToArray();
                    this._crlsSize += crlBytes.Length;
                    crls.Add(crlParser.ReadCrl(crlBytes));
                }
            }

            this._crls = X509StoreFactory.Create(
                "CRL/Collection",
                new X509CollectionStoreParameters(crls));
        }
    }

internal class BeIdSignatureFactory : ISignatureFactory
    {
        private readonly EidWrapper.EncryptionAlgorithms _encryptionAlgorithm;
        public BeIdSignatureFactory(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
        {
            this._encryptionAlgorithm = encryptionAlgorithm;
        }

        public IStreamCalculator CreateCalculator()
        {
            BeIdSigner signer = new(this._encryptionAlgorithm);
            signer.Init(true, null);
            return new DefaultSignatureCalculator(signer);
        }

        public object AlgorithmDetails
        {
            get
            {
                return MapAlgorithm(this._encryptionAlgorithm);
            }
        }

        public static AlgorithmIdentifier MapAlgorithm(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
        {
            switch (encryptionAlgorithm)
            {
                case EidWrapper.EncryptionAlgorithms.RSA:
                    return new AlgorithmIdentifier(PkcsObjectIdentifiers.Sha256WithRsaEncryption, DerNull.Instance);
                case EidWrapper.EncryptionAlgorithms.ECDSA:
                    return new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha384, DerNull.Instance);
                default:
                    throw new ArgumentException(
quot;Unsupported encryption algorithm: {encryptionAlgorithm}");
            }
        }
    }

internal class BeIdSigner : ISigner
    {
        private byte[] _input;
        private readonly EidWrapper.EncryptionAlgorithms _encryptionAlgorithm;

        public BeIdSigner(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
        {
            this._encryptionAlgorithm = encryptionAlgorithm;
        }

        public void Init(bool forSigning, ICipherParameters parameters)
        {
            this.Reset();
        }

        public void Update(byte input)
        {
            throw new NotImplementedException("The \"ISigner.Update\" method is not implemented for the \"BeIdSigner\" class.");
        }

        public void BlockUpdate(byte[] input, int inOff, int length)
        {
            this._input = input.Skip(inOff).Take(length).ToArray();
        }

        public byte[] GenerateSignature()
        {
            return this._encryptionAlgorithm == EidWrapper.EncryptionAlgorithms.ECDSA
                ? PlainToDer(EidWrapper.Instance.SignData(this._input))
                : EidWrapper.Instance.SignData(this._input);
        }

        public bool VerifySignature(byte[] signature)
        {
            throw new NotImplementedException("The \"ISigner.VerifySignature\" method is not implemented for the \"BeIdSigner\" class.");
        }

        public void Reset()
        {
            this._input = null;
        }

        public string AlgorithmName => throw new NotImplementedException("The \"ISigner.AlgorithmName\" property is not implemented for the \"BeIdSigner\" class.");

        private static byte[] PlainToDer(byte[] plain)
        {
            int valueLength = plain.Length / 2;
            BigInteger r = new(1, plain, 0, valueLength);
            BigInteger s = new(1, plain, valueLength, valueLength);
            return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
        }
    }
尴尬癌患者 2025-01-28 13:34:00

问题。

答案总结了

此 。

此限制主要是由于Itext pdfpkcs7 类,用于创建和验证嵌入PDF中的CMS签名容器。当它用于创建签名容器时,它将其存储在 signaturealgorithm 字段中的标识符不会考虑使用过的哈希算法。例如,它使用相同的RSA值(带有PKCS1 V1.5填充)签名,无论它实际上是SHA1WITHRSA还是SHA256WITHRSA或任何组合。

对于RSA,这是可以的,因为确实有一个标识符可以用于所有这些情况。对于DSA来说,这有点不错,因为在许多情况下,DSA仅限于SHA1使用。

对于ECDSA而言,这不是 可以,只有标识符考虑了哈希算法。在所有这些情况下,Itext使用EC公共密钥标识符,这简直是错误的。几乎没有人注意到此错误的原因是Adobe Acrobat验证显然 sign aturalealgorithm 字段的内容:您甚至可以将RSA标识符写入ECDSA签名的该领域验证成功而没有问题。

因此,要创建适当的ECDSA签名,当前不应使用 pdfpkcs7 类。由于所有 pdfsigner.signdetached 在内部使用 pdfpkcs7 类,这又意味着一个人必须使用它们,而是 pdfsigner.signexexnexternal.signexternalcontainer 。结果,一个人不得使用 iexternalsignature 实现来检索一个人的签名值,而是 iexternalalSignatureContainer 实现,其中人们以不同的方式构建CMS签名容器,例如使用Bouncycastle类使用。

在手动的情况下 beidSignature iexternalsignature 的实现,必须相应地更换。

有关更多详细信息,请阅读本节使用哪个接口 itext知识基础文章使用ITEXT 7的数字签名

ECDSA签名格式

有两种主要格式可以存储ECDSA签名值,要么作为两个整数的TLV(DER)编码序列,要么作为这两个整数的固定长度表示的串联。

根据所使用的格式,必须分别为ECDSA和Plain-ECDSA使用特定的算法标识符。如果一个人需要特定的标识符,则可以将签名值从一种格式转换为另一种格式。

在手工的情况下比利时ID卡以普通格式返回ECDSA签名值。要使用更常见的非平台ECDSA标识符,必须将该值转换为DER格式。可以使用此方法完成此操作:

byte[] PlainToDer(byte[] plain)
{
    int valueLength = plain.Length / 2;
    BigInteger r = new BigInteger(1, plain, 0, valueLength);
    BigInteger s = new BigInteger(1, plain, valueLength, valueLength);
    return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
}

使用itext 7

This answer sums up the comments to the question.

iText and ECDSA signatures

First of all one has to realize that currently (i.e. for iText 7.2.2) ECDSA is not supported by all parts of the iText signing API.

This limitation is mostly due to the iText PdfPKCS7 class used to create and validate CMS signature containers embedded in PDFs. When it is used to create a signature container, the identifier it stores in the signatureAlgorithm field does not take the used hash algorithm into account. E.g. it uses the same value for RSA (with PKCS1 v1.5 padding) signatures, no matter if it's actually SHA1withRSA or SHA256withRSA or whichever combination.

For RSA this is ok because there indeed is an identifier that can be used for all these cases. For DSA it is somewhat ok because in many contexts DSA is limited to use with SHA1 only.

For ECDSA this is not ok, there only are identifiers taking the hash algorithm into account. iText uses the EC public key identifier in all these cases which is simply wrong. The reason why hardly anyone noticed this bug is that Adobe Acrobat validation apparently ignores the contents of this signatureAlgorithm field: You can even write the RSA identifier into this field of an ECDSA signature and the validation succeeds without an indication of a problem.

To create proper ECDSA signatures, therefore, one currently should not use the PdfPKCS7 class. As all the PdfSigner.signDetached methods internally use the PdfPKCS7 class, this in turn means that one must not use them but instead PdfSigner.signExternalContainer. As a consequence, one must not use an IExternalSignature implementation to retrieve one's signature value but instead an IExternalSignatureContainer implementation in which one builds the CMS signature container differently, for example using BouncyCastle classes.

In the case at hand the BeIDSignature implementation of IExternalSignature, therefore, must be replaced accordingly.

For further details please read the section Which Interface to Use of the iText knowledge base article Digital Signing with iText 7.

ECDSA signature formats

There are two major formats in which an ECDSA signature value can be stored, either as a TLV (DER) encoded sequence of two integers or (plain encoding) as the concatenation of fixed length representations of those two integers.

Depending on the used format one has to use specific algorithm identifiers for ECDSA and PLAIN-ECDSA respectively. If one needs a specific identifier, one can convert the signature value from one format to the other.

In the case at hand the Belgian ID card returns the ECDSA signature value in plain format. To use the more common non-PLAIN ECDSA identifiers, one has to convert that value to the DER format. This can be done using this method:

byte[] PlainToDer(byte[] plain)
{
    int valueLength = plain.Length / 2;
    BigInteger r = new BigInteger(1, plain, 0, valueLength);
    BigInteger s = new BigInteger(1, plain, valueLength, valueLength);
    return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
}

(X509Certificate2Signature helper method from the code examples of Digital Signing with iText 7)

在梵高的星空下 2025-01-28 13:34:00

一些想法:

Some thoughts:

  • Are you sure that there is an ECDSA key available on the chip ? I had a short look at the documentation (not sure it's up to date - cf. eid-mw github), which only mentions RSA. Additionally if you can sign using RSA/SHA256, having ECDSA support as well would mean that there's a second key pair on the card - I have some doubts about this ;
  • Try to sign with ECDSA / SHA384 in Adobe Reader using your eID - check whether you can validate the signature ;
  • Validate the signature online, using the SD-DSS tool: the diagnostic data may help you in pin-pointing what is wrong (e.g. a sha384 digest was generated, but the signature structure mentions sha256 as digest algo).
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文