Kenan
Assistant Engineer
Assistant Engineer
  • UID621
  • Fans0
  • Follows0
  • Posts55
Reads:542Replies:0

Java security - provider-related architecture

Created#
More Posted time:Jan 17, 2017 14:30 PM
Security provider
The attribute of Java as secure software is reflected in the concept of its security software package. In other words, common concepts in the security field including identification, encryption and signing are supported by Java through the security software package. Java defines the security software package as a set of abstract interfaces. Sun, creator of Java, provides a set of implementations. The security software package consists of security providers, algorithms and engines. An engine is a set of operations while an algorithm defines how to execute those operations. A security provider, however, is responsible for implementing the two abstract concepts.
For example, a message digest is an engine, an operation that can be executed by developers. The idea of message digest has nothing to do with how message digest is calculated. All message digests share the same attributes, and the abstracted interface is the engine. The algorithm for implementing message digests can be MD5 or SHA and is implemented by specific classes. The security provider acts as a link between the engine and the algorithm and manages both of them. Security providers are designed to provide developers with a simple mechanism for easily changing or replacing existing algorithms and their implementations. Through the security provider, developers only need to use the engine interface, without concern for the specific class implementing the algorithm, or the specific security provider providing the algorithm.
Architecture
The architecture of the Java security software package system is comprised of four parts:
Engine
JVM provides the engine class, which is part of Java core APIs.
Algorithm
Each engine is implemented by a set of algorithms. Java provides a set of default algorithm implementations (provided by Sun) while third-party organizations can provide more implementations.
Provider
Algorithm classes are managed by a provider, and the provider manages the mappings between the algorithms and their implementations.
Security class
Security classes maintain a list of providers. By viewing security classes, you can learn about providers and their provided supported algorithms.
The work flow of the security provider system is as follows:
Service class > Engine: Invoke an interface.
Engine > Security class: Query.
Security class > Provider: Locate the provider.
Provider > Algorithm: Locate appropriate algorithms.
Algorithm > Service class: Return the calculation result.
Using the getInstance() method for MessageDigest as an example, it is discovered that the Security.getImpl() method is called for implementation while the internal implementation is made by such statements:
GetInstance.getInstance
                (type, getSpiClass(type), algorithm, params, provider).toArray();

In this case, getInstance returns an instance object whose class declaration is as follows:

public static final class Instance {
        public final Provider provider;
        public final Object impl;

        private Instance(Provider arg0, Object arg1) {
            this.provider = arg0;
            this.impl = arg1;
        }

        public Object[] toArray() {
            return new Object[] {this.impl, this.provider};
        }
    }


From the statements above, we can see that it is actually an encapsulation of a provider and an object (actual algorithms).
Provider selection
When JVM starts, it registers providers in $JREHOME/lib/security/java.security. Using the files on my PC as an example:
#
# List of providers and their preference orders (see above):
#
security.provider.1=sun.security.provider.Sun
security.provider.2=sun.security.rsa.SunRsaSign
security.provider.3=sun.security.ec.SunEC
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
security.provider.5=com.sun.crypto.provider.SunJCE
security.provider.6=sun.security.jgss.SunProvider
security.provider.7=com.sun.security.sasl.Provider
security.provider.8=org.jcp.xml.dsig.internal.dom.XMLDSigRI
security.provider.9=sun.security.smartcardio.SunPCSC
security.provider.10=apple.security.AppleProvider


JVM registers these providers during startup; namely Security
reads the file during initialization. To develop custom security providers, you need to place classes under the path to system classes. By viewing the code of the Provider class, you can see that Provider is actually a Properties file, which stores engine names and the implementations of specific algorithm classes in KV pairs.
In the following application example, you can view the actual provider engine and algorithms:
package com.taobao.cd.security;

import java.security.Provider;
import java.security.Security;
import java.util.Enumeration;

public class ProviderTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Provider[] providers = Security.getProviders();
        for (int i = 0; i < providers.length; i++) {
            System.out.println("" + (i + 1) + ":" + providers);
            for (Enumeration e = providers.keys(); e.hasMoreElements();) {
                System.out.println("\t" + e.nextElement());
            }
        }
    }

}

Part of the output is as follows:

1:SUN version 1.8
    Alg.Alias.Signature.SHA1/DSA
    Alg.Alias.Signature.1.2.840.10040.4.3
    Alg.Alias.Signature.DSS
    SecureRandom.SHA1PRNG ImplementedIn
    KeyStore.JKS
    Alg.Alias.MessageDigest.SHA-1
    MessageDigest.SHA
    ...


Architectural design of engine classes
The architectural design of engine classes is worth noting. As mentioned above, engine classes provide interfaces for service developers and also for third-party providers. Each engine provides providers with an interface, namely the SPI (security provider interface).
The following uses MessageDigest (the message digest engine) as an example. MessageDigest inherits MessageDigestSpi. MessageDigestSpi abstract classes define the operations to be executed by the message digest engine. MessageDigest internally has a Delegate class while the core method of MessageDigest is getInstance(), which is used to obtain the instances of a class. The actual implementation is as follows:
public static MessageDigest getInstance(String algorithm, String provider)
        throws NoSuchAlgorithmException, NoSuchProviderException
    {
        if (provider == null || provider.length() == 0)
            throw new IllegalArgumentException("missing provider");
        Object[] objs = Security.getImpl(algorithm, "MessageDigest", provider);
        if (objs[0] instanceof MessageDigest) {
            MessageDigest md = (MessageDigest)objs[0];
            md.provider = (Provider)objs[1];
            return md;
        } else {
            MessageDigest delegate =
                new Delegate((MessageDigestSpi)objs[0], algorithm);
            delegate.provider = (Provider)objs[1];
            return delegate;
        }
    }


By using Security.getImpl() to obtain corresponding providers and algorithm implementation classes and by executing type identification and the else delegate logic, a MessageDigest instance will be returned.
The following uses the MD5 algorithm provided by Sun security as an example to explain it more specifically. Though MD5 does not directly implement MessageDigestSpi, its parent class DigestBase inherits MessageDigestSpi. Therefore, the algorithm still meets the design requirements of the architecture. DigestBase achieves the general implementation of message digests and provides three abstract interfaces:
abstract void implCompress(byte[] arg0, int arg1);

    abstract void implDigest(byte[] arg0, int arg1);

    abstract void implReset();

Part of the specific implementation of the code in MD5 is as follows:

void implDigest(byte[] arg0, int arg1) {
        long arg2 = this.bytesProcessed << 3;
        int arg4 = (int) this.bytesProcessed & 63;
        int arg5 = arg4 < 56 ? 56 - arg4 : 120 - arg4;
        this.engineUpdate(padding, 0, arg5);
        ByteArrayAccess.i2bLittle4((int) arg2, this.buffer, 56);
        ByteArrayAccess.i2bLittle4((int) (arg2 >>> 32), this.buffer, 60);
        this.implCompress(this.buffer, 0);
        ByteArrayAccess.i2bLittle(this.state, 0, arg0, arg1, 16);
    }

The following class diagram shows the architecture of the class:


Guest