secure by design - domain primitives

Abstract

  • Domain primitives are the smallest building blocks and form the basis of your domain model.
  • You should never represent a concept in your domain model as a language primitive or a generic type.
  • If a term in your domain already exists outside of your domain but with a slightly different meaning, you should introduce a new term instead of redefining the existing one.
  • A domain primitive is immutable and can only exist if it’s valid in the current domain.
  • When domain primitives are used, the rest of the code is greatly simplified and becomes more secure.
  • You should harden APIs by using your domain primitive library.
  • A read-once object is a useful way to represent sensitive data in your code.
  • The value of a read-once object can only be retrieved once.
  • The read-once object design pattern can mitigate leakage of sensitive data.
  • Domain primitives provide the same type of security that concurrent taint analysis would.

Domain primitives are the smallest building blocks and form the basis of your domain model.

  • domain primitives and invariants: security issues caused by inexact, error-prone and ambiguous code
  • read-once objects: security problems due to leakage of sensitive data
  • standing on the shoulders of domain primitives: security issues caused by code burdened by too much complexity

Domain primitives, like value objects, are defined by their value rather than by an identity. Domain primitives lower the cognitive load on developers because there’s no need to understand their inner workings in order to use them. You can safely use them with the confidence that they always represent valid values and well-defined concepts. If they aren’t valid, the won’t exist.

  • The invariants of domain primitives are checked at the time of creation.
  • Domain primitives can only exist if they are valid.
  • Domain primitives should always be used instead of language primitives or generic types.
  • Their meaning is defined within the boundaries of the current domain, even if the same term exists outside of the current domain.
  • You should use your domain primitive library to create secure code.

Domain primitives vs value objects

oop - Domain Objects and Value Objects - are they equal?

The Value Object is simple enough to be reusable across different domains. The Domain Objects model your actual domain and are typically written to model your specific business or domain, including your business logic.

Avoid exposing your domain publicly

If you expose your internal domain in the public API, then you can’t evolve your domain without forcing the software clients to evolve with you. Hence, you have no other option but to evolve at the same pace as your consumers are able to adapt their clients.

use a different representation of each of your domain objects, e.g. a DTO

Read-once objects

Key aspects of a read-once object:

  • Its main purpose is to facilitate detection of unintentional use.
  • It represents a sensitive value or concept.
  • It’s often a domain primitive.
  • Its value can be read once, and once only.
  • It prevents serialization of sensitive data.
  • It prevents subclassing and extension.

To enforce such read-once object in Java, we can use:

  • enforce invariants at creation
  • declare class as final to prevent subclassing
  • wrap value in an AtomicReference, so when the accessor method value is called, it sets the sensitive value to null
  • implements java.io.Externalizable interface and always throws an exception in order to prevent accidental serialization
// final to prevent subclassing
public final class SensitiveValue implements java.io.Externalizable {
 
  // declare a transient field
  private transient final AtomicReferece<String> value;
 
  public SensitiveValue(final String value) {
    // validate domain rules at the time of creation
    notBlank(value);
    this.value = new AtomicReference<>(value);
  }
 
  public String value() {
    // reminder that the value can only be retrieved once
    return notNull(value.getAndSet(null), "Sensitive value has already been consumed");
  }
 
  @Override
  public String toString() {
    // toString doesn't reveal the sensitive value
    return "SensitiveValue[value=***]";
  }
 
  @Override
  public void writeExternal(final ObjectOutput out) {
    // throw an exception to prevent serialization
    throw new UnsupportedOperationException("Not allowed on sensitive value");
  }
 
  @Override
  public void readExternal(final ObjectInput in) {
    // throw an exception to prevent serialization
    throw new UnsupportedOperationException("Not allowed on sensitive value");
  }
}

We as humans have a tendency to miss subtle details such as validation when looking at lots of code, and, at the same time, missing validation is something that can cause severe security problems. There’s clearly a need to declutter our entities.

Tip

Use domain primitives for method arguments, constructor arguments, return values, and data fields in entities.

The main benefits of using domain primitives in entity code include the following:

  • Input is always validated. The type system ensures you use domain primitives.
  • Validation is consistent. It’s always done by the domain primitive constructor.
  • Entity code is less cluttered and more to the point. It doesn’t need to do boundary checks, format controls, and so on.
  • Entity code is more readable. It speaks the language of the domain.

Taint analysis

In the field of security research, taint analysis investigates how to stop malicious attack data from being used by marking input as tainted. Every input is considered suspicious until it has been cleared of suspicion, which is done when the data is checked through some mechanism.

Most taint analysis tools follow the same framework for terminology:

  • Taint sources: The places where dirty input might come into the system, which can be user interfaces, import jobs, or integrations with external systems
  • Untainting: The way data is cleaned of suspicion through some type of check
  • Propagation policy: What determines whether the result is marked tainted or not when data is processed or combined
  • Taint sinks: The places where data is used in a sensitive way: rendered to the user, written to the database, or similar

Note

Running an application with taint analysis instrumentation is currently undergoing research, not something we advise you to do in production—the performance penalty is way too high.

Domain primitives provide the same type of security that concurrent taint analysis would.