Skip to content

Commit

Permalink
Merge pull request #12091 from mlschroe/master
Browse files Browse the repository at this point in the history
[backend] Implement "cosign" signatures
  • Loading branch information
mlschroe committed Jan 18, 2022
2 parents 6051adf + 66cafe1 commit 67efd39
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 6 deletions.
46 changes: 40 additions & 6 deletions src/backend/BSConSign.pm
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,32 @@ use strict;
use BSPGP;
use JSON::XS ();
use Digest::MD5 ();
use Digest::SHA ();
use MIME::Base64 ();
use IO::Compress::RawDeflate;

sub createsig {
my ($signfunc, $digest, $reference, $creator, $timestamp) = @_;
sub canonical_json {
return JSON::XS->new->utf8->canonical->encode($_[0]);
}

sub createpayload {
my ($type, $digest, $reference, $creator, $timestamp) = @_;
my $critical = {
'type' => 'atomic container signature',
'type' => $type,
'image' => { 'docker-manifest-digest' => $digest },
'identity' => { 'docker-reference' => $reference },
};
my $optional = {};
$optional->{'creator'} = $creator if $creator;
$optional->{'timestamp'} = $timestamp if $timestamp;
my $data = { 'critical' => $critical, 'optional' => $optional };
my $data_json = JSON::XS->new->utf8->canonical->encode($data);
my $sig = $signfunc->($data_json);
return canonical_json($data);
}

sub createsig {
my ($signfunc, $digest, $reference, $creator, $timestamp) = @_;
my $payload = createpayload('atomic container signature', $digest, $reference, $creator, $timestamp);
my $sig = $signfunc->($payload);
my $sd = BSPGP::pk2sigdata($sig);
die("no issuer\n") unless $sd->{'issuer'};
# create onepass_sig packet
Expand All @@ -51,7 +61,7 @@ sub createsig {
# create literal data packet
my $fn = 'rpmsig-req.bin';
my $t = $sd->{'signtime'} || time();
my $literal_data_pkt = pack('CCa*N', 0x62, length($fn), $fn, $t).$data_json;
my $literal_data_pkt = pack('CCa*N', 0x62, length($fn), $fn, $t).$payload;
$literal_data_pkt = BSPGP::pkencodepacket(11, $literal_data_pkt);
# create compressed packet
my $packets = "$onepass_sig_pkt$literal_data_pkt$sig";
Expand All @@ -72,4 +82,28 @@ sub sig2openshift {
return $data;
}

sub createcosign {
my ($signfunc, $digest, $reference, $creator, $timestamp) = @_;
my $payload = createpayload('cosign container image signature', $digest, $reference, $creator, $timestamp);
my $payload_digest = 'sha256:'.Digest::SHA::sha256_hex($payload);
# signfunc must return the openssl rsa signature
my $sig = MIME::Base64::encode_base64($signfunc->($payload), '');
my $config = {
'architecture' => '',
'config' => {},
'created' => '0001-01-01T00:00:00Z',
'history' => [ { 'created' => '0001-01-01T00:00:00Z' } ],
'os' => '',
'rootfs' => { 'type' => 'layers', 'diff_ids' => [ $payload_digest ] },
};
my $config_json = canonical_json($config);
my $payload_layer = {
'annotations' => { 'dev.cosignproject.cosign/signature' => $sig },
'digest' => $payload_digest,
'mediaType' => 'application/vnd.dev.cosign.simplesigning.v1+json',
'size' => length($payload),
};
return ($config_json, $payload_layer, $payload);
}

1;
79 changes: 79 additions & 0 deletions src/backend/BSPublisher/Registry.pm
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,17 @@ sub push_blob {
return $blobid;
}

sub push_blob_string {
my ($repodir, $blobdata) = @_;
my $blob_id = 'sha256:'.Digest::SHA::sha256_hex($blobdata);
my $dir = "$repodir/:blobs";
return $blob_id if -e "$dir/$blob_id";
mkdir_p($dir) unless -d $dir;
unlink("$dir/.$blob_id.$$");
writestr("$dir/.$blob_id.$$", "$dir/$blob_id", $blobdata);
return $blob_id;
}

sub push_manifest {
my ($repodir, $mani_json) = @_;
my $mani_id = 'sha256:'.Digest::SHA::sha256_hex($mani_json);
Expand Down Expand Up @@ -420,6 +431,66 @@ sub update_sigs {
}
}

sub update_cosign {
my ($prp, $repo, $gun, $imagedigests, $pubkey, $signargs, $oci, $knownmanifests, $knownblobs) = @_;

my $creator = 'OBS';
my ($projid, $repoid) = split('/', $prp, 2);
my @signcmd;
push @signcmd, $BSConfig::sign;
push @signcmd, '--project', $projid if $BSConfig::sign_project;
push @signcmd, @{$signargs || []};
my $signfunc = sub { BSUtil::xsystem($_[0], @signcmd, '-O', '-h', 'sha256') };
my $repodir = "$registrydir/$repo";
my $oldsigs = BSUtil::retrieve("$repodir/:cosign", 1) || {};
return if !%$oldsigs && !%$imagedigests;
my $gpgpubkey = BSPGP::unarmor($pubkey);
my $pubkey_fp = BSPGP::pk2fingerprint($gpgpubkey);
if (($oldsigs->{'pubkey'} || '') ne $pubkey_fp || ($oldsigs->{'gun'} || '') ne $gun || ($oldsigs->{'creator'} || '') ne ($creator || '')) {
$oldsigs = {}; # fingerprint/gun/creator mismatch, do not use old signatures
}
my $sigs = { 'pubkey' => $pubkey_fp, 'gun' => $gun, 'creator' => $creator, 'digests' => {} };
for my $digest (sort keys %$imagedigests) {
my $old = ($oldsigs->{'digests'} || {})->{$digest};
if ($old) {
$sigs->{'digests'}->{$digest} = $old;
next;
}
print "creating cosign signature for $gun $digest\n";
my ($config, $payload_layer, $payload) = BSConSign::createcosign($signfunc, $digest, $gun, $creator);
my $config_blobid = push_blob_string($repodir, $config);
$knownblobs->{$config_blobid} = 1;
my $payload_blobid = push_blob_string($repodir, $payload);
$knownblobs->{$payload_blobid} = 1;
die unless $payload_blobid eq $payload_layer->{'digest'};
my $config_data = {
'mediaType' => $oci ? $BSContar::mt_oci_config : $BSContar::mt_docker_config,
'size' => length($config),
'digest' => $config_blobid,
};
my $mediaType = $oci ? $BSContar::mt_oci_manifest : $BSContar::mt_docker_manifest;
my $mani = {
'schemaVersion' => 2,
'mediaType' => $mediaType,
'config' => $config_data,
'layers' => [ $payload_layer ],
};
my $mani_json = BSContar::create_dist_manifest($mani);
my $mani_id = push_manifest($repodir, $mani_json);
$knownmanifests->{$mani_id} = 1;
$sigs->{'digests'}->{$digest} = $mani_id;
}
if (BSUtil::identical($oldsigs, $sigs)) {
print "local cosign signatures: no change.\n";
return;
}
if (%{$sigs->{'digests'}}) {
BSUtil::store("$repodir/.cosign.$$", "$repodir/:cosign", $sigs);
} else {
unlink("$repodir/:cosign");
}
}

sub push_containers {
my ($prp, $repo, $gun, $multiarch, $tags, $pubkey, $signargs) = @_;

Expand Down Expand Up @@ -621,6 +692,13 @@ sub push_containers {
$info{$tag} = $taginfo;
}

# write signatures file
if ($gun && %knownimagedigests) {
update_cosign($prp, $repo, $gun, \%knownimagedigests, $pubkey, $signargs, 0, \%knownmanifests, \%knownblobs);
} elsif (-e "$repodir/:cosign") {
unlink("$repodir/:cosign");
}

# now get rid of old entries
for (sort(ls("$repodir/:tags"))) {
next if $knowntags{$_};
Expand All @@ -645,6 +723,7 @@ sub push_containers {
unlink("$repodir/:tuf.old");
unlink("$repodir/:tuf");
unlink("$repodir/:sigs");
unlink("$repodir/:cosign");
disownrepo($prp, $repo);
return $containerdigests;
}
Expand Down
5 changes: 5 additions & 0 deletions src/backend/bs_repserver
Original file line number Diff line number Diff line change
Expand Up @@ -4197,6 +4197,11 @@ sub registry_manifest {
die("404 NAME_UNKNOWN\n") unless -d $repodir;
my $mani_json;
my $is_manifest;
if ($manifest =~ /^sha256-([0-9a-f]{64}).sig$/) {
my $digest = "sha256:$1";
my $sigs = BSUtil::retrieve("$repodir/:cosign", 1);
$manifest = $sigs->{'digests'}->{$digest} if $sigs && $sigs->{'digests'}->{$digest};
}
if ($manifest !~ /^sha256:[0-9a-f]{64}$/s) {
$mani_json = readstr("$repodir/:tags/$manifest", 1);
} else {
Expand Down

0 comments on commit 67efd39

Please sign in to comment.