-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SSL support to mug #10
base: main
Are you sure you want to change the base?
Conversation
Hi! I've added the ability to upgrade a TCP connection to a TLS connection via There are still two things left:
(closed by mistake, sorry!) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking really nice! Very clean!! I've left some minor notes inline.
When you are ready for a review please un-draft this PR. Thank you!
I've merged the latest changes from main, but looks like GitHub isn't showing it here for some reason |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work, thank you! I've left notes on the API largely. The TLS side I'm less qualified to speak about, but it looks good from what I can tell.
src/mug/tls.gleam
Outdated
|
||
// For connect only | ||
/// An invalid option was passed | ||
Options(opt: Dynamic) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this dynamic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docs types it as any, and doesn't explain what it's supposed to be, but from conventions I assume that this is raised if a passed option is invalid.
src/mug/tls.gleam
Outdated
NoApplicationProtocol | ||
} | ||
|
||
pub type Error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is large a duplication of the existing error type, let's merge them into one.
src/mug/tls.gleam
Outdated
Cacertfile, | ||
dynamic.from(string.to_utf_codepoints(cacerts)), | ||
) | ||
True, None -> #(Cacerts, dynamic.from(get_system_cacerts())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we load the certs rather than have the SSL application do that? It would be able to cache them, for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand what you mean. Does the SSL application automatically load system cacerts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And caching is done by erlang's ssl module automatically
src/mug/tls.gleam
Outdated
@@ -0,0 +1,630 @@ | |||
import gleam/bytes_builder.{type BytesBuilder} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This module seems to be almost exactly the same as the existing module. We could merge them into one rather than having duplicate functions for the programmer to pick between.
For merging them into one, I wonder how common methods like I'd request you to give me another review of the code, and bring to my notice any other problems, which I'll fix before doing this refactoring. |
That sounds good! I presume we cannot cheaply and reliably introspect the connection to determine what mode it is in. |
Hi! I've merged the two files into one. There's a unified API now for both TCP and TLS. A simple example: let assert Ok(socket) =
mug.new("echo.example.com", port: 443)
|> mug.timeout(milliseconds: 10_000)
|> mug.with_tls() // enable TLS!
|> mug.connect()
let assert Ok(Nil) = mug.send(socket, <<"Hello, Joe!\n":utf8>>)
let assert Ok(data) = mug.receive(socket, 500)
should.equal(data, <<"Hello, Joe!\n":utf8>>)
let assert Ok(_) = mug.shutdown(socket) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! Great work here! Here's some notes:
The tests fail cryptically when run now due to certs being missing. The developer-experience needs to be good, so people don't get stuck there not knowing what to do. It would be good if it was automatic, or for the script being at the root (so it's more easy to run) and for instructions to be given to the programmer telling them what to do, instead of a cryptic crash.
The terms TLS and SSL are both used here, but it's not clear to me why either is picked in each case or what the difference is in this package. Could you expand on this a little please? 🙏
Some of the functions are missing type annotations, could you ensure they all have all their annotations please.
For a few names abbreviations have been used and wordshavebeenwrittenlikethis. Please use full words always and put underscores between words, with the exception for existing established abbreviations or initialisms or acronyms such as TLS.
Remove all the use of catch all patterns (_ -> ...
) please, they cause bugs when types change as the logic doesn't get updated.
The builder API can silently fail to configure TLS if you give it certificates but TLS has not been enabled, which seems like it could be error prone. Let's remove these builder functions and have the programmer explicitly construct the TLS configuration, to make it clear as possible.
And thank you for waiting while I dealt with some family matters.
certfile: String, | ||
keyfile: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
certfile: String, | |
keyfile: String, | |
certificate_path: String, | |
key_path: String, |
pub type Key { | ||
/// A der-encoded key. | ||
/// `alg` is one of 'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo'. | ||
DerEncodedKey(alg: String, key: BitArray) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DerEncodedKey(alg: String, key: BitArray) | |
DerEncodedKey(algorithm: String, key: BitArray) |
If the string has to be one of a known finite variants then this should be a custom type rather than a string.
Certificates( | ||
use_system_cacerts: Bool, | ||
cacerts: Option(CaCertificates), | ||
certs_keys: List(CertsKeys), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
certs_keys: List(CertsKeys), | |
certificate_keys: List(CertsKeys), |
PemEncodedCaCertificates(cacertfile: String) | ||
} | ||
|
||
pub type CertsKeys { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This custom type is plural, but the data within is singular. Is this correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's because the certfile has a chain of certificates inside it.
/// certs override PEM-encoded ones. Therefore, if you wish to use a PEM encoded CA cert along | ||
/// with the system's CA, you should decode the PEM into a DER using (`public_key:pem_decode`)[https://www.erlang.org/doc/apps/public_key/public_key.html#pem-api] | ||
/// and use the DerEncodedCaCertificates variant instead. | ||
Certificates( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like one can use system certs wit custom certs, but the custom certs must all be der or pem. How come that specific restriction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If PEM certificates are passed, then the ssl
application ignores DER certificates, including system cacerts, which are DER encoded. Thus if the user passes like a hundred DER certificates, passing just one PEM certificate will result in all those DER certs being ignored.
Hi! I've added some SSL support to mug using Erlang's
ssl
module. It lives in themug/ssl
module and has an API very similar to the existing TCP API.Here's an example of connecting to an SSL host:
There are still a few bits and bobs missing, like the ability to upgrade from a regular TCP connection to an encrypted TLS one. There is also an unimplemented function for adding the certs_keys option since I'm not able to figure out how to do it right now (I'll give it a try soon, I'm new to both Gleam and Erlang).
I've added a few tests in
test/mug_ssl_test.gleam
which are built off oftest/mug_test.gleam
. One of them isn't currently working because I'm not able to figure out some ca certificate nonsense (hence, it has been commented out).Please let me know if any changes have to be done, and I'd request you check my doc comments for any grammatical errors, since English is not my first language. Thanks!