packages feed

credential-store-0.1.0.0: src/System/CredentialStore/Windows.hs

module System.CredentialStore.Windows
    ( CredentialStore
    , getCredential
    , putCredential
    , deleteCredential
    , withCredentialStore
    ) where

import Control.Exception.Safe
import Control.Monad
import Data.ByteArray
import Foreign
import System.Win32.Time
import System.Win32.Types

import System.CredentialStore.WinTypes

data CredentialStore = CredentialStore

withCredentialStore :: (CredentialStore -> IO a) -> IO a
withCredentialStore f = f CredentialStore

getCredential :: ByteArray ba => CredentialStore -> String -> IO (Maybe ba)
getCredential _ name =
    withTString name $ \tstr ->
    alloca $ \pptr -> do
        result <- c_CredRead tstr cRED_TYPE_GENERIC 0 pptr
        if result
            then do
                ptr <- peek pptr
                cred <- finally
                  (do
                    rec <- peek ptr
                    decryptCredential (castPtr $ crCredentialBlob rec, crCredentialBlobSize rec)
                  ) (c_CredFree ptr)
                return $ Just cred
            else do
                errCode <- getLastError
                unless (errCode == eRROR_NOT_FOUND) $ failWith "CredRead" errCode
                return Nothing

putCredential :: ByteArrayAccess ba => CredentialStore -> String -> ba -> IO ()
putCredential _ name value =
    withEncryptedCredential value $ \(val, len) ->
    withTString name $ \tstr ->
    withTString "" $ \emptystr ->
    alloca $ \rec -> do
        poke rec CREDENTIAL
            { crFlags = 0
            , crType = cRED_TYPE_GENERIC
            , crTargetName = tstr
            , crComment = emptystr
            , crLastWritten = FILETIME 0
            , crCredentialBlobSize = len
            , crCredentialBlob = castPtr val
            , crPersist = cRED_PERSIST_LOCAL_MACHINE
            , crAttributeCount = 0
            , crAttributes = nullPtr
            , crTargetAlias = emptystr
            , crUserName = emptystr
            }
        failIfFalse_ "CredWrite" $ c_CredWrite rec 0

deleteCredential :: CredentialStore -> String -> IO ()
deleteCredential _ name = withTString name $ \tstr -> failIfFalse_ "CredDelete" $ c_CredDelete tstr cRED_TYPE_GENERIC 0

withEncryptedCredential :: ByteArrayAccess ba => ba -> ((LPTSTR, DWORD) -> IO a) -> IO a
withEncryptedCredential value f =
    withByteArray value $ \val ->
    alloca $ \protType ->
    alloca $ \sizeptr -> do
        let len = fromIntegral $ Data.ByteArray.length value
        poke sizeptr 0
        ret <- c_CredProtect True val len nullPtr sizeptr protType
        errCode <- getLastError
        unless (not ret && errCode == eRROR_INSUFFICIENT_BUFFER) $ failWith "CredProtect(NULL)" errCode
        needed <- peek sizeptr
        allocaBytes (fromIntegral needed * 2) $ \outputPtr -> do
            failIfFalse_ "CredProtect" $ c_CredProtect True val len outputPtr sizeptr protType
            outputLen <- peek sizeptr
            f (outputPtr, outputLen * 2)

decryptCredential :: ByteArray ba => (LPTSTR, DWORD) -> IO ba
decryptCredential (val, len) =
    alloca $ \sizeptr -> do
        poke sizeptr 0
        ret <- c_CredUnprotect True val len nullPtr sizeptr
        errCode <- getLastError
        unless (not ret && errCode == eRROR_INSUFFICIENT_BUFFER) $ failWith "CredUnprotect(NULL)" errCode
        needed <- peek sizeptr
        create (fromIntegral needed) $ \outputPtr ->
            failIfFalse_ "CredUnprotect" $ c_CredUnprotect True val len outputPtr sizeptr