Skip to content

SM-DP+

The SM-DP+ is the server half of a consumer profile download. It holds the profile inventory, turns an operator’s order into an activation code, proves its identity to an eUICC, and hands over a profile that only that eUICC can install.

It exposes two interfaces that could not differ more in who is allowed to call them, on one hostname, in front of one Lambda. That asymmetry is the whole architecture, so it is the first picture.

Two front doors into one SM-DP+ Lambda: ES9+ arrives unauthenticated through CloudFront, while ES2+ is refused at the edge with 403 and reaches the Lambda only through the console BFF behind API Gateway, the shared authorizer and a role check.

Source: diagrams/smdp-plus/architecture.drawio. Paths, the AWS_IAM Function URL and the guard behaviour are taken from AminSM-DPplus-lambda/template.yml.

Who may call what

InterfaceCallerAuthenticationEnforced by
ES9+ /gsma/rsp2/es9plus/*an LPA on a devicenone, deliberatelynothing — the edge passes it through
ES2+ /gsma/rsp2/es2plus/*the operator’s BSSrefused at the edgeEs2PlusGuard, a CloudFront Function
ES2+ via the consolea signed-in operatorOIDC token, then a role checkAPI Gateway → shared authorizer → console BFF

Two of those rows surprise people, so both are worth stating plainly.

ES9+ is unauthenticated on purpose. An LPA sitting in a handset has no credential to present to a server it has never met — mutual authentication happens inside the protocol, between the eUICC and the SM-DP+ certificates, not around it in a transport header. Requiring a token here would make the interface unusable by the only client it exists for.

ES2+ is refused at the edge rather than secured. In a real deployment ES2+ sits behind mutually authenticated TLS between operator and SM-DP+. This implementation has no such relationship to offer, so instead of pretending, the distribution rejects the path outright:

AminSM-DPplus-lambda/template.yml — Es2PlusGuard
function handler(event) {
var uri = event.request.uri.toLowerCase();
if (uri.indexOf('/gsma/rsp2/es2plus') === 0) {
return {
statusCode: 403,
statusDescription: 'Forbidden',
headers: {
'content-type': { value: 'application/json' },
'cache-control': { value: 'no-store' }
},
body: '{"error":"forbidden"}'
};
}
return event.request;
}

The guard exists because of a real failure, not a hypothetical one. The distribution originally forwarded every path to the function, which meant downloadOrder and confirmOrder were callable by anyone who knew the hostname — a stranger could create orders in the inventory and mint working activation codes for them. The template records it being verified live on 2026-08-25, and notes that this is why the inventory filled with ICCIDs nobody ordered.

The sanctioned route to ES2+ is now the console: a signed-in operator, a token checked by the shared authorizer, a role check, and then a direct Lambda invoke that never traverses the public hostname at all.

Interfaces

  • ES9+ — profile download — the three calls that move a profile from the inventory onto a card, with the exact request and response fields.

Where it runs

Hostnamesmdpplus.rsplab.click
RepositoryAminSM-DPplus-lambda (private)
Stackamin-smdpp-dev, us-east-1
Front doorCloudFront → Lambda Function URL, AuthType: AWS_IAM
Consolesmdpplus.rsplab.click/console (AminSmDpPlusAdmin)
SpecificationsGSMA SGP.22, interfaces ES2+, ES8+, ES9+