Go API Documentation

github.com/TykTechnologies/tyk/certs

No package summary is available.

Package

Files: 1. Third party imports: 1. Imports from organisation: 0. Tests: 0. Benchmarks: 0.

Constants

const (
	cacheDefaultTTL		= 300	// 5 minutes.
	cacheCleanInterval	= 600	// 10 minutes.
)
const (
	CertificatePrivate	CertificateType	= iota
	CertificatePublic
	CertificateAny
)

Vars

var (
	CertManagerLogPrefix = "cert_storage"
)
var (
	GenCertificate		= tykcrypto.GenCertificate
	GenServerCertificate	= tykcrypto.GenServerCertificate
	HexSHA256		= tykcrypto.HexSHA256
)

Types

CertificateBasics

This type doesn't have documentation.

Field name Field type Comment
ID

string

No comment on field.
IssuerCN

string

No comment on field.
SubjectCN

string

No comment on field.
DNSNames

[]string

No comment on field.
HasPrivateKey

bool

No comment on field.
NotBefore

time.Time

No comment on field.
NotAfter

time.Time

No comment on field.
IsCA

bool

No comment on field.
type CertificateBasics struct {
	ID		string		`json:"id"`
	IssuerCN	string		`json:"issuer_cn"`
	SubjectCN	string		`json:"subject_cn"`
	DNSNames	[]string	`json:"dns_names"`
	HasPrivateKey	bool		`json:"has_private"`
	NotBefore	time.Time	`json:"not_before"`
	NotAfter	time.Time	`json:"not_after"`
	IsCA		bool		`json:"is_ca"`
}

CertificateManager

This type doesn't have documentation.

Field name Field type Comment
type

any

No comment on field.
type CertificateManager interface {
	List(certIDs []string, mode CertificateType) (out []*tls.Certificate)
	ListPublicKeys(keyIDs []string) (out []string)
	ListRawPublicKey(keyID string) (out interface{})
	ListAllIds(prefix string) (out []string)
	GetRaw(certID string) (string, error)
	Add(certData []byte, orgID string) (string, error)
	Delete(certID string, orgID string)
	CertPool(certIDs []string) *x509.CertPool
	FlushCache()
}

CertificateMeta

This type doesn't have documentation.

Field name Field type Comment
ID

string

No comment on field.
Fingerprint

string

No comment on field.
HasPrivateKey

bool

No comment on field.
Issuer

pkix.Name

No comment on field.
Subject

pkix.Name

No comment on field.
NotBefore

time.Time

No comment on field.
NotAfter

time.Time

No comment on field.
DNSNames

[]string

No comment on field.
IsCA

bool

No comment on field.
type CertificateMeta struct {
	ID		string		`json:"id"`
	Fingerprint	string		`json:"fingerprint"`
	HasPrivateKey	bool		`json:"has_private"`
	Issuer		pkix.Name	`json:"issuer,omitempty"`
	Subject		pkix.Name	`json:"subject,omitempty"`
	NotBefore	time.Time	`json:"not_before,omitempty"`
	NotAfter	time.Time	`json:"not_after,omitempty"`
	DNSNames	[]string	`json:"dns_names,omitempty"`
	IsCA		bool		`json:"is_ca"`
}

CertificateType

This type doesn't have documentation.

Field name Field type Comment
type

int

No comment on field.
type CertificateType int

certificateManager

This type doesn't have documentation.

Field name Field type Comment
storage

storage.Handler

No comment on field.
logger

*logrus.Entry

No comment on field.
cache

cache.Repository

No comment on field.
secret

string

No comment on field.
migrateCertList

bool

No comment on field.
type certificateManager struct {
	storage		storage.Handler
	logger		*logrus.Entry
	cache		cache.Repository
	secret		string
	migrateCertList	bool
}

Functions

func ExtractCertificateBasics

func ExtractCertificateBasics(cert *tls.Certificate, certID string) *CertificateBasics {
	return &CertificateBasics{
		ID:		certID,
		IssuerCN:	cert.Leaf.Issuer.CommonName,
		SubjectCN:	cert.Leaf.Subject.CommonName,
		DNSNames:	cert.Leaf.DNSNames,
		HasPrivateKey:	!isPrivateKeyEmpty(cert),
		NotAfter:	cert.Leaf.NotAfter,
		NotBefore:	cert.Leaf.NotBefore,
		IsCA:		cert.Leaf.IsCA,
	}
}

Cognitive complexity: 1, Cyclomatic complexity: 1

func ExtractCertificateMeta

func ExtractCertificateMeta(cert *tls.Certificate, certID string) *CertificateMeta {
	return &CertificateMeta{
		ID:		certID,
		Fingerprint:	string(cert.Leaf.Extensions[0].Value),
		HasPrivateKey:	!isPrivateKeyEmpty(cert),
		Issuer:		cert.Leaf.Issuer,
		Subject:	cert.Leaf.Subject,
		NotBefore:	cert.Leaf.NotBefore,
		NotAfter:	cert.Leaf.NotAfter,
		DNSNames:	cert.Leaf.DNSNames,
		IsCA:		cert.Leaf.IsCA,
	}
}

Cognitive complexity: 1, Cyclomatic complexity: 1

func GetCertIDAndChainPEM

func GetCertIDAndChainPEM(certData []byte, secret string) (string, []byte, error) {
	var keyPEM, keyRaw []byte
	var publicKeyPem []byte
	var certBlocks [][]byte
	var certID string
	var certChainPEM []byte

	rest := certData

	for {
		var block *pem.Block

		block, rest = pem.Decode(rest)
		if block == nil {
			break
		}

		if strings.HasSuffix(block.Type, "PRIVATE KEY") {
			if len(keyRaw) > 0 {
				err := errors.New("Found multiple private keys")
				return certID, certChainPEM, err
			}

			keyRaw = block.Bytes
			keyPEM = pem.EncodeToMemory(block)
		} else if block.Type == "CERTIFICATE" {

			cert, err := x509.ParseCertificate(block.Bytes)
			if err != nil {
				return certID, certChainPEM, err
			}

			if cert.NotAfter.Before(time.Now()) {
				return certID, certChainPEM, errors.New("certificate is expired")
			}

			certBlocks = append(certBlocks, pem.EncodeToMemory(block))
		} else if block.Type == "PUBLIC KEY" {
			publicKeyPem = pem.EncodeToMemory(block)
		}
	}

	certChainPEM = bytes.Join(certBlocks, []byte("\n"))

	if len(certChainPEM) == 0 {
		if len(publicKeyPem) == 0 {
			err := errors.New("Failed to decode certificate. It should be PEM encoded.")
			return certID, certChainPEM, err
		} else {
			certChainPEM = publicKeyPem
		}
	} else if len(publicKeyPem) > 0 {
		err := errors.New("Public keys can't be combined with certificates")
		return certID, certChainPEM, err
	}

	// Found private key, check if it match the certificate
	if len(keyPEM) > 0 {
		cert, err := tls.X509KeyPair(certChainPEM, keyPEM)
		if err != nil {
			return certID, certChainPEM, err
		}

		// Encrypt private key and append it to the chain
		encryptedKeyPEMBlock, err := x509.EncryptPEMBlock(rand.Reader, "ENCRYPTED PRIVATE KEY", keyRaw, []byte(secret), x509.PEMCipherAES256)
		if err != nil {
			return certID, certChainPEM, err
		}

		certChainPEM = append(certChainPEM, []byte("\n")...)
		certChainPEM = append(certChainPEM, pem.EncodeToMemory(encryptedKeyPEMBlock)...)

		certID = tykcrypto.HexSHA256(cert.Certificate[0])
	} else if len(publicKeyPem) > 0 {
		publicKey, _ := pem.Decode(publicKeyPem)
		certID = tykcrypto.HexSHA256(publicKey.Bytes)
	} else {
		// Get first cert
		certRaw, _ := pem.Decode(certChainPEM)
		cert, err := x509.ParseCertificate(certRaw.Bytes)
		if err != nil {
			err := errors.New("Error while parsing certificate: " + err.Error())
			return certID, certChainPEM, err
		}

		certID = tykcrypto.HexSHA256(cert.Raw)
	}
	return certID, certChainPEM, nil
}

Cognitive complexity: 36, Cyclomatic complexity: 17

Uses: bytes.Join, errors.New, pem.Block, pem.Decode, pem.EncodeToMemory, rand.Reader, strings.HasSuffix, time.Now, tls.X509KeyPair, tykcrypto.HexSHA256, x509.EncryptPEMBlock, x509.PEMCipherAES256, x509.ParseCertificate.

func NewCertificateManager

func NewCertificateManager(storage storage.Handler, secret string, logger *logrus.Logger, migrateCertList bool) *certificateManager {
	if logger == nil {
		logger = logrus.New()
	}

	return &certificateManager{
		storage:		storage,
		logger:			logger.WithFields(logrus.Fields{"prefix": CertManagerLogPrefix}),
		cache:			cache.New(cacheDefaultTTL, cacheCleanInterval),
		secret:			secret,
		migrateCertList:	migrateCertList,
	}
}

Cognitive complexity: 4, Cyclomatic complexity: 2

Uses: cache.New, logrus.Fields, logrus.New.

func NewSlaveCertManager

func NewSlaveCertManager(localStorage, rpcStorage storage.Handler, secret string, logger *logrus.Logger, migrateCertList bool) *certificateManager {
	if logger == nil {
		logger = logrus.New()
	}
	log := logger.WithFields(logrus.Fields{"prefix": CertManagerLogPrefix})

	cm := &certificateManager{
		logger:			log,
		cache:			cache.New(cacheDefaultTTL, cacheCleanInterval),
		secret:			secret,
		migrateCertList:	migrateCertList,
	}

	callbackOnPullCertFromRPC := func(key, val string) error {
		// calculate the orgId from the keyId
		certID, _, _ := GetCertIDAndChainPEM([]byte(val), "")
		orgID := getOrgFromKeyID(key, certID)
		// save the cert in local redis
		_, err := cm.Add([]byte(val), orgID)
		return err
	}

	mdcbStorage := storage.NewMdcbStorage(localStorage, rpcStorage, log, callbackOnPullCertFromRPC)
	cm.storage = mdcbStorage
	return cm
}

Cognitive complexity: 5, Cyclomatic complexity: 2

Uses: cache.New, logrus.Fields, logrus.New, storage.NewMdcbStorage.

func ParsePEM

func ParsePEM(data []byte, secret string) ([]*pem.Block, error) {
	var pemBlocks []*pem.Block

	for {
		var block *pem.Block
		block, data = pem.Decode(data)

		if block == nil {
			break
		}

		if x509.IsEncryptedPEMBlock(block) {
			var err error
			block.Bytes, err = x509.DecryptPEMBlock(block, []byte(secret))
			block.Headers = nil
			block.Type = strings.Replace(block.Type, "ENCRYPTED ", "", 1)

			if err != nil {
				return nil, err
			}
		}

		pemBlocks = append(pemBlocks, block)
	}

	return pemBlocks, nil
}

Cognitive complexity: 8, Cyclomatic complexity: 5

Uses: pem.Block, pem.Decode, strings.Replace, x509.DecryptPEMBlock, x509.IsEncryptedPEMBlock.

func ParsePEMCertificate

func ParsePEMCertificate(data []byte, secret string) (*tls.Certificate, error) {
	var cert tls.Certificate

	blocks, err := ParsePEM(data, secret)
	if err != nil {
		return nil, err
	}

	var certID string

	for _, block := range blocks {
		if block.Type == "CERTIFICATE" {
			certID = tykcrypto.HexSHA256(block.Bytes)
			cert.Certificate = append(cert.Certificate, block.Bytes)
			continue
		}

		if strings.HasSuffix(block.Type, "PRIVATE KEY") {
			cert.PrivateKey, err = parsePrivateKey(block.Bytes)
			if err != nil {
				return nil, err
			}
			continue
		}

		if block.Type == "PUBLIC KEY" {
			// Create a dummny cert just for listing purpose
			cert.Certificate = append(cert.Certificate, block.Bytes)
			cert.Leaf = tykcrypto.PrefixPublicKeyCommonName(block.Bytes)
		}
	}

	if len(cert.Certificate) == 0 {
		return nil, errors.New("Can't find CERTIFICATE block")
	}

	if cert.Leaf == nil {
		cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])

		if err != nil {
			return nil, err
		}
	}

	// Cache certificate fingerprint
	cert.Leaf.Extensions = append([]pkix.Extension{{
		Value: []byte(certID),
	}}, cert.Leaf.Extensions...)

	return &cert, nil
}

Cognitive complexity: 21, Cyclomatic complexity: 10

Uses: errors.New, pkix.Extension, strings.HasSuffix, tls.Certificate, tykcrypto.HexSHA256, tykcrypto.PrefixPublicKeyCommonName, x509.ParseCertificate.

func (*certificateManager) Add

func (c *certificateManager) Add(certData []byte, orgID string) (string, error) {

	certID, certChainPEM, err := GetCertIDAndChainPEM(certData, c.secret)
	if err != nil {
		c.logger.Error(err)
		return "", err
	}
	certID = orgID + certID

	if found, err := c.storage.Exists("raw-" + certID); err == nil && found {
		return "", errors.New("Certificate with " + certID + " id already exists")
	}

	if err := c.storage.SetKey("raw-"+certID, string(certChainPEM), 0); err != nil {
		c.logger.Error(err)
		return "", err
	}

	if orgID != "" {
		c.storage.AppendToSet(orgID+"-index", "raw-"+certID)
	}

	return certID, nil
}

Cognitive complexity: 8, Cyclomatic complexity: 6

Uses: errors.New.

func (*certificateManager) CertPool

func (c *certificateManager) CertPool(certIDs []string) *x509.CertPool {
	pool := x509.NewCertPool()

	for _, cert := range c.List(certIDs, CertificatePublic) {
		if cert != nil && !tykcrypto.IsPublicKey(cert) {
			pool.AddCert(cert.Leaf)
		}
	}

	return pool
}

Cognitive complexity: 5, Cyclomatic complexity: 4

Uses: tykcrypto.IsPublicKey, x509.NewCertPool.

func (*certificateManager) Delete

func (c *certificateManager) Delete(certID string, orgID string) {

	if orgID != "" {
		c.storage.RemoveFromList(orgID+"-index", "raw-"+certID)
	}

	c.storage.DeleteKey("raw-" + certID)
	c.cache.Delete(certID)
}

Cognitive complexity: 2, Cyclomatic complexity: 2

func (*certificateManager) FlushCache

func (c *certificateManager) FlushCache() {
	c.cache.Flush()
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*certificateManager) GetRaw

func (c *certificateManager) GetRaw(certID string) (string, error) {
	return c.storage.GetKey("raw-" + certID)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (*certificateManager) List

func (c *certificateManager) List(certIDs []string, mode CertificateType) (out []*tls.Certificate) {
	var cert *tls.Certificate
	var rawCert []byte

	for _, id := range certIDs {
		if cert, found := c.cache.Get(id); found {
			if isCertCanBeListed(cert.(*tls.Certificate), mode) {
				out = append(out, cert.(*tls.Certificate))
			}
			continue
		}

		val, err := c.storage.GetKey("raw-" + id)
		// fallback to file
		if err != nil {
			// Try read from file
			rawCert, err = ioutil.ReadFile(id)
			if err != nil {
				c.logger.Warn("Can't retrieve certificate:", id, err)
				out = append(out, nil)
				continue
			}
		} else {
			rawCert = []byte(val)
		}

		cert, err = ParsePEMCertificate(rawCert, c.secret)
		if err != nil {
			c.logger.Error("Error while parsing certificate: ", id, " ", err)
			c.logger.Debug("Failed certificate: ", string(rawCert))
			out = append(out, nil)
			continue
		}

		c.cache.Set(id, cert, cache.DefaultExpiration)

		if isCertCanBeListed(cert, mode) {
			out = append(out, cert)
		}
	}

	return out
}

Cognitive complexity: 17, Cyclomatic complexity: 8

Uses: cache.DefaultExpiration, ioutil.ReadFile, tls.Certificate.

func (*certificateManager) ListAllIds

func (c *certificateManager) ListAllIds(prefix string) (out []string) {
	indexKey := prefix + "-index"
	exists, _ := c.storage.Exists(indexKey)
	if !c.migrateCertList || (exists && prefix != "") {
		keys, _ := c.storage.GetListRange(indexKey, 0, -1)
		for _, key := range keys {
			out = append(out, strings.TrimPrefix(key, "raw-"))
		}
	} else {
		// If list is not exists, but migrated record exists, it means it just empty

		if _, err := c.storage.GetKey(indexKey + "-migrated"); err == nil {
			return out
		}

		keys := c.storage.GetKeys("raw-" + prefix + "*")

		for _, key := range keys {
			if prefix != "" {
				c.storage.AppendToSet(indexKey, key)
			}
			out = append(out, strings.TrimPrefix(key, "raw-"))
		}
	}
	c.storage.SetKey(indexKey+"-migrated", "1", 0)

	return out
}

Cognitive complexity: 14, Cyclomatic complexity: 8

Uses: strings.TrimPrefix.

func (*certificateManager) ListPublicKeys

Returns list of fingerprints

func (c *certificateManager) ListPublicKeys(keyIDs []string) (out []string) {
	var rawKey []byte
	var err error

	for _, id := range keyIDs {
		if fingerprint, found := c.cache.Get("pub-" + id); found {
			out = append(out, fingerprint.(string))
			continue
		}

		if isSHA256(id) {
			var val string
			val, err := c.storage.GetKey("raw-" + id)
			if err != nil {
				c.logger.Warn("Can't retrieve public key from Redis:", id, err)
				out = append(out, "")
				continue
			}
			rawKey = []byte(val)
		} else {
			rawKey, err = ioutil.ReadFile(id)
			if err != nil {
				c.logger.Error("Error while reading public key from file:", id, err)
				out = append(out, "")
				continue
			}
		}

		block, _ := pem.Decode(rawKey)
		if block == nil {
			c.logger.Error("Can't parse public key:", id)
			out = append(out, "")
			continue
		}

		fingerprint := tykcrypto.HexSHA256(block.Bytes)
		c.cache.Set("pub-"+id, fingerprint, cache.DefaultExpiration)
		out = append(out, fingerprint)
	}

	return out
}

Cognitive complexity: 15, Cyclomatic complexity: 7

Uses: cache.DefaultExpiration, ioutil.ReadFile, pem.Decode, tykcrypto.HexSHA256.

func (*certificateManager) ListRawPublicKey

Returns list of fingerprints

func (c *certificateManager) ListRawPublicKey(keyID string) (out interface{}) {
	var rawKey []byte
	var err error

	if isSHA256(keyID) {
		var val string
		val, err := c.storage.GetKey("raw-" + keyID)
		if err != nil {
			c.logger.Warn("Can't retrieve public key from Redis:", keyID, err)
			return nil
		}
		rawKey = []byte(val)
	} else {
		rawKey, err = ioutil.ReadFile(keyID)
		if err != nil {
			c.logger.Error("Error while reading public key from file:", keyID, err)
			return nil
		}
	}

	block, _ := pem.Decode(rawKey)
	if block == nil {
		c.logger.Error("Can't parse public key:", keyID)
		return nil
	}

	out, err = x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		c.logger.Error("Error while parsing public key:", keyID, err)
		return nil
	}

	return out
}

Cognitive complexity: 13, Cyclomatic complexity: 6

Uses: ioutil.ReadFile, pem.Decode, x509.ParsePKIXPublicKey.

Private functions

func getOrgFromKeyID

getOrgFromKeyID (key,certID string) string
References: strings.ReplaceAll.

func isCertCanBeListed

isCertCanBeListed (cert *tls.Certificate, mode CertificateType) bool

func isPrivateKeyEmpty

isPrivateKeyEmpty (cert *tls.Certificate) bool

func isSHA256

isSHA256 (value string) bool
References: hex.DecodeString.

func parsePrivateKey

Extracted from: https://golang.org/src/crypto/tls/tls.go

Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys. OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.

parsePrivateKey (der []byte) (crypto.PrivateKey, error)
References: ecdsa.PrivateKey, errors.New, rsa.PrivateKey, x509.ParseECPrivateKey, x509.ParsePKCS1PrivateKey, x509.ParsePKCS8PrivateKey.

func publicKey

publicKey (priv interface{}) interface{}
References: ecdsa.PrivateKey, rsa.PrivateKey.

func flushStorage

flushStorage ()


Tests

Files: 1. Third party imports: 1. Imports from organisation: 0. Tests: 3. Benchmarks: 0.

Test functions

TestAddCertificate

References: assert.Equal, assert.Error, assert.NoError, pem.Block, pem.Decode, pem.EncodeToMemory, rand.Reader, rsa.GenerateKey, testing.T, tykcrypto.HexSHA256, x509.MarshalPKIXPublicKey.

TestCertificateStorage

References: assert.Equal, filepath.Join, ioutil.TempDir, ioutil.WriteFile, os.RemoveAll, pem.Block, pem.EncodeToMemory, rand.Reader, rsa.GenerateKey, testing.T, x509.MarshalPKIXPublicKey.

TestStorageIndex

References: storage.DummyStorage.