INTEGRITY Dokumentace

Nakonfigurujte svou mobilní aplikaci nebo zařízení IoT

Tento tutoriál ukazuje, jak nakonfigurovat zařízení IoT (Internet of Things) a mobilní aplikaci tak, aby používaly klientské certifikáty s API Shield.

Podrobnosti scénáře

Tento návod používá příklad zařízení, které zaznamenává teplotní údaje a odesílá je pomocí požadavku POST na API chráněné Cloudflare. Mobilní aplikace vytvořená ve Swiftu pro iOS tyto údaje načítá a zobrazuje.

Aby byl tento příklad jednoduchý, je API implementováno jako Cloudflare Worker (s využitím kódu z Tutoriál To-Do List o vytvoření aplikace jamstack).

Teploty se ukládají v Workers KV pomocí zdrojové IP adresy jako klíče, ale snadno můžete použít i hodnota z klientského certifikátu, například otisk.

Níže uvedený ukázkový kód API při požadavku POST uloží teplotu a časové razítko do KV a při požadavku GET vrátí posledních pět teplot.

const defaultData = { temperatures: [] };

const getCache = (key) => TEMPERATURES.get(key);
const setCache = (key, data) => TEMPERATURES.put(key, data);

async function addTemperature(request) {
	// Pull previously recorded temperatures for this client.
	const ip = request.headers.get("CF-Connecting-IP");
	const cacheKey = `data-${ip}`;
	let data;
	const cache = await getCache(cacheKey);
	if (!cache) {
		await setCache(cacheKey, JSON.stringify(defaultData));
		data = defaultData;
	} else {
		data = JSON.parse(cache);
	}

	// Append the recorded temperatures with the submitted reading (assuming it has both temperature and a timestamp).
	try {
		const body = await request.text();
		const val = JSON.parse(body);

		if (val.temperature && val.time) {
			data.temperatures.push(val);
			await setCache(cacheKey, JSON.stringify(data));
			return new Response("", { status: 201 });
		} else {
			return new Response(
				"Unable to parse temperature and/or timestamp from JSON POST body",
				{ status: 400 },
			);
		}
	} catch (err) {
		return new Response(err, { status: 500 });
	}
}

function compareTimestamps(a, b) {
	return -1 * (Date.parse(a.time) - Date.parse(b.time));
}

// Return the 5 most recent temperature measurements.
async function getTemperatures(request) {
	const ip = request.headers.get("CF-Connecting-IP");
	const cacheKey = `data-${ip}`;

	const cache = await getCache(cacheKey);
	if (!cache) {
		return new Response(JSON.stringify(defaultData), {
			status: 200,
			headers: { "content-type": "application/json" },
		});
	} else {
		data = JSON.parse(cache);
		const retval = JSON.stringify(
			data.temperatures.sort(compareTimestamps).splice(0, 5),
		);
		return new Response(retval, {
			status: 200,
			headers: { "content-type": "application/json" },
		});
	}
}

export default {
	async fetch(request, env, ctx) {
		return request.method === "POST"
			? addTemperature(request)
			: getTemperatures(request);
	},
};

1. Ověření API

Odeslat vzorová data do API metodou POST

Chcete-li API ověřit ještě před přidáním autentizace mTLS, odešlete metodou POST náhodnou hodnotu teploty:

$ TEMPERATURE=$(echo $((361 + RANDOM %11)) | awk '{printf("%.2f",$1/10.0)}')
$ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

$ echo -e "$TEMPERATURE\n$TIMESTAMP"
36.70
2020-09-28T02:54:56Z

$ curl --verbose --header "Content-Type: application/json" --data '{"temperature":'''$TEMPERATURE''', "time": "'''$TIMESTAMP'''"}' https://shield.upinatoms.com/temps 2>&1 | grep "< HTTP/2"
< HTTP/2 201

Získání ukázkových dat z API pomocí GET

Požadavek GET na temps endpoint vrátí nejnovější hodnoty, včetně té odeslané v příkladu výše:

$ curl --silent https://shield.upinatoms.com/temps | jq .
[
  {
    "temperature": 36.3,
    "time": "2020-09-28T02:57:49Z"
  },
  {
    "temperature": 36.7,
    "time": "2020-09-28T02:54:56Z"
  },
  {
    "temperature": 36.2,
    "time": "2020-09-28T02:33:08Z"
  }
]

2. Vytvoření certifikátů vydaných Cloudflare

Než budete moci pomocí API Shield chránit své API nebo webovou aplikaci, vytvořte klientské certifikáty vydané Cloudflare.

Můžete vytvořte klientský certifikát v Cloudflare dashboardu.

Vzhledem k tomu, že většina vývojářů pracujících ve velkém měřítku generuje vlastní privátní klíče a žádosti o podepsání certifikátu (CSR) přes API, tento příklad používá k vytvoření klientských certifikátů rozhraní Cloudflare API.

Chcete-li vytvořit bootstrap certifikát pro aplikaci iOS a zařízení IoT, tento příklad používá Nástroj Cloudflare pro infrastrukturu veřejných klíčů, CFSSL:

# Generate a private key and CSR for the iOS device.

$ cat <<'EOF' | tee -a csr.json
{
    "hosts": [
        "ios-bootstrap.devices.upinatoms.com"
    ],
    "CN": "ios-bootstrap.devices.upinatoms.com",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [{
        "C": "US",
        "L": "Austin",
        "O": "Temperature Testers, Inc.",
        "OU": "Tech Operations",
        "ST": "Texas"
    }]
}
EOF

$ cfssl genkey csr.json | cfssljson -bare certificate

2020/09/27 21:28:46 [INFO] generate received request
2020/09/27 21:28:46 [INFO] received CSR
2020/09/27 21:28:46 [INFO] generating key: rsa-2048
2020/09/27 21:28:47 [INFO] encoded CSR

$ mv certificate-key.pem ios-key.pem
$ mv certificate.csr ios.csr

# Do the same for the IoT sensor.

$ sed -i.bak 's/ios-bootstrap/sensor-001/g' csr.json
$ cfssl genkey csr.json | cfssljson -bare certificate
...
$ mv certificate-key.pem sensor-key.pem
$ mv certificate.csr sensor.csr

# now ask that these CSRs be signed by the private CA issued for your zone
# we need to replace actual newlines in the CSR with ‘\n’ before POST’ing
$ CSR=$(cat ios.csr | perl -pe 's/\n/\\n/g')
$ request_body=$(< <(cat <<EOF
{
  "validity_days": 3650,
  "csr":"$CSR"
}
EOF
))

# save the response so we can view it and then extra the certificate
$ curl https://api.cloudflare.com/client/v4/zones/{zone_id}/client_certificates \
--header "X-Auth-Email: <EMAIL>" \
--header "X-Auth-Key: <API_KEY>" \
--header "Content-Type: application/json" \
--data "$request_body" > response.json

$ cat response.json | jq .

{
  "success": true,
  "errors": [],
  "messages": [],
  "result": {
    "id": "7bf7f70c-7600-42e1-81c4-e4c0da9aa515",
    "certificate_authority": {
      "id": "8f5606d9-5133-4e53-b062-a2e5da51be5e",
      "name": "Cloudflare Managed CA for account 11cbe197c050c9e422aaa103cfe30ed8"
    },
    "certificate": "-----BEGIN CERTIFICATE-----\nMIIEkzCCA...\n-----END CERTIFICATE-----\n",
    "csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIDITCCA...\n-----END CERTIFICATE REQUEST-----\n",
    "ski": "eb2a48a19802a705c0e8a39489a71bd586638fdf",
    "serial_number": "133270673305904147240315902291726509220894288063",
    "signature": "SHA256WithRSA",
    "common_name": "ios-bootstrap.devices.upinatoms.com",
    "organization": "Temperature Testers, Inc.",
    "organizational_unit": "Tech Operations",
    "country": "US",
    "state": "Texas",
    "location": "Austin",
    "expires_on": "2030-09-26T02:41:00Z",
    "issued_on": "2020-09-28T02:41:00Z",
    "fingerprint_sha256": "84b045d498f53a59bef53358441a3957de81261211fc9b6d46b0bf5880bdaf25",
    "validity_days": 3650
  }
}

$ cat response.json | jq .result.certificate | perl -npe 's/\\n/\n/g; s/"//g' > ios.pem

# Now ask that the second client certificate signing request be signed.

$ CSR=$(cat sensor.csr | perl -pe 's/\n/\\n/g')
$ request_body=$(< <(cat <<EOF
{
  "validity_days": 3650,
  "csr":"$CSR"
}
EOF
))

$ curl https://api.cloudflare.com/client/v4/zones/{zone_id}/client_certificates \
--header "X-Auth-Email: <EMAIL>" \
--header "X-Auth-Key: <API_KEY>" \
--header "Content-Type: application/json" \
--data "$request_body" | perl -npe 's/\\n/\n/g; s/"//g' > sensor.pem

3. Vložte klientský certifikát do své mobilní aplikace

Chcete-li nakonfigurovat mobilní aplikaci tak, aby bezpečně vyžadovala teplotní data odeslaná zařízením IoT, vložte do mobilní aplikace klientský certifikát.

Pro zjednodušení tento příklad vkládá „bootstrap“ certifikát a klíč do balíčku aplikace jako soubor ve formátu PKCS#12:

$ openssl pkcs12 -export -out bootstrap-cert.pfx -inkey ios-key.pem -in ios.pem
Enter Export Password:
Verifying - Enter Export Password:

V reálném nasazení by se bootstrap certifikát měl používat pouze společně s přihlašovacími údaji uživatele k ověření vůči koncovému bodu API, který dokáže vrátit jedinečný uživatelský certifikát. Firemní uživatelé budou k distribuci certifikátů pravděpodobně chtít využít správu mobilních zařízení (MDM).

Vložení klientského certifikátu do aplikace pro Android

Následující příklad ukazuje, jak lze v aplikaci pro Android použít klientský certifikát k volání přes HTTP. Je třeba přidat následující oprávnění v AndroidManifest.xml a povolte internetové připojení.

<uses-permission android:name="android.permission.INTERNET" />

Pro účely ukázky je certifikát v tomto příkladu uložen v app/src/main/res/raw/cert.pem a privátní klíč je uložený v app/src/main/res/raw/key.pem. Tyto soubory můžete uložit i jiným bezpečným způsobem.

Následující příklad používá OkHttpClient, ale můžete použít i jiné klienty, například HttpURLConnection podobným způsobem. Klíčové je použít SSLSocketFactory.

private OkHttpClient setUpClient() {
    try {
        final String SECRET = "secret"; // You may also store this String somewhere more secure.
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

        // Get private key
        InputStream privateKeyInputStream = getResources().openRawResource(R.raw.key);
        byte[] privateKeyByteArray = new byte[privateKeyInputStream.available()];
        privateKeyInputStream.read(privateKeyByteArray);

        String privateKeyContent = new String(privateKeyByteArray, Charset.defaultCharset())
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replaceAll(System.lineSeparator(), "")
                .replace("-----END PRIVATE KEY-----", "");

        byte[] rawPrivateKeyByteArray = Base64.getDecoder().decode(privateKeyContent);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(rawPrivateKeyByteArray);

        // Get certificate
        InputStream certificateInputStream = getResources().openRawResource(R.raw.cert);
        Certificate certificate = certificateFactory.generateCertificate(certificateInputStream);

        // Set up KeyStore
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, SECRET.toCharArray());
        keyStore.setKeyEntry("client", keyFactory.generatePrivate(keySpec), SECRET.toCharArray(), new Certificate[]{certificate});
        certificateInputStream.close();

        // Set up Trust Managers
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init((KeyStore) null);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        // Set up Key Managers
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(keyStore, SECRET.toCharArray());
        KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();

        // Obtain SSL Socket Factory
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagers, trustManagers, new SecureRandom());
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

        // Finally, return the client, which will then be used to make HTTP calls.
        OkHttpClient client = new OkHttpClient.Builder()
                .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManagers[0])
                .build();

        return client;

    } catch (CertificateException | IOException | NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException | KeyManagementException | InvalidKeySpecException e) {
        e.printStackTrace();
        return null;
    }
}

Výše uvedená funkce vrací OkHttpClient vložený s klientským certifikátem. Tohoto klienta teď můžete použít k odesílání HTTP požadavků na koncový bod API chráněný pomocí mTLS.


4. Vložte klientský certifikát do svého zařízení IoT

Chcete-li připravit zařízení IoT na zabezpečenou komunikaci s koncovým bodem API, vložte do zařízení certifikát a nastavte je tak, aby tento certifikát používalo při odesílání požadavků POST.

Tento příklad předpokládá, že certifikát a soukromý klíč jsou bezpečně zkopírovány do /etc/ssl/private/sensor-key.pem a /etc/ssl/certs/sensor.pem.

Ukázkový skript je upraven tak, aby odkazoval na tyto soubory:

import requests
import json
from datetime import datetime

def readSensor():

    # Takes a reading from a temperature sensor and store it to temp_measurement

    dateTimeObj = datetime.now()
    timestampStr = dateTimeObj.strftime('%Y-%m-%dT%H:%M:%SZ')

    measurement = {'temperature':str(temp_measurement),'time':timestampStr}
    return measurement

def main():

    print("Cloudflare API Shield [IoT device demonstration]")

    temperature = readSensor()
    payload = json.dumps(temperature)

    url = 'https://shield.upinatoms.com/temps'
    json_headers = {'Content-Type': 'application/json'}
    cert_file = ('/etc/ssl/certs/sensor.pem', '/etc/ssl/private/sensor-key.pem')

    r = requests.post(url, headers = json_headers, data = payload, cert = cert_file)

    print("Request body: ", r.request.body)
    print("Response status code: %d" % r.status_code)

Když se skript pokusí připojit k https://shield.upinatoms.com/temps, Cloudflare vyžádá odeslání klientského certifikátu a skript odešle obsah /etc/ssl/certs/sensor.pem. Poté, jak je potřeba k dokončení handshake SSL/TLS, skript prokáže, že disponuje /etc/ssl/private/sensor-key.pem.

Bez klientského certifikátu Cloudflare požadavek odmítne:

Cloudflare API Shield [IoT device demonstration]
Request body:  {"temperature": "36.5", "time": "2020-09-28T15:52:19Z"}
Response status code: 403

Když zařízení IoT předloží platný klientský certifikát, požadavek POST je úspěšný a údaj o teplotě se zaznamená:

Cloudflare API Shield [IoT device demonstration]
Request body:  {"temperature": "36.5", "time": "2020-09-28T15:56:45Z"}
Response status code: 201

5. Povolte mTLS

Po vytvoření certifikátů vydaných Cloudflare je dalším krokem povolte mTLS pro hostitele, které chcete chránit pomocí API Shield.


6. Nakonfigurujte API Shield tak, aby vyžadoval klientské certifikáty

Chcete-li nakonfigurovat API Shield tak, aby vyžadoval klientské certifikáty, vytvořte pravidlo mTLS.