Assemblies

Team-Fly    

 
Application Development Using Visual Basic and .NET
By Robert J. Oberg, Peter Thorsteinson, Dana L. Wyatt
Table of Contents
Chapter 9.  Assemblies and Deployment


In .NET, assemblies are components . Assemblies, which may be composed of one or more DLL or EXE files, are the unit of deployment, not individual DLLs or EXEs. Security evidence and versioning are based on the assembly. Assemblies contain Microsoft Intermediate Language (MSIL) instructions, resource data, and metadata. Since metadata describes the content of the assembly, they do not require any external descriptions, such as in the system registry. .NET components are much simpler and less error prone to install and uninstall, than traditional COM components, which had extensive registry entries.

A digital signature is required before an assembly can be deployed in the GAC. Digitally signed assemblies provide cryptographically generated verification information that can be used by the CLR to enforce crucial dependency rules when locating and loading assemblies. This is distinct from the security verification that is done to make sure that code is type safe.

The identity of an unsigned assembly is defined simply as a human readable name , along with a version number. The identity of a digitally signed assembly also includes its originator, uniquely associated with a cryptographic key pair. Optionally , an assembly's identity may also include a culture code for supporting culturally specific character sets and string formats.

An assembly's version can be checked so that the CLR can insure that the same assembly version with which the client was built and tested is loaded. This eliminates the infamous DLL Hell problem, where Windows applications could easily break when an older version was replaced with a newer version (or vice versa). A digitally signed assembly can be used to verify that the assembly contents were not altered since the time when it was digitally signed. Not only will you not accidentally use the wrong version, but also you will not be tricked into using a malicious tampered component that could do serious harm.

Although there is often a one-to-one correspondence between namespace and assembly, an assembly may contain multiple namespaces, and one namespace may be distributed among multiple assemblies. While there is often a one-to-one correspondence between assembly and binary code file (i.e., DLL or EXE) one assembly can span multiple binary code files. While an assembly is the unit of deployment, an application is the unit of configuration.

Componentized Version of Case Study

graphics/codeexample.gif

For our next step of the case study, we split our Hotel Administrator's program into three assemblies. The CaseStudy directory for this chapter has an AcmeGui application program (EXE), and two component (DLL) assemblies: Customer and Hotel . The code associated with the customer and hotel classes have been moved to separate assemblies. When we discuss configuration later in the chapter, it is the AcmeGui application that will be configured.

We will use the Customer and Hotel assemblies to understand the issues associated with deployment. All public members of the Customer and Hotel assembly will be visible to code outside of their respective assemblies. Members marked as Friend scope can only be used within the assembly.

graphics/codeexample.gif

If you look at Figure 9-6, you will see that the Solution Explorer window shows that the AcmeGui project has references to the Customer and Hotel dynamic link libraries. These references enable the compiler to find the Hotel and Customer types used by AcmeGui , and then build the application. They do not dictate where the DLLs have to be when the project is deployed; we will explain how this works when we discuss deployment. You will also notice references made to system assemblies such as System.dll . Looking at the properties for the reference will show you where the assembly is located. [1]

[1] Select the assembly in the Solution Explorer, right-mouse-click, and select Properties in the context menu.

Figure 9-6. AcmeGui's Solution Explorer showing References.

graphics/09fig06.jpg

Creating a DLL is simple, as we saw in the previous section. The AcmeGui solution illustrates a somewhat different approach than we used previously. Rather than having separate solutions for the class libraries and the application that uses them, we have one solution with several projects. To make the DLLs easy to find during compilation, we set the Output path to be up one directory, as illustrated in Figure 9-7.

Figure 9-7. Setting the Output path for the Customer class library project.

graphics/09fig07.jpg

We also set the project dependencies to indicate that the AcmeGui project depends on the Customer and Hotel projects. (Use the Project Dependencies dialog, as illustrated in Figure 9-8. This dialog is brought up from the menu Project Project Dependencies.) The result is that the DLLs will be built first. The advantage of having all the projects in one solution is that you can be assured that the class libraries are kept up to date.

Figure 9-8. Setting the project dependencies to ensure proper build order.

graphics/09fig08.jpg

Contents of an Assembly

Every Assembly has a Manifest that describes the metadata information associated with the Assembly. A manifest provides the following information about an assembly.

  • Assembly identity based on name, version, culture, and, optionally, a digital signature.

  • Lists files that contribute to the assembly contents.

  • Lists other assemblies on which the assembly is dependent.

  • Lists permissions required by the assembly to carry out its duties .

The VB.NET assembly created by Visual Studio has a file, AssemblyInfo.vb , that contains the following attributes that can be used to set the information associated with an assembly.

 <Assembly: AssemblyTitle("")> <Assembly: AssemblyDescription("")> <Assembly: AssemblyCompany("")> <Assembly: AssemblyProduct("")> <Assembly: AssemblyCopyright("")> <Assembly: AssemblyTrademark("")> <Assembly: CLSCompliant(True)> ... <Assembly: AssemblyVersion("1.0.*")> 

To explore how versioning, digital signing, and deployment work, we use the ILDASM tool introduced in Chapter 2 to view the appropriate metadata. If you have not already done so, you may wish to add ILDASM to your Tools menu in Visual Studio. Bring up the External Tools dialog from the Tools External Tools menu. Click the Add button and type ILDASM for the Title. Click the graphics/navigate_icon.jpg button and navigate to the directory where ildasm.exe is located (the bin folder under FrameworkSDK under Microsoft Visual Studio .NET under Program Files ). Figure 9-9 illustrates adding ILDASM as an external tool in this manner. Note that we have also specified the solution directory as the initial directory for ILDASM.

Figure 9-9. Adding ildasm.exe as an external tool.

graphics/09fig09.jpg

Figure 9-10 shows the top level that you will see when you open the Customer.dll assembly in ILDASM. You see an entry for the MANIFEST, and under the OI.NetVB.Acme namespace, you see entries for the Customer and Customers classes, the ICustomer interface, and the CustomerListItem value type. Clicking on a plus (+) button will expand an entry.

Figure 9-10. Top-level ILDASM view of Customer component.

graphics/09fig10.jpg

To view the manifest, double-click the MANIFEST node, shown in Figure 9-10, and the resulting manifest information is displayed in Figure 9-11. Some of the information will vary if you have rebuilt any of the samples or you have a later version of .NET.

Figure 9-11. ILDASM showing manifest of Customer.dll.

graphics/09fig11.jpg

The manifest contains information about the dependencies and contents of the assembly. You can see that the manifest for Customer contains, among others, the following external dependency.

 .assembly extern mscorlib {   .publickeytoken = (B7 7A 5C 56 19 34 E0 89)   .ver 1:0:3300:0 } 

The .assembly extern mscorlib metadata statement indicates that the Customer assembly makes use of, and is therefore dependent on, the standard assembly mccorlib.dll , which is required by all managed code. When an assembly makes a reference to another assembly, you will see an .assembly extern metadata statement. If you open AcmeGui in ILDASM and look at the manifest you will see several dependencies, including the Customer and Hotel assemblies as well as the System.Windows.Forms assembly.

 ... .assembly extern Hotel {   .ver 1:0:797:24817 } ... .assembly extern System.Windows.Forms {   .publickeytoken = (B7 7A 5C 56 19 34 E0 89)   .ver 1:0:3300:0 } ... .assembly extern Customer {   .ver 1:0:797:24817 ... 

The System.Windows.Forms assembly is a shared assembly, which can be seen in the \WINNT\Assembly directory using Windows Explorer, as illustrated in Figure 9-12.

Figure 9-12. Viewing shared assembly information in Windows Explorer.

graphics/09fig12.jpg

In the System.Windows.Forms shared assembly, the .publickeytoken = (B7 7A 5C 56 19 34 E0 89) metadata statement provides a public key token, which is the lowest 8 bytes of a hash of the public key that matches the corresponding private key owned by the System.Windows.Forms assembly's author. This public key token cannot actually be used directly to authenticate the identity of the author of the System.Windows.Forms . However, the original public key specified in the System.Windows.Forms manifest can be used to mathematically verify that the matching private key was actually used to digitally sign the System.Windows.Forms assembly. Since Microsoft authored System.Windows.Forms.dll , the public key token seen above is Microsoft-specific. Of course, the matching private key is a closely guarded corporate secret, and it is believed by most security experts that such a private key is, in practice, virtually impossible to determine from the public key. However, there is no guarantee that some mathematical genius will not find a back door someday!

The .publickeytoken declaration

The .publickeytoken declaration provides only the least significant 8 bytes of the SHA1 hash of the producer's public key (which is 128 bytes), which saves some space, but can still be used to verify at runtime that the assembly being loaded comes from the same publisher as the one you compiled against. Alternatively, the . publickey declaration could have been used, which provides the full public key. This would take up more space, but it makes it harder for villains to find a private key that matches the full public key.

mscorlib , which is also a shared assembly, is not deployed in the GAC. Microsoft made a single exception here because mscorlib is so closely tied with the CLR engine ( mscorwks ); it is installed in the appropriate install directory ( \WINNT\Microsoft.NET \Framework\ v1.0.3705 ), where the version number reflects the current .NET version.

As we shall see shortly, the .publickeytoken statement is only present in the client assembly's manifest if the referenced assembly has been digitally signed, and all assemblies intended for shared deployment must be digitally signed. Microsoft has digitally signed the standard .NET assemblies, such as mscorlib.dll and System.Windows.Forms.dll , with private keys belonging to them. This is why the public key token for many of those shared assemblies, seen in the \WINNT\Assembly directory using Windows Explorer, have the same value repeated. Assemblies authored and digitally signed by other vendors are signed with their own distinct private keys, and they will therefore result in a different public key token in their client assembly's manifests . Later, we will look at how you can create your own private and public key pair and digitally sign your own assemblies for deployment into the GAC.

Nonetheless, while unique, none of these digital keys can identify who the author of a particular module is. A developer of assemblies can use the signcode utility to add a digital certificate that will identify the publisher of the assembly.

The .ver 1:0:3300:0 metadata statement indicates the version of the System.Windows.Forms assembly. While these numbers have no intrinsic meaning, the Microsoft suggested format of this version specification is Major:Minor:Build:Revision. Over time, as new versions of this assembly are released, existing clients that were built to use this version will continue using this version, assuming the conventional meaning of major and minor values. Newer client programs will of course be able to access newer versions of this assembly as they become available. The old and new versions can be deployed side by side via the GAC and be simultaneously available to old and new client programs.

Now let us shift our focus to the information about the component itself in its manifest. ILDASM shows the assembly metadata in the Customer manifest:

 .assembly Customer {   ...   .hash algorithm 0x00008004   .ver 1:0:756:15183 } 

The .assembly Directive

The .assembly directive declares the manifest and specifies to which assembly the current module belongs. In this example, the .assembly directive specifies the name of the assembly to be Customer. It is this name (combined with the version number and optionally a public key) rather than the name of the DLL or EXE file that is used at runtime to resolve the identity of the assembly. Also note that if the assembly is signed, you will see the .publickey defined within the .assembly directive. It also indicates what custom attributes have been added to the metadata.

The .assembly Customer metadata statement indicates that the assembly name is Customer . Note that this is not the name of a component class within the assembly, but rather the assembly itself. Note that this assembly is not digitally signed, and therefore it does not contain a public key.

In multifile assemblies (see a later section), the manifest stores a hash of each file in the assembly. The .hash algorithm 0x00008004 metadata statement indicates that SHA1 is the hash algorithm that is to produce this hash code value. There are many hash code algorithms in existence. However, initially, only MD5 (0x000803) and SHA1 (0x000804) are supported by .NET.

Hash Algorithms

A hash algorithm is a mathematical function that takes the original data of arbitrary size as input and generates a hash code, also known as a message digest, which is a fixed- sized binary output. An effective hash function is a one-way function that is highly collision-free, with a result that is relatively small and fixed in size. Ideally, a hash function is efficient to calculate as well. A one-way function is a function that has no inverse, so that you cannot effectively reproduce the original data from the hash code value. [*] The phrase "highly collision free" means that the probability that two distinct original input data samples generate the same hash code is very small, and it is unlikely to calculate two distinct input data samples that result in the same hash code value. The well known MD5 and SHA1 hash algorithms are considered to be excellent choices for use in digital signing, and they are both supported by .NET.

[*] One way encryption codes are used to store passwords is in a passwords database. When you log in, the password you enter is encrypted and compared with what is stored in the database. If they match, you can log in. The password cannot be reconstructed from the encrypted value stored in the passwords database.

Versioning an Assembly

An assembly manifest contains the version of the assembly as well as the version of each of the assemblies that the assembly depends on. The version number of an assembly is composed of four numerical fields: Major, Minor, Build, and Revision. There are no semantics assigned to any of these fields by the CLR. Microsoft does suggest the following convention:

  • Majora change to this field indicates major incompatible changes.

  • Minora change to this field indicates minor, but incompatible changes.

  • Builda change to this field indicates a new backward compatible release.

  • Revisiona change to this field indicates a backward compatible emergency bug fix.

None of this is enforced by the CLR. You enforce this convention, or any other convention you choose, by testing assemblies for compatibility and specifying the version policy in a configuration file that we will discuss.

In the metadata for the Customer assembly, the .ver 1:0:756:15183 gives us the assembly's version: Major Version 1, Minor Version 0, Build Number 756, Revision 15183.

The version information for the manifest can be defined in the source code using the assembly attribute AssemblyVersion . This attribute (as with other global attributes) must appear in a source file after the Imports statements but before any namespace or class definitions. The AssemblyVersionAttribute class is defined in the System::Reflection namespace. If this attribute is not used, a default version number of 0.0.0.0 is listed in the assembly manifest, which is generally not desirable.

In a project created with the VisualStudio.NET project wizard, the source file named AssemblyInfo.vb is automatically generated, with a version of 1.0.* , producing a major version of 1, and a minor version of 0 and automatically generated build and revision values. If you change the AssemblyVersion to, for example, "1.1.0.0" , as shown below, the version number displayed in the manifest will be modified accordingly to 1:1:0:0 .

 <Assembly: AssemblyVersion("1.1.0.0")> 

If you specify any version number at all, you must at a minimum, specify the major number. If you only specify the major number, the remaining values will default to zero. If you also specify the minor value, you can omit the remaining fields, which will then default to zero, or you can specify an asterisk, which will provide automatically generated values. The asterisk will cause the build value to equal the number of days since January 1, 2000, and the revision value will be set to the number of seconds since midnight, divided by 2. If you specify major, minor, and build values, and specify an asterisk for the revision value, then only the revision is defaulted to the number of seconds since midnight, divided by 2. If all four fields are explicitly specified, then all four values will be reflected in the manifest. Table 9-1 shows a few valid AssemblyVersion specifications along with the resulting version number in the manifest.

Table 9-1. Examples of Version Specifications
Specified in Source Result in Manifest
none 0:0:0:0
1 1:0:0:0
1.1 1:1:0:0
1.1.* 1:1:464:27461
1.1.43 1:1:43:0
1.1.43.* 1:1:43:29832
1.1.43.52 1:1:43:52

If you use the asterisk, then the revision and/or the build number will automatically change every time you rebuild the component. You must make an explicit change to the major and minor numbers if you wish to have their values changed.

Strong Names

Before we can discuss version policy, we have to introduce the idea of a strong name. A strong name is guaranteed to be globally unique for any version of any assembly. Strong names are generated by digitally signing the assembly. This ensures that the strong name is not only unique, but it is a name that can only be generated by an individual that owns a secret private key.

A strong name is made up of a simple text name, a public key, and a hash code that has been encrypted with the matching private key. The hash code is known as a message digest, and the encrypted hash code is known as a digital signature. The digital signature effectively identifies the assembly's author and ensures that the assembly has not been altered. Two assemblies that have the same strong name and versions are considered to be identical assemblies. Two assemblies with different strong names are considered to be different. A strong name is also known as a cryptographically strong name, since, unlike a simple text name, a strong name is guaranteed to uniquely identify the assembly based on its contents and its author's private key. A strong name has the following useful properties:

  • A strong name guarantees uniqueness based on encryption technology.

  • A strong name establishes a unique namespace based on the use of a private key. [2]

    [2] Do not confuse this namespace with the namespace used by the compiler to disambiguate class names.

  • A strong name prevents unauthorized individuals from modifying the assembly.

  • A strong name prevents unauthorized individuals from versioning the assembly.

  • A strong name allows the CLR to find the right version of a shared assembly.

Digital Signatures

Digital signatures are based on public key cryptographic techniques. In the world of cryptography, the two main cryptographic techniques are symmetric ciphers (shared key) and asymmetric ciphers (public key). Symmetric ciphers use one shared secret key for encryption as well as decryption. DES, Triple DES, and RC2 are examples of symmetric cipher algorithms. Symmetric ciphers can be very efficient and powerful for message privacy between two trusted cooperating individuals, but they are generally unsuitable for digital signatures. Digital signatures are not used for privacy, but are used for identification and authentication. If you share your symmetric key with everyone who would potentially want to identify or authenticate you, you would inevitably share it with people who would want to impersonate you.

Asymmetric ciphers are used in digital signatures. [3] Asymmetric ciphers, also known as public key ciphers, make use of a public/private key pair. The paired keys are mathematically related and they are generated together. It is, however, exceedingly difficult to calculate one key from the other. The public key is typically exposed to everyone who would like to authenticate its owner. On the other hand, the owner keeps the matching private signing key secret so that no one can impersonate him or her. RSA and DSA are examples of public key cipher systems.

[3] Asymmetric ciphers are also used in shared key management, which is not discussed here.

Public key cryptography is based on a very interesting mathematical scheme that allows plain text to be encrypted with one key and decrypted only with the matching key. For example, if a public key is used to encrypt the original data (known as plain text), then only the matching private key is capable of decrypting it. Not even the encrypting key can decrypt it! This scenario is useful for sending secret messages to only the individual who knows the private key.

The opposite scenario is where the individual who owns the private key uses that private key to encrypt the plain text. The resulting cipher text is by no means a secret, since everyone who is interested can obtain the public key to decrypt it. This scenario is useless for secrecy , but very effective for authentication purposes. To improve performance, instead of encrypting the original data, a highly characteristic hash code is encrypted instead.

If you use the matching public key to decrypt the encrypted hash code, you can recalculate the hash code on the original data, and compare the two values. If the two values match, you can be certain that the owner of the private key was the digital signer. Of course, the owner of the private key has to make sure to keep the private key secret. Otherwise, you cannot prove that the data has not been tampered with from the time when it was digitally signed. Figure 9-13 shows how a digital signature works.

Figure 9-13. How a digital signature works.

graphics/09fig13.gif

Digital Signing with SHA1 and RSA

To sign the assembly, the producer calculates a SHA1 hash of the assembly (with the bytes reserved for the signature preset to zero), and then encrypts the hash value with a public key using RSA encryption. The public key and the encrypted hash are then stored in the assembly's metadata.

Digitally Signing an Assembly

The process of digitally signing an assembly involves generating a public/private key pair, calculating a hash code on the assembly, encrypting the hash code with the private key, and writing the encrypted hash code along with the public key into the assembly for all to see. The encrypted hash code and public key together comprise the entire digital signature. The digital signature is written into a reserved area within the assembly that is not included in the hash code calculation. All these steps are performed with two simple tools named the Strong Name utility ( Sn.exe) and the Assembly Linker ( Al.exe ). To build and digitally sign an assembly, the following steps are performed.

  1. Develop and build the component.

  2. Generate a public/private key pair.

  3. Calculate a hash code on the contents of the assembly.

  4. Encrypt the hash code using the private key.

  5. Place the encrypted hash code into the manifest.

  6. Place the public key into the manifest.

Step 1 is, of course, usually performed using Visual Studio .NET. Steps 2 through 6 are known as digital signing. Step 2 is accomplished using the

Strong Name utility Sn.exe . Steps 3 through 6 are accomplished using either Visual Studio .NET or the Assembly Linker utility Al.exe (that's A-el , not A-one ).

graphics/codeexample.gif

To illustrate this process, we will develop a version of our Customer and Hotel assemblies that have strong names. They are located in the SignedCaseStudy directory. We generate key pairs for the assemblies using Sn.exe . This tool generates a cryptographically strong name for the assembly. You generate a public-private key pair and place them into a file named KeyPair.snk as shown in the following command. You can run Sn.exe in the Hotel and Customer source directories to generate the key files together with the source code for each assembly. In the example provided, a distinct key file is created for each of the Hotel and Customer assemblies. You could just as easily create one key file and use it in both assemblies.

 sn -k KeyPair.snk 

The resulting KeyPair.snk file is a binary file and is not intended to be human readable. If you are curious , you can write these keys into a comma-delimited text file with the following command, and then view it using Notepad.exe . This is not a required step.

 sn -o KeyPair.snk KeyPair.txt 

In the example you will find these files in the SignedCaseStudy\Hotel and SignedCaseStudy\Customer subdirectories.

The next step is to apply the private key to each assembly. For developing and testing, it is convenient to do this at compilation time. When you release the assembly, however, you have to use the official private key of the company. For security reasons, this key is probably only known to the corporate digital signing authority. The process of creating the strong name cannot be postponed until after the assembly is built because the public key is part of the assembly's identity. Users of the assembly have to compile against the full identity of the assembly. Delay signing, which splits the process of assigning the strong name into two steps, is designed to solve this problem.

If you just want to apply the digital signature automatically at compile time without delay signing, you simply use the AssemblyKeyFileAttribute , which in the example is in the AssemblyInfo.vb files of the Customer and Hotel projects. The KeyPair.snk files generated previously with the Sn.exe tool is specified in the attribute. The file path can be absolute, or it can be relative to the project output directory. Once the appropriate KeyPair.snk file has been added to the AssemblyKeyFile attribute in each assembly project, the code must be recompiled.

 graphics/codeexample.gif <Assembly: AssemblyKeyFile(_  "C:\OI\NetVb\Chap09\SignedCaseStudy\Hotel\KeyPair.snk")> <Assembly: AssemblyKeyFile(_ "C:\OI\NetVb\Chap09\SignedCaseStudy\Customer\KeyPair.snk")> 

Delayed signing requires a more complex procedure. When you build the assembly, the public key is supplied to the compiler so that it can be put in to the PublicKey field in the assembly's manifest. Space is reserved in the file for the signature, but the signature is not generated. When the actual signature is generated, it is placed in the file with the -R option to the Strong Name utility ( Sn.exe ).

To indicate to the compiler that you want to use delay signing, you include AssemblyDelaySign attribute in your source code. You also have to include the public key, using the AssemblyKeyFile attribute.

Assuming you have generated the public/private key pair as described previously, you then use the -p option of the Strong Name utility to obtain just the public key without giving out the still secret private key.

 sn -p KeyPair.snk PublicKey.snk 

You then add the following two attributes to AssemblyInfo.vb:

 <Assembly: AssemblyDelaySign(true)> <Assembly: AssemblyKeyFile("C:\...\PublicKey.snk")> 

The assembly still does not have a valid signature. You will not be able to install it into the GAC or load it from an application directory. You can disable signature verification of a particular assembly by using the -Vr option on the Strong Name utility.

 sn -Vr Customer.dll 

Before you ship the assembly, you must supply the valid signature. You use the -R option on the Strong Name utility and supply the public/private key pair.

 sn - R customer.dll KeyPair.snk 

Regardless of whether or not you choose to delay signing, once you have actually digitally signed an assembly, if you look at the manifest in ILDASM, you will see that the .publickey entry has been added to the assembly's metadata.

The .publickey attribute represents the originator's public key that resides in the corresponding KeyPair.snk file. This is the public key that can be used to decrypt the message digest to retrieve the original hash code. When the assembly is deployed into the GAC, this decrypted hash code is compared with a fresh recalculation of the hash code from the actual assembly contents. This comparison is made to determine if the assembly is legitimate (i.e., identical to the original) or illegitimate (i.e., corrupt or tampered). Of course, when you use Sn.exe , it will produce a different key pair, and the public key shown below will be different in your case accordingly.

If you use ILDASM to examine the manifest of the AcmeGui client program, you will see the following:

 .assembly extern  Customer  {  .publickeytoken = (EE FE D1 EB 3C AD 9E 15)  .ver 1:0:756:20269 } ... .assembly extern  Hotel  {  .publickeytoken = (37 6E 80 FB 8D 30 1E B2)  .ver 1:0:756:20146 } 

Now that Customer and Hotel have strong names, references to them have a public key token, which is a hash of the public key that matches the corresponding private key for the assembly. Note that we generated different keys for each assembly. Usually, each company will use the same key pair for all its public components.

Now that we have discussed strong names, we can discuss the two methods of deploying assemblies in .NET and their associated default version policies. After this discussion, we will show how the default policy can be overridden in a configuration file.


Team-Fly    
Top
 


Application Development Using Visual BasicR and .NET
Application Development Using Visual BasicR and .NET
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 190

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net