@@ -50,6 +50,9 @@ import Data.X509.CertificateStore (CertificateStore,
5050import Data.X509.Validation
5151import Net.IPv4 (IPv4 (.. ))
5252import Net.IPv6 (IPv6 (.. ))
53+ import System.Directory (removeFile , renameFile )
54+ import System.FilePath (splitFileName , (<.>) )
55+ import System.IO (hClose , openBinaryTempFile )
5356
5457import qualified Crypto.PubKey.RSA.Types as RSA
5558import qualified Data.ByteString as BS
@@ -231,9 +234,9 @@ writeCredentials
231234 -> (PrivateKey , SignedCertificate )
232235 -> IO ()
233236writeCredentials filename (key, cert) = do
234- BS. writeFile (filename <> " .pem" ) (BS. concat [keyBytes, " \n " , certBytes])
235- BS. writeFile (filename <> " .key" ) keyBytes
236- BS. writeFile (filename <> " .crt" ) certBytes
237+ writeFileAtomicPrivate (filename <> " .pem" ) (BS. concat [keyBytes, " \n " , certBytes])
238+ writeFileAtomicPrivate (filename <> " .key" ) keyBytes
239+ writeFileAtomicPrivate (filename <> " .crt" ) certBytes
237240 where
238241 keyBytes = encodePEM key
239242 certBytes = encodePEM cert
@@ -245,7 +248,7 @@ writeCertificate
245248 -> SignedCertificate
246249 -> IO ()
247250writeCertificate filename cert =
248- BS. writeFile (filename <> " .crt" ) (encodePEM cert)
251+ writeFileAtomicPrivate (filename <> " .crt" ) (encodePEM cert)
249252
250253
251254--
@@ -330,3 +333,26 @@ validateCertificateIP ip cert =
330333 []
331334 else
332335 [NameMismatch $ B8. unpack ip]
336+
337+
338+ -- | Writes a file atomically, and with private file permissions.
339+ --
340+ -- The file is either written successfully or an IO exception is raised and
341+ -- the original file is left unchanged.
342+ --
343+ -- On unix systems the file permissions are 600, i.e. user read and write,
344+ -- but no others.
345+ --
346+ -- On windows it is not possible to delete a file that is open by a process.
347+ -- This case will give an IO exception but the atomic property is not affected.
348+ --
349+ writeFileAtomicPrivate :: FilePath -> ByteString -> IO ()
350+ writeFileAtomicPrivate targetPath content = do
351+ let (targetDir, targetFile) = splitFileName targetPath
352+ bracketOnError
353+ (openBinaryTempFile targetDir $ targetFile <.> " tmp" )
354+ (\ (tmpPath, handle) -> hClose handle >> removeFile tmpPath)
355+ (\ (tmpPath, handle) -> do
356+ BS. hPut handle content
357+ hClose handle
358+ renameFile tmpPath targetPath)
0 commit comments