Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add libvinis integration #746

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ SALT=f1nd1ngn3m0
# * drop
# * moose - internal builds only
# * quench - internal builds only
# * vinis - internal builds only
# * internal - internal builds only
FEATURES=telio drop
# Used for mage targets, when set to 1, fetching docker images will be skipped if it is already fetched.
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "third-party/libquench-go"]
url = ../../libquench-go.git
path = third-party/libquench-go
[submodule "third-party/libvinis-go"]
path = third-party/libvinis-go
url = ../../libvinis-go.git
9 changes: 9 additions & 0 deletions ci/check_dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ libquench_artifact_url="${LIBQUENCH_ARTIFACTS_URL}/${LIBQUENCH_VERSION}/linux.zi
libquench_zipfile="${temp_dir}/libquench-${LIBQUENCH_VERSION}.zip"
libquench_dst="${temp_dir}/libquench-${LIBQUENCH_VERSION}"

libvinis_artifact_url="${LIBVINIS_ARTIFACTS_URL}/${LIBVINIS_VERSION}/linux.zip"
libvinis_zipfile="${temp_dir}/libvinis-${LIBVINIS_VERSION}.zip"
libvinis_dst="${temp_dir}/libvinis-${LIBVINIS_VERSION}"

lib_versions_file="${WORKDIR}/lib-versions.env"
checkout_completed_flag_file="${lib_root}/checkout-completed-flag"

Expand Down Expand Up @@ -114,6 +118,7 @@ if [[ "${FEATURES:-""}" == *internal* ]]; then
fetch_gitlab_artifact "${libmoose_nordvpnapp_artifact_url}" "${libmoose_nordvpnapp_zipfile}"
fetch_gitlab_artifact "${libmoose_worker_artifact_url}" "${libmoose_worker_zipfile}"
fetch_gitlab_artifact "${libquench_artifact_url}" "${libquench_zipfile}"
fetch_gitlab_artifact "${libvinis_artifact_url}" "${libvinis_zipfile}"
fi

# ====================[ Unzip files ]=========================
Expand All @@ -125,6 +130,7 @@ if [[ "${FEATURES:-""}" == *internal* ]]; then
unzip -o "${libmoose_nordvpnapp_zipfile}" -d "${temp_dir}" && mv "${temp_dir}/out" "${libmoose_nordvpnapp_dst}"
unzip -o "${libmoose_worker_zipfile}" -d "${temp_dir}" && mv "${temp_dir}/out" "${libmoose_worker_dst}"
unzip -o "${libquench_zipfile}" -d "${temp_dir}" && mv "${temp_dir}/dist" "${libquench_dst}"
unzip -o "${libvinis_zipfile}" -d "${temp_dir}" && mv "${temp_dir}/dist" "${libvinis_dst}"
fi

# ====================[ Copy to bin/deps/libs ]=========================
Expand All @@ -149,6 +155,9 @@ if [[ "${FEATURES:-""}" == *internal* ]]; then

# libquench
copy_to_libs "${libquench_dst}/linux/release" "libquench.so"

# libvinis
copy_to_libs "${libvinis_dst}/linux/release" "libvinis.so"
fi

# remove leftovers
Expand Down
4 changes: 4 additions & 0 deletions ci/compile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ if [[ $tags == *"quench"* ]]; then
source "${WORKDIR}"/ci/add_private_bindings.sh quench ./third-party/libquench-go
fi

if [[ $tags == *"vinis"* ]]; then
source "${WORKDIR}"/ci/add_private_bindings.sh vinis ./third-party/libvinis-go
fi

for program in ${!names_map[*]}; do # looping over keys
pushd "${WORKDIR}/cmd/${program}"
# BUILDMODE can be no value and `go` does not like empty parameter ''
Expand Down
2 changes: 1 addition & 1 deletion ci/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ excluded_packages="moose\|cmd\/daemon\|telio\|daemon\/vpn\/openvpn"
excluded_packages=$excluded_packages"\|meshnet\/mesh\/nordlynx\|fileshare\/drop"
excluded_packages=$excluded_packages"\|events\/moose"
excluded_packages=$excluded_packages"\|pb\|magefiles"
excluded_packages=$excluded_packages"\|daemon\/vpn\/quench"
excluded_packages=$excluded_packages"\|daemon\/vpn\/quench\/vinis"
excluded_categories="root,link,firewall,route,file,integration"

tags="internal"
Expand Down
9 changes: 9 additions & 0 deletions cmd/daemon/no_vinis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//go:build !vinis

package main

import "net/http"

func getPinningTransport(inner http.RoundTripper) http.RoundTripper {
return inner
}
18 changes: 10 additions & 8 deletions cmd/daemon/transports.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,11 @@ func createTimedOutTransport(
if containsH1 {
h1ReTransport := request.NewHTTPReTransport(createH1Transport(resolver, fwmark))
connectSubject.Subscribe(h1ReTransport.NotifyConnect)
h1Transport = request.NewPublishingRoundTripper(
h1ReTransport,
httpCallsSubject,
)
h1Transport = getPinningTransport(
request.NewPublishingRoundTripper(
h1ReTransport,
httpCallsSubject,
))
if !containsH3 {
return h1Transport
}
Expand All @@ -158,10 +159,11 @@ func createTimedOutTransport(
}
h3ReTransport := request.NewQuicTransport(createH3Transport)
connectSubject.Subscribe(h3ReTransport.NotifyConnect)
h3Transport = request.NewPublishingRoundTripper(
h3ReTransport,
httpCallsSubject,
)
h3Transport = getPinningTransport(
request.NewPublishingRoundTripper(
h3ReTransport,
httpCallsSubject,
))
if !containsH1 {
return h3Transport
}
Expand Down
13 changes: 13 additions & 0 deletions cmd/daemon/vinis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//go:build vinis

package main

import (
"net/http"

"github.com/NordSecurity/nordvpn-linux/request/vinis"
)

func getPinningTransport(inner http.RoundTripper) http.RoundTripper {
return vinis.New(inner)
}
3 changes: 2 additions & 1 deletion lib-versions.env
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ LIBTELIO_VERSION=v5.1.5
LIBDROP_VERSION=v8.1.1
LIBMOOSE_NORDVPNAPP_VERSION=v15.0.2-nordVpnApp
LIBMOOSE_WORKER_VERSION=v14.3.0-worker
LIBQUENCH_VERSION=v0.0.19
LIBQUENCH_VERSION=v0.0.20
LIBVINIS_VERSION=v0.1.5
11 changes: 11 additions & 0 deletions request/vinis/cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build vinis

package vinis

// #cgo amd64 LDFLAGS: -L${SRCDIR}/../../../../bin/deps/lib/amd64/latest -lvinis
// #cgo 386 LDFLAGS: -L${SRCDIR}/../../../../bin/deps/lib/i386/latest -lvinis
// #cgo arm LDFLAGS: -L${SRCDIR}/../../../../bin/deps/lib/armel/latest -lvinis
// #cgo arm LDFLAGS: -L${SRCDIR}/../../../../bin/deps/lib/armhf/latest -lvinis
// #cgo arm64 LDFLAGS: -L${SRCDIR}/../../../../bin/deps/lib/aarch64/latest -lvinis
// #cgo LDFLAGS: -ldl -lm
import "C"
145 changes: 145 additions & 0 deletions request/vinis/libvinis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//go:build vinis

package vinis

import (
"bytes"
"crypto/sha256"
"crypto/x509"
"fmt"
"net/http"
"strings"

vinisBindings "vinis"
)

const (
// PinURL is the url for certificate pins
PinURL = "https://"
)

// PinningTransport implements certificate pinning for HTTP requests
type PinningTransport struct {
inner http.RoundTripper
pdpClient *vinisBindings.PdpUrlConnectionClient
pdpCache *vinisBindings.PdpCache
}

func New(inner http.RoundTripper) http.RoundTripper {
//TODO/FIXME: remove after debug
return inner
// if inner == nil {
// inner = http.DefaultTransport
// }

// transport, ok := inner.(*http.Transport)
// if !ok {
// panic("wrapped transport must be *http.Transport")
// }

// pt := &PinningTransport{
// inner: inner,
// pdpClient: &vinisBindings.PdpUrlConnectionClient{Origin: PinURL},
// pdpCache: vinisBindings.NewMemoryCache(),
// }

// if transport.TLSClientConfig == nil {
// transport.TLSClientConfig = &tls.Config{}
// }

// transport.TLSClientConfig.VerifyPeerCertificate = pt.verifyCertificates

// return pt
}

// RoundTrip implements the http.RoundTripper interface
func (t *PinningTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return t.inner.RoundTrip(req)
}

func (t *PinningTransport) verifyCertificates(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
// when a TLS connection is established, the server presents a certificate chain.
// verifiedChains represents the validated certificate chains after the standard TLS verification process
if len(verifiedChains) == 0 || len(verifiedChains[0]) == 0 {
return fmt.Errorf("no verified certificate chain")
}

// leaf certificate
cert := verifiedChains[0][0]

// Pins must match exactly the certificates Subject Common Name or Subject Alternative Name
domains := NewDomainSet(cert.Subject.CommonName, cert.DNSNames...)

//fmt.Println("~~~LEAF CERT CN:", domain)
// fmt.Println("~~~CERT PUB KEY b64:", calculateEncodeB64Hash(cert.RawSubjectPublicKeyInfo))
// fmt.Println("~~~CERT PUB KEY hex:", calculateEncodeHexHash(cert.RawSubjectPublicKeyInfo))

dmns1 := domains.getDomains()

fmt.Println("~~~vinisBindings.FindPins domains:", dmns1)

pins, err := vinisBindings.FindPins(t.pdpCache, t.pdpClient, vinisBindings.PinQuery{Domains: dmns1})
if err != nil {
return fmt.Errorf("find pins: %w", err)
}

// RawSubjectPublicKeyInfo - DER encoded SubjectPublicKeyInfo.
certPubKeyHash := hash(cert.RawSubjectPublicKeyInfo)

for _, pin := range pins.Domains {
for _, fp := range pin.PubKeyFingerprints {
if comparePins(certPubKeyHash, fp) {
return nil
}
}
}

return fmt.Errorf("certificate pin not found")
}

func hash(data []byte) []byte {
sum := sha256.Sum256(data)
return sum[:]
}

func comparePins(crrPin, pinnedPin []byte) bool {
return bytes.Equal(crrPin, pinnedPin)
}

type domainSet struct {
names map[string]struct{}
}

func NewDomainSet(name string, names ...string) *domainSet { //TODO/FIXME: add unit tests
ds := &domainSet{
names: map[string]struct{}{},
}
ds.addName(name)
for _, nm := range names {
ds.addName(nm)
}
return ds
}

func (ds *domainSet) getDomains() []string {
rc := []string{}
for k := range ds.names {
rc = append(rc, k)
}
return rc
}

func (ds *domainSet) addName(nm string) { //TODO/FIXME: add unit tests
nmParts := strings.Split(nm, ".")
if len(nmParts) == 0 {
return
}
if nmParts[0] == "*" && len(nmParts) > 2 { // avoid `*.com` case, but `*.nordvpn.com` is ok
nm1 := strings.Join(nmParts[1:], ".")
ds.names[nm1] = struct{}{}
}
if nmParts[0] != "*" && len(nmParts) >= 2 {
nm1 := strings.Join(nmParts[:], ".")
ds.names[nm1] = struct{}{}
}
}
1 change: 1 addition & 0 deletions third-party/libvinis-go
Submodule libvinis-go added at 541b34
Loading