Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WPB-8988 Upgrade rusty-jwt-tools to support ecdsa_secp256r1_sha256 #4035

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2-features/WPB-8988
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Upgrade `rusty-jwt-tools` to support `ecdsa_secp256r1_sha256`
7 changes: 3 additions & 4 deletions hack/helm_vars/wire-server/values.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,10 @@ brig:
smtpPassword: dummy-smtp-password
dpopSigKeyBundle: |
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIFANnxZLNE4p+GDzWzR3wm/v8x/0bxZYkCyke1aTRucX
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgokD9kGYErMooLqpv
IRUVCtV1l6HmtqTJUFun0/4XLuahRANCAASWH/qkgOLwZz1GvEt0ch4HPRQUoj9U
TL8L7QANF9JztsEQ2omrX9l7RoosjAm+PKwrL+c3GiT63CSd1qrUpoZa
-----END PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEACPvhIdimF20tOPjbb+fXJrwS2RKDp7686T90AZ0+Th8=
-----END PUBLIC KEY-----
oauthJwkKeyPair: |
{
"kty": "OKP",
Expand Down
9 changes: 1 addition & 8 deletions libs/jwt-tools/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
# dependencies are added or removed.
{ mkDerivation
, base
, bytestring
, bytestring-conversion
, gitignoreSource
, hspec
Expand All @@ -29,13 +28,7 @@ mkDerivation {
utf8-string
];
librarySystemDepends = [ rusty_jwt_tools_ffi ];
testHaskellDepends = [
bytestring
hspec
imports
string-conversions
transformers
];
testHaskellDepends = [ hspec imports string-conversions ];
description = "FFI to rusty-jwt-tools";
license = lib.licenses.agpl3Only;
}
4 changes: 1 addition & 3 deletions libs/jwt-tools/jwt-tools.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,10 @@ test-suite jwt-tools-tests
main-is: Spec.hs
type: exitcode-stdio-1.0
build-depends:
bytestring
, hspec
hspec
, imports
, jwt-tools
, string-conversions
, transformers

hs-source-dirs: test
default-language: GHC2021
Expand Down
36 changes: 0 additions & 36 deletions libs/jwt-tools/test/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,13 @@
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

import Control.Monad.Trans.Except
import Data.ByteString.Char8 (split)
import Data.Jwt.Tools
import Data.String.Conversions
import Imports
import Test.Hspec

main :: IO ()
main = hspec $ do
describe "generateDpopToken FFI when passing valid inputs" $ do
it "should return an access token with the correct header" $ do
actual <- runExceptT $ generateDpopToken proof uid cid handle displayName tid domain nonce uri method maxSkewSecs expires now pem
-- The actual payload of the DPoP token is not deterministic as it depends on the current time.
-- We therefore only check the header, because if the header is correct, it means the token creation was successful.s
let expectedHeader = "eyJhbGciOiJFZERTQSIsInR5cCI6ImF0K2p3dCIsImp3ayI6eyJrdHkiOiJPS1AiLCJjcnYiOiJFZDI1NTE5IiwieCI6ImRZSTM4VWR4a3NDMEs0UXg2RTlKSzlZZkdtLWVoblkxOG9LbUhMMllzWmsifX0"
let actualHeader = either (const "") (head . split '.') actual
actualHeader `shouldBe` expectedHeader
describe "generateDpopToken FFI when passing a wrong nonce value" $ do
it "should return BackendNonceMismatchError" $ do
actual <- runExceptT $ generateDpopToken proof uid cid handle displayName tid domain (Nonce "foobar") uri method maxSkewSecs expires now pem
actual `shouldBe` Left BackendNonceMismatchError
describe "toResult" $ do
it "should convert to correct error" $ do
toResult Nothing (Just token) `shouldBe` Right (cs token)
Expand Down Expand Up @@ -103,25 +89,3 @@ main = hspec $ do
toResult Nothing Nothing `shouldBe` Left UnknownError
where
token = ""
proof = Proof "eyJhbGciOiJFZERTQSIsImp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6Im5MSkdOLU9hNkpzcTNLY2xaZ2dMbDdVdkFWZG1CMFE2QzNONUJDZ3BoSHcifSwidHlwIjoiZHBvcCtqd3QifQ.eyJhdWQiOiJodHRwczovL3dpcmUuY29tL2FjbWUvY2hhbGxlbmdlL2FiY2QiLCJjaGFsIjoid2EyVnJrQ3RXMXNhdUoyRDN1S1k4cmM3eTRrbDR1c0giLCJleHAiOjE3Mzk4ODA2NzQsImhhbmRsZSI6IndpcmVhcHA6Ly8lNDB5d2Z5ZG5pZ2Jud2h1b3pldGphZ3FAZXhhbXBsZS5jb20iLCJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9jbGllbnRzL2NjNmU2NDBlMjk2ZThiYmEvYWNjZXNzLXRva2VuIiwiaWF0IjoxNzA4MzQ0Njc0LCJqdGkiOiI2ZmM1OWU3Zi1iNjY2LTRmZmMtYjczOC00ZjQ3NjBjODg0Y2EiLCJuYW1lIjoi5reB4qqu5KSq5rK255Kh4bKV6re14Y2q6omE6Jy16Iu17ICV54Kb66-v56qp5KqW766M6bGw6oOy6b6m57m15pWJ4LqH54et6rOj54KHIiwibmJmIjoxNzA4MzQ0Njc0LCJub25jZSI6IllWZ2dHdWlTUTZlamhQNTNFX0tPS3ciLCJzdWIiOiJ3aXJlYXBwOi8vSWZ0VzBLeFVSb2F1QWVockRremJiQSFjYzZlNjQwZTI5NmU4YmJhQGV4YW1wbGUuY29tIiwidGVhbSI6ImMxNTE5NzVlLWIxOTMtNDAwOS1hM2QyLTc0N2M5NjFmMjMzMyJ9.SHxpMzOe2yC3y6DP7lEH0l7_eOKrUZZI0OjgtnCKjO4OBD0XqKOi0y_z07-7FWc-KtThlsaZatnBNTB67GhQBw"
uid = UserId "21fb56d0-ac54-4686-ae01-e86b0e4cdb6c"
nonce = Nonce "YVggGuiSQ6ejhP53E_KOKw"
expires = ExpiryEpoch 1739967074
handle = Handle "ywfydnigbnwhuozetjagq"
displayName = DisplayName "\230\183\129\226\170\174\228\164\170\230\178\182\231\146\161\225\178\149\234\183\181\225\141\170\234\137\132\232\156\181\232\139\181\236\128\149\231\130\155\235\175\175\231\170\169\228\170\150\239\174\140\233\177\176\234\131\178\233\190\166\231\185\181\230\149\137\224\186\135\231\135\173\234\179\163\231\130\135"
tid = TeamId "c151975e-b193-4009-a3d2-747c961f2333"

now = NowEpoch 1704982162
cid = ClientId 14730821443162901434
domain = Domain "example.com"
uri = Uri "https://example.com/clients/cc6e640e296e8bba/access-token"
method = POST
maxSkewSecs = MaxSkewSecs 1
pem =
PemBundle $
"-----BEGIN PRIVATE KEY-----\n\
\MC4CAQAwBQYDK2VwBCIEIMkvahkqR9sHJSmFeCl3B7aJjsQGgwy++cccWTbuDyy+\n\
\-----END PRIVATE KEY-----\n\
\-----BEGIN PUBLIC KEY-----\n\
\MCowBQYDK2VwAyEAdYI38UdxksC0K4Qx6E9JK9YfGm+ehnY18oKmHL2YsZk=\n\
\-----END PUBLIC KEY-----\n"
11 changes: 7 additions & 4 deletions nix/pkgs/rusty_jwt_tools_ffi/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ let
src = fetchFromGitHub {
owner = "wireapp";
repo = "rusty-jwt-tools";
rev = "60424bf7031e2fa535aac658d0b5643624d19537";
sha256 = "sha256-kdubK9FruZT8pbIwCHyAkxYj9yVM0q7ivNhNUNtNQCY=";
rev = "05441e98d9c7c5ec9bfcfba84e885988278f10e6";
sha256 = "sha256-HVq2BpPKp3cfdlKrS1AYWQ+a5VigFsYfSecZ60SFATI=";
};
cargoLockFile = builtins.toFile "cargo.lock" (builtins.readFile "${src}/Cargo.lock");

Expand All @@ -29,8 +29,11 @@ rustPlatform.buildRustPackage {
outputHashes = {
# if any of these need updating, replace / create new key with
# lib.fakeSha256, rebuild, and replace with actual hash.
"certval-0.1.4" = "sha256-gzkRC7/u/rARGPy3d37eBrAVml4XSDb6bRPpsESmttY=";
"jwt-simple-0.12.1" = "sha256-5PAOwulL8j6f4Ycoa5Q+1dqEA24uN8rJt+i2RebL6eo=";
"certval-0.1.4" = "sha256-4BWvSzFZhlA+mKj+Y6GNEwNSKikNGVjDoPxyxiw9TFE=";
"biscuit-0.6.0-beta1" = "sha256-no7b4Un+7AES7EwWdZh/oeIa4w0caKLAUFsHWqgJOrg=";
"jwt-simple-0.13.0" = "sha256-QkVi7EGrU3nF+/32tNjTtAILo8sjasR27nyRgBH+xoA=";
"rcgen-0.9.2" = "sha256-3jFzInwdzFBot+L2Vm5NLF1ml33GH2+Iv3LqqGhLxFs=";
"ring-0.17.0-not-released-yet" = "sha256-TP8yZo64J/d1fw8l2J4+ol70EcHvpvHJBdpF3A+6Dgo=";
};
};

Expand Down
2 changes: 1 addition & 1 deletion services/brig/brig.integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ optSettings:
setNonceTtlSecs: 5
setDpopMaxSkewSecs: 1
setDpopTokenExpirationTimeSecs: 300 # 5 minutes
setPublicKeyBundle: test/resources/jwt/ed25519_bundle.pem
setPublicKeyBundle: test/resources/jwt/ecdsa_secp256r1_sha256_key.pem
setEnableMLS: true
# To only allow specific email address domains to register, uncomment and update the setting below
# setAllowlistEmailDomains:
Expand Down
26 changes: 23 additions & 3 deletions services/brig/test/integration/API/User/Client.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1472,7 +1472,7 @@ testCreateAccessToken opts n brig = do
handle
(fromName u.userDisplayName)
(UUID.toText (toUUID tid))
signedOrError <- fmap encodeCompact <$> liftIO (signAccessToken dpopClaims)
signedOrError <- fmap encodeCompact <$> liftIO (signProofEcdsaP256 dpopClaims)
case signedOrError of
Left err -> liftIO $ assertFailure $ "failed to sign claims: " <> show err
Right signed -> do
Expand All @@ -1483,15 +1483,25 @@ testCreateAccessToken opts n brig = do
let accessToken = fromRight (error $ "failed to create token: " <> show response) $ responseJsonEither response
liftIO $ datrType accessToken @?= DPoP
where
signAccessToken :: DPoPClaimsSet -> IO (Either JWTError SignedJWT)
signAccessToken claims = runJOSE $ do
-- FUTUREWORK: parameterize the signing algorithm
_signProof :: DPoPClaimsSet -> IO (Either JWTError SignedJWT)
_signProof claims = runJOSE $ do
algo <- bestJWSAlg jwkKey
let h =
newJWSHeader ((), algo)
& (jwk ?~ HeaderParam () jwkPubKey)
& (typ ?~ HeaderParam () "dpop+jwt")
signJWT jwkKey h claims

signProofEcdsaP256 :: DPoPClaimsSet -> IO (Either JWTError SignedJWT)
signProofEcdsaP256 claims = runJOSE $ do
algo <- bestJWSAlg jwkKeyBundleEcdsaP256
let h =
newJWSHeader ((), algo)
& (jwk ?~ HeaderParam () jwkPublicKeyEcdsaP256)
& (typ ?~ HeaderParam () "dpop+jwt")
signJWT jwkKeyBundleEcdsaP256 h claims

jwkKey :: JWK
jwkKey = do
fromMaybe (error "invalid jwk") . A.decode $
Expand All @@ -1502,6 +1512,16 @@ testCreateAccessToken opts n brig = do
fromMaybe (error "invalid jwk") . A.decode $
"{\"kty\":\"OKP\",\"crv\":\"Ed25519\",\"x\":\"nLJGN-Oa6Jsq3KclZggLl7UvAVdmB0Q6C3N5BCgphHw\"}"

jwkKeyBundleEcdsaP256 :: JWK
jwkKeyBundleEcdsaP256 = do
fromMaybe (error "invalid jwk") . A.decode $
"{\"kty\":\"EC\",\"alg\":\"ES256\",\"crv\":\"P-256\",\"x\":\"hcYjloNodyCLF_rQd_HIszSpa2J-vzrgntneAJW5pA8\",\"y\":\"6MXxnHq1FmAWCc6A7YValxvekicBv53ARTQO35mRKJ8\",\"d\":\"yz1weEXJbJao6wLiml8fahLt3BnJxdHWfbpUB0i8GLo\"}"

jwkPublicKeyEcdsaP256 :: JWK
jwkPublicKeyEcdsaP256 = do
fromMaybe (error "invalid jwk") . A.decode $
"{\"kty\":\"EC\",\"alg\":\"ES256\",\"crv\":\"P-256\",\"x\":\"hcYjloNodyCLF_rQd_HIszSpa2J-vzrgntneAJW5pA8\",\"y\":\"6MXxnHq1FmAWCc6A7YValxvekicBv53ARTQO35mRKJ8\"}"

testCreateAccessTokenMissingProof :: Brig -> Http ()
testCreateAccessTokenMissingProof brig = do
uid <- userId <$> randomUser brig
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgokD9kGYErMooLqpv
IRUVCtV1l6HmtqTJUFun0/4XLuahRANCAASWH/qkgOLwZz1GvEt0ch4HPRQUoj9U
TL8L7QANF9JztsEQ2omrX9l7RoosjAm+PKwrL+c3GiT63CSd1qrUpoZa
-----END PRIVATE KEY-----
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIFANnxZLNE4p+GDzWzR3wm/v8x/0bxZYkCyke1aTRucX
-----END PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEACPvhIdimF20tOPjbb+fXJrwS2RKDp7686T90AZ0+Th8=
-----END PUBLIC KEY-----
Loading