> For the complete documentation index, see [llms.txt](https://docs.meeco.me/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.meeco.me/guides/certificates.md).

# Certificates

Certificates are used to establish trust between parties in [OID4VCI](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) flows. They are included in the `x5c` header of issued credentials and presentation requests, allowing verifiers and wallets to validate the certificate chain back to a trusted root.

SVX Wallet supports multiple key types, but the two most relevant for certificate-backed trust are:

* **Credential key** – used to sign credentials (for example `mso_mdoc` Document Signing Certificate, DSC).
* **Presentation request key** – used to sign presentation requests by the verifier, establishing verifier identity.

The typical setup flow is:

1. Create a root CA locally.
2. Create a Certificate Signing Request (CSR) via the SVX Wallet API.
3. Sign the CSR with your root CA using `openssl`.
4. Import the signed certificate back into the SVX Wallet API.
5. Optionally import trust anchor certificates for external chain validation.

## Who can undertake this operation?

Certificate management can only be performed by people who have access to the SVX Wallet Dashboard and API.

## Create a Root CA

Before creating CSRs, you need a root CA to sign leaf certificates. The following examples create a self-signed root CA for credential signing (IACA) and a separate root for presentation request signing.

```bash
# Create a root CA for credential signing (IACA)
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
  -x509 -sha256 -days 1825 \
  -keyout meeco_iaca.key \
  -out meeco_iaca.cer \
  -subj "/C=AU/O=Meeco/CN=Meeco IACA" \
  -addext "basicConstraints=critical,CA:true,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign" \
  -addext "subjectKeyIdentifier=hash" \
  -addext "subjectAltName=URI:<issuer_uri>"

# Create a root CA for presentation request signing
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
  -x509 -sha256 -days 1825 \
  -keyout meeco_reader_root.key \
  -out meeco_reader_root.cer \
  -subj "/C=AU/O=Meeco/CN=Meeco" \
  -addext "basicConstraints=critical,CA:true,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign" \
  -addext "subjectKeyIdentifier=hash" \
  -addext "subjectAltName=URI:<issuer_uri>"
```

## Create a CSR

CSRs are created via the SVX Wallet API against a named key. The API returns the CSR PEM for external signing.

Create a CSR for the credential signing key:

```bash
curl -sS -X POST "$SVX_WALLET_BASE_URL/system/certificates/csrs" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "key_name": "CredentialKey",
    "subject": {
      "common_name": "wallet.meeco.cloud",
      "country": "AU",
      "state": "New South Wales",
      "locality": "Sydney",
      "organization": "Meeco",
      "organizational_unit": "Identity"
    },
    "issuer_alternative_name_url": [
      "DNS:wallet.meeco.cloud"
    ]
  }' | tee credential-csr.json

jq -r '.csr.csr_pem' credential-csr.json > credential.csr.pem
```

Create a CSR for the presentation request signing key:

```bash
curl -sS -X POST "$SVX_WALLET_BASE_URL/system/certificates/csrs" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "key_name": "PresentationRequestKey",
    "subject": {
      "common_name": "wallet.meeco.cloud",
      "country": "AU",
      "state": "New South Wales",
      "locality": "Sydney",
      "organization": "Meeco",
      "organizational_unit": "Identity"
    },
    "issuer_alternative_name_url": [
      "DNS:wallet.meeco.cloud"
    ]
  }' | tee request-csr.json

jq -r '.csr.csr_pem' request-csr.json > request.csr.pem
```

## Sign the CSRs

Use `openssl` to sign each CSR with the appropriate root CA. Certificate extensions differ depending on the intended key usage.

Save the following extension configuration to a file (e.g. `openssl-extensions.ext`):

> **Note:** `openssl x509 -req` does not automatically copy all attributes from the CSR into the signed certificate. Properties such as `subjectAltName`, `keyUsage`, and `extendedKeyUsage` must be explicitly defined in the extension file to ensure they appear correctly in the issued certificate.

```ini
[ v3_mdoc_dsc ]
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature
extendedKeyUsage = 1.0.18013.5.1.2,1.3.6.1.4.1.61546.0
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
subjectAltName = DNS:wallet.meeco.cloud

[ v3_request ]
basicConstraints = critical,CA:false
keyUsage = critical,digitalSignature
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
subjectAltName = DNS:wallet.meeco.cloud
```

Sign the credential CSR (DSC):

```bash
openssl x509 -req -sha256 \
  -in credential.csr.pem \
  -CA meeco_iaca.cer \
  -CAkey meeco_iaca.key \
  -CAcreateserial \
  -out credential-leaf.cert.pem \
  -days 365 \
  -extfile openssl-extensions.ext \
  -extensions v3_mdoc_dsc
```

Sign the presentation request CSR:

```bash
openssl x509 -req -sha256 \
  -in request.csr.pem \
  -CA meeco_reader_root.cer \
  -CAkey meeco_reader_root.key \
  -CAcreateserial \
  -out request-leaf.cert.pem \
  -days 365 \
  -extfile openssl-extensions.ext \
  -extensions v3_request
```

## Import Signed Certificates

Once signed, import each leaf certificate the SVX Wallet API. The certificate must be DER-encoded and base64-encoded in the `x5c` field.

```bash
# Import credential leaf certificate
curl -sS -X POST "$SVX_WALLET_BASE_URL/system/certificates/import" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"key_name\":\"CredentialKey\",\"x5c\":[\"$(openssl x509 -in credential-leaf.cert.pem -outform DER | openssl base64 -A)\"]}" | jq

# Import presentation request leaf certificate
curl -sS -X POST "$SVX_WALLET_BASE_URL/system/certificates/import" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"key_name\":\"PresentationRequestKey\",\"x5c\":[\"$(openssl x509 -in request-leaf.cert.pem -outform DER | openssl base64 -A)\"]}" | jq
```

## Import Trust Anchor Certificates

Trust anchor certificates allow the wallet to validate incoming certificate chains from external issuers or verifiers.

Import a trust anchor certificate chain:

```bash
curl -X POST "$SVX_WALLET_BASE_URL/system/certificates/import" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"x5c\":[\"$(openssl x509 -in root-ca.cert.pem -outform DER | openssl base64 -A)\"]}"
```

## Remaining endpoints

The following endpoints are used to maintain and read certificate information:

* `GET /system/certificates` - List all imported trust anchor certificates.
* `DELETE /system/certificates/{certificate_id}` - Delete a trust anchor certificate.

## API References

## Import certificate

> Import certificate material in \`x5c\` form (DER-encoded, base64-encoded items).\
> \
> Behavior depends on whether \`key\_name\` is provided:\
> \- With \`key\_name\`: imports a \`managed\` certificate for that managed signing key.\
> &#x20; \- \`x5c\` must contain at least one certificate.\
> &#x20; \- The first item (leaf certificate) public key must match the managed key.\
> &#x20; \- The full provided chain is stored.\
> &#x20; \- \`fingerprint256\` is stored as \`null\`.\
> \- Without \`key\_name\`: imports a \`trust\_anchor\` certificate.\
> &#x20; \- \`x5c\` must contain exactly one certificate.\
> &#x20; \- \`fingerprint256\` is computed and stored.\
> \
> Notes:\
> \- For \`mso\_mdoc\` (mDL) credential issuance the credential key must have a valid DSC certificate; issuance\
> &#x20; will fail without it (see ISO 18013-5 for mDL DSC requirements).\
> \- When signing CSRs externally (for example with \`openssl x509 -req\`), explicitly provide extensions\
> &#x20; such as \`subjectAltName\`, \`keyUsage\`, and \`extendedKeyUsage\` so they are present in the issued certificate.<br>

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"servers":[{"url":"https://zyfla-wallet-sandbox.meeco.cloud","description":"SVX Wallet deployment (relative)"}],"security":[{},{"bearerAuth":[]},{"mutualTLS":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"JWT","scheme":"bearer","type":"http"},"mutualTLS":{"type":"mutualTLS"}},"schemas":{"ImportCertificateRequestDto":{"type":"object","properties":{"x5c":{"type":"array","minItems":1,"description":"Certificate chain in DER base64 format.\n- With `key_name`, provide at least the leaf certificate (full chain is accepted and stored).\n- Without `key_name`, exactly one certificate is required.\n","items":{"type":"string"}},"key_name":{"type":"string","description":"Optional managed signing key name. When present, imports as `managed`."}},"required":["x5c"]},"CertificateResponseDto":{"type":"object","properties":{"certificate":{"$ref":"#/components/schemas/CertificateModelDto"}},"required":["certificate"]},"CertificateModelDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["trust_anchor","managed"]},"key_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"x5c":{"type":"array","minItems":1,"items":{"type":"string"}},"fingerprint256":{"description":"Present for `trust_anchor`; `null` for `managed`.","anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time"}},"required":["id","type","key_name","x5c","fingerprint256","created_at"]}}},"paths":{"/system/certificates/import":{"post":{"summary":"Import certificate","description":"Import certificate material in `x5c` form (DER-encoded, base64-encoded items).\n\nBehavior depends on whether `key_name` is provided:\n- With `key_name`: imports a `managed` certificate for that managed signing key.\n  - `x5c` must contain at least one certificate.\n  - The first item (leaf certificate) public key must match the managed key.\n  - The full provided chain is stored.\n  - `fingerprint256` is stored as `null`.\n- Without `key_name`: imports a `trust_anchor` certificate.\n  - `x5c` must contain exactly one certificate.\n  - `fingerprint256` is computed and stored.\n\nNotes:\n- For `mso_mdoc` (mDL) credential issuance the credential key must have a valid DSC certificate; issuance\n  will fail without it (see ISO 18013-5 for mDL DSC requirements).\n- When signing CSRs externally (for example with `openssl x509 -req`), explicitly provide extensions\n  such as `subjectAltName`, `keyUsage`, and `extendedKeyUsage` so they are present in the issued certificate.\n","operationId":"CertificatesController_importCertificate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportCertificateRequestDto"}}}},"responses":{"201":{"description":"Certificate imported","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CertificateResponseDto"}}}}},"tags":["Certificates"]}}}}
```

## List certificates

> Returns a list of imported certificates and metadata.\
> \
> Certificates are stored by type:\
> \- \`managed\`: bound to a managed signing key (\`key\_name\` present), may contain a full \`x5c\` chain,\
> &#x20; and has \`fingerprint256 = null\`.\
> \- \`trust\_anchor\`: imported without \`key\_name\`, must contain exactly one certificate in \`x5c\`, and has\
> &#x20; a non-null \`fingerprint256\`.\
> \
> The response includes certificate id, type, associated \`key\_name\` (when bound), the stored \`x5c\`,\
> and \`fingerprint256\` according to the certificate type rules above.<br>

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"servers":[{"url":"https://zyfla-wallet-sandbox.meeco.cloud","description":"SVX Wallet deployment (relative)"}],"security":[{},{"bearerAuth":[]},{"mutualTLS":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"JWT","scheme":"bearer","type":"http"},"mutualTLS":{"type":"mutualTLS"}},"schemas":{"CertificatesResponseDto":{"type":"object","properties":{"certificates":{"type":"array","items":{"$ref":"#/components/schemas/CertificateModelDto"}}},"required":["certificates"]},"CertificateModelDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["trust_anchor","managed"]},"key_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"x5c":{"type":"array","minItems":1,"items":{"type":"string"}},"fingerprint256":{"description":"Present for `trust_anchor`; `null` for `managed`.","anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time"}},"required":["id","type","key_name","x5c","fingerprint256","created_at"]}}},"paths":{"/system/certificates":{"get":{"summary":"List certificates","description":"Returns a list of imported certificates and metadata.\n\nCertificates are stored by type:\n- `managed`: bound to a managed signing key (`key_name` present), may contain a full `x5c` chain,\n  and has `fingerprint256 = null`.\n- `trust_anchor`: imported without `key_name`, must contain exactly one certificate in `x5c`, and has\n  a non-null `fingerprint256`.\n\nThe response includes certificate id, type, associated `key_name` (when bound), the stored `x5c`,\nand `fingerprint256` according to the certificate type rules above.\n","operationId":"CertificatesController_listCertificates","parameters":[],"responses":{"200":{"description":"Certificates list retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CertificatesResponseDto"}}}}},"tags":["Certificates"]}}}}
```

## Delete certificate

> Delete an imported certificate by identifier.\
> \
> Removal deletes either:\
> \- a \`managed\` key-bound certificate chain, or\
> \- a trust entry (\`trust\_anchor\`).\
> \
> Removing a credential signing certificate can break \`mso\_mdoc\` issuance.<br>

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"servers":[{"url":"https://zyfla-wallet-sandbox.meeco.cloud","description":"SVX Wallet deployment (relative)"}],"security":[{},{"bearerAuth":[]},{"mutualTLS":[]}],"components":{"securitySchemes":{"bearerAuth":{"bearerFormat":"JWT","scheme":"bearer","type":"http"},"mutualTLS":{"type":"mutualTLS"}}},"paths":{"/system/certificates/{certificate_id}":{"delete":{"summary":"Delete certificate","description":"Delete an imported certificate by identifier.\n\nRemoval deletes either:\n- a `managed` key-bound certificate chain, or\n- a trust entry (`trust_anchor`).\n\nRemoving a credential signing certificate can break `mso_mdoc` issuance.\n","operationId":"CertificatesController_deleteCertificate","parameters":[{"name":"certificate_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Certificate ID"}],"responses":{"204":{"description":"Certificate deleted"}},"tags":["Certificates"]}}}}
```

## The TrustedCertificateNotFoundError object

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"components":{"schemas":{"TrustedCertificateNotFoundError":{"properties":{"error":{"description":"Unique error identifier","enum":["trusted_certificate_not_found"],"type":"string"},"extra_info":{"description":"Object that may contain more information about the error","type":"object"},"message":{"description":"User friendly error message","type":"string"}},"required":["error","message","extra_info"],"type":"object"}}}}
```

## The InvalidTrustedCertificateFormatError object

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"components":{"schemas":{"InvalidTrustedCertificateFormatError":{"properties":{"error":{"description":"Unique error identifier","enum":["invalid_trusted_certificate_format"],"type":"string"},"extra_info":{"description":"Object that may contain more information about the error","type":"object"},"message":{"description":"User friendly error message","type":"string"}},"required":["error","message","extra_info"],"type":"object"}}}}
```

## The FailedToLoadCertificateError object

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"components":{"schemas":{"FailedToLoadCertificateError":{"properties":{"error":{"description":"Unique error identifier","enum":["failed_to_load_certificate"],"type":"string"},"extra_info":{"description":"Object that may contain more information about the error","type":"object"},"message":{"description":"User friendly error message","type":"string"}},"required":["error","message","extra_info"],"type":"object"}}}}
```

## The CertificateAlreadyRegistered object

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"components":{"schemas":{"CertificateAlreadyRegistered":{"properties":{"error":{"description":"Unique error identifier","enum":["certificate_already_registered"],"type":"string"},"extra_info":{"description":"Object that may contain more information about the error","type":"object"},"message":{"description":"User friendly error message","type":"string"}},"required":["error","message","extra_info"],"type":"object"}}}}
```

## The CertificateSubjectDto object

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"components":{"schemas":{"CertificateSubjectDto":{"type":"object","properties":{"common_name":{"type":"string"},"country":{"type":"string"},"state":{"type":"string"},"locality":{"type":"string"},"organization":{"type":"string"},"organizational_unit":{"type":"string"}},"required":["common_name"]}}}}
```

## The CreateCsrRequestDto object

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"components":{"schemas":{"CreateCsrRequestDto":{"type":"object","properties":{"key_name":{"type":"string","description":"Managed signing key identifier","enum":["AccessTokenKey","CredentialKey","PresentationRequestKey","AdminSigningKey"]},"subject":{"$ref":"#/components/schemas/CertificateSubjectDto"},"issuer_alternative_name_url":{"type":"array","items":{"type":"string"}}},"required":["key_name","subject"]},"CertificateSubjectDto":{"type":"object","properties":{"common_name":{"type":"string"},"country":{"type":"string"},"state":{"type":"string"},"locality":{"type":"string"},"organization":{"type":"string"},"organizational_unit":{"type":"string"}},"required":["common_name"]}}}}
```

## The ImportCertificateRequestDto object

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"components":{"schemas":{"ImportCertificateRequestDto":{"type":"object","properties":{"x5c":{"type":"array","minItems":1,"description":"Certificate chain in DER base64 format.\n- With `key_name`, provide at least the leaf certificate (full chain is accepted and stored).\n- Without `key_name`, exactly one certificate is required.\n","items":{"type":"string"}},"key_name":{"type":"string","description":"Optional managed signing key name. When present, imports as `managed`."}},"required":["x5c"]}}}}
```

## The CsrResponseDto object

```json
{"openapi":"3.1.0","info":{"title":"SVX Wallet API","version":"4.0.1"},"components":{"schemas":{"CsrResponseDto":{"type":"object","properties":{"csr":{"type":"object","properties":{"key_name":{"type":"string"},"csr_pem":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"required":["key_name","csr_pem","created_at"]}},"required":["csr"]}}}}
```
