├── LICENSE.mit ├── README.md ├── authorized_keys.py ├── doc ├── rfc4819.txt └── rfc7076.txt ├── ssh-publickeyd └── test-keys.txt /LICENSE.mit: -------------------------------------------------------------------------------- 1 | MIT License (also available at ) 2 | 3 | (c) 2010–2016 Mantas Mikulėnas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssh-publickeyd, a RFC 4819 server 2 | 3 | This is a server implementation of VanDyke's [RFC 4819][] public key management protocol for SSHv2, which lets clients upload authorized SSH keys without needing to know implementation details. In the future it might also support [RFC 7076][]. 4 | 5 | ## Configuring OpenSSH server 6 | 7 | Add the following to your `/etc/ssh/sshd_config`: 8 | 9 | Subsystem publickey /usr/local/bin/ssh-publickeyd 10 | Subsystem publickey@vandyke.com /usr/local/bin/ssh-publickeyd 11 | 12 | You'll also need [nullroute.authorized\_keys](https://github.com/grawity/code/blob/master/lib/python/nullroute/authorized_keys.py) somewhere Python can find it. Apologies for not making it a proper pip module yet. 13 | 14 | ## Writing a client 15 | 16 | publickeyd is meant to be invoked as a SSH _subsystem_, for example, using `ssh -s foo.example.com publickey` or libssh2\_channel\_subsystem() ([example](http://www.libssh2.org/examples/subsystem_netconf.html)). 17 | 18 | However, the only difference between normal commands (`ssh foo whoami`) and subsystems is that the latter have a well-known name. Otherwise they work like regular commands and speak over stdin/stdout. 19 | 20 | After connecting, speak the [RFC 4819][] protocol. Its structure follows the main SSH protocol (binary length+data packets); see [RFC 4251 §5][] for a short reference. 21 | 22 | ## Known clients 23 | 24 | - VanDyke SecureCRT (did most of the testing on this) 25 | - Bitvise Tunnelier ([apparently](https://www.bitvise.com/wug-publickey), but untested) 26 | - Multinet SSH (untested) 27 | - there is a [wishlist](http://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/subsystem-publickey.html) entry for PuTTY 28 | - no OpenSSH yet 29 | 30 | ## Known servers 31 | 32 | - VanDyke VShell 33 | - Bitvise WinSSHd 34 | - Multinet SSH 35 | - ssh-publickeyd! 36 | 37 | [RFC 4819]: https://tools.ietf.org/html/rfc4819 38 | [RFC 7076]: https://tools.ietf.org/html/rfc7076 39 | [RFC 4251]: https://tools.ietf.org/html/rfc4251 40 | [RFC 4251 §5]: https://tools.ietf.org/html/rfc4251#section-5 41 | -------------------------------------------------------------------------------- /authorized_keys.py: -------------------------------------------------------------------------------- 1 | # Parser for OpenSSH authorized_keys files 2 | # (c) 2010-2016 Mantas Mikulėnas 3 | # Released under the MIT License (dist/LICENSE.mit) 4 | # 5 | # Features: 6 | # - supports OpenSSH options (with and without value) 7 | # - supports comments with spaces 8 | # - recognizes all possible (current and future) SSHv2 key types 9 | # 10 | # Not bugs: 11 | # - doesn't attempt to parse SSHv1 keys 12 | # 13 | # Test case: 14 | # ssh-foo="echo \"Here's ssh-rsa for you\"" future-algo AAAAC2Z1dHVyZS1hbGdv X y z. 15 | 16 | import base64 17 | import hashlib 18 | import struct 19 | 20 | class PublicKeyOptions(list): 21 | def __str__(self): 22 | o = [] 23 | for k, v in self: 24 | if v is True: 25 | o.append(k) 26 | else: 27 | o.append("%s=%s" % (k, v)) 28 | return ",".join(o) 29 | 30 | @classmethod 31 | def parse(klass, text): 32 | STATE_KEY = 0 33 | STATE_VALUE = 1 34 | STATE_VALUE_DQUOTE = 2 35 | STATE_VALUE_DQUOTE_ESCAPE = 3 36 | 37 | keys = [] 38 | values = [] 39 | current = "" 40 | state = STATE_KEY 41 | 42 | for char in text: 43 | if state == STATE_KEY: 44 | if char == ",": 45 | keys.append(current) 46 | values.append(True) 47 | current = "" 48 | elif char == "=": 49 | keys.append(current) 50 | current = "" 51 | state = STATE_VALUE 52 | else: 53 | current += char 54 | elif state == STATE_VALUE: 55 | if char == ",": 56 | values.append(current) 57 | current = "" 58 | state = STATE_KEY 59 | elif char == "\"": 60 | current += char 61 | state = STATE_VALUE_DQUOTE 62 | else: 63 | current += char 64 | elif state == STATE_VALUE_DQUOTE: 65 | if char == "\"": 66 | current += char 67 | state = STATE_VALUE 68 | elif char == "\\": 69 | current += char 70 | state = STATE_VALUE_DQUOTE_ESCAPE 71 | else: 72 | current += char 73 | elif state == STATE_VALUE_DQUOTE_ESCAPE: 74 | current += char 75 | state = STATE_VALUE_DQUOTE 76 | 77 | if current: 78 | if state == STATE_KEY: 79 | keys.append(current) 80 | values.append(True) 81 | else: 82 | values.append(current) 83 | 84 | return klass(zip(keys, values)) 85 | 86 | class PublicKey(object): 87 | def __init__(self, line=None, host_prefix=False): 88 | if line: 89 | tokens = self.parse(line) 90 | else: 91 | tokens = ("", None, None, None) 92 | 93 | self.prefix, self.algo, self.blob, self.comment = tokens 94 | 95 | if host_prefix: 96 | # expect known_hosts format 97 | self.hosts = self.prefix.split(",") 98 | else: 99 | # expect authorized_keys format 100 | self.options = PublicKeyOptions.parse(self.prefix) 101 | 102 | def __repr__(self): 103 | return "" % \ 104 | (self.prefix, self.algo, self.comment) 105 | 106 | def __str__(self): 107 | options = self.options 108 | blob = base64.b64encode(self.blob).decode("utf-8") 109 | comment = self.comment 110 | k = [self.algo, blob] 111 | if len(options): 112 | k.insert(0, str(options)) 113 | if len(comment): 114 | k.append(comment) 115 | return " ".join(k) 116 | 117 | def fingerprint(self, alg=None, hex=False): 118 | if alg is None: 119 | alg = hashlib.md5 120 | m = alg() 121 | m.update(self.blob) 122 | return m.hexdigest() if hex else m.digest() 123 | 124 | @classmethod 125 | def parse(self, line): 126 | STATE_NORMAL = 0 127 | STATE_DQUOTE = 1 128 | STATE_DQUOTE_ESCAPE = 2 129 | 130 | tokens = [] 131 | 132 | # Split into space-separated tokens, taking into account quoted spaces 133 | # in the OpenSSH 'options' prefix. 134 | 135 | current = "" 136 | state = STATE_NORMAL 137 | for char in line: 138 | if state == STATE_NORMAL: 139 | if char in " \t": 140 | tokens.append(current) 141 | current = "" 142 | elif char == "\"": 143 | current += char 144 | state = STATE_DQUOTE 145 | else: 146 | current += char 147 | elif state == STATE_DQUOTE: 148 | if char == "\"": 149 | current += char 150 | state = STATE_NORMAL 151 | elif char == "\\": 152 | current += char 153 | state = STATE_DQUOTE_ESCAPE 154 | else: 155 | current += char 156 | elif state == STATE_DQUOTE_ESCAPE: 157 | current += char 158 | state = STATE_DQUOTE 159 | if current: 160 | tokens.append(current) 161 | 162 | # Find the key-type token, which might look like anything, but is 163 | # *always* followed by the actual key blob. Conveniently, the blob 164 | # always starts with the same key type. 165 | 166 | algo_pos = None 167 | for pos, token in enumerate(tokens): 168 | token = token.encode("utf-8") 169 | if pos > 0 and token.startswith(b"AAAA"): 170 | # This assumes key types are shorter than 256 bytes. 171 | prefix = struct.pack("!Is", len(prev_token), prev_token) 172 | if base64.b64decode(token).startswith(prefix): 173 | algo_pos = pos - 1 174 | break 175 | else: 176 | prev_token = token 177 | if algo_pos is None: 178 | raise ValueError("key blob not found (or doesn't match declared key-type)") 179 | 180 | prefix = " ".join(tokens[:algo_pos]) 181 | algo = tokens[algo_pos] 182 | blob = tokens[algo_pos+1] 183 | blob = base64.b64decode(blob.encode("utf-8")) 184 | comment = " ".join(tokens[algo_pos+2:]) 185 | 186 | return prefix, algo, blob, comment 187 | 188 | if __name__ == "__main__": 189 | import os 190 | import sys 191 | 192 | try: 193 | path = sys.argv[1] 194 | except IndexError: 195 | path = os.path.expanduser("~/.ssh/authorized_keys") 196 | 197 | for line in open(path, "r"): 198 | line = line.strip() 199 | if line and not line.startswith("#"): 200 | print("line = %r" % line) 201 | try: 202 | key = PublicKey(line) 203 | print("* key = %r" % key) 204 | print(" - prefix = %r" % key.prefix) 205 | print(" - algo = %r" % key.algo) 206 | print(" - comment = %r" % key.comment) 207 | print(" - options = %r" % key.options) 208 | except ValueError as e: 209 | print("* failure = %r" % e) 210 | print() 211 | 212 | # vim: ft=python 213 | -------------------------------------------------------------------------------- /doc/rfc4819.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Network Working Group J. Galbraith 8 | Request for Comments: 4819 J. Van Dyke 9 | Category: Standards Track VanDyke Software 10 | J. Bright 11 | Silicon Circus 12 | March 2007 13 | 14 | 15 | Secure Shell Public Key Subsystem 16 | 17 | Status of This Memo 18 | 19 | This document specifies an Internet standards track protocol for the 20 | Internet community, and requests discussion and suggestions for 21 | improvements. Please refer to the current edition of the "Internet 22 | Official Protocol Standards" (STD 1) for the standardization state 23 | and status of this protocol. Distribution of this memo is unlimited. 24 | 25 | Copyright Notice 26 | 27 | Copyright (C) The IETF Trust (2007). 28 | 29 | Abstract 30 | 31 | Secure Shell defines a user authentication mechanism that is based on 32 | public keys, but does not define any mechanism for key distribution. 33 | No common key management solution exists in current implementations. 34 | This document describes a protocol that can be used to configure 35 | public keys in an implementation-independent fashion, allowing client 36 | software to take on the burden of this configuration. 37 | 38 | The Public Key Subsystem provides a server-independent mechanism for 39 | clients to add public keys, remove public keys, and list the current 40 | public keys known by the server. Rights to manage public keys are 41 | specific and limited to the authenticated user. 42 | 43 | A public key may also be associated with various restrictions, 44 | including a mandatory command or subsystem. 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | Galbraith, et al. Standards Track [Page 1] 59 | 60 | RFC 4819 Secure Shell Public Key Subsystem March 2007 61 | 62 | 63 | Table of Contents 64 | 65 | 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3 66 | 2. Terminology . . . . . . . . . . . . . . . . . . . . . . . . . 3 67 | 3. Public Key Subsystem Overview . . . . . . . . . . . . . . . . 3 68 | 3.1. Opening the Public Key Subsystem . . . . . . . . . . . . . 4 69 | 3.2. Requests and Responses . . . . . . . . . . . . . . . . . . 5 70 | 3.3. The Status Message . . . . . . . . . . . . . . . . . . . . 5 71 | 3.3.1. Status Codes . . . . . . . . . . . . . . . . . . . . . 5 72 | 3.4. The Version Packet . . . . . . . . . . . . . . . . . . . . 6 73 | 4. Public Key Subsystem Operations . . . . . . . . . . . . . . . 7 74 | 4.1. Adding a Public Key . . . . . . . . . . . . . . . . . . . 7 75 | 4.2. Removing a Public Key . . . . . . . . . . . . . . . . . . 10 76 | 4.3. Listing Public Keys . . . . . . . . . . . . . . . . . . . 10 77 | 4.4. Listing Server Capabilities . . . . . . . . . . . . . . . 10 78 | 5. Security Considerations . . . . . . . . . . . . . . . . . . . 11 79 | 6. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 12 80 | 6.1. Registrations . . . . . . . . . . . . . . . . . . . . . . 12 81 | 6.2. Names . . . . . . . . . . . . . . . . . . . . . . . . . . 12 82 | 6.2.1. Conventions for Names . . . . . . . . . . . . . . . . 12 83 | 6.2.2. Future Assignments of Names . . . . . . . . . . . . . 13 84 | 6.3. Public Key Subsystem Request Names . . . . . . . . . . . . 13 85 | 6.4. Public Key Subsystem Response Names . . . . . . . . . . . 13 86 | 6.5. Public Key Subsystem Attribute Names . . . . . . . . . . . 13 87 | 6.6. Public Key Subsystem Status Codes . . . . . . . . . . . . 14 88 | 6.6.1. Conventions . . . . . . . . . . . . . . . . . . . . . 14 89 | 6.6.2. Initial Assignments . . . . . . . . . . . . . . . . . 14 90 | 6.6.3. Future Assignments . . . . . . . . . . . . . . . . . . 15 91 | 7. References . . . . . . . . . . . . . . . . . . . . . . . . . . 15 92 | 7.1. Normative References . . . . . . . . . . . . . . . . . . . 15 93 | 7.2. Informative References . . . . . . . . . . . . . . . . . . 15 94 | 8. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 16 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Galbraith, et al. Standards Track [Page 2] 115 | 116 | RFC 4819 Secure Shell Public Key Subsystem March 2007 117 | 118 | 119 | 1. Introduction 120 | 121 | Secure Shell (SSH) is a protocol for secure remote login and other 122 | secure network services over an insecure network. Secure Shell 123 | defines a user authentication mechanism that is based on public keys, 124 | but does not define any mechanism for key distribution. Common 125 | practice is to authenticate once with password authentication and 126 | transfer the public key to the server. However, to date no two 127 | implementations use the same mechanism to configure a public key for 128 | use. 129 | 130 | This document describes a subsystem that can be used to configure 131 | public keys in an implementation-independent fashion. This approach 132 | allows client software to take on the burden of this configuration. 133 | The Public Key Subsystem protocol is designed for extreme simplicity 134 | in implementation. It is not intended as a Public Key Infrastructure 135 | for X.509 Certificates (PKIX) replacement. 136 | 137 | The Secure Shell Public Key Subsystem has been designed to run on top 138 | of the Secure Shell transport layer [2] and user authentication 139 | protocols [3]. It provides a simple mechanism for the client to 140 | manage public keys on the server. 141 | 142 | This document should be read only after reading the Secure Shell 143 | architecture [1] and Secure Shell connection [4] documents. 144 | 145 | This protocol is intended to be used from the Secure Shell Connection 146 | Protocol [4] as a subsystem, as described in the section "Starting a 147 | Shell or a Command". The subsystem name used with this protocol is 148 | "publickey". 149 | 150 | This protocol requires that the user be able to authenticate in some 151 | fashion before it can be used. If password authentication is used, 152 | servers SHOULD provide a configuration option to disable the use of 153 | password authentication after the first public key is added. 154 | 155 | 2. Terminology 156 | 157 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 158 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 159 | document are to be interpreted as described in RFC 2119 [5]. 160 | 161 | 3. Public Key Subsystem Overview 162 | 163 | The Public Key Subsystem provides a server-independent mechanism for 164 | clients to add public keys, remove public keys, and list the current 165 | public keys known by the server. The subsystem name is "publickey". 166 | 167 | 168 | 169 | 170 | Galbraith, et al. Standards Track [Page 3] 171 | 172 | RFC 4819 Secure Shell Public Key Subsystem March 2007 173 | 174 | 175 | The public keys added, removed, and listed using this protocol are 176 | specific and limited to those of the authenticated user. 177 | 178 | The operations to add, remove, and list the authenticated user's 179 | public keys are performed as request packets sent to the server. The 180 | server sends response packets that indicate success or failure as 181 | well as provide specific response data. 182 | 183 | The format of public key blobs are detailed in Section 6.6, "Public 184 | Key Algorithms" of the SSH Transport Protocol document [2]. 185 | 186 | 3.1. Opening the Public Key Subsystem 187 | 188 | The Public Key Subsystem is started by a client sending an 189 | SSH_MSG_CHANNEL_REQUEST over an existing session's channel. 190 | 191 | The details of how a session is opened are described in the SSH 192 | Connection Protocol document [4] in the section "Opening a Session". 193 | 194 | To open the Public Key Subsystem, the client sends: 195 | 196 | byte SSH_MSG_CHANNEL_REQUEST 197 | uint32 recipient channel 198 | string "subsystem" 199 | boolean want reply 200 | string "publickey" 201 | 202 | Client implementations SHOULD reject this request; it is normally 203 | sent only by the client. 204 | 205 | If want reply is TRUE, the server MUST respond with 206 | SSH_MSG_CHANNEL_SUCCESS if the Public Key Subsystem was successfully 207 | started, or SSH_MSG_CHANNEL_FAILURE if the server failed to start or 208 | does not support the Public Key Subsystem. 209 | 210 | The server SHOULD respond with SSH_MSG_CHANNEL_FAILURE if the user is 211 | not allowed access to the Public Key Subsystem (for example, because 212 | the user authenticated with a restricted public key). 213 | 214 | It is RECOMMENDED that clients request and check the reply for this 215 | request. 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | Galbraith, et al. Standards Track [Page 4] 227 | 228 | RFC 4819 Secure Shell Public Key Subsystem March 2007 229 | 230 | 231 | 3.2. Requests and Responses 232 | 233 | All Public Key Subsystem requests and responses are sent in the 234 | following form: 235 | 236 | uint32 length 237 | string name 238 | ... request/response specific data follows 239 | 240 | The length field describes the length of the name field and of the 241 | request/response-specific data, but does not include the length of 242 | the length field itself. The client MUST receive acknowledgement of 243 | each request prior to sending a new request. 244 | 245 | The version packet, as well as all requests and responses described 246 | in Section 4, are a description of the 'name' field and the data part 247 | of the packet. 248 | 249 | 3.3. The Status Message 250 | 251 | A request is acknowledged by sending a status packet. If there is 252 | data in response to the request, the status packet is sent after all 253 | data has been sent. 254 | 255 | string "status" 256 | uint32 status code 257 | string description [7] 258 | string language tag [6] 259 | 260 | A status message MUST be sent for any unrecognized packets, and the 261 | request SHOULD NOT close the subsystem. 262 | 263 | 3.3.1. Status Codes 264 | 265 | The status code gives the status in a more machine-readable format 266 | (suitable for localization), and can have the following values: 267 | 268 | SSH_PUBLICKEY_SUCCESS 0 269 | SSH_PUBLICKEY_ACCESS_DENIED 1 270 | SSH_PUBLICKEY_STORAGE_EXCEEDED 2 271 | SSH_PUBLICKEY_VERSION_NOT_SUPPORTED 3 272 | SSH_PUBLICKEY_KEY_NOT_FOUND 4 273 | SSH_PUBLICKEY_KEY_NOT_SUPPORTED 5 274 | SSH_PUBLICKEY_KEY_ALREADY_PRESENT 6 275 | SSH_PUBLICKEY_GENERAL_FAILURE 7 276 | SSH_PUBLICKEY_REQUEST_NOT_SUPPORTED 8 277 | SSH_PUBLICKEY_ATTRIBUTE_NOT_SUPPORTED 9 278 | 279 | 280 | 281 | 282 | Galbraith, et al. Standards Track [Page 5] 283 | 284 | RFC 4819 Secure Shell Public Key Subsystem March 2007 285 | 286 | 287 | If a request completed successfully, the server MUST send the status 288 | code SSH_PUBLICKEY_SUCCESS. The meaning of the failure codes is as 289 | implied by their names. 290 | 291 | 3.4. The Version Packet 292 | 293 | Both sides MUST start a connection by sending a version packet that 294 | indicates the version of the protocol they are using. 295 | 296 | string "version" 297 | uint32 protocol-version-number 298 | 299 | This document describes version 2 of the protocol. Version 1 was 300 | used by an early draft of this document. The version number was 301 | incremented after changes in the handling of status packets. 302 | 303 | Both sides send the highest version that they implement. The lower 304 | of the version numbers is the version of the protocol to use. If 305 | either side can't support the lower version, it should close the 306 | subsystem and notify the other side by sending an 307 | SSH_MSG_CHANNEL_CLOSE message. Before closing the subsystem, a 308 | status message with the status SSH_PUBLICKEY_VERSION_NOT_SUPPORTED 309 | SHOULD be sent. Note that, normally, status messages are only sent 310 | by the server (in response to requests from the client). This is the 311 | only occasion on which the client sends a status message. 312 | 313 | Both sides MUST wait to receive this version before continuing. The 314 | "version" packet MUST NOT be sent again after this initial exchange. 315 | The SSH_PUBLICKEY_VERSION_NOT_SUPPORTED status code must not be sent 316 | in response to any other request. 317 | 318 | Implementations MAY use the first 15 bytes of the version packet as a 319 | "magic cookie" to avoid processing spurious output from the user's 320 | shell (as described in Section 6.5 of [4]). These bytes will always 321 | be: 322 | 323 | 0x00 0x00 0x00 0x0F 0x00 0x00 0x00 0x07 0x76 0x65 0x72 0x73 0x69 0x6F 324 | 0x6E 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | Galbraith, et al. Standards Track [Page 6] 339 | 340 | RFC 4819 Secure Shell Public Key Subsystem March 2007 341 | 342 | 343 | 4. Public Key Subsystem Operations 344 | 345 | The Public Key Subsystem currently defines four operations: add, 346 | remove, list, and listattributes. 347 | 348 | 4.1. Adding a Public Key 349 | 350 | If the client wishes to add a public key, the client sends: 351 | 352 | string "add" 353 | string public key algorithm name 354 | string public key blob 355 | boolean overwrite 356 | uint32 attribute-count 357 | string attrib-name 358 | string attrib-value 359 | bool critical 360 | repeated attribute-count times 361 | 362 | The server MUST attempt to store the public key for the user in the 363 | appropriate location so the public key can be used for subsequent 364 | public key authentications. If the overwrite field is false and the 365 | specified key already exists, the server MUST return 366 | SSH_PUBLICKEY_KEY_ALREADY_PRESENT. If the server returns this, the 367 | client SHOULD provide an option to the user to overwrite the key. If 368 | the overwrite field is true and the specified key already exists, but 369 | cannot be overwritten, the server MUST return 370 | SSH_PUBLICKEY_ACCESS_DENIED. 371 | 372 | Attribute names are defined following the same scheme laid out for 373 | algorithm names in [1]. If the server does not implement a critical 374 | attribute, it MUST fail the add, with the status code 375 | SSH_PUBLICKEY_ATTRIBUTE_NOT_SUPPORTED. For the purposes of a 376 | critical attribute, mere storage of the attribute is not sufficient 377 | -- rather, the server must understand and implement the intent of the 378 | attribute. 379 | 380 | The following attributes are currently defined: 381 | 382 | "comment" 383 | 384 | The value of the comment attribute contains user-specified text about 385 | the public key. The server SHOULD make every effort to preserve this 386 | value and return it with the key during any subsequent list 387 | operation. The server MUST NOT attempt to interpret or act upon the 388 | content of the comment field in any way. The comment attribute must 389 | be specified in UTF-8 format [7]. 390 | 391 | 392 | 393 | 394 | Galbraith, et al. Standards Track [Page 7] 395 | 396 | RFC 4819 Secure Shell Public Key Subsystem March 2007 397 | 398 | 399 | The comment field is useful so the user can identify the key without 400 | resorting to comparing its fingerprint. This attribute SHOULD NOT be 401 | critical. 402 | 403 | "comment-language" 404 | 405 | If this attribute is specified, it MUST immediately follow a 406 | "comment" attribute and specify the language for that attribute [6]. 407 | The client MAY specify more than one comment if it additionally 408 | specifies a different language for each of those comments. The 409 | server SHOULD attempt to store each comment with its language 410 | attribute. This attribute SHOULD NOT be critical. 411 | 412 | "command-override" 413 | 414 | "command-override" specifies a command to be executed when this key 415 | is in use. The command should be executed by the server when it 416 | receives an "exec" or "shell" request from the client, in place of 417 | the command or shell which would otherwise have been executed as a 418 | result of that request. If the command string is empty, both "exec" 419 | and "shell" requests should be denied. If no "command-override" 420 | attribute is specified, all "exec" and "shell" requests should be 421 | permitted (as long as they satisfy other security or authorization 422 | checks the server may perform). This attribute SHOULD be critical. 423 | 424 | "subsystem" 425 | 426 | "subsystem" specifies a comma-separated list of subsystems that may 427 | be started (using a "subsystem" request) when this key is in use. 428 | This attribute SHOULD be critical. If the value is empty, no 429 | subsystems may be started. If the "subsystem" attribute is not 430 | specified, no restrictions are placed on which subsystems may be 431 | started when authenticated using this key. 432 | 433 | "x11" 434 | 435 | "x11" specifies that X11 forwarding may not be performed when this 436 | key is in use. The attribute-value field SHOULD be empty for this 437 | attribute. This attribute SHOULD be critical. 438 | 439 | "shell" 440 | 441 | "shell" specifies that session channel "shell" requests should be 442 | denied when this key is in use. The attribute-value field SHOULD be 443 | empty for this attribute. This attribute SHOULD be critical. 444 | 445 | 446 | 447 | 448 | 449 | 450 | Galbraith, et al. Standards Track [Page 8] 451 | 452 | RFC 4819 Secure Shell Public Key Subsystem March 2007 453 | 454 | 455 | "exec" 456 | 457 | "exec" specifies that session channel "exec" requests should be 458 | denied when this key is in use. The attribute-value field SHOULD be 459 | empty for this attribute. This attribute SHOULD be critical. 460 | 461 | "agent" 462 | 463 | "agent" specifies that session channel "auth-agent-req" requests 464 | should be denied when this key is in use. The attribute-value field 465 | SHOULD be empty for this attribute. This attribute SHOULD be 466 | critical. 467 | 468 | "env" 469 | 470 | "env" specifies that session channel "env" requests should be denied 471 | when this key is in use. The attribute-value field SHOULD be empty 472 | for this attribute. This attribute SHOULD be critical. 473 | 474 | "from" 475 | 476 | "from" specifies a comma-separated list of hosts from which the key 477 | may be used. If a host not in this list attempts to use this key for 478 | authorization purposes, the authorization attempt MUST be denied. 479 | The server SHOULD make a log entry regarding this. The server MAY 480 | provide a method for administrators to disallow the appearance of a 481 | host in this list. The server should use whatever method is 482 | appropriate for its platform to identify the host -- e.g., for IP- 483 | based networks, checking the IP address or performing a reverse DNS 484 | lookup. For IP-based networks, it is anticipated that each element 485 | of the "from" parameter will take the form of a specific IP address 486 | or hostname. 487 | 488 | "port-forward" 489 | 490 | "port-forward" specifies that no "direct-tcpip" requests should be 491 | accepted, except those to hosts specified in the comma-separated list 492 | supplied as a value to this attribute. If the value of this 493 | attribute is empty, all "direct-tcpip" requests should be refused 494 | when using this key. This attribute SHOULD be critical. 495 | 496 | "reverse-forward" 497 | 498 | "reverse-forward" specifies that no "tcpip-forward" requests should 499 | be accepted, except for the port numbers in the comma-separated list 500 | supplied as a value to this attribute. If the value of this 501 | attribute is empty, all "tcpip-forward" requests should be refused 502 | when using this key. This attribute SHOULD be critical. 503 | 504 | 505 | 506 | Galbraith, et al. Standards Track [Page 9] 507 | 508 | RFC 4819 Secure Shell Public Key Subsystem March 2007 509 | 510 | 511 | In addition to the attributes specified by the client, the server MAY 512 | provide a method for administrators to enforce certain attributes 513 | compulsorily. 514 | 515 | 4.2. Removing a Public Key 516 | 517 | If the client wishes to remove a public key, the client sends: 518 | 519 | string "remove" 520 | string public key algorithm name 521 | string public key blob 522 | 523 | The server MUST attempt to remove the public key for the user from 524 | the appropriate location, so that the public key cannot be used for 525 | subsequent authentications. 526 | 527 | 4.3. Listing Public Keys 528 | 529 | If the client wishes to list the known public keys, the client sends: 530 | 531 | string "list" 532 | 533 | The server will respond with zero or more of the following responses: 534 | 535 | string "publickey" 536 | string public key algorithm name 537 | string public key blob 538 | uint32 attribute-count 539 | string attrib-name 540 | string attrib-value 541 | repeated attribute-count times 542 | 543 | There is no requirement that the responses be in any particular 544 | order. Whilst some server implementations may send the responses in 545 | some order, client implementations should not rely on responses being 546 | in any order. 547 | 548 | Following the last "publickey" response, a status packet MUST be 549 | sent. 550 | 551 | Implementations SHOULD support this request. 552 | 553 | 4.4. Listing Server Capabilities 554 | 555 | If the client wishes to know which key attributes the server 556 | supports, it sends: 557 | 558 | string "listattributes" 559 | 560 | 561 | 562 | Galbraith, et al. Standards Track [Page 10] 563 | 564 | RFC 4819 Secure Shell Public Key Subsystem March 2007 565 | 566 | 567 | The server will respond with zero or more of the following responses: 568 | 569 | string "attribute" 570 | string attribute name 571 | boolean compulsory 572 | 573 | The "compulsory" field indicates whether this attribute will be 574 | compulsorily applied to any added keys (irrespective of whether the 575 | attribute has been specified by the client) due to administrative 576 | settings on the server. If the server does not support 577 | administrative settings of this nature, it MUST return false in the 578 | compulsory field. An example of use of the "compulsory" attribute 579 | would be a server with a configuration file specifying that the user 580 | is not permitted shell access. Given this, the server would return 581 | the "shell" attribute, with "compulsory" marked true. Whatever 582 | attributes the user subsequently asked the server to apply to their 583 | key, the server would also apply the "shell" attribute, rendering it 584 | impossible for the user to use a shell. 585 | 586 | Following the last "attribute" response, a status packet MUST be 587 | sent. 588 | 589 | An implementation MAY choose not to support this request. 590 | 591 | 5. Security Considerations 592 | 593 | This protocol assumes that it is run over a secure channel and that 594 | the endpoints of the channel have been authenticated. Thus, this 595 | protocol assumes that it is externally protected from network-level 596 | attacks. 597 | 598 | This protocol provides a mechanism that allows client authentication 599 | data to be uploaded and manipulated. It is the responsibility of the 600 | server implementation to enforce any access controls that may be 601 | required to limit the access allowed for any particular user (the 602 | user being authenticated externally to this protocol, typically using 603 | the SSH User Authentication Protocol [3]). In particular, it is 604 | possible for users to overwrite an existing key on the server with 605 | this protocol, whilst at the same time specifying fewer restrictions 606 | for the new key than were previously present. Servers should take 607 | care that when doing this, clients are not able to override presets 608 | from the server's administrator. 609 | 610 | This protocol requires the client to assume that the server will 611 | correctly implement and observe attributes applied to keys. 612 | Implementation errors in the server could cause clients to authorize 613 | keys for access they were not intended to have, or to apply fewer 614 | restrictions than were intended. 615 | 616 | 617 | 618 | Galbraith, et al. Standards Track [Page 11] 619 | 620 | RFC 4819 Secure Shell Public Key Subsystem March 2007 621 | 622 | 623 | 6. IANA Considerations 624 | 625 | This section contains conventions used in naming the namespaces, the 626 | initial state of the registry, and instructions for future 627 | assignments. 628 | 629 | 6.1. Registrations 630 | 631 | Consistent with Section 4.9.5 of [8], this document makes the 632 | following registration: 633 | 634 | The subsystem name "publickey". 635 | 636 | 6.2. Names 637 | 638 | In the following sections, the values for the namespaces are textual. 639 | The conventions and instructions to the IANA for future assignments 640 | are given in this section. The initial assignments are given in 641 | their respective sections. 642 | 643 | 6.2.1. Conventions for Names 644 | 645 | All names registered by the IANA in the following sections MUST be 646 | printable US-ASCII strings, and MUST NOT contain the characters 647 | at-sign ("@"), comma (","), or whitespace or control characters 648 | (ASCII codes 32 or less). Names are case-sensitive, and MUST NOT be 649 | longer than 64 characters. 650 | 651 | A provision is made here for locally extensible names. The IANA will 652 | not register and will not control names with the at-sign in them. 653 | Names with the at-sign in them will have the format of 654 | "name@domainname" (without the double quotes) where the part 655 | preceding the at-sign is the name. The format of the part preceding 656 | the at-sign is not specified; however, these names MUST be printable 657 | US-ASCII strings, and MUST NOT contain the comma character (","), or 658 | whitespace, or control characters (ASCII codes 32 or less). The part 659 | following the at-sign MUST be a valid, fully qualified Internet 660 | domain name [10] controlled by the person or organization defining 661 | the name. Names are case-sensitive, and MUST NOT be longer than 64 662 | characters. It is up to each domain how it manages its local 663 | namespace. It has been noted that these names resemble STD 11 [9] 664 | email addresses. This is purely coincidental and actually has 665 | nothing to do with STD 11 [9]. An example of a locally defined name 666 | is "our-attribute@example.com" (without the double quotes). 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | Galbraith, et al. Standards Track [Page 12] 675 | 676 | RFC 4819 Secure Shell Public Key Subsystem March 2007 677 | 678 | 679 | 6.2.2. Future Assignments of Names 680 | 681 | Requests for assignments of new Names MUST be done through the IETF 682 | Consensus method as described in [11]. 683 | 684 | 6.3. Public Key Subsystem Request Names 685 | 686 | The following table lists the initial assignments of Public Key 687 | Subsystem Request names. 688 | 689 | Request Name 690 | ------------- 691 | version 692 | add 693 | remove 694 | list 695 | listattributes 696 | 697 | 6.4. Public Key Subsystem Response Names 698 | 699 | The following table lists the initial assignments of Public Key 700 | Subsystem Response names. 701 | 702 | Response Name 703 | -------------- 704 | version 705 | status 706 | publickey 707 | attribute 708 | 709 | 6.5. Public Key Subsystem Attribute Names 710 | 711 | Attributes are used to define properties or restrictions for public 712 | keys. The following table lists the initial assignments of Public 713 | Key Subsystem Attribute names. 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | Galbraith, et al. Standards Track [Page 13] 731 | 732 | RFC 4819 Secure Shell Public Key Subsystem March 2007 733 | 734 | 735 | Attribute Name 736 | --------------- 737 | comment 738 | comment-language 739 | command-override 740 | subsystem 741 | x11 742 | shell 743 | exec 744 | agent 745 | env 746 | from 747 | port-forward 748 | reverse-forward 749 | 750 | 6.6. Public Key Subsystem Status Codes 751 | 752 | The status code is a byte value, describing the status of a request. 753 | 754 | 6.6.1. Conventions 755 | 756 | Status responses have status codes in the range 0 to 255. These 757 | numbers are allocated as follows. Of these, the range 192 to 255 is 758 | reserved for use by local, private extensions. 759 | 760 | 6.6.2. Initial Assignments 761 | 762 | The following table identifies the initial assignments of the Public 763 | Key Subsystem status code values. 764 | 765 | Status code Value Reference 766 | ------------ ----- --------- 767 | SSH_PUBLICKEY_SUCCESS 0 768 | SSH_PUBLICKEY_ACCESS_DENIED 1 769 | SSH_PUBLICKEY_STORAGE_EXCEEDED 2 770 | SSH_PUBLICKEY_VERSION_NOT_SUPPORTED 3 771 | SSH_PUBLICKEY_KEY_NOT_FOUND 4 772 | SSH_PUBLICKEY_KEY_NOT_SUPPORTED 5 773 | SSH_PUBLICKEY_KEY_ALREADY_PRESENT 6 774 | SSH_PUBLICKEY_GENERAL_FAILURE 7 775 | SSH_PUBLICKEY_REQUEST_NOT_SUPPORTED 8 776 | SSH_PUBLICKEY_ATTRIBUTE_NOT_SUPPORTED 9 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | Galbraith, et al. Standards Track [Page 14] 787 | 788 | RFC 4819 Secure Shell Public Key Subsystem March 2007 789 | 790 | 791 | 6.6.3. Future Assignments 792 | 793 | Requests for assignments of new status codes in the range of 0 to 191 794 | MUST be done through the Standards Action method as described in 795 | [11]. 796 | 797 | The IANA will not control the status code range of 192 through 255. 798 | This range is for private use. 799 | 800 | 7. References 801 | 802 | 7.1. Normative References 803 | 804 | [1] Ylonen, T. and C. Lonvick, "The Secure Shell (SSH) Protocol 805 | Architecture", RFC 4251, January 2006. 806 | 807 | [2] Ylonen, T. and C. Lonvick, "The Secure Shell (SSH) Transport 808 | Layer Protocol", RFC 4253, January 2006. 809 | 810 | [3] Ylonen, T. and C. Lonvick, "The Secure Shell (SSH) 811 | Authentication Protocol", RFC 4252, January 2006. 812 | 813 | [4] Ylonen, T. and C. Lonvick, "The Secure Shell (SSH) Connection 814 | Protocol", RFC 4254, January 2006. 815 | 816 | [5] Bradner, S., "Key words for use in RFCs to Indicate Requirement 817 | Levels", BCP 14, RFC 2119, March 1997. 818 | 819 | [6] Phillips, A. and M. Davis, "Tags for Identifying Languages", 820 | BCP 47, RFC 4646, September 2006. 821 | 822 | [7] Yergeau, F., "UTF-8, a transformation format of ISO 10646", 823 | STD 63, RFC 3629, November 2003. 824 | 825 | 7.2. Informative References 826 | 827 | [8] Lehtinen, S. and C. Lonvick, "The Secure Shell (SSH) Protocol 828 | Assigned Numbers", RFC 4250, January 2006. 829 | 830 | [9] Crocker, D., "Standard for the format of ARPA Internet text 831 | messages", STD 11, RFC 822, August 1982. 832 | 833 | [10] Mockapetris, P., "Domain names - concepts and facilities", 834 | STD 13, RFC 1034, November 1987. 835 | 836 | [11] Narten, T. and H. Alvestrand, "Guidelines for Writing an IANA 837 | Considerations Section in RFCs", BCP 26, RFC 2434, 838 | October 1998. 839 | 840 | 841 | 842 | Galbraith, et al. Standards Track [Page 15] 843 | 844 | RFC 4819 Secure Shell Public Key Subsystem March 2007 845 | 846 | 847 | 8. Acknowledgements 848 | 849 | Brent McClure contributed to the writing of this document. 850 | 851 | Authors' Addresses 852 | 853 | Joseph Galbraith 854 | VanDyke Software 855 | 4848 Tramway Ridge Blvd 856 | Suite 101 857 | Albuquerque, NM 87111 858 | US 859 | 860 | Phone: +1 505 332 5700 861 | EMail: galb@vandyke.com 862 | 863 | 864 | Jeff P. Van Dyke 865 | VanDyke Software 866 | 4848 Tramway Ridge Blvd 867 | Suite 101 868 | Albuquerque, NM 87111 869 | US 870 | 871 | Phone: +1 505 332 5700 872 | EMail: jpv@vandyke.com 873 | 874 | 875 | Jon Bright 876 | Silicon Circus 877 | 24 Jubilee Road 878 | Chichester, West Sussex PO19 7XB 879 | UK 880 | 881 | Phone: +49 172 524 0521 882 | EMail: jon@siliconcircus.com 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | Galbraith, et al. Standards Track [Page 16] 899 | 900 | RFC 4819 Secure Shell Public Key Subsystem March 2007 901 | 902 | 903 | Full Copyright Statement 904 | 905 | Copyright (C) The IETF Trust (2007). 906 | 907 | This document is subject to the rights, licenses and restrictions 908 | contained in BCP 78, and except as set forth therein, the authors 909 | retain all their rights. 910 | 911 | This document and the information contained herein are provided on an 912 | "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS 913 | OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND 914 | THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS 915 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF 916 | THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED 917 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 918 | 919 | Intellectual Property 920 | 921 | The IETF takes no position regarding the validity or scope of any 922 | Intellectual Property Rights or other rights that might be claimed to 923 | pertain to the implementation or use of the technology described in 924 | this document or the extent to which any license under such rights 925 | might or might not be available; nor does it represent that it has 926 | made any independent effort to identify any such rights. Information 927 | on the procedures with respect to rights in RFC documents can be 928 | found in BCP 78 and BCP 79. 929 | 930 | Copies of IPR disclosures made to the IETF Secretariat and any 931 | assurances of licenses to be made available, or the result of an 932 | attempt made to obtain a general license or permission for the use of 933 | such proprietary rights by implementers or users of this 934 | specification can be obtained from the IETF on-line IPR repository at 935 | http://www.ietf.org/ipr. 936 | 937 | The IETF invites any interested party to bring to its attention any 938 | copyrights, patents or patent applications, or other proprietary 939 | rights that may cover technology that may be required to implement 940 | this standard. Please address the information to the IETF at 941 | ietf-ipr@ietf.org. 942 | 943 | Acknowledgement 944 | 945 | Funding for the RFC Editor function is currently provided by the 946 | Internet Society. 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | Galbraith, et al. Standards Track [Page 17] 955 | 956 | -------------------------------------------------------------------------------- /doc/rfc7076.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Independent Submission M. Joseph 8 | Request for Comments: 7076 J. Susoy 9 | Category: Informational P6R, Inc 10 | ISSN: 2070-1721 November 2013 11 | 12 | 13 | P6R's Secure Shell Public Key Subsystem 14 | 15 | Abstract 16 | 17 | The Secure Shell (SSH) Public Key Subsystem protocol defines a key 18 | distribution protocol that is limited to provisioning an SSH server 19 | with a user's public keys. This document describes a new protocol 20 | that builds on the protocol defined in RFC 4819 to allow the 21 | provisioning of keys and certificates to a server using the SSH 22 | transport. 23 | 24 | The new protocol allows the calling client to organize keys and 25 | certificates in different namespaces on a server. These namespaces 26 | can be used by the server to allow a client to configure any 27 | application running on the server (e.g., SSH, Key Management 28 | Interoperability Protocol (KMIP), Simple Network Management Protocol 29 | (SNMP)). 30 | 31 | The new protocol provides a server-independent mechanism for clients 32 | to add public keys, remove public keys, add certificates, remove 33 | certificates, and list the current set of keys and certificates known 34 | by the server by namespace (e.g., list all public keys in the SSH 35 | namespace). 36 | 37 | Rights to manage keys and certificates in a particular namespace are 38 | specific and limited to the authorized user and are defined as part 39 | of the server's implementation. The described protocol is backward 40 | compatible to version 2 defined by RFC 4819. 41 | 42 | Status of This Memo 43 | 44 | This document is not an Internet Standards Track specification; it is 45 | published for informational purposes. 46 | 47 | This is a contribution to the RFC Series, independently of any other 48 | RFC stream. The RFC Editor has chosen to publish this document at 49 | its discretion and makes no statement about its value for 50 | implementation or deployment. Documents approved for publication by 51 | the RFC Editor are not a candidate for any level of Internet 52 | Standard; see Section 2 of RFC 5741. 53 | 54 | 55 | 56 | 57 | 58 | Joseph & Susoy Informational [Page 1] 59 | 60 | RFC 7076 P6R's Secure Shell Public Key Subsystem November 2013 61 | 62 | 63 | Information about the current status of this document, any errata, 64 | and how to provide feedback on it may be obtained at 65 | http://www.rfc-editor.org/info/rfc7076. 66 | 67 | Copyright Notice 68 | 69 | Copyright (c) 2013 IETF Trust and the persons identified as the 70 | document authors. All rights reserved. 71 | 72 | This document is subject to BCP 78 and the IETF Trust's Legal 73 | Provisions Relating to IETF Documents 74 | (http://trustee.ietf.org/license-info) in effect on the date of 75 | publication of this document. Please review these documents 76 | carefully, as they describe your rights and restrictions with respect 77 | to this document. 78 | 79 | Table of Contents 80 | 81 | 1. Introduction ....................................................3 82 | 2. Terminology .....................................................3 83 | 3. Overview of Extensions to the Public Key Subsystem ..............3 84 | 3.1. Extended Status Codes ......................................4 85 | 3.2. The Version Packet .........................................4 86 | 3.3. The Namespace Attribute ....................................4 87 | 4. New Operations ..................................................5 88 | 4.1. Adding a Certificate .......................................5 89 | 4.2. Removing a Certificate .....................................6 90 | 4.3. Listing Certificates .......................................6 91 | 4.4. Listing Namespaces .........................................7 92 | 5. Extending Public Key Operations .................................8 93 | 5.1. Adding a Public Key ........................................8 94 | 5.2. Removing a Public Key ......................................8 95 | 5.3. Listing Public Keys ........................................9 96 | 6. Security Considerations .........................................9 97 | 7. IANA Considerations ............................................10 98 | 8. References .....................................................10 99 | 8.1. Normative References ......................................10 100 | 8.2. Informative References ....................................10 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Joseph & Susoy Informational [Page 2] 115 | 116 | RFC 7076 P6R's Secure Shell Public Key Subsystem November 2013 117 | 118 | 119 | 1. Introduction 120 | 121 | This document describes a new protocol that builds on the protocol 122 | defined in RFC 4819 that can be used to configure public keys and 123 | certificates in an implementation-independent fashion. The concept 124 | of a namespace is added to the protocol's operations; it allows the 125 | client to organize keys and certificates by application or 126 | organizational structure. 127 | 128 | P6R's Secure Shell Public Key Subsystem has been designed to run on 129 | top of the Secure Shell transport layer [3] and user authentication 130 | protocols [4]. It provides a simple mechanism for the client to 131 | manage the public keys and certificates on the server related to that 132 | client. These keys and certificates are normally used for 133 | authentication of the client to a service, but they can be used for 134 | encrypting results back to the client as well. Uploaded keys and 135 | certificates are meant to be able to configure all protocols running 136 | on a server (e.g., SSH, SSL, KMIP [8]) that use keys and 137 | certificates, as well as the applications that run on a server. 138 | 139 | This document should be read only after reading the Secure Shell 140 | Public Key Subsystem [1] document. The new protocol described in 141 | this document builds on and is meant to be backwards compatible with 142 | the protocol described in [1]. 143 | 144 | 2. Terminology 145 | 146 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 147 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 148 | document are to be interpreted as described in RFC 2119 [2]. 149 | 150 | 3. Overview of Extensions to the Public Key Subsystem 151 | 152 | The Public Key Subsystem provides a server-independent mechanism for 153 | clients to add public keys, remove public keys, list the current 154 | public keys known by the server, add certificates, remove 155 | certificates, and list the current set of certificates known by the 156 | server. This secure key distribution mechanism is implemented by a 157 | new SSH subsystem with the name of "publickey@p6r.com". 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | Joseph & Susoy Informational [Page 3] 171 | 172 | RFC 7076 P6R's Secure Shell Public Key Subsystem November 2013 173 | 174 | 175 | 3.1. Extended Status Codes 176 | 177 | The status code gives the status in a more machine-readable format 178 | (suitable for localization) and can have the following values: 179 | 180 | SSH_PUBLICKEY_CERTIFICATE_NOT_FOUND 192 181 | SSH_PUBLICKEY_CERTIFICATE_NOT_SUPPORTED 193 182 | SSH_PUBLICKEY_CERTIFICATE_ALREADY_PRESENT 194 183 | SSH_PUBLICKEY_ACTION_NOT_AUTHORIZED 195 184 | SSH_PUBLICKEY_CANNOT_CREATE_NAMESPACE 196 185 | 186 | The meaning of the failure codes is as implied by their names. See 187 | Security Considerations for the use of the failure code: 188 | SSH_PUBLICKEY_ACTION_NOT_AUTHORIZED. 189 | 190 | 3.2. The Version Packet 191 | 192 | Both sides MUST start a connection by sending a version packet that 193 | indicates the version of the protocol they are using. 194 | 195 | string "version" 196 | uint32 protocol-version-number 197 | 198 | This document defines version 3 of the new protocol. We are using 199 | version 3 so that it can be backward compatible with the protocol 200 | defined by RFC 4819 [1]. 201 | 202 | 3.3. The Namespace Attribute 203 | 204 | The "namespace" attribute is added as an extension to what was 205 | described in RFC 4819. The purpose of this attribute is to be able 206 | to organize the uploaded keys and certificates into groups where each 207 | group represents an application or organization structure. This 208 | attribute is a string that should not be longer than 300 characters 209 | and MUST be specified in UTF-8 format [5]. 210 | 211 | This new protocol uses the "ssh" namespace for the manipulation of 212 | public keys in an SSH server and should be considered as the default 213 | namespace when none is provided. 214 | 215 | As a convention, namespaces used for protocols are lowercase strings 216 | of the protocol's standard abbreviation. For example, "ssl" should 217 | be the namespace used for the Secure Sockets Layer protocol. 218 | Namespaces for applications should contain the product and vendor's 219 | name. To help determine what namespaces already exist on a server, a 220 | new operation "list-namespaces" is defined in Section 4. 221 | 222 | 223 | 224 | 225 | 226 | Joseph & Susoy Informational [Page 4] 227 | 228 | RFC 7076 P6R's Secure Shell Public Key Subsystem November 2013 229 | 230 | 231 | 4. New Operations 232 | 233 | P6R's Public Key Subsystem extends the functionality defined in RFC 234 | 4819 with the following operations: add-certificate, 235 | remove-certificate, list-certificates, and list-namespaces. 236 | 237 | 4.1. Adding a Certificate 238 | 239 | If the client wishes to add a certificate, the client sends: 240 | 241 | string "add-certificate" 242 | string certificate format name 243 | string certificate blob 244 | boolean overwrite 245 | uint32 attribute-count 246 | string attrib-name 247 | string attrib-value 248 | bool critical 249 | repeated attribute-count times 250 | 251 | This request MUST include at least the "namespace" attribute so that 252 | the server knows where to save the certificate. Only one namespace 253 | attribute can be used per an add-certificate request. It is possible 254 | for the same user to save the same certificate into multiple 255 | namespaces, but this must be done with several separate 256 | add-certificate requests. 257 | 258 | If the namespace appearing in an add-certificate request does not 259 | already exist on a server, then it is created by this operation. 260 | However, if the user is not authorized to create a namespace, the 261 | server MUST return SSH_PUBLICKEY_CANNOT_CREATE_NAMESPACE. 262 | 263 | If the overwrite field is false and the specified certificate already 264 | exists in the given namespace, the server MUST return 265 | SSH_PUBLICKEY_CERTIFICATE_ALREADY_PRESENT. If the server returns 266 | this, the client SHOULD provide an option to the user to overwrite 267 | the certificate. If the overwrite field is true and the specified 268 | key already exists in the given namespace but cannot be overwritten, 269 | the server MUST return SSH_PUBLICKEY_ACCESS_DENIED. 270 | 271 | However, a user may not be authorized to add a certificate to the 272 | specified namespace. If the user does not have permission to add a 273 | certificate, then the server MUST return 274 | SSH_PUBLICKEY_ACTION_NOT_AUTHORIZED. 275 | 276 | Examples of possible "certificate format names" are: "X509", 277 | "pgp-sign-rsa", and "pgp-sign-dss". The format of the public key and 278 | certificate blobs are detailed in Section 6.6, "Public Key 279 | 280 | 281 | 282 | Joseph & Susoy Informational [Page 5] 283 | 284 | RFC 7076 P6R's Secure Shell Public Key Subsystem November 2013 285 | 286 | 287 | Algorithms", of the SSH Transport Protocol document [3], where X.509 288 | certificates are to be encoded using a DER format [6] [7] in a 289 | certificate blob. 290 | 291 | 4.2. Removing a Certificate 292 | 293 | If the client wishes to remove a certificate, the client sends: 294 | 295 | string "remove-certificate" 296 | string certificate format name 297 | string certificate blob 298 | uint32 attribute-count 299 | string attrib-name 300 | string attrib-value 301 | repeated attribute-count times 302 | 303 | This request MUST include at least the "namespace" attribute so that 304 | the server knows from where to delete the certificate. Only one 305 | namespace attribute can be used per remove-certificate request. The 306 | server MUST attempt to remove the certificate from the appropriate 307 | location. 308 | 309 | However, a user may not be authorized to remove a certificate from 310 | the specified namespace. If the user does not have permission to 311 | remove the certificate, then the server MUST return 312 | SSH_PUBLICKEY_ACTION_NOT_AUTHORIZED. 313 | 314 | Examples of possible "certificate format names" are: "X509", 315 | "pgp-sign-rsa", and "pgp-sign-dss". 316 | 317 | 4.3. Listing Certificates 318 | 319 | If the client wishes to list the known certificates, the client 320 | sends: 321 | 322 | string "list-certificates" 323 | 324 | The server will respond with zero or more of the following responses: 325 | 326 | string "certificate" 327 | string certificate format name 328 | string certificate blob 329 | uint32 attribute-count 330 | string attrib-name 331 | string attrib-value 332 | repeated attribute-count times 333 | 334 | 335 | 336 | 337 | 338 | Joseph & Susoy Informational [Page 6] 339 | 340 | RFC 7076 P6R's Secure Shell Public Key Subsystem November 2013 341 | 342 | 343 | There is no requirement that the responses be in any particular 344 | order. Whilst some server implementations may send the responses in 345 | some order, client implementations should not rely on responses being 346 | in any order. 347 | 348 | This response MUST include at least the "namespace" attribute so that 349 | a client can tell in which namespace the certificate resides. Only 350 | one namespace attribute can be used per list-certificate request. 351 | 352 | Following the last "certificate" response, a status packet MUST be 353 | sent. 354 | 355 | 4.4. Listing Namespaces 356 | 357 | If the client wishes to know existing namespaces on the server, it 358 | sends: 359 | 360 | string "list-namespaces" 361 | 362 | The server will respond with zero or more of the following responses: 363 | 364 | string "namespace" 365 | string namespace name 366 | 367 | It is possible that not all namespaces will be visible to every 368 | authenticated user. In this case, the responding server will return 369 | a subset of existing namespaces. See Security Considerations below. 370 | 371 | Following the last "namespace" response, a status packet MUST be 372 | sent. 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | Joseph & Susoy Informational [Page 7] 395 | 396 | RFC 7076 P6R's Secure Shell Public Key Subsystem November 2013 397 | 398 | 399 | 5. Extending Public Key Operations 400 | 401 | In addition to adding new operations, this document describes 402 | extensions to the operations defined in RFC 4819. 403 | 404 | 5.1. Adding a Public Key 405 | 406 | If the client wishes to add a public key, the client sends: 407 | 408 | string "add" 409 | string public key algorithm name 410 | string public key blob 411 | boolean overwrite 412 | uint32 attribute-count 413 | string attrib-name 414 | string attrib-value 415 | bool critical 416 | repeated attribute-count times 417 | 418 | This request MAY include one "namespace" attribute so that a client 419 | can save the public key into a specific namespace. It is possible 420 | for the same user to save the same key into multiple namespaces, but 421 | this requires multiple add requests. 422 | 423 | If the namespace appearing in an add public key request does not 424 | already exist on a server, then it is created by this operation. 425 | However, if the user is not authorized to create a namespace the 426 | server MUST return SSH_PUBLICKEY_CANNOT_CREATE_NAMESPACE, 427 | 428 | 5.2. Removing a Public Key 429 | 430 | If the client wishes to remove a public key, the client sends: 431 | 432 | string "remove" 433 | string public key algorithm name 434 | string public key blob 435 | uint32 attribute-count 436 | string attrib-name 437 | string attrib-value 438 | bool critical 439 | repeated attribute-count times 440 | 441 | This extension allows attributes to be added to a remove request. 442 | This request MAY include one "namespace" attribute so that a client 443 | can remove the public key from a specific namespace. 444 | 445 | 446 | 447 | 448 | 449 | 450 | Joseph & Susoy Informational [Page 8] 451 | 452 | RFC 7076 P6R's Secure Shell Public Key Subsystem November 2013 453 | 454 | 455 | 5.3. Listing Public Keys 456 | 457 | If the client wishes to list the known public keys, the client sends: 458 | 459 | string "list" 460 | uint32 attribute-count 461 | string attrib-name 462 | string attrib-value 463 | bool critical 464 | repeated attribute-count times 465 | 466 | This extension allows attributes to be added to a list request. This 467 | request MAY include one "namespace" attribute so that a client can 468 | list the public keys from a specific namespace. 469 | 470 | The server will respond with zero or more of the following responses: 471 | 472 | string "publickey" 473 | string public key algorithm name 474 | string public key blob 475 | uint32 attribute-count 476 | string attrib-name 477 | string attrib-value 478 | repeated attribute-count times 479 | 480 | This response MAY include the "namespace" attribute so that a client 481 | can tell in which namespace the key resides. 482 | 483 | 6. Security Considerations 484 | 485 | This protocol assumes that it is run over a secure channel and that 486 | the endpoints of the channel have been authenticated. Thus, this 487 | protocol assumes that it is externally protected from network-level 488 | attacks. 489 | 490 | This protocol provides a mechanism that allows key and certificate 491 | material to be uploaded and manipulated into a server application. 492 | It is the responsibility of the server implementation to enforce 493 | access controls that may be required to limit any particular user's 494 | access to the data in a namespace. For example, one user may be 495 | allowed to list only the contents of a namespace but not add or 496 | remove keys or certificates to/from it. The server MUST return 497 | SSH_PUBLICKEY_ACTION_NOT_AUTHORIZED when a user's action goes against 498 | its defined access controls. 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | Joseph & Susoy Informational [Page 9] 507 | 508 | RFC 7076 P6R's Secure Shell Public Key Subsystem November 2013 509 | 510 | 511 | This protocol requires the client to assume that the server will 512 | correctly implement and observe attributes applied to keys. 513 | Implementation errors in the server could cause clients to authorize 514 | keys and certificates for access they were not intended to have, or 515 | to apply fewer restrictions than were intended. 516 | 517 | 7. IANA Considerations 518 | 519 | Although Section 3.1 defines four new status codes, these are in the 520 | 'Private Use' range of IANA's Publickey Subsystem Status Codes 521 | registry as defined by Section 6.6.1 ("Conventions") in [1]. No IANA 522 | actions are required for this document. 523 | 524 | 8. References 525 | 526 | 8.1. Normative References 527 | 528 | [1] Galbraith, J., Van Dyke, J., and J. Bright, "Secure Shell Public 529 | Key Subsystem", RFC 4819, March 2007. 530 | 531 | [2] Bradner, S., "Key words for use in RFCs to Indicate Requirement 532 | Levels", BCP 14, RFC 2119, March 1997. 533 | 534 | [3] Ylonen, T. and C. Lonvick, Ed., "The Secure Shell (SSH) Transport 535 | Layer Protocol", RFC 4253, January 2006. 536 | 537 | [4] Ylonen, T. and C. Lonvick, Ed., "The Secure Shell (SSH) 538 | Authentication Protocol", RFC 4252, January 2006. 539 | 540 | [5] Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 541 | 63, RFC 3629, November 2003. 542 | 543 | [6] Cooper, D., Santesson, S., Farrell, S., Boeyen, S., Housley, R., 544 | and W. Polk, "Internet X.509 Public Key Infrastructure 545 | Certificate and Certificate Revocation List (CRL) Profile", RFC 546 | 5280, May 2008. 547 | 548 | [7] ITU-T Recommendation X.690 (2002) | ISO/IEC 8825-1:2002, 549 | Information technology -- ASN.1 encoding rules: Specification of 550 | Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and 551 | Distinguished Encoding Rules (DER). 552 | 553 | 8.2. Informative References 554 | 555 | [8] OASIS, "Key Management Interoperability Protocol (KMIP) 1.1", 556 | January 2013, . 558 | 559 | 560 | 561 | 562 | Joseph & Susoy Informational [Page 10] 563 | 564 | RFC 7076 P6R's Secure Shell Public Key Subsystem November 2013 565 | 566 | 567 | Authors' Addresses 568 | 569 | Mark Joseph, PhD 570 | P6R, Inc 571 | 1840 41st Ave 572 | Suite 102-139 573 | Capitola, CA 95010 574 | US 575 | 576 | Phone: +1 888 452 2580 (x702) 577 | EMail: mark@p6r.com 578 | 579 | 580 | Jim Susoy 581 | P6R, Inc 582 | 1840 41st Ave 583 | Suite 102-139 584 | Capitola, CA 95010 585 | US 586 | 587 | Phone: +1 888 452 2580 (x701) 588 | EMail: jim@p6r.com 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | Joseph & Susoy Informational [Page 11] 619 | 620 | -------------------------------------------------------------------------------- /ssh-publickeyd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Server for the RFC 4819 "public key assistant" subsystem 3 | # 4 | # (c) 2010-2015 Mantas Mikulėnas 5 | # Released under the MIT Expat License (./LICENSE) 6 | 7 | import os, sys 8 | import tempfile 9 | import struct 10 | 11 | import authorized_keys 12 | 13 | def trace(*args): 14 | print(*args, file=sys.stderr) 15 | 16 | class Keystore(object): 17 | pass 18 | 19 | class DebugYamlKeystore(Keystore): 20 | def __init__(self, path=None): 21 | import yaml 22 | 23 | self.path = path or os.path.expanduser("~/ssh-keys.yaml") 24 | 25 | def load(self): 26 | import yaml 27 | 28 | keys = {} 29 | 30 | try: 31 | with open(self.path, "r") as fh: 32 | data = yaml.load(fh) 33 | except: 34 | return keys 35 | 36 | for item in data: 37 | keys[item["algo"], item["pubkey"]] = item["attributes"] 38 | 39 | return keys 40 | 41 | def save(self, keys): 42 | import yaml 43 | 44 | data = [] 45 | 46 | for (kalgo, kblob), attrs in keys.items(): 47 | data.append({"algo": kalgo, "pubkey": kblob, "attributes": attrs}) 48 | 49 | with open(self.path, "w") as fh: 50 | fh.write("# vim: ft=yaml:nowrap:noet\n") 51 | yaml.dump(data, fh) 52 | 53 | def list(self): 54 | keys = self.load() 55 | 56 | for (kalgo, kblob), attrs in keys.items(): 57 | yield kalgo, kblob, attrs 58 | 59 | def has_key(self, kalgo, kblob): 60 | keys = self.load() 61 | 62 | return (kalgo, kblob) in keys 63 | 64 | def add(self, kalgo, kblob, attrs): 65 | keys = self.load() 66 | 67 | keys[kalgo, kblob] = attrs 68 | self.save(keys) 69 | 70 | def remove(self, kalgo, kblob): 71 | keys = self.load() 72 | 73 | if (kalgo, kblob) in keys: 74 | del keys[kalgo, kblob] 75 | self.save(keys) 76 | return True 77 | else: 78 | return False 79 | 80 | @classmethod 81 | def knows_attribute(self, attr): 82 | return True 83 | 84 | class OpenSSHKeystore(Keystore): # {{{ 85 | def __init__(self, path=None): 86 | self.path = path or os.path.expanduser("~/.ssh/authorized_keys") 87 | 88 | def load(self): 89 | keys = {} 90 | extra_attrs = [] 91 | 92 | ATTR_PREFIX = "# attribute: " 93 | 94 | for line in open(self.path, "r"): 95 | line = line.strip() 96 | if not line: 97 | pass 98 | elif line.startswith(ATTR_PREFIX): 99 | # hack to store attributes not supported by authorized_keys 100 | name, value = line[len(ATTR_PREFIX):].split("=", 2) 101 | extra_attrs.append({ 102 | "name": name, 103 | "value": value, 104 | "critical": False, 105 | }) 106 | elif line.startswith("#:"): 107 | pass 108 | elif line.startswith("#"): 109 | extra_attrs.append({ 110 | "name": "x-comment", 111 | "value": line[1:], 112 | "critical": False, 113 | }) 114 | else: 115 | key = authorized_keys.PublicKey(line) 116 | attrs = self.convopt_openssh_to_vandyke(key.options) 117 | if len(key.comment): 118 | attrs.append({ 119 | "name": "comment", 120 | "value": key.comment, 121 | "critical": False, 122 | }) 123 | attrs += extra_attrs 124 | extra_attrs = [] 125 | keys[key.algo, key.blob] = attrs 126 | return keys 127 | 128 | def save(self, keys): 129 | with open(self.path, "w") as fh: 130 | for (kalgo, kblob), attrs in keys.items(): 131 | self._append_key(fh, kalgo, kblob, attrs) 132 | 133 | def _append_key(self, fh, kalgo, kblob, attrs): 134 | key = authorized_keys.PublicKey() 135 | key.algo = kalgo 136 | key.blob = kblob 137 | for attr in attrs: 138 | if attr["name"] == "comment": 139 | key.comment = attr["value"] 140 | key.options, unknown_attrs = self.convopt_vandyke_to_openssh(attrs) 141 | if len(key.comment): 142 | fh.write("#: %s\n" % key.comment) 143 | fpr = ":".join("%02x" % ord(c) for c in key.fingerprint()) 144 | print("#: %s" % fpr, file=fh) 145 | for attr in unknown_attrs: 146 | if attr["name"] == "x-comment": 147 | print("#%s" % attr["value"], file=fh) 148 | else: 149 | print("# attr: %(name)s=%(value)s" % attr, file=fh) 150 | print(key, file=fh) 151 | print("", file=fh) 152 | 153 | def list(self): 154 | keys = self.load() 155 | 156 | for (kalgo, kblob), attrs in keys.items(): 157 | yield kalgo, kblob, attrs 158 | 159 | def has_key(self, kalgo, kblob): 160 | keys = self.load() 161 | 162 | return (kalgo, kblob) in keys 163 | 164 | def add(self, kalgo, kblob, attrs): 165 | with open(self.path, "a") as fh: 166 | self._append_key(fh, kalgo, kblob, attrs) 167 | 168 | #keys = self.load() 169 | #keys[kalgo, kblob] = attrs 170 | #return self.save(keys) 171 | 172 | def remove(self, kalgo, kblob): 173 | keys = self.load() 174 | 175 | if (kalgo, kblob) in keys: 176 | del keys[kalgo, kblob] 177 | self.save(keys) 178 | return True 179 | else: 180 | return False 181 | 182 | attributes = ("agent", "command-override", "comment", "from", 183 | "port-forward", "x11", "x-openssh-option") 184 | 185 | @classmethod 186 | def knows_attribute(self, name): 187 | return name in self.attributes 188 | 189 | @staticmethod 190 | def convopt_openssh_to_vandyke(in_opts): 191 | tmp_attrs = [] 192 | 193 | for opt, value in in_opts: 194 | if opt == "command": 195 | tmp_attrs.append(("command-override", value)) 196 | elif opt == "from": 197 | tmp_attrs.append(("from", value)) 198 | elif opt == "no-agent-forwarding": 199 | tmp_attrs.append(("agent", "")) 200 | elif opt == "no-port-forwarding": 201 | tmp_attrs.append(("port-forward", "")) 202 | tmp_attrs.append(("reverse-forward", "")) 203 | elif opt == "no-x11-forwarding": 204 | tmp_attrs.append(("x11", "")) 205 | else: 206 | if value is True: 207 | attr_value = opt 208 | else: 209 | attr_value = "%s=%s" % (opt, value) 210 | tmp_attrs.append(("x-openssh-option", attr_value)) 211 | 212 | out_attrs = [{"name": attr[0], "value": attr[1], "critical": False} 213 | for attr in tmp_attrs] 214 | 215 | return out_attrs 216 | 217 | @staticmethod 218 | def convopt_vandyke_to_openssh(in_attrs): 219 | out_opts = authorized_keys.PublicKeyOptions() 220 | unknown_attrs = [] 221 | 222 | for attr in in_attrs: 223 | # TODO: 224 | name, value, _ = attr["name"], attr["value"], attr["critical"] 225 | if name == "agent": 226 | out_opts.append(("no-agent-forwarding", True)) 227 | elif name == "command-override": 228 | out_opts.append(("command", value)) 229 | elif name == "comment": 230 | pass 231 | elif name == "from": 232 | out_opts.append(("from", value)) 233 | elif name == "port-forward": 234 | out_opts.append(("no-port-forwarding", True)) 235 | elif name == "x11": 236 | out_opts.append(("no-x11-forwarding", True)) 237 | elif name == "x-openssh-option": 238 | if "=" in value: 239 | out_opts.append(value.split("=", 1)) 240 | else: 241 | out_opts.append((value, True)) 242 | else: 243 | unknown_attrs.append(attr) 244 | 245 | return out_opts, unknown_attrs 246 | # }}} 247 | 248 | class SshEndOfStream(Exception): 249 | pass 250 | 251 | class SshStream(object): 252 | def __init__(self, inputfd, outputfd=None): 253 | self.inputfd = inputfd 254 | self.outputfd = outputfd or inputfd 255 | 256 | def read(self, *args): 257 | buf = self.inputfd.read(*args) 258 | if not buf: 259 | raise SshEndOfStream 260 | return buf 261 | 262 | def write(self, *args): 263 | return self.outputfd.write(*args) 264 | 265 | def writef(self, *args): 266 | r = self.write(*args) 267 | if r: 268 | r = self.outputfd.flush() 269 | return r 270 | 271 | def read_u32(self): 272 | buf = self.read(4) 273 | val, = struct.unpack("!L", buf) 274 | return val 275 | 276 | def read_bool(self): 277 | buf = self.read(1) 278 | val, = struct.unpack("!?", buf) 279 | return val 280 | 281 | def read_string(self): 282 | length = self.read_u32() 283 | buf = self.read(length) 284 | return buf 285 | 286 | def read_packet(self): 287 | packet_length = self.read_u32() 288 | name = self.read_string() 289 | data_length = packet_length - (4 + len(name)) 290 | return name, data_length 291 | 292 | def write_packet(self, *data): 293 | fmt = "!L" 294 | packed = [] 295 | for datum in data: 296 | if isinstance(datum, int): 297 | fmt += "L" 298 | packed += [datum] 299 | elif isinstance(datum, bool): 300 | fmt += "?" 301 | packed += [datum] 302 | elif isinstance(datum, str): 303 | buf = datum.encode("utf-8") 304 | fmt += "L%ds" % len(buf) 305 | packed += [len(buf), buf] 306 | elif isinstance(datum, bytes): 307 | fmt += "L%ds" % len(datum) 308 | packed += [len(datum), datum] 309 | else: 310 | trace("write_packet(): unknown type %r of %r" % (type(datum), datum)) 311 | data_length = struct.calcsize(fmt) - 4 312 | buf = struct.pack(fmt, data_length, *packed) 313 | self.writef(buf) 314 | 315 | def write_status(self, code, msg): 316 | self.write_packet("status", code, msg, "en_US") 317 | # }}} 318 | 319 | class PublicKeySubsystem(object): 320 | SUCCESS = 0 321 | ACCESS_DENIED = 1 322 | STORAGE_EXCEEDED = 2 323 | VERSION_NOT_SUPPORTED = 3 324 | KEY_NOT_FOUND = 4 325 | KEY_NOT_SUPPORTED = 5 326 | KEY_ALREADY_PRESENT = 6 327 | GENERAL_FAILURE = 7 328 | REQUEST_NOT_SUPPORTED = 8 329 | ATTRIBUTE_NOT_SUPPORTED = 9 330 | 331 | statuses = { 332 | SUCCESS: "Success", 333 | ACCESS_DENIED: "Access denied", 334 | VERSION_NOT_SUPPORTED: "Protocol version not supported", 335 | KEY_NOT_FOUND: "Key not found", 336 | KEY_NOT_SUPPORTED: "Key type not supported", 337 | KEY_ALREADY_PRESENT: "Key already present", 338 | GENERAL_FAILURE: "General failure", 339 | REQUEST_NOT_SUPPORTED: "Request not supported", 340 | ATTRIBUTE_NOT_SUPPORTED: "Attribute not supported", 341 | } 342 | 343 | fatal_codes = { VERSION_NOT_SUPPORTED } 344 | 345 | def __init__(self, stream): 346 | self.keystore = None 347 | self.stream = stream 348 | 349 | def recv_list(self): 350 | for kalgo, kblob, attrs in self.keystore.list(): 351 | data = [kalgo, kblob, len(attrs)] 352 | for attr in attrs: 353 | data += attr["name"], attr["value"] 354 | self.stream.write_packet("publickey", *data) 355 | 356 | return self.SUCCESS 357 | 358 | def recv_add(self): 359 | kalgo = self.stream.read_string() 360 | kblob = self.stream.read_string() 361 | overwrite = self.stream.read_bool() 362 | num_attrs = self.stream.read_u32() 363 | attrs = [] 364 | 365 | while num_attrs: 366 | attrs.append({ 367 | "name": self.stream.read_string(), 368 | "value": self.stream.read_string(), 369 | "critical": self.stream.read_bool(), 370 | }) 371 | num_attrs -= 1 372 | 373 | if self.keystore.has_key(kalgo, kblob) and not overwrite: 374 | return self.KEY_ALREADY_PRESENT 375 | 376 | for attr in attrs: 377 | if attr["critical"] and not keystore.knows_attribute(attr["name"]): 378 | return self.ATTRIBUTE_NOT_SUPPORTED 379 | 380 | self.keystore.add(kalgo, kblob, attrs) 381 | 382 | return self.SUCCESS 383 | 384 | def recv_remove(self): 385 | kalgo = self.stream.read_string() 386 | kblob = self.stream.read_string() 387 | 388 | if self.keystore.remove(kalgo, kblob): 389 | return self.SUCCESS 390 | else: 391 | return self.KEY_NOT_FOUND 392 | 393 | def recv_listattributes(self): 394 | for attr in KnownAttributes: 395 | self.stream.write_packet("attribute", attr, False) 396 | 397 | return self.SUCCESS 398 | 399 | def handle_message(self, name, data_length): 400 | if name == b"version": 401 | ver = self.stream.read_u32() 402 | if ver == 2: 403 | self.stream.write_packet("version", ver) 404 | return None 405 | else: 406 | return self.VERSION_NOT_SUPPORTED 407 | elif name == b"list": 408 | return self.recv_list() 409 | elif name == b"add": 410 | return self.recv_add() 411 | elif name == b"remove": 412 | return self.recv_remove() 413 | elif name == b"listattributes": 414 | return self.recv_listattributes() 415 | else: 416 | trace("received unknown message %r [%r bytes]" % (name, data_length)) 417 | sys.stdin.read(data_length) 418 | return self.REQUEST_NOT_SUPPORTED 419 | 420 | def loop(self): 421 | while True: 422 | name, data_length = self.stream.read_packet() 423 | code = self.handle_message(name, data_length) 424 | if code is not None: 425 | msg = self.statuses.get(code, self.GENERAL_FAILURE) 426 | self.stream.write_status(code, msg) 427 | if code in self.fatal_codes: 428 | break 429 | 430 | def run(self): 431 | try: 432 | self.loop() 433 | except SshEndOfStream: 434 | trace("end of stream") 435 | sys.exit() 436 | 437 | def is_interactive(): 438 | try: 439 | return os.isatty(sys.stdin.fileno()) 440 | except AttributeError: 441 | return false 442 | 443 | if is_interactive(): 444 | trace("This tool is intended to be run as a SSH subsystem, not interactively.") 445 | sys.exit(2) 446 | 447 | os.umask(0o077) 448 | sys.stdin = open("/dev/stdin", "rb") 449 | sys.stdout = open("/dev/stdout", "wb") 450 | sys.stderr = open("/tmp/publickey-%d.trace" % os.getuid(), "a") 451 | 452 | stream = SshStream(sys.stdin, sys.stdout) 453 | 454 | pks = PublicKeySubsystem(stream) 455 | pks.keystore = OpenSSHKeystore() 456 | #pks.keystore = DebugYamlKeystore() 457 | pks.run() 458 | -------------------------------------------------------------------------------- /test-keys.txt: -------------------------------------------------------------------------------- 1 | # vim: nowrap:nolbr 2 | 3 | # Standard RSA and DSA 4 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQDnl0aq8MPSkSyLlUGgb0in8zE0NeXKw+r83dJ+7c6CwXaSTc57+LksRyLXIl4O7oVumHLl7CIZfmA0awKblovXT0eCF0UryDAKVfIdZr1IPeb7ruH/B3oVKAbyZ+DkZjc= RSA (standard) 5 | ssh-dss AAAAB3NzaC1kc3MAAACBAMV5fgyEyJ/v0VPGubbKAY8SpEexDk27y6kOdJDPKMLcP6rMQME+C/2S6UcwJ12y5wMBbvK0xyZCRYdQ255332cWp3E6iq+Iz3VSdAtoveBtFPMc2W8vzLHxkcUSsq0YzXqWoC/6npX6l7C+pnTyl6yZk5OjljFRMHeFY9HVZr8VAAAAFQCFZeGOYW1W6+7RDSSWUJqxcE+VJwAAAIBflwGJyfR4/jKeY6g5bBz8lt/6smAfva2xDEULb8NfYj8Kq3TgvP7j0uc90sFytexMDiVLIxrv8KUkfJrnJzNkvZbBs8JfaUp8H8fpX+fQ/P1iS6Dwvr6r0wLPVj5HBfI3zVm4PVB4GePz5GihelhRNneERqVaftwOWwkhW3XtngAAAIADXOzETbHBTfcGq8JVTh6L2bT23m2ceRZZLJQl/PJuckH2VQFOnuceiQM7if3Jhn0TOR/hXBKTL4TdXu2GutGFjjpbAhfrpRkYtCwYz18eoHF8mKKC6Ul3VJs6jdJUGwVQjbRq3jWSKUvH2F/V3sWgU+zD1EVgLkv+60IUIX1Yrg== DSS/DSA (standard) 6 | 7 | # ECDSA (common nistp# aka secp#r1 curves, using OpenSSH) 8 | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOl5Lgv1xpLXjM3V3Ns4FupEEU5M3fXzHQRpNzgnjE8odke7yl+cDHq2b2jTlT7bSvD/84C6a555VNO79BdMhhA= ECDSA with nistp256 9 | ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNWCWuLBcyRlmBy17MduoQsGlxYyVdeSBAqOF2N75OutLKxU95F50ccHY0BUgQJ1shzzRES+5gNFuXRQRfmwQAlVTNaDUKkwDSG360bG38ee6QhshMLfvGh7SzH7GkN8cw== ECDSA with nistp384 10 | ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFWRslqV72FNfdoVy3OOItjarnDzoCYhF2lKq2sO0tqRiY2fuIgNgqhr4JH+XMPdxSwaiDREgIniGofx2pdBRmrowGpUWz2HRwnudjhAvzSgKf1eD+rmLWZY6WLEG9s/TYClsDhWcfEAbncHE2wFfaDyEUq82lPqgcwtH2aAkEC2NFUWQ== ECDSA with nistp521 11 | 12 | # ECDSA (secp#k1 curves, using Bitvise WinSSHd) 13 | ecdsa-sha2-1.3.132.0.10 AAAAF2VjZHNhLXNoYTItMS4zLjEzMi4wLjEwAAAADDEuMy4xMzIuMC4xMAAAAEEE1J7KtFeIRM5MbcWcaZnvGCFyWEDZA0s0cLcBjegHv0bbOpUvOP5RO0U4tI23BGhpQEN+tfIYmqAt6R9MRjNMYw== ECDSA with secp256k1 (Bitvise WinSSHd) 14 | 15 | # EdDSA (Ed25519 curve, using OpenSSH) 16 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAd6Kuf1E7mjX3vo2tlrRn40bYoH6fyLWm5KY5tQVYR0 Ed25519 (OpenSSH) 17 | 18 | # Nonsense, but syntactically valid 19 | ssh-foo="echo \"Here's ssh-rsa for you\"",ssh-quux future-algo AAAAC2Z1dHVyZS1hbGdv X y z. 20 | 21 | # OpenSSH certificates 22 | ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAILFEJyunlz9scYU3mwbOEJoSSkeO1z20uNBw13tEn+lJAAAAICeF4LbtRqwIRhewXifa5PKpbSU9P/K8CzeVYj8J/iBoAAAAAAAAAAEAAAABAAAABHVzZXIAAAAIAAAABHVzZXIAAAAANouDYAAAAAA4a2VgAAAAHwAAAA5zb3VyY2UtYWRkcmVzcwAAAAkAAAAFeHh4eHgAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIChVgsws0E4fVJKB6YW4MGmvOkNeinq59FI6jq+0PiZPAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEA5xY/OEAJ3tgg8/KJqaBR5KMdYYRDiMJ6u4VKS9lQOV1HJQvDDvjj3F5k53BIqTJRVQx242YWs+B3C4db/uLgB user key 23 | 24 | # XMSS (OpenSSH 7.7) 25 | ssh-xmss@openssh.com AAAAFHNzaC14bXNzQG9wZW5zc2guY29tAAAAFVhNU1NfU0hBMi0yNTZfVzE2X0gxMAAAAEAcmlRJSoeeH7eV9wzgRYL3hGKfC3aSyPPBMQhWxHU6vNl2Yjj4L6mHdB+f4oSFpP+Er5qmLVg06Uz1bbr+UXwd grawity@frost 26 | 27 | # end 28 | --------------------------------------------------------------------------------