@@ -17,10 +17,12 @@ module Cardano.X509.Configuration
17
17
18
18
-- * Description of Certificates
19
19
, CertDescription (.. )
20
- , fromConfiguration
21
20
22
21
-- * Effectful Functions
23
22
, ConfigurationKey (.. )
23
+ , ErrInvalidTLSConfiguration
24
+ , ErrInvalidExpiryDays
25
+ , fromConfiguration
24
26
, decodeConfigFile
25
27
, genCertificate
26
28
) where
@@ -36,34 +38,27 @@ import Data.Hourglass (Minutes (..), Period (..), dateAddPeriod,
36
38
import Data.List (stripPrefix )
37
39
import Data.Semigroup ((<>) )
38
40
import Data.String (fromString )
39
- import Data.X509 (AltName (.. ), DistinguishedName (.. ),
40
- DnElement (.. ), ExtAuthorityKeyId (.. ),
41
- ExtBasicConstraints (.. ), ExtExtendedKeyUsage (.. ),
42
- ExtKeyUsage (.. ), ExtKeyUsageFlag (.. ),
43
- ExtKeyUsagePurpose (.. ), ExtSubjectAltName (.. ),
44
- ExtSubjectKeyId (.. ), ExtensionRaw , Extensions (.. ),
45
- PubKey ( .. ), SignedCertificate , extensionEncode , hashDN )
41
+ import Data.X509 (DistinguishedName (.. ), DnElement (.. ),
42
+ ExtAuthorityKeyId (.. ), ExtBasicConstraints (.. ),
43
+ ExtExtendedKeyUsage (.. ), ExtKeyUsage (.. ),
44
+ ExtKeyUsageFlag (.. ), ExtKeyUsagePurpose (.. ),
45
+ ExtSubjectAltName (.. ), ExtSubjectKeyId (.. ),
46
+ ExtensionRaw , Extensions (.. ), PubKey (.. ),
47
+ SignedCertificate , extensionEncode , hashDN )
46
48
import Data.X509.Validation (ValidationChecks (.. ), defaultChecks )
47
49
import Data.Yaml (decodeFileEither , parseMonad , withObject )
48
50
import GHC.Generics (Generic )
49
- import Net.IP (IP , case_ , decode )
50
- import Net.IPv4 (IPv4 (.. ))
51
- import Net.IPv6 (IPv6 (.. ))
52
- import Network.Transport.Internal (encodeWord32 )
53
51
import System.IO (FilePath )
54
52
import Time.System (dateCurrent )
55
53
import Time.Types (DateTime (.. ))
56
54
57
- import Data.X509.Extra (signAlgRSA256 , signCertificate )
55
+ import Data.X509.Extra (parseSAN , signAlgRSA256 , signCertificate )
58
56
59
57
import qualified Data.Aeson as Aeson
60
58
import qualified Data.Aeson.Types as Aeson
61
- import qualified Data.ByteString.Builder as BS
62
- import qualified Data.ByteString.Lazy as LBS
63
59
import qualified Data.Char as Char
64
60
import qualified Data.HashMap.Lazy as HM
65
61
import qualified Data.List.NonEmpty as NonEmpty
66
- import qualified Data.Text as T
67
62
import qualified Data.X509 as X509
68
63
69
64
@@ -76,7 +71,7 @@ data TLSConfiguration = TLSConfiguration
76
71
{ tlsCa :: CertConfiguration
77
72
, tlsServer :: ServerConfiguration
78
73
, tlsClients :: [CertConfiguration ]
79
- } deriving (Generic )
74
+ } deriving (Generic , Show , Eq )
80
75
81
76
instance FromJSON TLSConfiguration where
82
77
parseJSON = Aeson. genericParseJSON (aesonDropPrefix " tls" )
@@ -86,14 +81,14 @@ data DirConfiguration = DirConfiguration
86
81
{ outDirServer :: FilePath
87
82
, outDirClients :: FilePath
88
83
, outDirCA :: Maybe FilePath
89
- }
84
+ } deriving ( Generic , Show , Eq )
90
85
91
86
-- | Foreign Certificate Configuration
92
87
data CertConfiguration = CertConfiguration
93
88
{ certOrganization :: String
94
89
, certCommonName :: String
95
90
, certExpiryDays :: Int
96
- } deriving (Generic )
91
+ } deriving (Generic , Show , Eq )
97
92
98
93
instance FromJSON CertConfiguration where
99
94
parseJSON = Aeson. genericParseJSON (aesonDropPrefix " cert" )
@@ -102,7 +97,7 @@ instance FromJSON CertConfiguration where
102
97
data ServerConfiguration = ServerConfiguration
103
98
{ serverConfiguration :: CertConfiguration
104
99
, serverAltNames :: NonEmpty String
105
- }
100
+ } deriving ( Generic , Show , Eq )
106
101
107
102
-- NOTE We keep the declaration structure 'flat' such that servers config
108
103
-- are simply client config with an extra field 'altDNS'
@@ -137,12 +132,37 @@ data CertDescription m pub priv outdir = CertDescription
137
132
}
138
133
139
134
135
+ --
136
+ -- Effectful Functions
137
+ --
138
+
139
+
140
+ -- | Type-alias for signature readability
141
+ newtype ConfigurationKey = ConfigurationKey
142
+ { getConfigurationKey :: String
143
+ } deriving (Eq , Show )
144
+
145
+
146
+ newtype ErrInvalidExpiryDays
147
+ = ErrInvalidExpiryDays String
148
+ deriving (Show )
149
+
150
+ instance Exception ErrInvalidExpiryDays
151
+
152
+
153
+ newtype ErrInvalidTLSConfiguration
154
+ = ErrInvalidTLSConfiguration String
155
+ deriving (Show )
156
+
157
+ instance Exception ErrInvalidTLSConfiguration
158
+
159
+
140
160
-- | Describe a list of certificates to generate & sign from a foreign config
141
161
--
142
162
-- Description can then be used with @genCertificate@ to obtain corresponding
143
163
-- certificate
144
164
fromConfiguration
145
- :: Applicative m
165
+ :: ( Applicative m )
146
166
=> TLSConfiguration -- ^ Foreign TLS configuration / setup
147
167
-> DirConfiguration -- ^ Output directories configuration
148
168
-> m (pub , priv ) -- ^ Key pair generator
@@ -201,15 +221,6 @@ fromConfiguration tlsConf dirConf genKeys (caPub, caPriv) =
201
221
(caConfig, svConfig : clConfigs)
202
222
203
223
204
- --
205
- -- Effectful Functions
206
- --
207
-
208
- -- | Type-alias for signature readability
209
- newtype ConfigurationKey = ConfigurationKey
210
- { getConfigurationKey :: String
211
- } deriving (Eq , Show )
212
-
213
224
-- | Decode a configuration file (.yaml). The expected file structure is:
214
225
-- <configuration-key>:
215
226
-- tls:
@@ -220,17 +231,18 @@ newtype ConfigurationKey = ConfigurationKey
220
231
-- where the 'configuration-key' represents the target environment (dev, test,
221
232
-- bench, etc.).
222
233
decodeConfigFile
223
- :: (MonadIO m , MonadFail m )
234
+ :: (MonadIO m , MonadThrow m )
224
235
=> ConfigurationKey -- ^ Target configuration Key
225
236
-> FilePath -- ^ Target configuration file
226
237
-> m TLSConfiguration
227
238
decodeConfigFile (ConfigurationKey cKey) filepath =
228
239
decodeFileMonad filepath >>= parseMonad parser
229
240
where
230
- errMsg key = " Invalid TLS Configuration: property '" <> key <> " ' " <>
231
- " not found in configuration file."
241
+ errMsg key = " property '" <> key <> " ' " <> " not found in configuration file."
232
242
233
- decodeFileMonad = (liftIO . decodeFileEither) >=> either (fail . show ) return
243
+ decodeFileMonad = (liftIO . decodeFileEither) >=> either
244
+ (throwM . ErrInvalidTLSConfiguration . show )
245
+ return
234
246
235
247
parser = withObject " TLS Configuration" (parseK cKey >=> parseK " tls" )
236
248
@@ -246,6 +258,10 @@ genCertificate desc = do
246
258
((pub, priv), now) <- (,) <$> (certGenKeys desc) <*> dateCurrent
247
259
248
260
let conf = certConfiguration desc
261
+
262
+ when (certExpiryDays conf <= 0 ) $
263
+ throwM $ ErrInvalidExpiryDays " expiry days should be a positive integer"
264
+
249
265
let cert = X509. Certificate
250
266
{ X509. certVersion = 2
251
267
, X509. certSerial = fromIntegral (certSerial desc)
@@ -307,24 +323,12 @@ usExtensionsV3 purpose subDN issDN =
307
323
svExtensionsV3 :: DistinguishedName -> DistinguishedName -> NonEmpty String -> [ExtensionRaw ]
308
324
svExtensionsV3 subDN issDN altNames =
309
325
let
310
- subjectAltName = ExtSubjectAltName ( parseAltName <$> NonEmpty. toList altNames)
326
+ subjectAltName =
327
+ ExtSubjectAltName $ map parseSAN (NonEmpty. toList altNames)
311
328
in
312
- extensionEncode False subjectAltName : usExtensionsV3 KeyUsagePurpose_ServerAuth subDN issDN
329
+ extensionEncode False subjectAltName :
330
+ usExtensionsV3 KeyUsagePurpose_ServerAuth subDN issDN
313
331
314
- parseAltName :: String -> AltName
315
- parseAltName name = do
316
- let
317
- ipv4ToByteString :: IPv4 -> ByteString
318
- ipv4ToByteString (IPv4 bytes) = encodeWord32 bytes
319
- ipv6ToByteString :: IPv6 -> ByteString
320
- ipv6ToByteString ipv6 = LBS. toStrict (BS. toLazyByteString $ ipv6ByteStringBuilder ipv6)
321
- ipv6ByteStringBuilder :: IPv6 -> BS. Builder
322
- ipv6ByteStringBuilder (IPv6 parta partb) = BS. word64BE parta <> BS. word64BE partb
323
-
324
- go :: Maybe IP -> AltName
325
- go (Just address) = AltNameIP $ case_ ipv4ToByteString ipv6ToByteString address
326
- go Nothing = AltNameDNS name
327
- go $ decode $ T. pack name
328
332
329
333
clExtensionsV3 :: DistinguishedName -> DistinguishedName -> [ExtensionRaw ]
330
334
clExtensionsV3 =
0 commit comments