前言 在电信 和计算机网络 领域,ASN.1 (Abstract Syntax Notation One ) 是一套标准 ,是描述数据 的表示、编码、传输、解码的灵活的记法。它提供了一套正式、无歧义和精确的规则以描述独立于特定计算机硬件的对象结构。[1]
ASN.1是ISO 和ITU-T 的联合标准,最初是1984年的CCITT X.409:1984 的一部分。由于其广泛应用,1988年ASN.1移到独立标准X.208 ,1995年进行全面修订后变成X.680 系列标准。
ASN.1本身只定义了表示信息的抽象句法 ,但是没有限定其编码的方法。各种ASN.1编码规则提供了由ASN.1描述其抽象句法的数据的值的传送语法(具体表达)。标准的ASN.1编码规则有基本编码规则 (BER,Basic Encoding Rules)、规范编码规则 (CER,Canonical Encoding Rules)、唯一编码规则 (DER,Distinguished Encoding Rules)、压缩编码规则 (PER,Packed Encoding Rules)和XML编码规则 (XER,XML Encoding Rules)。为了使ASN.1能够描述一些原先没有使用ASN.1定义,因此不适用上述任一编码规则的数据传输和表示的应用和协议,另外制订了ECN 来扩展ASN.1的编码形式。ECN可以提供非常灵活的表明方法,但还没有得到普遍应用。
ASN.1与特定的ASN.1编码规则一起通过使用独立于计算机架构和编程语言的方法来描述数据结构,为结构化数据的交互提供了手段,特别是在网络环境的应用程序。
应用层 协议如X.400 (email )、X.500 和LDAP (目录服务 )、H.323 (VoIP )和SNMP 使用 ASN.1 描述它们交互的协议数据单元 。在UMTS 的接入和非接入层也有广泛的应用。 ASN.1的其他应用领域参见此处[1] 。
这里[2] 列举了很多ASN.1的自由或者商业的工具。
摘自维基百科ASN.1
作业一: 要求:
使用ASN.1编写一个数据结构。具体什么数据自己考虑。
分别使用asn1c、JavaAsn1Compiler等对这个数据结构进行编译。可以使用c/java/python进行编码,并存储,而后用另外一种编程语言进行解码,比如,用C编码,可以用java或者python解码;
对上述的数据结构,使用protobuffer实现一次。这里不强制要求不同的语言实现编码和解码。
环境和工具配置 1. 操作系统: Windows11本机和Ubuntu22虚拟机
2. Ubuntu配置java和javac 在终端中输入java或javac命令,没有java或javac的话会提示你安装,如下图所示,根据提示的命令选择需要的版本直接安装就行
3. ubuntu安装asn1c编译器 (1)下载asn1c的源码 :
1 git clone https://github.com/vlm/asn1c.git
或者直接下载源码包自行解压https://github.com/vlm/asn1c
注意:源码目录的路径中不要含有中文,否则在执行make和make install时会报错
(2)下载依赖库 :
1 sudo apt-get install libtool automake bison flex
(3)生成配置文件 :进入刚刚解压的文件夹,就是那个asn1c开头的文件夹,执行
1 test -f configure || autoreconf -iv
(4)配置ASN1C config :在asn1源码文件夹目录里执行
(5)编译 :执行命令
(6)安装 :执行命令
(7)执行命令
测试是否安装成功
4.Ubuntu或者Windows安装JavaAsn1Compiler JavaAsn1Compiler下载地址 : https://sourceforge.net/projects/jac-asn1/files/latest/download ,直接下载解压就行, Ubuntu 和 Windows 都可以,解压后有 HOW TO RUN.txt 记录了使用方法。
1.使用ASN.1编写一个数据结构 文件命名为rectangle.asn1
1 2 3 4 5 6 7 8 9 RectangleModule1 DEFINITIONS ::= BEGIN Rectangle ::= SEQUENCE { height INTEGER, width INTEGER, author OCTET STRING, title OCTET STRING } END
2.使用asn1c进行编译 编译后会产生很多文件,建议先建立一个文件夹,将rectangle.asn1文件复制进去,再编译。
编译命令:
1 asn1c -fnative-types rectangle.asn1
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 -fnative-types: Deprecated option Compiled Rectangle.c Compiled Rectangle.h Copied /usr/local/share/asn1c/OPEN_TYPE.h -> OPEN_TYPE.h Copied /usr/local/share/asn1c/OPEN_TYPE.c -> OPEN_TYPE.c Copied /usr/local/share/asn1c/constr_CHOICE.h -> constr_CHOICE.h Copied /usr/local/share/asn1c/INTEGER.h -> INTEGER.h Copied /usr/local/share/asn1c/INTEGER.c -> INTEGER.c Copied /usr/local/share/asn1c/NativeInteger.h -> NativeInteger.h Copied /usr/local/share/asn1c/NativeInteger.c -> NativeInteger.c Copied /usr/local/share/asn1c/constr_CHOICE.c -> constr_CHOICE.c Copied /usr/local/share/asn1c/constr_SEQUENCE.h -> constr_SEQUENCE.h Copied /usr/local/share/asn1c/constr_SEQUENCE.c -> constr_SEQUENCE.c Copied /usr/local/share/asn1c/asn_application.h -> asn_application.h Copied /usr/local/share/asn1c/asn_application.c -> asn_application.c Copied /usr/local/share/asn1c/asn_ioc.h -> asn_ioc.h Copied /usr/local/share/asn1c/asn_system.h -> asn_system.h Copied /usr/local/share/asn1c/asn_codecs.h -> asn_codecs.h Copied /usr/local/share/asn1c/asn_internal.h -> asn_internal.h Copied /usr/local/share/asn1c/asn_internal.c -> asn_internal.c Copied /usr/local/share/asn1c/asn_random_fill.h -> asn_random_fill.h Copied /usr/local/share/asn1c/asn_random_fill.c -> asn_random_fill.c Copied /usr/local/share/asn1c/asn_bit_data.h -> asn_bit_data.h Copied /usr/local/share/asn1c/asn_bit_data.c -> asn_bit_data.c Copied /usr/local/share/asn1c/OCTET_STRING.h -> OCTET_STRING.h Copied /usr/local/share/asn1c/OCTET_STRING.c -> OCTET_STRING.c Copied /usr/local/share/asn1c/BIT_STRING.h -> BIT_STRING.h Copied /usr/local/share/asn1c/BIT_STRING.c -> BIT_STRING.c Copied /usr/local/share/asn1c/asn_codecs_prim.c -> asn_codecs_prim.c Copied /usr/local/share/asn1c/asn_codecs_prim.h -> asn_codecs_prim.h Copied /usr/local/share/asn1c/ber_tlv_length.h -> ber_tlv_length.h Copied /usr/local/share/asn1c/ber_tlv_length.c -> ber_tlv_length.c Copied /usr/local/share/asn1c/ber_tlv_tag.h -> ber_tlv_tag.h Copied /usr/local/share/asn1c/ber_tlv_tag.c -> ber_tlv_tag.c Copied /usr/local/share/asn1c/ber_decoder.h -> ber_decoder.h Copied /usr/local/share/asn1c/ber_decoder.c -> ber_decoder.c Copied /usr/local/share/asn1c/der_encoder.h -> der_encoder.h Copied /usr/local/share/asn1c/der_encoder.c -> der_encoder.c Copied /usr/local/share/asn1c/constr_TYPE.h -> constr_TYPE.h Copied /usr/local/share/asn1c/constr_TYPE.c -> constr_TYPE.c Copied /usr/local/share/asn1c/constraints.h -> constraints.h Copied /usr/local/share/asn1c/constraints.c -> constraints.c Copied /usr/local/share/asn1c/xer_support.h -> xer_support.h Copied /usr/local/share/asn1c/xer_support.c -> xer_support.c Copied /usr/local/share/asn1c/xer_decoder.h -> xer_decoder.h Copied /usr/local/share/asn1c/xer_decoder.c -> xer_decoder.c Copied /usr/local/share/asn1c/xer_encoder.h -> xer_encoder.h Copied /usr/local/share/asn1c/xer_encoder.c -> xer_encoder.c Copied /usr/local/share/asn1c/per_support.h -> per_support.h Copied /usr/local/share/asn1c/per_support.c -> per_support.c Copied /usr/local/share/asn1c/per_decoder.h -> per_decoder.h Copied /usr/local/share/asn1c/per_decoder.c -> per_decoder.c Copied /usr/local/share/asn1c/per_encoder.h -> per_encoder.h Copied /usr/local/share/asn1c/per_encoder.c -> per_encoder.c Copied /usr/local/share/asn1c/per_opentype.h -> per_opentype.h Copied /usr/local/share/asn1c/per_opentype.c -> per_opentype.c Copied /usr/local/share/asn1c/oer_decoder.h -> oer_decoder.h Copied /usr/local/share/asn1c/oer_encoder.h -> oer_encoder.h Copied /usr/local/share/asn1c/oer_support.h -> oer_support.h Copied /usr/local/share/asn1c/oer_decoder.c -> oer_decoder.c Copied /usr/local/share/asn1c/oer_encoder.c -> oer_encoder.c Copied /usr/local/share/asn1c/oer_support.c -> oer_support.c Copied /usr/local/share/asn1c/OPEN_TYPE_oer.c -> OPEN_TYPE_oer.c Copied /usr/local/share/asn1c/INTEGER_oer.c -> INTEGER_oer.c Copied /usr/local/share/asn1c/BIT_STRING_oer.c -> BIT_STRING_oer.c Copied /usr/local/share/asn1c/OCTET_STRING_oer.c -> OCTET_STRING_oer.c Copied /usr/local/share/asn1c/NativeInteger_oer.c -> NativeInteger_oer.c Copied /usr/local/share/asn1c/constr_CHOICE_oer.c -> constr_CHOICE_oer.c Copied /usr/local/share/asn1c/constr_SEQUENCE_oer.c -> constr_SEQUENCE_oer.c Generated Makefile.am.libasncodec Copied /usr/local/share/asn1c/converter-example.c -> converter-example.c implicit Generated pdu_collection.c Generated converter-example.mk Copied /usr/local/share/asn1c/converter-example.c -> converter-example.c implicit Generated pdu_collection.c Generated Makefile.am.asn1convert
3.使用JavaAsn1Compiler进行编译和编码 JavaAsn1Compiler编译 以Ubuntu为例,先进入 JAC.jar所在的目录,也就是JavaAsn1Compiler_3.0\lib\,再在终端中执行命令:
1 2 java -jar JAC.jar -d 编译后生成文件的地址 -p rectangle rectangle.asn1的地址 java -jar JAC.jar -d ~/桌面/asn/asn-java -p rectangle ~/桌面/asn/asn-java/rectangle.asn1
编译成功后会在指定的输出目录(-d后的目录)生成文件夹,里面包含一个.java文件
进行编码 编写java文件 在编译生成的.java文件,也就是Rectangle.java文件中类里面添加以下代码,来给数据结构赋值。以下的代码要包在类里,也就是加在最后一个}的前面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public static void main (String []args) { ByteArrayOutputStream outStream = new ByteArrayOutputStream (); BerOutputStream out = new BerOutputStream (outStream); Rectangle rec = new Rectangle (); byte [] name = new byte [4 ]; name[0 ] = '1' ; name[1 ] = '2' ; name[2 ] = '3' ; name[3 ] = '4' ; rec.author.setValue(name); rec.title.setValue(name); rec.height.setValue(1011 ); rec.width.setValue(21 ); try { rec.encode(out); System.out.println(out.toString()); }catch (java.io.IOException e1){ System.out.println(e1); } File f = new File ("test_rec" ); try { OutputStream outFile = new FileOutputStream (f); try {outFile.write(outStream.toByteArray());} catch (java.io.IOException e2){ System.out.println(e2); } }catch (java.io.FileNotFoundException e1){ System.out.println(e1); } }
编译java文件 使用javac Rectangle.java命令进行编译,发现报错:
报错一:
将Rectangle.java复制到 JavaAsn1Compiler/JavaAsn1Compiler_3.0/Eclipse Project/JavaAsn1Compiler/src/ 中再进行编译,此时可能会产生以下的报错。
报错二
根据报错信息找到相应的文件,找到乱码发现不影响程序功能,直接删除就行。
报错三:
缺少相关的库,在java编译asn1文件之后的文件中,也就是Rectangle.java中,加入如下内容:
1 2 import com.chaosinmotion.asn1.*; import java.io.*;
报错四:
根据报错信息,找到指定文件,在相应位置加上 @Deprecated
Rectangle.java文件最终代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 package rectangle;import com.turkcelltech.jac.*;import com.chaosinmotion.asn1.Tag;import com.chaosinmotion.asn1.*;import java.io.*;public class Rectangle extends Sequence { public ASN1Integer height = new ASN1Integer ("height" ); public ASN1Integer width = new ASN1Integer ("width" ); public OctetString author = new OctetString ("author" ); public OctetString title = new OctetString ("title" ); public Rectangle () { super (); setUpElements(); } public Rectangle (String name) { super (name); setUpElements(); } protected void setUpElements () { super .addElement(height); super .addElement(width); super .addElement(author); super .addElement(title); } public static void main (String []args) { ByteArrayOutputStream outStream = new ByteArrayOutputStream (); BerOutputStream out = new BerOutputStream (outStream); Rectangle rec = new Rectangle (); byte [] name = new byte [4 ]; name[0 ] = '1' ; name[1 ] = '2' ; name[2 ] = '3' ; name[3 ] = '4' ; rec.author.setValue(name); rec.title.setValue(name); rec.height.setValue(1011 ); rec.width.setValue(21 ); try { rec.encode(out); System.out.println(out.toString()); }catch (java.io.IOException e1){ System.out.println(e1); } File f = new File ("test_rec" ); try { OutputStream outFile = new FileOutputStream (f); try {outFile.write(outStream.toByteArray());} catch (java.io.IOException e2){ System.out.println(e2); } }catch (java.io.FileNotFoundException e1){ System.out.println(e1); } } }
再执行一次javac命令就会生成一个Rectangle.class文件:
执行Rectangle.class 执行java Rectangle命令,发现报错:
原因是Rectangle.java文件中写了package rectangle,所以我们要在src目录下新建rectangle文件夹,将Rectangle.class文件复制进去,在src目录下打开终端执行java rectangle/Rectangle 命令,会生成编码文件test_rec。
4.使用python进行解码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pyasn1.type import univ, namedtypefrom pyasn1.codec.der.decoder import decodeclass Rectangle (univ.Sequence ): componentType = namedtype.NamedTypes( namedtype.NamedType('height' , univ.Integer()), namedtype.NamedType('width' , univ.Integer()), namedtype.NamedType('author' , univ.OctetString()), namedtype.NamedType('title' , univ.OctetString()), ) text = open ('C:/Users/86157/Desktop/JavaAsn1Compiler_3.0/Eclipse Project/JavaAsn1Compiler/src/test_rec' ,'rb' ).read() received_record, rest_of_substrate = decode(text, asn1Spec = Rectangle()) for field in received_record: print ('{} is {}' .format (field,received_record[field]))
5.protobuffer实现 安装配置Protobuffer 源文件下载 官方的 release 包是托管在 GitHub 的:https://github.com/protocolbuffers/protobuf/releases
按需选择对应架构的源码包,我这里选择:protobuf-all-21.12.zip
添加依赖 1 sudo apt-get install autoconf automake libtool curl make g++ unzip -y
编译/安装 解压上述源码包,cd 到解压目录,执行:
1 2 3 4 5 6 7 8 ./autogen.sh ./configure sudo make sudo make install #刷新动态库配置 sudo ldconfig #检查版本 protoc --version
编译proto 首先根据上述数据结构编写proto文件
1 2 3 4 5 6 7 syntax = "proto2"; message Rectangle { required int32 height = 1; required int32 width = 2; optional string author = 3; optional string title = 4; }
生成python,java,cpp文件
1 2 3 protoc --proto_path= rectangle.proto --python_out ./ protoc --proto_path= rectangle.proto --cpp_out ./ protoc --proto_path= rectangle.proto --java_out ./
参考链接 asn1的安装配置与使用:
https://blog.csdn.net/qq_28256407/article/details/120571402
https://blog.csdn.net/adgentleman/article/details/88576354
https://blog.csdn.net/weixin_33849942/article/details/91736131
protobuffer的安装配置与使用:
https://eyunzhu.com/cdata/ll3_nlQ_8KF8ML9_-9HYkw
http://i.lckiss.com/?p=8088
https://www.hi-dhl.com/2020/10/28/android/04-probuff-ubuntu/
实验参考:
https://www.jianshu.com/p/5deb48440af3
https://kindhearted57.github.io/2020/05/12/%E5%AF%86%E7%A0%81%E5%B7%A5%E7%A8%8B%E8%AF%BE%E5%90%8E%E4%BD%9C%E4%B8%9A5-ANS1.html
作业二 要求 搜索一个使用ASN.1编码的公私密钥或者证书文件,对其中的DER进行详细解释。可以自己分析,也可以通过工具分析(比如asn1dump)。分析的过程需要写出数据结构的组成,就是写出TLV来。
X.509证书 X.509是密码学里公钥证书的格式标准。X.509证书已应用在包括TLS/SSL在内的众多网络协议里,同时它也用在很多非在线应用场景里,比如电子签名服务。 X.509证书里含有公钥、身份信息(比如网络主机名,组织的名称或个体名称等)和签名信息(可以是证书签发机构CA的签名,也可以是自签名)。
X.509还附带了证书吊销列表和用于从最终对证书进行签名的证书签发机构直到最终可信点为止的证书合法性验证算法。X.509是ITU-T标准化部门基于他们之前的ASN.1定义的一套证书标准。
证书示例 我们先从网络获取一个证书实例,选取一个网站,点击网页链接的锁的标志,后续操作如下,最终我选择的是der形式存储:
证书结构 X.509使用ASN.1来描述公钥证书的结构,通常编码为DER格式,也可以进一步BASE64编码为可打印的PEM格式。V3版本的X.509结构如下:
证书
版本号
序列号
签名算法
颁发者
证书有效期
主题
主题公钥信息
颁发者唯一身份信息(可选项)
主题唯一身份信息(可选项)
扩展信息(可选项)
证书签名算法
数字签名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 Certificate ::= SEQUENCE { tbsCertificate TBSCertificate, signatureAlgorithm AlgorithmIdentifier, signatureValue BIT STRING } TBSCertificate ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, serialNumber CertificateSerialNumber, signature AlgorithmIdentifier, issuer Name, validity Validity, subject Name, subjectPublicKeyInfo SubjectPublicKeyInfo, issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version MUST be v2 or v3 subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version MUST be v2 or v3 extensions [3] EXPLICIT Extensions OPTIONAL -- If present, version MUST be v3 } Version ::= INTEGER { v1(0), v2(1), v3(2) } CertificateSerialNumber ::= INTEGER Validity ::= SEQUENCE { notBefore Time, notAfter Time } Time ::= CHOICE { utcTime UTCTime, generalTime GeneralizedTime } UniqueIdentifier ::= BIT STRING SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier, subjectPublicKey BIT STRING } Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension Extension ::= SEQUENCE { extnID OBJECT IDENTIFIER, critical BOOLEAN DEFAULT FALSE, extnValue OCTET STRING }
证书分析 查看证书内容 我们先直接点击保存的证书文件,再点击详细信息,查看证书的详细内容。
用Asn1dump分析证书文件
证书签名算法的OID为 1.2.840.113549.1.1.11 ,我们在www.oid-info.com 上查询OID,发现它是sha256WithRSAEncryption
1 2 3 4 Certificate ::= SEQUENCE { tbsCertificate TBSCertificate, - 证书 signatureAlgorithm AlgorithmIdentifier, - 证书签名算法 1.2.840.113549.1.1.11 sha256WithRSAEncryption signatureValue BIT STRING } - 数字签名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 TBSCertificate ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, -版本号 serialNumber CertificateSerialNumber, -序列号 signature AlgorithmIdentifier, -签名算法 issuer Name, -颁发者 validity Validity, -有效期 subject Name, -主题 subjectPublicKeyInfo SubjectPublicKeyInfo, -主题公钥信息 issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, -颁发者唯一身份信息(可选项) -- If present, version MUST be v2 or v3 subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, -主题唯一身份信息(可选项) -- If present, version MUST be v2 or v3 extensions [3] EXPLICIT Extensions OPTIONAL - 扩展信息(可选项) -- If present, version MUST be v3 } #版本号 V3 Version ::= INTEGER { v1(0), v2(1), v3(2) } #序列号 044d72d77cdda702dd5a67f2a23bbdd9 CertificateSerialNumber ::= INTEGER #签名算法 sha256RSA sha256 #颁发者 CN = DigiCert TLS RSA SHA256 2020 CA1,O = DigiCert Inc,C = US #有效期 从2023年2月21日 8:00:00 到 2024年3月21日 7:59:59 Validity ::= SEQUENCE { notBefore Time, notAfter Time } Time ::= CHOICE { utcTime UTCTime, generalTime GeneralizedTime } #主题使用者CN = *.github.io,O = GitHub, Inc.,L = San Francisco,S = California,C = US #公钥 30 82 01 0a 02 82 01 01 00 b8 b0 60 0e 1a 2f f1 b1 86 4b 64 ec 11 9f a6 79 be e8 87 f1 88 c5 b4 49 9b 10 bb ca af ea af be 54 0c 78 43 7f ca 7b 4e 45 5b 0b 24 29 f1 bb 23 fc 19 a4 c7 6c 70 49 76 53 d3 09 23 65 b2 48 7b b6 1c aa 07 1a e2 79 1a f9 7a 5e e7 16 f8 a6 4a d5 39 a3 e2 0d f7 57 ef ed f8 08 76 5b 52 da 8b d0 e6 1e 6e 2f f9 0f 99 4b 6a 52 ca 34 e1 a4 c9 20 33 d3 97 e8 7a 77 c5 03 10 26 41 82 61 47 a2 af c4 56 3f 76 a2 38 cb b2 70 ae 72 7a 43 c1 7e 27 a3 5e d6 e3 f6 e7 a5 30 70 bd 2a 96 27 7a 7b fb 40 d2 57 77 af 23 12 27 42 3a c6 0b 6a 8c bd ba 2d ee 3f 9f 15 ee 62 57 a4 a6 95 50 af 43 b0 ac 76 b8 e1 0e d9 ff 56 ec 74 50 86 b5 1f 96 2c d1 95 05 e5 b7 05 67 93 4e 9e f2 5a 38 1f a7 8f 43 5a de 3c 57 da 48 7a 50 c6 88 38 15 c8 97 2c 2c ec f8 39 09 36 bd 19 8d 03 56 41 66 07 24 e3 02 03 01 00 01 #公钥参数 05 00
参考链接 X.509维基百科
[X.509证书结构](https://www.cnblogs.com/xinzhao/p/8963724.html"ECC公钥格式详解 “)
https://zhuanlan.zhihu.com/p/520700949
https://wuziqingwzq.github.io/ca/2018/05/11/x509-knowledge-asn2.html
https://blog.csdn.net/qq_39385118/article/details/107510032