218 lines
6.5 KiB
Go
218 lines
6.5 KiB
Go
package mint
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
func VersionNegotiation(offered, supported []uint16) (bool, uint16) {
|
|
for _, offeredVersion := range offered {
|
|
for _, supportedVersion := range supported {
|
|
logf(logTypeHandshake, "[server] version offered by client [%04x] <> [%04x]", offeredVersion, supportedVersion)
|
|
if offeredVersion == supportedVersion {
|
|
// XXX: Should probably be highest supported version, but for now, we
|
|
// only support one version, so it doesn't really matter.
|
|
return true, offeredVersion
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, 0
|
|
}
|
|
|
|
func DHNegotiation(keyShares []KeyShareEntry, groups []NamedGroup) (bool, NamedGroup, []byte, []byte) {
|
|
for _, share := range keyShares {
|
|
for _, group := range groups {
|
|
if group != share.Group {
|
|
continue
|
|
}
|
|
|
|
pub, priv, err := newKeyShare(share.Group)
|
|
if err != nil {
|
|
// If we encounter an error, just keep looking
|
|
continue
|
|
}
|
|
|
|
dhSecret, err := keyAgreement(share.Group, share.KeyExchange, priv)
|
|
if err != nil {
|
|
// If we encounter an error, just keep looking
|
|
continue
|
|
}
|
|
|
|
return true, group, pub, dhSecret
|
|
}
|
|
}
|
|
|
|
return false, 0, nil, nil
|
|
}
|
|
|
|
const (
|
|
ticketAgeTolerance uint32 = 5 * 1000 // five seconds in milliseconds
|
|
)
|
|
|
|
func PSKNegotiation(identities []PSKIdentity, binders []PSKBinderEntry, context []byte, psks PreSharedKeyCache) (bool, int, *PreSharedKey, CipherSuiteParams, error) {
|
|
logf(logTypeNegotiation, "Negotiating PSK offered=[%d] supported=[%d]", len(identities), psks.Size())
|
|
for i, id := range identities {
|
|
identityHex := hex.EncodeToString(id.Identity)
|
|
|
|
psk, ok := psks.Get(identityHex)
|
|
if !ok {
|
|
logf(logTypeNegotiation, "No PSK for identity %x", identityHex)
|
|
continue
|
|
}
|
|
|
|
// For resumption, make sure the ticket age is correct
|
|
if psk.IsResumption {
|
|
extTicketAge := id.ObfuscatedTicketAge - psk.TicketAgeAdd
|
|
knownTicketAge := uint32(time.Since(psk.ReceivedAt) / time.Millisecond)
|
|
ticketAgeDelta := knownTicketAge - extTicketAge
|
|
if knownTicketAge < extTicketAge {
|
|
ticketAgeDelta = extTicketAge - knownTicketAge
|
|
}
|
|
if ticketAgeDelta > ticketAgeTolerance {
|
|
logf(logTypeNegotiation, "WARNING potential replay [%x]", psk.Identity)
|
|
logf(logTypeNegotiation, "Ticket age exceeds tolerance |%d - %d| = [%d] > [%d]",
|
|
extTicketAge, knownTicketAge, ticketAgeDelta, ticketAgeTolerance)
|
|
return false, 0, nil, CipherSuiteParams{}, fmt.Errorf("WARNING Potential replay for identity %x", psk.Identity)
|
|
}
|
|
}
|
|
|
|
params, ok := cipherSuiteMap[psk.CipherSuite]
|
|
if !ok {
|
|
err := fmt.Errorf("tls.cryptoinit: Unsupported ciphersuite from PSK [%04x]", psk.CipherSuite)
|
|
return false, 0, nil, CipherSuiteParams{}, err
|
|
}
|
|
|
|
// Compute binder
|
|
binderLabel := labelExternalBinder
|
|
if psk.IsResumption {
|
|
binderLabel = labelResumptionBinder
|
|
}
|
|
|
|
h0 := params.Hash.New().Sum(nil)
|
|
zero := bytes.Repeat([]byte{0}, params.Hash.Size())
|
|
earlySecret := HkdfExtract(params.Hash, zero, psk.Key)
|
|
binderKey := deriveSecret(params, earlySecret, binderLabel, h0)
|
|
|
|
// context = ClientHello[truncated]
|
|
// context = ClientHello1 + HelloRetryRequest + ClientHello2[truncated]
|
|
ctxHash := params.Hash.New()
|
|
ctxHash.Write(context)
|
|
|
|
binder := computeFinishedData(params, binderKey, ctxHash.Sum(nil))
|
|
if !bytes.Equal(binder, binders[i].Binder) {
|
|
logf(logTypeNegotiation, "Binder check failed for identity %x; [%x] != [%x]", psk.Identity, binder, binders[i].Binder)
|
|
return false, 0, nil, CipherSuiteParams{}, fmt.Errorf("Binder check failed identity %x", psk.Identity)
|
|
}
|
|
|
|
logf(logTypeNegotiation, "Using PSK with identity %x", psk.Identity)
|
|
return true, i, &psk, params, nil
|
|
}
|
|
|
|
logf(logTypeNegotiation, "Failed to find a usable PSK")
|
|
return false, 0, nil, CipherSuiteParams{}, nil
|
|
}
|
|
|
|
func PSKModeNegotiation(canDoDH, canDoPSK bool, modes []PSKKeyExchangeMode) (bool, bool) {
|
|
logf(logTypeNegotiation, "Negotiating PSK modes [%v] [%v] [%+v]", canDoDH, canDoPSK, modes)
|
|
dhAllowed := false
|
|
dhRequired := true
|
|
for _, mode := range modes {
|
|
dhAllowed = dhAllowed || (mode == PSKModeDHEKE)
|
|
dhRequired = dhRequired && (mode == PSKModeDHEKE)
|
|
}
|
|
|
|
// Use PSK if we can meet DH requirement and modes were provided
|
|
usingPSK := canDoPSK && (!dhRequired || canDoDH) && (len(modes) > 0)
|
|
|
|
// Use DH if allowed
|
|
usingDH := canDoDH && (dhAllowed || !usingPSK)
|
|
|
|
logf(logTypeNegotiation, "Results of PSK mode negotiation: usingDH=[%v] usingPSK=[%v]", usingDH, usingPSK)
|
|
return usingDH, usingPSK
|
|
}
|
|
|
|
func CertificateSelection(serverName *string, signatureSchemes []SignatureScheme, certs []*Certificate) (*Certificate, SignatureScheme, error) {
|
|
// Select for server name if provided
|
|
candidates := certs
|
|
if serverName != nil {
|
|
candidatesByName := []*Certificate{}
|
|
for _, cert := range certs {
|
|
for _, name := range cert.Chain[0].DNSNames {
|
|
if len(*serverName) > 0 && name == *serverName {
|
|
candidatesByName = append(candidatesByName, cert)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(candidatesByName) == 0 {
|
|
return nil, 0, fmt.Errorf("No certificates available for server name: %s", *serverName)
|
|
}
|
|
|
|
candidates = candidatesByName
|
|
}
|
|
|
|
// Select for signature scheme
|
|
for _, cert := range candidates {
|
|
for _, scheme := range signatureSchemes {
|
|
if !schemeValidForKey(scheme, cert.PrivateKey) {
|
|
continue
|
|
}
|
|
|
|
return cert, scheme, nil
|
|
}
|
|
}
|
|
|
|
return nil, 0, fmt.Errorf("No certificates compatible with signature schemes")
|
|
}
|
|
|
|
func EarlyDataNegotiation(usingPSK, gotEarlyData, allowEarlyData bool) bool {
|
|
usingEarlyData := gotEarlyData && usingPSK && allowEarlyData
|
|
logf(logTypeNegotiation, "Early data negotiation (%v, %v, %v) => %v", usingPSK, gotEarlyData, allowEarlyData, usingEarlyData)
|
|
return usingEarlyData
|
|
}
|
|
|
|
func CipherSuiteNegotiation(psk *PreSharedKey, offered, supported []CipherSuite) (CipherSuite, error) {
|
|
for _, s1 := range offered {
|
|
if psk != nil {
|
|
if s1 == psk.CipherSuite {
|
|
return s1, nil
|
|
}
|
|
continue
|
|
}
|
|
|
|
for _, s2 := range supported {
|
|
if s1 == s2 {
|
|
return s1, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0, fmt.Errorf("No overlap between offered and supproted ciphersuites (psk? [%v])", psk != nil)
|
|
}
|
|
|
|
func ALPNNegotiation(psk *PreSharedKey, offered, supported []string) (string, error) {
|
|
for _, p1 := range offered {
|
|
if psk != nil {
|
|
if p1 != psk.NextProto {
|
|
continue
|
|
}
|
|
}
|
|
|
|
for _, p2 := range supported {
|
|
if p1 == p2 {
|
|
return p1, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the client offers ALPN on resumption, it must match the earlier one
|
|
var err error
|
|
if psk != nil && psk.IsResumption && (len(offered) > 0) {
|
|
err = fmt.Errorf("ALPN for PSK not provided")
|
|
}
|
|
return "", err
|
|
}
|