Authen-NZRealMe

 view release on metacpan or  search on metacpan

lib/Authen/NZRealMe/XMLEnc.pm  view on Meta::CPAN

    $xc->registerNs( NS_PAIR('xenc') );
    $xc->registerNs( NS_PAIR('ds') );

    while(@namespaces) {
        my $prefix = shift @namespaces;
        my $uri    = shift @namespaces;
        $xc->registerNs($prefix, $uri);
    }

    return $xc;
}


sub _decrypt_one_encrypted_data_element {
    my($self, $xc, $ed_node) = @_;

    my $algorithm_uri = $xc->findvalue(
        './xenc:EncryptionMethod/@Algorithm', $ed_node
    );
    my $algorithm = $self->_find_enc_alg($algorithm_uri);

    my $key = eval {
        $self->_extract_encrypted_key($xc, $ed_node);
    } or do {
        die "Error decrypting <KeyInfo> for"
          . " algorithm=$algorithm->{name} error: $@";
    };

    my $b64_value = $xc->findvalue(
        './xenc:CipherData/xenc:CipherValue', $ed_node
    ) or die "Unable to find <CipherData><CipherValue> for <EncryptedData>";
    my $ciphertext = decode_base64($b64_value);
    my $plaintext = eval {
        $self->_decrypt($algorithm, $key, $ciphertext);
    } or do {
        die "Error decrypting <EncryptedData> using"
          . " algorithm=$algorithm->{name} error: $@";
    };
    return $plaintext;
}


sub _extract_encrypted_key {
    my($self, $xc, $ed_node) = @_;

    my($key_info) = $xc->findnodes('./ds:KeyInfo', $ed_node)
        or die "Unable to find <KeyInfo> in <EncryptedData> element";
    my($encrypted_key) = $xc->findnodes('./xenc:EncryptedKey', $key_info)
        or die "Unable to find <EncryptedKey> in <KeyInfo> element";

    my $algorithm_ns = $xc->findvalue(
        './xenc:EncryptionMethod/@Algorithm', $encrypted_key
    ) or die "Unable to find Algorithm in <EncryptedKey>";
    my $algorithm = $self->_find_enc_alg($algorithm_ns);

    my $b64_value = $xc->findvalue(
        './xenc:CipherData/xenc:CipherValue', $encrypted_key
    ) or die "Unable to find <CipherData><CipherValue> for <EncryptedKey>";
    my $ciphertext = decode_base64($b64_value);

    return $self->_decrypt($algorithm, $self->rsa_private_key, $ciphertext);
}


sub _encrypt_bytes {
    my($self, $algorithm, @args) = @_;

    my $method = $algorithm->{encrypt_method}
        or die "no encrypt_method for $algorithm->{name}";

    $self->$method(@args);
}


sub rsa_private_key {
    my($self) = @_;

    return Crypt::OpenSSL::RSA->new_private_key($self->key_text);
}


sub rsa_public_key {
    my($self) = @_;

    return Crypt::OpenSSL::RSA->new_public_key($self->pub_key_text);
}


sub pub_key_text {
    my($self) = @_;

    return $self->{pub_key_text} if $self->{pub_key_text};

    my $cert_text = $self->pub_cert_text();
    my $x509 = Crypt::OpenSSL::X509->new_from_string($cert_text);
    $self->{pub_key_text} = $x509->pubkey();

    return $self->{pub_key_text};
}


sub pub_cert_text {
    my($self) = @_;

    return $self->{pub_cert_text} if $self->{pub_cert_text};
    my $path = $self->{pub_cert_file}
        or croak "signing cert must be set with 'pub_cert_file' or 'pub_cert_text'";

    $self->{pub_cert_text} = $self->_slurp_file($path);

    return $self->{pub_cert_text};
}


sub key_text {
    my($self) = @_;

    return $self->{key_text} if $self->{key_text};

    my $path = $self->{key_file}
        or croak "signing key must be set with 'key_file' or 'key_text'";

    $self->{key_text} = $self->_slurp_file($path);

    return $self->{key_text};
}


sub _slurp_file {
    my($self, $path) = @_;

    local($/) = undef;
    open my $fh, '<', $path or die "open($path): $!";
    my $text = <$fh>;

    return $text;
}

lib/Authen/NZRealMe/XMLEnc.pm  view on Meta::CPAN


sub _key_gen_aes128cbc {
    my($self) = @_;

    my $aes128_key  = random_bytes(Crypt::Cipher::keysize('AES'));
    my $iv          = random_bytes(Crypt::Cipher::blocksize('AES'));
    return {
        key   => $aes128_key,
        iv    => $iv,
    };
}


sub _decrypt_aes256cbc {
    my($self, $aes256_key, $ciphertext) = @_;

    my $cipher    = 'AES';
    my $padding   = 0; # no padding - we handle that below
    my $blocksize = Crypt::Cipher::blocksize($cipher);
    my $iv        = substr($ciphertext, 0, $blocksize, '');
    my $cbc = Crypt::Mode::CBC->new($cipher, $padding);
    my $plaintext = $cbc->decrypt($ciphertext, $aes256_key, $iv);
    return $self->_strip_padding($plaintext, $blocksize);
}


sub _encrypt_aes256cbc {
    my($self, $key_info, $plaintext) = @_;

    my $cipher     = 'AES';
    my $padding    = 0; # no padding - we handle that below
    my $blocksize  = Crypt::Cipher::blocksize($cipher);
    my $aes256_key = $key_info->{key} or die "No key in key_info";
    my $iv         = $key_info->{iv}  or die "No iv in key_info";
    $plaintext     = $self->_add_padding($plaintext, $blocksize);
    my $cbc = Crypt::Mode::CBC->new($cipher, $padding);
    my $ciphertext = $cbc->encrypt($plaintext, $aes256_key, $iv);
    return $ciphertext;
}


sub _key_gen_aes256cbc {
    my($self) = @_;

    my $aes256_key  = random_bytes(Crypt::Cipher::keysize('AES'));
    my $iv          = random_bytes(Crypt::Cipher::blocksize('AES'));
    return {
        key   => $aes256_key,
        iv    => $iv,
    };
}

1;

__END__

=head1 SYNOPSIS

  my $decrypter = Authen::NZRealMe->class_for('xml_encrypter')->new(
      pub_cert_file => $self->signing_cert_pathname,
      key_file      => $path_to_private_key_file,
  );

  my $xml = $decrypter->decrypt_encrypted_data_elements($xml);

=head1 METHODS

=head2 new( )

Constructor.  Generally called indirectly via the
L<Authen::NZRealMe::ServiceProvider/resolve_posted_assertion> method, which
does so like this:

  Authen::NZRealMe->class_for('xml_encrypter')->new( options );

Options are passed in as key => value pairs.

For decryption, the Service Provider's RSA signing private key must be passed
to the constructor using either the C<key_text> or the C<key_file> option.
This key is used to decrypt the random key used by the block cipher to encrypt
the assertion.

In normal use (consuming assertions from the RealMe service), this module is
never called upon to perform encryption.  It does include an implementation of
encryption for use by the test suite.  When creating an encrypted assertion,
two rounds of encryption are performed.  First, an AES key is generated at
random and used by the block cipher to encrypt the assertion.  Next, the AES
key is encrypted using the Service Provider's RSA public key and the result is
included along with the encrypted assertion.  See the test suite for more
details.

=head2 decrypt_encrypted_data_elements( $xml )

Takes an XML document (as a string) and returns a modified version (also as a
string) in which all C<< <EncryptedData> >> elements are replaced with the
unencrypted document fragment.

=head2 encrypt_one_element

Currently only needed by the test suite, which calls it like this:

  my $encrypted_xml = $encrypter->encrypt_one_element($signed_xml,
      algorithm => 'xenc_aes128cbc',
      target_id => $target_id,
  );

Returns a new XML string in which one element from the supplied XML document
has been replaced with an C<< <EncryptedData> >> element.

=head2 id_attr

An accessor method for the attribute name used by C<encrypt_one_element> to
find the target element to be encrypted.  The default name is 'ID', and can be
overriden by passing a new value for the 'id_attr' option to the constructor.

=head2 key_text

An accessor for the PEM-encoded text used to instantiate an RSA private key
object for decryption.  The value can be supplied directly using the
'key_text' argument to the constructor, or indirectly with the 'key_file'
argument.

=head2 pub_cert_text

An accessor for the PEM-encoded text used to instantiate an X509 certificate
which in turn is used to create an RSA public key object for encryption.  The
value can be supplied directly using the 'pub_cert_text' argument to the
constructor, or indirectly with the 'pub_cert_file' argument.

=head2 pub_key_text

An accessor for the PEM-encoded text used to instantiate an RSA public key
object for encryption.  The value can be supplied directly using the
'pub_key_text' argument to the constructor, or indirectly with the
'pub_cert_text' or 'pub_cert_file' arguments.

=head2 register_encryption_algorithm

Used at module-load-time to register handler methods for each supported
encryption algorithm - i.e.: the method is called once per algorithm.  A
sub-class which added support for addition algorithms would need to ensure that
this routine is called for each.

=head2 rsa_private_key

Accessor method which returns a L<Crypt::OpenSSL::RSA> private key object
using the C<key_text> method.

=head2 rsa_public_key

Accessor method which returns a L<Crypt::OpenSSL::RSA> public key object
using the C<pub_key_text> method.

=head1 SUPPORTED ALGORITHMS

=head2 rsa15 - RSAES-PKCS1-v1_5

Used only to encrypt/decrypt the random key which in turn is used by the block
cipher to encrypt the XML element data. This is used by the old RealMe service to
encrypt the random key.

=head2 aes128cbc - AES128-CBC

This is the oold supported block cipher used for the encryption/decryption of
XML elements.  Whilst it is recognised that use of this cipher is not
recommended due to concerns about its security, it is the cipher used by the
RealMe service to encrypt SAML assertions.

=head2 rsa_oaep_mgf1p - RSA-OAEP-MGF1P

Used only to encrypt/decrypt the random key which in turn is used by the block
cipher to encrypt the XML element data. This is used by the new RealMe service to
encrypt the random key.

=head2 aes256cbc - AES256-CBC

This is the supported block cipher used for the encryption/decryption
of XML elements.  This is the cipher used by the new RealMe service to
encrypt SAML assertions.

=head1 SEE ALSO

See L<Authen::NZRealMe> for documentation index.


=head1 LICENSE AND COPYRIGHT

Copyright (c) 2010-2022 Enrolment Services, New Zealand Electoral Commission

Written by Grant McLean E<lt>grant@catalyst.net.nzE<gt>

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.

=cut




( run in 2.518 seconds using v1.01-cache-2.11-cpan-5735350b133 )