1 Introduction 2 Ground Rules Building a File System 3 File Systems 4 File Content Data Structure 5 Allocation Cluster Manager 6 Exceptions and Emancipation 7 Base Classes, Testing, and More 8 File Meta Data 9 Native File Class 10 Our File System 11 Allocation Table 12 File System Support Code 13 Initializing the File System 14 Contiguous Files 15 Rebuilding the File System 16 Native File System Support Methods 17 Lookups, Wildcards, and Unicode, Oh My 18 Finishing the File System Class The Init Program 19 Hardware Abstraction and UOS Architecture 20 Init Command Mode 21 Using Our File System 22 Hardware and Device Lists 23 Fun with Stores: Partitions 24 Fun with Stores: RAID 25 Fun with Stores: RAM Disks 26 Init wrap-up The Executive 27 Overview of The Executive 28 Starting the Kernel 29 The Kernel 30 Making a Store Bootable 31 The MMC 32 The HMC 33 Loading the components 34 Using the File Processor 35 Symbols and the SSC 36 The File Processor and Device Management 37 The File Processor and File System Management 38 Finishing Executive Startup Users and Security 39 Introduction to Users and Security 40 More Fun With Stores: File Heaps 41 File Heaps, part 2 42 SysUAF 43 TUser 44 SysUAF API Terminal I/O 45 Shells and UCL 46 UOS API, the Application Side 47 UOS API, the Executive Side 48 I/O Devices 49 Streams 50 Terminal Output Filters 51 The TTerminal Class 52 Handles 53 Putting it All Together 54 Getting Terminal Input 55 QIO 56 Cooking Terminal Input 57 Putting it all together, part 2 58 Quotas and I/O UCL 59 UCL Basics 60 Symbol Substitution 61 Command execution 62 Command execution, part 2 63 Command Abbreviation 64 ASTs 65 Expressions, Part 1 66 Expressions, Part 2: Support code 67 Expressions, part 3: Parsing 68 SYS_GETJPIW and SYS_TRNLNM 69 Expressions, part 4: Evaluation UCL Lexical Functions 70 PROCESS_SCAN 71 PROCESS_SCAN, Part 2 72 TProcess updates 73 Unicode revisted 74 Lexical functions: F$CONTEXT 75 Lexical functions: F$PID 76 Lexical Functions: F$CUNITS 77 Lexical Functions: F$CVSI and F$CVUI 78 UOS Date and Time Formatting 79 Lexical Functions: F$CVTIME 80 LIB_CVTIME 81 Date/Time Contexts 82 SYS_GETTIM, LIB_Get_Timestamp, SYS_ASCTIM, and LIB_SYS_ASCTIM 83 Lexical Functions: F$DELTA_TIME 84 Lexical functions: F$DEVICE 85 SYS_DEVICE_SCAN 86 Lexical functions: F$DIRECTORY 87 Lexical functions: F$EDIT and F$ELEMENT 88 Lexical functions: F$ENVIRONMENT 89 SYS_GETUAI 90 Lexical functions: F$EXTRACT and F$IDENTIFIER 91 LIB_FAO and LIB_FAOL 92 LIB_FAO and LIB_FAOL, part 2 93 Lexical functions: F$FAO 94 File Processing Structures 95 Lexical functions: F$FILE_ATTRIBUTES 96 SYS_DISPLAY 97 Lexical functions: F$GETDVI 98 Parse_GetDVI 99 GetDVI 100 GetDVI, part 2 101 GetDVI, part 3 102 Lexical functions: F$GETJPI 103 GETJPI 104 Lexical functions: F$GETSYI 105 GETSYI 106 Lexical functions: F$INTEGER, F$LENGTH, F$LOCATE, and F$MATCH_WILD 107 Lexical function: F$PARSE 108 FILESCAN 109 SYS_PARSE 110 Lexical Functions: F$MODE, F$PRIVILEGE, and F$PROCESS 111 File Lookup Service 112 Lexical Functions: F$SEARCH 113 SYS_SEARCH 114 F$SETPRV and SYS_SETPRV 115 Lexical Functions: F$STRING, F$TIME, and F$TYPE 116 More on symbols 117 Lexical Functions: F$TRNLNM 118 SYS_TRNLNM, Part 2 119 Lexical functions: F$UNIQUE, F$USER, and F$VERIFY 120 Lexical functions: F$MESSAGE 121 TUOS_File_Wrapper 122 OPEN, CLOSE, and READ system services UCL Commands 123 WRITE 124 Symbol assignment 125 The @ command 126 @ and EXIT 127 CRELNT system service 128 DELLNT system service 129 IF...THEN...ELSE 130 Comments, labels, and GOTO 131 GOSUB and RETURN 132 CALL, SUBROUTINE, and ENDSUBROUTINE 133 ON, SET {NO}ON, and error handling 134 INQUIRE 135 SYS_WRITE Service 136 OPEN 137 CLOSE 138 DELLNM system service 139 READ 140 Command Recall 141 RECALL 142 RUN 143 LIB_RUN 144 The Data Stream Interface 145 Preparing for execution 146 EOJ and LOGOUT 147 SYS_DELPROC and LIB_GET_FOREIGN CUSPs and utilities 148 The I/O Queue 149 Timers 150 Logging in, part one 151 Logging in, part 2 152 System configuration 153 SET NODE utility 154 UUI 155 SETTERM utility 156 SETTERM utility, part 2 157 SETTERM utility, part 3 158 AUTHORIZE utility 159 AUTHORIZE utility, UI 160 AUTHORIZE utility, Access Restrictions 161 AUTHORIZE utility, Part 4 162 AUTHORIZE utility, Reporting 163 AUTHORIZE utility, Part 6 164 Authentication 165 Hashlib 166 Authenticate, Part 7 167 Logging in, part 3 168 DAY_OF_WEEK, CVT_FROM_INTERNAL_TIME, and SPAWN 169 DAY_OF_WEEK and CVT_FROM_INTERNAL_TIME 170 LIB_SPAWN 171 CREPRC 172 CREPRC, Part 2 173 COPY 174 COPY, part 2 175 COPY, part 3 176 COPY, part 4 177 LIB_Get_Default_File_Protection and LIB_Substitute_Wildcards 178 CREATESTREAM, STREAMNAME, and Set_Contiguous 179 Help Files 180 LBR Services 181 LBR Services, Part 2 182 LIBRARY utility 183 LIBRARY utility, Part 2 184 FS Services 185 FS Services, Part 2 186 Implementing Help 187 HELP 188 HELP, Part 2 189 DMG_Get_Key and LIB_Put_Formatted_Output 190 LIBRARY utility, Part 3 191 Shutting Down UOS 192 SHUTDOWN 193 WAIT 194 SETIMR 195 WAITFR and Scheduling 196 REPLY, OPCOM, and Mailboxes 197 REPLY utility 198 Mailboxes 199 BRKTHRU 200 OPCOM 201 Mailbox Services 202 Mailboxes, Part 2 203 DEFINE 204 CRELNM 205 DISABLE 206 STOP 207 OPCCRASH and SHUTDOWN 208 APPEND Glossary/Index Downloads |
Authentication
Security review But let's say that the fence has two gates with different locks. Now if one has the key to either gate, one can get to the house. In other words, the house is now 1/2 as secure as if there was only one gate because there are twice as many ways to get through the fence. In fact, we can determine the relative security strength by dividing 1 by the number of gates in the fence. Thus, 100 gates means the house is only 1% as secure as if there was only one gate. Of course, all gates sharing the same key only count as a single gate because there is still only one key that can get you in. But we will assume each gate has a unique key for the sake of illustration. We can make the house more secure by adding another encircling fence inside the first one. Now, even if someone can get through the first fense, they have to get through the second fence as well. If each fence has only one gate, this configuration would be twice as secure as a single fence with a single gate, because it takes twice as many steps to get through. This process can continue indefinitely, with as many concentric circles of defense as desired. To calculate the total relative security, you would determine the relative security of each layer (circle) of protection and sum them up. Thus, a single fence with a single gate is a relative security of 1. A single fence with two gates is a relative security of 0.5. Two fences with one gate each has a relative security of 2. Two fences, where one fence has two gates, and the other has one, would have a relative security of 1.5.
Security failure modes Second, even if they key isn't copied, shared, or stolen, brute force methods could be used to determine the password or passcode. This would be akin to someone picking the lock on a gate. Programs can be written that try every possible combination of characters to guess the correct password - trying each possibility until the correct one is found. This illustrates how multiple gates are less secure - if there are two choices, it only requires half the number of guesses to come up with one that works, statistically speaking. There are two mitigations for this issue. First, each password should be sufficiently complex that it would take a long time for a brute force method to come up with the correct key. At a minimum, passwords ought to be at least eight characters long with a good mix of different kinds of symbols that don't match known patterns (such as could be found in a dictionary). The longer the password, the more secure it is from such attacks. The second mitigation is something we've already seen in the Login utility: if there are too many failures in a single login session, we exit. This forces the accessor to reconnect and try again. Of course, this only slows things down for an attacker and is insufficient, in itself, to secure the system. Note that a challenge/response mechanism is nearly impossible to circumvent via brute force. Nearly. But such mechanisms are somewhat expensive. Third, a nefarious agent can enter through the gate with a user that has valid access, even without them realizing. This is typically the result of the user running malware after they log in. There are various mitigation policies for this as well, ranging from not allowing the user to run anything except approved programs (such as with a captive account), to disallowing the user to run anything that isn't verifiable as friendly through some sort of certification mechanism. Even so, these aren't fool-proof. The best mitigation policy here is to limit the number of kinds of privileges granted to users. That way, even if they do run malware, it can only damage their own data at worst. And if one has robust backup policies, even that kind of damage can be minimized.
Good security practices The first thing to consider is a system password. This provides an outer fence with only one gate. If there is a system break-in, the admin can change the password and thus resecure the system. Of course, the admin then needs to let the users all know the new system password. On high-security systems, the system password should be changed regularly. The next step is to make sure that each individual has a separate user account. This makes it easy to audit who is doing what, and also to protect users from each other (whether through mistakes or malice). If a single user is compromised, that user account can be disabled without affecting any other individual using the system. On high-security systems, consider using passwords that expire after 30 days to force the user to change the password on a regular basis. Each user account should have the absolute minimum privileges necessary to do whatever their job requires, and no more than that. In the case of a personal computer, there should be a admin account with all privileges that is only used to do potentially dangerous things (such as installing new software), and the normal user account which the user can use the rest of the time. Further, users with special privileges authorized should have their default privileges set to the average user's privileges. This will require that the user manually elevate his privilege when necessary, rather than having the privileges always active. This will prevent a user from mistakenly doing something potentially dangerous to the integrity of the system - it at least forces them to think about what they are doing and intentionally elevate their privileges before hand. However, this is not fail-safe either, since the user may fail to reduce their privileges afterwards - making subsequent mistakes easier to make. Finally, the admin should regularly review security audits (a topic for a future article) to identify threats and address them proactively. The fact is that no system is 100% secure. That means that a lack of proactively monitoring the system's integrity and taking appropriate actions is a guarantee of eventual loss of that integrity. One last issue to consider is human psychology. Although it is counter-intuitive, having too much security can result in a less secure system. How so? Because if the user can't remember the multiple passwords required to get logged in, they may choose passwords that are too easy to guess or write them down somewhere. This is not far-fetched when one considers how often people forget even a single password to access a web site (and that is usually a password that they provided and ought to be able to remember!). Now multiply that by the number of different passwords they have for email, phones, computers, web sites, and so forth. Most people are already overburdened as far as remembering passwords. A common problem is that users may use the same password in multiple contexts. Thus a security breach on a completely unrelated system may result in the discovery of a password to your own system. Having too many fences around the system may result in the users compensating in ways that compromise the system. For an embedded controller in an appliance, no password may be needed at all. For a personal computer, usually a single password protecting the system will suffice. For the average multiuser system, perhaps a system password and a single per-user password is sufficient. For high-security systems, multiple layers of protection may be required, passwords might be randomly created and assigned to users, remote access may be disabled, and users who forget their system credentials might need to appear in person before a manager to regain access to the system.
Authentication details To allow the use of zero, one, or more authorizaton factors, the UAF records each have a pointer to a list of TUAF_Authentication records. If the pointer is 0, the account has no authentication required. For each item in the list, there is a corresponding authentication step required during login. Article 43 covered the code to manage this list. Each authentication factor may involve a password. This password must be stored in the SYSUAF file so that the password the user supplies can be compared to it. However, one must always be cautious about storing sensitive data in files - and a password is pretty sensitive. We reduce the possibility of someone getting ahold of the data by protecting the SYSUAF.DAT file so that only users with certain privileges can access it. But there are a few different ways that such data could still fall into the wrong hands. First, someone might make a copy of the file (for perfectly legitimate purposes) that could be accessed. Or as files are deleted, moved, or extended, the location on the disk that contained the data might become available. This is a general security problem for all file systems, but UOS can be configured to erase old data on the disk. However we can't guarantee that UOS will be configured this way. Add to this, the danger of the data becoming available via backup copies of the file, and we can see that there is a huge potential attack surface for people to get ahold of passwords - including ones for high-privilege users. By now, you might be getting the impression that attaining a 100% secure system of any kind (for any operating system) is next to impossible. That may be true. However, we can breathe easier if we can reduce the possibility to an astronomically small value. Any properly secured modern operating system is going to be pretty secure - even if not quite 100%. One way that modern operating systems help protect password data, even in light of the aforementioned dangers, is to use encryption/hashing so that the password is not stored in plain-text form. That is, even if someone gets ahold of the data, the password is not going to be staring them in the face in any recognizable form. There are a couple of ways to encrypt passwords (or any text): true encryption, and hashing. Hashing isn't true encryption because it is a one-way process. The original text cannot be recovered from a hashed value because it isn't a one-to-one mapping. On the other hand, one could theoretically reverse-engineer a hash to come up with something that would hash to the same value, which would be the same as having the original password. In other words, multiple different passwords might hash to the same value. The best solution is to have a hashing algorithm that is difficult to reverse-engineer. On the other hand, a true encryption creates a one-to-one correspondence with the hashed value. This allows an encrypted value to be decrypted to its original value if one has the encryption key. The problem with storing an encrypted password is that there must be an encryption key stored with it. But now you have to protect the key as well - because with that key, one can unencrypt the encrypted password and gain access to the account. Which type of mechanism is used may not matter on most systems. There are many different hash and encryption algorithms, each with its own characteristics that define how hard it is to break, how large the key size must be, how long it takes to encrypt data, and how long it takes to decrypt the data. Regardless of what mechanism we use, we will call the result the "protected value" or "ciphertext". Since true encryption requires a key to be used both for encryption and decryption, we'd have to store that key or use some known value as the key. As discussed, adding another key simply gives us additional data that must also be protected. So, UOS will simply use the password itself as the encryption key, should a true encryption algorithm be chosen to protect the password. Depending upon the encryption algorithm used, this could be a very secure way of protecting the password. But if all that is wanted is a way to obscure the password text in the file, pretty much any hash or encryption algorithm could be used. UOS will provide a few different algorithms that the administrator can choose from to protect the password. From the SYSUAF perspective, it doesn't matter if it is a hash or encryption since we never reverse the protected password. But how then do we know if the user provides the proper password? We simply use the same mechanism to convert the password entered at login time and then compare that result with the result that we previously stored when the password was originally applied to the account. If they match, we consider the entered password to be valid. Again, a hashed value may mean that multiple different possible passwords might match. On one hand, there is no visible relationship between these passwords, so it isn't likely much of a risk. On the other hand, the more possible matches there are, the less time it will take a brute-force attack to succeed. Also, consider that hash algorithms tend to run much faster (take less processor and memory overhead) than encryption algorithms. So, you have to consider how secure you want your system, given all of the pros and cons involved. Although we talked about an infinite-key encryption algorithm that we used to generate a UUID for initializing disks in Init, it is not an algorithm we will use for password hashing in UOS. It works well for generating a UUID, but it wouldn't be safe for protecting military secrets, for instance. Although I understand the basic principles of hashing and encryption, I am not a cryptologist (if for no other reason that I'm not fond of the math). Fortunately, I don't need to be - the experts have already created various algorithms and we need only to implement those algorithms and use them. In fact, we don't even have to implement most of the algorithms because others have done the work for us. For that reason, we won't be covering the actual hashing code in these articles, but you can look at it after the next code release if you wish. But we will look at some of the code in the Hashlib module in the next article. Hashlib provides for hashing and encryption and is used at the application layer. Here is the documentation for the various routines that Hashlib provides (note that we will not address true encryption at this point - hashing will serve us adequately):
HASH_Count Returns the highest hash index of the installed hash algorithms.
Format HASH_Count
Description This service returns the highest index of the installed hash algorithms.
Condition Values Returned No code returned; the result value is the highest hash algorithm index. HASH_Default Returns the index of the default hash algorithm to use for secure hash operations.
Format HASH_Default
Description This service returns the index of the default hash algorithm used for security purposes.
Condition Values Returned No code returned; the result value is the index of the default hash algorithm. HASH_Hash Returns the hash of a string, according to the specified hash algorithm.
Format HASH_Hash index, source, target, key, resultlength
Arguments index
source
target
key
resultlength
Description This service returns a hashed value of the passed data. In the case of Hash_Plaintext, the service returns a copy of the passed string. If the calling code does not know the result size of the hash, it should check the resulting length; if the result length is equal to the receiving buffer size, the receiving buffer should be increased and the hash attempted again. This should be repeated until the result length is less than the receiving buffer size, which will guarantee that the entire hash is returned. Note that the above hash sizes indicate the number of bits in the result.
Condition Values Returned SS_NORMAL Normal completion of service. HASH_Index Returns the hash algorithm index corresponding to the specified name.
Format HASH_Index name result
Arguments name
result
Description This service returns the index of the hash algorithm corresponding to the passed name.
Condition Values Returned SS_NORMAL Normal completion of service. HASH_Name Returns the hash algorithm name of the specified index.
Format HASH_Name index result
Arguments index
result
Description This service returns the name of the hash algorithm corresponding to the passed index value.
Condition Values Returned SS_NORMAL Normal completion of service. In the next article, we will look at the Hashlib code that implements the above functions.
Copyright © 2022 by Alan Conroy. This article may be copied in whole or in part as long as this copyright is included. |