Skip to content
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/1-api-changes/mls-private-keys
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Expose MLS public keys in a new endpoint `GET /mls/public-keys`.
1 change: 1 addition & 0 deletions changelog.d/6-federation/mls-private-keys
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add mlsPrivateKeyPaths setting to galley
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
apiVersion: v1
kind: Secret
metadata:
name: galley
name: galley-aws
labels:
app: galley
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
Expand Down
5 changes: 5 additions & 0 deletions charts/galley/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ data:
enableIndexedBillingTeamMembers: {{ .settings.enableIndexedBillingTeamMembers }}
{{- end }}
federationDomain: {{ .settings.federationDomain }}
mlsPrivateKeyPaths:
{{- if $.Values.secrets.mlsPrivateKeys.removal.ed25519 }}
removal:
ed25519: "/etc/wire/galley/secrets/removal_ed25519.pem"
{{- end }}
{{- if .settings.featureFlags }}
featureFlags:
sso: {{ .settings.featureFlags.sso }}
Expand Down
12 changes: 9 additions & 3 deletions charts/galley/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,37 @@ spec:
annotations:
# An annotation of the configmap checksum ensures changes to the configmap cause a redeployment upon `helm upgrade`
checksum/configmap: {{ include (print .Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print .Template.BasePath "/secret.yaml") . | sha256sum }}
checksum/aws-secret: {{ include (print .Template.BasePath "/aws-secret.yaml") . | sha256sum }}
checksum/mls-secret: {{ include (print .Template.BasePath "/mls-secret.yaml") . | sha256sum }}
spec:
serviceAccountName: {{ .Values.serviceAccount.name }}
volumes:
- name: "galley-config"
configMap:
name: "galley"
- name: "galley-secrets"
secret:
secretName: "galley-mls"
containers:
- name: galley
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ default "" .Values.imagePullPolicy | quote }}
volumeMounts:
- name: "galley-config"
mountPath: "/etc/wire/galley/conf"
- name: "galley-secrets"
mountPath: "/etc/wire/galley/secrets"
env:
{{- if hasKey .Values.secrets "awsKeyId" }}
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: galley
name: galley-aws
key: awsKeyId
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: galley
name: galley-aws
key: awsSecretKey
{{- end }}
- name: AWS_REGION
Expand Down
14 changes: 14 additions & 0 deletions charts/galley/templates/mls-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Secret
metadata:
name: galley-mls
labels:
app: galley
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
type: Opaque
data:
{{- if .Values.secrets.mlsPrivateKeys.removal.ed25519 }}
removal_ed25519.pem: {{ .Values.secrets.mlsPrivateKeys.removal.ed25519 | b64enc | quote }}
{{- end -}}
5 changes: 5 additions & 0 deletions charts/galley/templates/tests/galley-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ spec:
- name: "galley-integration-secrets"
configMap:
name: "galley-integration-secrets"
- name: "galley-secrets"
secret:
secretName: "galley-mls"
containers:
- name: integration
image: "{{ .Values.image.repository }}-integration:{{ .Values.image.tag }}"
Expand All @@ -47,6 +50,8 @@ spec:
- name: "galley-integration-secrets"
# TODO: see corresp. TODO in brig.
mountPath: "/etc/wire/integration-secrets"
- name: "galley-secrets"
mountPath: "/etc/wire/galley/secrets"
env:
# these dummy values are necessary for Amazonka's "Discover"
- name: AWS_ACCESS_KEY_ID
Expand Down
2 changes: 1 addition & 1 deletion charts/galley/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,4 @@ serviceAccount:
annotations: {}
automountServiceAccountToken: true

secrets: {}
secrets:
3 changes: 3 additions & 0 deletions deploy/services-demo/conf/ed25519.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIAocCDXsKIAjb65gOUn5vEF0RIKnVJkKR4ebQzuZ709c
-----END PRIVATE KEY-----
3 changes: 3 additions & 0 deletions deploy/services-demo/conf/galley.demo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ settings:
conversationCodeURI: https://127.0.0.1/conversation-join/
concurrentDeletionEvents: 1024
deleteConvThrottleMillis: 0
mlsPrivateKeyPaths:
removal:
ed25519: conf/ed25519.pem

featureFlags: # see #RefConfigOptions in `/docs/reference`
sso: disabled-by-default
Expand Down
5 changes: 3 additions & 2 deletions docs/legacy/developer/api-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,9 @@ version.

#### Adding a new endpoint

We add the new endpoint to the routing table, and set its version range to only
include the development version. The supported version is unaffected.
We add the new endpoint to the routing table. There is no need to set its
version range to only include the development version, since the supported
version is unaffected.

#### Removing an endpoint

Expand Down
24 changes: 24 additions & 0 deletions docs/legacy/reference/config-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ Even when the flag is `disabled`, galley will keep writing to the
been added in order to deploy new code and backfill data in
production.

### MLS private key paths

The `mlsPrivateKeyPaths` field should contain a mapping from *purposes* and
signature schemes to file paths of corresponding x509 private keys in PEM
format.

At the moment, the only purpose is `removal`, meaning that the key will be used
to sign external remove proposals.

For example:

```
mlsPrivateKeyPaths:
removal:
ed25519: /etc/secrets/ed25519.pem
```

A simple way to generate an ed25519 private key, discarding the corresponding
certificate, is to run the following command:

```
openssl req -nodes -newkey ed25519 -keyout ed25519.pem -out /dev/null -subj /
```

## Feature flags

> Also see [Wire docs](https://docs.wire.com/how-to/install/team-feature-settings.html) where some of the feature flags are documented from an operations point of view.
Expand Down
7 changes: 7 additions & 0 deletions hack/helm_vars/wire-server/values.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ galley:
secrets:
awsKeyId: dummykey
awsSecretKey: dummysecret
mlsPrivateKeys:
removal:
ed25519: |
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIAocCDXsKIAjb65gOUn5vEF0RIKnVJkKR4ebQzuZ709c
-----END PRIVATE KEY-----

gundeck:
replicaCount: 1
imagePullPolicy: {{ .Values.imagePullPolicy }}
Expand Down
9 changes: 5 additions & 4 deletions libs/wire-api/src/Wire/API/MLS/CipherSuite.hs
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ csHash MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 ctx value =
HKDF.expand (HKDF.extract @SHA256 (mempty :: ByteString) value) ctx 16

csVerifySignature :: CipherSuiteTag -> ByteString -> ByteString -> ByteString -> Bool
csVerifySignature MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 pub x sig = fromMaybe False . maybeCryptoError $ do
pub' <- Ed25519.publicKey pub
sig' <- Ed25519.signature sig
pure $ Ed25519.verify pub' x sig'
csVerifySignature MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 pub x sig =
fromMaybe False . maybeCryptoError $ do
pub' <- Ed25519.publicKey pub
sig' <- Ed25519.signature sig
pure $ Ed25519.verify pub' x sig'

csSignatureScheme :: CipherSuiteTag -> SignatureSchemeTag
csSignatureScheme MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 = Ed25519
39 changes: 39 additions & 0 deletions libs/wire-api/src/Wire/API/MLS/Credential.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Control.Lens ((?~))
import Data.Aeson (FromJSON (..), FromJSONKey (..), ToJSON (..), ToJSONKey (..))
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import Data.Bifunctor
import Data.Binary
import Data.Binary.Get
import Data.Binary.Parser
Expand Down Expand Up @@ -156,3 +157,41 @@ instance ParseMLS ClientIdentity where

mkClientIdentity :: Qualified UserId -> ClientId -> ClientIdentity
mkClientIdentity (Qualified uid domain) = ClientIdentity domain uid

-- | Possible uses of a private key in the context of MLS.
data SignaturePurpose
= -- | Creating external remove proposals.
RemovalPurpose
deriving (Eq, Ord, Show, Bounded, Enum)

signaturePurposeName :: SignaturePurpose -> Text
signaturePurposeName RemovalPurpose = "removal"

signaturePurposeFromName :: Text -> Either String SignaturePurpose
signaturePurposeFromName name =
note ("Unsupported signature purpose " <> T.unpack name)
. getAlt
$ flip foldMap [minBound .. maxBound] $ \s ->
guard (signaturePurposeName s == name) $> s

instance FromJSON SignaturePurpose where
parseJSON =
Aeson.withText "SignaturePurpose" $
either fail pure . signaturePurposeFromName

instance FromJSONKey SignaturePurpose where
fromJSONKey =
Aeson.FromJSONKeyTextParser $
either fail pure . signaturePurposeFromName

instance S.ToParamSchema SignaturePurpose where
toParamSchema _ = mempty & S.type_ ?~ S.SwaggerString

instance FromHttpApiData SignaturePurpose where
parseQueryParam = first T.pack . signaturePurposeFromName

instance ToJSON SignaturePurpose where
toJSON = Aeson.String . signaturePurposeName

instance ToJSONKey SignaturePurpose where
toJSONKey = Aeson.toJSONKeyText signaturePurposeName
66 changes: 66 additions & 0 deletions libs/wire-api/src/Wire/API/MLS/Keys.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- This file is part of the Wire Server implementation.
--
-- Copyright (C) 2022 Wire Swiss GmbH <opensource@wire.com>
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU Affero General Public License as published by the Free
-- Software Foundation, either version 3 of the License, or (at your option) any
-- later version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
-- details.
--
-- 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/>.

module Wire.API.MLS.Keys
( MLSKeys (..),
MLSPublicKeys (..),
mlsKeysToPublic,
)
where

import Crypto.PubKey.Ed25519
import Data.Aeson (FromJSON (..), ToJSON (..))
import Data.ByteArray
import Data.Json.Util
import qualified Data.Map as Map
import Data.Schema
import qualified Data.Swagger as S
import Imports
import Wire.API.MLS.Credential

data MLSKeys = MLSKeys
{ mlsKeyPair_ed25519 :: Maybe (SecretKey, PublicKey)
}

instance Semigroup MLSKeys where
MLSKeys Nothing <> MLSKeys ed2 = MLSKeys ed2
MLSKeys ed1 <> MLSKeys _ = MLSKeys ed1

instance Monoid MLSKeys where
mempty = MLSKeys Nothing

newtype MLSPublicKeys = MLSPublicKeys
{ unMLSPublicKeys :: Map SignaturePurpose (Map SignatureSchemeTag ByteString)
}
deriving (FromJSON, ToJSON, S.ToSchema) via Schema MLSPublicKeys
deriving newtype (Semigroup, Monoid)

instance ToSchema MLSPublicKeys where
schema =
named "MLSKeys" $
MLSPublicKeys <$> unMLSPublicKeys
.= map_ (map_ base64Schema)

mlsKeysToPublic1 :: MLSKeys -> Map SignatureSchemeTag ByteString
mlsKeysToPublic1 (MLSKeys mEd25519key) =
fold $ Map.singleton Ed25519 . convert . snd <$> mEd25519key

mlsKeysToPublic :: (SignaturePurpose -> MLSKeys) -> MLSPublicKeys
mlsKeysToPublic f = flip foldMap [minBound .. maxBound] $ \purpose ->
MLSPublicKeys (Map.singleton purpose (mlsKeysToPublic1 (f purpose)))
7 changes: 7 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Public/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import Wire.API.Error
import qualified Wire.API.Error.Brig as BrigError
import Wire.API.Error.Galley
import Wire.API.Event.Conversation
import Wire.API.MLS.Keys
import Wire.API.MLS.Message
import Wire.API.MLS.Serialisation
import Wire.API.MLS.Servant
Expand Down Expand Up @@ -1363,6 +1364,12 @@ type MLSMessagingAPI =
:> ReqBody '[MLS] (RawMLS SomeMessage)
:> MultiVerb1 'POST '[JSON] (Respond 201 "Message sent" MLSMessageSendingStatus)
)
:<|> Named
"mls-public-keys"
( Summary "Get public keys used by the backend to sign external proposals"
:> "public-keys"
:> MultiVerb1 'GET '[JSON] (Respond 200 "Public keys" MLSPublicKeys)
)

type MLSAPI = LiftNamed (ZLocalUser :> "mls" :> MLSMessagingAPI)

Expand Down
1 change: 1 addition & 0 deletions libs/wire-api/wire-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ library
Wire.API.MLS.Extension
Wire.API.MLS.Group
Wire.API.MLS.KeyPackage
Wire.API.MLS.Keys
Wire.API.MLS.Message
Wire.API.MLS.Proposal
Wire.API.MLS.Serialisation
Expand Down
5 changes: 5 additions & 0 deletions services/galley/galley.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ library
Galley.API.Message
Galley.API.MLS
Galley.API.MLS.KeyPackage
Galley.API.MLS.Keys
Galley.API.MLS.Message
Galley.API.MLS.Welcome
Galley.API.One2One
Expand Down Expand Up @@ -115,6 +116,7 @@ library
Galley.Intra.Team
Galley.Intra.User
Galley.Intra.Util
Galley.Keys
Galley.Monad
Galley.Options
Galley.Queue
Expand Down Expand Up @@ -175,6 +177,8 @@ library
aeson >=2.0.1.0
, amazonka >=1.4.5
, amazonka-sqs >=1.4.5
, asn1-encoding
, asn1-types
, async >=2.0
, base >=4.6 && <5
, base64-bytestring >=1.0
Expand Down Expand Up @@ -270,6 +274,7 @@ library
, warp >=3.0
, wire-api
, wire-api-federation
, x509

default-language: Haskell2010

Expand Down
3 changes: 3 additions & 0 deletions services/galley/galley.integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ settings:
# Once set, DO NOT change it: if you do, existing users may have a broken experience and/or stop working
# Remember to keep it the same in Brig
federationDomain: example.com
mlsPrivateKeyPaths:
removal:
ed25519: test/resources/ed25519.pem

featureFlags: # see #RefConfigOptions in `/docs/reference`
sso: disabled-by-default
Expand Down
2 changes: 2 additions & 0 deletions services/galley/src/Galley/API/MLS.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ module Galley.API.MLS
postMLSMessage,
postMLSMessageFromLocalUser,
postMLSMessageFromLocalUserV1,
getMLSPublicKeys,
)
where

import Galley.API.MLS.Keys
import Galley.API.MLS.Message
import Galley.API.MLS.Welcome
Loading