Create your own clients
With an organisation key, your organisation creates its own clients and gets each client’s certificate in the same answer. No registration key, no email, no waiting for Knovas.
The organisation key
Knovas issues the key for your organisation. It is shown only once, when it is issued, and starts with knovas_ook_. Knovas stores only a fingerprint of it, so it cannot show you the key again: store it in your secrets manager straight away.
| The key can | The key cannot |
|---|---|
| Create a client in your organisation and return its certificate | Open /secured/..., read documents, or list your clients |
| Renew the certificate of one of your clients, together with that client’s current private key | Do anything in another organisation |
A client is one user of Knovas: a person, a machine or an installation. Every client has its own certificate and its own private space for documents. Clients do not see each other’s documents, even within one organisation.
| Limit | Value |
|---|---|
| Lifetime of a key | At most one year. Knovas tells you the date. |
| Clients per key | Set by Knovas when it issues the key (100 unless agreed otherwise). Every answer tells you how many are left (clients_left). |
| Events per key and hour | 20: clients created, certificates renewed and refused duplicates together |
| Active keys per organisation | 2, so you can switch to a new key before the old one is revoked |
- Store it in a secrets manager and send it only in the
Authorizationheader. - Never put it in a web address, a request body, a log, a code repository, an email or a chat. Its
knovas_ook_prefix lets secret scanners find it.
Every client created and every certificate renewed with your key sends an email to the contact Knovas named when it issued the key (otherwise to your organisation’s main contact). The email names the client id, never the key. If you get one for something you did not do, contact Knovas at once: the key may have leaked.
If the key leaks, is lost or should change
| Situation | What happens |
|---|---|
| The key leaked | Tell Knovas at once. Knovas revokes it, and it stops working on its next use. Clients created with it keep working, unless Knovas also withdraws their certificates; those then stop working within 45 seconds. |
| The key is lost | Knovas cannot show it again. It revokes the lost key and issues a new one. |
| You want a new key | Ask Knovas for a second key, switch your systems to it, then ask Knovas to revoke the first one. |
Create a client
Send POST https://api.knovas.ch/onboard_client (port 443, no client certificate) with the header Authorization: Knovas-Org-Key <your organisation key> and these fields:
| Field | Required | What it is |
|---|---|---|
first_name, last_name | Yes | The client’s name (each at most 255 characters). |
email | Yes | The client’s email address. It is written into the certificate, so it must be a plain address without special characters. Knovas never checks it and never writes to it. |
country, city, postal_code, address | No | The client’s own address, if it has one. |
csr_pem | No, but recommended | A signing request for a key you created (see below). |
allow_duplicate | No | true creates a second client with an email address that a client of your organisation already has. |
Any other field is refused (400 UNKNOWN_FIELD). You never name an organisation or a client id: the organisation always comes from the key, and Knovas chooses the client id (400 FORBIDDEN_FIELD).
Who makes the private key
With csr_pem (recommended) | Without csr_pem | |
|---|---|---|
| Who makes the private key | You, on the client’s machine | Knovas, for this one answer |
| What the answer carries | The certificate | The certificate and the private key |
| If the answer is lost | Retry with the same Idempotency-Key and you get the certificate | The private key is gone. Create a new client |
Either way Knovas writes the certificate’s details itself: the client id, your organisation’s name and the email. From a signing request it takes only the public key. Accepted keys: RSA with at least 2048 bits, or EC on the P-256 or P-384 curve. Knovas never stores a private key it makes. The example creates the key on this machine and sends only a signing request:
umask 077 # new files are readable by you only
IDEMPOTENCY_KEY=$(openssl rand -hex 16) # keep it until you have the answer
# 1. A new key and a signing request, on the client's machine
rm -f client_key.pem # permissions only apply to new files
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-keyout client_key.pem -out client.csr -subj "/CN=new client"
# 2. Create the client. --retry repeats the same request after a timeout or a
# temporary error such as 503, with the same Idempotency-Key and body.
RESPONSE=$(curl -s --retry 4 -X POST https://api.knovas.ch/onboard_client \
-H "Authorization: Knovas-Org-Key $KNOVAS_ORG_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile csr client.csr \
'{first_name: "Jane", last_name: "Doe", email: "jane.doe@example.ch", csr_pem: $csr}')")
echo "$RESPONSE" | jq -r '.client_id'
echo "$RESPONSE" | jq -r '.certificate_pem' > client_cert.pem
echo "$RESPONSE" | jq -r '.ca_root_cert' > ca_root_cert.pem# pip install cryptography requests
import os
import time
import uuid
import requests
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.x509.oid import NameOID
# 1. A new key and a signing request, on the client's machine. Save the key first.
key = ec.generate_private_key(ec.SECP256R1())
if os.path.lexists("client_key.pem"): # permissions only apply to new files
os.remove("client_key.pem")
fd = os.open("client_key.pem", os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "wb") as f:
f.write(key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8,
serialization.NoEncryption()))
csr = (x509.CertificateSigningRequestBuilder()
.subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "new client")]))
.sign(key, hashes.SHA256()))
# 2. Create the client. A retry sends the same headers and the same body.
headers = {"Authorization": f"Knovas-Org-Key {os.environ['KNOVAS_ORG_KEY']}",
"Idempotency-Key": str(uuid.uuid4())} # keep it until you have the answer
body = {"first_name": "Jane", "last_name": "Doe", "email": "jane.doe@example.ch",
"csr_pem": csr.public_bytes(serialization.Encoding.PEM).decode()}
r = None
for attempt in range(4):
try:
r = requests.post("https://api.knovas.ch/onboard_client", headers=headers, json=body, timeout=60)
except (requests.ConnectionError, requests.Timeout):
r = None # no answer: retry, same key and body
if r is not None and r.status_code != 503:
break
time.sleep(2 ** attempt)
if r is None:
raise SystemExit("No answer. Retry later with the same Idempotency-Key and body.")
r.raise_for_status() # 201 created, or 200 for a repeated request
answer = r.json()
with open("client_cert.pem", "w") as f:
f.write(answer["certificate_pem"])
with open("ca_root_cert.pem", "w") as f:
f.write(answer["ca_root_cert"])
print("client", answer["client_id"], "- clients left:", answer.get("clients_left"))The client can use its new certificate on port 8443 straight away (see Test the connection). Keep client_id: renewing needs it. Without csr_pem the answer also has private_key: save it at once, readable by you only, because Knovas never sends it again.
Answers
An error answer looks like {"status": "error", "error": "…", "error_code": "…"}; a 400 also names the field where it can.
| Answer | What it means | What to do |
|---|---|---|
201 | Client created. The answer has client_id, certificate_serial, certificate_pem, ca_root_cert, clients_left and, without csr_pem, private_key. | Save the files. |
200 | You sent the same request again (same Idempotency-Key, same body): the first answer, without a private key. | Use it like the first answer. |
400 VALIDATION_ERROR, UNKNOWN_FIELD, FORBIDDEN_FIELD, INVALID_EMAIL, INVALID_CSR | A field, the Idempotency-Key or the signing request is not valid. | Fix the request. |
401 UNAUTHENTICATED | The key is missing, wrong, revoked or expired, or your organisation is not active. The answer is the same for every cause. | Check the key. If it is right, ask Knovas. |
403 ONBOARDING_KEY_EXHAUSTED | The key has created all the clients it may. | Ask Knovas to raise the limit or issue a new key. |
403 ONBOARDING_KEY_NO_CONTACT | Nobody can receive the notices for this key. | Ask Knovas to name a contact. |
404 FEATURE_DISABLED | Creating clients is not switched on where you sent the request. | Ask Knovas. |
409 DUPLICATE_CLIENT | A client of your organisation already has this email address. The answer does not say which. | For a second client, send "allow_duplicate": true. |
422 IDEMPOTENCY_KEY_REUSED | This Idempotency-Key was used before for a different request. | Use a new Idempotency-Key for a new client. |
429 ONBOARDING_RATE_LIMITED | The key had 20 events in the last hour. When the hour is full, a duplicate gets this answer too, not 409. | Wait and retry. |
429 without a code | More than 30 requests a minute from your address (short bursts of 10). | Wait and retry. |
500 CLIENT_CREATION_FAILED | Nothing was saved. | Retry. |
503 CLIENT_CREATION_UNCERTAIN | Knovas does not know whether the client was saved. | Retry with the same Idempotency-Key and body: you get the stored answer (200) or a new attempt (201). |
503 SERVICE_UNAVAILABLE | The certificate service is not available. | Retry later. |
Retrying safely
Send a new Idempotency-Key for every new client: 1 to 100 characters from A–Z a–z 0–9 . _ : - (a UUID fits). Keep it, with the exact body, until you have the answer. A retry with the same key and the same body gets the stored answer (200). It creates no second client, does not count against the key’s limits, and works even if the key has reached a limit meanwhile, as long as the key itself is still valid. Change anything in the body (a new signing request, another field order) and it is a different request (422).
| If the answer is lost | What it costs |
|---|---|
With csr_pem and an Idempotency-Key | Nothing. Retry and you get the certificate. |
Without csr_pem | The private key was only in the lost answer and is never sent again, so that client cannot be used. Create a new one with "allow_duplicate": true and a new Idempotency-Key, and tell Knovas the unusable client’s id. |
Without an Idempotency-Key | A retry is a new request. If the first one was saved, the retry gets 409 DUPLICATE_CLIENT. |
So: send a signing request, and always send an Idempotency-Key.
Renewing a client’s certificate
A client whose certificate still works renews it itself on port 8443 (see Create your own key); no organisation key is needed. Use the organisation key when your organisation renews centrally, or when the certificate has already expired: port 8443 refuses an expired certificate, but it can still prove possession here.
Renewing needs both your organisation key and a proof of possession: a signature over a short message, made with the private key of the client’s current certificate. Either alone renews nothing. A client whose private key is lost, or whose certificate is an older one without OU=Semantix-Customer-<client id> (or was signed by an earlier Knovas client CA), cannot renew this way: ask Knovas to issue a new certificate.
| Call (port 443) | Who makes the new key | Body |
|---|---|---|
POST /sign_certificate | You (signing request) | customer_id, csr, validity_days, current_certificate, possession_proof |
POST /generate_certificate | Knovas | certificate_data (only customer_id and validity_days), current_certificate, possession_proof |
customer_id: the client’sclient_id, written with hyphens (8-4-4-4-12).validity_days: 1 to 365 (default 365).current_certificate: the client’s current certificate, exactly one PEM block (no chain, no key). It may be expired. Its key must be RSA with at least 2048 bits, or EC on P-256 or P-384.possession_proof:{"signed_at": …, "signature": …}, built as below. Build a new one for every attempt.
The proof of possession
The message has five lines of text, each ending in a line break (\n), the last one too:
knovas-renew-v1
<client id, lower-case, with hyphens>
<serial number of current_certificate, in decimal>
<SHA-256 of the new public key, or server-generated>
<signed_at: the time now, in Unix seconds>- Line 4 for
/sign_certificate: the SHA-256 of the new public key’sSubjectPublicKeyInfo(DER), as 64 lower-case hex digits. For/generate_certificate: the wordserver-generated. signed_atmust be within 5 minutes of Knovas’ clock. In the body it is a number, not a string.- Sign the message with the client’s current private key. An RSA key signs with RSASSA-PSS (SHA-256, MGF1 with SHA-256, salt length 32); keys Knovas made are RSA. An EC key signs with ECDSA and SHA-256.
signatureis the signature in standard base64 (with=padding) on one line, with no spaces or line breaks.
The example renews with a signing request. It reads the client’s current files client_cert.pem and client_key.pem and writes new_client_key.pem and new_client_cert.pem:
umask 077
CLIENT_ID=$(printf '%s' "<client id>" | tr 'A-F' 'a-f') # client_id from /onboard_client
# 1. A new key and a signing request. Keep the new key even if the call fails.
rm -f new_client_key.pem
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
-keyout new_client_key.pem -out new_client.csr -subj "/CN=renewal"
# 2. The message: five lines, each ending in \n
SERIAL=$(openssl x509 -in client_cert.pem -noout -serial | cut -d= -f2)
SERIAL=$(echo "ibase=16; $SERIAL" | BC_LINE_LENGTH=0 bc) # hex to decimal
NEW_KEY=$(openssl req -in new_client.csr -noout -pubkey | openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -r | cut -d' ' -f1)
SIGNED_AT=$(date +%s)
printf 'knovas-renew-v1\n%s\n%s\n%s\n%s\n' "$CLIENT_ID" "$SERIAL" "$NEW_KEY" "$SIGNED_AT" > renewal.txt
# 3. Sign it with the CURRENT private key. An RSA key (all keys made by Knovas):
SIGNATURE=$(openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 \
-sigopt rsa_mgf1_md:sha256 -sign client_key.pem renewal.txt | openssl base64 -A)
# An EC key instead:
# SIGNATURE=$(openssl dgst -sha256 -sign client_key.pem renewal.txt | openssl base64 -A)
# 4. Renew. On an error the answer has an error_code instead (see below).
RESPONSE=$(curl -s -X POST https://api.knovas.ch/sign_certificate \
-H "Authorization: Knovas-Org-Key $KNOVAS_ORG_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg id "$CLIENT_ID" --rawfile csr new_client.csr --rawfile cert client_cert.pem \
--argjson at "$SIGNED_AT" --arg sig "$SIGNATURE" \
'{customer_id: $id, csr: $csr, validity_days: 365, current_certificate: $cert,
possession_proof: {signed_at: $at, signature: $sig}}')")
echo "$RESPONSE" | jq -r '.certificate' > new_client_cert.pem# pip install cryptography requests
import base64
import hashlib
import os
import time
import requests
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
from cryptography.x509.oid import NameOID
client_id = "<client id>".lower() # client_id from /onboard_client
with open("client_cert.pem", "rb") as f:
current_cert = x509.load_pem_x509_certificate(f.read())
with open("client_key.pem", "rb") as f:
current_key = serialization.load_pem_private_key(f.read(), password=None)
# 1. A new key and a signing request. Save the key first and keep it even if the call fails.
new_key = ec.generate_private_key(ec.SECP256R1())
if os.path.lexists("new_client_key.pem"): # permissions only apply to new files
os.remove("new_client_key.pem")
fd = os.open("new_client_key.pem", os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "wb") as f:
f.write(new_key.private_bytes(serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8,
serialization.NoEncryption()))
csr = (x509.CertificateSigningRequestBuilder()
.subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "renewal")]))
.sign(new_key, hashes.SHA256()))
# 2. The message: five lines, each ending in \n
spki = new_key.public_key().public_bytes(serialization.Encoding.DER,
serialization.PublicFormat.SubjectPublicKeyInfo)
signed_at = int(time.time())
lines = ["knovas-renew-v1", client_id, str(current_cert.serial_number),
hashlib.sha256(spki).hexdigest(), str(signed_at)]
message = "".join(line + "\n" for line in lines).encode("utf-8")
# 3. Sign it with the CURRENT private key
if isinstance(current_key, rsa.RSAPrivateKey):
signature = current_key.sign(
message, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=32), hashes.SHA256())
else:
signature = current_key.sign(message, ec.ECDSA(hashes.SHA256()))
# 4. Renew
r = requests.post("https://api.knovas.ch/sign_certificate", timeout=60,
headers={"Authorization": f"Knovas-Org-Key {os.environ['KNOVAS_ORG_KEY']}"},
json={"customer_id": client_id,
"csr": csr.public_bytes(serialization.Encoding.PEM).decode(),
"validity_days": 365,
"current_certificate": current_cert.public_bytes(serialization.Encoding.PEM).decode(),
"possession_proof": {"signed_at": signed_at,
"signature": base64.b64encode(signature).decode()}})
r.raise_for_status()
with open("new_client_cert.pem", "w") as f:
f.write(r.json()["certificate"])For /generate_certificate, line 4 is server-generated, the body is {"certificate_data": {"customer_id": …, "validity_days": …}, "current_certificate": …, "possession_proof": …}, and the answer carries the new private_key (encrypted) with its private_key_password. A complete program with error handling is in the Organisation Onboarding reference of the Knovas Developer Kit; your Knovas contact sends it with the key.
After a renewal
- The old certificate keeps working for 48 hours, then Knovas refuses it. Switch the client to the new certificate and key within those 48 hours.
- The old certificate cannot prove a second renewal. Next time, prove with the new one.
- Every renewal sends a notice to your contact and counts against the key’s 20 events an hour. It does not use up a client from the key’s limit.
- If the answer is lost, the proof is used up and the new certificate is not sent again. If the old certificate had not expired, it still works for 48 hours: use it to renew on port 8443. If it had expired, contact Knovas.
| Answer | What it means |
|---|---|
200 | Renewed. /sign_certificate: certificate, certificate_chain, serial_number, expires_at. /generate_certificate: certificate_pem, private_key, private_key_password, ca_root_cert. |
400 VALIDATION_ERROR, UNKNOWN_FIELD, INVALID_CSR | The body is not valid, for example validity_days outside 1–365. |
401 UNAUTHENTICATED | The organisation key failed. |
403 POSSESSION_PROOF_INVALID | The proof does not hold: wrong signature or message, signed_at more than 5 minutes off, a certificate Knovas did not issue to this client, a revoked one, or one that already proved a renewal. The answer does not say which. |
403 ONBOARDING_KEY_NO_CONTACT | Nobody can receive the notices for this key. Ask Knovas. |
404 NOT_FOUND | The client does not exist or is not in your organisation (the same answer for both). |
409 POSSESSION_PROOF_REUSED | This exact proof was used already. Build a new one. |
429 | ONBOARDING_RATE_LIMITED: 20 events in the last hour for this key. Without a code: more than 30 requests a minute from your address. |
500 RENEWAL_FAILED | Nothing was saved. Retry with a new proof. |
503 RENEWAL_UNCERTAIN | Knovas does not know whether the renewal was saved. See below. |
503 SERVICE_UNAVAILABLE | Nothing was issued. Retry later with a new proof. |
After 503 RENEWAL_UNCERTAIN, a gateway error (502, 504) or any answer your software does not know:
- Keep the new private key and the current certificate and key. A certificate may already exist for the new key.
- Do not retry with a new proof. If the renewal was saved, the current certificate has already proved once, and every further try is refused.
- If the current certificate has not expired, renew on port 8443 (see Create your own key).
- If it has expired, contact Knovas with the client id and the time of the request.
Questions? Write to contact@knovas.ch.
This page describes Knovas 1.6.0. Last updated .