133 lines
3.9 KiB
Markdown
133 lines
3.9 KiB
Markdown
# Provisioning server
|
|
|
|
Baresip Studio can provision accounts from a server-encrypted bundle using a
|
|
per-device RSA key pair generated in Android Keystore.
|
|
|
|
Endpoint contract
|
|
- Base URL: whatever you pass as `endpoint=` in the `baresip:` URI
|
|
- Path used by the app: `<endpoint>/bundle`
|
|
- Method: POST
|
|
- Request Content-Type: application/json
|
|
- Response Content-Type: application/json
|
|
|
|
Enrollment request body
|
|
{
|
|
"extension": "101",
|
|
"public_key": "<base64 DER SubjectPublicKeyInfo>"
|
|
}
|
|
|
|
Provisioning bundle response body
|
|
{
|
|
"encrypted_key": "<base64 RSA-OAEP ciphertext>",
|
|
"iv": "<base64 12-byte GCM nonce>",
|
|
"ciphertext": "<base64 AES-GCM ciphertext>",
|
|
"tag": "<base64 16-byte GCM tag>"
|
|
}
|
|
|
|
The plaintext AES-GCM payload after decryption should be a JSON object like:
|
|
{
|
|
"connect_string": "sip:101@pbx.example.com",
|
|
"transport": "tls",
|
|
"sip_verify_server": "yes",
|
|
"username": "101",
|
|
"password": "secret",
|
|
"display_name": "Extension 101",
|
|
"account_name": "Desk Phone",
|
|
"outbound1": "sip:pbx.example.com",
|
|
"outbound2": "",
|
|
"register": true,
|
|
"reg_int": 900,
|
|
"check_origin": true,
|
|
"media_enc": "",
|
|
"media_nat": "",
|
|
"stun_server": "",
|
|
"stun_user": "",
|
|
"stun_pass": "",
|
|
"rtcp_mux": false,
|
|
"rel100": false,
|
|
"dtmf_mode": 2,
|
|
"answer_mode": 0,
|
|
"auto_redirect": false,
|
|
"vm_uri": "",
|
|
"country_code": "",
|
|
"tel_provider": "",
|
|
"numeric_keypad": false,
|
|
"default_account": true,
|
|
"custom_params": "",
|
|
"client_cert": "<PEM>",
|
|
"client_key": "<PEM>",
|
|
"ca_certs": "<PEM>"
|
|
}
|
|
|
|
Python server example
|
|
- Requirements: python >= 3.10
|
|
- pip install fastapi uvicorn pydantic
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
from pydantic import BaseModel
|
|
from cryptography.hazmat.primitives.asymmetric import padding
|
|
from cryptography.hazmat.primitives import hashes, serialization
|
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
import base64
|
|
import json
|
|
|
|
app = FastAPI()
|
|
|
|
class EnrollRequest(BaseModel):
|
|
extension: str
|
|
public_key: str
|
|
|
|
class BundleResponse(BaseModel):
|
|
encrypted_key: str
|
|
iv: str
|
|
ciphertext: str
|
|
tag: str
|
|
|
|
SERVER_RSA_PRIVATE_KEY_PEM = """-----BEGIN RSA PRIVATE KEY-----
|
|
...
|
|
-----END RSA PRIVATE KEY-----"""
|
|
|
|
def server_private_key():
|
|
return serialization.load_pem_private_key(
|
|
SERVER_RSA_PRIVATE_KEY_PEM.encode(), password=None
|
|
)
|
|
|
|
@app.post("/bundle", response_model=BundleResponse)
|
|
def bundle(req: EnrollRequest):
|
|
pub = serialization.load_der_public_key(base64.b64decode(req.public_key))
|
|
aes_key = AESGCM.generate_key(bit_length=256)
|
|
aesgcm = AESGCM(aes_key)
|
|
iv = b"012345678901" # replace with os.urandom(12) in production
|
|
payload = {
|
|
"connect_string": f"sip:{req.extension}@pbx.example.com",
|
|
"transport": "tls",
|
|
"sip_verify_server": "yes",
|
|
"username": req.extension,
|
|
"password": "secret",
|
|
"client_cert": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
|
"client_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
|
|
"ca_certs": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
|
}
|
|
plaintext = json.dumps(payload).encode("utf-8")
|
|
encrypted = aesgcm.encrypt(iv, plaintext, None)
|
|
encrypted_key = server_private_key().encrypt(
|
|
aes_key,
|
|
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None),
|
|
)
|
|
ciphertext = encrypted[:-16]
|
|
tag = encrypted[-16:]
|
|
return BundleResponse(
|
|
encrypted_key=base64.b64encode(encrypted_key).decode(),
|
|
iv=base64.b64encode(iv).decode(),
|
|
ciphertext=base64.b64encode(ciphertext).decode(),
|
|
tag=base64.b64encode(tag).decode(),
|
|
)
|
|
|
|
Example baresip URI
|
|
baresip://provision?endpoint=https://pbx.example.com&extension=101
|
|
|
|
Security notes
|
|
- Always serve over HTTPS.
|
|
- Keep SERVER_RSA_PRIVATE_KEY_PEM secret and offline from the app.
|
|
- Treat the bundle URL as a secret; short expiry and single-use are recommended.
|