mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
167 lines
7.0 KiB
Haskell
167 lines
7.0 KiB
Haskell
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE QuasiQuotes #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
module Handler.Register where
|
|
|
|
import Startlude hiding ( ask )
|
|
|
|
import Control.Carrier.Error.Either ( runError
|
|
, Error
|
|
, throwError
|
|
)
|
|
import Control.Carrier.Lift
|
|
import Control.Effect.Throw ( liftEither )
|
|
import Crypto.Cipher.Types
|
|
import Data.ByteArray.Sized
|
|
import qualified Data.ByteString as BS
|
|
import qualified Data.Text as T
|
|
import Database.Persist
|
|
import Network.HTTP.Types.Status
|
|
import Yesod.Core hiding ( expiresAt )
|
|
import Yesod.Persist.Core
|
|
|
|
import Daemon.ZeroConf
|
|
import Foundation
|
|
import Handler.Register.Nginx
|
|
import Handler.Register.Tor
|
|
import Handler.Types.HmacSig
|
|
import Handler.Types.Register
|
|
import Lib.Crypto
|
|
import Lib.Error
|
|
import Lib.Password
|
|
import Lib.ProductKey
|
|
import Lib.Ssl
|
|
import Lib.SystemPaths
|
|
import Lib.Tor
|
|
import Model
|
|
import Settings
|
|
|
|
postRegisterR :: Handler RegisterRes
|
|
postRegisterR = handleS9ErrT $ do
|
|
settings <- getsYesod appSettings
|
|
|
|
productKey <- liftIO . getProductKey . appFilesystemBase $ settings
|
|
req <- requireCheckJsonBody
|
|
|
|
-- Decrypt torkey and password. This acts as product key authentication.
|
|
torKeyFileContents <- decryptTorkey productKey req
|
|
password <- decryptPassword productKey req
|
|
rsaKeyFileContents <- decryptRSAKey productKey req
|
|
|
|
-- Check for existing registration.
|
|
checkExistingPasswordRegistration rootAccountName >>= \case
|
|
Nothing -> pure ()
|
|
Just claimedAt -> do
|
|
res <- mapExceptT (liftIO . runM . injectFilesystemBaseFromContext settings)
|
|
$ getRegistration productKey claimedAt
|
|
sendResponseStatus (Status 209 "Preexisting") res
|
|
|
|
-- install new tor hidden service key and restart tor
|
|
registerResTorAddress <- runM (injectFilesystemBaseFromContext settings $ bootupTor torKeyFileContents) >>= \case
|
|
Just t -> pure t
|
|
Nothing -> throwE TorServiceTimeoutE
|
|
|
|
-- install new ssl CA cert + nginx conf and restart nginx
|
|
registerResCert <-
|
|
runM . handleS9ErrC . liftEither <=< liftIO . runM . injectFilesystemBaseFromContext settings $ do
|
|
bootupHttpNginx
|
|
runError @S9Error $ bootupSslNginx rsaKeyFileContents
|
|
|
|
-- create an hmac of the torAddress + caCert for front end
|
|
registerResTorAddressSig <- produceProofOfKey productKey registerResTorAddress
|
|
registerResCertSig <- produceProofOfKey productKey registerResCert
|
|
|
|
-- must match CN in config/csr.conf
|
|
let registerResCertName = root_CA_CERT_NAME
|
|
registerResLanAddress <- runM . injectFilesystemBaseFromContext settings $ getStart9AgentHostnameLocal
|
|
|
|
-- registration successful, save the password hash
|
|
registerResClaimedAt <- saveAccountRegistration rootAccountName password
|
|
pure RegisterRes { .. }
|
|
|
|
|
|
decryptTorkey :: MonadIO m => Text -> RegisterReq -> S9ErrT m ByteString
|
|
decryptTorkey productKey RegisterReq { registerTorKey, registerTorCtrCounter, registerTorKdfSalt } = do
|
|
aesKey <- case mkAesKey registerTorKdfSalt productKey of
|
|
Just k -> pure k
|
|
Nothing -> throwE ProductKeyE
|
|
|
|
torKeyFileContents <- case makeIV registerTorCtrCounter of
|
|
Just counter -> pure $ decryptAes256Ctr aesKey counter (unSizedByteArray registerTorKey)
|
|
Nothing -> throwE $ ClientCryptographyE "invalid torkey aes ctr counter"
|
|
|
|
unless (torKeyPrefix `BS.isPrefixOf` torKeyFileContents) (throwE $ ClientCryptographyE "invalid tor key encryption")
|
|
|
|
pure torKeyFileContents
|
|
where torKeyPrefix = "== ed25519v1-secret: type0 =="
|
|
|
|
decryptPassword :: MonadIO m => Text -> RegisterReq -> S9ErrT m Text
|
|
decryptPassword productKey RegisterReq { registerPassword, registerPasswordCtrCounter, registerPasswordKdfSalt } = do
|
|
aesKey <- case mkAesKey registerPasswordKdfSalt productKey of
|
|
Just k -> pure k
|
|
Nothing -> throwE ProductKeyE
|
|
|
|
password <- case makeIV registerPasswordCtrCounter of
|
|
Just counter -> pure $ decryptAes256Ctr aesKey counter registerPassword
|
|
Nothing -> throwE $ ClientCryptographyE "invalid password aes ctr counter"
|
|
|
|
let decoded = decodeUtf8 password
|
|
unless (passwordPrefix `T.isPrefixOf` decoded) (throwE $ ClientCryptographyE "invalid password encryption")
|
|
|
|
-- drop password prefix in this case
|
|
pure . T.drop (T.length passwordPrefix) $ decoded
|
|
where passwordPrefix = "== password =="
|
|
|
|
decryptRSAKey :: MonadIO m => Text -> RegisterReq -> S9ErrT m ByteString
|
|
decryptRSAKey productKey RegisterReq { registerRsa, registerRsaCtrCounter, registerRsaKdfSalt } = do
|
|
aesKey <- case mkAesKey registerRsaKdfSalt productKey of
|
|
Just k -> pure k
|
|
Nothing -> throwE ProductKeyE
|
|
|
|
cert <- case makeIV registerRsaCtrCounter of
|
|
Just counter -> pure $ decryptAes256Ctr aesKey counter registerRsa
|
|
Nothing -> throwE $ ClientCryptographyE "invalid password aes ctr counter"
|
|
|
|
unless (certPrefix `BS.isPrefixOf` cert) (throwE $ ClientCryptographyE "invalid cert encryption")
|
|
|
|
pure cert
|
|
where certPrefix = "-----BEGIN RSA PRIVATE KEY-----"
|
|
|
|
|
|
checkExistingPasswordRegistration :: Text -> S9ErrT Handler (Maybe UTCTime)
|
|
checkExistingPasswordRegistration acctIdentifier = lift . runDB $ do
|
|
mAccount <- getBy $ UniqueAccount acctIdentifier
|
|
pure $ fmap (accountCreatedAt . entityVal) mAccount
|
|
|
|
saveAccountRegistration :: Text -> Text -> S9ErrT Handler UTCTime
|
|
saveAccountRegistration acctName password = lift . runDB $ do
|
|
now <- liftIO getCurrentTime
|
|
account <- setPassword password $ accountNoPw now
|
|
insert_ account
|
|
pure now
|
|
where accountNoPw t = Account t t acctName ""
|
|
|
|
produceProofOfKey :: MonadIO m => Text -> Text -> m HmacSig
|
|
produceProofOfKey key message = do
|
|
salt <- random16
|
|
let hmac = computeHmac key message salt
|
|
pure $ HmacSig hmac message salt
|
|
|
|
getRegistration :: (MonadIO m, HasFilesystemBase sig m, Has (Error S9Error) sig m) => Text -> UTCTime -> m RegisterRes
|
|
getRegistration productKey registerResClaimedAt = do
|
|
torAddress <- getAgentHiddenServiceUrlMaybe >>= \case
|
|
Nothing -> throwError $ NotFoundE "prior registration" "torAddress"
|
|
Just t -> pure $ t
|
|
caCert <- readSystemPath rootCaCertPath >>= \case
|
|
Nothing -> throwError $ NotFoundE "prior registration" "cert"
|
|
Just t -> pure t
|
|
|
|
-- create an hmac of the torAddress + caCert for front end
|
|
registerResTorAddressSig <- produceProofOfKey productKey torAddress
|
|
registerResCertSig <- produceProofOfKey productKey caCert
|
|
|
|
let registerResCertName = root_CA_CERT_NAME
|
|
registerResLanAddress <- getStart9AgentHostnameLocal
|
|
|
|
pure RegisterRes { .. }
|