From 57b6a99255d4a88be9c0f910f8524677e34ff4bc Mon Sep 17 00:00:00 2001 From: Thierry Parmentelat Date: Wed, 26 Oct 2011 18:09:10 +0200 Subject: [PATCH] trimmed useless imports, unstarred all imports --- sfa/managers/aggregate_manager.py | 4 - sfa/managers/component_manager_pl.py | 3 +- sfa/methods/CreateSliver.py | 2 +- sfa/methods/DeleteSliver.py | 1 - sfa/methods/GetCredential.py | 3 - sfa/methods/GetGids.py | 9 +- sfa/methods/GetSelfCredential.py | 4 +- sfa/methods/GetTicket.py | 7 +- sfa/methods/GetVersion.py | 1 - sfa/methods/List.py | 1 - sfa/methods/ListResources.py | 2 - sfa/methods/ListSlices.py | 2 - sfa/methods/RedeemTicket.py | 2 - sfa/methods/Register.py | 7 - sfa/methods/RegisterPeerObject.py | 7 +- sfa/methods/Remove.py | 1 - sfa/methods/RemovePeerObject.py | 5 +- sfa/methods/RenewSliver.py | 5 +- sfa/methods/Resolve.py | 3 +- sfa/methods/ResolveGENI.py | 2 - sfa/methods/Shutdown.py | 2 - sfa/methods/SliverStatus.py | 1 - sfa/methods/Start.py | 2 - sfa/methods/Stop.py | 2 - sfa/methods/Update.py | 4 +- sfa/methods/UpdateSliver.py | 2 - sfa/methods/get_aggregates.py | 3 - sfa/methods/get_key.py | 5 +- sfa/methods/get_registries.py | 5 +- sfa/methods/get_trusted_certs.py | 2 +- sfa/methods/register_peer_object.py | 6 +- sfa/methods/remove_peer_object.py | 5 +- sfa/methods/reset_slice.py | 3 - sfa/plc/aggregate.py | 7 +- sfa/plc/api.py | 28 +- sfa/plc/network.py | 2 +- sfa/plc/sfa-import-plc.py | 18 +- sfa/plc/sfa-nuke-plc.py | 2 - sfa/plc/sfaImport.py | 10 +- sfa/plc/slices.py | 17 +- sfa/rspecs/pg_rspec_converter.py | 2 +- sfa/rspecs/rspec.py | 8 +- sfa/rspecs/sfa_rspec_converter.py | 4 +- sfa/rspecs/versions/pgv2.py | 2 +- sfa/server/aggregate.py | 1 - sfa/server/componentserver.py | 18 +- sfa/server/interface.py | 3 +- sfa/server/registry.py | 2 - sfa/server/sfa_component_setup.py | 2 +- sfa/server/threadedserver.py | 2 - sfa/trust/auth.py | 6 +- sfa/trust/certificate.py | 1574 +++++++++---------- sfa/trust/credential.py | 2127 +++++++++++++------------- sfa/trust/credential_legacy.py | 3 +- sfa/trust/gid.py | 2 +- sfa/trust/hierarchy.py | 4 +- sfa/trust/sfaticket.py | 3 +- sfa/util/PostgreSQL.py | 2 +- sfa/util/api.py | 11 +- sfa/util/filter.py | 5 +- sfa/util/method.py | 10 +- sfa/util/parameter.py | 4 +- sfa/util/policy.py | 2 +- sfa/util/rspecHelper.py | 2 +- sfa/util/table.py | 12 +- sfa/util/xml.py | 15 +- sfa/util/xrn.py | 4 +- sfatables/commands/Add.py | 4 +- sfatables/commands/Delete.py | 2 +- sfatables/commands/Insert.py | 2 +- sfatables/commands/List.py | 2 +- sfatables/runtime.py | 10 +- sfatables/sfatables | 4 +- sfatables/xmlextension.py | 1 - sfatables/xmlrule.py | 8 +- 75 files changed, 1978 insertions(+), 2082 deletions(-) diff --git a/sfa/managers/aggregate_manager.py b/sfa/managers/aggregate_manager.py index 923b94f1..061df121 100644 --- a/sfa/managers/aggregate_manager.py +++ b/sfa/managers/aggregate_manager.py @@ -5,7 +5,6 @@ import sys from sfa.util.faults import RecordNotFound, SliverDoesNotExist from sfa.util.xrn import get_authority, hrn_to_urn, urn_to_hrn, Xrn, urn_to_sliver_id from sfa.util.plxrn import slicename_to_hrn, hrn_to_pl_slicename -#from sfa.util.record import * from sfa.util.version import version_core from sfa.util.sfatime import utcparse from sfa.util.callids import Callids @@ -19,9 +18,6 @@ import sfa.plc.peers as peers from sfa.plc.api import SfaAPI from sfa.plc.aggregate import Aggregate from sfa.plc.slices import Slices -#unused? -#from sfa.plc.network import * -#from sfa.plc.slices import * def GetVersion(api): diff --git a/sfa/managers/component_manager_pl.py b/sfa/managers/component_manager_pl.py index ec5e24c6..8aca53c8 100644 --- a/sfa/managers/component_manager_pl.py +++ b/sfa/managers/component_manager_pl.py @@ -1,7 +1,6 @@ -import os import xmlrpclib -from sfa.util.faults import * +from sfa.util.faults import SliverDoesNotExist from sfa.util.plxrn import PlXrn from sfa.trust.sfaticket import SfaTicket from sfa.util.version import version_core diff --git a/sfa/methods/CreateSliver.py b/sfa/methods/CreateSliver.py index 7895de3c..dcb25eb4 100644 --- a/sfa/methods/CreateSliver.py +++ b/sfa/methods/CreateSliver.py @@ -1,4 +1,4 @@ -from sfa.util.faults import * +from sfa.util.faults import SfaInvalidArgument from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed diff --git a/sfa/methods/DeleteSliver.py b/sfa/methods/DeleteSliver.py index ae301777..f766cb12 100644 --- a/sfa/methods/DeleteSliver.py +++ b/sfa/methods/DeleteSliver.py @@ -1,4 +1,3 @@ -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed diff --git a/sfa/methods/GetCredential.py b/sfa/methods/GetCredential.py index 34a4cb93..da3e97b7 100644 --- a/sfa/methods/GetCredential.py +++ b/sfa/methods/GetCredential.py @@ -1,6 +1,3 @@ -# -from sfa.trust.rights import * -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed diff --git a/sfa/methods/GetGids.py b/sfa/methods/GetGids.py index 37ad7967..e50f9404 100644 --- a/sfa/methods/GetGids.py +++ b/sfa/methods/GetGids.py @@ -1,9 +1,6 @@ -from sfa.util.faults import * +from sfa.util.faults import RecordNotFound from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth -from sfa.trust.gid import GID -from sfa.trust.certificate import Certificate from sfa.trust.credential import Credential class GetGids(Method): @@ -30,15 +27,15 @@ class GetGids(Method): def call(self, xrns, creds): # validate the credential valid_creds = self.api.auth.checkCredentials(creds, 'getgids') + # xxxpylintxxx origin_hrn is unused.. origin_hrn = Credential(string=valid_creds[0]).get_gid_caller().get_hrn() # resolve the record manager = self.api.get_interface_manager() records = manager.resolve(self.api, xrns, full = False) if not records: - raise RecordNotFound(hrns) + raise RecordNotFound(xrns) - gids = [] allowed_fields = ['hrn', 'type', 'gid'] for record in records: for key in record.keys(): diff --git a/sfa/methods/GetSelfCredential.py b/sfa/methods/GetSelfCredential.py index 6a8261c9..de21ab57 100644 --- a/sfa/methods/GetSelfCredential.py +++ b/sfa/methods/GetSelfCredential.py @@ -1,12 +1,10 @@ -from sfa.util.faults import * +from sfa.util.faults import RecordNotFound, ConnectionKeyGIDMismatch from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed from sfa.util.record import SfaRecord -from sfa.trust.credential import Credential from sfa.trust.certificate import Certificate -from sfa.trust.rights import Right, Rights class GetSelfCredential(Method): """ diff --git a/sfa/methods/GetTicket.py b/sfa/methods/GetTicket.py index 3a250d57..14696931 100644 --- a/sfa/methods/GetTicket.py +++ b/sfa/methods/GetTicket.py @@ -1,13 +1,10 @@ -import time -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth -from sfa.util.config import Config -from sfa.trust.credential import Credential from sfa.util.sfatablesRuntime import run_sfatables +from sfa.trust.credential import Credential + class GetTicket(Method): """ Retrieve a ticket. This operation is currently implemented on PLC diff --git a/sfa/methods/GetVersion.py b/sfa/methods/GetVersion.py index 2a8f6b21..72fe8061 100644 --- a/sfa/methods/GetVersion.py +++ b/sfa/methods/GetVersion.py @@ -1,4 +1,3 @@ -from sfa.util.faults import * from sfa.util.method import Method from sfa.util.parameter import Parameter diff --git a/sfa/methods/List.py b/sfa/methods/List.py index 8b4fcbea..a5d11235 100644 --- a/sfa/methods/List.py +++ b/sfa/methods/List.py @@ -1,5 +1,4 @@ -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed diff --git a/sfa/methods/ListResources.py b/sfa/methods/ListResources.py index fb831170..b8d7e2d0 100644 --- a/sfa/methods/ListResources.py +++ b/sfa/methods/ListResources.py @@ -1,7 +1,5 @@ -import sys import zlib -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed diff --git a/sfa/methods/ListSlices.py b/sfa/methods/ListSlices.py index fa65b074..7fc85130 100644 --- a/sfa/methods/ListSlices.py +++ b/sfa/methods/ListSlices.py @@ -1,7 +1,5 @@ -from sfa.util.faults import * from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth from sfa.trust.credential import Credential class ListSlices(Method): diff --git a/sfa/methods/RedeemTicket.py b/sfa/methods/RedeemTicket.py index 3aff1e75..cab0e931 100644 --- a/sfa/methods/RedeemTicket.py +++ b/sfa/methods/RedeemTicket.py @@ -1,5 +1,3 @@ -import xmlrpclib -from sfa.util.faults import * from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed diff --git a/sfa/methods/Register.py b/sfa/methods/Register.py index f4b78018..619ed00b 100644 --- a/sfa/methods/Register.py +++ b/sfa/methods/Register.py @@ -1,12 +1,5 @@ - -from sfa.trust.certificate import Keypair, convert_public_key -from sfa.trust.gid import * -from sfa.util.faults import * from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.util.record import SfaRecord -from sfa.trust.auth import Auth -from sfa.trust.gid import create_uuid from sfa.trust.credential import Credential class Register(Method): diff --git a/sfa/methods/RegisterPeerObject.py b/sfa/methods/RegisterPeerObject.py index 11fd1bdf..2eec2f5d 100644 --- a/sfa/methods/RegisterPeerObject.py +++ b/sfa/methods/RegisterPeerObject.py @@ -1,14 +1,9 @@ -from sfa.trust.certificate import Keypair, convert_public_key -from sfa.trust.gid import * - -from sfa.util.faults import * +from sfa.util.faults import SfaInvalidArgument from sfa.util.xrn import get_authority from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed from sfa.util.record import SfaRecord from sfa.util.table import SfaTable -from sfa.trust.auth import Auth -from sfa.trust.gid import create_uuid from sfa.trust.credential import Credential class RegisterPeerObject(Method): diff --git a/sfa/methods/Remove.py b/sfa/methods/Remove.py index 73437a39..c547c262 100644 --- a/sfa/methods/Remove.py +++ b/sfa/methods/Remove.py @@ -1,4 +1,3 @@ -from sfa.util.faults import * from sfa.util.xrn import Xrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed diff --git a/sfa/methods/RemovePeerObject.py b/sfa/methods/RemovePeerObject.py index 460aa98c..fa30e882 100644 --- a/sfa/methods/RemovePeerObject.py +++ b/sfa/methods/RemovePeerObject.py @@ -1,11 +1,8 @@ -from sfa.util.faults import * +from sfa.util.faults import UnknownSfaType, SfaInvalidArgument from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth -from sfa.util.record import SfaRecord from sfa.util.table import SfaTable from sfa.trust.credential import Credential -from types import StringTypes class RemovePeerObject(Method): """ diff --git a/sfa/methods/RenewSliver.py b/sfa/methods/RenewSliver.py index 1669517a..4a0e8361 100644 --- a/sfa/methods/RenewSliver.py +++ b/sfa/methods/RenewSliver.py @@ -1,10 +1,11 @@ -from sfa.util.faults import * +import datetime + +from sfa.util.faults import InsufficientRights from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter from sfa.trust.credential import Credential from sfa.util.sfatime import utcparse -import datetime class RenewSliver(Method): """ diff --git a/sfa/methods/Resolve.py b/sfa/methods/Resolve.py index 36b2bde0..1e1e7a44 100644 --- a/sfa/methods/Resolve.py +++ b/sfa/methods/Resolve.py @@ -1,6 +1,5 @@ -import traceback import types -from sfa.util.faults import * + from sfa.util.xrn import Xrn, urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed diff --git a/sfa/methods/ResolveGENI.py b/sfa/methods/ResolveGENI.py index c6e52726..d9781971 100644 --- a/sfa/methods/ResolveGENI.py +++ b/sfa/methods/ResolveGENI.py @@ -1,7 +1,5 @@ -from sfa.util.faults import * from sfa.util.method import Method from sfa.util.parameter import Parameter -from sfa.trust.credential import Credential class ResolveGENI(Method): """ diff --git a/sfa/methods/Shutdown.py b/sfa/methods/Shutdown.py index 00142b64..9788608a 100644 --- a/sfa/methods/Shutdown.py +++ b/sfa/methods/Shutdown.py @@ -1,5 +1,3 @@ -from sfa.util.faults import * -from sfa.util.method import Method from sfa.util.parameter import Parameter from sfa.methods.Stop import Stop diff --git a/sfa/methods/SliverStatus.py b/sfa/methods/SliverStatus.py index 231bec56..18613b2a 100644 --- a/sfa/methods/SliverStatus.py +++ b/sfa/methods/SliverStatus.py @@ -1,4 +1,3 @@ -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed diff --git a/sfa/methods/Start.py b/sfa/methods/Start.py index e119f3cf..6882a37b 100644 --- a/sfa/methods/Start.py +++ b/sfa/methods/Start.py @@ -1,8 +1,6 @@ -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth from sfa.trust.credential import Credential class Start(Method): diff --git a/sfa/methods/Stop.py b/sfa/methods/Stop.py index cdae0fcf..e8d3397e 100644 --- a/sfa/methods/Stop.py +++ b/sfa/methods/Stop.py @@ -1,8 +1,6 @@ -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth from sfa.trust.credential import Credential class Stop(Method): diff --git a/sfa/methods/Update.py b/sfa/methods/Update.py index aa881ea2..31b17e99 100644 --- a/sfa/methods/Update.py +++ b/sfa/methods/Update.py @@ -1,7 +1,5 @@ -import time -from sfa.util.faults import * from sfa.util.method import Method -from sfa.util.parameter import Parameter, Mixed +from sfa.util.parameter import Parameter from sfa.trust.credential import Credential class Update(Method): diff --git a/sfa/methods/UpdateSliver.py b/sfa/methods/UpdateSliver.py index 83075726..f9baae4a 100644 --- a/sfa/methods/UpdateSliver.py +++ b/sfa/methods/UpdateSliver.py @@ -1,5 +1,3 @@ -from sfa.util.faults import * -from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed from sfa.methods.CreateSliver import CreateSliver diff --git a/sfa/methods/get_aggregates.py b/sfa/methods/get_aggregates.py index 59d6001a..23c8d609 100644 --- a/sfa/methods/get_aggregates.py +++ b/sfa/methods/get_aggregates.py @@ -1,9 +1,6 @@ -from types import StringTypes -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth from sfa.server.aggregate import Aggregates class get_aggregates(Method): diff --git a/sfa/methods/get_key.py b/sfa/methods/get_key.py index 9cec0ec5..4bb65874 100644 --- a/sfa/methods/get_key.py +++ b/sfa/methods/get_key.py @@ -1,11 +1,10 @@ import os import tempfile import commands -from sfa.util.faults import * +from sfa.util.faults import NonExistingRecord, RecordNotFound from sfa.util.xrn import hrn_to_urn from sfa.util.method import Method -from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth +from sfa.util.parameter import Parameter from sfa.util.table import SfaTable from sfa.trust.certificate import Keypair from sfa.trust.gid import create_uuid diff --git a/sfa/methods/get_registries.py b/sfa/methods/get_registries.py index b404bb97..65d94449 100644 --- a/sfa/methods/get_registries.py +++ b/sfa/methods/get_registries.py @@ -1,9 +1,6 @@ -from types import StringTypes -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth from sfa.server.registry import Registries class get_registries(Method): @@ -28,7 +25,7 @@ class get_registries(Method): def call(self, cred, xrn = None): hrn, type = urn_to_hrn(xrn) self.api.auth.check(cred, 'list') - registries = Registries(self.api).interfaces.values() + registries = Registries(self.api).values() if hrn: registries = [reg for reg in registries if reg['hrn'] == hrn] return registries diff --git a/sfa/methods/get_trusted_certs.py b/sfa/methods/get_trusted_certs.py index 704fd429..460ab4dc 100644 --- a/sfa/methods/get_trusted_certs.py +++ b/sfa/methods/get_trusted_certs.py @@ -1,4 +1,4 @@ -from sfa.util.faults import * +#from sfa.util.faults import * from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed from sfa.trust.auth import Auth diff --git a/sfa/methods/register_peer_object.py b/sfa/methods/register_peer_object.py index 484fae50..42ef2408 100644 --- a/sfa/methods/register_peer_object.py +++ b/sfa/methods/register_peer_object.py @@ -1,14 +1,10 @@ -from sfa.trust.certificate import Keypair, convert_public_key -from sfa.trust.gid import * -from sfa.util.faults import * +from sfa.util.faults import SfaInvalidArgument from sfa.util.xrn import get_authority from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed from sfa.util.record import SfaRecord from sfa.util.table import SfaTable -from sfa.trust.auth import Auth -from sfa.trust.gid import create_uuid from sfa.trust.credential import Credential class register_peer_object(Method): diff --git a/sfa/methods/remove_peer_object.py b/sfa/methods/remove_peer_object.py index 41d74dc1..a5101895 100644 --- a/sfa/methods/remove_peer_object.py +++ b/sfa/methods/remove_peer_object.py @@ -1,11 +1,8 @@ -from sfa.util.faults import * +from sfa.util.faults import UnknownSfaType, SfaInvalidArgument from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth -from sfa.util.record import SfaRecord from sfa.util.table import SfaTable from sfa.trust.credential import Credential -from types import StringTypes class remove_peer_object(Method): """ diff --git a/sfa/methods/reset_slice.py b/sfa/methods/reset_slice.py index a4e9c5e2..15fb4a5b 100644 --- a/sfa/methods/reset_slice.py +++ b/sfa/methods/reset_slice.py @@ -1,9 +1,6 @@ -from sfa.util.faults import * from sfa.util.xrn import urn_to_hrn from sfa.util.method import Method from sfa.util.parameter import Parameter, Mixed -from sfa.trust.auth import Auth -from sfa.plc.slices import Slices class reset_slice(Method): """ diff --git a/sfa/plc/aggregate.py b/sfa/plc/aggregate.py index 5ad2bfff..d6997421 100644 --- a/sfa/plc/aggregate.py +++ b/sfa/plc/aggregate.py @@ -1,9 +1,6 @@ #!/usr/bin/python -from sfa.util.xrn import * -from sfa.util.plxrn import * -#from sfa.rspecs.sfa_rspec import SfaRSpec -#from sfa.rspecs.pg_rspec import PGRSpec -#from sfa.rspecs.rspec_version import RSpecVersion +from sfa.util.xrn import hrn_to_urn, urn_to_hrn +from sfa.util.plxrn import PlXrn, hostname_to_urn, hrn_to_pl_slicename from sfa.rspecs.rspec import RSpec from sfa.rspecs.version_manager import VersionManager from sfa.plc.vlink import get_tc_rate diff --git a/sfa/plc/api.py b/sfa/plc/api.py index 8a03a87c..4ce2f9e8 100644 --- a/sfa/plc/api.py +++ b/sfa/plc/api.py @@ -2,25 +2,26 @@ # SFA XML-RPC and SOAP interfaces # -import sys import os -import traceback -import string +import tempfile import datetime import xmlrpclib -from sfa.util.faults import * -from sfa.util.api import * -from sfa.util.config import * +from sfa.util.faults import RecordNotFound, MissingSfaInfo +from sfa.util.api import BaseAPI +from sfa.util.config import Config from sfa.util.sfalogging import logger import sfa.util.xmlrpcprotocol as xmlrpcprotocol -from sfa.trust.auth import Auth -from sfa.trust.rights import Right, Rights, determine_rights -from sfa.trust.credential import Credential,Keypair -from sfa.trust.certificate import Certificate -from sfa.util.xrn import get_authority, hrn_to_urn -from sfa.util.plxrn import hostname_to_hrn, hrn_to_pl_slicename, hrn_to_pl_slicename, slicename_to_hrn +from sfa.util.xrn import hrn_to_urn +from sfa.util.plxrn import hostname_to_hrn, hrn_to_pl_slicename, \ + hrn_to_pl_slicename, slicename_to_hrn, hrn_to_pl_login_base from sfa.util.nodemanager import NodeManager + +from sfa.trust.auth import Auth +from sfa.trust.rights import determine_rights +from sfa.trust.credential import Credential +from sfa.trust.certificate import Certificate, Keypair +from sfa.trust.gid import GID try: from collections import defaultdict except: @@ -653,6 +654,7 @@ class ComponentAPI(BaseAPI): def sliver_exists(self): sliver_dict = self.nodemanager.GetXIDs() + ### xxx slicename is undefined if slicename in sliver_dict.keys(): return True else: @@ -706,6 +708,7 @@ class ComponentAPI(BaseAPI): cert_str = Certificate(filename=cert_filename).save_to_string(save_parents=True) registry = self.get_registry() cred = registry.GetSelfCredential(cert_str, hrn, 'node') + # xxx credfile is undefined Credential(string=cred).save_to_file(credfile, save_parents=True) return cred @@ -716,6 +719,7 @@ class ComponentAPI(BaseAPI): """ files = ["server.key", "server.cert", "node.cred"] for f in files: + # xxx KEYDIR is undefined, could be meant to be "/var/lib/sfa/" from sfa_component_setup.py filepath = KEYDIR + os.sep + f if os.path.isfile(filepath): os.unlink(f) diff --git a/sfa/plc/network.py b/sfa/plc/network.py index 9276fb0f..5b2983b7 100644 --- a/sfa/plc/network.py +++ b/sfa/plc/network.py @@ -6,7 +6,7 @@ from StringIO import StringIO from lxml import etree from xmlbuilder import XMLBuilder -from sfa.util.faults import * +from sfa.util.faults import InvalidRSpec from sfa.util.xrn import get_authority from sfa.util.plxrn import hrn_to_pl_slicename, hostname_to_urn diff --git a/sfa/plc/sfa-import-plc.py b/sfa/plc/sfa-import-plc.py index 95793a10..0781a605 100755 --- a/sfa/plc/sfa-import-plc.py +++ b/sfa/plc/sfa-import-plc.py @@ -14,22 +14,17 @@ # RSA keys at this time, not DSA keys. ## +import os import getopt import sys -import tempfile -from sfa.util.record import * from sfa.util.table import SfaTable from sfa.util.xrn import get_leaf, get_authority from sfa.util.plxrn import hostname_to_hrn, slicename_to_hrn, email_to_hrn, hrn_to_pl_slicename from sfa.util.config import Config -from sfa.trust.certificate import convert_public_key, Keypair -from sfa.trust.trustedroots import * -from sfa.trust.hierarchy import * from sfa.util.xrn import Xrn -from sfa.plc.api import * -from sfa.trust.gid import create_uuid -from sfa.plc.sfaImport import sfaImport, _cleanup_string + +from sfa.plc.sfaImport import sfaImport def process_options(): @@ -125,7 +120,8 @@ def main(): sites_dict[site['login_base']] = site # Get all plc users - persons = shell.GetPersons(plc_auth, {'peer_id': None, 'enabled': True}, ['person_id', 'email', 'key_ids', 'site_ids']) + persons = shell.GetPersons(plc_auth, {'peer_id': None, 'enabled': True}, + ['person_id', 'email', 'key_ids', 'site_ids']) persons_dict = {} for person in persons: persons_dict[person['person_id']] = person @@ -173,8 +169,12 @@ def main(): continue node = nodes_dict[node_id] site_auth = get_authority(site_hrn) + print 'site_auth',site_auth site_name = get_leaf(site_hrn) hrn = hostname_to_hrn(site_auth, site_name, node['hostname']) + print 'site_name',site_name + print 'node hostname',node['hostname'] + print 'hrn',hrn if hrn not in existing_hrns or \ (hrn, 'node') not in existing_records: sfaImporter.import_node(hrn, node) diff --git a/sfa/plc/sfa-nuke-plc.py b/sfa/plc/sfa-nuke-plc.py index fb84020b..be7b0c10 100755 --- a/sfa/plc/sfa-nuke-plc.py +++ b/sfa/plc/sfa-nuke-plc.py @@ -11,8 +11,6 @@ import sys import os from optparse import OptionParser -from sfa.trust.hierarchy import * -from sfa.util.record import * from sfa.util.table import SfaTable from sfa.util.sfalogging import logger diff --git a/sfa/plc/sfaImport.py b/sfa/plc/sfaImport.py index 1effe718..4de6e1bd 100644 --- a/sfa/plc/sfaImport.py +++ b/sfa/plc/sfaImport.py @@ -8,20 +8,16 @@ # RSA keys at this time, not DSA keys. ## -import getopt -import sys -import tempfile - from sfa.util.sfalogging import _SfaLogger -from sfa.util.record import * +from sfa.util.record import SfaRecord from sfa.util.table import SfaTable from sfa.util.xrn import get_authority, hrn_to_urn from sfa.util.plxrn import email_to_hrn from sfa.util.config import Config from sfa.trust.certificate import convert_public_key, Keypair from sfa.trust.trustedroots import TrustedRoots -from sfa.trust.hierarchy import * +from sfa.trust.hierarchy import Hierarchy from sfa.trust.gid import create_uuid @@ -234,8 +230,6 @@ class sfaImport: def import_site(self, hrn, site): - shell = self.shell - plc_auth = self.plc_auth urn = hrn_to_urn(hrn, 'authority') self.logger.info("Import: site %s"%hrn) diff --git a/sfa/plc/slices.py b/sfa/plc/slices.py index 0b269029..9180446f 100644 --- a/sfa/plc/slices.py +++ b/sfa/plc/slices.py @@ -1,17 +1,11 @@ -import datetime -import time -import traceback -import sys - from types import StringTypes -from sfa.util.xrn import Xrn, get_leaf, get_authority, hrn_to_urn, urn_to_hrn -from sfa.util.plxrn import hrn_to_pl_slicename, hrn_to_pl_login_base -from sfa.util.faults import * -from sfa.util.record import SfaRecord +from collections import defaultdict + +from sfa.util.xrn import get_leaf, get_authority, urn_to_hrn +from sfa.util.plxrn import hrn_to_pl_slicename from sfa.util.policy import Policy + from sfa.plc.vlink import VLink -from sfa.util.prefixTree import prefixTree -from collections import defaultdict MAXINT = 2L**31-1 @@ -190,6 +184,7 @@ class Slices: self.api.logger.log_exc('Failed to add/remove slice from nodes') def verify_slice_links(self, slice, links, peer=None): + # nodes is undefined here if not links or not nodes: return for link in links: diff --git a/sfa/rspecs/pg_rspec_converter.py b/sfa/rspecs/pg_rspec_converter.py index 42e7ccdf..1c57d7da 100755 --- a/sfa/rspecs/pg_rspec_converter.py +++ b/sfa/rspecs/pg_rspec_converter.py @@ -1,7 +1,7 @@ #!/usr/bin/python from lxml import etree from StringIO import StringIO -from sfa.util.xrn import * +from sfa.util.xrn import Xrn, urn_to_hrn from sfa.rspecs.rspec import RSpec from sfa.rspecs.version_manager import VersionManager diff --git a/sfa/rspecs/rspec.py b/sfa/rspecs/rspec.py index b86f996a..00e048b8 100755 --- a/sfa/rspecs/rspec.py +++ b/sfa/rspecs/rspec.py @@ -1,11 +1,11 @@ #!/usr/bin/python from datetime import datetime, timedelta + from sfa.util.xml import XML, XpathFilter -from sfa.rspecs.version_manager import VersionManager -from sfa.util.xrn import * -from sfa.util.plxrn import hostname_to_urn +from sfa.util.faults import InvalidRSpecElement + from sfa.rspecs.rspec_elements import RSpecElement, RSpecElements -from sfa.util.faults import SfaNotImplemented, InvalidRSpec, InvalidRSpecElement +from sfa.rspecs.version_manager import VersionManager class RSpec: diff --git a/sfa/rspecs/sfa_rspec_converter.py b/sfa/rspecs/sfa_rspec_converter.py index 6ba56c1d..7bcc7878 100755 --- a/sfa/rspecs/sfa_rspec_converter.py +++ b/sfa/rspecs/sfa_rspec_converter.py @@ -1,8 +1,6 @@ #!/usr/bin/python -from lxml import etree -from StringIO import StringIO -from sfa.util.xrn import * +from sfa.util.xrn import hrn_to_urn from sfa.rspecs.rspec import RSpec from sfa.rspecs.version_manager import VersionManager diff --git a/sfa/rspecs/versions/pgv2.py b/sfa/rspecs/versions/pgv2.py index 889cfec1..af5b367e 100644 --- a/sfa/rspecs/versions/pgv2.py +++ b/sfa/rspecs/versions/pgv2.py @@ -1,7 +1,7 @@ from lxml import etree from copy import deepcopy from StringIO import StringIO -from sfa.util.xrn import * +from sfa.util.xrn import urn_to_sliver_id from sfa.util.plxrn import hostname_to_urn, xrn_to_hostname from sfa.rspecs.rspec_version import BaseVersion from sfa.rspecs.rspec_elements import RSpecElement, RSpecElements diff --git a/sfa/server/aggregate.py b/sfa/server/aggregate.py index 6d7516d7..e7340e10 100644 --- a/sfa/server/aggregate.py +++ b/sfa/server/aggregate.py @@ -1,4 +1,3 @@ -from sfa.util.faults import * from sfa.server.sfaserver import SfaServer from sfa.util.xrn import hrn_to_urn from sfa.server.interface import Interfaces, Interface diff --git a/sfa/server/componentserver.py b/sfa/server/componentserver.py index 85969778..70e3dac5 100644 --- a/sfa/server/componentserver.py +++ b/sfa/server/componentserver.py @@ -6,22 +6,14 @@ # TODO: investigate ways to combine this with existing PLC server? ## -import sys -import traceback import threading -import socket, os -import SocketServer -import BaseHTTPServer -import SimpleHTTPServer +import socket import SimpleXMLRPCServer -from OpenSSL import SSL from sfa.util.sfalogging import logger from sfa.trust.certificate import Keypair, Certificate -from sfa.trust.credential import * -from sfa.util.faults import * from sfa.plc.api import ComponentAPI -from sfa.server.sfaserver import verify_callback, ThreadedServer +from sfa.server.threadedserver import ThreadedServer ## @@ -46,9 +38,9 @@ class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): peer_cert = Certificate() peer_cert.load_from_pyopenssl_x509(self.connection.get_peer_certificate()) self.api = ComponentAPI(peer_cert = peer_cert, - interface = self.server.interface, - key_file = self.server.key_file, - cert_file = self.server.cert_file) + interface = self.server.interface, + key_file = self.server.key_file, + cert_file = self.server.cert_file) # get arguments request = self.rfile.read(int(self.headers["content-length"])) # In previous versions of SimpleXMLRPCServer, _dispatch diff --git a/sfa/server/interface.py b/sfa/server/interface.py index 1ecee511..d2b96b7f 100644 --- a/sfa/server/interface.py +++ b/sfa/server/interface.py @@ -1,4 +1,4 @@ -from sfa.util.faults import * +#from sfa.util.faults import * from sfa.util.storage import XmlStorage import sfa.util.xmlrpcprotocol as xmlrpcprotocol @@ -28,6 +28,7 @@ class Interface: def get_server(self, key_file, cert_file, timeout=30): server = None if self.client_type == 'geniclientlight' and GeniClientLight: + # xxx url and self.api are undefined server = GeniClientLight(url, self.api.key_file, self.api.cert_file) else: server = xmlrpcprotocol.get_server(self.get_url(), key_file, cert_file, timeout) diff --git a/sfa/server/registry.py b/sfa/server/registry.py index b55f9acd..2a37c22a 100644 --- a/sfa/server/registry.py +++ b/sfa/server/registry.py @@ -2,8 +2,6 @@ # Registry is a SfaServer that implements the Registry interface # from sfa.server.sfaserver import SfaServer -from sfa.util.faults import * -from sfa.util.xrn import hrn_to_urn from sfa.server.interface import Interfaces, Interface from sfa.util.config import Config diff --git a/sfa/server/sfa_component_setup.py b/sfa/server/sfa_component_setup.py index b0a5a478..6d74468f 100755 --- a/sfa/server/sfa_component_setup.py +++ b/sfa/server/sfa_component_setup.py @@ -4,7 +4,7 @@ import os import tempfile from optparse import OptionParser -from sfa.util.faults import * +from sfa.util.faults import ConnectionKeyGIDMismatch from sfa.util.config import Config import sfa.util.xmlrpcprotocol as xmlrpcprotocol from sfa.util.plxrn import hrn_to_pl_slicename, slicename_to_hrn diff --git a/sfa/server/threadedserver.py b/sfa/server/threadedserver.py index 86d39cf7..03ba5b3c 100644 --- a/sfa/server/threadedserver.py +++ b/sfa/server/threadedserver.py @@ -21,8 +21,6 @@ from sfa.util.config import Config from sfa.util.cache import Cache from sfa.trust.certificate import Certificate from sfa.trust.trustedroots import TrustedRoots -#seems useless -#from sfa.trust.credential import * #can we get rid of that ? from sfa.plc.api import SfaAPI diff --git a/sfa/trust/auth.py b/sfa/trust/auth.py index 4e5cf563..dfab5dab 100644 --- a/sfa/trust/auth.py +++ b/sfa/trust/auth.py @@ -3,7 +3,8 @@ # import sys -from sfa.util.faults import * +from sfa.util.faults import InsufficientRights, MissingCallerGID, MissingTrustedRoots, PermissionError, \ + BadRequestHash, ConnectionKeyGIDMismatch, SfaPermissionDenied from sfa.util.sfalogging import logger from sfa.util.config import Config from sfa.util.xrn import get_authority @@ -148,6 +149,7 @@ class Auth: def authenticateCert(self, certStr, requestHash): cert = Certificate(string=certStr) + # xxx should be validateCred ?? self.validateCert(self, cert) def gidNoop(self, gidStr, value, requestHash): @@ -314,7 +316,7 @@ class Auth: if not isinstance(creds, list): creds = [creds] creds = [] - if not isinistance(caller_hrn_list, list): + if not isinstance(caller_hrn_list, list): caller_hrn_list = [caller_hrn_list] for cred in creds: try: diff --git a/sfa/trust/certificate.py b/sfa/trust/certificate.py index bcec9d61..f0a2d71c 100644 --- a/sfa/trust/certificate.py +++ b/sfa/trust/certificate.py @@ -1,784 +1,790 @@ -#---------------------------------------------------------------------- -# Copyright (c) 2008 Board of Trustees, Princeton University -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and/or hardware specification (the "Work") to -# deal in the Work without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Work, and to permit persons to whom the Work -# is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Work. -# -# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS -# IN THE WORK. -#---------------------------------------------------------------------- - -## -# SFA uses two crypto libraries: pyOpenSSL and M2Crypto to implement -# the necessary crypto functionality. Ideally just one of these libraries -# would be used, but unfortunately each of these libraries is independently -# lacking. The pyOpenSSL library is missing many necessary functions, and -# the M2Crypto library has crashed inside of some of the functions. The -# design decision is to use pyOpenSSL whenever possible as it seems more -# stable, and only use M2Crypto for those functions that are not possible -# in pyOpenSSL. -# -# This module exports two classes: Keypair and Certificate. -## -# - -import functools -import os -import tempfile -import base64 -import traceback -from tempfile import mkstemp - -from OpenSSL import crypto -import M2Crypto -from M2Crypto import X509 - -from sfa.util.sfalogging import logger -from sfa.util.xrn import urn_to_hrn -from sfa.util.faults import * -from sfa.util.sfalogging import logger - -glo_passphrase_callback = None - -## -# A global callback msy be implemented for requesting passphrases from the -# user. The function will be called with three arguments: -# -# keypair_obj: the keypair object that is calling the passphrase -# string: the string containing the private key that's being loaded -# x: unknown, appears to be 0, comes from pyOpenSSL and/or m2crypto -# -# The callback should return a string containing the passphrase. - -def set_passphrase_callback(callback_func): - global glo_passphrase_callback - - glo_passphrase_callback = callback_func - -## -# Sets a fixed passphrase. - -def set_passphrase(passphrase): - set_passphrase_callback( lambda k,s,x: passphrase ) - -## -# Check to see if a passphrase works for a particular private key string. -# Intended to be used by passphrase callbacks for input validation. - -def test_passphrase(string, passphrase): - try: - crypto.load_privatekey(crypto.FILETYPE_PEM, string, (lambda x: passphrase)) - return True - except: - return False - -def convert_public_key(key): - keyconvert_path = "/usr/bin/keyconvert.py" - if not os.path.isfile(keyconvert_path): - raise IOError, "Could not find keyconvert in %s" % keyconvert_path - - # we can only convert rsa keys - if "ssh-dss" in key: - return None - - (ssh_f, ssh_fn) = tempfile.mkstemp() - ssl_fn = tempfile.mktemp() - os.write(ssh_f, key) - os.close(ssh_f) - - cmd = keyconvert_path + " " + ssh_fn + " " + ssl_fn - os.system(cmd) - - # this check leaves the temporary file containing the public key so - # that it can be expected to see why it failed. - # TODO: for production, cleanup the temporary files - if not os.path.exists(ssl_fn): - return None - - k = Keypair() - try: - k.load_pubkey_from_file(ssl_fn) - except: - logger.log_exc("convert_public_key caught exception") - k = None - - # remove the temporary files - os.remove(ssh_fn) - os.remove(ssl_fn) - - return k - -## -# Public-private key pairs are implemented by the Keypair class. -# A Keypair object may represent both a public and private key pair, or it -# may represent only a public key (this usage is consistent with OpenSSL). - -class Keypair: - key = None # public/private keypair - m2key = None # public key (m2crypto format) - - ## - # Creates a Keypair object - # @param create If create==True, creates a new public/private key and - # stores it in the object - # @param string If string!=None, load the keypair from the string (PEM) - # @param filename If filename!=None, load the keypair from the file - - def __init__(self, create=False, string=None, filename=None): - if create: - self.create() - if string: - self.load_from_string(string) - if filename: - self.load_from_file(filename) - - ## - # Create a RSA public/private key pair and store it inside the keypair object - - def create(self): - self.key = crypto.PKey() - self.key.generate_key(crypto.TYPE_RSA, 1024) - - ## - # Save the private key to a file - # @param filename name of file to store the keypair in - - def save_to_file(self, filename): - open(filename, 'w').write(self.as_pem()) - self.filename=filename - - ## - # Load the private key from a file. Implicity the private key includes the public key. - - def load_from_file(self, filename): - self.filename=filename - buffer = open(filename, 'r').read() - self.load_from_string(buffer) - - ## - # Load the private key from a string. Implicitly the private key includes the public key. - - def load_from_string(self, string): - if glo_passphrase_callback: - self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string, functools.partial(glo_passphrase_callback, self, string) ) - self.m2key = M2Crypto.EVP.load_key_string(string, functools.partial(glo_passphrase_callback, self, string) ) - else: - self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string) - self.m2key = M2Crypto.EVP.load_key_string(string) - - ## - # Load the public key from a string. No private key is loaded. - - def load_pubkey_from_file(self, filename): - # load the m2 public key - m2rsakey = M2Crypto.RSA.load_pub_key(filename) - self.m2key = M2Crypto.EVP.PKey() - self.m2key.assign_rsa(m2rsakey) - - # create an m2 x509 cert - m2name = M2Crypto.X509.X509_Name() - m2name.add_entry_by_txt(field="CN", type=0x1001, entry="junk", len=-1, loc=-1, set=0) - m2x509 = M2Crypto.X509.X509() - m2x509.set_pubkey(self.m2key) - m2x509.set_serial_number(0) - m2x509.set_issuer_name(m2name) - m2x509.set_subject_name(m2name) - ASN1 = M2Crypto.ASN1.ASN1_UTCTIME() - ASN1.set_time(500) - m2x509.set_not_before(ASN1) - m2x509.set_not_after(ASN1) - # x509v3 so it can have extensions - # prob not necc since this cert itself is junk but still... - m2x509.set_version(2) - junk_key = Keypair(create=True) - m2x509.sign(pkey=junk_key.get_m2_pkey(), md="sha1") - - # convert the m2 x509 cert to a pyopenssl x509 - m2pem = m2x509.as_pem() - pyx509 = crypto.load_certificate(crypto.FILETYPE_PEM, m2pem) - - # get the pyopenssl pkey from the pyopenssl x509 - self.key = pyx509.get_pubkey() - self.filename=filename - - ## - # Load the public key from a string. No private key is loaded. - - def load_pubkey_from_string(self, string): - (f, fn) = tempfile.mkstemp() - os.write(f, string) - os.close(f) - self.load_pubkey_from_file(fn) - os.remove(fn) - - ## - # Return the private key in PEM format. - - def as_pem(self): - return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key) - - ## - # Return an M2Crypto key object - - def get_m2_pkey(self): - if not self.m2key: - self.m2key = M2Crypto.EVP.load_key_string(self.as_pem()) - return self.m2key - - ## - # Returns a string containing the public key represented by this object. - - def get_pubkey_string(self): - m2pkey = self.get_m2_pkey() - return base64.b64encode(m2pkey.as_der()) - - ## - # Return an OpenSSL pkey object - - def get_openssl_pkey(self): - return self.key - - ## - # Given another Keypair object, return TRUE if the two keys are the same. - - def is_same(self, pkey): - return self.as_pem() == pkey.as_pem() - - def sign_string(self, data): - k = self.get_m2_pkey() - k.sign_init() - k.sign_update(data) - return base64.b64encode(k.sign_final()) - - def verify_string(self, data, sig): - k = self.get_m2_pkey() - k.verify_init() - k.verify_update(data) - return M2Crypto.m2.verify_final(k.ctx, base64.b64decode(sig), k.pkey) - - def compute_hash(self, value): - return self.sign_string(str(value)) - - # only informative - def get_filename(self): - return getattr(self,'filename',None) - - def dump (self, *args, **kwargs): - print self.dump_string(*args, **kwargs) - - def dump_string (self): - result="" - result += "KEYPAIR: pubkey=%40s..."%self.get_pubkey_string() - filename=self.get_filename() - if filename: result += "Filename %s\n"%filename - return result - -## -# The certificate class implements a general purpose X509 certificate, making -# use of the appropriate pyOpenSSL or M2Crypto abstractions. It also adds -# several addition features, such as the ability to maintain a chain of -# parent certificates, and storage of application-specific data. -# -# Certificates include the ability to maintain a chain of parents. Each -# certificate includes a pointer to it's parent certificate. When loaded -# from a file or a string, the parent chain will be automatically loaded. -# When saving a certificate to a file or a string, the caller can choose -# whether to save the parent certificates as well. - -class Certificate: - digest = "md5" - - cert = None - issuerKey = None - issuerSubject = None - parent = None - isCA = None # will be a boolean once set - - separator="-----parent-----" - - ## - # Create a certificate object. - # - # @param lifeDays life of cert in days - default is 1825==5 years - # @param create If create==True, then also create a blank X509 certificate. - # @param subject If subject!=None, then create a blank certificate and set - # it's subject name. - # @param string If string!=None, load the certficate from the string. - # @param filename If filename!=None, load the certficiate from the file. - # @param isCA If !=None, set whether this cert is for a CA - - def __init__(self, lifeDays=1825, create=False, subject=None, string=None, filename=None, isCA=None): - self.data = {} - if create or subject: - self.create(lifeDays) - if subject: - self.set_subject(subject) - if string: - self.load_from_string(string) - if filename: - self.load_from_file(filename) - - # Set the CA bit if a value was supplied - if isCA != None: - self.set_is_ca(isCA) - - # Create a blank X509 certificate and store it in this object. - - def create(self, lifeDays=1825): - self.cert = crypto.X509() - # FIXME: Use different serial #s - self.cert.set_serial_number(3) - self.cert.gmtime_adj_notBefore(0) # 0 means now - self.cert.gmtime_adj_notAfter(lifeDays*60*60*24) # five years is default - self.cert.set_version(2) # x509v3 so it can have extensions - - - ## - # Given a pyOpenSSL X509 object, store that object inside of this - # certificate object. - - def load_from_pyopenssl_x509(self, x509): - self.cert = x509 - - ## - # Load the certificate from a string - - def load_from_string(self, string): - # if it is a chain of multiple certs, then split off the first one and - # load it (support for the ---parent--- tag as well as normal chained certs) - - string = string.strip() - - # If it's not in proper PEM format, wrap it - if string.count('-----BEGIN CERTIFICATE') == 0: - string = '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----' % string - - # If there is a PEM cert in there, but there is some other text first - # such as the text of the certificate, skip the text - beg = string.find('-----BEGIN CERTIFICATE') - if beg > 0: - # skipping over non cert beginning - string = string[beg:] - - parts = [] - - if string.count('-----BEGIN CERTIFICATE-----') > 1 and \ - string.count(Certificate.separator) == 0: - parts = string.split('-----END CERTIFICATE-----',1) - parts[0] += '-----END CERTIFICATE-----' - else: - parts = string.split(Certificate.separator, 1) - - self.cert = crypto.load_certificate(crypto.FILETYPE_PEM, parts[0]) - - # if there are more certs, then create a parent and let the parent load - # itself from the remainder of the string - if len(parts) > 1 and parts[1] != '': - self.parent = self.__class__() - self.parent.load_from_string(parts[1]) - - ## - # Load the certificate from a file - - def load_from_file(self, filename): - file = open(filename) - string = file.read() - self.load_from_string(string) - self.filename=filename - - ## - # Save the certificate to a string. - # - # @param save_parents If save_parents==True, then also save the parent certificates. - - def save_to_string(self, save_parents=True): - string = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert) - if save_parents and self.parent: - string = string + self.parent.save_to_string(save_parents) - return string - - ## - # Save the certificate to a file. - # @param save_parents If save_parents==True, then also save the parent certificates. - - def save_to_file(self, filename, save_parents=True, filep=None): - string = self.save_to_string(save_parents=save_parents) - if filep: - f = filep - else: - f = open(filename, 'w') - f.write(string) - f.close() - self.filename=filename - - ## - # Save the certificate to a random file in /tmp/ - # @param save_parents If save_parents==True, then also save the parent certificates. - def save_to_random_tmp_file(self, save_parents=True): - fp, filename = mkstemp(suffix='cert', text=True) - fp = os.fdopen(fp, "w") - self.save_to_file(filename, save_parents=True, filep=fp) - return filename - - ## - # Sets the issuer private key and name - # @param key Keypair object containing the private key of the issuer - # @param subject String containing the name of the issuer - # @param cert (optional) Certificate object containing the name of the issuer - - def set_issuer(self, key, subject=None, cert=None): - self.issuerKey = key - if subject: - # it's a mistake to use subject and cert params at the same time - assert(not cert) - if isinstance(subject, dict) or isinstance(subject, str): - req = crypto.X509Req() - reqSubject = req.get_subject() - if (isinstance(subject, dict)): - for key in reqSubject.keys(): - setattr(reqSubject, key, subject[key]) - else: - setattr(reqSubject, "CN", subject) - subject = reqSubject - # subject is not valid once req is out of scope, so save req - self.issuerReq = req - if cert: - # if a cert was supplied, then get the subject from the cert - subject = cert.cert.get_subject() - assert(subject) - self.issuerSubject = subject - - ## - # Get the issuer name - - def get_issuer(self, which="CN"): - x = self.cert.get_issuer() - return getattr(x, which) - - ## - # Set the subject name of the certificate - - def set_subject(self, name): - req = crypto.X509Req() - subj = req.get_subject() - if (isinstance(name, dict)): - for key in name.keys(): - setattr(subj, key, name[key]) - else: - setattr(subj, "CN", name) - self.cert.set_subject(subj) - - ## - # Get the subject name of the certificate - - def get_subject(self, which="CN"): - x = self.cert.get_subject() - return getattr(x, which) - - ## - # Get a pretty-print subject name of the certificate - - def get_printable_subject(self): - x = self.cert.get_subject() - return "[ OU: %s, CN: %s, SubjectAltName: %s ]" % (getattr(x, "OU"), getattr(x, "CN"), self.get_data()) - - ## - # Get the public key of the certificate. - # - # @param key Keypair object containing the public key - - def set_pubkey(self, key): - assert(isinstance(key, Keypair)) - self.cert.set_pubkey(key.get_openssl_pkey()) - - ## - # Get the public key of the certificate. - # It is returned in the form of a Keypair object. - - def get_pubkey(self): - m2x509 = X509.load_cert_string(self.save_to_string()) - pkey = Keypair() - pkey.key = self.cert.get_pubkey() - pkey.m2key = m2x509.get_pubkey() - return pkey - - def set_intermediate_ca(self, val): - return self.set_is_ca(val) - - # Set whether this cert is for a CA. All signers and only signers should be CAs. - # The local member starts unset, letting us check that you only set it once - # @param val Boolean indicating whether this cert is for a CA - def set_is_ca(self, val): - if val is None: - return - - if self.isCA != None: - # Can't double set properties - raise "Cannot set basicConstraints CA:?? more than once. Was %s, trying to set as %s" % (self.isCA, val) - - self.isCA = val - if val: - self.add_extension('basicConstraints', 1, 'CA:TRUE') - else: - self.add_extension('basicConstraints', 1, 'CA:FALSE') - - - - ## - # Add an X509 extension to the certificate. Add_extension can only be called - # once for a particular extension name, due to limitations in the underlying - # library. - # - # @param name string containing name of extension - # @param value string containing value of the extension - - def add_extension(self, name, critical, value): - oldExtVal = None - try: - oldExtVal = self.get_extension(name) - except: - # M2Crypto LookupError when the extension isn't there (yet) - pass - - # This code limits you from adding the extension with the same value - # The method comment says you shouldn't do this with the same name - # But actually it (m2crypto) appears to allow you to do this. - if oldExtVal and oldExtVal == value: - # don't add this extension again - # just do nothing as here - return - # FIXME: What if they are trying to set with a different value? - # Is this ever OK? Or should we raise an exception? -# elif oldExtVal: -# raise "Cannot add extension %s which had val %s with new val %s" % (name, oldExtVal, value) - - ext = crypto.X509Extension (name, critical, value) - self.cert.add_extensions([ext]) - - ## - # Get an X509 extension from the certificate - - def get_extension(self, name): - - # pyOpenSSL does not have a way to get extensions - m2x509 = X509.load_cert_string(self.save_to_string()) - value = m2x509.get_ext(name).get_value() - - return value - - ## - # Set_data is a wrapper around add_extension. It stores the parameter str in - # the X509 subject_alt_name extension. Set_data can only be called once, due - # to limitations in the underlying library. - - def set_data(self, str, field='subjectAltName'): - # pyOpenSSL only allows us to add extensions, so if we try to set the - # same extension more than once, it will not work - if self.data.has_key(field): - raise "Cannot set ", field, " more than once" - self.data[field] = str - self.add_extension(field, 0, str) - - ## - # Return the data string that was previously set with set_data - - def get_data(self, field='subjectAltName'): - if self.data.has_key(field): - return self.data[field] - - try: - uri = self.get_extension(field) - self.data[field] = uri - except LookupError: - return None - - return self.data[field] - - ## - # Sign the certificate using the issuer private key and issuer subject previous set with set_issuer(). - - def sign(self): - logger.debug('certificate.sign') - assert self.cert != None - assert self.issuerSubject != None - assert self.issuerKey != None - self.cert.set_issuer(self.issuerSubject) - self.cert.sign(self.issuerKey.get_openssl_pkey(), self.digest) - - ## - # Verify the authenticity of a certificate. - # @param pkey is a Keypair object representing a public key. If Pkey - # did not sign the certificate, then an exception will be thrown. - - def verify(self, pkey): - # pyOpenSSL does not have a way to verify signatures - m2x509 = X509.load_cert_string(self.save_to_string()) - m2pkey = pkey.get_m2_pkey() - # verify it - return m2x509.verify(m2pkey) - - # XXX alternatively, if openssl has been patched, do the much simpler: - # try: - # self.cert.verify(pkey.get_openssl_key()) - # return 1 - # except: - # return 0 - - ## - # Return True if pkey is identical to the public key that is contained in the certificate. - # @param pkey Keypair object - - def is_pubkey(self, pkey): - return self.get_pubkey().is_same(pkey) - - ## - # Given a certificate cert, verify that this certificate was signed by the - # public key contained in cert. Throw an exception otherwise. - # - # @param cert certificate object - - def is_signed_by_cert(self, cert): - k = cert.get_pubkey() - result = self.verify(k) - return result - - ## - # Set the parent certficiate. - # - # @param p certificate object. - - def set_parent(self, p): - self.parent = p - - ## - # Return the certificate object of the parent of this certificate. - - def get_parent(self): - return self.parent - - ## - # Verification examines a chain of certificates to ensure that each parent - # signs the child, and that some certificate in the chain is signed by a - # trusted certificate. - # - # Verification is a basic recursion:
-    #     if this_certificate was signed by trusted_certs:
-    #         return
-    #     else
-    #         return verify_chain(parent, trusted_certs)
-    # 
- # - # At each recursion, the parent is tested to ensure that it did sign the - # child. If a parent did not sign a child, then an exception is thrown. If - # the bottom of the recursion is reached and the certificate does not match - # a trusted root, then an exception is thrown. - # Also require that parents are CAs. - # - # @param Trusted_certs is a list of certificates that are trusted. - # - - def verify_chain(self, trusted_certs = None): - # Verify a chain of certificates. Each certificate must be signed by - # the public key contained in it's parent. The chain is recursed - # until a certificate is found that is signed by a trusted root. - - # verify expiration time - if self.cert.has_expired(): - logger.debug("verify_chain: NO, Certificate %s has expired" % self.get_printable_subject()) - raise CertExpired(self.get_printable_subject(), "client cert") - - # if this cert is signed by a trusted_cert, then we are set - for trusted_cert in trusted_certs: - if self.is_signed_by_cert(trusted_cert): - # verify expiration of trusted_cert ? - if not trusted_cert.cert.has_expired(): - logger.debug("verify_chain: YES. Cert %s signed by trusted cert %s"%( - self.get_printable_subject(), trusted_cert.get_printable_subject())) - return trusted_cert - else: - logger.debug("verify_chain: NO. Cert %s is signed by trusted_cert %s, but that signer is expired..."%( - self.get_printable_subject(),trusted_cert.get_printable_subject())) - raise CertExpired(self.get_printable_subject()," signer trusted_cert %s"%trusted_cert.get_printable_subject()) - - # if there is no parent, then no way to verify the chain - if not self.parent: - logger.debug("verify_chain: NO. %s has no parent and issuer %s is not in %d trusted roots"%(self.get_printable_subject(), self.get_issuer(), len(trusted_certs))) - raise CertMissingParent(self.get_printable_subject() + ": Issuer %s not trusted by any of %d trusted roots, and cert has no parent." % (self.get_issuer(), len(trusted_certs))) - - # if it wasn't signed by the parent... - if not self.is_signed_by_cert(self.parent): - logger.debug("verify_chain: NO. %s is not signed by parent %s, but by %s"%self.get_printable_subject(), self.parent.get_printable_subject(), self.get_issuer()) - raise CertNotSignedByParent(self.get_printable_subject() + ": Parent %s, issuer %s" % (self.parent.get_printable_subject(), self.get_issuer())) - - # Confirm that the parent is a CA. Only CAs can be trusted as - # signers. - # Note that trusted roots are not parents, so don't need to be - # CAs. - # Ugly - cert objects aren't parsed so we need to read the - # extension and hope there are no other basicConstraints - if not self.parent.isCA and not (self.parent.get_extension('basicConstraints') == 'CA:TRUE'): - logger.warn("verify_chain: cert %s's parent %s is not a CA" % (self.get_printable_subject(), self.parent.get_printable_subject())) - raise CertNotSignedByParent(self.get_printable_subject() + ": Parent %s not a CA" % self.parent.get_printable_subject()) - - # if the parent isn't verified... - logger.debug("verify_chain: .. %s, -> verifying parent %s"%(self.get_printable_subject(),self.parent.get_printable_subject())) - self.parent.verify_chain(trusted_certs) - - return - - ### more introspection - def get_extensions(self): - # pyOpenSSL does not have a way to get extensions - triples=[] - m2x509 = X509.load_cert_string(self.save_to_string()) - nb_extensions=m2x509.get_ext_count() - logger.debug("X509 had %d extensions"%nb_extensions) - for i in range(nb_extensions): - ext=m2x509.get_ext_at(i) - triples.append( (ext.get_name(), ext.get_value(), ext.get_critical(),) ) - return triples - - def get_data_names(self): - return self.data.keys() - - def get_all_datas (self): - triples=self.get_extensions() - for name in self.get_data_names(): - triples.append( (name,self.get_data(name),'data',) ) - return triples - - # only informative - def get_filename(self): - return getattr(self,'filename',None) - - def dump (self, *args, **kwargs): - print self.dump_string(*args, **kwargs) - - def dump_string (self,show_extensions=False): - result = "" - result += "CERTIFICATE for %s\n"%self.get_printable_subject() - result += "Issued by %s\n"%self.get_issuer() - filename=self.get_filename() - if filename: result += "Filename %s\n"%filename - if show_extensions: - all_datas=self.get_all_datas() - result += " has %d extensions/data attached"%len(all_datas) - for (n,v,c) in all_datas: - if c=='data': - result += " data: %s=%s\n"%(n,v) - else: - result += " ext: %s (crit=%s)=<<<%s>>>\n"%(n,c,v) - return result +#---------------------------------------------------------------------- +# Copyright (c) 2008 Board of Trustees, Princeton University +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and/or hardware specification (the "Work") to +# deal in the Work without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Work, and to permit persons to whom the Work +# is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Work. +# +# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS +# IN THE WORK. +#---------------------------------------------------------------------- + +## +# SFA uses two crypto libraries: pyOpenSSL and M2Crypto to implement +# the necessary crypto functionality. Ideally just one of these libraries +# would be used, but unfortunately each of these libraries is independently +# lacking. The pyOpenSSL library is missing many necessary functions, and +# the M2Crypto library has crashed inside of some of the functions. The +# design decision is to use pyOpenSSL whenever possible as it seems more +# stable, and only use M2Crypto for those functions that are not possible +# in pyOpenSSL. +# +# This module exports two classes: Keypair and Certificate. +## +# + +import functools +import os +import tempfile +import base64 +from tempfile import mkstemp + +from OpenSSL import crypto +import M2Crypto +from M2Crypto import X509 + +from sfa.util.faults import CertExpired, CertMissingParent, CertNotSignedByParent +from sfa.util.sfalogging import logger + +glo_passphrase_callback = None + +## +# A global callback msy be implemented for requesting passphrases from the +# user. The function will be called with three arguments: +# +# keypair_obj: the keypair object that is calling the passphrase +# string: the string containing the private key that's being loaded +# x: unknown, appears to be 0, comes from pyOpenSSL and/or m2crypto +# +# The callback should return a string containing the passphrase. + +def set_passphrase_callback(callback_func): + global glo_passphrase_callback + + glo_passphrase_callback = callback_func + +## +# Sets a fixed passphrase. + +def set_passphrase(passphrase): + set_passphrase_callback( lambda k,s,x: passphrase ) + +## +# Check to see if a passphrase works for a particular private key string. +# Intended to be used by passphrase callbacks for input validation. + +def test_passphrase(string, passphrase): + try: + crypto.load_privatekey(crypto.FILETYPE_PEM, string, (lambda x: passphrase)) + return True + except: + return False + +def convert_public_key(key): + keyconvert_path = "/usr/bin/keyconvert.py" + if not os.path.isfile(keyconvert_path): + raise IOError, "Could not find keyconvert in %s" % keyconvert_path + + # we can only convert rsa keys + if "ssh-dss" in key: + return None + + (ssh_f, ssh_fn) = tempfile.mkstemp() + ssl_fn = tempfile.mktemp() + os.write(ssh_f, key) + os.close(ssh_f) + + cmd = keyconvert_path + " " + ssh_fn + " " + ssl_fn + os.system(cmd) + + # this check leaves the temporary file containing the public key so + # that it can be expected to see why it failed. + # TODO: for production, cleanup the temporary files + if not os.path.exists(ssl_fn): + return None + + k = Keypair() + try: + k.load_pubkey_from_file(ssl_fn) + except: + logger.log_exc("convert_public_key caught exception") + k = None + + # remove the temporary files + os.remove(ssh_fn) + os.remove(ssl_fn) + + return k + +## +# Public-private key pairs are implemented by the Keypair class. +# A Keypair object may represent both a public and private key pair, or it +# may represent only a public key (this usage is consistent with OpenSSL). + +class Keypair: + key = None # public/private keypair + m2key = None # public key (m2crypto format) + + ## + # Creates a Keypair object + # @param create If create==True, creates a new public/private key and + # stores it in the object + # @param string If string!=None, load the keypair from the string (PEM) + # @param filename If filename!=None, load the keypair from the file + + def __init__(self, create=False, string=None, filename=None): + if create: + self.create() + if string: + self.load_from_string(string) + if filename: + self.load_from_file(filename) + + ## + # Create a RSA public/private key pair and store it inside the keypair object + + def create(self): + self.key = crypto.PKey() + self.key.generate_key(crypto.TYPE_RSA, 1024) + + ## + # Save the private key to a file + # @param filename name of file to store the keypair in + + def save_to_file(self, filename): + open(filename, 'w').write(self.as_pem()) + self.filename=filename + + ## + # Load the private key from a file. Implicity the private key includes the public key. + + def load_from_file(self, filename): + self.filename=filename + buffer = open(filename, 'r').read() + self.load_from_string(buffer) + + ## + # Load the private key from a string. Implicitly the private key includes the public key. + + def load_from_string(self, string): + if glo_passphrase_callback: + self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string, functools.partial(glo_passphrase_callback, self, string) ) + self.m2key = M2Crypto.EVP.load_key_string(string, functools.partial(glo_passphrase_callback, self, string) ) + else: + self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string) + self.m2key = M2Crypto.EVP.load_key_string(string) + + ## + # Load the public key from a string. No private key is loaded. + + def load_pubkey_from_file(self, filename): + # load the m2 public key + m2rsakey = M2Crypto.RSA.load_pub_key(filename) + self.m2key = M2Crypto.EVP.PKey() + self.m2key.assign_rsa(m2rsakey) + + # create an m2 x509 cert + m2name = M2Crypto.X509.X509_Name() + m2name.add_entry_by_txt(field="CN", type=0x1001, entry="junk", len=-1, loc=-1, set=0) + m2x509 = M2Crypto.X509.X509() + m2x509.set_pubkey(self.m2key) + m2x509.set_serial_number(0) + m2x509.set_issuer_name(m2name) + m2x509.set_subject_name(m2name) + ASN1 = M2Crypto.ASN1.ASN1_UTCTIME() + ASN1.set_time(500) + m2x509.set_not_before(ASN1) + m2x509.set_not_after(ASN1) + # x509v3 so it can have extensions + # prob not necc since this cert itself is junk but still... + m2x509.set_version(2) + junk_key = Keypair(create=True) + m2x509.sign(pkey=junk_key.get_m2_pkey(), md="sha1") + + # convert the m2 x509 cert to a pyopenssl x509 + m2pem = m2x509.as_pem() + pyx509 = crypto.load_certificate(crypto.FILETYPE_PEM, m2pem) + + # get the pyopenssl pkey from the pyopenssl x509 + self.key = pyx509.get_pubkey() + self.filename=filename + + ## + # Load the public key from a string. No private key is loaded. + + def load_pubkey_from_string(self, string): + (f, fn) = tempfile.mkstemp() + os.write(f, string) + os.close(f) + self.load_pubkey_from_file(fn) + os.remove(fn) + + ## + # Return the private key in PEM format. + + def as_pem(self): + return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key) + + ## + # Return an M2Crypto key object + + def get_m2_pkey(self): + if not self.m2key: + self.m2key = M2Crypto.EVP.load_key_string(self.as_pem()) + return self.m2key + + ## + # Returns a string containing the public key represented by this object. + + def get_pubkey_string(self): + m2pkey = self.get_m2_pkey() + return base64.b64encode(m2pkey.as_der()) + + ## + # Return an OpenSSL pkey object + + def get_openssl_pkey(self): + return self.key + + ## + # Given another Keypair object, return TRUE if the two keys are the same. + + def is_same(self, pkey): + return self.as_pem() == pkey.as_pem() + + def sign_string(self, data): + k = self.get_m2_pkey() + k.sign_init() + k.sign_update(data) + return base64.b64encode(k.sign_final()) + + def verify_string(self, data, sig): + k = self.get_m2_pkey() + k.verify_init() + k.verify_update(data) + return M2Crypto.m2.verify_final(k.ctx, base64.b64decode(sig), k.pkey) + + def compute_hash(self, value): + return self.sign_string(str(value)) + + # only informative + def get_filename(self): + return getattr(self,'filename',None) + + def dump (self, *args, **kwargs): + print self.dump_string(*args, **kwargs) + + def dump_string (self): + result="" + result += "KEYPAIR: pubkey=%40s..."%self.get_pubkey_string() + filename=self.get_filename() + if filename: result += "Filename %s\n"%filename + return result + +## +# The certificate class implements a general purpose X509 certificate, making +# use of the appropriate pyOpenSSL or M2Crypto abstractions. It also adds +# several addition features, such as the ability to maintain a chain of +# parent certificates, and storage of application-specific data. +# +# Certificates include the ability to maintain a chain of parents. Each +# certificate includes a pointer to it's parent certificate. When loaded +# from a file or a string, the parent chain will be automatically loaded. +# When saving a certificate to a file or a string, the caller can choose +# whether to save the parent certificates as well. + +class Certificate: + digest = "md5" + + cert = None + issuerKey = None + issuerSubject = None + parent = None + isCA = None # will be a boolean once set + + separator="-----parent-----" + + ## + # Create a certificate object. + # + # @param lifeDays life of cert in days - default is 1825==5 years + # @param create If create==True, then also create a blank X509 certificate. + # @param subject If subject!=None, then create a blank certificate and set + # it's subject name. + # @param string If string!=None, load the certficate from the string. + # @param filename If filename!=None, load the certficiate from the file. + # @param isCA If !=None, set whether this cert is for a CA + + def __init__(self, lifeDays=1825, create=False, subject=None, string=None, filename=None, isCA=None): + self.data = {} + if create or subject: + self.create(lifeDays) + if subject: + self.set_subject(subject) + if string: + self.load_from_string(string) + if filename: + self.load_from_file(filename) + + # Set the CA bit if a value was supplied + if isCA != None: + self.set_is_ca(isCA) + + # Create a blank X509 certificate and store it in this object. + + def create(self, lifeDays=1825): + self.cert = crypto.X509() + # FIXME: Use different serial #s + self.cert.set_serial_number(3) + self.cert.gmtime_adj_notBefore(0) # 0 means now + self.cert.gmtime_adj_notAfter(lifeDays*60*60*24) # five years is default + self.cert.set_version(2) # x509v3 so it can have extensions + + + ## + # Given a pyOpenSSL X509 object, store that object inside of this + # certificate object. + + def load_from_pyopenssl_x509(self, x509): + self.cert = x509 + + ## + # Load the certificate from a string + + def load_from_string(self, string): + # if it is a chain of multiple certs, then split off the first one and + # load it (support for the ---parent--- tag as well as normal chained certs) + + string = string.strip() + + # If it's not in proper PEM format, wrap it + if string.count('-----BEGIN CERTIFICATE') == 0: + string = '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----' % string + + # If there is a PEM cert in there, but there is some other text first + # such as the text of the certificate, skip the text + beg = string.find('-----BEGIN CERTIFICATE') + if beg > 0: + # skipping over non cert beginning + string = string[beg:] + + parts = [] + + if string.count('-----BEGIN CERTIFICATE-----') > 1 and \ + string.count(Certificate.separator) == 0: + parts = string.split('-----END CERTIFICATE-----',1) + parts[0] += '-----END CERTIFICATE-----' + else: + parts = string.split(Certificate.separator, 1) + + self.cert = crypto.load_certificate(crypto.FILETYPE_PEM, parts[0]) + + # if there are more certs, then create a parent and let the parent load + # itself from the remainder of the string + if len(parts) > 1 and parts[1] != '': + self.parent = self.__class__() + self.parent.load_from_string(parts[1]) + + ## + # Load the certificate from a file + + def load_from_file(self, filename): + file = open(filename) + string = file.read() + self.load_from_string(string) + self.filename=filename + + ## + # Save the certificate to a string. + # + # @param save_parents If save_parents==True, then also save the parent certificates. + + def save_to_string(self, save_parents=True): + string = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert) + if save_parents and self.parent: + string = string + self.parent.save_to_string(save_parents) + return string + + ## + # Save the certificate to a file. + # @param save_parents If save_parents==True, then also save the parent certificates. + + def save_to_file(self, filename, save_parents=True, filep=None): + string = self.save_to_string(save_parents=save_parents) + if filep: + f = filep + else: + f = open(filename, 'w') + f.write(string) + f.close() + self.filename=filename + + ## + # Save the certificate to a random file in /tmp/ + # @param save_parents If save_parents==True, then also save the parent certificates. + def save_to_random_tmp_file(self, save_parents=True): + fp, filename = mkstemp(suffix='cert', text=True) + fp = os.fdopen(fp, "w") + self.save_to_file(filename, save_parents=True, filep=fp) + return filename + + ## + # Sets the issuer private key and name + # @param key Keypair object containing the private key of the issuer + # @param subject String containing the name of the issuer + # @param cert (optional) Certificate object containing the name of the issuer + + def set_issuer(self, key, subject=None, cert=None): + self.issuerKey = key + if subject: + # it's a mistake to use subject and cert params at the same time + assert(not cert) + if isinstance(subject, dict) or isinstance(subject, str): + req = crypto.X509Req() + reqSubject = req.get_subject() + if (isinstance(subject, dict)): + for key in reqSubject.keys(): + setattr(reqSubject, key, subject[key]) + else: + setattr(reqSubject, "CN", subject) + subject = reqSubject + # subject is not valid once req is out of scope, so save req + self.issuerReq = req + if cert: + # if a cert was supplied, then get the subject from the cert + subject = cert.cert.get_subject() + assert(subject) + self.issuerSubject = subject + + ## + # Get the issuer name + + def get_issuer(self, which="CN"): + x = self.cert.get_issuer() + return getattr(x, which) + + ## + # Set the subject name of the certificate + + def set_subject(self, name): + req = crypto.X509Req() + subj = req.get_subject() + if (isinstance(name, dict)): + for key in name.keys(): + setattr(subj, key, name[key]) + else: + setattr(subj, "CN", name) + self.cert.set_subject(subj) + + ## + # Get the subject name of the certificate + + def get_subject(self, which="CN"): + x = self.cert.get_subject() + return getattr(x, which) + + ## + # Get a pretty-print subject name of the certificate + + def get_printable_subject(self): + x = self.cert.get_subject() + return "[ OU: %s, CN: %s, SubjectAltName: %s ]" % (getattr(x, "OU"), getattr(x, "CN"), self.get_data()) + + ## + # Get the public key of the certificate. + # + # @param key Keypair object containing the public key + + def set_pubkey(self, key): + assert(isinstance(key, Keypair)) + self.cert.set_pubkey(key.get_openssl_pkey()) + + ## + # Get the public key of the certificate. + # It is returned in the form of a Keypair object. + + def get_pubkey(self): + m2x509 = X509.load_cert_string(self.save_to_string()) + pkey = Keypair() + pkey.key = self.cert.get_pubkey() + pkey.m2key = m2x509.get_pubkey() + return pkey + + def set_intermediate_ca(self, val): + return self.set_is_ca(val) + + # Set whether this cert is for a CA. All signers and only signers should be CAs. + # The local member starts unset, letting us check that you only set it once + # @param val Boolean indicating whether this cert is for a CA + def set_is_ca(self, val): + if val is None: + return + + if self.isCA != None: + # Can't double set properties + raise Exception, "Cannot set basicConstraints CA:?? more than once. Was %s, trying to set as %s" % (self.isCA, val) + + self.isCA = val + if val: + self.add_extension('basicConstraints', 1, 'CA:TRUE') + else: + self.add_extension('basicConstraints', 1, 'CA:FALSE') + + + + ## + # Add an X509 extension to the certificate. Add_extension can only be called + # once for a particular extension name, due to limitations in the underlying + # library. + # + # @param name string containing name of extension + # @param value string containing value of the extension + + def add_extension(self, name, critical, value): + oldExtVal = None + try: + oldExtVal = self.get_extension(name) + except: + # M2Crypto LookupError when the extension isn't there (yet) + pass + + # This code limits you from adding the extension with the same value + # The method comment says you shouldn't do this with the same name + # But actually it (m2crypto) appears to allow you to do this. + if oldExtVal and oldExtVal == value: + # don't add this extension again + # just do nothing as here + return + # FIXME: What if they are trying to set with a different value? + # Is this ever OK? Or should we raise an exception? +# elif oldExtVal: +# raise "Cannot add extension %s which had val %s with new val %s" % (name, oldExtVal, value) + + ext = crypto.X509Extension (name, critical, value) + self.cert.add_extensions([ext]) + + ## + # Get an X509 extension from the certificate + + def get_extension(self, name): + + # pyOpenSSL does not have a way to get extensions + m2x509 = X509.load_cert_string(self.save_to_string()) + value = m2x509.get_ext(name).get_value() + + return value + + ## + # Set_data is a wrapper around add_extension. It stores the parameter str in + # the X509 subject_alt_name extension. Set_data can only be called once, due + # to limitations in the underlying library. + + def set_data(self, str, field='subjectAltName'): + # pyOpenSSL only allows us to add extensions, so if we try to set the + # same extension more than once, it will not work + if self.data.has_key(field): + raise "Cannot set ", field, " more than once" + self.data[field] = str + self.add_extension(field, 0, str) + + ## + # Return the data string that was previously set with set_data + + def get_data(self, field='subjectAltName'): + if self.data.has_key(field): + return self.data[field] + + try: + uri = self.get_extension(field) + self.data[field] = uri + except LookupError: + return None + + return self.data[field] + + ## + # Sign the certificate using the issuer private key and issuer subject previous set with set_issuer(). + + def sign(self): + logger.debug('certificate.sign') + assert self.cert != None + assert self.issuerSubject != None + assert self.issuerKey != None + self.cert.set_issuer(self.issuerSubject) + self.cert.sign(self.issuerKey.get_openssl_pkey(), self.digest) + + ## + # Verify the authenticity of a certificate. + # @param pkey is a Keypair object representing a public key. If Pkey + # did not sign the certificate, then an exception will be thrown. + + def verify(self, pkey): + # pyOpenSSL does not have a way to verify signatures + m2x509 = X509.load_cert_string(self.save_to_string()) + m2pkey = pkey.get_m2_pkey() + # verify it + return m2x509.verify(m2pkey) + + # XXX alternatively, if openssl has been patched, do the much simpler: + # try: + # self.cert.verify(pkey.get_openssl_key()) + # return 1 + # except: + # return 0 + + ## + # Return True if pkey is identical to the public key that is contained in the certificate. + # @param pkey Keypair object + + def is_pubkey(self, pkey): + return self.get_pubkey().is_same(pkey) + + ## + # Given a certificate cert, verify that this certificate was signed by the + # public key contained in cert. Throw an exception otherwise. + # + # @param cert certificate object + + def is_signed_by_cert(self, cert): + k = cert.get_pubkey() + result = self.verify(k) + return result + + ## + # Set the parent certficiate. + # + # @param p certificate object. + + def set_parent(self, p): + self.parent = p + + ## + # Return the certificate object of the parent of this certificate. + + def get_parent(self): + return self.parent + + ## + # Verification examines a chain of certificates to ensure that each parent + # signs the child, and that some certificate in the chain is signed by a + # trusted certificate. + # + # Verification is a basic recursion:
+    #     if this_certificate was signed by trusted_certs:
+    #         return
+    #     else
+    #         return verify_chain(parent, trusted_certs)
+    # 
+ # + # At each recursion, the parent is tested to ensure that it did sign the + # child. If a parent did not sign a child, then an exception is thrown. If + # the bottom of the recursion is reached and the certificate does not match + # a trusted root, then an exception is thrown. + # Also require that parents are CAs. + # + # @param Trusted_certs is a list of certificates that are trusted. + # + + def verify_chain(self, trusted_certs = None): + # Verify a chain of certificates. Each certificate must be signed by + # the public key contained in it's parent. The chain is recursed + # until a certificate is found that is signed by a trusted root. + + # verify expiration time + if self.cert.has_expired(): + logger.debug("verify_chain: NO, Certificate %s has expired" % self.get_printable_subject()) + raise CertExpired(self.get_printable_subject(), "client cert") + + # if this cert is signed by a trusted_cert, then we are set + for trusted_cert in trusted_certs: + if self.is_signed_by_cert(trusted_cert): + # verify expiration of trusted_cert ? + if not trusted_cert.cert.has_expired(): + logger.debug("verify_chain: YES. Cert %s signed by trusted cert %s"%( + self.get_printable_subject(), trusted_cert.get_printable_subject())) + return trusted_cert + else: + logger.debug("verify_chain: NO. Cert %s is signed by trusted_cert %s, but that signer is expired..."%( + self.get_printable_subject(),trusted_cert.get_printable_subject())) + raise CertExpired(self.get_printable_subject()," signer trusted_cert %s"%trusted_cert.get_printable_subject()) + + # if there is no parent, then no way to verify the chain + if not self.parent: + logger.debug("verify_chain: NO. %s has no parent and issuer %s is not in %d trusted roots"%(self.get_printable_subject(), self.get_issuer(), len(trusted_certs))) + raise CertMissingParent(self.get_printable_subject() + ": Issuer %s not trusted by any of %d trusted roots, and cert has no parent." % (self.get_issuer(), len(trusted_certs))) + + # if it wasn't signed by the parent... + if not self.is_signed_by_cert(self.parent): + logger.debug("verify_chain: NO. %s is not signed by parent %s, but by %s"%\ + (self.get_printable_subject(), + self.parent.get_printable_subject(), + self.get_issuer())) + raise CertNotSignedByParent("%s: Parent %s, issuer %s"\ + % (self.get_printable_subject(), + self.parent.get_printable_subject(), + self.get_issuer())) + + # Confirm that the parent is a CA. Only CAs can be trusted as + # signers. + # Note that trusted roots are not parents, so don't need to be + # CAs. + # Ugly - cert objects aren't parsed so we need to read the + # extension and hope there are no other basicConstraints + if not self.parent.isCA and not (self.parent.get_extension('basicConstraints') == 'CA:TRUE'): + logger.warn("verify_chain: cert %s's parent %s is not a CA" % \ + (self.get_printable_subject(), self.parent.get_printable_subject())) + raise CertNotSignedByParent("%s: Parent %s not a CA" % (self.get_printable_subject(), + self.parent.get_printable_subject())) + + # if the parent isn't verified... + logger.debug("verify_chain: .. %s, -> verifying parent %s"%\ + (self.get_printable_subject(),self.parent.get_printable_subject())) + self.parent.verify_chain(trusted_certs) + + return + + ### more introspection + def get_extensions(self): + # pyOpenSSL does not have a way to get extensions + triples=[] + m2x509 = X509.load_cert_string(self.save_to_string()) + nb_extensions=m2x509.get_ext_count() + logger.debug("X509 had %d extensions"%nb_extensions) + for i in range(nb_extensions): + ext=m2x509.get_ext_at(i) + triples.append( (ext.get_name(), ext.get_value(), ext.get_critical(),) ) + return triples + + def get_data_names(self): + return self.data.keys() + + def get_all_datas (self): + triples=self.get_extensions() + for name in self.get_data_names(): + triples.append( (name,self.get_data(name),'data',) ) + return triples + + # only informative + def get_filename(self): + return getattr(self,'filename',None) + + def dump (self, *args, **kwargs): + print self.dump_string(*args, **kwargs) + + def dump_string (self,show_extensions=False): + result = "" + result += "CERTIFICATE for %s\n"%self.get_printable_subject() + result += "Issued by %s\n"%self.get_issuer() + filename=self.get_filename() + if filename: result += "Filename %s\n"%filename + if show_extensions: + all_datas=self.get_all_datas() + result += " has %d extensions/data attached"%len(all_datas) + for (n,v,c) in all_datas: + if c=='data': + result += " data: %s=%s\n"%(n,v) + else: + result += " ext: %s (crit=%s)=<<<%s>>>\n"%(n,c,v) + return result diff --git a/sfa/trust/credential.py b/sfa/trust/credential.py index a18019d8..8fd11e8e 100644 --- a/sfa/trust/credential.py +++ b/sfa/trust/credential.py @@ -1,1063 +1,1064 @@ -#---------------------------------------------------------------------- -# Copyright (c) 2008 Board of Trustees, Princeton University -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and/or hardware specification (the "Work") to -# deal in the Work without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Work, and to permit persons to whom the Work -# is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Work. -# -# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS -# IN THE WORK. -#---------------------------------------------------------------------- -## -# Implements SFA Credentials -# -# Credentials are signed XML files that assign a subject gid privileges to an object gid -## - -import os -from types import StringTypes -import datetime -from StringIO import StringIO -from tempfile import mkstemp -from xml.dom.minidom import Document, parseString - -HAVELXML = False -try: - from lxml import etree - HAVELXML = True -except: - pass - -from sfa.util.faults import * -from sfa.util.sfalogging import logger -from sfa.util.sfatime import utcparse -from sfa.trust.certificate import Keypair -from sfa.trust.credential_legacy import CredentialLegacy -from sfa.trust.rights import Right, Rights, determine_rights -from sfa.trust.gid import GID -from sfa.util.xrn import urn_to_hrn, hrn_authfor_hrn - -# 2 weeks, in seconds -DEFAULT_CREDENTIAL_LIFETIME = 86400 * 14 - - -# TODO: -# . make privs match between PG and PL -# . Need to add support for other types of credentials, e.g. tickets -# . add namespaces to signed-credential element? - -signature_template = \ -''' - - - - - - - - - - - - - - - - - - - - - - -''' - -# PG formats the template (whitespace) slightly differently. -# Note that they don't include the xmlns in the template, but add it later. -# Otherwise the two are equivalent. -#signature_template_as_in_pg = \ -#''' -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -#''' - -## -# Convert a string into a bool -# used to convert an xsd:boolean to a Python boolean -def str2bool(str): - if str.lower() in ['true','1']: - return True - return False - - -## -# Utility function to get the text of an XML element - -def getTextNode(element, subele): - sub = element.getElementsByTagName(subele)[0] - if len(sub.childNodes) > 0: - return sub.childNodes[0].nodeValue - else: - return None - -## -# Utility function to set the text of an XML element -# It creates the element, adds the text to it, -# and then appends it to the parent. - -def append_sub(doc, parent, element, text): - ele = doc.createElement(element) - ele.appendChild(doc.createTextNode(text)) - parent.appendChild(ele) - -## -# Signature contains information about an xmlsec1 signature -# for a signed-credential -# - -class Signature(object): - - def __init__(self, string=None): - self.refid = None - self.issuer_gid = None - self.xml = None - if string: - self.xml = string - self.decode() - - - def get_refid(self): - if not self.refid: - self.decode() - return self.refid - - def get_xml(self): - if not self.xml: - self.encode() - return self.xml - - def set_refid(self, id): - self.refid = id - - def get_issuer_gid(self): - if not self.gid: - self.decode() - return self.gid - - def set_issuer_gid(self, gid): - self.gid = gid - - def decode(self): - try: - doc = parseString(self.xml) - except ExpatError,e: - logger.log_exc ("Failed to parse credential, %s"%self.xml) - raise - sig = doc.getElementsByTagName("Signature")[0] - self.set_refid(sig.getAttribute("xml:id").strip("Sig_")) - keyinfo = sig.getElementsByTagName("X509Data")[0] - szgid = getTextNode(keyinfo, "X509Certificate") - szgid = "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" % szgid - self.set_issuer_gid(GID(string=szgid)) - - def encode(self): - self.xml = signature_template % (self.get_refid(), self.get_refid()) - - -## -# A credential provides a caller gid with privileges to an object gid. -# A signed credential is signed by the object's authority. -# -# Credentials are encoded in one of two ways. The legacy style places -# it in the subjectAltName of an X509 certificate. The new credentials -# are placed in signed XML. -# -# WARNING: -# In general, a signed credential obtained externally should -# not be changed else the signature is no longer valid. So, once -# you have loaded an existing signed credential, do not call encode() or sign() on it. - -def filter_creds_by_caller(creds, caller_hrn_list): - """ - Returns a list of creds who's gid caller matches the - specified caller hrn - """ - if not isinstance(creds, list): creds = [creds] - if not isinstance(caller_hrn_list, list): - caller_hrn_list = [caller_hrn_list] - caller_creds = [] - for cred in creds: - try: - tmp_cred = Credential(string=cred) - if tmp_cred.get_gid_caller().get_hrn() in caller_hrn_list: - caller_creds.append(cred) - except: pass - return caller_creds - -class Credential(object): - - ## - # Create a Credential object - # - # @param create If true, create a blank x509 certificate - # @param subject If subject!=None, create an x509 cert with the subject name - # @param string If string!=None, load the credential from the string - # @param filename If filename!=None, load the credential from the file - # FIXME: create and subject are ignored! - def __init__(self, create=False, subject=None, string=None, filename=None): - self.gidCaller = None - self.gidObject = None - self.expiration = None - self.privileges = None - self.issuer_privkey = None - self.issuer_gid = None - self.issuer_pubkey = None - self.parent = None - self.signature = None - self.xml = None - self.refid = None - self.legacy = None - - # Check if this is a legacy credential, translate it if so - if string or filename: - if string: - str = string - elif filename: - str = file(filename).read() - - if str.strip().startswith("-----"): - self.legacy = CredentialLegacy(False,string=str) - self.translate_legacy(str) - else: - self.xml = str - self.decode() - - # Find an xmlsec1 path - self.xmlsec_path = '' - paths = ['/usr/bin','/usr/local/bin','/bin','/opt/bin','/opt/local/bin'] - for path in paths: - if os.path.isfile(path + '/' + 'xmlsec1'): - self.xmlsec_path = path + '/' + 'xmlsec1' - break - - def get_subject(self): - if not self.gidObject: - self.decode() - return self.gidObject.get_printable_subject() - - def get_summary_tostring(self): - if not self.gidObject: - self.decode() - obj = self.gidObject.get_printable_subject() - caller = self.gidCaller.get_printable_subject() - exp = self.get_expiration() - # Summarize the rights too? The issuer? - return "[ Grant %s rights on %s until %s ]" % (caller, obj, exp) - - def get_signature(self): - if not self.signature: - self.decode() - return self.signature - - def set_signature(self, sig): - self.signature = sig - - - ## - # Translate a legacy credential into a new one - # - # @param String of the legacy credential - - def translate_legacy(self, str): - legacy = CredentialLegacy(False,string=str) - self.gidCaller = legacy.get_gid_caller() - self.gidObject = legacy.get_gid_object() - lifetime = legacy.get_lifetime() - if not lifetime: - self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME)) - else: - self.set_expiration(int(lifetime)) - self.lifeTime = legacy.get_lifetime() - self.set_privileges(legacy.get_privileges()) - self.get_privileges().delegate_all_privileges(legacy.get_delegate()) - - ## - # Need the issuer's private key and name - # @param key Keypair object containing the private key of the issuer - # @param gid GID of the issuing authority - - def set_issuer_keys(self, privkey, gid): - self.issuer_privkey = privkey - self.issuer_gid = gid - - - ## - # Set this credential's parent - def set_parent(self, cred): - self.parent = cred - self.updateRefID() - - ## - # set the GID of the caller - # - # @param gid GID object of the caller - - def set_gid_caller(self, gid): - self.gidCaller = gid - # gid origin caller is the caller's gid by default - self.gidOriginCaller = gid - - ## - # get the GID of the object - - def get_gid_caller(self): - if not self.gidCaller: - self.decode() - return self.gidCaller - - ## - # set the GID of the object - # - # @param gid GID object of the object - - def set_gid_object(self, gid): - self.gidObject = gid - - ## - # get the GID of the object - - def get_gid_object(self): - if not self.gidObject: - self.decode() - return self.gidObject - - - - ## - # Expiration: an absolute UTC time of expiration (as either an int or string or datetime) - # - def set_expiration(self, expiration): - if isinstance(expiration, (int, float)): - self.expiration = datetime.datetime.fromtimestamp(expiration) - elif isinstance (expiration, datetime.datetime): - self.expiration = expiration - elif isinstance (expiration, StringTypes): - self.expiration = utcparse (expiration) - else: - logger.error ("unexpected input type in Credential.set_expiration") - - - ## - # get the lifetime of the credential (always in datetime format) - - def get_expiration(self): - if not self.expiration: - self.decode() - # at this point self.expiration is normalized as a datetime - DON'T call utcparse again - return self.expiration - - ## - # For legacy sake - def get_lifetime(self): - return self.get_expiration() - - ## - # set the privileges - # - # @param privs either a comma-separated list of privileges of a Rights object - - def set_privileges(self, privs): - if isinstance(privs, str): - self.privileges = Rights(string = privs) - else: - self.privileges = privs - - - ## - # return the privileges as a Rights object - - def get_privileges(self): - if not self.privileges: - self.decode() - return self.privileges - - ## - # determine whether the credential allows a particular operation to be - # performed - # - # @param op_name string specifying name of operation ("lookup", "update", etc) - - def can_perform(self, op_name): - rights = self.get_privileges() - - if not rights: - return False - - return rights.can_perform(op_name) - - - ## - # Encode the attributes of the credential into an XML string - # This should be done immediately before signing the credential. - # WARNING: - # In general, a signed credential obtained externally should - # not be changed else the signature is no longer valid. So, once - # you have loaded an existing signed credential, do not call encode() or sign() on it. - - def encode(self): - # Create the XML document - doc = Document() - signed_cred = doc.createElement("signed-credential") - -# Declare namespaces -# Note that credential/policy.xsd are really the PG schemas -# in a PL namespace. -# Note that delegation of credentials between the 2 only really works -# cause those schemas are identical. -# Also note these PG schemas talk about PG tickets and CM policies. - signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") - signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.planet-lab.org/resources/sfa/credential.xsd") - signed_cred.setAttribute("xsi:schemaLocation", "http://www.planet-lab.org/resources/sfa/ext/policy/1 http://www.planet-lab.org/resources/sfa/ext/policy/1/policy.xsd") - -# PG says for those last 2: -# signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd") -# signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd") - - doc.appendChild(signed_cred) - - # Fill in the bit - cred = doc.createElement("credential") - cred.setAttribute("xml:id", self.get_refid()) - signed_cred.appendChild(cred) - append_sub(doc, cred, "type", "privilege") - append_sub(doc, cred, "serial", "8") - append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string()) - append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn()) - append_sub(doc, cred, "target_gid", self.gidObject.save_to_string()) - append_sub(doc, cred, "target_urn", self.gidObject.get_urn()) - append_sub(doc, cred, "uuid", "") - if not self.expiration: - self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME)) - self.expiration = self.expiration.replace(microsecond=0) - append_sub(doc, cred, "expires", self.expiration.isoformat()) - privileges = doc.createElement("privileges") - cred.appendChild(privileges) - - if self.privileges: - rights = self.get_privileges() - for right in rights.rights: - priv = doc.createElement("privilege") - append_sub(doc, priv, "name", right.kind) - append_sub(doc, priv, "can_delegate", str(right.delegate).lower()) - privileges.appendChild(priv) - - # Add the parent credential if it exists - if self.parent: - sdoc = parseString(self.parent.get_xml()) - # If the root node is a signed-credential (it should be), then - # get all its attributes and attach those to our signed_cred - # node. - # Specifically, PG and PLadd attributes for namespaces (which is reasonable), - # and we need to include those again here or else their signature - # no longer matches on the credential. - # We expect three of these, but here we copy them all: -# signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") -# and from PG (PL is equivalent, as shown above): -# signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd") -# signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd") - - # HOWEVER! - # PL now also declares these, with different URLs, so - # the code notices those attributes already existed with - # different values, and complains. - # This happens regularly on delegation now that PG and - # PL both declare the namespace with different URLs. - # If the content ever differs this is a problem, - # but for now it works - different URLs (values in the attributes) - # but the same actual schema, so using the PG schema - # on delegated-to-PL credentials works fine. - - # Note: you could also not copy attributes - # which already exist. It appears that both PG and PL - # will actually validate a slicecred with a parent - # signed using PG namespaces and a child signed with PL - # namespaces over the whole thing. But I don't know - # if that is a bug in xmlsec1, an accident since - # the contents of the schemas are the same, - # or something else, but it seems odd. And this works. - parentRoot = sdoc.documentElement - if parentRoot.tagName == "signed-credential" and parentRoot.hasAttributes(): - for attrIx in range(0, parentRoot.attributes.length): - attr = parentRoot.attributes.item(attrIx) - # returns the old attribute of same name that was - # on the credential - # Below throws InUse exception if we forgot to clone the attribute first - oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True)) - if oldAttr and oldAttr.value != attr.value: - msg = "Delegating cred from owner %s to %s over %s replaced attribute %s value '%s' with '%s'" % (self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(), self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value) - logger.warn(msg) - #raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg) - - p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True) - p = doc.createElement("parent") - p.appendChild(p_cred) - cred.appendChild(p) - # done handling parent credential - - # Create the tag - signatures = doc.createElement("signatures") - signed_cred.appendChild(signatures) - - # Add any parent signatures - if self.parent: - for cur_cred in self.get_credential_list()[1:]: - sdoc = parseString(cur_cred.get_signature().get_xml()) - ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True) - signatures.appendChild(ele) - - # Get the finished product - self.xml = doc.toxml() - - - def save_to_random_tmp_file(self): - fp, filename = mkstemp(suffix='cred', text=True) - fp = os.fdopen(fp, "w") - self.save_to_file(filename, save_parents=True, filep=fp) - return filename - - def save_to_file(self, filename, save_parents=True, filep=None): - if not self.xml: - self.encode() - if filep: - f = filep - else: - f = open(filename, "w") - f.write(self.xml) - f.close() - - def save_to_string(self, save_parents=True): - if not self.xml: - self.encode() - return self.xml - - def get_refid(self): - if not self.refid: - self.refid = 'ref0' - return self.refid - - def set_refid(self, rid): - self.refid = rid - - ## - # Figure out what refids exist, and update this credential's id - # so that it doesn't clobber the others. Returns the refids of - # the parents. - - def updateRefID(self): - if not self.parent: - self.set_refid('ref0') - return [] - - refs = [] - - next_cred = self.parent - while next_cred: - refs.append(next_cred.get_refid()) - if next_cred.parent: - next_cred = next_cred.parent - else: - next_cred = None - - - # Find a unique refid for this credential - rid = self.get_refid() - while rid in refs: - val = int(rid[3:]) - rid = "ref%d" % (val + 1) - - # Set the new refid - self.set_refid(rid) - - # Return the set of parent credential ref ids - return refs - - def get_xml(self): - if not self.xml: - self.encode() - return self.xml - - ## - # Sign the XML file created by encode() - # - # WARNING: - # In general, a signed credential obtained externally should - # not be changed else the signature is no longer valid. So, once - # you have loaded an existing signed credential, do not call encode() or sign() on it. - - def sign(self): - if not self.issuer_privkey or not self.issuer_gid: - return - doc = parseString(self.get_xml()) - sigs = doc.getElementsByTagName("signatures")[0] - - # Create the signature template to be signed - signature = Signature() - signature.set_refid(self.get_refid()) - sdoc = parseString(signature.get_xml()) - sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True) - sigs.appendChild(sig_ele) - - self.xml = doc.toxml() - - - # Split the issuer GID into multiple certificates if it's a chain - chain = GID(filename=self.issuer_gid) - gid_files = [] - while chain: - gid_files.append(chain.save_to_random_tmp_file(False)) - if chain.get_parent(): - chain = chain.get_parent() - else: - chain = None - - - # Call out to xmlsec1 to sign it - ref = 'Sig_%s' % self.get_refid() - filename = self.save_to_random_tmp_file() - signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \ - % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read() - os.remove(filename) - - for gid_file in gid_files: - os.remove(gid_file) - - self.xml = signed - - # This is no longer a legacy credential - if self.legacy: - self.legacy = None - - # Update signatures - self.decode() - - - ## - # Retrieve the attributes of the credential from the XML. - # This is automatically called by the various get_* methods of - # this class and should not need to be called explicitly. - - def decode(self): - if not self.xml: - return - doc = parseString(self.xml) - sigs = [] - signed_cred = doc.getElementsByTagName("signed-credential") - - # Is this a signed-cred or just a cred? - if len(signed_cred) > 0: - creds = signed_cred[0].getElementsByTagName("credential") - signatures = signed_cred[0].getElementsByTagName("signatures") - if len(signatures) > 0: - sigs = signatures[0].getElementsByTagName("Signature") - else: - creds = doc.getElementsByTagName("credential") - - if creds is None or len(creds) == 0: - # malformed cred file - raise CredentialNotVerifiable("Malformed XML: No credential tag found") - - # Just take the first cred if there are more than one - cred = creds[0] - - self.set_refid(cred.getAttribute("xml:id")) - self.set_expiration(utcparse(getTextNode(cred, "expires"))) - self.gidCaller = GID(string=getTextNode(cred, "owner_gid")) - self.gidObject = GID(string=getTextNode(cred, "target_gid")) - - - # Process privileges - privs = cred.getElementsByTagName("privileges")[0] - rlist = Rights() - for priv in privs.getElementsByTagName("privilege"): - kind = getTextNode(priv, "name") - deleg = str2bool(getTextNode(priv, "can_delegate")) - if kind == '*': - # Convert * into the default privileges for the credential's type - # Each inherits the delegatability from the * above - _ , type = urn_to_hrn(self.gidObject.get_urn()) - rl = determine_rights(type, self.gidObject.get_urn()) - for r in rl.rights: - r.delegate = deleg - rlist.add(r) - else: - rlist.add(Right(kind.strip(), deleg)) - self.set_privileges(rlist) - - - # Is there a parent? - parent = cred.getElementsByTagName("parent") - if len(parent) > 0: - parent_doc = parent[0].getElementsByTagName("credential")[0] - parent_xml = parent_doc.toxml() - self.parent = Credential(string=parent_xml) - self.updateRefID() - - # Assign the signatures to the credentials - for sig in sigs: - Sig = Signature(string=sig.toxml()) - - for cur_cred in self.get_credential_list(): - if cur_cred.get_refid() == Sig.get_refid(): - cur_cred.set_signature(Sig) - - - ## - # Verify - # trusted_certs: A list of trusted GID filenames (not GID objects!) - # Chaining is not supported within the GIDs by xmlsec1. - # - # trusted_certs_required: Should usually be true. Set False means an - # empty list of trusted_certs would still let this method pass. - # It just skips xmlsec1 verification et al. Only used by some utils - # - # Verify that: - # . All of the signatures are valid and that the issuers trace back - # to trusted roots (performed by xmlsec1) - # . The XML matches the credential schema - # . That the issuer of the credential is the authority in the target's urn - # . In the case of a delegated credential, this must be true of the root - # . That all of the gids presented in the credential are valid - # . Including verifying GID chains, and includ the issuer - # . The credential is not expired - # - # -- For Delegates (credentials with parents) - # . The privileges must be a subset of the parent credentials - # . The privileges must have "can_delegate" set for each delegated privilege - # . The target gid must be the same between child and parents - # . The expiry time on the child must be no later than the parent - # . The signer of the child must be the owner of the parent - # - # -- Verify does *NOT* - # . ensure that an xmlrpc client's gid matches a credential gid, that - # must be done elsewhere - # - # @param trusted_certs: The certificates of trusted CA certificates - def verify(self, trusted_certs=None, schema=None, trusted_certs_required=True): - if not self.xml: - self.decode() - - # validate against RelaxNG schema - if HAVELXML and not self.legacy: - if schema and os.path.exists(schema): - tree = etree.parse(StringIO(self.xml)) - schema_doc = etree.parse(schema) - xmlschema = etree.XMLSchema(schema_doc) - if not xmlschema.validate(tree): - error = xmlschema.error_log.last_error - message = "%s: %s (line %s)" % (self.get_summary_tostring(), error.message, error.line) - raise CredentialNotVerifiable(message) - - if trusted_certs_required and trusted_certs is None: - trusted_certs = [] - -# trusted_cert_objects = [GID(filename=f) for f in trusted_certs] - trusted_cert_objects = [] - ok_trusted_certs = [] - # If caller explicitly passed in None that means skip cert chain validation. - # Strange and not typical - if trusted_certs is not None: - for f in trusted_certs: - try: - # Failures here include unreadable files - # or non PEM files - trusted_cert_objects.append(GID(filename=f)) - ok_trusted_certs.append(f) - except Exception, exc: - logger.error("Failed to load trusted cert from %s: %r", f, exc) - trusted_certs = ok_trusted_certs - - # Use legacy verification if this is a legacy credential - if self.legacy: - self.legacy.verify_chain(trusted_cert_objects) - if self.legacy.client_gid: - self.legacy.client_gid.verify_chain(trusted_cert_objects) - if self.legacy.object_gid: - self.legacy.object_gid.verify_chain(trusted_cert_objects) - return True - - # make sure it is not expired - if self.get_expiration() < datetime.datetime.utcnow(): - raise CredentialNotVerifiable("Credential %s expired at %s" % (self.get_summary_tostring(), self.expiration.isoformat())) - - # Verify the signatures - filename = self.save_to_random_tmp_file() - if trusted_certs is not None: - cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs]) - - # If caller explicitly passed in None that means skip cert chain validation. - # - Strange and not typical - if trusted_certs is not None: - # Verify the gids of this cred and of its parents - for cur_cred in self.get_credential_list(): - cur_cred.get_gid_object().verify_chain(trusted_cert_objects) - cur_cred.get_gid_caller().verify_chain(trusted_cert_objects) - - refs = [] - refs.append("Sig_%s" % self.get_refid()) - - parentRefs = self.updateRefID() - for ref in parentRefs: - refs.append("Sig_%s" % ref) - - for ref in refs: - # If caller explicitly passed in None that means skip xmlsec1 validation. - # Strange and not typical - if trusted_certs is None: - break - -# print "Doing %s --verify --node-id '%s' %s %s 2>&1" % \ -# (self.xmlsec_path, ref, cert_args, filename) - verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \ - % (self.xmlsec_path, ref, cert_args, filename)).read() - if not verified.strip().startswith("OK"): - # xmlsec errors have a msg= which is the interesting bit. - mstart = verified.find("msg=") - msg = "" - if mstart > -1 and len(verified) > 4: - mstart = mstart + 4 - mend = verified.find('\\', mstart) - msg = verified[mstart:mend] - raise CredentialNotVerifiable("xmlsec1 error verifying cred %s using Signature ID %s: %s %s" % (self.get_summary_tostring(), ref, msg, verified.strip())) - os.remove(filename) - - # Verify the parents (delegation) - if self.parent: - self.verify_parent(self.parent) - - # Make sure the issuer is the target's authority, and is - # itself a valid GID - self.verify_issuer(trusted_cert_objects) - return True - - ## - # Creates a list of the credential and its parents, with the root - # (original delegated credential) as the last item in the list - def get_credential_list(self): - cur_cred = self - list = [] - while cur_cred: - list.append(cur_cred) - if cur_cred.parent: - cur_cred = cur_cred.parent - else: - cur_cred = None - return list - - ## - # Make sure the credential's target gid (a) was signed by or (b) - # is the same as the entity that signed the original credential, - # or (c) is an authority over the target's namespace. - # Also ensure that the credential issuer / signer itself has a valid - # GID signature chain (signed by an authority with namespace rights). - def verify_issuer(self, trusted_gids): - root_cred = self.get_credential_list()[-1] - root_target_gid = root_cred.get_gid_object() - root_cred_signer = root_cred.get_signature().get_issuer_gid() - - # Case 1: - # Allow non authority to sign target and cred about target. - # - # Why do we need to allow non authorities to sign? - # If in the target gid validation step we correctly - # checked that the target is only signed by an authority, - # then this is just a special case of case 3. - # This short-circuit is the common case currently - - # and cause GID validation doesn't check 'authority', - # this allows users to generate valid slice credentials. - if root_target_gid.is_signed_by_cert(root_cred_signer): - # cred signer matches target signer, return success - return - - # Case 2: - # Allow someone to sign credential about themeselves. Used? - # If not, remove this. - #root_target_gid_str = root_target_gid.save_to_string() - #root_cred_signer_str = root_cred_signer.save_to_string() - #if root_target_gid_str == root_cred_signer_str: - # # cred signer is target, return success - # return - - # Case 3: - - # root_cred_signer is not the target_gid - # So this is a different gid that we have not verified. - # xmlsec1 verified the cert chain on this already, but - # it hasn't verified that the gid meets the HRN namespace - # requirements. - # Below we'll ensure that it is an authority. - # But we haven't verified that it is _signed by_ an authority - # We also don't know if xmlsec1 requires that cert signers - # are marked as CAs. - - # Note that if verify() gave us no trusted_gids then this - # call will fail. So skip it if we have no trusted_gids - if trusted_gids and len(trusted_gids) > 0: - root_cred_signer.verify_chain(trusted_gids) - else: - logger.debug("No trusted gids. Cannot verify that cred signer is signed by a trusted authority. Skipping that check.") - - # See if the signer is an authority over the domain of the target. - # There are multiple types of authority - accept them all here - # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn()) - root_cred_signer_type = root_cred_signer.get_type() - if (root_cred_signer_type.find('authority') == 0): - #logger.debug('Cred signer is an authority') - # signer is an authority, see if target is in authority's domain - signerhrn = root_cred_signer.get_hrn() - if hrn_authfor_hrn(signerhrn, root_target_gid.get_hrn()): - return - - # We've required that the credential be signed by an authority - # for that domain. Reasonable and probably correct. - # A looser model would also allow the signer to be an authority - # in my control framework - eg My CA or CH. Even if it is not - # the CH that issued these, eg, user credentials. - - # Give up, credential does not pass issuer verification - - raise CredentialNotVerifiable("Could not verify credential owned by %s for object %s. Cred signer %s not the trusted authority for Cred target %s" % (self.gidCaller.get_urn(), self.gidObject.get_urn(), root_cred_signer.get_hrn(), root_target_gid.get_hrn())) - - - ## - # -- For Delegates (credentials with parents) verify that: - # . The privileges must be a subset of the parent credentials - # . The privileges must have "can_delegate" set for each delegated privilege - # . The target gid must be the same between child and parents - # . The expiry time on the child must be no later than the parent - # . The signer of the child must be the owner of the parent - def verify_parent(self, parent_cred): - # make sure the rights given to the child are a subset of the - # parents rights (and check delegate bits) - if not parent_cred.get_privileges().is_superset(self.get_privileges()): - raise ChildRightsNotSubsetOfParent(("Parent cred ref %s rights " % parent_cred.get_refid()) + - self.parent.get_privileges().save_to_string() + (" not superset of delegated cred %s ref %s rights " % (self.get_summary_tostring(), self.get_refid())) + - self.get_privileges().save_to_string()) - - # make sure my target gid is the same as the parent's - if not parent_cred.get_gid_object().save_to_string() == \ - self.get_gid_object().save_to_string(): - raise CredentialNotVerifiable("Delegated cred %s: Target gid not equal between parent and child. Parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) - - # make sure my expiry time is <= my parent's - if not parent_cred.get_expiration() >= self.get_expiration(): - raise CredentialNotVerifiable("Delegated credential %s expires after parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) - - # make sure my signer is the parent's caller - if not parent_cred.get_gid_caller().save_to_string(False) == \ - self.get_signature().get_issuer_gid().save_to_string(False): - raise CredentialNotVerifiable("Delegated credential %s not signed by parent %s's caller" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) - - # Recurse - if parent_cred.parent: - parent_cred.verify_parent(parent_cred.parent) - - - def delegate(self, delegee_gidfile, caller_keyfile, caller_gidfile): - """ - Return a delegated copy of this credential, delegated to the - specified gid's user. - """ - # get the gid of the object we are delegating - object_gid = self.get_gid_object() - object_hrn = object_gid.get_hrn() - - # the hrn of the user who will be delegated to - delegee_gid = GID(filename=delegee_gidfile) - delegee_hrn = delegee_gid.get_hrn() - - #user_key = Keypair(filename=keyfile) - #user_hrn = self.get_gid_caller().get_hrn() - subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn) - dcred = Credential(subject=subject_string) - dcred.set_gid_caller(delegee_gid) - dcred.set_gid_object(object_gid) - dcred.set_parent(self) - dcred.set_expiration(self.get_expiration()) - dcred.set_privileges(self.get_privileges()) - dcred.get_privileges().delegate_all_privileges(True) - #dcred.set_issuer_keys(keyfile, delegee_gidfile) - dcred.set_issuer_keys(caller_keyfile, caller_gidfile) - dcred.encode() - dcred.sign() - - return dcred - - # only informative - def get_filename(self): - return getattr(self,'filename',None) - - ## - # Dump the contents of a credential to stdout in human-readable format - # - # @param dump_parents If true, also dump the parent certificates - def dump (self, *args, **kwargs): - print self.dump_string(*args, **kwargs) - - - def dump_string(self, dump_parents=False): - result="" - result += "CREDENTIAL %s\n" % self.get_subject() - filename=self.get_filename() - if filename: result += "Filename %s\n"%filename - result += " privs: %s\n" % self.get_privileges().save_to_string() - gidCaller = self.get_gid_caller() - if gidCaller: - result += " gidCaller:\n" - result += gidCaller.dump_string(8, dump_parents) - - if self.get_signature(): - print " gidIssuer:" - self.get_signature().get_issuer_gid().dump(8, dump_parents) - - gidObject = self.get_gid_object() - if gidObject: - result += " gidObject:\n" - result += gidObject.dump_string(8, dump_parents) - - if self.parent and dump_parents: - result += "\nPARENT" - result += self.parent.dump_string(True) - - return result +#---------------------------------------------------------------------- +# Copyright (c) 2008 Board of Trustees, Princeton University +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and/or hardware specification (the "Work") to +# deal in the Work without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Work, and to permit persons to whom the Work +# is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Work. +# +# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS +# IN THE WORK. +#---------------------------------------------------------------------- +## +# Implements SFA Credentials +# +# Credentials are signed XML files that assign a subject gid privileges to an object gid +## + +import os +from types import StringTypes +import datetime +from StringIO import StringIO +from tempfile import mkstemp +from xml.dom.minidom import Document, parseString + +HAVELXML = False +try: + from lxml import etree + HAVELXML = True +except: + pass + +from xml.parsers.expat import ExpatError + +from sfa.util.faults import CredentialNotVerifiable, ChildRightsNotSubsetOfParent +from sfa.util.sfalogging import logger +from sfa.util.sfatime import utcparse +from sfa.trust.credential_legacy import CredentialLegacy +from sfa.trust.rights import Right, Rights, determine_rights +from sfa.trust.gid import GID +from sfa.util.xrn import urn_to_hrn, hrn_authfor_hrn + +# 2 weeks, in seconds +DEFAULT_CREDENTIAL_LIFETIME = 86400 * 14 + + +# TODO: +# . make privs match between PG and PL +# . Need to add support for other types of credentials, e.g. tickets +# . add namespaces to signed-credential element? + +signature_template = \ +''' + + + + + + + + + + + + + + + + + + + + + + +''' + +# PG formats the template (whitespace) slightly differently. +# Note that they don't include the xmlns in the template, but add it later. +# Otherwise the two are equivalent. +#signature_template_as_in_pg = \ +#''' +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +# +#''' + +## +# Convert a string into a bool +# used to convert an xsd:boolean to a Python boolean +def str2bool(str): + if str.lower() in ['true','1']: + return True + return False + + +## +# Utility function to get the text of an XML element + +def getTextNode(element, subele): + sub = element.getElementsByTagName(subele)[0] + if len(sub.childNodes) > 0: + return sub.childNodes[0].nodeValue + else: + return None + +## +# Utility function to set the text of an XML element +# It creates the element, adds the text to it, +# and then appends it to the parent. + +def append_sub(doc, parent, element, text): + ele = doc.createElement(element) + ele.appendChild(doc.createTextNode(text)) + parent.appendChild(ele) + +## +# Signature contains information about an xmlsec1 signature +# for a signed-credential +# + +class Signature(object): + + def __init__(self, string=None): + self.refid = None + self.issuer_gid = None + self.xml = None + if string: + self.xml = string + self.decode() + + + def get_refid(self): + if not self.refid: + self.decode() + return self.refid + + def get_xml(self): + if not self.xml: + self.encode() + return self.xml + + def set_refid(self, id): + self.refid = id + + def get_issuer_gid(self): + if not self.gid: + self.decode() + return self.gid + + def set_issuer_gid(self, gid): + self.gid = gid + + def decode(self): + try: + doc = parseString(self.xml) + except ExpatError,e: + logger.log_exc ("Failed to parse credential, %s"%self.xml) + raise + sig = doc.getElementsByTagName("Signature")[0] + self.set_refid(sig.getAttribute("xml:id").strip("Sig_")) + keyinfo = sig.getElementsByTagName("X509Data")[0] + szgid = getTextNode(keyinfo, "X509Certificate") + szgid = "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" % szgid + self.set_issuer_gid(GID(string=szgid)) + + def encode(self): + self.xml = signature_template % (self.get_refid(), self.get_refid()) + + +## +# A credential provides a caller gid with privileges to an object gid. +# A signed credential is signed by the object's authority. +# +# Credentials are encoded in one of two ways. The legacy style places +# it in the subjectAltName of an X509 certificate. The new credentials +# are placed in signed XML. +# +# WARNING: +# In general, a signed credential obtained externally should +# not be changed else the signature is no longer valid. So, once +# you have loaded an existing signed credential, do not call encode() or sign() on it. + +def filter_creds_by_caller(creds, caller_hrn_list): + """ + Returns a list of creds who's gid caller matches the + specified caller hrn + """ + if not isinstance(creds, list): creds = [creds] + if not isinstance(caller_hrn_list, list): + caller_hrn_list = [caller_hrn_list] + caller_creds = [] + for cred in creds: + try: + tmp_cred = Credential(string=cred) + if tmp_cred.get_gid_caller().get_hrn() in caller_hrn_list: + caller_creds.append(cred) + except: pass + return caller_creds + +class Credential(object): + + ## + # Create a Credential object + # + # @param create If true, create a blank x509 certificate + # @param subject If subject!=None, create an x509 cert with the subject name + # @param string If string!=None, load the credential from the string + # @param filename If filename!=None, load the credential from the file + # FIXME: create and subject are ignored! + def __init__(self, create=False, subject=None, string=None, filename=None): + self.gidCaller = None + self.gidObject = None + self.expiration = None + self.privileges = None + self.issuer_privkey = None + self.issuer_gid = None + self.issuer_pubkey = None + self.parent = None + self.signature = None + self.xml = None + self.refid = None + self.legacy = None + + # Check if this is a legacy credential, translate it if so + if string or filename: + if string: + str = string + elif filename: + str = file(filename).read() + + if str.strip().startswith("-----"): + self.legacy = CredentialLegacy(False,string=str) + self.translate_legacy(str) + else: + self.xml = str + self.decode() + + # Find an xmlsec1 path + self.xmlsec_path = '' + paths = ['/usr/bin','/usr/local/bin','/bin','/opt/bin','/opt/local/bin'] + for path in paths: + if os.path.isfile(path + '/' + 'xmlsec1'): + self.xmlsec_path = path + '/' + 'xmlsec1' + break + + def get_subject(self): + if not self.gidObject: + self.decode() + return self.gidObject.get_printable_subject() + + def get_summary_tostring(self): + if not self.gidObject: + self.decode() + obj = self.gidObject.get_printable_subject() + caller = self.gidCaller.get_printable_subject() + exp = self.get_expiration() + # Summarize the rights too? The issuer? + return "[ Grant %s rights on %s until %s ]" % (caller, obj, exp) + + def get_signature(self): + if not self.signature: + self.decode() + return self.signature + + def set_signature(self, sig): + self.signature = sig + + + ## + # Translate a legacy credential into a new one + # + # @param String of the legacy credential + + def translate_legacy(self, str): + legacy = CredentialLegacy(False,string=str) + self.gidCaller = legacy.get_gid_caller() + self.gidObject = legacy.get_gid_object() + lifetime = legacy.get_lifetime() + if not lifetime: + self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME)) + else: + self.set_expiration(int(lifetime)) + self.lifeTime = legacy.get_lifetime() + self.set_privileges(legacy.get_privileges()) + self.get_privileges().delegate_all_privileges(legacy.get_delegate()) + + ## + # Need the issuer's private key and name + # @param key Keypair object containing the private key of the issuer + # @param gid GID of the issuing authority + + def set_issuer_keys(self, privkey, gid): + self.issuer_privkey = privkey + self.issuer_gid = gid + + + ## + # Set this credential's parent + def set_parent(self, cred): + self.parent = cred + self.updateRefID() + + ## + # set the GID of the caller + # + # @param gid GID object of the caller + + def set_gid_caller(self, gid): + self.gidCaller = gid + # gid origin caller is the caller's gid by default + self.gidOriginCaller = gid + + ## + # get the GID of the object + + def get_gid_caller(self): + if not self.gidCaller: + self.decode() + return self.gidCaller + + ## + # set the GID of the object + # + # @param gid GID object of the object + + def set_gid_object(self, gid): + self.gidObject = gid + + ## + # get the GID of the object + + def get_gid_object(self): + if not self.gidObject: + self.decode() + return self.gidObject + + + + ## + # Expiration: an absolute UTC time of expiration (as either an int or string or datetime) + # + def set_expiration(self, expiration): + if isinstance(expiration, (int, float)): + self.expiration = datetime.datetime.fromtimestamp(expiration) + elif isinstance (expiration, datetime.datetime): + self.expiration = expiration + elif isinstance (expiration, StringTypes): + self.expiration = utcparse (expiration) + else: + logger.error ("unexpected input type in Credential.set_expiration") + + + ## + # get the lifetime of the credential (always in datetime format) + + def get_expiration(self): + if not self.expiration: + self.decode() + # at this point self.expiration is normalized as a datetime - DON'T call utcparse again + return self.expiration + + ## + # For legacy sake + def get_lifetime(self): + return self.get_expiration() + + ## + # set the privileges + # + # @param privs either a comma-separated list of privileges of a Rights object + + def set_privileges(self, privs): + if isinstance(privs, str): + self.privileges = Rights(string = privs) + else: + self.privileges = privs + + + ## + # return the privileges as a Rights object + + def get_privileges(self): + if not self.privileges: + self.decode() + return self.privileges + + ## + # determine whether the credential allows a particular operation to be + # performed + # + # @param op_name string specifying name of operation ("lookup", "update", etc) + + def can_perform(self, op_name): + rights = self.get_privileges() + + if not rights: + return False + + return rights.can_perform(op_name) + + + ## + # Encode the attributes of the credential into an XML string + # This should be done immediately before signing the credential. + # WARNING: + # In general, a signed credential obtained externally should + # not be changed else the signature is no longer valid. So, once + # you have loaded an existing signed credential, do not call encode() or sign() on it. + + def encode(self): + # Create the XML document + doc = Document() + signed_cred = doc.createElement("signed-credential") + +# Declare namespaces +# Note that credential/policy.xsd are really the PG schemas +# in a PL namespace. +# Note that delegation of credentials between the 2 only really works +# cause those schemas are identical. +# Also note these PG schemas talk about PG tickets and CM policies. + signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") + signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.planet-lab.org/resources/sfa/credential.xsd") + signed_cred.setAttribute("xsi:schemaLocation", "http://www.planet-lab.org/resources/sfa/ext/policy/1 http://www.planet-lab.org/resources/sfa/ext/policy/1/policy.xsd") + +# PG says for those last 2: +# signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd") +# signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd") + + doc.appendChild(signed_cred) + + # Fill in the bit + cred = doc.createElement("credential") + cred.setAttribute("xml:id", self.get_refid()) + signed_cred.appendChild(cred) + append_sub(doc, cred, "type", "privilege") + append_sub(doc, cred, "serial", "8") + append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string()) + append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn()) + append_sub(doc, cred, "target_gid", self.gidObject.save_to_string()) + append_sub(doc, cred, "target_urn", self.gidObject.get_urn()) + append_sub(doc, cred, "uuid", "") + if not self.expiration: + self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME)) + self.expiration = self.expiration.replace(microsecond=0) + append_sub(doc, cred, "expires", self.expiration.isoformat()) + privileges = doc.createElement("privileges") + cred.appendChild(privileges) + + if self.privileges: + rights = self.get_privileges() + for right in rights.rights: + priv = doc.createElement("privilege") + append_sub(doc, priv, "name", right.kind) + append_sub(doc, priv, "can_delegate", str(right.delegate).lower()) + privileges.appendChild(priv) + + # Add the parent credential if it exists + if self.parent: + sdoc = parseString(self.parent.get_xml()) + # If the root node is a signed-credential (it should be), then + # get all its attributes and attach those to our signed_cred + # node. + # Specifically, PG and PLadd attributes for namespaces (which is reasonable), + # and we need to include those again here or else their signature + # no longer matches on the credential. + # We expect three of these, but here we copy them all: +# signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") +# and from PG (PL is equivalent, as shown above): +# signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd") +# signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd") + + # HOWEVER! + # PL now also declares these, with different URLs, so + # the code notices those attributes already existed with + # different values, and complains. + # This happens regularly on delegation now that PG and + # PL both declare the namespace with different URLs. + # If the content ever differs this is a problem, + # but for now it works - different URLs (values in the attributes) + # but the same actual schema, so using the PG schema + # on delegated-to-PL credentials works fine. + + # Note: you could also not copy attributes + # which already exist. It appears that both PG and PL + # will actually validate a slicecred with a parent + # signed using PG namespaces and a child signed with PL + # namespaces over the whole thing. But I don't know + # if that is a bug in xmlsec1, an accident since + # the contents of the schemas are the same, + # or something else, but it seems odd. And this works. + parentRoot = sdoc.documentElement + if parentRoot.tagName == "signed-credential" and parentRoot.hasAttributes(): + for attrIx in range(0, parentRoot.attributes.length): + attr = parentRoot.attributes.item(attrIx) + # returns the old attribute of same name that was + # on the credential + # Below throws InUse exception if we forgot to clone the attribute first + oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True)) + if oldAttr and oldAttr.value != attr.value: + msg = "Delegating cred from owner %s to %s over %s replaced attribute %s value '%s' with '%s'" % (self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(), self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value) + logger.warn(msg) + #raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg) + + p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True) + p = doc.createElement("parent") + p.appendChild(p_cred) + cred.appendChild(p) + # done handling parent credential + + # Create the tag + signatures = doc.createElement("signatures") + signed_cred.appendChild(signatures) + + # Add any parent signatures + if self.parent: + for cur_cred in self.get_credential_list()[1:]: + sdoc = parseString(cur_cred.get_signature().get_xml()) + ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True) + signatures.appendChild(ele) + + # Get the finished product + self.xml = doc.toxml() + + + def save_to_random_tmp_file(self): + fp, filename = mkstemp(suffix='cred', text=True) + fp = os.fdopen(fp, "w") + self.save_to_file(filename, save_parents=True, filep=fp) + return filename + + def save_to_file(self, filename, save_parents=True, filep=None): + if not self.xml: + self.encode() + if filep: + f = filep + else: + f = open(filename, "w") + f.write(self.xml) + f.close() + + def save_to_string(self, save_parents=True): + if not self.xml: + self.encode() + return self.xml + + def get_refid(self): + if not self.refid: + self.refid = 'ref0' + return self.refid + + def set_refid(self, rid): + self.refid = rid + + ## + # Figure out what refids exist, and update this credential's id + # so that it doesn't clobber the others. Returns the refids of + # the parents. + + def updateRefID(self): + if not self.parent: + self.set_refid('ref0') + return [] + + refs = [] + + next_cred = self.parent + while next_cred: + refs.append(next_cred.get_refid()) + if next_cred.parent: + next_cred = next_cred.parent + else: + next_cred = None + + + # Find a unique refid for this credential + rid = self.get_refid() + while rid in refs: + val = int(rid[3:]) + rid = "ref%d" % (val + 1) + + # Set the new refid + self.set_refid(rid) + + # Return the set of parent credential ref ids + return refs + + def get_xml(self): + if not self.xml: + self.encode() + return self.xml + + ## + # Sign the XML file created by encode() + # + # WARNING: + # In general, a signed credential obtained externally should + # not be changed else the signature is no longer valid. So, once + # you have loaded an existing signed credential, do not call encode() or sign() on it. + + def sign(self): + if not self.issuer_privkey or not self.issuer_gid: + return + doc = parseString(self.get_xml()) + sigs = doc.getElementsByTagName("signatures")[0] + + # Create the signature template to be signed + signature = Signature() + signature.set_refid(self.get_refid()) + sdoc = parseString(signature.get_xml()) + sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True) + sigs.appendChild(sig_ele) + + self.xml = doc.toxml() + + + # Split the issuer GID into multiple certificates if it's a chain + chain = GID(filename=self.issuer_gid) + gid_files = [] + while chain: + gid_files.append(chain.save_to_random_tmp_file(False)) + if chain.get_parent(): + chain = chain.get_parent() + else: + chain = None + + + # Call out to xmlsec1 to sign it + ref = 'Sig_%s' % self.get_refid() + filename = self.save_to_random_tmp_file() + signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \ + % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read() + os.remove(filename) + + for gid_file in gid_files: + os.remove(gid_file) + + self.xml = signed + + # This is no longer a legacy credential + if self.legacy: + self.legacy = None + + # Update signatures + self.decode() + + + ## + # Retrieve the attributes of the credential from the XML. + # This is automatically called by the various get_* methods of + # this class and should not need to be called explicitly. + + def decode(self): + if not self.xml: + return + doc = parseString(self.xml) + sigs = [] + signed_cred = doc.getElementsByTagName("signed-credential") + + # Is this a signed-cred or just a cred? + if len(signed_cred) > 0: + creds = signed_cred[0].getElementsByTagName("credential") + signatures = signed_cred[0].getElementsByTagName("signatures") + if len(signatures) > 0: + sigs = signatures[0].getElementsByTagName("Signature") + else: + creds = doc.getElementsByTagName("credential") + + if creds is None or len(creds) == 0: + # malformed cred file + raise CredentialNotVerifiable("Malformed XML: No credential tag found") + + # Just take the first cred if there are more than one + cred = creds[0] + + self.set_refid(cred.getAttribute("xml:id")) + self.set_expiration(utcparse(getTextNode(cred, "expires"))) + self.gidCaller = GID(string=getTextNode(cred, "owner_gid")) + self.gidObject = GID(string=getTextNode(cred, "target_gid")) + + + # Process privileges + privs = cred.getElementsByTagName("privileges")[0] + rlist = Rights() + for priv in privs.getElementsByTagName("privilege"): + kind = getTextNode(priv, "name") + deleg = str2bool(getTextNode(priv, "can_delegate")) + if kind == '*': + # Convert * into the default privileges for the credential's type + # Each inherits the delegatability from the * above + _ , type = urn_to_hrn(self.gidObject.get_urn()) + rl = determine_rights(type, self.gidObject.get_urn()) + for r in rl.rights: + r.delegate = deleg + rlist.add(r) + else: + rlist.add(Right(kind.strip(), deleg)) + self.set_privileges(rlist) + + + # Is there a parent? + parent = cred.getElementsByTagName("parent") + if len(parent) > 0: + parent_doc = parent[0].getElementsByTagName("credential")[0] + parent_xml = parent_doc.toxml() + self.parent = Credential(string=parent_xml) + self.updateRefID() + + # Assign the signatures to the credentials + for sig in sigs: + Sig = Signature(string=sig.toxml()) + + for cur_cred in self.get_credential_list(): + if cur_cred.get_refid() == Sig.get_refid(): + cur_cred.set_signature(Sig) + + + ## + # Verify + # trusted_certs: A list of trusted GID filenames (not GID objects!) + # Chaining is not supported within the GIDs by xmlsec1. + # + # trusted_certs_required: Should usually be true. Set False means an + # empty list of trusted_certs would still let this method pass. + # It just skips xmlsec1 verification et al. Only used by some utils + # + # Verify that: + # . All of the signatures are valid and that the issuers trace back + # to trusted roots (performed by xmlsec1) + # . The XML matches the credential schema + # . That the issuer of the credential is the authority in the target's urn + # . In the case of a delegated credential, this must be true of the root + # . That all of the gids presented in the credential are valid + # . Including verifying GID chains, and includ the issuer + # . The credential is not expired + # + # -- For Delegates (credentials with parents) + # . The privileges must be a subset of the parent credentials + # . The privileges must have "can_delegate" set for each delegated privilege + # . The target gid must be the same between child and parents + # . The expiry time on the child must be no later than the parent + # . The signer of the child must be the owner of the parent + # + # -- Verify does *NOT* + # . ensure that an xmlrpc client's gid matches a credential gid, that + # must be done elsewhere + # + # @param trusted_certs: The certificates of trusted CA certificates + def verify(self, trusted_certs=None, schema=None, trusted_certs_required=True): + if not self.xml: + self.decode() + + # validate against RelaxNG schema + if HAVELXML and not self.legacy: + if schema and os.path.exists(schema): + tree = etree.parse(StringIO(self.xml)) + schema_doc = etree.parse(schema) + xmlschema = etree.XMLSchema(schema_doc) + if not xmlschema.validate(tree): + error = xmlschema.error_log.last_error + message = "%s: %s (line %s)" % (self.get_summary_tostring(), error.message, error.line) + raise CredentialNotVerifiable(message) + + if trusted_certs_required and trusted_certs is None: + trusted_certs = [] + +# trusted_cert_objects = [GID(filename=f) for f in trusted_certs] + trusted_cert_objects = [] + ok_trusted_certs = [] + # If caller explicitly passed in None that means skip cert chain validation. + # Strange and not typical + if trusted_certs is not None: + for f in trusted_certs: + try: + # Failures here include unreadable files + # or non PEM files + trusted_cert_objects.append(GID(filename=f)) + ok_trusted_certs.append(f) + except Exception, exc: + logger.error("Failed to load trusted cert from %s: %r", f, exc) + trusted_certs = ok_trusted_certs + + # Use legacy verification if this is a legacy credential + if self.legacy: + self.legacy.verify_chain(trusted_cert_objects) + if self.legacy.client_gid: + self.legacy.client_gid.verify_chain(trusted_cert_objects) + if self.legacy.object_gid: + self.legacy.object_gid.verify_chain(trusted_cert_objects) + return True + + # make sure it is not expired + if self.get_expiration() < datetime.datetime.utcnow(): + raise CredentialNotVerifiable("Credential %s expired at %s" % (self.get_summary_tostring(), self.expiration.isoformat())) + + # Verify the signatures + filename = self.save_to_random_tmp_file() + if trusted_certs is not None: + cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs]) + + # If caller explicitly passed in None that means skip cert chain validation. + # - Strange and not typical + if trusted_certs is not None: + # Verify the gids of this cred and of its parents + for cur_cred in self.get_credential_list(): + cur_cred.get_gid_object().verify_chain(trusted_cert_objects) + cur_cred.get_gid_caller().verify_chain(trusted_cert_objects) + + refs = [] + refs.append("Sig_%s" % self.get_refid()) + + parentRefs = self.updateRefID() + for ref in parentRefs: + refs.append("Sig_%s" % ref) + + for ref in refs: + # If caller explicitly passed in None that means skip xmlsec1 validation. + # Strange and not typical + if trusted_certs is None: + break + +# print "Doing %s --verify --node-id '%s' %s %s 2>&1" % \ +# (self.xmlsec_path, ref, cert_args, filename) + verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \ + % (self.xmlsec_path, ref, cert_args, filename)).read() + if not verified.strip().startswith("OK"): + # xmlsec errors have a msg= which is the interesting bit. + mstart = verified.find("msg=") + msg = "" + if mstart > -1 and len(verified) > 4: + mstart = mstart + 4 + mend = verified.find('\\', mstart) + msg = verified[mstart:mend] + raise CredentialNotVerifiable("xmlsec1 error verifying cred %s using Signature ID %s: %s %s" % (self.get_summary_tostring(), ref, msg, verified.strip())) + os.remove(filename) + + # Verify the parents (delegation) + if self.parent: + self.verify_parent(self.parent) + + # Make sure the issuer is the target's authority, and is + # itself a valid GID + self.verify_issuer(trusted_cert_objects) + return True + + ## + # Creates a list of the credential and its parents, with the root + # (original delegated credential) as the last item in the list + def get_credential_list(self): + cur_cred = self + list = [] + while cur_cred: + list.append(cur_cred) + if cur_cred.parent: + cur_cred = cur_cred.parent + else: + cur_cred = None + return list + + ## + # Make sure the credential's target gid (a) was signed by or (b) + # is the same as the entity that signed the original credential, + # or (c) is an authority over the target's namespace. + # Also ensure that the credential issuer / signer itself has a valid + # GID signature chain (signed by an authority with namespace rights). + def verify_issuer(self, trusted_gids): + root_cred = self.get_credential_list()[-1] + root_target_gid = root_cred.get_gid_object() + root_cred_signer = root_cred.get_signature().get_issuer_gid() + + # Case 1: + # Allow non authority to sign target and cred about target. + # + # Why do we need to allow non authorities to sign? + # If in the target gid validation step we correctly + # checked that the target is only signed by an authority, + # then this is just a special case of case 3. + # This short-circuit is the common case currently - + # and cause GID validation doesn't check 'authority', + # this allows users to generate valid slice credentials. + if root_target_gid.is_signed_by_cert(root_cred_signer): + # cred signer matches target signer, return success + return + + # Case 2: + # Allow someone to sign credential about themeselves. Used? + # If not, remove this. + #root_target_gid_str = root_target_gid.save_to_string() + #root_cred_signer_str = root_cred_signer.save_to_string() + #if root_target_gid_str == root_cred_signer_str: + # # cred signer is target, return success + # return + + # Case 3: + + # root_cred_signer is not the target_gid + # So this is a different gid that we have not verified. + # xmlsec1 verified the cert chain on this already, but + # it hasn't verified that the gid meets the HRN namespace + # requirements. + # Below we'll ensure that it is an authority. + # But we haven't verified that it is _signed by_ an authority + # We also don't know if xmlsec1 requires that cert signers + # are marked as CAs. + + # Note that if verify() gave us no trusted_gids then this + # call will fail. So skip it if we have no trusted_gids + if trusted_gids and len(trusted_gids) > 0: + root_cred_signer.verify_chain(trusted_gids) + else: + logger.debug("No trusted gids. Cannot verify that cred signer is signed by a trusted authority. Skipping that check.") + + # See if the signer is an authority over the domain of the target. + # There are multiple types of authority - accept them all here + # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn()) + root_cred_signer_type = root_cred_signer.get_type() + if (root_cred_signer_type.find('authority') == 0): + #logger.debug('Cred signer is an authority') + # signer is an authority, see if target is in authority's domain + signerhrn = root_cred_signer.get_hrn() + if hrn_authfor_hrn(signerhrn, root_target_gid.get_hrn()): + return + + # We've required that the credential be signed by an authority + # for that domain. Reasonable and probably correct. + # A looser model would also allow the signer to be an authority + # in my control framework - eg My CA or CH. Even if it is not + # the CH that issued these, eg, user credentials. + + # Give up, credential does not pass issuer verification + + raise CredentialNotVerifiable("Could not verify credential owned by %s for object %s. Cred signer %s not the trusted authority for Cred target %s" % (self.gidCaller.get_urn(), self.gidObject.get_urn(), root_cred_signer.get_hrn(), root_target_gid.get_hrn())) + + + ## + # -- For Delegates (credentials with parents) verify that: + # . The privileges must be a subset of the parent credentials + # . The privileges must have "can_delegate" set for each delegated privilege + # . The target gid must be the same between child and parents + # . The expiry time on the child must be no later than the parent + # . The signer of the child must be the owner of the parent + def verify_parent(self, parent_cred): + # make sure the rights given to the child are a subset of the + # parents rights (and check delegate bits) + if not parent_cred.get_privileges().is_superset(self.get_privileges()): + raise ChildRightsNotSubsetOfParent(("Parent cred ref %s rights " % parent_cred.get_refid()) + + self.parent.get_privileges().save_to_string() + (" not superset of delegated cred %s ref %s rights " % (self.get_summary_tostring(), self.get_refid())) + + self.get_privileges().save_to_string()) + + # make sure my target gid is the same as the parent's + if not parent_cred.get_gid_object().save_to_string() == \ + self.get_gid_object().save_to_string(): + raise CredentialNotVerifiable("Delegated cred %s: Target gid not equal between parent and child. Parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) + + # make sure my expiry time is <= my parent's + if not parent_cred.get_expiration() >= self.get_expiration(): + raise CredentialNotVerifiable("Delegated credential %s expires after parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) + + # make sure my signer is the parent's caller + if not parent_cred.get_gid_caller().save_to_string(False) == \ + self.get_signature().get_issuer_gid().save_to_string(False): + raise CredentialNotVerifiable("Delegated credential %s not signed by parent %s's caller" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) + + # Recurse + if parent_cred.parent: + parent_cred.verify_parent(parent_cred.parent) + + + def delegate(self, delegee_gidfile, caller_keyfile, caller_gidfile): + """ + Return a delegated copy of this credential, delegated to the + specified gid's user. + """ + # get the gid of the object we are delegating + object_gid = self.get_gid_object() + object_hrn = object_gid.get_hrn() + + # the hrn of the user who will be delegated to + delegee_gid = GID(filename=delegee_gidfile) + delegee_hrn = delegee_gid.get_hrn() + + #user_key = Keypair(filename=keyfile) + #user_hrn = self.get_gid_caller().get_hrn() + subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn) + dcred = Credential(subject=subject_string) + dcred.set_gid_caller(delegee_gid) + dcred.set_gid_object(object_gid) + dcred.set_parent(self) + dcred.set_expiration(self.get_expiration()) + dcred.set_privileges(self.get_privileges()) + dcred.get_privileges().delegate_all_privileges(True) + #dcred.set_issuer_keys(keyfile, delegee_gidfile) + dcred.set_issuer_keys(caller_keyfile, caller_gidfile) + dcred.encode() + dcred.sign() + + return dcred + + # only informative + def get_filename(self): + return getattr(self,'filename',None) + + ## + # Dump the contents of a credential to stdout in human-readable format + # + # @param dump_parents If true, also dump the parent certificates + def dump (self, *args, **kwargs): + print self.dump_string(*args, **kwargs) + + + def dump_string(self, dump_parents=False): + result="" + result += "CREDENTIAL %s\n" % self.get_subject() + filename=self.get_filename() + if filename: result += "Filename %s\n"%filename + result += " privs: %s\n" % self.get_privileges().save_to_string() + gidCaller = self.get_gid_caller() + if gidCaller: + result += " gidCaller:\n" + result += gidCaller.dump_string(8, dump_parents) + + if self.get_signature(): + print " gidIssuer:" + self.get_signature().get_issuer_gid().dump(8, dump_parents) + + gidObject = self.get_gid_object() + if gidObject: + result += " gidObject:\n" + result += gidObject.dump_string(8, dump_parents) + + if self.parent and dump_parents: + result += "\nPARENT" + result += self.parent.dump_string(True) + + return result diff --git a/sfa/trust/credential_legacy.py b/sfa/trust/credential_legacy.py index dda7096f..e66e6993 100644 --- a/sfa/trust/credential_legacy.py +++ b/sfa/trust/credential_legacy.py @@ -7,9 +7,8 @@ import xmlrpclib -from sfa.util.faults import * +from sfa.util.faults import MissingDelegateBit, ChildRightsNotSubsetOfParent from sfa.trust.certificate import Certificate -from sfa.trust.rights import Right,Rights from sfa.trust.gid import GID ## diff --git a/sfa/trust/gid.py b/sfa/trust/gid.py index 15ad6bff..656de4be 100644 --- a/sfa/trust/gid.py +++ b/sfa/trust/gid.py @@ -30,7 +30,7 @@ import uuid from sfa.trust.certificate import Certificate -from sfa.util.faults import * +from sfa.util.faults import GidInvalidParentHrn, GidParentHrn from sfa.util.sfalogging import logger from sfa.util.xrn import hrn_to_urn, urn_to_hrn, hrn_authfor_hrn diff --git a/sfa/trust/hierarchy.py b/sfa/trust/hierarchy.py index dc66ef80..9648c9d4 100644 --- a/sfa/trust/hierarchy.py +++ b/sfa/trust/hierarchy.py @@ -14,7 +14,7 @@ import os -from sfa.util.faults import * +from sfa.util.faults import MissingAuthority from sfa.util.sfalogging import logger from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, urn_to_hrn from sfa.trust.certificate import Keypair @@ -204,7 +204,7 @@ class Hierarchy: def get_auth_info(self, xrn): hrn, type = urn_to_hrn(xrn) if not self.auth_exists(hrn): - logger.warning("Hierarchy: mising authority - xrn=%s, hrn=%s"%(xrn,hrn)) + logger.warning("Hierarchy: missing authority - xrn=%s, hrn=%s"%(xrn,hrn)) raise MissingAuthority(hrn) (directory, gid_filename, privkey_filename, dbinfo_filename) = \ diff --git a/sfa/trust/sfaticket.py b/sfa/trust/sfaticket.py index 0be5d933..018d929e 100644 --- a/sfa/trust/sfaticket.py +++ b/sfa/trust/sfaticket.py @@ -5,8 +5,7 @@ import xmlrpclib from sfa.trust.certificate import Certificate -from sfa.trust.rights import * -from sfa.trust.gid import * +from sfa.trust.gid import GID # Ticket is tuple: # (gidCaller, gidObject, attributes, rspec, delegate) diff --git a/sfa/util/PostgreSQL.py b/sfa/util/PostgreSQL.py index 19cd4d0e..f16f48d7 100644 --- a/sfa/util/PostgreSQL.py +++ b/sfa/util/PostgreSQL.py @@ -21,7 +21,7 @@ import sys try: import pgdb except: print >> sys.stderr, "WARNING, could not import pgdb" -from sfa.util.faults import * +from sfa.util.faults import SfaDBError from sfa.util.sfalogging import logger if not psycopg2: diff --git a/sfa/util/api.py b/sfa/util/api.py index 39d65382..dca8f51e 100644 --- a/sfa/util/api.py +++ b/sfa/util/api.py @@ -2,20 +2,15 @@ # SFA XML-RPC and SOAP interfaces # -import sys -import os -import traceback import string import xmlrpclib -from sfa.util.faults import * -from sfa.util.config import * -import sfa.util.xmlrpcprotocol as xmlrpcprotocol +from sfa.util.faults import SfaNotImplemented, SfaAPIError, SfaInvalidAPIMethod, SfaFault +from sfa.util.config import Config from sfa.util.sfalogging import logger from sfa.trust.auth import Auth from sfa.util.cache import Cache -from sfa.trust.credential import * -from sfa.trust.certificate import * +from sfa.trust.certificate import Keypair, Certificate # this is wrong all right, but temporary from sfa.managers.import_manager import import_manager diff --git a/sfa/util/filter.py b/sfa/util/filter.py index ada44ba5..8f037cab 100644 --- a/sfa/util/filter.py +++ b/sfa/util/filter.py @@ -5,11 +5,10 @@ except NameError: from sets import Set set = Set -import time try: import pgdb except: pass -from sfa.util.faults import * +from sfa.util.faults import SfaInvalidArgument from sfa.util.parameter import Parameter, Mixed, python_type @@ -128,7 +127,7 @@ class Filter(Parameter, dict): for char in modifiers.keys(): if field[0] == char: - modifiers[char]=True; + modifiers[char]=True field = field[1:] break diff --git a/sfa/util/method.py b/sfa/util/method.py index 4c37c676..97ddb399 100644 --- a/sfa/util/method.py +++ b/sfa/util/method.py @@ -3,18 +3,14 @@ # # -import os, time -from types import * -from types import StringTypes -import traceback +import time +from types import IntType, LongType, StringTypes import textwrap -import xmlrpclib from sfa.util.sfalogging import logger -from sfa.util.faults import * +from sfa.util.faults import SfaFault, SfaInvalidAPIMethod, SfaInvalidArgumentCount, SfaInvalidArgument from sfa.util.parameter import Parameter, Mixed, python_type, xmlrpc_type -from sfa.trust.auth import Auth class Method: """ diff --git a/sfa/util/parameter.py b/sfa/util/parameter.py index 7e384192..e520bfc2 100644 --- a/sfa/util/parameter.py +++ b/sfa/util/parameter.py @@ -5,8 +5,8 @@ # Copyright (C) 2006 The Trustees of Princeton University # -from types import * -from sfa.util.faults import * +from types import NoneType, IntType, LongType, FloatType, StringTypes, DictType, TupleType, ListType +from sfa.util.faults import SfaAPIError class Parameter: """ diff --git a/sfa/util/policy.py b/sfa/util/policy.py index 196c71ee..5e43be55 100644 --- a/sfa/util/policy.py +++ b/sfa/util/policy.py @@ -1,6 +1,6 @@ import os -from sfa.util.storage import * +from sfa.util.storage import SimpleStorage class Policy(SimpleStorage): diff --git a/sfa/util/rspecHelper.py b/sfa/util/rspecHelper.py index 89f15afd..deaa746a 100755 --- a/sfa/util/rspecHelper.py +++ b/sfa/util/rspecHelper.py @@ -7,7 +7,7 @@ from lxml import etree from StringIO import StringIO from optparse import OptionParser -from sfa.util.faults import * +from sfa.util.faults import InvalidRSpec from sfa.util.sfalogging import logger def merge_rspecs(rspecs): diff --git a/sfa/util/table.py b/sfa/util/table.py index 0e162897..065e8abb 100644 --- a/sfa/util/table.py +++ b/sfa/util/table.py @@ -3,11 +3,13 @@ # # TODO: Use existing PLC database methods? or keep this separate? -from sfa.util.PostgreSQL import * -from sfa.trust.gid import * -from sfa.util.record import * -from sfa.util.config import * -from sfa.util.filter import * +from types import StringTypes + +from sfa.util.config import Config +from sfa.util.parameter import Parameter +from sfa.util.filter import Filter +from sfa.util.PostgreSQL import PostgreSQL +from sfa.util.record import SfaRecord, AuthorityRecord, NodeRecord, SliceRecord, UserRecord class SfaTable(list): diff --git a/sfa/util/xml.py b/sfa/util/xml.py index e79fd472..a1a6fa34 100755 --- a/sfa/util/xml.py +++ b/sfa/util/xml.py @@ -1,10 +1,8 @@ #!/usr/bin/python from lxml import etree from StringIO import StringIO -from datetime import datetime, timedelta -from sfa.util.xrn import * -from sfa.util.plxrn import hostname_to_urn -from sfa.util.faults import SfaNotImplemented, InvalidXML + +from sfa.util.faults import InvalidXML class XpathFilter: @staticmethod @@ -220,8 +218,9 @@ class XML: f = open(filename, 'w') f.write(self.toxml()) f.close() - -if __name__ == '__main__': - rspec = RSpec('/tmp/resources.rspec') - print rspec + +# no RSpec in scope +#if __name__ == '__main__': +# rspec = RSpec('/tmp/resources.rspec') +# print rspec diff --git a/sfa/util/xrn.py b/sfa/util/xrn.py index 6a2f36e0..1f506289 100644 --- a/sfa/util/xrn.py +++ b/sfa/util/xrn.py @@ -23,7 +23,7 @@ import re -from sfa.util.faults import * +from sfa.util.faults import SfaAPIError # for convenience and smoother translation - we should get rid of these functions eventually def get_leaf(hrn): return Xrn(hrn).get_leaf() @@ -98,7 +98,7 @@ class Xrn: @staticmethod def urn_full (urn): if urn.startswith(Xrn.URN_PREFIX): return urn - else: return Xrn.URN_PREFIX+URN + else: return Xrn.URN_PREFIX+urn @staticmethod def urn_meaningful (urn): if urn.startswith(Xrn.URN_PREFIX): return urn[len(Xrn.URN_PREFIX):] diff --git a/sfatables/commands/Add.py b/sfatables/commands/Add.py index e7657fff..987cff50 100644 --- a/sfatables/commands/Add.py +++ b/sfatables/commands/Add.py @@ -1,7 +1,7 @@ -import os, time +import os import libxml2 from sfatables.command import Command -from sfatables.globals import * +from sfatables.globals import sfatables_config, target_dir, match_dir class Add(Command): def __init__(self): diff --git a/sfatables/commands/Delete.py b/sfatables/commands/Delete.py index 50b1d626..36469908 100644 --- a/sfatables/commands/Delete.py +++ b/sfatables/commands/Delete.py @@ -1,5 +1,5 @@ import os, time -from sfatables.globals import * +from sfatables.globals import sfatables_config from sfatables.command import Command class Delete(Command): diff --git a/sfatables/commands/Insert.py b/sfatables/commands/Insert.py index d4010920..852985eb 100644 --- a/sfatables/commands/Insert.py +++ b/sfatables/commands/Insert.py @@ -1,7 +1,7 @@ import os, time import libxml2 from sfatables.command import Command -from sfatables.globals import * +from sfatables.globals import sfatables_config, target_dir, match_dir class Insert(Command): def __init__(self): diff --git a/sfatables/commands/List.py b/sfatables/commands/List.py index 70d72064..cea40bb7 100644 --- a/sfatables/commands/List.py +++ b/sfatables/commands/List.py @@ -1,7 +1,7 @@ import os, time import libxml2 -from sfatables.globals import * +from sfatables.globals import sfatables_config from sfatables.pretty import Pretty from sfatables.command import Command diff --git a/sfatables/runtime.py b/sfatables/runtime.py index 99226f4f..e22967c7 100644 --- a/sfatables/runtime.py +++ b/sfatables/runtime.py @@ -2,15 +2,13 @@ import sys import os -import pdb -from optparse import OptionParser import libxml2 +import libxslt -from sfatables import commands -from sfatables.globals import * -from sfatables.commands.List import * -from sfatables.xmlrule import * +from sfatables.globals import sfatables_config +from sfatables.commands.List import List +from sfatables.xmlrule import XMLRule class SFATablesRules: def __init__(self, chain_name): diff --git a/sfatables/sfatables b/sfatables/sfatables index b413ef1a..a06680b6 100755 --- a/sfatables/sfatables +++ b/sfatables/sfatables @@ -8,14 +8,12 @@ import sys import os -import pdb import glob -import libxml2 from optparse import OptionParser from sfatables import commands from sfatables.xmlextension import Xmlextension -from sfatables.globals import * +from sfatables.globals import target_dir, match_dir def load_commands(module, list): command_dict={} diff --git a/sfatables/xmlextension.py b/sfatables/xmlextension.py index 5e298db9..f90e0fb1 100644 --- a/sfatables/xmlextension.py +++ b/sfatables/xmlextension.py @@ -5,7 +5,6 @@ # - The parameters that the processor needs to evaluate the context import libxml2 -from sfatables.globals import * class Xmlextension: def __init__(self, file_path): diff --git a/sfatables/xmlrule.py b/sfatables/xmlrule.py index e21f9d86..46f36018 100644 --- a/sfatables/xmlrule.py +++ b/sfatables/xmlrule.py @@ -1,10 +1,11 @@ +import sys,os + import libxml2 # allow to run sfa2wsdl if this is missing (for mac) -import sys try:import libxslt except: print >>sys.stderr, "WARNING, could not import libxslt" -from sfatables.globals import * +from sfatables.globals import sfatables_config class XMLRule: def apply_processor(self, type, doc, output_xpath_filter=None): @@ -88,14 +89,13 @@ class XMLRule: # then target(target_args, rspec) # else rspec - import pdb if (self.match(rspec)): return (True,self.wrap_up(self.target(rspec))) else: return (False,self.wrap_up(rspec)) - def apply_compiled(rspec): + def apply_compiled(self, rspec): # Not supported yet return None -- 2.43.0