277 lines
7.8 KiB
C
277 lines
7.8 KiB
C
//
|
|
// Copyright © 2023 osy. All rights reserved.
|
|
//
|
|
// 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 "GenerateKey.h"
|
|
#include <stdio.h>
|
|
#include <openssl/bio.h>
|
|
#include <openssl/conf.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/objects.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/pkcs12.h>
|
|
#include <openssl/x509v3.h>
|
|
|
|
#define X509_ENTRY_MAX_LENGTH (1024)
|
|
|
|
/* Add extension using V3 code: we can set the config file as NULL
|
|
* because we wont reference any other sections.
|
|
*/
|
|
static int add_ext(X509 *cert, int nid, char *value) {
|
|
X509_EXTENSION *ex;
|
|
X509V3_CTX ctx;
|
|
/* This sets the 'context' of the extensions. */
|
|
/* No configuration database */
|
|
X509V3_set_ctx_nodb(&ctx);
|
|
/* Issuer and subject certs: both the target since it is self signed,
|
|
* no request and no CRL
|
|
*/
|
|
X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
|
|
ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
|
|
if (!ex) {
|
|
return 0;
|
|
}
|
|
|
|
X509_add_ext(cert, ex, -1);
|
|
X509_EXTENSION_free(ex);
|
|
return 1;
|
|
}
|
|
|
|
static int mkrsacert(X509 **x509p, EVP_PKEY **pkeyp, const char *commonName, const char *organizationName, long serial, int days, int isClient) {
|
|
X509 *x = NULL;
|
|
EVP_PKEY *pk = NULL;
|
|
BIGNUM *bne = NULL;
|
|
RSA *rsa = NULL;
|
|
X509_NAME *name = NULL;
|
|
|
|
if ((pk = EVP_PKEY_new()) == NULL) {
|
|
goto err;
|
|
}
|
|
|
|
if ((x = X509_new()) == NULL) {
|
|
goto err;
|
|
}
|
|
|
|
bne = BN_new();
|
|
if (!bne || !BN_set_word(bne, RSA_F4)){
|
|
goto err;
|
|
}
|
|
|
|
rsa = RSA_new();
|
|
if (!rsa || !RSA_generate_key_ex(rsa, 4096, bne, NULL)) {
|
|
goto err;
|
|
}
|
|
BN_free(bne);
|
|
bne = NULL;
|
|
if (!EVP_PKEY_assign_RSA(pk, rsa)) {
|
|
goto err;
|
|
}
|
|
rsa = NULL; // EVP_PKEY_assign_RSA takes ownership
|
|
|
|
X509_set_version(x, 2);
|
|
ASN1_INTEGER_set(X509_get_serialNumber(x), serial);
|
|
X509_gmtime_adj(X509_get_notBefore(x), 0);
|
|
X509_gmtime_adj(X509_get_notAfter(x), (long)60*60*24*days);
|
|
X509_set_pubkey(x, pk);
|
|
|
|
name = X509_get_subject_name(x);
|
|
|
|
/* This function creates and adds the entry, working out the
|
|
* correct string type and performing checks on its length.
|
|
* Normally we'd check the return value for errors...
|
|
*/
|
|
X509_NAME_add_entry_by_txt(name, SN_commonName,
|
|
MBSTRING_UTF8, (const unsigned char *)commonName, -1, -1, 0);
|
|
X509_NAME_add_entry_by_txt(name, SN_organizationName,
|
|
MBSTRING_UTF8, (const unsigned char *)organizationName, -1, -1, 0);
|
|
|
|
/* Its self signed so set the issuer name to be the same as the
|
|
* subject.
|
|
*/
|
|
X509_set_issuer_name(x, name);
|
|
|
|
/* Add various extensions: standard extensions */
|
|
add_ext(x, NID_basic_constraints, "critical,CA:TRUE");
|
|
add_ext(x, NID_key_usage, "critical,keyCertSign,cRLSign,keyEncipherment,digitalSignature");
|
|
if (isClient) {
|
|
add_ext(x, NID_ext_key_usage, "clientAuth");
|
|
} else {
|
|
add_ext(x, NID_ext_key_usage, "serverAuth");
|
|
}
|
|
add_ext(x, NID_subject_key_identifier, "hash");
|
|
|
|
if (!X509_sign(x, pk, EVP_sha256())) {
|
|
goto err;
|
|
}
|
|
|
|
*x509p = x;
|
|
*pkeyp = pk;
|
|
return 1;
|
|
err:
|
|
if (pk) {
|
|
EVP_PKEY_free(pk);
|
|
}
|
|
if (x) {
|
|
X509_free(x);
|
|
}
|
|
if (bne) {
|
|
BN_free(bne);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static _Nullable CFDataRef CreateP12FromKey(EVP_PKEY *pkey, X509 *cert) {
|
|
PKCS12 *p12;
|
|
BIO *mem;
|
|
char *ptr;
|
|
long length;
|
|
CFDataRef data;
|
|
|
|
p12 = PKCS12_create("password", NULL, pkey, cert, NULL, NID_pbe_WithSHA1And3_Key_TripleDES_CBC, NID_pbe_WithSHA1And40BitRC2_CBC, PKCS12_DEFAULT_ITER, 1, 0);
|
|
if (!p12) {
|
|
ERR_print_errors_fp(stderr);
|
|
return NULL;
|
|
}
|
|
mem = BIO_new(BIO_s_mem());
|
|
if (!mem || !i2d_PKCS12_bio(mem, p12)) {
|
|
ERR_print_errors_fp(stderr);
|
|
PKCS12_free(p12);
|
|
BIO_free(mem);
|
|
return NULL;
|
|
}
|
|
PKCS12_free(p12);
|
|
length = BIO_get_mem_data(mem, &ptr);
|
|
data = CFDataCreate(kCFAllocatorDefault, (void *)ptr, length);
|
|
BIO_free(mem);
|
|
return data;
|
|
}
|
|
|
|
static _Nullable CFDataRef CreatePrivatePEMFromKey(EVP_PKEY *pkey) {
|
|
BIO *mem;
|
|
char *ptr;
|
|
long length;
|
|
CFDataRef data;
|
|
|
|
mem = BIO_new(BIO_s_mem());
|
|
if (!mem || !PEM_write_bio_PrivateKey(mem, pkey, NULL, NULL, 0, NULL, NULL)) {
|
|
ERR_print_errors_fp(stderr);
|
|
BIO_free(mem);
|
|
return NULL;
|
|
}
|
|
length = BIO_get_mem_data(mem, &ptr);
|
|
data = CFDataCreate(kCFAllocatorDefault, (void *)ptr, length);
|
|
BIO_free(mem);
|
|
return data;
|
|
}
|
|
|
|
static _Nullable CFDataRef CreatePublicPEMFromCert(X509 *cert) {
|
|
BIO *mem;
|
|
char *ptr;
|
|
long length;
|
|
CFDataRef data;
|
|
|
|
mem = BIO_new(BIO_s_mem());
|
|
if (!mem || !PEM_write_bio_X509(mem, cert)) {
|
|
ERR_print_errors_fp(stderr);
|
|
BIO_free(mem);
|
|
return NULL;
|
|
}
|
|
length = BIO_get_mem_data(mem, &ptr);
|
|
data = CFDataCreate(kCFAllocatorDefault, (void *)ptr, length);
|
|
BIO_free(mem);
|
|
return data;
|
|
}
|
|
|
|
static _Nullable CFDataRef CreatePublicKeyFromCert(X509 *cert) {
|
|
EVP_PKEY* pubkey;
|
|
BIO *mem;
|
|
char *ptr;
|
|
long length;
|
|
CFDataRef data;
|
|
|
|
pubkey = X509_get_pubkey(cert);
|
|
if (!pubkey) {
|
|
ERR_print_errors_fp(stderr);
|
|
return NULL;
|
|
}
|
|
mem = BIO_new(BIO_s_mem());
|
|
if (!mem || !i2d_PUBKEY_bio(mem, pubkey)) {
|
|
ERR_print_errors_fp(stderr);
|
|
EVP_PKEY_free(pubkey);
|
|
BIO_free(mem);
|
|
return NULL;
|
|
}
|
|
length = BIO_get_mem_data(mem, &ptr);
|
|
data = CFDataCreate(kCFAllocatorDefault, (void *)ptr, length);
|
|
BIO_free(mem);
|
|
EVP_PKEY_free(pubkey);
|
|
return data;
|
|
}
|
|
|
|
_Nullable CFArrayRef GenerateRSACertificate(CFStringRef _Nonnull commonName, CFStringRef _Nonnull organizationName, CFNumberRef _Nullable serial, CFNumberRef _Nullable days, CFBooleanRef _Nonnull isClient) {
|
|
char _commonName[X509_ENTRY_MAX_LENGTH];
|
|
char _organizationName[X509_ENTRY_MAX_LENGTH];
|
|
long _serial = 0;
|
|
int _days = 365;
|
|
int _isClient = 0;
|
|
X509 *cert;
|
|
EVP_PKEY *pkey;
|
|
CFDataRef arr[4] = {NULL};
|
|
CFArrayRef cfarr = NULL;
|
|
|
|
if (!CFStringGetCString(commonName, _commonName, X509_ENTRY_MAX_LENGTH, kCFStringEncodingUTF8)) {
|
|
return NULL;
|
|
}
|
|
if (!CFStringGetCString(organizationName, _organizationName, X509_ENTRY_MAX_LENGTH, kCFStringEncodingUTF8)) {
|
|
return NULL;
|
|
}
|
|
if (serial) {
|
|
CFNumberGetValue(serial, kCFNumberLongType, &_serial);
|
|
}
|
|
if (days) {
|
|
CFNumberGetValue(days, kCFNumberIntType, &_days);
|
|
}
|
|
_isClient = CFBooleanGetValue(isClient);
|
|
|
|
OpenSSL_add_all_algorithms();
|
|
ERR_load_crypto_strings();
|
|
if (!mkrsacert(&cert, &pkey, _commonName, _organizationName, _serial, _days, _isClient)) {
|
|
ERR_print_errors_fp(stderr);
|
|
return NULL;
|
|
}
|
|
arr[0] = CreateP12FromKey(pkey, cert);
|
|
arr[1] = CreatePrivatePEMFromKey(pkey);
|
|
arr[2] = CreatePublicPEMFromCert(cert);
|
|
arr[3] = CreatePublicKeyFromCert(cert);
|
|
if (arr[0] && arr[1] && arr[2] && arr[3]) {
|
|
cfarr = CFArrayCreate(kCFAllocatorDefault, (const void **)arr, 4, &kCFTypeArrayCallBacks);
|
|
}
|
|
if (arr[0]) {
|
|
CFRelease(arr[0]);
|
|
}
|
|
if (arr[1]) {
|
|
CFRelease(arr[1]);
|
|
}
|
|
if (arr[2]) {
|
|
CFRelease(arr[2]);
|
|
}
|
|
if (arr[3]) {
|
|
CFRelease(arr[3]);
|
|
}
|
|
EVP_PKEY_free(pkey);
|
|
X509_free(cert);
|
|
return cfarr;
|
|
}
|