Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Key rotation policy. #194

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ propose-settings-policy: ## 🚀 Deploy the settings policy
@echo -e "\e[34m$@\e[0m" || true
@CCF_PLATFORM=${CCF_PLATFORM} ./scripts/submit_proposal.sh --network-url "${KMS_URL}" --proposal-file ./governance/policies/settings-policy.json --certificate_dir "${KEYS_DIR}" --member-count ${MEMBER_COUNT}

# Propose a new key rotation policy
propose-key-rotation-policy: ## 🚀 Deploy the key rotation policy
@echo -e "\e[34m$@\e[0m" || true
@CCF_PLATFORM=${CCF_PLATFORM} ./scripts/submit_proposal.sh --network-url "${KMS_URL}" --proposal-file ./governance/proposals/set_key_rotation_policy.json --certificate_dir "${KEYS_DIR}" --member-count ${MEMBER_COUNT}

# Propose a new key release policy
propose-add-key-release-policy: ## 🚀 Deploy the add claim key release policy to the sandbox or mCCF
@echo -e "\e[34m$@\e[0m" || true
Expand All @@ -107,10 +112,6 @@ propose-rm-key-release-policy: ## 🚀 Deploy the remove claim key release polic
$(call check_defined, KMS_URL)
@CCF_PLATFORM=${CCF_PLATFORM} ./scripts/submit_proposal.sh --network-url "${KMS_URL}" --proposal-file ./governance/policies/key-release-policy-remove.json --certificate_dir "${KEYS_DIR}"

propose-key-rotation-policy: ## 🚀 Deploy the key rotation policy to the sandbox or mCCF
@echo -e "\e[34m$@\e[0m" || true
@CCF_PLATFORM=${CCF_PLATFORM} ./scripts/submit_proposal.sh --network-url "${KMS_URL}" --proposal-file ./governance/policies/key-rotation-policy.json --certificate_dir "${KEYS_DIR}" --member-count ${MEMBER_COUNT}

refresh-key: ## 🚀 Refresh a key on the instance
@echo -e "\e[34m$@\e[0m" || true
$(call check_defined, KMS_URL)
Expand Down
24 changes: 24 additions & 0 deletions scripts/kms/key_rotation_policy_set.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

key_rotation_policy_set() {
set -e

REPO_ROOT="$(realpath "$(dirname "$(realpath "${BASH_SOURCE[0]}")")/../..")"
ROTATION_POLICY_PROPOSAL=$1

# If rotation policy is not set, use default from the proposal file
if [ -z "$ROTATION_POLICY" ]; then
ROTATION_POLICY=$(cat "$ROTATION_POLICY_PROPOSAL")
fi

# Echo the ROTATION_POLICY to the proposal file
echo "$ROTATION_POLICY" | jq > "$WORKSPACE/proposals/set_key_rotation_policy.json"

# Submit the proposal
source "$REPO_ROOT/scripts/ccf/propose.sh"
ccf-propose "$WORKSPACE/proposals/set_key_rotation_policy.json"

set +e
}

key_rotation_policy_set "$@"
3 changes: 3 additions & 0 deletions scripts/kms_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ make propose-settings-policy
# Add key release policy
make propose-add-key-release-policy

# Add key rotation policy
make propose-key-rotation-policy

# Add demo validation policy
make propose-jwt-demo-validation-policy

Expand Down
1 change: 1 addition & 0 deletions src/endpoints/IKeyItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enableEndpoint();
// Define the interface for storing keys
export interface IKeyItem extends JsonWebKeyEdDSAPublic {
timestamp?: number;
expiry?: number;
receipt?: string;
id?: number;
d?: string;
Expand Down
5 changes: 4 additions & 1 deletion src/endpoints/KeyGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class KeyGeneration {
};

// Generate new key item
public static generateKeyItem = (id: number): IKeyItem => {
public static generateKeyItem = (id: number, expiry?: number) => {
const keyType = "x25519";
const keys = ccfcrypto.generateEddsaKeyPair(keyType);
const jwk: IKeyItem = ccfcrypto.eddsaPemToJwk(
Expand All @@ -39,6 +39,9 @@ export class KeyGeneration {

// We will get an untrusted timestamp from the host. Is this a threat?
jwk.timestamp = Date.now();
if (expiry) {
jwk.expiry = expiry;
}
jwk.id = id;

Logger.secret(`JWK: `, jwk);
Expand Down
13 changes: 12 additions & 1 deletion src/endpoints/keyEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import { IAttestationReport } from "../attestation/ISnpAttestationReport";
import { IKeyItem } from "./IKeyItem";
import { KeyGeneration } from "./KeyGeneration";
import { validateAttestation } from "../attestation/AttestationValidation";
import { hpkeKeyIdMap, hpkeKeysMap } from "../repositories/Maps";
import { hpkeKeyIdMap, hpkeKeysMap, keyRotationPolicyMap } from "../repositories/Maps";
import { ServiceRequest } from "../utils/ServiceRequest";
import { LogContext, Logger } from "../utils/Logger";
import { KeyRotationPolicy } from "../policies/KeyRotationPolicy";

// Enable the endpoint
enableEndpoint();
Expand Down Expand Up @@ -175,6 +176,7 @@ export const key = (
logContext
);
}

const receipt = hpkeKeysMap.receipt(kid);

if (validateAttestationResult.statusCode === 202) {
Expand Down Expand Up @@ -341,6 +343,15 @@ export const unwrapKey = (
);
}

const [expired, _depricated] = KeyRotationPolicy.isExpired(keyRotationPolicyMap, keyItem, logContext);
if (expired) {
return ServiceResult.Failed<string>(
{ errorMessage: `${name}:kid ${wrappedKid} has expired` },
410, // 410 Gone, key no longer available
logContext
);
}

const receipt = hpkeKeysMap.receipt(wrappedKid);

// Get receipt if available, otherwise return accepted
Expand Down
23 changes: 20 additions & 3 deletions src/endpoints/refreshEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import * as ccfapp from "@microsoft/ccf-app";
import { ServiceResult } from "../utils/ServiceResult";
import { IKeyItem } from "./IKeyItem";
import { hpkeKeyIdMap, hpkeKeysMap } from "../repositories/Maps";
import { hpkeKeyIdMap, hpkeKeysMap, keyRotationPolicyMap } from "../repositories/Maps";
import { KeyGeneration } from "./KeyGeneration";
import { enableEndpoint } from "../utils/Tooling";
import { ServiceRequest } from "../utils/ServiceRequest";
import { LogContext, Logger } from "../utils/Logger";
import { KeyRotationPolicy } from "../policies/KeyRotationPolicy";

// Enable the endpoint
enableEndpoint();
Expand All @@ -31,21 +32,37 @@ export const refresh = (
const [_, isValidIdentity] = serviceRequest.isAuthenticated();
if (isValidIdentity.failure) return isValidIdentity;

// Get key rotation policy if available
const creationTime = Date.now();
Logger.info(`Creation time: ${creationTime}`, logContext);

const expiry = KeyRotationPolicy.getKeyItemExpiryTime(
keyRotationPolicyMap,
creationTime,
logContext
)?.expiryTimeMs;

if (expiry !== undefined) {
Logger.info(`${name}: Key rotation policy defined. Expiry: ${expiry}`, logContext);
} else {
Logger.info(`${name}: Key rotation policy not defined`, logContext);
}

try {
// Get HPKE key pair id
const id = hpkeKeyIdMap.size + 1;

// since OHTTP is limited to 2 char ids, we can only have ids from 10 to 99
// So the current logic is to have ids rotate from 10 to 99
const keyItem = KeyGeneration.generateKeyItem(id % 90 + 10);
const keyItem = KeyGeneration.generateKeyItem(id % 90 + 10, expiry);

// Store HPKE key pair kid
keyItem.kid = `${keyItem.kid!}_${id}`;
hpkeKeyIdMap.storeItem(id, keyItem.kid);

// Store HPKE key pair
hpkeKeysMap.storeItem(keyItem.kid, keyItem, keyItem.x);
Logger.info(`Key item with id ${id} and kid ${keyItem.kid} stored`);
Logger.info(`Key item with id ${id} and kid ${keyItem.kid} stored`, logContext);

delete keyItem.d;
const ret = keyItem;
Expand Down
4 changes: 4 additions & 0 deletions src/policies/IKeyRotationPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IKeyRotationPolicy {
rotation_interval_seconds: number;
grace_period_seconds: number;
}
184 changes: 184 additions & 0 deletions src/policies/KeyRotationPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import * as ccfapp from "@microsoft/ccf-app";
import { ccf } from "@microsoft/ccf-app/global";
import { IKeyRotationPolicy } from "./IKeyRotationPolicy";
import { Logger, LogContext } from "../utils/Logger";
import { KmsError } from "../utils/KmsError";
import { IKeyItem } from "../endpoints/IKeyItem";
import { enableEndpoint } from "../utils/Tooling";


// Enable the endpoint
enableEndpoint();
/**
* Interface representing the expiry times.
*/
export interface IExpiryTimes {
expiryTimeMs: number;
expiryTimeAndGraceMs: number;
}

/**
* Class representing a Key Rotation Policy.
*/
export class KeyRotationPolicy {
/**
* Constructs a new KeyRotationPolicy instance.
* @param keyRotationPolicy - The key rotation policy settings.
*/
constructor(public keyRotationPolicy: IKeyRotationPolicy) { }

/**
* The log context for the KeyRotationPolicy class.
* @private
*/
private static readonly logContext = new LogContext().appendScope(
"KeyRotationPolicy"
);

/**
* Returns the default key rotation policy.
* @returns The default key rotation policy.
*/
public static defaultKeyRotationPolicy(): IKeyRotationPolicy {
return {
rotation_interval_seconds: 300,
grace_period_seconds: 60,
};
}

/**
* Logs the key rotation policy settings.
* @param keyRotationPolicy - The key rotation policy to log.
*/
public static logKeyRotationPolicy(
keyRotationPolicy: IKeyRotationPolicy
): void {
Logger.debug(
`Rotation Interval Seconds: ${keyRotationPolicy.grace_period_seconds}`,
KeyRotationPolicy.logContext
);
Logger.debug(
`Grace Period Seconds: ${keyRotationPolicy.grace_period_seconds}`,
KeyRotationPolicy.logContext
);
}

/**
* Loads the key rotation from the key rotation policy map.
* If a key rotation policy is found, it is parsed and returned as an instance of `KeyRotationPolicy`.
* If no key rotation policy is found, default key rotation policy are used.
* @param keyRotationPolicyMap - The map containing the key rotation policy.
* @param logContextIn - The log context to use.
* @returns A new KeyRotationPolicy instance.
* @throws {KmsError} If the key rotation policy cannot be parsed.
*/
public static getKeyRotationPolicyFromMap(
keyRotationPolicyMap: ccfapp.KvMap,
logContextIn: LogContext
): IKeyRotationPolicy | undefined {
const logContext = (logContextIn?.clone() || new LogContext()).appendScope(
"loadKeyRotationPolicyFromMap"
);

// Load the key rotation from the map
const key = "key_rotation_policy"; // Ensure the key matches the stored key in governance
const keyBuf = ccf.strToBuf(key);

const keyRotationPolicy = keyRotationPolicyMap.get(keyBuf);
const keyRotationPolicyStr = keyRotationPolicy
? ccf.bufToStr(keyRotationPolicy)
: undefined;
Logger.debug(
`Loading key rotation policy: ${keyRotationPolicyStr}`,
logContext
);

let keyRotation: IKeyRotationPolicy;
if (!keyRotationPolicyStr) {
Logger.info(
`No key rotation policy found`,
logContext
);
return undefined;
} else {
try {
keyRotation = JSON.parse(keyRotationPolicyStr) as IKeyRotationPolicy;
} catch {
const error = `Failed to parse key rotation policy: ${keyRotationPolicyStr}`;
Logger.error(error, logContext);
throw new KmsError(error, logContext);
}
}
return keyRotation;
}

/**
*
* @param keyRotationPolicyMap - The map containing the key rotation policy.
* @param keyItem - The key item to check.
* @param logContextIn - The log context to use.
* @returns [expired, deprecated] - A tuple containing two number values. The first value indicates the key expiring time, and the second value indicates if the key deprecation time.
*/
public static getKeyItemExpiryTime(
keyRotationPolicyMap: ccfapp.KvMap,
keyItemCreationTime: number,
logContextIn: LogContext): IExpiryTimes | undefined {

const keyRotation = KeyRotationPolicy.getKeyRotationPolicyFromMap(
keyRotationPolicyMap,
logContextIn
);

if (keyRotation !== undefined) {
const gracePeriodSeconds = keyRotation.grace_period_seconds;
const rotationIntervalSeconds = keyRotation.rotation_interval_seconds;
Logger.info(`Key rotation policy content: ${JSON.stringify(keyRotation)}`, logContextIn);

// Get the current time using TrustedTime
const currentTime = Date.now();

// Get the creation time of the key
const creationTimeMs = keyItemCreationTime;

// Calculate the expiry time of the key by adding the rotation interval to the creation date
const expiryTimeMs = creationTimeMs + (rotationIntervalSeconds * 1000);
const expiryTimeAndGraceMs = expiryTimeMs + (gracePeriodSeconds * 1000);

Logger.info(`Key rotation policy Creation time: ${new Date(creationTimeMs)}, Current Time: ${new Date(currentTime)}, delta (ms): ${currentTime - creationTimeMs}, expiryTimeMs (${expiryTimeMs}): ${new Date(expiryTimeMs)}, expiryTimeAndGraceMs (${expiryTimeAndGraceMs}): ${new Date(expiryTimeAndGraceMs)}`);
return {expiryTimeMs, expiryTimeAndGraceMs};
} else {
Logger.info(`Key rotation policy is not defined, cannot calculate expiry time`, logContextIn);
return undefined;
}
}

/**
*
* @param keyRotationPolicyMap - The map containing the key rotation policy.
* @param keyItem - The key item to check.
* @param logContextIn - The log context to use.
* @returns [expired, deprecated] - A tuple containing two boolean values. The first value indicates if the key has expired, and the second value indicates if the key is deprecated.
*/
public static isExpired(
keyRotationPolicyMap: ccfapp.KvMap,
keyItem: IKeyItem,
logContextIn: LogContext): [boolean, boolean] {

const currentTimeMs = Date.now();
const expiryTimes = KeyRotationPolicy.getKeyItemExpiryTime(keyRotationPolicyMap, keyItem.timestamp!, logContextIn);

// check if key rotation policy is defined
if (!expiryTimes) {
return [false, false];
}

if (currentTimeMs > expiryTimes.expiryTimeMs) {
Logger.warn(`Key has expired and is no longer valid`, logContextIn);
return [true, true];
} else if (currentTimeMs > expiryTimes.expiryTimeAndGraceMs) {
Logger.warn(`Key is deprecated and will expire soon`, logContextIn);
return [false, true];
}
return [false, false];
}
}
2 changes: 2 additions & 0 deletions src/repositories/Maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ export const keyReleaseMapName = "public:ccf.gov.policies.key_release";
export const keyReleasePolicyMap = ccf.kv[keyReleaseMapName];
export const settingsMapName = "public:ccf.gov.policies.settings";
export const settingsPolicyMap = ccf.kv[settingsMapName];
export const keyRotationMapName = "public:ccf.gov.policies.key_rotation";
export const keyRotationPolicyMap = ccf.kv[keyRotationMapName];
//#endregion
Loading
Loading