/usr/lib/postfix/sbin/postfix-tls-script is in postfix 3.1.0-3.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 | #!/bin/sh
#++
# NAME
# postfix-tls 1
# SUMMARY
# Postfix TLS management
# SYNOPSIS
# \fBpostfix tls\fR \fIsubcommand\fR
# DESCRIPTION
# The "\fBpostfix tls \fIsubcommand\fR" feature enables
# opportunistic TLS in the Postfix SMTP client or server, and
# manages Postfix SMTP server private keys and certificates.
#
# The following subcommands are available:
# .IP "\fBenable-client\fR [\fB-r \fIrandsource\fR]"
# Enable opportunistic TLS in the Postfix SMTP client, if all
# SMTP client TLS settings are at their default values.
# Otherwise, suggest parameter settings without making any
# changes.
# .sp
# Specify \fIrandsource\fR to update the value of the
# \fBtls_random_source\fR configuration parameter (typically,
# /dev/urandom). Prepend \fBdev:\fR to device paths or
# \fBegd:\fR to EGD socket paths.
# .sp
# See also the \fBall-default-client\fR subcommand.
# .IP "\fBenable-server\fR [\fB-r \fIrandsource\fR] [\fB-a \fIalgorithm\fR] [\fB-b \fIbits\fR] [\fIhostname\fB...\fR]"
# Create a new private key and self-signed server certificate
# and enable opportunistic TLS in the Postfix SMTP server,
# if all SMTP server TLS settings are at their default values.
# Otherwise, suggest parameter settings without making any
# changes.
# .sp
# The \fIrandsource\fR parameter is as with \fBenable-client\fR
# above, and the remaining options are as with \fBnew-server-key\fR
# below.
# .sp
# See also the \fBall-default-server\fR subcommand.
# .IP "\fBnew-server-key\fR [\fB-a \fIalgorithm\fR] [\fB-b \fIbits\fR] [\fIhostname\fB...\fR]"
# Create a new private key and self-signed server certificate,
# but do not deploy them. Log and display commands to deploy
# the new key and corresponding certificate. Also log and
# display commands to output a corresponding CSR or TLSA
# records which may be needed to obtain a CA certificate or
# to update DNS before the new key can be deployed.
# .sp
# The \fIalgorithm\fR defaults to \fBrsa\fR, and \fIbits\fR
# defaults to 2048. If you choose the \fBecdsa\fR \fIalgorithm\fR
# then \fIbits\fR will be an EC curve name (by default
# \fBsecp256r1\fR, also known as prime256v1). Curves other
# than \fBsecp256r1\fR, \fBsecp384r1\fR or \fBsecp521r1\fR
# are unlikely to be widely interoperable. When generating
# EC keys, use one of these three. DSA keys are obsolete and
# are not supported.
# .sp
# Note: ECDSA support requires OpenSSL 1.0.0 or later and may
# not be available on your system. Not all client systems
# will support ECDSA, so you'll generally want to deploy both
# RSA and ECDSA certificates to make use of ECDSA with
# compatible clients and RSA with the rest. If you want to
# deploy certificate chains with intermediate CAs for both
# RSA and ECDSA, you'll want at least OpenSSL 1.0.2, as earlier
# versions may not handle multiple chain files correctly.
# .sp
# The first \fIhostname\fR argument will be the \fBCommonName\fR
# of both the subject and issuer of the self-signed certificate.
# It, and any additional \fIhostname\fR arguments, will also
# be listed as DNS alternative names in the certificate. If
# no \fIhostname\fR is provided the value of the \fBmyhostname\fR
# main.cf parameter will be used.
# .sp
# For RSA, the generated private key and certificate files
# are named \fBkey-\fIyyyymmdd-hhmmss\fB.pem\fR and
# \fBcert-\fIyyyymmdd-hhmmss\fB.pem\fR, where \fIyyyymmdd\fR
# is the calendar date and \fIhhmmss\fR is the time of day
# in UTC. For ECDSA, the file names start with \fBeckey-\fR
# and \fBeccert-\fR instead of \fBkey-\fR and \fBcert-\fR
# respectively.
# .sp
# Before deploying the new key and certificate with DANE,
# update the DNS with new DANE TLSA records, then wait for
# secondary nameservers to update and then for stale records
# in remote DNS caches to expire.
# .sp
# Before deploying a new CA certificate make sure to include
# all the required intermediate issuing CA certificates in
# the certificate chain file. The server certificate must
# be the first certificate in the chain file. Overwrite and
# deploy the file with the original self-signed certificate
# that was generated together with the key.
# .IP "\fBnew-server-cert\fR [\fB-a \fIalgorithm\fR] [\fB-b \fIbits\fR] [\fIhostname\fB...\fR]"
# This is just like \fBnew-server-key\fR except that, rather
# than generating a new private key, any currently deployed
# private key is copied to the new key file. Thus if you're
# publishing DANE TLSA "3 1 1" or "3 1 2" records, there is
# no need to update DNS records. The \fIalgorithm\fR and
# \fIbits\fR arguments are used only if no key of the same
# algorithm is already configured.
# .sp
# This command is rarely needed, because the self-signed
# certificates generated have a 100-year nominal expiration
# time. The underlying public key algorithms may well be
# obsoleted by quantum computers long before then.
# .sp
# The most plausible reason for using this command is when
# the system hostname changes, and you'd like the name in the
# certificate to match the new hostname (not required for
# DANE "3 1 1", but some needlessly picky non-DANE opportunistic
# TLS clients may log warnings or even refuse to communicate).
# .IP "\fBdeploy-server-cert \fIcertfile\fB \fIkeyfile\fR"
# This subcommand deploys the certificates in \fIcertfile\fR
# and private key in \fIkeyfile\fR (which are typically
# generated by the commands above, which will also log and
# display the full command needed to deploy the generated key
# and certificate). After the new certificate and key are
# deployed any obsolete keys and certificates may be removed
# by hand. The \fIkeyfile\fR and \fIcertfile\fR filenames
# may be relative to the Postfix configuration directory.
# .IP "\fBoutput-server-csr\fR [\fB-k \fIkeyfile\fR] [\fIhostname\fB...\fR]"
# Write to stdout a certificate signing request (CSR) for the
# specified \fIkeyfile\fR.
# .sp
# Instead of an absolute pathname or a pathname relative to
# $config_directory, \fIkeyfile\fR may specify one of the
# supported key algorithm names (see "\fBpostconf -T
# public-key-algorithms\fR"). In that case, the corresponding
# setting from main.cf is used to locate the \fIkeyfile\fR.
# The default \fIkeyfile\fR value is \fBrsa\fR.
# .sp
# Zero or more \fIhostname\fR values can be specified. The
# default \fIhostname\fR is the value of \fBmyhostname\fR
# main.cf parameter.
# .IP "\fBoutput-server-tlsa\fR [\fB-h \fIhostname\fR] [\fIkeyfile\fB...\fR]"
# Write to stdout a DANE TLSA RRset suitable for a port 25
# SMTP server on host \fIhostname\fR with keys from any of
# the specified \fIkeyfile\fR values. The default \fIhostname\fR
# is the value of the \fBmyhostname\fR main.cf parameter.
# .sp
# Instead of absolute pathnames or pathnames relative to
# $config_directory, the \fIkeyfile\fR list may specify
# names of supported public key algorithms (see "\fBpostconf
# -T public-key-algorithms\fR"). In that case, the actual
# \fIkeyfile\fR list uses the values of the corresponding
# Postfix server TLS key file parameters. If a parameter
# value is empty or equal to \fBnone\fR, then no TLSA record
# is output for that algorithm.
# .sp
# The default \fIkeyfile\fR list consists of the two supported
# algorithms \fBrsa\fR and \fBecdsa\fR.
# AUXILIARY COMMANDS
# .IP "\fBall-default-client\fR"
# Exit with status 0 (success) if all SMTP client TLS settings are
# at their default values. Otherwise, exit with a non-zero status.
# This is typically used as follows:
# .sp
# \fBpostfix tls all-default-client &&
# postfix tls enable-tls-client\fR
# .IP "\fBall-default-server\fR"
# Exit with status 0 (success) if all SMTP server TLS settings are
# at their default values. Otherwise, exit with a non-zero status.
# This is typically used as follows:
# .sp
# \fBpostfix tls all-default-server &&
# postfix tls enable-tls-server\fR
# CONFIGURATION PARAMETERS
# .ad
# .fi
# The "\fBpostfix tls \fIsubcommand\fR" feature reads
# or updates the following configuration parameters.
# .IP "\fBcommand_directory (see 'postconf -d' output)\fR"
# The location of all postfix administrative commands.
# .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
# The default location of the Postfix main.cf and master.cf
# configuration files.
# .IP "\fBopenssl_path (openssl)\fR"
# The location of the OpenSSL command line program \fBopenssl\fR(1).
# .IP "\fBsmtp_tls_loglevel (0)\fR"
# Enable additional Postfix SMTP client logging of TLS activity.
# .IP "\fBsmtp_tls_security_level (empty)\fR"
# The default SMTP TLS security level for the Postfix SMTP client;
# when a non-empty value is specified, this overrides the obsolete
# parameters smtp_use_tls, smtp_enforce_tls, and smtp_tls_enforce_peername.
# .IP "\fBsmtp_tls_session_cache_database (empty)\fR"
# Name of the file containing the optional Postfix SMTP client
# TLS session cache.
# .IP "\fBsmtpd_tls_cert_file (empty)\fR"
# File with the Postfix SMTP server RSA certificate in PEM format.
# .IP "\fBsmtpd_tls_eccert_file (empty)\fR"
# File with the Postfix SMTP server ECDSA certificate in PEM format.
# .IP "\fBsmtpd_tls_eckey_file ($smtpd_tls_eccert_file)\fR"
# File with the Postfix SMTP server ECDSA private key in PEM format.
# .IP "\fBsmtpd_tls_key_file ($smtpd_tls_cert_file)\fR"
# File with the Postfix SMTP server RSA private key in PEM format.
# .IP "\fBsmtpd_tls_loglevel (0)\fR"
# Enable additional Postfix SMTP server logging of TLS activity.
# .IP "\fBsmtpd_tls_received_header (no)\fR"
# Request that the Postfix SMTP server produces Received: message
# headers that include information about the protocol and cipher used,
# as well as the remote SMTP client CommonName and client certificate issuer
# CommonName.
# .IP "\fBsmtpd_tls_security_level (empty)\fR"
# The SMTP TLS security level for the Postfix SMTP server; when
# a non-empty value is specified, this overrides the obsolete parameters
# smtpd_use_tls and smtpd_enforce_tls.
# .IP "\fBtls_random_source (see 'postconf -d' output)\fR"
# The external entropy source for the in-memory \fBtlsmgr\fR(8) pseudo
# random number generator (PRNG) pool.
# SEE ALSO
# master(8) Postfix master program
# postfix(1) Postfix administrative interface
# README FILES
# .ad
# .fi
# Use "\fBpostconf readme_directory\fR" or
# "\fBpostconf html_directory\fR" to locate this information.
# .na
# .nf
# TLS_README, Postfix TLS configuration and operation
# LICENSE
# .ad
# .fi
# The Secure Mailer license must be distributed with this software.
# HISTORY
# The "\fBpostfix tls\fR" command was introduced with Postfix
# version 3.1.
# AUTHOR(S)
# Viktor Dukhovni
#--
RSA_BITS=2048 # default
EC_CURVE=secp256r1 # default
case $daemon_directory in
"") echo This script must be run by the postfix command. 1>&2
echo Do not run directly. 1>&2
exit 1;;
esac
umask 022
SHELL=/bin/sh
postconf=$command_directory/postconf
LOGGER="$command_directory/postlog -t $MAIL_LOGTAG/postfix-tls-script"
INFO="$LOGGER -p info"
WARN="$LOGGER -p warn"
ERROR="$LOGGER -p error"
FATAL="$LOGGER -p fatal"
# Overwrite SMTP client and server settings only when these are at defaults.
client_settings="
smtp_use_tls
smtp_enforce_tls
smtp_tls_enforce_peername
smtp_tls_security_level
smtp_tls_cert_file
smtp_tls_dcert_file
smtp_tls_eccert_file
"
server_settings="
smtpd_use_tls
smtpd_enforce_tls
smtpd_tls_security_level
smtpd_tls_cert_file
smtpd_tls_dcert_file
smtpd_tls_eccert_file
"
#
# Can't do much without these in place.
#
cd $command_directory || {
# Let's hope there's a "postlog" somewhere else on the PATH
FATAL="postlog -p fatal -t $MAIL_LOGTAG/postfix-tls-script"
msg="no Postfix command directory '${command_directory}'"
$FATAL "$msg" || { echo "$msg" >&2; sleep 1; }
exit 1
}
check_getopt() {
OPTIND=1
a=
b=
c=
set -- -a 1 -b 2 -c -- -pos
while getopts :a:b:c o
do
case $o in
a) a="${OPTARG}";;
b) b="${OPTARG}";;
c) c=3;;
*) return 1;;
esac
done
shift `expr ${OPTIND} - 1`
if [ "${a}" != "1" -o "${b}" != 2 -o "${c}" != 3 \
-o "${OPTIND}" -ne 7 -o "$1" != "-pos" ]; then
return 1
fi
}
check_getopt || {
$FATAL "/bin/sh does not implement a compatible 'getopts' built-in"
exit 1
}
# ----- BEGIN OpenSSL-specific -----
# No need to set the location of the OpenSSL command in each Postfix instance,
# the value from the default instance is used for all instances.
#
default_config_directory=`$postconf -dh config_directory`
openssl=`$postconf -c $default_config_directory -xh openssl_path`
"$openssl" version >/dev/null 2>&1 || {
$FATAL "No working openssl(1) command found with 'openssl_path = $openssl'"
exit 1
}
# ----- END OpenSSL-specific -----
test -n "$config_directory" -a -d "$config_directory" || {
$FATAL no Postfix configuration directory $config_directory!
exit 1
}
# Do we support TLS and if so which algorithms?
#
$postconf -T compile-version | grep . >/dev/null || {
mail_version=`$postconf -dh mail_version`
$FATAL "Postfix $mail_version is not compiled with TLS support"
exit 1
}
rsa=
ecdsa=
for _algo in `$postconf -T public-key-algorithms | egrep '^(rsa|ecdsa)$'`
do
eval $_algo=$_algo
done
# ----- BEGIN OpenSSL-specific -----
if [ -n "${ecdsa}" ]; then
$openssl ecparam -name secp256r1 >/dev/null 2>&1 || {
cat <<-EOM | $WARN
Postfix supports ECDSA, but the $openssl command does not. Consider
setting the openssl_path parameter to a more capable version of the
command-line utility than $openssl (with PATH=$PATH).
EOM
ecdsa=
}
fi
if [ -n "${rsa}" ]; then
DEFALG=rsa
elif [ -n "${ecdsa}" ]; then
DEFALG=ecdsa
else
mail_version=`$postconf -dh mail_version`
$FATAL "Postfix $mail_version does not support either RSA or ECDSA"
exit 1
fi
# Make sure stdin is open when testing
if [ -r /dev/stdin ] < /dev/null; then
stdin=/dev/stdin
elif [ -r /dev/fd/0 ] </dev/null; then
stdin=/dev/fd/0
else
$FATAL No /dev/fd/0 or /dev/stdin found
exit 1
fi
hex_sha256() {
$openssl dgst -binary -sha256 | od -An -vtx1 | tr -d ' \012'
}
# We require SHA2-256 support from openssl(1)
#
null256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
tmp=`hex_sha256 </dev/null 2>/dev/null`
if [ "${tmp}" != "${null256}" ]; then
cat <<EOF >&2
Your $openssl does not support the SHA2-256 digest algorithm. To enable
'postfix tls', install an OpenSSL that does. Install its openssl(1) command
at /usr/local/bin/openssl or other suitable location, and set the
'openssl_path' parameter in $default_config_directory/main.cf accordingly.
EOF
$FATAL "No 'postfix tls' support when openssl(1) is obsolete"
exit 1
fi
read_key() {
[ -n "$1" -a -f "$1" ] || return 1
# Old OpenSSL versions return success even for unsupported sub-commands!
# So we inspect the output instead. Don't prompt if the key is password
# protected.
#
while read cmd key_algo key_param cert_param; do
$openssl $cmd -passin "pass:umask 077" -in "$1" |
grep . && return 0
done 2>/dev/null <<-EOF
rsa rsa smtpd_tls_key_file smtpd_tls_cert_file
ec ecdsa smtpd_tls_eckey_file smtpd_tls_eccert_file
EOF
return 1
}
pubkey_dgst() {
[ -n "$1" -a -f "$1" ] || return 1
# Old OpenSSL versions return success even for unsupported sub-commands!
# So we inspect the output instead.
#
for cmd in ec rsa; do
$openssl $cmd -passin "pass:umask 077" -in "$1" -pubout |
$openssl $cmd -pubin -outform DER |
hex_sha256 | egrep -v "${null256}" && return 0
done 2>/dev/null
return 1
}
cert_pubkey_dgst() {
[ -n "$1" -a -f "$1" ] || return 1
# Old OpenSSL versions return success even for unsupported sub-commands!
# So we inspect the output instead.
#
for cmd in ec rsa; do
$openssl x509 -pubkey -noout -in "$1" |
$openssl $cmd -pubin -outform DER |
hex_sha256 | egrep -v "${null256}" && return 0
done 2>/dev/null
return 1
}
copy_key() {
_algo=$1; shift
_bits=$1; shift
_fold=$1; shift
_fnew=$1; shift
_umask=`umask`
umask 077
read_key "${_fold}" > "${_fnew}" # sets key_algo of current key
_ret=$?
umask "${_umask}"
if [ "${_ret}" -ne 0 ]; then
$FATAL "Error copying private key from '${_fold}' to '${_fnew}'"
return 1
fi
if [ "${key_algo}" != "${_algo}" ]; then
$FATAL "Key algorithm '$key_algo' of '${_fold}' is not '${_algo}'"
return 1
fi
# XXX: We'd need C-code in postconf to portably check for compatible "bits"
}
create_key() {
_algo=$1
_bits=$2
_fnew=$3
_umask=`umask`
case $_algo in
"") $FATAL "Internal error: empty algorithm"; return 1;;
$rsa) set -- "${openssl}" genrsa -out "${_fnew}" "${_bits}";;
$ecdsa) set -- "${openssl}" ecparam -param_enc named_curve -genkey \
-out "${_fnew}" -name "${_bits}";;
*) $FATAL "Internal error: bad algorithm '${_algo}'"
return 1;;
esac
umask 077
_err=`"$@" 2>&1`
_ret=$?
umask "${_umask}"
if [ "${_ret}" -ne 0 ]; then
echo "${_err}" | $WARN
$FATAL "error generating new ${_algo} ${_bits} private key"
return 1
fi
}
create_cert() {
_k=$1; shift
_c=$1; shift
set_fqdn "$1"
if [ $# -gt 0 ]; then shift; fi
set -- "$fqdn" "$@"
if [ -r "${_c}" ]; then
$FATAL "New certificate file already exists: ${_c}"
return 1
fi
# Generate a new self-signed (~100 year) certificate
#
(
echo "default_md = sha256"
echo "x509_extensions = v3"
echo "prompt = yes"
echo "distinguished_name = dn"
echo "[dn]"
echo "[v3]"
echo "basicConstraints = CA:false"
echo "subjectKeyIdentifier = hash"
echo "extendedKeyUsage = serverAuth, clientAuth"
echo "subjectAltName = @alts"
echo "[alts]"
i=1; for dns in "$@"; do
# XXX map empty to $myhostname
echo "DNS.$i = $dns"
i=`expr $i + 1`
done
) | $openssl req -x509 -config $stdin -new -key "${_k}" \
-subj "/CN=$fqdn" -days 36525 -out "${_c}" || {
rm -f "${_c}" "${_k}"
$FATAL "error generating self-signed SSL certificate"
return 1
}
}
output_server_csr() {
set_keyfile "$1" || return 1
shift
set_fqdn "$1" || return 1
shift
set -- "$fqdn" "$@"
(
echo "default_md = sha256"
echo "req_extensions = v3"
echo "prompt = yes"
echo "distinguished_name = dn"
echo "[dn]"
echo "[v3]"
echo "subjectKeyIdentifier = hash"
echo "extendedKeyUsage = serverAuth, clientAuth"
echo "subjectAltName = @alts"
echo "[alts]"
i=1; for dns in "$@"; do
echo "DNS.$i = $dns"
i=`expr $i + 1`
done
) | $openssl req -config $stdin -new -key "$keyfile" -subj /
}
# ----- END OpenSSL-specific -----
info_enable_client() {
cat <<-EOM
*** Non-default SMTP client TLS settings detected, no changes made.
For opportunistic TLS in the Postfix SMTP client, the below settings
are typical:
smtp_tls_security_level = may
smtp_tls_loglevel = 1
EOM
if get_cache_db_type dbtype
then
echo " smtp_tls_session_cache_database = ${dbtype}:\${data_directory}/smtp_scache"
fi
}
info_client_deployed() {
cat <<-EOM
Enabled opportunistic TLS in the Postfix SMTP client.
Run the command:
# postfix reload
if you want the new settings to take effect immediately.
EOM
}
info_enable_server() {
cat <<-EOM
*** Non-default SMTP server TLS settings detected, no changes made.
For opportunistic TLS in the Postfix SMTP server, the below settings
are typical:
smtpd_tls_security_level = may
smtpd_tls_loglevel = 1
You can use "postfix tls new-server-cert" to create a new certificate.
Or, "postfix tls new-server-key" to also force a new private key.
If you publish DANE TLSA records, see:
https://tools.ietf.org/html/rfc7671#section-8
https://tools.ietf.org/html/rfc7671#section-5.1
https://tools.ietf.org/html/rfc7671#section-5.2
https://community.letsencrypt.org/t/please-avoid-3-0-1-and-3-0-2-dane-tlsa-records-with-le-certificates/7022
EOM
}
# args: certfile keyfile deploy
info_created() {
cat <<-EOM
New private key and self-signed certificate created. To deploy run:
# postfix tls deploy-server-cert $1 $2
EOM
}
# args: certfile keyfile deploy
info_server_deployed() {
if [ "$3" = "enable" ]; then
echo "Enabled opportunistic TLS in the Postfix SMTP server"
fi
cat <<-EOM
New TLS private key and certificate deployed.
Run the command:
# postfix reload
if you want the new settings to take effect immediately.
EOM
}
# args: certfile keyfile deploy
info_csr() {
cat <<-EOM
To generate a CSR run:
# postfix tls output-server-csr -k $2 [<hostname> ...]
EOM
if [ -z "$3" ]; then
echo "Save the signed certificate chain in $1, and deploy as above."
else
echo "Save the signed certificate chain in $1."
fi
}
# args: certfile keyfile deploy
info_tlsa() {
# If already deployed, info for how to show all the deployed keys.
# Otherwise, just the new keys, so that TLSA records can be updated
# first.
if [ -n "$3" ]; then shift $#; fi
cat <<-EOM
To generate TLSA records run:
# postfix tls output-server-tlsa [-h <hostname>] $2
EOM
}
# args: certfile keyfile deploy
info_dane_dns() {
# If already deployed, too late to wait, otherwise advise updating TLSA
# RRs before deployment.
if [ -n "$3" ]; then
cat <<-EOM
(If you have DANE TLSA RRs, update them as soon as possible to match
the newly deployed keys).
EOM
else
cat <<-EOM
(deploy after updating the DNS and waiting for stale RRs to expire).
EOM
fi
}
set_fqdn() {
if [ -n "$1" ]; then fqdn=$1; return 0; fi
fqdn=`$postconf -xh myhostname` || return 1
case $fqdn in /*) fqdn=`cat "${fqdn}"` || return 1;; esac
}
set_keyfile() {
keyfile=$1
case $keyfile in
rsa) if [ -n "${rsa}" ]; then
keyfile=`$postconf -nxh smtpd_tls_key_file`
else
keyfile=
fi
;;
ecdsa) if [ -n "${ecdsa}" ]; then
keyfile=`$postconf -nxh smtpd_tls_eckey_file`
else
keyfile=
fi
;;
"") : empty ok;;
none) : see below;;
/*) ;;
*) # User-specified key pathnames are relative to the configuration
# directory
keyfile="${config_directory}/${keyfile}";;
esac
if [ "${keyfile}" = "none" ]; then keyfile= ; fi
}
check_key() {
read_key "$1" >/dev/null && return 0
$FATAL "no private key found in file: $1"
return 1
}
# Create new key or copy existing if specified.
#
ensure_key() {
_algo=$1; shift
_bits=$1; shift
stamp=`TZ=UTC date +%Y%m%d-%H%M%S`
case $_algo in
"") $FATAL "Internal error: empty algorithm "; return 1;;
$rsa) keyfile="${config_directory}/key-${stamp}.pem"
certfile="${config_directory}/cert-${stamp}.pem";;
$ecdsa) keyfile="${config_directory}/eckey-${stamp}.pem"
certfile="${config_directory}/eccert-${stamp}.pem";;
*) $FATAL "Internal error: bad algorithm '${_algo}'"
return 1;;
esac
if [ -r "${keyfile}" ]; then
$FATAL "New private key file already exists: ${keyfile}"
return 1
fi
if [ -r "${certfile}" ]; then
$FATAL "New certificate file already exists: ${certfile}"
return 1
fi
if [ -n "$1" ]; then
copy_key "${_algo}" "${_bits}" "$1" "${keyfile}" && return 0
else
create_key "${_algo}" "${_bits}" "${keyfile}" && return 0
fi
rm -f "${keyfile}"
return 1
}
init_random_source() {
tls_random_source=$1
if [ -z "${tls_random_source}" ]; then
tls_random_source=`$postconf -xh tls_random_source`
fi
if [ -n "${tls_random_source}" ]; then
return 0
fi
if [ -r /dev/urandom ]
then
tls_random_source=dev:/dev/urandom
else
$FATAL no default TLS random source defined and no /dev/urandom
return 1
fi
}
# Don't be too clever by half.
all_default() {
for var in "$@"
do
val=`$postconf -nh "${var}"`
if [ -n "$val" ]; then return 1; fi
done
return 0
}
# Select read-write database type for TLS session caches.
#
get_cache_db_type() {
var=$1; shift
prio=0
ret=1
for _dbtype in `$postconf -m`
do
_prio=0
case $_dbtype in
lmdb) _prio=2;;
btree) _prio=1;;
esac
if [ "$_prio" -gt "$prio" ]
then
eval "$var=\$_dbtype"
prio=$_prio
ret=0
fi
done
return $ret
}
deploy_server_cert() {
certfile=$1; shift
keyfile=$1; shift
deploy=$1; shift
# Sets key_algo, key_param and cert_param
check_key "$keyfile" || return 1
cd=`cert_pubkey_dgst "${certfile}"` || {
$FATAL "error computing certificate public key digest"
return 1
}
kd=`pubkey_dgst "$keyfile"` || {
$FATAL "error computing public key digest"
return 1
}
if [ "$cd" != "$kd" ]; then
$FATAL "Certificate in ${certfile} does not match key in ${keyfile}"
return 1
fi
set -- \
"${key_param} = ${keyfile}" \
"${cert_param} = ${certfile}"
if [ "${deploy}" = "enable" ]; then
set -- "$@" \
"smtpd_tls_security_level = may" \
"smtpd_tls_received_header = yes" \
"smtpd_tls_loglevel = 1"
fi
if [ -n "${tls_random_source}" ]; then
set -- "$@" "tls_random_source = ${tls_random_source}"
fi
# All in one shot, since postconf delays modifying "hot" main.cf files.
$postconf -e "$@" || return 1
}
# Prepare a new cert and perhaps re-use any existing private key.
#
new_server_cert() {
algo=$1; shift
bits=$1; shift
oldkey=$1; shift
deploy=$1; shift
# resets keyfile (copy or else new) and new certfile
ensure_key "$algo" "$bits" "${oldkey}" || return 1
create_cert "${keyfile}" "${certfile}" "$@" || return 1
if [ -n "${deploy}" ]; then
deploy_server_cert "${certfile}" "${keyfile}" "${deploy}" || return 1
fi
(
if [ -z "${deploy}" ]; then
info_created "${certfile}" "${keyfile}" "${deploy}"
else
info_server_deployed "${certfile}" "${keyfile}" "${deploy}"
fi
info_csr "${certfile}" "${keyfile}" "${deploy}"
info_tlsa "${certfile}" "${keyfile}" "${deploy}"
if [ -z "${oldkey}" ]; then
info_dane_dns "${certfile}" "${keyfile}" "${deploy}"
fi
) | $INFO
}
enable_client() {
if all_default ${client_settings}
then
set -- \
"smtp_tls_security_level = may" \
"smtp_tls_loglevel = 1"
if get_cache_db_type dbtype
then
set -- "$@" \
"smtp_tls_session_cache_database = ${dbtype}:${data_directory}/smtp_scache"
fi
if [ -n "${tls_random_source}" ]; then
set -- "$@" "tls_random_source = ${tls_random_source}"
fi
# All in one shot, since postconf delays modifying "hot" main.cf files.
$postconf -e "$@" || return 1
info_client_deployed
else
info_enable_client
fi | $INFO
}
enable_server() {
algo=$1; shift
bits=$1; shift
if all_default ${server_settings}
then
# algo bits keyfile deploy [hostnames ...]
new_server_cert "${algo}" "${bits}" "" "enable" "$@" || return 1
else
info_enable_server | $INFO
fi
}
output_server_tlsa() {
hostname=$1
check_key "$2" || return 1
data=`pubkey_dgst "$2"` || return 1
if [ -z "$data" ]
then
$FATAL error computing SHA2-256 SPKI digest of "$key"
return 1
fi
echo "_25._tcp.$hostname. IN TLSA 3 1 1 $data"
}
#
# Parse JCL
#
case $1 in
enable-client)
cmd=$1; shift; OPTIND=1
rand=
while getopts :r: _opt
do
case $_opt in
r) rand="${OPTARG}";;
*) $FATAL "usage: postfix tls $cmd [-r devrandom]"
exit 1;;
esac
done
# No positional arguments supported with enable-client
if [ $# -ge "${OPTIND}" ]; then
$FATAL "usage: postfix tls $cmd [-r devrandom]"
exit 1
fi
# But, shift anyway
shift `expr $OPTIND - 1`
init_random_source "${rand}" || exit 1
enable_client || exit 1
;;
enable-server)
cmd=$1; shift; OPTIND=1
algo=$DEFALG
bits=
rand=
while getopts :a:b:r: _opt
do
case $_opt in
a) algo="${OPTARG}";;
b) bits="${OPTARG}";;
r) rand="${OPTARG}";;
*) $FATAL "usage: postfix tls $cmd [-a algorithm] [-b bits ] [-r devrandom] [hostname ...]"
exit 1;;
esac
done
# Here positional arguments are hostnames for the new certificate, as
# many as the user wants
shift `expr $OPTIND - 1`
case $algo in
"") $FATAL "Internal error: empty algorithm "; return 1;;
$rsa) : ${bits:=${RSA_BITS}};;
$ecdsa) : ${bits:=${EC_CURVE}};;
*) $FATAL "Unsupported private key algorithm: $algo"
exit 1;;
esac
init_random_source "${rand}" || exit 1
enable_server "${algo}" "${bits}" "$@" || exit 1
;;
new-server-key)
cmd=$1; shift; OPTIND=1
algo=$DEFALG
while getopts :a:b: _opt
do
case $_opt in
a) algo="${OPTARG}";;
b) bits="${OPTARG}";;
*) $FATAL "usage: postfix tls $cmd [-a algorithm] [-b bits ] [hostname ...]"
exit 1;;
esac
done
# Here positional arguments are hostnames for the new certificate, as
# many as the user wants
shift `expr $OPTIND - 1`
case $algo in
"") $FATAL "Internal error: empty algorithm "; return 1;;
$rsa) : ${bits:=${RSA_BITS}};;
$ecdsa) : ${bits:=${EC_CURVE}};;
*) $FATAL "Unsupported public key algorithm: $algo"
exit 1;;
esac
# Force new key
new_server_cert "${algo}" "${bits}" "" "" "$@" || exit 1
;;
new-server-cert)
cmd=$1; shift; OPTIND=1
algo=$DEFALG
while getopts :a:b: _opt
do
case $_opt in
a) algo="${OPTARG}";;
b) bits="${OPTARG}";;
*) $FATAL "usage: postfix tls $cmd [-a algorithm] [-b bits ] [hostname ...]"
exit 1;;
esac
done
# Here positional arguments are hostnames for the new certificate, as
# many as the user wants
shift `expr $OPTIND - 1`
case $algo in
"") $FATAL "Invalid empty key algorithm"; exit 1;;
$rsa) : ${bits:=${RSA_BITS}};;
$ecdsa) : ${bits:=${EC_CURVE}};;
*) $FATAL "Unsupported private key algorithm: $algo"
exit 1;;
esac
# Existing keyfile or empty
set_keyfile "${algo}"
# Try to re-use (copy) existing key.
new_server_cert "${algo}" "${bits}" "${keyfile}" "" "$@" || exit 1
;;
deploy-server-cert)
if [ $# -ne 3 ]; then
$FATAL "usage: postfix tls $1 certfile keyfile"
exit 1
fi
shift
# User-specified key and cert pathnames are relative to the
# configuration directory
#
case "${1}" in
/*) certfile="${1}" ;;
*) certfile="${config_directory}/${1}" ;;
esac
case "${2}" in
/*) certfile="${2}" ;;
*) certfile="${config_directory}/${2}" ;;
esac
deploy_server_cert "${certfile}" "${keyfile}" || exit 1
info_server_deployed "${certfile}" "${keyfile}" "deploy" | $INFO
;;
output-server-csr)
cmd=$1; shift; OPTIND=1
k=
while getopts :k: _opt
do
case $_opt in
k) k="${OPTARG}";;
*) $FATAL "usage: postfix tls $cmd [-k keyfile] [hostname ...]"
exit 1;;
esac
done
# Here positional arguments are hostnames for the new certificate, as
# many as the user wants
shift `expr $OPTIND - 1`
if [ -n "${k}" ]; then
set_keyfile "${k}"
else
for _algo in $rsa $ecdsa
do
set_keyfile "${_algo}"
if [ -n "${keyfile}" ]; then
break
fi
done
fi
if [ -z "${keyfile}" -o ! -r "${keyfile}" ]; then
$FATAL "No usable keyfile specified or configured"
exit 1
fi
# Default <hostname> from $myhostname
if [ $# -eq 0 ]; then
set_fqdn
set -- "$fqdn"
fi
# Output a CSR for the requested names
output_server_csr "$keyfile" "$@" || exit 1
;;
output-server-tlsa)
cmd=$1; shift; OPTIND=1
hostname=
while getopts :h: _opt
do
case $_opt in
h) hostname="${OPTARG}";;
*) $FATAL "usage: postfix tls $cmd [-h hostname] [keyfile ...]"
exit 1;;
esac
done
set_fqdn "${hostname}"
# Here positional arguments are keyfiles for which we ouput "3 1 1"
# TLSA RRs, as many keyfiles as the user wants. By default the live
# RSA and/or ECDSA keys.
shift `expr $OPTIND - 1`
if [ $# -eq 0 ]; then set -- $rsa $ecdsa; fi
found=
for _k in "$@"
do
set_keyfile "${_k}"
if [ -z "${keyfile}" ]; then continue; fi
echo "; ${keyfile}"
output_server_tlsa "${fqdn}" "${keyfile}" || exit 1
found=1
done
if [ -z "${found}" ]; then
$FATAL "No usable keyfiles specified or configured"
exit 1
fi
;;
all-default-client)
cmd=$1; shift; OPTIND=1
# No arguments for all-default-client
if [ $# -ge "${OPTIND}" ]; then
$FATAL "usage: postfix tls $cmd"
exit 1
fi
all_default ${client_settings} || exit 1
;;
all-default-server)
cmd=$1; shift; OPTIND=1
# No arguments for all-default-server
if [ $# -ge "${OPTIND}" ]; then
$FATAL "usage: postfix tls $cmd"
exit 1
fi
all_default ${server_settings} || exit 1
;;
*)
$ERROR "unknown tls command: '$1'"
$FATAL "usage: postfix tls enable-client (or enable-server, new-server-key, new-server-cert, deploy-server-cert, output-server-csr, output-server-tlsa, all-default-client, all-default-server)"
exit 1
;;
esac
|