assertion package

import "code.pfad.fr/gopenidclient/assertion"

package assertion authenticate OpenID Connect clients using private_key_jwt (private/public certificates instead of a shared secret), via OAuth 2.0 assertions specified in RFC 7521.

It offers better security since not secret must be transmitted (only the public certificate must be uploaded to the identity provider).

Example
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"log"
	"net/http"
	"time"

	"code.pfad.fr/gopenidclient"
	"code.pfad.fr/gopenidclient/assertion"
)

// only for example purpose (to prevent circular dependency)
type concreteProvider struct {
	gopenidclient.Provider
	Issuer          string
	ClientID        string
	Scopes          []string
	ClientAssertion gopenidclient.Assertion
}

func main() {
	// persist the privateKey somewhere (can be serialized using x509.MarshalPKCS1PrivateKey for instance)
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatal(err)
	}

	// create the (downgradable) certificate
	certificate := assertion.NewSesquiennial(privateKey, "Example Org")
	rs256 := assertion.RS256{
		GetThumbprint: certificate.Thumbprint,
		Validity:      time.Minute,
		CliendID:      "<OAUTH2_CLIENT_ID from identity provider>",
		Key:           privateKey,
	}

	// setup the provider using the assertion (instead of the client secret)
	// for instance using code.pfad.fr/gopenidclient/coreos.Provider
	var provider gopenidclient.Provider = (&concreteProvider{
		Issuer:          "issuer_url",
		ClientID:        rs256.CliendID,
		Scopes:          []string{"openid", "email", "profile"},
		ClientAssertion: rs256,
		// ClientSecret can be omitted
	})

	// the provider can be used like any other provider
	provider.SetRedirectURL("http://localhost:8080/auth/callback")

	// the public certificate can be exposed to ease the transmission to the identity provider
	http.HandleFunc("/auth/certificate.pem", func(w http.ResponseWriter, r *http.Request) {
		certificate.ServeDER(w, r)
	})

	// the certificate can be downgraded on ExchangeHandler.HandleCallback error
}

Index

Examples

type RS256

RS256 generates JWT assertions with RSA+SHA256. Suitable for Azure AD

func (RS256) Assert

Assert generates, serializes and signs a JWT

func (RS256) Type

Type returns urn:ietf:params:oauth:client-assertion-type:jwt-bearer

type Sesquiennial

Sesquiennial generates public certificates valid for 18 months (always starting January 1st). By default it serves the most recent certificate. A call to Sesquiennial.Downgrade will temporarly serve the certificate from previous year (which should still be valid thanks to the 18-months period). This gives 6 months to the administrator to refresh the public certificate at the identity provider.

func NewSesquiennial

NewSesquiennial creates a new Sesquiennial.

func (*Sesquiennial) DER

DER returns a ASN.1 marshalled X.509 v3 certificate, valid for 18 months, starting on {year}-01-01.

func (*Sesquiennial) Downgrade

Downgrade will serve the previous certificate if not downgraded yet (and return true). Calling it again will cancel the downgrade and return false. This should be called in case the Identity Provider denied a request with a "Client assertion failed signature validation" error.

func (*Sesquiennial) IsDowngraded

IsDowngraded indicates if the previous certificate is being used. I can be used to display a message to the administrator to refresh the public certificate at the identity provider.

func (*Sesquiennial) ServeDER

ServeDER will serve the current public certificate as DER. You can get the previous version, by adding a "?old" query parameter

func (*Sesquiennial) Thumbprint

Thumbprint returns the thumbprint of the currently served certificate. It is suitable to use for the GetThumbprint field of RS256.

type Thumbprint

Thumbprint contains the base64 encoded-hashes of a given certificate.

func NewThumbprint

NewThumbprint returns the base64 encoded-hashes of the provided DER certificate.

Source Files

rs256.go sesquiennial.go thumbprint.go