storj/pkg/peertls/peertls.go

160 lines
4.7 KiB
Go
Raw Normal View History

Transport security (#63) * wip initial transport security * wip: transport security (add tests / refactor) * wip tests * refactoring - still wip * refactor, improve tests * wip tls testing * fix typo * wip testing * wip testing * wip * tls_test passing * code-style improvemente / refactor; service and tls tests passing! * code-style auto-format * add TestNewServer_LoadTLS * refactor; test improvements * refactor * add client cert * port changes * Merge remote-tracking branch 'upstream/master' * Merge remote-tracking branch 'upstream/master' * Merge remote-tracking branch 'upstream/master' * files created * Merge remote-tracking branch 'upstream/master' into coyle/kad-tests * wip * add separate `Process` tests for bolt and redis-backed overlay * more testing * fix gitignore * fix linter error * goimports goimports GOIMPORTS GoImPortS!!!! * wip * fix port madness * forgot to add * add `mux` as handler and shorten context timeouts * gofreakingimports * fix comments * refactor test & add logger/monkit registry * debugging travis * add comment * Set redisAddress to empty string for bolt-test * Merge remote-tracking branch 'upstream/master' into coyle/kad-tests * Merge branch 'tls' into tls-upstream * tls: add client cert refactor refactor; test improvements add TestNewServer_LoadTLS code-style auto-format code-style improvemente / refactor; service and tls tests passing! tls_test passing wip wip testing wip testing fix typo wip tls testing refactor, improve tests refactoring - still wip wip tests wip: transport security (add tests / refactor) wip initial transport security * fixing linter things * wip * remove bkad dependencie from tests * wip * wip * wip * wip * wip * updated coyle/kademlia * wip * cleanup * ports * overlay upgraded * linter fixes * piecestore kademlia newID * Merge branch 'master' into tls-upstream * master: Add error to the return values of Ranger.Range method (#90) udp-forwarding: demo week work! (#84) * Merge branch 'kad-tests' into tls-upstream * kad-tests: piecestore kademlia newID linter fixes overlay upgraded ports cleanup wip updated coyle/kademlia wip wip wip wip wip remove bkad dependencie from tests wip wip files created port changes * wip * finish merging service tests * add test for different client/server certs * wip * Merge branch 'master' into tls-upstream * master: Add context to Ranger.Range method (#99) Coyle/kad client (#91) * wip * wip; refactoring/cleanup * wip * Merge branch 'master' into tls * master: Bolt backed overlay cache (#94) internal/test: switch errors to error classes (#96) * wip - test passing * cleanup * remove port.go * cleanup * Merge branch 'master' into tls * master: hardcode version (#111) Coyle/docker fix (#109) pkg/kademlia tests and restructuring (#97) Use continue instead of return in table tests (#106) prepend storjlabs to docker tag (#108) Automatically build, tag and push docker images on merge to master (#103) * more belated merging * more belated merging * more belated merging * add copyrights * cleanup * goimports * refactoring * wip * wip * implement `TLSFileOptions#loadTLS`, refactoring: `peertls.TestNewClient_LoadTLS` is the failing holdout; Still trying to figure out why I'm getting ECDSA verification is failing. * not sure if actually working: Tests are now passing (no more "ECDSA verification failed"); however, `len(*tls.Certificates.Certificate) == 1` which I don't think should be the case if the root and leaf are being created correctly. * Experimenting/bugfixing?: I think leaf certs should be properly signed by the parent now but not entirely certain. It's also unclear to me why in `VerifyPeerCertificate`, `len(rawCerts) == 1` when the certs should contain both the root and leaf afaik. * Properly write/read certificate chain (root/leaf): I think I'm now properly reading and writing the root and leaf certificate chain such that they're both being received by `VerifyPeerCertificate`. The next step is to parse the certificates with `x509.ParseCertificate` (or similar) and verify that the public keys and signatures match. * Add tls certificate chain signature veification (spike): + `VerifyPeerCertificate` verifies signatures of certificates using the key of it's parent if there is one; otherwise, it verifies the certificate is self-signed + TODO: refactor + TODO: test * refactoring `VerifyPeerCertificate` * cleanup * refactor * Merge branch 'master' into tls * master: Remove some structural folders we don't seem to be using. (#125) license code with agplv3 (#126) Update .clabot (#124) added team memebers (#123) clabot file added (#121) ECClient (#110) docker image issue fixed (#118) Piecestore Farmer CLI (#92) Define Path type (#101) adds netstate pagination (#95) Transport Client (#89) Implement psclient interface (#107) pkg/process: start replacing pkg/process with cobra helpers (#98) protos/netstate: remove stuff we're not using (#100) adding coveralls / code coverage (#112) * responding to review feedback / cleanup / add copywrite headers * suggestions * realitive * Merge pull request #1 from coyle/coyle/tls suggestions * remove unnecessary `_`s * Merge branch 'tls' of github.com:bryanchriswhite/storj into tls * 'tls' of github.com:bryanchriswhite/storj: realitive suggestions * Responding to review feedback: + refactor `VerifyPeerCertificate` * remove tls expiration * remove "hosts" and "clien option" from tls options * goimports * linter fixes
2018-07-09 18:43:13 +01:00
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package peertls
import (
"crypto"
"crypto/ecdsa"
"crypto/tls"
"crypto/x509"
"encoding/asn1"
"fmt"
"math/big"
"os"
"github.com/zeebo/errs"
)
var (
// ErrNotExist is used when a file or directory doesn't exist
ErrNotExist = errs.Class("file or directory not found error")
// ErrNoOverwrite is used when `create == true && overwrite == false`
// and tls certs/keys already exist at the specified paths
ErrNoOverwrite = errs.Class("tls overwrite disabled error")
// ErrGenerate is used when an error occured during cert/key generation
ErrGenerate = errs.Class("tls generation error")
// ErrTLSOptions is used inconsistently and should probably just be removed
ErrTLSOptions = errs.Class("tls options error")
// ErrTLSTemplate is used when an error occurs during tls template generation
ErrTLSTemplate = errs.Class("tls template error")
// ErrVerifyPeerCert is used when an error occurs during `VerifyPeerCertificate`
ErrVerifyPeerCert = errs.Class("tls peer certificate verification error")
// ErrVerifySignature is used when a cert-chain signature verificaion error occurs
ErrVerifySignature = errs.Class("tls certificate signature verification error")
)
func IsNotExist(err error) bool {
return os.IsNotExist(err) || ErrNotExist.Has(err)
}
// TLSFileOptions stores information about a tls certificate and key, and options for use with tls helper functions/methods
type TLSFileOptions struct {
RootCertRelPath string
RootCertAbsPath string
LeafCertRelPath string
LeafCertAbsPath string
// NB: Populate absolute paths from relative paths,
// with respect to pwd via `.EnsureAbsPaths`
RootKeyRelPath string
RootKeyAbsPath string
LeafKeyRelPath string
LeafKeyAbsPath string
LeafCertificate *tls.Certificate
// Create if cert or key nonexistent
Create bool
// Overwrite if `create` is true and cert and/or key exist
Overwrite bool
}
type ecdsaSignature struct {
R, S *big.Int
}
func VerifyPeerCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
// Verify parent ID/sig
// Verify leaf ID/sig
// Verify leaf signed by parent
// TODO(bryanchriswhite): see "S/Kademlia extensions - Secure nodeId generation"
// (https://www.pivotaltracker.com/story/show/158238535)
for i, cert := range rawCerts {
isValid := false
if i < len(rawCerts)-1 {
parentCert, err := x509.ParseCertificate(rawCerts[i+1])
if err != nil {
return ErrVerifyPeerCert.New("unable to parse certificate", err)
}
childCert, err := x509.ParseCertificate(cert)
if err != nil {
return ErrVerifyPeerCert.New("unable to parse certificate", err)
}
isValid, err = verifyCertSignature(parentCert, childCert)
if err != nil {
return ErrVerifyPeerCert.Wrap(err)
}
} else {
rootCert, err := x509.ParseCertificate(cert)
if err != nil {
return ErrVerifyPeerCert.New("unable to parse certificate", err)
}
isValid, err = verifyCertSignature(rootCert, rootCert)
if err != nil {
return ErrVerifyPeerCert.Wrap(err)
}
}
if !isValid {
return ErrVerifyPeerCert.New("certificate chain signature verification failed")
}
}
return nil
}
// NewTLSFileOptions initializes a new `TLSFileOption` struct given the arguments
func NewTLSFileOptions(baseCertPath, baseKeyPath string, create, overwrite bool) (_ *TLSFileOptions, _ error) {
t := &TLSFileOptions{
RootCertRelPath: fmt.Sprintf("%s.root.cert", baseCertPath),
RootKeyRelPath: fmt.Sprintf("%s.root.key", baseKeyPath),
LeafCertRelPath: fmt.Sprintf("%s.leaf.cert", baseCertPath),
LeafKeyRelPath: fmt.Sprintf("%s.leaf.key", baseKeyPath),
Overwrite: overwrite,
Create: create,
}
if err := t.EnsureExists(); err != nil {
return nil, err
}
return t, nil
}
func verifyCertSignature(parentCert, childCert *x509.Certificate) (bool, error) {
pubkey := parentCert.PublicKey.(*ecdsa.PublicKey)
signature := new(ecdsaSignature)
if _, err := asn1.Unmarshal(childCert.Signature, signature); err != nil {
return false, ErrVerifySignature.New("unable to unmarshal ecdsa signature", err)
}
h := crypto.SHA256.New()
h.Write(childCert.RawTBSCertificate)
digest := h.Sum(nil)
isValid := ecdsa.Verify(pubkey, digest, signature.R, signature.S)
return isValid, nil
}
// * Copyright 2017 gRPC authors.
// * Licensed under the Apache License, Version 2.0 (the "License");
// * (see https://github.com/grpc/grpc-go/blob/v1.13.0/credentials/credentials_util_go18.go)
// cloneTLSConfig returns a shallow clone of the exported
// fields of cfg, ignoring the unexported sync.Once, which
// contains a mutex and must not be copied.
//
// If cfg is nil, a new zero tls.Config is returned.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}