diff options
author | Andrew Cady <d@jerkface.net> | 2016-01-21 01:46:06 -0500 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2016-01-21 01:48:47 -0500 |
commit | d313b531e9f83878face52c33955f477db164e1f (patch) | |
tree | bb991d836a48e9ae35d00bdc6dcffd95e02d98ec | |
parent | c7a9033041a0b6eb4006cb195a84233577f817e3 (diff) |
Generate RSA keys and CSRs using HsOpenSSL
Unfortunately, an external process is still needed to convert x509 CSRs
from PEM to DER.
-rw-r--r-- | acme-encrypt.cabal | 2 | ||||
-rw-r--r-- | acme.hs | 33 |
2 files changed, 25 insertions, 10 deletions
diff --git a/acme-encrypt.cabal b/acme-encrypt.cabal index 4d3aa44..66b80ec 100644 --- a/acme-encrypt.cabal +++ b/acme-encrypt.cabal | |||
@@ -24,7 +24,7 @@ executable acme-encrypt-exe | |||
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, |
27 | optparse-applicative, directory, mtl | 27 | optparse-applicative, directory, mtl, process-extras |
28 | -- , acme-encrypt | 28 | -- , acme-encrypt |
29 | default-language: Haskell2010 | 29 | default-language: Haskell2010 |
30 | 30 | ||
@@ -41,10 +41,11 @@ import OpenSSL.EVP.PKey | |||
41 | import OpenSSL.EVP.Sign | 41 | import OpenSSL.EVP.Sign |
42 | import OpenSSL.PEM | 42 | import OpenSSL.PEM |
43 | import OpenSSL.RSA | 43 | import OpenSSL.RSA |
44 | import OpenSSL.X509.Request | ||
44 | import Options.Applicative hiding (header) | 45 | import Options.Applicative hiding (header) |
45 | import qualified Options.Applicative as Opt | 46 | import qualified Options.Applicative as Opt |
46 | import System.Directory | 47 | import System.Directory |
47 | import System.Process (readProcess) | 48 | import System.Process.ByteString |
48 | 49 | ||
49 | stagingDirectoryUrl, liveDirectoryUrl :: String | 50 | stagingDirectoryUrl, liveDirectoryUrl :: String |
50 | liveDirectoryUrl = "https://acme-v01.api.letsencrypt.org/directory" | 51 | liveDirectoryUrl = "https://acme-v01.api.letsencrypt.org/directory" |
@@ -77,12 +78,24 @@ cmdopts = CmdOpts <$> strOption (long "key" <> metavar "FILE" <> help "filename | |||
77 | <*> switch (long "staging" <> help "use staging servers instead of live servers (certificates will not be real!)") | 78 | <*> switch (long "staging" <> help "use staging servers instead of live servers (certificates will not be real!)") |
78 | 79 | ||
79 | genKey :: String -> IO () | 80 | genKey :: String -> IO () |
80 | genKey privKeyFile = void $ readProcess "openssl" (words "genrsa -out" ++ [privKeyFile, "4096"]) "" | 81 | genKey privKeyFile = withOpenSSL $ do |
81 | 82 | kp <- generateRSAKey' 4096 65537 | |
82 | genReq :: String -> String -> String -> IO () | 83 | pem <- writePKCS8PrivateKey kp Nothing |
83 | genReq privKeyFile domain out = void $ readProcess "openssl" (args privKeyFile domain out) "" | 84 | writeFile privKeyFile pem |
84 | where | 85 | |
85 | args k d o = words "req -new -sha256 -outform DER -key" ++ [k, "-subj", "/CN=" ++ d, "-out", o] | 86 | genReq :: FilePath -> String -> IO ByteString |
87 | genReq domainKeyFile domain = withOpenSSL $ do | ||
88 | (Keys priv pub) <- readKeys domainKeyFile | ||
89 | Just dig <- getDigestByName "SHA256" | ||
90 | req <- newX509Req | ||
91 | setSubjectName req [("CN", domain)] | ||
92 | setVersion req 0 | ||
93 | setPublicKey req pub | ||
94 | signX509Req req priv (Just dig) | ||
95 | pem <- writeX509Req req ReqNewFormat | ||
96 | -- Sigh. No DER support for X509 reqs in HsOpenSSL. | ||
97 | (_, o, _) <- readProcessWithExitCode "openssl" (words "req -outform der") (encodeUtf8 $ T.pack pem) | ||
98 | return o | ||
86 | 99 | ||
87 | data Keys = Keys SomeKeyPair RSAPubKey | 100 | data Keys = Keys SomeKeyPair RSAPubKey |
88 | readKeys :: String -> IO Keys | 101 | readKeys :: String -> IO Keys |
@@ -108,11 +121,13 @@ go (CmdOpts privKeyFile domain challengeDir email termOverride staging) = do | |||
108 | 121 | ||
109 | doesDirectoryExist domain >>= flip unless (createDirectory domain) | 122 | doesDirectoryExist domain >>= flip unless (createDirectory domain) |
110 | doesFileExist domainKeyFile >>= flip unless (genKey domainKeyFile) | 123 | doesFileExist domainKeyFile >>= flip unless (genKey domainKeyFile) |
111 | doesFileExist domainCSRFile >>= flip unless (genReq domainKeyFile domain domainCSRFile) | ||
112 | 124 | ||
113 | csrData <- B.readFile domainCSRFile | ||
114 | keys <- readKeys privKeyFile | 125 | keys <- readKeys privKeyFile |
115 | 126 | ||
127 | doesFileExist domainCSRFile >>= flip unless (genReq domainKeyFile domain >>= B.writeFile domainCSRFile) | ||
128 | |||
129 | csrData <- B.readFile domainCSRFile | ||
130 | |||
116 | -- TODO: verify that challengeDir is writable before continuing. | 131 | -- TODO: verify that challengeDir is writable before continuing. |
117 | 132 | ||
118 | runACME directoryUrl keys $ do | 133 | runACME directoryUrl keys $ do |