From 84f7d34079fcc68bfa335adec9a4fbcb75d52bd4 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Tue, 16 Sep 2008 16:05:38 -0700 Subject: [PATCH] Make it easier to bootstrap the PKI for SSL connections in OpenFlow. --- controller/controller.8.in | 16 +- controller/controller.c | 16 +- include/util.h | 1 + include/vconn-ssl.h | 25 +-- include/vconn.h | 2 +- lib/util.c | 2 +- lib/vconn-ssl.c | 307 ++++++++++++++++++++++++++++++++++--- lib/vconn.c | 9 +- secchan/secchan.8.in | 20 +++ secchan/secchan.c | 14 +- switch/switch.c | 14 +- utilities/dpctl.c | 2 +- 12 files changed, 381 insertions(+), 47 deletions(-) diff --git a/controller/controller.8.in b/controller/controller.8.in index 2781fdebf..1df6b7528 100644 --- a/controller/controller.8.in +++ b/controller/controller.8.in @@ -68,10 +68,24 @@ controller's certificate authority (CA), that certifies the switch's private key to identify a trustworthy switch. .TP -\fB-C\fR, \fB--ca-cert=\fIcacert.pem\fR +\fB-C\fR, \fB--ca-cert=\fIswitch-cacert.pem\fR Specifies a PEM file containing the CA certificate used to verify that the switch is connected to a trustworthy controller. +.TP +\fB--peer-ca-cert=\fIcontroller-cacert.pem\fR +Specifies a PEM file that contains one or more additional certificates +to send to switches. \fIcontroller-cacert.pem\fR should be the CA +certificate used to sign the controller's own certificate (the +certificate specified on \fB-c\fR or \fB--certificate\fR). + +This option is not useful in normal operation, because the switch must +already have the controller CA certificate for it to have any +confidence in the controller's identity. However, this option allows +a newly installed switch to obtain the controller CA certificate on +first boot using, e.g., the \fB--bootstrap-ca-cert\fR option to +\fBsecchan\fR(8). + .TP .BR \-n ", " \-\^\-noflow By default, the controller sets up a flow in each OpenFlow switch diff --git a/controller/controller.c b/controller/controller.c index 1bed5c612..dcffee44e 100644 --- a/controller/controller.c +++ b/controller/controller.c @@ -232,7 +232,10 @@ do_switching(struct switch_ *sw) static void parse_options(int argc, char *argv[]) { - enum { OPT_MAX_IDLE = UCHAR_MAX + 1 }; + enum { + OPT_MAX_IDLE = UCHAR_MAX + 1, + OPT_PEER_CA_CERT + }; static struct option long_options[] = { {"detach", no_argument, 0, 'D'}, {"pidfile", optional_argument, 0, 'P'}, @@ -243,7 +246,10 @@ parse_options(int argc, char *argv[]) {"verbose", optional_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, +#ifdef HAVE_OPENSSL VCONN_SSL_LONG_OPTIONS + {"peer-ca-cert", required_argument, 0, OPT_PEER_CA_CERT}, +#endif {0, 0, 0, 0}, }; char *short_options = long_options_to_short_options(long_options); @@ -301,8 +307,14 @@ parse_options(int argc, char *argv[]) vlog_set_verbosity(optarg); break; +#ifdef HAVE_OPENSSL VCONN_SSL_OPTION_HANDLERS + case OPT_PEER_CA_CERT: + vconn_ssl_set_peer_ca_cert_file(optarg); + break; +#endif + case '?': exit(EXIT_FAILURE); @@ -320,7 +332,7 @@ usage(void) "usage: %s [OPTIONS] METHOD\n" "where METHOD is any OpenFlow connection method.\n", program_name, program_name); - vconn_usage(true, true); + vconn_usage(true, true, false); printf("\nOther options:\n" " -D, --detach run in background as daemon\n" " -P, --pidfile[=FILE] create pidfile (default: %s/controller.pid)\n" diff --git a/include/util.h b/include/util.h index 6f3763708..d9c0b707a 100644 --- a/include/util.h +++ b/include/util.h @@ -83,6 +83,7 @@ extern "C" { void set_program_name(const char *); +void out_of_memory(void); void *xmalloc(size_t); void *xcalloc(size_t, size_t); void *xrealloc(void *, size_t); diff --git a/include/vconn-ssl.h b/include/vconn-ssl.h index fb80068e4..cacfd6418 100644 --- a/include/vconn-ssl.h +++ b/include/vconn-ssl.h @@ -39,24 +39,25 @@ bool vconn_ssl_is_configured(void); void vconn_ssl_set_private_key_file(const char *file_name); void vconn_ssl_set_certificate_file(const char *file_name); -void vconn_ssl_set_ca_cert_file(const char *file_name); +void vconn_ssl_set_ca_cert_file(const char *file_name, bool bootstrap); +void vconn_ssl_set_peer_ca_cert_file(const char *file_name); #define VCONN_SSL_LONG_OPTIONS \ {"private-key", required_argument, 0, 'p'}, \ {"certificate", required_argument, 0, 'c'}, \ {"ca-cert", required_argument, 0, 'C'}, -#define VCONN_SSL_OPTION_HANDLERS \ - case 'p': \ - vconn_ssl_set_private_key_file(optarg); \ - break; \ - \ - case 'c': \ - vconn_ssl_set_certificate_file(optarg); \ - break; \ - \ - case 'C': \ - vconn_ssl_set_ca_cert_file(optarg); \ +#define VCONN_SSL_OPTION_HANDLERS \ + case 'p': \ + vconn_ssl_set_private_key_file(optarg); \ + break; \ + \ + case 'c': \ + vconn_ssl_set_certificate_file(optarg); \ + break; \ + \ + case 'C': \ + vconn_ssl_set_ca_cert_file(optarg, false); \ break; #else /* !HAVE_OPENSSL */ static inline bool vconn_ssl_is_configured(void) diff --git a/include/vconn.h b/include/vconn.h index 91bb6879d..f085dcbab 100644 --- a/include/vconn.h +++ b/include/vconn.h @@ -44,7 +44,7 @@ struct ofp_header; struct pvconn; struct vconn; -void vconn_usage(bool active, bool passive); +void vconn_usage(bool active, bool passive, bool bootstrap); /* Active vconns: virtual connections to OpenFlow devices. */ int vconn_open(const char *name, int min_version, struct vconn **); diff --git a/lib/util.c b/lib/util.c index 6a0c420c6..0c70710dd 100644 --- a/lib/util.c +++ b/lib/util.c @@ -40,7 +40,7 @@ const char *program_name; -static void +void out_of_memory(void) { ofp_fatal(0, "virtual memory exhausted"); diff --git a/lib/vconn-ssl.c b/lib/vconn-ssl.c index 2801c0ca9..4bc123b58 100644 --- a/lib/vconn-ssl.c +++ b/lib/vconn-ssl.c @@ -35,23 +35,28 @@ #include "vconn-ssl.h" #include "dhparams.h" #include +#include #include #include #include #include #include #include +#include #include +#include +#include #include +#include "dynamic-string.h" #include "ofpbuf.h" -#include "socket-util.h" -#include "util.h" #include "openflow.h" #include "packets.h" #include "poll-loop.h" #include "socket-util.h" -#include "vconn.h" +#include "socket-util.h" +#include "util.h" #include "vconn-provider.h" +#include "vconn.h" #include "vlog.h" #define THIS_MODULE VLM_vconn_ssl @@ -145,6 +150,18 @@ static SSL_CTX *ctx; /* Required configuration. */ static bool has_private_key, has_certificate, has_ca_cert; +/* Ordinarily, we require a CA certificate for the peer to be locally + * available. 'has_ca_cert' is true when this is the case, and neither of the + * following variables matter. + * + * We can, however, bootstrap the CA certificate from the peer at the beginning + * of our first connection then use that certificate on all subsequent + * connections, saving it to a file for use in future runs also. In this case, + * 'has_ca_cert' is false, 'bootstrap_ca_cert' is true, and 'ca_cert_file' + * names the file to be saved. */ +static bool bootstrap_ca_cert; +static char *ca_cert_file; + /* Who knows what can trigger various SSL errors, so let's throttle them down * quite a bit. */ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25); @@ -157,6 +174,7 @@ static int interpret_ssl_error(const char *function, int ret, int error, int *want); static void ssl_tx_poll_callback(int fd, short int revents, void *vconn_); static DH *tmp_dh_callback(SSL *ssl, int is_export UNUSED, int keylength); +static void log_ca_cert(const char *file_name, X509 *cert); short int want_to_poll_events(int want) @@ -195,12 +213,13 @@ new_ssl_vconn(const char *name, int fd, enum session_type type, VLOG_ERR("Certificate must be configured to use SSL"); goto error; } - if (!has_ca_cert) { + if (!has_ca_cert && !bootstrap_ca_cert) { VLOG_ERR("CA certificate must be configured to use SSL"); goto error; } if (!SSL_CTX_check_private_key(ctx)) { - VLOG_ERR("Private key does not match certificate public key"); + VLOG_ERR("Private key does not match certificate public key: %s", + ERR_error_string(ERR_get_error(), NULL)); goto error; } @@ -223,6 +242,9 @@ new_ssl_vconn(const char *name, int fd, enum session_type type, VLOG_ERR("SSL_set_fd: %s", ERR_error_string(ERR_get_error(), NULL)); goto error; } + if (bootstrap_ca_cert && type == CLIENT) { + SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL); + } /* Create and return the ssl_vconn. */ sslv = xmalloc(sizeof *sslv); @@ -316,6 +338,93 @@ ssl_open(const char *name, char *suffix, struct vconn **vconnp) } } +static int +do_ca_cert_bootstrap(struct vconn *vconn) +{ + struct ssl_vconn *sslv = ssl_vconn_cast(vconn); + STACK_OF(X509) *chain; + X509 *ca_cert; + FILE *file; + int error; + int fd; + + chain = SSL_get_peer_cert_chain(sslv->ssl); + if (!chain || !sk_X509_num(chain)) { + VLOG_ERR("could not bootstrap CA cert: no certificate presented by " + "peer"); + return EPROTO; + } + ca_cert = sk_X509_value(chain, sk_X509_num(chain) - 1); + + /* Check that 'ca_cert' is self-signed. Otherwise it is not a CA + * certificate and we should not attempt to use it as one. */ + error = X509_check_issued(ca_cert, ca_cert); + if (error) { + VLOG_ERR("could not bootstrap CA cert: obtained certificate is " + "not self-signed (%s)", + X509_verify_cert_error_string(error)); + if (sk_X509_num(chain) < 2) { + VLOG_ERR("only one certificate was received, so probably the peer " + "is not configured to send its CA certificate"); + } + return EPROTO; + } + + fd = open(ca_cert_file, O_CREAT | O_EXCL | O_WRONLY, 0444); + if (fd < 0) { + VLOG_ERR("could not bootstrap CA cert: creating %s failed: %s", + ca_cert_file, strerror(errno)); + return errno; + } + + file = fdopen(fd, "w"); + if (!file) { + int error = errno; + VLOG_ERR("could not bootstrap CA cert: fdopen failed: %s", + strerror(error)); + unlink(ca_cert_file); + return error; + } + + if (!PEM_write_X509(file, ca_cert)) { + VLOG_ERR("could not bootstrap CA cert: PEM_write_X509 to %s failed: " + "%s", ca_cert_file, ERR_error_string(ERR_get_error(), NULL)); + fclose(file); + unlink(ca_cert_file); + return EIO; + } + + if (fclose(file)) { + int error = errno; + VLOG_ERR("could not bootstrap CA cert: writing %s failed: %s", + ca_cert_file, strerror(error)); + unlink(ca_cert_file); + return error; + } + + VLOG_WARN("successfully bootstrapped CA cert to %s", ca_cert_file); + log_ca_cert(ca_cert_file, ca_cert); + bootstrap_ca_cert = false; + has_ca_cert = true; + + /* SSL_CTX_add_client_CA makes a copy of ca_cert's relevant data. */ + SSL_CTX_add_client_CA(ctx, ca_cert); + + /* SSL_CTX_use_certificate() takes ownership of the certificate passed in. + * 'ca_cert' is owned by sslv->ssl, so we need to duplicate it. */ + ca_cert = X509_dup(ca_cert); + if (!ca_cert) { + out_of_memory(); + } + if (SSL_CTX_load_verify_locations(ctx, ca_cert_file, NULL) != 1) { + VLOG_ERR("SSL_CTX_load_verify_locations: %s", + ERR_error_string(ERR_get_error(), NULL)); + return EPROTO; + } + VLOG_WARN("killing successful connection to retry using CA cert"); + return EPROTO; +} + static int ssl_connect(struct vconn *vconn) { @@ -345,6 +454,21 @@ ssl_connect(struct vconn *vconn) shutdown(sslv->fd, SHUT_RDWR); return EPROTO; } + } else if (bootstrap_ca_cert) { + return do_ca_cert_bootstrap(vconn); + } else if ((SSL_get_verify_mode(sslv->ssl) + & (SSL_VERIFY_NONE | SSL_VERIFY_PEER)) + != SSL_VERIFY_PEER) { + /* Two or more SSL connections completed at the same time while we + * were in bootstrap mode. Only one of these can finish the + * bootstrap successfully. The other one(s) must be rejected + * because they were not verified against the bootstrapped CA + * certificate. (Alternatively we could verify them against the CA + * certificate, but that's more trouble than it's worth. These + * connections will succeed the next time they retry, assuming that + * they have a certificate against the correct CA.) */ + VLOG_ERR("rejecting SSL connection during bootstrap race window"); + return EPROTO; } else { return 0; } @@ -413,8 +537,8 @@ interpret_ssl_error(const char *function, int ret, int error, return EPROTO; } } else { - VLOG_DBG_RL(&rl, "%s: %s", - function, ERR_error_string(queued_error, NULL)); + VLOG_WARN_RL(&rl, "%s: %s", + function, ERR_error_string(queued_error, NULL)); break; } } @@ -422,8 +546,8 @@ interpret_ssl_error(const char *function, int ret, int error, case SSL_ERROR_SSL: { int queued_error = ERR_get_error(); if (queued_error != 0) { - VLOG_DBG_RL(&rl, "%s: %s", - function, ERR_error_string(queued_error, NULL)); + VLOG_WARN_RL(&rl, "%s: %s", + function, ERR_error_string(queued_error, NULL)); } else { VLOG_ERR_RL(&rl, "%s: SSL_ERROR_SSL without queued error", function); @@ -915,30 +1039,167 @@ vconn_ssl_set_certificate_file(const char *file_name) has_certificate = true; } +/* Reads the X509 certificate or certificates in file 'file_name'. On success, + * stores the address of the first element in an array of pointers to + * certificates in '*certs' and the number of certificates in the array in + * '*n_certs', and returns 0. On failure, stores a null pointer in '*certs', 0 + * in '*n_certs', and returns a positive errno value. + * + * The caller is responsible for freeing '*certs'. */ +int +read_cert_file(const char *file_name, X509 ***certs, size_t *n_certs) +{ + FILE *file; + size_t allocated_certs = 0; + + *certs = NULL; + *n_certs = 0; + + file = fopen(file_name, "r"); + if (!file) { + VLOG_ERR("failed to open %s for reading: %s", + file_name, strerror(errno)); + return errno; + } + + for (;;) { + X509 *certificate; + int c; + + /* Read certificate from file. */ + certificate = PEM_read_X509(file, NULL, NULL, NULL); + if (!certificate) { + size_t i; + + VLOG_ERR("PEM_read_X509 failed reading %s: %s", + file_name, ERR_error_string(ERR_get_error(), NULL)); + for (i = 0; i < *n_certs; i++) { + X509_free((*certs)[i]); + } + free(*certs); + *certs = NULL; + *n_certs = 0; + return EIO; + } + + /* Add certificate to array. */ + if (*n_certs >= allocated_certs) { + allocated_certs = 1 + 2 * allocated_certs; + *certs = xrealloc(*certs, sizeof *certs * allocated_certs); + } + (*certs)[(*n_certs)++] = certificate; + + /* Are there additional certificates in the file? */ + do { + c = getc(file); + } while (isspace(c)); + if (c == EOF) { + break; + } + ungetc(c, file); + } + fclose(file); + return 0; +} + + +/* Sets 'file_name' as the name of a file containing one or more X509 + * certificates to send to the peer. Typical use in OpenFlow is to send the CA + * certificate to the peer, which enables a switch to pick up the controller's + * CA certificate on its first connection. */ void -vconn_ssl_set_ca_cert_file(const char *file_name) +vconn_ssl_set_peer_ca_cert_file(const char *file_name) { - STACK_OF(X509_NAME) *ca_list; + X509 **certs; + size_t n_certs; + size_t i; if (ssl_init()) { return; } - /* Set up list of CAs that the server will accept from the client. */ - ca_list = SSL_load_client_CA_file(file_name); - if (ca_list == NULL) { - VLOG_ERR("SSL_load_client_CA_file: %s", - ERR_error_string(ERR_get_error(), NULL)); - return; + if (!read_cert_file(file_name, &certs, &n_certs)) { + for (i = 0; i < n_certs; i++) { + if (SSL_CTX_add_extra_chain_cert(ctx, certs[i]) != 1) { + VLOG_ERR("SSL_CTX_add_extra_chain_cert: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + } + free(certs); } - SSL_CTX_set_client_CA_list(ctx, ca_list); +} - /* Set up CAs for OpenSSL to trust in verifying the peer's certificate. */ - if (SSL_CTX_load_verify_locations(ctx, file_name, NULL) != 1) { - VLOG_ERR("SSL_CTX_load_verify_locations: %s", - ERR_error_string(ERR_get_error(), NULL)); +/* Logs fingerprint of CA certificate 'cert' obtained from 'file_name'. */ +static void +log_ca_cert(const char *file_name, X509 *cert) +{ + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int n_bytes; + struct ds fp; + char *subject; + + ds_init(&fp); + if (!X509_digest(cert, EVP_sha1(), digest, &n_bytes)) { + ds_put_cstr(&fp, ""); + } else { + unsigned int i; + for (i = 0; i < n_bytes; i++) { + if (i) { + ds_put_char(&fp, ':'); + } + ds_put_format(&fp, "%02hhx", digest[i]); + } + } + subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + VLOG_WARN("Trusting CA cert from %s (%s) (fingerprint %s)", file_name, + subject ? subject : "", ds_cstr(&fp)); + free(subject); + ds_destroy(&fp); +} + +/* Sets 'file_name' as the name of the file from which to read the CA + * certificate used to verify the peer within SSL connections. If 'bootstrap' + * is false, the file must exist. If 'bootstrap' is false, then the file is + * read if it is exists; if it does not, then it will be created from the CA + * certificate received from the peer on the first SSL connection. */ +void +vconn_ssl_set_ca_cert_file(const char *file_name, bool bootstrap) +{ + X509 **certs; + size_t n_certs; + struct stat s; + + if (ssl_init()) { return; } - has_ca_cert = true; + if (bootstrap && stat(file_name, &s) && errno == ENOENT) { + bootstrap_ca_cert = true; + ca_cert_file = xstrdup(file_name); + } else if (!read_cert_file(file_name, &certs, &n_certs)) { + size_t i; + + /* Set up list of CAs that the server will accept from the client. */ + for (i = 0; i < n_certs; i++) { + /* SSL_CTX_add_client_CA makes a copy of the relevant data. */ + if (SSL_CTX_add_client_CA(ctx, certs[i]) != 1) { + VLOG_ERR("failed to add client certificate %d from %s: %s", + i, file_name, + ERR_error_string(ERR_get_error(), NULL)); + } else { + log_ca_cert(file_name, certs[i]); + } + X509_free(certs[i]); + } + + /* Set up CAs for OpenSSL to trust in verifying the peer's + * certificate. */ + if (SSL_CTX_load_verify_locations(ctx, file_name, NULL) != 1) { + VLOG_ERR("SSL_CTX_load_verify_locations: %s", + ERR_error_string(ERR_get_error(), NULL)); + return; + } + + has_ca_cert = true; + } } diff --git a/lib/vconn.c b/lib/vconn.c index 1c3b699ce..ee7f67868 100644 --- a/lib/vconn.c +++ b/lib/vconn.c @@ -129,9 +129,10 @@ check_vconn_classes(void) } /* Prints information on active (if 'active') and passive (if 'passive') - * connection methods supported by the vconn. */ + * connection methods supported by the vconn. If 'bootstrap' is true, also + * advertises options to bootstrap the CA certificate. */ void -vconn_usage(bool active, bool passive) +vconn_usage(bool active, bool passive, bool bootstrap UNUSED) { /* Really this should be implemented via callbacks into the vconn * providers, but that seems too heavy-weight to bother with at the @@ -172,6 +173,10 @@ vconn_usage(bool active, bool passive) " -p, --private-key=FILE file with private key\n" " -c, --certificate=FILE file with certificate for private key\n" " -C, --ca-cert=FILE file with peer CA certificate\n"); + if (bootstrap) { + printf(" --bootstrap-ca-cert=FILE file with peer CA certificate " + "to read or create\n"); + } #endif } diff --git a/secchan/secchan.8.in b/secchan/secchan.8.in index c04e8d311..4fd2f24e1 100644 --- a/secchan/secchan.8.in +++ b/secchan/secchan.8.in @@ -341,6 +341,26 @@ private key to identify a trustworthy switch. Specifies a PEM file containing the CA certificate used to verify that the switch is connected to a trustworthy controller. +.TP +\fB--bootstrap-ca-cert=\fIcacert.pem\fR +When \fIcacert.pem\fR exists, this option has the same effect as +\fB-C\fR or \fB--ca-cert\fR. If it does not exist, then \fBsecchan\fR +will attempt to obtain the CA certificate from the controller on its +first SSL connection and save it to the named PEM file. If it is +successful, it will immediately drop the connection and reconnect, and +from then on all SSL connections must be authenticated by a +certificate signed by the CA certificate thus obtained. + +\fBThis option exposes the SSL connection to a man-in-the-middle +attack obtaining the initial CA certificate\fR, but it may be useful +for bootstrapping. + +This option is only useful if the controller sends its CA certificate +as part of the SSL certificate chain. The SSL protocol does not +require the controller to send the CA certificate, but +\fBcontroller\fR(8) can be configured to do so with the +\fB--peer-ca-cert\fR option. + .TP \fB-P\fR[\fIpidfile\fR], \fB--pidfile\fR[\fB=\fIpidfile\fR] Causes a file (by default, \fBsecchan.pid\fR) to be created indicating diff --git a/secchan/secchan.c b/secchan/secchan.c index f00df4a42..e2a9585c1 100644 --- a/secchan/secchan.c +++ b/secchan/secchan.c @@ -1993,7 +1993,8 @@ parse_options(int argc, char *argv[], struct settings *s) OPT_MAX_IDLE, OPT_MAX_BACKOFF, OPT_RATE_LIMIT, - OPT_BURST_LIMIT + OPT_BURST_LIMIT, + OPT_BOOTSTRAP_CA_CERT }; static struct option long_options[] = { {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN}, @@ -2012,7 +2013,10 @@ parse_options(int argc, char *argv[], struct settings *s) {"verbose", optional_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, +#ifdef HAVE_OPENSSL VCONN_SSL_LONG_OPTIONS + {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT}, +#endif {0, 0, 0, 0}, }; char *short_options = long_options_to_short_options(long_options); @@ -2142,8 +2146,14 @@ parse_options(int argc, char *argv[], struct settings *s) vlog_set_verbosity(optarg); break; +#ifdef HAVE_OPENSSL VCONN_SSL_OPTION_HANDLERS + case OPT_BOOTSTRAP_CA_CERT: + vconn_ssl_set_ca_cert_file(optarg, true); + break; +#endif + case '?': exit(EXIT_FAILURE); @@ -2235,7 +2245,7 @@ usage(void) "CONTROLLER is an active OpenFlow connection method; if it is\n" "omitted, then secchan performs controller discovery.\n", program_name, program_name); - vconn_usage(true, true); + vconn_usage(true, true, true); printf("\nController discovery options:\n" " --accept-vconn=REGEX accept matching discovered controllers\n" " --no-resolv-conf do not update /etc/resolv.conf\n" diff --git a/switch/switch.c b/switch/switch.c index 4c750613a..a7624126c 100644 --- a/switch/switch.c +++ b/switch/switch.c @@ -162,7 +162,8 @@ parse_options(int argc, char *argv[]) OPT_MFR_DESC, OPT_HW_DESC, OPT_SW_DESC, - OPT_SERIAL_NUM + OPT_SERIAL_NUM, + OPT_BOOTSTRAP_CA_CERT }; static struct option long_options[] = { @@ -180,7 +181,10 @@ parse_options(int argc, char *argv[]) {"hw-desc", required_argument, 0, OPT_HW_DESC}, {"sw-desc", required_argument, 0, OPT_SW_DESC}, {"serial_num", required_argument, 0, OPT_SERIAL_NUM}, +#ifdef HAVE_OPENSSL VCONN_SSL_LONG_OPTIONS + {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT}, +#endif {0, 0, 0, 0}, }; char *short_options = long_options_to_short_options(long_options); @@ -271,8 +275,14 @@ parse_options(int argc, char *argv[]) listen_pvconn_name = optarg; break; +#ifdef HAVE_OPENSSL VCONN_SSL_OPTION_HANDLERS + case OPT_BOOTSTRAP_CA_CERT: + vconn_ssl_set_ca_cert_file(optarg, true); + break; +#endif + case '?': exit(EXIT_FAILURE); @@ -290,7 +300,7 @@ usage(void) "usage: %s [OPTIONS] CONTROLLER\n" "where CONTROLLER is an active OpenFlow connection method.\n", program_name, program_name); - vconn_usage(true, true); + vconn_usage(true, true, true); printf("\nConfiguration options:\n" " -i, --interfaces=NETDEV[,NETDEV]...\n" " add specified initial switch ports\n" diff --git a/utilities/dpctl.c b/utilities/dpctl.c index eef55a4d2..f957b8225 100644 --- a/utilities/dpctl.c +++ b/utilities/dpctl.c @@ -213,7 +213,7 @@ usage(void) " benchmark VCONN N COUNT bandwidth of COUNT N-byte echos\n" "where each SWITCH is an active OpenFlow connection method.\n", program_name, program_name); - vconn_usage(true, false); + vconn_usage(true, false, false); printf("\nOptions:\n" " -t, --timeout=SECS give up after SECS seconds\n" " -v, --verbose=MODULE[:FACILITY[:LEVEL]] set logging levels\n" -- 2.43.0