• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1
2Example use case
3================
4
5.. toctree::
6   :maxdepth: 2
7
8To briefly explain how to approach pyasn1, consider a quick workflow example.
9
10Grab ASN.1 schema for SSH keys
11------------------------------
12
13ASN.1 is widely used in many Internet protocols. Frequently, whenever ASN.1 is employed,
14data structures are described in ASN.1 schema language right in the RFC.
15Take `RFC2437 <https://www.ietf.org/rfc/rfc2437.txt>`_ for example -- we can look into
16it 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
39Compile ASN.1 schema into Python
40--------------------------------
41
42In the best case, you should be able to automatically compile ASN.1 spec into
43Python classes. For that purpose we have the `asn1ate <https://github.com/kimgr/asn1ate>`_
44tool:
45
46.. code-block:: bash
47
48    $ asn1ate pkcs-1.asn > rsakey.py
49
50Though it may not work out as, as it stands now, asn1ate does not support
51all ASN.1 language constructs.
52
53Alternatively, you could check out the `pyasn1-modules <https://github.com/etingof/pyasn1-modules>`_
54package to see if it already has the ASN.1 spec you are looking for compiled and shipped
55there. 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
61As a last resort, you could express ASN.1 in Python by hand. The end result
62should be a declarative Python code resembling original ASN.1 syntax like
63this:
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
85Read your ~/.ssh/id_rsa
86-----------------------
87
88Given we've put our Python classes into the `rsakey.py` module, we could import
89the 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
91set 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('.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
109Once 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
125Play with the keys
126------------------
127
128As 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
143Technically, pyasn1 classes `emulate <https://docs.python.org/3/reference/datamodel.html#emulating-container-types>`_
144Python built-in types.
145
146Transform to built-ins
147----------------------
148
149ASN.1 data structures exhibit a way more complicated behaviour compared to
150Python types. You may wish to simplify things by turning the whole tree of
151pyasn1 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
164You 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
172Write it back
173-------------
174
175Possibly not that applicable to the SSH key example, but you can of course modify
176any part of the ASN.1 data structure and serialise it back into the same or other
177wire 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