mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-18 08:26:45 +02:00
181 lines
4.2 KiB
Go
181 lines
4.2 KiB
Go
package handshake
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/lucas-clemente/quic-go/crypto"
|
|
"github.com/lucas-clemente/quic-go/internal/utils"
|
|
"github.com/lucas-clemente/quic-go/qerr"
|
|
)
|
|
|
|
type serverConfigClient struct {
|
|
raw []byte
|
|
ID []byte
|
|
obit []byte
|
|
expiry time.Time
|
|
|
|
kex crypto.KeyExchange
|
|
sharedSecret []byte
|
|
}
|
|
|
|
var (
|
|
errMessageNotServerConfig = errors.New("ServerConfig must have TagSCFG")
|
|
)
|
|
|
|
// parseServerConfig parses a server config
|
|
func parseServerConfig(data []byte) (*serverConfigClient, error) {
|
|
message, err := ParseHandshakeMessage(bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if message.Tag != TagSCFG {
|
|
return nil, errMessageNotServerConfig
|
|
}
|
|
|
|
scfg := &serverConfigClient{raw: data}
|
|
err = scfg.parseValues(message.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return scfg, nil
|
|
}
|
|
|
|
func (s *serverConfigClient) parseValues(tagMap map[Tag][]byte) error {
|
|
// SCID
|
|
scfgID, ok := tagMap[TagSCID]
|
|
if !ok {
|
|
return qerr.Error(qerr.CryptoMessageParameterNotFound, "SCID")
|
|
}
|
|
if len(scfgID) != 16 {
|
|
return qerr.Error(qerr.CryptoInvalidValueLength, "SCID")
|
|
}
|
|
s.ID = scfgID
|
|
|
|
// KEXS
|
|
// TODO: setup Key Exchange
|
|
kexs, ok := tagMap[TagKEXS]
|
|
if !ok {
|
|
return qerr.Error(qerr.CryptoMessageParameterNotFound, "KEXS")
|
|
}
|
|
if len(kexs)%4 != 0 {
|
|
return qerr.Error(qerr.CryptoInvalidValueLength, "KEXS")
|
|
}
|
|
c255Foundat := -1
|
|
|
|
for i := 0; i < len(kexs)/4; i++ {
|
|
if bytes.Equal(kexs[4*i:4*i+4], []byte("C255")) {
|
|
c255Foundat = i
|
|
break
|
|
}
|
|
}
|
|
if c255Foundat < 0 {
|
|
return qerr.Error(qerr.CryptoNoSupport, "KEXS: Could not find C255, other key exchanges are not supported")
|
|
}
|
|
|
|
// AEAD
|
|
aead, ok := tagMap[TagAEAD]
|
|
if !ok {
|
|
return qerr.Error(qerr.CryptoMessageParameterNotFound, "AEAD")
|
|
}
|
|
if len(aead)%4 != 0 {
|
|
return qerr.Error(qerr.CryptoInvalidValueLength, "AEAD")
|
|
}
|
|
var aesgFound bool
|
|
for i := 0; i < len(aead)/4; i++ {
|
|
if bytes.Equal(aead[4*i:4*i+4], []byte("AESG")) {
|
|
aesgFound = true
|
|
break
|
|
}
|
|
}
|
|
if !aesgFound {
|
|
return qerr.Error(qerr.CryptoNoSupport, "AEAD")
|
|
}
|
|
|
|
// PUBS
|
|
pubs, ok := tagMap[TagPUBS]
|
|
if !ok {
|
|
return qerr.Error(qerr.CryptoMessageParameterNotFound, "PUBS")
|
|
}
|
|
|
|
var pubs_kexs []struct{Length uint32; Value []byte}
|
|
var last_len uint32
|
|
|
|
for i := 0; i < len(pubs)-3; i += int(last_len)+3 {
|
|
// the PUBS value is always prepended by 3 byte little endian length field
|
|
|
|
err := binary.Read(bytes.NewReader([]byte{pubs[i], pubs[i+1], pubs[i+2], 0x00}), binary.LittleEndian, &last_len);
|
|
if err != nil {
|
|
return qerr.Error(qerr.CryptoInvalidValueLength, "PUBS not decodable")
|
|
}
|
|
if last_len == 0 {
|
|
return qerr.Error(qerr.CryptoInvalidValueLength, "PUBS")
|
|
}
|
|
|
|
if i+3+int(last_len) > len(pubs) {
|
|
return qerr.Error(qerr.CryptoInvalidValueLength, "PUBS")
|
|
}
|
|
|
|
pubs_kexs = append(pubs_kexs, struct{Length uint32; Value []byte}{last_len, pubs[i+3:i+3+int(last_len)]})
|
|
}
|
|
|
|
if c255Foundat >= len(pubs_kexs) {
|
|
return qerr.Error(qerr.CryptoMessageParameterNotFound, "KEXS not in PUBS")
|
|
}
|
|
|
|
if pubs_kexs[c255Foundat].Length != 32 {
|
|
return qerr.Error(qerr.CryptoInvalidValueLength, "PUBS")
|
|
}
|
|
|
|
var err error
|
|
s.kex, err = crypto.NewCurve25519KEX()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
|
|
s.sharedSecret, err = s.kex.CalculateSharedKey(pubs_kexs[c255Foundat].Value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// OBIT
|
|
obit, ok := tagMap[TagOBIT]
|
|
if !ok {
|
|
return qerr.Error(qerr.CryptoMessageParameterNotFound, "OBIT")
|
|
}
|
|
if len(obit) != 8 {
|
|
return qerr.Error(qerr.CryptoInvalidValueLength, "OBIT")
|
|
}
|
|
s.obit = obit
|
|
|
|
// EXPY
|
|
expy, ok := tagMap[TagEXPY]
|
|
if !ok {
|
|
return qerr.Error(qerr.CryptoMessageParameterNotFound, "EXPY")
|
|
}
|
|
if len(expy) != 8 {
|
|
return qerr.Error(qerr.CryptoInvalidValueLength, "EXPY")
|
|
}
|
|
// make sure that the value doesn't overflow an int64
|
|
// furthermore, values close to MaxInt64 are not a valid input to time.Unix, thus set MaxInt64/2 as the maximum value here
|
|
expyTimestamp := utils.MinUint64(binary.LittleEndian.Uint64(expy), math.MaxInt64/2)
|
|
s.expiry = time.Unix(int64(expyTimestamp), 0)
|
|
|
|
// TODO: implement VER
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *serverConfigClient) IsExpired() bool {
|
|
return s.expiry.Before(time.Now())
|
|
}
|
|
|
|
func (s *serverConfigClient) Get() []byte {
|
|
return s.raw
|
|
}
|