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\" }" \
  | jq
import 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 typeOutput JOSE
SignedData (1 signer)JWS (compact or general JSON)
EnvelopedData (1 recip.)JWE (compact or general JSON)
SignedAndEnvelopedDataNested 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


Did this page help you?