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
- The server's address (ask your administrator).
- The server's CA certificate, so your tools trust it (also from your administrator).
- Possibly an invitation: a one-time token that either grants you a certificate immediately or names exactly what group membership you receive on approval. Some deployments require one; some do not. If you were given a link containing
?invite=..., you have one already.
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:
- Building the ACME key authorization from the challenge's
tokenand your account key, exactly as RFC 8555 §8.1 describes —token, a dot, and your account key's thumbprint. - Asking the device to attest the key you are enrolling, with that key authorization as the challenge it binds.
- 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:
- Your credential needs the
autorenewattribute, exactly as the native renewal endpoint requires it. The requirement belongs to the operation ("may this identity renew itself unattended"), not to the dialect that asked, so your request is judged againstPOST /enroll/renew's declared requirement whichever door it arrived at. - The new certificate request must name the same subject as the certificate you presented. Renewal never changes who you are, and a request naming a different subject is refused rather than quietly issued under the old name.
- If your key custody was proven at issuance, you must renew onto the same key. Nothing attested a new one, so moving to it is refused — see Living with your certificate.
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:
csrattrsanswers 404, which RFC 7030 §4.5.2 itself defines as "a CSR Attributes Response is not available". The attribute a client most wants from that operation is the subject, and the subject is precisely what this CA never takes from a certificate request — naming any would be instructing you to send something that has no effect on what you receive. The question you were really asking is answered byGET /enroll/profile, authenticated by the same invitation token: the permitted algorithms, the RSA floor, any key-custody requirement, and the validity your invitation will be judged against.serverkeygenanswers 501, and 501 rather than 404 because this is a refusal and not a gap: a CA that generates your private key is the opposite of everything here. No setting turns it on.fullcmcanswers 501 for a plainer reason: it is a second enrollment protocol inside the first, and nothing this deployment decides needs any of it.
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:
- An invitation whose profile requires proven key custody. RFC 7030 §4.2.1 fixes this request to a base64 PKCS#10 and nothing else, so there is nowhere in it for an attestation statement to ride. Rather than invent an extension to a standard, or issue as though the requirement had been met, this refuses and names the doors that do carry a statement: Path 1, 2 or 3, where the statement arrives in the same message as the certificate request, or Path 4's
device-attest-01challenge. - An invitation that enrols through an administrator's approval queue. EST's only way to say "pending" is a 202 with a
Retry-After, after which RFC 7030 §4.2.3 requires the client to repeat the identical request — and this deployment spends an invitation when a request is queued, so the repeat would present a spent invitation and be refused, and the certificate an administrator eventually approved would have no way of reaching you. Answering 202 would be promising an answer that could never be collected. Use an auto-issuing invitation, or enrol through Path 1, 2 or 3, which give you a request ID an approval can be collected against.
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
- Auto-issuing invitation: the certificate returns in the same response. Install it alongside
authbox.keyand you are done. - No invitation, or one requiring approval: your request sits in the enrollment queue until an administrator approves or rejects it. There is nothing more to do on your end — no polling endpoint to hit.
authboxclient enrollwill have told you a request ID; ask your administrator to look it up if it has been a while.
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.