fix(nix): update vendorHash and vendor dir for new deps
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.11.x"
|
||||
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
install:
|
||||
- go get -t ./...
|
||||
- go get golang.org/x/lint/golint
|
||||
# Install gometalinter
|
||||
- go get github.com/alecthomas/gometalinter
|
||||
|
||||
script:
|
||||
- go test -race -v .
|
||||
- gometalinter --install
|
||||
- gometalinter --disable-all -E vet -E ineffassign --tests .
|
||||
- gometalinter --disable-all -E gofmt -E misspell -E goimports --tests u_*
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement. You (or your employer) retain the copyright to your contribution,
|
||||
this simply gives us permission to use and redistribute your contributions as
|
||||
part of the project. Head over to <https://cla.developers.google.com/> to see
|
||||
your current agreements on file or to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
# How this package works
|
||||
### Chapter 1: [Making private things public](./u_public.go)
|
||||
There are numerous handshake-related structs in crypto/tls, most of which are either private or have private fields.
|
||||
One of them — `clientHandshakeState` — has private function `handshake()`,
|
||||
which is called in the beginning of default handshake.
|
||||
Unfortunately, user will not be able to directly access this struct outside of tls package.
|
||||
As a result, we decided to employ following workaround: declare public copies of private structs.
|
||||
Now user is free to manipulate fields of public `ClientHandshakeState`.
|
||||
Then, right before handshake, we can shallow-copy public state into private `clientHandshakeState`,
|
||||
call `handshake()` on it and carry on with default Golang handshake process.
|
||||
After handshake is done we shallow-copy private state back to public, allowing user to read results of handshake.
|
||||
|
||||
### Chapter 2: [TLSExtension](./u_tls_extensions.go)
|
||||
The way we achieve reasonable flexibilty with extensions is inspired by
|
||||
[ztls'](https://github.com/zmap/zcrypto/blob/master/tls/handshake_extensions.go) design.
|
||||
However, our design has several differences, so we wrote it from scratch.
|
||||
This design allows us to have an array of `TLSExtension` objects and then marshal them in order:
|
||||
```Golang
|
||||
type TLSExtension interface {
|
||||
writeToUConn(*UConn) error
|
||||
|
||||
Len() int // includes header
|
||||
|
||||
// Read reads up to len(p) bytes into p.
|
||||
// It returns the number of bytes read (0 <= n <= len(p)) and any error encountered.
|
||||
Read(p []byte) (n int, err error) // implements io.Reader
|
||||
}
|
||||
```
|
||||
`writeToUConn()` applies appropriate per-extension changes to `UConn`.
|
||||
|
||||
`Len()` provides the size of marshaled extension, so we can allocate appropriate buffer beforehand,
|
||||
catch out-of-bound errors easily and guide size-dependent extensions such as padding.
|
||||
|
||||
`Read(buffer []byte)` _writes(see: io.Reader interface)_ marshaled extensions into provided buffer.
|
||||
This avoids extra allocations.
|
||||
|
||||
### Chapter 3: [UConn](./u_conn.go)
|
||||
`UConn` extends standard `tls.Conn`. Most notably, it stores slice with `TLSExtension`s and public
|
||||
`ClientHandshakeState`.
|
||||
Whenever `UConn.BuildHandshakeState()` gets called (happens automatically in `UConn.Handshake()`
|
||||
or could be called manually), config will be applied according to chosen `ClientHelloID`.
|
||||
From contributor's view there are 2 main behaviors:
|
||||
* `HelloGolang` simply calls default Golang's [`makeClientHello()`](./handshake_client.go)
|
||||
and directly stores it into `HandshakeState.Hello`. utls-specific stuff is ignored.
|
||||
* Other ClientHelloIDs fill `UConn.Hello.{Random, CipherSuites, CompressionMethods}` and `UConn.Extensions` with
|
||||
per-parrot setup, which then gets applied to appropriate standard tls structs,
|
||||
and then marshaled by utls into `HandshakeState.Hello`.
|
||||
|
||||
### Chapter 4: Tests
|
||||
|
||||
Tests exist, but coverage is very limited. What's covered is a conjunction of
|
||||
* TLS 1.2
|
||||
* Working parrots without any unsupported extensions (only Android 5.1 at this time)
|
||||
* Ciphersuites offered by parrot.
|
||||
* Ciphersuites supported by Golang
|
||||
* Simple conversation with reference implementation of OpenSSL.
|
||||
(e.g. no automatic checks for renegotiations, parroting quality and such)
|
||||
|
||||
plus we test some other minor things.
|
||||
Basically, current tests aim to provide a sanity check.
|
||||
|
||||
# Merging upstream
|
||||
```Bash
|
||||
git remote add -f golang git@github.com:golang/go.git
|
||||
git checkout -b golang-upstream golang/master
|
||||
git subtree split -P src/crypto/tls/ -b golang-tls-upstream
|
||||
git checkout master
|
||||
git merge --no-commit golang-tls-upstream
|
||||
```
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
#  uTLS
|
||||
[](https://github.com/refraction-networking/utls/actions/workflows/go.yml)
|
||||
[](https://godoc.org/github.com/refraction-networking/utls#UConn)
|
||||
---
|
||||
uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resistance, low-level access to handshake, fake session tickets and some other features. Handshake is still performed by "crypto/tls", this library merely changes ClientHello part of it and provides low-level access.
|
||||
|
||||
**Minimum Go Version**: Go 1.21
|
||||
|
||||
If you have any questions, bug reports or contributions, you are welcome to publish those on GitHub. If you want to do so in private, ~~you can contact one of developers personally via sergey.frolov@colorado.edu~~.
|
||||
|
||||
You can contact one of developers personally via gaukas.wang@colorado.edu.
|
||||
|
||||
Documentation below may not keep up with all the changes and new features at all times,
|
||||
so you are encouraged to use [godoc](https://godoc.org/github.com/refraction-networking/utls#UConn).
|
||||
|
||||
*Note: Information provided below in this README.md could be obsolete. We welcome
|
||||
any contributions to refresh the documentations in addition to code contributions.*
|
||||
|
||||
# Features
|
||||
## Low-level access to handshake
|
||||
* Read/write access to all bits of client hello message.
|
||||
* Read access to fields of ClientHandshakeState, which, among other things, includes ServerHello and MasterSecret.
|
||||
* Read keystream. Can be used, for example, to "write" something in ciphertext.
|
||||
|
||||
## ClientHello fingerprinting resistance
|
||||
Golang's ClientHello has a very unique fingerprint, which especially sticks out on mobile clients,
|
||||
where Golang is not too popular yet.
|
||||
Some members of anti-censorship community are concerned that their tools could be trivially blocked based on
|
||||
ClientHello with relatively small collateral damage. There are multiple solutions to this issue.
|
||||
|
||||
**It is highly recommended to use multiple fingeprints, including randomized ones to avoid relying on a single fingerprint.**
|
||||
[utls.Roller](#roller) does this automatically.
|
||||
|
||||
### Randomized Fingerprint
|
||||
Randomized Fingerprints are supposedly good at defeating blacklists, since
|
||||
those fingerprints have random ciphersuites and extensions in random order.
|
||||
Note that all used ciphersuites and extensions are fully supported by uTLS,
|
||||
which provides a solid moving target without any compatibility or parrot-is-dead attack risks.
|
||||
|
||||
But note that there's a small chance that generated fingerprint won't work,
|
||||
so you may want to keep generating until a working one is found,
|
||||
and then keep reusing the working fingerprint to avoid suspicious behavior of constantly changing fingerprints.
|
||||
[utls.Roller](#roller) reuses working fingerprint automatically.
|
||||
|
||||
#### Generating randomized fingerprints
|
||||
|
||||
To generate a randomized fingerprint, simply do:
|
||||
```Golang
|
||||
uTlsConn := tls.UClient(tcpConn, &config, tls.HelloRandomized)
|
||||
```
|
||||
you can use `helloRandomizedALPN` or `helloRandomizedNoALPN` to ensure presence or absence of
|
||||
ALPN(Application-Layer Protocol Negotiation) extension.
|
||||
It is recommended, but certainly not required to include ALPN (or use helloRandomized which may or may not include ALPN).
|
||||
If you do use ALPN, you will want to correctly handle potential application layer protocols (likely h2 or http/1.1).
|
||||
|
||||
#### Reusing randomized fingerprint
|
||||
```Golang
|
||||
// oldConn is an old connection that worked before, so we want to reuse it
|
||||
// newConn is a new connection we'd like to establish
|
||||
newConn := tls.UClient(tcpConn, &config, oldConn.ClientHelloID)
|
||||
```
|
||||
|
||||
### Parroting
|
||||
This package can be used to parrot ClientHello of popular browsers.
|
||||
There are some caveats to this parroting:
|
||||
* We are forced to offer ciphersuites and tls extensions that are not supported by crypto/tls.
|
||||
This is not a problem, if you fully control the server and turn unsupported things off on server side.
|
||||
* Parroting could be imperfect, and there is no parroting beyond ClientHello.
|
||||
#### Compatibility risks of available parrots
|
||||
|
||||
| Parrot | Ciphers* | Signature* | Unsupported extensions | TLS Fingerprint ID |
|
||||
| ------------- | -------- | ---------- | ---------------------- | --------------------------------------------- |
|
||||
| Chrome 62 | no | no | ChannelID | [0a4a74aeebd1bb66](https://tlsfingerprint.io/id/0a4a74aeebd1bb66) |
|
||||
| Chrome 70 | no | no | ChannelID, Encrypted Certs | [bc4c7e42f4961cd7](https://tlsfingerprint.io/id/bc4c7e42f4961cd7) |
|
||||
| Chrome 72 | no | no | ChannelID, Encrypted Certs | [bbf04e5f1881f506](https://tlsfingerprint.io/id/bbf04e5f1881f506) |
|
||||
| Chrome 83 | no | no | ChannelID, Encrypted Certs | [9c673fd64a32c8dc](https://tlsfingerprint.io/id/9c673fd64a32c8dc) |
|
||||
| Firefox 56 | very low | no | None | [c884bad7f40bee56](https://tlsfingerprint.io/id/c884bad7f40bee56) |
|
||||
| Firefox 65 | very low | no | MaxRecordSize | [6bfedc5d5c740d58](https://tlsfingerprint.io/id/6bfedc5d5c740d58) |
|
||||
| iOS 11.1 | low** | no | None | [71a81bafd58e1301](https://tlsfingerprint.io/id/71a81bafd58e1301) |
|
||||
| iOS 12.1 | low** | no | None | [ec55e5b4136c7949](https://tlsfingerprint.io/id/ec55e5b4136c7949) |
|
||||
|
||||
\* Denotes very rough guesstimate of likelihood that unsupported things will get echoed back by the server in the wild,
|
||||
*visibly breaking the connection*.
|
||||
\*\* No risk, if `utls.EnableWeakCiphers()` is called prior to using it.
|
||||
|
||||
#### Parrots FAQ
|
||||
> Does it really look like, say, Google Chrome with all the [GREASE](https://tools.ietf.org/html/draft-davidben-tls-grease-01) and stuff?
|
||||
|
||||
It LGTM, but please open up Wireshark and check. If you see something — [say something](https://github.com/refraction-networking/utls/issues).
|
||||
|
||||
> Aren't there side channels? Everybody knows that the ~~bird is a word~~[parrot is dead](https://people.cs.umass.edu/~amir/papers/parrot.pdf)
|
||||
|
||||
There sure are. If you found one that approaches practicality at line speed — [please tell us](https://github.com/refraction-networking/utls/issues).
|
||||
|
||||
However, there is a difference between this sort of parroting and techniques like SkypeMorth.
|
||||
Namely, TLS is highly standardized protocol, therefore simply not that many subtle things in TLS protocol
|
||||
could be different and/or suddenly change in one of mimicked implementation(potentially undermining the mimicry).
|
||||
It is possible that we have a distinguisher right now, but amount of those potential distinguishers is limited.
|
||||
|
||||
### Custom Handshake
|
||||
It is possible to create custom handshake by
|
||||
1) Use `HelloCustom` as an argument for `UClient()` to get empty config
|
||||
2) Fill tls header fields: UConn.Hello.{Random, CipherSuites, CompressionMethods}, if needed, or stick to defaults.
|
||||
3) Configure and add various [TLS Extensions](u_tls_extensions.go) to UConn.Extensions: they will be marshaled in order.
|
||||
4) Set Session and SessionCache, as needed.
|
||||
|
||||
If you need to manually control all the bytes on the wire(certainly not recommended!),
|
||||
you can set UConn.HandshakeStateBuilt = true, and marshal clientHello into UConn.HandshakeState.Hello.raw yourself.
|
||||
In this case you will be responsible for modifying other parts of Config and ClientHelloMsg to reflect your setup
|
||||
and not confuse "crypto/tls", which will be processing response from server.
|
||||
|
||||
### Fingerprinting Captured Client Hello
|
||||
You can use a captured client hello to generate new ones that mimic/have the same properties as the original.
|
||||
The generated client hellos _should_ look like they were generated from the same client software as the original fingerprinted bytes.
|
||||
In order to do this:
|
||||
1) Create a `ClientHelloSpec` from the raw bytes of the original client hello
|
||||
2) Use `HelloCustom` as an argument for `UClient()` to get empty config
|
||||
3) Use `ApplyPreset` with the generated `ClientHelloSpec` to set the appropriate connection properties
|
||||
```
|
||||
uConn := UClient(&net.TCPConn{}, nil, HelloCustom)
|
||||
fingerprinter := &Fingerprinter{}
|
||||
generatedSpec, err := fingerprinter.FingerprintClientHello(rawCapturedClientHelloBytes)
|
||||
if err != nil {
|
||||
panic("fingerprinting failed: %v", err)
|
||||
}
|
||||
if err := uConn.ApplyPreset(generatedSpec); err != nil {
|
||||
panic("applying generated spec failed: %v", err)
|
||||
}
|
||||
```
|
||||
The `rawCapturedClientHelloBytes` should be the full tls record, including the record type/version/length header.
|
||||
|
||||
## Roller
|
||||
|
||||
A simple wrapper, that allows to easily use multiple latest(auto-updated) fingerprints.
|
||||
|
||||
```Golang
|
||||
// NewRoller creates Roller object with default range of HelloIDs to cycle
|
||||
// through until a working/unblocked one is found.
|
||||
func NewRoller() (*Roller, error)
|
||||
```
|
||||
|
||||
```Golang
|
||||
// Dial attempts to connect to given address using different HelloIDs.
|
||||
// If a working HelloID is found, it is used again for subsequent Dials.
|
||||
// If tcp connection fails or all HelloIDs are tried, returns with last error.
|
||||
//
|
||||
// Usage examples:
|
||||
//
|
||||
// Dial("tcp4", "google.com:443", "google.com")
|
||||
// Dial("tcp", "10.23.144.22:443", "mywebserver.org")
|
||||
func (c *Roller) Dial(network, addr, serverName string) (*UConn, error)
|
||||
```
|
||||
|
||||
## Fake Session Tickets
|
||||
Fake session tickets is a very nifty trick that allows power users to hide parts of handshake, which may have some very fingerprintable features of handshake, and saves 1 RTT.
|
||||
Currently, there is a simple function to set session ticket to any desired state:
|
||||
|
||||
```Golang
|
||||
// If you want you session tickets to be reused - use same cache on following connections
|
||||
func (uconn *UConn) SetSessionState(session *ClientSessionState)
|
||||
```
|
||||
|
||||
Note that session tickets (fake ones or otherwise) are not reused.
|
||||
To reuse tickets, create a shared cache and set it on current and further configs:
|
||||
|
||||
```Golang
|
||||
// If you want you session tickets to be reused - use same cache on following connections
|
||||
func (uconn *UConn) SetSessionCache(cache ClientSessionCache)
|
||||
```
|
||||
|
||||
## Custom TLS extensions
|
||||
If you want to add your own fake (placeholder, without added functionality) extension for mimicry purposes, you can embed `*tls.GenericExtension` into your own struct and override `Len()` and `Read()` methods. For example, [DelegatedCredentials](https://datatracker.ietf.org/doc/draft-ietf-tls-subcerts/) extension can be implemented as follows:
|
||||
|
||||
```Golang
|
||||
const FakeDelegatedCredentials uint16 = 0x0022
|
||||
|
||||
type FakeDelegatedCredentialsExtension struct {
|
||||
*tls.GenericExtension
|
||||
SignatureAlgorithms []tls.SignatureScheme
|
||||
}
|
||||
|
||||
func (e *FakeDelegatedCredentialsExtension) Len() int {
|
||||
return 6 + 2*len(e.SignatureAlgorithms)
|
||||
}
|
||||
|
||||
func (e *FakeDelegatedCredentialsExtension) Read(b []byte) (n int, err error) {
|
||||
if len(b) < e.Len() {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
offset := 0
|
||||
appendUint16 := func(val uint16) {
|
||||
b[offset] = byte(val >> 8)
|
||||
b[offset+1] = byte(val & 0xff)
|
||||
offset += 2
|
||||
}
|
||||
|
||||
// Extension type
|
||||
appendUint16(FakeDelegatedCredentials)
|
||||
|
||||
algosLength := 2 * len(e.SignatureAlgorithms)
|
||||
|
||||
// Extension data length
|
||||
appendUint16(uint16(algosLength) + 2)
|
||||
|
||||
// Algorithms list length
|
||||
appendUint16(uint16(algosLength))
|
||||
|
||||
// Algorithms list
|
||||
for _, a := range e.SignatureAlgorithms {
|
||||
appendUint16(uint16(a))
|
||||
}
|
||||
return e.Len(), io.EOF
|
||||
}
|
||||
```
|
||||
|
||||
Then it can be used just like normal extension:
|
||||
|
||||
```Golang
|
||||
&tls.ClientHelloSpec{
|
||||
//...
|
||||
Extensions: []tls.TLSExtension{
|
||||
//...
|
||||
&FakeDelegatedCredentialsExtension{
|
||||
SignatureAlgorithms: []tls.SignatureScheme{
|
||||
tls.ECDSAWithP256AndSHA256,
|
||||
tls.ECDSAWithP384AndSHA384,
|
||||
tls.ECDSAWithP521AndSHA512,
|
||||
tls.ECDSAWithSHA1,
|
||||
},
|
||||
},
|
||||
//...
|
||||
}
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
# Client Hello IDs
|
||||
See full list of `clientHelloID` values [here](https://godoc.org/github.com/refraction-networking/utls#ClientHelloID).
|
||||
There are different behaviors you can get, depending on your `clientHelloID`:
|
||||
|
||||
1. ```utls.HelloRandomized``` adds/reorders extensions, ciphersuites, etc. randomly.
|
||||
`HelloRandomized` adds ALPN in a percentage of cases, you may want to use `HelloRandomizedALPN` or
|
||||
`HelloRandomizedNoALPN` to choose specific behavior explicitly, as ALPN might affect application layer.
|
||||
2. ```utls.HelloGolang```
|
||||
HelloGolang will use default "crypto/tls" handshake marshaling codepath, which WILL
|
||||
overwrite your changes to Hello(Config, Session are fine).
|
||||
You might want to call BuildHandshakeState() before applying any changes.
|
||||
UConn.Extensions will be completely ignored.
|
||||
3. ```utls.HelloCustom```
|
||||
will prepare ClientHello with empty uconn.Extensions so you can fill it with TLSExtension's manually.
|
||||
4. The rest will will parrot given browser. Such parrots include, for example:
|
||||
* `utls.HelloChrome_Auto`- parrots recommended(usually latest) Google Chrome version
|
||||
* `utls.HelloChrome_58` - parrots Google Chrome 58
|
||||
* `utls.HelloFirefox_Auto` - parrots recommended(usually latest) Firefox version
|
||||
* `utls.HelloFirefox_55` - parrots Firefox 55
|
||||
|
||||
# Usage
|
||||
## Examples
|
||||
Find basic examples [here](examples/examples.go).
|
||||
Here's a more [advanced example](https://github.com/sergeyfrolov/gotapdance/blob//9a777f35a04b0c4c5dacd30bca0e9224eb737b5e/tapdance/conn_raw.go#L275-L292) showing how to generate randomized ClientHello, modify generated ciphersuites a bit, and proceed with the handshake.
|
||||
### Migrating from "crypto/tls"
|
||||
Here's how default "crypto/tls" is typically used:
|
||||
```Golang
|
||||
dialConn, err := net.Dial("tcp", "172.217.11.46:443")
|
||||
if err != nil {
|
||||
fmt.Printf("net.Dial() failed: %+v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
config := tls.Config{ServerName: "www.google.com"}
|
||||
tlsConn := tls.Client(dialConn, &config)
|
||||
n, err = tlsConn.Write("Hello, World!")
|
||||
//...
|
||||
```
|
||||
To start using using uTLS:
|
||||
1. Import this library (e.g. `import tls "github.com/refraction-networking/utls"`)
|
||||
2. Pick the [Client Hello ID](#client-hello-ids)
|
||||
3. Simply substitute `tlsConn := tls.Client(dialConn, &config)`
|
||||
with `tlsConn := tls.UClient(dialConn, &config, tls.clientHelloID)`
|
||||
|
||||
### Customizing handshake
|
||||
Some customizations(such as setting session ticket/clientHello) have easy-to-use functions for them. The idea is to make common manipulations easy:
|
||||
```Golang
|
||||
cRandom := []byte{100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
|
||||
110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
|
||||
120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
|
||||
130, 131}
|
||||
tlsConn.SetClientRandom(cRandom)
|
||||
masterSecret := make([]byte, 48)
|
||||
copy(masterSecret, []byte("masterSecret is NOT sent over the wire")) // you may use it for real security
|
||||
|
||||
// Create a session ticket that wasn't actually issued by the server.
|
||||
sessionState := utls.MakeClientSessionState(sessionTicket, uint16(tls.VersionTLS12),
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
masterSecret,
|
||||
nil, nil)
|
||||
tlsConn.SetSessionState(sessionState)
|
||||
```
|
||||
|
||||
For other customizations there are following functions
|
||||
```
|
||||
// you can use this to build the state manually and change it
|
||||
// for example use Randomized ClientHello, and add more extensions
|
||||
func (uconn *UConn) BuildHandshakeState() error
|
||||
```
|
||||
```
|
||||
// Then apply the changes and marshal final bytes, which will be sent
|
||||
func (uconn *UConn) MarshalClientHello() error
|
||||
```
|
||||
|
||||
## Contributors' guide
|
||||
Please refer to [this document](./CONTRIBUTORS_GUIDE.md) if you're interested in internals
|
||||
|
||||
## Credits
|
||||
The initial development of uTLS was completed during an internship at [Google Jigsaw](https://jigsaw.google.com/). This is not an official Google product.
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We will only maintain one branch which is the master branch. Unless otherwise requested, no security patches will be applied to older Major/Minor versions.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
For a vulnerability of low to no severity, which causing no threats to security, you may report it openly to us by [opening an issue](https://github.com/refraction-networking/utls/issues/new)
|
||||
|
||||
If the vulnerability you are reporting inflicts some security impact, please [do so privately](https://github.com/refraction-networking/utls/security/advisories/new).
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import "strconv"
|
||||
|
||||
// An AlertError is a TLS alert.
|
||||
//
|
||||
// When using a QUIC transport, QUICConn methods will return an error
|
||||
// which wraps AlertError rather than sending a TLS alert.
|
||||
type AlertError uint8
|
||||
|
||||
func (e AlertError) Error() string {
|
||||
return alert(e).String()
|
||||
}
|
||||
|
||||
type alert uint8
|
||||
|
||||
const (
|
||||
// alert level
|
||||
alertLevelWarning = 1
|
||||
alertLevelError = 2
|
||||
)
|
||||
|
||||
const (
|
||||
alertCloseNotify alert = 0
|
||||
alertUnexpectedMessage alert = 10
|
||||
alertBadRecordMAC alert = 20
|
||||
alertDecryptionFailed alert = 21
|
||||
alertRecordOverflow alert = 22
|
||||
alertDecompressionFailure alert = 30
|
||||
alertHandshakeFailure alert = 40
|
||||
alertBadCertificate alert = 42
|
||||
alertUnsupportedCertificate alert = 43
|
||||
alertCertificateRevoked alert = 44
|
||||
alertCertificateExpired alert = 45
|
||||
alertCertificateUnknown alert = 46
|
||||
alertIllegalParameter alert = 47
|
||||
alertUnknownCA alert = 48
|
||||
alertAccessDenied alert = 49
|
||||
alertDecodeError alert = 50
|
||||
alertDecryptError alert = 51
|
||||
alertExportRestriction alert = 60
|
||||
alertProtocolVersion alert = 70
|
||||
alertInsufficientSecurity alert = 71
|
||||
alertInternalError alert = 80
|
||||
alertInappropriateFallback alert = 86
|
||||
alertUserCanceled alert = 90
|
||||
alertNoRenegotiation alert = 100
|
||||
alertMissingExtension alert = 109
|
||||
alertUnsupportedExtension alert = 110
|
||||
alertCertificateUnobtainable alert = 111
|
||||
alertUnrecognizedName alert = 112
|
||||
alertBadCertificateStatusResponse alert = 113
|
||||
alertBadCertificateHashValue alert = 114
|
||||
alertUnknownPSKIdentity alert = 115
|
||||
alertCertificateRequired alert = 116
|
||||
alertNoApplicationProtocol alert = 120
|
||||
alertECHRequired alert = 121
|
||||
)
|
||||
|
||||
var alertText = map[alert]string{
|
||||
alertCloseNotify: "close notify",
|
||||
alertUnexpectedMessage: "unexpected message",
|
||||
alertBadRecordMAC: "bad record MAC",
|
||||
alertDecryptionFailed: "decryption failed",
|
||||
alertRecordOverflow: "record overflow",
|
||||
alertDecompressionFailure: "decompression failure",
|
||||
alertHandshakeFailure: "handshake failure",
|
||||
alertBadCertificate: "bad certificate",
|
||||
alertUnsupportedCertificate: "unsupported certificate",
|
||||
alertCertificateRevoked: "revoked certificate",
|
||||
alertCertificateExpired: "expired certificate",
|
||||
alertCertificateUnknown: "unknown certificate",
|
||||
alertIllegalParameter: "illegal parameter",
|
||||
alertUnknownCA: "unknown certificate authority",
|
||||
alertAccessDenied: "access denied",
|
||||
alertDecodeError: "error decoding message",
|
||||
alertDecryptError: "error decrypting message",
|
||||
alertExportRestriction: "export restriction",
|
||||
alertProtocolVersion: "protocol version not supported",
|
||||
alertInsufficientSecurity: "insufficient security level",
|
||||
alertInternalError: "internal error",
|
||||
alertInappropriateFallback: "inappropriate fallback",
|
||||
alertUserCanceled: "user canceled",
|
||||
alertNoRenegotiation: "no renegotiation",
|
||||
alertMissingExtension: "missing extension",
|
||||
alertUnsupportedExtension: "unsupported extension",
|
||||
alertCertificateUnobtainable: "certificate unobtainable",
|
||||
alertUnrecognizedName: "unrecognized name",
|
||||
alertBadCertificateStatusResponse: "bad certificate status response",
|
||||
alertBadCertificateHashValue: "bad certificate hash value",
|
||||
alertUnknownPSKIdentity: "unknown PSK identity",
|
||||
alertCertificateRequired: "certificate required",
|
||||
alertNoApplicationProtocol: "no application protocol",
|
||||
alertECHRequired: "encrypted client hello required",
|
||||
}
|
||||
|
||||
func (e alert) String() string {
|
||||
s, ok := alertText[e]
|
||||
if ok {
|
||||
return "tls: " + s
|
||||
}
|
||||
return "tls: alert(" + strconv.Itoa(int(e)) + ")"
|
||||
}
|
||||
|
||||
func (e alert) Error() string {
|
||||
return e.String()
|
||||
}
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
// verifyHandshakeSignature verifies a signature against pre-hashed
|
||||
// (if required) handshake contents.
|
||||
func verifyHandshakeSignature(sigType uint8, pubkey crypto.PublicKey, hashFunc crypto.Hash, signed, sig []byte) error {
|
||||
switch sigType {
|
||||
case signatureECDSA:
|
||||
pubKey, ok := pubkey.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an ECDSA public key, got %T", pubkey)
|
||||
}
|
||||
if !ecdsa.VerifyASN1(pubKey, signed, sig) {
|
||||
return errors.New("ECDSA verification failure")
|
||||
}
|
||||
case signatureEd25519:
|
||||
pubKey, ok := pubkey.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an Ed25519 public key, got %T", pubkey)
|
||||
}
|
||||
if !ed25519.Verify(pubKey, signed, sig) {
|
||||
return errors.New("Ed25519 verification failure")
|
||||
}
|
||||
case signaturePKCS1v15:
|
||||
pubKey, ok := pubkey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an RSA public key, got %T", pubkey)
|
||||
}
|
||||
if err := rsa.VerifyPKCS1v15(pubKey, hashFunc, signed, sig); err != nil {
|
||||
return err
|
||||
}
|
||||
case signatureRSAPSS:
|
||||
pubKey, ok := pubkey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an RSA public key, got %T", pubkey)
|
||||
}
|
||||
signOpts := &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}
|
||||
if err := rsa.VerifyPSS(pubKey, hashFunc, signed, sig, signOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("internal error: unknown signature type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
serverSignatureContext = "TLS 1.3, server CertificateVerify\x00"
|
||||
clientSignatureContext = "TLS 1.3, client CertificateVerify\x00"
|
||||
)
|
||||
|
||||
var signaturePadding = []byte{
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
}
|
||||
|
||||
// signedMessage returns the pre-hashed (if necessary) message to be signed by
|
||||
// certificate keys in TLS 1.3. See RFC 8446, Section 4.4.3.
|
||||
func signedMessage(sigHash crypto.Hash, context string, transcript hash.Hash) []byte {
|
||||
if sigHash == directSigning {
|
||||
b := &bytes.Buffer{}
|
||||
b.Write(signaturePadding)
|
||||
io.WriteString(b, context)
|
||||
b.Write(transcript.Sum(nil))
|
||||
return b.Bytes()
|
||||
}
|
||||
h := sigHash.New()
|
||||
h.Write(signaturePadding)
|
||||
io.WriteString(h, context)
|
||||
h.Write(transcript.Sum(nil))
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// typeAndHashFromSignatureScheme returns the corresponding signature type and
|
||||
// crypto.Hash for a given TLS SignatureScheme.
|
||||
func typeAndHashFromSignatureScheme(signatureAlgorithm SignatureScheme) (sigType uint8, hash crypto.Hash, err error) {
|
||||
switch signatureAlgorithm {
|
||||
case PKCS1WithSHA1, PKCS1WithSHA256, PKCS1WithSHA384, PKCS1WithSHA512:
|
||||
sigType = signaturePKCS1v15
|
||||
case PSSWithSHA256, PSSWithSHA384, PSSWithSHA512:
|
||||
sigType = signatureRSAPSS
|
||||
case ECDSAWithSHA1, ECDSAWithP256AndSHA256, ECDSAWithP384AndSHA384, ECDSAWithP521AndSHA512:
|
||||
sigType = signatureECDSA
|
||||
case Ed25519:
|
||||
sigType = signatureEd25519
|
||||
default:
|
||||
return 0, 0, fmt.Errorf("unsupported signature algorithm: %v", signatureAlgorithm)
|
||||
}
|
||||
switch signatureAlgorithm {
|
||||
case PKCS1WithSHA1, ECDSAWithSHA1:
|
||||
hash = crypto.SHA1
|
||||
case PKCS1WithSHA256, PSSWithSHA256, ECDSAWithP256AndSHA256:
|
||||
hash = crypto.SHA256
|
||||
case PKCS1WithSHA384, PSSWithSHA384, ECDSAWithP384AndSHA384:
|
||||
hash = crypto.SHA384
|
||||
case PKCS1WithSHA512, PSSWithSHA512, ECDSAWithP521AndSHA512:
|
||||
hash = crypto.SHA512
|
||||
case Ed25519:
|
||||
hash = directSigning
|
||||
default:
|
||||
return 0, 0, fmt.Errorf("unsupported signature algorithm: %v", signatureAlgorithm)
|
||||
}
|
||||
return sigType, hash, nil
|
||||
}
|
||||
|
||||
// legacyTypeAndHashFromPublicKey returns the fixed signature type and crypto.Hash for
|
||||
// a given public key used with TLS 1.0 and 1.1, before the introduction of
|
||||
// signature algorithm negotiation.
|
||||
func legacyTypeAndHashFromPublicKey(pub crypto.PublicKey) (sigType uint8, hash crypto.Hash, err error) {
|
||||
switch pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return signaturePKCS1v15, crypto.MD5SHA1, nil
|
||||
case *ecdsa.PublicKey:
|
||||
return signatureECDSA, crypto.SHA1, nil
|
||||
case ed25519.PublicKey:
|
||||
// RFC 8422 specifies support for Ed25519 in TLS 1.0 and 1.1,
|
||||
// but it requires holding on to a handshake transcript to do a
|
||||
// full signature, and not even OpenSSL bothers with the
|
||||
// complexity, so we can't even test it properly.
|
||||
return 0, 0, fmt.Errorf("tls: Ed25519 public keys are not supported before TLS 1.2")
|
||||
default:
|
||||
return 0, 0, fmt.Errorf("tls: unsupported public key: %T", pub)
|
||||
}
|
||||
}
|
||||
|
||||
var rsaSignatureSchemes = []struct {
|
||||
scheme SignatureScheme
|
||||
minModulusBytes int
|
||||
maxVersion uint16
|
||||
}{
|
||||
// RSA-PSS is used with PSSSaltLengthEqualsHash, and requires
|
||||
// emLen >= hLen + sLen + 2
|
||||
{PSSWithSHA256, crypto.SHA256.Size()*2 + 2, VersionTLS13},
|
||||
{PSSWithSHA384, crypto.SHA384.Size()*2 + 2, VersionTLS13},
|
||||
{PSSWithSHA512, crypto.SHA512.Size()*2 + 2, VersionTLS13},
|
||||
// PKCS #1 v1.5 uses prefixes from hashPrefixes in crypto/rsa, and requires
|
||||
// emLen >= len(prefix) + hLen + 11
|
||||
// TLS 1.3 dropped support for PKCS #1 v1.5 in favor of RSA-PSS.
|
||||
{PKCS1WithSHA256, 19 + crypto.SHA256.Size() + 11, VersionTLS12},
|
||||
{PKCS1WithSHA384, 19 + crypto.SHA384.Size() + 11, VersionTLS12},
|
||||
{PKCS1WithSHA512, 19 + crypto.SHA512.Size() + 11, VersionTLS12},
|
||||
{PKCS1WithSHA1, 15 + crypto.SHA1.Size() + 11, VersionTLS12},
|
||||
}
|
||||
|
||||
// signatureSchemesForCertificate returns the list of supported SignatureSchemes
|
||||
// for a given certificate, based on the public key and the protocol version,
|
||||
// and optionally filtered by its explicit SupportedSignatureAlgorithms.
|
||||
//
|
||||
// This function must be kept in sync with supportedSignatureAlgorithms.
|
||||
// FIPS filtering is applied in the caller, selectSignatureScheme.
|
||||
func signatureSchemesForCertificate(version uint16, cert *Certificate) []SignatureScheme {
|
||||
priv, ok := cert.PrivateKey.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var sigAlgs []SignatureScheme
|
||||
switch pub := priv.Public().(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
if version != VersionTLS13 {
|
||||
// In TLS 1.2 and earlier, ECDSA algorithms are not
|
||||
// constrained to a single curve.
|
||||
sigAlgs = []SignatureScheme{
|
||||
ECDSAWithP256AndSHA256,
|
||||
ECDSAWithP384AndSHA384,
|
||||
ECDSAWithP521AndSHA512,
|
||||
ECDSAWithSHA1,
|
||||
}
|
||||
break
|
||||
}
|
||||
switch pub.Curve {
|
||||
case elliptic.P256():
|
||||
sigAlgs = []SignatureScheme{ECDSAWithP256AndSHA256}
|
||||
case elliptic.P384():
|
||||
sigAlgs = []SignatureScheme{ECDSAWithP384AndSHA384}
|
||||
case elliptic.P521():
|
||||
sigAlgs = []SignatureScheme{ECDSAWithP521AndSHA512}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
case *rsa.PublicKey:
|
||||
size := pub.Size()
|
||||
sigAlgs = make([]SignatureScheme, 0, len(rsaSignatureSchemes))
|
||||
for _, candidate := range rsaSignatureSchemes {
|
||||
if size >= candidate.minModulusBytes && version <= candidate.maxVersion {
|
||||
sigAlgs = append(sigAlgs, candidate.scheme)
|
||||
}
|
||||
}
|
||||
case ed25519.PublicKey:
|
||||
sigAlgs = []SignatureScheme{Ed25519}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if cert.SupportedSignatureAlgorithms != nil {
|
||||
var filteredSigAlgs []SignatureScheme
|
||||
for _, sigAlg := range sigAlgs {
|
||||
if isSupportedSignatureAlgorithm(sigAlg, cert.SupportedSignatureAlgorithms) {
|
||||
filteredSigAlgs = append(filteredSigAlgs, sigAlg)
|
||||
}
|
||||
}
|
||||
return filteredSigAlgs
|
||||
}
|
||||
return sigAlgs
|
||||
}
|
||||
|
||||
// selectSignatureScheme picks a SignatureScheme from the peer's preference list
|
||||
// that works with the selected certificate. It's only called for protocol
|
||||
// versions that support signature algorithms, so TLS 1.2 and 1.3.
|
||||
func selectSignatureScheme(vers uint16, c *Certificate, peerAlgs []SignatureScheme) (SignatureScheme, error) {
|
||||
supportedAlgs := signatureSchemesForCertificate(vers, c)
|
||||
if len(supportedAlgs) == 0 {
|
||||
return 0, unsupportedCertificateError(c)
|
||||
}
|
||||
if len(peerAlgs) == 0 && vers == VersionTLS12 {
|
||||
// For TLS 1.2, if the client didn't send signature_algorithms then we
|
||||
// can assume that it supports SHA1. See RFC 5246, Section 7.4.1.4.1.
|
||||
peerAlgs = []SignatureScheme{PKCS1WithSHA1, ECDSAWithSHA1}
|
||||
}
|
||||
// Pick signature scheme in the peer's preference order, as our
|
||||
// preference order is not configurable.
|
||||
for _, preferredAlg := range peerAlgs {
|
||||
// [uTLS] SECTION BEGIN
|
||||
// if fips140tls.Required() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) {
|
||||
// continue
|
||||
// }
|
||||
// [uTLS] SECTION END
|
||||
if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) {
|
||||
return preferredAlg, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New("tls: peer doesn't support any of the certificate's signature algorithms")
|
||||
}
|
||||
|
||||
// unsupportedCertificateError returns a helpful error for certificates with
|
||||
// an unsupported private key.
|
||||
func unsupportedCertificateError(cert *Certificate) error {
|
||||
switch cert.PrivateKey.(type) {
|
||||
case rsa.PrivateKey, ecdsa.PrivateKey:
|
||||
return fmt.Errorf("tls: unsupported certificate: private key is %T, expected *%T",
|
||||
cert.PrivateKey, cert.PrivateKey)
|
||||
case *ed25519.PrivateKey:
|
||||
return fmt.Errorf("tls: unsupported certificate: private key is *ed25519.PrivateKey, expected ed25519.PrivateKey")
|
||||
}
|
||||
|
||||
signer, ok := cert.PrivateKey.(crypto.Signer)
|
||||
if !ok {
|
||||
return fmt.Errorf("tls: certificate private key (%T) does not implement crypto.Signer",
|
||||
cert.PrivateKey)
|
||||
}
|
||||
|
||||
switch pub := signer.Public().(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
switch pub.Curve {
|
||||
case elliptic.P256():
|
||||
case elliptic.P384():
|
||||
case elliptic.P521():
|
||||
default:
|
||||
return fmt.Errorf("tls: unsupported certificate curve (%s)", pub.Curve.Params().Name)
|
||||
}
|
||||
case *rsa.PublicKey:
|
||||
return fmt.Errorf("tls: certificate RSA key size too small for supported signature algorithms")
|
||||
case ed25519.PublicKey:
|
||||
default:
|
||||
return fmt.Errorf("tls: unsupported certificate key (%T)", pub)
|
||||
}
|
||||
|
||||
if cert.SupportedSignatureAlgorithms != nil {
|
||||
return fmt.Errorf("tls: peer doesn't support the certificate custom signature algorithms")
|
||||
}
|
||||
|
||||
return fmt.Errorf("tls: internal error: unsupported key (%T)", cert.PrivateKey)
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type cacheEntry struct {
|
||||
refs atomic.Int64
|
||||
cert *x509.Certificate
|
||||
}
|
||||
|
||||
// certCache implements an intern table for reference counted x509.Certificates,
|
||||
// implemented in a similar fashion to BoringSSL's CRYPTO_BUFFER_POOL. This
|
||||
// allows for a single x509.Certificate to be kept in memory and referenced from
|
||||
// multiple Conns. Returned references should not be mutated by callers. Certificates
|
||||
// are still safe to use after they are removed from the cache.
|
||||
//
|
||||
// Certificates are returned wrapped in an activeCert struct that should be held by
|
||||
// the caller. When references to the activeCert are freed, the number of references
|
||||
// to the certificate in the cache is decremented. Once the number of references
|
||||
// reaches zero, the entry is evicted from the cache.
|
||||
//
|
||||
// The main difference between this implementation and CRYPTO_BUFFER_POOL is that
|
||||
// CRYPTO_BUFFER_POOL is a more generic structure which supports blobs of data,
|
||||
// rather than specific structures. Since we only care about x509.Certificates,
|
||||
// certCache is implemented as a specific cache, rather than a generic one.
|
||||
//
|
||||
// See https://boringssl.googlesource.com/boringssl/+/master/include/openssl/pool.h
|
||||
// and https://boringssl.googlesource.com/boringssl/+/master/crypto/pool/pool.c
|
||||
// for the BoringSSL reference.
|
||||
type certCache struct {
|
||||
sync.Map
|
||||
}
|
||||
|
||||
var globalCertCache = new(certCache)
|
||||
|
||||
// activeCert is a handle to a certificate held in the cache. Once there are
|
||||
// no alive activeCerts for a given certificate, the certificate is removed
|
||||
// from the cache by a finalizer.
|
||||
type activeCert struct {
|
||||
cert *x509.Certificate
|
||||
}
|
||||
|
||||
// active increments the number of references to the entry, wraps the
|
||||
// certificate in the entry in an activeCert, and sets the finalizer.
|
||||
//
|
||||
// Note that there is a race between active and the finalizer set on the
|
||||
// returned activeCert, triggered if active is called after the ref count is
|
||||
// decremented such that refs may be > 0 when evict is called. We consider this
|
||||
// safe, since the caller holding an activeCert for an entry that is no longer
|
||||
// in the cache is fine, with the only side effect being the memory overhead of
|
||||
// there being more than one distinct reference to a certificate alive at once.
|
||||
func (cc *certCache) active(e *cacheEntry) *activeCert {
|
||||
e.refs.Add(1)
|
||||
a := &activeCert{e.cert}
|
||||
runtime.SetFinalizer(a, func(_ *activeCert) {
|
||||
if e.refs.Add(-1) == 0 {
|
||||
cc.evict(e)
|
||||
}
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// evict removes a cacheEntry from the cache.
|
||||
func (cc *certCache) evict(e *cacheEntry) {
|
||||
cc.Delete(string(e.cert.Raw))
|
||||
}
|
||||
|
||||
// newCert returns a x509.Certificate parsed from der. If there is already a copy
|
||||
// of the certificate in the cache, a reference to the existing certificate will
|
||||
// be returned. Otherwise, a fresh certificate will be added to the cache, and
|
||||
// the reference returned. The returned reference should not be mutated.
|
||||
func (cc *certCache) newCert(der []byte) (*activeCert, error) {
|
||||
if entry, ok := cc.Load(string(der)); ok {
|
||||
return cc.active(entry.(*cacheEntry)), nil
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entry := &cacheEntry{cert: cert}
|
||||
if entry, loaded := cc.LoadOrStore(string(der), entry); loaded {
|
||||
return cc.active(entry.(*cacheEntry)), nil
|
||||
}
|
||||
return cc.active(entry), nil
|
||||
}
|
||||
+725
@@ -0,0 +1,725 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/hmac"
|
||||
"crypto/rc4"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"hash"
|
||||
"runtime"
|
||||
_ "unsafe" // for linkname
|
||||
|
||||
"github.com/refraction-networking/utls/internal/boring"
|
||||
"golang.org/x/sys/cpu"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
// CipherSuite is a TLS cipher suite. Note that most functions in this package
|
||||
// accept and expose cipher suite IDs instead of this type.
|
||||
type CipherSuite struct {
|
||||
ID uint16
|
||||
Name string
|
||||
|
||||
// Supported versions is the list of TLS protocol versions that can
|
||||
// negotiate this cipher suite.
|
||||
SupportedVersions []uint16
|
||||
|
||||
// Insecure is true if the cipher suite has known security issues
|
||||
// due to its primitives, design, or implementation.
|
||||
Insecure bool
|
||||
}
|
||||
|
||||
var (
|
||||
supportedUpToTLS12 = []uint16{VersionTLS10, VersionTLS11, VersionTLS12}
|
||||
supportedOnlyTLS12 = []uint16{VersionTLS12}
|
||||
supportedOnlyTLS13 = []uint16{VersionTLS13}
|
||||
)
|
||||
|
||||
// CipherSuites returns a list of cipher suites currently implemented by this
|
||||
// package, excluding those with security issues, which are returned by
|
||||
// [InsecureCipherSuites].
|
||||
//
|
||||
// The list is sorted by ID. Note that the default cipher suites selected by
|
||||
// this package might depend on logic that can't be captured by a static list,
|
||||
// and might not match those returned by this function.
|
||||
func CipherSuites() []*CipherSuite {
|
||||
return []*CipherSuite{
|
||||
{TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", supportedOnlyTLS13, false},
|
||||
{TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384", supportedOnlyTLS13, false},
|
||||
{TLS_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256", supportedOnlyTLS13, false},
|
||||
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
|
||||
{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
|
||||
{TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
|
||||
{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
|
||||
{TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
|
||||
}
|
||||
}
|
||||
|
||||
// InsecureCipherSuites returns a list of cipher suites currently implemented by
|
||||
// this package and which have security issues.
|
||||
//
|
||||
// Most applications should not use the cipher suites in this list, and should
|
||||
// only use those returned by [CipherSuites].
|
||||
func InsecureCipherSuites() []*CipherSuite {
|
||||
// This list includes RC4, CBC_SHA256, and 3DES cipher suites. See
|
||||
// cipherSuitesPreferenceOrder for details.
|
||||
return []*CipherSuite{
|
||||
{TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
||||
{TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
|
||||
{TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, true},
|
||||
{TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, true},
|
||||
{TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
||||
{TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, true},
|
||||
{TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, true},
|
||||
{TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
||||
{TLS_ECDHE_RSA_WITH_RC4_128_SHA, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
|
||||
{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
|
||||
}
|
||||
}
|
||||
|
||||
// CipherSuiteName returns the standard name for the passed cipher suite ID
|
||||
// (e.g. "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"), or a fallback representation
|
||||
// of the ID value if the cipher suite is not implemented by this package.
|
||||
func CipherSuiteName(id uint16) string {
|
||||
for _, c := range CipherSuites() {
|
||||
if c.ID == id {
|
||||
return c.Name
|
||||
}
|
||||
}
|
||||
for _, c := range InsecureCipherSuites() {
|
||||
if c.ID == id {
|
||||
return c.Name
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("0x%04X", id)
|
||||
}
|
||||
|
||||
const (
|
||||
// suiteECDHE indicates that the cipher suite involves elliptic curve
|
||||
// Diffie-Hellman. This means that it should only be selected when the
|
||||
// client indicates that it supports ECC with a curve and point format
|
||||
// that we're happy with.
|
||||
suiteECDHE = 1 << iota
|
||||
// suiteECSign indicates that the cipher suite involves an ECDSA or
|
||||
// EdDSA signature and therefore may only be selected when the server's
|
||||
// certificate is ECDSA or EdDSA. If this is not set then the cipher suite
|
||||
// is RSA based.
|
||||
suiteECSign
|
||||
// suiteTLS12 indicates that the cipher suite should only be advertised
|
||||
// and accepted when using TLS 1.2.
|
||||
suiteTLS12
|
||||
// suiteSHA384 indicates that the cipher suite uses SHA384 as the
|
||||
// handshake hash.
|
||||
suiteSHA384
|
||||
)
|
||||
|
||||
// A cipherSuite is a TLS 1.0–1.2 cipher suite, and defines the key exchange
|
||||
// mechanism, as well as the cipher+MAC pair or the AEAD.
|
||||
type cipherSuite struct {
|
||||
id uint16
|
||||
// the lengths, in bytes, of the key material needed for each component.
|
||||
keyLen int
|
||||
macLen int
|
||||
ivLen int
|
||||
ka func(version uint16) keyAgreement
|
||||
// flags is a bitmask of the suite* values, above.
|
||||
flags int
|
||||
cipher func(key, iv []byte, isRead bool) any
|
||||
mac func(key []byte) hash.Hash
|
||||
aead func(key, fixedNonce []byte) aead
|
||||
}
|
||||
|
||||
var cipherSuites = []*cipherSuite{ // TODO: replace with a map, since the order doesn't matter.
|
||||
{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, 32, 0, 12, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadChaCha20Poly1305},
|
||||
{TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, 32, 0, 12, ecdheECDSAKA, suiteECDHE | suiteECSign | suiteTLS12, nil, nil, aeadChaCha20Poly1305},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadAESGCM},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECSign | suiteTLS12, nil, nil, aeadAESGCM},
|
||||
{TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECSign | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, ecdheRSAKA, suiteECDHE | suiteTLS12, cipherAES, macSHA256, nil},
|
||||
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, ecdheECDSAKA, suiteECDHE | suiteECSign | suiteTLS12, cipherAES, macSHA256, nil},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECSign, cipherAES, macSHA1, nil},
|
||||
{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},
|
||||
{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECSign, cipherAES, macSHA1, nil},
|
||||
{TLS_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, rsaKA, suiteTLS12, nil, nil, aeadAESGCM},
|
||||
{TLS_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, rsaKA, suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM},
|
||||
{TLS_RSA_WITH_AES_128_CBC_SHA256, 16, 32, 16, rsaKA, suiteTLS12, cipherAES, macSHA256, nil},
|
||||
{TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil},
|
||||
{TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil},
|
||||
{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, suiteECDHE, cipher3DES, macSHA1, nil},
|
||||
{TLS_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, rsaKA, 0, cipher3DES, macSHA1, nil},
|
||||
{TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, 0, cipherRC4, macSHA1, nil},
|
||||
{TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, suiteECDHE, cipherRC4, macSHA1, nil},
|
||||
{TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, suiteECDHE | suiteECSign, cipherRC4, macSHA1, nil},
|
||||
}
|
||||
|
||||
// selectCipherSuite returns the first TLS 1.0–1.2 cipher suite from ids which
|
||||
// is also in supportedIDs and passes the ok filter.
|
||||
func selectCipherSuite(ids, supportedIDs []uint16, ok func(*cipherSuite) bool) *cipherSuite {
|
||||
for _, id := range ids {
|
||||
candidate := cipherSuiteByID(id)
|
||||
if candidate == nil || !ok(candidate) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, suppID := range supportedIDs {
|
||||
if id == suppID {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A cipherSuiteTLS13 defines only the pair of the AEAD algorithm and hash
|
||||
// algorithm to be used with HKDF. See RFC 8446, Appendix B.4.
|
||||
type cipherSuiteTLS13 struct {
|
||||
id uint16
|
||||
keyLen int
|
||||
aead func(key, fixedNonce []byte) aead
|
||||
hash crypto.Hash
|
||||
}
|
||||
|
||||
// cipherSuitesTLS13 should be an internal detail,
|
||||
// but widely used packages access it using linkname.
|
||||
// Notable members of the hall of shame include:
|
||||
// - github.com/quic-go/quic-go
|
||||
// - github.com/sagernet/quic-go
|
||||
//
|
||||
// Do not remove or change the type signature.
|
||||
// See go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname cipherSuitesTLS13
|
||||
var cipherSuitesTLS13 = []*cipherSuiteTLS13{ // TODO: replace with a map.
|
||||
{TLS_AES_128_GCM_SHA256, 16, aeadAESGCMTLS13, crypto.SHA256},
|
||||
{TLS_CHACHA20_POLY1305_SHA256, 32, aeadChaCha20Poly1305, crypto.SHA256},
|
||||
{TLS_AES_256_GCM_SHA384, 32, aeadAESGCMTLS13, crypto.SHA384},
|
||||
}
|
||||
|
||||
// cipherSuitesPreferenceOrder is the order in which we'll select (on the
|
||||
// server) or advertise (on the client) TLS 1.0–1.2 cipher suites.
|
||||
//
|
||||
// Cipher suites are filtered but not reordered based on the application and
|
||||
// peer's preferences, meaning we'll never select a suite lower in this list if
|
||||
// any higher one is available. This makes it more defensible to keep weaker
|
||||
// cipher suites enabled, especially on the server side where we get the last
|
||||
// word, since there are no known downgrade attacks on cipher suites selection.
|
||||
//
|
||||
// The list is sorted by applying the following priority rules, stopping at the
|
||||
// first (most important) applicable one:
|
||||
//
|
||||
// - Anything else comes before RC4
|
||||
//
|
||||
// RC4 has practically exploitable biases. See https://www.rc4nomore.com.
|
||||
//
|
||||
// - Anything else comes before CBC_SHA256
|
||||
//
|
||||
// SHA-256 variants of the CBC ciphersuites don't implement any Lucky13
|
||||
// countermeasures. See https://www.isg.rhul.ac.uk/tls/Lucky13.html and
|
||||
// https://www.imperialviolet.org/2013/02/04/luckythirteen.html.
|
||||
//
|
||||
// - Anything else comes before 3DES
|
||||
//
|
||||
// 3DES has 64-bit blocks, which makes it fundamentally susceptible to
|
||||
// birthday attacks. See https://sweet32.info.
|
||||
//
|
||||
// - ECDHE comes before anything else
|
||||
//
|
||||
// Once we got the broken stuff out of the way, the most important
|
||||
// property a cipher suite can have is forward secrecy. We don't
|
||||
// implement FFDHE, so that means ECDHE.
|
||||
//
|
||||
// - AEADs come before CBC ciphers
|
||||
//
|
||||
// Even with Lucky13 countermeasures, MAC-then-Encrypt CBC cipher suites
|
||||
// are fundamentally fragile, and suffered from an endless sequence of
|
||||
// padding oracle attacks. See https://eprint.iacr.org/2015/1129,
|
||||
// https://www.imperialviolet.org/2014/12/08/poodleagain.html, and
|
||||
// https://blog.cloudflare.com/yet-another-padding-oracle-in-openssl-cbc-ciphersuites/.
|
||||
//
|
||||
// - AES comes before ChaCha20
|
||||
//
|
||||
// When AES hardware is available, AES-128-GCM and AES-256-GCM are faster
|
||||
// than ChaCha20Poly1305.
|
||||
//
|
||||
// When AES hardware is not available, AES-128-GCM is one or more of: much
|
||||
// slower, way more complex, and less safe (because not constant time)
|
||||
// than ChaCha20Poly1305.
|
||||
//
|
||||
// We use this list if we think both peers have AES hardware, and
|
||||
// cipherSuitesPreferenceOrderNoAES otherwise.
|
||||
//
|
||||
// - AES-128 comes before AES-256
|
||||
//
|
||||
// The only potential advantages of AES-256 are better multi-target
|
||||
// margins, and hypothetical post-quantum properties. Neither apply to
|
||||
// TLS, and AES-256 is slower due to its four extra rounds (which don't
|
||||
// contribute to the advantages above).
|
||||
//
|
||||
// - ECDSA comes before RSA
|
||||
//
|
||||
// The relative order of ECDSA and RSA cipher suites doesn't matter,
|
||||
// as they depend on the certificate. Pick one to get a stable order.
|
||||
var cipherSuitesPreferenceOrder = []uint16{
|
||||
// AEADs w/ ECDHE
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
|
||||
// CBC w/ ECDHE
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
|
||||
// AEADs w/o ECDHE
|
||||
TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
|
||||
// CBC w/o ECDHE
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
|
||||
// 3DES
|
||||
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
|
||||
// CBC_SHA256
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
|
||||
// RC4
|
||||
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
TLS_RSA_WITH_RC4_128_SHA,
|
||||
}
|
||||
|
||||
var cipherSuitesPreferenceOrderNoAES = []uint16{
|
||||
// ChaCha20Poly1305
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
|
||||
// AES-GCM w/ ECDHE
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
|
||||
// The rest of cipherSuitesPreferenceOrder.
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
TLS_RSA_WITH_RC4_128_SHA,
|
||||
}
|
||||
|
||||
// disabledCipherSuites are not used unless explicitly listed in Config.CipherSuites.
|
||||
var disabledCipherSuites = map[uint16]bool{
|
||||
// CBC_SHA256
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: true,
|
||||
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: true,
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256: true,
|
||||
|
||||
// RC4
|
||||
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: true,
|
||||
TLS_ECDHE_RSA_WITH_RC4_128_SHA: true,
|
||||
TLS_RSA_WITH_RC4_128_SHA: true,
|
||||
}
|
||||
|
||||
// rsaKexCiphers contains the ciphers which use RSA based key exchange,
|
||||
// which we also disable by default unless a GODEBUG is set.
|
||||
var rsaKexCiphers = map[uint16]bool{
|
||||
TLS_RSA_WITH_RC4_128_SHA: true,
|
||||
TLS_RSA_WITH_3DES_EDE_CBC_SHA: true,
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA: true,
|
||||
TLS_RSA_WITH_AES_256_CBC_SHA: true,
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256: true,
|
||||
TLS_RSA_WITH_AES_128_GCM_SHA256: true,
|
||||
TLS_RSA_WITH_AES_256_GCM_SHA384: true,
|
||||
}
|
||||
|
||||
// tdesCiphers contains 3DES ciphers,
|
||||
// which we also disable by default unless a GODEBUG is set.
|
||||
var tdesCiphers = map[uint16]bool{
|
||||
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: true,
|
||||
TLS_RSA_WITH_3DES_EDE_CBC_SHA: true,
|
||||
}
|
||||
|
||||
var (
|
||||
// Keep in sync with crypto/internal/fips140/aes/gcm.supportsAESGCM.
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
|
||||
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
|
||||
|
||||
hasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
|
||||
)
|
||||
|
||||
var aesgcmCiphers = map[uint16]bool{
|
||||
// TLS 1.2
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: true,
|
||||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: true,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: true,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: true,
|
||||
// TLS 1.3
|
||||
TLS_AES_128_GCM_SHA256: true,
|
||||
TLS_AES_256_GCM_SHA384: true,
|
||||
}
|
||||
|
||||
// aesgcmPreferred returns whether the first known cipher in the preference list
|
||||
// is an AES-GCM cipher, implying the peer has hardware support for it.
|
||||
func aesgcmPreferred(ciphers []uint16) bool {
|
||||
for _, cID := range ciphers {
|
||||
if c := cipherSuiteByID(cID); c != nil {
|
||||
return aesgcmCiphers[cID]
|
||||
}
|
||||
if c := cipherSuiteTLS13ByID(cID); c != nil {
|
||||
return aesgcmCiphers[cID]
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cipherRC4(key, iv []byte, isRead bool) any {
|
||||
cipher, _ := rc4.NewCipher(key)
|
||||
return cipher
|
||||
}
|
||||
|
||||
func cipher3DES(key, iv []byte, isRead bool) any {
|
||||
block, _ := des.NewTripleDESCipher(key)
|
||||
if isRead {
|
||||
return cipher.NewCBCDecrypter(block, iv)
|
||||
}
|
||||
return cipher.NewCBCEncrypter(block, iv)
|
||||
}
|
||||
|
||||
func cipherAES(key, iv []byte, isRead bool) any {
|
||||
block, _ := aes.NewCipher(key)
|
||||
if isRead {
|
||||
return cipher.NewCBCDecrypter(block, iv)
|
||||
}
|
||||
return cipher.NewCBCEncrypter(block, iv)
|
||||
}
|
||||
|
||||
// macSHA1 returns a SHA-1 based constant time MAC.
|
||||
func macSHA1(key []byte) hash.Hash {
|
||||
h := sha1.New
|
||||
// The BoringCrypto SHA1 does not have a constant-time
|
||||
// checksum function, so don't try to use it.
|
||||
if !boring.Enabled {
|
||||
h = newConstantTimeHash(h)
|
||||
}
|
||||
return hmac.New(h, key)
|
||||
}
|
||||
|
||||
// macSHA256 returns a SHA-256 based MAC. This is only supported in TLS 1.2 and
|
||||
// is currently only used in disabled-by-default cipher suites.
|
||||
func macSHA256(key []byte) hash.Hash {
|
||||
return hmac.New(sha256.New, key)
|
||||
}
|
||||
|
||||
type aead interface {
|
||||
cipher.AEAD
|
||||
|
||||
// explicitNonceLen returns the number of bytes of explicit nonce
|
||||
// included in each record. This is eight for older AEADs and
|
||||
// zero for modern ones.
|
||||
explicitNonceLen() int
|
||||
}
|
||||
|
||||
const (
|
||||
aeadNonceLength = 12
|
||||
noncePrefixLength = 4
|
||||
)
|
||||
|
||||
// prefixNonceAEAD wraps an AEAD and prefixes a fixed portion of the nonce to
|
||||
// each call.
|
||||
type prefixNonceAEAD struct {
|
||||
// nonce contains the fixed part of the nonce in the first four bytes.
|
||||
nonce [aeadNonceLength]byte
|
||||
aead cipher.AEAD
|
||||
}
|
||||
|
||||
func (f *prefixNonceAEAD) NonceSize() int { return aeadNonceLength - noncePrefixLength }
|
||||
func (f *prefixNonceAEAD) Overhead() int { return f.aead.Overhead() }
|
||||
func (f *prefixNonceAEAD) explicitNonceLen() int { return f.NonceSize() }
|
||||
|
||||
func (f *prefixNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
|
||||
copy(f.nonce[4:], nonce)
|
||||
return f.aead.Seal(out, f.nonce[:], plaintext, additionalData)
|
||||
}
|
||||
|
||||
func (f *prefixNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
copy(f.nonce[4:], nonce)
|
||||
return f.aead.Open(out, f.nonce[:], ciphertext, additionalData)
|
||||
}
|
||||
|
||||
// xorNonceAEAD wraps an AEAD by XORing in a fixed pattern to the nonce
|
||||
// before each call.
|
||||
type xorNonceAEAD struct {
|
||||
nonceMask [aeadNonceLength]byte
|
||||
aead cipher.AEAD
|
||||
}
|
||||
|
||||
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
|
||||
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
|
||||
func (f *xorNonceAEAD) explicitNonceLen() int { return 0 }
|
||||
|
||||
func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
|
||||
for i, b := range nonce {
|
||||
f.nonceMask[4+i] ^= b
|
||||
}
|
||||
result := f.aead.Seal(out, f.nonceMask[:], plaintext, additionalData)
|
||||
for i, b := range nonce {
|
||||
f.nonceMask[4+i] ^= b
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *xorNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
for i, b := range nonce {
|
||||
f.nonceMask[4+i] ^= b
|
||||
}
|
||||
result, err := f.aead.Open(out, f.nonceMask[:], ciphertext, additionalData)
|
||||
for i, b := range nonce {
|
||||
f.nonceMask[4+i] ^= b
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func aeadAESGCM(key, noncePrefix []byte) aead {
|
||||
if len(noncePrefix) != noncePrefixLength {
|
||||
panic("tls: internal error: wrong nonce length")
|
||||
}
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var aead cipher.AEAD
|
||||
if boring.Enabled {
|
||||
aead, err = boring.NewGCMTLS(aes)
|
||||
} else {
|
||||
boring.Unreachable()
|
||||
// [uTLS] SECTION BEGIN
|
||||
// aead, err = gcm.NewGCMForTLS12(aes.(*fipsaes.Block))
|
||||
aead, err = cipher.NewGCM(aes)
|
||||
// [uTLS] SECTION END
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ret := &prefixNonceAEAD{aead: aead}
|
||||
copy(ret.nonce[:], noncePrefix)
|
||||
return ret
|
||||
}
|
||||
|
||||
// aeadAESGCMTLS13 should be an internal detail,
|
||||
// but widely used packages access it using linkname.
|
||||
// Notable members of the hall of shame include:
|
||||
// - github.com/xtls/xray-core
|
||||
// - github.com/v2fly/v2ray-core
|
||||
//
|
||||
// Do not remove or change the type signature.
|
||||
// See go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname aeadAESGCMTLS13
|
||||
func aeadAESGCMTLS13(key, nonceMask []byte) aead {
|
||||
if len(nonceMask) != aeadNonceLength {
|
||||
panic("tls: internal error: wrong nonce length")
|
||||
}
|
||||
aes, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var aead cipher.AEAD
|
||||
if boring.Enabled {
|
||||
aead, err = boring.NewGCMTLS13(aes)
|
||||
} else {
|
||||
boring.Unreachable()
|
||||
// [uTLS] SECTION BEGIN
|
||||
// aead, err = gcm.NewGCMForTLS13(aes.(*fipsaes.Block))
|
||||
aead, err = cipher.NewGCM(aes)
|
||||
// [uTLS] SECTION END
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ret := &xorNonceAEAD{aead: aead}
|
||||
copy(ret.nonceMask[:], nonceMask)
|
||||
return ret
|
||||
}
|
||||
|
||||
func aeadChaCha20Poly1305(key, nonceMask []byte) aead {
|
||||
if len(nonceMask) != aeadNonceLength {
|
||||
panic("tls: internal error: wrong nonce length")
|
||||
}
|
||||
aead, err := chacha20poly1305.New(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ret := &xorNonceAEAD{aead: aead}
|
||||
copy(ret.nonceMask[:], nonceMask)
|
||||
return ret
|
||||
}
|
||||
|
||||
type constantTimeHash interface {
|
||||
hash.Hash
|
||||
ConstantTimeSum(b []byte) []byte
|
||||
}
|
||||
|
||||
// cthWrapper wraps any hash.Hash that implements ConstantTimeSum, and replaces
|
||||
// with that all calls to Sum. It's used to obtain a ConstantTimeSum-based HMAC.
|
||||
type cthWrapper struct {
|
||||
h constantTimeHash
|
||||
}
|
||||
|
||||
func (c *cthWrapper) Size() int { return c.h.Size() }
|
||||
func (c *cthWrapper) BlockSize() int { return c.h.BlockSize() }
|
||||
func (c *cthWrapper) Reset() { c.h.Reset() }
|
||||
func (c *cthWrapper) Write(p []byte) (int, error) { return c.h.Write(p) }
|
||||
func (c *cthWrapper) Sum(b []byte) []byte { return c.h.ConstantTimeSum(b) }
|
||||
|
||||
func newConstantTimeHash(h func() hash.Hash) func() hash.Hash {
|
||||
boring.Unreachable()
|
||||
return func() hash.Hash {
|
||||
return &cthWrapper{h().(constantTimeHash)}
|
||||
}
|
||||
}
|
||||
|
||||
// tls10MAC implements the TLS 1.0 MAC function. RFC 2246, Section 6.2.3.
|
||||
func tls10MAC(h hash.Hash, out, seq, header, data, extra []byte) []byte {
|
||||
h.Reset()
|
||||
h.Write(seq)
|
||||
h.Write(header)
|
||||
h.Write(data)
|
||||
res := h.Sum(out)
|
||||
if extra != nil {
|
||||
h.Write(extra)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func rsaKA(version uint16) keyAgreement {
|
||||
return rsaKeyAgreement{}
|
||||
}
|
||||
|
||||
func ecdheECDSAKA(version uint16) keyAgreement {
|
||||
return &ecdheKeyAgreement{
|
||||
isRSA: false,
|
||||
version: version,
|
||||
}
|
||||
}
|
||||
|
||||
func ecdheRSAKA(version uint16) keyAgreement {
|
||||
return &ecdheKeyAgreement{
|
||||
isRSA: true,
|
||||
version: version,
|
||||
}
|
||||
}
|
||||
|
||||
// mutualCipherSuite returns a cipherSuite given a list of supported
|
||||
// ciphersuites and the id requested by the peer.
|
||||
func mutualCipherSuite(have []uint16, want uint16) *cipherSuite {
|
||||
for _, id := range have {
|
||||
if id == want {
|
||||
return cipherSuiteByID(id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cipherSuiteByID(id uint16) *cipherSuite {
|
||||
for _, cipherSuite := range utlsSupportedCipherSuites {
|
||||
if cipherSuite.id == id {
|
||||
return cipherSuite
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mutualCipherSuiteTLS13(have []uint16, want uint16) *cipherSuiteTLS13 {
|
||||
for _, id := range have {
|
||||
if id == want {
|
||||
return cipherSuiteTLS13ByID(id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13 {
|
||||
for _, cipherSuite := range cipherSuitesTLS13 {
|
||||
if cipherSuite.id == id {
|
||||
return cipherSuite
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A list of cipher suite IDs that are, or have been, implemented by this
|
||||
// package.
|
||||
//
|
||||
// See https://www.iana.org/assignments/tls-parameters/tls-parameters.xml
|
||||
const (
|
||||
// TLS 1.0 - 1.2 cipher suites.
|
||||
TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005
|
||||
TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000a
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002f
|
||||
TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035
|
||||
TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003c
|
||||
TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009c
|
||||
TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009d
|
||||
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA uint16 = 0xc007
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xc009
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xc00a
|
||||
TLS_ECDHE_RSA_WITH_RC4_128_SHA uint16 = 0xc011
|
||||
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xc012
|
||||
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xc013
|
||||
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xc014
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc023
|
||||
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc027
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02f
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02b
|
||||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc030
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc02c
|
||||
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca8
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca9
|
||||
|
||||
// TLS 1.3 cipher suites.
|
||||
TLS_AES_128_GCM_SHA256 uint16 = 0x1301
|
||||
TLS_AES_256_GCM_SHA384 uint16 = 0x1302
|
||||
TLS_CHACHA20_POLY1305_SHA256 uint16 = 0x1303
|
||||
|
||||
// TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator
|
||||
// that the client is doing version fallback. See RFC 7507.
|
||||
TLS_FALLBACK_SCSV uint16 = 0x5600
|
||||
|
||||
// Legacy names for the corresponding cipher suites with the correct _SHA256
|
||||
// suffix, retained for backward compatibility.
|
||||
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 = TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 = TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
||||
)
|
||||
+1819
File diff suppressed because it is too large
Load Diff
+120
@@ -0,0 +1,120 @@
|
||||
// Code generated by "stringer -linecomment -type=SignatureScheme,CurveID,ClientAuthType -output=common_string.go"; DO NOT EDIT.
|
||||
|
||||
package tls
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[PKCS1WithSHA256-1025]
|
||||
_ = x[PKCS1WithSHA384-1281]
|
||||
_ = x[PKCS1WithSHA512-1537]
|
||||
_ = x[PSSWithSHA256-2052]
|
||||
_ = x[PSSWithSHA384-2053]
|
||||
_ = x[PSSWithSHA512-2054]
|
||||
_ = x[ECDSAWithP256AndSHA256-1027]
|
||||
_ = x[ECDSAWithP384AndSHA384-1283]
|
||||
_ = x[ECDSAWithP521AndSHA512-1539]
|
||||
_ = x[Ed25519-2055]
|
||||
_ = x[PKCS1WithSHA1-513]
|
||||
_ = x[ECDSAWithSHA1-515]
|
||||
}
|
||||
|
||||
const (
|
||||
_SignatureScheme_name_0 = "PKCS1WithSHA1"
|
||||
_SignatureScheme_name_1 = "ECDSAWithSHA1"
|
||||
_SignatureScheme_name_2 = "PKCS1WithSHA256"
|
||||
_SignatureScheme_name_3 = "ECDSAWithP256AndSHA256"
|
||||
_SignatureScheme_name_4 = "PKCS1WithSHA384"
|
||||
_SignatureScheme_name_5 = "ECDSAWithP384AndSHA384"
|
||||
_SignatureScheme_name_6 = "PKCS1WithSHA512"
|
||||
_SignatureScheme_name_7 = "ECDSAWithP521AndSHA512"
|
||||
_SignatureScheme_name_8 = "PSSWithSHA256PSSWithSHA384PSSWithSHA512Ed25519"
|
||||
)
|
||||
|
||||
var (
|
||||
_SignatureScheme_index_8 = [...]uint8{0, 13, 26, 39, 46}
|
||||
)
|
||||
|
||||
func (i SignatureScheme) String() string {
|
||||
switch {
|
||||
case i == 513:
|
||||
return _SignatureScheme_name_0
|
||||
case i == 515:
|
||||
return _SignatureScheme_name_1
|
||||
case i == 1025:
|
||||
return _SignatureScheme_name_2
|
||||
case i == 1027:
|
||||
return _SignatureScheme_name_3
|
||||
case i == 1281:
|
||||
return _SignatureScheme_name_4
|
||||
case i == 1283:
|
||||
return _SignatureScheme_name_5
|
||||
case i == 1537:
|
||||
return _SignatureScheme_name_6
|
||||
case i == 1539:
|
||||
return _SignatureScheme_name_7
|
||||
case 2052 <= i && i <= 2055:
|
||||
i -= 2052
|
||||
return _SignatureScheme_name_8[_SignatureScheme_index_8[i]:_SignatureScheme_index_8[i+1]]
|
||||
default:
|
||||
return "SignatureScheme(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[CurveP256-23]
|
||||
_ = x[CurveP384-24]
|
||||
_ = x[CurveP521-25]
|
||||
_ = x[X25519-29]
|
||||
_ = x[X25519MLKEM768-4588]
|
||||
}
|
||||
|
||||
const (
|
||||
_CurveID_name_0 = "CurveP256CurveP384CurveP521"
|
||||
_CurveID_name_1 = "X25519"
|
||||
_CurveID_name_2 = "X25519MLKEM768"
|
||||
)
|
||||
|
||||
var (
|
||||
_CurveID_index_0 = [...]uint8{0, 9, 18, 27}
|
||||
)
|
||||
|
||||
func (i CurveID) String() string {
|
||||
switch {
|
||||
case 23 <= i && i <= 25:
|
||||
i -= 23
|
||||
return _CurveID_name_0[_CurveID_index_0[i]:_CurveID_index_0[i+1]]
|
||||
case i == 29:
|
||||
return _CurveID_name_1
|
||||
case i == 4588:
|
||||
return _CurveID_name_2
|
||||
default:
|
||||
return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[NoClientCert-0]
|
||||
_ = x[RequestClientCert-1]
|
||||
_ = x[RequireAnyClientCert-2]
|
||||
_ = x[VerifyClientCertIfGiven-3]
|
||||
_ = x[RequireAndVerifyClientCert-4]
|
||||
}
|
||||
|
||||
const _ClientAuthType_name = "NoClientCertRequestClientCertRequireAnyClientCertVerifyClientCertIfGivenRequireAndVerifyClientCert"
|
||||
|
||||
var _ClientAuthType_index = [...]uint8{0, 12, 29, 49, 72, 98}
|
||||
|
||||
func (i ClientAuthType) String() string {
|
||||
if i < 0 || i >= ClientAuthType(len(_ClientAuthType_index)-1) {
|
||||
return "ClientAuthType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _ClientAuthType_name[_ClientAuthType_index[i]:_ClientAuthType_index[i+1]]
|
||||
}
|
||||
+1701
File diff suppressed because it is too large
Load Diff
+140
@@ -0,0 +1,140 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"slices"
|
||||
_ "unsafe" // for linkname
|
||||
)
|
||||
|
||||
// Defaults are collected in this file to allow distributions to more easily patch
|
||||
// them to apply local policies.
|
||||
|
||||
// var tlsmlkem = godebug.New("tlsmlkem") [uTLS]
|
||||
|
||||
// defaultCurvePreferences is the default set of supported key exchanges, as
|
||||
// well as the preference order.
|
||||
func defaultCurvePreferences() []CurveID {
|
||||
// [uTLS section begins]
|
||||
// if tlsmlkem.Value() == "0" {
|
||||
// return []CurveID{X25519, CurveP256, CurveP384, CurveP521}
|
||||
// }
|
||||
// [uTLS section ends]
|
||||
|
||||
return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521}
|
||||
}
|
||||
|
||||
// defaultSupportedSignatureAlgorithms contains the signature and hash algorithms that
|
||||
// the code advertises as supported in a TLS 1.2+ ClientHello and in a TLS 1.2+
|
||||
// CertificateRequest. The two fields are merged to match with TLS 1.3.
|
||||
// Note that in TLS 1.2, the ECDSA algorithms are not constrained to P-256, etc.
|
||||
var defaultSupportedSignatureAlgorithms = []SignatureScheme{
|
||||
PSSWithSHA256,
|
||||
ECDSAWithP256AndSHA256,
|
||||
Ed25519,
|
||||
PSSWithSHA384,
|
||||
PSSWithSHA512,
|
||||
PKCS1WithSHA256,
|
||||
PKCS1WithSHA384,
|
||||
PKCS1WithSHA512,
|
||||
ECDSAWithP384AndSHA384,
|
||||
ECDSAWithP521AndSHA512,
|
||||
PKCS1WithSHA1,
|
||||
ECDSAWithSHA1,
|
||||
}
|
||||
|
||||
// [uTLS section begins]
|
||||
// var tlsrsakex = godebug.New("tlsrsakex")
|
||||
// var tls3des = godebug.New("tls3des")
|
||||
// [uTLS section ends]
|
||||
|
||||
func defaultCipherSuites() []uint16 {
|
||||
suites := slices.Clone(cipherSuitesPreferenceOrder)
|
||||
return slices.DeleteFunc(suites, func(c uint16) bool {
|
||||
return disabledCipherSuites[c] ||
|
||||
// [uTLS section begins]
|
||||
// tlsrsakex.Value() != "1" && rsaKexCiphers[c] ||
|
||||
// tls3des.Value() != "1" && tdesCiphers[c]
|
||||
rsaKexCiphers[c] ||
|
||||
tdesCiphers[c]
|
||||
// [uTLS section ends]
|
||||
})
|
||||
}
|
||||
|
||||
// defaultCipherSuitesTLS13 is also the preference order, since there are no
|
||||
// disabled by default TLS 1.3 cipher suites. The same AES vs ChaCha20 logic as
|
||||
// cipherSuitesPreferenceOrder applies.
|
||||
//
|
||||
// defaultCipherSuitesTLS13 should be an internal detail,
|
||||
// but widely used packages access it using linkname.
|
||||
// Notable members of the hall of shame include:
|
||||
// - github.com/quic-go/quic-go
|
||||
// - github.com/sagernet/quic-go
|
||||
//
|
||||
// Do not remove or change the type signature.
|
||||
// See go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname defaultCipherSuitesTLS13
|
||||
var defaultCipherSuitesTLS13 = []uint16{
|
||||
TLS_AES_128_GCM_SHA256,
|
||||
TLS_AES_256_GCM_SHA384,
|
||||
TLS_CHACHA20_POLY1305_SHA256,
|
||||
}
|
||||
|
||||
// defaultCipherSuitesTLS13NoAES should be an internal detail,
|
||||
// but widely used packages access it using linkname.
|
||||
// Notable members of the hall of shame include:
|
||||
// - github.com/quic-go/quic-go
|
||||
// - github.com/sagernet/quic-go
|
||||
//
|
||||
// Do not remove or change the type signature.
|
||||
// See go.dev/issue/67401.
|
||||
//
|
||||
//go:linkname defaultCipherSuitesTLS13NoAES
|
||||
var defaultCipherSuitesTLS13NoAES = []uint16{
|
||||
TLS_CHACHA20_POLY1305_SHA256,
|
||||
TLS_AES_128_GCM_SHA256,
|
||||
TLS_AES_256_GCM_SHA384,
|
||||
}
|
||||
|
||||
// The FIPS-only policies below match BoringSSL's
|
||||
// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2.
|
||||
// https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa
|
||||
|
||||
var defaultSupportedVersionsFIPS = []uint16{
|
||||
VersionTLS12,
|
||||
VersionTLS13,
|
||||
}
|
||||
|
||||
// defaultCurvePreferencesFIPS are the FIPS-allowed curves,
|
||||
// in preference order (most preferable first).
|
||||
var defaultCurvePreferencesFIPS = []CurveID{CurveP256, CurveP384}
|
||||
|
||||
// defaultSupportedSignatureAlgorithmsFIPS currently are a subset of
|
||||
// defaultSupportedSignatureAlgorithms without Ed25519 and SHA-1.
|
||||
var defaultSupportedSignatureAlgorithmsFIPS = []SignatureScheme{
|
||||
PSSWithSHA256,
|
||||
PSSWithSHA384,
|
||||
PSSWithSHA512,
|
||||
PKCS1WithSHA256,
|
||||
ECDSAWithP256AndSHA256,
|
||||
PKCS1WithSHA384,
|
||||
ECDSAWithP384AndSHA384,
|
||||
PKCS1WithSHA512,
|
||||
}
|
||||
|
||||
// defaultCipherSuitesFIPS are the FIPS-allowed cipher suites.
|
||||
var defaultCipherSuitesFIPS = []uint16{
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
|
||||
// defaultCipherSuitesTLS13FIPS are the FIPS-allowed cipher suites for TLS 1.3.
|
||||
var defaultCipherSuitesTLS13FIPS = []uint16{
|
||||
TLS_AES_128_GCM_SHA256,
|
||||
TLS_AES_256_GCM_SHA384,
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2023, Gaukas Wang
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# Dict TLS
|
||||
|
||||
This is a vendored version of [godicttls](https://github.com/gaukas/godicttls)
|
||||
|
||||
Below is a copy of the original README.md
|
||||
|
||||
# godicttls
|
||||
Dictionary for TLS written in Go providing bidirectional mapping values to their names, plus enum convenience for values.
|
||||
|
||||
Last Update with data fetched from [IANA](www.iana.org) in March 2023:
|
||||
- Transport Layer Security (TLS) Parameters [link](https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml)
|
||||
- Transport Layer Security (TLS) Extensions [link](https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml)
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-6
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
Alert_close_notify uint8 = 0
|
||||
Alert_unexpected_message uint8 = 10
|
||||
Alert_bad_record_mac uint8 = 20
|
||||
Alert_decryption_failed uint8 = 21
|
||||
Alert_record_overflow uint8 = 22
|
||||
Alert_decompression_failure uint8 = 30
|
||||
Alert_handshake_failure uint8 = 40
|
||||
Alert_no_certificate uint8 = 41
|
||||
Alert_bad_certificate uint8 = 42
|
||||
Alert_unsupported_certificate uint8 = 43
|
||||
Alert_certificate_revoked uint8 = 44
|
||||
Alert_certificate_expired uint8 = 45
|
||||
Alert_certificate_unknown uint8 = 46
|
||||
Alert_illegal_parameter uint8 = 47
|
||||
Alert_unknown_ca uint8 = 48
|
||||
Alert_access_denied uint8 = 49
|
||||
Alert_decode_error uint8 = 50
|
||||
Alert_decrypt_error uint8 = 51
|
||||
Alert_too_many_cids_requested uint8 = 52
|
||||
Alert_export_restriction uint8 = 60
|
||||
Alert_protocol_version uint8 = 70
|
||||
Alert_insufficient_security uint8 = 71
|
||||
Alert_internal_error uint8 = 80
|
||||
Alert_inappropriate_fallback uint8 = 86
|
||||
Alert_user_canceled uint8 = 90
|
||||
Alert_no_renegotiation uint8 = 100
|
||||
Alert_missing_extension uint8 = 109
|
||||
Alert_unsupported_extension uint8 = 110
|
||||
Alert_certificate_unobtainable uint8 = 111
|
||||
Alert_unrecognized_name uint8 = 112
|
||||
Alert_bad_certificate_status_response uint8 = 113
|
||||
Alert_bad_certificate_hash_value uint8 = 114
|
||||
Alert_unknown_psk_identity uint8 = 115
|
||||
Alert_certificate_required uint8 = 116
|
||||
Alert_no_application_protocol uint8 = 120
|
||||
)
|
||||
|
||||
var DictAlertValueIndexed = map[uint8]string{
|
||||
0: "close_notify",
|
||||
10: "unexpected_message",
|
||||
20: "bad_record_mac",
|
||||
21: "decryption_failed",
|
||||
22: "record_overflow",
|
||||
30: "decompression_failure",
|
||||
40: "handshake_failure",
|
||||
41: "no_certificate",
|
||||
42: "bad_certificate",
|
||||
43: "unsupported_certificate",
|
||||
44: "certificate_revoked",
|
||||
45: "certificate_expired",
|
||||
46: "certificate_unknown",
|
||||
47: "illegal_parameter",
|
||||
48: "unknown_ca",
|
||||
49: "access_denied",
|
||||
50: "decode_error",
|
||||
51: "decrypt_error",
|
||||
52: "too_many_cids_requested",
|
||||
60: "export_restriction",
|
||||
70: "protocol_version",
|
||||
71: "insufficient_security",
|
||||
80: "internal_error",
|
||||
86: "inappropriate_fallback",
|
||||
90: "user_canceled",
|
||||
100: "no_renegotiation",
|
||||
109: "missing_extension",
|
||||
110: "unsupported_extension",
|
||||
111: "certificate_unobtainable",
|
||||
112: "unrecognized_name",
|
||||
113: "bad_certificate_status_response",
|
||||
114: "bad_certificate_hash_value",
|
||||
115: "unknown_psk_identity",
|
||||
116: "certificate_required",
|
||||
120: "no_application_protocol",
|
||||
}
|
||||
|
||||
var DictAlertNameIndexed = map[string]uint8{
|
||||
"close_notify": 0,
|
||||
"unexpected_message": 10,
|
||||
"bad_record_mac": 20,
|
||||
"decryption_failed": 21,
|
||||
"record_overflow": 22,
|
||||
"decompression_failure": 30,
|
||||
"handshake_failure": 40,
|
||||
"no_certificate": 41,
|
||||
"bad_certificate": 42,
|
||||
"unsupported_certificate": 43,
|
||||
"certificate_revoked": 44,
|
||||
"certificate_expired": 45,
|
||||
"certificate_unknown": 46,
|
||||
"illegal_parameter": 47,
|
||||
"unknown_ca": 48,
|
||||
"access_denied": 49,
|
||||
"decode_error": 50,
|
||||
"decrypt_error": 51,
|
||||
"too_many_cids_requested": 52,
|
||||
"export_restriction": 60,
|
||||
"protocol_version": 70,
|
||||
"insufficient_security": 71,
|
||||
"internal_error": 80,
|
||||
"inappropriate_fallback": 86,
|
||||
"user_canceled": 90,
|
||||
"no_renegotiation": 100,
|
||||
"missing_extension": 109,
|
||||
"unsupported_extension": 110,
|
||||
"certificate_unobtainable": 111,
|
||||
"unrecognized_name": 112,
|
||||
"bad_certificate_status_response": 113,
|
||||
"bad_certificate_hash_value": 114,
|
||||
"unknown_psk_identity": 115,
|
||||
"certificate_required": 116,
|
||||
"no_application_protocol": 120,
|
||||
}
|
||||
Generated
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#authorization-data
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
AuthData_x509_attr_cert uint16 = 0
|
||||
AuthData_saml_assertion uint16 = 1
|
||||
AuthData_x509_attr_cert_url uint16 = 2
|
||||
AuthData_saml_assertion_url uint16 = 3
|
||||
AuthData_keynote_assertion_list uint16 = 64
|
||||
AuthData_keynote_assertion_list_url uint16 = 65
|
||||
AuthData_dtcp_authorization uint16 = 66
|
||||
)
|
||||
|
||||
var DictAuthorizationDataFormatValueIndexed = map[uint16]string{
|
||||
0: "x509_attr_cert",
|
||||
1: "saml_assertion",
|
||||
2: "x509_attr_cert_url",
|
||||
3: "saml_assertion_url",
|
||||
64: "keynote_assertion_list",
|
||||
65: "keynote_assertion_list_url",
|
||||
66: "dtcp_authorization",
|
||||
}
|
||||
|
||||
var DictAuthorizationDataFormatNameIndexed = map[string]uint16{
|
||||
"x509_attr_cert": 0,
|
||||
"saml_assertion": 1,
|
||||
"x509_attr_cert_url": 2,
|
||||
"saml_assertion_url": 3,
|
||||
"Unassigned": 0,
|
||||
"keynote_assertion_list": 64,
|
||||
"keynote_assertion_list_url": 65,
|
||||
"dtcp_authorization": 66,
|
||||
}
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#cachedinformationtype
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
CachedInformationType_cert uint8 = 1
|
||||
CachedInformationType_cert_req uint8 = 2
|
||||
)
|
||||
|
||||
var DictCachedInformationTypeValueIndexed = map[uint8]string{
|
||||
1: "cert",
|
||||
2: "cert_req",
|
||||
}
|
||||
|
||||
var DictCachedInformationTypeNameIndexed = map[string]uint8{
|
||||
"cert": 1,
|
||||
"cert_req": 2,
|
||||
}
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-certificate-compression-algorithm-ids
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
CertCompAlg_zlib uint16 = 1
|
||||
CertCompAlg_brotli uint16 = 2
|
||||
CertCompAlg_zstd uint16 = 3
|
||||
)
|
||||
|
||||
var DictCertificateCompressionAlgorithmValueIndexed = map[uint16]string{
|
||||
1: "zlib",
|
||||
2: "brotli",
|
||||
3: "zstd",
|
||||
}
|
||||
|
||||
var DictCertificateCompressionAlgorithmNameIndexed = map[string]uint16{
|
||||
"zlib": 1,
|
||||
"brotli": 2,
|
||||
"zstd": 3,
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#certificate-status
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
CertStatusType_ocsp uint8 = 1
|
||||
CertStatusType_ocsp_multi uint8 = 2
|
||||
)
|
||||
|
||||
var DictCertificateStatusTypeValueIndexed = map[uint8]string{
|
||||
1: "ocsp",
|
||||
2: "ocsp_multi",
|
||||
}
|
||||
|
||||
var DictCertificateStatusTypeNameIndexed = map[string]uint8{
|
||||
"ocsp": 1,
|
||||
"ocsp_multi": 2,
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-extensiontype-values-3
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
CertType_X509 uint8 = 0
|
||||
CertType_OpenPGP uint8 = 1
|
||||
CertType_Raw_Public_Key uint8 = 2
|
||||
CertType_1609Dot2 uint8 = 3
|
||||
)
|
||||
|
||||
var DictCertificateTypeValueIndexed = map[uint8]string{
|
||||
0: "X509",
|
||||
1: "OpenPGP",
|
||||
2: "Raw Public Key",
|
||||
3: "1609Dot2",
|
||||
}
|
||||
|
||||
var DictCertificateTypeNameIndexed = map[string]uint8{
|
||||
"X509": 0,
|
||||
"OpenPGP": 1,
|
||||
"Raw Public Key": 2,
|
||||
"1609Dot2": 3,
|
||||
}
|
||||
+1084
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-2
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
ClientCertTypeIdentifier_rsa_sign uint8 = 1
|
||||
ClientCertTypeIdentifier_dss_sign uint8 = 2
|
||||
ClientCertTypeIdentifier_rsa_fixed_dh uint8 = 3
|
||||
ClientCertTypeIdentifier_dss_fixed_dh uint8 = 4
|
||||
ClientCertTypeIdentifier_rsa_ephemeral_dh uint8 = 5
|
||||
ClientCertTypeIdentifier_dss_ephemeral_dh uint8 = 6
|
||||
ClientCertTypeIdentifier_fortezza_dms uint8 = 20
|
||||
ClientCertTypeIdentifier_ecdsa_sign uint8 = 64
|
||||
ClientCertTypeIdentifier_rsa_fixed_ecdh uint8 = 65
|
||||
ClientCertTypeIdentifier_ecdsa_fixed_ecdh uint8 = 66
|
||||
ClientCertTypeIdentifier_gost_sign256 uint8 = 67
|
||||
ClientCertTypeIdentifier_gost_sign512 uint8 = 68
|
||||
)
|
||||
|
||||
var DictClientCertificateTypeIdentifierValueIndexed = map[uint8]string{
|
||||
1: "rsa_sign",
|
||||
2: "dss_sign",
|
||||
3: "rsa_fixed_dh",
|
||||
4: "dss_fixed_dh",
|
||||
5: "rsa_ephemeral_dh",
|
||||
6: "dss_ephemeral_dh",
|
||||
20: "fortezza_dms",
|
||||
64: "ecdsa_sign",
|
||||
65: "rsa_fixed_ecdh",
|
||||
66: "ecdsa_fixed_ecdh",
|
||||
67: "gost_sign256",
|
||||
68: "gost_sign512",
|
||||
}
|
||||
|
||||
var DictClientCertificateTypeIdentifierNameIndexed = map[string]uint8{
|
||||
"rsa_sign": 1,
|
||||
"dss_sign": 2,
|
||||
"rsa_fixed_dh": 3,
|
||||
"dss_fixed_dh": 4,
|
||||
"rsa_ephemeral_dh": 5,
|
||||
"dss_ephemeral_dh": 6,
|
||||
"fortezza_dms": 20,
|
||||
"ecdsa_sign": 64,
|
||||
"rsa_fixed_ecdh": 65,
|
||||
"ecdsa_fixed_ecdh": 66,
|
||||
"gost_sign256": 67,
|
||||
"gost_sign512": 68,
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/comp-meth-ids/comp-meth-ids-2.csv
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
CompMeth_NULL uint8 = 0
|
||||
CompMeth_DEFLATE uint8 = 1
|
||||
CompMeth_LZS uint8 = 64
|
||||
)
|
||||
|
||||
var DictCompMethValueIndexed = map[uint8]string{
|
||||
0: "NULL",
|
||||
1: "DEFLATE",
|
||||
64: "LZS",
|
||||
}
|
||||
|
||||
var DictCompMethNameIndexed = map[string]uint8{
|
||||
"NULL": 0,
|
||||
"DEFLATE": 1,
|
||||
"LZS": 64,
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-5
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
ContentType_change_cipher_spec uint8 = 20
|
||||
ContentType_alert uint8 = 21
|
||||
ContentType_handshake uint8 = 22
|
||||
ContentType_application_data uint8 = 23
|
||||
ContentType_heartbeat uint8 = 24
|
||||
ContentType_tls12_cid uint8 = 25
|
||||
ContentType_ACK uint8 = 26
|
||||
)
|
||||
|
||||
var DictContentTypeValueIndexed = map[uint8]string{
|
||||
20: "change_cipher_spec",
|
||||
21: "alert",
|
||||
22: "handshake",
|
||||
23: "application_data",
|
||||
24: "heartbeat",
|
||||
25: "tls12_cid",
|
||||
26: "ACK",
|
||||
}
|
||||
|
||||
var DictContentTypeNameIndexed = map[string]uint8{
|
||||
"change_cipher_spec": 20,
|
||||
"alert": 21,
|
||||
"handshake": 22,
|
||||
"application_data": 23,
|
||||
"heartbeat": 24,
|
||||
"tls12_cid": 25,
|
||||
"ACK": 26,
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-10
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
ECCurve_explicit_prime uint16 = 1
|
||||
ECCurve_explicit_char2 uint16 = 2
|
||||
ECCurve_named_curve uint16 = 3
|
||||
)
|
||||
|
||||
var DictECCurveTypeValueIndexed = map[uint16]string{
|
||||
1: "explicit_prime",
|
||||
2: "explicit_char2",
|
||||
3: "named_curve",
|
||||
}
|
||||
|
||||
var DictECCurveTypeNameIndexed = map[string]uint16{
|
||||
"explicit_prime": 1,
|
||||
"explicit_char2": 2,
|
||||
"named_curve": 3,
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-9
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
ECPoint_uncompressed uint8 = 0
|
||||
ECPoint_ansiX962_compressed_prime uint8 = 1
|
||||
ECPoint_ansiX962_compressed_char2 uint8 = 2
|
||||
)
|
||||
|
||||
var DictECPointFormatValueIndexed = map[uint8]string{
|
||||
0: "uncompressed",
|
||||
1: "ansiX962_compressed_prime",
|
||||
2: "ansiX962_compressed_char2",
|
||||
}
|
||||
|
||||
var DictECPointFormatNameIndexed = map[string]uint8{
|
||||
"uncompressed": 0,
|
||||
"ansiX962_compressed_prime": 1,
|
||||
"ansiX962_compressed_char2": 2,
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#tls-extensiontype-values-1
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
ExtType_server_name uint16 = 0
|
||||
ExtType_max_fragment_length uint16 = 1
|
||||
ExtType_client_certificate_url uint16 = 2
|
||||
ExtType_trusted_ca_keys uint16 = 3
|
||||
ExtType_truncated_hmac uint16 = 4
|
||||
ExtType_status_request uint16 = 5
|
||||
ExtType_user_mapping uint16 = 6
|
||||
ExtType_client_authz uint16 = 7
|
||||
ExtType_server_authz uint16 = 8
|
||||
ExtType_cert_type uint16 = 9
|
||||
ExtType_supported_groups uint16 = 10
|
||||
ExtType_ec_point_formats uint16 = 11
|
||||
ExtType_srp uint16 = 12
|
||||
ExtType_signature_algorithms uint16 = 13
|
||||
ExtType_use_srtp uint16 = 14
|
||||
ExtType_heartbeat uint16 = 15
|
||||
ExtType_application_layer_protocol_negotiation uint16 = 16
|
||||
ExtType_status_request_v2 uint16 = 17
|
||||
ExtType_signed_certificate_timestamp uint16 = 18
|
||||
ExtType_client_certificate_type uint16 = 19
|
||||
ExtType_server_certificate_type uint16 = 20
|
||||
ExtType_padding uint16 = 21
|
||||
ExtType_encrypt_then_mac uint16 = 22
|
||||
ExtType_extended_master_secret uint16 = 23
|
||||
ExtType_token_binding uint16 = 24
|
||||
ExtType_cached_info uint16 = 25
|
||||
ExtType_tls_lts uint16 = 26
|
||||
ExtType_compress_certificate uint16 = 27
|
||||
ExtType_record_size_limit uint16 = 28
|
||||
ExtType_pwd_protect uint16 = 29
|
||||
ExtType_pwd_clear uint16 = 30
|
||||
ExtType_password_salt uint16 = 31
|
||||
ExtType_ticket_pinning uint16 = 32
|
||||
ExtType_tls_cert_with_extern_psk uint16 = 33
|
||||
ExtType_delegated_credentials uint16 = 34 // IANA name: delegated_credentials, IETF name: delegated_credential
|
||||
ExtType_session_ticket uint16 = 35
|
||||
ExtType_TLMSP uint16 = 36
|
||||
ExtType_TLMSP_proxying uint16 = 37
|
||||
ExtType_TLMSP_delegate uint16 = 38
|
||||
ExtType_supported_ekt_ciphers uint16 = 39
|
||||
ExtType_pre_shared_key uint16 = 41
|
||||
ExtType_early_data uint16 = 42
|
||||
ExtType_supported_versions uint16 = 43
|
||||
ExtType_cookie uint16 = 44
|
||||
ExtType_psk_key_exchange_modes uint16 = 45
|
||||
ExtType_certificate_authorities uint16 = 47
|
||||
ExtType_oid_filters uint16 = 48
|
||||
ExtType_post_handshake_auth uint16 = 49
|
||||
ExtType_signature_algorithms_cert uint16 = 50
|
||||
ExtType_key_share uint16 = 51
|
||||
ExtType_transparency_info uint16 = 52
|
||||
ExtType_connection_id_deprecated uint16 = 53 // deprecated
|
||||
ExtType_connection_id uint16 = 54
|
||||
ExtType_external_id_hash uint16 = 55
|
||||
ExtType_external_session_id uint16 = 56
|
||||
ExtType_quic_transport_parameters uint16 = 57
|
||||
ExtType_ticket_request uint16 = 58
|
||||
ExtType_dnssec_chain uint16 = 59
|
||||
ExtType_renegotiation_info uint16 = 65281
|
||||
)
|
||||
|
||||
// Not IANA assigned
|
||||
const (
|
||||
ExtType_next_protocol_negotiation uint16 = 13172 // https://datatracker.ietf.org/doc/html/draft-agl-tls-nextprotoneg-04
|
||||
ExtType_application_settings uint16 = 17513 // https://www.ietf.org/archive/id/draft-vvv-tls-alps-01.html
|
||||
ExtType_application_settings_new uint16 = 17613 // https://www.ietf.org/archive/id/draft-vvv-tls-alps-01.html
|
||||
ExtType_channel_id_old uint16 = 30031 // https://datatracker.ietf.org/doc/html/draft-balfanz-tls-channelid-01
|
||||
ExtType_channel_id uint16 = 30032 // https://datatracker.ietf.org/doc/html/draft-balfanz-tls-channelid-01
|
||||
)
|
||||
|
||||
var DictExtTypeValueIndexed = map[uint16]string{
|
||||
0: "server_name",
|
||||
1: "max_fragment_length",
|
||||
2: "client_certificate_url",
|
||||
3: "trusted_ca_keys",
|
||||
4: "truncated_hmac",
|
||||
5: "status_request",
|
||||
6: "user_mapping",
|
||||
7: "client_authz",
|
||||
8: "server_authz",
|
||||
9: "cert_type",
|
||||
10: "supported_groups",
|
||||
11: "ec_point_formats",
|
||||
12: "srp",
|
||||
13: "signature_algorithms",
|
||||
14: "use_srtp",
|
||||
15: "heartbeat",
|
||||
16: "application_layer_protocol_negotiation",
|
||||
17: "status_request_v2",
|
||||
18: "signed_certificate_timestamp",
|
||||
19: "client_certificate_type",
|
||||
20: "server_certificate_type",
|
||||
21: "padding",
|
||||
22: "encrypt_then_mac",
|
||||
23: "extended_master_secret",
|
||||
24: "token_binding",
|
||||
25: "cached_info",
|
||||
26: "tls_lts",
|
||||
27: "compress_certificate",
|
||||
28: "record_size_limit",
|
||||
29: "pwd_protect",
|
||||
30: "pwd_clear",
|
||||
31: "password_salt",
|
||||
32: "ticket_pinning",
|
||||
33: "tls_cert_with_extern_psk",
|
||||
34: "delegated_credentials", // IANA name: delegated_credentials, IETF name: delegated_credential
|
||||
35: "session_ticket",
|
||||
36: "TLMSP",
|
||||
37: "TLMSP_proxying",
|
||||
38: "TLMSP_delegate",
|
||||
39: "supported_ekt_ciphers",
|
||||
41: "pre_shared_key",
|
||||
42: "early_data",
|
||||
43: "supported_versions",
|
||||
44: "cookie",
|
||||
45: "psk_key_exchange_modes",
|
||||
47: "certificate_authorities",
|
||||
48: "oid_filters",
|
||||
49: "post_handshake_auth",
|
||||
50: "signature_algorithms_cert",
|
||||
51: "key_share",
|
||||
52: "transparency_info",
|
||||
53: "connection_id_deprecated", // deprecated
|
||||
54: "connection_id",
|
||||
55: "external_id_hash",
|
||||
56: "external_session_id",
|
||||
57: "quic_transport_parameters",
|
||||
58: "ticket_request",
|
||||
59: "dnssec_chain",
|
||||
65281: "renegotiation_info",
|
||||
|
||||
13172: "next_protocol_negotiation",
|
||||
17513: "application_settings",
|
||||
17613: "application_settings_new",
|
||||
30031: "channel_id_old",
|
||||
30032: "channel_id",
|
||||
}
|
||||
|
||||
var DictExtTypeNameIndexed = map[string]uint16{
|
||||
"server_name": 0,
|
||||
"max_fragment_length": 1,
|
||||
"client_certificate_url": 2,
|
||||
"trusted_ca_keys": 3,
|
||||
"truncated_hmac": 4,
|
||||
"status_request": 5,
|
||||
"user_mapping": 6,
|
||||
"client_authz": 7,
|
||||
"server_authz": 8,
|
||||
"cert_type": 9,
|
||||
"supported_groups": 10,
|
||||
"ec_point_formats": 11,
|
||||
"srp": 12,
|
||||
"signature_algorithms": 13,
|
||||
"use_srtp": 14,
|
||||
"heartbeat": 15,
|
||||
"application_layer_protocol_negotiation": 16,
|
||||
"status_request_v2": 17,
|
||||
"signed_certificate_timestamp": 18,
|
||||
"client_certificate_type": 19,
|
||||
"server_certificate_type": 20,
|
||||
"padding": 21,
|
||||
"encrypt_then_mac": 22,
|
||||
"extended_master_secret": 23,
|
||||
"token_binding": 24,
|
||||
"cached_info": 25,
|
||||
"tls_lts": 26,
|
||||
"compress_certificate": 27,
|
||||
"record_size_limit": 28,
|
||||
"pwd_protect": 29,
|
||||
"pwd_clear": 30,
|
||||
"password_salt": 31,
|
||||
"ticket_pinning": 32,
|
||||
"tls_cert_with_extern_psk": 33,
|
||||
"delegated_credentials": 34, // IANA name: delegated_credentials
|
||||
"delegated_credential": 34, // IETF name: delegated_credential
|
||||
"session_ticket": 35,
|
||||
"TLMSP": 36,
|
||||
"TLMSP_proxying": 37,
|
||||
"TLMSP_delegate": 38,
|
||||
"supported_ekt_ciphers": 39,
|
||||
"pre_shared_key": 41,
|
||||
"early_data": 42,
|
||||
"supported_versions": 43,
|
||||
"cookie": 44,
|
||||
"psk_key_exchange_modes": 45,
|
||||
"certificate_authorities": 47,
|
||||
"oid_filters": 48,
|
||||
"post_handshake_auth": 49,
|
||||
"signature_algorithms_cert": 50,
|
||||
"key_share": 51,
|
||||
"transparency_info": 52,
|
||||
"connection_id_deprecated": 53, // deprecated
|
||||
"connection_id": 54,
|
||||
"external_id_hash": 55,
|
||||
"external_session_id": 56,
|
||||
"quic_transport_parameters": 57,
|
||||
"ticket_request": 58,
|
||||
"dnssec_chain": 59,
|
||||
"renegotiation_info": 65281,
|
||||
|
||||
"next_protocol_negotiation": 13172,
|
||||
"application_settings": 17513,
|
||||
"application_settings_new": 17613,
|
||||
"channel_id_old": 30031,
|
||||
"channel_id": 30032,
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-7
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
HandshakeType_hello_request uint8 = 0
|
||||
HandshakeType_client_hello uint8 = 1
|
||||
HandshakeType_server_hello uint8 = 2
|
||||
HandshakeType_hello_verify_request uint8 = 3
|
||||
HandshakeType_new_session_ticket uint8 = 4
|
||||
HandshakeType_end_of_early_data uint8 = 5
|
||||
HandshakeType_hello_retry_request uint8 = 6
|
||||
HandshakeType_encrypted_extensions uint8 = 8
|
||||
HandshakeType_request_connection_id uint8 = 9
|
||||
HandshakeType_new_connection_id uint8 = 10
|
||||
HandshakeType_certificate uint8 = 11
|
||||
HandshakeType_server_key_exchange uint8 = 12
|
||||
HandshakeType_certificate_request uint8 = 13
|
||||
HandshakeType_server_hello_done uint8 = 14
|
||||
HandshakeType_certificate_verify uint8 = 15
|
||||
HandshakeType_client_key_exchange uint8 = 16
|
||||
HandshakeType_client_certificate_request uint8 = 17
|
||||
HandshakeType_finished uint8 = 20
|
||||
HandshakeType_certificate_url uint8 = 21
|
||||
HandshakeType_certificate_status uint8 = 22
|
||||
HandshakeType_supplemental_data uint8 = 23
|
||||
HandshakeType_key_update uint8 = 24
|
||||
HandshakeType_compressed_certificate uint8 = 25
|
||||
HandshakeType_ekt_key uint8 = 26
|
||||
HandshakeType_message_hash uint8 = 254
|
||||
|
||||
// Not IANA assigned
|
||||
HandshakeType_next_protocol uint8 = 67
|
||||
)
|
||||
|
||||
var DictHandshakeTypeValueIndexed = map[uint8]string{
|
||||
0: "hello_request",
|
||||
1: "client_hello",
|
||||
2: "server_hello",
|
||||
3: "hello_verify_request",
|
||||
4: "new_session_ticket",
|
||||
5: "end_of_early_data",
|
||||
6: "hello_retry_request",
|
||||
7: "Unassigned",
|
||||
8: "encrypted_extensions",
|
||||
9: "request_connection_id",
|
||||
10: "new_connection_id",
|
||||
11: "certificate",
|
||||
12: "server_key_exchange",
|
||||
13: "certificate_request",
|
||||
14: "server_hello_done",
|
||||
15: "certificate_verify",
|
||||
16: "client_key_exchange",
|
||||
17: "client_certificate_request",
|
||||
20: "finished",
|
||||
21: "certificate_url",
|
||||
22: "certificate_status",
|
||||
23: "supplemental_data",
|
||||
24: "key_update",
|
||||
25: "compressed_certificate",
|
||||
26: "ekt_key",
|
||||
254: "message_hash",
|
||||
|
||||
67: "next_protocol",
|
||||
}
|
||||
|
||||
var DictHandshakeTypeNameIndexed = map[string]uint8{
|
||||
"hello_request": 0,
|
||||
"client_hello": 1,
|
||||
"server_hello": 2,
|
||||
"hello_verify_request": 3,
|
||||
"new_session_ticket": 4,
|
||||
"end_of_early_data": 5,
|
||||
"hello_retry_request": 6,
|
||||
"encrypted_extensions": 8,
|
||||
"request_connection_id": 9,
|
||||
"new_connection_id": 10,
|
||||
"certificate": 11,
|
||||
"server_key_exchange": 12,
|
||||
"certificate_request": 13,
|
||||
"server_hello_done": 14,
|
||||
"certificate_verify": 15,
|
||||
"client_key_exchange": 16,
|
||||
"client_certificate_request": 17,
|
||||
"finished": 20,
|
||||
"certificate_url": 21,
|
||||
"certificate_status": 22,
|
||||
"supplemental_data": 23,
|
||||
"key_update": 24,
|
||||
"compressed_certificate": 25,
|
||||
"ekt_key": 26,
|
||||
"message_hash": 254,
|
||||
|
||||
"next_protocol": 67,
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
HashAlg_none uint8 = 0 // deprecated in TLS 1.3
|
||||
HashAlg_md5 uint8 = 1 // deprecated in TLS 1.3
|
||||
HashAlg_sha1 uint8 = 2
|
||||
HashAlg_sha224 uint8 = 3 // deprecated in TLS 1.3
|
||||
HashAlg_sha256 uint8 = 4
|
||||
HashAlg_sha384 uint8 = 5
|
||||
HashAlg_sha512 uint8 = 6
|
||||
HashAlg_Intrinsic uint8 = 8
|
||||
)
|
||||
|
||||
var DictHashAlgorithmValueIndexed = map[uint8]string{
|
||||
0: "none",
|
||||
1: "md5",
|
||||
2: "sha1",
|
||||
3: "sha224",
|
||||
4: "sha256",
|
||||
5: "sha384",
|
||||
6: "sha512",
|
||||
7: "Reserved",
|
||||
8: "Intrinsic",
|
||||
}
|
||||
|
||||
var DictHashAlgorithmNameIndexed = map[string]uint8{
|
||||
"none": 0,
|
||||
"md5": 1,
|
||||
"sha1": 2,
|
||||
"sha224": 3,
|
||||
"sha256": 4,
|
||||
"sha384": 5,
|
||||
"sha512": 6,
|
||||
"Reserved": 7,
|
||||
"Intrinsic": 8,
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/heartbeat-message-types.csv
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
HeartbeatMessage_request uint8 = 1
|
||||
HeartbeatMessage_response uint8 = 2
|
||||
)
|
||||
|
||||
var DictHeartbeatMessageTypeValueIndexed = map[uint8]string{
|
||||
1: "heartbeat_request",
|
||||
2: "heartbeat_response",
|
||||
}
|
||||
|
||||
var DictHeartbeatMessageTypeNameIndexed = map[string]uint8{
|
||||
"heartbeat_request": 1,
|
||||
"heartbeat_response": 2,
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/heartbeat-modes.csv
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
HeartbeatMode_peer_allowed_to_send uint8 = 1
|
||||
HeartbeatMode_peer_not_allowed_to_send uint8 = 2
|
||||
)
|
||||
|
||||
var DictHeartbeatModeValueIndexed = map[uint8]string{
|
||||
1: "peer_allowed_to_send",
|
||||
2: "peer_not_allowed_to_send",
|
||||
}
|
||||
|
||||
var DictHeartbeatModeNameIndexed = map[string]uint8{
|
||||
"peer_allowed_to_send": 1,
|
||||
"peer_not_allowed_to_send": 2,
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/hpke/hpke.xhtml
|
||||
// last updated: December 2023
|
||||
|
||||
const (
|
||||
AEAD_AES_128_GCM uint16 = 0x0001 // NIST Special Publication 800-38D
|
||||
AEAD_AES_256_GCM uint16 = 0x0002 // NIST Special Publication 800-38D
|
||||
AEAD_CHACHA20_POLY1305 uint16 = 0x0003 // RFC 8439
|
||||
AEAD_EXPORT_ONLY uint16 = 0xFFFF // RFC 9180
|
||||
)
|
||||
|
||||
var DictAEADIdentifierValueIndexed = map[uint16]string{
|
||||
0x0000: "Reserved", // RFC 9180
|
||||
0x0001: "AES-128-GCM",
|
||||
0x0002: "AES-256-GCM",
|
||||
0x0003: "ChaCha20Poly1305",
|
||||
0xFFFF: "Export-only", // RFC 9180
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/hpke/hpke.xhtml
|
||||
// last updated: December 2023
|
||||
|
||||
const (
|
||||
HKDF_SHA256 uint16 = 0x0001
|
||||
HKDF_SHA384 uint16 = 0x0002
|
||||
HKDF_SHA512 uint16 = 0x0003
|
||||
)
|
||||
|
||||
var DictKDFIdentifierValueIndexed = map[uint16]string{
|
||||
0x0000: "Reserved", // RFC 9180
|
||||
0x0001: "HKDF_SHA256",
|
||||
0x0002: "HKDF_SHA384",
|
||||
0x0003: "HKDF_SHA512",
|
||||
}
|
||||
|
||||
var DictKDFIdentifierNameIndexed = map[string]uint16{
|
||||
"Reserved": 0x0000, // RFC 9180
|
||||
"HKDF_SHA256": 0x0001,
|
||||
"HKDF_SHA384": 0x0002,
|
||||
"HKDF_SHA512": 0x0003,
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/hpke/hpke.xhtml
|
||||
// last updated: December 2023
|
||||
|
||||
const (
|
||||
DHKEM_P256_HKDF_SHA256 uint16 = 0x0010 // RFC 5869
|
||||
DHKEM_P384_HKDF_SHA384 uint16 = 0x0011 // RFC 5869
|
||||
DHKEM_P521_HKDF_SHA512 uint16 = 0x0012 // RFC 5869
|
||||
DHKEM_CP256_HKDF_SHA256 uint16 = 0x0013 // RFC 6090
|
||||
DHKEM_CP384_HKDF_SHA384 uint16 = 0x0014 // RFC 6090
|
||||
DHKEM_CP521_HKDF_SHA512 uint16 = 0x0015 // RFC 6090
|
||||
DHKEM_SECP256K1_HKDF_SHA256 uint16 = 0x0016 // draft-wahby-cfrg-hpke-kem-secp256k1-01
|
||||
|
||||
DHKEM_X25519_HKDF_SHA256 uint16 = 0x0020 // RFC 7748
|
||||
DHKEM_X448_HKDF_SHA512 uint16 = 0x0021 // RFC 7748
|
||||
|
||||
X25519_KYBER768_DRAFT00 uint16 = 0x0030 // draft-westerbaan-cfrg-hpke-xyber768d00-02
|
||||
)
|
||||
|
||||
var DictKEMIdentifierValueIndexed = map[uint16]string{
|
||||
0x0000: "Reserved", // RFC 9180
|
||||
|
||||
0x0010: "DHKEM(P-256, HKDF-SHA256)",
|
||||
0x0011: "DHKEM(P-384, HKDF-SHA384)",
|
||||
0x0012: "DHKEM(P-521, HKDF-SHA512)",
|
||||
0x0013: "DHKEM(CP-256, HKDF-SHA256)",
|
||||
0x0014: "DHKEM(CP-384, HKDF-SHA384)",
|
||||
0x0015: "DHKEM(CP-521, HKDF-SHA512)",
|
||||
0x0016: "DHKEM(secp256k1, HKDF-SHA256)",
|
||||
|
||||
0x0020: "DHKEM(X25519, HKDF-SHA256)",
|
||||
0x0021: "DHKEM(X448, HKDF-SHA512)",
|
||||
|
||||
0x0030: "X25519Kyber768Draft00",
|
||||
}
|
||||
|
||||
var DictKEMIdentifierNameIndexed = map[string]uint16{
|
||||
"Reserved": 0x0000, // RFC 9180
|
||||
|
||||
"DHKEM(P-256, HKDF-SHA256)": 0x0010,
|
||||
"DHKEM(P-384, HKDF-SHA384)": 0x0011,
|
||||
"DHKEM(P-521, HKDF-SHA512)": 0x0012,
|
||||
"DHKEM(CP-256, HKDF-SHA256)": 0x0013,
|
||||
"DHKEM(CP-384, HKDF-SHA384)": 0x0014,
|
||||
"DHKEM(CP-521, HKDF-SHA512)": 0x0015,
|
||||
"DHKEM(secp256k1, HKDF-SHA256)": 0x0016,
|
||||
|
||||
"DHKEM(X25519, HKDF-SHA256)": 0x0020,
|
||||
"DHKEM(X448, HKDF-SHA512)": 0x0021,
|
||||
|
||||
"X25519Kyber768Draft00": 0x0030,
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-pskkeyexchangemode.csv
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
PSKKeyExchangeMode_psk_ke uint8 = 0
|
||||
PSKKeyExchangeMode_psk_dhe_ke uint8 = 1
|
||||
)
|
||||
|
||||
var DictPSKKeyExchangeModeValueIndexed = map[uint8]string{
|
||||
0: "psk_ke",
|
||||
1: "psk_dhe_ke",
|
||||
}
|
||||
|
||||
var DictPSKKeyExchangeModeNameIndexed = map[string]uint8{
|
||||
"psk_ke": 0,
|
||||
"psk_dhe_ke": 1,
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/quic/quic.xhtml#quic-frame-types
|
||||
// last updated: July 2023
|
||||
|
||||
const (
|
||||
QUICFrameType_PADDING uint8 = 0x00
|
||||
QUICFrameType_PING uint8 = 0x01
|
||||
QUICFrameType_ACK uint8 = 0x02
|
||||
QUICFrameType_ACK_ecn uint8 = 0x03
|
||||
QUICFrameType_RESET_STREAM uint8 = 0x04
|
||||
QUICFrameType_STOP_SENDING uint8 = 0x05
|
||||
QUICFrameType_CRYPTO uint8 = 0x06
|
||||
QUICFrameType_NEW_TOKEN uint8 = 0x07
|
||||
QUICFrameType_STREAM uint8 = 0x08
|
||||
QUICFrameType_STREAM_fin uint8 = 0x09
|
||||
QUICFrameType_STREAM_len uint8 = 0x0a
|
||||
QUICFrameType_STREAM_len_fin uint8 = 0x0b
|
||||
QUICFrameType_STREAM_off uint8 = 0x0c
|
||||
QUICFrameType_STREAM_off_fin uint8 = 0x0d
|
||||
QUICFrameType_STREAM_off_len uint8 = 0x0e
|
||||
QUICFrameType_STREAM_off_len_fin uint8 = 0x0f
|
||||
QUICFrameType_MAX_DATA uint8 = 0x10
|
||||
QUICFrameType_MAX_STREAM_DATA uint8 = 0x11
|
||||
QUICFrameType_MAX_STREAMS_bidi uint8 = 0x12
|
||||
QUICFrameType_MAX_STREAMS_uni uint8 = 0x13
|
||||
QUICFrameType_DATA_BLOCKED uint8 = 0x14
|
||||
QUICFrameType_STREAM_DATA_BLOCKED uint8 = 0x15
|
||||
QUICFrameType_STREAMS_BLOCKED_bidi uint8 = 0x16
|
||||
QUICFrameType_STREAMS_BLOCKED_uni uint8 = 0x17
|
||||
QUICFrameType_NEW_CONNECTION_ID uint8 = 0x18
|
||||
QUICFrameType_RETIRE_CONNECTION_ID uint8 = 0x19
|
||||
QUICFrameType_PATH_CHALLENGE uint8 = 0x1a
|
||||
QUICFrameType_PATH_RESPONSE uint8 = 0x1b
|
||||
QUICFrameType_CONNECTION_CLOSE uint8 = 0x1c
|
||||
QUICFrameType_CONNECTION_CLOSE_app uint8 = 0x1d
|
||||
QUICFrameType_HANDSHAKE_DONE uint8 = 0x1e
|
||||
QUICFrameType_DATAGRAM uint8 = 0x30 // RFC9221
|
||||
QUICFrameType_DATAGRAM_len uint8 = 0x31 // RFC9221
|
||||
)
|
||||
|
||||
var DictQUICFrameTypeValueIndexed = map[uint8]string{
|
||||
0x00: "PADDING",
|
||||
0x01: "PING",
|
||||
0x02: "ACK",
|
||||
0x03: "ACK_ecn",
|
||||
0x04: "RESET_STREAM",
|
||||
0x05: "STOP_SENDING",
|
||||
0x06: "CRYPTO",
|
||||
0x07: "NEW_TOKEN",
|
||||
0x08: "STREAM",
|
||||
0x09: "STREAM_fin",
|
||||
0x0a: "STREAM_len",
|
||||
0x0b: "STREAM_len_fin",
|
||||
0x0c: "STREAM_off",
|
||||
0x0d: "STREAM_off_fin",
|
||||
0x0e: "STREAM_off_len",
|
||||
0x0f: "STREAM_off_len_fin",
|
||||
0x10: "MAX_DATA",
|
||||
0x11: "MAX_STREAM_DATA",
|
||||
0x12: "MAX_STREAMS_bidi",
|
||||
0x13: "MAX_STREAMS_uni",
|
||||
0x14: "DATA_BLOCKED",
|
||||
0x15: "STREAM_DATA_BLOCKED",
|
||||
0x16: "STREAMS_BLOCKED_bidi",
|
||||
0x17: "STREAMS_BLOCKED_uni",
|
||||
0x18: "NEW_CONNECTION_ID",
|
||||
0x19: "RETIRE_CONNECTION_ID",
|
||||
0x1a: "PATH_CHALLENGE",
|
||||
0x1b: "PATH_RESPONSE",
|
||||
0x1c: "CONNECTION_CLOSE",
|
||||
0x1d: "CONNECTION_CLOSE_app",
|
||||
0x1e: "HANDSHAKE_DONE",
|
||||
0x30: "DATAGRAM",
|
||||
0x31: "DATAGRAM_len",
|
||||
}
|
||||
|
||||
var DictQUICFrameTypeNameIndexed = map[string]uint8{
|
||||
"PADDING": 0x00,
|
||||
"PING": 0x01,
|
||||
"ACK": 0x02,
|
||||
"ACK_ecn": 0x03,
|
||||
"RESET_STREAM": 0x04,
|
||||
"STOP_SENDING": 0x05,
|
||||
"CRYPTO": 0x06,
|
||||
"NEW_TOKEN": 0x07,
|
||||
"STREAM": 0x08,
|
||||
"STREAM_fin": 0x09,
|
||||
"STREAM_len": 0x0a,
|
||||
"STREAM_len_fin": 0x0b,
|
||||
"STREAM_off": 0x0c,
|
||||
"STREAM_off_fin": 0x0d,
|
||||
"STREAM_off_len": 0x0e,
|
||||
"STREAM_off_len_fin": 0x0f,
|
||||
"MAX_DATA": 0x10,
|
||||
"MAX_STREAM_DATA": 0x11,
|
||||
"MAX_STREAMS_bidi": 0x12,
|
||||
"MAX_STREAMS_uni": 0x13,
|
||||
"DATA_BLOCKED": 0x14,
|
||||
"STREAM_DATA_BLOCKED": 0x15,
|
||||
"STREAMS_BLOCKED_bidi": 0x16,
|
||||
"STREAMS_BLOCKED_uni": 0x17,
|
||||
"NEW_CONNECTION_ID": 0x18,
|
||||
"RETIRE_CONNECTION_ID": 0x19,
|
||||
"PATH_CHALLENGE": 0x1a,
|
||||
"PATH_RESPONSE": 0x1b,
|
||||
"CONNECTION_CLOSE": 0x1c,
|
||||
"CONNECTION_CLOSE_app": 0x1d,
|
||||
"HANDSHAKE_DONE": 0x1e,
|
||||
"DATAGRAM": 0x30,
|
||||
"DATAGRAM_len": 0x31,
|
||||
}
|
||||
Generated
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/quic/quic.xhtml#quic-transport-error-codes
|
||||
// last updated: July 2023
|
||||
|
||||
const (
|
||||
QUICTransportErrorCode_NO_ERROR uint16 = 0x0000
|
||||
QUICTransportErrorCode_INTERNAL_ERROR uint16 = 0x0001
|
||||
QUICTransportErrorCode_CONNECTION_REFUSED uint16 = 0x0002
|
||||
QUICTransportErrorCode_FLOW_CONTROL_ERROR uint16 = 0x0003
|
||||
QUICTransportErrorCode_STREAM_LIMIT_ERROR uint16 = 0x0004
|
||||
QUICTransportErrorCode_STREAM_STATE_ERROR uint16 = 0x0005
|
||||
QUICTransportErrorCode_FINAL_SIZE_ERROR uint16 = 0x0006
|
||||
QUICTransportErrorCode_FRAME_ENCODING_ERROR uint16 = 0x0007
|
||||
QUICTransportErrorCode_TRANSPORT_PARAMETER_ERROR uint16 = 0x0008
|
||||
QUICTransportErrorCode_CONNECTION_ID_LIMIT_ERROR uint16 = 0x0009
|
||||
QUICTransportErrorCode_PROTOCOL_VIOLATION uint16 = 0x000A
|
||||
QUICTransportErrorCode_INVALID_TOKEN uint16 = 0x000B
|
||||
QUICTransportErrorCode_APPLICATION_ERROR uint16 = 0x000C
|
||||
QUICTransportErrorCode_CRYPTO_BUFFER_EXCEEDED uint16 = 0x000D
|
||||
QUICTransportErrorCode_KEY_UPDATE_ERROR uint16 = 0x000E
|
||||
QUICTransportErrorCode_AEAD_LIMIT_REACHED uint16 = 0x000F
|
||||
QUICTransportErrorCode_NO_VIABLE_PATH uint16 = 0x0010
|
||||
QUICTransportErrorCode_VERSION_NEGOTIATION_ERROR uint16 = 0x0011 // RFC9368
|
||||
QUICTransportErrorCode_CRYPTO_ERROR uint16 = 0x0100 // 0x0100-0x01FF, use with bitwise operator
|
||||
)
|
||||
|
||||
var DictQUICTransportErrorCodeValueIndexed = map[uint16]string{
|
||||
0x0000: "NO_ERROR",
|
||||
0x0001: "INTERNAL_ERROR",
|
||||
0x0002: "CONNECTION_REFUSED",
|
||||
0x0003: "FLOW_CONTROL_ERROR",
|
||||
0x0004: "STREAM_LIMIT_ERROR",
|
||||
0x0005: "STREAM_STATE_ERROR",
|
||||
0x0006: "FINAL_SIZE_ERROR",
|
||||
0x0007: "FRAME_ENCODING_ERROR",
|
||||
0x0008: "TRANSPORT_PARAMETER_ERROR",
|
||||
0x0009: "CONNECTION_ID_LIMIT_ERROR",
|
||||
0x000A: "PROTOCOL_VIOLATION",
|
||||
0x000B: "INVALID_TOKEN",
|
||||
0x000C: "APPLICATION_ERROR",
|
||||
0x000D: "CRYPTO_BUFFER_EXCEEDED",
|
||||
0x000E: "KEY_UPDATE_ERROR",
|
||||
0x000F: "AEAD_LIMIT_REACHED",
|
||||
0x0010: "NO_VIABLE_PATH",
|
||||
0x0011: "VERSION_NEGOTIATION_ERROR",
|
||||
0x0100: "CRYPTO_ERROR",
|
||||
}
|
||||
|
||||
var DictQUICTransportErrorCodeNameIndexed = map[string]uint16{
|
||||
"NO_ERROR": 0x0000,
|
||||
"INTERNAL_ERROR": 0x0001,
|
||||
"CONNECTION_REFUSED": 0x0002,
|
||||
"FLOW_CONTROL_ERROR": 0x0003,
|
||||
"STREAM_LIMIT_ERROR": 0x0004,
|
||||
"STREAM_STATE_ERROR": 0x0005,
|
||||
"FINAL_SIZE_ERROR": 0x0006,
|
||||
"FRAME_ENCODING_ERROR": 0x0007,
|
||||
"TRANSPORT_PARAMETER_ERROR": 0x0008,
|
||||
"CONNECTION_ID_LIMIT_ERROR": 0x0009,
|
||||
"PROTOCOL_VIOLATION": 0x000A,
|
||||
"INVALID_TOKEN": 0x000B,
|
||||
"APPLICATION_ERROR": 0x000C,
|
||||
"CRYPTO_BUFFER_EXCEEDED": 0x000D,
|
||||
"KEY_UPDATE_ERROR": 0x000E,
|
||||
"AEAD_LIMIT_REACHED": 0x000F,
|
||||
"NO_VIABLE_PATH": 0x0010,
|
||||
"VERSION_NEGOTIATION_ERROR": 0x0011,
|
||||
"CRYPTO_ERROR": 0x0100,
|
||||
}
|
||||
Generated
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/quic/quic.xhtml#quic-transport
|
||||
// last updated: July 2023
|
||||
|
||||
const (
|
||||
QUICTransportParameter_original_destination_connection_id uint64 = 0x00
|
||||
QUICTransportParameter_max_idle_timeout uint64 = 0x01
|
||||
QUICTransportParameter_stateless_reset_token uint64 = 0x02
|
||||
QUICTransportParameter_max_udp_payload_size uint64 = 0x03
|
||||
QUICTransportParameter_initial_max_data uint64 = 0x04
|
||||
QUICTransportParameter_initial_max_stream_data_bidi_local uint64 = 0x05
|
||||
QUICTransportParameter_initial_max_stream_data_bidi_remote uint64 = 0x06
|
||||
QUICTransportParameter_initial_max_stream_data_uni uint64 = 0x07
|
||||
QUICTransportParameter_initial_max_streams_bidi uint64 = 0x08
|
||||
QUICTransportParameter_initial_max_streams_uni uint64 = 0x09
|
||||
QUICTransportParameter_ack_delay_exponent uint64 = 0x0a
|
||||
QUICTransportParameter_max_ack_delay uint64 = 0x0b
|
||||
QUICTransportParameter_disable_active_migration uint64 = 0x0c
|
||||
QUICTransportParameter_preferred_address uint64 = 0x0d
|
||||
QUICTransportParameter_active_connection_id_limit uint64 = 0x0e
|
||||
QUICTransportParameter_initial_source_connection_id uint64 = 0x0f
|
||||
QUICTransportParameter_retry_source_connection_id uint64 = 0x10
|
||||
QUICTransportParameter_version_information uint64 = 0x11 // RFC9368
|
||||
QUICTransportParameter_max_datagram_frame_size uint64 = 0x20 // RFC9221
|
||||
QUICTransportParameter_discard uint64 = 0x173e // David_Schinazi: Receiver silently discards. https://github.com/quicwg/base-drafts/wiki/Quantum-Readiness-test
|
||||
QUICTransportParameter_google_handshake_message uint64 = 0x26ab // Google: Used to carry Google internal handshake message
|
||||
QUICTransportParameter_grease_quic_bit uint64 = 0x2ab2 // RFC9287
|
||||
QUICTransportParameter_initial_rtt uint64 = 0x3127 // Google: Initial RTT in microseconds
|
||||
QUICTransportParameter_google_connection_options uint64 = 0x3128 // Google: Google connection options for experimentation
|
||||
QUICTransportParameter_user_agent uint64 = 0x3129 // Google: User agent string (deprecated)
|
||||
QUICTransportParameter_google_version uint64 = 0x4752 // Google: Google QUIC version downgrade prevention
|
||||
)
|
||||
|
||||
var DictQUICTransportParameterValueIndexed = map[uint64]string{
|
||||
0x00: "original_destination_connection_id",
|
||||
0x01: "max_idle_timeout",
|
||||
0x02: "stateless_reset_token",
|
||||
0x03: "max_udp_payload_size",
|
||||
0x04: "initial_max_data",
|
||||
0x05: "initial_max_stream_data_bidi_local",
|
||||
0x06: "initial_max_stream_data_bidi_remote",
|
||||
0x07: "initial_max_stream_data_uni",
|
||||
0x08: "initial_max_streams_bidi",
|
||||
0x09: "initial_max_streams_uni",
|
||||
0x0a: "ack_delay_exponent",
|
||||
0x0b: "max_ack_delay",
|
||||
0x0c: "disable_active_migration",
|
||||
0x0d: "preferred_address",
|
||||
0x0e: "active_connection_id_limit",
|
||||
0x0f: "initial_source_connection_id",
|
||||
0x10: "retry_source_connection_id",
|
||||
0x11: "version_information",
|
||||
0x20: "max_datagram_frame_size",
|
||||
0x173e: "discard",
|
||||
0x26ab: "google handshake message",
|
||||
0x2ab2: "grease_quic_bit",
|
||||
0x3127: "initial_rtt",
|
||||
0x3128: "google_connection_options",
|
||||
0x3129: "user_agent",
|
||||
0x4752: "google_version",
|
||||
}
|
||||
|
||||
var DictQUICTransportParameterNameIndexed = map[string]uint64{
|
||||
"original_destination_connection_id": 0x00,
|
||||
"max_idle_timeout": 0x01,
|
||||
"stateless_reset_token": 0x02,
|
||||
"max_udp_payload_size": 0x03,
|
||||
"initial_max_data": 0x04,
|
||||
"initial_max_stream_data_bidi_local": 0x05,
|
||||
"initial_max_stream_data_bidi_remote": 0x06,
|
||||
"initial_max_stream_data_uni": 0x07,
|
||||
"initial_max_streams_bidi": 0x08,
|
||||
"initial_max_streams_uni": 0x09,
|
||||
"ack_delay_exponent": 0x0a,
|
||||
"max_ack_delay": 0x0b,
|
||||
"disable_active_migration": 0x0c,
|
||||
"preferred_address": 0x0d,
|
||||
"active_connection_id_limit": 0x0e,
|
||||
"initial_source_connection_id": 0x0f,
|
||||
"retry_source_connection_id": 0x10,
|
||||
"version_information": 0x11,
|
||||
"max_datagram_frame_size": 0x20,
|
||||
"discard": 0x173e,
|
||||
"google handshake message": 0x26ab,
|
||||
"grease_quic_bit": 0x2ab2,
|
||||
"initial_rtt": 0x3127,
|
||||
"google_connection_options": 0x3128,
|
||||
"user_agent": 0x3129,
|
||||
"google_version": 0x4752,
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package dicttls
|
||||
|
||||
// Note: values in this file was used in TLS 1.2's signature_algorithms extension
|
||||
// in combination with the values in hashalgorithm.go.
|
||||
// signature_algorithms extension in TLS 1.3 uses values in signaturescheme.go
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
SigAlg_anonymous uint8 = 0 // deprecated in TLS 1.3
|
||||
SigAlg_rsa uint8 = 1
|
||||
SigAlg_dsa uint8 = 2 // deprecated in TLS 1.3
|
||||
SigAlg_ecdsa uint8 = 3
|
||||
SigAlg_ed25519 uint8 = 7
|
||||
SigAlg_ed448 uint8 = 8
|
||||
SigAlg_gostr34102012_256 uint8 = 64 // value changed in TLS 1.3, to 0x0709-0x070C
|
||||
SigAlg_gostr34102012_512 uint8 = 65 // value changed in TLS 1.3, to 0x070D-0x070F
|
||||
)
|
||||
|
||||
var DictSignatureAlgorithmValueIndexed = map[uint8]string{
|
||||
0: "anonymous",
|
||||
1: "rsa",
|
||||
2: "dsa",
|
||||
3: "ecdsa",
|
||||
7: "ed25519",
|
||||
8: "ed448",
|
||||
64: "gostr34102012_256",
|
||||
65: "gostr34102012_512",
|
||||
}
|
||||
|
||||
var DictSignatureAlgorithmNameIndexed = map[string]uint8{
|
||||
"anonymous": 0,
|
||||
"rsa": 1,
|
||||
"dsa": 2,
|
||||
"ecdsa": 3,
|
||||
"ed25519": 7,
|
||||
"ed448": 8,
|
||||
"gostr34102012_256": 64,
|
||||
"gostr34102012_512": 65,
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-signaturescheme.csv
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
SigScheme_rsa_pkcs1_sha1 uint16 = 0x0201
|
||||
SigScheme_ecdsa_sha1 uint16 = 0x0203
|
||||
SigScheme_rsa_pkcs1_sha256 uint16 = 0x0401
|
||||
SigScheme_ecdsa_secp256r1_sha256 uint16 = 0x0403
|
||||
SigScheme_rsa_pkcs1_sha256_legacy uint16 = 0x0420
|
||||
SigScheme_rsa_pkcs1_sha384 uint16 = 0x0501
|
||||
SigScheme_ecdsa_secp384r1_sha384 uint16 = 0x0503
|
||||
SigScheme_rsa_pkcs1_sha384_legacy uint16 = 0x0520
|
||||
SigScheme_rsa_pkcs1_sha512 uint16 = 0x0601
|
||||
SigScheme_ecdsa_secp521r1_sha512 uint16 = 0x0603
|
||||
SigScheme_rsa_pkcs1_sha512_legacy uint16 = 0x0620
|
||||
SigScheme_eccsi_sha256 uint16 = 0x0704
|
||||
SigScheme_iso_ibs1 uint16 = 0x0705
|
||||
SigScheme_iso_ibs2 uint16 = 0x0706
|
||||
SigScheme_iso_chinese_ibs uint16 = 0x0707
|
||||
SigScheme_sm2sig_sm3 uint16 = 0x0708
|
||||
SigScheme_gostr34102012_256a uint16 = 0x0709
|
||||
SigScheme_gostr34102012_256b uint16 = 0x070A
|
||||
SigScheme_gostr34102012_256c uint16 = 0x070B
|
||||
SigScheme_gostr34102012_256d uint16 = 0x070C
|
||||
SigScheme_gostr34102012_512a uint16 = 0x070D
|
||||
SigScheme_gostr34102012_512b uint16 = 0x070E
|
||||
SigScheme_gostr34102012_512c uint16 = 0x070F
|
||||
SigScheme_rsa_pss_rsae_sha256 uint16 = 0x0804
|
||||
SigScheme_rsa_pss_rsae_sha384 uint16 = 0x0805
|
||||
SigScheme_rsa_pss_rsae_sha512 uint16 = 0x0806
|
||||
SigScheme_ed25519 uint16 = 0x0807
|
||||
SigScheme_ed448 uint16 = 0x0808
|
||||
SigScheme_rsa_pss_pss_sha256 uint16 = 0x0809
|
||||
SigScheme_rsa_pss_pss_sha384 uint16 = 0x080A
|
||||
SigScheme_rsa_pss_pss_sha512 uint16 = 0x080B
|
||||
SigScheme_ecdsa_brainpoolP256r1tls13_sha256 uint16 = 0x081A
|
||||
SigScheme_ecdsa_brainpoolP384r1tls13_sha384 uint16 = 0x081B
|
||||
SigScheme_ecdsa_brainpoolP512r1tls13_sha512 uint16 = 0x081C
|
||||
)
|
||||
|
||||
var DictSignatureSchemeValueIndexed = map[uint16]string{
|
||||
0x0201: "rsa_pkcs1_sha1",
|
||||
0x0203: "ecdsa_sha1",
|
||||
0x0401: "rsa_pkcs1_sha256",
|
||||
0x0403: "ecdsa_secp256r1_sha256",
|
||||
0x0420: "rsa_pkcs1_sha256_legacy",
|
||||
0x0501: "rsa_pkcs1_sha384",
|
||||
0x0503: "ecdsa_secp384r1_sha384",
|
||||
0x0520: "rsa_pkcs1_sha384_legacy",
|
||||
0x0601: "rsa_pkcs1_sha512",
|
||||
0x0603: "ecdsa_secp521r1_sha512",
|
||||
0x0620: "rsa_pkcs1_sha512_legacy",
|
||||
0x0704: "eccsi_sha256",
|
||||
0x0705: "iso_ibs1",
|
||||
0x0706: "iso_ibs2",
|
||||
0x0707: "iso_chinese_ibs",
|
||||
0x0708: "sm2sig_sm3",
|
||||
0x0709: "gostr34102012_256a",
|
||||
0x070A: "gostr34102012_256b",
|
||||
0x070B: "gostr34102012_256c",
|
||||
0x070C: "gostr34102012_256d",
|
||||
0x070D: "gostr34102012_512a",
|
||||
0x070E: "gostr34102012_512b",
|
||||
0x070F: "gostr34102012_512c",
|
||||
0x0804: "rsa_pss_rsae_sha256",
|
||||
0x0805: "rsa_pss_rsae_sha384",
|
||||
0x0806: "rsa_pss_rsae_sha512",
|
||||
0x0807: "ed25519",
|
||||
0x0808: "ed448",
|
||||
0x0809: "rsa_pss_pss_sha256",
|
||||
0x080A: "rsa_pss_pss_sha384",
|
||||
0x080B: "rsa_pss_pss_sha512",
|
||||
0x081A: "ecdsa_brainpoolP256r1tls13_sha256",
|
||||
0x081B: "ecdsa_brainpoolP384r1tls13_sha384",
|
||||
0x081C: "ecdsa_brainpoolP512r1tls13_sha512",
|
||||
}
|
||||
|
||||
var DictSignatureSchemeNameIndexed = map[string]uint16{
|
||||
"rsa_pkcs1_sha1": 0x0201,
|
||||
"Reserved for backward compatibility": 0x0202,
|
||||
"ecdsa_sha1": 0x0203,
|
||||
"rsa_pkcs1_sha256": 0x0401,
|
||||
"ecdsa_secp256r1_sha256": 0x0403,
|
||||
"rsa_pkcs1_sha256_legacy": 0x0420,
|
||||
"rsa_pkcs1_sha384": 0x0501,
|
||||
"ecdsa_secp384r1_sha384": 0x0503,
|
||||
"rsa_pkcs1_sha384_legacy": 0x0520,
|
||||
"rsa_pkcs1_sha512": 0x0601,
|
||||
"ecdsa_secp521r1_sha512": 0x0603,
|
||||
"rsa_pkcs1_sha512_legacy": 0x0620,
|
||||
"eccsi_sha256": 0x0704,
|
||||
"iso_ibs1": 0x0705,
|
||||
"iso_ibs2": 0x0706,
|
||||
"iso_chinese_ibs": 0x0707,
|
||||
"sm2sig_sm3": 0x0708,
|
||||
"gostr34102012_256a": 0x0709,
|
||||
"gostr34102012_256b": 0x070A,
|
||||
"gostr34102012_256c": 0x070B,
|
||||
"gostr34102012_256d": 0x070C,
|
||||
"gostr34102012_512a": 0x070D,
|
||||
"gostr34102012_512b": 0x070E,
|
||||
"gostr34102012_512c": 0x070F,
|
||||
"rsa_pss_rsae_sha256": 0x0804,
|
||||
"rsa_pss_rsae_sha384": 0x0805,
|
||||
"rsa_pss_rsae_sha512": 0x0806,
|
||||
"ed25519": 0x0807,
|
||||
"ed448": 0x0808,
|
||||
"rsa_pss_pss_sha256": 0x0809,
|
||||
"rsa_pss_pss_sha384": 0x080A,
|
||||
"rsa_pss_pss_sha512": 0x080B,
|
||||
"ecdsa_brainpoolP256r1tls13_sha256": 0x081A,
|
||||
"ecdsa_brainpoolP384r1tls13_sha384": 0x081B,
|
||||
"ecdsa_brainpoolP512r1tls13_sha512": 0x081C,
|
||||
}
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-12
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
SupplementalDataType_user_mapping_data uint16 = 0
|
||||
SupplementalDataType_authz_data uint16 = 16386
|
||||
)
|
||||
|
||||
var DictSupplementalDataFormatValueIndexed = map[uint16]string{
|
||||
0: "user_mapping_data",
|
||||
16386: "authz_data",
|
||||
}
|
||||
|
||||
var DictSupplementalDataFormatNameIndexed = map[string]uint16{
|
||||
"user_mapping_data": 0,
|
||||
"authz_data": 16386,
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
SupportedGroups_sect163k1 uint16 = 1
|
||||
SupportedGroups_sect163r1 uint16 = 2
|
||||
SupportedGroups_sect163r2 uint16 = 3
|
||||
SupportedGroups_sect193r1 uint16 = 4
|
||||
SupportedGroups_sect193r2 uint16 = 5
|
||||
SupportedGroups_sect233k1 uint16 = 6
|
||||
SupportedGroups_sect233r1 uint16 = 7
|
||||
SupportedGroups_sect239k1 uint16 = 8
|
||||
SupportedGroups_sect283k1 uint16 = 9
|
||||
SupportedGroups_sect283r1 uint16 = 10
|
||||
SupportedGroups_sect409k1 uint16 = 11
|
||||
SupportedGroups_sect409r1 uint16 = 12
|
||||
SupportedGroups_sect571k1 uint16 = 13
|
||||
SupportedGroups_sect571r1 uint16 = 14
|
||||
SupportedGroups_secp160k1 uint16 = 15
|
||||
SupportedGroups_secp160r1 uint16 = 16
|
||||
SupportedGroups_secp160r2 uint16 = 17
|
||||
SupportedGroups_secp192k1 uint16 = 18
|
||||
SupportedGroups_secp192r1 uint16 = 19
|
||||
SupportedGroups_secp224k1 uint16 = 20
|
||||
SupportedGroups_secp224r1 uint16 = 21
|
||||
SupportedGroups_secp256k1 uint16 = 22
|
||||
SupportedGroups_secp256r1 uint16 = 23
|
||||
SupportedGroups_secp384r1 uint16 = 24
|
||||
SupportedGroups_secp521r1 uint16 = 25
|
||||
SupportedGroups_brainpoolP256r1 uint16 = 26
|
||||
SupportedGroups_brainpoolP384r1 uint16 = 27
|
||||
SupportedGroups_brainpoolP512r1 uint16 = 28
|
||||
SupportedGroups_x25519 uint16 = 29
|
||||
SupportedGroups_x448 uint16 = 30
|
||||
SupportedGroups_brainpoolP256r1tls13 uint16 = 31
|
||||
SupportedGroups_brainpoolP384r1tls13 uint16 = 32
|
||||
SupportedGroups_brainpoolP512r1tls13 uint16 = 33
|
||||
SupportedGroups_GC256A uint16 = 34
|
||||
SupportedGroups_GC256B uint16 = 35
|
||||
SupportedGroups_GC256C uint16 = 36
|
||||
SupportedGroups_GC256D uint16 = 37
|
||||
SupportedGroups_GC512A uint16 = 38
|
||||
SupportedGroups_GC512B uint16 = 39
|
||||
SupportedGroups_GC512C uint16 = 40
|
||||
SupportedGroups_curveSM2 uint16 = 41
|
||||
SupportedGroups_ffdhe2048 uint16 = 256
|
||||
SupportedGroups_ffdhe3072 uint16 = 257
|
||||
SupportedGroups_ffdhe4096 uint16 = 258
|
||||
SupportedGroups_ffdhe6144 uint16 = 259
|
||||
SupportedGroups_ffdhe8192 uint16 = 260
|
||||
SupportedGroups_arbitrary_explicit_prime_curves uint16 = 65281
|
||||
SupportedGroups_arbitrary_explicit_char2_curves uint16 = 65282
|
||||
)
|
||||
|
||||
var DictSupportedGroupsValueIndexed = map[uint16]string{
|
||||
1: "sect163k1",
|
||||
2: "sect163r1",
|
||||
3: "sect163r2",
|
||||
4: "sect193r1",
|
||||
5: "sect193r2",
|
||||
6: "sect233k1",
|
||||
7: "sect233r1",
|
||||
8: "sect239k1",
|
||||
9: "sect283k1",
|
||||
10: "sect283r1",
|
||||
11: "sect409k1",
|
||||
12: "sect409r1",
|
||||
13: "sect571k1",
|
||||
14: "sect571r1",
|
||||
15: "secp160k1",
|
||||
16: "secp160r1",
|
||||
17: "secp160r2",
|
||||
18: "secp192k1",
|
||||
19: "secp192r1",
|
||||
20: "secp224k1",
|
||||
21: "secp224r1",
|
||||
22: "secp256k1",
|
||||
23: "secp256r1",
|
||||
24: "secp384r1",
|
||||
25: "secp521r1",
|
||||
26: "brainpoolP256r1",
|
||||
27: "brainpoolP384r1",
|
||||
28: "brainpoolP512r1",
|
||||
29: "x25519",
|
||||
30: "x448",
|
||||
31: "brainpoolP256r1tls13",
|
||||
32: "brainpoolP384r1tls13",
|
||||
33: "brainpoolP512r1tls13",
|
||||
34: "GC256A",
|
||||
35: "GC256B",
|
||||
36: "GC256C",
|
||||
37: "GC256D",
|
||||
38: "GC512A",
|
||||
39: "GC512B",
|
||||
40: "GC512C",
|
||||
41: "curveSM2",
|
||||
256: "ffdhe2048",
|
||||
257: "ffdhe3072",
|
||||
258: "ffdhe4096",
|
||||
259: "ffdhe6144",
|
||||
260: "ffdhe8192",
|
||||
65281: "arbitrary_explicit_prime_curves",
|
||||
65282: "arbitrary_explicit_char2_curves",
|
||||
}
|
||||
|
||||
var DictSupportedGroupsNameIndexed = map[string]uint16{
|
||||
"sect163k1": 1,
|
||||
"sect163r1": 2,
|
||||
"sect163r2": 3,
|
||||
"sect193r1": 4,
|
||||
"sect193r2": 5,
|
||||
"sect233k1": 6,
|
||||
"sect233r1": 7,
|
||||
"sect239k1": 8,
|
||||
"sect283k1": 9,
|
||||
"sect283r1": 10,
|
||||
"sect409k1": 11,
|
||||
"sect409r1": 12,
|
||||
"sect571k1": 13,
|
||||
"sect571r1": 14,
|
||||
"secp160k1": 15,
|
||||
"secp160r1": 16,
|
||||
"secp160r2": 17,
|
||||
"secp192k1": 18,
|
||||
"secp192r1": 19,
|
||||
"secp224k1": 20,
|
||||
"secp224r1": 21,
|
||||
"secp256k1": 22,
|
||||
"secp256r1": 23,
|
||||
"secp384r1": 24,
|
||||
"secp521r1": 25,
|
||||
"brainpoolP256r1": 26,
|
||||
"brainpoolP384r1": 27,
|
||||
"brainpoolP512r1": 28,
|
||||
"x25519": 29,
|
||||
"x448": 30,
|
||||
"brainpoolP256r1tls13": 31,
|
||||
"brainpoolP384r1tls13": 32,
|
||||
"brainpoolP512r1tls13": 33,
|
||||
"GC256A": 34,
|
||||
"GC256B": 35,
|
||||
"GC256C": 36,
|
||||
"GC256D": 37,
|
||||
"GC512A": 38,
|
||||
"GC512B": 39,
|
||||
"GC512C": 40,
|
||||
"curveSM2": 41,
|
||||
"ffdhe2048": 256,
|
||||
"ffdhe3072": 257,
|
||||
"ffdhe4096": 258,
|
||||
"ffdhe6144": 259,
|
||||
"ffdhe8192": 260,
|
||||
"arbitrary_explicit_prime_curves": 65281,
|
||||
"arbitrary_explicit_char2_curves": 65282,
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package dicttls
|
||||
|
||||
// source: https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-14
|
||||
// last updated: March 2023
|
||||
|
||||
const (
|
||||
UserMappingType_upn_domain_hint uint8 = 64
|
||||
)
|
||||
|
||||
var DictUserMappingTypeValueIndexed = map[uint8]string{
|
||||
64: "upn_domain_hint",
|
||||
}
|
||||
|
||||
var DictUserMappingTypeNameIndexed = map[string]uint8{
|
||||
"upn_domain_hint": 64,
|
||||
}
|
||||
+646
@@ -0,0 +1,646 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/refraction-networking/utls/internal/hpke"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
// sortedSupportedAEADs is just a sorted version of hpke.SupportedAEADS.
|
||||
// We need this so that when we insert them into ECHConfigs the ordering
|
||||
// is stable.
|
||||
var sortedSupportedAEADs []uint16
|
||||
|
||||
func init() {
|
||||
for aeadID := range hpke.SupportedAEADs {
|
||||
sortedSupportedAEADs = append(sortedSupportedAEADs, aeadID)
|
||||
}
|
||||
slices.Sort(sortedSupportedAEADs)
|
||||
}
|
||||
|
||||
type echCipher struct {
|
||||
KDFID uint16
|
||||
AEADID uint16
|
||||
}
|
||||
|
||||
type echExtension struct {
|
||||
Type uint16
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type echConfig struct {
|
||||
raw []byte
|
||||
|
||||
Version uint16
|
||||
Length uint16
|
||||
|
||||
ConfigID uint8
|
||||
KemID uint16
|
||||
PublicKey []byte
|
||||
SymmetricCipherSuite []echCipher
|
||||
|
||||
MaxNameLength uint8
|
||||
PublicName []byte
|
||||
Extensions []echExtension
|
||||
}
|
||||
|
||||
var errMalformedECHConfig = errors.New("tls: malformed ECHConfigList")
|
||||
|
||||
func parseECHConfig(enc []byte) (skip bool, ec echConfig, err error) {
|
||||
s := cryptobyte.String(enc)
|
||||
ec.raw = []byte(enc)
|
||||
if !s.ReadUint16(&ec.Version) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
if !s.ReadUint16(&ec.Length) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
if len(ec.raw) < int(ec.Length)+4 {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
ec.raw = ec.raw[:ec.Length+4]
|
||||
if ec.Version != extensionEncryptedClientHello {
|
||||
s.Skip(int(ec.Length))
|
||||
return true, echConfig{}, nil
|
||||
}
|
||||
if !s.ReadUint8(&ec.ConfigID) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
if !s.ReadUint16(&ec.KemID) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
if !readUint16LengthPrefixed(&s, &ec.PublicKey) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
var cipherSuites cryptobyte.String
|
||||
if !s.ReadUint16LengthPrefixed(&cipherSuites) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
for !cipherSuites.Empty() {
|
||||
var c echCipher
|
||||
if !cipherSuites.ReadUint16(&c.KDFID) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
if !cipherSuites.ReadUint16(&c.AEADID) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c)
|
||||
}
|
||||
if !s.ReadUint8(&ec.MaxNameLength) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
var publicName cryptobyte.String
|
||||
if !s.ReadUint8LengthPrefixed(&publicName) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
ec.PublicName = publicName
|
||||
var extensions cryptobyte.String
|
||||
if !s.ReadUint16LengthPrefixed(&extensions) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
for !extensions.Empty() {
|
||||
var e echExtension
|
||||
if !extensions.ReadUint16(&e.Type) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) {
|
||||
return false, echConfig{}, errMalformedECHConfig
|
||||
}
|
||||
ec.Extensions = append(ec.Extensions, e)
|
||||
}
|
||||
|
||||
return false, ec, nil
|
||||
}
|
||||
|
||||
// parseECHConfigList parses a draft-ietf-tls-esni-18 ECHConfigList, returning a
|
||||
// slice of parsed ECHConfigs, in the same order they were parsed, or an error
|
||||
// if the list is malformed.
|
||||
func parseECHConfigList(data []byte) ([]echConfig, error) {
|
||||
s := cryptobyte.String(data)
|
||||
var length uint16
|
||||
if !s.ReadUint16(&length) {
|
||||
return nil, errMalformedECHConfig
|
||||
}
|
||||
if length != uint16(len(data)-2) {
|
||||
return nil, errMalformedECHConfig
|
||||
}
|
||||
var configs []echConfig
|
||||
for len(s) > 0 {
|
||||
if len(s) < 4 {
|
||||
return nil, errors.New("tls: malformed ECHConfig")
|
||||
}
|
||||
configLen := uint16(s[2])<<8 | uint16(s[3])
|
||||
skip, ec, err := parseECHConfig(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s = s[configLen+4:]
|
||||
if !skip {
|
||||
configs = append(configs, ec)
|
||||
}
|
||||
}
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
func pickECHConfig(list []echConfig) *echConfig {
|
||||
for _, ec := range list {
|
||||
if _, ok := hpke.SupportedKEMs[ec.KemID]; !ok {
|
||||
continue
|
||||
}
|
||||
var validSCS bool
|
||||
for _, cs := range ec.SymmetricCipherSuite {
|
||||
if _, ok := hpke.SupportedAEADs[cs.AEADID]; !ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := hpke.SupportedKDFs[cs.KDFID]; !ok {
|
||||
continue
|
||||
}
|
||||
validSCS = true
|
||||
break
|
||||
}
|
||||
if !validSCS {
|
||||
continue
|
||||
}
|
||||
if !validDNSName(string(ec.PublicName)) {
|
||||
continue
|
||||
}
|
||||
var unsupportedExt bool
|
||||
for _, ext := range ec.Extensions {
|
||||
// If high order bit is set to 1 the extension is mandatory.
|
||||
// Since we don't support any extensions, if we see a mandatory
|
||||
// bit, we skip the config.
|
||||
if ext.Type&uint16(1<<15) != 0 {
|
||||
unsupportedExt = true
|
||||
}
|
||||
}
|
||||
if unsupportedExt {
|
||||
continue
|
||||
}
|
||||
return &ec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pickECHCipherSuite(suites []echCipher) (echCipher, error) {
|
||||
for _, s := range suites {
|
||||
// NOTE: all of the supported AEADs and KDFs are fine, rather than
|
||||
// imposing some sort of preference here, we just pick the first valid
|
||||
// suite.
|
||||
if _, ok := hpke.SupportedAEADs[s.AEADID]; !ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := hpke.SupportedKDFs[s.KDFID]; !ok {
|
||||
continue
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
return echCipher{}, errors.New("tls: no supported symmetric ciphersuites for ECH")
|
||||
}
|
||||
|
||||
// [uTLS SECTION BEGIN]
|
||||
func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, error) {
|
||||
return encodeInnerClientHelloReorderOuterExts(inner, maxNameLength, nil)
|
||||
}
|
||||
|
||||
// [uTLS SECTION END]
|
||||
|
||||
// func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, error) {
|
||||
func encodeInnerClientHelloReorderOuterExts(inner *clientHelloMsg, maxNameLength int, outerExts []uint16) ([]byte, error) { // uTLS
|
||||
h, err := inner.marshalMsgReorderOuterExts(true, outerExts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h = h[4:] // strip four byte prefix
|
||||
|
||||
var paddingLen int
|
||||
if inner.serverName != "" {
|
||||
paddingLen = max(0, maxNameLength-len(inner.serverName))
|
||||
} else {
|
||||
paddingLen = maxNameLength + 9
|
||||
}
|
||||
paddingLen = 31 - ((len(h) + paddingLen - 1) % 32)
|
||||
|
||||
return append(h, make([]byte, paddingLen)...), nil
|
||||
}
|
||||
|
||||
func skipUint8LengthPrefixed(s *cryptobyte.String) bool {
|
||||
var skip uint8
|
||||
if !s.ReadUint8(&skip) {
|
||||
return false
|
||||
}
|
||||
return s.Skip(int(skip))
|
||||
}
|
||||
|
||||
func skipUint16LengthPrefixed(s *cryptobyte.String) bool {
|
||||
var skip uint16
|
||||
if !s.ReadUint16(&skip) {
|
||||
return false
|
||||
}
|
||||
return s.Skip(int(skip))
|
||||
}
|
||||
|
||||
type rawExtension struct {
|
||||
extType uint16
|
||||
data []byte
|
||||
}
|
||||
|
||||
func extractRawExtensions(hello *clientHelloMsg) ([]rawExtension, error) {
|
||||
s := cryptobyte.String(hello.original)
|
||||
if !s.Skip(4+2+32) || // header, version, random
|
||||
!skipUint8LengthPrefixed(&s) || // session ID
|
||||
!skipUint16LengthPrefixed(&s) || // cipher suites
|
||||
!skipUint8LengthPrefixed(&s) { // compression methods
|
||||
return nil, errors.New("tls: malformed outer client hello")
|
||||
}
|
||||
var rawExtensions []rawExtension
|
||||
var extensions cryptobyte.String
|
||||
if !s.ReadUint16LengthPrefixed(&extensions) {
|
||||
return nil, errors.New("tls: malformed outer client hello")
|
||||
}
|
||||
|
||||
for !extensions.Empty() {
|
||||
var extension uint16
|
||||
var extData cryptobyte.String
|
||||
if !extensions.ReadUint16(&extension) ||
|
||||
!extensions.ReadUint16LengthPrefixed(&extData) {
|
||||
return nil, errors.New("tls: invalid inner client hello")
|
||||
}
|
||||
rawExtensions = append(rawExtensions, rawExtension{extension, extData})
|
||||
}
|
||||
return rawExtensions, nil
|
||||
}
|
||||
|
||||
func decodeInnerClientHello(outer *clientHelloMsg, encoded []byte) (*clientHelloMsg, error) {
|
||||
// Reconstructing the inner client hello from its encoded form is somewhat
|
||||
// complicated. It is missing its header (message type and length), session
|
||||
// ID, and the extensions may be compressed. Since we need to put the
|
||||
// extensions back in the same order as they were in the raw outer hello,
|
||||
// and since we don't store the raw extensions, or the order we parsed them
|
||||
// in, we need to reparse the raw extensions from the outer hello in order
|
||||
// to properly insert them into the inner hello. This _should_ result in raw
|
||||
// bytes which match the hello as it was generated by the client.
|
||||
innerReader := cryptobyte.String(encoded)
|
||||
var versionAndRandom, sessionID, cipherSuites, compressionMethods []byte
|
||||
var extensions cryptobyte.String
|
||||
if !innerReader.ReadBytes(&versionAndRandom, 2+32) ||
|
||||
!readUint8LengthPrefixed(&innerReader, &sessionID) ||
|
||||
len(sessionID) != 0 ||
|
||||
!readUint16LengthPrefixed(&innerReader, &cipherSuites) ||
|
||||
!readUint8LengthPrefixed(&innerReader, &compressionMethods) ||
|
||||
!innerReader.ReadUint16LengthPrefixed(&extensions) {
|
||||
return nil, errors.New("tls: invalid inner client hello")
|
||||
}
|
||||
|
||||
// The specification says we must verify that the trailing padding is all
|
||||
// zeros. This is kind of weird for TLS messages, where we generally just
|
||||
// throw away any trailing garbage.
|
||||
for _, p := range innerReader {
|
||||
if p != 0 {
|
||||
return nil, errors.New("tls: invalid inner client hello")
|
||||
}
|
||||
}
|
||||
|
||||
rawOuterExts, err := extractRawExtensions(outer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recon := cryptobyte.NewBuilder(nil)
|
||||
recon.AddUint8(typeClientHello)
|
||||
recon.AddUint24LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(versionAndRandom)
|
||||
recon.AddUint8LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(outer.sessionId)
|
||||
})
|
||||
recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(cipherSuites)
|
||||
})
|
||||
recon.AddUint8LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(compressionMethods)
|
||||
})
|
||||
recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
for !extensions.Empty() {
|
||||
var extension uint16
|
||||
var extData cryptobyte.String
|
||||
if !extensions.ReadUint16(&extension) ||
|
||||
!extensions.ReadUint16LengthPrefixed(&extData) {
|
||||
recon.SetError(errors.New("tls: invalid inner client hello"))
|
||||
return
|
||||
}
|
||||
if extension == extensionECHOuterExtensions {
|
||||
if !extData.ReadUint8LengthPrefixed(&extData) {
|
||||
recon.SetError(errors.New("tls: invalid inner client hello"))
|
||||
return
|
||||
}
|
||||
var i int
|
||||
for !extData.Empty() {
|
||||
var extType uint16
|
||||
if !extData.ReadUint16(&extType) {
|
||||
recon.SetError(errors.New("tls: invalid inner client hello"))
|
||||
return
|
||||
}
|
||||
if extType == extensionEncryptedClientHello {
|
||||
recon.SetError(errors.New("tls: invalid outer extensions"))
|
||||
return
|
||||
}
|
||||
for ; i <= len(rawOuterExts); i++ {
|
||||
if i == len(rawOuterExts) {
|
||||
recon.SetError(errors.New("tls: invalid outer extensions"))
|
||||
return
|
||||
}
|
||||
if rawOuterExts[i].extType == extType {
|
||||
break
|
||||
}
|
||||
}
|
||||
recon.AddUint16(rawOuterExts[i].extType)
|
||||
recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(rawOuterExts[i].data)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
recon.AddUint16(extension)
|
||||
recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) {
|
||||
recon.AddBytes(extData)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
reconBytes, err := recon.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inner := &clientHelloMsg{}
|
||||
if !inner.unmarshal(reconBytes) {
|
||||
return nil, errors.New("tls: invalid reconstructed inner client hello")
|
||||
}
|
||||
|
||||
if !bytes.Equal(inner.encryptedClientHello, []byte{uint8(innerECHExt)}) {
|
||||
return nil, errInvalidECHExt
|
||||
}
|
||||
|
||||
if len(inner.supportedVersions) != 1 || (len(inner.supportedVersions) >= 1 && inner.supportedVersions[0] != VersionTLS13) {
|
||||
return nil, errors.New("tls: client sent encrypted_client_hello extension and offered incompatible versions")
|
||||
}
|
||||
|
||||
return inner, nil
|
||||
}
|
||||
|
||||
func decryptECHPayload(context *hpke.Receipient, hello, payload []byte) ([]byte, error) {
|
||||
outerAAD := bytes.Replace(hello[4:], payload, make([]byte, len(payload)), 1)
|
||||
return context.Open(outerAAD, payload)
|
||||
}
|
||||
|
||||
func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payload []byte) ([]byte, error) {
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint8(0) // outer
|
||||
b.AddUint16(kdfID)
|
||||
b.AddUint16(aeadID)
|
||||
b.AddUint8(id)
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(encodedKey) })
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { b.AddBytes(payload) })
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echClientContext, useKey bool) error {
|
||||
var encapKey []byte
|
||||
if useKey {
|
||||
encapKey = ech.encapsulatedKey
|
||||
}
|
||||
encodedInner, err := encodeInnerClientHello(inner, int(ech.config.MaxNameLength))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// NOTE: the tag lengths for all of the supported AEADs are the same (16
|
||||
// bytes), so we have hardcoded it here. If we add support for another AEAD
|
||||
// with a different tag length, we will need to change this.
|
||||
encryptedLen := len(encodedInner) + 16 // AEAD tag length
|
||||
outer.encryptedClientHello, err = generateOuterECHExt(ech.config.ConfigID, ech.kdfID, ech.aeadID, encapKey, make([]byte, encryptedLen))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serializedOuter, err := outer.marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serializedOuter = serializedOuter[4:] // strip the four byte prefix
|
||||
encryptedInner, err := ech.hpkeContext.Seal(serializedOuter, encodedInner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outer.encryptedClientHello, err = generateOuterECHExt(ech.config.ConfigID, ech.kdfID, ech.aeadID, encapKey, encryptedInner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validDNSName is a rather rudimentary check for the validity of a DNS name.
|
||||
// This is used to check if the public_name in a ECHConfig is valid when we are
|
||||
// picking a config. This can be somewhat lax because even if we pick a
|
||||
// valid-looking name, the DNS layer will later reject it anyway.
|
||||
func validDNSName(name string) bool {
|
||||
if len(name) > 253 {
|
||||
return false
|
||||
}
|
||||
labels := strings.Split(name, ".")
|
||||
if len(labels) <= 1 {
|
||||
return false
|
||||
}
|
||||
for _, l := range labels {
|
||||
labelLen := len(l)
|
||||
if labelLen == 0 {
|
||||
return false
|
||||
}
|
||||
for i, r := range l {
|
||||
if r == '-' && (i == 0 || i == labelLen-1) {
|
||||
return false
|
||||
}
|
||||
if (r < '0' || r > '9') && (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && r != '-' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ECHRejectionError is the error type returned when ECH is rejected by a remote
|
||||
// server. If the server offered a ECHConfigList to use for retries, the
|
||||
// RetryConfigList field will contain this list.
|
||||
//
|
||||
// The client may treat an ECHRejectionError with an empty set of RetryConfigs
|
||||
// as a secure signal from the server.
|
||||
type ECHRejectionError struct {
|
||||
RetryConfigList []byte
|
||||
}
|
||||
|
||||
func (e *ECHRejectionError) Error() string {
|
||||
return "tls: server rejected ECH"
|
||||
}
|
||||
|
||||
var errMalformedECHExt = errors.New("tls: malformed encrypted_client_hello extension")
|
||||
var errInvalidECHExt = errors.New("tls: client sent invalid encrypted_client_hello extension")
|
||||
|
||||
type echExtType uint8
|
||||
|
||||
const (
|
||||
innerECHExt echExtType = 1
|
||||
outerECHExt echExtType = 0
|
||||
)
|
||||
|
||||
func parseECHExt(ext []byte) (echType echExtType, cs echCipher, configID uint8, encap []byte, payload []byte, err error) {
|
||||
data := make([]byte, len(ext))
|
||||
copy(data, ext)
|
||||
s := cryptobyte.String(data)
|
||||
var echInt uint8
|
||||
if !s.ReadUint8(&echInt) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
echType = echExtType(echInt)
|
||||
if echType == innerECHExt {
|
||||
if !s.Empty() {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
return echType, cs, 0, nil, nil, nil
|
||||
}
|
||||
if echType != outerECHExt {
|
||||
err = errInvalidECHExt
|
||||
return
|
||||
}
|
||||
if !s.ReadUint16(&cs.KDFID) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
if !s.ReadUint16(&cs.AEADID) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
if !s.ReadUint8(&configID) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
if !readUint16LengthPrefixed(&s, &encap) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
if !readUint16LengthPrefixed(&s, &payload) {
|
||||
err = errMalformedECHExt
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: clone encap and payload so that mutating them does not mutate the
|
||||
// raw extension bytes.
|
||||
return echType, cs, configID, bytes.Clone(encap), bytes.Clone(payload), nil
|
||||
}
|
||||
|
||||
func marshalEncryptedClientHelloConfigList(configs []EncryptedClientHelloKey) ([]byte, error) {
|
||||
builder := cryptobyte.NewBuilder(nil)
|
||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
for _, c := range configs {
|
||||
builder.AddBytes(c.Config)
|
||||
}
|
||||
})
|
||||
return builder.Bytes()
|
||||
}
|
||||
|
||||
func (c *Conn) processECHClientHello(outer *clientHelloMsg) (*clientHelloMsg, *echServerContext, error) {
|
||||
echType, echCiphersuite, configID, encap, payload, err := parseECHExt(outer.encryptedClientHello)
|
||||
if err != nil {
|
||||
if errors.Is(err, errInvalidECHExt) {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
} else {
|
||||
c.sendAlert(alertDecodeError)
|
||||
}
|
||||
|
||||
return nil, nil, errInvalidECHExt
|
||||
}
|
||||
|
||||
if echType == innerECHExt {
|
||||
return outer, &echServerContext{inner: true}, nil
|
||||
}
|
||||
|
||||
if len(c.config.EncryptedClientHelloKeys) == 0 {
|
||||
return outer, nil, nil
|
||||
}
|
||||
|
||||
for _, echKey := range c.config.EncryptedClientHelloKeys {
|
||||
skip, config, err := parseECHConfig(echKey.Config)
|
||||
if err != nil || skip {
|
||||
c.sendAlert(alertInternalError)
|
||||
return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys Config: %s", err)
|
||||
}
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
echPriv, err := hpke.ParseHPKEPrivateKey(config.KemID, echKey.PrivateKey)
|
||||
if err != nil {
|
||||
c.sendAlert(alertInternalError)
|
||||
return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys PrivateKey: %s", err)
|
||||
}
|
||||
info := append([]byte("tls ech\x00"), echKey.Config...)
|
||||
hpkeContext, err := hpke.SetupReceipient(hpke.DHKEM_X25519_HKDF_SHA256, echCiphersuite.KDFID, echCiphersuite.AEADID, echPriv, info, encap)
|
||||
if err != nil {
|
||||
// attempt next trial decryption
|
||||
continue
|
||||
}
|
||||
|
||||
encodedInner, err := decryptECHPayload(hpkeContext, outer.original, payload)
|
||||
if err != nil {
|
||||
// attempt next trial decryption
|
||||
continue
|
||||
}
|
||||
|
||||
// NOTE: we do not enforce that the sent server_name matches the ECH
|
||||
// configs PublicName, since this is not particularly important, and
|
||||
// the client already had to know what it was in order to properly
|
||||
// encrypt the payload. This is only a MAY in the spec, so we're not
|
||||
// doing anything revolutionary.
|
||||
|
||||
echInner, err := decodeInnerClientHello(outer, encodedInner)
|
||||
if err != nil {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return nil, nil, errInvalidECHExt
|
||||
}
|
||||
|
||||
c.echAccepted = true
|
||||
|
||||
return echInner, &echServerContext{
|
||||
hpkeContext: hpkeContext,
|
||||
configID: configID,
|
||||
ciphersuite: echCiphersuite,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return outer, nil, nil
|
||||
}
|
||||
|
||||
func buildRetryConfigList(keys []EncryptedClientHelloKey) ([]byte, error) {
|
||||
var atLeastOneRetryConfig bool
|
||||
var retryBuilder cryptobyte.Builder
|
||||
retryBuilder.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
for _, c := range keys {
|
||||
if !c.SendAsRetry {
|
||||
continue
|
||||
}
|
||||
atLeastOneRetryConfig = true
|
||||
b.AddBytes(c.Config)
|
||||
}
|
||||
})
|
||||
if !atLeastOneRetryConfig {
|
||||
return nil, nil
|
||||
}
|
||||
return retryBuilder.Bytes()
|
||||
}
|
||||
+1370
File diff suppressed because it is too large
Load Diff
+1079
File diff suppressed because it is too large
Load Diff
+2004
File diff suppressed because it is too large
Load Diff
+1009
File diff suppressed because it is too large
Load Diff
+1148
File diff suppressed because it is too large
Load Diff
+20
@@ -0,0 +1,20 @@
|
||||
package boring
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const Enabled bool = false
|
||||
|
||||
func NewGCMTLS(_ cipher.Block) (cipher.AEAD, error) {
|
||||
return nil, errors.New("boring not implemented")
|
||||
}
|
||||
|
||||
func NewGCMTLS13(_ cipher.Block) (cipher.AEAD, error) {
|
||||
return nil, errors.New("boring not implemented")
|
||||
}
|
||||
|
||||
func Unreachable() {
|
||||
// do nothing
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package byteorder provides functions for decoding and encoding
|
||||
// little and big endian integer types from/to byte slices.
|
||||
package byteorder
|
||||
|
||||
func LEUint16(b []byte) uint16 {
|
||||
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
return uint16(b[0]) | uint16(b[1])<<8
|
||||
}
|
||||
|
||||
func LEPutUint16(b []byte, v uint16) {
|
||||
_ = b[1] // early bounds check to guarantee safety of writes below
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
}
|
||||
|
||||
func LEAppendUint16(b []byte, v uint16) []byte {
|
||||
return append(b,
|
||||
byte(v),
|
||||
byte(v>>8),
|
||||
)
|
||||
}
|
||||
|
||||
func LEUint32(b []byte) uint32 {
|
||||
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
}
|
||||
|
||||
func LEPutUint32(b []byte, v uint32) {
|
||||
_ = b[3] // early bounds check to guarantee safety of writes below
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
}
|
||||
|
||||
func LEAppendUint32(b []byte, v uint32) []byte {
|
||||
return append(b,
|
||||
byte(v),
|
||||
byte(v>>8),
|
||||
byte(v>>16),
|
||||
byte(v>>24),
|
||||
)
|
||||
}
|
||||
|
||||
func LEUint64(b []byte) uint64 {
|
||||
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
|
||||
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
|
||||
}
|
||||
|
||||
func LEPutUint64(b []byte, v uint64) {
|
||||
_ = b[7] // early bounds check to guarantee safety of writes below
|
||||
b[0] = byte(v)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v >> 16)
|
||||
b[3] = byte(v >> 24)
|
||||
b[4] = byte(v >> 32)
|
||||
b[5] = byte(v >> 40)
|
||||
b[6] = byte(v >> 48)
|
||||
b[7] = byte(v >> 56)
|
||||
}
|
||||
|
||||
func LEAppendUint64(b []byte, v uint64) []byte {
|
||||
return append(b,
|
||||
byte(v),
|
||||
byte(v>>8),
|
||||
byte(v>>16),
|
||||
byte(v>>24),
|
||||
byte(v>>32),
|
||||
byte(v>>40),
|
||||
byte(v>>48),
|
||||
byte(v>>56),
|
||||
)
|
||||
}
|
||||
|
||||
func BEUint16(b []byte) uint16 {
|
||||
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
return uint16(b[1]) | uint16(b[0])<<8
|
||||
}
|
||||
|
||||
func BEPutUint16(b []byte, v uint16) {
|
||||
_ = b[1] // early bounds check to guarantee safety of writes below
|
||||
b[0] = byte(v >> 8)
|
||||
b[1] = byte(v)
|
||||
}
|
||||
|
||||
func BEAppendUint16(b []byte, v uint16) []byte {
|
||||
return append(b,
|
||||
byte(v>>8),
|
||||
byte(v),
|
||||
)
|
||||
}
|
||||
|
||||
func BEUint32(b []byte) uint32 {
|
||||
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
|
||||
}
|
||||
|
||||
func BEPutUint32(b []byte, v uint32) {
|
||||
_ = b[3] // early bounds check to guarantee safety of writes below
|
||||
b[0] = byte(v >> 24)
|
||||
b[1] = byte(v >> 16)
|
||||
b[2] = byte(v >> 8)
|
||||
b[3] = byte(v)
|
||||
}
|
||||
|
||||
func BEAppendUint32(b []byte, v uint32) []byte {
|
||||
return append(b,
|
||||
byte(v>>24),
|
||||
byte(v>>16),
|
||||
byte(v>>8),
|
||||
byte(v),
|
||||
)
|
||||
}
|
||||
|
||||
func BEUint64(b []byte) uint64 {
|
||||
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
|
||||
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
|
||||
}
|
||||
|
||||
func BEPutUint64(b []byte, v uint64) {
|
||||
_ = b[7] // early bounds check to guarantee safety of writes below
|
||||
b[0] = byte(v >> 56)
|
||||
b[1] = byte(v >> 48)
|
||||
b[2] = byte(v >> 40)
|
||||
b[3] = byte(v >> 32)
|
||||
b[4] = byte(v >> 24)
|
||||
b[5] = byte(v >> 16)
|
||||
b[6] = byte(v >> 8)
|
||||
b[7] = byte(v)
|
||||
}
|
||||
|
||||
func BEAppendUint64(b []byte, v uint64) []byte {
|
||||
return append(b,
|
||||
byte(v>>56),
|
||||
byte(v>>48),
|
||||
byte(v>>40),
|
||||
byte(v>>32),
|
||||
byte(v>>24),
|
||||
byte(v>>16),
|
||||
byte(v>>8),
|
||||
byte(v),
|
||||
)
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package fips140tls
|
||||
|
||||
func Required() bool {
|
||||
return false
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
// Uint8to16 converts a slice of uint8 to a slice of uint16.
|
||||
// e.g. []uint8{0x00, 0x01, 0x00, 0x02} -> []uint16{0x0001, 0x0002}
|
||||
func Uint8to16(in []uint8) ([]uint16, error) {
|
||||
s := cryptobyte.String(in)
|
||||
var out []uint16
|
||||
for !s.Empty() {
|
||||
var v uint16
|
||||
if s.ReadUint16(&v) {
|
||||
out = append(out, v)
|
||||
} else {
|
||||
return nil, errors.New("ReadUint16 failed")
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package hkdf
|
||||
|
||||
import (
|
||||
"crypto/hkdf"
|
||||
"hash"
|
||||
)
|
||||
|
||||
func Extract[H hash.Hash](h func() H, secret, salt []byte) []byte {
|
||||
res, err := hkdf.Extract(h, secret, salt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func Expand[H hash.Hash](h func() H, pseudorandomKey []byte, info string, keyLength int) []byte {
|
||||
res, err := hkdf.Expand(h, pseudorandomKey, info, keyLength)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package hpke
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"math/bits"
|
||||
|
||||
"github.com/refraction-networking/utls/internal/byteorder"
|
||||
"github.com/refraction-networking/utls/internal/hkdf"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
// testingOnlyGenerateKey is only used during testing, to provide
|
||||
// a fixed test key to use when checking the RFC 9180 vectors.
|
||||
var testingOnlyGenerateKey func() (*ecdh.PrivateKey, error)
|
||||
|
||||
type hkdfKDF struct {
|
||||
hash crypto.Hash
|
||||
}
|
||||
|
||||
func (kdf *hkdfKDF) LabeledExtract(sid []byte, salt []byte, label string, inputKey []byte) []byte {
|
||||
labeledIKM := make([]byte, 0, 7+len(sid)+len(label)+len(inputKey))
|
||||
labeledIKM = append(labeledIKM, []byte("HPKE-v1")...)
|
||||
labeledIKM = append(labeledIKM, sid...)
|
||||
labeledIKM = append(labeledIKM, label...)
|
||||
labeledIKM = append(labeledIKM, inputKey...)
|
||||
return hkdf.Extract(kdf.hash.New, labeledIKM, salt)
|
||||
}
|
||||
|
||||
func (kdf *hkdfKDF) LabeledExpand(suiteID []byte, randomKey []byte, label string, info []byte, length uint16) []byte {
|
||||
labeledInfo := make([]byte, 0, 2+7+len(suiteID)+len(label)+len(info))
|
||||
labeledInfo = byteorder.BEAppendUint16(labeledInfo, length)
|
||||
labeledInfo = append(labeledInfo, []byte("HPKE-v1")...)
|
||||
labeledInfo = append(labeledInfo, suiteID...)
|
||||
labeledInfo = append(labeledInfo, label...)
|
||||
labeledInfo = append(labeledInfo, info...)
|
||||
return hkdf.Expand(kdf.hash.New, randomKey, string(labeledInfo), int(length))
|
||||
}
|
||||
|
||||
// dhKEM implements the KEM specified in RFC 9180, Section 4.1.
|
||||
type dhKEM struct {
|
||||
dh ecdh.Curve
|
||||
kdf hkdfKDF
|
||||
|
||||
suiteID []byte
|
||||
nSecret uint16
|
||||
}
|
||||
|
||||
type KemID uint16
|
||||
|
||||
const DHKEM_X25519_HKDF_SHA256 = 0x0020
|
||||
|
||||
var SupportedKEMs = map[uint16]struct {
|
||||
curve ecdh.Curve
|
||||
hash crypto.Hash
|
||||
nSecret uint16
|
||||
}{
|
||||
// RFC 9180 Section 7.1
|
||||
DHKEM_X25519_HKDF_SHA256: {ecdh.X25519(), crypto.SHA256, 32},
|
||||
}
|
||||
|
||||
func newDHKem(kemID uint16) (*dhKEM, error) {
|
||||
suite, ok := SupportedKEMs[kemID]
|
||||
if !ok {
|
||||
return nil, errors.New("unsupported suite ID")
|
||||
}
|
||||
return &dhKEM{
|
||||
dh: suite.curve,
|
||||
kdf: hkdfKDF{suite.hash},
|
||||
suiteID: byteorder.BEAppendUint16([]byte("KEM"), kemID),
|
||||
nSecret: suite.nSecret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (dh *dhKEM) ExtractAndExpand(dhKey, kemContext []byte) []byte {
|
||||
eaePRK := dh.kdf.LabeledExtract(dh.suiteID[:], nil, "eae_prk", dhKey)
|
||||
return dh.kdf.LabeledExpand(dh.suiteID[:], eaePRK, "shared_secret", kemContext, dh.nSecret)
|
||||
}
|
||||
|
||||
func (dh *dhKEM) Encap(pubRecipient *ecdh.PublicKey) (sharedSecret []byte, encapPub []byte, err error) {
|
||||
var privEph *ecdh.PrivateKey
|
||||
if testingOnlyGenerateKey != nil {
|
||||
privEph, err = testingOnlyGenerateKey()
|
||||
} else {
|
||||
privEph, err = dh.dh.GenerateKey(rand.Reader)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
dhVal, err := privEph.ECDH(pubRecipient)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
encPubEph := privEph.PublicKey().Bytes()
|
||||
|
||||
encPubRecip := pubRecipient.Bytes()
|
||||
kemContext := append(encPubEph, encPubRecip...)
|
||||
|
||||
return dh.ExtractAndExpand(dhVal, kemContext), encPubEph, nil
|
||||
}
|
||||
|
||||
func (dh *dhKEM) Decap(encPubEph []byte, secRecipient *ecdh.PrivateKey) ([]byte, error) {
|
||||
pubEph, err := dh.dh.NewPublicKey(encPubEph)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dhVal, err := secRecipient.ECDH(pubEph)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
kemContext := append(encPubEph, secRecipient.PublicKey().Bytes()...)
|
||||
|
||||
return dh.ExtractAndExpand(dhVal, kemContext), nil
|
||||
}
|
||||
|
||||
type context struct {
|
||||
aead cipher.AEAD
|
||||
|
||||
sharedSecret []byte
|
||||
|
||||
suiteID []byte
|
||||
|
||||
key []byte
|
||||
baseNonce []byte
|
||||
exporterSecret []byte
|
||||
|
||||
seqNum uint128
|
||||
}
|
||||
|
||||
type Sender struct {
|
||||
*context
|
||||
}
|
||||
|
||||
type Receipient struct {
|
||||
*context
|
||||
}
|
||||
|
||||
var aesGCMNew = func(key []byte) (cipher.AEAD, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cipher.NewGCM(block)
|
||||
}
|
||||
|
||||
type AEADID uint16
|
||||
|
||||
const (
|
||||
AEAD_AES_128_GCM = 0x0001
|
||||
AEAD_AES_256_GCM = 0x0002
|
||||
AEAD_ChaCha20Poly1305 = 0x0003
|
||||
)
|
||||
|
||||
var SupportedAEADs = map[uint16]struct {
|
||||
keySize int
|
||||
nonceSize int
|
||||
aead func([]byte) (cipher.AEAD, error)
|
||||
}{
|
||||
// RFC 9180, Section 7.3
|
||||
AEAD_AES_128_GCM: {keySize: 16, nonceSize: 12, aead: aesGCMNew},
|
||||
AEAD_AES_256_GCM: {keySize: 32, nonceSize: 12, aead: aesGCMNew},
|
||||
AEAD_ChaCha20Poly1305: {keySize: chacha20poly1305.KeySize, nonceSize: chacha20poly1305.NonceSize, aead: chacha20poly1305.New},
|
||||
}
|
||||
|
||||
type KDFID uint16
|
||||
|
||||
const KDF_HKDF_SHA256 = 0x0001
|
||||
|
||||
var SupportedKDFs = map[uint16]func() *hkdfKDF{
|
||||
// RFC 9180, Section 7.2
|
||||
KDF_HKDF_SHA256: func() *hkdfKDF { return &hkdfKDF{crypto.SHA256} },
|
||||
}
|
||||
|
||||
func newContext(sharedSecret []byte, kemID, kdfID, aeadID uint16, info []byte) (*context, error) {
|
||||
sid := suiteID(kemID, kdfID, aeadID)
|
||||
|
||||
kdfInit, ok := SupportedKDFs[kdfID]
|
||||
if !ok {
|
||||
return nil, errors.New("unsupported KDF id")
|
||||
}
|
||||
kdf := kdfInit()
|
||||
|
||||
aeadInfo, ok := SupportedAEADs[aeadID]
|
||||
if !ok {
|
||||
return nil, errors.New("unsupported AEAD id")
|
||||
}
|
||||
|
||||
pskIDHash := kdf.LabeledExtract(sid, nil, "psk_id_hash", nil)
|
||||
infoHash := kdf.LabeledExtract(sid, nil, "info_hash", info)
|
||||
ksContext := append([]byte{0}, pskIDHash...)
|
||||
ksContext = append(ksContext, infoHash...)
|
||||
|
||||
secret := kdf.LabeledExtract(sid, sharedSecret, "secret", nil)
|
||||
|
||||
key := kdf.LabeledExpand(sid, secret, "key", ksContext, uint16(aeadInfo.keySize) /* Nk - key size for AEAD */)
|
||||
baseNonce := kdf.LabeledExpand(sid, secret, "base_nonce", ksContext, uint16(aeadInfo.nonceSize) /* Nn - nonce size for AEAD */)
|
||||
exporterSecret := kdf.LabeledExpand(sid, secret, "exp", ksContext, uint16(kdf.hash.Size()) /* Nh - hash output size of the kdf*/)
|
||||
|
||||
aead, err := aeadInfo.aead(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &context{
|
||||
aead: aead,
|
||||
sharedSecret: sharedSecret,
|
||||
suiteID: sid,
|
||||
key: key,
|
||||
baseNonce: baseNonce,
|
||||
exporterSecret: exporterSecret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SetupSender(kemID, kdfID, aeadID uint16, pub *ecdh.PublicKey, info []byte) ([]byte, *Sender, error) {
|
||||
kem, err := newDHKem(kemID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sharedSecret, encapsulatedKey, err := kem.Encap(pub)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return encapsulatedKey, &Sender{context}, nil
|
||||
}
|
||||
|
||||
func SetupReceipient(kemID, kdfID, aeadID uint16, priv *ecdh.PrivateKey, info, encPubEph []byte) (*Receipient, error) {
|
||||
kem, err := newDHKem(kemID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sharedSecret, err := kem.Decap(encPubEph, priv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Receipient{context}, nil
|
||||
}
|
||||
|
||||
func (ctx *context) nextNonce() []byte {
|
||||
nonce := ctx.seqNum.bytes()[16-ctx.aead.NonceSize():]
|
||||
for i := range ctx.baseNonce {
|
||||
nonce[i] ^= ctx.baseNonce[i]
|
||||
}
|
||||
return nonce
|
||||
}
|
||||
|
||||
func (ctx *context) incrementNonce() {
|
||||
// Message limit is, according to the RFC, 2^95+1, which
|
||||
// is somewhat confusing, but we do as we're told.
|
||||
if ctx.seqNum.bitLen() >= (ctx.aead.NonceSize()*8)-1 {
|
||||
panic("message limit reached")
|
||||
}
|
||||
ctx.seqNum = ctx.seqNum.addOne()
|
||||
}
|
||||
|
||||
func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) {
|
||||
ciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad)
|
||||
s.incrementNonce()
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func (r *Receipient) Open(aad, ciphertext []byte) ([]byte, error) {
|
||||
plaintext, err := r.aead.Open(nil, r.nextNonce(), ciphertext, aad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.incrementNonce()
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func suiteID(kemID, kdfID, aeadID uint16) []byte {
|
||||
suiteID := make([]byte, 0, 4+2+2+2)
|
||||
suiteID = append(suiteID, []byte("HPKE")...)
|
||||
suiteID = byteorder.BEAppendUint16(suiteID, kemID)
|
||||
suiteID = byteorder.BEAppendUint16(suiteID, kdfID)
|
||||
suiteID = byteorder.BEAppendUint16(suiteID, aeadID)
|
||||
return suiteID
|
||||
}
|
||||
|
||||
func ParseHPKEPublicKey(kemID uint16, bytes []byte) (*ecdh.PublicKey, error) {
|
||||
kemInfo, ok := SupportedKEMs[kemID]
|
||||
if !ok {
|
||||
return nil, errors.New("unsupported KEM id")
|
||||
}
|
||||
return kemInfo.curve.NewPublicKey(bytes)
|
||||
}
|
||||
|
||||
func ParseHPKEPrivateKey(kemID uint16, bytes []byte) (*ecdh.PrivateKey, error) {
|
||||
kemInfo, ok := SupportedKEMs[kemID]
|
||||
if !ok {
|
||||
return nil, errors.New("unsupported KEM id")
|
||||
}
|
||||
return kemInfo.curve.NewPrivateKey(bytes)
|
||||
}
|
||||
|
||||
type uint128 struct {
|
||||
hi, lo uint64
|
||||
}
|
||||
|
||||
func (u uint128) addOne() uint128 {
|
||||
lo, carry := bits.Add64(u.lo, 1, 0)
|
||||
return uint128{u.hi + carry, lo}
|
||||
}
|
||||
|
||||
func (u uint128) bitLen() int {
|
||||
return bits.Len64(u.hi) + bits.Len64(u.lo)
|
||||
}
|
||||
|
||||
func (u uint128) bytes() []byte {
|
||||
b := make([]byte, 16)
|
||||
byteorder.BEPutUint64(b[0:], u.hi)
|
||||
byteorder.BEPutUint64(b[8:], u.lo)
|
||||
return b
|
||||
}
|
||||
Generated
Vendored
+157
@@ -0,0 +1,157 @@
|
||||
// Copyright 2024 The quic-go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file of
|
||||
// the quic-go repository.
|
||||
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The PacketType is the Long Header Type
|
||||
type PacketType uint8
|
||||
|
||||
const (
|
||||
// PacketTypeInitial is the packet type of an Initial packet
|
||||
PacketTypeInitial PacketType = 1 + iota
|
||||
// PacketTypeRetry is the packet type of a Retry packet
|
||||
PacketTypeRetry
|
||||
// PacketTypeHandshake is the packet type of a Handshake packet
|
||||
PacketTypeHandshake
|
||||
// PacketType0RTT is the packet type of a 0-RTT packet
|
||||
PacketType0RTT
|
||||
)
|
||||
|
||||
func (t PacketType) String() string {
|
||||
switch t {
|
||||
case PacketTypeInitial:
|
||||
return "Initial"
|
||||
case PacketTypeRetry:
|
||||
return "Retry"
|
||||
case PacketTypeHandshake:
|
||||
return "Handshake"
|
||||
case PacketType0RTT:
|
||||
return "0-RTT Protected"
|
||||
default:
|
||||
return fmt.Sprintf("unknown packet type: %d", t)
|
||||
}
|
||||
}
|
||||
|
||||
type ECN uint8
|
||||
|
||||
const (
|
||||
ECNUnsupported ECN = iota
|
||||
ECNNon // 00
|
||||
ECT1 // 01
|
||||
ECT0 // 10
|
||||
ECNCE // 11
|
||||
)
|
||||
|
||||
func ParseECNHeaderBits(bits byte) ECN {
|
||||
switch bits {
|
||||
case 0:
|
||||
return ECNNon
|
||||
case 0b00000010:
|
||||
return ECT0
|
||||
case 0b00000001:
|
||||
return ECT1
|
||||
case 0b00000011:
|
||||
return ECNCE
|
||||
default:
|
||||
panic("invalid ECN bits")
|
||||
}
|
||||
}
|
||||
|
||||
func (e ECN) ToHeaderBits() byte {
|
||||
//nolint:exhaustive // There are only 4 values.
|
||||
switch e {
|
||||
case ECNNon:
|
||||
return 0
|
||||
case ECT0:
|
||||
return 0b00000010
|
||||
case ECT1:
|
||||
return 0b00000001
|
||||
case ECNCE:
|
||||
return 0b00000011
|
||||
default:
|
||||
panic("ECN unsupported")
|
||||
}
|
||||
}
|
||||
|
||||
func (e ECN) String() string {
|
||||
switch e {
|
||||
case ECNUnsupported:
|
||||
return "ECN unsupported"
|
||||
case ECNNon:
|
||||
return "Not-ECT"
|
||||
case ECT1:
|
||||
return "ECT(1)"
|
||||
case ECT0:
|
||||
return "ECT(0)"
|
||||
case ECNCE:
|
||||
return "CE"
|
||||
default:
|
||||
return fmt.Sprintf("invalid ECN value: %d", e)
|
||||
}
|
||||
}
|
||||
|
||||
// A ByteCount in QUIC
|
||||
type ByteCount int64
|
||||
|
||||
// MaxByteCount is the maximum value of a ByteCount
|
||||
const MaxByteCount = ByteCount(1<<62 - 1)
|
||||
|
||||
// InvalidByteCount is an invalid byte count
|
||||
const InvalidByteCount ByteCount = -1
|
||||
|
||||
// A StatelessResetToken is a stateless reset token.
|
||||
type StatelessResetToken [16]byte
|
||||
|
||||
// MaxPacketBufferSize maximum packet size of any QUIC packet, based on
|
||||
// ethernet's max size, minus the IP and UDP headers. IPv6 has a 40 byte header,
|
||||
// UDP adds an additional 8 bytes. This is a total overhead of 48 bytes.
|
||||
// Ethernet's max packet size is 1500 bytes, 1500 - 48 = 1452.
|
||||
const MaxPacketBufferSize = 1452
|
||||
|
||||
// MaxLargePacketBufferSize is used when using GSO
|
||||
const MaxLargePacketBufferSize = 20 * 1024
|
||||
|
||||
// MinInitialPacketSize is the minimum size an Initial packet is required to have.
|
||||
const MinInitialPacketSize = 1200
|
||||
|
||||
// MinUnknownVersionPacketSize is the minimum size a packet with an unknown version
|
||||
// needs to have in order to trigger a Version Negotiation packet.
|
||||
const MinUnknownVersionPacketSize = MinInitialPacketSize
|
||||
|
||||
// MinStatelessResetSize is the minimum size of a stateless reset packet that we send
|
||||
const MinStatelessResetSize = 1 /* first byte */ + 20 /* max. conn ID length */ + 4 /* max. packet number length */ + 1 /* min. payload length */ + 16 /* token */
|
||||
|
||||
// MinConnectionIDLenInitial is the minimum length of the destination connection ID on an Initial packet.
|
||||
const MinConnectionIDLenInitial = 8
|
||||
|
||||
// DefaultAckDelayExponent is the default ack delay exponent
|
||||
const DefaultAckDelayExponent = 3
|
||||
|
||||
// DefaultActiveConnectionIDLimit is the default active connection ID limit
|
||||
const DefaultActiveConnectionIDLimit = 2
|
||||
|
||||
// MaxAckDelayExponent is the maximum ack delay exponent
|
||||
const MaxAckDelayExponent = 20
|
||||
|
||||
// DefaultMaxAckDelay is the default max_ack_delay
|
||||
const DefaultMaxAckDelay = 25 * time.Millisecond
|
||||
|
||||
// MaxMaxAckDelay is the maximum max_ack_delay
|
||||
const MaxMaxAckDelay = (1<<14 - 1) * time.Millisecond
|
||||
|
||||
// MaxConnIDLen is the maximum length of the connection ID
|
||||
const MaxConnIDLen = 20
|
||||
|
||||
// InvalidPacketLimitAES is the maximum number of packets that we can fail to decrypt when using
|
||||
// AEAD_AES_128_GCM or AEAD_AES_265_GCM.
|
||||
const InvalidPacketLimitAES = 1 << 52
|
||||
|
||||
// InvalidPacketLimitChaCha is the maximum number of packets that we can fail to decrypt when using AEAD_CHACHA20_POLY1305.
|
||||
const InvalidPacketLimitChaCha = 1 << 36
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
// Copyright 2024 The quic-go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file of
|
||||
// the quic-go repository.
|
||||
|
||||
package quicvarint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/refraction-networking/utls/internal/quicvarint/protocol"
|
||||
)
|
||||
|
||||
// taken from the QUIC draft
|
||||
const (
|
||||
// Min is the minimum value allowed for a QUIC varint.
|
||||
Min = 0
|
||||
|
||||
// Max is the maximum allowed value for a QUIC varint (2^62-1).
|
||||
Max = maxVarInt8
|
||||
|
||||
maxVarInt1 = 63
|
||||
maxVarInt2 = 16383
|
||||
maxVarInt4 = 1073741823
|
||||
maxVarInt8 = 4611686018427387903
|
||||
)
|
||||
|
||||
// Read reads a number in the QUIC varint format from r.
|
||||
func Read(r io.ByteReader) (uint64, error) {
|
||||
firstByte, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// the first two bits of the first byte encode the length
|
||||
len := 1 << ((firstByte & 0xc0) >> 6)
|
||||
b1 := firstByte & (0xff - 0xc0)
|
||||
if len == 1 {
|
||||
return uint64(b1), nil
|
||||
}
|
||||
b2, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len == 2 {
|
||||
return uint64(b2) + uint64(b1)<<8, nil
|
||||
}
|
||||
b3, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b4, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len == 4 {
|
||||
return uint64(b4) + uint64(b3)<<8 + uint64(b2)<<16 + uint64(b1)<<24, nil
|
||||
}
|
||||
b5, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b6, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b7, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b8, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint64(b8) + uint64(b7)<<8 + uint64(b6)<<16 + uint64(b5)<<24 + uint64(b4)<<32 + uint64(b3)<<40 + uint64(b2)<<48 + uint64(b1)<<56, nil
|
||||
}
|
||||
|
||||
// Append appends i in the QUIC varint format.
|
||||
func Append(b []byte, i uint64) []byte {
|
||||
if i <= maxVarInt1 {
|
||||
return append(b, uint8(i))
|
||||
}
|
||||
if i <= maxVarInt2 {
|
||||
return append(b, []byte{uint8(i>>8) | 0x40, uint8(i)}...)
|
||||
}
|
||||
if i <= maxVarInt4 {
|
||||
return append(b, []byte{uint8(i>>24) | 0x80, uint8(i >> 16), uint8(i >> 8), uint8(i)}...)
|
||||
}
|
||||
if i <= maxVarInt8 {
|
||||
return append(b, []byte{
|
||||
uint8(i>>56) | 0xc0, uint8(i >> 48), uint8(i >> 40), uint8(i >> 32),
|
||||
uint8(i >> 24), uint8(i >> 16), uint8(i >> 8), uint8(i),
|
||||
}...)
|
||||
}
|
||||
panic(fmt.Sprintf("%#x doesn't fit into 62 bits", i))
|
||||
}
|
||||
|
||||
// AppendWithLen append i in the QUIC varint format with the desired length.
|
||||
func AppendWithLen(b []byte, i uint64, length protocol.ByteCount) []byte {
|
||||
if length != 1 && length != 2 && length != 4 && length != 8 {
|
||||
panic("invalid varint length")
|
||||
}
|
||||
l := Len(i)
|
||||
if l == length {
|
||||
return Append(b, i)
|
||||
}
|
||||
if l > length {
|
||||
panic(fmt.Sprintf("cannot encode %d in %d bytes", i, length))
|
||||
}
|
||||
if length == 2 {
|
||||
b = append(b, 0b01000000)
|
||||
} else if length == 4 {
|
||||
b = append(b, 0b10000000)
|
||||
} else if length == 8 {
|
||||
b = append(b, 0b11000000)
|
||||
}
|
||||
for j := protocol.ByteCount(1); j < length-l; j++ {
|
||||
b = append(b, 0)
|
||||
}
|
||||
for j := protocol.ByteCount(0); j < l; j++ {
|
||||
b = append(b, uint8(i>>(8*(l-1-j))))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Len determines the number of bytes that will be needed to write the number i.
|
||||
func Len(i uint64) protocol.ByteCount {
|
||||
if i <= maxVarInt1 {
|
||||
return 1
|
||||
}
|
||||
if i <= maxVarInt2 {
|
||||
return 2
|
||||
}
|
||||
if i <= maxVarInt4 {
|
||||
return 4
|
||||
}
|
||||
if i <= maxVarInt8 {
|
||||
return 8
|
||||
}
|
||||
// Don't use a fmt.Sprintf here to format the error message.
|
||||
// The function would then exceed the inlining budget.
|
||||
panic(struct {
|
||||
message string
|
||||
num uint64
|
||||
}{"value doesn't fit into 62 bits: ", i})
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls12
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// PRF implements the TLS 1.2 pseudo-random function, as defined in RFC 5246,
|
||||
// Section 5 and allowed by SP 800-135, Revision 1, Section 4.2.2.
|
||||
func PRF(hash func() hash.Hash, secret []byte, label string, seed []byte, keyLen int) []byte {
|
||||
labelAndSeed := make([]byte, len(label)+len(seed))
|
||||
copy(labelAndSeed, label)
|
||||
copy(labelAndSeed[len(label):], seed)
|
||||
|
||||
result := make([]byte, keyLen)
|
||||
pHash(hash, result, secret, labelAndSeed)
|
||||
return result
|
||||
}
|
||||
|
||||
// pHash implements the P_hash function, as defined in RFC 5246, Section 5.
|
||||
func pHash(hash func() hash.Hash, result, secret, seed []byte) {
|
||||
h := hmac.New(hash, secret)
|
||||
h.Write(seed)
|
||||
a := h.Sum(nil)
|
||||
|
||||
for len(result) > 0 {
|
||||
h.Reset()
|
||||
h.Write(a)
|
||||
h.Write(seed)
|
||||
b := h.Sum(nil)
|
||||
n := copy(result, b)
|
||||
result = result[n:]
|
||||
|
||||
h.Reset()
|
||||
h.Write(a)
|
||||
a = h.Sum(nil)
|
||||
}
|
||||
}
|
||||
|
||||
const masterSecretLength = 48
|
||||
const extendedMasterSecretLabel = "extended master secret"
|
||||
|
||||
// MasterSecret implements the TLS 1.2 extended master secret derivation, as
|
||||
// defined in RFC 7627 and allowed by SP 800-135, Revision 1, Section 4.2.2.
|
||||
func MasterSecret(hash func() hash.Hash, preMasterSecret, transcript []byte) []byte {
|
||||
// [uTLS SECTION BEGIN]
|
||||
// "The TLS 1.2 KDF is an approved KDF when the following conditions are
|
||||
// satisfied: [...] (3) P_HASH uses either SHA-256, SHA-384 or SHA-512."
|
||||
// h := hash()
|
||||
// switch any(h).(type) {
|
||||
// case *sha256.Digest:
|
||||
// if h.Size() != 32 {
|
||||
// fips140.RecordNonApproved()
|
||||
// }
|
||||
// case *sha512.Digest:
|
||||
// if h.Size() != 46 && h.Size() != 64 {
|
||||
// fips140.RecordNonApproved()
|
||||
// }
|
||||
// default:
|
||||
// fips140.RecordNonApproved()
|
||||
// }
|
||||
// [uTLS SECTION END]
|
||||
|
||||
return PRF(hash, preMasterSecret, extendedMasterSecretLabel, transcript, masterSecretLength)
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package tls13 implements the TLS 1.3 Key Schedule as specified in RFC 8446,
|
||||
// Section 7.1 and allowed by FIPS 140-3 IG 2.4.B Resolution 7.
|
||||
package tls13
|
||||
|
||||
import (
|
||||
fips140 "hash"
|
||||
|
||||
"github.com/refraction-networking/utls/internal/byteorder"
|
||||
"github.com/refraction-networking/utls/internal/hkdf"
|
||||
)
|
||||
|
||||
// We don't set the service indicator in this package but we delegate that to
|
||||
// the underlying functions because the TLS 1.3 KDF does not have a standard of
|
||||
// its own.
|
||||
|
||||
// ExpandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1.
|
||||
func ExpandLabel[H fips140.Hash](hash func() H, secret []byte, label string, context []byte, length int) []byte {
|
||||
if len("tls13 ")+len(label) > 255 || len(context) > 255 {
|
||||
// It should be impossible for this to panic: labels are fixed strings,
|
||||
// and context is either a fixed-length computed hash, or parsed from a
|
||||
// field which has the same length limitation.
|
||||
//
|
||||
// Another reasonable approach might be to return a randomized slice if
|
||||
// we encounter an error, which would break the connection, but avoid
|
||||
// panicking. This would perhaps be safer but significantly more
|
||||
// confusing to users.
|
||||
panic("tls13: label or context too long")
|
||||
}
|
||||
hkdfLabel := make([]byte, 0, 2+1+len("tls13 ")+len(label)+1+len(context))
|
||||
hkdfLabel = byteorder.BEAppendUint16(hkdfLabel, uint16(length))
|
||||
hkdfLabel = append(hkdfLabel, byte(len("tls13 ")+len(label)))
|
||||
hkdfLabel = append(hkdfLabel, "tls13 "...)
|
||||
hkdfLabel = append(hkdfLabel, label...)
|
||||
hkdfLabel = append(hkdfLabel, byte(len(context)))
|
||||
hkdfLabel = append(hkdfLabel, context...)
|
||||
return hkdf.Expand(hash, secret, string(hkdfLabel), length)
|
||||
}
|
||||
|
||||
func extract[H fips140.Hash](hash func() H, newSecret, currentSecret []byte) []byte {
|
||||
if newSecret == nil {
|
||||
newSecret = make([]byte, hash().Size())
|
||||
}
|
||||
return hkdf.Extract(hash, newSecret, currentSecret)
|
||||
}
|
||||
|
||||
func deriveSecret[H fips140.Hash](hash func() H, secret []byte, label string, transcript fips140.Hash) []byte {
|
||||
if transcript == nil {
|
||||
transcript = hash()
|
||||
}
|
||||
return ExpandLabel(hash, secret, label, transcript.Sum(nil), transcript.Size())
|
||||
}
|
||||
|
||||
const (
|
||||
resumptionBinderLabel = "res binder"
|
||||
clientEarlyTrafficLabel = "c e traffic"
|
||||
clientHandshakeTrafficLabel = "c hs traffic"
|
||||
serverHandshakeTrafficLabel = "s hs traffic"
|
||||
clientApplicationTrafficLabel = "c ap traffic"
|
||||
serverApplicationTrafficLabel = "s ap traffic"
|
||||
earlyExporterLabel = "e exp master"
|
||||
exporterLabel = "exp master"
|
||||
resumptionLabel = "res master"
|
||||
)
|
||||
|
||||
type EarlySecret struct {
|
||||
secret []byte
|
||||
hash func() fips140.Hash
|
||||
}
|
||||
|
||||
func NewEarlySecret[H fips140.Hash](hash func() H, psk []byte) *EarlySecret {
|
||||
return &EarlySecret{
|
||||
secret: extract(hash, psk, nil),
|
||||
hash: func() fips140.Hash { return hash() },
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EarlySecret) ResumptionBinderKey() []byte {
|
||||
return deriveSecret(s.hash, s.secret, resumptionBinderLabel, nil)
|
||||
}
|
||||
|
||||
// ClientEarlyTrafficSecret derives the client_early_traffic_secret from the
|
||||
// early secret and the transcript up to the ClientHello.
|
||||
func (s *EarlySecret) ClientEarlyTrafficSecret(transcript fips140.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, clientEarlyTrafficLabel, transcript)
|
||||
}
|
||||
|
||||
type HandshakeSecret struct {
|
||||
secret []byte
|
||||
hash func() fips140.Hash
|
||||
}
|
||||
|
||||
func (s *EarlySecret) HandshakeSecret(sharedSecret []byte) *HandshakeSecret {
|
||||
derived := deriveSecret(s.hash, s.secret, "derived", nil)
|
||||
return &HandshakeSecret{
|
||||
secret: extract(s.hash, sharedSecret, derived),
|
||||
hash: s.hash,
|
||||
}
|
||||
}
|
||||
|
||||
// ClientHandshakeTrafficSecret derives the client_handshake_traffic_secret from
|
||||
// the handshake secret and the transcript up to the ServerHello.
|
||||
func (s *HandshakeSecret) ClientHandshakeTrafficSecret(transcript fips140.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, clientHandshakeTrafficLabel, transcript)
|
||||
}
|
||||
|
||||
// ServerHandshakeTrafficSecret derives the server_handshake_traffic_secret from
|
||||
// the handshake secret and the transcript up to the ServerHello.
|
||||
func (s *HandshakeSecret) ServerHandshakeTrafficSecret(transcript fips140.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, serverHandshakeTrafficLabel, transcript)
|
||||
}
|
||||
|
||||
type MasterSecret struct {
|
||||
secret []byte
|
||||
hash func() fips140.Hash
|
||||
}
|
||||
|
||||
func (s *HandshakeSecret) MasterSecret() *MasterSecret {
|
||||
derived := deriveSecret(s.hash, s.secret, "derived", nil)
|
||||
return &MasterSecret{
|
||||
secret: extract(s.hash, nil, derived),
|
||||
hash: s.hash,
|
||||
}
|
||||
}
|
||||
|
||||
// ClientApplicationTrafficSecret derives the client_application_traffic_secret_0
|
||||
// from the master secret and the transcript up to the server Finished.
|
||||
func (s *MasterSecret) ClientApplicationTrafficSecret(transcript fips140.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, clientApplicationTrafficLabel, transcript)
|
||||
}
|
||||
|
||||
// ServerApplicationTrafficSecret derives the server_application_traffic_secret_0
|
||||
// from the master secret and the transcript up to the server Finished.
|
||||
func (s *MasterSecret) ServerApplicationTrafficSecret(transcript fips140.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, serverApplicationTrafficLabel, transcript)
|
||||
}
|
||||
|
||||
// ResumptionMasterSecret derives the resumption_master_secret from the master secret
|
||||
// and the transcript up to the client Finished.
|
||||
func (s *MasterSecret) ResumptionMasterSecret(transcript fips140.Hash) []byte {
|
||||
return deriveSecret(s.hash, s.secret, resumptionLabel, transcript)
|
||||
}
|
||||
|
||||
type ExporterMasterSecret struct {
|
||||
secret []byte
|
||||
hash func() fips140.Hash
|
||||
}
|
||||
|
||||
// ExporterMasterSecret derives the exporter_master_secret from the master secret
|
||||
// and the transcript up to the server Finished.
|
||||
func (s *MasterSecret) ExporterMasterSecret(transcript fips140.Hash) *ExporterMasterSecret {
|
||||
return &ExporterMasterSecret{
|
||||
secret: deriveSecret(s.hash, s.secret, exporterLabel, transcript),
|
||||
hash: s.hash,
|
||||
}
|
||||
}
|
||||
|
||||
// EarlyExporterMasterSecret derives the exporter_master_secret from the early secret
|
||||
// and the transcript up to the ClientHello.
|
||||
func (s *EarlySecret) EarlyExporterMasterSecret(transcript fips140.Hash) *ExporterMasterSecret {
|
||||
return &ExporterMasterSecret{
|
||||
secret: deriveSecret(s.hash, s.secret, earlyExporterLabel, transcript),
|
||||
hash: s.hash,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ExporterMasterSecret) Exporter(label string, context []byte, length int) []byte {
|
||||
secret := deriveSecret(s.hash, s.secret, label, nil)
|
||||
h := s.hash()
|
||||
h.Write(context)
|
||||
return ExpandLabel(s.hash, secret, "exporter", h.Sum(nil), length)
|
||||
}
|
||||
|
||||
func TestingOnlyExporterSecret(s *ExporterMasterSecret) []byte {
|
||||
return s.secret
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package tls13
|
||||
|
||||
import fips140 "hash"
|
||||
|
||||
func NewEarlySecretFromSecret[H fips140.Hash](hash func() H, secret []byte) *EarlySecret {
|
||||
return &EarlySecret{
|
||||
secret: secret,
|
||||
hash: func() fips140.Hash { return hash() },
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EarlySecret) Secret() []byte {
|
||||
if s != nil {
|
||||
return s.secret
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMasterSecretFromSecret[H fips140.Hash](hash func() H, secret []byte) *MasterSecret {
|
||||
return &MasterSecret{
|
||||
secret: secret,
|
||||
hash: func() fips140.Hash { return hash() },
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MasterSecret) Secret() []byte {
|
||||
if s != nil {
|
||||
return s.secret
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdh"
|
||||
"crypto/md5"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// A keyAgreement implements the client and server side of a TLS 1.0–1.2 key
|
||||
// agreement protocol by generating and processing key exchange messages.
|
||||
type keyAgreement interface {
|
||||
// On the server side, the first two methods are called in order.
|
||||
|
||||
// In the case that the key agreement protocol doesn't use a
|
||||
// ServerKeyExchange message, generateServerKeyExchange can return nil,
|
||||
// nil.
|
||||
generateServerKeyExchange(*Config, *Certificate, *clientHelloMsg, *serverHelloMsg) (*serverKeyExchangeMsg, error)
|
||||
processClientKeyExchange(*Config, *Certificate, *clientKeyExchangeMsg, uint16) ([]byte, error)
|
||||
|
||||
// On the client side, the next two methods are called in order.
|
||||
|
||||
// This method may not be called if the server doesn't send a
|
||||
// ServerKeyExchange message.
|
||||
processServerKeyExchange(*Config, *clientHelloMsg, *serverHelloMsg, *x509.Certificate, *serverKeyExchangeMsg) error
|
||||
generateClientKeyExchange(*Config, *clientHelloMsg, *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error)
|
||||
}
|
||||
|
||||
var errClientKeyExchange = errors.New("tls: invalid ClientKeyExchange message")
|
||||
var errServerKeyExchange = errors.New("tls: invalid ServerKeyExchange message")
|
||||
|
||||
// rsaKeyAgreement implements the standard TLS key agreement where the client
|
||||
// encrypts the pre-master secret to the server's public key.
|
||||
type rsaKeyAgreement struct{}
|
||||
|
||||
func (ka rsaKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
|
||||
if len(ckx.ciphertext) < 2 {
|
||||
return nil, errClientKeyExchange
|
||||
}
|
||||
ciphertextLen := int(ckx.ciphertext[0])<<8 | int(ckx.ciphertext[1])
|
||||
if ciphertextLen != len(ckx.ciphertext)-2 {
|
||||
return nil, errClientKeyExchange
|
||||
}
|
||||
ciphertext := ckx.ciphertext[2:]
|
||||
|
||||
priv, ok := cert.PrivateKey.(crypto.Decrypter)
|
||||
if !ok {
|
||||
return nil, errors.New("tls: certificate private key does not implement crypto.Decrypter")
|
||||
}
|
||||
// Perform constant time RSA PKCS #1 v1.5 decryption
|
||||
preMasterSecret, err := priv.Decrypt(config.rand(), ciphertext, &rsa.PKCS1v15DecryptOptions{SessionKeyLen: 48})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We don't check the version number in the premaster secret. For one,
|
||||
// by checking it, we would leak information about the validity of the
|
||||
// encrypted pre-master secret. Secondly, it provides only a small
|
||||
// benefit against a downgrade attack and some implementations send the
|
||||
// wrong version anyway. See the discussion at the end of section
|
||||
// 7.4.7.1 of RFC 4346.
|
||||
return preMasterSecret, nil
|
||||
}
|
||||
|
||||
func (ka rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
|
||||
return errors.New("tls: unexpected ServerKeyExchange")
|
||||
}
|
||||
|
||||
func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
|
||||
preMasterSecret := make([]byte, 48)
|
||||
preMasterSecret[0] = byte(clientHello.vers >> 8)
|
||||
preMasterSecret[1] = byte(clientHello.vers)
|
||||
_, err := io.ReadFull(config.rand(), preMasterSecret[2:])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rsaKey, ok := cert.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("tls: server certificate contains incorrect key type for selected ciphersuite")
|
||||
}
|
||||
encrypted, err := rsa.EncryptPKCS1v15(config.rand(), rsaKey, preMasterSecret)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ckx := new(clientKeyExchangeMsg)
|
||||
ckx.ciphertext = make([]byte, len(encrypted)+2)
|
||||
ckx.ciphertext[0] = byte(len(encrypted) >> 8)
|
||||
ckx.ciphertext[1] = byte(len(encrypted))
|
||||
copy(ckx.ciphertext[2:], encrypted)
|
||||
return preMasterSecret, ckx, nil
|
||||
}
|
||||
|
||||
// sha1Hash calculates a SHA1 hash over the given byte slices.
|
||||
func sha1Hash(slices [][]byte) []byte {
|
||||
hsha1 := sha1.New()
|
||||
for _, slice := range slices {
|
||||
hsha1.Write(slice)
|
||||
}
|
||||
return hsha1.Sum(nil)
|
||||
}
|
||||
|
||||
// md5SHA1Hash implements TLS 1.0's hybrid hash function which consists of the
|
||||
// concatenation of an MD5 and SHA1 hash.
|
||||
func md5SHA1Hash(slices [][]byte) []byte {
|
||||
md5sha1 := make([]byte, md5.Size+sha1.Size)
|
||||
hmd5 := md5.New()
|
||||
for _, slice := range slices {
|
||||
hmd5.Write(slice)
|
||||
}
|
||||
copy(md5sha1, hmd5.Sum(nil))
|
||||
copy(md5sha1[md5.Size:], sha1Hash(slices))
|
||||
return md5sha1
|
||||
}
|
||||
|
||||
// hashForServerKeyExchange hashes the given slices and returns their digest
|
||||
// using the given hash function (for TLS 1.2) or using a default based on
|
||||
// the sigType (for earlier TLS versions). For Ed25519 signatures, which don't
|
||||
// do pre-hashing, it returns the concatenation of the slices.
|
||||
func hashForServerKeyExchange(sigType uint8, hashFunc crypto.Hash, version uint16, slices ...[]byte) []byte {
|
||||
if sigType == signatureEd25519 {
|
||||
var signed []byte
|
||||
for _, slice := range slices {
|
||||
signed = append(signed, slice...)
|
||||
}
|
||||
return signed
|
||||
}
|
||||
if version >= VersionTLS12 {
|
||||
h := hashFunc.New()
|
||||
for _, slice := range slices {
|
||||
h.Write(slice)
|
||||
}
|
||||
digest := h.Sum(nil)
|
||||
return digest
|
||||
}
|
||||
if sigType == signatureECDSA {
|
||||
return sha1Hash(slices)
|
||||
}
|
||||
return md5SHA1Hash(slices)
|
||||
}
|
||||
|
||||
// ecdheKeyAgreement implements a TLS key agreement where the server
|
||||
// generates an ephemeral EC public/private key pair and signs it. The
|
||||
// pre-master secret is then calculated using ECDH. The signature may
|
||||
// be ECDSA, Ed25519 or RSA.
|
||||
type ecdheKeyAgreement struct {
|
||||
version uint16
|
||||
isRSA bool
|
||||
key *ecdh.PrivateKey
|
||||
|
||||
// ckx and preMasterSecret are generated in processServerKeyExchange
|
||||
// and returned in generateClientKeyExchange.
|
||||
ckx *clientKeyExchangeMsg
|
||||
preMasterSecret []byte
|
||||
}
|
||||
|
||||
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
|
||||
var curveID CurveID
|
||||
for _, c := range clientHello.supportedCurves {
|
||||
if config.supportsCurve(ka.version, c) {
|
||||
curveID = c
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if curveID == 0 {
|
||||
return nil, errors.New("tls: no supported elliptic curves offered")
|
||||
}
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
return nil, errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
}
|
||||
|
||||
key, err := generateECDHEKey(config.rand(), curveID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ka.key = key
|
||||
|
||||
// See RFC 4492, Section 5.4.
|
||||
ecdhePublic := key.PublicKey().Bytes()
|
||||
serverECDHEParams := make([]byte, 1+2+1+len(ecdhePublic))
|
||||
serverECDHEParams[0] = 3 // named curve
|
||||
serverECDHEParams[1] = byte(curveID >> 8)
|
||||
serverECDHEParams[2] = byte(curveID)
|
||||
serverECDHEParams[3] = byte(len(ecdhePublic))
|
||||
copy(serverECDHEParams[4:], ecdhePublic)
|
||||
|
||||
priv, ok := cert.PrivateKey.(crypto.Signer)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tls: certificate private key of type %T does not implement crypto.Signer", cert.PrivateKey)
|
||||
}
|
||||
|
||||
var signatureAlgorithm SignatureScheme
|
||||
var sigType uint8
|
||||
var sigHash crypto.Hash
|
||||
if ka.version >= VersionTLS12 {
|
||||
signatureAlgorithm, err = selectSignatureScheme(ka.version, cert, clientHello.supportedSignatureAlgorithms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigType, sigHash, err = typeAndHashFromSignatureScheme(signatureAlgorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
sigType, sigHash, err = legacyTypeAndHashFromPublicKey(priv.Public())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA {
|
||||
return nil, errors.New("tls: certificate cannot be used with the selected cipher suite")
|
||||
}
|
||||
|
||||
signed := hashForServerKeyExchange(sigType, sigHash, ka.version, clientHello.random, hello.random, serverECDHEParams)
|
||||
|
||||
signOpts := crypto.SignerOpts(sigHash)
|
||||
if sigType == signatureRSAPSS {
|
||||
signOpts = &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash, Hash: sigHash}
|
||||
}
|
||||
sig, err := priv.Sign(config.rand(), signed, signOpts)
|
||||
if err != nil {
|
||||
return nil, errors.New("tls: failed to sign ECDHE parameters: " + err.Error())
|
||||
}
|
||||
|
||||
skx := new(serverKeyExchangeMsg)
|
||||
sigAndHashLen := 0
|
||||
if ka.version >= VersionTLS12 {
|
||||
sigAndHashLen = 2
|
||||
}
|
||||
skx.key = make([]byte, len(serverECDHEParams)+sigAndHashLen+2+len(sig))
|
||||
copy(skx.key, serverECDHEParams)
|
||||
k := skx.key[len(serverECDHEParams):]
|
||||
if ka.version >= VersionTLS12 {
|
||||
k[0] = byte(signatureAlgorithm >> 8)
|
||||
k[1] = byte(signatureAlgorithm)
|
||||
k = k[2:]
|
||||
}
|
||||
k[0] = byte(len(sig) >> 8)
|
||||
k[1] = byte(len(sig))
|
||||
copy(k[2:], sig)
|
||||
|
||||
return skx, nil
|
||||
}
|
||||
|
||||
func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
|
||||
if len(ckx.ciphertext) == 0 || int(ckx.ciphertext[0]) != len(ckx.ciphertext)-1 {
|
||||
return nil, errClientKeyExchange
|
||||
}
|
||||
|
||||
peerKey, err := ka.key.Curve().NewPublicKey(ckx.ciphertext[1:])
|
||||
if err != nil {
|
||||
return nil, errClientKeyExchange
|
||||
}
|
||||
preMasterSecret, err := ka.key.ECDH(peerKey)
|
||||
if err != nil {
|
||||
return nil, errClientKeyExchange
|
||||
}
|
||||
|
||||
return preMasterSecret, nil
|
||||
}
|
||||
|
||||
func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
|
||||
if len(skx.key) < 4 {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
if skx.key[0] != 3 { // named curve
|
||||
return errors.New("tls: server selected unsupported curve")
|
||||
}
|
||||
curveID := CurveID(skx.key[1])<<8 | CurveID(skx.key[2])
|
||||
|
||||
publicLen := int(skx.key[3])
|
||||
if publicLen+4 > len(skx.key) {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
serverECDHEParams := skx.key[:4+publicLen]
|
||||
publicKey := serverECDHEParams[4:]
|
||||
|
||||
sig := skx.key[4+publicLen:]
|
||||
if len(sig) < 2 {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
|
||||
if _, ok := curveForCurveID(curveID); !ok {
|
||||
return errors.New("tls: server selected unsupported curve")
|
||||
}
|
||||
|
||||
key, err := generateECDHEKey(config.rand(), curveID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ka.key = key
|
||||
|
||||
peerKey, err := key.Curve().NewPublicKey(publicKey)
|
||||
if err != nil {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
ka.preMasterSecret, err = key.ECDH(peerKey)
|
||||
if err != nil {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
|
||||
ourPublicKey := key.PublicKey().Bytes()
|
||||
ka.ckx = new(clientKeyExchangeMsg)
|
||||
ka.ckx.ciphertext = make([]byte, 1+len(ourPublicKey))
|
||||
ka.ckx.ciphertext[0] = byte(len(ourPublicKey))
|
||||
copy(ka.ckx.ciphertext[1:], ourPublicKey)
|
||||
|
||||
var sigType uint8
|
||||
var sigHash crypto.Hash
|
||||
if ka.version >= VersionTLS12 {
|
||||
signatureAlgorithm := SignatureScheme(sig[0])<<8 | SignatureScheme(sig[1])
|
||||
sig = sig[2:]
|
||||
if len(sig) < 2 {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
|
||||
if !isSupportedSignatureAlgorithm(signatureAlgorithm, clientHello.supportedSignatureAlgorithms) {
|
||||
return errors.New("tls: certificate used with invalid signature algorithm")
|
||||
}
|
||||
sigType, sigHash, err = typeAndHashFromSignatureScheme(signatureAlgorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
sigType, sigHash, err = legacyTypeAndHashFromPublicKey(cert.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if (sigType == signaturePKCS1v15 || sigType == signatureRSAPSS) != ka.isRSA {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
|
||||
sigLen := int(sig[0])<<8 | int(sig[1])
|
||||
if sigLen+2 != len(sig) {
|
||||
return errServerKeyExchange
|
||||
}
|
||||
sig = sig[2:]
|
||||
|
||||
signed := hashForServerKeyExchange(sigType, sigHash, ka.version, clientHello.random, serverHello.random, serverECDHEParams)
|
||||
if err := verifyHandshakeSignature(sigType, cert.PublicKey, sigHash, signed, sig); err != nil {
|
||||
return errors.New("tls: invalid signature by the server certificate: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ka *ecdheKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
|
||||
if ka.ckx == nil {
|
||||
return nil, nil, errors.New("tls: missing ServerKeyExchange message")
|
||||
}
|
||||
|
||||
return ka.preMasterSecret, ka.ckx, nil
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/hmac"
|
||||
"crypto/mlkem"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"github.com/refraction-networking/utls/internal/tls13"
|
||||
)
|
||||
|
||||
// This file contains the functions necessary to compute the TLS 1.3 key
|
||||
// schedule. See RFC 8446, Section 7.
|
||||
|
||||
// nextTrafficSecret generates the next traffic secret, given the current one,
|
||||
// according to RFC 8446, Section 7.2.
|
||||
func (c *cipherSuiteTLS13) nextTrafficSecret(trafficSecret []byte) []byte {
|
||||
return tls13.ExpandLabel(c.hash.New, trafficSecret, "traffic upd", nil, c.hash.Size())
|
||||
}
|
||||
|
||||
// trafficKey generates traffic keys according to RFC 8446, Section 7.3.
|
||||
func (c *cipherSuiteTLS13) trafficKey(trafficSecret []byte) (key, iv []byte) {
|
||||
key = tls13.ExpandLabel(c.hash.New, trafficSecret, "key", nil, c.keyLen)
|
||||
iv = tls13.ExpandLabel(c.hash.New, trafficSecret, "iv", nil, aeadNonceLength)
|
||||
return
|
||||
}
|
||||
|
||||
// finishedHash generates the Finished verify_data or PskBinderEntry according
|
||||
// to RFC 8446, Section 4.4.4. See sections 4.4 and 4.2.11.2 for the baseKey
|
||||
// selection.
|
||||
func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) []byte {
|
||||
finishedKey := tls13.ExpandLabel(c.hash.New, baseKey, "finished", nil, c.hash.Size())
|
||||
verifyData := hmac.New(c.hash.New, finishedKey)
|
||||
verifyData.Write(transcript.Sum(nil))
|
||||
return verifyData.Sum(nil)
|
||||
}
|
||||
|
||||
// exportKeyingMaterial implements RFC5705 exporters for TLS 1.3 according to
|
||||
// RFC 8446, Section 7.5.
|
||||
func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcript hash.Hash) func(string, []byte, int) ([]byte, error) {
|
||||
expMasterSecret := s.ExporterMasterSecret(transcript)
|
||||
return func(label string, context []byte, length int) ([]byte, error) {
|
||||
return expMasterSecret.Exporter(label, context, length), nil
|
||||
}
|
||||
}
|
||||
|
||||
type keySharePrivateKeys struct {
|
||||
curveID CurveID
|
||||
ecdhe *ecdh.PrivateKey
|
||||
mlkem *mlkem.DecapsulationKey768
|
||||
mlkemEcdhe *ecdh.PrivateKey // [uTLS] seperate ecdhe key for pq keyshare in line with Chrome, instead of reusing ecdhe key like stdlib
|
||||
}
|
||||
|
||||
const x25519PublicKeySize = 32
|
||||
|
||||
// generateECDHEKey returns a PrivateKey that implements Diffie-Hellman
|
||||
// according to RFC 8446, Section 4.2.8.2.
|
||||
func generateECDHEKey(rand io.Reader, curveID CurveID) (*ecdh.PrivateKey, error) {
|
||||
curve, ok := curveForCurveID(curveID)
|
||||
if !ok {
|
||||
return nil, errors.New("tls: internal error: unsupported curve")
|
||||
}
|
||||
|
||||
return curve.GenerateKey(rand)
|
||||
}
|
||||
|
||||
func curveForCurveID(id CurveID) (ecdh.Curve, bool) {
|
||||
switch id {
|
||||
case X25519:
|
||||
return ecdh.X25519(), true
|
||||
case CurveP256:
|
||||
return ecdh.P256(), true
|
||||
case CurveP384:
|
||||
return ecdh.P384(), true
|
||||
case CurveP521:
|
||||
return ecdh.P521(), true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func curveIDForCurve(curve ecdh.Curve) (CurveID, bool) {
|
||||
switch curve {
|
||||
case ecdh.X25519():
|
||||
return X25519, true
|
||||
case ecdh.P256():
|
||||
return CurveP256, true
|
||||
case ecdh.P384():
|
||||
return CurveP384, true
|
||||
case ecdh.P521():
|
||||
return CurveP521, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
+297
@@ -0,0 +1,297 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
"github.com/refraction-networking/utls/internal/tls12"
|
||||
)
|
||||
|
||||
type prfFunc func(secret []byte, label string, seed []byte, keyLen int) []byte
|
||||
|
||||
// Split a premaster secret in two as specified in RFC 4346, Section 5.
|
||||
func splitPreMasterSecret(secret []byte) (s1, s2 []byte) {
|
||||
s1 = secret[0 : (len(secret)+1)/2]
|
||||
s2 = secret[len(secret)/2:]
|
||||
return
|
||||
}
|
||||
|
||||
// pHash implements the P_hash function, as defined in RFC 4346, Section 5.
|
||||
func pHash(result, secret, seed []byte, hash func() hash.Hash) {
|
||||
h := hmac.New(hash, secret)
|
||||
h.Write(seed)
|
||||
a := h.Sum(nil)
|
||||
|
||||
j := 0
|
||||
for j < len(result) {
|
||||
h.Reset()
|
||||
h.Write(a)
|
||||
h.Write(seed)
|
||||
b := h.Sum(nil)
|
||||
copy(result[j:], b)
|
||||
j += len(b)
|
||||
|
||||
h.Reset()
|
||||
h.Write(a)
|
||||
a = h.Sum(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// prf10 implements the TLS 1.0 pseudo-random function, as defined in RFC 2246, Section 5.
|
||||
func prf10(secret []byte, label string, seed []byte, keyLen int) []byte {
|
||||
result := make([]byte, keyLen)
|
||||
hashSHA1 := sha1.New
|
||||
hashMD5 := md5.New
|
||||
|
||||
labelAndSeed := make([]byte, len(label)+len(seed))
|
||||
copy(labelAndSeed, label)
|
||||
copy(labelAndSeed[len(label):], seed)
|
||||
|
||||
s1, s2 := splitPreMasterSecret(secret)
|
||||
pHash(result, s1, labelAndSeed, hashMD5)
|
||||
result2 := make([]byte, len(result))
|
||||
pHash(result2, s2, labelAndSeed, hashSHA1)
|
||||
|
||||
for i, b := range result2 {
|
||||
result[i] ^= b
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// prf12 implements the TLS 1.2 pseudo-random function, as defined in RFC 5246, Section 5.
|
||||
func prf12(hashFunc func() hash.Hash) prfFunc {
|
||||
return func(secret []byte, label string, seed []byte, keyLen int) []byte {
|
||||
return tls12.PRF(hashFunc, secret, label, seed, keyLen)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
masterSecretLength = 48 // Length of a master secret in TLS 1.1.
|
||||
finishedVerifyLength = 12 // Length of verify_data in a Finished message.
|
||||
)
|
||||
|
||||
const masterSecretLabel = "master secret"
|
||||
const extendedMasterSecretLabel = "extended master secret"
|
||||
const keyExpansionLabel = "key expansion"
|
||||
const clientFinishedLabel = "client finished"
|
||||
const serverFinishedLabel = "server finished"
|
||||
|
||||
func prfAndHashForVersion(version uint16, suite *cipherSuite) (prfFunc, crypto.Hash) {
|
||||
switch version {
|
||||
case VersionTLS10, VersionTLS11:
|
||||
return prf10, crypto.Hash(0)
|
||||
case VersionTLS12:
|
||||
if suite.flags&suiteSHA384 != 0 {
|
||||
return prf12(sha512.New384), crypto.SHA384
|
||||
}
|
||||
return prf12(sha256.New), crypto.SHA256
|
||||
default:
|
||||
panic("unknown version")
|
||||
}
|
||||
}
|
||||
|
||||
func prfForVersion(version uint16, suite *cipherSuite) prfFunc {
|
||||
prf, _ := prfAndHashForVersion(version, suite)
|
||||
return prf
|
||||
}
|
||||
|
||||
// masterFromPreMasterSecret generates the master secret from the pre-master
|
||||
// secret. See RFC 5246, Section 8.1.
|
||||
func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, clientRandom, serverRandom []byte) []byte {
|
||||
seed := make([]byte, 0, len(clientRandom)+len(serverRandom))
|
||||
seed = append(seed, clientRandom...)
|
||||
seed = append(seed, serverRandom...)
|
||||
|
||||
return prfForVersion(version, suite)(preMasterSecret, masterSecretLabel, seed, masterSecretLength)
|
||||
}
|
||||
|
||||
// extMasterFromPreMasterSecret generates the extended master secret from the
|
||||
// pre-master secret. See RFC 7627.
|
||||
func extMasterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, transcript []byte) []byte {
|
||||
prf, hash := prfAndHashForVersion(version, suite)
|
||||
if version == VersionTLS12 {
|
||||
// Use the FIPS 140-3 module only for TLS 1.2 with EMS, which is the
|
||||
// only TLS 1.0-1.2 approved mode per IG D.Q.
|
||||
return tls12.MasterSecret(hash.New, preMasterSecret, transcript)
|
||||
}
|
||||
return prf(preMasterSecret, extendedMasterSecretLabel, transcript, masterSecretLength)
|
||||
}
|
||||
|
||||
// keysFromMasterSecret generates the connection keys from the master
|
||||
// secret, given the lengths of the MAC key, cipher key and IV, as defined in
|
||||
// RFC 2246, Section 6.3.
|
||||
func keysFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) {
|
||||
seed := make([]byte, 0, len(serverRandom)+len(clientRandom))
|
||||
seed = append(seed, serverRandom...)
|
||||
seed = append(seed, clientRandom...)
|
||||
|
||||
n := 2*macLen + 2*keyLen + 2*ivLen
|
||||
keyMaterial := prfForVersion(version, suite)(masterSecret, keyExpansionLabel, seed, n)
|
||||
clientMAC = keyMaterial[:macLen]
|
||||
keyMaterial = keyMaterial[macLen:]
|
||||
serverMAC = keyMaterial[:macLen]
|
||||
keyMaterial = keyMaterial[macLen:]
|
||||
clientKey = keyMaterial[:keyLen]
|
||||
keyMaterial = keyMaterial[keyLen:]
|
||||
serverKey = keyMaterial[:keyLen]
|
||||
keyMaterial = keyMaterial[keyLen:]
|
||||
clientIV = keyMaterial[:ivLen]
|
||||
keyMaterial = keyMaterial[ivLen:]
|
||||
serverIV = keyMaterial[:ivLen]
|
||||
return
|
||||
}
|
||||
|
||||
func newFinishedHash(version uint16, cipherSuite *cipherSuite) finishedHash {
|
||||
var buffer []byte
|
||||
if version >= VersionTLS12 {
|
||||
buffer = []byte{}
|
||||
}
|
||||
|
||||
prf, hash := prfAndHashForVersion(version, cipherSuite)
|
||||
if hash != 0 {
|
||||
return finishedHash{hash.New(), hash.New(), nil, nil, buffer, version, prf}
|
||||
}
|
||||
|
||||
return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), buffer, version, prf}
|
||||
}
|
||||
|
||||
// A finishedHash calculates the hash of a set of handshake messages suitable
|
||||
// for including in a Finished message.
|
||||
type finishedHash struct {
|
||||
client hash.Hash
|
||||
server hash.Hash
|
||||
|
||||
// Prior to TLS 1.2, an additional MD5 hash is required.
|
||||
clientMD5 hash.Hash
|
||||
serverMD5 hash.Hash
|
||||
|
||||
// In TLS 1.2, a full buffer is sadly required.
|
||||
buffer []byte
|
||||
|
||||
version uint16
|
||||
prf prfFunc
|
||||
}
|
||||
|
||||
func (h *finishedHash) Write(msg []byte) (n int, err error) {
|
||||
h.client.Write(msg)
|
||||
h.server.Write(msg)
|
||||
|
||||
if h.version < VersionTLS12 {
|
||||
h.clientMD5.Write(msg)
|
||||
h.serverMD5.Write(msg)
|
||||
}
|
||||
|
||||
if h.buffer != nil {
|
||||
h.buffer = append(h.buffer, msg...)
|
||||
}
|
||||
|
||||
return len(msg), nil
|
||||
}
|
||||
|
||||
func (h finishedHash) Sum() []byte {
|
||||
if h.version >= VersionTLS12 {
|
||||
return h.client.Sum(nil)
|
||||
}
|
||||
|
||||
out := make([]byte, 0, md5.Size+sha1.Size)
|
||||
out = h.clientMD5.Sum(out)
|
||||
return h.client.Sum(out)
|
||||
}
|
||||
|
||||
// clientSum returns the contents of the verify_data member of a client's
|
||||
// Finished message.
|
||||
func (h finishedHash) clientSum(masterSecret []byte) []byte {
|
||||
return h.prf(masterSecret, clientFinishedLabel, h.Sum(), finishedVerifyLength)
|
||||
}
|
||||
|
||||
// serverSum returns the contents of the verify_data member of a server's
|
||||
// Finished message.
|
||||
func (h finishedHash) serverSum(masterSecret []byte) []byte {
|
||||
return h.prf(masterSecret, serverFinishedLabel, h.Sum(), finishedVerifyLength)
|
||||
}
|
||||
|
||||
// hashForClientCertificate returns the handshake messages so far, pre-hashed if
|
||||
// necessary, suitable for signing by a TLS client certificate.
|
||||
func (h finishedHash) hashForClientCertificate(sigType uint8, hashAlg crypto.Hash) []byte {
|
||||
if (h.version >= VersionTLS12 || sigType == signatureEd25519) && h.buffer == nil {
|
||||
panic("tls: handshake hash for a client certificate requested after discarding the handshake buffer")
|
||||
}
|
||||
|
||||
if sigType == signatureEd25519 {
|
||||
return h.buffer
|
||||
}
|
||||
|
||||
if h.version >= VersionTLS12 {
|
||||
hash := hashAlg.New()
|
||||
hash.Write(h.buffer)
|
||||
return hash.Sum(nil)
|
||||
}
|
||||
|
||||
if sigType == signatureECDSA {
|
||||
return h.server.Sum(nil)
|
||||
}
|
||||
|
||||
return h.Sum()
|
||||
}
|
||||
|
||||
// discardHandshakeBuffer is called when there is no more need to
|
||||
// buffer the entirety of the handshake messages.
|
||||
func (h *finishedHash) discardHandshakeBuffer() {
|
||||
h.buffer = nil
|
||||
}
|
||||
|
||||
// noEKMBecauseRenegotiation is used as a value of
|
||||
// ConnectionState.ekm when renegotiation is enabled and thus
|
||||
// we wish to fail all key-material export requests.
|
||||
func noEKMBecauseRenegotiation(label string, context []byte, length int) ([]byte, error) {
|
||||
return nil, errors.New("crypto/tls: ExportKeyingMaterial is unavailable when renegotiation is enabled")
|
||||
}
|
||||
|
||||
// noEKMBecauseNoEMS is used as a value of ConnectionState.ekm when Extended
|
||||
// Master Secret is not negotiated and thus we wish to fail all key-material
|
||||
// export requests.
|
||||
func noEKMBecauseNoEMS(label string, context []byte, length int) ([]byte, error) {
|
||||
return nil, errors.New("crypto/tls: ExportKeyingMaterial is unavailable when neither TLS 1.3 nor Extended Master Secret are negotiated; override with GODEBUG=tlsunsafeekm=1")
|
||||
}
|
||||
|
||||
// ekmFromMasterSecret generates exported keying material as defined in RFC 5705.
|
||||
func ekmFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clientRandom, serverRandom []byte) func(string, []byte, int) ([]byte, error) {
|
||||
return func(label string, context []byte, length int) ([]byte, error) {
|
||||
switch label {
|
||||
case "client finished", "server finished", "master secret", "key expansion":
|
||||
// These values are reserved and may not be used.
|
||||
return nil, fmt.Errorf("crypto/tls: reserved ExportKeyingMaterial label: %s", label)
|
||||
}
|
||||
|
||||
seedLen := len(serverRandom) + len(clientRandom)
|
||||
if context != nil {
|
||||
seedLen += 2 + len(context)
|
||||
}
|
||||
seed := make([]byte, 0, seedLen)
|
||||
|
||||
seed = append(seed, clientRandom...)
|
||||
seed = append(seed, serverRandom...)
|
||||
|
||||
if context != nil {
|
||||
if len(context) >= 1<<16 {
|
||||
return nil, fmt.Errorf("crypto/tls: ExportKeyingMaterial context too long")
|
||||
}
|
||||
seed = append(seed, byte(len(context)>>8), byte(len(context)))
|
||||
seed = append(seed, context...)
|
||||
}
|
||||
|
||||
return prfForVersion(version, suite)(masterSecret, label, seed, length), nil
|
||||
}
|
||||
}
|
||||
+500
@@ -0,0 +1,500 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// QUICEncryptionLevel represents a QUIC encryption level used to transmit
|
||||
// handshake messages.
|
||||
type QUICEncryptionLevel int
|
||||
|
||||
const (
|
||||
QUICEncryptionLevelInitial = QUICEncryptionLevel(iota)
|
||||
QUICEncryptionLevelEarly
|
||||
QUICEncryptionLevelHandshake
|
||||
QUICEncryptionLevelApplication
|
||||
)
|
||||
|
||||
func (l QUICEncryptionLevel) String() string {
|
||||
switch l {
|
||||
case QUICEncryptionLevelInitial:
|
||||
return "Initial"
|
||||
case QUICEncryptionLevelEarly:
|
||||
return "Early"
|
||||
case QUICEncryptionLevelHandshake:
|
||||
return "Handshake"
|
||||
case QUICEncryptionLevelApplication:
|
||||
return "Application"
|
||||
default:
|
||||
return fmt.Sprintf("QUICEncryptionLevel(%v)", int(l))
|
||||
}
|
||||
}
|
||||
|
||||
// A QUICConn represents a connection which uses a QUIC implementation as the underlying
|
||||
// transport as described in RFC 9001.
|
||||
//
|
||||
// Methods of QUICConn are not safe for concurrent use.
|
||||
type QUICConn struct {
|
||||
conn *Conn
|
||||
|
||||
sessionTicketSent bool
|
||||
}
|
||||
|
||||
// A QUICConfig configures a [QUICConn].
|
||||
type QUICConfig struct {
|
||||
TLSConfig *Config
|
||||
|
||||
// EnableSessionEvents may be set to true to enable the
|
||||
// [QUICStoreSession] and [QUICResumeSession] events for client connections.
|
||||
// When this event is enabled, sessions are not automatically
|
||||
// stored in the client session cache.
|
||||
// The application should use [QUICConn.StoreSession] to store sessions.
|
||||
EnableSessionEvents bool
|
||||
}
|
||||
|
||||
// A QUICEventKind is a type of operation on a QUIC connection.
|
||||
type QUICEventKind int
|
||||
|
||||
const (
|
||||
// QUICNoEvent indicates that there are no events available.
|
||||
QUICNoEvent QUICEventKind = iota
|
||||
|
||||
// QUICSetReadSecret and QUICSetWriteSecret provide the read and write
|
||||
// secrets for a given encryption level.
|
||||
// QUICEvent.Level, QUICEvent.Data, and QUICEvent.Suite are set.
|
||||
//
|
||||
// Secrets for the Initial encryption level are derived from the initial
|
||||
// destination connection ID, and are not provided by the QUICConn.
|
||||
QUICSetReadSecret
|
||||
QUICSetWriteSecret
|
||||
|
||||
// QUICWriteData provides data to send to the peer in CRYPTO frames.
|
||||
// QUICEvent.Data is set.
|
||||
QUICWriteData
|
||||
|
||||
// QUICTransportParameters provides the peer's QUIC transport parameters.
|
||||
// QUICEvent.Data is set.
|
||||
QUICTransportParameters
|
||||
|
||||
// QUICTransportParametersRequired indicates that the caller must provide
|
||||
// QUIC transport parameters to send to the peer. The caller should set
|
||||
// the transport parameters with QUICConn.SetTransportParameters and call
|
||||
// QUICConn.NextEvent again.
|
||||
//
|
||||
// If transport parameters are set before calling QUICConn.Start, the
|
||||
// connection will never generate a QUICTransportParametersRequired event.
|
||||
QUICTransportParametersRequired
|
||||
|
||||
// QUICRejectedEarlyData indicates that the server rejected 0-RTT data even
|
||||
// if we offered it. It's returned before QUICEncryptionLevelApplication
|
||||
// keys are returned.
|
||||
// This event only occurs on client connections.
|
||||
QUICRejectedEarlyData
|
||||
|
||||
// QUICHandshakeDone indicates that the TLS handshake has completed.
|
||||
QUICHandshakeDone
|
||||
|
||||
// QUICResumeSession indicates that a client is attempting to resume a previous session.
|
||||
// [QUICEvent.SessionState] is set.
|
||||
//
|
||||
// For client connections, this event occurs when the session ticket is selected.
|
||||
// For server connections, this event occurs when receiving the client's session ticket.
|
||||
//
|
||||
// The application may set [QUICEvent.SessionState.EarlyData] to false before the
|
||||
// next call to [QUICConn.NextEvent] to decline 0-RTT even if the session supports it.
|
||||
QUICResumeSession
|
||||
|
||||
// QUICStoreSession indicates that the server has provided state permitting
|
||||
// the client to resume the session.
|
||||
// [QUICEvent.SessionState] is set.
|
||||
// The application should use [QUICConn.StoreSession] session to store the [SessionState].
|
||||
// The application may modify the [SessionState] before storing it.
|
||||
// This event only occurs on client connections.
|
||||
QUICStoreSession
|
||||
)
|
||||
|
||||
// A QUICEvent is an event occurring on a QUIC connection.
|
||||
//
|
||||
// The type of event is specified by the Kind field.
|
||||
// The contents of the other fields are kind-specific.
|
||||
type QUICEvent struct {
|
||||
Kind QUICEventKind
|
||||
|
||||
// Set for QUICSetReadSecret, QUICSetWriteSecret, and QUICWriteData.
|
||||
Level QUICEncryptionLevel
|
||||
|
||||
// Set for QUICTransportParameters, QUICSetReadSecret, QUICSetWriteSecret, and QUICWriteData.
|
||||
// The contents are owned by crypto/tls, and are valid until the next NextEvent call.
|
||||
Data []byte
|
||||
|
||||
// Set for QUICSetReadSecret and QUICSetWriteSecret.
|
||||
Suite uint16
|
||||
|
||||
// Set for QUICResumeSession and QUICStoreSession.
|
||||
SessionState *SessionState
|
||||
}
|
||||
|
||||
type quicState struct {
|
||||
events []QUICEvent
|
||||
nextEvent int
|
||||
|
||||
// eventArr is a statically allocated event array, large enough to handle
|
||||
// the usual maximum number of events resulting from a single call: transport
|
||||
// parameters, Initial data, Early read secret, Handshake write and read
|
||||
// secrets, Handshake data, Application write secret, Application data.
|
||||
eventArr [8]QUICEvent
|
||||
|
||||
started bool
|
||||
signalc chan struct{} // handshake data is available to be read
|
||||
blockedc chan struct{} // handshake is waiting for data, closed when done
|
||||
cancelc <-chan struct{} // handshake has been canceled
|
||||
cancel context.CancelFunc
|
||||
|
||||
waitingForDrain bool
|
||||
|
||||
// readbuf is shared between HandleData and the handshake goroutine.
|
||||
// HandshakeCryptoData passes ownership to the handshake goroutine by
|
||||
// reading from signalc, and reclaims ownership by reading from blockedc.
|
||||
readbuf []byte
|
||||
|
||||
transportParams []byte // to send to the peer
|
||||
|
||||
enableSessionEvents bool
|
||||
}
|
||||
|
||||
// QUICClient returns a new TLS client side connection using QUICTransport as the
|
||||
// underlying transport. The config cannot be nil.
|
||||
//
|
||||
// The config's MinVersion must be at least TLS 1.3.
|
||||
func QUICClient(config *QUICConfig) *QUICConn {
|
||||
return newQUICConn(Client(nil, config.TLSConfig), config)
|
||||
}
|
||||
|
||||
// QUICServer returns a new TLS server side connection using QUICTransport as the
|
||||
// underlying transport. The config cannot be nil.
|
||||
//
|
||||
// The config's MinVersion must be at least TLS 1.3.
|
||||
func QUICServer(config *QUICConfig) *QUICConn {
|
||||
return newQUICConn(Server(nil, config.TLSConfig), config)
|
||||
}
|
||||
|
||||
func newQUICConn(conn *Conn, config *QUICConfig) *QUICConn {
|
||||
conn.quic = &quicState{
|
||||
signalc: make(chan struct{}),
|
||||
blockedc: make(chan struct{}),
|
||||
enableSessionEvents: config.EnableSessionEvents,
|
||||
}
|
||||
conn.quic.events = conn.quic.eventArr[:0]
|
||||
return &QUICConn{
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the client or server handshake protocol.
|
||||
// It may produce connection events, which may be read with [QUICConn.NextEvent].
|
||||
//
|
||||
// Start must be called at most once.
|
||||
func (q *QUICConn) Start(ctx context.Context) error {
|
||||
if q.conn.quic.started {
|
||||
return quicError(errors.New("tls: Start called more than once"))
|
||||
}
|
||||
q.conn.quic.started = true
|
||||
if q.conn.config.MinVersion < VersionTLS13 {
|
||||
return quicError(errors.New("tls: Config MinVersion must be at least TLS 1.3"))
|
||||
}
|
||||
go q.conn.HandshakeContext(ctx)
|
||||
if _, ok := <-q.conn.quic.blockedc; !ok {
|
||||
return q.conn.handshakeErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextEvent returns the next event occurring on the connection.
|
||||
// It returns an event with a Kind of [QUICNoEvent] when no events are available.
|
||||
func (q *QUICConn) NextEvent() QUICEvent {
|
||||
qs := q.conn.quic
|
||||
if last := qs.nextEvent - 1; last >= 0 && len(qs.events[last].Data) > 0 {
|
||||
// Write over some of the previous event's data,
|
||||
// to catch callers erroniously retaining it.
|
||||
qs.events[last].Data[0] = 0
|
||||
}
|
||||
if qs.nextEvent >= len(qs.events) && qs.waitingForDrain {
|
||||
qs.waitingForDrain = false
|
||||
<-qs.signalc
|
||||
<-qs.blockedc
|
||||
}
|
||||
if qs.nextEvent >= len(qs.events) {
|
||||
qs.events = qs.events[:0]
|
||||
qs.nextEvent = 0
|
||||
return QUICEvent{Kind: QUICNoEvent}
|
||||
}
|
||||
e := qs.events[qs.nextEvent]
|
||||
qs.events[qs.nextEvent] = QUICEvent{} // zero out references to data
|
||||
qs.nextEvent++
|
||||
return e
|
||||
}
|
||||
|
||||
// Close closes the connection and stops any in-progress handshake.
|
||||
func (q *QUICConn) Close() error {
|
||||
if q.conn.quic.cancel == nil {
|
||||
return nil // never started
|
||||
}
|
||||
q.conn.quic.cancel()
|
||||
for range q.conn.quic.blockedc {
|
||||
// Wait for the handshake goroutine to return.
|
||||
}
|
||||
return q.conn.handshakeErr
|
||||
}
|
||||
|
||||
// HandleData handles handshake bytes received from the peer.
|
||||
// It may produce connection events, which may be read with [QUICConn.NextEvent].
|
||||
func (q *QUICConn) HandleData(level QUICEncryptionLevel, data []byte) error {
|
||||
c := q.conn
|
||||
if c.in.level != level {
|
||||
return quicError(c.in.setErrorLocked(errors.New("tls: handshake data received at wrong level")))
|
||||
}
|
||||
c.quic.readbuf = data
|
||||
<-c.quic.signalc
|
||||
_, ok := <-c.quic.blockedc
|
||||
if ok {
|
||||
// The handshake goroutine is waiting for more data.
|
||||
return nil
|
||||
}
|
||||
// The handshake goroutine has exited.
|
||||
c.handshakeMutex.Lock()
|
||||
defer c.handshakeMutex.Unlock()
|
||||
c.hand.Write(c.quic.readbuf)
|
||||
c.quic.readbuf = nil
|
||||
for q.conn.hand.Len() >= 4 && q.conn.handshakeErr == nil {
|
||||
b := q.conn.hand.Bytes()
|
||||
n := int(b[1])<<16 | int(b[2])<<8 | int(b[3])
|
||||
if n > maxHandshake {
|
||||
q.conn.handshakeErr = fmt.Errorf("tls: handshake message of length %d bytes exceeds maximum of %d bytes", n, maxHandshake)
|
||||
break
|
||||
}
|
||||
if len(b) < 4+n {
|
||||
return nil
|
||||
}
|
||||
if err := q.conn.handlePostHandshakeMessage(); err != nil {
|
||||
q.conn.handshakeErr = err
|
||||
}
|
||||
}
|
||||
if q.conn.handshakeErr != nil {
|
||||
return quicError(q.conn.handshakeErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type QUICSessionTicketOptions struct {
|
||||
// EarlyData specifies whether the ticket may be used for 0-RTT.
|
||||
EarlyData bool
|
||||
Extra [][]byte
|
||||
}
|
||||
|
||||
// SendSessionTicket sends a session ticket to the client.
|
||||
// It produces connection events, which may be read with [QUICConn.NextEvent].
|
||||
// Currently, it can only be called once.
|
||||
func (q *QUICConn) SendSessionTicket(opts QUICSessionTicketOptions) error {
|
||||
c := q.conn
|
||||
if !c.isHandshakeComplete.Load() {
|
||||
return quicError(errors.New("tls: SendSessionTicket called before handshake completed"))
|
||||
}
|
||||
if c.isClient {
|
||||
return quicError(errors.New("tls: SendSessionTicket called on the client"))
|
||||
}
|
||||
if q.sessionTicketSent {
|
||||
return quicError(errors.New("tls: SendSessionTicket called multiple times"))
|
||||
}
|
||||
q.sessionTicketSent = true
|
||||
return quicError(c.sendSessionTicket(opts.EarlyData, opts.Extra))
|
||||
}
|
||||
|
||||
// StoreSession stores a session previously received in a QUICStoreSession event
|
||||
// in the ClientSessionCache.
|
||||
// The application may process additional events or modify the SessionState
|
||||
// before storing the session.
|
||||
func (q *QUICConn) StoreSession(session *SessionState) error {
|
||||
c := q.conn
|
||||
if !c.isClient {
|
||||
return quicError(errors.New("tls: StoreSessionTicket called on the server"))
|
||||
}
|
||||
cacheKey := c.clientSessionCacheKey()
|
||||
if cacheKey == "" {
|
||||
return nil
|
||||
}
|
||||
cs := &ClientSessionState{session: session}
|
||||
c.config.ClientSessionCache.Put(cacheKey, cs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConnectionState returns basic TLS details about the connection.
|
||||
func (q *QUICConn) ConnectionState() ConnectionState {
|
||||
return q.conn.ConnectionState()
|
||||
}
|
||||
|
||||
// SetTransportParameters sets the transport parameters to send to the peer.
|
||||
//
|
||||
// Server connections may delay setting the transport parameters until after
|
||||
// receiving the client's transport parameters. See [QUICTransportParametersRequired].
|
||||
func (q *QUICConn) SetTransportParameters(params []byte) {
|
||||
if params == nil {
|
||||
params = []byte{}
|
||||
}
|
||||
q.conn.quic.transportParams = params
|
||||
if q.conn.quic.started {
|
||||
<-q.conn.quic.signalc
|
||||
<-q.conn.quic.blockedc
|
||||
}
|
||||
}
|
||||
|
||||
// quicError ensures err is an AlertError.
|
||||
// If err is not already, quicError wraps it with alertInternalError.
|
||||
func quicError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
var ae AlertError
|
||||
if errors.As(err, &ae) {
|
||||
return err
|
||||
}
|
||||
var a alert
|
||||
if !errors.As(err, &a) {
|
||||
a = alertInternalError
|
||||
}
|
||||
// Return an error wrapping the original error and an AlertError.
|
||||
// Truncate the text of the alert to 0 characters.
|
||||
return fmt.Errorf("%w%.0w", err, AlertError(a))
|
||||
}
|
||||
|
||||
func (c *Conn) quicReadHandshakeBytes(n int) error {
|
||||
for c.hand.Len() < n {
|
||||
if err := c.quicWaitForSignal(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) quicSetReadSecret(level QUICEncryptionLevel, suite uint16, secret []byte) {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICSetReadSecret,
|
||||
Level: level,
|
||||
Suite: suite,
|
||||
Data: secret,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conn) quicSetWriteSecret(level QUICEncryptionLevel, suite uint16, secret []byte) {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICSetWriteSecret,
|
||||
Level: level,
|
||||
Suite: suite,
|
||||
Data: secret,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conn) quicWriteCryptoData(level QUICEncryptionLevel, data []byte) {
|
||||
var last *QUICEvent
|
||||
if len(c.quic.events) > 0 {
|
||||
last = &c.quic.events[len(c.quic.events)-1]
|
||||
}
|
||||
if last == nil || last.Kind != QUICWriteData || last.Level != level {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICWriteData,
|
||||
Level: level,
|
||||
})
|
||||
last = &c.quic.events[len(c.quic.events)-1]
|
||||
}
|
||||
last.Data = append(last.Data, data...)
|
||||
}
|
||||
|
||||
func (c *Conn) quicResumeSession(session *SessionState) error {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICResumeSession,
|
||||
SessionState: session,
|
||||
})
|
||||
c.quic.waitingForDrain = true
|
||||
for c.quic.waitingForDrain {
|
||||
if err := c.quicWaitForSignal(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) quicStoreSession(session *SessionState) {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICStoreSession,
|
||||
SessionState: session,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conn) quicSetTransportParameters(params []byte) {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICTransportParameters,
|
||||
Data: params,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conn) quicGetTransportParameters() ([]byte, error) {
|
||||
if c.quic.transportParams == nil {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICTransportParametersRequired,
|
||||
})
|
||||
}
|
||||
for c.quic.transportParams == nil {
|
||||
if err := c.quicWaitForSignal(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.quic.transportParams, nil
|
||||
}
|
||||
|
||||
func (c *Conn) quicHandshakeComplete() {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICHandshakeDone,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Conn) quicRejectedEarlyData() {
|
||||
c.quic.events = append(c.quic.events, QUICEvent{
|
||||
Kind: QUICRejectedEarlyData,
|
||||
})
|
||||
}
|
||||
|
||||
// quicWaitForSignal notifies the QUICConn that handshake progress is blocked,
|
||||
// and waits for a signal that the handshake should proceed.
|
||||
//
|
||||
// The handshake may become blocked waiting for handshake bytes
|
||||
// or for the user to provide transport parameters.
|
||||
func (c *Conn) quicWaitForSignal() error {
|
||||
// Drop the handshake mutex while blocked to allow the user
|
||||
// to call ConnectionState before the handshake completes.
|
||||
c.handshakeMutex.Unlock()
|
||||
defer c.handshakeMutex.Lock()
|
||||
// Send on blockedc to notify the QUICConn that the handshake is blocked.
|
||||
// Exported methods of QUICConn wait for the handshake to become blocked
|
||||
// before returning to the user.
|
||||
select {
|
||||
case c.quic.blockedc <- struct{}{}:
|
||||
case <-c.quic.cancelc:
|
||||
return c.sendAlertLocked(alertCloseNotify)
|
||||
}
|
||||
// The QUICConn reads from signalc to notify us that the handshake may
|
||||
// be able to proceed. (The QUICConn reads, because we close signalc to
|
||||
// indicate that the handshake has completed.)
|
||||
select {
|
||||
case c.quic.signalc <- struct{}{}:
|
||||
c.hand.Write(c.quic.readbuf)
|
||||
c.quic.readbuf = nil
|
||||
case <-c.quic.cancelc:
|
||||
return c.sendAlertLocked(alertCloseNotify)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+441
@@ -0,0 +1,441 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
// A SessionState is a resumable session.
|
||||
type SessionState struct {
|
||||
// Encoded as a SessionState (in the language of RFC 8446, Section 3).
|
||||
//
|
||||
// enum { server(1), client(2) } SessionStateType;
|
||||
//
|
||||
// opaque Certificate<1..2^24-1>;
|
||||
//
|
||||
// Certificate CertificateChain<0..2^24-1>;
|
||||
//
|
||||
// opaque Extra<0..2^24-1>;
|
||||
//
|
||||
// struct {
|
||||
// uint16 version;
|
||||
// SessionStateType type;
|
||||
// uint16 cipher_suite;
|
||||
// uint64 created_at;
|
||||
// opaque secret<1..2^8-1>;
|
||||
// Extra extra<0..2^24-1>;
|
||||
// uint8 ext_master_secret = { 0, 1 };
|
||||
// uint8 early_data = { 0, 1 };
|
||||
// CertificateEntry certificate_list<0..2^24-1>;
|
||||
// CertificateChain verified_chains<0..2^24-1>; /* excluding leaf */
|
||||
// select (SessionState.early_data) {
|
||||
// case 0: Empty;
|
||||
// case 1: opaque alpn<1..2^8-1>;
|
||||
// };
|
||||
// select (SessionState.type) {
|
||||
// case server: Empty;
|
||||
// case client: struct {
|
||||
// select (SessionState.version) {
|
||||
// case VersionTLS10..VersionTLS12: Empty;
|
||||
// case VersionTLS13: struct {
|
||||
// uint64 use_by;
|
||||
// uint32 age_add;
|
||||
// };
|
||||
// };
|
||||
// };
|
||||
// };
|
||||
// } SessionState;
|
||||
//
|
||||
|
||||
// Extra is ignored by crypto/tls, but is encoded by [SessionState.Bytes]
|
||||
// and parsed by [ParseSessionState].
|
||||
//
|
||||
// This allows [Config.UnwrapSession]/[Config.WrapSession] and
|
||||
// [ClientSessionCache] implementations to store and retrieve additional
|
||||
// data alongside this session.
|
||||
//
|
||||
// To allow different layers in a protocol stack to share this field,
|
||||
// applications must only append to it, not replace it, and must use entries
|
||||
// that can be recognized even if out of order (for example, by starting
|
||||
// with an id and version prefix).
|
||||
Extra [][]byte
|
||||
|
||||
// EarlyData indicates whether the ticket can be used for 0-RTT in a QUIC
|
||||
// connection. The application may set this to false if it is true to
|
||||
// decline to offer 0-RTT even if supported.
|
||||
EarlyData bool
|
||||
|
||||
version uint16
|
||||
isClient bool
|
||||
cipherSuite uint16
|
||||
// createdAt is the generation time of the secret on the sever (which for
|
||||
// TLS 1.0–1.2 might be earlier than the current session) and the time at
|
||||
// which the ticket was received on the client.
|
||||
createdAt uint64 // seconds since UNIX epoch
|
||||
secret []byte // master secret for TLS 1.2, or the PSK for TLS 1.3
|
||||
extMasterSecret bool
|
||||
peerCertificates []*x509.Certificate
|
||||
activeCertHandles []*activeCert
|
||||
ocspResponse []byte
|
||||
scts [][]byte
|
||||
verifiedChains [][]*x509.Certificate
|
||||
alpnProtocol string // only set if EarlyData is true
|
||||
|
||||
// Client-side TLS 1.3-only fields.
|
||||
useBy uint64 // seconds since UNIX epoch
|
||||
ageAdd uint32
|
||||
ticket []byte
|
||||
}
|
||||
|
||||
// Bytes encodes the session, including any private fields, so that it can be
|
||||
// parsed by [ParseSessionState]. The encoding contains secret values critical
|
||||
// to the security of future and possibly past sessions.
|
||||
//
|
||||
// The specific encoding should be considered opaque and may change incompatibly
|
||||
// between Go versions.
|
||||
func (s *SessionState) Bytes() ([]byte, error) {
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint16(s.version)
|
||||
if s.isClient {
|
||||
b.AddUint8(2) // client
|
||||
} else {
|
||||
b.AddUint8(1) // server
|
||||
}
|
||||
b.AddUint16(s.cipherSuite)
|
||||
addUint64(&b, s.createdAt)
|
||||
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(s.secret)
|
||||
})
|
||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
for _, extra := range s.Extra {
|
||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(extra)
|
||||
})
|
||||
}
|
||||
})
|
||||
if s.extMasterSecret {
|
||||
b.AddUint8(1)
|
||||
} else {
|
||||
b.AddUint8(0)
|
||||
}
|
||||
if s.EarlyData {
|
||||
b.AddUint8(1)
|
||||
} else {
|
||||
b.AddUint8(0)
|
||||
}
|
||||
marshalCertificate(&b, Certificate{
|
||||
Certificate: certificatesToBytesSlice(s.peerCertificates),
|
||||
OCSPStaple: s.ocspResponse,
|
||||
SignedCertificateTimestamps: s.scts,
|
||||
})
|
||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
for _, chain := range s.verifiedChains {
|
||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
// We elide the first certificate because it's always the leaf.
|
||||
if len(chain) == 0 {
|
||||
b.SetError(errors.New("tls: internal error: empty verified chain"))
|
||||
return
|
||||
}
|
||||
for _, cert := range chain[1:] {
|
||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(cert.Raw)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
if s.EarlyData {
|
||||
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes([]byte(s.alpnProtocol))
|
||||
})
|
||||
}
|
||||
if s.isClient {
|
||||
if s.version >= VersionTLS13 {
|
||||
addUint64(&b, s.useBy)
|
||||
b.AddUint32(s.ageAdd)
|
||||
}
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func certificatesToBytesSlice(certs []*x509.Certificate) [][]byte {
|
||||
s := make([][]byte, 0, len(certs))
|
||||
for _, c := range certs {
|
||||
s = append(s, c.Raw)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ParseSessionState parses a [SessionState] encoded by [SessionState.Bytes].
|
||||
func ParseSessionState(data []byte) (*SessionState, error) {
|
||||
ss := &SessionState{}
|
||||
s := cryptobyte.String(data)
|
||||
var typ, extMasterSecret, earlyData uint8
|
||||
var cert Certificate
|
||||
var extra cryptobyte.String
|
||||
if !s.ReadUint16(&ss.version) ||
|
||||
!s.ReadUint8(&typ) ||
|
||||
(typ != 1 && typ != 2) ||
|
||||
!s.ReadUint16(&ss.cipherSuite) ||
|
||||
!readUint64(&s, &ss.createdAt) ||
|
||||
!readUint8LengthPrefixed(&s, &ss.secret) ||
|
||||
!s.ReadUint24LengthPrefixed(&extra) ||
|
||||
!s.ReadUint8(&extMasterSecret) ||
|
||||
!s.ReadUint8(&earlyData) ||
|
||||
len(ss.secret) == 0 ||
|
||||
!unmarshalCertificate(&s, &cert) {
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
for !extra.Empty() {
|
||||
var e []byte
|
||||
if !readUint24LengthPrefixed(&extra, &e) {
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
ss.Extra = append(ss.Extra, e)
|
||||
}
|
||||
switch extMasterSecret {
|
||||
case 0:
|
||||
ss.extMasterSecret = false
|
||||
case 1:
|
||||
ss.extMasterSecret = true
|
||||
default:
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
switch earlyData {
|
||||
case 0:
|
||||
ss.EarlyData = false
|
||||
case 1:
|
||||
ss.EarlyData = true
|
||||
default:
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
for _, cert := range cert.Certificate {
|
||||
c, err := globalCertCache.newCert(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ss.activeCertHandles = append(ss.activeCertHandles, c)
|
||||
ss.peerCertificates = append(ss.peerCertificates, c.cert)
|
||||
}
|
||||
ss.ocspResponse = cert.OCSPStaple
|
||||
ss.scts = cert.SignedCertificateTimestamps
|
||||
var chainList cryptobyte.String
|
||||
if !s.ReadUint24LengthPrefixed(&chainList) {
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
for !chainList.Empty() {
|
||||
var certList cryptobyte.String
|
||||
if !chainList.ReadUint24LengthPrefixed(&certList) {
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
var chain []*x509.Certificate
|
||||
if len(ss.peerCertificates) == 0 {
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
chain = append(chain, ss.peerCertificates[0])
|
||||
for !certList.Empty() {
|
||||
var cert []byte
|
||||
if !readUint24LengthPrefixed(&certList, &cert) {
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
c, err := globalCertCache.newCert(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ss.activeCertHandles = append(ss.activeCertHandles, c)
|
||||
chain = append(chain, c.cert)
|
||||
}
|
||||
ss.verifiedChains = append(ss.verifiedChains, chain)
|
||||
}
|
||||
if ss.EarlyData {
|
||||
var alpn []byte
|
||||
if !readUint8LengthPrefixed(&s, &alpn) {
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
ss.alpnProtocol = string(alpn)
|
||||
}
|
||||
if isClient := typ == 2; !isClient {
|
||||
if !s.Empty() {
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
return ss, nil
|
||||
}
|
||||
ss.isClient = true
|
||||
if len(ss.peerCertificates) == 0 {
|
||||
return nil, errors.New("tls: no server certificates in client session")
|
||||
}
|
||||
if ss.version < VersionTLS13 {
|
||||
if !s.Empty() {
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
return ss, nil
|
||||
}
|
||||
if !s.ReadUint64(&ss.useBy) || !s.ReadUint32(&ss.ageAdd) || !s.Empty() {
|
||||
return nil, errors.New("tls: invalid session encoding")
|
||||
}
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
// sessionState returns a partially filled-out [SessionState] with information
|
||||
// from the current connection.
|
||||
func (c *Conn) sessionState() *SessionState {
|
||||
return &SessionState{
|
||||
version: c.vers,
|
||||
cipherSuite: c.cipherSuite,
|
||||
createdAt: uint64(c.config.time().Unix()),
|
||||
alpnProtocol: c.clientProtocol,
|
||||
peerCertificates: c.peerCertificates,
|
||||
activeCertHandles: c.activeCertHandles,
|
||||
ocspResponse: c.ocspResponse,
|
||||
scts: c.scts,
|
||||
isClient: c.isClient,
|
||||
extMasterSecret: c.extMasterSecret,
|
||||
verifiedChains: c.verifiedChains,
|
||||
}
|
||||
}
|
||||
|
||||
// EncryptTicket encrypts a ticket with the [Config]'s configured (or default)
|
||||
// session ticket keys. It can be used as a [Config.WrapSession] implementation.
|
||||
func (c *Config) EncryptTicket(cs ConnectionState, ss *SessionState) ([]byte, error) {
|
||||
ticketKeys := c.ticketKeys(nil)
|
||||
stateBytes, err := ss.Bytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.encryptTicket(stateBytes, ticketKeys)
|
||||
}
|
||||
|
||||
func (c *Config) encryptTicket(state []byte, ticketKeys []ticketKey) ([]byte, error) {
|
||||
if len(ticketKeys) == 0 {
|
||||
return nil, errors.New("tls: internal error: session ticket keys unavailable")
|
||||
}
|
||||
|
||||
encrypted := make([]byte, aes.BlockSize+len(state)+sha256.Size)
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
ciphertext := encrypted[aes.BlockSize : len(encrypted)-sha256.Size]
|
||||
authenticated := encrypted[:len(encrypted)-sha256.Size]
|
||||
macBytes := encrypted[len(encrypted)-sha256.Size:]
|
||||
|
||||
if _, err := io.ReadFull(c.rand(), iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := ticketKeys[0]
|
||||
block, err := aes.NewCipher(key.aesKey[:])
|
||||
if err != nil {
|
||||
return nil, errors.New("tls: failed to create cipher while encrypting ticket: " + err.Error())
|
||||
}
|
||||
cipher.NewCTR(block, iv).XORKeyStream(ciphertext, state)
|
||||
|
||||
mac := hmac.New(sha256.New, key.hmacKey[:])
|
||||
mac.Write(authenticated)
|
||||
mac.Sum(macBytes[:0])
|
||||
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
// DecryptTicket decrypts a ticket encrypted by [Config.EncryptTicket]. It can
|
||||
// be used as a [Config.UnwrapSession] implementation.
|
||||
//
|
||||
// If the ticket can't be decrypted or parsed, DecryptTicket returns (nil, nil).
|
||||
func (c *Config) DecryptTicket(identity []byte, cs ConnectionState) (*SessionState, error) {
|
||||
ticketKeys := c.ticketKeys(nil)
|
||||
stateBytes := c.decryptTicket(identity, ticketKeys)
|
||||
if stateBytes == nil {
|
||||
return nil, nil
|
||||
}
|
||||
s, err := ParseSessionState(stateBytes)
|
||||
if err != nil {
|
||||
return nil, nil // drop unparsable tickets on the floor
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (c *Config) decryptTicket(encrypted []byte, ticketKeys []ticketKey) []byte {
|
||||
if len(encrypted) < aes.BlockSize+sha256.Size {
|
||||
return nil
|
||||
}
|
||||
|
||||
iv := encrypted[:aes.BlockSize]
|
||||
ciphertext := encrypted[aes.BlockSize : len(encrypted)-sha256.Size]
|
||||
authenticated := encrypted[:len(encrypted)-sha256.Size]
|
||||
macBytes := encrypted[len(encrypted)-sha256.Size:]
|
||||
|
||||
for _, key := range ticketKeys {
|
||||
mac := hmac.New(sha256.New, key.hmacKey[:])
|
||||
mac.Write(authenticated)
|
||||
expected := mac.Sum(nil)
|
||||
|
||||
if subtle.ConstantTimeCompare(macBytes, expected) != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key.aesKey[:])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
cipher.NewCTR(block, iv).XORKeyStream(plaintext, ciphertext)
|
||||
|
||||
return plaintext
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClientSessionState contains the state needed by a client to
|
||||
// resume a previous TLS session.
|
||||
type ClientSessionState struct {
|
||||
session *SessionState
|
||||
}
|
||||
|
||||
// ResumptionState returns the session ticket sent by the server (also known as
|
||||
// the session's identity) and the state necessary to resume this session.
|
||||
//
|
||||
// It can be called by [ClientSessionCache.Put] to serialize (with
|
||||
// [SessionState.Bytes]) and store the session.
|
||||
func (cs *ClientSessionState) ResumptionState() (ticket []byte, state *SessionState, err error) {
|
||||
if cs == nil || cs.session == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return cs.session.ticket, cs.session, nil
|
||||
}
|
||||
|
||||
// NewResumptionState returns a state value that can be returned by
|
||||
// [ClientSessionCache.Get] to resume a previous session.
|
||||
//
|
||||
// state needs to be returned by [ParseSessionState], and the ticket and session
|
||||
// state must have been returned by [ClientSessionState.ResumptionState].
|
||||
func NewResumptionState(ticket []byte, state *SessionState) (*ClientSessionState, error) {
|
||||
state.ticket = ticket
|
||||
return &ClientSessionState{
|
||||
session: state,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// // DecryptTicketWith decrypts an encrypted session ticket
|
||||
// // using a TicketKeys (ie []TicketKey) struct
|
||||
// //
|
||||
// // usedOldKey will be true if the key used for decryption is
|
||||
// // not the first in the []TicketKey slice
|
||||
// //
|
||||
// // [uTLS] changed to be made public and take a TicketKeys and use a fake conn receiver
|
||||
// func DecryptTicketWith(encrypted []byte, tks TicketKeys) (plaintext []byte, usedOldKey bool) {
|
||||
// // create fake conn
|
||||
// c := &Conn{
|
||||
// ticketKeys: tks.ToPrivate(),
|
||||
// }
|
||||
|
||||
// return c.decryptTicket(encrypted)
|
||||
// }
|
||||
+373
@@ -0,0 +1,373 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package tls partially implements TLS 1.2, as specified in RFC 5246,
|
||||
// and TLS 1.3, as specified in RFC 8446.
|
||||
package tls
|
||||
|
||||
// BUG(agl): The crypto/tls package only implements some countermeasures
|
||||
// against Lucky13 attacks on CBC-mode encryption, and only on SHA1
|
||||
// variants. See http://www.isg.rhul.ac.uk/tls/TLStiming.pdf and
|
||||
// https://www.imperialviolet.org/2013/02/04/luckythirteen.html.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Server returns a new TLS server side connection
|
||||
// using conn as the underlying transport.
|
||||
// The configuration config must be non-nil and must include
|
||||
// at least one certificate or else set GetCertificate.
|
||||
func Server(conn net.Conn, config *Config) *Conn {
|
||||
c := &Conn{
|
||||
conn: conn,
|
||||
config: config,
|
||||
}
|
||||
c.handshakeFn = c.serverHandshake
|
||||
return c
|
||||
}
|
||||
|
||||
// Client returns a new TLS client side connection
|
||||
// using conn as the underlying transport.
|
||||
// The config cannot be nil: users must set either ServerName or
|
||||
// InsecureSkipVerify in the config.
|
||||
func Client(conn net.Conn, config *Config) *Conn {
|
||||
c := &Conn{
|
||||
conn: conn,
|
||||
config: config,
|
||||
isClient: true,
|
||||
}
|
||||
c.handshakeFn = c.clientHandshake
|
||||
return c
|
||||
}
|
||||
|
||||
// A listener implements a network listener (net.Listener) for TLS connections.
|
||||
type listener struct {
|
||||
net.Listener
|
||||
config *Config
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next incoming TLS connection.
|
||||
// The returned connection is of type *Conn.
|
||||
func (l *listener) Accept() (net.Conn, error) {
|
||||
c, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Server(c, l.config), nil
|
||||
}
|
||||
|
||||
// NewListener creates a Listener which accepts connections from an inner
|
||||
// Listener and wraps each connection with [Server].
|
||||
// The configuration config must be non-nil and must include
|
||||
// at least one certificate or else set GetCertificate.
|
||||
func NewListener(inner net.Listener, config *Config) net.Listener {
|
||||
l := new(listener)
|
||||
l.Listener = inner
|
||||
l.config = config
|
||||
return l
|
||||
}
|
||||
|
||||
// Listen creates a TLS listener accepting connections on the
|
||||
// given network address using net.Listen.
|
||||
// The configuration config must be non-nil and must include
|
||||
// at least one certificate or else set GetCertificate.
|
||||
func Listen(network, laddr string, config *Config) (net.Listener, error) {
|
||||
// If this condition changes, consider updating http.Server.ServeTLS too.
|
||||
if config == nil || len(config.Certificates) == 0 &&
|
||||
config.GetCertificate == nil && config.GetConfigForClient == nil {
|
||||
return nil, errors.New("tls: neither Certificates, GetCertificate, nor GetConfigForClient set in Config")
|
||||
}
|
||||
l, err := net.Listen(network, laddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewListener(l, config), nil
|
||||
}
|
||||
|
||||
type timeoutError struct{}
|
||||
|
||||
func (timeoutError) Error() string { return "tls: DialWithDialer timed out" }
|
||||
func (timeoutError) Timeout() bool { return true }
|
||||
func (timeoutError) Temporary() bool { return true }
|
||||
|
||||
// DialWithDialer connects to the given network address using dialer.Dial and
|
||||
// then initiates a TLS handshake, returning the resulting TLS connection. Any
|
||||
// timeout or deadline given in the dialer apply to connection and TLS
|
||||
// handshake as a whole.
|
||||
//
|
||||
// DialWithDialer interprets a nil configuration as equivalent to the zero
|
||||
// configuration; see the documentation of [Config] for the defaults.
|
||||
//
|
||||
// DialWithDialer uses context.Background internally; to specify the context,
|
||||
// use [Dialer.DialContext] with NetDialer set to the desired dialer.
|
||||
func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (*Conn, error) {
|
||||
return dial(context.Background(), dialer, network, addr, config)
|
||||
}
|
||||
|
||||
func dial(ctx context.Context, netDialer *net.Dialer, network, addr string, config *Config) (*Conn, error) {
|
||||
if netDialer.Timeout != 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, netDialer.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
if !netDialer.Deadline.IsZero() {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithDeadline(ctx, netDialer.Deadline)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
rawConn, err := netDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
colonPos := strings.LastIndex(addr, ":")
|
||||
if colonPos == -1 {
|
||||
colonPos = len(addr)
|
||||
}
|
||||
hostname := addr[:colonPos]
|
||||
|
||||
if config == nil {
|
||||
config = defaultConfig()
|
||||
}
|
||||
// If no ServerName is set, infer the ServerName
|
||||
// from the hostname we're connecting to.
|
||||
if config.ServerName == "" {
|
||||
// Make a copy to avoid polluting argument or default.
|
||||
c := config.Clone()
|
||||
c.ServerName = hostname
|
||||
config = c
|
||||
}
|
||||
|
||||
conn := Client(rawConn, config)
|
||||
if err := conn.HandshakeContext(ctx); err != nil {
|
||||
rawConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Dial connects to the given network address using net.Dial
|
||||
// and then initiates a TLS handshake, returning the resulting
|
||||
// TLS connection.
|
||||
// Dial interprets a nil configuration as equivalent to
|
||||
// the zero configuration; see the documentation of Config
|
||||
// for the defaults.
|
||||
func Dial(network, addr string, config *Config) (*Conn, error) {
|
||||
return DialWithDialer(new(net.Dialer), network, addr, config)
|
||||
}
|
||||
|
||||
// Dialer dials TLS connections given a configuration and a Dialer for the
|
||||
// underlying connection.
|
||||
type Dialer struct {
|
||||
// NetDialer is the optional dialer to use for the TLS connections'
|
||||
// underlying TCP connections.
|
||||
// A nil NetDialer is equivalent to the net.Dialer zero value.
|
||||
NetDialer *net.Dialer
|
||||
|
||||
// Config is the TLS configuration to use for new connections.
|
||||
// A nil configuration is equivalent to the zero
|
||||
// configuration; see the documentation of Config for the
|
||||
// defaults.
|
||||
Config *Config
|
||||
}
|
||||
|
||||
// Dial connects to the given network address and initiates a TLS
|
||||
// handshake, returning the resulting TLS connection.
|
||||
//
|
||||
// The returned [Conn], if any, will always be of type *[Conn].
|
||||
//
|
||||
// Dial uses context.Background internally; to specify the context,
|
||||
// use [Dialer.DialContext].
|
||||
func (d *Dialer) Dial(network, addr string) (net.Conn, error) {
|
||||
return d.DialContext(context.Background(), network, addr)
|
||||
}
|
||||
|
||||
func (d *Dialer) netDialer() *net.Dialer {
|
||||
if d.NetDialer != nil {
|
||||
return d.NetDialer
|
||||
}
|
||||
return new(net.Dialer)
|
||||
}
|
||||
|
||||
// DialContext connects to the given network address and initiates a TLS
|
||||
// handshake, returning the resulting TLS connection.
|
||||
//
|
||||
// The provided Context must be non-nil. If the context expires before
|
||||
// the connection is complete, an error is returned. Once successfully
|
||||
// connected, any expiration of the context will not affect the
|
||||
// connection.
|
||||
//
|
||||
// The returned [Conn], if any, will always be of type *[Conn].
|
||||
func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
c, err := dial(ctx, d.netDialer(), network, addr, d.Config)
|
||||
if err != nil {
|
||||
// Don't return c (a typed nil) in an interface.
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// LoadX509KeyPair reads and parses a public/private key pair from a pair of
|
||||
// files. The files must contain PEM encoded data. The certificate file may
|
||||
// contain intermediate certificates following the leaf certificate to form a
|
||||
// certificate chain. On successful return, Certificate.Leaf will be populated.
|
||||
//
|
||||
// Before Go 1.23 Certificate.Leaf was left nil, and the parsed certificate was
|
||||
// discarded. This behavior can be re-enabled by setting "x509keypairleaf=0"
|
||||
// in the GODEBUG environment variable.
|
||||
func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) {
|
||||
certPEMBlock, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return Certificate{}, err
|
||||
}
|
||||
keyPEMBlock, err := os.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return Certificate{}, err
|
||||
}
|
||||
return X509KeyPair(certPEMBlock, keyPEMBlock)
|
||||
}
|
||||
|
||||
// var x509keypairleaf = godebug.New("x509keypairleaf") [uTLS]
|
||||
|
||||
// X509KeyPair parses a public/private key pair from a pair of
|
||||
// PEM encoded data. On successful return, Certificate.Leaf will be populated.
|
||||
//
|
||||
// Before Go 1.23 Certificate.Leaf was left nil, and the parsed certificate was
|
||||
// discarded. This behavior can be re-enabled by setting "x509keypairleaf=0"
|
||||
// in the GODEBUG environment variable.
|
||||
func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (Certificate, error) {
|
||||
fail := func(err error) (Certificate, error) { return Certificate{}, err }
|
||||
|
||||
var cert Certificate
|
||||
var skippedBlockTypes []string
|
||||
for {
|
||||
var certDERBlock *pem.Block
|
||||
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
|
||||
if certDERBlock == nil {
|
||||
break
|
||||
}
|
||||
if certDERBlock.Type == "CERTIFICATE" {
|
||||
cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
|
||||
} else {
|
||||
skippedBlockTypes = append(skippedBlockTypes, certDERBlock.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if len(cert.Certificate) == 0 {
|
||||
if len(skippedBlockTypes) == 0 {
|
||||
return fail(errors.New("tls: failed to find any PEM data in certificate input"))
|
||||
}
|
||||
if len(skippedBlockTypes) == 1 && strings.HasSuffix(skippedBlockTypes[0], "PRIVATE KEY") {
|
||||
return fail(errors.New("tls: failed to find certificate PEM data in certificate input, but did find a private key; PEM inputs may have been switched"))
|
||||
}
|
||||
return fail(fmt.Errorf("tls: failed to find \"CERTIFICATE\" PEM block in certificate input after skipping PEM blocks of the following types: %v", skippedBlockTypes))
|
||||
}
|
||||
|
||||
skippedBlockTypes = skippedBlockTypes[:0]
|
||||
var keyDERBlock *pem.Block
|
||||
for {
|
||||
keyDERBlock, keyPEMBlock = pem.Decode(keyPEMBlock)
|
||||
if keyDERBlock == nil {
|
||||
if len(skippedBlockTypes) == 0 {
|
||||
return fail(errors.New("tls: failed to find any PEM data in key input"))
|
||||
}
|
||||
if len(skippedBlockTypes) == 1 && skippedBlockTypes[0] == "CERTIFICATE" {
|
||||
return fail(errors.New("tls: found a certificate rather than a key in the PEM for the private key"))
|
||||
}
|
||||
return fail(fmt.Errorf("tls: failed to find PEM block with type ending in \"PRIVATE KEY\" in key input after skipping PEM blocks of the following types: %v", skippedBlockTypes))
|
||||
}
|
||||
if keyDERBlock.Type == "PRIVATE KEY" || strings.HasSuffix(keyDERBlock.Type, " PRIVATE KEY") {
|
||||
break
|
||||
}
|
||||
skippedBlockTypes = append(skippedBlockTypes, keyDERBlock.Type)
|
||||
}
|
||||
|
||||
// We don't need to parse the public key for TLS, but we so do anyway
|
||||
// to check that it looks sane and matches the private key.
|
||||
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return fail(err)
|
||||
}
|
||||
|
||||
// [uTLS section begins]
|
||||
// if x509keypairleaf.Value() != "0" {
|
||||
// cert.Leaf = x509Cert
|
||||
// } else {
|
||||
// x509keypairleaf.IncNonDefault()
|
||||
// }
|
||||
// [uTLS section ends]
|
||||
|
||||
cert.PrivateKey, err = parsePrivateKey(keyDERBlock.Bytes)
|
||||
if err != nil {
|
||||
return fail(err)
|
||||
}
|
||||
|
||||
switch pub := x509Cert.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
priv, ok := cert.PrivateKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return fail(errors.New("tls: private key type does not match public key type"))
|
||||
}
|
||||
if pub.N.Cmp(priv.N) != 0 {
|
||||
return fail(errors.New("tls: private key does not match public key"))
|
||||
}
|
||||
case *ecdsa.PublicKey:
|
||||
priv, ok := cert.PrivateKey.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
return fail(errors.New("tls: private key type does not match public key type"))
|
||||
}
|
||||
if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 {
|
||||
return fail(errors.New("tls: private key does not match public key"))
|
||||
}
|
||||
case ed25519.PublicKey:
|
||||
priv, ok := cert.PrivateKey.(ed25519.PrivateKey)
|
||||
if !ok {
|
||||
return fail(errors.New("tls: private key type does not match public key type"))
|
||||
}
|
||||
if !bytes.Equal(priv.Public().(ed25519.PublicKey), pub) {
|
||||
return fail(errors.New("tls: private key does not match public key"))
|
||||
}
|
||||
default:
|
||||
return fail(errors.New("tls: unknown public key algorithm"))
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
|
||||
// PKCS #1 private keys by default, while OpenSSL 1.0.0 generates PKCS #8 keys.
|
||||
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
|
||||
func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
|
||||
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
|
||||
return key, nil
|
||||
default:
|
||||
return nil, errors.New("tls: found unknown private key type in PKCS#8 wrapping")
|
||||
}
|
||||
}
|
||||
if key, err := x509.ParseECPrivateKey(der); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("tls: failed to parse private key")
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
)
|
||||
|
||||
// This file contains all the alias functions, symbols, names, etc. that
|
||||
// was once used in the old version of the library. This is to ensure
|
||||
// backwards compatibility with the old version of the library.
|
||||
|
||||
// TLS Extensions
|
||||
|
||||
// UtlsExtendedMasterSecretExtension is an alias for ExtendedMasterSecretExtension.
|
||||
//
|
||||
// Deprecated: Use ExtendedMasterSecretExtension instead.
|
||||
type UtlsExtendedMasterSecretExtension = ExtendedMasterSecretExtension
|
||||
|
||||
// Deprecated: Use KeySharePrivateKeys instead. This type is not used and will be removed in the future.
|
||||
// KeySharesParameters serves as a in-memory storage for generated keypairs by UTLS when generating
|
||||
// ClientHello. It is used to store both ecdhe and kem keypairs.
|
||||
type KeySharesParameters struct{}
|
||||
|
||||
func NewKeySharesParameters() *KeySharesParameters { return &KeySharesParameters{} }
|
||||
|
||||
func (*KeySharesParameters) AddEcdheKeypair(curveID CurveID, ecdheKey *ecdh.PrivateKey, ecdhePubKey *ecdh.PublicKey) {
|
||||
return
|
||||
}
|
||||
|
||||
func (*KeySharesParameters) GetEcdheKey(curveID CurveID) (ecdheKey *ecdh.PrivateKey, ok bool) { return }
|
||||
|
||||
func (*KeySharesParameters) GetEcdhePubkey(curveID CurveID) (params *ecdh.PublicKey, ok bool) { return }
|
||||
|
||||
func (*KeySharesParameters) AddKemKeypair(curveID CurveID, kemKey any, kemPubKey any) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ksp *KeySharesParameters) GetKemKey(curveID CurveID) (kemKey any, ok bool) { return }
|
||||
|
||||
func (ksp *KeySharesParameters) GetKemPubkey(curveID CurveID) (params any, ok bool) { return }
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/refraction-networking/utls/dicttls"
|
||||
)
|
||||
|
||||
var ErrUnknownExtension = errors.New("extension name is unknown to the dictionary")
|
||||
|
||||
type ClientHelloSpecJSONUnmarshaler struct {
|
||||
CipherSuites *CipherSuitesJSONUnmarshaler `json:"cipher_suites"`
|
||||
CompressionMethods *CompressionMethodsJSONUnmarshaler `json:"compression_methods"`
|
||||
Extensions *TLSExtensionsJSONUnmarshaler `json:"extensions"`
|
||||
TLSVersMin uint16 `json:"min_vers,omitempty"` // optional
|
||||
TLSVersMax uint16 `json:"max_vers,omitempty"` // optional
|
||||
}
|
||||
|
||||
func (chsju *ClientHelloSpecJSONUnmarshaler) ClientHelloSpec() ClientHelloSpec {
|
||||
return ClientHelloSpec{
|
||||
CipherSuites: chsju.CipherSuites.CipherSuites(),
|
||||
CompressionMethods: chsju.CompressionMethods.CompressionMethods(),
|
||||
Extensions: chsju.Extensions.Extensions(),
|
||||
TLSVersMin: chsju.TLSVersMin,
|
||||
TLSVersMax: chsju.TLSVersMax,
|
||||
}
|
||||
}
|
||||
|
||||
type CipherSuitesJSONUnmarshaler struct {
|
||||
cipherSuites []uint16
|
||||
}
|
||||
|
||||
func (c *CipherSuitesJSONUnmarshaler) UnmarshalJSON(jsonStr []byte) error {
|
||||
var cipherSuiteNames []string
|
||||
if err := json.Unmarshal(jsonStr, &cipherSuiteNames); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range cipherSuiteNames {
|
||||
if name == "GREASE" {
|
||||
c.cipherSuites = append(c.cipherSuites, GREASE_PLACEHOLDER)
|
||||
continue
|
||||
}
|
||||
|
||||
if id, ok := dicttls.DictCipherSuiteNameIndexed[name]; ok {
|
||||
c.cipherSuites = append(c.cipherSuites, id)
|
||||
} else {
|
||||
return fmt.Errorf("unknown cipher suite name: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CipherSuitesJSONUnmarshaler) CipherSuites() []uint16 {
|
||||
return c.cipherSuites
|
||||
}
|
||||
|
||||
type CompressionMethodsJSONUnmarshaler struct {
|
||||
compressionMethods []uint8
|
||||
}
|
||||
|
||||
func (c *CompressionMethodsJSONUnmarshaler) UnmarshalJSON(jsonStr []byte) error {
|
||||
var compressionMethodNames []string
|
||||
if err := json.Unmarshal(jsonStr, &compressionMethodNames); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range compressionMethodNames {
|
||||
if id, ok := dicttls.DictCompMethNameIndexed[name]; ok {
|
||||
c.compressionMethods = append(c.compressionMethods, id)
|
||||
} else {
|
||||
return fmt.Errorf("unknown compression method name: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CompressionMethodsJSONUnmarshaler) CompressionMethods() []uint8 {
|
||||
return c.compressionMethods
|
||||
}
|
||||
|
||||
type TLSExtensionsJSONUnmarshaler struct {
|
||||
AllowUnknownExt bool // if set, unknown extensions will be added as GenericExtension, without recovering ext payload
|
||||
UseRealPSK bool // if set, PSK extension will be real PSK extension, otherwise it will be fake PSK extension
|
||||
extensions []TLSExtensionJSON
|
||||
}
|
||||
|
||||
func (e *TLSExtensionsJSONUnmarshaler) UnmarshalJSON(jsonStr []byte) error {
|
||||
var accepters []tlsExtensionJSONAccepter
|
||||
if err := json.Unmarshal(jsonStr, &accepters); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var exts []TLSExtensionJSON = make([]TLSExtensionJSON, 0, len(accepters))
|
||||
for _, accepter := range accepters {
|
||||
if accepter.extNameOnly.Name == "GREASE" {
|
||||
exts = append(exts, &UtlsGREASEExtension{})
|
||||
continue
|
||||
}
|
||||
|
||||
if extID, ok := dicttls.DictExtTypeNameIndexed[accepter.extNameOnly.Name]; !ok {
|
||||
return fmt.Errorf("%w: %s", ErrUnknownExtension, accepter.extNameOnly.Name)
|
||||
} else {
|
||||
// get extension type from ID
|
||||
var ext TLSExtension = ExtensionFromID(extID)
|
||||
if ext == nil {
|
||||
if e.AllowUnknownExt {
|
||||
// fallback to generic extension, without recovering ext payload
|
||||
ext = genericExtension(extID, accepter.extNameOnly.Name)
|
||||
} else {
|
||||
return fmt.Errorf("extension %s (%d) is not JSON compatible", accepter.extNameOnly.Name, extID)
|
||||
}
|
||||
}
|
||||
|
||||
switch extID {
|
||||
case extensionPreSharedKey:
|
||||
// PSK extension, need to see if we do real or fake PSK
|
||||
if e.UseRealPSK {
|
||||
ext = &UtlsPreSharedKeyExtension{}
|
||||
} else {
|
||||
ext = &FakePreSharedKeyExtension{}
|
||||
}
|
||||
}
|
||||
|
||||
if extJsonCompatible, ok := ext.(TLSExtensionJSON); ok {
|
||||
exts = append(exts, extJsonCompatible)
|
||||
} else {
|
||||
return fmt.Errorf("extension %s (%d) is not JSON compatible", accepter.extNameOnly.Name, extID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unmashal extensions
|
||||
for idx, ext := range exts {
|
||||
// json.Unmarshal will call the UnmarshalJSON method of the extension
|
||||
if err := json.Unmarshal(accepters[idx].origJsonInput, ext); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
e.extensions = exts
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *TLSExtensionsJSONUnmarshaler) Extensions() []TLSExtension {
|
||||
var exts []TLSExtension = make([]TLSExtension, 0, len(e.extensions))
|
||||
for _, ext := range e.extensions {
|
||||
exts = append(exts, ext)
|
||||
}
|
||||
return exts
|
||||
}
|
||||
|
||||
func genericExtension(id uint16, name string) TLSExtension {
|
||||
var warningMsg string = "WARNING: extension "
|
||||
warningMsg += fmt.Sprintf("%d ", id)
|
||||
if len(name) > 0 {
|
||||
warningMsg += fmt.Sprintf("(%s) ", name)
|
||||
}
|
||||
warningMsg += "is falling back to generic extension"
|
||||
warningMsg += "\n"
|
||||
|
||||
fmt.Fprint(os.Stderr, warningMsg)
|
||||
|
||||
// fallback to generic extension
|
||||
return &GenericExtension{Id: id}
|
||||
}
|
||||
|
||||
type tlsExtensionJSONAccepter struct {
|
||||
extNameOnly struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
origJsonInput []byte
|
||||
}
|
||||
|
||||
func (t *tlsExtensionJSONAccepter) UnmarshalJSON(jsonStr []byte) error {
|
||||
t.origJsonInput = make([]byte, len(jsonStr))
|
||||
copy(t.origJsonInput, jsonStr)
|
||||
return json.Unmarshal(jsonStr, &t.extNameOnly)
|
||||
}
|
||||
+821
@@ -0,0 +1,821 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha512"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"log"
|
||||
|
||||
"github.com/refraction-networking/utls/internal/helper"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
// Naming convention:
|
||||
// Unsupported things are prefixed with "Fake"
|
||||
// Things, supported by utls, but not crypto/tls' are prefixed with "utls"
|
||||
// Supported things, that have changed their ID are prefixed with "Old"
|
||||
// Supported but disabled things are prefixed with "Disabled". We will _enable_ them.
|
||||
|
||||
// TLS handshake message types.
|
||||
const (
|
||||
utlsTypeEncryptedExtensions uint8 = 8 // implemention incomplete by crypto/tls
|
||||
// https://datatracker.ietf.org/doc/html/rfc8879#section-7.2
|
||||
utlsTypeCompressedCertificate uint8 = 25
|
||||
)
|
||||
|
||||
// TLS
|
||||
const (
|
||||
extensionNextProtoNeg uint16 = 13172 // not IANA assigned. Removed by crypto/tls since Nov 2019
|
||||
|
||||
utlsExtensionPadding uint16 = 21
|
||||
utlsExtensionCompressCertificate uint16 = 27 // https://datatracker.ietf.org/doc/html/rfc8879#section-7.1
|
||||
utlsExtensionApplicationSettings uint16 = 17513 // not IANA assigned
|
||||
utlsExtensionApplicationSettingsNew uint16 = 17613 // not IANA assigned
|
||||
utlsFakeExtensionCustom uint16 = 1234 // not IANA assigned, for ALPS
|
||||
utlsExtensionECH uint16 = 0xfe0d // draft-ietf-tls-esni-17
|
||||
utlsExtensionECHOuterExtensions uint16 = 0xfd00 // draft-ietf-tls-esni-17
|
||||
|
||||
// extensions with 'fake' prefix break connection, if server echoes them back
|
||||
fakeExtensionEncryptThenMAC uint16 = 22
|
||||
fakeExtensionTokenBinding uint16 = 24
|
||||
fakeExtensionDelegatedCredentials uint16 = 34
|
||||
fakeExtensionPreSharedKey uint16 = 41
|
||||
fakeOldExtensionChannelID uint16 = 30031 // not IANA assigned
|
||||
fakeExtensionChannelID uint16 = 30032 // not IANA assigned
|
||||
)
|
||||
|
||||
const (
|
||||
OLD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = uint16(0xcc13)
|
||||
OLD_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = uint16(0xcc14)
|
||||
|
||||
DISABLED_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = uint16(0xc024)
|
||||
DISABLED_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = uint16(0xc028)
|
||||
DISABLED_TLS_RSA_WITH_AES_256_CBC_SHA256 = uint16(0x003d)
|
||||
|
||||
FAKE_OLD_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = uint16(0xcc15) // we can try to craft these ciphersuites
|
||||
FAKE_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = uint16(0x009e) // from existing pieces, if needed
|
||||
|
||||
FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = uint16(0x0033)
|
||||
FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = uint16(0x0039)
|
||||
FAKE_TLS_RSA_WITH_RC4_128_MD5 = uint16(0x0004)
|
||||
FAKE_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = uint16(0x009f)
|
||||
FAKE_TLS_DHE_DSS_WITH_AES_128_CBC_SHA = uint16(0x0032)
|
||||
FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = uint16(0x006b)
|
||||
FAKE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = uint16(0x0067)
|
||||
FAKE_TLS_EMPTY_RENEGOTIATION_INFO_SCSV = uint16(0x00ff)
|
||||
|
||||
// https://docs.microsoft.com/en-us/dotnet/api/system.net.security.tlsciphersuite?view=netcore-3.1
|
||||
FAKE_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = uint16(0xc008)
|
||||
)
|
||||
|
||||
const (
|
||||
CurveSECP256R1 CurveID = 0x0017
|
||||
CurveSECP384R1 CurveID = 0x0018
|
||||
CurveSECP521R1 CurveID = 0x0019
|
||||
CurveX25519 CurveID = 0x001d
|
||||
|
||||
FakeCurveFFDHE2048 CurveID = 0x0100
|
||||
FakeCurveFFDHE3072 CurveID = 0x0101
|
||||
FakeCurveFFDHE4096 CurveID = 0x0102
|
||||
FakeCurveFFDHE6144 CurveID = 0x0103
|
||||
FakeCurveFFDHE8192 CurveID = 0x0104
|
||||
)
|
||||
|
||||
const (
|
||||
X25519Kyber768Draft00 CurveID = 0x6399
|
||||
|
||||
FakeCurveX25519Kyber512Draft00 CurveID = 0xfe30
|
||||
FakeCurveX25519Kyber768Draft00Old CurveID = 0xfe31
|
||||
FakeCurveP256Kyber768Draft00 CurveID = 0xfe32
|
||||
|
||||
X25519Kyber512Draft00 CurveID = FakeCurveX25519Kyber512Draft00
|
||||
X25519Kyber768Draft00Old CurveID = FakeCurveX25519Kyber768Draft00Old
|
||||
P256Kyber768Draft00 CurveID = FakeCurveP256Kyber768Draft00
|
||||
)
|
||||
|
||||
// Other things
|
||||
const (
|
||||
fakeRecordSizeLimit uint16 = 0x001c
|
||||
)
|
||||
|
||||
// newest signatures
|
||||
var (
|
||||
FakePKCS1WithSHA224 SignatureScheme = 0x0301
|
||||
FakeECDSAWithSHA224 SignatureScheme = 0x0303
|
||||
|
||||
FakeSHA1WithDSA SignatureScheme = 0x0202
|
||||
FakeSHA256WithDSA SignatureScheme = 0x0402
|
||||
|
||||
// fakeEd25519 = SignatureAndHash{0x08, 0x07}
|
||||
// fakeEd448 = SignatureAndHash{0x08, 0x08}
|
||||
)
|
||||
|
||||
// fake curves(groups)
|
||||
var (
|
||||
FakeFFDHE2048 = uint16(0x0100)
|
||||
FakeFFDHE3072 = uint16(0x0101)
|
||||
)
|
||||
|
||||
// https://tools.ietf.org/html/draft-ietf-tls-certificate-compression-04
|
||||
type CertCompressionAlgo uint16
|
||||
|
||||
const (
|
||||
CertCompressionZlib CertCompressionAlgo = 0x0001
|
||||
CertCompressionBrotli CertCompressionAlgo = 0x0002
|
||||
CertCompressionZstd CertCompressionAlgo = 0x0003
|
||||
)
|
||||
|
||||
const (
|
||||
PskModePlain uint8 = pskModePlain
|
||||
PskModeDHE uint8 = pskModeDHE
|
||||
)
|
||||
|
||||
type ClientHelloID struct {
|
||||
Client string
|
||||
|
||||
// Version specifies version of a mimicked clients (e.g. browsers).
|
||||
// Not used in randomized, custom handshake, and default Go.
|
||||
Version string
|
||||
|
||||
// Seed is only used for randomized fingerprints to seed PRNG.
|
||||
// Must not be modified once set.
|
||||
Seed *PRNGSeed
|
||||
|
||||
// Weights are only used for randomized fingerprints in func
|
||||
// generateRandomizedSpec(). Must not be modified once set.
|
||||
Weights *Weights
|
||||
}
|
||||
|
||||
func (p *ClientHelloID) Str() string {
|
||||
return fmt.Sprintf("%s-%s", p.Client, p.Version)
|
||||
}
|
||||
|
||||
func (p *ClientHelloID) IsSet() bool {
|
||||
return (p.Client == "") && (p.Version == "")
|
||||
}
|
||||
|
||||
const (
|
||||
// clients
|
||||
helloGolang = "Golang"
|
||||
helloRandomized = "Randomized"
|
||||
helloRandomizedALPN = "Randomized-ALPN"
|
||||
helloRandomizedNoALPN = "Randomized-NoALPN"
|
||||
helloCustom = "Custom"
|
||||
helloFirefox = "Firefox"
|
||||
helloChrome = "Chrome"
|
||||
helloIOS = "iOS"
|
||||
helloAndroid = "Android"
|
||||
helloEdge = "Edge"
|
||||
helloSafari = "Safari"
|
||||
hello360 = "360Browser"
|
||||
helloQQ = "QQBrowser"
|
||||
|
||||
// versions
|
||||
helloAutoVers = "0"
|
||||
)
|
||||
|
||||
type ClientHelloSpec struct {
|
||||
CipherSuites []uint16 // nil => default
|
||||
CompressionMethods []uint8 // nil => no compression
|
||||
Extensions []TLSExtension // nil => no extensions
|
||||
|
||||
TLSVersMin uint16 // [1.0-1.3] default: parse from .Extensions, if SupportedVersions ext is not present => 1.0
|
||||
TLSVersMax uint16 // [1.2-1.3] default: parse from .Extensions, if SupportedVersions ext is not present => 1.2
|
||||
|
||||
// GreaseStyle: currently only random
|
||||
// sessionID may or may not depend on ticket; nil => random
|
||||
GetSessionID func(ticket []byte) [32]byte
|
||||
|
||||
// TLSFingerprintLink string // ?? link to tlsfingerprint.io for informational purposes
|
||||
}
|
||||
|
||||
// ReadCipherSuites is a helper function to construct a list of cipher suites from
|
||||
// a []byte into []uint16.
|
||||
//
|
||||
// example: []byte{0x13, 0x01, 0x13, 0x02, 0x13, 0x03} => []uint16{0x1301, 0x1302, 0x1303}
|
||||
func (chs *ClientHelloSpec) ReadCipherSuites(b []byte) error {
|
||||
cipherSuites := []uint16{}
|
||||
s := cryptobyte.String(b)
|
||||
for !s.Empty() {
|
||||
var suite uint16
|
||||
if !s.ReadUint16(&suite) {
|
||||
return errors.New("unable to read ciphersuite")
|
||||
}
|
||||
cipherSuites = append(cipherSuites, unGREASEUint16(suite))
|
||||
}
|
||||
chs.CipherSuites = cipherSuites
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadCompressionMethods is a helper function to construct a list of compression
|
||||
// methods from a []byte into []uint8.
|
||||
func (chs *ClientHelloSpec) ReadCompressionMethods(compressionMethods []byte) error {
|
||||
chs.CompressionMethods = compressionMethods
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadTLSExtensions is a helper function to construct a list of TLS extensions from
|
||||
// a byte slice into []TLSExtension.
|
||||
func (chs *ClientHelloSpec) ReadTLSExtensions(b []byte, allowBluntMimicry bool, realPSK bool) error {
|
||||
extensions := cryptobyte.String(b)
|
||||
for !extensions.Empty() {
|
||||
var extension uint16
|
||||
var extData cryptobyte.String
|
||||
if !extensions.ReadUint16(&extension) {
|
||||
return fmt.Errorf("unable to read extension ID")
|
||||
}
|
||||
if !extensions.ReadUint16LengthPrefixed(&extData) {
|
||||
return fmt.Errorf("unable to read data for extension %x", extension)
|
||||
}
|
||||
|
||||
ext := ExtensionFromID(extension)
|
||||
extWriter, ok := ext.(TLSExtensionWriter)
|
||||
if ext != nil && ok { // known extension and implements TLSExtensionWriter properly
|
||||
switch extension {
|
||||
case extensionPreSharedKey:
|
||||
// PSK extension, need to see if we do real or fake PSK
|
||||
if realPSK {
|
||||
extWriter = &UtlsPreSharedKeyExtension{}
|
||||
} else {
|
||||
extWriter = &FakePreSharedKeyExtension{}
|
||||
}
|
||||
case extensionSupportedVersions:
|
||||
chs.TLSVersMin = 0
|
||||
chs.TLSVersMax = 0
|
||||
}
|
||||
|
||||
if _, err := extWriter.Write(extData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chs.Extensions = append(chs.Extensions, extWriter)
|
||||
} else {
|
||||
if allowBluntMimicry {
|
||||
chs.Extensions = append(chs.Extensions, &GenericExtension{extension, extData})
|
||||
} else {
|
||||
return fmt.Errorf("unsupported extension %d", extension)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (chs *ClientHelloSpec) AlwaysAddPadding() {
|
||||
alreadyHasPadding := false
|
||||
for idx, ext := range chs.Extensions {
|
||||
if _, ok := ext.(*UtlsPaddingExtension); ok {
|
||||
alreadyHasPadding = true
|
||||
break
|
||||
}
|
||||
if _, ok := ext.(PreSharedKeyExtension); ok {
|
||||
alreadyHasPadding = true // PSK must be last, so we can't append padding after it
|
||||
// instead we will insert padding before PSK
|
||||
chs.Extensions = append(chs.Extensions[:idx], append([]TLSExtension{&UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle}}, chs.Extensions[idx:]...)...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alreadyHasPadding {
|
||||
chs.Extensions = append(chs.Extensions, &UtlsPaddingExtension{GetPaddingLen: BoringPaddingStyle})
|
||||
}
|
||||
}
|
||||
|
||||
// Import TLS ClientHello data from client.tlsfingerprint.io:8443
|
||||
//
|
||||
// data is a map of []byte with following keys:
|
||||
// - cipher_suites: [10, 10, 19, 1, 19, 2, 19, 3, 192, 43, 192, 47, 192, 44, 192, 48, 204, 169, 204, 168, 192, 19, 192, 20, 0, 156, 0, 157, 0, 47, 0, 53]
|
||||
// - compression_methods: [0] => null
|
||||
// - extensions: [10, 10, 255, 1, 0, 45, 0, 35, 0, 16, 68, 105, 0, 11, 0, 43, 0, 18, 0, 13, 0, 0, 0, 10, 0, 27, 0, 5, 0, 51, 0, 23, 10, 10, 0, 21]
|
||||
// - pt_fmts (ec_point_formats): [1, 0] => len: 1, content: 0x00
|
||||
// - sig_algs (signature_algorithms): [0, 16, 4, 3, 8, 4, 4, 1, 5, 3, 8, 5, 5, 1, 8, 6, 6, 1] => len: 16, content: 0x0403, 0x0804, 0x0401, 0x0503, 0x0805, 0x0501, 0x0806, 0x0601
|
||||
// - supported_versions: [10, 10, 3, 4, 3, 3] => 0x0a0a, 0x0304, 0x0303 (GREASE, TLS 1.3, TLS 1.2)
|
||||
// - curves (named_groups, supported_groups): [0, 8, 10, 10, 0, 29, 0, 23, 0, 24] => len: 8, content: GREASE, 0x001d, 0x0017, 0x0018
|
||||
// - alpn: [0, 12, 2, 104, 50, 8, 104, 116, 116, 112, 47, 49, 46, 49] => len: 12, content: h2, http/1.1
|
||||
// - key_share: [10, 10, 0, 1, 0, 29, 0, 32] => {group: 0x0a0a, len:1}, {group: 0x001d, len:32}
|
||||
// - psk_key_exchange_modes: [1] => psk_dhe_ke(0x01)
|
||||
// - cert_compression_algs: [2, 0, 2] => brotli (0x0002)
|
||||
// - record_size_limit: [0, 255] => 255
|
||||
//
|
||||
// TLSVersMin/TLSVersMax are set to 0 if supported_versions is present.
|
||||
// To prevent conflict, they should be set manually if needed BEFORE calling this function.
|
||||
func (chs *ClientHelloSpec) ImportTLSClientHello(data map[string][]byte) error {
|
||||
var tlsExtensionTypes []uint16
|
||||
var err error
|
||||
|
||||
if data["cipher_suites"] == nil {
|
||||
return errors.New("cipher_suites is required")
|
||||
}
|
||||
chs.CipherSuites, err = helper.Uint8to16(data["cipher_suites"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if data["compression_methods"] == nil {
|
||||
return errors.New("compression_methods is required")
|
||||
}
|
||||
chs.CompressionMethods = data["compression_methods"]
|
||||
|
||||
if data["extensions"] == nil {
|
||||
return errors.New("extensions is required")
|
||||
}
|
||||
tlsExtensionTypes, err = helper.Uint8to16(data["extensions"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, extType := range tlsExtensionTypes {
|
||||
extension := ExtensionFromID(extType)
|
||||
extWriter, ok := extension.(TLSExtensionWriter)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported extension %d", extType)
|
||||
}
|
||||
if extension == nil || !ok {
|
||||
log.Printf("[Warning] Unsupported extension %d added as a &GenericExtension without Data", extType)
|
||||
chs.Extensions = append(chs.Extensions, &GenericExtension{extType, []byte{}})
|
||||
} else {
|
||||
switch extType {
|
||||
case extensionSupportedPoints:
|
||||
if data["pt_fmts"] == nil {
|
||||
return errors.New("pt_fmts is required")
|
||||
}
|
||||
_, err = extWriter.Write(data["pt_fmts"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case extensionSignatureAlgorithms:
|
||||
if data["sig_algs"] == nil {
|
||||
return errors.New("sig_algs is required")
|
||||
}
|
||||
_, err = extWriter.Write(data["sig_algs"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case extensionSupportedVersions:
|
||||
chs.TLSVersMin = 0
|
||||
chs.TLSVersMax = 0
|
||||
|
||||
if data["supported_versions"] == nil {
|
||||
return errors.New("supported_versions is required")
|
||||
}
|
||||
|
||||
// need to add uint8 length prefix
|
||||
fixedData := make([]byte, len(data["supported_versions"])+1)
|
||||
fixedData[0] = uint8(len(data["supported_versions"]) & 0xff)
|
||||
copy(fixedData[1:], data["supported_versions"])
|
||||
_, err = extWriter.Write(fixedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case extensionSupportedCurves:
|
||||
if data["curves"] == nil {
|
||||
return errors.New("curves is required")
|
||||
}
|
||||
|
||||
_, err = extWriter.Write(data["curves"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case extensionALPN:
|
||||
if data["alpn"] == nil {
|
||||
return errors.New("alpn is required")
|
||||
}
|
||||
|
||||
_, err = extWriter.Write(data["alpn"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case extensionKeyShare:
|
||||
if data["key_share"] == nil {
|
||||
return errors.New("key_share is required")
|
||||
}
|
||||
|
||||
// need to add (zero) data per each key share, [10, 10, 0, 1] => [10, 10, 0, 1, 0]
|
||||
fixedData := make([]byte, 0)
|
||||
for i := 0; i < len(data["key_share"]); i += 4 {
|
||||
fixedData = append(fixedData, data["key_share"][i:i+4]...)
|
||||
for j := 0; j < int(data["key_share"][i+3]); j++ {
|
||||
fixedData = append(fixedData, 0)
|
||||
}
|
||||
}
|
||||
// add uint16 length prefix
|
||||
fixedData = append([]byte{uint8(len(fixedData) >> 8), uint8(len(fixedData) & 0xff)}, fixedData...)
|
||||
|
||||
_, err = extWriter.Write(fixedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case extensionPSKModes:
|
||||
if data["psk_key_exchange_modes"] == nil {
|
||||
return errors.New("psk_key_exchange_modes is required")
|
||||
}
|
||||
|
||||
// need to add uint8 length prefix
|
||||
fixedData := make([]byte, len(data["psk_key_exchange_modes"])+1)
|
||||
fixedData[0] = uint8(len(data["psk_key_exchange_modes"]) & 0xff)
|
||||
copy(fixedData[1:], data["psk_key_exchange_modes"])
|
||||
_, err = extWriter.Write(fixedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case utlsExtensionCompressCertificate:
|
||||
if data["cert_compression_algs"] == nil {
|
||||
return errors.New("cert_compression_algs is required")
|
||||
}
|
||||
|
||||
// need to add uint8 length prefix
|
||||
fixedData := make([]byte, len(data["cert_compression_algs"])+1)
|
||||
fixedData[0] = uint8(len(data["cert_compression_algs"]) & 0xff)
|
||||
copy(fixedData[1:], data["cert_compression_algs"])
|
||||
_, err = extWriter.Write(fixedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case fakeRecordSizeLimit:
|
||||
if data["record_size_limit"] == nil {
|
||||
return errors.New("record_size_limit is required")
|
||||
}
|
||||
|
||||
_, err = extWriter.Write(data["record_size_limit"]) // uint16 as []byte
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case utlsExtensionApplicationSettings:
|
||||
// TODO: tlsfingerprint.io should record/provide application settings data
|
||||
extWriter.(*ApplicationSettingsExtension).SupportedProtocols = []string{"h2"}
|
||||
case utlsExtensionApplicationSettingsNew:
|
||||
extWriter.(*ApplicationSettingsExtensionNew).SupportedProtocols = []string{"h2"}
|
||||
case extensionPreSharedKey:
|
||||
log.Printf("[Warning] PSK extension added without data")
|
||||
default:
|
||||
if !isGREASEUint16(extType) {
|
||||
log.Printf("[Warning] extension %d added without data", extType)
|
||||
} /*else {
|
||||
log.Printf("[Warning] GREASE extension added but ID/Data discarded. They will be automatically re-GREASEd on ApplyPreset() call.")
|
||||
}*/
|
||||
}
|
||||
chs.Extensions = append(chs.Extensions, extWriter)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportTLSClientHelloFromJSON imports ClientHelloSpec from JSON data from client.tlsfingerprint.io format
|
||||
//
|
||||
// It calls ImportTLSClientHello internally after unmarshaling JSON data into map[string][]byte
|
||||
func (chs *ClientHelloSpec) ImportTLSClientHelloFromJSON(jsonB []byte) error {
|
||||
var data map[string][]byte
|
||||
err := json.Unmarshal(jsonB, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return chs.ImportTLSClientHello(data)
|
||||
}
|
||||
|
||||
// FromRaw converts a ClientHello message in the form of raw bytes into a ClientHelloSpec.
|
||||
//
|
||||
// ctrlFlags: []bool{bluntMimicry, realPSK}
|
||||
func (chs *ClientHelloSpec) FromRaw(raw []byte, ctrlFlags ...bool) error {
|
||||
if chs == nil {
|
||||
return errors.New("cannot unmarshal into nil ClientHelloSpec")
|
||||
}
|
||||
|
||||
var bluntMimicry = false
|
||||
var realPSK = false
|
||||
if len(ctrlFlags) > 0 {
|
||||
bluntMimicry = ctrlFlags[0]
|
||||
}
|
||||
if len(ctrlFlags) > 1 {
|
||||
realPSK = ctrlFlags[1]
|
||||
}
|
||||
|
||||
*chs = ClientHelloSpec{} // reset
|
||||
s := cryptobyte.String(raw)
|
||||
|
||||
var contentType uint8
|
||||
var recordVersion uint16
|
||||
if !s.ReadUint8(&contentType) || // record type
|
||||
!s.ReadUint16(&recordVersion) || !s.Skip(2) { // record version and length
|
||||
return errors.New("unable to read record type, version, and length")
|
||||
}
|
||||
|
||||
if recordType(contentType) != recordTypeHandshake {
|
||||
return errors.New("record is not a handshake")
|
||||
}
|
||||
|
||||
var handshakeVersion uint16
|
||||
var handshakeType uint8
|
||||
|
||||
if !s.ReadUint8(&handshakeType) || !s.Skip(3) || // message type and 3 byte length
|
||||
!s.ReadUint16(&handshakeVersion) || !s.Skip(32) { // 32 byte random
|
||||
return errors.New("unable to read handshake message type, length, and random")
|
||||
}
|
||||
|
||||
if handshakeType != typeClientHello {
|
||||
return errors.New("handshake message is not a ClientHello")
|
||||
}
|
||||
|
||||
chs.TLSVersMin = recordVersion
|
||||
chs.TLSVersMax = handshakeVersion
|
||||
|
||||
var ignoredSessionID cryptobyte.String
|
||||
if !s.ReadUint8LengthPrefixed(&ignoredSessionID) {
|
||||
return errors.New("unable to read session id")
|
||||
}
|
||||
|
||||
// CipherSuites
|
||||
var cipherSuitesBytes cryptobyte.String
|
||||
if !s.ReadUint16LengthPrefixed(&cipherSuitesBytes) {
|
||||
return errors.New("unable to read ciphersuites")
|
||||
}
|
||||
|
||||
if err := chs.ReadCipherSuites(cipherSuitesBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// CompressionMethods
|
||||
var compressionMethods cryptobyte.String
|
||||
if !s.ReadUint8LengthPrefixed(&compressionMethods) {
|
||||
return errors.New("unable to read compression methods")
|
||||
}
|
||||
|
||||
if err := chs.ReadCompressionMethods(compressionMethods); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.Empty() {
|
||||
// Extensions are optional
|
||||
return nil
|
||||
}
|
||||
|
||||
var extensions cryptobyte.String
|
||||
if !s.ReadUint16LengthPrefixed(&extensions) {
|
||||
return errors.New("unable to read extensions data")
|
||||
}
|
||||
|
||||
if err := chs.ReadTLSExtensions(extensions, bluntMimicry, realPSK); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if extension list includes padding, we update the padding-to-len according to
|
||||
// the raw ClientHello length
|
||||
for _, ext := range chs.Extensions {
|
||||
if _, ok := ext.(*UtlsPaddingExtension); ok {
|
||||
ext.(*UtlsPaddingExtension).GetPaddingLen = AlwaysPadToLen(len(raw) - 5)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals a ClientHello message in the form of JSON into a ClientHelloSpec.
|
||||
func (chs *ClientHelloSpec) UnmarshalJSON(jsonB []byte) error {
|
||||
var chsju ClientHelloSpecJSONUnmarshaler
|
||||
if err := json.Unmarshal(jsonB, &chsju); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*chs = chsju.ClientHelloSpec()
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
// HelloGolang will use default "crypto/tls" handshake marshaling codepath, which WILL
|
||||
// overwrite your changes to Hello(Config, Session are fine).
|
||||
// You might want to call BuildHandshakeState() before applying any changes.
|
||||
// UConn.Extensions will be completely ignored.
|
||||
HelloGolang = ClientHelloID{helloGolang, helloAutoVers, nil, nil}
|
||||
|
||||
// HelloCustom will prepare ClientHello with empty uconn.Extensions so you can fill it with
|
||||
// TLSExtensions manually or use ApplyPreset function
|
||||
HelloCustom = ClientHelloID{helloCustom, helloAutoVers, nil, nil}
|
||||
|
||||
// HelloRandomized* randomly adds/reorders extensions, ciphersuites, etc.
|
||||
HelloRandomized = ClientHelloID{helloRandomized, helloAutoVers, nil, nil}
|
||||
HelloRandomizedALPN = ClientHelloID{helloRandomizedALPN, helloAutoVers, nil, nil}
|
||||
HelloRandomizedNoALPN = ClientHelloID{helloRandomizedNoALPN, helloAutoVers, nil, nil}
|
||||
|
||||
// The rest will will parrot given browser.
|
||||
HelloFirefox_Auto = HelloFirefox_120
|
||||
HelloFirefox_55 = ClientHelloID{helloFirefox, "55", nil, nil}
|
||||
HelloFirefox_56 = ClientHelloID{helloFirefox, "56", nil, nil}
|
||||
HelloFirefox_63 = ClientHelloID{helloFirefox, "63", nil, nil}
|
||||
HelloFirefox_65 = ClientHelloID{helloFirefox, "65", nil, nil}
|
||||
HelloFirefox_99 = ClientHelloID{helloFirefox, "99", nil, nil}
|
||||
HelloFirefox_102 = ClientHelloID{helloFirefox, "102", nil, nil}
|
||||
HelloFirefox_105 = ClientHelloID{helloFirefox, "105", nil, nil}
|
||||
HelloFirefox_120 = ClientHelloID{helloFirefox, "120", nil, nil}
|
||||
|
||||
HelloChrome_Auto = HelloChrome_133
|
||||
HelloChrome_58 = ClientHelloID{helloChrome, "58", nil, nil}
|
||||
HelloChrome_62 = ClientHelloID{helloChrome, "62", nil, nil}
|
||||
HelloChrome_70 = ClientHelloID{helloChrome, "70", nil, nil}
|
||||
HelloChrome_72 = ClientHelloID{helloChrome, "72", nil, nil}
|
||||
HelloChrome_83 = ClientHelloID{helloChrome, "83", nil, nil}
|
||||
HelloChrome_87 = ClientHelloID{helloChrome, "87", nil, nil}
|
||||
HelloChrome_96 = ClientHelloID{helloChrome, "96", nil, nil}
|
||||
HelloChrome_100 = ClientHelloID{helloChrome, "100", nil, nil}
|
||||
HelloChrome_102 = ClientHelloID{helloChrome, "102", nil, nil}
|
||||
HelloChrome_106_Shuffle = ClientHelloID{helloChrome, "106", nil, nil} // TLS Extension shuffler enabled starting from 106
|
||||
|
||||
// Chrome w/ PSK: Chrome start sending this ClientHello after doing TLS 1.3 handshake with the same server.
|
||||
// Beta: PSK extension added. However, uTLS doesn't ship with full PSK support.
|
||||
// Use at your own discretion.
|
||||
HelloChrome_100_PSK = ClientHelloID{helloChrome, "100_PSK", nil, nil}
|
||||
HelloChrome_112_PSK_Shuf = ClientHelloID{helloChrome, "112_PSK", nil, nil}
|
||||
HelloChrome_114_Padding_PSK_Shuf = ClientHelloID{helloChrome, "114_PSK", nil, nil}
|
||||
|
||||
// Chrome w/ Post-Quantum Key Agreement
|
||||
// Beta: PQ extension added. However, uTLS doesn't ship with full PQ support. Use at your own discretion.
|
||||
HelloChrome_115_PQ = ClientHelloID{helloChrome, "115_PQ", nil, nil}
|
||||
HelloChrome_115_PQ_PSK = ClientHelloID{helloChrome, "115_PQ_PSK", nil, nil}
|
||||
|
||||
// Chrome ECH
|
||||
HelloChrome_120 = ClientHelloID{helloChrome, "120", nil, nil}
|
||||
// Chrome w/ Post-Quantum Key Agreement and Encrypted ClientHello
|
||||
HelloChrome_120_PQ = ClientHelloID{helloChrome, "120_PQ", nil, nil}
|
||||
// Chrome w/ ML-KEM curve
|
||||
HelloChrome_131 = ClientHelloID{helloChrome, "131", nil, nil}
|
||||
// Chrome w/ New ALPS codepoint
|
||||
HelloChrome_133 = ClientHelloID{helloChrome, "133", nil, nil}
|
||||
|
||||
HelloIOS_Auto = HelloIOS_14
|
||||
HelloIOS_11_1 = ClientHelloID{helloIOS, "111", nil, nil} // legacy "111" means 11.1
|
||||
HelloIOS_12_1 = ClientHelloID{helloIOS, "12.1", nil, nil}
|
||||
HelloIOS_13 = ClientHelloID{helloIOS, "13", nil, nil}
|
||||
HelloIOS_14 = ClientHelloID{helloIOS, "14", nil, nil}
|
||||
|
||||
HelloAndroid_11_OkHttp = ClientHelloID{helloAndroid, "11", nil, nil}
|
||||
|
||||
HelloEdge_Auto = HelloEdge_85 // HelloEdge_106 seems to be incompatible with this library
|
||||
HelloEdge_85 = ClientHelloID{helloEdge, "85", nil, nil}
|
||||
HelloEdge_106 = ClientHelloID{helloEdge, "106", nil, nil}
|
||||
|
||||
HelloSafari_Auto = HelloSafari_16_0
|
||||
HelloSafari_16_0 = ClientHelloID{helloSafari, "16.0", nil, nil}
|
||||
|
||||
Hello360_Auto = Hello360_7_5 // Hello360_11_0 seems to be incompatible with this library
|
||||
Hello360_7_5 = ClientHelloID{hello360, "7.5", nil, nil}
|
||||
Hello360_11_0 = ClientHelloID{hello360, "11.0", nil, nil}
|
||||
|
||||
HelloQQ_Auto = HelloQQ_11_1
|
||||
HelloQQ_11_1 = ClientHelloID{helloQQ, "11.1", nil, nil}
|
||||
)
|
||||
|
||||
type Weights struct {
|
||||
Extensions_Append_ALPN float64
|
||||
TLSVersMax_Set_VersionTLS13 float64
|
||||
CipherSuites_Remove_RandomCiphers float64
|
||||
SigAndHashAlgos_Append_ECDSAWithSHA1 float64
|
||||
SigAndHashAlgos_Append_ECDSAWithP521AndSHA512 float64
|
||||
SigAndHashAlgos_Append_PSSWithSHA256 float64
|
||||
SigAndHashAlgos_Append_PSSWithSHA384_PSSWithSHA512 float64
|
||||
CurveIDs_Append_X25519 float64
|
||||
CurveIDs_Append_CurveP521 float64
|
||||
Extensions_Append_Padding float64
|
||||
Extensions_Append_Status float64
|
||||
Extensions_Append_SCT float64
|
||||
Extensions_Append_Reneg float64
|
||||
Extensions_Append_EMS float64
|
||||
FirstKeyShare_Set_CurveP256 float64
|
||||
KeyShare_Append_RandomGroups float64
|
||||
Extensions_Append_ALPS float64
|
||||
}
|
||||
|
||||
// Do not modify them directly as they may being used. If you
|
||||
// want to use your custom weights, please make a copy first.
|
||||
var DefaultWeights = Weights{
|
||||
Extensions_Append_ALPN: 0.7,
|
||||
TLSVersMax_Set_VersionTLS13: 0.4,
|
||||
CipherSuites_Remove_RandomCiphers: 0.4,
|
||||
SigAndHashAlgos_Append_ECDSAWithSHA1: 0.63,
|
||||
SigAndHashAlgos_Append_ECDSAWithP521AndSHA512: 0.59,
|
||||
SigAndHashAlgos_Append_PSSWithSHA256: 0.51,
|
||||
SigAndHashAlgos_Append_PSSWithSHA384_PSSWithSHA512: 0.9,
|
||||
CurveIDs_Append_X25519: 0.71,
|
||||
CurveIDs_Append_CurveP521: 0.46,
|
||||
Extensions_Append_Padding: 0.62,
|
||||
Extensions_Append_Status: 0.74,
|
||||
Extensions_Append_SCT: 0.46,
|
||||
Extensions_Append_Reneg: 0.75,
|
||||
Extensions_Append_EMS: 0.77,
|
||||
FirstKeyShare_Set_CurveP256: 0.00, // legacy setting
|
||||
KeyShare_Append_RandomGroups: 0.50,
|
||||
Extensions_Append_ALPS: 0.33,
|
||||
}
|
||||
|
||||
// based on spec's GreaseStyle, GREASE_PLACEHOLDER may be replaced by another GREASE value
|
||||
// https://tools.ietf.org/html/draft-ietf-tls-grease-01
|
||||
const GREASE_PLACEHOLDER = 0x0a0a
|
||||
|
||||
func isGREASEUint16(v uint16) bool {
|
||||
// First byte is same as second byte
|
||||
// and lowest nibble is 0xa
|
||||
return ((v >> 8) == v&0xff) && v&0xf == 0xa
|
||||
}
|
||||
|
||||
func unGREASEUint16(v uint16) uint16 {
|
||||
if isGREASEUint16(v) {
|
||||
return GREASE_PLACEHOLDER
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// utlsMacSHA384 returns a SHA-384 based MAC. These are only supported in TLS 1.2
|
||||
// so the given version is ignored.
|
||||
func utlsMacSHA384(key []byte) hash.Hash {
|
||||
return hmac.New(sha512.New384, key)
|
||||
}
|
||||
|
||||
var utlsSupportedCipherSuites []*cipherSuite
|
||||
|
||||
func init() {
|
||||
utlsSupportedCipherSuites = append(cipherSuites, []*cipherSuite{
|
||||
{OLD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 32, 0, 12, ecdheRSAKA,
|
||||
suiteECDHE | suiteTLS12, nil, nil, aeadChaCha20Poly1305},
|
||||
{OLD_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 32, 0, 12, ecdheECDSAKA,
|
||||
suiteECDHE | suiteECSign | suiteTLS12, nil, nil, aeadChaCha20Poly1305},
|
||||
}...)
|
||||
}
|
||||
|
||||
// EnableWeakCiphers allows utls connections to continue in some cases, when weak cipher was chosen.
|
||||
// This provides better compatibility with servers on the web, but weakens security. Feel free
|
||||
// to use this option if you establish additional secure connection inside of utls connection.
|
||||
// This option does not change the shape of parrots (i.e. same ciphers will be offered either way).
|
||||
// Must be called before establishing any connections.
|
||||
func EnableWeakCiphers() {
|
||||
utlsSupportedCipherSuites = append(cipherSuites, []*cipherSuite{
|
||||
{DISABLED_TLS_RSA_WITH_AES_256_CBC_SHA256, 32, 32, 16, rsaKA,
|
||||
suiteTLS12, cipherAES, macSHA256, nil},
|
||||
|
||||
{DISABLED_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, 32, 48, 16, ecdheECDSAKA,
|
||||
suiteECDHE | suiteECSign | suiteTLS12 | suiteSHA384, cipherAES, utlsMacSHA384, nil},
|
||||
{DISABLED_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, 32, 48, 16, ecdheRSAKA,
|
||||
suiteECDHE | suiteTLS12 | suiteSHA384, cipherAES, utlsMacSHA384, nil},
|
||||
}...)
|
||||
}
|
||||
|
||||
func mapSlice[T any, U any](slice []T, transform func(T) U) []U {
|
||||
newSlice := make([]U, 0, len(slice))
|
||||
for _, t := range slice {
|
||||
newSlice = append(newSlice, transform(t))
|
||||
}
|
||||
return newSlice
|
||||
}
|
||||
|
||||
func panicOnNil(caller string, params ...any) {
|
||||
for i, p := range params {
|
||||
if p == nil {
|
||||
panic(fmt.Sprintf("tls: %s failed: the [%d] parameter is nil", caller, i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func anyTrue[T any](slice []T, predicate func(i int, t *T) bool) bool {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
if predicate(i, &slice[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func allTrue[T any](slice []T, predicate func(i int, t *T) bool) bool {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
if !predicate(i, &slice[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func uAssert(condition bool, msg string) {
|
||||
if !condition {
|
||||
panic(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func sliceEq[T comparable](sliceA []T, sliceB []T) bool {
|
||||
if len(sliceA) != len(sliceB) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(sliceA); i++ {
|
||||
if sliceA[i] != sliceB[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type Initializable interface {
|
||||
// IsInitialized returns a boolean indicating whether the extension has been initialized.
|
||||
// If false is returned, utls will initialize the extension.
|
||||
IsInitialized() bool
|
||||
}
|
||||
+984
@@ -0,0 +1,984 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"net"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
type ClientHelloBuildStatus int
|
||||
|
||||
const NotBuilt ClientHelloBuildStatus = 0
|
||||
const BuildByUtls ClientHelloBuildStatus = 1
|
||||
const BuildByGoTLS ClientHelloBuildStatus = 2
|
||||
|
||||
type UConn struct {
|
||||
*Conn
|
||||
|
||||
Extensions []TLSExtension
|
||||
ClientHelloID ClientHelloID
|
||||
sessionController *sessionController
|
||||
|
||||
clientHelloBuildStatus ClientHelloBuildStatus
|
||||
clientHelloSpec *ClientHelloSpec
|
||||
|
||||
HandshakeState PubClientHandshakeState
|
||||
|
||||
greaseSeed [ssl_grease_last_index]uint16
|
||||
|
||||
omitSNIExtension bool
|
||||
|
||||
// skipResumptionOnNilExtension is copied from `Config.PreferSkipResumptionOnNilExtension`.
|
||||
//
|
||||
// By default, if ClientHelloSpec is predefined or utls-generated (as opposed to HelloCustom), this flag will be updated to true.
|
||||
skipResumptionOnNilExtension bool
|
||||
|
||||
// certCompressionAlgs represents the set of advertised certificate compression
|
||||
// algorithms, as specified in the ClientHello. This is only relevant client-side, for the
|
||||
// server certificate. All other forms of certificate compression are unsupported.
|
||||
certCompressionAlgs []CertCompressionAlgo
|
||||
|
||||
// ech extension is a shortcut to the ECH extension in the Extensions slice if there is one.
|
||||
ech ECHExtension
|
||||
|
||||
// echCtx is the echContex returned by makeClientHello()
|
||||
echCtx *echClientContext
|
||||
}
|
||||
|
||||
// UClient returns a new uTLS client, with behavior depending on clientHelloID.
|
||||
// Config CAN be nil, but make sure to eventually specify ServerName.
|
||||
func UClient(conn net.Conn, config *Config, clientHelloID ClientHelloID) *UConn {
|
||||
if config == nil {
|
||||
config = &Config{}
|
||||
}
|
||||
tlsConn := Conn{conn: conn, config: config, isClient: true}
|
||||
handshakeState := PubClientHandshakeState{C: &tlsConn, Hello: &PubClientHelloMsg{}}
|
||||
uconn := UConn{Conn: &tlsConn, ClientHelloID: clientHelloID, HandshakeState: handshakeState}
|
||||
uconn.HandshakeState.uconn = &uconn
|
||||
uconn.handshakeFn = uconn.clientHandshake
|
||||
uconn.sessionController = newSessionController(&uconn)
|
||||
uconn.utls.sessionController = uconn.sessionController
|
||||
uconn.skipResumptionOnNilExtension = config.PreferSkipResumptionOnNilExtension || clientHelloID.Client != helloCustom
|
||||
return &uconn
|
||||
}
|
||||
|
||||
// BuildHandshakeState behavior varies based on ClientHelloID and
|
||||
// whether it was already called before.
|
||||
// If HelloGolang:
|
||||
//
|
||||
// [only once] make default ClientHello and overwrite existing state
|
||||
//
|
||||
// If any other mimicking ClientHelloID is used:
|
||||
//
|
||||
// [only once] make ClientHello based on ID and overwrite existing state
|
||||
// [each call] apply uconn.Extensions config to internal crypto/tls structures
|
||||
// [each call] marshal ClientHello.
|
||||
//
|
||||
// BuildHandshakeState is automatically called before uTLS performs handshake,
|
||||
// and should only be called explicitly to inspect/change fields of
|
||||
// default/mimicked ClientHello.
|
||||
// With the excpetion of session ticket and psk extensions, which cannot be changed
|
||||
// after calling BuildHandshakeState, all other fields can be modified.
|
||||
func (uconn *UConn) BuildHandshakeState() error {
|
||||
return uconn.buildHandshakeState(true)
|
||||
}
|
||||
|
||||
// BuildHandshakeStateWithoutSession is the same as BuildHandshakeState, but does not
|
||||
// set the session. This is only useful when you want to inspect the ClientHello before
|
||||
// setting the session manually through SetSessionTicketExtension or SetPSKExtension.
|
||||
// BuildHandshakeState is automatically called before uTLS performs handshake.
|
||||
func (uconn *UConn) BuildHandshakeStateWithoutSession() error {
|
||||
return uconn.buildHandshakeState(false)
|
||||
}
|
||||
|
||||
func (uconn *UConn) buildHandshakeState(loadSession bool) error {
|
||||
if uconn.ClientHelloID == HelloGolang {
|
||||
if uconn.clientHelloBuildStatus == BuildByGoTLS {
|
||||
return nil
|
||||
}
|
||||
uAssert(uconn.clientHelloBuildStatus == NotBuilt, "BuildHandshakeState failed: invalid call, client hello has already been built by utls")
|
||||
|
||||
// use default Golang ClientHello.
|
||||
hello, keySharePrivate, ech, err := uconn.makeClientHello()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uconn.HandshakeState.Hello = hello.getPublicPtr()
|
||||
uconn.HandshakeState.State13.KeyShareKeys = keySharePrivate.ToPublic()
|
||||
uconn.HandshakeState.C = uconn.Conn
|
||||
uconn.echCtx = ech
|
||||
uconn.clientHelloBuildStatus = BuildByGoTLS
|
||||
} else {
|
||||
uAssert(uconn.clientHelloBuildStatus == BuildByUtls || uconn.clientHelloBuildStatus == NotBuilt, "BuildHandshakeState failed: invalid call, client hello has already been built by go-tls")
|
||||
if uconn.clientHelloBuildStatus == NotBuilt {
|
||||
err := uconn.applyPresetByID(uconn.ClientHelloID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if uconn.omitSNIExtension {
|
||||
uconn.removeSNIExtension()
|
||||
}
|
||||
}
|
||||
|
||||
err := uconn.ApplyConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if loadSession {
|
||||
err = uconn.uLoadSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = uconn.MarshalClientHello()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if loadSession {
|
||||
uconn.uApplyPatch()
|
||||
uconn.sessionController.finalCheck()
|
||||
uconn.clientHelloBuildStatus = BuildByUtls
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uconn *UConn) uLoadSession() error {
|
||||
if cfg := uconn.config; cfg.SessionTicketsDisabled || cfg.ClientSessionCache == nil {
|
||||
return nil
|
||||
}
|
||||
switch uconn.sessionController.shouldLoadSession() {
|
||||
case shouldReturn:
|
||||
case shouldSetTicket:
|
||||
uconn.sessionController.setSessionTicketToUConn()
|
||||
case shouldSetPsk:
|
||||
uconn.sessionController.setPskToUConn()
|
||||
case shouldLoad:
|
||||
hello := uconn.HandshakeState.Hello.getPrivatePtr()
|
||||
uconn.sessionController.utlsAboutToLoadSession()
|
||||
session, earlySecret, binderKey, err := uconn.loadSession(hello)
|
||||
if session == nil || err != nil {
|
||||
return err
|
||||
}
|
||||
if session.version == VersionTLS12 {
|
||||
// We use the session ticket extension for tls 1.2 session resumption
|
||||
uconn.sessionController.initSessionTicketExt(session, hello.sessionTicket)
|
||||
uconn.sessionController.setSessionTicketToUConn()
|
||||
} else {
|
||||
uconn.sessionController.initPskExt(session, earlySecret, binderKey, hello.pskIdentities)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uconn *UConn) uApplyPatch() {
|
||||
helloLen := len(uconn.HandshakeState.Hello.Raw)
|
||||
if uconn.sessionController.shouldUpdateBinders() {
|
||||
uconn.sessionController.updateBinders()
|
||||
uconn.sessionController.setPskToUConn()
|
||||
}
|
||||
uAssert(helloLen == len(uconn.HandshakeState.Hello.Raw), "tls: uApplyPatch Failed: the patch should never change the length of the marshaled clientHello")
|
||||
}
|
||||
|
||||
func (uconn *UConn) DidTls12Resume() bool {
|
||||
return uconn.didResume
|
||||
}
|
||||
|
||||
// SetSessionState sets the session ticket, which may be preshared or fake.
|
||||
// If session is nil, the body of session ticket extension will be unset,
|
||||
// but the extension itself still MAY be present for mimicking purposes.
|
||||
// Session tickets to be reused - use same cache on following connections.
|
||||
//
|
||||
// Deprecated: This method is deprecated in favor of SetSessionTicketExtension,
|
||||
// as it only handles session override of TLS 1.2
|
||||
func (uconn *UConn) SetSessionState(session *ClientSessionState) error {
|
||||
sessionTicketExt := &SessionTicketExtension{Initialized: true}
|
||||
if session != nil {
|
||||
sessionTicketExt.Ticket = session.session.ticket
|
||||
sessionTicketExt.Session = session.session
|
||||
}
|
||||
return uconn.SetSessionTicketExtension(sessionTicketExt)
|
||||
}
|
||||
|
||||
// SetSessionTicket sets the session ticket extension.
|
||||
// If extension is nil, this will be a no-op.
|
||||
func (uconn *UConn) SetSessionTicketExtension(sessionTicketExt ISessionTicketExtension) error {
|
||||
if uconn.config.SessionTicketsDisabled || uconn.config.ClientSessionCache == nil {
|
||||
return fmt.Errorf("tls: SetSessionTicketExtension failed: session is disabled")
|
||||
}
|
||||
if sessionTicketExt == nil {
|
||||
return nil
|
||||
}
|
||||
return uconn.sessionController.overrideSessionTicketExt(sessionTicketExt)
|
||||
}
|
||||
|
||||
// SetPskExtension sets the psk extension for tls 1.3 resumption. This is a no-op if the psk is nil.
|
||||
func (uconn *UConn) SetPskExtension(pskExt PreSharedKeyExtension) error {
|
||||
if uconn.config.SessionTicketsDisabled || uconn.config.ClientSessionCache == nil {
|
||||
return fmt.Errorf("tls: SetPskExtension failed: session is disabled")
|
||||
}
|
||||
if pskExt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
uconn.HandshakeState.Hello.TicketSupported = true
|
||||
return uconn.sessionController.overridePskExt(pskExt)
|
||||
}
|
||||
|
||||
// If you want session tickets to be reused - use same cache on following connections
|
||||
func (uconn *UConn) SetSessionCache(cache ClientSessionCache) {
|
||||
uconn.config.ClientSessionCache = cache
|
||||
uconn.HandshakeState.Hello.TicketSupported = true
|
||||
}
|
||||
|
||||
// SetClientRandom sets client random explicitly.
|
||||
// BuildHandshakeFirst() must be called before SetClientRandom.
|
||||
// r must to be 32 bytes long.
|
||||
func (uconn *UConn) SetClientRandom(r []byte) error {
|
||||
if len(r) != 32 {
|
||||
return errors.New("Incorrect client random length! Expected: 32, got: " + strconv.Itoa(len(r)))
|
||||
} else {
|
||||
uconn.HandshakeState.Hello.Random = make([]byte, 32)
|
||||
copy(uconn.HandshakeState.Hello.Random, r)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (uconn *UConn) SetSNI(sni string) {
|
||||
hname := hostnameInSNI(sni)
|
||||
uconn.config.ServerName = hname
|
||||
for _, ext := range uconn.Extensions {
|
||||
sniExt, ok := ext.(*SNIExtension)
|
||||
if ok {
|
||||
sniExt.ServerName = hname
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveSNIExtension removes SNI from the list of extensions sent in ClientHello
|
||||
// It returns an error when used with HelloGolang ClientHelloID
|
||||
func (uconn *UConn) RemoveSNIExtension() error {
|
||||
if uconn.ClientHelloID == HelloGolang {
|
||||
return fmt.Errorf("cannot call RemoveSNIExtension on a UConn with a HelloGolang ClientHelloID")
|
||||
}
|
||||
uconn.omitSNIExtension = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uconn *UConn) removeSNIExtension() {
|
||||
filteredExts := make([]TLSExtension, 0, len(uconn.Extensions))
|
||||
for _, e := range uconn.Extensions {
|
||||
if _, ok := e.(*SNIExtension); !ok {
|
||||
filteredExts = append(filteredExts, e)
|
||||
}
|
||||
}
|
||||
uconn.Extensions = filteredExts
|
||||
}
|
||||
|
||||
// Handshake runs the client handshake using given clientHandshakeState
|
||||
// Requires hs.hello, and, optionally, hs.session to be set.
|
||||
func (c *UConn) Handshake() error {
|
||||
return c.HandshakeContext(context.Background())
|
||||
}
|
||||
|
||||
// HandshakeContext runs the client or server handshake
|
||||
// protocol if it has not yet been run.
|
||||
//
|
||||
// The provided Context must be non-nil. If the context is canceled before
|
||||
// the handshake is complete, the handshake is interrupted and an error is returned.
|
||||
// Once the handshake has completed, cancellation of the context will not affect the
|
||||
// connection.
|
||||
func (c *UConn) HandshakeContext(ctx context.Context) error {
|
||||
// Delegate to unexported method for named return
|
||||
// without confusing documented signature.
|
||||
return c.handshakeContext(ctx)
|
||||
}
|
||||
|
||||
func (c *UConn) handshakeContext(ctx context.Context) (ret error) {
|
||||
// Fast sync/atomic-based exit if there is no handshake in flight and the
|
||||
// last one succeeded without an error. Avoids the expensive context setup
|
||||
// and mutex for most Read and Write calls.
|
||||
if c.isHandshakeComplete.Load() {
|
||||
return nil
|
||||
}
|
||||
|
||||
handshakeCtx, cancel := context.WithCancel(ctx)
|
||||
// Note: defer this before starting the "interrupter" goroutine
|
||||
// so that we can tell the difference between the input being canceled and
|
||||
// this cancellation. In the former case, we need to close the connection.
|
||||
defer cancel()
|
||||
|
||||
// Start the "interrupter" goroutine, if this context might be canceled.
|
||||
// (The background context cannot).
|
||||
//
|
||||
// The interrupter goroutine waits for the input context to be done and
|
||||
// closes the connection if this happens before the function returns.
|
||||
if c.quic != nil {
|
||||
c.quic.cancelc = handshakeCtx.Done()
|
||||
c.quic.cancel = cancel
|
||||
} else if ctx.Done() != nil {
|
||||
done := make(chan struct{})
|
||||
interruptRes := make(chan error, 1)
|
||||
defer func() {
|
||||
close(done)
|
||||
if ctxErr := <-interruptRes; ctxErr != nil {
|
||||
// Return context error to user.
|
||||
ret = ctxErr
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-handshakeCtx.Done():
|
||||
// Close the connection, discarding the error
|
||||
_ = c.conn.Close()
|
||||
interruptRes <- handshakeCtx.Err()
|
||||
case <-done:
|
||||
interruptRes <- nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
c.handshakeMutex.Lock()
|
||||
defer c.handshakeMutex.Unlock()
|
||||
|
||||
if err := c.handshakeErr; err != nil {
|
||||
return err
|
||||
}
|
||||
if c.isHandshakeComplete.Load() {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.in.Lock()
|
||||
defer c.in.Unlock()
|
||||
|
||||
// [uTLS section begins]
|
||||
if c.isClient {
|
||||
err := c.BuildHandshakeState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// [uTLS section ends]
|
||||
c.handshakeErr = c.handshakeFn(handshakeCtx)
|
||||
if c.handshakeErr == nil {
|
||||
c.handshakes++
|
||||
} else {
|
||||
// If an error occurred during the hadshake try to flush the
|
||||
// alert that might be left in the buffer.
|
||||
c.flush()
|
||||
}
|
||||
|
||||
if c.handshakeErr == nil && !c.isHandshakeComplete.Load() {
|
||||
c.handshakeErr = errors.New("tls: internal error: handshake should have had a result")
|
||||
}
|
||||
if c.handshakeErr != nil && c.isHandshakeComplete.Load() {
|
||||
panic("tls: internal error: handshake returned an error but is marked successful")
|
||||
}
|
||||
|
||||
if c.quic != nil {
|
||||
if c.handshakeErr == nil {
|
||||
c.quicHandshakeComplete()
|
||||
// Provide the 1-RTT read secret now that the handshake is complete.
|
||||
// The QUIC layer MUST NOT decrypt 1-RTT packets prior to completing
|
||||
// the handshake (RFC 9001, Section 5.7).
|
||||
c.quicSetReadSecret(QUICEncryptionLevelApplication, c.cipherSuite, c.in.trafficSecret)
|
||||
} else {
|
||||
var a alert
|
||||
c.out.Lock()
|
||||
if !errors.As(c.out.err, &a) {
|
||||
a = alertInternalError
|
||||
}
|
||||
c.out.Unlock()
|
||||
// Return an error which wraps both the handshake error and
|
||||
// any alert error we may have sent, or alertInternalError
|
||||
// if we didn't send an alert.
|
||||
// Truncate the text of the alert to 0 characters.
|
||||
c.handshakeErr = fmt.Errorf("%w%.0w", c.handshakeErr, AlertError(a))
|
||||
}
|
||||
close(c.quic.blockedc)
|
||||
close(c.quic.signalc)
|
||||
}
|
||||
|
||||
return c.handshakeErr
|
||||
}
|
||||
|
||||
// Copy-pasted from tls.Conn in its entirety. But c.Handshake() is now utls' one, not tls.
|
||||
// Write writes data to the connection.
|
||||
func (c *UConn) Write(b []byte) (int, error) {
|
||||
// interlock with Close below
|
||||
for {
|
||||
x := c.activeCall.Load()
|
||||
if x&1 != 0 {
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
if c.activeCall.CompareAndSwap(x, x+2) {
|
||||
defer c.activeCall.Add(-2)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.Handshake(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
c.out.Lock()
|
||||
defer c.out.Unlock()
|
||||
|
||||
if err := c.out.err; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !c.isHandshakeComplete.Load() {
|
||||
return 0, alertInternalError
|
||||
}
|
||||
|
||||
if c.closeNotifySent {
|
||||
return 0, errShutdown
|
||||
}
|
||||
|
||||
// SSL 3.0 and TLS 1.0 are susceptible to a chosen-plaintext
|
||||
// attack when using block mode ciphers due to predictable IVs.
|
||||
// This can be prevented by splitting each Application Data
|
||||
// record into two records, effectively randomizing the IV.
|
||||
//
|
||||
// https://www.openssl.org/~bodo/tls-cbc.txt
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=665814
|
||||
// https://www.imperialviolet.org/2012/01/15/beastfollowup.html
|
||||
|
||||
var m int
|
||||
if len(b) > 1 && c.vers <= VersionTLS10 {
|
||||
if _, ok := c.out.cipher.(cipher.BlockMode); ok {
|
||||
n, err := c.writeRecordLocked(recordTypeApplicationData, b[:1])
|
||||
if err != nil {
|
||||
return n, c.out.setErrorLocked(err)
|
||||
}
|
||||
m, b = 1, b[1:]
|
||||
}
|
||||
}
|
||||
|
||||
n, err := c.writeRecordLocked(recordTypeApplicationData, b)
|
||||
return n + m, c.out.setErrorLocked(err)
|
||||
}
|
||||
|
||||
func (uconn *UConn) ApplyConfig() error {
|
||||
for _, ext := range uconn.Extensions {
|
||||
err := ext.writeToUConn(uconn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uconn *UConn) extensionsList() []uint16 {
|
||||
|
||||
outerExts := []uint16{}
|
||||
for _, ext := range uconn.Extensions {
|
||||
buffer := cryptobyte.String(make([]byte, 2000))
|
||||
ext.Read(buffer)
|
||||
var extension uint16
|
||||
buffer.ReadUint16(&extension)
|
||||
outerExts = append(outerExts, extension)
|
||||
}
|
||||
return outerExts
|
||||
}
|
||||
|
||||
func (uconn *UConn) computeAndUpdateOuterECHExtension(inner *clientHelloMsg, ech *echClientContext, useKey bool) error {
|
||||
// This function is mostly copied from
|
||||
// https://github.com/refraction-networking/utls/blob/e430876b1d82fdf582efc57f3992d448e7ab3d8a/ech.go#L408
|
||||
var encapKey []byte
|
||||
if useKey {
|
||||
encapKey = ech.encapsulatedKey
|
||||
}
|
||||
|
||||
encodedInner, err := encodeInnerClientHelloReorderOuterExts(inner, int(ech.config.MaxNameLength), uconn.extensionsList())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedLen := len(encodedInner) + 16
|
||||
outerECHExt, err := generateOuterECHExt(ech.config.ConfigID, ech.kdfID, ech.aeadID, encapKey, make([]byte, encryptedLen))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
echExtIdx := slices.IndexFunc(uconn.Extensions, func(ext TLSExtension) bool {
|
||||
_, ok := ext.(EncryptedClientHelloExtension)
|
||||
return ok
|
||||
})
|
||||
if echExtIdx < 0 {
|
||||
return fmt.Errorf("extension satisfying EncryptedClientHelloExtension not present")
|
||||
}
|
||||
oldExt := uconn.Extensions[echExtIdx]
|
||||
|
||||
uconn.Extensions[echExtIdx] = &GenericExtension{
|
||||
Id: extensionEncryptedClientHello,
|
||||
Data: outerECHExt,
|
||||
}
|
||||
|
||||
if err := uconn.MarshalClientHelloNoECH(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serializedOuter := uconn.HandshakeState.Hello.Raw
|
||||
serializedOuter = serializedOuter[4:]
|
||||
encryptedInner, err := ech.hpkeContext.Seal(serializedOuter, encodedInner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outerECHExt, err = generateOuterECHExt(ech.config.ConfigID, ech.kdfID, ech.aeadID, encapKey, encryptedInner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uconn.Extensions[echExtIdx] = &GenericExtension{
|
||||
Id: extensionEncryptedClientHello,
|
||||
Data: outerECHExt,
|
||||
}
|
||||
|
||||
if err := uconn.MarshalClientHelloNoECH(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uconn.Extensions[echExtIdx] = oldExt
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (uconn *UConn) MarshalClientHello() error {
|
||||
if len(uconn.config.EncryptedClientHelloConfigList) > 0 {
|
||||
inner, _, ech, err := uconn.makeClientHello()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copy compressed extensions to the ClientHelloInner
|
||||
inner.keyShares = KeyShares(uconn.HandshakeState.Hello.KeyShares).ToPrivate()
|
||||
inner.supportedSignatureAlgorithms = uconn.HandshakeState.Hello.SupportedSignatureAlgorithms
|
||||
inner.sessionId = uconn.HandshakeState.Hello.SessionId
|
||||
inner.supportedCurves = uconn.HandshakeState.Hello.SupportedCurves
|
||||
|
||||
ech.innerHello = inner
|
||||
|
||||
uconn.computeAndUpdateOuterECHExtension(inner, ech, true)
|
||||
|
||||
uconn.echCtx = ech
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := uconn.MarshalClientHelloNoECH(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// MarshalClientHelloNoECH marshals ClientHello as if there was no
|
||||
// ECH extension present.
|
||||
func (uconn *UConn) MarshalClientHelloNoECH() error {
|
||||
hello := uconn.HandshakeState.Hello
|
||||
headerLength := 2 + 32 + 1 + len(hello.SessionId) +
|
||||
2 + len(hello.CipherSuites)*2 +
|
||||
1 + len(hello.CompressionMethods)
|
||||
|
||||
extensionsLen := 0
|
||||
var paddingExt *UtlsPaddingExtension // reference to padding extension, if present
|
||||
for _, ext := range uconn.Extensions {
|
||||
if pe, ok := ext.(*UtlsPaddingExtension); !ok {
|
||||
// If not padding - just add length of extension to total length
|
||||
extensionsLen += ext.Len()
|
||||
} else {
|
||||
// If padding - process it later
|
||||
if paddingExt == nil {
|
||||
paddingExt = pe
|
||||
} else {
|
||||
return errors.New("multiple padding extensions")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if paddingExt != nil {
|
||||
// determine padding extension presence and length
|
||||
paddingExt.Update(headerLength + 4 + extensionsLen + 2)
|
||||
extensionsLen += paddingExt.Len()
|
||||
}
|
||||
|
||||
helloLen := headerLength
|
||||
if len(uconn.Extensions) > 0 {
|
||||
helloLen += 2 + extensionsLen // 2 bytes for extensions' length
|
||||
}
|
||||
|
||||
helloBuffer := bytes.Buffer{}
|
||||
bufferedWriter := bufio.NewWriterSize(&helloBuffer, helloLen+4) // 1 byte for tls record type, 3 for length
|
||||
// We use buffered Writer to avoid checking write errors after every Write(): whenever first error happens
|
||||
// Write() will become noop, and error will be accessible via Flush(), which is called once in the end
|
||||
|
||||
binary.Write(bufferedWriter, binary.BigEndian, typeClientHello)
|
||||
helloLenBytes := []byte{byte(helloLen >> 16), byte(helloLen >> 8), byte(helloLen)} // poor man's uint24
|
||||
binary.Write(bufferedWriter, binary.BigEndian, helloLenBytes)
|
||||
binary.Write(bufferedWriter, binary.BigEndian, hello.Vers)
|
||||
|
||||
binary.Write(bufferedWriter, binary.BigEndian, hello.Random)
|
||||
|
||||
binary.Write(bufferedWriter, binary.BigEndian, uint8(len(hello.SessionId)))
|
||||
binary.Write(bufferedWriter, binary.BigEndian, hello.SessionId)
|
||||
|
||||
binary.Write(bufferedWriter, binary.BigEndian, uint16(len(hello.CipherSuites)<<1))
|
||||
for _, suite := range hello.CipherSuites {
|
||||
binary.Write(bufferedWriter, binary.BigEndian, suite)
|
||||
}
|
||||
|
||||
binary.Write(bufferedWriter, binary.BigEndian, uint8(len(hello.CompressionMethods)))
|
||||
binary.Write(bufferedWriter, binary.BigEndian, hello.CompressionMethods)
|
||||
|
||||
if len(uconn.Extensions) > 0 {
|
||||
binary.Write(bufferedWriter, binary.BigEndian, uint16(extensionsLen))
|
||||
for _, ext := range uconn.Extensions {
|
||||
if _, err := bufferedWriter.ReadFrom(ext); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := bufferedWriter.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if helloBuffer.Len() != 4+helloLen {
|
||||
return errors.New("utls: unexpected ClientHello length. Expected: " + strconv.Itoa(4+helloLen) +
|
||||
". Got: " + strconv.Itoa(helloBuffer.Len()))
|
||||
}
|
||||
|
||||
hello.Raw = helloBuffer.Bytes()
|
||||
return nil
|
||||
}
|
||||
|
||||
// get current state of cipher and encrypt zeros to get keystream
|
||||
func (uconn *UConn) GetOutKeystream(length int) ([]byte, error) {
|
||||
zeros := make([]byte, length)
|
||||
|
||||
if outCipher, ok := uconn.out.cipher.(cipher.AEAD); ok {
|
||||
// AEAD.Seal() does not mutate internal state, other ciphers might
|
||||
return outCipher.Seal(nil, uconn.out.seq[:], zeros, nil), nil
|
||||
}
|
||||
return nil, errors.New("could not convert OutCipher to cipher.AEAD")
|
||||
}
|
||||
|
||||
// SetTLSVers sets min and max TLS version in all appropriate places.
|
||||
// Function will use first non-zero version parsed in following order:
|
||||
// 1. Provided minTLSVers, maxTLSVers
|
||||
// 2. specExtensions may have SupportedVersionsExtension
|
||||
// 3. [default] min = TLS 1.0, max = TLS 1.2
|
||||
//
|
||||
// Error is only returned if things are in clearly undesirable state
|
||||
// to help user fix them.
|
||||
func (uconn *UConn) SetTLSVers(minTLSVers, maxTLSVers uint16, specExtensions []TLSExtension) error {
|
||||
if minTLSVers == 0 && maxTLSVers == 0 {
|
||||
// if version is not set explicitly in the ClientHelloSpec, check the SupportedVersions extension
|
||||
supportedVersionsExtensionsPresent := 0
|
||||
for _, e := range specExtensions {
|
||||
switch ext := e.(type) {
|
||||
case *SupportedVersionsExtension:
|
||||
findVersionsInSupportedVersionsExtensions := func(versions []uint16) (uint16, uint16) {
|
||||
// returns (minVers, maxVers)
|
||||
minVers := uint16(0)
|
||||
maxVers := uint16(0)
|
||||
for _, vers := range versions {
|
||||
if isGREASEUint16(vers) {
|
||||
continue
|
||||
}
|
||||
if maxVers < vers || maxVers == 0 {
|
||||
maxVers = vers
|
||||
}
|
||||
if minVers > vers || minVers == 0 {
|
||||
minVers = vers
|
||||
}
|
||||
}
|
||||
return minVers, maxVers
|
||||
}
|
||||
|
||||
supportedVersionsExtensionsPresent += 1
|
||||
minTLSVers, maxTLSVers = findVersionsInSupportedVersionsExtensions(ext.Versions)
|
||||
if minTLSVers == 0 && maxTLSVers == 0 {
|
||||
return fmt.Errorf("SupportedVersions extension has invalid Versions field")
|
||||
} // else: proceed
|
||||
}
|
||||
}
|
||||
switch supportedVersionsExtensionsPresent {
|
||||
case 0:
|
||||
// if mandatory for TLS 1.3 extension is not present, just default to 1.2
|
||||
minTLSVers = VersionTLS10
|
||||
maxTLSVers = VersionTLS12
|
||||
case 1:
|
||||
default:
|
||||
return fmt.Errorf("uconn.Extensions contains %v separate SupportedVersions extensions",
|
||||
supportedVersionsExtensionsPresent)
|
||||
}
|
||||
}
|
||||
|
||||
if minTLSVers < VersionTLS10 || minTLSVers > VersionTLS13 {
|
||||
return fmt.Errorf("uTLS does not support 0x%X as min version", minTLSVers)
|
||||
}
|
||||
|
||||
if maxTLSVers < VersionTLS10 || maxTLSVers > VersionTLS13 {
|
||||
return fmt.Errorf("uTLS does not support 0x%X as max version", maxTLSVers)
|
||||
}
|
||||
|
||||
uconn.HandshakeState.Hello.SupportedVersions = makeSupportedVersions(minTLSVers, maxTLSVers)
|
||||
if uconn.config.EncryptedClientHelloConfigList == nil {
|
||||
uconn.config.MinVersion = minTLSVers
|
||||
uconn.config.MaxVersion = maxTLSVers
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uconn *UConn) SetUnderlyingConn(c net.Conn) {
|
||||
uconn.Conn.conn = c
|
||||
}
|
||||
|
||||
func (uconn *UConn) GetUnderlyingConn() net.Conn {
|
||||
return uconn.Conn.conn
|
||||
}
|
||||
|
||||
// MakeConnWithCompleteHandshake allows to forge both server and client side TLS connections.
|
||||
// Major Hack Alert.
|
||||
func MakeConnWithCompleteHandshake(tcpConn net.Conn, version uint16, cipherSuite uint16, masterSecret []byte, clientRandom []byte, serverRandom []byte, isClient bool) *Conn {
|
||||
tlsConn := &Conn{conn: tcpConn, config: &Config{}, isClient: isClient}
|
||||
cs := cipherSuiteByID(cipherSuite)
|
||||
if cs != nil {
|
||||
// This is mostly borrowed from establishKeys()
|
||||
clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
|
||||
keysFromMasterSecret(version, cs, masterSecret, clientRandom, serverRandom,
|
||||
cs.macLen, cs.keyLen, cs.ivLen)
|
||||
|
||||
var clientCipher, serverCipher interface{}
|
||||
var clientHash, serverHash hash.Hash
|
||||
if cs.cipher != nil {
|
||||
clientCipher = cs.cipher(clientKey, clientIV, true /* for reading */)
|
||||
clientHash = cs.mac(clientMAC)
|
||||
serverCipher = cs.cipher(serverKey, serverIV, false /* not for reading */)
|
||||
serverHash = cs.mac(serverMAC)
|
||||
} else {
|
||||
clientCipher = cs.aead(clientKey, clientIV)
|
||||
serverCipher = cs.aead(serverKey, serverIV)
|
||||
}
|
||||
|
||||
if isClient {
|
||||
tlsConn.in.prepareCipherSpec(version, serverCipher, serverHash)
|
||||
tlsConn.out.prepareCipherSpec(version, clientCipher, clientHash)
|
||||
} else {
|
||||
tlsConn.in.prepareCipherSpec(version, clientCipher, clientHash)
|
||||
tlsConn.out.prepareCipherSpec(version, serverCipher, serverHash)
|
||||
}
|
||||
|
||||
// skip the handshake states
|
||||
tlsConn.isHandshakeComplete.Store(true)
|
||||
tlsConn.cipherSuite = cipherSuite
|
||||
tlsConn.haveVers = true
|
||||
tlsConn.vers = version
|
||||
|
||||
// Update to the new cipher specs
|
||||
// and consume the finished messages
|
||||
tlsConn.in.changeCipherSpec()
|
||||
tlsConn.out.changeCipherSpec()
|
||||
|
||||
tlsConn.in.incSeq()
|
||||
tlsConn.out.incSeq()
|
||||
|
||||
return tlsConn
|
||||
} else {
|
||||
// TODO: Support TLS 1.3 Cipher Suites
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func makeSupportedVersions(minVers, maxVers uint16) []uint16 {
|
||||
a := make([]uint16, maxVers-minVers+1)
|
||||
for i := range a {
|
||||
a[i] = maxVers - uint16(i)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Extending (*Conn).readHandshake() to support more customized handshake messages.
|
||||
func (c *Conn) utlsHandshakeMessageType(msgType byte) (handshakeMessage, error) {
|
||||
switch msgType {
|
||||
case utlsTypeCompressedCertificate:
|
||||
return new(utlsCompressedCertificateMsg), nil
|
||||
case utlsTypeEncryptedExtensions:
|
||||
if c.isClient {
|
||||
return new(encryptedExtensionsMsg), nil
|
||||
} else {
|
||||
return new(utlsClientEncryptedExtensionsMsg), nil
|
||||
}
|
||||
default:
|
||||
return nil, c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
|
||||
}
|
||||
}
|
||||
|
||||
// Extending (*Conn).connectionStateLocked()
|
||||
func (c *Conn) utlsConnectionStateLocked(state *ConnectionState) {
|
||||
state.PeerApplicationSettings = c.utls.peerApplicationSettings
|
||||
}
|
||||
|
||||
type utlsConnExtraFields struct {
|
||||
// Application Settings (ALPS)
|
||||
peerApplicationSettings []byte
|
||||
localApplicationSettings []byte
|
||||
applicationSettingsCodepoint uint16
|
||||
|
||||
sessionController *sessionController
|
||||
}
|
||||
|
||||
// Read reads data from the connection.
|
||||
//
|
||||
// As Read calls [Conn.Handshake], in order to prevent indefinite blocking a deadline
|
||||
// must be set for both Read and [Conn.Write] before Read is called when the handshake
|
||||
// has not yet completed. See [Conn.SetDeadline], [Conn.SetReadDeadline], and
|
||||
// [Conn.SetWriteDeadline].
|
||||
func (c *UConn) Read(b []byte) (int, error) {
|
||||
if err := c.Handshake(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(b) == 0 {
|
||||
// Put this after Handshake, in case people were calling
|
||||
// Read(nil) for the side effect of the Handshake.
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
c.in.Lock()
|
||||
defer c.in.Unlock()
|
||||
|
||||
for c.input.Len() == 0 {
|
||||
if err := c.readRecord(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for c.hand.Len() > 0 {
|
||||
if err := c.handlePostHandshakeMessage(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
n, _ := c.input.Read(b)
|
||||
|
||||
// If a close-notify alert is waiting, read it so that we can return (n,
|
||||
// EOF) instead of (n, nil), to signal to the HTTP response reading
|
||||
// goroutine that the connection is now closed. This eliminates a race
|
||||
// where the HTTP response reading goroutine would otherwise not observe
|
||||
// the EOF until its next read, by which time a client goroutine might
|
||||
// have already tried to reuse the HTTP connection for a new request.
|
||||
// See https://golang.org/cl/76400046 and https://golang.org/issue/3514
|
||||
if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
|
||||
recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
|
||||
if err := c.readRecord(); err != nil {
|
||||
return n, err // will be io.EOF on closeNotify
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// handleRenegotiation processes a HelloRequest handshake message.
|
||||
func (c *UConn) handleRenegotiation() error {
|
||||
if c.vers == VersionTLS13 {
|
||||
return errors.New("tls: internal error: unexpected renegotiation")
|
||||
}
|
||||
|
||||
msg, err := c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
helloReq, ok := msg.(*helloRequestMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(helloReq, msg)
|
||||
}
|
||||
|
||||
if !c.isClient {
|
||||
return c.sendAlert(alertNoRenegotiation)
|
||||
}
|
||||
|
||||
switch c.config.Renegotiation {
|
||||
case RenegotiateNever:
|
||||
return c.sendAlert(alertNoRenegotiation)
|
||||
case RenegotiateOnceAsClient:
|
||||
if c.handshakes > 1 {
|
||||
return c.sendAlert(alertNoRenegotiation)
|
||||
}
|
||||
case RenegotiateFreelyAsClient:
|
||||
// Ok.
|
||||
default:
|
||||
c.sendAlert(alertInternalError)
|
||||
return errors.New("tls: unknown Renegotiation value")
|
||||
}
|
||||
|
||||
c.handshakeMutex.Lock()
|
||||
defer c.handshakeMutex.Unlock()
|
||||
|
||||
c.isHandshakeComplete.Store(false)
|
||||
|
||||
// [uTLS section begins]
|
||||
if err = c.BuildHandshakeState(); err != nil {
|
||||
return err
|
||||
}
|
||||
// [uTLS section ends]
|
||||
if c.handshakeErr = c.clientHandshake(context.Background()); c.handshakeErr == nil {
|
||||
c.handshakes++
|
||||
}
|
||||
return c.handshakeErr
|
||||
}
|
||||
|
||||
// handlePostHandshakeMessage processes a handshake message arrived after the
|
||||
// handshake is complete. Up to TLS 1.2, it indicates the start of a renegotiation.
|
||||
func (c *UConn) handlePostHandshakeMessage() error {
|
||||
if c.vers != VersionTLS13 {
|
||||
return c.handleRenegotiation()
|
||||
}
|
||||
|
||||
msg, err := c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.retryCount++
|
||||
if c.retryCount > maxUselessRecords {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return c.in.setErrorLocked(errors.New("tls: too many non-advancing records"))
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case *newSessionTicketMsgTLS13:
|
||||
return c.handleNewSessionTicket(msg)
|
||||
case *keyUpdateMsg:
|
||||
return c.handleKeyUpdate(msg)
|
||||
}
|
||||
// The QUIC layer is supposed to treat an unexpected post-handshake CertificateRequest
|
||||
// as a QUIC-level PROTOCOL_VIOLATION error (RFC 9001, Section 4.4). Returning an
|
||||
// unexpected_message alert here doesn't provide it with enough information to distinguish
|
||||
// this condition from other unexpected messages. This is probably fine.
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return fmt.Errorf("tls: received unexpected handshake message of type %T", msg)
|
||||
}
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/refraction-networking/utls/dicttls"
|
||||
"github.com/refraction-networking/utls/internal/hpke"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
// Unstable API: This is a work in progress and may change in the future. Using
|
||||
// it in your application may cause your application to break when updating to
|
||||
// a new version of uTLS.
|
||||
|
||||
const (
|
||||
OuterClientHello byte = 0x00
|
||||
InnerClientHello byte = 0x01
|
||||
)
|
||||
|
||||
type EncryptedClientHelloExtension interface {
|
||||
// TLSExtension must be implemented by all EncryptedClientHelloExtension implementations.
|
||||
TLSExtension
|
||||
|
||||
// MarshalClientHello is called by (*UConn).MarshalClientHello() when an ECH extension
|
||||
// is present to allow the ECH extension to take control of the generation of the
|
||||
// entire ClientHello message.
|
||||
MarshalClientHello(*UConn) error
|
||||
|
||||
mustEmbedUnimplementedECHExtension()
|
||||
}
|
||||
|
||||
type ECHExtension = EncryptedClientHelloExtension // alias
|
||||
|
||||
// type guard: GREASEEncryptedClientHelloExtension must implement EncryptedClientHelloExtension
|
||||
var (
|
||||
_ EncryptedClientHelloExtension = (*GREASEEncryptedClientHelloExtension)(nil)
|
||||
|
||||
_ EncryptedClientHelloExtension = (*UnimplementedECHExtension)(nil)
|
||||
)
|
||||
|
||||
type GREASEEncryptedClientHelloExtension struct {
|
||||
CandidateCipherSuites []HPKESymmetricCipherSuite
|
||||
cipherSuite HPKESymmetricCipherSuite // randomly picked from CandidateCipherSuites or generated if empty
|
||||
CandidateConfigIds []uint8
|
||||
configId uint8 // randomly picked from CandidateConfigIds or generated if empty
|
||||
EncapsulatedKey []byte // if empty, will generate random bytes
|
||||
CandidatePayloadLens []uint16 // Pre-encryption. If 0, will pick 128(+16=144)
|
||||
payload []byte // payload should be calculated ONCE and stored here, HRR will reuse this
|
||||
|
||||
initOnce sync.Once
|
||||
|
||||
UnimplementedECHExtension
|
||||
}
|
||||
|
||||
type GREASEECHExtension = GREASEEncryptedClientHelloExtension // alias
|
||||
|
||||
// init initializes the GREASEEncryptedClientHelloExtension with random values if they are not set.
|
||||
//
|
||||
// Based on cloudflare/go's echGenerateGreaseExt()
|
||||
func (g *GREASEEncryptedClientHelloExtension) init() error {
|
||||
var initErr error
|
||||
g.initOnce.Do(func() {
|
||||
// Set the config_id field to a random byte.
|
||||
//
|
||||
// Note: must not reuse this extension unless for HRR. It is required
|
||||
// to generate new random bytes for config_id for each new ClientHello,
|
||||
// but reuse the same config_id for HRR.
|
||||
if len(g.CandidateConfigIds) == 0 {
|
||||
var b []byte = make([]byte, 1)
|
||||
_, err := rand.Read(b[:])
|
||||
if err != nil {
|
||||
initErr = fmt.Errorf("error generating random byte for config_id: %w", err)
|
||||
return
|
||||
}
|
||||
g.configId = b[0]
|
||||
} else {
|
||||
// randomly pick one from the list
|
||||
rndIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(g.CandidateConfigIds))))
|
||||
if err != nil {
|
||||
initErr = fmt.Errorf("error generating random index for config_id: %w", err)
|
||||
return
|
||||
}
|
||||
g.configId = g.CandidateConfigIds[rndIndex.Int64()]
|
||||
}
|
||||
|
||||
// Set the cipher_suite field to a supported HpkeSymmetricCipherSuite.
|
||||
// The selection SHOULD vary to exercise all supported configurations,
|
||||
// but MAY be held constant for successive connections to the same server
|
||||
// in the same session.
|
||||
if len(g.CandidateCipherSuites) == 0 {
|
||||
g.cipherSuite = HPKESymmetricCipherSuite{uint16(defaultHpkeKdf), uint16(defaultHpkeAead)}
|
||||
} else {
|
||||
// randomly pick one from the list
|
||||
rndIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(g.CandidateCipherSuites))))
|
||||
if err != nil {
|
||||
initErr = fmt.Errorf("error generating random index for cipher_suite: %w", err)
|
||||
return
|
||||
}
|
||||
g.cipherSuite = HPKESymmetricCipherSuite{
|
||||
g.CandidateCipherSuites[rndIndex.Int64()].KdfId,
|
||||
g.CandidateCipherSuites[rndIndex.Int64()].AeadId,
|
||||
}
|
||||
// aead = hpke.AEAD(g.cipherSuite.AeadId)
|
||||
}
|
||||
|
||||
if len(g.EncapsulatedKey) == 0 {
|
||||
kem := uint16(defaultHpkeKem)
|
||||
|
||||
echPK, err := hpke.ParseHPKEPublicKey(uint16(kem), dummyX25519PublicKey)
|
||||
if err != nil {
|
||||
initErr = fmt.Errorf("tls: grease ech: failed to parse dummy public key: %w", err)
|
||||
return
|
||||
}
|
||||
suite := echCipher{
|
||||
KDFID: defaultHpkeKdf,
|
||||
AEADID: defaultHpkeAead,
|
||||
}
|
||||
g.EncapsulatedKey, _, err = hpke.SetupSender(kem, suite.KDFID, suite.AEADID, echPK, []byte{})
|
||||
if err != nil {
|
||||
initErr = fmt.Errorf("tls: grease ech: failed to setup encapsulated key: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(g.payload) == 0 {
|
||||
if len(g.CandidatePayloadLens) == 0 {
|
||||
g.CandidatePayloadLens = []uint16{128}
|
||||
}
|
||||
|
||||
// randomly pick one from the list
|
||||
rndIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(g.CandidatePayloadLens))))
|
||||
if err != nil {
|
||||
initErr = fmt.Errorf("error generating random index for payload length: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
initErr = g.randomizePayload(g.CandidatePayloadLens[rndIndex.Int64()])
|
||||
}
|
||||
})
|
||||
|
||||
return initErr
|
||||
}
|
||||
|
||||
func (g *GREASEEncryptedClientHelloExtension) randomizePayload(encodedHelloInnerLen uint16) error {
|
||||
if len(g.payload) != 0 {
|
||||
return errors.New("tls: grease ech: regenerating payload is forbidden")
|
||||
}
|
||||
|
||||
g.payload = make([]byte, cipherLen(g.cipherSuite.AeadId, int(encodedHelloInnerLen)))
|
||||
_, err := rand.Read(g.payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tls: generating grease ech payload: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeToUConn implements TLSExtension.
|
||||
//
|
||||
// For ECH extensions, writeToUConn simply points the ech field in UConn to the extension.
|
||||
func (g *GREASEEncryptedClientHelloExtension) writeToUConn(uconn *UConn) error {
|
||||
uconn.ech = g
|
||||
return uconn.MarshalClientHelloNoECH()
|
||||
}
|
||||
|
||||
// Len implements TLSExtension.
|
||||
func (g *GREASEEncryptedClientHelloExtension) Len() int {
|
||||
g.init()
|
||||
return 2 + 2 + 1 /* ClientHello Type */ + 4 /* CipherSuite */ + 1 /* Config ID */ + 2 + len(g.EncapsulatedKey) + 2 + len(g.payload)
|
||||
}
|
||||
|
||||
// Read implements TLSExtension.
|
||||
func (g *GREASEEncryptedClientHelloExtension) Read(b []byte) (int, error) {
|
||||
if len(b) < g.Len() {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
b[0] = byte(utlsExtensionECH >> 8)
|
||||
b[1] = byte(utlsExtensionECH & 0xFF)
|
||||
b[2] = byte((g.Len() - 4) >> 8)
|
||||
b[3] = byte((g.Len() - 4) & 0xFF)
|
||||
b[4] = OuterClientHello
|
||||
b[5] = byte(g.cipherSuite.KdfId >> 8)
|
||||
b[6] = byte(g.cipherSuite.KdfId & 0xFF)
|
||||
b[7] = byte(g.cipherSuite.AeadId >> 8)
|
||||
b[8] = byte(g.cipherSuite.AeadId & 0xFF)
|
||||
b[9] = g.configId
|
||||
b[10] = byte(len(g.EncapsulatedKey) >> 8)
|
||||
b[11] = byte(len(g.EncapsulatedKey) & 0xFF)
|
||||
copy(b[12:], g.EncapsulatedKey)
|
||||
b[12+len(g.EncapsulatedKey)] = byte(len(g.payload) >> 8)
|
||||
b[12+len(g.EncapsulatedKey)+1] = byte(len(g.payload) & 0xFF)
|
||||
copy(b[12+len(g.EncapsulatedKey)+2:], g.payload)
|
||||
|
||||
return g.Len(), io.EOF
|
||||
}
|
||||
|
||||
// MarshalClientHello implements EncryptedClientHelloExtension.
|
||||
func (*GREASEEncryptedClientHelloExtension) MarshalClientHello(*UConn) error {
|
||||
return errors.New("tls: grease ech: MarshalClientHello() is not implemented, use (*UConn).MarshalClientHello() instead")
|
||||
}
|
||||
|
||||
// Write implements TLSExtensionWriter.
|
||||
func (g *GREASEEncryptedClientHelloExtension) Write(b []byte) (int, error) {
|
||||
fullLen := len(b)
|
||||
extData := cryptobyte.String(b)
|
||||
|
||||
// Check the extension type, it must be OuterClientHello otherwise we are not
|
||||
// parsing the correct extension
|
||||
var chType uint8 // 0: outer, 1: inner
|
||||
var ignored cryptobyte.String
|
||||
if !extData.ReadUint8(&chType) || chType != 0 {
|
||||
return fullLen, errors.New("bad Client Hello type, expected 0, got " + fmt.Sprintf("%d", chType))
|
||||
}
|
||||
|
||||
// Parse the cipher suite
|
||||
if !extData.ReadUint16(&g.cipherSuite.KdfId) || !extData.ReadUint16(&g.cipherSuite.AeadId) {
|
||||
return fullLen, errors.New("bad cipher suite")
|
||||
}
|
||||
if g.cipherSuite.KdfId != dicttls.HKDF_SHA256 &&
|
||||
g.cipherSuite.KdfId != dicttls.HKDF_SHA384 &&
|
||||
g.cipherSuite.KdfId != dicttls.HKDF_SHA512 {
|
||||
return fullLen, errors.New("bad KDF ID: " + fmt.Sprintf("%d", g.cipherSuite.KdfId))
|
||||
}
|
||||
if g.cipherSuite.AeadId != dicttls.AEAD_AES_128_GCM &&
|
||||
g.cipherSuite.AeadId != dicttls.AEAD_AES_256_GCM &&
|
||||
g.cipherSuite.AeadId != dicttls.AEAD_CHACHA20_POLY1305 {
|
||||
return fullLen, errors.New("bad AEAD ID: " + fmt.Sprintf("%d", g.cipherSuite.AeadId))
|
||||
}
|
||||
g.CandidateCipherSuites = []HPKESymmetricCipherSuite{g.cipherSuite}
|
||||
|
||||
// GREASE the ConfigId
|
||||
if !extData.ReadUint8(&g.configId) {
|
||||
return fullLen, errors.New("bad config ID")
|
||||
}
|
||||
// we don't write to CandidateConfigIds because we don't really want to reuse the same config_id
|
||||
|
||||
// GREASE the EncapsulatedKey
|
||||
if !extData.ReadUint16LengthPrefixed(&ignored) {
|
||||
return fullLen, errors.New("bad encapsulated key")
|
||||
}
|
||||
g.EncapsulatedKey = make([]byte, len(ignored))
|
||||
n, err := rand.Read(g.EncapsulatedKey)
|
||||
if err != nil {
|
||||
return fullLen, fmt.Errorf("tls: generating grease ech encapsulated key: %w", err)
|
||||
}
|
||||
if n != len(g.EncapsulatedKey) {
|
||||
return fullLen, fmt.Errorf("tls: generating grease ech encapsulated key: short read for %d bytes", len(ignored)-n)
|
||||
}
|
||||
|
||||
// GREASE the payload
|
||||
if !extData.ReadUint16LengthPrefixed(&ignored) {
|
||||
return fullLen, errors.New("bad payload")
|
||||
}
|
||||
g.CandidatePayloadLens = []uint16{uint16(len(ignored) - cipherLen(g.cipherSuite.AeadId, 0))}
|
||||
|
||||
return fullLen, nil
|
||||
}
|
||||
|
||||
// UnimplementedECHExtension is a placeholder for an ECH extension that is not implemented.
|
||||
// All implementations of EncryptedClientHelloExtension should embed this struct to ensure
|
||||
// forward compatibility.
|
||||
type UnimplementedECHExtension struct{}
|
||||
|
||||
// writeToUConn implements TLSExtension.
|
||||
func (*UnimplementedECHExtension) writeToUConn(_ *UConn) error {
|
||||
return errors.New("tls: unimplemented ECHExtension")
|
||||
}
|
||||
|
||||
// Len implements TLSExtension.
|
||||
func (*UnimplementedECHExtension) Len() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Read implements TLSExtension.
|
||||
func (*UnimplementedECHExtension) Read(_ []byte) (int, error) {
|
||||
return 0, errors.New("tls: unimplemented ECHExtension")
|
||||
}
|
||||
|
||||
// MarshalClientHello implements EncryptedClientHelloExtension.
|
||||
func (*UnimplementedECHExtension) MarshalClientHello(*UConn) error {
|
||||
return errors.New("tls: unimplemented ECHExtension")
|
||||
}
|
||||
|
||||
// mustEmbedUnimplementedECHExtension is a noop function but is required to
|
||||
// ensure forward compatibility.
|
||||
func (*UnimplementedECHExtension) mustEmbedUnimplementedECHExtension() {
|
||||
panic("mustEmbedUnimplementedECHExtension() is not implemented")
|
||||
}
|
||||
|
||||
// BoringGREASEECH returns a GREASE scheme BoringSSL uses by default.
|
||||
func BoringGREASEECH() *GREASEEncryptedClientHelloExtension {
|
||||
return &GREASEEncryptedClientHelloExtension{
|
||||
CandidateCipherSuites: []HPKESymmetricCipherSuite{
|
||||
{
|
||||
KdfId: dicttls.HKDF_SHA256,
|
||||
AeadId: dicttls.AEAD_AES_128_GCM,
|
||||
},
|
||||
},
|
||||
CandidatePayloadLens: []uint16{128, 160, 192, 224}, // +16: 144, 176, 208, 240
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
// Fingerprinter is a struct largely for holding options for the FingerprintClientHello func
|
||||
type Fingerprinter struct {
|
||||
// AllowBluntMimicry will ensure that unknown extensions are
|
||||
// passed along into the resulting ClientHelloSpec as-is
|
||||
// WARNING: there could be numerous subtle issues with ClientHelloSpecs
|
||||
// that are generated with this flag which could compromise security and/or mimicry
|
||||
AllowBluntMimicry bool
|
||||
// AlwaysAddPadding will always add a UtlsPaddingExtension with BoringPaddingStyle
|
||||
// at the end of the extensions list if it isn't found in the fingerprinted hello.
|
||||
// This could be useful in scenarios where the hello you are fingerprinting does not
|
||||
// have any padding, but you suspect that other changes you make to the final hello
|
||||
// (including things like different SNI lengths) would cause padding to be necessary
|
||||
AlwaysAddPadding bool
|
||||
|
||||
RealPSKResumption bool // if set, PSK extension (if any) will be real PSK extension, otherwise it will be fake PSK extension
|
||||
}
|
||||
|
||||
// FingerprintClientHello returns a ClientHelloSpec which is based on the
|
||||
// ClientHello that is passed in as the data argument
|
||||
//
|
||||
// If the ClientHello passed in has extensions that are not recognized or cannot be handled
|
||||
// it will return a non-nil error and a nil *ClientHelloSpec value
|
||||
//
|
||||
// The data should be the full tls record, including the record type/version/length header
|
||||
// as well as the handshake type/length/version header
|
||||
// https://tools.ietf.org/html/rfc5246#section-6.2
|
||||
// https://tools.ietf.org/html/rfc5246#section-7.4
|
||||
//
|
||||
// It calls UnmarshalClientHello internally, and is kept for backwards compatibility
|
||||
func (f *Fingerprinter) FingerprintClientHello(data []byte) (clientHelloSpec *ClientHelloSpec, err error) {
|
||||
return f.RawClientHello(data)
|
||||
}
|
||||
|
||||
// RawClientHello returns a ClientHelloSpec which is based on the
|
||||
// ClientHello raw bytes that is passed in as the raw argument.
|
||||
//
|
||||
// It was renamed from FingerprintClientHello in v1.3.1 and earlier versions
|
||||
// as a more precise name for the function
|
||||
func (f *Fingerprinter) RawClientHello(raw []byte) (clientHelloSpec *ClientHelloSpec, err error) {
|
||||
clientHelloSpec = &ClientHelloSpec{}
|
||||
|
||||
err = clientHelloSpec.FromRaw(raw, f.AllowBluntMimicry, f.RealPSKResumption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.AlwaysAddPadding {
|
||||
clientHelloSpec.AlwaysAddPadding()
|
||||
}
|
||||
|
||||
return clientHelloSpec, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSONClientHello returns a ClientHelloSpec which is based on the
|
||||
// ClientHello JSON bytes that is passed in as the json argument.
|
||||
func (f *Fingerprinter) UnmarshalJSONClientHello(json []byte) (clientHelloSpec *ClientHelloSpec, err error) {
|
||||
clientHelloSpec = &ClientHelloSpec{}
|
||||
err = clientHelloSpec.UnmarshalJSON(json)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.AlwaysAddPadding {
|
||||
clientHelloSpec.AlwaysAddPadding()
|
||||
}
|
||||
|
||||
return clientHelloSpec, nil
|
||||
}
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
// Copyright 2022 uTLS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/refraction-networking/utls/internal/fips140tls"
|
||||
"github.com/refraction-networking/utls/internal/hpke"
|
||||
"github.com/refraction-networking/utls/internal/tls13"
|
||||
)
|
||||
|
||||
// This function is called by (*clientHandshakeStateTLS13).readServerCertificate()
|
||||
// to retrieve the certificate out of a message read by (*Conn).readHandshake()
|
||||
func (hs *clientHandshakeStateTLS13) utlsReadServerCertificate(msg any) (processedMsg any, err error) {
|
||||
for _, ext := range hs.uconn.Extensions {
|
||||
switch ext.(type) {
|
||||
case *UtlsCompressCertExtension:
|
||||
// Included Compressed Certificate extension
|
||||
if len(hs.uconn.certCompressionAlgs) > 0 {
|
||||
compressedCertMsg, ok := msg.(*utlsCompressedCertificateMsg)
|
||||
if ok {
|
||||
if err = transcriptMsg(compressedCertMsg, hs.transcript); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg, err = hs.decompressCert(*compressedCertMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tls: failed to decompress certificate message: %w", err)
|
||||
} else {
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// called by (*clientHandshakeStateTLS13).utlsReadServerCertificate() when UtlsCompressCertExtension is used
|
||||
func (hs *clientHandshakeStateTLS13) decompressCert(m utlsCompressedCertificateMsg) (*certificateMsgTLS13, error) {
|
||||
var (
|
||||
decompressed io.Reader
|
||||
compressed = bytes.NewReader(m.compressedCertificateMessage)
|
||||
c = hs.c
|
||||
)
|
||||
|
||||
// Check to see if the peer responded with an algorithm we advertised.
|
||||
supportedAlg := false
|
||||
for _, alg := range hs.uconn.certCompressionAlgs {
|
||||
if m.algorithm == uint16(alg) {
|
||||
supportedAlg = true
|
||||
}
|
||||
}
|
||||
if !supportedAlg {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return nil, fmt.Errorf("unadvertised algorithm (%d)", m.algorithm)
|
||||
}
|
||||
|
||||
switch CertCompressionAlgo(m.algorithm) {
|
||||
case CertCompressionBrotli:
|
||||
decompressed = brotli.NewReader(compressed)
|
||||
|
||||
case CertCompressionZlib:
|
||||
rc, err := zlib.NewReader(compressed)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return nil, fmt.Errorf("failed to open zlib reader: %w", err)
|
||||
}
|
||||
defer rc.Close()
|
||||
decompressed = rc
|
||||
|
||||
case CertCompressionZstd:
|
||||
rc, err := zstd.NewReader(compressed)
|
||||
if err != nil {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return nil, fmt.Errorf("failed to open zstd reader: %w", err)
|
||||
}
|
||||
defer rc.Close()
|
||||
decompressed = rc
|
||||
|
||||
default:
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return nil, fmt.Errorf("unsupported algorithm (%d)", m.algorithm)
|
||||
}
|
||||
|
||||
rawMsg := make([]byte, m.uncompressedLength+4) // +4 for message type and uint24 length field
|
||||
rawMsg[0] = typeCertificate
|
||||
rawMsg[1] = uint8(m.uncompressedLength >> 16)
|
||||
rawMsg[2] = uint8(m.uncompressedLength >> 8)
|
||||
rawMsg[3] = uint8(m.uncompressedLength)
|
||||
|
||||
n, err := decompressed.Read(rawMsg[4:])
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return nil, err
|
||||
}
|
||||
if n < len(rawMsg)-4 {
|
||||
// If, after decompression, the specified length does not match the actual length, the party
|
||||
// receiving the invalid message MUST abort the connection with the "bad_certificate" alert.
|
||||
// https://datatracker.ietf.org/doc/html/rfc8879#section-4
|
||||
c.sendAlert(alertBadCertificate)
|
||||
return nil, fmt.Errorf("decompressed len (%d) does not match specified len (%d)", n, m.uncompressedLength)
|
||||
}
|
||||
certMsg := new(certificateMsgTLS13)
|
||||
if !certMsg.unmarshal(rawMsg) {
|
||||
return nil, c.sendAlert(alertUnexpectedMessage)
|
||||
}
|
||||
return certMsg, nil
|
||||
}
|
||||
|
||||
// to be called in (*clientHandshakeStateTLS13).handshake(),
|
||||
// after hs.readServerFinished() and before hs.sendClientCertificate()
|
||||
func (hs *clientHandshakeStateTLS13) serverFinishedReceived() error {
|
||||
if err := hs.sendClientEncryptedExtensions(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *clientHandshakeStateTLS13) sendClientEncryptedExtensions() error {
|
||||
c := hs.c
|
||||
clientEncryptedExtensions := new(utlsClientEncryptedExtensionsMsg)
|
||||
if c.utls.applicationSettingsCodepoint != 0 {
|
||||
clientEncryptedExtensions.applicationSettingsCodepoint = c.utls.applicationSettingsCodepoint
|
||||
clientEncryptedExtensions.applicationSettings = c.utls.localApplicationSettings
|
||||
if _, err := c.writeHandshakeRecord(clientEncryptedExtensions, hs.transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs *clientHandshakeStateTLS13) utlsReadServerParameters(encryptedExtensions *encryptedExtensionsMsg) error {
|
||||
hs.c.utls.peerApplicationSettings = encryptedExtensions.utls.applicationSettings
|
||||
hs.c.utls.applicationSettingsCodepoint = encryptedExtensions.utls.applicationSettingsCodepoint
|
||||
|
||||
if hs.c.utls.applicationSettingsCodepoint != 0 {
|
||||
if hs.uconn.vers < VersionTLS13 {
|
||||
return errors.New("tls: server sent application settings at invalid version")
|
||||
}
|
||||
if len(hs.uconn.clientProtocol) == 0 {
|
||||
return errors.New("tls: server sent application settings without ALPN")
|
||||
}
|
||||
|
||||
// Check if the ALPN selected by the server exists in the client's list.
|
||||
if alps, ok := hs.uconn.config.ApplicationSettings[hs.serverHello.alpnProtocol]; ok {
|
||||
hs.c.utls.localApplicationSettings = alps
|
||||
} else {
|
||||
// return errors.New("tls: server selected ALPN doesn't match a client ALPS")
|
||||
return nil // ignore if client doesn't have ALPS in use.
|
||||
// TODO: is this a issue or not?
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) makeClientHelloForApplyPreset() (*clientHelloMsg, *keySharePrivateKeys, *echClientContext, error) {
|
||||
config := c.config
|
||||
|
||||
// [UTLS SECTION START]
|
||||
if len(config.ServerName) == 0 && !config.InsecureSkipVerify && len(config.InsecureServerNameToVerify) == 0 {
|
||||
return nil, nil, nil, errors.New("tls: at least one of ServerName, InsecureSkipVerify or InsecureServerNameToVerify must be specified in the tls.Config")
|
||||
}
|
||||
// [UTLS SECTION END]
|
||||
|
||||
nextProtosLength := 0
|
||||
for _, proto := range config.NextProtos {
|
||||
if l := len(proto); l == 0 || l > 255 {
|
||||
return nil, nil, nil, errors.New("tls: invalid NextProtos value")
|
||||
} else {
|
||||
nextProtosLength += 1 + l
|
||||
}
|
||||
}
|
||||
if nextProtosLength > 0xffff {
|
||||
return nil, nil, nil, errors.New("tls: NextProtos values too large")
|
||||
}
|
||||
|
||||
supportedVersions := config.supportedVersions(roleClient)
|
||||
if len(supportedVersions) == 0 {
|
||||
return nil, nil, nil, errors.New("tls: no supported versions satisfy MinVersion and MaxVersion")
|
||||
}
|
||||
maxVersion := config.maxSupportedVersion(roleClient)
|
||||
|
||||
hello := &clientHelloMsg{
|
||||
vers: maxVersion,
|
||||
compressionMethods: []uint8{compressionNone},
|
||||
random: make([]byte, 32),
|
||||
extendedMasterSecret: true,
|
||||
ocspStapling: true,
|
||||
scts: true,
|
||||
serverName: hostnameInSNI(config.ServerName),
|
||||
supportedCurves: config.curvePreferences(maxVersion),
|
||||
supportedPoints: []uint8{pointFormatUncompressed},
|
||||
secureRenegotiationSupported: true,
|
||||
alpnProtocols: config.NextProtos,
|
||||
supportedVersions: supportedVersions,
|
||||
}
|
||||
|
||||
// The version at the beginning of the ClientHello was capped at TLS 1.2
|
||||
// for compatibility reasons. The supported_versions extension is used
|
||||
// to negotiate versions now. See RFC 8446, Section 4.2.1.
|
||||
if hello.vers > VersionTLS12 {
|
||||
hello.vers = VersionTLS12
|
||||
}
|
||||
|
||||
if c.handshakes > 0 {
|
||||
hello.secureRenegotiation = c.clientFinished[:]
|
||||
}
|
||||
|
||||
preferenceOrder := cipherSuitesPreferenceOrder
|
||||
if !hasAESGCMHardwareSupport {
|
||||
preferenceOrder = cipherSuitesPreferenceOrderNoAES
|
||||
}
|
||||
configCipherSuites := config.cipherSuites()
|
||||
hello.cipherSuites = make([]uint16, 0, len(configCipherSuites))
|
||||
|
||||
for _, suiteId := range preferenceOrder {
|
||||
suite := mutualCipherSuite(configCipherSuites, suiteId)
|
||||
if suite == nil {
|
||||
continue
|
||||
}
|
||||
// Don't advertise TLS 1.2-only cipher suites unless
|
||||
// we're attempting TLS 1.2.
|
||||
if maxVersion < VersionTLS12 && suite.flags&suiteTLS12 != 0 {
|
||||
continue
|
||||
}
|
||||
hello.cipherSuites = append(hello.cipherSuites, suiteId)
|
||||
}
|
||||
|
||||
_, err := io.ReadFull(config.rand(), hello.random)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.New("tls: short read from Rand: " + err.Error())
|
||||
}
|
||||
|
||||
// A random session ID is used to detect when the server accepted a ticket
|
||||
// and is resuming a session (see RFC 5077). In TLS 1.3, it's always set as
|
||||
// a compatibility measure (see RFC 8446, Section 4.1.2).
|
||||
//
|
||||
// The session ID is not set for QUIC connections (see RFC 9001, Section 8.4).
|
||||
if c.quic == nil {
|
||||
hello.sessionId = make([]byte, 32)
|
||||
if _, err := io.ReadFull(config.rand(), hello.sessionId); err != nil {
|
||||
return nil, nil, nil, errors.New("tls: short read from Rand: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if maxVersion >= VersionTLS12 {
|
||||
hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms()
|
||||
}
|
||||
if testingOnlyForceClientHelloSignatureAlgorithms != nil {
|
||||
hello.supportedSignatureAlgorithms = testingOnlyForceClientHelloSignatureAlgorithms
|
||||
}
|
||||
|
||||
var keyShareKeys *keySharePrivateKeys
|
||||
if hello.supportedVersions[0] == VersionTLS13 {
|
||||
// Reset the list of ciphers when the client only supports TLS 1.3.
|
||||
if len(hello.supportedVersions) == 1 {
|
||||
hello.cipherSuites = nil
|
||||
}
|
||||
if fips140tls.Required() {
|
||||
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...)
|
||||
} else if hasAESGCMHardwareSupport {
|
||||
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...)
|
||||
} else {
|
||||
hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13NoAES...)
|
||||
}
|
||||
|
||||
if len(hello.supportedCurves) == 0 {
|
||||
return nil, nil, nil, errors.New("tls: no supported elliptic curves for ECDHE")
|
||||
}
|
||||
// curveID := hello.supportedCurves[0]
|
||||
// keyShareKeys = &keySharePrivateKeys{curveID: curveID}
|
||||
// // Note that if X25519MLKEM768 is supported, it will be first because
|
||||
// // the preference order is fixed.
|
||||
// if curveID == X25519MLKEM768 {
|
||||
// keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), X25519)
|
||||
// if err != nil {
|
||||
// return nil, nil, nil, err
|
||||
// }
|
||||
// seed := make([]byte, mlkem.SeedSize)
|
||||
// if _, err := io.ReadFull(config.rand(), seed); err != nil {
|
||||
// return nil, nil, nil, err
|
||||
// }
|
||||
// keyShareKeys.mlkem, err = mlkem.NewDecapsulationKey768(seed)
|
||||
// if err != nil {
|
||||
// return nil, nil, nil, err
|
||||
// }
|
||||
// mlkemEncapsulationKey := keyShareKeys.mlkem.EncapsulationKey().Bytes()
|
||||
// x25519EphemeralKey := keyShareKeys.ecdhe.PublicKey().Bytes()
|
||||
// hello.keyShares = []keyShare{
|
||||
// {group: X25519MLKEM768, data: append(mlkemEncapsulationKey, x25519EphemeralKey...)},
|
||||
// }
|
||||
// // If both X25519MLKEM768 and X25519 are supported, we send both key
|
||||
// // shares (as a fallback) and we reuse the same X25519 ephemeral
|
||||
// // key, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2.
|
||||
// if slices.Contains(hello.supportedCurves, X25519) {
|
||||
// hello.keyShares = append(hello.keyShares, keyShare{group: X25519, data: x25519EphemeralKey})
|
||||
// }
|
||||
// } else {
|
||||
// if _, ok := curveForCurveID(curveID); !ok {
|
||||
// return nil, nil, nil, errors.New("tls: CurvePreferences includes unsupported curve")
|
||||
// }
|
||||
// keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), curveID)
|
||||
// if err != nil {
|
||||
// return nil, nil, nil, err
|
||||
// }
|
||||
// hello.keyShares = []keyShare{{group: curveID, data: keyShareKeys.ecdhe.PublicKey().Bytes()}}
|
||||
// }
|
||||
}
|
||||
|
||||
// [UTLS] We don't need this, since it is not ready yet
|
||||
// if c.quic != nil {
|
||||
// p, err := c.quicGetTransportParameters()
|
||||
// if err != nil {
|
||||
// return nil, nil, nil, err
|
||||
// }
|
||||
// if p == nil {
|
||||
// p = []byte{}
|
||||
// }
|
||||
// hello.quicTransportParameters = p
|
||||
// }
|
||||
|
||||
var ech *echClientContext
|
||||
if c.config.EncryptedClientHelloConfigList != nil {
|
||||
if c.config.MinVersion != 0 && c.config.MinVersion < VersionTLS13 {
|
||||
return nil, nil, nil, errors.New("tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
|
||||
}
|
||||
if c.config.MaxVersion != 0 && c.config.MaxVersion <= VersionTLS12 {
|
||||
return nil, nil, nil, errors.New("tls: MaxVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated")
|
||||
}
|
||||
echConfigs, err := parseECHConfigList(c.config.EncryptedClientHelloConfigList)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
echConfig := pickECHConfig(echConfigs)
|
||||
if echConfig == nil {
|
||||
return nil, nil, nil, errors.New("tls: EncryptedClientHelloConfigList contains no valid configs")
|
||||
}
|
||||
ech = &echClientContext{config: echConfig}
|
||||
hello.encryptedClientHello = []byte{1} // indicate inner hello
|
||||
// We need to explicitly set these 1.2 fields to nil, as we do not
|
||||
// marshal them when encoding the inner hello, otherwise transcripts
|
||||
// will later mismatch.
|
||||
hello.supportedPoints = nil
|
||||
hello.ticketSupported = false
|
||||
hello.secureRenegotiationSupported = false
|
||||
hello.extendedMasterSecret = false
|
||||
|
||||
echPK, err := hpke.ParseHPKEPublicKey(ech.config.KemID, ech.config.PublicKey)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
suite, err := pickECHCipherSuite(ech.config.SymmetricCipherSuite)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
ech.kdfID, ech.aeadID = suite.KDFID, suite.AEADID
|
||||
info := append([]byte("tls ech\x00"), ech.config.raw...)
|
||||
ech.encapsulatedKey, ech.hpkeContext, err = hpke.SetupSender(ech.config.KemID, suite.KDFID, suite.AEADID, echPK, info)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return hello, keyShareKeys, ech, nil
|
||||
}
|
||||
|
||||
// clientHandshakeWithOneState checks that exactly one expected state is set (1.2 or 1.3)
|
||||
// and performs client TLS handshake with that state
|
||||
func (c *UConn) clientHandshake(ctx context.Context) (err error) {
|
||||
// [uTLS section begins]
|
||||
hello := c.HandshakeState.Hello.getPrivatePtr()
|
||||
ech := c.echCtx
|
||||
defer func() { c.HandshakeState.Hello = hello.getPublicPtr() }()
|
||||
|
||||
sessionIsLocked := c.utls.sessionController.isSessionLocked()
|
||||
|
||||
// after this point exactly 1 out of 2 HandshakeState pointers is non-nil,
|
||||
// useTLS13 variable tells which pointer
|
||||
// [uTLS section ends]
|
||||
|
||||
if c.config == nil {
|
||||
c.config = defaultConfig()
|
||||
}
|
||||
|
||||
// This may be a renegotiation handshake, in which case some fields
|
||||
// need to be reset.
|
||||
c.didResume = false
|
||||
|
||||
// [uTLS section begins]
|
||||
// don't make new ClientHello, use hs.hello
|
||||
// preserve the checks from beginning and end of makeClientHello()
|
||||
if len(c.config.ServerName) == 0 && !c.config.InsecureSkipVerify && len(c.config.InsecureServerNameToVerify) == 0 {
|
||||
return errors.New("tls: at least one of ServerName, InsecureSkipVerify or InsecureServerNameToVerify must be specified in the tls.Config")
|
||||
}
|
||||
|
||||
nextProtosLength := 0
|
||||
for _, proto := range c.config.NextProtos {
|
||||
if l := len(proto); l == 0 || l > 255 {
|
||||
return errors.New("tls: invalid NextProtos value")
|
||||
} else {
|
||||
nextProtosLength += 1 + l
|
||||
}
|
||||
}
|
||||
|
||||
if nextProtosLength > 0xffff {
|
||||
return errors.New("tls: NextProtos values too large")
|
||||
}
|
||||
|
||||
if c.handshakes > 0 {
|
||||
hello.secureRenegotiation = c.clientFinished[:]
|
||||
}
|
||||
|
||||
var (
|
||||
session *SessionState
|
||||
earlySecret *tls13.EarlySecret
|
||||
binderKey []byte
|
||||
)
|
||||
if !sessionIsLocked {
|
||||
// [uTLS section ends]
|
||||
|
||||
session, earlySecret, binderKey, err = c.loadSession(hello)
|
||||
|
||||
// [uTLS section start]
|
||||
} else {
|
||||
session = c.HandshakeState.Session
|
||||
|
||||
if c.HandshakeState.State13.EarlySecret != nil && session != nil {
|
||||
cipherSuite := cipherSuiteTLS13ByID(session.cipherSuite)
|
||||
earlySecret = tls13.NewEarlySecretFromSecret(cipherSuite.hash.New, c.HandshakeState.State13.EarlySecret)
|
||||
}
|
||||
|
||||
binderKey = c.HandshakeState.State13.BinderKey
|
||||
}
|
||||
// [uTLS section ends]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if session != nil {
|
||||
defer func() {
|
||||
// If we got a handshake failure when resuming a session, throw away
|
||||
// the session ticket. See RFC 5077, Section 3.2.
|
||||
//
|
||||
// RFC 8446 makes no mention of dropping tickets on failure, but it
|
||||
// does require servers to abort on invalid binders, so we need to
|
||||
// delete tickets to recover from a corrupted PSK.
|
||||
if err != nil {
|
||||
if cacheKey := c.clientSessionCacheKey(); cacheKey != "" {
|
||||
c.config.ClientSessionCache.Put(cacheKey, nil)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if ech != nil && c.clientHelloBuildStatus != BuildByUtls {
|
||||
// Split hello into inner and outer
|
||||
ech.innerHello = hello.clone()
|
||||
|
||||
// Overwrite the server name in the outer hello with the public facing
|
||||
// name.
|
||||
hello.serverName = string(ech.config.PublicName)
|
||||
// Generate a new random for the outer hello.
|
||||
hello.random = make([]byte, 32)
|
||||
_, err = io.ReadFull(c.config.rand(), hello.random)
|
||||
if err != nil {
|
||||
return errors.New("tls: short read from Rand: " + err.Error())
|
||||
}
|
||||
|
||||
// NOTE: we don't do PSK GREASE, in line with boringssl, it's meant to
|
||||
// work around _possibly_ broken middleboxes, but there is little-to-no
|
||||
// evidence that this is actually a problem.
|
||||
|
||||
if err := computeAndUpdateOuterECHExtension(hello, ech.innerHello, ech, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.serverName = hello.serverName
|
||||
|
||||
if _, err := c.writeHandshakeRecord(hello, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hello.earlyData {
|
||||
suite := cipherSuiteTLS13ByID(session.cipherSuite)
|
||||
transcript := suite.hash.New()
|
||||
if err := transcriptMsg(hello, transcript); err != nil {
|
||||
return err
|
||||
}
|
||||
earlyTrafficSecret := earlySecret.ClientEarlyTrafficSecret(transcript)
|
||||
c.quicSetWriteSecret(QUICEncryptionLevelEarly, suite.id, earlyTrafficSecret)
|
||||
}
|
||||
|
||||
// serverHelloMsg is not included in the transcript
|
||||
msg, err := c.readHandshake(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serverHello, ok := msg.(*serverHelloMsg)
|
||||
if !ok {
|
||||
c.sendAlert(alertUnexpectedMessage)
|
||||
return unexpectedMessageError(serverHello, msg)
|
||||
}
|
||||
|
||||
if err := c.pickTLSVersion(serverHello); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we are negotiating a protocol version that's lower than what we
|
||||
// support, check for the server downgrade canaries.
|
||||
// See RFC 8446, Section 4.1.3.
|
||||
maxVers := c.config.maxSupportedVersion(roleClient)
|
||||
tls12Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS12
|
||||
tls11Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS11
|
||||
if maxVers == VersionTLS13 && c.vers <= VersionTLS12 && (tls12Downgrade || tls11Downgrade) ||
|
||||
maxVers == VersionTLS12 && c.vers <= VersionTLS11 && tls11Downgrade {
|
||||
c.sendAlert(alertIllegalParameter)
|
||||
return errors.New("tls: downgrade attempt detected, possibly due to a MitM attack or a broken middlebox")
|
||||
}
|
||||
|
||||
// uTLS: do not create new handshakeState, use existing one
|
||||
c.HandshakeState.ServerHello = serverHello.getPublicPtr()
|
||||
if c.vers == VersionTLS13 {
|
||||
hs13 := c.HandshakeState.toPrivate13()
|
||||
hs13.serverHello = serverHello
|
||||
hs13.hello = hello
|
||||
hs13.echContext = ech
|
||||
if c.HandshakeState.State13.EarlySecret != nil && session.cipherSuite != 0 {
|
||||
hs13.earlySecret = tls13.NewEarlySecretFromSecret(cipherSuiteTLS13ByID(session.cipherSuite).hash.New, c.HandshakeState.State13.EarlySecret)
|
||||
}
|
||||
if c.HandshakeState.MasterSecret != nil && session.cipherSuite != 0 {
|
||||
hs13.masterSecret = tls13.NewMasterSecretFromSecret(cipherSuiteTLS13ByID(session.cipherSuite).hash.New, c.HandshakeState.MasterSecret)
|
||||
}
|
||||
if !sessionIsLocked {
|
||||
hs13.earlySecret = earlySecret
|
||||
hs13.binderKey = binderKey
|
||||
hs13.session = session
|
||||
}
|
||||
hs13.ctx = ctx
|
||||
// In TLS 1.3, session tickets are delivered after the handshake.
|
||||
err = hs13.handshake()
|
||||
if handshakeState := hs13.toPublic13(); handshakeState != nil {
|
||||
c.HandshakeState = *handshakeState
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
hs12 := c.HandshakeState.toPrivate12()
|
||||
hs12.serverHello = serverHello
|
||||
hs12.hello = hello
|
||||
hs12.ctx = ctx
|
||||
hs12.session = session
|
||||
err = hs12.handshake()
|
||||
if handshakeState := hs12.toPublic12(); handshakeState != nil {
|
||||
c.HandshakeState = *handshakeState
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *UConn) echTranscriptMsg(outer *clientHelloMsg, echCtx *echClientContext) (err error) {
|
||||
// Recreate the inner ClientHello from its compressed form using server's decodeInnerClientHello function.
|
||||
// See https://github.com/refraction-networking/utls/blob/e430876b1d82fdf582efc57f3992d448e7ab3d8a/ech.go#L276-L283
|
||||
encodedInner, err := encodeInnerClientHelloReorderOuterExts(echCtx.innerHello, int(echCtx.config.MaxNameLength), c.extensionsList())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decodedInner, err := decodeInnerClientHello(outer, encodedInner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := transcriptMsg(decodedInner, echCtx.innerTranscript); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
// Copyright 2022 uTLS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
// Only implemented client-side, for server certificates.
|
||||
// Alternate certificate message formats (https://datatracker.ietf.org/doc/html/rfc7250) are not
|
||||
// supported.
|
||||
// https://datatracker.ietf.org/doc/html/rfc8879
|
||||
type utlsCompressedCertificateMsg struct {
|
||||
raw []byte
|
||||
|
||||
algorithm uint16
|
||||
uncompressedLength uint32 // uint24
|
||||
compressedCertificateMessage []byte
|
||||
}
|
||||
|
||||
func (m *utlsCompressedCertificateMsg) marshal() ([]byte, error) {
|
||||
if m.raw != nil {
|
||||
return m.raw, nil
|
||||
}
|
||||
|
||||
var b cryptobyte.Builder
|
||||
b.AddUint8(utlsTypeCompressedCertificate)
|
||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddUint16(m.algorithm)
|
||||
b.AddUint24(m.uncompressedLength)
|
||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(m.compressedCertificateMessage)
|
||||
})
|
||||
})
|
||||
|
||||
var err error
|
||||
m.raw, err = b.Bytes()
|
||||
return m.raw, err
|
||||
}
|
||||
|
||||
func (m *utlsCompressedCertificateMsg) unmarshal(data []byte) bool {
|
||||
*m = utlsCompressedCertificateMsg{raw: data}
|
||||
s := cryptobyte.String(data)
|
||||
|
||||
if !s.Skip(4) || // message type and uint24 length field
|
||||
!s.ReadUint16(&m.algorithm) ||
|
||||
!s.ReadUint24(&m.uncompressedLength) ||
|
||||
!readUint24LengthPrefixed(&s, &m.compressedCertificateMessage) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type utlsEncryptedExtensionsMsgExtraFields struct {
|
||||
applicationSettings []byte
|
||||
applicationSettingsCodepoint uint16
|
||||
customExtension []byte
|
||||
}
|
||||
|
||||
func (m *encryptedExtensionsMsg) utlsUnmarshal(extension uint16, extData cryptobyte.String) bool {
|
||||
switch extension {
|
||||
case utlsExtensionApplicationSettings:
|
||||
fallthrough
|
||||
case utlsExtensionApplicationSettingsNew:
|
||||
m.utls.applicationSettingsCodepoint = extension
|
||||
m.utls.applicationSettings = []byte(extData)
|
||||
}
|
||||
return true // success/unknown extension
|
||||
}
|
||||
|
||||
type utlsClientEncryptedExtensionsMsg struct {
|
||||
raw []byte
|
||||
applicationSettings []byte
|
||||
applicationSettingsCodepoint uint16
|
||||
customExtension []byte
|
||||
}
|
||||
|
||||
func (m *utlsClientEncryptedExtensionsMsg) marshal() (x []byte, err error) {
|
||||
if m.raw != nil {
|
||||
return m.raw, nil
|
||||
}
|
||||
|
||||
var builder cryptobyte.Builder
|
||||
builder.AddUint8(typeEncryptedExtensions)
|
||||
builder.AddUint24LengthPrefixed(func(body *cryptobyte.Builder) {
|
||||
body.AddUint16LengthPrefixed(func(extensions *cryptobyte.Builder) {
|
||||
if m.applicationSettingsCodepoint != 0 {
|
||||
extensions.AddUint16(m.applicationSettingsCodepoint)
|
||||
extensions.AddUint16LengthPrefixed(func(msg *cryptobyte.Builder) {
|
||||
msg.AddBytes(m.applicationSettings)
|
||||
})
|
||||
}
|
||||
if len(m.customExtension) > 0 {
|
||||
extensions.AddUint16(utlsFakeExtensionCustom)
|
||||
extensions.AddUint16LengthPrefixed(func(msg *cryptobyte.Builder) {
|
||||
msg.AddBytes(m.customExtension)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
m.raw, err = builder.Bytes()
|
||||
return m.raw, err
|
||||
}
|
||||
|
||||
func (m *utlsClientEncryptedExtensionsMsg) unmarshal(data []byte) bool {
|
||||
*m = utlsClientEncryptedExtensionsMsg{raw: data}
|
||||
s := cryptobyte.String(data)
|
||||
|
||||
var extensions cryptobyte.String
|
||||
if !s.Skip(4) || // message type and uint24 length field
|
||||
!s.ReadUint16LengthPrefixed(&extensions) || !s.Empty() {
|
||||
return false
|
||||
}
|
||||
|
||||
for !extensions.Empty() {
|
||||
var extension uint16
|
||||
var extData cryptobyte.String
|
||||
if !extensions.ReadUint16(&extension) ||
|
||||
!extensions.ReadUint16LengthPrefixed(&extData) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch extension {
|
||||
case utlsExtensionApplicationSettings:
|
||||
fallthrough
|
||||
case utlsExtensionApplicationSettingsNew:
|
||||
m.applicationSettingsCodepoint = extension
|
||||
m.applicationSettings = []byte(extData)
|
||||
default:
|
||||
// Unknown extensions are illegal in EncryptedExtensions.
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"github.com/refraction-networking/utls/internal/hpke"
|
||||
)
|
||||
|
||||
type HPKERawPublicKey = []byte
|
||||
type HPKE_KEM_ID = uint16 // RFC 9180
|
||||
type HPKE_KDF_ID = uint16 // RFC 9180
|
||||
type HPKE_AEAD_ID = uint16 // RFC 9180
|
||||
|
||||
type HPKESymmetricCipherSuite struct {
|
||||
KdfId HPKE_KDF_ID
|
||||
AeadId HPKE_AEAD_ID
|
||||
}
|
||||
|
||||
const defaultHpkeKdf = hpke.KDF_HKDF_SHA256
|
||||
const defaultHpkeKem = hpke.DHKEM_X25519_HKDF_SHA256
|
||||
const defaultHpkeAead = hpke.AEAD_AES_128_GCM
|
||||
|
||||
var dummyX25519PublicKey = []byte{
|
||||
143, 38, 37, 36, 12, 6, 229, 30, 140, 27, 167, 73, 26, 100, 203, 107, 216,
|
||||
81, 163, 222, 52, 211, 54, 210, 46, 37, 78, 216, 157, 97, 241, 244,
|
||||
}
|
||||
|
||||
// cipherLen returns the length of a ciphertext corresponding to a message of
|
||||
// length mLen.
|
||||
func cipherLen(a uint16, mLen int) int {
|
||||
switch a {
|
||||
case hpke.AEAD_AES_128_GCM, hpke.AEAD_AES_256_GCM, hpke.AEAD_ChaCha20Poly1305:
|
||||
return mLen + 16
|
||||
default:
|
||||
panic("hpke: invalid AEAD identifier")
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/mlkem"
|
||||
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// kyberDecapsulate implements decapsulation according to Kyber Round 3.
|
||||
func kyberDecapsulate(dk *mlkem.DecapsulationKey768, c []byte) ([]byte, error) {
|
||||
K, err := dk.Decapsulate(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kyberSharedSecret(c, K), nil
|
||||
}
|
||||
|
||||
func kyberSharedSecret(c, K []byte) []byte {
|
||||
// Package mlkem implements ML-KEM, which compared to Kyber removed a
|
||||
// final hashing step. Compute SHAKE-256(K || SHA3-256(c), 32) to match Kyber.
|
||||
// See https://words.filippo.io/mlkem768/#bonus-track-using-a-ml-kem-implementation-as-kyber-v3.
|
||||
h := sha3.NewShake256()
|
||||
h.Write(K)
|
||||
ch := sha3.New256()
|
||||
ch.Write(c)
|
||||
h.Write(ch.Sum(nil))
|
||||
out := make([]byte, 32)
|
||||
h.Read(out)
|
||||
return out
|
||||
}
|
||||
+3241
File diff suppressed because it is too large
Load Diff
+486
@@ -0,0 +1,486 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
var ErrEmptyPsk = errors.New("tls: empty psk detected; remove the psk extension for this connection or set OmitEmptyPsk to true to conceal it in utls")
|
||||
|
||||
type PreSharedKeyCommon struct {
|
||||
Identities []PskIdentity
|
||||
Binders [][]byte
|
||||
BinderKey []byte // this will be used to compute the binder when hello message is ready
|
||||
EarlySecret []byte
|
||||
Session *SessionState
|
||||
}
|
||||
|
||||
// The lifecycle of a PreSharedKeyExtension:
|
||||
//
|
||||
// Creation Phase:
|
||||
// - The extension is created.
|
||||
//
|
||||
// Write Phase:
|
||||
//
|
||||
// - [writeToUConn() called]:
|
||||
//
|
||||
// > - During this phase, it is important to note that implementations should not write any session data to the UConn (Underlying Connection) as the session is not yet loaded. The session context is not active at this point.
|
||||
//
|
||||
// Initialization Phase:
|
||||
//
|
||||
// - [IsInitialized() called]:
|
||||
//
|
||||
// If IsInitialized() returns true
|
||||
//
|
||||
// > - GetPreSharedKeyCommon() will be called subsequently and the PSK states in handshake/clientHello will be fully initialized.
|
||||
//
|
||||
// If IsInitialized() returns false:
|
||||
//
|
||||
// > - [conn.loadSession() called]:
|
||||
//
|
||||
// >> - Once the session is available:
|
||||
//
|
||||
// >>> - [InitializeByUtls() called]:
|
||||
//
|
||||
// >>>> - The InitializeByUtls() method is invoked to initialize the extension based on the loaded session data.
|
||||
//
|
||||
// >>>> - This step prepares the extension for further processing.
|
||||
//
|
||||
// Marshal Phase:
|
||||
//
|
||||
// - [Len() called], [Read() called]:
|
||||
//
|
||||
// > - Implementations should marshal the extension into bytes, using placeholder binders to maintain the correct length.
|
||||
//
|
||||
// Binders Preparation Phase:
|
||||
//
|
||||
// - [PatchBuiltHello(hello) called]:
|
||||
//
|
||||
// > - The client hello is already marshaled in the "hello.Raw" format.
|
||||
//
|
||||
// > - Implementations are expected to update the binders within the marshaled client hello.
|
||||
//
|
||||
// - [GetPreSharedKeyCommon() called]:
|
||||
//
|
||||
// > - Implementations should gather and provide the final pre-shared key (PSK) related data.
|
||||
//
|
||||
// > - This data will be incorporated into both the clientHello and HandshakeState, ensuring that the PSK-related information is properly set and ready for the handshake process.
|
||||
type PreSharedKeyExtension interface {
|
||||
// TLSExtension must be implemented by all PreSharedKeyExtension implementations.
|
||||
TLSExtension
|
||||
|
||||
// If false is returned, utls will invoke `InitializeByUtls()` for the necessary initialization.
|
||||
Initializable
|
||||
|
||||
SetOmitEmptyPsk(val bool)
|
||||
|
||||
// InitializeByUtls is invoked when IsInitialized() returns false.
|
||||
// It initializes the extension using a real and valid TLS 1.3 session.
|
||||
InitializeByUtls(session *SessionState, earlySecret []byte, binderKey []byte, identities []PskIdentity)
|
||||
|
||||
// GetPreSharedKeyCommon retrieves the final PreSharedKey-related states as defined in PreSharedKeyCommon.
|
||||
GetPreSharedKeyCommon() PreSharedKeyCommon
|
||||
|
||||
// PatchBuiltHello is called once the hello message is fully applied and marshaled.
|
||||
// Its purpose is to update the binders of PSK (Pre-Shared Key) identities.
|
||||
PatchBuiltHello(hello *PubClientHelloMsg) error
|
||||
|
||||
mustEmbedUnimplementedPreSharedKeyExtension() // this works like a type guard
|
||||
}
|
||||
|
||||
type UnimplementedPreSharedKeyExtension struct{}
|
||||
|
||||
func (UnimplementedPreSharedKeyExtension) mustEmbedUnimplementedPreSharedKeyExtension() {}
|
||||
|
||||
func (*UnimplementedPreSharedKeyExtension) IsInitialized() bool {
|
||||
panic("tls: IsInitialized is not implemented for the PreSharedKeyExtension")
|
||||
}
|
||||
|
||||
func (*UnimplementedPreSharedKeyExtension) InitializeByUtls(session *SessionState, earlySecret []byte, binderKey []byte, identities []PskIdentity) {
|
||||
panic("tls: Initialize is not implemented for the PreSharedKeyExtension")
|
||||
}
|
||||
|
||||
func (*UnimplementedPreSharedKeyExtension) writeToUConn(*UConn) error {
|
||||
panic("tls: writeToUConn is not implemented for the PreSharedKeyExtension")
|
||||
}
|
||||
|
||||
func (*UnimplementedPreSharedKeyExtension) Len() int {
|
||||
panic("tls: Len is not implemented for the PreSharedKeyExtension")
|
||||
}
|
||||
|
||||
func (*UnimplementedPreSharedKeyExtension) Read([]byte) (int, error) {
|
||||
panic("tls: Read is not implemented for the PreSharedKeyExtension")
|
||||
}
|
||||
|
||||
func (*UnimplementedPreSharedKeyExtension) GetPreSharedKeyCommon() PreSharedKeyCommon {
|
||||
panic("tls: GetPreSharedKeyCommon is not implemented for the PreSharedKeyExtension")
|
||||
}
|
||||
|
||||
func (*UnimplementedPreSharedKeyExtension) PatchBuiltHello(hello *PubClientHelloMsg) error {
|
||||
panic("tls: ReadWithRawHello is not implemented for the PreSharedKeyExtension")
|
||||
}
|
||||
|
||||
func (*UnimplementedPreSharedKeyExtension) SetOmitEmptyPsk(val bool) {
|
||||
panic("tls: SetOmitEmptyPsk is not implemented for the PreSharedKeyExtension")
|
||||
}
|
||||
|
||||
// UtlsPreSharedKeyExtension is an extension used to set the PSK extension in the
|
||||
// ClientHello.
|
||||
type UtlsPreSharedKeyExtension struct {
|
||||
UnimplementedPreSharedKeyExtension
|
||||
PreSharedKeyCommon
|
||||
cipherSuite *cipherSuiteTLS13
|
||||
cachedLength *int
|
||||
// Deprecated: Set OmitEmptyPsk in Config instead.
|
||||
OmitEmptyPsk bool
|
||||
}
|
||||
|
||||
func (e *UtlsPreSharedKeyExtension) IsInitialized() bool {
|
||||
return e.Session != nil
|
||||
}
|
||||
|
||||
func (e *UtlsPreSharedKeyExtension) InitializeByUtls(session *SessionState, earlySecret []byte, binderKey []byte, identities []PskIdentity) {
|
||||
e.Session = session
|
||||
e.EarlySecret = earlySecret
|
||||
e.BinderKey = binderKey
|
||||
e.cipherSuite = cipherSuiteTLS13ByID(e.Session.cipherSuite)
|
||||
e.Identities = identities
|
||||
e.Binders = make([][]byte, 0, len(e.Identities))
|
||||
for i := 0; i < len(e.Identities); i++ {
|
||||
e.Binders = append(e.Binders, make([]byte, e.cipherSuite.hash.Size()))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *UtlsPreSharedKeyExtension) writeToUConn(uc *UConn) error {
|
||||
uc.HandshakeState.Hello.TicketSupported = true // This doesn't matter though, as utls doesn't care about this field. We write this for consistency.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *UtlsPreSharedKeyExtension) GetPreSharedKeyCommon() PreSharedKeyCommon {
|
||||
return e.PreSharedKeyCommon
|
||||
}
|
||||
|
||||
func pskExtLen(identities []PskIdentity, binders [][]byte) int {
|
||||
if len(identities) == 0 || len(binders) == 0 {
|
||||
// If there isn't psk identities, we don't write this ticket to the client hello, and therefore the length should be 0.
|
||||
return 0
|
||||
}
|
||||
length := 4 // extension type + extension length
|
||||
length += 2 // identities length
|
||||
for _, identity := range identities {
|
||||
length += 2 + len(identity.Label) + 4 // identity length + identity + obfuscated ticket age
|
||||
}
|
||||
length += 2 // binders length
|
||||
for _, binder := range binders {
|
||||
length += len(binder) + 1
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
func (e *UtlsPreSharedKeyExtension) Len() int {
|
||||
if e.Session == nil {
|
||||
return 0
|
||||
}
|
||||
if e.cachedLength != nil {
|
||||
return *e.cachedLength
|
||||
}
|
||||
length := pskExtLen(e.Identities, e.Binders)
|
||||
e.cachedLength = &length
|
||||
return length
|
||||
}
|
||||
|
||||
func readPskIntoBytes(b []byte, identities []PskIdentity, binders [][]byte) (int, error) {
|
||||
extLen := pskExtLen(identities, binders)
|
||||
if extLen == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if len(b) < extLen {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
b[0] = byte(extensionPreSharedKey >> 8)
|
||||
b[1] = byte(extensionPreSharedKey)
|
||||
b[2] = byte((extLen - 4) >> 8)
|
||||
b[3] = byte(extLen - 4)
|
||||
|
||||
// identities length
|
||||
identitiesLength := 0
|
||||
for _, identity := range identities {
|
||||
identitiesLength += 2 + len(identity.Label) + 4 // identity length + identity + obfuscated ticket age
|
||||
}
|
||||
b[4] = byte(identitiesLength >> 8)
|
||||
b[5] = byte(identitiesLength)
|
||||
|
||||
// identities
|
||||
offset := 6
|
||||
for _, identity := range identities {
|
||||
b[offset] = byte(len(identity.Label) >> 8)
|
||||
b[offset+1] = byte(len(identity.Label))
|
||||
offset += 2
|
||||
copy(b[offset:], identity.Label)
|
||||
offset += len(identity.Label)
|
||||
b[offset] = byte(identity.ObfuscatedTicketAge >> 24)
|
||||
b[offset+1] = byte(identity.ObfuscatedTicketAge >> 16)
|
||||
b[offset+2] = byte(identity.ObfuscatedTicketAge >> 8)
|
||||
b[offset+3] = byte(identity.ObfuscatedTicketAge)
|
||||
offset += 4
|
||||
}
|
||||
|
||||
// binders length
|
||||
bindersLength := 0
|
||||
for _, binder := range binders {
|
||||
// check if binder size is valid
|
||||
bindersLength += len(binder) + 1 // binder length + binder
|
||||
}
|
||||
b[offset] = byte(bindersLength >> 8)
|
||||
b[offset+1] = byte(bindersLength)
|
||||
offset += 2
|
||||
|
||||
// binders
|
||||
for _, binder := range binders {
|
||||
b[offset] = byte(len(binder))
|
||||
offset++
|
||||
copy(b[offset:], binder)
|
||||
offset += len(binder)
|
||||
}
|
||||
|
||||
return extLen, io.EOF
|
||||
}
|
||||
|
||||
func (e *UtlsPreSharedKeyExtension) SetOmitEmptyPsk(val bool) {
|
||||
e.OmitEmptyPsk = val
|
||||
}
|
||||
|
||||
func (e *UtlsPreSharedKeyExtension) Read(b []byte) (int, error) {
|
||||
if !e.OmitEmptyPsk && e.Len() == 0 {
|
||||
return 0, ErrEmptyPsk
|
||||
}
|
||||
return readPskIntoBytes(b, e.Identities, e.Binders)
|
||||
}
|
||||
|
||||
func (e *UtlsPreSharedKeyExtension) PatchBuiltHello(hello *PubClientHelloMsg) error {
|
||||
if e.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
private := hello.getCachedPrivatePtr()
|
||||
if private == nil {
|
||||
private = hello.getPrivatePtr()
|
||||
}
|
||||
private.original = hello.Raw
|
||||
private.pskBinders = e.Binders // set the placeholder to the private Hello
|
||||
|
||||
//--- mirror loadSession() begin ---//
|
||||
transcript := e.cipherSuite.hash.New()
|
||||
helloBytes, err := private.marshalWithoutBinders() // no marshal() will be actually called, as we have set the field `raw`
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
transcript.Write(helloBytes)
|
||||
pskBinders := [][]byte{e.cipherSuite.finishedHash(e.BinderKey, transcript)}
|
||||
|
||||
if err := private.updateBinders(pskBinders); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copied from handshake_messages.go in 1.22
|
||||
lenWithoutBinders := len(helloBytes)
|
||||
b := cryptobyte.NewFixedBuilder(private.original[:lenWithoutBinders])
|
||||
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
for _, binder := range private.pskBinders {
|
||||
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||
b.AddBytes(binder)
|
||||
})
|
||||
}
|
||||
})
|
||||
if out, err := b.Bytes(); err != nil || len(out) != len(private.original) {
|
||||
return errors.New("tls: internal error: failed to update binders")
|
||||
}
|
||||
|
||||
//--- mirror loadSession() end ---//
|
||||
e.Binders = pskBinders
|
||||
|
||||
// no need to care about other PSK related fields, they will be handled separately
|
||||
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
func (e *UtlsPreSharedKeyExtension) Write(b []byte) (int, error) {
|
||||
return len(b), nil // ignore the data
|
||||
}
|
||||
|
||||
func (e *UtlsPreSharedKeyExtension) UnmarshalJSON(_ []byte) error {
|
||||
return nil // ignore the data
|
||||
}
|
||||
|
||||
// FakePreSharedKeyExtension is an extension used to set the PSK extension in the
|
||||
// ClientHello.
|
||||
//
|
||||
// It does not compute binders based on ClientHello, but uses the binders specified instead.
|
||||
// We still need to learn more of the safety
|
||||
// of hardcoding both Identities and Binders without recalculating the latter.
|
||||
type FakePreSharedKeyExtension struct {
|
||||
UnimplementedPreSharedKeyExtension
|
||||
|
||||
Identities []PskIdentity `json:"identities"`
|
||||
Binders [][]byte `json:"binders"`
|
||||
// Deprecated: Set OmitEmptyPsk in Config instead.
|
||||
OmitEmptyPsk bool
|
||||
}
|
||||
|
||||
func (e *FakePreSharedKeyExtension) IsInitialized() bool {
|
||||
return e.Identities != nil && e.Binders != nil
|
||||
}
|
||||
|
||||
func (e *FakePreSharedKeyExtension) InitializeByUtls(session *SessionState, earlySecret []byte, binderKey []byte, identities []PskIdentity) {
|
||||
panic("InitializeByUtls failed: don't let utls initialize FakePreSharedKeyExtension; provide your own identities and binders or use UtlsPreSharedKeyExtension")
|
||||
}
|
||||
|
||||
func (e *FakePreSharedKeyExtension) writeToUConn(uc *UConn) error {
|
||||
if uc.config.ClientSessionCache == nil {
|
||||
return nil // don't write the extension if there is no session cache
|
||||
}
|
||||
if session, ok := uc.config.ClientSessionCache.Get(uc.clientSessionCacheKey()); !ok || session == nil {
|
||||
return nil // don't write the extension if there is no session cache available for this session
|
||||
}
|
||||
uc.HandshakeState.Hello.PskIdentities = e.Identities
|
||||
uc.HandshakeState.Hello.PskBinders = e.Binders
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *FakePreSharedKeyExtension) Len() int {
|
||||
return pskExtLen(e.Identities, e.Binders)
|
||||
}
|
||||
|
||||
func (e *FakePreSharedKeyExtension) SetOmitEmptyPsk(val bool) {
|
||||
e.OmitEmptyPsk = val
|
||||
}
|
||||
|
||||
func (e *FakePreSharedKeyExtension) Read(b []byte) (int, error) {
|
||||
if !e.OmitEmptyPsk && e.Len() == 0 {
|
||||
return 0, ErrEmptyPsk
|
||||
}
|
||||
for _, b := range e.Binders {
|
||||
if !(anyTrue(validHashLen, func(_ int, valid *int) bool {
|
||||
return len(b) == *valid
|
||||
})) {
|
||||
return 0, errors.New("tls: FakePreSharedKeyExtension.Read failed: invalid binder size")
|
||||
}
|
||||
}
|
||||
|
||||
return readPskIntoBytes(b, e.Identities, e.Binders)
|
||||
}
|
||||
|
||||
func (e *FakePreSharedKeyExtension) GetPreSharedKeyCommon() PreSharedKeyCommon {
|
||||
return PreSharedKeyCommon{
|
||||
Identities: e.Identities,
|
||||
Binders: e.Binders,
|
||||
}
|
||||
}
|
||||
|
||||
var validHashLen = mapSlice(cipherSuitesTLS13, func(c *cipherSuiteTLS13) int {
|
||||
return c.hash.Size()
|
||||
})
|
||||
|
||||
func (*FakePreSharedKeyExtension) PatchBuiltHello(*PubClientHelloMsg) error {
|
||||
return nil // no need to patch the hello since we don't need to update binders
|
||||
}
|
||||
|
||||
func (e *FakePreSharedKeyExtension) Write(b []byte) (n int, err error) {
|
||||
fullLen := len(b)
|
||||
s := cryptobyte.String(b)
|
||||
|
||||
var identitiesLength uint16
|
||||
if !s.ReadUint16(&identitiesLength) {
|
||||
return 0, errors.New("tls: invalid PSK extension")
|
||||
}
|
||||
|
||||
// identities
|
||||
for identitiesLength > 0 {
|
||||
var identityLength uint16
|
||||
if !s.ReadUint16(&identityLength) {
|
||||
return 0, errors.New("tls: invalid PSK extension")
|
||||
}
|
||||
identitiesLength -= 2
|
||||
|
||||
if identityLength > identitiesLength {
|
||||
return 0, errors.New("tls: invalid PSK extension")
|
||||
}
|
||||
|
||||
var identity []byte
|
||||
if !s.ReadBytes(&identity, int(identityLength)) {
|
||||
return 0, errors.New("tls: invalid PSK extension")
|
||||
}
|
||||
|
||||
identitiesLength -= identityLength // identity
|
||||
|
||||
var obfuscatedTicketAge uint32
|
||||
if !s.ReadUint32(&obfuscatedTicketAge) {
|
||||
return 0, errors.New("tls: invalid PSK extension")
|
||||
}
|
||||
|
||||
e.Identities = append(e.Identities, PskIdentity{
|
||||
Label: identity,
|
||||
ObfuscatedTicketAge: obfuscatedTicketAge,
|
||||
})
|
||||
|
||||
identitiesLength -= 4 // obfuscated ticket age
|
||||
}
|
||||
|
||||
var bindersLength uint16
|
||||
if !s.ReadUint16(&bindersLength) {
|
||||
return 0, errors.New("tls: invalid PSK extension")
|
||||
}
|
||||
|
||||
// binders
|
||||
for bindersLength > 0 {
|
||||
var binderLength uint8
|
||||
if !s.ReadUint8(&binderLength) {
|
||||
return 0, errors.New("tls: invalid PSK extension")
|
||||
}
|
||||
bindersLength -= 1
|
||||
|
||||
if uint16(binderLength) > bindersLength {
|
||||
return 0, errors.New("tls: invalid PSK extension")
|
||||
}
|
||||
|
||||
var binder []byte
|
||||
if !s.ReadBytes(&binder, int(binderLength)) {
|
||||
return 0, errors.New("tls: invalid PSK extension")
|
||||
}
|
||||
|
||||
e.Binders = append(e.Binders, binder)
|
||||
|
||||
bindersLength -= uint16(binderLength)
|
||||
}
|
||||
|
||||
return fullLen, nil
|
||||
}
|
||||
|
||||
func (e *FakePreSharedKeyExtension) UnmarshalJSON(data []byte) error {
|
||||
var pskAccepter struct {
|
||||
PskIdentities []PskIdentity `json:"identities"`
|
||||
PskBinders [][]byte `json:"binders"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &pskAccepter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.Identities = pskAccepter.PskIdentities
|
||||
e.Binders = pskAccepter.PskBinders
|
||||
return nil
|
||||
}
|
||||
|
||||
// type guard
|
||||
var (
|
||||
_ PreSharedKeyExtension = (*UtlsPreSharedKeyExtension)(nil)
|
||||
_ TLSExtensionJSON = (*UtlsPreSharedKeyExtension)(nil)
|
||||
_ PreSharedKeyExtension = (*FakePreSharedKeyExtension)(nil)
|
||||
_ TLSExtensionJSON = (*FakePreSharedKeyExtension)(nil)
|
||||
_ TLSExtensionWriter = (*FakePreSharedKeyExtension)(nil)
|
||||
)
|
||||
|
||||
// type ExternalPreSharedKeyExtension struct{} // TODO: wait for whoever cares about external PSK to implement it
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Psiphon Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Released under utls licence:
|
||||
* https://github.com/refraction-networking/utls/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
// This code is a pared down version of:
|
||||
// https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/158caea562287284cc3fa5fcd1b3c97b1addf659/psiphon/common/prng/prng.go
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
crypto_rand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
const (
|
||||
PRNGSeedLength = 32
|
||||
)
|
||||
|
||||
// PRNGSeed is a PRNG seed.
|
||||
type PRNGSeed [PRNGSeedLength]byte
|
||||
|
||||
// NewPRNGSeed creates a new PRNG seed using crypto/rand.Read.
|
||||
func NewPRNGSeed() (*PRNGSeed, error) {
|
||||
seed := new(PRNGSeed)
|
||||
_, err := crypto_rand.Read(seed[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return seed, nil
|
||||
}
|
||||
|
||||
// newSaltedPRNGSeed creates a new seed derived from an existing seed and a
|
||||
// salt. A HKDF is applied to the seed and salt.
|
||||
//
|
||||
// newSaltedPRNGSeed is intended for use cases where a single seed needs to be
|
||||
// used in distinct contexts to produce independent random streams.
|
||||
func newSaltedPRNGSeed(seed *PRNGSeed, salt string) (*PRNGSeed, error) {
|
||||
saltedSeed := new(PRNGSeed)
|
||||
_, err := io.ReadFull(
|
||||
hkdf.New(sha3.New256, seed[:], []byte(salt), nil), saltedSeed[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return saltedSeed, nil
|
||||
}
|
||||
|
||||
// prng is a seeded, unbiased PRNG based on SHAKE256. that is suitable for use
|
||||
// cases such as obfuscation. Seeding is based on crypto/rand.Read.
|
||||
//
|
||||
// This PRNG is _not_ for security use cases including production cryptographic
|
||||
// key generation.
|
||||
//
|
||||
// It is safe to make concurrent calls to a PRNG instance.
|
||||
//
|
||||
// PRNG conforms to io.Reader and math/rand.Source, with additional helper
|
||||
// functions.
|
||||
type prng struct {
|
||||
rand *rand.Rand
|
||||
randomStreamMutex sync.Mutex
|
||||
randomStream sha3.ShakeHash
|
||||
}
|
||||
|
||||
// newPRNG generates a seed and creates a PRNG with that seed.
|
||||
func newPRNG() (*prng, error) {
|
||||
seed, err := NewPRNGSeed()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPRNGWithSeed(seed)
|
||||
}
|
||||
|
||||
// newPRNGWithSeed initializes a new PRNG using an existing seed.
|
||||
func newPRNGWithSeed(seed *PRNGSeed) (*prng, error) {
|
||||
shake := sha3.NewShake256()
|
||||
_, err := shake.Write(seed[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := &prng{
|
||||
randomStream: shake,
|
||||
}
|
||||
p.rand = rand.New(p)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// newPRNGWithSaltedSeed initializes a new PRNG using a seed derived from an
|
||||
// existing seed and a salt with NewSaltedSeed.
|
||||
func newPRNGWithSaltedSeed(seed *PRNGSeed, salt string) (*prng, error) {
|
||||
saltedSeed, err := newSaltedPRNGSeed(seed, salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPRNGWithSeed(saltedSeed)
|
||||
}
|
||||
|
||||
// Read reads random bytes from the PRNG stream into b. Read conforms to
|
||||
// io.Reader and always returns len(p), nil.
|
||||
func (p *prng) Read(b []byte) (int, error) {
|
||||
p.randomStreamMutex.Lock()
|
||||
defer p.randomStreamMutex.Unlock()
|
||||
|
||||
// ShakeHash.Read never returns an error:
|
||||
// https://godoc.org/golang.org/x/crypto/sha3#ShakeHash
|
||||
_, _ = io.ReadFull(p.randomStream, b)
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Int63 is equivalent to math/read.Int63.
|
||||
func (p *prng) Int63() int64 {
|
||||
i := p.Uint64()
|
||||
return int64(i & (1<<63 - 1))
|
||||
}
|
||||
|
||||
// Int63 is equivalent to math/read.Uint64.
|
||||
func (p *prng) Uint64() uint64 {
|
||||
var b [8]byte
|
||||
p.Read(b[:])
|
||||
return binary.BigEndian.Uint64(b[:])
|
||||
}
|
||||
|
||||
// Seed must exist in order to use a PRNG as a math/rand.Source. This call is
|
||||
// not supported and ignored.
|
||||
func (p *prng) Seed(_ int64) {
|
||||
}
|
||||
|
||||
// FlipWeightedCoin returns the result of a weighted
|
||||
// random coin flip. If the weight is 0.5, the outcome
|
||||
// is equally likely to be true or false. If the weight
|
||||
// is 1.0, the outcome is always true, and if the
|
||||
// weight is 0.0, the outcome is always false.
|
||||
//
|
||||
// Input weights > 1.0 are treated as 1.0.
|
||||
func (p *prng) FlipWeightedCoin(weight float64) bool {
|
||||
if weight > 1.0 {
|
||||
weight = 1.0
|
||||
}
|
||||
f := float64(p.Int63()) / float64(math.MaxInt64)
|
||||
return f > 1.0-weight
|
||||
}
|
||||
|
||||
// Intn is equivalent to math/read.Intn, except it returns 0 if n <= 0
|
||||
// instead of panicking.
|
||||
func (p *prng) Intn(n int) int {
|
||||
if n <= 0 {
|
||||
return 0
|
||||
}
|
||||
return p.rand.Intn(n)
|
||||
}
|
||||
|
||||
// Int63n is equivalent to math/read.Int63n, except it returns 0 if n <= 0
|
||||
// instead of panicking.
|
||||
func (p *prng) Int63n(n int64) int64 {
|
||||
if n <= 0 {
|
||||
return 0
|
||||
}
|
||||
return p.rand.Int63n(n)
|
||||
}
|
||||
|
||||
// Intn is equivalent to math/read.Perm.
|
||||
func (p *prng) Perm(n int) []int {
|
||||
return p.rand.Perm(n)
|
||||
}
|
||||
|
||||
// Range selects a random integer in [min, max].
|
||||
// If min < 0, min is set to 0. If max < min, min is returned.
|
||||
func (p *prng) Range(min, max int) int {
|
||||
if min < 0 {
|
||||
min = 0
|
||||
}
|
||||
if max < min {
|
||||
return min
|
||||
}
|
||||
n := p.Intn(max - min + 1)
|
||||
n += min
|
||||
return n
|
||||
}
|
||||
+925
@@ -0,0 +1,925 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdh"
|
||||
"crypto/mlkem"
|
||||
"crypto/x509"
|
||||
"hash"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClientHandshakeState includes both TLS 1.3-only and TLS 1.2-only states,
|
||||
// only one of them will be used, depending on negotiated version.
|
||||
//
|
||||
// ClientHandshakeState will be converted into and from either
|
||||
// - clientHandshakeState (TLS 1.2)
|
||||
// - clientHandshakeStateTLS13 (TLS 1.3)
|
||||
//
|
||||
// uTLS will call .handshake() on one of these private internal states,
|
||||
// to perform TLS handshake using standard crypto/tls implementation.
|
||||
type PubClientHandshakeState struct {
|
||||
C *Conn
|
||||
ServerHello *PubServerHelloMsg
|
||||
Hello *PubClientHelloMsg
|
||||
MasterSecret []byte
|
||||
Session *SessionState
|
||||
|
||||
State12 TLS12OnlyState
|
||||
State13 TLS13OnlyState
|
||||
|
||||
uconn *UConn
|
||||
}
|
||||
|
||||
// TLS 1.3 only
|
||||
type TLS13OnlyState struct {
|
||||
// Deprecated: Use KeyShareKeys instead. KeyShareKeys will take precedence if both are set.
|
||||
// Support may be removed in the future.
|
||||
EcdheKey *ecdh.PrivateKey
|
||||
// Deprecated: Use KeyShareKeys instead. This variable is no longer used.
|
||||
// Will be removed in the future.
|
||||
KeySharesParams *KeySharesParameters
|
||||
// Deprecated: Use KeyShareKeys instead. This variable is no longer used.
|
||||
// Will be removed in the future.
|
||||
KEMKey *KemPrivateKey
|
||||
|
||||
KeyShareKeys *KeySharePrivateKeys
|
||||
Suite *PubCipherSuiteTLS13
|
||||
EarlySecret []byte
|
||||
BinderKey []byte
|
||||
CertReq *CertificateRequestMsgTLS13
|
||||
UsingPSK bool // don't set this field when building client hello
|
||||
SentDummyCCS bool
|
||||
Transcript hash.Hash
|
||||
TrafficSecret []byte // client_application_traffic_secret_0
|
||||
}
|
||||
|
||||
// TLS 1.2 and before only
|
||||
type TLS12OnlyState struct {
|
||||
FinishedHash FinishedHash
|
||||
Suite PubCipherSuite
|
||||
}
|
||||
|
||||
func (chs *TLS13OnlyState) private13KeyShareKeys() *keySharePrivateKeys {
|
||||
if chs.KeyShareKeys != nil {
|
||||
return chs.KeyShareKeys.ToPrivate()
|
||||
}
|
||||
|
||||
if chs.EcdheKey != nil {
|
||||
return &keySharePrivateKeys{
|
||||
ecdhe: chs.EcdheKey,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// func kyberGoToCircl(kyberKey *mlkem768.DecapsulationKey, ecdhKey *ecdh.PrivateKey) (kem.PrivateKey, error) {
|
||||
// return hybrid.Kyber768X25519().UnmarshalBinaryPrivateKey(append(ecdhKey.Bytes(), kyberKey.Bytes()...))
|
||||
// }
|
||||
|
||||
func (chs *PubClientHandshakeState) toPrivate13() *clientHandshakeStateTLS13 {
|
||||
if chs == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &clientHandshakeStateTLS13{
|
||||
c: chs.C,
|
||||
serverHello: chs.ServerHello.getPrivatePtr(),
|
||||
hello: chs.Hello.getPrivatePtr(),
|
||||
keyShareKeys: chs.State13.private13KeyShareKeys(),
|
||||
|
||||
session: chs.Session,
|
||||
binderKey: chs.State13.BinderKey,
|
||||
|
||||
certReq: chs.State13.CertReq.toPrivate(),
|
||||
usingPSK: chs.State13.UsingPSK,
|
||||
sentDummyCCS: chs.State13.SentDummyCCS,
|
||||
suite: chs.State13.Suite.toPrivate(),
|
||||
transcript: chs.State13.Transcript,
|
||||
trafficSecret: chs.State13.TrafficSecret,
|
||||
|
||||
uconn: chs.uconn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (chs13 *clientHandshakeStateTLS13) toPublic13() *PubClientHandshakeState {
|
||||
if chs13 == nil {
|
||||
return nil
|
||||
} else {
|
||||
tls13State := TLS13OnlyState{
|
||||
KeyShareKeys: chs13.keyShareKeys.ToPublic(),
|
||||
EarlySecret: chs13.earlySecret.Secret(),
|
||||
BinderKey: chs13.binderKey,
|
||||
CertReq: chs13.certReq.toPublic(),
|
||||
UsingPSK: chs13.usingPSK,
|
||||
SentDummyCCS: chs13.sentDummyCCS,
|
||||
Suite: chs13.suite.toPublic(),
|
||||
TrafficSecret: chs13.trafficSecret,
|
||||
Transcript: chs13.transcript,
|
||||
}
|
||||
return &PubClientHandshakeState{
|
||||
C: chs13.c,
|
||||
ServerHello: chs13.serverHello.getPublicPtr(),
|
||||
Hello: chs13.hello.getPublicPtr(),
|
||||
|
||||
Session: chs13.session,
|
||||
|
||||
MasterSecret: chs13.masterSecret.Secret(),
|
||||
|
||||
State13: tls13State,
|
||||
|
||||
uconn: chs13.uconn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (chs *PubClientHandshakeState) toPrivate12() *clientHandshakeState {
|
||||
if chs == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &clientHandshakeState{
|
||||
c: chs.C,
|
||||
serverHello: chs.ServerHello.getPrivatePtr(),
|
||||
hello: chs.Hello.getPrivatePtr(),
|
||||
suite: chs.State12.Suite.getPrivatePtr(),
|
||||
session: chs.Session,
|
||||
|
||||
masterSecret: chs.MasterSecret,
|
||||
|
||||
finishedHash: chs.State12.FinishedHash.getPrivateObj(),
|
||||
|
||||
uconn: chs.uconn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (chs12 *clientHandshakeState) toPublic12() *PubClientHandshakeState {
|
||||
if chs12 == nil {
|
||||
return nil
|
||||
} else {
|
||||
tls12State := TLS12OnlyState{
|
||||
Suite: chs12.suite.getPublicObj(),
|
||||
FinishedHash: chs12.finishedHash.getPublicObj(),
|
||||
}
|
||||
return &PubClientHandshakeState{
|
||||
C: chs12.c,
|
||||
ServerHello: chs12.serverHello.getPublicPtr(),
|
||||
Hello: chs12.hello.getPublicPtr(),
|
||||
|
||||
Session: chs12.session,
|
||||
|
||||
MasterSecret: chs12.masterSecret,
|
||||
|
||||
State12: tls12State,
|
||||
|
||||
uconn: chs12.uconn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// type EcdheParameters interface {
|
||||
// ecdheParameters
|
||||
// }
|
||||
|
||||
type CertificateRequestMsgTLS13 struct {
|
||||
// Deprecated: crypto/tls no longer use this variable. This field won't be read or used by utls, but will still be populated.
|
||||
// Support may be removed in the future.
|
||||
Raw []byte
|
||||
|
||||
OcspStapling bool
|
||||
Scts bool
|
||||
SupportedSignatureAlgorithms []SignatureScheme
|
||||
SupportedSignatureAlgorithmsCert []SignatureScheme
|
||||
CertificateAuthorities [][]byte
|
||||
}
|
||||
|
||||
func (crm *certificateRequestMsgTLS13) toPublic() *CertificateRequestMsgTLS13 {
|
||||
if crm == nil {
|
||||
return nil
|
||||
} else {
|
||||
rawBytes := []byte{}
|
||||
if raw, err := crm.marshal(); err == nil {
|
||||
rawBytes = raw
|
||||
}
|
||||
|
||||
return &CertificateRequestMsgTLS13{
|
||||
Raw: rawBytes,
|
||||
OcspStapling: crm.ocspStapling,
|
||||
Scts: crm.scts,
|
||||
SupportedSignatureAlgorithms: crm.supportedSignatureAlgorithms,
|
||||
SupportedSignatureAlgorithmsCert: crm.supportedSignatureAlgorithmsCert,
|
||||
CertificateAuthorities: crm.certificateAuthorities,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (crm *CertificateRequestMsgTLS13) toPrivate() *certificateRequestMsgTLS13 {
|
||||
if crm == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &certificateRequestMsgTLS13{
|
||||
ocspStapling: crm.OcspStapling,
|
||||
scts: crm.Scts,
|
||||
supportedSignatureAlgorithms: crm.SupportedSignatureAlgorithms,
|
||||
supportedSignatureAlgorithmsCert: crm.SupportedSignatureAlgorithmsCert,
|
||||
certificateAuthorities: crm.CertificateAuthorities,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type PubCipherSuiteTLS13 struct {
|
||||
Id uint16
|
||||
KeyLen int
|
||||
Aead func(key, fixedNonce []byte) aead
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
func (c *cipherSuiteTLS13) toPublic() *PubCipherSuiteTLS13 {
|
||||
if c == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &PubCipherSuiteTLS13{
|
||||
Id: c.id,
|
||||
KeyLen: c.keyLen,
|
||||
Aead: c.aead,
|
||||
Hash: c.hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *PubCipherSuiteTLS13) toPrivate() *cipherSuiteTLS13 {
|
||||
if c == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &cipherSuiteTLS13{
|
||||
id: c.Id,
|
||||
keyLen: c.KeyLen,
|
||||
aead: c.Aead,
|
||||
hash: c.Hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type PubServerHelloMsg struct {
|
||||
Raw []byte // renamed to serverHelloMsg.original in crypto/tls
|
||||
Vers uint16
|
||||
Random []byte
|
||||
SessionId []byte
|
||||
CipherSuite uint16
|
||||
CompressionMethod uint8
|
||||
NextProtoNeg bool
|
||||
NextProtos []string
|
||||
OcspStapling bool
|
||||
Scts [][]byte
|
||||
ExtendedMasterSecret bool
|
||||
TicketSupported bool // used by go tls to determine whether to add the session ticket ext
|
||||
SecureRenegotiation []byte
|
||||
SecureRenegotiationSupported bool
|
||||
AlpnProtocol string
|
||||
|
||||
// 1.3
|
||||
SupportedVersion uint16
|
||||
ServerShare KeyShare
|
||||
SelectedIdentityPresent bool
|
||||
SelectedIdentity uint16
|
||||
Cookie []byte // HelloRetryRequest extension
|
||||
SelectedGroup CurveID // HelloRetryRequest extension
|
||||
|
||||
}
|
||||
|
||||
func (shm *PubServerHelloMsg) getPrivatePtr() *serverHelloMsg {
|
||||
if shm == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &serverHelloMsg{
|
||||
original: shm.Raw,
|
||||
vers: shm.Vers,
|
||||
random: shm.Random,
|
||||
sessionId: shm.SessionId,
|
||||
cipherSuite: shm.CipherSuite,
|
||||
compressionMethod: shm.CompressionMethod,
|
||||
nextProtoNeg: shm.NextProtoNeg,
|
||||
nextProtos: shm.NextProtos,
|
||||
ocspStapling: shm.OcspStapling,
|
||||
scts: shm.Scts,
|
||||
extendedMasterSecret: shm.ExtendedMasterSecret,
|
||||
ticketSupported: shm.TicketSupported,
|
||||
secureRenegotiation: shm.SecureRenegotiation,
|
||||
secureRenegotiationSupported: shm.SecureRenegotiationSupported,
|
||||
alpnProtocol: shm.AlpnProtocol,
|
||||
supportedVersion: shm.SupportedVersion,
|
||||
serverShare: shm.ServerShare.ToPrivate(),
|
||||
selectedIdentityPresent: shm.SelectedIdentityPresent,
|
||||
selectedIdentity: shm.SelectedIdentity,
|
||||
cookie: shm.Cookie,
|
||||
selectedGroup: shm.SelectedGroup,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (shm *serverHelloMsg) getPublicPtr() *PubServerHelloMsg {
|
||||
if shm == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &PubServerHelloMsg{
|
||||
Raw: shm.original,
|
||||
Vers: shm.vers,
|
||||
Random: shm.random,
|
||||
SessionId: shm.sessionId,
|
||||
CipherSuite: shm.cipherSuite,
|
||||
CompressionMethod: shm.compressionMethod,
|
||||
NextProtoNeg: shm.nextProtoNeg,
|
||||
NextProtos: shm.nextProtos,
|
||||
OcspStapling: shm.ocspStapling,
|
||||
Scts: shm.scts,
|
||||
ExtendedMasterSecret: shm.extendedMasterSecret,
|
||||
TicketSupported: shm.ticketSupported,
|
||||
SecureRenegotiation: shm.secureRenegotiation,
|
||||
SecureRenegotiationSupported: shm.secureRenegotiationSupported,
|
||||
AlpnProtocol: shm.alpnProtocol,
|
||||
SupportedVersion: shm.supportedVersion,
|
||||
ServerShare: shm.serverShare.ToPublic(),
|
||||
SelectedIdentityPresent: shm.selectedIdentityPresent,
|
||||
SelectedIdentity: shm.selectedIdentity,
|
||||
Cookie: shm.cookie,
|
||||
SelectedGroup: shm.selectedGroup,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type PubClientHelloMsg struct {
|
||||
Raw []byte // renamed to clientHelloMsg.original in crypto/tls
|
||||
Vers uint16
|
||||
Random []byte
|
||||
SessionId []byte
|
||||
CipherSuites []uint16
|
||||
CompressionMethods []uint8
|
||||
NextProtoNeg bool
|
||||
ServerName string
|
||||
OcspStapling bool
|
||||
Scts bool
|
||||
Ems bool // [uTLS] actually implemented due to its prevalence
|
||||
SupportedCurves []CurveID
|
||||
SupportedPoints []uint8
|
||||
TicketSupported bool
|
||||
SessionTicket []uint8
|
||||
SupportedSignatureAlgorithms []SignatureScheme
|
||||
SecureRenegotiation []byte
|
||||
SecureRenegotiationSupported bool
|
||||
AlpnProtocols []string
|
||||
|
||||
// 1.3
|
||||
SupportedSignatureAlgorithmsCert []SignatureScheme
|
||||
SupportedVersions []uint16
|
||||
Cookie []byte
|
||||
KeyShares []KeyShare
|
||||
EarlyData bool
|
||||
PskModes []uint8
|
||||
PskIdentities []PskIdentity
|
||||
PskBinders [][]byte
|
||||
QuicTransportParameters []byte
|
||||
|
||||
cachedPrivateHello *clientHelloMsg // todo: further optimize to reduce clientHelloMsg construction
|
||||
encryptedClientHello []byte
|
||||
}
|
||||
|
||||
func (chm *PubClientHelloMsg) getPrivatePtr() *clientHelloMsg {
|
||||
if chm == nil {
|
||||
return nil
|
||||
} else {
|
||||
private := &clientHelloMsg{
|
||||
original: chm.Raw,
|
||||
vers: chm.Vers,
|
||||
random: chm.Random,
|
||||
sessionId: chm.SessionId,
|
||||
cipherSuites: chm.CipherSuites,
|
||||
compressionMethods: chm.CompressionMethods,
|
||||
serverName: chm.ServerName,
|
||||
ocspStapling: chm.OcspStapling,
|
||||
supportedCurves: chm.SupportedCurves,
|
||||
supportedPoints: chm.SupportedPoints,
|
||||
ticketSupported: chm.TicketSupported,
|
||||
sessionTicket: chm.SessionTicket,
|
||||
supportedSignatureAlgorithms: chm.SupportedSignatureAlgorithms,
|
||||
supportedSignatureAlgorithmsCert: chm.SupportedSignatureAlgorithmsCert,
|
||||
secureRenegotiationSupported: chm.SecureRenegotiationSupported,
|
||||
secureRenegotiation: chm.SecureRenegotiation,
|
||||
extendedMasterSecret: chm.Ems,
|
||||
alpnProtocols: chm.AlpnProtocols,
|
||||
scts: chm.Scts,
|
||||
|
||||
supportedVersions: chm.SupportedVersions,
|
||||
cookie: chm.Cookie,
|
||||
keyShares: KeyShares(chm.KeyShares).ToPrivate(),
|
||||
earlyData: chm.EarlyData,
|
||||
pskModes: chm.PskModes,
|
||||
pskIdentities: PskIdentities(chm.PskIdentities).ToPrivate(),
|
||||
pskBinders: chm.PskBinders,
|
||||
quicTransportParameters: chm.QuicTransportParameters,
|
||||
encryptedClientHello: chm.encryptedClientHello,
|
||||
|
||||
nextProtoNeg: chm.NextProtoNeg,
|
||||
}
|
||||
chm.cachedPrivateHello = private
|
||||
return private
|
||||
}
|
||||
}
|
||||
|
||||
func (chm *PubClientHelloMsg) getCachedPrivatePtr() *clientHelloMsg {
|
||||
if chm == nil {
|
||||
return nil
|
||||
} else {
|
||||
return chm.cachedPrivateHello
|
||||
}
|
||||
}
|
||||
|
||||
func (chm *clientHelloMsg) getPublicPtr() *PubClientHelloMsg {
|
||||
if chm == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &PubClientHelloMsg{
|
||||
Raw: chm.original,
|
||||
Vers: chm.vers,
|
||||
Random: chm.random,
|
||||
SessionId: chm.sessionId,
|
||||
CipherSuites: chm.cipherSuites,
|
||||
CompressionMethods: chm.compressionMethods,
|
||||
NextProtoNeg: chm.nextProtoNeg,
|
||||
ServerName: chm.serverName,
|
||||
OcspStapling: chm.ocspStapling,
|
||||
Scts: chm.scts,
|
||||
Ems: chm.extendedMasterSecret,
|
||||
SupportedCurves: chm.supportedCurves,
|
||||
SupportedPoints: chm.supportedPoints,
|
||||
TicketSupported: chm.ticketSupported,
|
||||
SessionTicket: chm.sessionTicket,
|
||||
SupportedSignatureAlgorithms: chm.supportedSignatureAlgorithms,
|
||||
SecureRenegotiation: chm.secureRenegotiation,
|
||||
SecureRenegotiationSupported: chm.secureRenegotiationSupported,
|
||||
AlpnProtocols: chm.alpnProtocols,
|
||||
|
||||
SupportedSignatureAlgorithmsCert: chm.supportedSignatureAlgorithmsCert,
|
||||
SupportedVersions: chm.supportedVersions,
|
||||
Cookie: chm.cookie,
|
||||
KeyShares: keyShares(chm.keyShares).ToPublic(),
|
||||
EarlyData: chm.earlyData,
|
||||
PskModes: chm.pskModes,
|
||||
PskIdentities: pskIdentities(chm.pskIdentities).ToPublic(),
|
||||
PskBinders: chm.pskBinders,
|
||||
QuicTransportParameters: chm.quicTransportParameters,
|
||||
cachedPrivateHello: chm,
|
||||
encryptedClientHello: chm.encryptedClientHello,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalClientHello allows external code to parse raw client hellos.
|
||||
// It returns nil on failure.
|
||||
func UnmarshalClientHello(data []byte) *PubClientHelloMsg {
|
||||
m := &clientHelloMsg{}
|
||||
if m.unmarshal(data) {
|
||||
return m.getPublicPtr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal allows external code to convert a ClientHello object back into
|
||||
// raw bytes.
|
||||
func (chm *PubClientHelloMsg) Marshal() ([]byte, error) {
|
||||
return chm.getPrivatePtr().marshal()
|
||||
}
|
||||
|
||||
// A CipherSuite is a specific combination of key agreement, cipher and MAC
|
||||
// function. All cipher suites currently assume RSA key agreement.
|
||||
type PubCipherSuite struct {
|
||||
Id uint16
|
||||
// the lengths, in bytes, of the key material needed for each component.
|
||||
KeyLen int
|
||||
MacLen int
|
||||
IvLen int
|
||||
Ka func(version uint16) keyAgreement
|
||||
// flags is a bitmask of the suite* values, above.
|
||||
Flags int
|
||||
Cipher func(key, iv []byte, isRead bool) interface{}
|
||||
Mac func(macKey []byte) hash.Hash
|
||||
Aead func(key, fixedNonce []byte) aead
|
||||
}
|
||||
|
||||
func (cs *PubCipherSuite) getPrivatePtr() *cipherSuite {
|
||||
if cs == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &cipherSuite{
|
||||
id: cs.Id,
|
||||
keyLen: cs.KeyLen,
|
||||
macLen: cs.MacLen,
|
||||
ivLen: cs.IvLen,
|
||||
ka: cs.Ka,
|
||||
flags: cs.Flags,
|
||||
cipher: cs.Cipher,
|
||||
mac: cs.Mac,
|
||||
aead: cs.Aead,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *cipherSuite) getPublicObj() PubCipherSuite {
|
||||
if cs == nil {
|
||||
return PubCipherSuite{}
|
||||
} else {
|
||||
return PubCipherSuite{
|
||||
Id: cs.id,
|
||||
KeyLen: cs.keyLen,
|
||||
MacLen: cs.macLen,
|
||||
IvLen: cs.ivLen,
|
||||
Ka: cs.ka,
|
||||
Flags: cs.flags,
|
||||
Cipher: cs.cipher,
|
||||
Mac: cs.mac,
|
||||
Aead: cs.aead,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A FinishedHash calculates the hash of a set of handshake messages suitable
|
||||
// for including in a Finished message.
|
||||
type FinishedHash struct {
|
||||
Client hash.Hash
|
||||
Server hash.Hash
|
||||
|
||||
// Prior to TLS 1.2, an additional MD5 hash is required.
|
||||
ClientMD5 hash.Hash
|
||||
ServerMD5 hash.Hash
|
||||
|
||||
// In TLS 1.2, a full buffer is sadly required.
|
||||
Buffer []byte
|
||||
|
||||
Version uint16
|
||||
Prfv2 prfFunc
|
||||
|
||||
// Deprecated: Use Prfv2 instead. Prfv2 will be used if both are set.
|
||||
Prf prfFuncOld
|
||||
}
|
||||
|
||||
type prfFuncOld func(result, secret, label, seed []byte)
|
||||
|
||||
func prfFuncV1ToV2(v1 prfFuncOld) prfFunc {
|
||||
return func(secret []byte, label string, seed []byte, keyLen int) []byte {
|
||||
res := make([]byte, keyLen)
|
||||
v1(res, secret, []byte(label), seed)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
func prfFuncV2ToV1(v2 prfFunc) prfFuncOld {
|
||||
return func(result, secret, label, seed []byte) {
|
||||
copy(result, v2(secret, string(label), seed, len(result)))
|
||||
}
|
||||
}
|
||||
|
||||
func (fh *FinishedHash) getPrivateObj() finishedHash {
|
||||
if fh == nil {
|
||||
return finishedHash{}
|
||||
} else {
|
||||
res := finishedHash{
|
||||
client: fh.Client,
|
||||
server: fh.Server,
|
||||
clientMD5: fh.ClientMD5,
|
||||
serverMD5: fh.ServerMD5,
|
||||
buffer: fh.Buffer,
|
||||
version: fh.Version,
|
||||
}
|
||||
|
||||
if fh.Prfv2 != nil {
|
||||
res.prf = fh.Prfv2
|
||||
} else if fh.Prf != nil {
|
||||
res.prf = prfFuncV1ToV2(fh.Prf)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
func (fh *finishedHash) getPublicObj() FinishedHash {
|
||||
if fh == nil {
|
||||
return FinishedHash{}
|
||||
} else {
|
||||
res := FinishedHash{
|
||||
Client: fh.client,
|
||||
Server: fh.server,
|
||||
ClientMD5: fh.clientMD5,
|
||||
ServerMD5: fh.serverMD5,
|
||||
Buffer: fh.buffer,
|
||||
Version: fh.version,
|
||||
}
|
||||
|
||||
res.Prfv2 = fh.prf
|
||||
res.Prf = prfFuncV2ToV1(fh.prf)
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
// TLS 1.3 Key Share. See RFC 8446, Section 4.2.8.
|
||||
type KeyShare struct {
|
||||
Group CurveID `json:"group"`
|
||||
Data []byte `json:"key_exchange,omitempty"` // optional
|
||||
}
|
||||
|
||||
func (ks KeyShare) ToPrivate() keyShare {
|
||||
return keyShare{group: ks.Group, data: ks.Data}
|
||||
}
|
||||
|
||||
func (ks keyShare) ToPublic() KeyShare {
|
||||
return KeyShare{Group: ks.group, Data: ks.data}
|
||||
}
|
||||
|
||||
type KeyShares []KeyShare
|
||||
type keyShares []keyShare
|
||||
|
||||
func (kss keyShares) ToPublic() []KeyShare {
|
||||
var KSS []KeyShare
|
||||
for _, ks := range kss {
|
||||
KSS = append(KSS, ks.ToPublic())
|
||||
}
|
||||
return KSS
|
||||
}
|
||||
func (KSS KeyShares) ToPrivate() []keyShare {
|
||||
var kss []keyShare
|
||||
for _, KS := range KSS {
|
||||
kss = append(kss, KS.ToPrivate())
|
||||
}
|
||||
return kss
|
||||
}
|
||||
|
||||
// TLS 1.3 PSK Identity. Can be a Session Ticket, or a reference to a saved
|
||||
// session. See RFC 8446, Section 4.2.11.
|
||||
type PskIdentity struct {
|
||||
Label []byte `json:"identity"`
|
||||
ObfuscatedTicketAge uint32 `json:"obfuscated_ticket_age"`
|
||||
}
|
||||
|
||||
type PskIdentities []PskIdentity
|
||||
type pskIdentities []pskIdentity
|
||||
|
||||
func (pss pskIdentities) ToPublic() []PskIdentity {
|
||||
var PSS []PskIdentity
|
||||
for _, ps := range pss {
|
||||
PSS = append(PSS, PskIdentity{Label: ps.label, ObfuscatedTicketAge: ps.obfuscatedTicketAge})
|
||||
}
|
||||
return PSS
|
||||
}
|
||||
|
||||
func (PSS PskIdentities) ToPrivate() []pskIdentity {
|
||||
var pss []pskIdentity
|
||||
for _, PS := range PSS {
|
||||
pss = append(pss, pskIdentity{label: PS.Label, obfuscatedTicketAge: PS.ObfuscatedTicketAge})
|
||||
}
|
||||
return pss
|
||||
}
|
||||
|
||||
// ClientSessionState is public, but all its fields are private. Let's add setters, getters and constructor
|
||||
|
||||
// ClientSessionState contains the state needed by clients to resume TLS sessions.
|
||||
func MakeClientSessionState(
|
||||
SessionTicket []uint8,
|
||||
Vers uint16,
|
||||
CipherSuite uint16,
|
||||
MasterSecret []byte,
|
||||
ServerCertificates []*x509.Certificate,
|
||||
VerifiedChains [][]*x509.Certificate) *ClientSessionState {
|
||||
// TODO: Add EMS to this constructor in uTLS v2
|
||||
css := &ClientSessionState{
|
||||
session: &SessionState{
|
||||
version: Vers,
|
||||
cipherSuite: CipherSuite,
|
||||
secret: MasterSecret,
|
||||
peerCertificates: ServerCertificates,
|
||||
verifiedChains: VerifiedChains,
|
||||
ticket: SessionTicket,
|
||||
},
|
||||
}
|
||||
return css
|
||||
}
|
||||
|
||||
// Encrypted ticket used for session resumption with server
|
||||
func (css *ClientSessionState) SessionTicket() []uint8 {
|
||||
return css.session.ticket
|
||||
}
|
||||
|
||||
// SSL/TLS version negotiated for the session
|
||||
func (css *ClientSessionState) Vers() uint16 {
|
||||
return css.session.version
|
||||
}
|
||||
|
||||
// Ciphersuite negotiated for the session
|
||||
func (css *ClientSessionState) CipherSuite() uint16 {
|
||||
return css.session.cipherSuite
|
||||
}
|
||||
|
||||
// MasterSecret generated by client on a full handshake
|
||||
func (css *ClientSessionState) MasterSecret() []byte {
|
||||
return css.session.secret
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) EMS() bool {
|
||||
return css.session.extMasterSecret
|
||||
}
|
||||
|
||||
// Certificate chain presented by the server
|
||||
func (css *ClientSessionState) ServerCertificates() []*x509.Certificate {
|
||||
return css.session.peerCertificates
|
||||
}
|
||||
|
||||
// Certificate chains we built for verification
|
||||
func (css *ClientSessionState) VerifiedChains() [][]*x509.Certificate {
|
||||
return css.session.verifiedChains
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) SetSessionTicket(SessionTicket []uint8) {
|
||||
css.session.ticket = SessionTicket
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) SetVers(Vers uint16) {
|
||||
if css.session == nil {
|
||||
css.session = &SessionState{}
|
||||
}
|
||||
css.session.version = Vers
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) SetCipherSuite(CipherSuite uint16) {
|
||||
if css.session == nil {
|
||||
css.session = &SessionState{}
|
||||
}
|
||||
css.session.cipherSuite = CipherSuite
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) SetCreatedAt(createdAt uint64) {
|
||||
if css.session == nil {
|
||||
css.session = &SessionState{}
|
||||
}
|
||||
css.session.createdAt = createdAt
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) SetMasterSecret(MasterSecret []byte) {
|
||||
if css.session == nil {
|
||||
css.session = &SessionState{}
|
||||
}
|
||||
css.session.secret = MasterSecret
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) SetEMS(ems bool) {
|
||||
if css.session == nil {
|
||||
css.session = &SessionState{}
|
||||
}
|
||||
css.session.extMasterSecret = ems
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) SetServerCertificates(ServerCertificates []*x509.Certificate) {
|
||||
if css.session == nil {
|
||||
css.session = &SessionState{}
|
||||
}
|
||||
css.session.peerCertificates = ServerCertificates
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) SetVerifiedChains(VerifiedChains [][]*x509.Certificate) {
|
||||
if css.session == nil {
|
||||
css.session = &SessionState{}
|
||||
}
|
||||
css.session.verifiedChains = VerifiedChains
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) SetUseBy(useBy uint64) {
|
||||
if css.session == nil {
|
||||
css.session = &SessionState{}
|
||||
}
|
||||
css.session.useBy = useBy
|
||||
}
|
||||
|
||||
func (css *ClientSessionState) SetAgeAdd(ageAdd uint32) {
|
||||
if css.session == nil {
|
||||
css.session = &SessionState{}
|
||||
}
|
||||
css.session.ageAdd = ageAdd
|
||||
}
|
||||
|
||||
// TicketKey is the internal representation of a session ticket key.
|
||||
type TicketKey struct {
|
||||
AesKey [16]byte
|
||||
HmacKey [16]byte
|
||||
// created is the time at which this ticket key was created. See Config.ticketKeys.
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
type TicketKeys []TicketKey
|
||||
type ticketKeys []ticketKey
|
||||
|
||||
func TicketKeyFromBytes(b [32]byte) TicketKey {
|
||||
// [uTLS]
|
||||
// empty config is required
|
||||
config := &Config{}
|
||||
tk := config.ticketKeyFromBytes(b)
|
||||
return tk.ToPublic()
|
||||
}
|
||||
|
||||
func (tk ticketKey) ToPublic() TicketKey {
|
||||
return TicketKey{
|
||||
AesKey: tk.aesKey,
|
||||
HmacKey: tk.hmacKey,
|
||||
Created: tk.created,
|
||||
}
|
||||
}
|
||||
|
||||
func (TK TicketKey) ToPrivate() ticketKey {
|
||||
return ticketKey{
|
||||
aesKey: TK.AesKey,
|
||||
hmacKey: TK.HmacKey,
|
||||
created: TK.Created,
|
||||
}
|
||||
}
|
||||
|
||||
func (tks ticketKeys) ToPublic() []TicketKey {
|
||||
var TKS []TicketKey
|
||||
for _, ks := range tks {
|
||||
TKS = append(TKS, ks.ToPublic())
|
||||
}
|
||||
return TKS
|
||||
}
|
||||
|
||||
func (TKS TicketKeys) ToPrivate() []ticketKey {
|
||||
var tks []ticketKey
|
||||
for _, TK := range TKS {
|
||||
tks = append(tks, TK.ToPrivate())
|
||||
}
|
||||
return tks
|
||||
}
|
||||
|
||||
type kemPrivateKey struct {
|
||||
secretKey any
|
||||
curveID CurveID
|
||||
}
|
||||
|
||||
// Deprecated: Use KeySharePrivateKeys instead. This type is no longer used.
|
||||
// Will be removed in the future.
|
||||
type KemPrivateKey struct {
|
||||
SecretKey any
|
||||
CurveID CurveID
|
||||
}
|
||||
|
||||
func (kpk *KemPrivateKey) ToPrivate() *kemPrivateKey {
|
||||
if kpk == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &kemPrivateKey{
|
||||
secretKey: kpk.SecretKey,
|
||||
curveID: kpk.CurveID,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (kpk *kemPrivateKey) ToPublic() *KemPrivateKey {
|
||||
if kpk == nil {
|
||||
return nil
|
||||
} else {
|
||||
return &KemPrivateKey{
|
||||
SecretKey: kpk.secretKey,
|
||||
CurveID: kpk.curveID,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type KeySharePrivateKeys struct {
|
||||
CurveID CurveID
|
||||
Ecdhe *ecdh.PrivateKey
|
||||
Mlkem *mlkem.DecapsulationKey768
|
||||
MlkemEcdhe *ecdh.PrivateKey
|
||||
}
|
||||
|
||||
func (ksp *KeySharePrivateKeys) ToPrivate() *keySharePrivateKeys {
|
||||
if ksp == nil {
|
||||
return nil
|
||||
}
|
||||
return &keySharePrivateKeys{
|
||||
curveID: ksp.CurveID,
|
||||
ecdhe: ksp.Ecdhe,
|
||||
mlkem: ksp.Mlkem,
|
||||
mlkemEcdhe: ksp.MlkemEcdhe,
|
||||
}
|
||||
}
|
||||
|
||||
func (ksp *keySharePrivateKeys) ToPublic() *KeySharePrivateKeys {
|
||||
if ksp == nil {
|
||||
return nil
|
||||
}
|
||||
return &KeySharePrivateKeys{
|
||||
CurveID: ksp.curveID,
|
||||
Ecdhe: ksp.ecdhe,
|
||||
Mlkem: ksp.mlkem,
|
||||
MlkemEcdhe: ksp.mlkemEcdhe,
|
||||
}
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
// Copyright 2023 The uTLS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A UQUICConn represents a connection which uses a QUIC implementation as the underlying
|
||||
// transport as described in RFC 9001.
|
||||
//
|
||||
// Methods of UQUICConn are not safe for concurrent use.
|
||||
type UQUICConn struct {
|
||||
conn *UConn
|
||||
|
||||
sessionTicketSent bool
|
||||
}
|
||||
|
||||
// QUICClient returns a new TLS client side connection using QUICTransport as the
|
||||
// underlying transport. The config cannot be nil.
|
||||
//
|
||||
// The config's MinVersion must be at least TLS 1.3.
|
||||
func UQUICClient(config *QUICConfig, clientHelloID ClientHelloID) *UQUICConn {
|
||||
return newUQUICConn(UClient(nil, config.TLSConfig, clientHelloID))
|
||||
}
|
||||
|
||||
func newUQUICConn(uconn *UConn) *UQUICConn {
|
||||
uconn.quic = &quicState{
|
||||
signalc: make(chan struct{}),
|
||||
blockedc: make(chan struct{}),
|
||||
}
|
||||
uconn.quic.events = uconn.quic.eventArr[:0]
|
||||
return &UQUICConn{
|
||||
conn: uconn,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the client or server handshake protocol.
|
||||
// It may produce connection events, which may be read with NextEvent.
|
||||
//
|
||||
// Start must be called at most once.
|
||||
func (q *UQUICConn) Start(ctx context.Context) error {
|
||||
if q.conn.quic.started {
|
||||
return quicError(errors.New("tls: Start called more than once"))
|
||||
}
|
||||
q.conn.quic.started = true
|
||||
if q.conn.config.MinVersion < VersionTLS13 {
|
||||
return quicError(errors.New("tls: Config MinVersion must be at least TLS 1.13"))
|
||||
}
|
||||
go q.conn.HandshakeContext(ctx)
|
||||
if _, ok := <-q.conn.quic.blockedc; !ok {
|
||||
return q.conn.handshakeErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *UQUICConn) ApplyPreset(p *ClientHelloSpec) error {
|
||||
return q.conn.ApplyPreset(p)
|
||||
}
|
||||
|
||||
// NextEvent returns the next event occurring on the connection.
|
||||
// It returns an event with a Kind of QUICNoEvent when no events are available.
|
||||
func (q *UQUICConn) NextEvent() QUICEvent {
|
||||
qs := q.conn.quic
|
||||
if last := qs.nextEvent - 1; last >= 0 && len(qs.events[last].Data) > 0 {
|
||||
// Write over some of the previous event's data,
|
||||
// to catch callers erroniously retaining it.
|
||||
qs.events[last].Data[0] = 0
|
||||
}
|
||||
if qs.nextEvent >= len(qs.events) {
|
||||
qs.events = qs.events[:0]
|
||||
qs.nextEvent = 0
|
||||
return QUICEvent{Kind: QUICNoEvent}
|
||||
}
|
||||
e := qs.events[qs.nextEvent]
|
||||
qs.events[qs.nextEvent] = QUICEvent{} // zero out references to data
|
||||
qs.nextEvent++
|
||||
return e
|
||||
}
|
||||
|
||||
// Close closes the connection and stops any in-progress handshake.
|
||||
func (q *UQUICConn) Close() error {
|
||||
if q.conn.quic.cancel == nil {
|
||||
return nil // never started
|
||||
}
|
||||
q.conn.quic.cancel()
|
||||
for range q.conn.quic.blockedc {
|
||||
// Wait for the handshake goroutine to return.
|
||||
}
|
||||
return q.conn.handshakeErr
|
||||
}
|
||||
|
||||
// HandleData handles handshake bytes received from the peer.
|
||||
// It may produce connection events, which may be read with NextEvent.
|
||||
func (q *UQUICConn) HandleData(level QUICEncryptionLevel, data []byte) error {
|
||||
c := q.conn
|
||||
if c.in.level != level {
|
||||
return quicError(c.in.setErrorLocked(errors.New("tls: handshake data received at wrong level")))
|
||||
}
|
||||
c.quic.readbuf = data
|
||||
<-c.quic.signalc
|
||||
_, ok := <-c.quic.blockedc
|
||||
if ok {
|
||||
// The handshake goroutine is waiting for more data.
|
||||
return nil
|
||||
}
|
||||
// The handshake goroutine has exited.
|
||||
c.handshakeMutex.Lock()
|
||||
defer c.handshakeMutex.Unlock()
|
||||
c.hand.Write(c.quic.readbuf)
|
||||
c.quic.readbuf = nil
|
||||
for q.conn.hand.Len() >= 4 && q.conn.handshakeErr == nil {
|
||||
b := q.conn.hand.Bytes()
|
||||
n := int(b[1])<<16 | int(b[2])<<8 | int(b[3])
|
||||
if n > maxHandshake {
|
||||
q.conn.handshakeErr = fmt.Errorf("tls: handshake message of length %d bytes exceeds maximum of %d bytes", n, maxHandshake)
|
||||
break
|
||||
}
|
||||
if len(b) < 4+n {
|
||||
return nil
|
||||
}
|
||||
if err := q.conn.handlePostHandshakeMessage(); err != nil {
|
||||
q.conn.handshakeErr = err
|
||||
}
|
||||
}
|
||||
if q.conn.handshakeErr != nil {
|
||||
return quicError(q.conn.handshakeErr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendSessionTicket sends a session ticket to the client.
|
||||
// It produces connection events, which may be read with NextEvent.
|
||||
// Currently, it can only be called once.
|
||||
func (q *UQUICConn) SendSessionTicket(opts QUICSessionTicketOptions) error {
|
||||
c := q.conn
|
||||
if !c.isHandshakeComplete.Load() {
|
||||
return quicError(errors.New("tls: SendSessionTicket called before handshake completed"))
|
||||
}
|
||||
if c.isClient {
|
||||
return quicError(errors.New("tls: SendSessionTicket called on the client"))
|
||||
}
|
||||
if q.sessionTicketSent {
|
||||
return quicError(errors.New("tls: SendSessionTicket called multiple times"))
|
||||
}
|
||||
q.sessionTicketSent = true
|
||||
return quicError(c.sendSessionTicket(opts.EarlyData, opts.Extra))
|
||||
}
|
||||
|
||||
// ConnectionState returns basic TLS details about the connection.
|
||||
func (q *UQUICConn) ConnectionState() ConnectionState {
|
||||
return q.conn.ConnectionState()
|
||||
}
|
||||
|
||||
// SetTransportParameters sets the transport parameters to send to the peer.
|
||||
//
|
||||
// Server connections may delay setting the transport parameters until after
|
||||
// receiving the client's transport parameters. See QUICTransportParametersRequired.
|
||||
func (q *UQUICConn) SetTransportParameters(params []byte) {
|
||||
if params == nil {
|
||||
params = []byte{}
|
||||
}
|
||||
q.conn.quic.transportParams = params // this won't be used for building ClientHello when using a preset
|
||||
|
||||
// // instead, we set the transport parameters hold by the ClientHello
|
||||
// for _, ext := range q.conn.Extensions {
|
||||
// if qtp, ok := ext.(*QUICTransportParametersExtension); ok {
|
||||
// qtp.TransportParametersExtData = params
|
||||
// }
|
||||
// }
|
||||
|
||||
if q.conn.quic.started {
|
||||
<-q.conn.quic.signalc
|
||||
<-q.conn.quic.blockedc
|
||||
}
|
||||
}
|
||||
|
||||
func (uc *UConn) QUICSetReadSecret(level QUICEncryptionLevel, suite uint16, secret []byte) {
|
||||
uc.quic.events = append(uc.quic.events, QUICEvent{
|
||||
Kind: QUICSetReadSecret,
|
||||
Level: level,
|
||||
Suite: suite,
|
||||
Data: secret,
|
||||
})
|
||||
}
|
||||
|
||||
func (uc *UConn) QUICSetWriteSecret(level QUICEncryptionLevel, suite uint16, secret []byte) {
|
||||
uc.quic.events = append(uc.quic.events, QUICEvent{
|
||||
Kind: QUICSetWriteSecret,
|
||||
Level: level,
|
||||
Suite: suite,
|
||||
Data: secret,
|
||||
})
|
||||
}
|
||||
|
||||
func (uc *UConn) QUICGetTransportParameters() ([]byte, error) {
|
||||
if uc.quic.transportParams == nil {
|
||||
uc.quic.events = append(uc.quic.events, QUICEvent{
|
||||
Kind: QUICTransportParametersRequired,
|
||||
})
|
||||
}
|
||||
for uc.quic.transportParams == nil {
|
||||
if err := uc.quicWaitForSignal(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return uc.quic.transportParams, nil
|
||||
}
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"math/big"
|
||||
|
||||
"github.com/refraction-networking/utls/internal/quicvarint"
|
||||
)
|
||||
|
||||
const (
|
||||
// RFC IDs
|
||||
max_idle_timeout uint64 = 0x1
|
||||
max_udp_payload_size uint64 = 0x3
|
||||
initial_max_data uint64 = 0x4
|
||||
initial_max_stream_data_bidi_local uint64 = 0x5
|
||||
initial_max_stream_data_bidi_remote uint64 = 0x6
|
||||
initial_max_stream_data_uni uint64 = 0x7
|
||||
initial_max_streams_bidi uint64 = 0x8
|
||||
initial_max_streams_uni uint64 = 0x9
|
||||
max_ack_delay uint64 = 0xb
|
||||
disable_active_migration uint64 = 0xc
|
||||
active_connection_id_limit uint64 = 0xe
|
||||
initial_source_connection_id uint64 = 0xf
|
||||
version_information uint64 = 0x11 // RFC 9368
|
||||
padding uint64 = 0x15
|
||||
max_datagram_frame_size uint64 = 0x20 // RFC 9221
|
||||
grease_quic_bit uint64 = 0x2ab2
|
||||
|
||||
// Legacy IDs from draft
|
||||
version_information_legacy uint64 = 0xff73db // draft-ietf-quic-version-negotiation-13 and early
|
||||
)
|
||||
|
||||
type TransportParameters []TransportParameter
|
||||
|
||||
func (tps TransportParameters) Marshal() []byte {
|
||||
var b []byte
|
||||
for _, tp := range tps {
|
||||
b = quicvarint.Append(b, tp.ID())
|
||||
b = quicvarint.Append(b, uint64(len(tp.Value())))
|
||||
b = append(b, tp.Value()...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// TransportParameter represents a QUIC transport parameter.
|
||||
//
|
||||
// Caller will write the following to the wire:
|
||||
//
|
||||
// var b []byte
|
||||
// b = quicvarint.Append(b, ID())
|
||||
// b = quicvarint.Append(b, len(Value()))
|
||||
// b = append(b, Value())
|
||||
//
|
||||
// Therefore Value() should return the exact bytes to be written to the wire AFTER the length field,
|
||||
// i.e., the bytes MAY be a Variable Length Integer per RFC depending on the type of the transport
|
||||
// parameter, but MUST NOT including the length field unless the parameter is defined so.
|
||||
type TransportParameter interface {
|
||||
ID() uint64
|
||||
Value() []byte
|
||||
}
|
||||
|
||||
type GREASETransportParameter struct {
|
||||
IdOverride uint64 // if set to a valid GREASE ID, use this instead of randomly generated one.
|
||||
Length uint16 // if len(ValueOverride) == 0, will generate random data of this size.
|
||||
ValueOverride []byte // if len(ValueOverride) > 0, use this instead of random bytes.
|
||||
}
|
||||
|
||||
const (
|
||||
GREASE_MAX_MULTIPLIER = (0x3FFFFFFFFFFFFFFF - 27) / 31
|
||||
)
|
||||
|
||||
// IsGREASEID returns true if id is a valid GREASE ID for
|
||||
// transport parameters.
|
||||
func (GREASETransportParameter) IsGREASEID(id uint64) bool {
|
||||
return id >= 27 && (id-27)%31 == 0
|
||||
}
|
||||
|
||||
// GetGREASEID returns a random valid GREASE ID for transport parameters.
|
||||
func (GREASETransportParameter) GetGREASEID() uint64 {
|
||||
max := big.NewInt(GREASE_MAX_MULTIPLIER)
|
||||
|
||||
randMultiply, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
return 27
|
||||
}
|
||||
|
||||
return 27 + randMultiply.Uint64()*31
|
||||
}
|
||||
|
||||
func (g *GREASETransportParameter) ID() uint64 {
|
||||
if !g.IsGREASEID(g.IdOverride) {
|
||||
g.IdOverride = g.GetGREASEID()
|
||||
}
|
||||
return g.IdOverride
|
||||
}
|
||||
|
||||
func (g *GREASETransportParameter) Value() []byte {
|
||||
if len(g.ValueOverride) == 0 {
|
||||
g.ValueOverride = make([]byte, g.Length)
|
||||
rand.Read(g.ValueOverride)
|
||||
}
|
||||
return g.ValueOverride
|
||||
}
|
||||
|
||||
type MaxIdleTimeout uint64 // in milliseconds
|
||||
|
||||
func (MaxIdleTimeout) ID() uint64 {
|
||||
return max_idle_timeout
|
||||
}
|
||||
|
||||
func (m MaxIdleTimeout) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(m))
|
||||
}
|
||||
|
||||
type MaxUDPPayloadSize uint64
|
||||
|
||||
func (MaxUDPPayloadSize) ID() uint64 {
|
||||
return max_udp_payload_size
|
||||
}
|
||||
|
||||
func (m MaxUDPPayloadSize) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(m))
|
||||
}
|
||||
|
||||
type InitialMaxData uint64
|
||||
|
||||
func (InitialMaxData) ID() uint64 {
|
||||
return initial_max_data
|
||||
}
|
||||
|
||||
func (i InitialMaxData) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(i))
|
||||
}
|
||||
|
||||
type InitialMaxStreamDataBidiLocal uint64
|
||||
|
||||
func (InitialMaxStreamDataBidiLocal) ID() uint64 {
|
||||
return initial_max_stream_data_bidi_local
|
||||
}
|
||||
|
||||
func (i InitialMaxStreamDataBidiLocal) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(i))
|
||||
}
|
||||
|
||||
type InitialMaxStreamDataBidiRemote uint64
|
||||
|
||||
func (InitialMaxStreamDataBidiRemote) ID() uint64 {
|
||||
return initial_max_stream_data_bidi_remote
|
||||
}
|
||||
|
||||
func (i InitialMaxStreamDataBidiRemote) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(i))
|
||||
}
|
||||
|
||||
type InitialMaxStreamDataUni uint64
|
||||
|
||||
func (InitialMaxStreamDataUni) ID() uint64 {
|
||||
return initial_max_stream_data_uni
|
||||
}
|
||||
|
||||
func (i InitialMaxStreamDataUni) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(i))
|
||||
}
|
||||
|
||||
type InitialMaxStreamsBidi uint64
|
||||
|
||||
func (InitialMaxStreamsBidi) ID() uint64 {
|
||||
return initial_max_streams_bidi
|
||||
}
|
||||
|
||||
func (i InitialMaxStreamsBidi) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(i))
|
||||
}
|
||||
|
||||
type InitialMaxStreamsUni uint64
|
||||
|
||||
func (InitialMaxStreamsUni) ID() uint64 {
|
||||
return initial_max_streams_uni
|
||||
}
|
||||
|
||||
func (i InitialMaxStreamsUni) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(i))
|
||||
}
|
||||
|
||||
type MaxAckDelay uint64
|
||||
|
||||
func (MaxAckDelay) ID() uint64 {
|
||||
return max_ack_delay
|
||||
}
|
||||
|
||||
func (m MaxAckDelay) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(m))
|
||||
}
|
||||
|
||||
type DisableActiveMigration struct{}
|
||||
|
||||
func (*DisableActiveMigration) ID() uint64 {
|
||||
return disable_active_migration
|
||||
}
|
||||
|
||||
// Its Value MUST ALWAYS be empty.
|
||||
func (*DisableActiveMigration) Value() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
type ActiveConnectionIDLimit uint64
|
||||
|
||||
func (ActiveConnectionIDLimit) ID() uint64 {
|
||||
return active_connection_id_limit
|
||||
}
|
||||
|
||||
func (a ActiveConnectionIDLimit) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(a))
|
||||
}
|
||||
|
||||
type InitialSourceConnectionID []byte // if empty, will be set to the Connection ID used for the Initial packet.
|
||||
|
||||
func (InitialSourceConnectionID) ID() uint64 {
|
||||
return initial_source_connection_id
|
||||
}
|
||||
|
||||
func (i InitialSourceConnectionID) Value() []byte {
|
||||
return []byte(i)
|
||||
}
|
||||
|
||||
type VersionInformation struct {
|
||||
ChoosenVersion uint32
|
||||
AvailableVersions []uint32 // Also known as "Other Versions" in early drafts.
|
||||
|
||||
LegacyID bool // If true, use the legacy-assigned ID (0xff73db) instead of the RFC-assigned one (0x11).
|
||||
}
|
||||
|
||||
const (
|
||||
VERSION_NEGOTIATION uint32 = 0x00000000 // rfc9000
|
||||
VERSION_1 uint32 = 0x00000001 // rfc9000
|
||||
VERSION_2 uint32 = 0x6b3343cf // rfc9369
|
||||
|
||||
VERSION_GREASE uint32 = 0x0a0a0a0a // -> 0x?a?a?a?a
|
||||
)
|
||||
|
||||
func (v *VersionInformation) ID() uint64 {
|
||||
if v.LegacyID {
|
||||
return version_information_legacy
|
||||
}
|
||||
return version_information
|
||||
}
|
||||
|
||||
func (v *VersionInformation) Value() []byte {
|
||||
var b []byte
|
||||
b = binary.BigEndian.AppendUint32(b, v.ChoosenVersion)
|
||||
for _, version := range v.AvailableVersions {
|
||||
if version != VERSION_GREASE {
|
||||
b = binary.BigEndian.AppendUint32(b, version)
|
||||
} else {
|
||||
b = binary.BigEndian.AppendUint32(b, v.GetGREASEVersion())
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (*VersionInformation) GetGREASEVersion() uint32 {
|
||||
// get a random uint32
|
||||
max := big.NewInt(math.MaxUint32)
|
||||
randVal, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
return VERSION_GREASE
|
||||
}
|
||||
|
||||
return uint32(randVal.Uint64()&math.MaxUint32) | 0x0a0a0a0a // all GREASE versions are in 0x?a?a?a?a
|
||||
}
|
||||
|
||||
type PaddingTransportParameter []byte
|
||||
|
||||
func (PaddingTransportParameter) ID() uint64 {
|
||||
return padding
|
||||
}
|
||||
|
||||
func (p PaddingTransportParameter) Value() []byte {
|
||||
return p
|
||||
}
|
||||
|
||||
type MaxDatagramFrameSize uint64
|
||||
|
||||
func (MaxDatagramFrameSize) ID() uint64 {
|
||||
return max_datagram_frame_size
|
||||
}
|
||||
|
||||
func (m MaxDatagramFrameSize) Value() []byte {
|
||||
return quicvarint.Append([]byte{}, uint64(m))
|
||||
}
|
||||
|
||||
type GREASEQUICBit struct{}
|
||||
|
||||
func (*GREASEQUICBit) ID() uint64 {
|
||||
return grease_quic_bit
|
||||
}
|
||||
|
||||
// Its Value MUST ALWAYS be empty.
|
||||
func (*GREASEQUICBit) Value() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
type FakeQUICTransportParameter struct {
|
||||
Id uint64
|
||||
Val []byte
|
||||
}
|
||||
|
||||
func (f *FakeQUICTransportParameter) ID() uint64 {
|
||||
if f.Id == 0 {
|
||||
panic("it is not allowed to use a FakeQUICTransportParameter without setting the ID")
|
||||
}
|
||||
return f.Id
|
||||
}
|
||||
|
||||
func (f *FakeQUICTransportParameter) Value() []byte {
|
||||
return f.Val
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Roller struct {
|
||||
HelloIDs []ClientHelloID
|
||||
HelloIDMu sync.Mutex
|
||||
WorkingHelloID *ClientHelloID
|
||||
TcpDialTimeout time.Duration
|
||||
TlsHandshakeTimeout time.Duration
|
||||
r *prng
|
||||
}
|
||||
|
||||
// NewRoller creates Roller object with default range of HelloIDs to cycle through until a
|
||||
// working/unblocked one is found.
|
||||
func NewRoller() (*Roller, error) {
|
||||
r, err := newPRNG()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcpDialTimeoutInc := r.Intn(14)
|
||||
tcpDialTimeoutInc = 7 + tcpDialTimeoutInc
|
||||
|
||||
tlsHandshakeTimeoutInc := r.Intn(20)
|
||||
tlsHandshakeTimeoutInc = 11 + tlsHandshakeTimeoutInc
|
||||
|
||||
return &Roller{
|
||||
HelloIDs: []ClientHelloID{
|
||||
HelloChrome_Auto,
|
||||
HelloFirefox_Auto,
|
||||
HelloIOS_Auto,
|
||||
HelloRandomized,
|
||||
},
|
||||
TcpDialTimeout: time.Second * time.Duration(tcpDialTimeoutInc),
|
||||
TlsHandshakeTimeout: time.Second * time.Duration(tlsHandshakeTimeoutInc),
|
||||
r: r,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Dial attempts to establish connection to given address using different HelloIDs.
|
||||
// If a working HelloID is found, it is used again for subsequent Dials.
|
||||
// If tcp connection fails or all HelloIDs are tried, returns with last error.
|
||||
//
|
||||
// Usage examples:
|
||||
// Dial("tcp4", "google.com:443", "google.com")
|
||||
// Dial("tcp", "10.23.144.22:443", "mywebserver.org")
|
||||
func (c *Roller) Dial(network, addr, serverName string) (*UConn, error) {
|
||||
helloIDs := make([]ClientHelloID, len(c.HelloIDs))
|
||||
copy(helloIDs, c.HelloIDs)
|
||||
c.r.rand.Shuffle(len(c.HelloIDs), func(i, j int) {
|
||||
helloIDs[i], helloIDs[j] = helloIDs[j], helloIDs[i]
|
||||
})
|
||||
|
||||
c.HelloIDMu.Lock()
|
||||
workingHelloId := c.WorkingHelloID // keep using same helloID, if it works
|
||||
c.HelloIDMu.Unlock()
|
||||
if workingHelloId != nil {
|
||||
helloIDFound := false
|
||||
for i, ID := range helloIDs {
|
||||
if ID == *workingHelloId {
|
||||
helloIDs[i] = helloIDs[0]
|
||||
helloIDs[0] = *workingHelloId // push working hello ID first
|
||||
helloIDFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !helloIDFound {
|
||||
helloIDs = append([]ClientHelloID{*workingHelloId}, helloIDs...)
|
||||
}
|
||||
}
|
||||
|
||||
var tcpConn net.Conn
|
||||
var err error
|
||||
for _, helloID := range helloIDs {
|
||||
tcpConn, err = net.DialTimeout(network, addr, c.TcpDialTimeout)
|
||||
if err != nil {
|
||||
return nil, err // on tcp Dial failure return with error right away
|
||||
}
|
||||
|
||||
client := UClient(tcpConn, nil, helloID)
|
||||
client.SetSNI(serverName)
|
||||
client.SetDeadline(time.Now().Add(c.TlsHandshakeTimeout))
|
||||
err = client.Handshake()
|
||||
client.SetDeadline(time.Time{}) // unset timeout
|
||||
if err != nil {
|
||||
continue // on tls Dial error keep trying HelloIDs
|
||||
}
|
||||
|
||||
c.HelloIDMu.Lock()
|
||||
c.WorkingHelloID = &client.ClientHelloID
|
||||
c.HelloIDMu.Unlock()
|
||||
return client, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/refraction-networking/utls/internal/tls13"
|
||||
)
|
||||
|
||||
// Tracking the state of calling conn.loadSession
|
||||
type LoadSessionTrackerState int
|
||||
|
||||
const NeverCalled LoadSessionTrackerState = 0
|
||||
const UtlsAboutToCall LoadSessionTrackerState = 1
|
||||
const CalledByULoadSession LoadSessionTrackerState = 2
|
||||
const CalledByGoTLS LoadSessionTrackerState = 3
|
||||
|
||||
// The state of the session controller
|
||||
type sessionControllerState int
|
||||
|
||||
const NoSession sessionControllerState = 0
|
||||
const SessionTicketExtInitialized sessionControllerState = 1
|
||||
const SessionTicketExtAllSet sessionControllerState = 2
|
||||
const PskExtInitialized sessionControllerState = 3
|
||||
const PskExtAllSet sessionControllerState = 4
|
||||
|
||||
// sessionController is responsible for managing and controlling all session related states. It manages the lifecycle of the session ticket extension and the psk extension, including initialization, removal if the client hello spec doesn't contain any of them, and setting the prepared state to the client hello.
|
||||
//
|
||||
// Users should never directly modify the underlying state. Violations will result in undefined behaviors.
|
||||
//
|
||||
// Users should never construct sessionController by themselves, use the function `newSessionController` instead.
|
||||
type sessionController struct {
|
||||
// sessionTicketExt logically owns the session ticket extension
|
||||
sessionTicketExt ISessionTicketExtension
|
||||
|
||||
// pskExtension logically owns the psk extension
|
||||
pskExtension PreSharedKeyExtension
|
||||
|
||||
// uconnRef is a reference to the uconn
|
||||
uconnRef *UConn
|
||||
|
||||
// state represents the internal state of the sessionController. Users are advised to modify the state only through designated methods and avoid direct manipulation, as doing so may result in undefined behavior.
|
||||
state sessionControllerState
|
||||
|
||||
// loadSessionTracker keeps track of how the conn.loadSession method is being utilized.
|
||||
loadSessionTracker LoadSessionTrackerState
|
||||
|
||||
// callingLoadSession is a boolean flag that indicates whether the `conn.loadSession` function is currently being invoked.
|
||||
callingLoadSession bool
|
||||
|
||||
// locked is a boolean flag that becomes true once all states are appropriately set. Once `locked` is true, further modifications are disallowed, except for the binders.
|
||||
locked bool
|
||||
}
|
||||
|
||||
// newSessionController constructs a new SessionController
|
||||
func newSessionController(uconn *UConn) *sessionController {
|
||||
return &sessionController{
|
||||
uconnRef: uconn,
|
||||
sessionTicketExt: nil,
|
||||
pskExtension: nil,
|
||||
state: NoSession,
|
||||
locked: false,
|
||||
callingLoadSession: false,
|
||||
loadSessionTracker: NeverCalled,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sessionController) isSessionLocked() bool {
|
||||
return s.locked
|
||||
}
|
||||
|
||||
type shouldLoadSessionResult int
|
||||
|
||||
const shouldReturn shouldLoadSessionResult = 0
|
||||
const shouldSetTicket shouldLoadSessionResult = 1
|
||||
const shouldSetPsk shouldLoadSessionResult = 2
|
||||
const shouldLoad shouldLoadSessionResult = 3
|
||||
|
||||
// shouldLoadSession determines the appropriate action to take when it is time to load the session for the clientHello.
|
||||
// There are several possible scenarios:
|
||||
// - If a session ticket is already initialized, typically via the `initSessionTicketExt()` function, the ticket should be set in the client hello.
|
||||
// - If a pre-shared key (PSK) is already initialized, typically via the `overridePskExt()` function, the PSK should be set in the client hello.
|
||||
// - If both the `sessionTicketExt` and `pskExtension` are nil, which might occur if the client hello spec does not include them, we should skip the loadSession().
|
||||
// - In all other cases, the function proceeds to load the session.
|
||||
func (s *sessionController) shouldLoadSession() shouldLoadSessionResult {
|
||||
if s.sessionTicketExt == nil && s.pskExtension == nil || s.uconnRef.clientHelloBuildStatus != NotBuilt {
|
||||
// No need to load session since we don't have the related extensions.
|
||||
return shouldReturn
|
||||
}
|
||||
if s.state == SessionTicketExtInitialized {
|
||||
return shouldSetTicket
|
||||
}
|
||||
if s.state == PskExtInitialized {
|
||||
return shouldSetPsk
|
||||
}
|
||||
return shouldLoad
|
||||
}
|
||||
|
||||
// utlsAboutToLoadSession updates the loadSessionTracker to `UtlsAboutToCall` to signal the initiation of a session loading operation,
|
||||
// provided that the preconditions are met. If the preconditions are not met (due to incorrect utls implementation), this function triggers a panic.
|
||||
func (s *sessionController) utlsAboutToLoadSession() {
|
||||
uAssert(s.state == NoSession && !s.locked, "tls: aboutToLoadSession failed: must only load session when the session of the client hello is not locked and when there's currently no session")
|
||||
s.loadSessionTracker = UtlsAboutToCall
|
||||
}
|
||||
|
||||
func (s *sessionController) assertHelloNotBuilt(caller string) {
|
||||
if s.uconnRef.clientHelloBuildStatus != NotBuilt {
|
||||
panic(fmt.Sprintf("tls: %s failed: we can't modify the session after the clientHello is built", caller))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sessionController) assertControllerState(caller string, desired sessionControllerState, moreDesiredStates ...sessionControllerState) {
|
||||
if s.state != desired && !anyTrue(moreDesiredStates, func(_ int, state *sessionControllerState) bool {
|
||||
return s.state == *state
|
||||
}) {
|
||||
panic(fmt.Sprintf("tls: %s failed: undesired controller state %d", caller, s.state))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sessionController) assertNotLocked(caller string) {
|
||||
if s.locked {
|
||||
panic(fmt.Sprintf("tls: %s failed: you must not modify the session after it's locked", caller))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sessionController) assertCanSkip(caller, extensionName string) {
|
||||
if !s.uconnRef.skipResumptionOnNilExtension {
|
||||
panic(fmt.Sprintf("tls: %s failed: session resumption is enabled, but there is no %s in the ClientHelloSpec; Please consider provide one in the ClientHelloSpec; If this is intentional, you may consider disable resumption by setting Config.SessionTicketsDisabled to true, or set Config.PreferSkipResumptionOnNilExtension to true to suppress this exception", caller, extensionName))
|
||||
}
|
||||
}
|
||||
|
||||
// finalCheck performs a comprehensive check on the updated state to ensure the correctness of the changes.
|
||||
// If the checks pass successfully, the sessionController's state will be locked.
|
||||
// Any failure in passing the tests indicates incorrect implementations in the utls, which will result in triggering a panic.
|
||||
// Refer to the documentation for the `locked` field for more detailed information.
|
||||
func (s *sessionController) finalCheck() {
|
||||
s.assertControllerState("SessionController.finalCheck", PskExtAllSet, SessionTicketExtAllSet, NoSession)
|
||||
s.locked = true
|
||||
}
|
||||
|
||||
func initializationGuard[E Initializable, I func(E)](extension E, initializer I) {
|
||||
uAssert(!extension.IsInitialized(), "tls: initialization failed: the extension is already initialized")
|
||||
initializer(extension)
|
||||
uAssert(extension.IsInitialized(), "tls: initialization failed: the extension is not initialized after initialization")
|
||||
}
|
||||
|
||||
// initSessionTicketExt initializes the ticket and sets the state to `TicketInitialized`.
|
||||
func (s *sessionController) initSessionTicketExt(session *SessionState, ticket []byte) {
|
||||
s.assertNotLocked("initSessionTicketExt")
|
||||
s.assertHelloNotBuilt("initSessionTicketExt")
|
||||
s.assertControllerState("initSessionTicketExt", NoSession)
|
||||
panicOnNil("initSessionTicketExt", session, ticket)
|
||||
if s.sessionTicketExt == nil {
|
||||
s.assertCanSkip("initSessionTicketExt", "session ticket extension")
|
||||
return
|
||||
}
|
||||
initializationGuard(s.sessionTicketExt, func(e ISessionTicketExtension) {
|
||||
s.sessionTicketExt.InitializeByUtls(session, ticket)
|
||||
})
|
||||
s.state = SessionTicketExtInitialized
|
||||
}
|
||||
|
||||
// initPSK initializes the PSK extension using a valid session. The PSK extension
|
||||
// should not be initialized previously, and the parameters must not be nil;
|
||||
// otherwise, this function will trigger a panic.
|
||||
func (s *sessionController) initPskExt(session *SessionState, earlySecret *tls13.EarlySecret, binderKey []byte, pskIdentities []pskIdentity) {
|
||||
s.assertNotLocked("initPskExt")
|
||||
s.assertHelloNotBuilt("initPskExt")
|
||||
s.assertControllerState("initPskExt", NoSession)
|
||||
panicOnNil("initPskExt", session, earlySecret, pskIdentities)
|
||||
if s.pskExtension == nil {
|
||||
s.assertCanSkip("initPskExt", "pre-shared key extension")
|
||||
return
|
||||
}
|
||||
initializationGuard(s.pskExtension, func(e PreSharedKeyExtension) {
|
||||
publicPskIdentities := mapSlice(pskIdentities, func(private pskIdentity) PskIdentity {
|
||||
return PskIdentity{
|
||||
Label: private.label,
|
||||
ObfuscatedTicketAge: private.obfuscatedTicketAge,
|
||||
}
|
||||
})
|
||||
e.InitializeByUtls(session, earlySecret.Secret(), binderKey, publicPskIdentities)
|
||||
})
|
||||
|
||||
s.state = PskExtInitialized
|
||||
}
|
||||
|
||||
// setSessionTicketToUConn write the ticket states from the session ticket extension to the client hello and handshake state.
|
||||
func (s *sessionController) setSessionTicketToUConn() {
|
||||
uAssert(s.sessionTicketExt != nil && s.state == SessionTicketExtInitialized, "tls: setSessionTicketExt failed: invalid state")
|
||||
s.uconnRef.HandshakeState.Session = s.sessionTicketExt.GetSession()
|
||||
s.uconnRef.HandshakeState.Hello.SessionTicket = s.sessionTicketExt.GetTicket()
|
||||
s.state = SessionTicketExtAllSet
|
||||
}
|
||||
|
||||
// setPskToUConn sets the psk to the handshake state and client hello.
|
||||
func (s *sessionController) setPskToUConn() {
|
||||
uAssert(s.pskExtension != nil && (s.state == PskExtInitialized || s.state == PskExtAllSet), "tls: setPskToUConn failed: invalid state")
|
||||
pskCommon := s.pskExtension.GetPreSharedKeyCommon()
|
||||
if s.state == PskExtInitialized {
|
||||
s.uconnRef.HandshakeState.State13.EarlySecret = pskCommon.EarlySecret
|
||||
s.uconnRef.HandshakeState.Session = pskCommon.Session
|
||||
s.uconnRef.HandshakeState.Hello.PskIdentities = pskCommon.Identities
|
||||
s.uconnRef.HandshakeState.Hello.PskBinders = pskCommon.Binders
|
||||
} else if s.state == PskExtAllSet {
|
||||
uAssert(s.uconnRef.HandshakeState.Session == pskCommon.Session && sliceEq(s.uconnRef.HandshakeState.State13.EarlySecret, pskCommon.EarlySecret) &&
|
||||
allTrue(s.uconnRef.HandshakeState.Hello.PskIdentities, func(i int, psk *PskIdentity) bool {
|
||||
return pskCommon.Identities[i].ObfuscatedTicketAge == psk.ObfuscatedTicketAge && sliceEq(pskCommon.Identities[i].Label, psk.Label)
|
||||
}), "tls: setPskToUConn failed: only binders are allowed to change on state `PskAllSet`")
|
||||
}
|
||||
s.uconnRef.HandshakeState.State13.BinderKey = pskCommon.BinderKey
|
||||
s.state = PskExtAllSet
|
||||
}
|
||||
|
||||
// shouldUpdateBinders determines whether binders should be updated based on the presence of an initialized psk extension.
|
||||
// This function returns true if an initialized psk extension exists. Binders are allowed to be updated when the state is `PskAllSet`,
|
||||
// as the `BuildHandshakeState` function can be called multiple times in this case. However, it's important to note that
|
||||
// the session state, apart from binders, should not be altered more than once.
|
||||
func (s *sessionController) shouldUpdateBinders() bool {
|
||||
if s.pskExtension == nil {
|
||||
return false
|
||||
}
|
||||
return (s.state == PskExtInitialized || s.state == PskExtAllSet)
|
||||
}
|
||||
|
||||
func (s *sessionController) updateBinders() {
|
||||
uAssert(s.shouldUpdateBinders(), "tls: updateBinders failed: shouldn't update binders")
|
||||
s.pskExtension.PatchBuiltHello(s.uconnRef.HandshakeState.Hello)
|
||||
}
|
||||
|
||||
func (s *sessionController) overrideExtension(extension Initializable, override func(), initializedState sessionControllerState) error {
|
||||
panicOnNil("overrideExtension", extension)
|
||||
s.assertNotLocked("overrideExtension")
|
||||
s.assertControllerState("overrideExtension", NoSession)
|
||||
override()
|
||||
if extension.IsInitialized() {
|
||||
s.state = initializedState
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// overridePskExt allows the user of utls to customize the psk extension.
|
||||
func (s *sessionController) overridePskExt(pskExt PreSharedKeyExtension) error {
|
||||
return s.overrideExtension(pskExt, func() { s.pskExtension = pskExt }, PskExtInitialized)
|
||||
}
|
||||
|
||||
// overridePskExt allows the user of utls to customize the session ticket extension.
|
||||
func (s *sessionController) overrideSessionTicketExt(sessionTicketExt ISessionTicketExtension) error {
|
||||
return s.overrideExtension(sessionTicketExt, func() { s.sessionTicketExt = sessionTicketExt }, SessionTicketExtInitialized)
|
||||
}
|
||||
|
||||
// syncSessionExts synchronizes the sessionController with the session-related
|
||||
// extensions from the extension list after applying client hello specs.
|
||||
//
|
||||
// - If the extension list is missing the session ticket extension or PSK
|
||||
// extension, owned extensions are dropped and states are reset.
|
||||
// - If the user provides a session ticket extension or PSK extension, the
|
||||
// corresponding extension from the extension list will be replaced.
|
||||
// - If the user doesn't provide session-related extensions, the extensions
|
||||
// from the extension list will be utilized.
|
||||
//
|
||||
// This function ensures that there is only one session ticket extension or PSK
|
||||
// extension, and that the PSK extension is the last extension in the extension
|
||||
// list.
|
||||
func (s *sessionController) syncSessionExts() error {
|
||||
uAssert(s.uconnRef.clientHelloBuildStatus == NotBuilt, "tls: checkSessionExts failed: we can't modify the session after the clientHello is built")
|
||||
s.assertNotLocked("checkSessionExts")
|
||||
s.assertHelloNotBuilt("checkSessionExts")
|
||||
s.assertControllerState("checkSessionExts", NoSession, SessionTicketExtInitialized, PskExtInitialized)
|
||||
numSessionExt := 0
|
||||
hasPskExt := false
|
||||
for i, e := range s.uconnRef.Extensions {
|
||||
switch ext := e.(type) {
|
||||
case ISessionTicketExtension:
|
||||
uAssert(numSessionExt == 0, "tls: checkSessionExts failed: multiple ISessionTicketExtensions in the extension list")
|
||||
if s.sessionTicketExt == nil {
|
||||
// If there isn't a user-provided session ticket extension, use the one from the spec
|
||||
s.sessionTicketExt = ext
|
||||
} else {
|
||||
// Otherwise, replace the one in the extension list with the user-provided one
|
||||
s.uconnRef.Extensions[i] = s.sessionTicketExt
|
||||
}
|
||||
numSessionExt += 1
|
||||
case PreSharedKeyExtension:
|
||||
uAssert(i == len(s.uconnRef.Extensions)-1, "tls: checkSessionExts failed: PreSharedKeyExtension must be the last extension")
|
||||
if s.pskExtension == nil {
|
||||
// If there isn't a user-provided psk extension, use the one from the spec
|
||||
s.pskExtension = ext
|
||||
} else {
|
||||
// Otherwise, replace the one in the extension list with the user-provided one
|
||||
s.uconnRef.Extensions[i] = s.pskExtension
|
||||
}
|
||||
s.pskExtension.SetOmitEmptyPsk(s.uconnRef.config.OmitEmptyPsk)
|
||||
hasPskExt = true
|
||||
}
|
||||
}
|
||||
if numSessionExt == 0 {
|
||||
if s.state == SessionTicketExtInitialized {
|
||||
return errors.New("tls: checkSessionExts failed: the user provided a session ticket, but the specification doesn't contain one")
|
||||
}
|
||||
s.sessionTicketExt = nil
|
||||
s.uconnRef.HandshakeState.Session = nil
|
||||
s.uconnRef.HandshakeState.Hello.SessionTicket = nil
|
||||
}
|
||||
if !hasPskExt {
|
||||
if s.state == PskExtInitialized {
|
||||
return errors.New("tls: checkSessionExts failed: the user provided a psk, but the specification doesn't contain one")
|
||||
}
|
||||
s.pskExtension = nil
|
||||
s.uconnRef.HandshakeState.State13.BinderKey = nil
|
||||
s.uconnRef.HandshakeState.State13.EarlySecret = nil
|
||||
s.uconnRef.HandshakeState.Session = nil
|
||||
s.uconnRef.HandshakeState.Hello.PskIdentities = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// onEnterLoadSessionCheck is intended to be invoked upon entering the `conn.loadSession` function.
|
||||
// It is designed to ensure the correctness of the utls implementation. If the utls implementation is found to be incorrect, this function will trigger a panic.
|
||||
func (s *sessionController) onEnterLoadSessionCheck() {
|
||||
uAssert(!s.locked, "tls: LoadSessionCoordinator.onEnterLoadSessionCheck failed: session is set and locked, no call to loadSession is allowed")
|
||||
switch s.loadSessionTracker {
|
||||
case UtlsAboutToCall, NeverCalled:
|
||||
s.callingLoadSession = true
|
||||
case CalledByULoadSession, CalledByGoTLS:
|
||||
panic("tls: LoadSessionCoordinator.onEnterLoadSessionCheck failed: you must not call loadSession() twice")
|
||||
default:
|
||||
panic("tls: LoadSessionCoordinator.onEnterLoadSessionCheck failed: unimplemented state")
|
||||
}
|
||||
}
|
||||
|
||||
// onLoadSessionReturn is intended to be invoked upon returning from the `conn.loadSession` function.
|
||||
// It serves as a validation step for the correctness of the underlying utls implementation.
|
||||
// If the utls implementation is incorrect, this function will trigger a panic.
|
||||
func (s *sessionController) onLoadSessionReturn() {
|
||||
uAssert(s.callingLoadSession, "tls: LoadSessionCoordinator.onLoadSessionReturn failed: it's not loading sessions, perhaps this function is not being called by loadSession.")
|
||||
switch s.loadSessionTracker {
|
||||
case NeverCalled:
|
||||
s.loadSessionTracker = CalledByGoTLS
|
||||
case UtlsAboutToCall:
|
||||
s.loadSessionTracker = CalledByULoadSession
|
||||
default:
|
||||
panic("tls: LoadSessionCoordinator.onLoadSessionReturn failed: unimplemented state")
|
||||
}
|
||||
s.callingLoadSession = false
|
||||
}
|
||||
|
||||
// shouldLoadSessionWriteBinders checks if `conn.loadSession` should proceed to write binders and marshal the client hello. If the utls implementation
|
||||
// is incorrect, this function will trigger a panic.
|
||||
func (s *sessionController) shouldLoadSessionWriteBinders() bool {
|
||||
uAssert(s.callingLoadSession, "tls: shouldWriteBinders failed: LoadSessionCoordinator isn't loading sessions, perhaps this function is not being called by loadSession.")
|
||||
|
||||
switch s.loadSessionTracker {
|
||||
case NeverCalled:
|
||||
return true
|
||||
case UtlsAboutToCall:
|
||||
return false
|
||||
default:
|
||||
panic("tls: shouldWriteBinders failed: unimplemented state")
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package tls
|
||||
|
||||
import "io"
|
||||
|
||||
type ISessionTicketExtension interface {
|
||||
TLSExtension
|
||||
|
||||
// If false is returned, utls will invoke `InitializeByUtls()` for the necessary initialization.
|
||||
Initializable
|
||||
|
||||
// InitializeByUtls is invoked when IsInitialized() returns false.
|
||||
// It initializes the extension using a real and valid TLS 1.2 session.
|
||||
InitializeByUtls(session *SessionState, ticket []byte)
|
||||
|
||||
GetSession() *SessionState
|
||||
|
||||
GetTicket() []byte
|
||||
}
|
||||
|
||||
// SessionTicketExtension implements session_ticket (35)
|
||||
type SessionTicketExtension struct {
|
||||
Session *SessionState
|
||||
Ticket []byte
|
||||
Initialized bool
|
||||
}
|
||||
|
||||
func (e *SessionTicketExtension) writeToUConn(uc *UConn) error {
|
||||
// session states are handled later. At this point tickets aren't
|
||||
// being loaded by utls, so don't write anything to the UConn.
|
||||
uc.HandshakeState.Hello.TicketSupported = true // This doesn't really matter, this field is only used to add session ticket ext in go tls.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *SessionTicketExtension) Len() int {
|
||||
return 4 + len(e.Ticket)
|
||||
}
|
||||
|
||||
func (e *SessionTicketExtension) Read(b []byte) (int, error) {
|
||||
if len(b) < e.Len() {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
extBodyLen := e.Len() - 4
|
||||
|
||||
b[0] = byte(extensionSessionTicket >> 8)
|
||||
b[1] = byte(extensionSessionTicket)
|
||||
b[2] = byte(extBodyLen >> 8)
|
||||
b[3] = byte(extBodyLen)
|
||||
if extBodyLen > 0 {
|
||||
copy(b[4:], e.Ticket)
|
||||
}
|
||||
return e.Len(), io.EOF
|
||||
}
|
||||
|
||||
func (e *SessionTicketExtension) IsInitialized() bool {
|
||||
return e.Initialized
|
||||
}
|
||||
|
||||
func (e *SessionTicketExtension) InitializeByUtls(session *SessionState, ticket []byte) {
|
||||
uAssert(!e.Initialized, "tls: InitializeByUtls failed: the SessionTicketExtension is initialized")
|
||||
uAssert(session.version == VersionTLS12 && session != nil && ticket != nil, "tls: InitializeByUtls failed: the session is not a tls 1.2 session")
|
||||
e.Session = session
|
||||
e.Ticket = ticket
|
||||
e.Initialized = true
|
||||
}
|
||||
|
||||
func (e *SessionTicketExtension) UnmarshalJSON(_ []byte) error {
|
||||
return nil // no-op
|
||||
}
|
||||
|
||||
func (e *SessionTicketExtension) Write(_ []byte) (int, error) {
|
||||
// RFC 5077, Section 3.2
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (e *SessionTicketExtension) GetSession() *SessionState {
|
||||
return e.Session
|
||||
}
|
||||
|
||||
func (e *SessionTicketExtension) GetTicket() []byte {
|
||||
return e.Ticket
|
||||
}
|
||||
+1946
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user