1 2 Example use case 3 ================ 4 5 .. toctree:: 6 :maxdepth: 2 7 8 To briefly explain how to approach pyasn1, consider a quick workflow example. 9 10 Grab ASN.1 schema for SSH keys 11 ------------------------------ 12 13 ASN.1 is widely used in many Internet protocols. Frequently, whenever ASN.1 is employed, 14 data structures are described in ASN.1 schema language right in the RFC. 15 Take `RFC2437 <https://www.ietf.org/rfc/rfc2437.txt>`_ for example -- we can look into 16 it and weed out data structures specification into a local file: 17 18 .. code-block:: python 19 20 # pkcs-1.asn 21 22 PKCS-1 {iso(1) member(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) modules(0) pkcs-1(1)} 23 24 DEFINITIONS EXPLICIT TAGS ::= BEGIN 25 RSAPrivateKey ::= SEQUENCE { 26 version Version, 27 modulus INTEGER, 28 publicExponent INTEGER, 29 privateExponent INTEGER, 30 prime1 INTEGER, 31 prime2 INTEGER, 32 exponent1 INTEGER, 33 exponent2 INTEGER, 34 coefficient INTEGER 35 } 36 Version ::= INTEGER 37 END 38 39 Compile ASN.1 schema into Python 40 -------------------------------- 41 42 In the best case, you should be able to automatically compile ASN.1 spec into 43 Python classes. For that purpose we have the `asn1ate <https://github.com/kimgr/asn1ate>`_ 44 tool: 45 46 .. code-block:: bash 47 48 $ pyasn1gen.py pkcs-1.asn > rsakey.py 49 50 Though it may not work out as, as it stands now, asn1ate does not support 51 all ASN.1 language constructs. 52 53 Alternatively, you could check out the `pyasn1-modules <https://github.com/etingof/pyasn1-modules>`_ 54 package to see if it already has the ASN.1 spec you are looking for compiled and shipped 55 there. Then just install the package, import the data structure you need and use it: 56 57 .. code-block:: bash 58 59 $ pip install pyasn1-modules 60 61 As a last resort, you could express ASN.1 in Python by hand. The end result 62 should be a declarative Python code resembling original ASN.1 syntax like 63 this: 64 65 .. code-block:: python 66 67 # rsakey.py 68 69 class Version(Integer): 70 pass 71 72 class RSAPrivateKey(Sequence): 73 componentType = NamedTypes( 74 NamedType('version', Version()), 75 NamedType('modulus', Integer()), 76 NamedType('publicExponent', Integer()), 77 NamedType('privateExponent', Integer()), 78 NamedType('prime1', Integer()), 79 NamedType('prime2', Integer()), 80 NamedType('exponent1', Integer()), 81 NamedType('exponent2', Integer()), 82 NamedType('coefficient', Integer()) 83 ) 84 85 Read your ~/.ssh/id_rsa 86 ----------------------- 87 88 Given we've put our Python classes into the `rsakey.py` module, we could import 89 the top-level object for SSH keys container and initialize it from our 90 `~/.ssh/id_rsa` file (for sake of simplicity here we assume no passphrase is 91 set on the key file): 92 93 .. code-block:: python 94 95 from base64 import b64decode 96 from pyasn1.codec.der.decoder import decode as der_decoder 97 from rsakey import RSAPrivateKey 98 99 # Read SSH key from file (assuming no passphrase) 100 with open open('.ssh/id_rsa') as key_file: 101 b64_serialisation = ''.join(key_file.readlines()[1:-1]) 102 103 # Undo BASE64 serialisation 104 der_serialisation = b64decode(b64_serialisation) 105 106 # Undo DER serialisation, reconstruct SSH key structure 107 private_key, rest_of_input = der_decoder(der_serialisation, asn1Spec=RSAPrivateKey()) 108 109 Once we have Python ASN.1 structures initialized, we could inspect them: 110 111 .. code-block:: pycon 112 113 >>> print('%s' % private_key) 114 RSAPrivateKey: 115 version=0 116 modulus=280789907761334970323210643584308373... 117 publicExponent=65537 118 privateExponent=1704567874679144879123080924... 119 prime1=1780178536719561265324798296279384073... 120 prime2=1577313184995269616049017780493740138... 121 exponent1=1193974819720845247396384239609024... 122 exponent2=9240965721817961178848297404494811... 123 coefficient=10207364473358910343346707141115... 124 125 Play with the keys 126 ------------------ 127 128 As well as use them nearly as we do with native Python types: 129 130 .. code-block:: pycon 131 132 >>> pk = private_key 133 >>> 134 >>> pk['prime1'] * pk['prime2'] == pk['modulus'] 135 True 136 >>> pk['prime1'] == pk['modulus'] // pk['prime2'] 137 True 138 >>> pk['exponent1'] == pk['privateExponent'] % (pk['prime1'] - 1) 139 True 140 >>> pk['exponent2'] == pk['privateExponent'] % (pk['prime2'] - 1) 141 True 142 143 Technically, pyasn1 classes `emulate <https://docs.python.org/3/reference/datamodel.html#emulating-container-types>`_ 144 Python built-in types. 145 146 Transform to built-ins 147 ---------------------- 148 149 ASN.1 data structures exhibit a way more complicated behaviour compared to 150 Python types. You may wish to simplify things by turning the whole tree of 151 pyasn1 objects into an analogous tree made of base Python types: 152 153 .. code-block:: pycon 154 155 >>> from pyasn1.codec.native.encoder import encode 156 >>> ... 157 >>> py_private_key = encode(private_key) 158 >>> py_private_key 159 {'version': 0, 'modulus': 280789907761334970323210643584308373, 'publicExponent': 65537, 160 'privateExponent': 1704567874679144879123080924, 'prime1': 1780178536719561265324798296279384073, 161 'prime2': 1577313184995269616049017780493740138, 'exponent1': 1193974819720845247396384239609024, 162 'exponent2': 9240965721817961178848297404494811, 'coefficient': 10207364473358910343346707141115} 163 164 You can do vice-versa: initialize ASN.1 structure from a dict: 165 166 .. code-block:: pycon 167 168 >>> from pyasn1.codec.native.decoder import decode 169 >>> py_private_key = {'modulus': 280789907761334970323210643584308373} 170 >>> private_key = decode(py_private_key, asn1Spec=RSAPrivateKey()) 171 172 Write it back 173 ------------- 174 175 Possibly not that applicable to the SSH key example, but you can of course modify 176 any part of the ASN.1 data structure and serialise it back into the same or other 177 wire representation: 178 179 .. code-block:: python 180 181 from pyasn1.codec.der.encoder import encode as der_encoder 182 183 # Serialise SSH key data structure into DER stream 184 der_serialisation = der_encoder(private_key) 185 186 # Serialise DER stream into BASE64 stream 187 b64_serialisation = '-----BEGIN RSA PRIVATE KEY-----\n' 188 b64_serialisation += b64encode(der_serialisation) 189 b64_serialisation += '-----END RSA PRIVATE KEY-----\n' 190 191 with open('.ssh/id_rsa.new', 'w') as key_file: 192 key_file.write(b64_serialisation) 193 194