2019-01-24 20:15:10 +00:00
// Copyright (C) 2019 Storj Labs, Inc.
2018-12-07 13:44:25 +00:00
// See LICENSE for copying information.
package peertls
import (
2018-12-13 20:01:43 +00:00
"bytes"
"crypto"
2018-12-07 13:44:25 +00:00
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
2018-12-13 20:01:43 +00:00
"encoding/binary"
"encoding/gob"
"time"
2018-12-07 13:44:25 +00:00
"github.com/zeebo/errs"
2019-02-07 09:04:29 +00:00
"storj.io/storj/pkg/pkcrypto"
2018-12-13 20:01:43 +00:00
)
const (
// SignedCertExtID is the asn1 object ID for a pkix extensionHandler holding a
// signature of the cert it's extending, signed by some CA (e.g. the root cert chain).
// This extensionHandler allows for an additional signature per certificate.
SignedCertExtID = iota
// RevocationExtID is the asn1 object ID for a pkix extensionHandler containing the
// most recent certificate revocation data
// for the current TLS cert chain.
RevocationExtID
// RevocationBucket is the bolt bucket to store revocation data in
RevocationBucket = "revocations"
)
const (
// LeafIndex is the index of the leaf certificate in a cert chain (0)
LeafIndex = iota
// CAIndex is the index of the CA certificate in a cert chain (1)
CAIndex
)
var (
// ExtensionIDs is a map from an enum to object identifiers used in peertls extensions.
ExtensionIDs = map [ int ] asn1 . ObjectIdentifier {
// NB: 2.999.X is reserved for "example" OIDs
// (see http://oid-info.com/get/2.999)
SignedCertExtID : { 2 , 999 , 1 , 1 } ,
RevocationExtID : { 2 , 999 , 1 , 2 } ,
}
// ErrExtension is used when an error occurs while processing an extensionHandler.
ErrExtension = errs . Class ( "extension error" )
// ErrRevocation is used when an error occurs involving a certificate revocation
ErrRevocation = errs . Class ( "revocation processing error" )
// ErrRevocationDB is used when an error occurs involving the revocations database
ErrRevocationDB = errs . Class ( "revocation database error" )
2019-02-06 16:40:55 +00:00
// ErrRevokedCert is used when a certificate in the chain is revoked and not expected to be
ErrRevokedCert = ErrRevocation . New ( "a certificate in the chain is revoked" )
2018-12-13 20:01:43 +00:00
// ErrUniqueExtensions is used when multiple extensions have the same Id
ErrUniqueExtensions = ErrExtension . New ( "extensions are not unique" )
// ErrRevocationTimestamp is used when a revocation's timestamp is older than the last recorded revocation
ErrRevocationTimestamp = ErrExtension . New ( "revocation timestamp is older than last known revocation" )
2018-12-07 13:44:25 +00:00
)
// TLSExtConfig is used to bind cli flags for determining which extensions will
// be used by the server
type TLSExtConfig struct {
2019-02-06 16:40:55 +00:00
Revocation bool ` help:"if true, client leaves may contain the most recent certificate revocation for the current certificate" default:"true" `
WhitelistSignedLeaf bool ` help:"if true, client leaves must contain a valid \"signed certificate extension\" (NB: verified against certs in the peer ca whitelist; i.e. if true, a whitelist must be provided)" default:"false" `
2018-12-07 13:44:25 +00:00
}
2018-12-13 20:01:43 +00:00
// ExtensionHandlers is a collection of `extensionHandler`s for convenience (see `VerifyFunc`)
2019-01-02 17:39:17 +00:00
type ExtensionHandlers [ ] ExtensionHandler
2018-12-13 20:01:43 +00:00
type extensionVerificationFunc func ( pkix . Extension , [ ] [ ] * x509 . Certificate ) error
2019-01-02 17:39:17 +00:00
// ExtensionHandler represents a verify function for handling an extension
// with the given ID
type ExtensionHandler struct {
ID asn1 . ObjectIdentifier
Verify extensionVerificationFunc
2018-12-13 20:01:43 +00:00
}
// ParseExtOptions holds options for calling `ParseExtensions`
type ParseExtOptions struct {
CAWhitelist [ ] * x509 . Certificate
2019-02-06 16:40:55 +00:00
RevDB RevocationDB
2018-12-13 20:01:43 +00:00
}
// Revocation represents a certificate revocation for storage in the revocation
// database and for use in a TLS extension
type Revocation struct {
Timestamp int64
CertHash [ ] byte
Signature [ ] byte
}
2018-12-07 13:44:25 +00:00
2019-02-06 16:40:55 +00:00
// RevocationDB stores certificate revocation data.
type RevocationDB interface {
Get ( chain [ ] * x509 . Certificate ) ( * Revocation , error )
Put ( chain [ ] * x509 . Certificate , ext pkix . Extension ) error
List ( ) ( [ ] * Revocation , error )
Close ( ) error
2018-12-07 13:44:25 +00:00
}
2018-12-13 20:01:43 +00:00
// ParseExtensions parses an extension config into a slice of extension handlers
// with their respective ids (`asn1.ObjectIdentifier`) and a "verify" function
// to be used in the context of peer certificate verification.
func ParseExtensions ( c TLSExtConfig , opts ParseExtOptions ) ( handlers ExtensionHandlers ) {
2018-12-07 13:44:25 +00:00
if c . WhitelistSignedLeaf {
2019-01-02 17:39:17 +00:00
handlers = append ( handlers , ExtensionHandler {
ID : ExtensionIDs [ SignedCertExtID ] ,
Verify : verifyCAWhitelistSignedLeafFunc ( opts . CAWhitelist ) ,
2018-12-13 20:01:43 +00:00
} )
}
if c . Revocation {
2019-01-02 17:39:17 +00:00
handlers = append ( handlers , ExtensionHandler {
ID : ExtensionIDs [ RevocationExtID ] ,
Verify : func ( certExt pkix . Extension , chains [ ] [ ] * x509 . Certificate ) error {
2018-12-13 20:01:43 +00:00
if err := opts . RevDB . Put ( chains [ 0 ] , certExt ) ; err != nil {
return err
2018-12-07 13:44:25 +00:00
}
2018-12-13 20:01:43 +00:00
return nil
2018-12-07 13:44:25 +00:00
} ,
} )
}
2018-12-13 20:01:43 +00:00
return handlers
}
// NewRevocationExt generates a revocation extension for a certificate.
func NewRevocationExt ( key crypto . PrivateKey , revokedCert * x509 . Certificate ) ( pkix . Extension , error ) {
nowUnix := time . Now ( ) . Unix ( )
2019-02-07 17:08:52 +00:00
hash := pkcrypto . SHA256Hash ( revokedCert . Raw )
2018-12-13 20:01:43 +00:00
rev := Revocation {
Timestamp : nowUnix ,
CertHash : make ( [ ] byte , len ( hash ) ) ,
}
copy ( rev . CertHash , hash )
if err := rev . Sign ( key ) ; err != nil {
return pkix . Extension { } , err
}
revBytes , err := rev . Marshal ( )
if err != nil {
return pkix . Extension { } , err
}
ext := pkix . Extension {
Id : ExtensionIDs [ RevocationExtID ] ,
Value : make ( [ ] byte , len ( revBytes ) ) ,
}
copy ( ext . Value , revBytes )
return ext , nil
}
// AddRevocationExt generates a revocation extension for a cert and attaches it
// to the cert which will replace the revoked cert.
func AddRevocationExt ( key crypto . PrivateKey , revokedCert , newCert * x509 . Certificate ) error {
ext , err := NewRevocationExt ( key , revokedCert )
if err != nil {
return err
}
err = AddExtension ( newCert , ext )
if err != nil {
return err
}
return nil
}
// AddSignedCertExt generates a signed certificate extension for a cert and attaches
// it to that cert.
func AddSignedCertExt ( key crypto . PrivateKey , cert * x509 . Certificate ) error {
2019-02-07 20:39:20 +00:00
signature , err := pkcrypto . HashAndSign ( key , cert . RawTBSCertificate )
2018-12-13 20:01:43 +00:00
if err != nil {
return err
}
err = AddExtension ( cert , pkix . Extension {
Id : ExtensionIDs [ SignedCertExtID ] ,
Value : signature ,
} )
if err != nil {
return err
}
return nil
}
// AddExtension adds one or more extensions to a certificate
func AddExtension ( cert * x509 . Certificate , exts ... pkix . Extension ) ( err error ) {
if len ( exts ) == 0 {
return nil
}
if ! uniqueExts ( append ( cert . ExtraExtensions , exts ... ) ) {
return ErrUniqueExtensions
}
for _ , ext := range exts {
e := pkix . Extension { Id : ext . Id , Value : make ( [ ] byte , len ( ext . Value ) ) }
copy ( e . Value , ext . Value )
cert . ExtraExtensions = append ( cert . ExtraExtensions , e )
}
return nil
2018-12-07 13:44:25 +00:00
}
// VerifyFunc returns a peer certificate verification function which iterates
// over all the leaf cert's extensions and receiver extensions and calls
2018-12-13 20:01:43 +00:00
// `extensionHandler#verify` when it finds a match by id (`asn1.ObjectIdentifier`)
func ( e ExtensionHandlers ) VerifyFunc ( ) PeerCertVerificationFunc {
if len ( e ) == 0 {
return nil
}
2018-12-07 13:44:25 +00:00
return func ( _ [ ] [ ] byte , parsedChains [ ] [ ] * x509 . Certificate ) error {
2018-12-13 20:01:43 +00:00
leafExts := make ( map [ string ] pkix . Extension )
for _ , ext := range parsedChains [ 0 ] [ LeafIndex ] . ExtraExtensions {
leafExts [ ext . Id . String ( ) ] = ext
}
for _ , handler := range e {
2019-01-02 17:39:17 +00:00
if ext , ok := leafExts [ handler . ID . String ( ) ] ; ok {
err := handler . Verify ( ext , parsedChains )
2018-12-13 20:01:43 +00:00
if err != nil {
return ErrExtension . Wrap ( err )
2018-12-07 13:44:25 +00:00
}
}
}
return nil
}
}
2018-12-13 20:01:43 +00:00
// Verify checks if the signature of the revocation was produced by the passed cert's public key.
func ( r Revocation ) Verify ( signingCert * x509 . Certificate ) error {
pubKey , ok := signingCert . PublicKey . ( crypto . PublicKey )
if ! ok {
2019-02-07 09:04:29 +00:00
return pkcrypto . ErrUnsupportedKey . New ( "%T" , signingCert . PublicKey )
2018-12-13 20:01:43 +00:00
}
2019-02-07 17:08:52 +00:00
data := r . TBSBytes ( )
2019-02-07 20:39:20 +00:00
if err := pkcrypto . HashAndVerifySignature ( pubKey , data , r . Signature ) ; err != nil {
2018-12-13 20:01:43 +00:00
return err
}
return nil
}
// TBSBytes (ToBeSigned) returns the hash of the revoked certificate hash and
// the timestamp (i.e. hash(hash(cert bytes) + timestamp)).
2019-02-07 17:08:52 +00:00
func ( r * Revocation ) TBSBytes ( ) [ ] byte {
var tsBytes [ binary . MaxVarintLen64 ] byte
binary . PutVarint ( tsBytes [ : ] , r . Timestamp )
toHash := append ( append ( [ ] byte { } , r . CertHash ... ) , tsBytes [ : ] ... )
2018-12-13 20:01:43 +00:00
2019-02-07 17:08:52 +00:00
return pkcrypto . SHA256Hash ( toHash )
2018-12-13 20:01:43 +00:00
}
// Sign generates a signature using the passed key and attaches it to the revocation.
func ( r * Revocation ) Sign ( key crypto . PrivateKey ) error {
2019-02-07 17:08:52 +00:00
data := r . TBSBytes ( )
2019-02-07 20:39:20 +00:00
sig , err := pkcrypto . HashAndSign ( key , data )
2018-12-13 20:01:43 +00:00
if err != nil {
return err
}
2019-02-07 17:08:52 +00:00
r . Signature = sig
2018-12-13 20:01:43 +00:00
return nil
}
// Marshal serializes a revocation to bytes
func ( r Revocation ) Marshal ( ) ( [ ] byte , error ) {
data := new ( bytes . Buffer )
// NB: using gob instead of asn1 because we plan to leave tls and asn1 is meh
encoder := gob . NewEncoder ( data )
err := encoder . Encode ( r )
if err != nil {
return nil , err
}
return data . Bytes ( ) , nil
}
// Unmarshal deserializes a revocation from bytes
func ( r * Revocation ) Unmarshal ( data [ ] byte ) error {
// NB: using gob instead of asn1 because we plan to leave tls and asn1 is meh
decoder := gob . NewDecoder ( bytes . NewBuffer ( data ) )
if err := decoder . Decode ( r ) ; err != nil {
return err
}
return nil
}
func verifyCAWhitelistSignedLeafFunc ( caWhitelist [ ] * x509 . Certificate ) extensionVerificationFunc {
return func ( certExt pkix . Extension , chains [ ] [ ] * x509 . Certificate ) error {
if caWhitelist == nil {
return ErrVerifyCAWhitelist . New ( "no whitelist provided" )
}
leaf := chains [ 0 ] [ LeafIndex ]
for _ , ca := range caWhitelist {
2019-02-07 20:39:20 +00:00
err := pkcrypto . HashAndVerifySignature ( ca . PublicKey , leaf . RawTBSCertificate , certExt . Value )
2018-12-13 20:01:43 +00:00
if err == nil {
return nil
}
}
return ErrVerifyCAWhitelist . New ( "leaf extension" )
}
}