commaai/openpilot · error · RuntimeError

Invalid smdpSigned2

Error message

Invalid smdpSigned2

What it means

prepare_download() decodes the SM-DP+ server's base64 smdpSigned2 blob and parses it as DER/BER-TLV looking for a root tag 0x30 (ASN.1 SEQUENCE). If no 0x30 element is found, the server response is structurally invalid from the client's perspective and RuntimeError('Invalid smdpSigned2') is raised.

Source

Thrown at openpilot/common/esim/lpa.py:499

  device_info = encode_tlv(TAG_STATUS, tac) + encode_tlv(0xA1, b"")
  ctx_inner = encode_tlv(TAG_STATUS, matching_id.encode("utf-8")) + encode_tlv(0xA1, device_info)
  content = b64d(b64_signed1) + b64d(b64_sig1) + b64d(b64_pk_id) + b64d(b64_cert) + encode_tlv(0xA0, ctx_inner)
  response = es10x_command(client, encode_tlv(TAG_AUTH_SERVER, content))
  root = require_tag(response, TAG_AUTH_SERVER, "AuthenticateServerResponse")
  error_tag = find_tag(root, 0xA1)
  if error_tag is not None:
    code = int.from_bytes(error_tag, "big") if error_tag else 0
    raise RuntimeError(f"AuthenticateServer rejected by eUICC: {AUTH_SERVER_ERROR_CODES.get(code, 'unknown')} (0x{code:02X})")
  return b64e(response)


def prepare_download(client: AtClient, b64_signed2: str, b64_sig2: str, b64_cert: str, cc: str | None = None) -> str:
  smdp_signed2 = b64d(b64_signed2)
  smdp_signature2 = b64d(b64_sig2)
  smdp_certificate = b64d(b64_cert)
  smdp_signed2_root = find_tag(smdp_signed2, 0x30)
  if smdp_signed2_root is None:
    raise RuntimeError("Invalid smdpSigned2")
  transaction_id = find_tag(smdp_signed2_root, TAG_STATUS)
  cc_required_flag = find_tag(smdp_signed2_root, 0x01)
  if transaction_id is None or cc_required_flag is None:
    raise RuntimeError("Invalid smdpSigned2")
  content = smdp_signed2 + smdp_signature2
  if int.from_bytes(cc_required_flag, "big") != 0:
    if not cc:
      raise RuntimeError("Confirmation code required but not provided")
    content += encode_tlv(0x04, hashlib.sha256(hashlib.sha256(cc.encode("utf-8")).digest() + transaction_id).digest())
  content += smdp_certificate
  response = es10x_command(client, encode_tlv(TAG_PREPARE_DOWNLOAD, content))
  require_tag(response, TAG_PREPARE_DOWNLOAD, "PrepareDownloadResponse")
  return b64e(response)


def _parse_tlv_header_len(data: bytes) -> int:
  tag_len = 2 if data[0] & 0x1F == 0x1F else 1
  length_byte = data[tag_len]

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Verify the argument mapping: prepare_download(client, b64_signed2, b64_sig2, b64_cert, cc) must receive exactly the server's smdpSigned2, smdpSignature2, smdpCertificate fields
  2. Decode and inspect the blob offline (openssl asn1parse / base64 -d) to confirm it is a DER SEQUENCE
  3. If the server payload genuinely lacks structure, escalate to the carrier — their ES9+ response is malformed
Defensive patterns

Strategy: validation

Validate before calling

from openpilot.common.esim.lpa import find_tag

raw = base64.b64decode(b64_signed2)
if find_tag(raw, 0x30) is None:
  raise SystemExit('smdpSigned2 is not a valid DER SEQUENCE — wrong field or server bug')

Type guard

def is_valid_smdp_signed2(b64: str) -> bool:
  try:
    return find_tag(base64.b64decode(b64), 0x30) is not None
  except Exception:
    return False

Prevention

When it happens

Trigger: handleNotification/authenticateClient exchange returned a smdpSigned2 that is not valid DER (wrong field passed, base64 of a different value, truncated payload), or a server response format that deviates from SGP.22 expectations.

Common situations: Field mixups when wiring the ES9+ JSON into prepare_download (passing smdpSignature2 or the cert in the signed2 slot); carrier server bugs or SGP.22 version differences; base64 decoding of a corrupted string.

Related errors


AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15). Data as JSON: /api/errors/0254790177f5d5fd. Report an issue: GitHub.