259 lines
5.1 KiB
Go
259 lines
5.1 KiB
Go
// Copyright (C) 2018 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package identity
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/pem"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"storj.io/storj/pkg/peertls"
|
|
"storj.io/storj/pkg/storj"
|
|
"storj.io/storj/pkg/utils"
|
|
)
|
|
|
|
// TLSFilesStatus is the status of keys
|
|
type TLSFilesStatus int
|
|
|
|
// Four possible outcomes for four files
|
|
const (
|
|
NoCertNoKey = TLSFilesStatus(iota)
|
|
CertNoKey
|
|
NoCertKey
|
|
CertKey
|
|
)
|
|
|
|
var (
|
|
// ErrChainLength is used when the length of a cert chain isn't what was expected
|
|
ErrChainLength = errs.Class("cert chain length error")
|
|
// ErrZeroBytes is returned for zero slice
|
|
ErrZeroBytes = errs.New("byte slice was unexpectedly empty")
|
|
)
|
|
|
|
type encodedChain struct {
|
|
chain [][]byte
|
|
extensions [][][]byte
|
|
}
|
|
|
|
// DecodeAndParseChainPEM parses a PEM chain
|
|
func DecodeAndParseChainPEM(PEMBytes []byte) ([]*x509.Certificate, error) {
|
|
var (
|
|
encChain encodedChain
|
|
blockErrs utils.ErrorGroup
|
|
)
|
|
for {
|
|
var pemBlock *pem.Block
|
|
pemBlock, PEMBytes = pem.Decode(PEMBytes)
|
|
if pemBlock == nil {
|
|
break
|
|
}
|
|
switch pemBlock.Type {
|
|
case peertls.BlockTypeCertificate:
|
|
encChain.AddCert(pemBlock.Bytes)
|
|
case peertls.BlockTypeExtension:
|
|
if err := encChain.AddExtension(pemBlock.Bytes); err != nil {
|
|
blockErrs.Add(err)
|
|
}
|
|
}
|
|
}
|
|
if err := blockErrs.Finish(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return encChain.Parse()
|
|
}
|
|
|
|
func decodePEM(PEMBytes []byte) ([][]byte, error) {
|
|
var DERBytes [][]byte
|
|
|
|
for {
|
|
var DERBlock *pem.Block
|
|
|
|
DERBlock, PEMBytes = pem.Decode(PEMBytes)
|
|
if DERBlock == nil {
|
|
break
|
|
}
|
|
|
|
DERBytes = append(DERBytes, DERBlock.Bytes)
|
|
}
|
|
|
|
if len(DERBytes) == 0 || len(DERBytes[0]) == 0 {
|
|
return nil, ErrZeroBytes
|
|
}
|
|
|
|
return DERBytes, nil
|
|
}
|
|
|
|
func newCAWorker(ctx context.Context, difficulty uint16, parentCert *x509.Certificate, parentKey crypto.PrivateKey, caC chan FullCertificateAuthority, eC chan error) {
|
|
var (
|
|
k crypto.PrivateKey
|
|
i storj.NodeID
|
|
err error
|
|
)
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
k, err = peertls.NewKey()
|
|
if err != nil {
|
|
eC <- err
|
|
return
|
|
}
|
|
switch kE := k.(type) {
|
|
case *ecdsa.PrivateKey:
|
|
i, err = NodeIDFromKey(&kE.PublicKey)
|
|
if err != nil {
|
|
eC <- err
|
|
return
|
|
}
|
|
default:
|
|
eC <- peertls.ErrUnsupportedKey.New("%T", k)
|
|
return
|
|
}
|
|
}
|
|
|
|
d, err := i.Difficulty()
|
|
if err != nil {
|
|
eC <- err
|
|
continue
|
|
}
|
|
if d >= difficulty {
|
|
break
|
|
}
|
|
}
|
|
|
|
ct, err := peertls.CATemplate()
|
|
if err != nil {
|
|
eC <- err
|
|
return
|
|
}
|
|
|
|
c, err := peertls.NewCert(k, parentKey, ct, parentCert)
|
|
if err != nil {
|
|
eC <- err
|
|
return
|
|
}
|
|
|
|
ca := FullCertificateAuthority{
|
|
Cert: c,
|
|
Key: k,
|
|
ID: i,
|
|
}
|
|
if parentCert != nil {
|
|
ca.RestChain = []*x509.Certificate{parentCert}
|
|
}
|
|
caC <- ca
|
|
}
|
|
|
|
// writeChainData writes data to path ensuring permissions are appropriate for a cert
|
|
func writeChainData(path string, data []byte) error {
|
|
err := writeFile(path, 0744, 0644, data)
|
|
if err != nil {
|
|
return errs.New("unable to write certificate to \"%s\": %v", path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// writeKeyData writes data to path ensuring permissions are appropriate for a cert
|
|
func writeKeyData(path string, data []byte) error {
|
|
err := writeFile(path, 0700, 0600, data)
|
|
if err != nil {
|
|
return errs.New("unable to write key to \"%s\": %v", path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// writeFile writes to path, creating directories and files with the necessary permissions
|
|
func writeFile(path string, dirmode, filemode os.FileMode, data []byte) error {
|
|
if err := os.MkdirAll(filepath.Dir(path), dirmode); err != nil {
|
|
return errs.Wrap(err)
|
|
}
|
|
|
|
if err := ioutil.WriteFile(path, data, filemode); err != nil {
|
|
return errs.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func statTLSFiles(certPath, keyPath string) TLSFilesStatus {
|
|
_, err := os.Stat(certPath)
|
|
hasCert := os.IsExist(err)
|
|
|
|
_, err = os.Stat(keyPath)
|
|
hasKey := os.IsExist(err)
|
|
|
|
if hasCert && hasKey {
|
|
return CertKey
|
|
} else if hasCert {
|
|
return CertNoKey
|
|
} else if hasKey {
|
|
return NoCertKey
|
|
}
|
|
|
|
return NoCertNoKey
|
|
}
|
|
|
|
func (t TLSFilesStatus) String() string {
|
|
switch t {
|
|
case CertKey:
|
|
return "certificate and key"
|
|
case CertNoKey:
|
|
return "certificate"
|
|
case NoCertKey:
|
|
return "key"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (e *encodedChain) AddCert(b []byte) {
|
|
e.chain = append(e.chain, b)
|
|
e.extensions = append(e.extensions, [][]byte{})
|
|
}
|
|
|
|
func (e *encodedChain) AddExtension(b []byte) error {
|
|
chainLen := len(e.chain)
|
|
if chainLen < 1 {
|
|
return ErrChainLength.New("expected: >= 1; actual: %d", chainLen)
|
|
}
|
|
|
|
i := chainLen - 1
|
|
e.extensions[i] = append(e.extensions[i], b)
|
|
return nil
|
|
}
|
|
|
|
func (e *encodedChain) Parse() ([]*x509.Certificate, error) {
|
|
chain, err := ParseCertChain(e.chain)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var extErrs utils.ErrorGroup
|
|
for i, cert := range chain {
|
|
for _, ee := range e.extensions[i] {
|
|
ext := pkix.Extension{}
|
|
_, err := asn1.Unmarshal(ee, &ext)
|
|
if err != nil {
|
|
extErrs.Add(err)
|
|
}
|
|
cert.ExtraExtensions = append(cert.ExtraExtensions, ext)
|
|
}
|
|
}
|
|
if err := extErrs.Finish(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return chain, nil
|
|
}
|