github.com/silveraid/fabric-ca@v1.1.0-preview.0.20180127000700-71974f53ab08/lib/serverrequestcontext.go (about)

     1  /*
     2  Copyright IBM Corp. 2017 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8                   http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package lib
    18  
    19  import (
    20  	"crypto/x509"
    21  	"encoding/hex"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"net/http"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"github.com/cloudflare/cfssl/config"
    30  	"github.com/cloudflare/cfssl/log"
    31  	"github.com/cloudflare/cfssl/revoke"
    32  	"github.com/cloudflare/cfssl/signer"
    33  	gmux "github.com/gorilla/mux"
    34  	"github.com/hyperledger/fabric-ca/api"
    35  	"github.com/hyperledger/fabric-ca/lib/attr"
    36  	"github.com/hyperledger/fabric-ca/lib/spi"
    37  	"github.com/hyperledger/fabric-ca/util"
    38  	"github.com/hyperledger/fabric/common/attrmgr"
    39  	"github.com/pkg/errors"
    40  )
    41  
    42  // serverRequestContext represents an HTTP request/response context in the server
    43  type serverRequestContext struct {
    44  	req            *http.Request
    45  	resp           http.ResponseWriter
    46  	endpoint       *serverEndpoint
    47  	ca             *CA
    48  	enrollmentID   string
    49  	enrollmentCert *x509.Certificate
    50  	ui             spi.User
    51  	caller         spi.User
    52  	body           struct {
    53  		read bool   // true after body is read
    54  		buf  []byte // the body itself
    55  		err  error  // any error from reading the body
    56  	}
    57  	callerRoles map[string]bool
    58  }
    59  
    60  const (
    61  	registrarRole = "hf.Registrar.Roles"
    62  )
    63  
    64  // newServerRequestContext is the constructor for a serverRequestContext
    65  func newServerRequestContext(r *http.Request, w http.ResponseWriter, se *serverEndpoint) *serverRequestContext {
    66  	return &serverRequestContext{
    67  		req:      r,
    68  		resp:     w,
    69  		endpoint: se,
    70  	}
    71  }
    72  
    73  // BasicAuthentication authenticates the caller's username and password
    74  // found in the authorization header and returns the username
    75  func (ctx *serverRequestContext) BasicAuthentication() (string, error) {
    76  	r := ctx.req
    77  	// Get the authorization header
    78  	authHdr := r.Header.Get("authorization")
    79  	if authHdr == "" {
    80  		return "", newHTTPErr(401, ErrNoAuthHdr, "No authorization header")
    81  	}
    82  	// Extract the username and password from the header
    83  	username, password, ok := r.BasicAuth()
    84  	if !ok {
    85  		return "", newAuthErr(ErrNoUserPass, "No user/pass in authorization header")
    86  	}
    87  	// Get the CA that is targeted by this request
    88  	ca, err := ctx.GetCA()
    89  	if err != nil {
    90  		return "", err
    91  	}
    92  	// Error if max enrollments is disabled for this CA
    93  	log.Debugf("ca.Config: %+v", ca.Config)
    94  	caMaxEnrollments := ca.Config.Registry.MaxEnrollments
    95  	if caMaxEnrollments == 0 {
    96  		return "", newAuthErr(ErrEnrollDisabled, "Enroll is disabled")
    97  	}
    98  	// Get the user info object for this user
    99  	ctx.ui, err = ca.registry.GetUser(username, nil)
   100  	if err != nil {
   101  		return "", newAuthErr(ErrInvalidUser, "Failed to get user: %s", err)
   102  	}
   103  	// Check the user's password and max enrollments if supported by registry
   104  	err = ctx.ui.Login(password, caMaxEnrollments)
   105  	if err != nil {
   106  		return "", newAuthErr(ErrInvalidPass, "Login failure: %s", err)
   107  	}
   108  	// Store the enrollment ID associated with this server request context
   109  	ctx.enrollmentID = username
   110  	// Return the username
   111  	return username, nil
   112  }
   113  
   114  // TokenAuthentication authenticates the caller by token
   115  // in the authorization header.
   116  // Returns the enrollment ID or error.
   117  func (ctx *serverRequestContext) TokenAuthentication() (string, error) {
   118  	r := ctx.req
   119  	// Get the authorization header
   120  	authHdr := r.Header.Get("authorization")
   121  	if authHdr == "" {
   122  		return "", newHTTPErr(401, ErrNoAuthHdr, "No authorization header")
   123  	}
   124  	// Get the CA
   125  	ca, err := ctx.GetCA()
   126  	if err != nil {
   127  		return "", err
   128  	}
   129  	// Get the request body
   130  	body, err := ctx.ReadBodyBytes()
   131  	if err != nil {
   132  		return "", err
   133  	}
   134  	// Verify the token; the signature is over the header and body
   135  	cert, err2 := util.VerifyToken(ca.csp, authHdr, body)
   136  	if err2 != nil {
   137  		return "", newAuthErr(ErrInvalidToken, "Invalid token in authorization header: %s", err2)
   138  	}
   139  	// Make sure the caller's cert was issued by this CA
   140  	err2 = ca.VerifyCertificate(cert)
   141  	if err2 != nil {
   142  		return "", newAuthErr(ErrUntrustedCertificate, "Untrusted certificate: %s", err2)
   143  	}
   144  	id := util.GetEnrollmentIDFromX509Certificate(cert)
   145  	log.Debugf("Checking for revocation/expiration of certificate owned by '%s'", id)
   146  	// VerifyCertificate ensures that the certificate passed in hasn't
   147  	// expired and checks the CRL for the server.
   148  	expired, checked := revoke.VerifyCertificate(cert)
   149  	if !checked {
   150  		return "", newHTTPErr(401, ErrCertRevokeCheckFailure, "Failed while checking for revocation")
   151  	}
   152  	if expired {
   153  		return "", newAuthErr(ErrCertExpired,
   154  			"The certificate in the authorization header is a revoked or expired certificate")
   155  	}
   156  	aki := hex.EncodeToString(cert.AuthorityKeyId)
   157  	serial := util.GetSerialAsHex(cert.SerialNumber)
   158  	aki = strings.ToLower(strings.TrimLeft(aki, "0"))
   159  	serial = strings.ToLower(strings.TrimLeft(serial, "0"))
   160  	certs, err := ca.CertDBAccessor().GetCertificate(serial, aki)
   161  	if err != nil {
   162  		return "", newHTTPErr(500, ErrCertNotFound, "Failed searching certificates: %s", err)
   163  	}
   164  	if len(certs) == 0 {
   165  		return "", newAuthErr(ErrCertNotFound, "Certificate not found with AKI '%s' and serial '%s'", aki, serial)
   166  	}
   167  	for _, certificate := range certs {
   168  		if certificate.Status == "revoked" {
   169  			return "", newAuthErr(ErrCertRevoked, "The certificate in the authorization header is a revoked certificate")
   170  		}
   171  	}
   172  	ctx.enrollmentID = id
   173  	ctx.enrollmentCert = cert
   174  	log.Debugf("Successful token authentication of '%s'", id)
   175  	return id, nil
   176  }
   177  
   178  // GetECert returns the enrollment certificate of the caller, assuming
   179  // token authentication was successful.
   180  func (ctx *serverRequestContext) GetECert() *x509.Certificate {
   181  	return ctx.enrollmentCert
   182  }
   183  
   184  // GetCA returns the CA to which this request is targeted and checks to make sure the database has been initialized
   185  func (ctx *serverRequestContext) GetCA() (*CA, error) {
   186  	_, err := ctx.getCA()
   187  	if err != nil {
   188  		return nil, errors.WithMessage(err, "Failed to get CA instance")
   189  	}
   190  	if !ctx.ca.dbInitialized {
   191  		err := ctx.ca.initDB()
   192  		if err != nil {
   193  			return nil, errors.WithMessage(err, fmt.Sprintf("%s handler failed to initialize DB", strings.TrimLeft(ctx.req.URL.String(), "/")))
   194  		}
   195  	}
   196  	return ctx.ca, nil
   197  }
   198  
   199  // GetCA returns the CA to which this request is targeted
   200  func (ctx *serverRequestContext) getCA() (*CA, error) {
   201  	if ctx.ca == nil {
   202  		// Get the CA name
   203  		name, err := ctx.getCAName()
   204  		if err != nil {
   205  			return nil, err
   206  		}
   207  		// Get the CA by its name
   208  		ctx.ca, err = ctx.endpoint.Server.GetCA(name)
   209  		if err != nil {
   210  			return nil, err
   211  		}
   212  	}
   213  	return ctx.ca, nil
   214  }
   215  
   216  // GetAttrExtension returns an attribute extension to place into a signing request
   217  func (ctx *serverRequestContext) GetAttrExtension(attrReqs []*api.AttributeRequest, profile string) (*signer.Extension, error) {
   218  	ca, err := ctx.GetCA()
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	ui, err := ca.registry.GetUser(ctx.enrollmentID, nil)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	allAttrs, _ := ui.GetAttributes(nil)
   227  	if attrReqs == nil {
   228  		attrReqs = getDefaultAttrReqs(allAttrs)
   229  		if attrReqs == nil {
   230  			// No attributes are being requested, so we are done
   231  			return nil, nil
   232  		}
   233  	}
   234  	attrs, err := ca.attrMgr.ProcessAttributeRequests(
   235  		convertAttrReqs(attrReqs),
   236  		convertAttrs(allAttrs),
   237  	)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  	if attrs != nil {
   242  		buf, err := json.Marshal(attrs)
   243  		if err != nil {
   244  			errors.Wrap(err, "Failed to marshal attributes")
   245  		}
   246  		ext := &signer.Extension{
   247  			ID:       config.OID(attrmgr.AttrOID),
   248  			Critical: false,
   249  			Value:    hex.EncodeToString(buf),
   250  		}
   251  		log.Debugf("Attribute extension being added to certificate is: %+v", ext)
   252  		return ext, nil
   253  	}
   254  	return nil, nil
   255  }
   256  
   257  // caNameReqBody is a sparse request body to unmarshal only the CA name
   258  type caNameReqBody struct {
   259  	CAName string `json:"caname,omitempty"`
   260  }
   261  
   262  // getCAName returns the targeted CA name for this request
   263  func (ctx *serverRequestContext) getCAName() (string, error) {
   264  	// Check the query parameters first
   265  	ca := ctx.req.URL.Query().Get("ca")
   266  	if ca != "" {
   267  		return ca, nil
   268  	}
   269  	// Next, check the request body, if there is one
   270  	var body caNameReqBody
   271  	_, err := ctx.TryReadBody(&body)
   272  	if err != nil {
   273  		return "", err
   274  	}
   275  	if body.CAName != "" {
   276  		return body.CAName, nil
   277  	}
   278  	// No CA name in the request body either, so use the default CA name
   279  	return ctx.endpoint.Server.CA.Config.CA.Name, nil
   280  }
   281  
   282  // ReadBody reads the request body and JSON unmarshals into 'body'
   283  func (ctx *serverRequestContext) ReadBody(body interface{}) error {
   284  	empty, err := ctx.TryReadBody(body)
   285  	if err != nil {
   286  		return err
   287  	}
   288  	if empty {
   289  		return newHTTPErr(400, ErrEmptyReqBody, "Empty request body")
   290  	}
   291  	return nil
   292  }
   293  
   294  // TryReadBody reads the request body into 'body' if not empty
   295  func (ctx *serverRequestContext) TryReadBody(body interface{}) (bool, error) {
   296  	buf, err := ctx.ReadBodyBytes()
   297  	if err != nil {
   298  		return false, err
   299  	}
   300  	empty := len(buf) == 0
   301  	if !empty {
   302  		err = json.Unmarshal(buf, body)
   303  		if err != nil {
   304  			return true, newHTTPErr(400, ErrBadReqBody, "Invalid request body: %s; body=%s",
   305  				err, string(buf))
   306  		}
   307  	}
   308  	return empty, nil
   309  }
   310  
   311  // ReadBodyBytes reads the request body and returns bytes
   312  func (ctx *serverRequestContext) ReadBodyBytes() ([]byte, error) {
   313  	if !ctx.body.read {
   314  		r := ctx.req
   315  		buf, err := ioutil.ReadAll(r.Body)
   316  		ctx.body.buf = buf
   317  		ctx.body.err = err
   318  		ctx.body.read = true
   319  	}
   320  	err := ctx.body.err
   321  	if err != nil {
   322  		return nil, newHTTPErr(400, ErrReadingReqBody, "Failed reading request body: %s", err)
   323  	}
   324  	return ctx.body.buf, nil
   325  }
   326  
   327  func (ctx *serverRequestContext) GetUser(userName string) (spi.User, error) {
   328  	ca, err := ctx.getCA()
   329  	if err != nil {
   330  		return nil, err
   331  	}
   332  	registry := ca.registry
   333  
   334  	user, err := registry.GetUser(userName, nil)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  
   339  	err = ctx.CanManageUser(user)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  
   344  	return user, nil
   345  }
   346  
   347  // CanManageUser determines if the caller has the right type and affiliation to act on on a user
   348  func (ctx *serverRequestContext) CanManageUser(user spi.User) error {
   349  	userAff := strings.Join(user.GetAffiliationPath(), ".")
   350  	err := ctx.ContainsAffiliation(userAff)
   351  	if err != nil {
   352  		return err
   353  	}
   354  
   355  	userType := user.GetType()
   356  	err = ctx.CanActOnType(userType)
   357  	if err != nil {
   358  		return err
   359  	}
   360  
   361  	return nil
   362  }
   363  
   364  // CanModifyUser determines if the modifications to the user are allowed
   365  func (ctx *serverRequestContext) CanModifyUser(req *api.ModifyIdentityRequest, checkAff bool, checkType bool, checkAttrs bool, userToModify spi.User) error {
   366  	if checkAff {
   367  		reqAff := req.Affiliation
   368  		log.Debugf("Checking if caller is authorized to change affiliation to '%s'", reqAff)
   369  		err := ctx.ContainsAffiliation(reqAff)
   370  		if err != nil {
   371  			return err
   372  		}
   373  	}
   374  
   375  	if checkType {
   376  		reqType := req.Type
   377  		log.Debugf("Checking if caller is authorized to change type to '%s'", reqType)
   378  		err := ctx.CanActOnType(reqType)
   379  		if err != nil {
   380  			return err
   381  		}
   382  	}
   383  
   384  	if checkAttrs {
   385  		reqAttrs := req.Attributes
   386  		log.Debugf("Checking if caller is authorized to change attributes to %+v", reqAttrs)
   387  		err := attr.CanRegisterRequestedAttributes(reqAttrs, userToModify, ctx.caller)
   388  		if err != nil {
   389  			return newAuthErr(ErrRegAttrAuth, "Failed to register attributes: %s", err)
   390  		}
   391  	}
   392  
   393  	return nil
   394  }
   395  
   396  // GetCaller gets the user who is making this server request
   397  func (ctx *serverRequestContext) GetCaller() (spi.User, error) {
   398  	if ctx.caller != nil {
   399  		return ctx.caller, nil
   400  	}
   401  
   402  	var err error
   403  	id := ctx.enrollmentID
   404  	if id == "" {
   405  		return nil, newAuthErr(ErrCallerIsNotAuthenticated, "Caller is not authenticated")
   406  	}
   407  	ca, err := ctx.GetCA()
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  	// Get the user info object for this user
   412  	ctx.caller, err = ca.registry.GetUser(id, nil)
   413  	if err != nil {
   414  		return nil, newAuthErr(ErrGettingUser, "Failed to get user")
   415  	}
   416  	return ctx.caller, nil
   417  }
   418  
   419  // ContainsAffiliation returns an error if the requested affiliation does not contain the caller's affiliation
   420  func (ctx *serverRequestContext) ContainsAffiliation(affiliation string) error {
   421  	validAffiliation, err := ctx.containsAffiliation(affiliation)
   422  	if err != nil {
   423  		return newHTTPErr(500, ErrGettingAffiliation, "Failed to validate if caller has authority to get ID: %s", err)
   424  	}
   425  	if !validAffiliation {
   426  		return newAuthErr(ErrCallerNotAffiliated, "Caller does not have authority to act on affiliation '%s'", affiliation)
   427  	}
   428  	return nil
   429  }
   430  
   431  // containsAffiliation returns true if the requested affiliation contains the caller's affiliation
   432  func (ctx *serverRequestContext) containsAffiliation(affiliation string) (bool, error) {
   433  	caller, err := ctx.GetCaller()
   434  	if err != nil {
   435  		return false, err
   436  	}
   437  
   438  	callerAffiliationPath := GetUserAffiliation(caller)
   439  	log.Debugf("Checking to see if affiliation '%s' contains caller's affiliation '%s'", affiliation, callerAffiliationPath)
   440  
   441  	// If the caller has root affiliation return "true"
   442  	if callerAffiliationPath == "" {
   443  		log.Debug("Caller has root affiliation")
   444  		return true, nil
   445  	}
   446  
   447  	if affiliation == callerAffiliationPath {
   448  		return true, nil
   449  	}
   450  
   451  	callerAffiliationPath = callerAffiliationPath + "."
   452  	if strings.HasPrefix(affiliation, callerAffiliationPath) {
   453  		return true, nil
   454  	}
   455  
   456  	return false, nil
   457  }
   458  
   459  // IsRegistrar returns an error if the caller is not a registrar
   460  func (ctx *serverRequestContext) IsRegistrar() error {
   461  	_, isRegistrar, err := ctx.isRegistrar()
   462  	if err != nil {
   463  		return err
   464  	}
   465  	if !isRegistrar {
   466  		return newAuthErr(ErrMissingRegAttr, "Caller is not a registrar")
   467  	}
   468  
   469  	return nil
   470  }
   471  
   472  // isRegistrar returns back true if the caller is a registrar along with the types the registrar is allowed to register
   473  func (ctx *serverRequestContext) isRegistrar() (string, bool, error) {
   474  	caller, err := ctx.GetCaller()
   475  	if err != nil {
   476  		return "", false, err
   477  	}
   478  
   479  	log.Debugf("Checking to see if caller '%s' is a registrar", caller.GetName())
   480  
   481  	rolesStr, err := caller.GetAttribute("hf.Registrar.Roles")
   482  	if err != nil {
   483  		return "", false, newAuthErr(ErrRegAttrAuth, "'%s' is not a registrar", caller.GetName())
   484  	}
   485  
   486  	// Has some value for attribute 'hf.Registrar.Roles' then user is a registrar
   487  	if rolesStr.Value != "" {
   488  		return rolesStr.Value, true, nil
   489  	}
   490  
   491  	return "", false, nil
   492  }
   493  
   494  // CanActOnType returns true if the caller has the proper authority to take action on specific type
   495  func (ctx *serverRequestContext) CanActOnType(userType string) error {
   496  	canAct, err := ctx.canActOnType(userType)
   497  	if err != nil {
   498  		return newHTTPErr(500, ErrGettingType, "Failed to verify if user can act on type '%s': %s", userType, err)
   499  	}
   500  	if !canAct {
   501  		return newAuthErr(ErrCallerNotAffiliated, "Registrar does not have authority to act on type '%s'", userType)
   502  	}
   503  	return nil
   504  }
   505  
   506  func (ctx *serverRequestContext) canActOnType(requestedType string) (bool, error) {
   507  	caller, err := ctx.GetCaller()
   508  	if err != nil {
   509  		return false, err
   510  	}
   511  
   512  	log.Debugf("Checking to see if caller '%s' can act on type '%s'", caller.GetName(), requestedType)
   513  
   514  	typesStr, isRegistrar, err := ctx.isRegistrar()
   515  	if err != nil {
   516  		return false, err
   517  	}
   518  	if !isRegistrar {
   519  		return false, newAuthErr(ErrRegAttrAuth, "'%s' is not allowed to manage users", caller.GetName())
   520  	}
   521  
   522  	var types []string
   523  	if typesStr != "" {
   524  		types = strings.Split(typesStr, ",")
   525  	} else {
   526  		types = make([]string, 0)
   527  	}
   528  
   529  	if !util.StrContained(requestedType, types) {
   530  		log.Debug("Caller with types '%s' is not authorized to act on '%s'", types, requestedType)
   531  		return false, nil
   532  	}
   533  
   534  	return true, nil
   535  }
   536  
   537  // HasRole returns an error if the caller does not have the attribute or the value is false for a boolean attribute
   538  func (ctx *serverRequestContext) HasRole(role string) error {
   539  	hasRole, err := ctx.hasRole(role)
   540  	if err != nil {
   541  		return err
   542  	}
   543  	if !hasRole {
   544  		return newHTTPErr(400, ErrMissingRole, "Caller has a value of 'false' for attribute/role '%s'", role)
   545  	}
   546  	return nil
   547  }
   548  
   549  // HasRole returns true if the caller has the attribute and value of the attribute is true
   550  func (ctx *serverRequestContext) hasRole(role string) (bool, error) {
   551  	if ctx.callerRoles == nil {
   552  		ctx.callerRoles = make(map[string]bool)
   553  	}
   554  
   555  	roleStatus, hasRole := ctx.callerRoles[role]
   556  	if hasRole {
   557  		return roleStatus, nil
   558  	}
   559  
   560  	caller, err := ctx.GetCaller()
   561  	if err != nil {
   562  		return false, err
   563  	}
   564  
   565  	roleAttr, err := caller.GetAttribute(role)
   566  	if err != nil {
   567  		return false, err
   568  	}
   569  	roleStatus, err = strconv.ParseBool(roleAttr.Value)
   570  	if err != nil {
   571  		return false, errors.Wrap(err, fmt.Sprintf("Failed to get boolean value of '%s'", role))
   572  	}
   573  	ctx.callerRoles[role] = roleStatus
   574  
   575  	return ctx.callerRoles[role], nil
   576  }
   577  
   578  // GetVar returns the parameter path variable from the URL
   579  func (ctx *serverRequestContext) GetVar(name string) (string, error) {
   580  	vars := gmux.Vars(ctx.req)
   581  	if vars == nil {
   582  		return "", newHTTPErr(500, ErrHTTPRequest, "Failed to correctly handle HTTP request")
   583  	}
   584  	value := vars[name]
   585  	return value, nil
   586  }
   587  
   588  // GetBoolQueryParm returns query parameter from the URL
   589  func (ctx *serverRequestContext) GetBoolQueryParm(name string) (bool, error) {
   590  	var err error
   591  
   592  	value := false
   593  	param := ctx.req.URL.Query().Get(name)
   594  	if param != "" {
   595  		value, err = strconv.ParseBool(param)
   596  		if err != nil {
   597  			return false, newHTTPErr(400, ErrUpdateConfigRemoveAff, "Failed to correctly parse value of '%s' query parameter: %s", name, err)
   598  		}
   599  	}
   600  
   601  	return value, nil
   602  }
   603  
   604  func convertAttrReqs(attrReqs []*api.AttributeRequest) []attrmgr.AttributeRequest {
   605  	rtn := make([]attrmgr.AttributeRequest, len(attrReqs))
   606  	for i := range attrReqs {
   607  		rtn[i] = attrmgr.AttributeRequest(attrReqs[i])
   608  	}
   609  	return rtn
   610  }
   611  
   612  func convertAttrs(attrs []api.Attribute) []attrmgr.Attribute {
   613  	rtn := make([]attrmgr.Attribute, len(attrs))
   614  	for i := range attrs {
   615  		rtn[i] = attrmgr.Attribute(&attrs[i])
   616  	}
   617  	return rtn
   618  }
   619  
   620  // Return attribute requests for attributes which should by default be added to an ECert
   621  func getDefaultAttrReqs(attrs []api.Attribute) []*api.AttributeRequest {
   622  	count := 0
   623  	for _, attr := range attrs {
   624  		if attr.ECert {
   625  			count++
   626  		}
   627  	}
   628  	if count == 0 {
   629  		return nil
   630  	}
   631  	reqs := make([]*api.AttributeRequest, count)
   632  	count = 0
   633  	for _, attr := range attrs {
   634  		if attr.ECert {
   635  			reqs[count] = &api.AttributeRequest{Name: attr.Name}
   636  			count++
   637  		}
   638  	}
   639  	return reqs
   640  }