summaryrefslogtreecommitdiff
path: root/scripts/mkdkimkey.sh
blob: 571417374f4355c6336f7d464cf23a2ef436e0ba (plain)
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
#********************************************************************************
# Create/Handle domainkeys for openqmail/eQmail/(net)qmail and derivatives      #
#                                                                               #
# Author: Kai Peter (parts taken from Joerg Backschues), ©2014                  #
# Version: 0.32 -> 0.46                                                         #
# Licence: This program is  Copyright(C) ©2015 Kai Peter.  It can be copied and #
#          modified according to the GNU GENERAL PUBLIC LICENSE (GPL) Version 2 #
#          or a later version. This software comes without any warranty.        #
#                                                                               #
# Description: Creation of domain keys and DNS TXT records for bind             #
#                                                                               #
# Addendum for s/qmail:                                                         #
#                                                                               #
# a) This version is modified for s/qmail (etc/dkimkey -> ssl/domainkeys)       #
# b) RSA and Ed25519 private/public keys are considered (-> RFC 8463)           #
# c) tinydns supports DKIM records while just providing the public key;         #
#    beware of the 'selector'; it is set to 'default'                           #
# d) Unlike previous versions, the new private key is *not* automatically       #
#    linked to a file named 'default'; but rather to                            #
#    rsa|ed25519.private_<selector> -> <selector>                               #
# e) The ed25519 public key is stripped from the ASN1 header information        #
#    This base64 encoded key is available as 'ed25519.basekey_<Selector>        #
# f) RSA and Ed25519 private keys may share the same <Selector> name.           #
#    If identical, the Ed25519 private key is linked as '<Selector>_'           #
#    to be picked up automatically by qmail-dksign for simultaneous signing.    #
#                                                                               #
# Changelog:                                                                    #
#                                                                               #
# 0.46 Fix for RSA DKIM TXT file generation, compactified TXT data.             # 
#********************************************************************************
DKDIR="HOME/ssl/domainkeys"
USR="qmailq"
GRP="sqmail"
MODULUS=2048
CURVE=0
VERBOSE=0
SELECTOR="default"
DOMAIN=""
ASN1="MCowBQYDK2VwAyEA"
PRINT=0

OPENSSL=$(which openssl 2>/dev/null)
if [ $? -ne 0 ] ; then
   echo "Couldn't find openssl! Aborting!" ; exit 0 ; 
fi

showHelp() {
echo "Usage:  $(basename $0) [-hpc] [-s selector] [-m modulus] domain"
echo
echo "-h   (show this help and exit)"
echo "-v   (verbose output)"
echo "-p   (print TXT record for domain)"
echo "-s   <selector> (set the selector)"
echo "-m   <N> (set RSA modulo size; default: 2048 bits)"
echo "-c   (generate Ed25519 keys)"
echo ""
echo "RSA key generation:"
echo "=================="
echo ""
echo "     mkdkimkey -s rsa-selector domain"
echo ""
echo "Ed25519 key generation:"
echo "======================"
echo ""
echo "     mkdkimkey -c -s ed-selector domain"
echo ""
echo "Key activation:"
echo "=============="
echo "     The created private key 'rsa|ed25519.private_<selector>'"
echo "     is automatically symlinked to '<selector>'."
echo "     If included in 'control/dkimdomains', this file name"
echo "     is picked up by qmail-dksign for signing outgoing mails."
echo "     The default name for '<selector>' is 'default'."
echo "     This is implicitely assumed if no particular '<selector>' is given."
echo ""
exit 1 ; 
}

readInit() {

FLAG=""
PARAMS="vhpcs:m:"

if [ $# -gt 0 ]; then
   while getopts ${PARAMS} FLAG
   do
     case ${FLAG} in
       v) VERBOSE=1;; 
       p) PRINT=1;;
       s) SELECTOR=${OPTARG};;
       m) MODULUS=${OPTARG};;
       c) CURVE=1;;
       h) showHelp;;
       *) showHelp;;
     esac
   done
   shift $((OPTIND-1))
fi 

# Validate the input a bit ...
DOMAIN=$1 ; if [ "x${DOMAIN}" = "x"  ] ; then showHelp ; fi
# Only one argument is allowed ($1) and have to follow any other options
if [ $2 ] ; then echo "Syntax ERROR!;" showHelp ; fi

# Create main DKIM directory for keys if required

if [ ! -d ${DKDIR} ] ; then mkdir -p ${DKDIR} ; fi
}

showTXT() {
# backslashes MUST NOT be used in TXT records to quote semicolons !

cd ${DKDIR}/${DOMAIN}

echo "Domain's public key in '${DKDIR}/${DOMAIN}' used for TXT DNS record:"

echo -n "${SELECTOR}._domainkey.${DOMAIN}. IN TXT "
if [ -f rsa.public_${SELECTOR} ]; then
  key=`grep -v -e '^-' rsa.public_${SELECTOR} | tr -d '\n'`
  echo "\"v=DKIM1;k=rsa;t=y;p=${key}\""
elif [ -f ed25519.public_${SELECTOR} ]; then
  key=`grep -v -e '^-' ed25519.public_${SELECTOR} | tr -d '\n'`
  basekey=${key#${ASN1}}
  if [ `echo -n ${basekey} | wc -c | awk '{print $1}'` -eq 44 ]; then
    echo "\"v=DKIM1;k=ed25519;t=y;p=${basekey}\""
    echo ${basekey} > ed25519.basekey_${SELECTOR}
  else
    (errString="error generating Ed25519 public key" && showError; echo ${key}; echo ${basekey}; exit 1)
  fi
fi
echo -n "You need to publish this TXT record in the DNS before activating [rsa|ed25519].private_${SELECTOR} -> ${SELECTOR} for signing."

echo ; exit 0;
}

showError() {
  echo "Domainkey for domain '${DOMAIN}' with selector '${SELECTOR}' [${errString}]!";
}

###############################################################################
#  Main
###############################################################################

# Read input args; create dirs, do some validation

readInit ${@}

if [ ${PRINT} -eq 1 ]; then showTXT; fi

# Do some tests for existing keys

if [ ${VERBOSE} -eq 1 ] ; then
  if [ ${CURVE} -eq 0 ] ; then
    test -f ${DKDIR}/${DOMAIN}/rsa.private_${SELECTOR} && \
    (errString="already exists" && showError)
  else
    test -f ${DKDIR}/${DOMAIN}/ed25519.private_${SELECTOR} && \
    (errString="already exists" && showError)
  fi
fi

# Create a directory for domain and populate it with new keys
# Existing old keys are safed as 'previous'

mkdir -p ${DKDIR}/${DOMAIN}
cd ${DKDIR}/${DOMAIN}

if [ ${CURVE} -eq 0 ] ; then
  if [ -f rsa.public_${SELECTOR} ]; then
    cp rsa.public_${SELECTOR} rsa.public_${SELECTOR}.previous
    cp rsa.private_${SELECTOR} rsa.private_${SELECTOR}.previous
  fi
  ${OPENSSL} genrsa -out rsa.private_${SELECTOR} ${MODULUS}
  ${OPENSSL} rsa -in rsa.private_${SELECTOR} \
             -out rsa.public_${SELECTOR} -pubout -outform PEM
   ln -sf rsa.private_${SELECTOR} ${SELECTOR}
else
  if [ -f ed25519.public_${SELECTOR} ]; then
    cp ed25519.public_${SELECTOR}  ed25519.public_${SELECTOR}.previous
    cp ed25519.private_${SELECTOR} ed25519.private_${SELECTOR}.previous
  fi
  ${OPENSSL} genpkey -algorithm Ed25519 \
             -out ed25519.private_${SELECTOR}
  ${OPENSSL} pkey -in ed25519.private_${SELECTOR} \
             -out ed25519.public_${SELECTOR} -pubout 
  if [ -f ${SELECTOR} ]; then
    ln -sf ed25519.private_${SELECTOR} "${SELECTOR}_"
  else
    ln -sf ed25519.private_${SELECTOR} ${SELECTOR}
  fi
fi

# Set permissions

chmod 0711 ${DKDIR}
chmod 0700 ${DKDIR}/${DOMAIN}
chmod 0600 ${DKDIR}/${DOMAIN}/*
chown -R ${USR}:${GRP} ${DKDIR}

# Do some tests

if [ ${CURVE} -eq 0 ] ; then
  test -f rsa.public_${SELECTOR} || \
  (errString="does not exist" && showError)
else
  test -f ed25519.public_${SELECTOR} || \
  (errString="does not exist" && showError)
fi

# Done

[ "$?" = 0 ] && showTXT
exit 0