github.com/hyperledger/fabric-ca@v2.0.0-alpha.0.20201120210307-7b4f34729db1+incompatible/lib/serverenroll.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package lib
     8  
     9  import (
    10  	"crypto/x509"
    11  	"encoding/asn1"
    12  	"encoding/pem"
    13  	"time"
    14  
    15  	"github.com/cloudflare/cfssl/config"
    16  	"github.com/cloudflare/cfssl/csr"
    17  	cferr "github.com/cloudflare/cfssl/errors"
    18  	"github.com/cloudflare/cfssl/log"
    19  	"github.com/cloudflare/cfssl/signer"
    20  	"github.com/hyperledger/fabric-ca/internal/pkg/api"
    21  	"github.com/hyperledger/fabric-ca/internal/pkg/util"
    22  	"github.com/hyperledger/fabric-ca/lib/caerrors"
    23  	"github.com/hyperledger/fabric-ca/lib/server/user"
    24  	"github.com/pkg/errors"
    25  )
    26  
    27  const (
    28  	commonNameLength             = 64
    29  	serialNumberLength           = 64
    30  	countryNameLength            = 2
    31  	localityNameLength           = 128
    32  	stateOrProvinceNameLength    = 128
    33  	organizationNameLength       = 64
    34  	organizationalUnitNameLength = 64
    35  )
    36  
    37  var (
    38  	// The X.509 BasicConstraints object identifier (RFC 5280, 4.2.1.9)
    39  	basicConstraintsOID   = asn1.ObjectIdentifier{2, 5, 29, 19}
    40  	commonNameOID         = asn1.ObjectIdentifier{2, 5, 4, 3}
    41  	serialNumberOID       = asn1.ObjectIdentifier{2, 5, 4, 5}
    42  	countryOID            = asn1.ObjectIdentifier{2, 5, 4, 6}
    43  	localityOID           = asn1.ObjectIdentifier{2, 5, 4, 7}
    44  	stateOID              = asn1.ObjectIdentifier{2, 5, 4, 8}
    45  	organizationOID       = asn1.ObjectIdentifier{2, 5, 4, 10}
    46  	organizationalUnitOID = asn1.ObjectIdentifier{2, 5, 4, 11}
    47  )
    48  
    49  func newEnrollEndpoint(s *Server) *serverEndpoint {
    50  	return &serverEndpoint{
    51  		Path:      "enroll",
    52  		Methods:   []string{"POST"},
    53  		Handler:   enrollHandler,
    54  		Server:    s,
    55  		successRC: 201,
    56  	}
    57  }
    58  
    59  func newReenrollEndpoint(s *Server) *serverEndpoint {
    60  	return &serverEndpoint{
    61  		Path:      "reenroll",
    62  		Methods:   []string{"POST"},
    63  		Handler:   reenrollHandler,
    64  		Server:    s,
    65  		successRC: 201,
    66  	}
    67  }
    68  
    69  // Handle an enroll request, guarded by basic authentication
    70  func enrollHandler(ctx *serverRequestContextImpl) (interface{}, error) {
    71  	id, err := ctx.BasicAuthentication()
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	resp, err := handleEnroll(ctx, id)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	err = ctx.ui.LoginComplete()
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	return resp, nil
    84  }
    85  
    86  // Handle a reenroll request, guarded by token authentication
    87  func reenrollHandler(ctx *serverRequestContextImpl) (interface{}, error) {
    88  	// Authenticate the caller
    89  	id, err := ctx.TokenAuthentication()
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	return handleEnroll(ctx, id)
    94  }
    95  
    96  // Handle the common processing for enroll and reenroll
    97  func handleEnroll(ctx *serverRequestContextImpl, id string) (interface{}, error) {
    98  	var req api.EnrollmentRequestNet
    99  	err := ctx.ReadBody(&req)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	// Get the targeted CA
   104  	ca, err := ctx.GetCA()
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	// Set expiry based on the requested CA profile else use expiry from the default
   109  	// profile
   110  	profile := ca.Config.Signing.Default
   111  	if req.Profile != "" && ca.Config.Signing != nil &&
   112  		ca.Config.Signing.Profiles != nil && ca.Config.Signing.Profiles[req.Profile] != nil {
   113  		profile = ca.Config.Signing.Profiles[req.Profile]
   114  	}
   115  	req.NotAfter = time.Now().Round(time.Minute).Add(profile.Expiry).UTC()
   116  
   117  	caexpiry, err := ca.getCACertExpiry()
   118  	if err != nil {
   119  		return nil, errors.New("Failed to get CA certificate information")
   120  	}
   121  
   122  	// Make sure requested expiration for enrollment certificate is not after CA certificate
   123  	// expiration
   124  	if !caexpiry.IsZero() && req.NotAfter.After(caexpiry) {
   125  		log.Debugf("Requested expiry '%s' is after the CA certificate expiry '%s'. Will use CA cert expiry",
   126  			req.NotAfter, caexpiry)
   127  		req.NotAfter = caexpiry
   128  	}
   129  
   130  	// Process the sign request from the caller.
   131  	// Make sure it is authorized and do any swizzling appropriate to the request.
   132  	err = processSignRequest(id, &req.SignRequest, ca, ctx)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	// Get an attribute extension if one is being requested
   137  	ext, err := ctx.GetAttrExtension(req.AttrReqs, req.Profile)
   138  	if err != nil {
   139  		return nil, errors.WithMessage(err, "Failed to find requested attributes")
   140  	}
   141  	// If there is an extension requested, add it to the request
   142  	if ext != nil {
   143  		log.Debugf("Adding attribute extension to CSR: %+v", ext)
   144  		req.Extensions = append(req.Extensions, *ext)
   145  	}
   146  	// Sign the certificate
   147  	cert, err := ca.enrollSigner.Sign(req.SignRequest)
   148  	if err != nil {
   149  		return nil, errors.WithMessage(err, "Certificate signing failure")
   150  	}
   151  	// Add server info to the response
   152  	resp := &api.EnrollmentResponseNet{
   153  		Cert: util.B64Encode(cert),
   154  	}
   155  	err = ca.fillCAInfo(&resp.ServerInfo)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	// Success
   160  	return resp, nil
   161  }
   162  
   163  // Process the sign request.
   164  // Make any authorization checks needed, depending on the contents
   165  // of the CSR (Certificate Signing Request).
   166  // In particular, if the request is for an intermediate CA certificate,
   167  // the caller must have the "hf.IntermediateCA" attribute.
   168  // Check to see that CSR values do not exceed the character limit
   169  // as specified in RFC 3280, page 103.
   170  // Set the OU fields of the request.
   171  func processSignRequest(id string, req *signer.SignRequest, ca *CA, ctx *serverRequestContextImpl) error {
   172  	// Decode and parse the request into a CSR so we can make checks
   173  	block, _ := pem.Decode([]byte(req.Request))
   174  	if block == nil {
   175  		return caerrors.NewHTTPErr(400, caerrors.ErrBadCSR, "CSR Decode failed")
   176  	}
   177  	if block.Type != "NEW CERTIFICATE REQUEST" && block.Type != "CERTIFICATE REQUEST" {
   178  		return cferr.Wrap(cferr.CSRError,
   179  			cferr.BadRequest, errors.New("not a certificate or csr"))
   180  	}
   181  	csrReq, err := x509.ParseCertificateRequest(block.Bytes)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	log.Debugf("Processing sign request: id=%s, CommonName=%s, Subject=%+v", id, csrReq.Subject.CommonName, req.Subject)
   186  	if (req.Subject != nil && req.Subject.CN != id) || csrReq.Subject.CommonName != id {
   187  		return caerrors.NewHTTPErr(403, caerrors.ErrCNInvalidEnroll, "The CSR subject common name must equal the enrollment ID")
   188  	}
   189  	isForCACert, err := isRequestForCASigningCert(csrReq, ca, req.Profile)
   190  	if err != nil {
   191  		return err
   192  	}
   193  	if isForCACert {
   194  		// This is a request for a CA certificate, so make sure the caller
   195  		// has the 'hf.IntermediateCA' attribute
   196  		err := ca.attributeIsTrue(id, "hf.IntermediateCA")
   197  		if err != nil {
   198  			return caerrors.NewAuthorizationErr(caerrors.ErrInvokerMissAttr, "Enrolled failed: %s", err)
   199  		}
   200  	}
   201  	// Check the CSR input length
   202  	err = csrInputLengthCheck(csrReq)
   203  	if err != nil {
   204  		return caerrors.NewHTTPErr(400, caerrors.ErrInputValidCSR, "CSR input validation failed: %s", err)
   205  	}
   206  	caller, err := ctx.GetCaller()
   207  	if err != nil {
   208  		return err
   209  	}
   210  	// Set the OUs in the request appropriately.
   211  	setRequestOUs(req, caller)
   212  	log.Debug("Finished processing sign request")
   213  	return nil
   214  }
   215  
   216  // Check to see if this is a request for a CA signing certificate.
   217  // This can occur if the profile or the CSR has the IsCA bit set.
   218  // See the X.509 BasicConstraints extension (RFC 5280, 4.2.1.9).
   219  func isRequestForCASigningCert(csrReq *x509.CertificateRequest, ca *CA, profile string) (bool, error) {
   220  	// Check the profile to see if the IsCA bit is set
   221  	sp := getSigningProfile(ca, profile)
   222  	if sp == nil {
   223  		return false, errors.Errorf("Invalid profile: '%s'", profile)
   224  	}
   225  	if sp.CAConstraint.IsCA {
   226  		log.Debugf("Request is for a CA signing certificate as set in profile '%s'", profile)
   227  		return true, nil
   228  	}
   229  	// Check the CSR to see if the IsCA bit is set
   230  	for _, val := range csrReq.Extensions {
   231  		if val.Id.Equal(basicConstraintsOID) {
   232  			var constraints csr.BasicConstraints
   233  			var rest []byte
   234  			var err error
   235  			if rest, err = asn1.Unmarshal(val.Value, &constraints); err != nil {
   236  				return false, caerrors.NewHTTPErr(400, caerrors.ErrBadCSR, "Failed parsing CSR constraints: %s", err)
   237  			} else if len(rest) != 0 {
   238  				return false, caerrors.NewHTTPErr(400, caerrors.ErrBadCSR, "Trailing data after X.509 BasicConstraints")
   239  			}
   240  			if constraints.IsCA {
   241  				log.Debug("Request is for a CA signing certificate as indicated in the CSR")
   242  				return true, nil
   243  			}
   244  		}
   245  	}
   246  	// The IsCA bit was not set
   247  	log.Debug("Request is not for a CA signing certificate")
   248  	return false, nil
   249  }
   250  
   251  func getSigningProfile(ca *CA, profile string) *config.SigningProfile {
   252  	if profile == "" {
   253  		return ca.Config.Signing.Default
   254  	}
   255  	return ca.Config.Signing.Profiles[profile]
   256  }
   257  
   258  // Checks to make sure that character limits are not exceeded for CSR fields
   259  func csrInputLengthCheck(req *x509.CertificateRequest) error {
   260  	log.Debug("Checking CSR fields to make sure that they do not exceed maximum character limits")
   261  
   262  	for _, n := range req.Subject.Names {
   263  		value := n.Value.(string)
   264  		switch {
   265  		case n.Type.Equal(commonNameOID):
   266  			if len(value) > commonNameLength {
   267  				return errors.Errorf("The CN '%s' exceeds the maximum character limit of %d", value, commonNameLength)
   268  			}
   269  		case n.Type.Equal(serialNumberOID):
   270  			if len(value) > serialNumberLength {
   271  				return errors.Errorf("The serial number '%s' exceeds the maximum character limit of %d", value, serialNumberLength)
   272  			}
   273  		case n.Type.Equal(organizationalUnitOID):
   274  			if len(value) > organizationalUnitNameLength {
   275  				return errors.Errorf("The organizational unit name '%s' exceeds the maximum character limit of %d", value, organizationalUnitNameLength)
   276  			}
   277  		case n.Type.Equal(organizationOID):
   278  			if len(value) > organizationNameLength {
   279  				return errors.Errorf("The organization name '%s' exceeds the maximum character limit of %d", value, organizationNameLength)
   280  			}
   281  		case n.Type.Equal(countryOID):
   282  			if len(value) > countryNameLength {
   283  				return errors.Errorf("The country name '%s' exceeds the maximum character limit of %d", value, countryNameLength)
   284  			}
   285  		case n.Type.Equal(localityOID):
   286  			if len(value) > localityNameLength {
   287  				return errors.Errorf("The locality name '%s' exceeds the maximum character limit of %d", value, localityNameLength)
   288  			}
   289  		case n.Type.Equal(stateOID):
   290  			if len(value) > stateOrProvinceNameLength {
   291  				return errors.Errorf("The state name '%s' exceeds the maximum character limit of %d", value, stateOrProvinceNameLength)
   292  			}
   293  		}
   294  	}
   295  
   296  	return nil
   297  }
   298  
   299  // Set the OU fields of the sign request based on the identity's type and affilation.
   300  // For example, if the type is 'peer' and the affiliation is 'a.b.c', the
   301  // OUs become 'OU=c,OU=b,OU=a,OU=peer'.
   302  // This is necessary because authorization decisions are made based on the OU fields,
   303  // so we ignore any OU values specified in the enroll request and set them according
   304  // to the type and affiliation.
   305  func setRequestOUs(req *signer.SignRequest, caller user.User) {
   306  	s := req.Subject
   307  	if s == nil {
   308  		s = &signer.Subject{}
   309  	}
   310  	names := []csr.Name{}
   311  	// Add non-OU fields from request
   312  	for _, name := range s.Names {
   313  		if name.C != "" || name.L != "" || name.O != "" || name.ST != "" || name.SerialNumber != "" {
   314  			name.OU = ""
   315  			names = append(names, name)
   316  		}
   317  	}
   318  	// Add an OU field with the type
   319  	names = append(names, csr.Name{OU: caller.GetType()})
   320  	for _, aff := range caller.GetAffiliationPath() {
   321  		names = append(names, csr.Name{OU: aff})
   322  	}
   323  	// Replace with new names
   324  	s.Names = names
   325  	req.Subject = s
   326  }