PCR-ORACLE(8)

๐Ÿ“ฆ version 0.6.6, in opensuse-leap 16.0

๐Ÿ’– PCR-ORACLE(8) ๐Ÿ’–

okay bestie, welcome to pcr-oracle โ€” source file PCR-ORACLE(8), and like the tagline literally says: Predict TPM PCR values and Seal Secrets. that's the whole vibe. let's get into it ๐Ÿซถ


โœจ NAME โœจ

pcr-oracle โ€” predict TPM PCR values and seal secrets ๐Ÿ’…


โœจ SYNOPSIS โœจ

pcr-oracle [options] action ...

โœจ DESCRIPTION โœจ

okay so this utility is genuinely iconic. it can predict TPM PCR values at a specific time during system boot, create a PCR policy for those values, and then seal a secret to that policy. like, the whole pipeline. slay.

In simple prediction mode, pcr-oracle will compute expected hash values for a subset of PCRs, and write them to standard output. The most common use case will probably be to base the prediction on the TPM event log that was generated by the firmware during the most recent boot โ€” but ngl, there are other modes as well. it's flexible like that ๐Ÿ’

In sealing mode, pcr-oracle will seal a given secret (such as a LUKS device password) to either a set of PCR values, or to an authorized PCR policy. two paths, both valid, pick your fighter.

๐ŸŽ€ PCR Policies vs Authorized Policies ๐ŸŽ€

okay so this is the part where it gets good, like fr.

The simplest approach to sealing a secret against an expected system state using the TPM is to create what is called a "PCR Policy". This policy essentially requires a given set of PCRs to contain a specific set of values. This policy can be used to "seal" a secret, i.e. encrypt it using a key only the TPM has access to. main character energy: the TPM holds the key, nobody else does ๐Ÿ”ฅ

At some point, for example during the boot process, the boot loader will attempt to unseal this sealed secret. If the TPM's PCRs contain the expected values at this point in time, unsealing will succeed. If they do not contain the expected values, unsealing will either fail, or produce gibberish. like, literally nonsense bytes. ๐Ÿ’€

While regular PCR policies are sound and simple, they are not very flexible, unfortunately. For example, transparently handling updates of the boot loader is rather difficult because we would first have to retrieve the secret before we're able to re-seal it to the future set of PCR values. annoying. this is where Authorized Policies come in โœจ

TPM Authorized Policies provide a mechanism by which a secret is not sealed to a set of specific PCR values, but to a list of PCR indices, and a public RSA key. This combination (PCR indices + public key) is called an Authorized Policy. cute, right?

The owner of the system can then use the secret RSA key to "authorize" a specific set of PCR values, which is in essence a digital signature. very demure, very cryptographic ๐ŸŽ€

When unsealing the secret during system boot, the boot loader would present the TPM with the original Authorized Policy, and the signed set of PCR values. The TPM then essentially compares the current set of PCR values (as selected by the policy) to the authorized set of values, and verifies the authorization's signature using the public key from the policy. If the PCR values match, and the signature checks out, it will be able to unseal the secret. iykyk ๐Ÿ‘‘

The benefit of this approach is that you can now seal the secret (such as a LUKS password) once โ€” and when we later update the boot loader, shim loader etc, we just have to authorize the new set of PCR values using the RSA secret key. no re-sealing drama. obsessed. ๐ŸŒธ

๐Ÿฆ‹ Policy File Formats ๐Ÿฆ‹

To make things a little more complex, different file formats are being used to store policies and sealed secrets, depending on the implementation. so yeah. buckle up ๐ŸŽข

The current standard is the so-called TPM2.0 key file format, described in https://www.hansenpartnership.com/draft-bottomley-tpm2-keys.html. This format combines key material and policies into a single, ASN.1 encoded file. one file to rule them all ๐Ÿ’…

In addition, file formats have been used in the past that stored TPM objects in a native format defined in the TPM specification. vintage. retro. arguably iconic.

Finally, the systemd-boot implementation for unlocking disk partitions stores policies in a JSON formatted file, which can be fed to the systemd-cryptenroll tool. This file will contain any number of signed policies that we want systemd-cryptsetup to recognize during boot time. Each entry contains the list of PCRs that compose the PCR policy, a finger print of the public key, the hash of the policy, and the signature (in base64) of the PCR policy. like a little JSON scrapbook of trust ๐Ÿงš

pcr-oracle supports all three implementations. The target implementation can be selected using the --target-platform option, which accepts tpm2.0, systemd and oldgrub as arguments, respectively.

The default format is tpm2.0. no cap. โญ

๐Ÿ’œ RSA Key File Formats ๐Ÿ’œ

By default, pcr-oracle stores private RSA keys as PEM files and public RSA keys in native TPM encoded format. This behavior can be tuned, however โ€” when it detects a file name that has a .pem extension, it will automatically assume that the caller expects the file to be in PEM format. it just knows. ๐Ÿ”ฎ

In addition, you can explicitly force a specific file format by prefixing the entire path by either pem: or native:, respectively. be as explicit as you want, bestie, we respect it ๐Ÿ™ˆ


โœจ ACTIONS โœจ

The pcr-oracle command supports the following actions:

  • self-test โ€” Perform a TPM self-test to verify the chip exists and works. like a little vibe check for your silicon ๐Ÿ’…
  • rsa-test โ€” Check whether the TPM supports a given RSA key length. The desired key length is given using the --rsa-bits option. that's the whole test. ๐ŸŽ€
  • ecc-test โ€” Check whether the TPM supports ECC SRK keys. short, sweet, elliptic. ๐Ÿฆ‹
  • load-test โ€” Find out which Storage Root Key a sealed key was created under. The key is given with the --input or the --nvindex option. See the SRK detection example below. โฌ‡๏ธ
  • predict โ€” Try to predict a specific set of PCR values. the classic. the moment. โœจ
  • create-authorized-policy โ€” Create a PCR authorized policy. Optionally, a new RSA key can be generated. ๐Ÿ”‘
  • store-public-key โ€” Given a private key, extract the public portion and write it to a separate file. This is useful when a secret key has been generated while creating the authorized policy. split it, ship the public half ๐Ÿ’–
  • seal-secret โ€” Read a secret piece of data from a file, and seal it against a TPM policy (either a PCR policy or an authorized policy). Note that the TPM is limited in the maximum amount of secret data it can seal; it's probably safe to assume a limit of 128 bytes. ๐Ÿ”ฅ
  • sign โ€” When using an authorized policy, predict a set of PCR values and sign them using an RSA key. signature time, periodt. โœ๏ธ
  • unseal-secret โ€” This action exists primarily for test purposes. Given a sealed secret and (optionally) a signed policy, unseal the secret and write it to the specified output file. mostly for testing but still lowkey essential. ๐Ÿงช

๐ŸŒˆ EXAMPLES ๐ŸŒˆ

This section illustrates the use of pcr-oracle in different scenarios through HOWTO examples. very tutorial. very helpful. we love a walkthrough ๐Ÿซถ

๐ŸŒธ TPM Self-test ๐ŸŒธ

To perform a TPM self-test, simply execute pcr-oracle like this:

# pcr-oracle self-test

The reason this subcommand exists is that it allows an installer to set up full disk encryption without having to pull in all of tpm-tools. minimal dependency queen ๐Ÿ‘‘

๐ŸŒธ TPM RSA key size test ๐ŸŒธ

To perform a TPM RSA key size test, simply execute pcr-oracle like this:

# pcr-oracle --rsa-bits 2048 rsa-test

This subcommand allows external programs, such as fde-tools, to find out the largest supported RSA key size. it's giving "asking the TPM politely what it can handle" ๐Ÿ’…

๐ŸŒธ SRK detection ๐ŸŒธ

The parent of a sealed key is chosen when the key is sealed and can never change afterwards; signing a new PCR policy for the key does not touch it. A boot loader that is told to use a different SRK will therefore fail to unseal the key, and the key file itself is of little help: the tpm2key format records a rsaParent boolean, which tells RSA from ECC but not the RSA key size, and the oldgrub format records nothing at all. ๐Ÿ’€ yes, really. nothing.

The load-test action answers the question by asking the TPM, since TPM2_Load only succeeds under the actual parent:

# pcr-oracle --input /etc/fde/authorized-policy/sealed.tpm load-test
RSA3072

The algorithm name is written to standard output in the spelling grub2 expects for GRUB_TPM2_SRK_ALG, and the exit status is 0 when a match was found, 1 otherwise. Without further options, the candidates ECC, RSA2048, RSA3072 and RSA4096 are tried in that order; be aware that creating the larger RSA keys takes a while on most TPMs. like, go get a snack. ๐Ÿ“ Passing --ecc-srk or --rsa-bits restricts the test to that single candidate, which turns the action into a check of a configuration you already believe in. confidence, but verified. โœจ When the key names a persistent SRK, no probing takes place: the SRK is read from the TPM directly. no guessing needed.

๐ŸŒธ Prediction Mode ๐ŸŒธ

In prediction mode, pcr-oracle is invoked like this:

# pcr-oracle --from eventlog \
        --before --stop-event grub-command=tpm2_key_protector_init \
        predict 0,2,4,7,9

This would create a PCR prediction for registers 0, 2, 3, 7, and 9 by scanning the current TPM event log until the point where grub executes a command called tpm2_key_protector_init. ๐ŸŽฏ

As it processes the log, it will recompute the hash values for certain events to match the latest changes that were made to the system. For instance, for EFI Boot Variable events, it will consult the current value of a EFI variable and rehash it. The same holds for EFI Boot Service Application events for grub2 or shim; or for EFI GPT events. it's reading the receipts and updating them. iconic. ๐Ÿงพ

In the example above, pcr-oracle will print the predicted values to standard output. If you want to process these values with a tool from the tpm2.0-tools package, you may prefer binary output, which you can select by invoking the tool with --format binary. ๐ŸŒˆ

๐ŸŒธ Sealing Against PCR Policies ๐ŸŒธ

In order to seal a secret against a predicted set of PCRs, one would typically invoke pcr-oracle like this:

# pcr-oracle --from eventlog \
        --target-platform oldgrub \
        --before --stop-event grub-command=tpm2_key_protector_init \
        --input secret --output sealed \
        seal-secret 0,2,4,7,9
Note that the size of the secret to be sealed must not exceed 128 bytes. ๐Ÿ“ that is a hard limit, do not test it, do not push it.

The reverse operation looks like this:

# pcr-oracle --input sealed --output recovered \
        --target-platform oldgrub \
        unseal-secret 0,2,4,7,9

This will instruct the TPM to unseal the given sealed data, computing the policy hash using the current values of the indicated PCRs. ๐Ÿ”“ back it comes.

๐ŸŒธ Sealing Against Authorized Policies ๐ŸŒธ

The following sequence of commands seals a secret against an authorized policy, and then authorizes a set of PCR values by signing them into a signed policy:

# pcr-oracle \
        --target-platform oldgrub \
        --private-key policy-key.pem \
        --auth authorized.policy \
        create-authorized-policy 0,2,4,7,9
# pcr-oracle \
        --target-platform oldgrub \
        --auth authorized.policy \
        --input file-containing-secret \
        --output sealed-secret \
        seal-secret
# pcr-oracle \
        --target-platform oldgrub \
        --private-key policy-key.pem \
        --from eventlog \
        --before --stop-event grub-command=tpm2_key_protector_init \
        --output signed.policy \
        sign 0,2,4,7,9

three commands, one sealed secret, one authorizing signature, zero drama. the way that pipeline just works ๐Ÿ˜ญ ๐Ÿ’–

so the first of those commands makes the authorized policy and writes it into a file called authorized.policy ๐Ÿ’– that file, plus the public portion of the RSA key, has to live somewhere your boot path can actually reach when it's time to unlock the LUKS key โ€” store it somewhere accessible, bestie, no cap.

The second command seals the secret against that policy โœจ clear text goes in file-containing-secret, the encrypted result goes in sealed-secret.

And finally, the third command flexes pcr-oracle's prediction logic to predict a specific set of PCR values โ€” literally exactly the way the prediction mode section above describes ๐Ÿฆ‹ โ€” then signs those values with the given RSA key and drops the result in signed.policy. Iconic behavior, truly.

Depending on which boot loader you're using, it may help to hand over the public portion of the RSA key in a format that's trivial to handle. Like, adding DER parsing of RSA keys to grub would be so overkill ๐Ÿ’€ in a case like that you want the key in a form that can be fed to the TPM chip more or less directly. pcr-oracle supports this via its store-public-key subcommand:

# pcr-oracle \
        --private-key policy-key.pem \
        --public-key policy-pubkey \
        store-public-key

This one reads the RSA private key from the PEM file and writes the public key as a TPM2B_PUBLIC object to the indicated output file policy-pubkey ๐ŸŽ€

Alternatively โ€” if you want that public key stored as a PEM formatted key โ€” just give the public key file name a .pem extension:

# pcr-oracle \
        --private-key policy-key.pem \
        --public-key policy-pubkey.pem \
        store-public-key

For the deets, please see section RSA Key File Formats ๐Ÿ‘‘

โœจ Using the TPM2.0 Policy File Format โœจ

The following seals a secret against PCR 0,2,4,7 and writes the result to sealed-pcr.tpm using the new tpm2.0 format ๐ŸŒธ

# pcr-oracle \
     --target-platform=tpm2.0 \
     --input secret \
     --output sealed-pcr.tpm \
     seal-secret 0,2,4,7

To seal a secret for an authorized policy:

# pcr-oracle \
     --target-platform=tpm2.0 \
     --auth authorized.policy \
     --input secret \
     --output sealed-auth.tpm \
     seal-secret

To sign the policy against PCR 0,2,4,7 for sealed-auth.tpm:

# pcr-oracle \
     --target-platform=tpm2.0 \
     --policy-name "sealing test" \
     --private-key policy-key.pem \
     --input sealed-auth.tpm \
     --output sealed-auth-signed.tpm \
     sign 0,2,4,7

๐Ÿ” Sealing and Unsealing using a TPM NV Index ๐Ÿ”

By storing a sealed key directly inside a TPM Non-Volatile (NV) index, bootloaders (such as GRUB) can unseal keys without having to read any key file from disk ๐Ÿ‘€ that's the whole vibe.

The following command seals a secret against PCR 0,2,4,7 and writes the resulting key container directly into the TPM NV Index 0x01800001:

# pcr-oracle \
     --target-platform=tpm2.0 \
     --nvindex 0x01800001 \
     --input secret \
     seal-secret 0,2,4,7

To unseal the secret directly from the TPM NV Index โ€” no input key file needed at all:

# pcr-oracle \
     --target-platform=tpm2.0 \
     --nvindex 0x01800001 \
     --output recovered \
     unseal-secret

It's also possible to update an existing key container with a signed policy and write the resulting container straight to a TPM NV Index ๐Ÿ’… when you do that, the output file on disk is optional, because the key container is stored directly in the NV Index.

For example, to sign a PCR policy for sealed-auth.tpm and write the updated container to the TPM NV Index 0x01800001 without generating/updating a disk file:

# pcr-oracle \
     --target-platform=tpm2.0 \
     --policy-name "authorized-policy-test" \
     --private-key policy-key.pem \
     --input sealed-auth.tpm \
     --nvindex 0x01800001 \
     sign 0,2,4,7

๐Ÿงš Generating a Signed Policy for Systemd ๐Ÿงš

When you're using systemd-boot instead of grub2, you'd create an authorized policy as above and seal your secret LUKS key against that ๐Ÿซถ the main difference is in the way the signed policy is stored:

# pcr-oracle \
     --target-platform=systemd \
     --private-key policy-key.pem \
     --from eventlog \
     --output tpm2-pcr-signature.json \
     sign 0,2,4,7,9,12

This predicts a set of PCR values, signs them using the specified private key, and then stores the signature in tpm2-pcr-signature.json โœจ heads up: this command will not overwrite the JSON file โ€” it updates it by adding a new entry. If there already is an entry for the predicted set of PCR values, this merely updates the public key fingerprint and signature. Lowkey polite of it, honestly.

These policies can then be enrolled for future system boots using systemd-cryptenroll:

# systemd-cryptenroll \
     --tpm2-device=auto \
     --tpm2-public-key=public-key.pem \
     --tpm2-public-key-pcrs="0,2,4,7,9,12"   --tpm2-signature=tpm2-pcr-signature.json \
     /dev/sda3

๐Ÿ“ Creating and Replaying Test Cases ๐Ÿ“

Firmwares tend to be buggy โ€” like every piece of code, tbh โ€” and UEFI firmwares are no different ๐Ÿ˜ญ in order to create a broad base of test cases to validate pcr-oracle against, it comes with support for creating and replaying test cases.

A test case is mostly a dump of the data that pcr-oracle processes when re-hashing the events from the TPM event log ๐Ÿ‘‘ this includes the content of UEFI variables such as Boot entries, variables related to Secure Boot, GPT headers, full copies of any EFI Applications loaded during bootup (such as shim.efi, grub.efi), and hashes for other files loaded by the boot loader (such as grub.cfg, or the kernel and initrd). It's giving forensic scrapbook, and we love that for us.

The following command creates a test case and stores it in a directory tree at /tmp/pcr-oracle.test:

# pcr-oracle --from eventlog --verify current -d \
        --create-testcase /tmp/pcr-oracle.test \
        predict all

If you want to submit your test case, please use tar to archive the test case and create an issue in the github issue tracker at github.com/okirch/pcr-oracle ๐Ÿฆ‹

To replay a test case, you can do this:

# pcr-oracle --from eventlog --verify current -d \
        --replay-testcase /tmp/pcr-oracle.test \
        predict all

โœจ OPTIONS โœจ

okay bestie, this is the big one โ€” every single flag, no skipping, we're going through all of them ๐Ÿ’…

  • --from source โ€” in prediction mode, this tells pcr-oracle where to start the PCRs from. Your choices are eventlog, current, or zero. ๐Ÿ’–

with eventlog, the tool scans the TPM event log and re-computes the event hashes based on the current state of the system, fr fr.

with zero, prediction starts with all PCRs cleared. with current, prediction starts with all PCRs set to the current values of the system's TPM. pick your vibe ๐Ÿฆ‹

  • -Z, --from-zero โ€” the shorthand bestie: sets the PCR initial state to zero. literally the same as --from zero.
  • -C, --from-current โ€” shorthand that sets the PCR initial state to the current state of the host's PCRs. equivalent to --from current โœจ
  • -S, --from-snapshot โ€” shorthand that sets the PCR initial state to a snapshot taken during boot. equivalent to --from snapshot ๐ŸŽ€
  • -L, --from-eventlog โ€” shorthand that sets the PCR initial state predicted from the event log. equivalent to --from eventlog.
  • --algorithm hash-alg โ€” by default pcr-oracle operates on PCR registers using the sha256 hash algorithm. this option lets you pick a different one, assuming the chip supports it. ngl, for backward compatibility with version 1 of the specification, all TPMv2 chips also support sha1 โ€” but using that is not recommended ๐Ÿ™ˆ
  • --verify source โ€” after applying all the updates, compare the predicted PCR values against the given source (e.g. current or snapshot). vibe check for your PCRs ๐Ÿ’…
  • --format fmt โ€” in prediction mode, pcr-oracle writes predicted PCR values to standard output in a human readable format. this option picks a different output format. ๐ŸŒธ

a format of binary writes the predicted values as a binary octet stream, all values concatenated together. this format can be used with tools like tpm2_policypcr(1). a format of tpm2-tools will use a reporting format โ€” similar to the one that the tpm2-tools produce as output. the default behavior is selected by format plain.

  • --stop-event event-desc โ€” specify at which point to stop processing the TPM event log, and report the predicted PCR values. the format of event-desc is an event type, followed by a =, followed by a string argument.

currently only two event types are supported: grub-command and grub-file. these correspond to the IPL event emitted by grub when executing a command, or reading a file, respectively.

for a grub command event, the string argument is the name of the command, with no arguments. for a grub file event, the string argument is a file name or a (partial) file path. path tail matching is performed, i.e. an event for reading EFI/BOOT/grub.cfg can be matched by specifying a stop event for grub.cfg, BOOT/grub.cfg, or EFI/BOOT/grub.cfg, respectively. โœจ

  • --before โ€” when a stop event has been given, report predicted PCR values before processing the event. this is the default behavior ๐Ÿ‘‘
  • --after โ€” when a stop event has been given, report predicted PCR values after processing the event. this may be useful, for example, if you don't measure grub's command events (tracked in PCR8) but only its file load events (tracked in PCR9). in that case it would be totally sufficient to stop processing after grub loaded grub.cfg from the EFI System Partition. lowkey genius ๐Ÿงš
  • --disable-synthesis โ€” disables predictive event synthesis and deduplication for shim extra files. periodt.
  • --boot-entry id โ€” in a systemd-boot environment, the values of PCR registers also depend on the contents of the kernel and initrd image, as well as the kernel command line options. after a kernel update or an initrd rebuild, these values will change ๐Ÿ˜ญ so when predicting PCR values in this case, pcr-oracle needs to be told to refer to the future kernel rather than the current one.

that's what --boot-entry is for. it takes one argument, which is either an ID (in the sense of systemd-boot IDs) or auto. with auto, pcr-oracle makes a best guess as to what kernel image will be used on next boot. ๐Ÿฆ‹

  • --next-kernel name โ€” an alias for --boot-entry. specifying this sets the boot entry to use for the next boot prediction.
  • --auth path, --authorized-policy path โ€” specify the location of the authorized policy. together with the create-authorized-policy action, the newly created policy is written to this location. for subsequent actions, such as seal, the policy is read from this location. ๐Ÿ’–
  • --pcr-policy path โ€” specify the location of the PCR policy file used during authorized policy unsealing.
  • --input path โ€” specify the input secret file path for sealing, or a sealed secret for unsealing.
  • --output path โ€” specify the output destination path for sealing, unsealing, or public key storage.
  • --private-key path โ€” specify the RSA secret key to be used with authorized policies. for notes on the file format, please see section RSA Key File Formats. ๐Ÿ”‘
  • --public-key path โ€” specify an RSA public key to be used with authorized policies. for notes on the file format, please see section RSA Key File Formats. ๐Ÿ”‘
  • --rsa-generate-key โ€” when used while creating an authorized policy, a signature, or when storing the public key, this generates the RSA private key "on the fly". this should not be different from what openssl genrsa does โ€” the only reason this switch exists is that it may save you from increasing the footprint of your installed system (by not having to install the openssl command line utilities, for example). iconic, honestly โœจ
  • --rsa-bits bits โ€” by default RSA 2048 is used as the algorithm to create the public and private key and configure the storage root key (SRK) in TPM. this option lets the user specify the larger RSA key size. the supported key sizes are: 2048, 3072, and 4096 bits. ๐Ÿ“
  • --ecc-srk โ€” switches the storage root key (SRK) configuration to Elliptic Curve Cryptography (ECC P-256) instead of the default RSA algorithm. slay ๐ŸŒˆ
  • --persistent-srk handle/index โ€” configure a persistent storage root key (SRK) handle or index on the TPM.
  • -n, --nvindex handle โ€” specify the TPM NV Index handle (which must start with 0x01 in the MSB, e.g. 0x01800001) to write, sign, or unseal a key container. this option is currently only supported when using the tpm2.0 target platform. when sealing or signing a policy, the resulting key container is stored directly in the specified NV space (even if no disk-based output file is specified). when unsealing, the key container is read directly from the specified NV space, bypassing the need for a disk-based input file. ๐Ÿ’…
  • --tpm-eventlog path โ€” by default the tool reads the current TPM event log. it's possible to process an event log generated on a different system by specifying it with this option. ๐Ÿฆ‹
  • --target-platform name โ€” write key and policy information using file format(s) compatible with the specified target implementation. please see the section Policy File Formats for more information.
  • --key-format name โ€” deprecated alias for --target-platform. prints a warning to use --target-platform instead. โš ๏ธ
  • --policy-format name โ€” deprecated alias for --target-platform. prints a warning to use --target-platform instead. โš ๏ธ
  • --policy-name name โ€” for the tpm2.0 format there's an extra field, Name, for the authorized policies. this option is only valid when signing the sealed key in tpm2.0 format. the Name is optional and only used for display purposes. if the user doesn't specify a name, the default name is default. ๐ŸŽ€
  • --create-testcase path โ€” dumps current boot state variables, files, and hashes into a directory tree at the given path, to create a test case for regression testing. ๐Ÿงช
  • --replay-testcase path โ€” replays a previously captured test case from the directory tree at path, simulating the recorded system state. ๐Ÿ”
  • --compare-current โ€” complements --replay-testcase to compare predictions of the test case against the host's actual current event log. ๐Ÿ’–

๐ŸŒŸ SEE ALSO ๐ŸŒŸ

tpm2_policypcr(1), tpm2_policyauthorize(1), tpm2_create(1).

go read those too bestie, iykyk ๐Ÿซถ


๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

pcr-oracle was written by Olaf Kirch, with significant contributions from Gary Lin, Michael Chang and Alberto Planas. legends, every single one ๐Ÿ˜ญ๐Ÿ’œ


and that's the whole page, no cap ๐Ÿ’… this is version 0.6.6, dated September 18, 2026 โ€” the PCR-ORACLE(8) manual, and honestly? it ate. go predict some PCRs, bestie, you've got this ๐ŸŒˆโœจ๐Ÿ“

๐Ÿ“ฆ where this page comes from