Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/pyasn1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# pyasn1 Examples

Each sub-directory contains a self-contained example. The order in
which the examples are to appear is specified in `order.json` (an
array of directory names in the expected order).

In each example directory you'll find:

* `config.toml` - must conform to the specification outlined here:
https://docs.pyscript.net/latest/user-guide/configuration/ This is
parsed and ultimately turned into a JSON representation as part of
the package's API object.
* `setup.py` - Python code for contextual and environmental setup,
NOT SEEN BY THE END USER, but is run before the `code.py` code is
evaluated. Allows us to create useful (IPython) shims, avoid
repeating boilerplate and whatnot.
* `code.py` - the actual code added to the editor which forms the
practical example of using the package.
84 changes: 84 additions & 0 deletions examples/pyasn1/ber_cer_der_codecs/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# pyasn1 decouples ASN.1 types from serialization. The same value can
# be encoded with several codecs: BER (basic), CER (canonical, useful
# for streaming), and DER (canonical, used in X.509, PKCS, etc.).
from pyasn1.type.univ import (
Integer, OctetString, ObjectIdentifier, Sequence, SequenceOf,
)
from pyasn1.type.char import UTF8String
from pyasn1.type.namedtype import NamedTypes, NamedType
from pyasn1.codec.ber.encoder import encode as ber_encode
from pyasn1.codec.cer.encoder import encode as cer_encode
from pyasn1.codec.der.encoder import encode as der_encode
from pyasn1.codec.der.decoder import decode as der_decode


def hexdump(data):
return " ".join(f"{b:02X}" for b in data)


# A SEQUENCE describing a tagged measurement: a sensor OID, a label,
# and an integer reading.
class Measurement(Sequence):
componentType = NamedTypes(
NamedType("sensor", ObjectIdentifier()),
NamedType("label", UTF8String()),
NamedType("reading", Integer()),
)


reading = Measurement()
reading["sensor"] = "1.3.6.1.4.1.99999.1" # made-up enterprise OID
reading["label"] = "kitchen-thermometer"
reading["reading"] = 21

heading("One value, three encodings")
note(
"Each codec produces standards-compliant bytes. BER is the most "
"permissive; CER and DER are canonical (a given value has exactly "
"one valid encoding)."
)

for name, encode in [("BER", ber_encode), ("CER", cer_encode), ("DER", der_encode)]:
substrate = encode(reading)
note(f"<strong>{name}</strong> ({len(substrate)} bytes):")
display(HTML(f"<pre>{hexdump(substrate)}</pre>"), append=True)

heading("A SEQUENCE OF Measurement")
note(
"ASN.1 SEQUENCE OF is the natural fit for a homogeneous list. "
"We build three readings, encode the lot to DER, and round-trip "
"back to inspect the contents."
)


class MeasurementLog(SequenceOf):
componentType = Measurement()


log = MeasurementLog()
samples = [
("1.3.6.1.4.1.99999.1", "kitchen-thermometer", 21),
("1.3.6.1.4.1.99999.2", "garage-thermometer", 8),
("1.3.6.1.4.1.99999.3", "attic-thermometer", 27),
]
for oid, label, value in samples:
item = Measurement()
item["sensor"] = oid
item["label"] = label
item["reading"] = value
log.append(item)

substrate = der_encode(log)
note(f"DER-encoded log is <strong>{len(substrate)}</strong> bytes.")
display(HTML(f"<pre>{hexdump(substrate)}</pre>"), append=True)

decoded_log, _ = der_decode(substrate, asn1Spec=MeasurementLog())
rows = ["<table><tr><th>sensor OID</th><th>label</th><th>reading</th></tr>"]
for item in decoded_log:
rows.append(
f"<tr><td><code>{item['sensor']}</code></td>"
f"<td>{item['label']}</td>"
f"<td>{int(item['reading'])}</td></tr>"
)
rows.append("</table>")
display(HTML("".join(rows)), append=True)
1 change: 1 addition & 0 deletions examples/pyasn1/ber_cer_der_codecs/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pyasn1"]
18 changes: 18 additions & 0 deletions examples/pyasn1/ber_cer_der_codecs/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Lighter setup: same names as cell 1, no IPython shim."""
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
return _display(*args, **kwargs, target=__pyscript_display_target__)


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)

88 changes: 88 additions & 0 deletions examples/pyasn1/encode_decode_record/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""
A first look at pyasn1: defining an ASN.1 SEQUENCE, populating it,
encoding it to DER bytes, and decoding it back into a Python object.

The ASN.1 schema we model here is the classic introductory example:

Record ::= SEQUENCE {
id INTEGER,
room [0] INTEGER OPTIONAL,
house [1] INTEGER DEFAULT 0
}

Docs: https://pyasn1.readthedocs.io/
"""
from IPython.core.display import display, HTML
# pyasn1 imports used by this example.
from pyasn1.type.univ import Integer, Sequence
from pyasn1.type.namedtype import (
NamedTypes, NamedType, OptionalNamedType, DefaultedNamedType,
)
from pyasn1.type.tag import Tag, tagClassContext, tagFormatSimple
from pyasn1.codec.der.encoder import encode as der_encode
from pyasn1.codec.der.decoder import decode as der_decode


def hexdump(data):
"""Return a space-separated hex string of the bytes in `data`."""
return " ".join(f"{b:02X}" for b in data)



# Define the Record schema as a Python class. Each named type maps to a
# field in the SEQUENCE; the context-specific implicit tags ([0], [1])
# are attached via .subtype(implicitTag=...).
class Record(Sequence):
componentType = NamedTypes(
NamedType("id", Integer()),
OptionalNamedType(
"room",
Integer().subtype(
implicitTag=Tag(tagClassContext, tagFormatSimple, 0)
),
),
DefaultedNamedType(
"house",
Integer(0).subtype(
implicitTag=Tag(tagClassContext, tagFormatSimple, 1)
),
),
)


heading("Building a Record value")
note(
"We populate the SEQUENCE much like a dict, then ask pyasn1 for "
"its human-readable form via str()."
)

record = Record()
record["id"] = 123
record["room"] = 321

display(HTML(f"<pre>{str(record)}</pre>"), append=True)

heading("Encoding to DER")
note(
"DER (Distinguished Encoding Rules) gives us a compact, canonical "
"byte representation. Notice how the optional 'room' field is "
"included but the defaulted 'house' field is omitted."
)

substrate = der_encode(record)
display(HTML(f"<pre>DER bytes: {hexdump(substrate)}</pre>"), append=True)
display(HTML(f"<pre>Length: {len(substrate)} bytes</pre>"), append=True)

heading("Decoding DER back into a Record")
note(
"Pass the schema (asn1Spec=Record()) so the decoder knows how to "
"interpret the implicit tags. The defaulted 'house' field comes "
"back as 0 even though it was not present in the bytes."
)

decoded, leftover = der_decode(substrate, asn1Spec=Record())
for field_name in ("id", "room", "house"):
note(f"<code>{field_name}</code> = <strong>{int(decoded[field_name])}</strong>")

note(f"Leftover bytes after decoding: <code>{len(leftover)}</code>")
note(f"Round-trip equal? <strong>{record == decoded}</strong>")
1 change: 1 addition & 0 deletions examples/pyasn1/encode_decode_record/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pyasn1"]
37 changes: 37 additions & 0 deletions examples/pyasn1/encode_decode_record/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Shim setup for the first example. Includes the full IPython shim."""
import sys
import types
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
return _display(
*args, **kwargs, target=__pyscript_display_target__,
)


ipython = types.ModuleType("IPython")
core = types.ModuleType("IPython.core")
core_display = types.ModuleType("IPython.core.display")
core_display.display = display
core_display.HTML = HTML
ipython.core = core
core.display = core_display
ipython.get_ipython = lambda: None
ipython.display = core_display
sys.modules["IPython"] = ipython
sys.modules["IPython.core"] = core
sys.modules["IPython.core.display"] = core_display
sys.modules["IPython.display"] = core_display


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)

95 changes: 95 additions & 0 deletions examples/pyasn1/native_python_bridge/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# The "native" codec converts pyasn1 objects to plain Python dicts/lists
# and back. This is handy for moving data between an ASN.1-shaped wire
# format and the dict/list world of JSON, configs, and tests.

from pyasn1.type.univ import Integer, Sequence, SequenceOf
from pyasn1.type.char import UTF8String
from pyasn1.type.namedtype import NamedTypes, NamedType, OptionalNamedType
from pyasn1.codec.der.encoder import encode as der_encode
from pyasn1.codec.der.decoder import decode as der_decode
from pyasn1.codec.native.encoder import encode as to_python
from pyasn1.codec.native.decoder import decode as from_python


def hexdump(data):
return " ".join(f"{b:02X}" for b in data)


class Address(Sequence):
componentType = NamedTypes(
NamedType("street", UTF8String()),
NamedType("city", UTF8String()),
OptionalNamedType("postcode", UTF8String()),
)


class Contact(Sequence):
componentType = NamedTypes(
NamedType("name", UTF8String()),
NamedType("age", Integer()),
NamedType("address", Address()),
)


class ContactBook(SequenceOf):
componentType = Contact()


heading("From Python dicts into pyasn1")
note(
"Start with ordinary Python data, then hand it to the native "
"decoder along with a schema. pyasn1 builds the corresponding "
"ASN.1 object tree."
)

people = [
{
"name": "Ada Lovelace",
"age": 36,
"address": {"street": "1 Analytical Way", "city": "London"},
},
{
"name": "Grace Hopper",
"age": 85,
"address": {
"street": "42 Compiler Ave",
"city": "Arlington",
"postcode": "22202",
},
},
]

book = from_python(people, asn1Spec=ContactBook())
display(HTML(f"<pre>{str(book)}</pre>"), append=True)

heading("Round-tripping through DER")
note(
"Encode to DER for the wire, decode back, then flatten to native "
"Python with the native encoder. Optional fields that were absent "
"stay absent in the output."
)

wire = der_encode(book)
note(f"DER payload is <strong>{len(wire)}</strong> bytes.")
display(HTML(f"<pre>{hexdump(wire)}</pre>"), append=True)

restored, _ = der_decode(wire, asn1Spec=ContactBook())
as_python = to_python(restored)

# Render the recovered Python structure as an HTML table.
rows = ["<table><tr><th>name</th><th>age</th><th>city</th><th>postcode</th></tr>"]
for entry in as_python:
addr = entry["address"]
rows.append(
f"<tr><td>{entry['name']}</td>"
f"<td>{entry['age']}</td>"
f"<td>{addr['city']}</td>"
f"<td>{addr.get('postcode', '—')}</td></tr>"
)
rows.append("</table>")
display(HTML("".join(rows)), append=True)

note(
"Tip: pair this with <code>pyasn1-modules</code> to work with "
"real-world ASN.1 schemas like X.509 certificates, PKCS, and SNMP."
)
1 change: 1 addition & 0 deletions examples/pyasn1/native_python_bridge/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["pyasn1"]
17 changes: 17 additions & 0 deletions examples/pyasn1/native_python_bridge/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Lighter setup: same names as cell 1, no IPython shim."""
import js
from pyscript import window, HTML, display as _display

js.alert = window.alert


def display(*args, **kwargs):
return _display(*args, **kwargs, target=__pyscript_display_target__)


def heading(text, level=2):
display(HTML(f"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)
5 changes: 5 additions & 0 deletions examples/pyasn1/order.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"encode_decode_record",
"ber_cer_der_codecs",
"native_python_bridge"
]