AuthBox documentation — User guide

Documentation embedded in this build.

Enrolling with AuthBox

Before you can talk to anything AuthBox protects, you need a client certificate. This page walks every way to get one. Pick whichever fits how you work — none of them is more "official" than another, and your administrator may have already decided which ones this deployment offers.

One thing is true of every path below: your private key is generated on your own machine, and it never leaves it. AuthBox does not generate your key, does not receive it, and does not store it. What you send is a certificate signing request (a CSR) — a public key plus the name you are asking to be issued under — never the private half.

What you will need

If what you are actually after is an SSH certificate — logging in to machines without anybody putting your key in an authorized_keys file on each of them — that is the same intake and the same invitation, but a different key, a different flag and a different thing arriving back: Logging in to a machine is the page for it.

Path 1: the command-line tool (authboxclient)

The most convenient path if you can install a binary. It generates the key, fetches the enrollment profile so it can tell you locally if a choice would be refused, builds the CSR, and submits it — one command:

authboxclient enroll -server https://authbox.example:8443 -ca ca.pem \
    -subject "CN=alice,O=acme,C=US" -invitation YOUR-TOKEN

Drop -invitation if this deployment does not require one. If your invitation auto-issues, the certificate comes back immediately. Otherwise the request is queued and an administrator has to approve it — you will see status pending and a request ID.

authboxclient keeps your credential in a local, encrypted store rather than a bare key file lying around your home directory. authboxclient credentials list shows what you hold; authboxclient status reports the currently active one.

If you also run authbox-agent, it renews this credential automatically before it expires — see Certificates.

Path 2: the console, if you already have an invitation

An administrator can create an invitation from the console's Invitations page and hand you the link it generates. Opening that link takes you to the public enrollment page with your token already filled in — you still generate the key and CSR yourself (the console never asks you to paste a private key, and never could safely process one if you tried). From there, follow the on-page instructions, which are the same openssl/curl steps in Path 3 below, or run the one-line script the page also offers.

Path 3: openssl and curl, by hand

Nothing above is required. You can do everything with tools you likely already have:

openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
    -keyout authbox.key -out authbox.csr \
    -subj "/C=US/O=your-org/CN=your-name"

Order matters, and openssl reverses it. A name written CN=alice,O=acme,C=US is given to -subj as /C=US/O=acme/CN=alice — most general first. Typing it the other way round produces a different name that looks the same in a listing, and enrollment will be refused for a subject that does not match what your invitation (if any) names.

Keep authbox.key somewhere safe. Without it, the certificate you get back is useless, and nobody — not even AuthBox itself — can issue you another one that matches it.

Submit the request:

curl --data-binary @authbox.csr \
    -H "X-AuthBox-Invitation: YOUR-TOKEN" \
    https://authbox.example:8443/enroll

Drop the header if you have no invitation. You can also paste the CSR's contents into the form on the public enrollment page instead of using curl.

Path 4: ACME

If your deployment runs AuthBox's ACME server (ask your administrator whether acme.enabled is set), any standard ACME client — certbot, acme.sh, your platform's built-in ACME support — can enroll the same way it would against any other certificate authority. Your ACME account has to be bound to an invitation first (an External Account Binding, in ACME's own terms); your administrator provides the EAB key ID and HMAC key alongside the invitation.

What your client will see. An order is placed, an authorization comes back already valid with no challenges to complete, and finalizing it returns your certificate. That is deliberate: the External Account Binding already proved you were authorized, so a second network challenge would prove the same thing twice. One thing will surprise you, and the order response says so up front rather than leaving it to be discovered: the certificate's subject is the one your invitation was bound to, not the identifier your client asked for. Finalizing with a certificate request that disagrees is refused rather than quietly overridden.

When ACME asks you to prove your key lives in hardware

If your invitation names a profile that requires proven key custody, the flow above changes in exactly one place: the order comes back pending, and its authorization carries one device-attest-01 challenge to answer first. This is draft-ietf-acme-device-attest, the ACME device attestation extension, and it proves something the External Account Binding cannot — that the key this certificate will cover was generated inside hardware a manufacturer vouches for.

Your client answers it by:

  1. Building the ACME key authorization from the challenge's token and your account key, exactly as RFC 8555 §8.1 describes — token, a dot, and your account key's thumbprint.
  2. Asking the device to attest the key you are enrolling, with that key authorization as the challenge it binds.
  3. POSTing the resulting WebAuthn attestation object to the challenge URL as {"attObj": "<base64url>"}.

Two formats can answer it here: apple, whose attestation certificate carries the challenge in an extension, and tpm, a TPM 2.0 key certification whose extraData carries it. piv cannot — a PIV attestation certificate is minted once, when the key is generated, and has nowhere to put a challenge that did not exist yet. That is not an AuthBox limitation but the draft's own rule, and a PIV attestation is fully accepted at the enrollment door above, where the certificate request arrives in the same message as the statement and the binding is already there. If you hold a PIV attestation, use Path 1, 2 or 3.

The key you attest must be the key you certify. The attestation is checked against the public key in your certificate request at finalize, and a mismatch is refused. This is what makes the challenge worth anything: attestation objects are public, so without that check anybody holding a copy of one could answer the challenge and then obtain a certificate for a key they generated in software.

What you get for it. Exactly what Path 1–3's attested enrollment gets: the two attributes below, recorded against your credential, earned by the check rather than granted by anybody.

Path 5: EST (RFC 7030)

For gear that asks for EST by name and cannot speak ACME — defence, telco and industrial hardware whose certificate stack was fixed long before it reached you. Ask your administrator whether est.enabled is set.

If it is, the operations below sit on the same enrollment listener as everything above, and they are a different way of spelling a request rather than a different way of having one granted: the same invitation, the same profile, the same queue, the same ledger, the same refusals. Two things EST's own wire format cannot carry are stated at the end of this section rather than left for you to discover.

Fetch the CA chain first: GET /.well-known/est/cacerts. This is the one operation that needs no credentials of any kind, and RFC 7030 §4.1.1 gives the reason: a client that has not yet been provisioned with this CA's certificate cannot validate anything at all — not a certificate it is issued, and not even the TLS certificate of the server it is asking. What is served is public by construction; trust anchors are published, not protected. The response is a base64 certs-only PKCS#7, so read it like this:

curl -s https://authbox.example:8443/.well-known/est/cacerts \
    | openssl base64 -d \
    | openssl pkcs7 -inform DER -print_certs

Enrolling: POST /.well-known/est/simpleenroll. The body is your certificate request in DER, base64-encoded — raw DER is refused, and the refusal names the encoding rather than describing the failure. The Content-Type is application/pkcs10. Authentication is HTTP Basic, and this is the first thing that will surprise you: your invitation token is the password, and the username is not used. That is RFC 7030 §3.2.3's own provision — a client may present "a password that is not associated with a username" — and an invitation token is exactly that: a bearer secret that names its own audience, with nothing honest to put beside it. Send an empty username:

openssl req -new -key authbox.key -subj "/C=US/O=your-org/CN=your-name" -outform DER \
    | openssl base64 > authbox.b64

curl --data-binary @authbox.b64 \
    -H "Content-Type: application/pkcs10" \
    -u ":$TOKEN" \
    https://authbox.example:8443/.well-known/est/simpleenroll \
    | openssl base64 -d | openssl pkcs7 -inform DER -print_certs

Two things about what comes back. The first is Path 4's surprise, unchanged, because it is the same rule and not a resemblance: the certificate's subject is the one your invitation was bound to, not what your certificate request asked for. The CSR is a carrier for your public key; the name comes from the invitation and the profile. The second is that the response contains only the certificate you were issued — RFC 7030 §4.2.3 says a successful response carries "only the certificate that was issued", so the chain is not in it. Fetching the chain is what /cacerts above is for.

Renewing: POST /.well-known/est/simplereenroll. Present the certificate you are renewing as your client certificate at the TLS handshake; that possession is the whole authorization, and a handshake the server did not verify counts as presenting nothing. This is POST /enroll/renew in another dialect — the same code decides both — so the same three rules hold, with no EST-shaped exception to any of them:

Three operations are served; three are refused on purpose. All six are routed, so a refusal reaches you as a reason rather than a bare 404 that would be indistinguishable from an older build:

Two invitations this door will not admit, and where each one does work. Both are refused up front, with the reason in the body and your invitation still unspent:

Proving your key lives in hardware

Everything above enrols a key and says nothing about where that key lives. If your key was generated inside a security key, a smart card, or a phone's secure enclave, the device can usually produce an attestation: a short certificate chain, signed by the manufacturer, saying "this public key was generated on one of our devices and cannot be exported from it". AuthBox can check that chain and record the result.

Get the chain from your device's own tooling. For a YubiKey PIV slot:

ykman piv keys attest 9a attestation.pem
ykman piv certificates export f9 device.pem
cat attestation.pem device.pem > chain.pem

Then submit the certificate request and the statement together, as JSON:

python3 - <<'EOF' > enroll.json
import json
print(json.dumps({
    "csr": open("authbox.csr").read(),
    "attestation": {"format": "piv", "chain": open("chain.pem").read()},
}))
EOF

curl --data-binary @enroll.json -H "Content-Type: application/json" \
    -H "X-AuthBox-Invitation: YOUR-TOKEN" \
    https://authbox.example:8443/enroll

The manufacturer's ROOT is not part of what you send, and sending it would not help: a statement that carried its own trust anchor would be vouching for itself. AuthBox compares your chain against the roots your administrator put in the deployment's dataset, so attested enrollment works only for manufacturers your deployment has decided to trust — ask them which.

What this proves, stated plainly. That a manufacturer your deployment configured signed a chain ending in a certificate for the exact public key in your certificate request, and that you hold that key (your certificate request is signed with it). What the manufacturer asserts by signing such a chain is that the key was generated on the device and cannot leave it.

What it does not prove. Nothing about the device now, or about who is holding it. Nothing AuthBox checked for itself about the key being unexportable — that is the manufacturer's claim about its own product, and configuring their root is the act of deciding to believe it. And nothing about PIN or touch policy, which is a different question that this does not answer.

A statement that does not check out refuses the enrollment. It never falls back to an ordinary one: if you presented evidence and got a certificate back, you would reasonably conclude the evidence was accepted, so AuthBox refuses instead of quietly issuing you a certificate recorded as software-held. The commonest causes are a chain for a different key than the one in your request, and a manufacturer this deployment does not trust.

Three limits worth knowing before you try. The statement is accepted only on an enrollment that issues immediately — an auto-issuing invitation — because a request that waits in the queue for an administrator has nowhere to record the result when it is finally approved. Your subject must already have an entity record in the deployment's dataset: attestation is recorded onto a credential of an identity AuthBox already knows, and enrolling is not allowed to create one. And a statement that BINDS a challenge — Apple's attestation certificates, and every TPM certification — needs a challenge to bind: this door does not issue one, so ask your administrator for a nonce from the administrative surface (which needs a certificate you already hold, so this serves adding a second, hardware-held key rather than a first enrollment), or use the ACME device-attest-01 challenge in Path 4, which issues its own.

Afterwards. The result is recorded against your credential as two attributes, key_custody: attested and attestation: whichever format proved it (piv, apple, or tpm). A deployment can then require them — an operation, or a route on the proxy, can demand key_custody of at least attested, and your certificate satisfies it where an ordinary one does not. One consequence follows immediately, and it is in Living with your certificate: renewing such a credential onto a DIFFERENT key is refused, because nothing attested the new one.

What happens after you submit

The certificate you receive is built from the deployment's certificate profile, not from everything you asked for: a profile fixes some fields and may drop others your request tried to set. What you asked for and what you were issued can legitimately differ, and that is not a bug — see the console tour's notes on the Enrollment queue page for how an administrator sees the same distinction.