. "$BST_LIB" # BST-TIMEOUT: 600 # the interpreter is one large static image and this suite # # starts it twice; on a loaded guest a single start has # # been minutes, and the runner's 60s default would report # # the port as broken when what ran out was the clock. # # python3-asn1crypto 1.5.1 (poky meta/recipes-devtools/python, `inherit pypi # setuptools3', sdist sha256 13ae3850...) on wasm32-hwjs -- the fast, pure-Python # ASN.1 parser and serialiser that is the DER/BER layer under X.509, PKCS#1/7/8/12, # CMS, OCSP, TSP and CRL handling. # # WHY PURENESS IS THE LOAD-BEARING PROPERTY HERE, not a footnote. This image's # python3 is one static binary: lib-dynload is empty by construction and this arm # has no dynamic loader at all (BRINTOS-1476), so nothing on this image can import # an out-of-tree .so. asn1crypto 1.5.1 ships no C at all -- no .c, no .pyx, no # compiled speedup -- which is what makes it portable here without a patch. A # version bump that grew one would not fail at build time; it would install into # site-packages and die at `import asn1crypto' on target. The no-.so case below # asserts that negative directly, and the empty lib-dynload beside it is the # MECHANISM, so a reader can see why the negative matters without leaving the file. # # CTYPES, which the recipe RDEPENDS on and which CANNOT WORK on this image. poky's # recipe lists python3-ctypes among the stdlib splits. That split is packaged here # and its Lib/ctypes is installed, but _ctypes is configured out of this # interpreter, so `import ctypes' raises ModuleNotFoundError: No module named # '_ctypes'. It costs this port nothing because asn1crypto never imports it -- # `grep -rn ctypes asn1crypto/' over the 1.5.1 sdist is empty, on every arm and # every code path, not merely at module scope. The RDEPENDS is poky being # conservative about a package that does not need it. The no-ctypes case below # pins that, so a version bump that grew a ctypes import is a red here rather # than an ImportError in whatever imports asn1crypto first. # # HOW THE FIXTURES ARE PINNED. Five DER blobs -- an X.509 certificate, a PKCS#8 # private key, a CMS SignedData message, an OCSP response and a CRL -- carried in # $BST_DATA/python3-asn1crypto-fixtures.b64 and produced once by OPENSSL 3.0.13, # not by asn1crypto. That provenance is the whole point: a decoder checked against # blobs its own encoder wrote agrees with itself whatever it puts on the wire, # while these were written by an independent implementation of X.690. The field # expectations below are literals read off those blobs with `openssl x509 -text' # and friends, not this module's output read back. # # THE RE-ENCODE CASES ARE THE ONES A STUB CANNOT PASS. Each fixture is decoded and # re-serialised with .dump(force=True) -- which rebuilds the octets from the parsed # values rather than returning the retained original -- and compared byte-for-byte # with the input. A module that parsed nothing and returned a plausible object # satisfies a field read; it cannot reproduce 929 octets of somebody else's DER. # # The semantic values come from one probe under $BST_DATA, run by the interpreter # under test, compared line for line against the SAME probe's output on an x86-64 # control (CPython 3.12.3, asn1crypto 1.5.1, recorded 2026-09-07). Nothing in the # probe depends on pointer width, byte order, the platform string, the clock or # hash randomisation, which is what makes "identical to x86-64" a claim rather than # a transcription of whatever this arm happened to do. One interpreter start covers # all of them deliberately -- a `python3 -c' per case would be ninety start-ups of a # large static image on a guest where one has taken minutes. # # NOT COVERED HERE, on purpose: signature verification. asn1crypto parses and # serialises; it computes no hashes and checks no signatures by design, and the # certificate, CMS and OCSP fixtures below are therefore asserted as STRUCTURES, # not as trusted objects. There is no ptest in this recipe to skip. require python3 python3-asn1crypto/present SITE=/usr/lib/python3.12/site-packages SCRATCH=${TMPDIR:-/tmp}/bst-python3-asn1crypto.$$ mkdir -p "$SCRATCH" 2>/dev/null # --------------------------------------------------------- the deliverable --- # The package as files, not as an import, so a red separates "not installed" from # "installed and does not work". One file per structure family the suite goes on to # drive: a partial install that dropped `ocsp.py' would still satisfy # `import asn1crypto'. check python3-asn1crypto/package-dir test -d "$SITE/asn1crypto" check python3-asn1crypto/core-module test -f "$SITE/asn1crypto/core.py" check python3-asn1crypto/parser-module test -f "$SITE/asn1crypto/parser.py" check python3-asn1crypto/x509-module test -f "$SITE/asn1crypto/x509.py" check python3-asn1crypto/keys-module test -f "$SITE/asn1crypto/keys.py" check python3-asn1crypto/cms-module test -f "$SITE/asn1crypto/cms.py" check python3-asn1crypto/ocsp-module test -f "$SITE/asn1crypto/ocsp.py" check python3-asn1crypto/crl-module test -f "$SITE/asn1crypto/crl.py" check python3-asn1crypto/algos-module test -f "$SITE/asn1crypto/algos.py" check python3-asn1crypto/pem-module test -f "$SITE/asn1crypto/pem.py" check python3-asn1crypto/pkcs12-module test -f "$SITE/asn1crypto/pkcs12.py" check python3-asn1crypto/tsp-module test -f "$SITE/asn1crypto/tsp.py" # The fixtures the probe reads. Their absence would make every semantic case below # empty, which reads as a broken decoder rather than as a payload that never # shipped -- so it is named here, once, as its own case. check python3-asn1crypto/fixtures-present test -f "$BST_DATA/python3-asn1crypto-fixtures.b64" check python3-asn1crypto/probe-present test -f "$BST_DATA/python3-asn1crypto-probe.py" # THE NO-.so ASSERTION, and the mechanism that makes it matter. The first case is # the ticket's requirement: nothing asn1crypto owns may be a compiled extension. # The second is why -- lib-dynload is empty on this image, so there is nowhere an # extension could be loaded from even if one shipped. # # Both are `find' over a directory, so both would pass VACUOUSLY against a # directory that is not there -- the one way this pair could report a clean port on # an image carrying no python3 at all. Each is preceded by the existence case that # makes its emptiness mean something. check python3-asn1crypto/site-packages-dir test -d "$SITE" check_eq python3-asn1crypto/no-so "" \ "$(find "$SITE/asn1crypto" -name '*.so' 2>/dev/null)" check python3-asn1crypto/lib-dynload-dir test -d /usr/lib/python3.12/lib-dynload check_eq python3-asn1crypto/lib-dynload-empty "" \ "$(find /usr/lib/python3.12/lib-dynload -name '*.so' 2>/dev/null)" # The module resolves to the package the recipe installed, from the interpreter's # own side. `import asn1crypto' succeeding from some other path -- a stray copy, a # namespace package -- would satisfy every case below while the port had installed # nothing. check_eq python3-asn1crypto/import-path "$SITE/asn1crypto/__init__.py" \ "$(python3 -c 'import asn1crypto; print(asn1crypto.__file__)' 2>&1 | tail -1)" # _ctypes is absent from this interpreter (see the header). This case pins that # asn1crypto does not care: the whole package imports with ctypes unavailable. # Importing every submodule, not just the top level, is deliberate -- a ctypes # import in ocsp.py would be invisible to `import asn1crypto' alone. check_eq python3-asn1crypto/no-ctypes "ok" \ "$(python3 -c 'import asn1crypto.x509, asn1crypto.keys, asn1crypto.cms, asn1crypto.ocsp, asn1crypto.crl, asn1crypto.pkcs12, asn1crypto.tsp, asn1crypto.csr, asn1crypto.pdf, asn1crypto.pem; import sys; print("bad" if "ctypes" in sys.modules else "ok")' 2>&1 | tail -1)" # ------------------------------------------------------------ the probe run --- # Forked, so a failure to stage the exec costs this file its cases rather than the # run its terminator. PROBE_OUT=$SCRATCH/probe.out ( python3 "$BST_DATA/python3-asn1crypto-probe.py" > "$PROBE_OUT" 2>&1 ) # field : the value the probe printed for , empty if it never got # there. Empty is a red against every expectation below, which is the intent -- a # probe that died half-way must not leave the cases it never reached looking # untested. field() { sed -n "s/^$1 //p" "$PROBE_OUT" | head -1; } # THE VERSION THE RECIPE PINS. poky builds asn1crypto ${PV}; a mismatch means the # recipe and the installed module have gone out of step and every expectation # below describes a different release. check_eq python3-asn1crypto/version "1.5.1" "$(field version)" check_eq python3-asn1crypto/version-info "1.5.1" "$(field version-info)" check_eq python3-asn1crypto/module-is-source "py" "$(field file-suffix)" # The fixtures arrived whole. A payload truncated in transit would decode to # something and fail the field cases with a misleading cause. check_eq python3-asn1crypto/fixture-len-cert "929" "$(field fixture-len-cert)" check_eq python3-asn1crypto/fixture-len-pkcs8 "1218" "$(field fixture-len-pkcs8)" check_eq python3-asn1crypto/fixture-len-cms "2233" "$(field fixture-len-cms)" check_eq python3-asn1crypto/fixture-len-ocsp "1496" "$(field fixture-len-ocsp)" check_eq python3-asn1crypto/fixture-len-crl "486" "$(field fixture-len-crl)" # ------------------------------------------------- X.509, field by field --- # The serial is 0x0102030405060708, set explicitly when the fixture was made, so # it exercises a multi-octet INTEGER whose value a truncating decoder gets wrong in # a way a small serial would hide. check_eq python3-asn1crypto/cert-serial "72623859790382856" "$(field cert-serial)" check_eq python3-asn1crypto/cert-version "v3" "$(field cert-version)" check_eq python3-asn1crypto/cert-sig-algo "sha256_rsa" "$(field cert-sig-algo)" check_eq python3-asn1crypto/cert-sig-algo-oid "1.2.840.113549.1.1.11" "$(field cert-sig-algo-oid)" # The RDNs, one case per attribute type. This is a SEQUENCE OF SET OF # AttributeTypeAndValue with PrintableString and UTF8String both present, so a # decoder that mishandled either string type or flattened the SET would be a red # on exactly one of these rather than on all of them. check_eq python3-asn1crypto/cert-subject-cn "brintOS Test CA" "$(field cert-subject-cn)" check_eq python3-asn1crypto/cert-subject-o "Emcraft Systems" "$(field cert-subject-o)" check_eq python3-asn1crypto/cert-subject-ou "brintOS" "$(field cert-subject-ou)" check_eq python3-asn1crypto/cert-subject-c "RU" "$(field cert-subject-c)" check_eq python3-asn1crypto/cert-subject-st "Moscow" "$(field cert-subject-st)" check_eq python3-asn1crypto/cert-issuer-cn "brintOS Test CA" "$(field cert-issuer-cn)" check_eq python3-asn1crypto/cert-self-signed "True" "$(field cert-self-signed)" # UTCTime is where a decoder can be plausibly wrong by a century: the fixture's # years are 26 and 36, and RFC 5280 4.1.2.5.1 puts both in the 2000s. The tzinfo # case beside them says the instants are UTC rather than naive, which is what makes # a validity comparison mean anything. check_eq python3-asn1crypto/cert-not-before "2026-09-07T23:15:00+00:00" "$(field cert-not-before)" check_eq python3-asn1crypto/cert-not-after "2036-09-04T23:15:00+00:00" "$(field cert-not-after)" check_eq python3-asn1crypto/cert-not-before-tz "True" "$(field cert-not-before-tz)" check_eq python3-asn1crypto/cert-pubkey-algo "rsa" "$(field cert-pubkey-algo)" check_eq python3-asn1crypto/cert-pubkey-bits "2048" "$(field cert-pubkey-bits)" # The certificate's own SHA-256, which asn1crypto computes over the bytes it holds. # It is the fingerprint `openssl x509 -fingerprint -sha256' prints for this # fixture, so it ties the object on target to the blob in the repository. check_eq python3-asn1crypto/cert-sha256 \ "eb1325c36cb8920f5c313c11890c4764e4f1ecbf0c492046e9c6bf61bb8dbbc7" "$(field cert-sha256)" # The extensions, by OID and by parsed value. basicConstraints (2.5.29.19), # subjectKeyIdentifier (2.5.29.14) and authorityKeyIdentifier (2.5.29.35) are the # three OpenSSL puts on a self-signed CA, and `cert.ca' is the parsed answer a # path-building consumer acts on. check_eq python3-asn1crypto/cert-ext-count "3" "$(field cert-ext-count)" check_eq python3-asn1crypto/cert-ext-oids "2.5.29.14,2.5.29.19,2.5.29.35" "$(field cert-ext-oids)" check_eq python3-asn1crypto/cert-ca "True" "$(field cert-ca)" check_eq python3-asn1crypto/cert-key-identifier \ "fc123b01f95e837507f59d7480502e33e74590f8" "$(field cert-key-identifier)" # This fixture carries NO keyUsage. "absent" and "the extension permits nothing" # are opposite answers to a consumer deciding whether a key may sign, so the # accessor must report absence rather than an empty set. check_eq python3-asn1crypto/cert-key-usage-absent "absent" "$(field cert-key-usage)" # ------------------------------------------------------ PKCS#8 private key --- check_eq python3-asn1crypto/pkcs8-version "0" "$(field pkcs8-version)" check_eq python3-asn1crypto/pkcs8-algo "rsa" "$(field pkcs8-algo)" check_eq python3-asn1crypto/pkcs8-algo-oid "1.2.840.113549.1.1.1" "$(field pkcs8-algo-oid)" check_eq python3-asn1crypto/pkcs8-bit-size "2048" "$(field pkcs8-bit-size)" check_eq python3-asn1crypto/pkcs8-algorithm "rsa" "$(field pkcs8-algorithm)" # The inner RSAPrivateKey is an OCTET STRING whose content is itself DER: parsing # it is a second decode nested in the first, and `two-prime' is the version field # of the structure that comes out. check_eq python3-asn1crypto/pkcs8-rsa-version "two-prime" "$(field pkcs8-rsa-version)" # The cross-check that says the two fixtures are ONE key pair rather than two # blobs that each happen to decode: the private key's modulus is the # certificate's. Both sides come through a nested parse, so a decoder that # returned plausible-but-wrong integers fails here even where each blob read # cleanly on its own. check_eq python3-asn1crypto/pkcs8-modulus-matches-cert "True" "$(field pkcs8-modulus-matches-cert)" # ---------------------------------------------------------- CMS SignedData --- check_eq python3-asn1crypto/cms-content-type "signed_data" "$(field cms-content-type)" check_eq python3-asn1crypto/cms-version "v1" "$(field cms-version)" check_eq python3-asn1crypto/cms-digest-algos "sha256" "$(field cms-digest-algos)" check_eq python3-asn1crypto/cms-encap-type "data" "$(field cms-encap-type)" # The encapsulated payload, recovered as bytes. This is the one case in the file # that reads user data back out of a container, and it is exact. check_eq python3-asn1crypto/cms-payload "brintOS asn1crypto fixture payload" "$(field cms-payload)" check_eq python3-asn1crypto/cms-signer-count "1" "$(field cms-signer-count)" check_eq python3-asn1crypto/cms-cert-count "2" "$(field cms-cert-count)" check_eq python3-asn1crypto/cms-signer-version "v1" "$(field cms-signer-version)" check_eq python3-asn1crypto/cms-signer-digest "sha256" "$(field cms-signer-digest)" check_eq python3-asn1crypto/cms-signer-sig-algo "rsassa_pkcs1v15" "$(field cms-signer-sig-algo)" # The SignerIdentifier is a CHOICE; taking the issuerAndSerialNumber arm and # reading both halves of it is what says the CHOICE resolved to the right member # rather than to whichever one came first. check_eq python3-asn1crypto/cms-signer-serial "4096" "$(field cms-signer-serial)" check_eq python3-asn1crypto/cms-signer-issuer-cn "brintOS Test CA" "$(field cms-signer-issuer-cn)" # ------------------------------------------------------------ OCSP response --- check_eq python3-asn1crypto/ocsp-status "successful" "$(field ocsp-status)" check_eq python3-asn1crypto/ocsp-version "v1" "$(field ocsp-version)" check_eq python3-asn1crypto/ocsp-responder-cn "brintOS Test CA" "$(field ocsp-responder-cn)" check_eq python3-asn1crypto/ocsp-response-count "1" "$(field ocsp-response-count)" check_eq python3-asn1crypto/ocsp-cert-serial "4096" "$(field ocsp-cert-serial)" check_eq python3-asn1crypto/ocsp-hash-algo "sha1" "$(field ocsp-hash-algo)" # The two CertID hashes, as `openssl ocsp -resp_text' prints them. They are # OCTET STRINGs of a fixed length, so a decoder off by one octet is a red here # and nowhere else. check_eq python3-asn1crypto/ocsp-issuer-name-hash \ "34C8A6F78B63DEBAEF29258D7F3FD61B7CC40DCD" "$(field ocsp-issuer-name-hash)" check_eq python3-asn1crypto/ocsp-issuer-key-hash \ "FC123B01F95E837507F59D7480502E33E74590F8" "$(field ocsp-issuer-key-hash)" # CertStatus is a CHOICE of three implicitly-tagged alternatives, two of them # empty. `revoked' is the only one carrying content, so reading the reason out of # it is what says the tag was resolved rather than guessed. check_eq python3-asn1crypto/ocsp-cert-status "revoked" "$(field ocsp-cert-status)" check_eq python3-asn1crypto/ocsp-revocation-reason "key_compromise" "$(field ocsp-revocation-reason)" check_eq python3-asn1crypto/ocsp-sig-algo "sha256_rsa" "$(field ocsp-sig-algo)" # -------------------------------------------------------------------- CRL --- check_eq python3-asn1crypto/crl-version "v2" "$(field crl-version)" check_eq python3-asn1crypto/crl-sig-algo "sha256_rsa" "$(field crl-sig-algo)" check_eq python3-asn1crypto/crl-issuer-cn "brintOS Test CA" "$(field crl-issuer-cn)" check_eq python3-asn1crypto/crl-entry-count "1" "$(field crl-entry-count)" check_eq python3-asn1crypto/crl-revoked-serial "4096" "$(field crl-revoked-serial)" check_eq python3-asn1crypto/crl-crl-number "1" "$(field crl-crl-number)" check_eq python3-asn1crypto/crl-entry-ext "crl_reason" "$(field crl-entry-ext)" # The reason code is a nested parse again: an extnValue OCTET STRING whose content # is an ENUMERATED. `key_compromise' is asn1crypto's name for value 1. check_eq python3-asn1crypto/crl-reason "key_compromise" "$(field crl-reason)" # The revoked serial is the CMS signer's serial: the fixtures describe one issued # certificate seen through three different structures, so a decoder returning # plausible-but-wrong integers cannot make all three agree. check_eq python3-asn1crypto/crl-issuer-serial-matches-cms "True" \ "$(field crl-issuer-serial-matches-cms)" # ------------------------------------------------- byte-identical re-encode --- # .dump() returns the retained original, so these say the decode consumed exactly # the input and kept it whole -- including any octet no field case above reads. for f in cert pkcs8 cms ocsp crl; do check_eq "python3-asn1crypto/roundtrip-$f" "True" "$(field "roundtrip-$f")" done # .dump(force=True) re-serialises from the PARSED VALUES instead, so these are the # cases that say the ENCODER agrees octet for octet with OpenSSL's. This is the # assertion a stub cannot pass and the reason the fixtures were made by a # different implementation. for f in cert pkcs8 cms ocsp crl; do check_eq "python3-asn1crypto/reencode-$f" "True" "$(field "reencode-$f")" done # --------------------------------------------------------------- PEM armour --- # The text form every one of these blobs is shipped in. `detect' must say yes to # the armoured form and no to raw DER -- a detector that said yes to both would # send binary through the base64 path. check_eq python3-asn1crypto/pem-first-line "-----BEGIN CERTIFICATE-----" "$(field pem-first-line)" check_eq python3-asn1crypto/pem-type "CERTIFICATE" "$(field pem-type)" check_eq python3-asn1crypto/pem-roundtrip "True" "$(field pem-roundtrip)" check_eq python3-asn1crypto/pem-detect "True" "$(field pem-detect)" check_eq python3-asn1crypto/pem-detect-der "False" "$(field pem-detect-der)" # ---------------------------------------------------------- the low parser --- # parse() reads one TLV with no spec at all -- the path an unknown structure takes. # The header is pinned as hex because it is where the long-form length lives: 0x82 # says two length octets follow, and 0x039d is 925, which must equal the content # length reported beside it. A parser that mis-read the long form would still # return a plausible tag. check_eq python3-asn1crypto/parse-class "0" "$(field parse-class)" check_eq python3-asn1crypto/parse-method "1" "$(field parse-method)" check_eq python3-asn1crypto/parse-tag "16" "$(field parse-tag)" check_eq python3-asn1crypto/parse-header-hex "3082039d" "$(field parse-header-hex)" check_eq python3-asn1crypto/parse-content-len "925" "$(field parse-content-len)" # Empty, because the fixture is exactly one structure. A non-empty trailer would # mean the decode stopped early and dropped the tail in silence -- the failure a # round-trip-only check cannot see. check_eq python3-asn1crypto/parse-trailer-len "0" "$(field parse-trailer-len)" # ------------------------------------------------------------------ refusals --- # Damaged input must RAISE rather than return a partial object, which is the # dangerous outcome for a parser fed hostile input. The probe reports # `returned::' when it did not, and both such cases below are pinned # to what asn1crypto ACTUALLY does rather than to what it ought to do. check_eq python3-asn1crypto/bad-truncated "ValueError" "$(field bad-truncated)" check_eq python3-asn1crypto/bad-truncated-msg \ "Insufficient data - 925 bytes requested but only 396 availab" "$(field bad-truncated-msg)" check_eq python3-asn1crypto/bad-empty "ValueError" "$(field bad-empty)" check_eq python3-asn1crypto/bad-empty-msg \ "Insufficient data - 1 bytes requested but only 0 available" "$(field bad-empty-msg)" # The right bytes read as the wrong structure: a certificate is a constructed # SEQUENCE and an INTEGER is primitive, so the method bit is what refuses it. check_eq python3-asn1crypto/bad-wrong-tag "ValueError" "$(field bad-wrong-tag)" # A primitive-encoded SEQUENCE. X.690 8.9.1 requires constructed; a decoder that # ignored the method bit would accept a structure nothing else on the wire will. check_eq python3-asn1crypto/bad-primitive-sequence "ValueError" "$(field bad-primitive-sequence)" # An OID whose length octets promise more than the buffer holds -- the refusal # that DOES fire on an OID, and the case that separates "asn1crypto tolerates a # ragged final arc" from "asn1crypto tolerates anything". check_eq python3-asn1crypto/bad-oid-short-buffer "ValueError" "$(field bad-oid-short-buffer)" # TWO PATHS THAT DO NOT RAISE, pinned as the behaviours they are. # # Indefinite-length BER (0x80 length, end-of-contents pair) is forbidden by DER, # and asn1crypto ACCEPTS it -- it is a BER parser as well as a DER one, and every # real-world CMS message depends on that. The case pins which of the two behaviours # the module has: a build that started refusing BER would break its callers, and # one that quietly re-encoded BER as DER would be a signature bug. check_eq python3-asn1crypto/ber-indefinite-accepted \ "returned:OrderedDict:OrderedDict({'0': 5})" "$(field ber-indefinite)" # # An OID with the continuation bit set on its LAST octet inside the declared # length. asn1crypto does not refuse it: it yields `1.2' and silently DROPS the # incomplete arc. That is upstream 1.5.1 behaviour on every arm, not something # this port introduced -- it is pinned here, rather than asserted away, so that a # version bump which changes it shows up as a red on this image. check_eq python3-asn1crypto/bad-oid-ragged-arc "returned:str:1.2" "$(field bad-oid)" rm -rf "$SCRATCH" 2>/dev/null