diff --git a/adr/decisions/2026-06-16-mlkem-direct-key-wrap.md b/adr/decisions/2026-06-16-mlkem-direct-key-wrap.md new file mode 100644 index 0000000000..02887394df --- /dev/null +++ b/adr/decisions/2026-06-16-mlkem-direct-key-wrap.md @@ -0,0 +1,112 @@ +--- +status: accepted +date: 2026-06-16 +tags: + - cryptography + - mlkem + - kas + - hsm + - fips +--- +# ML-KEM-wrapped KAOs use the Decaps shared secret directly as the AES-GCM wrap key (no HKDF) + +## Context and Problem Statement + +PR [opentdf/platform#3537](https://github.com/opentdf/platform/pull/3537) introduces a pure ML-KEM-768 / ML-KEM-1024 wrapping scheme for KAOs (key-access objects) — wire type `mlkem-wrapped`. The first draft of that PR derived the AES-256-GCM wrap key from the ML-KEM Decaps output via HKDF-SHA256 over a fixed `"TDF"` salt, mirroring the existing hybrid PQ/T (`hybrid-wrapped`) path. + +The intended downstream consumer is an HSM-backed KAS provider: specifically Thales Luna T-Series with firmware 7.15.1 in strict-FIPS mode. On that HSM, `CKM_ML_KEM_KEY_DECAP` can only materialize its 32-byte shared secret as a sensitive, non-extractable AES key object (`CKK_AES`). The HSM refuses to emit the Decaps result as `CKK_GENERIC_SECRET`, returning `CKR_ATTRIBUTE_TYPE_INVALID`, which means we cannot: + +* run `CKM_SHA256_HMAC` over the shared secret (so no HKDF-on-HSM), nor +* extract the shared secret to run HKDF off-HSM (`CKA_EXTRACTABLE=false`). + +Any KDF in the unwrap chain therefore blocks HSM-backed KAS providers on this firmware. + +## Decision Drivers + +* Must support HSM-backed KAS providers (Thales Luna T-Series 7.15.1 in strict-FIPS mode) without an HSM firmware change, vendor RFE, or unsafe key extraction. +* Must remain FIPS-compliant. +* Must not change the on-wire envelope format (the wire format is the same ASN.1 DER `MLKEMWrappedKey { MLKEMCiphertext, EncryptedDEK }` — only the internal key-derivation step is removed). +* Must not regress security relative to the HKDF-using draft. +* Must not bleed into the hybrid PQ/T (`hybrid-wrapped`) wrap path, where HKDF is load-bearing as the combiner for the two shared-secret halves. + +## Considered Options + +1. Use the ML-KEM Decaps output directly as the AES-256-GCM wrap key for `mlkem-wrapped`. +2. Keep HKDF-SHA256 over `(sharedSecret, salt, info)` and require vendor firmware support for `CKM_GENERIC_SECRET_KEY_GEN` from Decaps. +3. Keep HKDF and require KAS operators to mark the ML-KEM private key as software-only (no HSM) when used with this wire format. + +## Decision Outcome + +Chosen option: **(1) Use the ML-KEM Decaps shared secret directly as the AES-256-GCM wrap key.** + +The 32-byte Decaps output is fed straight into AES-256-GCM with a fresh random nonce; the AES-GCM ciphertext + tag are stored as `EncryptedDEK` inside the existing ASN.1 envelope. The `salt` / `info` parameters that flow into the unified `kemEncryptor` / `kemDecryptor` are ignored by the ML-KEM adapter (they remain meaningful for the X-Wing and NIST EC + ML-KEM hybrid adapters, which still derive their AES key via HKDF as the combiner). + +### Wire format + +Unchanged. The envelope is still: + +```asn1 +MLKEMWrappedKey ::= SEQUENCE { + mlkemCiphertext [0] IMPLICIT OCTET STRING, + encryptedDEK [1] IMPLICIT OCTET STRING +} +``` + +`encryptedDEK` is now `AES-256-GCM(K = mlkemSharedSecret, nonce = random12B, AAD = none, plaintext = DEK)` with the standard 12-byte nonce prefix + 16-byte tag layout produced by `ocrypto.AesGcm.Encrypt`. No HKDF; no `salt`; no `info`. + +### FIPS 203 justification + +FIPS 203 (Module-Lattice-Based Key-Encapsulation Mechanism Standard) specifies the Decaps output `K` as a uniformly random 32-byte shared secret produced by hashing through the spec's internal G/H/J SHA-3 family functions: + +* §7.3 *ML-KEM.Decaps*: "Output: shared secret key K ∈ B^{32}". +* §6.3 *ML-KEM.Decaps* (the variant exposing the implicit-rejection branch) likewise emits a 32-byte K, including in the failure path where K is derived pseudorandomly from `(z, c)` using J — preserving indistinguishability from a real success. + +Because K is already a 32-byte uniformly random string by construction, an additional HKDF expansion would not increase its entropy or change its distribution — at best, HKDF would re-mix uniformly-random input bits into a different uniformly-random 32-byte output. It is not load-bearing. + +ML-KEM also produces a fresh K per encapsulation by construction (encapsulation samples fresh randomness `m` and packs it through K-PKE encrypt, so every wrap operation produces an independent K). The per-call key-isolation property HKDF is conventionally used to provide is therefore already present in the input. + +### Cryptographic argument + +The properties we need for a DEK-wrap key are: + +1. **Uniform 32-byte distribution.** ML-KEM `Decaps` outputs a 32-byte K drawn from the SHA-3 family applied to fresh per-encapsulation randomness; FIPS 203 specifies this directly. +2. **Per-wrap independence.** Encapsulation samples a fresh 32-byte `m` per call, so K is independent across wraps by construction; no domain separation tag is required to keep wraps from colliding. +3. **Authenticated wrap.** AES-256-GCM provides confidentiality and integrity for the wrapped DEK; a wrong-key unwrap fails at the GCM tag-check stage. FIPS 203 §6.3's implicit-rejection design means a wrong-key Decaps still returns a 32-byte K, but that K is pseudorandom and uncorrelated with the encryptor's K, so the AES-GCM tag verification fails. + +Skipping HKDF therefore neither lowers the wrap key's entropy nor weakens the unwrap-failure behaviour observed by the caller. The only thing HKDF would have added is a fixed-string domain-separation tag (`info`); since the `mlkem-wrapped` wire type is itself a domain-separation tag, there is no cross-protocol collision risk to defend against. + +### Code shape + +* `lib/ocrypto/kem.go`: the `kem` interface gains a `wrapKey(sharedSecret, salt, info []byte) ([]byte, error)` method. `mlkemKEM.wrapKey` returns the shared secret verbatim; `xwingKEM.wrapKey` and `nistHybridKEM.wrapKey` both delegate to the existing `hkdfWrapKey` (renamed from `deriveKEMWrapKey`). The `wrapDEKWithKEM` / `unwrapDEKWithKEM` helpers ask the adapter for the key. +* `lib/ocrypto/mlkem.go`: the `MLKEM{768,1024}{Wrap,Unwrap}DEK` entry points pass `nil, nil` for salt/info so the ignore-semantics are obvious at the call site. +* `lib/ocrypto/hybrid_common.go`: `defaultTDFSalt()` is retained — it is still the default HKDF salt for the X-Wing and NIST hybrid adapters and for ECIES (`FromPublicPEMWithSalt`). + +### Consequences + +* **Good**, because HSM-backed KAS providers (Thales Luna T-Series 7.15.1 in strict-FIPS mode) can now perform `mlkem-wrapped` unwrap end-to-end without ever extracting the Decaps shared secret. The 32-byte K stays on-HSM as a `CKK_AES`, sensitive, non-extractable object and is used directly by `CKM_AES_GCM`. +* **Good**, because the wire format does not change: the ASN.1 envelope is byte-identical, and only the internal key derivation is removed. +* **Good**, because the unified `kem` interface keeps the wrap/unwrap path single-source; the per-scheme key-derivation policy is the only thing that diverges, and it is captured in one method on the adapter. +* **Neutral**, because the `salt` and `info` parameters threaded through the unified encryptor / decryptor constructors still exist (they are needed for X-Wing and NIST hybrid). They are silently ignored for ML-KEM. The `TestMLKEMSaltInfoIgnored` test pins this behaviour so it cannot regress. +* **Bad**, because any wire-format artifact produced by the HKDF-using draft of PR #3537 is no longer decryptable. This is acceptable: PR #3537 is not merged and the HKDF-using artifacts existed only in the PR branch and its test fixtures. + +### Migration + +* PR #3537 is not merged. Any `mlkem-wrapped` envelopes that were produced by intermediate versions of that branch are no longer decryptable after this change. +* The hybrid PQ/T (`hybrid-wrapped`) wrap path is **unchanged**. Both X-Wing and NIST EC + ML-KEM continue to use HKDF-SHA256 over the combined `(EC || ML-KEM)` shared secret, because the KDF is the combiner and is load-bearing for those schemes. + +### Out of scope + +* Maintaining an HKDF-using variant of `mlkem-wrapped` for non-HSM consumers. There is no consumer that requires HKDF — software KAS implementations can use the Decaps output directly with no measurable difference in behaviour or security, and the KDF only adds compute cost on the unwrap path. A second wire variant would split the ecosystem with no upside. +* Generalising direct-shared-secret wrapping to the hybrid PQ/T schemes. For X-Wing and NIST EC + ML-KEM the AES wrap key must be derived from `(ecdhSecret || mlkemSecret)` via a KDF, because (a) the combined input is 64+ bytes (not 32), and (b) HKDF is the combiner that turns the two halves into a single uniformly-random key. Removing HKDF there would reduce security, not just compute. + +## Validation + +* `TestMLKEMSharedSecretIsAESWrapKey` (lib/ocrypto/mlkem_test.go) extracts the AES-GCM ciphertext from an `mlkem-wrapped` envelope and opens it using `AES-256-GCM(K = sharedSecret)` directly, asserting the recovered plaintext matches the original DEK. This pins the no-KDF contract from both directions (encrypt-side and decrypt-side). +* `TestMLKEMSaltInfoIgnored` (lib/ocrypto/mlkem_test.go) wraps with one `(salt, info)` pair and unwraps with a different pair (and again with `nil, nil`); both must succeed, proving salt/info are no-ops for ML-KEM. +* The existing `TestMLKEM{768,1024}WrapUnwrapRoundTrip`, `TestMLKEM{768,1024}WrapUnwrapWrongKeyFails`, and `TestMLKEM{768,1024}WrapDEKFormats` tests continue to pass. + +## More Information + +* FIPS 203, *Module-Lattice-Based Key-Encapsulation Mechanism Standard*, August 2024: https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.203.pdf +* OpenTDF platform PR #3537 (ML-KEM-768 / ML-KEM-1024 post-quantum encryption support): https://github.com/opentdf/platform/pull/3537 +* Related: `lib/ocrypto/HYBRID_NIST_KEY_WRAPPING.md` (hybrid PQ/T variant, which retains HKDF as the combiner). diff --git a/docs/grpc/index.html b/docs/grpc/index.html index e22a412c00..27ca2623c6 100644 --- a/docs/grpc/index.html +++ b/docs/grpc/index.html @@ -4379,6 +4379,18 @@

Algorithm

+ + ALGORITHM_MLKEM_768 + 20 +

+ + + + ALGORITHM_MLKEM_1024 + 21 +

+ + @@ -4584,6 +4596,18 @@

KasPublicKeyAlgEnum

+ + KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + 20 +

+ + + + KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 + 21 +

+ + diff --git a/docs/openapi/authorization/authorization.openapi.yaml b/docs/openapi/authorization/authorization.openapi.yaml index 5a112b61b6..cb0927c76c 100644 --- a/docs/openapi/authorization/authorization.openapi.yaml +++ b/docs/openapi/authorization/authorization.openapi.yaml @@ -143,6 +143,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.KasPublicKeyAlgEnum: type: string @@ -157,6 +159,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 policy.SourceType: type: string title: SourceType diff --git a/docs/openapi/authorization/v2/authorization.openapi.yaml b/docs/openapi/authorization/v2/authorization.openapi.yaml index 0e03a9e182..e6914f49ba 100644 --- a/docs/openapi/authorization/v2/authorization.openapi.yaml +++ b/docs/openapi/authorization/v2/authorization.openapi.yaml @@ -178,6 +178,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.KasPublicKeyAlgEnum: type: string @@ -192,6 +194,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 policy.SourceType: type: string title: SourceType diff --git a/docs/openapi/policy/actions/actions.openapi.yaml b/docs/openapi/policy/actions/actions.openapi.yaml index 3f16ddef73..6528f1ea00 100644 --- a/docs/openapi/policy/actions/actions.openapi.yaml +++ b/docs/openapi/policy/actions/actions.openapi.yaml @@ -206,6 +206,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -257,6 +259,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 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 b5270ccdb5..53c117c1cc 100644 --- a/docs/openapi/policy/attributes/attributes.openapi.yaml +++ b/docs/openapi/policy/attributes/attributes.openapi.yaml @@ -725,6 +725,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -776,6 +778,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 policy.SortDirection: type: string title: SortDirection diff --git a/docs/openapi/policy/dynamicvaluemapping/dynamic_value_mapping.openapi.yaml b/docs/openapi/policy/dynamicvaluemapping/dynamic_value_mapping.openapi.yaml index c05551ba69..0ef548fff7 100644 --- a/docs/openapi/policy/dynamicvaluemapping/dynamic_value_mapping.openapi.yaml +++ b/docs/openapi/policy/dynamicvaluemapping/dynamic_value_mapping.openapi.yaml @@ -206,6 +206,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -257,6 +259,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 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 2d803524bb..b8c7b84cad 100644 --- a/docs/openapi/policy/kasregistry/key_access_server_registry.openapi.yaml +++ b/docs/openapi/policy/kasregistry/key_access_server_registry.openapi.yaml @@ -526,6 +526,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.KasPublicKeyAlgEnum: type: string @@ -540,6 +542,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 policy.KeyMode: type: string title: KeyMode @@ -1181,7 +1185,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, 6, 7, 8] + this in [1, 2, 3, 4, 5, 6, 7, 8, 20, 21] ``` $ref: '#/components/schemas/policy.Algorithm' @@ -1742,7 +1746,7 @@ components: Filter keys by algorithm The key_algorithm must be one of the defined values.: ``` - this in [0, 1, 2, 3, 4, 5, 6, 7, 8] + this in [0, 1, 2, 3, 4, 5, 6, 7, 8, 20, 21] ``` $ref: '#/components/schemas/policy.Algorithm' @@ -2020,7 +2024,7 @@ components: Required The key_algorithm must be one of the defined values.: ``` - this in [1, 2, 3, 4, 5, 6, 7, 8] + this in [1, 2, 3, 4, 5, 6, 7, 8, 20, 21] ``` $ref: '#/components/schemas/policy.Algorithm' diff --git a/docs/openapi/policy/namespaces/namespaces.openapi.yaml b/docs/openapi/policy/namespaces/namespaces.openapi.yaml index d6f72e3e69..c813ecd93c 100644 --- a/docs/openapi/policy/namespaces/namespaces.openapi.yaml +++ b/docs/openapi/policy/namespaces/namespaces.openapi.yaml @@ -356,6 +356,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.KasPublicKeyAlgEnum: type: string @@ -370,6 +372,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 policy.SortDirection: type: string title: SortDirection diff --git a/docs/openapi/policy/objects.openapi.yaml b/docs/openapi/policy/objects.openapi.yaml index b888894b3c..c2e9a36bab 100644 --- a/docs/openapi/policy/objects.openapi.yaml +++ b/docs/openapi/policy/objects.openapi.yaml @@ -24,6 +24,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -75,6 +77,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 policy.KeyMode: type: string title: KeyMode diff --git a/docs/openapi/policy/obligations/obligations.openapi.yaml b/docs/openapi/policy/obligations/obligations.openapi.yaml index 01fcb57b6a..4b5c79f0b5 100644 --- a/docs/openapi/policy/obligations/obligations.openapi.yaml +++ b/docs/openapi/policy/obligations/obligations.openapi.yaml @@ -556,6 +556,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -607,6 +609,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 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 cf6f526825..1d08486fa0 100644 --- a/docs/openapi/policy/registeredresources/registered_resources.openapi.yaml +++ b/docs/openapi/policy/registeredresources/registered_resources.openapi.yaml @@ -416,6 +416,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -467,6 +469,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 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 c17e546137..5810360c25 100644 --- a/docs/openapi/policy/resourcemapping/resource_mapping.openapi.yaml +++ b/docs/openapi/policy/resourcemapping/resource_mapping.openapi.yaml @@ -416,6 +416,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -467,6 +469,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 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 1113568374..0639d2fdbc 100644 --- a/docs/openapi/policy/subjectmapping/subject_mapping.openapi.yaml +++ b/docs/openapi/policy/subjectmapping/subject_mapping.openapi.yaml @@ -452,6 +452,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -503,6 +505,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 policy.SortDirection: type: string title: SortDirection diff --git a/docs/openapi/policy/unsafe/unsafe.openapi.yaml b/docs/openapi/policy/unsafe/unsafe.openapi.yaml index eff9dbd4d7..771e2f691d 100644 --- a/docs/openapi/policy/unsafe/unsafe.openapi.yaml +++ b/docs/openapi/policy/unsafe/unsafe.openapi.yaml @@ -390,6 +390,8 @@ components: - ALGORITHM_HPQT_XWING - ALGORITHM_HPQT_SECP256R1_MLKEM768 - ALGORITHM_HPQT_SECP384R1_MLKEM1024 + - ALGORITHM_MLKEM_768 + - ALGORITHM_MLKEM_1024 description: Supported key algorithms. policy.AttributeRuleTypeEnum: type: string @@ -441,6 +443,8 @@ components: - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_XWING - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP256R1_MLKEM768 - KAS_PUBLIC_KEY_ALG_ENUM_HPQT_SECP384R1_MLKEM1024 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + - KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 policy.KeyMode: type: string title: KeyMode diff --git a/lib/ocrypto/asym_decryption.go b/lib/ocrypto/asym_decryption.go index 28c31798a2..300f8f9c03 100644 --- a/lib/ocrypto/asym_decryption.go +++ b/lib/ocrypto/asym_decryption.go @@ -43,11 +43,22 @@ func FromPrivatePEMWithSalt(privateKeyInPem string, salt, info []byte) (PrivateK if block == nil { return AsymDecryption{}, errors.New("failed to parse PEM formatted private key") } - - // Hybrid PQ/T private keys are PKCS#8-wrapped under one of our known OIDs. - // Peek at the AlgorithmIdentifier and route hybrids to their constructors; - // everything else (RSA, EC, EC PRIVATE KEY) falls through to x509. + // Pure ML-KEM private keys are PKCS#8-wrapped under the NIST OIDs handled by + // the unified kem path. Try these first so an ML-KEM key is never misrouted + // into the hybrid OID dispatcher. if block.Type == pemBlockPrivateKey { + switch oid, seed, err := parseKEMPrivatePKCS8(block.Bytes); { + case err == nil: + if k, ok := kemByOID(oid); ok { + return newKEMDecryptor(k, seed, salt, info) + } + case !errors.Is(err, errNotKEM): + return AsymDecryption{}, err + } + + // Hybrid PQ/T private keys are PKCS#8-wrapped under our composite-KEM + // OIDs. Route hybrids to their per-scheme constructors; everything else + // (RSA, EC, EC PRIVATE KEY) falls through to x509. if dec, matched, err := hybridDecryptorFromPKCS8(block.Bytes, salt, info); matched { return dec, err } @@ -95,19 +106,6 @@ func FromPrivatePEMWithSalt(privateKeyInPem string, salt, info []byte) (PrivateK return nil, errors.New("not a supported PEM formatted private key") } -func NewAsymDecryption(privateKeyInPem string) (AsymDecryption, error) { - d, err := FromPrivatePEMWithSalt(privateKeyInPem, nil, nil) - if err != nil { - return AsymDecryption{}, err - } - switch d := d.(type) { - case AsymDecryption: - return d, nil - default: - return AsymDecryption{}, errors.New("not an RSA private key") - } -} - // Decrypt decrypts ciphertext with private key. func (asymDecryption AsymDecryption) Decrypt(data []byte) ([]byte, error) { if asymDecryption.PrivateKey == nil { @@ -221,15 +219,8 @@ func hybridDecryptorFromPKCS8(der, salt, info []byte) (PrivateKeyDecryptor, bool // KEY). Fall through to the legacy decoder. return nil, false, nil //nolint:nilerr // intentional fall-through on non-envelope input } - switch { - case oid.Equal(oidXWing): - dec, err := NewSaltedXWingDecryptor(raw, salt, info) - return dec, true, err - case oid.Equal(oidCompositeMLKEM768P256): - dec, err := NewP256MLKEM768Decryptor(raw) - return dec, true, err - case oid.Equal(oidCompositeMLKEM1024P384): - dec, err := NewP384MLKEM1024Decryptor(raw) + if k, ok := hybridKEMByOID(oid); ok { + dec, err := newKEMDecryptor(k, raw, salt, info) return dec, true, err } // Valid PKCS#8 envelope with a non-hybrid OID. If the stdlib recognises it, diff --git a/lib/ocrypto/asym_encryption.go b/lib/ocrypto/asym_encryption.go index 860e799cbf..5d302cd756 100644 --- a/lib/ocrypto/asym_encryption.go +++ b/lib/ocrypto/asym_encryption.go @@ -26,6 +26,7 @@ const ( RSA SchemeType = "wrapped" EC SchemeType = "ec-wrapped" Hybrid SchemeType = "hybrid-wrapped" + MLKEM SchemeType = "mlkem-wrapped" ) type PublicKeyEncryptor interface { @@ -74,11 +75,23 @@ func FromPublicPEMWithSalt(publicKeyInPem string, salt, info []byte) (PublicKeyE if block == nil { return nil, errors.New("failed to parse PEM formatted public key") } - - // Hybrid PQ/T public keys are SPKI-wrapped under one of our known OIDs. - // Peek at the AlgorithmIdentifier and route hybrids to their constructors; - // everything else (RSA, EC, CERTIFICATE) falls through to the x509 path. + // Pure ML-KEM public keys are SPKI-wrapped under the NIST OIDs handled by + // the unified kem path. Try these first so an ML-KEM key is never misrouted + // into the hybrid OID dispatcher (which would treat an unknown OID as an + // error rather than falling through). if block.Type == pemBlockPublicKey { + switch oid, key, err := ParseKEMPublicSPKI(block.Bytes); { + case err == nil: + if k, ok := kemByOID(oid); ok { + return newKEMEncryptor(k, key, salt, info) + } + case !errors.Is(err, errNotKEM): + return nil, err + } + + // Hybrid PQ/T public keys are SPKI-wrapped under our composite-KEM OIDs. + // Peek at the AlgorithmIdentifier and route hybrids to their per-scheme + // constructors; everything else (RSA, EC) falls through to the x509 path. if enc, matched, err := hybridEncryptorFromSPKI(block.Bytes, salt, info); matched { return enc, err } @@ -117,25 +130,6 @@ func newECIES(pub *ecdh.PublicKey, salt, info []byte) (ECEncryptor, error) { return ECEncryptor{pub, ek, salt, info}, err } -// NewAsymEncryption creates and returns a new AsymEncryption. -// -// Deprecated: Use FromPublicPEM instead. -func NewAsymEncryption(publicKeyInPem string) (AsymEncryption, error) { - pub, err := getPublicPart(publicKeyInPem) - if err != nil { - return AsymEncryption{}, err - } - - switch pub := pub.(type) { - case *rsa.PublicKey: - return AsymEncryption{pub}, nil - default: - break - } - - return AsymEncryption{}, fmt.Errorf("unsupported public key type: %T", pub) -} - func getPublicPart(publicKeyInPem string) (any, error) { block, _ := pem.Decode([]byte(publicKeyInPem)) if block == nil { @@ -303,15 +297,8 @@ func hybridEncryptorFromSPKI(der, salt, info []byte) (PublicKeyEncryptor, bool, // which handles PKCS#1 keys, certificates, and stdlib-recognised SPKI. return nil, false, nil //nolint:nilerr // intentional fall-through on non-envelope input } - switch { - case oid.Equal(oidXWing): - enc, err := NewXWingEncryptor(raw, salt, info) - return enc, true, err - case oid.Equal(oidCompositeMLKEM768P256): - enc, err := NewP256MLKEM768Encryptor(raw) - return enc, true, err - case oid.Equal(oidCompositeMLKEM1024P384): - enc, err := NewP384MLKEM1024Encryptor(raw) + if k, ok := hybridKEMByOID(oid); ok { + enc, err := newKEMEncryptor(k, raw, salt, info) return enc, true, err } // Valid SPKI envelope with a non-hybrid OID. If the stdlib recognises it, diff --git a/lib/ocrypto/benchmark_test.go b/lib/ocrypto/benchmark_test.go index b325672176..199f1939e2 100644 --- a/lib/ocrypto/benchmark_test.go +++ b/lib/ocrypto/benchmark_test.go @@ -234,7 +234,7 @@ func BenchmarkUnwrapDEK(b *testing.B) { if err != nil { b.Fatal(err) } - rsaEnc, err := NewAsymEncryption(rsaPubPEM) + rsaEnc, err := FromPublicPEM(rsaPubPEM) if err != nil { b.Fatal(err) } @@ -242,7 +242,7 @@ func BenchmarkUnwrapDEK(b *testing.B) { if err != nil { b.Fatal(err) } - rsaDec, err := NewAsymDecryption(rsaPrivPEM) + rsaDec, err := FromPrivatePEM(rsaPrivPEM) if err != nil { b.Fatal(err) } @@ -364,7 +364,7 @@ func BenchmarkUnwrapDEK(b *testing.B) { if err != nil { b.Fatal(err) } - xwingWrapped, err := XWingWrapDEK(xwingKP.publicKey, testDEK) + xwingWrapped, err := wrapDEKWithKEM(xwingKEM{}, xwingKP.publicKey, testDEK, salt, nil) if err != nil { b.Fatal(err) } @@ -378,7 +378,7 @@ func BenchmarkUnwrapDEK(b *testing.B) { if err != nil { b.Fatal(err) } - p256Wrapped, err := P256MLKEM768WrapDEK(p256KP.publicKey, testDEK) + p256Wrapped, err := wrapDEKWithKEM(hybridNISTKEM{params: &p256mlkem768Params}, p256KP.publicKey, testDEK, nil, nil) if err != nil { b.Fatal(err) } @@ -392,7 +392,7 @@ func BenchmarkUnwrapDEK(b *testing.B) { if err != nil { b.Fatal(err) } - p384Wrapped, err := P384MLKEM1024WrapDEK(p384KP.publicKey, testDEK) + p384Wrapped, err := wrapDEKWithKEM(hybridNISTKEM{params: &p384mlkem1024Params}, p384KP.publicKey, testDEK, nil, nil) if err != nil { b.Fatal(err) } @@ -473,7 +473,7 @@ func TestWrappedKeySizeComparison(t *testing.T) { if err != nil { t.Fatal(err) } - rsaEnc, err := NewAsymEncryption(rsaPubPEM) + rsaEnc, err := FromPublicPEM(rsaPubPEM) if err != nil { t.Fatal(err) } @@ -547,7 +547,7 @@ func TestWrappedKeySizeComparison(t *testing.T) { if err != nil { t.Fatal(err) } - xwingWrapped, err := XWingWrapDEK(xwingKP.publicKey, testDEK) + xwingWrapped, err := wrapDEKWithKEM(xwingKEM{}, xwingKP.publicKey, testDEK, nil, nil) if err != nil { t.Fatal(err) } @@ -567,7 +567,7 @@ func TestWrappedKeySizeComparison(t *testing.T) { if err != nil { t.Fatal(err) } - p256Wrapped, err := P256MLKEM768WrapDEK(p256KP.publicKey, testDEK) + p256Wrapped, err := wrapDEKWithKEM(hybridNISTKEM{params: &p256mlkem768Params}, p256KP.publicKey, testDEK, nil, nil) if err != nil { t.Fatal(err) } @@ -587,7 +587,7 @@ func TestWrappedKeySizeComparison(t *testing.T) { if err != nil { t.Fatal(err) } - p384Wrapped, err := P384MLKEM1024WrapDEK(p384KP.publicKey, testDEK) + p384Wrapped, err := wrapDEKWithKEM(hybridNISTKEM{params: &p384mlkem1024Params}, p384KP.publicKey, testDEK, nil, nil) if err != nil { t.Fatal(err) } diff --git a/lib/ocrypto/ec_key_pair.go b/lib/ocrypto/ec_key_pair.go index c5e288373b..84056d02b8 100644 --- a/lib/ocrypto/ec_key_pair.go +++ b/lib/ocrypto/ec_key_pair.go @@ -4,6 +4,7 @@ import ( "crypto/ecdh" "crypto/ecdsa" "crypto/elliptic" + "crypto/mlkem" "crypto/rand" "crypto/sha256" "crypto/x509" @@ -17,46 +18,6 @@ import ( "golang.org/x/crypto/hkdf" ) -type ECCMode uint8 - -type KeyType string - -const ( - RSA2048Key KeyType = "rsa:2048" - RSA4096Key KeyType = "rsa:4096" - EC256Key KeyType = "ec:secp256r1" - EC384Key KeyType = "ec:secp384r1" - 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 - ECCModeSecp521r1 ECCMode = 2 - ECCModeSecp256k1 ECCMode = 3 -) - -const ( - ECCurveP256Size = 256 - ECCurveP384Size = 384 - ECCurveP521Size = 521 - RSA2048Size = 2048 - RSA4096Size = 4096 -) - type KeyPair interface { PublicKeyInPemFormat() (string, error) PrivateKeyInPemFormat() (string, error) @@ -79,6 +40,10 @@ func NewKeyPair(kt KeyType) (KeyPair, error) { return NewECKeyPair(mode) case HybridSecp256r1MLKEM768Key, HybridSecp384r1MLKEM1024Key, HybridXWingKey: return NewHybridKeyPair(kt) + case MLKEM768Key: + return NewMLKEMKeyPair() + case MLKEM1024Key: + return NewMLKEM1024KeyPair() default: return nil, fmt.Errorf("unsupported key type: %v", kt) } @@ -88,95 +53,12 @@ type ECKeyPair struct { PrivateKey *ecdsa.PrivateKey } -func IsECKeyType(kt KeyType) bool { - switch kt { //nolint:exhaustive // only handle ec types - case EC256Key, EC384Key, EC521Key: - return true - default: - return false - } -} - -func IsRSAKeyType(kt KeyType) bool { - switch kt { //nolint:exhaustive // only handle rsa types - case RSA2048Key, RSA4096Key: - return true - default: - return false - } -} - -// GetECCurveFromECCMode return elliptic curve from ecc mode -func GetECCurveFromECCMode(mode ECCMode) (elliptic.Curve, error) { - var c elliptic.Curve - - switch mode { - case ECCModeSecp256r1: - c = elliptic.P256() - case ECCModeSecp384r1: - c = elliptic.P384() - case ECCModeSecp521r1: - c = elliptic.P521() - case ECCModeSecp256k1: - // TODO FIXME - unsupported? - return nil, errors.New("unsupported ECC mode") - default: - return nil, fmt.Errorf("unsupported ECC mode %d", mode) - } - - return c, nil -} - -func (mode ECCMode) String() string { - switch mode { - case ECCModeSecp256r1: - return "ec:secp256r1" - case ECCModeSecp384r1: - return "ec:secp384r1" - case ECCModeSecp521r1: - return "ec:secp521r1" - case ECCModeSecp256k1: - return "ec:secp256k1" - } - return "unspecified" -} - -// ECSizeToMode converts a curve size to an ECCMode -func ECSizeToMode(size int) (ECCMode, error) { - switch size { - case ECCurveP256Size: - return ECCModeSecp256r1, nil - case ECCurveP384Size: - return ECCModeSecp384r1, nil - case ECCurveP521Size: - return ECCModeSecp521r1, nil - default: - return 0, fmt.Errorf("unsupported EC curve size: %d", size) - } +type MLKEMKeyPair struct { + PrivateKey *mlkem.DecapsulationKey768 } -func ECKeyTypeToMode(kt KeyType) (ECCMode, error) { - switch kt { //nolint:exhaustive // only handle ec types - case EC256Key: - return ECCModeSecp256r1, nil - case EC384Key: - return ECCModeSecp384r1, nil - case EC521Key: - return ECCModeSecp521r1, nil - default: - return 0, fmt.Errorf("unsupported type: %v", kt) - } -} - -func RSAKeyTypeToBits(kt KeyType) (int, error) { - switch kt { //nolint:exhaustive // only handle rsa types - case RSA2048Key: - return RSA2048Size, nil - case RSA4096Key: - return RSA4096Size, nil - default: - return 0, fmt.Errorf("unsupported type: %v", kt) - } +type MLKEM1024KeyPair struct { + PrivateKey *mlkem.DecapsulationKey1024 } // NewECKeyPair Generates an EC key pair of the given bit size. @@ -452,7 +334,7 @@ func ECPrivateKeyInPemFormat(privateKey ecdsa.PrivateKey) (string, error) { privateKeyPem := pem.EncodeToMemory( &pem.Block{ - Type: "PRIVATE KEY", + Type: pemBlockPrivateKey, Bytes: privateKeyBytes, }, ) @@ -468,7 +350,7 @@ func ECPublicKeyInPemFormat(publicKey ecdsa.PublicKey) (string, error) { publicKeyPem := pem.EncodeToMemory( &pem.Block{ - Type: "PUBLIC KEY", + Type: pemBlockPublicKey, Bytes: pkb, }, ) @@ -509,3 +391,77 @@ func GetECKeySize(pemData []byte) (int, error) { func (keyPair ECKeyPair) GetKeyType() KeyType { return EC256Key } + +func NewMLKEMKeyPair() (MLKEMKeyPair, error) { + privateKey, err := mlkem.GenerateKey768() + if err != nil { + return MLKEMKeyPair{}, fmt.Errorf("mlkem.GenerateKey768 failed: %w", err) + } + + return MLKEMKeyPair{PrivateKey: privateKey}, nil +} + +func NewMLKEM1024KeyPair() (MLKEM1024KeyPair, error) { + privateKey, err := mlkem.GenerateKey1024() + if err != nil { + return MLKEM1024KeyPair{}, fmt.Errorf("mlkem.GenerateKey1024 failed: %w", err) + } + + return MLKEM1024KeyPair{PrivateKey: privateKey}, nil +} + +func (keyPair MLKEMKeyPair) PrivateKeyInPemFormat() (string, error) { + if keyPair.PrivateKey == nil { + return "", errors.New("failed to generate PEM formatted private key") + } + + der, err := marshalKEMPrivatePKCS8(OIDMLKEM768, keyPair.PrivateKey.Bytes()) + if err != nil { + return "", fmt.Errorf("marshal ML-KEM-768 PKCS#8 failed: %w", err) + } + return string(pem.EncodeToMemory(&pem.Block{Type: pemBlockPrivateKey, Bytes: der})), nil +} + +func (keyPair MLKEMKeyPair) PublicKeyInPemFormat() (string, error) { + if keyPair.PrivateKey == nil { + return "", errors.New("failed to generate PEM formatted public key") + } + + der, err := marshalKEMPublicSPKI(OIDMLKEM768, keyPair.PrivateKey.EncapsulationKey().Bytes()) + if err != nil { + return "", fmt.Errorf("marshal ML-KEM-768 SPKI failed: %w", err) + } + return string(pem.EncodeToMemory(&pem.Block{Type: pemBlockPublicKey, Bytes: der})), nil +} + +func (keyPair MLKEMKeyPair) GetKeyType() KeyType { + return MLKEM768Key +} + +func (keyPair MLKEM1024KeyPair) PrivateKeyInPemFormat() (string, error) { + if keyPair.PrivateKey == nil { + return "", errors.New("failed to generate PEM formatted private key") + } + + der, err := marshalKEMPrivatePKCS8(OIDMLKEM1024, keyPair.PrivateKey.Bytes()) + if err != nil { + return "", fmt.Errorf("marshal ML-KEM-1024 PKCS#8 failed: %w", err) + } + return string(pem.EncodeToMemory(&pem.Block{Type: pemBlockPrivateKey, Bytes: der})), nil +} + +func (keyPair MLKEM1024KeyPair) PublicKeyInPemFormat() (string, error) { + if keyPair.PrivateKey == nil { + return "", errors.New("failed to generate PEM formatted public key") + } + + der, err := marshalKEMPublicSPKI(OIDMLKEM1024, keyPair.PrivateKey.EncapsulationKey().Bytes()) + if err != nil { + return "", fmt.Errorf("marshal ML-KEM-1024 SPKI failed: %w", err) + } + return string(pem.EncodeToMemory(&pem.Block{Type: pemBlockPublicKey, Bytes: der})), nil +} + +func (keyPair MLKEM1024KeyPair) GetKeyType() KeyType { + return MLKEM1024Key +} diff --git a/lib/ocrypto/ecc_mode.go b/lib/ocrypto/ecc_mode.go new file mode 100644 index 0000000000..a2dbeec9fa --- /dev/null +++ b/lib/ocrypto/ecc_mode.go @@ -0,0 +1,71 @@ +package ocrypto + +import ( + "crypto/elliptic" + "errors" + "fmt" +) + +type ECCMode uint8 + +const ( + ECCModeSecp256r1 ECCMode = 0 + ECCModeSecp384r1 ECCMode = 1 + ECCModeSecp521r1 ECCMode = 2 + ECCModeSecp256k1 ECCMode = 3 +) + +const ( + ECCurveP256Size = 256 + ECCurveP384Size = 384 + ECCurveP521Size = 521 +) + +// GetECCurveFromECCMode return elliptic curve from ecc mode +func GetECCurveFromECCMode(mode ECCMode) (elliptic.Curve, error) { + var c elliptic.Curve + + switch mode { + case ECCModeSecp256r1: + c = elliptic.P256() + case ECCModeSecp384r1: + c = elliptic.P384() + case ECCModeSecp521r1: + c = elliptic.P521() + case ECCModeSecp256k1: + // TODO FIXME - unsupported? + return nil, errors.New("unsupported ECC mode") + default: + return nil, fmt.Errorf("unsupported ECC mode %d", mode) + } + + return c, nil +} + +func (mode ECCMode) String() string { + switch mode { + case ECCModeSecp256r1: + return "ec:secp256r1" + case ECCModeSecp384r1: + return "ec:secp384r1" + case ECCModeSecp521r1: + return "ec:secp521r1" + case ECCModeSecp256k1: + return "ec:secp256k1" + } + return "unspecified" +} + +// ECSizeToMode converts a curve size to an ECCMode +func ECSizeToMode(size int) (ECCMode, error) { + switch size { + case ECCurveP256Size: + return ECCModeSecp256r1, nil + case ECCurveP384Size: + return ECCModeSecp384r1, nil + case ECCurveP521Size: + return ECCModeSecp521r1, nil + default: + return 0, fmt.Errorf("unsupported EC curve size: %d", size) + } +} diff --git a/lib/ocrypto/hybrid_common.go b/lib/ocrypto/hybrid_common.go index f7178d009c..a9f9631d27 100644 --- a/lib/ocrypto/hybrid_common.go +++ b/lib/ocrypto/hybrid_common.go @@ -5,6 +5,26 @@ import ( "fmt" ) +// WrapDEK parses the recipient's KEM public key PEM via the OID/PEM-routed +// dispatcher and produces the ASN.1-encoded wrapped DEK envelope. It covers +// both pure ML-KEM (`mlkem-wrapped`) and hybrid PQ/T (`hybrid-wrapped`) KAOs so +// SDK and service callers can wrap against any KEM scheme without a per-scheme +// switch — the encryptor returned by FromPublicPEM selects the format. +func WrapDEK(ktype KeyType, kasPublicKeyPEM string, dek []byte) ([]byte, error) { + if !IsKEMKeyType(ktype) { + return nil, fmt.Errorf("unsupported KEM key type: %s", ktype) + } + + enc, err := FromPublicPEM(kasPublicKeyPEM) + if err != nil { + return nil, fmt.Errorf("kem public key: %w", err) + } + if enc.KeyType() != ktype { + return nil, fmt.Errorf("kem key type mismatch: PEM is %s, requested %s", enc.KeyType(), ktype) + } + return enc.Encrypt(dek) +} + // HybridWrapDEK parses the recipient's hybrid public key PEM via the // OID-routed dispatcher, asserts the encryptor matches the requested ktype, // and produces the ASN.1-encoded wrapped DEK envelope used in diff --git a/lib/ocrypto/hybrid_conformance_test.go b/lib/ocrypto/hybrid_conformance_test.go index 4f1bfeb8d0..4945139f49 100644 --- a/lib/ocrypto/hybrid_conformance_test.go +++ b/lib/ocrypto/hybrid_conformance_test.go @@ -178,23 +178,22 @@ func assertHybridNISTPrivateKeyLayout(t *testing.T, keyPair HybridNISTKeyPair, p func assertHybridNISTWrappedCiphertextLayout(t *testing.T, keyPair HybridNISTKeyPair, params *hybridNISTParams, mlkemCiphertextSize int) { t.Helper() - enc, err := NewP256MLKEM768Encryptor(keyPair.publicKey) - if params.keyType == HybridSecp384r1MLKEM1024Key { - enc, err = NewP384MLKEM1024Encryptor(keyPair.publicKey) - } + pubPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + enc, err := FromPublicPEM(pubPEM) require.NoError(t, err) wrappedDER, err := enc.Encrypt([]byte("layout-test-dek")) require.NoError(t, err) - var wrapped HybridNISTWrappedKey + var wrapped kemEnvelope rest, err := asn1.Unmarshal(wrappedDER, &wrapped) require.NoError(t, err) require.Empty(t, rest) - require.Len(t, wrapped.HybridCiphertext, mlkemCiphertextSize+params.ecPubSize) - require.Len(t, wrapped.HybridCiphertext[:mlkemCiphertextSize], mlkemCiphertextSize) + require.Len(t, wrapped.KEMCiphertext, mlkemCiphertextSize+params.ecPubSize) + require.Len(t, wrapped.KEMCiphertext[:mlkemCiphertextSize], mlkemCiphertextSize) - ephemeralECPub := wrapped.HybridCiphertext[mlkemCiphertextSize:] + ephemeralECPub := wrapped.KEMCiphertext[mlkemCiphertextSize:] require.Len(t, ephemeralECPub, params.ecPubSize) require.Equal(t, byte(0x04), ephemeralECPub[0], "ephemeral EC point must be uncompressed SEC1") _, err = params.curve.NewPublicKey(ephemeralECPub) diff --git a/lib/ocrypto/hybrid_nist.go b/lib/ocrypto/hybrid_nist.go index c127e7093e..2b6aa454c5 100644 --- a/lib/ocrypto/hybrid_nist.go +++ b/lib/ocrypto/hybrid_nist.go @@ -41,14 +41,6 @@ const ( P384MLKEM1024CiphertextSize = P384MLKEM1024MLKEMCtSize + P384MLKEM1024ECPublicKeySize // 1665 ) -// HybridNISTWrappedKey is the ASN.1 envelope stored in wrapped_key. The IETF -// composite-KEM draft defines only the KEM; this DEK wrapping envelope is -// kept identical to its pre-conformance shape so the TDF layer is unaffected. -type HybridNISTWrappedKey struct { - HybridCiphertext []byte `asn1:"tag:0"` - EncryptedDEK []byte `asn1:"tag:1"` -} - // hybridNISTParams captures the curve-specific parameters for one composite-KEM // hybrid scheme. type hybridNISTParams struct { @@ -97,18 +89,6 @@ type HybridNISTKeyPair struct { params *hybridNISTParams } -// HybridNISTEncryptor implements PublicKeyEncryptor for composite-KEM hybrids. -type HybridNISTEncryptor struct { - publicKey []byte - params *hybridNISTParams -} - -// HybridNISTDecryptor implements PrivateKeyDecryptor for composite-KEM hybrids. -type HybridNISTDecryptor struct { - privateKey []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 @@ -212,98 +192,133 @@ func (k HybridNISTKeyPair) GetKeyType() KeyType { return k.params.keyType } -func NewP256MLKEM768Encryptor(publicKey []byte) (*HybridNISTEncryptor, error) { - return newHybridNISTEncryptor(&p256mlkem768Params, publicKey) -} - -func NewP384MLKEM1024Encryptor(publicKey []byte) (*HybridNISTEncryptor, error) { - return newHybridNISTEncryptor(&p384mlkem1024Params, publicKey) -} - -func newHybridNISTEncryptor(p *hybridNISTParams, publicKey []byte) (*HybridNISTEncryptor, error) { - expectedSize := p.mlkemPubSize + p.ecPubSize - if len(publicKey) != expectedSize { - return nil, fmt.Errorf("invalid %s public key size: got %d want %d", p.keyType, len(publicKey), expectedSize) +// hybridNISTKEM adapts a NIST composite-KEM hybrid (EC + ML-KEM) onto the +// shared kem interface so it flows through wrapDEKWithKEM / unwrapDEKWithKEM +// and the single kemEnvelope wire format. The combiner that produces the +// AES-256 wrap key is folded into encapsulate / decapsulate, so wrapKey is an +// identity pass-through of the already-derived key (mirroring pure ML-KEM). +type hybridNISTKEM struct { + params *hybridNISTParams +} + +func (k hybridNISTKEM) keyType() KeyType { return k.params.keyType } +func (hybridNISTKEM) scheme() SchemeType { return Hybrid } +func (k hybridNISTKEM) pubSize() int { return k.params.mlkemPubSize + k.params.ecPubSize } +func (k hybridNISTKEM) ctSize() int { return k.params.mlkemCtSize + k.params.ecPubSize } + +// privSize is negative to mark a variable-length private key encoding +// (mlkemSeed || ECPrivateKey DER, whose length depends on the RFC 5915 +// serialization). newKEMDecryptor skips its exact-size check for this scheme; +// decapsulate validates the layout instead. +func (hybridNISTKEM) privSize() int { return -1 } + +// encapsulate performs ECDH against an ephemeral key, ML-KEM encapsulation, and +// the SHA3-256 combiner (draft-ietf-lamps-pq-composite-kem-14 §3.4), returning +// the 32-byte combined key as the "shared secret" and `mlkemCT || ephemeralEC` +// as the ciphertext. Math is preserved verbatim from the former +// hybridNISTWrapDEK. +func (k hybridNISTKEM) encapsulate(publicKeyRaw []byte) ([]byte, []byte, error) { + p := k.params + expectedPubSize := p.mlkemPubSize + p.ecPubSize + if len(publicKeyRaw) != expectedPubSize { + return nil, nil, fmt.Errorf("invalid %s public key size: got %d want %d", p.keyType, len(publicKeyRaw), expectedPubSize) } - return &HybridNISTEncryptor{ - publicKey: append([]byte(nil), publicKey...), - params: p, - }, nil -} -func (e *HybridNISTEncryptor) Encrypt(data []byte) ([]byte, error) { - return hybridNISTWrapDEK(e.params, e.publicKey, data) -} + mlkemPubBytes := publicKeyRaw[:p.mlkemPubSize] + ecPubBytes := publicKeyRaw[p.mlkemPubSize:] -func (e *HybridNISTEncryptor) PublicKeyInPemFormat() (string, error) { - der, err := marshalHybridSPKI(e.params.oid, e.publicKey) + ecPub, err := p.curve.NewPublicKey(ecPubBytes) if err != nil { - return "", err + return nil, nil, fmt.Errorf("invalid EC public key: %w", err) } - return string(pem.EncodeToMemory(&pem.Block{Type: pemBlockPublicKey, Bytes: der})), nil -} + ephemeral, err := p.curve.GenerateKey(rand.Reader) + if err != nil { + return nil, nil, fmt.Errorf("ECDH ephemeral key generation failed: %w", err) + } + tradSS, err := ephemeral.ECDH(ecPub) + if err != nil { + return nil, nil, fmt.Errorf("ECDH failed: %w", err) + } + ephemeralPub := ephemeral.PublicKey().Bytes() -func (e *HybridNISTEncryptor) Type() SchemeType { return Hybrid } -func (e *HybridNISTEncryptor) KeyType() KeyType { return e.params.keyType } -func (e *HybridNISTEncryptor) EphemeralKey() []byte { return nil } + mlkemSS, mlkemCT, err := mlkemEncapsulate(p, mlkemPubBytes) + if err != nil { + return nil, nil, err + } -func (e *HybridNISTEncryptor) Metadata() (map[string]string, error) { - return make(map[string]string), nil -} + wrapKey := hybridNISTCombiner(p, mlkemSS, tradSS, ephemeralPub, ecPubBytes) -func NewP256MLKEM768Decryptor(privateKey []byte) (*HybridNISTDecryptor, error) { - return newHybridNISTDecryptor(&p256mlkem768Params, privateKey) -} + hybridCt := make([]byte, 0, len(mlkemCT)+len(ephemeralPub)) + hybridCt = append(hybridCt, mlkemCT...) + hybridCt = append(hybridCt, ephemeralPub...) -func NewP384MLKEM1024Decryptor(privateKey []byte) (*HybridNISTDecryptor, error) { - return newHybridNISTDecryptor(&p384mlkem1024Params, privateKey) + return wrapKey, hybridCt, nil } -func newHybridNISTDecryptor(p *hybridNISTParams, privateKey []byte) (*HybridNISTDecryptor, error) { - if len(privateKey) <= mlkemSeedSize { +// decapsulate is the symmetric inverse of encapsulate, returning the combiner +// output as the "shared secret". Math is preserved verbatim from the former +// hybridNISTUnwrapDEK. The ciphertext length is validated by unwrapDEKWithKEM +// against ctSize() before this is called. +func (k hybridNISTKEM) decapsulate(privateKeyRaw, ct []byte) ([]byte, error) { + p := k.params + if len(privateKeyRaw) <= mlkemSeedSize { return nil, fmt.Errorf("invalid %s private key: shorter than ML-KEM seed + ECPrivateKey", p.keyType) } - // Parse the EC DER tail up front so a malformed key surfaces at - // construction time — mirrors newHybridNISTEncryptor's exact-size check - // on the public-key side. The parsed key itself is discarded; Decrypt - // re-parses (cheap relative to ML-KEM decapsulation) for code simplicity. - ecPriv, err := x509.ParseECPrivateKey(privateKey[mlkemSeedSize:]) + mlkemSeed := privateKeyRaw[:mlkemSeedSize] + ecPrivDER := privateKeyRaw[mlkemSeedSize:] + + mlkemCT := ct[:p.mlkemCtSize] + ephemeralPubBytes := ct[p.mlkemCtSize:] + + ecdsaPriv, err := x509.ParseECPrivateKey(ecPrivDER) if err != nil { - return nil, fmt.Errorf("invalid %s private key: parse ECPrivateKey: %w", p.keyType, err) + return nil, fmt.Errorf("parse ECPrivateKey: %w", err) } - if ecPriv.Curve != p.namedCurve { - return nil, fmt.Errorf("invalid %s private key: EC curve mismatch", p.keyType) + if ecdsaPriv.Curve != p.namedCurve { + return nil, fmt.Errorf("EC private key curve mismatch for %s", p.keyType) } - return &HybridNISTDecryptor{ - privateKey: append([]byte(nil), privateKey...), - params: p, - }, nil -} - -func (d *HybridNISTDecryptor) Decrypt(data []byte) ([]byte, error) { - return hybridNISTUnwrapDEK(d.params, d.privateKey, data) -} + ecdhPriv, err := ecdsaPriv.ECDH() + if err != nil { + return nil, fmt.Errorf("convert ECDSA to ECDH: %w", err) + } + tradPK := ecdhPriv.PublicKey().Bytes() -// KeyType identifies the hybrid scheme so KAS-layer callers can cross-check -// the OID-routed decryptor against an asserted algorithm before trusting it. -func (d *HybridNISTDecryptor) KeyType() KeyType { - return d.params.keyType -} + ephemeralPub, err := p.curve.NewPublicKey(ephemeralPubBytes) + if err != nil { + return nil, fmt.Errorf("invalid ephemeral EC public key: %w", err) + } + tradSS, err := ecdhPriv.ECDH(ephemeralPub) + if err != nil { + return nil, fmt.Errorf("ECDH failed: %w", err) + } -func P256MLKEM768WrapDEK(publicKeyRaw, dek []byte) ([]byte, error) { - return hybridNISTWrapDEK(&p256mlkem768Params, publicKeyRaw, dek) -} + // ML-KEM implicit rejection (FIPS 203 §6.3) yields a pseudorandom shared + // secret on a wrong-key ciphertext rather than an error here; the AES-GCM + // decrypt in unwrapDEKWithKEM provides authentication. + mlkemSS, err := mlkemDecapsulate(p, mlkemSeed, mlkemCT) + if err != nil { + return nil, fmt.Errorf("ML-KEM decapsulate failed: %w", err) + } -func P256MLKEM768UnwrapDEK(privateKeyRaw, wrappedDER []byte) ([]byte, error) { - return hybridNISTUnwrapDEK(&p256mlkem768Params, privateKeyRaw, wrappedDER) + return hybridNISTCombiner(p, mlkemSS, tradSS, ephemeralPubBytes, tradPK), nil } -func P384MLKEM1024WrapDEK(publicKeyRaw, dek []byte) ([]byte, error) { - return hybridNISTWrapDEK(&p384mlkem1024Params, publicKeyRaw, dek) +func (k hybridNISTKEM) publicKeyPEM(pub []byte) (string, error) { + der, err := marshalHybridSPKI(k.params.oid, pub) + if err != nil { + return "", err + } + return string(pem.EncodeToMemory(&pem.Block{Type: pemBlockPublicKey, Bytes: der})), nil } -func P384MLKEM1024UnwrapDEK(privateKeyRaw, wrappedDER []byte) ([]byte, error) { - return hybridNISTUnwrapDEK(&p384mlkem1024Params, privateKeyRaw, wrappedDER) +// wrapKey is an identity pass-through: encapsulate / decapsulate already ran the +// SHA3-256 combiner, which emits the 32-byte AES-256 key directly (no extra KDF +// per draft-14 §3.4). The length check guards against a combiner change. +func (hybridNISTKEM) wrapKey(sharedSecret, _ /*salt*/, _ /*info*/ []byte) ([]byte, error) { + if len(sharedSecret) != kemWrapKeySize { + return nil, fmt.Errorf("invalid hybrid NIST wrap key size: got %d want %d", len(sharedSecret), kemWrapKeySize) + } + return append([]byte(nil), sharedSecret...), nil } // hybridNISTCombiner returns the 32-byte SHA3-256 digest defined in @@ -382,124 +397,3 @@ func mlkemDecapsulate(p *hybridNISTParams, mlkemSeed, mlkemCT []byte) ([]byte, e return nil, fmt.Errorf("unsupported ML-KEM key type: %s", p.keyType) } } - -func hybridNISTWrapDEK(p *hybridNISTParams, publicKeyRaw, dek []byte) ([]byte, error) { - expectedPubSize := p.mlkemPubSize + p.ecPubSize - if len(publicKeyRaw) != expectedPubSize { - return nil, fmt.Errorf("invalid %s public key size: got %d want %d", p.keyType, len(publicKeyRaw), expectedPubSize) - } - - mlkemPubBytes := publicKeyRaw[:p.mlkemPubSize] - ecPubBytes := publicKeyRaw[p.mlkemPubSize:] - - ecPub, err := p.curve.NewPublicKey(ecPubBytes) - if err != nil { - return nil, fmt.Errorf("invalid EC public key: %w", err) - } - ephemeral, err := p.curve.GenerateKey(rand.Reader) - if err != nil { - return nil, fmt.Errorf("ECDH ephemeral key generation failed: %w", err) - } - tradSS, err := ephemeral.ECDH(ecPub) - if err != nil { - return nil, fmt.Errorf("ECDH failed: %w", err) - } - ephemeralPub := ephemeral.PublicKey().Bytes() - - mlkemSS, mlkemCT, err := mlkemEncapsulate(p, mlkemPubBytes) - if err != nil { - return nil, err - } - - wrapKey := hybridNISTCombiner(p, mlkemSS, tradSS, ephemeralPub, ecPubBytes) - - hybridCt := make([]byte, 0, len(mlkemCT)+len(ephemeralPub)) - hybridCt = append(hybridCt, mlkemCT...) - hybridCt = append(hybridCt, ephemeralPub...) - - 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 []byte) ([]byte, error) { - if len(privateKeyRaw) <= mlkemSeedSize { - return nil, fmt.Errorf("invalid %s private key: shorter than ML-KEM seed + ECPrivateKey", p.keyType) - } - mlkemSeed := privateKeyRaw[:mlkemSeedSize] - ecPrivDER := privateKeyRaw[mlkemSeedSize:] - - 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.mlkemCtSize + p.ecPubSize - if len(wrapped.HybridCiphertext) != expectedCtSize { - return nil, fmt.Errorf("invalid %s ciphertext size: got %d want %d", - p.keyType, len(wrapped.HybridCiphertext), expectedCtSize) - } - - mlkemCT := wrapped.HybridCiphertext[:p.mlkemCtSize] - ephemeralPubBytes := wrapped.HybridCiphertext[p.mlkemCtSize:] - - ecdsaPriv, err := x509.ParseECPrivateKey(ecPrivDER) - if err != nil { - return nil, fmt.Errorf("parse ECPrivateKey: %w", err) - } - if ecdsaPriv.Curve != p.namedCurve { - return nil, fmt.Errorf("EC private key curve mismatch for %s", p.keyType) - } - ecdhPriv, err := ecdsaPriv.ECDH() - if err != nil { - return nil, fmt.Errorf("convert ECDSA to ECDH: %w", err) - } - tradPK := ecdhPriv.PublicKey().Bytes() - - ephemeralPub, err := p.curve.NewPublicKey(ephemeralPubBytes) - if err != nil { - return nil, fmt.Errorf("invalid ephemeral EC public key: %w", err) - } - tradSS, err := ecdhPriv.ECDH(ephemeralPub) - if err != nil { - return nil, fmt.Errorf("ECDH failed: %w", err) - } - - // ML-KEM implicit rejection (FIPS 203 §6.3) yields a pseudorandom shared - // secret on a wrong-key ciphertext rather than an error here; the AES-GCM - // decrypt below provides authentication. - mlkemSS, err := mlkemDecapsulate(p, mlkemSeed, mlkemCT) - if err != nil { - return nil, fmt.Errorf("ML-KEM decapsulate failed: %w", err) - } - - wrapKey := hybridNISTCombiner(p, mlkemSS, tradSS, ephemeralPubBytes, tradPK) - - 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 -} diff --git a/lib/ocrypto/hybrid_nist_test.go b/lib/ocrypto/hybrid_nist_test.go index bc9e238f56..bff6def4a7 100644 --- a/lib/ocrypto/hybrid_nist_test.go +++ b/lib/ocrypto/hybrid_nist_test.go @@ -75,10 +75,10 @@ func TestP256MLKEM768WrapUnwrapRoundTrip(t *testing.T) { require.NoError(t, err) dek := []byte("0123456789abcdef0123456789abcdef") - wrapped, err := P256MLKEM768WrapDEK(keyPair.publicKey, dek) + wrapped, err := wrapDEKWithKEM(hybridNISTKEM{params: &p256mlkem768Params}, keyPair.publicKey, dek, nil, nil) require.NoError(t, err) - plaintext, err := P256MLKEM768UnwrapDEK(keyPair.privateKey, wrapped) + plaintext, err := unwrapDEKWithKEM(hybridNISTKEM{params: &p256mlkem768Params}, keyPair.privateKey, wrapped, nil, nil) require.NoError(t, err) assert.Equal(t, dek, plaintext) } @@ -88,10 +88,10 @@ func TestP384MLKEM1024WrapUnwrapRoundTrip(t *testing.T) { require.NoError(t, err) dek := []byte("0123456789abcdef0123456789abcdef") - wrapped, err := P384MLKEM1024WrapDEK(keyPair.publicKey, dek) + wrapped, err := wrapDEKWithKEM(hybridNISTKEM{params: &p384mlkem1024Params}, keyPair.publicKey, dek, nil, nil) require.NoError(t, err) - plaintext, err := P384MLKEM1024UnwrapDEK(keyPair.privateKey, wrapped) + plaintext, err := unwrapDEKWithKEM(hybridNISTKEM{params: &p384mlkem1024Params}, keyPair.privateKey, wrapped, nil, nil) require.NoError(t, err) assert.Equal(t, dek, plaintext) } @@ -102,10 +102,10 @@ func TestP256MLKEM768WrapUnwrapWrongKeyFails(t *testing.T) { wrongKeyPair, err := NewP256MLKEM768KeyPair() require.NoError(t, err) - wrapped, err := P256MLKEM768WrapDEK(keyPair.publicKey, []byte("top secret dek")) + wrapped, err := wrapDEKWithKEM(hybridNISTKEM{params: &p256mlkem768Params}, keyPair.publicKey, []byte("top secret dek"), nil, nil) require.NoError(t, err) - _, err = P256MLKEM768UnwrapDEK(wrongKeyPair.privateKey, wrapped) + _, err = unwrapDEKWithKEM(hybridNISTKEM{params: &p256mlkem768Params}, wrongKeyPair.privateKey, wrapped, nil, nil) 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 @@ -119,30 +119,14 @@ func TestP384MLKEM1024WrapUnwrapWrongKeyFails(t *testing.T) { wrongKeyPair, err := NewP384MLKEM1024KeyPair() require.NoError(t, err) - wrapped, err := P384MLKEM1024WrapDEK(keyPair.publicKey, []byte("top secret dek")) + wrapped, err := wrapDEKWithKEM(hybridNISTKEM{params: &p384mlkem1024Params}, keyPair.publicKey, []byte("top secret dek"), nil, nil) require.NoError(t, err) - _, err = P384MLKEM1024UnwrapDEK(wrongKeyPair.privateKey, wrapped) + _, err = unwrapDEKWithKEM(hybridNISTKEM{params: &p384mlkem1024Params}, wrongKeyPair.privateKey, wrapped, nil, nil) 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) @@ -158,7 +142,7 @@ func TestP256MLKEM768PEMDispatch(t *testing.T) { decryptor, err := FromPrivatePEM(privatePEM) require.NoError(t, err) - nistEncryptor, ok := encryptor.(*HybridNISTEncryptor) + nistEncryptor, ok := encryptor.(*kemEncryptor) require.True(t, ok) assert.Equal(t, Hybrid, nistEncryptor.Type()) assert.Equal(t, HybridSecp256r1MLKEM768Key, nistEncryptor.KeyType()) @@ -168,7 +152,7 @@ func TestP256MLKEM768PEMDispatch(t *testing.T) { require.NoError(t, err) assert.Empty(t, metadata) - nistDecryptor, ok := decryptor.(*HybridNISTDecryptor) + nistDecryptor, ok := decryptor.(*kemDecryptor) require.True(t, ok) wrapped, err := nistEncryptor.Encrypt([]byte("dispatch-dek")) @@ -194,13 +178,13 @@ func TestP384MLKEM1024PEMDispatch(t *testing.T) { decryptor, err := FromPrivatePEM(privatePEM) require.NoError(t, err) - nistEncryptor, ok := encryptor.(*HybridNISTEncryptor) + nistEncryptor, ok := encryptor.(*kemEncryptor) 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) + nistDecryptor, ok := decryptor.(*kemDecryptor) require.True(t, ok) wrapped, err := nistEncryptor.Encrypt([]byte("dispatch-dek-384")) diff --git a/lib/ocrypto/kem.go b/lib/ocrypto/kem.go new file mode 100644 index 0000000000..d86a49e448 --- /dev/null +++ b/lib/ocrypto/kem.go @@ -0,0 +1,373 @@ +package ocrypto + +import ( + "crypto/mlkem" + "encoding/asn1" + "encoding/pem" + "fmt" +) + +// kem is the post-quantum KEM contract implemented by every KEM family: the +// pure ML-KEM schemes and the IETF-draft hybrid PQ/T schemes (X-Wing and the +// NIST EC + ML-KEM composites). Each family is adapted onto this interface +// (mlkemKEM, xwingKEM, hybridNISTKEM) and they all share one envelope, one +// AES-GCM call site, and the unified kemEncryptor / kemDecryptor types, reached +// via the OID-routed dispatcher in asym_encryption.go / asym_decryption.go. +// The wrap-key derivation still differs per family; see wrapKey below. +type kem interface { + keyType() KeyType + scheme() SchemeType + pubSize() int + privSize() int + ctSize() int + encapsulate(pub []byte) (sharedSecret, ciphertext []byte, err error) + decapsulate(priv, ct []byte) (sharedSecret []byte, err error) + // publicKeyPEM returns the PEM serialization for the given raw public key. + // Each adapter handles its own format. After the planned follow-up moves + // X-Wing and the NIST hybrid keys onto standard SPKI PEM blocks this + // per-adapter hook collapses to a single shared helper. + publicKeyPEM(pub []byte) (string, error) + // wrapKey returns the AES-256 key used to seal the DEK from the + // shared secret produced by encapsulate / decapsulate. + // + // ML-KEM returns the 32-byte Decaps output verbatim (no KDF) so that an + // HSM-backed KAS holding the shared secret as a CKK_AES, non-extractable + // object can perform AES-GCM directly. See FIPS 203 §6.3 / §7.3 and + // adr/decisions/2026-06-16-mlkem-direct-key-wrap.md. + // + // Hybrid PQ/T schemes (X-Wing, NIST EC + ML-KEM) concatenate two + // shared-secret halves and still require HKDF-SHA256 over (salt, info) + // for proper combiner hygiene. + wrapKey(sharedSecret, salt, info []byte) ([]byte, error) +} + +// kemEnvelope is the ASN.1 wire format for every KEM-wrapped DEK across +// `hybrid-wrapped` and `mlkem-wrapped` KAOs. It is byte-identical to the three +// legacy structs (MLKEMWrappedKey, XWingWrappedKey, HybridNISTWrappedKey) it +// replaces — same tags, same field order. +type kemEnvelope struct { + KEMCiphertext []byte `asn1:"tag:0"` + EncryptedDEK []byte `asn1:"tag:1"` +} + +// kemWrapKeySize is the AES-256 wrap key length. Pure ML-KEM uses the +// 32-byte Decaps output directly; hybrid PQ/T schemes derive it via +// HKDF-SHA256 over the combined shared secret. +const kemWrapKeySize = 32 + +// kemRegistry maps the SPKI/PKCS#8 OID published for a KEM scheme to a +// constructor that returns a kem adapter bound to that scheme. ML-KEM is the +// only family with standardized OIDs landed today; the planned hybrid PQ/T +// SPKI follow-up adds X-Wing and the two NIST hybrid OIDs by inserting +// registry entries here. +var kemRegistry = map[string]func() kem{ + OIDMLKEM768.String(): func() kem { return mlkemKEM{variant: mlkem768} }, + OIDMLKEM1024.String(): func() kem { return mlkemKEM{variant: mlkem1024} }, +} + +// kemByOID returns the kem adapter registered for the supplied OID, or false +// if the OID is not a recognised KEM algorithm. +func kemByOID(oid asn1.ObjectIdentifier) (kem, bool) { + ctor, ok := kemRegistry[oid.String()] + if !ok { + return nil, false + } + return ctor(), true +} + +// hybridKEMByOID returns the hybrid PQ/T kem adapter for the supplied +// AlgorithmIdentifier OID. The hybrid schemes are kept out of kemRegistry +// because their PKCS#8 private-key encoding differs from the RFC 5958 KEM +// CHOICE that parseKEMPrivatePKCS8 expects (X-Wing/NIST store the raw key +// directly, ML-KEM double-wraps the seed in a [0] IMPLICIT OCTET STRING). +// The OID-routing dispatchers in asym_encryption.go / asym_decryption.go use +// this helper to build kemEncryptor / kemDecryptor for hybrid keys. +func hybridKEMByOID(oid asn1.ObjectIdentifier) (kem, bool) { + switch { + case oid.Equal(oidXWing): + return xwingKEM{}, true + case oid.Equal(oidCompositeMLKEM768P256): + return hybridNISTKEM{params: &p256mlkem768Params}, true + case oid.Equal(oidCompositeMLKEM1024P384): + return hybridNISTKEM{params: &p384mlkem1024Params}, true + default: + return nil, false + } +} + +// IsKEMKeyType reports whether the supplied KeyType is one of the KEM schemes +// — pure ML-KEM or hybrid PQ/T — that wrap a DEK through FromPublicPEM / +// FromPrivatePEM rather than the RSA/EC paths. Callers use it as the routing +// gate before delegating to WrapDEK. +func IsKEMKeyType(kt KeyType) bool { + return IsMLKEMKeyType(kt) || IsHybridKeyType(kt) +} + +// --- mlkemKEM adapter ------------------------------------------------------- + +type mlkemVariant int + +const ( + mlkem768 mlkemVariant = iota + mlkem1024 +) + +type mlkemKEM struct { + variant mlkemVariant +} + +func (m mlkemKEM) keyType() KeyType { + if m.variant == mlkem1024 { + return MLKEM1024Key + } + return MLKEM768Key +} + +func (mlkemKEM) scheme() SchemeType { return MLKEM } + +func (m mlkemKEM) pubSize() int { + if m.variant == mlkem1024 { + return MLKEM1024PublicKeySize + } + return MLKEM768PublicKeySize +} + +func (m mlkemKEM) privSize() int { + if m.variant == mlkem1024 { + return MLKEM1024PrivateKeySize + } + return MLKEM768PrivateKeySize +} + +func (m mlkemKEM) ctSize() int { + if m.variant == mlkem1024 { + return MLKEM1024CiphertextSize + } + return MLKEM768CiphertextSize +} + +func (m mlkemKEM) encapsulate(pub []byte) ([]byte, []byte, error) { + if len(pub) != m.pubSize() { + return nil, nil, fmt.Errorf("invalid %s public key size: got %d want %d", m.keyType(), len(pub), m.pubSize()) + } + if m.variant == mlkem1024 { + ek, err := mlkem.NewEncapsulationKey1024(pub) + if err != nil { + return nil, nil, fmt.Errorf("mlkem.NewEncapsulationKey1024 failed: %w", err) + } + ss, ct := ek.Encapsulate() + return ss, ct, nil + } + ek, err := mlkem.NewEncapsulationKey768(pub) + if err != nil { + return nil, nil, fmt.Errorf("mlkem.NewEncapsulationKey768 failed: %w", err) + } + ss, ct := ek.Encapsulate() + return ss, ct, nil +} + +func (m mlkemKEM) decapsulate(priv, ct []byte) ([]byte, error) { + if len(priv) != m.privSize() { + return nil, fmt.Errorf("invalid %s private key size: got %d want %d", m.keyType(), len(priv), m.privSize()) + } + if m.variant == mlkem1024 { + dk, err := mlkem.NewDecapsulationKey1024(priv) + if err != nil { + return nil, fmt.Errorf("mlkem.NewDecapsulationKey1024 failed: %w", err) + } + ss, err := dk.Decapsulate(ct) + if err != nil { + return nil, fmt.Errorf("mlkem1024 decapsulate failed: %w", err) + } + return ss, nil + } + dk, err := mlkem.NewDecapsulationKey768(priv) + if err != nil { + return nil, fmt.Errorf("mlkem.NewDecapsulationKey768 failed: %w", err) + } + ss, err := dk.Decapsulate(ct) + if err != nil { + return nil, fmt.Errorf("mlkem768 decapsulate failed: %w", err) + } + return ss, nil +} + +func (m mlkemKEM) publicKeyPEM(pub []byte) (string, error) { + oid := OIDMLKEM768 + if m.variant == mlkem1024 { + oid = OIDMLKEM1024 + } + der, err := marshalKEMPublicSPKI(oid, pub) + if err != nil { + return "", fmt.Errorf("marshal %s SPKI failed: %w", m.keyType(), err) + } + return string(pem.EncodeToMemory(&pem.Block{Type: pemBlockPublicKey, Bytes: der})), nil +} + +// wrapKey returns the ML-KEM Decaps output directly as the AES-256 wrap key. +// +// FIPS 203 §6.3 / §7.3 specify that ML-KEM Decaps emits a uniformly random +// 32-byte shared secret K that is suitable for direct use as a symmetric key, +// and ML-KEM produces a fresh K per encapsulation by construction. salt and +// info are ignored on purpose so that HSM-backed KAS providers that can only +// materialize the shared secret as a non-extractable CKK_AES object (e.g. +// Thales Luna T-Series 7.15.1 in strict-FIPS mode, which rejects HMAC over +// the Decaps output with CKR_ATTRIBUTE_TYPE_INVALID) can still complete +// AES-GCM unwrap without an HKDF step. +func (mlkemKEM) wrapKey(sharedSecret, _ /*salt*/, _ /*info*/ []byte) ([]byte, error) { + if len(sharedSecret) != kemWrapKeySize { + return nil, fmt.Errorf("invalid ML-KEM shared secret size: got %d want %d", len(sharedSecret), kemWrapKeySize) + } + return append([]byte(nil), sharedSecret...), nil +} + +// --- wrap / unwrap ---------------------------------------------------------- + +// wrapDEKWithKEM encapsulates against pub, asks the scheme adapter for an +// AES-256 wrap key (HKDF for hybrid PQ/T, direct shared-secret for pure +// ML-KEM), and emits the kemEnvelope ASN.1 DER blob carrying (KEM +// ciphertext, AES-GCM-encrypted DEK). +func wrapDEKWithKEM(k kem, pub, dek, salt, info []byte) ([]byte, error) { + sharedSecret, ciphertext, err := k.encapsulate(pub) + if err != nil { + return nil, err + } + + wrapKey, err := k.wrapKey(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(kemEnvelope{ + KEMCiphertext: ciphertext, + EncryptedDEK: encryptedDEK, + }) + if err != nil { + return nil, fmt.Errorf("asn1.Marshal failed: %w", err) + } + + return wrappedDER, nil +} + +// unwrapDEKWithKEM parses the kemEnvelope DER blob, decapsulates with priv to +// recover the shared secret, asks the scheme adapter for the matching AES-256 +// wrap key, and AES-GCM decrypts the DEK. +func unwrapDEKWithKEM(k kem, priv, der, salt, info []byte) ([]byte, error) { + var env kemEnvelope + rest, err := asn1.Unmarshal(der, &env) + 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(env.KEMCiphertext) != k.ctSize() { + return nil, fmt.Errorf("invalid %s ciphertext size: got %d want %d", k.keyType(), len(env.KEMCiphertext), k.ctSize()) + } + + sharedSecret, err := k.decapsulate(priv, env.KEMCiphertext) + if err != nil { + return nil, err + } + + wrapKey, err := k.wrapKey(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(env.EncryptedDEK) + if err != nil { + return nil, fmt.Errorf("AES-GCM decrypt failed: %w", err) + } + + return plaintext, nil +} + +// --- unified encryptor / decryptor ------------------------------------------ + +// kemEncryptor satisfies PublicKeyEncryptor for every KEM family. It replaces +// the per-variant MLKEMEncryptor*, XWingEncryptor, and HybridNISTEncryptor +// types behind the FromPublicPEM factory. +type kemEncryptor struct { + k kem + publicKey []byte + salt []byte + info []byte +} + +func newKEMEncryptor(k kem, publicKey, salt, info []byte) (*kemEncryptor, error) { + if len(publicKey) != k.pubSize() { + return nil, fmt.Errorf("invalid %s public key size: got %d want %d", k.keyType(), len(publicKey), k.pubSize()) + } + return &kemEncryptor{ + k: k, + publicKey: append([]byte(nil), publicKey...), + salt: cloneOrNil(salt), + info: cloneOrNil(info), + }, nil +} + +func (e *kemEncryptor) Encrypt(data []byte) ([]byte, error) { + return wrapDEKWithKEM(e.k, e.publicKey, data, e.salt, e.info) +} + +func (e *kemEncryptor) PublicKeyInPemFormat() (string, error) { + return e.k.publicKeyPEM(e.publicKey) +} + +func (e *kemEncryptor) Type() SchemeType { return e.k.scheme() } +func (e *kemEncryptor) KeyType() KeyType { return e.k.keyType() } +func (e *kemEncryptor) EphemeralKey() []byte { return nil } + +func (e *kemEncryptor) Metadata() (map[string]string, error) { + return make(map[string]string), nil +} + +// kemDecryptor satisfies PrivateKeyDecryptor for every KEM family. It replaces +// the per-variant MLKEMDecryptor*, XWingDecryptor, and HybridNISTDecryptor +// types behind the FromPrivatePEM factory. +type kemDecryptor struct { + k kem + privateKey []byte + salt []byte + info []byte +} + +func newKEMDecryptor(k kem, privateKey, salt, info []byte) (*kemDecryptor, error) { + // A negative privSize signals a variable-length encoding (the NIST hybrid's + // mlkemSeed||ECPrivateKey DER), whose exact length is validated inside + // decapsulate. Fixed-size schemes (ML-KEM, X-Wing) keep the strict check. + if k.privSize() >= 0 && len(privateKey) != k.privSize() { + return nil, fmt.Errorf("invalid %s private key size: got %d want %d", k.keyType(), len(privateKey), k.privSize()) + } + return &kemDecryptor{ + k: k, + privateKey: append([]byte(nil), privateKey...), + salt: cloneOrNil(salt), + info: cloneOrNil(info), + }, nil +} + +func (d *kemDecryptor) Decrypt(data []byte) ([]byte, error) { + return unwrapDEKWithKEM(d.k, d.privateKey, data, d.salt, d.info) +} + +// KeyType reports the KEM scheme this decryptor was built for. It lets callers +// (e.g. the service layer's assertDecryptorAlgorithm guard) confirm that a PEM +// dispatched to the scheme they expected. +func (d *kemDecryptor) KeyType() KeyType { return d.k.keyType() } diff --git a/lib/ocrypto/key_type.go b/lib/ocrypto/key_type.go new file mode 100644 index 0000000000..cb050c7ff0 --- /dev/null +++ b/lib/ocrypto/key_type.go @@ -0,0 +1,85 @@ +package ocrypto + +import "fmt" + +type KeyType string + +const ( + RSA2048Key KeyType = "rsa:2048" + RSA4096Key KeyType = "rsa:4096" + EC256Key KeyType = "ec:secp256r1" + EC384Key KeyType = "ec:secp384r1" + EC521Key KeyType = "ec:secp521r1" + MLKEM768Key KeyType = "mlkem:768" + MLKEM1024Key KeyType = "mlkem:1024" +) + +const ( + RSA2048Size = 2048 + RSA4096Size = 4096 +) + +// 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, + MLKEM768Key, MLKEM1024Key, + HybridXWingKey, HybridSecp256r1MLKEM768Key, HybridSecp384r1MLKEM1024Key: + return KeyType(alg), nil + default: + return "", fmt.Errorf("unrecognized key type: %s", alg) + } +} + +func IsECKeyType(kt KeyType) bool { + switch kt { //nolint:exhaustive // only handle ec types + case EC256Key, EC384Key, EC521Key: + return true + default: + return false + } +} + +func IsRSAKeyType(kt KeyType) bool { + switch kt { //nolint:exhaustive // only handle rsa types + case RSA2048Key, RSA4096Key: + return true + default: + return false + } +} + +func IsMLKEMKeyType(kt KeyType) bool { + switch kt { //nolint:exhaustive // only handle mlkem types + case MLKEM768Key, MLKEM1024Key: + return true + default: + return false + } +} + +func ECKeyTypeToMode(kt KeyType) (ECCMode, error) { + switch kt { //nolint:exhaustive // only handle ec types + case EC256Key: + return ECCModeSecp256r1, nil + case EC384Key: + return ECCModeSecp384r1, nil + case EC521Key: + return ECCModeSecp521r1, nil + default: + return 0, fmt.Errorf("unsupported type: %v", kt) + } +} + +func RSAKeyTypeToBits(kt KeyType) (int, error) { + switch kt { //nolint:exhaustive // only handle rsa types + case RSA2048Key: + return RSA2048Size, nil + case RSA4096Key: + return RSA4096Size, nil + default: + return 0, fmt.Errorf("unsupported type: %v", kt) + } +} diff --git a/lib/ocrypto/mlkem.go b/lib/ocrypto/mlkem.go new file mode 100644 index 0000000000..eccb2431bf --- /dev/null +++ b/lib/ocrypto/mlkem.go @@ -0,0 +1,106 @@ +package ocrypto + +import ( + "encoding/asn1" + "errors" + "fmt" +) + +// errNotKEM is returned by the generic SPKI / PKCS#8 KEM parsers when the +// supplied DER blob is not a recognised KEM algorithm, signalling the caller +// to fall through to other algorithm parsers. +var errNotKEM = errors.New("not a recognised KEM key") + +const ( + MLKEM768PublicKeySize = 1184 // mlkem768 encapsulation key + MLKEM768PrivateKeySize = 64 // mlkem768 seed (d || z) + MLKEM768CiphertextSize = 1088 // mlkem768 ciphertext + MLKEM1024PublicKeySize = 1568 // mlkem1024 encapsulation key + MLKEM1024PrivateKeySize = 64 // mlkem1024 seed (d || z) + MLKEM1024CiphertextSize = 1568 // mlkem1024 ciphertext +) + +// NIST-assigned OIDs for ML-KEM (FIPS 203). +var ( + OIDMLKEM768 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 4, 2} + OIDMLKEM1024 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 4, 3} +) + +type kemAlgorithmIdentifier struct { + Algorithm asn1.ObjectIdentifier +} + +type kemSPKI struct { + Algorithm kemAlgorithmIdentifier + PublicKey asn1.BitString +} + +// kemPKCS8 mirrors RFC 5958 OneAsymmetricKey v1. +type kemPKCS8 struct { + Version int + Algorithm kemAlgorithmIdentifier + PrivateKey []byte +} + +// marshalKEMPublicSPKI encodes a raw KEM encapsulation key as RFC 5280 +// SubjectPublicKeyInfo using the supplied algorithm OID. +func marshalKEMPublicSPKI(oid asn1.ObjectIdentifier, rawKey []byte) ([]byte, error) { + return asn1.Marshal(kemSPKI{ + Algorithm: kemAlgorithmIdentifier{Algorithm: oid}, + PublicKey: asn1.BitString{Bytes: rawKey, BitLength: len(rawKey) * bitsPerByte}, + }) +} + +// marshalKEMPrivatePKCS8 encodes a raw KEM seed (or private key) as RFC 5958 +// OneAsymmetricKey, with the inner KEM-PrivateKey CHOICE selected as [0] +// IMPLICIT OCTET STRING. +func marshalKEMPrivatePKCS8(oid asn1.ObjectIdentifier, rawSeedOrKey []byte) ([]byte, error) { + inner, err := asn1.MarshalWithParams(rawSeedOrKey, "tag:0,implicit") + if err != nil { + return nil, fmt.Errorf("asn1.MarshalWithParams seed failed: %w", err) + } + return asn1.Marshal(kemPKCS8{ + Version: 0, + Algorithm: kemAlgorithmIdentifier{Algorithm: oid}, + PrivateKey: inner, + }) +} + +// ParseKEMPublicSPKI returns the OID and raw encapsulation key bytes from any +// SPKI DER blob whose AlgorithmIdentifier has no parameters. If the blob is +// not a well-formed parameter-less SPKI structure the sentinel errNotKEM is +// returned so the caller can fall through to other parsers. +func ParseKEMPublicSPKI(der []byte) (asn1.ObjectIdentifier, []byte, error) { + var s kemSPKI + rest, err := asn1.Unmarshal(der, &s) + if err != nil || len(rest) != 0 { + return nil, nil, errNotKEM + } + if s.PublicKey.BitLength%bitsPerByte != 0 { + return nil, nil, errors.New("KEM SPKI bit string is not byte-aligned") + } + return s.Algorithm.Algorithm, s.PublicKey.RightAlign(), nil +} + +// parseKEMPrivatePKCS8 returns the OID and raw seed bytes from any PKCS#8 DER +// blob whose AlgorithmIdentifier matches a registered KEM scheme and whose +// inner private key is encoded as [0] IMPLICIT OCTET STRING. The sentinel +// errNotKEM is returned for any non-KEM PKCS#8 blob so the caller can fall +// through to other parsers. +func parseKEMPrivatePKCS8(der []byte) (asn1.ObjectIdentifier, []byte, error) { + var p kemPKCS8 + rest, err := asn1.Unmarshal(der, &p) + if err != nil || len(rest) != 0 { + return nil, nil, errNotKEM + } + if _, ok := kemRegistry[p.Algorithm.Algorithm.String()]; !ok { + return nil, nil, errNotKEM + } + + var innerSeed []byte + innerRest, err := asn1.UnmarshalWithParams(p.PrivateKey, &innerSeed, "tag:0,implicit") + if err != nil || len(innerRest) != 0 { + return nil, nil, fmt.Errorf("KEM PKCS#8 inner seed parse failed: %w", err) + } + return p.Algorithm.Algorithm, innerSeed, nil +} diff --git a/lib/ocrypto/mlkem_test.go b/lib/ocrypto/mlkem_test.go new file mode 100644 index 0000000000..355317e33b --- /dev/null +++ b/lib/ocrypto/mlkem_test.go @@ -0,0 +1,355 @@ +package ocrypto + +import ( + "encoding/asn1" + "encoding/pem" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMLKEM768WrapUnwrapRoundTrip(t *testing.T) { + keyPair, err := NewMLKEMKeyPair() + require.NoError(t, err) + + publicKeyBytes := keyPair.PrivateKey.EncapsulationKey().Bytes() + privateKeyBytes := keyPair.PrivateKey.Bytes() + + dek := []byte("0123456789abcdef0123456789abcdef") + wrapped, err := wrapDEKWithKEM(mlkemKEM{variant: mlkem768}, publicKeyBytes, dek, nil, nil) + require.NoError(t, err) + + plaintext, err := unwrapDEKWithKEM(mlkemKEM{variant: mlkem768}, privateKeyBytes, wrapped, nil, nil) + require.NoError(t, err) + assert.Equal(t, dek, plaintext) +} + +func TestMLKEM1024WrapUnwrapRoundTrip(t *testing.T) { + keyPair, err := NewMLKEM1024KeyPair() + require.NoError(t, err) + + publicKeyBytes := keyPair.PrivateKey.EncapsulationKey().Bytes() + privateKeyBytes := keyPair.PrivateKey.Bytes() + + dek := []byte("0123456789abcdef0123456789abcdef") + wrapped, err := wrapDEKWithKEM(mlkemKEM{variant: mlkem1024}, publicKeyBytes, dek, nil, nil) + require.NoError(t, err) + + plaintext, err := unwrapDEKWithKEM(mlkemKEM{variant: mlkem1024}, privateKeyBytes, wrapped, nil, nil) + require.NoError(t, err) + assert.Equal(t, dek, plaintext) +} + +func TestMLKEM768WrapUnwrapWrongKeyFails(t *testing.T) { + keyPair1, err := NewMLKEMKeyPair() + require.NoError(t, err) + keyPair2, err := NewMLKEMKeyPair() + require.NoError(t, err) + + publicKey1 := keyPair1.PrivateKey.EncapsulationKey().Bytes() + privateKey2 := keyPair2.PrivateKey.Bytes() + + wrapped, err := wrapDEKWithKEM(mlkemKEM{variant: mlkem768}, publicKey1, []byte("top secret dek"), nil, nil) + require.NoError(t, err) + + _, err = unwrapDEKWithKEM(mlkemKEM{variant: mlkem768}, privateKey2, wrapped, nil, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "AES-GCM decrypt failed") +} + +func TestMLKEM1024WrapUnwrapWrongKeyFails(t *testing.T) { + keyPair1, err := NewMLKEM1024KeyPair() + require.NoError(t, err) + keyPair2, err := NewMLKEM1024KeyPair() + require.NoError(t, err) + + publicKey1 := keyPair1.PrivateKey.EncapsulationKey().Bytes() + privateKey2 := keyPair2.PrivateKey.Bytes() + + wrapped, err := wrapDEKWithKEM(mlkemKEM{variant: mlkem1024}, publicKey1, []byte("top secret dek"), nil, nil) + require.NoError(t, err) + + _, err = unwrapDEKWithKEM(mlkemKEM{variant: mlkem1024}, privateKey2, wrapped, nil, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "AES-GCM decrypt failed") +} + +func TestKEMEnvelopeASN1RoundTrip(t *testing.T) { + original := kemEnvelope{ + KEMCiphertext: []byte("ciphertext"), + EncryptedDEK: []byte("encrypted-dek"), + } + + der, err := asn1.Marshal(original) + require.NoError(t, err) + + var decoded kemEnvelope + rest, err := asn1.Unmarshal(der, &decoded) + require.NoError(t, err) + assert.Empty(t, rest) + assert.Equal(t, original, decoded) +} + +func TestMLKEM768CiphertextSizeValidation(t *testing.T) { + keyPair, err := NewMLKEMKeyPair() + require.NoError(t, err) + + privateKeyBytes := keyPair.PrivateKey.Bytes() + + invalidWrapped := kemEnvelope{ + KEMCiphertext: []byte("too-short"), + EncryptedDEK: []byte("encrypted-dek"), + } + + der, err := asn1.Marshal(invalidWrapped) + require.NoError(t, err) + + _, err = unwrapDEKWithKEM(mlkemKEM{variant: mlkem768}, privateKeyBytes, der, nil, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "ciphertext size") +} + +func TestMLKEM1024CiphertextSizeValidation(t *testing.T) { + keyPair, err := NewMLKEM1024KeyPair() + require.NoError(t, err) + + privateKeyBytes := keyPair.PrivateKey.Bytes() + + invalidWrapped := kemEnvelope{ + KEMCiphertext: []byte("too-short"), + EncryptedDEK: []byte("encrypted-dek"), + } + + der, err := asn1.Marshal(invalidWrapped) + require.NoError(t, err) + + _, err = unwrapDEKWithKEM(mlkemKEM{variant: mlkem1024}, privateKeyBytes, der, nil, nil) + require.Error(t, err) + assert.Contains(t, err.Error(), "ciphertext size") +} + +// TestMLKEMSaltInfoIgnored verifies that salt/info passed to the ML-KEM +// encryptor/decryptor are ignored: an envelope produced with one (salt, info) +// pair must unwrap correctly under a different (salt, info) pair, because pure +// ML-KEM uses the Decaps shared secret directly as the AES-GCM wrap key. +func TestMLKEMSaltInfoIgnored(t *testing.T) { + keyPair, err := NewMLKEMKeyPair() + require.NoError(t, err) + + publicKeyBytes := keyPair.PrivateKey.EncapsulationKey().Bytes() + privateKeyBytes := keyPair.PrivateKey.Bytes() + + encryptor, err := newKEMEncryptor(mlkemKEM{variant: mlkem768}, publicKeyBytes, []byte("salt-A"), []byte("info-A")) + require.NoError(t, err) + + // Decrypt with deliberately different salt/info; for ML-KEM both must be no-ops. + decryptor, err := newKEMDecryptor(mlkemKEM{variant: mlkem768}, privateKeyBytes, []byte("salt-B"), []byte("info-B")) + require.NoError(t, err) + + dek := []byte("test-dek-value-123456") + wrapped, err := encryptor.Encrypt(dek) + require.NoError(t, err) + + plaintext, err := decryptor.Decrypt(wrapped) + require.NoError(t, err) + assert.Equal(t, dek, plaintext) + + // Also decrypt with nil salt/info to make the contract explicit. + bareDecryptor, err := newKEMDecryptor(mlkemKEM{variant: mlkem768}, privateKeyBytes, nil, nil) + require.NoError(t, err) + plaintextBare, err := bareDecryptor.Decrypt(wrapped) + require.NoError(t, err) + assert.Equal(t, dek, plaintextBare) +} + +// TestMLKEMSharedSecretIsAESWrapKey verifies that the AES-GCM-encrypted DEK +// inside an ML-KEM envelope can be opened by AES-256-GCM using the raw 32-byte +// shared secret produced by Decaps — i.e. there is no KDF between Decaps and +// the AES-GCM unwrap key. This is the load-bearing assertion for HSM-backed +// KAS providers that can only materialize the Decaps output as a non- +// extractable CKK_AES object. +func TestMLKEMSharedSecretIsAESWrapKey(t *testing.T) { + t.Run("MLKEM768", func(t *testing.T) { + assertSharedSecretIsAESWrapKey(t, mlkemKEM{variant: mlkem768}, MLKEM768CiphertextSize) + }) + t.Run("MLKEM1024", func(t *testing.T) { + assertSharedSecretIsAESWrapKey(t, mlkemKEM{variant: mlkem1024}, MLKEM1024CiphertextSize) + }) +} + +func assertSharedSecretIsAESWrapKey(t *testing.T, k mlkemKEM, expectedCtSize int) { + t.Helper() + + var ( + pubBytes []byte + privBytes []byte + ) + if k.variant == mlkem1024 { + kp, err := NewMLKEM1024KeyPair() + require.NoError(t, err) + pubBytes = kp.PrivateKey.EncapsulationKey().Bytes() + privBytes = kp.PrivateKey.Bytes() + } else { + kp, err := NewMLKEMKeyPair() + require.NoError(t, err) + pubBytes = kp.PrivateKey.EncapsulationKey().Bytes() + privBytes = kp.PrivateKey.Bytes() + } + + dek := []byte("0123456789abcdef0123456789abcdef") // 32-byte DEK + wrappedDER, err := wrapDEKWithKEM(k, pubBytes, dek, nil, nil) + require.NoError(t, err) + + // Parse the envelope to pull out the KEM ciphertext and the + // AES-GCM-wrapped DEK independently of unwrapDEKWithKEM. + var env kemEnvelope + rest, err := asn1.Unmarshal(wrappedDER, &env) + require.NoError(t, err) + require.Empty(t, rest) + require.Len(t, env.KEMCiphertext, expectedCtSize) + + // Reproduce the wrap key the way an HSM-backed KAS would: Decaps then + // straight into AES-256-GCM, no KDF. + sharedSecret, err := k.decapsulate(privBytes, env.KEMCiphertext) + require.NoError(t, err) + require.Len(t, sharedSecret, kemWrapKeySize, "FIPS 203 §6.3/§7.3 mandates a 32-byte shared secret") + + gcm, err := NewAESGcm(sharedSecret) + require.NoError(t, err) + plaintext, err := gcm.Decrypt(env.EncryptedDEK) + require.NoError(t, err) + assert.Equal(t, dek, plaintext, "AES-GCM with sharedSecret-as-key must recover the DEK") + + // Also verify the symmetric direction: an AES-GCM seal under the shared + // secret must be openable by unwrapDEKWithKEM-style code, i.e. the wrap + // key on both sides is exactly the Decaps output. + sealed, err := gcm.Encrypt(dek) + require.NoError(t, err) + roundTrip, err := gcm.Decrypt(sealed) + require.NoError(t, err) + assert.Equal(t, dek, roundTrip) +} + +func TestMLKEMEncryptorImplementsInterface(t *testing.T) { + keyPair, err := NewMLKEMKeyPair() + require.NoError(t, err) + + publicKeyBytes := keyPair.PrivateKey.EncapsulationKey().Bytes() + + encryptor, err := newKEMEncryptor(mlkemKEM{variant: mlkem768}, publicKeyBytes, nil, nil) + require.NoError(t, err) + + assert.Equal(t, MLKEM, encryptor.Type()) + assert.Equal(t, MLKEM768Key, encryptor.KeyType()) + assert.Nil(t, encryptor.EphemeralKey()) + + metadata, err := encryptor.Metadata() + require.NoError(t, err) + assert.Empty(t, metadata) +} + +func TestMLKEM768Encapsulate(t *testing.T) { + keyPair, err := NewMLKEMKeyPair() + require.NoError(t, err) + + publicKeyBytes := keyPair.PrivateKey.EncapsulationKey().Bytes() + + sharedSecret, ciphertext, err := mlkemKEM{variant: mlkem768}.encapsulate(publicKeyBytes) + require.NoError(t, err) + assert.Len(t, sharedSecret, 32) + assert.Len(t, ciphertext, MLKEM768CiphertextSize) +} + +func TestMLKEM1024Encapsulate(t *testing.T) { + keyPair, err := NewMLKEM1024KeyPair() + require.NoError(t, err) + + publicKeyBytes := keyPair.PrivateKey.EncapsulationKey().Bytes() + + sharedSecret, ciphertext, err := mlkemKEM{variant: mlkem1024}.encapsulate(publicKeyBytes) + require.NoError(t, err) + assert.Len(t, sharedSecret, 32) + assert.Len(t, ciphertext, MLKEM1024CiphertextSize) +} + +func TestMLKEM768EncapsulateInvalidKeySize(t *testing.T) { + _, _, err := mlkemKEM{variant: mlkem768}.encapsulate([]byte("too-short")) + require.Error(t, err) + assert.Contains(t, err.Error(), "public key size") +} + +func TestMLKEM1024EncapsulateInvalidKeySize(t *testing.T) { + _, _, err := mlkemKEM{variant: mlkem1024}.encapsulate([]byte("too-short")) + require.Error(t, err) + assert.Contains(t, err.Error(), "public key size") +} + +func TestMLKEM768PEMRoundTrip(t *testing.T) { + keyPair, err := NewMLKEMKeyPair() + require.NoError(t, err) + + pubPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + assert.True(t, strings.HasPrefix(pubPEM, "-----BEGIN PUBLIC KEY-----")) + pubBlock, _ := pem.Decode([]byte(pubPEM)) + require.NotNil(t, pubBlock) + assert.Equal(t, "PUBLIC KEY", pubBlock.Type) + + privPEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + assert.True(t, strings.HasPrefix(privPEM, "-----BEGIN PRIVATE KEY-----")) + privBlock, _ := pem.Decode([]byte(privPEM)) + require.NotNil(t, privBlock) + assert.Equal(t, "PRIVATE KEY", privBlock.Type) + + enc, err := FromPublicPEM(pubPEM) + require.NoError(t, err) + assert.Equal(t, MLKEM, enc.Type()) + assert.Equal(t, MLKEM768Key, enc.KeyType()) + + dek := []byte("ml-kem-768 round-trip data") + wrapped, err := enc.Encrypt(dek) + require.NoError(t, err) + + dec, err := FromPrivatePEM(privPEM) + require.NoError(t, err) + plaintext, err := dec.Decrypt(wrapped) + require.NoError(t, err) + assert.Equal(t, dek, plaintext) +} + +func TestMLKEM1024PEMRoundTrip(t *testing.T) { + keyPair, err := NewMLKEM1024KeyPair() + require.NoError(t, err) + + pubPEM, err := keyPair.PublicKeyInPemFormat() + require.NoError(t, err) + assert.True(t, strings.HasPrefix(pubPEM, "-----BEGIN PUBLIC KEY-----")) + pubBlock, _ := pem.Decode([]byte(pubPEM)) + require.NotNil(t, pubBlock) + assert.Equal(t, "PUBLIC KEY", pubBlock.Type) + + privPEM, err := keyPair.PrivateKeyInPemFormat() + require.NoError(t, err) + assert.True(t, strings.HasPrefix(privPEM, "-----BEGIN PRIVATE KEY-----")) + privBlock, _ := pem.Decode([]byte(privPEM)) + require.NotNil(t, privBlock) + assert.Equal(t, "PRIVATE KEY", privBlock.Type) + + enc, err := FromPublicPEM(pubPEM) + require.NoError(t, err) + assert.Equal(t, MLKEM, enc.Type()) + assert.Equal(t, MLKEM1024Key, enc.KeyType()) + + dek := []byte("ml-kem-1024 round-trip data") + wrapped, err := enc.Encrypt(dek) + require.NoError(t, err) + + dec, err := FromPrivatePEM(privPEM) + require.NoError(t, err) + plaintext, err := dec.Decrypt(wrapped) + require.NoError(t, err) + assert.Equal(t, dek, plaintext) +} diff --git a/lib/ocrypto/xwing.go b/lib/ocrypto/xwing.go index b92c93fecb..3358522600 100644 --- a/lib/ocrypto/xwing.go +++ b/lib/ocrypto/xwing.go @@ -3,7 +3,6 @@ package ocrypto import ( "crypto/rand" "crypto/sha256" - "encoding/asn1" "encoding/pem" "fmt" "io" @@ -32,31 +31,11 @@ const ( // TODO(DSPX-TBD): swap the primitive to a draft-10 implementation once one // is available in Go (tracking: upgrade cloudflare/circl xwing to draft-10). -// XWingWrappedKey is the ASN.1 envelope stored in wrapped_key. The X-Wing -// drafts define only the KEM; this DEK wrapping envelope is local to OpenTDF -// and unchanged across draft revisions. -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 { @@ -94,78 +73,37 @@ func (k XWingKeyPair) GetKeyType() KeyType { return HybridXWingKey } -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) - } +// xwingKEM adapts the X-Wing KEM onto the shared kem interface so it flows +// through wrapDEKWithKEM / unwrapDEKWithKEM and the single kemEnvelope wire +// format, alongside pure ML-KEM and the NIST composite hybrids. +type xwingKEM struct{} - return &XWingEncryptor{ - publicKey: append([]byte(nil), publicKey...), - salt: cloneOrNil(salt), - info: cloneOrNil(info), - }, nil +func (xwingKEM) keyType() KeyType { return HybridXWingKey } +func (xwingKEM) scheme() SchemeType { return Hybrid } +func (xwingKEM) pubSize() int { return XWingPublicKeySize } +func (xwingKEM) privSize() int { return XWingPrivateKeySize } +func (xwingKEM) ctSize() int { return XWingCiphertextSize } + +func (xwingKEM) encapsulate(pub []byte) ([]byte, []byte, error) { + return XWingEncapsulate(pub) } -func (e *XWingEncryptor) Encrypt(data []byte) ([]byte, error) { - return xwingWrapDEK(e.publicKey, data, e.salt, e.info) +func (xwingKEM) decapsulate(priv, ct []byte) ([]byte, error) { + return xwing.Decapsulate(ct, priv), nil } -func (e *XWingEncryptor) PublicKeyInPemFormat() (string, error) { - der, err := marshalHybridSPKI(oidXWing, e.publicKey) +func (xwingKEM) publicKeyPEM(pub []byte) (string, error) { + der, err := marshalHybridSPKI(oidXWing, pub) if err != nil { return "", err } return string(pem.EncodeToMemory(&pem.Block{Type: pemBlockPublicKey, Bytes: der})), nil } -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) -} - -// KeyType identifies the hybrid scheme so KAS-layer callers can cross-check -// the OID-routed decryptor against an asserted algorithm before trusting it. -func (d *XWingDecryptor) KeyType() KeyType { - return HybridXWingKey -} - -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) +// wrapKey derives the AES-256 wrap key from the X-Wing shared secret via +// HKDF-SHA256 over (salt, info), per the hybrid PQ/T combiner-hygiene contract. +func (xwingKEM) wrapKey(sharedSecret, salt, info []byte) ([]byte, error) { + return deriveXWingWrapKey(sharedSecret, salt, info) } // XWingEncapsulate performs the X-Wing KEM encapsulation, returning the shared @@ -183,75 +121,6 @@ func XWingEncapsulate(publicKeyRaw []byte) ([]byte, []byte, error) { 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() diff --git a/lib/ocrypto/xwing_test.go b/lib/ocrypto/xwing_test.go index 1eea9c4e8c..9a5bed1935 100644 --- a/lib/ocrypto/xwing_test.go +++ b/lib/ocrypto/xwing_test.go @@ -1,7 +1,6 @@ package ocrypto import ( - "encoding/asn1" "encoding/pem" "testing" @@ -45,10 +44,10 @@ func TestXWingWrapUnwrapRoundTrip(t *testing.T) { require.NoError(t, err) dek := []byte("0123456789abcdef0123456789abcdef") - wrapped, err := XWingWrapDEK(keyPair.publicKey, dek) + wrapped, err := wrapDEKWithKEM(xwingKEM{}, keyPair.publicKey, dek, nil, nil) require.NoError(t, err) - plaintext, err := XWingUnwrapDEK(keyPair.privateKey, wrapped) + plaintext, err := unwrapDEKWithKEM(xwingKEM{}, keyPair.privateKey, wrapped, nil, nil) require.NoError(t, err) assert.Equal(t, dek, plaintext) } @@ -59,30 +58,14 @@ func TestXWingWrapUnwrapWrongKeyFails(t *testing.T) { wrongKeyPair, err := NewXWingKeyPair() require.NoError(t, err) - wrapped, err := XWingWrapDEK(keyPair.publicKey, []byte("top secret dek")) + wrapped, err := wrapDEKWithKEM(xwingKEM{}, keyPair.publicKey, []byte("top secret dek"), nil, nil) require.NoError(t, err) - _, err = XWingUnwrapDEK(wrongKeyPair.privateKey, wrapped) + _, err = unwrapDEKWithKEM(xwingKEM{}, wrongKeyPair.privateKey, wrapped, nil, nil) 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) @@ -98,7 +81,7 @@ func TestXWingPEMDispatch(t *testing.T) { decryptor, err := FromPrivatePEMWithSalt(privatePEM, []byte("salt"), []byte("info")) require.NoError(t, err) - xwingEncryptor, ok := encryptor.(*XWingEncryptor) + xwingEncryptor, ok := encryptor.(*kemEncryptor) require.True(t, ok) assert.Equal(t, Hybrid, xwingEncryptor.Type()) assert.Equal(t, HybridXWingKey, xwingEncryptor.KeyType()) @@ -108,7 +91,7 @@ func TestXWingPEMDispatch(t *testing.T) { require.NoError(t, err) assert.Empty(t, metadata) - xwingDecryptor, ok := decryptor.(*XWingDecryptor) + xwingDecryptor, ok := decryptor.(*kemDecryptor) require.True(t, ok) wrapped, err := xwingEncryptor.Encrypt([]byte("dispatch-dek")) diff --git a/otdfctl/cmd/policy/kasKeys.go b/otdfctl/cmd/policy/kasKeys.go index b9e4640db8..bdf692d68a 100644 --- a/otdfctl/cmd/policy/kasKeys.go +++ b/otdfctl/cmd/policy/kasKeys.go @@ -89,6 +89,10 @@ func generateKeyPair(alg policy.Algorithm) (ocrypto.KeyPair, error) { key, err = ocrypto.NewKeyPair(ocrypto.HybridSecp256r1MLKEM768Key) case policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024: key, err = ocrypto.NewKeyPair(ocrypto.HybridSecp384r1MLKEM1024Key) + case policy.Algorithm_ALGORITHM_MLKEM_768: + key, err = ocrypto.NewKeyPair(ocrypto.MLKEM768Key) + case policy.Algorithm_ALGORITHM_MLKEM_1024: + key, err = ocrypto.NewKeyPair(ocrypto.MLKEM1024Key) case policy.Algorithm_ALGORITHM_UNSPECIFIED: fallthrough default: diff --git a/otdfctl/docs/man/policy/kas-registry/key/create.md b/otdfctl/docs/man/policy/kas-registry/key/create.md index 76f5a85af9..86d5bc6205 100644 --- a/otdfctl/docs/man/policy/kas-registry/key/create.md +++ b/otdfctl/docs/man/policy/kas-registry/key/create.md @@ -75,9 +75,11 @@ 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` | + | `hpqt:xwing` | + | `mlkem:768` | + | `mlkem:1024` | 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 b08a2ea843..cd4c012f94 100644 --- a/otdfctl/docs/man/policy/kas-registry/key/import.md +++ b/otdfctl/docs/man/policy/kas-registry/key/import.md @@ -79,6 +79,8 @@ 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` | + | `hpqt:xwing` | + | `mlkem:768` | + | `mlkem:1024` | diff --git a/otdfctl/docs/man/policy/kas-registry/key/rotate.md b/otdfctl/docs/man/policy/kas-registry/key/rotate.md index 726d85f74b..5f65e2cdca 100644 --- a/otdfctl/docs/man/policy/kas-registry/key/rotate.md +++ b/otdfctl/docs/man/policy/kas-registry/key/rotate.md @@ -88,9 +88,11 @@ 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` | + | `hpqt:xwing` | + | `mlkem:768` | + | `mlkem:1024` | 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/e2e/kas-keys.bats b/otdfctl/e2e/kas-keys.bats index 44cf4359bb..8b813a4268 100644 --- a/otdfctl/e2e/kas-keys.bats +++ b/otdfctl/e2e/kas-keys.bats @@ -191,6 +191,48 @@ format_kas_name_as_uri() { assert_not_equal "$(echo "$output" | jq -r .key.metadata.updated_at)" "null" } +@test "kas-keys: create key (local mode, mlkem:768)" { + KEY_ID=$(generate_key_id) + # Local mode: otdfctl generates the ML-KEM keypair internally, so we assert + # the public key is present/non-empty rather than matching a known value. + run_otdfctl_key create --kas "${KAS_REGISTRY_ID}" --key-id "${KEY_ID}" --algorithm "mlkem:768" --mode "local" --wrapping-key-id "wrapping-key-1" --wrapping-key "${WRAPPING_KEY}" --json + assert_success + assert_equal "$(echo "$output" | jq -r .kas_id)" "${KAS_REGISTRY_ID}" + assert_equal "$(echo "$output" | jq -r .key.key_id)" "${KEY_ID}" + assert_equal "$(echo "$output" | jq -r .key.key_algorithm)" "20" # mlkem:768 + assert_equal "$(echo "$output" | jq -r .key.key_mode)" "1" # local + assert_equal "$(echo "$output" | jq -r .key.key_status)" "1" # active + assert_equal "$(echo "$output" | jq -r .key.legacy)" "null" + assert_not_equal "$(echo "$output" | jq -r .key.public_key_ctx.pem)" "null" + assert_not_equal "$(echo "$output" | jq -r .key.public_key_ctx.pem)" "" + assert_equal "$(echo "$output" | jq -r .key.private_key_ctx.key_id)" "wrapping-key-1" + assert_not_equal "$(echo "$output" | jq -r .key.private_key_ctx.wrapped_key)" "null" + assert_not_equal "$(echo "$output" | jq -r .key.private_key_ctx.wrapped_key)" "" + assert_not_equal "$(echo "$output" | jq -r .key.metadata.created_at)" "null" + assert_not_equal "$(echo "$output" | jq -r .key.metadata.updated_at)" "null" +} + +@test "kas-keys: create key (local mode, mlkem:1024)" { + KEY_ID=$(generate_key_id) + # Local mode: otdfctl generates the ML-KEM keypair internally, so we assert + # the public key is present/non-empty rather than matching a known value. + run_otdfctl_key create --kas "${KAS_REGISTRY_ID}" --key-id "${KEY_ID}" --algorithm "mlkem:1024" --mode "local" --wrapping-key-id "wrapping-key-1" --wrapping-key "${WRAPPING_KEY}" --json + assert_success + assert_equal "$(echo "$output" | jq -r .kas_id)" "${KAS_REGISTRY_ID}" + assert_equal "$(echo "$output" | jq -r .key.key_id)" "${KEY_ID}" + assert_equal "$(echo "$output" | jq -r .key.key_algorithm)" "21" # mlkem:1024 + assert_equal "$(echo "$output" | jq -r .key.key_mode)" "1" # local + assert_equal "$(echo "$output" | jq -r .key.key_status)" "1" # active + assert_equal "$(echo "$output" | jq -r .key.legacy)" "null" + assert_not_equal "$(echo "$output" | jq -r .key.public_key_ctx.pem)" "null" + assert_not_equal "$(echo "$output" | jq -r .key.public_key_ctx.pem)" "" + assert_equal "$(echo "$output" | jq -r .key.private_key_ctx.key_id)" "wrapping-key-1" + assert_not_equal "$(echo "$output" | jq -r .key.private_key_ctx.wrapped_key)" "null" + assert_not_equal "$(echo "$output" | jq -r .key.private_key_ctx.wrapped_key)" "" + assert_not_equal "$(echo "$output" | jq -r .key.metadata.created_at)" "null" + assert_not_equal "$(echo "$output" | jq -r .key.metadata.updated_at)" "null" +} + @test "kas-keys: create key (public_key mode)" { KEY_ID=$(generate_key_id) run_otdfctl_key create --kas "${KAS_REGISTRY_ID}" --key-id "${KEY_ID}" --algorithm "rsa:2048" --mode "public_key" --public-key-pem "${PEM_B64}" --json diff --git a/otdfctl/pkg/cli/sdkHelpers.go b/otdfctl/pkg/cli/sdkHelpers.go index 943fbbc71e..d88651afa0 100644 --- a/otdfctl/pkg/cli/sdkHelpers.go +++ b/otdfctl/pkg/cli/sdkHelpers.go @@ -125,12 +125,16 @@ 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 + case "hpqt:xwing": + return policy.Algorithm_ALGORITHM_HPQT_XWING, nil + case "mlkem:768": + return policy.Algorithm_ALGORITHM_MLKEM_768, nil + case "mlkem:1024": + return policy.Algorithm_ALGORITHM_MLKEM_1024, nil default: return policy.Algorithm_ALGORITHM_UNSPECIFIED, errors.New("invalid algorithm") } @@ -148,12 +152,16 @@ 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 + case policy.Algorithm_ALGORITHM_HPQT_XWING: + return "hpqt:xwing", nil + case policy.Algorithm_ALGORITHM_MLKEM_768: + return "mlkem:768", nil + case policy.Algorithm_ALGORITHM_MLKEM_1024: + return "mlkem:1024", nil default: return "", errors.New("invalid enum algorithm") } diff --git a/otdfctl/pkg/utils/pemvalidate.go b/otdfctl/pkg/utils/pemvalidate.go index 29fc17f8ef..ab4bda79fb 100644 --- a/otdfctl/pkg/utils/pemvalidate.go +++ b/otdfctl/pkg/utils/pemvalidate.go @@ -41,10 +41,6 @@ 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") @@ -53,6 +49,18 @@ func ValidatePublicKeyPEM(pemBytes []byte, expected policy.Algorithm) error { if enc.KeyType() != ocrypto.HybridSecp384r1MLKEM1024Key { return errors.New("algorithm mismatch: expected hybrid NIST P-384 + ML-KEM-1024") } + 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_MLKEM_768: + if enc.KeyType() != ocrypto.MLKEM768Key { + return errors.New("algorithm mismatch: expected ML-KEM-768") + } + case policy.Algorithm_ALGORITHM_MLKEM_1024: + if enc.KeyType() != ocrypto.MLKEM1024Key { + return errors.New("algorithm mismatch: expected ML-KEM-1024") + } case policy.Algorithm_ALGORITHM_UNSPECIFIED: fallthrough default: 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 c3cd4c7dc2..61fe847fae 100644 --- a/protocol/go/policy/kasregistry/key_access_server_registry.pb.go +++ b/protocol/go/policy/kasregistry/key_access_server_registry.pb.go @@ -4297,560 +4297,562 @@ var file_policy_kasregistry_key_access_server_registry_proto_rawDesc = []byte{ 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, 0x3a, 0x02, 0x18, 0x01, 0x22, 0xd5, 0x0c, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, + 0x69, 0x6f, 0x6e, 0x3a, 0x02, 0x18, 0x01, 0x22, 0xdd, 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, 0xad, 0x01, + 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0xb5, 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, 0x75, 0xba, 0x48, 0x72, 0xba, 0x01, 0x6f, + 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x42, 0x7d, 0xba, 0x48, 0x7a, 0xba, 0x01, 0x77, 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, + 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x1a, 0x28, 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, - 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, 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, + 0x20, 0x34, 0x2c, 0x20, 0x35, 0x2c, 0x20, 0x36, 0x2c, 0x20, 0x37, 0x2c, 0x20, 0x38, 0x2c, 0x20, + 0x32, 0x30, 0x2c, 0x20, 0x32, 0x31, 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, 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, 0x86, 0x04, 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, 0x12, 0x26, 0x0a, 0x06, - 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x06, 0x73, 0x65, - 0x61, 0x72, 0x63, 0x68, 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, 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, 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, + 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, - 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, 0x5f, 0x6b, 0x65, 0x79, 0x2e, - 0x6b, 0x65, 0x79, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x20, 0x7c, 0x7c, + 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, 0x8f, 0x04, 0x0a, + 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0xb9, 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, 0x80, 0x01, 0xba, 0x48, + 0x7d, 0xba, 0x01, 0x7a, 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, 0x2b, 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, 0x2c, 0x20, 0x32, 0x30, 0x2c, 0x20, 0x32, 0x31, 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, 0x12, 0x26, 0x0a, + 0x06, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x06, 0x73, + 0x65, 0x61, 0x72, 0x63, 0x68, 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, 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, 0xf6, 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, 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, 0xe0, 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, 0xae, 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, 0x7d, 0xba, 0x48, 0x7a, 0xba, 0x01, 0x77, 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, 0x28, 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, 0x2c, 0x20, 0x32, 0x30, 0x2c, 0x20, + 0x32, 0x31, 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, 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, 0x33, 0x29, 0x20, 0x26, 0x26, 0x20, + 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, - 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, + 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, 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, 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, + 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, 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, 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, + 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, 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, 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, + 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, + 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, 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, + 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, 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, 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, + 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, 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, 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, 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, + 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, 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, 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, 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, + 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, 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, 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, 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, - 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, + 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, 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 e7f4f3eaed..7ef5a16e4d 100644 --- a/protocol/go/policy/objects.pb.go +++ b/protocol/go/policy/objects.pb.go @@ -364,6 +364,8 @@ const ( 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 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 KasPublicKeyAlgEnum = 20 + KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 KasPublicKeyAlgEnum = 21 ) // Enum value maps for KasPublicKeyAlgEnum. @@ -378,6 +380,8 @@ var ( 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", + 20: "KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768", + 21: "KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024", } KasPublicKeyAlgEnum_value = map[string]int32{ "KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED": 0, @@ -389,6 +393,8 @@ var ( "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, + "KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768": 20, + "KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024": 21, } ) @@ -432,20 +438,24 @@ const ( Algorithm_ALGORITHM_HPQT_XWING Algorithm = 6 Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768 Algorithm = 7 Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024 Algorithm = 8 + Algorithm_ALGORITHM_MLKEM_768 Algorithm = 20 + Algorithm_ALGORITHM_MLKEM_1024 Algorithm = 21 ) // Enum value maps for Algorithm. var ( Algorithm_name = map[int32]string{ - 0: "ALGORITHM_UNSPECIFIED", - 1: "ALGORITHM_RSA_2048", - 2: "ALGORITHM_RSA_4096", - 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", + 0: "ALGORITHM_UNSPECIFIED", + 1: "ALGORITHM_RSA_2048", + 2: "ALGORITHM_RSA_4096", + 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", + 20: "ALGORITHM_MLKEM_768", + 21: "ALGORITHM_MLKEM_1024", } Algorithm_value = map[string]int32{ "ALGORITHM_UNSPECIFIED": 0, @@ -457,6 +467,8 @@ var ( "ALGORITHM_HPQT_XWING": 6, "ALGORITHM_HPQT_SECP256R1_MLKEM768": 7, "ALGORITHM_HPQT_SECP384R1_MLKEM1024": 8, + "ALGORITHM_MLKEM_768": 20, + "ALGORITHM_MLKEM_1024": 21, } ) @@ -4174,7 +4186,7 @@ var file_policy_objects_proto_rawDesc = []byte{ 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 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, 0x9b, + 0x59, 0x50, 0x45, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x02, 0x2a, 0xea, 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, @@ -4200,47 +4212,56 @@ var file_policy_objects_proto_rawDesc = []byte{ 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, + 0x5f, 0x4d, 0x4c, 0x4b, 0x45, 0x4d, 0x31, 0x30, 0x32, 0x34, 0x10, 0x0c, 0x12, 0x25, 0x0a, 0x21, + 0x4b, 0x41, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x41, + 0x4c, 0x47, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x5f, 0x4d, 0x4c, 0x4b, 0x45, 0x4d, 0x5f, 0x37, 0x36, + 0x38, 0x10, 0x14, 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, 0x4d, + 0x4c, 0x4b, 0x45, 0x4d, 0x5f, 0x31, 0x30, 0x32, 0x34, 0x10, 0x15, 0x2a, 0xb7, 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, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, + 0x4d, 0x4c, 0x4b, 0x45, 0x4d, 0x5f, 0x37, 0x36, 0x38, 0x10, 0x14, 0x12, 0x18, 0x0a, 0x14, 0x41, + 0x4c, 0x47, 0x4f, 0x52, 0x49, 0x54, 0x48, 0x4d, 0x5f, 0x4d, 0x4c, 0x4b, 0x45, 0x4d, 0x5f, 0x31, + 0x30, 0x32, 0x34, 0x10, 0x15, 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 96f43f927c..a445729acd 100644 --- a/sdk/basekey.go +++ b/sdk/basekey.go @@ -46,6 +46,10 @@ func KeyTypeToPolicyAlgorithm(kt ocrypto.KeyType) (policy.Algorithm, error) { return policy.Algorithm_ALGORITHM_HPQT_SECP256R1_MLKEM768, nil case ocrypto.HybridSecp384r1MLKEM1024Key: return policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024, nil + case ocrypto.MLKEM768Key: + return policy.Algorithm_ALGORITHM_MLKEM_768, nil + case ocrypto.MLKEM1024Key: + return policy.Algorithm_ALGORITHM_MLKEM_1024, nil default: return policy.Algorithm_ALGORITHM_UNSPECIFIED, fmt.Errorf("unknown key type: %s", kt) } @@ -69,6 +73,10 @@ func PolicyAlgorithmToKeyType(alg policy.Algorithm) (ocrypto.KeyType, error) { return ocrypto.HybridSecp256r1MLKEM768Key, nil case policy.Algorithm_ALGORITHM_HPQT_SECP384R1_MLKEM1024: return ocrypto.HybridSecp384r1MLKEM1024Key, nil + case policy.Algorithm_ALGORITHM_MLKEM_768: + return ocrypto.MLKEM768Key, nil + case policy.Algorithm_ALGORITHM_MLKEM_1024: + return ocrypto.MLKEM1024Key, nil case policy.Algorithm_ALGORITHM_UNSPECIFIED: fallthrough default: diff --git a/sdk/experimental/tdf/key_access.go b/sdk/experimental/tdf/key_access.go index 3974b06cf4..ad849c739c 100644 --- a/sdk/experimental/tdf/key_access.go +++ b/sdk/experimental/tdf/key_access.go @@ -165,8 +165,8 @@ 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.IsKEMKeyType(ktype) { + return wrapKeyWithKEM(ktype, pubKeyInfo.PEM, symKey) } if ocrypto.IsECKeyType(ktype) { // Handle EC key wrapping @@ -249,10 +249,18 @@ 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) +// wrapKeyWithKEM wraps a DEK with any KEM scheme — pure ML-KEM or hybrid +// (X-Wing, NIST PQ/T). Returns the base64-encoded envelope, the manifest +// scheme name (`hybrid-wrapped` or `mlkem-wrapped`), and an empty ephemeral +// key string (KEMs do not emit one in this profile). +func wrapKeyWithKEM(ktype ocrypto.KeyType, kasPublicKeyPEM string, symKey []byte) (string, string, string, error) { + wrappedDER, err := ocrypto.WrapDEK(ktype, kasPublicKeyPEM, symKey) if err != nil { - return "", "", "", fmt.Errorf("hybrid wrap failed: %w", err) + return "", "", "", fmt.Errorf("kem wrap failed: %w", err) } - return string(ocrypto.Base64Encode(wrappedDER)), "hybrid-wrapped", "", nil + scheme := "hybrid-wrapped" + if ocrypto.IsMLKEMKeyType(ktype) { + scheme = "mlkem-wrapped" + } + return string(ocrypto.Base64Encode(wrappedDER)), scheme, "", nil } diff --git a/sdk/experimental/tdf/keysplit/attributes.go b/sdk/experimental/tdf/keysplit/attributes.go index 27cc1e386f..f21fab3824 100644 --- a/sdk/experimental/tdf/keysplit/attributes.go +++ b/sdk/experimental/tdf/keysplit/attributes.go @@ -213,6 +213,10 @@ func convertAlgEnum2Simple(a policy.KasPublicKeyAlgEnum) policy.Algorithm { 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_MLKEM_768: + return policy.Algorithm_ALGORITHM_MLKEM_768 + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024: + return policy.Algorithm_ALGORITHM_MLKEM_1024 case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED: return policy.Algorithm_ALGORITHM_UNSPECIFIED default: diff --git a/sdk/granter.go b/sdk/granter.go index ede578cc17..571616efa0 100644 --- a/sdk/granter.go +++ b/sdk/granter.go @@ -292,6 +292,10 @@ func convertAlgEnum2Simple(a policy.KasPublicKeyAlgEnum) policy.Algorithm { 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_MLKEM_768: + return policy.Algorithm_ALGORITHM_MLKEM_768 + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024: + return policy.Algorithm_ALGORITHM_MLKEM_1024 case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED: return policy.Algorithm_ALGORITHM_UNSPECIFIED default: @@ -484,6 +488,10 @@ func algProto2String(e policy.KasPublicKeyAlgEnum) string { 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_MLKEM_768: + return string(ocrypto.MLKEM768Key) + case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024: + return string(ocrypto.MLKEM1024Key) case policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_UNSPECIFIED: return "" } diff --git a/sdk/idp_access_token_source.go b/sdk/idp_access_token_source.go index 9b84b4022f..7290be61dc 100644 --- a/sdk/idp_access_token_source.go +++ b/sdk/idp_access_token_source.go @@ -37,10 +37,14 @@ func getNewDPoPKey(dpopKeyPair *ocrypto.RsaKeyPair) (string, jwk.Key, *ocrypto.A return "", nil, nil, fmt.Errorf("error setting the key algorithm: %w", err) } - asymDecryption, err := ocrypto.NewAsymDecryption(dpopPrivateKeyPEM) + decryptor, err := ocrypto.FromPrivatePEM(dpopPrivateKeyPEM) if err != nil { return "", nil, nil, fmt.Errorf("error creating asymmetric decryptor: %w", err) } + asymDecryption, ok := decryptor.(ocrypto.AsymDecryption) + if !ok { + return "", nil, nil, fmt.Errorf("DPoP key is not an RSA private key: %T", decryptor) + } return dpopPublicKeyPEM, dpopKey, &asymDecryption, nil } diff --git a/sdk/kas_client.go b/sdk/kas_client.go index bb2b1f7f3a..b9c0374f99 100644 --- a/sdk/kas_client.go +++ b/sdk/kas_client.go @@ -273,9 +273,13 @@ func (k *KASClient) handleRSAKeyResponse(response *kas.RewrapResponse) (map[stri return nil, fmt.Errorf("ocrypto.PrivateKeyInPemFormat failed: %w", err) } - asymDecryption, err := ocrypto.NewAsymDecryption(clientPrivateKey) + decryptor, err := ocrypto.FromPrivatePEM(clientPrivateKey) if err != nil { - return nil, fmt.Errorf("ocrypto.NewAsymDecryption failed: %w", err) + return nil, fmt.Errorf("ocrypto.FromPrivatePEM failed: %w", err) + } + asymDecryption, ok := decryptor.(ocrypto.AsymDecryption) + if !ok { + return nil, fmt.Errorf("session key is not an RSA private key: %T", decryptor) } return k.processRSAResponse(response, asymDecryption) diff --git a/sdk/kas_client_test.go b/sdk/kas_client_test.go index 7fd4b6b60c..2d2df7e167 100644 --- a/sdk/kas_client_test.go +++ b/sdk/kas_client_test.go @@ -42,7 +42,10 @@ func (fake FakeAccessTokenSource) MakeToken(tokenMaker func(jwk.Key) ([]byte, er func getTokenSource(t *testing.T) FakeAccessTokenSource { dpopKey, _ := ocrypto.NewRSAKeyPair(2048) dpopPEM, _ := dpopKey.PrivateKeyInPemFormat() - decryption, _ := ocrypto.NewAsymDecryption(dpopPEM) + dpopDecryptor, err := ocrypto.FromPrivatePEM(dpopPEM) + require.NoError(t, err) + decryption, ok := dpopDecryptor.(ocrypto.AsymDecryption) + require.True(t, ok, "expected RSA decryptor, got %T", dpopDecryptor) dpopPEMPublic, _ := dpopKey.PublicKeyInPemFormat() encryption, _ := ocrypto.FromPublicPEM(dpopPEMPublic) dpopJWK, err := jwk.ParseKey([]byte(dpopPEM), jwk.WithPEM(true)) @@ -130,7 +133,8 @@ func TestCreatingRequest(t *testing.T) { } func Test_StoreKASKeys(t *testing.T) { - s, err := New("http://localhost:8080", + s, err := New( + "http://localhost:8080", WithPlatformConfiguration(PlatformConfiguration{ "idp": map[string]interface{}{ "issuer": "https://example.org", @@ -457,8 +461,10 @@ func Test_processRSAResponse(t *testing.T) { require.NoError(t, err) privateKeyPEM, err := mockPrivateKey.PrivateKeyInPemFormat() require.NoError(t, err) - mockDecryptor, err := ocrypto.NewAsymDecryption(privateKeyPEM) + mockPrivateDecryptor, err := ocrypto.FromPrivatePEM(privateKeyPEM) require.NoError(t, err) + mockDecryptor, ok := mockPrivateDecryptor.(ocrypto.AsymDecryption) + require.True(t, ok) // Create a mock AsymEncryption to create the wrapped key publicKeyPEM, err := mockPrivateKey.PublicKeyInPemFormat() diff --git a/sdk/tdf.go b/sdk/tdf.go index 1d6ef1182c..ce42099603 100644 --- a/sdk/tdf.go +++ b/sdk/tdf.go @@ -44,6 +44,7 @@ const ( kWrapped = "wrapped" kECWrapped = "ec-wrapped" kHybridWrapped = "hybrid-wrapped" + kMLKEMWrapped = "mlkem-wrapped" kKasProtocol = "kas" kSplitKeyType = "split" kGCMCipherAlgorithm = "AES-256-GCM" @@ -676,12 +677,12 @@ func createKeyAccess(kasInfo KASInfo, symKey []byte, policyBinding PolicyBinding ktype := ocrypto.KeyType(kasInfo.Algorithm) switch { - case ocrypto.IsHybridKeyType(ktype): - wrappedKey, err := generateWrapKeyWithHybrid(kasInfo.Algorithm, kasInfo.PublicKey, symKey) + case ocrypto.IsKEMKeyType(ktype): + wrappedKey, scheme, err := generateWrapKeyWithKEM(ktype, kasInfo.PublicKey, symKey) if err != nil { return KeyAccess{}, err } - keyAccess.KeyType = kHybridWrapped + keyAccess.KeyType = scheme keyAccess.WrappedKey = wrappedKey case ocrypto.IsECKeyType(ktype): mode, err := ocrypto.ECKeyTypeToMode(ktype) @@ -770,12 +771,19 @@ 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) +// generateWrapKeyWithKEM wraps a DEK with any KEM scheme — pure ML-KEM or +// hybrid (X-Wing, NIST PQ/T). Returns the base64-encoded envelope and the +// wire scheme name (`hybrid-wrapped` or `mlkem-wrapped`) for the manifest. +func generateWrapKeyWithKEM(ktype ocrypto.KeyType, publicKeyPEM string, symKey []byte) (string, string, error) { + wrappedDER, err := ocrypto.WrapDEK(ktype, publicKeyPEM, symKey) if err != nil { - return "", fmt.Errorf("generateWrapKeyWithHybrid: %w", err) + return "", "", fmt.Errorf("generateWrapKeyWithKEM: %w", err) } - return string(ocrypto.Base64Encode(wrappedDER)), nil + scheme := kHybridWrapped + if ocrypto.IsMLKEMKeyType(ktype) { + scheme = kMLKEMWrapped + } + return string(ocrypto.Base64Encode(wrappedDER)), scheme, nil } // create policy object @@ -1247,7 +1255,7 @@ func createRewrapRequest(_ context.Context, r *Reader) (map[string]*kas.Unsigned invalidPolicy = !ok alg, ok = policyBinding["alg"].(string) invalidPolicy = invalidPolicy || !ok - case (PolicyBinding): + case PolicyBinding: hash = policyBinding.Hash alg = policyBinding.Alg default: diff --git a/sdk/tdf_test.go b/sdk/tdf_test.go index de75b9a8d2..2124c97003 100644 --- a/sdk/tdf_test.go +++ b/sdk/tdf_test.go @@ -3127,8 +3127,8 @@ func (f *FakeKas) getRewrapResponse(rewrapRequest string, fulfillableObligations kasPrivateKey = strings.ReplaceAll(lk.private, "\n\t", "\n") } - asymDecrypt, err := ocrypto.NewAsymDecryption(kasPrivateKey) - f.s.Require().NoError(err, "ocrypto.NewAsymDecryption failed") + asymDecrypt, err := ocrypto.FromPrivatePEM(kasPrivateKey) + f.s.Require().NoError(err, "ocrypto.FromPrivatePEM failed") symmetricKey, err := asymDecrypt.Decrypt(wrappedKey) f.s.Require().NoError(err, "ocrypto.Decrypt failed for kao:[%s # %s (%s)] kas:[%s # %s (%s)]", kao.GetKasUrl(), kao.GetKid(), kao.GetSplitId(), f.URL, f.KID, f.Algorithm) asymEncrypt, err := ocrypto.FromPublicPEM(bodyData.GetClientPublicKey()) diff --git a/service/cmd/keygen/main.go b/service/cmd/keygen/main.go index 721191e286..f182339e6e 100644 --- a/service/cmd/keygen/main.go +++ b/service/cmd/keygen/main.go @@ -1,4 +1,4 @@ -// Package main generates hybrid post-quantum KAS key pairs (X-Wing, P256+ML-KEM-768, P384+ML-KEM-1024) +// Package main generates post-quantum KAS key pairs (X-Wing, P256+ML-KEM-768, P384+ML-KEM-1024, ML-KEM-768, ML-KEM-1024) // as PEM files for use with the OpenTDF platform. package main @@ -45,6 +45,18 @@ func main() { privateOut: "kas-p384mlkem1024-private.pem", publicOut: "kas-p384mlkem1024-public.pem", }, + { + name: "ML-KEM-768", + newKeyPair: generateMLKEM768, + privateOut: "kas-mlkem768-private.pem", + publicOut: "kas-mlkem768-public.pem", + }, + { + name: "ML-KEM-1024", + newKeyPair: generateMLKEM1024, + privateOut: "kas-mlkem1024-private.pem", + publicOut: "kas-mlkem1024-public.pem", + }, } for _, s := range specs { @@ -114,3 +126,35 @@ func generateP384MLKEM1024() (string, string, error) { } return priv, pub, nil } + +func generateMLKEM768() (string, string, error) { + kp, err := ocrypto.NewMLKEMKeyPair() + 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 generateMLKEM1024() (string, string, error) { + kp, err := ocrypto.NewMLKEM1024KeyPair() + 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 12443eaa25..d746e06917 100644 --- a/service/internal/security/basic_manager.go +++ b/service/internal/security/basic_manager.go @@ -38,6 +38,8 @@ var BasicManagerSupportedAlgorithms = []ocrypto.KeyType{ ocrypto.HybridXWingKey, ocrypto.HybridSecp256r1MLKEM768Key, ocrypto.HybridSecp384r1MLKEM1024Key, + ocrypto.MLKEM768Key, + ocrypto.MLKEM1024Key, } type BasicManager struct { @@ -82,17 +84,14 @@ func (b *BasicManager) Decrypt(ctx context.Context, keyDetails trust.KeyDetails, return nil, fmt.Errorf("failed to create decryptor from private PEM: %w", err) } - switch keyDetails.Algorithm() { + alg := keyDetails.Algorithm() + switch alg { //nolint:exhaustive // KEM key types are handled by the IsKEMKeyType branch below case ocrypto.RSA2048Key, ocrypto.RSA4096Key: plaintext, err := decrypter.Decrypt(ciphertext) if err != nil { return nil, fmt.Errorf("failed to decrypt with RSA: %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 ocrypto.NewAESProtectedKey(plaintext) case ocrypto.EC256Key, ocrypto.EC384Key, ocrypto.EC521Key: ecPrivKey, err := ocrypto.ECPrivateKeyFromPem(privKey) if err != nil { @@ -106,34 +105,27 @@ func (b *BasicManager) Decrypt(ctx context.Context, keyDetails trust.KeyDetails, if err != nil { return nil, fmt.Errorf("failed to decrypt with ephemeral key: %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.HybridXWingKey, ocrypto.HybridSecp256r1MLKEM768Key, ocrypto.HybridSecp384r1MLKEM1024Key: + return ocrypto.NewAESProtectedKey(plaintext) + } + + if ocrypto.IsKEMKeyType(alg) { if len(ephemeralPublicKey) > 0 { - return nil, errors.New("ephemeral public key should not be provided for hybrid decryption") + return nil, fmt.Errorf("ephemeral public key should not be provided for %s decryption", alg) } - // FromPrivatePEM routes by the OID inside the PKCS#8 envelope. Cross- - // check the routed decryptor against the algorithm the key record - // claims; a mismatch means the stored PEM does not match its metadata. - kt, ok := decrypter.(interface{ KeyType() ocrypto.KeyType }) - if !ok || kt.KeyType() != keyDetails.Algorithm() { - return nil, fmt.Errorf("hybrid key %s algorithm mismatch: PEM dispatched away from %s", keyDetails.ID(), keyDetails.Algorithm()) + // FromPrivatePEM routes by the OID inside the SPKI/PKCS#8 envelope. Cross- + // check the routed decryptor against the algorithm the key record claims; + // a mismatch means the stored PEM does not match its metadata. + if kt, ok := decrypter.(interface{ KeyType() ocrypto.KeyType }); !ok || kt.KeyType() != alg { + return nil, fmt.Errorf("KEM key %s algorithm mismatch: PEM dispatched away from %s", keyDetails.ID(), alg) } plaintext, err := decrypter.Decrypt(ciphertext) if err != nil { - return nil, fmt.Errorf("failed to decrypt with hybrid [%s]: %w", keyDetails.Algorithm(), err) - } - protectedKey, err := ocrypto.NewAESProtectedKey(plaintext) - if err != nil { - return nil, fmt.Errorf("failed to create protected key: %w", err) + return nil, fmt.Errorf("failed to decrypt with %s: %w", alg, err) } - return protectedKey, nil + return ocrypto.NewAESProtectedKey(plaintext) } - return nil, fmt.Errorf("unsupported algorithm: %s", keyDetails.Algorithm()) + return nil, fmt.Errorf("unsupported algorithm: %s", alg) } func (b *BasicManager) DeriveKey(ctx context.Context, keyDetails trust.KeyDetails, ephemeralPublicKeyBytes []byte, curve elliptic.Curve) (ocrypto.ProtectedKey, error) { diff --git a/service/internal/security/crypto_provider.go b/service/internal/security/crypto_provider.go index dcbe1ae5a1..4a0b9a28ec 100644 --- a/service/internal/security/crypto_provider.go +++ b/service/internal/security/crypto_provider.go @@ -18,4 +18,8 @@ const ( // Used for hybrid NIST EC + ML-KEM wrapping of the KAO AlgorithmHPQTSecp256r1MLKEM768 = "hpqt:secp256r1-mlkem768" AlgorithmHPQTSecp384r1MLKEM1024 = "hpqt:secp384r1-mlkem1024" + + // Used for encryption with ML-KEM of the KAO + AlgorithmMLKEM768 = "mlkem:768" + AlgorithmMLKEM1024 = "mlkem:1024" ) diff --git a/service/internal/security/in_process_provider.go b/service/internal/security/in_process_provider.go index 7d4b624e22..bf1d99c3c8 100644 --- a/service/internal/security/in_process_provider.go +++ b/service/internal/security/in_process_provider.go @@ -27,6 +27,8 @@ var InProcessSupportedAlgorithms = []ocrypto.KeyType{ ocrypto.HybridXWingKey, ocrypto.HybridSecp256r1MLKEM768Key, ocrypto.HybridSecp384r1MLKEM1024Key, + ocrypto.MLKEM768Key, + ocrypto.MLKEM1024Key, } func convertPEMToJWK(_ string) (string, error) { @@ -96,11 +98,8 @@ 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 + if kemKey, err := k.cryptoProvider.KEMPublicKey(kid); err == nil { + return kemKey, nil } return k.cryptoProvider.ECPublicKey(kid) default: @@ -274,6 +273,12 @@ func (a *InProcessProvider) Decrypt(ctx context.Context, keyDetails trust.KeyDet } return a.cryptoProvider.Decrypt(ctx, trust.KeyIdentifier(kid), ciphertext, nil) + case AlgorithmMLKEM768, AlgorithmMLKEM1024: + if len(ephemeralPublicKey) > 0 { + return nil, errors.New("ephemeral public key should not be provided for ML-KEM decryption") + } + return a.cryptoProvider.Decrypt(ctx, trust.KeyIdentifier(kid), ciphertext, nil) + default: return nil, errors.New("unsupported key algorithm") } @@ -360,9 +365,7 @@ func (a *InProcessProvider) determineKeyType(kid string) (string, error) { return key.Algorithm, nil case StandardECCrypto: return key.Algorithm, nil - case StandardXWingCrypto: - return key.Algorithm, nil - case StandardHybridCrypto: + case StandardKEMCrypto: return key.Algorithm, nil } diff --git a/service/internal/security/in_process_provider_test.go b/service/internal/security/in_process_provider_test.go index 9870ad53bb..e56eca2cfc 100644 --- a/service/internal/security/in_process_provider_test.go +++ b/service/internal/security/in_process_provider_test.go @@ -229,3 +229,29 @@ func TestInProcessProviderDetermineKeyType(t *testing.T) { _, err = provider.determineKeyType("missing") require.Error(t, err) } + +func TestInProcessProviderDetermineKeyTypeMLKEM(t *testing.T) { + cryptoProvider, material := newStandardCryptoWithMLKEMForTest(t) + providerIface := NewSecurityProviderAdapter(cryptoProvider, nil, nil) + provider, ok := providerIface.(*InProcessProvider) + require.True(t, ok) + + keyType, err := provider.determineKeyType(material.mlkem768Kid) + require.NoError(t, err) + assert.Equal(t, AlgorithmMLKEM768, keyType) + + keyType, err = provider.determineKeyType(material.mlkem1024Kid) + require.NoError(t, err) + assert.Equal(t, AlgorithmMLKEM1024, keyType) + + details, err := provider.FindKeyByID(t.Context(), trust.KeyIdentifier(material.mlkem768Kid)) + require.NoError(t, err) + assert.Equal(t, ocrypto.KeyType(AlgorithmMLKEM768), details.Algorithm()) + + details, err = provider.FindKeyByID(t.Context(), trust.KeyIdentifier(material.mlkem1024Kid)) + require.NoError(t, err) + assert.Equal(t, ocrypto.KeyType(AlgorithmMLKEM1024), details.Algorithm()) + + _, 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 cbc3d1cdf0..8713712678 100644 --- a/service/internal/security/standard_crypto.go +++ b/service/internal/security/standard_crypto.go @@ -67,16 +67,14 @@ type StandardECCrypto struct { sk *ecdh.PrivateKey } -type StandardXWingCrypto struct { +// StandardKEMCrypto holds any KEM-based key (X-Wing, NIST hybrid PQ/T, +// or pure ML-KEM). The decryptor is created at load time so per-call +// dispatch reduces to decryptor.Decrypt(ciphertext). +type StandardKEMCrypto struct { KeyPairInfo - xwingPrivateKeyPem string - xwingPublicKeyPem string -} - -type StandardHybridCrypto struct { - KeyPairInfo - hybridPrivateKeyPem string - hybridPublicKeyPem string + privateKeyPem string + publicKeyPem string + decryptor ocrypto.PrivateKeyDecryptor } // List of keys by identifier @@ -163,36 +161,33 @@ func loadKey(k KeyPairInfo) (any, error) { ecPrivateKeyPem: string(privatePEM), ecCertificatePEM: string(certPEM), }, nil - case AlgorithmHPQTXWing: - dec, err := ocrypto.FromPrivatePEM(string(privatePEM)) + case AlgorithmHPQTXWing, + AlgorithmHPQTSecp256r1MLKEM768, AlgorithmHPQTSecp384r1MLKEM1024, + AlgorithmMLKEM768, AlgorithmMLKEM1024: + decryptor, err := ocrypto.FromPrivatePEM(string(privatePEM)) if err != nil { - return nil, fmt.Errorf("failed to parse X-Wing private key: %w", err) + return nil, fmt.Errorf("ocrypto.FromPrivatePEM (%s) failed: %w", k.Algorithm, err) } - if err := assertDecryptorAlgorithm(dec, k.Algorithm, k.KID); err != nil { + // Confirm the PEM dispatched to the scheme this KAS key claims, so a + // mislabeled key fails fast at load instead of producing garbage on + // unwrap. Covers both the hybrid PQ/T and pure ML-KEM decryptors. + if err := assertDecryptorAlgorithm(decryptor, k.Algorithm, k.KID); err != nil { return nil, err } - return StandardXWingCrypto{ - KeyPairInfo: k, - xwingPrivateKeyPem: string(privatePEM), - xwingPublicKeyPem: string(certPEM), - }, nil - case AlgorithmHPQTSecp256r1MLKEM768, AlgorithmHPQTSecp384r1MLKEM1024: - dec, err := ocrypto.FromPrivatePEM(string(privatePEM)) - if err != nil { - return nil, fmt.Errorf("failed to parse hybrid private key: %w", err) - } - if err := assertDecryptorAlgorithm(dec, k.Algorithm, k.KID); err != nil { - return nil, err - } - return StandardHybridCrypto{ - KeyPairInfo: k, - hybridPrivateKeyPem: string(privatePEM), - hybridPublicKeyPem: string(certPEM), + return StandardKEMCrypto{ + KeyPairInfo: k, + privateKeyPem: string(privatePEM), + publicKeyPem: string(certPEM), + decryptor: decryptor, }, nil case AlgorithmRSA2048, AlgorithmRSA4096: - asymDecryption, err := ocrypto.NewAsymDecryption(string(privatePEM)) + decryptor, err := ocrypto.FromPrivatePEM(string(privatePEM)) if err != nil { - return nil, fmt.Errorf("ocrypto.NewAsymDecryption failed: %w", err) + return nil, fmt.Errorf("ocrypto.FromPrivatePEM failed: %w", err) + } + asymDecryption, ok := decryptor.(ocrypto.AsymDecryption) + if !ok { + return nil, fmt.Errorf("unexpected private key decryptor type: %T", decryptor) } publicKeyEncryptor, err := ocrypto.FromPublicPEM(string(certPEM)) if err != nil { @@ -229,9 +224,13 @@ func loadDeprecatedKeys(rsaKeys map[string]StandardKeyInfo, ecKeys map[string]St return nil, fmt.Errorf("failed to rsa private key file: %w", err) } - asymDecryption, err := ocrypto.NewAsymDecryption(string(privatePemData)) + decryptor, err := ocrypto.FromPrivatePEM(string(privatePemData)) if err != nil { - return nil, fmt.Errorf("ocrypto.NewAsymDecryption failed: %w", err) + return nil, fmt.Errorf("ocrypto.FromPrivatePEM failed: %w", err) + } + asymDecryption, ok := decryptor.(ocrypto.AsymDecryption) + if !ok { + return nil, fmt.Errorf("unexpected private key decryptor type: %T", decryptor) } publicPemData, err := os.ReadFile(kasInfo.PublicKeyPath) @@ -369,40 +368,21 @@ func (s StandardCrypto) ECPublicKey(kid string) (string, error) { return string(pemBytes), nil } -func (s StandardCrypto) XWingPublicKey(kid string) (string, error) { +// KEMPublicKey returns the public-key PEM for any KEM-based key +// (X-Wing, NIST hybrid PQ/T, or pure ML-KEM). +func (s StandardCrypto) KEMPublicKey(kid string) (string, error) { k, ok := s.keysByID[kid] if !ok { - return "", fmt.Errorf("no xwing key with id [%s]: %w", kid, ErrCertNotFound) + return "", fmt.Errorf("no key with id [%s]: %w", kid, ErrCertNotFound) } - xw, ok := k.(StandardXWingCrypto) + kem, ok := k.(StandardKEMCrypto) if !ok { - return "", fmt.Errorf("key with id [%s] is not an X-Wing key: %w", kid, ErrCertNotFound) + return "", fmt.Errorf("key with id [%s] is not a KEM 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) + if kem.publicKeyPem == "" { + return "", fmt.Errorf("no public key with id [%s]: %w", kid, ErrCertNotFound) } + return kem.publicKeyPem, nil } func (s StandardCrypto) RSADecrypt(_ crypto.Hash, kid string, _ string, ciphertext []byte) ([]byte, error) { @@ -515,38 +495,14 @@ func (s *StandardCrypto) Decrypt(_ context.Context, keyID trust.KeyIdentifier, c return nil, fmt.Errorf("error decrypting data: %w", err) } - case StandardXWingCrypto: + case StandardKEMCrypto: if len(ephemeralPublicKey) > 0 { - return nil, errors.New("ephemeral public key should not be provided for X-Wing decryption") + return nil, fmt.Errorf("ephemeral public key should not be provided for %s decryption", key.Algorithm) } - dec, err := ocrypto.FromPrivatePEM(key.xwingPrivateKeyPem) - if err != nil { - return nil, fmt.Errorf("failed to parse X-Wing private key: %w", err) - } - if err := assertDecryptorAlgorithm(dec, key.Algorithm, kid); err != nil { - return nil, err - } - rawKey, err = dec.Decrypt(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") - } - - dec, err := ocrypto.FromPrivatePEM(key.hybridPrivateKeyPem) - if err != nil { - return nil, fmt.Errorf("failed to parse hybrid private key: %w", err) - } - if err := assertDecryptorAlgorithm(dec, key.Algorithm, kid); err != nil { - return nil, err - } - rawKey, err = dec.Decrypt(ciphertext) + rawKey, err = key.decryptor.Decrypt(ciphertext) if err != nil { - return nil, fmt.Errorf("failed to decrypt with hybrid [%s]: %w", key.Algorithm, err) + return nil, fmt.Errorf("failed to decrypt with %s: %w", key.Algorithm, err) } default: diff --git a/service/internal/security/test_helpers_test.go b/service/internal/security/test_helpers_test.go index f3736529b7..f15144a1f1 100644 --- a/service/internal/security/test_helpers_test.go +++ b/service/internal/security/test_helpers_test.go @@ -17,6 +17,14 @@ type testKeyMaterial struct { ecKid string ecPrivatePEM string ecPublicPEM string + + mlkem768Kid string + mlkem768PrivatePEM string + mlkem768PublicPEM string + + mlkem1024Kid string + mlkem1024PrivatePEM string + mlkem1024PublicPEM string } func writeTempFile(t *testing.T, dir, name, contents string) string { @@ -83,6 +91,55 @@ func newStandardCryptoForTest(t *testing.T, includeRSA, includeEC bool) (*Standa return crypto, material } +func newStandardCryptoWithMLKEMForTest(t *testing.T) (*StandardCrypto, testKeyMaterial) { + t.Helper() + + dir := t.TempDir() + var keys []KeyPairInfo + var material testKeyMaterial + + kp768, err := ocrypto.NewMLKEMKeyPair() + require.NoError(t, err) + mlkem768Private, err := kp768.PrivateKeyInPemFormat() + require.NoError(t, err) + mlkem768Public, err := kp768.PublicKeyInPemFormat() + require.NoError(t, err) + + material.mlkem768Kid = "mlkem768-test-key" + material.mlkem768PrivatePEM = mlkem768Private + material.mlkem768PublicPEM = mlkem768Public + + keys = append(keys, KeyPairInfo{ + Algorithm: AlgorithmMLKEM768, + KID: material.mlkem768Kid, + Private: writeTempFile(t, dir, "mlkem768-private.pem", mlkem768Private), + Certificate: writeTempFile(t, dir, "mlkem768-public.pem", mlkem768Public), + }) + + kp1024, err := ocrypto.NewMLKEM1024KeyPair() + require.NoError(t, err) + mlkem1024Private, err := kp1024.PrivateKeyInPemFormat() + require.NoError(t, err) + mlkem1024Public, err := kp1024.PublicKeyInPemFormat() + require.NoError(t, err) + + material.mlkem1024Kid = "mlkem1024-test-key" + material.mlkem1024PrivatePEM = mlkem1024Private + material.mlkem1024PublicPEM = mlkem1024Public + + keys = append(keys, KeyPairInfo{ + Algorithm: AlgorithmMLKEM1024, + KID: material.mlkem1024Kid, + Private: writeTempFile(t, dir, "mlkem1024-private.pem", mlkem1024Private), + Certificate: writeTempFile(t, dir, "mlkem1024-public.pem", mlkem1024Public), + }) + + crypto, err := NewStandardCrypto(StandardConfig{Keys: keys}) + require.NoError(t, err) + + return crypto, material +} + func exportProtectedKey(t *testing.T, key ocrypto.ProtectedKey) []byte { t.Helper() raw, err := (&noOpEncapsulator{}).Encapsulate(key) diff --git a/service/kas/access/provider.go b/service/kas/access/provider.go index 89f90b5e85..f5d00f69a6 100644 --- a/service/kas/access/provider.go +++ b/service/kas/access/provider.go @@ -52,15 +52,25 @@ 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"` + ECTDFEnabled bool `mapstructure:"ec_tdf_enabled" json:"ec_tdf_enabled"` + // HybridTDFEnabled is a preview feature that enables support for hybrid rewrap in TDFs. + // Enabling is required to parse KAOs with the `hybrid-wrapped` type, + // and (currently) also enables responding with hybrid encrypted responses. + // This feature is experimental and may be removed or changed in future releases. + // Enabling this also enables ML-KEM rewrap support; it cannot be enabled + // while ML-KEM is disabled (see normalizePreview). HybridTDFEnabled bool `mapstructure:"hybrid_tdf_enabled" json:"hybrid_tdf_enabled"` - KeyManagement bool `mapstructure:"key_management" json:"key_management"` + // MLKEMTDFEnabled is a preview feature that enables support for ML-KEM rewrap in TDFs. + // Enabling is required to parse KAOs with the `mlkem-wrapped` type, + // and (currently) also enables responding with ML-KEM encrypted responses. + // This feature is experimental and may be removed or changed in future releases. + MLKEMTDFEnabled bool `mapstructure:"mlkem_tdf_enabled" json:"mlkem_tdf_enabled"` + KeyManagement bool `mapstructure:"key_management" json:"key_management"` } // Specifies the preferred/default key for a given algorithm type. @@ -82,11 +92,13 @@ func (p *Provider) IsReady(ctx context.Context) error { // overrides, and emits a warning when the configured clock skew exceeds the default. func (p *Provider) ApplyConfig(cfg KASConfig, securityCfg *config.SecurityConfig) { p.KASConfig = cfg + p.normalizePreview() p.securityConfig = securityCfg if p.Logger != nil { if skew := p.acceptableSkew(); skew > config.DefaultUnsafeClockSkew { - p.Logger.Warn("configured SRT acceptable skew exceeds default", + p.Logger.Warn( + "configured SRT acceptable skew exceeds default", slog.Duration("configured_skew", skew), slog.Duration("default_skew", config.DefaultUnsafeClockSkew), ) @@ -145,14 +157,13 @@ func (kasCfg KASConfig) String() string { } return fmt.Sprintf( - "KASConfig{Keyring:%v, ECCertID:%q, RSACertID:%q, RootKey:%s, KeyCacheExpiration:%s, ECTDFEnabled:%t, HybridTDFEnabled:%t, Preview:%+v, RegisteredKASURI:%q}", + "KASConfig{Keyring:%v, ECCertID:%q, RSACertID:%q, RootKey:%s, KeyCacheExpiration:%s, ECTDFEnabled:%t, Preview:%+v, RegisteredKASURI:%q}", kasCfg.Keyring, kasCfg.ECCertID, kasCfg.RSACertID, rootKeySummary, kasCfg.KeyCacheExpiration, kasCfg.ECTDFEnabled, - kasCfg.HybridTDFEnabled, kasCfg.Preview, kasCfg.RegisteredKASURI, ) @@ -171,12 +182,19 @@ 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), ) } +// normalizePreview applies implied preview settings. Enabling the hybrid +// preview also enables ML-KEM rewrap support (see Preview.HybridTDFEnabled). +func (kasCfg *KASConfig) normalizePreview() { + if kasCfg.Preview.HybridTDFEnabled { + kasCfg.Preview.MLKEMTDFEnabled = true + } +} + // If there exists *any* legacy keys, returns empty list. // Otherwise, create a copy with legacy=true for all values func inferLegacyKeys(keys []CurrentKeyFor) []CurrentKeyFor { diff --git a/service/kas/access/provider_test.go b/service/kas/access/provider_test.go index bd7872c451..56e4a18861 100644 --- a/service/kas/access/provider_test.go +++ b/service/kas/access/provider_test.go @@ -11,6 +11,45 @@ func TestInferLegacyKeys_empty(t *testing.T) { assert.Empty(t, inferLegacyKeys(nil)) } +func TestApplyConfig_NormalizesHybridToMLKEM(t *testing.T) { + tests := []struct { + name string + preview Preview + wantMLKEM bool + }{ + { + name: "hybrid on enables mlkem", + preview: Preview{HybridTDFEnabled: true}, + wantMLKEM: true, + }, + { + name: "hybrid and mlkem on stays on", + preview: Preview{HybridTDFEnabled: true, MLKEMTDFEnabled: true}, + wantMLKEM: true, + }, + { + name: "mlkem on without hybrid is untouched", + preview: Preview{MLKEMTDFEnabled: true}, + wantMLKEM: true, + }, + { + name: "both off stays off", + preview: Preview{}, + wantMLKEM: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := &Provider{} + p.ApplyConfig(KASConfig{Preview: tc.preview}, nil) + assert.Equal(t, tc.wantMLKEM, p.Preview.MLKEMTDFEnabled) + // HybridTDFEnabled is never altered by normalization. + assert.Equal(t, tc.preview.HybridTDFEnabled, p.Preview.HybridTDFEnabled) + }) + } +} + func TestInferLegacyKeys_singles(t *testing.T) { one := []CurrentKeyFor{ { diff --git a/service/kas/access/publicKey.go b/service/kas/access/publicKey.go index d7f381c307..14c6dd8a05 100644 --- a/service/kas/access/publicKey.go +++ b/service/kas/access/publicKey.go @@ -77,7 +77,8 @@ func (p *Provider) LegacyPublicKey(ctx context.Context, req *connect.Request[kas return nil, connect.NewError(connect.CodeInternal, errors.Join(ErrConfig, errors.New("configuration error"))) } case security.AlgorithmRSA2048, security.AlgorithmHPQTXWing, - security.AlgorithmHPQTSecp256r1MLKEM768, security.AlgorithmHPQTSecp384r1MLKEM1024, "": + security.AlgorithmHPQTSecp256r1MLKEM768, security.AlgorithmHPQTSecp384r1MLKEM1024, + security.AlgorithmMLKEM768, security.AlgorithmMLKEM1024, "": // For RSA keys, return the public key in PKCS8 format pem, err = keyDetails.ExportPublicKey(ctx, trust.KeyTypePKCS8) if err != nil { @@ -154,7 +155,9 @@ func (p *Provider) PublicKey(ctx context.Context, req *connect.Request[kaspb.Pub return r(ecPublicKeyPem, kid, err) case security.AlgorithmHPQTXWing, security.AlgorithmHPQTSecp256r1MLKEM768, - security.AlgorithmHPQTSecp384r1MLKEM1024: + security.AlgorithmHPQTSecp384r1MLKEM1024, + security.AlgorithmMLKEM768, + security.AlgorithmMLKEM1024: switch fmt { case "pkcs8", "": publicKeyPEM, err := keyDetails.ExportPublicKey(ctx, trust.KeyTypePKCS8) diff --git a/service/kas/access/rewrap.go b/service/kas/access/rewrap.go index 67a0e33690..2aa93f34e3 100644 --- a/service/kas/access/rewrap.go +++ b/service/kas/access/rewrap.go @@ -760,7 +760,7 @@ func (p *Provider) verifyRewrapRequests(ctx context.Context, req *kaspb.Unsigned continue } case "hybrid-wrapped": - if !p.HybridTDFEnabled && !p.Preview.HybridTDFEnabled { + if !p.Preview.HybridTDFEnabled { p.Logger.WarnContext(ctx, "hybrid-wrapped not enabled") failedKAORewrap(results, kao, err400("bad request")) continue @@ -773,6 +773,20 @@ func (p *Provider) verifyRewrapRequests(ctx context.Context, req *kaspb.Unsigned failedKAORewrap(results, kao, err400("bad request")) continue } + case "mlkem-wrapped": + if !p.Preview.MLKEMTDFEnabled { + p.Logger.WarnContext(ctx, "mlkem-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 ML-KEM 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 77d0bbffcf..3c2e073132 100644 --- a/service/kas/kas.go +++ b/service/kas/kas.go @@ -29,8 +29,8 @@ func OnConfigUpdate(p *access.Provider) serviceregistry.OnConfigUpdateHook { } p.ApplyConfig(kasCfg, p.SecurityConfig()) - p.Logger.TraceContext(ctx, "kas config reloaded", slog.Any("config", kasCfg)) - logSupportedMechanisms(ctx, p.Logger, p.KeyDelegator, &kasCfg) + p.Logger.TraceContext(ctx, "kas config reloaded", slog.Any("config", p.KASConfig)) + logSupportedMechanisms(ctx, p.Logger, p.KeyDelegator, &p.KASConfig) return nil } @@ -115,8 +115,8 @@ func NewRegistration() *serviceregistry.Service[kasconnect.AccessServiceHandler] p.ApplyConfig(kasCfg, srp.Security) p.Tracer = srp.Tracer - srp.Logger.Debug("kas config loaded", slog.Any("config", kasCfg)) - logSupportedMechanisms(context.Background(), srp.Logger, p.KeyDelegator, &kasCfg) + srp.Logger.Debug("kas config loaded", slog.Any("config", p.KASConfig)) + logSupportedMechanisms(context.Background(), srp.Logger, p.KeyDelegator, &p.KASConfig) if err := srp.RegisterReadinessCheck("kas", p.IsReady); err != nil { srp.Logger.Error("failed to register kas readiness check", slog.String("error", err.Error())) @@ -198,17 +198,18 @@ func logSupportedMechanisms(ctx context.Context, l *logger.Logger, kd *trust.Del // filterMechanismsByPreview drops algorithms whose corresponding rewrap path is // disabled. Keep aligned with the gating in service/kas/access/rewrap.go for -// "ec-wrapped" and "hybrid-wrapped" key access objects. +// "ec-wrapped", "hybrid-wrapped", and "mlkem-wrapped" key access objects. func filterMechanismsByPreview(algs []ocrypto.KeyType, kasCfg *access.KASConfig) []ocrypto.KeyType { ecEnabled := kasCfg.ECTDFEnabled || kasCfg.Preview.ECTDFEnabled - hybridEnabled := kasCfg.HybridTDFEnabled || kasCfg.Preview.HybridTDFEnabled out := make([]ocrypto.KeyType, 0, len(algs)) for _, a := range algs { switch { case !ecEnabled && ocrypto.IsECKeyType(a): continue - case !hybridEnabled && ocrypto.IsHybridKeyType(a): + case !kasCfg.Preview.HybridTDFEnabled && ocrypto.IsHybridKeyType(a): + continue + case !kasCfg.Preview.MLKEMTDFEnabled && ocrypto.IsMLKEMKeyType(a): continue } out = append(out, a) diff --git a/service/kas/kas_test.go b/service/kas/kas_test.go index 4772de138b..a6947cfbd3 100644 --- a/service/kas/kas_test.go +++ b/service/kas/kas_test.go @@ -53,12 +53,6 @@ func TestFilterMechanismsByPreview(t *testing.T) { cfg: &access.KASConfig{Preview: access.Preview{ECTDFEnabled: true}}, want: []ocrypto.KeyType{"rsa:2048", "rsa:4096", "ec:secp256r1", "ec:secp384r1"}, }, - { - name: "top-level hybrid flag on keeps hpqt only", - algs: allAlgs, - cfg: &access.KASConfig{HybridTDFEnabled: true}, - want: []ocrypto.KeyType{"rsa:2048", "rsa:4096", "hpqt:xwing", "hpqt:secp256r1-mlkem768"}, - }, { name: "preview hybrid flag on keeps hpqt only", algs: allAlgs, @@ -68,7 +62,7 @@ func TestFilterMechanismsByPreview(t *testing.T) { { name: "both flags on keeps everything", algs: allAlgs, - cfg: &access.KASConfig{ECTDFEnabled: true, HybridTDFEnabled: true}, + cfg: &access.KASConfig{Preview: access.Preview{ECTDFEnabled: true, HybridTDFEnabled: true, MLKEMTDFEnabled: true}}, want: allAlgs, }, { @@ -77,13 +71,14 @@ func TestFilterMechanismsByPreview(t *testing.T) { cfg: &access.KASConfig{Preview: access.Preview{ ECTDFEnabled: true, HybridTDFEnabled: true, + MLKEMTDFEnabled: true, }}, want: allAlgs, }, { name: "empty input returns empty", algs: []ocrypto.KeyType{}, - cfg: &access.KASConfig{ECTDFEnabled: true, HybridTDFEnabled: true}, + cfg: &access.KASConfig{Preview: access.Preview{ECTDFEnabled: true, HybridTDFEnabled: true, MLKEMTDFEnabled: true}}, want: []ocrypto.KeyType{}, }, { @@ -92,6 +87,28 @@ func TestFilterMechanismsByPreview(t *testing.T) { cfg: &access.KASConfig{}, want: []ocrypto.KeyType{"rsa:2048"}, }, + { + // (HybridTDFEnabled=true, MLKEMTDFEnabled=false) is unreachable at + // runtime because normalizePreview forces ML-KEM on whenever hybrid + // is enabled, so this asserts the negative gating via a reachable + // all-off config instead. + name: "all preview flags off drops pure mlkem and hybrid", + algs: []ocrypto.KeyType{"rsa:2048", "hpqt:xwing", "mlkem:768", "mlkem:1024"}, + cfg: &access.KASConfig{}, + want: []ocrypto.KeyType{"rsa:2048"}, + }, + { + name: "mlkem on keeps pure mlkem", + algs: []ocrypto.KeyType{"rsa:2048", "hpqt:xwing", "mlkem:768", "mlkem:1024"}, + cfg: &access.KASConfig{Preview: access.Preview{HybridTDFEnabled: true, MLKEMTDFEnabled: true}}, + want: []ocrypto.KeyType{"rsa:2048", "hpqt:xwing", "mlkem:768", "mlkem:1024"}, + }, + { + name: "mlkem on without hybrid keeps pure mlkem but drops hybrid", + algs: []ocrypto.KeyType{"rsa:2048", "hpqt:xwing", "mlkem:768", "mlkem:1024"}, + cfg: &access.KASConfig{Preview: access.Preview{MLKEMTDFEnabled: true}}, + want: []ocrypto.KeyType{"rsa:2048", "mlkem:768", "mlkem:1024"}, + }, } for _, tc := range tests { @@ -171,7 +188,7 @@ func TestLogSupportedMechanisms_EmitsInfoLine(t *testing.T) { }, { name: "both preview flags", - cfg: &access.KASConfig{Preview: access.Preview{ECTDFEnabled: true, HybridTDFEnabled: true}}, + cfg: &access.KASConfig{Preview: access.Preview{ECTDFEnabled: true, HybridTDFEnabled: true, MLKEMTDFEnabled: true}}, wantMechanisms: []string{"ec:secp256r1", "hpqt:xwing", "rsa:2048"}, }, } diff --git a/service/policy/db/grant_mappings.go b/service/policy/db/grant_mappings.go index 7cd8830f92..2d1683c56e 100644 --- a/service/policy/db/grant_mappings.go +++ b/service/policy/db/grant_mappings.go @@ -28,6 +28,10 @@ func mapAlgorithmToKasPublicKeyAlg(alg policy.Algorithm) policy.KasPublicKeyAlgE 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_MLKEM_768: + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 + case policy.Algorithm_ALGORITHM_MLKEM_1024: + return policy.KasPublicKeyAlgEnum_KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 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 86e9fbc9a6..4b40df67cf 100644 --- a/service/policy/kasregistry/key_access_server_registry.proto +++ b/service/policy/kasregistry/key_access_server_registry.proto @@ -436,7 +436,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, 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 + expression: "this in [1, 2, 3, 4, 5, 6, 7, 8, 20, 21]" // 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, ALGORITHM_MLKEM_768, ALGORITHM_MLKEM_1024 }]; // The algorithm to be used for the key // Required KeyMode key_mode = 4 [(buf.validate.field).cel = { @@ -480,7 +480,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, 6, 7, 8]" // Allow unspecified and all supported algorithm values + expression: "this in [0, 1, 2, 3, 4, 5, 6, 7, 8, 20, 21]" // Allow unspecified and all supported algorithm values, including ALGORITHM_MLKEM_768 and ALGORITHM_MLKEM_1024 }]; // Filter keys by algorithm oneof kas_filter { @@ -593,7 +593,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, 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 + expression: "this in [1, 2, 3, 4, 5, 6, 7, 8, 20, 21]" // 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, ALGORITHM_MLKEM_768, ALGORITHM_MLKEM_1024 }]; // Required KeyMode key_mode = 3 [ diff --git a/service/policy/objects.proto b/service/policy/objects.proto index 5a241e72ea..7a474fffce 100644 --- a/service/policy/objects.proto +++ b/service/policy/objects.proto @@ -490,6 +490,8 @@ enum 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; + KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_768 = 20; + KAS_PUBLIC_KEY_ALG_ENUM_MLKEM_1024 = 21; } // Deprecated @@ -663,6 +665,8 @@ enum Algorithm { ALGORITHM_HPQT_XWING = 6; ALGORITHM_HPQT_SECP256R1_MLKEM768 = 7; ALGORITHM_HPQT_SECP384R1_MLKEM1024 = 8; + ALGORITHM_MLKEM_768 = 20; + ALGORITHM_MLKEM_1024 = 21; } // The status of the key diff --git a/test/tdf-roundtrips.bats b/test/tdf-roundtrips.bats index e6f63f4fca..cd16a6c84c 100755 --- a/test/tdf-roundtrips.bats +++ b/test/tdf-roundtrips.bats @@ -89,6 +89,48 @@ printf '%s\n' "$output" | grep "Hello P384+ML-KEM-1024 wrappers!" } +@test "examples: roundtrip Z-TDF with ML-KEM-768 wrapped KAO" { + echo "[INFO] create a tdf3 format file" + run go run ./examples encrypt -o sensitive-with-mlkem768.txt.tdf --autoconfigure=false -A "mlkem:768" "Hello 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" = mlkem-wrapped ] + + kid=$(jq -r '.encryptionInformation.keyAccess[0].kid' <<<"${output}") + echo "kao.kid=$kid" + [ "$kid" = m1 ] + + echo "[INFO] decrypting..." + run go run ./examples decrypt sensitive-with-mlkem768.txt.tdf + echo "$output" + printf '%s\n' "$output" | grep "Hello ML-KEM-768 wrappers!" +} + +@test "examples: roundtrip Z-TDF with ML-KEM-1024 wrapped KAO" { + echo "[INFO] create a tdf3 format file" + run go run ./examples encrypt -o sensitive-with-mlkem1024.txt.tdf --autoconfigure=false -A "mlkem:1024" "Hello 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" = mlkem-wrapped ] + + kid=$(jq -r '.encryptionInformation.keyAccess[0].kid' <<<"${output}") + echo "kao.kid=$kid" + [ "$kid" = m2 ] + + echo "[INFO] decrypting..." + run go run ./examples decrypt sensitive-with-mlkem1024.txt.tdf + echo "$output" + printf '%s\n' "$output" | grep "Hello 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")" @@ -272,6 +314,10 @@ services: alg: hpqt:secp256r1-mlkem768 - kid: h2 alg: hpqt:secp384r1-mlkem1024 + - kid: m1 + alg: mlkem:768 + - kid: m2 + alg: mlkem:1024 policy: enabled: true authorization: @@ -331,6 +377,14 @@ server: alg: hpqt:secp384r1-mlkem1024 private: kas-p384mlkem1024-private.pem cert: kas-p384mlkem1024-public.pem + - kid: m1 + alg: mlkem:768 + private: kas-mlkem768-private.pem + cert: kas-mlkem768-public.pem + - kid: m2 + alg: mlkem:1024 + private: kas-mlkem1024-private.pem + cert: kas-mlkem1024-public.pem port: 8080 opa: embedded: true diff --git a/tests-bdd/cukes/utils/utils_genKeys.go b/tests-bdd/cukes/utils/utils_genKeys.go index 3ad0b57599..5a3d344b95 100644 --- a/tests-bdd/cukes/utils/utils_genKeys.go +++ b/tests-bdd/cukes/utils/utils_genKeys.go @@ -207,7 +207,7 @@ func createJavaKeystore(ctx context.Context, certPath, keystorePath string) { log.Printf("Java keystore generated successfully: %s", keystorePath) } -// generateHybridKeys creates X-Wing, P256+ML-KEM-768, and P384+ML-KEM-1024 key pairs. +// generateHybridKeys creates post-quantum key pairs: X-Wing, P256+ML-KEM-768, P384+ML-KEM-1024, ML-KEM-768, and ML-KEM-1024. func generateHybridKeys(outputPath string) { specs := []struct { name string @@ -218,6 +218,8 @@ func generateHybridKeys(outputPath string) { {"X-Wing", generateXWingKeyPair, "kas-xwing-private.pem", "kas-xwing-public.pem"}, {"P256+ML-KEM-768", generateP256MLKEM768KeyPair, "kas-p256mlkem768-private.pem", "kas-p256mlkem768-public.pem"}, {"P384+ML-KEM-1024", generateP384MLKEM1024KeyPair, "kas-p384mlkem1024-private.pem", "kas-p384mlkem1024-public.pem"}, + {"ML-KEM-768", generateMLKEM768KeyPair, "kas-mlkem768-private.pem", "kas-mlkem768-public.pem"}, + {"ML-KEM-1024", generateMLKEM1024KeyPair, "kas-mlkem1024-private.pem", "kas-mlkem1024-public.pem"}, } for _, s := range specs { @@ -289,3 +291,35 @@ func generateP384MLKEM1024KeyPair() (string, string, error) { } return priv, pub, nil } + +func generateMLKEM768KeyPair() (string, string, error) { + kp, err := ocrypto.NewMLKEMKeyPair() + 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 generateMLKEM1024KeyPair() (string, string, error) { + kp, err := ocrypto.NewMLKEM1024KeyPair() + 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 +}