pki: Enroll an X.509 certificate with a SCEP server

This commit is contained in:
Andreas Steffen 2022-08-01 11:57:41 +02:00
parent a9d70bd485
commit 7c7a5a0260
6 changed files with 893 additions and 133 deletions

View File

@ -1,2 +1,6 @@
pki.load = pki.load =
Plugins to load in ipsec pki tool. Plugins to load in the pki tool.
pki.scep.renewal_via_pkcs_req = no
Some SCEP servers (e.g. openxpki) are incorrectly doing certificate renewal
via messageType PKCSReq (19) instead of RenewalReq (17).

View File

@ -13,6 +13,7 @@ pki_SOURCES = pki.c pki.h command.c command.h \
commands/print.c \ commands/print.c \
commands/pub.c \ commands/pub.c \
commands/req.c \ commands/req.c \
commands/scep.c \
commands/scepca.c \ commands/scepca.c \
commands/self.c \ commands/self.c \
commands/signcrl.c \ commands/signcrl.c \

View File

@ -25,7 +25,7 @@
/** /**
* Maximum number of commands (+1). * Maximum number of commands (+1).
*/ */
#define MAX_COMMANDS 15 #define MAX_COMMANDS 16
/** /**
* Maximum number of options in a command (+3) * Maximum number of options in a command (+3)

714
src/pki/commands/scep.c Normal file
View File

@ -0,0 +1,714 @@
/*
* Copyright (C) 2005 Jan Hutter, Martin Willi
* Copyright (C) 2012 Tobias Brunner
* Copyright (C) 2022 Andreas Steffen, strongSec GmbH
*
* Copyright (C) secunet Security Networks AG
*
* This program 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. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program 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.
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include "pki.h"
#include "scep/scep.h"
#include <credentials/certificates/certificate.h>
#include <credentials/certificates/x509.h>
#include <credentials/sets/mem_cred.h>
#include <asn1/asn1.h>
/* default polling time interval in SCEP manual mode */
#define DEFAULT_POLL_INTERVAL 60 /* seconds */
/**
* Enroll an X.509 certificate with a SCEP server (RFC 8894)
*/
static int scep()
{
char *arg, *url = NULL, *file = NULL, *dn = NULL, *error = NULL;
char *ca_enc_file = NULL, *ca_sig_file = NULL;
char *old_cert_file = NULL, *old_key_file = NULL;
cred_encoding_type_t form = CERT_ASN1_DER;
chunk_t scep_response = chunk_empty;
chunk_t challenge_password = chunk_empty;
chunk_t serialNumber = chunk_empty;
chunk_t transID = chunk_empty;
chunk_t pkcs10_encoding = chunk_empty;
chunk_t cert_encoding = chunk_empty;
chunk_t pkcs7_req = chunk_empty;
chunk_t certPoll = chunk_empty;
chunk_t issuerAndSubject = chunk_empty;
chunk_t data = chunk_empty;
hash_algorithm_t digest_alg = HASH_SHA256;
encryption_algorithm_t cipher = ENCR_AES_CBC;
uint16_t key_size = 128;
signature_params_t *scheme = NULL;
private_key_t *private = NULL, *priv_signer = NULL;
public_key_t *public = NULL;
certificate_t *pkcs10 = NULL, *x509_signer = NULL, *cert = NULL;
certificate_t *x509_ca_sig = NULL, *x509_ca_enc = NULL;
identification_t *subject = NULL, *issuer = NULL;
container_t *container = NULL;
pkcs7_t *pkcs7;
mem_cred_t *creds = NULL;
scep_msg_t scep_msg_type;
scep_attributes_t attrs = empty_scep_attributes;
uint32_t caps_flags;
u_int poll_interval = DEFAULT_POLL_INTERVAL;
u_int max_poll_time = 0;
u_int poll_start = 0;
time_t notBefore, notAfter;
linked_list_t *san;
enumerator_t *enumerator;
int status = 1;
bool ok, stored = FALSE;
scep_http_params_t http_params = {
.get_request = FALSE, .timeout = 30, .bind = NULL
};
bool pss = lib->settings->get_bool(lib->settings,
"%s.rsa_pss", FALSE, lib->ns);
bool renewal_via_pkcs_req = lib->settings->get_bool(lib->settings,
"%s.scep.renewal_via_pkcs_req", FALSE, lib->ns);
/* initialize certificate validity */
notBefore = time(NULL);
notAfter = notBefore + 365 * 24 * 60 * 60;
/* initialize list of subjectAltNames */
san = linked_list_create();
/* initialize CA certificate storage */
creds = mem_cred_create();
lib->credmgr->add_set(lib->credmgr, &creds->set);
while (TRUE)
{
switch (command_getopt(&arg))
{
case 'h':
goto usage;
case 'u':
url = arg;
continue;
case 'i':
file = arg;
continue;
case 'd':
dn = arg;
continue;
case 'a':
san->insert_last(san, identification_create_from_string(arg));
continue;
case 'p':
challenge_password = chunk_create(arg, strlen(arg));
continue;
case 'e':
ca_enc_file = arg;
continue;
case 's':
ca_sig_file = arg;
continue;
case 'c':
cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, arg, BUILD_END);
if (!cert)
{
DBG1(DBG_APP, "could not load cacert file '%s'", arg);
goto end;
}
creds->add_cert(creds, TRUE, cert);
continue;
case 'o':
old_cert_file = arg;
continue;
case 'k':
old_key_file = arg;
continue;
case 'C':
if (strcaseeq(arg, "des3"))
{
cipher = ENCR_3DES;
key_size = 0;
}
else if (strcaseeq(arg, "aes"))
{
cipher = ENCR_AES_CBC;
key_size = 128;
}
else
{
error = "invalid --cipher type";
goto usage;
}
continue;
case 'g':
if (!enum_from_name(hash_algorithm_short_names, arg, &digest_alg))
{
error = "invalid --digest type";
goto usage;
}
continue;
case 'R':
if (streq(arg, "pss"))
{
pss = TRUE;
}
if (streq(arg, "pkcs1"))
{
pss = FALSE;
}
else {
error = "invalid RSA padding";
goto usage;
}
continue;
case 't': /* --pollinterval */
poll_interval = atoi(optarg);
if (poll_interval <= 0)
{
error = "invalid interval specified";
goto usage;
}
continue;
case 'm': /* --maxpolltime */
max_poll_time = atoi(optarg);
continue;
case 'f':
if (!get_form(arg, &form, CRED_CERTIFICATE))
{
error = "invalid certificate output format";
goto usage;
}
continue;
case EOF:
break;
default:
error = "invalid --scep option";
goto usage;
}
break;
}
if (!url)
{
error = "--url is required";
goto usage;
}
if (!ca_enc_file)
{
error = "--cacert-enc is required";
goto usage;
}
if (!ca_sig_file)
{
error = "--cacert-sig is required";
goto usage;
}
if (old_cert_file && !old_key_file)
{
error = "--oldkey is required if --oldcert is set";
goto usage;
}
if (!dn)
{
error = "--dn is required";
goto usage;
}
subject = identification_create_from_string(dn);
if (subject->get_type(subject) != ID_DER_ASN1_DN)
{
DBG1(DBG_APP, "supplied --dn is not a distinguished name");
goto end;
}
/* load RSA private key from file or stdin */
if (file)
{
private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA,
BUILD_FROM_FILE, file, BUILD_END);
}
else
{
chunk_t chunk;
set_file_mode(stdin, CERT_ASN1_DER);
if (!chunk_from_fd(0, &chunk))
{
DBG1(DBG_APP, "reading private key failed: %s\n", strerror(errno));
goto end;
}
private = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA,
BUILD_BLOB, chunk, BUILD_END);
free(chunk.ptr);
}
if (!private)
{
DBG1(DBG_APP, "parsing private key failed");
goto end;
}
public = private->get_public_key(private);
/* Request capabilities from SCEP server */
if (!scep_http_request(url, chunk_empty, SCEP_GET_CA_CAPS, &http_params,
&scep_response))
{
DBG1(DBG_APP, "did not receive a valid scep response");
goto end;
}
caps_flags = scep_parse_caps(scep_response);
chunk_free(&scep_response);
/* check support of selected digest algorithm */
switch (digest_alg)
{
case HASH_SHA256:
ok = (caps_flags & SCEP_CAPS_SHA256) ||
(caps_flags & SCEP_CAPS_SCEPSTANDARD);
break;
case HASH_SHA384:
ok = (caps_flags & SCEP_CAPS_SHA384);
break;
case HASH_SHA512:
ok = (caps_flags & SCEP_CAPS_SHA512);
break;
case HASH_SHA224:
ok = (caps_flags & SCEP_CAPS_SHA224);
break;
case HASH_SHA1:
ok = (caps_flags & SCEP_CAPS_SHA1);
break;
default:
ok = FALSE;
}
if (!ok)
{
DBG1(DBG_APP, "%N digest algorithm not supported by CA",
hash_algorithm_short_names, digest_alg);
goto end;
}
/* check support of selected encryption algorithm */
switch (cipher)
{
case ENCR_AES_CBC:
ok = (caps_flags & SCEP_CAPS_AES) ||
(caps_flags & SCEP_CAPS_SCEPSTANDARD);
break;
case ENCR_3DES:
ok = (caps_flags & SCEP_CAPS_DES3);
break;
default:
ok = FALSE;
}
if (!ok)
{
DBG1(DBG_APP, "%N encryption algorithm not supported by CA",
encryption_algorithm_names, cipher);
goto end;
}
DBG2(DBG_APP, "%N digest and %N encryption algorithm supported by CA",
hash_algorithm_short_names, digest_alg,
encryption_algorithm_names, cipher);
/* check support of HTTP POST operation */
if ((caps_flags & SCEP_CAPS_POSTPKIOPERATION) ||
(caps_flags & SCEP_CAPS_SCEPSTANDARD))
{
http_params.get_request = FALSE;
}
DBG2(DBG_APP, "HTTP POST %ssupported",
http_params.get_request ? "not " : "");
scheme = get_signature_scheme(private, digest_alg, pss);
if (!scheme)
{
DBG1(DBG_APP, "no signature scheme found");
goto end;
}
/* generate PKCS#10 certificate request */
pkcs10 = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_PKCS10_REQUEST,
BUILD_SIGNING_KEY, private,
BUILD_SUBJECT, subject,
BUILD_SUBJECT_ALTNAMES, san,
BUILD_CHALLENGE_PWD, challenge_password,
BUILD_SIGNATURE_SCHEME, scheme,
BUILD_END);
if (!pkcs10)
{
DBG1(DBG_APP, "generating certificate request failed");
goto end;
}
/* generate PKCS#10 encoding */
if (!pkcs10->get_encoding(pkcs10, CERT_ASN1_DER, &pkcs10_encoding))
{
DBG1(DBG_APP, "encoding certificate request failed");
goto end;
}
if (!scep_generate_transaction_id(public, &transID, &serialNumber))
{
DBG1(DBG_APP, "generating transaction ID failed");
goto end;
}
DBG1(DBG_APP, "transaction ID: %.*s", (int)transID.len, transID.ptr);
if (old_cert_file)
{
/* load old client certificate */
x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, old_cert_file, BUILD_END);
if (!x509_signer)
{
DBG1(DBG_APP, "could not load old cert file '%s'", old_cert_file);
goto end;
}
/* load old RSA private key */
priv_signer = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA,
BUILD_FROM_FILE, old_key_file, BUILD_END);
if (!priv_signer)
{
DBG1(DBG_APP, "parsing old private key failed");
goto end;
}
/* check support of Renewal Operation */
if (!(caps_flags & SCEP_CAPS_RENEWAL))
{
DBG1(DBG_APP, "Renewal operation not supported by SCEP server");
goto end;
}
DBG2(DBG_APP, "SCEP Renewal operation supported");
/* set message type for SCEP renewal request */
scep_msg_type = renewal_via_pkcs_req ? SCEP_PKCSReq_MSG :
SCEP_RenewalReq_MSG;
}
else
{
/* create self-signed X.509 certificate */
x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
BUILD_SIGNING_KEY, private,
BUILD_PUBLIC_KEY, public,
BUILD_SUBJECT, subject,
BUILD_NOT_BEFORE_TIME, notBefore,
BUILD_NOT_AFTER_TIME, notAfter,
BUILD_SERIAL, serialNumber,
BUILD_SUBJECT_ALTNAMES, san,
BUILD_SIGNATURE_SCHEME, scheme,
BUILD_END);
if (!x509_signer)
{
DBG1(DBG_APP, "generating self-sigend certificate failed");
goto end;
}
/* the signing key is identical to the client key */
priv_signer = private->get_ref(private);
/* set message type for SCEP request */
scep_msg_type = SCEP_PKCSReq_MSG;
}
creds->add_cert(creds, FALSE, x509_signer->get_ref(x509_signer));
creds->add_key(creds, priv_signer->get_ref(priv_signer));
/* load CA or RA certificate used for encryption */
x509_ca_enc = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, ca_enc_file, BUILD_END);
if (!x509_ca_enc)
{
DBG1(DBG_APP, "could not load encryption cacert file '%s'", ca_enc_file);
goto end;
}
/* load CA certificate used for signature verification */
x509_ca_sig = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, ca_sig_file, BUILD_END);
if (!x509_ca_sig)
{
DBG1(DBG_APP, "could not load signature cacert file '%s'", ca_sig_file);
goto end;
}
creds->add_cert(creds, TRUE, x509_ca_sig->get_ref(x509_ca_sig));
/* build pkcs7 request */
pkcs7_req = scep_build_request(pkcs10_encoding, transID, scep_msg_type,
x509_ca_enc, cipher, key_size, x509_signer,
digest_alg, priv_signer);
if (!pkcs7_req.ptr)
{
DBG1(DBG_APP, "failed to build SCEP request");
goto end;
}
if (!scep_http_request(url, pkcs7_req, SCEP_PKI_OPERATION, &http_params,
&scep_response))
{
DBG1(DBG_APP, "did not receive a valid SCEP response");
goto end;
}
if (!scep_parse_response(scep_response, transID, &container, &attrs))
{
goto end;
}
/* in case of manual mode, we are going into a polling loop */
if (attrs.pkiStatus == SCEP_PENDING)
{
issuer = x509_ca_sig->get_subject(x509_ca_sig);
issuerAndSubject = asn1_wrap(ASN1_SEQUENCE, "cc",
issuer->get_encoding(issuer),
subject->get_encoding(subject));
if (max_poll_time > 0)
{
DBG1(DBG_APP, " SCEP request pending, polling every %d seconds"
" up to %d seconds", poll_interval, max_poll_time);
}
else
{
DBG1(DBG_APP, " SCEP request pending, polling indefinitely"
" every %d seconds", poll_interval);
}
poll_start = time_monotonic(NULL);
}
while (attrs.pkiStatus == SCEP_PENDING)
{
if (max_poll_time > 0 &&
(time_monotonic(NULL) - poll_start) >= max_poll_time)
{
DBG1(DBG_APP, "maximum poll time reached: %d seconds", max_poll_time);
goto end;
}
DBG1(DBG_APP, " going to sleep for %d seconds", poll_interval);
sleep(poll_interval);
chunk_free(&certPoll);
chunk_free(&scep_response);
chunk_free(&attrs.transID);
chunk_free(&attrs.recipientNonce);
container->destroy(container);
container = NULL;
DBG1(DBG_APP, "transaction ID: %.*s", (int)transID.len, transID.ptr);
certPoll = scep_build_request(issuerAndSubject, transID, SCEP_CertPoll_MSG,
x509_ca_enc, cipher, key_size, x509_signer,
digest_alg, priv_signer);
if (!certPoll.ptr)
{
DBG1(DBG_APP, "failed to build SCEP certPoll request");
goto end;
}
if (!scep_http_request(url, certPoll, SCEP_PKI_OPERATION,
&http_params, &scep_response))
{
DBG1(DBG_APP, "did not receive a valid SCEP response");
goto end;
}
if (!scep_parse_response(scep_response, transID, &container, &attrs))
{
goto end;
}
}
if (attrs.pkiStatus != SCEP_SUCCESS)
{
DBG1(DBG_APP, "reply status is not 'SUCCESS'");
goto end;
}
if (!container->get_data(container, &data))
{
DBG1(DBG_APP, "extracting enveloped-data failed");
goto end;
}
container->destroy(container);
/* decrypt enveloped-data container */
container = lib->creds->create(lib->creds,
CRED_CONTAINER, CONTAINER_PKCS7,
BUILD_BLOB_ASN1_DER, data,
BUILD_END);
chunk_free(&data);
if (!container)
{
DBG1(DBG_APP, "could not decrypt envelopedData");
goto end;
}
if (!container->get_data(container, &data))
{
DBG1(DBG_APP, "extracting encrypted-data failed");
goto end;
}
container->destroy(container);
/* parse signed-data container */
container = lib->creds->create(lib->creds,
CRED_CONTAINER, CONTAINER_PKCS7,
BUILD_BLOB_ASN1_DER, data,
BUILD_END);
chunk_free(&data);
if (!container)
{
DBG1(DBG_APP, "could not parse signed-data");
goto end;
}
/* no need to verify the signed-data container, the signature does NOT
* cover the contained certificates */
/* store the end entity certificate */
pkcs7 = (pkcs7_t*)container;
enumerator = pkcs7->create_cert_enumerator(pkcs7);
while (enumerator->enumerate(enumerator, &cert))
{
x509_t *x509 = (x509_t*)cert;
enumerator_t *certs;
time_t from, until;
bool trusted, valid;
if (!(x509->get_flags(x509) & X509_CA))
{
DBG1(DBG_APP, "certificate \"%Y\"", cert->get_subject(cert));
if (stored)
{
DBG1(DBG_APP, "multiple certs received, only first stored");
continue;
}
/* establish trust relativ to root CA */
creds->add_cert(creds, FALSE, cert->get_ref(cert));
certs = lib->credmgr->create_trusted_enumerator(lib->credmgr,
KEY_RSA, cert->get_subject(cert), FALSE);
trusted = certs->enumerate(certs, &cert, NULL);
valid = cert->get_validity(cert, NULL, &from, &until);
DBG1(DBG_APP, "certificate is %strusted, valid from %T until %T "
"(currently %svalid)",
trusted ? "" : "not ", &from, FALSE, &until, FALSE,
valid ? "" : "not ");
certs->destroy(certs);
if (!cert->get_encoding(cert, form, &cert_encoding))
{
DBG1(DBG_APP, "encoding certificate failed");
break;
}
set_file_mode(stdout, form);
if (fwrite(cert_encoding.ptr, cert_encoding.len, 1, stdout) != 1)
{
DBG1(DBG_APP, "writing certificate failed");
break;
}
else
{
stored = TRUE;
status = 0;
}
}
}
enumerator->destroy(enumerator);
end:
lib->credmgr->remove_set(lib->credmgr, &creds->set);
creds->destroy(creds);
san->destroy_offset(san, offsetof(identification_t, destroy));
signature_params_destroy(scheme);
DESTROY_IF(subject);
DESTROY_IF(private);
DESTROY_IF(public);
DESTROY_IF(priv_signer);
DESTROY_IF(x509_signer);
DESTROY_IF(pkcs10);
DESTROY_IF(x509_ca_enc);
DESTROY_IF(x509_ca_sig);
DESTROY_IF(container);
chunk_free(&scep_response);
chunk_free(&serialNumber);
chunk_free(&transID);
chunk_free(&pkcs10_encoding);
chunk_free(&cert_encoding);
chunk_free(&pkcs7_req);
chunk_free(&certPoll);
chunk_free(&issuerAndSubject);
chunk_free(&attrs.transID);
chunk_free(&attrs.recipientNonce);
return status;
usage:
lib->credmgr->remove_set(lib->credmgr, &creds->set);
creds->destroy(creds);
san->destroy_offset(san, offsetof(identification_t, destroy));
return command_usage(error);
}
/**
* Register the command.
*/
static void __attribute__ ((constructor))reg()
{
command_register((command_t) {
scep, 'S', "scep",
"Enroll an X.509 certificate with a SCEP server",
{"--url url [--in file] --dn distinguished-name [--san subjectAltName]+",
"[--password password] --cacert-enc file --cacert-sig file [--cacert file]+",
"[--oldcert file --oldkey file] [--cipher aes|des3]",
"[--digest sha256|sha384|sha512|sha224|sha1] [--rsa-padding pkcs1|pss]",
"[--interval time] [--maxpolltime time] [--outform der|pem]"},
{
{"help", 'h', 0, "show usage information"},
{"url", 'u', 1, "URL of the SCEP server"},
{"in", 'i', 1, "RSA private key input file, default: stdin"},
{"dn", 'd', 1, "subject distinguished name"},
{"san", 'a', 1, "subjectAltName to include in cert request"},
{"password", 'p', 1, "challengePassword to include in cert request"},
{"cacert-enc", 'e', 1, "CA certificate for encryption"},
{"cacert-sig", 's', 1, "CA certificate for signature verification"},
{"cacert", 'c', 1, "Additional CA certificates"},
{"oldcert", 'o', 1, "Old certificate about to be renewed"},
{"oldkey", 'k', 1, "Old RSA private key about to be replaced"},
{"cipher", 'C', 1, "encryption cipher, default: aes"},
{"digest", 'g', 1, "digest for signature creation, default: sha256"},
{"rsa-padding", 'R', 1, "padding for RSA signatures, default: pkcs1"},
{"interval", 't', 1, "poll interval, default: 60s"},
{"maxpolltime", 'm', 1, "maximum poll time, default: 0 (no limit)"},
{"outform", 'f', 1, "encoding of stored certificates, default: der"},
}
});
}

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2012 Tobias Brunner
* Copyright (C) 2005 Jan Hutter, Martin Willi * Copyright (C) 2005 Jan Hutter, Martin Willi
* Copyright (C) 2012 Tobias Brunner
* Copyright (C) 2022 Andreas Steffen, strongSec GmbH * Copyright (C) 2022 Andreas Steffen, strongSec GmbH
* *
* Copyright (C) secunet Security Networks AG * Copyright (C) secunet Security Networks AG
@ -21,6 +21,7 @@
#include <library.h> #include <library.h>
#include <utils/debug.h> #include <utils/debug.h>
#include <utils/lexparser.h>
#include <asn1/asn1.h> #include <asn1/asn1.h>
#include <asn1/asn1_parser.h> #include <asn1/asn1_parser.h>
#include <asn1/oid.h> #include <asn1/oid.h>
@ -29,6 +30,12 @@
#include "scep.h" #include "scep.h"
static const char *operations[] = {
"PKIOperation",
"GetCACert",
"GetCACaps"
};
static const char *pkiStatus_values[] = { "0", "2", "3" }; static const char *pkiStatus_values[] = { "0", "2", "3" };
static const char *pkiStatus_names[] = { static const char *pkiStatus_names[] = {
@ -58,6 +65,20 @@ static const char *failInfo_reasons[] = {
"badCertId - No certificate could be identified matching the provided criteria" "badCertId - No certificate could be identified matching the provided criteria"
}; };
static const char *caps_names[] = {
"AES",
"DES3",
"SHA-256",
"SHA-384",
"SHA-512",
"SHA-224",
"SHA-1",
"POSTPKIOperation",
"SCEPStandard",
"GetNextCACert",
"Renewal"
};
const scep_attributes_t empty_scep_attributes = { const scep_attributes_t empty_scep_attributes = {
SCEP_Unknown_MSG , /* msgType */ SCEP_Unknown_MSG , /* msgType */
SCEP_UNKNOWN , /* pkiStatus */ SCEP_UNKNOWN , /* pkiStatus */
@ -125,75 +146,46 @@ void extract_attributes(pkcs7_t *pkcs7, enumerator_t *enumerator,
} }
/** /**
* Generates a unique fingerprint of the pkcs10 request * Generate a transaction ID as the SHA-1 hash of the publicKeyInfo
* by computing an MD5 hash over it * the transaction ID is also used as a unique serial number
*/ */
chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10) bool scep_generate_transaction_id(public_key_t *public,
chunk_t *transId, chunk_t *serialNumber)
{ {
chunk_t digest = chunk_alloca(HASH_SIZE_MD5); chunk_t digest;
hasher_t *hasher;
hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
if (!hasher || !hasher->get_hash(hasher, pkcs10, digest.ptr))
{
DESTROY_IF(hasher);
return chunk_empty;
}
hasher->destroy(hasher);
return chunk_to_hex(digest, NULL, FALSE);
}
/**
* Generate a transaction id as the MD5 hash of an public key
* the transaction id is also used as a unique serial number
*/
void scep_generate_transaction_id(public_key_t *key, chunk_t *transID,
chunk_t *serialNumber)
{
chunk_t digest = chunk_alloca(HASH_SIZE_MD5);
chunk_t keyEncoding = chunk_empty, keyInfo;
hasher_t *hasher;
int zeros = 0, msb_set = 0; int zeros = 0, msb_set = 0;
key->get_encoding(key, PUBKEY_ASN1_DER, &keyEncoding); if (public->get_fingerprint(public, KEYID_PUBKEY_INFO_SHA1, &digest))
keyInfo = asn1_wrap(ASN1_SEQUENCE, "mm",
asn1_algorithmIdentifier(OID_RSA_ENCRYPTION),
asn1_bitstring("m", keyEncoding));
hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
if (!hasher || !hasher->get_hash(hasher, keyInfo, digest.ptr))
{ {
memset(digest.ptr, 0, digest.len); /* the transaction ID is the fingerprint in hex format */
} *transId = chunk_to_hex(digest, NULL, TRUE);
DESTROY_IF(hasher);
free(keyInfo.ptr);
/* the serialNumber should be valid ASN1 integer content: /**
* remove leading zeros, add one if MSB is set (two's complement) */ * the serial number must be a valid positive ASN.1 integer
while (zeros < digest.len) * remove leading zeros, add one if MSB is set (two's complement)
{ */
if (digest.ptr[zeros]) while (zeros < digest.len)
{ {
if (digest.ptr[zeros] & 0x80) if (digest.ptr[zeros])
{ {
msb_set = 1; if (digest.ptr[zeros] & 0x80)
{
msb_set = 1;
}
break;
} }
break; zeros++;
} }
zeros++; *serialNumber = chunk_alloc(digest.len - zeros + msb_set);
if (msb_set)
{
serialNumber->ptr[0] = 0x00;
}
memcpy(serialNumber->ptr + msb_set, digest.ptr + zeros,
digest.len - zeros);
return TRUE;
} }
*serialNumber = chunk_alloc(digest.len - zeros + msb_set); return FALSE;
if (msb_set)
{
serialNumber->ptr[0] = 0x00;
}
memcpy(serialNumber->ptr + msb_set, digest.ptr + zeros,
digest.len - zeros);
/* the transaction id is the serial number in hex format */
*transID = chunk_to_hex(digest, NULL, TRUE);
} }
/** /**
@ -347,6 +339,7 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op,
int len; int len;
status_t status; status_t status;
char *complete_url = NULL; char *complete_url = NULL;
const char *operation;
host_t *srcip = NULL; host_t *srcip = NULL;
/* initialize response */ /* initialize response */
@ -356,69 +349,70 @@ bool scep_http_request(const char *url, chunk_t msg, scep_op_t op,
{ {
srcip = host_create_from_string(http_params->bind, 0); srcip = host_create_from_string(http_params->bind, 0);
} }
DBG2(DBG_APP, "sending scep request to '%s'", url); DBG2(DBG_APP, "sending scep request to '%s'", url);
if (op == SCEP_PKI_OPERATION) operation = operations[op];
switch (op)
{ {
const char operation[] = "PKIOperation"; case SCEP_PKI_OPERATION:
default:
if (http_params->get_request)
{
char *escaped_req = escape_http_request(msg);
if (http_params->get_request) /* form complete url */
{ len = strlen(url) + 20 + strlen(operation) +
char *escaped_req = escape_http_request(msg); strlen(escaped_req) + 1;
complete_url = malloc(len);
snprintf(complete_url, len, "%s?operation=%s&message=%s"
, url, operation, escaped_req);
free(escaped_req);
/* form complete url */ status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
len = strlen(url) + 20 + strlen(operation) + strlen(escaped_req) + 1;
complete_url = malloc(len);
snprintf(complete_url, len, "%s?operation=%s&message=%s"
, url, operation, escaped_req);
free(escaped_req);
status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
FETCH_TIMEOUT, http_params->timeout, FETCH_TIMEOUT, http_params->timeout,
FETCH_REQUEST_HEADER, "Pragma:", FETCH_REQUEST_HEADER, "Pragma:",
FETCH_REQUEST_HEADER, "Host:", FETCH_REQUEST_HEADER, "Host:",
FETCH_REQUEST_HEADER, "Accept:", FETCH_REQUEST_HEADER, "Accept:",
FETCH_SOURCEIP, srcip, FETCH_SOURCEIP, srcip,
FETCH_END); FETCH_END);
} }
else /* HTTP_POST */ else /* HTTP_POST */
{ {
/* form complete url */ /* form complete url */
len = strlen(url) + 11 + strlen(operation) + 1; len = strlen(url) + 11 + strlen(operation) + 1;
complete_url = malloc(len); complete_url = malloc(len);
snprintf(complete_url, len, "%s?operation=%s", url, operation); snprintf(complete_url, len, "%s?operation=%s", url, operation);
status = lib->fetcher->fetch(lib->fetcher, complete_url, response, status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
FETCH_TIMEOUT, http_params->timeout, FETCH_TIMEOUT, http_params->timeout,
FETCH_REQUEST_DATA, msg, FETCH_REQUEST_DATA, msg,
FETCH_REQUEST_TYPE, "", FETCH_REQUEST_TYPE, "",
FETCH_REQUEST_HEADER, "Expect:", FETCH_REQUEST_HEADER, "Expect:",
FETCH_SOURCEIP, srcip, FETCH_SOURCEIP, srcip,
FETCH_END); FETCH_END);
} }
} break;
else /* SCEP_GET_CA_CERT */ case SCEP_GET_CA_CERT:
{ case SCEP_GET_CA_CAPS:
const char operation[] = "GetCACert"; {
/* form complete url */
len = strlen(url) + 11 + strlen(operation) + 1;
complete_url = malloc(len);
snprintf(complete_url, len, "%s?operation=%s", url, operation);
/* form complete url */ status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
len = strlen(url) + 11 + strlen(operation) + 1;
complete_url = malloc(len);
snprintf(complete_url, len, "%s?operation=%s", url, operation);
status = lib->fetcher->fetch(lib->fetcher, complete_url, response,
FETCH_TIMEOUT, http_params->timeout, FETCH_TIMEOUT, http_params->timeout,
FETCH_SOURCEIP, srcip, FETCH_SOURCEIP, srcip,
FETCH_END); FETCH_END);
}
} }
DESTROY_IF(srcip); DESTROY_IF(srcip);
free(complete_url); free(complete_url);
return (status == SUCCESS); return (status == SUCCESS);
} }
err_t scep_parse_response(chunk_t response, chunk_t transID, bool scep_parse_response(chunk_t response, chunk_t transID,
container_t **out, scep_attributes_t *attrs) container_t **out, scep_attributes_t *attrs)
{ {
enumerator_t *enumerator; enumerator_t *enumerator;
@ -426,16 +420,19 @@ err_t scep_parse_response(chunk_t response, chunk_t transID,
container_t *container; container_t *container;
auth_cfg_t *auth; auth_cfg_t *auth;
*out = NULL;
container = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7, container = lib->creds->create(lib->creds, CRED_CONTAINER, CONTAINER_PKCS7,
BUILD_BLOB_ASN1_DER, response, BUILD_END); BUILD_BLOB_ASN1_DER, response, BUILD_END);
if (!container) if (!container)
{ {
return "error parsing the scep response"; DBG1(DBG_APP, "error parsing the scep response");
return FALSE;
} }
if (container->get_type(container) != CONTAINER_PKCS7_SIGNED_DATA) if (container->get_type(container) != CONTAINER_PKCS7_SIGNED_DATA)
{ {
container->destroy(container); DBG1(DBG_APP, "scep response is not PKCS#7 signed-data");
return "scep response is not PKCS#7 signed-data"; goto error;
} }
enumerator = container->create_signature_enumerator(container); enumerator = container->create_signature_enumerator(container);
@ -446,16 +443,45 @@ err_t scep_parse_response(chunk_t response, chunk_t transID,
if (!chunk_equals(transID, attrs->transID)) if (!chunk_equals(transID, attrs->transID))
{ {
enumerator->destroy(enumerator); enumerator->destroy(enumerator);
container->destroy(container); DBG1(DBG_APP, "transaction ID of scep response does not match");
return "transaction ID of scep response does not match"; goto error;
} }
} }
enumerator->destroy(enumerator); enumerator->destroy(enumerator);
if (!verified) if (!verified)
{ {
container->destroy(container); DBG1(DBG_APP, "unable to verify PKCS#7 container");
return "unable to verify PKCS#7 container"; goto error;
} }
*out = container; *out = container;
return NULL;
return TRUE;
error:
container->destroy(container);
return FALSE;
} }
uint32_t scep_parse_caps(chunk_t response)
{
uint32_t caps_flags = 0;
chunk_t line;
DBG2(DBG_APP, "CA Capabilities:");
while (fetchline(&response, &line))
{
int i;
for (i = 0; i < countof(caps_names); i++)
{
if (strncaseeq(caps_names[i], line.ptr, line.len))
{
DBG2(DBG_APP, " %s", caps_names[i]);
caps_flags |= (1 << i);
}
}
}
return caps_flags;
}

View File

@ -1,6 +1,6 @@
/* /*
* Copyright (C) 2012 Tobias Brunner
* Copyright (C) 2005 Jan Hutter, Martin Willi * Copyright (C) 2005 Jan Hutter, Martin Willi
* Copyright (C) 2012 Tobias Brunner
* Copyright (C) 2022 Andreas Steffen, strongSec GmbH * Copyright (C) 2022 Andreas Steffen, strongSec GmbH
* *
* Copyright (C) secunet Security Networks AG * Copyright (C) secunet Security Networks AG
@ -25,36 +25,37 @@
/* supported SCEP operation types */ /* supported SCEP operation types */
typedef enum { typedef enum {
SCEP_PKI_OPERATION, SCEP_PKI_OPERATION,
SCEP_GET_CA_CERT SCEP_GET_CA_CERT,
SCEP_GET_CA_CAPS
} scep_op_t; } scep_op_t;
/* SCEP pkiStatus values */ /* SCEP pkiStatus values */
typedef enum { typedef enum {
SCEP_SUCCESS, SCEP_SUCCESS,
SCEP_FAILURE, SCEP_FAILURE,
SCEP_PENDING, SCEP_PENDING,
SCEP_UNKNOWN SCEP_UNKNOWN
} pkiStatus_t; } pkiStatus_t;
/* SCEP messageType values */ /* SCEP messageType values */
typedef enum { typedef enum {
SCEP_CertRep_MSG, SCEP_CertRep_MSG,
SCEP_RenewalReq_MSG, SCEP_RenewalReq_MSG,
SCEP_PKCSReq_MSG, SCEP_PKCSReq_MSG,
SCEP_CertPoll_MSG, SCEP_CertPoll_MSG,
SCEP_GetCert_MSG, SCEP_GetCert_MSG,
SCEP_GetCRL_MSG, SCEP_GetCRL_MSG,
SCEP_Unknown_MSG SCEP_Unknown_MSG
} scep_msg_t; } scep_msg_t;
/* SCEP failure reasons */ /* SCEP failure reasons */
typedef enum { typedef enum {
SCEP_badAlg_REASON = 0, SCEP_badAlg_REASON = 0,
SCEP_badMessageCheck_REASON = 1, SCEP_badMessageCheck_REASON = 1,
SCEP_badRequest_REASON = 2, SCEP_badRequest_REASON = 2,
SCEP_badTime_REASON = 3, SCEP_badTime_REASON = 3,
SCEP_badCertId_REASON = 4, SCEP_badCertId_REASON = 4,
SCEP_unknown_REASON = 5 SCEP_unknown_REASON = 5
} failInfo_t; } failInfo_t;
/* SCEP attributes */ /* SCEP attributes */
@ -69,20 +70,32 @@ typedef struct {
/* SCEP http parameters */ /* SCEP http parameters */
typedef struct { typedef struct {
bool get_request; bool get_request;
u_int timeout; u_int timeout;
char *bind; char *bind;
} scep_http_params_t; } scep_http_params_t;
/* SCEP CA Capabilities */
typedef enum {
SCEP_CAPS_AES = 0,
SCEP_CAPS_DES3 = 1,
SCEP_CAPS_SHA256 = 2,
SCEP_CAPS_SHA384 = 3,
SCEP_CAPS_SHA512 = 4,
SCEP_CAPS_SHA224 = 5,
SCEP_CAPS_SHA1 = 6,
SCEP_CAPS_POSTPKIOPERATION = 7,
SCEP_CAPS_SCEPSTANDARD = 8,
SCEP_CAPS_GETNEXTCACERT = 9,
SCEP_CAPS_RENEWAL = 10
} scep_caps_t;
extern const scep_attributes_t empty_scep_attributes; extern const scep_attributes_t empty_scep_attributes;
bool parse_attributes(chunk_t blob, scep_attributes_t *attrs); bool parse_attributes(chunk_t blob, scep_attributes_t *attrs);
void scep_generate_transaction_id(public_key_t *key, bool scep_generate_transaction_id(public_key_t *key,
chunk_t *transID, chunk_t *transId, chunk_t *serialNumber);
chunk_t *serialNumber);
chunk_t scep_generate_pkcs10_fingerprint(chunk_t pkcs10);
chunk_t scep_transId_attribute(chunk_t transaction_id); chunk_t scep_transId_attribute(chunk_t transaction_id);
@ -98,7 +111,9 @@ chunk_t scep_build_request(chunk_t data, chunk_t transID, scep_msg_t msg,
bool scep_http_request(const char *url, chunk_t msg, scep_op_t op, bool scep_http_request(const char *url, chunk_t msg, scep_op_t op,
scep_http_params_t *http_params, chunk_t *response); scep_http_params_t *http_params, chunk_t *response);
err_t scep_parse_response(chunk_t response, chunk_t transID, bool scep_parse_response(chunk_t response, chunk_t transID, container_t **out,
container_t **out, scep_attributes_t *attrs); scep_attributes_t *attrs);
uint32_t scep_parse_caps(chunk_t response);
#endif /* _SCEP_H */ #endif /* _SCEP_H */