diff options
author | Andrew Cady <d@jerkface.net> | 2016-01-27 07:27:59 -0500 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2016-01-27 07:27:59 -0500 |
commit | 552e6d79204cb6439c86ade4c1bf9bc785e47535 (patch) | |
tree | 56ab37fc1ab02c9687e7fe1a4c86cd2efa216c1b | |
parent | eaaebe924ba1cebc40a160a28f3779fd08c2181b (diff) |
Embed issuer certificate in binary
This permits the program to be run from outside the source directory.
-rw-r--r-- | acme-certify.cabal | 4 | ||||
-rw-r--r-- | acme-certify.hs | 23 | ||||
-rw-r--r-- | src/Network/ACME/Issuer.hs | 9 |
3 files changed, 21 insertions, 15 deletions
diff --git a/acme-certify.cabal b/acme-certify.cabal index 01b0e7d..42c4192 100644 --- a/acme-certify.cabal +++ b/acme-certify.cabal | |||
@@ -13,12 +13,12 @@ cabal-version: >=1.10 | |||
13 | 13 | ||
14 | library | 14 | library |
15 | hs-source-dirs: src | 15 | hs-source-dirs: src |
16 | exposed-modules: Network.ACME, Network.ACME.Encoding | 16 | exposed-modules: Network.ACME, Network.ACME.Encoding, Network.ACME.Issuer |
17 | build-depends: base >= 4.7 && < 5, | 17 | build-depends: base >= 4.7 && < 5, |
18 | cryptonite, aeson, bytestring, base64-bytestring, SHA, | 18 | cryptonite, aeson, bytestring, base64-bytestring, SHA, |
19 | mtl, text, HsOpenSSL, wreq, lens, lens-aeson, time, | 19 | mtl, text, HsOpenSSL, wreq, lens, lens-aeson, time, |
20 | email-validate, pipes, directory, network-uri, errors, | 20 | email-validate, pipes, directory, network-uri, errors, |
21 | resourcet | 21 | resourcet, file-embed |
22 | default-language: Haskell2010 | 22 | default-language: Haskell2010 |
23 | 23 | ||
24 | executable acme-certify | 24 | executable acme-certify |
diff --git a/acme-certify.hs b/acme-certify.hs index 219b0c1..98f6711 100644 --- a/acme-certify.hs +++ b/acme-certify.hs | |||
@@ -16,6 +16,7 @@ import Network.ACME (canProvision, certify, | |||
16 | ensureWritableDir, fileProvisioner, | 16 | ensureWritableDir, fileProvisioner, |
17 | genReq, (</>)) | 17 | genReq, (</>)) |
18 | import Network.ACME.Encoding (Keys (..), readKeys) | 18 | import Network.ACME.Encoding (Keys (..), readKeys) |
19 | import Network.ACME.Issuer (letsEncryptX1CrossSigned) | ||
19 | import Network.URI | 20 | import Network.URI |
20 | import OpenSSL | 21 | import OpenSSL |
21 | import OpenSSL.DH | 22 | import OpenSSL.DH |
@@ -29,9 +30,10 @@ import System.IO | |||
29 | import Text.Domain.Validate hiding (validate) | 30 | import Text.Domain.Validate hiding (validate) |
30 | import Text.Email.Validate | 31 | import Text.Email.Validate |
31 | 32 | ||
32 | stagingDirectoryUrl, liveDirectoryUrl :: URI | 33 | stagingDirectoryUrl, liveDirectoryUrl, defaultTerms :: URI |
33 | Just liveDirectoryUrl = parseAbsoluteURI "https://acme-v01.api.letsencrypt.org/directory" | 34 | Just liveDirectoryUrl = parseAbsoluteURI "https://acme-v01.api.letsencrypt.org/directory" |
34 | Just stagingDirectoryUrl = parseAbsoluteURI "https://acme-staging.api.letsencrypt.org/directory" | 35 | Just stagingDirectoryUrl = parseAbsoluteURI "https://acme-staging.api.letsencrypt.org/directory" |
36 | Just defaultTerms = parseAbsoluteURI "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" | ||
35 | 37 | ||
36 | main :: IO () | 38 | main :: IO () |
37 | main = execParser opts >>= go | 39 | main = execParser opts >>= go |
@@ -54,9 +56,6 @@ data CmdOpts = CmdOpts { | |||
54 | optSkipProvisionCheck :: Bool | 56 | optSkipProvisionCheck :: Bool |
55 | } | 57 | } |
56 | 58 | ||
57 | defaultTerms :: URI | ||
58 | Just defaultTerms = parseAbsoluteURI "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" | ||
59 | |||
60 | cmdopts :: Parser CmdOpts | 59 | cmdopts :: Parser CmdOpts |
61 | cmdopts = CmdOpts <$> strOption (long "key" <> metavar "FILE" <> | 60 | cmdopts = CmdOpts <$> strOption (long "key" <> metavar "FILE" <> |
62 | help "Filename of your private RSA key") | 61 | help "Filename of your private RSA key") |
@@ -111,26 +110,24 @@ go CmdOpts { .. } = do | |||
111 | domainDir = fromMaybe (head optDomains) optDomainDir | 110 | domainDir = fromMaybe (head optDomains) optDomainDir |
112 | privKeyFile = optKeyFile | 111 | privKeyFile = optKeyFile |
113 | requestDomains = map domainName' optDomains | 112 | requestDomains = map domainName' optDomains |
113 | email = either (error . ("Error: invalid email address: " ++)) id . validate . fromString <$> optEmail | ||
114 | 114 | ||
115 | doesDirectoryExist domainDir `otherwiseM` createDirectory domainDir | 115 | issuerCert <- readX509 letsEncryptX1CrossSigned |
116 | 116 | ||
117 | let issuerCertFile = "lets-encrypt-x1-cross-signed.pem" | 117 | seq email (return ()) |
118 | issuerCert <- readFile issuerCertFile >>= readX509 | 118 | doesDirectoryExist domainDir `otherwiseM` createDirectory domainDir |
119 | challengeDir <- ensureWritableDir optChallengeDir "challenge directory" | ||
120 | void $ ensureWritableDir domainDir "domain directory" | ||
119 | 121 | ||
120 | Just domainKeys <- getOrCreateKeys domainKeyFile | 122 | Just domainKeys <- getOrCreateKeys domainKeyFile |
121 | Just keys <- getOrCreateKeys privKeyFile | 123 | Just keys <- getOrCreateKeys privKeyFile |
122 | 124 | ||
123 | challengeDir <- ensureWritableDir optChallengeDir "challenge directory" | ||
124 | void $ ensureWritableDir domainDir "domain directory" | ||
125 | |||
126 | unless optSkipProvisionCheck $ | 125 | unless optSkipProvisionCheck $ |
127 | forM_ requestDomains $ canProvision challengeDir >=> | 126 | forM_ requestDomains $ canProvision challengeDir >=> |
128 | (`unless` error "Error: cannot provision files to web server via challenge directory") | 127 | (`unless` error "Error: cannot provision files to web server via challenge directory") |
129 | 128 | ||
130 | certReq <- genReq domainKeys requestDomains | 129 | certReq <- genReq domainKeys requestDomains |
131 | 130 | ||
132 | let email = either (error . ("Error: invalid email address: " ++)) id . validate . fromString <$> optEmail | ||
133 | |||
134 | dh <- if optSkipDH then return Nothing else Just <$> getOrCreateDH domainDhFile | 131 | dh <- if optSkipDH then return Nothing else Just <$> getOrCreateDH domainDhFile |
135 | 132 | ||
136 | certificate <- certify directoryUrl keys ((,) terms <$> email) (fileProvisioner challengeDir) certReq | 133 | certificate <- certify directoryUrl keys ((,) terms <$> email) (fileProvisioner challengeDir) certReq |
diff --git a/src/Network/ACME/Issuer.hs b/src/Network/ACME/Issuer.hs new file mode 100644 index 0000000..451aa14 --- /dev/null +++ b/src/Network/ACME/Issuer.hs | |||
@@ -0,0 +1,9 @@ | |||
1 | {-# LANGUAGE TemplateHaskell #-} | ||
2 | |||
3 | module Network.ACME.Issuer where | ||
4 | |||
5 | import Data.ByteString.Char8 | ||
6 | import Data.FileEmbed | ||
7 | |||
8 | letsEncryptX1CrossSigned :: String | ||
9 | letsEncryptX1CrossSigned = unpack $(embedFile "lets-encrypt-x1-cross-signed.pem") | ||