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