mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Minor fix on keys
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
validateAndMapAltNameType,
|
||||
validateCaDateField
|
||||
} 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";
|
||||
|
||||
export const registerCertificatesRouter = async (server: FastifyZodProvider) => {
|
||||
@@ -49,44 +50,48 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
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({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
profileId: req.body.profileId,
|
||||
certificateRequest: {
|
||||
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
|
||||
}
|
||||
certificateRequest: mappedCertificateRequest
|
||||
});
|
||||
|
||||
const profile = await server.services.certificateProfile.getProfileById({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
interface CertificateRequestInput {
|
||||
keyUsages?: string[];
|
||||
extendedKeyUsages?: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export const mapEnumsForValidation = <T extends CertificateRequestInput>(request: T): T => {
|
||||
@@ -12,7 +11,7 @@ export const mapEnumsForValidation = <T extends CertificateRequestInput>(request
|
||||
dataEncipherment: "data_encipherment",
|
||||
keyAgreement: "key_agreement",
|
||||
keyCertSign: "key_cert_sign",
|
||||
crlSign: "crl_sign",
|
||||
cRLSign: "crl_sign",
|
||||
encipherOnly: "encipher_only",
|
||||
decipherOnly: "decipher_only"
|
||||
};
|
||||
|
||||
@@ -608,6 +608,31 @@ describe("CertificateTemplateV2Service", () => {
|
||||
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 () => {
|
||||
const wildcardTemplate = {
|
||||
...sampleTemplate,
|
||||
|
||||
@@ -120,6 +120,7 @@ export const certificateTemplateV2ServiceFactory = ({
|
||||
request: TCertificateRequest
|
||||
): TTemplateValidationResult => {
|
||||
const errors: string[] = [];
|
||||
|
||||
const warnings: string[] = [];
|
||||
|
||||
template.attributes?.forEach((attrPolicy) => {
|
||||
|
||||
@@ -141,6 +141,86 @@ describe("CertificateV3Service", () => {
|
||||
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 () => {
|
||||
const profileId = "profile-123";
|
||||
const mockProfile = {
|
||||
|
||||
Reference in New Issue
Block a user