owned this note
owned this note
Published
Linked with GitHub
# Pluto - Origo golang networking code and Tracy's TLS client notes
structure and added comments
## Modeling JJ's TLS client cont
- fork divergence analysis
- JJ's changes since oldest commit
### diff repl?
- in [tls:](https://github.com/golang/go/tree/master/src/crypto/tls) `git checkout f62c9701b4`
- in [tls-fork](https://github.com/opex-research/tls-fork), check out oldest commit: `b353f85`
- diff: 42k lines of diff.
- 14:03 sitting down, end at 1430
- oh. matt diff'd the [client](https://github.com/pluto/origo-playground/blob/main/tls-fork-client.diff) and [proxy](https://github.com/pluto/origo-playground/blob/main/tls-fork-proxy.diff), not the whole repo.
- 14:35 getting distracted, take break
### 14:52 back from break. Look what files they're primarily changing in their repo.
note differences in repo structure. They add a few dirs:
- 3phs_material - their pictures and some pdf presentations and stack exchange questions.
- ecdh - copied from `src/crypto/ecdh`
- tlsinternal - copied from `src/crypto/internal`
and files:
- new_crypto_utils and tests
- new_ectf
after making adjustments, we are down to a 4k line diff between tls and oldest tls-fork.
After a couple more adjustments, we have a 600 line diff.
## 15:06 JJ's fork initial changes analysis
### setup
After a couple adjustments, just a 600 line diff between:
- in [tls:](https://github.com/golang/go/tree/master/src/crypto/tls) `git checkout f62c9701b4` - thanks matt for the commit hash
- in [tls-fork](https://github.com/opex-research/tls-fork), check out oldest commit: `b353f85`, remove 3phs_material and copy `ecdh` and `tls/internal` into the fork.
### findings
- cipher_suites Remove AESGCM hardware support.
- conn - big changes, added connection methods
- SetSecret, GecSecretMap - helpers to save and access key information in the `Conn`
- ShowRecordmap, GetRecordMap - save the the payload, typ, etc.
- RecordMeta struct (type) added, and included in the connection object
- decrypt updated to:
- save the SF message Record metadata
- accept a handshakeComplete bool - used to capture post handshake traffic - SR
- handshake_client
- `makeClientHello` - a private key is added, with a comment about Janus
- some logic for injecting the janus proxy key share
- adjustments per janus key shares
- Set secret repeatedly called for each secret key that is saved
- handshake_client_tls13
- logic around ecdh key ajdusted
- ES, dES, HS, etc secrets set
- these are all related to witness gen
- key_schedule - minor changes, dummy getter added: `ellipticCurveForCurveID`
- internal/edwards2555
## JJ's fork subsequent code changes
In tls-fork: `git difft b353f85 client` - reduced to about 1757 significant line diff, and counting
- did they make these assembly changes in `tlsinternal/nistec/p256_asm_s380x.s` and `p256_asm_arm64.s`?
tbc
## annotated `Call` and `Store` methods in golang codebase
##### Calling code
```go
// handle a request to the proxy, if hsonly is set, stop after handshake,
// otherwise make a round trip request to the server
func handleRequest(hsonly bool, serverDomain string, serverEndpoint string, proxyListenerURL string) {
// create the and send the request
req := r.NewRequest(serverDomain, serverEndpoint, proxyListenerURL)
data, err := req.Call(hsonly)
if err != nil {
log.Error().Msg("req.Call()")
}
// if a server request was made (not handshake-only)
// store the server response to file storage
if !hsonly {
err = req.Store(data)
if err != nil {
log.Error().Msg("req.Store(data)")
}
}
}
```
##### call
```go
// provided:
// RequestTLS contents:
// ServerDomain: serverDomain, (cli arg)
// ServerPath: serverPath, (cli arg) "testserver.origodata.io"
// ProxyURL: proxyURL, (cli arg)
// UrlPrivateParts: "",
// AccessToken: "",
// StorageLocation: "./local_storage/",
// hsOnly - whether to stop after handshake
func (r *RequestTLS) Call(hsOnly bool) (RequestData, error) {
// (1) Handshake
// (1.1) TLS set up
// TLS config for Go TLS library bindings
config := &tls.Config{
InsecureSkipVerify: false,
CurvePreferences: []tls.CurveID{tls.CurveP256},
PreferServerCipherSuites: false,
MinVersion: tls.VersionTLS13,
MaxVersion: tls.VersionTLS13,
CipherSuites: []uint16{
tls.TLS_AES_128_GCM_SHA256,
},
ServerName: r.ServerDomain,
}
// (1.2) local server testing settings
if r.ServerDomain == "localhost" {
PathCaCrt := "certs/certificates/ca.crt"
// set up cert verification
caCert, _ := ioutil.ReadFile(PathCaCrt)
caCertPool, _ := x509.SystemCertPool()
caCertPool.AppendCertsFromPEM(caCert)
config.RootCAs = caCertPool
r.ServerDomain += ":8081" // TODO(matt) should be CLI flag
}
// measure start time
start := time.Now()
// (1.3) tls connection - TCP "Dial" the Proxy, get a connection object
conn, err := tls.Dial("tcp", r.ProxyURL, config)
if err != nil {
log.Error().Err(err).Msg("tls.Dial()")
return RequestData{}, err
}
defer conn.Close()
// tls handshake time
elapsed := time.Since(start)
log.Debug().Str("time", elapsed.String()).Msg("client tls handshake took.")
// state := conn.ConnectionState()
// return here if handshakeOnly flag set
// (1.4) hsOnly toggle breakpoint
if hsOnly {
return RequestData{}, nil
}
// (2) server round trip
// (2.1) construct server URL
// server settings
serverURL := "https://" + r.ServerDomain + r.ServerPath
if r.UrlPrivateParts != "" {
serverURL += r.UrlPrivateParts
}
// measure request-response roundtrip
start = time.Now()
// (2.2) construct HTTP GET request for server using http client lib
// build request
request, err := http.NewRequest(http.MethodGet, serverURL, nil)
if err != nil {
panic(err)
}
// (2.2.1) don't close the connection immediately? Why not?
request.Close = false
// (2.2.2) set the headers on the request
// request headers
request.Header.Set("Content-Type", "application/json")
if r.AccessToken != "" {
request.Header.Set("Authorization", "Bearer "+r.AccessToken)
}
// (2.3) (go-API-specific) initialize connection buffers for the TLS connection and send the request
bufr := bufio.NewReader(conn)
bufw := bufio.NewWriter(conn)
// (2.3.1) send a request over the connection, write to buffer
// write request to connection buffer
err = request.Write(bufw)
if err != nil {
log.Error().Err(err).Msg("request.Write(bufw)")
return RequestData{}, err
}
// (2.3.2) close and flush the connection, write the rest to buffer
// writes buffer data into connection io.Writer
err = bufw.Flush()
if err != nil {
log.Error().Err(err).Msg("bufw.Flush()")
return RequestData{}, err
}
// (2.3.3) write the server response to the read buffer
// read response
resp, err := http.ReadResponse(bufr, request)
if err != nil {
log.Error().Err(err).Msg("http.ReadResponse(bufr, request)")
return RequestData{}, err
}
// (2.3.4) close and flush the request buffer?
defer resp.Body.Close()
// (2.4) reads response body
msg, _ := ioutil.ReadAll(resp.Body)
log.Trace().Msg("response data:")
log.Trace().Msg(string(msg))
// catch time
elapsed = time.Since(start)
log.Debug().Str("time", elapsed.String()).Msg("client request-response roundtrip took.")
// (3) return connection data (specific to the go client library)
// access to recorded session data
return RequestData{
secrets: conn.GetSecretMap(),
recordMap: conn.GetRecordMap(),
}, nil
}
```
##### store
```go
// takes RequestData, generated by the call method
func (r *RequestTLS) Store(data RequestData) error {
// (1) store RequestData as a json object (smelly code)
// (1.1) make custom mutable json data objects
jsonData := make(map[string]map[string]string)
jsonData["keys"] = make(map[string]string)
// (1.2) dump the keys from data into the json object
// and hex-encode the secrets
for k, v := range data.secrets {
jsonData["keys"][k] = hex.EncodeToString(v)
}
// (1.3) dump the record data into the json object
// hex encode the payloads, additionalData, and ciphertext
for k, v := range data.recordMap {
jsonData[k] = make(map[string]string)
jsonData[k]["typ"] = v.Typ
jsonData[k]["additionalData"] = hex.EncodeToString(v.AdditionalData)
jsonData[k]["payload"] = hex.EncodeToString(v.Payload)
jsonData[k]["ciphertext"] = hex.EncodeToString(v.Ciphertext)
}
// (1.4) marshall the json object into a json file
file, err := json.MarshalIndent(jsonData, "", " ")
if err != nil {
log.Error().Err(err).Msg("json.MarshalIndent")
return err
}
// (1.5) write the json to some location in file storage
err = ioutil.WriteFile(r.StorageLocation+"session_params_13.json", file, 0644)
if err != nil {
log.Error().Err(err).Msg("ioutil.WriteFile")
}
return err
}
```
#### Tracy's code
*Note that Tracy started with the web-assembly translation of the golang request code.*
Denoting divergence from the golang codebase with lettering notation e.g.
- (1.1) for mirrored code
- (1.a) for novel introduced code
```rust
#[tokio::main]
async fn main() -> io::Result<()> {
env_logger::init();
// (1) Handshake
// (1.1) TLS set up - see below
// (1.2.a) Load the root certificates.
// noting that this code looks significantly more involved (and imperative) than it likely needs to be
let mut root_cert_store = RootCertStore::empty();
root_cert_store.add_server_trust_anchors( webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
tls_client::OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject.as_ref(),
ta.subject_public_key_info.as_ref(),
ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
)
}));
// (1.2.b) load the local root certificate from local storage
// and add it to the root cert store
const LOCALHOST_DEBUG_CA_CERT: &[u8] = include_bytes!("../../certs/ca-cert.cer");
let cert = CertificateDer::from(LOCALHOST_DEBUG_CA_CERT.to_vec());
let (added, _) = root_cert_store.add_parsable_certificates(&[cert.to_vec()]);
assert_eq!(added, 1);
// (1.2.c) configure localhost as proxy destination
// which is provided in golang code by RequestTLS
let target_host = "localhost";
let target_port = "8080";
// noting overly complex code smell for constructing the proxy_url
let mut proxy_url = Url::parse("wss://localhost:8070/v1").expect("Failed to parse target_url");
proxy_url.query_pairs_mut().append_pair("target_host", &target_host);
proxy_url.query_pairs_mut().append_pair("target_port", &target_port);
// Hacky adapter to connect websocket to proxy with TLS.
use async_tls::TlsConnector;
use async_tungstenite::async_std::connect_async_with_tls_connector;
use rustls::{RootCertStore as TLSRootCertStore, ClientConfig};
use ws_stream_tungstenite::WsStream;
// (1.2.c) construct ANOTHER root CA
// with sprinkled invocation of web-socket magicks
let mut ws_cert_store = TLSRootCertStore::empty();
let (added, _) = ws_cert_store.add_parsable_certificates(&[cert.to_vec()]);
assert_eq!(added, 1);
// (1.1) construct a websocket http client with `ws_cert_store` as CA
// (1.1.a) what do these settings do?
let config = ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(ws_cert_store)
.with_no_client_auth();
// (1.1.b) create the TlsConnector, assumed to be similar to an HTTP client
let connector = TlsConnector::from(Arc::new(config));
// (1.3.a) tls connection - Dial the proxy via websockets
// create the response as a websocket
let (ws_stream, _) = connect_async_with_tls_connector(proxy_url, Some(connector)).await.unwrap();
let ws = WsStream::new( ws_stream );
println!("Connected to proxy via TCP socket: {:?}", ws);
// (1.1.a) tls connection - duplicate the tls client config
let config = tls_client::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
use tls_client::{ClientConnection, ServerName, RustCryptoBackend13};
use tls_proxy::OrigoConnection;
use tls_client_async::bind_client;
// (1.3.b) construct connection from abstracted "OrigoConnection"
// what lives in OrigoConnection?
// scary note: many complexity demon live in this invocation
let origo_conn = OrigoConnection::new();
let tls_client = ClientConnection::new(
Arc::new(config),
Box::new(RustCryptoBackend13::new(origo_conn)),
ServerName::try_from(target_host).unwrap(),
)
.unwrap();
// (1.3.c) construct connection over the ws client binding?
let (conn, conn_fut) = bind_client(ws, tls_client);
let conn_task = tokio::spawn(conn_fut);
let tls_connection = TokioIo::new(conn.compat());
println!("Connected to server via TLS: {:?}", tls_connection);
// (1.3) dial the proxy for the handshake
// Attach the hyper HTTP client to the TLS connection
let (mut request_sender, connection) = hyper::client::conn::http1::handshake(tls_connection)
.await
.unwrap();
// (1.3.d) code smell - tokio task spawn never awaited
// Spawn the HTTP task to be run concurrently
tokio::spawn(connection);
println!("HTTP task spawned");
// (1.4) - hsOnly toggle - dropped
// (2) server round trip
// (2.2) summon a new http client from the above complexity demon
// and construct a get request
use http_body_util::Full;
use hyper::{body::Bytes, Request, Version};
let mut request = Request::builder()
.method("GET")
.uri("/")
.version(Version::HTTP_10);
let headers = request.headers_mut().unwrap();
// (2.2.2) set headers on request
// but entirely different ones from the ones in the origo codebase
// recall:
// ```go
// request.Header.Set("Content-Type", "application/json")
// if r.AccessToken != "" {
// request.Header.Set("Authorization", "Bearer "+r.AccessToken)
// }
// ```
// Using "identity" instructs the Server not to use compression for its HTTP
// response. TLSNotary tooling does not support compression.
headers.insert("Host", target_host.parse().unwrap());
headers.insert("Accept-Encoding", "identity".parse().unwrap());
headers.insert("Connection", "close".parse().unwrap());
if headers.get("Accept").is_none() {
headers.insert("Accept", "*/*".parse().unwrap());
}
// (2.2.a) fill the request body
// is this reasonable, or sloppy use of http request construction api?
let body = Full::new(Bytes::from(vec![])); // TODO Empty::<Bytes>::new()
let request = request.body(body).unwrap();
// (2.3) send the request, record if received 2xx status code response
println!("Sending request: {:?}", request);
match request_sender.send_request(request).await {
Ok(response) => {
println!("Response: {:?}", response.status());
assert!(response.status().is_success()); // status is 200-299
println!("Respose: OK");
}
Err(e) if e.is_incomplete_message() => {
println!("Response: IncompleteMessage (ignored)")
} // TODO
Err(e) => panic!("{:?}", e),
};
println!("HTTP request sent!");
// code smell - does not attach to request
let response = conn_task.await;
println!("HTTP Response Received {:?}", response);
Ok(())
}
```