diff options
author | Andrew Cady <d@jerkface.net> | 2016-01-20 10:48:29 -0500 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2016-01-20 10:48:29 -0500 |
commit | 010742ccd357994b2784a862378e6329a19147d2 (patch) | |
tree | a52fe8da350ef7fc5c96466e67e7ff1b07f077e4 | |
parent | d5f2692141aa6d2938a56d596d5b557be7257262 (diff) |
Generate the private key with specified filename
(Won't generate if file exists.)
-rw-r--r-- | acme-encrypt.cabal | 3 | ||||
-rw-r--r-- | acme.hs | 87 |
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 | ||
@@ -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 | ||
8 | module Main where | 8 | module Main where |
9 | 9 | ||
10 | import Crypto.Number.Serialize (i2osp) | 10 | import Control.Lens hiding ((.=)) |
11 | import Data.Aeson (encode, object, ToJSON(..), (.=)) | 11 | import Control.Monad |
12 | import Data.ByteString (ByteString) | 12 | import Crypto.Number.Serialize (i2osp) |
13 | import qualified Data.ByteString as B | 13 | import Data.Aeson (ToJSON (..), encode, object, (.=)) |
14 | import qualified Data.ByteString.Char8 as BC | 14 | import Data.Aeson.Lens hiding (key) |
15 | import qualified Data.ByteString.Lazy.Char8 as LC | 15 | import qualified Data.Aeson.Lens as JSON |
16 | import qualified Data.ByteString.Lazy as LB | 16 | import Data.ByteString (ByteString) |
17 | import qualified Data.ByteString as B | ||
17 | import qualified Data.ByteString.Base64.URL as Base64 | 18 | import qualified Data.ByteString.Base64.URL as Base64 |
18 | import Data.Digest.Pure.SHA (bytestringDigest, sha256) | 19 | import qualified Data.ByteString.Char8 as BC |
19 | import Data.Text.Encoding (decodeUtf8) | 20 | import qualified Data.ByteString.Lazy as LB |
20 | import qualified Data.Text as T | 21 | import qualified Data.ByteString.Lazy.Char8 as LC |
21 | import OpenSSL.EVP.PKey | 22 | import Data.Digest.Pure.SHA (bytestringDigest, sha256) |
22 | import OpenSSL.PEM | 23 | import Data.Maybe |
23 | import OpenSSL.RSA | 24 | import qualified Data.Text as T |
24 | import System.Process (readProcess) | 25 | import Data.Text.Encoding (decodeUtf8) |
25 | import Network.Wreq hiding (header) | 26 | import Network.Wreq hiding (header) |
26 | import Control.Lens hiding ((.=)) | 27 | import OpenSSL.EVP.PKey |
27 | import Data.Aeson.Lens hiding (key) | 28 | import OpenSSL.PEM |
28 | import qualified Data.Aeson.Lens as JSON | 29 | import OpenSSL.RSA |
29 | import Options.Applicative hiding (header) | 30 | import Options.Applicative hiding (header) |
30 | import qualified Options.Applicative as Opt | 31 | import qualified Options.Applicative as Opt |
31 | import Data.Maybe | 32 | import System.Directory |
33 | import System.Process (readProcess) | ||
32 | 34 | ||
33 | directoryUrl :: String | 35 | directoryUrl :: String |
34 | directoryUrl = "https://acme-v01.api.letsencrypt.org/directory" | 36 | directoryUrl = "https://acme-v01.api.letsencrypt.org/directory" |
@@ -41,9 +43,9 @@ main = execParser opts >>= go | |||
41 | 43 | ||
42 | data CmdOpts = CmdOpts { | 44 | data 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 | ||
49 | defaultTerms :: String | 51 | defaultTerms :: String |
@@ -52,14 +54,19 @@ defaultTerms = "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" | |||
52 | cmdopts :: Parser CmdOpts | 54 | cmdopts :: Parser CmdOpts |
53 | cmdopts = CmdOpts <$> strOption (long "key" <> metavar "FILE" <> help "filename of your private RSA key") | 55 | cmdopts = 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 | ||
60 | genKey :: String -> IO () | ||
61 | genKey privKeyFile = readProcess "openssl" (words "genrsa 4096 -out" ++ [privKeyFile]) "" >>= writeFile privKeyFile | ||
62 | |||
58 | go :: CmdOpts -> IO () | 63 | go :: CmdOpts -> IO () |
59 | go (CmdOpts privKeyFile domain email termOverride) = do | 64 | go (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 | ||
95 | data Directory = Directory { | 102 | data 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 | ||
103 | getDirectory :: String -> IO (Maybe Directory) | 110 | getDirectory :: String -> IO (Maybe Directory) |
@@ -195,16 +202,16 @@ encodeOrdered JWK{..} = LC.pack $ | |||
195 | 202 | ||
196 | -------------------------------------------------------------------------------- | 203 | -------------------------------------------------------------------------------- |
197 | data Header = Header | 204 | data 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 | ||
204 | data JWK = JWK | 211 | data 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 | ||
224 | data Reg = Reg | 231 | data 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 | ||
237 | data Request = Request | 244 | data 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 |