Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Ed25519 #18

Merged
merged 3 commits into from Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion HACKING.DKIM
Expand Up @@ -30,11 +30,18 @@ New version - update version numbers in these files:
New algorithm:
create new algorithm class by copying and editing
lib/Mail/DKIM/Algorithm/rsa_sha1.pm
edit lib/Mail/DKIM/Common.pm:
edit lib/Mail/DKIM/Signature.pm:
get_algorithm_class() - add a check for your new algorithm and return
the name of your new algorithm class
add a "use" line at the top of this file so that your algorithm class
gets imported
if the new algorithm uses a different key type (k=), also edit
lib/Mail/DKIM/PublicKey.pm:
check()
convert()
verify_digest()
lib/Mail/DKIM/Verifier.pm:
_check_and_verify_signature()

--

Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -30,6 +30,7 @@ DEPENDENCIES
This module requires these other modules and libraries:

Crypt::OpenSSL::RSA
Crypt::PK::Ed25519
Digest::SHA
Mail::Address (part of the MailTools package)
MIME::Base64
Expand Down
56 changes: 56 additions & 0 deletions lib/Mail/DKIM/Algorithm/ed25519_sha256.pm
@@ -0,0 +1,56 @@
package Mail::DKIM::Algorithm::ed25519_sha256;
use strict;
use warnings;
# VERSION
# ABSTRACT: edd2519 sha256 algorithm class

# Copyright 2005-2006 Messiah College. All rights reserved.
# Jason Long <jlong@messiah.edu>

# Copyright (c) 2004 Anthony D. Urso. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.

use base 'Mail::DKIM::Algorithm::Base';
use Carp;
use MIME::Base64;
use Digest::SHA;

sub init_digests {
my $self = shift;

# initialize a SHA-256 Digest
$self->{header_digest} = new Digest::SHA(256);
$self->{body_digest} = new Digest::SHA(256);
}

sub sign {
my $self = shift;
croak 'wrong number of arguments' unless ( @_ == 1 );
my ($private_key) = @_;

my $digest = $self->{header_digest}->digest;
my $signature = $private_key->sign_digest( 'SHA-256', $digest );

return encode_base64( $signature, '' );
}

sub verify {
my $self = shift;
croak 'wrong number of arguments' unless ( @_ == 0 );

my $base64 = $self->signature->data;
my $public_key = $self->signature->get_public_key;

my $digest = $self->{header_digest}->digest;
my $sig = decode_base64($base64);

return unless $public_key->verify_digest( 'SHA-256', $digest, $sig );
return $self->check_body_hash;
}

sub wants_pre_signature_headers {
return 1;
}

1;
114 changes: 86 additions & 28 deletions lib/Mail/DKIM/PrivateKey.pm
Expand Up @@ -95,42 +95,85 @@ The returned object is of type L<Crypt::OpenSSL::RSA>.
=cut

sub convert {
use Crypt::OpenSSL::RSA;

my $self = shift;

# Use different libs subject to key type.
if ( $self->{'TYPE'} eq 'rsa' ) {
use Crypt::OpenSSL::RSA;
}
elsif ( $self->{'TYPE'} eq 'ed25519' ) {
use Crypt::PK::Ed25519;
}

$self->data
or return;

# have to PKCS1ify the privkey because openssl is too finicky...
my $pkcs = "-----BEGIN RSA PRIVATE KEY-----\n";
if ( $self->{'TYPE'} eq 'rsa' ) {

# have to PKCS1ify the privkey because openssl is too finicky...
my $pkcs = "-----BEGIN RSA PRIVATE KEY-----\n";

for ( my $i = 0 ; $i < length $self->data ; $i += 64 ) {
$pkcs .= substr $self->data, $i, 64;
$pkcs .= "\n";
}

$pkcs .= "-----END RSA PRIVATE KEY-----\n";

my $cork;

eval {
local $SIG{__DIE__};
$cork = new_private_key Crypt::OpenSSL::RSA($pkcs);
1
} || do {
$self->errorstr($@);
return;
};

$cork
or return;

# segfaults on my machine
# $cork->check_key or
# return;

$self->cork($cork);

for ( my $i = 0 ; $i < length $self->data ; $i += 64 ) {
$pkcs .= substr $self->data, $i, 64;
$pkcs .= "\n";
}
elsif ( $self->{'TYPE'} eq 'ed25519' ) {
my $cork;

$pkcs .= "-----END RSA PRIVATE KEY-----\n";
eval {
local $SIG{__DIE__};
$cork = new Crypt::PK::Ed25519;

my $cork;
# Prepend/append with PEM boilerplate
my $pem = "-----BEGIN ED25519 PRIVATE KEY-----\n";
$pem .= $self->data;
$pem .= "\n";
$pem .= "-----END ED25519 PRIVATE KEY-----\n";

eval {
local $SIG{__DIE__};
$cork = new_private_key Crypt::OpenSSL::RSA($pkcs);
1
} || do {
$self->errorstr($@);
return;
};
# Pass PEM text buffer
$cork->import_key(\$pem)
or die 'failed to load Ed25519 private key';

$cork
or return;
# Alternatively, import_raw_key() could be used,
# but requires the 32-byte key, which must be extracted
# from the ASN.1 structure first.

1
} || do {
$self->errorstr($@);
return;
};

$cork
or return;

# segfaults on my machine
# $cork->check_key or
# return;
$self->cork($cork);

$self->cork($cork);
}

return 1;
}
Expand Down Expand Up @@ -168,12 +211,27 @@ sub sign_digest {
my $self = shift;
my ( $digest_algorithm, $digest ) = @_;

my $rsa_priv = $self->cork;
$rsa_priv->use_no_padding;
if ( $self->{'TYPE'} eq 'rsa') {

my $rsa_priv = $self->cork;
$rsa_priv->use_no_padding;

my $k = $rsa_priv->size;
my $EM = calculate_EM( $digest_algorithm, $digest, $k );
return $rsa_priv->decrypt($EM);

my $k = $rsa_priv->size;
my $EM = calculate_EM( $digest_algorithm, $digest, $k );
return $rsa_priv->decrypt($EM);
}
elsif ( $self->{'TYPE'} eq 'ed25519' ) {

my $ed = $self->cork;
if ( !$ed ) {
$@ = $@ ne '' ? "Ed25519 failed: $@" : 'Ed25519 unknown problem';
die;
}

return $ed->sign_message($digest);

}
}

=cut
Expand Down