]>
Commit | Line | Data |
---|---|---|
064af421 BP |
1 | #! /bin/sh |
2 | ||
9ff33ca7 | 3 | # Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Nicira, Inc. |
a14bc59f BP |
4 | # |
5 | # Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | # you may not use this file except in compliance with the License. | |
7 | # You may obtain a copy of the License at: | |
8 | # | |
9 | # http://www.apache.org/licenses/LICENSE-2.0 | |
10 | # | |
11 | # Unless required by applicable law or agreed to in writing, software | |
12 | # distributed under the License is distributed on an "AS IS" BASIS, | |
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | # See the License for the specific language governing permissions and | |
15 | # limitations under the License. | |
16 | ||
064af421 BP |
17 | set -e |
18 | ||
19 | pkidir='@PKIDIR@' | |
20 | command= | |
21 | prev= | |
22 | force=no | |
23 | batch=no | |
674ebdd1 | 24 | unique_name=no |
064af421 BP |
25 | log='@LOGDIR@/ovs-pki.log' |
26 | keytype=rsa | |
27 | bits=2048 | |
0f5ed573 EM |
28 | |
29 | # OS-specific compatibility routines | |
30 | case $(uname -s) in | |
8be8c95e | 31 | FreeBSD|NetBSD|Darwin) |
0f5ed573 EM |
32 | file_mod_epoch() |
33 | { | |
34 | stat -r "$1" | awk '{print $10}' | |
35 | } | |
36 | ||
37 | file_mod_date() | |
38 | { | |
39 | stat -f '%Sm' "$1" | |
40 | } | |
41 | ||
42 | sha1sum() | |
43 | { | |
44 | sha1 "$@" | |
45 | } | |
46 | ;; | |
47 | *) | |
48 | file_mod_epoch() | |
49 | { | |
50 | date -r "$1" +%s | |
51 | } | |
52 | ||
53 | file_mod_date() | |
54 | { | |
55 | date -r "$1" | |
56 | } | |
57 | ;; | |
58 | esac | |
59 | ||
064af421 BP |
60 | for option; do |
61 | # This option-parsing mechanism borrowed from a Autoconf-generated | |
62 | # configure script under the following license: | |
63 | ||
64 | # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, | |
65 | # 2002, 2003, 2004, 2005, 2006, 2009 Free Software Foundation, Inc. | |
66 | # This configure script is free software; the Free Software Foundation | |
67 | # gives unlimited permission to copy, distribute and modify it. | |
68 | ||
69 | # If the previous option needs an argument, assign it. | |
70 | if test -n "$prev"; then | |
71 | eval $prev=\$option | |
72 | prev= | |
73 | continue | |
74 | fi | |
75 | case $option in | |
76 | *=*) optarg=`expr "X$option" : '[^=]*=\(.*\)'` ;; | |
77 | *) optarg=yes ;; | |
78 | esac | |
79 | ||
80 | case $dashdash$option in | |
81 | --) | |
82 | dashdash=yes ;; | |
83 | -h|--help) | |
84 | cat <<EOF | |
85 | ovs-pki, for managing a simple OpenFlow public key infrastructure | |
86 | usage: $0 [OPTION...] COMMAND [ARG...] | |
87 | ||
88 | The valid stand-alone commands and their arguments are: | |
89 | init Initialize the PKI | |
90 | req NAME Create new private key and certificate request | |
91 | named NAME-privkey.pem and NAME-req.pem, resp. | |
92 | sign NAME [TYPE] Sign switch certificate request NAME-req.pem, | |
93 | producing certificate NAME-cert.pem | |
94 | req+sign NAME [TYPE] Combine the above two steps, producing all three files. | |
95 | verify NAME [TYPE] Checks that NAME-cert.pem is a valid TYPE certificate | |
96 | fingerprint FILE Prints the fingerprint for FILE | |
97 | self-sign NAME Sign NAME-req.pem with NAME-privkey.pem, | |
98 | producing self-signed certificate NAME-cert.pem | |
064af421 BP |
99 | Each TYPE above is a certificate type: 'switch' (default) or 'controller'. |
100 | ||
101 | Options for 'init', 'req', and 'req+sign' only: | |
102 | -k, --key=rsa|dsa Type of keys to use (default: rsa) | |
103 | -B, --bits=NBITS Number of bits in keys (default: 2048). For DSA keys, | |
104 | this has an effect only on 'init'. | |
105 | -D, --dsaparam=FILE File with DSA parameters (DSA only) | |
106 | (default: dsaparam.pem within PKI directory) | |
2562714a | 107 | Options for use with the 'sign' command: |
064af421 BP |
108 | -b, --batch Skip fingerprint verification |
109 | Options that apply to any command: | |
110 | -d, --dir=DIR Directory where the PKI is located | |
111 | (default: $pkidir) | |
112 | -f, --force Continue even if file or directory already exists | |
113 | -l, --log=FILE Log openssl output to FILE (default: ovs-log.log) | |
674ebdd1 | 114 | -u, --unique NAME is unique (don't append UUID/date) |
064af421 | 115 | -h, --help Print this usage message. |
d3e565ce | 116 | -V, --version Display version information. |
064af421 BP |
117 | EOF |
118 | exit 0 | |
119 | ;; | |
d3e565ce BP |
120 | -V|--version) |
121 | echo "ovs-pki (Open vSwitch) @VERSION@" | |
122 | exit 0 | |
123 | ;; | |
064af421 BP |
124 | --di*=*) |
125 | pkidir=$optarg | |
126 | ;; | |
127 | --di*|-d) | |
128 | prev=pkidir | |
129 | ;; | |
130 | --k*=*) | |
131 | keytype=$optarg | |
132 | ;; | |
133 | --k*|-k) | |
134 | prev=keytype | |
135 | ;; | |
136 | --bi*=*) | |
137 | bits=$optarg | |
138 | ;; | |
139 | --bi*|-B) | |
140 | prev=bits | |
141 | ;; | |
142 | --ds*=*) | |
143 | dsaparam=$optarg | |
144 | ;; | |
145 | --ds*|-D) | |
146 | prev=dsaparam | |
147 | ;; | |
148 | --l*=*) | |
149 | log=$optarg | |
150 | ;; | |
151 | --l*|-l) | |
152 | prev=log | |
153 | ;; | |
154 | --force|-f) | |
155 | force=yes | |
156 | ;; | |
157 | --ba*|-b) | |
158 | batch=yes | |
159 | ;; | |
674ebdd1 LR |
160 | --un*|-u) |
161 | unique_name=yes | |
162 | ;; | |
064af421 BP |
163 | -*) |
164 | echo "unrecognized option $option" >&2 | |
165 | exit 1 | |
166 | ;; | |
167 | *) | |
168 | if test -z "$command"; then | |
169 | command=$option | |
170 | elif test -z "${arg1+set}"; then | |
171 | arg1=$option | |
172 | elif test -z "${arg2+set}"; then | |
173 | arg2=$option | |
174 | else | |
175 | echo "$option: only two arguments may be specified" >&2 | |
176 | exit 1 | |
177 | fi | |
178 | ;; | |
179 | esac | |
180 | shift | |
181 | done | |
182 | if test -n "$prev"; then | |
183 | option=--`echo $prev | sed 's/_/-/g'` | |
184 | { echo "$as_me: error: missing argument to $option" >&2 | |
185 | { (exit 1); exit 1; }; } | |
186 | fi | |
187 | if test -z "$command"; then | |
188 | echo "$0: missing command name; use --help for help" >&2 | |
189 | exit 1 | |
190 | fi | |
191 | if test "$keytype" != rsa && test "$keytype" != dsa; then | |
7cdc630f | 192 | echo "$0: argument to -k or --key must be rsa or dsa" >&2 |
064af421 BP |
193 | exit 1 |
194 | fi | |
195 | if test "$bits" -lt 1024; then | |
7cdc630f | 196 | echo "$0: argument to -B or --bits must be at least 1024" >&2 |
064af421 BP |
197 | exit 1 |
198 | fi | |
199 | if test -z "$dsaparam"; then | |
200 | dsaparam=$pkidir/dsaparam.pem | |
201 | fi | |
202 | case $log in | |
d1cec3c6 | 203 | /* | ?:[\\/]*) ;; |
37d03458 | 204 | *) log=`pwd`/$log ;; |
064af421 BP |
205 | esac |
206 | ||
ccc9fc5a BP |
207 | logdir=$(dirname "$log") |
208 | if test ! -d "$logdir"; then | |
03736a67 | 209 | mkdir -p -m750 "$logdir" 2>/dev/null || true |
ccc9fc5a BP |
210 | if test ! -d "$logdir"; then |
211 | echo "$0: log directory $logdir does not exist and cannot be created" >&2 | |
212 | exit 1 | |
213 | fi | |
214 | fi | |
215 | ||
064af421 BP |
216 | if test "$command" = "init"; then |
217 | if test -e "$pkidir" && test "$force" != "yes"; then | |
218 | echo "$0: $pkidir already exists and --force not specified" >&2 | |
219 | exit 1 | |
220 | fi | |
221 | ||
222 | if test ! -d "$pkidir"; then | |
223 | mkdir -p "$pkidir" | |
224 | fi | |
225 | cd "$pkidir" | |
226 | exec 3>>$log | |
227 | ||
228 | if test $keytype = dsa && test ! -e dsaparam.pem; then | |
229 | echo "Generating DSA parameters, please wait..." >&2 | |
230 | openssl dsaparam -out dsaparam.pem $bits 1>&3 2>&3 | |
231 | fi | |
232 | ||
a20d2466 JP |
233 | # Get the current date to add some uniqueness to this certificate |
234 | curr_date=`date +"%Y %b %d %T"` | |
235 | ||
064af421 BP |
236 | # Create the CAs. |
237 | for ca in controllerca switchca; do | |
238 | echo "Creating $ca..." >&2 | |
37d03458 | 239 | oldpwd=`pwd` |
064af421 BP |
240 | mkdir -p $ca |
241 | cd $ca | |
242 | ||
243 | mkdir -p certs crl newcerts | |
244 | mkdir -p -m 0700 private | |
064af421 BP |
245 | touch index.txt |
246 | test -e crlnumber || echo 01 > crlnumber | |
247 | test -e serial || echo 01 > serial | |
248 | ||
249 | # Put DSA parameters in directory. | |
250 | if test $keytype = dsa && test ! -e dsaparam.pem; then | |
251 | cp ../dsaparam.pem . | |
252 | fi | |
253 | ||
a20d2466 | 254 | # Write CA configuration file. |
064af421 | 255 | if test ! -e ca.cnf; then |
a20d2466 | 256 | sed "s/@ca@/$ca/g;s/@curr_date@/$curr_date/g" > ca.cnf <<'EOF' |
064af421 BP |
257 | [ req ] |
258 | prompt = no | |
259 | distinguished_name = req_distinguished_name | |
260 | ||
261 | [ req_distinguished_name ] | |
262 | C = US | |
263 | ST = CA | |
264 | L = Palo Alto | |
265 | O = Open vSwitch | |
266 | OU = @ca@ | |
a20d2466 | 267 | CN = OVS @ca@ CA Certificate (@curr_date@) |
064af421 BP |
268 | |
269 | [ ca ] | |
270 | default_ca = the_ca | |
271 | ||
272 | [ the_ca ] | |
273 | dir = . # top dir | |
274 | database = $dir/index.txt # index file. | |
275 | new_certs_dir = $dir/newcerts # new certs dir | |
276 | certificate = $dir/cacert.pem # The CA cert | |
277 | serial = $dir/serial # serial no file | |
278 | private_key = $dir/private/cakey.pem# CA private key | |
279 | RANDFILE = $dir/private/.rand # random number file | |
d652859b | 280 | default_days = 3650 # how long to certify for |
064af421 | 281 | default_crl_days= 30 # how long before next CRL |
29dd784d | 282 | default_md = sha512 # message digest to use |
064af421 BP |
283 | policy = policy # default policy |
284 | email_in_dn = no # Don't add the email into cert DN | |
285 | name_opt = ca_default # Subject name display option | |
286 | cert_opt = ca_default # Certificate display option | |
c8efec6e | 287 | copy_extensions = copy # Copy extensions from request |
c6c9e1e3 | 288 | unique_subject = no # Allow certs with duplicate subjects |
064af421 BP |
289 | |
290 | # For the CA policy | |
291 | [ policy ] | |
292 | countryName = optional | |
293 | stateOrProvinceName = optional | |
294 | organizationName = match | |
295 | organizationalUnitName = optional | |
296 | commonName = supplied | |
297 | emailAddress = optional | |
c8efec6e QX |
298 | |
299 | # For the x509v3 extension | |
300 | [ ca_cert ] | |
301 | basicConstraints=CA:true | |
302 | ||
303 | [ usr_cert ] | |
304 | basicConstraints=CA:false | |
064af421 BP |
305 | EOF |
306 | fi | |
307 | ||
308 | # Create certificate authority. | |
309 | if test $keytype = dsa; then | |
310 | newkey=dsa:dsaparam.pem | |
311 | else | |
312 | newkey=rsa:$bits | |
313 | fi | |
314 | openssl req -config ca.cnf -nodes \ | |
315 | -newkey $newkey -keyout private/cakey.pem -out careq.pem \ | |
316 | 1>&3 2>&3 | |
c8efec6e QX |
317 | openssl ca -config ca.cnf -create_serial \ |
318 | -extensions ca_cert -out cacert.pem \ | |
d652859b | 319 | -days 3650 -batch -keyfile private/cakey.pem -selfsign \ |
064af421 BP |
320 | -infiles careq.pem 1>&3 2>&3 |
321 | chmod 0700 private/cakey.pem | |
322 | ||
323 | cd "$oldpwd" | |
324 | done | |
325 | exit 0 | |
326 | fi | |
327 | ||
328 | one_arg() { | |
329 | if test -z "$arg1" || test -n "$arg2"; then | |
330 | echo "$0: $command must have exactly one argument; use --help for help" >&2 | |
331 | exit 1 | |
332 | fi | |
333 | } | |
334 | ||
064af421 BP |
335 | one_or_two_args() { |
336 | if test -z "$arg1"; then | |
337 | echo "$0: $command must have one or two arguments; use --help for help" >&2 | |
338 | exit 1 | |
339 | fi | |
340 | } | |
341 | ||
342 | must_not_exist() { | |
343 | if test -e "$1" && test "$force" != "yes"; then | |
344 | echo "$0: $1 already exists and --force not supplied" >&2 | |
345 | exit 1 | |
346 | fi | |
347 | } | |
348 | ||
064af421 BP |
349 | make_tmpdir() { |
350 | TMP=/tmp/ovs-pki.tmp$$ | |
351 | rm -rf $TMP | |
352 | trap "rm -rf $TMP" 0 | |
353 | mkdir -m 0700 $TMP | |
354 | } | |
355 | ||
356 | fingerprint() { | |
bb60fa74 BP |
357 | file=$1 |
358 | name=${1-$2} | |
0f5ed573 | 359 | date=$(file_mod_date "$file") |
00961f7c | 360 | if grep -e '-BEGIN CERTIFICATE-' "$file" > /dev/null; then |
064af421 BP |
361 | fingerprint=$(openssl x509 -noout -in "$file" -fingerprint | |
362 | sed 's/SHA1 Fingerprint=//' | tr -d ':') | |
363 | else | |
364 | fingerprint=$(sha1sum "$file" | awk '{print $1}') | |
365 | fi | |
366 | printf "$name\\t$date\\n" | |
367 | case $file in | |
368 | $fingerprint*) | |
369 | printf "\\t(correct fingerprint in filename)\\n" | |
370 | ;; | |
371 | *) | |
372 | printf "\\tfingerprint $fingerprint\\n" | |
373 | ;; | |
374 | esac | |
375 | } | |
376 | ||
377 | verify_fingerprint() { | |
378 | fingerprint "$@" | |
379 | if test $batch != yes; then | |
380 | echo "Does fingerprint match? (yes/no)" | |
381 | read answer | |
382 | if test "$answer" != yes; then | |
383 | echo "Match failure, aborting" >&2 | |
384 | exit 1 | |
385 | fi | |
386 | fi | |
387 | } | |
388 | ||
389 | check_type() { | |
390 | if test x = x"$1"; then | |
391 | type=switch | |
392 | elif test "$1" = switch || test "$1" = controller; then | |
393 | type=$1 | |
394 | else | |
395 | echo "$0: type argument must be 'switch' or 'controller'" >&2 | |
396 | exit 1 | |
397 | fi | |
398 | } | |
399 | ||
400 | parse_age() { | |
401 | number=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\1/') | |
402 | unit=$(echo $1 | sed 's/^\([0-9]\+\)\([[:alpha:]]\+\)/\2/') | |
403 | case $unit in | |
404 | s) | |
405 | factor=1 | |
406 | ;; | |
407 | min) | |
408 | factor=60 | |
409 | ;; | |
410 | h) | |
411 | factor=3600 | |
412 | ;; | |
413 | day) | |
414 | factor=86400 | |
415 | ;; | |
416 | *) | |
417 | echo "$1: age not in the form Ns, Nmin, Nh, Nday (e.g. 1day)" >&2 | |
418 | exit 1 | |
419 | ;; | |
420 | esac | |
421 | echo $(($number * $factor)) | |
422 | } | |
423 | ||
424 | must_exist() { | |
425 | if test ! -e "$1"; then | |
426 | echo "$0: $1 does not exist" >&2 | |
427 | exit 1 | |
428 | fi | |
429 | } | |
430 | ||
431 | pkidir_must_exist() { | |
432 | if test ! -e "$pkidir"; then | |
433 | echo "$0: $pkidir does not exist (need to run 'init' or use '--dir'?)" >&2 | |
434 | exit 1 | |
435 | elif test ! -d "$pkidir"; then | |
436 | echo "$0: $pkidir is not a directory" >&2 | |
437 | exit 1 | |
438 | fi | |
439 | } | |
440 | ||
441 | make_request() { | |
442 | must_not_exist "$arg1-privkey.pem" | |
443 | must_not_exist "$arg1-req.pem" | |
444 | make_tmpdir | |
674ebdd1 LR |
445 | if test $unique_name != yes; then |
446 | # Use uuidgen or date to create unique subject DNs. | |
447 | unique=`(uuidgen) 2>/dev/null` || unique=`date +"%Y %b %d %T"` | |
448 | cn="$arg1 id:$unique" | |
449 | else | |
450 | cn="$arg1" | |
451 | fi | |
064af421 BP |
452 | cat > "$TMP/req.cnf" <<EOF |
453 | [ req ] | |
454 | prompt = no | |
455 | distinguished_name = req_distinguished_name | |
c8efec6e | 456 | req_extensions = v3_req |
064af421 BP |
457 | |
458 | [ req_distinguished_name ] | |
459 | C = US | |
460 | ST = CA | |
461 | L = Palo Alto | |
462 | O = Open vSwitch | |
463 | OU = Open vSwitch certifier | |
674ebdd1 | 464 | CN = $cn |
c8efec6e QX |
465 | |
466 | [ v3_req ] | |
467 | subjectAltName = DNS:$cn | |
064af421 BP |
468 | EOF |
469 | if test $keytype = rsa; then | |
99e5e05d BP |
470 | (umask 077 && openssl genrsa -out "$1-privkey.pem" $bits) 1>&3 2>&3 \ |
471 | || exit $? | |
064af421 BP |
472 | else |
473 | must_exist "$dsaparam" | |
99e5e05d BP |
474 | (umask 077 && openssl gendsa -out "$1-privkey.pem" "$dsaparam") \ |
475 | 1>&3 2>&3 || exit $? | |
064af421 | 476 | fi |
99e5e05d BP |
477 | openssl req -config "$TMP/req.cnf" -new -text \ |
478 | -key "$1-privkey.pem" -out "$1-req.pem" 1>&3 2>&3 | |
064af421 BP |
479 | } |
480 | ||
481 | sign_request() { | |
482 | must_exist "$1" | |
483 | must_not_exist "$2" | |
484 | pkidir_must_exist | |
485 | ||
8492d147 | 486 | case "$1" in |
d1cec3c6 | 487 | /* | ?:[\\/]*) |
8492d147 GS |
488 | request_file="$1" |
489 | ;; | |
490 | *) | |
491 | request_file="`pwd`/$1" | |
492 | ;; | |
493 | esac | |
494 | ||
064af421 | 495 | (cd "$pkidir/${type}ca" && |
c8efec6e | 496 | openssl ca -config ca.cnf -extensions usr_cert -batch -in "$request_file") \ |
8492d147 | 497 | > "$2.tmp$$" 2>&3 |
064af421 BP |
498 | mv "$2.tmp$$" "$2" |
499 | } | |
500 | ||
501 | glob() { | |
bb60fa74 | 502 | files=$(echo $1) |
064af421 BP |
503 | if test "$files" != "$1"; then |
504 | echo "$files" | |
505 | fi | |
506 | } | |
507 | ||
508 | exec 3>>$log || true | |
509 | if test "$command" = req; then | |
510 | one_arg | |
511 | ||
512 | make_request "$arg1" | |
513 | fingerprint "$arg1-req.pem" | |
514 | elif test "$command" = sign; then | |
515 | one_or_two_args | |
516 | check_type "$arg2" | |
517 | verify_fingerprint "$arg1-req.pem" | |
518 | ||
af32ce7a | 519 | sign_request "$arg1-req.pem" "$arg1-cert.pem" |
064af421 BP |
520 | elif test "$command" = req+sign; then |
521 | one_or_two_args | |
522 | check_type "$arg2" | |
523 | ||
524 | pkidir_must_exist | |
525 | make_request "$arg1" | |
526 | sign_request "$arg1-req.pem" "$arg1-cert.pem" | |
527 | fingerprint "$arg1-req.pem" | |
528 | elif test "$command" = verify; then | |
529 | one_or_two_args | |
530 | must_exist "$arg1-cert.pem" | |
531 | check_type "$arg2" | |
532 | ||
533 | pkidir_must_exist | |
534 | openssl verify -CAfile "$pkidir/${type}ca/cacert.pem" "$arg1-cert.pem" | |
535 | elif test "$command" = fingerprint; then | |
536 | one_arg | |
537 | ||
538 | fingerprint "$arg1" | |
539 | elif test "$command" = self-sign; then | |
540 | one_arg | |
541 | must_exist "$arg1-req.pem" | |
542 | must_exist "$arg1-privkey.pem" | |
543 | must_not_exist "$arg1-cert.pem" | |
c8efec6e QX |
544 | make_tmpdir |
545 | cat > "$TMP/v3.ext" <<EOF | |
546 | subjectAltName = DNS:$arg1 | |
547 | EOF | |
064af421 | 548 | |
99e5e05d BP |
549 | # Create both the private key and certificate with restricted permissions. |
550 | (umask 077 && \ | |
551 | openssl x509 -in "$arg1-req.pem" -out "$arg1-cert.pem.tmp" \ | |
c8efec6e QX |
552 | -signkey "$arg1-privkey.pem" -req -days 3650 -text \ |
553 | -extfile $TMP/v3.ext) 2>&3 || exit $? | |
99e5e05d BP |
554 | |
555 | # Reset the permissions on the certificate to the user's default. | |
556 | cat "$arg1-cert.pem.tmp" > "$arg1-cert.pem" | |
557 | rm -f "$arg1-cert.pem.tmp" | |
064af421 BP |
558 | else |
559 | echo "$0: $command command unknown; use --help for help" >&2 | |
560 | exit 1 | |
561 | fi |