aboutsummaryrefslogtreecommitdiff
path: root/rpki/fields.py
blob: 3d859aaa9398d7a74102c356f15f87f30c9e01b8 (plain) (blame)
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# $Id$
#
# Copyright (C) 2013--2014  Dragon Research Labs ("DRL")
# Portions copyright (C) 2011--2012  Internet Systems Consortium ("ISC")
#
# 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 AND ISC DISCLAIM ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL DRL OR
# ISC 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.

"""
Common Django ORM field classes.

Many of these are complex ASN.1 DER objects stored as SQL BLOBs, since
the only sane text representation would just be the Base64 encoding of
the DER and thus would add no value.
"""

from django.db import models
from south.modelsinspector import add_introspection_rules

import rpki.x509
import rpki.sundial


class EnumField(models.PositiveSmallIntegerField):
  """
  An enumeration type that uses strings in Python and small integers
  in SQL.
  """

  description = "An enumeration type"

  __metaclass__ = models.SubfieldBase

  def __init__(self, *args, **kwargs):
    if isinstance(kwargs.get("choices"), (tuple, list)) and isinstance(kwargs["choices"][0], (str, unicode)):
      kwargs["choices"] = tuple(enumerate(kwargs["choices"], 1))
    models.PositiveSmallIntegerField.__init__(self, *args, **kwargs)
    self.enum_i2s = dict(self.flatchoices)
    self.enum_s2i = dict((v, k) for k, v in self.flatchoices)

  def to_python(self, value):
    return self.enum_i2s.get(value, value)

  def get_prep_value(self, value):
    return self.enum_s2i.get(value, value)


class SundialField(models.DateTimeField):
  """
  A field type for our customized datetime objects.
  """
  __metaclass__ = models.SubfieldBase

  description = "A datetime type using our customized datetime objects"

  def to_python(self, value):
    if isinstance(value, rpki.sundial.pydatetime.datetime):
      return rpki.sundial.datetime.from_datetime(
        models.DateTimeField.to_python(self, value))
    else:
      return value

  def get_prep_value(self, value):
    if isinstance(value, rpki.sundial.datetime):
      return value.to_datetime()
    else:
      return value


class BlobField(models.Field):
  """
  Basic BLOB field, no type conversion, just an opaque byte string.

  "BLOB" = "Binary Large OBject".  Most SQL implementations seem to
  have such a thing, but support appears to predate standardization,
  so they all do it slightly differently and we have to cope.

  In PostgreSQL, BLOBs are called "bytea".

  In MySQL, there are different sizes of BLOBs and one must pick the
  right one to avoid data truncation.  RPKI manifests and CRLs can be
  longer than 65535 octets, so in MySQL the only safe BLOB type for
  general use is "LONGBLOB".

  SQLite...is not like the other children: data types are more like
  guidelines than actual rules.  But "BLOB" works.

  For anything else, we just use "BLOB" and hope for the best.
  """

  __metaclass__ = models.SubfieldBase
  description   = "Raw BLOB type without ASN.1 encoding/decoding"

  def __init__(self, *args, **kwargs):
    self.blob_type = kwargs.pop("blob_type", None)
    kwargs["serialize"] = False
    kwargs["blank"] = True
    kwargs["default"] = None
    models.Field.__init__(self, *args, **kwargs)

  def db_type(self, connection):
    if self.blob_type is not None:
      return self.blob_type
    elif connection.settings_dict['ENGINE'] == "django.db.backends.mysql":
      return "LONGBLOB"
    elif connection.settings_dict['ENGINE'] == "django.db.backends.posgresql":
      return "bytea"
    else:
      return "BLOB"


# For reasons which now escape me, I had a few fields in the old
# hand-coded SQL which used MySQL type BINARY(20) to hold SKIs.
# Presumably this was so that I could then use those SKIs in indexes
# and searches, but apparently I never got around to that part.
#
# SKIs probably would be better stored as hex strings anyway, so not
# bothering with a separate binary type model for this.  Deal with
# this if and when it ever becomes an issue.


class DERField(BlobField):
  """
  Field class for DER objects.  These are derived from BLOBs, but with
  automatic translation between ASN.1 and Python types.

  DERField itself is an abstract class, concrete field classes are
  derived from it.
  """

  __metaclass__ = models.SubfieldBase

  def to_python(self, value):
    assert value is None or isinstance(value, (self.rpki_type, str))
    if isinstance(value, str):
      return self.rpki_type(DER = value)
    else:
      return value

  def get_prep_value(self, value):
    assert value is None or isinstance(value, (self.rpki_type, str))
    if isinstance(value, self.rpki_type):
      return value.get_DER()
    else:
      return value

class CertificateField(DERField):
  description = "X.509 certificate"
  rpki_type   = rpki.x509.X509

class KeyField(DERField):
  description = "RSA keypair"
  rpki_type   = rpki.x509.RSA

class CRLField(DERField):
  description = "Certificate Revocation List"
  rpki_type   = rpki.x509.CRL

class PKCS10Field(DERField):
  description = "PKCS #10 certificate request"
  rpki_type   = rpki.x509.PKCS10 

class ManifestField(DERField):
  description = "RPKI Manifest"
  rpki_type   = rpki.x509.SignedManifest

class ROAField(DERField):
  description = "ROA"
  rpki_type   = rpki.x509.ROA

class GhostbusterField(DERField):
  description = "Ghostbuster Record"
  rpki_type   = rpki.x509.Ghostbuster


field_classes = (EnumField, SundialField, BlobField, CertificateField, KeyField,
                 CRLField, PKCS10Field, ManifestField, ROAField, GhostbusterField)

add_introspection_rules([(field_classes, [], {})],
                        [r"^rpki\.fields\." + cls.__name__ for cls in field_classes])

del field_classes