Skip to content
Draft
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 index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AppRegistry } from 'react-native'
import BackgroundGeolocation from 'react-native-background-geolocation'
import 'react-native-gesture-handler'
import 'react-native-url-polyfill/auto'
import 'text-encoding-polyfill'

import App from './src/App'
import { GeolocationTrackingHeadlessTask } from './src/app/domain/geolocation/tracking'
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@
"redux-logger": "3.0.6",
"redux-persist": "^6.0.0",
"rn-flipper-async-storage-advanced": "^1.0.4",
"semver": "^7.3.2"
"semver": "^7.3.2",
"text-encoding-polyfill": "^0.6.7"
},
"devDependencies": {
"@babel/core": "7.14.8",
Expand Down
39 changes: 18 additions & 21 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ const App = ({ setClient }) => {

return (
<>
<CryptoWebView />
<RootNavigator initialRoute={initialRoute} setClient={setClient} />
{canDisplayLauncher() && (
<LauncherView
Expand Down Expand Up @@ -322,7 +323,6 @@ const WrappedApp = () => {

const Wrapper = () => {
const [markNameWrapper] = useState(() => rnperformance.mark('Wrapper'))
const [hasCrypto, setHasCrypto] = useState(false)

useEffect(() => {
rnperformance.measure({
Expand Down Expand Up @@ -361,26 +361,23 @@ const Wrapper = () => {
<>
<RestartProvider>
{__DEV__ && <FlipperAsyncStorage />}
<CryptoWebView setHasCrypto={setHasCrypto} />
{hasCrypto && (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<HttpServerProvider>
<HomeStateProvider>
<SplashScreenProvider>
<SecureBackgroundSplashScreenWrapper>
<ThemeProvider>
<PermissionsChecker>
<WrappedApp />
</PermissionsChecker>
</ThemeProvider>
</SecureBackgroundSplashScreenWrapper>
</SplashScreenProvider>
</HomeStateProvider>
</HttpServerProvider>
</PersistGate>
</Provider>
)}
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<HttpServerProvider>
<HomeStateProvider>
<SplashScreenProvider>
<SecureBackgroundSplashScreenWrapper>
<ThemeProvider>
<PermissionsChecker>
<WrappedApp />
</PermissionsChecker>
</ThemeProvider>
</SecureBackgroundSplashScreenWrapper>
</SplashScreenProvider>
</HomeStateProvider>
</HttpServerProvider>
</PersistGate>
</Provider>
</RestartProvider>
</>
)
Expand Down
69 changes: 69 additions & 0 deletions src/app/domain/crypto/services/crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import QuickCrypto from 'react-native-quick-crypto'

const base64urlencode = (a: Uint8Array): string => {
let str = ''

const bytes = new Uint8Array(a)
const len = bytes.byteLength

for (let i = 0; i < len; i++) {
str += String.fromCharCode(bytes[i])
}

return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}

export const generateHttpServerSecurityKey = (): string => {
const array = new Uint8Array(16)
QuickCrypto.getRandomValues(array)

const securityKey = base64urlencode(array)

return securityKey
}

const sha256 = async (value: string): Promise<Uint8Array> => {
// TextEncoder is implemented only starting RN 0.74
// Using text-encoding-polyfill for this POC
const encoder = new TextEncoder()
const data = encoder.encode(value)
const buffer = await QuickCrypto.subtle.digest('SHA-256', data)

return new Uint8Array(buffer)
}

const generateCodeVerifier = (): string => {
const array = new Uint8Array(32)
window.crypto.getRandomValues(array)
return base64urlencode(array)
}

const generateCodeChallengeFromVerifier = async (
value: string
): Promise<string> => {
const hashed = await sha256(value)
const base64encoded = base64urlencode(hashed)
return base64encoded
}

interface PKCE {
codeVerifier: string
codeChallenge: string
}

/**
* Create and return a couple of PKCE keys
*
* @returns {PKCE} PKCE codes
* throws
*/
export const createPKCE = async (): Promise<PKCE> => {
const codeVerifier = generateCodeVerifier()
const codeChallenge = await generateCodeChallengeFromVerifier(codeVerifier)

const pkce = {
codeVerifier,
codeChallenge
}
return pkce
}
6 changes: 2 additions & 4 deletions src/components/webviews/CryptoWebView/CryptoWebView.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { SupervisedWebView } from '/components/webviews/SupervisedWebView'

const log = Minilog('🥷 CryptoWebView')

export const CryptoWebView = ({ setHasCrypto }) => {
export const CryptoWebView = () => {
const [isLoading, setIsLoading] = useState(true)
const webviewRef = useRef()
const [markName] = useState(() => rnperformance.mark('CryptoWebView'))
Expand Down Expand Up @@ -57,16 +57,14 @@ export const CryptoWebView = ({ setHasCrypto }) => {
markName: markName,
measureName: 'CryptoWebView Loaded'
})
setHasCrypto?.(true)
}

return () => {
if (!isLoading) {
unsubscribeFromCrypto(processMessage)
setHasCrypto?.(false)
}
}
}, [isLoading, setHasCrypto, webviewRef])
}, [isLoading, webviewRef])

return (
<View style={styles.cryptoView}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

/**
* @typedef {('computePass'|'computePKCE'|'generateHttpServerSecurityKey')} CryptoMethodName
* @typedef {('computePass')} CryptoMethodName
*/

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { computePKCEFunctionDeclaration } from './jsFunctions/jsComputePKCE'
import { computePassFunctionDeclaration } from './jsFunctions/jsComputePass'
import { generateHttpServerSecurityKeyFunctionDeclaration } from './jsFunctions/jsGenerateHttpServerSecurityKey'
import {
postMessageFunctionDeclaration,
listenMessageFunctionDeclaration
Expand All @@ -13,14 +11,10 @@ const jsCode = `
${listenMessageFunctionDeclaration}
${windowPasswordObjectDeclaration}
${computePassFunctionDeclaration}
${computePKCEFunctionDeclaration}
${generateHttpServerSecurityKeyFunctionDeclaration}
${subtleFunctionDeclaration}

const messagingFunctions = {
computePass,
computePKCE,
generateHttpServerSecurityKey,
sublteProxy
}

Expand Down

This file was deleted.

This file was deleted.

18 changes: 1 addition & 17 deletions src/libs/clientHelpers/authorizeClient.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
import type CozyClient from 'cozy-client'

import { queryResultToCrypto } from '/components/webviews/CryptoWebView/cryptoObservable/cryptoObservable'
import { finalizeClientCreation } from '/libs/clientHelpers/createClient'
import {
CozyClientCreationContext,
STATE_CONNECTED
} from '/libs/clientHelpers/types'

interface PkceResult {
codeVerifier: string
codeChallenge: string
}

/**
* Create and return a couple of PKCE keys
* To make the PKCE creation possible, a CryptoWebView must be present in the ReactNative component tree
*
* @returns {object} message result from the CryptoWebView's `computePKCE` method
* throws
*/
export const createPKCE = async (): Promise<PkceResult> => {
return (await queryResultToCrypto('computePKCE', {})) as unknown as PkceResult
}
import { createPKCE } from '/app/domain/crypto/services/crypto'

interface AuthorizeClientParams {
client: CozyClient
Expand Down
19 changes: 6 additions & 13 deletions src/libs/httpserver/httpServerProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ import Config from 'react-native-config'
import type CozyClient from 'cozy-client'
import Minilog from 'cozy-minilog'

import { generateHttpServerSecurityKey } from '/app/domain/crypto/services/crypto'
import { isOfflineCompatible } from '/app/domain/offline/isOfflineCompatible'
import rnperformance from '/app/domain/performances/measure'
import { getErrorMessage } from '/libs/functions/getErrorMessage'
import HttpServer from '/libs/httpserver/HttpServer'
import { fetchAppDataForSlug } from '/libs/httpserver/indexDataFetcher'
import { getServerBaseFolder } from '/libs/httpserver/httpPaths'
import { queryResultToCrypto } from '/components/webviews/CryptoWebView/cryptoObservable/cryptoObservable'
import { setCookie } from '/libs/httpserver/httpCookieManager'
import { HtmlSource } from '/libs/httpserver/models'
import {
fillIndexWithData,
getIndexForFqdnAndSlug
} from '/libs/httpserver/indexGenerator'

import {
addBodyClasses,
Expand All @@ -27,11 +31,6 @@ import {
addColorSchemeMetaIfNecessary
} from './server-helpers'

import {
fillIndexWithData,
getIndexForFqdnAndSlug
} from '/libs/httpserver/indexGenerator'

const log = Minilog('HttpServerProvider')

const DEFAULT_PORT = Config.HTTP_SERVER_DEFAULT_PORT
Expand All @@ -54,10 +53,6 @@ interface HttpServerState {
) => Promise<IndexHtmlForSlug | false>
}

interface SecurityKeyResult {
securityKey: string
}

export const HttpServerContext = createContext<HttpServerState | undefined>(
undefined
)
Expand Down Expand Up @@ -99,9 +94,7 @@ export const HttpServerProvider = (
.then(async url => {
log.debug('🚀 Serving at URL', url)

const { securityKey } = (await queryResultToCrypto(
'generateHttpServerSecurityKey'
)) as unknown as SecurityKeyResult
const securityKey = generateHttpServerSecurityKey()

await server.setSecurityKey(securityKey)
setServerSecurityKey(securityKey)
Expand Down
Loading
Loading