scitt-ccf-ledger

SCITT Configuration Guide

When SCITT-CCF nodes are first deployed, they are started with an initial node configuration.

Members registered in the initial node configuration must then be activated.

Members can then make and vote on proposals to update SCITT service configuration.

Once SCITT is appropriately configured members can vote to open the service.

SCITT Configuration

SCITT configuration can be set via the set_scitt_configuration action within a governance proposal. Each item in args.configuration within set_scitt_configuration is a separate configuration option. Existing configuration options are outlined in the sections below.

Example configuration proposal using a JavaScript policy:

{
  "actions": [
    {
      "name": "set_scitt_configuration",
      "args": {
        "configuration": {
          "policy": {
            "policyScript": "export function apply(phdr) { if (!phdr.cwt.iss) {return 'Issuer not found'} else if (phdr.cwt.iss !== 'did:x509:0:sha256:HnwZ4lezuxq/GVcl/Sk7YWW170qAD0DZBLXilXet0jg=::eku:1.3.6.1.4.1.311.10.3.13') { return 'Invalid issuer'; } return true; }"
          },
          "authentication": {
            "allowUnauthenticated": false,
            "jwt": {
              "requiredClaims": {
                "aud": "scitt",
                "iss": "https://authserver.com/",
                "http://unique.claim/department_id": "654987"
              }
            }
          }
        }
      }
    }
  ]
}

Example configuration proposal, using a Rego policy:

  "actions": [
    {
      "name": "set_scitt_configuration",
      "args": {
        "configuration": {
          "policy": {
            "policyRego": "\npackage policy\ndefault allow := false\nissuer_allowed if {\n    input.phdr[\"CWT Claims\"].iss == \"did:x509:0:sha256:HnwZ4lezuxq_GVcl_Sk7YWW170qAD0DZBLXilXet0jg::eku:1.3.6.1.4.1.311.10.3.13\"\n}\nseconds_since_epoch := time.now_ns() / 1000000000\niat_in_the_past if {\n    input.phdr[\"CWT Claims\"].iat < seconds_since_epoch\n}\nsvn_positive if {\n    input.phdr[\"CWT Claims\"]._svn >= 0\n}\nallow if {\n    issuer_allowed\n    iat_in_the_past\n    svn_positive\n}\n"
          },
          "authentication": {
            "allowUnauthenticated": false,
            "jwt": {
              "requiredClaims": {
                "aud": "scitt",
                "iss": "https://authserver.com/",
                "http://unique.claim/department_id": "654987"
              }
            }
          }
        }
      }
    }
  ]
}

SCITT API Authentication

API authentication can be turned off entirely or JWT authentication can be set up. Until a JWT provider is configured or API authentication is disabled, the initial configuration rejects all API requests as unauthorized.

Disabling API Authentication

If API authentication is disabled then requests won’t require any form of authentication. (Claims submitted via the API are still validated.)

Example set_scitt_configuration snippet:

"authentication": {
  "allowUnauthenticated": true
}

JWT API Authentication

If JWT authentication is enabled then API requests must include a header containing an acceptable JWT from a trusted identity provider. For more details see the CCF documentation on JWTs.

Extra requiredClaims can be configured which must then be present in an API request’s JWT for authentication to succeed.

To enable JWT authentication in SCITT, add the following config to a set_scitt_configuration action:

"authentication": {
  "allowUnauthenticated": false,
  "jwt": {
    "requiredClaims": {
      "foo": "bar",
    }
  }
}

Per-Endpoint Authentication (Write-Only JWT)

The optional allowUnauthenticatedReads field can widen unauthenticated access to selected SCITT retrieval endpoints while keeping statement registration protected by JWT. It does not override allowUnauthenticated when the service is already configured to allow unauthenticated access.

allowUnauthenticated allowUnauthenticatedReads Statement registration Selected retrieval endpoints
false not set or false JWT required JWT required
false true JWT required Unauthenticated access allowed
true any value Unauthenticated access allowed Unauthenticated access allowed

The selected retrieval endpoints are:

Example: require JWT for writes only, reads are open:

"authentication": {
  "allowUnauthenticated": false,
  "allowUnauthenticatedReads": true,
  "jwt": {
    "requiredClaims": {
      "aud": "https://mst-instance.confidential-ledger.azure.com",
      "iss": "https://login.microsoftonline.com/{tenant-id}/v2.0"
    }
  }
}

Service metadata endpoints remain publicly accessible regardless of authentication settings. These include /configuration, /version, /jwks, /.well-known/scitt-keys, /.well-known/scitt-keys/{kid_value}, and /.well-known/transparency-configuration.

Policy object

Accepted algorithms

List of accepted COSE signature algorithms when verifying signatures in submitted claims. If not set, the default accepted algorithms are shown in the example snippet below.

Example set_scitt_configuration snippet:

"acceptedAlgorithms": ["ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "EDDSA"]

Policy script

JS code that determines whether an entry should be accepted. Should export an apply function taking multiple arguments, and return true if the entry should be accepted or a string describing why the entry has failed the policy.

Policy scripts are executed by the CCF JavaScript runtime, which wraps and extends QuickJS. Most ES2023 features are [supported](https://test262.fyi/# qjs).

Example set_scitt_configuration snippet:

"policy": {
  "policyScript": "export function apply (phdr, uhdr, payload, details) { return true; }"
}

Function argument mapping takes place in scitt::js::protected_header_to_js_val().

Function arguments:

  1. protected_headers (Object) representation of the subset of COSE protected header parameters parsed by scitt-ccf-ledger

     {
       // Algorithm identifier (integer)
       alg?: number,
          
       // Critical headers array
       crit?: Array<number | string>,
          
       // Key ID
       kid?: string,
          
       // Issuer
       issuer?: string,
          
       // Feed
       feed?: string,
          
       // Issued at timestamp
       iat?: number,
          
       // Software version number
       svn?: number,
          
       // Content type (can be integer or string)
       cty?: number | string,
          
       // X.509 certificate chain (array of PEM strings)
       x5chain?: string[],
          
       // CWT Claims object
       cwt: {
         iss?: string,  // Issuer
         sub?: string,  // Subject
         iat?: number,  // Issued at
         svn?: number   // Software version number
       },
          
       // Microsoft Attested Service Map
       "attestedsvc": {
         svc_id?: string,
         attestation?: ArrayBuffer,
         attestation_type?: string,
            
         // COSE Key object
         cose_key?: {
           kty?: number,           // Key type
           crv?: number,           // Curve (for EC keys)
           n?: ArrayBuffer,        // Modulus (for RSA keys)
           x_e?: ArrayBuffer,      // X coordinate (EC) or exponent (RSA)
           y?: ArrayBuffer         // Y coordinate (EC only)
         },
            
         // SHA-256 hash of COSE key (hex string)
         cose_key_sha256?: string,
            
         snp_endorsements?: ArrayBuffer,
         uvm_endorsements?: ArrayBuffer,
         ver?: number  // Version
       }
     }
    
  2. unprotected_headers (Object) object representation of the subset of COSE unprotected header parameters parsed by scitt-ccf-ledger

     {
       // X.509 certificate chain (array of PEM strings)
       x5chain?: string[]
     }
    
  3. payload (ArrayBuffer)

     ArrayBuffer
    
  4. verified_sev_snp_details (Object) present when signature issuer is did:attestedsvc, details added after the signature and the attestation verification

     {
       // Empty object if no attestation details
       // OR if attestation details exist:
          
       // See https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf
       // Section 7.3 - Table 23 for the semantics and size of the following fields before their
       // encoding to hex string.
    
       // Measurement (hex string)
       measurement?: string,
          
       // Report data (hex string)
       report_data?: string,
    
       // Host data (hex string)
       host_data?: string,
    
       // Reported TCB
       // See https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf
       // Section 2.2 (TCB_VERSION) for the semantics and size of the following fields
       // Note that the fmc field is only present on Turin platforms
       reported_tcb?: {
         microcode: number,   // Lowest current patch level of all cores
         snp: number,         // Security Version Number (SVN) of SNP firmware
         tee: number,         // SVN of PSP operating system
         boot_loader: number, // SVN of PSP bootloader
         fmc?: number,        // SVN of FMC fw
         hexstring: string    // Combined hexstring representation of the fields listed above
                              // Often used as a compact representation in security advisories
       },
    
       // Product name of the architecture, computed from the CPUID_FAM_ID and CPUID_MOD_ID fields
       // one of "Milan", "Genoa", "Turin".
       product_name?: string,
          
       // See https://github.com/microsoft/confidential-aci-examples/blob/main/docs/Confidential_ACI_SCHEME.md#reference-info-base64
       // for additional detail on the UVM Endorsements object and reference values for the Confidential ACI platform
    
       // UVM Endorsements object
       uvm_endorsements?: {
         did: string,   // Decentralized identifier
         feed: string,  // Feed identifier
         svn: string    // Software version number
       }
     }
    

Policy Rego

Rego code that determines whether an entry should be accepted. The package must be called “policy”, and expose a rule called “allow” that must evaluate to true when the value of input is acceptable.

The package can also expose an “errors” rule that must evaluate to an array of strings. The values, if set, will be returned to the caller as error reasons. This allows easy incremental definition of error reasons, for example:

errors contains "Invalid parameter value" if { not parameter_is_valid }

Mapping from the Signed Statement to Rego input takes place in scitt::js::rego_input_from_signed_statement().

Attributes in input object:

  1. phdr (Object) representation of the subset of COSE protected header parameters parsed by scitt-ccf-ledger

     {
       // Algorithm identifier (integer)
       alg: number,
          
       // Content type (can be integer or string)
       cty?: number | string,
          
       // CWT Claims object
       "CWT Claims": {
         iss?: string,  // Issuer
         sub?: string,  // Subject
         iat?: number,  // Issued at
         _svn?: number   // Software version number
       }
     }
    
  2. payload (string)

     Hexadecimal representation
    
  3. attestation (Object) present when signature issuer is did:attestedsvc, details added after the signature and the attestation verification

     {
       // Empty object if no attestation details
       // OR if attestation details exist:
          
       // See https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf
       // Section 7.3 - Table 23 for the semantics and size of the following fields before their
       // encoding to hex string.
    
       // Measurement (hex string)
       measurement?: string,
          
       // Report data (hex string)
       report_data?: string,
    
       // Host data (hex string)
       host_data?: string,
    
       // Reported TCB
       // See https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf
       // Section 2.2 (TCB_VERSION) for the semantics and size of the following fields
       // Note that the fmc field is only present on Turin platforms
       reported_tcb?: {
         microcode: number,   // Lowest current patch level of all cores
         snp: number,         // Security Version Number (SVN) of SNP firmware
         tee: number,         // SVN of PSP operating system
         boot_loader: number, // SVN of PSP bootloader
         fmc?: number,        // SVN of FMC fw
         hexstring: string    // Combined hexstring representation of the fields listed above
                              // Often used as a compact representation in security advisories
       },
    
       // Product name of the architecture, computed from the CPUID_FAM_ID and CPUID_MOD_ID fields
       // one of "Milan", "Genoa", "Turin".
       product_name?: string,
          
       // See https://github.com/microsoft/confidential-aci-examples/blob/main/docs/Confidential_ACI_SCHEME.md#reference-info-base64
       // for additional detail on the UVM Endorsements object and reference values for the Confidential ACI platform
    
       // UVM Endorsements object
       uvm_endorsements?: {
         did: string,   // Decentralized identifier
         feed: string,  // Feed identifier
         svn: string    // Software version number
       }
     }
    

Example set_scitt_configuration snippet:

"policy": {
  "policyRego": "package policy\ndefault allow := false\n allow if input.profile == \"X509\""
}

It is possible to configure a policy execution limit, specified in number of rego statements:

"policy": {
  "policyRego": "...",
  "policyRegoStatementLimit": 2000
}

The default value is 10000, if registration policy execution exceeds the limit, it will be aborted and an error message returned.

Maximum signed statement size

By default, signed statements larger than 1MB (1,048,576 bytes) are rejected. This limit can be configured using the maxSignedStatementBytes parameter in the set_scitt_configuration action.

Example set_scitt_configuration snippet to allow signed statements up to 2MB:

"maxSignedStatementBytes": 2097152

The value must be a positive integer representing the maximum size in bytes. If not set, the default limit of 1MB is used.

This can also be used to restrict the maximum size to a value smaller than the default. For example, to limit signed statements to 512KB:

"maxSignedStatementBytes": 524288

CCF specific configuration

Please refer to the latest CCF configuration documentation to understand all of the possible options.

Ledger signature mode

The application selects the CCF ledger signature mode at link time through ccf::get_ledger_sign_mode(). This build uses CoseAllowDualJoin: nodes emit only COSE Sign1 ledger signatures while continuing to accept join requests from nodes using CCF’s default Dual mode. This supports the first phase of a rolling upgrade to COSE-only ledger signatures.

The mode is not part of the CCF node JSON configuration. Once every node in every ledger has been upgraded, change the callback to return CoseOnly and perform a second rolling upgrade. After COSE-only signatures have advanced beyond the latest traditional signature, the ledger cannot be recovered with a Dual binary; use a CoseAllowDualJoin or CoseOnly binary. See Upgrading to COSE-Only Ledger Signatures for the complete sequence.

Historical cache soft limit

The size of the historical state cache can be configured per node using the historical_cache_soft_limit option in the CCF node configuration. Once the soft limit is exceeded, least recently used states will be evicted from the cache.

Example node configuration snippet:

"historical_cache_soft_limit": "512MB"

Receipt issuance

Receipts can contain the issuer and subject fields identifying the service.

To use the specific values in the receipts please set it through the CCF v6 configuration:

"cose_signatures": {
  "issuer": "myservicedomain.com",
  "subject": "scitt.ccf.signature.v1"
}

Once the value is set, the public keys can be discoverd through the $issuer/.well-known/transparency-configuration endpoint.