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

crypto.protocol.SignedCodec.scala Maven / Gradle / Ivy

The newest version!
//: ----------------------------------------------------------------------------
//: Copyright (C) 2017 Verizon.  All Rights Reserved.
//:
//:   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 nelson
package crypto
package protocol

import nelson.crypto.AuthFailure.SignatureMismatch
import scodec._
import scodec.bits.{BitVector, ByteVector}

/**
 * A codec that delegates through to the provided target codec but prefixes the
 * encoded value with an HMAC signature when encoding and verifies the
 * signature when decoding. This ensures that the encoded bits were not
 * tampered with.
 */
abstract class SignedCodec[A] extends Codec[A] {

  import SignedCodec._

  def signingKey: SignatureKey

  /** Returns the underlying codec that is to be used before the signature is applied */
  def targetCodec: Codec[A]

  /**
   * The codec to use for the signature portion of the encoded bits.
   */
  def signatureCodec: Codec[BitVector]

  def signature(bits: BitVector): Attempt[ByteVector]

  override def sizeBound: SizeBound = signatureCodec.sizeBound.atLeast

  final def encode(a: A): Attempt[BitVector] = {
    for {
      encodedTarget <- targetCodec.encode(a)
      signature <- signature(encodedTarget)
      sigBits <- signatureCodec.encode(signature.toBitVector)
    } yield sigBits ++ encodedTarget
  }

  final def decode(bits: BitVector): Attempt[DecodeResult[A]] = for {
    sigResult <- signatureCodec.decode(bits)
    expectedSignature <- signature(sigResult.remainder)
    result <- {
      val sigBytes = sigResult.value.toByteVector
      if (verifySignature(expectedSignature, sigBytes))
        targetCodec.decode(sigResult.remainder)
      else Attempt.failure(
        AuthFailureErr(SignatureMismatch(expectedSignature.length, sigBytes.size)))
    }
  } yield result

  override def toString: String = "SignedCodec"
}

object SignedCodec {
  val defaultSignatureLengthBytes: Int = 16
  val defaultSignatureLengthBits: Int = defaultSignatureLengthBytes * 8

  val defaultSignatureCodec: Codec[BitVector] = codecs.bits(defaultSignatureLengthBits.toLong)

  def signingKey(env: AuthEnv): SignatureKey =
    env.signingKey

  /**
   * The MessageDigest.isEqual is a constant time equal method to prevent
   * leaking signature size information.
   * It mitigates attacks like http://www.emerose.com/timing-attacks-explained
   */
  def verifySignature(expected: ByteVector, provided: ByteVector): Boolean =
    // this could probably be slightly optimized, but even replacing it with a
    // constant `true` makes a difference of less than 1% in benchmarks.
    java.security.MessageDigest.isEqual(expected.toArray, provided.toArray)

  def fromAuthEnv[A](env: AuthEnv)(codec: EncryptionKey => Codec[A]): Codec[A] =
    new SignedCodec[A] {
      def signingKey: SignatureKey = env.signingKey

      def targetCodec: Codec[A] = codec(env.encryptionKey)

      def signatureCodec: Codec[BitVector] = defaultSignatureCodec

      def signature(bits: BitVector): Attempt[ByteVector] =
        authResultToAttempt(
          env.signer.signature(signingKey, bits.toByteVector, defaultSignatureLengthBytes))
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy