> For the complete documentation index, see [llms.txt](https://help.security.gallagher.com/knowledge-hub/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.security.gallagher.com/knowledge-hub/developer/rest-api/card-encoding.md).

# Card encoding

These routes allow clients to get Gallagher card encoding data for MIFARE DESFire and MIFARE Classic cards.

* The following features are required in the licence file:
  * RESTCardholders
  * RESTEncoding
  * Encoding
  * Photo-ID
* The REST Client operator should have the following privileges:
  * View Cardholders
  * Print/Preview & Encode Card
  * Allow Re-Printing and Re-Encoding (optional, depending on whether you want to allow re-encoding of already-encoded cards)
* Bytes are encoded as hex strings in decrypted encoding data.
* Sensitive data is encrypted in responses.
* Using the client certificate for authentication is recommended.

#### Configuration End to End Encryption

1. Using openssl to generate ECDH\_P384 public/private key pair

```bash
openssl ecparam -name secp384r1 -genkey -noout -out private.pem

openssl ec -in private.pem -pubout -outform DER | tail -c 97 | head -c 97 | xxd -p -c 97

openssl req -new -x509 -key private.pem -out E2EEncryptionKey.crt -days 365 -subj "/CN=Command Center REST Client E2E Encryption Key"

openssl pkcs12 -export -out key.pfx -inkey private.pem -in E2EEncryptionKey.crt
```

2. Store the private key securely on the encoding client side, e.g., in the Windows Certificate Store.

```powershell
Import-PfxCertificate -FilePath "path\to\key.pfx" -CertStoreLocation Cert:\CurrentUser\My -Password (Read-Host "Enter PFX Password" -AsSecureString)
```

3. Open the REST Client settings in Configuration Client, put the X9.63 hex format of public key to the Remote Public Key input in E2E Key tab.

#### Code Example for Client-Side Decryption

```csharp
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;

var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true
};

// Load private key from Windows Certificate Store
// Open the Current User store
using X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);

// Find the certificate by Subject Name
X509Certificate2? cert = store.Certificates
    .Find(X509FindType.FindBySubjectDistinguishedName, "CN=Command Center REST Client E2E Encryption Key", false)
    .FirstOrDefault();

if (cert == null)
{
    Console.WriteLine("Error: Certificate not found.");
    return;
}

// 3. Get The Private Key
ECDiffieHellman? privateKey = cert.GetECDiffieHellmanPrivateKey();

if (privateKey == null)
{
    throw new Exception("Certificate found, but private key is not accessible or not ECDH.");
}

Console.WriteLine($"Successfully retrieved key. Curve: {privateKey.ExportParameters(false).Curve.Oid.FriendlyName}");

// Simulated API response (replace with actual API call and response content)
var apiResponseJson = @"{
    ""encryptedEncodingData"": {
        ""cipherText"": ""<base64-encoded cipher text>"",
        ""iv"": ""<base64-encoded IV>"",
        ""ephemeralPublicKey"": ""<base64-encoded ephemeral public key>"",
        ""tag"": ""<base64-encoded authentication tag>""
    },
    ""callback"": {
    ""encodeCompleted"": {
        ""href"": ""https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted?token=abc"" },
        ""encodeFailed"": {
            ""href"": ""https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeFailed?token=abc"" }
        }
    }";

var response = JsonSerializer.Deserialize<EncryptedEncodingDataResponse>(apiResponseJson, options);
if (response == null)
{
    throw new Exception("Cannot deserialize response.");
}

// Derive the shared secret
var encryptedData = response.EncryptedEncodingData;
using var ephemeralKey = ECDiffieHellman.Create();
ephemeralKey.ImportSubjectPublicKeyInfo(encryptedData.EphemeralPublicKey, out _);
byte[] sharedSecret = privateKey.DeriveKeyFromHash(ephemeralKey.PublicKey, HashAlgorithmName.SHA512);

byte[] aesKey = sharedSecret.Take(32).ToArray();
byte[] hmacKey = sharedSecret.Skip(32).Take(32).ToArray();

// Verify HMAC tag to ensure data integrity and authenticity before decryption
using var hmac = new HMACSHA256(hmacKey);
var contentToSign = encryptedData.CipherText
    .Concat(encryptedData.IV)
    .Concat(encryptedData.EphemeralPublicKey) // Include ephemeral key to bind it to this session
    .ToArray();

byte[] computedHash = hmac.ComputeHash(contentToSign);
if (!computedHash.SequenceEqual(encryptedData.Tag))
{
    throw new Exception("Error: HMAC tag verification failed. Data may use a different signing order or has been tampered with.");
}

// Decrypt the cipher text using AES-CBC with the shared secret as the key and the provided IV
using var aes = Aes.Create();
aes.Key = aesKey;
aes.IV = encryptedData.IV;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;

using var decryptor = aes.CreateDecryptor();
byte[] decryptedData = decryptor.TransformFinalBlock(encryptedData.CipherText, 0, encryptedData.CipherText.Length);

// Convert decryptedData to string
string decryptedJson = Encoding.UTF8.GetString(decryptedData);
Console.WriteLine("Decryption successful. Decrypted data:\n" + decryptedJson);
// Deserialize decrypted JSON to get the actual encoding data structure (e.g., DESFireEncodingResponse or ClassicEncodingResponse)
...

public record EncryptedEncodingData(byte[] CipherText, byte[] IV, byte[] EphemeralPublicKey, byte[] Tag);
public record Link(string Href);
public record CallbackLinks(Link EncodeCompleted, Link EncodeFailed);
public record EncryptedEncodingDataResponse(EncryptedEncodingData EncryptedEncodingData, CallbackLinks Callback);
```

## Get encoding payload for a DESFire card

> This returns the data you need to encode a Gallagher credential to a MIFARE DESFire card.\
> \
> POST the card's CSN (UID) and, if present on-card, the contents of the Gallagher Application\
> Directory (CAD) files.  The response contains the encrypted DESFire applications, keys,\
> application files, and CAD files to write, with callback links for you to report the success\
> of your encoding.\
> \
> \*\*Encode applications in the order they are returned\*\* so that the card remains usable if\
> encoding fails part-way through.<br>

````json
{"openapi":"3.2.0","info":{"title":"Command Centre REST API","version":"2.0"},"tags":[{"name":"Card encoding","description":"These routes allow clients to get Gallagher card encoding data for MIFARE DESFire and MIFARE Classic cards.\n- The following features are required in the licence file:\n  - RESTCardholders\n  - RESTEncoding\n  - Encoding\n  - Photo-ID\n- The REST Client operator should have the following privileges:\n  - View Cardholders\n  - Print/Preview & Encode Card\n  - Allow Re-Printing and Re-Encoding (optional, depending on whether you want to allow re-encoding of already-encoded cards)\n- Bytes are encoded as hex strings in decrypted encoding data.\n- Sensitive data is encrypted in responses.\n- Using the client certificate for authentication is recommended.\n\n### Configuration End to End Encryption\n1. Using openssl to generate ECDH_P384 public/private key pair\n  ```bash\n  openssl ecparam -name secp384r1 -genkey -noout -out private.pem\n\n  openssl ec -in private.pem -pubout -outform DER | tail -c 97 | head -c 97 | xxd -p -c 97\n\n  openssl req -new -x509 -key private.pem -out E2EEncryptionKey.crt -days 365 -subj \"/CN=Command Center REST Client E2E Encryption Key\"\n\n  openssl pkcs12 -export -out key.pfx -inkey private.pem -in E2EEncryptionKey.crt\n  ```\n2. Store the private key securely on the encoding client side, e.g., in the Windows Certificate Store.\n  ```powershell\n  Import-PfxCertificate -FilePath \"path\\to\\key.pfx\" -CertStoreLocation Cert:\\CurrentUser\\My -Password (Read-Host \"Enter PFX Password\" -AsSecureString)\n  ```\n3. Open the REST Client settings in Configuration Client, put the X9.63 hex format of public key to the Remote Public Key input in E2E Key tab.\n\n### Code Example for Client-Side Decryption\n```csharp\nusing System.Security.Cryptography;\nusing System.Security.Cryptography.X509Certificates;\nusing System.Text;\nusing System.Text.Json;\n\nvar options = new JsonSerializerOptions\n{\n    PropertyNameCaseInsensitive = true\n};\n\n// Load private key from Windows Certificate Store\n// Open the Current User store\nusing X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);\nstore.Open(OpenFlags.ReadOnly);\n\n// Find the certificate by Subject Name\nX509Certificate2? cert = store.Certificates\n    .Find(X509FindType.FindBySubjectDistinguishedName, \"CN=Command Center REST Client E2E Encryption Key\", false)\n    .FirstOrDefault();\n\nif (cert == null)\n{\n    Console.WriteLine(\"Error: Certificate not found.\");\n    return;\n}\n\n// 3. Get The Private Key\nECDiffieHellman? privateKey = cert.GetECDiffieHellmanPrivateKey();\n\nif (privateKey == null)\n{\n    throw new Exception(\"Certificate found, but private key is not accessible or not ECDH.\");\n}\n\nConsole.WriteLine($\"Successfully retrieved key. Curve: {privateKey.ExportParameters(false).Curve.Oid.FriendlyName}\");\n\n// Simulated API response (replace with actual API call and response content)\nvar apiResponseJson = @\"{\n    \"\"encryptedEncodingData\"\": {\n        \"\"cipherText\"\": \"\"<base64-encoded cipher text>\"\",\n        \"\"iv\"\": \"\"<base64-encoded IV>\"\",\n        \"\"ephemeralPublicKey\"\": \"\"<base64-encoded ephemeral public key>\"\",\n        \"\"tag\"\": \"\"<base64-encoded authentication tag>\"\"\n    },\n    \"\"callback\"\": {\n    \"\"encodeCompleted\"\": {\n        \"\"href\"\": \"\"https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted?token=abc\"\" },\n        \"\"encodeFailed\"\": {\n            \"\"href\"\": \"\"https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeFailed?token=abc\"\" }\n        }\n    }\";\n\nvar response = JsonSerializer.Deserialize<EncryptedEncodingDataResponse>(apiResponseJson, options);\nif (response == null)\n{\n    throw new Exception(\"Cannot deserialize response.\");\n}\n\n// Derive the shared secret\nvar encryptedData = response.EncryptedEncodingData;\nusing var ephemeralKey = ECDiffieHellman.Create();\nephemeralKey.ImportSubjectPublicKeyInfo(encryptedData.EphemeralPublicKey, out _);\nbyte[] sharedSecret = privateKey.DeriveKeyFromHash(ephemeralKey.PublicKey, HashAlgorithmName.SHA512);\n\nbyte[] aesKey = sharedSecret.Take(32).ToArray();\nbyte[] hmacKey = sharedSecret.Skip(32).Take(32).ToArray();\n\n// Verify HMAC tag to ensure data integrity and authenticity before decryption\nusing var hmac = new HMACSHA256(hmacKey);\nvar contentToSign = encryptedData.CipherText\n    .Concat(encryptedData.IV)\n    .Concat(encryptedData.EphemeralPublicKey) // Include ephemeral key to bind it to this session\n    .ToArray();\n\nbyte[] computedHash = hmac.ComputeHash(contentToSign);\nif (!computedHash.SequenceEqual(encryptedData.Tag))\n{\n    throw new Exception(\"Error: HMAC tag verification failed. Data may use a different signing order or has been tampered with.\");\n}\n\n// Decrypt the cipher text using AES-CBC with the shared secret as the key and the provided IV\nusing var aes = Aes.Create();\naes.Key = aesKey;\naes.IV = encryptedData.IV;\naes.Mode = CipherMode.CBC;\naes.Padding = PaddingMode.PKCS7;\n\nusing var decryptor = aes.CreateDecryptor();\nbyte[] decryptedData = decryptor.TransformFinalBlock(encryptedData.CipherText, 0, encryptedData.CipherText.Length);\n\n// Convert decryptedData to string\nstring decryptedJson = Encoding.UTF8.GetString(decryptedData);\nConsole.WriteLine(\"Decryption successful. Decrypted data:\\n\" + decryptedJson);\n// Deserialize decrypted JSON to get the actual encoding data structure (e.g., DESFireEncodingResponse or ClassicEncodingResponse)\n...\n\npublic record EncryptedEncodingData(byte[] CipherText, byte[] IV, byte[] EphemeralPublicKey, byte[] Tag);\npublic record Link(string Href);\npublic record CallbackLinks(Link EncodeCompleted, Link EncodeFailed);\npublic record EncryptedEncodingDataResponse(EncryptedEncodingData EncryptedEncodingData, CallbackLinks Callback);\n```\n"}],"servers":[{"url":"https://127.0.0.1:8904"}],"security":[{"API_key":[]},{"basic":[]}],"components":{"securitySchemes":{"API_key":{"type":"apiKey","name":"Authorization","in":"header","description":"\nClients authenticate by including a pre-shared API key in the `Authorization` header of each\nrequest after an authorisation method of `GGL-API-KEY`:\n\n`Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB`\n\nFor a fuller description see [API authentication](api-authentication).\n"},"basic":{"type":"http","scheme":"basic","description":"This is HTTP Basic authentication using a blank username and the API key as the password.\n\nExample:  `Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==`\n\nThat example is equivalent to the example in the description of the api_Key method:\nprepending a colon to the API key beginning with `C774` then encoding it into Base64\nproduces the 56 characters beginning with `OkM3`.\n\nThe colon is a separator between a username, which is blank in our case, and the password,\nwhich is our API key.\n\nFor a fuller description see [API authentication](api-authentication)\n"}},"parameters":{"id":{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"An internal identifier."},"secondary_id":{"name":"secondary_id","in":"path","required":true,"schema":{"type":"string"},"description":"An internal identifier."}},"schemas":{"DESFireEncodingRequest":{"type":"object","properties":{"csn":{"$ref":"#/components/schemas/HexString"},"cadFiles":{"type":"object","description":"Optional data read from CAD files, if present on the card.","additionalProperties":{"$ref":"#/components/schemas/HexString"}},"reason":{"type":"string","maxLength":100}},"required":["csn"],"description":"- `csn`: Card serial number (UID).\n- `cadFiles`: Gallagher Application Directory file contents as hex. Required if CAD files exist on card.\n- `reason`: Reason for encoding, e.g. \"New card issuance\", \"Lost card replacement\". Optional.\n"},"HexString":{"type":"string","description":"A string containing a hexadecimal encoding of a number.","pattern":"^0x([0-9A-Fa-f]{2})+$"},"EncryptedEncodingDataResponseOrDecryptedForInfo":{"title":"Encrypted response and its decrypted forms.","oneOf":[{"$ref":"#/components/schemas/EncryptedEncodingDataResponse"},{"$ref":"#/components/schemas/DecryptedDESFireDataForInfo"},{"$ref":"#/components/schemas/DecryptedClassicDataForInfo"}]},"EncryptedEncodingDataResponse":{"type":"object","title":"Response schema","properties":{"encryptedEncodingData":{"$ref":"#/components/schemas/EncryptedEncodingData"},"callback":{"$ref":"#/components/schemas/CallbackLinks"}},"required":["encryptedEncodingData","callback"]},"EncryptedEncodingData":{"type":"object","properties":{"cipherText":{"type":"string","description":"The encrypted encoding data, encoded as a base64 string."},"iv":{"type":"string","description":"The initialisation vector used for encryption, encoded as a base64 string."},"ephemeralPublicKey":{"type":"string","description":"The ephemeral public key used for encryption, encoded as a base64 string."},"tag":{"type":"string","description":"The authentication tag for the encrypted data, encoded as a base64 string."}},"required":["cipherText","iv","ephemeralPublicKey","tag"]},"CallbackLinks":{"type":"object","properties":{"encodeCompleted":{"$ref":"#/components/schemas/CallbackLink"},"encodeFailed":{"$ref":"#/components/schemas/CallbackLink"}},"required":["encodeCompleted","encodeFailed"]},"CallbackLink":{"type":"object","properties":{"href":{"type":"string","description":"Absolute or relative URL for callback invocation."}},"required":["href"]},"DecryptedDESFireDataForInfo":{"title":"Example DESFire card data after decryption","summary":"This is not actual API data.  It is for information only.","description":"This is not actual API data.  It is for information only.","type":"object","properties":{"cardMasterKey":{"$ref":"#/components/schemas/HexString"},"defaultAppKey":{"$ref":"#/components/schemas/HexString"},"applications":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":0},"name":{"type":"string"},"data":{"$ref":"#/components/schemas/HexString"},"version":{"type":"integer","minimum":1}}}},"keySettings1":{"type":"string"},"keySettings2":{"type":"string"},"files":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":0},"data":{"$ref":"#/components/schemas/HexString"},"accessRights":{"$ref":"#/components/schemas/HexString"},"communicationMode":{"$ref":"#/components/schemas/HexString"}}}}}}}}},"DecryptedClassicDataForInfo":{"title":"Example Classic card data after decryption","summary":"This is not actual API data.  It is for information only.","description":"This is not actual API data.  It is for information only.","type":"object","properties":{"sectors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":0},"name":{"type":"string"},"blocks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":0},"file":{"$ref":"#/components/schemas/HexString"}}}}}}}}}},"responses":{"404enc":{"description":"The cardholder does not exist or is not visible to the operator, or that card is not on the\ncardholder.\n"}}},"paths":{"/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_desfire":{"post":{"tags":["Card encoding"],"operationId":"getDesfireEncodingData","summary":"Get encoding payload for a DESFire card","description":"This returns the data you need to encode a Gallagher credential to a MIFARE DESFire card.\n\nPOST the card's CSN (UID) and, if present on-card, the contents of the Gallagher Application\nDirectory (CAD) files.  The response contains the encrypted DESFire applications, keys,\napplication files, and CAD files to write, with callback links for you to report the success\nof your encoding.\n\n**Encode applications in the order they are returned** so that the card remains usable if\nencoding fails part-way through.\n","parameters":[{"$ref":"#/components/parameters/id"},{"$ref":"#/components/parameters/secondary_id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DESFireEncodingRequest"}}}},"responses":{"200":{"description":"Returns encrypted DESFire encoding data and callback links.\n\nThe `encryptedEncodingData` field contains an encrypted payload that, when decrypted, has\nthe structure of [DESFireEncodingResponse](#schemas/DESFireEncodingResponse) with\napplications, keys, and files.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EncryptedEncodingDataResponseOrDecryptedForInfo"}}}},"400":{"description":"Possible reasons:\n- The request body is invalid.\n- There is no free application ID left on the card.\n- The card is not of an encodable type.  In API terms, its `credentialClass` needs to be `card`.\n- The reason for encoding is too long (over 100 characters).\n\nThe response body will contain an error message.\n"},"403":{"description":"Possible reasons:\n- Command Centre does not have a licence for the encoding API.\n- The operator lacks the necessary privilege.\n- Re-encoding is disabled for that card type and the card has already been encoded.\n"},"404":{"$ref":"#/components/responses/404enc"},"422":{"description":"Possible reasons:\n- Config Client is not configured with end to end encryption keys so it cannot encrypt the encoding data to return in the response.\n"}}}}}}
````

## Get encoding payload for a Classic card

> This returns the data you need to encode a Gallagher credential to a MIFARE Classic card.\
> \
> POST the card's CSN (UID) and card directory data (CAD/MAD) if present, plus any unavailable\
> sectors.  The response contains the sectors and blocks to write, and callback links for you\
> to report the success of your encoding.\
> \
> \*\*Encode applications in the order they are returned\*\* so that the card remains usable if\
> encoding fails part-way through.<br>

````json
{"openapi":"3.2.0","info":{"title":"Command Centre REST API","version":"2.0"},"tags":[{"name":"Card encoding","description":"These routes allow clients to get Gallagher card encoding data for MIFARE DESFire and MIFARE Classic cards.\n- The following features are required in the licence file:\n  - RESTCardholders\n  - RESTEncoding\n  - Encoding\n  - Photo-ID\n- The REST Client operator should have the following privileges:\n  - View Cardholders\n  - Print/Preview & Encode Card\n  - Allow Re-Printing and Re-Encoding (optional, depending on whether you want to allow re-encoding of already-encoded cards)\n- Bytes are encoded as hex strings in decrypted encoding data.\n- Sensitive data is encrypted in responses.\n- Using the client certificate for authentication is recommended.\n\n### Configuration End to End Encryption\n1. Using openssl to generate ECDH_P384 public/private key pair\n  ```bash\n  openssl ecparam -name secp384r1 -genkey -noout -out private.pem\n\n  openssl ec -in private.pem -pubout -outform DER | tail -c 97 | head -c 97 | xxd -p -c 97\n\n  openssl req -new -x509 -key private.pem -out E2EEncryptionKey.crt -days 365 -subj \"/CN=Command Center REST Client E2E Encryption Key\"\n\n  openssl pkcs12 -export -out key.pfx -inkey private.pem -in E2EEncryptionKey.crt\n  ```\n2. Store the private key securely on the encoding client side, e.g., in the Windows Certificate Store.\n  ```powershell\n  Import-PfxCertificate -FilePath \"path\\to\\key.pfx\" -CertStoreLocation Cert:\\CurrentUser\\My -Password (Read-Host \"Enter PFX Password\" -AsSecureString)\n  ```\n3. Open the REST Client settings in Configuration Client, put the X9.63 hex format of public key to the Remote Public Key input in E2E Key tab.\n\n### Code Example for Client-Side Decryption\n```csharp\nusing System.Security.Cryptography;\nusing System.Security.Cryptography.X509Certificates;\nusing System.Text;\nusing System.Text.Json;\n\nvar options = new JsonSerializerOptions\n{\n    PropertyNameCaseInsensitive = true\n};\n\n// Load private key from Windows Certificate Store\n// Open the Current User store\nusing X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);\nstore.Open(OpenFlags.ReadOnly);\n\n// Find the certificate by Subject Name\nX509Certificate2? cert = store.Certificates\n    .Find(X509FindType.FindBySubjectDistinguishedName, \"CN=Command Center REST Client E2E Encryption Key\", false)\n    .FirstOrDefault();\n\nif (cert == null)\n{\n    Console.WriteLine(\"Error: Certificate not found.\");\n    return;\n}\n\n// 3. Get The Private Key\nECDiffieHellman? privateKey = cert.GetECDiffieHellmanPrivateKey();\n\nif (privateKey == null)\n{\n    throw new Exception(\"Certificate found, but private key is not accessible or not ECDH.\");\n}\n\nConsole.WriteLine($\"Successfully retrieved key. Curve: {privateKey.ExportParameters(false).Curve.Oid.FriendlyName}\");\n\n// Simulated API response (replace with actual API call and response content)\nvar apiResponseJson = @\"{\n    \"\"encryptedEncodingData\"\": {\n        \"\"cipherText\"\": \"\"<base64-encoded cipher text>\"\",\n        \"\"iv\"\": \"\"<base64-encoded IV>\"\",\n        \"\"ephemeralPublicKey\"\": \"\"<base64-encoded ephemeral public key>\"\",\n        \"\"tag\"\": \"\"<base64-encoded authentication tag>\"\"\n    },\n    \"\"callback\"\": {\n    \"\"encodeCompleted\"\": {\n        \"\"href\"\": \"\"https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted?token=abc\"\" },\n        \"\"encodeFailed\"\": {\n            \"\"href\"\": \"\"https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeFailed?token=abc\"\" }\n        }\n    }\";\n\nvar response = JsonSerializer.Deserialize<EncryptedEncodingDataResponse>(apiResponseJson, options);\nif (response == null)\n{\n    throw new Exception(\"Cannot deserialize response.\");\n}\n\n// Derive the shared secret\nvar encryptedData = response.EncryptedEncodingData;\nusing var ephemeralKey = ECDiffieHellman.Create();\nephemeralKey.ImportSubjectPublicKeyInfo(encryptedData.EphemeralPublicKey, out _);\nbyte[] sharedSecret = privateKey.DeriveKeyFromHash(ephemeralKey.PublicKey, HashAlgorithmName.SHA512);\n\nbyte[] aesKey = sharedSecret.Take(32).ToArray();\nbyte[] hmacKey = sharedSecret.Skip(32).Take(32).ToArray();\n\n// Verify HMAC tag to ensure data integrity and authenticity before decryption\nusing var hmac = new HMACSHA256(hmacKey);\nvar contentToSign = encryptedData.CipherText\n    .Concat(encryptedData.IV)\n    .Concat(encryptedData.EphemeralPublicKey) // Include ephemeral key to bind it to this session\n    .ToArray();\n\nbyte[] computedHash = hmac.ComputeHash(contentToSign);\nif (!computedHash.SequenceEqual(encryptedData.Tag))\n{\n    throw new Exception(\"Error: HMAC tag verification failed. Data may use a different signing order or has been tampered with.\");\n}\n\n// Decrypt the cipher text using AES-CBC with the shared secret as the key and the provided IV\nusing var aes = Aes.Create();\naes.Key = aesKey;\naes.IV = encryptedData.IV;\naes.Mode = CipherMode.CBC;\naes.Padding = PaddingMode.PKCS7;\n\nusing var decryptor = aes.CreateDecryptor();\nbyte[] decryptedData = decryptor.TransformFinalBlock(encryptedData.CipherText, 0, encryptedData.CipherText.Length);\n\n// Convert decryptedData to string\nstring decryptedJson = Encoding.UTF8.GetString(decryptedData);\nConsole.WriteLine(\"Decryption successful. Decrypted data:\\n\" + decryptedJson);\n// Deserialize decrypted JSON to get the actual encoding data structure (e.g., DESFireEncodingResponse or ClassicEncodingResponse)\n...\n\npublic record EncryptedEncodingData(byte[] CipherText, byte[] IV, byte[] EphemeralPublicKey, byte[] Tag);\npublic record Link(string Href);\npublic record CallbackLinks(Link EncodeCompleted, Link EncodeFailed);\npublic record EncryptedEncodingDataResponse(EncryptedEncodingData EncryptedEncodingData, CallbackLinks Callback);\n```\n"}],"servers":[{"url":"https://127.0.0.1:8904"}],"security":[{"API_key":[]},{"basic":[]}],"components":{"securitySchemes":{"API_key":{"type":"apiKey","name":"Authorization","in":"header","description":"\nClients authenticate by including a pre-shared API key in the `Authorization` header of each\nrequest after an authorisation method of `GGL-API-KEY`:\n\n`Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB`\n\nFor a fuller description see [API authentication](api-authentication).\n"},"basic":{"type":"http","scheme":"basic","description":"This is HTTP Basic authentication using a blank username and the API key as the password.\n\nExample:  `Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==`\n\nThat example is equivalent to the example in the description of the api_Key method:\nprepending a colon to the API key beginning with `C774` then encoding it into Base64\nproduces the 56 characters beginning with `OkM3`.\n\nThe colon is a separator between a username, which is blank in our case, and the password,\nwhich is our API key.\n\nFor a fuller description see [API authentication](api-authentication)\n"}},"parameters":{"id":{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"An internal identifier."},"secondary_id":{"name":"secondary_id","in":"path","required":true,"schema":{"type":"string"},"description":"An internal identifier."}},"schemas":{"ClassicEncodingRequest":{"type":"object","properties":{"csn":{"$ref":"#/components/schemas/HexString"},"cad":{"$ref":"#/components/schemas/ClassicCad"},"mad":{"$ref":"#/components/schemas/ClassicMad"},"unavailableSectors":{"type":"array","items":{"type":"integer"}},"reason":{"type":"string","maxLength":100}},"required":["csn"],"description":"- `csn`: Card UID.\n- `cad`: Gallagher Application Directory sector/blocks (required if present on card).\n- `mad`: MAD bytes (required if present on card).\n- `unavailableSectors`: Sectors already occupied or reserved.\n- `reason`: Reason for encoding, e.g. \"New card issuance\", \"Lost card replacement\". Optional.\n"},"HexString":{"type":"string","description":"A string containing a hexadecimal encoding of a number.","pattern":"^0x([0-9A-Fa-f]{2})+$"},"ClassicCad":{"title":"MIFARE Classic CAD","type":"object","properties":{"sector":{"type":"integer"},"blocks":{"$ref":"#/components/schemas/ClassicBlocks"}},"required":["sector","blocks"]},"ClassicBlocks":{"type":"object","description":"Map of block index to data block bytes.  The block is binary data in hex string format that\ncorresponds to a file in the sector.\n","additionalProperties":{"$ref":"#/components/schemas/HexString"}},"ClassicMad":{"title":"MIFARE Classic MAD","type":"object","properties":{"blocks":{"$ref":"#/components/schemas/ClassicBlocks"}},"required":["blocks"]},"EncryptedEncodingDataResponseOrDecryptedForInfo":{"title":"Encrypted response and its decrypted forms.","oneOf":[{"$ref":"#/components/schemas/EncryptedEncodingDataResponse"},{"$ref":"#/components/schemas/DecryptedDESFireDataForInfo"},{"$ref":"#/components/schemas/DecryptedClassicDataForInfo"}]},"EncryptedEncodingDataResponse":{"type":"object","title":"Response schema","properties":{"encryptedEncodingData":{"$ref":"#/components/schemas/EncryptedEncodingData"},"callback":{"$ref":"#/components/schemas/CallbackLinks"}},"required":["encryptedEncodingData","callback"]},"EncryptedEncodingData":{"type":"object","properties":{"cipherText":{"type":"string","description":"The encrypted encoding data, encoded as a base64 string."},"iv":{"type":"string","description":"The initialisation vector used for encryption, encoded as a base64 string."},"ephemeralPublicKey":{"type":"string","description":"The ephemeral public key used for encryption, encoded as a base64 string."},"tag":{"type":"string","description":"The authentication tag for the encrypted data, encoded as a base64 string."}},"required":["cipherText","iv","ephemeralPublicKey","tag"]},"CallbackLinks":{"type":"object","properties":{"encodeCompleted":{"$ref":"#/components/schemas/CallbackLink"},"encodeFailed":{"$ref":"#/components/schemas/CallbackLink"}},"required":["encodeCompleted","encodeFailed"]},"CallbackLink":{"type":"object","properties":{"href":{"type":"string","description":"Absolute or relative URL for callback invocation."}},"required":["href"]},"DecryptedDESFireDataForInfo":{"title":"Example DESFire card data after decryption","summary":"This is not actual API data.  It is for information only.","description":"This is not actual API data.  It is for information only.","type":"object","properties":{"cardMasterKey":{"$ref":"#/components/schemas/HexString"},"defaultAppKey":{"$ref":"#/components/schemas/HexString"},"applications":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"keys":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":0},"name":{"type":"string"},"data":{"$ref":"#/components/schemas/HexString"},"version":{"type":"integer","minimum":1}}}},"keySettings1":{"type":"string"},"keySettings2":{"type":"string"},"files":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":0},"data":{"$ref":"#/components/schemas/HexString"},"accessRights":{"$ref":"#/components/schemas/HexString"},"communicationMode":{"$ref":"#/components/schemas/HexString"}}}}}}}}},"DecryptedClassicDataForInfo":{"title":"Example Classic card data after decryption","summary":"This is not actual API data.  It is for information only.","description":"This is not actual API data.  It is for information only.","type":"object","properties":{"sectors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":0},"name":{"type":"string"},"blocks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","minimum":0},"file":{"$ref":"#/components/schemas/HexString"}}}}}}}}}},"responses":{"404enc":{"description":"The cardholder does not exist or is not visible to the operator, or that card is not on the\ncardholder.\n"}}},"paths":{"/api/cardholders/{id}/cards/{secondary_id}/encoding_data/mifare_classic":{"post":{"tags":["Card encoding"],"operationId":"getMifareClassicEncodingData","summary":"Get encoding payload for a Classic card","description":"This returns the data you need to encode a Gallagher credential to a MIFARE Classic card.\n\nPOST the card's CSN (UID) and card directory data (CAD/MAD) if present, plus any unavailable\nsectors.  The response contains the sectors and blocks to write, and callback links for you\nto report the success of your encoding.\n\n**Encode applications in the order they are returned** so that the card remains usable if\nencoding fails part-way through.\n","parameters":[{"$ref":"#/components/parameters/id"},{"$ref":"#/components/parameters/secondary_id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassicEncodingRequest"}}}},"responses":{"200":{"description":"Returns callback links and encrypted Classic encoding data.\n\nThe `encryptedEncodingData` field contains an encrypted payload that, when decrypted,has\nthe structure of [ClassicEncodingResponse](#schemas/ClassicEncodingResponse) with\nsectors and blocks.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EncryptedEncodingDataResponseOrDecryptedForInfo"}}}},"400":{"description":"Possible reasons:\n- The request body was invalid.\n- There are no free sectors left on the card.\n- The card is not of an encodable type.  In API terms, its `credentialClass` needs to be `card`.\n- The reason for encoding is too long (over 100 characters).\n"},"403":{"description":"Possible reasons:\n- Command Centre does not have a licence for the encoding API.\n- The operator lacks the necessary privilege.\n- Re-encoding is disabled for that card type and the card has already been encoded.\n"},"404":{"$ref":"#/components/responses/404enc"},"422":{"description":"Possible reasons:\n- Config Client is not configured with end to end encryption keys so it cannot encrypt the encoding data to return in the response.\n"}}}}}}
````

## Notify Command Centre that encoding succeeded

> Notify Command Centre that encoding succeeded for the given credential.\
> \
> Do not code this URL into your application.  Take it from the 'callback' block that\
> accompanied the card's encoding data in the previous API call.<br>

````json
{"openapi":"3.2.0","info":{"title":"Command Centre REST API","version":"2.0"},"tags":[{"name":"Card encoding","description":"These routes allow clients to get Gallagher card encoding data for MIFARE DESFire and MIFARE Classic cards.\n- The following features are required in the licence file:\n  - RESTCardholders\n  - RESTEncoding\n  - Encoding\n  - Photo-ID\n- The REST Client operator should have the following privileges:\n  - View Cardholders\n  - Print/Preview & Encode Card\n  - Allow Re-Printing and Re-Encoding (optional, depending on whether you want to allow re-encoding of already-encoded cards)\n- Bytes are encoded as hex strings in decrypted encoding data.\n- Sensitive data is encrypted in responses.\n- Using the client certificate for authentication is recommended.\n\n### Configuration End to End Encryption\n1. Using openssl to generate ECDH_P384 public/private key pair\n  ```bash\n  openssl ecparam -name secp384r1 -genkey -noout -out private.pem\n\n  openssl ec -in private.pem -pubout -outform DER | tail -c 97 | head -c 97 | xxd -p -c 97\n\n  openssl req -new -x509 -key private.pem -out E2EEncryptionKey.crt -days 365 -subj \"/CN=Command Center REST Client E2E Encryption Key\"\n\n  openssl pkcs12 -export -out key.pfx -inkey private.pem -in E2EEncryptionKey.crt\n  ```\n2. Store the private key securely on the encoding client side, e.g., in the Windows Certificate Store.\n  ```powershell\n  Import-PfxCertificate -FilePath \"path\\to\\key.pfx\" -CertStoreLocation Cert:\\CurrentUser\\My -Password (Read-Host \"Enter PFX Password\" -AsSecureString)\n  ```\n3. Open the REST Client settings in Configuration Client, put the X9.63 hex format of public key to the Remote Public Key input in E2E Key tab.\n\n### Code Example for Client-Side Decryption\n```csharp\nusing System.Security.Cryptography;\nusing System.Security.Cryptography.X509Certificates;\nusing System.Text;\nusing System.Text.Json;\n\nvar options = new JsonSerializerOptions\n{\n    PropertyNameCaseInsensitive = true\n};\n\n// Load private key from Windows Certificate Store\n// Open the Current User store\nusing X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);\nstore.Open(OpenFlags.ReadOnly);\n\n// Find the certificate by Subject Name\nX509Certificate2? cert = store.Certificates\n    .Find(X509FindType.FindBySubjectDistinguishedName, \"CN=Command Center REST Client E2E Encryption Key\", false)\n    .FirstOrDefault();\n\nif (cert == null)\n{\n    Console.WriteLine(\"Error: Certificate not found.\");\n    return;\n}\n\n// 3. Get The Private Key\nECDiffieHellman? privateKey = cert.GetECDiffieHellmanPrivateKey();\n\nif (privateKey == null)\n{\n    throw new Exception(\"Certificate found, but private key is not accessible or not ECDH.\");\n}\n\nConsole.WriteLine($\"Successfully retrieved key. Curve: {privateKey.ExportParameters(false).Curve.Oid.FriendlyName}\");\n\n// Simulated API response (replace with actual API call and response content)\nvar apiResponseJson = @\"{\n    \"\"encryptedEncodingData\"\": {\n        \"\"cipherText\"\": \"\"<base64-encoded cipher text>\"\",\n        \"\"iv\"\": \"\"<base64-encoded IV>\"\",\n        \"\"ephemeralPublicKey\"\": \"\"<base64-encoded ephemeral public key>\"\",\n        \"\"tag\"\": \"\"<base64-encoded authentication tag>\"\"\n    },\n    \"\"callback\"\": {\n    \"\"encodeCompleted\"\": {\n        \"\"href\"\": \"\"https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted?token=abc\"\" },\n        \"\"encodeFailed\"\": {\n            \"\"href\"\": \"\"https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeFailed?token=abc\"\" }\n        }\n    }\";\n\nvar response = JsonSerializer.Deserialize<EncryptedEncodingDataResponse>(apiResponseJson, options);\nif (response == null)\n{\n    throw new Exception(\"Cannot deserialize response.\");\n}\n\n// Derive the shared secret\nvar encryptedData = response.EncryptedEncodingData;\nusing var ephemeralKey = ECDiffieHellman.Create();\nephemeralKey.ImportSubjectPublicKeyInfo(encryptedData.EphemeralPublicKey, out _);\nbyte[] sharedSecret = privateKey.DeriveKeyFromHash(ephemeralKey.PublicKey, HashAlgorithmName.SHA512);\n\nbyte[] aesKey = sharedSecret.Take(32).ToArray();\nbyte[] hmacKey = sharedSecret.Skip(32).Take(32).ToArray();\n\n// Verify HMAC tag to ensure data integrity and authenticity before decryption\nusing var hmac = new HMACSHA256(hmacKey);\nvar contentToSign = encryptedData.CipherText\n    .Concat(encryptedData.IV)\n    .Concat(encryptedData.EphemeralPublicKey) // Include ephemeral key to bind it to this session\n    .ToArray();\n\nbyte[] computedHash = hmac.ComputeHash(contentToSign);\nif (!computedHash.SequenceEqual(encryptedData.Tag))\n{\n    throw new Exception(\"Error: HMAC tag verification failed. Data may use a different signing order or has been tampered with.\");\n}\n\n// Decrypt the cipher text using AES-CBC with the shared secret as the key and the provided IV\nusing var aes = Aes.Create();\naes.Key = aesKey;\naes.IV = encryptedData.IV;\naes.Mode = CipherMode.CBC;\naes.Padding = PaddingMode.PKCS7;\n\nusing var decryptor = aes.CreateDecryptor();\nbyte[] decryptedData = decryptor.TransformFinalBlock(encryptedData.CipherText, 0, encryptedData.CipherText.Length);\n\n// Convert decryptedData to string\nstring decryptedJson = Encoding.UTF8.GetString(decryptedData);\nConsole.WriteLine(\"Decryption successful. Decrypted data:\\n\" + decryptedJson);\n// Deserialize decrypted JSON to get the actual encoding data structure (e.g., DESFireEncodingResponse or ClassicEncodingResponse)\n...\n\npublic record EncryptedEncodingData(byte[] CipherText, byte[] IV, byte[] EphemeralPublicKey, byte[] Tag);\npublic record Link(string Href);\npublic record CallbackLinks(Link EncodeCompleted, Link EncodeFailed);\npublic record EncryptedEncodingDataResponse(EncryptedEncodingData EncryptedEncodingData, CallbackLinks Callback);\n```\n"}],"servers":[{"url":"https://127.0.0.1:8904"}],"security":[{"API_key":[]},{"basic":[]}],"components":{"securitySchemes":{"API_key":{"type":"apiKey","name":"Authorization","in":"header","description":"\nClients authenticate by including a pre-shared API key in the `Authorization` header of each\nrequest after an authorisation method of `GGL-API-KEY`:\n\n`Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB`\n\nFor a fuller description see [API authentication](api-authentication).\n"},"basic":{"type":"http","scheme":"basic","description":"This is HTTP Basic authentication using a blank username and the API key as the password.\n\nExample:  `Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==`\n\nThat example is equivalent to the example in the description of the api_Key method:\nprepending a colon to the API key beginning with `C774` then encoding it into Base64\nproduces the 56 characters beginning with `OkM3`.\n\nThe colon is a separator between a username, which is blank in our case, and the password,\nwhich is our API key.\n\nFor a fuller description see [API authentication](api-authentication)\n"}},"parameters":{"id":{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"An internal identifier."},"secondary_id":{"name":"secondary_id","in":"path","required":true,"schema":{"type":"string"},"description":"An internal identifier."},"token":{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"Token provided by the encoding-data response callback link."}},"responses":{"404enc":{"description":"The cardholder does not exist or is not visible to the operator, or that card is not on the\ncardholder.\n"}}},"paths":{"/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted":{"post":{"tags":["Card encoding"],"operationId":"notifyEncodeCompleted","summary":"Notify Command Centre that encoding succeeded","description":"Notify Command Centre that encoding succeeded for the given credential.\n\nDo not code this URL into your application.  Take it from the 'callback' block that\naccompanied the card's encoding data in the previous API call.\n","parameters":[{"$ref":"#/components/parameters/id"},{"$ref":"#/components/parameters/secondary_id"},{"$ref":"#/components/parameters/token"}],"responses":{"200":{"description":"OK"},"400":{"description":"Bad request:  token is missing or not recognised."},"403":{"description":"The site does not have a licence for the Encoding API."},"404":{"$ref":"#/components/responses/404enc"},"410":{"description":"Callback token has expired or already been used."}}}}}}
````

## Notify Command Centre that encoding failed

> Notify Command Centre that encoding failed for the given credential. Optionally include an\
> error message.\
> \
> Do not code this URL into your application.  Take it from the 'callback' block that\
> accompanied the card's encoding data in the previous API call.<br>

````json
{"openapi":"3.2.0","info":{"title":"Command Centre REST API","version":"2.0"},"tags":[{"name":"Card encoding","description":"These routes allow clients to get Gallagher card encoding data for MIFARE DESFire and MIFARE Classic cards.\n- The following features are required in the licence file:\n  - RESTCardholders\n  - RESTEncoding\n  - Encoding\n  - Photo-ID\n- The REST Client operator should have the following privileges:\n  - View Cardholders\n  - Print/Preview & Encode Card\n  - Allow Re-Printing and Re-Encoding (optional, depending on whether you want to allow re-encoding of already-encoded cards)\n- Bytes are encoded as hex strings in decrypted encoding data.\n- Sensitive data is encrypted in responses.\n- Using the client certificate for authentication is recommended.\n\n### Configuration End to End Encryption\n1. Using openssl to generate ECDH_P384 public/private key pair\n  ```bash\n  openssl ecparam -name secp384r1 -genkey -noout -out private.pem\n\n  openssl ec -in private.pem -pubout -outform DER | tail -c 97 | head -c 97 | xxd -p -c 97\n\n  openssl req -new -x509 -key private.pem -out E2EEncryptionKey.crt -days 365 -subj \"/CN=Command Center REST Client E2E Encryption Key\"\n\n  openssl pkcs12 -export -out key.pfx -inkey private.pem -in E2EEncryptionKey.crt\n  ```\n2. Store the private key securely on the encoding client side, e.g., in the Windows Certificate Store.\n  ```powershell\n  Import-PfxCertificate -FilePath \"path\\to\\key.pfx\" -CertStoreLocation Cert:\\CurrentUser\\My -Password (Read-Host \"Enter PFX Password\" -AsSecureString)\n  ```\n3. Open the REST Client settings in Configuration Client, put the X9.63 hex format of public key to the Remote Public Key input in E2E Key tab.\n\n### Code Example for Client-Side Decryption\n```csharp\nusing System.Security.Cryptography;\nusing System.Security.Cryptography.X509Certificates;\nusing System.Text;\nusing System.Text.Json;\n\nvar options = new JsonSerializerOptions\n{\n    PropertyNameCaseInsensitive = true\n};\n\n// Load private key from Windows Certificate Store\n// Open the Current User store\nusing X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);\nstore.Open(OpenFlags.ReadOnly);\n\n// Find the certificate by Subject Name\nX509Certificate2? cert = store.Certificates\n    .Find(X509FindType.FindBySubjectDistinguishedName, \"CN=Command Center REST Client E2E Encryption Key\", false)\n    .FirstOrDefault();\n\nif (cert == null)\n{\n    Console.WriteLine(\"Error: Certificate not found.\");\n    return;\n}\n\n// 3. Get The Private Key\nECDiffieHellman? privateKey = cert.GetECDiffieHellmanPrivateKey();\n\nif (privateKey == null)\n{\n    throw new Exception(\"Certificate found, but private key is not accessible or not ECDH.\");\n}\n\nConsole.WriteLine($\"Successfully retrieved key. Curve: {privateKey.ExportParameters(false).Curve.Oid.FriendlyName}\");\n\n// Simulated API response (replace with actual API call and response content)\nvar apiResponseJson = @\"{\n    \"\"encryptedEncodingData\"\": {\n        \"\"cipherText\"\": \"\"<base64-encoded cipher text>\"\",\n        \"\"iv\"\": \"\"<base64-encoded IV>\"\",\n        \"\"ephemeralPublicKey\"\": \"\"<base64-encoded ephemeral public key>\"\",\n        \"\"tag\"\": \"\"<base64-encoded authentication tag>\"\"\n    },\n    \"\"callback\"\": {\n    \"\"encodeCompleted\"\": {\n        \"\"href\"\": \"\"https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeCompleted?token=abc\"\" },\n        \"\"encodeFailed\"\": {\n            \"\"href\"\": \"\"https://<commandcentre host name>/api/cardholders/{id}/cards/{secondary_id}/encodeFailed?token=abc\"\" }\n        }\n    }\";\n\nvar response = JsonSerializer.Deserialize<EncryptedEncodingDataResponse>(apiResponseJson, options);\nif (response == null)\n{\n    throw new Exception(\"Cannot deserialize response.\");\n}\n\n// Derive the shared secret\nvar encryptedData = response.EncryptedEncodingData;\nusing var ephemeralKey = ECDiffieHellman.Create();\nephemeralKey.ImportSubjectPublicKeyInfo(encryptedData.EphemeralPublicKey, out _);\nbyte[] sharedSecret = privateKey.DeriveKeyFromHash(ephemeralKey.PublicKey, HashAlgorithmName.SHA512);\n\nbyte[] aesKey = sharedSecret.Take(32).ToArray();\nbyte[] hmacKey = sharedSecret.Skip(32).Take(32).ToArray();\n\n// Verify HMAC tag to ensure data integrity and authenticity before decryption\nusing var hmac = new HMACSHA256(hmacKey);\nvar contentToSign = encryptedData.CipherText\n    .Concat(encryptedData.IV)\n    .Concat(encryptedData.EphemeralPublicKey) // Include ephemeral key to bind it to this session\n    .ToArray();\n\nbyte[] computedHash = hmac.ComputeHash(contentToSign);\nif (!computedHash.SequenceEqual(encryptedData.Tag))\n{\n    throw new Exception(\"Error: HMAC tag verification failed. Data may use a different signing order or has been tampered with.\");\n}\n\n// Decrypt the cipher text using AES-CBC with the shared secret as the key and the provided IV\nusing var aes = Aes.Create();\naes.Key = aesKey;\naes.IV = encryptedData.IV;\naes.Mode = CipherMode.CBC;\naes.Padding = PaddingMode.PKCS7;\n\nusing var decryptor = aes.CreateDecryptor();\nbyte[] decryptedData = decryptor.TransformFinalBlock(encryptedData.CipherText, 0, encryptedData.CipherText.Length);\n\n// Convert decryptedData to string\nstring decryptedJson = Encoding.UTF8.GetString(decryptedData);\nConsole.WriteLine(\"Decryption successful. Decrypted data:\\n\" + decryptedJson);\n// Deserialize decrypted JSON to get the actual encoding data structure (e.g., DESFireEncodingResponse or ClassicEncodingResponse)\n...\n\npublic record EncryptedEncodingData(byte[] CipherText, byte[] IV, byte[] EphemeralPublicKey, byte[] Tag);\npublic record Link(string Href);\npublic record CallbackLinks(Link EncodeCompleted, Link EncodeFailed);\npublic record EncryptedEncodingDataResponse(EncryptedEncodingData EncryptedEncodingData, CallbackLinks Callback);\n```\n"}],"servers":[{"url":"https://127.0.0.1:8904"}],"security":[{"API_key":[]},{"basic":[]}],"components":{"securitySchemes":{"API_key":{"type":"apiKey","name":"Authorization","in":"header","description":"\nClients authenticate by including a pre-shared API key in the `Authorization` header of each\nrequest after an authorisation method of `GGL-API-KEY`:\n\n`Authorization: GGL-API-KEY C774-B01F-D695-AA4B-215F-A158-AC22-ADEB`\n\nFor a fuller description see [API authentication](api-authentication).\n"},"basic":{"type":"http","scheme":"basic","description":"This is HTTP Basic authentication using a blank username and the API key as the password.\n\nExample:  `Authorization: Basic OkM3NzQtQjAxRi1ENjk1LUFBNEItMjE1Ri1BMTU4LUFDMjItQURFQg==`\n\nThat example is equivalent to the example in the description of the api_Key method:\nprepending a colon to the API key beginning with `C774` then encoding it into Base64\nproduces the 56 characters beginning with `OkM3`.\n\nThe colon is a separator between a username, which is blank in our case, and the password,\nwhich is our API key.\n\nFor a fuller description see [API authentication](api-authentication)\n"}},"parameters":{"id":{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"An internal identifier."},"secondary_id":{"name":"secondary_id","in":"path","required":true,"schema":{"type":"string"},"description":"An internal identifier."},"token":{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"Token provided by the encoding-data response callback link."}},"schemas":{"ErrorMessage":{"type":"object","properties":{"message":{"type":"string"}}}},"responses":{"404enc":{"description":"The cardholder does not exist or is not visible to the operator, or that card is not on the\ncardholder.\n"}}},"paths":{"/api/cardholders/{id}/cards/{secondary_id}/encodeFailed":{"post":{"tags":["Card encoding"],"operationId":"notifyEncodeFailed","summary":"Notify Command Centre that encoding failed","description":"Notify Command Centre that encoding failed for the given credential. Optionally include an\nerror message.\n\nDo not code this URL into your application.  Take it from the 'callback' block that\naccompanied the card's encoding data in the previous API call.\n","parameters":[{"$ref":"#/components/parameters/id"},{"$ref":"#/components/parameters/secondary_id"},{"$ref":"#/components/parameters/token"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorMessage"}}}},"responses":{"200":{"description":"OK"},"400":{"description":"Token is missing or not recognised, or the request body is invalid."},"403":{"description":"The site does not have a licence for the Encoding API."},"404":{"$ref":"#/components/responses/404enc"},"410":{"description":"Callback token has expired or already been used."}}}}}}
````


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://help.security.gallagher.com/knowledge-hub/developer/rest-api/card-encoding.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
