@@ -87,13 +87,17 @@ module Simplex.Messaging.Crypto
8787 signatureKeyPair ,
8888 publicToX509 ,
8989 encodeASNObj ,
90+ readECPrivateKey ,
9091
9192 -- * key encoding/decoding
9293 encodePubKey ,
9394 decodePubKey ,
9495 encodePrivKey ,
9596 decodePrivKey ,
9697 pubKeyBytes ,
98+ uncompressEncodePoint ,
99+ uncompressDecodePoint ,
100+ uncompressDecodePrivateNumber ,
97101
98102 -- * sign/verify
99103 Signature (.. ),
@@ -252,6 +256,12 @@ import Simplex.Messaging.Encoding
252256import Simplex.Messaging.Encoding.String
253257import Simplex.Messaging.Parsers (parseAll , parseString )
254258import Simplex.Messaging.Util ((<$?>) )
259+ import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
260+ import qualified Crypto.Store.PKCS8 as PK
261+ import qualified Crypto.PubKey.ECC.Types as ECC
262+ import qualified Data.ByteString.Lazy as BL
263+ import qualified Data.Binary as Bin
264+ import qualified Data.Bits as Bits
255265
256266-- | Cryptographic algorithms.
257267data Algorithm = Ed25519 | Ed448 | X25519 | X448
@@ -1542,3 +1552,56 @@ keyError :: (a, [ASN1]) -> Either String b
15421552keyError = \ case
15431553 (_, [] ) -> Left " unknown key algorithm"
15441554 _ -> Left " more than one key"
1555+
1556+ readECPrivateKey :: FilePath -> IO ECDSA. PrivateKey
1557+ readECPrivateKey f = do
1558+ -- this pattern match is specific to APNS key type, it may need to be extended for other push providers
1559+ [PK. Unprotected (X. PrivKeyEC X. PrivKeyEC_Named {privkeyEC_name, privkeyEC_priv})] <- PK. readKeyFile f
1560+ pure ECDSA. PrivateKey {private_curve = ECC. getCurveByName privkeyEC_name, private_d = privkeyEC_priv}
1561+
1562+ -- | Elliptic-Curve-Point-to-Octet-String Conversion without compression
1563+ -- | as required by RFC8291
1564+ -- | https://www.secg.org/sec1-v2.pdf#subsubsection.2.3.3
1565+ uncompressEncodePoint :: ECC. Point -> BL. ByteString
1566+ uncompressEncodePoint (ECC. Point x y) = " \x04 " <> encodeBigInt x <> encodeBigInt y
1567+ uncompressEncodePoint ECC. PointO = " \0"
1568+
1569+ uncompressDecodePoint :: BL. ByteString -> Either CE. CryptoError ECC. Point
1570+ uncompressDecodePoint " \0" = pure ECC. PointO
1571+ uncompressDecodePoint s
1572+ | BL. take 1 s /= prefix = Left CE. CryptoError_PointFormatUnsupported
1573+ | BL. length s /= 65 = Left CE. CryptoError_KeySizeInvalid
1574+ | otherwise = do
1575+ let s' = BL. drop 1 s
1576+ x <- decodeBigInt $ BL. take 32 s'
1577+ y <- decodeBigInt $ BL. drop 32 s'
1578+ pure $ ECC. Point x y
1579+ where
1580+ prefix = " \x04 " :: BL. ByteString
1581+
1582+ -- Used to test encryption against the RFC8291 Example - which gives the AS private key
1583+ uncompressDecodePrivateNumber :: BL. ByteString -> Either CE. CryptoError ECC. PrivateNumber
1584+ uncompressDecodePrivateNumber s
1585+ | BL. length s /= 32 = Left CE. CryptoError_KeySizeInvalid
1586+ | otherwise = do
1587+ decodeBigInt s
1588+
1589+ encodeBigInt :: Integer -> BL. ByteString
1590+ encodeBigInt i = do
1591+ let s1 = Bits. shiftR i 64
1592+ s2 = Bits. shiftR s1 64
1593+ s3 = Bits. shiftR s2 64
1594+ Bin. encode (w64 s3, w64 s2, w64 s1, w64 i)
1595+ where
1596+ w64 :: Integer -> Bin. Word64
1597+ w64 = fromIntegral
1598+
1599+ decodeBigInt :: BL. ByteString -> Either CE. CryptoError Integer
1600+ decodeBigInt s
1601+ | BL. length s /= 32 = Left CE. CryptoError_PointSizeInvalid
1602+ | otherwise = do
1603+ let (w3, w2, w1, w0) = Bin. decode s :: (Bin. Word64 , Bin. Word64 , Bin. Word64 , Bin. Word64 )
1604+ pure $ shift 3 w3 + shift 2 w2 + shift 1 w1 + shift 0 w0
1605+ where
1606+ shift i w = Bits. shiftL (fromIntegral w) (64 * i)
1607+
0 commit comments