Security

Published on November 2016 | Categories: Documents | Downloads: 64 | Comments: 0 | Views: 336
of 14
Download PDF   Embed   Report

Comments

Content

Class Loaders
A Java programming language compiler converts source into the machine language of a hypothetical machine, called the virtual machine. The virtual machine code is stored in a class file with a .class extension. Class files contain the code for all the methods of one class. These class files need to be interpreted by a program that can translate the instruction set of the virtual machine into the machine language of the target machine. Note that the virtual machine interpreter loads only those class files that are needed for the execution of a program. For example, suppose program execution starts with MyProgram.class. Here are the steps that the virtual machine carries out.
1. The virtual machine has a mechanism for loading class files, for example, by reading the files from disk

or by requesting them from the Web; it uses this mechanism to load the contents of the MyProgram class file. loaded as well. (The process of loading all the classes that a given class depends on is called resolving the class.) class needs to be created).

2. If the MyProgram class has instance variables or superclasses of another class type, these class files are

3. The virtual machine then executes the main method in MyProgram (which is static, so no instance of a 4. If the main method or a method that main calls requires additional classes, these are loaded next.

The class loading mechanism doesn't just use a single class loader, however. Every Java program has at least three class loaders:
• • •

The bootstrap class loader; The extension class loader; The system class loader (also sometimes called the application class loader).

The bootstrap class loader loads the system classes (typically, from the JAR file rt.jar). It is an integral part of the virtual machine, and is usually implemented in C. There is no ClassLoader object corresponding to the bootstrap class loader. For example,
String.class.getClassLoader()

returns null. The extension class loader loads a standard extension from the jre/lib/ext directory. You can drop JAR files into that directory, and the extension class loader will find the classes in them, even without any class path. The extension class loader is implemented in Java.

The system class loader loads the application classes. It locates classes in the directories and JAR/ZIP files on the class path, as set by the CLASSPATH environment variable or the -classpath command-line option. This class loader is also implemented in Java. Class loaders have a parent/child relationship. Every class loader except for the bootstrap class loader has a parent class loader. Using Class Loaders as Namespaces Every Java programmer knows that package names are used to eliminate name conflicts. There are two classes called Date in the standard library, but of course their real names are java.util.Date and java.sql.Date. The simple name is only a programmer convenience and requires the inclusion of appropriate import statements. In a running program, all class names contain their package name. It may surprise you, however, that you can have two classes in the same virtual machine that have the same class and package name. A class is determined by its full name and the class loader. This technique is useful for loading code from multiple sources. For example, a browser uses separate instances of the applet class loader class for each web page. This allows the virtual machine to separate classes from different web pages, no matter what they are named.

Bytecode Verification
When a class loader presents the bytecodes of a newly loaded Java platform class to the virtual machine, these bytecodes are first inspected by a verifier. The verifier checks that the instructions cannot perform actions that are obviously damaging. All classes except for system classes are verified. You can, however, deactivate verification with the undocumented -noverify option. For example,
java -noverify Hello

Here are some of the checks that the verifier carries out:
• • • • •

That variables are initialized before they are used That method calls match the types of object references That rules for accessing private data and methods are not violated That local variable accesses fall within the runtime stack That the runtime stack does not overflow

If any of these checks fails, then the class is considered corrupted and will not be loaded. This strict verification is an important security consideration. Accidental errors, such as uninitialized variables, can easily wreak havoc if they are not caught. More important, in the wide open world of the Internet, you must be protected against malicious programmers who create evil effects on purpose. For example, by modifying values on the runtime stack or by writing to the private data fields of system objects, a program can break through the security system of a browser. You may wonder, however, why a special verifier checks all these features. After all, the compiler would never allow you to generate a class file in which an uninitialized variable is used or in which a private data field is accessed from another class. Indeed, a class file generated by a compiler for the Java programming language always passes verification. However, the bytecode format used in the class files is well documented, and it is an easy matter for someone with some experience in assembly programming and a hex editor to manually produce a class file that contains valid but unsafe instructions for the Java virtual machine. Once again, keep in mind that the verifier is always guarding against maliciously altered class files, not just checking the class files produced by a compiler.

Security Managers and Permissions
Once a class has been loaded into the virtual machine by a class loader or by the default class loading mechanism and checked by the verifier, the third security mechanism of the Java platform springs into action: the security manager. A security manager is a class that controls whether a specific operation is permitted. Operations checked by a security manager include the following:
• • • • • • • • •

Whether the current thread can create a new class loader Whether the current thread can halt the virtual machine Whether a class can access a member of another class Whether the current thread can access a local file Whether the current thread can open a socket connection to an external host Whether a class can start a print job Whether a class can access the system clipboard Whether a class can access the AWT event queue Whether the current thread is trusted to bring up a top-level window

There are many other checks such as these throughout the Java library.The default behavior when running Java applications is that no security manager is installed, so all these operations are permitted. The applet viewer, on the other hand, immediately installs a security manager that is quite restrictive.For example, applets are not allowed to exit the virtual machine. If they try calling the exit method, then a security exception is thrown.When you run a Java application, the default is that no security manager is running. Your program can install a specific security manager by a call to the static setSecurityManager method in the System class.

Java 2 Platform Security JDK 1.0 had a very simple security model: Local classes had full permissions, and remote classes were confined to the sandbox. Just like a child that can only play in a sandbox, remote code was only allowed to paint on the screen and interact with the user. The applet security manager denied all access to local resources. JDK 1.1 implemented a slight modification: Remote code that was signed by a trusted entity was granted the same permissions as local classes. However, both versions of the JDK provided an all-or-nothing approach. Programs either had full access or they had to play in the sandbox.

The Java 2 platform has a much more flexible mechanism. A security policy maps code sources to permission sets (see Figure 9-4).

A code source has two properties: the code location (for example, an HTTP URL for remote code, or a file URL for local code in a JAR file) and certificates. You see later in this chapter how code can be certified by trusted parties. A permission is any property that is checked by a security manager. JDK 1.2 implementation supports a number of permission classes, each of which encapsulates the details of a particular permission. For example, the following instance of the FilePermission class states that it is okay to read and write any file in the /tmp directory. FilePermission p = new FilePermission("/tmp/*", "read,write"); More important, the default implementation of the Policy class in JDK 1.2 reads permissions from a permission file. Inside a permission file, the same read permission is expressed as permission java.io.FilePermission "/tmp/*", "read,write";

Figure 9-5 shows the hierarchy of the permission classes that were supplied with JDK 1.2. Many more permission classes have been added in subsequent JDK versions.

Security Policy Files The policy manager reads policy files that contain instructions for mapping code sources to permissions. Here is a typical policy file: grant codeBase "http://www.horstmann.com/classes" { permission java.io.FilePermission "/tmp/*", "read,write"; }; This file grants permission to read and write files in the /tmp directory to all code that was downloaded from http://www.horstmann.com/classes. Digital Signatures As we said earlier, applets were what started the craze over the Java platform. In practice, people discovered that although they could write animated applets like the famous “nervous text” applet, applets could not do a whole lot of useful stuff in the JDK 1.0 security model. For example, because applets under JDK 1.0 were so closely supervised, they couldn’t do much good on a corporate intranet, even though relatively little risk attaches to executing an applet from your company’s secure intranet. It quickly became clear to Sun that for applets to become truly useful, it was important for users to be able to assign different levels of security, depending on where the applet originated. If an applet comes from a trusted supplier and it has not been tampered with, the user of that applet can then decide whether to give the applet more privileges. To give more trust to an applet, we need to know two things: • Where did the applet come from? • Was the code corrupted in transit? In the past 50 years, mathematicians and computer scientists have developed sophisticated algorithms for ensuring the integrity of data and for electronic signatures. The java.security package contains implementations of many of these algorithms. Fortunately, you don’t need to understand the underlying mathematics to use the algorithms in the java.security package. In the next sections, we show you how message digests can detect changes in data files and how digital signatures can prove the identity of the signer.

Message Digests A message digest is a digital fingerprint of a block of data. For example, the so-called SHA1 (secure hash algorithm #1) condenses any data block, no matter how long, into a sequence of 160 bits (20 bytes). As with real fingerprints, one hopes that no two messages have the same SHA1 fingerprint. Of course, that cannot be true—there are only 2160 SHA1 fingerprints, so there must be some messages with the same fingerprint. But 2160 is so large that the probability of duplication occurring is negligible.

A message digest has two essential properties: • If one bit or several bits of the data are changed, then the message digest also changes. • A forger who is in possession of a given message cannot construct a fake message that has the same message digest as the original.

The second property is again a matter of probabilities, of course. Consider the following message by the billionaire father: “Upon my death, my property shall be divided equally among my children; however, my son George shall receive nothing.” That message has an SHA1 fingerprint of 2D 8B 35 F3 BF 49 CD B1 94 04 E0 66 21 2B 5E 57 70 49 E1 7E The distrustful father has deposited the message with one attorney and the fingerprint with another. Now, suppose George can bribe the lawyer holding the message. He wants to change the message so that Bill gets nothing. Of course, that changes the fingerprint to a completely different bit pattern: 2A 33 0B 4B B3 FE CC 1C 9D 5C 01 A7 09 51 0B 49 AC 8F 98 92

A number of algorithms have been designed to compute these message digests. The two bestknown are SHA1, the secure hash algorithm developed by the National Institute of Standards and Technology, and MD5, an algorithm invented by Ronald Rivest of MIT.

The Java programming language implements both SHA1 and MD5. The MessageDigest class is a factory for creating objects that encapsulate the fingerprinting algorithms. It has a static method, called getInstance, that returns an object of a class that extends the Message- Digest class. This means the MessageDigest class serves double duty: • As a factory class • As the superclass for all message digest algorithms For example, here is how you obtain an object that can compute SHA fingerprints: MessageDigest alg = MessageDigest.getInstance("SHA-1");

Message Signing In the last section, you saw how to compute a message digest, a fingerprint for the original message. If the message is altered, then the fingerprint of the altered message will not match the fingerprint of the original. If the message and its fingerprint are delivered separately, then the recipient can check whether the message has been tampered with. However, if both the message and the fingerprint were intercepted, it is an easy matter to modify the message and then recompute the fingerprint. After all, the message digest algorithms are publicly known, and they don’t require secret keys. In that case, the recipient of the forged message and the

recomputed fingerprint would never know that the message has been altered. Digital signatures solve this problem. To help you understand how digital signatures work, we explain a few concepts from the field called public key cryptography. Public key cryptography is based on the notion of a public key and private key. The idea is that you tell everyone in the world your public key. However, only you hold the private key, and it is important that you safeguard it and don’t release it to anyone else. The keys are matched by mathematical relationships, but the exact nature of these relationships is not important for us.

Suppose Alice wants to send Bob a message, and Bob wants to know this message came from Alice and not an impostor. Alice writes the message and then signs the message digest with her private key. Bob gets a copy of her public key. Bob then applies the public key to verify the signature. If the verification passes, then Bob can be assured of two facts: • The original message has not been altered. • The message was signed by Alice, the

holder of the private key that matches the public key that Bob used for verification. You can see why security for private keys is all-important. If someone steals Alice’s private key or if a government can require her to turn it over, then she is in trouble. The thief or a government agent can impersonate her by sending messages, money transfer instructions, and so on, that others will believe came from Alice. The X.509 Certificate Format To take advantage of public key cryptography, the public keys must be distributed. One of the most common distribution formats is called X.509. Certificates in the X.509 format are widely used by VeriSign, Microsoft, Netscape, and many other companies, for signing e-mail messages, authenticating program code, and certifying many other kinds of data. The X.509 standard is part of the X.500 series of recommendations for a directory service by the international telephone standards body, the CCITT. The precise structure of X.509 certificates is described in a formal notation, called “abstract syntax notation #1” or ASN.1. Figure 9–13 shows the ASN.1 definition of version 3 of the X.509 format. The exact syntax is not important for us, but, as you can see, ASN.1 gives a precise definition of the structure of a certificate file. The basic encoding rules, or BER, and a variation, called distinguished encoding rules (DER) describe precisely how to save this structure in a binary file. That is, BER and DER describe how to encode integers, character strings, bit strings, and constructs such as SEQUENCE, CHOICE, and OPTIONAL.

The Authentication Problem Suppose you get a message from your friend Alice, signed with her private key, using the method we just showed you. You might already have her public key, or you can easily get it by asking her for a copy or by getting it from her web page. Then, you can verify that the message was in fact authored by Alice and has not been tampered with. Now, suppose you get a message from a stranger who claims to represent a famous software company, urging you to run the program that is attached to the message. The stranger even sends you a copy of his public key so you can verify that he authored the message. You check that the signature is valid. This proves that the message was signed with the matching private key and that it has not been corrupted. Be careful: You still have no idea who wrote the message. Anyone could have generated a pair of public and private keys, signed the message with the private key, and sent the signed message and the public key to you. The problem of determining the identity of the sender is called the authentication problem. The usual way to solve the authentication problem is simple. Suppose the stranger and you have a common acquaintance you both trust. Suppose the stranger meets your acquaintance in person and hands over a disk with the public key. Your acquaintance later meets you, assures you that he met the stranger and that the stranger indeed works for the famous software company, and then gives you the disk (see Figure 9–14). That way, your acquaintance vouches for the authenticity of the stranger.

Code Signing

One of the most important uses of authentication technology is signing executable programs. If you download a program, you are naturally concerned about damage that a program can do. For example, the program could have been infected by a virus. If you know where the code comes from and that it has not been tampered with since it left its origin, then your comfort level will be a lot higher than without this knowledge. In fact, if the program was also written in the Java programming language, you can then use this information to make a rational decision about what privileges you will allow that program to have. You might want it to run just in a sandbox as a regular applet, or you might want to grant it a different set of rights and restrictions. For example, if you download a word processing program, you might want to grant it access to your printer and to files in a certain subdirectory. However, you might not want to give it the right to make network connections, so that the program can’t try to send your files to a third party without your knowledge. You now know how to implement this sophisticated scheme. 1. Use authentication to verify where the code came from. 2. Run the code with a security policy that enforces the permissions that you want to grant the program, depending on its origin. JAR File Signing In this section, we show you how to sign applets and web start applications for use with the Java Plug-in software. There are two scenarios: • Delivery in an intranet. • Delivery over the public Internet. In the first scenario, a system administrator installs policy files and certificates on local machines. Whenever the Java Plug-in tool loads signed code, it consults the policy file for the permissions and the keystore for signatures. Installing the policies and certificates is straightforward and can be done once per desktop. End users can then run signed corporate code outside the sandbox. Whenever a new program is created or an existing one is updated, it must be signed and deployed on the web server. However, no desktops need to be touched as the programs evolve. We think this is a reasonable scenario that can be an attractive alternative to deploying corporate applications on every desktop. In the second scenario, software vendors obtain certificates that are signed by CAs such as VeriSign. When an end user visits a web site that contains a signed applet, a pop-up dialog box identifies the software vendor and gives the end user two choices: to run the applet with full privileges or to confine it to the sandbox. To make a signed JAR file, programmers add their class files to a JAR file in the usual way. For example, javac FileReadApplet.java jar cvf FileReadApplet.jar *.class Then a trusted person at ACME runs the jarsigner tool, specifying the JAR file and the alias of the private key: jarsigner -keystore acmesoft.certs FileReadApplet.jar acmeroot The signed applet is now ready to be deployed on a web server. Next, let us turn to the client machine configuration. A policy file must be distributed to each client machine. To reference a keystore, a policy file starts with the line keystore "keystoreURL", "keystoreType";

Encryption So far, we have discussed one important cryptographic technique that is implemented in the Java security API, namely, authentication through digital signatures. A second important aspect of security is encryption. When information is authenticated, the information itself is plainly visible. The digital signature merely verifies that the information has not been changed. In contrast, when information is encrypted, it is not visible. It can only

be decrypted with a matching key. Authentication is sufficient for code signing—there is no need for hiding the code. However, encryption is necessary when applets or applications transfer confidential information, such as credit card numbers and other personal data. Until recently, patents and export controls have prevented many companies, including Sun, from offering strong encryption. Fortunately, export controls are now much less stringent, and the patent for an important algorithm has expired. As of Java SE 1.4, good encryption support has been part of the standard library. Symmetric Ciphers The Java cryptographic extensions contain a class Cipher that is the superclass for all encryption algorithms. You get a cipher object by calling the getInstance method: Cipher cipher = Cipher.getInstance(algorithName); or Cipher cipher = Cipher.getInstance(algorithName, providerName); The JDK comes with ciphers by the provider named "SunJCE". It is the default provider that is used if you don’t specify another provider name. You might want another provider if you need specialized algorithms that Sun does not support. The algorithm name is a string such as "AES" or "DES/CBC/PKCS5Padding". The Data Encryption Standard (DES) is a venerable block cipher with a key length of 56 bits. Nowadays, the DES algorithm is considered obsolete because it can be cracked with brute force (see, for example, http://www.eff.org/Privacy/Crypto/Crypto_misc/DESCracker/ ). A far better alternative is its successor, the Advanced Encryption Standard (AES). See http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf for a detailed description of the AES algorithm. We use AES for our example. Once you have a cipher object, you initialize it by setting the mode and the key: int mode = . . .; Key key = . . .; cipher.init(mode, key); The mode is one of Cipher.ENCRYPT_MODE Cipher.DECRYPT_MODE Cipher.WRAP_MODE Cipher.UNWRAP_MODE The wrap and unwrap modes encrypt one key with another—see the next section for an example. Now you can repeatedly call the update method to encrypt blocks of data: int blockSize = cipher.getBlockSize(); byte[] inBytes = new byte[blockSize]; . . . // read inBytes int outputSize= cipher.getOutputSize(blockSize); byte[] outBytes = new byte[outputSize]; int outLength = cipher.update(inBytes, 0, outputSize, outBytes); . . . // write outBytes When you are done, you must call the doFinal method once. If a final block of input data is available (with fewer than blockSize bytes), then call outBytes = cipher.doFinal(inBytes, 0, inLength);

If all input data have been encrypted, instead call outBytes = cipher.doFinal(); Key Generation To encrypt, you need to generate a key. Each cipher has a different format for keys, and you need to make sure that the key generation is random. Follow these steps: 1. Get a KeyGenerator for your algorithm. 2. Initialize the generator with a source for randomness. If the block length of the cipher is variable, also specify the desired block length. 3. Call the generateKey method. For example, here is how you generate an AES key. KeyGenerator keygen = KeyGenerator.getInstance("AES"); SecureRandom random = new SecureRandom(); // see below keygen.init(random); Key key = keygen.generateKey(); Public Key Ciphers The AES cipher that you have seen in the preceding section is a symmetric cipher. The same key is used for encryption and for decryption. The Achilles heel of symmetric ciphers is key distribution. If Alice sends Bob an encrypted method, then Bob needs the same key that Alice used. If Alice changes the key, then she needs to send Bob both the message and, through a secure channel, the new key. But perhaps she has no secure channel to Bob, which is why she encrypts her messages to him in the first place. Public key cryptography solves that problem. In a public key cipher, Bob has a key pair consisting of a public key and a matching private key. Bob can publish the public key anywhere, but he must closely guard the private key. Alice simply uses the public key to encrypt her messages to Bob. Actually, it’s not quite that simple. All known public key algorithms are much slower than symmetric key algorithms such as DES or AES. It would not be practical to use a public key algorithm to encrypt large amounts of information. However, that problem can easily be overcome by combining a public key cipher with a fast symmetric cipher, like this: 1. Alice generates a random symmetric encryption key. She uses it to encrypt her plaintext. 2. Alice encrypts the symmetric key with Bob’s public key. 3. Alice sends Bob both the encrypted symmetric key and the encrypted plaintext. 4. Bob uses his private key to decrypt the symmetric key. 5. Bob uses the decrypted symmetric key to decrypt the message. Nobody but Bob can decrypt the symmetric key because only Bob has the private key for decryption. Thus, the expensive public key encryption is only applied to a small amount of key data. The most commonly used public key algorithm is the RSA algorithm invented by Rivest, Shamir, and Adleman.

Sponsor Documents

Or use your account on DocShare.tips

Hide

Forgot your password?

Or register your new account on DocShare.tips

Hide

Lost your password? Please enter your email address. You will receive a link to create a new password.

Back to log-in

Close