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 deprecation policy. #194

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
38 changes: 37 additions & 1 deletion src/endpoints/keyEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ 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 { TrustedTime } from "../utils/TrustedTime";
import { KeyRotationPolicy } from "../policies/KeyRotationPolicy";

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

const keyRotation = KeyRotationPolicy.loadKeyRotationPolicyFromMap(
keyRotationPolicyMap,
logContext
).keyRotationPolicy;
const gracePeriodSeconds = keyRotation.gracePeriodSeconds;
const rotationIntervalSeconds = keyRotation.rotationIntervalSeconds;
// Get the current time using TrustedTime
const currentTime = TrustedTime.getCurrentTime();
const currentDate = new Date(currentTime);

// Get the creation date of the key
const creationDate = new Date(keyItem.timestamp!);

// Calculate the expiry date of the key by adding the rotation interval to the creation date
const expiryDateMillis =
creationDate.getTime() + rotationIntervalSeconds * 1000;
const expiryDate = new Date(expiryDateMillis);
// Calculate the grace period start date by subtracting the grace period from the expiry date
const gracePeriodMillis = gracePeriodSeconds * 1000;
const gracePeriodStartDate = new Date(
expiryDate.getTime() - gracePeriodMillis
);

if (currentDate > expiryDate) {
return ServiceResult.Failed<string>(
{ errorMessage: `${name}: Key has expired and is no longer valid` },
400,
logContext
);
} else if (currentDate > gracePeriodStartDate) {
Logger.warn(`${name}: Key is deprecated and will expire soon`);
}

const receipt = hpkeKeysMap.receipt(kid);

if (validateAttestationResult.statusCode === 202) {
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 {
rotationIntervalSeconds: number;
gracePeriodSeconds: number;
}
101 changes: 101 additions & 0 deletions src/policies/KeyRotationPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
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";

/**
* 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 {
rotationIntervalSeconds: 300,
gracePeriodSeconds: 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.rotationIntervalSeconds}`,
KeyRotationPolicy.logContext
);
Logger.debug(
`Grace Period Seconds: ${keyRotationPolicy.gracePeriodSeconds}`,
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 loadKeyRotationPolicyFromMap(
keyRotationPolicyMap: ccfapp.KvMap,
logContextIn: LogContext
): KeyRotationPolicy {
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.warn(
`No key rotation policy found, using default key rotation policy`,
logContext
);
keyRotation = KeyRotationPolicy.defaultKeyRotationPolicy();
} 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 new KeyRotationPolicy(keyRotation);
}
}
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
12 changes: 12 additions & 0 deletions src/utils/TrustedTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export class TrustedTime {
private static lastTimestamp: number = 0;

public static getCurrentTime(): number {
const currentTime = Date.now();
if (currentTime <= this.lastTimestamp) {
throw new Error("System time moved backwards.");
}
this.lastTimestamp = currentTime;
return currentTime;
}
}
74 changes: 74 additions & 0 deletions test/system-test/test_keyRotationPolicy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import pytest
from endpoints import key, refresh
from utils import apply_kms_constitution, apply_key_release_policy, trust_jwt_issuer, get_test_attestation, get_test_wrapping_key
from datetime import datetime, timedelta

def test_key_in_grace_period(setup_kms):
"""
Test the key retrieval during the grace period.
Simulates the key being in the grace period by manipulating the key's timestamp.
"""
apply_kms_constitution()
apply_key_release_policy()
refresh()
while True:
status_code, key_json = key(
attestation=get_test_attestation(),
wrapping_key=get_test_wrapping_key(),
)
if status_code != 202:
break
assert status_code == 200

# Simulate key being in the grace period by manipulating the key's timestamp
key_id = key_json["wrappedKid"]
key_item = hpkeKeysMap.store.get(key_id)
interval_period_seconds = 300 # Interval period in seconds
grace_period_seconds = 60 # Grace period in seconds
# Set the key's timestamp to be within the grace period
key_item.timestamp = (datetime.utcnow() - timedelta(seconds=interval_period_seconds - grace_period_seconds + 30)).isoformat() + "Z"
hpkeKeysMap.store.set(key_id, key_item)

# Attempt to retrieve the key again
status_code, key_json = key(
attestation=get_test_attestation(),
wrapping_key=get_test_wrapping_key(),
)
assert status_code == 200
assert key_json["wrappedKid"] != ""
assert key_json["wrapped"] == ""

def test_key_expired(setup_kms):
"""
Test the key retrieval after the key has expired.
Simulates the key being expired by manipulating the key's timestamp.
"""
apply_kms_constitution()
apply_key_release_policy()
refresh()
while True:
status_code, key_json = key(
attestation=get_test_attestation(),
wrapping_key=get_test_wrapping_key(),
)
if status_code != 202:
break
assert status_code == 200

# Simulate key being expired by manipulating the key's timestamp
key_id = key_json["wrappedKid"]
key_item = hpkeKeysMap.store.get(key_id)
interval_period_seconds = 300 # Interval period in seconds
key_item.timestamp = (datetime.utcnow() - timedelta(seconds=interval_period_seconds + 30)).isoformat() + "Z"
hpkeKeysMap.store.set(key_id, key_item)

# Attempt to retrieve the key again
status_code, key_json = key(
attestation=get_test_attestation(),
wrapping_key=get_test_wrapping_key(),
)
assert status_code == 400

if __name__ == "__main__":
import pytest
pytest.main([__file__, "-s"])
Loading