Commit 60274694 authored by Leigh B Stoller's avatar Leigh B Stoller

ABAC Speaksfor credential support.

The CM can now receive either an ABAC or a non-ABAC speaksfor
credential in the list of credentials. Thanks to Gary for getting
libabac built on boss so that I could use it! The AM probably needs a
little bit more work since it has a few V3 places where it does not
invoke CMV2 directly, but that should be easy to fix; all of the AMV2
functions will work tough.

Caveat; I don't bother to look at the speaksfor option; if we get a
speaksfor credential, I figure it was cause the user wants to use it!

I added a hacky script called genspeaksfor to create a proper speaks
for credential that allows me to speak for another user. For example:

	genspeaksfor -a urn:publicid:IDN+emulab.net+user+leebee \
	         urn:publicid:IDN+emulab.net+user+stoller

which generates an ABAC speaks for credential that allows me to spead
for leebee. To use the PG test scripts with this credential:

	createsliver.py* -S speaksfor.cred -s slice.cred

Where slice.cred is a plain slice credential issued to leebee and then
given to me via an out of band mechanism (:-).
parent 11f66ce4
......@@ -2154,7 +2154,8 @@ sub CheckCredentials($)
$GeniCredential::CreateFromSignedError);
goto bad;
}
if ($credential->type() eq "speaksfor") {
if ($credential->type() eq "speaksfor" ||
$credential->type() eq "abac") {
$speaksfor = $credential;
}
else {
......@@ -2186,10 +2187,52 @@ sub CheckCredentials($)
}
}
else {
$speaksfor = GeniCredential::CheckCredential($speaksfor);
if (GeniResponse::IsError($speaksfor)) {
$error = $speaksfor;
goto bad;
if ($speaksfor->type() eq "abac") {
#
# At the moment, the easiest thing to do is make the
# speaksfor credential look sorta like a normal
# credential.
#
# The signer of the credential is the one being
# spoken for. This is the target of the speaksfor.
# The speaker is derived from the TLS context, and
# is the owner of the credential.
#
my $speaker_certificate =
GeniCertificate->LoadFromString($ENV{'SSL_CLIENT_CERT'});
if (!defined($speaker_certificate)) {
print STDERR "Could not load speaker certificate:\n";
print STDERR $ENV{'SSL_CLIENT_CERT'} . "\n";
$error = GeniResponse->Create(GENIRESPONSE_FORBIDDEN,
undef,
"Could not load speaker certificate");
goto bad;
}
$speaksfor->SetOwnerCert($speaker_certificate);
#
# Grab the signer. Should only be one.
#
my @signer_certs = @{ $speaksfor->signer_certs() };
my $speakee_certificate =
GeniCertificate->LoadFromString($signer_certs[0]);
if (!defined($speakee_certificate)) {
print STDERR "Could not load user certificate:\n";
print STDERR $signer_certs[0] . "\n";
$error = GeniResponse->Create(GENIRESPONSE_FORBIDDEN,
undef,
"Could not load user certificate");
goto bad;
}
$speaksfor->SetTargetCert($speakee_certificate);
}
else {
$speaksfor = GeniCredential::CheckCredential($speaksfor);
if (GeniResponse::IsError($speaksfor)) {
$error = $speaksfor;
goto bad;
}
}
main::AddLogfileMetaDataFromSpeaksFor($speaksfor);
......
......@@ -445,26 +445,6 @@ sub CreateFromSigned($$;$)
my $credential_el = &$find( $root, "credential" );
goto bad unless defined( $credential_el );
# Dig out the entire credential structure to save it.
my ($credential) = $doc->getElementsByTagName("credential");
# Ditto the signatures.
my ($signatures) = $doc->getElementsByTagName("signatures");
my @signatures = $signatures->getElementsByTagName("Signature");
# Dig out the extensions
# now extensions is an xml element.
my ($extensions) = GeniXML::FindNodes('//n:extensions',
$root)->get_nodelist;
# UUID of the credential.
my $uuid_node = &$find( $credential_el, "uuid" );
goto bad
if (!defined($uuid_node));
my $this_uuid = $uuid_node->to_literal();
$this_uuid = undef
if (defined($this_uuid) && $this_uuid eq "");
# Type of the credential.
my $type_node = &$find( $credential_el, "type" );
goto bad
......@@ -473,16 +453,6 @@ sub CreateFromSigned($$;$)
goto bad
if (!defined($credtype) || $credtype eq "");
#
# No longer require this uuid; only PG credentials have it.
# If we try to store it, throw an error. See below.
#
if (defined($this_uuid) &&
! ($this_uuid =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/)) {
$msg = "Invalid this_uuid in credential";
goto bad;
}
# Expiration
my $expires_node = &$find( $credential_el, "expires" );
if (!defined($expires_node)) {
......@@ -503,6 +473,48 @@ sub CreateFromSigned($$;$)
}
$expires = POSIX::strftime("20%y-%m-%dT%H:%M:%S", localtime($when));
# Dig out the signatures.
my ($signatures) = $doc->getElementsByTagName("signatures");
my @signatures = $signatures->getElementsByTagName("Signature");
# We deal with ABAC on a totally separate path. Results in a bit
# of code duplication, but so be it.
if ($credtype eq "abac") {
my $ref = {
'type' => $credtype,
'string' => $string,
'valid_until' => $expires,
'signatures' => \@signatures,
};
return GeniCredential::ABAC->CreateFromSigned($ref, $root);
}
# Dig out the entire credential structure to save it.
my ($credential) = $doc->getElementsByTagName("credential");
# Dig out the extensions
# now extensions is an xml element.
my ($extensions) = GeniXML::FindNodes('//n:extensions',
$root)->get_nodelist;
# UUID of the credential.
my $uuid_node = &$find( $credential_el, "uuid" );
goto bad
if (!defined($uuid_node));
my $this_uuid = $uuid_node->to_literal();
$this_uuid = undef
if (defined($this_uuid) && $this_uuid eq "");
#
# No longer require this uuid; only PG credentials have it.
# If we try to store it, throw an error. See below.
#
if (defined($this_uuid) &&
! ($this_uuid =~ /^\w+\-\w+\-\w+\-\w+\-\w+$/)) {
$msg = "Invalid this_uuid in credential";
goto bad;
}
# Dig out the target certificate.
my $cert_node = &$find( $credential_el, "target_gid" );
goto bad
......@@ -1113,5 +1125,169 @@ sub CheckCredential($;$$)
return $credential;
}
########################################################################
# ABAC version of a credential. This a total hack job, will need to
# be flushed and redone later.
#
package GeniCredential::ABAC;
use Exporter;
use SelfLoader ();
use vars qw(@ISA @EXPORT $AUTOLOAD);
@ISA = qw(Exporter SelfLoader);
@EXPORT = qw ( );
use English;
use GeniDB;
use GeniHRN;
use GeniCertificate;
use GeniResponse;
use Carp;
use Data::Dumper;
use overload ('""' => 'Stringify');
#
# Stringify for output.
#
sub Stringify($)
{
my ($self) = @_;
return "[GeniCredential::ABAC ]";
}
AUTOLOAD {
my $self = $_[0];
my $type = ref($self) or croak "$self is not an object";
my $name = $AUTOLOAD;
$name =~ s/.*://; # strip fully-qualified portion
confess("No such method '$name' in class $type");
return undef;
}
sub field($$) { return ($_[0]->{$_[1]}); }
sub asString($) { return field($_[0], "string"); }
sub type($) { return field($_[0], "type"); }
sub owner_cert($) { return field($_[0], "owner_cert"); }
sub target_cert($) { return field($_[0], "target_cert"); }
sub owner_urn($) { return field($_[0], "owner_cert")->urn(); }
sub target_urn($) { return field($_[0], "target_cert")->urn(); }
sub owner_uuid($) { return field($_[0], "owner_cert")->uuid(); }
sub target_uuid($) { return field($_[0], "target_cert")->uuid(); }
sub signer_certs($) { return field($_[0], "signer_certs"); }
# Break circular reference someplace to avoid exit errors.
sub DESTROY {
my $self = shift;
$self = undef;
}
sub CreateFromSigned($$$)
{
my ($class, $self, $root) = @_;
my $msg = undef;
# Find an element (which must exist exactly once) within a node.
my $find = sub
{
my( $node, $name ) = @_;
my @cnodes = grep( $_->nodeName eq $name, $node->childNodes );
return undef unless scalar( @cnodes ) == 1;
return $cnodes[ 0 ];
};
my $credential_el = &$find( $root, "credential" );
goto bad unless defined( $credential_el );
my @signatures = @{ $self->{'signatures'} };
#
# There should be just a single signature at this point.
#
if (@signatures > 1) {
$msg = "Too many signatures in ABAC credential";
goto bad;
}
#
# Dig out the signer certs.
#
my $signer_certs = [];
my $cert_nodes = $signatures[0]->getElementsByTagName("X509Certificate");
foreach my $node (@$cert_nodes) {
my $signer_cert = $node->to_literal();
push(@$signer_certs, $signer_cert);
}
if (! @{ $signer_certs }) {
$msg = "Cannot find signer certs in credential";
goto bad;
}
$self->{'signer_certs'} = $signer_certs;
$self->{'idx'} = undef; # Only set when stored to DB.
bless($self, $class);
return $self;
bad:
if (!defined($msg)) {
$msg = "Internal error creating ABAC credential object";
}
print STDERR "$msg\n";
return undef;
}
#
# Set the owner/target cert so that this looks something like a
# normal credential.
#
sub SetTargetCert($$)
{
my ($self, $cert) = @_;
$self->{'target_cert'} = $cert;
return 0;
}
sub SetOwnerCert($$)
{
my ($self, $cert) = @_;
$self->{'owner_cert'} = $cert;
return 0;
}
sub CheckCredential($)
{
my ($self) = @_;
require ABAC;
my $context = ABAC::Context->new();
$context->load_attribute_chunk($self->asString());
if (0) {
print STDERR "Could not load $self into abac context\n";
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
"Could not load $self into abac context");
}
my $user = ABAC::ID->new_ID_chunk("-----BEGIN CERTIFICATE-----\n".
$self->target_cert()->cert() .
"-----END CERTIFICATE-----\n");
my $tool = ABAC::ID->new_ID_chunk("-----BEGIN CERTIFICATE-----\n".
$self->owner_cert()->cert() .
"-----END CERTIFICATE-----\n");
my ($success, $credentials) =
$context->query($user->keyid() . ".speaks_for_" . $user->keyid(),
$tool->keyid());
return GeniResponse->Create(GENIRESPONSE_ERROR, undef,
"Could not prove abac credential")
if (!$success);
return $self;
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -30,6 +30,7 @@
use strict;
use English;
use Getopt::Std;
use Data::Dumper;
#
# Generate a speaksfor credential, useful only for testing.
......@@ -37,11 +38,11 @@ use Getopt::Std;
#
sub usage()
{
print STDERR "Usage: $0 <user-urn> <speaker-urn>";
print STDERR "[permission,delegate ...]\n";
print STDERR "Usage: $0 [-a] <user-urn> <speaker-urn>";
exit(1);
}
my $optlist = "";
my $optlist = "a";
my $doabac = 0;
# Configure ...
my $TB = "@prefix@";
......@@ -75,6 +76,9 @@ my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"a"})) {
$doabac = 1;
}
usage()
if (@ARGV < 2);
my $user_urn = shift();
......@@ -98,14 +102,54 @@ if (!defined($speaker)) {
fatal("No such speaker in the DB");
}
my $credential = GeniCredential->Create($geniuser, $speaker);
fatal("Could not create credential")
if (!defined($credential));
$credential->SetType("speaksfor");
fatal("Could not sign speaksfor credential")
if ($credential->Sign($speaker->GetCertificate()));
if ($doabac) {
require ABAC;
import ABAC;
my $userfile = $geniuser->GetCertificate()->WriteToFile(1);
fatal("Could not write user cert/key to file!")
if (!defined($userfile));
my $speakerfile = $speaker->GetCertificate()->WriteToFile(1);
fatal("Could not write speaker cert/key to file!")
if (!defined($speakerfile));
my $abacuser = ABAC::ID->new($userfile);
fatal("Could not create user ABAC:ID")
if (!defined($abacuser));
$abacuser->load_privkey($userfile);
print $credential->{'string'};
my $abactool = ABAC::ID->new($speakerfile);
fatal("Could not create speaker ABAC:ID")
if (!defined($abactool));
my $abacattr = ABAC::Attribute->new($abacuser,
"speaks_for_" . $abacuser->keyid(),
365 * 24 * 60 * 60);
fatal("Could not create ABAC::Attribute")
if (!defined($abacattr));
$abacattr->principal($abactool->keyid());
$abacattr->bake();
my $xml = $abacattr->cert_chunk();
if (0) {
my $cred = GeniCredential->CreateFromSigned($xml);
$cred->SetTargetCert($geniuser->GetCertificate());
$cred->SetOwnerCert($speaker->GetCertificate());
$cred->CheckCredential();
}
else {
print $xml;
}
}
else {
my $credential = GeniCredential->Create($geniuser, $speaker);
fatal("Could not create credential")
if (!defined($credential));
$credential->SetType("speaksfor");
fatal("Could not sign speaksfor credential")
if ($credential->Sign($speaker->GetCertificate()));
print $credential->{'string'};
}
exit(0);
......@@ -44,7 +44,7 @@ datatypes xs = "http://www.w3.org/2001/XMLSchema-datatypes"
anyelementbody = (attribute * {text} | text | element * {anyelementbody} )*
# This is where we get the definition of RSpec from
include "../rspec/protogeni-rspec-common.rnc"
#include "../rspec/protogeni-rspec-common.rnc"
## Representation of a single privileges.
PrivilegeSpec = element privilege {
......@@ -84,6 +84,11 @@ TicketSpec = element ticket {
anyelementbody
}
## ABAC stuff. Not defined.
ABACSpec = element abac {
anyelementbody
}
## A list of signatures.
signatures = element signatures {
element sig:Signature { anyelementbody }+
......@@ -94,7 +99,7 @@ credentials = element credential {
## The ID for signature referencing.
attribute xml:id {xs:ID},
## The type of this credential. Currently a Privilege set or a Ticket.
element type { "privilege" | "ticket" | "capability" | "speaksfor" },
element type { "privilege" | "ticket" | "capability" | "speaksfor" | "abac"},
## A serial number.
element serial { xsd:string },
## GID of the owner of this credential.
......@@ -110,7 +115,7 @@ credentials = element credential {
## Expires on
element expires { xsd:dateTime },
## Privileges or a ticket
(PrivilegesSpec | TicketSpec | CapabilitiesSpec),
(PrivilegesSpec | TicketSpec | CapabilitiesSpec | ABACSpec),
## Optional Extensions
element extensions { anyelementbody }*,
## Parent that delegated to us
......@@ -123,3 +128,5 @@ SignedCredential = element signed-credential {
}
start = SignedCredential
......@@ -43,7 +43,6 @@
default namespace = "http://www.protogeni.net/resources/credential/0.1"
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" xmlns:sig="http://www.w3.org/2000/09/xmldsig#">
<xs:include schemaLocation="protogeni-rspec-common.xsd"/>
<xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="sig.xsd"/>
<xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
<xs:group name="anyelementbody">
......@@ -54,7 +53,10 @@
<xs:attributeGroup name="anyelementbody">
<xs:anyAttribute processContents="skip"/>
</xs:attributeGroup>
<!-- This is where we get the definition of RSpec from -->
<!--
This is where we get the definition of RSpec from
include "../rspec/protogeni-rspec-common.rnc"
-->
<xs:element name="privilege">
<xs:complexType>
<xs:sequence>
......@@ -122,6 +124,12 @@
<xs:documentation>The ticket must be "cashed in" by this date </xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="abac">
<xs:complexType mixed="true">
<xs:group ref="anyelementbody"/>
<xs:attributeGroup ref="anyelementbody"/>
</xs:complexType>
</xs:element>
<xs:element name="signatures">
<xs:complexType>
<xs:sequence>
......@@ -155,6 +163,7 @@
<xs:element ref="privileges"/>
<xs:element ref="ticket"/>
<xs:element ref="capabilities"/>
<xs:element ref="abac"/>
</xs:choice>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="extensions"/>
<xs:element minOccurs="0" ref="parent"/>
......@@ -172,6 +181,7 @@
<xs:enumeration value="ticket"/>
<xs:enumeration value="capability"/>
<xs:enumeration value="speaksfor"/>
<xs:enumeration value="abac"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
......
#!/usr/bin/perl -w
#
# Copyright (c) 2008-2012 University of Utah and the Flux Group.
# Copyright (c) 2008-2013 University of Utah and the Flux Group.
#
# {{{GENIPUBLIC-LICENSE
#
......@@ -337,10 +337,14 @@ if( opendir( DIR, "$GENICERTS/local" ) ) {
# covers the credential and all the parents up to the top.
#
foreach my $sigid (keys(%credentials)) {
my $nosigref = 0;
again:
my $cmd = "$XMLSEC1 --verify --store-signatures ";
$cmd .= "--print-debug "
if ($debug);
$cmd .= "--node-id Sig_$sigid $certarg $xmlfile";
$cmd .= "--node-id ". ($nosigref ? $sigid : "Sig_${sigid}");
$cmd .= "$certarg $xmlfile";
print STDERR "$cmd\n"
if ($debug);
......@@ -370,9 +374,18 @@ foreach my $sigid (keys(%credentials)) {
$issuer = undef;
}
}
close(SEC) or
fatal($! ? "Error closing $XMLSEC1 pipe: $!"
: "Exit status $? from $XMLSEC1");
if (!close(SEC)) {
if (! $|) {
fatal("Error closing $XMLSEC1 pipe: $!");
}
if ($debug) {
print STDERR "Exit status $? from $XMLSEC1\n";
}
}
if ($bad && !$nosigref) {
$nosigref = 1;
goto again;
}
fatal("$sigid in $xmlfile did not verify!")
if ($bad);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment