Skip to content

Commit

Permalink
add session
Browse files Browse the repository at this point in the history
  • Loading branch information
ainghazal committed Jan 10, 2024
1 parent 41eb06c commit e72aca0
Show file tree
Hide file tree
Showing 5 changed files with 534 additions and 0 deletions.
58 changes: 58 additions & 0 deletions internal/optional/optional.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package optional

import (
"reflect"

"github.com/ooni/minivpn/internal/runtimex"
)

// Value is an optional value. The zero value of this structure
// is equivalent to the one you get when calling [None].
type Value[T any] struct {
// indirect is the indirect pointer to the value.
indirect *T
}

// None constructs an empty value.
func None[T any]() Value[T] {
return Value[T]{nil}
}

// Some constructs a some value unless T is a pointer and points to
// nil, in which case [Some] is equivalent to [None].
func Some[T any](value T) Value[T] {
v := Value[T]{}
maybeSetFromValue(&v, value)
return v
}

// maybeSetFromValue sets the underlying value unless T is a pointer
// and points to nil in which case we set the Value to be empty.
func maybeSetFromValue[T any](v *Value[T], value T) {
rv := reflect.ValueOf(value)
if rv.Type().Kind() == reflect.Pointer && rv.IsNil() {
v.indirect = nil
return
}
v.indirect = &value
}

// IsNone returns whether this [Value] is empty.
func (v Value[T]) IsNone() bool {
return v.indirect == nil
}

// Unwrap returns the underlying value or panics. In case of
// panic, the value passed to panic is an error.
func (v Value[T]) Unwrap() T {
runtimex.Assert(!v.IsNone(), "is none")
return *v.indirect
}

// UnwrapOr returns the fallback if the [Value] is empty.
func (v Value[T]) UnwrapOr(fallback T) T {
if v.IsNone() {
return fallback
}
return v.Unwrap()
}
56 changes: 56 additions & 0 deletions internal/session/datachannelkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package session

import (
"errors"
"fmt"
"sync"
)

// DataChannelKey represents a pair of key sources that have been negotiated
// over the control channel, and from which we will derive local and remote
// keys for encryption and decrption over the data channel. The index refers to
// the short key_id that is passed in the lower 3 bits if a packet header.
// The setup of the keys for a given data channel (that is, for every key_id)
// is made by expanding the keysources using the prf function.
// Do note that we are not yet implementing key renegotiation - but the index
// is provided for convenience when/if we support that in the future.
type DataChannelKey struct {
index uint32
ready bool
local *KeySource
remote *KeySource
mu sync.Mutex
}

// errDayaChannelKey is a [DataChannelKey] error.
var errDataChannelKey = errors.New("bad data-channel key")

// Local returns the local [KeySource]
func (dck *DataChannelKey) Local() *KeySource {
return dck.local
}

// Remote returns the local [KeySource]
func (dck *DataChannelKey) Remote() *KeySource {
return dck.remote
}

// AddRemoteKey adds the server keySource to our dataChannelKey. This makes the
// dataChannelKey ready to be used.
func (dck *DataChannelKey) AddRemoteKey(k *KeySource) error {
dck.mu.Lock()
defer dck.mu.Unlock()
if dck.ready {
return fmt.Errorf("%w: %s", errDataChannelKey, "cannot overwrite remote key slot")
}
dck.remote = k
dck.ready = true
return nil
}

// Ready returns whether the [DataChannelKey] is ready.
func (dck *DataChannelKey) Ready() bool {
dck.mu.Lock()
defer dck.mu.Unlock()
return dck.ready
}
4 changes: 4 additions & 0 deletions internal/session/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Package session keeps state for the application, including internal state
// transitions for the OpenVPN protocol, data channel keys, and all the state
// pertaining to the different packet counters.
package session
60 changes: 60 additions & 0 deletions internal/session/keysource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package session

import (
"bytes"
"errors"
"fmt"

"github.com/ooni/minivpn/internal/bytesx"
)

// randomFn mocks the function to generate random bytes.
var randomFn = bytesx.GenRandomBytes

// errRandomBytes is the error returned when we cannot generate random bytes.
var errRandomBytes = errors.New("error generating random bytes")

// KeySource contains random data to generate keys.
type KeySource struct {
R1 [32]byte
R2 [32]byte
PreMaster [48]byte
}

// Bytes returns the byte representation of a [KeySource].
func (k *KeySource) Bytes() []byte {
buf := &bytes.Buffer{}
buf.Write(k.PreMaster[:])
buf.Write(k.R1[:])
buf.Write(k.R2[:])
return buf.Bytes()
}

// NewKeySource constructs a new [KeySource].
func NewKeySource() (*KeySource, error) {
random1, err := randomFn(32)
if err != nil {
return nil, fmt.Errorf("%w: %s", errRandomBytes, err.Error())
}

var r1, r2 [32]byte
var preMaster [48]byte
copy(r1[:], random1)

random2, err := randomFn(32)
if err != nil {
return nil, fmt.Errorf("%w: %s", errRandomBytes, err.Error())
}
copy(r2[:], random2)

random3, err := randomFn(48)
if err != nil {
return nil, fmt.Errorf("%w: %s", errRandomBytes, err.Error())
}
copy(preMaster[:], random3)
return &KeySource{
R1: r1,
R2: r2,
PreMaster: preMaster,
}, nil
}
Loading

0 comments on commit e72aca0

Please sign in to comment.