Skip to content

Commit 466b53f

Browse files
convert to crypton
*crypton* is today the dominant cryptographic primitives "kitchen sink" library. There is a need and want of additional cryptographic primitives in *hackage-server*, including HMAC (various use cases) and, later, additional public key signature algorithms to support OpenID Connect (for trusted publishing) and Passkeys/webauthn login. This change migrates *hackage-server* to *crypton*. The changes are kept to a minimum without any refactoring. Dependencies on *cryptohash-md5* and *cryptohash-sha256* have been dropped. It would have been nice to drop *ed25519*, but without it we would be depending on internal implementation details to define the `MemSize` instance for the *hackage-security* `Key` type.
1 parent 6f027ff commit 466b53f

6 files changed

Lines changed: 35 additions & 26 deletions

File tree

hackage-server.cabal

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ library
431431
, HStringTemplate ^>= 0.8
432432
, HTTP >= 4000.3.16 && < 4000.6
433433
, http-client ^>= 0.7 && < 0.8
434-
, http-client-tls ^>= 0.3
434+
, http-client-tls ^>= 0.4
435435
, http-types >= 0.10 && < 0.13
436436
, QuickCheck >= 2.14 && < 2.19
437437
, acid-state ^>= 0.16
@@ -451,10 +451,8 @@ library
451451
-- commonmark-0.2 needed by commonmark-extensions-0.2.2
452452
, commonmark-extensions ^>= 0.2.2
453453
-- Note: 0.2.2 added footnoteSpec to gfmExtensions
454-
, cryptohash-md5 ^>= 0.11.100
455-
, cryptohash-sha256 ^>= 0.11.100
454+
, crypton >= 1.1.4 && < 1.2
456455
, csv ^>= 0.1
457-
, ed25519 ^>= 0.0.5
458456
, hackage-security >= 0.6 && < 0.7
459457
-- N.B: hackage-security-0.6.2 uses Cabal-syntax-3.8.1.0
460458
-- see https://github.com/haskell/hackage-server/issues/1130
@@ -469,6 +467,7 @@ library
469467
, hslogger ^>= 1.3.1
470468
, lifted-base ^>= 0.2.1
471469
, mime-mail ^>= 0.5
470+
, ram >= 0.19
472471
, random >= 1.2 && < 1.4
473472
, rss ^>= 3000.2.0.7
474473
, safecopy ^>= 0.10

src/Distribution/Server/Features/Security/MD5.hs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import qualified Data.ByteString.Lazy as BS.Lazy
2828
import Data.SafeCopy
2929
import qualified Data.Serialize as Ser
3030

31-
-- cryptohash
32-
import qualified Crypto.Hash.MD5 as MD5
31+
import qualified Data.ByteArray as BA
32+
import qualified Crypto.Hash as Hash
3333

3434
-- hackage
3535
import Distribution.Server.Framework.MemSize
@@ -49,6 +49,9 @@ md5digestFromBS bs = case Ser.runGet (Ser.get :: Ser.Get MD5Digest) bs of
4949
Left e -> error ("md5digestFromBS: " ++ e)
5050
Right d -> d
5151

52+
fromCryptonDigest :: Hash.Digest Hash.MD5 -> MD5Digest
53+
fromCryptonDigest = md5digestFromBS . BA.convert
54+
5255
-- | The 'Show' instance for 'MD5Digest' prints the underlying digest
5356
-- (without showing the newtype wrapper)
5457
--
@@ -65,7 +68,7 @@ instance ReadDigest MD5Digest where
6568

6669
-- | Compute MD5 digest
6770
md5 :: BS.Lazy.ByteString -> MD5Digest
68-
md5 = md5digestFromBS . MD5.hashlazy
71+
md5 = fromCryptonDigest . Hash.hashlazy
6972

7073
instance MemSize MD5Digest where
7174
memSize _ = 3
@@ -109,13 +112,13 @@ instance Ser.Serialize MD5Digest where
109112
-- will run in constant memory.
110113
--
111114
lazyMD5 :: BS.Lazy.ByteString -> ByteStringWithMd5
112-
lazyMD5 = go MD5.init . BS.Lazy.toChunks
115+
lazyMD5 = go Hash.hashInit . BS.Lazy.toChunks
113116
where
114-
go :: MD5.Ctx -> [BS.ByteString] -> ByteStringWithMd5
117+
go :: Hash.Context Hash.MD5 -> [BS.ByteString] -> ByteStringWithMd5
115118
go !md5ctx [] =
116-
BsEndMd5 (md5digestFromBS (MD5.finalize md5ctx))
119+
BsEndMd5 (fromCryptonDigest (Hash.hashFinalize md5ctx))
117120
go !md5ctx (block : blocks') =
118-
BsChunk block (go (MD5.update md5ctx block) blocks')
121+
BsChunk block (go (Hash.hashUpdate md5ctx block) blocks')
119122

120123
-- | See 'lazyMD5'
121124
data ByteStringWithMd5 = BsChunk !BS.ByteString ByteStringWithMd5

src/Distribution/Server/Features/Security/Orphans.hs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import Control.DeepSeq
1313
import Data.SafeCopy
1414
import Data.Serialize
1515
import qualified Data.ByteString.Lazy as BS.L
16-
import qualified Crypto.Sign.Ed25519 as Ed25519
1716

1817
-- hackage
1918
import Distribution.Server.Framework.MemSize
@@ -71,13 +70,9 @@ instance MemSize (Some Sec.Key) where
7170
memSize (Some key) = memSize key
7271

7372
instance MemSize (Sec.Key typ) where
74-
memSize (Sec.KeyEd25519 pub pri) = memSize pub + memSize pri
75-
76-
instance MemSize Ed25519.PublicKey where
77-
memSize = memSize . Ed25519.unPublicKey
78-
79-
instance MemSize Ed25519.SecretKey where
80-
memSize = memSize . Ed25519.unSecretKey
73+
memSize (Sec.KeyEd25519 _pub _pri) =
74+
5 {- ByteString overhead -} + 4 {- 32-byte public key -}
75+
+ 5 {- ByteString overhead -} + 8 {- 64-byte public key -}
8176

8277
instance MemSize Sec.FileVersion where
8378
memSize (Sec.FileVersion v) = memSize v

src/Distribution/Server/Features/Security/SHA256.hs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import qualified Data.ByteString.Char8 as BS.Char8
2727
import qualified Data.ByteString.Lazy as BS.Lazy
2828
import qualified Data.Text.Encoding as T
2929

30-
-- cryptohash
31-
import qualified Crypto.Hash.SHA256 as SHA256
30+
import qualified Data.ByteArray as BA
31+
import qualified Crypto.Hash as Hash
3232

3333
-- hackage
3434
import Distribution.Server.Framework.MemSize
@@ -49,6 +49,9 @@ sha256digestFromBS bs = case runGet getSHA256NoPfx bs of
4949
Left e -> error ("sha256digestFromBS: " ++ e)
5050
Right d -> d
5151

52+
fromCryptonDigest :: Hash.Digest Hash.SHA256 -> SHA256Digest
53+
fromCryptonDigest = sha256digestFromBS . BA.convert
54+
5255
-- | 'Data.Serialize.Get' helper to read a raw 32byte SHA256Digest w/o
5356
-- any length-prefix
5457
getSHA256NoPfx :: Get SHA256Digest
@@ -80,7 +83,7 @@ instance ReadDigest SHA256Digest where
8083

8184
-- | Compute SHA256 digest
8285
sha256 :: BS.Lazy.ByteString -> SHA256Digest
83-
sha256 = sha256digestFromBS . SHA256.hashlazy
86+
sha256 = fromCryptonDigest . Hash.hashlazy
8487

8588
instance MemSize SHA256Digest where
8689
memSize _ = 5

src/Distribution/Server/Features/UserSignup.hs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,16 @@ import Network.Mail.Mime
4343
import Network.URI (URI(..), URIAuth(..))
4444
import Graphics.Captcha
4545
import qualified Data.ByteString.Base64 as Base64
46-
import qualified Crypto.Hash.SHA256 as SHA256
4746
import Data.String
4847
import Data.Char
4948
import Text.Read (readMaybe)
5049
import Data.Aeson
5150
import qualified Data.Aeson.KeyMap as KeyMap
5251
import qualified Data.Aeson.Key as Key
5352

53+
import qualified Data.ByteArray as BA
54+
import qualified Crypto.Hash as Hash
55+
5456

5557
-- | A feature to allow open account signup, and password reset,
5658
-- both with email confirmation.
@@ -270,7 +272,11 @@ userSignupFeature ServerEnv{serverBaseURI, serverCron}
270272
++ "has been used already, or that it has expired."]
271273

272274
hashTimeAndCaptcha :: UTCTime -> String -> BS.ByteString
273-
hashTimeAndCaptcha timestamp captcha = Base64.encode (SHA256.hash (fromString (show timestamp ++ map toUpper captcha)))
275+
hashTimeAndCaptcha timestamp captcha =
276+
go (show timestamp ++ map toUpper captcha)
277+
where
278+
go = Base64.encode . BA.convert
279+
. Hash.hashWith Hash.SHA256 . BS.pack
274280

275281
makeCaptchaHash :: IO (UTCTime, BS.ByteString, BS.ByteString)
276282
makeCaptchaHash = do

src/Distribution/Server/Users/AuthToken.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import qualified Data.Text as T
1818
import qualified Data.Text.Encoding as T
1919
import qualified Data.ByteString.Short as BSS
2020
import qualified Data.ByteString.Base16 as BS16
21-
import qualified Crypto.Hash.SHA256 as SHA256
2221
import Distribution.Pretty (Pretty(..))
2322
import Distribution.Parsec (Parsec(..))
2423
import qualified Distribution.Compat.CharParsing as P
2524

25+
import qualified Data.ByteArray as BA
26+
import qualified Crypto.Hash as Hash
27+
2628
import Data.SafeCopy
2729

2830
-- | Contains the original token which will be shown to the user
@@ -38,7 +40,8 @@ newtype AuthToken = AuthToken BSS.ShortByteString
3840

3941
convertToken :: OriginalToken -> AuthToken
4042
convertToken (OriginalToken bs) =
41-
AuthToken $ BSS.toShort $ SHA256.hash $ getRawNonceBytes bs
43+
AuthToken . BSS.toShort . BA.convert . Hash.hashWith Hash.SHA256
44+
$ getRawNonceBytes bs
4245

4346
viewOriginalToken :: OriginalToken -> T.Text
4447
viewOriginalToken (OriginalToken ot) = T.pack $ renderNonce ot

0 commit comments

Comments
 (0)