# -*- mode: cperl; -*-
#
# This fragment is inserted into the Makefile.PL to ensure that signature verification works.
# It has been noted that old versions of OpenSSL will satisfy the Crypt::OpenSSL::xx modules,
# but not support verification.  The hex strings are CSRs and hash values that should verify.
# Hex is used for portability and readability.
#
# Here, if a support module is present, it is tested and must work.
# All modules are optional - but at least one should be present.
{
    my $avail = 0;

    print STDERR ( "Checking crypto modules' operation with OpenSSL library ..." );

    my( @nover, @failtext );

    my $ok = eval {
        require Crypt::OpenSSL::DSA;
    };
    if( $ok ) {
        print STDERR ( " DSA" );
        eval {
            my $key = Crypt::OpenSSL::DSA->read_pub_key_str( << '_KEY_' ) or die "Can't read DSA public key";
-----BEGIN PUBLIC KEY-----
MIIBtzCCASwGByqGSM44BAEwggEfAoGBAOs6x6eSjwoque9hKIz94RwT6TLThTgD
2uslWeipGrydxIV3GVpHECbvJ3QfJOYNk6QlBvFs2L1a69v1GbW6o+ZHBITDw3kP
/JtWF/vThUXNB/9g2nhGODyEjwq0R6x+1dzTUTLYguAyafNpQzDUEpLZLkRyQp/6
DiUU7DXqlu4tAhUAsqEwY1v+Gduz5J2PXEuugmYSYBkCgYEA0qgvsy8wOqt8VUyR
CW0jPNPoeyyeICFypSBseiKKORlVBPz2JmdI6hohLO9rljK9wgEqdmh1yTM099rM
JP727RHBha9QKyNmN7/bP4+rHeK0vCa0XVu2FxuMFp7Kd5d7W0ucnKffQFLHcXvY
hduUNtCYKWWeiG3jUXPaU6FreNcDgYQAAoGAFVWpRu48Az2VrgwuYohcT9u9HlEb
xj9g9cYD8Jb/Hg9uaH3z+dus1ocuqnVgsaGRzBvlHYOwx8PYrvkebeuryShmpgOp
Zcs55fpyS9n7ML9XYFlSWnE4p1jOlHM6Am1XwZkGQ/0wCVcWpFNLNnlEIEPaic7F
Gs0wfthYSf4FK+I=
-----END PUBLIC KEY-----
_KEY_
            return $key->verify( pack( 'H*', 'a1d525953bc995b5c2568d5797b0e7525929bb7a46db1187c62d4ea3a040b746' ),
                                 pack( 'H*', '302c021439b795be2af338ae34384fa8b235b67c2bbdeeb1021471ce864c3c1a' .
                                             'f74a8eb04a32dfeea17cc0ad2ac7' ) );
        };
        if( $@ || !$ok ) {
            push @nover, 'DSA';
            chomp $@;
            push @failtext, qq{Failed to verify DSA signature: $@};
            print STDERR ( '-' );
        } else {
            ++$avail;
            print STDERR ( '+' );
        }
    } else {
        print STDERR ( " !DSA" );
    }

    $ok = eval {
        require Crypt::OpenSSL::RSA;
    };
    if( $ok ) {
        print STDERR ( " RSA" );
        eval {
            my $key = Crypt::OpenSSL::RSA->new_public_key( << '_KEY_' ) or die "Can't read RSA public key";
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4EhMEu4ppW+3LSgp/fKG
hZsEmgB9kDASa90enSMZvji0pAsAQW3FSwADQLpYC7HFEeJR4aeB7CE5xS1B4WIm
9gfRxLMCekqVHq3IjpCxAN5WjyZ5AsaUOZ0TkrJ7en8x2EeV5R1oM+5GEyv8BJ+f
lizG9Q5RHxpWIn1H1+PWD4dW2RSo/PVECmflceQQb6bmyxy+bka5Sr7WLxG95LLP
ss8zBVhlTn8nzMgrKHCFF6MzajapMItWg8vz3MpJLNVjrjp00tM3QkpkR3HM6HBN
xH5n7P8jiVh6V+OiGXgTEUpYzs0mAHG/A8l6pLLQvw4fUTECArx97nm6nohKZSij
bwIDAQAB
-----END PUBLIC KEY-----
_KEY_
            $key->use_sha256_hash;
            $key->use_pkcs1_padding;

            return $key->verify( pack( 'H*', '308201b602010030818831133011060a0992268993f22c64011916036f'.
                                             '726731173015060a0992268993f22c64011916074f70656e53534c3115'.
                                             '3013060a0992268993f22c640119160575736572733123300b06035504'.
                                             '030c04746573743014060a0992268993f22c6401010c06313233343536'.
                                             '311c301a06092a864886f70d010901160d7465737440746573742e636f'.
                                             '6d30820122300d06092a864886f70d01010105000382010f003082010a'.
                                             '0282010100e0484c12ee29a56fb72d2829fdf286859b049a007d903012'.
                                             '6bdd1e9d2319be38b4a40b00416dc54b000340ba580bb1c511e251e1a7'.
                                             '81ec2139c52d41e16226f607d1c4b3027a4a951eadc88e90b100de568f'.
                                             '267902c694399d1392b27b7a7f31d84795e51d6833ee46132bfc049f9f'.
                                             '962cc6f50e511f1a56227d47d7e3d60f8756d914a8fcf5440a67e571e4'.
                                             '106fa6e6cb1cbe6e46b94abed62f11bde4b2cfb2cf330558654e7f27cc'.
                                             'c82b28708517a3336a36a9308b5683cbf3dcca492cd563ae3a74d2d337'.
                                             '424a644771cce8704dc47e67ecff2389587a57e3a2197813114a58cecd'.
                                             '260071bf03c97aa4b2d0bf0e1f51310202bc7dee79ba9e884a6528a36f'.
                                             '0203010001a000' ),
                                 pack( 'H*', 'dc8ba14eade00b952cdaacf0f48986407f01802b3d2c626c0cea42f977'.
                                             'a39c3dbb58db01c1897fb72a3d46c811269e446c4c208f63030694216f'.
                                             '84ecca99163a60587e5bdf14075728e49e9b5e3992672021e694c1dcc2'.
                                             'a5f5ba4fb2c02de1453702ffe93ec2d9dbb3486f0a1e9cb75ee24e6f6a'.
                                             '23c4e57f8c611c5aec5de08e0a6b4ddff1d28f2ddcf5d61507f8dee490'.
                                             'deb185a20f2d52b29e43fc583d3b00b7ee9e7b54d2c4c828f7f7babf6d'.
                                             'd42d77dc2431029b61330e0b55a6dcc728f573af196fdebecdb4b78c48'.
                                             '3636e4c537fe36f69d86c5a7e024672a4504caa3a2be48e891ff4b83b4'.
                                             '815ea6972f54367466decce6be94c67aed04145392684726' ) );
        };
        if( $@ || !$ok ) {
            push @nover, 'RSA';
            chomp $@;
            push @failtext, qq{Failed to verify RSA signature: $@};
            print STDERR ( '-' );
        } else {
            ++$avail;
            print STDERR ( '+' );
        }
    } else {
        print STDERR ( '!RSA' );
    }

    $ok = eval {
        require Crypt::PK::ECC;
    };
    if( $ok ) { # ECC installed, see if it works.
        print STDERR ( " ECC" );
        eval {
            my $key = Crypt::PK::ECC->new( \<< '_KEY_' );
-----BEGIN PUBLIC KEY-----
MFowFAYHKoZIzj0CAQYJKyQDAwIIAQEHA0IABI0FB6fr9YoXkQ/isVsMRR6TvJSK
S6+3vxIE1gQ+feE5QjC++rnFEVzTzR4FmlRXiLseCDDuBjAMTz6NhxKPPdw=
-----END PUBLIC KEY-----
_KEY_
            return $key->verify_message( pack( 'H*', '30440220730d25ebe5f187c607577cc106d3141dc7f9082791'.
                                                     '4f2a6a11ebc9de6fdf1d26022042c02e4819f2c16c56181205'.
                                                     'c6c2176902f20cbfcfdc1fa82b30f79bd15d2172' ),
                                         pack( 'H*', '3081a80201003045310b300906035504061302415531133011'.
                                                     '06035504080c0a536f6d652d53746174653121301f06035504'.
                                                     '0a0c18496e7465726e6574205769646769747320507479204c'.
                                                     '7464305a301406072a8648ce3d020106092b24030302080101'.
                                                     '07034200048d0507a7ebf58a17910fe2b15b0c451e93bc948a'.
                                                     '4bafb7bf1204d6043e7de1394230befab9c5115cd3cd1e059a'.
                                                     '545788bb1e0830ee06300c4f3e8d87128f3ddca000' ),
                                         'SHA256' );
        };
        if( $@ || !$ok ) {
            push @nover, 'ECC';
            chomp $@;
            push @failtext, qq{Failed to verify ECC signature: $@};
            print STDERR ( '-' );
        } else {
            ++$avail;
            print STDERR ( '+' );
        }
    } else {
        print STDERR ( " !ECC" );
    }

    if( @nover || $ENV{CRYPT_PKCS10_DUMPCFG} ) {
        # At least one signature failed to verify
        #
        # Do not allow Makefile creation.  Provide diagnostic.
        # Exit providing status for automated testing.

        my @removed;
        unlink( $_ ) && push @removed, $_ foreach( qw/Makefile Build/ );

        my $sslver = eval {
            local $SIG{__WARN__} = sub {};

            my $text = qx/openssl version -a 2>&1/;
            return unless( $? == 0 && defined $text &&
                           $text !~ /invalid command/ );

            $text =~ s/(?msi:^WARNING:(?: can't open config file:)?[^\n]*\n)//g;
            return unless( length $text );

            # list-public-key-algorithms would be cleaner, but isn't present in
            # OpenSSL 0.9.8, which is still in use in 2016.

            if( 0 ) {
                my $algorithms = qx/openssl list-public-key-algorithms 2>&1/;
                if( $? == 0 && defined $algorithms &&
                    $algorithms !~ /invalid command/ ) {
                    $algorithms =~  s/(?msi:^WARNING:(?: can't open config file:)?[^\n]*\n)//g;
                    if( length $algorithms ) {
                        $algorithms = join( ' ',
                                            sort map { /^\s*OID:\s*(.*)$/? (qq("$1"),) : ()  }
                                            split( /\n/, $algorithms ) );
                        $text .= sprintf( "algorithms:   %s\n", $algorithms );
                    }
                }
            } else {
                my $ciphers = qx/openssl ciphers 2>&1/;
                if( $? == 0 && defined $ciphers &&
                    $ciphers !~ /invalid command/ ) {
                    $ciphers =~  s/(?msi:^WARNING:(?: can't open config file:)?[^\n]*\n)//g;
                    if( length $ciphers ) {
                        chomp $ciphers;
                        $ciphers = join( ' ', sort split( /:/, $ciphers ) );
                        $text .= sprintf( "ciphers:      %s\n", $ciphers );
                    }
                }
            }

            require Text::Wrap;

            $text =~ s/^((?:compiler|options|ciphers|algorithms):[ ]+)([^\n]*)\n/
                       $1 . Text::Wrap::wrap( "", ' ' x 20, $2 ) . "\n"/gmsexi;

            return $text;
        };
        if( defined $sslver && length $sslver ) {
            chomp $sslver;
            $sslver = sprintf( "    Installed version of OpenSSL:\n%s", $sslver );
            $sslver =~ s/\n/\n      /g;
            $sslver .= "\n";
        } else {
            $sslver = '';
        }

        # Include the following to make the test result 'na', omit for 'discard'
        #     OS unsupported
        # 'na' will produce reports not counted as FAIL; 'discard' won't report at all.

        my $diag = sprintf( qq(

    Unable to verify %s signatures.  You probably need to upgrade OpenSSL.
    You may also need to install or rebuild Crypt::OpenSSL::DSA, Crypt::PK::ECC,
    and/or Crypt::OpenSSL::RSA.  These support respectively DSA, ECC and RSA
    public keys in CSRs.  At least one must be installed.

    Note: These modules may install successfully even if OpenSSL does not support
    the corresponding encryption method.  However, they will not work.  In this
    case, you must either upgrade OpenSSL to include the encryption method or
    uninstall the Perl module.

    No Makefile will be generated for Crypt::PKCS10.

    %s\n%s), (@nover? join( ', ', @nover ): 'any'), join( "\n    ", @failtext ), $sslver );

        $diag .=  sprintf( "\nDeleted old %s\n", join( ', ', @removed ) ) if( @removed );

        warn( $diag );
        exit 0;
    }
    if( $avail ) {
        print STDERR ( " ... OK\n" );
    } else {
        warn( qq{

    No signatures can be verified because none of the recommended modules is installed.

    Please install Crypt::OpenSSL::DSA, Crypt::PK::ECC, and/or Crypt::OpenSSL::RSA.
}           );
    }
}
# ####### End of SSL Test
