summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Cady <d@jerkface.net>2016-01-20 10:48:29 -0500
committerAndrew Cady <d@jerkface.net>2016-01-20 10:48:29 -0500
commit010742ccd357994b2784a862378e6329a19147d2 (patch)
treea52fe8da350ef7fc5c96466e67e7ff1b07f077e4
parentd5f2692141aa6d2938a56d596d5b557be7257262 (diff)
Generate the private key with specified filename
(Won't generate if file exists.)
-rw-r--r--acme-encrypt.cabal3
-rw-r--r--acme.hs87
2 files changed, 49 insertions, 41 deletions
diff --git a/acme-encrypt.cabal b/acme-encrypt.cabal
index 057f305..cf510a2 100644
--- a/acme-encrypt.cabal
+++ b/acme-encrypt.cabal
@@ -23,7 +23,8 @@ executable acme-encrypt-exe
23 ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall 23 ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall
24 build-depends: base, 24 build-depends: base,
25 cryptonite, aeson, bytestring, base64-bytestring, SHA, 25 cryptonite, aeson, bytestring, base64-bytestring, SHA,
26 text, HsOpenSSL, process, wreq, lens, lens-aeson, optparse-applicative 26 text, HsOpenSSL, process, wreq, lens, lens-aeson,
27 optparse-applicative, directory
27 -- , acme-encrypt 28 -- , acme-encrypt
28 default-language: Haskell2010 29 default-language: Haskell2010
29 30
diff --git a/acme.hs b/acme.hs
index 832ebdd..57dafe6 100644
--- a/acme.hs
+++ b/acme.hs
@@ -1,5 +1,5 @@
1{-# LANGUAGE OverloadedStrings #-} 1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE RecordWildCards #-} 2{-# LANGUAGE RecordWildCards #-}
3{-# LANGUAGE ScopedTypeVariables #-} 3{-# LANGUAGE ScopedTypeVariables #-}
4 4
5-------------------------------------------------------------------------------- 5--------------------------------------------------------------------------------
@@ -7,28 +7,30 @@
7 7
8module Main where 8module Main where
9 9
10import Crypto.Number.Serialize (i2osp) 10import Control.Lens hiding ((.=))
11import Data.Aeson (encode, object, ToJSON(..), (.=)) 11import Control.Monad
12import Data.ByteString (ByteString) 12import Crypto.Number.Serialize (i2osp)
13import qualified Data.ByteString as B 13import Data.Aeson (ToJSON (..), encode, object, (.=))
14import qualified Data.ByteString.Char8 as BC 14import Data.Aeson.Lens hiding (key)
15import qualified Data.ByteString.Lazy.Char8 as LC 15import qualified Data.Aeson.Lens as JSON
16import qualified Data.ByteString.Lazy as LB 16import Data.ByteString (ByteString)
17import qualified Data.ByteString as B
17import qualified Data.ByteString.Base64.URL as Base64 18import qualified Data.ByteString.Base64.URL as Base64
18import Data.Digest.Pure.SHA (bytestringDigest, sha256) 19import qualified Data.ByteString.Char8 as BC
19import Data.Text.Encoding (decodeUtf8) 20import qualified Data.ByteString.Lazy as LB
20import qualified Data.Text as T 21import qualified Data.ByteString.Lazy.Char8 as LC
21import OpenSSL.EVP.PKey 22import Data.Digest.Pure.SHA (bytestringDigest, sha256)
22import OpenSSL.PEM 23import Data.Maybe
23import OpenSSL.RSA 24import qualified Data.Text as T
24import System.Process (readProcess) 25import Data.Text.Encoding (decodeUtf8)
25import Network.Wreq hiding (header) 26import Network.Wreq hiding (header)
26import Control.Lens hiding ((.=)) 27import OpenSSL.EVP.PKey
27import Data.Aeson.Lens hiding (key) 28import OpenSSL.PEM
28import qualified Data.Aeson.Lens as JSON 29import OpenSSL.RSA
29import Options.Applicative hiding (header) 30import Options.Applicative hiding (header)
30import qualified Options.Applicative as Opt 31import qualified Options.Applicative as Opt
31import Data.Maybe 32import System.Directory
33import System.Process (readProcess)
32 34
33directoryUrl :: String 35directoryUrl :: String
34directoryUrl = "https://acme-v01.api.letsencrypt.org/directory" 36directoryUrl = "https://acme-v01.api.letsencrypt.org/directory"
@@ -41,9 +43,9 @@ main = execParser opts >>= go
41 43
42data CmdOpts = CmdOpts { 44data CmdOpts = CmdOpts {
43 optKeyFile :: String, 45 optKeyFile :: String,
44 optDomain :: String, 46 optDomain :: String,
45 optEmail :: String, 47 optEmail :: Maybe String,
46 optTerms :: Maybe String 48 optTerms :: Maybe String
47} 49}
48 50
49defaultTerms :: String 51defaultTerms :: String
@@ -52,14 +54,19 @@ defaultTerms = "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
52cmdopts :: Parser CmdOpts 54cmdopts :: Parser CmdOpts
53cmdopts = CmdOpts <$> strOption (long "key" <> metavar "FILE" <> help "filename of your private RSA key") 55cmdopts = CmdOpts <$> strOption (long "key" <> metavar "FILE" <> help "filename of your private RSA key")
54 <*> strOption (long "domain" <> metavar "DOMAIN" <> help "the domain name to certify") 56 <*> strOption (long "domain" <> metavar "DOMAIN" <> help "the domain name to certify")
55 <*> strOption (long "email" <> metavar "ADDRESS" <> help "an email address with which to register an account") 57 <*> optional (strOption (long "email" <> metavar "ADDRESS" <> help "an email address with which to register an account"))
56 <*> optional (strOption (long "terms" <> metavar "URL" <> help "the terms param of the registration request")) 58 <*> optional (strOption (long "terms" <> metavar "URL" <> help "the terms param of the registration request"))
57 59
60genKey :: String -> IO ()
61genKey privKeyFile = readProcess "openssl" (words "genrsa 4096 -out" ++ [privKeyFile]) "" >>= writeFile privKeyFile
62
58go :: CmdOpts -> IO () 63go :: CmdOpts -> IO ()
59go (CmdOpts privKeyFile domain email termOverride) = do 64go (CmdOpts privKeyFile domain email termOverride) = do
60 let terms = fromMaybe defaultTerms termOverride 65 let terms = fromMaybe defaultTerms termOverride
66 doesFileExist privKeyFile >>= flip unless (genKey privKeyFile)
61 userKey_ <- readFile privKeyFile >>= flip readPrivateKey PwTTY 67 userKey_ <- readFile privKeyFile >>= flip readPrivateKey PwTTY
62 case toPublicKey $ fromPublicKey userKey_ of 68 pub <- maybe (return Nothing) (rsaCopyPublic >=> return . Just) (toKeyPair userKey_ :: Maybe RSAKeyPair)
69 case pub of
63 Nothing -> error "Error: failed to parse RSA key." 70 Nothing -> error "Error: failed to parse RSA key."
64 Just (userKey :: RSAPubKey) -> do 71 Just (userKey :: RSAPubKey) -> do
65 72
@@ -68,7 +75,7 @@ go (CmdOpts privKeyFile domain email termOverride) = do
68 let protected = b64 (header userKey nonce_) 75 let protected = b64 (header userKey nonce_)
69 76
70 -- Create user account 77 -- Create user account
71 signPayload "registration" privKeyFile userKey protected (registration email terms) 78 forM_ email $ \m -> signPayload "registration" privKeyFile userKey protected (registration m terms)
72 79
73 -- Obtain a challenge 80 -- Obtain a challenge
74 signPayload "challenge-request" privKeyFile userKey protected (authz domain) 81 signPayload "challenge-request" privKeyFile userKey protected (authz domain)
@@ -93,11 +100,11 @@ go (CmdOpts privKeyFile domain email termOverride) = do
93 signPayload "csr-request" privKeyFile userKey protected (csr csr_) 100 signPayload "csr-request" privKeyFile userKey protected (csr csr_)
94 101
95data Directory = Directory { 102data Directory = Directory {
96 _newCert :: String, 103 _newCert :: String,
97 _newAuthz :: String, 104 _newAuthz :: String,
98 _revokeCert :: String, 105 _revokeCert :: String,
99 _newReg :: String, 106 _newReg :: String,
100 _nonce :: String 107 _nonce :: String
101} 108}
102 109
103getDirectory :: String -> IO (Maybe Directory) 110getDirectory :: String -> IO (Maybe Directory)
@@ -195,16 +202,16 @@ encodeOrdered JWK{..} = LC.pack $
195 202
196-------------------------------------------------------------------------------- 203--------------------------------------------------------------------------------
197data Header = Header 204data Header = Header
198 { hAlg :: String 205 { hAlg :: String
199 , hJwk :: JWK 206 , hJwk :: JWK
200 , hNonce :: Maybe String 207 , hNonce :: Maybe String
201 } 208 }
202 deriving Show 209 deriving Show
203 210
204data JWK = JWK 211data JWK = JWK
205 { hE :: Integer 212 { hE :: Integer
206 , hKty :: String 213 , hKty :: String
207 , hN :: Integer 214 , hN :: Integer
208 } 215 }
209 deriving Show 216 deriving Show
210 217
@@ -222,7 +229,7 @@ instance ToJSON JWK where
222 ] 229 ]
223 230
224data Reg = Reg 231data Reg = Reg
225 { rMail :: String 232 { rMail :: String
226 , rAgreement :: String 233 , rAgreement :: String
227 } 234 }
228 deriving Show 235 deriving Show
@@ -235,9 +242,9 @@ instance ToJSON Reg where
235 ] 242 ]
236 243
237data Request = Request 244data Request = Request
238 { rHeader :: Header 245 { rHeader :: Header
239 , rProtected :: ByteString 246 , rProtected :: ByteString
240 , rPayload :: ByteString 247 , rPayload :: ByteString
241 , rSignature :: ByteString 248 , rSignature :: ByteString
242 } 249 }
243 deriving Show 250 deriving Show