See <https://godoc.org/github.com/big-squid/go-keypairs>
> Just because you _don't_ know someone doesn't make them smart.
# Philosophy
Don't get the two mixed up!
Go's standard library is great.
(furthermore, [just because you _do_ know someone doesn't make them _not_ smart](https://www.humancondition.com/asid-prophets-without-honour-in-their-own-home/))
Go has _excellent_ crytography support and provides wonderful
primitives for dealing with them.
Although I would **not** want to invent my own cryptographic algorithm,
I've read enough source code to know that, for standards I know well,
I feel much more confident in the security, extensibility, and documentation
of tooling that I've write myself.
I prefer to stay as close to Go's `crypto` package as possible,
just adding a light touch for JWT support and type safety.
# Type Safety
Go has _excellent_ crytography support and provides wonderful
primitives for dealing with them. Its Achilles' heel is they're **not typesafe**!
As of Go 1.11.5 `crypto.PublicKey` and `crypto.PrivateKey` are "marker interfaces"
or, in other words, empty interfaces that only serve to document intent without
actually providing a constraint to the type system.
`crypto.PublicKey` is a "marker interface", meaning that it is **not typesafe**!
// EInvalidJWKURL means that the url did not provide JWKs
varEInvalidJWKURL=errors.New("url does not lead to valid JWKs")
// KeyCache is an in-memory key cache
varKeyCache=map[string]CachableKey{}
// KeyCacheMux is used to guard the in-memory cache
varKeyCacheMux=sync.Mutex{}
// ErrInsecureDomain means that plain http was used where https was expected
varErrInsecureDomain=errors.New("Whitelists should only allow secure URLs (i.e. https://). To allow unsecured private networking (i.e. Docker) pass PrivateWhitelist as a list of private URLs")
// TODO Cacheable key (shouldn't this be private)?
// CachableKey represents
typeCachableKeystruct{
Keykeypairs.PublicKey
Expirytime.Time
@ -55,50 +67,64 @@ type ID interface {
}
*/
// StaleTime defines when public keys should be renewed (15 minutes by default)
varStaleTime=15*time.Minute
// DefaultKeyDuration defines how long a key should be considered fresh (48 hours by default)
varDefaultKeyDuration=48*time.Hour
// MinimumKeyDuration defines the minimum time that a key will be cached (1 hour by default)
varMinimumKeyDuration=time.Hour
// MaximumKeyDuration defines the maximum time that a key will be cached (72 hours by default)
varMaximumKeyDuration=72*time.Hour
typepublicKeysMapmap[string]keypairs.PublicKey
// PublicKeysMap is a newtype for a map of keypairs.PublicKey
typePublicKeysMapmap[string]keypairs.PublicKey
// OIDCJWKs fetches baseURL + ".well-known/openid-configuration" and then fetches and returns the Public Keys.
funcOIDCJWKs(baseURLstring)(PublicKeysMap,error){
maps,keys,err:=uncached.OIDCJWKs(baseURL)
// FetchOIDCPublicKeys fetches baseURL + ".well-known/openid-configuration" and then returns FetchPublicKeys(jwks_uri).
// ErrInvalidPrivateKey means that the key is not a valid Private Key
varErrInvalidPrivateKey=errors.New("PrivateKey must be of type *rsa.PrivateKey or *ecdsa.PrivateKey")
// ErrInvalidPublicKey means that the key is not a valid Public Key
varErrInvalidPublicKey=errors.New("PublicKey must be of type *rsa.PublicKey or *ecdsa.PublicKey")
// ErrParsePublicKey means that the bytes cannot be parsed in any known format
varErrParsePublicKey=errors.New("PublicKey bytes could not be parsed as PEM or DER (PKIX/SPKI, PKCS1, or X509 Certificate) or JWK")
// ErrParsePrivateKey means that the bytes cannot be parsed in any known format
varErrParsePrivateKey=errors.New("PrivateKey bytes could not be parsed as PEM or DER (PKCS8, SEC1, or PKCS1) or JWK")
// ErrParseJWK means that the JWK is valid JSON but not a valid JWK
varErrParseJWK=errors.New("JWK is missing required base64-encoded JSON fields")
// ErrInvalidKeyType means that the key is not an acceptable type
varErrInvalidKeyType=errors.New("The JWK's 'kty' must be either 'RSA' or 'EC'")
// ErrInvalidCurve means that a non-standard curve was used
varErrInvalidCurve=errors.New("The JWK's 'crv' must be either of the NIST standards 'P-256' or 'P-384'")
// ErrUnexpectedPublicKey means that a Private Key was expected
varErrUnexpectedPublicKey=errors.New("PrivateKey was given where PublicKey was expected")
// ErrUnexpectedPrivateKey means that a Public Key was expected
varErrUnexpectedPrivateKey=errors.New("PublicKey was given where PrivateKey was expected")
// ErrDevSwapPrivatePublic means that the developer compiled bad code that swapped public and private keys
constErrDevSwapPrivatePublic="[Developer Error] You passed either crypto.PrivateKey or crypto.PublicKey where the other was expected."
// ErrDevBadKeyType means that the developer compiled bad code that passes the wrong type
constErrDevBadKeyType="[Developer Error] crypto.PublicKey and crypto.PrivateKey are somewhat deceptive. They're actually empty interfaces that accept any object, even non-crypto objects. You passed an object of type '%T' by mistake."
// PrivateKey is a zero-cost typesafe substitue for crypto.PrivateKey
@ -63,34 +82,52 @@ type RSAPublicKey struct {
Expirytime.Time
}
// Thumbprint returns a JWK thumbprint. See https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
func(p*ECPublicKey)Thumbprint()string{
returnThumbprintUntypedPublicKey(p.PublicKey)
}
// KeyID returns the JWK `kid`, which will be the Thumbprint for keys generated with this library
func(p*ECPublicKey)KeyID()string{
returnp.KID
}
// Key returns the PublicKey
func(p*ECPublicKey)Key()crypto.PublicKey{
returnp.PublicKey
}
// ExpireAt sets the time at which this Public Key should be considered invalid
func(p*ECPublicKey)ExpireAt(ttime.Time){
p.Expiry=t
}
// ExpiresAt gets the time at which this Public Key should be considered invalid
func(p*ECPublicKey)ExpiresAt()time.Time{
returnp.Expiry
}
// Thumbprint returns a JWK thumbprint. See https://stackoverflow.com/questions/42588786/how-to-fingerprint-a-jwk
func(p*RSAPublicKey)Thumbprint()string{
returnThumbprintUntypedPublicKey(p.PublicKey)
}
// KeyID returns the JWK `kid`, which will be the Thumbprint for keys generated with this library
func(p*RSAPublicKey)KeyID()string{
returnp.KID
}
// Key returns the PublicKey
func(p*RSAPublicKey)Key()crypto.PublicKey{
returnp.PublicKey
}
// ExpireAt sets the time at which this Public Key should be considered invalid
func(p*RSAPublicKey)ExpireAt(ttime.Time){
p.Expiry=t
}
// ExpiresAt gets the time at which this Public Key should be considered invalid