def sign(self, sigstr): assert isinstance(sigstr, str) # for now, not unicode. Python 3? key_info = self._get_key_info() log.debug("sign %r with %s key (algo %s, fp %s)", sigstr, key_info["type"], key_info["algorithm"], key_info["fingerprint"]) if key_info["type"] == "agent": response = key_info["agent_key"].sign_ssh_data(None, sigstr) signed_raw = signature_from_agent_sign_response(response) signed = base64.b64encode(signed_raw) elif key_info["type"] == "ssh_key": hash_algo = key_info["algorithm"].split('-')[1] hash_class = { "sha1": SHA, "sha256": SHA256, "sha512": SHA512 }[hash_algo] hasher = hash_class.new() hasher.update(sigstr) signed_raw = key_info["signer"].sign(hasher) signed = base64.b64encode(signed_raw) else: raise MantaError("internal error: unknown key type: %r" % key_info["type"]) return (key_info["algorithm"], key_info["fingerprint"], signed)
def sign(self, sigstr): if not isinstance(sigstr, bytes): assert isinstance(sigstr, str) sigstr = sigstr.encode("utf-8") key_info = self._get_key_info() log.debug("sign %r with %s key (algo %s, fp %s)", sigstr, key_info["type"], key_info["algorithm"], key_info["fingerprint"]) if key_info["type"] == "agent": response = key_info["agent_key"].sign_ssh_data(sigstr) if re.search(r'^ecdsa-', key_info['algorithm']): signed_raw = ecdsa_sig_from_agent_signed_response(response) else: signed_raw = rsa_sig_from_agent_signed_response(response) signed = base64.b64encode(signed_raw) elif key_info["type"] == "ssh_key": signed = ssh_key_sign(key_info, sigstr) else: raise MantaError("internal error: unknown key type: %r" % key_info["type"]) return (key_info["algorithm"], key_info["fingerprint"], signed)
def _get_key_info(self): """Get key info appropriate for signing: either from the ssh agent or from a private key. """ if self._key_info_cache is not None: return self._key_info_cache errors = [] # First try the agent. try: key_info = agent_key_info_from_key_id(self.key_id) except MantaError: _, ex, _ = sys.exc_info() errors.append(ex) else: self._key_info_cache = key_info return self._key_info_cache # Try loading from "~/.ssh/*". try: key_info = ssh_key_info_from_key_data(self.key_id) except MantaError: _, ex, _ = sys.exc_info() errors.append(ex) else: self._key_info_cache = key_info return self._key_info_cache raise MantaError("could not find key info for signing: %s" % "; ".join(map(unicode, errors)))
def ssh_key_info_from_key_data(key_id, priv_key=None): """Get/load SSH key info necessary for signing. @param key_id {str} Either a private ssh key fingerprint, e.g. 'b3:f0:a1:6c:18:3b:42:63:fd:6e:57:42:74:17:d4:bc', or the path to an ssh private key file (like ssh's IdentityFile config option). @param priv_key {str} Optional. SSH private key file data (PEM format). @return {dict} with these keys: - type: "agent" - signer: Crypto signer class (a PKCS#1 v1.5 signer for RSA keys) - fingerprint: key fingerprint - algorithm: 'rsa-sha256' DSA not current supported. Hash algorithm selection is not exposed. - ... some others added by `load_ssh_key()` """ if FINGERPRINT_RE.match(key_id) and priv_key: key_info = { "fingerprint": key_id, "priv_key": priv_key } else: # Otherwise, we attempt to load necessary details from ~/.ssh. key_info = load_ssh_key(key_id) # Load an RSA key signer. key = None try: key = RSA.importKey(key_info["priv_key"]) except ValueError: if "priv_key_path" in key_info: prompt = "Passphrase [%s]: " % key_info["priv_key_path"] else: prompt = "Passphrase: " for i in range(3): passphrase = getpass(prompt) if not passphrase: break try: key = RSA.importKey(key_info["priv_key"], passphrase) except ValueError: continue else: break if not key: details = "" if "priv_key_path" in key_info: details = " (%s)" % key_info["priv_key_path"] raise MantaError("could not import key" + details) key_info["signer"] = PKCS1_v1_5.new(key) key_info["type"] = "ssh_key" key_info["algorithm"] = "rsa-sha256" return key_info
def ssh_key_info_from_key_data(key_id, priv_key=None): """Get/load SSH key info necessary for signing. @param key_id {str} Either a private ssh key fingerprint, e.g. 'b3:f0:a1:6c:18:3b:42:63:fd:6e:57:42:74:17:d4:bc', or the path to an ssh private key file (like ssh's IdentityFile config option). @param priv_key {str} Optional. SSH private key file data (PEM format). @return {dict} with these keys: - type: "agent" - signer: Crypto signer class (a PKCS#1 v1.5 signer for RSA keys) - fingerprint: key md5 fingerprint - algorithm: See ALGO_FROM_SSH_KEY_TYPE for supported list. - ... some others added by `load_ssh_key()` """ if FINGERPRINT_RE.match(key_id) and priv_key: key_info = {"fingerprint": key_id, "priv_key": priv_key} else: # Otherwise, we attempt to load necessary details from ~/.ssh. key_info = load_ssh_key(key_id) # Load a key signer. key = None try: key = serialization.load_pem_private_key(key_info["priv_key"], password=None, backend=default_backend()) except TypeError, ex: log.debug( "could not import key without passphrase (will " "try with passphrase): %s", ex) if "priv_key_path" in key_info: prompt = "Passphrase [%s]: " % key_info["priv_key_path"] else: prompt = "Passphrase: " for i in range(3): passphrase = getpass(prompt) if not passphrase: break try: key = serialization.load_pem_private_key( key_info["priv_key"], password=passphrase, backend=default_backend()) except ValueError: continue else: break if not key: details = "" if "priv_key_path" in key_info: details = " (%s)" % key_info["priv_key_path"] raise MantaError("could not import key" + details)
def agent_key_info_from_key_id(key_id): """Find a matching key in the ssh-agent. @param key_id {str} Either a private ssh key fingerprint, e.g. 'b3:f0:a1:6c:18:3b:42:63:fd:6e:57:42:74:17:d4:bc', or the path to an ssh private key file (like ssh's IdentityFile config option). @return {dict} with these keys: - type: "agent" - agent_key: paramiko AgentKey - fingerprint: key fingerprint - algorithm: "rsa-sha1" Currently don't support DSA agent signing. """ # Need the fingerprint of the key we're using for signing. If it # is a path to a priv key, then we need to load it. if not FINGERPRINT_RE.match(key_id): ssh_key = load_ssh_key(key_id, True) fingerprint = ssh_key["fingerprint"] else: fingerprint = key_id # Look for a matching fingerprint in the ssh-agent keys. keys = Agent().get_keys() for key in keys: raw_key = key.blob # The MD5 fingerprint functions return the hexdigest without the hash # algorithm prefix ("MD5:"), and the SHA256 functions return the # fingerprint with the prefix ("SHA256:"). Ideally we'd want to # normalize these, but more importantly we don't want to break backwards # compatibility for either the SHA or MD5 users. md5_fp = fingerprint_from_raw_ssh_pub_key(raw_key) sha_fp = sha256_fingerprint_from_raw_ssh_pub_key(raw_key) if (sha_fp == fingerprint or md5_fp == fingerprint or "MD5:" + md5_fp == fingerprint): # Canonicalize it to the md5 fingerprint. md5_fingerprint = md5_fp break else: raise MantaError('no ssh-agent key with fingerprint "%s"' % fingerprint) return { "type": "agent", "agent_key": key, "fingerprint": md5_fingerprint, "algorithm": ALGO_FROM_SSH_KEY_TYPE[key.name] }
def agent_key_info_from_key_id(key_id): """Find a matching key in the ssh-agent. @param key_id {str} Either a private ssh key fingerprint, e.g. 'b3:f0:a1:6c:18:3b:42:63:fd:6e:57:42:74:17:d4:bc', or the path to an ssh private key file (like ssh's IdentityFile config option). @return {dict} with these keys: - type: "agent" - agent_key: paramiko AgentKey - fingerprint: key fingerprint - algorithm: "rsa-sha1" Currently don't support DSA agent signing. """ # Need the fingerprint of the key we're using for signing. If it # is a path to a priv key, then we need to load it. if not FINGERPRINT_RE.match(key_id): ssh_key = load_ssh_key(key_id, True) fingerprint = ssh_key["fingerprint"] else: fingerprint = key_id # Look for a matching fingerprint in the ssh-agent keys. import paramiko keys = paramiko.Agent().get_keys() for key in keys: raw_key = str(key) if sha256_fingerprint_from_raw_ssh_pub_key(raw_key) == fingerprint: # Canonicalize it to the md5 fingerprint. md5_fingerprint = fingerprint_from_raw_ssh_pub_key(raw_key) break elif fingerprint_from_raw_ssh_pub_key(raw_key) == fingerprint: md5_fingerprint = fingerprint break else: raise MantaError('no ssh-agent key with fingerprint "%s"' % fingerprint) # TODO:XXX DSA support possible with paramiko? algorithm = 'rsa-sha1' return { "type": "agent", "agent_key": key, "fingerprint": md5_fingerprint, "algorithm": algorithm }
def load_ssh_key(key_id, skip_priv_key=False): """ Load a local ssh private key (in PEM format). PEM format is the OpenSSH default format for private keys. See similar code in imgapi.js#loadSSHKey. @param key_id {str} An ssh public key fingerprint or ssh private key path. @param skip_priv_key {boolean} Optional. Default false. If true, then this will skip loading the private key file and `priv_key` will be `None` in the retval. @returns {dict} with these keys: - pub_key_path - fingerprint - priv_key_path - priv_key """ priv_key = None # If `key_id` is already a private key path, then easy. if not FINGERPRINT_RE.match(key_id): if not skip_priv_key: f = open(key_id) try: priv_key = f.read() finally: f.close() pub_key_path = key_id + '.pub' f = open(pub_key_path) try: pub_key = f.read() finally: f.close() fingerprint = fingerprint_from_ssh_pub_key(pub_key) return dict(pub_key_path=pub_key_path, fingerprint=fingerprint, priv_key_path=key_id, priv_key=priv_key) # Else, look at all pub/priv keys in "~/.ssh" for a matching fingerprint. fingerprint = key_id pub_key_glob = expanduser('~/.ssh/*.pub') for pub_key_path in glob(pub_key_glob): f = open(pub_key_path) try: pub_key = f.read() finally: f.close() if fingerprint_from_ssh_pub_key(pub_key) == fingerprint: break else: raise MantaError("no '~/.ssh/*.pub' key found with fingerprint '%s'" % fingerprint) priv_key_path = os.path.splitext(pub_key_path)[0] if not skip_priv_key: f = open(priv_key_path) try: priv_key = f.read() finally: f.close() return dict(pub_key_path=pub_key_path, fingerprint=fingerprint, priv_key_path=priv_key_path, priv_key=priv_key)
def load_ssh_key(key_id, skip_priv_key=False): """ Load a local ssh private key (in PEM format). PEM format is the OpenSSH default format for private keys. See similar code in imgapi.js#loadSSHKey. @param key_id {str} An ssh public key fingerprint or ssh private key path. @param skip_priv_key {boolean} Optional. Default false. If true, then this will skip loading the private key file and `priv_key` will be `None` in the retval. @returns {dict} with these keys: - pub_key_path - fingerprint - priv_key_path - priv_key - algorithm """ priv_key = None # If `key_id` is already a private key path, then easy. if not FINGERPRINT_RE.match(key_id): if not skip_priv_key: f = io.open(key_id, 'rb') try: priv_key = f.read() finally: f.close() pub_key_path = key_id + '.pub' f = io.open(pub_key_path, 'r') try: pub_key = f.read() finally: f.close() fingerprint = fingerprint_from_ssh_pub_key(pub_key) # XXX: pubkey should NOT be in PEM format. try: algo = ALGO_FROM_SSH_KEY_TYPE[pub_key.split()[0]] except KeyError: raise MantaError("Unsupported key type for: {}".format(key_id)) return dict(pub_key_path=pub_key_path, fingerprint=fingerprint, priv_key_path=key_id, priv_key=priv_key, algorithm=algo) # Else, look at all pub/priv keys in "~/.ssh" for a matching fingerprint. fingerprint = key_id pub_key_glob = expanduser('~/.ssh/*.pub') pub_key = None for pub_key_path in glob(pub_key_glob): try: f = io.open(pub_key_path, 'r') except IOError: # This can happen if the .pub file is a broken symlink. log.debug("could not open '%s', skip it", pub_key_path) continue try: pub_key = f.read() finally: f.close() # The MD5 fingerprint functions return the hexdigest without the hash # algorithm prefix ("MD5:"), and the SHA256 functions return the # fingerprint with the prefix ("SHA256:"). Ideally we'd want to # normalize these, but more importantly we don't want to break backwards # compatibility for either the SHA or MD5 users. md5_fp = fingerprint_from_ssh_pub_key(pub_key) sha256_fp = sha256_fingerprint_from_ssh_pub_key(pub_key) if (sha256_fp == fingerprint or md5_fp == fingerprint or "MD5:" + md5_fp == fingerprint): # if the user has given us sha256 fingerprint, canonicalize # it to the md5 fingerprint fingerprint = md5_fp break else: raise MantaError("no '~/.ssh/*.pub' key found with fingerprint '%s'" % fingerprint) # XXX: pubkey should NOT be in PEM format. try: algo = ALGO_FROM_SSH_KEY_TYPE[pub_key.split()[0]] except KeyError: raise MantaError("Unsupported key type for: {}".format(key_id)) priv_key_path = os.path.splitext(pub_key_path)[0] if not skip_priv_key: f = io.open(priv_key_path, 'rb') try: priv_key = f.read() finally: f.close() return dict(pub_key_path=pub_key_path, fingerprint=fingerprint, priv_key_path=priv_key_path, priv_key=priv_key, algorithm=algo)
# This can happen if the .pub file is a broken symlink. log.debug("could not open '%s', skip it", pub_key_path) continue try: pub_key = f.read() finally: f.close() if sha256_fingerprint_from_ssh_pub_key(pub_key) == fingerprint: # if the user has given us sha256 fingerprint, canonicalize # it to the md5 fingerprint fingerprint = fingerprint_from_ssh_pub_key(pub_key) break elif fingerprint_from_ssh_pub_key(pub_key) == fingerprint: break else: raise MantaError("no '~/.ssh/*.pub' key found with fingerprint '%s'" % fingerprint) priv_key_path = os.path.splitext(pub_key_path)[0] if not skip_priv_key: f = open(priv_key_path) try: priv_key = f.read() finally: f.close() return dict(pub_key_path=pub_key_path, fingerprint=fingerprint, priv_key_path=priv_key_path, priv_key=priv_key) def unpack_agent_response(d): parts = []
# The MD5 fingerprint functions return the hexdigest without the hash # algorithm prefix ("MD5:"), and the SHA256 functions return the # fingerprint with the prefix ("SHA256:"). Ideally we'd want to # normalize these, but more importantly we don't want to break backwards # compatibility for either the SHA or MD5 users. md5_fp = fingerprint_from_ssh_pub_key(pub_key) sha256_fp = sha256_fingerprint_from_ssh_pub_key(pub_key) if (sha256_fp == fingerprint or md5_fp == fingerprint or "MD5:" + md5_fp == fingerprint): # if the user has given us sha256 fingerprint, canonicalize # it to the md5 fingerprint fingerprint = md5_fp break else: raise MantaError("no '~/.ssh/*.pub' key found with fingerprint '%s'" % fingerprint) # XXX: pubkey should NOT be in PEM format. try: algo = ALGO_FROM_SSH_KEY_TYPE[pub_key.split()[0]] except KeyError: raise MantaError("Unsupported key type for: {}".format(key_id)) priv_key_path = os.path.splitext(pub_key_path)[0] if not skip_priv_key: f = open(priv_key_path) try: priv_key = f.read() finally: f.close() return dict(pub_key_path=pub_key_path,