Ouroboros Tutorial 06

From Ouroboros
Jump to navigation Jump to search
Ouroboros Tutorial 06

In the previous tutorials, we told the clients not to check who they were talking to (auth=optional). In this tutorial, we set up certificates, so that the oping client knows it reaches the real oping-server, the oping server knows its clients, and a Layer only admits IPCPs that we gave a certificate. We use the single machine setup of Tutorial 04, and the same debug build, with one extra option that we use in Tutorial 07: -DDEBUG_PROTO_OAP=ON, which prints every message of the flow allocation protocol. We leave these lines out here.

Certificates in Ouroboros

The IRMd looks for certificates under /etc/ouroboros/security:

cacert/                     trusted root certificates (CAs)
untrusted/                  intermediate CA certificates, to complete a chain
server/<name>/crt.pem       certificate the server presents for <name>
server/<name>/key.pem       its private key
server/<name>/sec.conf      the server's security configuration for <name>
client/<name>/crt.pem       certificate a client presents to <name>
client/<name>/key.pem       its private key
client/<name>/sec.conf      the client's security configuration for <name>

The IRMd reads cacert and untrusted when it starts. It reads the files for a name each time a flow for that name is allocated, so we can change them while the IRMd runs.

When a server presents a certificate, the client checks that a trusted CA signed it, and that it was issued for the name the client allocates a flow to: the common name (CN) of the certificate must be that name. When a client presents a certificate, the server checks that a trusted CA signed it. The server does not check the name of the client: the CA decides who gets a certificate.

What each side of a flow allocation presents and checks. The same exchange authenticates the oping flows and the enrollment of an IPCP.

A small PKI

We create a root CA, an intermediate CA signed by the root, and a certificate for each name we need, signed by the intermediate. All keys are ECDSA keys on the P-256 curve. Save the script below as gen-pki.sh:

#!/bin/sh
# Ouroboros Tutorial 06: a root CA, an intermediate CA and leaf certificates.
set -e

key() {
        openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 \
                -out $1.key.pem
}

sign() { # sign <name> <issuer> <extensions>
        openssl req -new -key $1.key.pem -subj "/CN=$1" -out $1.csr
        printf "$3" > $1.ext
        openssl x509 -req -in $1.csr -CA $2.crt.pem -CAkey $2.key.pem \
                -CAcreateserial -days 365 -extfile $1.ext -out $1.crt.pem
        rm $1.csr $1.ext
}

key root
openssl req -new -x509 -key root.key.pem -subj "/CN=Tutorial Root CA" \
        -days 3650 -out root.crt.pem \
        -addext "basicConstraints=critical,CA:TRUE" \
        -addext "keyUsage=critical,keyCertSign,cRLSign"

key intermediate
sign intermediate root \
        "basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,cRLSign\n"

for n in "$@"; do
        key $n
        sign $n intermediate "basicConstraints=CA:FALSE\nkeyUsage=critical,digitalSignature\n"
done

openssl verify -CAfile root.crt.pem -untrusted intermediate.crt.pem *.crt.pem
rm -f *.srl

The sign function puts the name in the CN of the certificate, and marks the key for signing (digitalSignature), which the IRMd requires. Run it in an empty directory, with the names that get a certificate: the server name oping-server, a client that we call oping-client, the Layer name unicast-layer, and a third IPCP, uni-3, that will join the Layer:

$ mkdir pki && cd pki
$ sh ../gen-pki.sh oping-server oping-client unicast-layer uni-3
intermediate.crt.pem: OK
oping-client.crt.pem: OK
oping-server.crt.pem: OK
root.crt.pem: OK
uni-3.crt.pem: OK
unicast-layer.crt.pem: OK

Now install the root CA as a trusted certificate, and the intermediate CA to complete the chains:

$ sudo cp root.crt.pem /etc/ouroboros/security/cacert/
$ sudo cp intermediate.crt.pem /etc/ouroboros/security/untrusted/

Every machine that runs Ouroboros needs these two certificates. The private keys of the CAs, root.key.pem and intermediate.key.pem, are only needed to sign new certificates: keep them away from the machines that run Ouroboros.

The certificates of this tutorial and where they end up by the end of it. A client certificate is stored under the name of the server it is presented to.

Starting the IRMd

We use the configuration file of Tutorial 04, saved as /etc/ouroboros/tut06.conf, and the security configuration of Tutorial 04 for the IPCPs, which do not check each other yet:

$ for n in unicast-layer uni-1; do
>   sudo mkdir -p /etc/ouroboros/security/client/$n
>   printf 'auth=optional\nencryption=none\n' | sudo tee /etc/ouroboros/security/client/$n/sec.conf > /dev/null
> done

For the oping client, we now require authentication. The auth=required line is the default, but we write it down, so it is clear. As the file does not disable encryption, the flow will be encrypted, as in the last part of Tutorial 04:

$ sudo mkdir -p /etc/ouroboros/security/client/oping-server
$ printf 'auth=required\n' | sudo tee /etc/ouroboros/security/client/oping-server/sec.conf

Now start the IRMd, which loads the certificates of the CAs:

$ sudo irmd --stdout --config /etc/ouroboros/tut06.conf
==110158== irmd(DB): Loaded certificate: /etc/ouroboros/security/cacert/root.crt.pem.
==110158== irmd(II): Loaded certificates from /etc/ouroboros/security/cacert.
==110158== irmd(DB): Loaded certificate: /etc/ouroboros/security/untrusted/intermediate.crt.pem.
==110158== irmd(II): Loaded certificates from /etc/ouroboros/security/untrusted.

Authenticating the server

The server has no certificate yet, so the client refuses it:

$ oping -n oping-server -c 1
Failed to allocate flow: -1009.

The IRMd log shows why:

==110158== irmd/oap(DB): [65aceac38e6975c9] Preparing OAP request for oping-server.
==110158== irmd/oap(II): No authentication certificates for oping-server.
==110158== irmd/oap(II): Encryption enabled for oping-server.
==110158== irmd/oap(DB): Processing OAP request for oping-server.
==110158== irmd/oap(II): No authentication certificates for oping-server.
==110158== irmd/oap(II): [65aceac38e6975c9] Negotiated prime256v1 + aes-256-gcm.
==110158== irmd/oap(II): [65aceac38e6975c9] OAP request processed for oping-server.
==110158== irmd/oap(DB): [65aceac38e6975c9] Completing OAP for oping-server.
==110158== irmd/oap(II): [65aceac38e6975c9] Negotiated prime256v1 + aes-256-gcm.
==110158== irmd/oap(EE): [65aceac38e6975c9] Peer did not provide a certificate.
==110158== irmd/oap(EE): [65aceac38e6975c9] Failed to authenticate server.
==110158== irmd(EE): OAP completion failed for oping-server.

The client and the server agree on the encryption first. Only when the client completes the exchange, it finds that the server sent no certificate, and it refuses the flow. Error -1009 is an authentication error. Now give the server its certificate and key:

$ sudo mkdir -p /etc/ouroboros/security/server/oping-server
$ sudo cp oping-server.crt.pem /etc/ouroboros/security/server/oping-server/crt.pem
$ sudo cp oping-server.key.pem /etc/ouroboros/security/server/oping-server/key.pem

and ping again:

$ oping -n oping-server -c 1
Pinging oping-server with 64 bytes of data (1 packets):

64 bytes from oping-server: seq=0 time=2.041 ms

--- oping-server ping statistics ---
1 packets transmitted, 1 received, 0 out-of-order, 0% packet loss, time: 1001.536 ms
rtt min/avg/max/mdev = 2.041/2.041/2.041/NaN ms

The server side of the IRMd loads the certificate, and the client side checks it:

==110158== irmd/oap(DB): [9a6bb1d5abd28d31] Preparing OAP request for oping-server.
==110158== irmd/oap(II): No authentication certificates for oping-server.
==110158== irmd/oap(II): Encryption enabled for oping-server.
==110158== irmd/oap(DB): Processing OAP request for oping-server.
==110158== irmd/oap(II): Loaded authentication certificates for oping-server.
==110158== irmd/oap(II): [9a6bb1d5abd28d31] Negotiated prime256v1 + aes-256-gcm.
==110158== irmd/oap(II): [9a6bb1d5abd28d31] OAP request processed for oping-server.
==110158== irmd/oap(DB): [9a6bb1d5abd28d31] Completing OAP for oping-server.
==110158== irmd/oap(II): [9a6bb1d5abd28d31] Negotiated prime256v1 + aes-256-gcm.
==110158== irmd/oap(DB): [9a6bb1d5abd28d31] Successfully verified peer crt.
==110158== irmd/oap(DB): [9a6bb1d5abd28d31] Successfully authenticated peer.
==110158== irmd/oap(II): [9a6bb1d5abd28d31] OAP completed for oping-server.

The client verified the certificate against the CAs (verified peer crt), and the server's signature on its reply, which shows that the server holds the private key of the certificate (authenticated peer). It also checked that the certificate is for oping-server. If the certificate was issued for another name, the client refuses it with Peer crt for '<CN>' does not match 'oping-server', and if no trusted CA signed it, with Failed to verify peer with CA store.

Authenticating the client

A server can also require its clients to present a certificate. Put auth=required in the server's security configuration for oping-server:

$ printf 'auth=required\n' | sudo tee /etc/ouroboros/security/server/oping-server/sec.conf

The client has no certificate, so the server now refuses the client:

$ oping -n oping-server -c 1
Failed to allocate flow: -1009.
==110158== irmd/oap(DB): [e1e912ef1f2c6c84] Preparing OAP request for oping-server.
==110158== irmd/oap(II): No authentication certificates for oping-server.
==110158== irmd/oap(II): Encryption enabled for oping-server.
==110158== irmd/oap(DB): Processing OAP request for oping-server.
==110158== irmd/oap(II): Loaded authentication certificates for oping-server.
==110158== irmd/oap(II): Encryption enabled for oping-server.
==110158== irmd/oap(EE): [e1e912ef1f2c6c84] Peer did not provide a certificate.
==110158== irmd/oap(EE): [e1e912ef1f2c6c84] Failed to authenticate client.
==110158== irmd(EE): OAP processing failed for oping-server.
==110158== irmd(WW): Allocation rejected: oping-server (-1009).

Give the client the oping-client certificate for flows to oping-server:

$ sudo cp oping-client.crt.pem /etc/ouroboros/security/client/oping-server/crt.pem
$ sudo cp oping-client.key.pem /etc/ouroboros/security/client/oping-server/key.pem

and the ping works again:

$ oping -n oping-server -c 1
Pinging oping-server with 64 bytes of data (1 packets):

64 bytes from oping-server: seq=0 time=2.581 ms

--- oping-server ping statistics ---
1 packets transmitted, 1 received, 0 out-of-order, 0% packet loss, time: 1002.101 ms
rtt min/avg/max/mdev = 2.581/2.581/2.581/NaN ms

Now both sides load a certificate, and both check the other:

==110158== irmd/oap(DB): [06772ccc9b18ec84] Preparing OAP request for oping-server.
==110158== irmd/oap(II): Loaded authentication certificates for oping-server.
==110158== irmd/oap(II): Encryption enabled for oping-server.
==110158== irmd/oap(DB): Processing OAP request for oping-server.
==110158== irmd/oap(II): Loaded authentication certificates for oping-server.
==110158== irmd/oap(II): Encryption enabled for oping-server.
==110158== irmd/oap(DB): [06772ccc9b18ec84] Successfully verified peer crt.
==110158== irmd/oap(DB): [06772ccc9b18ec84] Successfully authenticated peer.
==110158== irmd/oap(II): [06772ccc9b18ec84] Negotiated prime256v1 + aes-256-gcm.
==110158== irmd/oap(II): [06772ccc9b18ec84] OAP request processed for oping-server.
==110158== irmd/oap(DB): [06772ccc9b18ec84] Completing OAP for oping-server.
==110158== irmd/oap(II): [06772ccc9b18ec84] Negotiated prime256v1 + aes-256-gcm.
==110158== irmd/oap(DB): [06772ccc9b18ec84] Successfully verified peer crt.
==110158== irmd/oap(DB): [06772ccc9b18ec84] Successfully authenticated peer.
==110158== irmd/oap(II): [06772ccc9b18ec84] OAP completed for oping-server.

The client certificate lives under the name of the server: a client can present a different certificate to each server it talks to.

Joining a Layer

IPCPs use the same flow allocation as applications, so the same certificates decide which IPCPs can join a Layer. When an IPCP enrolls, it allocates a flow to the Layer name, and an IPCP in the Layer accepts it. So the members of the Layer present a certificate for the Layer name, and we require a certificate from every IPCP that enrolls:

$ sudo mkdir -p /etc/ouroboros/security/server/unicast-layer
$ sudo cp unicast-layer.crt.pem /etc/ouroboros/security/server/unicast-layer/crt.pem
$ sudo cp unicast-layer.key.pem /etc/ouroboros/security/server/unicast-layer/key.pem
$ printf 'auth=required\n' | sudo tee /etc/ouroboros/security/server/unicast-layer/sec.conf

and the enrolling IPCPs check that they enroll with the real Layer:

$ printf 'auth=required\n' | sudo tee /etc/ouroboros/security/client/unicast-layer/sec.conf

Now create a third IPCP, uni-3, attach it to lo, and enroll it:

$ irm ipcp create name uni-3 type unicast
$ irm ipcp poa attach name uni-3 eth dev lo
$ irm ipcp enroll name uni-3 layer unicast-layer autobind eth dev lo
Failed to enroll IPCP.

uni-3 has no certificate, so the member that answered refuses it:

==110158== irmd/oap(DB): [4e1eb8e72a15b6a8] Preparing OAP request for unicast-layer.
==110158== irmd/oap(II): No authentication certificates for unicast-layer.
==110158== irmd/oap(II): Encryption enabled for unicast-layer.
==110158== irmd/oap(DB): Processing OAP request for unicast-layer.
==110158== irmd/oap(II): Loaded authentication certificates for unicast-layer.
==110158== irmd/oap(II): Encryption enabled for unicast-layer.
==110158== irmd/oap(EE): [4e1eb8e72a15b6a8] Peer did not provide a certificate.
==110158== irmd/oap(EE): [4e1eb8e72a15b6a8] Failed to authenticate client.
==110158== irmd(EE): OAP processing failed for unicast-layer.
==110158== irmd(EE): Could not enroll IPCP 110347.

Give uni-3 its certificate for the Layer, and enroll again:

$ sudo cp uni-3.crt.pem /etc/ouroboros/security/client/unicast-layer/crt.pem
$ sudo cp uni-3.key.pem /etc/ouroboros/security/client/unicast-layer/key.pem
$ irm ipcp enroll name uni-3 layer unicast-layer autobind eth dev lo

This time, both sides authenticate each other, and uni-3 enrolls:

==110158== irmd/oap(DB): [53e933c8d7b1197c] Preparing OAP request for unicast-layer.
==110158== irmd/oap(II): Loaded authentication certificates for unicast-layer.
==110158== irmd/oap(II): Encryption enabled for unicast-layer.
==110158== irmd/oap(DB): Processing OAP request for unicast-layer.
==110158== irmd/oap(II): Loaded authentication certificates for unicast-layer.
==110158== irmd/oap(II): Encryption enabled for unicast-layer.
==110158== irmd/oap(DB): [53e933c8d7b1197c] Successfully verified peer crt.
==110158== irmd/oap(DB): [53e933c8d7b1197c] Successfully authenticated peer.
==110158== irmd/oap(II): [53e933c8d7b1197c] Negotiated prime256v1 + aes-256-gcm.
==110158== irmd/oap(II): [53e933c8d7b1197c] OAP request processed for unicast-layer.
==110158== irmd/oap(DB): [53e933c8d7b1197c] Completing OAP for unicast-layer.
==110158== irmd/oap(II): [53e933c8d7b1197c] Negotiated prime256v1 + aes-256-gcm.
==110158== irmd/oap(DB): [53e933c8d7b1197c] Successfully verified peer crt.
==110158== irmd/oap(DB): [53e933c8d7b1197c] Successfully authenticated peer.
==110158== irmd/oap(II): [53e933c8d7b1197c] OAP completed for unicast-layer.
==110158== irmd(II): Enrolled IPCP 110347 in layer unicast-layer.

Here, all IPCPs share the security directory of one machine. On separate machines, each machine that runs a member of the Layer needs the server files for unicast-layer, and each machine that runs an IPCP that joins needs its own certificate in the client files for unicast-layer. The connections between members, to IPCP names such as uni-1, work the same way, with files for those names.

Tutorial 07 looks at the messages that carry the certificates, and at the other options of the security configuration.

Notes