Files
baresip-studio/SERVER.md

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.