| 1 | #!@PERL@ |
|---|
| 2 | use strict; |
|---|
| 3 | |
|---|
| 4 | # validity period for root certificates -- default is 2038, the best we can do for now |
|---|
| 5 | my $root_sign_period = int(((1<<31) - time()) / 86400); |
|---|
| 6 | |
|---|
| 7 | # but less so for client certificates |
|---|
| 8 | my $sign_period = '5000'; |
|---|
| 9 | |
|---|
| 10 | # check and get command line parameters |
|---|
| 11 | if($#ARGV < 1) |
|---|
| 12 | { |
|---|
| 13 | print <<__E; |
|---|
| 14 | |
|---|
| 15 | bbstored certificates utility. |
|---|
| 16 | |
|---|
| 17 | Bad command line parameters. |
|---|
| 18 | Usage: |
|---|
| 19 | bbstored-certs certs-dir command [arguments] |
|---|
| 20 | |
|---|
| 21 | certs-dir is the directory holding the root keys and certificates for the backup system |
|---|
| 22 | command is the action to perform, taking parameters. |
|---|
| 23 | |
|---|
| 24 | Commands are |
|---|
| 25 | |
|---|
| 26 | init |
|---|
| 27 | -- generate initial root certificates (certs-dir must not already exist) |
|---|
| 28 | sign certificate-name |
|---|
| 29 | -- sign a client certificate |
|---|
| 30 | sign-server certificate-name |
|---|
| 31 | -- sign a server certificate |
|---|
| 32 | |
|---|
| 33 | Signing requires confirmation that the certificate is correct and should be signed. |
|---|
| 34 | |
|---|
| 35 | __E |
|---|
| 36 | exit(1); |
|---|
| 37 | } |
|---|
| 38 | |
|---|
| 39 | # check for OPENSSL_CONF environment var being set |
|---|
| 40 | if(exists $ENV{'OPENSSL_CONF'}) |
|---|
| 41 | { |
|---|
| 42 | print <<__E; |
|---|
| 43 | |
|---|
| 44 | --------------------------------------- |
|---|
| 45 | |
|---|
| 46 | WARNING: |
|---|
| 47 | You have the OPENSSL_CONF environment variable set. |
|---|
| 48 | Use of non-standard openssl configs may cause problems. |
|---|
| 49 | |
|---|
| 50 | --------------------------------------- |
|---|
| 51 | |
|---|
| 52 | __E |
|---|
| 53 | } |
|---|
| 54 | |
|---|
| 55 | # directory structure: |
|---|
| 56 | # |
|---|
| 57 | # roots/ |
|---|
| 58 | # clientCA.pem -- root certificate for client (used on server) |
|---|
| 59 | # serverCA.pem -- root certificate for servers (used on clients) |
|---|
| 60 | # keys/ |
|---|
| 61 | # clientRootKey.pem -- root key for clients |
|---|
| 62 | # serverRootKey.pem -- root key for servers |
|---|
| 63 | # servers/ |
|---|
| 64 | # hostname.pem -- certificate for server 'hostname' |
|---|
| 65 | # clients/ |
|---|
| 66 | # account.pem -- certficiate for account 'account' (ID in hex) |
|---|
| 67 | # |
|---|
| 68 | |
|---|
| 69 | |
|---|
| 70 | # check parameters |
|---|
| 71 | my ($cert_dir,$command,@args) = @ARGV; |
|---|
| 72 | |
|---|
| 73 | # check directory exists |
|---|
| 74 | if($command ne 'init') |
|---|
| 75 | { |
|---|
| 76 | if(!-d $cert_dir) |
|---|
| 77 | { |
|---|
| 78 | die "$cert_dir does not exist"; |
|---|
| 79 | } |
|---|
| 80 | } |
|---|
| 81 | |
|---|
| 82 | # run command |
|---|
| 83 | if($command eq 'init') {&cmd_init;} |
|---|
| 84 | elsif($command eq 'sign') {&cmd_sign;} |
|---|
| 85 | elsif($command eq 'sign-server') {&cmd_sign_server;} |
|---|
| 86 | else |
|---|
| 87 | { |
|---|
| 88 | die "Unknown command $command" |
|---|
| 89 | } |
|---|
| 90 | |
|---|
| 91 | sub cmd_init |
|---|
| 92 | { |
|---|
| 93 | # create directories |
|---|
| 94 | unless(mkdir($cert_dir,0700) |
|---|
| 95 | && mkdir($cert_dir.'/roots',0700) |
|---|
| 96 | && mkdir($cert_dir.'/keys',0700) |
|---|
| 97 | && mkdir($cert_dir.'/servers',0700) |
|---|
| 98 | && mkdir($cert_dir.'/clients',0700)) |
|---|
| 99 | { |
|---|
| 100 | die "Failed to create directory structure" |
|---|
| 101 | } |
|---|
| 102 | |
|---|
| 103 | # create root keys and certrs |
|---|
| 104 | cmd_init_create_root('client'); |
|---|
| 105 | cmd_init_create_root('server'); |
|---|
| 106 | } |
|---|
| 107 | |
|---|
| 108 | sub cmd_init_create_root |
|---|
| 109 | { |
|---|
| 110 | my $entity = $_[0]; |
|---|
| 111 | |
|---|
| 112 | my $cert = "$cert_dir/roots/".$entity.'CA.pem'; |
|---|
| 113 | my $serial = "$cert_dir/roots/".$entity.'CA.srl'; |
|---|
| 114 | my $key = "$cert_dir/keys/".$entity.'RootKey.pem'; |
|---|
| 115 | my $csr = "$cert_dir/keys/".$entity.'RootCSR.pem'; |
|---|
| 116 | |
|---|
| 117 | # generate key |
|---|
| 118 | if(system("openssl genrsa -out $key 2048") != 0) |
|---|
| 119 | { |
|---|
| 120 | die "Couldn't generate private key." |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | # make CSR |
|---|
| 124 | die "Couldn't run openssl for CSR generation" unless |
|---|
| 125 | open(CSR,"|openssl req -new -key $key -sha1 -out $csr"); |
|---|
| 126 | print CSR <<__E; |
|---|
| 127 | . |
|---|
| 128 | . |
|---|
| 129 | . |
|---|
| 130 | . |
|---|
| 131 | . |
|---|
| 132 | Backup system $entity root |
|---|
| 133 | . |
|---|
| 134 | . |
|---|
| 135 | . |
|---|
| 136 | |
|---|
| 137 | __E |
|---|
| 138 | close CSR; |
|---|
| 139 | print "\n\n"; |
|---|
| 140 | die "Certificate request wasn't created.\n" unless -f $csr; |
|---|
| 141 | |
|---|
| 142 | # sign it to make a self-signed root CA key |
|---|
| 143 | if(system("openssl x509 -req -in $csr -sha1 -extensions v3_ca -signkey $key -out $cert -days $root_sign_period") != 0) |
|---|
| 144 | { |
|---|
| 145 | die "Couldn't generate root certificate." |
|---|
| 146 | } |
|---|
| 147 | |
|---|
| 148 | # write the initial serial number |
|---|
| 149 | open SERIAL,">$serial" or die "Can't open $serial for writing"; |
|---|
| 150 | print SERIAL "00\n"; |
|---|
| 151 | close SERIAL; |
|---|
| 152 | } |
|---|
| 153 | |
|---|
| 154 | sub cmd_sign |
|---|
| 155 | { |
|---|
| 156 | my $csr = $args[0]; |
|---|
| 157 | |
|---|
| 158 | if(!-f $csr) |
|---|
| 159 | { |
|---|
| 160 | die "$csr does not exist"; |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | # get the common name specified in this certificate |
|---|
| 164 | my $common_name = get_csr_common_name($csr); |
|---|
| 165 | |
|---|
| 166 | # look OK? |
|---|
| 167 | unless($common_name =~ m/\ABACKUP-([A-Fa-f0-9]+)\Z/) |
|---|
| 168 | { |
|---|
| 169 | die "The certificate presented does not appear to be a backup client certificate" |
|---|
| 170 | } |
|---|
| 171 | |
|---|
| 172 | my $acc = $1; |
|---|
| 173 | |
|---|
| 174 | # check against filename |
|---|
| 175 | if(!($csr =~ m/(\A|\/)([A-Fa-f0-9]+)-/) || $2 ne $acc) |
|---|
| 176 | { |
|---|
| 177 | die "Certificate request filename does not match name in certificate ($common_name)" |
|---|
| 178 | } |
|---|
| 179 | |
|---|
| 180 | print <<__E; |
|---|
| 181 | |
|---|
| 182 | This certificate is for backup account |
|---|
| 183 | |
|---|
| 184 | $acc |
|---|
| 185 | |
|---|
| 186 | Ensure this matches the account number you are expecting. The filename is |
|---|
| 187 | |
|---|
| 188 | $csr |
|---|
| 189 | |
|---|
| 190 | which should include this account number, and additionally, you should check |
|---|
| 191 | that you received it from the right person. |
|---|
| 192 | |
|---|
| 193 | Signing the wrong certificate compromises the security of your backup system. |
|---|
| 194 | |
|---|
| 195 | Would you like to sign this certificate? (type 'yes' to confirm) |
|---|
| 196 | __E |
|---|
| 197 | |
|---|
| 198 | return unless get_confirmation(); |
|---|
| 199 | |
|---|
| 200 | # out certificate |
|---|
| 201 | my $out_cert = "$cert_dir/clients/$acc"."-cert.pem"; |
|---|
| 202 | |
|---|
| 203 | # sign it! |
|---|
| 204 | if(system("openssl x509 -req -in $csr -sha1 -extensions usr_crt -CA $cert_dir/roots/clientCA.pem -CAkey $cert_dir/keys/clientRootKey.pem -out $out_cert -days $sign_period") != 0) |
|---|
| 205 | { |
|---|
| 206 | die "Signing failed" |
|---|
| 207 | } |
|---|
| 208 | |
|---|
| 209 | # tell user what to do next |
|---|
| 210 | print <<__E; |
|---|
| 211 | |
|---|
| 212 | |
|---|
| 213 | Certificate signed. |
|---|
| 214 | |
|---|
| 215 | Send the files |
|---|
| 216 | |
|---|
| 217 | $out_cert |
|---|
| 218 | $cert_dir/roots/serverCA.pem |
|---|
| 219 | |
|---|
| 220 | to the client. |
|---|
| 221 | |
|---|
| 222 | __E |
|---|
| 223 | } |
|---|
| 224 | |
|---|
| 225 | sub cmd_sign_server |
|---|
| 226 | { |
|---|
| 227 | my $csr = $args[0]; |
|---|
| 228 | |
|---|
| 229 | if(!-f $csr) |
|---|
| 230 | { |
|---|
| 231 | die "$csr does not exist"; |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | # get the common name specified in this certificate |
|---|
| 235 | my $common_name = get_csr_common_name($csr); |
|---|
| 236 | |
|---|
| 237 | # look OK? |
|---|
| 238 | if($common_name !~ m/\A[-a-zA-Z0-9.]+\Z/) |
|---|
| 239 | { |
|---|
| 240 | die "Invalid server name" |
|---|
| 241 | } |
|---|
| 242 | |
|---|
| 243 | print <<__E; |
|---|
| 244 | |
|---|
| 245 | This certificate is for backup server |
|---|
| 246 | |
|---|
| 247 | $common_name |
|---|
| 248 | |
|---|
| 249 | Signing the wrong certificate compromises the security of your backup system. |
|---|
| 250 | |
|---|
| 251 | Would you like to sign this certificate? (type 'yes' to confirm) |
|---|
| 252 | __E |
|---|
| 253 | |
|---|
| 254 | return unless get_confirmation(); |
|---|
| 255 | |
|---|
| 256 | # out certificate |
|---|
| 257 | my $out_cert = "$cert_dir/servers/$common_name"."-cert.pem"; |
|---|
| 258 | |
|---|
| 259 | # sign it! |
|---|
| 260 | if(system("openssl x509 -req -in $csr -sha1 -extensions usr_crt -CA $cert_dir/roots/serverCA.pem -CAkey $cert_dir/keys/serverRootKey.pem -out $out_cert -days $sign_period") != 0) |
|---|
| 261 | { |
|---|
| 262 | die "Signing failed" |
|---|
| 263 | } |
|---|
| 264 | |
|---|
| 265 | # tell user what to do next |
|---|
| 266 | print <<__E; |
|---|
| 267 | |
|---|
| 268 | |
|---|
| 269 | Certificate signed. |
|---|
| 270 | |
|---|
| 271 | Install the files |
|---|
| 272 | |
|---|
| 273 | $out_cert |
|---|
| 274 | $cert_dir/roots/clientCA.pem |
|---|
| 275 | |
|---|
| 276 | on the server. |
|---|
| 277 | |
|---|
| 278 | __E |
|---|
| 279 | } |
|---|
| 280 | |
|---|
| 281 | |
|---|
| 282 | sub get_csr_common_name |
|---|
| 283 | { |
|---|
| 284 | my $csr = $_[0]; |
|---|
| 285 | |
|---|
| 286 | open CSRTEXT,"openssl req -text -in $csr |" or die "Can't open openssl for reading"; |
|---|
| 287 | |
|---|
| 288 | my $subject; |
|---|
| 289 | while(<CSRTEXT>) |
|---|
| 290 | { |
|---|
| 291 | $subject = $1 if m/Subject:.+?CN=([-\.\w]+)/ |
|---|
| 292 | } |
|---|
| 293 | close CSRTEXT; |
|---|
| 294 | |
|---|
| 295 | if($subject eq '') |
|---|
| 296 | { |
|---|
| 297 | die "No subject found in CSR $csr" |
|---|
| 298 | } |
|---|
| 299 | |
|---|
| 300 | return $subject |
|---|
| 301 | } |
|---|
| 302 | |
|---|
| 303 | sub get_confirmation() |
|---|
| 304 | { |
|---|
| 305 | my $line = <STDIN>; |
|---|
| 306 | chomp $line; |
|---|
| 307 | if(lc $line ne 'yes') |
|---|
| 308 | { |
|---|
| 309 | print "CANCELLED\n"; |
|---|
| 310 | return 0; |
|---|
| 311 | } |
|---|
| 312 | |
|---|
| 313 | return 1; |
|---|
| 314 | } |
|---|
| 315 | |
|---|
| 316 | |
|---|
| 317 | |
|---|
| 318 | |
|---|
| 319 | |
|---|