From f02755a3436602af8b8c53bdda7c6b0d68f676b0 Mon Sep 17 00:00:00 2001 From: beejones Date: Thu, 19 Sep 2024 12:51:15 +0000 Subject: [PATCH 1/6] Add operators to key release policy --- README.md | 4 +-- src/attestation/AttestationValidation.ts | 4 +-- src/endpoints/keyReleasePolicyEndpoint.ts | 11 +++---- src/index.ts | 2 +- src/policies/IKeyReleasePolicy.ts | 8 +++-- ...yProps.ts => IKeyReleasePolicySnpProps.ts} | 2 +- src/policies/KeyReleasePolicy.ts | 29 +++++++++++++++++++ src/utils/Tooling.ts | 24 --------------- test/e2e-test/src/api.ts | 6 ++-- .../policies/IKeyReleasePolicyProps.test.ts | 4 +-- 10 files changed, 52 insertions(+), 42 deletions(-) rename src/policies/{IKeyReleasePolicyProps.ts => IKeyReleasePolicySnpProps.ts} (96%) diff --git a/README.md b/README.md index f17cba05..e03b2747 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ echo $wrapped curl $KMS_URL/app/unwrapKey -X POST --cacert ${KEYS_DIR}/service_cert.pem --cert ${KEYS_DIR}/user0_cert.pem --key ${KEYS_DIR}/user0_privk.pem -H "Content-Type: application/json" -d "{\"attestation\":$ATTESTATION, \"wrappingKey\":$WRAPPING_KEY, \"wrapped\":\"$wrapped\", \"wrappedKid\":\"$kid\"}" | jq # Get the latest private key (Tink) -wrapped_resp=$(curl $KMS_URL/app/key?fmt=tink -X POST --cacert ${KEYS_DIR}/service_cert.pem --cert ${KEYS_DIR}/user0_cert.pem --key ${KEYS_DIR}/user0_privk.pem -H "Content-Type: application/json" -d "{\"attestation\":$ATTESTATION, \"wrappingKey\":$WRAPPING_KEY}" | jq) +wrapped_resp=$(curl $KMS_URL/app/key?fmt=tink -X POST --cacert ${KEYS_DIR}/service_cert.pem --cert ${KEYS_DIR}/member0_cert.pem --key ${KEYS_DIR}/member0_privk.pem -H "Content-Type: application/json" -d "{\"attestation\":$ATTESTATION, \"wrappingKey\":$WRAPPING_KEY}" | jq) echo $wrapped_resp key=$(echo $wrapped_resp | jq -r '.wrapped' | jq -R 'fromjson' | jq '.keys[0]') # It has a format of "azu-kms://" like "azu-kms://tGe-cVHzNyim2Z0PzHO4y0ClXCa5J6x-bh7GmGJTr3c". @@ -182,7 +182,7 @@ wrapped=$(echo $keyMaterial | jq '.encryptedKeyset' -r) echo $wrapped # Unwrap key with attestation (Tink) -curl $KMS_URL/app/unwrapKey?fmt=tink -X POST --cacert ${KEYS_DIR}/service_cert.pem --cert ${KEYS_DIR}/user0_cert.pem --key ${KEYS_DIR}/user0_privk.pem -H "Content-Type: application/json" -d "{\"attestation\":$ATTESTATION, \"wrappingKey\":$WRAPPING_KEY, \"wrapped\":\"$wrapped\", \"wrappedKid\":\"$kid\"}" | jq +curl $KMS_URL/app/unwrapKey?fmt=tink -X POST --cacert ${KEYS_DIR}/service_cert.pem --cert ${KEYS_DIR}/member0_cert.pem --key ${KEYS_DIR}/member0_privk.pem -H "Content-Type: application/json" -d "{\"attestation\":$ATTESTATION, \"wrappingKey\":$WRAPPING_KEY, \"wrapped\":\"$wrapped\", \"wrappedKid\":\"$kid\"}" | jq # Get key release policy curl $KMS_URL/app/key_release_policy --cacert ${KEYS_DIR}/service_cert.pem --cert ${KEYS_DIR}/user0_cert.pem --key ${KEYS_DIR}/user0_privk.pem -H "Content-Type: application/json" | jq diff --git a/src/attestation/AttestationValidation.ts b/src/attestation/AttestationValidation.ts index 06c05148..8afad114 100644 --- a/src/attestation/AttestationValidation.ts +++ b/src/attestation/AttestationValidation.ts @@ -12,8 +12,8 @@ import { } from "@microsoft/ccf-app/global"; import { SnpAttestationClaims } from "./SnpAttestationClaims"; import { keyReleasePolicyMap } from "../repositories/Maps"; -import { getKeyReleasePolicy } from "../utils/Tooling"; import { Logger } from "../utils/Logger"; +import { KeyReleasePolicy } from "../policies/KeyReleasePolicy"; // Validate the attestation by means of the key release policy export const validateAttestation = ( @@ -116,7 +116,7 @@ export const validateAttestation = ( ); // Get the key release policy - const keyReleasePolicy = getKeyReleasePolicy(keyReleasePolicyMap); + const keyReleasePolicy = KeyReleasePolicy.getKeyReleasePolicyFromMap(keyReleasePolicyMap); Logger.debug( `Key release policy: ${JSON.stringify( keyReleasePolicy, diff --git a/src/endpoints/keyReleasePolicyEndpoint.ts b/src/endpoints/keyReleasePolicyEndpoint.ts index 94f9bfbb..bb32cb90 100644 --- a/src/endpoints/keyReleasePolicyEndpoint.ts +++ b/src/endpoints/keyReleasePolicyEndpoint.ts @@ -3,10 +3,11 @@ import * as ccfapp from "@microsoft/ccf-app"; import { ServiceResult } from "../utils/ServiceResult"; -import { IKeyReleasePolicyProps } from "../policies/IKeyReleasePolicyProps"; -import { enableEndpoint, getKeyReleasePolicy } from "../utils/Tooling"; +import { IKeyReleasePolicySnpProps } from "../policies/IKeyReleasePolicySnpProps"; +import { enableEndpoint } from "../utils/Tooling"; import { keyReleasePolicyMap } from "../repositories/Maps"; import { ServiceRequest } from "../utils/ServiceRequest"; +import { KeyReleasePolicy } from "../policies/KeyReleasePolicy"; // Enable the endpoint enableEndpoint(); @@ -17,7 +18,7 @@ enableEndpoint(); */ export const keyReleasePolicy = ( request: ccfapp.Request, -): ServiceResult => { +): ServiceResult => { const name = "keyReleasePolicy"; const serviceRequest = new ServiceRequest(name, request); @@ -25,6 +26,6 @@ export const keyReleasePolicy = ( const [_, isValidIdentity] = serviceRequest.isAuthenticated(); if (isValidIdentity.failure) return isValidIdentity; - const result = getKeyReleasePolicy(keyReleasePolicyMap); - return ServiceResult.Succeeded(result); + const result = KeyReleasePolicy.getKeyReleasePolicyFromMap(keyReleasePolicyMap); + return ServiceResult.Succeeded(result); }; diff --git a/src/index.ts b/src/index.ts index 1074e068..36d65ee7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,6 @@ export * from "./endpoints/keyReleasePolicyEndpoint"; export * from "./endpoints/IKeyItem"; export * from "./endpoints/KeyGeneration"; export * from "./endpoints/TinkKey"; -export * from "./policies/IKeyReleasePolicyProps"; +export * from "./policies/IKeyReleasePolicySnpProps"; export * from "./utils/Tooling"; export * from "./utils/ServiceResult"; diff --git a/src/policies/IKeyReleasePolicy.ts b/src/policies/IKeyReleasePolicy.ts index ce4a14df..f1665310 100644 --- a/src/policies/IKeyReleasePolicy.ts +++ b/src/policies/IKeyReleasePolicy.ts @@ -1,9 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { IKeyReleasePolicyProps } from ".."; +import { IKeyReleasePolicySnpProps } from ".."; export interface IKeyReleasePolicy { type: string; - claims: IKeyReleasePolicyProps; + operators?: { + gt?: IKeyReleasePolicySnpProps; + gte?: IKeyReleasePolicySnpProps; + } + claims: IKeyReleasePolicySnpProps; } diff --git a/src/policies/IKeyReleasePolicyProps.ts b/src/policies/IKeyReleasePolicySnpProps.ts similarity index 96% rename from src/policies/IKeyReleasePolicyProps.ts rename to src/policies/IKeyReleasePolicySnpProps.ts index 8d1f9b11..36f9ba19 100644 --- a/src/policies/IKeyReleasePolicyProps.ts +++ b/src/policies/IKeyReleasePolicySnpProps.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -export interface IKeyReleasePolicyProps { +export interface IKeyReleasePolicySnpProps { "x-ms-attestation-type"?: string[]; "x-ms-compliance-status"?: string[]; "x-ms-policy-hash"?: string[]; diff --git a/src/policies/KeyReleasePolicy.ts b/src/policies/KeyReleasePolicy.ts index 1ac448b3..e92b365e 100644 --- a/src/policies/KeyReleasePolicy.ts +++ b/src/policies/KeyReleasePolicy.ts @@ -1,11 +1,40 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import * as ccfapp from "@microsoft/ccf-app"; +import { ccf } from "@microsoft/ccf-app/global"; import { IKeyReleasePolicy } from "./IKeyReleasePolicy"; +import { IKeyReleasePolicySnpProps } from "./IKeyReleasePolicySnpProps"; +import { Logger } from "../utils/Logger"; export class KeyReleasePolicy implements IKeyReleasePolicy { public type = "add"; public claims = { "x-ms-attestation-type": ["snp"], }; + + +/** + * Retrieves the key release policy from a key release policy map. + * @param keyReleasePolicyMap - The key release policy map. + * @returns The key release policy as an object. + */ +public static getKeyReleasePolicyFromMap = ( + keyReleasePolicyMap: ccfapp.KvMap, +): IKeyReleasePolicySnpProps => { + const result: IKeyReleasePolicySnpProps = {}; + keyReleasePolicyMap.forEach((values: ArrayBuffer, key: ArrayBuffer) => { + const kvKey = ccf.bufToStr(key); + const kvValue = JSON.parse(ccf.bufToStr(values)); + result[kvKey] = kvValue; + Logger.debug(`key policy item with key: ${kvKey} and value: ${kvValue}`); + }); + Logger.debug( + `Resulting key release policy: ${JSON.stringify( + result, + )}, keys: ${Object.keys(result)}, keys: ${Object.keys(result).length}`, + ); + return result; +}; + } diff --git a/src/utils/Tooling.ts b/src/utils/Tooling.ts index 4984f450..187d863a 100644 --- a/src/utils/Tooling.ts +++ b/src/utils/Tooling.ts @@ -2,7 +2,6 @@ // Licensed under the MIT license. import * as ccfapp from "@microsoft/ccf-app"; -import { IKeyReleasePolicyProps } from "../policies/IKeyReleasePolicyProps"; import { ccf } from "@microsoft/ccf-app/global"; import { Logger } from "./Logger"; @@ -68,29 +67,6 @@ export const isPemPublicKey = (key: string): boolean => { return isLiteralNewline || isNewline; }; -/** - * Retrieves the key release policy from a key release policy map. - * @param keyReleasePolicyMap - The key release policy map. - * @returns The key release policy as an object. - */ -export const getKeyReleasePolicy = ( - keyReleasePolicyMap: ccfapp.KvMap, -): IKeyReleasePolicyProps => { - const result: IKeyReleasePolicyProps = {}; - keyReleasePolicyMap.forEach((values: ArrayBuffer, key: ArrayBuffer) => { - const kvKey = ccf.bufToStr(key); - const kvValue = JSON.parse(ccf.bufToStr(values)); - result[kvKey] = kvValue; - Logger.debug(`key policy item with key: ${kvKey} and value: ${kvValue}`); - }); - Logger.debug( - `Resulting key release policy: ${JSON.stringify( - result, - )}, keys: ${Object.keys(result)}, keys: ${Object.keys(result).length}`, - ); - return result; -}; - /** * Sets the key headers. * @returns An object containing the key headers. diff --git a/test/e2e-test/src/api.ts b/test/e2e-test/src/api.ts index a85950f9..b389586f 100644 --- a/test/e2e-test/src/api.ts +++ b/test/e2e-test/src/api.ts @@ -6,7 +6,7 @@ import axios from "axios"; import { IHeartbeatResponse, IKeyItem, - IKeyReleasePolicyProps, + IKeyReleasePolicySnpProps, ITinkPublicKeySet, } from "../../../src"; import { IWrapped } from "../../../src/endpoints/KeyWrapper"; @@ -400,7 +400,7 @@ export default class Api { httpsAgent: https.Agent, authorizationHeader?: string, ): Promise< - [number, IKeyReleasePolicyProps, { [key: string]: string | number }] + [number, IKeyReleasePolicySnpProps, { [key: string]: string | number }] > { console.log(`${member.name} Get key release policy`); console.log(`Get key release policy props:`, props); @@ -444,7 +444,7 @@ export default class Api { } return [ response.statusCode, - JSON.parse(response.data), + JSON.parse(response.data), response.headers, ]; } diff --git a/test/unit-test/policies/IKeyReleasePolicyProps.test.ts b/test/unit-test/policies/IKeyReleasePolicyProps.test.ts index 944560cc..8b20f515 100644 --- a/test/unit-test/policies/IKeyReleasePolicyProps.test.ts +++ b/test/unit-test/policies/IKeyReleasePolicyProps.test.ts @@ -4,12 +4,12 @@ // Use the CCF polyfill to mock-up all key-value map functionality for unit-test import "@microsoft/ccf-app/polyfill.js"; import { describe, expect, test } from "@jest/globals"; -import { IKeyReleasePolicyProps } from "../../../src"; +import { IKeyReleasePolicySnpProps } from "../../../src"; describe("Test Key Release Policy properties", () => { test("Should get all data successfully", () => { // Arrange - const policy: IKeyReleasePolicyProps = {}; + const policy: IKeyReleasePolicySnpProps = {}; // Act policy["x-ms-attestation-type"] = ["sevsnpvm", "none"]; From b5c6f1baee588eb8bc197acae93d2fc05ee717ed Mon Sep 17 00:00:00 2001 From: beejones Date: Tue, 24 Sep 2024 08:48:11 +0000 Subject: [PATCH 2/6] change key release policy to work with IKeyReleasePolicy instead of props --- governance/constitution/kms_actions.js | 72 ++++++++-------- src/attestation/AttestationValidation.ts | 40 +-------- src/endpoints/keyReleasePolicyEndpoint.ts | 6 +- src/policies/KeyReleasePolicy.ts | 86 ++++++++++++++++--- test/e2e-test/src/index.ts | 12 +-- .../policies/IKeyReleasePolicyProps.test.ts | 21 ----- .../IKeyReleasePolicySnpProps.test.ts | 45 ++++++++++ 7 files changed, 167 insertions(+), 115 deletions(-) delete mode 100644 test/unit-test/policies/IKeyReleasePolicyProps.test.ts create mode 100644 test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts diff --git a/governance/constitution/kms_actions.js b/governance/constitution/kms_actions.js index 7fc53faa..8a873dbb 100644 --- a/governance/constitution/kms_actions.js +++ b/governance/constitution/kms_actions.js @@ -117,14 +117,28 @@ actions.set( }; const keyReleaseMapName = "public:ccf.gov.policies.key_release"; // Function to add key release policy claims - const add = (claims) => { - let items = []; + const add = (type, claims) => { + let items = {}; console.log( - `Add claims to key release policy: ${JSON.stringify(claims)}`, + `Add claims to key release policy for ${type}: ${JSON.stringify(claims)}`, ); + // get all the claims for type from the KV + let keyBuf = ccf.strToBuf(type); + if (ccf.kv[keyReleaseMapName].has(keyBuf)) { + // type is already available + const itemsBuf = ccf.kv[keyReleaseMapName].get(keyBuf); + items = ccf.bufToStr(itemsBuf); + console.log(`KRP add ${type}=>key: ${type} already exist: ${items} in the key release policy`); + items = JSON.parse(items); + } else { + console.log(`KRP add ${type}=>key: ${type} is new in the key release policy`); + } + + + // iterate over every claim Object.keys(claims).forEach((key) => { if (CLAIMS[key] === undefined) { - throw new Error(`The claim ${key} is not an allowed claim`); + throw new Error(`KRP add ${type}=>The claim ${key} is not an allowed claim`); } let item = claims[key]; // Make sure item is always an array @@ -132,42 +146,30 @@ actions.set( item = [item]; } - let keyBuf = ccf.strToBuf(key); - if (ccf.kv[keyReleaseMapName].has(keyBuf)) { - // Key is already available - const itemsBuf = ccf.kv[keyReleaseMapName].get(keyBuf); - items = ccf.bufToStr(itemsBuf); - console.log(`key: ${key} already exist: ${items}`); - items = JSON.parse(items); - if (typeof item[0] === "boolean") { - //booleans are single value arrays - items = item; - } else { - // loop through the input and add it to the existing set - item.forEach((i) => { - items.push(i); - }); - } + if (items[key] !== undefined) { + item.forEach((i) => { + console.log(`KRP add ${type}=>Adding ${i} to ${key}`); + items[key].push(i); + }) } else { - // set single value - items = item; + items[key] = item; + console.log(`KRP add ${type}=>currrent items: `, items); } - - // prepare and store items - let jsonItems = JSON.stringify(items); - let jsonItemsBuf = ccf.strToBuf(jsonItems); - console.log( - `Voted key release policy item. Key: ${key}, value: ${jsonItems}`, - ); - ccf.kv[keyReleaseMapName].set(keyBuf, jsonItemsBuf); }); + + // Safe into KV + console.log(`KRP add ${type}=>items: `, items); + let jsonItems = JSON.stringify(items); + console.log(`KRP add ${type}=>Add claims to key release policy for ${type}: ${jsonItems}`); + let jsonItemsBuf = ccf.strToBuf(jsonItems); + ccf.kv[keyReleaseMapName].set(keyBuf, jsonItemsBuf); }; // Function to remove key release policy claims - const remove = (claims) => { + const remove = (type, claims) => { let items = []; console.log( - `Remove claims to key release policy: ${JSON.stringify(claims)}`, + `Remove claims to key release policy for ${type}: ${JSON.stringify(claims)}`, ); Object.keys(claims).forEach((key) => { if (CLAIMS[key] === undefined) { @@ -207,7 +209,7 @@ actions.set( if (items.length === 0) { ccf.kv[keyReleaseMapName].delete(keyBuf); } else { - let jsonItems = JSON.stringify(items); + let jsonItems = JSON.stringify({ type, claims: items }); let jsonItemsBuf = ccf.strToBuf(jsonItems); ccf.kv[keyReleaseMapName].set(keyBuf, jsonItemsBuf); } @@ -223,10 +225,10 @@ actions.set( const type = args.type; switch (type) { case "add": - add(args.claims); + add("claims", args.claims); break; case "remove": - remove(args.claims); + remove("claims", args.claims); break; default: throw new Error( diff --git a/src/attestation/AttestationValidation.ts b/src/attestation/AttestationValidation.ts index 8afad114..35e66513 100644 --- a/src/attestation/AttestationValidation.ts +++ b/src/attestation/AttestationValidation.ts @@ -120,12 +120,11 @@ export const validateAttestation = ( Logger.debug( `Key release policy: ${JSON.stringify( keyReleasePolicy, - )}, keys: ${Object.keys(keyReleasePolicy)}, keys: ${ - Object.keys(keyReleasePolicy).length + )}, keys: ${Object.keys(keyReleasePolicy)}, keys: ${Object.keys(keyReleasePolicy).length }`, ); - if (Object.keys(keyReleasePolicy).length === 0) { + if (Object.keys(keyReleasePolicy.claims).length === 0) { return ServiceResult.Failed( { errorMessage: @@ -134,39 +133,8 @@ export const validateAttestation = ( 400, ); } - - for (let inx = 0; inx < Object.keys(keyReleasePolicy).length; inx++) { - const key = Object.keys(keyReleasePolicy)[inx]; - - // check if key is in attestation - const attestationValue = attestationClaims[key]; - const policyValue = keyReleasePolicy[key]; - const isUndefined = typeof attestationValue === "undefined"; - Logger.debug( - `Checking key ${key}, typeof attestationValue: ${typeof attestationValue}, isUndefined: ${isUndefined}, attestation value: ${attestationValue}, policyValue: ${policyValue}`, - ); - if (isUndefined) { - return ServiceResult.Failed( - { errorMessage: `Missing claim in attestation: ${key}` }, - 400, - ); - } - if ( - policyValue.filter((p) => { - Logger.debug(`Check if policy value ${p} === ${attestationValue}`); - return p === attestationValue; - }).length === 0 - ) { - return ServiceResult.Failed( - { - errorMessage: `Attestation claim ${key}, value ${attestationValue} does not match policy values: ${policyValue}`, - }, - 400, - ); - } - } - - return ServiceResult.Succeeded(attestationClaims); + const policyValidationResult = KeyReleasePolicy.validateKeyReleasePolicy(keyReleasePolicy, attestationClaims); + return policyValidationResult; } catch (exception: any) { return ServiceResult.Failed( { errorMessage: `Internal error: ${exception.message}` }, diff --git a/src/endpoints/keyReleasePolicyEndpoint.ts b/src/endpoints/keyReleasePolicyEndpoint.ts index bb32cb90..221ae64e 100644 --- a/src/endpoints/keyReleasePolicyEndpoint.ts +++ b/src/endpoints/keyReleasePolicyEndpoint.ts @@ -3,11 +3,11 @@ import * as ccfapp from "@microsoft/ccf-app"; import { ServiceResult } from "../utils/ServiceResult"; -import { IKeyReleasePolicySnpProps } from "../policies/IKeyReleasePolicySnpProps"; import { enableEndpoint } from "../utils/Tooling"; import { keyReleasePolicyMap } from "../repositories/Maps"; import { ServiceRequest } from "../utils/ServiceRequest"; import { KeyReleasePolicy } from "../policies/KeyReleasePolicy"; +import { IKeyReleasePolicy } from "../policies/IKeyReleasePolicy"; // Enable the endpoint enableEndpoint(); @@ -18,7 +18,7 @@ enableEndpoint(); */ export const keyReleasePolicy = ( request: ccfapp.Request, -): ServiceResult => { +): ServiceResult => { const name = "keyReleasePolicy"; const serviceRequest = new ServiceRequest(name, request); @@ -27,5 +27,5 @@ export const keyReleasePolicy = ( if (isValidIdentity.failure) return isValidIdentity; const result = KeyReleasePolicy.getKeyReleasePolicyFromMap(keyReleasePolicyMap); - return ServiceResult.Succeeded(result); + return ServiceResult.Succeeded(result); }; diff --git a/src/policies/KeyReleasePolicy.ts b/src/policies/KeyReleasePolicy.ts index e92b365e..8f5a9c96 100644 --- a/src/policies/KeyReleasePolicy.ts +++ b/src/policies/KeyReleasePolicy.ts @@ -6,6 +6,8 @@ import { ccf } from "@microsoft/ccf-app/global"; import { IKeyReleasePolicy } from "./IKeyReleasePolicy"; import { IKeyReleasePolicySnpProps } from "./IKeyReleasePolicySnpProps"; import { Logger } from "../utils/Logger"; +import { ServiceResult } from "../utils/ServiceResult"; +import { IAttestationReport } from "../attestation/ISnpAttestationReport"; export class KeyReleasePolicy implements IKeyReleasePolicy { public type = "add"; @@ -13,6 +15,62 @@ export class KeyReleasePolicy implements IKeyReleasePolicy { "x-ms-attestation-type": ["snp"], }; + private static validateKeyReleasePolicyClaims(keyReleasePolicyClaims: IKeyReleasePolicySnpProps, attestationClaims: IAttestationReport): ServiceResult { + if (keyReleasePolicyClaims === null || keyReleasePolicyClaims === undefined) { + return ServiceResult.Failed( + { errorMessage: "Missing key release policy" }, + 500, + ); + } + if (attestationClaims === null || attestationClaims === undefined) { + return ServiceResult.Failed( + { errorMessage: "Missing attestation claims" }, + 500, + ); + } + + const claims = keyReleasePolicyClaims; + for (let inx = 0; inx < Object.keys(claims).length; inx++) { + const key = Object.keys(claims)[inx]; + + // check if key is in attestation + const attestationValue = attestationClaims[key]; + const policyValue = keyReleasePolicyClaims[key]; + const isUndefined = typeof attestationValue === "undefined"; + Logger.debug( + `Checking key ${key}, typeof attestationValue: ${typeof attestationValue}, isUndefined: ${isUndefined}, attestation value: ${attestationValue}, policyValue: ${policyValue}`, + ); + if (isUndefined) { + return ServiceResult.Failed( + { errorMessage: `Missing claim in attestation: ${key}` }, + 400, + ); + } + if ( + policyValue.filter((p) => { + Logger.debug(`Check if policy value ${p} === ${attestationValue}`); + return p.toString() === attestationValue.toString(); + }).length === 0 + ) { + return ServiceResult.Failed( + { + errorMessage: `Attestation claim ${key}, value ${attestationValue} does not match policy values: ${policyValue}`, + }, + 400, + ); + } + } + return ServiceResult.Succeeded(attestationClaims); + } + +public static validateKeyReleasePolicy(keyReleasePolicy: IKeyReleasePolicy, attestationClaims: IAttestationReport): ServiceResult { + const policyValidationResult = KeyReleasePolicy.validateKeyReleasePolicyClaims(keyReleasePolicy.claims, attestationClaims); + if (!policyValidationResult.success) { + return policyValidationResult; + } + + return policyValidationResult; +} /** * Retrieves the key release policy from a key release policy map. @@ -21,20 +79,20 @@ export class KeyReleasePolicy implements IKeyReleasePolicy { */ public static getKeyReleasePolicyFromMap = ( keyReleasePolicyMap: ccfapp.KvMap, -): IKeyReleasePolicySnpProps => { - const result: IKeyReleasePolicySnpProps = {}; - keyReleasePolicyMap.forEach((values: ArrayBuffer, key: ArrayBuffer) => { - const kvKey = ccf.bufToStr(key); - const kvValue = JSON.parse(ccf.bufToStr(values)); - result[kvKey] = kvValue; - Logger.debug(`key policy item with key: ${kvKey} and value: ${kvValue}`); - }); - Logger.debug( - `Resulting key release policy: ${JSON.stringify( - result, - )}, keys: ${Object.keys(result)}, keys: ${Object.keys(result).length}`, - ); - return result; +): IKeyReleasePolicy => { + let keyReleasePolicy: IKeyReleasePolicy = { type: "", claims: {} }; + let kvKey = "claims" + let kvKeyBuf = ccf.strToBuf(kvKey); + let kvValueBuf = keyReleasePolicyMap.get(kvKeyBuf); + if (!kvValueBuf) { + throw new Error("Key release policy claims not found in the key release policy map"); + } + let kvValue = ccf.bufToStr(kvValueBuf); + keyReleasePolicy[kvKey] = JSON.parse(kvValue) as IKeyReleasePolicySnpProps; + + + Logger.info(`Resulting key release policy: `, keyReleasePolicy); + return keyReleasePolicy; }; } diff --git a/test/e2e-test/src/index.ts b/test/e2e-test/src/index.ts index 42814536..c095d1d9 100644 --- a/test/e2e-test/src/index.ts +++ b/test/e2e-test/src/index.ts @@ -515,16 +515,16 @@ class Demo { console.log("keyReleasePolicy response: ", keyResponse); Demo.assert( - `keyResponse["x-ms-sevsnpvm-smt-allowed"][0] === true`, - keyResponse["x-ms-sevsnpvm-smt-allowed"][0] === true, + `keyResponse.claims["x-ms-sevsnpvm-smt-allowed"][0] === true`, + keyResponse.claims["x-ms-sevsnpvm-smt-allowed"][0] === true, ); Demo.assert( - `keyResponse["x-ms-ver"][0] === '2'`, - keyResponse["x-ms-ver"][0] === "2", + `keyResponse.claims["x-ms-ver"][0] === '2'`, + keyResponse.claims["x-ms-ver"][0] === "2", ); Demo.assert( - `keyResponse["x-ms-sevsnpvm-is-debuggable"][0] === false`, - keyResponse["x-ms-sevsnpvm-is-debuggable"][0] === false, + `keyResponse.claims["x-ms-sevsnpvm-is-debuggable"][0] === false`, + keyResponse.claims["x-ms-sevsnpvm-is-debuggable"][0] === false, ); // JWT not allowed diff --git a/test/unit-test/policies/IKeyReleasePolicyProps.test.ts b/test/unit-test/policies/IKeyReleasePolicyProps.test.ts deleted file mode 100644 index 8b20f515..00000000 --- a/test/unit-test/policies/IKeyReleasePolicyProps.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -// Use the CCF polyfill to mock-up all key-value map functionality for unit-test -import "@microsoft/ccf-app/polyfill.js"; -import { describe, expect, test } from "@jest/globals"; -import { IKeyReleasePolicySnpProps } from "../../../src"; - -describe("Test Key Release Policy properties", () => { - test("Should get all data successfully", () => { - // Arrange - const policy: IKeyReleasePolicySnpProps = {}; - - // Act - policy["x-ms-attestation-type"] = ["sevsnpvm", "none"]; - - // Assert - expect(policy["x-ms-attestation-type"]).toContain("sevsnpvm"); - expect(policy["x-ms-attestation-type"]).toContain("none"); - }); -}); diff --git a/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts b/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts new file mode 100644 index 00000000..d5a51856 --- /dev/null +++ b/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// Use the CCF polyfill to mock-up all key-value map functionality for unit-test +import "@microsoft/ccf-app/polyfill.js"; +import { describe, expect, test } from "@jest/globals"; +import { IKeyReleasePolicySnpProps } from "../../../src"; +import { KeyReleasePolicy } from "../../../src/policies/KeyReleasePolicy"; +import { Logger, LogLevel } from "../../../src/utils/Logger"; +import { IAttestationReport } from "../../../src/attestation/ISnpAttestationReport"; +import { IKeyReleasePolicy } from "../../../src/policies/IKeyReleasePolicy"; + +describe("Test Key Release Policy properties", () => { + test("Should get all data successfully", () => { + // Arrange + const policy: IKeyReleasePolicySnpProps = {}; + + // Act + policy["x-ms-attestation-type"] = ["sevsnpvm", "none"]; + + // Assert + expect(policy["x-ms-attestation-type"]).toContain("sevsnpvm"); + expect(policy["x-ms-attestation-type"]).toContain("none"); + }); + + describe("Validate Key Release Policy properties", () => { + test("Should validate successfully", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: IKeyReleasePolicy = {type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }}; + const attestationClaims: IAttestationReport = { "x-ms-attestation-type": "sevsnpvm" }; + + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy(policy, attestationClaims); + + // Debugging statements + console.log("Policy:", policy); + console.log("Attestation Claims:", attestationClaims); + console.log("Validation Result:", validationResult); + + // Assert + expect(validationResult.success).toBe(true); + }); + }); +}); From e27f5fdbf22f038c62520f35245360853d9344b1 Mon Sep 17 00:00:00 2001 From: beejones Date: Wed, 25 Sep 2024 15:14:54 +0000 Subject: [PATCH 3/6] add unit tests and remove operations --- README.md | 2 +- governance/constitution/kms_actions.js | 239 ++++++++++++++---- .../policies/key-release-policy-add.json | 6 + .../policies/key-release-policy-remove.json | 6 + governance/policies/key-rotation-policy.json | 20 +- scripts/setup_mCCF.sh | 4 +- src/attestation/AttestationValidation.ts | 11 +- src/endpoints/keyReleasePolicyEndpoint.ts | 3 +- src/policies/IKeyReleasePolicy.ts | 6 +- src/policies/IKeyReleasePolicySnpProps.ts | 40 +-- src/policies/KeyReleasePolicy.ts | 206 +++++++++++++-- .../IKeyReleasePolicySnpProps.test.ts | 236 +++++++++++++++-- 12 files changed, 649 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index e03b2747..bdebdd52 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ echo $wrapped curl $KMS_URL/app/unwrapKey?fmt=tink -X POST --cacert ${KEYS_DIR}/service_cert.pem --cert ${KEYS_DIR}/member0_cert.pem --key ${KEYS_DIR}/member0_privk.pem -H "Content-Type: application/json" -d "{\"attestation\":$ATTESTATION, \"wrappingKey\":$WRAPPING_KEY, \"wrapped\":\"$wrapped\", \"wrappedKid\":\"$kid\"}" | jq # Get key release policy -curl $KMS_URL/app/key_release_policy --cacert ${KEYS_DIR}/service_cert.pem --cert ${KEYS_DIR}/user0_cert.pem --key ${KEYS_DIR}/user0_privk.pem -H "Content-Type: application/json" | jq +curl $KMS_URL/app/keyReleasePolicy --cacert ${KEYS_DIR}/service_cert.pem --cert ${KEYS_DIR}/member0_cert.pem --key ${KEYS_DIR}/member0_privk.pem -H "Content-Type: application/json" | jq # Get receipt curl $KMS_URL/receipt?transaction_id=2.20 --cacert ${KEYS_DIR}/service_cert.pem --cert ${KEYS_DIR}/user0_cert.pem --key ${KEYS_DIR}/user0_privk.pem -H "Content-Type: application/json" -i -w '\n' diff --git a/governance/constitution/kms_actions.js b/governance/constitution/kms_actions.js index 8a873dbb..fe341a32 100644 --- a/governance/constitution/kms_actions.js +++ b/governance/constitution/kms_actions.js @@ -128,17 +128,22 @@ actions.set( // type is already available const itemsBuf = ccf.kv[keyReleaseMapName].get(keyBuf); items = ccf.bufToStr(itemsBuf); - console.log(`KRP add ${type}=>key: ${type} already exist: ${items} in the key release policy`); + console.log( + `KRP add ${type}=>key: ${type} already exist: ${items} in the key release policy`, + ); items = JSON.parse(items); } else { - console.log(`KRP add ${type}=>key: ${type} is new in the key release policy`); + console.log( + `KRP add ${type}=>key: ${type} is new in the key release policy`, + ); } - // iterate over every claim Object.keys(claims).forEach((key) => { if (CLAIMS[key] === undefined) { - throw new Error(`KRP add ${type}=>The claim ${key} is not an allowed claim`); + throw new Error( + `KRP add ${type}=>The claim ${key} is not an allowed claim`, + ); } let item = claims[key]; // Make sure item is always an array @@ -150,7 +155,7 @@ actions.set( item.forEach((i) => { console.log(`KRP add ${type}=>Adding ${i} to ${key}`); items[key].push(i); - }) + }); } else { items[key] = item; console.log(`KRP add ${type}=>currrent items: `, items); @@ -160,20 +165,91 @@ actions.set( // Safe into KV console.log(`KRP add ${type}=>items: `, items); let jsonItems = JSON.stringify(items); - console.log(`KRP add ${type}=>Add claims to key release policy for ${type}: ${jsonItems}`); + console.log( + `KRP add ${type}=>Add claims to key release policy for ${type}: ${jsonItems}`, + ); + let jsonItemsBuf = ccf.strToBuf(jsonItems); + ccf.kv[keyReleaseMapName].set(keyBuf, jsonItemsBuf); + }; + // Function to add key release policy operator + const addOperator = (type, claims) => { + let items = {}; + console.log( + `Add claims to key release policy for ${type}: ${JSON.stringify(claims)}`, + ); + // get all the claims for type from the KV + let keyBuf = ccf.strToBuf(type); + if (ccf.kv[keyReleaseMapName].has(keyBuf)) { + // type is already available + const itemsBuf = ccf.kv[keyReleaseMapName].get(keyBuf); + items = ccf.bufToStr(itemsBuf); + console.log( + `KRP add ${type}=>key: ${type} already exist: ${items} in the key release policy`, + ); + items = JSON.parse(items); + } else { + console.log( + `KRP add ${type}=>key: ${type} is new in the key release policy`, + ); + } + + // iterate over every claim + Object.keys(claims).forEach((key) => { + if (CLAIMS[key] === undefined) { + throw new Error( + `KRP add ${type}=>The claim ${key} is not an allowed claim`, + ); + } + let item = claims[key]; + // Make sure item is always an array + if (Array.isArray(item)) { + throw new Error(`The operator claim ${key} cannot be an array`); + } + + items[key] = item; + }); + + // Safe into KV + console.log(`KRP add ${type}=>items: `, items); + let jsonItems = JSON.stringify(items); + console.log( + `KRP add ${type}=>Add claims to key release policy for ${type}: ${jsonItems}`, + ); let jsonItemsBuf = ccf.strToBuf(jsonItems); ccf.kv[keyReleaseMapName].set(keyBuf, jsonItemsBuf); }; // Function to remove key release policy claims const remove = (type, claims) => { - let items = []; + let items = {}; console.log( - `Remove claims to key release policy for ${type}: ${JSON.stringify(claims)}`, + `Remove claims from key release policy for ${type}: ${JSON.stringify(claims)}`, ); + // get all the claims for type from the KV + let keyBuf = ccf.strToBuf(type); + if (ccf.kv[keyReleaseMapName].has(keyBuf)) { + // type is available + const itemsBuf = ccf.kv[keyReleaseMapName].get(keyBuf); + items = ccf.bufToStr(itemsBuf); + console.log( + `KRP remove ${type}=>key: ${type} exist: ${items} in the key release policy`, + ); + items = JSON.parse(items); + } else { + console.log( + `KRP remove ${type}=>key: ${type} does not exists in the key release policy`, + ); + throw new Error( + `The key ${type} does not exists in the key release policy`, + ); + } + + // iterate over every claim Object.keys(claims).forEach((key) => { if (CLAIMS[key] === undefined) { - throw new Error(`The claim ${key} is not an allowed claim`); + throw new Error( + `KRP remove ${type}=>The claim ${key} is not an allowed claim`, + ); } let item = claims[key]; // Make sure item is always an array @@ -181,54 +257,114 @@ actions.set( item = [item]; } - let keyBuf = ccf.strToBuf(key); - if (ccf.kv[keyReleaseMapName].has(keyBuf)) { - // Key must be available - const itemsBuf = ccf.kv[keyReleaseMapName].get(keyBuf); - items = ccf.bufToStr(itemsBuf); - console.log(`key: ${key} exist: ${items}`); - items = JSON.parse(items); - if (typeof item[0] === "boolean") { - //booleans are single value arrays, removing will remove the whole key - ccf.kv[keyReleaseMapName].delete(keyBuf); - } else { - // loop through the input and delete it from the existing set - item.forEach((i) => { - if (items.filter((ii) => ii === i).length === 0) { - throw new Error( - `Trying to remove value '${i}' from ${items} and it does not exist`, - ); - } - // Remove value from list - const index = items.indexOf(i); - if (index > -1) { - items.splice(index, 1); - } - }); - // update items - if (items.length === 0) { - ccf.kv[keyReleaseMapName].delete(keyBuf); - } else { - let jsonItems = JSON.stringify({ type, claims: items }); - let jsonItemsBuf = ccf.strToBuf(jsonItems); - ccf.kv[keyReleaseMapName].set(keyBuf, jsonItemsBuf); + if (items[key] !== undefined) { + item.forEach((i) => { + console.log(`KRP remove ${type}=>Removing ${i} from ${key}`); + items[key] = items[key].filter((value) => value !== i); + if (items[key].length === 0) { + delete items[key]; } - } + }); } else { + console.log( + `KRP remove ${type}=>Claim ${key} not found in the key release policy`, + ); + throw new Error( + `The claim ${key} does not exists in the key release policy`, + ); + } + }); + + // Safe into KV + console.log(`KRP remove ${type}=>items: `, items); + let jsonItems = JSON.stringify(items); + console.log( + `KRP remove ${type}=>Remove claims from key release policy for ${type}: ${jsonItems}`, + ); + let jsonItemsBuf = ccf.strToBuf(jsonItems); + ccf.kv[keyReleaseMapName].set(keyBuf, jsonItemsBuf); + }; + + const removeOperator = (type, claims) => { + let items = {}; + console.log( + `Remove claims from key release policy for ${type}: ${JSON.stringify(claims)}`, + ); + // get all the claims for type from the KV + let keyBuf = ccf.strToBuf(type); + if (ccf.kv[keyReleaseMapName].has(keyBuf)) { + // type is available + const itemsBuf = ccf.kv[keyReleaseMapName].get(keyBuf); + items = ccf.bufToStr(itemsBuf); + console.log( + `KRP remove ${type}=>key: ${type} exist: ${items} in the key release policy`, + ); + items = JSON.parse(items); + } else { + console.log( + `KRP remove ${type}=>key: ${type} does not exists in the key release policy`, + ); + throw new Error( + `The key ${type} does not exists in the key release policy`, + ); + } + + // iterate over every claim + Object.keys(claims).forEach((key) => { + if (CLAIMS[key] === undefined) { + throw new Error( + `KRP remove ${type}=>The claim ${key} is not an allowed claim`, + ); + } + let item = claims[key]; + // Make sure item is always an array + if (Array.isArray(item)) { + throw new Error(`The operator claim ${key} cannot be an array`); + } + + if (items[key] !== undefined) { + console.log(`KRP remove ${type}=>Removing ${item} from ${key}`); + delete items[key]; + } else { + console.log( + `KRP remove ${type}=>Claim ${key} not found in the key release policy`, + ); throw new Error( - `Cannot remove values of ${key} because the key does not exist in the key release policy claims`, + `The claim ${key} does not exists in the key release policy`, ); } }); + + // Save into KV + console.log(`KRP remove ${type}=>items: `, items); + let jsonItems = JSON.stringify(items); + console.log( + `KRP remove ${type}=>Remove claims from key release policy for ${type}: ${jsonItems}`, + ); + let jsonItemsBuf = ccf.strToBuf(jsonItems); + ccf.kv[keyReleaseMapName].set(keyBuf, jsonItemsBuf); }; const type = args.type; switch (type) { case "add": add("claims", args.claims); + if (args.gte !== undefined) { + addOperator("gte", args.gte); + } + if (args.gt !== undefined) { + addOperator("gt", args.gt); + } + break; case "remove": remove("claims", args.claims); + if (args.gte !== undefined) { + removeOperator("gte", args.gte); + } + if (args.gt !== undefined) { + removeOperator("gt", args.gt); + } break; default: throw new Error( @@ -239,18 +375,27 @@ actions.set( ), ); - actions.set( "set_key_rotation_policy", // validate function new Action( function (args) { - console.log(`set_key_rotation_policy, check args: ${JSON.stringify(args)}`); + console.log( + `set_key_rotation_policy, check args: ${JSON.stringify(args)}`, + ); checkType(args.key_rotation_policy, "object", "set_key_rotation_policy"); // Check settings policy - checkType(args.key_rotation_policy.rotation_interval_seconds, "integer", "Number_of_seconds_between_key_rotations"); - checkType(args.key_rotation_policy.grace_period_seconds, "integer", "Number_of_seconds_to_allow_an_expired_key_to_be_used_by_clients"); + checkType( + args.key_rotation_policy.rotation_interval_seconds, + "integer", + "Number_of_seconds_between_key_rotations", + ); + checkType( + args.key_rotation_policy.grace_period_seconds, + "integer", + "Number_of_seconds_to_allow_an_expired_key_to_be_used_by_clients", + ); console.log(`Key rotation policy validation passed.`); }, @@ -269,4 +414,4 @@ actions.set( ); }, ), -); \ No newline at end of file +); diff --git a/governance/policies/key-release-policy-add.json b/governance/policies/key-release-policy-add.json index 3450ebd9..45159931 100644 --- a/governance/policies/key-release-policy-add.json +++ b/governance/policies/key-release-policy-add.json @@ -4,6 +4,12 @@ "name": "set_key_release_policy", "args": { "service": "some service", + "gte": { + "x-ms-ver": "2" + }, + "gt": { + "x-ms-ver": "1" + }, "type": "add", "claims": { "x-ms-ver": ["2", "3"], diff --git a/governance/policies/key-release-policy-remove.json b/governance/policies/key-release-policy-remove.json index 4f4b3af8..1b0894ae 100644 --- a/governance/policies/key-release-policy-remove.json +++ b/governance/policies/key-release-policy-remove.json @@ -7,6 +7,12 @@ "type": "remove", "claims": { "x-ms-ver": ["2", "3"] + }, + "gte": { + "x-ms-ver": "2" + }, + "gt": { + "x-ms-ver": "1" } } } diff --git a/governance/policies/key-rotation-policy.json b/governance/policies/key-rotation-policy.json index 14c996a5..82522244 100644 --- a/governance/policies/key-rotation-policy.json +++ b/governance/policies/key-rotation-policy.json @@ -1,13 +1,13 @@ { - "actions": [ - { - "name": "set_key_rotation_policy", - "args": { - "key_rotation_policy": { - "rotation_interval_seconds": 300, - "grace_period_seconds": 60 - } + "actions": [ + { + "name": "set_key_rotation_policy", + "args": { + "key_rotation_policy": { + "rotation_interval_seconds": 300, + "grace_period_seconds": 60 } } - ] - } \ No newline at end of file + } + ] +} diff --git a/scripts/setup_mCCF.sh b/scripts/setup_mCCF.sh index 74a304d8..271af24b 100755 --- a/scripts/setup_mCCF.sh +++ b/scripts/setup_mCCF.sh @@ -5,7 +5,9 @@ set -euo pipefail export AUTHORIZATION="Bearer $ACCESS" -export CCF_NAME="acceu-..." +export AUTHORIZATION=$(./scripts/authorization_header.sh) +#export CCF_NAME="acceu-bingads-502-1" +export CCF_NAME="trust-coordinator-na-504" export CCF_PLATFORM=virtual export MEMBER_COUNT=1 export KMS_WORKSPACE=${PWD}/workspace diff --git a/src/attestation/AttestationValidation.ts b/src/attestation/AttestationValidation.ts index 35e66513..f4a3c3c1 100644 --- a/src/attestation/AttestationValidation.ts +++ b/src/attestation/AttestationValidation.ts @@ -116,11 +116,13 @@ export const validateAttestation = ( ); // Get the key release policy - const keyReleasePolicy = KeyReleasePolicy.getKeyReleasePolicyFromMap(keyReleasePolicyMap); + const keyReleasePolicy = + KeyReleasePolicy.getKeyReleasePolicyFromMap(keyReleasePolicyMap); Logger.debug( `Key release policy: ${JSON.stringify( keyReleasePolicy, - )}, keys: ${Object.keys(keyReleasePolicy)}, keys: ${Object.keys(keyReleasePolicy).length + )}, keys: ${Object.keys(keyReleasePolicy)}, keys: ${ + Object.keys(keyReleasePolicy).length }`, ); @@ -133,7 +135,10 @@ export const validateAttestation = ( 400, ); } - const policyValidationResult = KeyReleasePolicy.validateKeyReleasePolicy(keyReleasePolicy, attestationClaims); + const policyValidationResult = KeyReleasePolicy.validateKeyReleasePolicy( + keyReleasePolicy, + attestationClaims, + ); return policyValidationResult; } catch (exception: any) { return ServiceResult.Failed( diff --git a/src/endpoints/keyReleasePolicyEndpoint.ts b/src/endpoints/keyReleasePolicyEndpoint.ts index 221ae64e..c9093209 100644 --- a/src/endpoints/keyReleasePolicyEndpoint.ts +++ b/src/endpoints/keyReleasePolicyEndpoint.ts @@ -26,6 +26,7 @@ export const keyReleasePolicy = ( const [_, isValidIdentity] = serviceRequest.isAuthenticated(); if (isValidIdentity.failure) return isValidIdentity; - const result = KeyReleasePolicy.getKeyReleasePolicyFromMap(keyReleasePolicyMap); + const result = + KeyReleasePolicy.getKeyReleasePolicyFromMap(keyReleasePolicyMap); return ServiceResult.Succeeded(result); }; diff --git a/src/policies/IKeyReleasePolicy.ts b/src/policies/IKeyReleasePolicy.ts index f1665310..6125f76d 100644 --- a/src/policies/IKeyReleasePolicy.ts +++ b/src/policies/IKeyReleasePolicy.ts @@ -5,9 +5,7 @@ import { IKeyReleasePolicySnpProps } from ".."; export interface IKeyReleasePolicy { type: string; - operators?: { - gt?: IKeyReleasePolicySnpProps; - gte?: IKeyReleasePolicySnpProps; - } + gt?: IKeyReleasePolicySnpProps; + gte?: IKeyReleasePolicySnpProps; claims: IKeyReleasePolicySnpProps; } diff --git a/src/policies/IKeyReleasePolicySnpProps.ts b/src/policies/IKeyReleasePolicySnpProps.ts index 36f9ba19..ac807765 100644 --- a/src/policies/IKeyReleasePolicySnpProps.ts +++ b/src/policies/IKeyReleasePolicySnpProps.ts @@ -2,29 +2,29 @@ // Licensed under the MIT license. export interface IKeyReleasePolicySnpProps { - "x-ms-attestation-type"?: string[]; - "x-ms-compliance-status"?: string[]; - "x-ms-policy-hash"?: string[]; + "x-ms-attestation-type"?: string[] | string | string; + "x-ms-compliance-status"?: string[] | string; + "x-ms-policy-hash"?: string[] | string; "vm-configuration-secure-boot"?: boolean; - "vm-configuration-secure-boot-template-id"?: string[]; + "vm-configuration-secure-boot-template-id"?: string[] | string; "vm-configuration-tpm-enabled"?: boolean; - "vm-configuration-vmUniqueId"?: string[]; - "x-ms-sevsnpvm-authorkeydigest"?: string[]; - "x-ms-sevsnpvm-bootloader-svn"?: number[]; - "x-ms-sevsnpvm-familyId"?: string[]; - "x-ms-sevsnpvm-guestsvn"?: number[]; - "x-ms-sevsnpvm-hostdata"?: string[]; - "x-ms-sevsnpvm-idkeydigest"?: string[]; - "x-ms-sevsnpvm-imageId"?: string[]; + "vm-configuration-vmUniqueId"?: string[] | string; + "x-ms-sevsnpvm-authorkeydigest"?: string[] | string; + "x-ms-sevsnpvm-bootloader-svn"?: number[] | number; + "x-ms-sevsnpvm-familyId"?: string[] | string; + "x-ms-sevsnpvm-guestsvn"?: number[] | number; + "x-ms-sevsnpvm-hostdata"?: string[] | string; + "x-ms-sevsnpvm-idkeydigest"?: string[] | string; + "x-ms-sevsnpvm-imageId"?: string[] | string; "x-ms-sevsnpvm-is-debuggable"?: boolean; - "x-ms-sevsnpvm-launchmeasurement"?: string[]; - "x-ms-sevsnpvm-microcode-svn"?: number[]; + "x-ms-sevsnpvm-launchmeasurement"?: string[] | string; + "x-ms-sevsnpvm-microcode-svn"?: number[] | number; "x-ms-sevsnpvm-migration-allowed"?: boolean; - "x-ms-sevsnpvm-reportdata"?: string[]; - "x-ms-sevsnpvm-reportid"?: string[]; + "x-ms-sevsnpvm-reportdata"?: string[] | string; + "x-ms-sevsnpvm-reportid"?: string[] | string; "x-ms-sevsnpvm-smt-allowed"?: boolean; - "x-ms-sevsnpvm-snpfw-svn"?: number[]; - "x-ms-sevsnpvm-tee-svn"?: number[]; - "x-ms-sevsnpvm-vmpl"?: number[]; - "x-ms-ver"?: string[]; + "x-ms-sevsnpvm-snpfw-svn"?: number[] | number; + "x-ms-sevsnpvm-tee-svn"?: number[] | number; + "x-ms-sevsnpvm-vmpl"?: number[] | number; + "x-ms-ver"?: string[] | string; } diff --git a/src/policies/KeyReleasePolicy.ts b/src/policies/KeyReleasePolicy.ts index 8f5a9c96..57ec237a 100644 --- a/src/policies/KeyReleasePolicy.ts +++ b/src/policies/KeyReleasePolicy.ts @@ -15,8 +15,14 @@ export class KeyReleasePolicy implements IKeyReleasePolicy { "x-ms-attestation-type": ["snp"], }; - private static validateKeyReleasePolicyClaims(keyReleasePolicyClaims: IKeyReleasePolicySnpProps, attestationClaims: IAttestationReport): ServiceResult { - if (keyReleasePolicyClaims === null || keyReleasePolicyClaims === undefined) { + private static validateKeyReleasePolicyClaims( + keyReleasePolicyClaims: IKeyReleasePolicySnpProps, + attestationClaims: IAttestationReport, + ): ServiceResult { + if ( + keyReleasePolicyClaims === null || + keyReleasePolicyClaims === undefined + ) { return ServiceResult.Failed( { errorMessage: "Missing key release policy" }, 500, @@ -63,36 +69,182 @@ export class KeyReleasePolicy implements IKeyReleasePolicy { return ServiceResult.Succeeded(attestationClaims); } -public static validateKeyReleasePolicy(keyReleasePolicy: IKeyReleasePolicy, attestationClaims: IAttestationReport): ServiceResult { - const policyValidationResult = KeyReleasePolicy.validateKeyReleasePolicyClaims(keyReleasePolicy.claims, attestationClaims); - if (!policyValidationResult.success) { - return policyValidationResult; + private static validateKeyReleasePolicyOperators( + type: string, + keyReleasePolicyClaims: IKeyReleasePolicySnpProps, + attestationClaims: IAttestationReport, + ): ServiceResult { + if ( + keyReleasePolicyClaims === null || + keyReleasePolicyClaims === undefined + ) { + return ServiceResult.Failed( + { errorMessage: "Missing key release policy" }, + 500, + ); + } + if (attestationClaims === null || attestationClaims === undefined) { + return ServiceResult.Failed( + { errorMessage: "Missing attestation claims" }, + 500, + ); + } + const gte = type === "gte"; + const claims = keyReleasePolicyClaims; + for (let inx = 0; inx < Object.keys(claims).length; inx++) { + const key = Object.keys(claims)[inx]; + + // check if key is in attestation + let attestationValue = attestationClaims[key]; + let policyValue = keyReleasePolicyClaims[key]; + const isUndefined = typeof attestationValue === "undefined"; + Logger.debug( + `Checking key ${key}, typeof attestationValue: ${typeof attestationValue}, isUndefined: ${isUndefined}, attestation value: ${attestationValue}, policyValue: ${policyValue}`, + ); + if (isUndefined) { + return ServiceResult.Failed( + { + errorMessage: `Missing claim in attestation: ${key} for operator type ${type}`, + }, + 400, + ); + } + if (policyValue === null || policyValue === undefined) { + return ServiceResult.Failed( + { + errorMessage: `Missing policy value for claim ${key} for operator type ${type}`, + }, + 500, + ); + } + if ( + typeof policyValue !== "number" && + (typeof policyValue !== "string" || isNaN(parseFloat(policyValue))) + ) { + return ServiceResult.Failed( + { + errorMessage: `Policy value for claim ${key} is not a number or a string representing a number for operator type ${type}`, + }, + 400, + ); + } + + if (typeof policyValue !== "number") { + policyValue = parseFloat(policyValue); + } + + if (typeof policyValue !== "number") { + return ServiceResult.Failed( + { + errorMessage: `Policy value for claim ${key} is not a number or a string representing a number for operator type ${type} after conversion`, + }, + 400, + ); + } + + if (typeof attestationValue !== "number") { + attestationValue = parseFloat(attestationValue); + } + + if (gte) { + Logger.info( + `Checking if attestation value ${attestationValue} is greater than or equal to policy value ${policyValue}`, + ); + if (attestationValue >= policyValue === false) { + return ServiceResult.Failed( + { + errorMessage: `Attestation claim ${key}, value ${attestationValue} is not greater than or equal to policy value ${policyValue}`, + }, + 400, + ); + } + } else { + Logger.info( + `Checking if attestation value ${attestationValue} is greater than policy value ${policyValue}`, + ); + if (attestationValue > policyValue === false) { + return ServiceResult.Failed( + { + errorMessage: `Attestation claim ${key}, value ${attestationValue} is not greater than policy value ${policyValue}`, + }, + 400, + ); + } + } + } + return ServiceResult.Succeeded(attestationClaims); } - return policyValidationResult; -} + public static validateKeyReleasePolicy( + keyReleasePolicy: IKeyReleasePolicy, + attestationClaims: IAttestationReport, + ): ServiceResult { + // Check claims + let policyValidationResult = + KeyReleasePolicy.validateKeyReleasePolicyClaims( + keyReleasePolicy.claims, + attestationClaims, + ); + if (!policyValidationResult.success) { + return policyValidationResult; + } -/** - * Retrieves the key release policy from a key release policy map. - * @param keyReleasePolicyMap - The key release policy map. - * @returns The key release policy as an object. - */ -public static getKeyReleasePolicyFromMap = ( - keyReleasePolicyMap: ccfapp.KvMap, -): IKeyReleasePolicy => { - let keyReleasePolicy: IKeyReleasePolicy = { type: "", claims: {} }; - let kvKey = "claims" - let kvKeyBuf = ccf.strToBuf(kvKey); - let kvValueBuf = keyReleasePolicyMap.get(kvKeyBuf); - if (!kvValueBuf) { - throw new Error("Key release policy claims not found in the key release policy map"); + // Check operators gte and gt + if (keyReleasePolicy.gte !== null && keyReleasePolicy.gte !== undefined) { + Logger.info(`Validating gte operator`, keyReleasePolicy.gte); + policyValidationResult = + KeyReleasePolicy.validateKeyReleasePolicyOperators( + "gte", + keyReleasePolicy.gte, + attestationClaims, + ); + } + if (keyReleasePolicy.gt !== null && keyReleasePolicy.gt !== undefined) { + Logger.info(`Validating gt operator`, keyReleasePolicy.gt); + policyValidationResult = + KeyReleasePolicy.validateKeyReleasePolicyOperators( + "gt", + keyReleasePolicy.gt, + attestationClaims, + ); + } + + return policyValidationResult; } - let kvValue = ccf.bufToStr(kvValueBuf); - keyReleasePolicy[kvKey] = JSON.parse(kvValue) as IKeyReleasePolicySnpProps; + /** + * Retrieves the key release policy from a key release policy map. + * @param keyReleasePolicyMap - The key release policy map. + * @returns The key release policy as an object. + */ + public static getKeyReleasePolicyFromMap = ( + keyReleasePolicyMap: ccfapp.KvMap, + ): IKeyReleasePolicy => { + const keyReleasePolicy: IKeyReleasePolicy = { type: "", claims: {} }; - Logger.info(`Resulting key release policy: `, keyReleasePolicy); - return keyReleasePolicy; -}; + [ + { kvkey: "claims", optinal: false }, + { kvkey: "gte", optinal: true }, + { kvkey: "gt", optinal: true }, + ].forEach((kv) => { + const kvKey = kv.kvkey; + const kvKeyBuf = ccf.strToBuf(kvKey); + const kvValueBuf = keyReleasePolicyMap.get(kvKeyBuf); + if (!kvValueBuf) { + if (!kv.optinal) { + throw new Error( + `Key release policy ${kvKey} not found in the key release policy map`, + ); + } + } else { + let kvValue = ccf.bufToStr(kvValueBuf!); + keyReleasePolicy[kvKey] = JSON.parse( + kvValue, + ) as IKeyReleasePolicySnpProps; + } + }); + Logger.info(`Resulting key release policy: `, keyReleasePolicy); + return keyReleasePolicy; + }; } diff --git a/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts b/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts index d5a51856..357025dc 100644 --- a/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts +++ b/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts @@ -4,9 +4,9 @@ // Use the CCF polyfill to mock-up all key-value map functionality for unit-test import "@microsoft/ccf-app/polyfill.js"; import { describe, expect, test } from "@jest/globals"; -import { IKeyReleasePolicySnpProps } from "../../../src"; import { KeyReleasePolicy } from "../../../src/policies/KeyReleasePolicy"; import { Logger, LogLevel } from "../../../src/utils/Logger"; +import { IKeyReleasePolicySnpProps } from "../../../src"; import { IAttestationReport } from "../../../src/attestation/ISnpAttestationReport"; import { IKeyReleasePolicy } from "../../../src/policies/IKeyReleasePolicy"; @@ -23,23 +23,227 @@ describe("Test Key Release Policy properties", () => { expect(policy["x-ms-attestation-type"]).toContain("none"); }); - describe("Validate Key Release Policy properties", () => { - test("Should validate successfully", () => { - // Arrange - Logger.setLogLevel(LogLevel.DEBUG); - const policy: IKeyReleasePolicy = {type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }}; - const attestationClaims: IAttestationReport = { "x-ms-attestation-type": "sevsnpvm" }; + test("Should validate successfully", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: IKeyReleasePolicy = { + type: "", + claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + }; + const attestationClaims: IAttestationReport = { + "x-ms-attestation-type": "sevsnpvm", + }; + + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy( + policy, + attestationClaims, + ); + + // Assert + expect(validationResult.success).toBe(true); + }); + test("Should validate successfully Key Release Policy properties with operator gte", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: any = { + type: "", + claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + gte: { "x-ms-number": 8.6 }, + }; + const attestationClaims: any = { + "x-ms-attestation-type": "sevsnpvm", + "x-ms-number": 8.6, + }; + + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy( + policy, + attestationClaims, + ); + + // Assert + expect(validationResult.success).toBe(true); + }); + + test("Should fail validation Key Release Policy properties with operator gte, attestation is smaller", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: any = { + type: "", + claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + gte: { "x-ms-number": 8.6 }, + }; + const attestationClaims: any = { + "x-ms-attestation-type": "sevsnpvm", + "x-ms-number": 8, + }; + + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy( + policy, + attestationClaims, + ); + + // Assert + expect(validationResult.success).toBe(false); + }); + + test("Should fail validation Key Release Policy properties with operator gte, missing gte claim", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: any = { + type: "", + claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + gte: { "x-ms-number": 8.6 }, + }; + const attestationClaims: IAttestationReport = { + "x-ms-attestation-type": "sevsnpvm", + }; + + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy( + policy, + attestationClaims, + ); + + // Assert + expect(validationResult.success).toBe(false); + }); + + test("Should fail validation Key Release Policy properties with claims, missing claim", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: any = { + type: "", + claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + gte: { "x-ms-number": 8.6 }, + }; + const attestationClaims: IAttestationReport = { + "x-ms-attestation-type": "", + }; + + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy( + policy, + attestationClaims, + ); + + // Assert + expect(validationResult.success).toBe(false); + }); + + test("Should validate successfully Key Release Policy properties with operator gt", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: any = { + type: "", + claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + gt: { "x-ms-number": 8.6 }, + }; + const attestationClaims: any = { + "x-ms-attestation-type": "sevsnpvm", + "x-ms-number": 9.5, + }; - // Act - const validationResult = KeyReleasePolicy.validateKeyReleasePolicy(policy, attestationClaims); + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy( + policy, + attestationClaims, + ); - // Debugging statements - console.log("Policy:", policy); - console.log("Attestation Claims:", attestationClaims); - console.log("Validation Result:", validationResult); + // Assert + expect(validationResult.success).toBe(true); + }); + + test("Should validate successfully Key Release Policy properties with operator gt", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: any = { + type: "", + claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + gt: { "x-ms-number": 8.6 }, + }; + const attestationClaims: any = { + "x-ms-attestation-type": "sevsnpvm", + "x-ms-number": "9.5", + }; - // Assert - expect(validationResult.success).toBe(true); - }); + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy( + policy, + attestationClaims, + ); + + // Assert + expect(validationResult.success).toBe(true); + }); + + test("Should validate successfully Key Release Policy properties with operator gt", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: any = { + type: "", + claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + gt: { "x-ms-number": "8.6" }, + }; + const attestationClaims: any = { + "x-ms-attestation-type": "sevsnpvm", + "x-ms-number": "9.5", + }; + + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy( + policy, + attestationClaims, + ); + + // Assert + expect(validationResult.success).toBe(true); + }); + + test("Should fail validation Key Release Policy properties with operator gt, attestation is equal", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: any = { + type: "", + claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + gt: { "x-ms-number": 8.6 }, + }; + const attestationClaims: any = { + "x-ms-attestation-type": "sevsnpvm", + "x-ms-number": 8.6, + }; + + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy( + policy, + attestationClaims, + ); + + // Assert + expect(validationResult.success).toBe(false); + }); + + test("Should fail validation Key Release Policy properties with operator gt, missing gt claim", () => { + // Arrange + Logger.setLogLevel(LogLevel.DEBUG); + const policy: any = { + type: "", + claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + gt: { "x-ms-number": 8.6 }, + }; + const attestationClaims: IAttestationReport = { + "x-ms-attestation-type": "sevsnpvm", + }; + + // Act + const validationResult = KeyReleasePolicy.validateKeyReleasePolicy( + policy, + attestationClaims, + ); + + // Assert + expect(validationResult.success).toBe(false); }); }); From acc6188411eacea063e8eecad4c82a97de544779 Mon Sep 17 00:00:00 2001 From: beejones Date: Fri, 27 Sep 2024 09:14:46 +0000 Subject: [PATCH 4/6] Addressing PR feedback --- governance/constitution/kms_actions.js | 52 +++++++++++++++++-- scripts/setup_mCCF.sh | 18 +++++-- src/attestation/AttestationValidation.ts | 9 ---- src/endpoints/keyReleasePolicyEndpoint.ts | 10 ++-- src/policies/IKeyReleasePolicy.ts | 7 ++- src/policies/IKeyReleasePolicySnpProps.ts | 2 +- src/policies/KeyReleasePolicy.ts | 40 ++++++++++---- test/e2e-test/src/index.ts | 8 +++ .../IKeyReleasePolicySnpProps.test.ts | 24 ++++----- 9 files changed, 125 insertions(+), 45 deletions(-) diff --git a/governance/constitution/kms_actions.js b/governance/constitution/kms_actions.js index fe341a32..06f896cb 100644 --- a/governance/constitution/kms_actions.js +++ b/governance/constitution/kms_actions.js @@ -131,7 +131,18 @@ actions.set( console.log( `KRP add ${type}=>key: ${type} already exist: ${items} in the key release policy`, ); - items = JSON.parse(items); + try { + items = JSON.parse(items); + } catch (e) { + console.log( + `KRP add ${type}=>Error parsing ${items} from key release policy during add`, + e, + ); + throw new Error( + `Error parsing ${items} from key release policy during add`, + e, + ); + } } else { console.log( `KRP add ${type}=>key: ${type} is new in the key release policy`, @@ -186,7 +197,18 @@ actions.set( console.log( `KRP add ${type}=>key: ${type} already exist: ${items} in the key release policy`, ); - items = JSON.parse(items); + try { + items = JSON.parse(items); + } catch (e) { + console.log( + `KRP addOperator ${type}=>Error parsing ${items} from key release policy`, + e, + ); + throw new Error( + `Error parsing ${items} from key release policy during addOperator`, + e, + ); + } } else { console.log( `KRP add ${type}=>key: ${type} is new in the key release policy`, @@ -234,7 +256,18 @@ actions.set( console.log( `KRP remove ${type}=>key: ${type} exist: ${items} in the key release policy`, ); - items = JSON.parse(items); + try { + items = JSON.parse(items); + } catch (e) { + console.log( + `KRP remove ${type}=>Error parsing ${items} from key release policy`, + e, + ); + throw new Error( + `Error parsing ${items} from key release policy during remove`, + e, + ); + } } else { console.log( `KRP remove ${type}=>key: ${type} does not exists in the key release policy`, @@ -299,7 +332,18 @@ actions.set( console.log( `KRP remove ${type}=>key: ${type} exist: ${items} in the key release policy`, ); - items = JSON.parse(items); + try { + items = JSON.parse(items); + } catch (e) { + console.log( + `KRP removeOperator ${type}=>Error parsing ${items} from key release policy`, + e, + ); + throw new Error( + `Error parsing ${items} from key release policy during removeOperator`, + e, + ); + } } else { console.log( `KRP remove ${type}=>key: ${type} does not exists in the key release policy`, diff --git a/scripts/setup_mCCF.sh b/scripts/setup_mCCF.sh index 271af24b..69d5bf9c 100755 --- a/scripts/setup_mCCF.sh +++ b/scripts/setup_mCCF.sh @@ -3,11 +3,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. -set -euo pipefail export AUTHORIZATION="Bearer $ACCESS" -export AUTHORIZATION=$(./scripts/authorization_header.sh) -#export CCF_NAME="acceu-bingads-502-1" -export CCF_NAME="trust-coordinator-na-504" +export CCF_NAME="${CCF_NAME:-yourCcfName}" export CCF_PLATFORM=virtual export MEMBER_COUNT=1 export KMS_WORKSPACE=${PWD}/workspace @@ -19,3 +16,16 @@ export PRIVATE_CERT=${KEYS_DIR}/member0_privk.pem export SERVICE_CERT=${KEYS_DIR}/service_cert.pem export WRAPPING_KEY=$(jq -Rs . < test/data-samples/publicWrapKey.pem) export ATTESTATION=$( $PUBLIC_CERT + fi + if [ -n "$PRIVATE_CERT_PEM" ]; then + echo -n "$PRIVATE_CERT_PEM" > $PRIVATE_CERT + fi + fi diff --git a/src/attestation/AttestationValidation.ts b/src/attestation/AttestationValidation.ts index f4a3c3c1..cccc5434 100644 --- a/src/attestation/AttestationValidation.ts +++ b/src/attestation/AttestationValidation.ts @@ -126,15 +126,6 @@ export const validateAttestation = ( }`, ); - if (Object.keys(keyReleasePolicy.claims).length === 0) { - return ServiceResult.Failed( - { - errorMessage: - "The key release policy is missing. Please propose a new key release policy", - }, - 400, - ); - } const policyValidationResult = KeyReleasePolicy.validateKeyReleasePolicy( keyReleasePolicy, attestationClaims, diff --git a/src/endpoints/keyReleasePolicyEndpoint.ts b/src/endpoints/keyReleasePolicyEndpoint.ts index c9093209..f101cab7 100644 --- a/src/endpoints/keyReleasePolicyEndpoint.ts +++ b/src/endpoints/keyReleasePolicyEndpoint.ts @@ -26,7 +26,11 @@ export const keyReleasePolicy = ( const [_, isValidIdentity] = serviceRequest.isAuthenticated(); if (isValidIdentity.failure) return isValidIdentity; - const result = - KeyReleasePolicy.getKeyReleasePolicyFromMap(keyReleasePolicyMap); - return ServiceResult.Succeeded(result); + try { + const result = + KeyReleasePolicy.getKeyReleasePolicyFromMap(keyReleasePolicyMap); + return ServiceResult.Succeeded(result); + } catch (error: any) { + return ServiceResult.Failed({ errorMessage: error.message }, 500); + } }; diff --git a/src/policies/IKeyReleasePolicy.ts b/src/policies/IKeyReleasePolicy.ts index 6125f76d..dec2d9a9 100644 --- a/src/policies/IKeyReleasePolicy.ts +++ b/src/policies/IKeyReleasePolicy.ts @@ -3,8 +3,13 @@ import { IKeyReleasePolicySnpProps } from ".."; +export enum KeyReleasePolicyType { + ADD = "add", + REMOVE = "remove", +} + export interface IKeyReleasePolicy { - type: string; + type: KeyReleasePolicyType; gt?: IKeyReleasePolicySnpProps; gte?: IKeyReleasePolicySnpProps; claims: IKeyReleasePolicySnpProps; diff --git a/src/policies/IKeyReleasePolicySnpProps.ts b/src/policies/IKeyReleasePolicySnpProps.ts index ac807765..97d91b94 100644 --- a/src/policies/IKeyReleasePolicySnpProps.ts +++ b/src/policies/IKeyReleasePolicySnpProps.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. export interface IKeyReleasePolicySnpProps { - "x-ms-attestation-type"?: string[] | string | string; + "x-ms-attestation-type"?: string[] | string; "x-ms-compliance-status"?: string[] | string; "x-ms-policy-hash"?: string[] | string; "vm-configuration-secure-boot"?: boolean; diff --git a/src/policies/KeyReleasePolicy.ts b/src/policies/KeyReleasePolicy.ts index 57ec237a..6ca41cda 100644 --- a/src/policies/KeyReleasePolicy.ts +++ b/src/policies/KeyReleasePolicy.ts @@ -3,14 +3,14 @@ import * as ccfapp from "@microsoft/ccf-app"; import { ccf } from "@microsoft/ccf-app/global"; -import { IKeyReleasePolicy } from "./IKeyReleasePolicy"; +import { IKeyReleasePolicy, KeyReleasePolicyType } from "./IKeyReleasePolicy"; import { IKeyReleasePolicySnpProps } from "./IKeyReleasePolicySnpProps"; import { Logger } from "../utils/Logger"; import { ServiceResult } from "../utils/ServiceResult"; import { IAttestationReport } from "../attestation/ISnpAttestationReport"; export class KeyReleasePolicy implements IKeyReleasePolicy { - public type = "add"; + public type = KeyReleasePolicyType.ADD; public claims = { "x-ms-attestation-type": ["snp"], }; @@ -179,6 +179,17 @@ export class KeyReleasePolicy implements IKeyReleasePolicy { keyReleasePolicy: IKeyReleasePolicy, attestationClaims: IAttestationReport, ): ServiceResult { + // claims are mandatory + if (Object.keys(keyReleasePolicy.claims).length === 0) { + return ServiceResult.Failed( + { + errorMessage: + "The claims in the key release policy are missing. Please propose a new key release policy", + }, + 400, + ); + } + // Check claims let policyValidationResult = KeyReleasePolicy.validateKeyReleasePolicyClaims( @@ -220,27 +231,36 @@ export class KeyReleasePolicy implements IKeyReleasePolicy { public static getKeyReleasePolicyFromMap = ( keyReleasePolicyMap: ccfapp.KvMap, ): IKeyReleasePolicy => { - const keyReleasePolicy: IKeyReleasePolicy = { type: "", claims: {} }; + const keyReleasePolicy: IKeyReleasePolicy = { + type: KeyReleasePolicyType.ADD, + claims: {}, + }; [ - { kvkey: "claims", optinal: false }, - { kvkey: "gte", optinal: true }, - { kvkey: "gt", optinal: true }, + { kvkey: "claims", optional: false }, + { kvkey: "gte", optional: true }, + { kvkey: "gt", optional: true }, ].forEach((kv) => { const kvKey = kv.kvkey; const kvKeyBuf = ccf.strToBuf(kvKey); const kvValueBuf = keyReleasePolicyMap.get(kvKeyBuf); if (!kvValueBuf) { - if (!kv.optinal) { + if (!kv.optional) { throw new Error( `Key release policy ${kvKey} not found in the key release policy map`, ); } } else { let kvValue = ccf.bufToStr(kvValueBuf!); - keyReleasePolicy[kvKey] = JSON.parse( - kvValue, - ) as IKeyReleasePolicySnpProps; + try { + keyReleasePolicy[kvKey] = JSON.parse( + kvValue, + ) as IKeyReleasePolicySnpProps; + } catch (error) { + throw new Error( + `Key release policy ${kvKey} is not a valid JSON object: ${kvValue}`, + ); + } } }); diff --git a/test/e2e-test/src/index.ts b/test/e2e-test/src/index.ts index c095d1d9..17d2d7e1 100644 --- a/test/e2e-test/src/index.ts +++ b/test/e2e-test/src/index.ts @@ -526,6 +526,14 @@ class Demo { `keyResponse.claims["x-ms-sevsnpvm-is-debuggable"][0] === false`, keyResponse.claims["x-ms-sevsnpvm-is-debuggable"][0] === false, ); + Demo.assert( + `keyResponse.gte["x-ms-ver"] === "2"`, + keyResponse.gte["x-ms-ver"] === "2", + ); + Demo.assert( + `keyResponse.gt["x-ms-ver"] === "1"`, + keyResponse.gt["x-ms-ver"] === "1", + ); // JWT not allowed [statusCode, keyResponse] = await Api.keyReleasePolicy( diff --git a/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts b/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts index 357025dc..8d90922a 100644 --- a/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts +++ b/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts @@ -3,12 +3,20 @@ // Use the CCF polyfill to mock-up all key-value map functionality for unit-test import "@microsoft/ccf-app/polyfill.js"; -import { describe, expect, test } from "@jest/globals"; +import { beforeAll, describe, expect, test } from "@jest/globals"; import { KeyReleasePolicy } from "../../../src/policies/KeyReleasePolicy"; import { Logger, LogLevel } from "../../../src/utils/Logger"; import { IKeyReleasePolicySnpProps } from "../../../src"; import { IAttestationReport } from "../../../src/attestation/ISnpAttestationReport"; -import { IKeyReleasePolicy } from "../../../src/policies/IKeyReleasePolicy"; +import { + IKeyReleasePolicy, + KeyReleasePolicyType, +} from "../../../src/policies/IKeyReleasePolicy"; + +// Set the log level to DEBUG before all tests +beforeAll(() => { + Logger.setLogLevel(LogLevel.DEBUG); +}); describe("Test Key Release Policy properties", () => { test("Should get all data successfully", () => { @@ -25,9 +33,8 @@ describe("Test Key Release Policy properties", () => { test("Should validate successfully", () => { // Arrange - Logger.setLogLevel(LogLevel.DEBUG); const policy: IKeyReleasePolicy = { - type: "", + type: KeyReleasePolicyType.ADD, claims: { "x-ms-attestation-type": ["sevsnpvm"] }, }; const attestationClaims: IAttestationReport = { @@ -45,7 +52,6 @@ describe("Test Key Release Policy properties", () => { }); test("Should validate successfully Key Release Policy properties with operator gte", () => { // Arrange - Logger.setLogLevel(LogLevel.DEBUG); const policy: any = { type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }, @@ -68,7 +74,6 @@ describe("Test Key Release Policy properties", () => { test("Should fail validation Key Release Policy properties with operator gte, attestation is smaller", () => { // Arrange - Logger.setLogLevel(LogLevel.DEBUG); const policy: any = { type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }, @@ -91,7 +96,6 @@ describe("Test Key Release Policy properties", () => { test("Should fail validation Key Release Policy properties with operator gte, missing gte claim", () => { // Arrange - Logger.setLogLevel(LogLevel.DEBUG); const policy: any = { type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }, @@ -113,7 +117,6 @@ describe("Test Key Release Policy properties", () => { test("Should fail validation Key Release Policy properties with claims, missing claim", () => { // Arrange - Logger.setLogLevel(LogLevel.DEBUG); const policy: any = { type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }, @@ -135,7 +138,6 @@ describe("Test Key Release Policy properties", () => { test("Should validate successfully Key Release Policy properties with operator gt", () => { // Arrange - Logger.setLogLevel(LogLevel.DEBUG); const policy: any = { type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }, @@ -158,7 +160,6 @@ describe("Test Key Release Policy properties", () => { test("Should validate successfully Key Release Policy properties with operator gt", () => { // Arrange - Logger.setLogLevel(LogLevel.DEBUG); const policy: any = { type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }, @@ -181,7 +182,6 @@ describe("Test Key Release Policy properties", () => { test("Should validate successfully Key Release Policy properties with operator gt", () => { // Arrange - Logger.setLogLevel(LogLevel.DEBUG); const policy: any = { type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }, @@ -204,7 +204,6 @@ describe("Test Key Release Policy properties", () => { test("Should fail validation Key Release Policy properties with operator gt, attestation is equal", () => { // Arrange - Logger.setLogLevel(LogLevel.DEBUG); const policy: any = { type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }, @@ -227,7 +226,6 @@ describe("Test Key Release Policy properties", () => { test("Should fail validation Key Release Policy properties with operator gt, missing gt claim", () => { // Arrange - Logger.setLogLevel(LogLevel.DEBUG); const policy: any = { type: "", claims: { "x-ms-attestation-type": ["sevsnpvm"] }, From 30756480d785d24ee3ac91826a5469f0fbad5f77 Mon Sep 17 00:00:00 2001 From: beejones Date: Fri, 27 Sep 2024 12:28:04 +0000 Subject: [PATCH 5/6] adding multiple claims in KeyReleasePolicy.validateKeyReleasePolicy --- test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts b/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts index 8d90922a..f4cec45f 100644 --- a/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts +++ b/test/unit-test/policies/IKeyReleasePolicySnpProps.test.ts @@ -35,7 +35,7 @@ describe("Test Key Release Policy properties", () => { // Arrange const policy: IKeyReleasePolicy = { type: KeyReleasePolicyType.ADD, - claims: { "x-ms-attestation-type": ["sevsnpvm"] }, + claims: { "x-ms-attestation-type": ["sevsnpvm", "some claim"] }, }; const attestationClaims: IAttestationReport = { "x-ms-attestation-type": "sevsnpvm", From 0c67846f38a328335a4869f6176b919d5c666d6e Mon Sep 17 00:00:00 2001 From: beejones Date: Fri, 27 Sep 2024 12:37:55 +0000 Subject: [PATCH 6/6] cleanup --- src/policies/KeyReleasePolicy.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/policies/KeyReleasePolicy.ts b/src/policies/KeyReleasePolicy.ts index 6ca41cda..c5c2949b 100644 --- a/src/policies/KeyReleasePolicy.ts +++ b/src/policies/KeyReleasePolicy.ts @@ -35,9 +35,8 @@ export class KeyReleasePolicy implements IKeyReleasePolicy { ); } - const claims = keyReleasePolicyClaims; - for (let inx = 0; inx < Object.keys(claims).length; inx++) { - const key = Object.keys(claims)[inx]; + for (let inx = 0; inx < Object.keys(keyReleasePolicyClaims).length; inx++) { + const key = Object.keys(keyReleasePolicyClaims)[inx]; // check if key is in attestation const attestationValue = attestationClaims[key]; @@ -90,9 +89,8 @@ export class KeyReleasePolicy implements IKeyReleasePolicy { ); } const gte = type === "gte"; - const claims = keyReleasePolicyClaims; - for (let inx = 0; inx < Object.keys(claims).length; inx++) { - const key = Object.keys(claims)[inx]; + for (let inx = 0; inx < Object.keys(keyReleasePolicyClaims).length; inx++) { + const key = Object.keys(keyReleasePolicyClaims)[inx]; // check if key is in attestation let attestationValue = attestationClaims[key];