github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/common/crypto/expiration.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package crypto
     8  
     9  import (
    10  	"bytes"
    11  	"crypto/x509"
    12  	"encoding/pem"
    13  	"time"
    14  
    15  	"github.com/golang/protobuf/proto"
    16  	"github.com/hyperledger/fabric-protos-go/msp"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // ExpiresAt returns when the given identity expires, or a zero time.Time
    21  // in case we cannot determine that
    22  func ExpiresAt(identityBytes []byte) time.Time {
    23  	sId := &msp.SerializedIdentity{}
    24  	// If protobuf parsing failed, we make no decisions about the expiration time
    25  	if err := proto.Unmarshal(identityBytes, sId); err != nil {
    26  		return time.Time{}
    27  	}
    28  	return certExpirationTime(sId.IdBytes)
    29  }
    30  
    31  func certExpirationTime(pemBytes []byte) time.Time {
    32  	bl, _ := pem.Decode(pemBytes)
    33  	if bl == nil {
    34  		// If the identity isn't a PEM block, we make no decisions about the expiration time
    35  		return time.Time{}
    36  	}
    37  	cert, err := x509.ParseCertificate(bl.Bytes)
    38  	if err != nil {
    39  		return time.Time{}
    40  	}
    41  	return cert.NotAfter
    42  }
    43  
    44  // MessageFunc notifies a message happened with the given format, and can be replaced with Warnf or Infof of a logger.
    45  type MessageFunc func(format string, args ...interface{})
    46  
    47  // Scheduler invokes f after d time, and can be replaced with time.AfterFunc.
    48  type Scheduler func(d time.Duration, f func()) *time.Timer
    49  
    50  // TrackExpiration warns a week before one of the certificates expires
    51  func TrackExpiration(tls bool, serverCert []byte, clientCertChain [][]byte, sIDBytes []byte, info MessageFunc, warn MessageFunc, now time.Time, s Scheduler) {
    52  	sID := &msp.SerializedIdentity{}
    53  	if err := proto.Unmarshal(sIDBytes, sID); err != nil {
    54  		return
    55  	}
    56  
    57  	trackCertExpiration(sID.IdBytes, "enrollment", info, warn, now, s)
    58  
    59  	if !tls {
    60  		return
    61  	}
    62  
    63  	trackCertExpiration(serverCert, "server TLS", info, warn, now, s)
    64  
    65  	if len(clientCertChain) == 0 || len(clientCertChain[0]) == 0 {
    66  		return
    67  	}
    68  
    69  	trackCertExpiration(clientCertChain[0], "client TLS", info, warn, now, s)
    70  }
    71  
    72  func trackCertExpiration(rawCert []byte, certRole string, info MessageFunc, warn MessageFunc, now time.Time, sched Scheduler) {
    73  	expirationTime := certExpirationTime(rawCert)
    74  	if expirationTime.IsZero() {
    75  		// If the certificate expiration time cannot be classified, return.
    76  		return
    77  	}
    78  
    79  	timeLeftUntilExpiration := expirationTime.Sub(now)
    80  	oneWeek := time.Hour * 24 * 7
    81  
    82  	if timeLeftUntilExpiration < 0 {
    83  		warn("The %s certificate has expired", certRole)
    84  		return
    85  	}
    86  
    87  	info("The %s certificate will expire on %s", certRole, expirationTime)
    88  
    89  	if timeLeftUntilExpiration < oneWeek {
    90  		days := timeLeftUntilExpiration / (time.Hour * 24)
    91  		hours := (timeLeftUntilExpiration - (days * time.Hour * 24)) / time.Hour
    92  		warn("The %s certificate expires within %d days and %d hours", certRole, days, hours)
    93  		return
    94  	}
    95  
    96  	timeLeftUntilOneWeekBeforeExpiration := timeLeftUntilExpiration - oneWeek
    97  
    98  	sched(timeLeftUntilOneWeekBeforeExpiration, func() {
    99  		warn("The %s certificate will expire within one week", certRole)
   100  	})
   101  
   102  }
   103  
   104  var (
   105  	// ErrPubKeyMismatch is used by CertificatesWithSamePublicKey to indicate the two public keys mismatch
   106  	ErrPubKeyMismatch = errors.New("public keys do not match")
   107  )
   108  
   109  // LogNonPubKeyMismatchErr logs an error which is not an ErrPubKeyMismatch error
   110  func LogNonPubKeyMismatchErr(log func(template string, args ...interface{}), err error, cert1DER, cert2DER []byte) {
   111  	cert1PEM := &pem.Block{Type: "CERTIFICATE", Bytes: cert1DER}
   112  	cert2PEM := &pem.Block{Type: "CERTIFICATE", Bytes: cert2DER}
   113  	log("Failed determining if public key of %s matches public key of %s: %s",
   114  		string(pem.EncodeToMemory(cert1PEM)),
   115  		string(pem.EncodeToMemory(cert2PEM)),
   116  		err)
   117  }
   118  
   119  // CertificatesWithSamePublicKey returns nil if both byte slices
   120  // are valid DER encoding of certificates with the same public key.
   121  func CertificatesWithSamePublicKey(der1, der2 []byte) error {
   122  	cert1canonized, err := publicKeyFromCertificate(der1)
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	cert2canonized, err := publicKeyFromCertificate(der2)
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	if bytes.Equal(cert1canonized, cert2canonized) {
   133  		return nil
   134  	}
   135  	return ErrPubKeyMismatch
   136  }
   137  
   138  // publicKeyFromCertificate returns the public key of the given ASN1 DER certificate.
   139  func publicKeyFromCertificate(der []byte) ([]byte, error) {
   140  	cert, err := x509.ParseCertificate(der)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	return x509.MarshalPKIXPublicKey(cert.PublicKey)
   145  }