github.com/htcondor/osdf-client/v6@v6.13.0-rc1.0.20231009141709-766e7b4d1dc8/get_stashserver_caches.go (about) 1 package stashcp 2 3 import ( 4 "bytes" 5 "crypto/rsa" 6 "crypto/sha1" 7 "crypto/x509" 8 _ "embed" 9 "encoding/hex" 10 "encoding/pem" 11 "errors" 12 "os" 13 "path" 14 "path/filepath" 15 "strings" 16 17 log "github.com/sirupsen/logrus" 18 ) 19 20 //go:embed resources/opensciencegrid.org.pub 21 var osgpubkey []byte 22 23 func get_stashservers_caches(responselines_b [][]byte) (map[string][]string, error) { 24 25 /** 26 After the geo order of the selected server list on line zero, 27 the rest of the response is in .cvmfswhitelist format. 28 This is done to avoid using https for every request on the 29 wlcg-wpad servers and takes advantage of conveniently 30 existing infrastructure. 31 The format contains the following lines: 32 1. Creation date stamp, e.g. 20200414170005. For debugging 33 only. 34 2. Expiration date stamp, e.g. E20200421170005. cvmfs clients 35 check this to avoid replay attacks, but for this api that 36 is not much of a risk so it is ignored. 37 3. "Repository" name, e.g. Nstash-servers. cvmfs clients 38 also check this but it is not important here. 39 4. With cvmfs the 4th line has a repository fingerprint, but 40 for this api it instead contains a semi-colon separated list 41 of named server lists. Each server list is of the form 42 name=servers where servers is comma-separated. Ends with 43 "hash=-sha1" because cvmfs_server expects the hash name 44 to be there. e.g. 45 xroot=stashcache.t2.ucsd.edu,sg-gftp.pace.gatech.edu;xroots=xrootd-local.unl.edu,stashcache.t2.ucsd.edu;hash=-sha1 46 5. A two-dash separator, i.e "--" 47 6. The sha1 hash of lines 1 through 4. 48 7. The signature, i.e. an RSA encryption of the hash that can 49 be decrypted by the OSG cvmfs public key. Contains binary 50 information so it may contain a variable number of newlines 51 which would have caused it to have been split into multiple 52 response "lines". 53 **/ 54 55 if len(responselines_b) < 8 { 56 57 log.Errorln("stashservers response too short, less than 8 lines:", len(responselines_b)) 58 return nil, errors.New("stashservers response too short, less than 8 lines") 59 } 60 61 // Get the 5th row (4th index), the last 5 characters 62 hashname_b := string(responselines_b[4][len(responselines_b[4])-5:]) 63 64 if hashname_b != "-sha1" { 65 66 log.Error("stashservers response does not have sha1 hash:", string(hashname_b)) 67 return nil, errors.New("stashservers response does not have sha1 hash") 68 } 69 70 sha1Hash := sha1.New() 71 sha1Hash.Write(bytes.Join(responselines_b[1:5], []byte("\n"))) 72 sha1Hash.Write([]byte("\n")) 73 hashed := sha1Hash.Sum(nil) 74 hashStr := hex.EncodeToString(hashed) 75 76 log.Debugln("Hashed:", hashStr, "From CVMFS:", string(responselines_b[6])) 77 if string(responselines_b[6]) != hashStr { 78 log.Debugln("stashservers hash", string(responselines_b[6]), "does not match expected hash ", hashname_b) 79 log.Debugln("hashed text:\n", string(hashname_b)) 80 log.Errorln("stashservers response hash does not match expected hash") 81 return nil, errors.New("stashservers response hash does not match expected hash") 82 } 83 84 var pubKey *rsa.PublicKey 85 var err error 86 if pubKey, err = readPublicKey(); err != nil { 87 // The signature check isn't critical to be done everywhere; 88 // any tampering will likely to be caught somewhere and 89 // investigated. 90 log.Warnln("Public Key not found, will not verify caches") 91 } else { 92 sig := bytes.Join(responselines_b[7:], []byte("\n")) 93 err = rsa.VerifyPKCS1v15(pubKey, 0, []byte(hashStr), sig) 94 if err != nil { 95 log.Errorln("Error from public key verification of cache list:", err) 96 //return nil, err 97 } else { 98 log.Debugln("Signature Matched") 99 } 100 101 } 102 103 // Split the caches by type (xroot, xroots) in the returned by the GeoIP service 104 var toReturn = make(map[string][]string) 105 log.Debugf("Cache list: %s", string(responselines_b[4])) 106 for _, transferType := range strings.Split(string(responselines_b[4]), ";") { 107 splitType := strings.Split(transferType, "=") 108 toReturn[splitType[0]] = strings.Split(splitType[1], ",") 109 } 110 111 return toReturn, nil 112 113 } 114 115 func getKeyLocation() string { 116 osgpub := "opensciencegrid.org.pub" 117 var checkedLocation string = path.Join("/etc/cvmfs/keys/opensciencegrid.org/", osgpub) 118 if _, err := os.Stat(checkedLocation); err == nil { 119 return checkedLocation 120 } 121 prefix := os.Getenv("OSG_LOCATION") 122 if prefix != "" { 123 checkedLocation = path.Join(prefix, "etc/stashcache", osgpub) 124 if _, err := os.Stat(checkedLocation); err == nil { 125 return checkedLocation 126 } 127 checkedLocation = path.Join(prefix, "usr/share/stashcache", osgpub) 128 if _, err := os.Stat(checkedLocation); err == nil { 129 return checkedLocation 130 } 131 132 } 133 134 // Try the current directory 135 checkedLocation, _ = filepath.Abs(osgpub) 136 if _, err := os.Stat(checkedLocation); err == nil { 137 return checkedLocation 138 } 139 140 return "" 141 142 } 143 144 // Largely adapted from https://gist.github.com/jshap70/259a87a7146393aab5819873a193b88c 145 func readPublicKey() (*rsa.PublicKey, error) { 146 var err error 147 publicKeyPath := getKeyLocation() 148 var pubkeyContents []byte 149 if publicKeyPath == "" { 150 pubkeyContents = osgpubkey 151 } else { 152 var err error 153 pubkeyContents, err = os.ReadFile(publicKeyPath) 154 if err != nil { 155 log.Errorln("Error reading public key:", err) 156 return nil, err 157 } 158 } 159 160 pubPem, rest := pem.Decode(pubkeyContents) 161 if pubPem.Type != "PUBLIC KEY" { 162 log.WithFields(log.Fields{"PEM Type": pubPem.Type}).Error("RSA public key is of the wrong type") 163 return nil, errors.New("RSA public key is of the wrong type") 164 } 165 var parsedKey interface{} 166 if parsedKey, err = x509.ParsePKIXPublicKey(pubPem.Bytes); err != nil { 167 log.Errorln("Unable to parse RSA public key:", err) 168 return nil, errors.New("Unable to parse RSA public key") 169 } 170 log.Debugf("Got a %T, with remaining data: %q", parsedKey, rest) 171 172 var pubKey *rsa.PublicKey 173 var ok bool 174 if pubKey, ok = parsedKey.(*rsa.PublicKey); !ok { 175 log.Errorln("Failed to convert RSA public key") 176 } 177 178 return pubKey, nil 179 180 }