RFC 1507

DASS - Distributed Authentication Security Service

๐Ÿ“… published September 1993, from ietf

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 logindata or token, 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:

  1. comply with X.509,
  2. allow possible interoperability with other architectures using different algorithms, and
  3. 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 ๐Ÿซถ
  • 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:

  1. Certificate version is X.509 1992.
  2. Issuer name is the Principal name (which is an X.500 name).
  3. Issuer UID is the Principal UID.
  4. Validity is for all time.
  5. Subject name is constructed from the Principal name by removing the last simple name from the hierarchical name.
  6. Subject UID is the CA_UID.
  7. Subject Public Key is the CA_Public_Key
  8. Sequence number is 1.
  9. 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_name using its own credentials to verify the authenticity of the user's credentials. These node credentials must have previously been established by a call to initialize_server during 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_credentials are available, use them in a call to Get_Pub_Keys to 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 adding CSS=X509.
    • 2) If TA_credentials are not available, look up the public key of the login agent in the naming service.

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 the Invalid password error 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

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 combine network_login, create_credentials, and combine_credentials in 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_credentials like 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
  • 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_credentials or accept_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_login for the deets on TA_credentials and 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 Name field
      • (ii) UID of the CA from the Subject UID field
      • (iii) Public Key of the CA from the Subject Public Key field
    • 4) no node credentials
    • 5) no cached outgoing associations
    • 6) no cached incoming associations

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 ๐ŸŽ€:

  1. Encrypted Shared Key from cache
  2. Login Ticket from Claimant Credentials (if present)
  3. Shared Key Ticket from cache (if no delegation and if present)
  4. Node Ticket from cache (if present)
  5. Delegator from cache (if delegation and if present)
  6. Authenticator
  7. Principal name from credentials (if present and parameter requests this)
  8. Node name from credentials (if present and parameter requests this)
  9. 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:

  1. Encrypted Shared Key, from the Authentication Token.
  2. 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:

  1. 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.
  2. There are no verifier credentials.
  3. 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 ๐ŸŒˆ.)
  4. Remote node credentials (Node name, Username, Node Ticket)
  5. There are no local node credentials.
  6. 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_token using 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 ๐ŸŒธ

  1. a) If there is no Login Ticket in the claimant credentials, then return False.
  2. b) If the current time is not within the validity interval of the Login Ticket, then return False.
  3. 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. 1) Encrypted shared key from the encrypted shared key subfield of the claimant information of the accepted credentials.
    2. 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. 3) Initialize all other entries in the cache to missing.
  4. 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. 1) Set the Public Key of the principal field in the Claimant information to be the Public Key of Client Principal.
    2. 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.
  5. 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 โœจ
  6. 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 ๐Ÿ™ˆ
  7. 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. 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. 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. 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. 4) Return True ๐Ÿ‘‘
  8. 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. 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. 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. 3) Return True ๐Ÿ’–
  9. 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, return false ๐Ÿฅฒ

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:

  1. Current-directory-name: the name marking where we are in the tree walk. at the start, it's the name of the TA.
  2. 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.
  3. 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.
  4. Search-when-descending: a list of PK/UID/Name triples of issuers that'll be trusted when descending the tree. it starts out empty.
  5. Saved-RDNs: a sequence of Relative Distinguished Names (RDNs) that get stripped off the right of the target name to form Least-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:

  1. find all cross certificates stored in the naming service under Current-directory-name where 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 in Trusted-Key-Set for Current-directory-name into 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-Set 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.
    • (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.
  2. if any cross certs were found (whether or not they all got eliminated by the filter), set Current-directory-name to 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 ๐Ÿ”
  1. 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.
  2. 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 the Trusted-Key-Set.
  3. strip the rightmost simple name off the Current-directory-name.
  4. repeat from step (a) (testing to see if current-directory-name is the same as Common-point-Name).

c) Searching the "target side" of the tree for a crosslink ๐Ÿ”

  1. initialization: set Current-directory-name to the name supplied as input to this procedure.
  2. 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.
  3. 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-set for that directory. ๐Ÿ”„
  4. if after steps (b) and (c) there are no PK/UID/Name triples corresponding to Current-directory-name in Trusted-Key-Set, shorten Current-directory-name by one RDN (pushing it onto the Saved-RDNs stack) and repeat this process until Current-directory-name is equal to Least-common-ancestor-name or there is at least one triple in Trusted-key-set corresponding to Current-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:

  1. remove the first RDN from Saved-RDNs and append it to the Current-directory-name.
  2. find all the child directory certificates for the directory whose name is in the current-directory-name.
  3. 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-set for 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.
  4. take the key, UID, and name from each remaining certificate and form a new triple corresponding to Current-directory-name in Trusted-Key-Set. if this set is empty, then the algorithm exits with the Incomplete-chain-of-trustworthy-CAs error condition. ๐Ÿšซ
  5. 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 with Try_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_CA to 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_certificate function. 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_Certificate to 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 by GSS_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 from GSS_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_token produces a mutual authentication token and a new credentials structure. if delegation was requested, the new credentials structure will be an output of GSS_Accept_sec_context. in any case, it will be used in the subsequent steps of this procedure ๐Ÿช„.
  • use the DASS Get_principal_name function to extract the principal name from the credentials produced by Accept_token. this name is one of the outputs of GSS_Accept_sec_context ๐Ÿท๏ธ.
  • apply the DASS Verify_principal_name to 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 of COMPLETE. return a handle to that context ๐Ÿ”.
  • if delegation was requested, return the new credentials from GSS_Accept_sec_context. otherwise, call Delete_credentials ๐Ÿงน.
  • if mutual authentication was requested, wrap the mutual authentication token from Accept_token in 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