Saturday, August 8, 2015

SSL/x.509 certificate infrastructure

After working with x.509 (SSL) certificates a bit, I think some changes in the infrastructure are needed. It's become obvious to me that the major CAs aren't entirely secure, there've been too many compromises of them. In addition they make it too costly to truly use certificates effectively.

For client certificates, StartSSL has the right idea: charging based on the cost of the verification, not per certificate. They'll give you a free SSL client certificate for your e-mail address, because it's all automated and they don't really incur any significant costs above normal operation doing it. If you want a certificate where actual identity's verified by checking passport etc., you pay for the verification when it's done. After that you don't pay anything for getting certificates issued unless/until issuing one requires a new verification. This needs to be the norm for client certificates. In addition we need ways for an organization to issue certificates for it's members. Your employer, for instance, is a much better source of a client certificate tied to your position as an employee because they know for certain who their employees are while a CA doesn't. Ditto my bank: if they issue me a client certificate saying I have such-and-such an account with them, that certificate should be more acceptable to financial sites than a CA-issued one because my bank knows for certain that I've got that account with them and they've followed the required-by-law checks on my identity before giving me that account. And we need to move certificate generation/issuing into mail clients as well as browsers. It's not that hard since most mail clients these days use a browser component to display HTML e-mail and fetch remote content. That component can be used to go through the same process as a browser would. The critical thing is that private key generation is done on the client side and that private key can never exist on the server at any point.

Server certificates are where the real costs come in. It's prohibitively expensive to get server certificates, that's one of the reasons many places haven't gone to strong SSL. It's also difficult to determine from the certificate whether the server really belongs to the organization, because the CA issuing the certificates doesn't know which servers really belong to the organization. I'd much rather see CAs verifying the identity of the organization and giving out a signing certificate that let the organization issue it's own server certificates, with that signing certificate restricted so only certificates claiming the servers belong to that organization are valid. Then companies could issue certificates without worrying about how much it was going to cost. It'd also facilitate issuing client certificates for employees and system-to-system communication. It's not like actually issuing certificates is hard, you can set up Web pages to do it automatically without much trouble and the database to hold all the needed information is fairly trivial if you only need to handle your organization instead of every organization in the world the way CAs have to. Yes, most organizations won't employ the high-level security measures the CAs do to protect the signing keys. OTOH, each organization would be a much less lucrative target because a compromise would only affect one organization's certificates. Because of the way most organizations operate, it'd actually be harder to get at the signing certificates because most of the time they could be on a USB drive in the sysadmin's desk drawer and while physical access might be easy it requires the attacker to be physically at that location and most of them won't be on the same continent as their target.

The above would also make it easier to validate Web sites from the user's end. When I set up access to my bank's Web site, for instance, my browser would ask me to verify the organization issuing certificate. If I said it was OK, the browser would then tie that certificate to a human-readable name for the organization and tie the site I'd visited to that certificate. When I visit a site again, if the organization certificate matches what I have on file the browser lets me in. If the site isn't tied to an organization certificate, it'd prompt me to check the one the site presented and tell it what identity I wanted it tied to. If it was another of my bank's sites (or servers), I'd just tell it "This site belongs to my bank." and it'd make the connection. If the site and the organization certificate didn't match (eg. I thought I was visiting my bank's site but it's not using my bank's organization certificate) I can tell it "No match." and it'd block access. And if I had a match on file but it didn't match what the server was presenting, I'd get a similar prompt but with a stronger warning so I can correct an error if I really need to (eg. the bank's changed things around and I need to change my stuff to match) but it's really likely something malicious is going on and I need to be really sure before I accept it.

All of this would destroy the current business model for certificate authorities, their revenue comes from issuing individual certificates and these changes would mean CAs wouldn't be issuing more than a single certificate per organization instead of dozens to thousands. But there are other opportunities. PGP introduced the idea of "network of trust", where no single signature identified a key as truly belonging to a given individual and you evaluated how many signatures it had and how trustworthy each signature was when making the decision whether the key belonged to who it claimed to or not. The same thing could be done with certificates. I go and get a client certificate from StartSSL. Then when my employer wants to issue me a client certificate I can at the same time submit my own certificate and have my employer sign that as well. When I open a bank account, I can have my bank sign my client certificate as well as issuing me a bank one, leaving my client certificate with 3 signatures on it. Now, suppose my employer's compromised and their signing keys are stolen. My company-issued certificate's invalid, obviously, but my own certificate's still good because StartSSL's and my bank's signatures are still trusted. Same with StartSSL, if they're compromised my own client certificate's still OK because I've got 2 other signatures on it from uncompromised entities. As long as I have at least one uncompromised signature, I can avoid catastrophic failure. This applies to organization and server certificates too, and CAs could replace their revenue stream with one based on cross-certifying certificates so that single compromises wouldn't be fatal to a large number of certificates.

Wednesday, February 4, 2015

Rails ActiveModel/ActiveRecord XML/JSON serialization replacement

I need a bit more control over the XML schema than the standard ActiveRecord/ActiveModel XML serializers provide, and a bit of control over root tags and attribute names in JSON that normally isn't there. I don't need the full control that something like RABL provides, and I'd like deserialization from XML to be automatically available for any XML I can serialize to. None of the existing gems provides what I want without having to hand-code methods on all classes involved, especially the deserialization part. And if I've written the deserialization code and have the hashes to control that, the serialization code based on those same hashes won't be that much additional work. The hard part will be eventually making it a drop-in replacement for the ActiveModel XML and JSON serializers and compatible with the parts of ActiveRecord that interact with serialization. I'll deal with that later, though, I'm just going to lay the groundwork for compatibility now and deal with actually integrating it once I've got the code working outside that framework.

XML serialization/deserialization description and control data:

Item hash:
  • name: element or attribute name
  • source: object attribute name, literal data string, nil for no contents
  • +type: Ruby class name, Boolean short for "TrueClass or FalseClass", Literal for a literal element (no source attribute), default String if this is not the root element or the class being deserialized for the root element
  • options: {options hash}, default {}
  • *attributes: [array of item hashes describing attributes of this element], default []
  • *children: [array of item hashes describing child elements of this element], default []
+ = needed only during deserialization
* = valid only in element item hashes, ignored in attribute item hashes

Options hash:
  • *default: default value if source's value is nil, default no value
  • trim: true/false, trim leading/trailing whitespace, default false
  • elide: true/false, omit if value (after trimming if relevant) is nil or empty, default false
* = not applicable to items of Literal type or items where source is nil.

Control attributes on the object:

xml_attributes: an item hash describing the root element of the object
  • Canonically the source would be nil, but a source attribute is legal if the root element will have actual text content.
  • The type attribute of the root element defaults to the type of the object if not specified, and canonically it isn't specified since forcing a mismatching type will cause problems.
  • The name attribute can be overridden by specifying an element name when serializing the object. When deserializing the root element's name is ignored and it's assumed the caller is deserializing the correct class.
attributes: a hash describing the attributes to be serialized
  • The key is the source attribute name.
  • The value is nil or a string giving the element name to be used.
If xml_attributes is not present, attributes will be used instead. One or the other must be provided.

When serializing XML:
  • If attributes is used then the attributes are serialized as child elements of the root and the default for include_types is true and use_source_names is true if the value is nil and false if the value names an element name. If xml_attributes is used the default for include_types and use_source_names is false. This allows the XML to be unchanged if an old-style attributes hash is being used.
When serializing JSON:
  • If xml_attributes is used then the attributes sub-hash is serialized first followed by the children sub-hash. Items with a nil source or Literal for type are ignored, they're relevant only to XML serialization.
  • When deserializing, if type is absent then the type is determined by Ruby's default rules and the format of the value. Contained objects will end up deserialized to a hash and a class-specific attributes= method will be needed to recognize the attribute name, create an object of the correct class and initialize it with the hash before assigning it to the attribute.
to_xml options:
  • root_name: name of the root element if not the class name of the object
  • include_types: include type and nil attributes in XML
  • use_source_names: follow Ruby's naming conventions for element names
root_name is normally used when attributes is used to control serialization, or if the root element name in xml_attributes needs to be overridden. Setting both include_types and use_source_names to true will yield the same XML normally produced by the old serializer when using xml_attributes .

to_json options:
  • include_root_in_json: include the class name as the root element of the JSON representation, default false
  • include_all_roots_in_json: include the class name as the root element for all contained objects (implies include_root_in_json), default false
include_root_in_json only puts the root element in at the top level, not on contained objects. That makes it compatible with the JSON expected when the matching flag is set in the from_json call.