XML-Enc

 view release on metacpan or  search on metacpan

lib/XML/Enc.pm  view on Meta::CPAN


our $DEBUG = 0;


# Source: https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html#sec-Alg-Block
# 5.2.1 Triple DES - 64 bit Initialization Vector (IV) (8 bytes)
# 5.2.2 AES - 128 bit initialization vector (IV) (16 bytes)

sub _assert_symmetric_algorithm {
    my $algo = shift;

    state $SYMMETRIC = {
        'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' => {
            ivsize   => 8,
            keysize  => 24,
            modename => 'DES_EDE'
        },
        'http://www.w3.org/2001/04/xmlenc#aes128-cbc' => {
            ivsize   => '16',
            keysize  => 16,
            modename => 'AES'
        },
        'http://www.w3.org/2001/04/xmlenc#aes192-cbc' => {
            ivsize   => '16',
            keysize  => 24,
            modename => 'AES'
        },
        'http://www.w3.org/2001/04/xmlenc#aes256-cbc' => {
            ivsize   => '16',
            keysize  => 32,
            modename => 'AES'
        },
        'http://www.w3.org/2009/xmlenc11#aes128-gcm' => {
            ivsize   => '12',
            keysize  => 16,
            modename => 'AES',
            tagsize  => 16
        },
        'http://www.w3.org/2009/xmlenc11#aes192-gcm' => {
            ivsize   => '12',
            keysize  => 24,
            modename => 'AES',
            tagsize  => 16
        },
        'http://www.w3.org/2009/xmlenc11#aes256-gcm' => {
            ivsize   => '12',
            keysize  => 32,
            modename => 'AES',
            tagsize  => 16
        },
    };

    die "Unsupported symmetric algo $algo" unless $SYMMETRIC->{ $algo };
    return $SYMMETRIC->{$algo}
}

sub _assert_encryption_digest {
    my $algo = shift;

    state $ENC_DIGEST = {
        'http://www.w3.org/2000/09/xmldsig#sha1' => 'SHA1',
        'http://www.w3.org/2001/04/xmlenc#sha256' => 'SHA256',
        'http://www.w3.org/2001/04/xmldsig-more#sha224' => 'SHA224',
        'http://www.w3.org/2001/04/xmldsig-more#sha384' => 'SHA384',
        'http://www.w3.org/2001/04/xmlenc#sha512' => 'SHA512',
    };
    die "Unsupported encryption digest algo $algo" unless $ENC_DIGEST->{ $algo };
    return $ENC_DIGEST->{ $algo };
}



sub new {
    my $class   = shift;
    my $params  = shift;
    my $self    = {};

    bless $self, $class;

    if ( exists $params->{ 'key' } ) {
        $self->{key} = $params->{ 'key' };
        $self->_load_key( $params->{ 'key' } );
    }
    if ( exists $params->{ 'cert' } ) {
        $self->{cert} = $params->{ 'cert' };
        $self->_load_cert_file( $params->{ 'cert' } );
    }
    if (exists $params->{'no_xml_declaration'}) {
        $self->{'no_xml_declaration'} = $params->{'no_xml_declaration'} ? $params->{'no_xml_declaration'} : 0;
    }

    my $enc_method = exists($params->{'data_enc_method'}) ? $params->{'data_enc_method'} : 'aes256-cbc';
    $self->{'data_enc_method'} = $self->_setEncryptionMethod($enc_method);

    my $key_method = exists($params->{'key_transport'}) ? $params->{'key_transport'} : 'rsa-oaep-mgf1p ';
    $self->{'key_transport'} = $self->_setKeyEncryptionMethod($key_method);

    if (exists $params->{'oaep_mgf_alg'}) {
        $self->{'oaep_mgf_alg'} = $self->_setOAEPAlgorithm($params->{'oaep_mgf_alg'});
    }
    if (exists $params->{'oaep_label_hash'} ) {
        $self->{'oaep_label_hash'} = $self->_setOAEPDigest($params->{'oaep_label_hash'});
    }

    $self->{'oaep_params'} = exists($params->{'oaep_params'}) ? $params->{'oaep_params'} : '';

    $self->{'key_name'} = $params->{'key_name'} if exists($params->{'key_name'});

    return $self;
}


sub decrypt {
    my $self = shift;
    my $xml  = shift;
    my %options = @_;

    local $XML::LibXML::skipXMLDeclaration = $self->{ no_xml_declaration };

    my $doc = XML::LibXML->load_xml(
                                    string => $xml,
                                    no_network      => 1,
                                    load_ext_dtd    => 0,
                                    expand_entities => 0
                                );

    my $xpc = XML::LibXML::XPathContext->new($doc);
    $xpc->registerNs('dsig', 'http://www.w3.org/2000/09/xmldsig#');
    $xpc->registerNs('xenc', 'http://www.w3.org/2001/04/xmlenc#');
    $xpc->registerNs('xenc11', 'http://www.w3.org/2009/xmlenc11#');
    $xpc->registerNs('saml', 'urn:oasis:names:tc:SAML:2.0:assertion');

    return $doc unless $xpc->exists('//xenc:EncryptedData');

    die "You cannot decrypt XML without a private key." unless $self->{key_obj};

    my $parser = XML::LibXML->new(
                                    no_network      => 1,
                                    load_ext_dtd    => 0,
                                    expand_entities => 0
                                );
    $self->_decrypt_encrypted_key_nodes($xpc, $parser, %options);
    $self->_decrypt_uri_nodes($xpc, $parser, %options);

    return $doc->serialize();
}

sub _decrypt_encrypted_key_nodes {
    my $self = shift;
    my $xpc = shift;
    my $parser = shift;
    my %options = @_;

    my $k = $self->_get_named_key_nodes(
        '//xenc:EncryptedData/dsig:KeyInfo/xenc:EncryptedKey',
        $xpc, $options{key_name}
    );

    $k->foreach(
        sub {
            my $key = $self->_get_key_from_node($_, $xpc);
            return unless $key;
            my $encrypted_node = $_->parentNode->parentNode;
            $self->_decrypt_encrypted_node($encrypted_node,
                $key, $xpc, $parser);
        }
    );
}

sub _decrypt_uri_nodes {
    my $self = shift;
    my $xpc  = shift;
    my $parser = shift;
    my %options = @_;

    my $uri_nodes = $xpc->findnodes('//dsig:KeyInfo/dsig:RetrievalMethod/@URI');
    my @uri_nodes = $uri_nodes->map(sub { my $v = $_->getValue; $v =~ s/^#//; return $v; });

    foreach my $uri (@uri_nodes) {
        my $encrypted_key_nodes = $self->_get_named_key_nodes(
            sprintf('//xenc:EncryptedKey[@Id="%s"]', $uri),
            $xpc, $options{key_name});

        $encrypted_key_nodes->foreach(
            sub {

                my $key = $self->_get_key_from_node($_, $xpc);
                return unless $key;

                my $encrypted_nodes = $xpc->findnodes(sprintf('//dsig:KeyInfo/dsig:RetrievalMethod[@URI="#%s"]/../..', $uri));
                return unless $encrypted_nodes->size;

                $encrypted_nodes->foreach(sub {
                    $self->_decrypt_encrypted_node(
                        $_,
                        $key,
                        $xpc,
                        $parser
                    );
                });

                # We don't need the encrypted key here
                $_->removeChildNodes();
            }
        );
    }
}

sub _get_named_key_nodes {
    my $self = shift;
    my $xpath = shift;
    my $xpc = shift;
    my $name = shift;

    my $nodes = $xpc->findnodes($xpath);
    return $nodes unless $name;
    return $nodes->grep(
        sub {
            $xpc->findvalue('dsig:KeyInfo/dsig:KeyName', $_) eq $name;
        }
    );
}

sub _decrypt_encrypted_node {
    my $self = shift;
    my $node = shift;
    my $key  = shift;
    my $xpc  = shift;
    my $parser = shift;

    my $algo         = $self->_get_encryption_algorithm($node, $xpc);
    my $cipher_value = $self->_get_cipher_value($node, $xpc);
    my $oaep         = $self->_get_oaep_params($node, $xpc);

    my $decrypted_data = $self->_DecryptData($algo, $key, $cipher_value);

    # Sooo.. parse_balanced_chunk breaks when there is a <xml version="1'>
    # bit in the decrypted data and thus we have to remove it.
    # We try parsing the XML here and if that works we get all the nodes
    my $new = eval { $parser->load_xml(string => $decrypted_data)->findnodes('//*')->[0]; };

    if ($new) {
        $node->addSibling($new);
        $node->unbindNode();
        return;
    }

    $decrypted_data = $parser->parse_balanced_chunk($decrypted_data);
    if (($node->parentNode->localname //'') eq 'EncryptedID') {
        $node->parentNode->replaceNode($decrypted_data);
        return;
    }
    $node->replaceNode($decrypted_data);
    return;
}

sub _get_key_from_node {
    my $self = shift;
    my $node = shift;
    my $xpc  = shift;

    my $algo         = $self->_get_encryption_algorithm($_, $xpc);
    my $cipher_value = $self->_get_cipher_value($_, $xpc);
    my $digest_name  = $self->_get_digest_method($_, $xpc);
    my $oaep         = $self->_get_oaep_params($_, $xpc);
    my $mgf          = $self->_get_mgf($_, $xpc);

    return $self->_decrypt_key(
        $cipher_value,
        $algo,
        $digest_name,
        $oaep,
        $mgf,
    );
}

sub _get_encryption_algorithm {
    my $self = shift;
    my $node = shift;
    my $xpc  = shift;

    my $nodes = $xpc->findnodes('./xenc:EncryptionMethod/@Algorithm', $node);
    return $nodes->get_node(1)->getValue if $nodes->size;
    confess "Unable to determine encryption method algorithm from " . $node->nodePath;
}

sub _get_cipher_value {
    my $self = shift;
    my $node = shift;
    my $xpc  = shift;

    my $nodes = $xpc->findnodes('./xenc:CipherData/xenc:CipherValue', $node);
    return decode_base64($nodes->get_node(1)->textContent) if $nodes->size;
    confess "Unable to get the CipherValue from " . $node->nodePath;
}

sub _get_mgf {
    my $self = shift;
    my $node = shift;
    my $xpc  = shift;

    my $value = $xpc->findvalue('./xenc:EncryptionMethod/xenc11:MGF/@Algorithm', $node);
    return $value if $value;
    return;
}

sub _get_oaep_params {
    my $self = shift;
    my $node = shift;
    my $xpc  = shift;

    my $value = $xpc->findvalue('./xenc:EncryptionMethod/xenc:OAEPparams', $node);
    return decode_base64($value) if $value;
    return;
}

sub _get_digest_method {
    my $self = shift;
    my $node = shift;
    my $xpc  = shift;

    my $value = $xpc->findvalue(
        './xenc:EncryptionMethod/dsig:DigestMethod/@Algorithm', $node);
    return _assert_encryption_digest($value) if $value;
    return;
}


sub encrypt {
    my $self    = shift;
    my ($xml)   = @_;

    local $XML::LibXML::skipXMLDeclaration = $self->{ no_xml_declaration };

    # Create the EncryptedData node
    my ($encrypted) = $self->_create_encrypted_data_xml();

    my $dom = XML::LibXML->load_xml(
                                    string => $xml,
                                    no_network      => 1,
                                    load_ext_dtd    => 0,
                                    expand_entities => 0
                                );

    my $xpc = XML::LibXML::XPathContext->new($encrypted);
    $xpc->registerNs('dsig', 'http://www.w3.org/2000/09/xmldsig#');
    $xpc->registerNs('xenc', 'http://www.w3.org/2001/04/xmlenc#');
    $xpc->registerNs('xenc11', 'http://www.w3.org/2009/xmlenc11#');
    $xpc->registerNs('saml', 'urn:oasis:names:tc:SAML:2.0:assertion');

    # Encrypt the data an empty key is passed by reference to allow
    # the key to be generated at the same time the data is being encrypted
    my $key;
    my $method = $self->{data_enc_method};
    my $encrypteddata = $self->_EncryptData ($method, $dom->serialize(), \$key);

    # Encrypt the Key immediately after the data is encrypted.  It is passed by
    # reference to reduce the number of times that the unencrypted key is
    # stored in memory
    $self->_EncryptKey($self->{key_transport}, \$key);

    my $base64_key  = encode_base64($key);
    my $base64_data = encode_base64($encrypteddata);

    # Insert KeyName into the XML
    if (defined $self->{key_name} and $self->{key_name} ne '') {
        $encrypted = $self->_setKeyName($encrypted, $xpc, $self->{key_name});
    }

    # Insert OAEPparams into the XML
    if ($self->{oaep_params} ne '') {
        $encrypted = $self->_setOAEPparams($encrypted, $xpc, encode_base64($self->{oaep_params}));
    }

    # Insert Encrypted data into XML
    $encrypted = $self->_setEncryptedData($encrypted, $xpc, $base64_data);

    # Insert the Encrypted Key into the XML
    $self->_setKeyEncryptedData($encrypted, $xpc, $base64_key);

    return $encrypted->serialize();
}

sub _setEncryptionMethod {
    my $self    = shift;
    my $method  = shift;

    my %methods = (
                    'aes128-cbc'    => 'http://www.w3.org/2001/04/xmlenc#aes128-cbc',
                    'aes192-cbc'    => 'http://www.w3.org/2001/04/xmlenc#aes192-cbc',
                    'aes256-cbc'    => 'http://www.w3.org/2001/04/xmlenc#aes256-cbc',
                    'tripledes-cbc' => 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc',
                    'aes128-gcm'    => 'http://www.w3.org/2009/xmlenc11#aes128-gcm',
                    'aes192-gcm'    => 'http://www.w3.org/2009/xmlenc11#aes192-gcm',
                    'aes256-gcm'    => 'http://www.w3.org/2009/xmlenc11#aes256-gcm',
                  );

    return exists($methods{$method}) ? $methods{$method} : $methods{'aes256-cbc'};
}

sub _setKeyName {
    my $self         = shift;
    my $context      = shift;
    my $xpc          = shift;
    my $keyname      = shift;

    my $node = $xpc->findnodes('//xenc:EncryptedKey/dsig:KeyInfo/dsig:KeyName', $context);

    $node->[0]->removeChildNodes();
    $node->[0]->appendText(defined $keyname ? $keyname : 'key_name');
    return $context;
}

sub _setOAEPparams {
    my $self         = shift;
    my $context      = shift;
    my $xpc          = shift;
    my $oaep_params  = shift;

    my $node = $xpc->findnodes('//xenc:EncryptedKey/xenc:EncryptionMethod/xenc:OAEPparams', $context);

    $node->[0]->removeChildNodes();
    $node->[0]->appendText($oaep_params);
    return $context;
}

sub _setOAEPAlgorithm {
    my $self    = shift;
    my $method  = shift;

    state $setOAEPAlgorithm = {
        'mgf1sha1'   => 'http://www.w3.org/2009/xmlenc11#mgf1sha1',
        'mgf1sha224' => 'http://www.w3.org/2009/xmlenc11#mgf1sha224',
        'mgf1sha256' => 'http://www.w3.org/2009/xmlenc11#mgf1sha256',
        'mgf1sha384' => 'http://www.w3.org/2009/xmlenc11#mgf1sha384',
        'mgf1sha512' => 'http://www.w3.org/2009/xmlenc11#mgf1sha512',
    };

    return $setOAEPAlgorithm->{$method} // $setOAEPAlgorithm->{'rsa-oaep-mgf1p'};
}

sub _getOAEPAlgorithm {
    my $self    = shift;
    my $method  = shift;

    state $OAEPAlgorithm = {
        'http://www.w3.org/2009/xmlenc11#mgf1sha1'   => 'SHA1',
        'http://www.w3.org/2009/xmlenc11#mgf1sha224' => 'SHA224',
        'http://www.w3.org/2009/xmlenc11#mgf1sha256' => 'SHA256',
        'http://www.w3.org/2009/xmlenc11#mgf1sha384' => 'SHA384',
        'http://www.w3.org/2009/xmlenc11#mgf1sha512' => 'SHA512',
    };

    return $OAEPAlgorithm->{$method} // 'SHA1';
}

sub _setOAEPDigest {
    my $self    = shift;
    my $method  = shift;

    state $OAEPDigest = {
        'sha1'      => 'http://www.w3.org/2000/09/xmldsig#sha1',
        'sha224'    => 'http://www.w3.org/2001/04/xmldsig-more#sha224',
        'sha256'    => 'http://www.w3.org/2001/04/xmlenc#sha256',
        'sha384'    => 'http://www.w3.org/2001/04/xmldsig-more#sha384',
        'sha512'    => 'http://www.w3.org/2001/04/xmlenc#sha512',
    };

    return $OAEPDigest->{$method} // $OAEPDigest->{'sha256'};
}

sub _getParamsAlgorithm {
    my $self    = shift;
    my $method  = shift;

    state $ParamsAlgorithm = {
        'http://www.w3.org/2000/09/xmldsig#sha1' => 'SHA1',
        'http://www.w3.org/2001/04/xmldsig-more#sha224' => 'SHA224',
        'http://www.w3.org/2001/04/xmlenc#sha256' => 'SHA256',
        'http://www.w3.org/2001/04/xmldsig-more#sha384' => 'SHA384',
        'http://www.w3.org/2001/04/xmlenc#sha512' => 'SHA512',
    };

    return $ParamsAlgorithm->{$method} // $ParamsAlgorithm->{'http://www.w3.org/2000/09/xmldsig#sha1'};
}

sub _setKeyEncryptionMethod {
    my $self    = shift;
    my $method  = shift;

    state $enc_methods = {
        'rsa-1_5'        => 'http://www.w3.org/2001/04/xmlenc#rsa-1_5',
        'rsa-oaep-mgf1p' => 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p',
        'rsa-oaep'       => 'http://www.w3.org/2009/xmlenc11#rsa-oaep',
    };

    return $enc_methods->{$method} // $enc_methods->{'rsa-oaep-mgf1p'};
}

sub _DecryptData {
    my $self            = shift;
    my $method          = shift;
    my $key             = shift;
    my $encrypteddata   = shift;

    my $method_vars = _assert_symmetric_algorithm($method);

    my $ivsize   = $method_vars->{ivsize};
    my $tagsize  = $method_vars->{tagsize};

    my $iv          = substr $encrypteddata, 0, $ivsize;
    my $encrypted   = substr $encrypteddata, $ivsize;

    # XML Encryption 5.2 Block Encryption Algorithms
    # The resulting cipher text is prefixed by the IV.
    if ($method !~ /gcm/ ){
        my $cbc = Crypt::Mode::CBC->new($method_vars->{modename}, 0);
        return $self->_remove_padding($cbc->decrypt($encrypted, $key, $iv));
    }

    my $gcm = Crypt::AuthEnc::GCM->new("AES", $key, $iv);

    # Note that GCM support for additional authentication
    # data is not used in the XML specification.
    my $tag = substr $encrypted, -$tagsize;
    $encrypted = substr $encrypted, 0, (length $encrypted) - $tagsize;
    my $plaintext = $gcm->decrypt_add($encrypted);

    die "Tag expected did not match returned Tag"
        unless $gcm->decrypt_done($tag);

    return $plaintext;
}

sub _EncryptData {
    my $self    = shift;
    my $method  = shift;
    my $data    = shift;
    my $key     = shift;


    my $method_vars = _assert_symmetric_algorithm($method);

    my $ivsize   = $method_vars->{ivsize};

lib/XML/Enc.pm  view on Meta::CPAN

}

sub _EncryptKey {
    my $self        = shift;
    my $keymethod   = shift;
    my $key         = shift;

    my $rsa_pub = $self->{cert_obj};

    # FIXME: this could use some refactoring and some simplfication
    if ($keymethod eq 'http://www.w3.org/2001/04/xmlenc#rsa-1_5') {
        ${$key} = $rsa_pub->encrypt(${$key}, 'v1.5');
    }
    elsif ($keymethod eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p') {
        if ($CryptX::VERSION lt 0.081) {
            ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', 'SHA1', $self->{oaep_params});
        } else {
            my $oaep_label_hash = (defined $self->{oaep_label_hash} && $self->{oaep_label_hash} ne '') ?
                            $self->_getParamsAlgorithm($self->{oaep_label_hash}) : 'SHA1';
            ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', 'SHA1', $self->{oaep_params}, $oaep_label_hash);
        }
    }
    elsif ($keymethod eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep') {
        my $mgf_hash    = defined $self->{oaep_mgf_alg} ?
                            $self->_getOAEPAlgorithm($self->{oaep_mgf_alg}) : undef;
        if ($CryptX::VERSION lt 0.081) {
            ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', $mgf_hash, $self->{oaep_params});
        } else {
            my $oaep_label_hash = (defined $self->{oaep_label_hash} && $self->{oaep_label_hash} ne '') ?
                            $self->_getParamsAlgorithm($self->{oaep_label_hash}) : $mgf_hash;
            ${$key} = $rsa_pub->encrypt(${$key}, 'oaep', $mgf_hash, $self->{oaep_params}, $oaep_label_hash);
        }
    } else {
        die "Unsupported algorithm for key encyption $keymethod}";
    }

    print "Encrypted key: ", encode_base64(${$key}) if $DEBUG;
}

sub _setEncryptedData {
    my $self         = shift;
    my $context      = shift;
    my $xpc          = shift;
    my $cipherdata   = shift;

    my $node = $xpc->findnodes('xenc:EncryptedData/xenc:CipherData/xenc:CipherValue', $context);

    $node->[0]->removeChildNodes();
    $node->[0]->appendText($cipherdata);
    return $context;
}

sub _setKeyEncryptedData {
    my $self         = shift;
    my $context      = shift;
    my $xpc          = shift;
    my $cipherdata   = shift;

    my $node;

    if ($xpc->findvalue('dsig:KeyInfo/dsig:RetrievalMethod/@Type', $context)
                eq 'http://www.w3.org/2001/04/xmlenc#EncryptedKey')
    {
        my $id = $xpc->findvalue('dsig:KeyInfo/dsig:RetrievalMethod/@URI', $context);
        $id    =~ s/#//g;

        my $keyinfo = $xpc->find('//*[@Id=\''. $id . '\']', $context);
        if (! $keyinfo ) {
            die "Unable to find EncryptedKey";
        }

        $node = $keyinfo->[0]->findnodes('//xenc:EncryptedKey/xenc:CipherData', $context)->[0];
    } else {
        $node = $xpc->findnodes('//dsig:KeyInfo/xenc:EncryptedKey/xenc:CipherData/xenc:CipherValue')->[0];
    }
    $node->removeChildNodes();
    $node->appendText($cipherdata);
}

sub _remove_padding {
    my $self    = shift;
    my $padded  = shift;

    my $len = length $padded;
    my $padlen = ord substr $padded, $len - 1;
    return substr $padded, 0, $len - $padlen;
}

sub _add_padding {
    my $self    = shift;
    my $data    = shift;
    my $blksize = shift;

    my $len = length $data;
    my $padlen = $blksize - ($len % $blksize);
    my @pad;
    my $n = 0;
    while ($n < $padlen -1 ) {
        $pad[$n] = 176 + int(rand(80));
        $n++;
    }

    return $data . pack ("C*", @pad, $padlen);
}

##
## _trim($string)
##
## Arguments:
##    $string:      string String to remove whitespace
##
## Returns: string  Trimmed String
##
## Trim the whitespace from the begining and end of the string
##
sub _trim {
    my $string = shift;
    $string =~ s/^\s+//;
    $string =~ s/\s+$//;
    return $string;
}

##
## _load_key($file)
##
## Arguments: $self->{ key }
##
## Returns: nothing
##
## Load the key and process it acording to its headers
##
sub _load_key {
    my $self = shift;
    my $file = $self->{ key };

lib/XML/Enc.pm  view on Meta::CPAN

        if ( $text =~ m/BEGIN ([DR]SA) PRIVATE KEY/ ) {
            my $key_used = $1;

            if ( $key_used eq 'RSA' ) {
                $self->_load_rsa_key( $text );
            }
            else {
                $self->_load_dsa_key( $text );
            }

            return 1;
        } elsif ( $text =~ m/BEGIN EC PRIVATE KEY/ ) {
            $self->_load_ecdsa_key( $text );
        } elsif ( $text =~ m/BEGIN PRIVATE KEY/ ) {
            $self->_load_rsa_key( $text );
        } elsif ($text =~ m/BEGIN CERTIFICATE/) {
            $self->_load_x509_key( $text );
        }
        else {
            confess "Could not detect type of key $file.";
        }
    }
    else {
        confess "Could not load key $file: $!";
    }

    return;
}

##
## _load_rsa_key($key_text)
##
## Arguments:
##    $key_text:    string RSA Private Key as String
##
## Returns: nothing
##
## Populate:
##   self->{KeyInfo}
##   self->{key_obj}
##   self->{key_type}
##
sub _load_rsa_key {
    my $self        = shift;
    my ($key_text)  = @_;

    eval {
        require Crypt::PK::RSA;
    };
    confess "Crypt::PK::RSA needs to be installed so that we can handle RSA keys." if $@;

    my $rsaKey = Crypt::PK::RSA->new(\$key_text );

    if ( $rsaKey ) {
        $self->{ key_obj }  = $rsaKey;
        $self->{ key_type } = 'rsa';

        if (!$self->{ x509 }) {
            my $keyhash = $rsaKey->key2hash();

            $self->{KeyInfo} = "<dsig:KeyInfo>
                                 <dsig:KeyValue>
                                  <dsig:RSAKeyValue>
                                   <dsig:Modulus>$keyhash->{N}</dsig:Modulus>
                                   <dsig:Exponent>$keyhash->{d}</dsig:Exponent>
                                  </dsig:RSAKeyValue>
                                 </dsig:KeyValue>
                                </dsig:KeyInfo>";
        }
    }
    else {
        confess "did not get a new Crypt::PK::RSA object";
    }
}

##
## _load_x509_key($key_text)
##
## Arguments:
##    $key_text:    string RSA Private Key as String
##
## Returns: nothing
##
## Populate:
##   self->{key_obj}
##   self->{key_type}
##
sub _load_x509_key {
    my $self        = shift;
    my $key_text    = shift;

    eval {
        require Crypt::OpenSSL::X509;
    };
    confess "Crypt::OpenSSL::X509 needs to be installed so that we
            can handle X509 Certificates." if $@;

    my $x509Key = Crypt::OpenSSL::X509->new_private_key( $key_text );

    if ( $x509Key ) {
        $x509Key->use_pkcs1_padding();
        $self->{ key_obj } = $x509Key;
        $self->{key_type} = 'x509';
    }
    else {
        confess "did not get a new Crypt::OpenSSL::X509 object";
    }
}

##
## _load_cert_file()
##
## Arguments: none
##
## Returns: nothing
##
## Read the file name from $self->{ cert } and
## Populate:
##   self->{key_obj}
##   $self->{KeyInfo}
##
sub _load_cert_file {
    my $self = shift;

    eval {
        require Crypt::OpenSSL::X509;
    };

    die "Crypt::OpenSSL::X509 needs to be installed so that we can handle X509 certs.\n" if $@;

    my $file = $self->{ cert };
    if (!-r $file) {
        die "Could not find certificate file $file";
    }
    open my $CERT, '<', $file  or die "Unable to open $file\n";
    my $text = '';
    local $/ = undef;
    $text = <$CERT>;
    close $CERT;

    my $cert = Crypt::PK::RSA->new(\$text);
    die "Could not load certificate from $file" unless $cert;

    $self->{ cert_obj } = $cert;
    my $cert_text = $cert->export_key_pem('public_x509');
    $cert_text =~ s/-----[^-]*-----//gm;
    $self->{KeyInfo} = "<dsig:KeyInfo><dsig:X509Data><dsig:X509Certificate>\n"._trim($cert_text)."\n</dsig:X509Certificate></dsig:X509Data></dsig:KeyInfo>";
    return;
}

sub _create_encrypted_data_xml {
    my $self    = shift;

    local $XML::LibXML::skipXMLDeclaration = $self->{ no_xml_declaration };
    my $doc = XML::LibXML::Document->new();

    my $xencns = 'http://www.w3.org/2001/04/xmlenc#';
    my $dsigns = 'http://www.w3.org/2000/09/xmldsig#';
    my $xenc11ns = 'http://www.w3.org/2009/xmlenc11#';

    my $encdata = $self->_create_node($doc, $xencns, $doc, 'xenc:EncryptedData',
                            {
                                Type    => 'http://www.w3.org/2001/04/xmlenc#Element',
                            }
                        );

    $doc->setDocumentElement ($encdata);

    my $encmethod = $self->_create_node(
                            $doc,
                            $xencns,
                            $encdata,
                            'xenc:EncryptionMethod',
                            {
                                Algorithm => $self->{data_enc_method},
                            }
                        );

    my $keyinfo = $self->_create_node(
                            $doc,
                            $dsigns,
                            $encdata,
                            'dsig:KeyInfo',
                        );

    my $enckey = $self->_create_node(
                            $doc,
                            $xencns,
                            $keyinfo,
                            'xenc:EncryptedKey',
                        );

    my $kencmethod = $self->_create_node(
                            $doc,
                            $xencns,
                            $enckey,
                            'xenc:EncryptionMethod',
                            {
                                Algorithm => $self->{key_transport},
                            }
                        );

    if ($self->{key_transport} eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep' ||
        $self->{key_transport} eq 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' &&
        $self->{oaep_label_hash}) {
        my $digestmethod = $self->_create_node(
                            $doc,
                            $dsigns,
                            $kencmethod,
                            'dsig:DigestMethod',
                            {
                                Algorithm => $self->{oaep_label_hash},
                            }
                        );
    };

    if ($self->{'oaep_params'} ne '') {
        my $oaep_params = $self->_create_node(
                            $doc,
                            $xencns,
                            $kencmethod,
                            'xenc:OAEPparams',
                        );
    };

    if ($self->{key_transport} eq 'http://www.w3.org/2009/xmlenc11#rsa-oaep' &&
        $self->{oaep_mgf_alg}) {
        my $oaepmethod = $self->_create_node(
                            $doc,
                            $xenc11ns,
                            $kencmethod,
                            'xenc11:MGF',
                            {
                                Algorithm => $self->{oaep_mgf_alg},
                            }
                        );
    };

    my $keyinfo2 = $self->_create_node(
                            $doc,
                            $dsigns,
                            $enckey,
                            'dsig:KeyInfo',
                        );

    if (defined $self->{key_name}) {
        my $keyname = $self->_create_node(
                            $doc,
                            $dsigns,
                            $keyinfo2,
                            'dsig:KeyName',
                        );
    };

    my $keycipherdata = $self->_create_node(
                            $doc,
                            $xencns,
                            $enckey,
                            'xenc:CipherData',
                        );

    my $keyciphervalue = $self->_create_node(
                            $doc,
                            $xencns,
                            $keycipherdata,
                            'xenc:CipherValue',
                        );

    my $cipherdata = $self->_create_node(
                            $doc,
                            $xencns,
                            $encdata,
                            'xenc:CipherData',
                        );

    my $ciphervalue = $self->_create_node(
                            $doc,
                            $xencns,
                            $cipherdata,
                            'xenc:CipherValue',
                        );

    return $doc;
}

sub _create_node {
    my $self        = shift;
    my $doc         = shift;
    my $nsuri       = shift;
    my $parent      = shift;
    my $name        = shift;
    my $attributes  = shift;

    my $node = $doc->createElementNS ($nsuri, $name);
    for (keys %$attributes) {
        $node->addChild (
            $doc->createAttribute (
            #$node->setAttribute (
                        $_ => $attributes->{$_}
                        )
            );
    }
    $parent->addChild($node);
}

1;

__END__

=pod

lib/XML/Enc.pm  view on Meta::CPAN


=back

=item B<key_transport>

Specify the encryption method to be used for key transport.  Supported methods are:

Used in encryption.  Optional.  Default method: rsa-oaep-mgf1p

=over

=item * L<rsa-1_5|https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html#rsa-1_5>

=item * L<rsa-oaep-mgf1p|https://www.w3.org/TR/2002/REC-xmlenc-core-20021210/Overview.html#rsa-oaep-mgf1p>

=item * L<rsa-oaep|http://www.w3.org/2009/xmlenc11#rsa-oaep>

=back

=item B<oaep_mgf_alg>

Specify the Algorithm to be used for rsa-oaep.  Supported algorithms are:

Used in encryption.  Optional.  Default method: mgf1sha1

=over

=item * L<mgf1sha1|http://www.w3.org/2009/xmlenc11#mgf1sha1>

=item * L<mgf1sha224|http://www.w3.org/2009/xmlenc11#mgf1sha224>

=item * L<mgf1sha265|http://www.w3.org/2009/xmlenc11#mgf1sha256>

=item * L<mgf1sha384|http://www.w3.org/2009/xmlenc11#mgf1sha384>

=item * L<mgf1sha512|http://www.w3.org/2009/xmlenc11#mgf1sha512>

=back

=item B<oaep_params>

Specify the OAEPparams value to use as part of the mask generation function (MGF).
It is optional but can be specified for rsa-oaep and rsa-oaep-mgf1p EncryptionMethods.

It is base64 encoded and stored in the XML as OAEPparams.

If specified you MAY specify the oaep_label_hash that should be used.  You should note
that not all implementations support an oaep_label_hash that differs from that of the
MGF specified in the xenc11:MGF element or the default MGF1 with SHA1.

The oaep_label_hash is stored in the DigestMethod child element of the EncryptionMethod.

=item B<oaep_label_hash>

Specify the Hash Algorithm to use for the rsa-oaep label as specified by oaep_params.

The default is sha1.  Supported algorithms are:

=over

=item * L<sha1|http://www.w3.org/2000/09/xmldsig#sha1>

=item * L<sha224|http://www.w3.org/2001/04/xmldsig-more#sha224>

=item * L<sha256|http://www.w3.org/2001/04/xmlenc#sha256>

=item * L<sha384|http://www.w3.org/2001/04/xmldsig-more#sha384>

=item * L<sha512|http://www.w3.org/2001/04/xmlenc#sha512>

=back

=item B<key_name>

Specify a key name to add to the KeyName element.  If it is not specified then no
KeyName element is added to the KeyInfo

=back

=head2 decrypt( ... )

Main decryption function.

Arguments:

=over

=item B<xml>

XML containing the encrypted data.

=back

=head2 encrypt( ... )

Main encryption function.

Arguments:

=over

=item B<xml>

XML containing the plaintext data.

=back

=head1 AUTHOR

Timothy Legge <timlegge@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by TImothy Legge.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut



( run in 1.816 second using v1.01-cache-2.11-cpan-71847e10f99 )