pkcs10 (empty) → 0.1.0.0
raw patch · 7 files changed
+841/−0 lines, 7 filesdep +asn1-encodingdep +asn1-parsedep +asn1-typessetup-changed
Dependencies added: asn1-encoding, asn1-parse, asn1-types, base, bytestring, containers, cryptonite, memory, pem, pkcs10, tasty, tasty-hunit, x509
Files
- LICENSE +13/−0
- README.md +24/−0
- Setup.hs +2/−0
- pkcs10.cabal +64/−0
- src/Data/X509/PKCS10.hs +519/−0
- test/Keys.hs +42/−0
- test/Spec.hs +177/−0
+ LICENSE view
@@ -0,0 +1,13 @@+Copyright 2015 Timothy Klim++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.
+ README.md view
@@ -0,0 +1,24 @@+PKCS#10 library+=========++[](https://travis-ci.org/fcomb/pkcs10-hs)++### Example++```haskell+import Crypto.Hash+import Crypto.PubKey.RSA+import Data.X509+import Data.X509.PKCS10++main :: IO ()+main = do+ let rsaKeySize = 128+ let publicExponent = 3+ (pubKey, privKey) <- generate rsaKeySize publicExponent+ let subjectAttrs = makeX520Attributes [(X520CommonName, "node.fcomb.io"), (X520OrganizationName, "fcomb")]+ let extAttrs = PKCS9Attributes [PKCS9Attribute $ ExtBasicConstraints False Nothing, PKCS9Attribute $ ExtKeyUsage [KeyUsage_digitalSignature,KeyUsage_nonRepudiation,KeyUsage_keyEncipherment]]+ Right req <- generateCSR subjectAttrs extAttrs (KeyPairRSA pubKey privKey) SHA512+ putStrLn . show . toPEM $ req -- export in PEM format+ putStrLn . show $ verify (csrToSigned req) $ PubKeyRSA pubKey -- sign CSR before verify+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ pkcs10.cabal view
@@ -0,0 +1,64 @@+name: pkcs10+version: 0.1.0.0+synopsis: PKCS#10 library+description: Please see README.md+homepage: https://github.com/fcomb/pkcs10-hs#readme+license: Apache-2.0+license-file: LICENSE+author: Timothy Klim <hackage@timothyklim.com>+maintainer: Timothy Klim <hackage@timothyklim.com>+copyright: Timothy Klim <hackage@timothyklim.com>+category: Data+build-type: Simple+cabal-version: >=1.10+stability: experimental+extra-doc-files: README.md++library+ ghc-options: -W+ hs-source-dirs: src+ exposed-modules: Data.X509.PKCS10+ default-language: Haskell2010+ build-depends: base >= 4.3 && < 5+ , bytestring+ , containers+ , memory+ , x509+ , cryptonite+ , pem+ , asn1-types+ , asn1-encoding+ , asn1-parse++test-suite tests+ ghc-options: -W+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: test+ main-is: Spec.hs+ other-modules: Keys+ build-depends: base >= 4.3 && < 5+ , pkcs10+ , tasty+ , tasty-hunit+ , bytestring+ , cryptonite+ , pem+ , x509+ , asn1-types+ , asn1-encoding+ , asn1-parse++-- executable example+-- ghc-options: -W+-- default-language: Haskell2010+-- hs-source-dirs: example+-- main-is: Main.hs+-- build-depends: base >= 4.3 && < 5+-- , pkcs10+-- , cryptonite+-- , x509++source-repository head+ type: git+ location: git://github.com/fcomb/pkcs10-hs
+ src/Data/X509/PKCS10.hs view
@@ -0,0 +1,519 @@+{-# LANGUAGE ExistentialQuantification #-}++-- |+-- Module : Data.X509.PKCS10+-- License : Apache-2.0+-- Maintainer : Timothy Klim <hackage@timothyklim.com>+-- Stability : experimental+-- Portability : unknown+--+-- Read/Write PKCS10 certificate signing request (also CSR or certification request).+--+-- Follows RFC2986+--+module Data.X509.PKCS10+ ( X520Attribute(..)+ , X520Attributes(..)+ , PKCS9Attribute(..)+ , PKCS9Attributes(..)+ , CertificationRequestInfo(..)+ , CertificationRequest(..)+ , SignedCertificationRequest(..)+ , Version(..)+ , Signature(..)+ , KeyPair(..)+ , makeX520Attributes+ , generateCSR+ , csrToSigned+ , verify+ , toDER+ , fromDER+ , toPEM+ , toNewFormatPEM+ , fromPEM+ ) where++import Control.Applicative ((<$>), (<*>))+import Crypto.Hash+import qualified Crypto.PubKey.DSA as DSA+import qualified Crypto.PubKey.RSA as RSA+import qualified Crypto.PubKey.RSA.PKCS15 as RSA+import Crypto.Random (MonadRandom)+import Data.ASN1.BinaryEncoding+import Data.ASN1.BitArray+import Data.ASN1.Encoding+import Data.ASN1.OID+import Data.ASN1.Parse+import Data.ASN1.Types+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC+import Data.PEM+import Data.Typeable+import Data.X509++-- | A list of X520 attributes.+data X520Attribute =+ X520CommonName+ | X520SerialNumber+ | X520Name+ | X520Surname+ | X520GivenName+ | X520Initials+ | X520GenerationQualifier+ | X520CountryName+ | X520LocalityName+ | X520StateOrProvinceName+ | X520StreetAddress+ | X520OrganizationName+ | X520OrganizationalUnitName+ | X520Title+ | X520DNQualifier+ | X520Pseudonym+ | X509SubjectAltName+ | EmailAddress+ | IPAddress+ | DomainComponent+ | UserId+ | RawAttribute [Integer]+ deriving (Show, Eq)++instance OIDable X520Attribute where+ getObjectID X520CommonName = [2,5,4,3]+ getObjectID X520SerialNumber = [2,5,4,5]+ getObjectID X520Name = [2,5,4,41]+ getObjectID X520Surname = [2,5,4,4]+ getObjectID X520GivenName = [2,5,4,42]+ getObjectID X520Initials = [2,5,4,43]+ getObjectID X520GenerationQualifier = [2,5,4,44]+ getObjectID X520CountryName = [2,5,4,6]+ getObjectID X520LocalityName = [2,5,4,7]+ getObjectID X520StateOrProvinceName = [2,5,4,8]+ getObjectID X520StreetAddress = [2,5,4,9]+ getObjectID X520OrganizationName = [2,5,4,10]+ getObjectID X520OrganizationalUnitName = [2,5,4,11]+ getObjectID X520Title = [2,5,4,12]+ getObjectID X520DNQualifier = [2,5,4,46]+ getObjectID X520Pseudonym = [2,5,4,65]+ getObjectID X509SubjectAltName = [2,5,29,17]+ getObjectID EmailAddress = [1,2,840,113549,1,9,1]+ getObjectID IPAddress = [1,3,6,1,4,1,42,2,11,2,1]+ getObjectID DomainComponent = [0,9,2342,19200300,100,1,25]+ getObjectID UserId = [0,9,2342,19200300,100,1,1]+ getObjectID (RawAttribute oid) = oid++instance OIDNameable X520Attribute where+ fromObjectID [2,5,4,3] = Just X520CommonName+ fromObjectID [2,5,4,5] = Just X520SerialNumber+ fromObjectID [2,5,4,41] = Just X520Name+ fromObjectID [2,5,4,4] = Just X520Surname+ fromObjectID [2,5,4,42] = Just X520GivenName+ fromObjectID [2,5,4,43] = Just X520Initials+ fromObjectID [2,5,4,44] = Just X520GenerationQualifier+ fromObjectID [2,5,4,6] = Just X520CountryName+ fromObjectID [2,5,4,7] = Just X520LocalityName+ fromObjectID [2,5,4,8] = Just X520StateOrProvinceName+ fromObjectID [2,5,4,9] = Just X520StreetAddress+ fromObjectID [2,5,4,10] = Just X520OrganizationName+ fromObjectID [2,5,4,11] = Just X520OrganizationalUnitName+ fromObjectID [2,5,4,12] = Just X520Title+ fromObjectID [2,5,4,46] = Just X520DNQualifier+ fromObjectID [2,5,4,65] = Just X520Pseudonym+ fromObjectID [2,5,29,17] = Just X509SubjectAltName+ fromObjectID [1,2,840,113549,1,9,1] = Just EmailAddress+ fromObjectID [1,3,6,1,4,1,42,2,11,2,1] = Just IPAddress+ fromObjectID [0,9,2342,19200300,100,1,25] = Just DomainComponent+ fromObjectID [0,9,2342,19200300,100,1,1] = Just UserId+ fromObjectID oid = Just $ RawAttribute oid++-- | A list of PKCS9 extension attributes.+data PKCS9Attribute =+ forall e . (Extension e, Show e, Eq e, Typeable e) => PKCS9Attribute e++-- | PKCS9 extension attributes.+newtype PKCS9Attributes =+ PKCS9Attributes [PKCS9Attribute] deriving (Show, Eq)++instance Show PKCS9Attribute where+ show (PKCS9Attribute e) = show e++instance Eq PKCS9Attribute where+ (PKCS9Attribute x) == (PKCS9Attribute y) =+ case cast y of+ Just y' -> x == y'+ Nothing -> False++-- | X520 attributes.+newtype X520Attributes =+ X520Attributes [(X520Attribute, ASN1CharacterString)] deriving (Show, Eq)++-- | CSR class.+data CertificationRequest = CertificationRequest {+ certificationRequestInfo :: CertificationRequestInfo+, signatureAlgorithm :: SignatureALG+, signature :: Signature+} deriving (Show, Eq)++-- | A signed CSR class.+data SignedCertificationRequest = SignedCertificationRequest {+ certificationRequest :: CertificationRequest+, rawCertificationRequestInfo :: B.ByteString -- raw bytes for verifying signature+} deriving (Show, Eq)++-- | Certificate request info.+data CertificationRequestInfo = CertificationRequestInfo {+ version :: Version+, subject :: X520Attributes+, subjectPublicKeyInfo :: PubKey+, attributes :: PKCS9Attributes+} deriving (Show, Eq)++-- | Version of CSR (default 0).+newtype Version = Version Int deriving (Show, Eq)++-- | Signature of certificate request info.+newtype Signature =+ Signature B.ByteString deriving (Show, Eq)++instance ASN1Object CertificationRequest where+ toASN1 (CertificationRequest info sigAlg sig) xs =+ Start Sequence :+ (toASN1 info .+ toASN1 sigAlg .+ toASN1 sig)+ (End Sequence : xs)++ fromASN1 xs =+ f <$> (parseSignedCertificationRequest xs)+ where+ f (scr, xs') = (certificationRequest scr, xs')++parseSignedCertificationRequest :: [ASN1] -> Either String (SignedCertificationRequest, [ASN1])+parseSignedCertificationRequest (Start Sequence : xs) =+ f $ parseCertReqInfo xs+ where+ parseCertReqInfo xs' =+ case fromASN1 xs' of+ Right (cri @ CertificationRequestInfo {}, rest) ->+ runParseASN1State (p cri raw) rest+ where+ raw = encodeASN1' DER $ take (length xs' - length rest) xs'+ Left e -> Left e+ p cri raw = (flip SignedCertificationRequest raw) <$>+ (CertificationRequest cri <$> getObject <*> getObject)+ f (Right (req, End Sequence : xs')) =+ Right (req, xs')+ f (Right _) = Left "fromASN1: PKCS9.CertificationRequest: unknown format"+ f (Left e) = Left e++parseSignedCertificationRequest _ =+ Left "fromASN1: PKCS9.CertificationRequest: unknown format"++instance ASN1Object Signature where+ toASN1 (Signature bs) xs =+ (BitString $ toBitArray bs 0) : xs++ fromASN1 (BitString s : xs) =+ Right (Signature $ bitArrayGetData s, xs)++ fromASN1 _ = Left "fromASN1: PKCS9.Signature: unknown format"++instance ASN1Object CertificationRequestInfo where+ toASN1 (CertificationRequestInfo version subject pubKey attributes) xs =+ Start Sequence :+ (toASN1 version .+ toASN1 subject .+ toASN1 pubKey .+ toASN1 attributes)+ (End Sequence : xs)++ fromASN1 (Start Sequence : xs) =+ f $ runParseASN1State p xs+ where+ p = CertificationRequestInfo <$> getObject+ <*> getObject+ <*> getObject+ <*> getObject+ f (Right (req, End Sequence : xs)) = Right (req, xs)+ f (Right _) = Left "fromASN1: PKCS9.CertificationRequestInfo: unknown format"+ f (Left e) = Left e++ fromASN1 _ = Left "fromASN1: PKCS9.CertificationRequestInfo: unknown format"++instance ASN1Object Version where+ toASN1 (Version v) xs =+ (IntVal $ fromIntegral v) : xs++ fromASN1 (IntVal n : xs) =+ Right (Version $ fromIntegral n, xs)++ fromASN1 _ = Left "fromASN1: PKCS9.Version: unknown format"++instance ASN1Object X520Attributes where+ toASN1 (X520Attributes attrs) xs =+ Start Sequence :+ attrSet +++ End Sequence : xs+ where+ attrSet = concatMap f attrs+ f (attr, s) = [Start Set, Start Sequence, oid attr, cs s, End Sequence, End Set]+ oid attr = OID $ getObjectID attr+ cs s = ASN1String s++ fromASN1 (Start Sequence : xs) =+ f (X520Attributes []) xs+ where+ f (X520Attributes attrs) (Start Set :+ Start Sequence :+ (OID oid) :+ (ASN1String cs) :+ End Sequence :+ End Set :+ rest) =+ case fromObjectID oid of+ Just attr -> f (X520Attributes $ (attr, cs) : attrs) rest+ _ -> Left ("fromASN1: X520.Attributes: unknown oid" ++ show oid)+ f (X520Attributes attrs) (End Sequence : rest) =+ Right (X520Attributes $ reverse attrs, rest)+ f _ _ = Left "fromASN1: X520.Attributes: unknown format"++ fromASN1 _ = Left "fromASN1: X520.Attributes: unknown format"++instance ASN1Object PKCS9Attribute where+ toASN1 (PKCS9Attribute attr) xs =+ Start Sequence : oid : os : End Sequence : xs+ where+ oid = OID $ extOID attr+ os = (OctetString . encodeASN1' DER . extEncode) attr++ fromASN1 (Start Sequence : OID oid : OctetString os : End Sequence : xs) =+ case oid of+ [2,5,29,14] -> f (decode :: Either String ExtSubjectKeyId)+ [2,5,29,15] -> f (decode :: Either String ExtKeyUsage)+ [2,5,29,17] -> f (decode :: Either String ExtSubjectAltName)+ [2,5,29,19] -> f (decode :: Either String ExtBasicConstraints)+ [2,5,29,31] -> f (decode :: Either String ExtCrlDistributionPoints)+ [2,5,29,35] -> f (decode :: Either String ExtAuthorityKeyId)+ [2,5,29,37] -> f (decode :: Either String ExtExtendedKeyUsage)+ _ -> Left "fromASN1: PKCS9.Attribute: unknown oid"+ where+ decode :: forall e . (Extension e, Show e, Eq e, Typeable e) => Either String e+ decode = extDecode =<< decodeDER os+ f (Right attr) = Right (PKCS9Attribute attr, xs)+ f (Left e) = Left e++ fromASN1 _ = Left "fromASN1: PKCS9.Attribute: unknown format"++extensionRequestOid :: [Integer]+extensionRequestOid = [1,2,840,113549,1,9,14]++instance ASN1Object PKCS9Attributes where+ toASN1 (PKCS9Attributes exts) xs =+ Start (Container Context 0) :+ ctx +++ End (Container Context 0) : xs+ where+ ctx = case exts of+ [] -> []+ es ->+ [Start Sequence, extOid, Start Set, Start Sequence] +++ extSet +++ [End Sequence, End Set, End Sequence]+ where+ extOid = OID extensionRequestOid+ extSet = concatMap (flip toASN1 []) es++ fromASN1 (Start (Container Context 0) : xs) =+ f xs+ where+ f (Start Sequence :+ (OID extOid) :+ Start Set :+ Start Sequence :+ rest) | extOid == extensionRequestOid =+ g [] rest+ where+ g exts (End Sequence :+ End Set :+ End Sequence :+ End (Container Context 0) :+ rest') =+ Right (PKCS9Attributes $ reverse exts, rest')+ g exts (rest' @ (Start Sequence : _)) =+ case fromASN1 rest' of+ Right (attr, xss) -> g (attr : exts) xss+ Left e -> Left e+ g _ _ = Left "fromASN1: PKCS9.Attribute: unknown format"+ f (End (Container Context 0) : rest') = Right (PKCS9Attributes [], rest')+ f _ = Left "fromASN1: PKCS9.Attributes: unknown format"++ fromASN1 _ = Left "fromASN1: PKCS9.Attributes: unknown format"++class RSA.HashAlgorithmASN1 a => HashAlgorithmConversion a where+ fromHashAlgorithmASN1 :: a -> HashALG++instance HashAlgorithmConversion MD2 where+ fromHashAlgorithmASN1 MD2 = HashMD2++instance HashAlgorithmConversion MD5 where+ fromHashAlgorithmASN1 MD5 = HashMD5++instance HashAlgorithmConversion SHA1 where+ fromHashAlgorithmASN1 SHA1 = HashSHA1++instance HashAlgorithmConversion SHA224 where+ fromHashAlgorithmASN1 SHA224 = HashSHA224++instance HashAlgorithmConversion SHA256 where+ fromHashAlgorithmASN1 SHA256 = HashSHA256++instance HashAlgorithmConversion SHA384 where+ fromHashAlgorithmASN1 SHA384 = HashSHA384++instance HashAlgorithmConversion SHA512 where+ fromHashAlgorithmASN1 SHA512 = HashSHA512++-- | Helper to convert string values as utf8 asn1 strings.+makeX520Attributes :: [(X520Attribute, String)] -> X520Attributes+makeX520Attributes xs =+ X520Attributes $ fmap f xs+ where+ f (attr, s) = (attr, asn1CharacterString UTF8 s)++encodeToDER :: ASN1Object o => o -> BC.ByteString+encodeToDER = encodeASN1' DER . flip toASN1 []++decodeDER :: BC.ByteString -> Either String [ASN1]+decodeDER = either (Left . show) Right . decodeASN1' DER++decodeFromDER :: ASN1Object o => BC.ByteString -> Either String (o, [ASN1])+decodeFromDER bs = fromASN1 =<< decodeDER bs++-- | Key pair for RSA and DSA keys.+data KeyPair =+ KeyPairRSA RSA.PublicKey RSA.PrivateKey+ | KeyPairDSA DSA.PublicKey DSA.PrivateKey+ deriving (Show, Eq)++makeCertReqInfo :: X520Attributes -> PKCS9Attributes -> PubKey -> CertificationRequestInfo+makeCertReqInfo subject extAttrs pubKey =+ CertificationRequestInfo {+ version = Version 0+ , subject = subject+ , subjectPublicKeyInfo = pubKey+ , attributes = extAttrs+ }++makeCertReq :: HashAlgorithmConversion hashAlg => CertificationRequestInfo -> BC.ByteString -> hashAlg -> PubKeyALG -> CertificationRequest+makeCertReq certReq sig hashAlg pubKeyAlg =+ CertificationRequest {+ certificationRequestInfo = certReq+ , signatureAlgorithm = SignatureALG (fromHashAlgorithmASN1 hashAlg) pubKeyAlg+ , signature = Signature sig+ }++instance ASN1Object DSA.Signature where+ toASN1 DSA.Signature { DSA.sign_r = r, DSA.sign_s = s } xs =+ Start Sequence : IntVal r : IntVal s : End Sequence : xs++ fromASN1 (Start Sequence : IntVal r : IntVal s : End Sequence : xs) =+ Right (DSA.Signature { DSA.sign_r = r, DSA.sign_s = s }, xs)++ fromASN1 _ = Left "fromASN1: DSA.Signature: unknown format"++-- | Generate CSR.+generateCSR :: (MonadRandom m, HashAlgorithmConversion hashAlg, HashAlgorithm hashAlg) => X520Attributes -> PKCS9Attributes -> KeyPair -> hashAlg -> m (Either String CertificationRequest)++generateCSR subject extAttrs (KeyPairRSA pubKey privKey) hashAlg =+ f <$> sign certReqInfo+ where+ certReqInfo = makeCertReqInfo subject extAttrs $ PubKeyRSA pubKey+ sign = RSA.signSafer (Just hashAlg) privKey . encodeToDER+ f = either (Left . show) (Right . certReq)+ certReq s = makeCertReq certReqInfo s hashAlg PubKeyALG_RSA++generateCSR subject extAttrs (KeyPairDSA pubKey privKey) hashAlg =+ f <$> sign certReqInfo+ where+ certReqInfo = makeCertReqInfo subject extAttrs $ PubKeyDSA pubKey+ sign = DSA.sign privKey hashAlg . encodeToDER+ f = Right . certReq . encodeToDER+ certReq s = makeCertReq certReqInfo s hashAlg PubKeyALG_DSA++-- | Sign CSR.+csrToSigned :: CertificationRequest -> SignedCertificationRequest+csrToSigned req = SignedCertificationRequest {+ certificationRequest = req+, rawCertificationRequestInfo = encodeToDER . certificationRequestInfo $ req+}++-- | Verify signed CSR.+verify :: SignedCertificationRequest -> PubKey -> Bool+verify SignedCertificationRequest {+ certificationRequest = CertificationRequest {+ signatureAlgorithm = SignatureALG hashAlg PubKeyALG_RSA+ , signature = Signature sm+ }+ , rawCertificationRequestInfo = m+ }+ (PubKeyRSA pubKey) =+ rsaVerify hashAlg pubKey m sm+ where+ rsaVerify HashMD2 = RSA.verify (Just MD2)+ rsaVerify HashMD5 = RSA.verify (Just MD5)+ rsaVerify HashSHA1 = RSA.verify (Just SHA1)+ rsaVerify HashSHA224 = RSA.verify (Just SHA224)+ rsaVerify HashSHA256 = RSA.verify (Just SHA256)+ rsaVerify HashSHA384 = RSA.verify (Just SHA384)+ rsaVerify HashSHA512 = RSA.verify (Just SHA512)++verify SignedCertificationRequest {+ certificationRequest = CertificationRequest {+ signatureAlgorithm = SignatureALG HashSHA1 PubKeyALG_DSA+ , signature = Signature sm+ }+ , rawCertificationRequestInfo = m+ }+ (PubKeyDSA pubKey) =+ case decodeFromDER sm of+ Right (dsaSig, _) -> DSA.verify SHA1 pubKey dsaSig m+ _ -> False++verify _ _ = False++-- | Convert CSR to DER as ByteString.+toDER :: CertificationRequest -> BC.ByteString+toDER = encodeToDER++requestHeader :: String+requestHeader = "CERTIFICATE REQUEST"++-- | Convert CSR to PEM format.+toPEM :: CertificationRequest -> PEM+toPEM req = PEM {+ pemName = requestHeader+, pemHeader = []+, pemContent = toDER req+}++newFormatRequestHeader :: String+newFormatRequestHeader = "NEW CERTIFICATE REQUEST"++-- | Convert CSR to PEM new format.+toNewFormatPEM :: CertificationRequest -> PEM+toNewFormatPEM req = PEM {+ pemName = newFormatRequestHeader+, pemHeader = []+, pemContent = toDER req+}++-- | Convert ByteString to signed CSR.+fromDER :: BC.ByteString -> Either String SignedCertificationRequest+fromDER bs =+ fst <$> (parseSignedCertificationRequest =<< decodeDER bs)++-- | Convert PEM to signed CSR.+fromPEM :: PEM -> Either String SignedCertificationRequest+fromPEM p =+ if pemName p == requestHeader || pemName p == newFormatRequestHeader+ then fromDER . pemContent $ p+ else Left "PEM: unknown format"
+ test/Keys.hs view
@@ -0,0 +1,42 @@+module Keys (+ rsaPrivateKey+ , rsaPublicKey+ , dsaParams+ , dsaPublicKey+ , dsaPrivateKey+) where++import qualified Crypto.PubKey.DSA as DSA+import qualified Crypto.PubKey.RSA as RSA++rsaPrivateKey = RSA.PrivateKey {+ RSA.private_pub = RSA.PublicKey {+ RSA.public_size = 256+ , RSA.public_n = 0xca52ed05138108d0c116904625e325c7a0a004622df97bb304f38d34967e1eccc5ed32c5365e080a0ddae36c016da091397e59ee4cc2a8000e41663cb81f21ac54a4cbb4daa274930d9561dc5f0c11ef9d1f58dda3b1fdf3d878ef896df85c2952c8706dc9792d3cbccfedf3fe9a53b547e7561b9cd364740fc35962fd92d6a3d77bb85e3e4a4e2b58d67fd94109e6187bb2c99fafb6fedfbe2976470b32f9a876fccd1ca90e1f27815d80026ce1bf05d25f453d68553dd3c618c9ee4f22e3b6eb3a0bf5b22e50b9075a69543839430c9327c4ceffb48ed45ae75713cb3161d687ae67cd75d7c4c5673c71fdd57215a7884007458dd482b3db584176fe9274e7+ , RSA.public_e = 65537+ }+ , RSA.private_d = 0x7f6ddd64ce14fe3564f7ce24b066317c5b198dc93c859eb8710ce8c9cf635e08c13c95368af88c7db09e4590d811710926ba02f0491e6ceba40ee2d2a970ca279d24323d155b2eab13ea1b39ce0f6f1888d09e8af237ffe7de56c51fbcc8a95b498d7eea56f3e05f19d8a8597de93b81b8e8e65dfbd754b498dd03e68a293acfd42cd1eb1f63e527cd628832df0f80b5dc820e6f595507f40b0d0398a97d429fd58b4e87139ec81a55224999836b10459317a0ed3b643585137152737ac4bc0009f688970550ecabd569d02d6733d499ac2fc6388fc2a189680404b606c7878878447ffdbe355712652a627f9bb0c80155a5bb77051a7212ac31ac8ea3c34d61+ , RSA.private_p = 0xff41ae8b0519bd9ca727aea29f47f9821c64fbf189f72cc697dfa7a550ae452e2f4321ae59a6e88615970ed484c59419eaba4aeb2852acf8020f07df65ef845df842c70262f9fb195566cb702ee7b8a7c703af45ce20f19b659aa2a84fc00e9ff8776c26a1453188c17f0b347f2ac5027963d98c81fecc08829bf000b93633b7+ , RSA.private_q = 0xcae9c71787c3436ff8e2cfaaa6adf947d7581af5fc97289a7151dc0ae074dd47b07d14f4a040db11d7dc1b7b36ea4fcb6dce0c1f34a20051adaf02d21e18a17fd927e3bca07e90f8e5e217aa6c74c02649ed5aea65d2325b075055aa57b7383c03215fa6b7ad4aa812209c5d983d9ac5361cb41a66d71082c25291c65964a851+ , RSA.private_dP = 0x19301f0c22d5b66b35e769892c948a9f7733d3d4b2db8c34909a064dfed723867a2d72ae64b45eaf9483d277d01e3b0e0fa6906c020f9e77dea4230fbd5c54407d9601534ff700fb2ffd33c7ff28e884cdd3a4410c251e124a8566d59b0105b06d71435fd07c26762b39e11e2e0a4764861bff44a9333e68fc926cad75994ea3+ , RSA.private_dQ = 0x57d5c491e1f9db6a4ff15675877b4daa148495ff1f5d0b7d774a3917183c5e41171bbe037a8f38854bcc82b4de7ba518e92a454308d48b90cfbf2f586720c060084f651c94d8d4c679345a8c323083b0cf558882689b09a32fd24d92d42f828f2fc7b59104776616c4ea0ae5d4ededde2a63586f094f555d54e2cb29722ec951+ , RSA.private_qinv = 0xe1d1ff7d7a234b84d804d46464a18366b34de81bc34bf8d705462ca86ea94fea0d1c5f8f8e32353d1a8c28a333fb20a49952d1a4b2b78b68dc1fedc2b351158e73d8217df62973766d0db9f295d0a68765937731090dcf5d4781bb6dd1eaf4d7c0a523c400dc599391b066d6a779a208b0b205fb194fc830db74e1131cadb230+ }++rsaPublicKey = RSA.private_pub rsaPrivateKey++dsaParams = DSA.Params {+ DSA.params_p = 0xa8f9cd201e5e35d892f85f80e4db2599a5676a3b1d4f190330ed3256b26d0e80a0e49a8fffaaad2a24f472d2573241d4d6d6c7480c80b4c67bb4479c15ada7ea8424d2502fa01472e760241713dab025ae1b02e1703a1435f62ddf4ee4c1b664066eb22f2e3bf28bb70a2a76e4fd5ebe2d1229681b5b06439ac9c7e9d8bde283+ , DSA.params_g = 0x2b3152ff6c62f14622b8f48e59f8af46883b38e79b8c74deeae9df131f8b856e3ad6c8455dab87cc0da8ac973417ce4f7878557d6cdf40b35b4a0ca3eb310c6a95d68ce284ad4e25ea28591611ee08b8444bd64b25f3f7c572410ddfb39cc728b9c936f85f419129869929cdb909a6a3a99bbe089216368171bd0ba81de4fe33+ , DSA.params_q = 0xf85f0f83ac4df7ea0cdf8f469bfeeaea14156495+ }++dsaPublicKey = DSA.PublicKey {+ DSA.public_y = 0xa01542c3da410dd57930ca724f0f507c4df43d553c7f69459939685941ceb95c7dcc3f175a403b359621c0d4328e98f15f330a63865baf3e7eb1604a0715e16eed64fd14b35d3a534259a6a7ddf888c4dbb5f51bbc6ed339e5bb2a239d5cfe2100ac8e2f9c16e536f25119ab435843af27dc33414a9e4602f96d7c94d6021cec+ , DSA.public_params = dsaParams+ }++dsaPrivateKey = DSA.PrivateKey {+ DSA.private_x = 0xae56f66b0a9405b9cca54c60ec4a3bb5f8be7c3f+ , DSA.private_params = dsaParams+ }
+ test/Spec.hs view
@@ -0,0 +1,177 @@+module Main where++import Crypto.Hash+import qualified Crypto.PubKey.DSA as DSA+import qualified Crypto.PubKey.RSA as RSA+import Data.ASN1.BinaryEncoding+import Data.ASN1.Encoding+import Data.ASN1.Types+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC+import Data.PEM+import Data.X509+import Data.X509.PKCS10+import Keys+import Test.Tasty+import Test.Tasty.HUnit++main :: IO ()+main = do+ defaultMain tests++tests :: TestTree+tests = testGroup "Tests" [unitTests]++unitTests = testGroup "Unit tests"+ [ testCase "CSR with subject and extension (RSA)" $ do+ req <- defaultRsaCSR subjectAttrs extAttrs+ checkCSR (csrToSigned req) subjectAttrs extAttrs $ PubKeyRSA rsaPublicKey+ , testCase "CSR with empty subject and extension (RSA)" $ do+ req <- defaultRsaCSR emptySubjectAttrs emptyExtAttrs+ checkCSR (csrToSigned req) emptySubjectAttrs emptyExtAttrs $ PubKeyRSA rsaPublicKey+ , testCase "CSR fixtures (RSA)" $ do+ checkRsaFixtureCSR "rsa" "rsa1" pemSubjectAttrs pemExtAttrs readRSA+ checkRsaFixtureCSR "rsa" "rsa2" pemSubjectAttrs emptyExtAttrs readRSA+ checkRsaFixtureCSR "rsa" "rsa3" emptySubjectAttrs emptyExtAttrs readRSA+ , testCase "CSR with subject and extension (DSA)" $ do+ req <- defaultDsaCSR subjectAttrs extAttrs+ checkCSR (csrToSigned req) subjectAttrs extAttrs $ PubKeyDSA dsaPublicKey+ , testCase "CSR with empty subject and extension (DSA)" $ do+ req <- defaultDsaCSR emptySubjectAttrs emptyExtAttrs+ checkCSR (csrToSigned req) emptySubjectAttrs emptyExtAttrs $ PubKeyDSA dsaPublicKey+ , testCase "CSR fixtures (DSA)" $ do+ checkRsaFixtureCSR "dsa" "dsa1" pemSubjectAttrs pemExtAttrs readDSA+ checkRsaFixtureCSR "dsa" "dsa2" pemSubjectAttrs emptyExtAttrs readDSA+ checkRsaFixtureCSR "dsa" "dsa3" emptySubjectAttrs emptyExtAttrs readDSA+ ]++subjectAttrs = makeX520Attributes [(X520CommonName, "node.fcomb.io"), (X520OrganizationName, "fcomb")]++pemSubjectAttrs = X520Attributes [(X520CommonName, asn1CharacterString Printable "api"), (X520CommonName, asn1CharacterString Printable "fcomb"), (X520CommonName, asn1CharacterString Printable "com"), (X520StateOrProvinceName, asn1CharacterString Printable "Moscow"), (X520LocalityName, asn1CharacterString Printable "Moscow"), (X520OrganizationName, asn1CharacterString Printable "End Point"), (X520OrganizationalUnitName, asn1CharacterString Printable "fcomb"), (EmailAddress, asn1CharacterString IA5 "in@fcomb.io"), (X509SubjectAltName, asn1CharacterString Printable "DNS.1=fcomb.com")]++emptySubjectAttrs = X520Attributes []++extAttrs = PKCS9Attributes [PKCS9Attribute $ ExtExtendedKeyUsage [KeyUsagePurpose_ServerAuth, KeyUsagePurpose_CodeSigning], PKCS9Attribute $ ExtKeyUsage [KeyUsage_digitalSignature, KeyUsage_cRLSign]]++pemExtAttrs = PKCS9Attributes [PKCS9Attribute $ ExtBasicConstraints False Nothing, PKCS9Attribute $ ExtKeyUsage [KeyUsage_digitalSignature,KeyUsage_nonRepudiation,KeyUsage_keyEncipherment]]++emptyExtAttrs = PKCS9Attributes []++readRSA pem = PubKeyRSA $ readRsaPubKey pem 256++readDSA pem = PubKeyDSA $ readDsaPubKey pem++checkRsaFixtureCSR keyName csrName subjectAttrs extAttrs pkf = do+ keyPem <- readPEMFile $ "./test/fixtures/" ++ keyName ++ ".pem"+ csrPem <- readPEMFile $ "./test/fixtures/" ++ csrName ++ ".csr"+ checkCSR (readCSR csrPem) subjectAttrs extAttrs $ pkf keyPem++defaultRsaCSR subjectAttrs extAttrs = do+ Right req <- generateCSR subjectAttrs extAttrs (KeyPairRSA rsaPublicKey rsaPrivateKey) SHA512+ return req++defaultDsaCSR subjectAttrs extAttrs = do+ Right req <- generateCSR subjectAttrs extAttrs (KeyPairDSA dsaPublicKey dsaPrivateKey) SHA1+ return $ req++readPEMFile file = do+ content <- B.readFile file+ return $ either error (pemContent . head) $ pemParseBS content++decodeFromDER :: ASN1Object o => BC.ByteString -> Either String (o, [ASN1])+decodeFromDER bs =+ f asn+ where+ asn = fromASN1 <$> decodeASN1' DER bs+ f = either (Left . show) id++checkAttrs csr subjectAttrs extAttrs = do+ let certReqInfo = certificationRequestInfo csr+ assertBool "subject == subjectAttrs" $+ (subject certReqInfo) == subjectAttrs+ assertBool "attributes == extAttrs" $+ (attributes certReqInfo) == extAttrs++checkDER csr = do+ let (Right csr') = (fromDER . toDER) csr+ assertBool "csr' == csr" $ (certificationRequest csr') == csr++checkPEM csr = do+ let (Right csr') = (fromPEM . toPEM) csr+ assertBool "csr' == csr" $ (certificationRequest csr') == csr+ let (Right csr'') = (fromPEM . toNewFormatPEM) csr+ assertBool "csr'' == csr" $+ (certificationRequest csr'') == csr+ assertBool "pemName == CERTIFICATE REQUEST" $+ (pemName . toPEM $ csr) == "CERTIFICATE REQUEST"+ assertBool "pemName == NEW CERTIFICATE REQUEST" $+ (pemName . toNewFormatPEM $ csr) == "NEW CERTIFICATE REQUEST"++verifyCSR csr pubKey = do+ assertBool "verify csr" $ verify csr pubKey+ let cri = (certificationRequest csr) {+ signature = Signature $ BC.pack "invalid"+ }+ let csr' = csr { certificationRequest = cri }+ assertBool "verify not valid csr" $ not $ verify csr' pubKey++checkCSR csr subjectAttrs extAttrs pubKey = do+ checkAttrs (certificationRequest csr) subjectAttrs extAttrs+ checkDER (certificationRequest csr)+ checkPEM (certificationRequest csr)+ verifyCSR csr pubKey++readRsaPubKey :: B.ByteString -> Int -> RSA.PublicKey+readRsaPubKey bs size =+ case decodeASN1' DER bs of+ Right (Start Sequence :+ IntVal _ :+ IntVal public_n :+ IntVal public_e :+ IntVal private_d :+ IntVal private_p :+ IntVal private_q :+ IntVal private_dP :+ IntVal private_dQ :+ IntVal private_qinv :+ End Sequence : _) ->+ RSA.private_pub $ RSA.PrivateKey {+ RSA.private_pub = RSA.PublicKey {+ RSA.public_size = size+ , RSA.public_n = public_n+ , RSA.public_e = public_e+ }+ , RSA.private_d = private_d+ , RSA.private_p = private_p+ , RSA.private_q = private_q+ , RSA.private_dP = private_dP+ , RSA.private_dQ = private_dQ+ , RSA.private_qinv = private_qinv+ }+ _ -> error "RSA.PrivateKey: unknown format"++readDsaPubKey :: B.ByteString -> DSA.PublicKey+readDsaPubKey bs =+ case decodeASN1' DER bs of+ Right (Start Sequence :+ IntVal _ :+ IntVal params_p :+ IntVal params_q :+ IntVal params_g :+ IntVal public_y :+ IntVal _ : -- private_x :+ End Sequence : _) ->+ DSA.PublicKey {+ DSA.public_y = public_y+ , DSA.public_params = DSA.Params {+ DSA.params_p = params_p+ , DSA.params_g = params_g+ , DSA.params_q = params_q+ }+ }+ _ -> error "DSA.PrivateKey: unknown format"++readCSR bs =+ case fromDER bs of+ Right (scr @ SignedCertificationRequest {}) -> scr+ Left e -> error e