RFC 1507
DASS - Distributed Authentication Security Service
DASS: Distributed Authentication Security Service ๐
- Network Working Group ๐ธ
- RFC: 1507
- Author: C. Kaufman, Digital Equipment Corporation ๐
- Status: Experimental Protocol (see the Status section below) ๐งช
- Date: September 1993 ๐
- Obsoletes/Updates: the title block doesn't list anything this one obsoletes or updates ๐ โโ๏ธ
Part 1 of 27 ๐ฆ
Status of this Memo ๐
This memo defines an Experimental Protocol for the Internet community, so it doesn't count as an Internet standard. ๐งช The authors actually want your discussion and your ideas for making it better. ๐ซถ For the standardization state and status of this protocol, check the current edition of the "Internet Official Protocol Standards." Also, this memo can be distributed as much as you want, no limits. ๐
Table of Contents ๐ง
1. Introduction ................................................ 2
1.1 What is DASS? .......................................... 2
1.2 Central Concepts ....................................... 4
1.3 What This Document Won't Tell You ..................... 11
1.4 The Relationship between DASS and ISO Standards ....... 17
1.5 An Authentication Walkthrough ......................... 20
2. Services Used .............................................. 25
2.1 Time Service .......................................... 25
2.2 Random Numbers ........................................ 26
2.3 Naming Service ........................................ 26
3. Services Provided .......................................... 37
3.1 Certificate Contents .................................. 38
3.2 Encrypted Private Key Structure ....................... 40
3.3 Authentication Tokens ................................. 40
3.4 Credentials ........................................... 43
3.5 CA State .............................................. 47
3.6 Data types used in the routines ....................... 47
3.7 Error conditions ...................................... 49
3.8 Certificate Maintenance Functions ..................... 49
3.9 Credential Maintenance Functions ...................... 55
3.10 Authentication Procedures ............................. 63
3.11 DASSlessness Determination Functions .................. 87
4. Certificate and message formats ............................ 89
4.1 ASN.1 encodings ....................................... 89
4.2 Encoding Rules ........................................ 96
4.3 Version numbers and forward compatibility ............. 96
4.4 Cryptographic Encodings ............................... 97
Annex A - Typical Usage ........................................ 101
A.1 Creating a CA ........................................ 101
A.2 Creating a User Principal ............................ 102
A.3 Creating a Server Principal .......................... 103
A.4 Booting a Server Principal ........................... 103
A.5 A user logs on to the network ........................ 103
A.6 An Rlogin (TCP/IP) connection is made ................ 104
A.7 A Transport-Independent Connection ................... 104
Annex B - Support of the GSSAPI ................................ 104
B.1 Summary of GSSAPI .................................... 105
B.2 Implementation of GSSAPI over DASS ................... 106
B.3 Syntax ............................................... 110
Annex C - Imported ASN.1 definitions ........................... 112
Glossary ....................................................... 114
Security Considerations ......................................... 119
Author's Address ................................................ 119
Figures
Figure 1 - Authentication Exchange Overview .................... 24
1. Introduction โจ
1.1 What is DASS? ๐
Okay so, authentication is the security service that answers one big question: who actually sent this? ๐ The goal is to reliably learn the name of whoever originated a message or request. The classic way people authenticate to computers (and computers authenticate to each other) is by supplying a password. ๐ But there are a bunch of problems with password-based schemes, and DASS is here to solve them. The goal is to give you authentication in a distributed environment that's both more secure (way harder for a bad guy to impersonate a good guy) and easier to use than existing mechanisms. ๐
In a distributed environment, authentication gets really tricky ๐ญ. Users don't just log on to one machine and use the resources there. They start processes on one machine, and those processes can request services on another machine. Sometimes the second system has to request services from a third system on the user's behalf. ๐ง And with current network technology, it's fairly easy to eavesdrop on conversations between computers and pick up any passwords that might be going by. ๐
DASS uses cryptographic mechanisms to provide "strong, mutual" authentication. ๐ฆ Mutual means the two parties communicating each reliably learn the name of the other. Strong means that in the exchange, neither one obtains any information it could use to impersonate the other to a third party. This can't be done with passwords alone. ๐ You can do mutual authentication with passwords, by having a "sign" and a "counter-sign" that the two parties utter to assure each other of their identities. But whichever party speaks first reveals information that the second (unauthenticated) party can use to impersonate it. Longer sequences (the kind you see in spy movies) can't solve the problem in general. ๐ต๏ธโโ๏ธ And anyone who can eavesdrop on the conversation can impersonate either party in a later conversation, unless passwords are only good once.
Cryptography gives you a way to prove you know a secret without revealing it. ๐ People can't run cryptographic algorithms in their heads, so they can't strongly authenticate to computers directly. DASS lays the groundwork for "smart cards": microcomputers sealed inside credit cards that, once activated by a PIN, strongly authenticate to a computer. ๐ณ Until smart cards are widely available, the first link from a user to a DASS node is still vulnerable to eavesdropping. DASS mechanisms are built so that after the initial authentication, smart card and password based authentication look exactly the same. ๐ธ
Today, systems think of user identities in terms of accounts on individual computers. ๐ฉ If I have accounts on ten machines, there's no way to know a priori that those ten accounts all belong to the same individual. If I want to access a resource through any of the ten machines, I must tell the resource about all ten accounts. And I must tell the resource again when I get an eleventh account. ๐
DASS supports the concept of global identity and network login. โจ A user is assigned a name from a global namespace, and that name will be recognized by any node in the network. (In some cases, a resource may be configured as accessible only by a particular user acting through a particular node. That's an access control decision, and DASS supports it, but the user is still known by their global identity.) ๐ง Practically speaking, this means a user can have a single password (or smart card) that works on all the systems that allow them access, and access control mechanisms can conveniently give access to a user through any computer they happen to be logged into. ๐ Because a single user secret is good on all systems, it should never be necessary for a user to enter a password other than at initial login. Because cryptographic mechanisms are used, the password should never appear on the network beyond the initial login node. ๐
DASS was designed as a component of the Distributed System Security Architecture (DSSA) (see "The Digital Distributed System Security Architecture" by M. Gasser, A. Goldstein, C. Kaufman, and B. Lampson, 1989 National Computer Security Conference). ๐๏ธ It's a goal of DSSA that access control on all systems be based on users' global names, and that the concept of "accounts" on computers eventually be replaced with unnamed rights to execute processes on those computers. Until that happens, computers will keep supporting "local accounts," and access controls on resources on those systems will still be based on those accounts. ๐๏ธ Right now, within the Berkeley rtools running over the Internet Protocol suite, there's a ".rhosts database" that gives access to local accounts from remote accounts. The vision is to extend those databases so they grant access to local accounts based on DASS global names, as a bridge between the past and the future. ๐ DASS should greatly simplify administering those databases for the (presumably common) case where a user should be granted access to an account, ignoring their choice of intermediate systems. ๐
1.2 Central Concepts ๐
1.2.1 Strong Authentication with Public Keys ๐
DASS makes heavy use of the RSA Public Key cryptosystem. ๐ The important properties of the RSA algorithms for this discussion are:
- It supports the creation of a public/private key pair, where operations with one key reverse the operations of the other, but it's computationally infeasible to derive the private key from the public key. ๐งฉ
- It supports the "signing" of a message with the private key. After that, anyone who knows the public key can "verify" the signature and know that it was constructed with knowledge of the private key and that the message wasn't altered afterward. โ๏ธ
- It supports the "enciphering" of a message by anyone who knows the public key, such that only someone with knowledge of the private key can recover the message. ๐คซ
With access to the RSA algorithms, it's easy to see how you could build a "strong" authentication mechanism. ๐คฉ Each "principal" (user or computer) would construct a public/private key pair, publish the public key, and keep the private key secret. To authenticate to you, I would write a message, sign it with my private key, and send it to you. You would verify the message using my public key and know it came from me. If mutual authentication were desired, you could create an acknowledgment and sign it with your private key. I could verify it with your public key, and then I'd know you received my message. ๐
The authentication algorithms used by DASS are considerably more complex than what I just described. ๐ง That's because they have to deal with a large number of practical concerns, including subtle security threats. Some of these are discussed below. ๐
1.2.2 Timestamps vs. Challenge/Response โจ
okay so cryptosystems let you sign a message so the receiver knows the signer actually knew some secret ๐ that's the whole vibe. BUT free-standing public key auth is so expensive that nobody is signing every single message in a chat ๐ and even if they did, someone could still rearrange the messages or play them back multiple times. so auth usually happens when you set up a "connection," and the whole conversation runs under that one peer-entity authentication ๐ซถ the connection might be protected against messages getting modified or reordered, but that protection is mostly separate from the auth that happened at the start. fun fact: DASS gives you a shared key as a side effect of authentication, and that key can be used for this purpose ๐ฆ
now let's say I sign the message "It's really me!" with my private key and send it to you. you verify the signature, you know it came from me, and you give that connection access to my resources ๐ but anyone watching the network could replay that message to any server (just like a password!) and impersonate me ๐ญ so it's important that the message only gets accepted by you, and only once. I can make it useless at other servers by putting your name in the message, and you only accept it if you see your name in there ๐ keeping you from accepting it twice is the harder part ๐
there are two "standard" ways to do this replay protection: challenge/response and timestamp-based ๐ธ in challenge/response, I tell you I want to authenticate, you generate a "challenge" (usually a number), and I include that challenge in the message I sign. you only accept a message that has the recently generated challenge in it, and you make sure you never hand out the same challenge to me twice. you can do that with a sequence number, a timestamp, or a random number that's big enough that a duplicate is basically impossible ๐
in the timestamp-based scheme, I put the current time in my message. you have a rule that you won't accept messages more than, say, five minutes old, and you keep track of every message you've seen in the last five minutes ๐ง if someone replays it within five minutes, you reject it because you remember seeing it. if they replay it after five minutes, you reject it as timed out ๐
the downside of challenge/response is that it needs extra messages ๐ one-way auth could be done with a single message, and mutual auth with one message in each direction, but challenge/response always needs at least three messages.
the downside of timestamp-based is that it needs secure, synchronized time โฐ if our clocks drift apart by more than five minutes, you'll reject every single one of my auth attempts ๐ญ and if a network time service spoofer tricks you into turning back your clock, then replays an expired message, you'll accept it again. the multicast nature of existing distributed time services and the chance of getting caught make this pretty unlikely, but it absolutely has to be considered in any security analysis ๐ฅ the timestamp scheme also means the server has to keep state about every message it's seen in the clock skew interval. to be secure, that state must be kept on stable storage (unless rebooting takes longer than the permitted clock skew interval) ๐
DASS uses the timestamp-based scheme ๐ the main reasons were that auth messages could be "piggybacked" onto existing connection establishment messages, and that DASS would fit the same "form factor" (number and direction of messages) as Kerberos ๐
1.2.3 Delegation ๐ฆ
in a distributed environment, auth alone isn't enough โจ when I log onto a computer, I don't just want to prove who I am to that computer. I also want to use it to reach network resources (like file systems and database systems) on my behalf ๐ my files should (normally) be protected so I can get to them through any node I log in through. DASS lets them be protected like that without letting every system I might ever use get into those files when I'm not around ๐ซถ when I log in, my password gives my login node access to my RSA secret. it can use that secret to "impersonate" me on any requests it makes for me ๐ and it should forget every secret tied to me when I log off ๐งน that limits how much we have to trust computer systems. if someone takes over a computer, they can only impersonate the people who use that computer after the takeover, and nobody else ๐
normally, when I access a network service, I want to strongly authenticate to it ๐ I want to prove who I am to that service, but I don't want the service to learn anything that would let it impersonate me. that way I can use a service without trusting it for more than the thing it's actually delivering ๐ธ with some services, like remote login services, I might want the service to act for me when it calls other services. DASS has a mechanism for passing secrets to those services so they can impersonate me ๐ง
future versions of this architecture might allow "limited delegation," so a user could delegate to a server only the rights that server needs to do what the user wants ๐ฎ this version can only limit delegation by time, though. the info a user gives a server (other than the initial login node) can be used to impersonate them, but only for a limited period ๐ smart cards will let that time limit apply to the initial login node as well ๐ณ
1.2.4 Certification Authorities ๐
here's a flaw in the strong auth mechanism above: it assumes every "principal" (user and node) already knows the public key of every other principal it wants to authenticate ๐ฌ if I can trick a server into thinking my public key is actually yours, I can impersonate you by signing a message, saying it's from you, and letting the server verify it with the key it thinks is yours ๐
to avoid securely installing every principal's public key in every other principal's database, the "Certification Authority" was invented ๐ a certification authority is a principal trusted to act as an introduction service. each principal goes to the CA, presents its public key, and proves it has a particular name (the exact mechanisms vary with the type of principal and the level of security needed) ๐ซถ the CA then creates a "certificate," which is a message containing the principal's name and public key, an expiration date, and bookkeeping info, all signed with the CA's private key ๐ then all "subscribers" to a particular CA can authenticate to one another by presenting their certificates and proving they know the matching secret ๐ CAs only need to act when new principals are being named and new private keys are being created, so they can be kept under tight physical security ๐
the two problems with the scheme as described so far are "revocation" and "scaleability" ๐ญ
1.2.4.1 Certificate Revocation ๐
revocation is the process of announcing that a key has (or may have) fallen into the wrong hands and should no longer be accepted as proof of some identity ๐จ with certificates like the ones above, someone who learns your secret and your certificate can impersonate you forever, even after you've found out about the compromise ๐ฑ it's missing the equivalent of changing your password ๐ DASS supports two independent mechanisms for revoking certificates. a third may be added in the future ๐
method one is timeouts and renewals. part of the signed certificate message can be a time after which the certificate shouldn't be believed โณ the CA periodically renews certificates by signing a new one with a later timeout. if a key gets compromised, a new key is generated and a new certificate gets signed, and the old certificate is only valid until its timeout ๐งพ timeouts aren't perfect revocation, though. they're slow (timeouts are typically measured in months so the load on the CA and the communication with users stays manageable) and they depend on servers having an accurate source of the current time โฐ someone who can trick a server into turning back its clock can use expired certificates ๐
method two is listing all non-revoked certificates in the naming service and only believing the certificates found there โก the big advantage is that it's almost immediate (the only delay is name service "skulking" and caching delays). the disadvantages are: (1) the availability of authentication is only as good as the availability of the naming service, and (2) the security of revocation is only as good as the security of the naming service ๐ซ
a third method, which DASS doesn't support right now, is for certification authorities to periodically issue "revocation lists," which list the certificates that should no longer be accepted ๐ธ
1.2.4.2 Certification Authority Hierarchy โจ
Using a CA as an introduction service scales way better than making every principal learn every other principal's public key through some out-of-band means ๐ but it has a big problem: it creates a central point of trust ๐ฌ. The CA can impersonate any principal by inventing a new key and making a certificate that says the new key represents that principal. In a big organization there might not be any single person trusted enough to run the CA ๐. Even if there were, it would be impractical for every individual to authenticate to that one person. Replicating the CA fixes the availability problem but makes the trust problem worse ๐. When authentication is used in a global context, like between companies, the idea of a single CA is untenable.
DASS fixes this by creating a hierarchy of CAs ๐. The CA hierarchy is tied to the naming hierarchy. For each directory in the namespace, there is a single CA responsible for certifying the public keys of its members. That CA also certifies the public keys of the CAs of all child directories and of the CA of the parent directory. Thanks to this cross-certification, knowing the public key of any CA lets you verify a series of intermediate CAs and, finally, the public key of any principal ๐ฆ.
Because the CA hierarchy is tied to the naming hierarchy, the trust placed in any individual CA is limited ๐ซถ. If a CA is compromised, it can impersonate the principals listed in its directory, but it cannot impersonate arbitrary principals ๐ก๏ธ.
DASS gives every principal a way to know the public key of its "parent" CA, meaning the CA controlling the directory where it's named ๐. Here's the result, the rules for what a compromised CA can actually do:
- a) A CA can impersonate any principal named in its directory.
- b) A CA can impersonate any principal to a server named in its directory.
- c) A CA can impersonate any principal named in a subdirectory to any server not named in the same subdirectory.
- d) A CA can impersonate to any server in a subdirectory any principal not named in the same subdirectory.
So a compromise low in the naming tree takes down all principals below that directory, while a compromise high in the tree only messes with authentication for principals that are far apart in the naming hierarchy ๐ณ. Especially when multiple organizations share a namespace (like in X.500), a compromised CA in one organization can not result in false authentication within another organization ๐.
DASS uses the X.500 directory hierarchy for principal naming ๐. At the top are names of countries. National authorities aren't expected to set up certification authorities (at least initially), so a different mechanism has to be used to authenticate entities that are "distant" in the naming hierarchy. DASS's answer is the "cross-certificate", where a CA certifies the public key of some CA or principal that isn't its parent or child. By limiting certificate chains to parent certificates, then a single "cross certificate", then child certificates, a DASS implementation can avoid needing CAs near the root of the tree, or avoid having to trust them even if they do exist ๐ง . A special case can also be supported, where a global authority whose name is not the root certifies the local roots of independent "islands" ๐๏ธ.
1.2.5 User vs. Node Authentication ๐ฏ
In concept, DASS mechanisms support mutual authentication of two principals whether those principals are people, computers, or applications ๐ค. They've been extended, though, to handle a super common case: a pair of principals acting together (a user and a node) authenticating to a single principal (a remote server). This is done by having, optionally, two sets of secrets in each credentials structure, one for the user and one for the node ๐. When authentication uses such credentials, both secrets sign the request so the receiving party can verify that both principals are present โ๏ธ.
This setup has a bunch of perks. It lets access controls be enforced based on both the identity of the user and the identity of the originating node ๐. It also makes it possible to define users of systems who have no network-wide identities, and those users can access network resources based on node credentials alone. The security is weaker, because a node can impersonate all of its users even when they're not logged in ๐ญ. But it offers an easier transition from existing global identities for all users ๐.
1.2.6 Protection of User Keys ๐
DASS mechanisms generally deal with authentication between principals who each know a private key. For principals who are people, special mechanisms are provided for maintaining that private key ๐๏ธ. In many cases it'll be most convenient to keep passwords as secrets instead of private keys. This architecture specifies a way of storing private keys encrypted under passwords. That would be as secure as hiding a private key, except people tend to pick passwords from a small space (like words in a dictionary), so a password is way easier to guess than a private key ๐ฉ. To deal with this weakness, DASS specifies a protocol between a login node and a login agent, so the login agent can audit and limit the rate of password guesses ๐ฆ. Using these features is optional. A user with a smart card could store a private key directly and bypass all of these mechanisms ๐ณ. If users can be forced to choose "good" passwords, the login agent could be eliminated and encrypted credentials could be stored directly in the naming service ๐ง.
Another way user keys are protected: the architecture doesn't require them to be available except briefly at login ๐ซง. This reduces the threat of a user walking away from a logged-on workstation and someone else taking over the workstation and extracting their key ๐. It also makes RSA-based smart cards practical, since the card can keep the user's private key and do one signature operation at login to authenticate an entire session โจ.
1.3 What This Document Won't Tell You ๐
Architecture documents are difficult to read by nature, and this one is no exception ๐ . The reason is that an architecture document contains the details needed to build interoperable implementations, but it isn't a design specification. It goes out of its way to leave out any details an implementation could choose without affecting interoperability. It also doesn't specify all the uses of the services it provides, because those services are properly regarded as general-purpose tools ๐ ๏ธ.
The rest of this section has info that isn't really part of the authentication architecture, but it may help you understand why the architecture is the way it is ๐ง .
1.3.1 How DASS is Embedded in an Operating System ๐ป
Architecturally, DASS doesn't need any operating system support to be used by an application (other than the services listed in Section 2). Still, actual implementations are expected to be closely tied to the operating systems of host computers ๐ค. This is done for both security and convenience.
When a user logs into a node, a set of credentials gets created for that user and the operating system associates it with all processes started by or on behalf of the user ๐โโ๏ธ. When a user delegates to a service, the remote operating system is expected to accept the delegation and start the remote process with the delegated credentials ๐ค. Most nodes are expected to have credentials of their own and support the concept of user accounts. When user credentials are created, the node is expected to verify them in its own context, figure out the right user account, and add node credentials to the created credentials set ๐ซถ.
1.3.2 Forms of Credentials ๐
In DASS there's a single data structure called "Credentials" with a large number of optional parts ๐ฆ. In an implementation, not all of the architecturally allowed subsets will necessarily be supported, and credentials structures with different subsets of the data may be implemented quite differently.
The major categories of credentials likely to be supported in an implementation are:
- Claimant credentials - these are the credentials that would normally be associated with a user process so it can create authentication tokens. They'd contain the user's name, login ticket, session private key, and (at least logically) local node credentials and cached outgoing contexts ๐.
- Verifier credentials - these are the credentials that would normally be associated with a server, which has to verify tokens and produce mutual authentication response tokens. Since servers may be started by a node on demand, some representation of verifier credentials has to exist independent of a process. If an operating system wants to authenticate a request before starting a server process, the credentials must exist in usable form. An implementation may choose to have all services on a "node" share one verifier credentials structure, or it may give each service its own ๐ธ.
- Combined credentials - architecturally, a server may have a structure that's both claimant credentials and verifier credentials combined, so the server can act in either role using a single structure. The contents overlap a bit. There is no requirement, however, that an implementation support such a structure ๐ฆ.
- Stub credentials - in the architecture, a credentials structure is created whenever a token is accepted. If delegation took place, these are claimant credentials that their possessor can use to create additional tokens. If no delegation took place, this structure exists as an architectural placeholder that an implementation may use to try authenticating user and node names. An implementation might choose to implement stub credentials with a different mechanism than claimant or verifier credentials. In particular, it might do whatever user and node authentication is useful on its own and not support this structure at all ๐ โจ.
1.3.3 Support for Alternative Certification Authority Implementations โจ
okay so a huge part of why DASS is designed the way it is? protecting certification authorities from getting compromised ๐๐ CAs only get used to make certs for new principals and to renew them when they expire (expiration intervals are probably measured in months, so not every day ๐). that means they don't need to be super highly available. for max security, a CA could live on a standalone PC where the hardware, software, and keys get locked in a safe whenever it's not in use ๐. the certs the CA generates have to get delivered to the naming service to be registered, and one possible way is for the CA to have an RS232 line to an online component that can pass along certificates and related info but not login sessions. the whole point is to make it implausible to mount a network attack against the CA. or the certs could just be carried over to the network on a floppy disk ๐พ (yes, really ๐)
to keep CAs secure, a whole host of design details must be done right ๐ the most important one is the design of user and system manager interfaces that make it hard to "trick" someone into doing the wrong thing, like certifying an impostor or revealing a key ๐ key generation also must be carefully protected so the generated key can't be guessed (because of lack of randomness) and isn't recorded anywhere a penetrator can grab it. and because a cert has relatively little human-readable info (its biggest components are UIDs and public keys), it's a real challenge to design a user interface that makes sure the human operator only authorizes signing intended certs ๐ญ these considerations are beyond the scope of the architecture (since they don't affect interoperability), but they did affect the design in subtle ways ๐ฆ in particular, DASS does not assume uniform security throughout the CA hierarchy, and it's designed so that a compromise of a CA in one part of the hierarchy doesn't have global implications ๐
the architecture doesn't require that CAs be off-line โจ a CA could be software that runs on any node once the proper secret is installed. admin convenience can come from integrating the CA with account registration utilities and naming service maintenance. but then the CA would have to be on-line when in use in order to register certificates in the naming service. the CA key could be unlocked with a password, entered on each use, which both authenticates the CA operator and makes sure compromise of the host node while the CA isn't in use won't compromise the CA. ngl this design would be open to attacks based on planting Trojan horses in the CA software ๐ but it's entirely interoperable with a more secure implementation, so it's a valid choice ๐ซถ realistic tradeoffs must be made between security, cost, and administrative convenience, keeping in mind that a system is only as secure as its weakest link, and that there's no benefit in making the CA substantially more secure than the other components of the system ๐
1.3.4 Services Provided vs. Application Program Interface ๐ง
okay so Section 3 of this document specifies "abstract interfaces" to the services DASS provides ๐ it tells you what services are provided, what parameters the caller supplies, and what data gets returned. it does not specify the calling interfaces ๐ calling interfaces may be platform, operating system, and language dependent. they don't affect interoperability, so different implementations with completely different calling interfaces can still talk to each other over a network ๐ธ they do affect portability though. a program that runs on one platform can only run on another that implements an identical API ๐ญ
so to support portability of applications, not just between DASS implementations but between DASS and Kerberos implementations, a "Generic Security Service API" has been designed and is outlined in Annex B โจ this API could be the only "published" interface to DASS services. BUT (lowkey a big but) it doesn't give access to all the functions DASS provides, and it provides some non-DASS services too ๐ it doesn't give access to the "login" service, for example, so the login function can't be implemented in a portable way. clearly an implementation has to provide some implementation of the login function, though maybe only to one system program, and that implementation need not be portable ๐ similarly, the Generic API provides no access to node authentication information, so applications that use those services may not be portable either ๐ฅบ
the Generic API provides services for encryption of user data for integrity and possibly privacy. these aren't specified as part of the DASS architecture, because we envisioned that such services would be provided by the communications network and not in applications ๐ฆ the Generic API provides them anyway because they're provided by Kerberos, there exist applications that use them, and they're desired in the context of the IETF-CAT work. the DASS architecture includes a Key Distribution service so that the encryption functions of the Generic API can be supported and integrated, and Annex B specifies how those services can be implemented using DASS services ๐
the Services Provided also differ from the GSSAPI because there are important extensions envisioned to the API for future applications, and it was important to make sure those services were architecturally available ๐ฅ in particular, DASS lets a principal have multiple aliases and lets the receiver of an authentication token verify any one of them ๐ we want DASS to support the case where a server only learns the name it's trying to validate while evaluating an ACL, which can be long after a connection is accepted. so the Services Provided section separates the Accept_token function from the Verify Principal Name โจ the other motivation behind a different interface is that DASS provides node authentication, meaning the ability to authenticate the node a request originates from as well as the user ๐ because Kerberos provides no such mechanism, the capability is missing from the GSSAPI, but we expect some applications will want to make use of it ๐
1.3.5 Use of a Naming Service ๐ธ
with the exception of the syntactical representation of names (which is tied to X.500), the DASS architecture is designed to be independent of the particular underlying naming service โจ the intention is that certificates get stored in an X.500 naming service, in the fields architecturally reserved for this purpose in the standard. but this specification allows for the possibility of different forms of certificate stores, so it's not locked in ๐ฆ the SPX implementation of DASS implements its own certificate distribution service, because they did not want to introduce a dependency on an X.500 naming service ๐
1.3.6 Key Hiding - Credentials ๐
the abstract interfaces described in Section 3 specify that "credentials" and "keys" are the inputs and outputs of various routines ๐ credentials structures in particular contain secret information that should not be made available to the calling application ๐ in most cases, keeping that info away from apps is simply a matter of prudence, because a misbehaving application can do nearly as much damage with the credentials as it can by using the secrets directly ๐ having access to the keys themselves may let an app bypass auditing, or leak a key to an accomplice who can use it on another node where a lot of activity is less likely to get noticed ๐ฑ in some cases, most dramatically where a "node key" is present in user credentials, it's vital that the contents of the credentials be kept out of the hands of applications ๐จ
to accomplish this, a concrete interface is expected to create "credentials handles" that get passed in and out of DASS routines ๐ the credentials themselves would be kept in some portion of memory where unprivileged code can't get at them ๐ซถ
there's another aspect of how credentials get used that matters for real implementations ๐ in normal use, a user creates a set of credentials while logging on to a system, and then uses them from many processes or jobs. when many processes share a set of credentials, it's important for performance that they share one set rather than each getting a copy made, because information gets cached in credentials as a side effect of some requests, and for good performance those caches should be shared โญ
picture this: a system is running a series of copy commands moving files from one system to another ๐ง the user's credentials were established when they logged on. the first time a copy is requested, a new process starts up, opens a connection to the destination system, and creates a token to authenticate itself. creating that token is an expensive operation, BUT information gets computed and "cached" in the credentials structure, which lets any subsequent tokens on behalf of that user to that server be computed cheaply ๐ฅ after the copy completes, the connection is closed and the process terminates. then in response to a second copy request, another new process is created and a new token computed. for this operation to get a performance benefit from the caching, the information computed by the first process has to somehow make it to the second one ๐ญ
a model for how this caching might work is the way Kerberos caches credentials ๐ Kerberos keeps credentials in a file whose name can be computed from the name of the local user. that file is initialized as part of the login process, and its protection is set so that only processes running under the UID of the user may read and write it ๐ processes cache information there, and all processes running on behalf of the user share the file โจ
but there are two problems with this scheme ๐ first, on a diskless node, putting information in a file exposes it to eavesdroppers on the network. second, it doesn't accomplish the "key hiding" function described earlier in this section ๐ in a more secure implementation, the kernel or a privileged process would manage some "pool" of credentials for all processes on a node, and would grant access to them only through the DASS calls ๐ credentials structures are complex and varying length, so DASS may organize them as a set of pools rather than as contiguous blocks of data ๐ฆ all such design issues are "beyond the scope of the architecture" ๐ implementations must decide how to control access to credentials. they could copy the Kerberos scheme of having credentials available to processes with the UID of the login session that created them, and to privileged processes, or there may be a more elaborate mechanism for "passing" credentials handles from process to process ๐ this design should probably follow the operating system mechanisms for passing around local privileges ๐ซถ
1.3.7 Key Hiding - Contexts ๐ง
okay so the GSSAPI has this thing called a security context, and it has some of the same key hiding problems as a credentials structure ๐ญ Contexts are what get used when you cryptographically protect user data, either from being modified or from being disclosed and modified. They use keys that were established during authentication ๐ The "services provided" spec says create_ and accept_token return a "shared key" and an "instance identifier" ๐
The GSSAPI, though, says you get back a context handle, and that handle is an integer. A secure implementation would keep the key and instance identifier in protected memory and only let you touch them through the interfaces it provides. no cap, that's the move ๐ก๏ธ
Unlike credentials, there's probably no need for mechanisms that let contexts be shared between processes ๐ โโ๏ธ Contexts are normally tied to some kind of communications "connection," and the ends of a connection usually aren't shared. If an implementation decides to offer extra services like message sequencing or duplicate detection, then contexts will have to carry additional fields. And those fields can be created and maintained without any extra authentication services โจ
1.4 The Relationship between DASS and ISO Standards ๐ธ
This section gives you an intro to DASS authentication in terms of the ISO Authentication Framework (DP10181-2) ๐ The whole point is to give you an intuitive feel for how DASS works and how its mechanisms and terminology line up with the standards. Yes, some important details get skipped here, but they're all spelled out in section 3, so don't panic ๐ซถ
1.4.1 Concepts
The main goal of authentication is to prevent impersonation, which is basically pretending to be someone you're not ๐ญ Authentication always involves some form of identification. Without it, anyone could claim to be whoever they wanted and literally get away with it ๐
If it didn't matter who you were talking to, you wouldn't need fancy authentication procedures at all ๐ But in most systems, especially timesharing and distributed processing environments, people's rights are often limited by security policy. Authorization (identity based access control) and accountability (audit) provisions could be bypassed if masquerading attempts were impossible to prevent or detect. That's a whole mess ๐ฉ
Almost all practical authentication mechanisms that work in distributed environments depend on knowing some secret information ๐คซ The big differences are in how you show evidence that you know the secret. Some schemes, like the classic simple use of passwords, are pretty easy to attack. The threats to authentication can be sorted into three buckets:
- forgery, attempting to guess or otherwise fabricate evidence;
- replay, where one can eavesdrop upon another's authentication exchange and learn enough to impersonate them; and
- interception, where one slips between the communicants and is able to modify the communications channel unnoticed.
Most of these attacks can be countered with what's called strong authentication ๐ช Strong authentication refers to techniques that let you prove you know a particular secret without revealing even a hint about it. So neither the entity you're authenticating to, nor an eavesdropper on the conversation, can use an authentication exchange to get better at impersonating the authenticating principal later on ๐ โโ๏ธ
Strong authentication mechanisms, especially the ones used here, rely on cryptographic techniques. DASS in particular uses public key cryptography ๐
ngl this part matters: interception attacks can't be countered by strong authentication alone. They generally need extra security mechanisms to protect the communication channel, like data encryption ๐จ
1.4.2 Principals and Their Roles
All authentication happens on behalf of principals ๐ In DASS, these types of principals are recognized:
- user principals, normally people with accounts who are responsible for performing particular tasks. Generally it's users who are authorized to do things by virtue of having been granted access rights, or who are to be held accountable for specific actions subject to being audited. ๐ฉโ๐ป
- server principals, which are accessed by users. ๐ฅ๏ธ
- node principals, corresponding to locations where users and servers, or more accurately, processes acting on behalf of principals can reside. ๐
Principals can act in one of two capacities ๐ฌ
- the claimant is the active entity seeking to authenticate itself, and
- the verifier is the passive entity to whom the claimant is authenticating.
Users are normally claimants, while servers are usually verifiers. Although, sometimes servers can also be claimants, so keep your eyes open ๐
There's one more kind of principal:
- certification authorities (CA's) issue certificates which attest to another principal's public key. ๐๏ธโจ
1.4.3 Representation, Delegation and Representation Transfer
Okay, here's the vibe. Users are responsible for what the computer does, but humans can't directly do anything inside a computer system ๐ What actually does the useful work is a process running on behalf of a user. For security-controlled functions, that process is the agent, or representative, of the user, and the user has authorized it to do things on their behalf ๐ง In ISO Authentication Framework terms, the user is said to have a representation in the process.
That representation has to come into existence somehow ๐ฑ Delegation is the act of creating a representation. A user is said to create a representation for themselves by delegating to a process. If the user creates another process, say by doing an rlogin on a different computer, a representation may be needed there as well. This may be done automatically by a process known as representation transfer. DASS uses the term delegation to also mean creating additional representations on remote systems too ๐
A representation is instantiated in DASS as credentials. Credentials include the identity of the principal as well as the cryptographic "state" needed to do strong authentication ๐ Claimant information in credentials lets principals authenticate themselves to others, whereas verifier information in credentials lets principals verify the claims of others ๐ Credentials meant mainly for a claimant will be called claimant credentials in what follows, and credentials meant mainly for verification will be called verifier credentials. A particular set of credentials may or may not contain all of the data needed to be used in both roles. That depends on how the credentials were created ๐คทโโ๏ธ
In some contexts, though not here, the idea of representation and/or delegation is sometimes called proxy. That term is used in ECMA TR/46. We avoid it because it could get confused with an unrelated use of the term in the context of DECnet ๐
1.4.4 Key Distribution, Replay, Mutual Authentication and Trust
Strong authentication uses cryptographic techniques ๐ง The particular mechanisms used in DASS end up distributing cryptographic keys as a side effect. These keys are suitable for providing a data origin authentication service and/or a data confidentiality service between a pair of authenticated principals ๐
Replay detection is provided by putting timestamps on relevant authentication messages, combined with remembering previously accepted messages until they go "stale" โฐ That's different from other techniques, like challenge and response exchanges ๐
Authentication can be one-way or mutual ๐ค In one-way authentication, only one party (in DASS, the claimant) authenticates to the other. Mutual authentication adds authentication of the verifier back to the claimant. In certain communications schemes, like connectionless transfer, only one-way authentication is meaningful. DASS supports mutual authentication as a simple extension of one-way authentication for environments where it makes sense ๐ฆ
DASS can potentially allow many different "trust relationships" to exist ๐ All principals trust one or more CA's to safeguard the certification process. Principals use certificates as the basis for authenticating identities, and they trust that the CA's issuing certificates act responsibly. Users expect CA's to make sure that certificates (and related secrets) are only made for principals that the CA knows or has properly authenticated on its own. Trust is the whole thing, bestie ๐ซถ
1.5 An Authentication Walkthrough ๐ฆ
The OSI Authentication Framework describes authentication as happening in six phases โญ This section tries to describe DASS in those terms.
1.5.1 Installation
In this phase, principal certificates get created, along with the additional information needed to create claimant and verifier credentials ๐ OSI defines three sub-phases:
- Enrollment. In DASS, this is the definition of a principal in terms of a key, name and UID.
- Validation, confirmation of identity to the satisfaction of the CA, after which the CA generates a certificate.
- Confirmation. In DASS, this is the act of giving the user the certificate and the CA's own name, key and UID, followed up by the user creating a trusted authority for that CA. A trusted authority is a certificate for the CA signed by the user. ๐
Included in this step in DASS is posting the certificate so it's available to principals who want to verify the principal's identity ๐ฎ Also, the user principal saves the trusted authority so it's available when the user creates credentials ๐พ
1.5.2 Distribution
DASS distributes certificates by placing them in the name service. ๐
1.5.3 Acquisition ๐
Whenever principals wanna authenticate to each other ๐ , they hit up the Name Service to grab whatever public key certificates they need and whip up their credentials. In DASS, acquisition literally means obtaining credentials ๐
Okay so, claimant credentials are how a principal gets represented inside a process. More precisely, they give a process a representation of the principal to use ๐ญ The iconic part is that the principal delegates to a temporary delegation key while doing this, so the claimant's long term principal key doesn't have to hang around in the system ๐
You make claimant credentials by calling the get credentials primitive ๐ They're a DASS specific data structure with:
- a name
- a ticket, which is a data structure containing:
- a validity interval,
- UID, and
- (temporary) delegation public key, along with a
- digital signature on the above, made with the principal private key
- the delegation private key
Optionally, there can also be credential info about the node the user is logged into and the account on that node โจ You'll find a full breakdown of everything credentials can hold in section 3. Verifier credentials are made with initialize_server ๐ก๏ธ and they hold a principal (long term) private key. Why? Because servers usually need to keep running indefinitely without anyone re-entering a long term key ๐ญ That's the whole vibe.
Claimants and verifiers also each have a trusted authority, which is basically info about a trusted CA. That info is made up of:
- its name (this will appear in the
"issuer"field in principal certificates), - its public key (to use in verifying certificates issued by that CA), and
- its UID.
Principals use trusted authorities to verify certificates for other principals' public keys ๐ต๏ธ CAs form a hierarchy that mirrors the naming hierarchy, and every directory in the naming hierarchy is controlled by a single CA. Each CA certifies three kinds of CAs: the CA of its parent directory, the CAs of each of its child directories, and (optionally) CAs elsewhere in the naming hierarchy. That last bit is mainly for when the directories up to a common ancestor don't have CAs ๐ณ
Even though a principal only has a single CA as a trusted authority, it can still securely obtain the public key of any principal in the namespace by "walking the CA hierarchy" ๐ถโโ๏ธโจ
1.5.4 Transfer ๐ฆ
The DASS exchange of authentication information is illustrated in Figure 1-1. During the transfer phase, the DASS claimant sends an authentication token to the verifier ๐จ Authentication tokens are made by invoking the create_token primitive. The authentication token is cryptographically protected and specified as a DASS data structure in ASN.1. It includes:
- a ticket,
- a DES authenticating key, encrypted using the intended verifier's public key
- one of the following:
- if delegation is not being performed, a digital signature on the encrypted DES key using the delegation private key, or
- if delegation is being performed, sending the delegation private key, DES encrypted using the DES authenticating key
- an authenticator, which is a cryptographic checksum made using the DES authenticating key over a buffer containing:
- a timestamp
- any application supplied "channel bindings". For example, addresses or other context information. The purpose of this field is to thwart substitution and replay attacks ๐
- additional optional information concerning node authentication and context.
As a side effect, after init_authentication_context, the caller gets a local authentication context, which is a data structure containing:
- the DES key, and
- if mutual authentication is being requested, the expected response.
To build an authentication token, the claimant needs to access the verifier's public key certificate from the Name Service (labeled CDC, for Certificate Distribution Center, in the figure) ๐
Quick note ๐ an authenticator can only be used once, but it's totally allowed to re-establish the same local authentication context multiple times. The ticket and DES key establishment components of the token can have a relatively long lifetime ๐ซถ That's a performance win: if you cache authentication contexts (along with other components from a successfully used token and the associated verified principal public key value), you can skip repeating the public key operations. And creating (and verifying) "fresh" authenticators from a cached authentication context is a relatively inexpensive operation โจ
Claimant Actions | Communications | Verifier Actions
| |
verifier name | |
| | |
| | +---+|
\------------------->| ||
trusted | | ||
authorities | |CDC||
| +-----------+ |certificate| ||
| | Verify |<-------------| ||
\--->|Certificate| | +---+|
+-----------+ | |
Claimant | | |
credentials Verifier | | Verifier
| Public Key | | Credentials
| | | | |
| V | | V
| +-----------+ | Authentication | +-----------+
| | Make | | Token | | Check | Replay
\--->| Token |-------------------->| Token |<-->Cache
+-----------+ | | +-----------+
DES <---/ | | | | | \----->DES
key | | | /Claimant key
| | |/Public Key
| | / | trusted
| | Claimant /| V authorities
| |+---+ Name / | +-----------+ |
authentication || |<-------/ | | Verify |<----/
context || |certificate| |Certificate|
| ||CDC|------------>| |-->accept/
| || | | +-----------+ reject
| || | | | \
| |+---+ |authentication\
V | mutual | context V
+-----------+ | authentication | | claimant
/--| Accept | | response | +----------+credentials
V | Mutual |<--------------------| Make |(delegation)
accept/ +-----------+ | | | Response |
reject | | +----------+
| |
Figure 1 - Authentication Exchange Overview
1.5.5 Verification ๐
When an authentication token arrives, the verifier pulls out the DES key using its verifier credentials ๐ Then it accesses the Name Service (labeled CDC for Certificate Distribution Center) to grab the certificates it needs for the cryptographic checks on the incoming info, and verifies all of the signatures on the received certificates and the authentication token. Verification can result in creating new claimant credentials if delegation is performed ๐
As part of this process, verified authenticators are retained for a suitable timeout period โณ
1.5.6 Unenrolment ๐๏ธ
Unenrolment is when info gets removed from the Name Service ๐ฎ The only other form of revocation DASS supports is certificate timeout. Every certificate has an expiration time (in ordinary use, about a year from its signing date). DASS does not currently support the revocation lists in X.509 ๐ฌ
2. Services Used ๐ง
Besides the operating system services it needs to keep its internal state going, DASS leans on three things: a global distributed database to store its certificates, a reliable source of time, and a source of random numbers for creating cryptographic keys ๐ฒ๐
2.1 Time Service โฐ
DASS needs the current time for several of its algorithms. Some uses of time are security critical, and in others, network synchronization of clocks is required. DASS does not, however, depend on having a single source of time that is both secure and tightly synchronized ๐
The requirements on system provided time are:
- Validating certificates and tickets: The system needs the date and time accurate to within a few hours, with no particular synchronization requirements. If this time is inaccurate, valid requests may be rejected and expired messages may be accepted. Certificate expiration is a backup revocation mechanism, so this can only cause a security compromise in the event of multiple failures. In theory, this could be provided by having a local clock on every node accurate to within a few hours over the life of the product to provide this function.
If an insecure network time service is used to provide this time, there are theoretical security threats, but they are expected to be logistically impractical to exploit ๐ค
- Detecting replay of authentication tokens: The system needs a strictly monotonic time source that is reasonably synchronized across the network (within a few minutes) for the system to work. Inaccuracy does not present a security threat except as noted below. It may constitute an availability threat because valid requests may be rejected. To get strict monotonicity in the presence of a rapid series of requests, time must be returned with high precision. There is no requirement for a high degree of accuracy.
Inaccurate time could present a security threat in this scenario: if a client's clock is made sufficiently fast that its tokens are rejected, someone harvesting those tokens from the wire could replay them later and impersonate the client. In some environments, this might be an easier threat than harvesting tokens and preventing their delivery ๐
- Aging stale entries from caches: DASS requires reasonably accurate timing of intervals. To the extent that intervals are reported as shorter than they actually were, revocation of certificates from the naming service may not be as timely as it should be โฑ๏ธ
2.2 Random Numbers ๐ฒ
okay so DASS needs a source of "cryptographic quality" random numbers to make keys ๐ and cryptographic quality basically means this: if you know any of the "random numbers" from a series AND all the unprotected state info, an attacker still can't predict any of the other numbers in that series ๐. Hardware sources are the ideal โจ, but there are other techniques that MAY also be acceptable ๐ . For example, a 56 bit "truly random" seed (think a bunch of coin tosses ๐ช) could be used as a DES key to encrypt an infinite length known text block in CBC mode, which makes a pseudo-random sequence, as long as the key and the current spot in the sequence are adequately protected ๐. There is a LOT of drama about what counts as cryptographic quality random numbers, and this doc is not trying to settle that fight ๐ซถ.
2.3 Naming Service ๐
DASS creates and uses "certificates" for every principal in the system, plus encrypted credentials for most of them ๐. All of this info lives in an online service tied to the principal being certified. The long term vision is an X.500 naming service, and DASS will authenticate X.500 names from day one โจ. To avoid depending on an X.500 naming service being around (and to get the perks of a "login agent" that controls password guessing ๐ก๏ธ), there's also an alternative certificate distribution center protocol described here.
The specific requirements DASS places on the naming service are:
- Highly available ๐: It MUST be highly available. A user's naming service entry MUST be available to any node where the user wants to get services (or service gets denied ๐ซ). A server's naming service entry MUST be available from any node that wants to invoke the service (or, again, service is denied).
- Timely โฐ: It MUST be timely. The presence of "stale" info in the naming service MAY cause some problems ๐ฌ. When a password changes, the old password MAY remain valid (and the new one invalid) to the extent the naming service serves stale info. When a user or server joins the network, it can't take part in authentication until the info added to the naming service is available at the node doing the authenticating. In the unusual case that a key changes, the entity whose key changed can't use the new key until the new certificate is uniformly available.
- Secure ๐: It MUST be secure with regard to certain specific properties. In general, the security of DASS protected applications does not depend on the security of the naming service. It is expected that the availability needs of the naming service will stop it from being as secure as some applications need to be. There are two aspects of DASS security that DO depend on the naming service's security: timely revocation of certificates, and protection of user secrets against dictionary based password guessing ๐ต๏ธ. DASS depends on removing certificates from the naming service so they get revoked faster than waiting for them to time out. For this mechanism to provide any actual security, it MUST NOT be possible for a network entity to "impersonate" the naming service, and the naming service MUST be able to enforce access controls that stop a revoked certificate from being reinstated by an unauthorized entity. In the long run, DASS itself is expected to secure the naming service, which brings some potential recursion problems (to be addressed in the naming service design) ๐. If the naming service isn't authenticated (as is expected in early versions), attacks where a revoked certificate gets "reinstated" through impersonation of the naming service are possible ๐.
The specific functions DASS requests of the naming service are simple:
- Given an X.500 name, store a set of certificates associated with that name.
- Given an X.500 name, retrieve the set of certificates associated with that name.
- Given an X.500 name, store a set of encrypted credentials associated with that name.
- Given an X.500 name, retrieve a set of encrypted credentials associated with that name.
Implementations over a particular naming service MAY add more specialized functions for efficiency ๐. For example, the certificates associated with a name MAY be split into several sets (child, parent, cross, self) so that only the relevant ones get retrieved. For access to the naming service itself to be secure, the protocols SHOULD be authenticated. Certificates SHOULD generally be readable without authentication, to avoid recursion problems. Requests to read encrypted credentials SHOULD be specialized and SHOULD include proof of knowledge of the password, so the naming service can audit and slow down false password guesses ๐ โโ๏ธ.
The following sections describe the interfaces to specific naming services:
2.3.1 Interface to X.500 ๐
Certificates for a particular name are stored as attributes of the entry, as specified in X.509 โจ. X.509 defines attributes for parent and cross certificates (CrossCertificatePair, CACertificate) for some principals, but we'll have to define a DASSUserPrincipal object class that includes these attributes so we can use them with ordinary users. Retrieval happens through normal X.500 protocols. Certificates SHOULD be world readable and modifiable only by appropriate authorities.
Encrypted credentials are stored with the principal's entry under an attribute that's yet to be defined. The credentials SHOULD be encoded as specified in section 4. In the absence of extensions to the X.500 protocol that control password guessing, the encrypted credentials SHOULD be world readable and updatable only by the named principal and other appropriate authorities.
2.3.2 Interface to CDC ๐ง
The CDC (Certificate Distribution Center) is a special purpose name server built to serve DASS until an X.500 service is available in all the environments where DASS needs to operate ๐ธ. The CDC talks to DASS clients using a special purpose protocol that was designed for efficiency, simplicity, and security. CDCs use DASS as an authentication mechanism, and they protect encrypted credentials from unaudited password guessing.
Each DASS client keeps a list of CDCs and the portion of the namespace each one serves. Each directory has a master replica, which is the only one that will accept updates. The CDCs keep consistent with one another using protocols that are beyond the scope of this document. When a DASS client wants to make a request of a CDC, it opens a TCP or DECnet connection to the CDC and sends an ASN.1 (BER) encoded request, then receives a matching ASN.1 (BER) encoded response. Clients are expected to learn the IP or DECnet address and port number of the CDC that supports a particular name from a local configuration file. To maximize performance, the requests bundle what would otherwise be several requests for individual certificates ๐. It is intended that all the certificates needed for an authentication operation be retrievable with at most two CDC requests/responses (one to the client's CDC and one to the server's CDC) ๐ซ.
This document describes the protocol a DASS client would use to retrieve certificates and credentials from a CDC and to update a user password. This protocol does not provide for updates to the certificate and credential databases. Such updates MUST be supported for a practical system, but they could be handled either by extensions to this protocol or by local security mechanisms implemented on the nodes supporting the CDC. Similarly, availability can be boosted by replicating the CDC. Automating the replication of updates could be done by extensions to this protocol or by some other mechanism. This specification assumes that updates and replication are local matters, solved by individual CA/CDC implementations ๐ซง.
Requests and responses are encoded as follows:
2.3.2.1 ReadPrinCertRequest
This request asks the CDC to return the child certificates and selected incoming cross certificates for the specified object. The format of the request is:
ReadPrinCertRequest ::= [4] IMPLICIT SEQUENCE {
flags [0] BIT STRING DEFAULT {},
index [1] IMPLICIT INTEGER DEFAULT 0,
resolveFrom [2] Name OPTIONAL,
principal Name,
crossCertIssuers ListOfIssuers OPTIONAL
}
ListOfIssuers ::= SEQUENCE OF Name
The first 24 bits of flags, if present, hold a protocol version number. Clients following this spec SHOULD place the value 2.0.0 in the three bytes. Servers following this spec SHOULD accept any value of the form 1.x.x or 2.x.x. The flags bits beyond the first 24 are reserved for future use. They SHOULD NOT be supplied by clients and SHOULD be ignored by servers ๐
โโ๏ธ.
index is only used if the response exceeds the size of a single message. In that case, the query is repeated with index set to the value that was returned by ReadPrinCertResponse ๐. resolveFrom and principal imply a set of entities for which certificates should be retrieved. resolveFrom (if present) MUST be an ancestor of principal, and child certificates will be retrieved for principal and for all names that are ancestors of principal but descendants of resolveFrom. The encoding of names is per X.500 and is specified in more detail in section 4. The CDC returns the certificates in order of the object they came from, parents before children ๐.
crossCertIssuers is a list of cross certifiers that would be believed in the context of this authentication. If supplied, the CDC MAY return a chain of certificates starting with one of the named crossCertIssuers and ending with the named principal ๐ฆ. One of resolveFrom or crossCertIssuers MUST be present in any request. If both are present, the CDC MAY return either chain โจ.
2.3.2.2 ReadPrinCertResponse ๐
okay so this is the reply a CDC sends back to a ReadPrinCertRequest ๐ it looks like this:
ReadPrinCertResponse ::= [5] IMPLICIT SEQUENCE {
status [0] IMPLICIT CDCstatus DEFAULT success,
index [1] INTEGER OPTIONAL,
resolveTo [2] Name OPTIONAL,
certSequence [3] IMPLICIT CertSequence,
indexInvalidator [4] OCTET STRING (SIZE(8)) OPTIONAL,
flags [5] BIT STRING OPTIONAL
}
CertSequence ::= SEQUENCE OF Certificate
status tells you whether things worked or what went wrong ๐
index shows up when the request couldn't be fully satisfied in one go because of size limits ๐ฆ when it's there, the request SHOULD be repeated with this index included so you can get more โจ
resolveTo will be present if index is present, and it SHOULD be supplied in the request for more certificates ๐ certSequence holds the certificates that matched your search criteria ๐
indexInvalidator MAY be present and tells you which version of the database is being read ๐๏ธ if a set of certificates is being read across multiple requests (because there were too many for one message), the reader SHOULD check that indexInvalidator is the same on every request. if it isn't, the server MAY have skipped or duplicated some certificates ๐ฑ indexInvalidator MUST NOT be present if the version number in the request was missing or version 1.x.x. ๐
The first 24 bits of flags, if present, indicate the protocol version number ๐ Implementers of this version of the spec SHOULD supply 2.0.0, and they SHOULD accept any version number of the form 1.x.x or 2.x.x. ๐ซถ
2.3.2.3 ReadOutgoingCertRequest ๐ฆ
this one asks the CDC for a list of all the parent and outgoing cross certificates for a specified object ๐ a CDC is capable of storing cross certificates either with the subject or with the issuer of the cross certificate, so it's a bit of a juggle ๐คน when this request comes in, the CDC returns all the parent and cross certificates stored with the issuer for the named principal and all of its ancestors ๐ณ
ReadOutgoingCertRequest ::= [6] IMPLICIT SEQUENCE {
flags [0] BIT STRING DEFAULT {},
index [1] IMPLICIT INTEGER DEFAULT 0,
principal Name
}
The first 24 bits of flags is a protocol version number and it SHOULD contain 2.0.0 for clients implementing this version of the spec ๐ Servers implementing this version of the spec SHOULD accept any version number of the form 1.x.x or 2.x.x. The remaining bits are reserved for future use (they SHOULD NOT be supplied by clients and they SHOULD be ignored by servers) ๐ซ
index is used for continuation (see ReadPrinCertRequest) โก๏ธ principal is the name for which certificates are requested ๐งพ
2.3.2.4 ReadOutgoingCertResponse ๐ธ
this is the response to a ReadOutgoingCertRequest, and it's shaped like this ๐งฉ
ReadOutgoingCertResponse::= [7] IMPLICIT SEQUENCE {
status [0] IMPLICIT CDCStatus DEFAULT success,
index [1] INTEGER OPTIONAL,
certSequence [2] IMPLICIT CertSequence,
indexInvalidator [3] OCTET STRING (SIZE(8))
OPTIONAL,
flags [4] BIT STRING OPTIONAL
}
CertSequence ::= SEQUENCE OF Certificate
status indicates success or the cause of failure ๐ index is used for continuation, so check out ReadPrinCertRequest for the vibe ๐ certSequence is the list of parent and outgoing cross certificates ๐ indexInvalidator is used for continuation too, and the same rules apply as in ReadPrinCertResponse when it comes to version numbers ๐ข
The first 24 bits of flags, if present, contain the protocol version number ๐ Clients implementing this version of the spec SHOULD supply the value 2.0.0. Servers SHOULD accept any values of the form 1.x.x or 2.x.x. The remaining bits are reserved for future use (they SHOULD NOT be supplied by clients and they SHOULD be ignored by servers) ๐
โโ๏ธ
2.3.2.5 ReadCredentialRequest ๐
this request is how you retrieve a principal's encrypted credentials ๐๏ธ to stop unaudited password guessing, the structure includes an encrypted value that proves the requester knows the password that will decrypt it. main character security energy ๐ค
ReadCredentialRequest ::= [2] IMPLICIT SEQUENCE {
flags [0] BIT STRING DEFAULT {}
principal Name,
logindata [2] BIT STRING DEFAULT {},
token [3] BIT STRING OPTIONAL
}
The first 24 bits of flags contains the version number of the protocol ๐ฟ The value 2.0.0 SHOULD be supplied, and any value of the form 1.x.x or 2.x.x SHOULD be accepted. Any additional bits are reserved for future use (they SHOULD NOT be supplied by clients and they SHOULD be ignored by servers) ๐งผ
principal is the name of the principal whose encrypted credentials you want ๐ซต
logindata is an encrypted value ๐ it MAY only be present if the version number is 2.0.0 or higher, and it MUST be present to read credentials that are protected by the login agent functionality of the CDC ๐ก๏ธ it's built as a single RSA block encrypted under the public key of the CDC. the public key of the CDC is learned by some local means, and the possibilities include a local configuration file or using DASS to read and verify a chain of certificates ending with the CDC (the CDC serving a directory SHOULD have its public key listed under a name made of the directory name with the RDN "CSS=X509"; the OID for the type CSS is 1.3.24.9.1). ๐ the contents of the block go like this:
- the low order eight bytes hold a randomly generated DES key, with the last byte of the DES key placed in the last byte of the RSA block. the CDC uses this DES key to encrypt the response. key parity bits are ignored ๐๏ธ
- the next to last eight bytes hold a long Posix time, with the integer time encoded as a byte string in big endian order โฐ
- the next eight bytes (counting from the end) hold a hash of the password. the algorithm for computing this hash is listed in section 4.4.2. the CDC never computes this hash; it just compares the value it receives with the value associated with the credentials ๐
- the next sixteen bytes (counting from the end) hold zero 0๏ธโฃ
- the rest of the RSA block (which SHOULD be the same size as the public modulus of the CDC) holds a random number. the first byte SHOULD be chosen to be non-zero, but so the value in the block does not exceed the RSA modulus. servers SHOULD ignore these bits. this random number need not be of cryptographic strength, but it SHOULD NOT be the same value for all encryptions. repeating the DES key would be adequate ๐ฒ
- the byte string built this way is encrypted using the RSA algorithm, by treating the string of bytes as a "big endian" integer and treating the integer result as "big endian" to make a string of bytes ๐งฎ
token will not be present in the initial implementation, but a space is reserved in case some future implementation wants to authenticate and audit the node a user is logging in from ๐ชโจ
2.3.2.6 ReadCredentialProtectedResponse ๐ก๏ธ
this is the second possible response to a ReadPrinLoginRequest ๐โโ๏ธ it comes back when the encrypted credentials are protected from password guessing by the CDC acting as a login agent ๐ต๏ธโโ๏ธ
ReadCredentialProtectedResponse::=[16] IMPLICIT SEQUENCE {
status [0] IMPLICIT CDCStatus DEFAULT success,
encryptedCredential [1] BIT STRING,
flags [2] BIT STRING OPTIONAL
}
status indicates that the request succeeded or what the cause of the failure was ๐ฅฒ
encryptedCredential contains the DASSPrivateKey structure (defined in section 4.1), encrypted under a DES key computed from the user's name and password as specified in section 4.4.2, and then reencrypted under the DES key provided in the ReadPrinLoginRequest ๐๐
The first 24 bits of flags, if present, contain the version number of the protocol ๐ฉต Implementers of this version of the spec SHOULD supply 2.0.0 and SHOULD accept any version number of the form 2.x.x. Other bits are reserved for future use (they SHOULD NOT be supplied and they SHOULD be ignored) ๐
2.3.2.7 WriteCredentialRequest โ๏ธ
this is a request to update the encrypted credential structure ๐๏ธ you use it when a user's key or password changes, so it's kind of a glow-up moment ๐
WriteCredentialRequest ::= [17] IMPLICIT SEQUENCE {
flags [0] BIT STRING DEFAULT {},
authtoken [2] BIT STRING OPTIONAL,
principal [3] Name,
logindata [4] BIT STRING DEFAULT {},
furtherSensitiveStuff [5] BIT STRING
}
The first 24 bits of flags is a version number ๐ Clients implementing this version of the spec SHOULD supply 2.0.0. Servers SHOULD accept any value of the form 2.x.x. Additional bits are reserved for future use (clients SHOULD NOT supply them and servers SHOULD ignore them) ๐งน
token, if present, authenticates the entity making the request ๐ชช a request will be accepted either from a principal proving knowledge of the password (see logindata below), or from a principal presenting a token in this field and satisfying the authorization policy of the CDC. this field need not be present if logindata includes the hash2 of the password (anyone who knows the old password may set a new one) ๐
principal is the name of the object whose encrypted credentials SHOULD be updated ๐ฏ
logindata is encrypted as in ReadPrinLoginRequest ๐ it proves that the requester knows the old password of the principal being updated (unless the token supplied is from the user's CA), and it includes the key that encrypts furtherSensitiveStuff ๐๏ธ
furtherSensitiveStuff is an encrypted field, built like this ๐ง
- the first eight bytes consist of the hash2 defined in section 4.4.2, with the last byte of the hash2 value stored first. the CDC stores this value and compares it with the values supplied in future requests of ReadCredentialRequest and WriteCredentialRequest ๐ง
- the next (variable number of) bytes contain a DASSPrivateKey structure (defined in section 4.1). this is the new credential structure that will be returned by the CDC on future ReadCredentialRequests ๐
- the result is padded with zero bytes to a multiple of eight bytes ๐ซง
- the entire padded string is encrypted using the key from
logindataortoken, using DES in CBC mode with zero IV ๐
put together, that's the new eight byte "hash2" defined in section 4.4.2, concatenated with the DASSPrivateKey structure encrypted under the new "hash1", all encrypted under the DES key included in logindata ๐
2.3.2.8 HereIsStatus ๐
this is the response message for ill-formed requests and for requests that only return a status and no data ๐ its syntax is:
HereIsStatus ::= [1] IMPLICIT SEQUENCE {
status [0] IMPLICIT CDCStatus DEFAULT success
}
status indicates success or the cause of the failure ๐
2.3.2.9 Status Codes โจ
okay so here's the full list of CDCStatus codes that servers can send back ๐
not every code works with every call, and a few of them aren't possible with ANY of the calls in this doc ๐
CDCStatus ::= INTEGER {
success(0),
accessDenied(1),
wrongCDC(2), --this CDC does not store the
--requested information
unrecognizedCA(3),
unrecognizedPrincipal(4),
decodeRequestError(5),--invalid BER
illegalRequest(6), --request not recognised
objectDoesNotExist(7),
illegalAttribute(8),
notPrimaryCDC(9),--write requests not accepted
--at this CDC replica
authenticationFailure(11),
incorrectPassword(12),
objectAlreadyExists(13),
objectWouldBeOrphan(15),
objectIsPermanent(16),
objectIsTentative(17),
parentIsTentative(18),
certificateNotFound(19),
attributeNotFound(20),
ioErrorOnCertifDatabase(100),
databaseFull(101),
serverInternalError(102),
serverFatalError(103),
insufficientResources(104)
}
3. Services Provided โจ
okay so this section is all about the services DASS provides, described as abstract interfaces plus a model implementation ๐ง a real implementation might only support some of them, and it could bundle things together or fill in some parameters automatically. the calling interfaces can be pretty language- and OS-specific. for example, an implementation could set up security contexts once and pass them along implicitly instead of stuffing them into every single call ๐ซถ it might also wrap keys into opaque structures to use with supplied encryption and decryption routines, for extra security, modularity, and export-regulation compliance ๐ Annex B has a Portable API so apps using a limited subset of DASS can be moved between operating systems, and between DASS and Kerberos-based environments ๐ the model implementation has data structures with cached values for speed, and implementations can pick different contents or caching strategies, as long as the same sequence of calls gives the same output for some caching policy.
DASS works with four kinds of data structures: Certificates, Credentials, Tokens, and Certification Authority State โจ Certificates and Tokens get passed between implementations, so their exact format has to be architecturally specified, and that bit-for-bit spec is in section 4 ๐ Credentials mostly live inside a single node, so their format isn't specified here. the contents of all of these are listed below, followed by the algorithms that manipulate them.
DASS has three kinds of services: Certificate Maintenance, Credential Maintenance, and Authentication. the first two only exist to support the third ๐ฆ Certificate maintenance keeps the database of public keys in the naming service. these functions tend to be pretty specialized and might not be supported on every platform. before authentication can happen, both authenticating principals must have built their credentials structures using the Credential Maintenance calls ๐
The Authentication functions use credential info and certificates, produce and consume authentication tokens, and tell the two communicating parties each other's names ๐
3.1 Certificate Contents
okay so for this architecture, a certificate is a data structure posted in the naming service ๐ it proclaims that knowing the private key tied to a stated public key authenticates a named principal ๐ certificates are "signed" by some authority, anyone can read them, and anyone who knows the authority's public key can verify them ๐
DASS organizes the CA trust hierarchy around the naming hierarchy. every directory in the naming hierarchy has a trusted authority. usually each authority makes certificates for each of its children (in the naming hierarchy) and for its parent (also in the naming hierarchy). so if you know any authority's public key, you can learn anyone else's by "walking the tree" ๐ณ principals can still authenticate even when their ancestor directories aren't taking part in DASS, because authorities can also make "cross-certificates" that certify the public key of a named entity which isn't a descendent โจ the rules for finding and following these cross-certificates are in the Get_Pub_Keys routines.
Every principal is expected to know the public key of the CA of the directory it's named in. that has to be securely learned when the principal is initialized, and it can be kept in some form of local storage, or the principal can sign a certificate listing its name and public key and its parent's name and public key, then post that certificate in the naming service ๐
The syntax and content of DASS certificates are defined in terms of X.509 (Directory - Authentication Framework) ๐ X.509 prescribes one single certificate syntax, but DASS says certificates come in one of six types ๐
- Normal Principal certificates are signed by a CA and certify the name and public key of a principal, where the name of the CA is a prefix of the name of the principal and is one component shorter ๐ธ
- Trusted Authority certificates are signed by an ordinary principal and certify the name and public key of the principal's CA (the CA whose name is a prefix of the principal's name and is one component shorter) ๐ซถ
- Child certificates are signed by a CA and certify the name and public key of a CA of a descendent directory (where the name of the issuing CA is a prefix of the name of the subject CA and is one component shorter) ๐ง
- Parent certificates are signed by a CA and certify the name and public key of the CA of its parent directory (whose name is a prefix of the name of the issuer and is one component shorter) ๐ฆ
- Cross certificates are signed by a CA and certify the name and public key of a CA of a directory where neither name is a prefix of the other ๐
- Self certificates are signed by a principal or a CA, and the issuer and subject name are the same. they aren't used in this version of the architecture, but they're defined as a convenient data structure where implementations may insecurely pass public keys, and they may be used in the future in certain key roll-over procedures ๐
It's intended that some future version of the architecture relaxes the restrictions above, where prefixes have to be one component shorter. handling certificates where prefixes are two or more components shorter makes the treewalking logic more complicated and isn't immediately necessary, so those certificates are disallowed for now ๐
The syntax of certificates is defined in section 4. for the algorithms that follow, here's the part of the content they use (names in brackets are the field names in the ASN.1 encoded structure):
- UID of the issuer (optional)
- Full name of the issuer (the authority or principal signing)
[issuer] - UID of the subject (optional)
- Full name of the subject (the authority or principal whose key is being certified)
[subject] - Public Key of the subject
[subjectPublicKey] - Period of validity (effective date and expiration date)
[valid] - Signature over the entire content of the certificate, created using the private key of the issuer
When parsing a certificate, the reader compares the two name fields to figure out what type of certificate it is ๐ for Parent and Trusted Authority certificates, the names are ignored for all further processing. for Child and Normal Principal certificates, only the suffix by which the child's name is longer than the parent's is used for further processing. why? so that if a branch of the namespace gets renamed, all of the certificates in the moved branch stay valid for DASS processing ๐ the full names in these certificates only exist to:
- comply with X.509,
- allow possible interoperability with other architectures using different algorithms, and
- let principals securely store their own names in trusted authority certificates, in case they don't have enough local storage to keep them ๐
3.2 Encrypted Private Key Structure
so humans only have to remember a password instead of a whole set of credentials, and installing nodes and servers is easier, there's a defined format for encrypting RSA secrets under a password and posting them in the naming service ๐ this structure only needs to exist when passwords are used to protect RSA secrets. servers that keep their secrets in non-volatile memory, or users who carry smart cards, don't need it at all ๐
This structure includes the RSA private/public key pair, encrypted under a DES key. The DES key is computed as a one-way hash of the password. This structure also optionally includes the UID of the principal ๐ซถ that's only needed if a single RSA key is shared by multiple principals (with multiple UIDs).
Since this structure is posted in the name service and may be used by multiple implementations, its format has to be architecturally defined โจ the exact encoding is listed in section 4.
3.3 Authentication Tokens
this section of the doc defines the contents of the authentication tokens that get produced and consumed by Create_token and Accept_token ๐ with DASS, the token passed from the client to the server is complex, with a large number of optional parts, while the token passed from the server to the client (only for mutual authentication) is small and simple ๐
The authentication token can have a whole bunch of parts, and most of them are optional depending on the type of authentication. below is what each part contains and why it's there ๐ธ the actual encoding isn't described here, since the authors figured those details would be distracting. the encoding is in section 4.
The authentication process begins when the initiator calls Create_token with the name of the target. this routine returns an authentication token, which gets sent to the target. the target calls Accept_token and passes it the token. Both routines produce a second "mutual authentication token," and the target sends this back to the initiator to prove that it received the token ๐ฆ
3.3.1 Initial Authentication Token ๐
okay so the initial authentication token is made of a bunch of pieces ๐ (the names in brackets are the field names inside the ASN.1 encoded structures from section 4, so peek there if you're curious โจ)
- a) Encrypted Shared Key -
[authenticatingKey]๐ - this is a Shared (DES) key that's been encrypted under the public key of the target. the encrypted bundle also holds a validity interval and a recognizable pattern, so the receiver can tell whether the decryption actually worked ๐ - b) Login Ticket -
[sourcePrincipal.userTicket]๐ - this is a "delegation certificate" signed by a principal's long term private key, delegating to a short term public key. its "active ingredients" are:- 1) UID of delegating principal
[subjectUID] - 2) Period of validity
[validity] - 3) Delegation public key
[delegatingPublicKey] - 4) Signature by private key of principal - the existence of this signature is testimony that the private key corresponding to the delegation public key speaks for the user during the validity interval. this data structure is optional and will be missing if the authentication is only on behalf of a Local Username on a node (aka a proxy) rather than on behalf of a real principal with a real key ๐ซถ
- 1) UID of delegating principal
- c) Shared Key Ticket -
[sourcePrincipal.sharedKeyTicketSignature]โ๏ธ - this is a signature of the Encrypted Shared Key by the Delegation Public key in the Login Ticket. the existence of this signature is testimony that the DES key in the encrypted shared key speaks for the user. this data structure is optional and will be missing if the authentication is only on behalf of a Local Username on a node (aka a proxy) rather than on behalf of a real principal with a real key. it will also be missing if delegation is taking place ๐ - d) Node Ticket -
[sourceNode.nodeTicketSignature]๐ฅ๏ธ - this is a signature of the Encrypted Shared key and a "Local Username" on the host node, made by the node's private key. the existence of this signature is testimony by the node that the DES key in the encrypted shared key speaks for the named account on that node ๐ - e) Delegator -
[sourcePrincipal.delegator]๐๏ธ - this data structure holds the private login key, encrypted under the Shared key. it's optional and only shows up if the initiator is delegating to the destination ๐ฆ - f) Authenticator -
[authenticatorData]โฑ๏ธ - this one holds a timestamp and a message digest of the channel bindings, signed by the Shared Key. it is always present โจ - g) Principal name -
[sourcePrincipal.userName]๐ธ - the name of the initiating principal. it's optional, and it'll be missing for a strong proxy, where bits on the wire are at a premium and the destination can build the name on its own ๐ง - h) Node name -
[sourceNode.nodeName]๐ - the name of the initiating node. also optional. it'll be missing for a strong proxy when the name is already present elsewhere in the message being passed ๐ - i) Local Username -
[sourceNode.username]๐ - the local user name on the initiating node. optional too, and missing for a strong proxy when that name is already present elsewhere in the message being passed ๐ซถ
3.3.2 Mutual Authentication Token ๐ฆ
the authentication buffer that goes from the target back to the initiator (when there's mutual authentication) is way simpler, honestly iconic ๐ . it only carries the timestamp taken from the authenticator, encrypted under the Shared Key. it's ASN.1 encoded so there's room for future extensions ๐ฎ
3.4 Credentials ๐
okay this is where DASS keeps its internal state, and it's giving organized ๐. it uses Credentials structures. there are lots of kinds of info that can live in credentials, but instead of making a brand new data structure for each kind, DASS gives you ONE credentials structure where most of the fields are optional. โจ
operating systems have to provide some way for several processes to share credentials. one example would be storing the credentials in a file, with the file name used as a "handle" that every process using those credentials can grab ๐๏ธ. some of the calls coming up update credentials, and that matters a lot for performance. the updates (like the ones that happen inside Verify_Principal_Name and Verify_Node_Name, where the caches get refreshed) need to be visible to all the processes sharing those credentials, or things get messy ๐ญ
in a lot of the calls ahead, the credentials you pass might be labeled as claimant credentials, verifier credentials, or something similar. that label is about whose credentials they are, not about a type of credentials ๐. DASS only supports one type of credentials, though the fields in one sort of principal's credentials can look super different from another's ๐ธ
an implementation can also choose to support several kinds of credentials structures, where each one only handles a subset of the functions, if it isn't doing the full architecture ๐งฉ. for example, that'd happen if an implementation didn't handle a server that both receives requests from other principals AND makes its own requests, all using one set of credentials ๐
here's the list of fields a credentials structure might hold. they're grouped by common usage ๐
3.4.1 Claimant information ๐โโ๏ธ
this is the info used when the holder of these credentials is asking for something. it includes:
- a) Full X.500 name of the principal ๐
- b) Public Key of the principal ๐
- c) Login Ticket - a login ticket contains:
- 1) the UID of the principal
- 2) a period of validity (effective date & expiration date)
- 3) a delegation public key
- 4) a signature of the ticket contents by the principal's long term key
- d) Delegation Private Key (corresponding to the public key in c3) ๐๏ธ
- e) Encrypted Shared Key (present only when credentials were created by
accept_token; this info is needed to verify a node ticket after credentials are accepted) ๐
3.4.2 Verifier information ๐ก๏ธ
this is the info a server needs to decrypt incoming requests. it's also used by generate_server_ticket to generate a login ticket โจ
- a) RSA private key. ๐
3.4.3 Trusted Authority ๐๏ธ
this info is used to seed the walk of the CA hierarchy, so you can reliably find the public key(s) tied to a name ๐งญ. normally, the trusted authority in a set of credentials is the directory parent of the principal named in Claimant information. in some situations, though, it might instead be the directory parent of the node where the credentials live ๐ณ
- a) Full X.500 name of a CA ๐
- b) Corresponding RSA Public Key ๐
- c) Corresponding UID ๐ชช
3.4.4 Remote node authentication ๐ฅ๏ธ
this info only shows up for credentials generated by "Accept_token". it covers any remote node that vouched for the request ๐ซถ
- a) Full X.500 name of the node ๐
- b) Local Username on the node ๐
- c) Node ticket. ๐๏ธ
3.4.5 Local node credentials ๐ป
this info gets added by Combine_credentials, and Create_token uses it to add a node signature to outbound requests ๐
- a) Full X.500 name of the node ๐
- b) Local Username on the node ๐
- c) RSA private key of the node ๐
3.4.6 Cached outgoing contexts ๐ค
there can be one (or more) of these structures for each server this principal has made authentication tokens for ๐ง. they're a cache, so they can be thrown out at any time, and the only effect is on performance, not correctness โจ. for each association, the following info is kept:
- a) Destination RSA Public Key (index) ๐
- b) Encrypted Shared key ๐
- c) Shared Key Ticket (optional, included if there has been a non-delegating connection) โ๏ธ
- d) Node Ticket ๐๏ธ
- e) Delegator (optional, included if there has been a delegating connection) ๐๏ธ
- f) Validity interval โฑ๏ธ
- g) Shared Key ๐ธ
3.4.7 Cached Incoming Contexts ๐ฅ
there's one of these structures for each client this server has received an authentication token from ๐ฌ. it's also a cache, so it can be discarded at any time with no effect except on performance ๐งน. (an implementation can keep one System-wide Cache, along with a list of incoming timestamps. it's unlikely that the same Encrypted Shared Key comes out of encrypting Shared keys made by different clients or for different servers, but an implementation must ensure that an entry made for one client/server can not be reused by another client/server ๐ซ. it could also keep separate caches for the Shared Key/Validity Interval/Delegation Public Key, the Nodename/UID/key/username and the Principal name/UID/key.) for each association, the following info is kept:
- a) Encrypted Shared key (index) ๐
- b) Shared Key ๐ธ
- c) Validity Interval โฑ๏ธ
- d) Full X.500 name of Client Principal ๐
- e) UID of Client Principal ๐ชช
- f) Public Key of Client Principal ๐
- g) Name of Client Node ๐ฅ๏ธ
- h) UID of Client Node ๐ชช
- i) Public Key of Client Node ๐
- j) Local Username on Client node ๐
- k) Delegation Public key of Client Principal's Login Ticket ๐๏ธ
the Name, UID and Public key of the Principal all get entered together once the Login Ticket has been verified ๐. same deal for the Node name, Node key and Username, which get entered together once the Node Ticket has been verified ๐ซถ. these pieces of info are only present if they've actually been verified, so no cap, that's the vibe โจ
3.4.8 Received Authenticators ๐งพ
this is a record of every authenticator that's been received, and it's used to catch replayed messages ๐ต๏ธโโ๏ธ. (this list has to be shared by all targets that could accept the same authenticator. channel bindings stop other targets from accepting that same authenticator, but this still includes different servers' sharing the same key ๐.) entries can be deleted once their timestamp is old enough that they'd no longer be accepted โณ. this list is kept separate from the Cached incoming context, so that info can be thrown out at any time ๐งน. an implementation could save these timestamps along with the cached incoming context, but only if it can guarantee it never purges cache entries before the timestamp has aged enough โ
. this list is looked up using an extract from the signature on the Authenticator. the extract must be at least 64 bits, so two authenticators with matching signatures is very unlikely ๐ฒ
- a) Extract from Signature from Authenticator ๐งฉ
- b) Timestamp โฑ๏ธ
if an implementation runs out of room to store more authenticators, it has two options ๐ซฃ. it may reject the token that would have overflowed the table, or it may temporarily narrow the allowed clock skew so it can free up some of the space used for "old" authenticators. the first strategy will always falsely reject tokens ๐. the second may cause false rejections too, if the allowed clock skew gets narrowed past the actual clock skew in the network ๐
that's the end of this chunk, and the credentials stuff is lowkey wild ๐
3.5 CA State โจ
okay so the CA needs to keep some internal state so it can actually generate certificates ๐ this internal state must be protected at all times, and we're talking serious care to make sure it never gets disclosed ๐ a CA may choose to keep extra state info too, to level up its security ๐
the big one is that the CA is responsible for making sure the same UID never gets serially reused by two holders of a single name ๐ซถ usually you can handle that by creating the UID when the user gets registered ๐ธ and if someone wants to keep their UIDs when they transfer over from another CA, the CA must keep a record of any UIDs used by previous holders of the name ๐
since CA actions are so security sensitive, the CA should also keep an audit trail of every certificate it signs, so a history can be rebuilt if something goes wrong ๐ญ and for the CA operator's convenience, the CA should record a list of the directories it's responsible for plus their UIDs, so nobody has to type them in every time the CA gets used ๐ the state includes at least this info:
- Public Key of CA
- Private Key of CA
- Serial number of next certificate to be issued
3.6 Data types used in the routines ๐ง
there are a bunch of abstract data types that show up as parameters to the routines in this section, so here's the full lineup ๐
a) Integer
b) Name okay so names are always X.500 names unless noted otherwise ๐ DASS is mostly naming service independent, but the syntax of certificates and tokens only allows X.500 names. if DASS gets used somewhere with a different name form, those names must be translated into something syntactically compliant with X.500, using some mechanism that's beyond the scope of this architecture ๐งฉ the only other name form in this architecture is a "local user name", which is the simple name of an "account" on a node. as a type, these show up in parameter lists as "Strings".
c) String a String is a sequence of printable characters ๐จ๏ธ
d) Absolute Time a UTC time ๐ฐ๏ธ the precision isn't stated, but a precision of around one second for all times is enough.
e) Time Interval a Time interval is made of 2 times: a Start Time and an End Time, both of which are Absolute Times โณ
f) Timestamp a Timestamp is a time in POSIX format, aka two 32 bit Integers. the first is seconds and the second is nanoseconds โฑ๏ธ
g) Duration a Duration is the length of a time interval.
h) Octet String a sequence of bytes containing binary data ๐ค
i) Boolean a value of either True or False โ
j) UID a UID is a bit string of 128 bits ๐ชช
k) OID an OID is an ISO Object Identifier ๐ข
l) Shared key a Shared key is a DES key, a sequence of 8 bytes ๐๏ธ
m) CA State a structure of the form described in '3.5
n) Credentials a structure of the form described in '3.4
o) Certificate an ASN.1 encoding of the structure described in '3.1
p) Authentication Token an ASN.1 encoding of the structure described in '3.3.1
q) Mutual Authentication Token an ASN.1 encoding of the structure described in '3.3.2
r) Encrypted Credentials an ASN.1 encoding of the structure described in '3.2
s) Public key a representation of an RSA Public key, including all the information needed to encode the public key in a certificate ๐
t) Set of Public key/UID pairs a set of Public key/UID pairs. this data type is only used internally in DASS, so it doesn't appear in any interface used to other architectures ๐คซ
3.7 Error conditions ๐จ
these routines can return the following error conditions (an implementation may indicate errors with more or less precision):
a) Incomplete chain of trustworthy CAs b) Target has no keys which can be trusted. c) Invalid Authentication Token d) Login Ticket Expired e) Invalid Password f) Invalid Credentials g) Invalid Authenticator h) Duplicate Authenticator
3.8 Certificate Maintenance Functions ๐
authentication services depend on a set of data structures kept in the naming service, and there are two kinds of info in there ๐ first up, Certificates, which associate names and public keys and are signed by off-line Certification Authorities. second, Encrypted Credentials, which contain RSA Private Keys and certain context info encrypted under passwords. Encrypted Credentials are only necessary in environments where passwords are used. Credentials may alternatively be stored in some other secure manner (for example on a smart card) ๐ณ
the certificate maintenance services are designed so the most sensitive part, the actual signing of certificates, can be done by an off-line authority ๐ฐ once signed, certificates must be posted in the naming service to be believed. the precise mechanisms for moving certificates between off-line CAs and the on-line naming service are implementation dependent. for the off-line mechanisms to provide any actual security, the CAs must be told what to sign in some reliable manner, and the mechanisms for doing this are implementation dependent too ๐คทโโ๏ธ
the abstract interface says the CA is given all of the information that goes into a certificate, and it produces the signed certificate. there are requirements surrounding the auditing of a CA's actions ๐งพ the details of what actions are audited, where the audit trail is maintained, and what utilities exist to search that audit trail are not specified here. the functions a CA must provide are:
3.8.1 Install CA ๐ ๏ธ
Install_CA(
keysize Integer, --inputs
CA_state CA State, --outputs
CA_Public_Key Public Key)
this routine need only generate a public/private key pair of the requested size. Keysize is likely to be an implementation constant rather than a parameter. the value is likely to be either 512 or 640. key sizes throughout will have to increase over time as factoring technology and CPU speeds improve ๐ both keys are stored as part of the CA_state, and the public key is returned so that other CAs may cross-certify this one ๐ค the `Next Serial number' in the CA state is set to 1.
3.8.2 Create Certificate ๐
Create_certificate(
--inputs
Renewal Boolean,
Include_UID Boolean,
Issuer_name Name,
Issuer_UID UID,
Effective_date Absolute Time,
Expiration_date Absolute Time,
Subject_name Name,
Subject_UID UID,
Subject_public_key Public Key,
--updated
CA_state CA State,
--outputs
Certificate Certificate)
this procedure creates and signs a certificate. note that the various contents of the certificate must be communicated to the CA in some reliable fashion ๐ฌ the Issuer_name and Issuer_UID are the name and UID of the directory on whose behalf the certificate is being signed.
this routine formats and signs a certificate with the private key in CA_state โ๏ธ it audits the creation of the certificate and updates the sequence number which is part of CA_state. the Issuer and Subject names are X.500 names. if the CA state includes a history of what UIDs have previously been used by what names, this call will only succeed in the collision case if the Renewal boolean is set true. if the Include_UID boolean is set true, this routine will generate a 1992 format X.509 certificate. otherwise, it will generate a 1988 format X.509 certificate ๐
3.8.3 Create Principal ๐
Create_principal(
--inputs
Password String,
keysize Integer,
Principal_name Name,
Principal_UID UID,
Parent_Public_key Public Key,
Parent_UID UID,
--outputs
Encrypted_Credentials Encrypted Credentials,
Trusted_authority_certificate Certificate)
this procedure creates a new principal by generating a new public/private key pair, encrypting the public and private keys under the password, and signing a trusted authority certificate for the parent CA ๐ง in an implementation not using passwords (e.g., smart cards), an alternative mechanism must be used for initially creating principals. if a principal has protected storage for trusted authority information, it is not necessary to create a trusted authority certificate and store it in the naming service. some procedure analogous to this one must be executed, however, in which the principal learns the public key and UID of its CA and its own name ๐ซ
this routine creates two output structures with the following steps:
a) Generate a public/private key pair using the indicated keysize. an implementation will likely fix the keysize as an implementation constant, most likely 512 or 640 bits, rather than accepting it as a parameter. key sizes generally will have to increase over time as factoring technology and CPU speeds improve.
b) Form the encrypted credentials by using the public key, private key, and Principal_UID, and encrypting them using a hash of the password as the key.
c) Generate a trusted authority certificate (which is identical in format to a "parent" certificate) getting fields as follows:
- Certificate version is X.509 1992.
- Issuer name is the Principal name (which is an X.500 name).
- Issuer UID is the Principal UID.
- Validity is for all time.
- Subject name is constructed from the Principal name by removing the last simple name from the hierarchical name.
- Subject UID is the CA_UID.
- Subject Public Key is the CA_Public_Key
- Sequence number is 1.
- Sign the certificate with the newly generated private key of the principal.
3.8.4 Change Password โจ
Change_password( --inputs
Encrypted_credentials Encrypted Credentials,
Old_password String,
New_password String,
--outputs
Encrypted_credentials Encrypted Credentials)
Okay so this one is all about changing your password, but only if you still know the old one ๐. If credentials are stored encrypted under a password, you can switch it up as long as the old one is known. Bestie, though, it is insufficient to just change a password once it's been disclosed ๐ญ. Anyone who knows the old password may already have learned the user's private key. If a password has been disclosed, the secure recovery move is to call create_principal again, then create_certificate to certify the new key ๐ซถ.
Using DASS, it may not be appropriate for users to change their passwords periodically as a precaution, unless they also change their private keys with the procedure above. Honestly, the only likely use of change_password is when an administrator picked a password for someone while setting up their account, and that person wants to swap it for something they can actually remember ๐ง . A future version of the architecture may smooth key roll-over by having change_password also generate a new key and sign a "self" certificate in which the old key certifies the new one. As a separate step, a CA that notices a self certificate posted in the naming service could certify the new key instead of the old one when the user's certificate is renewed ๐. This isn't as rapid or as reliable as having the user interact directly with the CA, but it's a reasonable tradeoff between security and convenience when there's no evidence of password compromise ๐
.
This routine simply decrypts the encrypted credentials structure supplied, using the password supplied. It returns a bad status if the format of the decrypted information is bad, which means the password was incorrect ๐. Otherwise, it creates a new encrypted credentials structure by encrypting the same data with the new password. It would be highly desirable for the user interface to offer random password generation and to block easily guessed passwords using length, character set, and dictionary lookup rules, but those capabilities are beyond the scope of this document ๐. If encrypted credentials live in some local secure storage, the above function is all that's necessary. In fact, if the storage is sufficiently secure, no password is needed, and credentials could be stored unenciphered ๐ธ. If they're stored in a naming service, this function must be coupled with one that retrieves the old encrypted credentials from the naming service and stores the new ones. The full protocol will likely include access control checks that make the principal acquire credentials and produce tokens. For best security, the encrypted credentials should be accessible only through a login agent ๐ก๏ธ. The login agent's job is to audit and limit the rate of password guessing. If passwords are well chosen, there's no significant threat from password guessing, because searching the space is computationally infeasible ๐ง. In the context of a login agent, change password will be implemented with a specialized protocol that requires knowledge of the password and (for best security) a trusted authority from which the public key of the login agent can be learned. See section 2.3.2 for the plans for the non-X.500 credential storage facility ๐ฆ.
3.8.5 Change Name โจ
Change_name(
--inputs
Claimant_Credentials Credentials,
New_name Name,
CA_Public_Key Public Key,
CA_UID UID,
--outputs
Trusted_Authority_Certificate Certificate)
Main character energy: DASS lets a principal have many current aliases, but only one current name ๐. A principal can authenticate itself as any of its aliases, but it checks other people's names relative to the name it uses for itself. Making aliases is easy, since you just use the create_certificate function once for each alias ๐. Changing the name of a principal is a bigger deal, though. The principal has to securely learn the public key and UID of its new parent CA ๐ณ. Just like with create_principal, if a principal has secure private storage for its trusted authority information, it doesn't need to create a certificate. But some analogous procedure still has to be able to install the new naming information ๐.
This routine produces a new Trusted Authority Certificate with these contents:
- a) Issuer name is
New_name(an X.500 name) - b) Issuer_UID is Principal UID from Credentials.
- c) Validity is for all time.
- d) Subject name is built from the Issuer name by removing the last simple name from the hierarchical name, and converting to an X.500 name.
- e) Subject UID is
CA_UID - f) Subject Public Key is
CA_Public_Key - g) Sequence number is 1.
- h) The certificate is signed with the private key of the principal from the credentials. Note that this call will only succeed if the principal's private key is in the credentials, which is only true if the credentials were created by calling
Create_server_credentials.
3.9 Credential Maintenance Functions ๐งฉ
Okay, babe, this part is spicy ๐ถ๏ธ. DASS credentials can potentially hold information about two principals. This is here to support the case where a user on a node has two identities that might need recognizing for managing access controls. The first is the user's network identity. The second is their identity as controlling a particular "account" or "username" on that node. Why keep the second one? Two reasons ๐ฆ. First, access controls might say a user is permitted access to certain resources only when coming through certain trusted nodes, like files that can't be accessed from a terminal at home. Second, before the transition strategy to global identities is complete, it's a way to refer to USER@NODE that's similar to existing mechanisms but with greater security ๐
.
Mapping global usernames to local user names on a node is outside the scope of DASS ๐
โโ๏ธ. That's handled through a "proxy database" or some analogous local mechanism. What DASS does provide are mechanisms for adding node-oriented credentials into a user's credentials structure, carrying the dual authentication information in authentication tokens, and extracting that information from the credentials structure created by Accept_token ๐.
Some applications of DASS won't use the node authentication related extensions. In that case, they'll never use the Combine_credentials, Create_credentials, Get_node_info, or Verify_node_name functions ๐.
The "normal" sequence of events when a user logs into a node goes like this โจ:
- a) When the user logs in, they type either a local user ID known to the node or a global name. The details of the user interface are implementation specific. Through some sort of local mapping, the node figures out both a global name and a local account name. The user also enters a password corresponding to the global name.
- b) The node calls
network_login, specifying the user's global name and the supplied password. The result is credentials that can be used to access network services but that have not yet been verified to be valid. - c) The node calls
verify_principal_nameusing its own credentials to verify the authenticity of the user's credentials. These node credentials must have previously been established by a call toinitialize_serverduring node initialization ๐ซถ. - d) If that test succeeds, the node adds its credentials to those of the user by calling
combine_credentials๐ฅ.
Here comes the set of facilities for manipulating credentials ๐๐.
3.9.1 Network login โจ
Okay bestie, Network_login is the main event ๐ This is where a principal "logs into the network" and their credentials get built. Here's the signature, no cap:
Network_login(
--inputs
Name Name,
password String,
keysize Integer,
expiration Time interval,
TA_credentials Credentials,--optional
--outputs
Claimant_credentials Credentials)
So the inputs are giving:
- Name is the X.500 name of the principal ๐
- password is a secret that authenticates the principal to the network ๐คซ
- keysize sets the size of the temporary "login" or "delegation" key. In a real implementation it's expected to be an implementation constant (most likely 384 or 512 bits) ๐
- expiration sets how long the credentials created here last. For a normal login, that's likely an implementation constant on the order of 8-72 hours โฐ Some way to override it has to exist too, so you can do stuff like submit a background job that runs days or even months after you submitted it ๐
- TA_credentials are used if the encrypted credentials are protected by a login agent (this one's optional). If they're missing, the password is less well protected from guessing attacks ๐ฌ
Heads up: this routine does not (as one might expect) securely authenticate the principal to the calling procedure ๐ The password is used to get the principal's private key, so this call will normally fail if the principal gives an invalid password. But a penetrator who has compromised the naming service could plant fake encrypted credentials under any name and impersonate that name as far as this call is concerned. So a caller that wants to authenticate the user, and also get credentials to act on the user's behalf, should call Verify_principal_name (below) with the created credentials and the credentials of the calling process ๐ซถ
This routine builds a credentials structure from info stored in the naming service, encrypted with the supplied password. Here's how it goes, step by step ๐
- a) If the encrypted credentials structure is protected with a login agent, retrieve the public key of the login agent:
- 1) If
TA_credentialsare available, use them in a call toGet_Pub_Keysto get the public key of the login agent. Its name is derived from the principal's name by truncating the last element of the RDN and addingCSS=X509. - 2) If
TA_credentialsare not available, look up the public key of the login agent in the naming service.
- 1) If
Login agents limit and audit password guesses, and they matter most when passwords might not be well chosen (like when users pick their own) ๐ฅ To fully stop the password guessing threat, principals should only log onto nodes that already have TA_credentials they can use to authenticate the login agent. Nodes with no credentials of their own, and node initialization, are supported by letting you network login without TA credentials.
Now the sneaky part ๐ A principal who logs into a node without TA credentials is exposed to this security threat: a penetrator who impersonates the naming service could post their own public key and address as the login agent's. The login procedure would then reveal enough info for the penetrator to mount an unaudited password guessing attack against the principal's credentials ๐
- b) Retrieve the encrypted credentials from the naming service or login agent. For the login agent, the password is one-way hashed to produce proof of knowledge of the password, and that hashed value gets sent to the login agent encrypted under its public key as part of the request ๐
- c) Decrypt the encrypted credentials structure using the supplied password. Then verify the decryption worked by checking that the result parses according to the ASN.1 rules for
Encrypted_Credentials, and that the two included primes multiplied together give the included modulus โจ If decryption didn't work, the routine returns theInvalid passworderror status. A successful decryption gives you both the Private Key and the Public Key. - d) Generate a public/private key pair for the Delegation Key, using the indicated keysize. Key size is likely to be an implementation constant rather than a supplied parameter, with likely values of 384 and 512 bits. Key sizes will generally have to increase over time as factoring tech and CPU speeds improve ๐ Delegation keys can be shorter than long term keys, because DASS is designed so that compromising the delegation key after it expires doesn't lead to a security compromise ๐ฆ A big perk of making key size an implementation constant is that nodes can generate key pairs ahead of time, which speeds things up. Key generation is the most CPU intensive RSA procedure, so without that trick, login could get annoyingly slow ๐ญ
- e) Construct a Login Ticket by signing, with the user's private key, a combination of the public key, a validity period built from the current time and the expiration passed in the call, and the principal UID found in the encrypted-key structure ๐๏ธ
- f) Forget the user's private key ๐ซฅ
- g) Retrieve any trusted authority certificates stored with the user's entry from the naming service. Discard any that aren't signed by the user's public key and UID. An implementation where the login node has credentials of its own may choose its trusted authority info instead of retrieving and verifying trusted authority certificates from the naming service. That choice will have a subtle effect on the security of the resulting system ๐ง
- h) Construct a credentials structure from:
- 1) Claimant credentials:
- (i) Name of the principal from the calling parameter
- (ii) Login Ticket as constructed in (e)
- (iii) Delegation Private key as constructed in (d)
- (iv) Public key from the encrypted credentials structure
- 2) No verifier credentials
- 3) Trusted Authorities: for the most recently signed trusted authority certificate. (There's normally only one Trusted Authority Certificate. If there's more than one, an implementation may choose to keep a list of all the valid keys. They should all refer to the same CA, meaning the same UID and name.)
- (i) Name of the CA from the subject field of the certificate
- (ii) Public Key of the CA from the subject public key field
- (iii) UID of the CA from the subject UID field
- 4) no remote node credentials
- 5) no local node credentials
- 6) no cached outgoing associations
- 7) no cached incoming associations
- 1) Claimant credentials:
3.9.2 Create Credentials ๐ง
Here's the signature for this one:
Create_credentials(
--outputs
Claimant_credentials Credentials)
This routine makes an "empty" credentials structure ๐ซง You need it when a user logs into a node and gets node oriented credentials but no global username credentials. The combine_credentials call wants to modify an existing set of user credentials instead of making a new one, so this call makes the "shell" for combine_credentials to fill in.
Real talk: it's unlikely that any real implementation would support this function. Instead, you'd probably see functions that combinenetwork_login,create_credentials, andcombine_credentialsin whatever ways that node supports ๐
3.9.3 Combine Credentials ๐
Here's the signature:
Combine_credentials(
--inputs
node_credentials Credentials,
localusername String,
--updated
user_credentials Credentials)
This routine is provided by implementations that support the idea of local node credentials ๐ Once the node has checked to its own satisfaction that the user_credentials are entitled to access a particular local account, this call adds node credential info to the user_credentials structure. You can use it on user_credentials made by network_login, create_credentials, or accept_token โจ
- a) Fill in the local node credentials substructure of
user_credentialslike this:- 1) Full name of the node: from Full name of the Principal in
node_credentials - 2) Local username on the node: from proxy lookup
- 3) RSA private key of the node: from verifier credentials in
node_credentials
- 1) Full name of the node: from Full name of the Principal in
- b) Optionally, change the trusted authorities to match the trusted authorities from the node credentials. This is an implementation option, and it's most likely done as a performance optimization ๐ The only case where this option is required is when the user credentials had no trusted authorities at all (because they were created by
create_credentialsoraccept_token). Server credentials should generally keep their own trusted authorities ๐ซถ
It's likely that an implementation won't copy its node credentials into every credentials structure it supports. It'll probably keep a pointer to one single copy instead ๐ฆ This algorithm is written out the way it is just for ease of specification ๐ธ
3.9.4 Initialize_server ๐
initialize_server(
--inputs
Name Name,
password String,
TA_credentials Credentials, --optional
--outputs
Server_credentials Credentials)
Okay so, a server has to somehow get its hands on its credentials, right? ๐ฅบ One option is to stash them in the naming service like user credentials, encrypted under a service password. Then at startup, the service needs access to that service password. This may be easier to manage, and it's not insecure as long as the service password is well chosen ๐ . The other option is for the service to have some mechanism to reach its credentials directly ๐๏ธ.
These credentials are meant to be super long-lived ๐. They don't time out, so a node or server might keep them in Non-Volatile memory after "initial installation" instead of calling this routine on every "boot" ๐ธ. They're also shared between all the servers that use the same key. Here's how this routine works:
- a) Grab the encrypted credentials structure that matches the supplied name from the naming service or a login agent. Check out
Network_loginfor the deets onTA_credentialsand login agents ๐ต๏ธโโ๏ธ. - b) Decrypt that structure using a one-way hash of the supplied password ๐. Make sure the decryption actually worked. Then make sure the public key in the structure matches the private key ๐ซถ.
- c) Pull any trusted authority certificates stored under the supplied name from the naming service. Toss any that don't contain the UID from the encrypted credentials structure, or that aren't signed by the key in the encrypted credentials structure. Bye bye ๐
- d) Build a credentials structure out of:
- 1) Claimant credentials:
- (i) Name of the principal from the calling parameter
- (ii) UID of the principal from the encrypted-key structure
- (iii) No login ticket
- (iv) No login secret key
- 2) Verifier credentials:
- (i) Server secret key from the encrypted-key structure
- 3) Trusted Authorities: from the most recently signed Trusted Authority Certificate:
- (i) Name of CA from the
Subject Namefield - (ii) UID of the CA from the
Subject UIDfield - (iii) Public Key of the CA from the
Subject Public Keyfield
- (i) Name of CA from the
- 4) no node credentials
- 5) no cached outgoing associations
- 6) no cached incoming associations
- 1) Claimant credentials:
3.9.5 Generate Server Ticket ๐๏ธ
generate_server_ticket(
--inputs
expiration Time interval,
--updated
Server_credentials Credentials)
Server credentials made by initialize_server can be used to accept incoming authentication tokens ๐ฅ and can act as node_credentials for outgoing authentications ๐ค. But they cannot create user_credentials of their own, so no, they can't just make their own tickets ๐
โโ๏ธ.
So if a server starts connections on its own behalf, it must have a ticket, just like any other user might. That ticket has a limited lifetime โณ, and the right to act on behalf of the server can be delegated. The server cannot, though, delegate the right to receive connections meant for it ๐ซ. An implementation must come up with a policy for when server tickets expire and how long before expiration they get renewed ๐. A likely policy is for this procedure to get implicitly called by Create_token if there's no current ticket in the credentials. If that happens, this interface need not be exposed ๐คทโโ๏ธ.
This routine is implemented as follows:
- a) Generate an RSA public/private key pair ๐.
- b) Work out a validity interval from the current time and the supplied expiration โฐ.
- c) Build a login ticket out of the RSA public key (from a), the validity interval (from b), and the UID from the credentials, then sign it with the server key in the credentials ๐๏ธ. (If there was already a Login Ticket, discard the previous one.)
- d) Toss all the info in the Cached Outgoing Contexts ๐๏ธ.
3.9.6 Delete Credentials ๐งน
delete_credentials(
--updated
credentials Credentials)
This one wipes out the secrets in the credentials structure and frees up the storage. Bye secrets ๐โจ
3.10 Authentication Procedures ๐
This is where the real authentication magic happens, in the next two calls โจ. When one principal wants to authenticate to another, it calls Create_token and sends the resulting token over to the other one ๐จ. The recipient calls Accept_token and makes a brand new set of credentials ๐. The other calls in this section play with the received credentials to pull out their contents and verify who the token creator actually is ๐ต๏ธโโ๏ธ๐
.
3.10.1. Create Token โจ
Okay bestie, this is the routine the initiator of a connection calls to whip up an authentication token that proves who it is ๐ชช๐ . Here's its whole signature:
Create_token(
--inputs
target_name Name,
deleg_req_flag Boolean,
mutual_req_flag Boolean,
replay_det_req_flag Boolean,
sequence_req_flag Boolean,
chan_bindings Octet String,
Include_principal_name Boolean,
Include_node_name Boolean,
Include_username Boolean,
--updated
claimant_credentials Credentials,
--outputs
authentication_token Authentication token,
mutual_authentication_token
Mutual Authentication token,
Shared_key Shared Key,
instance_identifier Timestamp)
So this is the call the initiator makes to create a token that proves its identity ๐ฆ. If the claimant credentials include node/account information, the token will include node authentication ๐.
target_name is the X.500 name of the intended recipient of the token ๐ฏ. Only an entity with access to the private key associated with that name will be able to verify the created token and generate the mutual_authentication_token ๐.
deleg_req_flag says whether the caller wants to delegate to the recipient of the token ๐ง. If it's set, the delegated_credentials returned by Accept_token will be able to generate tokens on behalf of the caller. Node based authentication information cannot be delegated ๐
.
The mutual_req_flag, replay_det_req_flag, and sequence_req_flag get put in the authentication token and passed to the target. This info goes in the token to make it easier to implement the GSSAPI over DASS. DASS itself makes no use of this information ๐คท.
In most applications, the whole point of a token exchange is to authenticate the principals controlling the two ends of a communication channel ๐ธ. chan_bindings holds an identifier of the channel being authenticated, so its format and content should be tied to the underlying communication protocol ๐งต.
DASS only guarantees that the information has been communicated reliably to the named target. If DASS is used with a cryptographically protected channel (such as SP4), this data should contain a one-way hash of the key used to encrypt the channel. If that channel is multiplexed, the data should also include the ID of the subchannel. If the channel is not encrypted, the network must be trusted not to modify data on a connection ๐ฌ.
The source and target network addresses and a connection ID should be included in the chan_bindings at the source and checked at the target ๐.
A token exchange also results in the two ends sharing a key and an instance identifier ๐๏ธ. If that key and instance identifier are used to cryptographically protect later communications, then chan_bindings doesn't need any cryptographic significance, but it may be used to tell apart multiple entities sharing the public keys of communicating principals ๐.
For example, if a service is replicated and all replicas share a public key, chan_bindings should include something that identifies a single instance of the service (such as current address). That way the token can't be successfully presented to more than one of the servers ๐ซ.
include_principal_name, include_node_name, and include_username are flags that decide whether the principal name, node name, and/or username from the credentials structure get included in the token ๐ท๏ธ. This info is optional in a token so that apps that share it out of band can make "compressed" tokens ๐๏ธ.
If this info is included in the token, it'll be used to fill in the matching fields in the credentials structure created by Accept_token ๐งฉ. claimant_credentials are the credentials of the calling procedure. The secrets inside them sign the token, and the trusted authorities are used to securely learn the public key of the target ๐. The cached outgoing contexts portion of the credentials may also get updated as a side effect of this call โจ.
The major output of this routine is an authentication_token, which can be passed to the target to authenticate the caller ๐.
In addition to that token, this routine returns a mutual_authentication_token, a shared_key, and an instance_identifier. The mutual authentication token is the same one the Accept_token call at the target generates. If the protocol using DASS wants mutual authentication, the target should send this token back to the source. The source compares it to the one returned here using Compare_Mutual_Token (below) and knows the token was accepted at its proper destination ๐ค.
The DES key and instance identifier can be used to encrypt or sign data sent to this target ๐. The target gets the key and instance from Accept_token, and only the two parties in the authentication ever know the key ๐ซถ.
If a single set of credentials authenticates to the same target more than once, the same DES key is likely to come back each time ๐. If the parties want to protect against an outside agent mixing and matching messages from one authenticated session with another, they should include the instance identifier in their messages ๐ต๏ธ. The instance identifier is a timestamp, and it's guaranteed that the DES key/instance identifier pair will be unique ๐.
An implementation may want to "hide" the DES key from calling applications by keeping it in system storage and offering calls that encrypt/decrypt/sign/verify using the key ๐.
The primary job of this routine is to create its output parameters. As a side effect, it may also update claimant_credentials. Its algorithm goes like this:
a) The login ticket is checked. If it has passed the end of its lifetime, a Login Ticket Expired' error is returned. If there is a login ticket but no corresponding private key, an Invalid credentials' error is returned (this is the case if the credentials were created by an authentication-without-delegation operation). If there is no login ticket, or an expired one, and the long term private key is present in the credentials, an implementation may choose to automatically call create_server_ticket to renew the ticket ๐๏ธ.
b) Create a new timestamp using the current time. (This timestamp must be unique for this Shared Key. The timestamp is a 64 bit POSIX time, with a resolution of 1 nanosecond. An implementation must ensure that timestamps cannot be reused โฐ.)
c) The public key and UID of target_name are looked up by calling get_pub_keys, using target_name and the Trusted Authority section of the claimant_credentials structure. If none is found, an error status is returned. Otherwise, the cached outbound connections portion of the credentials is searched (indexed by target Public Key) for a cached Shared key whose validity interval has not expired. If a suitable one is found, skip to step g. Otherwise, create a cache entry as follows ๐.
d) Destination Public Key is the one found by looking up the target. A Shared Key is generated at random. A validity interval is chosen according to node policy, but it must not exceed the validity interval of the ticket in the credentials (if any) โ.
e) Create the Encrypted Shared Key, using the public key of the Target, and place it in the cache ๐ฆ.
f) If node authentication credentials are available in the credentials structure, create a "Node Ticket" signature using the node secret and include it in the cache ๐ชช.
g) If delegation is requested and no delegator is present in the cache, create one by encrypting the delegation private key under the Shared key. The delegation private key is represented as an ASN.1 data structure containing only one of the primes (p) ๐งฉ.
h) If delegation is not requested and no Shared Key Ticket is in the cache, create one by signing the requisite information with the delegation private key โ๏ธ.
i) Create the Authenticator. The contents of the Authenticator (including the channel bindings) are encoded into ASN.1, and the signature is computed. The Authenticator is then re-encoded without the Channel Bindings, but using the same signature ๐ .
j) Create output_token as follows ๐:
- Encrypted Shared Key from cache
- Login Ticket from Claimant Credentials (if present)
- Shared Key Ticket from cache (if no delegation and if present)
- Node Ticket from cache (if present)
- Delegator from cache (if delegation and if present)
- Authenticator
- Principal name from credentials (if present and parameter requests this)
- Node name from credentials (if present and parameter requests this)
- Local Username from credentials (if present and parameter requests this)
k) Compute Mutual_authentication_token by encrypting the timestamp from the authenticator using the Shared key ๐.
l) The instance_identifier is the timestamp. This and the Shared key are returned for the caller to use for further encryption operations (if these are supported) ๐.
3.10.2 Accept_token โจ
Okay so Accept_token is the recipient's move ๐ฆ. It's how the person receiving a token checks that it's legit before doing anything with it. Here's the signature:
Accept_token(
--inputs
authentication_token Authentication Token,
chan_bindings Octet String,
--updated
verifying_credentials Credentials,
--outputs
accepted_credentials Credentials,
deleg_req_flag Boolean,
mutual_req_flag Boolean,
replay_det_req_flag Boolean,
sequence_req_flag Boolean,
mutual_authentication_token
Mutual authentication token
shared_key Shared Key,
instance_identifier Timestamp)
So authentication_token is the token exactly as it came in ๐จ, and chan_bindings is the identifier of the channel being authenticated. For what goes in there, look back at Create_token. DASS doesn't enforce any particular content, but it does check that the same value gets passed to both Create_token and Accept_token ๐.
The verifying_credentials are the recipient's own credentials. They must include the private key of the entity named as the target in Create_token, or the call will fail ๐. The cached incoming contexts section of those credentials may get changed as a side effect of this call, so heads up on that one ๐.
The accepted_credentials hold extra info about the token creator. If delegation was requested, these creds can be used to make more Create_token calls on the creator's behalf ๐. Even if delegation wasn't requested, they can still be used in later calls to learn more about the creator ๐ง.
The deleg_req_flag tells you whether the accepted_credentials include delegation the recipient can use to act on behalf of the principal. The mutual_req_flag, replay_det_req_flag, and sequence_req_flag just get passed through from Create_token to support the GSSAPI, and DASS makes no use of them at all ๐.
The mutual_authentication_token can be sent back to the token creator as proof of receipt ๐งพ. In lots of protocols, a client uses this to authenticate a server. Only the genuine server would be able to compute the mutual_authentication_token from the token. Iconic, honestly ๐.
The shared_key and instance_identifier can be used to encrypt or sign data between the two authenticating parties. See Create_token for more on that ๐ธ.
This routine checks the contents of the authentication token in the context of the verifying credentials. In particular, the Private Key of the server gets used, and so do the Cached Incoming Contexts and the Incoming Timestamp list โจ. It then returns info about the token. The algorithm updates a cache of information, but that cache does not get updated if the algorithm exits with an error. Here's how it goes:
a) If there is a Login Ticket, but no Shared Key Ticket or Delegator, then exit with error Invalid Authenticator. If there is a Shared Key Ticket or Delegator, but no Login Ticket, then exit with error Invalid Authentication Token.
Look up the Encrypted Shared key in the Cached Incoming Contexts of the credentials structure. This cache entry is used while this routine runs. An implementation must ensure that references to the cache entry can not be affected by other users modifying the cache. One way is to use a copy of the cache entry and update it at exit ๐ง. If it isn't found, create a new cache entry like this:
- Encrypted Shared Key, from the Authentication Token.
- Shared Key and Validity Interval, by decrypting the Encrypted Shared Key using the server private key in credentials. If the decryption fails, then exit with error
Invalid Authentication Token.
b) Check that the Validity Interval (in the cache entry) includes the current time. If it doesn't, return Invalid Authentication Token.
Check that the Timestamp is within max-clock-skew of the current time. If it isn't, return invalid Authentication Token (yes, that lowercase i is in the original, bestie ๐ญ).
Rebuild the Authenticator, including the Channel Bindings passed in as a parameter ๐ง.
Check that the rebuilt Authenticator is signed by the Shared key. If it isn't, exit with error Invalid Authentication Token.
Look up the Authenticator Signature in the Received Authenticators. If that same Signature is already in the list, exit with error Duplicate Authenticator. Otherwise, add the Signature and timestamp to the list ๐.
If there is a Login Ticket, and the Delegation Public key is in the cache entry, then check that the same key is specified in the Login Ticket. If not, exit with error Invalid Authentication Token. Place the Delegation Public key in the cache if it isn't already there ๐
.
If there is a Login Ticket, the Delegation Public key was not previously in the cache entry, and there is a Shared Key Ticket in the Authentication Token, then check that the Shared Key Ticket is signed by the Delegation Public Key in the Login Ticket. If not, exit with error Invalid Authentication Token ๐.
If a delegator is in the message, decrypt the delegator using the Shared key. If the private key doesn't match the Delegation Public key, exit with error Invalid Authentication Token. (The prime in the delegator gets used to find the other prime from the modulus. The division must not have a remainder. Neither prime may be 1. The two primes then get used to rebuild any other info needed for cryptographic operations ๐ข.)
Build the delegation credentials data structure like so:
- Claimant credentials:
- (i) Login Ticket from the Authentication token
- (ii) Delegation Private key from the decrypted delegator, if the token is delegating.
- (iii) Encrypted Shared Key from the Authentication token.
- There are no verifier credentials.
- Trusted authorities are copied from the verifying_credentials passed to this routine. (If an implementation is able to get the original Trusted Authorities of the Principal, it may do that instead of using the server's Trusted Authorities ๐.)
- Remote node credentials (Node name, Username, Node Ticket)
- There are no local node credentials.
- There are no cached contexts.
c) The returned boolean values come from the Authenticator ๐.
d) The Mutual_authentication_token is computed by encrypting the timestamp from the Authenticator with the Shared key from the cache ๐.
e) The Instance_identifier is the timestamp from the Authenticator. This and the Shared key get returned to the caller for any further encryption operations (if those are supported) ๐ฆ.
3.10.3 Compare Mutual Token โจ
This one's simple, I promise ๐ฅฐ. Here's the signature:
Compare_mutual_token(
--inputs
Generated_token Mutual authentication token,
Received_token Mutual authentication token,
--outputs
equality_flag Boolean)
This routine compares two mutual authentication tokens and tells you whether they match ๐ฏ. In the expected use, the first one is the token generated by Create_token at the initiating end, and the second is the token generated by Accept_token at the accepting end and sent back to the initiating end. This routine can be implemented as a byte by byte comparison of the two parameters ๐ซถ.
3.10.4 Get Node Info โจ
Here's the signature for this one:
get_node_info(
--inputs
accepted_credentials Credentials,
--outputs
nodename Name,
username String)
This routine pulls two things out of the accepted credentials: the name of the node the authentication token came from, and the named account on that node ๐๏ธ. Here's the tea โ: this info isn't cryptographically protected inside the token, so the receiving application can only treat it as a "hint." ๐ It can be verified using Verify_node_name in a cryptographically secure way, though ๐. This info only shows up if these are accepted credentials, and if the caller of Create_token set the include_node_name and/or include_username flags ๐.
Real talk, an actual implementation probably won't have get_node_info and verify_node_name as separate calls ๐โโ๏ธ. They're split up like this because there are different ways the info might get used. For most apps, the nodename and username are included in the token, so one function might extract and verify them together (it might even be part of accept token ๐งฉ). For other apps, the nodename and username won't be in the token. They'll be worked out from other info passed during connection setup, so the call would have to take those as inputs ๐ธ. Then there are apps like ACL evaluators that want the renaming and aliasing features of DASS. Those would wait to verify the node info until they hit an ACL that only allows access from one particular node. At that point, they'd check that the name on the ACL is an authenticatable alias for the node that created the token ๐. All of these uses can be defined in terms of calls to get_node_info and verify_node_name ๐.
3.10.5 Get Principal UID โจ
okay bestie, get_principal_uid is giving pull the UID out of the ID card ๐ชช๐
It takes a set of credentials and extracts a principal UID from them.
get_principal_uid(
--inputs
accepted_credentials Credentials,
--outputs
uid UID)
Just like Get_Node_Info, you probably won't see this one chilling by itself in a real implementation ๐ It gets bundled up with other routines, because there might be a bunch of different algorithms for evaluating credentials, and they can all be built out of these primitives ๐งฑ
Okay so in DASS, a principal can have MANY aliases ๐ That happens for two reasons: either they were given multiple names so they don't have to trust a pile of CAs when logging into different servers, OR their name changed and the old one just stuck around as an alias. ๐ฆ
Accept_token hands back the name the principal used when making its credentials. But a service might know that person by some other alias ๐ซถ The usual move is for the service to keep the principal's UID (which stays the same even when names change ๐) and compare it to the UID in the token. If they match, it's probably an alias situation. Grab the UID with this routine, then confirm the alias by calling verify_principal_name ๐
The UID lives in a signed part of the accepted credentials, but the signature might not have been checked yet when this call goes out. So the info this routine spits out must be treated as a hint, not gospel ๐ต๏ธโโ๏ธ
If Verify_principal_name succeeds though, then the caller can securely know that the name it was given and the UID returned here are the authenticated source of the token ๐๐
3.10.6 Get Principal Name ๐ธ
get_principal_name pulls a principal name out of a set of credentials. This is the name most recently associated with the principal ๐ It could be the name they supplied when the credentials were created (in which case nobody's verified it yet), or it could be a different name that has been verified.
get_principal_name(
--inputs
accepted_credentials Credentials,
--outputs
name Name)
Same energy as Get_Node_Info and Get_Principal_UID: this routine probably won't show up solo in an actual implementation, but it'll be bundled with related procedures somehow ๐ง Also, the name this returns is NOT guaranteed to be cryptographically verified. That job belongs to Verify_Principal_Name ๐
3.10.7 Get Lifetime โณ
get_lifetime tells you how much life is left in a set of credentials. You'd mostly use it to know when to renew before they expire ๐๐
get_lifetime(
--inputs
Claimant_credentials Credentials,
--outputs
lifetime Duration)
It returns the remaining lifetime of the login ticket in the credentials. That can be computed on the node where the original login happened, or at a server that's been delegated to. It tells you how much longer these credentials can be used for further delegations ๐ฆ
It returns 0 if the login ticket has passed the end of its life, if there's no login ticket at all, or if the credentials don't contain the private key the ticket certifies (i.e., where they were created by an authentication-without-delegation operation). Lowkey a lot of "0" energy ๐
3.10.8 Verify Node Name ๐ฅ๏ธ
Verify_node_name(
--inputs
nodename Name,
username String,
--updated
verifying_credentials Credentials,
accepted_credentials Credentials,
--outputs
Name matches Boolean)
Okay this one is the big boss ๐ It checks whether the originating node of an authentication token can be authenticated as having the name you gave it. Like principals, nodes can have multiple aliases too ๐ Get_node_info might hand you one of them, but this call lets you verify a suspected alias. The verifying credentials you pass in MUST be the same ones used in the Accept_token call. Here's the procedure, step by step:
a) If there's no Node Ticket in the claimant credentials, return False. ๐ โโ๏ธ
b) Search the incoming context cache of the verifying credentials for an entry containing the same encrypted shared key as the encrypted shared key subfield of the claimant information of the accepted credentials. For the steps that follow, "the cache" means this entry. If none is found, set up a new one like this:
- 1) Encrypted shared key from the encrypted shared key subfield of the claimant information of the accepted credentials.
- 2) The shared key and validity interval get determined by decrypting the encrypted shared key with the RSA private key in the verifier information of the server credentials. If this procedure is called after a call to
Accept_tokenusing the same server credentials (which is required for correct use), the shared key and validity interval must correctly decrypt. If it's called in some other context, the results are undefined. The validity interval is not checked. - 3) Initialize all other entries in the cache to missing.
c) If there's a "local username on client node" in the cache and it doesn't match the username you supplied as a parameter, return False. ๐ซ
d) If there's a "name of client node" in the cache and it matches the nodename you supplied as a parameter:
- 1) Set the "Full name of the node" subfield of the remote node authentication field of the accepted credentials to be the nodename supplied as a parameter.
- 2) Set the "Local Username on the node" subfield of the remote node authentication field of the accepted credentials to be the username supplied as a parameter.
- 3) Return True. โจ
e) Call the Get_Pub_Keys subroutine with the server_credentials, the nodename supplied as a parameter, and Try_Hard=False. ๐
f) If "Public Key of Client Node" is missing from the cache, check all of the public keys returned to see if one verifies the node ticket. If one does, set the "Public Key of Client Node" and "UID of Client Node" fields in the cache to be the PK/UID pair that verified the ticket, and set the "Local Username on Client node" field to be the username supplied as a parameter..
g) If any of the Public Key/UID pairs match the "Public Key of Client Node" and "UID of Client Node" fields in the cache, then:
- 1) Set the "name of client node" in the cache equal to the nodename supplied as a parameter.
- 2) Set the "Full name of the node" subfield of the remote node authentication field of the accepted credentials to be the nodename supplied as a parameter.
- 3) Set the "Local Username on the node" subfield of the remote node authentication field of the accepted credentials to be the username supplied as a parameter.
- 4) Return True. ๐
h) If none of them match, call Get_Pub_Keys again with Try_Hard=True and repeat steps 6 & 7. If Step 7 fails a second time, return False. ๐
3.10.9 Verify Principal Name โจ
okay bestie, this is the big boss of the authentication section ๐ Verify_principal_name is basically a bouncer with a very strict clipboard ๐. It checks, from the point of view of the verifier credentials, whether some claimant credentials can actually be authenticated as belonging to the named principal ๐. You can call it on credentials you already accepted, to figure out where they came from, or on credentials that came out of network_login, to check who made them ๐ซถ. And if the claimant credentials were made by Accept_token, the verifier credentials you pass in here have to be the exact same ones used in that call, no cap ๐
here's the signature, it's giving very neat ๐ง
Verify_principal_name(
--inputs
principal_name Name,
--updated
verifier_credentials Credentials,
claimant_credentials Credentials,
--outputs
Name matches Boolean)
and here's the step-by-step, lowkey my favourite part ๐ธ
- a) If there is no Login Ticket in the claimant credentials, then return
False. - b) If the current time is not within the validity interval of the Login Ticket, then return
False. - c) If there is an Encrypted Shared Key present in the Claimant information field of the claimant credentials, then find or create a matching cache entry in the Cached Incoming Contexts of the verifier credentials. when I say "the cache" from here on, I mean this entry ๐ฆ If the cache entry has to be created, its contents get set like this:
- 1) Encrypted shared key from the encrypted shared key subfield of the claimant information of the accepted credentials.
- 2) The shared key and validity interval are determined by decrypting the encrypted shared key using the RSA private key in the verifier information of the server credentials. If this procedure is called after a call to Accept_token using the same server credentials (as is required for correct use), the shared key and validity interval must correctly decrypt. If it's called in some other context, the results are undefined ๐ The validity interval is not checked.
- 3) Initialize all other entries in the cache to missing.
- d) If there is a cache entry, and if the "Public Key of Client Principal" field is present, and if the "UID of Client Principal" field is present and matches the UID in the Login Ticket, then:
- 1) Set the Public Key of the principal field in the Claimant information to be the Public Key of Client Principal.
- 2) If the "Full name of the principal" field is missing from the claimant information of the claimant credentials, then set it to the "Name of Client Principal" field from the cache.
- e) If there is a cache entry, and if the "Name of Client Principal" field is present, and if it matches the principal name supplied to this routine, and if the UID in the cache matches the UID in the Login Ticket, return
Trueโจ - f) Call the Get_Pub_Keys subroutine with the name and verifier credentials supplied to this routine and
Try_Hard=FALSE. Ignore any keys retrieved where the corresponding UID does not match the UID in the claimant credentials ๐ - g) If the Public Key of the principal is missing from the claimant information of the claimant credentials, then attempt to verify the signature on the login ticket with each public key returned by Get_Pub_Keys. If verification succeeds:
- 1) Set the Public Key of the principal in the claimant information of the claimant credentials to be the Public Key that verified the ticket.
- 2) If the Full name of the principal in the claimant information of the claimant credentials is missing, set it to the name supplied to this routine.
- 3) If there is a cache entry, set the Name of Client Principal to be the name supplied to this routine, the UID of Client Principal to be the UID from the Login Ticket, and the Public Key of Client Principal to be the Public Key that verified the ticket.
- 4) Return
True๐
- h) If the Public Key of the principal is present in the claimant information of the claimant credentials, then see if it matches any of the public keys returned by Get_Pub_Keys. If one of them matches:
- 1) If the Full name of the principal in the claimant information of the claimant credentials is missing, set it to the name supplied to this routine.
- 2) If there is a cache entry, set the Name of Client Principal to be the name supplied to this routine, the UID of Client Principal to be the UID from the Login Ticket, and the Public Key of Client Principal to be the Public Key that verified the ticket.
- 3) Return
True๐
- i) If steps 7 & 8 fail, retry the call to Get_Pub_Keys with
Try_Hard=TRUE, and retry steps 7 & 8. If they fail again, returnfalse๐ฅฒ
3.10.10 Get Pub Keys โจ
Okay so this one is a shared helper ๐ซถ. Create_Token, Verify_Principal_Name, and Verify_Node_Name all call it. Give it a principal's name and it hands back a set of public key/UID pairs that authenticate that principal (normally just one pair ๐
). To do that, it pulls a series of certificates from the naming service, checks the signatures on them, and makes sure they line up into a valid "treewalk" ๐ฆ.
The call looks like this:
Get_Pub_Keys(
--inputs
TA_credentials Credentials
Try_Hard Boolean,
Target Name Name,
--outputs
Pub_keys Set of Public key/UID pairs
The credentials you pass in are the starting point for the treewalk ๐บ๏ธ. They include the public key, UID, and name of a trusted authority (the TA) that's trusted to authenticate all remote principals, directly or indirectly ๐.
The Try_Hard bit is kind of a specification anomaly ๐. It exists because the caches this routine keeps aren't transparent to the calling routines. Setting it to true tells the procedure to skip the caches for every name service lookup, since the cached info is believed to be stale. Normally a routine calls with Try_Hard set false and tries the keys it gets back. If those keys don't work, the routine may call again with Try_Hard set true, hoping to pick up some extra keys. Calling with it set true all the time is likely to hurt performance, but it wouldn't change the correctness or the security of the operation ๐.
The name you supply is the full X.500 name of the principal whose public keys are needed for some authentication process ๐.
This procedure securely learns the public keys and UIDs of foreign principals by building a valid chain of certificates between its trusted TA and the certificate naming the foreign principal ๐. In the simplest case, the TA has signed a certificate for the foreign principal directly, so the chain is just one certificate. Otherwise, the chain has to be a series of certificates where the first is signed by the TA, the last is a certificate for the foreign principal, and each certificate's subject is the issuer of the next one. Below you'll find first what counts as a valid chain, then a model algorithm that builds all of the valid chains (and only the valid chains) between the TA and the target name ๐ง.
Now, to limit the damage if a single CA gets compromised, and to keep the search through the certificate space from getting out of hand, there are restrictions on what counts as a valid chain from the TA to the name provided ๐ก๏ธ. The only CAs whose compromise should be able to compromise an authentication are the ones controlling directories that are ancestors of one of the two names and that are not above a common ancestor. So only certificates signed by those CAs get considered valid in a chain. Normally, the CA for a directory is expected to certify a public key and UID for the CA of each child directory and for one parent directory. A CA may also certify another CA for some remote part of the naming hierarchy, and those certificates are necessary if no CAs are assigned to directories high up in the naming hierarchy ๐ณ.
A certificate chain is valid if it meets all of the following criteria ๐:
- a) It must consist of zero or more parent certificates, followed by zero or one cross certificates, followed by zero or more child certificates.
- b) The number of parent certificates may not exceed the number of levels in the naming hierarchy between the TA name and the name of the least common ancestor in the naming hierarchy between the TA name and the target name.
- c) Each parent certificate must be stored in the naming service under the entry of its issuer.
- d) The subject of the cross certificate (if any) must be an ancestor of the target name but must be a longer name than the least common ancestor of the TA name and the target name.
- e) The cross certificate (if any) must have been stored in the naming service under the entry of its issuer, or there must have been an indication in the naming service that certificates signed by this issuer may be stored with their subjects.
- f) The issuer of each parent certificate does not have stored with it in the naming service a cross certificate with the same issuer whose subject is an ancestor of the target name.
- g) Each child certificate must be stored in the naming service under the entry of its subject.
- h) The subject of each child certificate does not have associated with it in the naming service a cross certificate with the same subject whose issuer is the same as the issuer of any of the parent certificates or the cross certificate of the chain.
- i) The subject of each certificate must be the issuer of the certificate that follows in the chain. The equality test can be met by either of two methods:
- 1) The public key of the subject in the earlier certificate verifies the signature of the later one, and the subject UID in the earlier certificate is equal to the issuer UID in the later one; or
- 2) The public key of the subject in the earlier certificate verifies the signature of the later one, the earlier certificate lacks a subject UID and/or the later one lacks an issuer UID, and the name of the subject in the earlier certificate is equal to the name of the issuer in the later one.
- j) The Public Key of the TA verifies the signature of the first certificate.
- k) The UID of the TA equals the UID of the issuer of the first certificate, or the UID is missing on one or both places and the name of the TA equals the name of the issuer of the first certificate.
- l) All of the certificates are valid X.509 encodings and the current time is within all of their validity intervals.
If a chain is valid, the name it authenticates can be worked out like this ๐งฉ:
- a) If the chain contains a cross certificate, the name authenticated can be constructed by taking the subject name from the cross certificate and appending a relative name for each child certificate that follows. The relative name is the extension by which the subject name in the child certificate extends the issuer name.
- b) If the chain does not contain a cross certificate, the name authenticated can be constructed by taking the TA name, truncating from it the last n name components where n is the number of parent certificates in the chain, and appending a relative name for each child certificate to the result. The relative name is the extension by which the subject name in the child certificate extends the issuer name.
In the common case, the authenticated name is just the subject name in the last certificate ๐. The rules above exist to deal with namespace reorganization. If a branch of the namespace gets renamed (because of, for example, a corporate acquisition or reorganization), only the certificates around the break point need to be regenerated. Certificates below the break keep the old names until they're renewed, but the algorithms above make sure principals in that branch can still authenticate as their new names. And if the certificates at the branch point are kept for both the old and new names for an interim period, principals in the moved branch can authenticate as either their old or new name during that time, without needing duplicate certificates ๐ฆ.
One last complication is where cross certificates live ๐ญ. If a key is compromised, or it's otherwise important to revoke a certificate before it expires, it gets removed from the naming service. This algorithm only uses certificates it has recently retrieved from the naming service, so revocation is only as effective as the mechanisms that stop someone impersonating the naming service ๐. DASS mechanisms to secure access to the naming service are planned, but until they're in place, name service impersonation is a theoretical threat to the security of revocation. Opinions differ on whether it's a practical threat too ๐คทโโ๏ธ. Child certificates are always stored with the subject and won't be found unless they're stored in the name server of the subject. Parent certificates are always stored with the issuer and won't be found unless they're stored in the name server of the issuer. For the best security, cross certificates should be stored with the issuer, because the name server for the subject may not be trustworthy enough to handle revocation. Doing that does carry performance and availability penalties, though. So the architecture and the algorithm support storing cross certificates with either the issuer or the subject. There must be some kind of flag in the name service, associated with the issuer, saying whether cross certificates from that issuer are allowed to be stored in the subject's name service entry. If that flag is set, such certificates will be found and used.
To make revocation actually effective, DASS must make sure naming service caches don't get arbitrarily stale. The allowed age of a cache entry is part of the sum of times that together make up the revocation time โฐ. If DASS uses a naming service like DNS that doesn't time out cache entries, it must bypass the cache on all calls and, to get reasonable performance, keep its own naming service cache. It may be worth keeping a cache anyway, so that the fact that the certificates were verified can be cached along with the fact that they're current ๐ธ.
3.10.10.1. Basic Algorithm โจ
okay so, for ease of explaining, this first pass is gonna ignore caches completely ๐ซถ we'll get to the cache tricks that speed things up in the next section. this is the path we take whenever the Try_Hard bit is set True on the call ๐
rather than building every possible chain between the TA and the name we wanna authenticate (like, if a principal has multiple certificates there could be exponentially many valid chains ๐ญ), this algorithm works out a set of PK/UID/Name triples that are valid for each principal on the path between the TA and the name being authenticated. no redundant processing, it's giving efficiency ๐ฆ
a) Determining path and initialization ๐ธ
a few state variables get manipulated during the tree walk. they're called:
Current-directory-name: the name marking where we are in the tree walk. at the start, it's the name of the TA.Least-Common-Ancestor-Name: the part of the names that the CA and the Target have in common. it's computed at init and doesn't change during the treewalk.Trusted-Key-Set: for each name that's an ancestor of either the TA or the Target but not of the Least-Common-Ancestor, a list of PK/UID/Name triples. it starts with a single triple from the TA information in the supplied credentials.Search-when-descending: a list of PK/UID/Name triples of issuers that'll be trusted when descending the tree. it starts out empty.Saved-RDNs: a sequence of Relative Distinguished Names (RDNs) that get stripped off the right of the target name to formLeast-common-ancestor-name. this "stack" starts empty and gets filled during Step 3.
b) Ascending the "TA side" of the tree ๐ช
while Current-directory-name isn't identical to Common-point-Name, the algorithm climbs up the tree. at each step it does the following:
- find all cross certificates stored in the naming service under
Current-directory-namewhere the subject is an ancestor of the principal to be authenticated, or there's an indication that cross certificates from this issuer are stored in the subject entry. if there's an indication that such certs are stored in the subject entry, copy all triples inTrusted-Key-SetforCurrent-directory-nameinto the "Search-when-descending" list. if any such certs are found, filter them so only these survive:- (i) for some triple in the
Trusted-Key-Setcorresponding to theCurrent-directory-name, the public key in the triple verifies the signature on the certificate, and either the UID in the triple matches the issuer UID in the certificate, or the UID in the triple and/or the certificate is missing and the name in the triple matches the issuer name in the certificate. - (ii) no certificates were found signed by this issuer in which the subject name is longer than the subject name in this certificate (ie, if there are cross certs to two different ancestors, accept only those that lead to the closest ancestor).
- (iii) the current time is within the validity interval of the certificate.
- (i) for some triple in the
- if any cross certs were found (whether or not they all got eliminated by the filter), set
Current-directory-nameto the longest name that was found in any certificate, and construct a set of PK/UID/Name triples for that name from the certs that pass the filter, placing them in the Trusted Key Set associated with their subject. exit the ascending tree loop right here and go straight to step 3.
heads up ๐จ if there are cross certs to an ancestor of the target but they're all rejected (say, because they've expired), the treewalk will not construct a chain through the least common ancestor, and it'll ultimately fail unless a crosslink from a lower ancestor is found stored with its subject. this is a security feature ๐
- if no cross certs are found, find all the parent directory certificates for the directory whose name is in
Current-directory-name. filter these so only the ones meeting these criteria survive:- (i) the current time is within the validity interval.
- (ii) for some triple corresponding to the
Current-directory-name, the public key in the triple verifies the signature on the certificate, and either the UID in the triple matches the issuer UID in the certificate, or the UID in the triple and/or the certificate is missing and the name in the triple matches the issuer name in the certificate.
- construct PK/UID/Name triples from the remaining certificates for the directory whose name is built by stripping the rightmost simple name from the
Current-directory-name, and place them in theTrusted-Key-Set. - strip the rightmost simple name off the
Current-directory-name. - repeat from step (a) (testing to see if
current-directory-nameis the same asCommon-point-Name).
c) Searching the "target side" of the tree for a crosslink ๐
- initialization: set
Current-directory-nameto the name supplied as input to this procedure. - retrieve from the naming service all cross certs associated with
Current-directory-name, and filter to only those that meet these criteria:- (i) the current time is within their validity interval.
- (ii) the subject name is equal to
Current-directory-name. - (iii) for some PK/UID/Name triple in the "Search-when-descending" list compiled while ascending the tree, the Public Key verifies the signature on the certificate, and either the UID matches the issuer UID in the certificate, or a UID is missing from the triple and/or the certificate and the Name in the triple matches the issuer name in the certificate.
- (iv) there are no certificates found meeting criteria (ii) and (iii) matching a PK/UID/Name triple in the Search-when-descending list whose subject is a directory lower in the naming hierarchy.
- if any qualifying certs are found, construct PK/UID/Name triples for each of them. these should replace rather than supplement any triples already in the
Trusted-key-setfor that directory. ๐ - if after steps (b) and (c) there are no PK/UID/Name triples corresponding to
Current-directory-nameinTrusted-Key-Set, shortenCurrent-directory-nameby one RDN (pushing it onto theSaved-RDNsstack) and repeat this process untilCurrent-directory-nameis equal toLeast-common-ancestor-nameor there is at least one triple inTrusted-key-setcorresponding toCurrent-directory-name.
d) Descending the tree ๐ข
while the list Saved-RDNs is not Empty, the algorithm moves down the tree. at each step it does the following:
- remove the first RDN from
Saved-RDNsand append it to theCurrent-directory-name. - find all the child directory certificates for the directory whose name is in the
current-directory-name. - filter these to find only those that meet these criteria:
- (i) the current time is within the validity interval.
- (ii) for some PK/UID/Name triple in the
Current-key-setfor the parent directory, the Public Key verifies the signature on the certificate, and either the UID matches the issuer UID of the certificate, or the UID is missing from the triple and/or the certificate and the Name in the triple matches the issuer name in the certificate. - (iii) the issuer name in the certificate is a prefix of the subject name, and the difference between the two names is the final RDN of
Current-directory-name.
- take the key, UID, and name from each remaining certificate and form a new triple corresponding to
Current-directory-nameinTrusted-Key-Set. if this set is empty, then the algorithm exits with theIncomplete-chain-of-trustworthy-CAserror condition. ๐ซ - repeat from step (a), appending a new simple name to
Current-directory-name.
e) Find public keys ๐๏ธ
if there are no triples in the Trusted-Key-Set for the named principal, the algorithm exits with the Target-has-no-keys-w error condition. otherwise, the Public Key and UID are extracted from each pair, duplicates are eliminated, and this set is returned as the Pub_keys. ta-da ๐
3.10.10.2 Allowed Variations - Caching โจ
Okay so some caching can be added without changing what Get_Pub_Keys does at all ๐ง . For example, a crypto-cache could remember which public key verified a signature, then skip the verification if the same key checks the same data structure again. But sometimes a cache just can't be totally invisible, or it's a huge pain to make it so ๐ญ.
For good performance, it's important that certificates not get re-retrieved from the naming service on every single authentication ๐ . That has to be balanced against the need for changes in the naming service to show up in DASS calls on a timely basis. There are two cases to think about: changes that make an authentication which previously would have succeeded now fail, and changes that make one which previously would have failed now succeed. These two cases have different time constraints.
In general, changes that make authentication succeed have to show up pretty quickly, like on the order of minutes ๐โโ๏ธ. Picture this: a user tries an operation, it fails, they track down a system manager, the manager makes the right updates, and then the user retries. It is totally unacceptable for that operation to keep failing for a long stretch just because of stale caches ๐ค.
Changes that make authentication fail have to be reflected reliably within a bounded period of time, and that's a security thing ๐. If a user leaves the company, it must be possible to revoke their ability to authenticate within a relatively short window, like hours.
So a naming service cache holding arbitrarily old information is a no-go ๐ซ. To meet the second constraint, naming service cache entries must be timed out within a reasonable period, unless an implementation verifies that the certificate is still present. (A crypto-cache that lasted longer would be legal. Instead of deleting a name service cache entry, an implementation might just check that the entry is still in the naming service, which avoids repeating the cryptographic "verify" ๐.)
To make sure information cached for even a few hours doesn't block authentication for that whole stretch, it must be possible to bypass caches whenever the result would otherwise be a failure ๐. Since the performance of authentication failures isn't a serious concern, it's fine to expect that before an operation fails, a retry will go to the naming service to check for any new relevant certificates (or, in certain obscure conditions, to see whether any relevant certificates have been deleted).
If a call to Get_Pub_Keys has the Try_Hard bit set to True, this procedure must return results based on the contents of the naming service no more than five minutes old. That's normally done by ignoring the name service caches and going straight to the naming service ๐ฏ. If the Try_Hard bit is False, this procedure may return results based on the naming service any time in the previous few hours. What that means is it may ignore any certificate added in the last few hours, and it may use any certificate deleted in the last few hours ๐. Procedures that call this routine with Try_Hard set to false must be prepared to call it again with Try_Hard True if their operation fails, possibly because of this result.
The exact timer values for "five minutes" and "a few hours" are expected to be implementation constants โฐ.
In the envisioned implementation, the entire "ascending treewalk" gets retrieved, verified, and its digested contents cached when a principal first establishes credentials ๐ณ. A mechanism should be provided to refresh this information periodically for principals whose sessions might be long lived. Still, if a user's ancestor's keys change (which is pretty unlikely), it would probably be acceptable to make the user log out and log back in. That's consistent with how existing security mechanisms already behave ๐ซถ.
The descending treewalk is different. It's expected to be a more conventional cache, where entries are kept in a fixed amount of memory with a "least recently used" replacement policy, plus a watchdog timer that makes sure stale information doesn't stick around forever ๐งน. A call to Get_Pub_Keys with Try_Hard set to false checks that cache for relevant certificates first, and only goes out to the naming service if none are found there. If there are newer certificates in the naming service, they might not be found, and so an authentication might fail ๐ฌ.
When Try_Hard is false, an implementation may assume that certificates not in the cache don't exist, as long as that assumption doesn't cause an authentication to falsely succeed. In that case, it may only make that assumption if the certificates have been verified not to exist within the revocation time (a few hours) ๐ฐ๏ธ.
3.11 DASSlessness Determination Functions ๐ฆ
Okay, so to play nicely with alternative authentication mechanisms, and to stay backward compatible with older (insecure) ones, it's sometimes super important to figure out, in a secure way, which authentication mechanism applies to a particular named principal ๐. For some apps, a local mechanism handles this: either the person creating access control info has to know and specify the mechanism for each principal, or a system admin on the node keeps a database mapping names to mechanisms. Three applications come to mind where scaleability makes that kind of setup not realistic ๐ฉ:
- a) To transparently secure proxy-based applications (like rlogin) in an environment where some hosts have been upgraded to support DASS and some haven't, a node must be willing to accept connections authenticated only by their network addresses. But it only gets to do that if it can be assured that those nodes do not have DASS installed. A resource becomes secure without any administrative action once every node authorized to access it has been upgraded ๐.
In this scenario, the server node must be able to determine whether the client node is DASSless in a secure fashion ๐.
- b) Similarly, in a mixed environment where some servers run DASS and some don't, it may be desirable for clients to authenticate servers if they can. But it would be unacceptable for a client to lose access to a DASSless server once DASS gets installed on the client ๐ฑ. In a situation where server authentication is desirable but not essential, the client would like to find out, in a secure fashion, whether the server can accept DASS authentication.
- c) In a DASS/Kerberos interoperability scenario, a server may decide that Kerberos authentication is "good enough" for principals that don't have DASS credentials, without bringing in trust in online authorities when DASS credentials are available ๐ค. Like case a), we want it to be true that when the last principal with authority to access an object is upgraded to DASS, we automatically stop trusting PasswdEtc servers, and the object owner doesn't have to do anything administratively. For this to work, the authenticator must learn in a secure fashion that the principal is incapable of DASS authentication.
Reliably determining DASSlessness is optional for implementations of DASS and for applications ๐. No other DASS capabilities rely on this one.
The interface to the DASSlessness inquiry function is specified as a call that's independent of all the others ๐. This capability must be exposed to the calling application, so that a server that receives a request with no token can ask whether the named principal should be believed without a token. It might also improve performance and usability in real interfaces if DASSlessness were returned alongside a bad status from the function that creates a token, in cases where the token is aimed at a server that can't process it ๐ก. That way an application could decide whether to make the request without a token (and give up server authentication) or to abort the request ๐โโ๏ธ.
3.11.1 Query DASSlessness ๐
Query_DASSlessness(
--inputs
verifying_credentials Credentials,
principal_name Name,
--outputs
alternate_authentication Set of OIDs)
This function uses the verifying credentials to search for an alternative authentication mechanism certificate for the named principal, or for any CA on the path between the verifying credentials and the named principal ๐บ๏ธ. Such a certificate is identical to a DASS X.509 certificate, except that it lists a different algorithm identifier for the subject's public key than the one DASS expects.
This function works exactly like Get_Pub_Keys, except for these things:
- a) If, in any set of certificates found, no valid DASS certificate is found, but one or more certificates are found that would otherwise be valid except for an invalid subject public key OID, then the OID from that certificate (or those certificates) is returned, and the algorithm terminates ๐.
- b) On the initial execution,
Try_Hard=False. If that first execution fails to retrieve any valid PK/UID pairs and also fails to find any invalid OID certificates, repeat the execution withTry_Hard=True๐. - c) If either execution finds PK/UID pairs, or if neither finds any invalid OID certificates, fail by returning a null set ๐ซ .
4. Certificate and message formats โจ
4.1 ASN.1 encoding ๐งฉ
Some definitions are taken from X.501 and X.509 ๐.
Dass DEFINITIONS ::=
BEGIN
--CCITT Definitions:
joint-iso-ccitt OBJECT IDENTIFIER ::= {2}
ds OBJECT IDENTIFIER ::= {joint-iso-ccitt 5}
algorithm OBJECT IDENTIFIER ::= {ds 8}
encryptionAlgorithm OBJECT IDENTIFIER ::= {algorithm 1}
hashAlgorithm OBJECT IDENTIFIER ::= {algorithm 2}
signatureAlgorithm OBJECT IDENTIFIER ::= {algorithm 3}
rsa OBJECT IDENTIFIER ::= {encryptionAlgorithm 1}
iso OBJECT IDENTIFIER ::= {1}
identified-organization OBJECT IDENTIFIER ::= {iso 3}
ecma OBJECT IDENTIFIER ::= {identified-organization 12}
member-company OBJECT IDENTIFIER ::= {ecma 2}
digital OBJECT IDENTIFIER ::= {member-company 1011}
--1989 OSI Implementors Workshop "Stable" Agreements
oiw OBJECT IDENTIFIER ::= {identified-organization 14}
dssig OBJECT IDENTIFIER ::= {oiw 7}
oiwAlgorithm OBJECT IDENTIFIER ::= {dssig 2}
oiwEncryptionAlgorithm OBJECT IDENTIFIER ::= {oiwAlgorithm 1}
oiwHashAlgorithm OBJECT IDENTIFIER ::= {oiwAlgorithm 2}
oiwSignatureAlgorithm OBJECT IDENTIFIER ::= {oiwAlgorithm 3}
oiwMD2 OBJECT IDENTIFIER ::= {oiwHashAlgorithm 1}
--null parameter
oiwMD2withRSA OBJECT IDENTIFIER ::= {oiwSignatureAlgorithm 1}
--null parameter
--X.501 definitions
Okay bestie, the ASN.1 bit keeps going, so let's pick it up right where it left off ๐
AttributeType ::= OBJECT IDENTIFIER
AttributeValue ::= ANY
AttributeValueAssertion ::= SEQUENCE {AttributeType,AttributeValue}
Name ::= CHOICE { --only one for now
RDNSequence
}
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
DistinguishedName ::= RDNSequence
RelativeDistinguishedName ::= SET OF AttributeValueAssertion
--X.509 definitions (with proposed 1992 extensions presumed)
ENCRYPTED MACRO ::=
BEGIN
TYPE NOTATION ::= type(ToBeEnciphered)
VALUE NOTATION ::= value(VALUE BIT STRING)
END -- of ENCRYPTED
SIGNED MACRO ::=
BEGIN
TYPE NOTATION ::= type (ToBeSigned)
VALUE NOTATION ::= value (VALUE
SEQUENCE{
ToBeSigned,
AlgorithmIdentifier, --of the algorithm used to
--generate the signature
ENCRYPTED OCTET STRING --where the octet string is the
--result of the hashing of the
--value of "ToBeSigned"
}
)
END -- of SIGNED
SIGNATURE MACRO ::=
BEGIN
TYPE NOTATION ::= type (OfSignature)
VALUE NOTATION ::= value (VALUE
SEQUENCE {
AlgorithmIdentifier, --of the algorithm used to compute
ENCRYPTED OCTET STRING -- the signature where the octet
-- string is a function (e.g., a
-- compressed or hashed version)
-- of the value 'OfSignature',
-- which may include the
-- identifier of the algorithm
-- used to compute the signature
}
)
END -- of SIGNATURE
Certificate ::= SIGNED SEQUENCE {
version [0] Version DEFAULT v1988,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
valid Validity,
subject Name,
subjectPublicKey SubjectPublicKeyInfo,
issuerUID [1] IMPLICIT UID OPTIONAL, -- v1992
subjectUID [2] IMPLICIT UID OPTIONAL -- v1992
}
--The Algorithm Identifier for both the signature field
--and in the signature itself is:
-- oiwMD2withRSA (1.3.14.7.2.3.1)
Version ::= INTEGER {v1988(0), v1992(1)}
CertificateSerialNumber ::= INTEGER
Validity ::= SEQUENCE {
NotBefore UTCTime,
NotAfter UTCTime
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameter ANY DEFINED BY algorithm OPTIONAL
}
--The algorithms we support in one context or another are:
--oiwMD2withRSA (1.3.14.7.2.3.1) with parameter NULL
--rsa (2.5.8.1.1) with parameter keysize INTEGER which is
-- the keysize in bits
--decDEA (1.3.12.1001.7.1.2) with optional parameter
-- missing
--decDEAMAC (1.3.12.2.1011.7.3.3) with optional parameter
-- missing
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier, -- rsa (2.5.8.1.1)
subjectPublicKey BIT STRING
-- the "bits" further decode into a DASS public key
}
UID ::= BIT STRING
-- the following definitions are for Digital specified Algorithms
cryptoAlgorithm OBJECT IDENTIFIER ::= {digital 7}
decEncryptionAlgorithm OBJECT IDENTIFIER ::= {cryptoAlgorithm 1}
decHashAlgorithm OBJECT IDENTIFIER ::= {cryptoAlgorithm 2}
decSignatureAlgorithm OBJECT IDENTIFIER ::= {cryptoAlgorithm 3}
decDASSLessness OBJECT IDENTIFIER ::= {cryptoAlgorithm 6}
decMD2withRSA OBJECT IDENTIFIER ::= {decSignatureAlgorithm 1}
decMD4withRSA OBJECT IDENTIFIER ::= {decSignatureAlgorithm 2}
decDEAMAC OBJECT IDENTIFIER ::= {decSignatureAlgorithm 3}
decDEA OBJECT IDENTIFIER ::= {decEncryptionAlgorithm 2}
decMD2 OBJECT IDENTIFIER ::= {decHashAlgorithm 1}
decMD4 OBJECT IDENTIFIER ::= {decHashAlgorithm 2}
ShortPosixTime ::= INTEGER -- number of seconds since base time
LongPosixTime ::= SEQUENCE {
INTEGER, -- number of seconds since base time
INTEGER -- number of nanoseconds since second
}
ShortPosixValidity ::= SEQUENCE {
notBefore ShortPosixTime,
notAfter ShortPosixTime }
-- Note: Annex C of X.509 prescribes the following format for the
-- representation of a public key, but does not give the structure
-- a name.
DASSPublicKey ::= SEQUENCE {
modulus INTEGER,
exponent INTEGER
}
DASSPrivateKey ::= SEQUENCE {
p INTEGER , -- prime p
q [0] IMPLICIT INTEGER OPTIONAL , -- prime q
mod[1] IMPLICIT INTEGER OPTIONAL, -- modulus
exp [2] IMPLICIT INTEGER OPTIONAL, -- public exponent
dp [3] IMPLICIT INTEGER OPTIONAL , -- exponent mod p
dq [4] IMPLICIT INTEGER OPTIONAL , -- exponent mod q
cr [5] IMPLICIT INTEGER OPTIONAL , -- Chinese
--remainder coefficient
uid[6] IMPLICIT UID OPTIONAL,
more[7] IMPLICIT BIT STRING OPTIONAL --Reserved for
--future use
}
LocalUserName ::= OCTET STRING
ChannelId ::= OCTET STRING
VersionNumber ::= OCTET STRING (SIZE(3))
-- first octet is major version
-- second octet is minor version
-- third octet is ECO rev.
versionZero VersionNumber ::= '000000'H
Authenticator ::= SIGNED SEQUENCE {
type BIT STRING,
-- first bit `delegation required'
-- second bit `Mutual Authentication Requested'
whenSigned LongPosixTime ,
channelId [3] IMPLICIT ChannelId OPTIONAL
-- channel bindings are included when doing the
-- signature, but excluded when transmitting the
-- Authenticator
}
-- uses decDEAMAC (1.3.12.2.1011.7.3.3)
EncryptedKey ::= SEQUENCE {
algorithm AlgorithmIdentifier,
-- uses rsa (2.5.8.1.1)
encryptedAuthKey BIT STRING
-- as defined in section 4.4.5
}
SignatureOnEncryptedKey ::= SIGNATURE EncryptedKey
-- uses oiwMD2withRSA (1.3.14.7.2.3.1)
-- Signature bits computed over EncryptedKey structure
LoginTicket ::= SIGNED SEQUENCE {
version [0] IMPLICIT VersionNumber DEFAULT versionZero,
validity ShortPosixValidity ,
subjectUID UID ,
delegatingPublicKey SubjectPublicKeyInfo
}
-- uses oiwMD2withRSA (1.3.14.7.2.3.1)
Delegator ::= SEQUENCE {
algorithm AlgorithmIdentifier
-- decDEA encryption (1.3.12.1001.7.1.2)
encryptedPrivKey ENCRYPTED DASSPrivateKey,
-- (only p is included)
}
UserClaimant ::= SEQUENCE {
userTicket [0] IMPLICIT LoginTicket,
evidence CHOICE {
delegator [1] IMPLICIT Delegator ,
-- encrypted delegation private key
-- under DES authenticating key
-- present if delegating
sharedKeyTicketSignature [2]
IMPLICIT SignatureOnEncryptedKey
-- present if not delegating
} ,
userName [3] IMPLICIT Name OPTIONAL
-- name of user principal
}
EncryptedKeyandUserName ::= SEQUENCE {
encryptedKey EncryptedKey ,
username LocalUserName
}
SignatureOnEncryptedKeyandUserName ::=
SIGNATURE EncryptedKeyandUserName
-- uses oiwMD2withRSA (1.3.14.7.2.3.1)
-- Signature bits computed over
-- EncryptedKeyandUserName structure
-- using node private key
}
NodeClaimant ::= SEQUENCE {
nodeTicket Signature[0] IMPLICIT
SignatureOnEncryptedKeyandUserName,
nodeName [1] IMPLICIT Name OPTIONAL,
username [2] IMPLICIT LocalUserName OPTIONAL
}
AuthenticationToken ::= SEQUENCE {
version [0] IMPLICIT VersionNumber DEFAULT versionZero,
authenticator [1] IMPLICIT Authenticator ,
encryptedKey [2] IMPLICIT EncryptedKey OPTIONAL ,
-- required if initiating token
userclaimant [3] IMPLICIT UserClaimant OPTIONAL ,
-- missing if only doing node authentication
-- required if not doing node authentication
nodeclaimant [4] IMPLICIT NodeClaimant OPTIONAL
-- missing if only doing principal authentication
-- required if not doing principal authentication
}
MutualAuthenticationToken ::= CHOICE {
v1Response [0] IMPLICIT OCTET STRING (SIZE(6))
-- Constructed as follows: A single DES block
-- of eight octets is constructed from the two
-- integers in the timestamp. First four bytes
-- are the high order integer encoded MSB
-- first; Last four bytes are the low order
-- integer encoded MSB first. The block is
-- encrypted using the shared DES key, and
-- the first six bytes are the OCTET STRING.
-- With the [0] type and 6-byte length, the
-- MutualAuthenticationToken has a fixed
-- length of eight bytes.
}
END
4.2 Encoding Rules ๐
Okay so, whenever a structure is to be signed, it MUST always be constructed the same way ๐ This is super important where a signed structure has to be rebuilt by the recipient before the signature gets verified, no cap. The rules listed below are taken from X.509 โจ
- the definite form of length encoding SHALL be used, encoded in the minimum number of octets;
- for string types, the constructed form of encoding SHALL NOT be used;
- if the value of a type is its default value, it SHALL be absent;
- the components of a Set type SHALL be encoded in ascending order of their tag value;
- the components of a Set-of type SHALL be encoded in ascending order of their octet value;
- if the value of a Boolean type is true, the encoding SHALL have its contents octet set to
FF'16; - if there are any, unused bits in the final octet of the encoding of a BitString value SHALL be set to zero;
- the encoding of a Real type SHALL be such that bases 8, 10 and 16 SHALL NOT be used, and the binary scaling factor SHALL be zero.
4.3 Version numbers and forward compatibility ๐
okay so the LoginTicket and AuthenticationToken structures both carry a three octet version identifier ๐ฆ. It's there to make transitions to future revisions of this architecture way easier โจ. The default value, which implementations SHOULD always supply for this version of the architecture, is 0.0.0 (three zero octets).
The first octet is the major version ๐. An implementation of this version of the architecture SHOULD refuse to process data structures where it is other than zero, because changing it indicates that the interpretation of some subsidiary data structure has changed. The second octet is the minor version. An implementation of this version of the architecture SHOULD ignore the value of this octet. Some future version of the architecture MAY set a value other than zero and MAY specify some different processing of the remainder of the structure based on that different value. Such a change would be backward compatible and interoperable ๐ธ.
The third octet is the ECO revision. No implementation SHOULD make any processing decisions based on the value of that octet. It MAY be logged, however, to help in debugging interoperability problems ๐.
In the CDC protocol, there is also a three octet version numbering scheme, where versions 1.0.0 and 2.0.0 have been defined. Implementations SHOULD follow the same rules above and reject major version numbers greater than 2 ๐
โโ๏ธ.
ASN.1 is inherently extensible because it allows new fields to be added "onto the end" of existing data structures in an unambiguous way ๐ . Implementations of DASS are encouraged to ignore any such additional fields in order to enhance backwards compatibility with future versions of the architecture. Unfortunately, commonly available ASN.1 compilers lack this capability, so this behavior cannot reasonably be required and may limit options for future extensions ๐ญ.
4.4 Cryptographic Encoding ๐
Some of the substructures listed in the previous sections are specified as ENCRYPTED OCTET STRINGs containing encrypted information. DASS uses the DES, RSA, and MD2 cryptosystems. Each of those cryptosystems specifies a function from octet string into another in the presence of a key (except MD2, which is keyless). This section describes how to form the octet strings on which the DES and RSA operations are performed ๐ง.
4.4.1 Algorithm Independence vs. Key Parity ๐๏ธ
All of the defined encodings for DASS for secret key encryption are based on DES. It is intended, however, that other cryptosystems could be substituted without any other changes for formats or algorithms. The required "form factor" for such a cryptosystem is that it have a 64 bit key and operate on 64 bit blocks (this appears to be a common form factor for a cryptosystem). For this reason, DES keys are in all places treated as though they were 64 bits long rather than 56. Only in the operation of the algorithm itself are eight bits of the key dropped and key parity bits substituted. Choosing a key always involves picking a 64 bit random number ๐ฒ.
4.4.2 Password Hashing ๐ง
Encrypted credentials are encrypted using DES as described in the next section. The key for that encryption is derived from the user's password and name by the following algorithm:
a) Put the rightmost RDN of the user's name in canonical form according to BER and the X.509 encoding rules. For any string types that are case insensitive, map to upper case, and where matching is independent of number of spaces collapse all multiple spaces to a single space and delete leading and trailing spaces.
Note: the RDN is used to add "salt" to the hash calculation so that someone can't precompute the hash of all the words in a dictionary and then apply them against all names. Deriving the salt from the last RDN of the name is a compromise. If it were derived from the whole name, all encrypted keys would be obsoleted when a branch of the namespace was renamed. If it were independent of name, interaction with a login agent would take two extra messages to retrieve the salt. With this scheme, encrypted keys are obsoleted by a change in the last RDN and if a final RDN is common to a large number of users, dictionary attacks against them are easier; but the common case works as desired ๐ค.
b) Compute TEMP as the MD2 message digest of the concatenation of the password and the RDN computed above.
c) Repeat the following 40 times: Use the first 64 bits of TEMP as a DES key to encrypt the second 64 bits; XOR the result with the first 64 bits of TEMP; and compute a new TEMP as MD2 of the 128 bit result. ๐
d) Use the final 64 bits of the result (called hash1) as the key to decrypt the encrypted credentials. Use the first 64 bits (called hash2) as the proof of knowledge of the password for presentation to a login agent (if any) ๐.
4.4.3 Digital DEA encryption ๐
DES encryption is used in the following places:
- In the encryption of the encrypted credentials structure
- To encrypt the delegator in authentication tokens
- To encrypt the time in the mutual authenticator
In the first two cases, a varying length block of information coded in ASN.1 is encrypted. This is done by dividing the block of information into 8 octet blocks, padding the last block with zero bytes if necessary, and encrypting the result using the CBC mode of DES. A zero IV is used.
In the third case, a fixed length (8 byte) quantity (a timestamp) is encrypted. The timestamp is mapped to a byte string using "big endian" order and the block is encrypted using the ECB mode of DES โฐ.
4.4.4 Digital MAC Signing โ๏ธ
DES signing is used in the Authenticator. Here, the signature is computed over an ASN.1 structure. The signature is the CBC residue of the structure padded to a multiple of eight bytes with zeros. The CBC is computed with an IV of zero.
4.4.5 RSA Encryption ๐
RSA encryption is used in the Encrypted Shared Key. RSA encryption is best thought of as operating on blocks which are integers rather than octet strings and the results are also integers. Because an RSA encryption permutes the integers between zero and (modulus-1), it is generally thought of as acting on a block of size (keysizeinbits-1) and producing a block of size (keysizeinbits) where keysizeinbits is the smallest number of bits in which the modulus can be represented.
DASS only supports key sizes which are a multiple of eight bits (This restriction is only required to support interoperation with certain existing implementations. If the key size is not a multiple of eight bits, the high order byte may not be able to hold values as large as the mandated '64'. This is not a problem so long as the two high order bytes together are non-zero, but certain early implementations check for the value '64' and will not interoperate with implementations that use some other value.) ๐.
The encrypted shared key structure is laid out as follows:
- The DES key to be shared is placed in the last eight bytes
- The POSIX format creation time encoded in four bytes using big endian byte order is placed in the next four (from the end) bytes
- The POSIX format expiration time encoded in four bytes using big endian byte order is placed in the next four (from the end) bytes
- Four zero bytes are placed in the next four (from the end) bytes
- The first byte contains the constant '64' (decimal)
- All remaining bytes are filled with random bytes (the security of the system does not depend on the cryptographic randomness of these bytes, but they SHOULD NOT be a frequently repeating or predictable value. Repeating the DES key from the last bytes would be good)
The RSA algorithm is applied to the integer formed by treating the bytes above as an integer in big endian order and the resulting integer is converted to a BIT STRING by laying out the integer in 'big endian' order.
On decryption, the process is reversed; the decryptor SHOULD verify the four explicitly zero bytes but SHOULD NOT verify the contents of the high order byte or the random bytes ๐ง.
4.4.6 oiwMD2withRSA Signatures ๐๏ธ
RSA-MD2 signatures are used on certificates, login tickets, shared key tickets, and node tickets. In all cases, a signature is computed on an ASN.1 encoded string using an RSA private key. This is done as follows:
- The MD2 algorithm is applied to the ASN.1 encoded string to produce a 128 bit message digest
- The message digest is placed in the low order bytes of the RSA block (big endian)
- The next two lowest order bytes are the ASN.1 'T' and 'L' for an OCTET STRING.
- The remainder of the RSA block is filled with zeros
- The RSA operation is performed, and the resulting integer is converted to an octet string by laying out the bytes in big endian order.
On verification, a value like the above or one where the message digest is present but the 'T' and 'L' are missing (zero) SHOULD be accepted for backwards compatibility with an earlier definition of this crypto algorithm ๐ซถ.
4.4.7 decMD2withRSA Signatures ๐
This algorithm is the same as the oiwMD2withRSA algorithm as defined above. We allocated an algorithm object identifier from the Digital space in case the definition of that OID should change. It will not be used unless the meaning of oiwMD2withRSA becomes unstable ๐ฆ.
Annex A
Typical Usage โจ
This annex describes one way a system could use DASS services (as described in section 3) to provide security services. While this example provided motivation for some of the properties of DASS, it is not intended to represent the only way that DASS may be used. This goes through the steps that would be needed to install DASS "from scratch" ๐ง.
A.1 Creating a CA ๐๏ธ
okay so a CA gets created by initializing its state ๐ each CA can sign certificates that will live in some directory in the name service. but before anyone in a wider context believes those certs (beyond the sub-tree of the name space headed by that directory), the CA has to be certified by a CA for the parent directory. the procedure below does exactly that โจ for the most secure setup, the CA should run on an off-line system and talk to the rest of the network by swapping files through something simple and specialized, like an RS232 line or a floppy disk ๐พ it's assumed that access to the CA is controlled and that the CA will accept instructions from an operator ๐
- Call
Install_CAto create the CA State. This state is saved within the CA system and is never disclosed. ๐คซ - If this is the first CA in the namespace and the CA is intended to certify only members of a single directory, we are done. Otherwise, the new CA must be linked into the CA hierarchy by cross-certifying the parent and children of this CA. There is no requirement that CA hierarchies be created from the root down, but to simplify exposition, only this case will be described. The newly created CA must learn its name, its UID, the UID of its parent directory, and the public key of the parent directory CA by some out of band reliable means. Most likely, this would be done by looking up the information in the naming service and asking the CA operator to verify it. The CA then forms this information into a parent certificate and signs it using the
Create_certificatefunction. It communicates the certificate to the network and posts it in the naming service. ๐ - This name, UID, and public key of the new CA are taken to the CA of the parent directory, which verifies it (again by some unspecified out-of-band mechanism) and calls
Create_Certificateto create a child certificate using its own Name and UID in the issuer fields. This certificate is also placed in the naming service. ๐
A CA can sign certificates for more than one directory. In this case it is possible that a single CA will take the role of both CAs in the example above. The above procedure can be simplified in this case, as no interchange of information is required. ๐ง โจ
A.2 Creating a User Principal ๐ค
okay bestie, a system manager can make a new user principal by invoking the Create_principal function, supplying the principal's name, UID, and the public key/UID of the parent CA. the public key and UID must be obtained in a reliable out of band manner ๐ this is probably done by having that info "wired into" the utility that creates new principals. at account creation time, the system manager must supply what will become the user's password. this might be done by having the user present and directly enter a password, or by having the password picked by some random generator ๐ฒ
the trusted authority certificate and corresponding user public key generated by the Create_principal function get sent to the CA, which verifies its contents (again by an out-of-band mechanism) and signs a corresponding certificate. the encrypted credentials, CA signed certificate, and trusted authority certificates are all placed in the naming service. the process by which the password is made known to the user must be protected by some out-of-band mechanism ๐
in some cases the principal may wish to generate its own key, and not use the Encrypted_Credentials. (e.g., if the Principal is represented by a Smart Card ๐ณ). this may be done using a procedure similar to the one for creating a new CA. ๐ฆ
A.3 Creating a Server Principal ๐ฅ๏ธ
a server also has a public/private key pair! conceptually, the same procedure used to create a user principal can be used to create a server. in practice, the most important difference is likely to be how the password is protected when it's installed on a server compared to when it's given to a user ๐
a server may wish to retrieve (and store) its Encrypted Credentials directly and never have them placed in the naming service. in this case some other mechanism can be used (e.g., passing the floppy disk containing the encrypted credentials to the server ๐พ). this would require a variant of the Initialize_Server routine which does not fetch the Encrypted Credentials from the naming service. ๐ซถ
A.4 Booting a Server Principal ๐
when the server first boots it needs its name (unreliably) and password (reliably). it then calls Initialize_Server to get its credentials and trusted authority certificates (which it will later need in order to authenticate users) โจ these credentials never time out, and they're expected to be saved for a long time. in particular, the associated Incoming Timestamp List must be preserved while there are any timestamps on it. it's desirable to preserve the Cached Incoming Contexts as long as there are any contexts likely to be reused. ๐๏ธ
if a server wants to initiate associations on its own behalf, then it must call Generate_Server_Ticket. it must repeat this at intervals if the expiration period expires. โฐ
a node that wishes to do node authentication (or which acts as a server under its own name) must be created as a server. ๐จ
A.5 A user logs on to the network ๐
the system that the user logs onto finds the user's name and password. then it calls Network_Login to get credentials for the user. these credentials are saved until the user wants to make a network connection. the credentials have a time limit, so the user will have to get new credentials in order to make connections after the time limit runs out โ the credentials are then checked by calling Verify_Principal_Name, in order to check that the key specified in the encrypted credentials has been certified by the CA. โ
if the system does source node authentication, it will call Combine_credentials once the local username has been found. (this can either be found by looking the principal's global name up in a file, or the user can be asked to give the local name directly. alternatively, the user can be asked to give his local username, which the system looks up to find the global name.) ๐
A.6 An Rlogin (TCP/IP) connection is made ๐
when the user calls a modified version of the rlogin utility, it calls Create_token in order to create the Initial Authentication Token, which gets passed to the other system as part of the rlogin protocol ๐ซ the rlogind utility at the destination node calls Accept_token to verify it. then it looks up in a local rhosts-like database to determine whether this global user is allowed access to the requested destination account. it calls Verify_principal_name and/or Verify_node_name to confirm the identity of the requester. if access is allowed, the connection is accepted and the Mutual Authentication Token is returned in the response message. ๐
the source receives the returned Mutual Authentication Token and uses it to confirm it's communicating with the correct destination node. ๐ซถ
rlogind then calls Combine_credentials to combine its node/account information with the global user identification in the received credentials, just in case the user accesses any network resources from the destination system. ๐ง
A.7 A Transport-Independent Connection ๐ฆ
as an alternative to the description in A.6, an application that wants to be portable between different underlying transports may call create_token to create an authentication token, which it then sends to its peer. the peer can then call accept_token and verify_principal_name and learn the identity of the requester. ๐
Annex B โจ Support of the GSSAPI
okay this part is giving big "one API to rule them all" energy ๐ in order to support applications that need to be portable across a variety of underlying security mechanisms, a "Generic Security Service API" (or GSSAPI) was designed, which gives access to a common core of security services expected to be provided by several mechanisms. the GSSAPI was designed with DASS, Kerberos V4, and Kerberos V5 in mind, and could be written as a front end to any or all of those systems. it's hoped that it could serve as an interface to other security systems as well ๐
application portability requires that the security services supported be comparable. applications using the GSSAPI won't be able to access all of the features of the underlying security mechanisms ๐ญ for example, the GSSAPI does not allow access to the "node authentication" features of DASS. to the extent the underlying security mechanisms do not support all the features of GSSAPI, applications using those features will not be portable to those security mechanisms. for example, Kerberos V4 does not support delegation, so applications using that feature of the GSSAPI won't be portable to Kerberos V4. ๐
this annex explains how the GSSAPI can be implemented using the primitive services provided by DASS. ๐ฅ
B.1 Summary of GSSAPI โจ
okay so the latest draft of the GSSAPI spec is out as an internet draft ๐. it's still evolving, so think of this as a quick hype summary and not the final word ๐. we're only covering the bits whose implementation would be DASS specific, bestie ๐ซถ
okay so GSSAPI gives us four classes of functions: Credential Management, Context-Level Calls, Per-message calls, and Support Calls ๐ . it also has two types of objects, Credentials and Contexts, and two kinds of data structures that get sent around as opaque byte strings, Tokens and Messages ๐. Credentials hold the keys and support info used to make tokens. Contexts hold the keys and support info used to sign and encrypt messages ๐.
the Credential Management functions are kinda "incomplete" ๐ญ. like, you literally could not build a useful security setup with just GSSAPI. functions that make credentials from passwords or smart cards are needed, but GSSAPI doesn't provide them ๐ โโ๏ธ. the vibe is that those get called by mechanism-specific functions at user login, or through some separate utility, instead of inside apps that are supposed to be portable ๐ง. the Credential Management functions that portable apps can actually use are:
GSS_Acquire_cred: get a handle to an existing credential structure, based on a name or the process default ๐.GSS_Release_cred: release credentials after use ๐งน.
the Context-Level Calls use credentials to set up contexts ๐ฆ. contexts are kinda like connections: they're created in pairs and usually used at the two ends of a connection to process messages tied to that connection ๐. the Context-Level Calls we care about are:
GSS_Init_sec_context: given credentials and the name of a destination, create a new context and a token that lets the destination create a matching context ๐จ.GSS_Accept_sec_context: given credentials and an incoming token, create a context that matches the one at the initiating end, and provide info identifying the initiator ๐๏ธ.GSS_Delete_sec_context: delete a context after use ๐๏ธ.
the Per-Message Calls use contexts to sign, verify, encrypt, and decrypt messages between the holders of matching contexts ๐ฅ. they are:
GSS_Sign: given a context and a message, produce a string of bytes that is a signature on the provided message โ๏ธ.GSS_Verify: given a context, a message, and the bytes returned byGSS_Sign, verify that the message is authentic (unaltered since it was signed by the matching context) ๐.GSS_Seal: given a context and a message, produce a string of bytes that includes the message and a signature. the message may optionally be encrypted ๐.GSS_Unseal: given a context and the string of bytes fromGSS_Seal, return the original message and a status showing whether it's authentic ๐.
the Support Calls are the boring-but-useful squad ๐. they provide utilities like translating names and status codes into printable strings ๐จ๏ธ.
B.2 Implementation of GSSAPI over DASS ๐ฆ
B.2.1 Data Structures ๐งฉ
the GSSAPI objects and data structures don't map neatly onto the objects and data structures of the DASS architecture, bestie ๐ญ.
this section describes how those data structures can be implemented using the DASS data structures and primitives ๐ธ.
credential handles correspond to the credentials structures in DASS. the portable API assumes that the credential structures themselves are kept away from applications, and handles are passed to and from the various subroutines instead ๐.
context initialization tokens correspond to the tokens of DASS. GSSAPI prescribes a particular ASN.1 encoded form for tokens, and that form includes a mechanism-specific bit string inside it. an implementation of GSSAPI should enclose the DASS token within the GSSAPI "wrapper" ๐.
context handles have no matching structure in DASS. the Create_token and Accept_token calls of DASS return a shared key and an instance identifier. an implementation of the GSSAPI must take those values, along with some other status info, and package them as a "context" opaque structure ๐ฆ. these data structures must be allocated and freed with the appropriate calls โจ.
per-message tokens and sealed messages have no matching data structure within DASS. to fully support GSSAPI functionality, DASS must be extended to include this functionality ๐ ๏ธ. these data structures are created by cryptographic routines, given the keys and status info in context structures and the messages passed to them. they're not properly part of the DASS architecture, but the formats of these data structures are included in section C.3 ๐.
B.2.2 Procedures ๐ฌ
this section explains how the functions of the GSSAPI can be provided in terms of the Services Provided by DASS. not all of the DASS features are accessible through the GSSAPI ๐.
B.2.2.1 GSS_Acquire_cred โจ
the GSSAPI doesn't provide a mechanism for logging in users or establishing server credentials ๐
. it assumes that some system-specific mechanism already created those credentials, and that applications need some way to get at them. a model implementation might save all credentials in a node-global pool, indexed by some sort of credential name ๐. the credentials in the pool would be access controlled by some local policy, which is not the concern of portable applications ๐ก๏ธ. those applications simply call GSS_Acquire_cred, and if they pass the access control check, they get a handle to the credentials that can be used in subsequent calls ๐๏ธ.
B.2.2.2 GSS_Release_cred ๐งน
this call corresponds to the delete_credentials call of DASS ๐๏ธ.
B.2.2.3 GSS_Init_sec_context ๐
in the course of a normal mutual authentication, this routine gets called twice ๐. the procedure can tell whether this is the first or second call by checking whether the input_context_handle is zero (it will be zero on the first call) ๐ต๏ธ. on the first call, it uses the DASS Create_token service to create a token, and it also allocates and fills in a "context" structure ๐ง. that structure holds the key, instance identifier, and mutual authentication token returned by Create_token, and it will also hold the flags that were passed into the Init_sec_context call ๐ฉ. the token returned by Init_sec_context is the DASS token inside the GSSAPI token "wrapper" ๐. the DASS token includes the optional principal name ๐ท๏ธ.
if mutual authentication isn't requested in the GSSAPI call, the mutual authentication token returned by DASS gets ignored, and the initial call returns a COMPLETE status โ
. if mutual authentication is requested, the mutual authentication token gets stored in the context information and a CONTINUE_NEEDED status is returned โณ.
on the second call to GSS_Init_sec_context (with input_context_handle non-zero), the returned token gets compared to the one in the context information using the Compare_mutual_token procedure. a COMPLETE status is returned if they match ๐ฏ.
B.2.2.4 GSS_Accept_sec_context ๐ฅ
this routine in GSSAPI accepts an incoming token and creates a context ๐ง. it combines the effects of a series of DASS functions. it could be implemented as follows:
- remove the GSSAPI "wrapper" from the incoming token, then pass the rest and the credentials to
Accept_token.Accept_tokenproduces a mutual authentication token and a new credentials structure. if delegation was requested, the new credentials structure will be an output ofGSS_Accept_sec_context. in any case, it will be used in the subsequent steps of this procedure ๐ช. - use the DASS
Get_principal_namefunction to extract the principal name from the credentials produced byAccept_token. this name is one of the outputs ofGSS_Accept_sec_context๐ท๏ธ. - apply the DASS
Verify_principal_nameto the new credentials and the retrieved name, to authenticate the token as having come from the named principal ๐ต๏ธโโ๏ธ. - create and populate a context structure with the key and timestamp returned by
Accept_token, and a status ofCOMPLETE. return a handle to that context ๐. - if delegation was requested, return the new credentials from
GSS_Accept_sec_context. otherwise, callDelete_credentials๐งน. - if mutual authentication was requested, wrap the mutual authentication token from
Accept_tokenin a GSSAPI "wrapper" and return it. otherwise, return a null string ๐.
B.2.2.5 GSS_Delete_sec_context ๐๏ธ
this routine simply deletes the context state. no calls to DASS are required ๐ .
B.2.2.6 GSS_Sign โ๏ธ
this routine takes a context handle and a message as input. it creates a per_msg_token by computing a digital signature on the message, using the key and timestamp in the context block ๐. no DASS services are required. if additional cryptographic services were requested (replay detection or sequencing), a timestamp or sequence number must be prepended to the message and sent with the signature. the syntax for this message is listed in section C.3 ๐.
B.2.2.7 GSS_Verify ๐
this routine repeats the calculation of the sign routine and verifies the signature provided ๐งฎ. if replay detection or sequencing services are provided, the context must maintain, as part of its state information, the sequence numbers or timestamps of messages already received, and this one must be checked for acceptability ๐ฐ๏ธ.
B.2.2.8 GSS_Seal ๐
this routine performs the same functions as Sign, but it also optionally encrypts the message for privacy using the shared key. then it encapsulates the whole thing in a GSSAPI-specified ASN.1 wrapper ๐.
B.2.2.9 GSS_Unseal ๐
this routine performs the same functions as GSS_Verify, but it also parses the data structure including the signature and message, and decrypts the message if necessary ๐.
B.3 Syntax โจ
okay so the GSSAPI spec basically says "hey, please use this ASN.1 encoding" for the tokens and messages it generates ๐ here's the whole thing, bestie:
--optional top-level token definitions to frame
-- different mechanisms
GSSAPI DEFINITIONS ::=
BEGIN
MechType ::= OBJECT IDENTIFIER
-- data structure definitions
ContextToken ::=
-- option indication (delegation, etc.) indicated
-- within mechanism-specific token
[APPLICATION 0] IMPLICIT SEQUENCE {
thisMech MechType,
responseExpected BOOLEAN,
innerContextToken ANY DEFINED BY MechType
-- contents mechanism-specific
}
PerMsgToken ::=
-- as emitted by GSS_Sign and processed by
-- GSS_Verify
[APPLICATION 1] IMPLICIT SEQUENCE {
thisMech MechType,
innerMsgToken ANY DEFINED BY MechType
-- contents mechanism-specific
}
SealedMessage ::=
-- as emitted by GSS_Seal and processed by
-- GSS_Unseal
[APPLICATION 2] IMPLICIT SEQUENCE {
sealingToken PERMSGTOKEN,
confFlag BOOLEAN,
userData OCTET STRING
-- encrypted if confFlag TRUE
}
The DASS MechType OID is 1.3.12.2.1011.7.5, so that's the one that shows up in thisMech ๐
The innerContextToken is either a DASS token or a mutual authentication token ๐ฆ
The innerMsgToken is a null string when the message is encrypted and the token rides along inside a SealedMessage ๐ซถ
Otherwise, the innerMsgToken is an eight octet sequence, and it's the CBC residue computed with a key and a string of bytes that get built like this:
- Pad the application's message with 1-8 bytes of pad so the total length is a multiple of 8 octets. Each pad byte is set to the number of pad bytes, so it's literally self-describing ๐ญ
- Compute the key by taking the association's timestamp (two four byte integers, big endian, most significant one first), flipping the high order bit so it can't alias with mutual authenticators, and encrypting that block in ECB mode with the association's shared key. ๐ฅ
Now the userData field of a SealedMessage is exactly the application's byte string if confFlag=FALSE. If confFlag is TRUE, though, the message gets encrypted, and here's the glow-up process:
- Pad the application's message with 1-8 bytes of pad so the length is 4 (mod 8). Each pad byte again equals the number of pad bytes ๐
- Append a four byte CRC32, computed over the message + pad.
- Compute a key the same way as above: association timestamp, high order bit complemented to avoid aliasing with mutual authenticators, ECB encryption under the shared key. ๐ธ
- Encrypt message + pad + CRC32 using CBC and the key from the previous step. โจ
Now, why any of this? Here's the logic, iykyk ๐
- The shared key of an association can get reused by lots of associations between the same pair of principals, so the association timestamp has to get bound into the messages somehow. Otherwise, messages from an old association could get replayed into a new sequence ๐ The association key trick handles that, and it has a side benefit too. An implementation may wish to keep long term keys away from applications for confinement reasons, but still put the association's encryption in process context for performance. Defining an association key makes that combo possible ๐ง
- The association specific key isn't specified as the output of Create_token and Accept_token because the DCE RPC security implementation needs a series of associations between two principals to always share the same key. The spec authors didn't want to support a different interface in that application, so, yeah, that's the reason ๐
- The CRC32 after the pad is a cheap integrity check whenever data is encrypted ๐ฅ
- Padding works differently for encrypted and signed messages, so there's no threat of sending the same message both encrypted and unencrypted and then using the last block of the encrypted one as a signature on the unencrypted one. Iconic design, honestly ๐
Annex C
Imported ASN.1 definitions
This annex is a bunch of extracts from the ASN.1 description of the X.509 and X.500 definitions that the DASS ASN.1 definitions reference ๐
CCITT DEFINITIONS ::=
BEGIN joint-iso-ccitt OBJECT IDENTIFIER ::= {2} ds
OBJECT IDENTIFIER ::= {joint-iso-ccitt 5} algorithm
OBJECT IDENTIFIER ::= {ds 8}
iso OBJECT IDENTIFIER ::= {1} identified-
organization OBJECT IDENTIFIER ::= {iso 3} ecma OBJECT
IDENTIFIER ::= {identified-organization 12} digital
OBJECT IDENTIFIER ::= { ecma 1011 }
-- X.501 definitions
AttributeType ::= OBJECT IDENTIFIER AttributeValue ::= ANY
-- useful ones are
-- OCTET STRING ,
-- PrintableString ,
-- NumericString ,
-- T61String ,
-- VisibleString
AttributeValueAssertion ::= SEQUENCE {AttributeType,
AttributeValue}
Name ::= CHOICE {-- only one possibility for now --
RDNSequence}
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
DistinguishedName ::= RDNSequence
RelativeDistinguishedName ::= SET OF AttributeValueAssertion
-- X.509 definitions
Certificate ::= SIGNED SEQUENCE {
version [0] Version DEFAULT 1988 ,
serialNumber SerialNumber ,
signature AlgorithmIdentifier ,
issuer Name,
valid Validity,
subject Name,
subjectPublicKey SubjectPublicKeyInfo }
Version ::= INTEGER { 1988(0)} SerialNumber ::= INTEGER Validity
::= SEQUENCE{
notBefore UTCTime,
notAfter UTCTime}
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier ,
subjectPublicKey BIT STRING
}
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER ,
parameters ANY DEFINED BY algorithm OPTIONAL}
ALGORITHM MACRO BEGIN TYPE NOTATION ::= "PARAMETER" type VALUE
NOTATION ::= value (VALUE OBJECT IDENTIFIER) END -- of ALGORITHM
ENCRYPTED MACRO BEGIN TYPE NOTATION ::=type(ToBeEnciphered) VALUE
NOTATION ::= value(VALUE BIT STRING)
-- the value of the bit string is generated by
-- taking the octets which form the complete
-- encoding (using the ASN.1 Basic Encoding Rules)
-- of the value of the ToBeEnciphered type and
-- applying an encipherment procedure to those octets-- END
SIGNED MACRO ::= BEGIN TYPE NOTATION ::= type (ToBeSigned) VALUE
NOTATION ::= value(VALUE SEQUENCE{
ToBeSigned,
AlgorithIdentifier, -- of the algorithm used to generate
-- the signature
ENCRYPTED OCTET STRING
-- where the octet string is the result
-- of the hashing of the value of "ToBeSigned" END -- of
SIGNED
SIGNATURE MACRO ::= BEGIN TYPE NOTATION ::= type(OfSignature) VALUE
NOTATION ::= value(VALUE
SEQUENCE{
AlgorithmIdentifier,
-- of the algorithm used to compute the signature
ENCRYPTED OCTET STRING
-- where the octet string is a function (e.g., a
-- compressed or hashed version) of the value
-- "OfSignature", which may include the identifier
-- of the algorithm used to compute
-- the signature--}
) END -- of SIGNATURE
-- X.509 Annex H (not part of the standard)
encryptionAlgorithm OBJECT IDENTIFIER ::= {algorithm 1} rsa ALGORITHM
PARAMETER KeySize
::= {encryptionAlgorithm 1}
KeySize ::= INTEGER
END
Glossary โจ
okay bestie, this is the vocab list you need before anything else makes sense ๐
authentication ๐ is the process of working out who the other party is (usually their name) in some communication exchange.
authentication context is cached info used during one particular instance of authentication. It includes a shared symmetric (DES) key plus the components of the authentication token that got conveyed when the context was set up. ๐ง
authentication token is info sent during a strong authentication exchange that can be used to authenticate whoever sent it. It can, but doesn't have to, include the claimant identity and ticket, plus signed and encrypted secret key exchange messages that convey a secret key for future cryptographic operations. An authentication token names a particular protocol data structure component. ๐
authorization is the process of working out the rights that go with a particular principal. ๐
certificate is the public key of a particular principal, together with some other info about the names of the principal and the certifying authority. It's made unforgeable by encipherment with the private key of the certification authority that issued it. no cap, that's a lot of moving parts ๐
certification authority is an authority trusted by one or more principals to create and assign certificates. ๐ธ
claimant is the party that initiates the authentication process. In the DASS architecture, claimants possess credentials that include their identity, their authenticating private key, and a ticket certifying their authenticating public key. ๐ฆ
credentials are the info "state" principals need in order to authenticate. Credentials may contain info used to initiate the authentication process (claimant information), info used to respond to an authentication request (verifier information), and cached info useful for improving performance. โจ
cryptographic checksum is info derived by performing a cryptographic transformation on the data unit. The receiver can use it to verify the authenticity of data passed in cleartext. ๐
decipher means to reverse the effects of encipherment, so a message becomes comprehensible again by using a cryptographic key. ๐๏ธ
delegation is the granting of temporary credentials that let a process act on behalf of a principal. ๐
delegation key is a short term public/private key pair a claimant uses to act on behalf of a principal for a bounded period. The delegation public key appears in the ticket, whereas the delegation private key is used to sign secret key exchange messages. it's giving secure handoff ๐ค
DES stands for Data Encryption Standard: a symmetric (secret key) encryption algorithm used by DASS. An alternate encryption algorithm could be substituted with little or no disruption to the architecture. ๐
DES key is a 56-bit secret quantity used as a parameter to the DES encryption algorithm. ๐
digital signature is a value computed from a block of data and a key, and only someone who knows the key could compute it. A digital signature computed with a secret key can only be verified by someone who knows that secret key. A digital signature computed with a private key can be verified by anyone who knows the corresponding public key. iykyk โ๏ธ
encipher means to render something incomprehensible except to the holder of a particular key. If you encipher with a secret key, only the holder of the same secret can decipher the message. If you encipher with a public key, only the holder of the corresponding private key can decipher it. ๐
initial trust certificate is a certificate signed by a principal for its own use, which states the name and public key of a trusted authority. ๐
global user name is a hierarchical name for a user that's unique within the entire domain of discussion (typically the network). ๐
local user name is a simple, non-hierarchical name a user is known by within a limited context, such as on a single computer. ๐ป
principal is an abstract entity that can be authenticated by name. In DASS there are user principals and server principals. ๐
private key is a cryptographic key used in asymmetric (public key) cryptography to decrypt and/or sign messages. In asymmetric cryptography, knowing the encryption key is independent of knowing the decryption key. The decryption (or signing) private key cannot be derived from the encrypting (or verifying) public key. ๐
proxy is a mapping from an external name to a local account name, for purposes of establishing a set of local access rights. Note that this differs from the definition in ECMA TR/46. ๐ช
public key is a cryptographic key used in asymmetric cryptography to encrypt messages and/or verify signatures. ๐
RSA is the Rivest-Shamir-Adelman public key cryptosystem, based on modular exponentiation where the modulus is the product of two large primes. When the term RSA key is used, it should be clear from context whether the public key, the private key, or the public/private pair is intended. ๐งฎ
secret key is a cryptographic key used in symmetric cryptography to encrypt, sign, decrypt and verify messages. In symmetric cryptography, knowledge of the decryption key implies knowledge of the encryption key, and vice-versa. ๐คซ
sign is a process that takes a piece of data and a key and produces a digital signature that can only be calculated by someone with the key. The holder of a corresponding key can verify the signature. โ๏ธ
source is the initiator of an authentication exchange. ๐
strong authentication is authentication by means of cryptographically derived authentication tokens and credentials. The actual working definition is closer to that of "zero knowledge" proof: authentication so as to not reveal any information usable by either the verifier, or by an eavesdropping third party, to further their potential ability to impersonate the claimant. this is the main character energy of security ๐ญ๐ฅ
target is the intended second party (other than the source) to an authentication exchange. ๐ฏ
ticket is a data structure certifying an authenticating (public) key by virtue of being signed by a user principal using their (long term) private key. The ticket also includes the UID of the principal. ๐๏ธ
trusted authority is the public key, name and UID of a certification authority trusted in some context to certify the public keys of other principals. ๐๏ธ
UID is a 128 bit unique identifier produced according to OSF standard specifications. ๐
user key is a "long term" RSA key whose private portion authenticates its holder as having the access rights of a particular person. ๐
verify means to cryptographically process a piece of data and a digital signature to determine that the holder of a particular key signed the data. โ
verifier is the party who will perform the operations necessary to verify the claimed identity of a claimant. ๐ต๏ธ
Security Considerations ๐
โ ๏ธ bestie, this part is serious. Security issues are discussed throughout this memo, not just in this one spot. read the whole thing, every section counts. ๐
Author's Address ๐
Charles Kaufman
Digital Equipment Corporation
ZKO3-3/U14
110 Spit Brook Road
Nashua, NH 03062
Phone: (603) 881-1495
Email: kaufman@zk3.dec.com
General comments on this document should be sent to cat-ietf@mit.edu. ๐ฌ
Minor corrections should be sent to the author. ๐ซถ
๐ฆ where this page comes from
- ietf: rfc1507 1993-09
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms