482 lines
13 KiB
Go
482 lines
13 KiB
Go
package mint
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/x509"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/bifurcation/mint/syntax"
|
|
)
|
|
|
|
type HandshakeMessageBody interface {
|
|
Type() HandshakeType
|
|
Marshal() ([]byte, error)
|
|
Unmarshal(data []byte) (int, error)
|
|
}
|
|
|
|
// struct {
|
|
// ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */
|
|
// Random random;
|
|
// opaque legacy_session_id<0..32>;
|
|
// CipherSuite cipher_suites<2..2^16-2>;
|
|
// opaque legacy_compression_methods<1..2^8-1>;
|
|
// Extension extensions<0..2^16-1>;
|
|
// } ClientHello;
|
|
type ClientHelloBody struct {
|
|
LegacyVersion uint16
|
|
Random [32]byte
|
|
LegacySessionID []byte
|
|
CipherSuites []CipherSuite
|
|
Extensions ExtensionList
|
|
}
|
|
|
|
type clientHelloBodyInnerTLS struct {
|
|
LegacyVersion uint16
|
|
Random [32]byte
|
|
LegacySessionID []byte `tls:"head=1,max=32"`
|
|
CipherSuites []CipherSuite `tls:"head=2,min=2"`
|
|
LegacyCompressionMethods []byte `tls:"head=1,min=1"`
|
|
Extensions []Extension `tls:"head=2"`
|
|
}
|
|
|
|
type clientHelloBodyInnerDTLS struct {
|
|
LegacyVersion uint16
|
|
Random [32]byte
|
|
LegacySessionID []byte `tls:"head=1,max=32"`
|
|
EmptyCookie uint8
|
|
CipherSuites []CipherSuite `tls:"head=2,min=2"`
|
|
LegacyCompressionMethods []byte `tls:"head=1,min=1"`
|
|
Extensions []Extension `tls:"head=2"`
|
|
}
|
|
|
|
func (ch ClientHelloBody) Type() HandshakeType {
|
|
return HandshakeTypeClientHello
|
|
}
|
|
|
|
func (ch ClientHelloBody) Marshal() ([]byte, error) {
|
|
if ch.LegacyVersion == tls12Version {
|
|
return syntax.Marshal(clientHelloBodyInnerTLS{
|
|
LegacyVersion: ch.LegacyVersion,
|
|
Random: ch.Random,
|
|
LegacySessionID: []byte{},
|
|
CipherSuites: ch.CipherSuites,
|
|
LegacyCompressionMethods: []byte{0},
|
|
Extensions: ch.Extensions,
|
|
})
|
|
} else {
|
|
return syntax.Marshal(clientHelloBodyInnerDTLS{
|
|
LegacyVersion: ch.LegacyVersion,
|
|
Random: ch.Random,
|
|
LegacySessionID: []byte{},
|
|
CipherSuites: ch.CipherSuites,
|
|
LegacyCompressionMethods: []byte{0},
|
|
Extensions: ch.Extensions,
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func (ch *ClientHelloBody) Unmarshal(data []byte) (int, error) {
|
|
var read int
|
|
var err error
|
|
|
|
// Note that this might be 0, in which case we do TLS. That
|
|
// makes the tests easier.
|
|
if ch.LegacyVersion != dtls12WireVersion {
|
|
var inner clientHelloBodyInnerTLS
|
|
read, err = syntax.Unmarshal(data, &inner)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if len(inner.LegacyCompressionMethods) != 1 || inner.LegacyCompressionMethods[0] != 0 {
|
|
return 0, fmt.Errorf("tls.clienthello: Invalid compression method")
|
|
}
|
|
|
|
ch.LegacyVersion = inner.LegacyVersion
|
|
ch.Random = inner.Random
|
|
ch.LegacySessionID = inner.LegacySessionID
|
|
ch.CipherSuites = inner.CipherSuites
|
|
ch.Extensions = inner.Extensions
|
|
} else {
|
|
var inner clientHelloBodyInnerDTLS
|
|
read, err = syntax.Unmarshal(data, &inner)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if inner.EmptyCookie != 0 {
|
|
return 0, fmt.Errorf("tls.clienthello: Invalid cookie")
|
|
}
|
|
|
|
if len(inner.LegacyCompressionMethods) != 1 || inner.LegacyCompressionMethods[0] != 0 {
|
|
return 0, fmt.Errorf("tls.clienthello: Invalid compression method")
|
|
}
|
|
|
|
ch.LegacyVersion = inner.LegacyVersion
|
|
ch.Random = inner.Random
|
|
ch.LegacySessionID = inner.LegacySessionID
|
|
ch.CipherSuites = inner.CipherSuites
|
|
ch.Extensions = inner.Extensions
|
|
}
|
|
return read, nil
|
|
}
|
|
|
|
// TODO: File a spec bug to clarify this
|
|
func (ch ClientHelloBody) Truncated() ([]byte, error) {
|
|
if len(ch.Extensions) == 0 {
|
|
return nil, fmt.Errorf("tls.clienthello.truncate: No extensions")
|
|
}
|
|
|
|
pskExt := ch.Extensions[len(ch.Extensions)-1]
|
|
if pskExt.ExtensionType != ExtensionTypePreSharedKey {
|
|
return nil, fmt.Errorf("tls.clienthello.truncate: Last extension is not PSK")
|
|
}
|
|
|
|
body, err := ch.Marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
chm := &HandshakeMessage{
|
|
msgType: ch.Type(),
|
|
body: body,
|
|
length: uint32(len(body)),
|
|
}
|
|
chData := chm.Marshal()
|
|
|
|
psk := PreSharedKeyExtension{
|
|
HandshakeType: HandshakeTypeClientHello,
|
|
}
|
|
_, err = psk.Unmarshal(pskExt.ExtensionData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Marshal just the binders so that we know how much to truncate
|
|
binders := struct {
|
|
Binders []PSKBinderEntry `tls:"head=2,min=33"`
|
|
}{Binders: psk.Binders}
|
|
binderData, _ := syntax.Marshal(binders)
|
|
binderLen := len(binderData)
|
|
|
|
chLen := len(chData)
|
|
return chData[:chLen-binderLen], nil
|
|
}
|
|
|
|
// struct {
|
|
// ProtocolVersion legacy_version = 0x0303; /* TLS v1.2 */
|
|
// Random random;
|
|
// opaque legacy_session_id_echo<0..32>;
|
|
// CipherSuite cipher_suite;
|
|
// uint8 legacy_compression_method = 0;
|
|
// Extension extensions<6..2^16-1>;
|
|
// } ServerHello;
|
|
type ServerHelloBody struct {
|
|
Version uint16
|
|
Random [32]byte
|
|
LegacySessionID []byte `tls:"head=1,max=32"`
|
|
CipherSuite CipherSuite
|
|
LegacyCompressionMethod uint8
|
|
Extensions ExtensionList `tls:"head=2"`
|
|
}
|
|
|
|
func (sh ServerHelloBody) Type() HandshakeType {
|
|
return HandshakeTypeServerHello
|
|
}
|
|
|
|
func (sh ServerHelloBody) Marshal() ([]byte, error) {
|
|
return syntax.Marshal(sh)
|
|
}
|
|
|
|
func (sh *ServerHelloBody) Unmarshal(data []byte) (int, error) {
|
|
return syntax.Unmarshal(data, sh)
|
|
}
|
|
|
|
// struct {
|
|
// opaque verify_data[verify_data_length];
|
|
// } Finished;
|
|
//
|
|
// verifyDataLen is not a field in the TLS struct, but we add it here so
|
|
// that calling code can tell us how much data to expect when we marshal /
|
|
// unmarshal. (We could add this to the marshal/unmarshal methods, but let's
|
|
// try to keep the signature consistent for now.)
|
|
//
|
|
// For similar reasons, we don't use the `syntax` module here, because this
|
|
// struct doesn't map well to standard TLS presentation language concepts.
|
|
//
|
|
// TODO: File a spec bug
|
|
type FinishedBody struct {
|
|
VerifyDataLen int
|
|
VerifyData []byte
|
|
}
|
|
|
|
func (fin FinishedBody) Type() HandshakeType {
|
|
return HandshakeTypeFinished
|
|
}
|
|
|
|
func (fin FinishedBody) Marshal() ([]byte, error) {
|
|
if len(fin.VerifyData) != fin.VerifyDataLen {
|
|
return nil, fmt.Errorf("tls.finished: data length mismatch")
|
|
}
|
|
|
|
body := make([]byte, len(fin.VerifyData))
|
|
copy(body, fin.VerifyData)
|
|
return body, nil
|
|
}
|
|
|
|
func (fin *FinishedBody) Unmarshal(data []byte) (int, error) {
|
|
if len(data) < fin.VerifyDataLen {
|
|
return 0, fmt.Errorf("tls.finished: Malformed finished; too short")
|
|
}
|
|
|
|
fin.VerifyData = make([]byte, fin.VerifyDataLen)
|
|
copy(fin.VerifyData, data[:fin.VerifyDataLen])
|
|
return fin.VerifyDataLen, nil
|
|
}
|
|
|
|
// struct {
|
|
// Extension extensions<0..2^16-1>;
|
|
// } EncryptedExtensions;
|
|
//
|
|
// Marshal() and Unmarshal() are handled by ExtensionList
|
|
type EncryptedExtensionsBody struct {
|
|
Extensions ExtensionList `tls:"head=2"`
|
|
}
|
|
|
|
func (ee EncryptedExtensionsBody) Type() HandshakeType {
|
|
return HandshakeTypeEncryptedExtensions
|
|
}
|
|
|
|
func (ee EncryptedExtensionsBody) Marshal() ([]byte, error) {
|
|
return syntax.Marshal(ee)
|
|
}
|
|
|
|
func (ee *EncryptedExtensionsBody) Unmarshal(data []byte) (int, error) {
|
|
return syntax.Unmarshal(data, ee)
|
|
}
|
|
|
|
// opaque ASN1Cert<1..2^24-1>;
|
|
//
|
|
// struct {
|
|
// ASN1Cert cert_data;
|
|
// Extension extensions<0..2^16-1>
|
|
// } CertificateEntry;
|
|
//
|
|
// struct {
|
|
// opaque certificate_request_context<0..2^8-1>;
|
|
// CertificateEntry certificate_list<0..2^24-1>;
|
|
// } Certificate;
|
|
type CertificateEntry struct {
|
|
CertData *x509.Certificate
|
|
Extensions ExtensionList
|
|
}
|
|
|
|
type CertificateBody struct {
|
|
CertificateRequestContext []byte
|
|
CertificateList []CertificateEntry
|
|
}
|
|
|
|
type certificateEntryInner struct {
|
|
CertData []byte `tls:"head=3,min=1"`
|
|
Extensions ExtensionList `tls:"head=2"`
|
|
}
|
|
|
|
type certificateBodyInner struct {
|
|
CertificateRequestContext []byte `tls:"head=1"`
|
|
CertificateList []certificateEntryInner `tls:"head=3"`
|
|
}
|
|
|
|
func (c CertificateBody) Type() HandshakeType {
|
|
return HandshakeTypeCertificate
|
|
}
|
|
|
|
func (c CertificateBody) Marshal() ([]byte, error) {
|
|
inner := certificateBodyInner{
|
|
CertificateRequestContext: c.CertificateRequestContext,
|
|
CertificateList: make([]certificateEntryInner, len(c.CertificateList)),
|
|
}
|
|
|
|
for i, entry := range c.CertificateList {
|
|
inner.CertificateList[i] = certificateEntryInner{
|
|
CertData: entry.CertData.Raw,
|
|
Extensions: entry.Extensions,
|
|
}
|
|
}
|
|
|
|
return syntax.Marshal(inner)
|
|
}
|
|
|
|
func (c *CertificateBody) Unmarshal(data []byte) (int, error) {
|
|
inner := certificateBodyInner{}
|
|
read, err := syntax.Unmarshal(data, &inner)
|
|
if err != nil {
|
|
return read, err
|
|
}
|
|
|
|
c.CertificateRequestContext = inner.CertificateRequestContext
|
|
c.CertificateList = make([]CertificateEntry, len(inner.CertificateList))
|
|
|
|
for i, entry := range inner.CertificateList {
|
|
c.CertificateList[i].CertData, err = x509.ParseCertificate(entry.CertData)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("tls:certificate: Certificate failed to parse: %v", err)
|
|
}
|
|
|
|
c.CertificateList[i].Extensions = entry.Extensions
|
|
}
|
|
|
|
return read, nil
|
|
}
|
|
|
|
// struct {
|
|
// SignatureScheme algorithm;
|
|
// opaque signature<0..2^16-1>;
|
|
// } CertificateVerify;
|
|
type CertificateVerifyBody struct {
|
|
Algorithm SignatureScheme
|
|
Signature []byte `tls:"head=2"`
|
|
}
|
|
|
|
func (cv CertificateVerifyBody) Type() HandshakeType {
|
|
return HandshakeTypeCertificateVerify
|
|
}
|
|
|
|
func (cv CertificateVerifyBody) Marshal() ([]byte, error) {
|
|
return syntax.Marshal(cv)
|
|
}
|
|
|
|
func (cv *CertificateVerifyBody) Unmarshal(data []byte) (int, error) {
|
|
return syntax.Unmarshal(data, cv)
|
|
}
|
|
|
|
func (cv *CertificateVerifyBody) EncodeSignatureInput(data []byte) []byte {
|
|
// TODO: Change context for client auth
|
|
// TODO: Put this in a const
|
|
const context = "TLS 1.3, server CertificateVerify"
|
|
sigInput := bytes.Repeat([]byte{0x20}, 64)
|
|
sigInput = append(sigInput, []byte(context)...)
|
|
sigInput = append(sigInput, []byte{0}...)
|
|
sigInput = append(sigInput, data...)
|
|
return sigInput
|
|
}
|
|
|
|
func (cv *CertificateVerifyBody) Sign(privateKey crypto.Signer, handshakeHash []byte) (err error) {
|
|
sigInput := cv.EncodeSignatureInput(handshakeHash)
|
|
cv.Signature, err = sign(cv.Algorithm, privateKey, sigInput)
|
|
logf(logTypeHandshake, "Signed: alg=[%04x] sigInput=[%x], sig=[%x]", cv.Algorithm, sigInput, cv.Signature)
|
|
return
|
|
}
|
|
|
|
func (cv *CertificateVerifyBody) Verify(publicKey crypto.PublicKey, handshakeHash []byte) error {
|
|
sigInput := cv.EncodeSignatureInput(handshakeHash)
|
|
logf(logTypeHandshake, "About to verify: alg=[%04x] sigInput=[%x], sig=[%x]", cv.Algorithm, sigInput, cv.Signature)
|
|
return verify(cv.Algorithm, publicKey, sigInput, cv.Signature)
|
|
}
|
|
|
|
// struct {
|
|
// opaque certificate_request_context<0..2^8-1>;
|
|
// Extension extensions<2..2^16-1>;
|
|
// } CertificateRequest;
|
|
type CertificateRequestBody struct {
|
|
CertificateRequestContext []byte `tls:"head=1"`
|
|
Extensions ExtensionList `tls:"head=2"`
|
|
}
|
|
|
|
func (cr CertificateRequestBody) Type() HandshakeType {
|
|
return HandshakeTypeCertificateRequest
|
|
}
|
|
|
|
func (cr CertificateRequestBody) Marshal() ([]byte, error) {
|
|
return syntax.Marshal(cr)
|
|
}
|
|
|
|
func (cr *CertificateRequestBody) Unmarshal(data []byte) (int, error) {
|
|
return syntax.Unmarshal(data, cr)
|
|
}
|
|
|
|
// struct {
|
|
// uint32 ticket_lifetime;
|
|
// uint32 ticket_age_add;
|
|
// opaque ticket_nonce<1..255>;
|
|
// opaque ticket<1..2^16-1>;
|
|
// Extension extensions<0..2^16-2>;
|
|
// } NewSessionTicket;
|
|
type NewSessionTicketBody struct {
|
|
TicketLifetime uint32
|
|
TicketAgeAdd uint32
|
|
TicketNonce []byte `tls:"head=1,min=1"`
|
|
Ticket []byte `tls:"head=2,min=1"`
|
|
Extensions ExtensionList `tls:"head=2"`
|
|
}
|
|
|
|
const ticketNonceLen = 16
|
|
|
|
func NewSessionTicket(ticketLen int, ticketLifetime uint32) (*NewSessionTicketBody, error) {
|
|
buf := make([]byte, 4+ticketNonceLen+ticketLen)
|
|
_, err := prng.Read(buf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tkt := &NewSessionTicketBody{
|
|
TicketLifetime: ticketLifetime,
|
|
TicketAgeAdd: binary.BigEndian.Uint32(buf[:4]),
|
|
TicketNonce: buf[4 : 4+ticketNonceLen],
|
|
Ticket: buf[4+ticketNonceLen:],
|
|
}
|
|
|
|
return tkt, err
|
|
}
|
|
|
|
func (tkt NewSessionTicketBody) Type() HandshakeType {
|
|
return HandshakeTypeNewSessionTicket
|
|
}
|
|
|
|
func (tkt NewSessionTicketBody) Marshal() ([]byte, error) {
|
|
return syntax.Marshal(tkt)
|
|
}
|
|
|
|
func (tkt *NewSessionTicketBody) Unmarshal(data []byte) (int, error) {
|
|
return syntax.Unmarshal(data, tkt)
|
|
}
|
|
|
|
// enum {
|
|
// update_not_requested(0), update_requested(1), (255)
|
|
// } KeyUpdateRequest;
|
|
//
|
|
// struct {
|
|
// KeyUpdateRequest request_update;
|
|
// } KeyUpdate;
|
|
type KeyUpdateBody struct {
|
|
KeyUpdateRequest KeyUpdateRequest
|
|
}
|
|
|
|
func (ku KeyUpdateBody) Type() HandshakeType {
|
|
return HandshakeTypeKeyUpdate
|
|
}
|
|
|
|
func (ku KeyUpdateBody) Marshal() ([]byte, error) {
|
|
return syntax.Marshal(ku)
|
|
}
|
|
|
|
func (ku *KeyUpdateBody) Unmarshal(data []byte) (int, error) {
|
|
return syntax.Unmarshal(data, ku)
|
|
}
|
|
|
|
// struct {} EndOfEarlyData;
|
|
type EndOfEarlyDataBody struct{}
|
|
|
|
func (eoed EndOfEarlyDataBody) Type() HandshakeType {
|
|
return HandshakeTypeEndOfEarlyData
|
|
}
|
|
|
|
func (eoed EndOfEarlyDataBody) Marshal() ([]byte, error) {
|
|
return []byte{}, nil
|
|
}
|
|
|
|
func (eoed *EndOfEarlyDataBody) Unmarshal(data []byte) (int, error) {
|
|
return 0, nil
|
|
}
|