fleet/website/api/controllers/get-est-device-certificate.js
Zach Wasserman 31c8edbd9f
Fix JSON parsing for fleetdm.com EST certificate issuance (#23977)
Missed a JSON.parse after refactoring the HTTP request code. This was
missed because we had to comment out some of the code for testing in
order to skip authentication.
2024-11-20 09:50:18 -08:00

148 lines
4.5 KiB
JavaScript
Vendored

module.exports = {
friendlyName: 'Get a device certificate via EST protocol',
description: 'Take a certificate signing request and authentication token, then use the EST protocol to request a certificate to be issued.',
extendedDescription: 'This action is the result of a customer hackathon working on issuing device certificates to Linux devices.',
moreInfoUrl: 'https://github.com/fleetdm/confidential/issues/8785',
inputs: {
csrData: {
required: true,
type: 'string',
description: 'Certificate Signing Request (CSR) data'
},
authToken: {
required: true,
type: 'string',
description: 'Authorization token provided by IdP',
}
},
exits: {
success: {
description: 'Successfully generated certificate',
extendedDescription: 'This action is the result of a hackathon where we were relying on this datashape.',
outputType: {certificate:'string'},
outputFriendlyName: 'Certificate',
},
invalidToken: {
description: 'The IdP auth token is invalid',
statusCode: 403,
},
invalidCsr: {
description: 'The provided CSR data was invalid.',
responseType: 'badRequest'
}
},
fn: async function ({ csrData, authToken }) {
const INTROSPECT_ENDPOINT = sails.config.custom.certIssueIdpIntrospectEndpoint;
if (!INTROSPECT_ENDPOINT) {
throw new Error('sails.config.custom.certIssueIdpIntrospectEndpoint is required');
}
const IDP_CLIENT_ID = sails.config.custom.certIssueIdpClientId;
if (!IDP_CLIENT_ID) {
throw new Error('sails.config.custom.certIssueIdpClientId is required');
}
const EST_ENDPOINT = sails.config.custom.certIssueEstEndpoint;
if (!EST_ENDPOINT) {
throw new Error('sails.config.custom.certIssueEstEndpoint is required');
}
const EST_CLIENT_ID = sails.config.custom.certIssueEstClientId;
if (!EST_CLIENT_ID) {
throw new Error('sails.config.custom.certIssueEstClientId is required');
}
const EST_CLIENT_KEY = sails.config.custom.certIssueEstClientKey;
if (!EST_CLIENT_KEY) {
throw new Error('sails.config.custom.certIssueEstClientKey is required');
}
// Ask the IdP to introspect the auth token (ensure it's valid and extract the values).
const introspectResponse = await sails.helpers.http.sendHttpRequest.with({
url: INTROSPECT_ENDPOINT,
method: 'POST',
enctype: 'application/x-www-form-urlencoded',
body: {
'client_id': IDP_CLIENT_ID,
'token': authToken,
},
});
if (!introspectResponse.body) {
throw 'invalidToken';
}
const introspectBody = JSON.parse(introspectResponse.body);
if (!introspectBody.active) {
throw 'invalidToken';
}
const introspectUsername = introspectBody.username;
// Extract the email and username from the CSR. Ensure they match.
let jsrsasign = require('jsrsasign');
const csrUtil = jsrsasign.asn1.csr.CSRUtil;
const csrObj = csrUtil.getParam(csrData);
let csrEmail = '';
let csrUsername = '';
for (const extension of csrObj.extreq) {
if (extension.extname === 'subjectAltName') {
for (const extentry of extension.array) {
if ('rfc822' in extentry) {
csrEmail = extentry.rfc822;
}
if ('other' in extentry) {
if ('oid' in extentry.other && extentry.other.oid === '1.3.6.1.4.1.311.20.2.3') {
csrUsername = extentry.other.value.utf8str.str;
}
}
}
}
}
if (csrEmail === '') {
throw 'invalidCsr';
}
if (!csrEmail.startsWith(csrUsername)) {
throw 'invalidCsr';
}
// Ensure username from IdP auth matches username in CSR. If they don't match, perhaps the user
// is trying to get a certificate with another user's name?
if (csrEmail !== introspectUsername) {
throw 'invalidToken';
}
// Ask the PKI provider for a certificate
const request = require('@sailshq/request');
const estResponse = await new Promise((resolve, reject) => {
request({
url: EST_ENDPOINT,
method: 'POST',
body: csrData.replace(/(-----(BEGIN|END) CERTIFICATE REQUEST-----|\n)/g, ''),
headers: {
'Content-Type': 'application/pkcs10',
'Authorization': `Basic ${Buffer.from(`${EST_CLIENT_ID}:${EST_CLIENT_KEY}`).toString('base64')}`,
},
}, (err, response)=>{
if (err) {
reject(err);
} else {
resolve(response);
}
});
});
return estResponse.body;
}
};