diff --git a/.github/scripts/init-temp-keys.sh b/.github/scripts/init-temp-keys.sh index 4bb94ea76b..e9227c74d8 100755 --- a/.github/scripts/init-temp-keys.sh +++ b/.github/scripts/init-temp-keys.sh @@ -50,6 +50,9 @@ openssl rsa -in "$opt_output/kas-private.pem" -pubout -out "$opt_output/kas-cert openssl ecparam -name prime256v1 >ecparams.tmp openssl req -x509 -nodes -newkey ec:ecparams.tmp -subj "/CN=kas" -keyout "$opt_output/kas-ec-private.pem" -out "$opt_output/kas-ec-cert.pem" -days 365 +# Generate hybrid post-quantum key pairs (X-Wing, P256+ML-KEM-768, P384+ML-KEM-1024) +go run ./service/cmd/keygen -output "$opt_output" + mkdir -p keys openssl req -x509 -nodes -newkey RSA:2048 -subj "/CN=ca" -keyout keys/keycloak-ca-private.pem -out keys/keycloak-ca.pem -days 365 printf "subjectAltName=DNS:localhost,IP:127.0.0.1" >keys/sanX509.conf diff --git a/.github/scripts/watch.sh b/.github/scripts/watch.sh index defedcd798..acde7b6b75 100755 --- a/.github/scripts/watch.sh +++ b/.github/scripts/watch.sh @@ -53,18 +53,54 @@ done file_to_watch="$1" shift +file_signature() { + if [[ ! -e "$1" ]]; then + echo "missing" + return + fi + + if stat -c '%i:%s:%Y' "$1" >/dev/null 2>&1; then + stat -c '%i:%s:%Y' "$1" + return + fi + + stat -f '%i:%z:%m' "$1" +} + wait_for_change_to() { - if which inotifywait; then - echo "[INFO] inotifywaiting to [${file_to_watch}]" - inotifywait -e modify -e move -e create -e delete -e attrib -r "${file_to_watch}" + if command -v inotifywait >/dev/null 2>&1; then + local watch_dir + local watch_name + local changed_file + + watch_dir=$(dirname "${file_to_watch}") + watch_name=$(basename "${file_to_watch}") + + echo "[INFO] inotifywaiting to [${file_to_watch}] via [${watch_dir}]" + while true; do + changed_file=$(inotifywait -q \ + -e close_write \ + -e moved_to \ + -e delete \ + -e attrib \ + --format '%f' \ + "${watch_dir}") + + if [[ "${changed_file}" == "${watch_name}" ]]; then + return + fi + done else - m=$(date -r "${file_to_watch}" +%s) + local m + local n + + m=$(file_signature "${file_to_watch}") echo "[INFO] stat checking [${file_to_watch}] from [${m}]" while true; do sleep 1 - n=$(date -r "${file_to_watch}" +%s) - echo "[INFO] stat checking [${file_to_watch}] from [${m} < ${n}]" - if [[ $m < $n ]]; then + n=$(file_signature "${file_to_watch}") + echo "[INFO] stat checking [${file_to_watch}] from [${m} != ${n}]" + if [[ "${m}" != "${n}" ]]; then return fi done diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 237a7ab46d..5ed9bd038c 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -598,7 +598,7 @@ jobs: - uses: bufbuild/buf-setup-action@a47c93e0b1648d5651a065437926377d060baa99 # v1.50.0 with: github_token: ${{ github.token }} - version: "1.68.2" + version: "1.68.3" - uses: bufbuild/buf-lint-action@06f9dd823d873146471cfaaf108a993fe00e5325 # v1.1.1 with: input: service diff --git a/.gitignore b/.gitignore index 4b8411c9f6..7d89ba5ac6 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ opentdf.yaml tmp-gen/ /examples/examples /**/kas-*.pem +!/service/pkg/server/testdata/kas-*.pem /opentdf /sdkjava/target /serviceapp diff --git a/Makefile b/Makefile index 404584dcb6..d2b8023e80 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ .PHONY: all buf-check build clean connect-wrapper-generate docker-build fix fmt go-lint license lint otdfctl/otdfctl policy-sql-gen proto-generate proto-helper-generate proto-lint sdk/sdk sqlc-check test tidy toolcheck -MODS=protocol/go lib/ocrypto lib/fixtures lib/flattening lib/identifier sdk service examples otdfctl -HAND_MODS=lib/ocrypto lib/fixtures lib/flattening lib/identifier sdk service examples otdfctl +MODS=protocol/go lib/ocrypto lib/fixtures lib/flattening lib/identifier sdk service examples otdfctl tests-bdd +HAND_MODS=lib/ocrypto lib/fixtures lib/flattening lib/identifier sdk service examples otdfctl tests-bdd REQUIRED_BUF_VERSION=1.68.2 REQUIRED_SQLC_VERSION=1.31.0 diff --git a/docs/grpc/index.html b/docs/grpc/index.html index c1bfdab139..a59d73dd56 100644 --- a/docs/grpc/index.html +++ b/docs/grpc/index.html @@ -4116,6 +4116,24 @@

Algorithm

+ + ALGORITHM_HPQT_XWING + 6 +

+ + + + ALGORITHM_HPQT_SECP256R1_MLKEM768 + 7 +

+ + + + ALGORITHM_HPQT_SECP384R1_MLKEM1024 + 8 +

+ + @@ -4227,6 +4245,24 @@

KasPublicKeyAlgEnum

+ + KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + 10 +

+ + + + KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + 11 +

+ + + + KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + 12 +

+ + @@ -6279,7 +6315,7 @@

KeyAccess

Type of key wrapping used for the data encryption key Required: Always -Values: 'wrapped' (RSA-wrapped for ZTDF), 'ec-wrapped' (experimental ECDH-wrapped)

+Values: 'wrapped' (RSA-wrapped for ZTDF), 'ec-wrapped' (experimental ECDH-wrapped), 'hybrid-wrapped' (experimental X-Wing-wrapped)

@@ -6336,7 +6372,7 @@

KeyAccess

Ephemeral public key for ECDH key derivation (ec-wrapped type only) Required: When key_type="ec-wrapped" (experimental ECDH-based ZTDF) -Omitted: When key_type="wrapped" (RSA-based ZTDF) +Omitted: When key_type="wrapped" or key_type="hybrid-wrapped" Should be a PEM-encoded PKCS#8 (ASN.1) formatted public key Used to derive the symmetric key for unwrapping the DEK

@@ -6664,7 +6700,7 @@

RewrapResponse

KAS's ephemeral session public key in PEM format Required: For EC-based operations (key_type="ec-wrapped") -Optional: Empty for RSA-based ZTDF (key_type="wrapped") +Optional: Empty for RSA-based or X-Wing-based ZTDF (key_type="wrapped" or key_type="hybrid-wrapped") Used by client to perform ECDH key agreement and decrypt the kas_wrapped_key values

diff --git a/docs/openapi/authorization/authorization.openapi.yaml b/docs/openapi/authorization/authorization.openapi.yaml index dd0cb366aa..5a112b61b6 100644 --- a/docs/openapi/authorization/authorization.openapi.yaml +++ b/docs/openapi/authorization/authorization.openapi.yaml @@ -140,6 +140,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.KasPublicKeyAlgEnum: type: string @@ -151,6 +154,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.SourceType: type: string title: SourceType @@ -186,6 +192,7 @@ components: Example Request Get Decisions to answer the question - Do Bob (represented by entity chain ec1) and Alice (represented by entity chain ec2) have TRANSMIT authorization for 2 resources; resource1 (attr-set-1) defined by attributes foo:bar resource2 (attr-set-2) defined by attribute foo:bar, color:red ? + { "actions": [ { @@ -257,11 +264,13 @@ components: Example response for a Decision Request - Do Bob (represented by entity chain ec1) and Alice (represented by entity chain ec2) have TRANSMIT authorization for 2 resources; resource1 (attr-set-1) defined by attributes foo:bar resource2 (attr-set-2) defined by attribute foo:bar, color:red ? + Results: - bob has permitted authorization to transmit for a resource defined by attr-set-1 attributes and has a watermark obligation - bob has denied authorization to transmit a for a resource defined by attr-set-2 attributes - alice has permitted authorization to transmit for a resource defined by attr-set-1 attributes - alice has denied authorization to transmit a for a resource defined by attr-set-2 attributes + { "entityChainId": "ec1", "resourceAttributesId": "attr-set-1", @@ -449,7 +458,9 @@ components: additionalProperties: false description: |- Request to get entitlements for one or more entities for an optional attribute scope + Example: Get entitlements for bob and alice (both represented using an email address + { "entities": [ { @@ -480,6 +491,7 @@ components: additionalProperties: false description: |- Example Response for a request of : Get entitlements for bob and alice (both represented using an email address + { "entitlements": [ { @@ -551,6 +563,7 @@ components: Example Request Get Decisions by Token to answer the question - Do Bob and client1 (represented by token tok1) and Alice and client2 (represented by token tok2) have TRANSMIT authorization for 2 resources; resource1 (attr-set-1) defined by attributes foo:bar resource2 (attr-set-2) defined by attribute foo:bar, color:red ? + { "actions": [ { diff --git a/docs/openapi/authorization/v2/authorization.openapi.yaml b/docs/openapi/authorization/v2/authorization.openapi.yaml index 0f4f92d55f..7e9d837275 100644 --- a/docs/openapi/authorization/v2/authorization.openapi.yaml +++ b/docs/openapi/authorization/v2/authorization.openapi.yaml @@ -175,6 +175,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.KasPublicKeyAlgEnum: type: string @@ -186,6 +189,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.SourceType: type: string title: SourceType diff --git a/docs/openapi/kas/kas.openapi.yaml b/docs/openapi/kas/kas.openapi.yaml index 2ba076acc5..a357028153 100644 --- a/docs/openapi/kas/kas.openapi.yaml +++ b/docs/openapi/kas/kas.openapi.yaml @@ -235,7 +235,7 @@ components: description: |- Type of key wrapping used for the data encryption key Required: Always - Values: 'wrapped' (RSA-wrapped for ZTDF), 'ec-wrapped' (experimental ECDH-wrapped) + Values: 'wrapped' (RSA-wrapped for ZTDF), 'ec-wrapped' (experimental ECDH-wrapped), 'hybrid-wrapped' (experimental X-Wing-wrapped) url: type: string title: kas_url @@ -282,7 +282,7 @@ components: description: |- Ephemeral public key for ECDH key derivation (ec-wrapped type only) Required: When key_type="ec-wrapped" (experimental ECDH-based ZTDF) - Omitted: When key_type="wrapped" (RSA-based ZTDF) + Omitted: When key_type="wrapped" or key_type="hybrid-wrapped" Should be a PEM-encoded PKCS#8 (ASN.1) formatted public key Used to derive the symmetric key for unwrapping the DEK title: KeyAccess @@ -472,7 +472,7 @@ components: description: |- KAS's ephemeral session public key in PEM format Required: For EC-based operations (key_type="ec-wrapped") - Optional: Empty for RSA-based ZTDF (key_type="wrapped") + Optional: Empty for RSA-based or X-Wing-based ZTDF (key_type="wrapped" or key_type="hybrid-wrapped") Used by client to perform ECDH key agreement and decrypt the kas_wrapped_key values schemaVersion: type: string diff --git a/docs/openapi/policy/actions/actions.openapi.yaml b/docs/openapi/policy/actions/actions.openapi.yaml index 4f4681fc77..db1d5d2a32 100644 --- a/docs/openapi/policy/actions/actions.openapi.yaml +++ b/docs/openapi/policy/actions/actions.openapi.yaml @@ -203,6 +203,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -229,6 +232,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.SourceType: type: string title: SourceType diff --git a/docs/openapi/policy/attributes/attributes.openapi.yaml b/docs/openapi/policy/attributes/attributes.openapi.yaml index 5e208ad63b..1eb291ec16 100644 --- a/docs/openapi/policy/attributes/attributes.openapi.yaml +++ b/docs/openapi/policy/attributes/attributes.openapi.yaml @@ -722,6 +722,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -748,6 +751,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.SortDirection: type: string title: SortDirection diff --git a/docs/openapi/policy/kasregistry/key_access_server_registry.openapi.yaml b/docs/openapi/policy/kasregistry/key_access_server_registry.openapi.yaml index e5ea9f456e..a3eb12f037 100644 --- a/docs/openapi/policy/kasregistry/key_access_server_registry.openapi.yaml +++ b/docs/openapi/policy/kasregistry/key_access_server_registry.openapi.yaml @@ -523,6 +523,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.KasPublicKeyAlgEnum: type: string @@ -534,6 +537,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.KeyMode: type: string title: KeyMode @@ -1165,7 +1171,7 @@ components: Required The algorithm to be used for the key The key_algorithm must be one of the defined values.: ``` - this in [1, 2, 3, 4, 5] + this in [1, 2, 3, 4, 5, 6, 7, 8] ``` $ref: '#/components/schemas/policy.Algorithm' @@ -1722,7 +1728,7 @@ components: Filter keys by algorithm The key_algorithm must be one of the defined values.: ``` - this in [0, 1, 2, 3, 4, 5] + this in [0, 1, 2, 3, 4, 5, 6, 7, 8] ``` $ref: '#/components/schemas/policy.Algorithm' @@ -1996,7 +2002,7 @@ components: Required The key_algorithm must be one of the defined values.: ``` - this in [1, 2, 3, 4, 5] + this in [1, 2, 3, 4, 5, 6, 7, 8] ``` $ref: '#/components/schemas/policy.Algorithm' diff --git a/docs/openapi/policy/namespaces/namespaces.openapi.yaml b/docs/openapi/policy/namespaces/namespaces.openapi.yaml index 25f2b14fa1..191af3eaf6 100644 --- a/docs/openapi/policy/namespaces/namespaces.openapi.yaml +++ b/docs/openapi/policy/namespaces/namespaces.openapi.yaml @@ -353,6 +353,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.KasPublicKeyAlgEnum: type: string @@ -364,6 +367,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.SortDirection: type: string title: SortDirection diff --git a/docs/openapi/policy/objects.openapi.yaml b/docs/openapi/policy/objects.openapi.yaml index ba1c5e3f6f..9c72e91e30 100644 --- a/docs/openapi/policy/objects.openapi.yaml +++ b/docs/openapi/policy/objects.openapi.yaml @@ -21,6 +21,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -47,6 +50,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.KeyMode: type: string title: KeyMode @@ -987,6 +993,7 @@ components: authoritative source such as an IDP (Identity Provider) or User Store. Examples include such ADFS/LDAP, OKTA, etc. For now, a valid property must contain both a selector expression & a resulting value. + The external_selector_value is a specifier to select a value from a flattened external representation of an Entity (such as from idP/LDAP), and the external_value is the value selected by the external_selector_value on that diff --git a/docs/openapi/policy/obligations/obligations.openapi.yaml b/docs/openapi/policy/obligations/obligations.openapi.yaml index 1780201216..154701d935 100644 --- a/docs/openapi/policy/obligations/obligations.openapi.yaml +++ b/docs/openapi/policy/obligations/obligations.openapi.yaml @@ -553,6 +553,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -579,6 +582,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.SortDirection: type: string title: SortDirection diff --git a/docs/openapi/policy/registeredresources/registered_resources.openapi.yaml b/docs/openapi/policy/registeredresources/registered_resources.openapi.yaml index 1a7db3d364..9c761fa78d 100644 --- a/docs/openapi/policy/registeredresources/registered_resources.openapi.yaml +++ b/docs/openapi/policy/registeredresources/registered_resources.openapi.yaml @@ -413,6 +413,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -439,6 +442,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.SortDirection: type: string title: SortDirection diff --git a/docs/openapi/policy/resourcemapping/resource_mapping.openapi.yaml b/docs/openapi/policy/resourcemapping/resource_mapping.openapi.yaml index 8fb2f4e53f..c49765740a 100644 --- a/docs/openapi/policy/resourcemapping/resource_mapping.openapi.yaml +++ b/docs/openapi/policy/resourcemapping/resource_mapping.openapi.yaml @@ -413,6 +413,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -439,6 +442,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.SourceType: type: string title: SourceType diff --git a/docs/openapi/policy/subjectmapping/subject_mapping.openapi.yaml b/docs/openapi/policy/subjectmapping/subject_mapping.openapi.yaml index bf25db5752..1608ae7962 100644 --- a/docs/openapi/policy/subjectmapping/subject_mapping.openapi.yaml +++ b/docs/openapi/policy/subjectmapping/subject_mapping.openapi.yaml @@ -449,6 +449,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -475,6 +478,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.SortDirection: type: string title: SortDirection @@ -1291,6 +1297,7 @@ components: authoritative source such as an IDP (Identity Provider) or User Store. Examples include such ADFS/LDAP, OKTA, etc. For now, a valid property must contain both a selector expression & a resulting value. + The external_selector_value is a specifier to select a value from a flattened external representation of an Entity (such as from idP/LDAP), and the external_value is the value selected by the external_selector_value on that diff --git a/docs/openapi/policy/unsafe/unsafe.openapi.yaml b/docs/openapi/policy/unsafe/unsafe.openapi.yaml index 17c9652a41..279b0154a6 100644 --- a/docs/openapi/policy/unsafe/unsafe.openapi.yaml +++ b/docs/openapi/policy/unsafe/unsafe.openapi.yaml @@ -387,6 +387,9 @@ components: - ALGORITHM_EC_P256 - ALGORITHM_EC_P384 - ALGORITHM_EC_P521 + - ALGORITHM_HPQT_XWING + - ALGORITHM_HPQT_SECP256R1_MLKEM768 + - ALGORITHM_HPQT_SECP384R1_MLKEM1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -413,6 +416,9 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 - KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 policy.KeyMode: type: string title: KeyMode diff --git a/examples/cmd/decrypt.go b/examples/cmd/decrypt.go index e66878ec9c..11279c7999 100644 --- a/examples/cmd/decrypt.go +++ b/examples/cmd/decrypt.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" + "github.com/opentdf/platform/lib/ocrypto" "github.com/opentdf/platform/sdk" "github.com/spf13/cobra" @@ -50,7 +51,7 @@ func decrypt(cmd *cobra.Command, args []string) error { } opts := []sdk.TDFReaderOption{} if alg != "" { - kt, err := keyTypeForKeyType(alg) + kt, err := ocrypto.ParseKeyType(alg) if err != nil { return err } @@ -79,7 +80,7 @@ func decrypt(cmd *cobra.Command, args []string) error { opts := []sdk.TDFReaderOption{} if alg != "" { - kt, err := keyTypeForKeyType(alg) + kt, err := ocrypto.ParseKeyType(alg) if err != nil { return err } diff --git a/examples/cmd/encrypt.go b/examples/cmd/encrypt.go index 4b8adc9eb4..d6574335e1 100644 --- a/examples/cmd/encrypt.go +++ b/examples/cmd/encrypt.go @@ -2,7 +2,6 @@ package cmd import ( "encoding/json" - "fmt" "os" "strings" @@ -73,7 +72,7 @@ func encrypt(cmd *cobra.Command, args []string) error { })) } if alg != "" { - kt, err := keyTypeForKeyType(alg) + kt, err := ocrypto.ParseKeyType(alg) if err != nil { return err } @@ -91,15 +90,3 @@ func encrypt(cmd *cobra.Command, args []string) error { cmd.Println(string(manifestJSON)) return nil } - -func keyTypeForKeyType(alg string) (ocrypto.KeyType, error) { - switch alg { - case string(ocrypto.RSA2048Key): - return ocrypto.RSA2048Key, nil - case string(ocrypto.EC256Key): - return ocrypto.EC256Key, nil - default: - // do not submit add ocrypto.UnknownKey - return ocrypto.RSA2048Key, fmt.Errorf("unsupported key type [%s]", alg) - } -} diff --git a/lib/ocrypto/BENCHMARK_REPORT.md b/lib/ocrypto/BENCHMARK_REPORT.md new file mode 100644 index 0000000000..8c01339a51 --- /dev/null +++ b/lib/ocrypto/BENCHMARK_REPORT.md @@ -0,0 +1,168 @@ +# Benchmark Report: Hybrid Post-Quantum Key Wrapping Performance + +**Platform:** Apple M4, darwin/arm64, Go 1.25.9 +**Date:** 2026-04-29 +**Methodology:** `go test -bench=. -benchmem -count=5` (median of 5 runs) + +> **Note:** Wrap and unwrap benchmarks mirror the actual TDF code paths: +> - **Wrap** follows `sdk/tdf.go` (`generateWrapKeyWithRSA`, `generateWrapKeyWithEC`, `generateWrapKeyWithHybrid`) +> - **Unwrap** follows `service/internal/security/standard_crypto.go:Decrypt()` +> +> This includes PEM parsing, ephemeral keygen, ECDH, HKDF, AES-GCM, and ASN.1 marshaling — not simplified library-level `WrapDEK()` / `UnwrapDEK()` calls. + +## How to Run + +```bash +# Full benchmark suite (use -count=5 for statistical significance) +cd lib/ocrypto && go test -bench=. -benchmem -count=5 -timeout=10m + +# Quick single-count run +cd lib/ocrypto && go test -bench=. -benchmem -count=1 -timeout=5m + +# Specific benchmark groups +cd lib/ocrypto && go test -bench=BenchmarkKeyGeneration -benchmem +cd lib/ocrypto && go test -bench=BenchmarkWrapDEK -benchmem +cd lib/ocrypto && go test -bench=BenchmarkUnwrapDEK -benchmem +cd lib/ocrypto && go test -bench=BenchmarkHybridSubOps -benchmem + +# Wrapped key size comparison table +cd lib/ocrypto && go test -v -run TestWrappedKeySizeComparison +``` + +## Results + +### Key Generation + +| Scheme | Time | B/op | allocs/op | vs EC P-256 | +|--------|-----:|-----:|----------:|-------------| +| RSA-2048 | 47.7 ms | 652 KB | 5,929 | ~6,400x slower | +| EC P-256 | 7.4 us | 984 B | 16 | baseline | +| EC P-384 | 71.3 us | 1.2 KB | 19 | ~9.6x slower | +| X-Wing | 43.8 us | 9.8 KB | 9 | ~5.9x slower | +| P256+ML-KEM-768 | 34.8 us | 11.4 KB | 13 | ~4.7x slower | +| P384+ML-KEM-1024 | 113.8 us | 17.9 KB | 16 | ~15x slower | + +**Takeaway:** RSA-2048 key generation is orders of magnitude slower than everything else (~48ms). All hybrid schemes generate keys in under 115us. EC P-256 is fastest at ~7us; EC P-384 keygen is ~10x slower than P-256 due to the larger field size. + +### Wrap DEK (32-byte AES-256 key) + +These benchmarks follow the exact TDF wrapping paths: +- **RSA:** `FromPublicPEM` -> `Encrypt` (OAEP) +- **EC:** `NewECKeyPair` -> `ComputeECDHKey` -> `CalculateHKDF` -> `AES-GCM Encrypt` +- **Hybrid:** `PubKeyFromPem` -> `Encapsulate` -> `CalculateHKDF` -> `AES-GCM Encrypt` -> `ASN.1 Marshal` + +| Scheme | Time | Wrapped Size | B/op | allocs/op | vs EC P-256 | +|--------|-----:|-------------:|-----:|----------:|-------------| +| RSA-2048 | 25.5 us | 256 B | 4.1 KB | 33 | 0.5x (faster) | +| EC P-256 | 54.5 us | 60 B | 12.0 KB | 158 | baseline | +| EC P-384 | 449.3 us | 60 B | 14.3 KB | 189 | ~8.2x slower | +| X-Wing | 77.4 us | 1,190 B | 16.4 KB | 42 | ~1.4x slower | +| P256+ML-KEM-768 | 75.2 us | 1,223 B | 18.7 KB | 59 | ~1.4x slower | +| P384+ML-KEM-1024 | 369.9 us | 1,735 B | 27.0 KB | 68 | ~6.8x slower | + +**Takeaway:** P256+ML-KEM-768 wrapping (~75us) is only ~1.4x slower than EC P-256 (~55us) — the ephemeral EC keygen + ECDH in the EC path narrows the gap significantly. RSA wrap is fastest since it's just OAEP padding. The two P-384-based schemes are the slowest (EC P-384 ~449us, P384+ML-KEM-1024 ~370us) — the P-384 ECDH operation alone dominates EC P-384's wrap cost since each call re-generates an ephemeral key. + +### Unwrap DEK + +These benchmarks follow the KAS unwrap paths: +- **RSA:** pre-loaded `AsymDecryption.Decrypt` (key parsed at startup) +- **EC:** `NewSaltedECDecryptor(cachedKey, TDFSalt)` -> `DecryptWithEphemeralKey` +- **Hybrid:** `PrivateKeyFromPem` -> `UnwrapDEK` (PEM parsed each call) + +| Scheme | Time | B/op | allocs/op | vs EC P-256 | +|--------|-----:|-----:|----------:|-------------| +| RSA-2048 | 737.3 us | 560 B | 8 | ~26x slower | +| EC P-256 | 28.4 us | 4.1 KB | 40 | baseline | +| EC P-384 | 230.5 us | 4.6 KB | 55 | ~8.1x slower | +| X-Wing | 90.4 us | 12.4 KB | 37 | ~3.2x slower | +| P256+ML-KEM-768 | 96.3 us | 13.8 KB | 51 | ~3.4x slower | +| P384+ML-KEM-1024 | 400.2 us | 20.1 KB | 60 | ~14x slower | + +**Takeaway:** RSA unwrap is the slowest operation in the entire suite (~737us) due to private key exponentiation. P256+ML-KEM-768 unwraps in ~96us — fast enough for real-time use. EC P-384 unwrap (~231us) is ~8x slower than P-256 because of the more expensive curve operations. Hybrid unwraps include PEM parsing overhead that could be optimized by caching parsed keys (as EC already does). + +### Wrap + Unwrap Round-Trip Summary + +| Scheme | Wrap + Unwrap | Quantum Safe? | +|--------|-------------:|:-------------:| +| RSA-2048 | 763 us | No | +| EC P-256 | 83 us | No | +| EC P-384 | 680 us | No | +| X-Wing | 168 us | Yes | +| P256+ML-KEM-768 | 172 us | Yes | +| P384+ML-KEM-1024 | 770 us | Yes | + +## Analysis: Where Time Is Spent + +The `BenchmarkHybridSubOps` benchmarks break down hybrid wrap operations into their constituent parts: + +### X-Wing Sub-Operations + +| Operation | Time | % of Wrap | +|-----------|-----:|----------:| +| Encapsulate (X25519 + ML-KEM-768) | 71.6 us | 92.5% | +| HKDF key derivation | 0.49 us | 0.6% | +| AES-GCM encrypt (32B DEK) | 0.37 us | 0.5% | +| ASN.1 marshal | 0.52 us | 0.7% | +| PEM parsing + overhead | ~4.4 us | 5.7% | + +### P256+ML-KEM-768 Sub-Operations + +| Operation | Time | % of Wrap | +|-----------|-----:|----------:| +| Encapsulate (ECDH P-256 + ML-KEM-768) | 70.0 us | 93.1% | +| HKDF key derivation | 0.51 us | 0.7% | +| AES-GCM encrypt (32B DEK) | 0.37 us | 0.5% | +| ASN.1 marshal | 0.51 us | 0.7% | +| PEM parsing + overhead | ~3.8 us | 5.1% | + +### P384+ML-KEM-1024 Sub-Operations + +| Operation | Time | % of Wrap | +|-----------|-----:|----------:| +| Encapsulate (ECDH P-384 + ML-KEM-1024) | 359.9 us | 97.3% | +| HKDF key derivation | 0.51 us | 0.1% | +| AES-GCM encrypt (32B DEK) | 0.37 us | 0.1% | +| ASN.1 marshal | 0.54 us | 0.1% | +| PEM parsing + overhead | ~8.6 us | 2.3% | + +**Conclusion:** KEM encapsulation dominates all hybrid schemes at 93-97% of total time. HKDF, AES-GCM, and ASN.1 marshaling are all sub-microsecond and negligible. The P-384 elliptic curve ECDH is ~5x slower than P-256, which is why P384+ML-KEM-1024 is significantly slower than P256+ML-KEM-768. + +## Manifest Size Impact + +| Scheme | Wrapped Key | Public Key (PEM) | Base64 Wrapped | Notes | +|--------|------------:|-----------------:|---------------:|-------| +| RSA-2048 | 256 B | 451 B | 344 B | No ephemeral key in manifest | +| EC P-256 | 60 B | 178 B | 80 B | + ephemeral key (91 B) in manifest | +| EC P-384 | 60 B | 215 B | 80 B | + ephemeral key (120 B) in manifest | +| X-Wing | 1,190 B | 1,714 B | 1,588 B | All in single ASN.1 blob | +| P256+ML-KEM-768 | 1,223 B | 1,785 B | 1,632 B | All in single ASN.1 blob | +| P384+ML-KEM-1024 | 1,735 B | 2,347 B | 2,316 B | All in single ASN.1 blob | + +> Base64 overhead = ceil(raw_bytes * 4/3). TDF manifests store wrapped keys as base64. + +Hybrid schemes produce wrapped keys that are ~20x larger than EC P-256 (1.2-1.7 KB vs 60 B). For a TDF with a single recipient, this adds ~1-2 KB to the manifest. For multi-recipient TDFs, the overhead scales linearly per recipient. + +## Trade-offs Summary + +| Concern | RSA-2048 | EC P-256 | EC P-384 | X-Wing | P256+ML-KEM-768 | P384+ML-KEM-1024 | +|---------|----------|----------|----------|--------|-----------------|-------------------| +| Quantum resistance | None | None | None | Yes (hybrid) | Yes (hybrid) | Yes (hybrid) | +| Key generation | 48 ms (slow) | 7.4 us (fastest) | 71 us | 44 us | 35 us | 114 us | +| Wrap latency | 26 us | 55 us | 449 us | 77 us | 75 us | 370 us | +| Unwrap latency | 737 us (slow) | 28 us | 231 us | 90 us | 96 us | 400 us | +| Round-trip | 763 us | 83 us | 680 us | 168 us | 172 us | 770 us | +| Wrapped key size | 256 B | 60 B | 60 B | 1,190 B | 1,223 B | 1,735 B | +| Standards basis | PKCS#1 | ECIES | ECIES | IETF draft | NIST SP 800-227 | NIST SP 800-227 | + +### Recommendations + +- **P256+ML-KEM-768** is the best all-around hybrid choice: NIST-standardized, fastest hybrid round-trip (~172us), and moderate size overhead (1.2 KB wrapped keys). Only ~1.4x slower than EC P-256 for wrapping. +- **P384+ML-KEM-1024** provides a higher classical security level (Cat 3 classical / Cat 5 PQ) at the cost of ~4-5x more latency. Use when policy requires P-384 or equivalent classical strength. +- **X-Wing** offers a simpler construction (X25519 + ML-KEM-768) but is based on an IETF draft rather than a NIST standard. Performance is comparable to P256+ML-KEM-768. +- **EC P-256** remains the fastest and smallest option for environments where quantum resistance is not yet required. +- **EC P-384** is significantly more expensive than P-256 (~8-10x for both wrap and unwrap) without quantum protection — prefer P384+ML-KEM-1024 if the latency budget already covers P-384, since it adds PQ resistance for similar cost. +- **RSA-2048** has the worst unwrap performance (~737us) and should be considered legacy. + +### Optimization Opportunities + +- **Hybrid unwrap PEM caching:** The KAS currently parses hybrid private key PEM on every unwrap call. Caching the parsed key (as EC already does) would save ~5-10us per unwrap. diff --git a/lib/ocrypto/HYBRID_NIST_KEY_WRAPPING.md b/lib/ocrypto/HYBRID_NIST_KEY_WRAPPING.md new file mode 100644 index 0000000000..e6cbf4fe74 --- /dev/null +++ b/lib/ocrypto/HYBRID_NIST_KEY_WRAPPING.md @@ -0,0 +1,308 @@ +# NIST EC + ML-KEM Hybrid Key Wrapping + +## Overview + +This document describes the hybrid post-quantum key wrapping scheme used in TDF (Trusted Data Format) that combines classical elliptic curve cryptography (ECDH) with post-quantum lattice-based cryptography (ML-KEM) to protect data encryption keys (split keys). + +Two variants are supported. Hybrid security is bounded by the stronger of the two underlying primitives against each adversary class, so the post-quantum strength is set by ML-KEM and the classical strength is set by ECDH. + +| Variant | Classical (ECDH) | Post-quantum (ML-KEM) | +|---------|------------------|-----------------------| +| P-256 + ML-KEM-768 | NIST Category 1 | NIST Category 3 | +| P-384 + ML-KEM-1024 | NIST Category 3 | NIST Category 5 | + +References: +- NIST PQC Call for Proposals §4.A.5 (category definitions): https://csrc.nist.gov/csrc/media/projects/post-quantum-cryptography/documents/call-for-proposals-final-dec-2016.pdf +- FIPS 203 (ML-KEM-768 = Cat 3, ML-KEM-1024 = Cat 5): https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.203.pdf +- NIST SP 800-57 Part 1 Rev. 5 Table 2 (P-256 = 128-bit ≈ Cat 1, P-384 = 192-bit ≈ Cat 3): https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-57pt1r5.pdf + +Core implementation: `lib/ocrypto/hybrid_nist.go` + +## Key Format + +### Combined Public Key + +The KAS (Key Access Server) hosts a combined public key in PEM format. The raw bytes inside the PEM are a simple concatenation of the EC and ML-KEM public keys: + +``` +[ EC Public Key (uncompressed point) | ML-KEM Public Key ] +``` + +| Variant | EC Public Key Size | ML-KEM Public Key Size | Combined Size | PEM Block Type | +|---------|-------------------|----------------------|---------------|----------------| +| P-256 + ML-KEM-768 | 65 bytes | 1184 bytes | 1249 bytes | `SECP256R1 MLKEM768 PUBLIC KEY` | +| P-384 + ML-KEM-1024 | 97 bytes | 1568 bytes | 1665 bytes | `SECP384R1 MLKEM1024 PUBLIC KEY` | + +### Combined Private Key + +The KAS holds a combined private key, also a concatenation: + +``` +[ EC Private Key (raw scalar) | ML-KEM Private Key ] +``` + +The ML-KEM portion is stored in the 64-byte seed form (`d || z`) defined by FIPS 203 §7.1, not the expanded ~2400/3168-byte decapsulation key. The seed is what `crypto/mlkem` (Go 1.25) emits via `Bytes()` and consumes via `NewDecapsulationKey768` / `NewDecapsulationKey1024`. The constant `mlkemSeedSize = 64` in `hybrid_nist.go` and the size checks in `decodeSizedPEMBlock` enforce this layout. + +| Variant | EC Private Key Size | ML-KEM Seed Size | Combined Size | PEM Block Type | +|---------|--------------------|------------------|---------------|----------------| +| P-256 + ML-KEM-768 | 32 bytes | 64 bytes | 96 bytes | `SECP256R1 MLKEM768 PRIVATE KEY` | +| P-384 + ML-KEM-1024 | 48 bytes | 64 bytes | 112 bytes | `SECP384R1 MLKEM1024 PRIVATE KEY` | + +### How the Client Obtains the Public Key + +The client obtains the combined public key from KAS in one of two ways: + +1. **Fetched at runtime (autoconfigure)**: The SDK calls the KAS `PublicKey` gRPC endpoint, specifying the hybrid algorithm. KAS returns the combined PEM. The response is cached for 5 minutes. +2. **Provided manually**: The caller supplies the public key PEM via `WithKasInformation(...)` when configuring the SDK. + +## Wrap (Encrypt the Split Key) + +Function: `hybridNISTWrapDEK` (`hybrid_nist.go`, line 339) + +This is performed on the **client side** during TDF encryption. + +### Step 1 - Split the Public Key + +The combined public key bytes are split at the known EC public key size boundary: + +``` +ecPubBytes = publicKeyRaw[:ecPubSize] // 65 bytes for P-256, 97 bytes for P-384 +mlkemPubBytes = publicKeyRaw[ecPubSize:] // 1184 bytes for ML-KEM-768, 1568 bytes for ML-KEM-1024 +``` + +### Step 2 - ECDH (Classical Key Agreement) + +An ephemeral EC key pair is generated on the same curve as the KAS static key: + +1. Generate ephemeral EC key pair: `ephemeral_private, ephemeral_public` +2. Compute ECDH shared secret: `ecdhSecret = ECDH(ephemeral_private, KAS_ec_public)` +3. Retain `ephemeral_public` bytes for inclusion in the output + +This is a standard elliptic curve Diffie-Hellman operation. The ephemeral key provides forward secrecy - even if the KAS static key is later compromised, past wrapped keys remain protected. + +### Step 3 - ML-KEM Encapsulate (Post-Quantum KEM) + +ML-KEM (Module Lattice-based Key Encapsulation Mechanism, formerly known as Kyber) is a KEM, not a key exchange. The encapsulation operation takes only the public key and produces two outputs: + +``` +(mlkemSecret, mlkemCiphertext) = ML-KEM.Encapsulate(KAS_mlkem_public) +``` + +- `mlkemSecret` (32 bytes): A shared secret known to the encapsulator +- `mlkemCiphertext` (1088 bytes for ML-KEM-768, 1568 bytes for ML-KEM-1024): An opaque ciphertext that only the ML-KEM private key holder can decapsulate to recover the same shared secret + +Internally, `EncapsulateTo`: +1. Generates random coins (entropy) +2. Uses the ML-KEM public key (a matrix over a polynomial ring) to encrypt those random coins into the ciphertext +3. Derives the shared secret from both the random coins and the ciphertext + +No ephemeral ML-KEM key pair is generated by the client. The ciphertext itself serves as the "ephemeral" artifact sent to KAS. + +### Step 4 - Combine Secrets + +The two shared secrets from the classical and post-quantum operations are concatenated: + +``` +combinedSecret = ecdhSecret || mlkemSecret +``` + +This is a simple byte concatenation. The security property is that an attacker must break **both** ECDH and ML-KEM to recover the combined secret. If quantum computers break ECDH but ML-KEM remains secure, the combined secret is still protected (and vice versa). + +### Step 5 - Key Derivation (HKDF) + +A 32-byte AES-256 wrapping key is derived from the combined secret using HKDF-SHA256: + +``` +wrapKey = HKDF-SHA256( + IKM: combinedSecret, // ecdhSecret || mlkemSecret + salt: SHA256("TDF"), // 32-byte fixed salt + info: // empty by default +) -> 32 bytes +``` + +Function: `deriveHybridNISTWrapKey` (`hybrid_nist.go`, line 479) + +The salt is a hardcoded SHA-256 digest of the ASCII string `"TDF"`, shared across all TDF key wrapping schemes. + +### Step 6 - AES-GCM Encrypt the Split Key + +The split key (the actual data encryption key shard) is encrypted using AES-256-GCM: + +``` +encryptedDEK = AES-256-GCM.Encrypt(key=wrapKey, plaintext=splitKey) +``` + +The output format is: + +``` +[ nonce (12 bytes) | ciphertext | authentication tag (16 bytes) ] +``` + +The nonce is randomly generated. The authentication tag provides integrity verification. + +### Step 7 - Package as ASN.1 DER + +The ephemeral EC public key, ML-KEM ciphertext, and encrypted DEK are packaged into an ASN.1 DER structure: + +```asn1 +HybridNISTWrappedKey ::= SEQUENCE { + hybridCiphertext [0] OCTET STRING, -- ephemeralECPub || mlkemCiphertext + encryptedDEK [1] OCTET STRING -- AES-GCM nonce + ciphertext + tag +} +``` + +Where `hybridCiphertext` is: + +``` +[ ephemeral EC public key (65 or 97 bytes) | ML-KEM ciphertext (1088 or 1568 bytes) ] +``` + +| Variant | Ephemeral EC Pub | ML-KEM Ciphertext | hybridCiphertext Size | +|---------|-----------------|-------------------|----------------------| +| P-256 + ML-KEM-768 | 65 bytes | 1088 bytes | 1153 bytes | +| P-384 + ML-KEM-1024 | 97 bytes | 1568 bytes | 1665 bytes | + +This DER blob is then base64-encoded and stored as the `wrappedKey` field in the TDF manifest's Key Access Object, with `keyType` set to `"hybrid-wrapped"`. + +## Unwrap (Decrypt the Split Key) + +Function: `hybridNISTUnwrapDEK` (`hybrid_nist.go`, line 406) + +This is performed on the **KAS server side** when a client sends a rewrap request. KAS holds the combined private key on disk, loaded at startup. + +### Step 1 - Parse the ASN.1 DER + +The base64-decoded DER blob is unmarshalled: + +``` +ASN.1 Unmarshal -> HybridNISTWrappedKey { + hybridCiphertext: [ephemeralECPub | mlkemCiphertext] + encryptedDEK: [nonce | ciphertext | tag] +} +``` + +### Step 2 - Split the Hybrid Ciphertext + +The `hybridCiphertext` is split at the known EC public key size boundary: + +``` +ephemeralECPub = hybridCiphertext[:ecPubSize] // 65 or 97 bytes +mlkemCiphertext = hybridCiphertext[ecPubSize:] // 1088 or 1568 bytes +``` + +### Step 3 - Split the Private Key + +The combined private key is split at the known EC private key size boundary: + +``` +ecPrivBytes = privateKeyRaw[:ecPrivSize] // 32 or 48 bytes +mlkemPrivBytes = privateKeyRaw[ecPrivSize:] // 2400 or 3168 bytes +``` + +### Step 4 - ECDH (Reconstruct Classical Shared Secret) + +KAS uses its static EC private key with the client's ephemeral EC public key: + +``` +ecdhSecret = ECDH(KAS_ec_private, ephemeral_ec_public) +``` + +This produces the same `ecdhSecret` that the client computed in Wrap Step 2, because `ECDH(a, g^b) == ECDH(b, g^a)`. + +### Step 5 - ML-KEM Decapsulate (Reconstruct Post-Quantum Shared Secret) + +KAS uses its ML-KEM private key to decapsulate the ciphertext: + +``` +mlkemSecret = ML-KEM.Decapsulate(KAS_mlkem_private, mlkemCiphertext) +``` + +The ML-KEM private key contains the secret trapdoor in the lattice structure. Decapsulation recovers the random coins from the ciphertext and derives the same 32-byte shared secret that the client obtained during encapsulation. + +### Step 6 - Combine Secrets + +Identical to Wrap Step 4: + +``` +combinedSecret = ecdhSecret || mlkemSecret +``` + +### Step 7 - Key Derivation (HKDF) + +Identical to Wrap Step 5: + +``` +wrapKey = HKDF-SHA256( + IKM: combinedSecret, + salt: SHA256("TDF"), + info: +) -> 32 bytes +``` + +Both sides derive the same `wrapKey` because both sides have the same `ecdhSecret` and `mlkemSecret`. + +### Step 8 - AES-GCM Decrypt the Split Key + +``` +splitKey = AES-256-GCM.Decrypt(key=wrapKey, ciphertext=encryptedDEK) +``` + +The AES-GCM decryption verifies the authentication tag. If the tag does not match (indicating tampering or a wrong key), decryption fails. + +KAS now has the original split key. It enforces policy checks, and if the requesting client is authorized, returns the split key (protected by the session transport layer). + +## Security Properties + +### Hybrid Security Guarantee + +The combined secret is derived from both ECDH and ML-KEM. An attacker must break **both** to recover the wrap key: + +- If a quantum computer breaks ECDH (recovers `ecdhSecret` from the ephemeral public key), ML-KEM still protects the combined secret +- If a classical vulnerability is found in ML-KEM (recovers `mlkemSecret` from the ciphertext), ECDH still protects the combined secret + +### Forward Secrecy + +- The ephemeral EC key pair is generated fresh for each wrap operation, providing forward secrecy on the classical side +- ML-KEM encapsulation generates fresh randomness for each operation, providing forward secrecy on the post-quantum side + +### What is NOT in the Combiner + +Unlike the X-Wing KEM (which uses SHA3-256 and mixes in public keys, ciphertexts, and a domain label), this NIST hybrid implementation: + +- Does **not** mix the ephemeral EC public key into the KDF +- Does **not** mix the ML-KEM ciphertext into the KDF +- Does **not** mix the static public keys into the KDF +- Does **not** use a domain separation label in the HKDF info + +The two raw shared secrets are simply concatenated and passed through HKDF. This is a common and accepted pattern for hybrid KEM composition, though it provides less identity binding than the X-Wing combiner approach. + +## Comparison with Other Key Wrapping Schemes in TDF + +| Aspect | RSA (`wrapped`) | EC (`ec-wrapped`) | NIST Hybrid (`hybrid-wrapped`) | X-Wing (`hybrid-wrapped`) | +|--------|-----------------|-------------------|-------------------------------|--------------------------| +| Classical | RSA-2048 | ECDH (P-256/384/521) | ECDH (P-256/384) | X25519 | +| Post-Quantum | None | None | ML-KEM-768/1024 | ML-KEM-768 | +| Combiner | N/A | HKDF only | Concatenation + HKDF | SHA3-256 (spec-defined, inside circl library) + HKDF | +| Output Format | Base64(RSA ciphertext) | Base64(AES-GCM ciphertext) | Base64(ASN.1 DER) | Base64(ASN.1 DER) | +| Ephemeral Key | None | EC ephemeral | EC ephemeral + ML-KEM ciphertext | X-Wing ciphertext (contains both) | +| Identity Binding | N/A | No | No | Yes (public keys mixed into SHA3-256) | + +## Manifest Example + +A Key Access Object in the TDF manifest for hybrid wrapping: + +```json +{ + "type": "hybrid-wrapped", + "url": "https://kas.example.com", + "kid": "hybrid-key-1", + "sid": "split-1", + "wrappedKey": "", + "policyBinding": { + "alg": "HS256", + "hash": "" + } +} +``` + +Note: Unlike `ec-wrapped`, the `ephemeralPublicKey` field is **not** used in the manifest for hybrid wrapping. The ephemeral EC public key is embedded inside the `wrappedKey` ASN.1 structure alongside the ML-KEM ciphertext. diff --git a/lib/ocrypto/asym_decryption.go b/lib/ocrypto/asym_decryption.go index 426f859723..fdd7bb5005 100644 --- a/lib/ocrypto/asym_decryption.go +++ b/lib/ocrypto/asym_decryption.go @@ -43,6 +43,14 @@ func FromPrivatePEMWithSalt(privateKeyInPem string, salt, info []byte) (PrivateK if block == nil { return AsymDecryption{}, errors.New("failed to parse PEM formatted private key") } + switch block.Type { + case PEMBlockXWingPrivateKey: + return NewSaltedXWingDecryptor(block.Bytes, salt, info) + case PEMBlockP256MLKEM768PrivateKey: + return NewSaltedP256MLKEM768Decryptor(block.Bytes, salt, info) + case PEMBlockP384MLKEM1024PrivateKey: + return NewSaltedP384MLKEM1024Decryptor(block.Bytes, salt, info) + } priv, err := x509.ParsePKCS8PrivateKey(block.Bytes) switch { diff --git a/lib/ocrypto/asym_encryption.go b/lib/ocrypto/asym_encryption.go index c44aa64cce..7e08199509 100644 --- a/lib/ocrypto/asym_encryption.go +++ b/lib/ocrypto/asym_encryption.go @@ -23,8 +23,9 @@ import ( type SchemeType string const ( - RSA SchemeType = "wrapped" - EC SchemeType = "ec-wrapped" + RSA SchemeType = "wrapped" + EC SchemeType = "ec-wrapped" + Hybrid SchemeType = "hybrid-wrapped" ) type PublicKeyEncryptor interface { @@ -69,6 +70,19 @@ func FromPublicPEM(publicKeyInPem string) (PublicKeyEncryptor, error) { } func FromPublicPEMWithSalt(publicKeyInPem string, salt, info []byte) (PublicKeyEncryptor, error) { + block, _ := pem.Decode([]byte(publicKeyInPem)) + if block == nil { + return nil, errors.New("failed to parse PEM formatted public key") + } + switch block.Type { + case PEMBlockXWingPublicKey: + return NewXWingEncryptor(block.Bytes, salt, info) + case PEMBlockP256MLKEM768PublicKey: + return NewP256MLKEM768Encryptor(block.Bytes, salt, info) + case PEMBlockP384MLKEM1024PublicKey: + return NewP384MLKEM1024Encryptor(block.Bytes, salt, info) + } + pub, err := getPublicPart(publicKeyInPem) if err != nil { return nil, err diff --git a/lib/ocrypto/benchmark_test.go b/lib/ocrypto/benchmark_test.go new file mode 100644 index 0000000000..b86b3075e7 --- /dev/null +++ b/lib/ocrypto/benchmark_test.go @@ -0,0 +1,816 @@ +package ocrypto + +import ( + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "fmt" + "testing" +) + +// Sink variables to prevent compiler from optimizing away results. +var ( + sinkBytes []byte + errSink error +) + +// testDEK is a 32-byte AES-256 key used as the payload for wrap/unwrap benchmarks. +var testDEK = []byte("0123456789abcdef0123456789abcdef") + +func BenchmarkKeyGeneration(b *testing.B) { + b.Run("RSA-2048", func(b *testing.B) { + for b.Loop() { + _, errSink = NewRSAKeyPair(2048) + } + }) + b.Run("EC-P256", func(b *testing.B) { + for b.Loop() { + _, errSink = NewECKeyPair(ECCModeSecp256r1) + } + }) + b.Run("EC-P384", func(b *testing.B) { + for b.Loop() { + _, errSink = NewECKeyPair(ECCModeSecp384r1) + } + }) + b.Run("XWing", func(b *testing.B) { + for b.Loop() { + _, errSink = NewXWingKeyPair() + } + }) + b.Run("P256_MLKEM768", func(b *testing.B) { + for b.Loop() { + _, errSink = NewP256MLKEM768KeyPair() + } + }) + b.Run("P384_MLKEM1024", func(b *testing.B) { + for b.Loop() { + _, errSink = NewP384MLKEM1024KeyPair() + } + }) +} + +// benchTDFSalt matches tdf.go:tdfSalt() — SHA-256("TDF"). +func benchTDFSalt() []byte { + digest := sha256.New() + digest.Write([]byte("TDF")) + return digest.Sum(nil) +} + +// BenchmarkWrapDEK mirrors the actual TDF key-wrapping paths in sdk/tdf.go: +// - RSA: FromPublicPEM -> Encrypt (generateWrapKeyWithRSA) +// - EC: NewECKeyPair -> ComputeECDHKey -> HKDF -> AES-GCM (generateWrapKeyWithEC) +// - Hybrid: PubKeyFromPem -> Encapsulate -> HKDF -> AES-GCM -> ASN.1 (generateWrapKeyWithHybrid) +func BenchmarkWrapDEK(b *testing.B) { + salt := benchTDFSalt() + + // RSA-2048: setup KAS public key + rsaKP, err := NewRSAKeyPair(2048) + if err != nil { + b.Fatal(err) + } + rsaPubPEM, err := rsaKP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + + // EC P-256: setup KAS public key PEM + ecKP, err := NewECKeyPair(ECCModeSecp256r1) + if err != nil { + b.Fatal(err) + } + ecKASPubPEM, err := ecKP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + + // EC P-384: setup KAS public key PEM + ec384KP, err := NewECKeyPair(ECCModeSecp384r1) + if err != nil { + b.Fatal(err) + } + ec384KASPubPEM, err := ec384KP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + + // X-Wing: setup KAS public key PEM + xwingKP, err := NewXWingKeyPair() + if err != nil { + b.Fatal(err) + } + xwingPubPEM, err := xwingKP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + + // P256+MLKEM768: setup KAS public key PEM + p256KP, err := NewP256MLKEM768KeyPair() + if err != nil { + b.Fatal(err) + } + p256PubPEM, err := p256KP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + + // P384+MLKEM1024: setup KAS public key PEM + p384KP, err := NewP384MLKEM1024KeyPair() + if err != nil { + b.Fatal(err) + } + p384PubPEM, err := p384KP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + + // RSA: tdf.go calls FromPublicPEM -> Encrypt + b.Run("RSA-2048", func(b *testing.B) { + for b.Loop() { + enc, err := FromPublicPEM(rsaPubPEM) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = enc.Encrypt(testDEK) + } + b.ReportMetric(float64(len(sinkBytes)), "wrapped-bytes") + }) + + // EC: tdf.go generates ephemeral EC keypair, computes ECDH, derives via HKDF, AES-GCM wraps + b.Run("EC-P256", func(b *testing.B) { + for b.Loop() { + ephKP, err := NewECKeyPair(ECCModeSecp256r1) + if err != nil { + b.Fatal(err) + } + ephPrivPEM, err := ephKP.PrivateKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + ecdhKey, err := ComputeECDHKey([]byte(ephPrivPEM), []byte(ecKASPubPEM)) + if err != nil { + b.Fatal(err) + } + sessionKey, err := CalculateHKDF(salt, ecdhKey) + if err != nil { + b.Fatal(err) + } + gcm, err := NewAESGcm(sessionKey) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = gcm.Encrypt(testDEK) + } + b.ReportMetric(float64(len(sinkBytes)), "wrapped-bytes") + }) + + b.Run("EC-P384", func(b *testing.B) { + for b.Loop() { + ephKP, err := NewECKeyPair(ECCModeSecp384r1) + if err != nil { + b.Fatal(err) + } + ephPrivPEM, err := ephKP.PrivateKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + ecdhKey, err := ComputeECDHKey([]byte(ephPrivPEM), []byte(ec384KASPubPEM)) + if err != nil { + b.Fatal(err) + } + sessionKey, err := CalculateHKDF(salt, ecdhKey) + if err != nil { + b.Fatal(err) + } + gcm, err := NewAESGcm(sessionKey) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = gcm.Encrypt(testDEK) + } + b.ReportMetric(float64(len(sinkBytes)), "wrapped-bytes") + }) + + // X-Wing: tdf.go parses PEM, calls Encapsulate, HKDF, AES-GCM, then ASN.1 marshal + b.Run("XWing", func(b *testing.B) { + for b.Loop() { + pubKey, err := XWingPubKeyFromPem([]byte(xwingPubPEM)) + if err != nil { + b.Fatal(err) + } + ss, ct, err := XWingEncapsulate(pubKey) + if err != nil { + b.Fatal(err) + } + wrapKey, err := CalculateHKDF(salt, ss) + if err != nil { + b.Fatal(err) + } + gcm, err := NewAESGcm(wrapKey) + if err != nil { + b.Fatal(err) + } + encDEK, err := gcm.Encrypt(testDEK) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = asn1.Marshal(HybridNISTWrappedKey{ + HybridCiphertext: ct, + EncryptedDEK: encDEK, + }) + } + b.ReportMetric(float64(len(sinkBytes)), "wrapped-bytes") + }) + + // P256+MLKEM768: same flow as X-Wing with different Encapsulate/PEM parse + b.Run("P256_MLKEM768", func(b *testing.B) { + for b.Loop() { + pubKey, err := P256MLKEM768PubKeyFromPem([]byte(p256PubPEM)) + if err != nil { + b.Fatal(err) + } + ss, ct, err := P256MLKEM768Encapsulate(pubKey) + if err != nil { + b.Fatal(err) + } + wrapKey, err := CalculateHKDF(salt, ss) + if err != nil { + b.Fatal(err) + } + gcm, err := NewAESGcm(wrapKey) + if err != nil { + b.Fatal(err) + } + encDEK, err := gcm.Encrypt(testDEK) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = asn1.Marshal(HybridNISTWrappedKey{ + HybridCiphertext: ct, + EncryptedDEK: encDEK, + }) + } + b.ReportMetric(float64(len(sinkBytes)), "wrapped-bytes") + }) + + // P384+MLKEM1024: same flow with P384 variant + b.Run("P384_MLKEM1024", func(b *testing.B) { + for b.Loop() { + pubKey, err := P384MLKEM1024PubKeyFromPem([]byte(p384PubPEM)) + if err != nil { + b.Fatal(err) + } + ss, ct, err := P384MLKEM1024Encapsulate(pubKey) + if err != nil { + b.Fatal(err) + } + wrapKey, err := CalculateHKDF(salt, ss) + if err != nil { + b.Fatal(err) + } + gcm, err := NewAESGcm(wrapKey) + if err != nil { + b.Fatal(err) + } + encDEK, err := gcm.Encrypt(testDEK) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = asn1.Marshal(HybridNISTWrappedKey{ + HybridCiphertext: ct, + EncryptedDEK: encDEK, + }) + } + b.ReportMetric(float64(len(sinkBytes)), "wrapped-bytes") + }) +} + +// BenchmarkUnwrapDEK mirrors the actual KAS unwrap paths in +// service/internal/security/standard_crypto.go:Decrypt(): +// - RSA: pre-loaded AsymDecryption.Decrypt (key already parsed) +// - EC: ECPrivateKeyFromPem (cached) -> NewSaltedECDecryptor(TDFSalt) -> DecryptWithEphemeralKey +// - Hybrid: PrivateKeyFromPem -> UnwrapDEK (PEM parsed each time in current KAS code) +func BenchmarkUnwrapDEK(b *testing.B) { + salt := benchTDFSalt() + + // RSA-2048: KAS pre-loads the AsymDecryption at startup + rsaKP, err := NewRSAKeyPair(2048) + if err != nil { + b.Fatal(err) + } + rsaPubPEM, err := rsaKP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + rsaPrivPEM, err := rsaKP.PrivateKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + rsaEnc, err := NewAsymEncryption(rsaPubPEM) + if err != nil { + b.Fatal(err) + } + rsaWrapped, err := rsaEnc.Encrypt(testDEK) + if err != nil { + b.Fatal(err) + } + rsaDec, err := NewAsymDecryption(rsaPrivPEM) + if err != nil { + b.Fatal(err) + } + + // EC P-256: KAS caches the parsed private key, creates decryptor per request + ecKASKP, err := NewECKeyPair(ECCModeSecp256r1) + if err != nil { + b.Fatal(err) + } + ecKASPubPEM, err := ecKASKP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + ecKASPrivPEM, err := ecKASKP.PrivateKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + // Wrap using the TDF path: ephemeral keygen + ECDH + HKDF + AES-GCM + ecEphKP, err := NewECKeyPair(ECCModeSecp256r1) + if err != nil { + b.Fatal(err) + } + ecEphPrivPEM, err := ecEphKP.PrivateKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + ecEphPubPEM, err := ecEphKP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + ecdhKey, err := ComputeECDHKey([]byte(ecEphPrivPEM), []byte(ecKASPubPEM)) + if err != nil { + b.Fatal(err) + } + ecSessionKey, err := CalculateHKDF(salt, ecdhKey) + if err != nil { + b.Fatal(err) + } + ecGCM, err := NewAESGcm(ecSessionKey) + if err != nil { + b.Fatal(err) + } + ecWrapped, err := ecGCM.Encrypt(testDEK) + if err != nil { + b.Fatal(err) + } + // KAS receives the ephemeral public key as DER (parsed from PEM in the manifest). + // DecryptWithEphemeralKey first tries x509.ParsePKIXPublicKey (DER), then compressed. + ecEphPubECDH, err := ECPubKeyFromPem([]byte(ecEphPubPEM)) + if err != nil { + b.Fatal(err) + } + ecEphDER, err := x509.MarshalPKIXPublicKey(ecEphPubECDH) + if err != nil { + b.Fatal(err) + } + // KAS parses private key once (cached in StandardECCrypto) + ecKASPrivKey, err := ECPrivateKeyFromPem([]byte(ecKASPrivPEM)) + if err != nil { + b.Fatal(err) + } + + // EC P-384: same flow as P-256, just on a different curve + ec384KASKP, err := NewECKeyPair(ECCModeSecp384r1) + if err != nil { + b.Fatal(err) + } + ec384KASPubPEM, err := ec384KASKP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + ec384KASPrivPEM, err := ec384KASKP.PrivateKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + ec384EphKP, err := NewECKeyPair(ECCModeSecp384r1) + if err != nil { + b.Fatal(err) + } + ec384EphPrivPEM, err := ec384EphKP.PrivateKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + ec384EphPubPEM, err := ec384EphKP.PublicKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + ec384DhKey, err := ComputeECDHKey([]byte(ec384EphPrivPEM), []byte(ec384KASPubPEM)) + if err != nil { + b.Fatal(err) + } + ec384SessionKey, err := CalculateHKDF(salt, ec384DhKey) + if err != nil { + b.Fatal(err) + } + ec384GCM, err := NewAESGcm(ec384SessionKey) + if err != nil { + b.Fatal(err) + } + ec384Wrapped, err := ec384GCM.Encrypt(testDEK) + if err != nil { + b.Fatal(err) + } + ec384EphPubECDH, err := ECPubKeyFromPem([]byte(ec384EphPubPEM)) + if err != nil { + b.Fatal(err) + } + ec384EphDER, err := x509.MarshalPKIXPublicKey(ec384EphPubECDH) + if err != nil { + b.Fatal(err) + } + ec384KASPrivKey, err := ECPrivateKeyFromPem([]byte(ec384KASPrivPEM)) + if err != nil { + b.Fatal(err) + } + + // X-Wing: KAS parses PEM each call, then calls UnwrapDEK + xwingKP, err := NewXWingKeyPair() + if err != nil { + b.Fatal(err) + } + xwingPrivPEM, err := xwingKP.PrivateKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + xwingWrapped, err := XWingWrapDEK(xwingKP.publicKey, testDEK) + if err != nil { + b.Fatal(err) + } + + // P256+MLKEM768: KAS parses PEM each call, then calls UnwrapDEK + p256KP, err := NewP256MLKEM768KeyPair() + if err != nil { + b.Fatal(err) + } + p256PrivPEM, err := p256KP.PrivateKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + p256Wrapped, err := P256MLKEM768WrapDEK(p256KP.publicKey, testDEK) + if err != nil { + b.Fatal(err) + } + + // P384+MLKEM1024: KAS parses PEM each call, then calls UnwrapDEK + p384KP, err := NewP384MLKEM1024KeyPair() + if err != nil { + b.Fatal(err) + } + p384PrivPEM, err := p384KP.PrivateKeyInPemFormat() + if err != nil { + b.Fatal(err) + } + p384Wrapped, err := P384MLKEM1024WrapDEK(p384KP.publicKey, testDEK) + if err != nil { + b.Fatal(err) + } + + // RSA: KAS has pre-loaded AsymDecryption, just calls Decrypt + b.Run("RSA-2048", func(b *testing.B) { + for b.Loop() { + sinkBytes, errSink = rsaDec.Decrypt(rsaWrapped) + } + }) + + // EC: KAS creates NewSaltedECDecryptor(cachedSK, TDFSalt, nil) -> DecryptWithEphemeralKey + b.Run("EC-P256", func(b *testing.B) { + for b.Loop() { + dec, err := NewSaltedECDecryptor(ecKASPrivKey, salt, nil) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = dec.DecryptWithEphemeralKey(ecWrapped, ecEphDER) + } + }) + + b.Run("EC-P384", func(b *testing.B) { + for b.Loop() { + dec, err := NewSaltedECDecryptor(ec384KASPrivKey, salt, nil) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = dec.DecryptWithEphemeralKey(ec384Wrapped, ec384EphDER) + } + }) + + // X-Wing: KAS parses PEM then calls UnwrapDEK + b.Run("XWing", func(b *testing.B) { + for b.Loop() { + privKey, err := XWingPrivateKeyFromPem([]byte(xwingPrivPEM)) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = XWingUnwrapDEK(privKey, xwingWrapped) + } + }) + + // P256+MLKEM768: KAS parses PEM then calls UnwrapDEK + b.Run("P256_MLKEM768", func(b *testing.B) { + for b.Loop() { + privKey, err := P256MLKEM768PrivateKeyFromPem([]byte(p256PrivPEM)) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = P256MLKEM768UnwrapDEK(privKey, p256Wrapped) + } + }) + + // P384+MLKEM1024: KAS parses PEM then calls UnwrapDEK + b.Run("P384_MLKEM1024", func(b *testing.B) { + for b.Loop() { + privKey, err := P384MLKEM1024PrivateKeyFromPem([]byte(p384PrivPEM)) + if err != nil { + b.Fatal(err) + } + sinkBytes, errSink = P384MLKEM1024UnwrapDEK(privKey, p384Wrapped) + } + }) +} + +func BenchmarkHybridSubOps(b *testing.B) { + // Setup X-Wing + xwingKP, err := NewXWingKeyPair() + if err != nil { + b.Fatal(err) + } + xwingSS, xwingCt, err := XWingEncapsulate(xwingKP.publicKey) + if err != nil { + b.Fatal(err) + } + + // Setup P256+MLKEM768 + p256KP, err := NewP256MLKEM768KeyPair() + if err != nil { + b.Fatal(err) + } + p256SS, p256Ct, err := P256MLKEM768Encapsulate(p256KP.publicKey) + if err != nil { + b.Fatal(err) + } + + // Setup P384+MLKEM1024 + p384KP, err := NewP384MLKEM1024KeyPair() + if err != nil { + b.Fatal(err) + } + p384SS, p384Ct, err := P384MLKEM1024Encapsulate(p384KP.publicKey) + if err != nil { + b.Fatal(err) + } + + salt := defaultTDFSalt() + + // Pre-derive a wrap key for AES-GCM benchmarks + wrapKey, err := deriveXWingWrapKey(xwingSS, salt, nil) + if err != nil { + b.Fatal(err) + } + + b.Run("XWing/Encapsulate", func(b *testing.B) { + for b.Loop() { + sinkBytes, sinkBytes, errSink = XWingEncapsulate(xwingKP.publicKey) + } + }) + b.Run("XWing/HKDF", func(b *testing.B) { + for b.Loop() { + sinkBytes, errSink = deriveXWingWrapKey(xwingSS, salt, nil) + } + }) + b.Run("XWing/AES-GCM-Encrypt", func(b *testing.B) { + gcm, err := NewAESGcm(wrapKey) + if err != nil { + b.Fatal(err) + } + for b.Loop() { + sinkBytes, errSink = gcm.Encrypt(testDEK) + } + }) + b.Run("XWing/ASN1-Marshal", func(b *testing.B) { + wrapped := XWingWrappedKey{XWingCiphertext: xwingCt, EncryptedDEK: testDEK} + for b.Loop() { + sinkBytes, errSink = asn1.Marshal(wrapped) + } + }) + + // P256+MLKEM768 sub-ops + p256WrapKey, err := deriveHybridNISTWrapKey(p256SS, salt, nil) + if err != nil { + b.Fatal(err) + } + b.Run("P256_MLKEM768/Encapsulate", func(b *testing.B) { + for b.Loop() { + sinkBytes, sinkBytes, errSink = P256MLKEM768Encapsulate(p256KP.publicKey) + } + }) + b.Run("P256_MLKEM768/HKDF", func(b *testing.B) { + for b.Loop() { + sinkBytes, errSink = deriveHybridNISTWrapKey(p256SS, salt, nil) + } + }) + b.Run("P256_MLKEM768/AES-GCM-Encrypt", func(b *testing.B) { + gcm, err := NewAESGcm(p256WrapKey) + if err != nil { + b.Fatal(err) + } + for b.Loop() { + sinkBytes, errSink = gcm.Encrypt(testDEK) + } + }) + b.Run("P256_MLKEM768/ASN1-Marshal", func(b *testing.B) { + wrapped := HybridNISTWrappedKey{HybridCiphertext: p256Ct, EncryptedDEK: testDEK} + for b.Loop() { + sinkBytes, errSink = asn1.Marshal(wrapped) + } + }) + + // P384+MLKEM1024 sub-ops + p384WrapKey, err := deriveHybridNISTWrapKey(p384SS, salt, nil) + if err != nil { + b.Fatal(err) + } + b.Run("P384_MLKEM1024/Encapsulate", func(b *testing.B) { + for b.Loop() { + sinkBytes, sinkBytes, errSink = P384MLKEM1024Encapsulate(p384KP.publicKey) + } + }) + b.Run("P384_MLKEM1024/HKDF", func(b *testing.B) { + for b.Loop() { + sinkBytes, errSink = deriveHybridNISTWrapKey(p384SS, salt, nil) + } + }) + b.Run("P384_MLKEM1024/AES-GCM-Encrypt", func(b *testing.B) { + gcm, err := NewAESGcm(p384WrapKey) + if err != nil { + b.Fatal(err) + } + for b.Loop() { + sinkBytes, errSink = gcm.Encrypt(testDEK) + } + }) + b.Run("P384_MLKEM1024/ASN1-Marshal", func(b *testing.B) { + wrapped := HybridNISTWrappedKey{HybridCiphertext: p384Ct, EncryptedDEK: testDEK} + for b.Loop() { + sinkBytes, errSink = asn1.Marshal(wrapped) + } + }) +} + +func TestWrappedKeySizeComparison(t *testing.T) { + type sizeResult struct { + scheme string + wrappedLen int + pubKeyLen int + notes string + } + + var results []sizeResult + + // RSA-2048 + rsaKP, err := NewRSAKeyPair(2048) + if err != nil { + t.Fatal(err) + } + rsaPubPEM, err := rsaKP.PublicKeyInPemFormat() + if err != nil { + t.Fatal(err) + } + rsaEnc, err := NewAsymEncryption(rsaPubPEM) + if err != nil { + t.Fatal(err) + } + rsaWrapped, err := rsaEnc.Encrypt(testDEK) + if err != nil { + t.Fatal(err) + } + results = append(results, sizeResult{ + scheme: "RSA-2048", + wrappedLen: len(rsaWrapped), + pubKeyLen: len(rsaPubPEM), + notes: "No ephemeral key", + }) + + // EC P-256 + ecKP, err := NewECKeyPair(ECCModeSecp256r1) + if err != nil { + t.Fatal(err) + } + ecPubPEM, err := ecKP.PublicKeyInPemFormat() + if err != nil { + t.Fatal(err) + } + ecEnc, err := FromPublicPEM(ecPubPEM) + if err != nil { + t.Fatal(err) + } + ecWrapped, err := ecEnc.Encrypt(testDEK) + if err != nil { + t.Fatal(err) + } + ecEphemeral := ecEnc.EphemeralKey() + results = append(results, sizeResult{ + scheme: "EC P-256", + wrappedLen: len(ecWrapped), + pubKeyLen: len(ecPubPEM), + notes: fmt.Sprintf("+ ephemeral key (%d bytes)", len(ecEphemeral)), + }) + + // EC P-384 + ec384KP, err := NewECKeyPair(ECCModeSecp384r1) + if err != nil { + t.Fatal(err) + } + ec384PubPEM, err := ec384KP.PublicKeyInPemFormat() + if err != nil { + t.Fatal(err) + } + ec384Enc, err := FromPublicPEM(ec384PubPEM) + if err != nil { + t.Fatal(err) + } + ec384Wrapped, err := ec384Enc.Encrypt(testDEK) + if err != nil { + t.Fatal(err) + } + ec384Ephemeral := ec384Enc.EphemeralKey() + results = append(results, sizeResult{ + scheme: "EC P-384", + wrappedLen: len(ec384Wrapped), + pubKeyLen: len(ec384PubPEM), + notes: fmt.Sprintf("+ ephemeral key (%d bytes)", len(ec384Ephemeral)), + }) + + // X-Wing + xwingKP, err := NewXWingKeyPair() + if err != nil { + t.Fatal(err) + } + xwingPubPEM, err := xwingKP.PublicKeyInPemFormat() + if err != nil { + t.Fatal(err) + } + xwingWrapped, err := XWingWrapDEK(xwingKP.publicKey, testDEK) + if err != nil { + t.Fatal(err) + } + results = append(results, sizeResult{ + scheme: "X-Wing", + wrappedLen: len(xwingWrapped), + pubKeyLen: len(xwingPubPEM), + notes: "All in ASN.1 blob", + }) + + // P256+MLKEM768 + p256KP, err := NewP256MLKEM768KeyPair() + if err != nil { + t.Fatal(err) + } + p256PubPEM, err := p256KP.PublicKeyInPemFormat() + if err != nil { + t.Fatal(err) + } + p256Wrapped, err := P256MLKEM768WrapDEK(p256KP.publicKey, testDEK) + if err != nil { + t.Fatal(err) + } + results = append(results, sizeResult{ + scheme: "P256+MLKEM768", + wrappedLen: len(p256Wrapped), + pubKeyLen: len(p256PubPEM), + notes: "All in ASN.1 blob", + }) + + // P384+MLKEM1024 + p384KP, err := NewP384MLKEM1024KeyPair() + if err != nil { + t.Fatal(err) + } + p384PubPEM, err := p384KP.PublicKeyInPemFormat() + if err != nil { + t.Fatal(err) + } + p384Wrapped, err := P384MLKEM1024WrapDEK(p384KP.publicKey, testDEK) + if err != nil { + t.Fatal(err) + } + results = append(results, sizeResult{ + scheme: "P384+MLKEM1024", + wrappedLen: len(p384Wrapped), + pubKeyLen: len(p384PubPEM), + notes: "All in ASN.1 blob", + }) + + // Print table + t.Logf("\n%-20s %20s %20s %s", "Scheme", "Wrapped Key (bytes)", "Public Key (bytes)", "Notes") + t.Logf("%-20s %20s %20s %s", "------", "-------------------", "------------------", "-----") + for _, r := range results { + t.Logf("%-20s %20d %20d %s", r.scheme, r.wrappedLen, r.pubKeyLen, r.notes) + } +} diff --git a/lib/ocrypto/ec_key_pair.go b/lib/ocrypto/ec_key_pair.go index f9a9554d4f..70e30cc8df 100644 --- a/lib/ocrypto/ec_key_pair.go +++ b/lib/ocrypto/ec_key_pair.go @@ -29,6 +29,19 @@ const ( EC521Key KeyType = "ec:secp521r1" ) +// ParseKeyType validates a string as a known KeyType, returning an error for +// unrecognized values. +func ParseKeyType(alg string) (KeyType, error) { + switch KeyType(alg) { + case RSA2048Key, RSA4096Key, + EC256Key, EC384Key, EC521Key, + HybridXWingKey, HybridSecp256r1MLKEM768Key, HybridSecp384r1MLKEM1024Key: + return KeyType(alg), nil + default: + return "", fmt.Errorf("unrecognized key type: %s", alg) + } +} + const ( ECCModeSecp256r1 ECCMode = 0 ECCModeSecp384r1 ECCMode = 1 @@ -64,6 +77,8 @@ func NewKeyPair(kt KeyType) (KeyPair, error) { return nil, err } return NewECKeyPair(mode) + case HybridSecp256r1MLKEM768Key, HybridSecp384r1MLKEM1024Key, HybridXWingKey: + return NewHybridKeyPair(kt) default: return nil, fmt.Errorf("unsupported key type: %v", kt) } diff --git a/lib/ocrypto/go.mod b/lib/ocrypto/go.mod index fb1efec86f..206f123530 100644 --- a/lib/ocrypto/go.mod +++ b/lib/ocrypto/go.mod @@ -5,8 +5,9 @@ go 1.25.0 toolchain go1.25.9 require ( - github.com/stretchr/testify v1.11.1 - golang.org/x/crypto v0.49.0 + github.com/cloudflare/circl v1.6.3 + github.com/stretchr/testify v1.10.0 + golang.org/x/crypto v0.45.0 ) require ( @@ -14,6 +15,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect + golang.org/x/sys v0.38.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/lib/ocrypto/go.sum b/lib/ocrypto/go.sum index 8b6a6d0a01..0388adaa2d 100644 --- a/lib/ocrypto/go.sum +++ b/lib/ocrypto/go.sum @@ -1,3 +1,5 @@ +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -14,10 +16,12 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/lib/ocrypto/hybrid_common.go b/lib/ocrypto/hybrid_common.go new file mode 100644 index 0000000000..4f4ee05417 --- /dev/null +++ b/lib/ocrypto/hybrid_common.go @@ -0,0 +1,75 @@ +package ocrypto + +import ( + "crypto/sha256" + "encoding/pem" + "fmt" +) + +// HybridWrapDEK parses the recipient's hybrid public key PEM, encapsulates +// against it using the scheme implied by ktype, and returns the ASN.1-encoded +// wrapped DEK envelope used in `hybrid-wrapped` manifests. It dispatches across +// both the X-Wing and NIST EC + ML-KEM families so SDK call sites do not need +// to repeat the algorithm switch. +// +// The HKDF salt is the default TDF salt; callers that need a non-default salt +// should call the per-scheme `*WrapDEK` helpers directly. +func HybridWrapDEK(ktype KeyType, kasPublicKeyPEM string, dek []byte) ([]byte, error) { + switch ktype { //nolint:exhaustive // only handle hybrid types + case HybridXWingKey: + pubKey, err := XWingPubKeyFromPem([]byte(kasPublicKeyPEM)) + if err != nil { + return nil, fmt.Errorf("X-Wing public key: %w", err) + } + return XWingWrapDEK(pubKey, dek) + case HybridSecp256r1MLKEM768Key: + pubKey, err := P256MLKEM768PubKeyFromPem([]byte(kasPublicKeyPEM)) + if err != nil { + return nil, fmt.Errorf("P-256+ML-KEM-768 public key: %w", err) + } + return P256MLKEM768WrapDEK(pubKey, dek) + case HybridSecp384r1MLKEM1024Key: + pubKey, err := P384MLKEM1024PubKeyFromPem([]byte(kasPublicKeyPEM)) + if err != nil { + return nil, fmt.Errorf("P-384+ML-KEM-1024 public key: %w", err) + } + return P384MLKEM1024WrapDEK(pubKey, dek) + default: + return nil, fmt.Errorf("unsupported hybrid key type: %s", ktype) + } +} + +// defaultTDFSalt returns the salt used for HKDF derivation in all TDF hybrid +// key wrapping schemes (X-Wing and NIST EC + ML-KEM). Defined here rather than +// in a per-scheme file so that any change applies uniformly across schemes. +func defaultTDFSalt() []byte { + digest := sha256.New() + digest.Write([]byte("TDF")) + return digest.Sum(nil) +} + +// rawToPEM wraps a fixed-size byte slice in a PEM block of the given type. Used +// by both X-Wing and NIST hybrid key serialization. +func rawToPEM(blockType string, raw []byte, expectedSize int) (string, error) { + if len(raw) != expectedSize { + return "", fmt.Errorf("invalid %s size: got %d want %d", blockType, len(raw), expectedSize) + } + + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: blockType, + Bytes: raw, + }) + if pemBytes == nil { + return "", fmt.Errorf("failed to encode %s to PEM", blockType) + } + + return string(pemBytes), nil +} + +// cloneOrNil returns a copy of data, or nil if data is empty. +func cloneOrNil(data []byte) []byte { + if len(data) == 0 { + return nil + } + return append([]byte(nil), data...) +} diff --git a/lib/ocrypto/hybrid_nist.go b/lib/ocrypto/hybrid_nist.go new file mode 100644 index 0000000000..ee0a575ac5 --- /dev/null +++ b/lib/ocrypto/hybrid_nist.go @@ -0,0 +1,519 @@ +package ocrypto + +import ( + "crypto/ecdh" + "crypto/mlkem" + "crypto/rand" + "crypto/sha256" + "encoding/asn1" + "fmt" + "io" + + "golang.org/x/crypto/hkdf" +) + +const ( + HybridSecp256r1MLKEM768Key KeyType = "hpqt:secp256r1-mlkem768" + HybridSecp384r1MLKEM1024Key KeyType = "hpqt:secp384r1-mlkem1024" +) + +// ML-KEM seed size (d || z) used by crypto/mlkem for private key serialization. +const mlkemSeedSize = 64 + +// Sizes for P-256 + ML-KEM-768 hybrid. +const ( + P256MLKEM768ECPublicKeySize = 65 // uncompressed P-256 point + P256MLKEM768ECPrivateKeySize = 32 // P-256 scalar + P256MLKEM768MLKEMPubKeySize = 1184 // mlkem768 encapsulation key + P256MLKEM768MLKEMPrivKeySize = mlkemSeedSize + P256MLKEM768MLKEMCtSize = 1088 // mlkem768 ciphertext + + P256MLKEM768PublicKeySize = P256MLKEM768ECPublicKeySize + P256MLKEM768MLKEMPubKeySize // 1249 + P256MLKEM768PrivateKeySize = P256MLKEM768ECPrivateKeySize + P256MLKEM768MLKEMPrivKeySize // 96 + P256MLKEM768CiphertextSize = P256MLKEM768ECPublicKeySize + P256MLKEM768MLKEMCtSize // 1153 + + PEMBlockP256MLKEM768PublicKey = "SECP256R1 MLKEM768 PUBLIC KEY" + PEMBlockP256MLKEM768PrivateKey = "SECP256R1 MLKEM768 PRIVATE KEY" +) + +// Sizes for P-384 + ML-KEM-1024 hybrid. +const ( + P384MLKEM1024ECPublicKeySize = 97 // uncompressed P-384 point + P384MLKEM1024ECPrivateKeySize = 48 // P-384 scalar + P384MLKEM1024MLKEMPubKeySize = 1568 // mlkem1024 encapsulation key + P384MLKEM1024MLKEMPrivKeySize = mlkemSeedSize + P384MLKEM1024MLKEMCtSize = 1568 // mlkem1024 ciphertext + + P384MLKEM1024PublicKeySize = P384MLKEM1024ECPublicKeySize + P384MLKEM1024MLKEMPubKeySize // 1665 + P384MLKEM1024PrivateKeySize = P384MLKEM1024ECPrivateKeySize + P384MLKEM1024MLKEMPrivKeySize // 112 + P384MLKEM1024CiphertextSize = P384MLKEM1024ECPublicKeySize + P384MLKEM1024MLKEMCtSize // 1665 + + PEMBlockP384MLKEM1024PublicKey = "SECP384R1 MLKEM1024 PUBLIC KEY" + PEMBlockP384MLKEM1024PrivateKey = "SECP384R1 MLKEM1024 PRIVATE KEY" +) + +// AES-256 key size used for wrap key derivation. +const hybridNISTWrapKeySize = 32 + +// HybridNISTWrappedKey is the ASN.1 envelope stored in wrapped_key. +type HybridNISTWrappedKey struct { + HybridCiphertext []byte `asn1:"tag:0"` + EncryptedDEK []byte `asn1:"tag:1"` +} + +// hybridNISTParams captures the curve-specific parameters for a NIST hybrid scheme. +type hybridNISTParams struct { + curve ecdh.Curve + ecPubSize int + ecPrivSize int + mlkemPubSize int + mlkemPrivSize int + mlkemCtSize int + pubPEMBlock string + privPEMBlock string + keyType KeyType +} + +var p256mlkem768Params = hybridNISTParams{ + curve: ecdh.P256(), + ecPubSize: P256MLKEM768ECPublicKeySize, + ecPrivSize: P256MLKEM768ECPrivateKeySize, + mlkemPubSize: P256MLKEM768MLKEMPubKeySize, + mlkemPrivSize: P256MLKEM768MLKEMPrivKeySize, + mlkemCtSize: P256MLKEM768MLKEMCtSize, + pubPEMBlock: PEMBlockP256MLKEM768PublicKey, + privPEMBlock: PEMBlockP256MLKEM768PrivateKey, + keyType: HybridSecp256r1MLKEM768Key, +} + +var p384mlkem1024Params = hybridNISTParams{ + curve: ecdh.P384(), + ecPubSize: P384MLKEM1024ECPublicKeySize, + ecPrivSize: P384MLKEM1024ECPrivateKeySize, + mlkemPubSize: P384MLKEM1024MLKEMPubKeySize, + mlkemPrivSize: P384MLKEM1024MLKEMPrivKeySize, + mlkemCtSize: P384MLKEM1024MLKEMCtSize, + pubPEMBlock: PEMBlockP384MLKEM1024PublicKey, + privPEMBlock: PEMBlockP384MLKEM1024PrivateKey, + keyType: HybridSecp384r1MLKEM1024Key, +} + +// HybridNISTKeyPair holds a hybrid EC + ML-KEM keypair as raw bytes. +type HybridNISTKeyPair struct { + publicKey []byte + privateKey []byte + params *hybridNISTParams +} + +// HybridNISTEncryptor implements PublicKeyEncryptor for NIST hybrid schemes. +type HybridNISTEncryptor struct { + publicKey []byte + salt []byte + info []byte + params *hybridNISTParams +} + +// HybridNISTDecryptor implements PrivateKeyDecryptor for NIST hybrid schemes. +type HybridNISTDecryptor struct { + privateKey []byte + salt []byte + info []byte + params *hybridNISTParams +} + +// IsHybridKeyType returns true if the key type is a hybrid post-quantum type. +func IsHybridKeyType(kt KeyType) bool { + switch kt { //nolint:exhaustive // only handle hybrid types + case HybridXWingKey, HybridSecp256r1MLKEM768Key, HybridSecp384r1MLKEM1024Key: + return true + default: + return false + } +} + +// NewHybridKeyPair creates a key pair for the given hybrid key type. +func NewHybridKeyPair(kt KeyType) (KeyPair, error) { + switch kt { //nolint:exhaustive // only handle hybrid types + case HybridXWingKey: + return NewXWingKeyPair() + case HybridSecp256r1MLKEM768Key: + return NewP256MLKEM768KeyPair() + case HybridSecp384r1MLKEM1024Key: + return NewP384MLKEM1024KeyPair() + default: + return nil, fmt.Errorf("unsupported hybrid key type: %v", kt) + } +} + +func NewP256MLKEM768KeyPair() (HybridNISTKeyPair, error) { + return newHybridNISTKeyPair(&p256mlkem768Params, func() ([]byte, []byte, error) { + dk, err := mlkem.GenerateKey768() + if err != nil { + return nil, nil, err + } + return dk.EncapsulationKey().Bytes(), dk.Bytes(), nil + }) +} + +func NewP384MLKEM1024KeyPair() (HybridNISTKeyPair, error) { + return newHybridNISTKeyPair(&p384mlkem1024Params, func() ([]byte, []byte, error) { + dk, err := mlkem.GenerateKey1024() + if err != nil { + return nil, nil, err + } + return dk.EncapsulationKey().Bytes(), dk.Bytes(), nil + }) +} + +func newHybridNISTKeyPair(p *hybridNISTParams, genMLKEM func() (pub, priv []byte, err error)) (HybridNISTKeyPair, error) { + ecPriv, err := p.curve.GenerateKey(rand.Reader) + if err != nil { + return HybridNISTKeyPair{}, fmt.Errorf("ECDH key generation failed: %w", err) + } + ecPub := ecPriv.PublicKey().Bytes() // uncompressed point + ecPrivBytes := ecPriv.Bytes() // raw scalar + + mlkemPub, mlkemPriv, err := genMLKEM() + if err != nil { + return HybridNISTKeyPair{}, fmt.Errorf("ML-KEM key generation failed: %w", err) + } + + pubKey := make([]byte, 0, p.ecPubSize+p.mlkemPubSize) + pubKey = append(pubKey, ecPub...) + pubKey = append(pubKey, mlkemPub...) + + privKey := make([]byte, 0, p.ecPrivSize+p.mlkemPrivSize) + privKey = append(privKey, ecPrivBytes...) + privKey = append(privKey, mlkemPriv...) + + return HybridNISTKeyPair{ + publicKey: pubKey, + privateKey: privKey, + params: p, + }, nil +} + +func (k HybridNISTKeyPair) PublicKeyInPemFormat() (string, error) { + return rawToPEM(k.params.pubPEMBlock, k.publicKey, k.params.ecPubSize+k.params.mlkemPubSize) +} + +func (k HybridNISTKeyPair) PrivateKeyInPemFormat() (string, error) { + return rawToPEM(k.params.privPEMBlock, k.privateKey, k.params.ecPrivSize+k.params.mlkemPrivSize) +} + +func (k HybridNISTKeyPair) GetKeyType() KeyType { + return k.params.keyType +} + +func P256MLKEM768PubKeyFromPem(data []byte) ([]byte, error) { + return decodeSizedPEMBlock(data, PEMBlockP256MLKEM768PublicKey, P256MLKEM768PublicKeySize) +} + +func P256MLKEM768PrivateKeyFromPem(data []byte) ([]byte, error) { + return decodeSizedPEMBlock(data, PEMBlockP256MLKEM768PrivateKey, P256MLKEM768PrivateKeySize) +} + +func P384MLKEM1024PubKeyFromPem(data []byte) ([]byte, error) { + return decodeSizedPEMBlock(data, PEMBlockP384MLKEM1024PublicKey, P384MLKEM1024PublicKeySize) +} + +func P384MLKEM1024PrivateKeyFromPem(data []byte) ([]byte, error) { + return decodeSizedPEMBlock(data, PEMBlockP384MLKEM1024PrivateKey, P384MLKEM1024PrivateKeySize) +} + +func NewP256MLKEM768Encryptor(publicKey, salt, info []byte) (*HybridNISTEncryptor, error) { + return newHybridNISTEncryptor(&p256mlkem768Params, publicKey, salt, info) +} + +func NewP384MLKEM1024Encryptor(publicKey, salt, info []byte) (*HybridNISTEncryptor, error) { + return newHybridNISTEncryptor(&p384mlkem1024Params, publicKey, salt, info) +} + +func newHybridNISTEncryptor(p *hybridNISTParams, publicKey, salt, info []byte) (*HybridNISTEncryptor, error) { + expectedSize := p.ecPubSize + p.mlkemPubSize + if len(publicKey) != expectedSize { + return nil, fmt.Errorf("invalid %s public key size: got %d want %d", p.keyType, len(publicKey), expectedSize) + } + return &HybridNISTEncryptor{ + publicKey: append([]byte(nil), publicKey...), + salt: cloneOrNil(salt), + info: cloneOrNil(info), + params: p, + }, nil +} + +func (e *HybridNISTEncryptor) Encrypt(data []byte) ([]byte, error) { + return hybridNISTWrapDEK(e.params, e.publicKey, data, e.salt, e.info) +} + +func (e *HybridNISTEncryptor) PublicKeyInPemFormat() (string, error) { + return rawToPEM(e.params.pubPEMBlock, e.publicKey, e.params.ecPubSize+e.params.mlkemPubSize) +} + +func (e *HybridNISTEncryptor) Type() SchemeType { return Hybrid } +func (e *HybridNISTEncryptor) KeyType() KeyType { return e.params.keyType } +func (e *HybridNISTEncryptor) EphemeralKey() []byte { return nil } + +func (e *HybridNISTEncryptor) Metadata() (map[string]string, error) { + return make(map[string]string), nil +} + +func NewP256MLKEM768Decryptor(privateKey []byte) (*HybridNISTDecryptor, error) { + return NewSaltedP256MLKEM768Decryptor(privateKey, defaultTDFSalt(), nil) +} + +func NewSaltedP256MLKEM768Decryptor(privateKey, salt, info []byte) (*HybridNISTDecryptor, error) { + return newHybridNISTDecryptor(&p256mlkem768Params, privateKey, salt, info) +} + +func NewP384MLKEM1024Decryptor(privateKey []byte) (*HybridNISTDecryptor, error) { + return NewSaltedP384MLKEM1024Decryptor(privateKey, defaultTDFSalt(), nil) +} + +func NewSaltedP384MLKEM1024Decryptor(privateKey, salt, info []byte) (*HybridNISTDecryptor, error) { + return newHybridNISTDecryptor(&p384mlkem1024Params, privateKey, salt, info) +} + +func newHybridNISTDecryptor(p *hybridNISTParams, privateKey, salt, info []byte) (*HybridNISTDecryptor, error) { + expectedSize := p.ecPrivSize + p.mlkemPrivSize + if len(privateKey) != expectedSize { + return nil, fmt.Errorf("invalid %s private key size: got %d want %d", p.keyType, len(privateKey), expectedSize) + } + return &HybridNISTDecryptor{ + privateKey: append([]byte(nil), privateKey...), + salt: cloneOrNil(salt), + info: cloneOrNil(info), + params: p, + }, nil +} + +func (d *HybridNISTDecryptor) Decrypt(data []byte) ([]byte, error) { + return hybridNISTUnwrapDEK(d.params, d.privateKey, data, d.salt, d.info) +} + +func P256MLKEM768WrapDEK(publicKeyRaw, dek []byte) ([]byte, error) { + return hybridNISTWrapDEK(&p256mlkem768Params, publicKeyRaw, dek, defaultTDFSalt(), nil) +} + +func P256MLKEM768UnwrapDEK(privateKeyRaw, wrappedDER []byte) ([]byte, error) { + return hybridNISTUnwrapDEK(&p256mlkem768Params, privateKeyRaw, wrappedDER, defaultTDFSalt(), nil) +} + +func P384MLKEM1024WrapDEK(publicKeyRaw, dek []byte) ([]byte, error) { + return hybridNISTWrapDEK(&p384mlkem1024Params, publicKeyRaw, dek, defaultTDFSalt(), nil) +} + +func P384MLKEM1024UnwrapDEK(privateKeyRaw, wrappedDER []byte) ([]byte, error) { + return hybridNISTUnwrapDEK(&p384mlkem1024Params, privateKeyRaw, wrappedDER, defaultTDFSalt(), nil) +} + +// hybridNISTEncapsulate performs hybrid encapsulation: +// 1. Generates an ephemeral EC key and computes ECDH shared secret +// 2. Encapsulates ML-KEM to produce a post-quantum shared secret +// 3. Combines both secrets (ECDH || ML-KEM) +// 4. Builds hybrid ciphertext (ephemeral EC point || ML-KEM ciphertext) +// +// Returns (combinedSecret, hybridCiphertext) without applying KDF or encryption. +func hybridNISTEncapsulate(p *hybridNISTParams, publicKeyRaw []byte) ([]byte, []byte, error) { + expectedPubSize := p.ecPubSize + p.mlkemPubSize + if len(publicKeyRaw) != expectedPubSize { + return nil, nil, fmt.Errorf("invalid %s public key size: got %d want %d", p.keyType, len(publicKeyRaw), expectedPubSize) + } + + ecPubBytes := publicKeyRaw[:p.ecPubSize] + mlkemPubBytes := publicKeyRaw[p.ecPubSize:] + + // ECDH: generate ephemeral key, compute shared secret + ecPub, err := p.curve.NewPublicKey(ecPubBytes) + if err != nil { + return nil, nil, fmt.Errorf("invalid EC public key: %w", err) + } + ephemeral, err := p.curve.GenerateKey(rand.Reader) + if err != nil { + return nil, nil, fmt.Errorf("ECDH ephemeral key generation failed: %w", err) + } + ecdhSecret, err := ephemeral.ECDH(ecPub) + if err != nil { + return nil, nil, fmt.Errorf("ECDH failed: %w", err) + } + ephemeralPub := ephemeral.PublicKey().Bytes() + + // ML-KEM: encapsulate + var mlkemSecret, mlkemCt []byte + switch p.keyType { //nolint:exhaustive // only NIST hybrid types + case HybridSecp256r1MLKEM768Key: + ek, ekErr := mlkem.NewEncapsulationKey768(mlkemPubBytes) + if ekErr != nil { + return nil, nil, fmt.Errorf("mlkem768 encapsulation key: %w", ekErr) + } + mlkemSecret, mlkemCt = ek.Encapsulate() + case HybridSecp384r1MLKEM1024Key: + ek, ekErr := mlkem.NewEncapsulationKey1024(mlkemPubBytes) + if ekErr != nil { + return nil, nil, fmt.Errorf("mlkem1024 encapsulation key: %w", ekErr) + } + mlkemSecret, mlkemCt = ek.Encapsulate() + default: + return nil, nil, fmt.Errorf("unsupported ML-KEM key type: %s", p.keyType) + } + + // Combine secrets: ECDH || ML-KEM + combinedSecret := make([]byte, 0, len(ecdhSecret)+len(mlkemSecret)) + combinedSecret = append(combinedSecret, ecdhSecret...) + combinedSecret = append(combinedSecret, mlkemSecret...) + + // Build hybrid ciphertext: ephemeral EC point || ML-KEM ciphertext + hybridCt := make([]byte, 0, len(ephemeralPub)+len(mlkemCt)) + hybridCt = append(hybridCt, ephemeralPub...) + hybridCt = append(hybridCt, mlkemCt...) + + return combinedSecret, hybridCt, nil +} + +// P256MLKEM768Encapsulate performs P-256 ECDH + ML-KEM-768 hybrid encapsulation. +func P256MLKEM768Encapsulate(publicKeyRaw []byte) ([]byte, []byte, error) { + return hybridNISTEncapsulate(&p256mlkem768Params, publicKeyRaw) +} + +// P384MLKEM1024Encapsulate performs P-384 ECDH + ML-KEM-1024 hybrid encapsulation. +func P384MLKEM1024Encapsulate(publicKeyRaw []byte) ([]byte, []byte, error) { + return hybridNISTEncapsulate(&p384mlkem1024Params, publicKeyRaw) +} + +func hybridNISTWrapDEK(p *hybridNISTParams, publicKeyRaw, dek, salt, info []byte) ([]byte, error) { + combinedSecret, hybridCt, err := hybridNISTEncapsulate(p, publicKeyRaw) + if err != nil { + return nil, err + } + + // Derive AES-256 wrap key via HKDF + wrapKey, err := deriveHybridNISTWrapKey(combinedSecret, salt, info) + if err != nil { + return nil, err + } + + // AES-GCM encrypt DEK + gcm, err := NewAESGcm(wrapKey) + if err != nil { + return nil, fmt.Errorf("NewAESGcm failed: %w", err) + } + encryptedDEK, err := gcm.Encrypt(dek) + if err != nil { + return nil, fmt.Errorf("AES-GCM encrypt failed: %w", err) + } + + wrappedDER, err := asn1.Marshal(HybridNISTWrappedKey{ + HybridCiphertext: hybridCt, + EncryptedDEK: encryptedDEK, + }) + if err != nil { + return nil, fmt.Errorf("asn1.Marshal failed: %w", err) + } + + return wrappedDER, nil +} + +func hybridNISTUnwrapDEK(p *hybridNISTParams, privateKeyRaw, wrappedDER, salt, info []byte) ([]byte, error) { + expectedPrivSize := p.ecPrivSize + p.mlkemPrivSize + if len(privateKeyRaw) != expectedPrivSize { + return nil, fmt.Errorf("invalid %s private key size: got %d want %d", p.keyType, len(privateKeyRaw), expectedPrivSize) + } + + var wrapped HybridNISTWrappedKey + rest, err := asn1.Unmarshal(wrappedDER, &wrapped) + if err != nil { + return nil, fmt.Errorf("asn1.Unmarshal failed: %w", err) + } + if len(rest) != 0 { + return nil, fmt.Errorf("asn1.Unmarshal left %d trailing bytes", len(rest)) + } + + expectedCtSize := p.ecPubSize + p.mlkemCtSize + if len(wrapped.HybridCiphertext) != expectedCtSize { + return nil, fmt.Errorf("invalid %s ciphertext size: got %d want %d", + p.keyType, len(wrapped.HybridCiphertext), expectedCtSize) + } + + // Split hybrid ciphertext + ephemeralPubBytes := wrapped.HybridCiphertext[:p.ecPubSize] + mlkemCtBytes := wrapped.HybridCiphertext[p.ecPubSize:] + + // Split private key + ecPrivBytes := privateKeyRaw[:p.ecPrivSize] + mlkemPrivBytes := privateKeyRaw[p.ecPrivSize:] + + // ECDH: reconstruct shared secret + ecPriv, err := p.curve.NewPrivateKey(ecPrivBytes) + if err != nil { + return nil, fmt.Errorf("invalid EC private key: %w", err) + } + ephemeralPub, err := p.curve.NewPublicKey(ephemeralPubBytes) + if err != nil { + return nil, fmt.Errorf("invalid ephemeral EC public key: %w", err) + } + ecdhSecret, err := ecPriv.ECDH(ephemeralPub) + if err != nil { + return nil, fmt.Errorf("ECDH failed: %w", err) + } + + // ML-KEM: decapsulate. Implicit rejection (FIPS 203 §6.3) means a wrong-key + // ciphertext yields a pseudorandom shared secret without an error here; + // authentication is enforced by the AES-GCM decrypt below. + var mlkemSecret []byte + switch p.keyType { //nolint:exhaustive // only NIST hybrid types + case HybridSecp256r1MLKEM768Key: + dk, dkErr := mlkem.NewDecapsulationKey768(mlkemPrivBytes) + if dkErr != nil { + return nil, fmt.Errorf("mlkem768 decapsulation key: %w", dkErr) + } + mlkemSecret, err = dk.Decapsulate(mlkemCtBytes) + case HybridSecp384r1MLKEM1024Key: + dk, dkErr := mlkem.NewDecapsulationKey1024(mlkemPrivBytes) + if dkErr != nil { + return nil, fmt.Errorf("mlkem1024 decapsulation key: %w", dkErr) + } + mlkemSecret, err = dk.Decapsulate(mlkemCtBytes) + default: + return nil, fmt.Errorf("unsupported ML-KEM key type: %s", p.keyType) + } + if err != nil { + return nil, fmt.Errorf("ML-KEM decapsulate failed: %w", err) + } + + // Combine secrets: ECDH || ML-KEM + combinedSecret := make([]byte, 0, len(ecdhSecret)+len(mlkemSecret)) + combinedSecret = append(combinedSecret, ecdhSecret...) + combinedSecret = append(combinedSecret, mlkemSecret...) + + // Derive AES-256 wrap key via HKDF + wrapKey, err := deriveHybridNISTWrapKey(combinedSecret, salt, info) + if err != nil { + return nil, err + } + + // AES-GCM decrypt DEK + gcm, err := NewAESGcm(wrapKey) + if err != nil { + return nil, fmt.Errorf("NewAESGcm failed: %w", err) + } + plaintext, err := gcm.Decrypt(wrapped.EncryptedDEK) + if err != nil { + return nil, fmt.Errorf("AES-GCM decrypt failed: %w", err) + } + + return plaintext, nil +} + +func deriveHybridNISTWrapKey(combinedSecret, salt, info []byte) ([]byte, error) { + if len(salt) == 0 { + salt = defaultTDFSalt() + } + + hkdfObj := hkdf.New(sha256.New, combinedSecret, salt, info) + derivedKey := make([]byte, hybridNISTWrapKeySize) + if _, err := io.ReadFull(hkdfObj, derivedKey); err != nil { + return nil, fmt.Errorf("hkdf failure: %w", err) + } + + return derivedKey, nil +} diff --git a/lib/ocrypto/hybrid_nist_test.go b/lib/ocrypto/hybrid_nist_test.go new file mode 100644 index 0000000000..e7c9808aae --- /dev/null +++ b/lib/ocrypto/hybrid_nist_test.go @@ -0,0 +1,240 @@ +package ocrypto + +import ( + "encoding/asn1" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestP256MLKEM768KeyPairAndPEM(t *testing.T) { + keyPair, err := NewP256MLKEM768KeyPair() + require.NoError(t, err) + + publicPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + privatePEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + + publicKey, err := P256MLKEM768PubKeyFromPem([]byte(publicPEM)) + require.NoError(t, err) + privateKey, err := P256MLKEM768PrivateKeyFromPem([]byte(privatePEM)) + require.NoError(t, err) + + assert.Len(t, publicKey, P256MLKEM768PublicKeySize) + assert.Len(t, privateKey, P256MLKEM768PrivateKeySize) + assert.Equal(t, HybridSecp256r1MLKEM768Key, keyPair.GetKeyType()) +} + +func TestP384MLKEM1024KeyPairAndPEM(t *testing.T) { + keyPair, err := NewP384MLKEM1024KeyPair() + require.NoError(t, err) + + publicPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + privatePEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + + publicKey, err := P384MLKEM1024PubKeyFromPem([]byte(publicPEM)) + require.NoError(t, err) + privateKey, err := P384MLKEM1024PrivateKeyFromPem([]byte(privatePEM)) + require.NoError(t, err) + + assert.Len(t, publicKey, P384MLKEM1024PublicKeySize) + assert.Len(t, privateKey, P384MLKEM1024PrivateKeySize) + assert.Equal(t, HybridSecp384r1MLKEM1024Key, keyPair.GetKeyType()) +} + +func TestNewKeyPairP256MLKEM768(t *testing.T) { + keyPair, err := NewP256MLKEM768KeyPair() + require.NoError(t, err) + assert.Equal(t, HybridSecp256r1MLKEM768Key, keyPair.GetKeyType()) +} + +func TestNewKeyPairP384MLKEM1024(t *testing.T) { + keyPair, err := NewP384MLKEM1024KeyPair() + require.NoError(t, err) + assert.Equal(t, HybridSecp384r1MLKEM1024Key, keyPair.GetKeyType()) +} + +func TestP256MLKEM768WrapUnwrapRoundTrip(t *testing.T) { + keyPair, err := NewP256MLKEM768KeyPair() + require.NoError(t, err) + + dek := []byte("0123456789abcdef0123456789abcdef") + wrapped, err := P256MLKEM768WrapDEK(keyPair.publicKey, dek) + require.NoError(t, err) + + plaintext, err := P256MLKEM768UnwrapDEK(keyPair.privateKey, wrapped) + require.NoError(t, err) + assert.Equal(t, dek, plaintext) +} + +func TestP384MLKEM1024WrapUnwrapRoundTrip(t *testing.T) { + keyPair, err := NewP384MLKEM1024KeyPair() + require.NoError(t, err) + + dek := []byte("0123456789abcdef0123456789abcdef") + wrapped, err := P384MLKEM1024WrapDEK(keyPair.publicKey, dek) + require.NoError(t, err) + + plaintext, err := P384MLKEM1024UnwrapDEK(keyPair.privateKey, wrapped) + require.NoError(t, err) + assert.Equal(t, dek, plaintext) +} + +func TestP256MLKEM768WrapUnwrapWrongKeyFails(t *testing.T) { + keyPair, err := NewP256MLKEM768KeyPair() + require.NoError(t, err) + wrongKeyPair, err := NewP256MLKEM768KeyPair() + require.NoError(t, err) + + wrapped, err := P256MLKEM768WrapDEK(keyPair.publicKey, []byte("top secret dek")) + require.NoError(t, err) + + _, err = P256MLKEM768UnwrapDEK(wrongKeyPair.privateKey, wrapped) + require.Error(t, err) + // Wrong-key failure must surface through AES-GCM authentication, not a + // parse/size mismatch — ML-KEM uses implicit rejection so DecapsulateTo + // returns a pseudorandom secret rather than an error. + assert.ErrorContains(t, err, "AES-GCM decrypt failed") +} + +func TestP384MLKEM1024WrapUnwrapWrongKeyFails(t *testing.T) { + keyPair, err := NewP384MLKEM1024KeyPair() + require.NoError(t, err) + wrongKeyPair, err := NewP384MLKEM1024KeyPair() + require.NoError(t, err) + + wrapped, err := P384MLKEM1024WrapDEK(keyPair.publicKey, []byte("top secret dek")) + require.NoError(t, err) + + _, err = P384MLKEM1024UnwrapDEK(wrongKeyPair.privateKey, wrapped) + require.Error(t, err) + assert.ErrorContains(t, err, "AES-GCM decrypt failed") +} + +func TestHybridNISTWrappedKeyASN1RoundTrip(t *testing.T) { + original := HybridNISTWrappedKey{ + HybridCiphertext: []byte("hybrid-ciphertext-data"), + EncryptedDEK: []byte("encrypted-dek-data"), + } + + der, err := asn1.Marshal(original) + require.NoError(t, err) + + var decoded HybridNISTWrappedKey + rest, err := asn1.Unmarshal(der, &decoded) + require.NoError(t, err) + assert.Empty(t, rest) + assert.Equal(t, original, decoded) +} + +func TestP256MLKEM768PEMDispatch(t *testing.T) { + keyPair, err := NewP256MLKEM768KeyPair() + require.NoError(t, err) + + publicPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + privatePEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + + encryptor, err := FromPublicPEMWithSalt(publicPEM, []byte("salt"), []byte("info")) + require.NoError(t, err) + + decryptor, err := FromPrivatePEMWithSalt(privatePEM, []byte("salt"), []byte("info")) + require.NoError(t, err) + + nistEncryptor, ok := encryptor.(*HybridNISTEncryptor) + require.True(t, ok) + assert.Equal(t, Hybrid, nistEncryptor.Type()) + assert.Equal(t, HybridSecp256r1MLKEM768Key, nistEncryptor.KeyType()) + assert.Nil(t, nistEncryptor.EphemeralKey()) + + metadata, err := nistEncryptor.Metadata() + require.NoError(t, err) + assert.Empty(t, metadata) + + nistDecryptor, ok := decryptor.(*HybridNISTDecryptor) + require.True(t, ok) + + wrapped, err := nistEncryptor.Encrypt([]byte("dispatch-dek")) + require.NoError(t, err) + + plaintext, err := nistDecryptor.Decrypt(wrapped) + require.NoError(t, err) + assert.Equal(t, []byte("dispatch-dek"), plaintext) +} + +func TestP384MLKEM1024PEMDispatch(t *testing.T) { + keyPair, err := NewP384MLKEM1024KeyPair() + require.NoError(t, err) + + publicPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + privatePEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + + encryptor, err := FromPublicPEMWithSalt(publicPEM, []byte("salt"), []byte("info")) + require.NoError(t, err) + + decryptor, err := FromPrivatePEMWithSalt(privatePEM, []byte("salt"), []byte("info")) + require.NoError(t, err) + + nistEncryptor, ok := encryptor.(*HybridNISTEncryptor) + require.True(t, ok) + assert.Equal(t, Hybrid, nistEncryptor.Type()) + assert.Equal(t, HybridSecp384r1MLKEM1024Key, nistEncryptor.KeyType()) + assert.Nil(t, nistEncryptor.EphemeralKey()) + + nistDecryptor, ok := decryptor.(*HybridNISTDecryptor) + require.True(t, ok) + + wrapped, err := nistEncryptor.Encrypt([]byte("dispatch-dek-384")) + require.NoError(t, err) + + plaintext, err := nistDecryptor.Decrypt(wrapped) + require.NoError(t, err) + assert.Equal(t, []byte("dispatch-dek-384"), plaintext) +} + +func TestP256MLKEM768Encapsulate(t *testing.T) { + keyPair, err := NewP256MLKEM768KeyPair() + require.NoError(t, err) + + pubKey, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + + pubKeyRaw, err := P256MLKEM768PubKeyFromPem([]byte(pubKey)) + require.NoError(t, err) + + combinedSecret, hybridCt, err := P256MLKEM768Encapsulate(pubKeyRaw) + require.NoError(t, err) + assert.NotEmpty(t, combinedSecret) + assert.Len(t, hybridCt, P256MLKEM768CiphertextSize) +} + +func TestP384MLKEM1024Encapsulate(t *testing.T) { + keyPair, err := NewP384MLKEM1024KeyPair() + require.NoError(t, err) + + pubKey, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + + pubKeyRaw, err := P384MLKEM1024PubKeyFromPem([]byte(pubKey)) + require.NoError(t, err) + + combinedSecret, hybridCt, err := P384MLKEM1024Encapsulate(pubKeyRaw) + require.NoError(t, err) + assert.NotEmpty(t, combinedSecret) + assert.Len(t, hybridCt, P384MLKEM1024CiphertextSize) +} + +func TestIsHybridKeyTypeIncludesNewTypes(t *testing.T) { + assert.True(t, IsHybridKeyType(HybridXWingKey)) + assert.True(t, IsHybridKeyType(HybridSecp256r1MLKEM768Key)) + assert.True(t, IsHybridKeyType(HybridSecp384r1MLKEM1024Key)) + assert.False(t, IsHybridKeyType(EC256Key)) + assert.False(t, IsHybridKeyType(RSA2048Key)) +} diff --git a/lib/ocrypto/xwing.go b/lib/ocrypto/xwing.go new file mode 100644 index 0000000000..1ea9d5ab25 --- /dev/null +++ b/lib/ocrypto/xwing.go @@ -0,0 +1,260 @@ +package ocrypto + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/asn1" + "encoding/pem" + "fmt" + "io" + + "github.com/cloudflare/circl/kem/xwing" + "golang.org/x/crypto/hkdf" +) + +const ( + HybridXWingKey KeyType = "hpqt:xwing" + + XWingPublicKeySize = xwing.PublicKeySize + XWingPrivateKeySize = xwing.PrivateKeySize + XWingCiphertextSize = xwing.CiphertextSize + + PEMBlockXWingPublicKey = "XWING PUBLIC KEY" + PEMBlockXWingPrivateKey = "XWING PRIVATE KEY" +) + +type XWingWrappedKey struct { + XWingCiphertext []byte `asn1:"tag:0"` + EncryptedDEK []byte `asn1:"tag:1"` +} + +type XWingKeyPair struct { + publicKey []byte + privateKey []byte +} + +type XWingEncryptor struct { + publicKey []byte + salt []byte + info []byte +} + +type XWingDecryptor struct { + privateKey []byte + salt []byte + info []byte +} + +func NewXWingKeyPair() (XWingKeyPair, error) { + sk, pk, err := xwing.GenerateKeyPair(rand.Reader) + if err != nil { + return XWingKeyPair{}, fmt.Errorf("xwing.GenerateKeyPair failed: %w", err) + } + + publicKey := make([]byte, XWingPublicKeySize) + privateKey := make([]byte, XWingPrivateKeySize) + pk.Pack(publicKey) + sk.Pack(privateKey) + + return XWingKeyPair{ + publicKey: publicKey, + privateKey: privateKey, + }, nil +} + +func (k XWingKeyPair) PublicKeyInPemFormat() (string, error) { + return rawToPEM(PEMBlockXWingPublicKey, k.publicKey, XWingPublicKeySize) +} + +func (k XWingKeyPair) PrivateKeyInPemFormat() (string, error) { + return rawToPEM(PEMBlockXWingPrivateKey, k.privateKey, XWingPrivateKeySize) +} + +func (k XWingKeyPair) GetKeyType() KeyType { + return HybridXWingKey +} + +func XWingPubKeyFromPem(data []byte) ([]byte, error) { + return decodeSizedPEMBlock(data, PEMBlockXWingPublicKey, XWingPublicKeySize) +} + +func XWingPrivateKeyFromPem(data []byte) ([]byte, error) { + return decodeSizedPEMBlock(data, PEMBlockXWingPrivateKey, XWingPrivateKeySize) +} + +func NewXWingEncryptor(publicKey, salt, info []byte) (*XWingEncryptor, error) { + if len(publicKey) != XWingPublicKeySize { + return nil, fmt.Errorf("invalid X-Wing public key size: got %d want %d", len(publicKey), XWingPublicKeySize) + } + + return &XWingEncryptor{ + publicKey: append([]byte(nil), publicKey...), + salt: cloneOrNil(salt), + info: cloneOrNil(info), + }, nil +} + +func (e *XWingEncryptor) Encrypt(data []byte) ([]byte, error) { + return xwingWrapDEK(e.publicKey, data, e.salt, e.info) +} + +func (e *XWingEncryptor) PublicKeyInPemFormat() (string, error) { + return rawToPEM(PEMBlockXWingPublicKey, e.publicKey, XWingPublicKeySize) +} + +func (e *XWingEncryptor) Type() SchemeType { + return Hybrid +} + +func (e *XWingEncryptor) KeyType() KeyType { + return HybridXWingKey +} + +func (e *XWingEncryptor) EphemeralKey() []byte { + return nil +} + +func (e *XWingEncryptor) Metadata() (map[string]string, error) { + return make(map[string]string), nil +} + +func NewXWingDecryptor(privateKey []byte) (*XWingDecryptor, error) { + return NewSaltedXWingDecryptor(privateKey, defaultTDFSalt(), nil) +} + +func NewSaltedXWingDecryptor(privateKey, salt, info []byte) (*XWingDecryptor, error) { + if len(privateKey) != XWingPrivateKeySize { + return nil, fmt.Errorf("invalid X-Wing private key size: got %d want %d", len(privateKey), XWingPrivateKeySize) + } + + return &XWingDecryptor{ + privateKey: append([]byte(nil), privateKey...), + salt: cloneOrNil(salt), + info: cloneOrNil(info), + }, nil +} + +func (d *XWingDecryptor) Decrypt(data []byte) ([]byte, error) { + return xwingUnwrapDEK(d.privateKey, data, d.salt, d.info) +} + +func XWingWrapDEK(publicKeyRaw, dek []byte) ([]byte, error) { + return xwingWrapDEK(publicKeyRaw, dek, defaultTDFSalt(), nil) +} + +func XWingUnwrapDEK(privateKeyRaw, wrappedDER []byte) ([]byte, error) { + return xwingUnwrapDEK(privateKeyRaw, wrappedDER, defaultTDFSalt(), nil) +} + +// XWingEncapsulate performs the X-Wing KEM encapsulation, returning the shared +// secret and ciphertext without applying KDF or encryption. +func XWingEncapsulate(publicKeyRaw []byte) ([]byte, []byte, error) { + if len(publicKeyRaw) != XWingPublicKeySize { + return nil, nil, fmt.Errorf("invalid X-Wing public key size: got %d want %d", len(publicKeyRaw), XWingPublicKeySize) + } + + sharedSecret, ciphertext, err := xwing.Encapsulate(publicKeyRaw, nil) + if err != nil { + return nil, nil, fmt.Errorf("xwing.Encapsulate failed: %w", err) + } + + return sharedSecret, ciphertext, nil +} + +func xwingWrapDEK(publicKeyRaw, dek, salt, info []byte) ([]byte, error) { + sharedSecret, ciphertext, err := XWingEncapsulate(publicKeyRaw) + if err != nil { + return nil, err + } + + wrapKey, err := deriveXWingWrapKey(sharedSecret, salt, info) + if err != nil { + return nil, err + } + + gcm, err := NewAESGcm(wrapKey) + if err != nil { + return nil, fmt.Errorf("NewAESGcm failed: %w", err) + } + + encryptedDEK, err := gcm.Encrypt(dek) + if err != nil { + return nil, fmt.Errorf("AES-GCM encrypt failed: %w", err) + } + + wrappedDER, err := asn1.Marshal(XWingWrappedKey{ + XWingCiphertext: ciphertext, + EncryptedDEK: encryptedDEK, + }) + if err != nil { + return nil, fmt.Errorf("asn1.Marshal failed: %w", err) + } + + return wrappedDER, nil +} + +func xwingUnwrapDEK(privateKeyRaw, wrappedDER, salt, info []byte) ([]byte, error) { + if len(privateKeyRaw) != XWingPrivateKeySize { + return nil, fmt.Errorf("invalid X-Wing private key size: got %d want %d", len(privateKeyRaw), XWingPrivateKeySize) + } + + var wrappedKey XWingWrappedKey + rest, err := asn1.Unmarshal(wrappedDER, &wrappedKey) + if err != nil { + return nil, fmt.Errorf("asn1.Unmarshal failed: %w", err) + } + if len(rest) != 0 { + return nil, fmt.Errorf("asn1.Unmarshal left %d trailing bytes", len(rest)) + } + if len(wrappedKey.XWingCiphertext) != XWingCiphertextSize { + return nil, fmt.Errorf("invalid X-Wing ciphertext size: got %d want %d", len(wrappedKey.XWingCiphertext), XWingCiphertextSize) + } + + sharedSecret := xwing.Decapsulate(wrappedKey.XWingCiphertext, privateKeyRaw) + + wrapKey, err := deriveXWingWrapKey(sharedSecret, salt, info) + if err != nil { + return nil, err + } + + gcm, err := NewAESGcm(wrapKey) + if err != nil { + return nil, fmt.Errorf("NewAESGcm failed: %w", err) + } + + plaintext, err := gcm.Decrypt(wrappedKey.EncryptedDEK) + if err != nil { + return nil, fmt.Errorf("AES-GCM decrypt failed: %w", err) + } + + return plaintext, nil +} + +func deriveXWingWrapKey(sharedSecret, salt, info []byte) ([]byte, error) { + if len(salt) == 0 { + salt = defaultTDFSalt() + } + + hkdfObj := hkdf.New(sha256.New, sharedSecret, salt, info) + derivedKey := make([]byte, xwing.SharedKeySize) + if _, err := io.ReadFull(hkdfObj, derivedKey); err != nil { + return nil, fmt.Errorf("hkdf failure: %w", err) + } + + return derivedKey, nil +} + +func decodeSizedPEMBlock(data []byte, blockType string, expectedSize int) ([]byte, error) { + block, _ := pem.Decode(data) + if block == nil { + return nil, fmt.Errorf("failed to parse PEM formatted %s", blockType) + } + if block.Type != blockType { + return nil, fmt.Errorf("unexpected PEM block type: got %s want %s", block.Type, blockType) + } + if len(block.Bytes) != expectedSize { + return nil, fmt.Errorf("invalid %s size: got %d want %d", blockType, len(block.Bytes), expectedSize) + } + + return append([]byte(nil), block.Bytes...), nil +} diff --git a/lib/ocrypto/xwing_test.go b/lib/ocrypto/xwing_test.go new file mode 100644 index 0000000000..c0bf783ba9 --- /dev/null +++ b/lib/ocrypto/xwing_test.go @@ -0,0 +1,129 @@ +package ocrypto + +import ( + "encoding/asn1" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestXWingKeyPairAndPEM(t *testing.T) { + keyPair, err := NewXWingKeyPair() + require.NoError(t, err) + + publicPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + privatePEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + + publicKey, err := XWingPubKeyFromPem([]byte(publicPEM)) + require.NoError(t, err) + privateKey, err := XWingPrivateKeyFromPem([]byte(privatePEM)) + require.NoError(t, err) + + assert.Len(t, publicKey, XWingPublicKeySize) + assert.Len(t, privateKey, XWingPrivateKeySize) + assert.Equal(t, HybridXWingKey, keyPair.GetKeyType()) +} + +func TestNewKeyPairXWing(t *testing.T) { + keyPair, err := NewXWingKeyPair() + require.NoError(t, err) + assert.Equal(t, HybridXWingKey, keyPair.GetKeyType()) +} + +func TestXWingWrapUnwrapRoundTrip(t *testing.T) { + keyPair, err := NewXWingKeyPair() + require.NoError(t, err) + + dek := []byte("0123456789abcdef0123456789abcdef") + wrapped, err := XWingWrapDEK(keyPair.publicKey, dek) + require.NoError(t, err) + + plaintext, err := XWingUnwrapDEK(keyPair.privateKey, wrapped) + require.NoError(t, err) + assert.Equal(t, dek, plaintext) +} + +func TestXWingWrapUnwrapWrongKeyFails(t *testing.T) { + keyPair, err := NewXWingKeyPair() + require.NoError(t, err) + wrongKeyPair, err := NewXWingKeyPair() + require.NoError(t, err) + + wrapped, err := XWingWrapDEK(keyPair.publicKey, []byte("top secret dek")) + require.NoError(t, err) + + _, err = XWingUnwrapDEK(wrongKeyPair.privateKey, wrapped) + require.Error(t, err) + assert.Contains(t, err.Error(), "AES-GCM decrypt failed") +} + +func TestXWingWrappedKeyASN1RoundTrip(t *testing.T) { + original := XWingWrappedKey{ + XWingCiphertext: []byte("ciphertext"), + EncryptedDEK: []byte("encrypted-dek"), + } + + der, err := asn1.Marshal(original) + require.NoError(t, err) + + var decoded XWingWrappedKey + rest, err := asn1.Unmarshal(der, &decoded) + require.NoError(t, err) + assert.Empty(t, rest) + assert.Equal(t, original, decoded) +} + +func TestXWingPEMDispatch(t *testing.T) { + keyPair, err := NewXWingKeyPair() + require.NoError(t, err) + + publicPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + privatePEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + + encryptor, err := FromPublicPEMWithSalt(publicPEM, []byte("salt"), []byte("info")) + require.NoError(t, err) + + decryptor, err := FromPrivatePEMWithSalt(privatePEM, []byte("salt"), []byte("info")) + require.NoError(t, err) + + xwingEncryptor, ok := encryptor.(*XWingEncryptor) + require.True(t, ok) + assert.Equal(t, Hybrid, xwingEncryptor.Type()) + assert.Equal(t, HybridXWingKey, xwingEncryptor.KeyType()) + assert.Nil(t, xwingEncryptor.EphemeralKey()) + + metadata, err := xwingEncryptor.Metadata() + require.NoError(t, err) + assert.Empty(t, metadata) + + xwingDecryptor, ok := decryptor.(*XWingDecryptor) + require.True(t, ok) + + wrapped, err := xwingEncryptor.Encrypt([]byte("dispatch-dek")) + require.NoError(t, err) + + plaintext, err := xwingDecryptor.Decrypt(wrapped) + require.NoError(t, err) + assert.Equal(t, []byte("dispatch-dek"), plaintext) +} + +func TestXWingEncapsulate(t *testing.T) { + keyPair, err := NewXWingKeyPair() + require.NoError(t, err) + + sharedSecret, ciphertext, err := XWingEncapsulate(keyPair.publicKey) + require.NoError(t, err) + assert.Len(t, sharedSecret, 32) + assert.Len(t, ciphertext, XWingCiphertextSize) +} + +func TestXWingEncapsulateInvalidKeySize(t *testing.T) { + _, _, err := XWingEncapsulate([]byte("too-short")) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid X-Wing public key size") +} diff --git a/opentdf-dev.yaml b/opentdf-dev.yaml index 93a471101f..5a90cfa605 100644 --- a/opentdf-dev.yaml +++ b/opentdf-dev.yaml @@ -22,6 +22,7 @@ services: registered_kas_uri: http://localhost:8080 # Should match what you have registered for *this* KAS in the policy db. preview: ec_tdf_enabled: false + hybrid_tdf_enabled: false key_management: false root_key: a8c4824daafcfa38ed0d13002e92b08720e6c4fcee67d52e954c1a6e045907d1 # For local development testing only keyring: diff --git a/opentdf-example.yaml b/opentdf-example.yaml index a0b97da826..9bc9f90ef2 100644 --- a/opentdf-example.yaml +++ b/opentdf-example.yaml @@ -123,4 +123,16 @@ server: alg: ec:secp256r1 private: /keys/kas-ec-private.pem cert: /keys/kas-ec-cert.pem + - kid: x1 + alg: hpqt:xwing + private: /keys/kas-xwing-private.pem + cert: /keys/kas-xwing-public.pem + - kid: h1 + alg: hpqt:secp256r1-mlkem768 + private: /keys/kas-p256mlkem768-private.pem + cert: /keys/kas-p256mlkem768-public.pem + - kid: h2 + alg: hpqt:secp384r1-mlkem1024 + private: /keys/kas-p384mlkem1024-private.pem + cert: /keys/kas-p384mlkem1024-public.pem port: 8080 diff --git a/opentdf-kas-mode.yaml b/opentdf-kas-mode.yaml index ebcfb6f0c2..4bdd67e376 100644 --- a/opentdf-kas-mode.yaml +++ b/opentdf-kas-mode.yaml @@ -33,6 +33,12 @@ services: - kid: r1 alg: rsa:2048 legacy: true + - kid: x1 + alg: hpqt:xwing + - kid: h1 + alg: hpqt:secp256r1-mlkem768 + - kid: h2 + alg: hpqt:secp384r1-mlkem1024 server: public_hostname: localhost tls: @@ -123,4 +129,16 @@ server: alg: ec:secp256r1 private: kas-ec-private.pem cert: kas-ec-cert.pem + - kid: x1 + alg: hpqt:xwing + private: kas-xwing-private.pem + cert: kas-xwing-public.pem + - kid: h1 + alg: hpqt:secp256r1-mlkem768 + private: kas-p256mlkem768-private.pem + cert: kas-p256mlkem768-public.pem + - kid: h2 + alg: hpqt:secp384r1-mlkem1024 + private: kas-p384mlkem1024-private.pem + cert: kas-p384mlkem1024-public.pem port: 8181 diff --git a/otdfctl/cmd/policy/kasKeys.go b/otdfctl/cmd/policy/kasKeys.go index 57e0e51471..14f70c55ee 100644 --- a/otdfctl/cmd/policy/kasKeys.go +++ b/otdfctl/cmd/policy/kasKeys.go @@ -83,6 +83,12 @@ func generateKeyPair(alg policy.Algorithm) (ocrypto.KeyPair, error) { key, err = generateECCKey(ecSecp384Len) case policy.Algorithm_ALGORITHM_EC_P521: key, err = generateECCKey(ecSecp521Len) + case policy.Algorithm_ALGORITHM_HPQT_XWING: + key, err = ocrypto.NewKeyPair(ocrypto.HybridXWingKey) + case policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768: + key, err = ocrypto.NewKeyPair(ocrypto.HybridSecp256r1MLKEM768Key) + case policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024: + key, err = ocrypto.NewKeyPair(ocrypto.HybridSecp384r1MLKEM1024Key) case policy.Algorithm_ALGORITHM_UNSPECIFIED: fallthrough default: diff --git a/otdfctl/cmd/policy/kasKeys_test.go b/otdfctl/cmd/policy/kasKeys_test.go new file mode 100644 index 0000000000..f1fc882d3a --- /dev/null +++ b/otdfctl/cmd/policy/kasKeys_test.go @@ -0,0 +1,43 @@ +package policy + +import ( + "testing" + + "github.com/opentdf/platform/lib/ocrypto" + "github.com/opentdf/platform/protocol/go/policy" + "github.com/stretchr/testify/require" +) + +func TestGenerateKeyPair_Hybrid(t *testing.T) { + tests := []struct { + name string + alg policy.Algorithm + keyType ocrypto.KeyType + }{ + {"X-Wing", policy.Algorithm_ALGORITHM_HPQT_XWING, ocrypto.HybridXWingKey}, + {"P256-MLKEM768", policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768, ocrypto.HybridSecp256r1MLKEM768Key}, + {"P384-MLKEM1024", policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024, ocrypto.HybridSecp384r1MLKEM1024Key}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kp, err := generateKeyPair(tt.alg) + require.NoError(t, err) + require.Equal(t, tt.keyType, kp.GetKeyType()) + + pubPem, err := kp.PublicKeyInPemFormat() + require.NoError(t, err) + require.NotEmpty(t, pubPem) + + privPem, err := kp.PrivateKeyInPemFormat() + require.NoError(t, err) + require.NotEmpty(t, privPem) + }) + } +} + +func TestGenerateKeyPair_Unsupported(t *testing.T) { + _, err := generateKeyPair(policy.Algorithm_ALGORITHM_UNSPECIFIED) + require.Error(t, err) + require.Contains(t, err.Error(), "unsupported algorithm") +} diff --git a/otdfctl/docs/man/policy/kas-registry/key/create.md b/otdfctl/docs/man/policy/kas-registry/key/create.md index 7c5aea0d99..76f5a85af9 100644 --- a/otdfctl/docs/man/policy/kas-registry/key/create.md +++ b/otdfctl/docs/man/policy/kas-registry/key/create.md @@ -75,6 +75,9 @@ otdfctl policy kas-registry key create --key-id "aws-key" --algorithm "rsa:2048" | `ec:secp256r1` | | `ec:secp384r1` | | `ec:secp521r1` | + | `hpqt:xwing` | + | `hpqt:secp256r1-mlkem768` | + | `hpqt:secp384r1-mlkem1024` | 2. The `"mode"` specifies where the key that is encrypting TDFs is stored. All keys will be encrypted when stored in Virtru's DB, for modes `"local"` and `"provider"` diff --git a/otdfctl/docs/man/policy/kas-registry/key/import.md b/otdfctl/docs/man/policy/kas-registry/key/import.md index e7cc0a7595..b08a2ea843 100644 --- a/otdfctl/docs/man/policy/kas-registry/key/import.md +++ b/otdfctl/docs/man/policy/kas-registry/key/import.md @@ -79,3 +79,6 @@ otdfctl policy kas-registry key import --key-id "imported-key" --algorithm "rsa: | `ec:secp256r1` | | `ec:secp384r1` | | `ec:secp521r1` | + | `hpqt:xwing` | + | `hpqt:secp256r1-mlkem768` | + | `hpqt:secp384r1-mlkem1024` | diff --git a/otdfctl/docs/man/policy/kas-registry/key/rotate.md b/otdfctl/docs/man/policy/kas-registry/key/rotate.md index 09ec687906..726d85f74b 100644 --- a/otdfctl/docs/man/policy/kas-registry/key/rotate.md +++ b/otdfctl/docs/man/policy/kas-registry/key/rotate.md @@ -88,6 +88,9 @@ otdfctl policy kas-registry key rotate --key "public-key-old" --kas "Secondary K | `ec:secp256r1` | | `ec:secp384r1` | | `ec:secp521r1` | + | `hpqt:xwing` | + | `hpqt:secp256r1-mlkem768` | + | `hpqt:secp384r1-mlkem1024` | 2. The `"mode"` specifies where the key that is encrypting TDFs is stored. All keys will be encrypted when stored in Virtru's DB, for modes `"local"` and `"provider"` diff --git a/otdfctl/pkg/cli/sdkHelpers.go b/otdfctl/pkg/cli/sdkHelpers.go index 4b1fb9a5c3..943fbbc71e 100644 --- a/otdfctl/pkg/cli/sdkHelpers.go +++ b/otdfctl/pkg/cli/sdkHelpers.go @@ -125,6 +125,12 @@ func KeyAlgToEnum(alg string) (policy.Algorithm, error) { return policy.Algorithm_ALGORITHM_EC_P384, nil case "ec:secp521r1": return policy.Algorithm_ALGORITHM_EC_P521, nil + case "hpqt:xwing": + return policy.Algorithm_ALGORITHM_HPQT_XWING, nil + case "hpqt:secp256r1-mlkem768": + return policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768, nil + case "hpqt:secp384r1-mlkem1024": + return policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024, nil default: return policy.Algorithm_ALGORITHM_UNSPECIFIED, errors.New("invalid algorithm") } @@ -142,6 +148,12 @@ func KeyEnumToAlg(enum policy.Algorithm) (string, error) { return "ec:secp384r1", nil case policy.Algorithm_ALGORITHM_EC_P521: return "ec:secp521r1", nil + case policy.Algorithm_ALGORITHM_HPQT_XWING: + return "hpqt:xwing", nil + case policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768: + return "hpqt:secp256r1-mlkem768", nil + case policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024: + return "hpqt:secp384r1-mlkem1024", nil default: return "", errors.New("invalid enum algorithm") } diff --git a/otdfctl/pkg/cli/sdkHelpers_test.go b/otdfctl/pkg/cli/sdkHelpers_test.go new file mode 100644 index 0000000000..52d3c64fef --- /dev/null +++ b/otdfctl/pkg/cli/sdkHelpers_test.go @@ -0,0 +1,46 @@ +package cli + +import ( + "testing" + + "github.com/opentdf/platform/protocol/go/policy" + "github.com/stretchr/testify/require" +) + +func TestKeyAlgToEnum_RoundTrip(t *testing.T) { + tests := []struct { + alg string + enum policy.Algorithm + }{ + {"rsa:2048", policy.Algorithm_ALGORITHM_RSA_2048}, + {"rsa:4096", policy.Algorithm_ALGORITHM_RSA_4096}, + {"ec:secp256r1", policy.Algorithm_ALGORITHM_EC_P256}, + {"ec:secp384r1", policy.Algorithm_ALGORITHM_EC_P384}, + {"ec:secp521r1", policy.Algorithm_ALGORITHM_EC_P521}, + {"hpqt:xwing", policy.Algorithm_ALGORITHM_HPQT_XWING}, + {"hpqt:secp256r1-mlkem768", policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768}, + {"hpqt:secp384r1-mlkem1024", policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024}, + } + + for _, tt := range tests { + t.Run(tt.alg, func(t *testing.T) { + got, err := KeyAlgToEnum(tt.alg) + require.NoError(t, err) + require.Equal(t, tt.enum, got) + + back, err := KeyEnumToAlg(got) + require.NoError(t, err) + require.Equal(t, tt.alg, back) + }) + } +} + +func TestKeyAlgToEnum_Invalid(t *testing.T) { + _, err := KeyAlgToEnum("not-a-real-alg") + require.Error(t, err) +} + +func TestKeyEnumToAlg_Invalid(t *testing.T) { + _, err := KeyEnumToAlg(policy.Algorithm_ALGORITHM_UNSPECIFIED) + require.Error(t, err) +} diff --git a/otdfctl/pkg/utils/pemvalidate.go b/otdfctl/pkg/utils/pemvalidate.go index e9de274236..29fc17f8ef 100644 --- a/otdfctl/pkg/utils/pemvalidate.go +++ b/otdfctl/pkg/utils/pemvalidate.go @@ -41,6 +41,18 @@ func ValidatePublicKeyPEM(pemBytes []byte, expected policy.Algorithm) error { if enc.KeyType() != ocrypto.EC521Key { return errors.New("algorithm mismatch: expected EC P-521") } + case policy.Algorithm_ALGORITHM_HPQT_XWING: + if enc.KeyType() != ocrypto.HybridXWingKey { + return errors.New("algorithm mismatch: expected hybrid X-Wing (X25519 + ML-KEM-768)") + } + case policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768: + if enc.KeyType() != ocrypto.HybridSecp256r1MLKEM768Key { + return errors.New("algorithm mismatch: expected hybrid NIST P-256 + ML-KEM-768") + } + case policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024: + if enc.KeyType() != ocrypto.HybridSecp384r1MLKEM1024Key { + return errors.New("algorithm mismatch: expected hybrid NIST P-384 + ML-KEM-1024") + } case policy.Algorithm_ALGORITHM_UNSPECIFIED: fallthrough default: diff --git a/otdfctl/pkg/utils/pemvalidate_test.go b/otdfctl/pkg/utils/pemvalidate_test.go index 082b403f5d..b2b33fa59d 100644 --- a/otdfctl/pkg/utils/pemvalidate_test.go +++ b/otdfctl/pkg/utils/pemvalidate_test.go @@ -9,6 +9,7 @@ import ( "encoding/pem" "testing" + "github.com/opentdf/platform/lib/ocrypto" "github.com/opentdf/platform/protocol/go/policy" "github.com/stretchr/testify/require" ) @@ -118,3 +119,45 @@ func TestValidatePublicKeyPEM_UnsupportedAlgorithm(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "unsupported or unspecified algorithm") } + +func TestValidatePublicKeyPEM_HybridXWing_OK(t *testing.T) { + kp, err := ocrypto.NewKeyPair(ocrypto.HybridXWingKey) + require.NoError(t, err) + pubPem, err := kp.PublicKeyInPemFormat() + require.NoError(t, err) + + err = ValidatePublicKeyPEM([]byte(pubPem), policy.Algorithm_ALGORITHM_HPQT_XWING) + require.NoError(t, err) +} + +func TestValidatePublicKeyPEM_HybridP256MLKEM768_OK(t *testing.T) { + kp, err := ocrypto.NewKeyPair(ocrypto.HybridSecp256r1MLKEM768Key) + require.NoError(t, err) + pubPem, err := kp.PublicKeyInPemFormat() + require.NoError(t, err) + + err = ValidatePublicKeyPEM([]byte(pubPem), policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768) + require.NoError(t, err) +} + +func TestValidatePublicKeyPEM_HybridP384MLKEM1024_OK(t *testing.T) { + kp, err := ocrypto.NewKeyPair(ocrypto.HybridSecp384r1MLKEM1024Key) + require.NoError(t, err) + pubPem, err := kp.PublicKeyInPemFormat() + require.NoError(t, err) + + err = ValidatePublicKeyPEM([]byte(pubPem), policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024) + require.NoError(t, err) +} + +func TestValidatePublicKeyPEM_HybridMismatch(t *testing.T) { + // Generate an X-Wing key but validate against a different hybrid algorithm + kp, err := ocrypto.NewKeyPair(ocrypto.HybridXWingKey) + require.NoError(t, err) + pubPem, err := kp.PublicKeyInPemFormat() + require.NoError(t, err) + + err = ValidatePublicKeyPEM([]byte(pubPem), policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768) + require.Error(t, err) + require.Contains(t, err.Error(), "algorithm mismatch") +} diff --git a/protocol/go/kas/kas.pb.go b/protocol/go/kas/kas.pb.go index 03317e94c6..7eb3bfbf5c 100644 --- a/protocol/go/kas/kas.pb.go +++ b/protocol/go/kas/kas.pb.go @@ -242,7 +242,7 @@ type KeyAccess struct { Protocol string `protobuf:"bytes,3,opt,name=protocol,proto3" json:"protocol,omitempty"` // Type of key wrapping used for the data encryption key // Required: Always - // Values: 'wrapped' (RSA-wrapped for ZTDF), 'ec-wrapped' (experimental ECDH-wrapped) + // Values: 'wrapped' (RSA-wrapped for ZTDF), 'ec-wrapped' (experimental ECDH-wrapped), 'hybrid-wrapped' (experimental X-Wing-wrapped) KeyType string `protobuf:"bytes,4,opt,name=key_type,json=type,proto3" json:"key_type,omitempty"` // URL of the Key Access Server that can unwrap this key // Optional: May be omitted if KAS URL is known from context @@ -269,7 +269,7 @@ type KeyAccess struct { Header []byte `protobuf:"bytes,9,opt,name=header,proto3" json:"header,omitempty"` // Ephemeral public key for ECDH key derivation (ec-wrapped type only) // Required: When key_type="ec-wrapped" (experimental ECDH-based ZTDF) - // Omitted: When key_type="wrapped" (RSA-based ZTDF) + // Omitted: When key_type="wrapped" or key_type="hybrid-wrapped" // Should be a PEM-encoded PKCS#8 (ASN.1) formatted public key // Used to derive the symmetric key for unwrapping the DEK EphemeralPublicKey string `protobuf:"bytes,10,opt,name=ephemeral_public_key,json=ephemeralPublicKey,proto3" json:"ephemeral_public_key,omitempty"` @@ -854,7 +854,7 @@ type RewrapResponse struct { EntityWrappedKey []byte `protobuf:"bytes,2,opt,name=entity_wrapped_key,json=entityWrappedKey,proto3" json:"entity_wrapped_key,omitempty"` // KAS's ephemeral session public key in PEM format // Required: For EC-based operations (key_type="ec-wrapped") - // Optional: Empty for RSA-based ZTDF (key_type="wrapped") + // Optional: Empty for RSA-based or X-Wing-based ZTDF (key_type="wrapped" or key_type="hybrid-wrapped") // Used by client to perform ECDH key agreement and decrypt the kas_wrapped_key values SessionPublicKey string `protobuf:"bytes,3,opt,name=session_public_key,json=sessionPublicKey,proto3" json:"session_public_key,omitempty"` // Deprecated: Legacy schema version identifier diff --git a/protocol/go/policy/kasregistry/key_access_server_registry.pb.go b/protocol/go/policy/kasregistry/key_access_server_registry.pb.go index 569a2dd0c4..5bb3c2a08b 100644 --- a/protocol/go/policy/kasregistry/key_access_server_registry.pb.go +++ b/protocol/go/policy/kasregistry/key_access_server_registry.pb.go @@ -4277,555 +4277,557 @@ var file_policy_kasregistry_key_access_server_registry_proto_rawDesc = []byte{ 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x02, 0x18, 0x01, 0x22, - 0xcc, 0x0c, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, + 0xd5, 0x0c, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x6b, 0x61, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x05, 0x6b, 0x61, 0x73, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x05, - 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0xa4, 0x01, 0x0a, 0x0d, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, + 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0xad, 0x01, 0x0a, 0x0d, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, - 0x42, 0x6c, 0xba, 0x48, 0x69, 0xba, 0x01, 0x66, 0x0a, 0x15, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, + 0x42, 0x75, 0xba, 0x48, 0x72, 0xba, 0x01, 0x6f, 0x0a, 0x15, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x34, 0x54, 0x68, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x1a, 0x17, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x5b, - 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33, 0x2c, 0x20, 0x34, 0x2c, 0x20, 0x35, 0x5d, 0x52, 0x0c, - 0x6b, 0x65, 0x79, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x93, 0x01, 0x0a, - 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x0f, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x65, 0x79, 0x4d, 0x6f, 0x64, 0x65, - 0x42, 0x67, 0xba, 0x48, 0x64, 0xba, 0x01, 0x61, 0x0a, 0x10, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, - 0x64, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x35, 0x54, 0x68, 0x65, 0x20, - 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, - 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, - 0x6e, 0x65, 0x64, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x20, 0x28, 0x31, 0x2d, 0x34, 0x29, - 0x2e, 0x1a, 0x16, 0x74, 0x68, 0x69, 0x73, 0x20, 0x3e, 0x3d, 0x20, 0x31, 0x20, 0x26, 0x26, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x20, 0x3c, 0x3d, 0x20, 0x34, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x4d, 0x6f, - 0x64, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, - 0x5f, 0x63, 0x74, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, - 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x12, 0x3d, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, - 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, - 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, - 0x65, 0x79, 0x43, 0x74, 0x78, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x12, 0x33, 0x0a, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, - 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4d, - 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x3a, 0xbb, 0x07, 0xba, 0x48, 0xb7, 0x07, 0x1a, 0x97, 0x03, 0x0a, 0x23, 0x70, 0x72, 0x69, 0x76, - 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x5f, 0x6f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, - 0xbc, 0x01, 0x54, 0x68, 0x65, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, - 0x79, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x69, 0x66, - 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x4b, 0x45, 0x59, - 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x52, 0x4f, 0x4f, - 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x20, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, - 0x45, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, - 0x4b, 0x45, 0x59, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, - 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x65, 0x6d, 0x70, - 0x74, 0x79, 0x20, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, - 0x73, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, - 0x45, 0x20, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x55, - 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x2e, 0x1a, 0xb0, - 0x01, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x65, - 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x29, 0x20, 0x26, 0x26, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x5f, 0x63, 0x74, 0x78, 0x2e, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, - 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x28, 0x28, 0x74, 0x68, 0x69, - 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x33, 0x20, - 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x2e, 0x77, - 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x3d, 0x3d, 0x20, 0x27, 0x27, - 0x29, 0x1a, 0xf4, 0x02, 0x0a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, - 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0xa8, 0x01, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x69, - 0x64, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x69, 0x66, - 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x4b, 0x45, 0x59, - 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x52, - 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x20, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, - 0x4f, 0x44, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x6d, - 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x66, 0x6f, 0x72, - 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, - 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x4b, 0x45, + 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x1a, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x5b, + 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33, 0x2c, 0x20, 0x34, 0x2c, 0x20, 0x35, 0x2c, 0x20, 0x36, + 0x2c, 0x20, 0x37, 0x2c, 0x20, 0x38, 0x5d, 0x52, 0x0c, 0x6b, 0x65, 0x79, 0x41, 0x6c, 0x67, 0x6f, + 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x93, 0x01, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, + 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x2e, 0x4b, 0x65, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x42, 0x67, 0xba, 0x48, 0x64, 0xba, 0x01, + 0x61, 0x0a, 0x10, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x69, + 0x6e, 0x65, 0x64, 0x12, 0x35, 0x54, 0x68, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, + 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x20, 0x28, 0x31, 0x2d, 0x34, 0x29, 0x2e, 0x1a, 0x16, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x3e, 0x3d, 0x20, 0x31, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x3c, 0x3d, + 0x20, 0x34, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, + 0x01, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x12, + 0x3d, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, + 0x74, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x52, + 0x0d, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x12, 0x2c, + 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x65, + 0x67, 0x61, 0x63, 0x79, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0xbb, 0x07, 0xba, 0x48, 0xb7, 0x07, + 0x1a, 0x97, 0x03, 0x0a, 0x23, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x5f, 0x63, 0x74, 0x78, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0xbc, 0x01, 0x54, 0x68, 0x65, 0x20, 0x77, + 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, + 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, + 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x20, 0x6f, + 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, + 0x44, 0x45, 0x52, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x2e, 0x20, 0x54, 0x68, + 0x65, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x6d, 0x75, + 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x69, 0x66, 0x20, 0x6b, + 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, + 0x4f, 0x44, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x20, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, - 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x2e, 0x1a, 0x9e, 0x01, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, + 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x2e, 0x1a, 0xb0, 0x01, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, - 0x3d, 0x3d, 0x20, 0x34, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, - 0x20, 0x3d, 0x3d, 0x20, 0x27, 0x27, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x28, 0x28, 0x74, 0x68, 0x69, - 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x20, - 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x3d, 0x3d, 0x20, 0x33, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, + 0x3d, 0x3d, 0x20, 0x32, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x2e, 0x77, 0x72, + 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27, 0x29, + 0x20, 0x7c, 0x7c, 0x20, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, + 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x33, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x29, 0x20, + 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x2e, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, + 0x6b, 0x65, 0x79, 0x20, 0x3d, 0x3d, 0x20, 0x27, 0x27, 0x29, 0x1a, 0xf4, 0x02, 0x0a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, - 0x64, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27, 0x29, 0x1a, 0xa3, 0x01, 0x0a, 0x23, 0x70, 0x72, 0x69, - 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x5f, 0x66, 0x6f, 0x72, - 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, - 0x12, 0x48, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, - 0x78, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, - 0x74, 0x20, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, - 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, - 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x2e, 0x1a, 0x32, 0x21, 0x28, 0x74, 0x68, - 0x69, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x34, - 0x20, 0x26, 0x26, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x72, 0x69, - 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x29, 0x29, 0x22, 0x3c, - 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0xa8, 0x01, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x69, 0x64, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, + 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, + 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, + 0x20, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x52, 0x45, 0x4d, + 0x4f, 0x54, 0x45, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, + 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, + 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, + 0x45, 0x59, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, + 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x2e, + 0x1a, 0x9e, 0x01, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, + 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, + 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x29, 0x20, 0x26, + 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x20, 0x3d, 0x3d, 0x20, 0x27, 0x27, 0x29, + 0x20, 0x7c, 0x7c, 0x20, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, + 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x33, 0x29, 0x20, + 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27, + 0x29, 0x1a, 0xa3, 0x01, 0x0a, 0x23, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x63, 0x74, 0x78, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x12, 0x48, 0x70, 0x72, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, + 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x74, 0x20, 0x69, 0x66, 0x20, 0x6b, 0x65, + 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, + 0x44, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x4e, + 0x4c, 0x59, 0x2e, 0x1a, 0x32, 0x21, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6b, 0x65, 0x79, 0x5f, + 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x20, 0x26, 0x26, 0x20, 0x68, 0x61, 0x73, + 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x63, 0x74, 0x78, 0x29, 0x29, 0x22, 0x3c, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x07, + 0x6b, 0x61, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x52, 0x06, 0x6b, + 0x61, 0x73, 0x4b, 0x65, 0x79, 0x22, 0x7a, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x38, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x42, 0x13, 0x0a, 0x0a, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, + 0x01, 0x22, 0x39, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x07, 0x6b, 0x61, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x61, - 0x73, 0x4b, 0x65, 0x79, 0x52, 0x06, 0x6b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x22, 0x7a, 0x0a, 0x0d, - 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, - 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x38, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, - 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, - 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x42, 0x13, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, - 0x72, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x22, 0x39, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4b, - 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x07, 0x6b, 0x61, - 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x52, 0x06, 0x6b, 0x61, 0x73, - 0x4b, 0x65, 0x79, 0x22, 0xd5, 0x03, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xa7, 0x01, 0x0a, 0x0d, 0x6b, 0x65, 0x79, 0x5f, - 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x11, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, - 0x68, 0x6d, 0x42, 0x6f, 0xba, 0x48, 0x6c, 0xba, 0x01, 0x69, 0x0a, 0x15, 0x6b, 0x65, 0x79, 0x5f, - 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, - 0x64, 0x12, 0x34, 0x54, 0x68, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, - 0x69, 0x74, 0x68, 0x6d, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x6f, 0x6e, 0x65, - 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x1a, 0x1a, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x6e, - 0x20, 0x5b, 0x30, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33, 0x2c, 0x20, 0x34, 0x2c, - 0x20, 0x35, 0x5d, 0x52, 0x0c, 0x6b, 0x65, 0x79, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, - 0x6d, 0x12, 0x21, 0x0a, 0x06, 0x6b, 0x61, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, 0x05, 0x6b, - 0x61, 0x73, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x08, 0x6b, 0x61, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x48, - 0x00, 0x52, 0x07, 0x6b, 0x61, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x07, 0x6b, 0x61, - 0x73, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, - 0x72, 0x05, 0x10, 0x01, 0x88, 0x01, 0x01, 0x48, 0x00, 0x52, 0x06, 0x6b, 0x61, 0x73, 0x55, 0x72, - 0x69, 0x12, 0x1b, 0x0a, 0x06, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x08, 0x48, 0x01, 0x52, 0x06, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x88, 0x01, 0x01, 0x12, 0x33, - 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x61, 0x67, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x0b, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x73, 0x53, 0x6f, - 0x72, 0x74, 0x42, 0x08, 0xba, 0x48, 0x05, 0x92, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, 0x73, 0x6f, - 0x72, 0x74, 0x42, 0x0c, 0x0a, 0x0a, 0x6b, 0x61, 0x73, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x22, 0x73, 0x0a, 0x10, 0x4c, - 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x29, 0x0a, 0x08, 0x6b, 0x61, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, - 0x79, 0x52, 0x07, 0x6b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x34, 0x0a, 0x0a, 0x70, 0x61, - 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0x86, 0x03, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x64, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x54, 0x0a, 0x18, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, - 0x18, 0x65, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, - 0x75, 0x6d, 0x52, 0x16, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x3a, 0xcc, 0x01, 0xba, 0x48, 0xc8, - 0x01, 0x1a, 0xc5, 0x01, 0x0a, 0x18, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x12, 0x52, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, - 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, - 0x20, 0x65, 0x69, 0x74, 0x68, 0x65, 0x72, 0x20, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, 0x20, 0x6f, - 0x72, 0x20, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x2c, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x2e, 0x1a, 0x55, 0x28, 0x28, 0x21, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x29, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x28, 0x68, - 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x73, 0x4b, 0x65, 0x79, 0x52, 0x06, 0x6b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x22, 0xde, 0x03, 0x0a, + 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0xb0, 0x01, 0x0a, 0x0d, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, + 0x68, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x42, 0x78, 0xba, 0x48, 0x75, + 0xba, 0x01, 0x72, 0x0a, 0x15, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, + 0x68, 0x6d, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x34, 0x54, 0x68, 0x65, 0x20, + 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x20, 0x6d, 0x75, + 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, + 0x1a, 0x23, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x5b, 0x30, 0x2c, 0x20, 0x31, 0x2c, + 0x20, 0x32, 0x2c, 0x20, 0x33, 0x2c, 0x20, 0x34, 0x2c, 0x20, 0x35, 0x2c, 0x20, 0x36, 0x2c, 0x20, + 0x37, 0x2c, 0x20, 0x38, 0x5d, 0x52, 0x0c, 0x6b, 0x65, 0x79, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, + 0x74, 0x68, 0x6d, 0x12, 0x21, 0x0a, 0x06, 0x6b, 0x61, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, + 0x05, 0x6b, 0x61, 0x73, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x08, 0x6b, 0x61, 0x73, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, + 0x01, 0x48, 0x00, 0x52, 0x07, 0x6b, 0x61, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x07, + 0x6b, 0x61, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, + 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x88, 0x01, 0x01, 0x48, 0x00, 0x52, 0x06, 0x6b, 0x61, 0x73, + 0x55, 0x72, 0x69, 0x12, 0x1b, 0x0a, 0x06, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x08, 0x48, 0x01, 0x52, 0x06, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x88, 0x01, 0x01, + 0x12, 0x33, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3d, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x0b, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x73, + 0x53, 0x6f, 0x72, 0x74, 0x42, 0x08, 0xba, 0x48, 0x05, 0x92, 0x01, 0x02, 0x10, 0x01, 0x52, 0x04, + 0x73, 0x6f, 0x72, 0x74, 0x42, 0x0c, 0x0a, 0x0a, 0x6b, 0x61, 0x73, 0x5f, 0x66, 0x69, 0x6c, 0x74, + 0x65, 0x72, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x22, 0x73, 0x0a, + 0x10, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x29, 0x0a, 0x08, 0x6b, 0x61, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x61, 0x73, + 0x4b, 0x65, 0x79, 0x52, 0x07, 0x6b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x34, 0x0a, 0x0a, + 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x86, 0x03, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x64, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x08, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x54, 0x0a, 0x18, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, - 0x6f, 0x72, 0x20, 0x21, 0x3d, 0x20, 0x30, 0x29, 0x29, 0x22, 0x3c, 0x0a, 0x11, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, - 0x0a, 0x07, 0x6b, 0x61, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x52, - 0x06, 0x6b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x22, 0xa4, 0x01, 0x0a, 0x10, 0x4b, 0x61, 0x73, 0x4b, - 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x06, - 0x6b, 0x61, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, - 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, 0x05, 0x6b, 0x61, 0x73, 0x49, 0x64, 0x12, - 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, - 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1e, - 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, - 0x72, 0x05, 0x10, 0x01, 0x88, 0x01, 0x01, 0x48, 0x00, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x19, - 0x0a, 0x03, 0x6b, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, - 0x72, 0x02, 0x10, 0x01, 0x52, 0x03, 0x6b, 0x69, 0x64, 0x42, 0x13, 0x0a, 0x0a, 0x69, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x22, 0xe5, - 0x0e, 0x0a, 0x10, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, - 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x38, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, + 0x6f, 0x72, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x16, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x3a, 0xcc, 0x01, 0xba, + 0x48, 0xc8, 0x01, 0x1a, 0xc5, 0x01, 0x0a, 0x18, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, + 0x12, 0x52, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x20, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, + 0x62, 0x65, 0x20, 0x65, 0x69, 0x74, 0x68, 0x65, 0x72, 0x20, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, + 0x20, 0x6f, 0x72, 0x20, 0x52, 0x45, 0x50, 0x4c, 0x41, 0x43, 0x45, 0x2c, 0x20, 0x77, 0x68, 0x65, + 0x6e, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x2e, 0x1a, 0x55, 0x28, 0x28, 0x21, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, + 0x73, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x29, 0x29, 0x20, 0x7c, 0x7c, 0x20, + 0x28, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x62, 0x65, 0x68, 0x61, + 0x76, 0x69, 0x6f, 0x72, 0x20, 0x21, 0x3d, 0x20, 0x30, 0x29, 0x29, 0x22, 0x3c, 0x0a, 0x11, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x27, 0x0a, 0x07, 0x6b, 0x61, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, + 0x79, 0x52, 0x06, 0x6b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x22, 0xa4, 0x01, 0x0a, 0x10, 0x4b, 0x61, + 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x21, + 0x0a, 0x06, 0x6b, 0x61, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, + 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, 0x05, 0x6b, 0x61, 0x73, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x1e, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, + 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x88, 0x01, 0x01, 0x48, 0x00, 0x52, 0x03, 0x75, 0x72, 0x69, + 0x12, 0x19, 0x0a, 0x03, 0x6b, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, + 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x03, 0x6b, 0x69, 0x64, 0x42, 0x13, 0x0a, 0x0a, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, + 0x22, 0xee, 0x0e, 0x0a, 0x10, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x38, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, + 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x07, 0x6e, + 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x44, 0x0a, 0x07, 0x6e, 0x65, 0x77, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, - 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x2e, 0x4e, 0x65, 0x77, 0x4b, 0x65, 0x79, 0x52, 0x06, 0x6e, 0x65, 0x77, 0x4b, 0x65, 0x79, 0x1a, - 0xcf, 0x04, 0x0a, 0x06, 0x4e, 0x65, 0x77, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x06, 0x6b, 0x65, - 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, - 0x02, 0x10, 0x01, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x9d, 0x01, 0x0a, 0x09, 0x61, - 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, - 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, - 0x6d, 0x42, 0x6c, 0xba, 0x48, 0x69, 0xba, 0x01, 0x66, 0x0a, 0x15, 0x6b, 0x65, 0x79, 0x5f, 0x61, - 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, - 0x12, 0x34, 0x54, 0x68, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, - 0x74, 0x68, 0x6d, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x6f, 0x6e, 0x65, 0x20, - 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x1a, 0x17, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x20, - 0x5b, 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33, 0x2c, 0x20, 0x34, 0x2c, 0x20, 0x35, 0x5d, 0x52, - 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x9e, 0x01, 0x0a, 0x08, 0x6b, - 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x65, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x42, 0x72, - 0xba, 0x48, 0x6f, 0xba, 0x01, 0x67, 0x0a, 0x14, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x5f, - 0x6d, 0x6f, 0x64, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x39, 0x54, 0x68, - 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x6d, - 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x20, 0x28, 0x31, 0x2d, 0x34, 0x29, 0x2e, 0x1a, 0x14, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x6e, - 0x20, 0x5b, 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33, 0x2c, 0x20, 0x34, 0x5d, 0x82, 0x01, 0x02, - 0x10, 0x01, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, - 0x01, 0x52, 0x0c, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x12, - 0x3d, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, - 0x74, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x52, - 0x0d, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x12, 0x2c, - 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x3a, 0xcd, 0x08, 0xba, 0x48, 0xc9, 0x08, 0x1a, 0xd8, 0x03, 0x0a, 0x23, 0x70, 0x72, 0x69, - 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x5f, 0x6f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, - 0x12, 0xcd, 0x01, 0x46, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x6b, - 0x65, 0x79, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, - 0x6b, 0x65, 0x79, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, + 0x79, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x4e, 0x65, 0x77, 0x4b, 0x65, 0x79, 0x52, 0x06, 0x6e, 0x65, 0x77, 0x4b, 0x65, + 0x79, 0x1a, 0xd8, 0x04, 0x0a, 0x06, 0x4e, 0x65, 0x77, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x06, + 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, + 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0xa6, 0x01, 0x0a, + 0x09, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x11, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, + 0x74, 0x68, 0x6d, 0x42, 0x75, 0xba, 0x48, 0x72, 0xba, 0x01, 0x6f, 0x0a, 0x15, 0x6b, 0x65, 0x79, + 0x5f, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, + 0x65, 0x64, 0x12, 0x34, 0x54, 0x68, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x61, 0x6c, 0x67, 0x6f, + 0x72, 0x69, 0x74, 0x68, 0x6d, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x6f, 0x6e, + 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, + 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x1a, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, + 0x6e, 0x20, 0x5b, 0x31, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x33, 0x2c, 0x20, 0x34, 0x2c, 0x20, 0x35, + 0x2c, 0x20, 0x36, 0x2c, 0x20, 0x37, 0x2c, 0x20, 0x38, 0x5d, 0x52, 0x09, 0x61, 0x6c, 0x67, 0x6f, + 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x9e, 0x01, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, + 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x2e, 0x4b, 0x65, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x42, 0x72, 0xba, 0x48, 0x6f, 0xba, 0x01, + 0x67, 0x0a, 0x14, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x5f, + 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x39, 0x54, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x77, + 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, + 0x65, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, + 0x69, 0x6e, 0x65, 0x64, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x20, 0x28, 0x31, 0x2d, 0x34, + 0x29, 0x2e, 0x1a, 0x14, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x5b, 0x31, 0x2c, 0x20, + 0x32, 0x2c, 0x20, 0x33, 0x2c, 0x20, 0x34, 0x5d, 0x82, 0x01, 0x02, 0x10, 0x01, 0x52, 0x07, 0x6b, + 0x65, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x42, 0x0a, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x43, 0x74, 0x78, 0x42, 0x06, 0xba, 0x48, 0x03, 0xc8, 0x01, 0x01, 0x52, 0x0c, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x12, 0x3d, 0x0a, 0x0f, 0x70, 0x72, + 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x72, 0x69, + 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x43, 0x74, 0x78, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4d, 0x75, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3a, 0xcd, 0x08, 0xba, + 0x48, 0xc9, 0x08, 0x1a, 0xd8, 0x03, 0x0a, 0x23, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, + 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0xcd, 0x01, 0x46, 0x6f, + 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x6b, 0x65, 0x79, 0x2c, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x69, + 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x69, 0x66, 0x20, 0x6b, 0x65, + 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, + 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, + 0x45, 0x59, 0x20, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, + 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, + 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, + 0x79, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x4b, - 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x52, - 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x20, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, + 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x20, 0x6f, + 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, + 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x2e, 0x1a, 0xe0, 0x01, 0x28, 0x28, + 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, 0x79, + 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, + 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, + 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x2e, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, + 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27, 0x29, 0x20, 0x7c, 0x7c, 0x20, + 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, + 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x33, 0x20, 0x7c, 0x7c, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, 0x79, + 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x69, 0x76, + 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x2e, 0x77, 0x72, 0x61, 0x70, + 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x3d, 0x3d, 0x20, 0x27, 0x27, 0x29, 0x1a, 0xb5, + 0x03, 0x0a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x6c, 0x79, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0xb9, 0x01, 0x46, 0x6f, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x6b, 0x65, 0x79, 0x2c, 0x20, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x69, 0x64, 0x20, + 0x69, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x69, 0x66, 0x20, 0x6b, + 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x52, 0x4f, 0x4f, - 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, - 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x65, - 0x6d, 0x70, 0x74, 0x79, 0x20, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x69, 0x73, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x52, 0x45, 0x4d, - 0x4f, 0x54, 0x45, 0x20, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, - 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x2e, - 0x1a, 0xe0, 0x01, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, - 0x79, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, - 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, - 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x29, 0x20, 0x26, - 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x70, - 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x2e, 0x77, - 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27, - 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, - 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, - 0x33, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, - 0x79, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x29, - 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, - 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, - 0x2e, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x20, 0x3d, 0x3d, 0x20, - 0x27, 0x27, 0x29, 0x1a, 0xb5, 0x03, 0x0a, 0x26, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, - 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0xb9, - 0x01, 0x46, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x6b, 0x65, 0x79, - 0x2c, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x20, 0x69, 0x64, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, - 0x20, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, - 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, - 0x52, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x20, 0x6f, 0x72, 0x20, 0x4b, 0x45, - 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x2e, 0x20, 0x49, - 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, - 0x66, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x4e, - 0x46, 0x49, 0x47, 0x5f, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x20, 0x61, 0x6e, 0x64, - 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, - 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x2e, 0x1a, 0xce, 0x01, 0x28, 0x28, 0x74, - 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, 0x79, 0x5f, - 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, - 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, - 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, - 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x20, 0x3d, 0x3d, 0x20, 0x27, - 0x27, 0x29, 0x20, 0x7c, 0x7c, 0x20, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, + 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x20, 0x6f, 0x72, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, + 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x6d, 0x75, 0x73, + 0x74, 0x20, 0x62, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x4b, + 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x52, + 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x4b, 0x45, 0x59, 0x5f, + 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, + 0x4f, 0x4e, 0x4c, 0x59, 0x2e, 0x1a, 0xce, 0x01, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, + 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, + 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, - 0x20, 0x32, 0x20, 0x7c, 0x7c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, - 0x65, 0x79, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x33, - 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, - 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x5f, 0x69, 0x64, 0x20, 0x21, 0x3d, 0x20, 0x27, 0x27, 0x29, 0x1a, 0xb3, 0x01, 0x0a, 0x23, - 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x5f, - 0x66, 0x6f, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6f, - 0x6e, 0x6c, 0x79, 0x12, 0x48, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x5f, 0x63, 0x74, 0x78, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, - 0x20, 0x73, 0x65, 0x74, 0x20, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x69, 0x73, 0x20, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x55, 0x42, - 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x2e, 0x1a, 0x42, 0x21, - 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, - 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x20, 0x26, 0x26, 0x20, 0x68, - 0x61, 0x73, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, - 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x29, - 0x29, 0x42, 0x13, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, - 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x22, 0x32, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x71, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x66, 0x71, 0x6e, 0x22, 0xe3, 0x02, 0x0a, 0x10, 0x52, - 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, - 0x36, 0x0a, 0x0f, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, - 0x64, 0x4f, 0x75, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x66, 0x0a, 0x1d, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x79, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, - 0x67, 0x73, 0x52, 0x1b, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x44, 0x65, 0x66, - 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, - 0x5c, 0x0a, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x70, - 0x70, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x51, 0x0a, - 0x12, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, - 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x6f, 0x6c, 0x69, + 0x20, 0x34, 0x29, 0x20, 0x26, 0x26, 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, + 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x20, 0x3d, 0x3d, 0x20, 0x27, 0x27, 0x29, 0x20, 0x7c, 0x7c, + 0x20, 0x28, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, + 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x20, 0x7c, 0x7c, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, + 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x33, 0x29, 0x20, 0x26, 0x26, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x69, 0x64, 0x20, + 0x21, 0x3d, 0x20, 0x27, 0x27, 0x29, 0x1a, 0xb3, 0x01, 0x0a, 0x23, 0x70, 0x72, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x12, 0x48, + 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x20, + 0x6d, 0x75, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x74, 0x20, + 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x4b, + 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, + 0x45, 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x2e, 0x1a, 0x42, 0x21, 0x28, 0x74, 0x68, 0x69, 0x73, + 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, + 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x20, 0x26, 0x26, 0x20, 0x68, 0x61, 0x73, 0x28, 0x74, 0x68, + 0x69, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x5f, 0x6b, 0x65, 0x79, 0x2e, 0x70, 0x72, 0x69, 0x76, 0x61, + 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x63, 0x74, 0x78, 0x29, 0x29, 0x42, 0x13, 0x0a, 0x0a, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, + 0x01, 0x22, 0x32, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, + 0x6e, 0x67, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x71, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x66, 0x71, 0x6e, 0x22, 0xe3, 0x02, 0x0a, 0x10, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x0f, 0x72, 0x6f, + 0x74, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x61, 0x73, + 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x4f, 0x75, 0x74, 0x4b, + 0x65, 0x79, 0x12, 0x66, 0x0a, 0x1d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, + 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, + 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x11, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, - 0x22, 0x8f, 0x01, 0x0a, 0x11, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x07, 0x6b, 0x61, 0x73, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x52, 0x06, 0x6b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x12, - 0x51, 0x0a, 0x11, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, - 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x52, 0x10, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x22, 0x7e, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x38, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x42, 0x13, 0x0a, - 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x05, 0xba, 0x48, 0x02, - 0x08, 0x01, 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x45, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x42, 0x61, - 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, - 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x14, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4b, - 0x61, 0x73, 0x4b, 0x65, 0x79, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x8e, - 0x01, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x62, 0x61, 0x73, - 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4b, 0x61, 0x73, 0x4b, 0x65, - 0x79, 0x52, 0x0a, 0x6e, 0x65, 0x77, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x40, 0x0a, - 0x11, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x52, 0x0f, - 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x22, - 0x36, 0x0a, 0x12, 0x4d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x71, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x66, 0x71, 0x6e, 0x22, 0xb4, 0x02, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4d, - 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x6b, 0x61, 0x73, 0x5f, - 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6b, 0x61, 0x73, 0x55, 0x72, - 0x69, 0x12, 0x55, 0x0a, 0x12, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6d, - 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, - 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x79, 0x2e, 0x4d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x11, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x55, 0x0a, 0x12, 0x61, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, - 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x61, 0x70, 0x70, 0x65, 0x64, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x11, 0x61, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, - 0x4d, 0x0a, 0x0e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, - 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x61, 0x70, - 0x70, 0x65, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, - 0x0d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xb8, - 0x01, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, - 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, - 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x38, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x33, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x61, 0x67, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x13, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x00, 0x22, 0x92, 0x01, 0x0a, 0x17, 0x4c, 0x69, - 0x73, 0x74, 0x4b, 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x61, 0x70, - 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x6f, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x1b, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x5c, 0x0a, 0x18, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x61, + 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x79, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, + 0x52, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, + 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x51, 0x0a, 0x12, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, + 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x11, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x8f, 0x01, 0x0a, 0x11, + 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x27, 0x0a, 0x07, 0x6b, 0x61, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, + 0x65, 0x79, 0x52, 0x06, 0x6b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x51, 0x0a, 0x11, 0x72, 0x6f, + 0x74, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, + 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, + 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x10, 0x72, 0x6f, 0x74, + 0x61, 0x74, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x22, 0x7e, 0x0a, + 0x11, 0x53, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, + 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, 0x12, 0x38, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, - 0x2e, 0x4b, 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x6b, 0x65, 0x79, - 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x34, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0xef, - 0x01, 0x0a, 0x18, 0x53, 0x6f, 0x72, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x28, 0x53, - 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, - 0x45, 0x52, 0x56, 0x45, 0x52, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, - 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x4f, 0x52, - 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x52, - 0x56, 0x45, 0x52, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, - 0x12, 0x24, 0x0a, 0x20, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x43, 0x43, - 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x55, 0x52, 0x49, 0x10, 0x02, 0x12, 0x2b, 0x0a, 0x27, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, + 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x48, 0x00, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x42, 0x13, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x05, 0xba, 0x48, 0x02, 0x08, 0x01, 0x22, 0x13, 0x0a, + 0x11, 0x47, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x45, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, + 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x8e, 0x01, 0x0a, 0x12, 0x53, 0x65, + 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x36, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, + 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x52, 0x0a, 0x6e, 0x65, + 0x77, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x40, 0x0a, 0x11, 0x70, 0x72, 0x65, 0x76, + 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x69, 0x6d, + 0x70, 0x6c, 0x65, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x69, + 0x6f, 0x75, 0x73, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x36, 0x0a, 0x12, 0x4d, 0x61, + 0x70, 0x70, 0x65, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x10, 0x0a, 0x03, 0x66, 0x71, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x66, + 0x71, 0x6e, 0x22, 0xb4, 0x02, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, + 0x67, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x6b, 0x61, 0x73, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6b, 0x61, 0x73, 0x55, 0x72, 0x69, 0x12, 0x55, 0x0a, 0x12, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, + 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x61, + 0x70, 0x70, 0x65, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x52, 0x11, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, + 0x6e, 0x67, 0x73, 0x12, 0x55, 0x0a, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x26, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x11, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4d, 0x0a, 0x0e, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x0d, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xb8, 0x01, 0x0a, 0x16, 0x4c, 0x69, + 0x73, 0x74, 0x4b, 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x48, 0x00, 0x52, 0x02, 0x69, 0x64, + 0x12, 0x38, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x48, 0x00, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x33, 0x0a, 0x0a, 0x70, 0x61, + 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, + 0x13, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x05, 0xba, + 0x48, 0x02, 0x08, 0x00, 0x22, 0x92, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, + 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x41, 0x0a, 0x0c, 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, + 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4b, 0x65, 0x79, 0x4d, + 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x6b, 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, + 0x6e, 0x67, 0x73, 0x12, 0x34, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x2e, 0x50, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x0a, 0x70, + 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0xef, 0x01, 0x0a, 0x18, 0x53, 0x6f, + 0x72, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x28, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, - 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, - 0x54, 0x10, 0x03, 0x12, 0x2b, 0x0a, 0x27, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, - 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x53, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, 0x54, 0x10, 0x04, - 0x2a, 0x9a, 0x01, 0x0a, 0x0f, 0x53, 0x6f, 0x72, 0x74, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x73, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x1e, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x41, 0x53, - 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x4f, 0x52, 0x54, - 0x5f, 0x4b, 0x41, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4b, - 0x45, 0x59, 0x5f, 0x49, 0x44, 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x53, 0x4f, 0x52, 0x54, 0x5f, - 0x4b, 0x41, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x52, - 0x45, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x53, 0x4f, - 0x52, 0x54, 0x5f, 0x4b, 0x41, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, 0x54, 0x10, 0x03, 0x32, 0x99, 0x0c, - 0x0a, 0x1e, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x7e, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x2f, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x70, 0x6f, 0x6c, 0x69, + 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, + 0x45, 0x44, 0x10, 0x00, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x45, 0x59, + 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x53, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x24, 0x0a, 0x20, 0x53, + 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, + 0x45, 0x52, 0x56, 0x45, 0x52, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x52, 0x49, 0x10, + 0x02, 0x12, 0x2b, 0x0a, 0x27, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x43, + 0x43, 0x45, 0x53, 0x53, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x53, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, 0x54, 0x10, 0x03, 0x12, 0x2b, + 0x0a, 0x27, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, + 0x53, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, + 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x5f, 0x41, 0x54, 0x10, 0x04, 0x2a, 0x9a, 0x01, 0x0a, 0x0f, + 0x53, 0x6f, 0x72, 0x74, 0x4b, 0x61, 0x73, 0x4b, 0x65, 0x79, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x22, 0x0a, 0x1e, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x41, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x53, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x41, 0x53, 0x5f, + 0x4b, 0x45, 0x59, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x49, 0x44, + 0x10, 0x01, 0x12, 0x21, 0x0a, 0x1d, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x41, 0x53, 0x5f, 0x4b, + 0x45, 0x59, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, + 0x5f, 0x41, 0x54, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x53, 0x4f, 0x52, 0x54, 0x5f, 0x4b, 0x41, + 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x53, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x50, 0x44, 0x41, + 0x54, 0x45, 0x44, 0x5f, 0x41, 0x54, 0x10, 0x03, 0x32, 0x99, 0x0c, 0x0a, 0x1e, 0x4b, 0x65, 0x79, + 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7e, 0x0a, 0x14, 0x4c, + 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x73, 0x12, 0x2f, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, + 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, + 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, + 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x78, 0x0a, 0x12, 0x47, + 0x65, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x12, 0x2d, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x7e, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, + 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x30, + 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x31, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, + 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x30, + 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x31, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4b, + 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x30, + 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x31, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x90, 0x01, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, + 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x61, + 0x6e, 0x74, 0x73, 0x12, 0x34, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, + 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, - 0x12, 0x78, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, - 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4b, - 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, - 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, - 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 0x12, 0x7e, 0x0a, 0x15, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, - 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, - 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, + 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x06, 0x88, 0x02, 0x01, 0x90, 0x02, 0x01, 0x12, 0x5a, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a, 0x15, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, - 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, - 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, - 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a, 0x15, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x12, 0x30, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, - 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4b, - 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, - 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x90, 0x01, 0x0a, 0x19, 0x4c, - 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x34, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, + 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x06, 0x88, 0x02, 0x01, 0x90, 0x02, 0x01, 0x12, 0x5a, 0x0a, - 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x06, 0x47, 0x65, 0x74, - 0x4b, 0x65, 0x79, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, - 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, - 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4b, - 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x08, - 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x74, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x08, 0x4c, 0x69, 0x73, 0x74, 0x4b, + 0x65, 0x79, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, + 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x5a, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, - 0x65, 0x79, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, - 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x00, 0x12, 0x5a, 0x0a, 0x09, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x24, - 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, - 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, - 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, - 0x0a, 0x53, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, - 0x2e, 0x53, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, - 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x0a, - 0x47, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, - 0x47, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6c, 0x0a, 0x0f, 0x4c, - 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2a, - 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, - 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x70, 0x6f, 0x6c, - 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xdb, 0x01, 0x0a, 0x16, 0x63, 0x6f, - 0x6d, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x79, 0x42, 0x1c, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x64, 0x66, 0x2f, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, - 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x2f, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, - 0xa2, 0x02, 0x03, 0x50, 0x4b, 0x58, 0xaa, 0x02, 0x12, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, - 0x4b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0xca, 0x02, 0x12, 0x50, 0x6f, + 0x72, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, + 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, + 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x09, + 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x52, + 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x79, 0x2e, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x42, + 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, + 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x42, + 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x79, 0x2e, 0x53, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5d, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, + 0x73, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x25, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, + 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, + 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x79, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x73, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6c, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, + 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2a, 0x2e, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x6b, + 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4b, + 0x65, 0x79, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x42, 0xdb, 0x01, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x2e, 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x42, + 0x1c, 0x4b, 0x65, 0x79, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, + 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, + 0x74, 0x64, 0x66, 0x2f, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2f, + 0x6b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0xa2, 0x02, 0x03, 0x50, 0x4b, + 0x58, 0xaa, 0x02, 0x12, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x4b, 0x61, 0x73, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0xca, 0x02, 0x12, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5c, + 0x4b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0xe2, 0x02, 0x1e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5c, 0x4b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, - 0xe2, 0x02, 0x1e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5c, 0x4b, 0x61, 0x73, 0x72, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x79, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0xea, 0x02, 0x13, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x3a, 0x3a, 0x4b, 0x61, 0x73, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x13, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x3a, 0x3a, 0x4b, 0x61, 0x73, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/protocol/go/policy/objects.pb.go b/protocol/go/policy/objects.pb.go index 78282c4edc..112291c6b1 100644 --- a/protocol/go/policy/objects.pb.go +++ b/protocol/go/policy/objects.pb.go @@ -237,31 +237,40 @@ func (SourceType) EnumDescriptor() ([]byte, []int) { type KasPublicKeyAlgEnum int32 const ( - KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED KasPublicKeyAlgEnum = 0 - KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048 KasPublicKeyAlgEnum = 1 - KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096 KasPublicKeyAlgEnum = 2 - KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 KasPublicKeyAlgEnum = 5 - KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 KasPublicKeyAlgEnum = 6 - KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 KasPublicKeyAlgEnum = 7 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED KasPublicKeyAlgEnum = 0 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048 KasPublicKeyAlgEnum = 1 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096 KasPublicKeyAlgEnum = 2 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 KasPublicKeyAlgEnum = 5 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 KasPublicKeyAlgEnum = 6 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 KasPublicKeyAlgEnum = 7 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING KasPublicKeyAlgEnum = 10 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 KasPublicKeyAlgEnum = 11 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 KasPublicKeyAlgEnum = 12 ) // Enum value maps for KasPublicKeyAlgEnum. var ( KasPublicKeyAlgEnum_name = map[int32]string{ - 0: "KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED", - 1: "KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048", - 2: "KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096", - 5: "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1", - 6: "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1", - 7: "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1", + 0: "KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED", + 1: "KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048", + 2: "KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096", + 5: "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1", + 6: "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1", + 7: "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1", + 10: "KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING", + 11: "KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768", + 12: "KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024", } KasPublicKeyAlgEnum_value = map[string]int32{ - "KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED": 0, - "KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048": 1, - "KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096": 2, - "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1": 5, - "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1": 6, - "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1": 7, + "KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED": 0, + "KAS_PUBLIC_KEY_ALG_ENUM_RSA_2048": 1, + "KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096": 2, + "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1": 5, + "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1": 6, + "KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1": 7, + "KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING": 10, + "KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768": 11, + "KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024": 12, } ) @@ -296,12 +305,15 @@ func (KasPublicKeyAlgEnum) EnumDescriptor() ([]byte, []int) { type Algorithm int32 const ( - Algorithm_ALGORITHM_UNSPECIFIED Algorithm = 0 - Algorithm_ALGORITHM_RSA_2048 Algorithm = 1 - Algorithm_ALGORITHM_RSA_4096 Algorithm = 2 - Algorithm_ALGORITHM_EC_P256 Algorithm = 3 - Algorithm_ALGORITHM_EC_P384 Algorithm = 4 - Algorithm_ALGORITHM_EC_P521 Algorithm = 5 + Algorithm_ALGORITHM_UNSPECIFIED Algorithm = 0 + Algorithm_ALGORITHM_RSA_2048 Algorithm = 1 + Algorithm_ALGORITHM_RSA_4096 Algorithm = 2 + Algorithm_ALGORITHM_EC_P256 Algorithm = 3 + Algorithm_ALGORITHM_EC_P384 Algorithm = 4 + Algorithm_ALGORITHM_EC_P521 Algorithm = 5 + Algorithm_ALGORITHM_HPQT_XWING Algorithm = 6 + Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768 Algorithm = 7 + Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024 Algorithm = 8 ) // Enum value maps for Algorithm. @@ -313,14 +325,20 @@ var ( 3: "ALGORITHM_EC_P256", 4: "ALGORITHM_EC_P384", 5: "ALGORITHM_EC_P521", + 6: "ALGORITHM_HPQT_XWING", + 7: "ALGORITHM_HPQT_SECP256R1_MLKEM768", + 8: "ALGORITHM_HPQT_SECP384R1_MLKEM1024", } Algorithm_value = map[string]int32{ - "ALGORITHM_UNSPECIFIED": 0, - "ALGORITHM_RSA_2048": 1, - "ALGORITHM_RSA_4096": 2, - "ALGORITHM_EC_P256": 3, - "ALGORITHM_EC_P384": 4, - "ALGORITHM_EC_P521": 5, + "ALGORITHM_UNSPECIFIED": 0, + "ALGORITHM_RSA_2048": 1, + "ALGORITHM_RSA_4096": 2, + "ALGORITHM_EC_P256": 3, + "ALGORITHM_EC_P384": 4, + "ALGORITHM_EC_P521": 5, + "ALGORITHM_HPQT_XWING": 6, + "ALGORITHM_HPQT_SECP256R1_MLKEM768": 7, + "ALGORITHM_HPQT_SECP384R1_MLKEM1024": 8, } ) @@ -3727,7 +3745,7 @@ var file_policy_objects_proto_rawDesc = []byte{ 0x00, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x52, - 0x4e, 0x41, 0x4c, 0x10, 0x02, 0x2a, 0x88, 0x02, 0x0a, 0x13, 0x4b, 0x61, 0x73, 0x50, 0x75, 0x62, + 0x4e, 0x41, 0x4c, 0x10, 0x02, 0x2a, 0x9b, 0x03, 0x0a, 0x13, 0x4b, 0x61, 0x73, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x41, 0x6c, 0x67, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x27, 0x0a, 0x23, 0x4b, 0x41, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x4c, 0x47, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, @@ -3744,40 +3762,56 @@ var file_policy_objects_proto_rawDesc = []byte{ 0x38, 0x34, 0x52, 0x31, 0x10, 0x06, 0x12, 0x28, 0x0a, 0x24, 0x4b, 0x41, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x4c, 0x47, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x45, 0x43, 0x5f, 0x53, 0x45, 0x43, 0x50, 0x35, 0x32, 0x31, 0x52, 0x31, 0x10, 0x07, - 0x2a, 0x9b, 0x01, 0x0a, 0x09, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x19, - 0x0a, 0x15, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, - 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x4c, 0x47, - 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x52, 0x53, 0x41, 0x5f, 0x32, 0x30, 0x34, 0x38, 0x10, - 0x01, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x52, - 0x53, 0x41, 0x5f, 0x34, 0x30, 0x39, 0x36, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x41, 0x4c, 0x47, - 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x45, 0x43, 0x5f, 0x50, 0x32, 0x35, 0x36, 0x10, 0x03, - 0x12, 0x15, 0x0a, 0x11, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x45, 0x43, - 0x5f, 0x50, 0x33, 0x38, 0x34, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x41, 0x4c, 0x47, 0x4f, 0x52, - 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x45, 0x43, 0x5f, 0x50, 0x35, 0x32, 0x31, 0x10, 0x05, 0x2a, 0x56, - 0x0a, 0x09, 0x4b, 0x65, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x16, 0x4b, - 0x45, 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x4b, 0x45, 0x59, 0x5f, 0x53, - 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x01, 0x12, 0x16, - 0x0a, 0x12, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x4f, 0x54, - 0x41, 0x54, 0x45, 0x44, 0x10, 0x02, 0x2a, 0x94, 0x01, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x4d, 0x6f, - 0x64, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, - 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, - 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, - 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4b, 0x45, - 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, - 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x4b, 0x45, - 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x03, 0x12, - 0x1c, 0x0a, 0x18, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, - 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x04, 0x42, 0x82, 0x01, - 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, 0x0c, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x64, 0x66, - 0x2f, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, - 0x6f, 0x6c, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0xa2, 0x02, 0x03, 0x50, - 0x58, 0x58, 0xaa, 0x02, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0xca, 0x02, 0x06, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0xe2, 0x02, 0x12, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5c, 0x47, 0x50, - 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x06, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x26, 0x0a, 0x22, 0x4b, 0x41, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, + 0x45, 0x59, 0x5f, 0x41, 0x4c, 0x47, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x48, 0x50, 0x51, 0x54, + 0x5f, 0x58, 0x57, 0x49, 0x4e, 0x47, 0x10, 0x0a, 0x12, 0x33, 0x0a, 0x2f, 0x4b, 0x41, 0x53, 0x5f, + 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, 0x4c, 0x47, 0x5f, 0x45, + 0x4e, 0x55, 0x4d, 0x5f, 0x48, 0x50, 0x51, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x50, 0x32, 0x35, 0x36, + 0x52, 0x31, 0x5f, 0x4d, 0x4c, 0x4b, 0x45, 0x4d, 0x37, 0x36, 0x38, 0x10, 0x0b, 0x12, 0x34, 0x0a, + 0x30, 0x4b, 0x41, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, + 0x41, 0x4c, 0x47, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x48, 0x50, 0x51, 0x54, 0x5f, 0x53, 0x45, + 0x43, 0x50, 0x33, 0x38, 0x34, 0x52, 0x31, 0x5f, 0x4d, 0x4c, 0x4b, 0x45, 0x4d, 0x31, 0x30, 0x32, + 0x34, 0x10, 0x0c, 0x2a, 0x84, 0x02, 0x0a, 0x09, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, + 0x6d, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x55, + 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, + 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x52, 0x53, 0x41, 0x5f, 0x32, 0x30, + 0x34, 0x38, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, + 0x4d, 0x5f, 0x52, 0x53, 0x41, 0x5f, 0x34, 0x30, 0x39, 0x36, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, + 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x45, 0x43, 0x5f, 0x50, 0x32, 0x35, + 0x36, 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, + 0x5f, 0x45, 0x43, 0x5f, 0x50, 0x33, 0x38, 0x34, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x41, 0x4c, + 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x45, 0x43, 0x5f, 0x50, 0x35, 0x32, 0x31, 0x10, + 0x05, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x48, + 0x50, 0x51, 0x54, 0x5f, 0x58, 0x57, 0x49, 0x4e, 0x47, 0x10, 0x06, 0x12, 0x25, 0x0a, 0x21, 0x41, + 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x48, 0x50, 0x51, 0x54, 0x5f, 0x53, 0x45, + 0x43, 0x50, 0x32, 0x35, 0x36, 0x52, 0x31, 0x5f, 0x4d, 0x4c, 0x4b, 0x45, 0x4d, 0x37, 0x36, 0x38, + 0x10, 0x07, 0x12, 0x26, 0x0a, 0x22, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, + 0x48, 0x50, 0x51, 0x54, 0x5f, 0x53, 0x45, 0x43, 0x50, 0x33, 0x38, 0x34, 0x52, 0x31, 0x5f, 0x4d, + 0x4c, 0x4b, 0x45, 0x4d, 0x31, 0x30, 0x32, 0x34, 0x10, 0x08, 0x2a, 0x56, 0x0a, 0x09, 0x4b, 0x65, + 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x16, 0x4b, 0x45, 0x59, 0x5f, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, + 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x4b, 0x45, 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x4b, 0x45, + 0x59, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x4f, 0x54, 0x41, 0x54, 0x45, 0x44, + 0x10, 0x02, 0x2a, 0x94, 0x01, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, + 0x0a, 0x14, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, + 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x4b, 0x45, 0x59, 0x5f, + 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x52, 0x4f, 0x4f, 0x54, + 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, + 0x44, 0x45, 0x5f, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x52, 0x4f, 0x4f, 0x54, + 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x4b, 0x45, 0x59, 0x5f, 0x4d, 0x4f, + 0x44, 0x45, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x4b, + 0x45, 0x59, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, + 0x45, 0x59, 0x5f, 0x4f, 0x4e, 0x4c, 0x59, 0x10, 0x04, 0x42, 0x82, 0x01, 0x0a, 0x0a, 0x63, 0x6f, + 0x6d, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x42, 0x0c, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x64, 0x66, 0x2f, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x67, + 0x6f, 0x2f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0xa2, 0x02, 0x03, 0x50, 0x58, 0x58, 0xaa, 0x02, + 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0xca, 0x02, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0xe2, 0x02, 0x12, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/sdk/basekey.go b/sdk/basekey.go index 5f3cc12004..96f43f927c 100644 --- a/sdk/basekey.go +++ b/sdk/basekey.go @@ -20,37 +20,55 @@ const ( baseKeyPublicKey = "public_key" ) -// TODO: Move this function to ocrypto? -func getKasKeyAlg(alg string) policy.Algorithm { - switch alg { - case string(ocrypto.RSA2048Key): - return policy.Algorithm_ALGORITHM_RSA_2048 - case string(ocrypto.RSA4096Key): - return policy.Algorithm_ALGORITHM_RSA_4096 - case string(ocrypto.EC256Key): - return policy.Algorithm_ALGORITHM_EC_P256 - case string(ocrypto.EC384Key): - return policy.Algorithm_ALGORITHM_EC_P384 - case string(ocrypto.EC521Key): - return policy.Algorithm_ALGORITHM_EC_P521 +func getKasKeyAlg(alg string) (policy.Algorithm, error) { + kt, err := ocrypto.ParseKeyType(alg) + if err != nil { + return policy.Algorithm_ALGORITHM_UNSPECIFIED, fmt.Errorf("invalid alg [%s]: %w", alg, err) + } + return KeyTypeToPolicyAlgorithm(kt) +} + +func KeyTypeToPolicyAlgorithm(kt ocrypto.KeyType) (policy.Algorithm, error) { + switch kt { + case ocrypto.RSA2048Key: + return policy.Algorithm_ALGORITHM_RSA_2048, nil + case ocrypto.RSA4096Key: + return policy.Algorithm_ALGORITHM_RSA_4096, nil + case ocrypto.EC256Key: + return policy.Algorithm_ALGORITHM_EC_P256, nil + case ocrypto.EC384Key: + return policy.Algorithm_ALGORITHM_EC_P384, nil + case ocrypto.EC521Key: + return policy.Algorithm_ALGORITHM_EC_P521, nil + case ocrypto.HybridXWingKey: + return policy.Algorithm_ALGORITHM_HPQT_XWING, nil + case ocrypto.HybridSecp256r1MLKEM768Key: + return policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768, nil + case ocrypto.HybridSecp384r1MLKEM1024Key: + return policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024, nil default: - return policy.Algorithm_ALGORITHM_UNSPECIFIED + return policy.Algorithm_ALGORITHM_UNSPECIFIED, fmt.Errorf("unknown key type: %s", kt) } } -// TODO: Move this function to ocrypto? -func formatAlg(alg policy.Algorithm) (string, error) { +func PolicyAlgorithmToKeyType(alg policy.Algorithm) (ocrypto.KeyType, error) { switch alg { case policy.Algorithm_ALGORITHM_RSA_2048: - return string(ocrypto.RSA2048Key), nil + return ocrypto.RSA2048Key, nil case policy.Algorithm_ALGORITHM_RSA_4096: - return string(ocrypto.RSA4096Key), nil + return ocrypto.RSA4096Key, nil case policy.Algorithm_ALGORITHM_EC_P256: - return string(ocrypto.EC256Key), nil + return ocrypto.EC256Key, nil case policy.Algorithm_ALGORITHM_EC_P384: - return string(ocrypto.EC384Key), nil + return ocrypto.EC384Key, nil case policy.Algorithm_ALGORITHM_EC_P521: - return string(ocrypto.EC521Key), nil + return ocrypto.EC521Key, nil + case policy.Algorithm_ALGORITHM_HPQT_XWING: + return ocrypto.HybridXWingKey, nil + case policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768: + return ocrypto.HybridSecp256r1MLKEM768Key, nil + case policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024: + return ocrypto.HybridSecp384r1MLKEM1024Key, nil case policy.Algorithm_ALGORITHM_UNSPECIFIED: fallthrough default: @@ -58,6 +76,11 @@ func formatAlg(alg policy.Algorithm) (string, error) { } } +func formatAlg(alg policy.Algorithm) (string, error) { + kt, err := PolicyAlgorithmToKeyType(alg) + return string(kt), err +} + // GetBaseKey retrieves the platform base KAS key from the well-known configuration. // The returned key material is expected to be public (algorithm, KID, PEM). func (s SDK) GetBaseKey(ctx context.Context) (*policy.SimpleKasKey, error) { @@ -114,7 +137,11 @@ func parseSimpleKasKey(baseKeyMap map[string]interface{}) (*policy.SimpleKasKey, if !ok { return nil, ErrBaseKeyInvalidFormat } - publicKey[baseKeyAlg] = getKasKeyAlg(alg) + a, err := getKasKeyAlg(alg) + if err != nil { + return nil, fmt.Errorf("%w: %w", ErrMarshalBaseKeyFailed, err) + } + publicKey[baseKeyAlg] = a baseKeyMap[baseKeyPublicKey] = publicKey configJSON, err := json.Marshal(baseKeyMap) if err != nil { diff --git a/sdk/basekey_test.go b/sdk/basekey_test.go index ea0210bdf0..74ad8f5d95 100644 --- a/sdk/basekey_test.go +++ b/sdk/basekey_test.go @@ -63,7 +63,7 @@ func (m *mockWellKnownService) GetWellKnownConfiguration( } func TestGetKasKeyAlg(t *testing.T) { - tests := []struct { + for _, test := range []struct { name string algStr string expected policy.Algorithm @@ -94,21 +94,45 @@ func TestGetKasKeyAlg(t *testing.T) { expected: policy.Algorithm_ALGORITHM_EC_P521, }, { - name: "unsupported algorithm", - algStr: "unsupported", - expected: policy.Algorithm_ALGORITHM_UNSPECIFIED, + name: "hybrid xwing", + algStr: string(ocrypto.HybridXWingKey), + expected: policy.Algorithm_ALGORITHM_HPQT_XWING, + }, + { + name: "hybrid p256 mlkem768", + algStr: string(ocrypto.HybridSecp256r1MLKEM768Key), + expected: policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768, }, { - name: "empty string", - algStr: "", - expected: policy.Algorithm_ALGORITHM_UNSPECIFIED, + name: "hybrid p384 mlkem1024", + algStr: string(ocrypto.HybridSecp384r1MLKEM1024Key), + expected: policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024, }, + } { + t.Run(test.name, func(t *testing.T) { + result, err := getKasKeyAlg(test.algStr) + require.NoError(t, err) + assert.Equal(t, test.expected, result, "Algorithm enum mismatch") + }) } - for _, test := range tests { + for _, test := range []struct { + name string + algStr string + }{ + { + name: "unsupported algorithm", + algStr: "unsupported", + }, + { + name: "empty string", + algStr: "", + }, + } { t.Run(test.name, func(t *testing.T) { - result := getKasKeyAlg(test.algStr) - assert.Equal(t, test.expected, result, "Algorithm enum mismatch") + result, err := getKasKeyAlg(test.algStr) + require.Error(t, err) + assert.Equal(t, policy.Algorithm_ALGORITHM_UNSPECIFIED, result, "Algorithm enum mismatch") }) } } @@ -150,6 +174,24 @@ func TestFormatAlg(t *testing.T) { expected: string(ocrypto.EC521Key), // Note: This matches the implementation expectError: false, }, + { + name: "Hybrid X-Wing", + alg: policy.Algorithm_ALGORITHM_HPQT_XWING, + expected: string(ocrypto.HybridXWingKey), + expectError: false, + }, + { + name: "Hybrid P256+ML-KEM-768", + alg: policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768, + expected: string(ocrypto.HybridSecp256r1MLKEM768Key), + expectError: false, + }, + { + name: "Hybrid P384+ML-KEM-1024", + alg: policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024, + expected: string(ocrypto.HybridSecp384r1MLKEM1024Key), + expectError: false, + }, { name: "Unspecified algorithm", alg: policy.Algorithm_ALGORITHM_UNSPECIFIED, @@ -324,6 +366,9 @@ func TestFormatAlg_GetKasKeyAlg_RoundTrip(t *testing.T) { {"EC-P256", policy.Algorithm_ALGORITHM_EC_P256}, {"EC-P384", policy.Algorithm_ALGORITHM_EC_P384}, {"EC-P521", policy.Algorithm_ALGORITHM_EC_P521}, + {"HPQT-XWing", policy.Algorithm_ALGORITHM_HPQT_XWING}, + {"HPQT-P256-MLKEM768", policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768}, + {"HPQT-P384-MLKEM1024", policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024}, } for _, tc := range supportedAlgs { @@ -331,7 +376,8 @@ func TestFormatAlg_GetKasKeyAlg_RoundTrip(t *testing.T) { formatted, err := formatAlg(tc.alg) require.NoError(t, err, "formatAlg should not error for %s", tc.name) - roundTripped := getKasKeyAlg(formatted) + roundTripped, err := getKasKeyAlg(formatted) + require.NoError(t, err) assert.Equal(t, tc.alg, roundTripped, "round-trip mismatch: formatAlg(%s) = %q → getKasKeyAlg returned %s, want %s", tc.name, formatted, roundTripped, tc.alg) diff --git a/sdk/experimental/tdf/key_access.go b/sdk/experimental/tdf/key_access.go index 6e97701ad5..3974b06cf4 100644 --- a/sdk/experimental/tdf/key_access.go +++ b/sdk/experimental/tdf/key_access.go @@ -165,6 +165,9 @@ func wrapKeyWithPublicKey(symKey []byte, pubKeyInfo keysplit.KASPublicKey) (stri // Determine key type based on algorithm ktype := ocrypto.KeyType(pubKeyInfo.Algorithm) + if ocrypto.IsHybridKeyType(ktype) { + return wrapKeyWithHybrid(ktype, pubKeyInfo.PEM, symKey) + } if ocrypto.IsECKeyType(ktype) { // Handle EC key wrapping return wrapKeyWithEC(ktype, pubKeyInfo.PEM, symKey) @@ -245,3 +248,11 @@ func wrapKeyWithRSA(kasPublicKeyPEM string, symKey []byte) (string, error) { return string(ocrypto.Base64Encode(encryptedKey)), nil } + +func wrapKeyWithHybrid(ktype ocrypto.KeyType, kasPublicKeyPEM string, symKey []byte) (string, string, string, error) { + wrappedDER, err := ocrypto.HybridWrapDEK(ktype, kasPublicKeyPEM, symKey) + if err != nil { + return "", "", "", fmt.Errorf("hybrid wrap failed: %w", err) + } + return string(ocrypto.Base64Encode(wrappedDER)), "hybrid-wrapped", "", nil +} diff --git a/sdk/experimental/tdf/key_access_test.go b/sdk/experimental/tdf/key_access_test.go index 9dac3ca3b6..daea79b69c 100644 --- a/sdk/experimental/tdf/key_access_test.go +++ b/sdk/experimental/tdf/key_access_test.go @@ -35,7 +35,7 @@ SQIDAQAB ) // createTestSplitResult creates a mock SplitResult for testing key access operations -func createTestSplitResult(kasURL, pubKey string, algorithm string) *keysplit.SplitResult { +func createTestSplitResult(pubKey string, algorithm string) *keysplit.SplitResult { // Generate random split data splitData := make([]byte, 32) _, err := rand.Read(splitData) @@ -46,11 +46,11 @@ func createTestSplitResult(kasURL, pubKey string, algorithm string) *keysplit.Sp split := keysplit.Split{ ID: "test-split-1", Data: splitData, - KASURLs: []string{kasURL}, + KASURLs: []string{testKAS1URL}, } pubKeyInfo := keysplit.KASPublicKey{ - URL: kasURL, + URL: testKAS1URL, Algorithm: algorithm, KID: "test-kid-1", PEM: pubKey, @@ -58,14 +58,14 @@ func createTestSplitResult(kasURL, pubKey string, algorithm string) *keysplit.Sp return &keysplit.SplitResult{ Splits: []keysplit.Split{split}, - KASPublicKeys: map[string]keysplit.KASPublicKey{kasURL: pubKeyInfo}, + KASPublicKeys: map[string]keysplit.KASPublicKey{testKAS1URL: pubKeyInfo}, } } func TestBuildKeyAccessObjects(t *testing.T) { t.Run("successfully creates key access objects with RSA public key", func(t *testing.T) { // Test that buildKeyAccessObjects correctly processes RSA keys and creates valid KeyAccess objects - splitResult := createTestSplitResult(testKAS1URL, testRSAPublicKey, "rsa:2048") + splitResult := createTestSplitResult(testRSAPublicKey, "rsa:2048") policyBytes := []byte(testPolicyJSON) metadata := testMetadata @@ -96,7 +96,7 @@ func TestBuildKeyAccessObjects(t *testing.T) { ecPublicKeyPEM, err := ecKeyPair.PublicKeyInPemFormat() require.NoError(t, err, "Should get public key in PEM format") - splitResult := createTestSplitResult(testKAS1URL, ecPublicKeyPEM, "ec:secp256r1") + splitResult := createTestSplitResult(ecPublicKeyPEM, "ec:secp256r1") policyBytes := []byte(testPolicyJSON) metadata := testMetadata @@ -112,6 +112,69 @@ func TestBuildKeyAccessObjects(t *testing.T) { assert.NotEmpty(t, keyAccess.WrappedKey, "Should contain wrapped key data") }) + t.Run("successfully creates key access objects with X-Wing public key", func(t *testing.T) { + xwingKeyPair, err := ocrypto.NewXWingKeyPair() + require.NoError(t, err) + + xwingPublicKeyPEM, err := xwingKeyPair.PublicKeyInPemFormat() + require.NoError(t, err) + + splitResult := createTestSplitResult(xwingPublicKeyPEM, string(ocrypto.HybridXWingKey)) + policyBytes := []byte(testPolicyJSON) + + keyAccessList, err := buildKeyAccessObjects(splitResult, policyBytes, testMetadata) + + require.NoError(t, err, "Should successfully create key access objects with valid X-Wing key") + require.Len(t, keyAccessList, 1) + + keyAccess := keyAccessList[0] + assert.Equal(t, "hybrid-wrapped", keyAccess.KeyType) + assert.NotEmpty(t, keyAccess.WrappedKey) + assert.Empty(t, keyAccess.EphemeralPublicKey) + }) + + t.Run("successfully creates key access objects with P256+ML-KEM-768 public key", func(t *testing.T) { + keyPair, err := ocrypto.NewP256MLKEM768KeyPair() + require.NoError(t, err) + + publicKeyPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + + splitResult := createTestSplitResult(publicKeyPEM, string(ocrypto.HybridSecp256r1MLKEM768Key)) + policyBytes := []byte(testPolicyJSON) + + keyAccessList, err := buildKeyAccessObjects(splitResult, policyBytes, testMetadata) + + require.NoError(t, err, "Should successfully create key access objects with valid P256+ML-KEM-768 key") + require.Len(t, keyAccessList, 1) + + keyAccess := keyAccessList[0] + assert.Equal(t, "hybrid-wrapped", keyAccess.KeyType) + assert.NotEmpty(t, keyAccess.WrappedKey) + assert.Empty(t, keyAccess.EphemeralPublicKey) + }) + + t.Run("successfully creates key access objects with P384+ML-KEM-1024 public key", func(t *testing.T) { + keyPair, err := ocrypto.NewP384MLKEM1024KeyPair() + require.NoError(t, err) + + publicKeyPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + + splitResult := createTestSplitResult(publicKeyPEM, string(ocrypto.HybridSecp384r1MLKEM1024Key)) + policyBytes := []byte(testPolicyJSON) + + keyAccessList, err := buildKeyAccessObjects(splitResult, policyBytes, testMetadata) + + require.NoError(t, err, "Should successfully create key access objects with valid P384+ML-KEM-1024 key") + require.Len(t, keyAccessList, 1) + + keyAccess := keyAccessList[0] + assert.Equal(t, "hybrid-wrapped", keyAccess.KeyType) + assert.NotEmpty(t, keyAccess.WrappedKey) + assert.Empty(t, keyAccess.EphemeralPublicKey) + }) + t.Run("handles multiple KAS URLs in single split", func(t *testing.T) { // Test that multiple KAS URLs in one split create separate KeyAccess objects splitData := make([]byte, 32) @@ -172,7 +235,7 @@ func TestBuildKeyAccessObjects(t *testing.T) { t.Run("handles empty metadata correctly", func(t *testing.T) { // Test that empty metadata is handled without creating encrypted metadata - splitResult := createTestSplitResult(testKAS1URL, testRSAPublicKey, "rsa:2048") + splitResult := createTestSplitResult(testRSAPublicKey, "rsa:2048") keyAccessList, err := buildKeyAccessObjects(splitResult, []byte(testPolicyJSON), "") @@ -424,6 +487,120 @@ func TestWrapKeyWithPublicKey(t *testing.T) { "Ephemeral key should end with PEM footer") }) + t.Run("wraps key with X-Wing public key", func(t *testing.T) { + symKey := make([]byte, 32) + _, err := rand.Read(symKey) + require.NoError(t, err) + + xwingKeyPair, err := ocrypto.NewXWingKeyPair() + require.NoError(t, err) + + xwingPublicKeyPEM, err := xwingKeyPair.PublicKeyInPemFormat() + require.NoError(t, err) + + pubKeyInfo := keysplit.KASPublicKey{ + URL: testKAS1URL, + Algorithm: string(ocrypto.HybridXWingKey), + KID: "test-kid", + PEM: xwingPublicKeyPEM, + } + + wrappedKey, keyType, ephemeralPubKey, err := wrapKeyWithPublicKey(symKey, pubKeyInfo) + + require.NoError(t, err, "Should wrap key with X-Wing public key") + assert.NotEmpty(t, wrappedKey) + assert.Equal(t, "hybrid-wrapped", keyType) + assert.Empty(t, ephemeralPubKey) + + decodedWrappedKey, err := ocrypto.Base64Decode([]byte(wrappedKey)) + require.NoError(t, err) + + privateKeyPEM, err := xwingKeyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + privateKey, err := ocrypto.XWingPrivateKeyFromPem([]byte(privateKeyPEM)) + require.NoError(t, err) + + plaintext, err := ocrypto.XWingUnwrapDEK(privateKey, decodedWrappedKey) + require.NoError(t, err) + assert.Equal(t, symKey, plaintext) + }) + + t.Run("wraps key with P256+ML-KEM-768 public key", func(t *testing.T) { + symKey := make([]byte, 32) + _, err := rand.Read(symKey) + require.NoError(t, err) + + keyPair, err := ocrypto.NewP256MLKEM768KeyPair() + require.NoError(t, err) + + publicKeyPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + + pubKeyInfo := keysplit.KASPublicKey{ + URL: testKAS1URL, + Algorithm: string(ocrypto.HybridSecp256r1MLKEM768Key), + KID: "test-kid", + PEM: publicKeyPEM, + } + + wrappedKey, keyType, ephemeralPubKey, err := wrapKeyWithPublicKey(symKey, pubKeyInfo) + + require.NoError(t, err, "Should wrap key with P256+ML-KEM-768 public key") + assert.NotEmpty(t, wrappedKey) + assert.Equal(t, "hybrid-wrapped", keyType) + assert.Empty(t, ephemeralPubKey) + + decodedWrappedKey, err := ocrypto.Base64Decode([]byte(wrappedKey)) + require.NoError(t, err) + + privateKeyPEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + privateKey, err := ocrypto.P256MLKEM768PrivateKeyFromPem([]byte(privateKeyPEM)) + require.NoError(t, err) + + plaintext, err := ocrypto.P256MLKEM768UnwrapDEK(privateKey, decodedWrappedKey) + require.NoError(t, err) + assert.Equal(t, symKey, plaintext) + }) + + t.Run("wraps key with P384+ML-KEM-1024 public key", func(t *testing.T) { + symKey := make([]byte, 32) + _, err := rand.Read(symKey) + require.NoError(t, err) + + keyPair, err := ocrypto.NewP384MLKEM1024KeyPair() + require.NoError(t, err) + + publicKeyPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + + pubKeyInfo := keysplit.KASPublicKey{ + URL: testKAS1URL, + Algorithm: string(ocrypto.HybridSecp384r1MLKEM1024Key), + KID: "test-kid", + PEM: publicKeyPEM, + } + + wrappedKey, keyType, ephemeralPubKey, err := wrapKeyWithPublicKey(symKey, pubKeyInfo) + + require.NoError(t, err, "Should wrap key with P384+ML-KEM-1024 public key") + assert.NotEmpty(t, wrappedKey) + assert.Equal(t, "hybrid-wrapped", keyType) + assert.Empty(t, ephemeralPubKey) + + decodedWrappedKey, err := ocrypto.Base64Decode([]byte(wrappedKey)) + require.NoError(t, err) + + privateKeyPEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + privateKey, err := ocrypto.P384MLKEM1024PrivateKeyFromPem([]byte(privateKeyPEM)) + require.NoError(t, err) + + plaintext, err := ocrypto.P384MLKEM1024UnwrapDEK(privateKey, decodedWrappedKey) + require.NoError(t, err) + assert.Equal(t, symKey, plaintext) + }) + t.Run("returns error for empty PEM", func(t *testing.T) { // Test error handling for missing public key PEM symKey := make([]byte, 32) diff --git a/sdk/experimental/tdf/keysplit/attributes.go b/sdk/experimental/tdf/keysplit/attributes.go index e12d838e97..27cc1e386f 100644 --- a/sdk/experimental/tdf/keysplit/attributes.go +++ b/sdk/experimental/tdf/keysplit/attributes.go @@ -7,6 +7,7 @@ import ( "log/slog" "github.com/opentdf/platform/protocol/go/policy" + "github.com/opentdf/platform/sdk" ) const unknownAlgorithm = "unknown" @@ -186,22 +187,11 @@ func extractKASGrants(grants []*policy.KeyAccessServer, kasKeys []*policy.Simple // formatAlgorithm converts policy algorithm enum to string func formatAlgorithm(alg policy.Algorithm) string { - switch alg { - case policy.Algorithm_ALGORITHM_UNSPECIFIED: - return unknownAlgorithm - case policy.Algorithm_ALGORITHM_EC_P256: - return "ec:secp256r1" - case policy.Algorithm_ALGORITHM_EC_P384: - return "ec:secp384r1" - case policy.Algorithm_ALGORITHM_EC_P521: - return "ec:secp521r1" - case policy.Algorithm_ALGORITHM_RSA_2048: - return "rsa:2048" - case policy.Algorithm_ALGORITHM_RSA_4096: - return "rsa:4096" - default: + kt, err := sdk.PolicyAlgorithmToKeyType(alg) + if err != nil { return unknownAlgorithm } + return string(kt) } // convertAlgEnum2Simple converts KAS key algorithm enum to policy algorithm enum @@ -217,6 +207,12 @@ func convertAlgEnum2Simple(a policy.KasPublicKeyAlgEnum) policy.Algorithm { return policy.Algorithm_ALGORITHM_RSA_2048 case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096: return policy.Algorithm_ALGORITHM_RSA_4096 + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING: + return policy.Algorithm_ALGORITHM_HPQT_XWING + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768: + return policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768 + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024: + return policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024 case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED: return policy.Algorithm_ALGORITHM_UNSPECIFIED default: diff --git a/sdk/experimental/tdf/keysplit/attributes_test.go b/sdk/experimental/tdf/keysplit/attributes_test.go index 4ed96d38a2..b7095e801d 100644 --- a/sdk/experimental/tdf/keysplit/attributes_test.go +++ b/sdk/experimental/tdf/keysplit/attributes_test.go @@ -46,6 +46,21 @@ func TestFormatAlgorithm(t *testing.T) { alg: policy.Algorithm_ALGORITHM_RSA_4096, expected: "rsa:4096", }, + { + name: "HPQT X-Wing", + alg: policy.Algorithm_ALGORITHM_HPQT_XWING, + expected: "hpqt:xwing", + }, + { + name: "HPQT P256+ML-KEM-768", + alg: policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768, + expected: "hpqt:secp256r1-mlkem768", + }, + { + name: "HPQT P384+ML-KEM-1024", + alg: policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024, + expected: "hpqt:secp384r1-mlkem1024", + }, { name: "unknown algorithm value", alg: policy.Algorithm(999), @@ -97,6 +112,21 @@ func TestConvertAlgEnum2Simple(t *testing.T) { algEnum: policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096, expected: policy.Algorithm_ALGORITHM_RSA_4096, }, + { + name: "HPQT X-Wing", + algEnum: policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING, + expected: policy.Algorithm_ALGORITHM_HPQT_XWING, + }, + { + name: "HPQT P256+ML-KEM-768", + algEnum: policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768, + expected: policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768, + }, + { + name: "HPQT P384+ML-KEM-1024", + algEnum: policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024, + expected: policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024, + }, { name: "unknown enum value", algEnum: policy.KasPublicKeyAlgEnum(999), diff --git a/sdk/experimental/tdf/writer_test.go b/sdk/experimental/tdf/writer_test.go index 296c582041..08f35cfcc8 100644 --- a/sdk/experimental/tdf/writer_test.go +++ b/sdk/experimental/tdf/writer_test.go @@ -58,6 +58,9 @@ func TestWriterEndToEnd(t *testing.T) { {"GetManifestIncludesInitialPolicy", testGetManifestIncludesInitialPolicy}, {"SparseIndicesInOrder", testSparseIndicesInOrder}, {"SparseIndicesOutOfOrder", testSparseIndicesOutOfOrder}, + {"HybridXWingFlow", testHybridXWingFlow}, + {"HybridP256MLKEM768Flow", testHybridP256MLKEM768Flow}, + {"HybridP384MLKEM1024Flow", testHybridP384MLKEM1024Flow}, } for _, tc := range testCases { @@ -877,6 +880,157 @@ func createTestAttributeWithRule(fqn, kasURL, kid string, rule policy.AttributeR return value } +func createTestAttributeWithAlgorithm(t *testing.T, fqn, kasURL, kid string, alg policy.Algorithm, pem string) *policy.Value { + t.Helper() + value := createTestAttribute(fqn, kasURL, kid) + require.NotEmpty(t, value.GetGrants(), "createTestAttribute returned no grants") + require.NotEmpty(t, value.GetGrants()[0].GetKasKeys(), "createTestAttribute returned no kas keys") + value.GetGrants()[0].GetKasKeys()[0].PublicKey.Algorithm = alg + value.GetGrants()[0].GetKasKeys()[0].PublicKey.Pem = pem + return value +} + +// hybridUnwrapForTest base64-decodes the wrappedKey from a manifest KAO and +// unwraps it with the matching hybrid private key, asserting the recovered DEK +// is non-empty. This proves the writer's `hybrid-wrapped` output is consumable +// by the corresponding ocrypto unwrap path. +func hybridUnwrapForTest(t *testing.T, ktype ocrypto.KeyType, privatePEM, wrappedKeyB64 string) { + t.Helper() + wrappedDER, err := ocrypto.Base64Decode([]byte(wrappedKeyB64)) + require.NoError(t, err, "Base64Decode wrapped key") + + switch ktype { //nolint:exhaustive // only handle hybrid types + case ocrypto.HybridXWingKey: + raw, err := ocrypto.XWingPrivateKeyFromPem([]byte(privatePEM)) + require.NoError(t, err) + dek, err := ocrypto.XWingUnwrapDEK(raw, wrappedDER) + require.NoError(t, err, "XWingUnwrapDEK") + assert.NotEmpty(t, dek, "X-Wing recovered DEK") + case ocrypto.HybridSecp256r1MLKEM768Key: + raw, err := ocrypto.P256MLKEM768PrivateKeyFromPem([]byte(privatePEM)) + require.NoError(t, err) + dek, err := ocrypto.P256MLKEM768UnwrapDEK(raw, wrappedDER) + require.NoError(t, err, "P256MLKEM768UnwrapDEK") + assert.NotEmpty(t, dek, "P-256+ML-KEM-768 recovered DEK") + case ocrypto.HybridSecp384r1MLKEM1024Key: + raw, err := ocrypto.P384MLKEM1024PrivateKeyFromPem([]byte(privatePEM)) + require.NoError(t, err) + dek, err := ocrypto.P384MLKEM1024UnwrapDEK(raw, wrappedDER) + require.NoError(t, err, "P384MLKEM1024UnwrapDEK") + assert.NotEmpty(t, dek, "P-384+ML-KEM-1024 recovered DEK") + default: + t.Fatalf("unsupported hybrid key type for round-trip: %s", ktype) + } +} + +func testHybridXWingFlow(t *testing.T) { + ctx := t.Context() + + keyPair, err := ocrypto.NewXWingKeyPair() + require.NoError(t, err) + pubPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + privPEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + + writer, err := NewWriter(ctx) + require.NoError(t, err) + + _, err = writer.WriteSegment(ctx, 0, []byte("hybrid xwing test data")) + require.NoError(t, err) + + attributes := []*policy.Value{ + createTestAttributeWithAlgorithm( + t, + "https://example.com/attr/Classification/value/Secret", + testKAS1, "xwing-kid", + policy.Algorithm_ALGORITHM_HPQT_XWING, pubPEM, + ), + } + result, err := writer.Finalize(ctx, WithAttributeValues(attributes)) + require.NoError(t, err) + assert.NotEmpty(t, result.Data) + + keyAccess := result.Manifest.KeyAccessObjs[0] + assert.Equal(t, "hybrid-wrapped", keyAccess.KeyType) + assert.NotEmpty(t, keyAccess.WrappedKey) + + validateManifestSchema(t, result.Manifest) + hybridUnwrapForTest(t, ocrypto.HybridXWingKey, privPEM, keyAccess.WrappedKey) +} + +func testHybridP256MLKEM768Flow(t *testing.T) { + ctx := t.Context() + + keyPair, err := ocrypto.NewP256MLKEM768KeyPair() + require.NoError(t, err) + pubPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + privPEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + + writer, err := NewWriter(ctx) + require.NoError(t, err) + + _, err = writer.WriteSegment(ctx, 0, []byte("hybrid p256 mlkem768 test data")) + require.NoError(t, err) + + attributes := []*policy.Value{ + createTestAttributeWithAlgorithm( + t, + "https://example.com/attr/Classification/value/Secret", + testKAS1, "p256mlkem768-kid", + policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768, pubPEM, + ), + } + result, err := writer.Finalize(ctx, WithAttributeValues(attributes)) + require.NoError(t, err) + assert.NotEmpty(t, result.Data) + + keyAccess := result.Manifest.KeyAccessObjs[0] + assert.Equal(t, "hybrid-wrapped", keyAccess.KeyType) + assert.NotEmpty(t, keyAccess.WrappedKey) + + validateManifestSchema(t, result.Manifest) + hybridUnwrapForTest(t, ocrypto.HybridSecp256r1MLKEM768Key, privPEM, keyAccess.WrappedKey) +} + +func testHybridP384MLKEM1024Flow(t *testing.T) { + ctx := t.Context() + + keyPair, err := ocrypto.NewP384MLKEM1024KeyPair() + require.NoError(t, err) + pubPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + privPEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + + writer, err := NewWriter(ctx) + require.NoError(t, err) + + _, err = writer.WriteSegment(ctx, 0, []byte("hybrid p384 mlkem1024 test data")) + require.NoError(t, err) + + attributes := []*policy.Value{ + createTestAttributeWithAlgorithm( + t, + "https://example.com/attr/Classification/value/Secret", + testKAS1, "p384mlkem1024-kid", + policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024, pubPEM, + ), + } + result, err := writer.Finalize(ctx, WithAttributeValues(attributes)) + require.NoError(t, err) + assert.NotEmpty(t, result.Data) + + keyAccess := result.Manifest.KeyAccessObjs[0] + assert.Equal(t, "hybrid-wrapped", keyAccess.KeyType) + assert.NotEmpty(t, keyAccess.WrappedKey) + + validateManifestSchema(t, result.Manifest) + hybridUnwrapForTest(t, ocrypto.HybridSecp384r1MLKEM1024Key, privPEM, keyAccess.WrappedKey) +} + // validateManifestSchema validates a TDF manifest against the JSON schema func validateManifestSchema(t *testing.T, manifest *Manifest) { t.Helper() diff --git a/sdk/granter.go b/sdk/granter.go index 8275a04f42..ede578cc17 100644 --- a/sdk/granter.go +++ b/sdk/granter.go @@ -286,6 +286,12 @@ func convertAlgEnum2Simple(a policy.KasPublicKeyAlgEnum) policy.Algorithm { return policy.Algorithm_ALGORITHM_RSA_2048 case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096: return policy.Algorithm_ALGORITHM_RSA_4096 + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING: + return policy.Algorithm_ALGORITHM_HPQT_XWING + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768: + return policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768 + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024: + return policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024 case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED: return policy.Algorithm_ALGORITHM_UNSPECIFIED default: @@ -293,24 +299,6 @@ func convertAlgEnum2Simple(a policy.KasPublicKeyAlgEnum) policy.Algorithm { } } -// convertStringToAlgorithm converts a string algorithm representation to policy.Algorithm -func convertStringToAlgorithm(alg string) policy.Algorithm { - switch ocrypto.KeyType(strings.ToLower(alg)) { - case ocrypto.EC256Key: - return policy.Algorithm_ALGORITHM_EC_P256 - case ocrypto.EC384Key: - return policy.Algorithm_ALGORITHM_EC_P384 - case ocrypto.EC521Key: - return policy.Algorithm_ALGORITHM_EC_P521 - case ocrypto.RSA2048Key: - return policy.Algorithm_ALGORITHM_RSA_2048 - case ocrypto.RSA4096Key: - return policy.Algorithm_ALGORITHM_RSA_4096 - default: - return policy.Algorithm_ALGORITHM_UNSPECIFIED - } -} - type grantType int const ( @@ -490,31 +478,18 @@ func algProto2String(e policy.KasPublicKeyAlgEnum) string { return string(ocrypto.RSA2048Key) case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_RSA_4096: return string(ocrypto.RSA4096Key) + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING: + return string(ocrypto.HybridXWingKey) + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768: + return string(ocrypto.HybridSecp256r1MLKEM768Key) + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024: + return string(ocrypto.HybridSecp384r1MLKEM1024Key) case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED: return "" } return "" } -func algProto2OcryptoKeyType(e policy.Algorithm) ocrypto.KeyType { - switch e { - case policy.Algorithm_ALGORITHM_EC_P256: - return ocrypto.EC256Key - case policy.Algorithm_ALGORITHM_EC_P384: - return ocrypto.EC384Key - case policy.Algorithm_ALGORITHM_EC_P521: - return ocrypto.EC521Key - case policy.Algorithm_ALGORITHM_RSA_2048: - return ocrypto.RSA2048Key - case policy.Algorithm_ALGORITHM_RSA_4096: - return ocrypto.RSA4096Key - case policy.Algorithm_ALGORITHM_UNSPECIFIED: - return ocrypto.KeyType("") - default: - return ocrypto.KeyType("") - } -} - func storeKeysToCache(logger *slog.Logger, kases []*policy.KeyAccessServer, keys []*policy.SimpleKasKey, c *kasKeyCache, kc *rlKeyCache) { for _, kas := range kases { keys := kas.GetPublicKey().GetCached().GetKeys() @@ -726,7 +701,10 @@ func (r granter) resolveTemplate(ctx context.Context, kaoKeyAlg string, genSplit } o.identifier = kpub.KID // Convert the string algorithm to the appropriate enum - algEnum := convertStringToAlgorithm(kpub.Algorithm) + algEnum, err := getKasKeyAlg(strings.ToLower(kpub.Algorithm)) + if err != nil { + return nil, fmt.Errorf("unsupported algorithm for kas [%s#%s]: %w", o.KASURI(), kpub.KID, err) + } r.keyCache.c[*o] = &policy.SimpleKasKey{ KasUri: o.KASURI(), PublicKey: &policy.SimpleKasPublicKey{ @@ -740,7 +718,10 @@ func (r granter) resolveTemplate(ctx context.Context, kaoKeyAlg string, genSplit if !ok || kpub.GetPublicKey() == nil || kpub.GetPublicKey().GetPem() == "" { return nil, fmt.Errorf("no key found for resource locator [%s]", o) } - algorithm := algProto2OcryptoKeyType(kpub.GetPublicKey().GetAlgorithm()) + algorithm, err := PolicyAlgorithmToKeyType(kpub.GetPublicKey().GetAlgorithm()) + if err != nil { + return nil, fmt.Errorf("invalid algorithm [%v] for kas %s with kid [%s]: %w", kpub.GetPublicKey().GetAlgorithm(), kpub.GetKasUri(), kpub.GetPublicKey().GetKid(), err) + } p = append(p, kaoTpl{o.KASURI(), splitID, o.ID(), kpub.GetPublicKey().GetPem(), algorithm}) } } diff --git a/sdk/schema/manifest-lax.schema.json b/sdk/schema/manifest-lax.schema.json index a31abd75fb..2d9c730279 100644 --- a/sdk/schema/manifest-lax.schema.json +++ b/sdk/schema/manifest-lax.schema.json @@ -52,7 +52,7 @@ "type": { "description": "The type of key access object.", "type": "string", - "enum": ["wrapped", "remote"] + "enum": ["wrapped", "ec-wrapped", "hybrid-wrapped", "remote"] }, "url": { "description": "A fully qualified URL pointing to a key access service responsible for managing access to the encryption keys.", diff --git a/sdk/schema/manifest.schema.json b/sdk/schema/manifest.schema.json index 6488623fa2..8a0e47ac94 100644 --- a/sdk/schema/manifest.schema.json +++ b/sdk/schema/manifest.schema.json @@ -52,7 +52,7 @@ "type": { "description": "The type of key access object.", "type": "string", - "enum": ["ec-wrapped", "remote", "wrapped"] + "enum": ["ec-wrapped", "hybrid-wrapped", "remote", "wrapped"] }, "url": { "description": "A fully qualified URL pointing to a key access service responsible for managing access to the encryption keys.", @@ -239,4 +239,4 @@ } }, "required": ["payload", "encryptionInformation"] - } \ No newline at end of file + } diff --git a/sdk/tdf.go b/sdk/tdf.go index db22208b5f..3940291a50 100644 --- a/sdk/tdf.go +++ b/sdk/tdf.go @@ -43,6 +43,7 @@ const ( kKeySize = 32 kWrapped = "wrapped" kECWrapped = "ec-wrapped" + kHybridWrapped = "hybrid-wrapped" kKasProtocol = "kas" kSplitKeyType = "split" kGCMCipherAlgorithm = "AES-256-GCM" @@ -674,7 +675,15 @@ func createKeyAccess(kasInfo KASInfo, symKey []byte, policyBinding PolicyBinding } ktype := ocrypto.KeyType(kasInfo.Algorithm) - if ocrypto.IsECKeyType(ktype) { + switch { + case ocrypto.IsHybridKeyType(ktype): + wrappedKey, err := generateWrapKeyWithHybrid(kasInfo.Algorithm, kasInfo.PublicKey, symKey) + if err != nil { + return KeyAccess{}, err + } + keyAccess.KeyType = kHybridWrapped + keyAccess.WrappedKey = wrappedKey + case ocrypto.IsECKeyType(ktype): mode, err := ocrypto.ECKeyTypeToMode(ktype) if err != nil { return KeyAccess{}, err @@ -686,7 +695,7 @@ func createKeyAccess(kasInfo KASInfo, symKey []byte, policyBinding PolicyBinding keyAccess.KeyType = kECWrapped keyAccess.WrappedKey = wrappedKeyInfo.wrappedKey keyAccess.EphemeralPublicKey = wrappedKeyInfo.publicKey - } else { + default: wrappedKey, err := generateWrapKeyWithRSA(kasInfo.PublicKey, symKey) if err != nil { return KeyAccess{}, err @@ -761,6 +770,14 @@ func generateWrapKeyWithRSA(publicKey string, symKey []byte) (string, error) { return string(ocrypto.Base64Encode(wrappedKey)), nil } +func generateWrapKeyWithHybrid(algorithm, publicKeyPEM string, symKey []byte) (string, error) { + wrappedDER, err := ocrypto.HybridWrapDEK(ocrypto.KeyType(algorithm), publicKeyPEM, symKey) + if err != nil { + return "", fmt.Errorf("generateWrapKeyWithHybrid: %w", err) + } + return string(ocrypto.Base64Encode(wrappedDER)), nil +} + // create policy object func createPolicyObject(attributes []AttributeValueFQN) (PolicyObject, error) { uuidObj, err := uuid.NewUUID() diff --git a/sdk/tdf_hybrid_test.go b/sdk/tdf_hybrid_test.go new file mode 100644 index 0000000000..86eb1e98e5 --- /dev/null +++ b/sdk/tdf_hybrid_test.go @@ -0,0 +1,84 @@ +package sdk + +import ( + "testing" + + "github.com/opentdf/platform/lib/ocrypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateKeyAccessWithXWingKey(t *testing.T) { + symKey := []byte("0123456789abcdef0123456789abcdef") + keyAccess, err := createKeyAccess(KASInfo{ + URL: "https://kas.example.com", + KID: "xwing-kid", + Algorithm: string(ocrypto.HybridXWingKey), + PublicKey: mockHybridXWingPublicKey, + }, symKey, PolicyBinding{}, "", "") + require.NoError(t, err) + + assert.Equal(t, kHybridWrapped, keyAccess.KeyType) + assert.Empty(t, keyAccess.EphemeralPublicKey) + assert.NotEmpty(t, keyAccess.WrappedKey) + + privateKey, err := ocrypto.XWingPrivateKeyFromPem([]byte(mockHybridXWingPrivateKey)) + require.NoError(t, err) + + wrappedKey, err := ocrypto.Base64Decode([]byte(keyAccess.WrappedKey)) + require.NoError(t, err) + + plaintext, err := ocrypto.XWingUnwrapDEK(privateKey, wrappedKey) + require.NoError(t, err) + assert.Equal(t, symKey, plaintext) +} + +func TestCreateKeyAccessWithP256MLKEM768Key(t *testing.T) { + symKey := []byte("0123456789abcdef0123456789abcdef") + keyAccess, err := createKeyAccess(KASInfo{ + URL: "https://kas.example.com", + KID: "p256mlkem768-kid", + Algorithm: string(ocrypto.HybridSecp256r1MLKEM768Key), + PublicKey: mockHybridP256MLKEM768PublicKey, + }, symKey, PolicyBinding{}, "", "") + require.NoError(t, err) + + assert.Equal(t, kHybridWrapped, keyAccess.KeyType) + assert.Empty(t, keyAccess.EphemeralPublicKey) + assert.NotEmpty(t, keyAccess.WrappedKey) + + privateKey, err := ocrypto.P256MLKEM768PrivateKeyFromPem([]byte(mockHybridP256MLKEM768PrivateKey)) + require.NoError(t, err) + + wrappedKey, err := ocrypto.Base64Decode([]byte(keyAccess.WrappedKey)) + require.NoError(t, err) + + plaintext, err := ocrypto.P256MLKEM768UnwrapDEK(privateKey, wrappedKey) + require.NoError(t, err) + assert.Equal(t, symKey, plaintext) +} + +func TestCreateKeyAccessWithP384MLKEM1024Key(t *testing.T) { + symKey := []byte("0123456789abcdef0123456789abcdef") + keyAccess, err := createKeyAccess(KASInfo{ + URL: "https://kas.example.com", + KID: "p384mlkem1024-kid", + Algorithm: string(ocrypto.HybridSecp384r1MLKEM1024Key), + PublicKey: mockHybridP384MLKEM1024PublicKey, + }, symKey, PolicyBinding{}, "", "") + require.NoError(t, err) + + assert.Equal(t, kHybridWrapped, keyAccess.KeyType) + assert.Empty(t, keyAccess.EphemeralPublicKey) + assert.NotEmpty(t, keyAccess.WrappedKey) + + privateKey, err := ocrypto.P384MLKEM1024PrivateKeyFromPem([]byte(mockHybridP384MLKEM1024PrivateKey)) + require.NoError(t, err) + + wrappedKey, err := ocrypto.Base64Decode([]byte(keyAccess.WrappedKey)) + require.NoError(t, err) + + plaintext, err := ocrypto.P384MLKEM1024UnwrapDEK(privateKey, wrappedKey) + require.NoError(t, err) + assert.Equal(t, symKey, plaintext) +} diff --git a/sdk/tdf_test.go b/sdk/tdf_test.go index 0aa58daa3f..650fce17b1 100644 --- a/sdk/tdf_test.go +++ b/sdk/tdf_test.go @@ -259,6 +259,120 @@ A1UdIwQYMBaAFLg9mMeD25ZGvmjSYaunIPoeekzlMA8GA1UdEwEB/wQFMAMBAf8w CgYIKoZIzj0EAwIDSAAwRQIhALYXC70t37RlmIkRDlUTehiVEHpSQXz04wQ9Ivw+ 4h4hAiBNR3rD3KieiJaiJrCfM6TPJL7TIch7pAhMHdG6IPJMoQ== -----END CERTIFICATE-----` + + mockHybridXWingPublicKey = `-----BEGIN XWING PUBLIC KEY----- +4pKOW2Z+TqohuAO7Z8m1J9Ik5jbLOZgpfKKTdEXD4mqLgXa8D5RNaYhtkfJn4js6 +MWO35ZJqlfVuCoBnruillfoEY2VD6OVB7AQqBJyzhLeAFsOYgwLIF9UBXixSo6ym +eAA6GhYvzViUx/oQSHCI6JWhKhINXqar7usmFlxC/da4rolS9AGAkpx3S8F1VRyN +tBahkbcLDptIqzRgOJWaXuYdtRqiuqahJyEWLjal/FQa0+dTA5oWn4IWUZRGvVut +T/AFXMHMPdY7JmmmhNJAJTqcblHJ+FJ1YTBi0bEQE7F4XlqpdRxwISJSAGCZN6WD +eTVF9CmpM0iJbnlN5cvAcwCFGamL5II0wwEhftMDIoCb+IiR8NQcQfJnFus9DvhR +k5k7HVtjoumWWksL+tOudUtmYaOnV4CwyAunc8W+vsQ6edoCQWy8peRMvVcoaDUE +tVuACiiwIWADLbsmGyiBR6x4/ashJLs46XsB4pmB1BfIf4yis+B5VvLCnZIsMvKH +jLl81PMH4AsIytN0H/GOkJI/y1iGZsJkCZN15WJ2tAuwyMqz0yooh1lZIedgTheE +lBKYx1YlFvmSCZBfLnKOIWFHpqkQ8OTGXWgBfvhwyhEui3ZhJbhmgBQrYAeN8ZcP +s2isMmmzKVRcMzVFXaynv+UIiWVAH5BeXUbL0BVIkJulgXJptEyXs6JPGbEu1/ug +uvaWx6xWQ3NiR+BOA5eIigwjoQEtyYRczlg0kUcivca+/TiE3yh2gkU2xhLNFXdL +mHw1jNIbD5O5UPoGEdJ2XRLLVZJhhdmSWpN51FoW/OA6tJaMedtaNwjKD9wjJYEM +izoy9cSNuWZNKMlNdNc9aPfIcQoD2xKKKZR7iHdVNpRQEYiWFrxqT8aY3JN8UjEy +hdqeS7VAbQc28Ts7kxNa2xRmBQaOn3pb/eO+5LV/Y0Y4E7U7TXt3eBwbo5luf6Sg +dnBjypqJAmUzpEl6MrOs7Hh7RxByyKDIgYiNAaNUnwOI7USjxlN5sKhpj+iKvCYF +oCmRBMQ8w+p2s3GiXtB/whh1gwtIzTGkBpyaJrpjrFm67Bp88aUG3GJ5QKy/YPct +tVKRPcNRhlsrOsEwB0qE/+Qj2gcPDAydGIp3hxiJxFgun1RQp/BkiEJW22xN2Vim +n+tyUcRRvQCEoSU0+NXHddQvkzwHJVpdvsgRfgxHIlpTGXBReaQnRGZl2zEDVZGb +vva6ubOu8XhMJAs1n9M41aVlP4bDmwmNv/oTKwi2kSdObkZv2INLucxtiJZ3BhbC +NMiEXVU9DdS/USqZFpcQHqYB63hAMdmRsaM1wVW7HbRumglJY+A/hqgaV2wGtjNR +5qNHS4FTT7euxsOs0PIxE3sOgDmseTd0UditGXd3O5xx7LkBe4GdNTuXq+rMgiZf +P0i2ugDCV4McS4zKfpamYPQKqBufsiNkbMSVu+Mb7kQcgZZBY8gTnEyEY2k9oiJ5 +v6dH/oNa5ohGvHJjEcm4/3y3C7er0OKKM8JLkuIuUYsP7yqPY0V8LPjKI/wS0wxS +yv8kgAJe+QrgMAOzPsw5fAeK5Lrz+OhSRsF/hRJnlAgbv/UAHZQR7ueSrL6Tj8tY +Ubd0E5EZlOEecFd/41z5QA== +-----END XWING PUBLIC KEY-----` + + // X-Wing private key is a 32-byte seed; the full X25519 + ML-KEM-768 + // components are derived from it at runtime by circl. + mockHybridXWingPrivateKey = `-----BEGIN XWING PRIVATE KEY----- +7uCvk28wGoVrwW5nU2huW28UXWa5tZMom6Zds8uohrA= +-----END XWING PRIVATE KEY-----` + + mockHybridP256MLKEM768PublicKey = `-----BEGIN SECP256R1 MLKEM768 PUBLIC KEY----- +BGtgB2txkSmwez5qXBEmetZijnkCuYWdhPvcPoAHr5Aiv1Zk895ARtcgbO1KIye8 +52Wnc48CY2baUfSHptVKmfsnRbmsDEdZoTWyaaaAmieMws9Z9yZuwVNB2sx7AQES +d7ozp2t+nAftGc0WhUYxXDaup0usxA0QkpR7VqDa/ATqek+XlQm3TJ4pzMMIPK9H +FY6iUJchpAZ7+nQesKpDErrzQxeoGbCOOIqxAyaHNVgbxAxVIj8c23iwdMZKwMPC +LDb22Do61X7vFV0WCTTeVRJJ6lXVA6TkyVpXmb2A4gpUtSTGssINSSxxrA/wklA8 +gpqb4lurdYylQxBwArs0wnzhmThm2T/gOW89Mw6XnLbNqosqlcESwjfKXCKt63ZB +ZDVR2Wlc2RxWu0jysaoqUkYIZREceoFjSUWwagI0o8TVeAiVUBJD0AgtNcouJjad +pJvB6MYQlpe2F2sr1gvOlZph1IBkgpO8dRlKZ1yEgzUchG1tummcs3/lq2ois8fl +ZABkxVs9fMkO5G3vOgzucYjQe698sLTzaRygiKHnVS5GsWhBOq6eWVfOiwt+Klvv +KI6AfBQ2UFXsrBqXW8sjApWQ1xZVICtSBsksBhuf8GDh3AlNYxAJw0AAO6vgc7is +SRVZKCOsCKby+LxlFiEabGYbES07iHBvqx5ZCGHl+RB5SqJzWV/nPIrHrBmNiFvq +EYTnnLJ1hyqa7ABjGCeisoU6Js3yw1L6QMwtZS4sjMQdSWCoVqIeBimFtbFacVKR +OhR2YUVesQ8hgp21CjtJRMF8917OCbc1xrMj4by5tr5Aq0LjRlL+EcIcNgkjJrz9 +sXrXK2GvxZcf/FknshQ4pme+AJIvp771I3SLVEvdhbf35aRJCIQ3xQ5Y6Ijxa5Ag +wls8WMkAwQw3IBjNeYVcYn6GiTOet7yAoU+Z9Jx06SK/kqQ7HMJDChkJtLJLNJH/ +a7HrRZlZ4TA92kLP+R/ym6Kle8rN1iNiUXLxmKUR51vuNKnPVh4AGKQb0MYQurjo +YWcVg6I6MYV2XFIJAiqTBdC8kWUyZFYrw5M4E3RM+0M/slEWyQAf1L26FQodUKce +dqpo94trsCjTpbRQ+aTSNZdAHMwxkrR02TzUmoWTl0TUE8nS6yxTwUcd9wy+5KnX +mzHD42I6eyTgnAhLiZOOEpBXBX6VM4Zq1z6GO25id7kju2kgEREFnFVf7G3JWrZz +yXJ1SBLVlina4kmexJTTAyzi9IQqcAewm6nsc7byBpBenBA6aAjYZMQYtaSK+IRE +6norOKeJ0w+ZpAPdC43Mw7GBhD4eI3FQcJOce2L9yXgPYQCEaEJvDJLmJKo26iq5 +WmYEQwe1abKouruKNyaON2TEWLQBSqujca8MFKBOsXisvApkdm+fK3CLVjgSCAM4 +7LfBDFSHA7wj+KC2oXqUgwinBSgYkkJ6Vahny6GadZdpZAmYQCNLhgDDMAOcVxUx +aSQeIECzocR08QslRXNV542FlXlVx2b/ZC5yaFwAUYOACbyYq1azeTOIWyFoKGKa +uRpK+2jglo7wB7kU5LcIB8ZTFwLjGDZsEKdbmgzV+yVDqxXU1llgvKfhFmOfSALF +S39eOBaIhT1WOWpxinZRaagtmi8UQ0uE0uGhIV7fe+lLGuLxYKa/KYQe7wL6j0sL +wQ== +-----END SECP256R1 MLKEM768 PUBLIC KEY-----` + + mockHybridP256MLKEM768PrivateKey = `-----BEGIN SECP256R1 MLKEM768 PRIVATE KEY----- +j60Fn/LBoCGnWihNB3Q6lXJaO4EkMCrEnOT3/z0zZ6Kdr+6m7ww3N6lLMP+Rb2O1 +3gjrOyiNXWbaTwl1mI2vi03PoW2bJbE1+wJuiT/2njjrBRyIxLqRD/4zbiscmhOp +-----END SECP256R1 MLKEM768 PRIVATE KEY-----` + + mockHybridP384MLKEM1024PublicKey = `-----BEGIN SECP384R1 MLKEM1024 PUBLIC KEY----- +BFFAmpsfvYsT+TgT6noyJJ/0402OEfhJCdG9cXJDQPo5Q7VGRTr14roVHy8VP6Me +p4aiDnR2RaL+K7SmqsGRzP/vOSNz3ApEl24pAI8CnLMHDhkM7fKyAQrDje4aqQQv +2/a7h0LFLy27wBaSzpKmCzVrDtoFrVY8G9smtWoxyGucRpT5qxvhNcoHxpt3fyaV +mn0yXgyHZNiyxxsrPtjWA4x3bATWl5+bMRZ8rlFcmVbYFuGmLcnqXzf3ESyaCxxi +nCRGnIqWSPrlB56kS+6xXIykOsXSLgjaA1QUG/3XSNjXcKlayMa4g6qalMQ0v5jR +M2Z3FZzKnIpzJT1HejYJOC1JavnsOgWGkjGyIRZwfgpExdkCaMOQn9vVBbF7axDI +pY48aFNJEUooKYUpfKPypksbnRIJjNd2Wek2F7H2wyYrfx62MalHmOCheNoGaTGW +diokDS81m8jIUVGGuzGlbaPWLz9wBKgofzGrlPnispojJu2ZRK0aHAM6eOz5N1Zn +zh6SVgTmVD9GGo3FgVMcBxmICxroLEEUuov8vkq1dkfwDHXZDmoTRTHrefzbyjNj +K1xUIdlbWWjmUokmZ4OqAck0nRwImL13qYvWMpcWYWUxpxHzPozXBBKIxTnAI9UM +VU85htoJIh5iMXZoy+/bu3TawdaMHNWbtOlcjyE2m3m2F0Z7v7RVHuP2mYfErW/H +NRCEKhsmUulJRRDnVvdQCwrpE+rDCCf6zcybaDzhSKMkZlP8V0UDGr4Ghgg7KfYD +hg1ppiVlFhljvMn8c2fGrWqFu1KjJ2MHbpIBIPu6pnOjjyuJp7xFtsh4ReqMbNsX +WMcJF83UvJvxd6+AgGiVbckRXUz5PEF6GeLxACKzMnwDqu9VWOSXifYbhuCWbv6M +diYVqg7zjafUU1Rpwolbc2rpGiwciYX7APrnM7jnVQk6kY7GyyxoXFjhk+vGf0+r +OAHKei4ldZnLObukFdOQiIhWIdCSoh47MutmsS7RLCmVOPzozkMzg+GgoeMxgt4U +sgWMhtP2dmi2VVY0PD0HeN1LCGJqqCPyop15QkNxYTiXx5sgwggppfkZrcbJLCuT +l5q5NRzwF3a3wZerjJd1L3QXM+QprnUIlwGCg25ZWnAzZB0FvxExIQ1wsusJZ24x +uPiBACs2sSvlZoPAN0imG6EWFvX8SfNzpTnpQP6jR2iAQea0EJPqSYv0kmtYIux3 +shtacT0DH/5TaiG6DITyzTzUV1OTY/mpBqBkzVfwLN9BCzyINHmSDjsmExCKY3CY +Kd3RLuLDdM0AkjHHcl9zo7RypdAGIKnScqASeoR3yOUaP4LKysCUB6kBDg3ZftQR +IHiLOC6aMMKMdZbpKDuECBYDxblTOrcBhnAlX7eokZk1IEMSbmWngwHTfVRbfx+A +A5pUOCjxkYanl234p4KZYLRFMonalmkLb3msgY81ctzZjOxIXAb4XkirAiGDZe2V +T32HKlVjK2hzZpwRjZZ5rKS5IuRUiF6KZnnqDDzaI4dSfd4Sy1+FtmaAMwvidjf7 +FaPxCaJAccnVObwwjYNFuN1otiK6d860ot9AEUbInOOFEpTqzUUChY7Yr9Y4H/L3 +iYRbiH6LLfzjzIiGUEIkWCcar6jyZoKqwD7sMgMVGBOQjRqGx79KVgbSt1uakCBM +UaS2AEwsmFVVD8HCBGkFW5n1kAtrtmpAVZQiSSALW1BDl8csWz+SwGFpVqmHXHBp +EAYQb4HpGOE5X4sIIj1iY/B0vqpZHCUWbTUzJ7TLfVunawHVbrhKfwcIxuU2BHb3 +fg8giZ6GzNrUwN8YLeejct4iOv7IPF1XUJiqH5Qmb5USx96jtaJJLY+QDwtxfDcc +acsJKxn1MpRgrlS4vx8HOIqUhYlAnc2gmoU5BvZAXhXSUaXTLCpBGJN0uhc0Lyo3 +d2BKQzJEqmEHtB8cU544AKdjwgbWsJhFtukXYg6rSYDZjTlKSztyV1omJ+9EUIN5 +v7CCLVfKunF0Yx1LAL8JjQUhaB3cGHicmq9lC8pSBkAjKn3ZIrO8zuCDt6FTtzYZ +QVLjh0Hwh2hYanBLTDpLCpPxOdjYtWWWTRMnZ1wLwFQ0dPhbY8d6hQMrH5bBgg37 +dG0Gc/66ok9LqouBxAbrPBiooEhENhm4j2uiUSSKxY/bwlNHkgBle/VImlJ8Gt08 +f+Pe1WdvKDHXa4HUtLeScODyYJAVXQF6Ww1pLvCRy/gd +-----END SECP384R1 MLKEM1024 PUBLIC KEY-----` + + mockHybridP384MLKEM1024PrivateKey = `-----BEGIN SECP384R1 MLKEM1024 PRIVATE KEY----- +0Tu+86kOEFj2BP8fRnWqaPq6d+E1Ufhl7/OJqmg1zLoYgPtEW4QBfLgxUM8Q0nUj +bh9BKtvBsaDd3GXyKpjIM0zXv8mzSf+bZQQ9zfaaSMJ3RsPoPhViUXLYnvbDrKfM +cql24SfEHKvj8gifFuV/eg== +-----END SECP384R1 MLKEM1024 PRIVATE KEY-----` ) type TestReadAt struct { @@ -349,6 +463,7 @@ func (s *TDFSuite) Test_SimpleTDF() { tdfOptions []TDFOption tdfReadOptions []TDFReaderOption useHex bool + expectedSize int64 // override default expectedTdfSize if non-zero } metaData := []byte(`{"displayName" : "openTDF go sdk"}`) @@ -428,6 +543,54 @@ func (s *TDFSuite) Test_SimpleTDF() { }, useHex: true, }, + { + name: "metadata-hybrid-p256-mlkem768", + tdfOptions: []TDFOption{ + WithKasInformation(KASInfo{ + URL: s.kasTestURLLookup["https://f.kas/"], + PublicKey: "", + }), + WithMetaData(string(metaData)), + WithDataAttributes(attributes...), + WithWrappingKeyAlg(ocrypto.HybridSecp256r1MLKEM768Key), + }, + tdfReadOptions: []TDFReaderOption{ + WithKasAllowlist([]string{s.kasTestURLLookup["https://f.kas/"]}), + }, + expectedSize: 3364, + }, + { + name: "metadata-hybrid-p384-mlkem1024", + tdfOptions: []TDFOption{ + WithKasInformation(KASInfo{ + URL: s.kasTestURLLookup["https://g.kas/"], + PublicKey: "", + }), + WithMetaData(string(metaData)), + WithDataAttributes(attributes...), + WithWrappingKeyAlg(ocrypto.HybridSecp384r1MLKEM1024Key), + }, + tdfReadOptions: []TDFReaderOption{ + WithKasAllowlist([]string{s.kasTestURLLookup["https://g.kas/"]}), + }, + expectedSize: 4048, + }, + { + name: "metadata-hybrid-xwing", + tdfOptions: []TDFOption{ + WithKasInformation(KASInfo{ + URL: s.kasTestURLLookup["https://h.kas/"], + PublicKey: "", + }), + WithMetaData(string(metaData)), + WithDataAttributes(attributes...), + WithWrappingKeyAlg(ocrypto.HybridXWingKey), + }, + tdfReadOptions: []TDFReaderOption{ + WithKasAllowlist([]string{s.kasTestURLLookup["https://h.kas/"]}), + }, + expectedSize: 3320, + }, } for _, config := range testConfigs { @@ -448,11 +611,14 @@ func (s *TDFSuite) Test_SimpleTDF() { tdfObj, err := s.sdk.CreateTDF(fileWriter, bufReader, config.tdfOptions...) s.Require().NoError(err) + expected := expectedTdfSize if config.useHex { - s.InDelta(float64(expectedTdfSizeWithHex), float64(tdfObj.size), 64.0) - } else { - s.InDelta(float64(expectedTdfSize), float64(tdfObj.size), 64.0) + expected = expectedTdfSizeWithHex + } + if config.expectedSize != 0 { + expected = config.expectedSize } + s.InDelta(float64(expected), float64(tdfObj.size), 64.0) // test meta data and build meta data readSeeker, err := os.Open(tdfFilename) @@ -2630,23 +2796,26 @@ func (s *TDFSuite) startBackend() { fa := &FakeAttributes{s: s} kasesToMake := []struct { - url, private, public, kid string + url, private, public, kid, algorithm string }{ - {"http://localhost:65432/", mockRSAPrivateKey1, mockRSAPublicKey1, defaultKID}, - {"http://[::1]:65432/", mockRSAPrivateKey1, mockRSAPublicKey1, defaultKID}, - {"https://a.kas/", mockRSAPrivateKey1, mockRSAPublicKey1, defaultKID}, - {"https://b.kas/", mockRSAPrivateKey2, mockRSAPublicKey2, defaultKID}, - {"https://c.kas/", mockRSAPrivateKey3, mockRSAPublicKey3, defaultKID}, - {"https://d.kas/", mockECPrivateKey1, mockECPublicKey1, "e1"}, - {"https://e.kas/", mockECPrivateKey2, mockECPublicKey2, defaultKID}, - {kasAu, mockRSAPrivateKey1, mockRSAPublicKey1, defaultKID}, - {kasCa, mockRSAPrivateKey2, mockRSAPublicKey2, defaultKID}, - {kasUk, mockRSAPrivateKey2, mockRSAPublicKey2, defaultKID}, - {kasNz, mockRSAPrivateKey3, mockRSAPublicKey3, defaultKID}, - {kasUs, mockRSAPrivateKey1, mockRSAPublicKey1, defaultKID}, - {baseKeyURL, mockRSAPrivateKey1, mockRSAPublicKey1, baseKeyKID}, - {evenMoreSpecificKas, mockRSAPrivateKey3, mockRSAPublicKey3, "r3"}, - {obligationKas, mockRSAPrivateKey3, mockRSAPublicKey3, "r3"}, + {"http://localhost:65432/", mockRSAPrivateKey1, mockRSAPublicKey1, defaultKID, "rsa:2048"}, + {"http://[::1]:65432/", mockRSAPrivateKey1, mockRSAPublicKey1, defaultKID, "rsa:2048"}, + {"https://a.kas/", mockRSAPrivateKey1, mockRSAPublicKey1, defaultKID, "rsa:2048"}, + {"https://b.kas/", mockRSAPrivateKey2, mockRSAPublicKey2, defaultKID, "rsa:2048"}, + {"https://c.kas/", mockRSAPrivateKey3, mockRSAPublicKey3, defaultKID, "rsa:2048"}, + {"https://d.kas/", mockECPrivateKey1, mockECPublicKey1, "e1", string(ocrypto.EC256Key)}, + {"https://e.kas/", mockECPrivateKey2, mockECPublicKey2, defaultKID, string(ocrypto.EC256Key)}, + {kasAu, mockRSAPrivateKey1, mockRSAPublicKey1, defaultKID, "rsa:2048"}, + {kasCa, mockRSAPrivateKey2, mockRSAPublicKey2, defaultKID, "rsa:2048"}, + {kasUk, mockRSAPrivateKey2, mockRSAPublicKey2, defaultKID, "rsa:2048"}, + {kasNz, mockRSAPrivateKey3, mockRSAPublicKey3, defaultKID, "rsa:2048"}, + {kasUs, mockRSAPrivateKey1, mockRSAPublicKey1, defaultKID, "rsa:2048"}, + {baseKeyURL, mockRSAPrivateKey1, mockRSAPublicKey1, baseKeyKID, "rsa:2048"}, + {evenMoreSpecificKas, mockRSAPrivateKey3, mockRSAPublicKey3, "r3", "rsa:2048"}, + {obligationKas, mockRSAPrivateKey3, mockRSAPublicKey3, "r3", "rsa:2048"}, + {"https://f.kas/", mockHybridP256MLKEM768PrivateKey, mockHybridP256MLKEM768PublicKey, "h1", string(ocrypto.HybridSecp256r1MLKEM768Key)}, + {"https://g.kas/", mockHybridP384MLKEM1024PrivateKey, mockHybridP384MLKEM1024PublicKey, "h2", string(ocrypto.HybridSecp384r1MLKEM1024Key)}, + {"https://h.kas/", mockHybridXWingPrivateKey, mockHybridXWingPublicKey, "h3", string(ocrypto.HybridXWingKey)}, } fkar := &FakeKASRegistry{kases: kasesToMake, s: s} @@ -2661,7 +2830,7 @@ func (s *TDFSuite) startBackend() { s.kases[i] = FakeKas{ s: s, privateKey: ki.private, KASInfo: KASInfo{ - URL: ki.url, PublicKey: ki.public, KID: ki.kid, Algorithm: "rsa:2048", + URL: ki.url, PublicKey: ki.public, KID: ki.kid, Algorithm: ki.algorithm, }, legakeys: map[string]keyInfo{}, attrToRequiredObligations: obligationMap, @@ -2750,7 +2919,7 @@ type FakeKASRegistry struct { kasregistryconnect.UnimplementedKeyAccessServerRegistryServiceHandler s *TDFSuite kases []struct { - url, private, public, kid string + url, private, public, kid, algorithm string } } @@ -2913,6 +3082,45 @@ func (f *FakeKas) getRewrapResponse(rewrapRequest string, fulfillableObligations entityWrappedKey, err = asymEncrypt.Encrypt(symmetricKey) f.s.Require().NoError(err, "ocrypto.AsymEncryption.encrypt failed") + case "hybrid-wrapped": + kasPrivateKey := strings.ReplaceAll(f.privateKey, "\n\t", "\n") + if kao.GetKid() != "" && kao.GetKid() != f.KID { + lk, ok := f.legakeys[kaoReq.GetKeyAccessObject().GetKid()] + f.s.Require().True(ok, "unable to find key [%s]", kao.GetKid()) + kasPrivateKey = strings.ReplaceAll(lk.private, "\n\t", "\n") + } + + var symmetricKey []byte + switch ocrypto.KeyType(f.Algorithm) { //nolint:exhaustive // only handle hybrid types + case ocrypto.HybridSecp256r1MLKEM768Key: + privateKey, err := ocrypto.P256MLKEM768PrivateKeyFromPem([]byte(kasPrivateKey)) + f.s.Require().NoError(err, "failed to extract P256+ML-KEM-768 private key from PEM") + symmetricKey, err = ocrypto.P256MLKEM768UnwrapDEK(privateKey, wrappedKey) + f.s.Require().NoError(err, "failed to unwrap P256+ML-KEM-768 wrapped key") + case ocrypto.HybridSecp384r1MLKEM1024Key: + privateKey, err := ocrypto.P384MLKEM1024PrivateKeyFromPem([]byte(kasPrivateKey)) + f.s.Require().NoError(err, "failed to extract P384+ML-KEM-1024 private key from PEM") + symmetricKey, err = ocrypto.P384MLKEM1024UnwrapDEK(privateKey, wrappedKey) + f.s.Require().NoError(err, "failed to unwrap P384+ML-KEM-1024 wrapped key") + case ocrypto.HybridXWingKey: + privateKey, err := ocrypto.XWingPrivateKeyFromPem([]byte(kasPrivateKey)) + f.s.Require().NoError(err, "failed to extract X-Wing private key from PEM") + symmetricKey, err = ocrypto.XWingUnwrapDEK(privateKey, wrappedKey) + f.s.Require().NoError(err, "failed to unwrap X-Wing wrapped key") + default: + f.s.Require().Failf("unsupported hybrid algorithm", "algorithm: %s", f.Algorithm) + } + + asymEncrypt, err := ocrypto.FromPublicPEMWithSalt(bodyData.GetClientPublicKey(), tdfSalt(), nil) + f.s.Require().NoError(err, "ocrypto.FromPublicPEMWithSalt failed") + if e, found := asymEncrypt.(ocrypto.ECEncryptor); found { + sessionKey, err := e.PublicKeyInPemFormat() + f.s.Require().NoError(err, "unable to serialize ephemeral key") + resp.SessionPublicKey = sessionKey + } + entityWrappedKey, err = asymEncrypt.Encrypt(symmetricKey) + f.s.Require().NoError(err, "ocrypto.encrypt failed") + case "wrapped": kasPrivateKey := strings.ReplaceAll(f.privateKey, "\n\t", "\n") if kao.GetKid() != "" && kao.GetKid() != f.KID { diff --git a/service/cmd/keygen/main.go b/service/cmd/keygen/main.go new file mode 100644 index 0000000000..721191e286 --- /dev/null +++ b/service/cmd/keygen/main.go @@ -0,0 +1,116 @@ +// Package main generates hybrid post-quantum KAS key pairs (X-Wing, P256+ML-KEM-768, P384+ML-KEM-1024) +// as PEM files for use with the OpenTDF platform. +package main + +import ( + "flag" + "log" + "os" + "path/filepath" + + "github.com/opentdf/platform/lib/ocrypto" +) + +type keySpec struct { + name string + newKeyPair func() (privatePEM, publicPEM string, err error) + privateOut string + publicOut string +} + +func main() { + outputDir := flag.String("output", ".", "directory to write PEM files") + flag.Parse() + + if err := os.MkdirAll(*outputDir, 0o755); err != nil { + log.Fatalf("failed to create output directory: %v", err) + } + + specs := []keySpec{ + { + name: "X-Wing", + newKeyPair: generateXWing, + privateOut: "kas-xwing-private.pem", + publicOut: "kas-xwing-public.pem", + }, + { + name: "P256+ML-KEM-768", + newKeyPair: generateP256MLKEM768, + privateOut: "kas-p256mlkem768-private.pem", + publicOut: "kas-p256mlkem768-public.pem", + }, + { + name: "P384+ML-KEM-1024", + newKeyPair: generateP384MLKEM1024, + privateOut: "kas-p384mlkem1024-private.pem", + publicOut: "kas-p384mlkem1024-public.pem", + }, + } + + for _, s := range specs { + privatePEM, publicPEM, err := s.newKeyPair() + if err != nil { + log.Fatalf("failed to generate %s key pair: %v", s.name, err) + } + + privPath := filepath.Join(*outputDir, s.privateOut) + pubPath := filepath.Join(*outputDir, s.publicOut) + + if err := os.WriteFile(privPath, []byte(privatePEM), 0o600); err != nil { + log.Fatalf("failed to write %s: %v", privPath, err) + } + if err := os.WriteFile(pubPath, []byte(publicPEM), 0o600); err != nil { + log.Fatalf("failed to write %s: %v", pubPath, err) + } + + log.Printf("Generated %s key pair:\n - Private: %s\n - Public: %s", s.name, privPath, pubPath) + } +} + +func generateXWing() (string, string, error) { + kp, err := ocrypto.NewXWingKeyPair() + if err != nil { + return "", "", err + } + priv, err := kp.PrivateKeyInPemFormat() + if err != nil { + return "", "", err + } + pub, err := kp.PublicKeyInPemFormat() + if err != nil { + return "", "", err + } + return priv, pub, nil +} + +func generateP256MLKEM768() (string, string, error) { + kp, err := ocrypto.NewP256MLKEM768KeyPair() + if err != nil { + return "", "", err + } + priv, err := kp.PrivateKeyInPemFormat() + if err != nil { + return "", "", err + } + pub, err := kp.PublicKeyInPemFormat() + if err != nil { + return "", "", err + } + return priv, pub, nil +} + +func generateP384MLKEM1024() (string, string, error) { + kp, err := ocrypto.NewP384MLKEM1024KeyPair() + if err != nil { + return "", "", err + } + priv, err := kp.PrivateKeyInPemFormat() + if err != nil { + return "", "", err + } + pub, err := kp.PublicKeyInPemFormat() + if err != nil { + return "", "", err + } + return priv, pub, nil +} diff --git a/service/internal/security/basic_manager.go b/service/internal/security/basic_manager.go index 6197e50eb3..139ae7ce31 100644 --- a/service/internal/security/basic_manager.go +++ b/service/internal/security/basic_manager.go @@ -97,6 +97,57 @@ func (b *BasicManager) Decrypt(ctx context.Context, keyDetails trust.KeyDetails, return nil, fmt.Errorf("failed to create protected key: %w", err) } return protectedKey, nil + case ocrypto.HybridXWingKey: + if len(ephemeralPublicKey) > 0 { + return nil, errors.New("ephemeral public key should not be provided for X-Wing decryption") + } + xwingPrivKey, err := ocrypto.XWingPrivateKeyFromPem(privKey) + if err != nil { + return nil, fmt.Errorf("failed to create X-Wing private key from PEM: %w", err) + } + plaintext, err := ocrypto.XWingUnwrapDEK(xwingPrivKey, ciphertext) + if err != nil { + return nil, fmt.Errorf("failed to decrypt with X-Wing: %w", err) + } + protectedKey, err := ocrypto.NewAESProtectedKey(plaintext) + if err != nil { + return nil, fmt.Errorf("failed to create protected key: %w", err) + } + return protectedKey, nil + case ocrypto.HybridSecp256r1MLKEM768Key: + if len(ephemeralPublicKey) > 0 { + return nil, errors.New("ephemeral public key should not be provided for hybrid decryption") + } + privKeyBytes, err := ocrypto.P256MLKEM768PrivateKeyFromPem(privKey) + if err != nil { + return nil, fmt.Errorf("failed to parse P256-MLKEM768 private key from PEM: %w", err) + } + plaintext, err := ocrypto.P256MLKEM768UnwrapDEK(privKeyBytes, ciphertext) + if err != nil { + return nil, fmt.Errorf("failed to decrypt with P256-MLKEM768: %w", err) + } + protectedKey, err := ocrypto.NewAESProtectedKey(plaintext) + if err != nil { + return nil, fmt.Errorf("failed to create protected key: %w", err) + } + return protectedKey, nil + case ocrypto.HybridSecp384r1MLKEM1024Key: + if len(ephemeralPublicKey) > 0 { + return nil, errors.New("ephemeral public key should not be provided for hybrid decryption") + } + privKeyBytes, err := ocrypto.P384MLKEM1024PrivateKeyFromPem(privKey) + if err != nil { + return nil, fmt.Errorf("failed to parse P384-MLKEM1024 private key from PEM: %w", err) + } + plaintext, err := ocrypto.P384MLKEM1024UnwrapDEK(privKeyBytes, ciphertext) + if err != nil { + return nil, fmt.Errorf("failed to decrypt with P384-MLKEM1024: %w", err) + } + protectedKey, err := ocrypto.NewAESProtectedKey(plaintext) + if err != nil { + return nil, fmt.Errorf("failed to create protected key: %w", err) + } + return protectedKey, nil } return nil, fmt.Errorf("unsupported algorithm: %s", keyDetails.Algorithm()) diff --git a/service/internal/security/crypto_provider.go b/service/internal/security/crypto_provider.go index 24f0f3c2ea..dcbe1ae5a1 100644 --- a/service/internal/security/crypto_provider.go +++ b/service/internal/security/crypto_provider.go @@ -11,4 +11,11 @@ const ( // Used for encryption with RSA of the KAO AlgorithmRSA2048 = "rsa:2048" AlgorithmRSA4096 = "rsa:4096" + + // Used for hybrid X-Wing wrapping of the KAO + AlgorithmHPQTXWing = "hpqt:xwing" + + // Used for hybrid NIST EC + ML-KEM wrapping of the KAO + AlgorithmHPQTSecp256r1MLKEM768 = "hpqt:secp256r1-mlkem768" + AlgorithmHPQTSecp384r1MLKEM1024 = "hpqt:secp384r1-mlkem1024" ) diff --git a/service/internal/security/in_process_provider.go b/service/internal/security/in_process_provider.go index 67c86487aa..0b9249f4aa 100644 --- a/service/internal/security/in_process_provider.go +++ b/service/internal/security/in_process_provider.go @@ -65,7 +65,7 @@ func (k *KeyDetailsAdapter) ExportPublicKey(_ context.Context, format trust.KeyT switch format { case trust.KeyTypeJWK: // For JWK format (currently only supported for RSA) - if k.algorithm == AlgorithmRSA2048 { + if k.algorithm == AlgorithmRSA2048 || k.algorithm == AlgorithmRSA4096 { return k.cryptoProvider.RSAPublicKeyAsJSON(kid) } // For EC keys, we return the public key in PEM format @@ -84,6 +84,12 @@ func (k *KeyDetailsAdapter) ExportPublicKey(_ context.Context, format trust.KeyT if rsaKey, err := k.cryptoProvider.RSAPublicKey(kid); err == nil { return rsaKey, nil } + if hybridKey, err := k.cryptoProvider.HybridPublicKey(kid); err == nil { + return hybridKey, nil + } + if xwingKey, err := k.cryptoProvider.XWingPublicKey(kid); err == nil { + return xwingKey, nil + } return k.cryptoProvider.ECPublicKey(kid) default: return "", ErrCertNotFound @@ -170,31 +176,17 @@ func (a *InProcessProvider) FindKeyByAlgorithm(_ context.Context, algorithm stri // FindKeyByID finds a key by ID func (a *InProcessProvider) FindKeyByID(_ context.Context, id trust.KeyIdentifier) (trust.KeyDetails, error) { - // Try to determine the algorithm by checking if the key works with known algorithms - for _, alg := range []string{AlgorithmECP256R1, AlgorithmRSA2048} { - // This is a hack since the original provider doesn't have a way to check if a key exists - switch alg { - case AlgorithmECP256R1: - if _, err := a.cryptoProvider.ECPublicKey(string(id)); err == nil { - return &KeyDetailsAdapter{ - id: id, - algorithm: ocrypto.KeyType(alg), - legacy: a.legacyKeys[string(id)], - cryptoProvider: a.cryptoProvider, - }, nil - } - case AlgorithmRSA2048: - if _, err := a.cryptoProvider.RSAPublicKey(string(id)); err == nil { - return &KeyDetailsAdapter{ - id: id, - algorithm: ocrypto.KeyType(alg), - legacy: a.legacyKeys[string(id)], - cryptoProvider: a.cryptoProvider, - }, nil - } - } + keyType, err := a.determineKeyType(string(id)) + if err != nil { + return nil, ErrCertNotFound } - return nil, ErrCertNotFound + + return &KeyDetailsAdapter{ + id: id, + algorithm: ocrypto.KeyType(keyType), + legacy: a.legacyKeys[string(id)], + cryptoProvider: a.cryptoProvider, + }, nil } // ListKeys lists all available keys @@ -207,7 +199,7 @@ func (a *InProcessProvider) ListKeysWith(ctx context.Context, opts trust.ListKey var keys []trust.KeyDetails // Try to find keys for known algorithms - for _, alg := range []string{AlgorithmRSA2048, AlgorithmECP256R1} { + for _, alg := range []string{AlgorithmRSA2048, AlgorithmRSA4096, AlgorithmECP256R1, AlgorithmHPQTXWing, AlgorithmHPQTSecp256r1MLKEM768, AlgorithmHPQTSecp384r1MLKEM1024} { if kids, err := a.cryptoProvider.ListKIDsByAlgorithm(alg); err == nil && len(kids) > 0 { for _, kid := range kids { if opts.LegacyOnly && !a.legacyKeys[kid] { @@ -242,7 +234,7 @@ func (a *InProcessProvider) Decrypt(ctx context.Context, keyDetails trust.KeyDet var err error // Try to determine the key type - keyType, err := a.determineKeyType(ctx, kid) + keyType, err := a.determineKeyType(kid) if err != nil { return nil, err } @@ -250,6 +242,8 @@ func (a *InProcessProvider) Decrypt(ctx context.Context, keyDetails trust.KeyDet var rawKey []byte switch keyType { case AlgorithmRSA2048: + fallthrough + case AlgorithmRSA4096: if len(ephemeralPublicKey) > 0 { return nil, errors.New("ephemeral public key should not be provided for RSA decryption") } @@ -261,6 +255,12 @@ func (a *InProcessProvider) Decrypt(ctx context.Context, keyDetails trust.KeyDet } protectedKey, err = a.cryptoProvider.ECDecrypt(ctx, kid, ephemeralPublicKey, ciphertext) + case AlgorithmHPQTXWing, AlgorithmHPQTSecp256r1MLKEM768, AlgorithmHPQTSecp384r1MLKEM1024: + if len(ephemeralPublicKey) > 0 { + return nil, errors.New("ephemeral public key should not be provided for hybrid decryption") + } + return a.cryptoProvider.Decrypt(ctx, trust.KeyIdentifier(kid), ciphertext, nil) + default: return nil, errors.New("unsupported key algorithm") } @@ -335,17 +335,22 @@ func (a *InProcessProvider) Close() { a.cryptoProvider.Close() } -// determineKeyType tries to determine the algorithm of a key based on its ID -// This is a helper method for the Decrypt method -func (a *InProcessProvider) determineKeyType(_ context.Context, kid string) (string, error) { - // First try RSA - if _, err := a.cryptoProvider.RSAPublicKey(kid); err == nil { - return AlgorithmRSA2048, nil +// determineKeyType returns the configured algorithm for a loaded key. +func (a *InProcessProvider) determineKeyType(kid string) (string, error) { + key, ok := a.cryptoProvider.keysByID[kid] + if !ok { + return "", errors.New("could not determine key type") } - // Then try EC - if _, err := a.cryptoProvider.ECPublicKey(kid); err == nil { - return AlgorithmECP256R1, nil + switch key := key.(type) { + case StandardRSACrypto: + return key.Algorithm, nil + case StandardECCrypto: + return key.Algorithm, nil + case StandardXWingCrypto: + return key.Algorithm, nil + case StandardHybridCrypto: + return key.Algorithm, nil } return "", errors.New("could not determine key type") diff --git a/service/internal/security/in_process_provider_test.go b/service/internal/security/in_process_provider_test.go index 41ce4c125e..9870ad53bb 100644 --- a/service/internal/security/in_process_provider_test.go +++ b/service/internal/security/in_process_provider_test.go @@ -218,14 +218,14 @@ func TestInProcessProviderDetermineKeyType(t *testing.T) { provider, ok := providerIface.(*InProcessProvider) require.True(t, ok) - keyType, err := provider.determineKeyType(t.Context(), material.rsaKid) + keyType, err := provider.determineKeyType(material.rsaKid) require.NoError(t, err) assert.Equal(t, AlgorithmRSA2048, keyType) - keyType, err = provider.determineKeyType(t.Context(), material.ecKid) + keyType, err = provider.determineKeyType(material.ecKid) require.NoError(t, err) assert.Equal(t, AlgorithmECP256R1, keyType) - _, err = provider.determineKeyType(t.Context(), "missing") + _, err = provider.determineKeyType("missing") require.Error(t, err) } diff --git a/service/internal/security/standard_crypto.go b/service/internal/security/standard_crypto.go index 6b86abdcbe..59953a8523 100644 --- a/service/internal/security/standard_crypto.go +++ b/service/internal/security/standard_crypto.go @@ -67,6 +67,18 @@ type StandardECCrypto struct { sk *ecdh.PrivateKey } +type StandardXWingCrypto struct { + KeyPairInfo + xwingPrivateKeyPem string + xwingPublicKeyPem string +} + +type StandardHybridCrypto struct { + KeyPairInfo + hybridPrivateKeyPem string + hybridPublicKeyPem string +} + // List of keys by identifier type keylist map[string]any @@ -150,6 +162,18 @@ func loadKey(k KeyPairInfo) (any, error) { ecPrivateKeyPem: string(privatePEM), ecCertificatePEM: string(certPEM), }, nil + case AlgorithmHPQTXWing: + return StandardXWingCrypto{ + KeyPairInfo: k, + xwingPrivateKeyPem: string(privatePEM), + xwingPublicKeyPem: string(certPEM), + }, nil + case AlgorithmHPQTSecp256r1MLKEM768, AlgorithmHPQTSecp384r1MLKEM1024: + return StandardHybridCrypto{ + KeyPairInfo: k, + hybridPrivateKeyPem: string(privatePEM), + hybridPublicKeyPem: string(certPEM), + }, nil case AlgorithmRSA2048, AlgorithmRSA4096: asymDecryption, err := ocrypto.NewAsymDecryption(string(privatePEM)) if err != nil { @@ -329,6 +353,42 @@ func (s StandardCrypto) ECPublicKey(kid string) (string, error) { return string(pemBytes), nil } +func (s StandardCrypto) XWingPublicKey(kid string) (string, error) { + k, ok := s.keysByID[kid] + if !ok { + return "", fmt.Errorf("no xwing key with id [%s]: %w", kid, ErrCertNotFound) + } + xw, ok := k.(StandardXWingCrypto) + if !ok { + return "", fmt.Errorf("key with id [%s] is not an X-Wing key: %w", kid, ErrCertNotFound) + } + if xw.xwingPublicKeyPem == "" { + return "", fmt.Errorf("no X-Wing public key with id [%s]: %w", kid, ErrCertNotFound) + } + return xw.xwingPublicKeyPem, nil +} + +func (s StandardCrypto) HybridPublicKey(kid string) (string, error) { + k, ok := s.keysByID[kid] + if !ok { + return "", fmt.Errorf("no hybrid key with id [%s]: %w", kid, ErrCertNotFound) + } + switch h := k.(type) { + case StandardXWingCrypto: + if h.xwingPublicKeyPem == "" { + return "", fmt.Errorf("no hybrid public key with id [%s]: %w", kid, ErrCertNotFound) + } + return h.xwingPublicKeyPem, nil + case StandardHybridCrypto: + if h.hybridPublicKeyPem == "" { + return "", fmt.Errorf("no hybrid public key with id [%s]: %w", kid, ErrCertNotFound) + } + return h.hybridPublicKeyPem, nil + default: + return "", fmt.Errorf("key with id [%s] is not a hybrid key: %w", kid, ErrCertNotFound) + } +} + func (s StandardCrypto) RSADecrypt(_ crypto.Hash, kid string, _ string, ciphertext []byte) ([]byte, error) { k, ok := s.keysByID[kid] if !ok { @@ -439,6 +499,49 @@ func (s *StandardCrypto) Decrypt(_ context.Context, keyID trust.KeyIdentifier, c return nil, fmt.Errorf("error decrypting data: %w", err) } + case StandardXWingCrypto: + if len(ephemeralPublicKey) > 0 { + return nil, errors.New("ephemeral public key should not be provided for X-Wing decryption") + } + + privateKey, err := ocrypto.XWingPrivateKeyFromPem([]byte(key.xwingPrivateKeyPem)) + if err != nil { + return nil, fmt.Errorf("failed to parse X-Wing private key: %w", err) + } + + rawKey, err = ocrypto.XWingUnwrapDEK(privateKey, ciphertext) + if err != nil { + return nil, fmt.Errorf("failed to decrypt with X-Wing: %w", err) + } + + case StandardHybridCrypto: + if len(ephemeralPublicKey) > 0 { + return nil, errors.New("ephemeral public key should not be provided for hybrid decryption") + } + + switch key.Algorithm { + case AlgorithmHPQTSecp256r1MLKEM768: + privateKey, err := ocrypto.P256MLKEM768PrivateKeyFromPem([]byte(key.hybridPrivateKeyPem)) + if err != nil { + return nil, fmt.Errorf("failed to parse P256-MLKEM768 private key: %w", err) + } + rawKey, err = ocrypto.P256MLKEM768UnwrapDEK(privateKey, ciphertext) + if err != nil { + return nil, fmt.Errorf("failed to decrypt with P256-MLKEM768: %w", err) + } + case AlgorithmHPQTSecp384r1MLKEM1024: + privateKey, err := ocrypto.P384MLKEM1024PrivateKeyFromPem([]byte(key.hybridPrivateKeyPem)) + if err != nil { + return nil, fmt.Errorf("failed to parse P384-MLKEM1024 private key: %w", err) + } + rawKey, err = ocrypto.P384MLKEM1024UnwrapDEK(privateKey, ciphertext) + if err != nil { + return nil, fmt.Errorf("failed to decrypt with P384-MLKEM1024: %w", err) + } + default: + return nil, fmt.Errorf("unsupported hybrid algorithm [%s]", key.Algorithm) + } + default: return nil, fmt.Errorf("unsupported key type for key ID [%s]", kid) } diff --git a/service/kas/access/provider.go b/service/kas/access/provider.go index e9c01dbe0c..89f90b5e85 100644 --- a/service/kas/access/provider.go +++ b/service/kas/access/provider.go @@ -52,13 +52,15 @@ type KASConfig struct { // Enabling is required to parse KAOs with the `ec-wrapped` type, // and (currently) also enables responding with ECIES encrypted responses. ECTDFEnabled bool `mapstructure:"ec_tdf_enabled" json:"ec_tdf_enabled"` + HybridTDFEnabled bool `mapstructure:"hybrid_tdf_enabled" json:"hybrid_tdf_enabled"` Preview Preview `mapstructure:"preview" json:"preview"` RegisteredKASURI string `mapstructure:"registered_kas_uri" json:"registered_kas_uri"` } type Preview struct { - ECTDFEnabled bool `mapstructure:"ec_tdf_enabled" json:"ec_tdf_enabled"` - KeyManagement bool `mapstructure:"key_management" json:"key_management"` + ECTDFEnabled bool `mapstructure:"ec_tdf_enabled" json:"ec_tdf_enabled"` + HybridTDFEnabled bool `mapstructure:"hybrid_tdf_enabled" json:"hybrid_tdf_enabled"` + KeyManagement bool `mapstructure:"key_management" json:"key_management"` } // Specifies the preferred/default key for a given algorithm type. @@ -143,13 +145,14 @@ func (kasCfg KASConfig) String() string { } return fmt.Sprintf( - "KASConfig{Keyring:%v, ECCertID:%q, RSACertID:%q, RootKey:%s, KeyCacheExpiration:%s, ECTDFEnabled:%t, Preview:%+v, RegisteredKASURI:%q}", + "KASConfig{Keyring:%v, ECCertID:%q, RSACertID:%q, RootKey:%s, KeyCacheExpiration:%s, ECTDFEnabled:%t, HybridTDFEnabled:%t, Preview:%+v, RegisteredKASURI:%q}", kasCfg.Keyring, kasCfg.ECCertID, kasCfg.RSACertID, rootKeySummary, kasCfg.KeyCacheExpiration, kasCfg.ECTDFEnabled, + kasCfg.HybridTDFEnabled, kasCfg.Preview, kasCfg.RegisteredKASURI, ) @@ -168,6 +171,7 @@ func (kasCfg KASConfig) LogValue() slog.Value { slog.String("root_key", rootKeyVal), slog.Duration("key_cache_expiration", kasCfg.KeyCacheExpiration), slog.Bool("ec_tdf_enabled", kasCfg.ECTDFEnabled), + slog.Bool("hybrid_tdf_enabled", kasCfg.HybridTDFEnabled), slog.Any("preview", kasCfg.Preview), slog.String("registered_kas_uri", kasCfg.RegisteredKASURI), ) diff --git a/service/kas/access/publicKey.go b/service/kas/access/publicKey.go index eb5b1fe780..d7f381c307 100644 --- a/service/kas/access/publicKey.go +++ b/service/kas/access/publicKey.go @@ -76,9 +76,8 @@ func (p *Provider) LegacyPublicKey(ctx context.Context, req *connect.Request[kas p.Logger.ErrorContext(ctx, "keyDetails.ExportCertificate failed", slog.Any("error", err)) return nil, connect.NewError(connect.CodeInternal, errors.Join(ErrConfig, errors.New("configuration error"))) } - case security.AlgorithmRSA2048: - fallthrough - case "": + case security.AlgorithmRSA2048, security.AlgorithmHPQTXWing, + security.AlgorithmHPQTSecp256r1MLKEM768, security.AlgorithmHPQTSecp384r1MLKEM1024, "": // For RSA keys, return the public key in PKCS8 format pem, err = keyDetails.ExportPublicKey(ctx, trust.KeyTypePKCS8) if err != nil { @@ -153,6 +152,14 @@ func (p *Provider) PublicKey(ctx context.Context, req *connect.Request[kaspb.Pub // For EC keys, export the public key ecPublicKeyPem, err := keyDetails.ExportPublicKey(ctx, trust.KeyTypePKCS8) return r(ecPublicKeyPem, kid, err) + case security.AlgorithmHPQTXWing, + security.AlgorithmHPQTSecp256r1MLKEM768, + security.AlgorithmHPQTSecp384r1MLKEM1024: + switch fmt { + case "pkcs8", "": + publicKeyPEM, err := keyDetails.ExportPublicKey(ctx, trust.KeyTypePKCS8) + return r(publicKeyPEM, kid, err) + } case security.AlgorithmRSA2048: fallthrough case "": diff --git a/service/kas/access/rewrap.go b/service/kas/access/rewrap.go index d62975a480..aba7ffa84e 100644 --- a/service/kas/access/rewrap.go +++ b/service/kas/access/rewrap.go @@ -746,6 +746,20 @@ func (p *Provider) verifyRewrapRequests(ctx context.Context, req *kaspb.Unsigned failedKAORewrap(results, kao, err400("bad request")) continue } + case "hybrid-wrapped": + if !p.HybridTDFEnabled && !p.Preview.HybridTDFEnabled { + p.Logger.WarnContext(ctx, "hybrid-wrapped not enabled") + failedKAORewrap(results, kao, err400("bad request")) + continue + } + + kid := trust.KeyIdentifier(kao.GetKeyAccessObject().GetKid()) + dek, err = p.KeyDelegator.Decrypt(ctx, kid, kao.GetKeyAccessObject().GetWrappedKey(), nil) + if err != nil { + p.Logger.WarnContext(ctx, "failed to decrypt hybrid key", slog.Any("error", err)) + failedKAORewrap(results, kao, err400("bad request")) + continue + } case "wrapped": var kidsToCheck []trust.KeyIdentifier if kao.GetKeyAccessObject().GetKid() != "" { diff --git a/service/kas/kas.go b/service/kas/kas.go index bf17719c9e..d7694aa12b 100644 --- a/service/kas/kas.go +++ b/service/kas/kas.go @@ -191,7 +191,7 @@ func initSecurityProviderAdapter(cryptoProvider *security.StandardCrypto, kasCfg } } if len(defaults) == 0 && len(legacies) == 0 { - for _, alg := range []string{security.AlgorithmECP256R1, security.AlgorithmRSA2048} { + for _, alg := range []string{security.AlgorithmECP256R1, security.AlgorithmRSA2048, security.AlgorithmHPQTXWing} { kid := cryptoProvider.FindKID(alg) if kid != "" { defaults = append(defaults, kid) diff --git a/service/kas/kas.proto b/service/kas/kas.proto index a01cd1b888..6168a6a5f8 100644 --- a/service/kas/kas.proto +++ b/service/kas/kas.proto @@ -55,7 +55,7 @@ message KeyAccess { // Type of key wrapping used for the data encryption key // Required: Always - // Values: 'wrapped' (RSA-wrapped for ZTDF), 'ec-wrapped' (experimental ECDH-wrapped) + // Values: 'wrapped' (RSA-wrapped for ZTDF), 'ec-wrapped' (experimental ECDH-wrapped), 'hybrid-wrapped' (experimental X-Wing-wrapped) string key_type = 4 [json_name = "type"]; // URL of the Key Access Server that can unwrap this key @@ -88,7 +88,7 @@ message KeyAccess { // Ephemeral public key for ECDH key derivation (ec-wrapped type only) // Required: When key_type="ec-wrapped" (experimental ECDH-based ZTDF) - // Omitted: When key_type="wrapped" (RSA-based ZTDF) + // Omitted: When key_type="wrapped" or key_type="hybrid-wrapped" // Should be a PEM-encoded PKCS#8 (ASN.1) formatted public key // Used to derive the symmetric key for unwrapping the DEK string ephemeral_public_key = 10; @@ -245,7 +245,7 @@ message RewrapResponse { // KAS's ephemeral session public key in PEM format // Required: For EC-based operations (key_type="ec-wrapped") - // Optional: Empty for RSA-based ZTDF (key_type="wrapped") + // Optional: Empty for RSA-based or X-Wing-based ZTDF (key_type="wrapped" or key_type="hybrid-wrapped") // Used by client to perform ECDH key agreement and decrypt the kas_wrapped_key values string session_public_key = 3; diff --git a/service/kas/key_indexer.go b/service/kas/key_indexer.go index 021b3f418a..9c336e2bae 100644 --- a/service/kas/key_indexer.go +++ b/service/kas/key_indexer.go @@ -46,42 +46,6 @@ func NewPlatformKeyIndexer(sdk *sdk.SDK, kasURI string, l *logger.Logger) *KeyIn } } -func convertEnumToAlg(alg policy.Algorithm) ocrypto.KeyType { - switch alg { - case policy.Algorithm_ALGORITHM_RSA_2048: - return ocrypto.RSA2048Key - case policy.Algorithm_ALGORITHM_RSA_4096: - return ocrypto.RSA4096Key - case policy.Algorithm_ALGORITHM_EC_P256: - return ocrypto.EC256Key - case policy.Algorithm_ALGORITHM_EC_P384: - return ocrypto.EC384Key - case policy.Algorithm_ALGORITHM_EC_P521: - return ocrypto.EC521Key - case policy.Algorithm_ALGORITHM_UNSPECIFIED: - fallthrough - default: - return "" - } -} - -func convertAlgToEnum(alg string) (policy.Algorithm, error) { - switch alg { - case string(ocrypto.RSA2048Key): - return policy.Algorithm_ALGORITHM_RSA_2048, nil - case string(ocrypto.RSA4096Key): - return policy.Algorithm_ALGORITHM_RSA_4096, nil - case string(ocrypto.EC256Key): - return policy.Algorithm_ALGORITHM_EC_P256, nil - case string(ocrypto.EC384Key): - return policy.Algorithm_ALGORITHM_EC_P384, nil - case string(ocrypto.EC521Key): - return policy.Algorithm_ALGORITHM_EC_P521, nil - default: - return policy.Algorithm_ALGORITHM_UNSPECIFIED, fmt.Errorf("unsupported algorithm: %s", alg) - } -} - func (p *KeyIndexer) String() string { return fmt.Sprintf("PlatformKeyIndexer[%s]", p.kasURI) } @@ -91,7 +55,11 @@ func (p *KeyIndexer) LogValue() slog.Value { } func (p *KeyIndexer) FindKeyByAlgorithm(ctx context.Context, algorithm string, includeLegacy bool) (trust.KeyDetails, error) { - alg, err := convertAlgToEnum(algorithm) + kt, err := ocrypto.ParseKeyType(algorithm) + if err != nil { + return nil, err + } + alg, err := sdk.KeyTypeToPolicyAlgorithm(kt) if err != nil { return nil, err } @@ -192,7 +160,13 @@ func (p *KeyAdapter) ID() trust.KeyIdentifier { // Might need to convert this to a standard format func (p *KeyAdapter) Algorithm() ocrypto.KeyType { - return convertEnumToAlg(p.key.GetKey().GetKeyAlgorithm()) + kt, err := sdk.PolicyAlgorithmToKeyType(p.key.GetKey().GetKeyAlgorithm()) + if err != nil { + p.log.Error("unable to format key with alg", + slog.String("kid", p.key.GetKey().GetKeyId()), + slog.Any("err", err)) + } + return kt } func (p *KeyAdapter) IsLegacy() bool { diff --git a/service/pkg/db/marshalHelpers.go b/service/pkg/db/marshalHelpers.go index 667ab27e21..31ce86bc0b 100644 --- a/service/pkg/db/marshalHelpers.go +++ b/service/pkg/db/marshalHelpers.go @@ -8,6 +8,7 @@ import ( "github.com/opentdf/platform/protocol/go/common" "github.com/opentdf/platform/protocol/go/policy" "github.com/opentdf/platform/protocol/go/policy/kasregistry" + "github.com/opentdf/platform/sdk" "google.golang.org/protobuf/encoding/protojson" ) @@ -146,22 +147,11 @@ func KasKeysProtoJSON(keysJSON []byte) ([]*policy.KasKey, error) { } func FormatAlg(alg policy.Algorithm) (string, error) { - switch alg { - case policy.Algorithm_ALGORITHM_RSA_2048: - return "rsa:2048", nil - case policy.Algorithm_ALGORITHM_RSA_4096: - return "rsa:4096", nil - case policy.Algorithm_ALGORITHM_EC_P256: - return "ec:secp256r1", nil - case policy.Algorithm_ALGORITHM_EC_P384: - return "ec:secp384r1", nil - case policy.Algorithm_ALGORITHM_EC_P521: - return "ec:secp521r1", nil - case policy.Algorithm_ALGORITHM_UNSPECIFIED: - fallthrough - default: + kt, err := sdk.PolicyAlgorithmToKeyType(alg) + if err != nil { return "", fmt.Errorf("unsupported algorithm: %s", alg) } + return string(kt), nil } func SimpleKasKeysProtoJSON(keysJSON []byte) ([]*policy.SimpleKasKey, error) { diff --git a/service/pkg/db/marshalHelpers_test.go b/service/pkg/db/marshalHelpers_test.go index a516a3b129..16a4234e8d 100644 --- a/service/pkg/db/marshalHelpers_test.go +++ b/service/pkg/db/marshalHelpers_test.go @@ -12,11 +12,14 @@ import ( // reverseAlgMap mirrors the SDK's getKasKeyAlg mapping: ocrypto.KeyType string → policy.Algorithm. // If FormatAlg produces a string that isn't in this map, the SDK would return ALGORITHM_UNSPECIFIED. var reverseAlgMap = map[string]policy.Algorithm{ - string(ocrypto.RSA2048Key): policy.Algorithm_ALGORITHM_RSA_2048, - string(ocrypto.RSA4096Key): policy.Algorithm_ALGORITHM_RSA_4096, - string(ocrypto.EC256Key): policy.Algorithm_ALGORITHM_EC_P256, - string(ocrypto.EC384Key): policy.Algorithm_ALGORITHM_EC_P384, - string(ocrypto.EC521Key): policy.Algorithm_ALGORITHM_EC_P521, + string(ocrypto.RSA2048Key): policy.Algorithm_ALGORITHM_RSA_2048, + string(ocrypto.RSA4096Key): policy.Algorithm_ALGORITHM_RSA_4096, + string(ocrypto.EC256Key): policy.Algorithm_ALGORITHM_EC_P256, + string(ocrypto.EC384Key): policy.Algorithm_ALGORITHM_EC_P384, + string(ocrypto.EC521Key): policy.Algorithm_ALGORITHM_EC_P521, + string(ocrypto.HybridXWingKey): policy.Algorithm_ALGORITHM_HPQT_XWING, + string(ocrypto.HybridSecp256r1MLKEM768Key): policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768, + string(ocrypto.HybridSecp384r1MLKEM1024Key): policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024, } func TestFormatAlg_RoundTrip(t *testing.T) { @@ -32,6 +35,9 @@ func TestFormatAlg_RoundTrip(t *testing.T) { {"EC-P256", policy.Algorithm_ALGORITHM_EC_P256}, {"EC-P384", policy.Algorithm_ALGORITHM_EC_P384}, {"EC-P521", policy.Algorithm_ALGORITHM_EC_P521}, + {"HPQT-XWing", policy.Algorithm_ALGORITHM_HPQT_XWING}, + {"HPQT-P256-MLKEM768", policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768}, + {"HPQT-P384-MLKEM1024", policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024}, } for _, tc := range supportedAlgs { diff --git a/service/pkg/server/start_test.go b/service/pkg/server/start_test.go index f13c7f2b7c..afa019a4e2 100644 --- a/service/pkg/server/start_test.go +++ b/service/pkg/server/start_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + "github.com/opentdf/platform/lib/ocrypto" "github.com/opentdf/platform/service/internal/auth" "github.com/opentdf/platform/service/internal/server" "github.com/opentdf/platform/service/logger" @@ -223,7 +224,9 @@ func TestStartTestSuite(t *testing.T) { } func (s *StartTestSuite) SetupSuite() { - // Create dummy KAS key files in testdata + // Generate fresh KAS key material for every run so nothing is committed + // alongside the source tree. all-no-config.yaml expects all of these paths + // to resolve, including the hybrid PQ key pairs added in PR #3276. keyFiles := map[string]string{ "kas-private.pem": dummyRsaPrivate, "kas-cert.pem": dummyRsaPublic, // Using public key as cert for dummy purposes @@ -231,6 +234,23 @@ func (s *StartTestSuite) SetupSuite() { "kas-ec-cert.pem": dummyEcCert, } + hybridPairs := []struct { + name string + newPair func() (priv, pub string, err error) + priv string + pub string + }{ + {"X-Wing", testXWingPair, "kas-xwing-private.pem", "kas-xwing-public.pem"}, + {"P-256+ML-KEM-768", testP256MLKEM768Pair, "kas-p256mlkem768-private.pem", "kas-p256mlkem768-public.pem"}, + {"P-384+ML-KEM-1024", testP384MLKEM1024Pair, "kas-p384mlkem1024-private.pem", "kas-p384mlkem1024-public.pem"}, + } + for _, p := range hybridPairs { + priv, pub, err := p.newPair() + s.Require().NoErrorf(err, "Failed to generate %s key pair", p.name) + keyFiles[p.priv] = priv + keyFiles[p.pub] = pub + } + for filename, content := range keyFiles { filePath := filepath.Join("testdata", filename) err := os.WriteFile(filePath, []byte(content), 0o600) @@ -245,7 +265,7 @@ func (s *StartTestSuite) TearDownSuite() { s.Require().NoError(err, "Failed to read testdata directory") for _, entry := range entries { - if !entry.IsDir() { + if entry.IsDir() { continue } if entry.Name() == ignoreFile { @@ -256,6 +276,47 @@ func (s *StartTestSuite) TearDownSuite() { } } +func testXWingPair() (string, string, error) { + kp, err := ocrypto.NewXWingKeyPair() + if err != nil { + return "", "", err + } + return testHybridPEMs(kp) +} + +func testP256MLKEM768Pair() (string, string, error) { + kp, err := ocrypto.NewP256MLKEM768KeyPair() + if err != nil { + return "", "", err + } + return testHybridPEMs(kp) +} + +func testP384MLKEM1024Pair() (string, string, error) { + kp, err := ocrypto.NewP384MLKEM1024KeyPair() + if err != nil { + return "", "", err + } + return testHybridPEMs(kp) +} + +type pemKeyPair interface { + PrivateKeyInPemFormat() (string, error) + PublicKeyInPemFormat() (string, error) +} + +func testHybridPEMs(kp pemKeyPair) (string, string, error) { + priv, err := kp.PrivateKeyInPemFormat() + if err != nil { + return "", "", err + } + pub, err := kp.PublicKeyInPemFormat() + if err != nil { + return "", "", err + } + return priv, pub, nil +} + func (s *StartTestSuite) Test_Start_When_Extra_Service_Registered() { testCases := []struct { name string diff --git a/service/pkg/server/testdata/all-no-config.yaml b/service/pkg/server/testdata/all-no-config.yaml index d6db2ae334..29dcd5fe8c 100644 --- a/service/pkg/server/testdata/all-no-config.yaml +++ b/service/pkg/server/testdata/all-no-config.yaml @@ -16,6 +16,12 @@ services: - kid: r1 alg: rsa:2048 legacy: true + - kid: x1 + alg: hpqt:xwing + - kid: h1 + alg: hpqt:secp256r1-mlkem768 + - kid: h2 + alg: hpqt:secp384r1-mlkem1024 entityresolution: log_level: info url: http://localhost:8888/auth @@ -117,3 +123,15 @@ server: alg: ec:secp256r1 private: ./testdata/kas-ec-private.pem cert: ./testdata/kas-ec-cert.pem + - kid: x1 + alg: hpqt:xwing + private: ./testdata/kas-xwing-private.pem + cert: ./testdata/kas-xwing-public.pem + - kid: h1 + alg: hpqt:secp256r1-mlkem768 + private: ./testdata/kas-p256mlkem768-private.pem + cert: ./testdata/kas-p256mlkem768-public.pem + - kid: h2 + alg: hpqt:secp384r1-mlkem1024 + private: ./testdata/kas-p384mlkem1024-private.pem + cert: ./testdata/kas-p384mlkem1024-public.pem diff --git a/service/policy/db/grant_mappings.go b/service/policy/db/grant_mappings.go index 7c3688af58..7cd8830f92 100644 --- a/service/policy/db/grant_mappings.go +++ b/service/policy/db/grant_mappings.go @@ -22,6 +22,12 @@ func mapAlgorithmToKasPublicKeyAlg(alg policy.Algorithm) policy.KasPublicKeyAlgE return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 case policy.Algorithm_ALGORITHM_EC_P521: // ALGORITHM_EC_P521 is an alias return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 + case policy.Algorithm_ALGORITHM_HPQT_XWING: + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING + case policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768: + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 + case policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024: + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 case policy.Algorithm_ALGORITHM_UNSPECIFIED: return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED default: diff --git a/service/policy/kasregistry/key_access_server_registry.proto b/service/policy/kasregistry/key_access_server_registry.proto index 1c902f95ec..5d0e805f7e 100644 --- a/service/policy/kasregistry/key_access_server_registry.proto +++ b/service/policy/kasregistry/key_access_server_registry.proto @@ -433,7 +433,7 @@ message CreateKeyRequest { Algorithm key_algorithm = 3 [(buf.validate.field).cel = { id: "key_algorithm_defined" message: "The key_algorithm must be one of the defined values." - expression: "this in [1, 2, 3, 4, 5]" // Allow ALGORITHM_RSA_2048, ALGORITHM_RSA_4096, ALGORITHM_EC_P256, ALGORITHM_EC_P384, ALGORITHM_EC_P521 + expression: "this in [1, 2, 3, 4, 5, 6, 7, 8]" // Allow ALGORITHM_RSA_2048, ALGORITHM_RSA_4096, ALGORITHM_EC_P256, ALGORITHM_EC_P384, ALGORITHM_EC_P521, ALGORITHM_HPQT_XWING, ALGORITHM_HPQT_SECP256R1_MLKEM768, ALGORITHM_HPQT_SECP384R1_MLKEM1024 }]; // The algorithm to be used for the key // Required KeyMode key_mode = 4 [(buf.validate.field).cel = { @@ -477,7 +477,7 @@ message ListKeysRequest { Algorithm key_algorithm = 1 [(buf.validate.field).cel = { id: "key_algorithm_defined" message: "The key_algorithm must be one of the defined values." - expression: "this in [0, 1, 2, 3, 4, 5]" // Allow unspecified and object.Algorithm values for currently supported RSA bit sizes and EC curve types + expression: "this in [0, 1, 2, 3, 4, 5, 6, 7, 8]" // Allow unspecified and all supported algorithm values }]; // Filter keys by algorithm oneof kas_filter { @@ -587,7 +587,7 @@ message RotateKeyRequest { Algorithm algorithm = 2 [(buf.validate.field).cel = { id: "key_algorithm_defined" message: "The key_algorithm must be one of the defined values." - expression: "this in [1, 2, 3, 4, 5]" // Allow ALGORITHM_RSA_2048, ALGORITHM_RSA_4096, ALGORITHM_EC_P256, ALGORITHM_EC_P384, ALGORITHM_EC_P521 + expression: "this in [1, 2, 3, 4, 5, 6, 7, 8]" // Allow ALGORITHM_RSA_2048, ALGORITHM_RSA_4096, ALGORITHM_EC_P256, ALGORITHM_EC_P384, ALGORITHM_EC_P521, ALGORITHM_HPQT_XWING, ALGORITHM_HPQT_SECP256R1_MLKEM768, ALGORITHM_HPQT_SECP384R1_MLKEM1024 }]; // Required KeyMode key_mode = 3 [ diff --git a/service/policy/objects.proto b/service/policy/objects.proto index 5b6e47023e..3e6ee4d794 100644 --- a/service/policy/objects.proto +++ b/service/policy/objects.proto @@ -391,6 +391,9 @@ enum KasPublicKeyAlgEnum { KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP256R1 = 5; KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP384R1 = 6; KAS_PUBLIC_KEY_ALG_ENUM_EC_SECP521R1 = 7; + KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING = 10; + KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 = 11; + KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 = 12; } // Deprecated @@ -561,6 +564,9 @@ enum Algorithm { ALGORITHM_EC_P256 = 3; ALGORITHM_EC_P384 = 4; ALGORITHM_EC_P521 = 5; + ALGORITHM_HPQT_XWING = 6; + ALGORITHM_HPQT_SECP256R1_MLKEM768 = 7; + ALGORITHM_HPQT_SECP384R1_MLKEM1024 = 8; } // The status of the key diff --git a/test/start-up-with-containers/action.yaml b/test/start-up-with-containers/action.yaml index acc8f85e86..4abe183502 100644 --- a/test/start-up-with-containers/action.yaml +++ b/test/start-up-with-containers/action.yaml @@ -115,8 +115,8 @@ runs: set -e allowed_algorithms=(ec:secp256r1 rsa:2048) if echo $PLATFORM_VERSION | awk -F. '{ if ($1 > 0 || ($1 == 0 && $2 > 7) || ($1 == 0 && $2 == 7 && $3 >= 1)) exit 0; else exit 1; }'; then - # For versions 0.7.1 and later, we allow rsa:4096 ec:secp384r1 ec:secp521r1 - allowed_algorithms+=(rsa:4096 ec:secp384r1 ec:secp521r1) + # For versions 0.7.1 and later, we allow rsa:4096 ec:secp384r1 ec:secp521r1 + allowed_algorithms+=(rsa:4096 ec:secp384r1 ec:secp521r1 hpqt:xwing hpqt:secp256r1-mlkem768 hpqt:secp384r1-mlkem1024) fi keyring='[{"kid":"ec1","alg":"ec:secp256r1"},{"kid":"r1","alg":"rsa:2048"}]' keys='[{"kid":"e1","alg":"ec:secp256r1","private":"kas-ec-private.pem","cert":"kas-ec-cert.pem"},{"kid":"ec1","alg":"ec:secp256r1","private":"kas-ec-private.pem","cert":"kas-ec-cert.pem"},{"kid":"r1","alg":"rsa:2048","private":"kas-private.pem","cert":"kas-cert.pem"}]' diff --git a/test/tdf-roundtrips.bats b/test/tdf-roundtrips.bats index cf5d0d5c01..7e5ff3aedb 100755 --- a/test/tdf-roundtrips.bats +++ b/test/tdf-roundtrips.bats @@ -26,6 +26,69 @@ printf '%s\n' "$output" | grep "Hello EC wrappers!" } +@test "examples: roundtrip Z-TDF with X-Wing wrapped KAO" { + echo "[INFO] create a tdf3 format file" + run go run ./examples encrypt -o sensitive-with-xwing.txt.tdf --autoconfigure=false -A "hpqt:xwing" "Hello X-Wing wrappers!" + echo "[INFO] echoing output; if successful, this is just the manifest" + echo "$output" + + echo "[INFO] Validate the manifest lists the expected type in its KAO" + kaotype=$(jq -r '.encryptionInformation.keyAccess[0].type' <<<"${output}") + echo "kao.type=$kaotype" + [ "$kaotype" = hybrid-wrapped ] + + kid=$(jq -r '.encryptionInformation.keyAccess[0].kid' <<<"${output}") + echo "kao.kid=$kid" + [ "$kid" = x1 ] + + echo "[INFO] decrypting..." + run go run ./examples decrypt sensitive-with-xwing.txt.tdf + echo "$output" + printf '%s\n' "$output" | grep "Hello X-Wing wrappers!" +} + +@test "examples: roundtrip Z-TDF with P256+ML-KEM-768 wrapped KAO" { + echo "[INFO] create a tdf3 format file" + run go run ./examples encrypt -o sensitive-with-p256mlkem768.txt.tdf --autoconfigure=false -A "hpqt:secp256r1-mlkem768" "Hello P256+ML-KEM-768 wrappers!" + echo "[INFO] echoing output; if successful, this is just the manifest" + echo "$output" + + echo "[INFO] Validate the manifest lists the expected type in its KAO" + kaotype=$(jq -r '.encryptionInformation.keyAccess[0].type' <<<"${output}") + echo "$kaotype" + [ "$kaotype" = hybrid-wrapped ] + + kid=$(jq -r '.encryptionInformation.keyAccess[0].kid' <<<"${output}") + echo "kao.kid=$kid" + [ "$kid" = h1 ] + + echo "[INFO] decrypting..." + run go run ./examples decrypt sensitive-with-p256mlkem768.txt.tdf + echo "$output" + printf '%s\n' "$output" | grep "Hello P256+ML-KEM-768 wrappers!" +} + +@test "examples: roundtrip Z-TDF with P384+ML-KEM-1024 wrapped KAO" { + echo "[INFO] create a tdf3 format file" + run go run ./examples encrypt -o sensitive-with-p384mlkem1024.txt.tdf --autoconfigure=false -A "hpqt:secp384r1-mlkem1024" "Hello P384+ML-KEM-1024 wrappers!" + echo "[INFO] echoing output; if successful, this is just the manifest" + echo "$output" + + echo "[INFO] Validate the manifest lists the expected type in its KAO" + kaotype=$(jq -r '.encryptionInformation.keyAccess[0].type' <<<"${output}") + echo "$kaotype" + [ "$kaotype" = hybrid-wrapped ] + + kid=$(jq -r '.encryptionInformation.keyAccess[0].kid' <<<"${output}") + echo "kao.kid=$kid" + [ "$kid" = h2 ] + + echo "[INFO] decrypting..." + run go run ./examples decrypt sensitive-with-p384mlkem1024.txt.tdf + echo "$output" + printf '%s\n' "$output" | grep "Hello P384+ML-KEM-1024 wrappers!" +} + @test "examples: legacy key support Z-TDF" { echo "[INFO] validating default key is r1" echo "[INFO] default key result: $(grpcurl "localhost:8080" "kas.AccessService/PublicKey")" @@ -77,7 +140,7 @@ echo "[INFO] validating default key is r1" echo "[INFO] default key result: $(grpcurl "localhost:8080" "kas.AccessService/PublicKey")" - [ $(grpcurl "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid) = r1 ] + [ "$(grpcurl "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid)" = r1 ] echo "[INFO] validating keys are correct by alg" [ "$(grpcurl -d '{"algorithm":"ec:secp256r1"}' "localhost:8080" "kas.AccessService/PublicKey" | jq -e -r .kid)" = e1 ] @@ -98,10 +161,21 @@ wait_for_green() { done } +write_opentdf_config() { + local tmp + + tmp=$(mktemp "./opentdf.yaml.tmp.XXXXXX") + if ! cat >"$tmp"; then + rm -f "$tmp" + return 1 + fi + mv -f "$tmp" opentdf.yaml +} + downgrade_config() { ec_current_key=$1 rsa_current_key=$2 - cat >opentdf.yaml <opentdf.yaml <