From c6466a719d784054a82578c619e7dfff613e777b Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Tue, 31 Jul 2018 17:48:05 -0400 Subject: Begin implementing HKDF Implement the `extract` phase of HKDF. --- btls.cabal | 9 +++-- src/Codec/Crypto/HKDF.hs | 51 ++++++++++++++++++++++++++++ src/Data/HMAC.hs | 6 +--- src/Internal/HKDF.chs | 45 +++++++++++++++++++++++++ src/Types.hs | 29 ++++++++++++++++ tests/Codec/Crypto/HKDFTests.hs | 74 +++++++++++++++++++++++++++++++++++++++++ tests/Tests.hs | 4 ++- 7 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 src/Codec/Crypto/HKDF.hs create mode 100644 src/Internal/HKDF.chs create mode 100644 src/Types.hs create mode 100644 tests/Codec/Crypto/HKDFTests.hs diff --git a/btls.cabal b/btls.cabal index 9b87874..02f2ed3 100644 --- a/btls.cabal +++ b/btls.cabal @@ -67,7 +67,8 @@ library -Wunused-type-variables -Wwrong-do-bind -optl-Wl,-z,relro -optl-Wl,-z,now -optl-Wl,-s - exposed-modules: Data.Digest + exposed-modules: Codec.Crypto.HKDF + , Data.Digest , Data.HMAC other-modules: Data.Digest.Internal , Foreign.Ptr.Cast @@ -75,8 +76,10 @@ library , Foreign.Ptr.CreateWithFinalizer , Internal.Base , Internal.Digest + , Internal.HKDF , Internal.HMAC , Result + , Types c-sources: cbits/btls.c -- Use special names for the BoringSSL libraries to avoid accidentally pulling -- in OpenSSL. @@ -117,13 +120,15 @@ test-suite tests -Wwrong-do-bind -optl-Wl,-z,relro -optl-Wl,-z,now -optl-Wl,-s main-is: Tests.hs - other-modules: Data.DigestTests + other-modules: Codec.Crypto.HKDFTests + , Data.DigestTests , Data.Digest.HashTests , Data.Digest.MD5Tests , Data.Digest.SHA1Tests , Data.Digest.SHA2Tests , Data.HMACTests build-depends: base >=4.9 && <4.10 + , base16-bytestring >=0.1.1.6 && <0.2 , btls , bytestring >=0.10 && <0.11 , process >=1.4.2 && <1.5 diff --git a/src/Codec/Crypto/HKDF.hs b/src/Codec/Crypto/HKDF.hs new file mode 100644 index 0000000..bb29ca6 --- /dev/null +++ b/src/Codec/Crypto/HKDF.hs @@ -0,0 +1,51 @@ +-- Copyright 2018 Google LLC +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); you may not +-- use this file except in compliance with the License. You may obtain a copy of +-- the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +-- License for the specific language governing permissions and limitations under +-- the License. + +module Codec.Crypto.HKDF + ( Salt(Salt), SecretKey(SecretKey) + , extract + ) where + +import qualified Data.ByteString as ByteString +import qualified Data.ByteString.Unsafe as ByteString +import Foreign (Ptr, Storable(peek), alloca, allocaArray) +import Foreign.C.Types +import Foreign.Marshal.Unsafe (unsafeLocalState) +import Unsafe.Coerce (unsafeCoerce) + +import Data.Digest.Internal (Algorithm(Algorithm)) +import Internal.Digest (evpMaxMDSize) +import Internal.HKDF +import Types (Salt(Salt), SecretKey(SecretKey)) + +-- | Computes an HKDF pseudorandom key (PRK) as specified by RFC 5869. +extract :: Algorithm -> Salt -> SecretKey -> SecretKey +extract (Algorithm md) (Salt salt) (SecretKey secret) = + unsafeLocalState $ + allocaArray evpMaxMDSize $ \pOutKey -> + alloca $ \pOutLen -> do + -- @HKDF_extract@ won't mutate @secret@ or @salt@, so the sharing inherent + -- in 'ByteString.unsafeUseAsCStringLen' is fine. + ByteString.unsafeUseAsCStringLen secret $ \(pSecret, secretLen) -> + ByteString.unsafeUseAsCStringLen salt $ \(pSalt, saltLen) -> + hkdfExtract + (asCUCharBuf pOutKey) pOutLen + md + (asCUCharBuf pSecret) (fromIntegral secretLen) + (asCUCharBuf pSalt) (fromIntegral saltLen) + outLen <- fromIntegral <$> peek pOutLen + SecretKey <$> ByteString.packCStringLen (pOutKey, outLen) + where + asCUCharBuf :: Ptr CChar -> Ptr CUChar + asCUCharBuf = unsafeCoerce diff --git a/src/Data/HMAC.hs b/src/Data/HMAC.hs index 4c424be..85e6886 100644 --- a/src/Data/HMAC.hs +++ b/src/Data/HMAC.hs @@ -31,14 +31,10 @@ import Data.Digest.Internal import Foreign.Ptr.ConstantTimeEquals (constantTimeEquals) import Internal.Base import Internal.HMAC +import Types (SecretKey(SecretKey)) type LazyByteString = ByteString.Lazy.ByteString --- | A secret key used as input to a cipher or HMAC. Equality comparisons on --- this type are variable-time. -newtype SecretKey = SecretKey ByteString - deriving (Eq, Ord, Show) - -- | A hash-based message authentication code. Equality comparisons on this type -- are constant-time. newtype HMAC = HMAC ByteString diff --git a/src/Internal/HKDF.chs b/src/Internal/HKDF.chs new file mode 100644 index 0000000..a3a48ed --- /dev/null +++ b/src/Internal/HKDF.chs @@ -0,0 +1,45 @@ +-- Copyright 2018 Google LLC +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); you may not +-- use this file except in compliance with the License. You may obtain a copy of +-- the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +-- License for the specific language governing permissions and limitations under +-- the License. + +module Internal.HKDF + ( hkdfExtract, hkdfExpand + ) where + +import Foreign (Ptr) +import Foreign.C.Types + +{#import Internal.Base#} +import Result + +#include + +hkdfExtract :: + Ptr CUChar -> Ptr CULong + -> Ptr EVPMD + -> Ptr CUChar -> CULong + -> Ptr CUChar -> CULong + -> IO () +hkdfExtract outKey outLen digest secret secretLen salt saltLen = + requireSuccess $ + {#call HKDF_extract as ^#} outKey outLen digest secret secretLen salt saltLen + +hkdfExpand :: + Ptr CUChar -> CULong + -> Ptr EVPMD + -> Ptr CUChar -> CULong + -> Ptr CUChar -> CULong + -> IO () +hkdfExpand outKey outLen digest prk prkLen info infoLen = + requireSuccess $ + {#call HKDF_expand as ^#} outKey outLen digest prk prkLen info infoLen diff --git a/src/Types.hs b/src/Types.hs new file mode 100644 index 0000000..a625c3e --- /dev/null +++ b/src/Types.hs @@ -0,0 +1,29 @@ +-- Copyright 2018 Google LLC +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); you may not +-- use this file except in compliance with the License. You may obtain a copy of +-- the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +-- License for the specific language governing permissions and limitations under +-- the License. + +module Types + ( Salt(Salt) + , SecretKey(SecretKey) + ) where + +import Data.ByteString (ByteString) + +-- | A salt. Equality comparisons on this type are variable-time. +newtype Salt = Salt ByteString + deriving (Eq, Ord, Show) + +-- | A secret key used as input to a cipher or HMAC. Equality comparisons on +-- this type are variable-time. +newtype SecretKey = SecretKey ByteString + deriving (Eq, Ord, Show) diff --git a/tests/Codec/Crypto/HKDFTests.hs b/tests/Codec/Crypto/HKDFTests.hs new file mode 100644 index 0000000..5daffdb --- /dev/null +++ b/tests/Codec/Crypto/HKDFTests.hs @@ -0,0 +1,74 @@ +-- Copyright 2018 Google LLC +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); you may not +-- use this file except in compliance with the License. You may obtain a copy of +-- the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +-- License for the specific language governing permissions and limitations under +-- the License. + +{-# LANGUAGE OverloadedStrings #-} + +module Codec.Crypto.HKDFTests (tests) where + +import Data.ByteString (ByteString) +import qualified Data.ByteString as ByteString +import qualified Data.ByteString.Base16 as ByteString.Base16 +import qualified Data.ByteString.Char8 as ByteString.Char8 +import Test.Tasty (TestTree, testGroup) +import Test.Tasty.HUnit ((@?=), testCase) + +import Codec.Crypto.HKDF (Salt(Salt), SecretKey(SecretKey)) +import qualified Codec.Crypto.HKDF as HKDF +import Data.Digest (sha1, sha256) + +tests :: TestTree +tests = testGroup "Codec.Crypto.HKDF" [testRFC5869] + +-- | Tests from RFC 5869. +testRFC5869 = testGroup "RFC 5869 examples" + [ t "test case 1" + sha256 + (SecretKey $ ByteString.replicate 22 0x0b) + (Salt $ ByteString.pack [0x00 .. 0x0c]) + (SecretKey $ hex "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5") + , t "test case 2" + sha256 + (SecretKey $ ByteString.pack [0x00 .. 0x4f]) + (Salt $ ByteString.pack [0x60 .. 0xaf]) + (SecretKey $ hex "06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244") + , t "test case 3" + sha256 + (SecretKey $ ByteString.replicate 22 0x0b) + (Salt "") + (SecretKey $ hex "19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04") + , t "test case 4" + sha1 + (SecretKey $ ByteString.replicate 11 0x0b) + (Salt $ ByteString.pack [0x00 .. 0x0c]) + (SecretKey $ hex "9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243") + , t "test case 5" + sha1 + (SecretKey $ ByteString.pack [0x00 .. 0x4f]) + (Salt $ ByteString.pack [0x60 .. 0xaf]) + (SecretKey $ hex "8adae09a2a307059478d309b26c4115a224cfaf6") + , t "test case 6" + sha1 + (SecretKey $ ByteString.replicate 22 0x0b) + (Salt "") + (SecretKey $ hex "da8c8a73c7fa77288ec6f5e7c297786aa0d32d01") + ] + where + t name hash ikm salt prk = + testGroup name [testCase "extract" $ HKDF.extract hash salt ikm @?= prk] + +hex :: ByteString -> ByteString +hex s = + case ByteString.Base16.decode s of + (r, "") -> r + _ -> error $ "invalid hex string " ++ ByteString.Char8.unpack s diff --git a/tests/Tests.hs b/tests/Tests.hs index ee38f2a..6555cf3 100644 --- a/tests/Tests.hs +++ b/tests/Tests.hs @@ -18,11 +18,13 @@ module Main import Test.Tasty (defaultMain, testGroup) +import qualified Codec.Crypto.HKDFTests import qualified Data.DigestTests import qualified Data.HMACTests main :: IO () main = defaultMain $ testGroup "btls" - [ Data.DigestTests.tests + [ Codec.Crypto.HKDFTests.tests + , Data.DigestTests.tests , Data.HMACTests.tests ] -- cgit v1.2.3