All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.okhttp3.tls.internal.der.Adapters.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2020 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3.tls.internal.der

import java.math.BigInteger
import java.net.ProtocolException
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.TimeZone
import kotlin.reflect.KClass
import okio.ByteString

/**
 * Built-in adapters for reading standard ASN.1 types.
 */
internal object Adapters {
  val BOOLEAN =
    BasicDerAdapter(
      name = "BOOLEAN",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 1L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): Boolean = reader.readBoolean()

          override fun encode(
            writer: DerWriter,
            value: Boolean,
          ) = writer.writeBoolean(value)
        },
    )

  val INTEGER_AS_LONG =
    BasicDerAdapter(
      name = "INTEGER",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 2L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): Long = reader.readLong()

          override fun encode(
            writer: DerWriter,
            value: Long,
          ) = writer.writeLong(value)
        },
    )

  val INTEGER_AS_BIG_INTEGER =
    BasicDerAdapter(
      name = "INTEGER",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 2L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): BigInteger = reader.readBigInteger()

          override fun encode(
            writer: DerWriter,
            value: BigInteger,
          ) = writer.writeBigInteger(value)
        },
    )

  val BIT_STRING =
    BasicDerAdapter(
      name = "BIT STRING",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 3L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): BitString = reader.readBitString()

          override fun encode(
            writer: DerWriter,
            value: BitString,
          ) = writer.writeBitString(value)
        },
    )

  val OCTET_STRING =
    BasicDerAdapter(
      name = "OCTET STRING",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 4L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): ByteString = reader.readOctetString()

          override fun encode(
            writer: DerWriter,
            value: ByteString,
          ) = writer.writeOctetString(value)
        },
    )

  val NULL =
    BasicDerAdapter(
      name = "NULL",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 5L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): Unit? = null

          override fun encode(
            writer: DerWriter,
            value: Unit?,
          ) {
          }
        },
    )

  val OBJECT_IDENTIFIER =
    BasicDerAdapter(
      name = "OBJECT IDENTIFIER",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 6L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): String = reader.readObjectIdentifier()

          override fun encode(
            writer: DerWriter,
            value: String,
          ) = writer.writeObjectIdentifier(value)
        },
    )

  val UTF8_STRING =
    BasicDerAdapter(
      name = "UTF8",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 12L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): String = reader.readUtf8String()

          override fun encode(
            writer: DerWriter,
            value: String,
          ) = writer.writeUtf8(value)
        },
    )

  /**
   * Permits alphanumerics, spaces, and these:
   *
   * ```
   *   ' () + , - . / : = ?
   * ```
   *
   * TODO(jwilson): constrain to printable string characters.
   */
  val PRINTABLE_STRING =
    BasicDerAdapter(
      name = "PRINTABLE STRING",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 19L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): String = reader.readUtf8String()

          override fun encode(
            writer: DerWriter,
            value: String,
          ) = writer.writeUtf8(value)
        },
    )

  /**
   * Based on International Alphabet No. 5. Note that there are bytes that IA5 and US-ASCII
   * disagree on interpretation.
   *
   * TODO(jwilson): constrain to IA5 characters.
   */
  val IA5_STRING =
    BasicDerAdapter(
      name = "IA5 STRING",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 22L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): String = reader.readUtf8String()

          override fun encode(
            writer: DerWriter,
            value: String,
          ) = writer.writeUtf8(value)
        },
    )

  /**
   * A timestamp like "191216030210Z" or "191215190210-0800" for 2019-12-15T19:02:10-08:00. The
   * cutoff of the 2-digit year is 1950-01-01T00:00:00Z.
   */
  val UTC_TIME =
    BasicDerAdapter(
      name = "UTC TIME",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 23L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): Long {
            val string = reader.readUtf8String()
            return parseUtcTime(string)
          }

          override fun encode(
            writer: DerWriter,
            value: Long,
          ) {
            val string = formatUtcTime(value)
            return writer.writeUtf8(string)
          }
        },
    )

  internal fun parseUtcTime(string: String): Long {
    val utc = TimeZone.getTimeZone("GMT")
    val dateFormat =
      SimpleDateFormat("yyMMddHHmmss'Z'").apply {
        timeZone = utc
        set2DigitYearStart(Date(-631152000000L)) // 1950-01-01T00:00:00Z.
      }

    try {
      val parsed = dateFormat.parse(string)
      return parsed.time
    } catch (e: ParseException) {
      throw ProtocolException("Failed to parse UTCTime $string")
    }
  }

  internal fun formatUtcTime(date: Long): String {
    val utc = TimeZone.getTimeZone("GMT")
    val dateFormat =
      SimpleDateFormat("yyMMddHHmmss'Z'").apply {
        timeZone = utc
        set2DigitYearStart(Date(-631152000000L)) // 1950-01-01T00:00:00Z.
      }

    return dateFormat.format(date)
  }

  /**
   * A timestamp like "191216030210Z" or "20191215190210-0800" for 2019-12-15T19:02:10-08:00. This
   * is the same as [UTC_TIME] with the exception of the 4-digit year.
   */
  val GENERALIZED_TIME =
    BasicDerAdapter(
      name = "GENERALIZED TIME",
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 24L,
      codec =
        object : BasicDerAdapter.Codec {
          override fun decode(reader: DerReader): Long {
            val string = reader.readUtf8String()
            return parseGeneralizedTime(string)
          }

          override fun encode(
            writer: DerWriter,
            value: Long,
          ) {
            val string = formatGeneralizedTime(value)
            return writer.writeUtf8(string)
          }
        },
    )

  /** Decodes any value without interpretation as [AnyValue]. */
  val ANY_VALUE =
    object : DerAdapter {
      override fun matches(header: DerHeader): Boolean = true

      override fun fromDer(reader: DerReader): AnyValue {
        reader.read("ANY") { header ->
          val bytes = reader.readUnknown()
          return AnyValue(
            tagClass = header.tagClass,
            tag = header.tag,
            constructed = header.constructed,
            length = header.length,
            bytes = bytes,
          )
        }
      }

      override fun toDer(
        writer: DerWriter,
        value: AnyValue,
      ) {
        writer.write("ANY", value.tagClass, value.tag) {
          writer.writeOctetString(value.bytes)
          writer.constructed = value.constructed
        }
      }
    }

  internal fun parseGeneralizedTime(string: String): Long {
    val utc = TimeZone.getTimeZone("GMT")
    val dateFormat =
      SimpleDateFormat("yyyyMMddHHmmss'Z'").apply {
        timeZone = utc
      }

    try {
      val parsed = dateFormat.parse(string)
      return parsed.time
    } catch (e: ParseException) {
      throw ProtocolException("Failed to parse GeneralizedTime $string")
    }
  }

  internal fun formatGeneralizedTime(date: Long): String {
    val utc = TimeZone.getTimeZone("GMT")
    val dateFormat =
      SimpleDateFormat("yyyyMMddHHmmss'Z'").apply {
        timeZone = utc
      }

    return dateFormat.format(date)
  }

  /**
   * Returns a composite adapter for a struct or data class. This may be used for both SEQUENCE and
   * SET types.
   *
   * The fields are specified as a list of member adapters. When decoding, a value for each
   * non-optional member but be included in sequence.
   *
   * TODO: for sets, sort by tag when encoding.
   * TODO: for set ofs, sort by encoded value when encoding.
   */
  fun  sequence(
    name: String,
    vararg members: DerAdapter<*>,
    decompose: (T) -> List<*>,
    construct: (List<*>) -> T,
  ): BasicDerAdapter {
    val codec =
      object : BasicDerAdapter.Codec {
        override fun decode(reader: DerReader): T {
          return reader.withTypeHint {
            val list = mutableListOf()

            while (list.size < members.size) {
              val member = members[list.size]
              list += member.fromDer(reader)
            }

            if (reader.hasNext()) {
              throw ProtocolException("unexpected ${reader.peekHeader()} at $reader")
            }

            return@withTypeHint construct(list)
          }
        }

        override fun encode(
          writer: DerWriter,
          value: T,
        ) {
          val list = decompose(value)
          writer.withTypeHint {
            for (i in list.indices) {
              val adapter = members[i] as DerAdapter
              adapter.toDer(writer, list[i])
            }
          }
        }
      }

    return BasicDerAdapter(
      name = name,
      tagClass = DerHeader.TAG_CLASS_UNIVERSAL,
      tag = 16L,
      codec = codec,
    )
  }

  /** Returns an adapter that decodes as the first of a list of available types. */
  fun choice(vararg choices: DerAdapter<*>): DerAdapter, Any?>> {
    return object : DerAdapter, Any?>> {
      override fun matches(header: DerHeader): Boolean = true

      override fun fromDer(reader: DerReader): Pair, Any?> {
        val peekedHeader =
          reader.peekHeader()
            ?: throw ProtocolException("expected a value at $reader")

        val choice =
          choices.firstOrNull { it.matches(peekedHeader) }
            ?: throw ProtocolException(
              "expected a matching choice but was $peekedHeader at $reader",
            )

        return choice to choice.fromDer(reader)
      }

      override fun toDer(
        writer: DerWriter,
        value: Pair, Any?>,
      ) {
        val (adapter, v) = value
        (adapter as DerAdapter).toDer(writer, v)
      }

      override fun toString(): String = choices.joinToString(separator = " OR ")
    }
  }

  /**
   * This decodes a value into its contents using a preceding member of the same SEQUENCE. For
   * example, extensions type IDs specify what types to use for the corresponding values.
   *
   * If the hint is unknown [chooser] should return null which will cause the value to be decoded as
   * an opaque byte string.
   *
   * This may optionally wrap the contents in a tag.
   */
  fun usingTypeHint(chooser: (Any?) -> DerAdapter<*>?): DerAdapter {
    return object : DerAdapter {
      override fun matches(header: DerHeader): Boolean = true

      override fun toDer(
        writer: DerWriter,
        value: Any?,
      ) {
        // If we don't understand this hint, encode the body as a byte string. The byte string
        // will include a tag and length header as a prefix.
        val adapter = chooser(writer.typeHint) as DerAdapter?
        when {
          adapter != null -> adapter.toDer(writer, value)
          else -> writer.writeOctetString(value as ByteString)
        }
      }

      override fun fromDer(reader: DerReader): Any? {
        val adapter = chooser(reader.typeHint) as DerAdapter?
        return when {
          adapter != null -> adapter.fromDer(reader)
          else -> reader.readUnknown()
        }
      }
    }
  }

  /**
   * Object class to adapter type. This approach limits us to one adapter per Kotlin class, which
   * might be too few for values like UTF_STRING and OBJECT_IDENTIFIER that share a Kotlin class but
   * have very different ASN.1 interpretations.
   */
  private val defaultAnyChoices =
    listOf(
      Boolean::class to BOOLEAN,
      BigInteger::class to INTEGER_AS_BIG_INTEGER,
      BitString::class to BIT_STRING,
      ByteString::class to OCTET_STRING,
      Unit::class to NULL,
      Nothing::class to OBJECT_IDENTIFIER,
      Nothing::class to UTF8_STRING,
      String::class to PRINTABLE_STRING,
      Nothing::class to IA5_STRING,
      Nothing::class to UTC_TIME,
      Long::class to GENERALIZED_TIME,
      AnyValue::class to ANY_VALUE,
    )

  fun any(
    vararg choices: Pair, DerAdapter<*>> = defaultAnyChoices.toTypedArray(),
    isOptional: Boolean = false,
    optionalValue: Any? = null,
  ): DerAdapter {
    return object : DerAdapter {
      override fun matches(header: DerHeader): Boolean = true

      override fun toDer(
        writer: DerWriter,
        value: Any?,
      ) {
        when {
          isOptional && value == optionalValue -> {
            // Write nothing.
          }

          else -> {
            for ((type, adapter) in choices) {
              if (type.isInstance(value) || (value == null && type == Unit::class)) {
                (adapter as DerAdapter).toDer(writer, value)
                return
              }
            }
          }
        }
      }

      override fun fromDer(reader: DerReader): Any? {
        if (isOptional && !reader.hasNext()) return optionalValue

        val peekedHeader =
          reader.peekHeader()
            ?: throw ProtocolException("expected a value at $reader")
        for ((_, adapter) in choices) {
          if (adapter.matches(peekedHeader)) {
            return adapter.fromDer(reader)
          }
        }

        throw ProtocolException("expected any but was $peekedHeader at $reader")
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy