Skip to content

Commit

Permalink
Add initial libvinis integration
Browse files Browse the repository at this point in the history
Signed-off-by: keliramu <ramunas.keliuotis@nordsec.com>
  • Loading branch information
keliramu committed Jan 20, 2025
1 parent 98ee7b9 commit df01949
Show file tree
Hide file tree
Showing 12 changed files with 209 additions and 10 deletions.
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 = ../../restricted/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://pdp.nordvpn.com"
)

// 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

0 comments on commit df01949

Please sign in to comment.