| /*****************************************************************************\ |
| * pem_key.c - Build a PEM-formatted RSA public key from mod and exp |
| ***************************************************************************** |
| * Copyright (C) SchedMD LLC. |
| * |
| * This file is part of Slurm, a resource management program. |
| * For details, see <https://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * Slurm is free software; you can redistribute it and/or modify it under |
| * the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * Slurm is distributed in the hope that it will be useful, but WITHOUT ANY |
| * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with Slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #include "src/common/slurm_protocol_api.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| |
| /* |
| * Use the libjwt base64 functions despite them not being prototyped in <jwt.h>. |
| * Otherwise we'd probably end up copying the exact same implementation |
| * (the implementation they use is originally from Apache and is BSD licensed) |
| * into Slurm rather than pick up another external dependency. |
| */ |
| extern int jwt_Base64decode(unsigned char *bufplain, const char *bufcoded); |
| extern int jwt_Base64encode(char *encoded, const char *string, int len); |
| |
| /* |
| * If the first hex character is '8', 'a', 'b', 'c', 'd', or 'e', |
| * prepend with an extra zero byte ("00" in hex). |
| */ |
| static void _handle_prepend(char **string) |
| { |
| char *p = NULL; |
| |
| if (**string > '7') { |
| xstrfmtcat(p, "00%s", *string); |
| xfree(*string); |
| *string = p; |
| } |
| } |
| |
| /* |
| * Convert base64url encoded value to DER formatted hex. |
| * Returns an xmalloc()'d string. |
| */ |
| static char *_to_hex(const char *base64url) |
| { |
| char *base64, *hex; |
| unsigned char *bin; |
| int binlen; |
| |
| base64 = xbase64_from_base64url(base64url); |
| |
| /* |
| * The binary format is ~33% smaller, |
| * but just make the buffer equal length. |
| */ |
| bin = xmalloc(strlen(base64)); |
| binlen = jwt_Base64decode(bin, base64); |
| |
| hex = xstring_bytes2hex(bin, binlen, NULL); |
| |
| /* Deal with DER formatting requirements */ |
| _handle_prepend(&hex); |
| |
| xfree(base64); |
| xfree(bin); |
| |
| return hex; |
| } |
| |
| /* |
| * Convert a number into the hex representation. |
| * Always return an even number of characters. |
| */ |
| static char *_hex(int len) |
| { |
| char *hex = NULL, *padded = NULL; |
| |
| xstrfmtcat(hex, "%x", len); |
| if (!(strlen(hex) % 2)) |
| return hex; |
| |
| /* |
| * Value must have an even number of characters, so prepend '0'. |
| */ |
| xstrfmtcat(padded, "0%s", hex); |
| xfree(hex); |
| return padded; |
| } |
| |
| /* |
| * Convert an integer into DER format. |
| * 0x00 - 0x7f are direct values - the "short format". |
| * Otherwise the "long format" is used. The first byte is the number of bytes |
| * needed to represent the value OR'd with 0x80, followed by the value itself. |
| */ |
| static char *_int_to_der_hex(int len) |
| { |
| char *encoded, *h = _hex(len); |
| |
| if (len <= 127) |
| return h; |
| |
| encoded = _hex(128 + strlen(h) / 2); |
| xstrcat(encoded, h); |
| xfree(h); |
| |
| return encoded; |
| } |
| |
| static int _to_bin(char **bin, char *hex) |
| { |
| int len = strlen(hex) / 2; |
| char *tmp = xmalloc(len); |
| |
| for (int i = 0; i < strlen(hex) - 1; i += 2) { |
| tmp[i / 2] = slurm_char_to_hex(hex[i]) * 16; |
| tmp[i / 2] += slurm_char_to_hex(hex[i + 1]); |
| } |
| |
| *bin = tmp; |
| |
| return len; |
| } |
| |
| /* |
| * Generate a PEM-formatted public key file from a given modulus and exponent. |
| * |
| * This code is manually smashing together DER from hex characters, |
| * and then constructing the certificate by translating that first |
| * into binary and then into base64. |
| * |
| * It would be nice if a (non-OpenSSL) library were available to deal with |
| * this, or if the x5c field were consistently populated in JWKS files, but |
| * otherwise we're stuck with this. |
| * |
| * Inspired by, and key magic strings sourced from: |
| * https://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js |
| * |
| * mod and exp need to be input in base64url format |
| */ |
| extern char *pem_from_mod_exp(const char *mod, const char *exp) |
| { |
| int modbytes, expbytes, binkeylen; |
| char *modhex, *exphex; |
| char *modhexlender, *exphexlender, *totallender; |
| char *layer1 = NULL, *layer2 = NULL, *layer3 = NULL; |
| char *layer1lender, *layer2lender; |
| char *binkey, *base64key, *pem = NULL; |
| |
| if (!mod || !exp) |
| fatal("%s: invalid JWKS file, missing mod and/or exp values", |
| __func__); |
| |
| modhex = _to_hex(mod); |
| exphex = _to_hex(exp); |
| |
| modbytes = strlen(modhex) / 2; |
| expbytes = strlen(exphex) / 2; |
| |
| modhexlender = _int_to_der_hex(modbytes); |
| exphexlender = _int_to_der_hex(expbytes); |
| totallender = _int_to_der_hex(2 + modbytes + expbytes |
| + strlen(modhexlender) / 2 |
| + strlen(exphexlender) / 2); |
| |
| /* |
| * Construct DER formatted key in hex. |
| */ |
| |
| /* Innermost */ |
| xstrcat(layer1, "0030"); |
| xstrcat(layer1, totallender); |
| xstrcat(layer1, "02"); |
| xstrcat(layer1, modhexlender); |
| xstrcat(layer1, modhex); |
| xstrcat(layer1, "02"); |
| xstrcat(layer1, exphexlender); |
| xstrcat(layer1, exphex); |
| |
| /* Wrap it again */ |
| layer1lender = _int_to_der_hex(strlen(layer1) / 2); |
| xstrcat(layer2, "300d06092a864886f70d010101050003"); |
| xstrcat(layer2, layer1lender); |
| xstrcat(layer2, layer1); |
| |
| /* And once more for good measure */ |
| layer2lender = _int_to_der_hex(strlen(layer2) / 2); |
| xstrcat(layer3, "30"); |
| xstrcat(layer3, layer2lender); |
| xstrcat(layer3, layer2); |
| |
| /* Convert the hex to binary */ |
| binkeylen = _to_bin(&binkey, layer3); |
| |
| /* And the binary into hex */ |
| base64key = xcalloc(2, binkeylen); |
| jwt_Base64encode(base64key, binkey, binkeylen); |
| |
| xstrcat(pem, "-----BEGIN PUBLIC KEY-----\n"); |
| xstrcat(pem, base64key); |
| xstrcat(pem, "\n-----END PUBLIC KEY-----\n"); |
| |
| xfree(modhex); |
| xfree(exphex); |
| xfree(modhexlender); |
| xfree(exphexlender); |
| xfree(totallender); |
| xfree(layer1); |
| xfree(layer2); |
| xfree(layer3); |
| xfree(layer1lender); |
| xfree(layer2lender); |
| xfree(binkey); |
| xfree(base64key); |
| |
| return pem; |
| } |