Simplify the fingerprint code.

There was a lot of stuff left over from experimentation.
This commit is contained in:
Luke Curley 2023-04-13 13:34:34 -07:00
parent 5ba457bf65
commit 02dd787f97
8 changed files with 29 additions and 103 deletions

View File

@ -17,4 +17,4 @@ go run filippo.io/mkcert -ecdsa -install
go run filippo.io/mkcert -ecdsa -days 10 -cert-file "$CRT" -key-file "$KEY" localhost 127.0.0.1 ::1 go run filippo.io/mkcert -ecdsa -days 10 -cert-file "$CRT" -key-file "$KEY" localhost 127.0.0.1 ::1
# Compute the sha256 fingerprint of the certificate for WebTransport # Compute the sha256 fingerprint of the certificate for WebTransport
openssl x509 -in "$CRT" -outform der | openssl dgst -sha256 > ../player/src/transport/fingerprint.hex openssl x509 -in "$CRT" -outform der | openssl dgst -sha256 > ../player/fingerprint.hex

1
player/.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules node_modules
.parcel-cache .parcel-cache
dist dist
fingerprint.hex

View File

@ -1,5 +1,14 @@
import Player from "./player" import Player from "./player"
// @ts-ignore embed the certificate fingerprint using bundler
import fingerprintHex from 'bundle-text:../fingerprint.hex';
// Convert the hex to binary.
let fingerprint = [];
for (let c = 0; c < fingerprintHex.length-1; c += 2) {
fingerprint.push(parseInt(fingerprintHex.substring(c, c+2), 16));
}
const params = new URLSearchParams(window.location.search) const params = new URLSearchParams(window.location.search)
const url = params.get("url") || "https://localhost:4443/watch" const url = params.get("url") || "https://localhost:4443/watch"
@ -7,6 +16,10 @@ const canvas = document.querySelector<HTMLCanvasElement>("canvas#video")!
const player = new Player({ const player = new Player({
url: url, url: url,
fingerprint: { // TODO remove when Chrome accepts the system CA
"algorithm": "sha-256",
"value": new Uint8Array(fingerprint),
},
canvas: canvas, canvas: canvas,
}) })

View File

@ -4,6 +4,7 @@ import Video from "../video"
export interface PlayerInit { export interface PlayerInit {
url: string; url: string;
fingerprint?: WebTransportHash; // the certificate fingerprint, temporarily needed for local development
canvas: HTMLCanvasElement; canvas: HTMLCanvasElement;
} }
@ -20,6 +21,8 @@ export default class Player {
this.transport = new Transport({ this.transport = new Transport({
url: props.url, url: props.url,
fingerprint: props.fingerprint,
audio: this.audio, audio: this.audio,
video: this.video, video: this.video,
}) })
@ -29,10 +32,6 @@ export default class Player {
this.transport.close() this.transport.close()
} }
async connect(url: string) {
await this.transport.connect(url)
}
play() { play() {
this.audio.play({}) this.audio.play({})
//this.video.play() //this.video.play()

View File

@ -1 +0,0 @@
fingerprint.hex

View File

@ -5,11 +5,10 @@ import * as MP4 from "../mp4"
import Audio from "../audio" import Audio from "../audio"
import Video from "../video" import Video from "../video"
// @ts-ignore bundler embeds data
import fingerprint from 'bundle-text:./fingerprint.hex';
export interface TransportInit { export interface TransportInit {
url: string; url: string;
fingerprint?: WebTransportHash; // the certificate fingerprint, temporarily needed for local development
audio: Audio; audio: Audio;
video: Video; video: Video;
} }
@ -28,7 +27,7 @@ export default class Transport {
this.audio = props.audio; this.audio = props.audio;
this.video = props.video; this.video = props.video;
this.quic = this.connect(props.url) this.quic = this.connect(props)
// Create a unidirectional stream for all of our messages // Create a unidirectional stream for all of our messages
this.api = this.quic.then((q) => { this.api = this.quic.then((q) => {
@ -43,22 +42,16 @@ export default class Transport {
(await this.quic).close() (await this.quic).close()
} }
async connect(url: string): Promise<WebTransport> { // Helper function to make creating a promise easier
// Convert the hex to binary. private async connect(props: TransportInit): Promise<WebTransport> {
let hash = [];
for (let c = 0; c < fingerprint.length-1; c += 2) { let options: WebTransportOptions = {};
hash.push(parseInt(fingerprint.substring(c, c+2), 16)); if (props.fingerprint) {
options.serverCertificateHashes = [ props.fingerprint ]
} }
const quic = new WebTransport(url, { const quic = new WebTransport(props.url, options)
"serverCertificateHashes": [{
"algorithm": "sha-256",
"value": new Uint8Array(hash),
}]
})
await quic.ready await quic.ready
return quic return quic
} }

View File

@ -1,64 +0,0 @@
package web
import (
"context"
"log"
"net/http"
"time"
"github.com/kixelated/invoker"
)
type Server struct {
inner http.Server
config Config
}
type Config struct {
Addr string
CertFile string
KeyFile string
Fingerprint string // the TLS certificate fingerprint
}
func New(config Config) (s *Server) {
s = new(Server)
s.config = config
s.inner = http.Server{
Addr: config.Addr,
}
http.HandleFunc("/fingerprint", s.handleFingerprint)
return s
}
func (s *Server) Run(ctx context.Context) (err error) {
return invoker.Run(ctx, s.runServe, s.runShutdown)
}
func (s *Server) runServe(context.Context) (err error) {
// NOTE: Doesn't support context, which is why we need runShutdown
err = s.inner.ListenAndServeTLS(s.config.CertFile, s.config.KeyFile)
log.Println(err)
return err
}
// Gracefully shut down the server when the context is cancelled
func (s *Server) runShutdown(ctx context.Context) (err error) {
<-ctx.Done()
timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = s.inner.Shutdown(timeout)
return ctx.Err()
}
// Return the sha256 of the certificate as a temporary work-around for local development.
// TODO remove this when WebTransport uses the system CA
func (s *Server) handleFingerprint(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
_, _ = w.Write([]byte(s.config.Fingerprint))
}

View File

@ -2,16 +2,13 @@ package main
import ( import (
"context" "context"
"crypto/sha256"
"crypto/tls" "crypto/tls"
"encoding/hex"
"flag" "flag"
"fmt" "fmt"
"log" "log"
"github.com/kixelated/invoker" "github.com/kixelated/invoker"
"github.com/kixelated/warp/server/internal/warp" "github.com/kixelated/warp/server/internal/warp"
"github.com/kixelated/warp/server/internal/web"
) )
func main() { func main() {
@ -53,19 +50,7 @@ func run(ctx context.Context) (err error) {
return fmt.Errorf("failed to create warp server: %w", err) return fmt.Errorf("failed to create warp server: %w", err)
} }
hash := sha256.Sum256(tlsCert.Certificate[0])
fingerprint := hex.EncodeToString(hash[:])
webConfig := web.Config{
Addr: *addr,
CertFile: *cert,
KeyFile: *key,
Fingerprint: fingerprint,
}
webServer := web.New(webConfig)
log.Printf("listening on %s", *addr) log.Printf("listening on %s", *addr)
return invoker.Run(ctx, invoker.Interrupt, warpServer.Run, webServer.Run) return invoker.Run(ctx, invoker.Interrupt, warpServer.Run)
} }