mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 22:28:15 +00:00
Minor fix on keys
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
|||||||
validateAndMapAltNameType,
|
validateAndMapAltNameType,
|
||||||
validateCaDateField
|
validateCaDateField
|
||||||
} from "@app/services/certificate-authority/certificate-authority-validators";
|
} from "@app/services/certificate-authority/certificate-authority-validators";
|
||||||
|
import { mapEnumsForValidation } from "@app/services/certificate-common/certificate-utils";
|
||||||
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
||||||
|
|
||||||
export const registerCertificatesRouter = async (server: FastifyZodProvider) => {
|
export const registerCertificatesRouter = async (server: FastifyZodProvider) => {
|
||||||
@@ -49,44 +50,48 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const rawCertificateRequest = {
|
||||||
|
commonName: req.body.commonName,
|
||||||
|
keyUsages: req.body.keyUsages,
|
||||||
|
extendedKeyUsages: req.body.extendedKeyUsages,
|
||||||
|
subjectAlternativeNames: req.body.altNames
|
||||||
|
? req.body.altNames
|
||||||
|
.split(", ")
|
||||||
|
.map((name) => name.trim())
|
||||||
|
.map((name) => {
|
||||||
|
const mappedType = validateAndMapAltNameType(name);
|
||||||
|
if (!mappedType) return null;
|
||||||
|
const typeMapping = {
|
||||||
|
dns: "dns_name",
|
||||||
|
ip: "ip_address",
|
||||||
|
email: "email",
|
||||||
|
url: "uri"
|
||||||
|
} as const;
|
||||||
|
return {
|
||||||
|
type: typeMapping[mappedType.type] as "dns_name" | "ip_address" | "email" | "uri",
|
||||||
|
value: mappedType.value
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item): item is NonNullable<typeof item> => item !== null)
|
||||||
|
: undefined,
|
||||||
|
validity: {
|
||||||
|
ttl: req.body.ttl
|
||||||
|
},
|
||||||
|
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
|
||||||
|
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
|
||||||
|
signatureAlgorithm: req.body.signatureAlgorithm,
|
||||||
|
keyAlgorithm: req.body.keyAlgorithm
|
||||||
|
};
|
||||||
|
|
||||||
|
const mappedCertificateRequest = mapEnumsForValidation(rawCertificateRequest);
|
||||||
|
|
||||||
const data = await server.services.certificateV3.issueCertificateFromProfile({
|
const data = await server.services.certificateV3.issueCertificateFromProfile({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
profileId: req.body.profileId,
|
profileId: req.body.profileId,
|
||||||
certificateRequest: {
|
certificateRequest: mappedCertificateRequest
|
||||||
commonName: req.body.commonName,
|
|
||||||
keyUsages: req.body.keyUsages,
|
|
||||||
extendedKeyUsages: req.body.extendedKeyUsages,
|
|
||||||
subjectAlternativeNames: req.body.altNames
|
|
||||||
? req.body.altNames
|
|
||||||
.split(", ")
|
|
||||||
.map((name) => name.trim())
|
|
||||||
.map((name) => {
|
|
||||||
const mappedType = validateAndMapAltNameType(name);
|
|
||||||
if (!mappedType) return null;
|
|
||||||
const typeMapping = {
|
|
||||||
dns: "dns_name",
|
|
||||||
ip: "ip_address",
|
|
||||||
email: "email",
|
|
||||||
url: "uri"
|
|
||||||
} as const;
|
|
||||||
return {
|
|
||||||
type: typeMapping[mappedType.type] as "dns_name" | "ip_address" | "email" | "uri",
|
|
||||||
value: mappedType.value
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((item): item is NonNullable<typeof item> => item !== null)
|
|
||||||
: undefined,
|
|
||||||
validity: {
|
|
||||||
ttl: req.body.ttl
|
|
||||||
},
|
|
||||||
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
|
|
||||||
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
|
|
||||||
signatureAlgorithm: req.body.signatureAlgorithm,
|
|
||||||
keyAlgorithm: req.body.keyAlgorithm
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const profile = await server.services.certificateProfile.getProfileById({
|
const profile = await server.services.certificateProfile.getProfileById({
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
interface CertificateRequestInput {
|
interface CertificateRequestInput {
|
||||||
keyUsages?: string[];
|
keyUsages?: string[];
|
||||||
extendedKeyUsages?: string[];
|
extendedKeyUsages?: string[];
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapEnumsForValidation = <T extends CertificateRequestInput>(request: T): T => {
|
export const mapEnumsForValidation = <T extends CertificateRequestInput>(request: T): T => {
|
||||||
@@ -12,7 +11,7 @@ export const mapEnumsForValidation = <T extends CertificateRequestInput>(request
|
|||||||
dataEncipherment: "data_encipherment",
|
dataEncipherment: "data_encipherment",
|
||||||
keyAgreement: "key_agreement",
|
keyAgreement: "key_agreement",
|
||||||
keyCertSign: "key_cert_sign",
|
keyCertSign: "key_cert_sign",
|
||||||
crlSign: "crl_sign",
|
cRLSign: "crl_sign",
|
||||||
encipherOnly: "encipher_only",
|
encipherOnly: "encipher_only",
|
||||||
decipherOnly: "decipher_only"
|
decipherOnly: "decipher_only"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -608,6 +608,31 @@ describe("CertificateTemplateV2Service", () => {
|
|||||||
expect(result.isValid).toBe(true);
|
expect(result.isValid).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should handle camelCase key usage mapping correctly", async () => {
|
||||||
|
const templateWithOptionalUsages = {
|
||||||
|
...sampleTemplate,
|
||||||
|
keyUsages: {
|
||||||
|
requiredUsages: { all: ["digital_signature", "non_repudiation", "key_agreement"] },
|
||||||
|
optionalUsages: { all: ["crl_sign", "decipher_only"] }
|
||||||
|
},
|
||||||
|
extendedKeyUsages: {
|
||||||
|
requiredUsages: { all: ["client_auth", "code_signing"] },
|
||||||
|
optionalUsages: { all: ["server_auth", "ocsp_signing"] }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mockCertificateTemplateV2DAL.findById.mockResolvedValue(templateWithOptionalUsages);
|
||||||
|
|
||||||
|
const requestWithCamelCaseUsages = {
|
||||||
|
...validRequest,
|
||||||
|
keyUsages: ["digital_signature", "non_repudiation", "key_agreement", "crl_sign", "decipher_only"],
|
||||||
|
extendedKeyUsages: ["client_auth", "code_signing"]
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await service.validateCertificateRequest("template-123", requestWithCamelCaseUsages);
|
||||||
|
expect(result.isValid).toBe(true);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
it("should validate wildcard patterns in optional attributes", async () => {
|
it("should validate wildcard patterns in optional attributes", async () => {
|
||||||
const wildcardTemplate = {
|
const wildcardTemplate = {
|
||||||
...sampleTemplate,
|
...sampleTemplate,
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ export const certificateTemplateV2ServiceFactory = ({
|
|||||||
request: TCertificateRequest
|
request: TCertificateRequest
|
||||||
): TTemplateValidationResult => {
|
): TTemplateValidationResult => {
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|
||||||
const warnings: string[] = [];
|
const warnings: string[] = [];
|
||||||
|
|
||||||
template.attributes?.forEach((attrPolicy) => {
|
template.attributes?.forEach((attrPolicy) => {
|
||||||
|
|||||||
@@ -141,6 +141,86 @@ describe("CertificateV3Service", () => {
|
|||||||
expect(result).toHaveProperty("certificateId", "cert-123");
|
expect(result).toHaveProperty("certificateId", "cert-123");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should correctly map camelCase key usages to snake_case before validation", async () => {
|
||||||
|
const profileId = "profile-123";
|
||||||
|
const mockProfile = {
|
||||||
|
id: profileId,
|
||||||
|
projectId: "project-123",
|
||||||
|
enrollmentType: EnrollmentType.API,
|
||||||
|
caId: "ca-123",
|
||||||
|
certificateTemplateId: "template-123"
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCA = {
|
||||||
|
id: "ca-123",
|
||||||
|
externalCa: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTemplate = {
|
||||||
|
id: "template-123",
|
||||||
|
signatureAlgorithm: { defaultAlgorithm: "RSA-SHA256" },
|
||||||
|
keyAlgorithm: { defaultKeyType: "RSA_2048" },
|
||||||
|
attributes: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCertificateResult = {
|
||||||
|
certificate: Buffer.from("cert"),
|
||||||
|
certificateChain: Buffer.from("chain"),
|
||||||
|
privateKey: Buffer.from("key"),
|
||||||
|
serialNumber: "123456"
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCertRecord = {
|
||||||
|
id: "cert-123",
|
||||||
|
serialNumber: "123456"
|
||||||
|
};
|
||||||
|
|
||||||
|
const camelCaseRequest = {
|
||||||
|
commonName: "test.example.com",
|
||||||
|
keyUsages: [
|
||||||
|
CertKeyUsage.DIGITAL_SIGNATURE,
|
||||||
|
CertKeyUsage.NON_REPUDIATION,
|
||||||
|
CertKeyUsage.KEY_AGREEMENT,
|
||||||
|
CertKeyUsage.CRL_SIGN,
|
||||||
|
CertKeyUsage.DECIPHER_ONLY
|
||||||
|
],
|
||||||
|
extendedKeyUsages: [
|
||||||
|
CertExtendedKeyUsage.CLIENT_AUTH,
|
||||||
|
CertExtendedKeyUsage.CODE_SIGNING,
|
||||||
|
CertExtendedKeyUsage.OCSP_SIGNING,
|
||||||
|
CertExtendedKeyUsage.SERVER_AUTH
|
||||||
|
],
|
||||||
|
validity: { ttl: "10d" }
|
||||||
|
};
|
||||||
|
|
||||||
|
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue(mockProfile);
|
||||||
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
||||||
|
isValid: true,
|
||||||
|
errors: [],
|
||||||
|
warnings: []
|
||||||
|
});
|
||||||
|
mockCertificateAuthorityDAL.findByIdWithAssociatedCa.mockResolvedValue(mockCA);
|
||||||
|
mockCertificateTemplateV2Service.getTemplateV2ById.mockResolvedValue(mockTemplate);
|
||||||
|
mockInternalCaService.issueCertFromCa.mockResolvedValue(mockCertificateResult);
|
||||||
|
mockCertificateDAL.findOne.mockResolvedValue(mockCertRecord);
|
||||||
|
mockCertificateDAL.updateById.mockResolvedValue({});
|
||||||
|
|
||||||
|
await service.issueCertificateFromProfile({
|
||||||
|
profileId,
|
||||||
|
certificateRequest: camelCaseRequest,
|
||||||
|
...mockActor
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify that the template validation service was called with mapped snake_case values
|
||||||
|
expect(mockCertificateTemplateV2Service.validateCertificateRequest).toHaveBeenCalledWith(
|
||||||
|
"template-123",
|
||||||
|
expect.objectContaining({
|
||||||
|
keyUsages: ["digital_signature", "non_repudiation", "key_agreement", "crl_sign", "decipher_only"],
|
||||||
|
extendedKeyUsages: ["client_auth", "code_signing", "ocsp_signing", "server_auth"]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("should throw ForbiddenRequestError when profile is not configured for API enrollment", async () => {
|
it("should throw ForbiddenRequestError when profile is not configured for API enrollment", async () => {
|
||||||
const profileId = "profile-123";
|
const profileId = "profile-123";
|
||||||
const mockProfile = {
|
const mockProfile = {
|
||||||
|
|||||||
Reference in New Issue
Block a user