Cryptography-based mechanisms include certificates, digital signatures, and message digests, which are used to "shrink-wrap" distributed software and data. They establish software origin and verify its integrity with a high degree of reliability, subject to the strength of the underlying cryptography algorithm.
In their simplest forms, CRC or digests are used to verify software integrity. For more involved implementations, Message Authentication Code (MAC), or Hash-based MAC (HMAC), specified in RFC 2104, may be applied, which add cryptographic protection (using symmetric secret keys) for improved protection. Both platforms support most common digest, MAC, and HMAC functions in their respective cryptography namespaces. See Part 2 of this series for details of supported algorithms. Numerous code samples are available on the Web for both Java and .NET (also see MSDN).
For application distribution, .NET supports software signing to prove application or publisher identities. For the first task, it provides so-called strong names, and for the second, signing with publisher certificates. These approaches are complementary and independent; they can be used individually or jointly, thus proving the identities of both the application and publisher. The users can configure .NET CAS policy based either on strong names, or on the software publisher, because they both provide strong assurances about the signed code. Because of their high levels of trust, strong-named assemblies can call only strong-named assemblies.
Strong names are used to prove authenticity of the assembly itself, but they have no ties to the author, as it is not required to use the developer's certificate to sign an assembly. A strong name can be viewed as a descendent of GUID mechanism, applied to the assembly names, and composed of text name, version number, culture information, plus the assembly's digital signature and a unique public key, all stored within the assembly's manifest, as shown in Figure 1:
Figure 1. Manifest of a Strong-Named Assembly
The same key pair should not be reused for signing multiple assemblies — unique key pairs should be generated for signing different assemblies. Note that a version is not guaranteed by the strong name due to applying CLR versioning policy, as the same key will be reused to sign a newer version of the particular assembly. .NET provides a tool named
sn.exe to generate key pairs and perform a number of validation steps related to strong names. Generated keys can either be picked up by
AssemblyLinker, or added to the assembly declaratively by using an attribute:
Clients, linked against a strong-named assembly, store a
PublicKeyToken representing the assembly, an eight-byte hash of the full public key used to create its strong name, as shown in Figure 2. This is done transparently by the compiler when a strong-named assembly is referenced. This is an example of early binding.
Figure 2. Manifest with Public Key Token
Late binding can be achieved by using
Assembly.Load and calling the full display name of the assembly, including its token:
Assembly.Load("CommonLibrary, Version= 1:0:1385:17444, Culture=neutral, PublicKeyToken=F5BE3B7C2C523B5D");
The CLR always verifies an assembly's key token at runtime, when it loads the referenced assembly. Two strong names are configured in security policy by default: one for Microsoft code, another for .NET's components submitted to ECMA for standardization.
Publisher certificates establish the identity of the code distributor by requiring him to use a personal certificate to sign a whole assembly or individual files in an assembly. The signature is then stored inside of the file and verified by the CLR at runtime. .NET provides the
Signcode.exe tool to perform the publisher signing operation. To do its job, it should have access to both the publisher certificate (with a valid trust chain), and the private key for that certificate, which will be used to sign the file(s).
Publisher certificates, as opposed to the strong names concept, are used to sign multiple applications and cannot uniquely identify a particular code module, nor are they intended to. Their intent is to identify a broad set of applications as originating from a particular vendor, in order to assign appropriate permissions based on the level of trust in this company.
As far as signing distributives goes, Java offers a single route that is similar the publisher-signing paradigm in .NET. However, there are significant differences in the approaches, since JAR specifications permit multiple signers and signing of a subset of the JAR's content.
Quite surprisingly, in Sun's default distribution of JDK 1.4.2, only
jce.jar is signed -- all of the other libraries do not have any signature. As a standard part of the JDK, Sun ships the
jarsigner tool, which works with Java keystores to obtain private keys and certificates for signing and verification to validate certification chains. This tool operates on existing JAR files (it does not create them), which are a standard distribution format in Java.
jarsigner -keystore PrivateKeystore.jks -sigfile DP -signedjar DemoApp_Sig.jar DemoApp.jar denis
When a JAR file is signed, its manifest is updated and two new files are added to the META-INF directory of the archive (see the JAR Guide for details): class signature and signature block.
The manifest file is called MANIFEST.MF and contains digest records of all signed files in the archive (which may be a subset of the archive!). Those records conform to the RFC 822 header specification and consist of a file name and one or more tuples (digest algorithm, file digest). As a rule, either
MD5 digest algorithms are used. It is the manifest itself, not the physical JAR file, that is signed, so it is important to understand that once a JAR is signed, its manifest should not change -- otherwise, all signatures will be invalidated.
Manifest-Version: 1.0 Created-By: 1.4.2-beta (Sun Microsystems Inc.) Name: javax/crypto/KeyGeneratorSpi.class SHA1-Digest: HxiOMRd8iUmo2/fulEI1QH7I2Do= Name: javax/crypto/spec/DHGenParameterSpec.class SHA1-Digest: zU+QpzVweIcLXLjmHLKpVo55k0Q=
A signature file represents a signer, and an archive contains as many of these files as there are signatures on it. File names vary, but they all have the same extension, so they looks like
<Name>.SF. Signature files contain "digests of digests" they consist of entries with digests of all digest records in the manifest file at the time of signing. Those records conform to RFC 822 header specification and have the same format as the manifest's ones. Additionally, this file also contains a digest for the entire manifest, which implies that the JAR manifest may not be changed once signed. Incidentally, this means that all signers have to sign the same set of files in the JAR — otherwise, if new files have been added to the JAR prior to generating another signature, their digests will be appended to the manifest and invalidate already existing signatures.
An important point to observe is that when a JAR file is signed, all of the files inside it are signed, not only the JAR itself. Up until JDK 1.2.1, signed code had a serious bug: it was possible to alter or replace the contents of a signed JAR, and the altered class was still allowed to run. This problem was rectified starting with version 1.2.1 by signing each class inside of a signed JAR.
Signature-Version: 1.0 Created-By: 1.4.2-beta (Sun Microsystems Inc.) SHA1-Digest-Manifest: qo3ltsjRkMm/qPyC8xrJ9BN/+pY= Name: javax/crypto/KeyGeneratorSpi.class SHA1-Digest: FkNlQ5G8vkiE8KZ8OMjP+Jogq9g= Name: javax/crypto/spec/DHGenParameterSpec.class SHA1-Digest: d/WLNnbH9jJWc1NnZ7s8ByAOS6M=
A block signature file contains the binary signature of the SF file and all public certificates needed for verification. This file is always created along with the SF one, and they are added to the archive in pairs. The file name is borrowed from the signature file, and the extension reflects the signature algorithm (RSA|DSA|PGP), so the whole name looks like
The JAR-signing flexibility comes from separating digest and signature generation, which adds a level of indirection to the whole process. When signing or verifying, individual signers operate on the manifest file, not the physical JAR archive, since it is the manifest entries that are signed. This allows for an archive to be signed by multiple entities and to add/delete/modify additional files in the signed JAR, as long as it does not affect the manifest (see the explanations in the signature file paragraph).
Note: Strong names in .NET offer an improved approach to versioning. JAR files, on the other hand, have more options for signing, so this category is a draw.
Once a piece of software's origin and integrity have been established, non-cryptographic approaches may be used to ensure that the code can not be used in an unintended manner. In particular, this implies that the platform's package- and class-protection mechanisms cannot be subverted by illegally joining those packages or using class derivation to gain access to protected or internal members. These types of protection are generally used to supplement CAS and aimed at preventing unauthorized execution or source code exposure.
Reflection mechanisms on both platforms allow for easy programmatic access to code details and very late binding of arbitrary code, or even utilize code generation capabilities -- see, for instance,.NET technology samples, or the Java Reflection tutorial. One common example of such a threat would be a spyware application, which secretly opens installed applications and inspects/executes their functionality in addition to its officially advertised function. To prevent such code abuse, granting reflection permissions (
System.Security.Permissions.ReflectionPermission in .NET,
java.lang.reflect.ReflectPermission in Java) in CAS policy should be done sparingly and only to highly trusted code, in order to restrict capabilities for unauthorized code inspection and execution.
In .NET, application modules are called assemblies, and located at runtime by a so-called probing algorithm. By default, this algorithm searches for dependent assemblies only in the main assembly's directory, its subdirectories, and the
Global Assembly Cache (GAC). Such a design is used to guard against possible attempts to access code outside of the assembly's "home." Note that it does not prevent loading and executing external assemblies via reflection, so CAS permissions should be applied as well.
Types in .NET are organized into namespaces. One may extend an already established namespace in his own assemblies, but will not gain any additional information by doing so, since the keyword
internal is applied at the assembly, and not namespace, level. Strong names are used as a cryptographically strong measure against replacement of the existing types.
If the designer wants to completely prohibit inheritance from a class or method overloading, the class or method may be declared
sealed. As an additional means of protection against source browsing, the C# language defines a
#line hidden directive to protect against stepping into the code with a debugger. This directive instructs the compiler to avoid generating debugging information for the affected area of code.
During execution of a Java application, class loaders are responsible for checking at loading/verification time that the loaded class is not going to violate any package protection rules (i.e., does not try to join a
protected package). Particular attention is paid to the integrity of system classes in
java.* packages — starting with version 1.3, the class-loading delegation model ensures that these are always loaded by the null, or primordial class loader (see "Secure Class Loading" for details).
The Java platform defines the following options for protecting packages from joining:
Sealed JAR files
These are used to prevent other classes from "joining" a package inside of that JAR, and thus obtaining access to the protected members. A package,
com.MyCompany.MyPackage.*, may be sealed by adding a
Sealedentry for that package to the JAR manifest file before signing it:
Name: com/MyCompany/MyPackage/ Sealed: true
Configuration restrictions for joining packages
These can be used to control which classes can be added to a restricted package by adding them to the java.security file. Note that none of Sun-supplied class loaders performs this check (due to historical reasons), which means that this protection is only effective with a custom class loader installed:
# List of comma-separated packages that start # with or equal this string will cause a security # exception to be thrown when passed to # checkPackageDefinition unless the corresponding # RuntimePermission ("defineClassInPackage."+package) # has been granted. # # by default, no packages are restricted for # definition, and none of the class loaders # supplied with the JDK call checkPackageDefinition. package.definition=com.MyCompany.MyPackage.private
Configuration restrictions for accessing packages
SecurityManageris installed, it checks the package-access policies defined in
java.securityfile. A package can have restricted access so that only classes with appropriate permissions can access it. For instance, all
sun.*packages are restricted in the default installation:
# List of comma-separated packages that start # with or equal this string will cause a security # exception to be thrown when passed to # checkPackageAccess unless the corresponding # RuntimePermission ("accessClassInPackage."+package) # has been granted. package.access=sun.
A sample Java application, demonstrating declarative access control to packages, can be obtained here.
Note: Configuration options in Java add a convenient method for declarative code protection, which gives it a slight edge over .NET in this category.