-
Notifications
You must be signed in to change notification settings - Fork 51
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
Allow passing in trustAnchors to newTLSClientAsyncStream #355
Conversation
Be careful, you pushed tests/testasyncstream binary file |
chronos/streams/tlsstream.nim
Outdated
@@ -465,8 +470,8 @@ proc newTLSClientAsyncStream*(rsource: AsyncStreamReader, | |||
sslEngineSetX509(res.ccontext.eng, addr res.xwc.vtable) | |||
else: | |||
sslClientInitFull(res.ccontext, addr res.x509, | |||
unsafeAddr MozillaTrustAnchors[0], | |||
uint(len(MozillaTrustAnchors))) | |||
unsafeAddr trustAnchors[0], |
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'm not sure about the memory safety of this, seems like the caller could discard the anchors after calling this provider, and it will then lead to garbage memory
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.
Totally agree.
First of all you should add assertion check at the beginning of this procedure.
doAssert(len(trustAnchors) > 0, "Some comment")
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.
Would changing the parameter to trustAnchors: sink openArray[X509TrustAnchor] = MozillaTrustAnchors
be sufficient? I'm not terribly familiar with how move/sink works, but my gut says this situation is what a sink
is for.
This simple test program below seems to confirm that, but:
- I don't know if it just gets lucky that the array's memory happens to still be intact
- Or if it copies the whole contents of the array, which might be too expensive for
newTLSClientAsyncStream
to have to do each time.
type
Thing = object
name: string
var globalThings: ptr Thing
var globalThingsLen: int
proc save(arr: sink openArray[Thing]) =
globalThings = unsafeAddr arr[0]
globalThingsLen = arr.len
proc dosomething() =
let arr = cast[ptr UncheckedArray[Thing]](globalThings)
for i in 0..<globalThingsLen:
echo arr[i]
proc main =
var myArrayOfThings: array[3, Thing]
myArrayOfThings[0] = Thing(name: "alice")
myArrayOfThings[1] = Thing(name: "bobby")
myArrayOfThings[2] = Thing(name: "cathy")
save myArrayOfThings
myArrayOfThings[1] = Thing(name: "A walrus")
dosomething()
when isMainModule:
main()
For me, that produces the following when compiled with --gc:orc
, which is what I'd hope it would produce:
(name: "alice")
(name: "bobby")
(name: "cathy")
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.
orc will only free stuff when they get out of scope, so as soon as main is finished, myArrayOfThings
will no longer exist.
You can check by calling dosomething()
after calling main()
(which crashes on my laptop)
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.
trustAnchors
should be put on the heap. You might add a field of the seq[X509TrustAnchor]
type to TLSAsyncStream
so that it gets the same lifetime as ctx
.
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 what I was thinking, but it seems like it could be an awful waste of memory to duplicate it for every stream compared with the current implementation that shares the anchors between streams. In my particular use case, I don't mind making a copy per stream (as I'll only have one trust anchor and very few client streams) but don't want to ruin the more common case in the process.
I suppose I could make it use a seq[X509TrustAnchor]
if provided, otherwise use the const Mozilla anchors as-is?
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.
List of trusted anchors is static array which should not be changed while process is running, seq[X509TrustAnchor]
unable to satisfy this requirement, its mutable data structure. And i understand that we should somehow preserve this array in memory because of GC but storing it with every instance of TlsAsyncStream
is also not a good solution. So i prefer to have openArray[X509TrustAnchor]
and some comments in function description that you should be sure that specified trusted anchors list will be preserved in memory while TlsAsyncStream
is alive.
Oops... in all my nim projects I gitignore these. I'll back it out. |
c6eab3c
to
4a81294
Compare
Alright, I've pushed a new version that allows for passing an optional seq instead of an array. I contemplated not using Any other thoughts? Thank you for reviewing the mess I've put up so far :) |
|
👍
👍
👍 |
|
@cheatfate My purpose in submitting this PR is to allow users of my program to set their TA at runtime, so What about something like this?
Callers of Users might accidentally make too many |
I dont want to allow people to change TAs at runtime at any cost but i wish to allow people to specify custom TAs.
What the real purpose of your PR, are you trying to alter official TAs list to add some "custom/malicious" authorities to be able to use this authorities to create new certificates? |
Nothing malicious. I'm not trying to alter the official Mozilla TA list -- nothing in my PR does that. If my change is accepted, people using I'm making an application that can start websockets servers and clients (via nim-websock). Some of the users will want to use their own self-signed certificates to secure the connection between their two devices. The application will generate a self-signed cert for the server at runtime. They will then provide their client with the server's public key. The client will use that public key as the only TA when connecting to their server. That's it. This is a use case described as "Non-CA trust anchors" in the 4th bullet point of these BearSSL docs: https://www.bearssl.org/x509.html#the-minimal-engine
The test case here shows pretty much exactly what I'm hoping to do, which is to make a TLS client that trusts only one TLS server: https://github.com/status-im/nim-chronos/pull/355/files#diff-7c38a4d2f5084806ec39ffde17cc1f0bccb0fd4802abecc5f9c2f8511208a71cR983 |
If there's a way to do this with |
Exactly, so you creating self-signed certificate, start your server with this certificate and use client with |
I don't want to disable verification. I want the clients to verify that the server they are connecting to is the one they expect and the only server they trust. |
I've pushed a new version that hopefully addresses the items from before:
|
Looks like we unable to achieve consensus here, you proposing to include list of TAs with every TLSAsyncStream instance, so every object will occupy +200KB of memory without any reason, sorry but i can't accept it.
Clients are unable to verify that server they are connecting is the one they expect, it can happens only when server's certificate can be verified with some well-known list of TAs (like Mozilla's one). If you want to establish such type of trust you should buy official SSL certificate or get free one from https://letsencrypt.org/. And in such way you dont need to alter Mozilla's list of TAs. |
I don't believe this is true, but please correct my understanding if I'm wrong: If you call If you call var mystore = newTrustAnchorStore(...)
var client1 = newTLSClientAsyncStream(..., trustAnchors=mystore)
var client2 = newTLSClientAsyncStream(..., trustAnchors=mystore) In the above code,
This is not true. Neither TLS nor BearSSL require users to use a "well-known list of TAs." The set of TAs a server/client use is up to the server/client. It's convenient that chronos includes Mozilla's TAs for making connections, but it's not a requirement to only use Mozilla's TAs. In the use-case I'm trying to support, my users aren't hosting things on the public Internet. They're not programmers and they wouldn't have a clue how to use letsencrypt. This is a desktop program that they run on a few computers. I want them to be able to connect their computers together without eavesdropping on their local/business network. It will work like this:
In this scenario, the certs must be generated and anchors must be chosen at runtime. If I were to compile in the trusted anchors and then distribute it to my users, it would not be secure for them because
I'm not trying to alter Mozilla's list of TAs. None of the code I've pushed alters Mozilla's list of TAs. I don't know why you keep claiming that. What I'm proposing isn't strange. Many projects allow you to provide custom TAs. For example:
|
chronos/streams/tlsstream.nim
Outdated
@@ -132,6 +136,12 @@ proc newTLSStreamProtocolError[T](message: T): ref TLSStreamProtocolError = | |||
proc raiseTLSStreamProtocolError[T](message: T) {.noreturn, noinline.} = | |||
raise newTLSStreamProtocolImpl(message) | |||
|
|||
proc newTrustAnchorStore*(anchors: openArray[X509TrustAnchor]): TrustAnchorStore = | |||
new(result) |
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.
Please avoid result
usage and use this procedure.
proc new*(T: typedesc[TrustAnchorStore], anchors: openArray[X509TrustAnchor]): TrustAnchorStore =
var res: seq[X509TrustAnchor]
for anchor in anchors:
res.add(anchor)
doAssert(unsafeAddr(anchor) != unsafeAddr(res.anchors[^1]), "Anchors should be copied")
TrustAnchorStore(anchors: res)
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 tried the above, but running the test fails with:
Traceback (most recent call last)
/Users/matt/.nimble/pkgs/unittest2-0.0.5/unittest2.nim(848) testasyncstream
/Users/matt/lib/nim-chronos/tests/testasyncstream.nim(1022) runTest`gensym1864
/Users/matt/lib/nim-chronos/tests/testasyncstream.nim(987) checkTrustAnchors
/Users/matt/lib/nim-chronos/chronos/streams/tlsstream.nim(143) newTrustAnchorStore
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
This also produce a SIGSEGV
proc newTrustAnchorStore*(anchors: openArray[X509TrustAnchor]): TrustAnchorStore =
var res: TrustAnchorStore
new(res)
for anchor in anchors:
res.anchors.add(anchor)
doAssert(unsafeAddr(anchor) != unsafeAddr(result.anchors[^1]), "Anchors should be copied")
return res
But the original (with new(result)
) succeeds.
I'm not understanding something about how Nim allocates memory, I think. Does anyone else know what's wrong?
Sorry for long reply, but this small reproducible source working for me in 1.6 type
X509TrustAnchor* = distinct string
TrustAnchorStore* = ref object
anchors: seq[X509TrustAnchor]
proc new*(T: typedesc[TrustAnchorStore],
anchors: openArray[X509TrustAnchor]): TrustAnchorStore =
var res: seq[X509TrustAnchor]
for anchor in anchors:
res.add(anchor)
doAssert(unsafeAddr(anchor) != unsafeAddr(res[^1]),
"Anchors should be copied")
TrustAnchorStore(anchors: res)
when isMainModule:
let store = TrustAnchorStore.new([X509TrustAnchor("test1"),
X509TrustAnchor("test2"),
X509TrustAnchor("test3"),
X509TrustAnchor("test4")])
echo string(store.anchors[0]) |
@iffy could you please update PR? |
@cheatfate I just pushed the change and the tests now pass. Thank you! |
I'd like to be able to use my own certs, including self-signed certs in some TLS connections. This change lets me provide my own trust anchors.