docs/trustmanager/x509utils.go

252 lines
6.4 KiB
Go

package trustmanager
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"net/url"
"time"
"github.com/endophage/gotuf/data"
)
// GetCertFromURL tries to get a X509 certificate given a HTTPS URL
func GetCertFromURL(urlStr string) (*x509.Certificate, error) {
url, err := url.Parse(urlStr)
if err != nil {
return nil, err
}
// Check if we are adding via HTTPS
if url.Scheme != "https" {
return nil, errors.New("only HTTPS URLs allowed")
}
// Download the certificate and write to directory
resp, err := http.Get(url.String())
if err != nil {
return nil, err
}
// Copy the content to certBytes
defer resp.Body.Close()
certBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// Try to extract the first valid PEM certificate from the bytes
cert, err := loadCertFromPEM(certBytes)
if err != nil {
return nil, err
}
return cert, nil
}
// CertToPEM is an utility function returns a PEM encoded x509 Certificate
func CertToPEM(cert *x509.Certificate) []byte {
pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
return pemCert
}
// KeyToPEM returns a PEM encoded key from a crypto.PrivateKey
func KeyToPEM(key crypto.PrivateKey) ([]byte, error) {
rsaKey, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("only RSA keys are currently supported")
}
keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}), nil
}
// EncryptPrivateKey returns an encrypted PEM encoded key given a Private key
// and a passphrase
func EncryptPrivateKey(key crypto.PrivateKey, passphrase string) ([]byte, error) {
rsaKey, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("only RSA keys are currently supported")
}
keyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
password := []byte(passphrase)
cipherType := x509.PEMCipherAES256
blockType := "RSA PRIVATE KEY"
encryptedPEMBlock, err := x509.EncryptPEMBlock(rand.Reader,
blockType,
keyBytes,
password,
cipherType)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(encryptedPEMBlock), nil
}
// loadCertFromPEM returns the first certificate found in a bunch of bytes or error
// if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85.
func loadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
for len(pemBytes) > 0 {
var block *pem.Block
block, pemBytes = pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("no certificates found in PEM data")
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
continue
}
return cert, nil
}
return nil, errors.New("no certificates found in PEM data")
}
func FingerprintPEMCert(pemCert []byte) (string, error) {
cert, err := loadCertFromPEM(pemCert)
if err != nil {
return "", err
}
return FingerprintCert(cert), nil
}
// FingerprintCert returns a TUF compliant fingerprint for a X509 Certificate
func FingerprintCert(cert *x509.Certificate) string {
return string(fingerprintCert(cert))
}
func fingerprintCert(cert *x509.Certificate) CertID {
block := pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}
pemdata := pem.EncodeToMemory(&block)
// Create new TUF Key so we can compute the TUF-compliant CertID
tufKey := data.NewTUFKey("RSA", pemdata, nil)
return CertID(tufKey.ID())
}
// loadCertsFromDir receives a store AddCertFromFile for each certificate found
func loadCertsFromDir(s *X509FileStore) {
certFiles := s.fileStore.ListAll()
for _, c := range certFiles {
s.AddCertFromFile(c)
}
}
// LoadCertFromFile tries to adds a X509 certificate to the store given a filename
func LoadCertFromFile(filename string) (*x509.Certificate, error) {
// TODO(diogo): handle multiple certificates in one file.
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
var block *pem.Block
block, b = pem.Decode(b)
for ; block != nil; block, b = pem.Decode(b) {
if block.Type == "CERTIFICATE" {
cert, err := x509.ParseCertificate(block.Bytes)
if err == nil {
return cert, nil
}
}
}
return nil, errors.New("could not load certificate from file")
}
// LoadKeyFromFile returns a PrivateKey given a filename
func LoadKeyFromFile(filename string) (crypto.PrivateKey, error) {
pemBytes, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
key, err := ParsePEMPrivateKey(pemBytes)
if err != nil {
return nil, err
}
return key, nil
}
// ParsePEMPrivateKey returns a private key from a PEM encoded private key. It
// only supports RSA (PKCS#1).
func ParsePEMPrivateKey(pemBytes []byte) (crypto.PrivateKey, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("no valid key found")
}
switch block.Type {
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(block.Bytes)
default:
return nil, fmt.Errorf("unsupported key type %q", block.Type)
}
}
// ParsePEMEncryptedPrivateKey returns a private key from a PEM encrypted private key. It
// only supports RSA (PKCS#1).
func ParsePEMEncryptedPrivateKey(pemBytes []byte, passphrase string) (crypto.PrivateKey, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("no valid private key found")
}
switch block.Type {
case "RSA PRIVATE KEY":
if !x509.IsEncryptedPEMBlock(block) {
return nil, errors.New("private key is not encrypted")
}
decryptedPEMBlock, err := x509.DecryptPEMBlock(block, []byte(passphrase))
if err != nil {
return nil, errors.New("could not decrypt private key")
}
return x509.ParsePKCS1PrivateKey(decryptedPEMBlock)
default:
return nil, fmt.Errorf("unsupported key type %q", block.Type)
}
}
func NewCertificate(gun, organization string) *x509.Certificate {
notBefore := time.Now()
notAfter := notBefore.Add(time.Hour * 24 * 365 * 2)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
// TODO(diogo): Don't silently ignore this error
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
return &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{organization},
CommonName: gun,
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
BasicConstraintsValid: true,
}
}