. "$BST_LIB" # BST-TIMEOUT: 1800 # FOUR python3 starts, and they are the whole cost. # # # # 1. `import' -- a bare `import pkcs11'; # # 2. `import-quiet' -- a second whose STDERR is the # # assertion; # # 3. the probe, which takes ONE start for a hundred-odd # # measurements over the pure-Python half; # # 4. `ext-import' -- the C extension, ON ITS OWN. See # # "THE EXTENSION IS DRIVEN IN ITS OWN INTERPRETER" # # below; this start is EXPECTED to die, which is why it # # is not folded into the probe. # # # # `lib-missing-module' reuses no start of its own: it is # # driven inside the same isolated start as `ext-import'. # # # # The work inside each start is milliseconds -- a few # # hundred DER integers and eleven sha256 digests over # # tables of at most 299 members -- against an interpreter # # start that is tens of seconds on this arm. The whole # # probe took 0.14s on an x86-64 control. # # # # 1800 is 450s per start, against the ~60s a python3 start # # has measured at on this arm, and matches the budget # # 99-python3-scrypt carries for three. The margin over # # 99-python3-xxhash's 1200/3 is for start 4: a trapping # # side module is killed by the runtime rather than by a # # return, and nothing here has timed that path. # # # # A suite-timeout red is a batch-wide cost, so if the four # # starts prove slower than that on target the RIGHT fix is # # this number, not fewer cases. # # # # NOT MEASURED ON TARGET YET: this port's first on-target # # run is the batch verify, and the measured elapsed goes in # # the test plan rather than in this comment, which would # # cost an image build to keep the shipped suite # # byte-identical. # # python3-pkcs11 0.7.0 (meta-openembedded/meta-python # recipes-devtools/python/python3-pkcs11_0.7.0.bb, `inherit pypi setuptools3', # PyPI sdist of python-pkcs11, unpatched) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE IS, AND WHY A MACHINE WITH NO HSM CAN STILL TEST MOST OF IT. # ============================================================================ # # PKCS#11 (Cryptoki) is the C interface a smartcard or an HSM presents through a # vendor-supplied shared object. python-pkcs11 is the Pythonic binding for it, # and it is TWO HALVES that are worth keeping apart when reading this file: # # - a Cython extension, `pkcs11/_pkcs11.cpython-312.so', which dlopens the # VENDOR MODULE and calls Cryptoki through it. Nothing here can exercise it # for real: there is no token behind this machine and no vendor module on # the image. # - a PURE-PYTHON half -- `constants.py', `mechanisms.py', `exceptions.py', # `types.py', `defaults.py' and the six codecs under `pkcs11/util/' -- which # is the specification's constant tables, the exception hierarchy, and the # DER key/signature encoders every caller needs BEFORE it ever reaches a # token. That half needs no module, no token and no dlopen, and it is what # this suite measures. # # THE ONE STRUCTURAL FACT THE WHOLE FILE RESTS ON. `import pkcs11' does NOT # import the extension. `pkcs11/__init__.py' line 29 imports `._pkcs11' INSIDE # `lib()', not at module scope -- the module's own docstring calls it "a # preemptive dynamic loading". So the package imports, and everything above # imports, on an interpreter that could not load the extension at all. # # ============================================================================ # THE EXTENSION IS DRIVEN IN ITS OWN INTERPRETER, AND THAT IS A SAFETY # REQUIREMENT RATHER THAN TIDINESS. # ============================================================================ # # On this arm a third-party CPython extension is a wasm SIDE MODULE. On the # batch-2396 image this one does NOT load at all: the loader refuses it before # any code runs, and the refusal is never-silent -- # # HWJS-5 dlopen service failed (policy -> ELIBBAD -80): hwjs-dlopen: # unresolved import env._Py_FatalErrorFunc (function) in # ".../pkcs11/_pkcs11.cpython-312.so" # # so `ext-import' sees an `ImportError', not a trap. That is a RESIDUAL GAP IN # BRINTOS-2046, the ticket that made the dynamic main export the CPython C API, # and not `linux-in-the-browser#184' (the post-load park trap, rc=132) that this # file predicted before it was ever run. Measured on the image, not read off # another port: # # - the main exports 1715 symbols; `_Py_FatalErrorFunc' is not among them, # while every symbol 2046 was filed for (`PyModule_Create2', # `PyArg_ParseTuple', `PyErr_SetString', `PyType_Ready') is; # - of this extension's 424 imports, `_Py_FatalErrorFunc' is the ONLY one the # main is meant to supply and does not. Every other unresolved name is # libc (`malloc', `memcpy', `dlopen', ...), a `__hwjs_*' runtime op, or the # module's own (`PyInit__pkcs11'), all bound from elsewhere; # - the cause is in the derivation, not the symbol. 2046 scans # `Include' for `PyAPI_FUNC(type) name', but CPython 3.12 declares # `PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalErrorFunc(...)' -- an attribute # macro between the return type and the name -- so the scan captures # `_Py_NO_RETURN' as if it were the symbol. `--export-if-defined' then drops # that undefined name silently. EIGHT real symbols are lost this way: # Py_Exit, Py_ExitStatusException, Py_FatalError, _Py_FatalErrorFormat, # _Py_FatalErrorFunc, _Py_FatalRefcountErrorFunc, _PyObject_AssertFailed, # PyThread_exit_thread. Cython's generated module reaches the first through # the `Py_FatalError(msg)' macro, which 3.12 defines as # `_Py_FatalErrorFunc(__func__, (msg))'. # # The fix belongs to BRINTOS-2046's `python3_%.bbappend' -- one regex, skipping # an optional attribute macro -- NOT to this recipe, which is why nothing here # works around it. These cases flip green by themselves when that lands. # # So `ext-import' and `lib-missing-module' run in a python3 start that carries # NOTHING ELSE. That isolation was written against the trap shape and it earns # its keep against this one too: the failure is confined to the four cases that # touch the extension, and the hundred-odd pure-Python measurements are green # beside them. # # ============================================================================ # WHAT WAS MEASURED, ON THE batch-2396 IMAGE. # ============================================================================ # # This block was written as a PREDICTION in phase A, which builds at recipe # scope and never boots an image. It has since been run. What follows is the # measurement; where the two disagreed, the prediction was wrong and is kept # only where the gap is worth knowing. 156 cases, 151 green, 5 red. # # - `import' and the whole probe are GREEN, as predicted. They are pure Python # over asn1crypto, another member of this same round (BRINTOS-2398). # - `ext-import' is RED, and NOT on the cause predicted. The prediction # offered two shapes and named `linux-in-the-browser#184' for both; the # measured shape is the loader refusing the module for an unresolved # `env._Py_FatalErrorFunc', which is a residual gap in BRINTOS-2046's export # derivation and a different defect from #184. Read the block above; it # carries the measurement and the one-regex fix. # - `ext-lib-type' and `lib-missing-names-path' are RED downstream of it, in # the same isolated start. # - `lib-missing-module' is the case the TICKET asks for, and it CANNOT REACH # ITS OWN ASSERTION today -- predicted, and correct, though for the cause # above rather than #184: `pkcs11.lib(path)' imports the extension first, so # the refusal arrives before `dlopen(3)' is ever called, and the case sees # `ImportError' where it wants `RuntimeError'. What it asserts is the answer # for the day the export gap closes: a `RuntimeError' whose message NAMES # THE PATH -- `_pkcs11.pyx' line 1402 raises `RuntimeError(dlfcn.dlerror())' # when `dlopen' returns NULL, and glibc's dlerror names the file it could not # open. It flips green by itself then, with no edit here. The thing it exists # to catch is a binding that ABORTS THE INTERPRETER on a missing module # rather than raising -- a real hazard on a fresh arm, and not # distinguishable from the load failure until that closes, so read it # together with `ext-import'. # # ============================================================================ # THE EXPECTED VALUES ARE THE SPECIFICATION'S, AND THE FIXTURES ARE OpenSSL'S. # ============================================================================ # # The constant tables are asserted TWICE over: a sha256 FINGERPRINT per table # (any value moved, any member added or dropped) and spot literals from PKCS#11 # v2.40 (what makes a fingerprint red legible). Each fingerprint's expected value # was computed on an x86-64 control AFTER cross-checking the tables member by # member against `extern/pkcs11t.h' -- the OASIS Cryptoki header shipped inside # this same sdist, which is a second source rather than a paraphrase of the # Python file. 306 of the 462 members carried by the six spec-derived enums are # defined in that header and ALL 306 AGREED; the remaining 156 are vendor # extensions and later-revision mechanisms the shipped header predates, and rest # on the control alone. The header does not ship in the runtime package -- it is # not inside a Python package -- so the cross-check is offline by necessity. # # The eleven DER fixtures in `data/python3-pkcs11-fixtures.b64' were produced by # OpenSSL 3.0.13, a different implementation, and not by this package: an RSA # key pair, an EC key pair on prime256v1, a DSA parameter set with its public # value, a DH parameter set with a public value derived from it, a self-signed # X.509 certificate over the RSA key, and an ECDSA and a DSA signature. Each is # decoded field by field, then RE-ENCODED and asserted byte-identical -- which is # the assertion a stub cannot pass -- and the fixtures are cross-checked against # each other where they share a secret: the RSA private key's modulus against the # public key's and against p*q, d against e, the certificate's public key against # the RSA fixture, the EC private key's curve against the public key's, y^q mod p # == 1 for DSA, y < p for DH. A codec that returned plausible-but-wrong integers # cannot make those agree. # # ============================================================================ # WHAT IS NOT COVERED, AND WHY THAT IS THE TICKET'S ANSWER RATHER THAN A GAP. # ============================================================================ # # Everything behind a token: slots, sessions, login, key generation, encryption, # signing, wrapping, object search. All of it needs a vendor module this image # does not have and this ticket does not ship one. The upstream `tests/' tree is # excluded from the package by `setup.py' (`find_packages(exclude=["tests"])'), # it needs a live module in `PKCS11_MODULE', and this distro carries no `ptest' # in DISTRO_FEATURES -- so nothing of it is on the image and `no-tests-package' # asserts that absence rather than leaving it unstated. P=python3-pkcs11 require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/pkcs11 DI=$SITE/python_pkcs11-0.7.0.dist-info PROBE=$BST_DATA/python3-pkcs11-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-pkcs11.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. These are file-layer facts, independent of # whether anything imports, so they stay green while the behaviour is held. check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" check $P/init-file test -s "$PKGDIR/__init__.py" check $P/constants-module test -s "$PKGDIR/constants.py" check $P/mechanisms-module test -s "$PKGDIR/mechanisms.py" check $P/exceptions-module test -s "$PKGDIR/exceptions.py" check $P/types-module test -s "$PKGDIR/types.py" check $P/defaults-module test -s "$PKGDIR/defaults.py" check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -s "$DI/METADATA" # The six codecs, each named. `util' is a package of its own and every one of # them is a separate import path a caller reaches directly. check $P/util-dir test -d "$PKGDIR/util" check $P/util-init test -s "$PKGDIR/util/__init__.py" check $P/util-rsa test -s "$PKGDIR/util/rsa.py" check $P/util-ec test -s "$PKGDIR/util/ec.py" check $P/util-dsa test -s "$PKGDIR/util/dsa.py" check $P/util-dh test -s "$PKGDIR/util/dh.py" check $P/util-x509 test -s "$PKGDIR/util/x509.py" # The upstream test tree is excluded by setup.py and needs a live module; this # says so as an absence rather than leaving it unstated. # # Asserted against THIS package's own RECORD, never against `$SITE/tests'. # That path is SHARED and is not pkcs11's to be absent from: a top-level # `tests' package installs there from whichever distribution ships one, and on # the batch-2396 image python3-reedsolo does exactly that -- `tests/perf.py', # `tests/test_reedsolo.py', `tests/test_creedsolo.py', none of them pkcs11's. # The first form went red on reedsolo's files while pkcs11 shipped no test file # at all, i.e. it reported this package's packaging on another package's. # RECORD is the installer's own list of what THIS distribution put on the # image, so `no tests/ entry in RECORD' is the same claim, scoped to the # package that is under test. check $P/dist-info-record test -s "$DI/RECORD" check $P/no-tests-package sh -c "! grep -q '^tests/' '$DI/RECORD'" check $P/no-ptest-dir sh -c "! test -d /usr/lib/python3-pkcs11/ptest" # The CYTHON SOURCES ship inside the runtime package, and that is upstream's # doing rather than the recipe's: setup.py sets include_package_data=True, so # `_pkcs11.pyx', `_errors.pyx', `_utils.pyx', `_pkcs11_defn.pxd' and # `_mswin.pxd' land beside the extension they were compiled from. Asserted as # the files they are; nothing here compiles them, and there is no cython on the # image to do it with. check $P/pyx-source test -s "$PKGDIR/_pkcs11.pyx" check $P/pxd-defn test -s "$PKGDIR/_pkcs11_defn.pxd" # The OASIS headers under extern/ do NOT ship -- they are not inside a Python # package, so include_package_data does not reach them. That matters because the # constant tables below are cross-checked against `extern/pkcs11t.h' OFFLINE for # exactly this reason, and this case is what pins the absence the header block # claims. check $P/no-extern-headers sh -c "! test -e '$PKGDIR/pkcs11t.h'" # The extension exists as a FILE whatever the loader later makes of it, and it # lands INSIDE the package -- setup.py declares Extension('pkcs11._pkcs11'), so # a case looking in site-packages itself would find nothing and read like a # build that produced no extension at all. SO=$PKGDIR/_pkcs11.cpython-312.so check $P/ext-present test -s "$SO" # EXACTLY ONE. setup.py declares a single Extension; a build that started # producing more, or fewer, is visible rather than silent. check_eq $P/ext-count "1" \ "$(ls "$PKGDIR"/_pkcs11*.so 2>/dev/null | wc -l | tr -d ' ')" # ...and it is a WebAssembly module, not a host object and not an empty stub. # `od' is reached through a pipe, so the read is a forked child's and cannot end # this suite (see bst_local). check_eq $P/ext-is-wasm "0061736d" \ "$(head -c 4 "$SO" 2>/dev/null | od -An -tx1 | tr -d ' \n')" # A SIDE MODULE, which is what `dylink.0' names. A wasm object built as a normal # static input would carry no such section and would be exactly as unloadable as # a missing file, while passing the magic-number case above. check_out $P/ext-dylink "dylink" sh -c "head -c 64 '$SO' | tr -d '\\000'" # THE LAZY IMPORT, read off the source. This is the fact every green below # depends on, and reading it here says the same thing before and after # linux-in-the-browser#184 closes -- a future version that moved the import to # module scope would turn this suite red wholesale, and this case is what would # say why. check_out $P/ext-import-is-lazy "from . import _pkcs11" \ sh -c "cat '$PKGDIR/__init__.py'" check_eq $P/ext-not-at-module-scope "0" \ "$(grep -c '^from \._pkcs11\|^import \._pkcs11\|^from \.\_pkcs11' \ "$PKGDIR/__init__.py" 2>/dev/null | tr -d ' ')" # ------------------------------------------------------------- the import --- # EXPECTED GREEN. Pure Python the whole way; see the header. check $P/import python3 -c 'import pkcs11' # ...and it is QUIET. A module that imports while printing to stderr is a # different result from one that imports cleanly. check_eq $P/import-quiet "" \ "$(python3 -c 'import pkcs11' 2>&1)" # ---------------------------------------------------------------- the probe --- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" rm -rf "$W" exit 0 fi bst_ok $P/probe-present if [ ! -f "$BST_DATA/python3-pkcs11-fixtures.b64" ]; then bst_notok $P/fixtures-present "python3-pkcs11-fixtures.b64 did not ship" else bst_ok $P/fixtures-present fi # ONE interpreter start for everything that follows. bst_probe bounds it and # keeps BOTH streams: a probe that dies early otherwise leaves every case below # reporting got:'' and naming a symptom each, with the actual traceback nowhere. bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" OUT="$W/probe" pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # THE EVIDENCE PLANE. If the probe emitted nothing, every case below is reading # an absent file: each would red with got:'' and the set would read exactly like # a broken library, when what actually happened is that no measurement was # taken. The word `unscorable' is PARSED -- brintos_local_verify re-measures a # suite that prints it on a fresh guest rather than trusting a shared boot's # numbers -- so it goes in this structural check, not a comment. if [ -s "$OUT" ]; then bst_ok $P/probe-output else bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" fi check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "115" "$(pv emitted_count)" # The probe reads its fixtures off $BST_DATA; if that failed, every codec case # below is unscorable and this is the line that says so. check_eq $P/fixtures-loaded "" "$(pv fixtures_error)" check_eq $P/fixtures-count "11" "$(pv fixtures_count)" # ------------------------------------------------------------ dependencies --- # asn1crypto is a HARD runtime dependency of every codec below and is another # member of this same round (BRINTOS-2398). Read this before any codec red. check_eq $P/asn1crypto-version "1.5.1" "$(pv asn1crypto_version)" check_eq $P/cached-property-imports "" "$(pv cached_property_import_error)" check_eq $P/import-error "" "$(pv import_error)" # ------------------------------------------------------------------ identity --- check_eq $P/module-file "$PKGDIR/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" check_eq $P/version "0.7.0" "$(pv version_attr)" check_eq $P/lib-callable "yes" "$(pv lib_is_callable)" # The structural fact the header names: importing the package must NOT have # pulled in the extension. If this says `no', every green in this suite became # hostage to linux-in-the-browser#184 and the header's reasoning is stale. check_eq $P/ext-stays-unimported "yes" "$(pv ext_not_imported)" # ------------------------------------------------- the constant tables --- # ONE CASE PER TABLE, each a member count and a sha256 over "NAME=" in # name order. A single value moved, a member added or a member dropped turns the # table's own case red and leaves the other ten green, which is what makes a red # here point at a table rather than at the package. check_eq $P/fp-attribute "103:825b7c8dd85c4f8856e317a67e982ea4" "$(pv fp_attribute)" check_eq $P/fp-object-class "10:d187ed488d63267e6a719aefa7aef042" "$(pv fp_object_class)" check_eq $P/fp-certificate-type "4:9ef9d2cb7d7ce745592cdf98b280227d" "$(pv fp_certificate_type)" check_eq $P/fp-user-type "3:b2505746fcab86dfe59991c13cc5ab5b" "$(pv fp_user_type)" check_eq $P/fp-mechanism-flag "20:f4c6504187c2b11f04e9a542c2d923d1" "$(pv fp_mechanism_flag)" check_eq $P/fp-slot-flag "3:b00f85d5250f0b0d6612271ee509dec6" "$(pv fp_slot_flag)" check_eq $P/fp-token-flag "18:2d25aba96442ecc6b1ef03ed55ae5a61" "$(pv fp_token_flag)" check_eq $P/fp-mechanism "299:f3708ca39b5a29d8b9780fe3977cc581" "$(pv fp_mechanism)" check_eq $P/fp-key-type "43:e99158dadf81dff692e9480cca38c058" "$(pv fp_key_type)" check_eq $P/fp-kdf "9:a5f1186dff4f7fc82d158d9294e2ad88" "$(pv fp_kdf)" check_eq $P/fp-mgf "5:a5aefedc8dacd9f55029fd5fb0d05c82" "$(pv fp_mgf)" # SPOT LITERALS, quoted from PKCS#11 v2.40 and cross-checked against # extern/pkcs11t.h. These are what make a fingerprint red readable: a table # regenerated from the wrong revision, or read with the wrong word size, gets # these wrong first. check_eq $P/cka-class "0x0" "$(pv cka_class)" check_eq $P/cka-token "0x1" "$(pv cka_token)" check_eq $P/cka-modulus "0x120" "$(pv cka_modulus)" check_eq $P/cka-ec-params "0x180" "$(pv cka_ec_params)" check_eq $P/cko-private-key "0x3" "$(pv cko_private_key)" check_eq $P/cko-certificate "0x1" "$(pv cko_certificate)" check_eq $P/ckk-rsa "0x0" "$(pv ckk_rsa)" check_eq $P/ckk-ec "0x3" "$(pv ckk_ec)" check_eq $P/ckm-rsa-pkcs "0x1" "$(pv ckm_rsa_pkcs)" check_eq $P/ckm-sha256-rsa-pkcs "0x40" "$(pv ckm_sha256_rsa_pkcs)" check_eq $P/ckm-aes-cbc-pad "0x1085" "$(pv ckm_aes_cbc_pad)" check_eq $P/ckm-ecdsa "0x1041" "$(pv ckm_ecdsa)" # The vendor range's floor, which is the one value in the mechanism table that # does not fit in the low half and is where a 32-bit truncation shows up. check_eq $P/ckm-vendor-defined "0x80000000" "$(pv ckm_vendor_defined)" check_eq $P/ckc-x-509 "0x0" "$(pv ckc_x_509)" check_eq $P/cku-user "0x1" "$(pv cku_user)" # The FLAG tables are bit fields, so what matters is that each member is a # distinct power of two rather than an ordinal -- a member holding 3 where 4 # belongs is invisible to a spot case and fatal to every mask built from it. check_eq $P/ckf-flags-are-bits "yes" "$(pv ckf_flags_are_bits)" check_eq $P/ckf-encrypt "0x100" "$(pv ckf_encrypt)" check_eq $P/ckf-sign "0x800" "$(pv ckf_sign)" check_eq $P/ckf-token-present "0x1" "$(pv ckf_token_present)" check_eq $P/ckf-login-required "0x4" "$(pv ckf_login_required)" # DEFAULT_KEY_CAPABILITIES is what every decode_* below consults when the caller # passes no capabilities, so it is asserted rather than left implicit in them. check_eq $P/caps-rsa "DECRYPT|ENCRYPT|SIGN|UNWRAP|VERIFY|WRAP" "$(pv caps_rsa)" check_eq $P/caps-ec "DERIVE|SIGN|VERIFY" "$(pv caps_ec)" check_eq $P/caps-aes "DECRYPT|ENCRYPT|SIGN|UNWRAP|VERIFY|WRAP" "$(pv caps_aes)" # -------------------------------------------------------- the exception tree --- # The HIERARCHY is what callers write `except' against; a class reparented to # Exception would still import, still have the right name, and silently stop # being caught by the handler that was meant to catch it. check_eq $P/exc-base-is-runtimeerror "yes" "$(pv exc_base_is_runtimeerror)" check_eq $P/exc-count "76" "$(pv exc_count)" check_eq $P/exc-all-under-base "yes" "$(pv exc_all_under_base)" check_eq $P/exc-names-spot \ "AlreadyInitialized,ArgumentsBad,DeviceError,FunctionFailed,NoSuchToken,PinIncorrect,SessionHandleInvalid,TokenNotPresent,UserNotLoggedIn" \ "$(pv exc_names_spot)" # Re-exported at package level, which is where every documented example catches # them from -- `pkcs11/__init__.py' does `from .exceptions import *'. check_eq $P/exc-reexported "yes" "$(pv exc_reexported)" check_eq $P/exc-already-initialized-is-pkcs11error "yes" \ "$(pv exc_already_initialized_is_pkcs11error)" # ---------------------------------------------------------- biginteger --- # The shared primitive every codec below inherits: PKCS#11 wants an UNSIGNED # big-endian byte string and asn1crypto hands back a signed INTEGER. A leading # zero byte left in, or a short value not padded, breaks every attribute at once, # so it is asserted here on its own before any key. check_eq $P/biginteger-type "bytes" "$(pv biginteger_type)" check_eq $P/biginteger-255 "ff" "$(pv biginteger_255)" check_eq $P/biginteger-high-bit "80" "$(pv biginteger_high_bit)" # ------------------------------------------------------------------ RSA --- check_eq $P/rsa-pub-keys \ "CLASS,ENCRYPT,KEY_TYPE,MODULUS,PUBLIC_EXPONENT,VERIFY,WRAP" \ "$(pv rsa_pub_keys)" check_eq $P/rsa-pub-class "PUBLIC_KEY" "$(pv rsa_pub_class)" check_eq $P/rsa-pub-keytype "RSA" "$(pv rsa_pub_keytype)" check_eq $P/rsa-pub-modulus-bits "2048" "$(pv rsa_pub_modulus_bits)" check_eq $P/rsa-pub-exponent "65537" "$(pv rsa_pub_exponent)" # The modulus itself, as a digest -- the one case that says the integer came back # byte for byte rather than merely the right length. check_eq $P/rsa-pub-modulus-sha256 "de27eed2f4ca8d62fb5355547ff901f8" \ "$(pv rsa_pub_modulus_sha256)" # BYTE-IDENTICAL back to OpenSSL's own DER. This is the assertion a stub cannot # pass. check_eq $P/rsa-pub-roundtrip "yes" "$(pv rsa_pub_roundtrip)" check_eq $P/rsa-priv-keys \ "CLASS,COEFFICIENT,DECRYPT,EXPONENT_1,EXPONENT_2,KEY_TYPE,MODULUS,PRIME_1,PRIME_2,PRIVATE_EXPONENT,PUBLIC_EXPONENT,SIGN,UNWRAP" \ "$(pv rsa_priv_keys)" check_eq $P/rsa-priv-class "PRIVATE_KEY" "$(pv rsa_priv_class)" # THE CROSS-CHECKS. Two fixtures, one key pair: the private key's modulus must be # the public key's, p*q must be that modulus, and d must invert e mod lambda(n). # A decoder returning plausible-but-wrong integers cannot make all three hold. check_eq $P/rsa-priv-modulus-matches-pub "yes" "$(pv rsa_priv_modulus_matches_pub)" check_eq $P/rsa-priv-primes-multiply "yes" "$(pv rsa_priv_primes_multiply)" check_eq $P/rsa-priv-exponents-invert "yes" "$(pv rsa_priv_exponents_invert)" # The capability flags an unqualified decode hands to create_object. check_eq $P/rsa-pub-caps "ENCRYPT,VERIFY,WRAP" "$(pv rsa_pub_caps)" # ------------------------------------------------------------------- EC --- check_eq $P/ec-pub-keys "CLASS,EC_PARAMS,EC_POINT,KEY_TYPE" "$(pv ec_pub_keys)" check_eq $P/ec-pub-keytype "EC" "$(pv ec_pub_keytype)" # EC_PARAMS is the DER of the named-curve OID: prime256v1 is 1.2.840.10045.3.1.7. check_eq $P/ec-pub-params "06082a8648ce3d030107" "$(pv ec_pub_params)" # EC_POINT is the point wrapped in an OCTET STRING, per PKCS#11: 04 41 then the # uncompressed point, itself starting 04. Both layers are asserted, because a # codec that dropped the wrapper produces a value every module rejects and no # length case alone would see. check_eq $P/ec-pub-point-len "67" "$(pv ec_pub_point_len)" check_eq $P/ec-pub-point-head "044104" "$(pv ec_pub_point_head)" check_eq $P/ec-pub-roundtrip "yes" "$(pv ec_pub_roundtrip)" # encode_ec_point=False is the OTHER documented shape -- the raw 65-byte point # with no wrapper, which some modules want. check_eq $P/ec-pub-point-unwrapped-len "65" "$(pv ec_pub_point_unwrapped_len)" check_eq $P/ec-priv-keys "CLASS,EC_PARAMS,KEY_TYPE,VALUE" "$(pv ec_priv_keys)" check_eq $P/ec-priv-params-match-pub "yes" "$(pv ec_priv_params_match_pub)" check_eq $P/ec-named-curve-secp256r1 "06082a8648ce3d030107" \ "$(pv ec_named_curve_secp256r1)" check_eq $P/ec-named-curve-matches-fixture "yes" \ "$(pv ec_named_curve_matches_fixture)" # ECDSA signatures: PKCS#11 wants r||s at fixed width, X.509 wants a DER # SEQUENCE. The fixture is OpenSSL's DER, so the decode is measured against a # foreign encoder and the encode is asserted byte-identical back to it. check_eq $P/ecdsa-sig-p1363-len "64" "$(pv ecdsa_sig_p1363_len)" check_eq $P/ecdsa-sig-p1363 \ "3c7f7a33e15e977c1560eaee1afc6c0443120b8882a8cf456f3dcafc160a34803ef8db72cd7b1d3df8c9d3fb5cf7a33baf37ffd65e53cc6332797ac95fba03ff" \ "$(pv ecdsa_sig_p1363)" check_eq $P/ecdsa-sig-roundtrip "yes" "$(pv ecdsa_sig_roundtrip)" # ------------------------------------------------------------------ DSA --- check_eq $P/dsa-params-keys "BASE,PRIME,SUBPRIME" "$(pv dsa_params_keys)" check_eq $P/dsa-params-prime-bits "1024" "$(pv dsa_params_prime_bits)" check_eq $P/dsa-params-subprime-bits "224" "$(pv dsa_params_subprime_bits)" check_eq $P/dsa-params-roundtrip "yes" "$(pv dsa_params_roundtrip)" # The parameters describe a group, so q | p-1 and g must have order q. Both are # arithmetic on the decoded integers and neither survives a field swapped for # another -- which a name-set case would not notice. check_eq $P/dsa-params-q-divides-p1 "yes" "$(pv dsa_params_q_divides_p1)" check_eq $P/dsa-params-g-order-q "yes" "$(pv dsa_params_g_order_q)" check_eq $P/dsa-pub-len "128" "$(pv dsa_pub_len)" check_eq $P/dsa-pub-roundtrip "yes" "$(pv dsa_pub_roundtrip)" # ...and y is a member of that group, which ties two independently produced # fixtures together. check_eq $P/dsa-pub-in-group "yes" "$(pv dsa_pub_in_group)" check_eq $P/dsa-sig-p1363-len "56" "$(pv dsa_sig_p1363_len)" check_eq $P/dsa-sig-p1363 \ "ec30661b55ae16bdae762c9a32a1ccdbfd08b5b0dea8b8065903978810748aa6ed2f876783d963ba52450c28f7559ea54c94e41463e81557" \ "$(pv dsa_sig_p1363)" check_eq $P/dsa-sig-roundtrip "yes" "$(pv dsa_sig_roundtrip)" # ------------------------------------------------------------------- DH --- check_eq $P/dh-params-keys "BASE,PRIME" "$(pv dh_params_keys)" check_eq $P/dh-params-prime-bits "1024" "$(pv dh_params_prime_bits)" check_eq $P/dh-params-base "2" "$(pv dh_params_base)" check_eq $P/dh-params-roundtrip "yes" "$(pv dh_params_roundtrip)" check_eq $P/dh-pub-len "128" "$(pv dh_pub_len)" check_eq $P/dh-pub-roundtrip "yes" "$(pv dh_pub_roundtrip)" check_eq $P/dh-pub-below-prime "yes" "$(pv dh_pub_below_prime)" # ----------------------------------------------------------------- X.509 --- check_eq $P/x509-keys \ "CERTIFICATE_TYPE,CLASS,ISSUER,SERIAL_NUMBER,SUBJECT,VALUE" "$(pv x509_keys)" check_eq $P/x509-class "CERTIFICATE" "$(pv x509_class)" check_eq $P/x509-cert-type "X_509" "$(pv x509_cert_type)" # VALUE must be the certificate BYTE FOR BYTE: PKCS#11 stores the DER, not a # re-encoding of it, and a re-encoding is what breaks a signature check later. check_eq $P/x509-value-is-der "yes" "$(pv x509_value_is_der)" # The fixture is self-signed, so SUBJECT and ISSUER must be identical -- a # cross-check no single field can fake. check_eq $P/x509-subject-is-issuer "yes" "$(pv x509_subject_is_issuer)" check_eq $P/x509-subject-sha256 "d6c7d35775933b47d65ef5f9b56f2bea" \ "$(pv x509_subject_sha256)" check_eq $P/x509-serial-len "22" "$(pv x509_serial_len)" # extended_set=True adds the four attributes a searchable certificate object # needs, and it is a separate documented signature. check_eq $P/x509-extended-keys \ "CERTIFICATE_TYPE,CLASS,END_DATE,HASH_OF_ISSUER_PUBLIC_KEY,HASH_OF_SUBJECT_PUBLIC_KEY,ISSUER,SERIAL_NUMBER,START_DATE,SUBJECT,VALUE" \ "$(pv x509_extended_keys)" check_eq $P/x509-pub-keys \ "CLASS,ENCRYPT,KEY_TYPE,MODULUS,PUBLIC_EXPONENT,VERIFY,WRAP" \ "$(pv x509_pub_keys)" # THE CROSS-CHECK THAT TIES THE WHOLE FIXTURE SET TOGETHER: the certificate was # signed with the RSA key above, so the public key pulled out of the certificate # by one codec must be the one the RSA codec decoded from a different blob. check_eq $P/x509-pub-modulus-matches-rsa "yes" \ "$(pv x509_pub_modulus_matches_rsa)" # ------------------------------------------------------------ error paths --- # A codec handed the wrong blob must RAISE rather than return a plausible key. # Each case carries the exception TYPE, so `no-raise' -- a silently accepted # blob, which is the dangerous outcome -- is distinguishable from the wrong # exception. check_eq $P/rsa-pub-rejects-cert "ValueError" "$(pv rsa_pub_rejects_cert)" check_eq $P/rsa-pub-rejects-empty "ValueError" "$(pv rsa_pub_rejects_empty)" check_eq $P/rsa-pub-rejects-truncated "ValueError" "$(pv rsa_pub_rejects_truncated)" check_eq $P/x509-rejects-rsa-key "ValueError" "$(pv x509_rejects_rsa_key)" check_eq $P/ec-named-curve-rejects-unknown "ValueError" \ "$(pv ec_named_curve_rejects_unknown)" # ========================================================================== # THE EXTENSION. ONE INTERPRETER START, CARRYING NOTHING ELSE. # ========================================================================== # Everything above is finished and scored before this runs. See the header: on # this arm a loaded side module traps at its first park with rc=132 and takes the # process with it (linux-in-the-browser#184), so these two cases are deliberately # the last thing this suite does and are deliberately alone in their interpreter. # # bst_probe bounds the start and keeps both streams, so a refusal (an ImportError # naming an unresolved `env.' symbol) and a trap (rc=132, nothing on stdout) are # told apart in the transcript rather than both reading as "red". cat > "$W/ext.py" <<'PY' import sys try: import pkcs11._pkcs11 as _e print("ext_import_error=") print("ext_lib_type=%s" % type(getattr(_e, "lib", None)).__name__) except BaseException as exc: print("ext_import_error=%s: %s" % (type(exc).__name__, exc)) print("ext_lib_type=") # The ticket's own case: lib() on a path with no PKCS#11 module. Expected, once # linux-in-the-browser#184 closes, to raise RuntimeError whose message NAMES the # path -- _pkcs11.pyx:1402 raises RuntimeError(dlerror()) when dlopen returns # NULL. What it exists to catch is a binding that ABORTS instead of raising. import pkcs11 _p = "/nonexistent/brintos-no-such-pkcs11-module.so" try: pkcs11.lib(_p) print("lib_missing_module=no-raise") print("lib_missing_names_path=no") except BaseException as exc: print("lib_missing_module=%s" % type(exc).__name__) print("lib_missing_names_path=%s" % ("yes" if _p in str(exc) else "no")) print("ext_probe_done=yes") PY bst_probe $P/ext-run "$W/ext" 600 python3 "$W/ext.py" ev() { grep "^$1=" "$W/ext" 2>/dev/null | head -n 1 | cut -d= -f2-; } # EXPECTED RED on linux-in-the-browser#184 -- see the header. Green here means # the platform gap closed and the four cases under it become real measurements. check_eq $P/ext-import "" "$(ev ext_import_error)" check_eq $P/ext-lib-type "type" "$(ev ext_lib_type)" # THE TICKET'S CASE. Today it cannot be reached -- the import above dies first -- # so it is red on the same cause. It flips green by itself the day #184 closes, # with no edit here. check_eq $P/lib-missing-module "RuntimeError" "$(ev lib_missing_module)" check_eq $P/lib-missing-names-path "yes" "$(ev lib_missing_names_path)" # ...and the interpreter SURVIVED to say so, which is the assertion that # separates "raised" from "aborted the process". A missing-module dlopen that # takes the interpreter down is a real defect on a fresh arm and this is the case # that would catch it once #184 is out of the way. check_eq $P/ext-probe-complete "yes" "$(ev ext_probe_done)" rm -rf "$W"