github.com/paul-lee-attorney/fabric-ca-1.4.7-gm@v0.0.0-20201120102036-c7ad827cf9ac/util/util.go (about) 1 /* 2 Copyright IBM Corp. 2016 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 util 18 19 import ( 20 "bytes" 21 "crypto/ecdsa" 22 "crypto/rsa" 23 "crypto/x509" 24 "encoding/base64" 25 "encoding/json" 26 "encoding/pem" 27 "fmt" 28 "io" 29 "io/ioutil" 30 "math/big" 31 mrand "math/rand" 32 "net/http" 33 "net/url" 34 "os" 35 "path" 36 "path/filepath" 37 "reflect" 38 "regexp" 39 "strings" 40 "testing" 41 "time" 42 43 "github.com/cloudflare/cfssl/log" 44 "github.com/paul-lee-attorney/fabric-2.1-gm/bccsp" 45 "github.com/paul-lee-attorney/gm/gmx509" 46 "github.com/paul-lee-attorney/gm/sm2" 47 "github.com/pkg/errors" 48 "github.com/spf13/viper" 49 "github.com/stretchr/testify/assert" 50 "golang.org/x/crypto/ocsp" 51 ) 52 53 var ( 54 rnd = mrand.NewSource(time.Now().UnixNano()) 55 // ErrNotImplemented used to return errors for functions not implemented 56 ErrNotImplemented = errors.New("NOT YET IMPLEMENTED") 57 ) 58 59 const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 60 const ( 61 letterIdxBits = 6 // 6 bits to represent a letter index 62 letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits 63 letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits 64 ) 65 66 // RevocationReasonCodes is a map between string reason codes to integers as defined in RFC 5280 67 var RevocationReasonCodes = map[string]int{ 68 "unspecified": ocsp.Unspecified, 69 "keycompromise": ocsp.KeyCompromise, 70 "cacompromise": ocsp.CACompromise, 71 "affiliationchanged": ocsp.AffiliationChanged, 72 "superseded": ocsp.Superseded, 73 "cessationofoperation": ocsp.CessationOfOperation, 74 "certificatehold": ocsp.CertificateHold, 75 "removefromcrl": ocsp.RemoveFromCRL, 76 "privilegewithdrawn": ocsp.PrivilegeWithdrawn, 77 "aacompromise": ocsp.AACompromise, 78 } 79 80 // SecretTag to tag a field as secret as in password, token 81 const SecretTag = "mask" 82 83 // URLRegex is the regular expression to check if a value is an URL 84 var URLRegex = regexp.MustCompile("(ldap|http)s*://(\\S+):(\\S+)@") 85 86 //ECDSASignature forms the structure for R and S value for ECDSA 87 type ECDSASignature struct { 88 R, S *big.Int 89 } 90 91 // RandomString returns a random string 92 func RandomString(n int) string { 93 b := make([]byte, n) 94 95 for i, cache, remain := n-1, rnd.Int63(), letterIdxMax; i >= 0; { 96 if remain == 0 { 97 cache, remain = rnd.Int63(), letterIdxMax 98 } 99 if idx := int(cache & letterIdxMask); idx < len(letterBytes) { 100 b[i] = letterBytes[idx] 101 i-- 102 } 103 cache >>= letterIdxBits 104 remain-- 105 } 106 107 return string(b) 108 } 109 110 // RemoveQuotes removes outer quotes from a string if necessary 111 func RemoveQuotes(str string) string { 112 if str == "" { 113 return str 114 } 115 if (strings.HasPrefix(str, "'") && strings.HasSuffix(str, "'")) || 116 (strings.HasPrefix(str, "\"") && strings.HasSuffix(str, "\"")) { 117 str = str[1 : len(str)-1] 118 } 119 return str 120 } 121 122 // ReadFile reads a file 123 func ReadFile(file string) ([]byte, error) { 124 return ioutil.ReadFile(file) 125 } 126 127 // WriteFile writes a file 128 func WriteFile(file string, buf []byte, perm os.FileMode) error { 129 dir := path.Dir(file) 130 // Create the directory if it doesn't exist 131 if _, err := os.Stat(dir); os.IsNotExist(err) { 132 err = os.MkdirAll(dir, 0755) 133 if err != nil { 134 return errors.Wrapf(err, "Failed to create directory '%s' for file '%s'", dir, file) 135 } 136 } 137 return ioutil.WriteFile(file, buf, perm) 138 } 139 140 // FileExists checks to see if a file exists 141 func FileExists(name string) bool { 142 if _, err := os.Stat(name); err != nil { 143 if os.IsNotExist(err) { 144 return false 145 } 146 } 147 return true 148 } 149 150 // Marshal to bytes 151 func Marshal(from interface{}, what string) ([]byte, error) { 152 buf, err := json.Marshal(from) 153 if err != nil { 154 return nil, errors.Wrapf(err, "Failed to marshal %s", what) 155 } 156 return buf, nil 157 } 158 159 // Unmarshal from bytes 160 func Unmarshal(from []byte, to interface{}, what string) error { 161 err := json.Unmarshal(from, to) 162 if err != nil { 163 return errors.Wrapf(err, "Failed to unmarshal %s", what) 164 } 165 return nil 166 } 167 168 // CreateToken creates a JWT-like token. 169 // In a normal JWT token, the format of the token created is: 170 // <algorithm,claims,signature> 171 // where each part is base64-encoded string separated by a period. 172 // In this JWT-like token, there are two differences: 173 // 1) the claims section is a certificate, so the format is: 174 // <certificate,signature> 175 // 2) the signature uses the private key associated with the certificate, 176 // and the signature is across both the certificate and the "body" argument, 177 // which is the body of an HTTP request, though could be any arbitrary bytes. 178 // @param cert The pem-encoded certificate 179 // @param key The pem-encoded key 180 // @param method http method of the request 181 // @param uri URI of the request 182 // @param body The body of an HTTP request 183 func CreateToken(csp bccsp.BCCSP, cert []byte, key bccsp.Key, method, uri string, body []byte) (string, error) { 184 x509Cert, err := GetX509CertificateFromPEM(cert) 185 if err != nil { 186 return "", err 187 } 188 publicKey := x509Cert.PublicKey 189 190 var token string 191 192 //The RSA Key Gen is commented right now as there is bccsp does 193 switch publicKey.(type) { 194 /* 195 case *rsa.PublicKey: 196 token, err = GenRSAToken(csp, cert, key, body) 197 if err != nil { 198 return "", err 199 } 200 */ 201 case *ecdsa.PublicKey: 202 token, err = GenECDSAToken(csp, cert, key, method, uri, body) 203 if err != nil { 204 return "", err 205 } 206 } 207 return token, nil 208 } 209 210 //GenRSAToken signs the http body and cert with RSA using RSA private key 211 // @csp : BCCSP instance 212 /* 213 func GenRSAToken(csp bccsp.BCCSP, cert []byte, key []byte, body []byte) (string, error) { 214 privKey, err := GetRSAPrivateKey(key) 215 if err != nil { 216 return "", err 217 } 218 b64body := B64Encode(body) 219 b64cert := B64Encode(cert) 220 bodyAndcert := b64body + "." + b64cert 221 hash := sha512.New384() 222 hash.Write([]byte(bodyAndcert)) 223 h := hash.Sum(nil) 224 RSAsignature, err := rsa.SignPKCS1v15(rand.Reader, privKey, crypto.SHA384, h[:]) 225 if err != nil { 226 return "", errors.Wrap(err, "Failed to rsa.SignPKCS1v15") 227 } 228 b64sig := B64Encode(RSAsignature) 229 token := b64cert + "." + b64sig 230 231 return token, nil 232 } 233 */ 234 235 //GenECDSAToken signs the http body and cert with ECDSA using EC private key 236 func GenECDSAToken(csp bccsp.BCCSP, cert []byte, key bccsp.Key, method, uri string, body []byte) (string, error) { 237 b64body := B64Encode(body) 238 b64cert := B64Encode(cert) 239 b64uri := B64Encode([]byte(uri)) 240 payload := method + "." + b64uri + "." + b64body + "." + b64cert 241 242 return genECDSAToken(csp, key, b64cert, payload) 243 } 244 245 func genECDSAToken(csp bccsp.BCCSP, key bccsp.Key, b64cert, payload string) (string, error) { 246 digest, digestError := csp.Hash([]byte(payload), &bccsp.SHAOpts{}) 247 if digestError != nil { 248 return "", errors.WithMessage(digestError, fmt.Sprintf("Hash failed on '%s'", payload)) 249 } 250 251 ecSignature, err := csp.Sign(key, digest, nil) 252 if err != nil { 253 return "", errors.WithMessage(err, "BCCSP signature generation failure") 254 } 255 if len(ecSignature) == 0 { 256 return "", errors.New("BCCSP signature creation failed. Signature must be different than nil") 257 } 258 259 b64sig := B64Encode(ecSignature) 260 token := b64cert + "." + b64sig 261 262 return token, nil 263 264 } 265 266 // VerifyToken verifies token signed by either ECDSA or RSA and 267 // returns the associated user ID 268 func VerifyToken(csp bccsp.BCCSP, token string, method, uri string, body []byte, compMode1_3 bool) (*x509.Certificate, error) { 269 270 if csp == nil { 271 return nil, errors.New("BCCSP instance is not present") 272 } 273 x509Cert, b64Cert, b64Sig, err := DecodeToken(token) 274 if err != nil { 275 return nil, err 276 } 277 sig, err := B64Decode(b64Sig) 278 if err != nil { 279 return nil, errors.WithMessage(err, "Invalid base64 encoded signature in token") 280 } 281 b64Body := B64Encode(body) 282 b64uri := B64Encode([]byte(uri)) 283 sigString := method + "." + b64uri + "." + b64Body + "." + b64Cert 284 285 pk2, err := csp.KeyImport(x509Cert, &bccsp.X509PublicKeyImportOpts{Temporary: true}) 286 if err != nil { 287 return nil, errors.WithMessage(err, "Public Key import into BCCSP failed with error") 288 } 289 if pk2 == nil { 290 return nil, errors.New("Public Key Cannot be imported into BCCSP") 291 } 292 293 //bccsp.X509PublicKeyImportOpts 294 //Using default hash algo 295 digest, digestError := csp.Hash([]byte(sigString), &bccsp.SHAOpts{}) 296 if digestError != nil { 297 return nil, errors.WithMessage(digestError, "Message digest failed") 298 } 299 300 valid, validErr := csp.Verify(pk2, sig, digest, nil) 301 if compMode1_3 && !valid { 302 log.Debugf("Failed to verify token based on new authentication header requirements: %s", err) 303 sigString := b64Body + "." + b64Cert 304 digest, digestError := csp.Hash([]byte(sigString), &bccsp.SHAOpts{}) 305 if digestError != nil { 306 return nil, errors.WithMessage(digestError, "Message digest failed") 307 } 308 valid, validErr = csp.Verify(pk2, sig, digest, nil) 309 } 310 311 if validErr != nil { 312 return nil, errors.WithMessage(validErr, "Token signature validation failure") 313 } 314 if !valid { 315 return nil, errors.New("Token signature validation failed") 316 } 317 318 return x509Cert, nil 319 } 320 321 // DecodeToken extracts an X509 certificate and base64 encoded signature from a token 322 func DecodeToken(token string) (*x509.Certificate, string, string, error) { 323 if token == "" { 324 return nil, "", "", errors.New("Invalid token; it is empty") 325 } 326 parts := strings.Split(token, ".") 327 if len(parts) != 2 { 328 return nil, "", "", errors.New("Invalid token format; expecting 2 parts separated by '.'") 329 } 330 b64cert := parts[0] 331 certDecoded, err := B64Decode(b64cert) 332 if err != nil { 333 return nil, "", "", errors.WithMessage(err, "Failed to decode base64 encoded x509 cert") 334 } 335 x509Cert, err := GetX509CertificateFromPEM(certDecoded) 336 if err != nil { 337 return nil, "", "", errors.WithMessage(err, "Error in parsing x509 certificate given block bytes") 338 } 339 return x509Cert, b64cert, parts[1], nil 340 } 341 342 //GetECPrivateKey get *ecdsa.PrivateKey from key pem 343 func GetECPrivateKey(raw []byte) (*ecdsa.PrivateKey, error) { 344 decoded, _ := pem.Decode(raw) 345 if decoded == nil { 346 return nil, errors.New("Failed to decode the PEM-encoded ECDSA key") 347 } 348 ECprivKey, err := x509.ParseECPrivateKey(decoded.Bytes) 349 if err == nil { 350 return ECprivKey, nil 351 } 352 key, err2 := x509.ParsePKCS8PrivateKey(decoded.Bytes) 353 if err2 == nil { 354 switch key.(type) { 355 case *ecdsa.PrivateKey: 356 return key.(*ecdsa.PrivateKey), nil 357 case *rsa.PrivateKey: 358 return nil, errors.New("Expecting EC private key but found RSA private key") 359 default: 360 return nil, errors.New("Invalid private key type in PKCS#8 wrapping") 361 } 362 } 363 return nil, errors.Wrap(err2, "Failed parsing EC private key") 364 } 365 366 //GetSM2PrivateKey get *sm2.PrivateKey from key pem 367 func GetSM2PrivateKey(raw []byte) (*sm2.PrivateKey, error) { 368 decoded, _ := pem.Decode(raw) 369 if decoded == nil { 370 return nil, errors.New("Failed to decode the PEM-encoded ECDSA key") 371 } 372 SM2privKey, err := gmx509.ParseSM2PrivateKey(decoded.Bytes) 373 if err == nil { 374 return ECprivKey, nil 375 } 376 key, err2 := gmx509.ParsePKCS8SM2PrivateKey(decoded.Bytes) 377 if err2 == nil { 378 return key.(*sm2.PrivateKey), nil 379 } 380 return nil, errors.Wrap(err2, "Failed parsing EC private key") 381 } 382 383 //GetRSAPrivateKey get *rsa.PrivateKey from key pem 384 func GetRSAPrivateKey(raw []byte) (*rsa.PrivateKey, error) { 385 decoded, _ := pem.Decode(raw) 386 if decoded == nil { 387 return nil, errors.New("Failed to decode the PEM-encoded RSA key") 388 } 389 RSAprivKey, err := x509.ParsePKCS1PrivateKey(decoded.Bytes) 390 if err == nil { 391 return RSAprivKey, nil 392 } 393 key, err2 := x509.ParsePKCS8PrivateKey(decoded.Bytes) 394 if err2 == nil { 395 switch key.(type) { 396 case *ecdsa.PrivateKey: 397 return nil, errors.New("Expecting RSA private key but found EC private key") 398 case *rsa.PrivateKey: 399 return key.(*rsa.PrivateKey), nil 400 default: 401 return nil, errors.New("Invalid private key type in PKCS#8 wrapping") 402 } 403 } 404 return nil, errors.Wrap(err, "Failed parsing RSA private key") 405 } 406 407 // B64Encode base64 encodes bytes 408 func B64Encode(buf []byte) string { 409 return base64.StdEncoding.EncodeToString(buf) 410 } 411 412 // B64Decode base64 decodes a string 413 func B64Decode(str string) (buf []byte, err error) { 414 return base64.StdEncoding.DecodeString(str) 415 } 416 417 // StrContained returns true if 'str' is in 'strs'; otherwise return false 418 func StrContained(str string, strs []string) bool { 419 for _, s := range strs { 420 if strings.ToLower(s) == strings.ToLower(str) { 421 return true 422 } 423 } 424 return false 425 } 426 427 // IsSubsetOf returns an error if there is something in 'small' that 428 // is not in 'big'. Both small and big are assumed to be comma-separated 429 // strings. All string comparisons are case-insensitive. 430 // Examples: 431 // 1) IsSubsetOf('a,B', 'A,B,C') returns nil 432 // 2) IsSubsetOf('A,B,C', 'B,C') returns an error because A is not in the 2nd set. 433 func IsSubsetOf(small, big string) error { 434 bigSet := strings.Split(big, ",") 435 smallSet := strings.Split(small, ",") 436 for _, s := range smallSet { 437 if s != "" && !StrContained(s, bigSet) { 438 return errors.Errorf("'%s' is not a member of '%s'", s, big) 439 } 440 } 441 return nil 442 } 443 444 // HTTPRequestToString returns a string for an HTTP request for debuggging 445 func HTTPRequestToString(req *http.Request) string { 446 body, _ := ioutil.ReadAll(req.Body) 447 req.Body = ioutil.NopCloser(bytes.NewReader(body)) 448 return fmt.Sprintf("%s %s\n%s", 449 req.Method, req.URL, string(body)) 450 } 451 452 // HTTPResponseToString returns a string for an HTTP response for debuggging 453 func HTTPResponseToString(resp *http.Response) string { 454 body, _ := ioutil.ReadAll(resp.Body) 455 resp.Body = ioutil.NopCloser(bytes.NewReader(body)) 456 return fmt.Sprintf("statusCode=%d (%s)\n%s", 457 resp.StatusCode, resp.Status, string(body)) 458 } 459 460 // CreateClientHome will create a home directory if it does not exist 461 func CreateClientHome() (string, error) { 462 log.Debug("CreateHome") 463 home := filepath.Dir(GetDefaultConfigFile("fabric-ca-client")) 464 465 if _, err := os.Stat(home); err != nil { 466 if os.IsNotExist(err) { 467 err := os.MkdirAll(home, 0755) 468 if err != nil { 469 return "", err 470 } 471 } 472 } 473 return home, nil 474 } 475 476 // GetDefaultConfigFile gets the default path for the config file to display in usage message 477 func GetDefaultConfigFile(cmdName string) string { 478 if cmdName == "fabric-ca-server" { 479 var fname = fmt.Sprintf("%s-config.yaml", cmdName) 480 // First check home env variables 481 home := "." 482 envs := []string{"FABRIC_CA_SERVER_HOME", "FABRIC_CA_HOME", "CA_CFG_PATH"} 483 for _, env := range envs { 484 envVal := os.Getenv(env) 485 if envVal != "" { 486 home = envVal 487 break 488 } 489 } 490 return path.Join(home, fname) 491 } 492 493 var fname = fmt.Sprintf("%s-config.yaml", cmdName) 494 // First check home env variables 495 var home string 496 envs := []string{"FABRIC_CA_CLIENT_HOME", "FABRIC_CA_HOME", "CA_CFG_PATH"} 497 for _, env := range envs { 498 envVal := os.Getenv(env) 499 if envVal != "" { 500 home = envVal 501 return path.Join(home, fname) 502 } 503 } 504 505 return path.Join(os.Getenv("HOME"), ".fabric-ca-client", fname) 506 } 507 508 // GetX509CertificateFromPEMFile gets an X509 certificate from a file 509 func GetX509CertificateFromPEMFile(file string) (*x509.Certificate, error) { 510 pemBytes, err := ReadFile(file) 511 if err != nil { 512 return nil, err 513 } 514 x509Cert, err := GetX509CertificateFromPEM(pemBytes) 515 if err != nil { 516 return nil, errors.Wrapf(err, "Invalid certificate in '%s'", file) 517 } 518 return x509Cert, nil 519 } 520 521 // GetX509CertificateFromPEM get an X509 certificate from bytes in PEM format 522 func GetX509CertificateFromPEM(cert []byte) (*x509.Certificate, error) { 523 block, _ := pem.Decode(cert) 524 if block == nil { 525 return nil, errors.New("Failed to PEM decode certificate") 526 } 527 x509Cert, err := gmx509.ParseCertificate(block.Bytes) 528 if err != nil { 529 return nil, errors.Wrap(err, "Error parsing certificate") 530 } 531 return x509Cert, nil 532 } 533 534 // GetX509CertificatesFromPEM returns X509 certificates from bytes in PEM format 535 func GetX509CertificatesFromPEM(pemBytes []byte) ([]*x509.Certificate, error) { 536 chain := pemBytes 537 var certs []*x509.Certificate 538 for len(chain) > 0 { 539 var block *pem.Block 540 block, chain = pem.Decode(chain) 541 if block == nil { 542 break 543 } 544 545 cert, err := gmx509.ParseCertificate(block.Bytes) 546 if err != nil { 547 return nil, errors.Wrap(err, "Error parsing certificate") 548 } 549 certs = append(certs, cert) 550 } 551 return certs, nil 552 } 553 554 // GetCertificateDurationFromFile returns the validity duration for a certificate 555 // in a file. 556 func GetCertificateDurationFromFile(file string) (time.Duration, error) { 557 cert, err := GetX509CertificateFromPEMFile(file) 558 if err != nil { 559 return 0, err 560 } 561 return GetCertificateDuration(cert), nil 562 } 563 564 // GetCertificateDuration returns the validity duration for a certificate 565 func GetCertificateDuration(cert *x509.Certificate) time.Duration { 566 return cert.NotAfter.Sub(cert.NotBefore) 567 } 568 569 // GetEnrollmentIDFromPEM returns the EnrollmentID from a PEM buffer 570 func GetEnrollmentIDFromPEM(cert []byte) (string, error) { 571 x509Cert, err := GetX509CertificateFromPEM(cert) 572 if err != nil { 573 return "", err 574 } 575 return GetEnrollmentIDFromX509Certificate(x509Cert), nil 576 } 577 578 // GetEnrollmentIDFromX509Certificate returns the EnrollmentID from the X509 certificate 579 func GetEnrollmentIDFromX509Certificate(cert *x509.Certificate) string { 580 return cert.Subject.CommonName 581 } 582 583 // MakeFileAbs makes 'file' absolute relative to 'dir' if not already absolute 584 func MakeFileAbs(file, dir string) (string, error) { 585 if file == "" { 586 return "", nil 587 } 588 if filepath.IsAbs(file) { 589 return file, nil 590 } 591 path, err := filepath.Abs(filepath.Join(dir, file)) 592 if err != nil { 593 return "", errors.Wrapf(err, "Failed making '%s' absolute based on '%s'", file, dir) 594 } 595 return path, nil 596 } 597 598 // MakeFileNamesAbsolute makes all file names in the list absolute, relative to home 599 func MakeFileNamesAbsolute(files []*string, home string) error { 600 for _, filePtr := range files { 601 abs, err := MakeFileAbs(*filePtr, home) 602 if err != nil { 603 return err 604 } 605 *filePtr = abs 606 } 607 return nil 608 } 609 610 // Fatal logs a fatal message and exits 611 func Fatal(format string, v ...interface{}) { 612 log.Fatalf(format, v...) 613 os.Exit(1) 614 } 615 616 // GetUser returns username and password from CLI input 617 func GetUser(v *viper.Viper) (string, string, error) { 618 var fabricCAServerURL string 619 fabricCAServerURL = v.GetString("url") 620 621 URL, err := url.Parse(fabricCAServerURL) 622 if err != nil { 623 return "", "", err 624 } 625 626 user := URL.User 627 if user == nil { 628 return "", "", errors.New("No username and password provided as part of the Fabric CA server URL") 629 } 630 631 eid := user.Username() 632 if eid == "" { 633 return "", "", errors.New("No username provided as part of URL") 634 } 635 636 pass, _ := user.Password() 637 if pass == "" { 638 return "", "", errors.New("No password provided as part of URL") 639 } 640 641 return eid, pass, nil 642 } 643 644 // GetSerialAsHex returns the serial number from certificate as hex format 645 func GetSerialAsHex(serial *big.Int) string { 646 hex := fmt.Sprintf("%x", serial) 647 return hex 648 } 649 650 // StructToString converts a struct to a string. If a field 651 // has a 'secret' tag, it is masked in the returned string 652 func StructToString(si interface{}) string { 653 rval := reflect.ValueOf(si).Elem() 654 tipe := rval.Type() 655 var buffer bytes.Buffer 656 buffer.WriteString("{ ") 657 for i := 0; i < rval.NumField(); i++ { 658 tf := tipe.Field(i) 659 if !rval.FieldByName(tf.Name).CanSet() { 660 continue // skip unexported fields 661 } 662 var fStr string 663 tagv := tf.Tag.Get(SecretTag) 664 if tagv == "password" || tagv == "username" { 665 fStr = fmt.Sprintf("%s:**** ", tf.Name) 666 } else if tagv == "url" { 667 val, ok := rval.Field(i).Interface().(string) 668 if ok { 669 val = GetMaskedURL(val) 670 fStr = fmt.Sprintf("%s:%v ", tf.Name, val) 671 } else { 672 fStr = fmt.Sprintf("%s:%v ", tf.Name, rval.Field(i).Interface()) 673 } 674 } else { 675 fStr = fmt.Sprintf("%s:%v ", tf.Name, rval.Field(i).Interface()) 676 } 677 buffer.WriteString(fStr) 678 } 679 buffer.WriteString(" }") 680 return buffer.String() 681 } 682 683 // GetMaskedURL returns masked URL. It masks username and password from the URL 684 // if present 685 func GetMaskedURL(url string) string { 686 matches := URLRegex.FindStringSubmatch(url) 687 688 // If there is a match, there should be four entries: 1 for 689 // the match and 3 for submatches 690 if len(matches) == 4 { 691 matchIdxs := URLRegex.FindStringSubmatchIndex(url) 692 matchStr := url[matchIdxs[0]:matchIdxs[1]] 693 for idx := 2; idx < len(matches); idx++ { 694 if matches[idx] != "" { 695 matchStr = strings.Replace(matchStr, matches[idx], "****", 1) 696 } 697 } 698 url = url[:matchIdxs[0]] + matchStr + url[matchIdxs[1]:len(url)] 699 } 700 return url 701 } 702 703 // NormalizeStringSlice checks for seperators 704 func NormalizeStringSlice(slice []string) []string { 705 var normalizedSlice []string 706 707 for _, item := range slice { 708 // Remove surrounding brackets "[]" if specified 709 if strings.HasPrefix(item, "[") && strings.HasSuffix(item, "]") { 710 item = item[1 : len(item)-1] 711 } 712 // Split elements based on comma and add to normalized slice 713 elems := strings.Split(item, ",") 714 for _, elem := range elems { 715 normalizedSlice = append(normalizedSlice, strings.TrimSpace(elem)) 716 } 717 } 718 return normalizedSlice 719 } 720 721 // NormalizeFileList provides absolute pathing for the list of files 722 func NormalizeFileList(files []string, homeDir string) ([]string, error) { 723 var err error 724 725 files = NormalizeStringSlice(files) 726 727 for i, file := range files { 728 files[i], err = MakeFileAbs(file, homeDir) 729 if err != nil { 730 return nil, err 731 } 732 } 733 734 return files, nil 735 } 736 737 // CheckHostsInCert checks to see if host correctly inserted into certificate 738 func CheckHostsInCert(certFile string, hosts ...string) error { 739 certBytes, err := ioutil.ReadFile(certFile) 740 if err != nil { 741 return errors.Wrapf(err, "Failed to read certificate file at '%s'", certFile) 742 } 743 744 cert, err := GetX509CertificateFromPEM(certBytes) 745 if err != nil { 746 return errors.Wrap(err, "Failed to get certificate") 747 } 748 749 // combine DNSNames and IPAddresses from cert 750 sans := cert.DNSNames 751 for _, ip := range cert.IPAddresses { 752 sans = append(sans, ip.String()) 753 } 754 for _, host := range hosts { 755 if !containsString(sans, host) { 756 return errors.Errorf("Host '%s' was not found in the certificate in file '%s'", host, certFile) 757 } 758 } 759 return nil 760 } 761 762 func containsString(list []string, item string) bool { 763 for _, elem := range list { 764 if elem == item { 765 return true 766 } 767 } 768 return false 769 } 770 771 // Read reads from Reader into a byte array 772 func Read(r io.Reader, data []byte) ([]byte, error) { 773 j := 0 774 for { 775 n, err := r.Read(data[j:]) 776 j = j + n 777 if err != nil { 778 if err == io.EOF { 779 break 780 } 781 return nil, errors.Wrapf(err, "Read failure") 782 } 783 784 if (n == 0 && j == len(data)) || j > len(data) { 785 return nil, errors.New("Size of requested data is too large") 786 } 787 } 788 789 return data[:j], nil 790 } 791 792 // Hostname name returns the hostname of the machine 793 func Hostname() string { 794 hostname, _ := os.Hostname() 795 if hostname == "" { 796 hostname = "localhost" 797 } 798 return hostname 799 } 800 801 // ValidateAndReturnAbsConf checks to see that there are no conflicts between the 802 // configuration file path and home directory. If no conflicts, returns back the absolute 803 // path for the configuration file and home directory. 804 func ValidateAndReturnAbsConf(configFilePath, homeDir, cmdName string) (string, string, error) { 805 var err error 806 var homeDirSet bool 807 var configFileSet bool 808 809 defaultConfig := GetDefaultConfigFile(cmdName) // Get the default configuration 810 811 if configFilePath == "" { 812 configFilePath = defaultConfig // If no config file path specified, use the default configuration file 813 } else { 814 configFileSet = true 815 } 816 817 if homeDir == "" { 818 homeDir = filepath.Dir(defaultConfig) // If no home directory specified, use the default directory 819 } else { 820 homeDirSet = true 821 } 822 823 // Make the home directory absolute 824 homeDir, err = filepath.Abs(homeDir) 825 if err != nil { 826 return "", "", errors.Wrap(err, "Failed to get full path of config file") 827 } 828 homeDir = strings.TrimRight(homeDir, "/") 829 830 if configFileSet && homeDirSet { 831 log.Warning("Using both --config and --home CLI flags; --config will take precedence") 832 } 833 834 if configFileSet { 835 configFilePath, err = filepath.Abs(configFilePath) 836 if err != nil { 837 return "", "", errors.Wrap(err, "Failed to get full path of configuration file") 838 } 839 return configFilePath, filepath.Dir(configFilePath), nil 840 } 841 842 configFile := filepath.Join(homeDir, filepath.Base(defaultConfig)) // Join specified home directory with default config file name 843 return configFile, homeDir, nil 844 } 845 846 // GetSliceFromList will return a slice from a list 847 func GetSliceFromList(split string, delim string) []string { 848 return strings.Split(strings.Replace(split, " ", "", -1), delim) 849 } 850 851 // ListContains looks through a comma separated list to see if a string exists 852 func ListContains(list, find string) bool { 853 items := strings.Split(list, ",") 854 for _, item := range items { 855 item = strings.TrimPrefix(item, " ") 856 if item == find { 857 return true 858 } 859 } 860 return false 861 } 862 863 //TODO: move these out of production code 864 865 // FatalError will check to see if an error occured if so it will cause the test cases exit 866 func FatalError(t *testing.T, err error, msg string, args ...interface{}) { 867 if len(args) > 0 { 868 msg = fmt.Sprintf(msg, args) 869 } 870 if !assert.NoError(t, err, msg) { 871 t.Fatal(msg) 872 } 873 } 874 875 // ErrorContains will check to see if an error occurred, if so it will check that it contains 876 // the appropriate error message 877 func ErrorContains(t *testing.T, err error, contains, msg string, args ...interface{}) { 878 if len(args) > 0 { 879 msg = fmt.Sprintf(msg, args) 880 } 881 if assert.Error(t, err, msg) { 882 assert.Contains(t, err.Error(), contains) 883 } 884 }