mirror of
https://github.com/open5gs/open5gs.git
synced 2026-07-10 00:18:59 +00:00
527 lines
15 KiB
C
527 lines
15 KiB
C
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
|
* (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2000-2012 Free Software Foundation, Inc.
|
|
* Copyright (C) 2017 Red Hat, Inc.
|
|
*
|
|
* Author: Nikos Mavrogiannopoulos
|
|
*
|
|
* This file is part of GnuTLS.
|
|
*
|
|
* The GnuTLS is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* as published by the Free Software Foundation; either version 2.1 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2019-2026 by Sukchan Lee <acetcom@gmail.com>
|
|
*
|
|
* This file is part of Open5GS.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "ogs-crypt.h"
|
|
|
|
/* aaaack but it's fast and const should make it shared text page. */
|
|
static const unsigned char pr2six[256] =
|
|
{
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
|
|
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
|
|
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
|
|
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
|
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
|
|
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
|
|
};
|
|
|
|
static bool ogs_base64_scan(
|
|
const char *in, size_t *out_nprbytes, size_t *out_decoded_size)
|
|
{
|
|
const unsigned char *p = NULL;
|
|
size_t nprbytes = 0, padding = 0;
|
|
size_t decoded_size = 0;
|
|
|
|
if (!in)
|
|
return false;
|
|
|
|
p = (const unsigned char *)in;
|
|
while (*p) {
|
|
if (pr2six[*p] <= 63) {
|
|
if (padding)
|
|
return false;
|
|
nprbytes++;
|
|
} else if (*p == '=') {
|
|
padding++;
|
|
if (padding > 2)
|
|
return false;
|
|
} else {
|
|
return false;
|
|
}
|
|
p++;
|
|
}
|
|
|
|
if (!nprbytes)
|
|
return false;
|
|
|
|
/* A single remaining base64 character cannot produce valid output. */
|
|
if (nprbytes % 4 == 1)
|
|
return false;
|
|
|
|
/* If padding exists, the encoded length including padding must be aligned. */
|
|
if (padding && ((nprbytes + padding) % 4))
|
|
return false;
|
|
|
|
decoded_size = ((nprbytes + 3) / 4) * 3;
|
|
decoded_size -= (4 - nprbytes) & 3;
|
|
|
|
if (!decoded_size)
|
|
return false;
|
|
|
|
if (out_nprbytes)
|
|
*out_nprbytes = nprbytes;
|
|
if (out_decoded_size)
|
|
*out_decoded_size = decoded_size;
|
|
|
|
return true;
|
|
}
|
|
|
|
int ogs_base64_decoded_size(const char *in)
|
|
{
|
|
size_t decoded_size = 0;
|
|
|
|
if (!ogs_base64_scan(in, NULL, &decoded_size))
|
|
return OGS_ERROR;
|
|
|
|
return (int)decoded_size;
|
|
}
|
|
|
|
int ogs_base64_decode_to_buffer(
|
|
uint8_t *out, size_t out_size, const char *in)
|
|
{
|
|
const unsigned char *bufin = NULL;
|
|
unsigned char *bufout = NULL;
|
|
size_t nprbytes = 0, decoded_size = 0;
|
|
|
|
if (!out || !out_size)
|
|
return OGS_ERROR;
|
|
|
|
if (!ogs_base64_scan(in, &nprbytes, &decoded_size))
|
|
return OGS_ERROR;
|
|
|
|
if (out_size < decoded_size)
|
|
return OGS_ERROR;
|
|
|
|
bufout = out;
|
|
bufin = (const unsigned char *)in;
|
|
|
|
while (nprbytes > 4) {
|
|
*(bufout++) =
|
|
(unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
|
|
*(bufout++) =
|
|
(unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
|
|
*(bufout++) =
|
|
(unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
|
|
bufin += 4;
|
|
nprbytes -= 4;
|
|
}
|
|
|
|
if (nprbytes > 1) {
|
|
*(bufout++) =
|
|
(unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
|
|
}
|
|
if (nprbytes > 2) {
|
|
*(bufout++) =
|
|
(unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
|
|
}
|
|
if (nprbytes > 3) {
|
|
*(bufout++) =
|
|
(unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
|
|
}
|
|
|
|
return (int)decoded_size;
|
|
}
|
|
|
|
int ogs_base64_decode_to_string(char *out, size_t out_size, const char *in)
|
|
{
|
|
int len;
|
|
|
|
if (!out || out_size <= 1)
|
|
return OGS_ERROR;
|
|
|
|
len = ogs_base64_decode_to_buffer((uint8_t *)out, out_size - 1, in);
|
|
if (len <= 0)
|
|
return OGS_ERROR;
|
|
|
|
out[len] = '\0';
|
|
return len;
|
|
}
|
|
|
|
static const char basis_64[] =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
int ogs_base64_encoded_size(size_t in_size)
|
|
{
|
|
size_t encoded_size;
|
|
|
|
if (in_size > (SIZE_MAX - 1) / 4 * 3)
|
|
return OGS_ERROR;
|
|
|
|
encoded_size = ((in_size + 2) / 3 * 4) + 1;
|
|
return (int)encoded_size;
|
|
}
|
|
|
|
int ogs_base64_encode_from_buffer(
|
|
char *out, size_t out_size, const uint8_t *in, size_t in_size)
|
|
{
|
|
size_t i;
|
|
int needed;
|
|
char *p = NULL;
|
|
|
|
if (!out || !out_size || (!in && in_size))
|
|
return OGS_ERROR;
|
|
|
|
needed = ogs_base64_encoded_size(in_size);
|
|
if (needed <= 0 || out_size < (size_t)needed)
|
|
return OGS_ERROR;
|
|
|
|
p = out;
|
|
for (i = 0; i + 2 < in_size; i += 3) {
|
|
*p++ = basis_64[(in[i] >> 2) & 0x3F];
|
|
*p++ = basis_64[((in[i] & 0x3) << 4) |
|
|
((int) (in[i + 1] & 0xF0) >> 4)];
|
|
*p++ = basis_64[((in[i + 1] & 0xF) << 2) |
|
|
((int) (in[i + 2] & 0xC0) >> 6)];
|
|
*p++ = basis_64[in[i + 2] & 0x3F];
|
|
}
|
|
|
|
if (i < in_size) {
|
|
*p++ = basis_64[(in[i] >> 2) & 0x3F];
|
|
if (i == (in_size - 1)) {
|
|
*p++ = basis_64[((in[i] & 0x3) << 4)];
|
|
*p++ = '=';
|
|
} else {
|
|
*p++ = basis_64[((in[i] & 0x3) << 4) |
|
|
((int) (in[i + 1] & 0xF0) >> 4)];
|
|
*p++ = basis_64[((in[i + 1] & 0xF) << 2)];
|
|
}
|
|
*p++ = '=';
|
|
}
|
|
|
|
*p++ = '\0';
|
|
return (int)(p - out);
|
|
}
|
|
|
|
int ogs_base64_encode_from_string(
|
|
char *out, size_t out_size, const char *in)
|
|
{
|
|
if (!in)
|
|
return OGS_ERROR;
|
|
|
|
return ogs_base64_encode_from_buffer(
|
|
out, out_size, (const uint8_t *)in, strlen(in));
|
|
}
|
|
|
|
/* copies data to result but removes newlines and <CR>
|
|
* returns the size of the data copied.
|
|
*
|
|
* It will fail with GNUTLS_E_BASE64_DECODING_ERROR if the
|
|
* end-result is the empty string.
|
|
*/
|
|
inline static int
|
|
cpydata(const uint8_t *data, int data_size, uint8_t **result, int *result_size)
|
|
{
|
|
int i, j;
|
|
|
|
*result = ogs_malloc(data_size + 1);
|
|
if (*result == NULL) {
|
|
ogs_error("ogs_malloc failed[%d]", data_size+1);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
for (j = i = 0; i < data_size; i++) {
|
|
if (data[i] == '\n' || data[i] == '\r' || data[i] == ' '
|
|
|| data[i] == '\t')
|
|
continue;
|
|
else if (data[i] == '-')
|
|
break;
|
|
(*result)[j] = data[i];
|
|
j++;
|
|
}
|
|
|
|
*result_size = j;
|
|
(*result)[j] = 0;
|
|
|
|
if (j==0) {
|
|
ogs_free(*result);
|
|
ogs_error("No Data");
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
return j;
|
|
}
|
|
|
|
#define MEMSUB(x,y) ((ssize_t)((ptrdiff_t)x-(ptrdiff_t)y))
|
|
#define ENDSTR "-----"
|
|
int ogs_fbase64_decode(const char *header,
|
|
const uint8_t *data, size_t data_size, ogs_datum_t *result)
|
|
{
|
|
static const char top[] = "-----BEGIN ";
|
|
static const char bottom[] = "-----END ";
|
|
uint8_t *rdata, *kdata;
|
|
int rdata_size, decoded_size;
|
|
#define MAX_PEM_HEADER_LEN 128
|
|
char pem_header[MAX_PEM_HEADER_LEN];
|
|
|
|
uint8_t *bufcoded;
|
|
int bufcoded_len;
|
|
char *p, *last;
|
|
|
|
ogs_assert(header);
|
|
ogs_assert(data);
|
|
ogs_assert(data_size);
|
|
ogs_assert(result);
|
|
|
|
memset(result, 0, sizeof(*result));
|
|
|
|
p = pem_header;
|
|
last = pem_header + MAX_PEM_HEADER_LEN;
|
|
|
|
p = ogs_slprintf(p, last, "%s", top);
|
|
if (header != NULL)
|
|
p = ogs_slprintf(p, last, "%s", header);
|
|
|
|
rdata = memmem(data, data_size, pem_header, strlen(pem_header));
|
|
if (rdata == NULL) {
|
|
ogs_error("Cound not find [%s]", pem_header);
|
|
ogs_log_hexdump(OGS_LOG_ERROR, data, data_size);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
data_size -= MEMSUB(rdata, data);
|
|
|
|
if (data_size < 4 + strlen(bottom)) {
|
|
ogs_error("Not enough data (%d < 4 + %d)",
|
|
(int)data_size, (int)strlen(bottom));
|
|
ogs_log_hexdump(OGS_LOG_ERROR, data, data_size);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
kdata =
|
|
memmem(rdata + 1, data_size - 1, ENDSTR, sizeof(ENDSTR) - 1);
|
|
/* allow CR as well.
|
|
*/
|
|
if (kdata == NULL) {
|
|
ogs_error("Cound not find [%s]", ENDSTR);
|
|
ogs_log_hexdump(OGS_LOG_ERROR, data, data_size);
|
|
return OGS_ERROR;
|
|
}
|
|
data_size -= strlen(ENDSTR);
|
|
data_size -= MEMSUB(kdata, rdata);
|
|
|
|
rdata = kdata + strlen(ENDSTR);
|
|
|
|
/* position is now after the ---BEGIN--- headers */
|
|
|
|
kdata = memmem(rdata, data_size, bottom, strlen(bottom));
|
|
if (kdata == NULL) {
|
|
ogs_error("Cound not find [%s]", bottom);
|
|
ogs_log_hexdump(OGS_LOG_ERROR, data, data_size);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
/* position of kdata is before the ----END--- footer
|
|
*/
|
|
rdata_size = MEMSUB(kdata, rdata);
|
|
|
|
if (rdata_size < 4) {
|
|
ogs_error("Not enough data [%d]", rdata_size);
|
|
ogs_log_hexdump(OGS_LOG_ERROR, data, data_size);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
if (cpydata(rdata, rdata_size, &bufcoded, &bufcoded_len) == OGS_ERROR) {
|
|
ogs_error("cpydata() failed");
|
|
ogs_log_hexdump(OGS_LOG_ERROR, rdata, rdata_size);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
decoded_size = ogs_base64_decoded_size((const char *)bufcoded);
|
|
if (decoded_size <= 0) {
|
|
ogs_error("ogs_base64_decoded_size() failed");
|
|
ogs_log_hexdump(OGS_LOG_ERROR, bufcoded, bufcoded_len);
|
|
ogs_free(bufcoded);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
result->data = ogs_calloc(1, decoded_size);
|
|
if (result->data == NULL) {
|
|
ogs_error("ogs_calloc() failed [%d]", decoded_size);
|
|
ogs_free(bufcoded);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
result->size = ogs_base64_decode_to_buffer(
|
|
result->data, decoded_size, (const char *)bufcoded);
|
|
if (result->size <= 0) {
|
|
ogs_error("ogs_base64_decode_to_buffer() failed");
|
|
ogs_log_hexdump(OGS_LOG_ERROR, bufcoded, bufcoded_len);
|
|
ogs_free(result->data);
|
|
result->data = NULL;
|
|
ogs_free(bufcoded);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
ogs_free(bufcoded);
|
|
|
|
return OGS_OK;
|
|
}
|
|
|
|
int ogs_pem_decode_curve25519_key(const char *filename, uint8_t *key)
|
|
{
|
|
int rv;
|
|
|
|
uint8_t buf[OGS_HUGE_LEN];
|
|
size_t bytes_read;
|
|
ogs_datum_t result;
|
|
|
|
#define CURVE25519_HEADER_LEN 16
|
|
const char *_header = "302e0201 00300506 032b656e 04220420";
|
|
uint8_t header[CURVE25519_HEADER_LEN];
|
|
|
|
ogs_assert(filename);
|
|
ogs_assert(key);
|
|
|
|
rv = ogs_file_read_full(filename, buf, OGS_HUGE_LEN, &bytes_read);
|
|
if (rv != OGS_OK) {
|
|
ogs_error("ogs_file_read_full[%s] failed", filename);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
rv = ogs_fbase64_decode("PRIVATE KEY", buf, bytes_read, &result);
|
|
if (rv != OGS_OK) {
|
|
ogs_error("ogs_fbase64_decode[%s] failed", filename);
|
|
ogs_log_hexdump(OGS_LOG_ERROR, result.data, result.size);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
rv = OGS_OK;
|
|
|
|
if (result.size != 48) {
|
|
ogs_error("Invalid size [%d]", (int)result.size);
|
|
rv = OGS_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (memcmp(ogs_hex_from_string(_header, header, sizeof(header)),
|
|
result.data, CURVE25519_HEADER_LEN) != 0) {
|
|
ogs_error("Invalid header [%d]", (int)result.size);
|
|
ogs_log_hexdump(OGS_LOG_FATAL, result.data, result.size);
|
|
rv = OGS_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
memcpy(key, result.data+CURVE25519_HEADER_LEN, OGS_ECCKEY_LEN);
|
|
|
|
cleanup:
|
|
if (result.data)
|
|
ogs_free(result.data);
|
|
|
|
return rv;
|
|
}
|
|
|
|
int ogs_pem_decode_secp256r1_key(const char *filename, uint8_t *key)
|
|
{
|
|
int rv;
|
|
|
|
uint8_t buf[OGS_HUGE_LEN];
|
|
size_t bytes_read;
|
|
ogs_datum_t result;
|
|
|
|
#define SECP256R1_HEADER_LEN 7
|
|
const char *_header = "30770201 010420";
|
|
uint8_t header[SECP256R1_HEADER_LEN];
|
|
|
|
ogs_assert(filename);
|
|
ogs_assert(key);
|
|
|
|
rv = ogs_file_read_full(filename, buf, OGS_HUGE_LEN, &bytes_read);
|
|
if (rv != OGS_OK) {
|
|
ogs_error("ogs_file_read_full[%s] failed", filename);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
rv = ogs_fbase64_decode("EC PRIVATE KEY", buf, bytes_read, &result);
|
|
if (rv != OGS_OK) {
|
|
ogs_error("ogs_fbase64_decode[%s] failed", filename);
|
|
ogs_log_hexdump(OGS_LOG_ERROR, result.data, result.size);
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
rv = OGS_OK;
|
|
|
|
if (result.size != 121) {
|
|
ogs_error("Invalid size [%d]", (int)result.size);
|
|
rv = OGS_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (memcmp(ogs_hex_from_string(_header, header, sizeof(header)),
|
|
result.data, SECP256R1_HEADER_LEN) != 0) {
|
|
ogs_error("Invalid header [%d]", (int)result.size);
|
|
ogs_log_hexdump(OGS_LOG_FATAL, result.data, result.size);
|
|
rv = OGS_ERROR;
|
|
goto cleanup;
|
|
}
|
|
|
|
memcpy(key, result.data+SECP256R1_HEADER_LEN, OGS_ECCKEY_LEN);
|
|
|
|
cleanup:
|
|
if (result.data)
|
|
ogs_free(result.data);
|
|
|
|
return rv;
|
|
}
|