summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Cady <d@jerkface.net>2016-01-20 09:39:11 -0500
committerAndrew Cady <d@jerkface.net>2016-01-20 09:42:48 -0500
commitd5f2692141aa6d2938a56d596d5b557be7257262 (patch)
tree81793984f4e3d4ab266afdf72fba24df32fed8ba
parent685a14d8ab5e92b57fce1e997978bd6607c2aac1 (diff)
Command line options replace hard-coded values
-rw-r--r--acme-encrypt.cabal2
-rw-r--r--acme.hs72
2 files changed, 45 insertions, 29 deletions
diff --git a/acme-encrypt.cabal b/acme-encrypt.cabal
index 45cb9de..057f305 100644
--- a/acme-encrypt.cabal
+++ b/acme-encrypt.cabal
@@ -23,7 +23,7 @@ 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 26 text, HsOpenSSL, process, wreq, lens, lens-aeson, optparse-applicative
27 -- , acme-encrypt 27 -- , acme-encrypt
28 default-language: Haskell2010 28 default-language: Haskell2010
29 29
diff --git a/acme.hs b/acme.hs
index b755f19..832ebdd 100644
--- a/acme.hs
+++ b/acme.hs
@@ -19,22 +19,48 @@ import Data.Digest.Pure.SHA (bytestringDigest, sha256)
19import Data.Text.Encoding (decodeUtf8) 19import Data.Text.Encoding (decodeUtf8)
20import qualified Data.Text as T 20import qualified Data.Text as T
21import OpenSSL.EVP.PKey 21import OpenSSL.EVP.PKey
22import OpenSSL.PEM (readPublicKey) 22import OpenSSL.PEM
23import OpenSSL.RSA 23import OpenSSL.RSA
24import System.Process (readProcess) 24import System.Process (readProcess)
25import Network.Wreq hiding (header) 25import Network.Wreq hiding (header)
26import Control.Lens hiding ((.=)) 26import Control.Lens hiding ((.=))
27import Data.Aeson.Lens hiding (key) 27import Data.Aeson.Lens hiding (key)
28import qualified Data.Aeson.Lens as JSON 28import qualified Data.Aeson.Lens as JSON
29import Options.Applicative hiding (header)
30import qualified Options.Applicative as Opt
31import Data.Maybe
29 32
30directoryUrl :: String 33directoryUrl :: String
31directoryUrl = "https://acme-v01.api.letsencrypt.org/directory" 34directoryUrl = "https://acme-v01.api.letsencrypt.org/directory"
32 35
33main :: IO () 36main :: IO ()
34main = do 37main = execParser opts >>= go
35 userKey_ <- readFile "user.pub" >>= readPublicKey 38 where
36 case toPublicKey userKey_ of 39 opts = info (helper <*> cmdopts) (fullDesc <> progDesc detailedDescription <> Opt.header "Let's Encrypt! ACME client")
37 Nothing -> error "Not a public RSA key." 40 detailedDescription = "This is a work in progress."
41
42data CmdOpts = CmdOpts {
43 optKeyFile :: String,
44 optDomain :: String,
45 optEmail :: String,
46 optTerms :: Maybe String
47}
48
49defaultTerms :: String
50defaultTerms = "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
51
52cmdopts :: Parser CmdOpts
53cmdopts = 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")
55 <*> 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"))
57
58go :: CmdOpts -> IO ()
59go (CmdOpts privKeyFile domain email termOverride) = do
60 let terms = fromMaybe defaultTerms termOverride
61 userKey_ <- readFile privKeyFile >>= flip readPrivateKey PwTTY
62 case toPublicKey $ fromPublicKey userKey_ of
63 Nothing -> error "Error: failed to parse RSA key."
38 Just (userKey :: RSAPubKey) -> do 64 Just (userKey :: RSAPubKey) -> do
39 65
40 Just nonce_ <- getNonce 66 Just nonce_ <- getNonce
@@ -42,10 +68,10 @@ main = do
42 let protected = b64 (header userKey nonce_) 68 let protected = b64 (header userKey nonce_)
43 69
44 -- Create user account 70 -- Create user account
45 signPayload "registration" userKey protected (registration email terms) 71 signPayload "registration" privKeyFile userKey protected (registration email terms)
46 72
47 -- Obtain a challenge 73 -- Obtain a challenge
48 signPayload "challenge-request" userKey protected (authz domain) 74 signPayload "challenge-request" privKeyFile userKey protected (authz domain)
49 75
50 -- Answser the challenge 76 -- Answser the challenge
51 let thumb = thumbprint (JWK (rsaE userKey) "RSA" (rsaN userKey)) 77 let thumb = thumbprint (JWK (rsaE userKey) "RSA" (rsaN userKey))
@@ -58,23 +84,13 @@ main = do
58 putStrLn ("With content:\n" ++ BC.unpack thumbtoken) 84 putStrLn ("With content:\n" ++ BC.unpack thumbtoken)
59 85
60 -- Notify Let's Encrypt we answsered the challenge 86 -- Notify Let's Encrypt we answsered the challenge
61 signPayload "challenge-response" userKey protected (challenge thumbtoken) 87 signPayload "challenge-response" privKeyFile userKey protected (challenge thumbtoken)
62 88
63 -- Wait for challenge validation 89 -- Wait for challenge validation
64 90
65 -- Send a CSR and get a certificate 91 -- Send a CSR and get a certificate
66 csr_ <- B.readFile (domain ++ ".csr.der") 92 csr_ <- B.readFile (domain ++ ".csr.der")
67 signPayload "csr-request" userKey protected (csr csr_) 93 signPayload "csr-request" privKeyFile userKey protected (csr csr_)
68
69 where
70 email :: String
71 email = "noteed@gmail.com"
72
73 domain :: String
74 domain = "aaa.reesd.com"
75
76 terms :: String
77 terms = "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
78 94
79data Directory = Directory { 95data Directory = Directory {
80 _newCert :: String, 96 _newCert :: String,
@@ -96,10 +112,10 @@ getNonce = fmap _nonce <$> getDirectory directoryUrl
96 112
97-------------------------------------------------------------------------------- 113--------------------------------------------------------------------------------
98-- | Sign and write a payload to a file with a nonce-protected header. 114-- | Sign and write a payload to a file with a nonce-protected header.
99signPayload :: RSAKey k => String -> k -> ByteString -> ByteString -> IO () 115signPayload :: RSAKey k => String -> String -> k -> ByteString -> ByteString -> IO ()
100signPayload name key protected payload = do 116signPayload name privKeyFile key protected payload = do
101 writePayload name protected payload 117 writePayload name protected payload
102 sig <- sign name 118 sig <- sign privKeyFile name
103 writeBody name key protected payload sig 119 writeBody name key protected payload sig
104 120
105-- | Write a payload to file with a nonce-protected header. 121-- | Write a payload to file with a nonce-protected header.
@@ -108,17 +124,17 @@ writePayload name protected payload =
108 LB.writeFile (name ++ ".txt") (LB.fromChunks [protected, ".", payload]) 124 LB.writeFile (name ++ ".txt") (LB.fromChunks [protected, ".", payload])
109 125
110-- | Sign a payload file using the user key. 126-- | Sign a payload file using the user key.
111sign :: String -> IO ByteString 127sign :: String -> String -> IO ByteString
112sign name = do 128sign privKeyFile name = do
113 sign_ (name ++ ".txt") (name ++ ".sig") 129 sign_ privKeyFile (name ++ ".txt") (name ++ ".sig")
114 sig_ <- B.readFile (name ++ ".sig") 130 sig_ <- B.readFile (name ++ ".sig")
115 return (b64 sig_) 131 return (b64 sig_)
116 132
117sign_ :: String -> String -> IO () 133sign_ :: String -> String -> String -> IO ()
118sign_ inp out = do 134sign_ privKeyFile inp out = do
119 _ <- readProcess "openssl" 135 _ <- readProcess "openssl"
120 [ "dgst", "-sha256" 136 [ "dgst", "-sha256"
121 , "-sign", "user.key" 137 , "-sign", privKeyFile
122 , "-out", out 138 , "-out", out
123 , inp 139 , inp
124 ] 140 ]