PKCS#7 → JOSE Migration (RSA-2048 → ML-KEM-768)
Convert a legacy PKCS#7 / CMS payload to a modern JOSE (JWE / JWS) token, then re-encrypt into PQC. Legacy container to modern format, in two calls.
🔀 PKCS#7 → JOSE Migration (RSA-2048 → ML-KEM-768)
Scenario: you have legacy PKCS#7 / CMS payloads — signed or encrypted with RSA — and need to migrate them to modern JOSE (JWE / JWS) format so future tooling can re-encrypt them into PQC. The /api/v3/migration/convert-pkcs7-to-jose endpoint does the format conversion; then /api/v3/crypto/reencrypt (or resign) takes over for the algorithm migration.
Format conversion vs algorithm conversion — this is two steps, not one:
- Step A (format): PKCS#7 container → JOSE container. Classical algorithms preserved or upgraded (CBC → GCM).
- Step B (algorithm): JOSE reencrypt / resign under a PQC target
kid.
Prerequisites
Same two identities as the AES-256 recipe. If the PKCS#7 was encrypted, the recipient's private key must already be imported into your tenant (see the PKCS#12 Import recipe).
Step-by-step
export BASE_URL="https://staging.ankatech.co"
export TENANT_ID="00000000-0000-0000-0000-000000000003"
export RSA_KID="legacy-rsa-imported" # already imported (see PKCS12 recipe)
export PQC_KID="mlkem768-modern" # PQC target for reencrypt
export P7_PATH="/path/to/legacy.p7"
# ADMIN_TOKEN + APP_TOKEN as in AES-256
# Step 2: analyze the PKCS#7 first (read-only preview)
curl -s -X POST "$BASE_URL/api/v3/migration/analyze-pkcs7" \
-H "Authorization: Bearer $APP_TOKEN" \
-F "file=@$P7_PATH" \
| jq '{type, signers, recipients, canConvert}'
# → { type: "SignedAndEnvelopedData",
# signers: [{ kid: "legacy-rsa-imported", algorithm: "RS256" }],
# recipients: [{ kid: "legacy-rsa-imported", algorithm: "RSA-OAEP" }],
# canConvert: true }
# Step 3: convert PKCS#7 → JOSE (JWE containing the payload, plus outer JWS if it was signed)
curl -s -X POST "$BASE_URL/api/v3/migration/convert-pkcs7-to-jose" \
-H "Authorization: Bearer $APP_TOKEN" \
-F "file=@$P7_PATH" \
| tee /tmp/jose.json | jq '{jweToken, jwsToken}'
# → { jweToken: {...}, jwsToken: "eyJ..." }
# The payload is now in modern JOSE form under the ORIGINAL RSA kids.
# Step 4: re-encrypt the JWE into ML-KEM-768 (assumes PQC key exists — see PKCS12 recipe)
curl -s -X POST "$BASE_URL/api/v3/crypto/reencrypt" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d "{ \"jweToken\": $(jq -c .jweToken /tmp/jose.json), \"newKid\":\"$PQC_KID\" }" \
| jq '{keyRequested, algorithmUsed}'
# → algorithmUsed: "ML-KEM-768+A256GCM" keyRequested: "mlkem768-modern"
# Step 5 (optional): re-sign the JWS with a PQC signature (ML-DSA-65)
curl -s -X POST "$BASE_URL/api/v3/crypto/resign" \
-H "Authorization: Bearer $APP_TOKEN" -H "Content-Type: application/json" \
-d "{ \"jwsToken\": \"$(jq -r .jwsToken /tmp/jose.json)\", \"newKid\":\"pqc-signer-kid\" }" \
| jqimport requests, json
BASE = "https://staging.ankatech.co"
TENANT_ID = "00000000-0000-0000-0000-000000000003"
RSA_KID, PQC_KID = "legacy-rsa-imported", "mlkem768-modern"
# admin / app tokens; PKCS12 already imported per that recipe
# Analyze first (preview)
with open("/path/to/legacy.p7", "rb") as f:
p7 = f.read()
analysis = requests.post(f"{BASE}/api/v3/migration/analyze-pkcs7",
headers=app,
files={"file": ("legacy.p7", p7)}).json()
print(f"PKCS#7 type: {analysis['type']}, canConvert={analysis['canConvert']}")
# Convert to JOSE
jose = requests.post(f"{BASE}/api/v3/migration/convert-pkcs7-to-jose",
headers=app,
files={"file": ("legacy.p7", p7)}).json()
# Re-encrypt JWE into PQC
if "jweToken" in jose:
re = requests.post(f"{BASE}/api/v3/crypto/reencrypt", headers=app, json={
"jweToken": jose["jweToken"],
"newKid": PQC_KID,
}).json()
print(f"Re-encrypted to {re['algorithmUsed']}")
# Optionally re-sign
if "jwsToken" in jose:
rs = requests.post(f"{BASE}/api/v3/crypto/resign", headers=app, json={
"jwsToken": jose["jwsToken"],
"newKid": "pqc-signer-kid",
}).json()
print(f"Re-signed with {rs['algorithmUsed']}")import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
import com.fasterxml.jackson.databind.*;
public class Pkcs7ToJoseMigration {
static final String BASE_URL = "https://staging.ankatech.co";
static final String TENANT_ID = "00000000-0000-0000-0000-000000000003";
static final String PQC_KID = "mlkem768-modern";
static final String P7_PATH = "/path/to/legacy.p7";
static final HttpClient http = HttpClient.newHttpClient();
static final ObjectMapper json = new ObjectMapper();
static byte[] multipartFile(String boundary, byte[] fileBytes) throws Exception {
var out = new java.io.ByteArrayOutputStream();
out.write(("--" + boundary + "\r\nContent-Disposition: form-data; name=\"file\";"
+ " filename=\"legacy.p7\"\r\nContent-Type: application/octet-stream\r\n\r\n").getBytes());
out.write(fileBytes);
out.write(("\r\n--" + boundary + "--\r\n").getBytes());
return out.toByteArray();
}
public static void main(String[] args) throws Exception {
String appAuth = "Bearer " + System.getenv("APP_TOKEN");
String boundary = "----" + System.currentTimeMillis();
byte[] p7 = Files.readAllBytes(Path.of(P7_PATH));
// 1. Analyze (read-only preview)
var analyzeResp = http.send(HttpRequest.newBuilder(URI.create(
BASE_URL + "/api/v3/migration/analyze-pkcs7"))
.header("Authorization", appAuth)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(multipartFile(boundary, p7))).build(),
HttpResponse.BodyHandlers.ofString());
JsonNode analysis = json.readTree(analyzeResp.body());
System.out.println("PKCS#7 type: " + analysis.get("type").asText()
+ ", canConvert=" + analysis.get("canConvert").asBoolean());
// 2. Convert to JOSE
var convertResp = http.send(HttpRequest.newBuilder(URI.create(
BASE_URL + "/api/v3/migration/convert-pkcs7-to-jose"))
.header("Authorization", appAuth)
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(multipartFile(boundary, p7))).build(),
HttpResponse.BodyHandlers.ofString());
JsonNode jose = json.readTree(convertResp.body());
// 3. Re-encrypt JWE into PQC
if (jose.has("jweToken")) {
var reResp = http.send(HttpRequest.newBuilder(URI.create(BASE_URL + "/api/v3/crypto/reencrypt"))
.header("Authorization", appAuth).header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(String.format(
"{ \"jweToken\": %s, \"newKid\": \"%s\" }", jose.get("jweToken"), PQC_KID))).build(),
HttpResponse.BodyHandlers.ofString());
System.out.println("Re-encrypted to "
+ json.readTree(reResp.body()).get("algorithmUsed").asText());
}
}
}const BASE_URL = "https://staging.ankatech.co";
const TENANT_ID = "00000000-0000-0000-0000-000000000003";
const RSA_KID = "legacy-rsa-imported", PQC_KID = "mlkem768-modern";
import { readFileSync } from "node:fs";
import { FormData, Blob } from "node:buffer";
const p7 = readFileSync("/path/to/legacy.p7");
// Analyze preview
const analyzeFd = new FormData();
analyzeFd.append("file", new Blob([p7]), "legacy.p7");
const analysis = await (await fetch(`${BASE_URL}/api/v3/migration/analyze-pkcs7`, {
method: "POST", headers: app, body: analyzeFd,
})).json();
console.log(`PKCS#7 type: ${analysis.type}, canConvert=${analysis.canConvert}`);
// Convert to JOSE
const convertFd = new FormData();
convertFd.append("file", new Blob([p7]), "legacy.p7");
const jose = await (await fetch(`${BASE_URL}/api/v3/migration/convert-pkcs7-to-jose`, {
method: "POST", headers: app, body: convertFd,
})).json();
// Re-encrypt into PQC
if (jose.jweToken) {
const re = await (await fetch(`${BASE_URL}/api/v3/crypto/reencrypt`, {
method: "POST", headers: jsonH(app),
body: JSON.stringify({ jweToken: jose.jweToken, newKid: PQC_KID }),
})).json();
console.log(`Re-encrypted to ${re.algorithmUsed}`);
}using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Pkcs7ToJoseMigration
{
const string BASE_URL = "https://staging.ankatech.co";
const string TENANT_ID = "00000000-0000-0000-0000-000000000003";
const string PQC_KID = "mlkem768-modern";
const string P7_PATH = "/path/to/legacy.p7";
static async Task Main()
{
var appHttp = new HttpClient();
appHttp.DefaultRequestHeaders.Add("Authorization",
$"Bearer {Environment.GetEnvironmentVariable("APP_TOKEN")}");
var p7 = File.ReadAllBytes(P7_PATH);
MultipartFormDataContent NewForm() {
var form = new MultipartFormDataContent();
var filePart = new ByteArrayContent(p7);
filePart.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
form.Add(filePart, "file", "legacy.p7");
return form;
}
// 1. Analyze
var analyzeResp = await appHttp.PostAsync(
$"{BASE_URL}/api/v3/migration/analyze-pkcs7", NewForm());
var analysis = JsonDocument.Parse(await analyzeResp.Content.ReadAsStringAsync()).RootElement;
Console.WriteLine($"PKCS#7 type: {analysis.GetProperty("type").GetString()}"
+ $", canConvert={analysis.GetProperty("canConvert").GetBoolean()}");
// 2. Convert to JOSE
var convertResp = await appHttp.PostAsync(
$"{BASE_URL}/api/v3/migration/convert-pkcs7-to-jose", NewForm());
var jose = JsonDocument.Parse(await convertResp.Content.ReadAsStringAsync()).RootElement;
// 3. Re-encrypt JWE into PQC
if (jose.TryGetProperty("jweToken", out var jweToken)) {
var reResp = await appHttp.PostAsync(
$"{BASE_URL}/api/v3/crypto/reencrypt",
new StringContent($"{{ \"jweToken\": {jweToken.GetRawText()},"
+ $" \"newKid\": \"{PQC_KID}\" }}",
Encoding.UTF8, "application/json"));
var re = JsonDocument.Parse(await reResp.Content.ReadAsStringAsync()).RootElement;
Console.WriteLine($"Re-encrypted to {re.GetProperty("algorithmUsed").GetString()}");
}
}
}Supported PKCS#7 → JOSE conversions
| Input PKCS#7 type | Output JOSE |
|---|---|
SignedData (1 signer) | JWS (compact or general JSON) |
EnvelopedData (1 recip.) | JWE (compact or general JSON) |
SignedAndEnvelopedData | Nested JWE(JWS) — outer JWE + inner JWS |
Multi-signer / multi-recipient PKCS#7 will be supported in a future release — currently returns 409 Conflict with a clear unsupported_configuration code. Use analyzePkcs7 first if you're not sure.
Where to go next
- PKCS#12 Import & RSA-2048 → ML-KEM-768 Migration — for keystores rather than payloads.
POST /migration/convert-pkcs7-to-jose— endpoint reference.- P-521 → ML-KEM-768 Re-Encrypt — the reencrypt step in more detail.
Updated 10 days ago