pls is a human-first tool for working with x509 certificates and other WebPKI/TLS
primitives. You ask it nicely to parse a file or get a server's certs and It Just Works.
Reading a chain of certificates:
pls:
pls parse ./test-data/certs/chain.pemopenssl:
openssl storeutl -noout -text -certs ./test-data/certs/chain.pemIt. Just. Works.
No fussing with arguments, no "whoops, the file is chain. Guess I need to
manually split it up somehow". Or, "how do I connect to a server and just get
its chain? Wait, I need to pipe some output into sed. Why is my terminal
hanging? What's this debug stuff?"
Oh, and pls also handles certs mixed with yaml, json, or w/e just fine. Good
luck getting that to work with openssl.
And because it's 2025, there's json output with the --json flag. If you pipe
pls' output into another cli tool, json output is automatic.
pls parse ./test-data/certs/chain.pem | jq '.certs|map(.subject.name)'
[
"CN=lan.fish",
"C=US, O=Let's Encrypt, CN=E6",
"C=US, O=Internet Security Research Group, CN=ISRG Root X2"
]I'm still missing some fields in the json output, that's coming.
Chains are assumed. Yes it's annoying to put a .[0]| in front of jq filters, but for that everything just works if you end up "accidentally" parsing a chain.
It's also much more concise:
$ openssl x509 -in ./test-data/certs/lan-fish.pem -text -noout | wc -l
73
$ pls parse ./test-data/certs/lan-fish.pem --text | wc -l
25As long as the ----BEGIN and ----END "headers" exist somewhere in the file, and the base64 characters in those headers are valid, pls can parse it.
pls will parse:
- a stack of PEM certificates (a chain).
- certs spread throughout a yaml file with multiple indentations
- certs in escaped json
- other PEM entities in a file. Things like public/private keys and CSRs.
See the GitHub releases for prebuilt binaries.
Or use cargo-binstall:
cargo binstall --git https://github.com/fisherdarling/pls pls-cli
cargo install --locked --git https://github.com/fisherdarling/pls
We're currently using a fork of
boringwhich adds more support for reading x509 fields.
pls parse ./certs/cloudflare.pemColoring is a little weird with termshot.
pls connect 1.1.1.1pls connect 1.1.1.1 --pqc --no-certpls connect 1.1.1.1 --curves X25519MLKEM768 --no-certUnless another option is set, e.g. --pem or --text, pls will output json if stdout is not a TTY.
There's a ton of fields in the json output and many are redundant. The goal is to
make writing jq filters very easy.
{
"certs": [
{
"subject": {
"name": "CN=lan.fish",
"ski": "a5bc6f4ea76b6de4f56bcf85ec86921c85b852b7",
"sans": {
"dns": ["lan.fish", "p2p.lan.fish"]
}
},
"serial": "0347bb00b7415c11d012f7ca5686100f4474",
"issuer": {
"name": "C=US, O=Let's Encrypt, CN=E6",
"aki": "9327469803a951688e98d6c44248db23bf5894d2"
},
"not_before": "2025-01-03T13:18:47Z",
"not_after": "2025-04-03T13:18:46Z",
"expires_in": 3737794,
"valid_in": -4038204,
"valid": null,
"verify_result": null,
"ski": "a5bc6f4ea76b6de4f56bcf85ec86921c85b852b7",
"aki": "9327469803a951688e98d6c44248db23bf5894d2",
"public_key": {
"bits": 256,
"curve": "id-ecPublicKey",
"type": "ec",
"group": "prime256v1",
"key": "02886615260d6c0d06713f3d9a90f51f2214b799a07bf732335ef793fa05192cad"
},
"key_usage": {
"critical": true,
"digital_signature": true,
"content_commitment": false,
"key_encipherment": false,
"data_encipherment": false,
"key_agreement": false,
"key_cert_sign": false,
"crl_sign": false,
"encipher_only": false,
"decipher_only": false,
"extended": {
"critical": false,
"server_auth": true,
"client_auth": true,
"code_signing": false,
"email_protection": false,
"time_stamping": false,
"ocsp_signing": false,
"custom": []
}
},
"signature": {
"algorithm": "ecdsa-with-SHA384",
"value": "3066023100ca00eb23cb0df9dc246ccbc5e98cb2d1630cb8f0c0d3f8bf251bc933bd3efcf1eb6dae7516caaa73793fa8b371b78160023100ee555bfcea977132ce2a96cb102d9aaeb6f3a615ffe20d3221f47ae383b9ebc6e7e2e13c144f5267d31dde16b96c66da"
},
"extensions": {},
"sha256": "876172fb012989edbc93d2c4c34399f1dff9b5e90f0f30b9c6d2ed82ec184620",
"sha1": "e317da2ee772c724754f489c4f06eb8f0fa14f37",
"md5": "146f1fa78dee36f9421126e2a7cc589a",
"pem": "</snip>"
}
],
"csrs": [],
"private_keys": []
}- Parse and display PEM encoded entities:
- x509 Certificates
- CSRs
- fingerprint: sha256 digest of DER
- Public/Private Keys
- fingerprint: sha256 digest of DER
- Parse and display DER encoded entities
- Automatically output JSON
- Output PEM on request
- Parse every file in a folder
- Entity specific commands, rather than only a catch-call
parse.-
cert parse -
csr parse -
pub parse -
priv/key parse
-
Because boringssl is awesome. If the library can't parse something that openssl, sorry about that.
After doing 12 cert rotations involving modifying and looking at random PEM in yaml,
you start begging openssl to just pls do the right thing.
Why does openssl default to PEM output??? It's useless. And if you specify -text you still need to add -noout. What? Oh, and dealing with chains in a single file is clearly blasphemous.
And another gripe of mine is the usage of ssl in both openssl and boringssl. I refuse to put ssl in the name of this tool. There's a reason TLS exists. pls is close to tls while being short and sweet.
In my rather short experience working with certificates, I've only every seen v3 x509 certs. I believe the spec is 20 years old now. I'm not putting in effort to support older versions.
If the curve is supported by boringssl, it's probably just not handled by pls. Please raise an issue.
The "parser" is rather opnionated (read: full of unwrap()s) on what's considered optional. If the cert is a useful variant of x509 v3, please open an issue and I'll see about fixing things / making it optional.



