diff --git a/src/api/endpoints/kms.ts b/src/api/endpoints/kms.ts new file mode 100644 index 0000000..c6e63a0 --- /dev/null +++ b/src/api/endpoints/kms.ts @@ -0,0 +1,63 @@ +import { ApiClient } from "../base"; +import { CreateKmsKeyRequest, DecryptKmsKeyOptions, DecryptKmsKeyResponse, EncryptKmsKeyOptions, EncryptKmsKeyResponse, GetKmsKeyByNameOptions, KmsKeyResponse, KmsPublicKeyReponse, KmsSignDataOptions, KmsSignDataResponse, KmsSigningAlgorithmsResponse, KmsVerifySignatureOptions, KmsVerifySignatureResponse, ListKmsKeyRequest, ListKmsKeysResponse, UpdateKmsKeyRequest } from "../types"; + +export class KmsApi { + constructor(private apiClient: ApiClient){} + + async ListKeys(params: ListKmsKeyRequest) : Promise{ + return this.apiClient.get("/api/v1/kms/keys", { + params: params, + }); + } + + async GetKeyById(keyId: string) : Promise { + return this.apiClient.get(`/api/v1/kms/keys/${keyId}`); + } + + async GetKeyByName(options: GetKmsKeyByNameOptions): Promise { + return this.apiClient.get(`/api/v1/kms/keys/key-name/${options.keyName}`, { + params: { projectId: options.projectId }, + }); + } + + async CreateKey(data: CreateKmsKeyRequest): Promise { + return this.apiClient.post(`/api/v1/kms/keys`, data); + } + + async updateKey(options: UpdateKmsKeyRequest): Promise { + const { keyId, ...updateData } = options; + return this.apiClient.patch(`/api/v1/kms/keys/${keyId}`, updateData); + } + + async deleteKey(keyId: string): Promise { + return this.apiClient.delete(`/api/v1/kms/keys/${keyId}`); + } + + async encryptData(options: EncryptKmsKeyOptions): Promise{ + const { keyId, plaintext } = options; + return this.apiClient.post(`/api/v1/kms/keys/${keyId}/encrypt`, { plaintext }); + } + + async decryptData(options: DecryptKmsKeyOptions): Promise{ + const { keyId, ciphertext } = options; + return this.apiClient.post(`/api/v1/kms/keys/${keyId}/decrypt`, { ciphertext }); + } + + async signData(options: KmsSignDataOptions): Promise{ + const {keyId, ...payloadOptions} = options; + return this.apiClient.post(`/api/v1/kms/keys/${keyId}/sign`, payloadOptions); + } + + async verifySignature(options: KmsVerifySignatureOptions): Promise { + const { keyId, ...payloadOptions } = options; + return this.apiClient.post(`/api/v1/kms/keys/${keyId}/verify`, payloadOptions); + } + + async getPublicKey(keyId: string): Promise { + return this.apiClient.get(`/api/v1/kms/keys/${keyId}/public-key`); + } + + async listSigningAlgorithms(keyId: string): Promise { + return this.apiClient.get(`/api/v1/kms/keys/${keyId}/signing-algorithms`); + } +} \ No newline at end of file diff --git a/src/api/types/index.ts b/src/api/types/index.ts index c8ddc8e..7a03cd9 100644 --- a/src/api/types/index.ts +++ b/src/api/types/index.ts @@ -6,6 +6,7 @@ export * from "./dynamic-secrets"; export * from "./environments"; export * from "./projects"; export * from "./folders"; +export * from "./kms"; export interface ApiResponse { statusCode: number; diff --git a/src/api/types/kms.ts b/src/api/types/kms.ts new file mode 100644 index 0000000..53ae87c --- /dev/null +++ b/src/api/types/kms.ts @@ -0,0 +1,142 @@ + + +export interface KmsKey { + id: string; + orgId: string; + name: string; + createdAt: string; + updatedAt: string; + encryptionAlgorithm: string; + description?: string; + isDisabled?: boolean; + projectId?: string; + keyUsage?: string; + version?: number; +} + +export interface KmsKeyResponse { + key: KmsKey; +} + +export interface GetKmsKeyByNameOptions { + keyName: string; + projectId: string; +} + +export interface ListKmsKeysResponse { + keys: KmsKey[]; + totalCount: number; +} + +export enum KmsKeyUsage { + EncryptDecrypt = "encrypt-decrypt", + SignVerify = "sign-verify" +} + +export enum KmsKeyEncryptionAlgorithm { + AES256 = "aes-256-gcm", + AES128 = "aes-128-gcm", + RSA_4096 = "RSA_4096", + ECC_NIST_P256 = "ECC_NIST_P256", +} +export interface CreateKmsKeyRequest { + projectId: string; + name: string; + description?: string; + keyUsage?: KmsKeyUsage; + encryptionAlgorithm?: KmsKeyEncryptionAlgorithm; +} + +export type CreateKmsOptions = { + projectId: string; + name: string; + description?: string; + keyUsage?: KmsKeyUsage; + encryptionAlgorithm?: KmsKeyEncryptionAlgorithm; +} +export interface UpdateKmsKeyRequest { + keyId: string; + name?: string; + isDisabled?: boolean; + description?: string; +} +export interface ListKmsKeyRequest { + projectId: string; + offset?: number; + limit?: number; + orderBy?: "name"; + orderDirection?: "asc"|"desc"; + search?: string; +} + +export type ListKmsOptions = { + projectId: string; + offset?: number; + limit?: number; + orderBy?: "name"; + orderDirection?: "asc" | "desc"; + search?: string; +} + +export interface EncryptKmsKeyOptions { + keyId: string; + plaintext: string; +} + +export interface DecryptKmsKeyOptions { + keyId: string; + ciphertext: string; +} +export interface EncryptKmsKeyResponse { + ciphertext: string; +} +export interface DecryptKmsKeyResponse { + plaintext: string; +} + +export enum KmsSigningAlgorithm{ + RSASSA_PSS_SHA_512 = "RSASSA_PSS_SHA_512", + RSASSA_PSS_SHA_384 = "RSASSA_PSS_SHA_384", + RSASSA_PSS_SHA_256 = "RSASSA_PSS_SHA_256", + RSASSA_PKCS1_V1_5_SHA_512 = "RSASSA_PKCS1_V1_5_SHA_512", + RSASSA_PKCS1_V1_5_SHA_384 = "RSASSA_PKCS1_V1_5_SHA_384", + RSASSA_PKCS1_V1_5_SHA_256 = "RSASSA_PKCS1_V1_5_SHA_256", + ECDSA_SHA_512 = "ECDSA_SHA_512", + ECDSA_SHA_384 = "ECDSA_SHA_384", + ECDSA_SHA_256 = "ECDSA_SHA_256", +} + +export interface KmsSignDataOptions { + keyId: string; + signingAlgorithm: KmsSigningAlgorithm; + data: string; + isDigest?:boolean; +} + +export interface KmsSignDataResponse { + signature: string; + keyId: string; + signingAlgorithm: KmsSigningAlgorithm; +} + +export interface KmsVerifySignatureOptions { + keyId: string; + data: string; + signature: string; + signingAlgorithm: KmsSigningAlgorithm; + isDigest?: boolean; +} + +export interface KmsVerifySignatureResponse { + signatureValid: boolean; + keyId: string; + signingAlgorithm: KmsSigningAlgorithm; +} + +export interface KmsPublicKeyReponse { + publicKey: string; +} + +export interface KmsSigningAlgorithmsResponse { + signingAlgorithms: KmsSigningAlgorithm[]; +} \ No newline at end of file diff --git a/src/custom/kms.ts b/src/custom/kms.ts new file mode 100644 index 0000000..bc1da36 --- /dev/null +++ b/src/custom/kms.ts @@ -0,0 +1,151 @@ +import { KmsApi } from "../api/endpoints/kms"; +import { CreateKmsOptions, DecryptKmsKeyOptions, EncryptKmsKeyOptions, GetKmsKeyByNameOptions, KmsKeyEncryptionAlgorithm, KmsKeyUsage, KmsSignDataOptions, KmsVerifySignatureOptions, ListKmsOptions } from "../api/types"; +import { newInfisicalError } from "./errors"; + +export default class KmsClient { + constructor(private apiClient: KmsApi) {} + + listKeys = async (options: ListKmsOptions) => { + try { + const res = await this.apiClient.ListKeys({ + projectId: options.projectId, + offset: options.offset ? options.offset : 0, + limit: options.limit ? options.limit : 100, + orderBy: options.orderBy, + orderDirection: options.orderDirection, + search: options.search, + }) + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + getKeyById = async (keyId: string) => { + try { + const res = await this.apiClient.GetKeyById(keyId); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + getKeyByName = async (options: GetKmsKeyByNameOptions) => { + try { + const res = await this.apiClient.GetKeyByName(options); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + createKey = async (data: CreateKmsOptions) =>{ + try { + const res = await this.apiClient.CreateKey({ + projectId: data.projectId, + name: data.name, + description: data.description, + keyUsage: data.keyUsage ? data.keyUsage : KmsKeyUsage.EncryptDecrypt, + encryptionAlgorithm: data.encryptionAlgorithm ? data.encryptionAlgorithm : KmsKeyEncryptionAlgorithm.AES256, + }); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + updateKey = async (options: { keyId: string; name?: string; isDisabled?: boolean; description?: string }) => { + try { + const res = await this.apiClient.updateKey({ + keyId: options.keyId, + name: options.name, + isDisabled: options.isDisabled, + description: options.description, + }); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + deleteKey = async (keyId: string) => { + try { + const res = await this.apiClient.deleteKey(keyId); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + encryptData = async (options: EncryptKmsKeyOptions) => { + try { + const res = await this.apiClient.encryptData({ + keyId: options.keyId, + plaintext: Buffer.from(options.plaintext).toString('base64'), + }); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + decryptData = async (options: DecryptKmsKeyOptions) => { + try { + const res = await this.apiClient.decryptData({ + keyId: options.keyId, + ciphertext: options.ciphertext, + }); + res.plaintext = Buffer.from(res.plaintext, 'base64').toString('utf-8'); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + signData = async (options: KmsSignDataOptions) => { + try { + const res = await this.apiClient.signData({ + keyId: options.keyId, + signingAlgorithm: options.signingAlgorithm, + data: Buffer.from(options.data).toString('base64'), + isDigest: options.isDigest ? options.isDigest : false, + }); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + verifySignature = async (options: KmsVerifySignatureOptions ) => { + try { + const res = await this.apiClient.verifySignature({ + keyId: options.keyId, + signature: Buffer.from(options.signature).toString('base64'), + signingAlgorithm: options.signingAlgorithm, + data: Buffer.from(options.data).toString('base64'), + isDigest: options.isDigest ? options.isDigest : false, + }); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + getPublicKey = async (keyId: string) => { + try { + const res = await this.apiClient.getPublicKey(keyId); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } + + listSigningAlgorithms = async (keyId: string) => { + try { + const res = await this.apiClient.listSigningAlgorithms(keyId); + return res; + } catch (err) { + throw newInfisicalError(err); + } + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index cde9a27..acd7673 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { DynamicSecretsApi } from "./api/endpoints/dynamic-secrets"; import { EnvironmentsApi } from "./api/endpoints/environments"; import { ProjectsApi } from "./api/endpoints/projects"; import { FoldersApi } from "./api/endpoints/folders"; +import { KmsApi } from "./api/endpoints/kms"; import SecretsClient from "./custom/secrets"; import AuthClient from "./custom/auth"; @@ -12,6 +13,7 @@ import DynamicSecretsClient from "./custom/dynamic-secrets"; import EnvironmentsClient from "./custom/environments"; import ProjectsClient from "./custom/projects"; import FoldersClient from "./custom/folders"; +import KmsClient from "./custom/kms"; type InfisicalSDKOptions = { siteUrl?: string; @@ -27,6 +29,7 @@ class InfisicalSDK { private environmentsApi: EnvironmentsApi; private projectsApi: ProjectsApi; private foldersApi: FoldersApi; + private kmsApi : KmsApi // Domain clients private authClient: AuthClient; @@ -35,6 +38,7 @@ class InfisicalSDK { private environmentsClient: EnvironmentsClient; private projectsClient: ProjectsClient; private foldersClient: FoldersClient; + private kmsClient: KmsClient; constructor(options?: InfisicalSDKOptions) { const baseURL = options?.siteUrl || "https://app.infisical.com"; @@ -49,6 +53,7 @@ class InfisicalSDK { this.environmentsApi = new EnvironmentsApi(this.apiClient); this.projectsApi = new ProjectsApi(this.apiClient); this.foldersApi = new FoldersApi(this.apiClient); + this.kmsApi = new KmsApi(this.apiClient); // Initialize domain clients this.authClient = new AuthClient( @@ -62,6 +67,7 @@ class InfisicalSDK { this.environmentsClient = new EnvironmentsClient(this.environmentsApi); this.projectsClient = new ProjectsClient(this.projectsApi); this.foldersClient = new FoldersClient(this.foldersApi); + this.kmsClient = new KmsClient(this.kmsApi); } private authenticate(accessToken: string) { @@ -85,6 +91,7 @@ class InfisicalSDK { folders = () => this.foldersClient; dynamicSecrets = () => this.dynamicSecretsClient; auth = () => this.authClient; + kms = () => this.kmsClient; } // Export main SDK class diff --git a/test/kms_test.ts b/test/kms_test.ts new file mode 100644 index 0000000..91c7937 --- /dev/null +++ b/test/kms_test.ts @@ -0,0 +1,82 @@ +import { InfisicalSDK, KmsKeyEncryptionAlgorithm, KmsKeyUsage, KmsSigningAlgorithm } from "../src"; + +(async () =>{ + const client = new InfisicalSDK(); + + await client.auth().universalAuth.login({ + clientId: "", + clientSecret: "", + }); + console.log("Logged in successfully"); + + const keys = await client.kms().listKeys({ + projectId: "", + offset: 0, + limit: 100, // DEFAULT VALUE + orderBy: "name", // OPTIONAL + orderDirection: 'desc', // OPTIONAL ALSO HAS "asc" + search: "" + }); + console.log("kms keys:", keys); + + const key = await client.kms().getKeyById(""); + console.log("Key details:", key); + + const keyByName = await client.kms().getKeyByName({ + keyName: "", + projectId: "" + }); + console.log("Key by name:", keyByName); + + const newKey = await client.kms().createKey({ + projectId: "", + name: "", + description: "", + keyUsage: KmsKeyUsage.SignVerify, // or KmsKeyUsage.EncryptDecrypt + encryptionAlgorithm: KmsKeyEncryptionAlgorithm.RSA_4096 // or KmsKeyEncryptionAlgorithm.AES256, etc. + }); + + console.log("Newly created key:", newKey); + + const updatedKey = await client.kms().updateKey({ + keyId: newKey.key.id, + name: "", + isDisabled: false, + description: "" + }); + console.log("Updated key:", updatedKey); + + const deletedKey = await client.kms().deleteKey(newKey.key.id); + console.log("Deleted key response:", deletedKey); + + const encryptedData = await client.kms().encryptData({ + keyId: newKey.key.id, + plaintext: "" + }); + console.log("Encrypted data:", encryptedData); + + const decryptedData = await client.kms().decryptData({ + keyId: newKey.key.id, + ciphertext: encryptedData.ciphertext + }); + console.log("Decrypted data:", decryptedData); + + const signedData = await client.kms().signData({ + keyId: newKey.key.id, + data: "<DATA_TO_SIGN>", + signingAlgorithm: KmsSigningAlgorithm.RSASSA_PSS_SHA_512 // Specify the signing algorithm + }); + console.log("Signed data:", signedData); + const verifiedSignature = await client.kms().verifySignature({ + keyId: newKey.key.id, + data: "<DATA_TO_SIGN>", + signature: signedData.signature, + signingAlgorithm: KmsSigningAlgorithm.RSASSA_PSS_SHA_512 // Specify the same algorithm used for signing + }); + console.log("Signature verification result:", verifiedSignature); + + const publicKey = await client.kms().getPublicKey(newKey.key.id); + console.log("Public key:", publicKey); + const signingAlgorithms = await client.kms().listSigningAlgorithms(newKey.key.id); + console.log("Signing algorithms:", signingAlgorithms); +})(); \ No newline at end of file