crypton-x509 1.7.7 → 1.10.0
raw patch · 19 files changed
Files
- ChangeLog.md +36/−0
- Data/X509.hs +71/−57
- Data/X509/AlgorithmIdentifier.hs +165/−69
- Data/X509/CRL.hs +62/−54
- Data/X509/Cert.hs +99/−77
- Data/X509/CertificateChain.hs +17/−15
- Data/X509/DistinguishedName.hs +37/−30
- Data/X509/EC.hs +48/−46
- Data/X509/Ext.hs +344/−166
- Data/X509/ExtensionRaw.hs +36/−25
- Data/X509/Internal.hs +10/−10
- Data/X509/OID.hs +40/−42
- Data/X509/PrivateKey.hs +505/−150
- Data/X509/PublicKey.hs +238/−161
- Data/X509/Signed.hs +130/−99
- Setup.hs +1/−0
- Tests/MLDSAKeys.hs +365/−0
- Tests/Tests.hs +401/−118
- crypton-x509.cabal +71/−61
+ ChangeLog.md view
@@ -0,0 +1,36 @@+# ChangeLog for crypton-x509++## 1.10.0++* Support ML-DSA (RFC 9881)+ [#35](https://github.com/kazu-yamamoto/crypton-certificate/pull/35)+* An ML-DSA private key keeps the form it was written in+ [#35](https://github.com/kazu-yamamoto/crypton-certificate/pull/35)++## 1.9.2++* Stop printing EC private keys+ [#34](https://github.com/kazu-yamamoto/crypton-certificate/pull/34)++## 1.9.1++* Implementing the X509 name constrains extension.+ [#30](https://github.com/kazu-yamamoto/crypton-certificate/pull/30)++## 1.9.0++* Using "ram" instead of "memory"+ [#29](https://github.com/kazu-yamamoto/crypton-certificate/pull/29)++## 1.8.0++* Depend on package `time-hourglass`, rather than `hourglass`. Date and+ time-related types and classes are now those from the former package.+ [#18](https://github.com/kazu-yamamoto/crypton-certificate/pull/18)+* Depend on package `crypton-asn1-types >= 0.4.1` rather than `asn1-types`.+ ASN.1-related types and classes are now those from the former package.++## 1.7.7++* Bump requirements to GHC 7.8 and transformers 0.4 series+ [#130](https://github.com/haskell-tls/hs-certificate/pull/130)
Data/X509.hs view
@@ -8,75 +8,80 @@ -- Read/Write X509 Certificate, CRL and their signed equivalents. -- -- Follows RFC5280 / RFC6818----module Data.X509- (+module Data.X509 ( -- * Types- SignedCertificate- , SignedCRL- , Certificate(..)- , PubKey(..)- , PubKeyEC(..)- , SerializedPoint(..)- , PrivKey(..)- , PrivKeyEC(..)- , pubkeyToAlg- , privkeyToAlg- , module Data.X509.AlgorithmIdentifier- , module Data.X509.Ext- , module Data.X509.ExtensionRaw+ SignedCertificate,+ SignedCRL,+ Certificate (..),+ PubKey (..),+ PubKeyEC (..),+ SerializedPoint (..),+ PrivKey (..),+ PrivKeyEC (..),+ PrivKeyMLDSA,+ MLDSAKeyForm (..),+ privkeyMLDSAFromSeed,+ privkeyMLDSAFromKey,+ privkeyMLDSA_key,+ privkeyMLDSA_seed,+ privkeyMLDSA_form,+ pubkeyToAlg,+ privkeyToAlg,+ module Data.X509.AlgorithmIdentifier,+ module Data.X509.Ext,+ module Data.X509.ExtensionRaw, -- * Certificate Revocation List (CRL)- , module Data.X509.CRL+ module Data.X509.CRL, -- * Naming- , DistinguishedName(..)- , DnElement(..)- , ASN1CharacterString(..)- , getDnElement+ DistinguishedName (..),+ DnElement (..),+ ASN1CharacterString (..),+ getDnElement, -- * Certificate Chain- , module Data.X509.CertificateChain+ module Data.X509.CertificateChain, -- * Signed types and marshalling- , Signed(..)- , SignedExact- , getSigned- , getSignedData- , objectToSignedExact- , objectToSignedExactF- , encodeSignedObject- , decodeSignedObject+ Signed (..),+ SignedExact,+ getSigned,+ getSignedData,+ objectToSignedExact,+ objectToSignedExactF,+ encodeSignedObject,+ decodeSignedObject, -- * Parametrized Signed accessor- , getCertificate- , getCRL- , decodeSignedCertificate- , decodeSignedCRL+ getCertificate,+ getCRL,+ decodeSignedCertificate,+ decodeSignedCRL, -- * Hash distinguished names related function- , hashDN- , hashDN_old- ) where+ hashDN,+ hashDN_old,+) where import Control.Arrow (second) -import Data.ASN1.Types-import Data.ASN1.Encoding import Data.ASN1.BinaryEncoding-import qualified Data.ByteString as B+import Data.ASN1.Encoding+import Data.ASN1.Types import qualified Data.ByteArray as BA+import qualified Data.ByteString as B -import Data.X509.Cert-import Data.X509.Ext-import Data.X509.ExtensionRaw+import Data.X509.AlgorithmIdentifier import Data.X509.CRL+import Data.X509.Cert import Data.X509.CertificateChain import Data.X509.DistinguishedName-import Data.X509.Signed-import Data.X509.PublicKey+import Data.X509.Ext+import Data.X509.ExtensionRaw import Data.X509.PrivateKey-import Data.X509.AlgorithmIdentifier+import Data.X509.PublicKey+import Data.X509.Signed import Crypto.Hash @@ -84,7 +89,7 @@ type SignedCertificate = SignedExact Certificate -- | A Signed CRL-type SignedCRL = SignedExact CRL+type SignedCRL = SignedExact CRL -- | Get the Certificate associated to a SignedCertificate getCertificate :: SignedCertificate -> Certificate@@ -107,19 +112,28 @@ -- OpenSSL algorithm is odd, and has been replicated here somewhat. -- only lower the case of ascii character. hashDN :: DistinguishedName -> B.ByteString-hashDN = shorten . hashWith SHA1 . encodeASN1' DER . flip toASN1 [] . DistinguishedNameInner . dnLowerUTF8- where dnLowerUTF8 (DistinguishedName l) = DistinguishedName $ map (second toLowerUTF8) l- toLowerUTF8 (ASN1CharacterString _ s) = ASN1CharacterString UTF8 (B.map asciiToLower s)- asciiToLower c- | c >= w8A && c <= w8Z = fromIntegral (fromIntegral c - fromEnum 'A' + fromEnum 'a')- | otherwise = c- w8A = fromIntegral $ fromEnum 'A'- w8Z = fromIntegral $ fromEnum 'Z'+hashDN =+ shorten+ . hashWith SHA1+ . encodeASN1' DER+ . flip toASN1 []+ . DistinguishedNameInner+ . dnLowerUTF8+ where+ dnLowerUTF8 (DistinguishedName l) = DistinguishedName $ map (second toLowerUTF8) l+ toLowerUTF8 (ASN1CharacterString _ s) = ASN1CharacterString UTF8 (B.map asciiToLower s)+ asciiToLower c+ | c >= w8A && c <= w8Z =+ fromIntegral (fromIntegral c - fromEnum 'A' + fromEnum 'a')+ | otherwise = c+ w8A = fromIntegral $ fromEnum 'A'+ w8Z = fromIntegral $ fromEnum 'Z' -- | Create an openssl style old hash of distinguished name hashDN_old :: DistinguishedName -> B.ByteString hashDN_old = shorten . hashWith MD5 . encodeASN1' DER . flip toASN1 [] shorten :: Digest a -> B.ByteString-shorten b = B.pack $ map i [3,2,1,0]- where i n = BA.index b n+shorten b = B.pack $ map i [3, 2, 1, 0]+ where+ i n = BA.index b n
Data/X509/AlgorithmIdentifier.hs view
@@ -4,93 +4,125 @@ -- Maintainer : Vincent Hanquez <vincent@snarc.org> -- Stability : experimental -- Portability : unknown----module Data.X509.AlgorithmIdentifier- ( HashALG(..)- , PubKeyALG(..)- , SignatureALG(..)- ) where+module Data.X509.AlgorithmIdentifier (+ HashALG (..),+ PubKeyALG (..),+ SignatureALG (..),+) where import Data.ASN1.Types import Data.List (find) -- | Hash Algorithm-data HashALG =- HashMD2+data HashALG+ = HashMD2 | HashMD5 | HashSHA1 | HashSHA224 | HashSHA256 | HashSHA384 | HashSHA512- deriving (Show,Eq)+ deriving (Show, Eq) -- | Public Key Algorithm-data PubKeyALG =- PubKeyALG_RSA -- ^ RSA Public Key algorithm- | PubKeyALG_RSAPSS -- ^ RSA PSS Key algorithm (RFC 3447)- | PubKeyALG_DSA -- ^ DSA Public Key algorithm- | PubKeyALG_EC -- ^ ECDSA & ECDH Public Key algorithm- | PubKeyALG_X25519 -- ^ ECDH 25519 key agreement- | PubKeyALG_X448 -- ^ ECDH 448 key agreement- | PubKeyALG_Ed25519 -- ^ EdDSA 25519 signature algorithm- | PubKeyALG_Ed448 -- ^ EdDSA 448 signature algorithm- | PubKeyALG_DH -- ^ Diffie Hellman Public Key algorithm- | PubKeyALG_Unknown OID -- ^ Unknown Public Key algorithm- deriving (Show,Eq)+data PubKeyALG+ = -- | RSA Public Key algorithm+ PubKeyALG_RSA+ | -- | RSA PSS Key algorithm (RFC 3447)+ PubKeyALG_RSAPSS+ | -- | DSA Public Key algorithm+ PubKeyALG_DSA+ | -- | ECDSA & ECDH Public Key algorithm+ PubKeyALG_EC+ | -- | ECDH 25519 key agreement+ PubKeyALG_X25519+ | -- | ECDH 448 key agreement+ PubKeyALG_X448+ | -- | EdDSA 25519 signature algorithm+ PubKeyALG_Ed25519+ | -- | EdDSA 448 signature algorithm+ PubKeyALG_Ed448+ | -- | ML-DSA-44 signature algorithm (RFC 9881)+ PubKeyALG_MLDSA44+ | -- | ML-DSA-65 signature algorithm (RFC 9881)+ PubKeyALG_MLDSA65+ | -- | ML-DSA-87 signature algorithm (RFC 9881)+ PubKeyALG_MLDSA87+ | -- | Diffie Hellman Public Key algorithm+ PubKeyALG_DH+ | -- | Unknown Public Key algorithm+ PubKeyALG_Unknown OID+ deriving (Show, Eq) -- | Signature Algorithm, often composed of a public key algorithm and a hash -- algorithm. For some signature algorithms the hash algorithm is intrinsic to -- the public key algorithm and is not needed in the data type.-data SignatureALG =- SignatureALG HashALG PubKeyALG+data SignatureALG+ = SignatureALG HashALG PubKeyALG | SignatureALG_IntrinsicHash PubKeyALG | SignatureALG_Unknown OID- deriving (Show,Eq)+ deriving (Show, Eq) instance OIDable PubKeyALG where- getObjectID PubKeyALG_RSA = [1,2,840,113549,1,1,1]- getObjectID PubKeyALG_RSAPSS = [1,2,840,113549,1,1,10]- getObjectID PubKeyALG_DSA = [1,2,840,10040,4,1]- getObjectID PubKeyALG_EC = [1,2,840,10045,2,1]- getObjectID PubKeyALG_X25519 = [1,3,101,110]- getObjectID PubKeyALG_X448 = [1,3,101,111]- getObjectID PubKeyALG_Ed25519 = [1,3,101,112]- getObjectID PubKeyALG_Ed448 = [1,3,101,113]- getObjectID PubKeyALG_DH = [1,2,840,10046,2,1]+ getObjectID PubKeyALG_RSA = [1, 2, 840, 113549, 1, 1, 1]+ getObjectID PubKeyALG_RSAPSS = [1, 2, 840, 113549, 1, 1, 10]+ getObjectID PubKeyALG_DSA = [1, 2, 840, 10040, 4, 1]+ getObjectID PubKeyALG_EC = [1, 2, 840, 10045, 2, 1]+ getObjectID PubKeyALG_X25519 = [1, 3, 101, 110]+ getObjectID PubKeyALG_X448 = [1, 3, 101, 111]+ getObjectID PubKeyALG_Ed25519 = [1, 3, 101, 112]+ getObjectID PubKeyALG_Ed448 = [1, 3, 101, 113]+ getObjectID PubKeyALG_MLDSA44 = [2, 16, 840, 1, 101, 3, 4, 3, 17]+ getObjectID PubKeyALG_MLDSA65 = [2, 16, 840, 1, 101, 3, 4, 3, 18]+ getObjectID PubKeyALG_MLDSA87 = [2, 16, 840, 1, 101, 3, 4, 3, 19]+ getObjectID PubKeyALG_DH = [1, 2, 840, 10046, 2, 1] getObjectID (PubKeyALG_Unknown oid) = oid -sig_table :: [ (OID, SignatureALG) ]+sig_table :: [(OID, SignatureALG)] sig_table =- [ ([1,2,840,113549,1,1,5], SignatureALG HashSHA1 PubKeyALG_RSA)- , ([1,2,840,113549,1,1,4], SignatureALG HashMD5 PubKeyALG_RSA)- , ([1,2,840,113549,1,1,2], SignatureALG HashMD2 PubKeyALG_RSA)- , ([1,2,840,113549,1,1,11], SignatureALG HashSHA256 PubKeyALG_RSA)- , ([1,2,840,113549,1,1,12], SignatureALG HashSHA384 PubKeyALG_RSA)- , ([1,2,840,113549,1,1,13], SignatureALG HashSHA512 PubKeyALG_RSA)- , ([1,2,840,113549,1,1,14], SignatureALG HashSHA224 PubKeyALG_RSA)- , ([1,2,840,10040,4,3], SignatureALG HashSHA1 PubKeyALG_DSA)- , ([1,2,840,10045,4,1], SignatureALG HashSHA1 PubKeyALG_EC)- , ([1,2,840,10045,4,3,1], SignatureALG HashSHA224 PubKeyALG_EC)- , ([1,2,840,10045,4,3,2], SignatureALG HashSHA256 PubKeyALG_EC)- , ([1,2,840,10045,4,3,3], SignatureALG HashSHA384 PubKeyALG_EC)- , ([1,2,840,10045,4,3,4], SignatureALG HashSHA512 PubKeyALG_EC)- , ([2,16,840,1,101,3,4,2,1], SignatureALG HashSHA256 PubKeyALG_RSAPSS)- , ([2,16,840,1,101,3,4,2,2], SignatureALG HashSHA384 PubKeyALG_RSAPSS)- , ([2,16,840,1,101,3,4,2,3], SignatureALG HashSHA512 PubKeyALG_RSAPSS)- , ([2,16,840,1,101,3,4,2,4], SignatureALG HashSHA224 PubKeyALG_RSAPSS)- , ([2,16,840,1,101,3,4,3,1], SignatureALG HashSHA224 PubKeyALG_DSA)- , ([2,16,840,1,101,3,4,3,2], SignatureALG HashSHA256 PubKeyALG_DSA)- , ([1,3,101,112], SignatureALG_IntrinsicHash PubKeyALG_Ed25519)- , ([1,3,101,113], SignatureALG_IntrinsicHash PubKeyALG_Ed448)- ]+ [ ([1, 2, 840, 113549, 1, 1, 5], SignatureALG HashSHA1 PubKeyALG_RSA)+ , ([1, 2, 840, 113549, 1, 1, 4], SignatureALG HashMD5 PubKeyALG_RSA)+ , ([1, 2, 840, 113549, 1, 1, 2], SignatureALG HashMD2 PubKeyALG_RSA)+ , ([1, 2, 840, 113549, 1, 1, 11], SignatureALG HashSHA256 PubKeyALG_RSA)+ , ([1, 2, 840, 113549, 1, 1, 12], SignatureALG HashSHA384 PubKeyALG_RSA)+ , ([1, 2, 840, 113549, 1, 1, 13], SignatureALG HashSHA512 PubKeyALG_RSA)+ , ([1, 2, 840, 113549, 1, 1, 14], SignatureALG HashSHA224 PubKeyALG_RSA)+ , ([1, 2, 840, 10040, 4, 3], SignatureALG HashSHA1 PubKeyALG_DSA)+ , ([1, 2, 840, 10045, 4, 1], SignatureALG HashSHA1 PubKeyALG_EC)+ , ([1, 2, 840, 10045, 4, 3, 1], SignatureALG HashSHA224 PubKeyALG_EC)+ , ([1, 2, 840, 10045, 4, 3, 2], SignatureALG HashSHA256 PubKeyALG_EC)+ , ([1, 2, 840, 10045, 4, 3, 3], SignatureALG HashSHA384 PubKeyALG_EC)+ , ([1, 2, 840, 10045, 4, 3, 4], SignatureALG HashSHA512 PubKeyALG_EC)+ , ([2, 16, 840, 1, 101, 3, 4, 2, 1], SignatureALG HashSHA256 PubKeyALG_RSAPSS)+ , ([2, 16, 840, 1, 101, 3, 4, 2, 2], SignatureALG HashSHA384 PubKeyALG_RSAPSS)+ , ([2, 16, 840, 1, 101, 3, 4, 2, 3], SignatureALG HashSHA512 PubKeyALG_RSAPSS)+ , ([2, 16, 840, 1, 101, 3, 4, 2, 4], SignatureALG HashSHA224 PubKeyALG_RSAPSS)+ , ([2, 16, 840, 1, 101, 3, 4, 3, 1], SignatureALG HashSHA224 PubKeyALG_DSA)+ , ([2, 16, 840, 1, 101, 3, 4, 3, 2], SignatureALG HashSHA256 PubKeyALG_DSA)+ , ([1, 3, 101, 112], SignatureALG_IntrinsicHash PubKeyALG_Ed25519)+ , ([1, 3, 101, 113], SignatureALG_IntrinsicHash PubKeyALG_Ed448)+ ,+ ( [2, 16, 840, 1, 101, 3, 4, 3, 17]+ , SignatureALG_IntrinsicHash PubKeyALG_MLDSA44+ )+ ,+ ( [2, 16, 840, 1, 101, 3, 4, 3, 18]+ , SignatureALG_IntrinsicHash PubKeyALG_MLDSA65+ )+ ,+ ( [2, 16, 840, 1, 101, 3, 4, 3, 19]+ , SignatureALG_IntrinsicHash PubKeyALG_MLDSA87+ )+ ] oidSig :: OID -> SignatureALG oidSig oid = maybe (SignatureALG_Unknown oid) id $ lookup oid sig_table sigOID :: SignatureALG -> OID sigOID (SignatureALG_Unknown oid) = oid-sigOID sig = maybe (error ("unknown OID for " ++ show sig)) fst $ find ((==) sig . snd) sig_table+sigOID sig =+ maybe (error ("unknown OID for " ++ show sig)) fst $+ find ((==) sig . snd) sig_table -- | PSS salt length. Always assume ``-sigopt rsa_pss_saltlen:-1`` saltLen :: HashALG -> Integer@@ -98,23 +130,87 @@ saltLen HashSHA384 = 48 saltLen HashSHA512 = 64 saltLen HashSHA224 = 28-saltLen _ = error "toASN1: X509.SignatureAlg.HashAlg: Unknown hash"+saltLen _ = error "toASN1: X509.SignatureAlg.HashAlg: Unknown hash" instance ASN1Object SignatureALG where- fromASN1 (Start Sequence:OID oid:Null:End Sequence:xs) =+ fromASN1 (Start Sequence : OID oid : Null : End Sequence : xs) = case oidSig oid of SignatureALG_IntrinsicHash _ ->- Left "fromASN1: X509.SignatureALG: EdDSA requires absent parameter"+ Left "fromASN1: X509.SignatureALG: EdDSA and ML-DSA require absent parameter" signatureAlg -> Right (signatureAlg, xs)- fromASN1 (Start Sequence:OID oid:End Sequence:xs) =+ fromASN1 (Start Sequence : OID oid : End Sequence : xs) = Right (oidSig oid, xs)- fromASN1 (Start Sequence:OID [1,2,840,113549,1,1,10]:Start Sequence:Start _:Start Sequence:OID hash1:End Sequence:End _:Start _:Start Sequence:OID [1,2,840,113549,1,1,8]:Start Sequence:OID _hash2:End Sequence:End Sequence:End _:Start _: IntVal _iv: End _: End Sequence : End Sequence:xs) =- Right (oidSig hash1, xs)- fromASN1 (Start Sequence:OID [1,2,840,113549,1,1,10]:Start Sequence:Start _:Start Sequence:OID hash1:Null:End Sequence:End _:Start _:Start Sequence:OID [1,2,840,113549,1,1,8]:Start Sequence:OID _hash2:Null:End Sequence:End Sequence:End _:Start _: IntVal _iv: End _: End Sequence : End Sequence:xs) =- Right (oidSig hash1, xs)+ fromASN1+ ( Start Sequence : OID [1, 2, 840, 113549, 1, 1, 10] : Start Sequence : Start _+ : Start Sequence+ : OID hash1+ : End Sequence+ : End _+ : Start _+ : Start Sequence+ : OID [1, 2, 840, 113549, 1, 1, 8]+ : Start Sequence+ : OID _hash2+ : End Sequence+ : End Sequence+ : End _+ : Start _+ : IntVal _iv+ : End _+ : End Sequence+ : End Sequence+ : xs+ ) =+ Right (oidSig hash1, xs)+ fromASN1+ ( Start Sequence : OID [1, 2, 840, 113549, 1, 1, 10] : Start Sequence : Start _+ : Start Sequence+ : OID hash1+ : Null+ : End Sequence+ : End _+ : Start _+ : Start Sequence+ : OID [1, 2, 840, 113549, 1, 1, 8]+ : Start Sequence+ : OID _hash2+ : Null+ : End Sequence+ : End Sequence+ : End _+ : Start _+ : IntVal _iv+ : End _+ : End Sequence+ : End Sequence+ : xs+ ) =+ Right (oidSig hash1, xs) fromASN1 _ = Left "fromASN1: X509.SignatureALG: unknown format"- toASN1 (SignatureALG_Unknown oid) = \xs -> Start Sequence:OID oid:Null:End Sequence:xs- toASN1 signatureAlg@(SignatureALG hashAlg PubKeyALG_RSAPSS) = \xs -> Start Sequence:OID [1,2,840,113549,1,1,10]:Start Sequence:Start (Container Context 0):Start Sequence:OID (sigOID signatureAlg):End Sequence:End (Container Context 0):Start (Container Context 1): Start Sequence:OID [1,2,840,113549,1,1,8]:Start Sequence:OID (sigOID signatureAlg):End Sequence:End Sequence:End (Container Context 1):Start (Container Context 2):IntVal (saltLen hashAlg):End (Container Context 2):End Sequence:End Sequence:xs- toASN1 signatureAlg@(SignatureALG_IntrinsicHash _) = \xs -> Start Sequence:OID (sigOID signatureAlg):End Sequence:xs- toASN1 signatureAlg = \xs -> Start Sequence:OID (sigOID signatureAlg):Null:End Sequence:xs+ toASN1 (SignatureALG_Unknown oid) = \xs -> Start Sequence : OID oid : Null : End Sequence : xs+ toASN1 signatureAlg@(SignatureALG hashAlg PubKeyALG_RSAPSS) = \xs ->+ Start Sequence+ : OID [1, 2, 840, 113549, 1, 1, 10]+ : Start Sequence+ : Start (Container Context 0)+ : Start Sequence+ : OID (sigOID signatureAlg)+ : End Sequence+ : End (Container Context 0)+ : Start (Container Context 1)+ : Start Sequence+ : OID [1, 2, 840, 113549, 1, 1, 8]+ : Start Sequence+ : OID (sigOID signatureAlg)+ : End Sequence+ : End Sequence+ : End (Container Context 1)+ : Start (Container Context 2)+ : IntVal (saltLen hashAlg)+ : End (Container Context 2)+ : End Sequence+ : End Sequence+ : xs+ toASN1 signatureAlg@(SignatureALG_IntrinsicHash _) = \xs -> Start Sequence : OID (sigOID signatureAlg) : End Sequence : xs+ toASN1 signatureAlg = \xs -> Start Sequence : OID (sigOID signatureAlg) : Null : End Sequence : xs
Data/X509/CRL.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE FlexibleContexts #-}+ -- | -- Module : Data.X509.CRL -- License : BSD-style@@ -8,89 +10,90 @@ -- Read and Write X509 Certificate Revocation List (CRL). -- -- follows RFC5280 / RFC6818.----{-# LANGUAGE FlexibleContexts #-}--module Data.X509.CRL- ( CRL(..)- , RevokedCertificate(..)- ) where--import Control.Applicative+module Data.X509.CRL (+ CRL (..),+ RevokedCertificate (..),+) where -import Data.Hourglass (DateTime, TimezoneOffset(..)) import Data.ASN1.Types+import Data.Hourglass (DateTime, TimezoneOffset (..)) -import Data.X509.DistinguishedName import Data.X509.AlgorithmIdentifier+import Data.X509.DistinguishedName import Data.X509.ExtensionRaw import Data.X509.Internal -- | Describe a Certificate revocation list data CRL = CRL- { crlVersion :: Integer- , crlSignatureAlg :: SignatureALG- , crlIssuer :: DistinguishedName- , crlThisUpdate :: DateTime- , crlNextUpdate :: Maybe DateTime+ { crlVersion :: Integer+ , crlSignatureAlg :: SignatureALG+ , crlIssuer :: DistinguishedName+ , crlThisUpdate :: DateTime+ , crlNextUpdate :: Maybe DateTime , crlRevokedCertificates :: [RevokedCertificate]- , crlExtensions :: Extensions- } deriving (Show,Eq)+ , crlExtensions :: Extensions+ }+ deriving (Show, Eq) -- | Describe a revoked certificate identifiable by serial number. data RevokedCertificate = RevokedCertificate { revokedSerialNumber :: Integer- , revokedDate :: DateTime- , revokedExtensions :: Extensions- } deriving (Show,Eq)+ , revokedDate :: DateTime+ , revokedExtensions :: Extensions+ }+ deriving (Show, Eq) instance ASN1Object CRL where toASN1 crl = encodeCRL crl fromASN1 = runParseASN1State parseCRL instance ASN1Object RevokedCertificate where- fromASN1 = runParseASN1State $- onNextContainer Sequence $- RevokedCertificate- <$> parseSerialNumber- <*> (getNext >>= toTime)- <*> getObject- where toTime (ASN1Time _ t _) = pure t- toTime _ = throwParseError "bad revocation date"+ fromASN1 =+ runParseASN1State $+ onNextContainer Sequence $+ RevokedCertificate+ <$> parseSerialNumber+ <*> (getNext >>= toTime)+ <*> getObject+ where+ toTime (ASN1Time _ t _) = pure t+ toTime _ = throwParseError "bad revocation date" toASN1 (RevokedCertificate serial time crlEntryExtensions) = \xs ->- [ Start Sequence ] ++- [ IntVal serial ] ++- [ ASN1Time TimeGeneralized time (Just (TimezoneOffset 0)) ] ++- toASN1 crlEntryExtensions [] ++- [ End Sequence ] ++- xs+ [Start Sequence]+ ++ [IntVal serial]+ ++ [ASN1Time TimeGeneralized time (Just (TimezoneOffset 0))]+ ++ toASN1 crlEntryExtensions []+ ++ [End Sequence]+ ++ xs parseSerialNumber :: ParseASN1 Integer parseSerialNumber = do n <- getNext case n of IntVal v -> return v- _ -> throwParseError ("missing serial" ++ show n)+ _ -> throwParseError ("missing serial" ++ show n) parseCRL :: ParseASN1 CRL parseCRL = do- CRL <$> (getNext >>= getVersion)+ CRL+ <$> (getNext >>= getVersion) <*> getObject <*> getObject <*> (getNext >>= getThisUpdate) <*> getNextUpdate <*> parseRevokedCertificates <*> parseCRLExtensions- where getVersion (IntVal v) = return $ fromIntegral v- getVersion _ = throwParseError "unexpected type for version"+ where+ getVersion (IntVal v) = return $ fromIntegral v+ getVersion _ = throwParseError "unexpected type for version" - getThisUpdate (ASN1Time _ t1 _) = return t1- getThisUpdate _ = throwParseError "bad this update format, expecting time"+ getThisUpdate (ASN1Time _ t1 _) = return t1+ getThisUpdate _ = throwParseError "bad this update format, expecting time" - getNextUpdate = getNextMaybe timeOrNothing+ getNextUpdate = getNextMaybe timeOrNothing - timeOrNothing (ASN1Time _ tnext _) = Just tnext- timeOrNothing _ = Nothing+ timeOrNothing (ASN1Time _ tnext _) = Just tnext+ timeOrNothing _ = Nothing parseRevokedCertificates :: ParseASN1 [RevokedCertificate] parseRevokedCertificates =@@ -99,19 +102,24 @@ parseCRLExtensions :: ParseASN1 Extensions parseCRLExtensions = fmap adapt $ onNextContainerMaybe (Container Context 0) $ getObject- where adapt (Just e) = e- adapt Nothing = Extensions Nothing+ where+ adapt (Just e) = e+ adapt Nothing = Extensions Nothing encodeCRL :: CRL -> ASN1S encodeCRL crl xs =- [IntVal $ crlVersion crl] ++- toASN1 (crlSignatureAlg crl) [] ++- toASN1 (crlIssuer crl) [] ++- [ASN1Time TimeGeneralized (crlThisUpdate crl) (Just (TimezoneOffset 0))] ++- (maybe [] (\t -> [ASN1Time TimeGeneralized t (Just (TimezoneOffset 0))]) (crlNextUpdate crl)) ++- maybeRevoked (crlRevokedCertificates crl) ++- maybeCrlExts (crlExtensions crl) ++- xs+ [IntVal $ crlVersion crl]+ ++ toASN1 (crlSignatureAlg crl) []+ ++ toASN1 (crlIssuer crl) []+ ++ [ASN1Time TimeGeneralized (crlThisUpdate crl) (Just (TimezoneOffset 0))]+ ++ ( maybe+ []+ (\t -> [ASN1Time TimeGeneralized t (Just (TimezoneOffset 0))])+ (crlNextUpdate crl)+ )+ ++ maybeRevoked (crlRevokedCertificates crl)+ ++ maybeCrlExts (crlExtensions crl)+ ++ xs where maybeRevoked [] = [] maybeRevoked xs' = asn1Container Sequence $ concatMap (\e -> toASN1 e []) xs'
Data/X509/Cert.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE FlexibleContexts #-}+ -- | -- Module : Data.X509.Cert -- License : BSD-style@@ -6,118 +8,138 @@ -- Portability : unknown -- -- X.509 Certificate types and functions----{-# LANGUAGE FlexibleContexts #-}--module Data.X509.Cert (Certificate(..)) where+module Data.X509.Cert (Certificate (..)) where import Data.ASN1.Types-import Control.Applicative ((<$>), (<*>))-import Data.X509.Internal-import Data.X509.PublicKey+import Data.Hourglass import Data.X509.AlgorithmIdentifier import Data.X509.DistinguishedName import Data.X509.ExtensionRaw-import Data.Hourglass+import Data.X509.Internal+import Data.X509.PublicKey -data CertKeyUsage =- CertKeyUsageDigitalSignature- | CertKeyUsageNonRepudiation- | CertKeyUsageKeyEncipherment- | CertKeyUsageDataEncipherment- | CertKeyUsageKeyAgreement- | CertKeyUsageKeyCertSign- | CertKeyUsageCRLSign- | CertKeyUsageEncipherOnly- | CertKeyUsageDecipherOnly- deriving (Show, Eq)+data CertKeyUsage+ = CertKeyUsageDigitalSignature+ | CertKeyUsageNonRepudiation+ | CertKeyUsageKeyEncipherment+ | CertKeyUsageDataEncipherment+ | CertKeyUsageKeyAgreement+ | CertKeyUsageKeyCertSign+ | CertKeyUsageCRLSign+ | CertKeyUsageEncipherOnly+ | CertKeyUsageDecipherOnly+ deriving (Show, Eq) -- | X.509 Certificate type. -- -- This type doesn't include the signature, it's describe in the RFC -- as tbsCertificate. data Certificate = Certificate- { certVersion :: Int -- ^ Version- , certSerial :: Integer -- ^ Serial number- , certSignatureAlg :: SignatureALG -- ^ Signature algorithm- , certIssuerDN :: DistinguishedName -- ^ Issuer DN- , certValidity :: (DateTime, DateTime) -- ^ Validity period (UTC)- , certSubjectDN :: DistinguishedName -- ^ Subject DN- , certPubKey :: PubKey -- ^ Public key- , certExtensions :: Extensions -- ^ Extensions- } deriving (Show,Eq)+ { certVersion :: Int+ -- ^ Version+ , certSerial :: Integer+ -- ^ Serial number+ , certSignatureAlg :: SignatureALG+ -- ^ Signature algorithm+ , certIssuerDN :: DistinguishedName+ -- ^ Issuer DN+ , certValidity :: (DateTime, DateTime)+ -- ^ Validity period (UTC)+ , certSubjectDN :: DistinguishedName+ -- ^ Subject DN+ , certPubKey :: PubKey+ -- ^ Public key+ , certExtensions :: Extensions+ -- ^ Extensions+ }+ deriving (Show, Eq) instance ASN1Object Certificate where- toASN1 certificate = \xs -> encodeCertificateHeader certificate ++ xs- fromASN1 s = runParseASN1State parseCertificate s+ toASN1 certificate = \xs -> encodeCertificateHeader certificate ++ xs+ fromASN1 s = runParseASN1State parseCertificate s parseCertHeaderVersion :: ParseASN1 Int parseCertHeaderVersion = maybe 0 id <$> onNextContainerMaybe (Container Context 0) (getNext >>= getVer)- where getVer (IntVal v) = return $ fromIntegral v- getVer _ = throwParseError "unexpected type for version"+ where+ getVer (IntVal v) = return $ fromIntegral v+ getVer _ = throwParseError "unexpected type for version" parseCertHeaderSerial :: ParseASN1 Integer parseCertHeaderSerial = do n <- getNext case n of IntVal v -> return v- _ -> throwParseError ("missing serial" ++ show n)+ _ -> throwParseError ("missing serial" ++ show n) parseCertHeaderValidity :: ParseASN1 (DateTime, DateTime) parseCertHeaderValidity = getNextContainer Sequence >>= toTimeBound- where toTimeBound [ ASN1Time _ t1 _, ASN1Time _ t2 _ ] = return (t1,t2)- toTimeBound _ = throwParseError "bad validity format"--{- | parse header structure of a x509 certificate. the structure is the following:- Version- Serial Number- Algorithm ID- Issuer- Validity- Not Before- Not After- Subject- Subject Public Key Info- Public Key Algorithm- Subject Public Key- Issuer Unique Identifier (Optional) (>= 2)- Subject Unique Identifier (Optional) (>= 2)- Extensions (Optional) (>= v3)--}+ where+ toTimeBound [ASN1Time _ t1 _, ASN1Time _ t2 _] = return (t1, t2)+ toTimeBound _ = throwParseError "bad validity format" +-- | parse header structure of a x509 certificate. the structure is the following:+-- Version+-- Serial Number+-- Algorithm ID+-- Issuer+-- Validity+-- Not Before+-- Not After+-- Subject+-- Subject Public Key Info+-- Public Key Algorithm+-- Subject Public Key+-- Issuer Unique Identifier (Optional) (>= 2)+-- Subject Unique Identifier (Optional) (>= 2)+-- Extensions (Optional) (>= v3) parseExtensions :: ParseASN1 Extensions parseExtensions = fmap adapt $ onNextContainerMaybe (Container Context 3) $ getObject- where adapt (Just e) = e- adapt Nothing = Extensions Nothing+ where+ adapt (Just e) = e+ adapt Nothing = Extensions Nothing parseCertificate :: ParseASN1 Certificate parseCertificate =- Certificate <$> parseCertHeaderVersion- <*> parseCertHeaderSerial- <*> getObject- <*> getObject- <*> parseCertHeaderValidity- <*> getObject- <*> getObject- <*> parseExtensions+ Certificate+ <$> parseCertHeaderVersion+ <*> parseCertHeaderSerial+ <*> getObject+ <*> getObject+ <*> parseCertHeaderValidity+ <*> getObject+ <*> getObject+ <*> parseExtensions encodeCertificateHeader :: Certificate -> [ASN1] encodeCertificateHeader cert =- eVer ++ eSerial ++ eAlgId ++ eIssuer ++ eValidity ++ eSubject ++ epkinfo ++ eexts- where eVer = asn1Container (Container Context 0) [IntVal (fromIntegral $ certVersion cert)]- eSerial = [IntVal $ certSerial cert]- eAlgId = toASN1 (certSignatureAlg cert) []- eIssuer = toASN1 (certIssuerDN cert) []- (t1, t2) = certValidity cert- eValidity = asn1Container Sequence [ASN1Time (timeType t1) t1 (Just (TimezoneOffset 0))- ,ASN1Time (timeType t2) t2 (Just (TimezoneOffset 0))]- eSubject = toASN1 (certSubjectDN cert) []- epkinfo = toASN1 (certPubKey cert) []- eexts = case certExtensions cert of- Extensions Nothing -> []- exts -> asn1Container (Container Context 3) $ toASN1 exts []- timeType t =- if t >= timeConvert (Date 2050 January 1)+ eVer+ ++ eSerial+ ++ eAlgId+ ++ eIssuer+ ++ eValidity+ ++ eSubject+ ++ epkinfo+ ++ eexts+ where+ eVer =+ asn1Container (Container Context 0) [IntVal (fromIntegral $ certVersion cert)]+ eSerial = [IntVal $ certSerial cert]+ eAlgId = toASN1 (certSignatureAlg cert) []+ eIssuer = toASN1 (certIssuerDN cert) []+ (t1, t2) = certValidity cert+ eValidity =+ asn1Container+ Sequence+ [ ASN1Time (timeType t1) t1 (Just (TimezoneOffset 0))+ , ASN1Time (timeType t2) t2 (Just (TimezoneOffset 0))+ ]+ eSubject = toASN1 (certSubjectDN cert) []+ epkinfo = toASN1 (certPubKey cert) []+ eexts = case certExtensions cert of+ Extensions Nothing -> []+ exts -> asn1Container (Container Context 3) $ toASN1 exts []+ timeType t =+ if t >= timeConvert (Date 2050 January 1) then TimeGeneralized else TimeUTC
Data/X509/CertificateChain.hs view
@@ -4,37 +4,39 @@ -- Maintainer : Vincent Hanquez <vincent@snarc.org> -- Stability : experimental -- Portability : unknown----module Data.X509.CertificateChain- ( CertificateChain(..)- , CertificateChainRaw(..)+module Data.X509.CertificateChain (+ CertificateChain (..),+ CertificateChainRaw (..),+ -- * marshall between CertificateChain and CertificateChainRaw- , decodeCertificateChain- , encodeCertificateChain- ) where+ decodeCertificateChain,+ encodeCertificateChain,+) where +import Data.ByteString (ByteString) import Data.X509.Cert (Certificate) import Data.X509.Signed (SignedExact, decodeSignedObject, encodeSignedObject)-import Data.ByteString (ByteString) -- | A chain of X.509 certificates in exact form. newtype CertificateChain = CertificateChain [SignedExact Certificate]- deriving (Show,Eq)+ deriving (Show, Eq) -- | Represent a chain of X.509 certificates in bytestring form. newtype CertificateChainRaw = CertificateChainRaw [ByteString]- deriving (Show,Eq)+ deriving (Show, Eq) -- | Decode a CertificateChainRaw into a CertificateChain if every -- raw certificate are decoded correctly, otherwise return the index of the -- failed certificate and the error associated.-decodeCertificateChain :: CertificateChainRaw -> Either (Int, String) CertificateChain+decodeCertificateChain+ :: CertificateChainRaw -> Either (Int, String) CertificateChain decodeCertificateChain (CertificateChainRaw l) = either Left (Right . CertificateChain) $ loop 0 l- where loop _ [] = Right []- loop i (r:rs) = case decodeSignedObject r of- Left err -> Left (i, err)- Right o -> either Left (Right . (o :)) $ loop (i+1) rs+ where+ loop _ [] = Right []+ loop i (r : rs) = case decodeSignedObject r of+ Left err -> Left (i, err)+ Right o -> either Left (Right . (o :)) $ loop (i + 1) rs -- | Convert a CertificateChain into a CertificateChainRaw encodeCertificateChain :: CertificateChain -> CertificateChainRaw
Data/X509/DistinguishedName.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE CPP #-}+ -- | -- Module : Data.X509.DistinguishedName -- License : BSD-style@@ -6,18 +8,15 @@ -- Portability : unknown -- -- X.509 Distinguished names types and functions--{-# LANGUAGE CPP #-}-module Data.X509.DistinguishedName- ( DistinguishedName(..)- , DistinguishedNameInner(..)- , ASN1CharacterString(..)+module Data.X509.DistinguishedName (+ DistinguishedName (..),+ DistinguishedNameInner (..),+ ASN1CharacterString (..), -- Distinguished Name Elements- , DnElement(..)- , getDnElement- ) where+ DnElement (..),+ getDnElement,+) where -import Control.Applicative #if MIN_VERSION_base(4,9,0) import Data.Semigroup #else@@ -27,24 +26,29 @@ import Data.X509.Internal -- | A list of OID and strings.-newtype DistinguishedName = DistinguishedName { getDistinguishedElements :: [(OID, ASN1CharacterString)] }- deriving (Show,Eq,Ord)+newtype DistinguishedName = DistinguishedName {getDistinguishedElements :: [(OID, ASN1CharacterString)]}+ deriving (Show, Eq, Ord) -- | Elements commonly available in a 'DistinguishedName' structure-data DnElement =- DnCommonName -- ^ CN- | DnCountry -- ^ Country- | DnOrganization -- ^ O- | DnOrganizationUnit -- ^ OU- | DnEmailAddress -- ^ Email Address (legacy)- deriving (Show,Eq)+data DnElement+ = -- | CN+ DnCommonName+ | -- | Country+ DnCountry+ | -- | O+ DnOrganization+ | -- | OU+ DnOrganizationUnit+ | -- | Email Address (legacy)+ DnEmailAddress+ deriving (Show, Eq) instance OIDable DnElement where- getObjectID DnCommonName = [2,5,4,3]- getObjectID DnCountry = [2,5,4,6]- getObjectID DnOrganization = [2,5,4,10]- getObjectID DnOrganizationUnit = [2,5,4,11]- getObjectID DnEmailAddress = [1,2,840,113549,1,9,1]+ getObjectID DnCommonName = [2, 5, 4, 3]+ getObjectID DnCountry = [2, 5, 4, 6]+ getObjectID DnOrganization = [2, 5, 4, 10]+ getObjectID DnOrganizationUnit = [2, 5, 4, 11]+ getObjectID DnEmailAddress = [1, 2, 840, 113549, 1, 9, 1] -- | Try to get a specific element in a 'DistinguishedName' structure getDnElement :: DnElement -> DistinguishedName -> Maybe ASN1CharacterString@@ -53,7 +57,7 @@ -- | Only use to encode a DistinguishedName without including it in a -- Sequence newtype DistinguishedNameInner = DistinguishedNameInner DistinguishedName- deriving (Show,Eq)+ deriving (Show, Eq) #if MIN_VERSION_base(4,9,0) instance Semigroup DistinguishedName where@@ -61,20 +65,21 @@ #endif instance Monoid DistinguishedName where- mempty = DistinguishedName []+ mempty = DistinguishedName [] #if !(MIN_VERSION_base(4,11,0)) mappend (DistinguishedName l1) (DistinguishedName l2) = DistinguishedName (l1++l2) #endif instance ASN1Object DistinguishedName where toASN1 dn = \xs -> encodeDN dn ++ xs- fromASN1 = runParseASN1State parseDN+ fromASN1 = runParseASN1State parseDN -- FIXME parseDNInner in fromASN1 is probably wrong as we don't have a container -- and thus hasNext should be replaced by a isFinished clause. instance ASN1Object DistinguishedNameInner where toASN1 (DistinguishedNameInner dn) = \xs -> encodeDNinner dn ++ xs- fromASN1 = runParseASN1State (DistinguishedNameInner . DistinguishedName <$> parseDNInner)+ fromASN1 =+ runParseASN1State (DistinguishedNameInner . DistinguishedName <$> parseDNInner) parseDN :: ParseASN1 DistinguishedName parseDN = DistinguishedName <$> onNextContainer Sequence parseDNInner@@ -87,11 +92,13 @@ s <- getNextContainer Sequence case s of [OID oid, ASN1String cs] -> return (oid, cs)- _ -> throwParseError ("expecting [OID,String] got " ++ show s)+ _ ->+ throwParseError ("expecting [OID,String] got " ++ show s) encodeDNinner :: DistinguishedName -> [ASN1] encodeDNinner (DistinguishedName dn) = concatMap dnSet dn- where dnSet (oid, cs) = asn1Container Set $ asn1Container Sequence [OID oid, ASN1String cs]+ where+ dnSet (oid, cs) = asn1Container Set $ asn1Container Sequence [OID oid, ASN1String cs] encodeDN :: DistinguishedName -> [ASN1] encodeDN dn = asn1Container Sequence $ encodeDNinner dn
Data/X509/EC.hs view
@@ -6,27 +6,25 @@ -- Portability : unknown -- -- Utilities related to Elliptic Curve certificates and keys.----module Data.X509.EC- (- unserializePoint- , ecPubKeyCurve- , ecPubKeyCurveName- , ecPrivKeyCurve- , ecPrivKeyCurveName- , lookupCurveNameByOID- ) where+module Data.X509.EC (+ unserializePoint,+ ecPubKeyCurve,+ ecPubKeyCurveName,+ ecPrivKeyCurve,+ ecPrivKeyCurveName,+ lookupCurveNameByOID,+) where import Data.ASN1.OID import Data.List (find) import Data.X509.OID-import Data.X509.PublicKey import Data.X509.PrivateKey+import Data.X509.PublicKey -import qualified Crypto.PubKey.ECC.Prim as ECC+import Crypto.Number.Serialize (os2ip)+import qualified Crypto.PubKey.ECC.Prim as ECC import qualified Crypto.PubKey.ECC.Types as ECC-import Crypto.Number.Serialize (os2ip) import qualified Data.ByteString as B @@ -35,91 +33,95 @@ unserializePoint :: ECC.Curve -> SerializedPoint -> Maybe ECC.Point unserializePoint curve (SerializedPoint bs) = case B.uncons bs of- Nothing -> Nothing+ Nothing -> Nothing Just (ptFormat, input) -> case ptFormat of- 4 -> if B.length input /= 2 * bytes+ 4 ->+ if B.length input /= 2 * bytes then Nothing else let (x, y) = B.splitAt bytes input- p = ECC.Point (os2ip x) (os2ip y)+ p = ECC.Point (os2ip x) (os2ip y) in if ECC.isPointValid curve p then Just p else Nothing -- 2 and 3 for compressed format. _ -> Nothing- where bits = ECC.curveSizeBits curve- bytes = (bits + 7) `div` 8+ where+ bits = ECC.curveSizeBits curve+ bytes = (bits + 7) `div` 8 -- | Return the curve associated to an EC Public Key. This does not check -- if a curve in explicit format is valid: if the input is not trusted one -- should consider 'ecPubKeyCurveName' instead. ecPubKeyCurve :: PubKeyEC -> Maybe ECC.Curve ecPubKeyCurve (PubKeyEC_Named name _) = Just $ ECC.getCurveByName name-ecPubKeyCurve pub@PubKeyEC_Prime{} =+ecPubKeyCurve pub@PubKeyEC_Prime{} = fmap buildCurve $ unserializePoint (buildCurve undefined) (pubkeyEC_generator pub) where prime = pubkeyEC_prime pub buildCurve g =- let cc = ECC.CurveCommon- { ECC.ecc_a = pubkeyEC_a pub- , ECC.ecc_b = pubkeyEC_b pub- , ECC.ecc_g = g- , ECC.ecc_n = pubkeyEC_order pub- , ECC.ecc_h = pubkeyEC_cofactor pub- }+ let cc =+ ECC.CurveCommon+ { ECC.ecc_a = pubkeyEC_a pub+ , ECC.ecc_b = pubkeyEC_b pub+ , ECC.ecc_g = g+ , ECC.ecc_n = pubkeyEC_order pub+ , ECC.ecc_h = pubkeyEC_cofactor pub+ } in ECC.CurveFP (ECC.CurvePrime prime cc) -- | Return the name of a standard curve associated to an EC Public Key ecPubKeyCurveName :: PubKeyEC -> Maybe ECC.CurveName ecPubKeyCurveName (PubKeyEC_Named name _) = Just name-ecPubKeyCurveName pub@PubKeyEC_Prime{} =+ecPubKeyCurveName pub@PubKeyEC_Prime{} = find matchPrimeCurve $ enumFrom $ toEnum 0 where matchPrimeCurve c = case ECC.getCurveByName c of ECC.CurveFP (ECC.CurvePrime p cc) ->- ECC.ecc_a cc == pubkeyEC_a pub &&- ECC.ecc_b cc == pubkeyEC_b pub &&- ECC.ecc_n cc == pubkeyEC_order pub &&- p == pubkeyEC_prime pub- _ -> False+ ECC.ecc_a cc == pubkeyEC_a pub+ && ECC.ecc_b cc == pubkeyEC_b pub+ && ECC.ecc_n cc == pubkeyEC_order pub+ && p == pubkeyEC_prime pub+ _ -> False -- | Return the EC curve associated to an EC Private Key. This does not check -- if a curve in explicit format is valid: if the input is not trusted one -- should consider 'ecPrivKeyCurveName' instead. ecPrivKeyCurve :: PrivKeyEC -> Maybe ECC.Curve ecPrivKeyCurve (PrivKeyEC_Named name _) = Just $ ECC.getCurveByName name-ecPrivKeyCurve priv@PrivKeyEC_Prime{} =+ecPrivKeyCurve priv@PrivKeyEC_Prime{} = fmap buildCurve $ unserializePoint (buildCurve undefined) (privkeyEC_generator priv) where prime = privkeyEC_prime priv buildCurve g =- let cc = ECC.CurveCommon- { ECC.ecc_a = privkeyEC_a priv- , ECC.ecc_b = privkeyEC_b priv- , ECC.ecc_g = g- , ECC.ecc_n = privkeyEC_order priv- , ECC.ecc_h = privkeyEC_cofactor priv- }+ let cc =+ ECC.CurveCommon+ { ECC.ecc_a = privkeyEC_a priv+ , ECC.ecc_b = privkeyEC_b priv+ , ECC.ecc_g = g+ , ECC.ecc_n = privkeyEC_order priv+ , ECC.ecc_h = privkeyEC_cofactor priv+ } in ECC.CurveFP (ECC.CurvePrime prime cc) -- | Return the name of a standard curve associated to an EC Private Key ecPrivKeyCurveName :: PrivKeyEC -> Maybe ECC.CurveName ecPrivKeyCurveName (PrivKeyEC_Named name _) = Just name-ecPrivKeyCurveName priv@PrivKeyEC_Prime{} =+ecPrivKeyCurveName priv@PrivKeyEC_Prime{} = find matchPrimeCurve $ enumFrom $ toEnum 0 where matchPrimeCurve c = case ECC.getCurveByName c of ECC.CurveFP (ECC.CurvePrime p cc) ->- ECC.ecc_a cc == privkeyEC_a priv &&- ECC.ecc_b cc == privkeyEC_b priv &&- ECC.ecc_n cc == privkeyEC_order priv &&- p == privkeyEC_prime priv- _ -> False+ ECC.ecc_a cc == privkeyEC_a priv+ && ECC.ecc_b cc == privkeyEC_b priv+ && ECC.ecc_n cc == privkeyEC_order priv+ && p == privkeyEC_prime priv+ _ -> False -- | Return the curve name associated to an OID lookupCurveNameByOID :: OID -> Maybe ECC.CurveName
Data/X509/Ext.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+ -- | -- Module : Data.X509.Ext -- License : BSD-style@@ -6,58 +9,59 @@ -- Portability : unknown -- -- extension processing module.----{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ScopedTypeVariables #-}-module Data.X509.Ext- ( Extension(..)+module Data.X509.Ext (+ Extension (..),+ -- * Common extension usually found in x509v3- , ExtBasicConstraints(..)- , ExtKeyUsage(..)- , ExtKeyUsageFlag(..)- , ExtExtendedKeyUsage(..)- , ExtKeyUsagePurpose(..)- , ExtSubjectKeyId(..)- , ExtSubjectAltName(..)- , ExtAuthorityKeyId(..)- , ExtCrlDistributionPoints(..)- , ExtNetscapeComment(..)- , AltName(..)- , DistributionPoint(..)- , ReasonFlag(..)+ ExtBasicConstraints (..),+ ExtKeyUsage (..),+ ExtKeyUsageFlag (..),+ ExtExtendedKeyUsage (..),+ ExtKeyUsagePurpose (..),+ ExtSubjectKeyId (..),+ ExtSubjectAltName (..),+ ExtAuthorityKeyId (..),+ ExtCrlDistributionPoints (..),+ ExtNetscapeComment (..),+ ExtNameConstraints (..),+ GeneralSubtree (..),+ AltName (..),+ DistributionPoint (..),+ ReasonFlag (..),+ -- * Accessor turning extension into a specific one- , extensionGet- , extensionGetE- , extensionDecode- , extensionEncode- ) where+ extensionGet,+ extensionGetE,+ extensionDecode,+ extensionEncode,+ recognizedOIDs,+) where -import qualified Data.ByteString as B-import qualified Data.ByteString.Char8 as BC-import Data.ASN1.Types-import Data.ASN1.Parse-import Data.ASN1.Encoding+import Control.Monad import Data.ASN1.BinaryEncoding import Data.ASN1.BitArray-import Data.Proxy+import Data.ASN1.Encoding+import Data.ASN1.Parse+import Data.ASN1.Types+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC import Data.List (find)-import Data.X509.ExtensionRaw+import Data.Proxy import Data.X509.DistinguishedName-import Control.Applicative-import Control.Monad+import Data.X509.ExtensionRaw -- | key usage flag that is found in the key usage extension field.-data ExtKeyUsageFlag =- KeyUsage_digitalSignature -- (0)- | KeyUsage_nonRepudiation -- (1) recent X.509 ver have renamed this bit to contentCommitment- | KeyUsage_keyEncipherment -- (2)+data ExtKeyUsageFlag+ = KeyUsage_digitalSignature -- (0)+ | KeyUsage_nonRepudiation -- (1) recent X.509 ver have renamed this bit to contentCommitment+ | KeyUsage_keyEncipherment -- (2) | KeyUsage_dataEncipherment -- (3)- | KeyUsage_keyAgreement -- (4)- | KeyUsage_keyCertSign -- (5)- | KeyUsage_cRLSign -- (6)- | KeyUsage_encipherOnly -- (7)- | KeyUsage_decipherOnly -- (8)- deriving (Show,Eq,Ord,Enum)+ | KeyUsage_keyAgreement -- (4)+ | KeyUsage_keyCertSign -- (5)+ | KeyUsage_cRLSign -- (6)+ | KeyUsage_encipherOnly -- (7)+ | KeyUsage_decipherOnly -- (8)+ deriving (Show, Eq, Ord, Enum) {- -- RFC 5280@@ -76,10 +80,10 @@ -- e.g. raw ascii string. Add method to parse and encode with -- ByteString class Extension a where- extOID :: a -> OID+ extOID :: a -> OID extHasNestedASN1 :: Proxy a -> Bool- extEncode :: a -> [ASN1]- extDecode :: [ASN1] -> Either String a+ extEncode :: a -> [ASN1]+ extDecode :: [ASN1] -> Either String a extDecodeBs :: B.ByteString -> Either String a extDecodeBs = (either (Left . show) Right . decodeASN1' BER) >=> extDecode@@ -87,24 +91,25 @@ extEncodeBs :: a -> B.ByteString extEncodeBs = encodeASN1' DER . extEncode - -- | Get a specific extension from a lists of raw extensions extensionGet :: Extension a => Extensions -> Maybe a-extensionGet (Extensions Nothing) = Nothing+extensionGet (Extensions Nothing) = Nothing extensionGet (Extensions (Just l)) = findExt l- where findExt [] = Nothing- findExt (x:xs) = case extensionDecode x of- Just (Right e) -> Just e- _ -> findExt xs+ where+ findExt [] = Nothing+ findExt (x : xs) = case extensionDecode x of+ Just (Right e) -> Just e+ _ -> findExt xs -- | Get a specific extension from a lists of raw extensions extensionGetE :: Extension a => Extensions -> Maybe (Either String a)-extensionGetE (Extensions Nothing) = Nothing+extensionGetE (Extensions Nothing) = Nothing extensionGetE (Extensions (Just l)) = findExt l- where findExt [] = Nothing- findExt (x:xs) = case extensionDecode x of- Just r -> Just r- _ -> findExt xs+ where+ findExt [] = Nothing+ findExt (x : xs) = case extensionDecode x of+ Just r -> Just r+ _ -> findExt xs -- | Try to decode an ExtensionRaw. --@@ -112,90 +117,102 @@ -- * Nothing, the OID doesn't match -- * Just Left, the OID matched, but the extension couldn't be decoded -- * Just Right, the OID matched, and the extension has been succesfully decoded-extensionDecode :: forall a . Extension a => ExtensionRaw -> Maybe (Either String a)+extensionDecode+ :: forall a. Extension a => ExtensionRaw -> Maybe (Either String a) extensionDecode er@(ExtensionRaw oid _ content)- | extOID (undefined :: a) /= oid = Nothing+ | extOID (undefined :: a) /= oid = Nothing | extHasNestedASN1 (Proxy :: Proxy a) = Just (tryExtRawASN1 er >>= extDecode)- | otherwise = Just (extDecodeBs content)+ | otherwise = Just (extDecodeBs content) -- | Encode an Extension to extensionRaw-extensionEncode :: forall a . Extension a => Bool -> a -> ExtensionRaw+extensionEncode :: forall a. Extension a => Bool -> a -> ExtensionRaw extensionEncode critical ext- | extHasNestedASN1 (Proxy :: Proxy a) = ExtensionRaw (extOID ext) critical (encodeASN1' DER $ extEncode ext)- | otherwise = ExtensionRaw (extOID ext) critical (extEncodeBs ext)+ | extHasNestedASN1 (Proxy :: Proxy a) =+ ExtensionRaw (extOID ext) critical (encodeASN1' DER $ extEncode ext)+ | otherwise =+ ExtensionRaw (extOID ext) critical (extEncodeBs ext) -- | Basic Constraints data ExtBasicConstraints = ExtBasicConstraints Bool (Maybe Integer)- deriving (Show,Eq)+ deriving (Show, Eq) instance Extension ExtBasicConstraints where- extOID = const [2,5,29,19]+ extOID = const [2, 5, 29, 19] extHasNestedASN1 = const True- extEncode (ExtBasicConstraints b Nothing) = [Start Sequence,Boolean b,End Sequence]- extEncode (ExtBasicConstraints b (Just i)) = [Start Sequence,Boolean b,IntVal i,End Sequence]+ extEncode (ExtBasicConstraints b Nothing) = [Start Sequence, Boolean b, End Sequence]+ extEncode (ExtBasicConstraints b (Just i)) = [Start Sequence, Boolean b, IntVal i, End Sequence] - extDecode [Start Sequence,Boolean b,IntVal v,End Sequence]- | v >= 0 = Right (ExtBasicConstraints b (Just v))+ extDecode [Start Sequence, Boolean b, IntVal v, End Sequence]+ | v >= 0 = Right (ExtBasicConstraints b (Just v)) | otherwise = Left "invalid pathlen"- extDecode [Start Sequence,Boolean b,End Sequence] = Right (ExtBasicConstraints b Nothing)- extDecode [Start Sequence,End Sequence] = Right (ExtBasicConstraints False Nothing)+ extDecode [Start Sequence, Boolean b, End Sequence] = Right (ExtBasicConstraints b Nothing)+ extDecode [Start Sequence, End Sequence] = Right (ExtBasicConstraints False Nothing) extDecode _ = Left "unknown sequence" -- | Describe key usage data ExtKeyUsage = ExtKeyUsage [ExtKeyUsageFlag]- deriving (Show,Eq)+ deriving (Show, Eq) instance Extension ExtKeyUsage where- extOID = const [2,5,29,15]+ extOID = const [2, 5, 29, 15] extHasNestedASN1 = const True extEncode (ExtKeyUsage flags) = [BitString $ flagsToBits flags] extDecode [BitString bits] = Right $ ExtKeyUsage $ bitsToFlags bits extDecode _ = Left "unknown sequence" -- | Key usage purposes for the ExtendedKeyUsage extension-data ExtKeyUsagePurpose =- KeyUsagePurpose_ServerAuth+data ExtKeyUsagePurpose+ = KeyUsagePurpose_ServerAuth | KeyUsagePurpose_ClientAuth | KeyUsagePurpose_CodeSigning | KeyUsagePurpose_EmailProtection | KeyUsagePurpose_TimeStamping | KeyUsagePurpose_OCSPSigning | KeyUsagePurpose_Unknown OID- deriving (Show,Eq,Ord)+ deriving (Show, Eq, Ord) extKeyUsagePurposedOID :: [(OID, ExtKeyUsagePurpose)] extKeyUsagePurposedOID =- [(keyUsagePurposePrefix 1, KeyUsagePurpose_ServerAuth)- ,(keyUsagePurposePrefix 2, KeyUsagePurpose_ClientAuth)- ,(keyUsagePurposePrefix 3, KeyUsagePurpose_CodeSigning)- ,(keyUsagePurposePrefix 4, KeyUsagePurpose_EmailProtection)- ,(keyUsagePurposePrefix 8, KeyUsagePurpose_TimeStamping)- ,(keyUsagePurposePrefix 9, KeyUsagePurpose_OCSPSigning)]- where keyUsagePurposePrefix r = [1,3,6,1,5,5,7,3,r]+ [ (keyUsagePurposePrefix 1, KeyUsagePurpose_ServerAuth)+ , (keyUsagePurposePrefix 2, KeyUsagePurpose_ClientAuth)+ , (keyUsagePurposePrefix 3, KeyUsagePurpose_CodeSigning)+ , (keyUsagePurposePrefix 4, KeyUsagePurpose_EmailProtection)+ , (keyUsagePurposePrefix 8, KeyUsagePurpose_TimeStamping)+ , (keyUsagePurposePrefix 9, KeyUsagePurpose_OCSPSigning)+ ]+ where+ keyUsagePurposePrefix r = [1, 3, 6, 1, 5, 5, 7, 3, r] -- | Extended key usage extension data ExtExtendedKeyUsage = ExtExtendedKeyUsage [ExtKeyUsagePurpose]- deriving (Show,Eq)+ deriving (Show, Eq) instance Extension ExtExtendedKeyUsage where- extOID = const [2,5,29,37]+ extOID = const [2, 5, 29, 37] extHasNestedASN1 = const True extEncode (ExtExtendedKeyUsage purposes) = [Start Sequence] ++ map (OID . lookupRev) purposes ++ [End Sequence]- where lookupRev (KeyUsagePurpose_Unknown oid) = oid- lookupRev kup = maybe (error "unknown key usage purpose") fst $ find ((==) kup . snd) extKeyUsagePurposedOID- extDecode l = ExtExtendedKeyUsage `fmap` (flip runParseASN1 l $ onNextContainer Sequence $ getMany $ do- n <- getNext- case n of- OID o -> return $ maybe (KeyUsagePurpose_Unknown o) id $ lookup o extKeyUsagePurposedOID- _ -> error "invalid content in extended key usage")+ where+ lookupRev (KeyUsagePurpose_Unknown oid) = oid+ lookupRev kup =+ maybe (error "unknown key usage purpose") fst $+ find ((==) kup . snd) extKeyUsagePurposedOID+ extDecode l =+ ExtExtendedKeyUsage+ `fmap` ( flip runParseASN1 l $ onNextContainer Sequence $ getMany $ do+ n <- getNext+ case n of+ OID o ->+ return $ maybe (KeyUsagePurpose_Unknown o) id $ lookup o extKeyUsagePurposedOID+ _ -> error "invalid content in extended key usage"+ ) -- | Provide a way to identify a public key by a short hash. data ExtSubjectKeyId = ExtSubjectKeyId B.ByteString- deriving (Show,Eq)+ deriving (Show, Eq) instance Extension ExtSubjectKeyId where- extOID = const [2,5,29,14]+ extOID = const [2, 5, 29, 14] extHasNestedASN1 = const True extEncode (ExtSubjectKeyId o) = [OctetString o] extDecode [OctetString o] = Right $ ExtSubjectKeyId o@@ -206,26 +223,25 @@ -- Not all name types are available, missing: -- otherName -- x400Address--- directoryName -- ediPartyName -- registeredID----data AltName =- AltNameRFC822 String+data AltName+ = AltNameRFC822 String | AltNameDNS String+ | AltNameDN DistinguishedName | AltNameURI String- | AltNameIP B.ByteString+ | AltNameIP B.ByteString | AltNameXMPP String | AltNameDNSSRV String- deriving (Show,Eq,Ord)+ deriving (Show, Eq, Ord) -- | Provide a way to supply alternate name that can be -- used for matching host name. data ExtSubjectAltName = ExtSubjectAltName [AltName]- deriving (Show,Eq,Ord)+ deriving (Show, Eq, Ord) instance Extension ExtSubjectAltName where- extOID = const [2,5,29,17]+ extOID = const [2, 5, 29, 17] extHasNestedASN1 = const True extEncode (ExtSubjectAltName names) = encodeGeneralNames names extDecode l = runParseASN1 (ExtSubjectAltName <$> parseGeneralNames) l@@ -233,24 +249,24 @@ -- | Provide a mean to identify the public key corresponding to the private key -- used to signed a certificate. data ExtAuthorityKeyId = ExtAuthorityKeyId B.ByteString- deriving (Show,Eq)+ deriving (Show, Eq) instance Extension ExtAuthorityKeyId where- extOID _ = [2,5,29,35]+ extOID _ = [2, 5, 29, 35] extHasNestedASN1 = const True extEncode (ExtAuthorityKeyId keyid) =- [Start Sequence,Other Context 0 keyid,End Sequence]- extDecode [Start Sequence,Other Context 0 keyid,End Sequence] =+ [Start Sequence, Other Context 0 keyid, End Sequence]+ extDecode [Start Sequence, Other Context 0 keyid, End Sequence] = Right $ ExtAuthorityKeyId keyid extDecode _ = Left "unknown sequence" -- | Identify how CRL information is obtained data ExtCrlDistributionPoints = ExtCrlDistributionPoints [DistributionPoint]- deriving (Show,Eq)+ deriving (Show, Eq) -- | Reason flag for the CRL-data ReasonFlag =- Reason_Unused+data ReasonFlag+ = Reason_Unused | Reason_KeyCompromise | Reason_CACompromise | Reason_AffiliationChanged@@ -259,92 +275,254 @@ | Reason_CertificateHold | Reason_PrivilegeWithdrawn | Reason_AACompromise- deriving (Show,Eq,Ord,Enum)+ deriving (Show, Eq, Ord, Enum) -- | Distribution point as either some GeneralNames or a DN-data DistributionPoint =- DistributionPointFullName [AltName]+data DistributionPoint+ = DistributionPointFullName [AltName] | DistributionNameRelative DistinguishedName- deriving (Show,Eq)+ deriving (Show, Eq) instance Extension ExtCrlDistributionPoints where- extOID _ = [2,5,29,31]+ extOID _ = [2, 5, 29, 31] extHasNestedASN1 = const True extEncode = error "extEncode ExtCrlDistributionPoints unimplemented" extDecode = error "extDecode ExtCrlDistributionPoints unimplemented"- --extEncode (ExtCrlDistributionPoints ) +-- extEncode (ExtCrlDistributionPoints )+ parseGeneralNames :: ParseASN1 [AltName] parseGeneralNames = onNextContainer Sequence $ getMany getAddr++getAddr :: ParseASN1 AltName+getAddr = do+ m <- onNextContainerMaybe (Container Context 0) getComposedAddr+ case m of+ Nothing -> getSimpleAddr+ Just r -> return r where- getAddr = do- m <- onNextContainerMaybe (Container Context 0) getComposedAddr- case m of- Nothing -> getSimpleAddr- Just r -> return r- getComposedAddr = do- n <- getNext- case n of- OID [1,3,6,1,5,5,7,8,5] -> do -- xmpp addr- c <- getNextContainerMaybe (Container Context 0)- case c of- Just [ASN1String cs] ->- case asn1CharacterToString cs of- Nothing -> throwParseError ("GeneralNames: invalid string for XMPP Addr")- Just s -> return $ AltNameXMPP s- _ -> throwParseError ("GeneralNames: expecting string for XMPP Addr got: " ++ show c)- OID [1,3,6,1,5,5,7,8,7] -> do -- DNSSRV addr- c <- getNextContainerMaybe (Container Context 0)- case c of- Just [ASN1String cs] ->- case asn1CharacterToString cs of- Nothing -> throwParseError ("GeneralNames: invalid string for DNSSrv Addr")- Just s -> return $ AltNameDNSSRV s- _ -> throwParseError ("GeneralNames: expecting string for DNSSRV Addr got: " ++ show c)- OID unknown -> throwParseError ("GeneralNames: unknown OID " ++ show unknown)- _ -> throwParseError ("GeneralNames: expecting OID but got " ++ show n)+ getComposedAddr = do+ n <- getNext+ case n of+ OID [1, 3, 6, 1, 5, 5, 7, 8, 5] -> do+ -- xmpp addr+ c <- getNextContainerMaybe (Container Context 0)+ case c of+ Just [ASN1String cs] ->+ case asn1CharacterToString cs of+ Nothing -> throwParseError ("GeneralNames: invalid string for XMPP Addr")+ Just s -> return $ AltNameXMPP s+ _ ->+ throwParseError ("GeneralNames: expecting string for XMPP Addr got: " ++ show c)+ OID [1, 3, 6, 1, 5, 5, 7, 8, 7] -> do+ -- DNSSRV addr+ c <- getNextContainerMaybe (Container Context 0)+ case c of+ Just [ASN1String cs] ->+ case asn1CharacterToString cs of+ Nothing -> throwParseError ("GeneralNames: invalid string for DNSSrv Addr")+ Just s -> return $ AltNameDNSSRV s+ _ ->+ throwParseError+ ("GeneralNames: expecting string for DNSSRV Addr got: " ++ show c)+ OID unknown -> throwParseError ("GeneralNames: unknown OID " ++ show unknown)+ _ -> throwParseError ("GeneralNames: expecting OID but got " ++ show n) - getSimpleAddr = do- n <- getNext- case n of- (Other Context 1 b) -> return $ AltNameRFC822 $ BC.unpack b- (Other Context 2 b) -> return $ AltNameDNS $ BC.unpack b- (Other Context 6 b) -> return $ AltNameURI $ BC.unpack b- (Other Context 7 b) -> return $ AltNameIP b- _ -> throwParseError ("GeneralNames: not coping with unknown stream " ++ show n)+ getSimpleAddr = do+ n <- getNext+ case n of+ (Other Context 1 b) -> return $ AltNameRFC822 $ BC.unpack b+ (Other Context 2 b) -> return $ AltNameDNS $ BC.unpack b+ (Other Context 4 b) -> case decodeASN1' DER b of+ Left e1 -> throwParseError $ show e1+ Right as -> case runParseASN1 getObject as of+ Right dn -> return $ AltNameDN dn+ Left e2 -> throwParseError e2+ (Other Context 6 b) -> return $ AltNameURI $ BC.unpack b+ (Other Context 7 b) -> return $ AltNameIP b+ _ ->+ throwParseError ("GeneralNames: not coping with unknown stream " ++ show n) encodeGeneralNames :: [AltName] -> [ASN1] encodeGeneralNames names = [Start Sequence]- ++ concatMap encodeAltName names- ++ [End Sequence]- where encodeAltName (AltNameRFC822 n) = [Other Context 1 $ BC.pack n]- encodeAltName (AltNameDNS n) = [Other Context 2 $ BC.pack n]- encodeAltName (AltNameURI n) = [Other Context 6 $ BC.pack n]- encodeAltName (AltNameIP n) = [Other Context 7 $ n]- encodeAltName (AltNameXMPP n) = [Start (Container Context 0),OID[1,3,6,1,5,5,7,8,5]- ,Start (Container Context 0), ASN1String $ asn1CharacterString UTF8 n, End (Container Context 0)- ,End (Container Context 0)]- encodeAltName (AltNameDNSSRV n) = [Start (Container Context 0),OID[1,3,6,1,5,5,7,8,5]- ,Start (Container Context 0), ASN1String $ asn1CharacterString UTF8 n, End (Container Context 0)- ,End (Container Context 0)]+ ++ concatMap encodeAltName names+ ++ [End Sequence] +encodeAltName :: AltName -> [ASN1]+encodeAltName (AltNameRFC822 n) = [Other Context 1 $ BC.pack n]+encodeAltName (AltNameDNS n) = [Other Context 2 $ BC.pack n]+encodeAltName (AltNameDN dn) = [Other Context 4 $ encodeASN1' DER $ toASN1 dn []]+encodeAltName (AltNameURI n) = [Other Context 6 $ BC.pack n]+encodeAltName (AltNameIP n) = [Other Context 7 $ n]+encodeAltName (AltNameXMPP n) =+ [ Start (Container Context 0)+ , OID [1, 3, 6, 1, 5, 5, 7, 8, 5]+ , Start (Container Context 0)+ , ASN1String $ asn1CharacterString UTF8 n+ , End (Container Context 0)+ , End (Container Context 0)+ ]+encodeAltName (AltNameDNSSRV n) =+ [ Start (Container Context 0)+ , OID [1, 3, 6, 1, 5, 5, 7, 8, 5]+ , Start (Container Context 0)+ , ASN1String $ asn1CharacterString UTF8 n+ , End (Container Context 0)+ , End (Container Context 0)+ ]+ bitsToFlags :: Enum a => BitArray -> [a]-bitsToFlags bits = concat $ flip map [0..(bitArrayLength bits-1)] $ \i -> do- let isSet = bitArrayGetBit bits i- if isSet then [toEnum $ fromIntegral i] else []+bitsToFlags bits = concat $ flip map [0 .. (bitArrayLength bits - 1)] $ \i -> do+ let isSet = bitArrayGetBit bits i+ if isSet then [toEnum $ fromIntegral i] else [] flagsToBits :: Enum a => [a] -> BitArray flagsToBits flags = foldl bitArraySetBit bitArrayEmpty $ map (fromIntegral . fromEnum) flags- where bitArrayEmpty = toBitArray (B.pack [0,0]) 7+ where+ bitArrayEmpty = toBitArray (B.pack [0, 0]) 7 data ExtNetscapeComment = ExtNetscapeComment B.ByteString- deriving (Show,Eq)+ deriving (Show, Eq) instance Extension ExtNetscapeComment where- extOID _ = [2,16,840,1,113730,1,13]+ extOID _ = [2, 16, 840, 1, 113730, 1, 13] extHasNestedASN1 = const False extEncode = error "Extension: Netscape Comment do not contain nested ASN1" extDecode = error "Extension: Netscape Comment do not contain nested ASN1" extEncodeBs (ExtNetscapeComment b) = b extDecodeBs = Right . ExtNetscapeComment++--------------------------------------------------------------------------------+-- RFC5280+--+-- NameConstraints ::= SEQUENCE {+-- permittedSubtrees [0] GeneralSubtrees OPTIONAL,+-- excludedSubtrees [1] GeneralSubtrees OPTIONAL+-- }+--+-- GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree+--+-- GeneralSubtree ::= SEQUENCE {+-- base GeneralName,+-- minimum [0] BaseDistance DEFAULT 0,+-- maximum [1] BaseDistance OPTIONAL+-- }+--------------------------------------------------------------------------------++data GeneralSubtree = GeneralSubtree AltName Integer (Maybe Integer)+ deriving (Show, Eq)++data ExtNameConstraints = ExtNameConstraints [GeneralSubtree] [GeneralSubtree]+ deriving (Show, Eq)++instance Extension ExtNameConstraints where+ extOID _ = [2, 5, 29, 30]+ extHasNestedASN1 _ = True+ extEncode = encodeNameConstraints+ extDecode = decodeNameConstraints++--------------------------------------------------------------------------------+-- Encoding+--------------------------------------------------------------------------------++encodeNameConstraints :: ExtNameConstraints -> [ASN1]+encodeNameConstraints (ExtNameConstraints permitted excluded) =+ [Start Sequence]+ ++ put 0 permitted+ ++ put 1 excluded+ ++ [End Sequence]+ where+ put _ [] = []+ put n xs =+ [Start (Container Context n), Start Sequence]+ ++ concatMap encodeGeneralSubtree xs+ ++ [End Sequence, End (Container Context n)]++encodeGeneralSubtree :: GeneralSubtree -> [ASN1]+encodeGeneralSubtree (GeneralSubtree base minimum' maximum') =+ [Start Sequence]+ ++ encodeAltName base+ ++ ( if minimum' /= 0+ then [Other Context 0 (encodeASN1' DER [IntVal minimum'])]+ else []+ )+ ++ maybe+ []+ ( \m ->+ [Other Context 1 (encodeASN1' DER [IntVal m])]+ )+ maximum'+ ++ [End Sequence]++--------------------------------------------------------------------------------+-- Decoding+--------------------------------------------------------------------------------++decodeNameConstraints :: [ASN1] -> Either String ExtNameConstraints+decodeNameConstraints+ (Start Sequence : xs) =+ go xs [] []+ where+ go (End Sequence : _) permitted excluded =+ Right $ ExtNameConstraints permitted excluded+ go (Start (Container Context 0) : rest) _ excluded = do+ (subs, rest') <- decodeSubtrees rest+ go rest' subs excluded+ go (Start (Container Context 1) : rest) permitted _ = do+ (subs, rest') <- decodeSubtrees rest+ go rest' permitted subs+ go _ _ _ = Left "Invalid NameConstraints"+ decodeSubtrees :: [ASN1] -> Either String ([GeneralSubtree], [ASN1])+ decodeSubtrees ys = loop ys []+ where+ loop (End (Container Context _) : rest) acc =+ Right (reverse acc, rest)+ loop zs acc = do+ (s, zs') <- decodeGeneralSubtree zs+ loop zs' (s : acc)+decodeNameConstraints _ =+ Left "NameConstraints: expected SEQUENCE"++decodeGeneralSubtree+ :: [ASN1]+ -> Either String (GeneralSubtree, [ASN1])+decodeGeneralSubtree (Start Sequence : xs) = do+ (base, rest1) <- runParseASN1State getAddr xs++ let (minimum', rest2) =+ case rest1 of+ (Other Context 0 bs : rs) ->+ case decodeASN1' DER bs of+ Right [IntVal n] -> (n, rs)+ _ -> (0, rest1)+ _ -> (0, rest1)++ let (maximum', rest3) =+ case rest2 of+ (Other Context 1 bs : rs) ->+ case decodeASN1' DER bs of+ Right [IntVal n] -> (Just n, rs)+ _ -> (Nothing, rest2)+ _ -> (Nothing, rest2)++ case rest3 of+ (End Sequence : rs) ->+ Right (GeneralSubtree base minimum' maximum', rs)+ _ ->+ Left "GeneralSubtree: missing end sequence"+decodeGeneralSubtree _ =+ Left "GeneralSubtree: invalid ASN1"++recognizedOIDs :: [OID]+recognizedOIDs =+ [ [2, 5, 29, 19] -- ExtBasicConstraints+ , [2, 5, 29, 15] -- ExtKeyUsage+ , [2, 5, 29, 37] -- ExtExtendedKeyUsage+ , [2, 5, 29, 14] -- ExtSubjectKeyId+ , [2, 5, 29, 17] -- ExtSubjectAltName+ , [2, 5, 29, 35] -- ExtAuthorityKeyId+ , [2, 5, 29, 31] -- ExtCrlDistributionPoints+ , [2, 5, 29, 30] -- ExtNameConstraints+ , [2, 16, 840, 1, 113730, 1, 13] -- ExtNetscapeComment+ ]
Data/X509/ExtensionRaw.hs view
@@ -6,33 +6,40 @@ -- Portability : unknown -- -- extension marshalling----module Data.X509.ExtensionRaw- ( ExtensionRaw(..)- , tryExtRawASN1- , extRawASN1- , Extensions(..)- ) where+module Data.X509.ExtensionRaw (+ ExtensionRaw (..),+ tryExtRawASN1,+ extRawASN1,+ Extensions (..),+) where -import Control.Applicative-import Data.ASN1.Types-import Data.ASN1.Encoding import Data.ASN1.BinaryEncoding-import Data.X509.Internal+import Data.ASN1.Encoding+import Data.ASN1.Types import qualified Data.ByteString as B+import Data.X509.Internal -- | An undecoded extension data ExtensionRaw = ExtensionRaw- { extRawOID :: OID -- ^ OID of this extension- , extRawCritical :: Bool -- ^ if this extension is critical- , extRawContent :: B.ByteString -- ^ undecoded content- } deriving (Show,Eq)+ { extRawOID :: OID+ -- ^ OID of this extension+ , extRawCritical :: Bool+ -- ^ if this extension is critical+ , extRawContent :: B.ByteString+ -- ^ undecoded content+ }+ deriving (Show, Eq) tryExtRawASN1 :: ExtensionRaw -> Either String [ASN1] tryExtRawASN1 (ExtensionRaw oid _ content) = case decodeASN1' BER content of- Left err -> Left $ "fromASN1: X509.ExtensionRaw: OID=" ++ show oid ++ ": cannot decode data: " ++ show err- Right r -> Right r+ Left err ->+ Left $+ "fromASN1: X509.ExtensionRaw: OID="+ ++ show oid+ ++ ": cannot decode data: "+ ++ show err+ Right r -> Right r extRawASN1 :: ExtensionRaw -> [ASN1] extRawASN1 extRaw = either error id $ tryExtRawASN1 extRaw@@ -40,25 +47,29 @@ -- | a Set of 'ExtensionRaw' newtype Extensions = Extensions (Maybe [ExtensionRaw])- deriving (Show,Eq)+ deriving (Show, Eq) instance ASN1Object Extensions where toASN1 (Extensions Nothing) = \xs -> xs toASN1 (Extensions (Just exts)) = \xs -> asn1Container Sequence (concatMap encodeExt exts) ++ xs fromASN1 s = runParseASN1State (Extensions <$> parseExtensions) s- where parseExtensions = onNextContainerMaybe Sequence (getMany getObject)+ where+ parseExtensions = onNextContainerMaybe Sequence (getMany getObject) instance ASN1Object ExtensionRaw where toASN1 extraw = \xs -> encodeExt extraw ++ xs- fromASN1 (Start Sequence:OID oid:xs) =+ fromASN1 (Start Sequence : OID oid : xs) = case xs of- Boolean b:OctetString obj:End Sequence:xs2 -> Right (ExtensionRaw oid b obj, xs2)- OctetString obj:End Sequence:xs2 -> Right (ExtensionRaw oid False obj, xs2)- _ -> Left ("fromASN1: X509.ExtensionRaw: unknown format:" ++ show xs)- fromASN1 l =+ Boolean b : OctetString obj : End Sequence : xs2 -> Right (ExtensionRaw oid b obj, xs2)+ OctetString obj : End Sequence : xs2 -> Right (ExtensionRaw oid False obj, xs2)+ _ ->+ Left ("fromASN1: X509.ExtensionRaw: unknown format:" ++ show xs)+ fromASN1 l = Left ("fromASN1: X509.ExtensionRaw: unknown format:" ++ show l) encodeExt :: ExtensionRaw -> [ASN1] encodeExt (ExtensionRaw oid critical content) =- asn1Container Sequence ([OID oid] ++ (if critical then [Boolean True] else []) ++ [OctetString content])+ asn1Container+ Sequence+ ([OID oid] ++ (if critical then [Boolean True] else []) ++ [OctetString content])
Data/X509/Internal.hs view
@@ -4,19 +4,19 @@ -- Maintainer : Vincent Hanquez <vincent@snarc.org> -- Stability : experimental -- Portability : unknown----module Data.X509.Internal- ( module Data.ASN1.Parse- , asn1Container- , OID+module Data.X509.Internal (+ module Data.ASN1.Parse,+ asn1Container,+ OID,+ -- * error handling- , ErrT- , runErrT- ) where+ ErrT,+ runErrT,+) where -import Data.ASN1.Types-import Data.ASN1.Parse import Control.Monad.Trans.Except+import Data.ASN1.Parse+import Data.ASN1.Types runErrT :: ExceptT e m a -> m (Either e a) runErrT = runExceptT
Data/X509/OID.hs view
@@ -4,20 +4,18 @@ -- Maintainer : Vincent Hanquez <vincent@snarc.org> -- Stability : experimental -- Portability : unknown----module Data.X509.OID- ( OIDTable- , lookupByOID- , lookupOID- , curvesOIDTable- ) where+module Data.X509.OID (+ OIDTable,+ lookupByOID,+ lookupOID,+ curvesOIDTable,+) where -import Control.Applicative import Crypto.PubKey.ECC.Types import Data.ASN1.OID import Data.List (find) -type OIDTable a = [(a,OID)]+type OIDTable a = [(a, OID)] lookupByOID :: OIDTable a -> OID -> Maybe a lookupByOID table oid = fst <$> find ((==) oid . snd) table@@ -27,37 +25,37 @@ curvesOIDTable :: OIDTable CurveName curvesOIDTable =- [ (SEC_p112r1, [1,3,132,0,6])- , (SEC_p112r2, [1,3,132,0,7])- , (SEC_p128r1, [1,3,132,0,28])- , (SEC_p128r2, [1,3,132,0,29])- , (SEC_p160k1, [1,3,132,0,9])- , (SEC_p160r1, [1,3,132,0,8])- , (SEC_p160r2, [1,3,132,0,30])- , (SEC_p192k1, [1,3,132,0,31])- , (SEC_p192r1, [1,2,840,10045,3,1,1])- , (SEC_p224k1, [1,3,132,0,32])- , (SEC_p224r1, [1,3,132,0,33])- , (SEC_p256k1, [1,3,132,0,10])- , (SEC_p256r1, [1,2,840,10045,3,1,7])- , (SEC_p384r1, [1,3,132,0,34])- , (SEC_p521r1, [1,3,132,0,35])- , (SEC_t113r1, [1,3,132,0,4])- , (SEC_t113r2, [1,3,132,0,5])- , (SEC_t131r1, [1,3,132,0,22])- , (SEC_t131r2, [1,3,132,0,23])- , (SEC_t163k1, [1,3,132,0,1])- , (SEC_t163r1, [1,3,132,0,2])- , (SEC_t163r2, [1,3,132,0,15])- , (SEC_t193r1, [1,3,132,0,24])- , (SEC_t193r2, [1,3,132,0,25])- , (SEC_t233k1, [1,3,132,0,26])- , (SEC_t233r1, [1,3,132,0,27])- , (SEC_t239k1, [1,3,132,0,3])- , (SEC_t283k1, [1,3,132,0,16])- , (SEC_t283r1, [1,3,132,0,17])- , (SEC_t409k1, [1,3,132,0,36])- , (SEC_t409r1, [1,3,132,0,37])- , (SEC_t571k1, [1,3,132,0,38])- , (SEC_t571r1, [1,3,132,0,39])+ [ (SEC_p112r1, [1, 3, 132, 0, 6])+ , (SEC_p112r2, [1, 3, 132, 0, 7])+ , (SEC_p128r1, [1, 3, 132, 0, 28])+ , (SEC_p128r2, [1, 3, 132, 0, 29])+ , (SEC_p160k1, [1, 3, 132, 0, 9])+ , (SEC_p160r1, [1, 3, 132, 0, 8])+ , (SEC_p160r2, [1, 3, 132, 0, 30])+ , (SEC_p192k1, [1, 3, 132, 0, 31])+ , (SEC_p192r1, [1, 2, 840, 10045, 3, 1, 1])+ , (SEC_p224k1, [1, 3, 132, 0, 32])+ , (SEC_p224r1, [1, 3, 132, 0, 33])+ , (SEC_p256k1, [1, 3, 132, 0, 10])+ , (SEC_p256r1, [1, 2, 840, 10045, 3, 1, 7])+ , (SEC_p384r1, [1, 3, 132, 0, 34])+ , (SEC_p521r1, [1, 3, 132, 0, 35])+ , (SEC_t113r1, [1, 3, 132, 0, 4])+ , (SEC_t113r2, [1, 3, 132, 0, 5])+ , (SEC_t131r1, [1, 3, 132, 0, 22])+ , (SEC_t131r2, [1, 3, 132, 0, 23])+ , (SEC_t163k1, [1, 3, 132, 0, 1])+ , (SEC_t163r1, [1, 3, 132, 0, 2])+ , (SEC_t163r2, [1, 3, 132, 0, 15])+ , (SEC_t193r1, [1, 3, 132, 0, 24])+ , (SEC_t193r2, [1, 3, 132, 0, 25])+ , (SEC_t233k1, [1, 3, 132, 0, 26])+ , (SEC_t233r1, [1, 3, 132, 0, 27])+ , (SEC_t239k1, [1, 3, 132, 0, 3])+ , (SEC_t283k1, [1, 3, 132, 0, 16])+ , (SEC_t283r1, [1, 3, 132, 0, 17])+ , (SEC_t409k1, [1, 3, 132, 0, 36])+ , (SEC_t409r1, [1, 3, 132, 0, 37])+ , (SEC_t571k1, [1, 3, 132, 0, 38])+ , (SEC_t571r1, [1, 3, 132, 0, 39]) ]
Data/X509/PrivateKey.hs view
@@ -6,113 +6,324 @@ -- Portability : unknown -- -- Private key handling in X.509 infrastructure----module Data.X509.PrivateKey- ( PrivKey(..)- , PrivKeyEC(..)- , privkeyToAlg- ) where+module Data.X509.PrivateKey (+ PrivKey (..),+ PrivKeyEC (..),+ PrivKeyMLDSA,+ MLDSAKeyForm (..),+ privkeyMLDSAFromSeed,+ privkeyMLDSAFromKey,+ privkeyMLDSA_key,+ privkeyMLDSA_seed,+ privkeyMLDSA_form,+ privkeyToAlg,+) where -import Control.Applicative ((<$>), pure) import Data.Maybe (fromMaybe)-import Data.Word (Word)+import Data.Proxy (Proxy (..)) -import Data.ByteArray (ByteArrayAccess, convert)+import Data.ByteArray (ByteArrayAccess, ScrubbedBytes, convert) import qualified Data.ByteString as B -import Data.ASN1.Types-import Data.ASN1.Encoding import Data.ASN1.BinaryEncoding import Data.ASN1.BitArray+import Data.ASN1.Encoding import Data.ASN1.Stream (getConstructedEnd)+import Data.ASN1.Types import Data.X509.AlgorithmIdentifier-import Data.X509.PublicKey (SerializedPoint(..))-import Data.X509.OID (lookupByOID, lookupOID, curvesOIDTable)+import Data.X509.OID (curvesOIDTable, lookupByOID, lookupOID)+import Data.X509.PublicKey (SerializedPoint (..)) -import Crypto.Error (CryptoFailable(..))+import Crypto.Debug (DebugShow (..), debugShowBytes)+import Crypto.Error (CryptoFailable (..)) import Crypto.Number.Serialize (i2osp, os2ip)-import qualified Crypto.PubKey.RSA as RSA+import qualified Crypto.PubKey.Curve25519 as X25519+import qualified Crypto.PubKey.Curve448 as X448 import qualified Crypto.PubKey.DSA as DSA import qualified Crypto.PubKey.ECC.Types as ECC-import qualified Crypto.PubKey.Curve25519 as X25519-import qualified Crypto.PubKey.Curve448 as X448-import qualified Crypto.PubKey.Ed25519 as Ed25519-import qualified Crypto.PubKey.Ed448 as Ed448+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Crypto.PubKey.Ed448 as Ed448+import qualified Crypto.PubKey.MLDSA as MLDSA+import qualified Crypto.PubKey.RSA as RSA -- | Elliptic Curve Private Key -- -- TODO: missing support for binary curve.-data PrivKeyEC =- PrivKeyEC_Prime- { privkeyEC_priv :: Integer- , privkeyEC_a :: Integer- , privkeyEC_b :: Integer- , privkeyEC_prime :: Integer+data PrivKeyEC+ = PrivKeyEC_Prime+ { privkeyEC_priv :: Integer+ , privkeyEC_a :: Integer+ , privkeyEC_b :: Integer+ , privkeyEC_prime :: Integer , privkeyEC_generator :: SerializedPoint- , privkeyEC_order :: Integer- , privkeyEC_cofactor :: Integer- , privkeyEC_seed :: Integer+ , privkeyEC_order :: Integer+ , privkeyEC_cofactor :: Integer+ , privkeyEC_seed :: Integer } | PrivKeyEC_Named- { privkeyEC_name :: ECC.CurveName- , privkeyEC_priv :: Integer+ { privkeyEC_name :: ECC.CurveName+ , privkeyEC_priv :: Integer }- deriving (Show,Eq)+ deriving (Eq) +-- | The curve is shown; @privkeyEC_priv@ is not. Use+-- 'Crypto.Debug.debugShow' to see it.+instance Show PrivKeyEC where+ showsPrec = showsPrivKeyEC (showString "<secret>")++instance DebugShow PrivKeyEC where+ debugShow k = showsPrivKeyEC (shows $ privkeyEC_priv k) 0 k ""++-- | What the two instances above share, so that a field added to+-- 'PrivKeyEC' cannot reach one of them and not the other. The first+-- argument renders @privkeyEC_priv@; everything else is what @deriving+-- Show@ used to write.+showsPrivKeyEC :: ShowS -> Int -> PrivKeyEC -> ShowS+showsPrivKeyEC priv d (PrivKeyEC_Prime _ a b p g o c s) =+ showParen (d > 10) $+ showString "PrivKeyEC_Prime {privkeyEC_priv = "+ . priv+ . showString ", privkeyEC_a = "+ . shows a+ . showString ", privkeyEC_b = "+ . shows b+ . showString ", privkeyEC_prime = "+ . shows p+ . showString ", privkeyEC_generator = "+ . shows g+ . showString ", privkeyEC_order = "+ . shows o+ . showString ", privkeyEC_cofactor = "+ . shows c+ . showString ", privkeyEC_seed = "+ . shows s+ . showChar '}'+showsPrivKeyEC priv d (PrivKeyEC_Named n _) =+ showParen (d > 10) $+ showString "PrivKeyEC_Named {privkeyEC_name = "+ . shows n+ . showString ", privkeyEC_priv = "+ . priv+ . showChar '}'++-- | Which of the three forms of RFC 9881 Section 6 a private key was read+-- as, and is written back out as.+data MLDSAKeyForm+ = -- | The seed alone, @seed@, which Section 6 recommends.+ MLDSAKeySeed+ | -- | The expanded key alone, @expandedKey@.+ MLDSAKeyExpanded+ | -- | Both together, @both@.+ MLDSAKeyBoth+ deriving (Show, Eq)++-- | An ML-DSA private key.+--+-- RFC 9881 Section 6 writes one as the seed it was derived from, as the+-- expanded key, or as both, and which of the three it was is part of the+-- key here. A key read from a file is therefore written back out as the+-- same thing it came in as, rather than as whichever form this module+-- happens to prefer.+--+-- The expanded key is there whatever the form: a seed is expanded as it is+-- read, so 'privkeyMLDSA_key' always answers. The seed is not, since a+-- key written as @expandedKey@ does not carry one; 'privkeyMLDSA_seed' is+-- a 'Maybe' for that reason, and a key whose seed was never on the wire+-- cannot be given one back -- @crypton@'s+-- @Crypto.PubKey.MLDSA.generateKeyPairAndSeed@ is where a seed that is to+-- be kept has to be caught.+--+-- The constructors are not exported: a seed and an expanded key that do+-- not belong together would be a key that signs with one and claims the+-- other, so the only ways in are 'privkeyMLDSAFromSeed', which expands the+-- seed itself, and 'privkeyMLDSAFromKey', which has no seed to disagree+-- with.+--+-- Two keys are equal when they hold the same thing /in the same form/: the+-- same key as a seed and as an expanded key are two different files, and+-- the encoding round trip says so.+data PrivKeyMLDSA p+ = PrivKeyMLDSA_Seed ScrubbedBytes (MLDSA.SigningKey p)+ | PrivKeyMLDSA_Expanded (MLDSA.SigningKey p)+ | PrivKeyMLDSA_Both ScrubbedBytes (MLDSA.SigningKey p)+ deriving (Eq)++-- | What the two instances below share, so that a form added to+-- 'PrivKeyMLDSA' cannot reach one of them and not the other. The+-- arguments render the seed and the key.+showsPrivKeyMLDSA+ :: (ScrubbedBytes -> String)+ -> (MLDSA.SigningKey p -> String)+ -> PrivKeyMLDSA p+ -> String+showsPrivKeyMLDSA seed key k = case k of+ PrivKeyMLDSA_Seed s sk -> con "PrivKeyMLDSA_Seed" [seed s, key sk]+ PrivKeyMLDSA_Expanded sk -> con "PrivKeyMLDSA_Expanded" [key sk]+ PrivKeyMLDSA_Both s sk -> con "PrivKeyMLDSA_Both" [seed s, key sk]+ where+ con name as = unwords (name : map (\a -> "(" ++ a ++ ")") as)++-- | The form is shown; neither the seed nor the key is. Use+-- 'Crypto.Debug.debugShow' to see them.+instance Show (PrivKeyMLDSA p) where+ show = showsPrivKeyMLDSA (const "<secret>") show++instance DebugShow (PrivKeyMLDSA p) where+ debugShow = showsPrivKeyMLDSA (debugShowBytes "seed") debugShow++-- | Build a key from its seed, to be written back out in the given form.+--+-- The expanded key is derived here, so it cannot fail to be the one the+-- seed belongs to. Passing 'MLDSAKeyExpanded' throws the seed away, which+-- is the way to turn a seed into a key that will be written without one.+privkeyMLDSAFromSeed+ :: (MLDSA.MLDSA p, ByteArrayAccess ba)+ => proxy p -> MLDSAKeyForm -> ba -> CryptoFailable (PrivKeyMLDSA p)+privkeyMLDSAFromSeed p form bs = case MLDSA.keyPairFromSeed p bs of+ CryptoFailed e -> CryptoFailed e+ CryptoPassed (_, sk) ->+ CryptoPassed $ case form of+ MLDSAKeySeed -> PrivKeyMLDSA_Seed seed sk+ MLDSAKeyExpanded -> PrivKeyMLDSA_Expanded sk+ MLDSAKeyBoth -> PrivKeyMLDSA_Both seed sk+ where+ seed = convert bs :: ScrubbedBytes++-- | Build a key from the expanded key alone, which is what a key that was+-- generated without keeping its seed has to be.+privkeyMLDSAFromKey :: MLDSA.SigningKey p -> PrivKeyMLDSA p+privkeyMLDSAFromKey = PrivKeyMLDSA_Expanded++-- | The expanded key, whatever form the key is in.+privkeyMLDSA_key :: PrivKeyMLDSA p -> MLDSA.SigningKey p+privkeyMLDSA_key (PrivKeyMLDSA_Seed _ sk) = sk+privkeyMLDSA_key (PrivKeyMLDSA_Expanded sk) = sk+privkeyMLDSA_key (PrivKeyMLDSA_Both _ sk) = sk++-- | The seed, for a key that has one.+privkeyMLDSA_seed :: PrivKeyMLDSA p -> Maybe ScrubbedBytes+privkeyMLDSA_seed (PrivKeyMLDSA_Seed s _) = Just s+privkeyMLDSA_seed (PrivKeyMLDSA_Expanded _) = Nothing+privkeyMLDSA_seed (PrivKeyMLDSA_Both s _) = Just s++-- | The form the key will be written out in.+privkeyMLDSA_form :: PrivKeyMLDSA p -> MLDSAKeyForm+privkeyMLDSA_form (PrivKeyMLDSA_Seed _ _) = MLDSAKeySeed+privkeyMLDSA_form (PrivKeyMLDSA_Expanded _) = MLDSAKeyExpanded+privkeyMLDSA_form (PrivKeyMLDSA_Both _ _) = MLDSAKeyBoth+ -- | Private key types known and used in X.509-data PrivKey =- PrivKeyRSA RSA.PrivateKey -- ^ RSA private key- | PrivKeyDSA DSA.PrivateKey -- ^ DSA private key- | PrivKeyEC PrivKeyEC -- ^ EC private key- | PrivKeyX25519 X25519.SecretKey -- ^ X25519 private key- | PrivKeyX448 X448.SecretKey -- ^ X448 private key- | PrivKeyEd25519 Ed25519.SecretKey -- ^ Ed25519 private key- | PrivKeyEd448 Ed448.SecretKey -- ^ Ed448 private key- deriving (Show,Eq)+data PrivKey+ = -- | RSA private key+ PrivKeyRSA RSA.PrivateKey+ | -- | DSA private key+ PrivKeyDSA DSA.PrivateKey+ | -- | EC private key+ PrivKeyEC PrivKeyEC+ | -- | X25519 private key+ PrivKeyX25519 X25519.SecretKey+ | -- | X448 private key+ PrivKeyX448 X448.SecretKey+ | -- | Ed25519 private key+ PrivKeyEd25519 Ed25519.SecretKey+ | -- | Ed448 private key+ PrivKeyEd448 Ed448.SecretKey+ | -- | ML-DSA-44 private key+ PrivKeyMLDSA44 (PrivKeyMLDSA MLDSA.MLDSA44)+ | -- | ML-DSA-65 private key+ PrivKeyMLDSA65 (PrivKeyMLDSA MLDSA.MLDSA65)+ | -- | ML-DSA-87 private key+ PrivKeyMLDSA87 (PrivKeyMLDSA MLDSA.MLDSA87)+ deriving (Show, Eq) +-- | Rendering a private key with the key material in it, for the times when+-- that is what is meant. Each arm is @crypton@'s own 'debugShow' for that+-- key type, except the EC one, which is this module's.+instance DebugShow PrivKey where+ debugShow k = case k of+ PrivKeyRSA p -> con "PrivKeyRSA" $ debugShow p+ PrivKeyDSA p -> con "PrivKeyDSA" $ debugShow p+ PrivKeyEC p -> con "PrivKeyEC" $ debugShow p+ PrivKeyX25519 p -> con "PrivKeyX25519" $ debugShow p+ PrivKeyX448 p -> con "PrivKeyX448" $ debugShow p+ PrivKeyEd25519 p -> con "PrivKeyEd25519" $ debugShow p+ PrivKeyEd448 p -> con "PrivKeyEd448" $ debugShow p+ PrivKeyMLDSA44 p -> con "PrivKeyMLDSA44" $ debugShow p+ PrivKeyMLDSA65 p -> con "PrivKeyMLDSA65" $ debugShow p+ PrivKeyMLDSA87 p -> con "PrivKeyMLDSA87" $ debugShow p+ where+ con name body = name ++ " (" ++ body ++ ")"+ instance ASN1Object PrivKey where fromASN1 = privkeyFromASN1 toASN1 = privkeyToASN1 privkeyFromASN1 :: [ASN1] -> Either String (PrivKey, [ASN1]) privkeyFromASN1 asn1 =- (mapFst PrivKeyRSA <$> rsaFromASN1 asn1) <!>- (mapFst PrivKeyDSA <$> dsaFromASN1 asn1) <!>- (mapFst PrivKeyEC <$> ecdsaFromASN1 asn1) <!>- newcurveFromASN1 asn1+ (mapFst PrivKeyRSA <$> rsaFromASN1 asn1)+ <!> (mapFst PrivKeyDSA <$> dsaFromASN1 asn1)+ <!> (mapFst PrivKeyEC <$> ecdsaFromASN1 asn1)+ <!> newcurveFromASN1 asn1 where mapFst f (a, b) = (f a, b) Left _ <!> b = b- a <!> _ = a+ a <!> _ = a rsaFromASN1 :: [ASN1] -> Either String (RSA.PrivateKey, [ASN1])-rsaFromASN1 (Start Sequence : IntVal 0 : IntVal n : IntVal e : IntVal d- : IntVal p : IntVal q : IntVal dP : IntVal dQ : IntVal qinv- : End Sequence : as) = pure (key, as)- where- key = RSA.PrivateKey (RSA.PublicKey (go n 1) n e) d p q dP dQ qinv- go m i- | 2 ^ (i * 8) > m = i- | otherwise = go m (i + 1)-rsaFromASN1 (Start Sequence : IntVal 0 : Start Sequence- : OID [1, 2, 840, 113549, 1, 1, 1] : Null : End Sequence- : OctetString bytes : End Sequence : as) = do+rsaFromASN1+ ( Start Sequence : IntVal 0 : IntVal n : IntVal e : IntVal d+ : IntVal p+ : IntVal q+ : IntVal dP+ : IntVal dQ+ : IntVal qinv+ : End Sequence+ : as+ ) = pure (key, as)+ where+ key = RSA.PrivateKey (RSA.PublicKey (go n 1) n e) d p q dP dQ qinv+ go m i+ | 2 ^ (i * 8) > m = i+ | otherwise = go m (i + 1)+rsaFromASN1+ ( Start Sequence : IntVal 0 : Start Sequence+ : OID [1, 2, 840, 113549, 1, 1, 1]+ : Null+ : End Sequence+ : OctetString bytes+ : End Sequence+ : as+ ) = do asn1 <- mapLeft failure (decodeASN1' BER bytes) fmap (const as) <$> rsaFromASN1 asn1- where- failure = ("rsaFromASN1: " ++) . show+ where+ failure = ("rsaFromASN1: " ++) . show rsaFromASN1 _ = Left "rsaFromASN1: unexpected format" dsaFromASN1 :: [ASN1] -> Either String (DSA.PrivateKey, [ASN1])-dsaFromASN1 (Start Sequence : IntVal 0 : IntVal p : IntVal q : IntVal g- : IntVal _ : IntVal x : End Sequence : as) =+dsaFromASN1+ ( Start Sequence : IntVal 0 : IntVal p : IntVal q : IntVal g+ : IntVal _+ : IntVal x+ : End Sequence+ : as+ ) = pure (DSA.PrivateKey (DSA.Params p g q) x, as)-dsaFromASN1 (Start Sequence : IntVal 0 : Start Sequence- : OID [1, 2, 840, 10040, 4, 1] : Start Sequence : IntVal p : IntVal q- : IntVal g : End Sequence : End Sequence : OctetString bytes- : End Sequence : as) = case decodeASN1' BER bytes of+dsaFromASN1+ ( Start Sequence : IntVal 0 : Start Sequence+ : OID [1, 2, 840, 10040, 4, 1]+ : Start Sequence+ : IntVal p+ : IntVal q+ : IntVal g+ : End Sequence+ : End Sequence+ : OctetString bytes+ : End Sequence+ : as+ ) = case decodeASN1' BER bytes of Right [IntVal x] -> pure (DSA.PrivateKey (DSA.Params p g q) x, as) Right _ -> Left "DSA.PrivateKey.fromASN1: unexpected format" Left e -> Left $ "DSA.PrivateKey.fromASN1: " ++ show e@@ -137,28 +348,43 @@ failure = Left $ failing $ "unknown curve " ++ show oid_ success = Right . flip PrivKeyEC_Named d mcurve = lookupByOID curvesOIDTable oid_- subgo (Start Sequence : IntVal 1 : Start Sequence- : OID [1, 2, 840, 10045, 1, 1] : IntVal p : End Sequence- : Start Sequence : OctetString a : OctetString b : BitString s- : End Sequence : OctetString g : IntVal o : IntVal c- : End Sequence : _) =+ subgo+ ( Start Sequence : IntVal 1 : Start Sequence+ : OID [1, 2, 840, 10045, 1, 1]+ : IntVal p+ : End Sequence+ : Start Sequence+ : OctetString a+ : OctetString b+ : BitString s+ : End Sequence+ : OctetString g+ : IntVal o+ : IntVal c+ : End Sequence+ : _+ ) = pure $ PrivKeyEC_Prime d a' b' p g' o c s'- where- a' = os2ip a- b' = os2ip b- g' = SerializedPoint g- s' = os2ip $ bitArrayGetData s+ where+ a' = os2ip a+ b' = os2ip b+ g' = SerializedPoint g+ s' = os2ip $ bitArrayGetData s subgo (Null : rest_) = subgo rest_ subgo [] = Left $ failing "curve is missing" subgo _ = Left $ failing "unexpected curve format"- go acc (Start Sequence : IntVal 0 : Start Sequence- : OID [1, 2, 840, 10045, 2, 1] : rest) = case rest' of+ go+ acc+ ( Start Sequence : IntVal 0 : Start Sequence+ : OID [1, 2, 840, 10045, 2, 1]+ : rest+ ) = case rest' of (OctetString bytes : rest'') -> do asn1 <- mapLeft (failing . show) (decodeASN1' BER bytes) fmap (const rest'') <$> go (oid ++ acc) asn1 _ -> Left $ failing "unexpected EC format"- where- (oid, rest') = spanEnd 0 rest+ where+ (oid, rest') = spanEnd 0 rest go _ _ = Left $ failing "unexpected EC format" spanEnd :: Word -> [ASN1] -> ([ASN1], [ASN1])@@ -175,93 +401,189 @@ spanTag _ as = ([], as) newcurveFromASN1 :: [ASN1] -> Either String (PrivKey, [ASN1])-newcurveFromASN1 ( Start Sequence- : IntVal v- : Start Sequence- : OID oid- : End Sequence- : OctetString bs- : xs)- | isValidVersion v = do- let (_, ys) = containerWithTag 0 xs- case primitiveWithTag 1 ys of- (_, End Sequence : zs) ->- case getP oid of- Just (name, parse) -> do- let err s = Left (name ++ ".SecretKey.fromASN1: " ++ s)- case decodeASN1' BER bs of- Right [OctetString key] ->- case parse key of- CryptoPassed s -> Right (s, zs)- CryptoFailed e -> err ("invalid secret key: " ++ show e)- Right _ -> err "unexpected inner format"- Left e -> err (show e)- Nothing -> Left ("newcurveFromASN1: unexpected OID " ++ show oid)- _ -> Left "newcurveFromASN1: unexpected end format"- | otherwise = Left ("newcurveFromASN1: unexpected version: " ++ show v)- where- getP [1,3,101,110] = Just ("X25519", fmap PrivKeyX25519 . X25519.secretKey)- getP [1,3,101,111] = Just ("X448", fmap PrivKeyX448 . X448.secretKey)- getP [1,3,101,112] = Just ("Ed25519", fmap PrivKeyEd25519 . Ed25519.secretKey)- getP [1,3,101,113] = Just ("Ed448", fmap PrivKeyEd448 . Ed448.secretKey)- getP _ = Nothing- isValidVersion version = version >= 0 && version <= 1+newcurveFromASN1+ ( Start Sequence+ : IntVal v+ : Start Sequence+ : OID oid+ : End Sequence+ : OctetString bs+ : xs+ )+ | isValidVersion v = do+ let (_, ys) = containerWithTag 0 xs+ case primitiveWithTag 1 ys of+ (_, End Sequence : zs) ->+ case getP oid of+ Just (name, parse) -> do+ let err s = Left (name ++ ".SecretKey.fromASN1: " ++ s)+ case decodeASN1' BER bs of+ Right [OctetString key] ->+ case parse key of+ CryptoPassed s -> Right (s, zs)+ CryptoFailed e -> err ("invalid secret key: " ++ show e)+ Right _ -> err "unexpected inner format"+ Left e -> err (show e)+ Nothing -> case getMLDSA oid of+ Just (name, parse) ->+ case decodeASN1' BER bs of+ Right inner -> (\k -> (k, zs)) <$> parse inner+ Left e -> Left (name ++ ".SigningKey.fromASN1: " ++ show e)+ Nothing -> Left ("newcurveFromASN1: unexpected OID " ++ show oid)+ _ -> Left "newcurveFromASN1: unexpected end format"+ | otherwise = Left ("newcurveFromASN1: unexpected version: " ++ show v)+ where+ getP [1, 3, 101, 110] = Just ("X25519", fmap PrivKeyX25519 . X25519.secretKey)+ getP [1, 3, 101, 111] = Just ("X448", fmap PrivKeyX448 . X448.secretKey)+ getP [1, 3, 101, 112] = Just ("Ed25519", fmap PrivKeyEd25519 . Ed25519.secretKey)+ getP [1, 3, 101, 113] = Just ("Ed448", fmap PrivKeyEd448 . Ed448.secretKey)+ getP _ = Nothing+ getMLDSA [2, 16, 840, 1, 101, 3, 4, 3, 17] =+ Just+ ( "ML-DSA-44"+ , mldsaFromASN1 (Proxy :: Proxy MLDSA.MLDSA44) PrivKeyMLDSA44 "ML-DSA-44"+ )+ getMLDSA [2, 16, 840, 1, 101, 3, 4, 3, 18] =+ Just+ ( "ML-DSA-65"+ , mldsaFromASN1 (Proxy :: Proxy MLDSA.MLDSA65) PrivKeyMLDSA65 "ML-DSA-65"+ )+ getMLDSA [2, 16, 840, 1, 101, 3, 4, 3, 19] =+ Just+ ( "ML-DSA-87"+ , mldsaFromASN1 (Proxy :: Proxy MLDSA.MLDSA87) PrivKeyMLDSA87 "ML-DSA-87"+ )+ getMLDSA _ = Nothing+ isValidVersion version = version >= 0 && version <= 1 newcurveFromASN1 _ = Left "newcurveFromASN1: unexpected format" +-- | The private key of RFC 9881 Section 6: a CHOICE of the seed, tagged+-- [0], the expanded key, or both. A seed is expanded into the key; with+-- both, the expanded key must be what the seed expands to. Which of the+-- three it was is kept, so that 'mldsaToASN1' writes back what was read.+mldsaFromASN1+ :: MLDSA.MLDSA p+ => proxy p+ -> (PrivKeyMLDSA p -> PrivKey)+ -> String+ -> [ASN1]+ -> Either String PrivKey+mldsaFromASN1 p con name inner = case inner of+ [Other Context 0 seed] -> con <$> fromSeed MLDSAKeySeed seed+ [OctetString expanded] -> con . privkeyMLDSAFromKey <$> fromExpanded expanded+ [Start Sequence, OctetString seed, OctetString expanded, End Sequence] -> do+ k <- fromSeed MLDSAKeyBoth seed+ sk <- fromExpanded expanded+ if privkeyMLDSA_key k == sk+ then Right (con k)+ else err "seed and expandedKey do not match"+ _ -> err "unexpected inner format"+ where+ err s = Left (name ++ ".SigningKey.fromASN1: " ++ s)+ fromSeed form seed = case privkeyMLDSAFromSeed p form seed of+ CryptoPassed k -> Right k+ CryptoFailed e -> err ("invalid seed: " ++ show e)+ fromExpanded expanded = case MLDSA.signingKey expanded of+ CryptoPassed sk -> Right sk+ CryptoFailed e -> err ("invalid expandedKey: " ++ show e)+ containerWithTag :: ASN1Tag -> [ASN1] -> ([ASN1], [ASN1]) containerWithTag etag (Start (Container _ atag) : xs) | etag == atag = getConstructedEnd 0 xs-containerWithTag _ xs = ([], xs)+containerWithTag _ xs = ([], xs) primitiveWithTag :: ASN1Tag -> [ASN1] -> (Maybe B.ByteString, [ASN1]) primitiveWithTag etag (Other _ atag bs : xs) | etag == atag = (Just bs, xs)-primitiveWithTag _ xs = (Nothing, xs)+primitiveWithTag _ xs = (Nothing, xs) privkeyToASN1 :: PrivKey -> ASN1S privkeyToASN1 (PrivKeyRSA rsa) = rsaToASN1 rsa privkeyToASN1 (PrivKeyDSA dsa) = dsaToASN1 dsa privkeyToASN1 (PrivKeyEC ecdsa) = ecdsaToASN1 ecdsa-privkeyToASN1 (PrivKeyX25519 k) = newcurveToASN1 [1,3,101,110] k-privkeyToASN1 (PrivKeyX448 k) = newcurveToASN1 [1,3,101,111] k-privkeyToASN1 (PrivKeyEd25519 k) = newcurveToASN1 [1,3,101,112] k-privkeyToASN1 (PrivKeyEd448 k) = newcurveToASN1 [1,3,101,113] k+privkeyToASN1 (PrivKeyX25519 k) = newcurveToASN1 [1, 3, 101, 110] k+privkeyToASN1 (PrivKeyX448 k) = newcurveToASN1 [1, 3, 101, 111] k+privkeyToASN1 (PrivKeyEd25519 k) = newcurveToASN1 [1, 3, 101, 112] k+privkeyToASN1 (PrivKeyEd448 k) = newcurveToASN1 [1, 3, 101, 113] k+privkeyToASN1 (PrivKeyMLDSA44 k) = mldsaToASN1 (getObjectID PubKeyALG_MLDSA44) k+privkeyToASN1 (PrivKeyMLDSA65 k) = mldsaToASN1 (getObjectID PubKeyALG_MLDSA65) k+privkeyToASN1 (PrivKeyMLDSA87 k) = mldsaToASN1 (getObjectID PubKeyALG_MLDSA87) k rsaToASN1 :: RSA.PrivateKey -> ASN1S-rsaToASN1 key = (++)- [ Start Sequence, IntVal 0, IntVal n, IntVal e, IntVal d, IntVal p- , IntVal q, IntVal dP, IntVal dQ, IntVal qinv, End Sequence- ]+rsaToASN1 key =+ (++)+ [ Start Sequence+ , IntVal 0+ , IntVal n+ , IntVal e+ , IntVal d+ , IntVal p+ , IntVal q+ , IntVal dP+ , IntVal dQ+ , IntVal qinv+ , End Sequence+ ] where RSA.PrivateKey (RSA.PublicKey _ n e) d p q dP dQ qinv = key dsaToASN1 :: DSA.PrivateKey -> ASN1S-dsaToASN1 (DSA.PrivateKey params@(DSA.Params p g q) y) = (++)- [ Start Sequence, IntVal 0, IntVal p, IntVal q, IntVal g, IntVal x- , IntVal y, End Sequence- ]+dsaToASN1 (DSA.PrivateKey params@(DSA.Params p g q) y) =+ (++)+ [ Start Sequence+ , IntVal 0+ , IntVal p+ , IntVal q+ , IntVal g+ , IntVal x+ , IntVal y+ , End Sequence+ ] where x = DSA.calculatePublic params y ecdsaToASN1 :: PrivKeyEC -> ASN1S-ecdsaToASN1 (PrivKeyEC_Named curveName d) = (++)- [ Start Sequence, IntVal 1, OctetString (i2osp d)- , Start (Container Context 0), OID oid, End (Container Context 0)- , End Sequence- ]+ecdsaToASN1 (PrivKeyEC_Named curveName d) =+ (++)+ [ Start Sequence+ , IntVal 1+ , OctetString (i2osp d)+ , Start (Container Context 0)+ , OID oid+ , End (Container Context 0)+ , End Sequence+ ] where err = error . ("ECDSA.PrivateKey.toASN1: " ++)- oid = fromMaybe (err $ "missing named curve " ++ show curveName)- (lookupOID curvesOIDTable curveName)-ecdsaToASN1 (PrivKeyEC_Prime d a b p g o c s) = (++)- [ Start Sequence, IntVal 1, OctetString (i2osp d)- , Start (Container Context 0), Start Sequence, IntVal 1- , Start Sequence, OID [1, 2, 840, 10045, 1, 1], IntVal p, End Sequence- , Start Sequence, OctetString a', OctetString b', BitString s'- , End Sequence, OctetString g' , IntVal o, IntVal c, End Sequence- , End (Container Context 0), End Sequence- ]+ oid =+ fromMaybe+ (err $ "missing named curve " ++ show curveName)+ (lookupOID curvesOIDTable curveName)+ecdsaToASN1 (PrivKeyEC_Prime d a b p g o c s) =+ (++)+ [ Start Sequence+ , IntVal 1+ , OctetString (i2osp d)+ , Start (Container Context 0)+ , Start Sequence+ , IntVal 1+ , Start Sequence+ , OID [1, 2, 840, 10045, 1, 1]+ , IntVal p+ , End Sequence+ , Start Sequence+ , OctetString a'+ , OctetString b'+ , BitString s'+ , End Sequence+ , OctetString g'+ , IntVal o+ , IntVal c+ , End Sequence+ , End (Container Context 0)+ , End Sequence+ ] where a' = i2osp a b' = i2osp b@@ -271,22 +593,55 @@ bytes = i2osp s newcurveToASN1 :: ByteArrayAccess key => OID -> key -> ASN1S-newcurveToASN1 oid key = (++)- [ Start Sequence, IntVal 0, Start Sequence, OID oid, End Sequence- , OctetString (encodeASN1' DER [OctetString $ convert key])- , End Sequence- ]+newcurveToASN1 oid key =+ (++)+ [ Start Sequence+ , IntVal 0+ , Start Sequence+ , OID oid+ , End Sequence+ , OctetString (encodeASN1' DER [OctetString $ convert key])+ , End Sequence+ ] +-- | The inverse of 'mldsaFromASN1': the form the key is in is the form it+-- is written in, so reading a key and writing it again gives back the+-- bytes it came from.+mldsaToASN1 :: OID -> PrivKeyMLDSA p -> ASN1S+mldsaToASN1 oid k =+ (++)+ [ Start Sequence+ , IntVal 0+ , Start Sequence+ , OID oid+ , End Sequence+ , OctetString (encodeASN1' DER inner)+ , End Sequence+ ]+ where+ inner = case k of+ PrivKeyMLDSA_Seed s _ -> [Other Context 0 (convert s)]+ PrivKeyMLDSA_Expanded sk -> [OctetString (convert sk)]+ PrivKeyMLDSA_Both s sk ->+ [ Start Sequence+ , OctetString (convert s)+ , OctetString (convert sk)+ , End Sequence+ ]+ mapLeft :: (a0 -> a1) -> Either a0 b -> Either a1 b mapLeft f (Left x) = Left (f x) mapLeft _ (Right x) = Right x -- | Convert a Private key to the Public Key Algorithm type privkeyToAlg :: PrivKey -> PubKeyALG-privkeyToAlg (PrivKeyRSA _) = PubKeyALG_RSA-privkeyToAlg (PrivKeyDSA _) = PubKeyALG_DSA-privkeyToAlg (PrivKeyEC _) = PubKeyALG_EC-privkeyToAlg (PrivKeyX25519 _) = PubKeyALG_X25519-privkeyToAlg (PrivKeyX448 _) = PubKeyALG_X448-privkeyToAlg (PrivKeyEd25519 _) = PubKeyALG_Ed25519-privkeyToAlg (PrivKeyEd448 _) = PubKeyALG_Ed448+privkeyToAlg (PrivKeyRSA _) = PubKeyALG_RSA+privkeyToAlg (PrivKeyDSA _) = PubKeyALG_DSA+privkeyToAlg (PrivKeyEC _) = PubKeyALG_EC+privkeyToAlg (PrivKeyX25519 _) = PubKeyALG_X25519+privkeyToAlg (PrivKeyX448 _) = PubKeyALG_X448+privkeyToAlg (PrivKeyEd25519 _) = PubKeyALG_Ed25519+privkeyToAlg (PrivKeyEd448 _) = PubKeyALG_Ed448+privkeyToAlg (PrivKeyMLDSA44 _) = PubKeyALG_MLDSA44+privkeyToAlg (PrivKeyMLDSA65 _) = PubKeyALG_MLDSA65+privkeyToAlg (PrivKeyMLDSA87 _) = PubKeyALG_MLDSA87
Data/X509/PublicKey.hs view
@@ -6,78 +6,92 @@ -- Portability : unknown -- -- Public key handling in X.509 infrastructure----module Data.X509.PublicKey- ( PubKey(..)- , PubKeyEC(..)- , SerializedPoint(..)- , pubkeyToAlg- ) where+module Data.X509.PublicKey (+ PubKey (..),+ PubKeyEC (..),+ SerializedPoint (..),+ pubkeyToAlg,+) where -import Data.ASN1.Types-import Data.ASN1.Encoding import Data.ASN1.BinaryEncoding import Data.ASN1.BitArray+import Data.ASN1.Encoding+import Data.ASN1.Types import Data.Bits import Data.ByteArray (convert) import Data.ByteString (ByteString) +import Data.X509.AlgorithmIdentifier import Data.X509.Internal import Data.X509.OID-import Data.X509.AlgorithmIdentifier -import Crypto.Error (CryptoFailable(..))-import qualified Crypto.PubKey.RSA.Types as RSA-import qualified Crypto.PubKey.DSA as DSA-import qualified Crypto.PubKey.ECC.Types as ECC+import Crypto.Error (CryptoFailable (..))+import Crypto.Number.Basic (numBytes)+import Crypto.Number.Serialize (os2ip) import qualified Crypto.PubKey.Curve25519 as X25519-import qualified Crypto.PubKey.Curve448 as X448-import qualified Crypto.PubKey.Ed25519 as Ed25519-import qualified Crypto.PubKey.Ed448 as Ed448-import Crypto.Number.Basic (numBytes)-import Crypto.Number.Serialize (os2ip)+import qualified Crypto.PubKey.Curve448 as X448+import qualified Crypto.PubKey.DSA as DSA+import qualified Crypto.PubKey.ECC.Types as ECC+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Crypto.PubKey.Ed448 as Ed448+import qualified Crypto.PubKey.MLDSA as MLDSA+import qualified Crypto.PubKey.RSA.Types as RSA import Data.Word import qualified Data.ByteString as B -- | Serialized Elliptic Curve Point newtype SerializedPoint = SerializedPoint ByteString- deriving (Show,Eq)+ deriving (Show, Eq) -- | Elliptic Curve Public Key -- -- TODO: missing support for binary curve.-data PubKeyEC =- PubKeyEC_Prime- { pubkeyEC_pub :: SerializedPoint- , pubkeyEC_a :: Integer- , pubkeyEC_b :: Integer- , pubkeyEC_prime :: Integer+data PubKeyEC+ = PubKeyEC_Prime+ { pubkeyEC_pub :: SerializedPoint+ , pubkeyEC_a :: Integer+ , pubkeyEC_b :: Integer+ , pubkeyEC_prime :: Integer , pubkeyEC_generator :: SerializedPoint- , pubkeyEC_order :: Integer- , pubkeyEC_cofactor :: Integer- , pubkeyEC_seed :: Integer+ , pubkeyEC_order :: Integer+ , pubkeyEC_cofactor :: Integer+ , pubkeyEC_seed :: Integer } | PubKeyEC_Named- { pubkeyEC_name :: ECC.CurveName- , pubkeyEC_pub :: SerializedPoint+ { pubkeyEC_name :: ECC.CurveName+ , pubkeyEC_pub :: SerializedPoint }- deriving (Show,Eq)+ deriving (Show, Eq) -- | Public key types known and used in X.509-data PubKey =- PubKeyRSA RSA.PublicKey -- ^ RSA public key- | PubKeyDSA DSA.PublicKey -- ^ DSA public key- | PubKeyDH (Integer,Integer,Integer,Maybe Integer,([Word8], Integer))- -- ^ DH format with (p,g,q,j,(seed,pgenCounter))- | PubKeyEC PubKeyEC -- ^ EC public key- | PubKeyX25519 X25519.PublicKey -- ^ X25519 public key- | PubKeyX448 X448.PublicKey -- ^ X448 public key- | PubKeyEd25519 Ed25519.PublicKey -- ^ Ed25519 public key- | PubKeyEd448 Ed448.PublicKey -- ^ Ed448 public key- | PubKeyUnknown OID B.ByteString -- ^ unrecognized format- deriving (Show,Eq)+data PubKey+ = -- | RSA public key+ PubKeyRSA RSA.PublicKey+ | -- | DSA public key+ PubKeyDSA DSA.PublicKey+ | -- | DH format with (p,g,q,j,(seed,pgenCounter))+ PubKeyDH (Integer, Integer, Integer, Maybe Integer, ([Word8], Integer))+ | -- | EC public key+ PubKeyEC PubKeyEC+ | -- | X25519 public key+ PubKeyX25519 X25519.PublicKey+ | -- | X448 public key+ PubKeyX448 X448.PublicKey+ | -- | Ed25519 public key+ PubKeyEd25519 Ed25519.PublicKey+ | -- | Ed448 public key+ PubKeyEd448 Ed448.PublicKey+ | -- | ML-DSA-44 public key+ PubKeyMLDSA44 (MLDSA.VerificationKey MLDSA.MLDSA44)+ | -- | ML-DSA-65 public key+ PubKeyMLDSA65 (MLDSA.VerificationKey MLDSA.MLDSA65)+ | -- | ML-DSA-87 public key+ PubKeyMLDSA87 (MLDSA.VerificationKey MLDSA.MLDSA87)+ | -- | unrecognized format+ PubKeyUnknown OID B.ByteString+ deriving (Show, Eq) -- Public key are in the format: --@@ -87,109 +101,150 @@ -- BitString -- End Sequence instance ASN1Object PubKey where- fromASN1 (Start Sequence:Start Sequence:OID pkalg:xs)+ fromASN1 (Start Sequence : Start Sequence : OID pkalg : xs) | pkalg == getObjectID PubKeyALG_RSA = case removeNull xs of- End Sequence:BitString bits:End Sequence:xs2 -> decodeASN1Err "RSA" bits xs2 (toPubKeyRSA . rsaPubFromASN1)+ End Sequence : BitString bits : End Sequence : xs2 -> decodeASN1Err "RSA" bits xs2 (toPubKeyRSA . rsaPubFromASN1) _ -> Left ("fromASN1: X509.PubKey: unknown RSA format: " ++ show xs)- | pkalg == getObjectID PubKeyALG_DSA =+ | pkalg == getObjectID PubKeyALG_DSA = case xs of- Start Sequence:IntVal p:IntVal q:IntVal g:End Sequence:End Sequence:BitString bits:End Sequence:xs2 ->- decodeASN1Err "DSA" bits xs2 (\l -> case l of- [IntVal dsapub] ->- let pubkey = DSA.PublicKey { DSA.public_params = DSA.Params { DSA.params_p = p- , DSA.params_q = q- , DSA.params_g = g- }- , DSA.public_y = dsapub }- in Right (PubKeyDSA pubkey, [])- _ -> Left "fromASN1: X509.PubKey: unknown DSA format"- )+ Start Sequence : IntVal p : IntVal q : IntVal g : End Sequence : End Sequence+ : BitString bits+ : End Sequence+ : xs2 ->+ decodeASN1Err+ "DSA"+ bits+ xs2+ ( \l -> case l of+ [IntVal dsapub] ->+ let pubkey =+ DSA.PublicKey+ { DSA.public_params =+ DSA.Params+ { DSA.params_p = p+ , DSA.params_q = q+ , DSA.params_g = g+ }+ , DSA.public_y = dsapub+ }+ in Right (PubKeyDSA pubkey, [])+ _ -> Left "fromASN1: X509.PubKey: unknown DSA format"+ ) _ -> Left "fromASN1: X509.PubKey: unknown DSA format" | pkalg == getObjectID PubKeyALG_EC = case xs of- OID curveOid:End Sequence:BitString bits:End Sequence:xs2 ->+ OID curveOid : End Sequence : BitString bits : End Sequence : xs2 -> case lookupByOID curvesOIDTable curveOid of Just curveName -> Right (PubKeyEC $ PubKeyEC_Named curveName (bitArrayToPoint bits), xs2)- Nothing -> Left ("fromASN1: X509.Pubkey: EC unknown curve " ++ show curveOid)+ Nothing -> Left ("fromASN1: X509.Pubkey: EC unknown curve " ++ show curveOid) Start Sequence- :IntVal 1- :Start Sequence- :OID [1,2,840,10045,1,1]- :IntVal prime- :End Sequence- :Start Sequence- :OctetString a- :OctetString b- :BitString seed- :End Sequence- :OctetString generator- :IntVal order- :IntVal cofactor- :End Sequence- :End Sequence- :BitString pub- :End Sequence- :xs2 ->- Right (PubKeyEC $ PubKeyEC_Prime- { pubkeyEC_pub = bitArrayToPoint pub- , pubkeyEC_a = os2ip a- , pubkeyEC_b = os2ip b- , pubkeyEC_prime = prime- , pubkeyEC_generator = SerializedPoint generator- , pubkeyEC_order = order- , pubkeyEC_cofactor = cofactor- , pubkeyEC_seed = os2ip $ bitArrayGetData seed- }, xs2)+ : IntVal 1+ : Start Sequence+ : OID [1, 2, 840, 10045, 1, 1]+ : IntVal prime+ : End Sequence+ : Start Sequence+ : OctetString a+ : OctetString b+ : BitString seed+ : End Sequence+ : OctetString generator+ : IntVal order+ : IntVal cofactor+ : End Sequence+ : End Sequence+ : BitString pub+ : End Sequence+ : xs2 ->+ Right+ ( PubKeyEC $+ PubKeyEC_Prime+ { pubkeyEC_pub = bitArrayToPoint pub+ , pubkeyEC_a = os2ip a+ , pubkeyEC_b = os2ip b+ , pubkeyEC_prime = prime+ , pubkeyEC_generator = SerializedPoint generator+ , pubkeyEC_order = order+ , pubkeyEC_cofactor = cofactor+ , pubkeyEC_seed = os2ip $ bitArrayGetData seed+ }+ , xs2+ ) _ -> Left $ "fromASN1: X509.PubKey: unknown EC format: " ++ show xs- | pkalg == getObjectID PubKeyALG_X25519 =+ | pkalg == getObjectID PubKeyALG_X25519 = case xs of- End Sequence:BitString bits:End Sequence:xs2 -> decodeCF "X25519" PubKeyX25519 bits xs2 X25519.publicKey+ End Sequence : BitString bits : End Sequence : xs2 -> decodeCF "X25519" PubKeyX25519 bits xs2 X25519.publicKey _ -> Left ("fromASN1: X509.PubKey: unknown X25519 format: " ++ show xs)- | pkalg == getObjectID PubKeyALG_X448 =+ | pkalg == getObjectID PubKeyALG_X448 = case xs of- End Sequence:BitString bits:End Sequence:xs2 -> decodeCF "X448" PubKeyX448 bits xs2 X448.publicKey+ End Sequence : BitString bits : End Sequence : xs2 -> decodeCF "X448" PubKeyX448 bits xs2 X448.publicKey _ -> Left ("fromASN1: X509.PubKey: unknown X448 format: " ++ show xs)- | pkalg == getObjectID PubKeyALG_Ed25519 =+ | pkalg == getObjectID PubKeyALG_Ed25519 = case xs of- End Sequence:BitString bits:End Sequence:xs2 -> decodeCF "Ed25519" PubKeyEd25519 bits xs2 Ed25519.publicKey+ End Sequence : BitString bits : End Sequence : xs2 -> decodeCF "Ed25519" PubKeyEd25519 bits xs2 Ed25519.publicKey _ -> Left ("fromASN1: X509.PubKey: unknown Ed25519 format: " ++ show xs)- | pkalg == getObjectID PubKeyALG_Ed448 =+ | pkalg == getObjectID PubKeyALG_Ed448 = case xs of- End Sequence:BitString bits:End Sequence:xs2 -> decodeCF "Ed448" PubKeyEd448 bits xs2 Ed448.publicKey+ End Sequence : BitString bits : End Sequence : xs2 -> decodeCF "Ed448" PubKeyEd448 bits xs2 Ed448.publicKey _ -> Left ("fromASN1: X509.PubKey: unknown Ed448 format: " ++ show xs)+ -- RFC 9881 Section 4: the parameters are absent and the BIT STRING+ -- is the raw public key.+ | pkalg == getObjectID PubKeyALG_MLDSA44 =+ case xs of+ End Sequence : BitString bits : End Sequence : xs2 -> decodeCF "ML-DSA-44" PubKeyMLDSA44 bits xs2 MLDSA.verificationKey+ _ -> Left ("fromASN1: X509.PubKey: unknown ML-DSA-44 format: " ++ show xs)+ | pkalg == getObjectID PubKeyALG_MLDSA65 =+ case xs of+ End Sequence : BitString bits : End Sequence : xs2 -> decodeCF "ML-DSA-65" PubKeyMLDSA65 bits xs2 MLDSA.verificationKey+ _ -> Left ("fromASN1: X509.PubKey: unknown ML-DSA-65 format: " ++ show xs)+ | pkalg == getObjectID PubKeyALG_MLDSA87 =+ case xs of+ End Sequence : BitString bits : End Sequence : xs2 -> decodeCF "ML-DSA-87" PubKeyMLDSA87 bits xs2 MLDSA.verificationKey+ _ -> Left ("fromASN1: X509.PubKey: unknown ML-DSA-87 format: " ++ show xs) | otherwise = Left $ "fromASN1: unknown public key OID: " ++ show pkalg- where decodeASN1Err format bits xs2 f =- case decodeASN1' BER (bitArrayGetData bits) of- Left err -> Left ("fromASN1: X509.PubKey " ++ format ++ " bitarray cannot be parsed: " ++ show err)- Right s -> case f s of- Left err -> Left err- Right (r, xsinner) -> Right (r, xsinner ++ xs2)- toPubKeyRSA = either Left (\(rsaKey, r) -> Right (PubKeyRSA rsaKey, r))-- bitArrayToPoint = SerializedPoint . bitArrayGetData+ where+ decodeASN1Err format bits xs2 f =+ case decodeASN1' BER (bitArrayGetData bits) of+ Left err ->+ Left+ ("fromASN1: X509.PubKey " ++ format ++ " bitarray cannot be parsed: " ++ show err)+ Right s -> case f s of+ Left err -> Left err+ Right (r, xsinner) -> Right (r, xsinner ++ xs2)+ toPubKeyRSA = either Left (\(rsaKey, r) -> Right (PubKeyRSA rsaKey, r)) - removeNull (Null:r) = r- removeNull l = l+ bitArrayToPoint = SerializedPoint . bitArrayGetData - decodeCF format c bits xs2 f = case f (bitArrayGetData bits) of- CryptoPassed pk -> Right (c pk, xs2)- CryptoFailed err -> Left ("fromASN1: X509.PubKey " ++ format ++ " bitarray contains an invalid public key: " ++ show err)+ removeNull (Null : r) = r+ removeNull l = l + decodeCF format c bits xs2 f = case f (bitArrayGetData bits) of+ CryptoPassed pk -> Right (c pk, xs2)+ CryptoFailed err ->+ Left+ ( "fromASN1: X509.PubKey "+ ++ format+ ++ " bitarray contains an invalid public key: "+ ++ show err+ ) fromASN1 l = Left ("fromASN1: X509.PubKey: unknown format:" ++ show l) toASN1 a = \xs -> encodePK a ++ xs -- | Convert a Public key to the Public Key Algorithm type pubkeyToAlg :: PubKey -> PubKeyALG-pubkeyToAlg (PubKeyRSA _) = PubKeyALG_RSA-pubkeyToAlg (PubKeyDSA _) = PubKeyALG_DSA-pubkeyToAlg (PubKeyDH _) = PubKeyALG_DH-pubkeyToAlg (PubKeyEC _) = PubKeyALG_EC-pubkeyToAlg (PubKeyX25519 _) = PubKeyALG_X25519-pubkeyToAlg (PubKeyX448 _) = PubKeyALG_X448-pubkeyToAlg (PubKeyEd25519 _) = PubKeyALG_Ed25519-pubkeyToAlg (PubKeyEd448 _) = PubKeyALG_Ed448+pubkeyToAlg (PubKeyRSA _) = PubKeyALG_RSA+pubkeyToAlg (PubKeyDSA _) = PubKeyALG_DSA+pubkeyToAlg (PubKeyDH _) = PubKeyALG_DH+pubkeyToAlg (PubKeyEC _) = PubKeyALG_EC+pubkeyToAlg (PubKeyX25519 _) = PubKeyALG_X25519+pubkeyToAlg (PubKeyX448 _) = PubKeyALG_X448+pubkeyToAlg (PubKeyEd25519 _) = PubKeyALG_Ed25519+pubkeyToAlg (PubKeyEd448 _) = PubKeyALG_Ed448+pubkeyToAlg (PubKeyMLDSA44 _) = PubKeyALG_MLDSA44+pubkeyToAlg (PubKeyMLDSA65 _) = PubKeyALG_MLDSA65+pubkeyToAlg (PubKeyMLDSA87 _) = PubKeyALG_MLDSA87 pubkeyToAlg (PubKeyUnknown oid _) = PubKeyALG_Unknown oid encodePK :: PubKey -> [ASN1]@@ -197,68 +252,85 @@ where pkalg = OID $ getObjectID $ pubkeyToAlg key encodeInner (PubKeyRSA pubkey) =- asn1Container Sequence [pkalg,Null] ++ [BitString $ toBitArray bits 0]- where bits = encodeASN1' DER $ rsaPubToASN1 pubkey []+ asn1Container Sequence [pkalg, Null] ++ [BitString $ toBitArray bits 0]+ where+ bits = encodeASN1' DER $ rsaPubToASN1 pubkey [] encodeInner (PubKeyDSA pubkey) = asn1Container Sequence ([pkalg] ++ dsaseq) ++ [BitString $ toBitArray bits 0] where- dsaseq = asn1Container Sequence [IntVal (DSA.params_p params)- ,IntVal (DSA.params_q params)- ,IntVal (DSA.params_g params)]+ dsaseq =+ asn1Container+ Sequence+ [ IntVal (DSA.params_p params)+ , IntVal (DSA.params_q params)+ , IntVal (DSA.params_g params)+ ] params = DSA.public_params pubkey- bits = encodeASN1' DER [IntVal $ DSA.public_y pubkey]+ bits = encodeASN1' DER [IntVal $ DSA.public_y pubkey] encodeInner (PubKeyEC (PubKeyEC_Named curveName (SerializedPoint bits))) =- asn1Container Sequence [pkalg,OID eOid] ++ [BitString $ toBitArray bits 0]+ asn1Container Sequence [pkalg, OID eOid] ++ [BitString $ toBitArray bits 0] where eOid = case lookupOID curvesOIDTable curveName of- Just oid -> oid- _ -> error ("undefined curve OID: " ++ show curveName)- encodeInner (PubKeyEC (PubKeyEC_Prime {})) =+ Just oid -> oid+ _ -> error ("undefined curve OID: " ++ show curveName)+ encodeInner (PubKeyEC (PubKeyEC_Prime{})) = error "encodeInner: unimplemented public key EC_Prime"- encodeInner (PubKeyX25519 pubkey) =+ encodeInner (PubKeyX25519 pubkey) = asn1Container Sequence [pkalg] ++ [BitString $ toBitArray (convert pubkey) 0]- encodeInner (PubKeyX448 pubkey) =+ encodeInner (PubKeyX448 pubkey) = asn1Container Sequence [pkalg] ++ [BitString $ toBitArray (convert pubkey) 0]- encodeInner (PubKeyEd25519 pubkey) =+ encodeInner (PubKeyEd25519 pubkey) = asn1Container Sequence [pkalg] ++ [BitString $ toBitArray (convert pubkey) 0]- encodeInner (PubKeyEd448 pubkey) =+ encodeInner (PubKeyEd448 pubkey) = asn1Container Sequence [pkalg] ++ [BitString $ toBitArray (convert pubkey) 0]+ encodeInner (PubKeyMLDSA44 pubkey) =+ asn1Container Sequence [pkalg] ++ [BitString $ toBitArray (convert pubkey) 0]+ encodeInner (PubKeyMLDSA65 pubkey) =+ asn1Container Sequence [pkalg] ++ [BitString $ toBitArray (convert pubkey) 0]+ encodeInner (PubKeyMLDSA87 pubkey) =+ asn1Container Sequence [pkalg] ++ [BitString $ toBitArray (convert pubkey) 0] encodeInner (PubKeyDH _) = error "encodeInner: unimplemented public key DH" encodeInner (PubKeyUnknown _ l) =- asn1Container Sequence [pkalg,Null] ++ [BitString $ toBitArray l 0]+ asn1Container Sequence [pkalg, Null] ++ [BitString $ toBitArray l 0] rsaPubToASN1 :: RSA.PublicKey -> [ASN1] -> [ASN1] rsaPubToASN1 pubkey xs =- Start Sequence : IntVal (RSA.public_n pubkey) : IntVal (RSA.public_e pubkey) : End Sequence : xs+ Start Sequence+ : IntVal (RSA.public_n pubkey)+ : IntVal (RSA.public_e pubkey)+ : End Sequence+ : xs rsaPubFromASN1 :: [ASN1] -> Either String (RSA.PublicKey, [ASN1])-rsaPubFromASN1 (Start Sequence:IntVal smodulus:IntVal pubexp:End Sequence:xs) =+rsaPubFromASN1 (Start Sequence : IntVal smodulus : IntVal pubexp : End Sequence : xs) = Right (pub, xs) where- pub = RSA.PublicKey { RSA.public_size = numBytes modulus- , RSA.public_n = modulus- , RSA.public_e = pubexp- }+ pub =+ RSA.PublicKey+ { RSA.public_size = numBytes modulus+ , RSA.public_n = modulus+ , RSA.public_e = pubexp+ } -- some bad implementation will not serialize ASN.1 integer properly, leading -- to negative modulus. if that's the case, we correct it. modulus = toPositive smodulus--rsaPubFromASN1 ( Start Sequence- : IntVal ver- : Start Sequence- : OID oid- : Null- : End Sequence- : OctetString bs- : xs- )- | ver /= 0 = Left "rsaPubFromASN1: Invalid version, expecting 0"- | oid /= [1,2,840,113549,1,1,1] =- Left "rsaPubFromASN1: invalid OID"- | otherwise =- let inner = either strError rsaPubFromASN1 $ decodeASN1' BER bs- strError = Left . ("fromASN1: RSA.PublicKey: " ++) . show- in either Left (\(k, _) -> Right (k, xs)) inner+rsaPubFromASN1+ ( Start Sequence+ : IntVal ver+ : Start Sequence+ : OID oid+ : Null+ : End Sequence+ : OctetString bs+ : xs+ )+ | ver /= 0 = Left "rsaPubFromASN1: Invalid version, expecting 0"+ | oid /= [1, 2, 840, 113549, 1, 1, 1] =+ Left "rsaPubFromASN1: invalid OID"+ | otherwise =+ let inner = either strError rsaPubFromASN1 $ decodeASN1' BER bs+ strError = Left . ("fromASN1: RSA.PublicKey: " ++) . show+ in either Left (\(k, _) -> Right (k, xs)) inner rsaPubFromASN1 _ = Left "fromASN1: RSA.PublicKey: unexpected format" @@ -266,14 +338,19 @@ -- to negative modulus. toPositive :: Integer -> Integer toPositive int- | int < 0 = uintOfBytes $ bytesOfInt int+ | int < 0 = uintOfBytes $ bytesOfInt int | otherwise = int where uintOfBytes = foldl (\acc n -> (acc `shiftL` 8) + fromIntegral n) 0 bytesOfInt :: Integer -> [Word8] bytesOfInt n = if testBit (head nints) 7 then nints else 0xff : nints- where nints = reverse $ plusOne $ reverse $ map complement $ bytesOfUInt (abs n)- plusOne [] = [1]- plusOne (x:xs) = if x == 0xff then 0 : plusOne xs else (x+1) : xs+ where+ nints = reverse $ plusOne $ reverse $ map complement $ bytesOfUInt (abs n)+ plusOne [] = [1]+ plusOne (x : xs) = if x == 0xff then 0 : plusOne xs else (x + 1) : xs bytesOfUInt x = reverse (list x)- where list i = if i <= 0xff then [fromIntegral i] else (fromIntegral i .&. 0xff) : list (i `shiftR` 8)+ where+ list i =+ if i <= 0xff+ then [fromIntegral i]+ else (fromIntegral i .&. 0xff) : list (i `shiftR` 8)
Data/X509/Signed.hs view
@@ -21,59 +21,69 @@ -- -- Signed represent the pure data type for compliant cases, and SignedExact -- the real world situation of having to deal with compliant and non-compliant cases.----module Data.X509.Signed- (+module Data.X509.Signed ( -- * Types- Signed(..)- , SignedExact+ Signed (..),+ SignedExact,+ -- * SignedExact to Signed- , getSigned- , getSignedData+ getSigned,+ getSignedData,+ -- * Marshalling function- , encodeSignedObject- , decodeSignedObject+ encodeSignedObject,+ decodeSignedObject,+ -- * Object to Signed and SignedExact- , objectToSignedExact- , objectToSignedExactF- , objectToSigned- , signedToExact- ) where+ objectToSignedExact,+ objectToSignedExactF,+ objectToSigned,+ signedToExact,+) where import Control.Arrow (first)+import Data.ASN1.BinaryEncoding+import qualified Data.ASN1.BinaryEncoding.Raw as Raw (toByteString)+import Data.ASN1.BitArray+import Data.ASN1.Encoding+import Data.ASN1.Stream+import Data.ASN1.Types import Data.ByteString (ByteString) import qualified Data.ByteString as B import Data.X509.AlgorithmIdentifier-import Data.ASN1.Types-import Data.ASN1.Encoding-import Data.ASN1.BinaryEncoding-import Data.ASN1.Stream-import Data.ASN1.BitArray-import qualified Data.ASN1.BinaryEncoding.Raw as Raw (toByteString) -- | Represent a signed object using a traditional X509 structure. -- -- When dealing with external certificate, use the SignedExact structure -- not this one. data (Show a, Eq a, ASN1Object a) => Signed a = Signed- { signedObject :: a -- ^ Object to sign- , signedAlg :: SignatureALG -- ^ Signature Algorithm used- , signedSignature :: B.ByteString -- ^ Signature as bytes- } deriving (Show, Eq)+ { signedObject :: a+ -- ^ Object to sign+ , signedAlg :: SignatureALG+ -- ^ Signature Algorithm used+ , signedSignature :: B.ByteString+ -- ^ Signature as bytes+ }+ deriving (Show, Eq) -- | Represent the signed object plus the raw data that we need to -- keep around for non compliant case to be able to verify signature. data (Show a, Eq a, ASN1Object a) => SignedExact a = SignedExact- { getSigned :: Signed a -- ^ get the decoded Signed data- , exactObjectRaw :: B.ByteString -- ^ The raw representation of the object a- -- TODO: in later version, replace with offset in exactRaw- , encodeSignedObject :: B.ByteString -- ^ The raw representation of the whole signed structure- } deriving (Show, Eq)+ { getSigned :: Signed a+ -- ^ get the decoded Signed data+ , exactObjectRaw :: B.ByteString+ -- ^ The raw representation of the object a+ -- TODO: in later version, replace with offset in exactRaw+ , encodeSignedObject :: B.ByteString+ -- ^ The raw representation of the whole signed structure+ }+ deriving (Show, Eq) -- | Get the signed data for the signature-getSignedData :: (Show a, Eq a, ASN1Object a)- => SignedExact a- -> B.ByteString+getSignedData+ :: (Show a, Eq a, ASN1Object a)+ => SignedExact a+ -> B.ByteString getSignedData = exactObjectRaw -- | make a 'SignedExact' copy of a 'Signed' object@@ -82,18 +92,23 @@ -- encoded object to have been made on a compliant DER ASN1 implementation. -- -- It's better to use 'objectToSignedExact' instead of this.-signedToExact :: (Show a, Eq a, ASN1Object a)- => Signed a- -> SignedExact a+signedToExact+ :: (Show a, Eq a, ASN1Object a)+ => Signed a+ -> SignedExact a signedToExact signed = sExact- where (sExact, ()) = objectToSignedExact fakeSigFunction (signedObject signed)- fakeSigFunction _ = (signedSignature signed, signedAlg signed, ())+ where+ (sExact, ()) = objectToSignedExact fakeSigFunction (signedObject signed)+ fakeSigFunction _ = (signedSignature signed, signedAlg signed, ()) -- | Transform an object into a 'SignedExact' object-objectToSignedExact :: (Show a, Eq a, ASN1Object a)- => (ByteString -> (ByteString, SignatureALG, r)) -- ^ signature function- -> a -- ^ object to sign- -> (SignedExact a, r)+objectToSignedExact+ :: (Show a, Eq a, ASN1Object a)+ => (ByteString -> (ByteString, SignatureALG, r))+ -- ^ signature function+ -> a+ -- ^ object to sign+ -> (SignedExact a, r) objectToSignedExact signatureFunction object = (signedExact, val) where (val, signedExact) = objectToSignedExactF (wrap . signatureFunction) object@@ -102,70 +117,86 @@ -- | A generalization of 'objectToSignedExact' where the signature function -- runs in an arbitrary functor. This allows for example to sign using an -- algorithm needing random values.-objectToSignedExactF :: (Functor f, Show a, Eq a, ASN1Object a)- => (ByteString -> f (ByteString, SignatureALG)) -- ^ signature function- -> a -- ^ object to sign- -> f (SignedExact a)+objectToSignedExactF+ :: (Functor f, Show a, Eq a, ASN1Object a)+ => (ByteString -> f (ByteString, SignatureALG))+ -- ^ signature function+ -> a+ -- ^ object to sign+ -> f (SignedExact a) objectToSignedExactF signatureFunction object = fmap buildSignedExact (signatureFunction objRaw)- where buildSignedExact (sigBits,sigAlg) =- let signed = Signed { signedObject = object- , signedAlg = sigAlg- , signedSignature = sigBits- }- signedRaw = encodeASN1' DER signedASN1- signedASN1 = Start Sequence- : objASN1- (toASN1 sigAlg- (BitString (toBitArray sigBits 0)- : End Sequence- : []))- in SignedExact signed objRaw signedRaw- objASN1 = \xs -> Start Sequence : toASN1 object (End Sequence : xs)- objRaw = encodeASN1' DER (objASN1 [])+ where+ buildSignedExact (sigBits, sigAlg) =+ let signed =+ Signed+ { signedObject = object+ , signedAlg = sigAlg+ , signedSignature = sigBits+ }+ signedRaw = encodeASN1' DER signedASN1+ signedASN1 =+ Start Sequence+ : objASN1+ ( toASN1+ sigAlg+ ( BitString (toBitArray sigBits 0)+ : End Sequence+ : []+ )+ )+ in SignedExact signed objRaw signedRaw+ objASN1 = \xs -> Start Sequence : toASN1 object (End Sequence : xs)+ objRaw = encodeASN1' DER (objASN1 []) -- | Transform an object into a 'Signed' object. -- -- It's recommended to use the SignedExact object instead of Signed.-objectToSigned :: (Show a, Eq a, ASN1Object a)- => (ByteString- -> (ByteString, SignatureALG, r))- -> a- -> (Signed a, r)+objectToSigned+ :: (Show a, Eq a, ASN1Object a)+ => ( ByteString+ -> (ByteString, SignatureALG, r)+ )+ -> a+ -> (Signed a, r) objectToSigned signatureFunction object = first getSigned $ objectToSignedExact signatureFunction object -- | Try to parse a bytestring that use the typical X509 signed structure format-decodeSignedObject :: (Show a, Eq a, ASN1Object a)- => ByteString- -> Either String (SignedExact a)+decodeSignedObject+ :: (Show a, Eq a, ASN1Object a)+ => ByteString+ -> Either String (SignedExact a) decodeSignedObject b = either (Left . show) parseSigned $ decodeASN1Repr' BER b- where -- the following implementation is very inefficient.- -- uses reverse and containing, move to a better solution eventually- parseSigned l = onContainer (fst $ getConstructedEndRepr l) $ \l2 ->- let (objRepr,rem1) = getConstructedEndRepr l2- (sigAlgSeq,rem2) = getConstructedEndRepr rem1- (sigSeq,_) = getConstructedEndRepr rem2- obj = onContainer objRepr (either Left Right . fromASN1 . map fst)- in case (obj, map fst sigSeq) of- (Right (o,[]), [BitString signature]) ->- let rawObj = Raw.toByteString $ concatMap snd objRepr- in case fromASN1 $ map fst sigAlgSeq of- Left s -> Left ("signed object error sigalg: " ++ s)- Right (sigAlg,_) ->- let signed = Signed- { signedObject = o- , signedAlg = sigAlg- , signedSignature = bitArrayGetData signature- }- in Right $ SignedExact- { getSigned = signed- , exactObjectRaw = rawObj- , encodeSignedObject = b- }- (Right (_,remObj), _) ->- Left $ ("signed object error: remaining stream in object: " ++ show remObj)- (Left err, _) -> Left $ ("signed object error: " ++ show err)- onContainer ((Start _, _) : l) f =- case reverse l of- ((End _, _) : l2) -> f $ reverse l2- _ -> f []- onContainer _ f = f []+ where+ -- the following implementation is very inefficient.+ -- uses reverse and containing, move to a better solution eventually+ parseSigned l = onContainer (fst $ getConstructedEndRepr l) $ \l2 ->+ let (objRepr, rem1) = getConstructedEndRepr l2+ (sigAlgSeq, rem2) = getConstructedEndRepr rem1+ (sigSeq, _) = getConstructedEndRepr rem2+ obj = onContainer objRepr (either Left Right . fromASN1 . map fst)+ in case (obj, map fst sigSeq) of+ (Right (o, []), [BitString signature]) ->+ let rawObj = Raw.toByteString $ concatMap snd objRepr+ in case fromASN1 $ map fst sigAlgSeq of+ Left s -> Left ("signed object error sigalg: " ++ s)+ Right (sigAlg, _) ->+ let signed =+ Signed+ { signedObject = o+ , signedAlg = sigAlg+ , signedSignature = bitArrayGetData signature+ }+ in Right $+ SignedExact+ { getSigned = signed+ , exactObjectRaw = rawObj+ , encodeSignedObject = b+ }+ (Right (_, remObj), _) ->+ Left $ ("signed object error: remaining stream in object: " ++ show remObj)+ (Left err, _) -> Left $ ("signed object error: " ++ show err)+ onContainer ((Start _, _) : l) f =+ case reverse l of+ ((End _, _) : l2) -> f $ reverse l2+ _ -> f []+ onContainer _ f = f []
Setup.hs view
@@ -1,2 +1,3 @@ import Distribution.Simple+ main = defaultMain
+ Tests/MLDSAKeys.hs view
@@ -0,0 +1,365 @@+-- | ML-DSA private keys in PKCS#8, as OpenSSL 3.6.4 writes them.+--+-- Not generated here: the point of these is that another implementation+-- made them. A round trip through this library's own encoder and decoder+-- says only that the two halves agree; these say that what comes out is+-- what the rest of the world reads.+--+-- openssl genpkey -algorithm ML-DSA-44 | openssl pkey -outform DER+--+-- OpenSSL writes the @both@ form of RFC 9881 section 6: the seed and the+-- expanded key in a SEQUENCE.+module MLDSAKeys (+ opensslMLDSA44,+ opensslMLDSA65,+ opensslMLDSA87,+) where++import qualified Data.ByteString as BS++import Data.ByteArray.Encoding (Base (Base16), convertFromBase)++hex :: String -> BS.ByteString+hex s = case convertFromBase Base16 (BS.pack (map (fromIntegral . fromEnum) s)) of+ Left e -> error e+ Right b -> b++-- | 2626 bytes of DER.+opensslMLDSA44 :: BS.ByteString+opensslMLDSA44 = hex $ concat+ [ "30820a3e020100300b060960864801650304031104820a2a30820a260420151dff249cb3"+ , "4b87169de5d99e2b19666f2a9efbecab731295509007c22ddcc604820a003053ed09fbcf"+ , "1d28675cc66c20e7bb04cb7e077b11f285aec9ce9ae530526ab0299ad6d0887cff9a40ff"+ , "d31ea92f2b7ef42338fe02d3159b236d1252012e65a60936c589b63435a8f2f65b5b6f16"+ , "058f72851d5e63f064187083aafbec42927db7b13528591a2336c476736e0c0a0c6dacb7"+ , "6c70949268a84b9530bb2bb2689608a72c48982044c01014a721d224644b820914876cd9"+ , "40090386418440221ba26ccc42818816608230420ba684d9b40921236959182d084852d8"+ , "380c04367102254e82b21021c78c4b442e123071822621184720c8226298120adb207118"+ , "248ae40084e2b4315ac220e0404089900023119201c628123351db10848bc88808382c0c"+ , "498c81360441380cc32012180460c2c844d0384144102909448ad2382592442a1929721a"+ , "2388d1328d51a68c21460e1811510ca3459a9608d0c824a4c87003b9840a284462b84d08"+ , "99501c803020b890c1b0654a20012403441146665430868b48910a448d4ca80801994d9a"+ , "847161288664248999427009c825609441a4948042200401046c2202311ca184a0002dc4"+ , "c24c51088898a6699aa2298b106d44448e1129721b000a93b229da1249cc164022182d91"+ , "26442014004c4866002461c1b0059a140c02238a1b410e0a3806cb9251524840cc80290b"+ , "04224218686440828c964852445209906162a27181066a5ab48c20984d893805cbc84881"+ , "8090d4a2915006651326649aa608e2c8059128041a27729c228853b86541382e89b23001"+ , "828d1b820022115159a41024436498a0508a06648c020093c6411b060e932021d0004021"+ , "21801c364609292198c2659c3449d8126a0811452246218c864ca0260c9c023199880c22"+ , "2149e39809d2342501a570d4a83150c0601c929082a60d02c629d0400a44068404b9611a"+ , "866003396d0a186463c0680820204b260a114992a30049099089044752a3186610298d8a"+ , "446682320850b2904ac289d9122d44462212246193a62804a28854a80c00b74c51044403"+ , "801012458e50162cd3285002c4051c388ca04412903070a1c861cc349080c42c43324224"+ , "45269c96696288688c268299b26c4c823000b2809c24051ab889530685028401d3c0491a"+ , "80680003861b252c523669d3c62019042453222983866d9102826496910a4321da462493"+ , "404ad23689233031a1104a01328108396880288d608271440404d81865d2a0818c44095c"+ , "a08d43188401c971101205c8c0045b1085dc2050cac410a320319a35cb74122d502593d4"+ , "edf07f9236080bd29e26691f48721d4724b5a1b13a14aae5f8a1d1e0504419ac39dea10a"+ , "5ad354bba5a81c391739a3505d5e92d3b35ad7a74c64b0c46229a61be43cfb9e14aa97ab"+ , "99295cf2212be68590e7e53659f2cb6a5718a0859acd609fd94845ddee0927c75547d1d4"+ , "ab9864f60266df48f9afae8ee8275f98c8273f6be0249e6f0c069da46d215596cbc1c5d2"+ , "ce17c67287e7696e45a6d9fea29ef1a7206855d87841a929837277f7aa0ef8118e52659e"+ , "a033c42ef06cbc913fb02ceeea3a1bc9d5c09e89d8dc49d85044a01e8803fe3a540b02f3"+ , "ec78fcf2611c103a8f9b5bd9db91b9a69a12b96402cc6866b16fd22923ce78e8c75754a3"+ , "6c04aa80b21f0a90c28ee2e6e567be4a278427619965e289dde9643fe4b483fa70367efb"+ , "9d52a517a2b1798dab0a4e89198cc3fd30afcd85ee423523f06cb23c7959c0d120256561"+ , "88c4a818e7fec6e7195cf3a5ad016f93d8a62cd6b557cc6400ed2e81b43613f0da80934d"+ , "d9be78bfdacd6fe50819498a076806a326fb3825dfff67a8dbf2da4cd81b88965d8b170e"+ , "87e56a796c028e6511ca9a3db4eefadc1a0d57d75e1d75ebefeffe9ac4d0ec5a60cad8ae"+ , "27e6f39afda951b2de41e4042845584e753654a619f1c9df5c7996ddde570ff42522538a"+ , "5e323936f2b3bced1eadffc5bcef84d16b729a8a9442ac7c3844b66dbc75deb4904bc207"+ , "948f162bbfbea6f8b7788f90cd66b61120b8c29b317cead830d018d6990b9bcfa6c907bd"+ , "8ea63176fa89bf0c4af60f28b4604bff034a36f70be8ed8d004ef520f292cc8e9202c0fb"+ , "28df92c04296547f3ab012068fd24cce21307201ade6b7b7071937471be57ef02173e7c0"+ , "fddcc302c210f3ba2defd3631b174a590371e81dc4a0c8a7c79ce59840d4fb1b2620ac11"+ , "c831b43da3525db213adbe3b35295aef04bed771db85cee59ce7a8ede9bf1782ed0b074f"+ , "c3db5bcc5b6d101e250df37913d3b76babc9ff7852fe9f47427d04ee0c19ed869297428c"+ , "84e0549e6fdbf16733fa32097dfd97ff5165c9dd34e6db794de6d9680e995fef0c22a3cc"+ , "28cf4fdb3b080269d7f1aa65516042186ad420a75841d7c762f0e4754113aa518d185d6a"+ , "934704b285d78fec4d34246c338e062687cc0f1f9311e948428ee7885492fd0c08da6471"+ , "d3a2afd3e7388989ceb0e91f905d1e0f6e2f67c2be69dbb6df0f421ca8403f69946e88fe"+ , "b695b0b1b6cd93a25a8316527505dbad480a59bce5b68d261851311ad410efd9ccb10ee5"+ , "0f8448d1493242050e016912f80789c15ea5bbf382600203360deec68ac3ccdd861c54b2"+ , "8555a398ef400792e6410f37aa74a502ecee6130da2f8b2a97005f76386978c1300e4e50"+ , "49a722a0af7de32b010a73a6514a614dc80901fa868ab749ed109ee5d10a3807e2b3b4d8"+ , "a5bbaa4b6d3f88104798135ecf77ddb314f7bfdc0a1eb461bc3a1890dea5e62ce4a1cdb0"+ , "411b585178bb6fedd29a56b1cabdc93d09e008199a6558742bf8d66899ea6f10aab34bb2"+ , "f6b08dddbba4169f6f61a7c5368addb74b6c4b8dbc9882abddbdcc403b749f5edd4a3b70"+ , "561a44b34c3f85ef84c845751e983025e50d984eaf38a0d470d73436a537ce1f07666751"+ , "8fcd15547d98e79f27c6241da5fdba9946a3916e2037f76ce82535a17a1618a8894e63a5"+ , "1fb2258a2e5e7794ad9e839cc03c3a2113204b514ac55e4e9e330b2295c65504eb16714d"+ , "af385fa09b3dc92a2594c75fada27a17cc09f6d5581b08a03136d1ddc4cd2bfe1f7c384a"+ , "9b01f26698bc0e3914448a6ce6f50ee09f618b6ee4b29694919cf1c1304c35c9fc429964"+ , "31f1c5e1965383bdd9d6e85a1df1f6a73065a1cc4dc00ae7e69bff0683d844c5bfdcdc08"+ , "05dcd36ffbcdd7bb94fe4194c715197401c97771cccf078d591cea169b25cb60e7148678"+ , "e4efb857bb101a78abb396a73fb8556fba6b265f7dc4c793cf58048a005be22917e61bf8"+ , "edb95cf1005c3a9b6c797f49f25dc13c869399091bcaa0e699cb8258e4b2c843da6a1048"+ , "48e4471eb1e8f08c7482c7531b30174cb433cac5050399fa607bc6244400cf0744caee56"+ , "31ac9a5ecf67abf77c6bfd438fb76f3d18d859b7e209c88e1e5fdeb77bfe999547a207c9"+ , "05e8889fa622043f2b8da2e052e5a160bb006bf40f69761820435c59585d49101607b046"+ , "291f407f795e7d71f5a43eafc36b319a03162a155a40799a61225efe3990aefa4779283d"+ , "ae2b66daa98b28ce95d0a0afcab2e42301f46eec233439e80391941904881e716c4f2c79"+ , "23711d2927e181193a6de8d4329ad358817ddb2463148c3ab985c19808aed1909cce"+ ]++-- | 4098 bytes of DER.+opensslMLDSA65 :: BS.ByteString+opensslMLDSA65 = hex $ concat+ [ "30820ffe020100300b060960864801650304031204820fea30820fe6042062c8740c6f5b"+ , "e0c726ecb2e8c6f1b22faf29ee7a562de1a95a6ace4993812c8e04820fc01a31edba22f1"+ , "50ee1738e8bf87429343d274b5bd69e60feda1dae7b881afd0354992fd2ba1a609acf0b4"+ , "4a0413a86ac8dca104cc460d7cd80db0f38c76ad816f8c692ec5b5c821aa21ded5f1e7b1"+ , "7a8ad3534d2850d1d3f08562ab2b8b00a18f8e796ecc8f9f6d44d19099aa49410fb23e37"+ , "981740d02d957bdd8dd19aa9463e52208184643106838414406247510502717570630322"+ , "333340348846565153734381027643367848711037342000305418167076011152763614"+ , "360036114818757772181646230261447105735122373755001016732454770100313157"+ , "017817783853214666474073350114481534144318477611815667075522034524034561"+ , "405058475248684860680411802753833017174355888413426888852426871532668712"+ , "111433846561762118875606253783552072354337335766843377042210322266862033"+ , "318347136247651233866617100468638340638002367511352001360712375002063667"+ , "513007528247530118344227327731070538433300147277812580606457801432336627"+ , "354041166152518730060160020748471117175588272420573372562508520727654411"+ , "207507452018363433575624541814133431773421663310458463112367254078641742"+ , "601601041567835052631717551465400580807676334023431865516435865273080275"+ , "105305862184715168400303265533644671604143424001288640550704174028060502"+ , "655158646531448117777064104154583325006466524860733405334611440438553060"+ , "561536627177465858121558620423860053888746373827387753536775632241028626"+ , "833887013814141312321545831375434468161657485845108015474443283753833717"+ , "753678738561138255737381602451863450022408252128406317347455344310081565"+ , "526811015763376527042614842148148805104082500020533740381561618131672237"+ , "806557607168670118153081638474686850760447381012211060418005871108243667"+ , "672823514026365327021815127654068432345354144202635848086606318504201076"+ , "347262054208141460281457238134715587066020113468581226120277165763646706"+ , "335268040647817457330735125026055883341578536181455620235405883043100185"+ , "836410080502321606151075828705654646467866335740352251484250636375724436"+ , "700476701341487842804788605677123552714662132871600465871663678172122308"+ , "117801578082508863160152568880765846330011510607645371033231548620270651"+ , "217822156271604815603740340311422686316862400374766762631221508365438245"+ , "322565585663385524478706630016782647387350068870323363054176624783638712"+ , "504004872703415740422242376674302301424417033455127724488012872205680268"+ , "074052636611562483800502260316637482200001108624572715700743752306022188"+ , "440684881514010455750377217655046186824282620128031056600471876815022773"+ , "465323471601830471086315511081723030757186532733414771668757146585873778"+ , "643523825637838431777263531448504444788253215812408468467773328233352487"+ , "470808848157886003582420058711663641226827647106518037834311220647336226"+ , "172235777106242832861510073285581843682263331052140236102155032852815016"+ , "601700412573263188186253846627461715416718320841166781368781674771073677"+ , "453304358813321407060576436306848150507710618286640354123320525147476718"+ , "177333846174371781255824717212823625567200001848525323737344106125680584"+ , "181846483106031726074672420026727441500182257456024485874381257168246807"+ , "585557261283143712318526341803073887550140836151518677231230288877371567"+ , "642718063771662662581227147768585853432257306476775841358601510038120126"+ , "407387663325055740436215754707688310d0d58aa8cc178e2462327d7adbbc0a8bd863"+ , "62d4c35db9d95f18842c38d2f408451e1f4f971623d954df6e0fa9a4595de7984746f11c"+ , "8b09de3bd6d2cc60788f319552e0e0364b1d5a3cce106c495bf349bcdb51000dec34590b"+ , "f11bcc1dcda4f2bf50efe394d62f846822fa3a96126974be047e456c7bb0a1124b448332"+ , "fcbd5865a1ef81be8838075bc129eca0c7dedc9a2736da6391124d1da59014391b35c625"+ , "74c66b96235ec17ad7ff4f7974d20d10a2ad9d7613c91e7785ef955f8d289baed537046b"+ , "90a18fd35058f6efbdf996625824f0f2b6da5449a64ec8c17155a152c5736f5185be3bf2"+ , "ece2ebb06947a0f39a12bd704b097b04291ae4af4ebdbe087a6a17cb1520c869f62ab927"+ , "99a6d2d798ef01978f6d00eeee529b28992d54aa47a64c99feca55ffcd52eb7c84a7fc19"+ , "3899a23b8a639d5f682e0bbf16cb12f609506866565fec091ee97001335a4252065447ec"+ , "81e734d2f911d0ac496fe94b40b7beb5b52d6f5949f5ade53b3aaa8d9ee35e7c7b6a5f80"+ , "f010b69e1f1d781c0f7dd730106b2dc17c9f9cfb64003acfca0013968d98d865f5a1d253"+ , "fbf78d7fc30f7bde65212f4aabfc3e976178ed6f94d12b084b3aa2031e4aeeabc2fb691e"+ , "9dbe40c2d7a32a92d86dab00c39c8bbf94b93f20c68ff711250ce43ad169c228133b0958"+ , "97c62cbe2706dad8cf23f2ed1ddca4200d6ce9fcb7c48ae6dd080316cfbe87d36a3a9a80"+ , "04caa374b0f08e9e0faadf53cd0130c0116aefa222ab2d2a7c8c7d5ab3e17e8a7d0a98dc"+ , "818331657ffb452e65e110b63ba008a6c1250a1198e11855078c1c76c3777dbd3da08160"+ , "78977b9873b9537da023466a3ccd47520bc3254224b583932259bfcf6df1cf9cae1bbde9"+ , "814c4bc05da3385d18af20e91dacebc9827cfb5c342205e49dbd3110af4c5ad0c9b3c435"+ , "945e9152929bb094444101dfc95c9847bec5721b0a629b2f1ef646c6eba796d4409fa889"+ , "240e127e9b530070dc78e504160aa8a7e01483c48d54925ae36ee91f6c1647c0c292522b"+ , "931ecc6544c1e1ef933205cc4665d7c43a5e537df9de1ce34b9eb985ab9abe607228a748"+ , "0f96f7f2c233feb9d3039980cf8c9aec54f92ec8211a9a2d7ad42eaa9c0da5e7d032e63d"+ , "d1c85cbc732c90f07e1411086c95f401a68da033db92a1144c09ada62e6022443b0ee70b"+ , "8ca04c4fe663c304449fcf4236b9cac8461d5ae3e3ccc409afc5f0aa4d02abe980995545"+ , "1198f88d143488f62d5ec2f5301f706a19efebda7878a6e1afa7daafcac0d45fb8451ea5"+ , "6c681937f0ccec0c1b3a437e12bdcbcb62a17ad1d99f499b8b9c1bb35e85dfb88bdccd91"+ , "f71e1bfba790a5200c166aedc29b7fed78b1e4a0691229f2e8add8377e369005867b1e5d"+ , "c43050fe546c65751e79ad032323e039e1ac67acc081c1b157771edfad6e275f105460f3"+ , "7da53ac4ea4941490527233925b7f860eebeaf89ef096a4a00979c61e6dc8bbcee482ad6"+ , "822041e8625a69fd6616981240cc2a01ca33bd67a674e273994632a43f141b89a89a24e4"+ , "fa94780b9b3fab34669f6f6acd264b4a0cb8f0f3f7260a784c79efcb9107d6d784ac4a67"+ , "a86f50da3c7cec7783914727dd0d06343d84f9760dd8c3de98eb3b6240277985b14e5baf"+ , "3ce83fb02d7bdd97fbd7aae97a6fd159c0c910f7004de43605dcec069c9e9dd9678b7bc6"+ , "2614217df4ba635ed1636149ac6c207bdb149a551d5b73037cb238f677a113316a857cea"+ , "6763fe1386e59568dd82970e7f006c0b312e49c31047e2af7b52c9964f94fd207fe7773a"+ , "68978dec08cf2cd350b9d1728c663725499e4f98504d8620762d175ac7a061552a65b088"+ , "fcf048b64d94f3e63bb1fb5daae5b8e6b4475fed3a7440406affcbbe1ad9100b50ec1fce"+ , "b5863a77ec8be20c17afa9fec9827d0cf108f945317c9b6aa295fd59ad9127d7455dd18d"+ , "5cae5bf6f821020255e7687f445e205ae71ecdb2511264284f04b6d5f3d30e26a608cfb0"+ , "a60623e1a66d3686344ce160092bf808c95ea6a9bef196a903a4d1482c354945fe88820e"+ , "f13501c270c02864a63134051184449ed9201d49184ff824c5185f82c49bda8cb1a2b051"+ , "163016814fd1bb5fdb2d936bc4f5c21e5ee3e8584179e43be5cde2f2b47c457be9980559"+ , "5bc3e9d8e4c9ce8e748e8cfa9f4ecacd228ddd7690084d0d893ab014bc72e70dad8264f5"+ , "8eb498caa0e3025f43fc71c02dd32afed753b5d8a46dd9c8b3978e8e4163423cfb0dbfec"+ , "a06b3666ab5743afc0d540604755bf716a1de0c6d32c7e28eda63e69053b9e54a4569ce5"+ , "58ed1049f3058efec60877a53b9f56ca3a217a95a3bf918f5d1a58fe2a890ff8f72f1d32"+ , "df30470cc667e504149b04207c13a16428e3f47b6ea1c6f889bd2363be544f91a326724b"+ , "fde2dd3984475c86a8dd5c7b45b5f76e9162959300790167bca61666085216404e140f4d"+ , "0c2787a85c6d2dfc018ab44e0ea0dc00a89eba64bf71a4e57a2266a96d7a39baebcb682b"+ , "c0361ff336ab7d0b440f5f9d320dd5f705e9316c3be45a3ff96f5ea87dc5ce397f3ae7c5"+ , "38188b253ac6df51906637a6ff5c26066f541eea6d63bdd7821451e1c4769216f5e45511"+ , "d99b949610d222135a8d3dfea12f0d704cdd4012882340db36c1e2085ed065b49fc998c6"+ , "bd4955282cc8c450fe8f5dac5fa4b0dd882a378b399b3097b8afab0ab1d2fe2433fda7f5"+ , "542707b50e49744832b3693b44377b8fbc9362367841e85e1ef9d2789617b302f935fc58"+ , "53fc5a6783bd307c2097f612b173d0b3b24fdf2b332ab9827ada091b8cb4243b23490438"+ , "84abf44ab780599d682484a898c89612dfa6e6e128626b23e32fc1c3bbe717cec0141bc7"+ , "379a3ebf1eb072f70e4aabbdc0a7635760cd74eb850c42bc2b9298794963b833c48ca544"+ , "683e512c5a734e713e10274b0ce5adbe15fad996643fa6c075db2fdac2cb4438027c6b5d"+ , "7eb4f2980c1513d92267f90a6ed8e287f6bde9e745712d2ac6de843f56223bb2f5bd35bc"+ , "c9fd83a782d437e0d6094af64255de70cc2240c7bbc57cb4c5ebff8bf288df4b28822306"+ , "02a1cb7a984e5f6923f8c5db79cea441f8a28a559be3c74d5352f00f5ab5ea6c369f94bf"+ , "432b66e7982c078980edef59d757d133033253b1b55066dac7954d2ca53fc42744eeb541"+ , "bcd971a1ebf832e2925177024222c3eeaa9226efe93e229d1561ff7ed471fc71fb435028"+ , "458438e20d671d26bf3fd1052a255a63b249d855c98f44cf1a01f72acb3e1dde713facf3"+ , "8195d7950e0584f98c0145d5e60d275d299a8607af142d12cfc7f1c1e19b5ce917a3062e"+ , "18f15890ef19f923edcf18a6af4e4b1514fe064b10153793acbc23c3ece6fe2e1095e202"+ , "1d92d82cca5b24d517bf9cd848af9813909604dfd4128b349dde403458e55a34ec4b41c8"+ , "97ea9a909515774e0aa757cb863296bc1c017a693bf7e104aabe3458a08f7b897a5b128d"+ , "463b03d52acd6fccafe603aeef466e3d88887533b76a0a33d3cad5ca1ad7"+ ]++-- | 4962 bytes of DER.+opensslMLDSA87 :: BS.ByteString+opensslMLDSA87 = hex $ concat+ [ "3082135e020100300b06096086480165030403130482134a30821346042086aea35731b2"+ , "7bde938f1eac39d757b84e88fddde9f585703952cca3a8f29b53048213204624fcb8e48e"+ , "f22d91bf330fb8378a5d2d89a7c7800d90c8f289209faef4e924f685483fb0f845abe9bc"+ , "130b4e070ca994d1fb925d736ba629eee21d10e126c02eb255fffa131ba3145c485db180"+ , "69c4bf25d5379c789593e78594b4931e02b497f5d16cfcd44ddf8970fc2271115ced6968"+ , "6c8fe8bf8eda96d66aaf309f650f54c885c396490000620929060bc465123632c892450a"+ , "094e8c1446d1a09113345214c488c3c401db20400b1712dc3844610888a0308502c64461"+ , "98619b068919184ce1805089c26c544671e2482840260160480c904001cc94305b269041"+ , "a4214428308922264c266d81284d0c366c62c211cb8630d238514188095b3689921626e2"+ , "388d10308a9c0010632242caa87003a4719022481cc18861c6480b028192960c8b100152"+ , "406adb846c19054624296603b2600ca76511056658c270d080851c98040a25691925290b"+ , "c7010ba051941264d446818aa4500b022e014231cc14229a382a50144111097140426022"+ , "072183b2688c1624c0c865db84216314125c16214022440b124a24829110a20903184e11"+ , "958184a825d19081d802028c18285286499ba024d9c24d8228469ab000e2924409c4818c"+ , "406c50348c9cb848494464403409cc301120c24863980540144a828684db3232d1b83184"+ , "420161c0658900450b208d08c18400c26d11b56053a42843c8091410261b9311c402600b"+ , "91880c24264a462501818cd9028184286ad4288c6204412146845c126413c00420246940"+ , "10610bb020009508d8107241c40d0c280d932485c032414432718a08405ab445da246c21"+ , "2562cc80208332660b1408004652c30061a30231c19451133305c0a20409166cc4408c12"+ , "4788014701999681c8c46d0ca26c4b368ac19829dab48cd3b68c011284c1a27090086cc2"+ , "34640c174e63120aa22266d18061c4262908474e0bc548d3a26d82184494a8049a804d50"+ , "36052016325cc0450b288d4a86695a2000db084e9c22819b380061a8881c1084c4462660"+ , "c028922252a33872e22672c91042d300264b124ad8927119320e1916325404624c0400c3"+ , "844d648045c0800d030668a4988098887011474d232066db2404a48421832664a2085042"+ , "805101250a5ab405c2a2001b2346a44609a00404a0846594462264c26499a08d92120e80"+ , "107000b800a2184593086a53187204087093060ae1a0881010429826468086410b364804"+ , "b020030266d3a42cd93842129781da9480180785648840e1c22ccab60c6038800cc93089"+ , "188901c9650105825b4681a33861990290ca027159c0045c240009870c42302c8332425b"+ , "2845a4189103c83000142c0812491126520a224804c924c11688dac201113046da84684c"+ , "c40ca2086908b510c8b640d32462c2a888a02665d8a42c09450a1b1464d28620c8386259"+ , "c67110041221352823174900b350121841a3484ca0947054006dd43801e39600112542a1"+ , "a66500c8850aa280d99468424686a30028a4c62d24496508130d61280aa0222d4a068610"+ , "348a8b16118aa8241a14609008000a4365a14849182630d9b2201a19800b34245bc46003"+ , "148201315092420421c1811bc205cb06911a3000013370cc0205e1a888030100c1081201"+ , "c92121170413472c8cc0655b162423434689362c542666980205ca868584946d9c288aa0"+ , "1664dc2442e3402c61c205892080a1008601937011106608014dcc186c99868de4406a41"+ , "080209b9711a46311a2081e3c88924a04d1112100a3101d90630830432e28244d8488643"+ , "144e19116641169109248a13c6900b2841cc146cdab229099268d48405d31251e0008ed1"+ , "38601321519a4670d9464a18148218000e18170e200728cb384402356a89a08141246c0c"+ , "1886024881d3a289429220a0188614446560c870c01010dc300ec2a00c0a1231d8384d19"+ , "4670e1920090322910822de1c008d11804a1102da3028981b231231484a1026ca3842d14"+ , "c7080cb68c11050d6124241b190582102e2440221bb1914c948010969193c45142188e23"+ , "431153c4614c42641ca61093b260042344cc249160a664881260ccb4712015419a80114c"+ , "160550902020b05041a02c1417208400464c264119470513860ce09460e20608a012418c"+ , "4260249030c318255b224619b86980240260101223b4315828495144529312264a8444da"+ , "b408e4b28cc0c8104bb861e4c2093b72853beba635de72bc5dffee98638ac0059b01dfa7"+ , "74f4a518039a355770b5c4e4cc09dcf898d3be26f98fa8e5b00baf0608055b66e90fd213"+ , "87a1e2a59d648650b128672779707d37ffae34d9f861a20f797bef2f191688653e789fdb"+ , "4f8ca396678f1ddd97befb8073e30577ee1ae6c879ec67185fe2511a47497613c3697263"+ , "8ead3a46de50376a11a19166adec634986d8a4331a7fdeb98734b6c1f9850619afa0d513"+ , "3409e2ce3b96a63db20b0e32aeb8f61e9ee7668a40f0bc1929b5a1890e347c91716cc3e1"+ , "9e6ab093215634b293d333c21df77e1fb7df0dc5588dcaccc4de79282e60d10c9a04fa16"+ , "3bee28c2275f0eb8d0ee30f4ed06541f57bef39b0808d14ae28c3585b4e538e87aec5ea8"+ , "b6c1fc19160ec3b4ddfc4f018d408bb5e34a5c691aa2b261e2bec96c66854611ff11f94d"+ , "a992c0db558d2d9851a758ea551a6285c8384b6a5790ff07cf4beabb644048b73b8a63ca"+ , "fa50885da407307d676bdfc01840e1513e47aac4306430f5ca946f3f7fa140bb6726138a"+ , "b29fcb3fbf551c6e97c4d63234d0e8a972cc1cd375422f71d8d3b8dc2eaeed51645196e4"+ , "b8c15a469b77b40aabaa3f0b6b6832f4c415fee3f83b9e02e2649879092bc980cdf39f28"+ , "b67b161f0dc49958fd4d32c2484c3a2732ceed9f2073bbd4247027118ead972ae6a74a58"+ , "a25af79ef715bd285f37602fd1a83605c37c0a39f5bf1b618e571eb96aa65c9162521fb6"+ , "79ef1f45bae2e6647c8ae17a3b3e1050004fb92f9f6cf0f178d65d462e59ff839c139262"+ , "1245da9faabbebe93025bf6522679cc84915a5315639f5d1ead92632975be7f29db03b90"+ , "892de9001dcfa7b8fbb7e35cbb3393b76ba88514f60600b9a6a8d0564643ddca1d6f72ad"+ , "c15389debf65bbb72add4fbe2c440ec6f8479d5b1d721feb0f698de826f1bb64ec2fb85a"+ , "61a9db8c6713cbfdedffa4fce1f403511af4765afb56f88a1664fbe5a37a736d9929ade5"+ , "8b192bf96433950f32385d94a4ea501005057dec3511b30996299c61d354712068eea89c"+ , "2ed098b94c7c353b4546ed9fb0637537155cc9abdf345246e20c7e300e13dad32c8d9f2e"+ , "1aba7ae20c8b90ab84e863a835cbf1a00288f6642a3ac050c91eb0987674dfdca68241b6"+ , "e5caf55937d838b5daeeaca88161a48c3951438d33d34814c9f71b0f5f9d657dd0702101"+ , "b1a5a8f708d0b8a3f19a17baa0d63e695e29c6ce78a2b443e023610b81aca48a8fae237b"+ , "0f82b98e69d89943d560c9731e4b395e4e75714ac915b56cb39cfbe9cf5e1d7ab1531883"+ , "21fead83dafddc45b9fa81f0f088bca3fe32684e290d8461e9514797c238262b1dbe42f0"+ , "827a1336273264fcd7ee59147c7e01e896b03b960b96f67cc3ac63f6dc05df6824069e19"+ , "d9518878a7d8b3f1356c452c5d93ffb0f443ed9180b6a7489c119631388f95eca3ce5ab6"+ , "8ffb56ee5cc8b879ac65ba27cd9819ffaa042f23e9ea3d4a1d00b64e73e14645a682c499"+ , "661760b0571f17c94680c30aab8cf3151405cd852621a23354591d4f0aad3d3a481fd634"+ , "b8521c25c3893f1cb7d34524878273bfce2fd45d134e1a78ece266bcfcfc7b043f49c1c3"+ , "916b2ff2848e93921b2b961d93a60fee7b4b34006a1fc5aac978bfb462a27fed57bcdead"+ , "a5864c8ca3e7e45a6cb1ca1af96ff56fe822dccde098ead28ff581a6ec34119dc44b8449"+ , "08ce63260a2fbcd887cc8557fde512c8048f4481452011f13709bf1919961a7a94d99f3c"+ , "e9c8c8311a12a2f6aae242a80af2971fa541999e33e110945a6f46a1e44f551256c8eefa"+ , "fbded8e69f0796b47a14e10e3d5ce65e9b06ef01fe7fa337b8ffcdc9159995bb1f7940bf"+ , "f9b7f06185c904f8bdd629f00db2fc12e5c53cc8cadff9913d755920fa61d3616fbacb90"+ , "d035a637dd5eaa1497efacb87eb2bc7e9fecf449773e925f51635f2a07d6945a8f270ab9"+ , "b1ebdba6228c8c53dfd1446eaaf2cc5a8887f40d91a97d6d9b0e27846185128c762b3981"+ , "a09619c3201b2dd3eafbfdb9a1863a13bfe97116b32dc8784474a1f5395694a0553a72ae"+ , "038afe497755b627632bd90ce3b3b0a15c5249fad5422a93e6cabc26989d6d586e8f935f"+ , "ccf40115b3fcbcda1c0bcb4d1da8eb5d9df47c940b410cb03c10aac83d11a6fe3066f092"+ , "6b3133e214b978f96902f591a159f3668d1c233122da7c3da21ffa9e8cffc556aa63cc7e"+ , "d7202d228fa149acf595261cd3d6f457bdfdb116a1b139ada16e265d417cca161ea4bfae"+ , "c4f2b1e4d2a1a9557a56f4cb36aba114e4d6279e45028fb25f96bc86546a3d351fb99433"+ , "8e9d108d9a5138badd1eb647a74b231871d8b5a994fa56eeb2e09ac04fd89c8f56c157a5"+ , "f9942c75087887098a5c0e959cf72e11bd9008d8eb57ccc4f9513200da7dbb6b3615efe5"+ , "90e619e1ff90c8781e9bf19d2b7b4e04884827d0bd94b297f4a8638575102dd236eca161"+ , "d9771cebd1cc529d85186c84ec86b0376a3ad7745b58af6bacca35b10fed57a349d2fb22"+ , "7b114ca86716a98de8a2aac6b5f678ecd7127e6a09babb182d8fcaff2394d3f65e98a43b"+ , "4d2f58bd29864f0cb0fbae6b6f1b68077f40cfb553c42d873157215e90437f4a648b2c66"+ , "ae049290f717197fb3f00acb82d659281009a0cf9682faa66fdcd2ad7219fb883a236fe9"+ , "3b7768e8df11ca70d094a9faa2c3a87e0293ec1a2feefdda5ea68a53ddced5f12aaa3820"+ , "6fe739d4ef29555dcc720bddcb0e09a757aca320442add161faf36d306682fc89f3aba84"+ , "b07c0a644602bf236f3fbe558e084b90108e3f16171c5bfab6f2e3d2017ef7b476bb3a0d"+ , "ce25e8398ac291259000dc320418d313d5ecba47a1173dfc66aa40a322076ad79c494f48"+ , "b5b2777545fd37cbdf6a295b0bd55982385e38dd4411cc560acc819a4a7e344befe841a0"+ , "964e84e15a517127fba2325e67223b42a308ef43e0a503cf850663a3986abea5b3ed9235"+ , "231ce6988f83c24fb0156a881f5380fb7aefbd6e8fcbb0cd192a80342ec8b5b1748d38fd"+ , "d62d2275594ac48a7dc4c1069a3663f33e10ec711e5d92506cf1e45f3fe21ace45ed80d5"+ , "c3221141a7308b3ea68263665c54d0cee1d36a49bb35595d0720e78b9b8f4e7f94f99947"+ , "219f8d222e0bcc9198bda51b569d70dd26e278b8c3606694c8e06539b6fdd1bef69ea854"+ , "647041ec4035f77bf13748f90ac6ca018f50af5511fd60f43d40ff0314310be5cfcba12b"+ , "c1fe793e66a5954ef7a7a5e1d5699042e96a3f61d026b35b376dd3f494325bcc3dad2571"+ , "5e40d5b9417a2d9c77b247200901a4b489aad5ab83d89f74da9e88e443d557536fb6c452"+ , "af978e477a87f0bcc8cfc0535a6023d7f92826e5ee1f75e6c81bff20d277555e03d7991b"+ , "3b3924023c33b5f80693bbbc33803a7a26206c654b05f6a39974dfe49b579b44a5f6706b"+ , "4841098b9bcaa215865faf270ff186e1208c4614d1217978f07574d85d2a506ae7f77763"+ , "b9e7419d00c8cee5688f14dd154ec9e7e3975f2119d0e9e64b071a6b80e8cf2c1b233317"+ , "d3a64e8caf73cea3b47f2e1102871d40851230e8f09e908fe86495f69fae0a5301b5b289"+ , "8167960fba4c1de5657ede5c958f3adcec887c9f21dfb25ff58edc283a28fb7f2e84fc2a"+ , "1ca28a4602baf26a71d2eaa5b7c2d5f4573d3329f4ed754893700f6e2379b97bb119de85"+ , "f3bc0367f0a23d31c4b7fc01c93574ffad9ea310d40b747fa12cda69e8da8a57ab09a703"+ , "33e76c02abeb43f92c9b118bf234fb33b2dc82f909fbb07250db23dde3dbc14a799b5fbf"+ , "4d5d2c361c8c21871c958117c1bc76d283c80156ab418848b8c189fc93ece61472e34109"+ , "231c2d888f785510f9b9310c49664bb206a548c8b54fb29bf75fc642ab200c1f6fc07ead"+ , "999fa61bc18ad5daba0e299658dde9865b51e54ff4cfe6cf26b5165884053c0ca7c6ff0d"+ , "0774f623ec012f9ec999e2a9b4e24bc854fa89100e07399e24a22c99945e8f1c1de485e5"+ , "43ee1e4f5f095f91d877d1f456c633f5ab5003ffbcea595827d88263ed67c6dc5f5a1aa4"+ , "0b5150c8f6cb4e336cf7ec2a69937fb9d5e1d56e901d608dc6d29c23e27959813fd678fa"+ , "957806dd510c3f69f8aca976178fe08a3e073272337bd1046885320c811d1e2d6d41d170"+ , "0a4cf6773c2cd62d0d5199ed928277a54eca08a42e9037be611d6e7344570db3cb0d82a0"+ , "d166e1162ea3d1b2d32ad10d90f542b8fa2dd9667c7ba465b7b66cda42d34acf612f7049"+ , "d6c720bb613fcffa6c749275c4326dca1a3c95c98ef3ebd79c2625e9b437e52a737a9c05"+ , "c5402a7ebcb81c71b7f9dff761ff3637a353969ce041397afe201cb5d4e519773b4fb9ab"+ , "5389cd6c43f57f7ddd0ce00e9150d31594b4d4584ed4c4f41d0990ec224bb825ff14046f"+ , "6d854347067607eb6740d578694c4542798bcff502517265b8173b0cd8200ae502321695"+ , "9cc405d2bab2ecd22dfa6a967edb6e84377ee2558177148a28bb7d07c85466ea4ddea4e1"+ , "49c0900217737ef402ca9580325c48e73a1a9c2cd62a6dea57489f0cb8399cd3e4a600a8"+ , "210b80de90e38c6c58d9e67b22788939bf01881afa9d325853a1ec6b783d5ce3bab56ac8"+ , "c967b451ece8e2f611faa073ce978344662d001714b493e7bd23937f5ebba2b3bc873931"+ , "e2f8d27fd01071f0b7cd32980f3438423c86ce2bd56d45665029ee7f3097"+ ]
Tests/Tests.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE ScopedTypeVariables #-}+ module Main where import Test.Tasty@@ -6,31 +7,41 @@ import qualified Data.ByteString as B -import Control.Applicative import Control.Monad -import Data.List (nub, sort)-import Data.ASN1.Types-import Data.X509+import Crypto.Debug (DebugShow (..)) import Crypto.Error (throwCryptoError) import qualified Crypto.PubKey.Curve25519 as X25519-import qualified Crypto.PubKey.Curve448 as X448-import qualified Crypto.PubKey.Ed25519 as Ed25519-import qualified Crypto.PubKey.Ed448 as Ed448-import qualified Crypto.PubKey.RSA as RSA+import qualified Crypto.PubKey.Curve448 as X448 import qualified Crypto.PubKey.DSA as DSA+import qualified Crypto.PubKey.ECC.Types as ECC+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Crypto.PubKey.Ed448 as Ed448+import qualified Crypto.PubKey.MLDSA as MLDSA+import qualified Crypto.PubKey.RSA as RSA+import Data.ASN1.BinaryEncoding (DER (..))+import Data.ASN1.Encoding (decodeASN1', encodeASN1')+import Data.ASN1.Types+import Data.ByteArray (convert)+import Data.List (isInfixOf, nub, sort)+import Data.Proxy (Proxy (..))+import Data.X509 +import MLDSAKeys+ import Data.Hourglass instance Arbitrary RSA.PublicKey where arbitrary = do- bytes <- elements [64,128,256]- e <- elements [0x3,0x10001]- n <- choose (2^(8*(bytes-1)),2^(8*bytes))- return $ RSA.PublicKey { RSA.public_size = bytes- , RSA.public_n = n- , RSA.public_e = e- }+ bytes <- elements [64, 128, 256]+ e <- elements [0x3, 0x10001]+ n <- choose (2 ^ (8 * (bytes - 1)), 2 ^ (8 * bytes))+ return $+ RSA.PublicKey+ { RSA.public_size = bytes+ , RSA.public_n = n+ , RSA.public_e = e+ } instance Arbitrary DSA.Params where arbitrary = DSA.Params <$> arbitrary <*> arbitrary <*> arbitrary@@ -51,24 +62,30 @@ arbitrary = Ed448.toPublic <$> arbitrary instance Arbitrary PubKey where- arbitrary = oneof- [ PubKeyRSA <$> arbitrary- , PubKeyDSA <$> arbitrary- --, PubKeyECDSA ECDSA_Hash_SHA384 <$> (B.pack <$> replicateM 384 arbitrary)- , PubKeyX25519 <$> arbitrary- , PubKeyX448 <$> arbitrary- , PubKeyEd25519 <$> arbitrary- , PubKeyEd448 <$> arbitrary- ]+ arbitrary =+ oneof+ [ PubKeyRSA <$> arbitrary+ , PubKeyDSA <$> arbitrary+ , -- , PubKeyECDSA ECDSA_Hash_SHA384 <$> (B.pack <$> replicateM 384 arbitrary)+ PubKeyX25519 <$> arbitrary+ , PubKeyX448 <$> arbitrary+ , PubKeyEd25519 <$> arbitrary+ , PubKeyEd448 <$> arbitrary+ , PubKeyMLDSA44 . MLDSA.toPublic <$> arbitraryMLDSA (Proxy :: Proxy MLDSA.MLDSA44)+ , PubKeyMLDSA65 . MLDSA.toPublic <$> arbitraryMLDSA (Proxy :: Proxy MLDSA.MLDSA65)+ , PubKeyMLDSA87 . MLDSA.toPublic <$> arbitraryMLDSA (Proxy :: Proxy MLDSA.MLDSA87)+ ] instance Arbitrary RSA.PrivateKey where- arbitrary = RSA.PrivateKey <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary+ arbitrary =+ RSA.PrivateKey+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary instance Arbitrary DSA.PrivateKey where arbitrary = DSA.PrivateKey <$> arbitrary <*> arbitrary@@ -86,56 +103,77 @@ arbitrary = throwCryptoError . Ed448.secretKey <$> arbitraryBS 57 57 instance Arbitrary PrivKey where- arbitrary = oneof- [ PrivKeyRSA <$> arbitrary- , PrivKeyDSA <$> arbitrary- --, PrivKeyECDSA ECDSA_Hash_SHA384 <$> (B.pack <$> replicateM 384 arbitrary)- , PrivKeyX25519 <$> arbitrary- , PrivKeyX448 <$> arbitrary- , PrivKeyEd25519 <$> arbitrary- , PrivKeyEd448 <$> arbitrary- ]+ arbitrary =+ oneof+ [ PrivKeyRSA <$> arbitrary+ , PrivKeyDSA <$> arbitrary+ , -- , PrivKeyECDSA ECDSA_Hash_SHA384 <$> (B.pack <$> replicateM 384 arbitrary)+ PrivKeyX25519 <$> arbitrary+ , PrivKeyX448 <$> arbitrary+ , PrivKeyEd25519 <$> arbitrary+ , PrivKeyEd448 <$> arbitrary+ , PrivKeyMLDSA44 <$> arbitraryPrivMLDSA (Proxy :: Proxy MLDSA.MLDSA44)+ , PrivKeyMLDSA65 <$> arbitraryPrivMLDSA (Proxy :: Proxy MLDSA.MLDSA65)+ , PrivKeyMLDSA87 <$> arbitraryPrivMLDSA (Proxy :: Proxy MLDSA.MLDSA87)+ ] +arbitraryMLDSA :: MLDSA.MLDSA p => proxy p -> Gen (MLDSA.SigningKey p)+arbitraryMLDSA p = snd . throwCryptoError . MLDSA.keyPairFromSeed p <$> arbitraryBS 32 32++-- | All three forms are generated, so that the marshalling round trip+-- covers each of them and not just whichever one this module would pick.+arbitraryPrivMLDSA :: MLDSA.MLDSA p => proxy p -> Gen (PrivKeyMLDSA p)+arbitraryPrivMLDSA p = do+ form <- elements [MLDSAKeySeed, MLDSAKeyExpanded, MLDSAKeyBoth]+ throwCryptoError . privkeyMLDSAFromSeed p form <$> arbitraryBS 32 32+ instance Arbitrary HashALG where- arbitrary = elements [HashMD2,HashMD5,HashSHA1,HashSHA224,HashSHA256,HashSHA384,HashSHA512]+ arbitrary =+ elements+ [HashMD2, HashMD5, HashSHA1, HashSHA224, HashSHA256, HashSHA384, HashSHA512] instance Arbitrary PubKeyALG where- arbitrary = elements [PubKeyALG_RSA,PubKeyALG_DSA,PubKeyALG_EC,PubKeyALG_DH]+ arbitrary = elements [PubKeyALG_RSA, PubKeyALG_DSA, PubKeyALG_EC, PubKeyALG_DH] instance Arbitrary SignatureALG where -- unfortunately as the encoding of this is a single OID as opposed to two OID,- -- the testing need to limit itself to Signature ALG that has been defined in the OID database. + -- the testing need to limit itself to Signature ALG that has been defined in the OID database. -- arbitrary = SignatureALG <$> arbitrary <*> arbitrary- arbitrary = elements- [ SignatureALG HashSHA1 PubKeyALG_RSA- , SignatureALG HashMD5 PubKeyALG_RSA- , SignatureALG HashMD2 PubKeyALG_RSA- , SignatureALG HashSHA256 PubKeyALG_RSA- , SignatureALG HashSHA384 PubKeyALG_RSA- , SignatureALG HashSHA512 PubKeyALG_RSA- , SignatureALG HashSHA224 PubKeyALG_RSA- , SignatureALG HashSHA1 PubKeyALG_DSA- , SignatureALG HashSHA224 PubKeyALG_DSA- , SignatureALG HashSHA256 PubKeyALG_DSA- , SignatureALG HashSHA224 PubKeyALG_EC- , SignatureALG HashSHA256 PubKeyALG_EC- , SignatureALG HashSHA384 PubKeyALG_EC- , SignatureALG HashSHA512 PubKeyALG_EC- , SignatureALG_IntrinsicHash PubKeyALG_Ed25519- , SignatureALG_IntrinsicHash PubKeyALG_Ed448- ]+ arbitrary =+ elements+ [ SignatureALG HashSHA1 PubKeyALG_RSA+ , SignatureALG HashMD5 PubKeyALG_RSA+ , SignatureALG HashMD2 PubKeyALG_RSA+ , SignatureALG HashSHA256 PubKeyALG_RSA+ , SignatureALG HashSHA384 PubKeyALG_RSA+ , SignatureALG HashSHA512 PubKeyALG_RSA+ , SignatureALG HashSHA224 PubKeyALG_RSA+ , SignatureALG HashSHA1 PubKeyALG_DSA+ , SignatureALG HashSHA224 PubKeyALG_DSA+ , SignatureALG HashSHA256 PubKeyALG_DSA+ , SignatureALG HashSHA224 PubKeyALG_EC+ , SignatureALG HashSHA256 PubKeyALG_EC+ , SignatureALG HashSHA384 PubKeyALG_EC+ , SignatureALG HashSHA512 PubKeyALG_EC+ , SignatureALG_IntrinsicHash PubKeyALG_Ed25519+ , SignatureALG_IntrinsicHash PubKeyALG_Ed448+ , SignatureALG_IntrinsicHash PubKeyALG_MLDSA44+ , SignatureALG_IntrinsicHash PubKeyALG_MLDSA65+ , SignatureALG_IntrinsicHash PubKeyALG_MLDSA87+ ] -arbitraryBS r1 r2 = choose (r1,r2) >>= \l -> (B.pack <$> replicateM l arbitrary)+arbitraryBS r1 r2 = choose (r1, r2) >>= \l -> (B.pack <$> replicateM l arbitrary) instance Arbitrary ASN1StringEncoding where- arbitrary = elements [IA5,UTF8]+ arbitrary = elements [IA5, UTF8] instance Arbitrary ASN1CharacterString where arbitrary = ASN1CharacterString <$> arbitrary <*> arbitraryBS 2 36 instance Arbitrary DistinguishedName where- arbitrary = DistinguishedName <$> (choose (1,5) >>= \l -> replicateM l arbitraryDE)- where arbitraryDE = (,) <$> arbitrary <*> arbitrary+ arbitrary = DistinguishedName <$> (choose (1, 5) >>= \l -> replicateM l arbitraryDE)+ where+ arbitraryDE = (,) <$> arbitrary <*> arbitrary instance Arbitrary DateTime where arbitrary = timeConvert <$> (arbitrary :: Gen Elapsed)@@ -143,13 +181,17 @@ arbitrary = Elapsed . Seconds <$> (choose (1, 100000000)) instance Arbitrary Extensions where- arbitrary = Extensions <$> oneof- [ pure Nothing- , Just <$> (listOf1 $ oneof- [ extensionEncode <$> arbitrary <*> (arbitrary :: Gen ExtKeyUsage)- ]- )- ]+ arbitrary =+ Extensions+ <$> oneof+ [ pure Nothing+ , Just+ <$> ( listOf1 $+ oneof+ [ extensionEncode <$> arbitrary <*> (arbitrary :: Gen ExtKeyUsage)+ ]+ )+ ] instance Arbitrary ExtKeyUsageFlag where arbitrary = elements $ enumFrom KeyUsage_digitalSignature@@ -157,67 +199,308 @@ arbitrary = ExtKeyUsage . sort . nub <$> listOf1 arbitrary instance Arbitrary ExtKeyUsagePurpose where- arbitrary = elements [ KeyUsagePurpose_ServerAuth- , KeyUsagePurpose_ClientAuth- , KeyUsagePurpose_CodeSigning- , KeyUsagePurpose_EmailProtection- , KeyUsagePurpose_TimeStamping- , KeyUsagePurpose_OCSPSigning ]+ arbitrary =+ elements+ [ KeyUsagePurpose_ServerAuth+ , KeyUsagePurpose_ClientAuth+ , KeyUsagePurpose_CodeSigning+ , KeyUsagePurpose_EmailProtection+ , KeyUsagePurpose_TimeStamping+ , KeyUsagePurpose_OCSPSigning+ ] instance Arbitrary ExtExtendedKeyUsage where arbitrary = ExtExtendedKeyUsage . nub <$> listOf1 arbitrary instance Arbitrary Certificate where- arbitrary = Certificate <$> pure 2- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary+ arbitrary =+ Certificate+ <$> pure 2+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary instance Arbitrary RevokedCertificate where- arbitrary = RevokedCertificate <$> arbitrary- <*> arbitrary- <*> arbitrary+ arbitrary =+ RevokedCertificate+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary instance Arbitrary CRL where- arbitrary = CRL <$> pure 1- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary+ arbitrary =+ CRL+ <$> pure 1+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary -property_unmarshall_marshall_id :: (Show o, Arbitrary o, ASN1Object o, Eq o) => o -> Bool+property_unmarshall_marshall_id+ :: (Show o, Arbitrary o, ASN1Object o, Eq o) => o -> Bool property_unmarshall_marshall_id o = case got of Right (gotObject, []) | gotObject == o -> True- | otherwise -> error ("object is different: " ++ show gotObject ++ " expecting " ++ show o)- Right (gotObject, l) -> error ("state remaining: " ++ show l ++ " marshalled: " ++ show oMarshalled ++ " parsed: " ++ show gotObject)- Left e -> error ("parsing failed: " ++ show e ++ " object: " ++ show o ++ " marshalled as: " ++ show oMarshalled)- where got = fromASN1 oMarshalled- oMarshalled = toASN1 o []+ | otherwise ->+ error ("object is different: " ++ show gotObject ++ " expecting " ++ show o)+ Right (gotObject, l) ->+ error+ ( "state remaining: "+ ++ show l+ ++ " marshalled: "+ ++ show oMarshalled+ ++ " parsed: "+ ++ show gotObject+ )+ Left e ->+ error+ ( "parsing failed: "+ ++ show e+ ++ " object: "+ ++ show o+ ++ " marshalled as: "+ ++ show oMarshalled+ )+ where+ got = fromASN1 oMarshalled+ oMarshalled = toASN1 o [] +-- | A scalar long enough that finding it in the rendered key cannot be an+-- accident of the curve parameters beside it.+newtype ECSecret = ECSecret Integer+ deriving (Show)++instance Arbitrary ECSecret where+ arbitrary = ECSecret <$> choose (10 ^ (40 :: Int), 10 ^ (41 :: Int))++-- | 'show' of an EC private key holds the curve and not the scalar, and+-- 'debugShow' holds both. The wrapper is checked too, since it is+-- @Credential@ and @ServerParams@ that a program actually prints.+property_ec_show_redacts :: ECSecret -> Bool+property_ec_show_redacts (ECSecret d) = all ok [named, prime]+ where+ named = PrivKeyEC_Named ECC.SEC_p256r1 d+ prime = PrivKeyEC_Prime d 1 2 3 (SerializedPoint B.empty) 4 1 5+ digits = show d+ ok k =+ not (digits `isInfixOf` show k)+ && not (digits `isInfixOf` show (PrivKeyEC k))+ && "<secret>" `isInfixOf` show k+ && digits `isInfixOf` debugShow k+ && digits `isInfixOf` debugShow (PrivKeyEC k)++-- | RFC 9881 Section 6: an ML-DSA-44 private key in PKCS#8 is the seed,+-- tagged [0], the expanded key, or both. All three give one signing key,+-- and each is written back out as what it was read as.+newtype MLDSASeed = MLDSASeed B.ByteString deriving (Show)++instance Arbitrary MLDSASeed where+ arbitrary = MLDSASeed <$> arbitraryBS 32 32++mldsaPKCS8ASN1 :: [ASN1] -> [ASN1]+mldsaPKCS8ASN1 inner =+ [ Start Sequence+ , IntVal 0+ , Start Sequence+ , OID [2, 16, 840, 1, 101, 3, 4, 3, 17]+ , End Sequence+ , OctetString (encodeASN1' DER inner)+ , End Sequence+ ]++mldsaPKCS8 :: [ASN1] -> Either String (PrivKey, [ASN1])+mldsaPKCS8 = fromASN1 . mldsaPKCS8ASN1++mldsaExpanded :: B.ByteString -> B.ByteString+mldsaExpanded seed =+ convert $+ snd $+ throwCryptoError $+ MLDSA.keyPairFromSeed (Proxy :: Proxy MLDSA.MLDSA44) seed++-- | The three encodings of one seed, with the form each of them is.+mldsaForms :: B.ByteString -> [(MLDSAKeyForm, [ASN1])]+mldsaForms seed =+ [ (MLDSAKeySeed, [Other Context 0 seed])+ , (MLDSAKeyExpanded, [OctetString expanded])+ ,+ ( MLDSAKeyBoth+ , [Start Sequence, OctetString seed, OctetString expanded, End Sequence]+ )+ ]+ where+ expanded = mldsaExpanded seed++-- | Each form parses, and all three carry the one signing key the seed+-- expands to. The forms themselves stay apart: the parsed keys are three+-- different values, because they are three different files.+property_mldsa_forms :: MLDSASeed -> Bool+property_mldsa_forms (MLDSASeed seed) =+ map (fmap fst . mldsaPKCS8 . snd) (mldsaForms seed) == map (Right . expected) forms+ && length (nub (map expected forms)) == 3+ where+ forms = map fst (mldsaForms seed)+ expected form =+ PrivKeyMLDSA44 $+ throwCryptoError $+ privkeyMLDSAFromSeed (Proxy :: Proxy MLDSA.MLDSA44) form seed++-- | Reading a key and writing it again gives back the bytes it came from,+-- for each of the three forms. This is what keeping the form is for: a+-- key store that rewrites a file must not turn a seed into an expanded key+-- behind the owner's back.+property_mldsa_form_round_trip :: MLDSASeed -> Bool+property_mldsa_form_round_trip (MLDSASeed seed) = all ok (mldsaForms seed)+ where+ ok (form, inner) = case mldsaPKCS8 inner of+ Right (k, []) ->+ toASN1 k [] == mldsaPKCS8ASN1 inner+ && formOf k == Just form+ _ -> False+ formOf (PrivKeyMLDSA44 k) = Just (privkeyMLDSA_form k)+ formOf _ = Nothing++-- | The seed caught at generation, written out and read back.+--+-- This is the whole point of the two halves: @crypton@'s+-- 'MLDSA.generateKeyPairAndSeed' is the only way to keep the seed a key was+-- made from, and the seed form of RFC 9881 Section 6 is where it goes.+-- Nothing else here generates a key, so without this the two never meet.+--+-- The key the seed expands to is the one that was generated, the file reads+-- back as what was written, and the file is shorter than the expanded key+-- alone -- which is what writing the seed is for.+property_mldsa_generated_seed :: Property+property_mldsa_generated_seed = ioProperty $ do+ (_, sk, seed) <- MLDSA.generateKeyPairAndSeed p+ let k = throwCryptoError $ privkeyMLDSAFromSeed p MLDSAKeySeed seed+ privkey = PrivKeyMLDSA44 k+ encoded = toASN1 privkey []+ return $+ privkeyMLDSA_key k == sk+ && fromASN1 encoded == Right (privkey, [])+ && B.length (encodeASN1' DER encoded) < B.length (convert sk :: B.ByteString)+ where+ p = Proxy :: Proxy MLDSA.MLDSA44++-- | What another implementation writes, this one reads and writes back.+--+-- Everything else here encodes with this library and decodes with it again,+-- which says the two halves agree and nothing about the bytes. These keys+-- were made by OpenSSL 3.6.4, so they say what no round trip can: that a+-- PKCS#8 from the rest of the world parses, and that what goes back out is+-- byte for byte what came in.+--+-- OpenSSL writes the @both@ form, which is also the case that has something+-- to check -- the expanded key must be what the seed expands to, or the+-- parse is refused.+property_mldsa_openssl :: Bool+property_mldsa_openssl = all ok keys+ where+ keys =+ [ (opensslMLDSA44, "ML-DSA-44")+ , (opensslMLDSA65, "ML-DSA-65")+ , (opensslMLDSA87, "ML-DSA-87")+ ]+ ok (der, _) = case decodeASN1' DER der of+ Left _ -> False+ Right asn1 -> case fromASN1 asn1 :: Either String (PrivKey, [ASN1]) of+ Right (k, []) ->+ formOf k == Just MLDSAKeyBoth+ && encodeASN1' DER (toASN1 k []) == der+ _ -> False+ formOf (PrivKeyMLDSA44 k) = Just (privkeyMLDSA_form k)+ formOf (PrivKeyMLDSA65 k) = Just (privkeyMLDSA_form k)+ formOf (PrivKeyMLDSA87 k) = Just (privkeyMLDSA_form k)+ formOf _ = Nothing++-- | 'show' of an ML-DSA private key holds neither the seed nor the key,+-- and 'debugShow' holds both.+property_mldsa_show_redacts :: MLDSASeed -> Bool+property_mldsa_show_redacts (MLDSASeed seed) = all ok (map fst (mldsaForms seed))+ where+ hex = concatMap byte . B.unpack+ byte w = [digit (w `div` 16), digit (w `mod` 16)]+ digit n = "0123456789abcdef" !! fromIntegral n+ ok form =+ let k =+ throwCryptoError $+ privkeyMLDSAFromSeed (Proxy :: Proxy MLDSA.MLDSA44) form seed+ wrapped = PrivKeyMLDSA44 k+ shown = show k ++ show wrapped+ debugged = debugShow k ++ debugShow wrapped+ expanded = hex (mldsaExpanded seed)+ in not (hex seed `isInfixOf` shown)+ && not (expanded `isInfixOf` shown)+ && expanded `isInfixOf` debugged+ && (form == MLDSAKeyExpanded || hex seed `isInfixOf` debugged)++property_mldsa_mismatch :: MLDSASeed -> MLDSASeed -> Property+property_mldsa_mismatch (MLDSASeed seed1) (MLDSASeed seed2) =+ seed1 /= seed2 ==>+ either (const True) (const False) $+ mldsaPKCS8+ [ Start Sequence+ , OctetString seed1+ , OctetString (mldsaExpanded seed2)+ , End Sequence+ ]+ property_extension_id :: (Show e, Eq e, Extension e) => e -> Bool property_extension_id e = case extDecode (extEncode e) of- Left err -> error err- Right v | v == e -> True- | otherwise -> error ("expected " ++ show e ++ " got: " ++ show v)+ Left err -> error err+ Right v+ | v == e -> True+ | otherwise -> error ("expected " ++ show e ++ " got: " ++ show v) -main = defaultMain $ testGroup "X509"- [ testGroup "marshall"- [ testProperty "pubkey" (property_unmarshall_marshall_id :: PubKey -> Bool)- , testProperty "privkey" (property_unmarshall_marshall_id :: PrivKey -> Bool)- , testProperty "signature alg" (property_unmarshall_marshall_id :: SignatureALG -> Bool)- , testGroup "extension"- [ testProperty "key-usage" (property_extension_id :: ExtKeyUsage -> Bool)- , testProperty "extended-key-usage" (property_extension_id :: ExtExtendedKeyUsage -> Bool)+main =+ defaultMain $+ testGroup+ "X509"+ [ testGroup+ "marshall"+ [ testProperty "pubkey" (property_unmarshall_marshall_id :: PubKey -> Bool)+ , testProperty "privkey" (property_unmarshall_marshall_id :: PrivKey -> Bool)+ , testProperty+ "signature alg"+ (property_unmarshall_marshall_id :: SignatureALG -> Bool)+ , testGroup+ "extension"+ [ testProperty "key-usage" (property_extension_id :: ExtKeyUsage -> Bool)+ , testProperty+ "extended-key-usage"+ (property_extension_id :: ExtExtendedKeyUsage -> Bool)+ ]+ , testProperty+ "extensions"+ (property_unmarshall_marshall_id :: Extensions -> Bool)+ , testProperty+ "certificate"+ (property_unmarshall_marshall_id :: Certificate -> Bool)+ , testProperty "crl" (property_unmarshall_marshall_id :: CRL -> Bool)+ ]+ , testGroup+ "show"+ [testProperty "ec privkey is redacted" property_ec_show_redacts]+ , testGroup+ "ML-DSA private key"+ [ testProperty "seed, expandedKey and both" property_mldsa_forms+ , testProperty+ "each form is written back as itself"+ property_mldsa_form_round_trip+ , testProperty+ "a generated seed survives the round trip"+ property_mldsa_generated_seed+ , testProperty+ "a key OpenSSL wrote reads and writes back unchanged"+ (property_mldsa_openssl)+ , testProperty "both refused if they disagree" property_mldsa_mismatch+ , testProperty "is redacted" property_mldsa_show_redacts+ ] ]- , testProperty "extensions" (property_unmarshall_marshall_id :: Extensions -> Bool)- , testProperty "certificate" (property_unmarshall_marshall_id :: Certificate -> Bool)- , testProperty "crl" (property_unmarshall_marshall_id :: CRL -> Bool)- ]- ]
crypton-x509.cabal view
@@ -1,64 +1,74 @@-Name: crypton-x509-version: 1.7.7-Description: X509 reader and writer. please see README-License: BSD3-License-file: LICENSE-Copyright: Vincent Hanquez <vincent@snarc.org>-Author: Vincent Hanquez <vincent@snarc.org>-Maintainer: Kazu Yamamoto <kazu@iij.ad.jp>-Synopsis: X509 reader and writer-Build-Type: Simple-Category: Data-stability: experimental-Homepage: https://github.com/kazu-yamamoto/crypton-certificate-Cabal-Version: >= 1.10+cabal-version: >=1.10+name: crypton-x509+version: 1.10.0+license: BSD3+license-file: LICENSE+copyright: Vincent Hanquez <vincent@snarc.org>+maintainer: Kazu Yamamoto <kazu@iij.ad.jp>+author: Vincent Hanquez <vincent@snarc.org>+stability: experimental+homepage: https://github.com/kazu-yamamoto/crypton-certificate+synopsis: X509 reader and writer+description: X509 reader and writer. please see README+category: Data+build-type: Simple+extra-source-files: ChangeLog.md -Library- Default-Language: Haskell2010- Build-Depends: base >= 4.7 && < 5- , bytestring- , memory- , transformers >= 0.4- , containers- , hourglass- , pem >= 0.1- , asn1-types >= 0.3.1 && < 0.4- , asn1-encoding >= 0.9 && < 0.10- , asn1-parse >= 0.9.3 && < 0.10- , crypton >= 0.24- Exposed-modules: Data.X509- Data.X509.EC- Other-modules: Data.X509.Internal- Data.X509.CertificateChain- Data.X509.AlgorithmIdentifier- Data.X509.DistinguishedName- Data.X509.Cert- Data.X509.PublicKey- Data.X509.PrivateKey- Data.X509.Ext- Data.X509.ExtensionRaw- Data.X509.CRL- Data.X509.OID- Data.X509.Signed- ghc-options: -Wall+source-repository head+ type: git+ location: https://github.com/kazu-yamamoto/crypton-certificate+ subdir: x509 -Test-Suite test-x509- Default-Language: Haskell2010- type: exitcode-stdio-1.0- hs-source-dirs: Tests- Main-is: Tests.hs- Build-Depends: base >= 3 && < 5- , bytestring- , mtl- , tasty- , tasty-quickcheck- , hourglass- , asn1-types- , crypton-x509- , crypton- ghc-options: -Wall -fno-warn-orphans -fno-warn-missing-signatures+library+ exposed-modules:+ Data.X509+ Data.X509.EC -source-repository head- type: git- location: https://github.com/kazu-yamamoto/crypton-certificate- subdir: x509+ other-modules:+ Data.X509.Internal+ Data.X509.CertificateChain+ Data.X509.AlgorithmIdentifier+ Data.X509.DistinguishedName+ Data.X509.Cert+ Data.X509.PublicKey+ Data.X509.PrivateKey+ Data.X509.Ext+ Data.X509.ExtensionRaw+ Data.X509.CRL+ Data.X509.OID+ Data.X509.Signed++ default-language: Haskell2010+ ghc-options: -Wall+ build-depends:+ base >=4.7 && <5,+ bytestring,+ containers,+ crypton >=2.1.8 && <2.3,+ crypton-asn1-encoding >=0.10.0 && <0.11,+ crypton-asn1-parse >=0.10.0 && <0.11,+ crypton-asn1-types >=0.4.1 && <0.5,+ crypton-pem >=0.2.4 && <0.4,+ ram,+ time-hourglass,+ transformers >=0.4++test-suite test-x509+ type: exitcode-stdio-1.0+ main-is: Tests.hs+ other-modules: MLDSAKeys+ hs-source-dirs: Tests+ default-language: Haskell2010+ ghc-options: -Wall -fno-warn-orphans -fno-warn-missing-signatures+ build-depends:+ base >=3 && <5,+ bytestring,+ crypton,+ crypton-x509,+ crypton-asn1-encoding,+ crypton-asn1-types,+ mtl,+ ram,+ tasty,+ tasty-quickcheck,+ time-hourglass