mirror of
https://code.sup39.dev/repos/Wqawg
synced 2024-12-29 00:24:51 +09:00
Add utility for loading permanent key files, and use it for ocap signing key
This commit is contained in:
parent
fbc9ad2b30
commit
9536d870e5
4 changed files with 108 additions and 25 deletions
|
@ -17,7 +17,6 @@ module Vervis.ActorKey
|
||||||
( ActorKey ()
|
( ActorKey ()
|
||||||
, generateActorKey
|
, generateActorKey
|
||||||
, actorKeyRotator
|
, actorKeyRotator
|
||||||
, loadActorKey
|
|
||||||
, actorKeyPublicBin
|
, actorKeyPublicBin
|
||||||
, actorKeySign
|
, actorKeySign
|
||||||
-- , actorKeyVerify
|
-- , actorKeyVerify
|
||||||
|
@ -41,6 +40,8 @@ import System.Directory (doesFileExist)
|
||||||
|
|
||||||
import qualified Data.ByteString as B (writeFile, readFile)
|
import qualified Data.ByteString as B (writeFile, readFile)
|
||||||
|
|
||||||
|
import Vervis.KeyFile
|
||||||
|
|
||||||
-- | Ed25519 signing key, we generate it on the server and use for signing. We
|
-- | Ed25519 signing key, we generate it on the server and use for signing. We
|
||||||
-- also make its public key available to whoever wishes to verify our
|
-- also make its public key available to whoever wishes to verify our
|
||||||
-- signatures.
|
-- signatures.
|
||||||
|
@ -56,6 +57,16 @@ data ActorKey = ActorKey
|
||||||
-- key once and potentially send the PEM many times.
|
-- key once and potentially send the PEM many times.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instance KeyFile ActorKey where
|
||||||
|
generateKey = generateActorKey
|
||||||
|
parseKey b = do
|
||||||
|
secret <- throwCryptoErrorIO $ secretKey b
|
||||||
|
return ActorKey
|
||||||
|
{ actorKeySecret = secret
|
||||||
|
, actorKeyPublic = toPublic secret
|
||||||
|
}
|
||||||
|
renderKey = convert . actorKeySecret
|
||||||
|
|
||||||
{-
|
{-
|
||||||
-- | Ed25519 public key for signature verification. We receive these public
|
-- | Ed25519 public key for signature verification. We receive these public
|
||||||
-- keys from other servers and we use them to verify HTTP request signatures.
|
-- keys from other servers and we use them to verify HTTP request signatures.
|
||||||
|
@ -161,24 +172,6 @@ actorKeyRotator interval keys =
|
||||||
error $
|
error $
|
||||||
"actorKeyRotator: interval out of range: " ++ show micros
|
"actorKeyRotator: interval out of range: " ++ show micros
|
||||||
|
|
||||||
-- | If a key file exists, load the key from there. Otherwise, generate a new
|
|
||||||
-- key, write it to the file and return it.
|
|
||||||
loadActorKey :: FilePath -> IO ActorKey
|
|
||||||
loadActorKey path = do
|
|
||||||
e <- doesFileExist path
|
|
||||||
if e
|
|
||||||
then do
|
|
||||||
b <- B.readFile path
|
|
||||||
secret <- throwCryptoErrorIO $ secretKey b
|
|
||||||
return ActorKey
|
|
||||||
{ actorKeySecret = secret
|
|
||||||
, actorKeyPublic = toPublic secret
|
|
||||||
}
|
|
||||||
else do
|
|
||||||
akey <- generateActorKey
|
|
||||||
B.writeFile path $ convert $ actorKeySecret akey
|
|
||||||
return akey
|
|
||||||
|
|
||||||
-- | The public key in PEM format, can be directly placed in responses.
|
-- | The public key in PEM format, can be directly placed in responses.
|
||||||
--
|
--
|
||||||
-- Well, right now it's actually just the public key in binary form, because
|
-- Well, right now it's actually just the public key in binary form, because
|
||||||
|
|
|
@ -57,7 +57,10 @@ import qualified Data.Text as T (unpack)
|
||||||
|
|
||||||
import Control.Concurrent.Local (forkCheck)
|
import Control.Concurrent.Local (forkCheck)
|
||||||
|
|
||||||
import Vervis.ActorKey (generateActorKey, actorKeyRotator, loadActorKey)
|
import Database.Persist.Schema.PostgreSQL (schemaBackend)
|
||||||
|
|
||||||
|
import Vervis.ActorKey (generateActorKey, actorKeyRotator)
|
||||||
|
import Vervis.KeyFile (isInitialSetup, loadKeyFile)
|
||||||
|
|
||||||
-- Import all relevant handler modules here.
|
-- Import all relevant handler modules here.
|
||||||
-- Don't forget to add new modules to your cabal file!
|
-- Don't forget to add new modules to your cabal file!
|
||||||
|
@ -115,8 +118,6 @@ makeFoundation appSettings = do
|
||||||
newTVarIO =<<
|
newTVarIO =<<
|
||||||
(,,) <$> generateActorKey <*> generateActorKey <*> pure True
|
(,,) <$> generateActorKey <*> generateActorKey <*> pure True
|
||||||
|
|
||||||
appCapSignKey <- loadActorKey $ appCapabilitySigningKeyFile appSettings
|
|
||||||
|
|
||||||
appActivities <- newTVarIO mempty
|
appActivities <- newTVarIO mempty
|
||||||
|
|
||||||
-- We need a log function to create a connection pool. We need a connection
|
-- We need a log function to create a connection pool. We need a connection
|
||||||
|
@ -124,11 +125,14 @@ makeFoundation appSettings = do
|
||||||
-- logging function. To get out of this loop, we initially create a
|
-- logging function. To get out of this loop, we initially create a
|
||||||
-- temporary foundation without a real connection pool, get a log function
|
-- temporary foundation without a real connection pool, get a log function
|
||||||
-- from there, and then create the real foundation.
|
-- from there, and then create the real foundation.
|
||||||
let mkFoundation appConnPool = App {..}
|
let mkFoundation appConnPool appCapSignKey = App {..}
|
||||||
-- The App {..} syntax is an example of record wild cards. For more
|
-- The App {..} syntax is an example of record wild cards. For more
|
||||||
-- information, see:
|
-- information, see:
|
||||||
-- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html
|
-- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html
|
||||||
tempFoundation = mkFoundation $ error "connPool forced in tempFoundation"
|
tempFoundation =
|
||||||
|
mkFoundation
|
||||||
|
(error "connPool forced in tempFoundation")
|
||||||
|
(error "capSignKey forced in tempFoundation")
|
||||||
logFunc = loggingFunction tempFoundation
|
logFunc = loggingFunction tempFoundation
|
||||||
|
|
||||||
-- Create the database connection pool
|
-- Create the database connection pool
|
||||||
|
@ -136,6 +140,9 @@ makeFoundation appSettings = do
|
||||||
(pgConnStr $ appDatabaseConf appSettings)
|
(pgConnStr $ appDatabaseConf appSettings)
|
||||||
(pgPoolSize $ appDatabaseConf appSettings)
|
(pgPoolSize $ appDatabaseConf appSettings)
|
||||||
|
|
||||||
|
setup <- isInitialSetup pool schemaBackend
|
||||||
|
capSignKey <- loadKeyFile setup $ appCapabilitySigningKeyFile appSettings
|
||||||
|
|
||||||
-- Perform database migration using our application's logging settings.
|
-- Perform database migration using our application's logging settings.
|
||||||
--runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc
|
--runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc
|
||||||
flip runLoggingT logFunc $
|
flip runLoggingT logFunc $
|
||||||
|
@ -149,7 +156,7 @@ makeFoundation appSettings = do
|
||||||
Right (_from, _to) -> $logInfo "DB migration success"
|
Right (_from, _to) -> $logInfo "DB migration success"
|
||||||
|
|
||||||
-- Return the foundation
|
-- Return the foundation
|
||||||
return $ mkFoundation pool
|
return $ mkFoundation pool capSignKey
|
||||||
|
|
||||||
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
||||||
-- applying some additional middlewares.
|
-- applying some additional middlewares.
|
||||||
|
|
82
src/Vervis/KeyFile.hs
Normal file
82
src/Vervis/KeyFile.hs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2019 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | Initial generation of key files, and later loading them.
|
||||||
|
--
|
||||||
|
-- Some programs need to generate a file, such as a signing key, and later
|
||||||
|
-- consistently use this file for program operation. And it's critical that
|
||||||
|
-- this very same file remains available. For example, if that file is an
|
||||||
|
-- encryption key used for encrypting all program state, then losing this file
|
||||||
|
-- means losing all program state.
|
||||||
|
--
|
||||||
|
-- In such a case, you may wish to have the following behavior:
|
||||||
|
--
|
||||||
|
-- * If we're in the initial program setup step, generate key files and store
|
||||||
|
-- them somewhere (file, database, etc.)
|
||||||
|
-- * If we aren't in that step anymore, require that these files are present,
|
||||||
|
-- and load them for use in the program. If a key file is missing, don't
|
||||||
|
-- just blindly generate a new one, because we *need* it to consistently be
|
||||||
|
-- the same file we originally generated. So if it's missing, report an
|
||||||
|
-- error to the user.
|
||||||
|
-- * Have a reliable way to determine whether we're in the initial setup
|
||||||
|
-- step, and make sure it's not easy to accidentally break this detection
|
||||||
|
--
|
||||||
|
-- This module implements such a mechanism for Vervis. It's really simple:
|
||||||
|
--
|
||||||
|
-- * If there are no tables in the DB, it's the initial setup phase
|
||||||
|
-- * If initial setup, require that key file doesn't exist, and generate one
|
||||||
|
-- * If not initial setup, require that key file exists
|
||||||
|
module Vervis.KeyFile
|
||||||
|
( KeyFile (..)
|
||||||
|
, isInitialSetup
|
||||||
|
, loadKeyFile
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Monad.Trans.Reader (runReaderT)
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Database.Persist.Schema (SchemaBackend, hasEntities)
|
||||||
|
import Database.Persist.Schema.SQL ()
|
||||||
|
import Database.Persist.Sql (SqlBackend, ConnectionPool, runSqlPool)
|
||||||
|
import System.Directory (doesFileExist)
|
||||||
|
|
||||||
|
import qualified Data.ByteString as B (readFile, writeFile)
|
||||||
|
|
||||||
|
class KeyFile a where
|
||||||
|
generateKey :: IO a
|
||||||
|
parseKey :: ByteString -> IO a
|
||||||
|
renderKey :: a -> ByteString
|
||||||
|
|
||||||
|
-- | Check whether we're in the initial setup step, in which we create keys.
|
||||||
|
-- Otherwise, we'll only use existing keys loaded from files.
|
||||||
|
isInitialSetup :: ConnectionPool -> SchemaBackend SqlBackend -> IO Bool
|
||||||
|
isInitialSetup pool sb =
|
||||||
|
flip runSqlPool pool . flip runReaderT sb $ not <$> hasEntities
|
||||||
|
|
||||||
|
loadKeyFile :: KeyFile a => Bool -> FilePath -> IO a
|
||||||
|
loadKeyFile setup path = do
|
||||||
|
e <- doesFileExist path
|
||||||
|
if e
|
||||||
|
then if setup
|
||||||
|
then fail $ "loadKeyFile: Initial setup but file already exists: " ++ path
|
||||||
|
else parseKey =<< B.readFile path
|
||||||
|
else if setup
|
||||||
|
then do
|
||||||
|
k <- generateKey
|
||||||
|
B.writeFile path $ renderKey k
|
||||||
|
return k
|
||||||
|
else fail $ "loadKeyFile: File not found: " ++ path
|
|
@ -143,6 +143,7 @@ library
|
||||||
Vervis.Handler.Workflow
|
Vervis.Handler.Workflow
|
||||||
Vervis.Import
|
Vervis.Import
|
||||||
Vervis.Import.NoFoundation
|
Vervis.Import.NoFoundation
|
||||||
|
Vervis.KeyFile
|
||||||
Vervis.MediaType
|
Vervis.MediaType
|
||||||
Vervis.Migration
|
Vervis.Migration
|
||||||
Vervis.Migration.Model
|
Vervis.Migration.Model
|
||||||
|
|
Loading…
Reference in a new issue