#!rsc
# RouterOS script: check-certificates
# Copyright (c) 2013-2019 Christian Hesse <mail@eworm.de>
#
# check for certificate validity

:global Identity;
:global CertRenewUrl;
:global CertRenewPass;

:global ParseKeyValueStore;
:global SendNotification;
:global UrlEncode;
:global WaitForFile;

:local FormatExpire do={
  :global CharacterReplace;
  :return [ $CharacterReplace [ $CharacterReplace [ :tostr $1 ] "w" "w " ] "d" "d " ];
}

:foreach Cert in=[ / certificate find where !revoked !ca expires-after<3w ] do={
  :local CertVal [ / certificate get $Cert ];

  :do {
    :if ([ :len $CertRenewUrl ] = 0) do={
      :log info "No CertRenewUrl given.";
      :error "No CertRenewUrl given.";
    }

    :foreach Type in={ ".pem"; ".p12" } do={
      :local CertFileName ([ $UrlEncode ($CertVal->"common-name") ] . $Type);
      :do {
        / tool fetch check-certificate=yes-without-crl \
            ($CertRenewUrl . $CertFileName) dst-path=$CertFileName;
        $WaitForFile $CertFileName;
        :foreach PassPhrase in=$CertRenewPass do={
          / certificate import file-name=$CertFileName passphrase=$PassPhrase;
        }
        / file remove [ find where name=$CertFileName ];
      } on-error={
        :log debug ("Could not download certificate file " . $CertFileName);
      }
    }

    :local CertNew [ / certificate find where common-name=($CertVal->"common-name") fingerprint!=[ :tostr ($CertVal->"fingerprint") ] expires-after>3w ];
    :local CertNewVal [ / certificate get $CertNew ];

    :if ($Cert != $CertNew) do={
      :log debug ("Certificate '" . $CertVal->"name" . "' was not updated, but replaced.");

      / ip service set certificate=($CertNewVal->"name") [ find where certificate=($CertVal->"name") ];

      :do {
        / ip ipsec identity set certificate=($CertNewVal->"name") [ / ip ipsec identity find where certificate=($CertVal->"name") ];
        / ip ipsec identity set remote-certificate=($CertNewVal->"name") [ / ip ipsec identity find where remote-certificate=($CertVal->"name") ];
      } on-error={
        :log debug ("Setting IPSEC certificates failed. Package 'security' not installed?");
      }

      :do {
        / ip hotspot profile set ssl-certificate=($CertNewVal->"name") [ / ip hotspot profile find where ssl-certificate=($CertVal->"name") ];
      } on-error={
        :log debug ("Setting hotspot certificates failed. Package 'hotspot' not installed?");
      }

      / certificate remove $Cert;
      / certificate set $CertNew name=($CertVal->"name");
    }

    $SendNotification ("Certificate renewed") \
      ("A certificate on " . $Identity . " has been renewed.\n\n" . \
        "Name:        " . ($CertVal->"name") . "\n" . \
        "CommonName:  " . ($CertNewVal->"common-name") . "\n" . \
        "Fingerprint: " . ($CertNewVal->"fingerprint") . "\n" . \
        "Issuer:      " . ([ $ParseKeyValueStore ($CertNewVal->"issuer") ]->"CN") . "\n" . \
        "Validity:    " . ($CertNewVal->"invalid-before") . " to " . ($CertNewVal->"invalid-after") . "\n" . \
        "Expires in:  " . [ $FormatExpire ($CertNewVal->"expires-after") ]);
    :log info ("The certificate " . ($CertVal->"name") . " has been renewed.");
  } on-error={
    :log debug ("Could not renew certificate " . ($CertVal->"name") . ".");
  }
}

:foreach Cert in=[ / certificate find where !revoked expires-after<2w fingerprint~"."] do={
  :local CertVal [ / certificate get $Cert ];

  :local ExpiresAfter [ $FormatExpire ($CertVal->"expires-after") ];
  :local State "is about to expire";
  :if (($CertVal->"expired") = true) do={
    :set ExpiresAfter "expired";
    :set State "expired";
  }

  $SendNotification ("Certificate warning!") \
    ("A certificate on " . $Identity . " " . $State . ".\n\n" . \
      "Name:        " . ($CertVal->"name") . "\n" . \
      "CommonName:  " . ($CertVal->"common-name") . "\n" . \
      "Fingerprint: " . ($CertVal->"fingerprint") . "\n" . \
      "Issuer:      " . ($CertVal->"ca") . ([ $ParseKeyValueStore ($CertVal->"issuer") ]->"CN") . "\n" . \
      "Validity:    " . ($CertVal->"invalid-before") . " to " . ($CertVal->"invalid-after") . "\n" . \
      "Expires in:  " . $ExpiresAfter);
  :log warning ("The certificate " . ($CertVal->"name") . " " . $State . \
      ", it is invalid after " . ($CertVal->"invalid-after") . ".");
}