Files
baresip-studio/SERVER.md

3.9 KiB

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": "" }

Provisioning bundle response body { "encrypted_key": "", "iv": "<base64 12-byte GCM nonce>", "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": "", "client_key": "", "ca_certs": "" }

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.