diff options
Diffstat (limited to 'libparc/parc/security/parc_X509Certificate.c')
-rw-r--r-- | libparc/parc/security/parc_X509Certificate.c | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/libparc/parc/security/parc_X509Certificate.c b/libparc/parc/security/parc_X509Certificate.c new file mode 100644 index 00000000..8cc4b0b7 --- /dev/null +++ b/libparc/parc/security/parc_X509Certificate.c @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * 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. + */ + + +/** + * parc_X509Certificate.c + * PARC Library + */ + +#include <config.h> + +#include <parc/security/parc_X509Certificate.h> +#include <parc/security/parc_Certificate.h> + +#include <parc/security/parc_CryptoHash.h> +#include <parc/security/parc_Security.h> +#include <parc/algol/parc_Buffer.h> +#include <parc/algol/parc_Object.h> +#include <parc/algol/parc_Memory.h> + +#include <openssl/bio.h> +#include <openssl/err.h> +#include <openssl/pem.h> +#include <openssl/x509v3.h> +#include <openssl/rand.h> +#include <openssl/pkcs12.h> + +static PARCCryptoHash *_getPublicKeyDigest(void *interfaceContext); +static PARCCryptoHash *_getCertificateDigest(void *interfaceContext); +static PARCBuffer *_getDEREncodedCertificate(void *interfaceContext); +static PARCBuffer *_getDEREncodedPublicKey(void *interfaceContext); +static PARCCertificateType _getCertificateType(const void *cert); +static PARCContainerEncoding _getContainerEncoding(const void *cert); + +PARCCertificateInterface *PARCX509CertificateInterface = &(PARCCertificateInterface) { + .GetPublicKeyDigest = _getPublicKeyDigest, + .GetCertificateDigest = _getCertificateDigest, + .GetDEREncodedCertificate = _getDEREncodedCertificate, + .GetDEREncodedPublicKey = _getDEREncodedPublicKey, + .GetCertificateType = _getCertificateType, + .GetContainerEncoding = _getContainerEncoding +}; + +struct parc_X509_certificate { + PARCCertificateType type; + PARCContainerEncoding encoding; + + // Cache of results + PARCCryptoHash *keyDigest; + PARCCryptoHash *certificateDigest; + PARCBuffer *derEncodedCertificate; + PARCBuffer *derEncodedKey; + + BIO *certificateBIO; + X509 *certificate; + EVP_PKEY *publicKey; +}; + +#ifdef __APPLE__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + +static PARCCryptoHash * +_getPublicKeyDigest(void *interfaceContext) +{ + parcSecurity_AssertIsInitialized(); + + assertNotNull(interfaceContext, "Parameter must be non-null PARCX509Certificate"); + + PARCX509Certificate *certificate = (PARCX509Certificate *) interfaceContext; + + if (certificate->keyDigest == NULL) { + PARCBuffer *derEncodedKey = _getDEREncodedPublicKey(certificate); + PARCCryptoHasher *hasher = parcCryptoHasher_Create(PARCCryptoHashType_SHA256); + parcCryptoHasher_Init(hasher); + parcCryptoHasher_UpdateBuffer(hasher, derEncodedKey); + PARCCryptoHash *hash = parcCryptoHasher_Finalize(hasher); + + certificate->keyDigest = parcCryptoHash_Create(PARCCryptoHashType_SHA256, parcCryptoHash_GetDigest(hash)); + parcCryptoHash_Release(&hash); + parcCryptoHasher_Release(&hasher); + } + + return certificate->keyDigest; +} + +static PARCCryptoHash * +_getCertificateDigest(void *interfaceContext) +{ + parcSecurity_AssertIsInitialized(); + + assertNotNull(interfaceContext, "Parameter must be non-null PARCX509Certificate"); + + PARCX509Certificate *certificate = (PARCX509Certificate *) interfaceContext; + + if (certificate->certificateDigest == NULL) { + uint8_t digestBuffer[SHA256_DIGEST_LENGTH]; + int result = X509_digest(certificate->certificate, EVP_sha256(), digestBuffer, NULL); + if (result) { + PARCBuffer *digest = + parcBuffer_Flip(parcBuffer_PutArray(parcBuffer_Allocate(SHA256_DIGEST_LENGTH), SHA256_DIGEST_LENGTH, digestBuffer)); + certificate->certificateDigest = parcCryptoHash_Create(PARCCryptoHashType_SHA256, digest); + parcBuffer_Release(&digest); + } + } + + return certificate->certificateDigest; +} + +static PARCBuffer * +_getDEREncodedCertificate(void *interfaceContext) +{ + parcSecurity_AssertIsInitialized(); + + assertNotNull(interfaceContext, "Parameter must be non-null PARCX509Certificate"); + + PARCX509Certificate *certificate = (PARCX509Certificate *) interfaceContext; + + if (certificate->derEncodedCertificate == NULL) { + uint8_t *der = NULL; + + // this allocates memory for der + int derLength = i2d_X509(certificate->certificate, &der); + if (derLength > 0) { + certificate->derEncodedCertificate = + parcBuffer_Flip(parcBuffer_PutArray(parcBuffer_Allocate(derLength), derLength, der)); + } + OPENSSL_free(der); + } + + return certificate->derEncodedCertificate; +} + +static PARCBuffer * +_getDEREncodedPublicKey(void *interfaceContext) +{ + parcSecurity_AssertIsInitialized(); + + assertNotNull(interfaceContext, "Parameter must be non-null PARCX509Certificate"); + + PARCX509Certificate *certificate = (PARCX509Certificate *) interfaceContext; + + if (certificate->derEncodedKey == NULL) { + uint8_t *der = NULL; + + // this allocates memory for der + int derLength = i2d_PUBKEY(certificate->publicKey, &der); + if (derLength > 0) { + certificate->derEncodedKey = parcBuffer_Flip(parcBuffer_PutArray(parcBuffer_Allocate(derLength), derLength, der)); + } + OPENSSL_free(der); + } + + return certificate->derEncodedKey; +} + +static PARCCertificateType +_getCertificateType(const void *instance) +{ + PARCX509Certificate *certificate = (PARCX509Certificate *) instance; + return certificate->type; +} + +static PARCContainerEncoding +_getContainerEncoding(const void *instance) +{ + PARCX509Certificate *certificate = (PARCX509Certificate *) instance; + return certificate->encoding; +} + +static void +_parcX509Certificate_FinalRelease(PARCX509Certificate **certP) +{ + PARCX509Certificate *cert = (PARCX509Certificate *) *certP; + if (cert->certificateBIO != NULL) { + BIO_free_all(cert->certificateBIO); + } + if (cert->publicKey != NULL) { + EVP_PKEY_free(cert->publicKey); + } + if (cert->certificate != NULL) { + X509_free(cert->certificate); + } + if (cert->keyDigest != NULL) { + parcCryptoHash_Release(&cert->keyDigest); + } + if (cert->certificateDigest != NULL) { + parcCryptoHash_Release(&cert->certificateDigest); + } + if (cert->derEncodedCertificate != NULL) { + parcBuffer_Release(&cert->derEncodedCertificate); + } + if (cert->derEncodedKey != NULL) { + parcBuffer_Release(&cert->derEncodedKey); + } +} + +parcObject_ExtendPARCObject(PARCX509Certificate, _parcX509Certificate_FinalRelease, NULL, NULL, NULL, NULL, NULL, NULL); + +static PARCX509Certificate * +_createEmptyCertificate() +{ + PARCX509Certificate *cert = parcObject_CreateInstance(PARCX509Certificate); + cert->certificateBIO = NULL; + cert->certificate = NULL; + cert->publicKey = NULL; + cert->keyDigest = NULL; + cert->certificateDigest = NULL; + cert->derEncodedCertificate = NULL; + cert->derEncodedKey = NULL; + assertNotNull(cert, "Failure allocating memory for a new PARCX509Certificate instance"); + + return cert; +} + +static bool +_addCertificateExtensionWithContext(X509 *cert, int nid, char *value) +{ + X509_EXTENSION *extension; + X509V3_CTX context; + + X509V3_set_ctx_nodb(&context); + X509V3_set_ctx(&context, cert, cert, NULL, NULL, 0); + extension = X509V3_EXT_conf_nid(NULL, &context, nid, value); + if (extension == NULL) { + return false; + } + X509_add_ext(cert, extension, -1); + X509_EXTENSION_free(extension); + return true; +} + +static bool +_addCertificateExtension(X509 *cert, int nid, char *value) +{ + X509_EXTENSION *extension = X509V3_EXT_conf_nid(NULL, NULL, nid, value); + if (extension == NULL) { + return false; + } + X509_add_ext(cert, extension, -1); + X509_EXTENSION_free(extension); + return true; +} + +static bool +_addKeyIdentifier(X509 *cert) +{ + unsigned char spkid[SHA256_DIGEST_LENGTH]; + char spkid_hex[1 + 2 * SHA256_DIGEST_LENGTH]; + + /* Generate a KeyID which is the SHA256 digest of the DER encoding + * of a SubjectPublicKeyInfo. Note that this is slightly uncommon, + * but it is more general and complete than digesting the BIT STRING + * component of the SubjectPublicKeyInfo itself (and no standard dictates + * how you must generate a key ID). This code must produce the same result + * as the Java version applied to the same SubjectPublicKeyInfo. + */ + + if (ASN1_item_digest(ASN1_ITEM_rptr(X509_PUBKEY), EVP_sha256(), X509_get_X509_PUBKEY(cert), spkid, NULL)) { + for (int i = 0; i < 32; i++) { + snprintf(&spkid_hex[2 * i], 3, "%02X", (unsigned) spkid[i]); + } + if (_addCertificateExtension(cert, NID_subject_key_identifier, spkid_hex) == true) { + if (_addCertificateExtensionWithContext(cert, NID_authority_key_identifier, "keyid:always") == true) { + return true; + } + } + } + return false; +} + +static bool +_addSubjectName(X509 *cert, const char *subjectname) +{ + // Set up the simple subject name and issuer name for the certificate. + X509_NAME *name = X509_get_subject_name(cert); + assertNotNull(name, "Got null name from X509_get_subject_name"); + + if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *) subjectname, -1, -1, 0)) { + if (X509_set_issuer_name(cert, name)) { + return true; + } + } + return false; +} + +static bool +_addRandomSerial(X509 *cert) +{ + unsigned long serial = 0; + unsigned char serial_bytes[sizeof(serial)]; + + // Construct random positive serial number. + RAND_bytes(serial_bytes, sizeof(serial_bytes)); + serial_bytes[0] &= 0x7F; + serial = 0; + for (int i = 0; i < sizeof(serial_bytes); i++) { + serial = (256 * serial) + serial_bytes[i]; + } + ASN1_INTEGER_set(X509_get_serialNumber(cert), serial); + return true; +} + +static bool +_addValidityPeriod(X509 *cert, size_t validityDays) +{ + // Set the validity from now for the specified number of days. + X509_gmtime_adj(X509_get_notBefore(cert), (long) 0); + X509_gmtime_adj(X509_get_notAfter(cert), (long) (60 * 60 * 24 * validityDays)); + return true; +} + +static bool +_addExtensions(X509 *cert) +{ + // Add the necessary extensions. + if (_addCertificateExtension(cert, NID_basic_constraints, "critical,CA:FALSE") == true) { + if (_addCertificateExtension(cert, NID_key_usage, "digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment,keyAgreement") == true) { + if (_addCertificateExtension(cert, NID_ext_key_usage, "clientAuth") == true) { + return true; + } + } + } + return false; +} + +static PARCX509Certificate * +_parcX509Certificate_CreateFromPEMFile(const char *filename) +{ + parcSecurity_AssertIsInitialized(); + + PARCX509Certificate *cert = _createEmptyCertificate(); + + FILE *fp = fopen(filename, "rb"); + if (fp == NULL) { + parcX509Certificate_Release(&cert); + return NULL; + } + + cert->certificateBIO = BIO_new(BIO_s_file()); + size_t result = BIO_read_filename(cert->certificateBIO, filename); + assertTrue(result == 1, "Unable to open the specified file"); + + cert->certificate = PEM_read_bio_X509(cert->certificateBIO, NULL, 0, NULL); + cert->publicKey = X509_get_pubkey(cert->certificate); + + return cert; +} + +parcObject_ImplementAcquire(parcX509Certificate, PARCX509Certificate); + +parcObject_ImplementRelease(parcX509Certificate, PARCX509Certificate); + +PARCX509Certificate * +parcX509Certificate_CreateFromPEMFile(const char *filename) +{ + PARCX509Certificate *certificate = _parcX509Certificate_CreateFromPEMFile(filename); + if (certificate) { + certificate->type = PARCCertificateType_X509; + certificate->encoding = PARCContainerEncoding_PEM; + return certificate; + } + return NULL; +} + +PARCX509Certificate * +parcX509Certificate_CreateFromDERBuffer(const PARCBuffer *buffer) +{ + parcSecurity_AssertIsInitialized(); + + PARCX509Certificate *cert = _createEmptyCertificate(); + cert->type = PARCCertificateType_X509; + cert->encoding = PARCContainerEncoding_DER; + + PARCByteArray *array = parcBuffer_Array(buffer); + uint8_t *arrayIn = parcByteArray_Array(array); + + cert->certificate = d2i_X509(&cert->certificate, (const unsigned char **) &arrayIn, parcBuffer_Remaining(buffer)); + if (cert->certificate == NULL) { + parcX509Certificate_Release(&cert); + return NULL; + } + cert->publicKey = X509_get_pubkey(cert->certificate); + + return cert; +} + +PARCX509Certificate * +parcX509Certificate_CreateSelfSignedCertificate(PARCBuffer **privateKeyBuffer, char *subjectName, int keyLength, size_t validityDays) +{ + parcSecurity_AssertIsInitialized(); + + RSA *rsa = RSA_new(); + assertNotNull(rsa, "RSA_new failed."); + + EVP_PKEY *privateKey = EVP_PKEY_new(); + assertNotNull(privateKey, "EVP_PKEY_new() failed."); + + X509 *cert = X509_new(); + assertNotNull(cert, "X509_new() failed."); + + int res; + BIGNUM *pub_exp; + + pub_exp = BN_new(); + + BN_set_word(pub_exp, RSA_F4); + res = 1; + bool result = false; + if (RSA_generate_key_ex(rsa, keyLength, pub_exp, NULL)) { + if (EVP_PKEY_set1_RSA(privateKey, rsa)) { + if (X509_set_version(cert, 2)) { // 2 => X509v3 + result = true; + } + } + } + if (result) { + // add serial number + if (_addRandomSerial(cert) == true) { + if (_addValidityPeriod(cert, validityDays) == true) { + if (X509_set_pubkey(cert, privateKey) == 1) { + if (_addSubjectName(cert, subjectName) == true) { + if (_addExtensions(cert) == true) { + if (_addKeyIdentifier(cert) == true) { + // The certificate is complete, sign it. + if (X509_sign(cert, privateKey, EVP_sha256())) { + result = true; + } else { + printf("error: (%d) %s\n", res, ERR_lib_error_string(res)); + } + } + } + } + } + } + } + } + + ERR_print_errors_fp(stdout); + + BN_free(pub_exp); + + uint8_t *certificateDerEncoding = NULL; + int numBytes = i2d_X509(cert, &certificateDerEncoding); + if (numBytes < 0) { + EVP_PKEY_free(privateKey); + RSA_free(rsa); + X509_free(cert); + + return NULL; + } + + PARCBuffer *derBuffer = parcBuffer_Allocate(numBytes); + parcBuffer_Flip(parcBuffer_PutArray(derBuffer, numBytes, certificateDerEncoding)); + + PARCX509Certificate *certificate = parcX509Certificate_CreateFromDERBuffer(derBuffer); + parcBuffer_Release(&derBuffer); + + uint8_t *privateKeyBytes = NULL; + int privateKeyByteCount = i2d_PrivateKey(privateKey, &privateKeyBytes); + if (privateKeyByteCount < 0) { + EVP_PKEY_free(privateKey); + RSA_free(rsa); + X509_free(cert); + + return NULL; + } + + *privateKeyBuffer = parcBuffer_Allocate(privateKeyByteCount); + parcBuffer_Flip(parcBuffer_PutArray(*privateKeyBuffer, privateKeyByteCount, privateKeyBytes)); + + return certificate; +} + + +PARCCryptoHash * +parcX509Certificate_GetCertificateDigest(PARCX509Certificate *certificate) +{ + return _getCertificateDigest(certificate); +} + +PARCCryptoHash * +parcX509Certificate_getPublicKeyDigest(PARCX509Certificate *certificate) +{ + return _getPublicKeyDigest(certificate); +} + +PARCBuffer * +parcX509Certificate_GetDEREncodedCertificate(PARCX509Certificate *certificate) +{ + return _getDEREncodedCertificate(certificate); +} + +PARCBuffer * +parcX509Certificate_GetDEREncodedPublicKey(PARCX509Certificate *certificate) +{ + return _getDEREncodedPublicKey(certificate); +} + +#ifdef __APPLE__ +#pragma clang diagnostic pop +#endif + |