Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit b4a083d

Browse files
authored
Merge pull request #3620 from input-output-hk/KtorZ/CO-389/cardano-sl-x509-as-library
[CO-389] Write properties for cardano-sl-x509
2 parents 61a5221 + cac524c commit b4a083d

File tree

10 files changed

+514
-69
lines changed

10 files changed

+514
-69
lines changed

pkgs/default.nix

+8-2
Original file line numberDiff line numberDiff line change
@@ -17900,6 +17900,7 @@ license = stdenv.lib.licenses.mit;
1790017900
, cardano-sl-util
1790117901
, cardano-sl-util-test
1790217902
, cardano-sl-wallet
17903+
, cardano-sl-x509
1790317904
, cassava
1790417905
, cereal
1790517906
, conduit
@@ -18018,6 +18019,7 @@ cardano-sl-networking
1801818019
cardano-sl-node-ipc
1801918020
cardano-sl-util
1802018021
cardano-sl-wallet
18022+
cardano-sl-x509
1802118023
cereal
1802218024
conduit
1802318025
connection
@@ -18248,8 +18250,8 @@ license = stdenv.lib.licenses.mit;
1824818250
, filepath
1824918251
, hourglass
1825018252
, ip
18251-
, network-transport
1825218253
, optparse-applicative
18254+
, QuickCheck
1825318255
, stdenv
1825418256
, text
1825518257
, universum
@@ -18282,7 +18284,6 @@ data-default-class
1828218284
filepath
1828318285
hourglass
1828418286
ip
18285-
network-transport
1828618287
optparse-applicative
1828718288
text
1828818289
universum
@@ -18292,6 +18293,11 @@ x509-store
1829218293
x509-validation
1829318294
yaml
1829418295
];
18296+
testHaskellDepends = [
18297+
base
18298+
QuickCheck
18299+
universum
18300+
];
1829518301
doHaddock = false;
1829618302
homepage = "https://github.com/input-output-hk/cardano-sl/x509/README.md";
1829718303
description = "Tool-suite for generating x509 certificates specialized for RSA with SHA-256";

tools/src/gencerts/Main.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Cardano.X509.Configuration (CertDescription (..),
1616
ServerConfiguration (..), TLSConfiguration (..),
1717
decodeConfigFile, fromConfiguration, genCertificate)
1818
import Data.X509.Extra (failIfReasons, genRSA256KeyPair,
19-
validateSHA256, writeCertificate, writeCredentials)
19+
validateCertificate, writeCertificate, writeCredentials)
2020

2121

2222
data Command = Command
@@ -79,7 +79,7 @@ main = do
7979

8080
forM_ descs $ \desc -> do
8181
(key, cert) <- genCertificate desc
82-
failIfReasons =<< validateSHA256
82+
failIfReasons =<< validateCertificate
8383
caCert
8484
(certChecks desc)
8585
(serverHost, serverPort)

wallet-new/cardano-sl-wallet-new.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ library
191191
, cardano-sl-node-ipc
192192
, cardano-sl-util
193193
, cardano-sl-wallet
194+
, cardano-sl-x509
194195
, cereal
195196
, conduit
196197
, connection

wallet-new/src/Cardano/Wallet/Client/Http.hs

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module Cardano.Wallet.Client.Http
88
, module Servant.Client
99
-- * Helper to load X509 certificates and private key
1010
, credentialLoadX509
11+
, readSignedObject
1112
, newManager
1213
, Manager
1314
) where
@@ -20,6 +21,8 @@ import Data.ByteString (ByteString)
2021
import Data.Default.Class (Default (..))
2122
import Data.X509 (CertificateChain, SignedCertificate)
2223
import Data.X509.CertificateStore (makeCertificateStore)
24+
import Data.X509.Extra (validateDefaultWithIP)
25+
import Data.X509.File (readSignedObject)
2326
import Network.Connection (TLSSettings (..))
2427
import Network.HTTP.Client (Manager, ManagerSettings,
2528
defaultManagerSettings, newManager)
@@ -73,6 +76,7 @@ mkHttpsManagerSettings serverId caChain credentials =
7376
}
7477
clientHooks = def
7578
{ onCertificateRequest = const . return . Just $ credentials
79+
, onServerCertificate = validateDefaultWithIP
7680
}
7781
clientSupported = def
7882
{ supportedCiphers = ciphersuite_default

x509/README.md

+8-6
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import Data.X509.Extra (genRSA256KeyPair)
1414

1515
main :: IO ()
1616
main = do
17+
confFile <-
18+
decodeConfigFile "dev" "lib/configuration.yaml"
19+
20+
let dirConf =
21+
DirConfiguration "server" "client" Nothing
22+
1723
(caDesc, certDescs) <-
18-
fromConfiguration
19-
<$> decodeConfigFile "dev" "lib/configuration.yaml"
20-
<*> pure (DirConfiguration "server" "client" Nothing)
21-
<*> pure genRSA256KeyPair
22-
<*> genRSA256KeyPair
24+
fromConfiguration confFile dirConf genRSA256KeyPair <$> genRSA256KeyPair
2325

2426
(caKey, caCert) <-
2527
genCertificate caDesc
@@ -34,7 +36,7 @@ main = do
3436
where
3537
findCert
3638
:: String
37-
-> [CertDescription IO PublicKey PrivateKey String]
39+
-> [CertDescription IO PublicKey PrivateKey String]
3840
-> CertDescription IO PublicKey PrivateKey String
3941
findCert outDir =
4042
head . find ((== outDir) . certOutDir)

x509/cardano-sl-x509.cabal

+19-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ library
3434
, filepath
3535
, hourglass
3636
, ip
37-
, network-transport
3837
, optparse-applicative
3938
, text
4039
, universum
@@ -46,3 +45,22 @@ library
4645

4746
exposed-modules: Data.X509.Extra
4847
Cardano.X509.Configuration
48+
49+
test-suite cardano-sl-x509-test
50+
default-language: Haskell2010
51+
default-extensions: DeriveGeneric
52+
NoImplicitPrelude
53+
OverloadedStrings
54+
TupleSections
55+
TypeApplications
56+
57+
type: exitcode-stdio-1.0
58+
59+
hs-source-dirs: test
60+
main-is: Main.hs
61+
other-modules: Test.Cardano.X509.Configuration.Arbitrary
62+
63+
build-depends: base
64+
, QuickCheck
65+
, cardano-sl-x509
66+
, universum

x509/src/Cardano/X509/Configuration.hs

+54-50
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ module Cardano.X509.Configuration
1717

1818
-- * Description of Certificates
1919
, CertDescription(..)
20-
, fromConfiguration
2120

2221
-- * Effectful Functions
2322
, ConfigurationKey(..)
23+
, ErrInvalidTLSConfiguration
24+
, ErrInvalidExpiryDays
25+
, fromConfiguration
2426
, decodeConfigFile
2527
, genCertificate
2628
) where
@@ -36,34 +38,27 @@ import Data.Hourglass (Minutes (..), Period (..), dateAddPeriod,
3638
import Data.List (stripPrefix)
3739
import Data.Semigroup ((<>))
3840
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)
4648
import Data.X509.Validation (ValidationChecks (..), defaultChecks)
4749
import Data.Yaml (decodeFileEither, parseMonad, withObject)
4850
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)
5351
import System.IO (FilePath)
5452
import Time.System (dateCurrent)
5553
import Time.Types (DateTime (..))
5654

57-
import Data.X509.Extra (signAlgRSA256, signCertificate)
55+
import Data.X509.Extra (parseSAN, signAlgRSA256, signCertificate)
5856

5957
import qualified Data.Aeson as Aeson
6058
import qualified Data.Aeson.Types as Aeson
61-
import qualified Data.ByteString.Builder as BS
62-
import qualified Data.ByteString.Lazy as LBS
6359
import qualified Data.Char as Char
6460
import qualified Data.HashMap.Lazy as HM
6561
import qualified Data.List.NonEmpty as NonEmpty
66-
import qualified Data.Text as T
6762
import qualified Data.X509 as X509
6863

6964

@@ -76,7 +71,7 @@ data TLSConfiguration = TLSConfiguration
7671
{ tlsCa :: CertConfiguration
7772
, tlsServer :: ServerConfiguration
7873
, tlsClients :: [CertConfiguration]
79-
} deriving (Generic)
74+
} deriving (Generic, Show, Eq)
8075

8176
instance FromJSON TLSConfiguration where
8277
parseJSON = Aeson.genericParseJSON (aesonDropPrefix "tls")
@@ -86,14 +81,14 @@ data DirConfiguration = DirConfiguration
8681
{ outDirServer :: FilePath
8782
, outDirClients :: FilePath
8883
, outDirCA :: Maybe FilePath
89-
}
84+
} deriving (Generic, Show, Eq)
9085

9186
-- | Foreign Certificate Configuration
9287
data CertConfiguration = CertConfiguration
9388
{ certOrganization :: String
9489
, certCommonName :: String
9590
, certExpiryDays :: Int
96-
} deriving (Generic)
91+
} deriving (Generic, Show, Eq)
9792

9893
instance FromJSON CertConfiguration where
9994
parseJSON = Aeson.genericParseJSON (aesonDropPrefix "cert")
@@ -102,7 +97,7 @@ instance FromJSON CertConfiguration where
10297
data ServerConfiguration = ServerConfiguration
10398
{ serverConfiguration :: CertConfiguration
10499
, serverAltNames :: NonEmpty String
105-
}
100+
} deriving (Generic, Show, Eq)
106101

107102
-- NOTE We keep the declaration structure 'flat' such that servers config
108103
-- are simply client config with an extra field 'altDNS'
@@ -137,12 +132,37 @@ data CertDescription m pub priv outdir = CertDescription
137132
}
138133

139134

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+
140160
-- | Describe a list of certificates to generate & sign from a foreign config
141161
--
142162
-- Description can then be used with @genCertificate@ to obtain corresponding
143163
-- certificate
144164
fromConfiguration
145-
:: Applicative m
165+
:: (Applicative m)
146166
=> TLSConfiguration -- ^ Foreign TLS configuration / setup
147167
-> DirConfiguration -- ^ Output directories configuration
148168
-> m (pub, priv) -- ^ Key pair generator
@@ -201,15 +221,6 @@ fromConfiguration tlsConf dirConf genKeys (caPub, caPriv) =
201221
(caConfig, svConfig : clConfigs)
202222

203223

204-
--
205-
-- Effectful Functions
206-
--
207-
208-
-- | Type-alias for signature readability
209-
newtype ConfigurationKey = ConfigurationKey
210-
{ getConfigurationKey :: String
211-
} deriving (Eq, Show)
212-
213224
-- | Decode a configuration file (.yaml). The expected file structure is:
214225
-- <configuration-key>:
215226
-- tls:
@@ -220,17 +231,18 @@ newtype ConfigurationKey = ConfigurationKey
220231
-- where the 'configuration-key' represents the target environment (dev, test,
221232
-- bench, etc.).
222233
decodeConfigFile
223-
:: (MonadIO m, MonadFail m)
234+
:: (MonadIO m, MonadThrow m)
224235
=> ConfigurationKey -- ^ Target configuration Key
225236
-> FilePath -- ^ Target configuration file
226237
-> m TLSConfiguration
227238
decodeConfigFile (ConfigurationKey cKey) filepath =
228239
decodeFileMonad filepath >>= parseMonad parser
229240
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."
232242

233-
decodeFileMonad = (liftIO . decodeFileEither) >=> either (fail . show) return
243+
decodeFileMonad = (liftIO . decodeFileEither) >=> either
244+
(throwM . ErrInvalidTLSConfiguration . show)
245+
return
234246

235247
parser = withObject "TLS Configuration" (parseK cKey >=> parseK "tls")
236248

@@ -246,6 +258,10 @@ genCertificate desc = do
246258
((pub, priv), now) <- (,) <$> (certGenKeys desc) <*> dateCurrent
247259

248260
let conf = certConfiguration desc
261+
262+
when (certExpiryDays conf <= 0) $
263+
throwM $ ErrInvalidExpiryDays "expiry days should be a positive integer"
264+
249265
let cert = X509.Certificate
250266
{ X509.certVersion = 2
251267
, X509.certSerial = fromIntegral (certSerial desc)
@@ -307,24 +323,12 @@ usExtensionsV3 purpose subDN issDN =
307323
svExtensionsV3 :: DistinguishedName -> DistinguishedName -> NonEmpty String -> [ExtensionRaw]
308324
svExtensionsV3 subDN issDN altNames =
309325
let
310-
subjectAltName = ExtSubjectAltName ( parseAltName <$> NonEmpty.toList altNames)
326+
subjectAltName =
327+
ExtSubjectAltName $ map parseSAN (NonEmpty.toList altNames)
311328
in
312-
extensionEncode False subjectAltName : usExtensionsV3 KeyUsagePurpose_ServerAuth subDN issDN
329+
extensionEncode False subjectAltName :
330+
usExtensionsV3 KeyUsagePurpose_ServerAuth subDN issDN
313331

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
328332

329333
clExtensionsV3 :: DistinguishedName -> DistinguishedName -> [ExtensionRaw]
330334
clExtensionsV3 =

0 commit comments

Comments
 (0)