1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
# $Id$
#
# Copyright (C) 2013--2014 Dragon Research Labs ("DRL")
# Portions copyright (C) 2009--2012 Internet Systems Consortium ("ISC")
# Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notices and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL,
# ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""
RPKI "up-down" protocol.
"""
import base64
import logging
import lxml.etree
import rpki.resource_set
import rpki.x509
import rpki.exceptions
import rpki.log
import rpki.xml_utils
import rpki.relaxng
from lxml.etree import Element, SubElement, tostring as ElementToString
logger = logging.getLogger(__name__)
xmlns = rpki.relaxng.up_down.xmlns
nsmap = rpki.relaxng.up_down.nsmap
version = "1"
## @var content_type
# MIME content type to use when sending up-down queries.
#content_type = "application/rpki-updown"
content_type = "application/x-rpki"
## @var allowed_content_types
# MIME content types which we consider acceptable for incoming up-down
# queries.
allowed_content_types = ("application/rpki-updown", "application/x-rpki")
tag_certificate = xmlns + "certificate"
tag_class = xmlns + "class"
tag_description = xmlns + "description"
tag_issuer = xmlns + "issuer"
tag_message = xmlns + "message"
tag_request = xmlns + "request"
tag_status = xmlns + "status"
class multi_uri(list):
"""
Container for a set of URIs. This probably could be simplified.
"""
def __init__(self, ini):
list.__init__(self)
if isinstance(ini, (list, tuple)):
self[:] = ini
elif isinstance(ini, str):
self[:] = ini.split(",")
for s in self:
if s.strip() != s or "://" not in s:
raise rpki.exceptions.BadURISyntax("Bad URI \"%s\"" % s)
else:
raise TypeError
def __str__(self):
return ",".join(self)
def rsync(self):
"""
Find first rsync://... URI in self.
"""
for s in self:
if s.startswith("rsync://"):
return s
return None
error_response_codes = {
1101 : "Already processing request",
1102 : "Version number error",
1103 : "Unrecognised request type",
1201 : "Request - no such resource class",
1202 : "Request - no resources allocated in resource class",
1203 : "Request - badly formed certificate request",
1301 : "Revoke - no such resource class",
1302 : "Revoke - no such key",
2001 : "Internal Server Error - Request not performed" }
exception_map = {
rpki.exceptions.NoActiveCA : 1202,
(rpki.exceptions.ClassNameUnknown, "revoke") : 1301,
rpki.exceptions.ClassNameUnknown : 1201,
(rpki.exceptions.NotInDatabase, "revoke") : 1302 }
def check_response(r_msg, q_type):
"""
Additional checks beyond the XML schema for whether this looks like
a reasonable up-down response message.
"""
r_type = r_msg.get("type")
if r_type == "error_response":
raise rpki.exceptions.UpstreamError(error_response_codes[int(r_msg.findtext(tag_status))])
if r_type != q_type + "_response":
raise UnexpectedUpDownResponse
if r_type == "issue_response" and (len(r_msg) != 1 or len(r_msg[0]) != 2):
logger.debug("Weird issue_response %r: len(r_msg) %s len(r_msg[0]) %s",
r_msg, len(r_msg), len(r_msg[0]) if len(r_msg) else None)
logger.debug("Offending message\n%s", ElementToString(r_msg))
raise rpki.exceptions.BadIssueResponse
def generate_error_response(r_msg, status = 2001, description = None):
"""
Generate an error response. If status is given, it specifies the
numeric code to use, otherwise we default to "internal error".
If description is specified, we use it as the description, otherwise
we just use the default string associated with status.
"""
assert status in error_response_codes
del r_msg[:]
r_msg.set("type", "error_response")
SubElement(r_msg, tag_status).text = str(status)
se = SubElement(r_msg, tag_description)
se.set("{http://www.w3.org/XML/1998/namespace}lang", "en-US")
se.text = str(description or error_response_codes[status])
def generate_error_response_from_exception(r_msg, e, q_type):
"""
Construct an error response from an exception. q_type
specifies the kind of query to which this is a response, since the
same exception can generate different codes in response to different
queries.
"""
t = type(e)
code = (exception_map.get((t, q_type)) or exception_map.get(t) or 2001)
generate_error_response(r_msg, code, e)
class cms_msg_no_sax(rpki.x509.XML_CMS_object):
"""
Class to hold a CMS-signed up-down PDU.
Name is a transition kludge: once we ditch SAX, this will become cms_msg.
"""
encoding = "UTF-8"
schema = rpki.relaxng.up_down
allow_extra_certs = True
allow_extra_crls = True
|