synta 0.2.0

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
#!/usr/bin/env python3
"""
Basic example of using Synta Python bindings

This example demonstrates:
- Decoding ASN.1 data
- Encoding ASN.1 data
- Working with various ASN.1 types
- Parsing X.509 certificates
"""

import base64
import synta
import synta.oids as oids


def example_integers():
    """Example: Working with INTEGERs"""
    print("\n" + "=" * 60)
    print("Example 1: Working with INTEGERs")
    print("=" * 60)

    # Decode an integer
    data = b'\x02\x01\x2A'  # DER-encoded INTEGER 42
    decoder = synta.Decoder(data, synta.Encoding.DER)
    integer = decoder.decode_integer()
    print(f"Decoded INTEGER: {integer.to_int()}")

    # Encode an integer
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_integer(100)
    encoded = encoder.finish()
    print(f"Encoded INTEGER 100: {encoded.hex()}")


def example_oids():
    """Example: Working with Object Identifiers"""
    print("\n" + "=" * 60)
    print("Example 2: Working with Object Identifiers")
    print("=" * 60)

    # Create OID from constant (RSA encryption)
    oid = oids.RSA_ENCRYPTION
    print(f"RSA Encryption OID: {oid}")
    print(f"Components: {oid.components()}")

    # Encode the OID
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_oid(oid)
    encoded = encoder.finish()
    print(f"Encoded: {encoded.hex()}")

    # Decode it back
    decoder = synta.Decoder(encoded, synta.Encoding.DER)
    decoded_oid = decoder.decode_oid()
    print(f"Decoded back: {decoded_oid}")


def example_octet_strings():
    """Example: Working with OCTET STRINGs"""
    print("\n" + "=" * 60)
    print("Example 3: Working with OCTET STRINGs")
    print("=" * 60)

    # Create and encode an octet string
    message = b"Hello, ASN.1!"
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_octet_string(message)
    encoded = encoder.finish()
    print(f"Encoded message: {encoded.hex()}")

    # Decode it back
    decoder = synta.Decoder(encoded, synta.Encoding.DER)
    octet_string = decoder.decode_octet_string()
    decoded_message = octet_string.to_bytes()
    print(f"Decoded message: {decoded_message.decode('utf-8')}")


def example_multiple_values():
    """Example: Encoding and decoding multiple values"""
    print("\n" + "=" * 60)
    print("Example 4: Multiple values in sequence")
    print("=" * 60)

    # Encode multiple values
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_integer(42)
    encoder.encode_boolean(True)
    encoder.encode_octet_string(b"test")
    encoded = encoder.finish()

    print(f"Encoded sequence: {encoded.hex()}")
    print(f"Total length: {len(encoded)} bytes")

    # Decode them back
    decoder = synta.Decoder(encoded, synta.Encoding.DER)

    integer = decoder.decode_integer()
    print(f"  1. INTEGER: {integer.to_int()}")

    boolean = decoder.decode_boolean()
    print(f"  2. BOOLEAN: {boolean.value()}")

    octet_string = decoder.decode_octet_string()
    print(f"  3. OCTET STRING: {octet_string.to_bytes()}")

    print(f"Remaining bytes: {decoder.remaining()}")


def example_string_types():
    """Example: Working with ASN.1 string types"""
    print("\n" + "=" * 60)
    print("Example 5: ASN.1 String Types")
    print("=" * 60)

    # UTF8String
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_utf8_string("Hello, World!")
    encoded = encoder.finish()
    decoder = synta.Decoder(encoded, synta.Encoding.DER)
    s = decoder.decode_utf8_string()
    print(f"UTF8String round-trip: {s.as_str()!r}")

    # PrintableString (A-Z, a-z, digits, and ' ( ) + , - . / : = ?)
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_printable_string("Example Corp")
    encoded = encoder.finish()
    decoder = synta.Decoder(encoded, synta.Encoding.DER)
    s = decoder.decode_printable_string()
    print(f"PrintableString round-trip: {s.as_str()!r}")

    # IA5String (ASCII)
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_ia5_string("user@example.com")
    encoded = encoder.finish()
    decoder = synta.Decoder(encoded, synta.Encoding.DER)
    s = decoder.decode_ia5_string()
    print(f"IA5String round-trip: {s.as_str()!r}")


def example_null():
    """Example: Working with ASN.1 NULL"""
    print("\n" + "=" * 60)
    print("Example 6: ASN.1 NULL")
    print("=" * 60)

    # Encode NULL
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_null()
    encoded = encoder.finish()
    print(f"Encoded NULL: {encoded.hex()}")  # Output: 0500

    # Decode NULL
    decoder = synta.Decoder(encoded, synta.Encoding.DER)
    null = decoder.decode_null()
    print(f"Decoded: {null!r}")


def example_real():
    """Example: Working with ASN.1 REAL (IEEE 754 floating point)"""
    import math
    print("\n" + "=" * 60)
    print("Example 7: ASN.1 REAL")
    print("=" * 60)

    # Zero encodes as a zero-length value (tag + length only)
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_real(0.0)
    data = encoder.finish()
    print(f"Encoded 0.0:      {data.hex()}")  # Output: 0900

    # Special values use a one-byte content
    for name, value in [("math.inf", math.inf), ("-math.inf", -math.inf), ("math.nan", math.nan)]:
        encoder = synta.Encoder(synta.Encoding.DER)
        encoder.encode_real(value)
        data = encoder.finish()
        print(f"Encoded {name:12} {data.hex()}")

    # Round-trip a finite value
    pi = 3.141592653589793
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_real(pi)
    encoded = encoder.finish()
    decoder = synta.Decoder(encoded, synta.Encoding.DER)
    r = decoder.decode_real()
    print(f"Round-trip π:     {r.value()}")

    # Construct directly and test helpers
    r_inf = synta.Real(math.inf)
    print(f"is_infinite:      {r_inf.is_infinite()}")
    print(f"is_finite:        {synta.Real(1.5).is_finite()}")
    print(f"float() cast:     {float(synta.Real(2.5))}")

    # encode_real_object accepts a Real object
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_real_object(synta.Real(1.0))
    data = encoder.finish()
    decoder = synta.Decoder(data, synta.Encoding.DER)
    assert decoder.decode_real().value() == 1.0
    print("encode_real_object round-trip: OK")


def example_decode_any():
    """Example: Dynamic decoding with decode_any"""
    print("\n" + "=" * 60)
    print("Example 8: decode_any — dynamic element parsing")
    print("=" * 60)

    # SEQUENCE { INTEGER 1, BOOLEAN TRUE }
    # 30 06 02 01 01 01 01 ff
    data = b'\x30\x06\x02\x01\x01\x01\x01\xff'
    decoder = synta.Decoder(data, synta.Encoding.DER)
    seq = decoder.decode_any()
    print(f"Decoded SEQUENCE: {seq}")
    print(f"  Element 0: {seq[0]!r}  (type: {type(seq[0]).__name__})")
    print(f"  Element 1: {seq[1]!r}  (type: {type(seq[1]).__name__})")

    # Individual primitives
    for raw in [b'\x02\x01\x2A', b'\x01\x01\xff', b'\x05\x00']:
        decoder = synta.Decoder(raw, synta.Encoding.DER)
        obj = decoder.decode_any()
        print(f"  {raw.hex()}{obj!r}")


# Self-signed RSA certificate for the certificate example
_EXAMPLE_CERT_PEM = """\
MIIDiTCCAnGgAwIBAgIUXhaeS3ad5SJp60GRJU73OQaO0xkwDQYJKoZIhvcNAQEL
BQAwVDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh
bmNpc2NvMQ0wCwYDVQQKDARUZXN0MREwDwYDVQQDDAh0ZXN0LmNvbTAeFw0yNjAy
MjMxMDU0MzRaFw0yNzAyMjMxMDU0MzRaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQI
DAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwEVGVzdDERMA8G
A1UEAwwIdGVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP
9oir0NwIXFZ6gOUo//akzjNjvUhA/V1KSUY0L/iXOGWRHFcdcf4gVhoCgR+DgCV6
bJKKrYPIvfEwmd8DdpPj1WU4Dztb4NNLgxquFZym2Swe0xDLQdtWoIQYerF/ER8D
9Pk0qQ5QVaCO+KB3UKyXiJwcTc/LJnDqEX24mrf0ZH/HqB2GsUE3aI9aW5Lgwm9A
7+gV7FrumaT7fQqpfNucWwlXU2SIRm//JKUrT0MGrh99vmmkGRZK+c9wLfIK+pny
UQxSD1E395bpQTqTWIfcMWti6af3ix3GsWeoXwY+GDfZlZ1w22GjLmSgg1RMhhKZ
9l+QFnI/GtmiXX2pCRZfAgMBAAGjUzBRMB0GA1UdDgQWBBRCjPvAUpiRe0Zs6DTL
K+KLoTZfezAfBgNVHSMEGDAWgBRCjPvAUpiRe0Zs6DTLK+KLoTZfezAPBgNVHRMB
Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQC+xBpTScGlcZGYvwtWLZuF2qRu
o9xhYzHXgIDJglXZ8i70O+ut2WRyI9RJOSMsa7BI2qmc87Ki9ZMCO2QIMQCQo7cB
kZtQvK8iGGHhSwepMORekzdbfUUs7N4YEM3Xako1+4RzL+T1Z3qzQ6nrnQ+gYyQo
GiIFKbYZONu2OlqXGQe5LPwbsPU52GUbttkxodaHgCdP7yKO/l3sDifXpaFEXJhY
5RZqgSG77jymh8YKcY0X9J53OtP1So/IOS1Za137k+eYci6iCB4w511qEhSRZdCy
+3NEoGrBearMcJHttGbMplR6TU6fDFRIUAdelPdWfpfmtl1iElRDwNdQkBBR"""


def example_certificate():
    """Example: Parsing an X.509 certificate"""
    print("\n" + "=" * 60)
    print("Example 9: X.509 Certificate Parsing")
    print("=" * 60)

    der = base64.b64decode(_EXAMPLE_CERT_PEM)
    cert = synta.Certificate.from_der(der)

    print(f"Subject:             {cert.subject}")
    print(f"Issuer:              {cert.issuer}")
    print(f"Version:             v{(cert.version or 0) + 1}")
    print(f"Serial number:       {cert.serial_number}")
    print(f"Not before:          {cert.not_before}")
    print(f"Not after:           {cert.not_after}")
    print(f"Signature algorithm: {cert.signature_algorithm}")
    print(f"Public key alg:      {cert.public_key_algorithm}")
    print(f"Public key length:   {len(cert.public_key)} bytes")


def example_common_oids():
    """Example: Common OIDs used in cryptography"""
    print("\n" + "=" * 60)
    print("Example 10: Common Cryptographic OIDs")
    print("=" * 60)

    common_oids = {
        "RSA Encryption":    oids.RSA_ENCRYPTION,
        "SHA-256":           oids.SHA256,
        "EC Public Key":     oids.EC_PUBLIC_KEY,
        "secp256r1 (P-256)": oids.EC_CURVE_P256,
        "Ed25519":           oids.ED25519,
    }

    for name, oid in common_oids.items():
        encoder = synta.Encoder(synta.Encoding.DER)
        encoder.encode_oid(oid)
        encoded = encoder.finish()
        print(f"{name:20} {str(oid):30} ({len(encoded)} bytes)")


def example_bit_string():
    """Example: Working with ASN.1 BIT STRINGs"""
    print("\n" + "=" * 60)
    print("Example 11: ASN.1 BIT STRING")
    print("=" * 60)

    # Construct a BIT STRING: value 0xff 0x00, 0 unused bits → 16 significant bits
    bs = synta.BitString(b'\xff\x00', 0)
    print(f"Bit length:    {bs.bit_len()}")
    print(f"Unused bits:   {bs.unused_bits()}")
    print(f"Bytes:         {bs.to_bytes().hex()}")

    # Encode and check the DER output
    encoder = synta.Encoder(synta.Encoding.DER)
    encoder.encode_bit_string(bs)
    encoded = encoder.finish()
    # DER: 03 03 00 ff 00 — tag, length, unused-bits, data
    print(f"DER encoding:  {encoded.hex()}")

    # Decode it back
    decoder = synta.Decoder(encoded, synta.Encoding.DER)
    decoded = decoder.decode_bit_string()
    print(f"Round-trip OK: {decoded.to_bytes().hex() == 'ff00'}")

    # With unused bits: a 13-bit value (last 3 bits of 0x80 are padding)
    bs2 = synta.BitString(b'\xff\x80', 3)
    encoder2 = synta.Encoder(synta.Encoding.DER)
    encoder2.encode_bit_string(bs2)
    encoded2 = encoder2.finish()
    print(f"13-bit value:  {encoded2.hex()}  ({bs2.bit_len()} bits)")


def example_additional_string_types():
    """Example: NumericString, TeletexString, VisibleString, GeneralString,
    UniversalString, and BMPString"""
    print("\n" + "=" * 60)
    print("Example 12: Additional ASN.1 String Types")
    print("=" * 60)

    # NumericString — only digits and spaces (tag 18 / 0x12)
    enc = synta.Encoder(synta.Encoding.DER)
    enc.encode_numeric_string("12345 67890")
    data = enc.finish()
    s = synta.Decoder(data, synta.Encoding.DER).decode_numeric_string()
    print(f"NumericString:    {s.as_str()!r}  (tag {data[0]:#04x})")

    # TeletexString / T61String — arbitrary bytes, used in legacy X.509 subjects (tag 20 / 0x14)
    enc = synta.Encoder(synta.Encoding.DER)
    enc.encode_teletex_string(b"Legacy Corp")
    data = enc.finish()
    s = synta.Decoder(data, synta.Encoding.DER).decode_teletex_string()
    print(f"TeletexString:    {s.to_bytes()!r}  (tag {data[0]:#04x})")

    # VisibleString — printable ASCII excluding space (tag 26 / 0x1a)
    enc = synta.Encoder(synta.Encoding.DER)
    enc.encode_visible_string("Hello,World!")
    data = enc.finish()
    s = synta.Decoder(data, synta.Encoding.DER).decode_visible_string()
    print(f"VisibleString:    {s.as_str()!r}  (tag {data[0]:#04x})")

    # GeneralString — used in Kerberos realms and principal names (tag 27 / 0x1b)
    enc = synta.Encoder(synta.Encoding.DER)
    enc.encode_general_string(b"EXAMPLE.COM")
    data = enc.finish()
    s = synta.Decoder(data, synta.Encoding.DER).decode_general_string()
    print(f"GeneralString:    {s.to_bytes()!r}  (tag {data[0]:#04x})")

    # UniversalString — UCS-4 encoding, 4 bytes per character (tag 28 / 0x1c)
    enc = synta.Encoder(synta.Encoding.DER)
    enc.encode_universal_string("Hi")
    data = enc.finish()
    s = synta.Decoder(data, synta.Encoding.DER).decode_universal_string()
    print(f"UniversalString:  {s.as_str()!r}  (tag {data[0]:#04x}, {len(data)-2} content bytes)")

    # BMPString — UCS-2 encoding, 2 bytes per character; BMP plane only (tag 30 / 0x1e)
    enc = synta.Encoder(synta.Encoding.DER)
    enc.encode_bmp_string("café")
    data = enc.finish()
    s = synta.Decoder(data, synta.Encoding.DER).decode_bmp_string()
    print(f"BMPString:        {s.as_str()!r}  (tag {data[0]:#04x}, {len(data)-2} content bytes)")


def example_structured_types():
    """Example: SEQUENCE, SET, EXPLICIT TAG, and RawElement"""
    print("\n" + "=" * 60)
    print("Example 13: Structured types and unknown tags")
    print("=" * 60)

    # Build a SEQUENCE containing INTEGER 42 and BOOLEAN TRUE
    inner = synta.Encoder(synta.Encoding.DER)
    inner.encode_integer(42)
    inner.encode_boolean(True)
    inner_bytes = inner.finish()

    outer = synta.Encoder(synta.Encoding.DER)
    outer.encode_sequence(inner_bytes)
    seq_encoded = outer.finish()
    print(f"SEQUENCE {{{inner_bytes.hex()}}}: {seq_encoded.hex()}")

    # Decode the SEQUENCE — decode_sequence returns a child Decoder
    decoder = synta.Decoder(seq_encoded, synta.Encoding.DER)
    child = decoder.decode_sequence()
    n = child.decode_integer()
    b = child.decode_boolean()
    print(f"  Decoded: INTEGER={n.to_int()}, BOOLEAN={b.value()}")

    # EXPLICIT TAG wrapping: [1] EXPLICIT INTEGER 99
    inner2 = synta.Encoder(synta.Encoding.DER)
    inner2.encode_integer(99)
    tagged_encoded = synta.Encoder(synta.Encoding.DER)
    tagged_encoded.encode_explicit_tag(1, "Context", inner2.finish())
    tagged_bytes = tagged_encoded.finish()
    print(f"[1] EXPLICIT INTEGER 99: {tagged_bytes.hex()}")

    decoder2 = synta.Decoder(tagged_bytes, synta.Encoding.DER)
    child2 = decoder2.decode_explicit_tag(1)
    print(f"  Decoded: INTEGER={child2.decode_integer().to_int()}")

    # RawElement — decode_any returns RawElement for tags synta doesn't know
    # Tag 25 (0x19) = ObjectDescriptor — not implemented
    raw_tlv = bytes([0x19, 0x03, ord('a'), ord('b'), ord('c')])
    val = synta.Decoder(raw_tlv, synta.Encoding.DER).decode_any()
    print(f"Unknown tag 25: {type(val).__name__}"
          f"  tag={val.tag_number}, class={val.tag_class}"
          f", data={val.data!r}")


def main():
    """Run all examples"""
    print("=" * 60)
    print("Synta Python Bindings - Examples")
    print("=" * 60)

    example_integers()
    example_oids()
    example_octet_strings()
    example_multiple_values()
    example_string_types()
    example_null()
    example_real()
    example_decode_any()
    example_certificate()
    example_common_oids()
    example_bit_string()
    example_additional_string_types()
    example_structured_types()

    print("\n" + "=" * 60)
    print("Examples completed successfully!")
    print("=" * 60)


if __name__ == '__main__':
    main()