Skip to content

Commit

Permalink
Update auth header token for CA (stitch)
Browse files Browse the repository at this point in the history
When interacting with Fabric CA REST services,
utilize the updated auth header token format
that includes the URL path and method.

The old format has been deprecated for a long time (since Fabric CA v1.4.0)
since the new format is more secure.
New versions of Fabric CA (v1.5.14 and later) will not support
the old format by default
(although they can still support the old format by setting
FABRIC_CA_SERVER_COMPATIBILITY_MODE_V1_3=true).

Signed-off-by: David Enyeart <[email protected]>
  • Loading branch information
denyeart committed Feb 10, 2025
1 parent b624bfa commit a403629
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 3 deletions.
36 changes: 34 additions & 2 deletions packages/stitch/src/libs/ca_lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export { getCaIdentities, scGenCSR, getCaAffiliations, registerCaIdentity, enrol
function getCaIdentities(opts: CaInput, cb: Function) {
const options = JSON.parse(JSON.stringify(opts));
options.body_obj = null; // set null for token gen
// set path and method for token gen
options.path = build_ca_path(options, '/api/v1/identities');
options.method = 'GET';

let called_cb = false;
generateCaAuthToken(options, (_: any, token: string) => {
Expand Down Expand Up @@ -80,6 +83,9 @@ function getCaIdentities(opts: CaInput, cb: Function) {
function getCaAffiliations(opts: CaInput, cb: Function) {
const options = JSON.parse(JSON.stringify(opts));
options.body_obj = null; // set null for token gen
// set path and method for token gen
options.path = build_ca_path(options, '/api/v1/affiliations');
options.method = 'GET';

let called_cb = false;
generateCaAuthToken(options, (_: any, token: string) => {
Expand Down Expand Up @@ -160,6 +166,9 @@ function registerCaIdentity(opts: CaReg, cb: Function) {
type: options.new_identity.type,
max_enrollments: Number(options.new_identity.max_enrollments)
};
// set path and method for token gen
options.path = build_ca_path(options, '/api/v1/identities');
options.method = 'POST';

let called_cb = false;
generateCaAuthToken(options, (_: any, token: string) => {
Expand Down Expand Up @@ -283,6 +292,9 @@ function reenrollCaIdentity(opts: CaInput, cb: Function) {
caName: options.ca_name,
certificate_request: csrPEM,
};
// set path and method for token gen
options.path = build_ca_path(options, '/api/v1/reenroll');
options.method = 'POST';

let called_cb = false;
generateCaAuthToken(options, (_: any, token: string) => {
Expand Down Expand Up @@ -338,15 +350,18 @@ function get_CN_from_str(str: any) {
*/
function deleteCaIdentity(opts: CaInput, cb: Function) {
const options = JSON.parse(JSON.stringify(opts));
const parsed = parseCertificate(opts.client_cert_b64pem);
const enroll_id = opts.enroll_id || get_CN_from_str(parsed.subject_parts.CN);
options.body_obj = null; // set null for token gen
// set path and method for token gen
options.path = build_ca_path(options, '/api/v1/identities/' + enroll_id);
options.method = 'DELETE';

const parsed = parseCertificate(opts.client_cert_b64pem);
if (!parsed || !parsed.subject_parts || !parsed.subject_parts.CN) {
return cb(fmt_ca_err({ funk: 'deleteCaIdentity' }, null, 'unable to delete id b/c cannot find enroll id in cert'), null);
} else {
let called_cb = false;
generateCaAuthToken(options, (_: any, token: string) => {
const enroll_id = opts.enroll_id || get_CN_from_str(parsed.subject_parts.CN);
const fetch_options = {
host: build_ca_url(options, '/api/v1/identities/' + enroll_id),
authorization: token,
Expand Down Expand Up @@ -388,6 +403,21 @@ function build_ca_url(opts: { ca_name: string, host: string }, path: string) {
return opts.host;
}

// ------------------------------------------------------------------------------------------------------
// construct the path to use for the ca, same logic as build_ca_url (above) but without the starting host
// ------------------------------------------------------------------------------------------------------
function build_ca_path(opts: { ca_name: string, host: string }, path: string) {
if (opts.ca_name) { // finding the ca name in input obj
return path + '?ca=' + opts.ca_name;
} else {
const parts = opts.host.split('?'); // finding the ca name in url as query param
if (parts && parts.length >= 2) {
return path + '?' + parts[1]; // parts[1] holds the ca name
}
}
return path;
}

// ----------------------------------------------------------------
// Get some JSON data via Fetch - expects json response
// ----------------------------------------------------------------
Expand Down Expand Up @@ -505,6 +535,8 @@ interface CaInput {
ext: Ext | null;
enroll_id: string | null;
timeout_ms: number | null;
path: string;
method: string;
}

interface CaReg extends CaInput {
Expand Down
7 changes: 6 additions & 1 deletion packages/stitch/src/libs/crypto_misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ function fmtKey(key: object | string, type: string) {
//------------------------------------------------------------------
// Generate authorization token required for accessing fabric-ca APIs
//------------------------------------------------------------------
function generateCaAuthToken(opts: { client_prv_key_b64pem: string, client_cert_b64pem: string, body_obj: any }, cb: Function) {
function generateCaAuthToken(opts: { client_prv_key_b64pem: string, client_cert_b64pem: string, body_obj: any, path: string, method: string }, cb: Function) {
let prvKeyPem = decode_b64_pem(opts.client_prv_key_b64pem);
let client_cert_b64pem = btoa(decode_b64_pem(opts.client_cert_b64pem)); // decode and encode to catch non encoded certs
let payload = null;
Expand All @@ -470,6 +470,11 @@ function generateCaAuthToken(opts: { client_prv_key_b64pem: string, client_cert_
payload = '.' + client_cert_b64pem;
}

if (opts.path && opts.method) {
const s = btoa(opts.path);
payload = opts.method + '.' + s + '.' + payload;
}

scSign({ prvKeyPEM: prvKeyPem, b_msg: utf8StrToUint8Array(payload) }, (_: any, b_signature: any) => { // subtle crypto method does not need the has function
let b64Sign = uint8ArrayToBase64(b_signature);
return cb(null, client_cert_b64pem + '.' + b64Sign);
Expand Down

0 comments on commit a403629

Please sign in to comment.