bifrost

package module
v1.14.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2023 License: MPL-2.0 Imports: 14 Imported by: 0

README

Bifrost Bifrost

A simple mTLS authentication toolkit.

Bifrost consists of a Certificate Authority (CA) server that issues X.509 certificates, a Go package to fetch such certificates, and a Go package with HTTP middleware to identify and authenticate clients using such TLS certificates in requests.

Bifrost CA does not authenticate certificate signing requests before issuance. You must authorise or control access to Bifrost CA as needed.

Bifrost CA issues certificates signed by a private key and a TLS X.509 certificate. A TLS reverse proxy can use the issuing certificate to authenticate clients and secure access to web applications. Bifrost identifies clients uniquely by ECDSA public keys. Client identity namespaces allow Bifrost to be natively multi-tenant.

Releases

Bifrost binaries are available on the releases page.

ghcr.io/realimage/bifrost container image on ghcr.io contains all binaries.

ghcr.io/realimage/bifrost-authz contains the bouncer binary. This ia an AWS Lambda Function meant to used as a Lambda Authorizer in AWS API Gateway instances.

ghcr.io/realimage/bifrost-ca contains the issuer binary along with the AWS Lambda Web Adapter extension, making it suitable for AWS Lambda deployments.

podman pull ghcr.io/realimage/bifrost

Identity

Bifrost identities are UUID version 5 UUIDs, derived from ECDSA public keys. A client's identity is the sha1 hash of the namespace appended to the X and Y curve points (big-endian) of its ECDSA P256 public key.

In pseudo-code,

bifrostUUID = UUIDv5(sha1(NamespaceClientIdentity + PublicKey.X.Bytes() + PublicKey.Y.Bytes())

Components

bf (alpha)

bf is an interactive tool that generates Bifrost CA material. It uses Charm Cloud to store your key material securely in the cloud.

bfid

bfid prints the Bifrost UUID of a certificate, public key, or private key.

bouncer

bouncer is an AWS Lambda Authorizer Function meant for use with an AWS API Gateway instance in mTLS mode. Deploy it to AWS Lambda and use it as a Lambda Authorizer in an AWS API Gateway instance using mTLS.

Bouncer either returns an access policy that authorizes the request or fails.

This sample JSON represents what Bifrost expects in the Lambda request context object.

"requestContext": {
    "authentication": {
        "clientCert": {
            "clientCertPem": "-----BEGIN CERTIFICATE-----\nMIIEZTCCAk0CAQEwDQ...",
            "issuerDN": "C=012d325d-6a4e-4076-b49a-a3e84e52bf79,O=765e4c02-b41a-4226-8522-2a52f4fbeebe",
            "serialNumber": "1",
            "subjectDN": "C=2ef463c4-cca9-4885-a8e2-d041c90d61fa,O=765e4c02-b41a-4226-8522-2a52f4fbeebe",
            "validity": {
                "notAfter": "Aug  5 00:28:21 2120 GMT",
                "notBefore": "Aug 29 00:28:21 2020 GMT"
            }
        }
    },
}
issuer

OpenAPI schema

issuer accepts certificate requests and returns signed certificates. It reads a CA certificate and key from the CRT and KEY environment variables respectively. Each variable must be a URI to a PEM-encoded certificate or key. Issuer understands file, S3, and AWS Secrets Manager ARN URIs. The CA certificate must be a valid Bifrost certificate. If unconfigured, it looks for crt.pem and key.pem in the current working directory.

issuer returns its issuing namespace at /namespace. If enabled, issuer exposes prometheus format metrics at /metrics.

Web Application (alpha)

issuer includes an embedded web application that can generate private keys and request certificates from the API. Enable it by setting WEB=true in the issuer process's environment.

hallpass

hallpass is a simple mTLS reverse proxy ("gateway") suitable for local development. If client authentication succeeds, it proxies the request to the backend url. It also stores the namespace and public key from the certificate into the AWS Lambda Web Adapter request context header.

Hallpass will log TLS Pre-Master secrets to a file if the SSLKEYLOGFILE environment variable is present. Wireshark can use this file to decrypt traffic.

JSON sample representing output added as an encoded string to the request context header.

"requestContext": {
    "identity": {
        "sourceIp": "197.23.1.43",
        "userAgent": "curl"
    },
    "authorizer": {
        "namespace": "80485314-6c73-40ff-86c5-a5942a0f514f",
        "publicKey": "{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"10138...\",\"y\":\"63295...\"}",
    }
}

Run hallpass in front of a HTTP server which is listening on localhost port 5000:

env BACKEND_URL=http://127.0.0.1:5000 ./hallpass

Build

Native

Install Node.js & Go. Build static binaries on your machine.

pushd web; npm ci; popd
go generate -x ./...

mkdir bin
env CGO_ENABLED=0 go build -o bin ./...
Container

Build all binaries.

podman build -t ghcr.io/realimage/bifrost .

Build the CA container with the AWS Lambda Web Adapter extension.

podman build -f ca.Containerfile -t bifrost-ca .

Take Bifrost out for a spin

Here's what you need to get started.

  1. Install all bifrost binaries by running go install ./....
  2. Generate a new namespace UUID using export BF_NS=$(uuidgen).
  3. Ensure that python, curl, and openssl are available in your environment.
Start your engines

Set up server key material and start the CA and TLS reverse-proxy.

  1. Create ECDSA P256 private key in PEM format:

    openssl ecparam -out key.pem -name prime256v1 -genkey -noout

  2. Create self-signed CA certificate:

    openssl req -new -key key.pem -x509 -nodes -days 3650 \
      -subj "/CN=$(bfid -ns "$BF_NS" key.pem)/O=$BF_NS" \
      -addext basicConstraints=critical,CA:TRUE,pathlen:0 \
     -out crt.pem
    
  3. Start issuer (CA), hallpass (TLS reverse-proxy), and a python web server (target).

    issuer &
    hallpass &
    python -m http.server 8080 &
    
Create a client identity
  1. Generate a new client identity key:

    openssl ecparam -out clientkey.pem -name prime256v1 -genkey -noout

  2. Create a Certificate Signing Request with the client private key:

    openssl req -new -key clientkey.pem -sha256 \
      -subj "/CN=$(bfid -ns "$BF_NS" clientkey.pem)/O=$BF_NS" \
      -out csr.pem
    
  3. Fetch signed certificate from the CA:

    curl -X POST -H "Content-Type: text/plain" --data-binary "@csr.pem" \
      "localhost:8888/issue" >clientcrt.pem`
    
  4. Make a request through hallpass to the python web server:

    curl --cert clientcrt.pem --key clientkey.pem -k https://localhost:8443

  5. Admire your shiny new client certificate (optional):

    $ openssl x509 -in clientcrt.pem -noout -text
    Certificate:
        Data:
            Version: 3 (0x2)
            Serial Number: 871355257622038992 (0xc17acfd7bbb09d0)
            Signature Algorithm: ecdsa-with-SHA256
            Issuer: CN = 46d6516e-715f-5a8a-8523-c2924b2a53d7, O = 00000000-0000-0000-0000-000000000000
            Validity
                Not Before: Jul 12 23:09:46 2023 GMT
                Not After : Jul 13 00:09:46 2023 GMT
            Subject: O = 00000000-0000-0000-0000-000000000000, CN = 8b9fca79-13e0-5157-b754-ff2e4e985c30
            Subject Public Key Info:
                Public Key Algorithm: id-ecPublicKey
                    Public-Key: (256 bit)
                    pub:
                        04:84:4a:3b:fa:2e:dd:07:d5:a7:96:26:68:ac:81:
                        16:8a:cb:57:02:0a:c7:ae:d3:b3:da:b5:b4:2d:a5:
                        c8:65:c2:4d:88:45:00:5a:44:f3:30:52:ab:63:42:
                        59:3d:50:68:50:45:e0:60:61:e1:57:b8:5c:dc:87:
                        7f:f9:7e:07:f6
                    ASN1 OID: prime256v1
                    NIST CURVE: P-256
            X509v3 extensions:
                X509v3 Key Usage: critical
                    Digital Signature
                X509v3 Extended Key Usage: 
                    TLS Web Client Authentication
                X509v3 Authority Key Identifier: 
                    CA:2F:94:0D:43:FB:6D:00:66:09:50:4C:8C:1F:A3:BC:C1:EF:98:F4
        Signature Algorithm: ecdsa-with-SHA256
        Signature Value:
            30:45:02:21:00:a3:2a:99:6e:29:b6:97:61:55:ac:a5:96:9c:
            ab:c3:86:44:4e:86:f5:1f:56:34:49:a7:36:b5:6c:db:72:65:
            a6:02:20:14:a9:d2:07:d5:63:17:d5:e0:3b:e3:f7:ef:e7:d0:
            65:86:c3:74:5e:b4:61:87:cd:af:6a:71:af:cd:cf:45:8b
    

Fishy Benchmarks

A toy benchmark for your favourite toy CA.

Fishy Benchmark

issuer issued 10,000 certificates on my Macbook Pro M1 Pro in ~41s. The slowest request completed in 12ms. With a mean response time of 4ms this is objectively the fastest CA on the planet. Statisticians hate this one weird trick.

LICENSE

Bifrost is available under the terms of the Mozilla Public License 2.0.

Qube Cinema © 2023

Documentation

Overview

Bifrost is an mTLS authentication toolkit.

Index

Examples

Constants

View Source
const (
	SignatureAlgorithm = x509.ECDSAWithSHA256
	PublicKeyAlgorithm = x509.ECDSA
)

Signature and Public Key Algorithms

Variables

View Source
var (
	ErrCertificateInvalid        = errors.New("certificate invalid")
	ErrCertificateRequestInvalid = errors.New("certificate request invalid")
	ErrIncorrectMismatch         = errors.New("namespace mismatch")
)

Errors.

Functions

func HTTPClient added in v0.0.6

func HTTPClient(clientCert tls.Certificate, roots *x509.CertPool, ssllog io.Writer) *http.Client

HTTPClient returns a http.Client set up for TLS Client Authentication (mTLS). If roots is not nil, then only those Root CAs are used to authenticate server certs. If ssllog is not nil, the client will log TLS key material to it.

func NewIdentity added in v0.0.8

func NewIdentity() (*ecdsa.PrivateKey, []byte, error)

NewIdentity generates a new ECDSA private key. The private key is also returned as a PEM encoded DER bytes in the second return value.

func UUID

func UUID(ns uuid.UUID, pubkey *ecdsa.PublicKey) uuid.UUID

UUID returns a unique identifier derived from the namespace and the client's public key identity. The UUID is generated by SHA-1 hashing the namesapce UUID with the big endian bytes of the X and Y curve points from the public key.

func X509ToTLSCertificate

func X509ToTLSCertificate(cert *x509.Certificate, key *ecdsa.PrivateKey) *tls.Certificate

X509ToTLSCertificate puts an x509.Certificate inside a tls.Certificate.

Types

type Certificate added in v1.11.0

type Certificate struct {
	*x509.Certificate
	Id        uuid.UUID
	Namespace uuid.UUID
	PublicKey *ecdsa.PublicKey
}

Certificate is a bifrost certificate. It embeds the x509 certificate and adds the bifrost ID, namespace, and public key.

func ParseCertificate

func ParseCertificate(asn1Data []byte) (*Certificate, error)

ParseCertificate parses a DER encoded certificate and validates it. On success, it returns the bifrost certificate.

func RequestCertificate

func RequestCertificate(
	ctx context.Context,
	url string,
	ns uuid.UUID,
	key *ecdsa.PrivateKey,
) (*Certificate, error)

RequestCertificate sends a certificate request to url and returns the signed certificate.

Example
exampleNS := uuid.MustParse("228b9676-998e-489a-8468-92d46a94a32d")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// TODO: handle errors
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
cert, _ := RequestCertificate(ctx, "https://bifrost-ca", exampleNS, key)
fmt.Println(cert.Subject)
Output:

func (Certificate) ToTLSCertificate added in v1.11.0

func (c Certificate) ToTLSCertificate(key *ecdsa.PrivateKey) (*tls.Certificate, error)

ToTLSCertificate puts a bifrost certificate inside a tls.Certificate.

func (*Certificate) Verify added in v1.11.0

func (c *Certificate) Verify() error

Verify validates a bifrost certificate. It checks for the correct signature algorithm, identity namespace, and identity. On success, it sets the ID, Namespace, and PublicKey fields.

type CertificateRequest added in v1.11.0

type CertificateRequest struct {
	*x509.CertificateRequest
	Id        uuid.UUID
	Namespace uuid.UUID
	PublicKey *ecdsa.PublicKey
}

func ParseCertificateRequest added in v1.5.2

func ParseCertificateRequest(asn1Data []byte) (*CertificateRequest, error)

ParseCertificateRequest parses a DER encoded certificate request and validates it. On success, it returns the bifrost namespace, certificate request, and certificate public key.

func (*CertificateRequest) Verify added in v1.11.0

func (c *CertificateRequest) Verify() error

Verify validates a bifrost certificate request.

Directories

Path Synopsis
Package asgard provides middleware for use in HTTP API servers.
Package asgard provides middleware for use in HTTP API servers.
cmd
bf
internal
cafiles
cafiles can fetch CA certificate and private key PEM files from many storage backends.
cafiles can fetch CA certificate and private key PEM files from many storage backends.
Package tinyca implements a Certificate Authority that issues certificates for client authentication.
Package tinyca implements a Certificate Authority that issues certificates for client authentication.
Package web embeds static website files that webservers can serve up to clients.
Package web embeds static website files that webservers can serve up to clients.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL