cryptostore 0.1.0.0 → 0.2.0.0
raw patch · 62 files changed
+2216/−821 lines, 62 filesdep +x509-validationdep ~x509PVP ok
version bump matches the API change (PVP)
Dependencies added: x509-validation
Dependency ranges changed: x509
API changes (from Hackage documentation)
- Crypto.Store.CMS: [adContentInfo] :: AuthenticatedData -> ContentInfo
- Crypto.Store.CMS: [ddContentInfo] :: DigestedData -> ContentInfo
- Crypto.Store.CMS: [sdContentInfo] :: SignedData -> ContentInfo
+ Crypto.Store.CMS: Attached :: a -> Encap a
+ Crypto.Store.CMS: Detached :: Encap a
+ Crypto.Store.CMS: Ed25519 :: SignatureAlg
+ Crypto.Store.CMS: Ed448 :: SignatureAlg
+ Crypto.Store.CMS: [SHAKE128] :: KnownNat n => Proxy n -> DigestProxy (SHAKE128 n)
+ Crypto.Store.CMS: [SHAKE128_256] :: DigestProxy (SHAKE128 256)
+ Crypto.Store.CMS: [SHAKE256] :: KnownNat n => Proxy n -> DigestProxy (SHAKE256 n)
+ Crypto.Store.CMS: [SHAKE256_512] :: DigestProxy (SHAKE256 512)
+ Crypto.Store.CMS: [adContentType] :: AuthenticatedData content -> ContentType
+ Crypto.Store.CMS: [adEncapsulatedContent] :: AuthenticatedData content -> content
+ Crypto.Store.CMS: [ddContentType] :: DigestedData content -> ContentType
+ Crypto.Store.CMS: [ddEncapsulatedContent] :: DigestedData content -> content
+ Crypto.Store.CMS: [sdContentType] :: SignedData content -> ContentType
+ Crypto.Store.CMS: [sdEncapsulatedContent] :: SignedData content -> content
+ Crypto.Store.CMS: class Encapsulates struct
+ Crypto.Store.CMS: data Encap a
+ Crypto.Store.CMS: fromAttached :: (MonadFail m, Encapsulates struct) => struct (Encap a) -> m (struct a)
+ Crypto.Store.CMS: fromDetached :: (MonadFail m, Encapsulates struct) => b -> struct (Encap a) -> m (struct b)
+ Crypto.Store.CMS: fromEncap :: b -> (a -> b) -> Encap a -> b
+ Crypto.Store.CMS: isAttached :: Encapsulates struct => struct (Encap a) -> Bool
+ Crypto.Store.CMS: isDetached :: Encapsulates struct => struct (Encap a) -> Bool
+ Crypto.Store.CMS: toAttachedCI :: Encapsulates struct => struct ByteString -> ContentInfo
+ Crypto.Store.CMS: toDetachedCI :: Encapsulates struct => struct ByteString -> (ByteString, ContentInfo)
+ Crypto.Store.CMS: type EncapsulatedContent = ByteString
+ Crypto.Store.Error: DigestMismatch :: StoreError
+ Crypto.Store.Error: SignatureNotVerified :: StoreError
+ Crypto.Store.PKCS12: fromNamedCredential :: String -> Maybe EncryptionScheme -> EncryptionScheme -> Password -> (CertificateChain, PrivKey) -> Either StoreError PKCS12
+ Crypto.Store.PKCS12: toNamedCredential :: String -> PKCS12 -> OptProtected (Maybe (CertificateChain, PrivKey))
+ Crypto.Store.PKCS8: instance Crypto.Store.ASN1.Generate.ASN1Elem e => Crypto.Store.CMS.Util.ProduceASN1Object e (Crypto.Store.PKCS8.Modern Crypto.PubKey.Curve25519.SecretKey)
+ Crypto.Store.PKCS8: instance Crypto.Store.ASN1.Generate.ASN1Elem e => Crypto.Store.CMS.Util.ProduceASN1Object e (Crypto.Store.PKCS8.Modern Crypto.PubKey.Curve448.SecretKey)
+ Crypto.Store.PKCS8: instance Crypto.Store.ASN1.Generate.ASN1Elem e => Crypto.Store.CMS.Util.ProduceASN1Object e (Crypto.Store.PKCS8.Modern Crypto.PubKey.Ed25519.SecretKey)
+ Crypto.Store.PKCS8: instance Crypto.Store.ASN1.Generate.ASN1Elem e => Crypto.Store.CMS.Util.ProduceASN1Object e (Crypto.Store.PKCS8.Modern Crypto.PubKey.Ed448.SecretKey)
+ Crypto.Store.PKCS8: instance GHC.Base.Monoid e => Crypto.Store.CMS.Util.ParseASN1Object e (Crypto.Store.PKCS8.Modern Crypto.PubKey.Curve25519.SecretKey)
+ Crypto.Store.PKCS8: instance GHC.Base.Monoid e => Crypto.Store.CMS.Util.ParseASN1Object e (Crypto.Store.PKCS8.Modern Crypto.PubKey.Curve448.SecretKey)
+ Crypto.Store.PKCS8: instance GHC.Base.Monoid e => Crypto.Store.CMS.Util.ParseASN1Object e (Crypto.Store.PKCS8.Modern Crypto.PubKey.Ed25519.SecretKey)
+ Crypto.Store.PKCS8: instance GHC.Base.Monoid e => Crypto.Store.CMS.Util.ParseASN1Object e (Crypto.Store.PKCS8.Modern Crypto.PubKey.Ed448.SecretKey)
- Crypto.Store.CMS: AuthEnvelopedData :: OriginatorInfo -> [RecipientInfo] -> ContentType -> ASN1ObjectExact AuthContentEncryptionParams -> EncryptedContent -> [Attribute] -> MessageAuthenticationCode -> [Attribute] -> AuthEnvelopedData
+ Crypto.Store.CMS: AuthEnvelopedData :: OriginatorInfo -> [RecipientInfo] -> ContentType -> ASN1ObjectExact AuthContentEncryptionParams -> content -> [Attribute] -> MessageAuthenticationCode -> [Attribute] -> AuthEnvelopedData content
- Crypto.Store.CMS: AuthEnvelopedDataCI :: AuthEnvelopedData -> ContentInfo
+ Crypto.Store.CMS: AuthEnvelopedDataCI :: AuthEnvelopedData (Encap EncryptedContent) -> ContentInfo
- Crypto.Store.CMS: AuthenticatedData :: OriginatorInfo -> [RecipientInfo] -> MACAlgorithm -> Maybe DigestAlgorithm -> ContentInfo -> [Attribute] -> MessageAuthenticationCode -> [Attribute] -> AuthenticatedData
+ Crypto.Store.CMS: AuthenticatedData :: OriginatorInfo -> [RecipientInfo] -> MACAlgorithm -> Maybe DigestAlgorithm -> ContentType -> content -> [Attribute] -> MessageAuthenticationCode -> [Attribute] -> AuthenticatedData content
- Crypto.Store.CMS: AuthenticatedDataCI :: AuthenticatedData -> ContentInfo
+ Crypto.Store.CMS: AuthenticatedDataCI :: AuthenticatedData (Encap EncapsulatedContent) -> ContentInfo
- Crypto.Store.CMS: CBC :: (ContentEncryptionCipher c) -> ContentEncryptionAlg
+ Crypto.Store.CMS: CBC :: ContentEncryptionCipher c -> ContentEncryptionAlg
- Crypto.Store.CMS: CCM :: (ContentEncryptionCipher c) -> AuthContentEncryptionAlg
+ Crypto.Store.CMS: CCM :: ContentEncryptionCipher c -> AuthContentEncryptionAlg
- Crypto.Store.CMS: CFB :: (ContentEncryptionCipher c) -> ContentEncryptionAlg
+ Crypto.Store.CMS: CFB :: ContentEncryptionCipher c -> ContentEncryptionAlg
- Crypto.Store.CMS: CTR :: (ContentEncryptionCipher c) -> ContentEncryptionAlg
+ Crypto.Store.CMS: CTR :: ContentEncryptionCipher c -> ContentEncryptionAlg
- Crypto.Store.CMS: DigestAlgorithm :: (DigestProxy hashAlg) -> DigestAlgorithm
+ Crypto.Store.CMS: DigestAlgorithm :: DigestProxy hashAlg -> DigestAlgorithm
- Crypto.Store.CMS: DigestedData :: DigestProxy hashAlg -> ContentInfo -> Digest hashAlg -> DigestedData
+ Crypto.Store.CMS: DigestedData :: DigestProxy hashAlg -> ContentType -> content -> Digest hashAlg -> DigestedData content
- Crypto.Store.CMS: DigestedDataCI :: DigestedData -> ContentInfo
+ Crypto.Store.CMS: DigestedDataCI :: DigestedData (Encap EncapsulatedContent) -> ContentInfo
- Crypto.Store.CMS: ECB :: (ContentEncryptionCipher c) -> ContentEncryptionAlg
+ Crypto.Store.CMS: ECB :: ContentEncryptionCipher c -> ContentEncryptionAlg
- Crypto.Store.CMS: EncryptedData :: ContentType -> ContentEncryptionParams -> EncryptedContent -> [Attribute] -> EncryptedData
+ Crypto.Store.CMS: EncryptedData :: ContentType -> ContentEncryptionParams -> content -> [Attribute] -> EncryptedData content
- Crypto.Store.CMS: EncryptedDataCI :: EncryptedData -> ContentInfo
+ Crypto.Store.CMS: EncryptedDataCI :: EncryptedData (Encap EncryptedContent) -> ContentInfo
- Crypto.Store.CMS: EnvelopedData :: OriginatorInfo -> [RecipientInfo] -> ContentType -> ContentEncryptionParams -> EncryptedContent -> [Attribute] -> EnvelopedData
+ Crypto.Store.CMS: EnvelopedData :: OriginatorInfo -> [RecipientInfo] -> ContentType -> ContentEncryptionParams -> content -> [Attribute] -> EnvelopedData content
- Crypto.Store.CMS: EnvelopedDataCI :: EnvelopedData -> ContentInfo
+ Crypto.Store.CMS: EnvelopedDataCI :: EnvelopedData (Encap EncryptedContent) -> ContentInfo
- Crypto.Store.CMS: GCM :: (ContentEncryptionCipher c) -> AuthContentEncryptionAlg
+ Crypto.Store.CMS: GCM :: ContentEncryptionCipher c -> AuthContentEncryptionAlg
- Crypto.Store.CMS: HMAC :: (DigestProxy hashAlg) -> MACAlgorithm
+ Crypto.Store.CMS: HMAC :: DigestProxy hashAlg -> MACAlgorithm
- Crypto.Store.CMS: SignedData :: [DigestAlgorithm] -> ContentInfo -> [CertificateChoice] -> [RevocationInfoChoice] -> [SignerInfo] -> SignedData
+ Crypto.Store.CMS: SignedData :: [DigestAlgorithm] -> ContentType -> content -> [CertificateChoice] -> [RevocationInfoChoice] -> [SignerInfo] -> SignedData content
- Crypto.Store.CMS: SignedDataCI :: SignedData -> ContentInfo
+ Crypto.Store.CMS: SignedDataCI :: SignedData (Encap EncapsulatedContent) -> ContentInfo
- Crypto.Store.CMS: [adAuthAttrs] :: AuthenticatedData -> [Attribute]
+ Crypto.Store.CMS: [adAuthAttrs] :: AuthenticatedData content -> [Attribute]
- Crypto.Store.CMS: [adDigestAlgorithm] :: AuthenticatedData -> Maybe DigestAlgorithm
+ Crypto.Store.CMS: [adDigestAlgorithm] :: AuthenticatedData content -> Maybe DigestAlgorithm
- Crypto.Store.CMS: [adMACAlgorithm] :: AuthenticatedData -> MACAlgorithm
+ Crypto.Store.CMS: [adMACAlgorithm] :: AuthenticatedData content -> MACAlgorithm
- Crypto.Store.CMS: [adMAC] :: AuthenticatedData -> MessageAuthenticationCode
+ Crypto.Store.CMS: [adMAC] :: AuthenticatedData content -> MessageAuthenticationCode
- Crypto.Store.CMS: [adOriginatorInfo] :: AuthenticatedData -> OriginatorInfo
+ Crypto.Store.CMS: [adOriginatorInfo] :: AuthenticatedData content -> OriginatorInfo
- Crypto.Store.CMS: [adRecipientInfos] :: AuthenticatedData -> [RecipientInfo]
+ Crypto.Store.CMS: [adRecipientInfos] :: AuthenticatedData content -> [RecipientInfo]
- Crypto.Store.CMS: [adUnauthAttrs] :: AuthenticatedData -> [Attribute]
+ Crypto.Store.CMS: [adUnauthAttrs] :: AuthenticatedData content -> [Attribute]
- Crypto.Store.CMS: [aeAuthAttrs] :: AuthEnvelopedData -> [Attribute]
+ Crypto.Store.CMS: [aeAuthAttrs] :: AuthEnvelopedData content -> [Attribute]
- Crypto.Store.CMS: [aeContentEncryptionParams] :: AuthEnvelopedData -> ASN1ObjectExact AuthContentEncryptionParams
+ Crypto.Store.CMS: [aeContentEncryptionParams] :: AuthEnvelopedData content -> ASN1ObjectExact AuthContentEncryptionParams
- Crypto.Store.CMS: [aeContentType] :: AuthEnvelopedData -> ContentType
+ Crypto.Store.CMS: [aeContentType] :: AuthEnvelopedData content -> ContentType
- Crypto.Store.CMS: [aeEncryptedContent] :: AuthEnvelopedData -> EncryptedContent
+ Crypto.Store.CMS: [aeEncryptedContent] :: AuthEnvelopedData content -> content
- Crypto.Store.CMS: [aeMAC] :: AuthEnvelopedData -> MessageAuthenticationCode
+ Crypto.Store.CMS: [aeMAC] :: AuthEnvelopedData content -> MessageAuthenticationCode
- Crypto.Store.CMS: [aeOriginatorInfo] :: AuthEnvelopedData -> OriginatorInfo
+ Crypto.Store.CMS: [aeOriginatorInfo] :: AuthEnvelopedData content -> OriginatorInfo
- Crypto.Store.CMS: [aeRecipientInfos] :: AuthEnvelopedData -> [RecipientInfo]
+ Crypto.Store.CMS: [aeRecipientInfos] :: AuthEnvelopedData content -> [RecipientInfo]
- Crypto.Store.CMS: [aeUnauthAttrs] :: AuthEnvelopedData -> [Attribute]
+ Crypto.Store.CMS: [aeUnauthAttrs] :: AuthEnvelopedData content -> [Attribute]
- Crypto.Store.CMS: [ddDigestAlgorithm] :: DigestedData -> DigestProxy hashAlg
+ Crypto.Store.CMS: [ddDigestAlgorithm] :: DigestedData content -> DigestProxy hashAlg
- Crypto.Store.CMS: [ddDigest] :: DigestedData -> Digest hashAlg
+ Crypto.Store.CMS: [ddDigest] :: DigestedData content -> Digest hashAlg
- Crypto.Store.CMS: [edContentEncryptionParams] :: EncryptedData -> ContentEncryptionParams
+ Crypto.Store.CMS: [edContentEncryptionParams] :: EncryptedData content -> ContentEncryptionParams
- Crypto.Store.CMS: [edContentType] :: EncryptedData -> ContentType
+ Crypto.Store.CMS: [edContentType] :: EncryptedData content -> ContentType
- Crypto.Store.CMS: [edEncryptedContent] :: EncryptedData -> EncryptedContent
+ Crypto.Store.CMS: [edEncryptedContent] :: EncryptedData content -> content
- Crypto.Store.CMS: [edUnprotectedAttrs] :: EncryptedData -> [Attribute]
+ Crypto.Store.CMS: [edUnprotectedAttrs] :: EncryptedData content -> [Attribute]
- Crypto.Store.CMS: [evContentEncryptionParams] :: EnvelopedData -> ContentEncryptionParams
+ Crypto.Store.CMS: [evContentEncryptionParams] :: EnvelopedData content -> ContentEncryptionParams
- Crypto.Store.CMS: [evContentType] :: EnvelopedData -> ContentType
+ Crypto.Store.CMS: [evContentType] :: EnvelopedData content -> ContentType
- Crypto.Store.CMS: [evEncryptedContent] :: EnvelopedData -> EncryptedContent
+ Crypto.Store.CMS: [evEncryptedContent] :: EnvelopedData content -> content
- Crypto.Store.CMS: [evOriginatorInfo] :: EnvelopedData -> OriginatorInfo
+ Crypto.Store.CMS: [evOriginatorInfo] :: EnvelopedData content -> OriginatorInfo
- Crypto.Store.CMS: [evRecipientInfos] :: EnvelopedData -> [RecipientInfo]
+ Crypto.Store.CMS: [evRecipientInfos] :: EnvelopedData content -> [RecipientInfo]
- Crypto.Store.CMS: [evUnprotectedAttrs] :: EnvelopedData -> [Attribute]
+ Crypto.Store.CMS: [evUnprotectedAttrs] :: EnvelopedData content -> [Attribute]
- Crypto.Store.CMS: [sdCRLs] :: SignedData -> [RevocationInfoChoice]
+ Crypto.Store.CMS: [sdCRLs] :: SignedData content -> [RevocationInfoChoice]
- Crypto.Store.CMS: [sdCertificates] :: SignedData -> [CertificateChoice]
+ Crypto.Store.CMS: [sdCertificates] :: SignedData content -> [CertificateChoice]
- Crypto.Store.CMS: [sdDigestAlgorithms] :: SignedData -> [DigestAlgorithm]
+ Crypto.Store.CMS: [sdDigestAlgorithms] :: SignedData content -> [DigestAlgorithm]
- Crypto.Store.CMS: [sdSignerInfos] :: SignedData -> [SignerInfo]
+ Crypto.Store.CMS: [sdSignerInfos] :: SignedData content -> [SignerInfo]
- Crypto.Store.CMS: authEnvelopData :: Applicative f => OriginatorInfo -> ContentEncryptionKey -> AuthContentEncryptionParams -> [ProducerOfRI f] -> [Attribute] -> [Attribute] -> ContentInfo -> f (Either StoreError ContentInfo)
+ Crypto.Store.CMS: authEnvelopData :: Applicative f => OriginatorInfo -> ContentEncryptionKey -> AuthContentEncryptionParams -> [ProducerOfRI f] -> [Attribute] -> [Attribute] -> ContentInfo -> f (Either StoreError (AuthEnvelopedData EncryptedContent))
- Crypto.Store.CMS: data AuthEnvelopedData
+ Crypto.Store.CMS: data AuthEnvelopedData content
- Crypto.Store.CMS: data AuthenticatedData
+ Crypto.Store.CMS: data AuthenticatedData content
- Crypto.Store.CMS: data DigestedData
+ Crypto.Store.CMS: data DigestedData content
- Crypto.Store.CMS: data EncryptedData
+ Crypto.Store.CMS: data EncryptedData content
- Crypto.Store.CMS: data EnvelopedData
+ Crypto.Store.CMS: data EnvelopedData content
- Crypto.Store.CMS: data SignedData
+ Crypto.Store.CMS: data SignedData content
- Crypto.Store.CMS: decryptData :: ContentEncryptionKey -> EncryptedData -> Either StoreError ContentInfo
+ Crypto.Store.CMS: decryptData :: ContentEncryptionKey -> EncryptedData EncryptedContent -> Either StoreError ContentInfo
- Crypto.Store.CMS: digestData :: DigestAlgorithm -> ContentInfo -> ContentInfo
+ Crypto.Store.CMS: digestData :: DigestAlgorithm -> ContentInfo -> DigestedData EncapsulatedContent
- Crypto.Store.CMS: digestVerify :: DigestedData -> Maybe ContentInfo
+ Crypto.Store.CMS: digestVerify :: DigestedData EncapsulatedContent -> Either StoreError ContentInfo
- Crypto.Store.CMS: encryptData :: ContentEncryptionKey -> ContentEncryptionParams -> [Attribute] -> ContentInfo -> Either StoreError ContentInfo
+ Crypto.Store.CMS: encryptData :: ContentEncryptionKey -> ContentEncryptionParams -> [Attribute] -> ContentInfo -> Either StoreError (EncryptedData EncryptedContent)
- Crypto.Store.CMS: envelopData :: Applicative f => OriginatorInfo -> ContentEncryptionKey -> ContentEncryptionParams -> [ProducerOfRI f] -> [Attribute] -> ContentInfo -> f (Either StoreError ContentInfo)
+ Crypto.Store.CMS: envelopData :: Applicative f => OriginatorInfo -> ContentEncryptionKey -> ContentEncryptionParams -> [ProducerOfRI f] -> [Attribute] -> ContentInfo -> f (Either StoreError (EnvelopedData EncryptedContent))
- Crypto.Store.CMS: generateAuthenticatedData :: Applicative f => OriginatorInfo -> AuthenticationKey -> MACAlgorithm -> Maybe DigestAlgorithm -> [ProducerOfRI f] -> [Attribute] -> [Attribute] -> ContentInfo -> f (Either StoreError ContentInfo)
+ Crypto.Store.CMS: generateAuthenticatedData :: Applicative f => OriginatorInfo -> AuthenticationKey -> MACAlgorithm -> Maybe DigestAlgorithm -> [ProducerOfRI f] -> [Attribute] -> [Attribute] -> ContentInfo -> f (Either StoreError (AuthenticatedData EncapsulatedContent))
- Crypto.Store.CMS: openAuthEnvelopedData :: Monad m => ConsumerOfRI m -> AuthEnvelopedData -> m (Either StoreError ContentInfo)
+ Crypto.Store.CMS: openAuthEnvelopedData :: Monad m => ConsumerOfRI m -> AuthEnvelopedData EncryptedContent -> m (Either StoreError ContentInfo)
- Crypto.Store.CMS: openEnvelopedData :: Monad m => ConsumerOfRI m -> EnvelopedData -> m (Either StoreError ContentInfo)
+ Crypto.Store.CMS: openEnvelopedData :: Monad m => ConsumerOfRI m -> EnvelopedData EncryptedContent -> m (Either StoreError ContentInfo)
- Crypto.Store.CMS: signData :: Applicative f => [ProducerOfSI f] -> ContentInfo -> f (Either StoreError ContentInfo)
+ Crypto.Store.CMS: signData :: Applicative f => [ProducerOfSI f] -> ContentInfo -> f (Either StoreError (SignedData EncapsulatedContent))
- Crypto.Store.CMS: verifyAuthenticatedData :: Monad m => ConsumerOfRI m -> AuthenticatedData -> m (Either StoreError ContentInfo)
+ Crypto.Store.CMS: verifyAuthenticatedData :: Monad m => ConsumerOfRI m -> AuthenticatedData EncapsulatedContent -> m (Either StoreError ContentInfo)
- Crypto.Store.CMS: verifySignedData :: Monad m => ConsumerOfSI m -> SignedData -> m (Maybe ContentInfo)
+ Crypto.Store.CMS: verifySignedData :: Monad m => ConsumerOfSI m -> SignedData EncapsulatedContent -> m (Either StoreError ContentInfo)
- Crypto.Store.PKCS12: CRLBag :: (Bag CRLInfo) -> SafeInfo
+ Crypto.Store.PKCS12: CRLBag :: Bag CRLInfo -> SafeInfo
- Crypto.Store.PKCS12: CertBag :: (Bag CertInfo) -> SafeInfo
+ Crypto.Store.PKCS12: CertBag :: Bag CertInfo -> SafeInfo
- Crypto.Store.PKCS12: KeyBag :: (FormattedKey PrivKey) -> SafeInfo
+ Crypto.Store.PKCS12: KeyBag :: FormattedKey PrivKey -> SafeInfo
- Crypto.Store.PKCS5: CBC :: (ContentEncryptionCipher c) -> ContentEncryptionAlg
+ Crypto.Store.PKCS5: CBC :: ContentEncryptionCipher c -> ContentEncryptionAlg
- Crypto.Store.PKCS5: CFB :: (ContentEncryptionCipher c) -> ContentEncryptionAlg
+ Crypto.Store.PKCS5: CFB :: ContentEncryptionCipher c -> ContentEncryptionAlg
- Crypto.Store.PKCS5: CTR :: (ContentEncryptionCipher c) -> ContentEncryptionAlg
+ Crypto.Store.PKCS5: CTR :: ContentEncryptionCipher c -> ContentEncryptionAlg
- Crypto.Store.PKCS5: ECB :: (ContentEncryptionCipher c) -> ContentEncryptionAlg
+ Crypto.Store.PKCS5: ECB :: ContentEncryptionCipher c -> ContentEncryptionAlg
Files
- ChangeLog.md +32/−1
- README.md +9/−6
- cryptostore.cabal +5/−2
- src/Crypto/Store/ASN1/Parse.hs +2/−2
- src/Crypto/Store/CMS.hs +80/−32
- src/Crypto/Store/CMS/Algorithms.hs +238/−62
- src/Crypto/Store/CMS/AuthEnveloped.hs +4/−4
- src/Crypto/Store/CMS/Authenticated.hs +99/−0
- src/Crypto/Store/CMS/Digested.hs +82/−0
- src/Crypto/Store/CMS/Encrypted.hs +14/−8
- src/Crypto/Store/CMS/Enveloped.hs +9/−11
- src/Crypto/Store/CMS/Info.hs +98/−252
- src/Crypto/Store/CMS/OriginatorInfo.hs +1/−2
- src/Crypto/Store/CMS/Signed.hs +107/−9
- src/Crypto/Store/CMS/Type.hs +39/−0
- src/Crypto/Store/Error.hs +4/−0
- src/Crypto/Store/PKCS12.hs +153/−32
- src/Crypto/Store/PKCS5/PBES1.hs +1/−0
- src/Crypto/Store/PKCS8.hs +116/−7
- src/Crypto/Store/PKCS8/EC.hs +1/−5
- src/Crypto/Store/Util.hs +6/−1
- src/Crypto/Store/X509.hs +2/−5
- tests/CMS/Instances.hs +79/−30
- tests/CMS/Tests.hs +46/−29
- tests/PKCS12/Instances.hs +5/−0
- tests/PKCS12/Tests.hs +16/−7
- tests/PKCS8/Instances.hs +13/−2
- tests/PKCS8/Tests.hs +4/−0
- tests/Util.hs +20/−0
- tests/X509/Instances.hs +78/−7
- tests/X509/Tests.hs +4/−0
- tests/files/cms-signed-data-detached.pem +120/−0
- tests/files/ecdsa-p256-pkcs12.pem +0/−305
- tests/files/ed25519-encrypted-pbes1.pem +32/−0
- tests/files/ed25519-encrypted-pbkdf2.pem +39/−0
- tests/files/ed25519-encrypted-scrypt.pem +18/−0
- tests/files/ed25519-pkcs12.pem +269/−0
- tests/files/ed25519-public.pem +3/−0
- tests/files/ed25519-self-signed-cert.pem +10/−0
- tests/files/ed25519-unencrypted-pkcs8.pem +3/−0
- tests/files/ed25519-unencrypted-trad.pem +3/−0
- tests/files/ed448-encrypted-pbes1.pem +40/−0
- tests/files/ed448-encrypted-pbkdf2.pem +42/−0
- tests/files/ed448-encrypted-scrypt.pem +18/−0
- tests/files/ed448-public.pem +4/−0
- tests/files/ed448-self-signed-cert.pem +11/−0
- tests/files/ed448-unencrypted-pkcs8.pem +4/−0
- tests/files/ed448-unencrypted-trad.pem +4/−0
- tests/files/x25519-encrypted-pbes1.pem +32/−0
- tests/files/x25519-encrypted-pbkdf2.pem +39/−0
- tests/files/x25519-encrypted-scrypt.pem +18/−0
- tests/files/x25519-public.pem +3/−0
- tests/files/x25519-self-signed-cert.pem +9/−0
- tests/files/x25519-unencrypted-pkcs8.pem +3/−0
- tests/files/x25519-unencrypted-trad.pem +3/−0
- tests/files/x448-encrypted-pbes1.pem +40/−0
- tests/files/x448-encrypted-pbkdf2.pem +42/−0
- tests/files/x448-encrypted-scrypt.pem +18/−0
- tests/files/x448-public.pem +4/−0
- tests/files/x448-self-signed-cert.pem +10/−0
- tests/files/x448-unencrypted-pkcs8.pem +4/−0
- tests/files/x448-unencrypted-trad.pem +4/−0
ChangeLog.md view
@@ -1,5 +1,36 @@ # Revision history for cryptostore -## 0.1.0.0 -- 2018-09-23+## 0.2.0.0 - 2019-03-24++* Added functions `toNamedCredential` and `fromNamedCredential` to handle+ PKCS#12 elements with an alias (friendly name).++* Functions `fromCredential` and `fromNamedCredential` now generate PKCS#12+ elements with the `localKeyId` attribute.++* Function `toCredential` is now able to locate the leaf certificate and issuers+ more reliably.++* Algorithms X25519, X448, Ed25519 and Ed448 are now supported.++* CMS functions `digestVerify` and `verifySignedData` now return an `Either`+ instead of a `Maybe`. Errors `DigestMismatch` and `SignatureNotVerified` are+ added to report failures.++* CMS types `SignedData`, `DigestedData` and `AuthenticatedData` now retain the+ encapsulated content in encoded form (with type alias `EncapsulatedContent`)+ instead of a decoded and parsed `ContentInfo`. The `ContentInfo` is parsed+ and provided only when successfully unwrapping the encapsulated type.++* The CMS interface is transformed to support detached content. CMS types now+ have a type parameter to distinguish between a direct reference to the+ encapsulated or encrypted content, and the `Encap` indirection which denotes+ an attached or detached content. Functions building CMS types do not return+ the `ContentInfo` directly anymore, but an intermediate type to be fed into+ `toAttachedCI` or `toDetachedCI`. Reverse transformation is possible with+ utility functions `fromAttached` and `fromDetached` when unwrapping a+ `ContentInfo`.++## 0.1.0.0 - 2018-09-23 * First version. Released on an unsuspecting world.
README.md view
@@ -137,8 +137,6 @@ The API also provides functions to generate/extract a pair containing a private key and a certificate chain. This pair is the type alias `Credential` in `tls`.-Currently the functions assume that the PKCS #12 file contains no other data-than the credential. ```haskell > :set -XOverloadedStrings@@ -170,6 +168,9 @@ > writeP12File "/path/to/newfile.p12" iParams "myintegrityassword" pkcs12' ``` +Variants `toNamedCredential` and `fromNamedCredential` are also available when+PKCS #12 elements need an alias (friendly name).+ ## Cryptographic Message Syntax The API to read and write CMS content is available in `Crypto.Store.CMS`. The@@ -187,7 +188,6 @@ Notable omissions: -* detached content * streaming * compressed data * and S/MIME external format (only PEM is supported, i.e. the textual encoding@@ -217,8 +217,10 @@ > keParams <- generateEncryptionParams (CBC AES128) > let pri = forPasswordRecipient "mypassword" kdf (PWRIKEK keParams) --- Generate the enveloped structure for this single recipient-> Right envelopedCI <- envelopData mempty ceKey ceParams [pri] [] info+-- Generate the enveloped structure for this single recipient. Encrypted+-- content is kept attached in the structure.+> Right envelopedData <- envelopData mempty ceKey ceParams [pri] [] info+> let envelopedCI = toAttachedCI envelopedData > writeCMSFile "/path/to/enveloped.pem" [envelopedCI] ``` @@ -230,7 +232,8 @@ -- Then this recipient just has to read the file and recover enveloped -- content using the password-> [EnvelopedDataCI envelopedData] <- readCMSFile "/path/to/enveloped.pem"+> [EnvelopedDataCI envelopedEncapData] <- readCMSFile "/path/to/enveloped.pem"+> envelopedData <- fromAttached envelopedEncapData > openEnvelopedData (withRecipientPassword "mypassword") envelopedData Right (DataCI "Hi, what will you need from the cryptostore?") ```
cryptostore.cabal view
@@ -1,5 +1,5 @@ name: cryptostore-version: 0.1.0.0+version: 0.2.0.0 synopsis: Serialization of cryptographic data types description: Haskell implementation of PKCS \#8, PKCS \#12 and CMS (Cryptographic Message Syntax).@@ -35,7 +35,9 @@ , Crypto.Store.ASN1.Parse , Crypto.Store.CMS.Algorithms , Crypto.Store.CMS.Attribute+ , Crypto.Store.CMS.Authenticated , Crypto.Store.CMS.AuthEnveloped+ , Crypto.Store.CMS.Digested , Crypto.Store.CMS.Encrypted , Crypto.Store.CMS.Enveloped , Crypto.Store.CMS.Info@@ -59,7 +61,8 @@ , asn1-types >= 0.3.1 && < 0.4 , asn1-encoding >= 0.9 && < 0.10 , hourglass >= 0.2- , x509 >= 1.7.3+ , x509 >= 1.7.5+ , x509-validation >= 1.5 default-language: Haskell2010 ghc-options: -Wall
src/Crypto/Store/ASN1/Parse.hs view
@@ -43,7 +43,7 @@ import Control.Applicative import Control.Arrow (first) import Control.Monad (MonadPlus(..), liftM2)-import Control.Monad.Fail+import Control.Monad.Fail as Fail data State e = State [(ASN1, e)] !e @@ -70,7 +70,7 @@ case runP m1 s of Left err -> Left err Right (a, s2) -> runP (m2 a) s2- fail = throwParseError+ fail = Fail.fail instance MonadFail (ParseASN1 e) where fail = throwParseError instance MonadPlus (ParseASN1 e) where
src/Crypto/Store/CMS.hs view
@@ -19,6 +19,8 @@ -- * <https://tools.ietf.org/html/rfc5084 RFC 5084>: Using AES-CCM and AES-GCM Authenticated Encryption in the Cryptographic Message Syntax (CMS) -- * <https://tools.ietf.org/html/rfc6476 RFC 6476>: Using Message Authentication Code (MAC) Encryption in the Cryptographic Message Syntax (CMS) -- * <https://tools.ietf.org/html/rfc8103 RFC 8103>: Using ChaCha20-Poly1305 Authenticated Encryption in the Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc8418 RFC 8418>: Use of the Elliptic Curve Diffie-Hellman Key Agreement Algorithm with X25519 and X448 in the Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc8419 RFC 8419>: Use of Edwards-Curve Digital Signature Algorithm (EdDSA) Signatures in the Cryptographic Message Syntax (CMS) {-# LANGUAGE RecordWildCards #-} module Crypto.Store.CMS ( ContentType(..)@@ -26,9 +28,20 @@ , getContentType -- * Reading and writing PEM files , module Crypto.Store.CMS.PEM+ -- * Content encapsulation+ , Encap(..)+ , fromEncap+ , Encapsulates+ , isAttached+ , fromAttached+ , toAttachedCI+ , isDetached+ , fromDetached+ , toDetachedCI -- * Signed data , SignatureValue , SignatureAlg(..)+ , EncapsulatedContent , SignedData(..) , ProducerOfSI , ConsumerOfSI@@ -75,7 +88,7 @@ , forKeyRecipient , withRecipientKey -- ** Password recipients- , PasswordRecipientInfo(..)+ , PasswordRecipientInfo(..) , forPasswordRecipient , withRecipientPassword -- * Digested data@@ -141,14 +154,20 @@ , ASN1ObjectExact ) where +import Data.ASN1.BinaryEncoding+import Data.ASN1.Encoding+import Data.ByteString (ByteString) import Data.Maybe (isJust) import Data.List (nub, unzip3) import Crypto.Hash +import Crypto.Store.ASN1.Parse import Crypto.Store.CMS.Algorithms import Crypto.Store.CMS.Attribute+import Crypto.Store.CMS.Authenticated import Crypto.Store.CMS.AuthEnveloped+import Crypto.Store.CMS.Digested import Crypto.Store.CMS.Encrypted import Crypto.Store.CMS.Enveloped import Crypto.Store.CMS.OriginatorInfo@@ -158,25 +177,27 @@ import Crypto.Store.CMS.Type import Crypto.Store.CMS.Util import Crypto.Store.Error+import Crypto.Store.Util -- DigestedData -- | Add a digested-data layer on the specified content info.-digestData :: DigestAlgorithm -> ContentInfo -> ContentInfo-digestData (DigestAlgorithm alg) ci = DigestedDataCI dd+digestData :: DigestAlgorithm -> ContentInfo -> DigestedData EncapsulatedContent+digestData (DigestAlgorithm alg) ci = dd where dd = DigestedData { ddDigestAlgorithm = alg- , ddContentInfo = ci- , ddDigest = hash (encapsulate ci)+ , ddContentType = getContentType ci+ , ddEncapsulatedContent = encapsulate ci+ , ddDigest = hash (encapsulate ci) } -- | Return the inner content info but only if the digest is valid.-digestVerify :: DigestedData -> Maybe ContentInfo+digestVerify :: DigestedData EncapsulatedContent -> Either StoreError ContentInfo digestVerify DigestedData{..} =- if ddDigest == hash (encapsulate ddContentInfo)- then Just ddContentInfo- else Nothing+ if ddDigest == hash ddEncapsulatedContent+ then decapsulate ddContentType ddEncapsulatedContent+ else Left DigestMismatch -- EncryptedData@@ -189,9 +210,9 @@ -> ContentEncryptionParams -> [Attribute] -> ContentInfo- -> Either StoreError ContentInfo+ -> Either StoreError (EncryptedData EncryptedContent) encryptData key params attrs ci =- EncryptedDataCI . build <$> contentEncrypt key params (encapsulate ci)+ build <$> contentEncrypt key params (encapsulate ci) where build ec = EncryptedData { edContentType = getContentType ci@@ -202,7 +223,7 @@ -- | Decrypt an encrypted content info using the specified key. decryptData :: ContentEncryptionKey- -> EncryptedData+ -> EncryptedData EncryptedContent -> Either StoreError ContentInfo decryptData key EncryptedData{..} = do decrypted <- contentDecrypt key edContentEncryptionParams edEncryptedContent@@ -223,12 +244,12 @@ -> [ProducerOfRI f] -> [Attribute] -> ContentInfo- -> f (Either StoreError ContentInfo)+ -> f (Either StoreError (EnvelopedData EncryptedContent)) envelopData oinfo key params envFns attrs ci = f <$> (sequence <$> traverse ($ key) envFns) where ebs = contentEncrypt key params (encapsulate ci)- f ris = EnvelopedDataCI <$> (build <$> ebs <*> ris)+ f ris = build <$> ebs <*> ris build bs ris = EnvelopedData { evOriginatorInfo = oinfo , evRecipientInfos = ris@@ -242,7 +263,7 @@ -- function. openEnvelopedData :: Monad m => ConsumerOfRI m- -> EnvelopedData+ -> EnvelopedData EncryptedContent -> m (Either StoreError ContentInfo) openEnvelopedData devFn EnvelopedData{..} = do r <- riAttempts (map (fmap (>>= decr) . devFn) evRecipientInfos)@@ -273,7 +294,7 @@ -> [Attribute] -> [Attribute] -> ContentInfo- -> f (Either StoreError ContentInfo)+ -> f (Either StoreError (AuthenticatedData EncapsulatedContent)) generateAuthenticatedData oinfo key macAlg digAlg envFns aAttrs uAttrs ci = f <$> (sequence <$> traverse ($ key) envFns) where@@ -289,13 +310,14 @@ in (l, encodeAuthAttrs l) ebs = mac macAlg key input- f ris = AuthenticatedDataCI <$> (build ebs <$> ris)+ f ris = build ebs <$> ris build authTag ris = AuthenticatedData { adOriginatorInfo = oinfo , adRecipientInfos = ris , adMACAlgorithm = macAlg , adDigestAlgorithm = digAlg- , adContentInfo = ci+ , adContentType = getContentType ci+ , adEncapsulatedContent = encapsulate ci , adAuthAttrs = aAttrs' , adMAC = authTag , adUnauthAttrs = uAttrs@@ -306,13 +328,13 @@ -- could be verified. verifyAuthenticatedData :: Monad m => ConsumerOfRI m- -> AuthenticatedData+ -> AuthenticatedData EncapsulatedContent -> m (Either StoreError ContentInfo) verifyAuthenticatedData devFn AuthenticatedData{..} = riAttempts (map (fmap (>>= unwrap) . devFn) adRecipientInfos) where- msg = encapsulate adContentInfo- ct = getContentType adContentInfo+ msg = adEncapsulatedContent+ ct = adContentType noAttr = null adAuthAttrs mdMatch = case adDigestAlgorithm of@@ -327,7 +349,7 @@ | isJust adDigestAlgorithm && noAttr = Left (InvalidInput "Missing auth attributes") | not noAttr && not attrMatch = Left (InvalidInput "Invalid auth attributes") | adMAC /= mac adMACAlgorithm k input = Left BadContentMAC- | otherwise = Right adContentInfo+ | otherwise = decapsulate adContentType adEncapsulatedContent -- AuthEnvelopedData@@ -347,14 +369,14 @@ -> [Attribute] -> [Attribute] -> ContentInfo- -> f (Either StoreError ContentInfo)+ -> f (Either StoreError (AuthEnvelopedData EncryptedContent)) authEnvelopData oinfo key params envFns aAttrs uAttrs ci = f <$> (sequence <$> traverse ($ key) envFns) where raw = encodeASN1Object params aad = encodeAuthAttrs aAttrs ebs = authContentEncrypt key params raw aad (encapsulate ci)- f ris = AuthEnvelopedDataCI <$> (build <$> ebs <*> ris)+ f ris = build <$> ebs <*> ris build (authTag, bs) ris = AuthEnvelopedData { aeOriginatorInfo = oinfo , aeRecipientInfos = ris@@ -370,7 +392,7 @@ -- 'ConsumerOfRI' function. openAuthEnvelopedData :: Monad m => ConsumerOfRI m- -> AuthEnvelopedData+ -> AuthEnvelopedData EncryptedContent -> m (Either StoreError ContentInfo) openAuthEnvelopedData devFn AuthEnvelopedData{..} = do r <- riAttempts (map (fmap (>>= decr) . devFn) aeRecipientInfos)@@ -389,18 +411,19 @@ -- processed by one or several 'ProducerOfSI' functions to create signer info -- elements. signData :: Applicative f- => [ProducerOfSI f] -> ContentInfo -> f (Either StoreError ContentInfo)+ => [ProducerOfSI f] -> ContentInfo -> f (Either StoreError (SignedData EncapsulatedContent)) signData sigFns ci = f <$> (sequence <$> traverse (\fn -> fn ct msg) sigFns) where msg = encapsulate ci ct = getContentType ci- f = fmap (SignedDataCI . build . unzip3)+ f = fmap (build . unzip3) build (sis, certLists, crlLists) = SignedData { sdDigestAlgorithms = nub (map siDigestAlgorithm sis)- , sdContentInfo = ci+ , sdContentType = getContentType ci+ , sdEncapsulatedContent = encapsulate ci , sdCertificates = concat certLists , sdCRLs = concat crlLists , sdSignerInfos = sis@@ -410,14 +433,15 @@ -- Verification of at least one signer info must be successful in order to -- return the inner content info. verifySignedData :: Monad m- => ConsumerOfSI m -> SignedData -> m (Maybe ContentInfo)+ => ConsumerOfSI m -> SignedData EncapsulatedContent -> m (Either StoreError ContentInfo) verifySignedData verFn SignedData{..} = f <$> siAttemps valid sdSignerInfos where- msg = encapsulate sdContentInfo- ct = getContentType sdContentInfo+ msg = sdEncapsulatedContent+ ct = sdContentType valid si = verFn ct msg si sdCertificates sdCRLs- f bool = if bool then Just sdContentInfo else Nothing+ f bool = if bool then decapsulate sdContentType sdEncapsulatedContent+ else Left SignatureNotVerified -- Utilities@@ -437,3 +461,27 @@ siAttemps _ [] = pure False siAttemps f (x:xs) = f x >>= orTail where orTail bool = if bool then return True else siAttemps f xs++decode :: ParseASN1 [ASN1Event] a -> ByteString -> Either StoreError a+decode parser bs = vals >>= mapLeft ParseFailure . runParseASN1_ parser+ where vals = mapLeft DecodingError (decodeASN1Repr' BER bs)++-- | Encode the information for encapsulation in another content info.+encapsulate :: ContentInfo -> ByteString+encapsulate (DataCI bs) = bs+encapsulate (SignedDataCI ed) = encodeASN1Object ed+encapsulate (EnvelopedDataCI ed) = encodeASN1Object ed+encapsulate (DigestedDataCI dd) = encodeASN1Object dd+encapsulate (EncryptedDataCI ed) = encodeASN1Object ed+encapsulate (AuthenticatedDataCI ad) = encodeASN1Object ad+encapsulate (AuthEnvelopedDataCI ae) = encodeASN1Object ae++-- | Decode the information from encapsulated content.+decapsulate :: ContentType -> ByteString -> Either StoreError ContentInfo+decapsulate DataType bs = pure (DataCI bs)+decapsulate SignedDataType bs = SignedDataCI <$> decode parse bs+decapsulate EnvelopedDataType bs = EnvelopedDataCI <$> decode parse bs+decapsulate DigestedDataType bs = DigestedDataCI <$> decode parse bs+decapsulate EncryptedDataType bs = EncryptedDataCI <$> decode parse bs+decapsulate AuthenticatedDataType bs = AuthenticatedDataCI <$> decode parse bs+decapsulate AuthEnvelopedDataType bs = AuthEnvelopedDataCI <$> decode parse bs
src/Crypto/Store/CMS/Algorithms.hs view
@@ -6,6 +6,7 @@ -- Portability : unknown -- -- Cryptographic Message Syntax algorithms+{-# LANGUAGE DataKinds #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}@@ -60,7 +61,9 @@ , transportEncrypt , transportDecrypt , KeyAgreementParams(..)+ , ECDHPair , ecdhGenerate+ , ecdhPublic , ecdhEncrypt , ecdhDecrypt , MaskGenerationFunc(..)@@ -86,10 +89,13 @@ import qualified Data.ByteArray as B import Data.ByteString (ByteString) import Data.Maybe (fromMaybe)+import Data.Proxy import Data.Word import qualified Data.X509 as X509 import Data.X509.EC +import GHC.TypeLits+ import qualified Crypto.Cipher.AES as Cipher import qualified Crypto.Cipher.CAST5 as Cipher import qualified Crypto.Cipher.Camellia as Cipher@@ -98,6 +104,7 @@ import qualified Crypto.Cipher.TripleDES as Cipher import Crypto.Cipher.Types import Crypto.Data.Padding+import Crypto.ECC (Curve_X25519, Curve_X448, ecdh) import Crypto.Error import qualified Crypto.Hash as Hash import qualified Crypto.KDF.PBKDF2 as PBKDF2@@ -105,10 +112,14 @@ import qualified Crypto.MAC.HMAC as HMAC import qualified Crypto.MAC.Poly1305 as Poly1305 import Crypto.Number.Serialize+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.DH as ECDH import qualified Crypto.PubKey.ECC.ECDSA as ECDSA 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.MaskGenFunction as MGF import qualified Crypto.PubKey.RSA.PSS as RSAPSS import qualified Crypto.PubKey.RSA.OAEP as RSAOAEP@@ -126,6 +137,7 @@ import qualified Crypto.Store.KeyWrap.AES as AES_KW import qualified Crypto.Store.KeyWrap.TripleDES as TripleDES_KW import qualified Crypto.Store.KeyWrap.RC2 as RC2_KW+import Crypto.Store.PKCS8.EC import Crypto.Store.Util @@ -150,6 +162,14 @@ SHA384 :: DigestProxy Hash.SHA384 -- | SHA-512 SHA512 :: DigestProxy Hash.SHA512+ -- | SHAKE128 (256 bits)+ SHAKE128_256 :: DigestProxy (Hash.SHAKE128 256)+ -- | SHAKE256 (512 bits)+ SHAKE256_512 :: DigestProxy (Hash.SHAKE256 512)+ -- | SHAKE128 (variable size)+ SHAKE128 :: KnownNat n => Proxy n -> DigestProxy (Hash.SHAKE128 n)+ -- | SHAKE256 (variable size)+ SHAKE256 :: KnownNat n => Proxy n -> DigestProxy (Hash.SHAKE256 n) deriving instance Show (DigestProxy hashAlg) deriving instance Eq (DigestProxy hashAlg)@@ -171,43 +191,95 @@ DigestAlgorithm SHA256 == DigestAlgorithm SHA256 = True DigestAlgorithm SHA384 == DigestAlgorithm SHA384 = True DigestAlgorithm SHA512 == DigestAlgorithm SHA512 = True+ DigestAlgorithm SHAKE128_256 == DigestAlgorithm SHAKE128_256 = True+ DigestAlgorithm SHAKE256_512 == DigestAlgorithm SHAKE256_512 = True+ DigestAlgorithm (SHAKE128 a) == DigestAlgorithm (SHAKE128 b) = natVal a == natVal b+ DigestAlgorithm (SHAKE256 a) == DigestAlgorithm (SHAKE256 b) = natVal a == natVal b _ == _ = False -instance Enumerable DigestAlgorithm where- values = [ DigestAlgorithm MD2- , DigestAlgorithm MD4- , DigestAlgorithm MD5- , DigestAlgorithm SHA1- , DigestAlgorithm SHA224- , DigestAlgorithm SHA256- , DigestAlgorithm SHA384- , DigestAlgorithm SHA512+data DigestType+ = Type_MD2+ | Type_MD4+ | Type_MD5+ | Type_SHA1+ | Type_SHA224+ | Type_SHA256+ | Type_SHA384+ | Type_SHA512+ | Type_SHAKE128_256+ | Type_SHAKE256_512+ | Type_SHAKE128_Len+ | Type_SHAKE256_Len++instance Enumerable DigestType where+ values = [ Type_MD2+ , Type_MD4+ , Type_MD5+ , Type_SHA1+ , Type_SHA224+ , Type_SHA256+ , Type_SHA384+ , Type_SHA512+ , Type_SHAKE128_256+ , Type_SHAKE256_512+ , Type_SHAKE128_Len+ , Type_SHAKE256_Len ] -instance OIDable DigestAlgorithm where- getObjectID (DigestAlgorithm MD2) = [1,2,840,113549,2,2]- getObjectID (DigestAlgorithm MD4) = [1,2,840,113549,2,4]- getObjectID (DigestAlgorithm MD5) = [1,2,840,113549,2,5]- getObjectID (DigestAlgorithm SHA1) = [1,3,14,3,2,26]- getObjectID (DigestAlgorithm SHA224) = [2,16,840,1,101,3,4,2,4]- getObjectID (DigestAlgorithm SHA256) = [2,16,840,1,101,3,4,2,1]- getObjectID (DigestAlgorithm SHA384) = [2,16,840,1,101,3,4,2,2]- getObjectID (DigestAlgorithm SHA512) = [2,16,840,1,101,3,4,2,3]+instance OIDable DigestType where+ getObjectID Type_MD2 = [1,2,840,113549,2,2]+ getObjectID Type_MD4 = [1,2,840,113549,2,4]+ getObjectID Type_MD5 = [1,2,840,113549,2,5]+ getObjectID Type_SHA1 = [1,3,14,3,2,26]+ getObjectID Type_SHA224 = [2,16,840,1,101,3,4,2,4]+ getObjectID Type_SHA256 = [2,16,840,1,101,3,4,2,1]+ getObjectID Type_SHA384 = [2,16,840,1,101,3,4,2,2]+ getObjectID Type_SHA512 = [2,16,840,1,101,3,4,2,3]+ getObjectID Type_SHAKE128_256 = [2,16,840,1,101,3,4,2,11]+ getObjectID Type_SHAKE256_512 = [2,16,840,1,101,3,4,2,12]+ getObjectID Type_SHAKE128_Len = [2,16,840,1,101,3,4,2,17]+ getObjectID Type_SHAKE256_Len = [2,16,840,1,101,3,4,2,18] -instance OIDNameable DigestAlgorithm where+instance OIDNameable DigestType where fromObjectID oid = unOIDNW <$> fromObjectID oid instance AlgorithmId DigestAlgorithm where- type AlgorithmType DigestAlgorithm = DigestAlgorithm+ type AlgorithmType DigestAlgorithm = DigestType algorithmName _ = "digest algorithm"- algorithmType = id - -- MD5 has NULL parameter, other algorithms have no parameter- parameterASN1S (DigestAlgorithm MD5) = gNull- parameterASN1S _ = id+ algorithmType (DigestAlgorithm MD2) = Type_MD2+ algorithmType (DigestAlgorithm MD4) = Type_MD4+ algorithmType (DigestAlgorithm MD5) = Type_MD5+ algorithmType (DigestAlgorithm SHA1) = Type_SHA1+ algorithmType (DigestAlgorithm SHA224) = Type_SHA224+ algorithmType (DigestAlgorithm SHA256) = Type_SHA256+ algorithmType (DigestAlgorithm SHA384) = Type_SHA384+ algorithmType (DigestAlgorithm SHA512) = Type_SHA512+ algorithmType (DigestAlgorithm SHAKE128_256) = Type_SHAKE128_256+ algorithmType (DigestAlgorithm SHAKE256_512) = Type_SHAKE256_512+ algorithmType (DigestAlgorithm (SHAKE128 _)) = Type_SHAKE128_Len+ algorithmType (DigestAlgorithm (SHAKE256 _)) = Type_SHAKE256_Len - parseParameter p = getNextMaybe nullOrNothing >> return p+ -- MD5 has NULL parameter, SHAKE128 and SHAKE256 have the bitsize as+ -- parameter, other algorithms have no parameter+ parameterASN1S (DigestAlgorithm MD5) = gNull+ parameterASN1S (DigestAlgorithm (SHAKE128 n)) = gIntVal (natVal n)+ parameterASN1S (DigestAlgorithm (SHAKE256 n)) = gIntVal (natVal n)+ parameterASN1S _ = id + parseParameter Type_MD2 = parseDigestParam (DigestAlgorithm MD2)+ parseParameter Type_MD4 = parseDigestParam (DigestAlgorithm MD4)+ parseParameter Type_MD5 = parseDigestParam (DigestAlgorithm MD5)+ parseParameter Type_SHA1 = parseDigestParam (DigestAlgorithm SHA1)+ parseParameter Type_SHA224 = parseDigestParam (DigestAlgorithm SHA224)+ parseParameter Type_SHA256 = parseDigestParam (DigestAlgorithm SHA256)+ parseParameter Type_SHA384 = parseDigestParam (DigestAlgorithm SHA384)+ parseParameter Type_SHA512 = parseDigestParam (DigestAlgorithm SHA512)+ parseParameter Type_SHAKE128_256 = parseDigestParam (DigestAlgorithm SHAKE128_256)+ parseParameter Type_SHAKE256_512 = parseDigestParam (DigestAlgorithm SHAKE256_512)+ parseParameter Type_SHAKE128_Len = parseBitLen 256 (DigestAlgorithm $ SHAKE128 p256)+ parseParameter Type_SHAKE256_Len = parseBitLen 512 (DigestAlgorithm $ SHAKE256 p512)+ -- | Compute the digest of a message. digest :: ByteArrayAccess message => DigestAlgorithm -> message -> ByteString digest (DigestAlgorithm hashAlg) message = B.convert (doHash hashAlg message)@@ -219,7 +291,25 @@ hashFromProxy :: proxy a -> a hashFromProxy _ = undefined +parseDigestParam :: Monoid e => DigestAlgorithm -> ParseASN1 e DigestAlgorithm+parseDigestParam p = getNextMaybe nullOrNothing >> return p +parseBitLen :: Monoid e => Integer -> a -> ParseASN1 e a+parseBitLen expected res = do+ IntVal n <- getNext+ if n == expected+ then return res+ else throwParseError ("Invalid bit length: " ++ show n)+ -- FIXME: should allow any input size but cryptonite requires+ -- divisibility by 8. For now we restrict to 256/512 bits only.++p256 :: Proxy 256+p256 = Proxy++p512 :: Proxy 512+p512 = Proxy++ -- Cipher-like things -- | Algorithms that are based on a secret key. This includes ciphers but also@@ -1497,57 +1587,97 @@ (gOctetString $ toWord32 outBits) in encodeASN1S $ asn1Container Sequence (ki . eui . spi) +-- | Key pair for ECDH.+data ECDHPair+ = PairEC ECC.Curve ECC.PrivateNumber ECC.Point+ | PairX25519 X25519.SecretKey X25519.PublicKey+ | PairX448 X448.SecretKey X448.PublicKey+ -- | Generate an ephemeral ECDH key.-ecdhGenerate :: MonadRandom m => X509.PubKey -> m (Either StoreError (ECC.Curve, ECC.PrivateNumber, X509.SerializedPoint))+ecdhGenerate :: MonadRandom m => X509.PubKey -> m (Either StoreError ECDHPair) ecdhGenerate (X509.PubKeyEC pub) = case ecPubKeyCurveName pub of Nothing -> return $ Left NamedCurveRequired Just n -> do let curve = ECC.getCurveByName n priv <- ECDH.generatePrivate curve- return $ Right (curve, priv, X509.pubkeyEC_pub pub)+ return $ case unserializePoint curve (X509.pubkeyEC_pub pub) of+ Nothing -> Left (InvalidInput "Invalid serialized point")+ Just pt -> Right (PairEC curve priv pt)+ecdhGenerate (X509.PubKeyX25519 pub) = do+ priv <- X25519.generateSecretKey+ return $ Right (PairX25519 priv pub)+ecdhGenerate (X509.PubKeyX448 pub) = do+ priv <- X448.generateSecretKey+ return $ Right (PairX448 priv pub) ecdhGenerate _ = return $ Left UnexpectedPublicKeyType +-- | Return the serialized public key corresponding to the ECDH private key.+ecdhPublic :: ECDHPair -> ByteString+ecdhPublic (PairEC curve d _) = unSerialize (getSerializedPoint curve d)+ where unSerialize (X509.SerializedPoint pt) = pt+ecdhPublic (PairX25519 priv _) = B.convert (X25519.toPublic priv)+ecdhPublic (PairX448 priv _) = B.convert (X448.toPublic priv)+ -- | Encrypt the specified content with an ECDH key pair and key-agreement -- algorithm. ecdhEncrypt :: (MonadRandom m, ByteArray ba)- => KeyAgreementParams -> Maybe ByteString -> ECC.Curve -> ECC.PrivateNumber -> X509.SerializedPoint -> ba -> m (Either StoreError ba)-ecdhEncrypt (StdDH dig kep) ukm curve d pt bs =- case unserializePoint curve pt of- Nothing -> return $ Left (InvalidInput "Invalid serialized point")- Just pub -> do- let s = ECDH.getShared curve d pub- k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes- keyEncrypt k kep bs-ecdhEncrypt (CofactorDH dig kep) ukm curve d pt bs =- case unserializePoint curve pt of- Nothing -> return $ Left (InvalidInput "Invalid serialized point")- Just pub -> do- let h = ECC.ecc_h (ECC.common_curve curve)- s = ECDH.getShared curve (h * d) pub- k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes- keyEncrypt k kep bs+ => KeyAgreementParams -> Maybe ByteString -> ECDHPair -> ba -> m (Either StoreError ba)+ecdhEncrypt (StdDH dig kep) ukm (PairEC curve d pub) bs = do+ let s = ECDH.getShared curve d pub+ k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes+ keyEncrypt k kep bs+ecdhEncrypt (StdDH dig kep) ukm (PairX25519 priv pub) bs =+ case fromCryptoFailable (ecdh x25519 priv pub) of+ Left e -> return (Left e)+ Right s ->+ let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes+ in keyEncrypt k kep bs+ecdhEncrypt (StdDH dig kep) ukm (PairX448 priv pub) bs =+ case fromCryptoFailable (ecdh x448 priv pub) of+ Left e -> return (Left e)+ Right s ->+ let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes+ in keyEncrypt k kep bs+ecdhEncrypt (CofactorDH dig kep) ukm (PairEC curve d pub) bs = do+ let h = ECC.ecc_h (ECC.common_curve curve)+ s = ECDH.getShared curve (h * d) pub+ k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes+ keyEncrypt k kep bs+ecdhEncrypt (CofactorDH _ _) _ (PairX25519 _ _) _ =+ return $ Left (InvalidInput "X25519 is not supported for cofactor DH")+ecdhEncrypt (CofactorDH _ _) _ (PairX448 _ _) _ =+ return $ Left (InvalidInput "X448 is not supported for cofactor DH") -- | Decrypt the specified content with an ECDH key pair and key-agreement -- algorithm. ecdhDecrypt :: ByteArray ba- => KeyAgreementParams -> Maybe ByteString -> X509.PrivKeyEC -> X509.SerializedPoint -> ba -> Either StoreError ba-ecdhDecrypt (StdDH dig kep) ukm priv pt bs =+ => KeyAgreementParams -> Maybe ByteString -> X509.PrivKey -> ByteString -> ba -> Either StoreError ba+ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyEC priv) pt bs = case ecPrivKeyCurve priv of Nothing -> Left UnsupportedEllipticCurve Just curve ->- case unserializePoint curve pt of+ case unserializePoint curve (X509.SerializedPoint pt) of Nothing -> Left (InvalidInput "Invalid serialized point") Just pub -> do let d = X509.privkeyEC_priv priv s = ECDH.getShared curve d pub k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes keyDecrypt k kep bs-ecdhDecrypt (CofactorDH dig kep) ukm priv pt bs =+ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyX25519 priv) pt bs = do+ s <- fromCryptoFailable (X25519.publicKey pt >>= ecdh x25519 priv)+ let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes+ keyDecrypt k kep bs+ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyX448 priv) pt bs = do+ s <- fromCryptoFailable (X448.publicKey pt >>= ecdh x448 priv)+ let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes+ keyDecrypt k kep bs+ecdhDecrypt (StdDH _ _) _ _ _ _ = Left UnexpectedPrivateKeyType+ecdhDecrypt (CofactorDH dig kep) ukm (X509.PrivKeyEC priv) pt bs = case ecPrivKeyCurve priv of Nothing -> Left UnsupportedEllipticCurve Just curve ->- case unserializePoint curve pt of+ case unserializePoint curve (X509.SerializedPoint pt) of Nothing -> Left (InvalidInput "Invalid serialized point") Just pub -> do let h = ECC.ecc_h (ECC.common_curve curve)@@ -1555,8 +1685,15 @@ s = ECDH.getShared curve (h * d) pub k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes keyDecrypt k kep bs+ecdhDecrypt (CofactorDH _ _) _ _ _ _ = Left UnexpectedPrivateKeyType +x25519 :: Proxy Curve_X25519+x25519 = Proxy +x448 :: Proxy Curve_X448+x448 = Proxy++ -- Utilities getCipher :: (BlockCipher cipher, ByteArray key)@@ -1713,6 +1850,8 @@ | TypeRSAPSS | TypeDSA DigestAlgorithm | TypeECDSA DigestAlgorithm+ | TypeEd25519+ | TypeEd448 deriving (Show,Eq) instance Enumerable SignatureType where@@ -1737,6 +1876,9 @@ , TypeECDSA (DigestAlgorithm SHA256) , TypeECDSA (DigestAlgorithm SHA384) , TypeECDSA (DigestAlgorithm SHA512)++ , TypeEd25519+ , TypeEd448 ] instance OIDable SignatureType where@@ -1762,6 +1904,9 @@ getObjectID (TypeECDSA (DigestAlgorithm SHA384)) = [1,2,840,10045,4,3,3] getObjectID (TypeECDSA (DigestAlgorithm SHA512)) = [1,2,840,10045,4,3,4] + getObjectID TypeEd25519 = [1,3,101,112]+ getObjectID TypeEd448 = [1,3,101,113]+ getObjectID ty = error ("Unsupported SignatureType: " ++ show ty) instance OIDNameable SignatureType where@@ -1773,6 +1918,8 @@ | RSAPSS PSSParams | DSA DigestAlgorithm | ECDSA DigestAlgorithm+ | Ed25519+ | Ed448 deriving (Show,Eq) instance AlgorithmId SignatureAlg where@@ -1784,35 +1931,42 @@ algorithmType (RSAPSS _) = TypeRSAPSS algorithmType (DSA alg) = TypeDSA alg algorithmType (ECDSA alg) = TypeECDSA alg+ algorithmType Ed25519 = TypeEd25519+ algorithmType Ed448 = TypeEd448 parameterASN1S RSAAnyHash = gNull parameterASN1S (RSA _) = gNull parameterASN1S (RSAPSS p) = asn1s p parameterASN1S (DSA _) = id parameterASN1S (ECDSA _) = id+ parameterASN1S Ed25519 = id+ parameterASN1S Ed448 = id parseParameter TypeRSAAnyHash = getNextMaybe nullOrNothing >> return RSAAnyHash parseParameter (TypeRSA alg) = getNextMaybe nullOrNothing >> return (RSA alg) parseParameter TypeRSAPSS = RSAPSS <$> parse parseParameter (TypeDSA alg) = return (DSA alg) parseParameter (TypeECDSA alg) = return (ECDSA alg)+ parseParameter TypeEd25519 = return Ed25519+ parseParameter TypeEd448 = return Ed448 --- | Sign a message using the specified algorithm and private key.-signatureGenerate :: MonadRandom m => SignatureAlg -> X509.PrivKey -> ByteString -> m (Either StoreError SignatureValue)-signatureGenerate RSAAnyHash _ _ =+-- | Sign a message using the specified algorithm and private key. The+-- corresponding public key is also required for some algorithms.+signatureGenerate :: MonadRandom m => SignatureAlg -> X509.PrivKey -> X509.PubKey -> ByteString -> m (Either StoreError SignatureValue)+signatureGenerate RSAAnyHash _ _ _ = error "signatureGenerate: should call signatureResolveHash first"-signatureGenerate (RSA alg) (X509.PrivKeyRSA priv) msg =+signatureGenerate (RSA alg) (X509.PrivKeyRSA priv) (X509.PubKeyRSA _) msg = let err = return . Left $ InvalidParameter ("Invalid hash algorithm for RSA: " ++ show alg) in withHashAlgorithmASN1 alg err $ \hashAlg -> mapLeft RSAError <$> RSA.signSafer (Just hashAlg) priv msg-signatureGenerate (RSAPSS p) (X509.PrivKeyRSA priv) msg =+signatureGenerate (RSAPSS p) (X509.PrivKeyRSA priv) (X509.PubKeyRSA _) msg = withPSSParams p $ \params -> mapLeft RSAError <$> RSAPSS.signSafer params priv msg-signatureGenerate (DSA alg) (X509.PrivKeyDSA priv) msg =+signatureGenerate (DSA alg) (X509.PrivKeyDSA priv) (X509.PubKeyDSA _) msg = case alg of DigestAlgorithm t -> Right . dsaFromSignature <$> DSA.sign priv (hashFromProxy t) msg-signatureGenerate (ECDSA alg) (X509.PrivKeyEC priv) msg =+signatureGenerate (ECDSA alg) (X509.PrivKeyEC priv) (X509.PubKeyEC _) msg = case alg of DigestAlgorithm t -> case ecdsaToPrivateKey priv of@@ -1820,7 +1974,11 @@ Just p -> let h = hashFromProxy t in Right . ecdsaFromSignature <$> ECDSA.sign p h msg-signatureGenerate _ _ _ = return (Left UnexpectedPrivateKeyType)+signatureGenerate Ed25519 (X509.PrivKeyEd25519 priv) (X509.PubKeyEd25519 pub) msg =+ return . Right . B.convert $ Ed25519.sign priv pub msg+signatureGenerate Ed448 (X509.PrivKeyEd448 priv) (X509.PubKeyEd448 pub) msg =+ return . Right . B.convert $ Ed448.sign priv pub msg+signatureGenerate _ _ _ _ = return (Left UnexpectedPrivateKeyType) -- | Verify a message signature using the specified algorithm and public key. signatureVerify :: SignatureAlg -> X509.PubKey -> ByteString -> SignatureValue -> Bool@@ -1840,6 +1998,14 @@ s <- ecdsaToSignature sig case alg of DigestAlgorithm t -> return $ ECDSA.verify (hashFromProxy t) p s msg+signatureVerify Ed25519 (X509.PubKeyEd25519 pub) msg sig =+ case Ed25519.signature sig of+ CryptoFailed _ -> False+ CryptoPassed s -> Ed25519.verify pub msg s+signatureVerify Ed448 (X509.PubKeyEd448 pub) msg sig =+ case Ed448.signature sig of+ CryptoFailed _ -> False+ CryptoPassed s -> Ed448.verify pub msg s signatureVerify _ _ _ _ = False withHashAlgorithmASN1 :: DigestAlgorithm@@ -1858,12 +2024,15 @@ -- | Return on which digest algorithm the specified signature algorithm is -- based, as well as a substitution algorithm for when a default digest -- algorithm is required.-signatureResolveHash :: DigestAlgorithm -> SignatureAlg -> (DigestAlgorithm, SignatureAlg)-signatureResolveHash d RSAAnyHash = (d, RSA d)-signatureResolveHash _ alg@(RSA d) = (d, alg)-signatureResolveHash _ alg@(RSAPSS p) = (pssHashAlgorithm p, alg)-signatureResolveHash _ alg@(DSA d) = (d, alg)-signatureResolveHash _ alg@(ECDSA d) = (d, alg)+signatureResolveHash :: Bool -> DigestAlgorithm -> SignatureAlg -> (DigestAlgorithm, SignatureAlg)+signatureResolveHash _ d RSAAnyHash = (d, RSA d)+signatureResolveHash _ _ alg@(RSA d) = (d, alg)+signatureResolveHash _ _ alg@(RSAPSS p) = (pssHashAlgorithm p, alg)+signatureResolveHash _ _ alg@(DSA d) = (d, alg)+signatureResolveHash _ _ alg@(ECDSA d) = (d, alg)+signatureResolveHash _ _ alg@Ed25519 = (DigestAlgorithm SHA512, alg)+signatureResolveHash True _ alg@Ed448 = (DigestAlgorithm SHAKE256_512, alg)+signatureResolveHash False _ alg@Ed448 = (DigestAlgorithm (SHAKE256 p512), alg) -- | Check that a signature algorithm is based on the specified digest algorithm -- and return a substitution algorithm for when a default digest algorithm is@@ -1882,6 +2051,13 @@ signatureCheckHash expected alg@(ECDSA found) | expected == found = Just alg | otherwise = Nothing+signatureCheckHash expected alg@Ed25519+ | expected == DigestAlgorithm SHA512 = Just alg+ | otherwise = Nothing+signatureCheckHash expected alg@Ed448+ | expected == DigestAlgorithm SHAKE256_512 = Just alg+ | expected == DigestAlgorithm (SHAKE256 p512) = Just alg+ | otherwise = Nothing dsaToSignature :: ByteString -> Maybe DSA.Signature dsaToSignature b = tryDecodeAndParse b $ onNextContainer Sequence $ do
src/Crypto/Store/CMS/AuthEnveloped.hs view
@@ -34,7 +34,7 @@ import Crypto.Store.CMS.Util -- | Authenticated-enveloped content information.-data AuthEnvelopedData = AuthEnvelopedData+data AuthEnvelopedData content = AuthEnvelopedData { aeOriginatorInfo :: OriginatorInfo -- ^ Optional information about the originator , aeRecipientInfos :: [RecipientInfo]@@ -43,7 +43,7 @@ -- ^ Inner content type , aeContentEncryptionParams :: ASN1ObjectExact AuthContentEncryptionParams -- ^ Encryption algorithm- , aeEncryptedContent :: EncryptedContent+ , aeEncryptedContent :: content -- ^ Encrypted content info , aeAuthAttrs :: [Attribute] -- ^ Optional authenticated attributes@@ -54,7 +54,7 @@ } deriving (Show,Eq) -instance ProduceASN1Object ASN1P AuthEnvelopedData where+instance ProduceASN1Object ASN1P (AuthEnvelopedData (Encap EncryptedContent)) where asn1s AuthEnvelopedData{..} = asn1Container Sequence (ver . oi . ris . eci . aa . tag . ua) where@@ -69,7 +69,7 @@ oi | aeOriginatorInfo == mempty = id | otherwise = originatorInfoASN1S (Container Context 0) aeOriginatorInfo -instance ParseASN1Object [ASN1Event] AuthEnvelopedData where+instance ParseASN1Object [ASN1Event] (AuthEnvelopedData (Encap EncryptedContent)) where parse = onNextContainer Sequence $ do IntVal v <- getNext
+ src/Crypto/Store/CMS/Authenticated.hs view
@@ -0,0 +1,99 @@+-- |+-- Module : Crypto.Store.CMS.Authenticated+-- License : BSD-style+-- Maintainer : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability : experimental+-- Portability : unknown+--+--+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RecordWildCards #-}+module Crypto.Store.CMS.Authenticated+ ( AuthenticatedData(..)+ ) where++import Control.Applicative+import Control.Monad++import Data.ASN1.Types+import qualified Data.ByteArray as B++import Crypto.Cipher.Types++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.Attribute+import Crypto.Store.CMS.Enveloped+import Crypto.Store.CMS.OriginatorInfo+import Crypto.Store.CMS.Signed+import Crypto.Store.CMS.Type+import Crypto.Store.CMS.Util++-- | Authenticated content information.+data AuthenticatedData content = AuthenticatedData+ { adOriginatorInfo :: OriginatorInfo+ -- ^ Optional information about the originator+ , adRecipientInfos :: [RecipientInfo]+ -- ^ Information for recipients, allowing to authenticate the content+ , adMACAlgorithm :: MACAlgorithm+ -- ^ MAC algorithm+ , adDigestAlgorithm :: Maybe DigestAlgorithm+ -- ^ Optional digest algorithm+ , adContentType :: ContentType+ -- ^ Inner content type+ , adEncapsulatedContent :: content+ -- ^ Encapsulated content+ , adAuthAttrs :: [Attribute]+ -- ^ Optional authenticated attributes+ , adMAC :: MessageAuthenticationCode+ -- ^ Message authentication code+ , adUnauthAttrs :: [Attribute]+ -- ^ Optional unauthenticated attributes+ }+ deriving (Show,Eq)++instance ProduceASN1Object ASN1P (AuthenticatedData (Encap EncapsulatedContent)) where+ asn1s AuthenticatedData{..} =+ asn1Container Sequence (ver . oi . ris . alg . dig . ci . aa . tag . ua)+ where+ ver = gIntVal v+ ris = asn1Container Set (asn1s adRecipientInfos)+ alg = algorithmASN1S Sequence adMACAlgorithm+ dig = algorithmMaybeASN1S (Container Context 1) adDigestAlgorithm+ ci = encapsulatedContentInfoASN1S adContentType adEncapsulatedContent+ aa = attributesASN1S(Container Context 2) adAuthAttrs+ tag = gOctetString (B.convert adMAC)+ ua = attributesASN1S (Container Context 3) adUnauthAttrs++ oi | adOriginatorInfo == mempty = id+ | otherwise = originatorInfoASN1S (Container Context 0) adOriginatorInfo++ v | hasChoiceOther adOriginatorInfo = 3+ | otherwise = 0++instance ParseASN1Object [ASN1Event] (AuthenticatedData (Encap EncapsulatedContent)) where+ parse =+ onNextContainer Sequence $ do+ IntVal v <- getNext+ when (v `notElem` [0, 1, 3]) $+ throwParseError ("AuthenticatedData: parsed invalid version: " ++ show v)+ oi <- parseOriginatorInfo (Container Context 0) <|> return mempty+ ris <- onNextContainer Set parse+ alg <- parseAlgorithm Sequence+ dig <- parseAlgorithmMaybe (Container Context 1)+ (ct, bs) <- parseEncapsulatedContentInfo+ aAttrs <- parseAttributes (Container Context 2)+ OctetString tag <- getNext+ uAttrs <- parseAttributes (Container Context 3)+ return AuthenticatedData { adOriginatorInfo = oi+ , adRecipientInfos = ris+ , adMACAlgorithm = alg+ , adDigestAlgorithm = dig+ , adContentType = ct+ , adEncapsulatedContent = bs+ , adAuthAttrs = aAttrs+ , adMAC = AuthTag $ B.convert tag+ , adUnauthAttrs = uAttrs+ }
+ src/Crypto/Store/CMS/Digested.hs view
@@ -0,0 +1,82 @@+-- |+-- Module : Crypto.Store.CMS.Digested+-- License : BSD-style+-- Maintainer : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability : experimental+-- Portability : unknown+--+--+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RecordWildCards #-}+module Crypto.Store.CMS.Digested+ ( DigestedData(..)+ ) where++import Control.Monad++import Data.ASN1.Types+import qualified Data.ByteArray as B++import Crypto.Hash hiding (MD5)++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.Signed+import Crypto.Store.CMS.Type+import Crypto.Store.CMS.Util++-- | Digested content information.+data DigestedData content = forall hashAlg. HashAlgorithm hashAlg => DigestedData+ { ddDigestAlgorithm :: DigestProxy hashAlg -- ^ Digest algorithm+ , ddContentType :: ContentType -- ^ Inner content type+ , ddEncapsulatedContent :: content -- ^ Encapsulated content+ , ddDigest :: Digest hashAlg -- ^ Digest value+ }++instance Show content => Show (DigestedData content) where+ showsPrec d DigestedData{..} = showParen (d > 10) $+ showString "DigestedData "+ . showString "{ ddDigestAlgorithm = " . shows ddDigestAlgorithm+ . showString ", ddContentType = " . shows ddContentType+ . showString ", ddEncapsulatedContent = " . shows ddEncapsulatedContent+ . showString ", ddDigest = " . shows ddDigest+ . showString " }"++instance Eq content => Eq (DigestedData content) where+ DigestedData a1 t1 e1 d1 == DigestedData a2 t2 e2 d2 =+ DigestAlgorithm a1 == DigestAlgorithm a2 && d1 `B.eq` d2 && t1 == t2 && e1 == e2++instance ASN1Elem e => ProduceASN1Object e (DigestedData (Encap EncapsulatedContent)) where+ asn1s DigestedData{..} =+ asn1Container Sequence (ver . alg . ci . dig)+ where+ v = if ddContentType == DataType then 0 else 2+ d = DigestAlgorithm ddDigestAlgorithm++ ver = gIntVal v+ alg = algorithmASN1S Sequence d+ ci = encapsulatedContentInfoASN1S ddContentType ddEncapsulatedContent+ dig = gOctetString (B.convert ddDigest)++instance Monoid e => ParseASN1Object e (DigestedData (Encap EncapsulatedContent)) where+ parse =+ onNextContainer Sequence $ do+ IntVal v <- getNext+ when (v /= 0 && v /= 2) $+ throwParseError ("DigestedData: parsed invalid version: " ++ show v)+ alg <- parseAlgorithm Sequence+ (ct, bs) <- parseEncapsulatedContentInfo+ OctetString digValue <- getNext+ case alg of+ DigestAlgorithm digAlg ->+ case digestFromByteString digValue of+ Nothing -> throwParseError "DigestedData: parsed invalid digest"+ Just d ->+ return DigestedData { ddDigestAlgorithm = digAlg+ , ddContentType = ct+ , ddEncapsulatedContent = bs+ , ddDigest = d+ }
src/Crypto/Store/CMS/Encrypted.hs view
@@ -38,19 +38,19 @@ type EncryptedContent = B.ByteString -- | Encrypted content information.-data EncryptedData = EncryptedData+data EncryptedData content = EncryptedData { edContentType :: ContentType -- ^ Inner content type , edContentEncryptionParams :: ContentEncryptionParams -- ^ Encryption algorithm- , edEncryptedContent :: EncryptedContent+ , edEncryptedContent :: content -- ^ Encrypted content info , edUnprotectedAttrs :: [Attribute] -- ^ Optional unprotected attributes } deriving (Show,Eq) -instance ASN1Elem e => ProduceASN1Object e EncryptedData where+instance ASN1Elem e => ProduceASN1Object e (EncryptedData (Encap EncryptedContent)) where asn1s EncryptedData{..} = asn1Container Sequence (ver . eci . ua) where@@ -59,7 +59,7 @@ (edContentType, edContentEncryptionParams, edEncryptedContent) ua = attributesASN1S (Container Context 1) edUnprotectedAttrs -instance Monoid e => ParseASN1Object e EncryptedData where+instance Monoid e => ParseASN1Object e (EncryptedData (Encap EncryptedContent)) where parse = onNextContainer Sequence $ do IntVal v <- getNext@@ -75,21 +75,27 @@ -- | Generate ASN.1 for EncryptedContentInfo. encryptedContentInfoASN1S :: (ASN1Elem e, ProduceASN1Object e alg)- => (ContentType, alg, B.ByteString) -> ASN1Stream e+ => (ContentType, alg, Encap EncryptedContent) -> ASN1Stream e encryptedContentInfoASN1S (ct, alg, ec) = asn1Container Sequence (ct' . alg' . ec') where ct' = gOID (getObjectID ct) alg' = asn1s alg- ec' = asn1Container (Container Context 0) (gOctetString ec)+ ec' = encapsulatedASN1S (Container Context 0) ec +encapsulatedASN1S :: ASN1Elem e+ => ASN1ConstructionType -> Encap EncryptedContent -> ASN1Stream e+encapsulatedASN1S _ Detached = id+encapsulatedASN1S ty (Attached bs) = asn1Container ty (gOctetString bs)+ -- | Parse EncryptedContentInfo from ASN.1. parseEncryptedContentInfo :: ParseASN1Object e alg- => ParseASN1 e (ContentType, alg, B.ByteString)+ => ParseASN1 e (ContentType, alg, Encap EncryptedContent) parseEncryptedContentInfo = onNextContainer Sequence $ do OID oid <- getNext alg <- parse- ec <- parseEncryptedContent+ b <- hasNext+ ec <- if b then Attached <$> parseEncryptedContent else return Detached withObjectID "content type" oid $ \ct -> return (ct, alg, ec) where parseEncryptedContent = parseWrapped <|> parsePrimitive
src/Crypto/Store/CMS/Enveloped.hs view
@@ -67,7 +67,6 @@ import Crypto.Store.CMS.Type import Crypto.Store.CMS.Util import Crypto.Store.Error-import Crypto.Store.PKCS8.EC -- | Encrypted key. type EncryptedKey = ByteString@@ -424,7 +423,7 @@ isPwriOri (PasswordRI _) = True -- | Enveloped content information.-data EnvelopedData = EnvelopedData+data EnvelopedData content = EnvelopedData { evOriginatorInfo :: OriginatorInfo -- ^ Optional information about the originator , evRecipientInfos :: [RecipientInfo]@@ -433,14 +432,14 @@ -- ^ Inner content type , evContentEncryptionParams :: ContentEncryptionParams -- ^ Encryption algorithm- , evEncryptedContent :: EncryptedContent+ , evEncryptedContent :: content -- ^ Encrypted content info , evUnprotectedAttrs :: [Attribute] -- ^ Optional unprotected attributes } deriving (Show,Eq) -instance ProduceASN1Object ASN1P EnvelopedData where+instance ProduceASN1Object ASN1P (EnvelopedData (Encap EncryptedContent)) where asn1s EnvelopedData{..} = asn1Container Sequence (ver . oi . ris . eci . ua) where@@ -460,7 +459,7 @@ | all isVersion0 evRecipientInfos = 0 | otherwise = 2 -instance ParseASN1Object [ASN1Event] EnvelopedData where+instance ParseASN1Object [ASN1Event] (EnvelopedData (Encap EncryptedContent)) where parse = onNextContainer Sequence $ do IntVal v <- getNext@@ -524,10 +523,10 @@ forKeyAgreeRecipient cert params inkey = do ephemeral <- ecdhGenerate (certPubKey obj) case ephemeral of- Right (curve, d, sp) -> do- let SerializedPoint pt = getSerializedPoint curve d+ Right pair -> do+ let pt = ecdhPublic pair aPub = OriginatorPublicKeyEC [] (toBitArray pt 0)- ek <- ecdhEncrypt params Nothing curve d sp inkey+ ek <- ecdhEncrypt params Nothing pair inkey return (KARI . build aPub <$> ek) Left err -> return $ Left err where@@ -553,16 +552,15 @@ -- This function can be used as parameter to -- 'Crypto.Store.CMS.openEnvelopedData'. withRecipientKeyAgree :: MonadRandom m => PrivKey -> SignedCertificate -> ConsumerOfRI m-withRecipientKeyAgree (PrivKeyEC priv) cert (KARI KARecipientInfo{..}) =+withRecipientKeyAgree priv cert (KARI KARecipientInfo{..}) = case kaOriginator of OriginatorPublic (OriginatorPublicKeyEC _ ba) -> case findRecipientEncryptedKey cert kaRecipientEncryptedKeys of Nothing -> pure (Left RecipientKeyNotFound) Just ek ->- let pub = SerializedPoint (bitArrayGetData ba)+ let pub = bitArrayGetData ba in pure (ecdhDecrypt kaKeyAgreementParams kaUkm priv pub ek) _ -> pure (Left UnsupportedOriginatorFormat)-withRecipientKeyAgree _ _ (KARI _) = pure (Left UnexpectedPrivateKeyType) withRecipientKeyAgree _ _ _ = pure (Left RecipientTypeMismatch) -- | Generate a Key Encryption Key recipient from a key encryption key and
src/Crypto/Store/CMS/Info.hs view
@@ -6,49 +6,38 @@ -- Portability : unknown -- -- CMS content information.-{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-} module Crypto.Store.CMS.Info ( ContentInfo(..) , getContentType- , SignedData(..)- , DigestedData(..)- , AuthenticatedData(..)- , encapsulate- , decapsulate+ , Encapsulates+ , isAttached+ , fromAttached+ , toAttachedCI+ , isDetached+ , fromDetached+ , toDetachedCI ) where -import Control.Applicative-import Control.Monad--import Data.ASN1.BinaryEncoding-import Data.ASN1.Encoding-import Data.ASN1.Types-import Data.ByteString (ByteString)-import qualified Data.ByteArray as B-import Data.Maybe (fromMaybe)+import Control.Monad.Fail (MonadFail) -import Crypto.Cipher.Types-import Crypto.Hash hiding (MD5)+import Data.ASN1.Types+import Data.ByteString (ByteString)+import Data.Functor.Identity+import Data.Maybe (isJust, isNothing) import Crypto.Store.ASN1.Generate import Crypto.Store.ASN1.Parse-import Crypto.Store.CMS.Algorithms-import Crypto.Store.CMS.Attribute+import Crypto.Store.CMS.Authenticated import Crypto.Store.CMS.AuthEnveloped+import Crypto.Store.CMS.Digested import Crypto.Store.CMS.Encrypted import Crypto.Store.CMS.Enveloped-import Crypto.Store.CMS.OriginatorInfo import Crypto.Store.CMS.Signed import Crypto.Store.CMS.Type import Crypto.Store.CMS.Util-import Crypto.Store.Error-import Crypto.Store.Util -- | Get the type of a content info. getContentType :: ContentInfo -> ContentType@@ -64,13 +53,20 @@ -- ContentInfo -- | CMS content information.-data ContentInfo = DataCI ByteString -- ^ Arbitrary octet string- | SignedDataCI SignedData -- ^ Signed content info- | EnvelopedDataCI EnvelopedData -- ^ Enveloped content info- | DigestedDataCI DigestedData -- ^ Content info with associated digest- | EncryptedDataCI EncryptedData -- ^ Encrypted content info- | AuthenticatedDataCI AuthenticatedData -- ^ Authenticatedcontent info- | AuthEnvelopedDataCI AuthEnvelopedData -- ^ Authenticated-enveloped content info+data ContentInfo = DataCI ByteString+ -- ^ Arbitrary octet string+ | SignedDataCI (SignedData (Encap EncapsulatedContent))+ -- ^ Signed content info+ | EnvelopedDataCI (EnvelopedData (Encap EncryptedContent))+ -- ^ Enveloped content info+ | DigestedDataCI (DigestedData (Encap EncapsulatedContent))+ -- ^ Content info with associated digest+ | EncryptedDataCI (EncryptedData (Encap EncryptedContent))+ -- ^ Encrypted content info+ | AuthenticatedDataCI (AuthenticatedData (Encap EncapsulatedContent))+ -- ^ Authenticatedcontent info+ | AuthEnvelopedDataCI (AuthEnvelopedData (Encap EncryptedContent))+ -- ^ Authenticated-enveloped content info deriving (Show,Eq) instance ProduceASN1Object ASN1P ContentInfo where@@ -115,239 +111,89 @@ OctetString bs -> return bs _ -> throwParseError "Data: parsed unexpected content" -isData :: ContentInfo -> Bool-isData (DataCI _) = True-isData _ = False ---- SignedData---- | Signed content information.-data SignedData = SignedData- { sdDigestAlgorithms :: [DigestAlgorithm] -- ^ Digest algorithms- , sdContentInfo :: ContentInfo -- ^ Inner content info- , sdCertificates :: [CertificateChoice] -- ^ The collection of certificates- , sdCRLs :: [RevocationInfoChoice] -- ^ The collection of CRLs- , sdSignerInfos :: [SignerInfo] -- ^ Per-signer information- }- deriving (Show,Eq)--instance ProduceASN1Object ASN1P SignedData where- asn1s SignedData{..} =- asn1Container Sequence (ver . dig . ci . certs . crls . sis)- where- ver = gIntVal v- dig = asn1Container Set (digestTypesASN1S sdDigestAlgorithms)- ci = encapsulatedContentInfoASN1S sdContentInfo- certs = gen 0 sdCertificates- crls = gen 1 sdCRLs- sis = asn1Container Set (asn1s sdSignerInfos)-- gen tag list- | null list = id- | otherwise = asn1Container (Container Context tag) (asn1s list)-- v | hasChoiceOther sdCertificates = 5- | hasChoiceOther sdCRLs = 5- | any isVersion3 sdSignerInfos = 3- | isData sdContentInfo = 1- | otherwise = 3---instance ParseASN1Object [ASN1Event] SignedData where- parse =- onNextContainer Sequence $ do- IntVal v <- getNext- when (v > 5) $- throwParseError ("SignedData: parsed invalid version: " ++ show v)- dig <- onNextContainer Set parseDigestTypes- inner <- parseEncapsulatedContentInfo- certs <- parseOptList 0- crls <- parseOptList 1- sis <- onNextContainer Set parse- return SignedData { sdDigestAlgorithms = dig- , sdContentInfo = inner- , sdCertificates = certs- , sdCRLs = crls- , sdSignerInfos = sis- }- where- parseOptList tag =- fromMaybe [] <$> onNextContainerMaybe (Container Context tag) parse--digestTypesASN1S :: ASN1Elem e => [DigestAlgorithm] -> ASN1Stream e-digestTypesASN1S list cont = foldr (algorithmASN1S Sequence) cont list--parseDigestTypes :: Monoid e => ParseASN1 e [DigestAlgorithm]-parseDigestTypes = getMany (parseAlgorithm Sequence)----- DigestedData---- | Digested content information.-data DigestedData = forall hashAlg. HashAlgorithm hashAlg => DigestedData- { ddDigestAlgorithm :: DigestProxy hashAlg -- ^ Digest algorithm- , ddContentInfo :: ContentInfo -- ^ Inner content info- , ddDigest :: Digest hashAlg -- ^ Digest value- }--instance Show DigestedData where- showsPrec d DigestedData{..} = showParen (d > 10) $- showString "DigestedData "- . showString "{ ddDigestAlgorithm = " . shows ddDigestAlgorithm- . showString ", ddContentInfo = " . shows ddContentInfo- . showString ", ddDigest = " . shows ddDigest- . showString " }"--instance Eq DigestedData where- DigestedData a1 i1 d1 == DigestedData a2 i2 d2 =- DigestAlgorithm a1 == DigestAlgorithm a2 && d1 `B.eq` d2 && i1 == i2--instance ASN1Elem e => ProduceASN1Object e DigestedData where- asn1s DigestedData{..} =- asn1Container Sequence (ver . alg . ci . dig)- where- v = if isData ddContentInfo then 0 else 2- d = DigestAlgorithm ddDigestAlgorithm-- ver = gIntVal v- alg = algorithmASN1S Sequence d- ci = encapsulatedContentInfoASN1S ddContentInfo- dig = gOctetString (B.convert ddDigest)--instance Monoid e => ParseASN1Object e DigestedData where- parse =- onNextContainer Sequence $ do- IntVal v <- getNext- when (v /= 0 && v /= 2) $- throwParseError ("DigestedData: parsed invalid version: " ++ show v)- alg <- parseAlgorithm Sequence- inner <- parseEncapsulatedContentInfo- OctetString bs <- getNext- case alg of- DigestAlgorithm digAlg ->- case digestFromByteString bs of- Nothing -> throwParseError "DigestedData: parsed invalid digest"- Just d ->- return DigestedData { ddDigestAlgorithm = digAlg- , ddContentInfo = inner- , ddDigest = d- }+-- Encapsulation +-- | Class of data structures with inner content that may be stored externally.+-- This class has instances for each CMS content type containing other+-- encapsulated or encrypted content info.+--+-- Functions 'fromAttached' and 'fromDetached' are used to introspect+-- encapsulation state (attached or detached), and recover a data structure with+-- actionable content.+--+-- Functions 'toAttachedCI' and 'toDetachedCI' are needed to decide about the+-- outer encapsulation state and build a 'ContentInfo'.+class Encapsulates struct where+ lens :: Functor f => (a -> f b) -> struct a -> f (struct b)+ toCI :: struct (Encap ByteString) -> ContentInfo --- Authenticated data+instance Encapsulates SignedData where+ lens f s = let g a = s { sdEncapsulatedContent = a }+ in fmap g (f $ sdEncapsulatedContent s)+ toCI = SignedDataCI --- | Authenticated content information.-data AuthenticatedData = AuthenticatedData- { adOriginatorInfo :: OriginatorInfo- -- ^ Optional information about the originator- , adRecipientInfos :: [RecipientInfo]- -- ^ Information for recipients, allowing to authenticate the content- , adMACAlgorithm :: MACAlgorithm- -- ^ MAC algorithm- , adDigestAlgorithm :: Maybe DigestAlgorithm- -- ^ Optional digest algorithm- , adContentInfo :: ContentInfo- -- ^ Inner content info- , adAuthAttrs :: [Attribute]- -- ^ Optional authenticated attributes- , adMAC :: MessageAuthenticationCode- -- ^ Message authentication code- , adUnauthAttrs :: [Attribute]- -- ^ Optional unauthenticated attributes- }- deriving (Show,Eq)+instance Encapsulates EnvelopedData where+ lens f s = let g a = s { evEncryptedContent = a }+ in fmap g (f $ evEncryptedContent s)+ toCI = EnvelopedDataCI -instance ProduceASN1Object ASN1P AuthenticatedData where- asn1s AuthenticatedData{..} =- asn1Container Sequence (ver . oi . ris . alg . dig . ci . aa . tag . ua)- where- ver = gIntVal v- ris = asn1Container Set (asn1s adRecipientInfos)- alg = algorithmASN1S Sequence adMACAlgorithm- dig = algorithmMaybeASN1S (Container Context 1) adDigestAlgorithm- ci = encapsulatedContentInfoASN1S adContentInfo- aa = attributesASN1S(Container Context 2) adAuthAttrs- tag = gOctetString (B.convert adMAC)- ua = attributesASN1S (Container Context 3) adUnauthAttrs+instance Encapsulates DigestedData where+ lens f s = let g a = s { ddEncapsulatedContent = a }+ in fmap g (f $ ddEncapsulatedContent s)+ toCI = DigestedDataCI - oi | adOriginatorInfo == mempty = id- | otherwise = originatorInfoASN1S (Container Context 0) adOriginatorInfo+instance Encapsulates EncryptedData where+ lens f s = let g a = s { edEncryptedContent = a }+ in fmap g (f $ edEncryptedContent s)+ toCI = EncryptedDataCI - v | hasChoiceOther adOriginatorInfo = 3- | otherwise = 0+instance Encapsulates AuthenticatedData where+ lens f s = let g a = s { adEncapsulatedContent = a }+ in fmap g (f $ adEncapsulatedContent s)+ toCI = AuthenticatedDataCI -instance ParseASN1Object [ASN1Event] AuthenticatedData where- parse =- onNextContainer Sequence $ do- IntVal v <- getNext- when (v `notElem` [0, 1, 3]) $- throwParseError ("AuthenticatedData: parsed invalid version: " ++ show v)- oi <- parseOriginatorInfo (Container Context 0) <|> return mempty- ris <- onNextContainer Set parse- alg <- parseAlgorithm Sequence- dig <- parseAlgorithmMaybe (Container Context 1)- inner <- parseEncapsulatedContentInfo- aAttrs <- parseAttributes (Container Context 2)- OctetString tag <- getNext- uAttrs <- parseAttributes (Container Context 3)- return AuthenticatedData { adOriginatorInfo = oi- , adRecipientInfos = ris- , adMACAlgorithm = alg- , adDigestAlgorithm = dig- , adContentInfo = inner- , adAuthAttrs = aAttrs- , adMAC = AuthTag $ B.convert tag- , adUnauthAttrs = uAttrs- }+instance Encapsulates AuthEnvelopedData where+ lens f s = let g a = s { aeEncryptedContent = a }+ in fmap g (f $ aeEncryptedContent s)+ toCI = AuthEnvelopedDataCI +-- | Return 'True' when the encapsulated content is attached.+isAttached :: Encapsulates struct => struct (Encap a) -> Bool+isAttached = isJust . fromAttached --- Utilities+-- | Unwrap the encapsulation, assuming the inner content is inside the data+-- structure. The monadic computation fails if the content was detached.+fromAttached :: (MonadFail m, Encapsulates struct) => struct (Encap a) -> m (struct a)+fromAttached = lens (fromEncap err return)+ where err = fail "fromAttached: detached" -decode :: ParseASN1 [ASN1Event] a -> ByteString -> Either StoreError a-decode parser bs = vals >>= mapLeft ParseFailure . runParseASN1_ parser- where vals = mapLeft DecodingError (decodeASN1Repr' BER bs)+-- | Keep the content inside the data structure.+toAttached :: Encapsulates struct => struct a -> struct (Encap a)+toAttached = runIdentity . lens (Identity . Attached) --- | Encode the information for encapsulation in another content info.-encapsulate :: ContentInfo -> ByteString-encapsulate (DataCI bs) = bs-encapsulate (SignedDataCI ed) = encodeASN1Object ed-encapsulate (EnvelopedDataCI ed) = encodeASN1Object ed-encapsulate (DigestedDataCI dd) = encodeASN1Object dd-encapsulate (EncryptedDataCI ed) = encodeASN1Object ed-encapsulate (AuthenticatedDataCI ad) = encodeASN1Object ad-encapsulate (AuthEnvelopedDataCI ae) = encodeASN1Object ae+-- | Transform the data structure into a content info, keeping the encapsulated+-- content attached. May be applied to structures with 'EncapsulatedContent' or+-- 'EncryptedContent'.+toAttachedCI :: Encapsulates struct => struct ByteString -> ContentInfo+toAttachedCI = toCI . toAttached --- | Decode the information from encapsulated content.-decapsulate :: ContentType -> ByteString -> Either StoreError ContentInfo-decapsulate DataType bs = pure (DataCI bs)-decapsulate SignedDataType bs = SignedDataCI <$> decode parse bs-decapsulate EnvelopedDataType bs = EnvelopedDataCI <$> decode parse bs-decapsulate DigestedDataType bs = DigestedDataCI <$> decode parse bs-decapsulate EncryptedDataType bs = EncryptedDataCI <$> decode parse bs-decapsulate AuthenticatedDataType bs = AuthenticatedDataCI <$> decode parse bs-decapsulate AuthEnvelopedDataType bs = AuthEnvelopedDataCI <$> decode parse bs+-- | Return 'True' when the encapsulated content is detached.+isDetached :: Encapsulates struct => struct (Encap a) -> Bool+isDetached = isNothing . fromAttached -encapsulatedContentInfoASN1S :: ASN1Elem e => ContentInfo -> ASN1Stream e-encapsulatedContentInfoASN1S ci = asn1Container Sequence (oid . cont)- where oid = gOID $ getObjectID $ getContentType ci- cont = asn1Container (Container Context 0) inner- inner = gOctetString (encapsulate ci)+-- | Recover the original data structure from a detached encapsulation and the+-- external content. The monadic computation fails if the content was attached.+fromDetached :: (MonadFail m, Encapsulates struct) => b -> struct (Encap a) -> m (struct b)+fromDetached c = lens (fromEncap (return c) err)+ where err _ = fail "fromDetached: attached" -parseEncapsulatedContentInfo :: Monoid e => ParseASN1 e ContentInfo-parseEncapsulatedContentInfo =- onNextContainer Sequence $ do- OID oid <- getNext- withObjectID "content type" oid $ \ct ->- onNextContainer (Container Context 0) (parseInner ct)- where- parseInner ct = do- bs <- parseContentSingle <|> parseContentChunks- case decapsulate ct bs of- Left err -> throwParseError- ("Unable to decode and parse encapsulated ASN.1: " ++ show err)- Right ci -> return ci+-- | Remove the content from the data structure to store it externally.+toDetached :: Encapsulates struct => struct a -> (a, struct (Encap a))+toDetached = let f a = (a, Detached) in lens f - parseContentSingle = do { OctetString bs <- getNext; return bs }- parseContentChunks = onNextContainer (Container Universal 4) $- B.concat <$> getMany parseContentSingle+-- | Transform the data structure into a content info, detaching the+-- encapsulated content. May be applied to structures with+-- 'EncapsulatedContent' or 'EncryptedContent'.+toDetachedCI :: Encapsulates struct => struct ByteString -> (ByteString, ContentInfo)+toDetachedCI = fmap toCI . toDetached
src/Crypto/Store/CMS/OriginatorInfo.hs view
@@ -54,8 +54,7 @@ instance Monoid OriginatorInfo where mempty = OriginatorInfo [] []- mappend (OriginatorInfo a b) (OriginatorInfo c d) =- OriginatorInfo (mappend a c) (mappend b d)+ mappend = (<>) instance HasChoiceOther OriginatorInfo where hasChoiceOther OriginatorInfo{..} =
src/Crypto/Store/CMS/Signed.hs view
@@ -10,26 +10,30 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RecordWildCards #-} module Crypto.Store.CMS.Signed- ( SignerInfo(..)+ ( EncapsulatedContent+ , SignedData(..)+ , SignerInfo(..) , SignerIdentifier(..) , IssuerAndSerialNumber(..)- , isVersion3 , ProducerOfSI , ConsumerOfSI , certSigner , withPublicKey , withSignerKey , withSignerCertificate+ , encapsulatedContentInfoASN1S+ , parseEncapsulatedContentInfo ) where import Control.Applicative import Control.Monad -import Data.ASN1.Types-import Data.ByteString (ByteString)-import Data.List-import Data.Maybe-import Data.X509+import Data.ASN1.Types+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Data.List+import Data.Maybe+import Data.X509 import Crypto.Random (MonadRandom) @@ -44,6 +48,9 @@ import Crypto.Store.CMS.Util import Crypto.Store.Error +-- | Encapsulated content.+type EncapsulatedContent = ByteString+ -- | Information related to a signer of a 'Crypto.Store.CMS.SignedData'. An -- element contains the signature material that was produced. data SignerInfo = SignerInfo@@ -173,9 +180,11 @@ cert = head chain obj = signedObject (getSigned cert) isn = IssuerAndSerialNumber (certIssuerDN obj) (certSerial obj)+ pub = certPubKey obj - (dig, alg') = signatureResolveHash def alg+ (dig, alg') = signatureResolveHash noAttr def alg + noAttr = null sAttrs (sAttrs, input) = case sAttrsM of Nothing -> ([], msg)@@ -183,7 +192,7 @@ let l = setContentTypeAttr ct $ setMessageDigestAttr md attrs in (l, encodeAuthAttrs l) - generate = signatureGenerate alg' priv input+ generate = signatureGenerate alg' priv pub input build sig = let si = SignerInfo { siSignerId = SignerIASN isn , siDigestAlgorithm = dig@@ -238,3 +247,92 @@ asX509 (CertificateCertificate c) = Just c asX509 _ = Nothing++-- | Signed content information.+data SignedData content = SignedData+ { sdDigestAlgorithms :: [DigestAlgorithm] -- ^ Digest algorithms+ , sdContentType :: ContentType -- ^ Inner content type+ , sdEncapsulatedContent :: content -- ^ Encapsulated content+ , sdCertificates :: [CertificateChoice] -- ^ The collection of certificates+ , sdCRLs :: [RevocationInfoChoice] -- ^ The collection of CRLs+ , sdSignerInfos :: [SignerInfo] -- ^ Per-signer information+ }+ deriving (Show,Eq)++instance ProduceASN1Object ASN1P (SignedData (Encap EncapsulatedContent)) where+ asn1s SignedData{..} =+ asn1Container Sequence (ver . dig . ci . certs . crls . sis)+ where+ ver = gIntVal v+ dig = asn1Container Set (digestTypesASN1S sdDigestAlgorithms)+ ci = encapsulatedContentInfoASN1S sdContentType sdEncapsulatedContent+ certs = gen 0 sdCertificates+ crls = gen 1 sdCRLs+ sis = asn1Container Set (asn1s sdSignerInfos)++ gen tag list+ | null list = id+ | otherwise = asn1Container (Container Context tag) (asn1s list)++ v | hasChoiceOther sdCertificates = 5+ | hasChoiceOther sdCRLs = 5+ | any isVersion3 sdSignerInfos = 3+ | sdContentType == DataType = 1+ | otherwise = 3+++instance ParseASN1Object [ASN1Event] (SignedData (Encap EncapsulatedContent)) where+ parse =+ onNextContainer Sequence $ do+ IntVal v <- getNext+ when (v > 5) $+ throwParseError ("SignedData: parsed invalid version: " ++ show v)+ dig <- onNextContainer Set parseDigestTypes+ (ct, bs) <- parseEncapsulatedContentInfo+ certs <- parseOptList 0+ crls <- parseOptList 1+ sis <- onNextContainer Set parse+ return SignedData { sdDigestAlgorithms = dig+ , sdContentType = ct+ , sdEncapsulatedContent = bs+ , sdCertificates = certs+ , sdCRLs = crls+ , sdSignerInfos = sis+ }+ where+ parseOptList tag =+ fromMaybe [] <$> onNextContainerMaybe (Container Context tag) parse++-- | Generate ASN.1 for EncapsulatedContentInfo.+encapsulatedContentInfoASN1S :: ASN1Elem e => ContentType -> Encap EncapsulatedContent -> ASN1Stream e+encapsulatedContentInfoASN1S ct ec = asn1Container Sequence (oid . cont)+ where oid = gOID (getObjectID ct)+ cont = encapsulatedASN1S (Container Context 0) ec++encapsulatedASN1S :: ASN1Elem e+ => ASN1ConstructionType -> Encap B.ByteString -> ASN1Stream e+encapsulatedASN1S _ Detached = id+encapsulatedASN1S ty (Attached bs) = asn1Container ty (gOctetString bs)++-- | Parse EncapsulatedContentInfo from ASN.1.+parseEncapsulatedContentInfo :: Monoid e => ParseASN1 e (ContentType, Encap EncapsulatedContent)+parseEncapsulatedContentInfo =+ onNextContainer Sequence $ do+ OID oid <- getNext+ withObjectID "content type" oid $ \ct ->+ wrap ct <$> onNextContainerMaybe (Container Context 0) parseInner+ where+ wrap ct Nothing = (ct, Detached)+ wrap ct (Just c) = (ct, Attached c)++ parseInner = parseContentSingle <|> parseContentChunks++ parseContentSingle = do { OctetString bs <- getNext; return bs }+ parseContentChunks = onNextContainer (Container Universal 4) $+ B.concat <$> getMany parseContentSingle++digestTypesASN1S :: ASN1Elem e => [DigestAlgorithm] -> ASN1Stream e+digestTypesASN1S list cont = foldr (algorithmASN1S Sequence) cont list++parseDigestTypes :: Monoid e => ParseASN1 e [DigestAlgorithm]+parseDigestTypes = getMany (parseAlgorithm Sequence)
src/Crypto/Store/CMS/Type.hs view
@@ -11,6 +11,8 @@ {-# LANGUAGE TypeFamilies #-} module Crypto.Store.CMS.Type ( ContentType(..)+ , Encap(..)+ , fromEncap ) where import Data.ASN1.OID@@ -48,3 +50,40 @@ instance OIDNameable ContentType where fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Denotes the state of encapsulated content in a CMS data structure. This+-- type is isomorphic to 'Maybe'.+data Encap a+ = Detached -- content is stored externally to the structure+ | Attached a -- content is stored inside the CMS struture+ deriving (Show,Eq)++instance Functor Encap where+ fmap _ Detached = Detached+ fmap f (Attached c) = Attached (f c)++instance Applicative Encap where+ pure = Attached++ Attached f <*> e = fmap f e+ Detached <*> _ = Detached++instance Foldable Encap where+ foldMap = fromEncap mempty++ foldr _ d Detached = d+ foldr f d (Attached c) = f c d++ foldl _ d Detached = d+ foldl f d (Attached c) = f d c++instance Traversable Encap where+ traverse _ Detached = pure Detached+ traverse f (Attached c) = Attached <$> f c++-- | Fold over an 'Encap' value. This is similar to function 'maybe'. If the+-- content is detached, the first argument is returned. Otherwise the second+-- argument is applied to the content.+fromEncap :: b -> (a -> b) -> Encap a -> b+fromEncap d _ Detached = d+fromEncap _ f (Attached c) = f c
src/Crypto/Store/Error.hs view
@@ -32,6 +32,10 @@ -- ^ MAC verification failed, incorrect key or password? | BadChecksum -- ^ Checksum verification failed, incorrect key or password?+ | DigestMismatch+ -- ^ Digest verification failed+ | SignatureNotVerified+ -- ^ Signature verification failed | InvalidInput String -- ^ Some condition is not met about input to algorithm | InvalidPassword String
src/Crypto/Store/PKCS12.hs view
@@ -53,7 +53,9 @@ , setLocalKeyId -- * Credentials , fromCredential+ , fromNamedCredential , toCredential+ , toNamedCredential -- * Password-based protection , Password , OptProtected(..)@@ -68,9 +70,11 @@ import Data.ASN1.Encoding import qualified Data.ByteArray as B import qualified Data.ByteString as BS-import Data.Maybe (fromMaybe)+import Data.List (partition)+import Data.Maybe (fromMaybe, mapMaybe) import Data.Semigroup import qualified Data.X509 as X509+import qualified Data.X509.Validation as X509 import Crypto.Cipher.Types @@ -280,7 +284,7 @@ cont = asn1Container (Container Context 0) inner inner = asn1Container Sequence (gIntVal 0 . eci) eci = encryptedContentInfoASN1S- (DataType, encryptionAlgorithm, encryptedData)+ (DataType, encryptionAlgorithm, Attached encryptedData) instance Monoid e => ParseASN1Object e ASElement where parse = onNextContainer Sequence $ do@@ -296,7 +300,7 @@ parseUnencrypted = parseOctetStringObject "PKCS12" parseEncrypted = onNextContainer Sequence $ do IntVal 0 <- getNext- (DataType, eScheme, ed) <- parseEncryptedContentInfo+ (DataType, eScheme, Attached ed) <- parseEncryptedContentInfo return PKCS5 { encryptionAlgorithm = eScheme, encryptedData = ed } @@ -454,23 +458,43 @@ instance Monoid e => ParseASN1Object e SafeContents where parse = SafeContents <$> onNextContainer Sequence parse --- | Return all private keys contained in the safe contents.-getSafeKeys :: SafeContents -> [OptProtected X509.PrivKey]-getSafeKeys (SafeContents scs) = loop scs+filterBags :: ([Attribute] -> Bool) -> SafeContents -> SafeContents+filterBags p (SafeContents scs) = SafeContents (mapMaybe f scs) where+ f (Bag (SafeContentsBag inner) attrs) =+ Just (Bag (SafeContentsBag $ filterBags p inner) attrs)+ f bag | p (bagAttributes bag) = Just bag+ | otherwise = Nothing++filterByFriendlyName :: String -> SafeContents -> SafeContents+filterByFriendlyName name = filterBags ((== Just name) . getFriendlyName)++filterByLocalKeyId :: BS.ByteString -> SafeContents -> SafeContents+filterByLocalKeyId d = filterBags ((== Just d) . getLocalKeyId)++getSafeKeysId :: SafeContents -> [OptProtected (Id X509.PrivKey)]+getSafeKeysId (SafeContents scs) = loop scs+ where loop [] = [] loop (bag : bags) = case bagInfo bag of- KeyBag (FormattedKey _ k) -> Unprotected k : loop bags- PKCS8ShroudedKeyBag k -> Protected (unshroud k) : loop bags- SafeContentsBag inner -> getSafeKeys inner ++ loop bags+ KeyBag (FormattedKey _ k) -> Unprotected (mkId k bag) : loop bags+ PKCS8ShroudedKeyBag k -> Protected (unshroud k bag) : loop bags+ SafeContentsBag inner -> getSafeKeysId inner ++ loop bags _ -> loop bags - unshroud shrouded pwd = do+ unshroud shrouded bag pwd = do bs <- decrypt shrouded pwd FormattedKey _ k <- decode bs- return k+ return (mkId k bag) +-- | Return all private keys contained in the safe contents.+getSafeKeys :: SafeContents -> [OptProtected X509.PrivKey]+getSafeKeys = map (fmap unId) . getSafeKeysId++getAllSafeKeysId :: [SafeContents] -> OptProtected [Id X509.PrivKey]+getAllSafeKeysId = applySamePassword . concatMap getSafeKeysId+ -- | Return all private keys contained in the safe content list. All shrouded -- private keys must derive from the same password. --@@ -481,32 +505,38 @@ getAllSafeKeys :: [SafeContents] -> OptProtected [X509.PrivKey] getAllSafeKeys = applySamePassword . concatMap getSafeKeys --- | Return all X.509 certificates contained in the safe contents.-getSafeX509Certs :: SafeContents -> [X509.SignedCertificate]-getSafeX509Certs (SafeContents scs) = loop scs+getSafeX509CertsId :: SafeContents -> [Id X509.SignedCertificate]+getSafeX509CertsId (SafeContents scs) = loop scs where loop [] = [] loop (bag : bags) = case bagInfo bag of- CertBag (Bag (CertX509 c) _) -> c : loop bags- SafeContentsBag inner -> getSafeX509Certs inner ++ loop bags+ CertBag (Bag (CertX509 c) _) -> mkId c bag : loop bags+ SafeContentsBag inner -> getSafeX509CertsId inner ++ loop bags _ -> loop bags +-- | Return all X.509 certificates contained in the safe contents.+getSafeX509Certs :: SafeContents -> [X509.SignedCertificate]+getSafeX509Certs = map unId . getSafeX509CertsId+ -- | Return all X.509 certificates contained in the safe content list. getAllSafeX509Certs :: [SafeContents] -> [X509.SignedCertificate] getAllSafeX509Certs = concatMap getSafeX509Certs --- | Return all X.509 CRLs contained in the safe contents.-getSafeX509CRLs :: SafeContents -> [X509.SignedCRL]-getSafeX509CRLs (SafeContents scs) = loop scs+getSafeX509CRLsId :: SafeContents -> [Id X509.SignedCRL]+getSafeX509CRLsId (SafeContents scs) = loop scs where loop [] = [] loop (bag : bags) = case bagInfo bag of- CRLBag (Bag (CRLX509 c) _) -> c : loop bags- SafeContentsBag inner -> getSafeX509CRLs inner ++ loop bags+ CRLBag (Bag (CRLX509 c) _) -> mkId c bag : loop bags+ SafeContentsBag inner -> getSafeX509CRLsId inner ++ loop bags _ -> loop bags +-- | Return all X.509 CRLs contained in the safe contents.+getSafeX509CRLs :: SafeContents -> [X509.SignedCRL]+getSafeX509CRLs = map unId . getSafeX509CRLsId+ -- | Return all X.509 CRLs contained in the safe content list. getAllSafeX509CRLs :: [SafeContents] -> [X509.SignedCRL] getAllSafeX509CRLs = concatMap getSafeX509CRLs@@ -515,14 +545,31 @@ -- Conversion to/from credentials getInnerCredential :: [SafeContents] -> SamePassword (Maybe (X509.CertificateChain, X509.PrivKey))-getInnerCredential l = SamePassword (fn <$> getAllSafeKeys l)+getInnerCredential l = SamePassword (fn <$> getAllSafeKeysId l) where- certs = getAllSafeX509Certs l-- fn [] = Nothing- fn [k] | null certs = Nothing- | otherwise = Just (X509.CertificateChain certs, k)- fn _ = Nothing+ certs = getAllSafeX509Certs l+ fn idKeys = do+ iKey <- single idKeys+ let k = unId iKey+ case idKeyId iKey of+ Just d -> do+ -- locate a single certificate with same ID as private key+ -- and follow the issuers to get all certificates in the chain+ let filtered = map (filterByLocalKeyId d) l+ leaf <- single (getAllSafeX509Certs filtered)+ pure (buildCertificateChain leaf certs, k)+ Nothing ->+ case idName iKey of+ Just name -> do+ -- same but using friendly name of private key+ let filtered = map (filterByFriendlyName name) l+ leaf <- single (getAllSafeX509Certs filtered)+ pure (buildCertificateChain leaf certs, k)+ Nothing -> do+ -- no identifier available, so we simply return all+ -- certificates with input order+ guard (not $ null certs)+ pure (X509.CertificateChain certs, k) -- | Extract the private key and certificate chain from a 'PKCS12' value. A -- credential is returned when the structure contains exactly one private key@@ -531,6 +578,23 @@ toCredential p12 = unSamePassword (SamePassword (unPKCS12 p12) >>= getInnerCredential) +getInnerCredentialNamed :: String -> [SafeContents] -> SamePassword (Maybe (X509.CertificateChain, X509.PrivKey))+getInnerCredentialNamed name l = SamePassword (fn <$> getAllSafeKeys filtered)+ where+ certs = getAllSafeX509Certs l+ filtered = map (filterByFriendlyName name) l+ fn keys = do+ k <- single keys+ leaf <- single (getAllSafeX509Certs filtered)+ pure (buildCertificateChain leaf certs, k)++-- | Extract a private key and certificate chain with the specified friendly+-- name from a 'PKCS12' value. A credential is returned when the structure+-- contains exactly one private key and one X.509 certificate with the name.+toNamedCredential :: String -> PKCS12 -> OptProtected (Maybe (X509.CertificateChain, X509.PrivKey))+toNamedCredential name p12 = unSamePassword $+ SamePassword (unPKCS12 p12) >>= getInnerCredentialNamed name+ -- | Build a 'PKCS12' value containing a private key and certificate chain. -- Distinct encryption is applied for both. Encrypting the certificate chain is -- optional.@@ -542,7 +606,30 @@ -> Password -> (X509.CertificateChain, X509.PrivKey) -> Either StoreError PKCS12-fromCredential algChain algKey pwd (X509.CertificateChain certs, key)+fromCredential = fromCredential' id++-- | Build a 'PKCS12' value containing a private key and certificate chain+-- identified with the specified friendly name. Distinct encryption is applied+-- for private key and certificates. Encrypting the certificate chain is+-- optional.+--+-- Note: advice is to always generate fresh and independent 'EncryptionScheme'+-- values so that the salt is not reused twice in the encryption process.+fromNamedCredential :: String+ -> Maybe EncryptionScheme -- for certificates+ -> EncryptionScheme -- for private key+ -> Password+ -> (X509.CertificateChain, X509.PrivKey)+ -> Either StoreError PKCS12+fromNamedCredential name = fromCredential' (setFriendlyName name)++fromCredential' :: ([Attribute] -> [Attribute])+ -> Maybe EncryptionScheme -- for certificates+ -> EncryptionScheme -- for private key+ -> Password+ -> (X509.CertificateChain, X509.PrivKey)+ -> Either StoreError PKCS12+fromCredential' trans algChain algKey pwd (X509.CertificateChain certs, key) | null certs = Left (InvalidInput "Empty certificate chain") | otherwise = (<>) <$> pkcs12Chain <*> pkcs12Key where@@ -552,14 +639,17 @@ Just alg -> encrypted alg pwd scChain Nothing -> Right (unencrypted scChain) - scChain = SafeContents (map toCertBag certs)- toCertBag c = Bag (CertBag (Bag (CertX509 c) [])) []+ scChain = SafeContents (zipWith toCertBag certAttrs certs)+ certAttrs = attrs : repeat []+ toCertBag a c = Bag (CertBag (Bag (CertX509 c) [])) a scKeyOrError = wrap <$> encrypt algKey pwd encodedKey - wrap shrouded = SafeContents [Bag (PKCS8ShroudedKeyBag shrouded) []]+ wrap shrouded = SafeContents [Bag (PKCS8ShroudedKeyBag shrouded) attrs] encodedKey = encodeASN1Object (FormattedKey PKCS8Format key) + X509.Fingerprint keyId = X509.getFingerprint (head certs) X509.HashSHA1+ attrs = trans (setLocalKeyId keyId []) -- Standard attributes @@ -633,6 +723,20 @@ applySamePassword :: [OptProtected a] -> OptProtected [a] applySamePassword = unSamePassword . traverse SamePassword +single :: [a] -> Maybe a+single [x] = Just x+single _ = Nothing++data Id a = Id+ { unId :: a+ , idKeyId :: Maybe BS.ByteString+ , idName :: Maybe String+ }++mkId :: a -> Bag info -> Id a+mkId val bag = val `seq` Id val (getLocalKeyId attrs) (getFriendlyName attrs)+ where attrs = bagAttributes bag+ decode :: ParseASN1Object [ASN1Event] obj => BS.ByteString -> Either StoreError obj decode bs = case decodeASN1Repr' BER bs of@@ -650,3 +754,20 @@ case decode bs of Left e -> throwParseError (name ++ ": " ++ show e) Right c -> return c++buildCertificateChain :: X509.SignedCertificate -> [X509.SignedCertificate]+ -> X509.CertificateChain+buildCertificateChain leaf authorities =+ X509.CertificateChain (leaf : findAuthorities leaf authorities)+ where+ findAuthorities cert others+ | subject cert == issuer cert = []+ | otherwise =+ case partition (\c -> subject c == issuer cert) others of+ ([c], others') -> c : findAuthorities c others'+ _ -> []++ signedCert = X509.signedObject . X509.getSigned++ subject c = X509.certSubjectDN (signedCert c)+ issuer c = X509.certIssuerDN (signedCert c)
src/Crypto/Store/PKCS5/PBES1.hs view
@@ -267,6 +267,7 @@ getV (DigestAlgorithm SHA256) = 64 getV (DigestAlgorithm SHA384) = 128 getV (DigestAlgorithm SHA512) = 128+getV t = error ("pkcs12Derive: unsupported hash: " ++ show t) hashFromProxy :: proxy a -> a hashFromProxy _ = undefined
src/Crypto/Store/PKCS8.hs view
@@ -51,12 +51,18 @@ import Data.ASN1.BinaryEncoding import Data.ASN1.BitArray import Data.ASN1.Encoding+import Data.ByteArray (ByteArrayAccess, convert) import Data.Maybe import qualified Data.X509 as X509 import qualified Data.ByteString as B+import Crypto.Error import Crypto.Number.Serialize (i2osp, i2ospOf_, os2ip)+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.ECDSA as ECDSA+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Crypto.PubKey.Ed448 as Ed448 import qualified Crypto.PubKey.RSA as RSA import Crypto.Store.ASN1.Generate@@ -129,12 +135,20 @@ rsa = X509.PrivKeyRSA . unFormat <$> parse dsa = X509.PrivKeyDSA . DSA.toPrivateKey . unFormat <$> parse ecdsa = X509.PrivKeyEC . unFormat <$> parse+ x25519 = X509.PrivKeyX25519 <$> parseModern+ x448 = X509.PrivKeyX448 <$> parseModern+ ed25519 = X509.PrivKeyEd25519 <$> parseModern+ ed448 = X509.PrivKeyEd448 <$> parseModern encrypted = inner . decrypt <$> parse getParser "PRIVATE KEY" = Unprotected <$> allTypes getParser "RSA PRIVATE KEY" = Unprotected <$> rsa getParser "DSA PRIVATE KEY" = Unprotected <$> dsa getParser "EC PRIVATE KEY" = Unprotected <$> ecdsa+ getParser "X25519 PRIVATE KEY" = Unprotected <$> x25519+ getParser "X448 PRIVATE KEY" = Unprotected <$> x448+ getParser "ED25519 PRIVATE KEY" = Unprotected <$> ed25519+ getParser "ED448 PRIVATE KEY" = Unprotected <$> ed448 getParser "ENCRYPTED PRIVATE KEY" = Protected <$> encrypted getParser _ = empty @@ -198,8 +212,13 @@ X509.PrivKeyRSA k -> ("RSA", traditional k) X509.PrivKeyDSA k -> ("DSA", traditional (dsaPrivToPair k)) X509.PrivKeyEC k -> ("EC", traditional k)+ X509.PrivKeyX25519 k -> ("X25519", tradModern k)+ X509.PrivKeyX448 k -> ("X448", tradModern k)+ X509.PrivKeyEd25519 k -> ("ED25519", tradModern k)+ X509.PrivKeyEd448 k -> ("ED448", tradModern k) where traditional a = asn1s (Traditional a)+ tradModern a = asn1s (Modern [] a) keyToModernPEM :: X509.PrivKey -> PEM keyToModernPEM privKey = mkPEM "PRIVATE KEY" (encodeASN1S asn1)@@ -211,6 +230,10 @@ X509.PrivKeyRSA k -> modern k X509.PrivKeyDSA k -> modern (dsaPrivToPair k) X509.PrivKeyEC k -> modern k+ X509.PrivKeyX25519 k -> modern k+ X509.PrivKeyX448 k -> modern k+ X509.PrivKeyEd25519 k -> modern k+ X509.PrivKeyEd448 k -> modern k where modern a = asn1s (Modern attrs a) @@ -263,7 +286,7 @@ asn1s (FormattedKey PKCS8Format k) = asn1s (Modern [] k) instance (Monoid e, ParseASN1Object e (Traditional a), ParseASN1Object e (Modern a)) => ParseASN1Object e (FormattedKey a) where- parse = (traditional <$> parseTraditional) <|> (modern <$> parseModern)+ parse = (modern <$> parseModern) <|> (traditional <$> parseTraditional) where traditional = FormattedKey TraditionalFormat modern = FormattedKey PKCS8Format@@ -292,11 +315,15 @@ asn1s (Modern attrs privKey) = modernPrivKeyASN1S attrs privKey instance Monoid e => ParseASN1Object e (Modern X509.PrivKey) where- parse = rsa <|> dsa <|> ecdsa+ parse = rsa <|> dsa <|> ecdsa <|> x25519 <|> x448 <|> ed25519 <|> ed448 where rsa = fmap X509.PrivKeyRSA <$> parse dsa = fmap (X509.PrivKeyDSA . DSA.toPrivateKey) <$> parse ecdsa = fmap X509.PrivKeyEC <$> parse+ x25519 = fmap X509.PrivKeyX25519 <$> parse+ x448 = fmap X509.PrivKeyX448 <$> parse+ ed25519 = fmap X509.PrivKeyEd25519 <$> parse+ ed448 = fmap X509.PrivKeyEd448 <$> parse skipVersion :: Monoid e => ParseASN1 e () skipVersion = do@@ -349,11 +376,7 @@ IntVal pexp1 <- getNext IntVal pexp2 <- getNext IntVal pcoef <- getNext- let calculate_modulus m i =- if (2 ^ (i * 8)) > m- then i- else calculate_modulus m (i+1)- pubKey = RSA.PublicKey { RSA.public_size = calculate_modulus n 1+ let pubKey = RSA.PublicKey { RSA.public_size = numBytes n , RSA.public_n = n , RSA.public_e = e }@@ -598,3 +621,89 @@ then do BitString seed <- getNext return (os2ip $ bitArrayGetData seed) else return 0+++-- X25519, X448, Ed25519, Ed448++instance ASN1Elem e => ProduceASN1Object e (Modern X25519.SecretKey) where+ asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)+ where+ v = gIntVal 0+ alg = asn1Container Sequence (gOID [1,3,101,110])+ bs = innerEddsaASN1S privKey+ att = attributesASN1S (Container Context 0) attrs++instance Monoid e => ParseASN1Object e (Modern X25519.SecretKey) where+ parse = onNextContainer Sequence $ do+ skipVersion+ onNextContainer Sequence $ do { OID [1,3,101,110] <- getNext; return () }+ (attrs, bs) <- parseAttrKeys+ Modern attrs <$> parseInnerEddsa "X25519" X25519.secretKey bs++instance ASN1Elem e => ProduceASN1Object e (Modern X448.SecretKey) where+ asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)+ where+ v = gIntVal 0+ alg = asn1Container Sequence (gOID [1,3,101,111])+ bs = innerEddsaASN1S privKey+ att = attributesASN1S (Container Context 0) attrs++instance Monoid e => ParseASN1Object e (Modern X448.SecretKey) where+ parse = onNextContainer Sequence $ do+ skipVersion+ onNextContainer Sequence $ do { OID [1,3,101,111] <- getNext; return () }+ (attrs, bs) <- parseAttrKeys+ Modern attrs <$> parseInnerEddsa "X448" X448.secretKey bs++instance ASN1Elem e => ProduceASN1Object e (Modern Ed25519.SecretKey) where+ asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)+ where+ v = gIntVal 0+ alg = asn1Container Sequence (gOID [1,3,101,112])+ bs = innerEddsaASN1S privKey+ att = attributesASN1S (Container Context 0) attrs++instance Monoid e => ParseASN1Object e (Modern Ed25519.SecretKey) where+ parse = onNextContainer Sequence $ do+ skipVersion+ onNextContainer Sequence $ do { OID [1,3,101,112] <- getNext; return () }+ (attrs, bs) <- parseAttrKeys+ Modern attrs <$> parseInnerEddsa "Ed25519" Ed25519.secretKey bs++instance ASN1Elem e => ProduceASN1Object e (Modern Ed448.SecretKey) where+ asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)+ where+ v = gIntVal 0+ alg = asn1Container Sequence (gOID [1,3,101,113])+ bs = innerEddsaASN1S privKey+ att = attributesASN1S (Container Context 0) attrs++instance Monoid e => ParseASN1Object e (Modern Ed448.SecretKey) where+ parse = onNextContainer Sequence $ do+ skipVersion+ onNextContainer Sequence $ do { OID [1,3,101,113] <- getNext; return () }+ (attrs, bs) <- parseAttrKeys+ Modern attrs <$> parseInnerEddsa "Ed448" Ed448.secretKey bs++innerEddsaASN1S :: (ASN1Elem e, ByteArrayAccess key) => key -> ASN1Stream e+innerEddsaASN1S key = gOctetString (encodeASN1S inner)+ where inner = gOctetString (convert key)++parseInnerEddsa :: Monoid e+ => String+ -> (B.ByteString -> CryptoFailable key)+ -> B.ByteString+ -> ParseASN1 e key+parseInnerEddsa name buildKey input =+ case either strError (runParseASN1 parser) (decodeASN1' BER input) of+ Left err -> throwParseError ("PKCS8: error parsing inner " ++ name ++ ": " ++ err)+ Right privKey -> return privKey+ where+ innerMsg = "PKCS8: error decoding inner " ++ name ++ ": "+ strError = Left . (innerMsg ++) . show+ parser = do+ OctetString bs <- getNext+ case buildKey bs of+ CryptoPassed privKey -> return privKey+ CryptoFailed _ ->+ throwParseError ("PKCS8: parsed invalid " ++ name ++ " secret key")
src/Crypto/Store/PKCS8/EC.hs view
@@ -22,7 +22,7 @@ import Data.X509 import Data.X509.EC -import Crypto.Number.Basic (numBits)+import Crypto.Number.Basic (numBits, numBytes) import Crypto.Number.Serialize (i2ospOf_) import Crypto.PubKey.ECC.Prim import Crypto.PubKey.ECC.Types@@ -32,10 +32,6 @@ -- | Number of bytes necessary to serialize n bits. bitsToBytes :: Int -> Int bitsToBytes n = (n + 7) `div` 8---- | Number of bytes necessary to serialize an integer.-numBytes :: Integer -> Int-numBytes = bitsToBytes . numBits -- | Number of bytes to serialize a field element. curveSizeBytes :: Curve -> Int
src/Crypto/Store/Util.hs view
@@ -7,6 +7,7 @@ -- -- {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE MagicHash #-} module Crypto.Store.Util ( (&&!)@@ -30,8 +31,12 @@ infixr 3 &&! -- | Reverse a bytearray.-reverseBytes :: (ByteArrayAccess bin, ByteArray bout) => bin -> bout+reverseBytes :: ByteArray ba => ba -> ba+#if MIN_VERSION_memory(0,14,18)+reverseBytes = B.reverse+#else reverseBytes = B.pack . reverse . B.unpack+#endif -- | Test if all bytes in a bytearray are equal to the value specified. Runs in -- constant time.
src/Crypto/Store/X509.hs view
@@ -40,6 +40,7 @@ import Data.Proxy import qualified Data.X509 as X509 import qualified Data.ByteString as B+import Crypto.Number.Basic (numBytes) import qualified Crypto.PubKey.RSA as RSA import Crypto.Store.ASN1.Generate@@ -167,12 +168,8 @@ parse = onNextContainer Sequence $ do IntVal modulus <- getNext IntVal pubexp <- getNext- let pub = RSA.PublicKey { RSA.public_size = calculate_modulus modulus 1+ let pub = RSA.PublicKey { RSA.public_size = numBytes modulus , RSA.public_n = modulus , RSA.public_e = pubexp } return (RSAPublicKey pub)- where- calculate_modulus n i- | (2 ^ (i * 8)) > n = i- | otherwise = calculate_modulus n (i + 1)
tests/CMS/Instances.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DataKinds #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | Orphan instances. module CMS.Instances@@ -11,6 +12,7 @@ import Data.ASN1.Types import qualified Data.ByteArray as B import Data.ByteString (ByteString)+import Data.Proxy import Data.X509 import Test.Tasty.QuickCheck@@ -34,25 +36,28 @@ if n == 0 then DataCI <$> arbitraryMessage else oneof [ DataCI <$> arbitraryMessage- , arbitrarySignedData- , arbitraryEnvelopedData- , arbitraryDigestedData- , arbitraryEncryptedData- , arbitraryAuthenticatedData- , arbitraryAuthEnvelopedData+ , arbitraryMode <*> arbitrarySignedData+ , arbitraryMode <*> arbitraryEnvelopedData+ , arbitraryMode <*> arbitraryDigestedData+ , arbitraryMode <*> arbitraryEncryptedData+ , arbitraryMode <*> arbitraryAuthenticatedData+ , arbitraryMode <*> arbitraryAuthEnvelopedData ] where arbitraryMessage :: Gen ByteString arbitraryMessage = resize 2048 (B.pack <$> arbitrary) - arbitrarySignedData :: Gen ContentInfo+ arbitraryMode :: Encapsulates struct => Gen (struct ByteString -> ContentInfo)+ arbitraryMode = elements [ toAttachedCI, snd . toDetachedCI ]++ arbitrarySignedData :: Gen (SignedData EncapsulatedContent) arbitrarySignedData = do alg <- arbitrary (sigFns, _) <- arbitrarySigVer alg inner <- scale (subtract $ length sigFns) arbitrary signData sigFns inner >>= failIfError - arbitraryEnvelopedData :: Gen ContentInfo+ arbitraryEnvelopedData :: Gen (EnvelopedData EncryptedContent) arbitraryEnvelopedData = do oinfo <- arbitrary (alg, key, attrs) <- getCommon@@ -60,19 +65,19 @@ inner <- scale (subtract $ length envFns) arbitrary envelopData oinfo key alg envFns attrs inner >>= failIfError - arbitraryDigestedData :: Gen ContentInfo+ arbitraryDigestedData :: Gen (DigestedData EncapsulatedContent) arbitraryDigestedData = do inner <- scale pred arbitrary dt <- arbitrary return $ digestData dt inner - arbitraryEncryptedData :: Gen ContentInfo+ arbitraryEncryptedData :: Gen (EncryptedData EncryptedContent) arbitraryEncryptedData = do (alg, key, attrs) <- getCommon inner <- scale pred arbitrary failIfError $ encryptData key alg attrs inner - arbitraryAuthenticatedData :: Gen ContentInfo+ arbitraryAuthenticatedData :: Gen (AuthenticatedData EncapsulatedContent) arbitraryAuthenticatedData = do (oinfo, alg, key, envFns, aAttrs, uAttrs) <- getCommonAuth dig <- arbitrary@@ -80,7 +85,7 @@ generateAuthenticatedData oinfo key alg dig envFns aAttrs uAttrs inner >>= failIfError - arbitraryAuthEnvelopedData :: Gen ContentInfo+ arbitraryAuthEnvelopedData :: Gen (AuthEnvelopedData EncryptedContent) arbitraryAuthEnvelopedData = do (oinfo, alg, key, envFns, aAttrs, uAttrs) <- getCommonAuth inner <- scale (subtract $ length envFns) arbitrary@@ -118,16 +123,26 @@ arbitraryAttributes :: Gen [Attribute] arbitraryAttributes = resize 3 arbitrary +p256 :: Proxy 256+p256 = Proxy++p512 :: Proxy 512+p512 = Proxy+ instance Arbitrary DigestAlgorithm where- arbitrary = elements- [ DigestAlgorithm MD2- , DigestAlgorithm MD4- , DigestAlgorithm MD5- , DigestAlgorithm SHA1- , DigestAlgorithm SHA224- , DigestAlgorithm SHA256- , DigestAlgorithm SHA384- , DigestAlgorithm SHA512+ arbitrary = oneof+ [ pure $ DigestAlgorithm MD2+ , pure $ DigestAlgorithm MD4+ , pure $ DigestAlgorithm MD5+ , pure $ DigestAlgorithm SHA1+ , pure $ DigestAlgorithm SHA224+ , pure $ DigestAlgorithm SHA256+ , pure $ DigestAlgorithm SHA384+ , pure $ DigestAlgorithm SHA512+ , pure $ DigestAlgorithm SHAKE128_256+ , pure $ DigestAlgorithm SHAKE256_512+ , pure $ DigestAlgorithm (SHAKE128 p256)+ , pure $ DigestAlgorithm (SHAKE256 p512) ] arbitraryIntegrityDigest :: Gen DigestAlgorithm@@ -184,6 +199,9 @@ , pure $ ECDSA (DigestAlgorithm SHA256) , pure $ ECDSA (DigestAlgorithm SHA384) , pure $ ECDSA (DigestAlgorithm SHA512)++ , pure Ed25519+ , pure Ed448 ] arbitraryKeyPair :: SignatureAlg -> Gen (PubKey, PrivKey)@@ -202,6 +220,12 @@ arbitraryKeyPair (ECDSA _) = do (pub, priv) <- arbitraryNamedEC return (PubKeyEC pub, PrivKeyEC priv)+arbitraryKeyPair Ed25519 = do+ (pub, priv) <- arbitraryEd25519+ return (PubKeyEd25519 pub, PrivKeyEd25519 priv)+arbitraryKeyPair Ed448 = do+ (pub, priv) <- arbitraryEd448+ return (PubKeyEd448 pub, PrivKeyEd448 priv) arbitrarySigVer :: SignatureAlg -> Gen ([ProducerOfSI Gen], ConsumerOfSI Gen) arbitrarySigVer alg = sized $ \n -> do@@ -352,11 +376,13 @@ kid <- arbitrarySmall KeyIdentifier kid Nothing <$> arbitrary -arbitraryAgreeParams :: KeyEncryptionParams -> Gen KeyAgreementParams-arbitraryAgreeParams alg = oneof- [ flip StdDH alg <$> arbitraryDigest- , flip CofactorDH alg <$> arbitraryDigest- ]+arbitraryAgreeParams :: Bool -> KeyEncryptionParams -> Gen KeyAgreementParams+arbitraryAgreeParams allowCofactorDH alg+ | allowCofactorDH = oneof+ [ flip StdDH alg <$> arbitraryDigest+ , flip CofactorDH alg <$> arbitraryDigest+ ]+ | otherwise = flip StdDH alg <$> arbitraryDigest where arbitraryDigest = elements@@ -387,11 +413,14 @@ return (envFn, devFn) arbitraryKA = do- (pub, priv) <- arbitraryNamedEC- cert <- arbitrarySignedCertificate (PubKeyEC pub)- kap <- arbitraryAlg >>= arbitraryAgreeParams+ (cert, priv) <- arbitraryDHParams+ let allowCofactorDH =+ case priv of+ PrivKeyEC _ -> True+ _ -> False+ kap <- arbitraryAlg >>= arbitraryAgreeParams allowCofactorDH let envFn = forKeyAgreeRecipient cert kap- devFn = withRecipientKeyAgree (PrivKeyEC priv) cert+ devFn = withRecipientKeyAgree priv cert return (envFn, devFn) arbitraryKEK = do@@ -430,6 +459,26 @@ , return AES256_WRAP_PAD , RC2_WRAP <$> choose (1, 1024) ]++ arbitraryDHParams = oneof [ arbitraryCredNamedEC+ , arbitraryCredX25519+ , arbitraryCredX448+ ]++ arbitraryCredNamedEC = do+ (pub, priv) <- arbitraryNamedEC+ cert <- arbitrarySignedCertificate (PubKeyEC pub)+ return (cert, PrivKeyEC priv)++ arbitraryCredX25519 = do+ (pub, priv) <- arbitraryX25519+ cert <- arbitrarySignedCertificate (PubKeyX25519 pub)+ return (cert, PrivKeyX25519 priv)++ arbitraryCredX448 = do+ (pub, priv) <- arbitraryX448+ cert <- arbitrarySignedCertificate (PubKeyX448 pub)+ return (cert, PrivKeyX448 priv) -- key wrapping in PWRIKEK is incompatible with CTR mode so we must never -- generate this combination
tests/CMS/Tests.hs view
@@ -49,8 +49,17 @@ count = 1 signedDataTests :: TestTree-signedDataTests =- testCaseSteps "SignedData" $ \step -> do+signedDataTests = signedDataAnyTests "SignedData" path getAttached+ where path = testFile "cms-signed-data.pem"++signedDataDetachedTests :: TestTree+signedDataDetachedTests =+ signedDataAnyTests "SignedDataDetached" path (getDetached message)+ where path = testFile "cms-signed-data-detached.pem"++signedDataAnyTests :: TestName -> FilePath -> (SignedData (Encap EncapsulatedContent) -> IO (SignedData EncapsulatedContent)) -> TestTree+signedDataAnyTests caseName path getInner =+ testCaseSteps caseName $ \step -> do cms <- readCMSFile path assertEqual "unexpected parse count" (length names) (length cms) @@ -59,11 +68,11 @@ step ("verifying " ++ name) assertBool "unexpected type" (hasType SignedDataType ci)- let SignedDataCI sd = ci+ let SignedDataCI sdEncap = ci+ sd <- getInner sdEncap result <- verifySignedData withSignerKey sd- assertJust result (verifyInnerMessage message)- where path = testFile "cms-signed-data.pem"- names = [ "RSA"+ assertRight result (verifyInnerMessage message)+ where names = [ "RSA" , "DSA" , "EC (named curve)" , "EC (explicit prime curve)"@@ -86,8 +95,7 @@ let (name, key) = keys !! index step ("testing " ++ name)- assertBool "unexpected type" (hasType EnvelopedDataType ci)- let EnvelopedDataCI ev = ci+ ev <- getEnveloppedAttached ci result <- openEnvelopedData (f key) ev assertRight result (verifyInnerMessage message) testKT caseName path keys = testCaseSteps caseName $ \step -> do@@ -100,8 +108,7 @@ let pairs = [ (c, m) | c <- map fst keys1, m <- modes ] forM_ (zip pairs cms) $ \((c, m), ci) -> do step ("testing " ++ c ++ " with " ++ m)- assertBool "unexpected type" (hasType EnvelopedDataType ci)- let EnvelopedDataCI ev = ci+ ev <- getEnveloppedAttached ci result <- openEnvelopedData (withRecipientKeyTrans priv) ev assertRight result (verifyInnerMessage message) testKA caseName path keys = testCaseSteps caseName $ \step -> do@@ -116,10 +123,13 @@ let pairs = [ (c, h) | c <- map fst keys, h <- mds ] forM_ (zip pairs cms) $ \((c, h), ci) -> do step ("testing " ++ c ++ " with " ++ h)- assertBool "unexpected type" (hasType EnvelopedDataType ci)- let EnvelopedDataCI ev = ci+ ev <- getEnveloppedAttached ci result <- openEnvelopedData (withRecipientKeyAgree priv cert) ev assertRight result (verifyInnerMessage message)+ getEnveloppedAttached ci = do+ assertBool "unexpected type" (hasType EnvelopedDataType ci)+ let EnvelopedDataCI evEncap = ci+ getAttached evEncap path1 = testFile "cms-enveloped-kekri-data.pem" path2 = testFile "cms-enveloped-pwri-data.pem" path3 = testFile "cms-enveloped-ktri-data.pem"@@ -160,12 +170,14 @@ step ("verifying " ++ name) assertBool "unexpected type" (hasType DigestedDataType ci)- let DigestedDataCI dd = ci- result = digestVerify dd- assertJust result (verifyInnerMessage message)+ let DigestedDataCI ddEncap = ci+ dd <- getAttached ddEncap+ let result = digestVerify dd+ assertRight result (verifyInnerMessage message) step ("digesting " ++ name)- let ci' = digestData alg (DataCI message)+ let dd' = digestData alg (DataCI message)+ ci' = toAttachedCI dd' ci @?= ci' where path = testFile "cms-digested-data.pem" algs = [ ("MD5", DigestAlgorithm MD5)@@ -187,13 +199,15 @@ step ("decrypting " ++ name) assertBool "unexpected type" (hasType EncryptedDataType ci)- let EncryptedDataCI ed = ci- result = decryptData key ed+ let EncryptedDataCI edEncap = ci+ ed <- getAttached edEncap+ let result = decryptData key ed assertRight result (verifyInnerMessage message) step ("encrypting " ++ name) let params = edContentEncryptionParams ed- ci' = encryptData key params [] (DataCI message)+ ed' = encryptData key params [] (DataCI message)+ ci' = toAttachedCI <$> ed' Right ci @?= ci' where path = testFile "cms-encrypted-data.pem" keys = [ ("DES_CBC", testKey 8)@@ -223,13 +237,15 @@ forM_ (zip [0..] cms) $ \(index, ci) -> do step ("testing vector " ++ show (index :: Int)) assertBool "unexpected type" (hasType AuthEnvelopedDataType ci)- let AuthEnvelopedDataCI ae = ci+ let AuthEnvelopedDataCI aeEncap = ci+ ae <- getAttached aeEncap result <- openAuthEnvelopedData (withRecipientPassword pwd) ae assertRight result (verifyInnerMessage msg) step ("testing encoded vector " ++ show index) let [Just ci'] = pemToContentInfo [] (contentInfoToPEM ci)- AuthEnvelopedDataCI ae' = ci'+ AuthEnvelopedDataCI aeEncap' = ci'+ ae' <- getAttached aeEncap' result' <- openAuthEnvelopedData (withRecipientPassword pwd) ae' assertRight result' (verifyInnerMessage msg) where path = testFile "cms-auth-enveloped-data-rfc6476.pem"@@ -246,32 +262,32 @@ collect alg $ do (sigFns, verFn) <- scale succ (arbitrarySigVer alg) r <- signData sigFns ci- let Right (SignedDataCI sd) = r+ let Right sd = r r' <- verifySignedData verFn sd- return (Just ci === r')+ return (Right ci === r') , testProperty "enveloping" $ \alg ci -> collect alg $ do (oinfo, key, envFns, devFn, attrs) <- getCommon alg r <- envelopData oinfo key alg envFns attrs ci- let Right (EnvelopedDataCI ev) = r+ let Right ev = r r' <- openEnvelopedData devFn ev return (Right ci === r') , testProperty "digesting" $ \alg ci -> collect alg $- let DigestedDataCI dd = digestData alg ci- in Just ci === digestVerify dd+ let dd = digestData alg ci+ in Right ci === digestVerify dd , testProperty "encrypting" $ \alg ci -> collect alg $ do key <- generateKey alg attrs <- arbitraryAttributes- let Right (EncryptedDataCI ed) = encryptData key alg attrs ci+ let Right ed = encryptData key alg attrs ci return (Right ci === decryptData key ed) , testProperty "authenticating" $ \alg dig ci -> collect alg $ do (oinfo, key, envFns, devFn, uAttrs) <- getCommon alg aAttrs <- if isNothing dig then pure [] else arbitraryAttributes r <- generateAuthenticatedData oinfo key alg dig envFns aAttrs uAttrs ci- let Right (AuthenticatedDataCI ad) = r+ let Right ad = r r' <- verifyAuthenticatedData devFn ad return (Right ci === r') , testProperty "enveloping with authentication" $ \alg ci ->@@ -279,7 +295,7 @@ (oinfo, key, envFns, devFn, uAttrs) <- getCommon alg aAttrs <- arbitraryAttributes r <- authEnvelopData oinfo key alg envFns aAttrs uAttrs ci- let Right (AuthEnvelopedDataCI ae) = r+ let Right ae = r r' <- openAuthEnvelopedData devFn ae return (Right ci === r') ]@@ -303,6 +319,7 @@ testGroup "CMS" [ dataTests , signedDataTests+ , signedDataDetachedTests , envelopedDataTests , digestedDataTests , encryptedDataTests
tests/PKCS12/Instances.hs view
@@ -2,6 +2,7 @@ -- | Orphan instances. module PKCS12.Instances ( arbitraryPassword+ , arbitraryAlias , arbitraryIntegrityParams , arbitraryPKCS12 ) where@@ -20,6 +21,10 @@ arbitrarySmall :: Gen ByteString arbitrarySmall = resize 10 (B.pack <$> arbitrary)++arbitraryAlias :: Gen String+arbitraryAlias = resize 16 asciiChar+ where asciiChar = listOf $ choose ('\x20','\x7f') arbitraryIntegrityParams :: Gen IntegrityParams arbitraryIntegrityParams = (,) <$> arbitraryIntegrityDigest <*> arbitrary
tests/PKCS12/Tests.hs view
@@ -81,19 +81,28 @@ let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c return $ Right (Right (Right c)) === (fmap (recover pI) <$> r) , localOption (QuickCheckTests 20) $ testProperty "converting credentials" $+ \pChain pKey privKey ->+ testCredConv privKey toCredential (fromCredential pChain pKey)+ , localOption (QuickCheckTests 20) $ testProperty "converting named credentials" $ \pChain pKey privKey -> do- pwd <- arbitraryPassword- chain <- arbitrary >>= arbitraryCertificateChain- let cred = (chain, privKey)- pkcs12 = fromCredential pChain pKey pwd cred- r = pkcs12 >>= recover pwd . toCredential- return $ Right (Just cred) === r+ name <- arbitraryAlias+ testCredConv privKey+ (toNamedCredential name)+ (fromNamedCredential name pChain pKey) ]+ where+ testCredConv privKey to from = do+ pwd <- arbitraryPassword+ chain <- arbitrary >>= arbitraryCertificateChain+ chain' <- shuffleCertificateChain chain+ let cred = (chain, privKey)+ r = from pwd (chain', privKey) >>= recover pwd . to+ return $ Right (Just cred) === r pkcs12Tests :: TestTree pkcs12Tests = testGroup "PKCS12" [ testType "RSA" "rsa"- , testType "EC (named curve)" "ecdsa-p256"+ , testType "Ed25519" "ed25519" , propertyTests ]
tests/PKCS8/Instances.hs view
@@ -1,9 +1,12 @@+{-# LANGUAGE FlexibleInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | Orphan instances. module PKCS8.Instances ( arbitraryPassword ) where +import Data.X509+ import Test.Tasty.QuickCheck import Crypto.Store.CMS@@ -35,5 +38,13 @@ instance Arbitrary PrivateKeyFormat where arbitrary = elements [ TraditionalFormat, PKCS8Format ] -instance Arbitrary a => Arbitrary (FormattedKey a) where- arbitrary = FormattedKey <$> arbitrary <*> arbitrary+instance Arbitrary (FormattedKey PrivKey) where+ arbitrary = do+ key <- arbitrary+ fmt <- case key of+ PrivKeyX25519 _ -> return PKCS8Format+ PrivKeyX448 _ -> return PKCS8Format+ PrivKeyEd25519 _ -> return PKCS8Format+ PrivKeyEd448 _ -> return PKCS8Format+ _ -> arbitrary+ return (FormattedKey fmt key)
tests/PKCS8/Tests.hs view
@@ -97,5 +97,9 @@ , testType "DSA" "dsa" , testType "EC (named curve)" "ecdsa-p256" , testType "EC (explicit prime curve)" "ecdsa-epc"+ , testType "X25519" "x25519"+ , testType "X448" "x448"+ , testType "Ed25519" "ed25519"+ , testType "Ed448" "ed448" , propertyTests ]
tests/Util.hs view
@@ -3,14 +3,20 @@ module Util ( assertJust , assertRight+ , getAttached+ , getDetached , testFile , TestKey(..) , TestIV(..) ) where +import Control.Monad (when)+ import Data.ByteString (ByteString, pack)+import Data.Maybe (isNothing) import Crypto.Cipher.Types+import Crypto.Store.CMS import Test.Tasty.HUnit import Test.Tasty.QuickCheck@@ -23,6 +29,20 @@ assertRight (Right b) f = f b assertRight (Left val) _ = assertFailure ("expecting Right but got: Left " ++ show val)++getAttached :: Encapsulates struct => struct (Encap a) -> IO (struct a)+getAttached e = do+ let m = fromAttached e+ when (isNothing m) $+ assertFailure "expecting attached but got detached content"+ let Just r = m in return r++getDetached :: Encapsulates struct => a -> struct (Encap a) -> IO (struct a)+getDetached c e = do+ let m = fromDetached c e+ when (isNothing m) $+ assertFailure "expecting detached but got attached content"+ let Just r = m in return r testFile :: String -> FilePath testFile name = "tests/files/" ++ name
tests/X509/Instances.hs view
@@ -6,22 +6,34 @@ , arbitraryLargeRSA , arbitraryDSA , arbitraryNamedEC+ , arbitraryX25519+ , arbitraryX448+ , arbitraryEd25519+ , arbitraryEd448 , arbitrarySignedCertificate , arbitraryCertificateChain+ , shuffleCertificateChain ) where +import Control.Monad (zipWithM)+ import Data.ASN1.Types import qualified Data.ByteArray as B import Data.Hourglass+import Data.List (nub) import Data.X509 import Test.Tasty.QuickCheck import Crypto.Number.Serialize (i2ospOf_)+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.ECDSA as ECDSA import qualified Crypto.PubKey.ECC.Generate as ECC 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.RSA as RSA import Crypto.Random @@ -49,6 +61,10 @@ , PubKeyDSA . fst <$> arbitraryDSA , PubKeyEC . fst <$> arbitraryNamedEC --, PubKeyEC . fst <$> arbitraryExplicitPrimeCurve+ , PubKeyX25519 . fst <$> arbitraryX25519+ , PubKeyX448 . fst <$> arbitraryX448+ , PubKeyEd25519 . fst <$> arbitraryEd25519+ , PubKeyEd448 . fst <$> arbitraryEd448 ] instance Arbitrary PrivKey where@@ -56,6 +72,10 @@ , PrivKeyDSA . snd <$> arbitraryDSA , PrivKeyEC . snd <$> arbitraryNamedEC , PrivKeyEC . snd <$> arbitraryExplicitPrimeCurve+ , PrivKeyX25519 . snd <$> arbitraryX25519+ , PrivKeyX448 . snd <$> arbitraryX448+ , PrivKeyEd25519 . snd <$> arbitraryEd25519+ , PrivKeyEd448 . snd <$> arbitraryEd448 ] arbitraryRSA :: Gen (RSA.PublicKey, RSA.PrivateKey)@@ -193,6 +213,26 @@ curveSizeBytes :: ECC.Curve -> Int curveSizeBytes curve = (ECC.curveSizeBits curve + 7) `div` 8 +arbitraryX25519 :: Gen (X25519.PublicKey, X25519.SecretKey)+arbitraryX25519 = do+ priv <- X25519.generateSecretKey+ return (X25519.toPublic priv, priv)++arbitraryX448 :: Gen (X448.PublicKey, X448.SecretKey)+arbitraryX448 = do+ priv <- X448.generateSecretKey+ return (X448.toPublic priv, priv)++arbitraryEd25519 :: Gen (Ed25519.PublicKey, Ed25519.SecretKey)+arbitraryEd25519 = do+ priv <- Ed25519.generateSecretKey+ return (Ed25519.toPublic priv, priv)++arbitraryEd448 :: Gen (Ed448.PublicKey, Ed448.SecretKey)+arbitraryEd448 = do+ priv <- Ed448.generateSecretKey+ return (Ed448.toPublic priv, priv)+ instance Arbitrary SignatureALG where arbitrary = elements [ SignatureALG HashSHA1 PubKeyALG_RSA@@ -211,6 +251,9 @@ , SignatureALG HashSHA256 PubKeyALG_EC , SignatureALG HashSHA384 PubKeyALG_EC , SignatureALG HashSHA512 PubKeyALG_EC++ , SignatureALG_IntrinsicHash PubKeyALG_Ed25519+ , SignatureALG_IntrinsicHash PubKeyALG_Ed448 ] instance Arbitrary DateTime where@@ -218,17 +261,26 @@ let arbitraryElapsed = Elapsed . Seconds <$> choose (1, 100000000) in timeConvert <$> arbitraryElapsed -arbitraryCertificate :: PubKey -> Gen Certificate-arbitraryCertificate pubKey =+arbitraryCertificateWithDNs :: PubKey+ -> DistinguishedName -- issuer+ -> DistinguishedName -- subject+ -> Gen Certificate+arbitraryCertificateWithDNs pubKey issuerDN subjectDN = Certificate <$> pure 2 <*> arbitrary <*> arbitrary- <*> arbitraryDN+ <*> pure issuerDN <*> arbitrary- <*> arbitraryDN+ <*> pure subjectDN <*> pure pubKey <*> pure (Extensions Nothing) +arbitraryCertificate :: PubKey -> Gen Certificate+arbitraryCertificate pubKey = do+ issuerDN <- arbitraryDN+ subjectDN <- arbitraryDN+ arbitraryCertificateWithDNs pubKey issuerDN subjectDN+ instance Arbitrary Certificate where arbitrary = arbitrary >>= arbitraryCertificate @@ -262,6 +314,25 @@ arbitraryCertificateChain :: PubKey -> Gen CertificateChain arbitraryCertificateChain pubKey = do- leaf <- arbitrarySignedCertificate pubKey- others <- resize 3 $ listOf (arbitrary >>= arbitrarySignedCertificate)- return $ CertificateChain (leaf:others)+ rootDN <- arbitraryDN+ otherDNs <- nub <$> resize 3 (listOf (arbitraryDN `suchThat` (/= rootDN)))+ if null otherDNs+ then do+ root <- generateCert pubKey (rootDN, rootDN)+ return $ CertificateChain [root]+ else do+ rootKey <- arbitrary+ root <- generateCert rootKey (rootDN, rootDN)+ let dnPairs = zip (rootDN : otherDNs) otherDNs -- (issuer, subject)+ keys <- vectorOf (length otherDNs - 1) arbitrary+ certs <- zipWithM generateCert (keys ++ [pubKey]) dnPairs+ return $ CertificateChain $ reverse (root : certs)+ where+ generateCert key (issuerDN, subjectDN) =+ arbitraryCertificateWithDNs key issuerDN subjectDN+ >>= arbitrarySignedExact++shuffleCertificateChain :: CertificateChain -> Gen CertificateChain+shuffleCertificateChain (CertificateChain []) = fail "empty certificate chain"+shuffleCertificateChain (CertificateChain (leaf : auths)) =+ CertificateChain . (leaf :) <$> shuffle auths
tests/X509/Tests.hs view
@@ -66,5 +66,9 @@ , keyTests "DSA" "dsa" 1 , keyTests "EC (named curve)" "ecdsa-p256" 1 -- , keyTests "EC (explicit prime curve)" "ecdsa-epc" 1+ , keyTests "X25519" "x25519" 1+ , keyTests "X448" "x448" 1+ , keyTests "Ed25519" "ed25519" 1+ , keyTests "Ed448" "ed448" 1 , propertyTests ]
+ tests/files/cms-signed-data-detached.pem view
@@ -0,0 +1,120 @@+-----BEGIN CMS-----+MIIECwYJKoZIhvcNAQcCoIID/DCCA/gCAQExDTALBglghkgBZQMEAgEwCwYJKoZI+hvcNAQcBoIICFDCCAhAwggF5oAMCAQICCQD3DetZLCk3mzANBgkqhkiG9w0BAQsF+ADAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5+MzUxNFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFt+cGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtBG2kKWfYT1jUXmb+DlrkD3Wfw0rKDjTdjjSswM2HggMtEbiutc+cxnoW+DaeP1SzSdY7NXlaqvzftCPL+vzJNaRM4HpG/XLqIdub5hzisbrwN1/Q77zmUvr1ka7NaXKZDuuepORJsNh5lHkp2+2VR0O0pQ3zLNjX8IVAGkdpoE5iMCAwEAAaNQME4wHQYDVR0OBBYEFPyU0yv/vEPO+p1kMDzLvtT6U+gYOMB8GA1UdIwQYMBaAFPyU0yv/vEPOp1kMDzLvtT6U+gYOMAwG+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAn2yZT/WNupO34PxVdWU65X9q+mCE4AtZKLbRa0iSkkEY961oqDiMVWiaN0sPVeV1GoNCz9J8lLKaEkR8b0F4KlJ0K+44V+5P0Hvb1UnKV2SsNxkmNbxwjO8Q+9aNjd4IqxAnSyQ45yMN4K5x1COJFXQETJ+ApFWhLZxgA0hqnotQGcxggG9MIIBuQIBATAuMCExHzAdBgkqhkiG9w0BCQEWEHRl+c3RAZXhhbXBsZS5jb20CCQD3DetZLCk3mzALBglghkgBZQMEAgGggeQwGAYJKoZI+hvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkwMzE2MDkwMDM4+WjAvBgkqhkiG9w0BCQQxIgQgu747Zx6FPf4woOYFlDZvJPAvMeok/0ZRdD/WDHPN+aCIweQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASowCwYJYIZIAWUDBAEWMAsG+CWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0DAgICAIAwDQYIKoZIhvcN+AwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwDQYJKoZIhvcNAQEBBQAEgYCz+vhp05pvQNr25On3965Z+JXk6auQV9tl1bxI39jxIPxykJgoVKIC5b6FIMnzpPzZA+I66bK6Tx7dY5PTRcYImjkKgFpubNObwhFaNGeOyKh+W+o7Kic/Zy2nlWwGVMQ3ad+fCDfp0iR46yzoL94pWXrt+2DSQ7QzMRWvKy0SvNdWw==+-----END CMS-----+-----BEGIN CMS-----+MIIEegYJKoZIhvcNAQcCoIIEazCCBGcCAQExDTALBglghkgBZQMEAgEwCwYJKoZI+hvcNAQcBoIIC1jCCAtIwggKPoAMCAQICCQCEbw8q6JYKTTALBglghkgBZQMEAwIw+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTAeFw0xODA3MDEwOTM1+MTRaFw0xODA3MzEwOTM1MTRaMCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBs+ZS5jb20wggG2MIIBKwYHKoZIzjgEATCCAR4CgYEAu1pOSXz76spaDcSOsEx8vzAB++xC1LNppXIxeP/mQHim7ADlf5uNPAavPt1Ky7kkJHbdxj9sWXUG+BvgC1CSNoEkO+0b4A2OTkYdfgCjk8CSjNK4LgsLZcNDW+MMa4G7AooGAxTL+WbvMxMKmWly9eDODd+VVN2M7S2UCKPlIkoDjcCFQDcpQZHCPm2jIkDaMNf3WJggezQxwKBgDKLgJAp/VlG+YYflUdnJzcAWS04vg+FQaRIQhocrzaUJQ/ZfPCYtjCozt3gL9KNwFghXBklo082s+Qwin52NgohB2fBKIOsvBPX1VU8oXmtQ4vIN1L6kmPZkeZvEkOJdVojHBJehU1CDh+UDT+PEH3joy2s3N9AC1SE1UplbHVBsE5A4GEAAKBgH/LWxi51YZsuzDiIkXdqPAB++jiJqfccumYpG4m+nIbCaBDCMXxxKmfr/uQBcYFIbdAU4bK4WL7joHjCjXeA/DDz+zjD3wqFSKax7xFKbBmAK0eC0UdwVlnx9qeJ9Ypduk58+CBWqQkyFJCIlf0aJFlKE+y8BEvuPC6Hlu2nTc9SHeo1AwTjAdBgNVHQ4EFgQUTAOzcwRJKUUZaP/WO6LOlOLC+YwEwHwYDVR0jBBgwFoAUTAOzcwRJKUUZaP/WO6LOlOLCYwEwDAYDVR0TBAUwAwEB+/zALBglghkgBZQMEAwIDMAAwLQIVAKC36+0syuqwTPBd7MLJ7NBJS10XAhQRKGb0+d0Q8f19UOAr5ROp10HSxdTGCAWowggFmAgEBMC4wITEfMB0GCSqGSIb3DQEJARYQ+dGVzdEBleGFtcGxlLmNvbQIJAIRvDyrolgpNMAsGCWCGSAFlAwQCAaCB5DAYBgkq+hkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xOTAzMTYwOTAw+MzhaMC8GCSqGSIb3DQEJBDEiBCC7vjtnHoU9/jCg5gWUNm8k8C8x6iT/RlF0P9YM+c81oIjB5BgkqhkiG9w0BCQ8xbDBqMAsGCWCGSAFlAwQBKjALBglghkgBZQMEARYw+CwYJYIZIAWUDBAECMAoGCCqGSIb3DQMHMA4GCCqGSIb3DQMCAgIAgDANBggqhkiG+9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIBKDALBglghkgBZQMEAwIEMDAu+AhUAqTVaVNHUEv4tM84y/QLPWSr0cP0CFQCvlZDKBaa0qlBWE2NyJ574gyXqkQ==+-----END CMS-----+-----BEGIN CMS-----+MIIDRwYJKoZIhvcNAQcCoIIDODCCAzQCAQExDTALBglghkgBZQMEAgEwCwYJKoZI+hvcNAQcBoIIBjTCCAYkwggEvoAMCAQICCQCX+mbx0uA+ADAKBggqhkjOPQQDAjAh+MR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5MzUx+NFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl+LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHYqvg1E3p4xXBUK+72zfa3M+LmJIkfZDsYqRsZwRXJ7TzRxsbeVgBDVy3Zj6Y4ZSB/dK7jxs54KTECoDcXLvEASj+UDBOMB0GA1UdDgQWBBRT2nMGMWO1FT6OJajOnO9gp1hHVzAfBgNVHSMEGDAWgBRT+2nMGMWO1FT6OJajOnO9gp1hHVzAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMCA0gA+MEUCIAvA93qcJRkeyRffr9IKzYQXkZE30qnCBzlEfmfUClagAiEA0GQKzKiwGTv6+uV1pwjprqyI7h0dObEESk3KMQRa+5zcxggGAMIIBfAIBATAuMCExHzAdBgkqhkiG+9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+ADALBglghkgBZQMEAgGg+geQwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkw+MzE2MDkwMDM4WjAvBgkqhkiG9w0BCQQxIgQgu747Zx6FPf4woOYFlDZvJPAvMeok+/0ZRdD/WDHPNaCIweQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASowCwYJYIZI+AWUDBAEWMAsGCWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0DAgICAIAw+DQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwCgYIKoZIzj0E+AwIERzBFAiEA0zBWegmlEC9Zp6Nql04cSiCD0ZdlFzecaGoF+xmctUICID+cT8s9+dF/iJdrrTKh4+UmuAXO+PMfVRXwjP1TFBq4z+-----END CMS-----+-----BEGIN CMS-----+MIIEPQYJKoZIhvcNAQcCoIIELjCCBCoCAQExDTALBglghkgBZQMEAgEwCwYJKoZI+hvcNAQcBoIICgjCCAn4wggIjoAMCAQICCQCgEjyokIqTbTAKBggqhkjOPQQDAjAh+MR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5MzUx+NFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl+LmNvbTCCAUswggEDBgcqhkjOPQIBMIH3AgEBMCwGByqGSM49AQECIQD/////AAAA+AQAAAAAAAAAAAAAAAP///////////////zBbBCD/////AAAAAQAAAAAAAAAAAAAA+AP///////////////AQgWsY12Ko6k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsD+FQDEnTYIhucEk2pmeOETnSa3gZ9+kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLesz+oPShOUXYmMKWT+NC4v4af5uO5+tKfA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////+AAAAAP//////////vOb6racXnoTzucrC/GMlUQIBAQNCAASRj2SKQoEgjz6+KYQx+zT4ayoZgTTCBYWY3EiaJl5ok0cqIU7SBaPdN+qTVczCMjUvrflk8wDmKmgdrVauL+H/l0o1AwTjAdBgNVHQ4EFgQUgvJMdTkQVKStsiGY0/eY/NB/nHswHwYDVR0jBBgw+FoAUgvJMdTkQVKStsiGY0/eY/NB/nHswDAYDVR0TBAUwAwEB/zAKBggqhkjOPQQD+AgNJADBGAiEA2aHN4T0QP85J2+vz2Rmpk+DEJzwb8Fv9DGj4gwcNiScCIQDwUiXY+FcmbsiZc8oQxVcMKHXelMQUbkPq12rSnAOJNaTGCAYEwggF9AgEBMC4wITEfMB0G+CSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAKASPKiQipNtMAsGCWCGSAFl+AwQCAaCB5DAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEP+Fw0xOTAzMTYwOTAwMzhaMC8GCSqGSIb3DQEJBDEiBCC7vjtnHoU9/jCg5gWUNm8k+8C8x6iT/RlF0P9YMc81oIjB5BgkqhkiG9w0BCQ8xbDBqMAsGCWCGSAFlAwQBKjAL+BglghkgBZQMEARYwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMHMA4GCCqGSIb3DQMC+AgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIBKDAKBggq+hkjOPQQDAgRIMEYCIQDtHZXf8lH3LgaqQR6AiCXWWn1rs+76ilGdbZtWy3ZVtgIh+APRXOwsrj/zkjQ4gSvCgi0swPjceDvNqayJ+zc0c5ecz+-----END CMS-----+-----BEGIN CMS-----+MIIEOwYJKoZIhvcNAQcCoIIELDCCBCgCAQExDTALBglghkgBZQMEAgEwCwYJKoZI+hvcNAQcBoIICFDCCAhAwggF5oAMCAQICCQD3DetZLCk3mzANBgkqhkiG9w0BAQsF+ADAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5+MzUxNFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFt+cGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtBG2kKWfYT1jUXmb+DlrkD3Wfw0rKDjTdjjSswM2HggMtEbiutc+cxnoW+DaeP1SzSdY7NXlaqvzftCPL+vzJNaRM4HpG/XLqIdub5hzisbrwN1/Q77zmUvr1ka7NaXKZDuuepORJsNh5lHkp2+2VR0O0pQ3zLNjX8IVAGkdpoE5iMCAwEAAaNQME4wHQYDVR0OBBYEFPyU0yv/vEPO+p1kMDzLvtT6U+gYOMB8GA1UdIwQYMBaAFPyU0yv/vEPOp1kMDzLvtT6U+gYOMAwG+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAn2yZT/WNupO34PxVdWU65X9q+mCE4AtZKLbRa0iSkkEY961oqDiMVWiaN0sPVeV1GoNCz9J8lLKaEkR8b0F4KlJ0K+44V+5P0Hvb1UnKV2SsNxkmNbxwjO8Q+9aNjd4IqxAnSyQ45yMN4K5x1COJFXQETJ+ApFWhLZxgA0hqnotQGcxggHtMIIB6QIBATAuMCExHzAdBgkqhkiG9w0BCQEWEHRl+c3RAZXhhbXBsZS5jb20CCQD3DetZLCk3mzALBglghkgBZQMEAgGggeQwGAYJKoZI+hvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkwMzE2MDkwMDM4+WjAvBgkqhkiG9w0BCQQxIgQgu747Zx6FPf4woOYFlDZvJPAvMeok/0ZRdD/WDHPN+aCIweQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASowCwYJYIZIAWUDBAEWMAsG+CWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0DAgICAIAwDQYIKoZIhvcN+AwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwPQYJKoZIhvcNAQEKMDCgDTAL+BglghkgBZQMEAgGhGjAYBgkqhkiG9w0BAQgwCwYJYIZIAWUDBAIBogMCAV4EgYBS+HLzcxWJqmnUPO4IpwKEf5Xsh359SPjFzcJUMQcAI/NswyRKN85WRqYrWuXbqBRX/+d1dO7o4hZEcU9y8VvWo6sZCba+teSvUG8qYmu13KE/UXYVW1ZIWbNWRsDhxDmlaL+PGIwcx501+iCuwogqFYvGzualDuMc+/ErbSQ6ZVN5Q==+-----END CMS-----
− tests/files/ecdsa-p256-pkcs12.pem
@@ -1,305 +0,0 @@------BEGIN PKCS12------MIID6AIBAzCCA+EGCSqGSIb3DQEHAaCCA9IEggPOMIIDyjCCAncGCSqGSIb3DQEH-BqCCAmgwggJkAgEAMIICXQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIFH0q-sY0hx1QCAggAgIICMPbvE6ijIbACCeQG+LUnydn7HbMHXK0C9fl3+gT6I3j01OiG-b9RAobZtnwLV1Zbvgw/hUMG3YMKJZ00ydG+C54Cvn/Ta7O4NJfrxJXq2IPL/0RZW-GtnbIMm71NL5c4wmbbn3nPXWXbcpTS2TC7bnLc6N+ePjKybtynqdFKcRQxiiLqtl-yzlh7nym+kJV5KSdaCOSUArU699Ymk99OAkYrJhceIJo6v3A0oxxTfYnoJjuxVIM-sVo3GTfK+kFORJ3Spxia5vCPoN301hfOaieCrd1RpCaB6Q1Y50nJbZI/bjQ0kjEY-O+7Cnw5XYl2AHHCTzcQ4mwzDDD75r7E4R7tA/XuTbmC6mFB3TyBFtsUtXZtCRFWD-vrao0ae9sY7GRe//GTI587ZQAXRaLgmbEnUGMkMubsrh/xwPSEDtpzvPtZW81Qf2-krkO0rwb7FJTzajoklpsgFgVw/VMKNElAO8YSNJ+JX/sagldvfjNHUIQMSgUignW-dQzSPBRwdOfdjqwgaYVwcNJ0JpgdMZpomNgJmEB10GJblaKXyzbRSH7AuO8Mrr7+-lrriXEinopnJCAb6YO+Wzc1bsSeVLu2ofeHblbMWb3yofLCuTtvJXR06Xs+ulIjx-J/sGL/P/JPt8LsnPIrGd67dHy3ChmTtBPNGjDoOzZ+EOOQmLTnsVHR6LDABTaXvR-+U+MHjc9QIpWVxaE/osyLhBPOHIDqyIGAX1WqHa4aPUzNxwgXn6TMgIfoUZGMIIB-SwYJKoZIhvcNAQcBoIIBPASCATgwggE0MIIBMAYLKoZIhvcNAQwKAQKggbQwgbEw-HAYKKoZIhvcNAQwBAzAOBAj7Rnc8QaBBAgICCAAEgZA1tAy4JyoPS9EXxO2e7Vow-MtDsVwyJzuitmD44ICjPxrJgmyKBFtGDLXBfadLI/VuD9oqlI8lP176hTYwwM+72-/hwKGXmJfP1s2r7sH28sVmbJO2dYjQXtaanyQ5aDGaFMbcMiuPK4y0vB98TyguD9-S+mWoT/MVE9F20xh82gxQycHhpdT664UxeA6YBJru4sxajAjBgkqhkiG9w0BCRUx-FgQUSzL+96khVinAc/v3rkVpgmeGLu4wQwYJKoZIhvcNAQkUMTYeNABQAEsAQwBT-ADEAMgAgACgAZQBjAGQAcwBhAC0AcAAyADUANgApACAALQBuAG8AbQBhAGM=------END PKCS12-----------BEGIN PKCS12------MIIELwIBAzCCA/UGCSqGSIb3DQEHAaCCA+YEggPiMIID3jCCAn8GCSqGSIb3DQEH-BqCCAnAwggJsAgEAMIICZQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIzybx-opbqZnkCAggAgIICOJEdutTDfLmiKQEJCzoT33Z6rEqdA2STzm1qL2Ir9RMTl2wL-zKqqMT9ORFErYaYuIjatj4uSxn//JumW7/fD1d1fJeY+0KajhGpMbp7hmU/wn4mL-nyUCcSqI+2VoXjQlmH2yRFTgfSBU91+4caap4HN/wGefTZsRG6EwyXkHS7JV9Ocu-qm1SYBRjxIcMt/4JSMNYVwuSDffFEIE8U9FjnuJKTl49GFdcKwBRJVUyTnYOlPse-YtFw7Wt/FESxG1IxGV9bU+Xf/SR1AfVlRdqofoCNVavwd8GFtWEfUdXlHjqo2ufN-fTLTzxQSGNakGknJHDxQFgUUAkrB+KyWBf+YItGfb5DPp4R3TJJvBH6lNTq1w8j4-aSncKElU/Wo75ULUmZqKra2B7gnIZ9JdTxijRaHothgwd6zDRVlNPSct+xDg4mwO-zFtjg4AEyCcK/NjbUsq17qbKXDyLkfwA6P3xQc3gepgj6xG+102WOMu0ZD25ucNa-Te0By0PjkrSrP2kW7PFnBWZFI7abNP00JRvQ0gLuj3ax8JW7Qie6DEOXOPqeeljK-gPguFb9GAX3o6lZed4K9dIMXB4btyl8mA6PIIPr23hHnyRcqno/Eq1FQQUwmN+XE-o7s22Tc/y/DQ4fgiMZkFZhW7DweRPrx4flqRmp8QIaG05wGgMW9q+u7s5Djew9kA-UkHmTAoHbSWal2XXyqkDfbKQp1QdC5fTxOmdFZdQ+xdh/jCknwvKVaDcp0Xs5ZWz-ZICgq4AwggFXBgkqhkiG9w0BBwGgggFIBIIBRDCCAUAwggE8BgsqhkiG9w0BDAoB-AqCBtDCBsTAcBgoqhkiG9w0BDAEDMA4ECNIHMplVd+ksAgIIAASBkBjKaNoqEBSQ-vnDxd3cf49x2w5su0kKScrVMXRjKS0EdhvkEZ/Ybmg9ro4VHvkmTmWv54eY+no3r-UbvVJ2Btct5LQdg6WgvB1/v1wJGuOH/Y8f9toZ+FjsLsOSkpzOP21iVX8pLJOTvK-F4dGPyyLGx8xtZZOeA8eevzFlrf99uwlzCkv4lG4ZS3FSKgCdb5DUjF2MCMGCSqG-SIb3DQEJFTEWBBRLMv73qSFWKcBz+/euRWmCZ4Yu7jBPBgkqhkiG9w0BCRQxQh5A-AFAASwBDAFMAMQAyACAAKABlAGMAZABzAGEALQBwADIANQA2ACkAIAAtAG0AYQBj-AGEAbABnACAAcwBoAGEAMTAxMCEwCQYFKw4DAhoFAAQUtk2zS8R+SstIRhAjP6LT-PfQ0BEUECOhUkmBy8gJ0AgIIAA==------END PKCS12-----------BEGIN PKCS12------MIIESwIBAzCCBAEGCSqGSIb3DQEHAaCCA/IEggPuMIID6jCCAocGCSqGSIb3DQEH-BqCCAngwggJ0AgEAMIICbQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIC5r4-uzdR0uECAggAgIICQHbHiFhHuPenOSHe/jkUiyUJE2ItJlxxYGWJylZI41Sz5Hlg-O75NCrJq4WFytBD7qeFt4bdJ5u3L4TmusDhZfjgi1iW2dtPmp+rwksikWITK4eHU-d8HJOx+PqtvtN+UI9rTObfVxg8SPQ76vR3NLaQCaLtqNwGmAWSzOlivDdXCoumez-VG7m7CX3luLSqhG2aag8EhV15dBc2eIj5Vd6zSRlybV06/MEdasAoeiARAFK4c+e-mC7gK1sxvECWnFwCVrYyWEn1EDLvGYjpUnLfHmTu+fV2LFCTxWmV7gXM0tODaYDM-hsHtMw9zlSAhVa2iBkL37mjPeSM9q6Un6NWw645+8DyTbWxGkrrpm1vGc/xJYp0G-5t05jzrFY/AQ9USVELw0PzBFwQiGsNxAHkR8z6k9wxfIES9IpdFAbNBzWmurlEtK-5OXP17hEmQlSc4EaOivN17g0oJxRcTXglHN6zwiP8iE5OfJehHgGWynytJ9wnN+W-fkQrX3NNVBxXT7jPMgVHkWBWytZeex6Wy4wUb2uKSYd3I3XEMGAFzg/3bcfHoTBN-FRwPCFX4DyrSkLsFDv4uAwtNkZjRzRbV9mW9hW3chkcNvH7HfS8plsm19LhayqHC-LnaM45zOmU1R7zPL269HUq4IZ/qEGspwDoN6wTRhbiXZMaYQ7BA1XoNVsTBCzijT-FxIO7Oi+IJNffXtSW6AuRtq7I99p5iSVrB1QTZr1pVD9KAsCzbGGMnUrei+LFScz-zuqYmWbsms/WtCqX9TCCAVsGCSqGSIb3DQEHAaCCAUwEggFIMIIBRDCCAUAGCyqG-SIb3DQEMCgECoIG0MIGxMBwGCiqGSIb3DQEMAQMwDgQI+WOkpHQZfYgCAggABIGQ-K6ntubGW+rZ1/c7EqUNvRVnoyxJRp/VG+vEQPf/VoyZpAvYGZPzmVseP2nSTZ9zk-ooElFmVR7tMVi86/wPhwyuotGdpou2s49lx4prH1mjdQY6W+Ybgj5HdI/vdo6Bsr-rjOewC1j4WG6Gz6lTQr+ADA/21vn/f2ooxHGf3Uh53yPe+MfCV8RYg6c5fhG5CaI-MXowIwYJKoZIhvcNAQkVMRYEFEsy/vepIVYpwHP7965FaYJnhi7uMFMGCSqGSIb3-DQEJFDFGHkQAUABLAEMAUwAxADIAIAAoAGUAYwBkAHMAYQAtAHAAMgA1ADYAKQAg-AC0AbQBhAGMAYQBsAGcAIABzAGgAYQAyADUANjBBMDEwDQYJYIZIAWUDBAIBBQAE-IOKPMwzYN46vB1aOSoIKQSfDo1A3CJH0PZleWonCGwdwBAjCHBkcsCYB1wICCAA=------END PKCS12-----------BEGIN PKCS12------MIIEWwIBAzCCBAEGCSqGSIb3DQEHAaCCA/IEggPuMIID6jCCAocGCSqGSIb3DQEH-BqCCAngwggJ0AgEAMIICbQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIn89b-Z3GPMpUCAggAgIICQGKf889YFGdAEDzO/krxYBRVQaf6j0ZOWH5G/VyKDbyXl8hm-9A4LQtfQa+q+LO8DhIea9AfA6aK7WKuAz5vnQIA4DpFF9r1qmGOFQsIbzB9Jo/ik-wRIp8bylPIdwfbyaUqH+0ITun1NjgdZCpGkV1Onay0uUPZq6St22T6Kv5g/b/x9f-MwaUwikXQVNNfOR+8wrW0djK0MOp9b8zZnmTvUDURK6nJEaDMeQgP0CFcb4F9Xv2-8r5v56zSU6edePQuxS5uALWSZQg+7TqQlD4d5nwT0+wkIU4QnDMHcZJGwblyunsy-d5RTJADCrky0V4BTz8l7/avwK66CkNdw2k2dbN3uJhaIjSZuoWkHmD/iAYS7fAQe-fHwBElEqOPS+KLsBtdOtwyhWnbqIFd1cL3p+ndZn5UEA07PWSHfpg+SejMK8RRff-HSBGVgdgvbXKH6PKrz/NoJVb7BO8xKJjIdVjC4GOVNlazhFdUNXryMgEC9n/tg+U-EZJNcMq74jR9tinCnE6sU1IAP8CpOtrMTpCXnnMIBl3JQCgfem7ahLDfY+3YIp8B-1Q0DnDG441e1iTcOBaxcOPWaTN8Lyc3SDDgE5D9YWr+Ez3s5OALtb0jy1AS6ODRJ-v1wgunlHqpapH9iA+ArOa4VTXp60MrUqhCdGkMv/EmiDRJ7P4Se/f2Tl7epCPCl3-/SEy/4EMX0hhpPGAAASaWhhDtTUsuNrX+G71NeJiyDFh/k55Wu3djblBkMszVsgY-P26MwEnl451bVHGzQzCCAVsGCSqGSIb3DQEHAaCCAUwEggFIMIIBRDCCAUAGCyqG-SIb3DQEMCgECoIG0MIGxMBwGCiqGSIb3DQEMAQMwDgQIlX6PffUsFMMCAggABIGQ-JI4+RRpUEffRaz3qRdXoM1w6rHCxyefKTxfRp/jsDvsHkBEOeWFE5cKm+3BXEm0T-RMGJlxB52ceYvVA9/Bo0StUKuxYw5dsbftBhjSsH/5i6i8DGVOCFycD0C3B1hn3e-8Xt+kQSCrZcc6vVd8gjiPvHhynPPnn2lli0NlE5j0DH+as3sxjtfEdPeQunKSFE1-MXowIwYJKoZIhvcNAQkVMRYEFEsy/vepIVYpwHP7965FaYJnhi7uMFMGCSqGSIb3-DQEJFDFGHkQAUABLAEMAUwAxADIAIAAoAGUAYwBkAHMAYQAtAHAAMgA1ADYAKQAg-AC0AbQBhAGMAYQBsAGcAIABzAGgAYQAzADgANDBRMEEwDQYJYIZIAWUDBAICBQAE-MC8cVO84hC0SnVOrqDYNQMWor1IyVaU/CaepQiEq949CrUONcjVM4BfgxoVse2WB-uAQIkqDilpIjT5oCAggA------END PKCS12-----------BEGIN PKCS12------MIID/QIBAzCCA8MGCSqGSIb3DQEHAaCCA7QEggOwMIIDrDCCAksGCSqGSIb3DQEH-AaCCAjwEggI4MIICNDCCAjAGCyqGSIb3DQEMCgEDoIIBpTCCAaEGCiqGSIb3DQEJ-FgGgggGRBIIBjTCCAYkwggEvoAMCAQICCQCX+mbx0uA+ADAKBggqhkjOPQQDAjAh-MR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5MzUx-NFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl-LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHYqvg1E3p4xXBUK+72zfa3M-LmJIkfZDsYqRsZwRXJ7TzRxsbeVgBDVy3Zj6Y4ZSB/dK7jxs54KTECoDcXLvEASj-UDBOMB0GA1UdDgQWBBRT2nMGMWO1FT6OJajOnO9gp1hHVzAfBgNVHSMEGDAWgBRT-2nMGMWO1FT6OJajOnO9gp1hHVzAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMCA0gA-MEUCIAvA93qcJRkeyRffr9IKzYQXkZE30qnCBzlEfmfUClagAiEA0GQKzKiwGTv6-uV1pwjprqyI7h0dObEESk3KMQRa+5zcxeDAjBgkqhkiG9w0BCRUxFgQUSzL+96kh-VinAc/v3rkVpgmeGLu4wUQYJKoZIhvcNAQkUMUQeQgBQAEsAQwBTADEAMgAgACgA-ZQBjAGQAcwBhAC0AcAAyADUANgApACAALQBjAGUAcgB0AHAAYgBlACAATgBPAE4A-RTCCAVkGCSqGSIb3DQEHAaCCAUoEggFGMIIBQjCCAT4GCyqGSIb3DQEMCgECoIG0-MIGxMBwGCiqGSIb3DQEMAQMwDgQIQrFaHl+yq4oCAggABIGQQw+qnMDWAzFgExmo-n/TFr+viTkGMvUHp1ku0i/84uJ5qZh8M8VxNz+kHMdqpICLo1QQNjzUfxNZGcdrG-9d4cUPTBJolOnAUgIJsB92V7gbrsfou+sTWe3Tcg71hVQIgGgrJAKuKFIyTByviX-m4Dm0TzfxmNJwK3RU8YuLnJkm3QHmGUYPJ3IJqFWNGYVXiNbMXgwIwYJKoZIhvcN-AQkVMRYEFEsy/vepIVYpwHP7965FaYJnhi7uMFEGCSqGSIb3DQEJFDFEHkIAUABL-AEMAUwAxADIAIAAoAGUAYwBkAHMAYQAtAHAAMgA1ADYAKQAgAC0AYwBlAHIAdABw-AGIAZQAgAE4ATwBOAEUwMTAhMAkGBSsOAwIaBQAEFAKLUf6d7uvD54jrsI9j+DpK-2CroBAhjf4XZU/977QICCAA=------END PKCS12-----------BEGIN PKCS12------MIIEkwIBAzCCBFkGCSqGSIb3DQEHAaCCBEoEggRGMIIEQjCCAtIGCSqGSIb3DQEH-BqCCAsMwggK/AgEAMIICuAYJKoZIhvcNAQcBMFcGCSqGSIb3DQEFDTBKMCkGCSqG-SIb3DQEFDDAcBAiggrBG/lAmGgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQME-AQIEEHMNnXuYwnMbjOSFmm5xEl2AggJQRvqqPaLjdtyoaWULLjqu1g0bPuZuwsV7-yBRI3/2sTrFfqBasHMl2nQOA3udFVtlCq9lUuaI/vyDRTqlM2xKqjzvoeFVCoXEB-qGBfYMPova4443zYYczv+WJLi56Y0z/rOwon9tZfQWWCPrRhhp1w/CqXLeuK06R1-/+AvNUGK9ihu3/DZ9wHTGHcfs7ciLe4U9YubiVHXwLzhMxZMvXgfs8ogXcDlckrv-xKuOl7Fww92TKni8IpYU1PWpeD5Qb5IYveaEGF7ELw4USHfYG38G7pBnj1Yy4+KT-+SC9rx5ZCbuELGtoWTJlcvI54kkLZ6sn+ZqxVVxOZql71f1nUqLiJmt5V8r6kgEt-+ARauqbrNMcPm4kPJmTADCLYLtYyriQyqbvb/X0bXgAevND/mrl3XTrqj1SwXRxP-+380fXmNYlz28QFgM1Z16RjxLfGzo/mMQx/vT27oGbwOkar8+wqcR9acZnjuVDEr-Fq3b0ski6mn6gM73H2F4xzv30UD+BUxgg/YJbcgLiCNBNMy5cr06j4ebvDKGWbgj-qAZBShbgOzpYMhUpX+KfN0eq7k6CHn9ukn3s6A/blak6oy7Lf+pQfg/ZxubAgA1I-q6Z19FH3QS6WYOaMvXLXKNe6T4YLtnu154iV430GMCEwhhFVL82ieuCKKGWXJey1-yQZAxr4m05rouZ6ul8rd1bxTsh6AZlZ5ylIi6pk+h7HD1cZxYthvPnrkWrAI849e-WYUu5VBykQUMTt6fUUltQiDTez9FtC1RN2+9qFrJacnxFm1PEPD0QjCCAWgGCSqG-SIb3DQEHAaCCAVkEggFVMIIBUTCCAU0GCyqGSIb3DQEMCgECoIG0MIGxMBwGCiqG-SIb3DQEMAQMwDgQI5D7IqrU5mUICAggABIGQh+/XDU/wsMCf7Gh0SY+9okbbdgLO-Hj3PlneCOMeJMJoIK2upJUU0MHety7Xeu8Xv4jMcspCTTqbIBwJtkMdZo7M7JlZ2-LZ1DgG6xRXYLTYUvWK5aTXZqwISEriTyWNetpBrfuCK48tvo/FP4WmmDHWwVUcTB-5g/Rsvdcu/c8chKWD+m4jtaXG1fO7kW9qcK9MYGGMCMGCSqGSIb3DQEJFTEWBBRL-Mv73qSFWKcBz+/euRWmCZ4Yu7jBfBgkqhkiG9w0BCRQxUh5QAFAASwBDAFMAMQAy-ACAAKABlAGMAZABzAGEALQBwADIANQA2ACkAIAAtAGMAZQByAHQAcABiAGUAIABh-AGUAcwAtADEAMgA4AC0AYwBiAGMwMTAhMAkGBSsOAwIaBQAEFJOaeethrlOXj6Bd-WTDuuxNrbxOTBAjuBPHa1rrDLwICCAA=------END PKCS12-----------BEGIN PKCS12------MIIEagIBAzCCBDAGCSqGSIb3DQEHAaCCBCEEggQdMIIEGTCCAp8GCSqGSIb3DQEH-BqCCApAwggKMAgEAMIIChQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQUwDgQIout0-wIR7cikCAggAgIICWC7qqjpGtFkZwlykv0FzU3eNU0c0L8Et3yaH1frNkhpnX7U0-oksJ55AsKNCYHxjCGfBi4/pFLJjzmIm7jw1EUNpw7Tz6/8xwov9JS7VhHLgIJ+Ak-SGUOEcrGBEtCWcwpovXlkqoawvmM304m81b2wSjNeOD+gD6q1nx0s6SOoy6ZyUK1-bSGnK/mdR4WEs2KeQ/MZK01UR5XI+E4qX9tmF9yL3IU0yokqWY91XKBR+vS4+pRd-YxVjKZYoMtuO+QdczAQSa4F36Lf/IXW7N45HeCnijCVWz/9r7S2KBjlDQ1duWF5s-nHucl6VTX5vr41hcTqWH+DQCi2w0y2zbAyqwz8xyronh2OF0mus25rKBuXaxo6BQ-C4msotkVk7K1E8d9Mo9HbrbjTr6q7q2XRTsD+YKFa8TcDFUTZ5gYsmpUAjevR3Hc-S5Db0nlLyMXJAasBFvuMfNKoCqWkgm2abrYAd6ROcIK9Mf7Vvs/Q++ED6PDDsew4-7TIleg7lVJ/VvK1Bf/r7w6ISR3R8G5rpjYX1Z5UKBx9JGdRsE92qfI3cpXy7jTo7-EDlvIXK1EE0iVOAbDRphQw7z682nzdqAuwwSUvLYIlekxjx+Trtig7xLOrnl0ARI-ivqCwqCWf+BeD6fC6jqyC75s97PRO1CwyrwjermHqZ29LAbzlsp3su4UgE/q+8zT-C1HeK6uSnOvsAXCMGyLI9N6w+IK0vMhYY2v5KRqJE9UTdJ/99t71rWooBkZfemF6-Kk7A8Q0a3x7bTyN1QgOlO9cIIeHkSYn65ObsWankSYm4Nff+LzCCAXIGCSqGSIb3-DQEHAaCCAWMEggFfMIIBWzCCAVcGCyqGSIb3DQEMCgECoIG0MIGxMBwGCiqGSIb3-DQEMAQMwDgQIQ0XzzqZ/BJkCAggABIGQH/cfMsPknp6dnjzVV9kj1rxmqLvkxB30-jCqwxsaOVJmRdPc+lRU3KTOeltejSHgPP8K/v2F8VqQZA0yXniLoNsL4Rlx1GLp7-qXbxZ/oD+3+ESLJyTjB74uVpi9L56ojJpvjYtZfHzlMBZtEpZBYO6CJh9dRVJt1i-ODacdYaAmvTZ+Gh6mT87AKugwDuLiQhWMYGQMCMGCSqGSIb3DQEJFTEWBBRLMv73-qSFWKcBz+/euRWmCZ4Yu7jBpBgkqhkiG9w0BCRQxXB5aAFAASwBDAFMAMQAyACAA-KABlAGMAZABzAGEALQBwADIANQA2ACkAIAAtAGMAZQByAHQAcABiAGUAIABQAEIA-RQAtAFMASABBADEALQBSAEMAMgAtADEAMgA4MDEwITAJBgUrDgMCGgUABBRXtjhP-ScsUZNfJPD+XGatgpre9TgQI6/JvBnA5OYECAggA------END PKCS12-----------BEGIN PKCS12------MIIEYAIBAzCCBCYGCSqGSIb3DQEHAaCCBBcEggQTMIIEDzCCApcGCSqGSIb3DQEH-BqCCAogwggKEAgEAMIICfQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI+UgP-cFrlQ6MCAggAgIICUJKytD8kl8Tjhl80ilyB/NxrxFUoFLx3ySy5PVMNEzpOM2Oc-bHDvAVle2ehL1ZR8c812a4EkNeKAMaRi9ymiRRrN+FRqmjfVqt+lPQidwXkpnS/b-Nd3nZvKQVsp3sQzeNNkBMobt1eUxA6mzp92/MZSQPvAS0tU92aNDj/PqiQzDA17e-qEn2Ns/u8JSzvk2d2fRWYeAdgibFebFxyhI6rdJdenE9RNf7JgALJiGZghM5f7NM-734cz3v1eP+CrxwFq0igXQW/qDqky2xe0p1k0uFwVjfs0SfajtBBSKXEBA88bTnf-+NCNiX7pzpp/eu8re3oRvx2o17FDxdezLj1Ez4zp1yM+O35abSpAIRW+pmSukqLy-1t6zfETxlSOwKGEGuX81rZzag4OT0zlmayuXijQfdn2Uv1dYoMvJRbyfIOG5Wbg4-6rWXIIy5AG6+nzzYWjMBh2e7Rts1rFrhx0hTklBpLJLeQfTnlJrwQN0SIdUIN9gg-4oOFRqrS0BUdYYi8qhizOoLeju1AUZFDQ0ShBC0kXAAYwsKhxvBANYQDi1+h0MY+-EAXJLGoSKgXqRB3F0jsXFlcGg3bPTfY8gAs9QBGYv3jfcy4A6fxNbXvUFOTRfoUg-ZGDXZLysvqarb45E36Mr3BtbvOCKLG5hmazAPteUpdcKepgAM8WBoWWIfoPcOh29-XRlIx+x93kjrQjDoo2EH8SmfWrh5vYIsDD2K9fozg844hZqq763SDXVYQ+BmTJWu-4XHaEu6wCyGX6AeE7flDl6Bc/1th2yfw8KQsF2MwggFwBgkqhkiG9w0BBwGgggFh-BIIBXTCCAVkwggFVBgsqhkiG9w0BDAoBAqCBtDCBsTAcBgoqhkiG9w0BDAEDMA4E-CNNFERdaKdyRAgIIAASBkAcC2AyLZzIegZfk/G8eA0EMOyjaneHbbqDficRNg0Ok-PkQXZEWNddUdgBggNwo9Y4rGUooHpxnbDEsmgh/iEvnr0UoqZ72uLGOidNnjqfy8-hOE0jotsl7LI/hIlH24Jniz4jwOoYhE94iEM1JfD4PvRNrbnBfLUcJ5zGCCptdXq-EVloYf236K3tHSXRADes+DGBjjAjBgkqhkiG9w0BCRUxFgQUSzL+96khVinAc/v3-rkVpgmeGLu4wZwYJKoZIhvcNAQkUMVoeWABQAEsAQwBTADEAMgAgACgAZQBjAGQA-cwBhAC0AcAAyADUANgApACAALQBjAGUAcgB0AHAAYgBlACAAUABCAEUALQBTAEgA-QQAxAC0AUgBDADIALQA0ADAwMTAhMAkGBSsOAwIaBQAEFB+JS38oqVLgJ/LV3sE+-rTlusdnvBAhD3Yt2Q836RAICCAA=------END PKCS12-----------BEGIN PKCS12------MIIEBQIBAzCCA8sGCSqGSIb3DQEHAaCCA7wEggO4MIIDtDCCAn8GCSqGSIb3DQEH-BqCCAnAwggJsAgEAMIICZQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI36cy-Nl2ETYkCAggAgIICOJDnrzg0U67gXWn/F3HiUgpK5ZhdaTzoSYoZPEVgCXlx04fa-WADXrFywcbF2HTU2wiasHws+9DHqRRsCKSzd7DcWiniSREXenhhl6ZjVUL1/kDX9-uVh8vpetzyIv5eO3LDvJw3TSbBBptH4vO1FCJSXNYA7vQCnKVBOxSpli4TW7PUjZ-TcQMofIHeFT11HCtlZ4cdV6NqePvc9m0UkyU8bxi/2ynqIyf4YtQiIBa5NsxXWOt-ZaKw2o7dKK7Hr8STnuLaSdJOkY7qh2TBeFfXNe9rjG7bUM0cjO+ELdHoNM3aJ1l2-T5Zx6kLIn99CFLUB89yt6Z4MideotQQA8K04uwh23vd3FDwj9G31u2Cm/sEgeO/g-5dataSR/Eo4+cDTLE8qlNjTBJdjlz2qVVPr/x0eMbMAc/WnDmvtSZEE7dzQfawtw-TubaChURw6zdKKwNOFesNvfDyIN98lsLuOhAWgNwj+gcsvA/MFBnodPTLiUEEMu5-R5oIjXmI2JYTcaPMrQX3dCgGCrOW3ddq2at1d+Lbnhvv2jPMCpesBjMPwv555GNj-O7tculdYrKYh/0WiqRE8AF9M/vlPomNIk0o6SXTGew/7VRFSi4O5zeOmSrxbiu2x-MORhd/fycw0eB/OIKQUYOxBGsFLMc3HNwGSFtVqRJTO6v9F0e+QtHlcD2VWTKqR4-w+wexcarA6N0qlDOCBGRAS3vJ5d7YQJTfV/n/4SnLDmWIhsrvmwwNiie+Eea6ABm-JpzknGQwggEtBgkqhkiG9w0BBwGgggEeBIIBGjCCARYwggESBgsqhkiG9w0BDAoB-AaCBijCBhwIBADATBgcqhkjOPQIBBggqhkjOPQMBBwRtMGsCAQEEIN1BcbroS4Rr-SL98OeRm0Hzo0POI2k/SkGxKn2EObv3/oUQDQgAEdiq+DUTenjFcFQr7vbN9rcwu-YkiR9kOxipGxnBFcntPNHGxt5WAENXLdmPpjhlIH90ruPGzngpMQKgNxcu8QBDF2-MCMGCSqGSIb3DQEJFTEWBBRLMv73qSFWKcBz+/euRWmCZ4Yu7jBPBgkqhkiG9w0B-CRQxQh5AAFAASwBDAFMAMQAyACAAKABlAGMAZABzAGEALQBwADIANQA2ACkAIAAt-AGsAZQB5AHAAYgBlACAATgBPAE4ARTAxMCEwCQYFKw4DAhoFAAQUjqGpn9xugean-d4IruG5yTl2yAl4ECEFTBPWXAettAgIIAA==------END PKCS12-----------BEGIN PKCS12------MIIEiQIBAzCCBE8GCSqGSIb3DQEHAaCCBEAEggQ8MIIEODCCAo8GCSqGSIb3DQEH-BqCCAoAwggJ8AgEAMIICdQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQImZIF-k9BCNzQCAggAgIICSKgLbeRZWeciVOjIMDaAOkLV0MP2cVad59E767+6nlAsNflO-qidcopd5snW9RUQ+eprSx9DAS2Rhqax1H3QWskpGDIVdD91LOHUhDUqx2akwDD2C-sOPlooaaTIoVZ9zbTpl3JDdJ7NHdRyHoWtbCu1xEvK+akcqK2fLvI8xwDSwU/DRE-RU7ELR7GX/jW+WuOvBdBGC8RyetVlC93sUjW7Cfl1QOTbJ28A/UCgD9zANXmMRez-nEiRw4iG30UX71EBVUJdNvLREqSF2l463G/dTphr4ak5IoLACHuQlVYfOWYCevXN-4R9LDY1pB56XedHkPATcrXAwxy/gapdIdYdVbUAIWUQzxXxtmZgeoBboVG68Ceq2-5zsM/Ktl3jnnAwFPx2EuuLHV+5yaNOGZFkz4NsFc0lSMlI24tf6J6GIXo4Crupw/-dywPn4dX68ILjdSiF9xf8lB4xysVEHfG8CWJvCg5KLNPgvRszCxbCDoSnZX4VnSy-Ra7Dg7x4ZWQyYCgt6wLoqEuiFTxNhtOI5UMxZDK08UhKdKOPWAEZnuZgnUC2Nnpd-6Bj/Gtd4N+SjqATYCACQDTIP9uDj7iMsTBmIwK3WjPF3hflSsh6ekIF1J/t8uG3/-lIDfhZnwlkj3Tli8pub5A/kIyPHyhYxUZaY+7QTwQB1Fws4v9K+nj6QCduBuV6or-MObYTnrlzz/sqKxohlfaiZroiJmYicjgSHiziBo/fE/miUgSWANin5quolzB/HY5-eZ0k04F9Im8xLFCA/PIEzt+SV3XNMIIBoQYJKoZIhvcNAQcBoIIBkgSCAY4wggGK-MIIBhgYLKoZIhvcNAQwKAQKgge8wgewwVwYJKoZIhvcNAQUNMEowKQYJKoZIhvcN-AQUMMBwECIpOphpdh/ohAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBAgQQ-Juna4/Yf0krRZ6mE2g9HKQSBkF8wVESzEUBn1Dw4eEU+2Hu5/Zl2pk082BCqAd+W-ig4eOZ1w2E0/TWSxYf3mzoLmwCrqn0GSmPfpK8D8ulP1MU6fr89YI1QPL5PhV4WU-NHI2Og5dRagkT8wcptOM4CVHivgbrceTMWwrp/SWZsBkSxBQEl+RkCwo7mx2KUYP-NosSC1cwEDgEmYa85ok6Kbk++DGBhDAjBgkqhkiG9w0BCRUxFgQUSzL+96khVinA-c/v3rkVpgmeGLu4wXQYJKoZIhvcNAQkUMVAeTgBQAEsAQwBTADEAMgAgACgAZQBj-AGQAcwBhAC0AcAAyADUANgApACAALQBrAGUAeQBwAGIAZQAgAGEAZQBzAC0AMQAy-ADgALQBjAGIAYzAxMCEwCQYFKw4DAhoFAAQULemygmsvFuzhulUVO5CzQbDYIzYE-CFkpGLVxDD7KAgIIAA==------END PKCS12-----------BEGIN PKCS12------MIIEYAIBAzCCBCYGCSqGSIb3DQEHAaCCBBcEggQTMIIEDzCCApcGCSqGSIb3DQEH-BqCCAogwggKEAgEAMIICfQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIrVmM-qkskLmMCAggAgIICUGUvn0KkHcJIGDZGDtYe5gbUX8HB5Ug3j4GF/tOT4losFD7A-YEQVsZnW1ywS5eemKuw/1oCsQ7YaJf0WFUTk3J26C9yQqsnQWxvPVGs8qOqHQoi9-C9L1KTRpAvWj6envRFkk9skr5YVNm6FqShPc/LbWBf5JJCw/G4yR3zvPZtpkKaaz-BMb7JWVRbyXuA6mbr7QaeymY8Zg602TNB3Awk8F9c/tiM6dk3/SSdZFL1aH7Fiog-W9qDJ+3MoLKwwZcBjZxknuimNWQgjsXihcog6idwaako5a8Vzzh+9Yr2u4Cd4jWU-TPY5DkspEpU0Se9lmjn4cKdX3AdNQNSPbn1UpBagirbM8/lfUkkbVGezUnTusy5Q-Yc7rTTJ6WDWpilP89ZMTIEnriLvF92GhqQvb6Q6IJ1prD/iuJG8RZEZ7Rdd/phJP-55dadyVlQb2mNOQb2zR6ikqn8F1WX+cEW609Xv/fsURblUFndhP+2DsxBbmdbrpP-rZoboP3S5eyK53dSoH0jE+dDwLmTCRaBHRBdTHEaN7/qNefK+TaSJ7/ZhkT6QrZx-JwM+7T0gWLTY19QSEbQDW+nmw/mT7YF9Y4AJJRjKWONZDlzz2JOuft+6gDqfivkz-oJt0sNklvCv+fwxIxQJINIEshhiqTmCiYb//LDlskKZw/X71FE2UeekmA5zdCXhW-ezaBLXifFYKHG3njl3IUxLSfZXz0SVmCXZb8Zvn0pOfAz5WQqIXak9nIgSMzh1cG-OQGVeNxiKE7M1rd+Iwx7S/y8QXIVhAUeXCyOjY0wggFwBgkqhkiG9w0BBwGgggFh-BIIBXTCCAVkwggFVBgsqhkiG9w0BDAoBAqCBtDCBsTAcBgoqhkiG9w0BDAEFMA4E-CHluHyg/OSArAgIIAASBkAL+Y4024MWAZN/tETNU9kDN4pn7kRwdvF/tE5qWJblk-BDoYOzUydhQgQNTxQloKxJwwQ/aYG/fmCZy9n7XF4wvSXjrIIqQ1fTiwsg5hpO/I-NfEBjAQMDQe2PZZn0v6Z2drzagtKvP+5dcKR9Rm4hdhlT3+sTQFPMuqE/naW0RUu-VgyufgDvrdo3eGkNV0cNcDGBjjAjBgkqhkiG9w0BCRUxFgQUSzL+96khVinAc/v3-rkVpgmeGLu4wZwYJKoZIhvcNAQkUMVoeWABQAEsAQwBTADEAMgAgACgAZQBjAGQA-cwBhAC0AcAAyADUANgApACAALQBrAGUAeQBwAGIAZQAgAFAAQgBFAC0AUwBIAEEA-MQAtAFIAQwAyAC0AMQAyADgwMTAhMAkGBSsOAwIaBQAEFM1Mf/02bHn5IeF/chYj-fTf91X+wBAhxy4p/PLmCegICCAA=------END PKCS12-----------BEGIN PKCS12------MIIEXgIBAzCCBCQGCSqGSIb3DQEHAaCCBBUEggQRMIIEDTCCApcGCSqGSIb3DQEH-BqCCAogwggKEAgEAMIICfQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIgDZr-yESnGsUCAggAgIICUI0Xi/tNudGKSqEhCSc2kdmly1P1UspJQiROTByJAGEGpiw6-MbKJ6HhfxJ2vJvtlz+1iA2iYKA6LnFUGaU57aBFZMjbPjTs4K7gILwHpMCtv+DPx-u4zROBGqYw5ufsvuEFQSaSeWZSwRYMPM8yVfQTZopdHSHGIhxbKFdtRMlnVOVyNh-p+IGo0yOsELkRIxrmhdawmNsjo3ua5pSRb8nitrKIxc+LatWpp4wbwb/Z5KwaMOf-1Xz1zKDNL4BlZVI9c3bPWM/sYWoLRxl4LF8xQK6G1lk+s9RR9n9i2r8BDZ2c2LRI-X7KVfUhi+BWlkRzjwkO8w0pL5acoc3hYg/rvm1TCh7b2sSE1JYP5xZziOl2TSaWh-1FNrAASb84F5XDKnA8DL+jSRevMERfP+uuWX26R3bO6D/Y/qDegq63VRSdFjSVDn-zxwUxrb93GLSbLBJ4vW14R2Dl5WrwLzCXvP3jTTjfDVg74Av9ODE9QqEw5eABJx1-uCUM8zhXx8NVzsRQ8U9aUOhJXK+HXVbrN4gRMZ5fqKtHeELG9mjV4guZ60cd8u4X-fcdzyOMQzedbGhoxHl3vmcSbJb+DUgzTF+Sw85V1nqV7oPRgJgx11ib74cdAfs4f-GSlnEY/wdP20unPGGTS5k9I7GoUFLM2bPXeue983JIEFrZXefdSXUyY8ug2UFxMl-Ta/7aFFTmz5SBfqXUuZX48FOL1GlUvl+8o5ISbTGNvt8jwunxWJcCJCWMgQ2s82k-PJpzqJlz8sE+4xz8qdTW1MR1HELw+v6bkRPDHH0wggFuBgkqhkiG9w0BBwGgggFf-BIIBWzCCAVcwggFTBgsqhkiG9w0BDAoBAqCBtDCBsTAcBgoqhkiG9w0BDAEGMA4E-CFdmR0tc6KJ+AgIIAASBkFi0RoWkd1KWd22KzsQ0u5Hl/3W8n1GgVKe1aAVx/YVo-W+/PNJPpFZ3r9X1rx39aWMnDVa6c5wsMQ5wJ2vVzobrdEMZkvxYRXoAeyoxRRHR3-01SFdSgqE3i6D/25ashBZWgyjHiwFd1xpK/uac9watUdcJ1JvFP5ZZ38T85E6IPF-hnXk/GYbvedO9Wtklvbt7DGBjDAjBgkqhkiG9w0BCRUxFgQUSzL+96khVinAc/v3-rkVpgmeGLu4wZQYJKoZIhvcNAQkUMVgeVgBQAEsAQwBTADEAMgAgACgAZQBjAGQA-cwBhAC0AcAAyADUANgApACAALQBrAGUAeQBwAGIAZQAgAFAAQgBFAC0AUwBIAEEA-MQAtAFIAQwAyAC0ANAAwMDEwITAJBgUrDgMCGgUABBTtLc+uuG76aVNLcrEgZ+1M-peI07AQIkVLG3UDA77sCAggA------END PKCS12-----
+ tests/files/ed25519-encrypted-pbes1.pem view
@@ -0,0 +1,32 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFcwGwYJKoZIhvcNAQUDMA4ECLtv2JchVem9AgIIAAQ4cVbXStmw+OANljhnJppr+YqzWhH0zLXu+SohCnQ5cb+R0AsffwhT118Fedfi6w2b9RAL8EhvLk3Q=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFcwGwYJKoZIhvcNAQUKMA4ECAHGvbJ4ns9TAgIIAAQ4GXgJkD9MzX1OzBP/Joo3+5ORepS0PLaDUk6yYU7jfD9ov3JZXnxxAyQsgfwIJcJHE0lDm8IL5qUA=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFAwHAYKKoZIhvcNAQwBATAOBAhN+aPvlVgjrgICCAAEMEd+AOxYLmDMzouXzEo/+Z4XCVMZrBGGrQIYSL3d7r/sqMHklH+SC0JtdrVHzJyCuhQ==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFAwHAYKKoZIhvcNAQwBAjAOBAjCex3tcisjiAICCAAEMAgOe/PJs/m84I+QR2TY+ojYndqNX4lWnQs6rl5YK94QB0V5rDZWN9YvmZGmX+/JwkA==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFgwHAYKKoZIhvcNAQwBAzAOBAgmU1ToGVo9cQICCAAEODaxyq/B/HNpbpsDCL/w+fYohLS5QA3s5OIKHo9uELz2GrTS7aI7idAEPY+iFo+eFD6Ql5gy7GVkb+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFgwHAYKKoZIhvcNAQwBBDAOBAgiVSOpDt2NGQICCAAEOLpMO1GOWTMQQ6MN0F+u+LyTzAruvs3lbad5wd0EyGE10m8c+2vXSsl6DMEkaXrjRovsCebZwVzK8+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFgwHAYKKoZIhvcNAQwBBTAOBAjdfsUUZ9k3AwICCAAEOERTz7/lepT7BrKWJ1L++IilhhSxYjjeHzCNYGoz8+WIRhcykLlZHnAcVTNXWkhNjMf8MEUmfh9My+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFgwHAYKKoZIhvcNAQwBBjAOBAhwossvpdyt4AICCAAEOBQ2Yh3yt8lrCppVUJGv+W7cdL+6gH1H6dL+cGeOsVnpFUNwnEQS92OLQYENAO4B9vqYgOhnq3rDF+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ed25519-encrypted-pbkdf2.pem view
@@ -0,0 +1,39 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGHMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAhYNXHDae7APAICCAAw+DAYIKoZIhvcNAgkFADARBgUrDgMCBwQII/ySuK0RdrIEOA/iQWXFUgSRi59Z/okt+ab+6coDwGEB6ywo+ysTpEwSTMICq6xWcp+BnACzs1FWrlYS/wWDxzeKy+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGKME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAiKSUa4hjdNcwICCAAw+DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQICGrlfM+5tqAEOAtOZfvoEAPcFs3P+k/D46jpfdBroO7vU3Ki/6+2gRWrnjWWL4UyYoMkT8ZHVk4uOa/4mvnakkU16+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGLME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAhMNKAxJTPu/AICCAAw+DAYIKoZIhvcNAgkFADAVBgkqhkiG9n0HQgoECHiEUPBMhqlQBDjXy5F/zsQz/cVA+F2QfS7D6zLuLjwPsNGX0m3+070d3usNVdyogbViSEhcbvTmOtjp0DB0BNMPr0A==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGdMFkGCSqGSIb3DQEFDTBMMCkGCSqGSIb3DQEFDDAcBAgrm/IrFyklqwICCAAw+DAYIKoZIhvcNAgkFADAfBgsqgwiMmks9AQEBAgQQCsvDlOh1VOEXubKKcLMWugRA+GRNH/la87zQtE7qPP8SsTQgY2/f10rEHLMZ80ysfVq0oGmRfrN79QZETYLdsckWt+HmJyslMCCCswvM8o6rt1wg==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGSMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAhuP6SU4nsQrgICCAAC+ARAwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgE6BAhgU/sm47TbAgQ4wCSj+KK1+rp72Ucw107jbsrffscMFx8NoYYK2RzxnwDZzjiy23bBQlxlEZ94Y2BzNT1Hi+WHJBcls=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGTMFcGCSqGSIb3DQEFDTBKMCwGCSqGSIb3DQEFDDAfBAiXc8D7B3BAMQICCAAC+AQUwDAYIKoZIhvcNAgkFADAaBggqhkiG9w0DAjAOAgIAoAQIQBrLaUdGxWoEOJvN+6DN4SgKuaFDap+JvqTmx2yKEEr0+0HbUMIVUOZXaiw1kVmLCR/jaoLUJGz6rjSGv+9RPCtrvw+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGSMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAjxi1l3WQPQPAICCAAC+AQgwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgF4BAgj4x/sdHkh0QQ4eEY9+UwHGaNE9AsBfjCUTjVRzvrJedZvbPTNIAjipMYW3uYzVcg5I+GE/4V7yhKUzBipS+RLxOGtk=+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ed25519-encrypted-scrypt.pem view
@@ -0,0 +1,18 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAgale+OTVw8jgICQAAC+AQgCAQEwHQYJYIZIAWUDBAECBBDYon9Yg2PXUt8HMaRZW75QBECKmy2N93fMD8+d+dpTnG3bXuLETOhn/SGCG35Pgzvp3gZoRR/tksLS0IBXdmbvjRGmMiWcMZ57Zcd1a+PJvyO9p6+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjA3W23hPPCvwICQAAC+AQgCAQEwHQYJYIZIAWUDBAEWBBDIidXH5KqQ5BsAcSf+VpjxBEBuN9xS3T/q39Sc+gVXmgBTsfD9XI1owhsiG8r49GF6iXztnyJPMIYDMkjsnk4ypdffSTXlRpRUv8lIj+vGE7LUbr+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAgrfV0wImuOLAICQAAC+AQgCAQEwHQYJYIZIAWUDBAEqBBDglOp/wHw9J8ue05R8hroDBEBP2X9ptHWTKrb++WwyGW+s2MuVulF4In3RjYNld1ZYQeeQk4Vnqwq9t+2djgXsKL4hhwEfhk0wCH64b+z0EbHIzA+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ed25519-pkcs12.pem view
@@ -0,0 +1,269 @@+-----BEGIN PKCS12-----+MIIDSgIBAzCCA0MGCSqGSIb3DQEHAaCCAzQEggMwMIIDLDCCAj8GCSqGSIb3DQEH+BqCCAjAwggIsAgEAMIICJQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIGFEN+2HUjTJMCAggAgIIB+MiKzSuuknXax1GE1S9jQ2WfuaNablKCYNUB6wAQJkqiwUjC+DhW1hAcz+4o8Ow1NmuGNiTR/9RKp9ZJmU/GFQCfKInNRL/4WzgQ1l5pKIYYKmKgH+YA5Ci1bnVA8SEXvQxto7K2maWrxvuSxLktHo7ajYfI+Ze5qiGYET9TqWXRiql9ld+oxbtTFDkSzNC4VxqU8HYkZFx/4ld6oKJZ71QaSZgf4t9+BkHNfOknOhAp39WKOhy+3UBmopdBUPpqJAdAPQmR5BUeRibIC+3RllvRW/FLOW/FwfyVoQESUu1u2nl4C/8z+t3i14EVcKuNbBvz0Qja+4fO+p6e9+q6clFVYCU6UvZQncKX8mRdCSGDZOla018L4+Lf4SauXnnyyGk8bEI6kqATtkPh/pK3nVBcjboX437+ZbGRj4H3viC7rBijaTizlx+gb8FhVaryk5bwlzBJQUrtcndd5WdjCYhZPMaB/xFxqSotWWzvxya3A0Z/SgBKj3I+6Dbv+hGIGACVfcT7k9W7pPC8QaESOCZJOqzckik4h9huEyyDQyvxKa18l+RXA9AQ+uop3gtG4WRiosSxzgNPh2e5RGVM40AY3ktrsuilZP9skDMF5viTin1C4p7rPxD3s+dzs5augp0VppJTA74czDilJs3f/u18PPQS2r92ckcM836SDbAjCB5gYJKoZIhvcN+AQcBoIHYBIHVMIHSMIHPBgsqhkiG9w0BDAoBAqBaMFgwHAYKKoZIhvcNAQwBAzAO+BAgHTXORITxbIwICCAAEOHln0Ez/AkpJiI7hvzh/t01Rsl0foaq6Mt28OJNcaLLH+CoyMtg2TTSDWlxrwcTc8A2Ug5mf5jzFPMWQwIwYJKoZIhvcNAQkVMRYEFBUJzG3C+AW0LFRvdr7FoWoQ3wIq6MD0GCSqGSIb3DQEJFDEwHi4AUABLAEMAUwAxADIAIAAo+AGUAZAAyADUANQAxADkAKQAgAC0AbgBvAG0AYQBj+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIDkQIBAzCCA1cGCSqGSIb3DQEHAaCCA0gEggNEMIIDQDCCAkcGCSqGSIb3DQEH+BqCCAjgwggI0AgEAMIICLQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIyxVd+QGw+wjsCAggAgIICABwReIhjPnmxn4HZySDDd8+z43vHNIlplmHIKyKuWkdPZNHi+knA/nqebK2gdLx5hcauatxUVEQCaob/wzbxPfK86NDbf/2LsZqm8TiZbMz69sqxY+2M9QpjRY5WKPnbA8s0uhnsdm5UxCHF4E2omZA6yqPcGIioleCJ+d3b7U74v0uxn9+SjdAY6fw2mJo155sTZHaK0GucDmzz0h4N8RzDhURTwxMy9eXEaBy07rRfDJ1iFTB+YTWNOu++XcwvzWxV8h6EEEBbSE0kuLbnPwN4L8kcxZV/DhlW6QbiwVOJWL4dyQ84+FzxkdIC5lzhvw0lzOGM1WMI27fvFhHXzmRDTPJRZhsjLAZjeJwlw6CDY+04VNi5k+PzoC/mQgRjf8BDCWMyHcHyimrlm43OuIr4lW3qfLP2bUHue6f8ppZq6napC1V86P+bTsYjoDrS/gFwjpieA8JjTVV9yWu+IRsA22th/ensplyfwU/wadDOXJFLvJrP1tL+EavSjphDR1+VSXI3Xp8k/g+RJzxd2P0sTcUyqCTGDCJKplYCD8V/azfitlGfretp+SFHqVVV/1yaFAZu+fI1IFN3YiSqeJrjbGeus9hhLs5jsiFzol3udh9whmp09rYCS+6BPXi0m+izvcLgffoLwjidx2hvwSbRjTUTj9iJGlz/Bn16djzDgyTlJVOm1fMIHy+BgkqhkiG9w0BBwGggeQEgeEwgd4wgdsGCyqGSIb3DQEMCgECoFowWDAcBgoqhkiG+9w0BDAEDMA4ECLuL8XSr9Wt3AgIIAAQ4sIhPGPPiVe7pC3a9af5gSKLNIdbbdYXg+DvnsCB76bZQDkWXfe3+eKwU0RjX4Tf7sEKBSitUQdyMxcDAjBgkqhkiG9w0BCRUx+FgQUFQnMbcIBbQsVG92vsWhahDfAirowSQYJKoZIhvcNAQkUMTweOgBQAEsAQwBT+ADEAMgAgACgAZQBkADIANQA1ADEAOQApACAALQBtAGEAYwBhAGwAZwAgAHMAaABh+ADEwMTAhMAkGBSsOAwIaBQAEFHPqC5cmwg+m5BlOEYjZTtH8iD8wBAj8srMZ8dqO+YAICCAA=+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIDrQIBAzCCA2MGCSqGSIb3DQEHAaCCA1QEggNQMIIDTDCCAk8GCSqGSIb3DQEH+BqCCAkAwggI8AgEAMIICNQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI+m6G+P6kpcWcCAggAgIICCL51B8K3PIhgUNA8wpwp4dad0LOS8IbVvrN522K3/urilvQl+ZuxvHgWhbg8vm47LYCZD+M8k3RHXSR5Rdd9qCOD44bVwFlM8Fq0yYg9hMjhGPgMk+SYKGErU2OppX3FJPAL397BlWTMpvpvH9t0nEnvAcEOEFHWytTN8/80Pl2j2lseGS+OEMCahKZ5yfLcNDl+SGA2hfi34he62i80LWWJye5UzPEKJgRU2KAIR1OMciGnpmN+E90ozaZbgrZqnwI3bYYpAGMPBWwgpp4CVvBpJxT5wkw82TaC9WXNutXQmBWri1cC+HXrkeqG7lO2E79AkJANVq0LSaVhcDb5lYoqif7oom9W7gA3SoFQdN8j6LE9XyHTc+wGCP4jspnA66fGECOIerziF7b4EfQEIFtmX6DSpE9WCjL9MoU8Ftxt1UzY6WzXSi+LrsSUb9R4sdkzjzkgw67r3UAyvO845qnXxNaUtCPPOq7pCSOwPNaecqSwNxQgxZg+xo0yyZbm3bjyrLK1suvhegk9TANTNjmyaYhQ/DyO07+V3ee01rMYVOre0hWliQRW+bfYh9cGLFKfQcMmwRv1bCUV0D8keOL3iTxQM7ReyIZeJDIqHfDckhWgbvpBs4dBX+GFEimDEsLfXjFPK8M+z3V9Syi5ywigwICyvXIat1NspD0xcTkp4BhKCVNct0m2Mo+7r2LA3IwgfYGCSqGSIb3DQEHAaCB6ASB5TCB4jCB3wYLKoZIhvcNAQwKAQKgWjBY+MBwGCiqGSIb3DQEMAQMwDgQI5KEgbc5PTYACAggABDhD49uTQNK70taieGdPG4+F+jDIhYIWX4BqI4Op+i5RrLmZLLh8IIwS9u54L2mU8iLixcUGJM7hwdjF0MCMGCSqG+SIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqEN8CKujBNBgkqhkiG9w0BCRQxQB4++AFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUAMQA5ACkAIAAtAG0AYQBjAGEAbABn+ACAAcwBoAGEAMgA1ADYwQTAxMA0GCWCGSAFlAwQCAQUABCA9iAPHbYTZUVBGKEN8+m6ecISHakuyKxNOMdo9sjrPOWQQI1CwiN3JXU8MCAggA+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIDvQIBAzCCA2MGCSqGSIb3DQEHAaCCA1QEggNQMIIDTDCCAk8GCSqGSIb3DQEH+BqCCAkAwggI8AgEAMIICNQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIhOBk+A4W2LFMCAggAgIICCLYRPF4zD4p8DxHKzg+OJXB2KIb7k/3LvCOt5XdpFuicw9Z9+oBVviniBOI9AnzYwIm8lKCtfEazSY7jfRqX3tbLNltN6uBr8EaQM6mu5glgHccEF+LG4A7CNgks85IwFovCj4zT1zE5PrT7B8xUmLlMCGup+dImlmBPvzUaNcqq4O3mg++m4NkCI0qW12LTczdy54mPiaNXaklqQHjHBSEGugtMexm9CWV8t2bXuZsZv161NKj+d9lu2EfVN+GutIervK5MOa0o91+VjdFWh4UW7GjWmlFfvCO92dg2BXnm+J+sSV45+PM3UyM9DhxjJjYpv8E4Qm0AH3ylx/WY+23AKa5LzCokfdrPOfYC4tQWDZhtxclvl+bZ2KDJ9TUaNoXB0pdaSYppgn+AfMTPacgtDRTVfWE7TbnyTT9GV67kNhZeeJnyuP+LwjjGT/F1HqjiQswz3+ssdIjsfXv2M3laCF30kWrsRj+nUx9aFbplovYhm+Bsimo+GBsk+JSV8aWDO5v9WInQ0nX1qdMj70edt3U/jU+8fXrdoGshyJlJL5vhTN0lrUKR+y+Rugp6DGu2Jh4C+8Plzt3rsck+VR1anY3sLqYs4Nm8NTmrlO5W6iVJI96i6Wkbi+kCkQ+8FZAw+a5NvNX9fY1BfFLGCGAcYBJsKXATMP4e5Wu2rpefYMv+gtLBVgVoDh+A5vGr0UwgfYGCSqGSIb3DQEHAaCB6ASB5TCB4jCB3wYLKoZIhvcNAQwKAQKgWjBY+MBwGCiqGSIb3DQEMAQMwDgQIEbbZB/ZOFvICAggABDgz1+uoucIRZUyWxQCnM2Z1+VrIDzkjSRnzfh63gtKAYax2kaYUzpGnZMY4DrYi1WQDTmnfTY7XE7zF0MCMGCSqG+SIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqEN8CKujBNBgkqhkiG9w0BCRQxQB4++AFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUAMQA5ACkAIAAtAG0AYQBjAGEAbABn+ACAAcwBoAGEAMwA4ADQwUTBBMA0GCWCGSAFlAwQCAgUABDAzqjArAvU6yK/h2AkT++5m4+jU4enOBGOCCtPDy0+0jaM1EkyDTqYyxoxsQBDXwSS0ECLaGyXHiSBnYAgII+AA==+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIDXwIBAzCCAyUGCSqGSIb3DQEHAaCCAxYEggMSMIIDDjCCAhMGCSqGSIb3DQEH+AaCCAgQEggIAMIIB/DCCAfgGCyqGSIb3DQEMCgEDoIIBczCCAW8GCiqGSIb3DQEJ+FgGgggFfBIIBWzCCAVcwggEJoAMCAQICFEInN18eR8QZFfy9Yb39ycd56f8PMAUG+AytlcDAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDkw+MjE1NTA1M1oXDTE4MTAwMjE1NTA1M1owITEfMB0GCSqGSIb3DQEJARYQdGVzdEBl+eGFtcGxlLmNvbTAqMAUGAytlcAMhAPb4Tt742Tc9pS4/MMDFCjYzApuBncDQ5JWS+0QL4ejEUo1MwUTAdBgNVHQ4EFgQUV4Bp7nHkyDQxsCEBe5xqQauBKXgwHwYDVR0j+BBgwFoAUV4Bp7nHkyDQxsCEBe5xqQauBKXgwDwYDVR0TAQH/BAUwAwEB/zAFBgMr+ZXADQQB+hVuzD7hONYSSQAZ60fo3ZBjs0R/guNoOWIGBCgSxaqhs//Np31/mmiZF+bsT67I2lYUyh0ogms4Y2av5dJ40FMXIwIwYJKoZIhvcNAQkVMRYEFBUJzG3CAW0L+FRvdr7FoWoQ3wIq6MEsGCSqGSIb3DQEJFDE+HjwAUABLAEMAUwAxADIAIAAoAGUA+ZAAyADUANQAxADkAKQAgAC0AYwBlAHIAdABwAGIAZQAgAE4ATwBOAEUwgfQGCSqG+SIb3DQEHAaCB5gSB4zCB4DCB3QYLKoZIhvcNAQwKAQKgWjBYMBwGCiqGSIb3DQEM+AQMwDgQIX1+nSa1n/HwCAggABDjgLTq2CBAruDxaYp+1TvD9bU2VV5rzE8J4e20O+1lH1ERi0gHw7nLtjAsxBWVI20NOyalAB0cG4LTFyMCMGCSqGSIb3DQEJFTEWBBQV+CcxtwgFtCxUb3a+xaFqEN8CKujBLBgkqhkiG9w0BCRQxPh48AFAASwBDAFMAMQAy+ACAAKABlAGQAMgA1ADUAMQA5ACkAIAAtAGMAZQByAHQAcABiAGUAIABOAE8ATgBF+MDEwITAJBgUrDgMCGgUABBRTDkUp0GY2M6X+Hz5M0GBAj6ZDYAQI8L4NQjeGa+4C+AggA+-----END PKCS12-----+-----BEGIN PKCS12-----+MIID7gIBAzCCA7QGCSqGSIb3DQEHAaCCA6UEggOhMIIDnTCCApIGCSqGSIb3DQEH+BqCCAoMwggJ/AgEAMIICeAYJKoZIhvcNAQcBMFcGCSqGSIb3DQEFDTBKMCkGCSqG+SIb3DQEFDDAcBAjB8PuFLlFLeAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQME+AQIEEBPci+1MGZrIWpKRWObpG4mAggIQ0CxiJz+08OHzg/h0QX5++RjcH3HeyNs++C84tjzJOcnRe1xRZ9ritRaPuFN+FrEXn73dKJmTogpXvGGlfxomoHyVuLRl1HBbf+PUz+rhj4NHoRfTVBwqaP3GNO7zOmipqQrMi7wZTtUtxaXgRzsEruHCIe99T+5MUQ+OCLRqhoHh7ukwZlAyYz/dtdnk3Q0GfQAmQjhf1VEsIMdE1YwcQBInA3B08uPFSnR+JG0J7Hs164wU/o7oust8uPxuGnmEJjcQ3DwCIHN+5cLiRmO5qnZc6lGKhJnHGEWI+BoziLgj993gkB/2Ir1RwWsze05bp6XPI6irRUyLOkC4WuSOFzTu2ck3fL+tVgmtK+wUSO93WwWspDNdQX5kR/gIl9WbAYWkCjAhk145dlXRTlJxpksp5taQF4dY3GHXs4+NrjCe5773t/xW1k1kioZL5mIjT2p9MUM2DT5wkbpd9KI76pIB2BEjiAo+cZTJ9e++Ck9JnUrck2gSYg4CloGabMXt9nsJ+V4UpcJm2eXAlswno4cxFmXKlT+GtqzzKQH1+sp7YAxrdqxATlwsyD52rEJo/rKOoFt6dyDcc8PLdXrErfoY1WAl+Z6p4iDp2VYqr+GMx0R5uArjhR+R7rmGqTfwDFgc6pp4HWBwGoQ2ZfWcpQBUNJPaZ4o4uEpd270XNR+mW+BQMXXbsRTUK9AT0iL7vCFVdyPQg4DMIIBAwYJKoZIhvcNAQcBoIH1BIHyMIHv+MIHsBgsqhkiG9w0BDAoBAqBaMFgwHAYKKoZIhvcNAQwBAzAOBAgfExpEdcn3jAIC+CAAEOOoKa1gzF3tuBnPxpeEqkeIrPQl+t4jyzFj11QC2QswhPTcc0NLIisoTllk0+m8VrMAhZzbz7xHcTMYGAMCMGCSqGSIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqE+N8CKujBZBgkqhkiG9w0BCRQxTB5KAFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUA+MQA5ACkAIAAtAGMAZQByAHQAcABiAGUAIABhAGUAcwAtADEAMgA4AC0AYwBiAGMw+MTAhMAkGBSsOAwIaBQAEFF0dkSA5il/b6lVIdVS9t3Yh092sBAi2pnHLHUDLgAIC+CAA=+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIDzQIBAzCCA5MGCSqGSIb3DQEHAaCCA4QEggOAMIIDfDCCAmcGCSqGSIb3DQEH+BqCCAlgwggJUAgEAMIICTQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQUwDgQIksEU+f1NeOYYCAggAgIICIJSOWj0qBbk5tlV/M5hekb8fYtjFVjbN287fAaWFEHty1EXo+6B1wKULhaIBJdlFDa8feE6icJOtYHwtiGKRAfo2HfAeu4IsHEY9Ejvbq/vT7QTH2+GvKWmE45mZTavedRnSLGBq9/JAOZMvVnIoW5DKji1e+piXP8sTAn7R6QWIELvf7l+LU/u1dWOZGWxWkztLAAU4E/PhAYlVMkOD0imZIYcF1wKhqpZUgzsq53AdOWBghz5+7Y54rULci1kZ9H0e/YvUGsB2ahmJtgO6vCLwi06+ohQ5ONzHPYOaZ+yKiBou+w4I+YeOLlk7zTDOFKAVgEHsg2XSMvFRVnoPDm/UxYzITKlmyu9WWptmQnVtlf0YKK6aE+lirn10ZUR4oO5AxlvNdJyuBOHFb5z0eEGXta+dKse381Y6lRAA2/nA7z5+3fosch+MeU0Jc7D/ZWWCmPOsLzPvWWOGYTxU7iba10iMhEvT55qCCMrYQAEJ45IwWItKCMY+aKiNZythBWiajke41tZYWgsVcl0PcDTlrmqaOcGCKJt797hH9QrfGlnpRZ65dIVY+LR8QSjZJl30doV6ga56Y2oktPvQCgs6MLcCU/QlAGTGmneMRnBwm0EgU/fQwg7yj+tIGwbkd4z8Fw4quRnAcvjrlmePM1vRo5cX49KGnq26WS+oMLWh6cGotVPRHZLzbA+gpLUBEv5HUYLS7vEbTp+TCn57OFrXgXXuxR1UmUwggENBgkqhkiG9w0BBwGggf8E+gfwwgfkwgfYGCyqGSIb3DQEMCgECoFowWDAcBgoqhkiG9w0BDAEDMA4ECFwonOIg++flzAgIIAAQ4kpdDC/If2Ro8CTME3LIf3OvZw38/CliPX6Yy4MLI7JSME5RQUrhY+/Saw4/L6B68mukeWZMBnOmExgYowIwYJKoZIhvcNAQkVMRYEFBUJzG3CAW0LFRvd+r7FoWoQ3wIq6MGMGCSqGSIb3DQEJFDFWHlQAUABLAEMAUwAxADIAIAAoAGUAZAAy+ADUANQAxADkAKQAgAC0AYwBlAHIAdABwAGIAZQAgAFAAQgBFAC0AUwBIAEEAMQAt+AFIAQwAyAC0AMQAyADgwMTAhMAkGBSsOAwIaBQAEFGs4YBeZuWInWICuG68IQgRV+jLS2BAh8zZrG9uqvuQICCAA=+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIDwwIBAzCCA4kGCSqGSIb3DQEHAaCCA3oEggN2MIIDcjCCAl8GCSqGSIb3DQEH+BqCCAlAwggJMAgEAMIICRQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIk4KE+evBBgTMCAggAgIICGNsQ9DokOToB4WN9X0SQpj0yKDTMzWWiJWouEdbnH9rpsLHB+oxPJRSdvl6qYD9BDIIyQBb72/oJEyv9x4vevZ4Y+dr7TI5iam8cqf/jqCRDcJngJ+F1OeoGw/48yniY+wLLX9stA7mLVlGOqmkv5ubr4oS3B2DvMgouaiqOzBY970zepk+aI8v5bDciknvNxLcqwb8gef9qSi2lXjSS+da1r/Auv8wy1DWMb0wXppkr6iE4eRD+GBrJBCV0+0GkdlEQdZlUwF9L6Rk5Gh6T5sfqb6MfetbKdJ9o6ZZsgBiePVco0yrV+gPVCaDkZwoIH+iSixn0YOqVjzWrnARyWKdf3esXGloIQhQnrfuDIdHK8YBlXuN3z+EfSmfRh/+aWG2DW4s9wcAB1t3GMZewMZ+z+Wrie/sYgp2o/105Q8dUQqNDu4JBYH+1xjfqyjlWOtzEyJk1wrUefOWIFlHkzwZcGrY7kUSJNWY+ljm+ZZbvJWQE7mPXet/+yZFSXv42FwFwU0C/Lo8BFI2c0KC4CVuFNmfNWToJfa9nnEvInh2rra/XyuyeHHRe++Bc9CMCkKWnOMoC/p3jnjatqjsJdHhXVViJQcvxjJXwyJpRi3G2v1SWz6oWFx0eX+7K+rZWnrTT3QI9vSX6aCQXcgc9XNRtdObvQ1tToOt+qgvUBerqDjmvTp6eKsNw5a+Z5ZxH7oqjcWgnQIYfT2aEKMQ38RMMIIBCwYJKoZIhvcNAQcBoIH9BIH6MIH3MIH0+BgsqhkiG9w0BDAoBAqBaMFgwHAYKKoZIhvcNAQwBAzAOBAi7tsvABCrGugICCAAE+OL5EeTq4HVyV26RNPe2S3AKQyGxNzkwMJMBGyhIG1XPPLqumvDgJqrPcAD/QwRVj+MiJb51N45xC0MYGIMCMGCSqGSIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqEN8CK+ujBhBgkqhkiG9w0BCRQxVB5SAFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUAMQA5+ACkAIAAtAGMAZQByAHQAcABiAGUAIABQAEIARQAtAFMASABBADEALQBSAEMAMgAt+ADQAMDAxMCEwCQYFKw4DAhoFAAQUzx421ERBMyF/yNxR0GS1DLlMANMECPiknVVT+vYXVAgIIAA==+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIDZwIBAzCCAy0GCSqGSIb3DQEHAaCCAx4EggMaMIIDFjCCAkcGCSqGSIb3DQEH+BqCCAjgwggI0AgEAMIICLQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI70+G+ehLEN/oCAggAgIICAHqLWEksJuuLz0gv91JjhDKOXcFZFi0FPLkid4IvSGamAOBt+ABgWh2v4GydPxAnLdeggMYwL/mjudrWuymgUu4fjIvTLZi54duYzBr4B+iMShlo5+C/kc5qTqhWBqPfRi6b9q1ryWZQuJR5j101BLRU90axIUpT7g8UEWzmLtEZfbZj7m+5cXWI21H/3WmzrbhBT+MBL7PeSmAL7SB3z+jGpqjIAlgDSABWpINyKQIGTpwAQK9+v1y8nJ+VfE+V/MMz4nVBdzpiP8eczg7v4CeJq/AhLU1zYS/1wRs0Smgh4azn+Hpt+AoY7KfiashHgZnQOQ/YCKoUwDBoZg9A3tFiFHR6FNXXX0VChUzPEbfAKtzQ+J9vz+prl3uIBvOmNbPLK9QUOgCRfmMqpSzqx7WCBU1KHynS69KBtEQCrxphItLzLon5K3+uFjuavUkGab6WexWZC7yuVihFZbRunXjrqfP0AMG98f6iUhSLKmyzHqho1Xx/vdJ+ly9BFt0lo4ZV4dySYLy8fOSOw2GqvxFEVj+Giz0a4RCC2vbK5IjyTf95lsp3qiFC+fBwMhv39AtapGQVdVqMrA5P5TFplSZoz2dpnIlPwCbpyvpEe8LKc6oUpu5eKmCeU+RQ0zj4+J8KAzfVbzS1yMJe+umu95i5vfEUCs98C8RvDCnHsx87T/x7w2z8b2MIHI+BgkqhkiG9w0BBwGggboEgbcwgbQwgbEGCyqGSIb3DQEMCgEBoDAwLgIBADAFBgMr+ZXAEIgQg6so49BSrEJR5mdkomLkSagSPGcNJIanjlT+3V6/L/e8xcDAjBgkqhkiG+9w0BCRUxFgQUFQnMbcIBbQsVG92vsWhahDfAirowSQYJKoZIhvcNAQkUMTweOgBQ+AEsAQwBTADEAMgAgACgAZQBkADIANQA1ADEAOQApACAALQBrAGUAeQBwAGIAZQAg+AE4ATwBOAEUwMTAhMAkGBSsOAwIaBQAEFG+LXFJAkw20X9gqruZyFGH+jWmOBAgD+bdl51a1P3gICCAA=+-----END PKCS12-----+-----BEGIN PKCS12-----+MIID+QIBAzCCA78GCSqGSIb3DQEHAaCCA7AEggOsMIIDqDCCAlcGCSqGSIb3DQEH+BqCCAkgwggJEAgEAMIICPQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIow4v+Lsy+8FoCAggAgIICEA1VfaX02WNEBrOhNyi065Y2/GOi/rq48aMVrxk66tdu8J1A+scvnzo7ZKJAb0f93U+NUwP4BTvd93vYmAPa++3Mq+alBZvkT+OM+fmIbI8ZACvlr+6B0FjU4+GOLferGud5k7GopaRjVnNErXPBtfmCi3xqfrloRZNaJo1PQIrVXUpGfK+xB5iBQVIyTFZa9CGc0OFenGbegoWhR0NEa7M/M4zynaY+4HNEqv+yY8mm+aiim2E+r/Q3e5zHdr63k90ltnA+xpNyjeaLhBHixgD1njzPvHkhNXenss+GIMi54pmSz52N+gRX3K9ZwT4eSOQwh19fAJmK2TpCgGyffuoQMRWJfhrAsZdLoLzpOdaL9CR0FEFwy+mB1j41xMcKe2yoSOGi8A92Lbkk3xN0c57fECcLbwoaZZyegAqAYA94xr4M6yzvpq+7qVZKNaB4F2n4iGbfwRgendan7LjTfPhyesjuph71/ee4WVygOm1HEq1lqLS4LzX+FFwINo39oQTAIYxoXK7u8V5f/3Qj6DgiQkVMdvwn9qi8Sf6BG6AvJw4cjSbb8g7Z+RQ9lUaRtVx0vSjxd11fOp2etNTcvxDfM9du4lT2lKxJUP4ZHiwWveCGukXwaVs7J+BGzhCB9X9Lju9OdSjzWRPe/esHZ3uyyKif+uKR2st9pMq5QHwK53lkiWfCdnLKYA+U+bgNu+U+gZs3xPpNzCCAUkGCSqGSIb3DQEHAaCCAToEggE2MIIBMjCCAS4GCyqG+SIb3DQEMCgECoIGeMIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAj3+PgR467622wICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEELWZsHeLIm5n+za4zYKSmtdYEQNaOREFJzeUI9EuL/4xTKOTRWK4kmQ6rYPc8/xbtUTdARKblfWGd+POrYXUivZPNJEn38GCAWmNsIWGGOVc5kO8UxfjAjBgkqhkiG9w0BCRUxFgQUFQnM+bcIBbQsVG92vsWhahDfAirowVwYJKoZIhvcNAQkUMUoeSABQAEsAQwBTADEAMgAg+ACgAZQBkADIANQA1ADEAOQApACAALQBrAGUAeQBwAGIAZQAgAGEAZQBzAC0AMQAy+ADgALQBjAGIAYzAxMCEwCQYFKw4DAhoFAAQUi8/XCc5c6tAh7OhhCR2ZKE8sCdEE+CEeT/cMXItocAgIIAA==+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIDwwIBAzCCA4kGCSqGSIb3DQEHAaCCA3oEggN2MIIDcjCCAl8GCSqGSIb3DQEH+BqCCAlAwggJMAgEAMIICRQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIRRt1+aGJ9pBoCAggAgIICGFNFd2VuEF5PLthNtYaAYLz2s4nHfzIM0J5BloKddcY7yeIB+mj6xMGPO0U2BKmRHk2lBtWkLRI1C7La+aLv/wqccnSq+gpXVfHGx++rOx6SDqjXK+53VdIrEGWIfYkJE3HqF/WCeuvot+qBK1JiRoJN1Sb8MSXHuoIjR21ic9vJzjGLI3+hyxmzzekk0TZ/7yfdoWc3MU76ZlBGMzVhbfHlz6Oi1GG7J+TMs6hnuO2BDJMSAAV+9C8xwU1B9RAiDsZXL3lCq8My2+u5BCNoYOcuPnvMlk1quZjuMuQJvFJH0ZfRnf97+zItYGcqNqxWbs3S61ma8T8jYZi1eL0hvD96n6Im+FRF63aAgeXWXLvzgGJ2+u58W++OrKEhpu+ipPtdwdWR46A3m8+jyctNolC8X/1goLs7EDDGfjQ77KIWaHxP5G3Ez7+v4dB7IlRIjHwoFwCd5TXiORrsQQWwtnSgr16st6cw2ObLjUeFKlAGBPYE1hP3rbR+6r3+nMsH8J58cfvqmpL0+ZnFvIVZcJtb5GG7FEoeVhQM3ZdIZjmXreF7cwe6K3s9+cnF2v3ikQ7rH835VUtWAPxPmh6e33jJLyF1LmmYMSv81R50jKqJdkqCI2dDUa1xG+ZgI6mJwtnwi4jSOCRs6icJR5XXbuyPOACACV1hrXEVTAXCnkvrB0vZ8FjTqbacS++1PI1v77ypPIn95aFn2fn7i8ojYf2MIIBCwYJKoZIhvcNAQcBoIH9BIH6MIH3MIH0+BgsqhkiG9w0BDAoBAqBaMFgwHAYKKoZIhvcNAQwBBTAOBAh05qDI2Ar72QICCAAE+OKOQzu+R5I48MSppj8vB6pLOR2z7g54qi3BqyZOeeDe2ACd6O3NfG/PYhBlaf0+3+Ve18P3K7uxS7MYGIMCMGCSqGSIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqEN8CK+ujBhBgkqhkiG9w0BCRQxVB5SAFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUAMQA5+ACkAIAAtAGsAZQB5AHAAYgBlACAAUABCAEUALQBTAEgAQQAxAC0AUgBDADIALQAx+ADIAODAxMCEwCQYFKw4DAhoFAAQU3eBhLahYNnEAfOqstFWLRhkGW64ECIM22J8l+0UAiAgIIAA==+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIDwQIBAzCCA4cGCSqGSIb3DQEHAaCCA3gEggN0MIIDcDCCAl8GCSqGSIb3DQEH+BqCCAlAwggJMAgEAMIICRQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI8DlD+f/FkAioCAggAgIICGKfq8w8XKEbdolrBnthXWJAkm8mw8JWavB//y97b4r7HjtPw+0uUvfXbhIRnYNu76tac9zlaH66n1xLLqcUAEK3RdwPLN2qx0hs3vulUV16TVao+U+smU65HjazxXC/Oa3lfxAYhlxX44Nfe9p6uCDdf30G4yDKJNNSiuw5K83jpxrlIeH+UIGakE7XQ9gmROuOSAXJzmBOjw6rUbBtbUgP/J//yvP9ZeJkB3OFAYhohGo/9ych+L0wlk6Md6Vtq0nv/zYQP9PofXzfZUh+rZha3rxKioH7xAhnRNxvgh4mksqTyJM6k+OsXZ3rLvxehTKgSm5clEzr/T3qxjTusuNovTlt/avBnpnZIwS1qzTeq0zrd/m7H9+yM6NV7Cbl3LtHzQEQkrJGxVACDc6QcCrmHb7tJBO9dAHiZds7OXlI3QJ4D/aGAru+mlKD4ZFp0XurJOu/p6BocEyXo1D0yYEMVoo9/KGH8F4ugwyIQmc0XxrO5mmeRzQk+PH1Em4Im4DCKXX0wrlBEPmVuevlh3JmWQ0UEtmMxrhaiQ+Ufjr9TD0Hn7D5wZGVu+B5ymH68fPVe/HwksVHIC28uYM4EDMdaKs0BiPRxI5u2jBcWfSlow/jVsK86TcZ2J+4+p2X1949rD9Rz5YKXkaOGnkbkIWAyYDFZF5LW4+jNXxCoo+zloz/CIJmNk3Sn3F+v7Ao4VDmb8FANwwLzzYoZ6ZI/KmPMIIBCQYJKoZIhvcNAQcBoIH7BIH4MIH1MIHy+BgsqhkiG9w0BDAoBAqBaMFgwHAYKKoZIhvcNAQwBBjAOBAjrE5kdbf4WxwICCAAE+OL0cHLqeh5j6Mpts0fF20kwdBzS1/9yXPUtcD+CzwEvFaJCMBpt8s4inBnxN9/vi+D7hg66rk27phMYGGMCMGCSqGSIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqEN8CK+ujBfBgkqhkiG9w0BCRQxUh5QAFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUAMQA5+ACkAIAAtAGsAZQB5AHAAYgBlACAAUABCAEUALQBTAEgAQQAxAC0AUgBDADIALQA0+ADAwMTAhMAkGBSsOAwIaBQAEFKL1V7ctReB15tch/MxfPTTEW0x3BAiQJg8kHcKT+2gICCAA=+-----END PKCS12-----
+ tests/files/ed25519-public.pem view
@@ -0,0 +1,3 @@+-----BEGIN PUBLIC KEY-----+MCowBQYDK2VwAyEA9vhO3vjZNz2lLj8wwMUKNjMCm4GdwNDklZLRAvh6MRQ=+-----END PUBLIC KEY-----
+ tests/files/ed25519-self-signed-cert.pem view
@@ -0,0 +1,10 @@+-----BEGIN CERTIFICATE-----+MIIBVzCCAQmgAwIBAgIUQic3Xx5HxBkV/L1hvf3Jx3np/w8wBQYDK2VwMCExHzAd+BgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMTgwOTAyMTU1MDUzWhcN+MTgxMDAyMTU1MDUzWjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29t+MCowBQYDK2VwAyEA9vhO3vjZNz2lLj8wwMUKNjMCm4GdwNDklZLRAvh6MRSjUzBR+MB0GA1UdDgQWBBRXgGnuceTINDGwIQF7nGpBq4EpeDAfBgNVHSMEGDAWgBRXgGnu+ceTINDGwIQF7nGpBq4EpeDAPBgNVHRMBAf8EBTADAQH/MAUGAytlcANBAH6FW7MP+uE41hJJABnrR+jdkGOzRH+C42g5YgYEKBLFqqGz/82nfX+aaJkVuxPrsjaVhTKHS+iCazhjZq/l0njQU=+-----END CERTIFICATE-----
+ tests/files/ed25519-unencrypted-pkcs8.pem view
@@ -0,0 +1,3 @@+-----BEGIN PRIVATE KEY-----+MC4CAQAwBQYDK2VwBCIEIOrKOPQUqxCUeZnZKJi5EmoEjxnDSSGp45U/t1evy/3v+-----END PRIVATE KEY-----
+ tests/files/ed25519-unencrypted-trad.pem view
@@ -0,0 +1,3 @@+-----BEGIN ED25519 PRIVATE KEY-----+MC4CAQAwBQYDK2VwBCIEIOrKOPQUqxCUeZnZKJi5EmoEjxnDSSGp45U/t1evy/3v+-----END ED25519 PRIVATE KEY-----
+ tests/files/ed448-encrypted-pbes1.pem view
@@ -0,0 +1,40 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MG8wGwYJKoZIhvcNAQUDMA4ECFWox81pCznKAgIIAARQzXqhO+QND3jDKs9zv1Kj+MNU8LPm/PpazS6ec7qkxavm3P+5IFMk8eQl61ZlzgsZh3mLzERa3PI6bWlZLSe8V+7UHVIpw3KCQcQP42hfAQSTc=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MG8wGwYJKoZIhvcNAQUKMA4ECDioAMUqN3XrAgIIAARQL2hyA+GU61GYy+T1t9aE+ImSrEUBls7Xl3Ot4hOiLjOAP6jrZqTV05RD9QQwxZHbRDVq/HX/6A3WK0cWd5LXJ+u9UUhF0M4QTSJ2YvgzC8GZU=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MGkwHAYKKoZIhvcNAQwBATAOBAj+fxvGvZUmFgICCAAESR52DDqYruEbDKjnQYPb+4pqdQ98Tenh9F9vWcYvlPHKroHOSrPuW5p0ISNcTMjs6WcwfzrRVRLKMIUwUxyOc+j4+KPXziQpkTCeg=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MGkwHAYKKoZIhvcNAQwBAjAOBAifgbGu7z4HxAICCAAESWH3dIM8ezNr3m5cuklg+rOFbFNZqS9bNjvRVX2rQ4UsNl1PVNw3cxe9Mg6AYg7mXffibNOVZWwzbYi/8jHv3+f2atBdH64mI2ADg=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MHAwHAYKKoZIhvcNAQwBAzAOBAjiQ6AN6cgfbAICCAAEUNi/xo5EN11/48aKiymw+3iiopFH549Yhzl7h/R8PMkR/73xSokPpShMUFiG9dfBHiJU+JrlCg7oINFK44+R0+YAN8vHdg6gXJHyxQuONZQZ7l+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MHAwHAYKKoZIhvcNAQwBBDAOBAgG3+c8DYCcnwICCAAEUARVfrP73l/qnbQwBVpZ+RxBDrm3QT3Xn/VmclpSGcGBurOV8qKO2nYj7WzvY6KOxXVfGsrVuh/uOP7hn9l4L+A2KiGc0TM0sa9GGwg7nVWydN+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MHAwHAYKKoZIhvcNAQwBBTAOBAjD/E31ow0hhwICCAAEUIEjJ4SJ9Aa5lUhe3a/q++1sf4bi9tFscXczGy19qKwja3zyisb7A4lFhFFojzN9Xf72qLQZlWerxG/E2xbaI+/toDPUlkEqareLJSm30dqltD+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MHAwHAYKKoZIhvcNAQwBBjAOBAgGWbPgRlLvWAICCAAEUBBrF4fOikjttSlIQSyj+Uc84dgLgNVoxjSgEw5TFVD1hnHf6IhkDE/ke+sKKn897QzW/8UtLdHwdHBE1F04W+BOx5TskfCIfvbw2U6OAFQUK2+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ed448-encrypted-pbkdf2.pem view
@@ -0,0 +1,42 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGfMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAgyO4eJgChTHgICCAAw+DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIjCW/gTtbGrQEUBOPwmDji8JmdtYKneqP+eHoOJVTC4h7EvPI8L69/qCWmdoQwb5FZQ4swhLYhEi6HUzaKF9rmF71SG0yj8x94+Ys5H6aRYf4yJ8mTYq/jNdYKf+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGiME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAgPnWL5vcrxMQICCAAw+DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIk8VyO/qFnksEUDDZtPg1FOH8PvST+LKW9GEgmQVzqxYyQPUDVL0EjVbP5TS4Cs/bA/Kx4vX3z312PeY3I8JZtVoBLjWX/+sy3xgpqnGSmuNcvMwWaaWzcYD06F+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGjME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAi08Idt7k1mcQICCAAw+DAYIKoZIhvcNAgkFADAVBgkqhkiG9n0HQgoECHBod624v49oBFBLW/h5PDwAyvm9+NpmnMEgv1BHHfVpimwOGrHb29zrB8vxNA0h+8Lo0BcSZTpnL8jbN+IRLfBmyy8zx+EON4rhK8NS/auC1Wz/a03e4bCtMtGA==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGtMFkGCSqGSIb3DQEFDTBMMCkGCSqGSIb3DQEFDDAcBAiULPupkmkOnQICCAAw+DAYIKoZIhvcNAgkFADAfBgsqgwiMmks9AQEBAgQQUeP3K6y6H4/WAYH3t0rKXgRQ++Oo9san10yxV3ZjXi4VJcXiW/efe8T6aomjdLAfKx0hNtnf4YngHtBVyAHPLfI1x+9zWCBTzEt+agp4Umazk59dBhiIB7eiOoNhUZOFvHTtc=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGqMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAiA7abz6Y7GFgICCAAC+ARAwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgE6BAiq6CcB7kKwXARQKLMY+sHJoPrAEufRO5/iMNEvWbhrOTY1pNnZkryJTfnJSW5mnNYOdTfYFiItrd/ivH5+g+yMdFYFBRNDT27J1OBDSJHQQkrXmf+pb966ElVy8=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGrMFcGCSqGSIb3DQEFDTBKMCwGCSqGSIb3DQEFDDAfBAjOvvRIconlRAICCAAC+AQUwDAYIKoZIhvcNAgkFADAaBggqhkiG9w0DAjAOAgIAoAQIX+HtJbu7CmEEUAC0+aGZdMCNUA7aBhThDoH4DhK3F+u4TejId5xx00znWPd5lB/yXWJhaK6Rce/+k69oF+UUu9NYZnwURQ6SpoMeu7jLRvuasVNDZs9NKPm+MH+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGqMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAgGFmNY/ilxeAICCAAC+AQgwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgF4BAggk5ndlY33agRQSd7A+aeL5zykrjjIGNEM6DyzjN7ooiAd4qHHC70FkaNQ8RJneqXXxi1nNZDdXhaLa/raU+ifdM8M8566L7O/s6N5uKR8W8+ieDlmmwAIZDEGI=+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ed448-encrypted-scrypt.pem view
@@ -0,0 +1,18 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjsra6FzTgntAICQAAC+AQgCAQEwHQYJYIZIAWUDBAECBBBBzcngO3oAta7DS9ulmjcgBFB4gmQnHQmI+rbK+RFBjSrSrvHGeYzEGZO16iD0bQnPUTwStLE4y4rLE7AvD0/6cjjW9DnvPmPBW26ZT+IyWW/JZMykzfM0dk6ccNLg0MHibNcg==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAgbq3ygXPa8GgICQAAC+AQgCAQEwHQYJYIZIAWUDBAEWBBA3IcY5TlZ2FzxzTGljkaVTBFAEJRrLXzngWHUH+No+Hq9zGFJ6yBekd403KmlaHCdVPd1JrmgnYoPS7RYxhTMKQ518Zn218Vtf4Pdjl+XrN2MQ1O/eiQM1lp4lvw7gt4XImUFA==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAhoIC6uL0dKvwICQAAC+AQgCAQEwHQYJYIZIAWUDBAEqBBA3aL14nbkyRn7MPVVqbgZFBFAY3ogxHUTT8ozS+tDmIogHDjazBBs98CIwdwTYrES20oZ41NCezpXlpPVFrzjqHm1rP6ZudloRPUHG/+xSEr1JeEKm5mBGTA1sjRJFyT5Chd2w==+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ed448-public.pem view
@@ -0,0 +1,4 @@+-----BEGIN PUBLIC KEY-----+MEMwBQYDK2VxAzoAWopNPKAV3+c61CJqDrnMnRntbN8QSNHZwdKEXjxV0q522y9I+vhkuuwOJeZpMNqlLL8frTS1ds3SA+-----END PUBLIC KEY-----
+ tests/files/ed448-self-signed-cert.pem view
@@ -0,0 +1,11 @@+-----BEGIN CERTIFICATE-----+MIIBojCCASKgAwIBAgIUP+cIZ3jsepQ7zNuhZtdcyzaGHa8wBQYDK2VxMCExHzAd+BgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMTgwOTAyMTU1MDUzWhcN+MTgxMDAyMTU1MDUzWjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29t+MEMwBQYDK2VxAzoAWopNPKAV3+c61CJqDrnMnRntbN8QSNHZwdKEXjxV0q522y9I+vhkuuwOJeZpMNqlLL8frTS1ds3SAo1MwUTAdBgNVHQ4EFgQUvzGR+t5kH8Ta2XUs+KP+uQfMQFUcwHwYDVR0jBBgwFoAUvzGR+t5kH8Ta2XUsKP+uQfMQFUcwDwYDVR0T+AQH/BAUwAwEB/zAFBgMrZXEDcwCrI8049W/mjhgaJm79n4Zwb1ryBg8aiFqmvszd+pFhGmhU1TWD+qPmBWOIdu0laToDvBiITaLAa94Ck0BZdNFsbqq1FRFXOzsLwV6kW+lLwG53nY7ITnC1MVytl8qjA0tsrnHqPwm5pp8rIzJudNk4S+AwA=+-----END CERTIFICATE-----
+ tests/files/ed448-unencrypted-pkcs8.pem view
@@ -0,0 +1,4 @@+-----BEGIN PRIVATE KEY-----+MEcCAQAwBQYDK2VxBDsEOceJ7Mc21YZq6/qusCHA5d3wARXuJxMKSRkvsyD4GEKq+hlubDZL0FQiS3OgjbeuKN+63xa+OVVz9XA==+-----END PRIVATE KEY-----
+ tests/files/ed448-unencrypted-trad.pem view
@@ -0,0 +1,4 @@+-----BEGIN ED448 PRIVATE KEY-----+MEcCAQAwBQYDK2VxBDsEOceJ7Mc21YZq6/qusCHA5d3wARXuJxMKSRkvsyD4GEKq+hlubDZL0FQiS3OgjbeuKN+63xa+OVVz9XA==+-----END ED448 PRIVATE KEY-----
+ tests/files/x25519-encrypted-pbes1.pem view
@@ -0,0 +1,32 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFcwGwYJKoZIhvcNAQUDMA4ECOlAaqWwdDweAgIIAAQ42XsuUzVmkZkOq+m4uBNW+6qzsaTJ7FPa4itB+tyv4BzPgFxAQTwzzJgSr9WkbYy6LKZIPporO/Ko=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFcwGwYJKoZIhvcNAQUKMA4ECH0TpXboY9MNAgIIAAQ4dojWsXYBrtraqLi1pBeF+uOyGGClKoj7XpGvXkNShtEnWTKjlNUebf02xQlcmxuksdXNffTMdsww=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFAwHAYKKoZIhvcNAQwBATAOBAjV52wnNhxkeAICCAAEMN5Tklnapb+agqTYnDle+pHKk61c0d0la81SFGz5pqFBPllX5zmnSWzzHFW7/B8cxLw==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFAwHAYKKoZIhvcNAQwBAjAOBAh7Iwy1tOrlZAICCAAEMK0FLzIhsz8+1ox7grnf++u+eD8k9LJ0X77X9GeNKyvIJl5lkkWaCblFwd2reMH3eug==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFgwHAYKKoZIhvcNAQwBAzAOBAgWrZKj/deGGQICCAAEOJOPMtVYLKsvzajTT5wG+hjhbX7RHWL9t9h1H1m/CAW/qJqALZNPQcQ8BiheRJv8LrgYr+Fzl4e6O+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFgwHAYKKoZIhvcNAQwBBDAOBAjPUjiYWb4CwwICCAAEOASPWNTFFZaViFQ+WQ4C+Upl6k5aTHq6KHpai/N6ABiwV8dcXAL5CXMzAxJWKW+ZdUgyydM7xAVn6+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFgwHAYKKoZIhvcNAQwBBTAOBAgbZrk3W2y+WgICCAAEONIxa0JMYEvV9curAwn5+5epAnnwv7vtqO/IT4mqkc0WLHPEZVhxL2EQWBGemGQWPxeRV5jK8RJvS+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MFgwHAYKKoZIhvcNAQwBBjAOBAgfr3469Ve4gAICCAAEOMHa3S4OYcEBCb+HHvJW+zpNlWtmW4/djMndGWpznIuguxK/7V+A3R7PgvA8K0FhpSPDR3bs/2QHW+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/x25519-encrypted-pbkdf2.pem view
@@ -0,0 +1,39 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGHMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAj6ZyFipSgetQICCAAw+DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIjgzX3kQ5dXgEOKlxrj9EtG1DNXBDgzDW+vnFReEN4zACwoxMQ41sAASIW1vCT1TuTIDXLxGM5CwfkyawnexrKlKmt+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGKME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAj4aVxRNkgd/QICCAAw+DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIu/UnAKsbyacEOE3fiHQdWKYI5Inf+Wndw5d7UX0nRmCjwNQLK1UL+QXujoVmi+3hEZSRfYcqj5M4t7sJlzZ63w8Td+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGLME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAgJmEUBljH9VAICCAAw+DAYIKoZIhvcNAgkFADAVBgkqhkiG9n0HQgoECB9jZKjkRqv6BDg7458AElgxBUGx+9iPo02FNkzdUO23NB66AV0puFZAYz4NUY/wgJLA3HMwBERdSP74hBjh+GUT3sw==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGdMFkGCSqGSIb3DQEFDTBMMCkGCSqGSIb3DQEFDDAcBAjyCgD/RmiEIgICCAAw+DAYIKoZIhvcNAgkFADAfBgsqgwiMmks9AQEBAgQQFNpqaqXLMkVwoDSdtl+SEwRA+XXRprNwSjtJ/m6V3vFKq+VCHOrj7o2MSpt/pvjVnbHKaHgX9V3TP8e1KOiiXJoo1+PBv4M1beoCokVjhCECZWyg==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGSMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAiTO27y8UQ3LwICCAAC+ARAwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgE6BAibXxpEYv15IAQ4zEoc+lFpScRoeJ/6aaSI6f4NFG4N4cVFTXvi5YIDlbpwmz4xdIr2uU+jhiGknXDcE2Z+m+ERCITjQ=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGTMFcGCSqGSIb3DQEFDTBKMCwGCSqGSIb3DQEFDDAfBAgxud22g8Bm+AICCAAC+AQUwDAYIKoZIhvcNAgkFADAaBggqhkiG9w0DAjAOAgIAoAQISGwHgT4bmFkEOC8m+drQbC9WA1ZvACMJWLXEL4JUgejLRDtfcvCC5kAhT2iy23RllpUftspZ4V/SzAePa+EFsn0jy8+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGSMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAiQqqDVfFc5OwICCAAC+AQgwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgF4BAgmnqFFlLCrwAQ4elCp+HTXrBbgtWoMW/hG5AUn2Vc9W3ycJ9tw9d4pxx3ZHV+FGiEMJsxqPwGVEDJvTgxy8+G55j3UE=+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/x25519-encrypted-scrypt.pem view
@@ -0,0 +1,18 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjTIFxP0Hwq3wICQAAC+AQgCAQEwHQYJYIZIAWUDBAECBBDJmyEj4MkNJQMLM4t25IIuBECqSAJnq6gSp0Nc+OwqkgspkwyYGxHfZAJ/aw7BHSGTs+LQexB738lqY/B5Ri2udxqfE/kjKzyn2KTHJ+nqHQk8+J+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAi0kO+7kfp0NwICQAAC+AQgCAQEwHQYJYIZIAWUDBAEWBBBnSlCJdiXvxblUH3GgOP3iBECaLsAsyLbHLKcA+JCxrNQ3JrGL0TVQHPu/6tYFAWJoM4ZCIww1i5nXZUIigNDYERUJbm9od6E2a8apA+TH5JE4yf+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjC70jTpl9lAQICQAAC+AQgCAQEwHQYJYIZIAWUDBAEqBBCKvOsxUwHu7QYwy/j409InBECFy0tXOpJYZD1V+S+x0XsC/nHHesRzKKbs86f//VcOQBOwbA044IcUNPiaSkuedtlAX84I1rtvtJGY6+b7fVHtJn+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/x25519-public.pem view
@@ -0,0 +1,3 @@+-----BEGIN PUBLIC KEY-----+MCowBQYDK2VuAyEAdNNs0T64alqPXbwrJt+rCdkerW6PgrQOUKHUlbPyahA=+-----END PUBLIC KEY-----
+ tests/files/x25519-self-signed-cert.pem view
@@ -0,0 +1,9 @@+-----BEGIN CERTIFICATE-----+MIIBOjCBpAIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0+QGV4YW1wbGUuY29tMB4XDTE4MDkwMjE1NTA1M1oXDTE4MTAwMjE1NTA1M1owITEf+MB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTAqMAUGAytlbgMhAHTTbNE++uGpaj128KybfqwnZHq1uj4K0DlCh1JWz8moQMA0GCSqGSIb3DQEBCwUAA4GBAE5F+wEzoWRJKgS6OYunevbkvDMOmAjhXg+P2zcuXSndAi6HLlHTsx0tdiTclrNqNkFcw+XaSBmGhpWy/qI4eM/lrWZV3+dfeNRBb6oqezjgbepC+J1akDNzseW1+Kr16ryQz9+STwnOqpQhrTQdBOaLlgofw3qqj88bWBsNciJWCX4+-----END CERTIFICATE-----
+ tests/files/x25519-unencrypted-pkcs8.pem view
@@ -0,0 +1,3 @@+-----BEGIN PRIVATE KEY-----+MC4CAQAwBQYDK2VuBCIEIIhgx84XP3vLVdFwZWT88BG/gGRXl6YYUeo0lWX2E8RY+-----END PRIVATE KEY-----
+ tests/files/x25519-unencrypted-trad.pem view
@@ -0,0 +1,3 @@+-----BEGIN X25519 PRIVATE KEY-----+MC4CAQAwBQYDK2VuBCIEIIhgx84XP3vLVdFwZWT88BG/gGRXl6YYUeo0lWX2E8RY+-----END X25519 PRIVATE KEY-----
+ tests/files/x448-encrypted-pbes1.pem view
@@ -0,0 +1,40 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MG8wGwYJKoZIhvcNAQUDMA4ECB4xXEg3WSinAgIIAARQM2CTxuxMnzkPDjXsonc3+lI7dqFNYNxBfgGoEur60JvnZyl7atO+lsCY08XYqLOAVwf/5aZC9Crkd0iHdtYZs+k8xCAG/KXBwdrrMiKgoRdXE=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MG8wGwYJKoZIhvcNAQUKMA4ECJFkCK4Fg7SgAgIIAARQ8CiLyhuksya2/pWvoemD+etLFhTSKFln9XfLXSyG70yCsPcH8kC5Xv3ppmNcgbYDC8iwxWwjH4b0QM/QuzaGK+F3itJG5sgWypa9Yt2rSwYUk=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MGgwHAYKKoZIhvcNAQwBATAOBAi75GJorMne3AICCAAESLScu+UVIvP1/FqkKBa1+lYTe0Z1nQWFs5bFdFsAAvw5RCSee2vcgFA5KhIqlPwdPgxmgAzIYgoSV0RqudMfq+OpABx2fMhpMhfA==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MGgwHAYKKoZIhvcNAQwBAjAOBAjxiQQdgW2rXwICCAAESHEAGY2q5vNhLbN7Swb1+UmDvXibgMjhTpunsmPouCahSf6Zvv8ahes53gvPg/j14hplD2d/MRDz5VRxYO7dP+v2mzZzuqhegssw==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MHAwHAYKKoZIhvcNAQwBAzAOBAhQmGA2NmFbsQICCAAEUAxfvgmmGrViSF3kj82o+EbNw0fbLqoaa/SdNfV6KRJhZ8j/+6URQ0xKMw3sSCp/fwSon8h9I633VkGyxEtyF+KRYOakvY5kBOt+JYiZcbovFT+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MHAwHAYKKoZIhvcNAQwBBDAOBAhziSywPrS4zQICCAAEUJaEyqSGPu9FZ9CLURVW+6ZRWj6TIaehX0cGGFCkTk3JqhpFFRNLte0SLGPkankzXKOV3D5mBUPF+UjvXSwaJ+FcyeszvN5xR6sNQkETZqpTD3+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MHAwHAYKKoZIhvcNAQwBBTAOBAj1Ch4rgGz1ywICCAAEUDJ1FbgIb/MHcuoLexYq+dqMB7sGctwBmrFZRTpbbHGNvEUV6MFlBkximrCb9nnHjCKj2sZcfQp0R5Q1YODn8+gzEAEjVuh2yFCKlwumPknt+O+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MHAwHAYKKoZIhvcNAQwBBjAOBAhCm0/fGye6+gICCAAEUHqw28mTyCaI6k+ILt/o+3qleloo0f4bL9cJiOBYnUB/S1AElr/85ULA70bECsjsWjoWsRE8IxJ5a3FtWJaat+gaNyLYQlMtVBMoK6feFFT8aE+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/x448-encrypted-pbkdf2.pem view
@@ -0,0 +1,42 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGfMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAgJDIKD8sQsoQICCAAw+DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIuYP9T2Y/nD4EUBXfNNfUENf19FHDdt9Z+b3s0OVeXeMbu+80TA2mtbk+wOucvB69hr8V/ZFW2HDiYJ2qMBMioOw197lTtVMIT+xS6gGSpl+tyPoWIoycGcnsq6+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGiME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAgu+SJlXxKQDAICCAAw+DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIeyJz7DTK2+QEUIlmFpQp5DB+gwVp+6G6dhe10uhETbfSTN8+lmYiqb+0p91DnV+3kpaTrT6fuU3hsjTH1xx4a2oElc+T1+m8xZhvmmyued7UPBrQdy2RcNUToQ+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGjME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAiHVuSXubRGxwICCAAw+DAYIKoZIhvcNAgkFADAVBgkqhkiG9n0HQgoECJoupjD8iD9gBFAIaJQUeSI3WfZn+vJ5oM88JZrDdxVKlZlOL4ffXYu3PIUSlV0r3V08m/sPMY/7hCmtvyxBkw6ncB9su++0tC+fIu1+VVotbarL2eaZaxC217ew==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGtMFkGCSqGSIb3DQEFDTBMMCkGCSqGSIb3DQEFDDAcBAjkfqkzdtzUKQICCAAw+DAYIKoZIhvcNAgkFADAfBgsqgwiMmks9AQEBAgQQG/iQQd82wsC5mQN+bFY58ARQ+1QsQhP+TfWeZP9oztCd7s0Y1PnTM7WsMW2YtyS6Sn1Duyj9gx17cXLWvzQz9mS1Y+EoORulT1bTOzj585xSrNQLSqlnqTHcczNPKHCpU7EWE=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGqMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAg5ABTk7x6EhgICCAAC+ARAwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgE6BAiwam2gNHnwhgRQdpEG++fxlriH5rAAFneldLBFHSjBMSKJZNftQv0HFOQzwPhoiYKGXrcldx4bq4B1cbSJR+XFNvROPF/F3g0B/xn3YiU/5weo5B6Rsu1ojqTBA=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGrMFcGCSqGSIb3DQEFDTBKMCwGCSqGSIb3DQEFDDAfBAiWX47sRP/MnQICCAAC+AQUwDAYIKoZIhvcNAgkFADAaBggqhkiG9w0DAjAOAgIAoAQIsvG8iVYWSzMEUBmO+u0pBmQkmBM3VI8A9cCRob5hAUojyd4NpodMJvpY4Da7mz20LgLyL5q2DsKgTfnzn+VTf+tP8vTv8i76Pr7aU89P784jI8nbtZgkcYizq++-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGqMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAhkgv+PZmmr5gICCAAC+AQgwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgF4BAjKasCGhxd/5ARQa6xv+vi/LtSLtwvrYHgWVGELLgkwqBJ6d2MM+ysEUeQILq5gbGcdnfGHtrakVtFir2ga6+lyrpv9Y0m5gLqgU0QPbm9XRqu1hdEKanozFs2H4=+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/x448-encrypted-scrypt.pem view
@@ -0,0 +1,18 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAiW/EHVo6GeIQICQAAC+AQgCAQEwHQYJYIZIAWUDBAECBBAmafVnJcoBMlXmITIT+/hxBFCP8hh1dr4EGyMt+yqivyYBBAlCfjbAquHwPcys7kELHgwHG0PAgspAKkOLCqSgZVuomuS1Ya6bNzgeI+S2iW5nYwXL8QjIYYEXAxLx8ZQMVb0A==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAhixLMqBNK2tAICQAAC+AQgCAQEwHQYJYIZIAWUDBAEWBBAA5uc3FQno7xKz2V8kxSCeBFACaBwzab3adlDg+bAIOfR1ytgNEI8+jLDxcPtEI6TjtmcLsT6NI3MaQfOnLy/gt/Uo5XbbiMJUGZ4Mm+ggEjLTPc7M2kLgGdKcLpnc8mWBCgyg==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAhULg3jQtrIRwICQAAC+AQgCAQEwHQYJYIZIAWUDBAEqBBBk6w5yfaTk8et4FwnFTrfRBFBl+mFvAdi6X6Wm+44LkvMjiVCZ1xGDwLcZOyc5OnyNOHnW5tLBa7qvghaQljOyHS+na0z9uKTalbvRP+GaYKE3JRIsi9MzXOSXlZHLl6fDiVRw==+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/x448-public.pem view
@@ -0,0 +1,4 @@+-----BEGIN PUBLIC KEY-----+MEIwBQYDK2VvAzkA3cHetiJ/T/5Oo62IF1sDHfTBN9TUjxcTMhX3lHxw8W5ryxQ8+LWc2VD0RcfBn1BI3+QVEefPUd/c=+-----END PUBLIC KEY-----
+ tests/files/x448-self-signed-cert.pem view
@@ -0,0 +1,10 @@+-----BEGIN CERTIFICATE-----+MIIBUjCBvAIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0+QGV4YW1wbGUuY29tMB4XDTE4MDkwMjE1NTA1M1oXDTE4MTAwMjE1NTA1M1owITEf+MB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTBCMAUGAytlbwM5AN3B3rYi+f0/+TqOtiBdbAx30wTfU1I8XEzIV95R8cPFua8sUPC1nNlQ9EXHwZ9QSN/kFRHnz+1Hf3MA0GCSqGSIb3DQEBCwUAA4GBACwDI7X1kRJwPjDXTB1lkmf0U806ng0NuFuY+mXgt+PYiUhR3g76FC0chw2+WeeQ6WE4VkQsfdZz10bSWGtcaH4fCYVkQXF/BmNz8+7Mv0L9iK2438SBcK3WAYB+bC9CZw/gKObzDHxml4tlDaMmIsfYVqiHXuEAPI3wjw+qoHmxWqY+-----END CERTIFICATE-----
+ tests/files/x448-unencrypted-pkcs8.pem view
@@ -0,0 +1,4 @@+-----BEGIN PRIVATE KEY-----+MEYCAQAwBQYDK2VvBDoEOOC8GlnbqcOSJ+ISHV8HJTD3WGdC1sQdZ+0Gkx7sUvL0+Bm0KxY3cO9Rg9MQb3qkfVngRes0sb86Q+-----END PRIVATE KEY-----
+ tests/files/x448-unencrypted-trad.pem view
@@ -0,0 +1,4 @@+-----BEGIN X448 PRIVATE KEY-----+MEYCAQAwBQYDK2VvBDoEOOC8GlnbqcOSJ+ISHV8HJTD3WGdC1sQdZ+0Gkx7sUvL0+Bm0KxY3cO9Rg9MQb3qkfVngRes0sb86Q+-----END X448 PRIVATE KEY-----