mirror of
https://github.com/strongswan/strongswan.git
synced 2025-10-04 00:00:14 -04:00
pki: Enroll an X.509 certificate with a SCEP server
This commit is contained in:
parent
a9d70bd485
commit
7c7a5a0260
@ -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).
|
||||||
|
@ -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 \
|
||||||
|
@ -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
714
src/pki/commands/scep.c
Normal 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"},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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 */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user