Zuzu

 view release on metacpan or  search on metacpan

lib/Zuzu/Module/Secure.pm  view on Meta::CPAN

	my $curve = eval { $x509->curve } // '';
	my ( $algorithm, $pk );
	if ( ( $x509->pubkey_type // '' ) eq 'ec' ) {
		$algorithm = $curve eq 'prime256v1'
			? 'ecdsa-p256-sha256'
			: $curve eq 'secp384r1'
				? 'ecdsa-p384-sha384'
				: $curve eq 'secp521r1'
					? 'ecdsa-p521-sha512'
					: undef;
		_error(
			'Certificate.public_key certificate public-key algorithm '
			. 'is unsupported',
		) if not defined $algorithm;
		$pk = Crypt::PK::ECC->new;
		_error( 'Certificate.public_key failed to import public key' )
			if not eval { $pk->import_key( \$pem ); 1 };
	}
	elsif ( ( $x509->pubkey_type // '' ) eq 'ed25519' ) {
		$algorithm = 'ed25519';
		$pk = Crypt::PK::Ed25519->new;
		_error( 'Certificate.public_key failed to import public key' )
			if not eval { $pk->import_key( \$pem ); 1 };
	}
	else {
		_error(
			'Certificate.public_key certificate public-key algorithm '
			. 'is unsupported',
		);
	}
	return _new_public_key( $public_key_class, $algorithm, $pk );
}

sub _new_tls_identity {
	my ( $tls_identity_class, %slots ) = @_;

	return native_object(
		class => $tls_identity_class,
		slots => \%slots,
		const => {
			_cert_pem => 1,
			_key_pem => 1,
			_password => 1,
			_chain_pem => 1,
			_source => 1,
		},
	);
}

sub _tls_identity_state {
	my ( $self, $label ) = @_;

	_error( "TypeException: $label expects TlsIdentity" )
		if not blessed($self)
			or not $self->can('class')
			or $self->class->name ne 'TlsIdentity';
	return $self->slots;
}

sub _tls_identity_from_pem {
	my ( $tls_identity_class, $certificate_pem, $private_key_pem, $password ) = @_;

	my $label = 'TlsIdentity.from_pem';
	my $cert_pem = _string_arg(
		$certificate_pem,
		$label,
		'String certificate_pem',
	);
	my $key_pem = _string_arg(
		$private_key_pem,
		$label,
		'String private_key_pem',
	);
	my $pass = _optional_password_text( $password, $label, 'String password' );
	my @blocks = _pem_blocks( $cert_pem, $label );
	_parse_x509_der( native_class( name => 'Certificate' ), $blocks[0], $label );
	_error( "$label expects PEM private key text" )
		if $key_pem !~ /-----BEGIN [A-Z ]*PRIVATE KEY-----/;

	return _new_tls_identity(
		$tls_identity_class,
		_cert_pem => _der_to_pem_certificate( $blocks[0] ),
		_key_pem => $key_pem,
		_password => $pass,
		_chain_pem => join( '', map { _der_to_pem_certificate($_) } @blocks ),
		_source => 'pem',
	);
}

sub _tls_identity_from_pkcs12 {
	my ( $tls_identity_class, $bytes_value, $password ) = @_;

	my $label = 'TlsIdentity.from_pkcs12';
	my $bytes = _binary_bytes( $bytes_value, $label, 'BinaryString bytes' );
	my $pass = _optional_password_text( $password, $label, 'String password' );
	my $pkcs12 = eval { Crypt::OpenSSL::PKCS12->new_from_string($bytes) };
	_error( "$label expects PKCS#12 data" ) if not defined $pkcs12;
	_error( "$label failed to decrypt PKCS#12 data" )
		if not eval { $pkcs12->mac_ok($pass) };
	my $cert_pem = eval { $pkcs12->certificate($pass) };
	my $key_pem = eval { $pkcs12->private_key($pass) };
	_error( "$label expects PKCS#12 data with certificate and private key" )
		if not defined $cert_pem or not defined $key_pem;
	my $ca_pem = eval { $pkcs12->ca_certificate($pass) } // '';
	my @blocks = _pem_blocks( $cert_pem, $label );

	return _new_tls_identity(
		$tls_identity_class,
		_cert_pem => _der_to_pem_certificate( $blocks[0] ),
		_key_pem => $key_pem,
		_password => '',
		_chain_pem => $cert_pem . $ca_pem,
		_source => 'pkcs12',
	);
}

sub _tls_identity_certificate {
	my ( $certificate_class, $self ) = @_;

	my $state = _tls_identity_state( $self, 'TlsIdentity.certificate' );
	my ( $der ) = _pem_blocks( $state->{_cert_pem}, 'TlsIdentity.certificate' );
	return _parse_x509_der( $certificate_class, $der, 'TlsIdentity.certificate' );
}

sub _tls_identity_private_key {
	my ( $signing_key_class, $self ) = @_;

	my $state = _tls_identity_state( $self, 'TlsIdentity.private_key' );
	my $options = Zuzu::Value::Dict->new(
		map => {
			format => 'pem',
			password => $state->{_password},
		},
	);
	return eval {
		_signing_import_private(
			$signing_key_class,
			$state->{_key_pem},
			$options,
		);
	} // do {
		_error(
			'TlsIdentity.private_key only supports Ed25519, ECDSA P-256, '
			. 'ECDSA P-384, and ECDSA P-521 private keys',
		);
	};
}

sub _signing_generate {
	my ( $signing_key_class, $algorithm ) = @_;

	my $label = 'SigningKey.generate';
	$algorithm = _signing_algorithm( $algorithm, $label );
	my $meta = _signing_meta($algorithm);
	my $pk = _new_key_for_algorithm($algorithm);
	if ( $meta->{type} eq 'ed25519' ) {
		$pk->generate_key;
	}
	elsif ( $meta->{type} eq 'ecdsa' ) {
		$pk->generate_key( $meta->{curve} );
	}
	return _new_signing_key( $signing_key_class, $algorithm, $pk );
}

sub _signing_import_private {
	my ( $signing_key_class, $key, $options ) = @_;

	my $label = 'SigningKey.import_private';
	my $opts = _signing_options( $options, $label );
	my $format = _key_format( $options, $key, $label );
	my $algorithm = _signing_algorithm_option( $opts, $label );
	my $password = exists $opts->{password}
		? _optional_password_text(
			$opts->{password},
			$label,
			'String options.password',
		)
		: undef;
	my $pk;
	if ( $format eq 'raw' ) {
		my $bytes = _binary_bytes( $key, $label, 'BinaryString key' );
		$algorithm //= 'ed25519';
		my $meta = _signing_meta($algorithm);
		_error( "$label expects a $meta->{private_length}-byte raw private key" )
			if length($bytes) != $meta->{private_length};
		$pk = _new_key_for_algorithm($algorithm);
		if ( $meta->{type} eq 'ed25519' ) {
			$pk->import_key_raw( $bytes, 'private' );
		}
		else {
			$pk->import_key_raw( $bytes, $meta->{curve} );
		}
	}
	elsif ( $format eq 'pem' ) {
		my $pem = _string_arg( $key, $label, 'String key' );
		if ( defined $algorithm ) {
			$pk = _new_key_for_algorithm($algorithm);
			_error( "$label expects PEM private key" )
				if not eval { $pk->import_key( \$pem, $password ); 1 };
			if ( _signing_meta($algorithm)->{type} eq 'ecdsa' ) {
				my $actual = _ecdsa_algorithm_for_key( $pk, $label );
				_error( "$label PEM key algorithm does not match $algorithm" )
					if $actual ne $algorithm;

lib/Zuzu/Module/Secure.pm  view on Meta::CPAN

					return $runtime->_new_task(
						name => 'SigningKey.import_public_async',
						status => 'fulfilled',
						result => $value,
					);
				},
			),
		},
		methods => {
			public_key => native_function(
				name => 'public_key',
				native => sub {
					my ( $self ) = @_;
					return _signing_public_key(
						$public_key_class,
						$self,
					);
				},
			),
			sign => native_function(
				name => 'sign',
				native => sub {
					my ( $self, $message ) = @_;
					return _signing_sign( $self, $message );
				},
			),
			sign_async => native_function(
				name => 'sign_async',
				native => sub {
					my ( $self, $message ) = @_;
					my $value = _signing_sign( $self, $message );
					return $runtime->_new_task(
						name => 'SigningKey.sign_async',
						status => 'fulfilled',
						result => $value,
					);
				},
			),
			export_private => native_function(
				name => 'export_private',
				native => sub {
					my ( $self, $options ) = @_;
					return _signing_export_private(
						$self,
						$options,
					);
				},
			),
		},
	);
	my $tls_identity_class;
	$tls_identity_class = native_class(
		name => 'TlsIdentity',
		static_methods => {
			from_pem => native_function(
				name => 'from_pem',
				native => sub {
					my (
						$self,
						$certificate_pem,
						$private_key_pem,
						$password,
					) = @_;
					return _tls_identity_from_pem(
						$tls_identity_class,
						$certificate_pem,
						$private_key_pem,
						$password,
					);
				},
			),
			from_pkcs12 => native_function(
				name => 'from_pkcs12',
				native => sub {
					my ( $self, $bytes, $password ) = @_;
					return _tls_identity_from_pkcs12(
						$tls_identity_class,
						$bytes,
						$password,
					);
				},
			),
		},
		methods => {
			certificate => native_function(
				name => 'certificate',
				native => sub {
					my ( $self ) = @_;
					return _tls_identity_certificate(
						$certificate_class,
						$self,
					);
				},
			),
			private_key => native_function(
				name => 'private_key',
				native => sub {
					my ( $self ) = @_;
					return _tls_identity_private_key(
						$signing_key_class,
						$self,
					);
				},
			),
		},
	);

	return {
		Secure => $secure_class,
		SecureRandom => $random_class,
		PasswordHash => $password_hash_class,
		KeyDerivation => $kdf_class,
		Cipher => $cipher_class,
		KeyAgreement => $key_agreement_class,
		SigningKey => $signing_key_class,
		Certificate => $certificate_class,
		PrivateKey => native_class( name => 'PrivateKey' ),
		PublicKey => $public_key_class,
		SealedBox => native_class( name => 'SealedBox' ),
		TlsIdentity => $tls_identity_class,
	};
}

1;

=pod

=head1 NAME

Zuzu::Module::Secure - std/secure bindings for ZuzuScript.

=head1 DESCRIPTION

Implements the Phase 1 C<std/secure> runtime-supported module skeleton.

=head1 COPYRIGHT AND LICENCE

B<< Zuzu::Module::Secure >> is copyright Toby Inkster.

It is free software; you may redistribute it and/or modify it under
the terms of either the Artistic License 1.0 or the GNU General Public
License version 2.

=cut



( run in 0.763 second using v1.01-cache-2.11-cpan-13bb782fe5a )