diff options
author | Andrew Cady <d@jerkface.net> | 2016-01-20 09:39:11 -0500 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2016-01-20 09:42:48 -0500 |
commit | d5f2692141aa6d2938a56d596d5b557be7257262 (patch) | |
tree | 81793984f4e3d4ab266afdf72fba24df32fed8ba | |
parent | 685a14d8ab5e92b57fce1e997978bd6607c2aac1 (diff) |
Command line options replace hard-coded values
-rw-r--r-- | acme-encrypt.cabal | 2 | ||||
-rw-r--r-- | acme.hs | 72 |
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 | ||
@@ -19,22 +19,48 @@ import Data.Digest.Pure.SHA (bytestringDigest, sha256) | |||
19 | import Data.Text.Encoding (decodeUtf8) | 19 | import Data.Text.Encoding (decodeUtf8) |
20 | import qualified Data.Text as T | 20 | import qualified Data.Text as T |
21 | import OpenSSL.EVP.PKey | 21 | import OpenSSL.EVP.PKey |
22 | import OpenSSL.PEM (readPublicKey) | 22 | import OpenSSL.PEM |
23 | import OpenSSL.RSA | 23 | import OpenSSL.RSA |
24 | import System.Process (readProcess) | 24 | import System.Process (readProcess) |
25 | import Network.Wreq hiding (header) | 25 | import Network.Wreq hiding (header) |
26 | import Control.Lens hiding ((.=)) | 26 | import Control.Lens hiding ((.=)) |
27 | import Data.Aeson.Lens hiding (key) | 27 | import Data.Aeson.Lens hiding (key) |
28 | import qualified Data.Aeson.Lens as JSON | 28 | import qualified Data.Aeson.Lens as JSON |
29 | import Options.Applicative hiding (header) | ||
30 | import qualified Options.Applicative as Opt | ||
31 | import Data.Maybe | ||
29 | 32 | ||
30 | directoryUrl :: String | 33 | directoryUrl :: String |
31 | directoryUrl = "https://acme-v01.api.letsencrypt.org/directory" | 34 | directoryUrl = "https://acme-v01.api.letsencrypt.org/directory" |
32 | 35 | ||
33 | main :: IO () | 36 | main :: IO () |
34 | main = do | 37 | main = 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 | |||
42 | data CmdOpts = CmdOpts { | ||
43 | optKeyFile :: String, | ||
44 | optDomain :: String, | ||
45 | optEmail :: String, | ||
46 | optTerms :: Maybe String | ||
47 | } | ||
48 | |||
49 | defaultTerms :: String | ||
50 | defaultTerms = "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" | ||
51 | |||
52 | cmdopts :: Parser CmdOpts | ||
53 | 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") | ||
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 | |||
58 | go :: CmdOpts -> IO () | ||
59 | go (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 | ||
79 | data Directory = Directory { | 95 | data 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. |
99 | signPayload :: RSAKey k => String -> k -> ByteString -> ByteString -> IO () | 115 | signPayload :: RSAKey k => String -> String -> k -> ByteString -> ByteString -> IO () |
100 | signPayload name key protected payload = do | 116 | signPayload 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. |
111 | sign :: String -> IO ByteString | 127 | sign :: String -> String -> IO ByteString |
112 | sign name = do | 128 | sign 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 | ||
117 | sign_ :: String -> String -> IO () | 133 | sign_ :: String -> String -> String -> IO () |
118 | sign_ inp out = do | 134 | sign_ 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 | ] |