This library provides an implementation of Oak end-to-end encrypted attested session between the communicating parties, e.g., between Oak enclaves and user devices or other Oak enclaves. This session is implemented using the Noise Protocol Framework in combination with remote attestation. It offers features such as bidirectional attestation, streaming sessions, and endorsement/evidence client-side caching.
- Attestation Verification: The library allows validating the attestation evidence signed by the hardware to ensure the authenticity and integrity of the peer execution environment. Additionally the attestation verification library provides the ability to rely on an attestation verification service that acts as a certificate authority.
- Noise Handshake: We rely on the Noise protocol framework to perform the handshake and establish a secure communication channel between parties. A number of Noise Patterns are supported.
- Session Binding: Each attesting party signs a session handshake token derived from the handshake transcript hash with the dedicated binding keyset derived from the attestation root of trust. This ensures that the attestation is securely bound to the session and cannot be reused in replay attacks.
- Support for unreliable transport: The implementation can be adapted to handle unreliable connections (e.g., UDP) by incorporating sequence numbers, nonce customization, and message duplication prevention mechanisms.
The SDK comprises two interacting parties: the Client (initiator) and the Server
(responder). The core component is a Session object that manages the handshake
process and facilitates message encryption/decryption upon successful handshake
completion. The Session object functions as a state machine, offering read
and write functions for data messages and
put_incoming_message/get_outgoing_message functions for protocol messages
that can be passed to and from the transport layer.
The session traverses through 3 distinct lifecycle states that represent the steps involved in the establishment of a session:
ATTESTATION: Exchange of the endorsed attestation evidence and its verificationHANDSHAKE: Noise Protocol handshake and binding of the session handshake transcript to the attestation evidenceOPEN: Session is open for secure communication
The Session always starts in ATTESTATION state, progresses to HANDSHAKE and
finally reaches the OPEN state. Transition to the next state happens only
after a put_incoming_message or get_outgoing_message call.
The following sequence diagram illustrates the sequence of messages that lead to an established session, and how the session is then used to implement a simple request/response unary RPC. The API and interactions between actor and transport should be considered as example placeholders for illustrative purposes. Please refer to the documentation of your chosen transport when integrating.
sequenceDiagram
autonumber
box Client side
actor C as Client
participant CS as ClientSession
participant CT as ClientTransport
end
box Server side
participant ST as ServerTransport
participant SS as ServerSession
actor S as Server
end
C->>CT: open
activate CT
CT--)ST: open channel (e.g. GRPC BiDi stream)
Note over CS,SS: ATTESTATION
CT->>+CS: get_outgoing_message
CS-->>-CT: message
CT--)ST: send(message)
deactivate CT
activate ST
ST->>+SS: put_incoming_message(message)
SS-->>-ST: ok
ST->>+SS: get_outgoing_message()
SS-->>-ST: message
ST--)CT: send(message)
deactivate ST
activate CT
CT->>+CS: put_incoming_message(message)
CS-->>-CT: ok
Note over CS,SS: HANDSHAKE
CT->>+CS: get_outgoing_message
CS-->>-CT: message
CT--)+ST: send(message)
deactivate CT
activate ST
ST->>+SS: put_incoming_message(message)
SS-->>-ST: ok
ST->>+SS: get_outgoing_message()
SS-->>-ST: message
ST--)CT: send(message)
deactivate ST
activate CT
CT->>+CS: put_incoming_message(message)
CS-->>-CT: ok
opt Binding followup
Note right of CT: If the client session had provided attestations,<br/>an binding follow-up message is required.<br/>See `Handshake flow` below for more details.
CT->>+CS: get_outgoing_message()
CS-->>-CT: message
CT--)ST: send(message)
activate ST
ST->>+SS: put_incoming_message(message)
SS-->>-ST: ok
deactivate ST
end
CT->>C: on_open
deactivate CT
Note over CS,SS: OPEN
C->>CT: write(request)
activate CT
CT->>+CS: write(request)
CS-->>-CT: ok
CT->>+CS: get_outgoing_message()
CS-->>-CT: message
CT--)ST: send(message)
deactivate CT
activate ST
ST->>+SS: put_incoming_message(message)
SS-->>-ST: ok
ST->>+SS: read()
SS-->>-ST: request
ST->>S: receive(request)
deactivate ST
S->>ST: write(response)
activate ST
ST->>+SS: write(response)
SS-->>-ST: ok
ST->>+SS: get_outgoing_message()
SS-->>-ST: message
ST--)CT: send(message)
deactivate ST
activate CT
CT->>+CS: put_incoming_message(message)
CS-->>-CT: ok
CT->>+CS: read()
CS-->>-CT: response
CT->>C: receive(response)
deactivate CT
The underlying implementation of Session objects involves a number of collaborating roles:
Attester: supplies attestation evidence.Endorser: supplies an attestation endorsement of the evidence provided by theAttester.AttestationVerifier: consumes the attestation evidence and its endorsement verifying their authenticity.SessionBinder: This creates a binding between the supplied endorsed evidence and the session using the session handshake transcript hash.SessionBindingVerifier: The binding of the handshake hash is verified against the peer's own handshake hash.
The Encrypted Session SDK supports the use of multiple concurrent attestation
implementations under different ATTESTATION_ID keys. Each implementation is
composed of one of each Attester, Endorser, AttestationVerifier,
SessionBinder and SessionBindingVerifier.
During the ATTESTATION state , the AttestationHandler, obtains endorsed
evidence by calling the Attester/Endorser implementation. Conversely, it
invokes the AttestationVerifier validation logic for any received evidence.
When multiple attestations are provided by the peer, an additional
AttestationAggregator role is used to combine them into a single pass/fail
verdict.
The following diagram depicts the interaction between the two attestation
providers. The client is configured in PeerUnidirectional mode, and the server
is configured in SelfUnidirectional mode. Only one mode of attestation is
configured so there is no need for an AttestationAggregator.
sequenceDiagram
autonumber
box Client side
participant AV as AttestationVerifier
participant CAP as ClientAttestationHandler
end
box Server side
participant SAP as ServerAttestationHandler
participant A as Attester
participant E as Endorser
end
Note over CAP,SAP: Interaction is mediated by the Session<br/> and a lower-level transport, e.g., GRPC
CAP--)+SAP: attestation request
SAP->>+A: quote()
A-->>-SAP: evidence
SAP->>+E: endorse(evidence)
E-->>-SAP: endorsements
SAP--)-CAP: attestation response (evidence, endorsements)
CAP->>AV: verify(evidence, endorsements)
AV-->>CAP: result
Note over CAP: Attestation result is<br/> passed to HandshakeHandler
The following diagram contains the flow for bidirectional attestation flow. In bidirectional attestation both the client and the server produce endorsed evidence, and they both verify their peer's. The client transmits its evidence as part of the initial request to the server. The server only replies with its own evidence if it is able to verify the client attestation.
sequenceDiagram
autonumber
box Client side
participant CAV as AttestationVerifier
participant CE as Endorser
participant CA as Attester
participant CAP as ClientAttestationHandler
end
box Server side
participant SAP as ServerAttestationHandler
participant SA as Attester
participant SE as Endorser
participant SAV as AttestationVerifier
end
Note over CAP,SAP: Interaction is mediated by the Session<br/> and a lower-level transport, e.g., GRPC
CAP->>+CA: quote()
CA-->>-CAP: client_evidence
CAP->>+CE: endorse(client_evidence)
CE-->>-CAP: client_endorsements
CAP--)+SAP: attestation request (client_evidence, client_endorsements)
SAP->>SAV: verify(client_evidence, client_endorsements)
SAV-->>SAP: result
SAP->>+SA: quote()
SA-->>-SAP: server_evidence
SAP->>+SE: endorse(server_evidence)
SE-->>-SAP: server_endorsements
SAP--)-CAP: attestation response (server_evidence, server_endorsements)
CAP->>CAV: verify(server_evidence, server_endorsements)
CAV-->>CAP: result
Note over CAP, SAP: Attestation result is<br/> passed to HandshakeHandler
Oak Session allows the clients to configure multiple attesters and corresponding attestation verifiers to use during the attestation step of the session initialization. Each attester and the corresponding verifier are identified by a unique attestation type ID string and are assumed to produce/consume the same type of evidence.
The evidence that is being exchanged by the parties is contained in the EndorsedEvidence message. It is included in theAttestRequest/AttestResponse messages in a map keyed by the attestation type ID. The verifying party uses the attestation type ID to identify the evidence to be consumed by its attestation verifiers. Each verifier that has received matching evidence produces AttestationResults. To reach the final attestation verdict individual attestation results are passed to the AttestationAggregator component that makes the decision on whether the attestation step succeeded or failed and passes it with the accompanying metadata to the session.
During the HANDSHAKE state, the HandshakeHandler initiates the Noise session
proper.Once the handshake is complete each party is in possession of two shared
symmetric keys; one can be used to encrypt the outgoing traffic, the other one
to decrypt incoming messages.
In order to bind the established secure session to the previously verified attestation we require each party follow up the handshake with signing a handshake token (a byte string derived from the handshake transcript and so uniquely identifying the session) with the binding keyset. The public key for the binding keyset must be included in the attestation evidence. The verifying peer configures SessionBindingVerifier at their end to extract the key and verify the binding signature. This confirms that the handshake was performed with the same entity that issued the attestation.
- For the unidirectional attestation use case where only the responder needs to be attested we append the signature to the last message of the handshake to avoid an unnecessary round trip
- The client sends their binding signatured in a separate follow up message
- If multiple attesters are used, each of them must include its own separate
binding key in the evidence.
- Session binding signatures are exchanged in HandshakeRequest/HandshakeResponse messages, in a map keyed by the attestation type ID, in the HandshakeRequest for the initiator or the HandshakeResponse for the responder.
The following diagram shows the handshake sequence. The client is configured in
PeerUnidirectional mode, and the server is configured in SelfUnidirectional
mode. Only one mode of attestation is configured.
sequenceDiagram
autonumber
box Client side
participant CSV as SessionBindingVerifier
participant CN as Noise Protocol
participant CH as ClientHandshakeHandler
end
box Server side
participant SH as ServerHandshakeHandler
participant SN as Noise Protocol
participant SSB as SessionBinder
end
CH->>+CN: build_initial_message()
CN-->>-CH: noise_message
CH--)SH: handshake request (noise_message)
SH->>+SN: get_noise_response(noise_message)
SN-->>-SH: noise_response, handshake_hash, session_key
SH->>+SSB: bind(handshake_hash)
SSB-->>-SH: binding
SH--)CH: handshake response (noise_message, binding)
CH->>+CN: process_response (noise_message)
CN-->>-CH: handshake_hash, session_key
CH->>+CSV: verify_binding (binding)
CSV-->>-CH: result
Note over CH,SH: Session Keys are extracted<br/>and handed over to the Encryptor
The following diagram contains the flow for bidirectional attestation. In bidirectional attestation both the client and the server produce bindings, and the client has to send one more response to the server with its bindings.
sequenceDiagram
autonumber
box Client side
participant CSV as SessionBindingVerifier
participant CSB as SessionBinder
participant CN as Noise Protocol
participant CH as ClientHandshakeHandler
end
box Server side
participant SH as ServerHandshakeHandler
participant SN as Noise Protocol
participant SSB as SessionBinder
participant SSV as SessionBindingVerifier
end
CH->>+CN: build_initial_message()
CN-->>-CH: noise_message
CH--)SH: handshake request (noise_message)
SH->>+SN: get_noise_response(noise_message)
SN-->>-SH: noise_response, handshake_hash, session_key
SH->>+SSB: bind(handshake_hash)
SSB-->>-SH: server_binding
SH--)CH: handshake response (noise_message, server_binding)
CH->>+CN: process_response (noise_message)
CN-->>-CH: handshake_hash, session_key
CH->>+CSV: verify_binding (server_binding)
CSV-->>-CH: result
CH->>+CSB: bind(handshake_hash)
CSB-->>-CH: client_binding
CH--)SH: followup request (client_binding)
SH->>+SSV: verify(client_binding)
SSV-->>-SH: result
Note over CH,SH: Session Keys are extracted<br/>and handed over to the Encryptor
Finally, once the session is in OPEN state, the Encryptor consumes the
session key obtained by the HandshakeHandler. This object handles the
encryption and decryption of application messages.
To use this library, follow these steps:
-
Create a
Configobject specifying the desired features of the session-
Select the attestation type, for example, SelfUnidirectional
-
Select the desired Noise handshake pattern, for example, NoiseNN
-
The attesting party must configure the following objects for each supported attestation type:
let config = SessionConfig::builder(AttestationType::SelfUnidirectional, HandshakeType::NoiseNN) .add_self_attester(attester_id.clone(), Box::new(my_attester)) .add_self_endorser(attester_id.clone(), Box::new(my_endorser)) .add_session_binder(attester_id.clone(), Box::new(my_session_binder)) .build();
-
The verifying party configures the following components:
AttestationVerifierSessionBindingVerifier- KeyExtractor can be used instead of providing SessionBindingVerifier directly. In this case it is used to extract the binding key from the corresponding evidence. The key is then used to construct SignatureBindingVerifier to verify the binding
- If neither SessionBindingVerifier nor KeyExtractor is provided for a given attestation verifier DefaultSigningKeyExtractor is used in its place. It takes the key from the top level signing_public_key field in AttestationResults
let client_config = SessionConfig::builder(AttestationType::PeerUnidirectional, HandshakeType::NoiseNN) .add_peer_verifier_with_key_extractor( attester_id.clone(), Box::new(my_attestation_verifier), Box::new(DefaultBindingKeyExtractor {}), ) .build();
-
-
Use the configuration to create a
Sessionobject (ClientSessionfor the initiator of the connection,ServerSessionfor the responder) -
Use
get_outgoing_messageandput_incoming_messageto exchange the attested evidence and handshake messages until the session becomes open -
Once
is_openbecomes true, the session is ready for use. You can usereadandwritefor plaintext messages and exchange the encrypted messages returned byget_outgoing_messageover the transport layer.
If one or both parties possess a static key where the public component is known by its peer (perhaps through the use of PKI), it is possible to use it in the handshake by picking a Noise NK or KK scheme.
The default OrderedChannelEncryptor includes consecutive nonces in the
encrypted messages and so protects from replay, reorder and packet drop attacks.
This presumes that the order of the messages is preserved by the communication
channel. To support use cases where this is infeasible we allow picking a
different encryptor or implementing your own. It is important in this case that
the user of the SDK implements alternative measures to protect from network
attacks.
While the primitives that we provide assume that attestation will be exchanged
and verified separately for each established session, it is possible to optimize
this step by implementing your own AttestationVerifier and, optionally,
Attester to cache the accepted attestation.