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 }