python-rfc8366-voucher is a Python bindings around the (compact CBOR-encoded) RFC8366 Voucher.
This wraps the latest Rust voucher crate, and is tested for Python 3.10, and 3.11 on Linux and macOS.
(TBA) https://github.com/AnimaGUS-minerva/python-rfc8366-voucher/docs/build/html/index.html
We have used the python-mbedtls project as template for organizing our internal Cython module builds and Sphinx based docs.
$ pip3 install git+https://github.com/AnimaGUS-minerva/python-rfc8366-voucher $ python3 >>> import voucher
To uninstall, try pip3 uninstall python-voucher
.
To locally install and test the python-voucher
module, try:
$ git clone https://github.com/AnimaGUS-minerva/python-rfc8366-voucher $ cd python-rfc8366-voucher $ make # build a `.whl` package $ pip3 install --force-reinstall ./dist/python_voucher-*.whl
In this section, we show how to use the basic parts of the library.
voucher.version
keeps the version string of the Rust-based voucher crate:
>>> import voucher
>>> _ = voucher.version # 'Rust voucher 0.8.8'
Using the voucher.mbedtls_version
module, we can obtain the run-time version
information of the mbed TLS backend:
>>> from voucher import mbedtls_version
>>> _ = mbedtls_version.version # 'mbed TLS 3.0.0'
>>> _ = mbedtls_version.version_info # (3, 0, 0)
Here we introduce the voucher
module abstraction in this library,
along with its API methods used when dealing with the BRSKI voucher attributes.
We then present some practical examples on how to perfrom CBOR encoding/decoding of BRSKI vouchers
with the underlying COSE signing and validation operations also considered.
In each of the examples, we assume that the following preparatory code block has been already executed:
import voucher
from voucher import * # Vrq, Vch, ATTR_*, ...
from voucher import from_cbor
import os
VOUCHER_SAMPLE_DIR = os.path.join(
os.path.dirname(os.path.realpath(__file__)), '../voucher/data')
def read_bytes_from(filepath):
return open(filepath, 'rb').read()
Each voucher.Vrq
and voucher.Vch
class abstracts "Voucher Request" and "Voucher" artifacts of
Constrained BRSKI, respectively. Once the class is instantiated, we can manage its attributes
using the dedicated API methods (.get()
, .set()
, .remove()
, etc.).
These methods operate with ATTR_*
constants that represent the BRSKI voucher attributes.
In this example, we demonstrate how to use the Vrq
class for a "Voucher Request" instance
created by Vrq()
. (Note that all of the methods belonging to the Vrq
instance shown below
can also be called by a "Voucher" instance created by Vch()
.)
# Create an empty voucher request.
vrq = Vrq()
# Add some attributes (the object is subscriptable for "setter" operations).
vrq[ATTR_ASSERTION] = ASSERTION_PROXIMITY # vrq.set(ATTR_ASSERTION, ASSERTION_PROXIMITY)
vrq[ATTR_CREATED_ON] = 1599086034 # vrq.set(ATTR_CREATED_ON, 1599086034)
vrq[ATTR_SERIAL_NUMBER] = '00-D0-E5-F2-00-02' # vrq.set(ATTR_SERIAL_NUMBER, '00-D0-E5-F2-00-02')
# Count attributes.
assert len(vrq) == 3
# Check for specific ones (the object is subscriptable for "getter" operations).
assert vrq[ATTR_CREATED_ON] == 1599086034 # assert vrq.get(ATTR_CREATED_ON) == 1599086034
assert vrq[ATTR_EXPIRES_ON] == None # assert vrq.get(ATTR_EXPIRES_ON) == None
# Remove a specific one.
assert vrq.remove(ATTR_CREATED_ON) == True
# Count attributes again.
assert len(vrq) == 2
# Iterate over everything.
for k, v in vrq:
print(f'vrq[{k}]: {v}')
# The built-in `print()` works for the object (since the `Vrq` class implements the
# `__repr__()` method).
print(vrq)
"""
voucher type: 'vrq'
# of attributes: 2
[ATTR_ASSERTION] ASSERTION_PROXIMITY
[ATTR_SERIAL_NUMBER] b'00-D0-E5-F2-00-02'
COSE signature algorithm: unknown
COSE signature: None
COSE content: None
COSE signer cert: None
"""
To encode a voucher
instance into a compact CBOR-encoded voucher, use the .serialize()
API.
In this example, we instantiate a new voucher request, populate it with some attributes, COSE-sign it, and finally encode it into a CBOR byte string.
# Create a voucher request with five attributes.
vrq = Vrq()
vrq[ATTR_ASSERTION] = ASSERTION_PROXIMITY
vrq[ATTR_CREATED_ON] = 1599086034
vrq[ATTR_NONCE] = bytes([48, 130, 1, 216, 48, 130, 1, 94, 160, 3, 2, 1, 2, 2, 1, 1, 48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 2, 48, 115, 49, 18, 48, 16, 6, 10, 9, 146, 38, 137, 147, 242, 44, 100, 1, 25, 22, 2, 99, 97, 49, 25, 48, 23, 6, 10, 9, 146, 38, 137, 147, 242, 44, 100, 1, 25, 22, 9, 115, 97, 110, 100, 101, 108, 109, 97, 110, 49, 66, 48, 64, 6, 3, 85, 4, 3, 12, 57, 35, 60, 83, 121, 115, 116, 101, 109, 86, 97, 114, 105, 97, 98, 108, 101, 58, 48, 120, 48, 48, 48, 48, 53, 53, 98, 56, 50, 53, 48, 99, 48, 100, 98, 56, 62, 32, 85, 110, 115, 116, 114, 117, 110, 103, 32, 70, 111, 117, 110, 116, 97, 105, 110, 32, 67, 65, 48, 30, 23, 13, 50, 48, 48, 56, 50, 57, 48, 52, 48, 48, 49, 54, 90, 23, 13, 50, 50, 48, 56, 50, 57, 48, 52, 48, 48, 49, 54, 90, 48, 70, 49, 18, 48, 16, 6, 10, 9, 146, 38, 137, 147, 242, 44, 100, 1, 25, 22, 2, 99, 97, 49, 25, 48, 23, 6, 10, 9, 146, 38, 137, 147, 242, 44, 100, 1, 25, 22, 9, 115, 97, 110, 100, 101, 108, 109, 97, 110, 49, 21, 48, 19, 6, 3, 85, 4, 3, 12, 12, 85, 110, 115, 116, 114, 117, 110, 103, 32, 74, 82, 67, 48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0, 4, 150, 101, 80, 114, 52, 186, 159, 229, 221, 230, 95, 246, 240, 129, 111, 233, 72, 158, 129, 12, 18, 7, 59, 70, 143, 151, 100, 43, 99, 0, 141, 2, 15, 87, 201, 124, 148, 127, 132, 140, 178, 14, 97, 214, 201, 136, 141, 21, 180, 66, 31, 215, 242, 106, 183, 228, 206, 5, 248, 167, 76, 211, 139, 58, 163, 16, 48, 14, 48, 12, 6, 3, 85, 29, 19, 1, 1, 255, 4, 2, 48, 0, 48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 2, 3, 104, 0, 48, 101, 2, 49, 0, 135, 158, 205, 227, 138, 5, 18, 46, 182, 247, 44, 178, 27, 195, 210, 92, 190, 230, 87, 55, 112, 86, 156, 236, 35, 12, 164, 140, 57, 241, 64, 77, 114, 212, 215, 85, 5, 155, 128, 130, 2, 14, 212, 29, 79, 17, 159, 231, 2, 48, 60, 20, 216, 138, 10, 252, 64, 71, 207, 31, 135, 184, 115, 193, 106, 40, 191, 184, 60, 15, 136, 67, 77, 157, 243, 247, 168, 110, 45, 198, 189, 136, 149, 68, 47, 32, 55, 237, 204, 228, 133, 91, 17, 218, 154, 25, 228, 232])
vrq[ATTR_PROXIMITY_REGISTRAR_CERT] = bytes([102, 114, 118, 85, 105, 90, 104, 89, 56, 80, 110, 86, 108, 82, 75, 67, 73, 83, 51, 113, 77, 81])
vrq[ATTR_SERIAL_NUMBER] = '00-D0-E5-F2-00-02'
# COSE-sign the voucher request.
KEY_PEM_F2_00_02 = read_bytes_from(
os.path.join(VOUCHER_SAMPLE_DIR, '00-D0-E5-F2-00-02/key.pem'))
vrq.sign(KEY_PEM_F2_00_02, SA_ES256)
# Encode the voucher request.
cbor = vrq.to_cbor()
assert len(cbor) == 630
To decode a COSE-signed CBOR-encoded voucher, use the voucher.from_cbor
method.
In this example, we decode a BRSKI "Voucher" sample in the
00-D0-E5-F2-00-02 constrained voucher directory
into a Vch
instance, COSE-validate it, and iterate through each attribute in the voucher.
VCH_F2_00_02 = read_bytes_from(
os.path.join(VOUCHER_SAMPLE_DIR, '00-D0-E5-F2-00-02/voucher_00-D0-E5-F2-00-02.vch'))
MASA_CRT_F2_00_02 = read_bytes_from(
os.path.join(VOUCHER_SAMPLE_DIR, '00-D0-E5-F2-00-02/masa.crt'))
# Decode the voucher.
vch = from_cbor(VCH_F2_00_02)
# COSE-validate the voucher.
assert vch.validate(MASA_CRT_F2_00_02)
# This voucher has five attributes.
assert len(vch) == 5
for k, v in vch:
print(f'vch[{k}] = {v}')
# Check data belonging to the attribute.
if k == ATTR_ASSERTION:
assert v == ASSERTION_LOGGED
elif k == ATTR_CREATED_ON:
assert v == 1599525239
elif k == ATTR_NONCE:
assert v == bytes([88, 83, 121, 70, 52, 76, 76, 73, 105, 113, 85, 50, 45, 79, 71, 107, 54, 108, 70, 67, 65, 103])
elif k == ATTR_PINNED_DOMAIN_CERT:
assert v[0:4] == bytes([77, 73, 73, 66])
elif k == ATTR_SERIAL_NUMBER:
assert v == b'00-D0-E5-F2-00-02'
else:
assert False