# BST-TIMEOUT: 900 # 8 upstream regress execs plus 5 tool execs. The regress # # suite's own compute is 0.09 s for all 8 on an x86-64 # # control, so essentially the whole cost here is 13 fresh # # wasm module instantiations -- and three of them are the # # ~29 MB tool images, which instantiate far slower than the # # ~1 MB regress ones. Measured on target below. . "$BST_LIB" # libfido2 1.14.0 -- Yubico's FIDO2/WebAuthn client library, from meta-oe # recipes-support. See libfido2_%.bbappend for what the port adds and why. # # --------------------------------------------------------------------------- # THIS IMAGE SHIPS THE OFFLINE HALF, AND THE MISSING HALF IS NOT A SCOPE CHOICE # # libfido2 discovers authenticators through libudev (src/hid_linux.c) and reaches # NFC readers over netlink (src/nfc_linux.c). Nothing PROVIDES udev on this arm # and eudev cannot be built here at all, and underneath that the guest has no USB # host controller -- /sys/bus/usb/devices is empty and /dev/bus does not exist # (both measured under BRINTOS-1338). So there is no transport to test and never # could be, under any configuration of this image. # # What is left is what a RELYING PARTY runs, which is a server-side role that # needs no authenticator attached: the CBOR encode and decode paths, the COSE key # types (ES256, ES384, RS256, EdDSA), and the credential and assertion parsers # and verifiers. That is the large majority of the library by translation unit # and it is entirely exercised below. # # --------------------------------------------------------------------------- # WHAT CLOSES THIS TICKET: UPSTREAM'S OWN regress SUITE, NOT A PROBE WE WROTE # # regress/ ships eight programmes and every one of them is device-free by # construction -- each is a plain main() over assert(3) with `#undef NDEBUG` at # the top, driven against fixture blobs compiled into it. regress_dev is the one # worth naming: it drives an entire CTAP2 conversation -- init, get-info, # make-credential, get-assertion -- through fido_dev_set_io_functions() against # recorded wire data from fuzz/wiredata_fido2.h, which is the transport layer # tested with no transport present. OE builds none of them, so the bbappend adds # the ptest arm and stages what regress/CMakeLists.txt's own add_regress_test() # calls name. # # WHY EVERY ONE OF THESE IS EXPECTED TO PASS. All eight pass on an x86-64 control # build of this same tarball configured the way the target arm configures it # (static, USE_UDEV=OFF, BUILD_TESTS=ON). What they assert are properties of the # CTAP2/COSE/CBOR FORMATS and of OpenSSL's verifiers, not of the machine, so # unlike a captured native control there is no class of case that may legitimately # differ between the arms. A red here is a real defect in the port, the arm, or a # library beneath it. # # The one dimension where this arm genuinely differs from upstream's CI is # POINTER WIDTH: wasm32 is ILP32 while libfido2 is developed and fuzzed on # x86-64. regress_compress (zlib round-trips through fido_compress/fido_uncompress) # and regress_cred (CBOR length arithmetic over attestation blobs) are the two to # watch first if this suite ever goes red. ptestdir=/usr/lib/libfido2/ptest runner=/usr/bin/ptest-runner inc=/usr/include lib=/usr/lib/libfido2.a pc=/usr/lib/pkgconfig/libfido2.pc # Upstream's own counts, from the 1.14.0 tarball the recipe fetches: # regress/CMakeLists.txt makes 8 add_regress_test() calls (7 unconditional plus # regress_compress under BUILD_STATIC_LIBS, which this arm sets), and the install # ships fido.h plus 10 headers under fido/. The archive's 45 translation units are # asserted by NAME below rather than by count -- 35 FIDO_SOURCES with hid_none.c # and without the NFC trio, plus 10 COMPAT_SOURCES -- because a count says nothing # about WHICH member went missing, and on this port the identity of the HID # backend is the whole point. want_progs=8 want_hdrs=10 # Below this file's BST-TIMEOUT on purpose, so an overrun is reported by # ptest-runner in its own voice instead of this whole file being killed around # it -- the arrangement 81-libcbor.test and 77-libpcre.test use. runner_t=700 tmp=${TMPDIR:-/tmp}/bst-libfido2.$$ mkdir -p "$tmp" 2>/dev/null || tmp=/tmp has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "not found: $2" ;; esac; } hasnt() { case "$3" in *"$2"*) bst_notok "$1" "present but must not be: $2" ;; *) bst_ok "$1" ;; esac; } # ----------------------------------------------------------- what shipped ---- # The -dev/-staticdev half. libfido2 installs no shared object here: the distro # is static-only, so libfido2.a IS the library. check_rc libfido2/ar 0 test -s "$lib" check_rc libfido2/hdr-fido-h 0 test -s "$inc/fido.h" check_rc libfido2/hdr-dir 0 test -d "$inc/fido" check_rc libfido2/no-so 1 test -e /usr/lib/libfido2.so check_rc libfido2/no-la 1 test -e /usr/lib/libfido2.la # THE udev RULES FILE MUST NOT BE HERE, and this is the case that pins the # decision rather than merely its consequence. Upstream installs udev/70-u2f.rules # whenever UDEV_RULES_DIR is set, and the stock meta-oe recipe sets it. With no # udev on this image that file would be a rule nothing reads, advertising a # transport that does not exist -- so the patch stops adding the subdirectory and # the bbappend drops the variable. A future recipe change that reinstated either # would land the file silently; this is what notices. check_rc libfido2/no-udev-rules 1 test -e /lib/udev/rules.d/70-u2f.rules # The pkg-config file, which is how a downstream consumer resolves this library. # Its Requires: line is asserted because it is the one field that makes a static # link work: -lfido2 alone is not enough, the caller also needs -lcrypto. check_rc libfido2/pc 0 test -s "$pc" check_out libfido2/pc-libs "-lfido2" cat "$pc" check_out libfido2/pc-version "1.14.0" cat "$pc" check_out libfido2/pc-requires "libcrypto" cat "$pc" # The public headers, counted by glob expansion rather than `ls | wc -l`, which # would fork twice. A -dev package that dropped, say, eddsa.h would still compile # every consumer that uses only ES256, so the gap would surface later in someone # else's build. set -- "$inc"/fido/*.h check_eq libfido2/hdr-count "$want_hdrs" "$#" for h in bio config credman eddsa err es256 es384 param rs256 types; do check_rc "libfido2/hdr-$h" 0 test -s "$inc/fido/$h.h" done # ------------------------------------------------ the tools, offline verbs ---- check_rc libfido2/tool-cred 0 test -x /usr/bin/fido2-cred check_rc libfido2/tool-assert 0 test -x /usr/bin/fido2-assert check_rc libfido2/tool-token 0 test -x /usr/bin/fido2-token # Each tool prints its usage to stderr and exits 1 when given no verb. That is # two facts in one exec: the ~29 MB wasm image instantiates and reaches main(), # and the binary is the one it claims to be. Captured rather than driven through # check_out, which requires exit 0. for t in cred assert token; do u=$( /usr/bin/fido2-$t 2>&1 ); urc=$? case "$u" in "usage: fido2-$t"*) has "libfido2/usage-$t" "usage: fido2-$t" "$u" ;; *) bst_notok "libfido2/usage-$t" "rc=$urc $(_bst_1line "$u")" ;; esac done # THE EMPTY MANIFEST, WHICH IS THE ON-TARGET PROOF THAT THE NO-DEVICE BACKEND # BEHAVES AS DESIGNED. `fido2-token -L` is the one command that calls # fido_dev_info_manifest() and nothing else, so it observes hid_none.c directly. # The designed answer is not an error: the stub returns FIDO_OK with zero # devices, so the tool prints NOTHING and exits 0 -- which is exactly what a host # with no authenticator attached does on any platform. A crash, a non-zero exit, # or a phantom device would each be a distinct defect, and only this case tells # them apart from one another. lst=$( /usr/bin/fido2-token -L 2>&1 ); lrc=$? check_eq libfido2/manifest-rc 0 "$lrc" check_eq libfido2/manifest-empty "" "$lst" # The credential parser refusing malformed input, driven from a pipeline so the # redirect forks (see the packager README on top-level rootfs redirects). This is # a NEGATIVE case on purpose: a valid attestation blob needs a signing step this # image cannot perform, and the positive paths are regress_cred's and # regress_assert's job below, against upstream's own fixtures. What it adds is # that the parser REJECTS rather than crashes or accepts -- the failure mode a # fixture-driven suite never sees. verr=$( printf 'not-a-credential\n' | /usr/bin/fido2-cred -V 2>&1 ); vrc=$? if [ "$vrc" -ne 0 ]; then bst_ok libfido2/cred-verify-rejects else bst_notok libfido2/cred-verify-rejects "accepted garbage: rc=0 $(_bst_1line "$verr")"; fi # ----------------------------------------------- the ptest payload ----------- check_rc libfido2/ptest-dir 0 test -d "$ptestdir" check_rc libfido2/ptest-script 0 test -x "$ptestdir/run-ptest" check_rc libfido2/ptest-progs 0 test -d "$ptestdir/progs" # A short count is the failure this asserts against: the bbappend derives its # list from upstream's own add_regress_test() calls and bbfatals on a programme # that was declared but not built, so a disagreement here means something was # lost after do_install_ptest. set -- "$ptestdir"/progs/* check_eq libfido2/prog-count "$want_progs" "$#" # ------------------------------------------------------- run it, once -------- # The harness is preferred wherever it exists, because this port closes on the # suite run UNDER ptest-runner -- its discovery and its tally are part of what is # asserted. The fallback runs the identical script directly and SKIPS only the # cases that are about the harness itself; they have no subject on an image that # ships no ptest-runner. out=$tmp/runner.out if [ -x "$runner" ]; then bst_ok libfido2/ptest-runner-present "$runner" -t "$runner_t" libfido2 > "$out" 2>&1 rc=$? check_eq libfido2/ptest-runner-rc 0 "$rc" begin=; end=; rerr= while read -r k v; do case "$k" in BEGIN:) begin=$v ;; END:) end=$v ;; ERROR:) rerr="$v" ;; esac done < "$out" check_eq libfido2/runner-begin "$ptestdir" "$begin" check_eq libfido2/runner-end "$ptestdir" "$end" check_eq libfido2/runner-no-error "" "$rerr" else bst_skip libfido2/ptest-runner-present "this image ships no ptest-runner" bst_skip libfido2/ptest-runner-rc "no ptest-runner to run" for c in runner-begin runner-end runner-no-error; do bst_skip "libfido2/$c" "harness cases have no subject without ptest-runner" done ( cd "$ptestdir" && sh ./run-ptest ) > "$out" 2>&1 fi # ------------------------------------- one case per upstream programme ------- # A single `while read` over the capture: no fork per case, and every programme # upstream declared gets its own BST case, so a red names the area of the library # that broke rather than just the suite. npass=0 nfail=0 while read -r verdict name _extra; do case "$verdict" in PASS:) bst_ok "libfido2/t-$name"; npass=$((npass + 1)) ;; FAIL:) bst_notok "libfido2/t-$name" "upstream regress programme failed"; nfail=$((nfail + 1)) ;; esac done < "$out" # The tally is asserted separately from the individual cases because the two fail # differently. A red above is one broken area of the library. A short TOTAL means # programmes never ran at all -- a truncated capture, a harness timeout, a payload # that did not install -- and without this that would read as a clean, shorter run. check_eq libfido2/suite-total "$want_progs" "$((npass + nfail))" check_eq libfido2/suite-failures 0 "$nfail" # The four named individually are the ones the ticket's pass criterion names, so # a capture that silently lost one of them is a red here rather than a smaller # total: CBOR round-trips and the assertion/credential parsers (assert, cred), # and the two signature verifiers it calls out (es256, eddsa). o=$(cat "$out" 2>/dev/null) for t in regress_assert regress_cred regress_es256 regress_eddsa; do has "libfido2/named-$t" "PASS: $t" "$o" done # ============================================================================ # The archive: what is in it, and -- more to the point -- what is not. # ============================================================================ # # Flattened to printable text once. GNU grep suppresses matches inside data it # classifies as binary and busybox tr does not handle [:print:] under -c, so the # octal range is the formulation both greps and both trs agree on -- derived # empirically by 69-libb64.test and reused since. flat=$tmp/lib.flat ( tr -c '\40-\176\n' ' ' < "$lib" > "$flat" 2>/dev/null ) a=$(cat "$flat" 2>/dev/null) # THE TRANSPORT SELECTION, PINNED ON TARGET. This is the port's central decision # and the archive is the only place it is observable after the build: hid_none.c # is the backend the patch adds, and the four absentees are what selecting it # excludes. A future toolchain or recipe change that quietly restored hid_linux.c # would not fail to build -- it would fail to LINK, much later, in whatever # consumer first pulled the archive -- and a change that dropped hid_none.c # without a replacement would leave fido_hid_manifest undefined for the same # distant reader. Either way the diagnosis starts here. has libfido2/member-hid-none "hid_none.c.o" "$a" hasnt libfido2/no-hid-linux "hid_linux.c.o" "$a" hasnt libfido2/no-hid-unix "hid_unix.c.o" "$a" hasnt libfido2/no-nfc-linux "nfc_linux.c.o" "$a" hasnt libfido2/no-netlink "netlink.c.o" "$a" # One translation unit per source in src/CMakeLists.txt's FIDO_SOURCES. A missing # member is a partial compile the link would only notice if something referenced # it -- and on a static-only arm with no consumer in the image, nothing would. for m in assert cred dev cbor es256 es384 rs256 eddsa ecdh aes256 \ blob buf compress config credman bio info io iso7816 largeblob \ log pin random reset rs1 time touch tpm types u2f util err authkey hid; do has "libfido2/member-$m" "$m.c.o" "$a" done # The openbsd-compat half, which is where a libc gap on this arm would show up # first: these are the functions glibc either lacks or that upstream refuses to # trust, and they are compiled in rather than linked from the system. for m in bsd-asprintf bsd-getpagesize clock_gettime explicit_bzero freezero \ recallocarray strlcat timingsafe_bcmp; do has "libfido2/compat-$m" "$m.c.o" "$a" done # A named sample across every layer of the API. Not a count: pinning the exact # external-symbol total would go false on any version bump, whereas these are the # entry points that define what the OFFLINE library IS -- the credential and # assertion objects and their verifiers, the four COSE key types, and the CBOR # seam underneath them. for s in fido_cred_new fido_cred_free fido_cred_verify fido_cred_set_authdata \ fido_cred_set_x509 fido_cred_set_sig fido_cred_pubkey_ptr \ fido_assert_new fido_assert_free fido_assert_verify \ fido_assert_set_authdata fido_assert_set_sig fido_assert_set_clientdata \ es256_pk_new es256_pk_from_ptr es384_pk_new rs256_pk_new eddsa_pk_new \ fido_dev_info_manifest fido_dev_set_io_functions \ fido_strerr fido_init; do has "libfido2/sym-$s" "$s" "$a" done rm -rf "$tmp" 2>/dev/null