freedombone-pin-cert 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. #!/bin/bash
  2. # _____ _ _
  3. # | __|___ ___ ___ _| |___ _____| |_ ___ ___ ___
  4. # | __| _| -_| -_| . | . | | . | . | | -_|
  5. # |__| |_| |___|___|___|___|_|_|_|___|___|_|_|___|
  6. #
  7. # Freedom in the Cloud
  8. #
  9. # Performs certificate pinning (HPKP) on a given domain name
  10. # License
  11. # =======
  12. #
  13. # Copyright (C) 2015-2018 Bob Mottram <bob@freedombone.net>
  14. #
  15. # This program is free software: you can redistribute it and/or modify
  16. # it under the terms of the GNU Affero General Public License as published by
  17. # the Free Software Foundation, either version 3 of the License, or
  18. # (at your option) any later version.
  19. #
  20. # This program is distributed in the hope that it will be useful,
  21. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. # GNU Affero General Public License for more details.
  24. #
  25. # You should have received a copy of the GNU Affero General Public License
  26. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  27. PROJECT_NAME='freedombone'
  28. export TEXTDOMAIN=${PROJECT_NAME}-pin-cert
  29. export TEXTDOMAINDIR="/usr/share/locale"
  30. WEBSITES_DIRECTORY=/etc/nginx/sites-available
  31. # 90 days
  32. PIN_MAX_AGE=7776000
  33. function pin_all_certs {
  34. if [ ! -d $WEBSITES_DIRECTORY ]; then
  35. return
  36. fi
  37. cd $WEBSITES_DIRECTORY || exit 2468724684
  38. for file in $(dir -d "*") ; do
  39. if grep -q "Public-Key-Pins" "$file"; then
  40. DOMAIN_NAME=$file
  41. KEY_FILENAME=/etc/ssl/private/${DOMAIN_NAME}.key
  42. if [ -f "$KEY_FILENAME" ]; then
  43. BACKUP_KEY_FILENAME=/etc/ssl/certs/${DOMAIN_NAME}.pem
  44. if [ -f "$BACKUP_KEY_FILENAME" ]; then
  45. KEY_HASH=$(openssl rsa -in "$KEY_FILENAME" -outform der -pubout | openssl dgst -sha256 -binary | openssl enc -base64)
  46. BACKUP_KEY_HASH=$(openssl rsa -in "$BACKUP_KEY_FILENAME" -outform der -pubout | openssl dgst -sha256 -binary | openssl enc -base64)
  47. if [ ${#BACKUP_KEY_HASH} -gt 5 ]; then
  48. PIN_HEADER="Public-Key-Pins 'pin-sha256=\"${KEY_HASH}\"; pin-sha256=\"${BACKUP_KEY_HASH}\"; max-age=${PIN_MAX_AGE}; includeSubDomains';"
  49. sed -i "s|Public-Key-Pins.*|${PIN_HEADER}|g" "$file"
  50. echo $"Pinned $DOMAIN_NAME with keys $KEY_HASH $BACKUP_KEY_HASH"
  51. fi
  52. fi
  53. fi
  54. fi
  55. done
  56. }
  57. if [[ "$1" == "all" ]]; then
  58. pin_all_certs
  59. systemctl restart nginx
  60. exit 0
  61. fi
  62. DOMAIN_NAME=$1
  63. REMOVE=$2
  64. KEY_FILENAME=/etc/ssl/private/${DOMAIN_NAME}.key
  65. BACKUP_KEY_FILENAME=/etc/ssl/certs/${DOMAIN_NAME}.pem
  66. SITE_FILENAME=$WEBSITES_DIRECTORY/${DOMAIN_NAME}
  67. if [ ! "${DOMAIN_NAME}" ]; then
  68. exit 0
  69. fi
  70. if [ ! -f "$SITE_FILENAME" ]; then
  71. exit 0
  72. fi
  73. if [[ $REMOVE == "remove" ]]; then
  74. if grep -q "Public-Key-Pins" "$SITE_FILENAME"; then
  75. sed -i "/Public-Key-Pins/d" "$SITE_FILENAME"
  76. echo $"Removed pinning for ${DOMAIN_NAME}"
  77. systemctl restart nginx
  78. fi
  79. exit 0
  80. fi
  81. if [ ! -f "$KEY_FILENAME" ]; then
  82. echo $"No private key certificate found for $DOMAIN_NAME"
  83. exit 1
  84. fi
  85. if [ ! -f "$BACKUP_KEY_FILENAME" ]; then
  86. echo $"No fullchain certificate found for $DOMAIN_NAME"
  87. exit 2
  88. fi
  89. KEY_HASH=$(openssl rsa -in "$KEY_FILENAME" -outform der -pubout | openssl dgst -sha256 -binary | openssl enc -base64)
  90. BACKUP_KEY_HASH=$(openssl rsa -in "$BACKUP_KEY_FILENAME" -outform der -pubout | openssl dgst -sha256 -binary | openssl enc -base64)
  91. if [ ${#KEY_HASH} -lt 5 ]; then
  92. echo 'Pin hash unexpectedly short'
  93. exit 3
  94. fi
  95. if [ ${#BACKUP_KEY_HASH} -lt 5 ]; then
  96. echo 'Backup pin hash unexpectedly short'
  97. exit 4
  98. fi
  99. PIN_HEADER="Public-Key-Pins 'pin-sha256=\"${KEY_HASH}\"; pin-sha256=\"${BACKUP_KEY_HASH}\"; max-age=5184000; includeSubDomains';"
  100. if ! grep -q "Public-Key-Pins" "$SITE_FILENAME"; then
  101. sed -i "/ssl_ciphers.*/a add_header ${PIN_HEADER}" "$SITE_FILENAME"
  102. else
  103. sed -i "s|Public-Key-Pins.*|${PIN_HEADER}|g" "$SITE_FILENAME"
  104. fi
  105. systemctl restart nginx
  106. if ! grep -q "add_header Public-Key-Pins" "$SITE_FILENAME"; then
  107. echo $'Pinning failed'
  108. fi
  109. echo "Pinned $DOMAIN_NAME with keys $KEY_HASH $BACKUP_KEY_HASH"
  110. exit 0