bifrost

package module
v1.18.2 Latest Latest
Warning

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

Go to latest
Published: Apr 15, 2024 License: MIT Imports: 20 Imported by: 0

README

Bifrost Bifrost

Go Reference

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 for Windows, MacOS, and Linux on the releases page.

Container images are available at 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())

Build

Native

Install Node.js & Go. Build static binaries on your machine for all supported platforms.

./build.sh
Container

Build an image with ko.

ko build --local ./cmd/bf

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=$(bf new ns).
  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 Bifrost ECDSA private key:

    bf new id -o key.pem

  2. Create self-signed CA root certificate:

    bf new ca -o cert.pem

  3. Start the CA issuer, reverse proxy, and the target web server.

    bf ca &
    bf proxy &
    python -m http.server 8080 &
    
Create a client identity
  1. Generate a new client identity key:

    bf new key -o clientkey.pem

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

    bf new csr clientkey.pem -o 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 the mTLS proxy 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

Bifrost CA issued 10,000 certificates on my Macbook Pro M1 Pro in ~41s. Your results may vary.

LICENSE

Bifrost is available under the terms of the MIT License.

Qube Cinema © 2023, 2024

Documentation

Overview

Package bifrost provides simple Public Key Infrastructure (PKI) services.

Bifrost identifies clients by their private keys. Keys are deterministically mapped to UUIDs by hashing them with the namespace UUID. The same key maps to different UUIDs in different namespaces. Clients can request certificates for their UUIDs. The certificates are signed by a root CA.

Index

Examples

Constants

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

Signature and Public Key Algorithms

Variables

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

Certificate related errors.

View Source
var StatsForNerds = metrics.NewSet()

StatsForNerds captures metrics from various bifrost processes.

Functions

func HTTPClient added in v0.0.6

func HTTPClient(
	caUrl string,
	ns uuid.UUID,
	privkey *PrivateKey,
	roots *x509.CertPool,
	ssllog io.Writer,
) (*http.Client, error)

HTTPClient returns a http.Client set up for TLS Client Authentication (mTLS). The client will request a new certificate from the bifrost caUrl when needed. 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 Template added in v1.16.0

func Template(ns uuid.UUID, key *PublicKey) *x509.CertificateRequest

Template returns a bifrost certificate template for the given namespace and public key.

func UUID

func UUID(ns uuid.UUID, pubkey *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 *PublicKey
}

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

func NewCertificate added in v1.16.0

func NewCertificate(cert *x509.Certificate) (*Certificate, error)

NewCertificate creates a bifrost certificate from an x509 certificate. It checks for the correct signature algorithm, identity namespace, and identity. On success, it sets the ID, Namespace, and PublicKey fields.

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,
	caUrl string,
	ns uuid.UUID,
	key *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, _ := NewPrivateKey()
cert, _ := RequestCertificate(ctx, "https://bifrost-ca", exampleNS, key)
fmt.Println(cert.Subject)
Output:

func (Certificate) IsCA added in v1.16.9

func (c Certificate) IsCA() bool

func (*Certificate) IssuedTo added in v1.16.0

func (c *Certificate) IssuedTo(key *PublicKey) bool

IssuedTo returns true if the certificate was issued to the given public key.

func (Certificate) ToTLSCertificate added in v1.11.0

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

ToTLSCertificate returns a tls.Certificate from a bifrost certificate and private key.

type CertificateRequest added in v1.11.0

type CertificateRequest struct {
	*x509.CertificateRequest

	ID        uuid.UUID
	Namespace uuid.UUID
	PublicKey *PublicKey
}

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

func NewCertificateRequest added in v1.16.0

func NewCertificateRequest(cert *x509.CertificateRequest) (*CertificateRequest, error)

NewCertificateRequest creates a bifrost certificate request from an x509 certificate request. It checks for the correct signature algorithm, identity namespace, and identity. On success, it sets the ID, Namespace, and PublicKey fields.

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.

type Identity added in v1.16.9

type Identity struct {
	Namespace uuid.UUID
	PublicKey *PublicKey
}

Identity represents a unique identity in the system.

func ParseIdentity added in v1.16.9

func ParseIdentity(data []byte) (*Identity, error)

ParseIdentity parses a Bifrost identity from a PEM-encoded block. The block may contain a private key, public key, certificate, or certificate request.

If the block contains a private or a public key, the returned identity will contain the public key. If the block contains a certificate or certificate request, the returned identity will contain the public key and namespace.

func (Identity) String added in v1.16.9

func (i Identity) String() string

func (Identity) UUID added in v1.16.9

func (i Identity) UUID() uuid.UUID

type PrivateKey added in v1.15.3

type PrivateKey struct {
	*ecdsa.PrivateKey
}

PrivateKey is a wrapper around an ECDSA private key. PrivateKey implements the Marshaler and Unmarshaler interfaces for binary, text, JSON, and DynamoDB. Keys are generated using the P-256 elliptic curve. Keys are serialised in PKCS #8, ASN.1 DER form.

func NewPrivateKey added in v1.15.0

func NewPrivateKey() (*PrivateKey, error)

NewPrivateKey generates a new bifrost private key.

func (PrivateKey) MarshalBinary added in v1.18.0

func (p PrivateKey) MarshalBinary() ([]byte, error)

MarshalBinary converts a private key to PKCS #8, ASN.1 DER form.

func (PrivateKey) MarshalDynamoDBAttributeValue added in v1.15.3

func (p PrivateKey) MarshalDynamoDBAttributeValue() (types.AttributeValue, error)

MarshalDynamoDBAttributeValue marshals the private key to PKCS #8, ASN.1 DER form.

func (PrivateKey) MarshalJSON added in v1.15.3

func (p PrivateKey) MarshalJSON() ([]byte, error)

MarshalJSON marshals the key to a JSON string containing PEM encoded PKCS #8, ASN.1 DER form.

func (PrivateKey) MarshalText added in v1.18.0

func (p PrivateKey) MarshalText() ([]byte, error)

MarshalText marshals the key to a PEM encoded PKCS #8, ASN.1 DER form.

func (PrivateKey) PublicKey added in v1.15.3

func (p PrivateKey) PublicKey() *PublicKey

PublicKey returns the public key corresponding to p.

func (PrivateKey) UUID added in v1.15.3

func (p PrivateKey) UUID(ns uuid.UUID) uuid.UUID

UUID returns the bifrost identifier for p in the given namespace.

func (*PrivateKey) UnmarshalBinary added in v1.18.0

func (p *PrivateKey) UnmarshalBinary(data []byte) error

UnmarshalBinary parses an unencrypted private key in PKCS #8, ASN.1 DER form. Unmarshal also supports private keys in SEC.1, ASN.1 DER form for backward compatibility.

func (*PrivateKey) UnmarshalDynamoDBAttributeValue added in v1.15.3

func (p *PrivateKey) UnmarshalDynamoDBAttributeValue(av types.AttributeValue) error

UnmarshalDynamoDBAttributeValue unmarshals the private key from PKCS #8, ASN.1 DER form.

func (*PrivateKey) UnmarshalJSON added in v1.15.3

func (p *PrivateKey) UnmarshalJSON(data []byte) error

UnmarshalJSON unmarshals the private key from PEM encoded PKCS #8, ASN.1 DER form.

func (*PrivateKey) UnmarshalText added in v1.18.0

func (p *PrivateKey) UnmarshalText(text []byte) error

UnmarshalText unmarshals the private key from PEM encoded PKCS #8, ASN.1 DER form. Unmarshal also supports EC PRIVATE KEY PEM blocks for backward compatibility.

type PublicKey added in v1.15.0

type PublicKey struct {
	*ecdsa.PublicKey
}

PublicKey is a wrapper around an ECDSA public key. It implements the Marshaler and Unmarshaler interfaces for binary, text, JSON, and DynamoDB. Keys are serialsed in PKIX, ASN.1 DER form.

func (*PublicKey) Equal added in v1.16.0

func (p *PublicKey) Equal(other *PublicKey) bool

func (PublicKey) MarshalBinary added in v1.18.0

func (p PublicKey) MarshalBinary() ([]byte, error)

MarshalBinary marshals a public key to PKIX, ASN.1 DER form.

func (PublicKey) MarshalDynamoDBAttributeValue added in v1.15.0

func (p PublicKey) MarshalDynamoDBAttributeValue() (types.AttributeValue, error)

MarshalDynamoDBAttributeValue marshals the public key to PKIX, ASN.1 DER form.

func (PublicKey) MarshalJSON added in v1.15.0

func (p PublicKey) MarshalJSON() ([]byte, error)

MarshalJSON marshals the public key to a JSON string containing PEM encoded PKIX, ASN.1 DER form.

func (PublicKey) MarshalText added in v1.18.0

func (p PublicKey) MarshalText() ([]byte, error)

MarshalText marshals the public key to a PEM encoded PKIX Public Key in ASN.1 DER form.

func (PublicKey) UUID added in v1.15.0

func (p PublicKey) UUID(ns uuid.UUID) uuid.UUID

UUID returns a unique identifier derived from the namespace and the client's public key.

func (*PublicKey) UnmarshalBinary added in v1.18.0

func (p *PublicKey) UnmarshalBinary(data []byte) error

UnmarshalBinary unmarshals a public key from PKIX, ASN.1 DER form.

func (*PublicKey) UnmarshalDynamoDBAttributeValue added in v1.15.0

func (p *PublicKey) UnmarshalDynamoDBAttributeValue(av types.AttributeValue) error

UnmarshalDynamoDBAttributeValue unmarshals the public key from PKIX, ASN.1 DER form.

func (*PublicKey) UnmarshalJSON added in v1.15.0

func (p *PublicKey) UnmarshalJSON(data []byte) error

UnmarshalJSON unmarshals the public key as a JSON string containing PEM encoded PKIX Public Key, ASN.1 DER form.

func (*PublicKey) UnmarshalText added in v1.18.0

func (p *PublicKey) UnmarshalText(text []byte) error

UnmarshalText unmarshals the public key from a PEM encoded PKIX Public Key in ASN.1 DER form.

Directories

Path Synopsis
Package asgard provides middleware for use in HTTP API servers.
Package asgard provides middleware for use in HTTP API servers.
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.
cmd
bf
internal
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