Home | History | Annotate | Download | only in source
      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