Monthly Archives: August 2014

Enable HTTPS on Zend Server for IBM i (SSL Cert setup)

Enabling application on IBMi to use a SSL certificate

There’s two common setups for using SSL certicates.

1) The $0 way of using self-signed certificate or

2) the $99 – $X,XXX way of using an internet certificate authority

I’ll go through both ways of accomplishing either

Option 1 : Creating and applying a Self Signed Certificate

Go to your IBMi’s Digital Certificate Manager website at:

http://REPLACE_WITH_YOUR_IBM_I_IP_ADDRESS_OR_DNS_NAME:2001/QIBM/ICSS/Cert/Admin/qycucm1.ndm/main0

(Change REPLACE_WITH_YOUR_IBM_I_IP_ADDRESS_OR_DNS_NAME to your IBM i IP or DNS Name)

Note: If you can’t access port 2001 make sure the admin instance of apache is running.

STRTCPSVR SERVER(*HTTP) HTTPSVR(*ADMIN)

1. Select Certificate store: *SYSTEM

2. Create cert for the DNS name application (i.e. myapp.example.com)

3. Assign cert to QIBM_HTTP_SERVER_ZENDSVR If you haven’t already you may have to create DNS entries for your app

4. Modify your httpd.conf file (also known as apache config) to listen on port 443 (ssl) (Replace 10.1.1.200 with your actual ip address)

Then you’ll have to restart your web server so the apache config is reloaded by going to http://MY_IBMi_IP_DNSNAME:2001/HTTPAdmin and pressing the restart button on your http instance of ZS

Option 2 : Using a VeriSign or Internet Certificate Authority (CA) SSL Certificate

The self sign certifcate has a limitation that it isn’t as trusted as one from VeriSign. Your browser may give you a warning like:

This Connection is Untrusted You have asked Firefox to connect securely to http://www.yoursite.com, but we can’t confirm that your connection is secure. http://www.yoursite.com uses an invalid security certificate. The certificate is not trusted because the issuer certificate is unknown. (Error code: sec_error_unknown_issuer)

In this case you’ll go to

  1. Review and compare SSL Certificates (http://www.whichssl.com/compare-ssl-certificates.html)
  2. Purchase a cert per subdomain (secure.mysite.com) OR get a wildcard SSL certificate (*.mysite.com) if you have many apps you want to use ssl with.
  3. Create certificate signing request. Copy the CSR encrypted data from IBMi DCM and paste it into a CA vendors site (i.e. comodo, symantec, thawted).  (Steps to get a CSR https://www.sslsupportdesk.com/certificate-signing-request-csr-instructions-for-ibm-as400-iseries/)
  4. CA Vendor should give you a) your Server Certificate, b) Intermediate Certs c) and Root CA Cert
  5. Download the certs to your computer and upload them to the IFS (record where on the IFS you uploaded the certs as this is needed later) and go back to the DCM.
  6. Import root and/or intermediate certificates: *SYSTEM certificate type Certificate authority (CA), Import File: crt file on the IFS of your root and/or intermediate certificates.  (Note this gotcha “An error occurred during certificate validation. The issuer of the certificate may not be in the certificate store or the issuer may not be enabled.” – http://www-01.ibm.com/support/docview.wss?uid=nas8N1011678)
  7. Import your Server certificate “Import Certificate”, Certificate Store: *SYSTEM, System or client, Import File: crt file on the IFS of your server cert
  8. Assign the certificate to your application

Different types of SSL Certs

Domain Validated Certificates (DV): verifies the owner of the domain for the certificate.
Organization Validated Certificates (OV): verifies an established organization includes company name and its address.
Extended Validated Certificates (EV): verifies an extensive review of the company was done by the certificate authority following the standards of Certificate Authority/Browser (CA/B) Forum. The browser url bar turns green

Notes:

The X509 standard and TLS

Debugging Tip- Make sure you are using the right root, and intermediate certificates

Example of Thawte certificate chain

ssl certificate hierarchy

SSL on 5250 emulator connection

When you first connect to the IBMi via SSL it downloads the SSL cert set on your IBMi . “The following certificate authority was discovered during SSL negotiations: Would you like to add this certificate authority to your trusted set?”

Chrome 58+ issue – Common Name Support Removed

Seeing this error on your chrome browser:

Your connection is not private

Attackers might be trying to steal information from (for example, passwords, messages, or credit cards). NET::ERR_CERT_COMMON_NAME_INVALID

This server could not prove that is is l its security certificate is from [missing_subjectAltName].  This may be caused by a misconfiguration or an attacker intercepting your connection.

Its probably that your missing the SANs field in your cert.  In the latest version of Chrome 58 they are no longer supporting Common Name field of an SSL Certificate.  This is the domain name like godzillai5.wordpress.com.  You’ll now need to have the domain name listed in the SAN (Subject Alternative Name) field.  Most Public CAs have been populating this field so if you bought a cert this won’t be much of an issue but if you have a self-signed cert or private PKIs you’ll need to re-issue your cert with the SAN field added.

How to create the san field?  Check this out: https://geekflare.com/san-ssl-certificate/

The main command is

openssl req -out sslcert.csr -newkey rsa:2048 -nodes -keyout private.key -config san.cnf

but you’ll need to set up the .cnf file that blog goes over

Advertisements

PHP Security on the IBM i – Locking down the IFS permissions – Best way to handle authorities in the web root and subdirectories.

Are you unable to modify another user’s PHP file on the IBM i? Do you constantly need to give QTMHHTTP read permissions to the new PHP you uploaded? After going through this guide you’ll fix these issues and streamline your PHP development on the IBM i while maintaining security of the IFS.

Overview of permissions on your Webroot folder for Zend Server:
*PUBLIC: DTAAUT ( *NONE)
PRIMARY GROUP: NOGROUP = DTAAUT (*RX)
OWNER: WEBCODERS= DTAAUT (*RWX)

Summary: Public gets no access, Primary Group is a group that the user QTMHHTTP is a part of and only has read access, and the owner is your development team group profile (WEBCODERS) with your web development team user profiles in that group.

As always test this on a development machine DON’T DO THIS IN PRODUCTION unless you’ve tested it

5 Steps to secure your PHP installation

1. Don’t give *PUBLIC access

CHGAUT OBJ('/www/zendsvr/htdocs/') USER(*PUBLIC) DTAAUT(*NONE) OBJAUT(*NONE) SUBTREE(*ALL)

I’ve heard many people who are insecurely using PHP on the IBM i. If you are giving *PUBLIC any access to your files under /www/zendsvr/htdocs you are giving too much access. You don’t want anyone with access to your IBM I to read your PHP files or your configuration files with database username and password. You should make sure *PUBLIC has no data authorities on all files under web root directory using the CHGAUT command recursively.

2. Set the Primary Group on the webroot (/www/zendsvr/htdocs) to a group with QTMHHTTP in it

CHGPGP OBJ('/www/zendsvr/htdocs') NEWPGP(NOGROUP) RVKOLDAUT(*NO) SUBTREE(*ALL)
CHGAUT OBJ('/www/zendsvr/htdocs/') USER(NOGROUP) DTAAUT(*RX) OBJAUT(*NONE) SUBTREE(*ALL)

Remember that each new object under a parent directory inherits the *PUBLIC authority, primary group authority and the owner authority of the parent. So you’ll want to set the primary group to NOGROUP and give it Read access. Make sure QTMHHTTP is a user of this group. This is the user that PHP is using to access the files and is typically called APACHE or NOBODY in linux systems.

3. Give access to a development team group profile so your web developers have write access to create new files and directories and read access to view the files on the server.  Unfortunately you’ll always have to re-run CHGAUT for WEBCODERS as when someone uploads a file they become the owner.  You may want to consider a daily job that automatically runs this or have your developers share the login information for WEBCODERS and always upload with that profile. 

CHGOWN OBJ('/www/zendsvr/htdocs/') NEWOWN(WEBCODERS) RVKOLDAUT(*NO) SUBTREE(*ALL)
CHGAUT OBJ('/www/zendsvr/htdocs/') USER(WEBCODERS) DTAAUT(*RWX) OBJAUT(*ALL) SUBTREE(*ALL)

4. Give write permissions to directories that QTMHHTTP needs write access.  If your PHP is saving a file or creating a file to the IFS it will need write permissions to that directory. 

CHGAUT OBJ('/www/zendsvr/writeable/uploads') USER(QTMHHTTP) DTAAUT(*RWX) OBJAUT(*NONE) SUBTREE(*NO)

Below are some shell functions you can use if you have bash or bourne shell

5. (Optional) Set the umask to set the default permissions given to new files created by a program (like FTP, SFTP, SSH).  In the example below the first 0 means give user rwx, 2 means give group rx, and 7 means give other nothing.

umask 027
#u=rwx,g=rx,o=

For SFTP you’d modify sshd_config to load a shell .profile that would then run the umask.  (replace * with the version number of openssh you’re using or find it by running find)

find / | grep sshd_config
vi /QOpenSys/QIBM/UserData/SC1/OpenSSH/openssh-*.*p*/etc/sshd_config
# set ibmpaseforishell to your favorite shell (in this case bash)
ibmpaseforishell=/QOpenSys/opt/freeware/bin/bash
#set the umask in our .profile so it will always load by using this command to append 
umask 022 to the end of the file (.profile).
echo "umask 022" >> ~/.profile

#shout out to @aaronbartell for informing me of umask

Experiment using Authorization List: 

Now I looked into using Authorization lists but they don’t inherit from the parent directory IF you’re using the mkdir command API (different from ibm i command line mkdir alias).  That would be the best case scenario since then new objects would get the WEBDEVAUTL authorization list inherited and your development team would be in that list and everyone on your team could create new files and directories and everyone else could modify them later.  Below are the commands to create a AUTL, but remember it will only work if your NOT using the mkdir command

CRTAUTL AUTL(WEBDEVAUTL) TEXT(‘Auth List for Web Developers’)
ADDAUTLE AUTL(WEBDEVAUTL) USER(WEBDEV1 WEBDEV2) AUT(*ALL)
CHGAUT OBJ(‘/www/zendsvr/htdocs/’) AUTL(WEBDEVAUTL) DTAAUT(*RWX) OBJAUT(*ALL) SUBTREE(*ALL)

More info on Mkdir not inheriting from the parent directory here: https://www.ibm.com/developerworks/community/forums/html/topic?id=77777777-0000-0000-0000-000014510624 .  Hopefully IBM will one day have mkdir have the same functionality as CRTDIR CL command.  Another good read about the IFS: http://publib.boulder.ibm.com/iseries/v5r1/ic2924/books/c415300522.htm

The “gotchas” of running Zend Framework 2 on older versions of Zend Server for IBM i – ZF2 skeleton Application

Have you tried the ZF2 skeleton Application on PHP on the IBM i and have run into issues getting it to work since your on ZS 5.5, 6.x and are unable to upgrade to ZS7 at the moment? Well here’s a few gotchas and things you’ll have to change to get it work correctly:


 

Fatal error: Can’t inherit abstract function Zend\Validator\Translator\TranslatorInterface::translate() (previously declared abstract in Zend\I18n\Translator\TranslatorInterface) in library/Zend/Mvc/I18n/Translator.php on line 19

1) Comment out the Translate method in i89n\Translator\TranslatorInterface.php

//public function translate($message, $textDomain = 'default', $locale = null);
//For some reason ZF2 has 2 methods with the same name but it works in newer versions of PHP

 

Zend\ServiceManager\Exception\ServiceNotFoundException

zendframework/library/Zend/ServiceManager/ServiceManager.php:529

Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Zend\Db\Adapter\Adapter

2) set your config_glob_paths to the full path of the config/application.config.php. Note: the path below has to be specific to where your file is located on the IFS.

'config_glob_paths' => array(
      '/www/zendsvr/htdocs/zf2test/config/autoload/{,*.}{global,local}.php',
),
//AFter updating to ZS 7 you should be able to change this back to the relative path of config/autoload/{,*.}{global,local}.php

 

“album” in MYLIB type *FILE not found. SQLCODE=-204

3) Add ‘platform_options’ to your config/autoload/global.php

//The SQL that is generated by Zend's classes is putting quotes around the table like:
//SELECT 'album'.* FROM 'album'
//after turning off quote_identifiers it will run this SQL:
//SELECT album.* FROM album
return array(
     'db' => array(
         'driver'           => 'IbmDb2',
         'platform'         => 'IbmDb2',
         'platform_options' => array('quote_identifiers' => false),
         'database'         => '*LOCAL', // IBM i serial number or db directory entry name goes here
         'persistent' => true,
        'driver_options'    => array(
            'autocommit'     => DB2_AUTOCOMMIT_OFF, //::TODO:: We might not be able to use the constant since db2 may have not loaded at this point
            'i5_naming'     => DB2_I5_NAMING_ON, //::TODO:: We might not be able to use the constant since db2 may have not loaded at this point
            'i5_lib'        => 'MYLIB',
            'i5_libl'       => 'MYLIB MYLIB2 MYLIB3',
            ),
     ),
     'service_manager' => array(
         'factories' => array(
             'Zend\Db\Adapter\Adapter'
                     => 'Zend\Db\Adapter\AdapterServiceFactory',
         ),
     ),
 );

 

You’re view doesn’t display titles or artists

4) This is because the DB2 is case-insensitive and automatically converts lower-case column names that were created in the CREATE TABLE ALBUM script to upper-case columns (i.e. title is now TITLE and artist is now ARTIST). You can either modify the CREATE TABLE sql to force lower case column names by putting double quotes around your columns ( i.e. “title” “artist”). If you do it this way you’ll always have to put double quotes around it when using SQL, but in the PHP it won’t require double quotes. OR you can change the Album Model’s exchangeArray() to use the upper case column names. I recommend using the upper-case columns because the Zend\Db\Adapter\Driver\IbmDb2\ibmdb2 Statement class doesn’t know that it has to put double quotes around lowercase field names. The only approach with this is that you’ll have to change the field names to uppercase throughout the Getting Started Example

Solution 1

/* Creating table MYLIB.ALBUM */
DROP Table MYLIB.ALBUM;
CREATE TABLE MYLIB.ALBUM (
    'id' INTEGER GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1, NO ORDER, NO CYCLE, NO MINVALUE, NO MAXVALUE, CACHE 20) NOT HIDDEN ,
    'artist' VARCHAR (100) NOT NULL NOT HIDDEN ,
    'title' VARCHAR (100) NOT NULL NOT HIDDEN ,
    PRIMARY KEY ('id')
) NOT VOLATILE ;

LABEL ON TABLE MYLIB.ALBUM IS 'Test table for ZF2' ;

 INSERT INTO MYLIB.ALBUM ('artist', 'title')
     VALUES  ('The  Military  Wives',  'In  My  Dreams');
 INSERT INTO MYLIB.ALBUM ('artist', 'title')
     VALUES  ('Adele',  '21');
 INSERT INTO MYLIB.ALBUM ('artist', 'title')
     VALUES  ('Bruce  Springsteen',  'Wrecking Ball (Deluxe)');
 INSERT INTO MYLIB.ALBUM ('artist', 'title')
     VALUES  ('Lana  Del  Rey',  'Born  To  Die');
 INSERT INTO MYLIB.ALBUM ('artist', 'title')
     VALUES  ('Gotye',  'Making  Mirrors');

GRANT SELECT,INSERT,UPDATE,DELETE ON MYLIB.ALBUM TO MYUSER;
SELECT * FROM MYLIB.ALBUM

Solution 2

<?php
namespace Album\Model;

 class Album
 {
     public $id;
     public $artist;
     public $title;

     public function exchangeArray($data)
     {
     //SOLUTION 2 - change the PHP file to use the upper-case column names
         $this->id     = (!empty($data['ID'])) ? $data['ID'] : null;
         $this->artist = (!empty($data['ARTIST'])) ? $data['ARTIST'] : null;
         $this->title  = (!empty($data['TITLE'])) ? $data['TITLE'] : null;
     }

	 public function getInputFilter()
     {
         if (!$this->inputFilter) {
             $inputFilter = new InputFilter();

             $inputFilter->add(array(
                 'name'     => 'ID',
                 'required' => true,
                 'filters'  => array(
                     array('name' => 'Int'),
                 ),
             ));

             $inputFilter->add(array(
                 'name'     => 'ARTIST',
                 'required' => true,
                 'filters'  => array(
                     array('name' => 'StripTags'),
                     array('name' => 'StringTrim'),
                 ),
                 'validators' => array(
                     array(
                         'name'    => 'StringLength',
                         'options' => array(
                             'encoding' => 'UTF-8',
                             'min'      => 1,
                             'max'      => 100,
                         ),
                     ),
                 ),
             ));

             $inputFilter->add(array(
                 'name'     => 'TITLE',
                 'required' => true,
                 'filters'  => array(
                     array('name' => 'StripTags'),
                     array('name' => 'StringTrim'),
                 ),
                 'validators' => array(
                     array(
                         'name'    => 'StringLength',
                         'options' => array(
                             'encoding' => 'UTF-8',
                             'min'      => 1,
                             'max'      => 100,
                         ),
                     ),
                 ),
             ));

             $this->inputFilter = $inputFilter;
         }

         return $this->inputFilter;
     }
 }

class AlbumForm extends Form
 {
     public function __construct($name = null)
     {
         // we want to ignore the name passed
         parent::__construct('album');

         $this->add(array(
             'name' => 'ID',
             'type' => 'Hidden',
         ));
         $this->add(array(
             'name' => 'TITLE',
             'type' => 'Text',
             'options' => array(
                 'label' => 'Title',
             ),
         ));
         $this->add(array(
             'name' => 'ARTIST',
             'type' => 'Text',
             'options' => array(
                 'label' => 'Artist',
             ),
         ));
         $this->add(array(
             'name' => 'submit',
             'type' => 'Submit',
             'attributes' => array(
                 'value' => 'Go',
                 'id' => 'submitbutton',
             ),
         ));
     }
 }

Fatal error: Can’t inherit abstract function Zend\Form\LabelAwareInterface::setLabel() (previously declared abstract in Zend\Form\ElementInterface) in /www/zendsvr/htdocs/zf2test/vendor/zendframework/zendframework/library/Zend/Form/Element.php on line 21

5) Comment out setlabel and getlabel in Zend/Form/View/LabelAwareInterface.php – https://github.com/zendframework/zf2/issues/5996

//public function setLabel($label);
    //public function getLabel();

Doctrine is not supported for the IBM DB2

This isn’t a ZF2 issue directly, but if you try and use ZF2 plugins such as ZFCuser you’ll run into issues. Doctrine doesn’t work that well with IBM DB2 currently. The current implementation is based on DB2 for Linux. There is talk about creating new classes that would handle the IBM DB2 specifically but there hasn’t been any work on it. A conversation looking into solving this is being had at – https://github.com/doctrine/dbal/pull/518 . Novice PHP programmers remember that there’s multiple ways of solving a problem and doctrine is not a must have to get the job done.

UPDATE on Case-sensitivity – Dec 3 2014

I think some of the case sensitivity issues can be fixed if Zend Framework’s Zend\Db\Adapter\Driver\IbmDb2\ibmdb2 is updated to use QSYS2.DELIMIT_NAME which is available in V7R1 TR 8 and V7R2. Also doctrine could be used if they used that feature. Not everyone is on that Version of the OS so this would need to be a feature that should be explicitly set. More info on QSYS2.DELIMIT_NAME – http://www.itjungle.com/fhg/fhg111214-story01.html