packages feed

acme-not-a-joke-0.1.0.0: src/Acme/NotAJoke/Api/Certificate.hs

module Acme.NotAJoke.Api.Certificate where

import Control.Lens hiding ((.=))
import Data.Aeson (encode)
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as ByteString
import Data.Coerce (coerce)
import qualified Network.Wreq as Wreq

import qualified Crypto.JOSE.JWS as JWS

import Acme.NotAJoke.Api.Endpoint
import Acme.NotAJoke.Api.JWS
import Acme.NotAJoke.Api.Nonce

{- | The goal of the whole ACME dance is to retrieve such Certificate.

You should be able to figure out most of the ACME flow by looking how you
build a Certificate and pulling all the dependencies.
-}
newtype Certificate = Certificate (Wreq.Response ByteString)
    deriving (Show)

-- | A PEM-file representation of a certificate.
newtype PEM = PEM ByteString

storeCert :: FilePath -> Certificate -> IO ()
storeCert path cert = ByteString.writeFile path (coerce $ readPEM cert)

-- | Lookup a PEM from a certificate.
readPEM :: Certificate -> PEM
readPEM (Certificate rsp) = PEM $ rsp ^. Wreq.responseBody

-- | Retrieves a certificate from an URL.
postGetCertificate :: JWS.JWK -> KID -> Url "certificate" -> Nonce -> IO (Maybe Certificate)
postGetCertificate jwk kid certificateUrl nonce = do
    let opts =
            Wreq.defaults
                & Wreq.header "Content-Type" .~ ["application/jose+json"]
                & Wreq.header "Accept" .~ ["application/pem-certificate-chain"]
    ebody <- (kidSign jwk ep kid nonce "")
    case ebody of
        Right body -> do
            e <- Wreq.postWith opts (wrequrl ep) $ encode body
            pure $ Just $ Certificate e
        Left err -> do
            print err
            pure Nothing
  where
    ep :: Endpoint "certificate"
    ep = coerce certificateUrl