. "$BST_LIB" # BST-TIMEOUT: 300 # # ~130 cases: file reads, string compares, about twenty short program runs and # two runs of one probe. Nothing here forks a server or waits on anything. 300 # leaves ample room and stays useful as a hang detector. # # p11-kit 0.25.3 on wasm32-hwjs glibc -- the library that lets several programs # share one set of PKCS#11 modules (poky/meta, recipes-support/p11-kit; one # meta-brintos bbappend and one patch). # # --------------------------------------------------------------------------- # WHAT THIS PORT SHIPS, AND WHY THERE IS NO SHARED OBJECT ANYWHERE. # # libp11-kit.a, the p11-kit and trust programs, eight headers, p11-kit-1.pc, the # module-configuration directories, p11-kit-trust.module, trust-extract-compat # and two bash completions. NOT shipped: libp11-kit.so, p11-kit-proxy.so, # p11-kit-client.so and p11-kit-trust.so. # # That is the platform, not the recipe. wasmld's HWJS-3 coro arm refuses a # -shared side module that owns thread-local storage; common/library.c declares # _p11_library_get_thread_local, it lands in libp11-kit-internal.a, and every one # of the three shared targets links it. Measured on the stock recipe: all three # run the whole merge pass and stop on the same TLS-CENSUS line. # # --------------------------------------------------------------------------- # THE CONSEQUENCE IS THE SHAPE OF THIS SUITE. # # p11-kit exists to dlopen PKCS#11 modules. This distro is `no-dso' and # static-only, so there is nothing to open -- even though glibc still RESOLVES # dlopen at link time, which is why the build's own meson log says # `Checking for function "dlopen" : YES' and nothing failed at configure. # # So the port divides in three, and the suite asserts the three SEPARATELY so # that the day this platform grows a loader it is visible which half changed: # # PURE the RFC 7512 URI parser and formatter, the attribute table, the # matcher, the configuration reader. No module is involved. This is # the port's real coverage and it is asserted in full, through # data/p11-kit-probe -- there is no command-line surface for any of # it, so a shell test could only say the archive exists. # # PACKAGE what landed where the packaging says, including the four absences # above. A `-staticdev'-shaped port's most likely regression is a # file in the wrong package, and it is the cheapest thing to assert. # # MODULE everything that needs dlopen. Asserted as REFUSING, with the # refusal's own wording, rather than skipped -- a suite that omits # this half cannot tell a platform that grew a loader from one that # silently stopped trying. Every case here is expected red-adjacent # by construction and green as written: the assertion is that the # program runs, reports the failure, and exits non-zero, which is # what a correctly built p11-kit does on a machine with no loader. # # --------------------------------------------------------------------------- # NOTHING BELOW HAS RUN ON THE TARGET, AND EVERYTHING BELOW HAS BEEN MEASURED # SOMEWHERE. This suite was written in the batch's recipe phase, which forbids # building the image, so every expectation is a PREDICTION about wasm32-hwjs. # None of them is a guess: each literal was measured on an x86_64 control built # from the same 0.25.3 tree through the same recipe and the same meson options # (p11-kit-native, built in this ticket's own build directory), driven into this # image's exact state -- the trust module configured and its .so absent. # # That distinction is worth stating because the first draft of this suite WAS # reasoned from the sources, and the control overturned six of its expectations: # the formatter orders fields alphabetically rather than as parsed, it escapes # `/', p11_kit_uri_message(OK) returns NULL rather than "Success", neither # program has a --version, and every module-dependent command exits 0 rather # than failing. Where the verify disagrees with what is written here, the verify # is right. bst_ok p11-kit/suite-canary # first line: absorbs brintos/linux#96, which drops it tmp=${TMPDIR:-/tmp} nl=' ' hasfile() { if [ -s "$2" ]; then bst_ok "$1"; else bst_notok "$1" "absent or empty: $2"; fi; } nofile() { _id=$1; shift; if [ ! -e "$1" ]; then bst_ok "$_id"; else bst_notok "$_id" "present: $1"; fi; } hasdir() { if [ -d "$2" ]; then bst_ok "$1"; else bst_notok "$1" "not a directory: $2"; fi; } has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not present: $2" ;; esac; } PFX=${P11_KIT_PREFIX:-/usr} # --- PACKAGE: what the two programs are --------------------------------------- require p11-kit p11-kit/present check p11-kit/trust-present command -v trust # NEITHER PROGRAM HAS A --version, and that is measured rather than assumed: # `p11-kit --version' is rc=2 `unknown global option'. So the version this image # carries is pinned through p11-kit-1.pc below and nowhere else -- which is # worth knowing before someone writes a health check around a flag that does not # exist. The refusal is asserted so that a release which ADDS one is noticed. check_rc p11-kit/no-version-flag 2 p11-kit --version check_out p11-kit/no-version-message "unknown global option: --version" \ sh -c 'p11-kit --version 2>&1; exit 0' # Bare `p11-kit' is the command table, rc=2. The four commands named are the # ones this port's story is about: two that work without a loader, two that # dispatch to the libexec helpers. usage=$(p11-kit 2>&1); usage_rc=$? check_eq p11-kit/usage-rc 2 "$usage_rc" for c in list-modules print-config remote server; do has "p11-kit/usage-lists-$c" " $c" "$usage" done # --- PACKAGE: the -dev / -staticdev deliverables ------------------------------ hasfile p11-kit/staticdev-lib "$PFX/lib/libp11-kit.a" hasfile p11-kit/pc "$PFX/lib/pkgconfig/p11-kit-1.pc" for h in deprecated.h iter.h p11-kit.h pin.h remote.h uri.h; do hasfile "p11-kit/header-${h%.h}" "$PFX/include/p11-kit-1/p11-kit/$h" done # pkcs11.h and pkcs11x.h are installed by the top-level meson.build, beside the # six the p11-kit subdirectory installs, and a consumer needs them to name a # single CK_ type. They are asserted separately because they come from a # different install_headers() call and so fail independently. hasfile p11-kit/header-pkcs11 "$PFX/include/p11-kit-1/p11-kit/pkcs11.h" hasfile p11-kit/header-pkcs11x "$PFX/include/p11-kit-1/p11-kit/pkcs11x.h" pcbody=$(cat "$PFX/lib/pkgconfig/p11-kit-1.pc" 2>/dev/null) # The FILE is p11-kit-1.pc and the pkg-config Name field is `p11-kit' -- the -1 # is an API-generation suffix meson puts in the filename and not in the field. # Asserted apart from pc-version because a consumer reaches the two by different # routes: `pkg-config p11-kit-1' names the file, Name: is what it prints back. hasline p11-kit/pc-name 'Name: p11-kit' "$pcbody" hasline p11-kit/pc-version 'Version: 0.25.3' "$pcbody" has p11-kit/pc-cflags '-I${includedir}/p11-kit-1' "$pcbody" # The .pc carries the module and config paths as pkg-config VARIABLES, which is # how a downstream package (gnutls, opensc) learns where to drop its .module # file. They are part of the port's contract, not decoration. has p11-kit/pc-var-configs 'p11_module_configs=' "$pcbody" has p11-kit/pc-var-path 'p11_module_path=' "$pcbody" if command -v pkg-config >/dev/null 2>&1; then check_out p11-kit/pkg-config-modversion "0.25.3" pkg-config --modversion p11-kit-1 check_out p11-kit/pkg-config-libs "-lp11-kit" pkg-config --libs p11-kit-1 else bst_skip p11-kit/pkg-config-modversion "pkg-config not on this image" bst_skip p11-kit/pkg-config-libs "pkg-config not on this image" fi # --- PACKAGE: the configuration surface --------------------------------------- hasdir p11-kit/dir-share-modules "$PFX/share/p11-kit/modules" hasfile p11-kit/trust-module-file "$PFX/share/p11-kit/modules/p11-kit-trust.module" hasfile p11-kit/conf-example /etc/pkcs11/pkcs11.conf.example hasfile p11-kit/extract-compat "$PFX/libexec/p11-kit/trust-extract-compat" # `p11-kit remote' and `p11-kit server' are not built into the p11-kit binary -- # it EXECs these two. They are asserted present because their absence turns two # advertised commands into `not a valid command', which is what the native # control does when the same paths are missing, and reads like a broken CLI # rather than a missing file. hasfile p11-kit/helper-remote "$PFX/libexec/p11-kit/p11-kit-remote" hasfile p11-kit/helper-server "$PFX/libexec/p11-kit/p11-kit-server" # The two bash completions are BUILT and packaged (p11-kit-bash-completion) and # deliberately NOT installed: no `*-bash-completion' package is on this image at # all, for any recipe, so taking p11-kit's would be this port inventing a # convention. Asserted absent so the roster and the image cannot drift apart in # silence. nofile p11-kit/completion-p11-kit-not-installed "$PFX/share/bash-completion/completions/p11-kit" nofile p11-kit/completion-trust-not-installed "$PFX/share/bash-completion/completions/trust" # p11-kit-trust.module names the .so this image does not have. That is the # deliberate half of the port -- a configured-but-unloadable module is a # diagnosable state, an unconfigured one is silence -- so the file's CONTENT is # asserted rather than just its presence. has p11-kit/trust-module-names-so "p11-kit-trust.so" \ "$(cat "$PFX/share/p11-kit/modules/p11-kit-trust.module" 2>/dev/null)" # --- PACKAGE: the four absences, which are this port's central decision ------- for so in "$PFX/lib/libp11-kit.so" "$PFX/lib/libp11-kit.so.0" \ "$PFX/lib/p11-kit-proxy.so" "$PFX/lib/pkcs11/p11-kit-client.so" \ "$PFX/lib/pkcs11/p11-kit-trust.so"; do nofile "p11-kit/no-so-${so##*/}" "$so" done # ...and no shared object of any name under either directory, so a future # upstream that adds one is caught rather than silently shipped unloadable. if [ -n "$(find "$PFX/lib/pkcs11" "$PFX/lib" -maxdepth 1 -name 'libp11*.so*' -o -maxdepth 1 -name 'p11-kit-*.so' 2>/dev/null)" ]; then bst_notok p11-kit/no-shared-objects "a p11-kit shared object is installed" else bst_ok p11-kit/no-shared-objects fi # --- PURE: print-config is the configuration reader, through the CLI ---------- # # It is the only subcommand of either program that produces its whole answer # without a loader, which makes it both the positive control for the MODULE # cases below and the strongest CLI assertion this port has: it prints the # MERGED configuration, so every line is one the reader parsed out of # /usr/share/p11-kit/modules/p11-kit-trust.module. If this one ever fails too, # the fault is the program and not the absent loader. pc=$(p11-kit print-config 2>&1); pc_rc=$? check_eq p11-kit/print-config-rc 0 "$pc_rc" hasline p11-kit/print-config-global '[global]' "$pc" hasline p11-kit/print-config-section '[p11-kit-trust]' "$pc" hasline p11-kit/print-config-module 'module = p11-kit-trust.so' "$pc" hasline p11-kit/print-config-priority 'priority = 1' "$pc" hasline p11-kit/print-config-policy 'trust-policy = yes' "$pc" hasline p11-kit/print-config-disable 'disable-in = p11-kit-proxy' "$pc" # The one value in that file that is itself a PKCS#11 URI, carried through the # reader unaltered -- `%23' must not be decoded to `#' on the way. hasline p11-kit/print-config-lookup \ 'x-trust-lookup = pkcs11:library-description=PKCS%2311%20Kit%20Trust%20Module' "$pc" # --- MODULE: every path that needs dlopen ------------------------------------- # # EXPECTED, NOT YET MEASURED ON THIS TARGET, but not guessed either: each case # below was measured on the x86_64 control with the module file present and the # .so absent, which is exactly this image's state. The result is not what a # reader expects -- ALL OF THESE EXIT 0. p11-kit treats a non-critical module # that will not load as a module it does not have, reports it on stderr and # carries on, so `rc' is worthless as a signal here and the DIAGNOSTIC is the # assertion. That is the trap this section exists to pin down: a health check # built on the exit status of `trust list' reports success on an image with no # trust store at all. lm=$(p11-kit list-modules 2>&1); lm_rc=$? check_eq p11-kit/list-modules-rc 0 "$lm_rc" has p11-kit/list-modules-load-failed "couldn't load module" "$lm" has p11-kit/list-modules-names-the-so "p11-kit-trust.so" "$lm" for c in list dump; do o=$(trust $c 2>&1); rc=$? check_eq "p11-kit/trust-$c-rc" 0 "$rc" has "p11-kit/trust-$c-no-policy" "no modules containing trust policy are registered" "$o" done # `trust extract' into each supported format. All five go through the trust # module, so all five produce an EMPTY extraction -- and, measured on the # control, all five still exit 0 and still CREATE the output. A caller that # checks only the exit status ships an empty CA bundle believing it succeeded, # which is the failure this port most needs on record. Asserted one per format, # because a platform that grew a loader would flip them together while a bug in # one extractor would not. for fmt in x509-directory pem-directory openssl-directory; do out=$tmp/bst-p11kit-extract.$$ rm -rf "$out" e=$(trust extract --format="$fmt" --filter=ca-anchors "$out" 2>&1); e_rc=$? check_eq "p11-kit/trust-extract-$fmt-rc" 0 "$e_rc" has "p11-kit/trust-extract-$fmt-no-policy" \ "no modules containing trust policy are registered" "$e" if [ -d "$out" ] && [ -z "$(ls -A "$out" 2>/dev/null)" ]; then bst_ok "p11-kit/trust-extract-$fmt-empty" else bst_notok "p11-kit/trust-extract-$fmt-empty" "not an empty directory: $out" fi rm -rf "$out" done # pem-bundle is a CONCATENATION, so with no anchor to concatenate it comes out # genuinely zero bytes: present, readable, and holding nothing. out=$tmp/bst-p11kit-extract.$$ rm -rf "$out" e=$(trust extract --format=pem-bundle --filter=ca-anchors "$out" 2>&1); e_rc=$? check_eq "p11-kit/trust-extract-pem-bundle-rc" 0 "$e_rc" if [ -f "$out" ] && [ ! -s "$out" ]; then bst_ok "p11-kit/trust-extract-pem-bundle-empty-file" else bst_notok "p11-kit/trust-extract-pem-bundle-empty-file" "not a zero-byte file: $out" fi rm -rf "$out" # java-cacerts is a STRUCTURED keystore, so the empty extraction is not an empty # file -- measured on the control it is exactly 32 bytes: the JKS magic # feedfeed, format version 2, an entry count of ZERO, and the 20-byte SHA-1 # trailer over them. That entry count is the assertion that matters, and it is # read directly rather than inferred from the size, so a release that changed # the trailer would not be mistaken for one that started emitting anchors. out=$tmp/bst-p11kit-extract.$$ rm -rf "$out" e=$(trust extract --format=java-cacerts --filter=ca-anchors "$out" 2>&1); e_rc=$? check_eq "p11-kit/trust-extract-java-cacerts-rc" 0 "$e_rc" if [ -f "$out" ]; then bst_ok "p11-kit/trust-extract-java-cacerts-file" jks=$(od -An -tx1 -N 12 "$out" 2>/dev/null | tr -d ' \n') check_eq "p11-kit/trust-extract-java-cacerts-magic" "feedfeed" "${jks%????????????????}" check_eq "p11-kit/trust-extract-java-cacerts-header" \ "feedfeed0000000200000000" "$jks" else bst_notok "p11-kit/trust-extract-java-cacerts-file" "no file written: $out" bst_notok "p11-kit/trust-extract-java-cacerts-magic" "no file written: $out" bst_notok "p11-kit/trust-extract-java-cacerts-header" "no file written: $out" fi rm -rf "$out" # `trust anchor --store' READS the file named and stores what is in it, so a # missing input is refused before any module is reached -- rc=1, unlike every # other case in this section. It is here as the control that says `trust' can # still fail properly: without it, the block above reads as a program that # returns 0 whatever happens. anchor=$tmp/bst-p11kit-anchor.$$.pem rm -f "$anchor" a=$(trust anchor --store "$anchor" 2>&1); a_rc=$? check_eq p11-kit/trust-anchor-missing-input-rc 1 "$a_rc" has p11-kit/trust-anchor-missing-input-msg "couldn't open and map file" "$a" nofile p11-kit/trust-anchor-wrote-nothing "$anchor" # `trust check-format' parses a .p11-kit file and needs no module. It is the # other loader-free program path, and it carries the same exit-status trap: # a file it REJECTS still exits 0, with the verdict on stdout. cf=$(trust check-format /dev/null 2>&1); cf_rc=$? check_eq p11-kit/check-format-rc 0 "$cf_rc" has p11-kit/check-format-rejects "not recognized as .p11-kit format" "$cf" has p11-kit/check-format-verdict "FAIL" "$cf" # `p11-kit server' is deliberately NOT RUN. It forks a p11-kit-remote per # module and then waits; on the control the same invocation could not be made # to terminate on its own, and a suite that hangs the guest costs every member # of this batch its round. Its two helpers are asserted present above and its # command registration is asserted in the usage table; that is as far as this # suite goes on purpose. bst_skip p11-kit/server-not-run "would fork and wait; helpers and registration asserted instead" # --- PURE: the probe, which is where this port's coverage actually is --------- probe=$tmp/bst-p11-kit-probe.$$ scratch=$tmp/bst-p11-kit-cfg.$$ hasfile p11-kit/probe-shipped "$BST_DATA/p11-kit-probe" rm -rf "$scratch"; mkdir -p "$scratch" 2>/dev/null if cp "$BST_DATA/p11-kit-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then bst_ok p11-kit/probe-stage out=$("$probe" "$scratch" 2>&1); rc=$? bst_ok p11-kit/probe-ran else bst_notok p11-kit/probe-stage "cannot stage $BST_DATA/p11-kit-probe" out= bst_notok p11-kit/probe-ran "probe not staged" fi # Every case is named whatever the probe printed, so a probe that produced # nothing emits `not ok' lines rather than contributing nothing to the count. p() { hasline "p11-kit/$1" "$2" "$out"; } p probe-begin 'probe-begin=p11-kit' p probe-end 'probe-end=p11-kit' # 1. Parse -> read back. Each field separately: a parser that put `model' in # `serial' would round-trip perfectly and fail exactly here. p parse-new 'parse-new=ok' p parse-rc 'parse-rc=0' p parse-token-label 'parse-token-label=The Token' p parse-token-model 'parse-token-model=Deluxe' p parse-token-serial 'parse-token-serial=7AF7B2E4' p parse-token-manufacturer 'parse-token-manufacturer=Snake Oil, Inc.' p parse-module-description 'parse-module-description=Test Library' p parse-object-label 'parse-object-label=Test Certificate' # %01%02%03 must decode to THREE BYTES. A parser that stored the escaped # spelling would report 9 here, and nothing else in the suite would notice. p parse-object-id-len 'parse-object-id-len=3' p parse-object-id-hex 'parse-object-id-hex=010203' p parse-object-class 'parse-object-class=1' p parse-object-class-cert 'parse-object-class-is-certificate=yes' p parse-pin-source 'parse-pin-source=file:/etc/pin' p parse-module-name 'parse-module-name=softhsm' p parse-vendor-query 'parse-vendor-query=brintos' p parse-vendor-absent 'parse-vendor-query-absent=(null)' p parse-any-unrecognized 'parse-any-unrecognized=0' # 2. Round trip, per type mask -- the mask decides which half comes back out, # so a formatter that ignored it would look correct under FOR_ANY alone. # # THE ORDER IS THE FORMATTER'S, NOT THE INPUT'S, and both these lines say so: # `token' comes back AFTER `serial' although it was written first. That is # p11_kit_uri_format walking its own field table, and a caller that compares a # formatted URI against the string it parsed will find them unequal for a URI # that round-tripped perfectly. It is asserted rather than normalised because # it is the single most likely wrong assumption about this API. p rt-token 'rt-token=pkcs11:serial=7AF7B2E4;token=The%20Token' p rt-object 'rt-object=pkcs11:object=Test%20Certificate;type=cert' p rt-object-on-token 'rt-object-on-token=pkcs11:token=The%20Token;object=Test%20Certificate;type=cert' # The escaping rules in one value: space, comma, percent AND SLASH all come back # percent-encoded. RFC 7512 permits `/' unescaped in a path component and # p11-kit escapes it anyway, so a consumer comparing against a hand-written URI # with a literal slash gets a mismatch. p rt-escapes 'rt-escapes=pkcs11:token=a%20b%2Cc%25d%2Fe' # An empty value is not an absent one, and survives the round trip. p rt-empty-serial 'rt-empty-serial=pkcs11:serial=;token=T' p rt-slot 'rt-slot=pkcs11:slot-id=7' # 3. Refusals: the CODE as well as the refusal, because p11_kit_uri_message is # what callers print and a wrong code prints a wrong cause. p bad-scheme "bad-scheme=-2:URI scheme must be 'pkcs11:'" p bad-scheme-empty "bad-scheme-empty=-2:URI scheme must be 'pkcs11:'" p bad-encoding 'bad-encoding=-3:URI encoding invalid or corrupted' p bad-encoding-truncated 'bad-encoding-truncated=-3:URI encoding invalid or corrupted' p bad-syntax-attr 'bad-syntax-attr=-4:URI syntax is invalid' p bad-version 'bad-version=-5:URI version component is invalid' # An UNRECOGNISED attribute is ACCEPTED and flagged -- the difference between # "malformed" and "asks for something I cannot match". p unknown-attr-rc 'unknown-attr-rc=0' p unknown-attr-flag 'unknown-attr-unrecognized=1' # p11_kit_uri_message(P11_KIT_URI_OK) returns NULL, not "Success". Every other # code maps to a string, so a caller that prints the message unconditionally # prints `(null)' -- or crashes -- on the SUCCESS path. Asserted because it is a # real edge in the API and would otherwise look like a probe bug when read. p message-ok 'message-ok=(null)' p message-not-found 'message-not-found=The URI component was not found' # 4. Matching: what a caller actually does with a URI. No module needed -- the # structs are the caller's own. p match-setup 'match-setup=ok' p match-token-yes 'match-token-yes=1' p match-token-no 'match-token-no=0' p match-attrs-yes 'match-attrs-yes=1' p match-attrs-no 'match-attrs-no=0' p match-attrs-after-clear 'match-attrs-after-clear=1' # 5. The configuration reader, asserted with NO LOADER INVOLVED. Neither # configured module can load on either arm -- the paths do not exist -- so # every line below is about what the reader did with the .module files. # # Pass A has a non-critical module that fails and a CRITICAL one that is # `disable-in' this program. `list' says both things at once: the critical # module was never reached (or the result would be NULL) and a non-critical # failure is tolerated. Pass B makes the first module critical and nothing # else, and NULL is what says `critical' is read rather than ignored. Either # line alone would pass against a reader that ignored every setting. p config-mkdir 'config-mkdir=ok' p config-write 'config-write=ok' p modules-a-result 'modules-a-result=list' p modules-a-count 'modules-a-count=0' p config-rewrite 'config-rewrite=ok' p modules-b-result 'modules-b-result=NULL' p modules-b-message "modules-b-message=aborting initialization because module 'one' was marked as critical" # 6. The whole control, line for line. Every case above names one fact so a red # says which; this one case says the target and the x86_64 control agree in # full, and catches a line neither the author nor a reviewer thought to name. # # data/p11-kit-native.txt is the verbatim stdout of THIS probe source built # natively against p11-kit-native -- the same 0.25.3 tree, the same recipe, # the same meson options -- so a diff can only be the platform. if [ -s "$BST_DATA/p11-kit-native.txt" ]; then ctl=$(cat "$BST_DATA/p11-kit-native.txt" 2>/dev/null) # stderr carries p11-kit's own diagnostics with a PID in them, so the # comparison is against stdout only. $out above captured both; re-run # capturing stdout alone rather than filtering, which would need a regex the # target's sed may not have. if [ -x "$probe" ]; then rm -rf "$scratch"; mkdir -p "$scratch" 2>/dev/null got=$("$probe" "$scratch" 2>/dev/null) check_eq p11-kit/probe-matches-native-control "$ctl" "$got" else bst_notok p11-kit/probe-matches-native-control "probe not staged" fi else bst_notok p11-kit/probe-matches-native-control "control absent: $BST_DATA/p11-kit-native.txt" fi rm -f "$probe" rm -rf "$scratch"