Skip to content

Commit

Permalink
Merge pull request #18 from searler/checksum
Browse files Browse the repository at this point in the history
generalize SignatureCodec to support checksums and Digests
  • Loading branch information
mpilquist committed Sep 8, 2014
2 parents 49027b5 + ea2469e commit f2c06cf
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 29 deletions.
86 changes: 86 additions & 0 deletions src/main/scala/scodec/codecs/ChecksumCodec.scala
@@ -0,0 +1,86 @@
package scodec
package codecs

import java.security.MessageDigest
import java.util.Arrays
import java.util.zip.{ CRC32, Adler32, Checksum }
import scodec.bits.ByteVector

/**
* Create "checksum" implementations for [[SignerFactory]]
* @group checksum
*/
object ChecksumFactory {
/**
* Creates a `java.security.Digest` factory for the specified algorithm
*
*/
def digest(algorithm: String): SignerFactory =
new DigestFactory(algorithm)

/**
* Fletcher-16 checksum
*/
val fletcher16: SignerFactory = new ChecksumFactory {
def newSigner = new Fletcher16Checksum
}

/**
* CRC-32 checksum
*/
val crc32: SignerFactory = new ChecksumFactory {
def newSigner = new ZipChecksumSigner(new CRC32())
}

/**
* Adler-32 checksum
*/
val adler32: SignerFactory = new ChecksumFactory {
def newSigner = new ZipChecksumSigner(new Adler32())
}

/**
* `java.security.Digest` implementation of Signer
*/
private class DigestSigner(impl: MessageDigest) extends Signer {
def update(data: Array[Byte]): Unit = impl.update(data)
def sign: Array[Byte] = impl.digest
def verify(signature: Array[Byte]): Boolean = MessageDigest.isEqual(impl.digest(), signature)
}

/**
* A checksum does not have a distinct verify implementation
*/
private trait ChecksumFactory extends SignerFactory {
def newVerifier: Signer = newSigner
}

private class DigestFactory(val algorithm: String) extends ChecksumFactory {
def newSigner: Signer = new DigestSigner(MessageDigest.getInstance(algorithm))
}

/**
* http://en.wikipedia.org/wiki/Fletcher's_checksum
*/
private class Fletcher16Checksum extends Signer {
var checksum = (0, 0)
def update(data: Array[Byte]): Unit = {
checksum = data.foldLeft(checksum) { (p, b) =>
val lsb = (p._2 + (0xff & b)) % 255
((p._1 + lsb) % 255, lsb)
}
}
def sign: Array[Byte] = Array(checksum._1.asInstanceOf[Byte], checksum._2.asInstanceOf[Byte])
def verify(signature: Array[Byte]): Boolean = Arrays.equals(sign, signature)
}

/**
* `java.util.zip.Checksum` implementation of Signer
*/
private class ZipChecksumSigner(impl: Checksum) extends Signer {
def update(data: Array[Byte]): Unit = impl.update(data, 0, data.length)
def sign: Array[Byte] = ByteVector.fromLong(impl.getValue()).drop(4).toArray
def verify(signature: Array[Byte]): Boolean = MessageDigest.isEqual(sign, signature)
}

}
65 changes: 43 additions & 22 deletions src/main/scala/scodec/codecs/SignatureCodec.scala
Expand Up @@ -9,50 +9,70 @@ import java.security.cert.Certificate
import scodec.bits.{ BitVector, ByteVector }

/**
* Represents the ability to create a `java.security.Signature` for use with [[fixedSizeSignature]] and [[variableSizeSignature]].
* Represents the ability to create a "checksum" for use with [[fixedSizeSignature]] and [[variableSizeSignature]].
* @group crypto
*/
trait SignatureFactory {
trait Signer {
def update(data:Array[Byte]):Unit
def sign:Array[Byte]
def verify(signature:Array[Byte]):Boolean
}

/** Creates a signature initialized for signing. */
def newSigner: Signature
/**
* Signer implementation for `java.security.Signature`
* @group crypto
*/
class SignatureSigner(impl:Signature) extends Signer {
def update(data:Array[Byte]):Unit = impl.update(data)
def sign:Array[Byte] = impl.sign
def verify(signature:Array[Byte]):Boolean = impl.verify(signature)
}

/** Creates a signature initialized for verifying. */
def newVerifier: Signature
/**
* Represents the ability to create a [[Signer]] for use with [[fixedSizeSignature]] and [[variableSizeSignature]].
* @group crypto
*/
trait SignerFactory {

/** Creates a [[Signer]] initialized for signing. */
def newSigner: Signer

/** Creates a [[Signer]] initialized for verifying. */
def newVerifier: Signer
}

/**
* Companion for [[SignatureFactory]].
* Create `java.security.Signature` implementations for [[SignerFactory]]
* @group crypto
*/
object SignatureFactory {

/** Creates a signature factory for the specified algorithm using the specified private and public keys. */
def apply(algorithm: String, privateKey: PrivateKey, publicKey: PublicKey): SignatureFactory =
def apply(algorithm: String, privateKey: PrivateKey, publicKey: PublicKey): SignerFactory =
new SimpleSignatureFactory(algorithm, privateKey, publicKey)

/** Creates a signature factory for the specified algorithm using the specified key pair. */
def apply(algorithm: String, keyPair: KeyPair): SignatureFactory =
def apply(algorithm: String, keyPair: KeyPair): SignerFactory =
new SimpleSignatureFactory(algorithm, keyPair.getPrivate, keyPair.getPublic)

/** Creates a signature factory for the specified algorithm using the specified key pair. */
def apply(algorithm: String, privateKey: PrivateKey, certificate: Certificate): SignatureFactory =
def apply(algorithm: String, privateKey: PrivateKey, certificate: Certificate): SignerFactory =
new SimpleSignatureFactory(algorithm, privateKey, certificate.getPublicKey)

/** Creates a signature factory that only supports signing for the specified algorithm using the specified private key. */
def signing(algorithm: String, privateKey: PrivateKey): SignatureFactory =
new SimpleSignatureFactorySigning(algorithm, privateKey) with SignatureFactory {
def signing(algorithm: String, privateKey: PrivateKey): SignerFactory =
new SimpleSignatureFactorySigning(algorithm, privateKey) with SignerFactory {
def newVerifier = sys.error("Cannot verify with a signature factory that only supports signing.")
}

/** Creates a signature factory that only supports signing for the specified algorithm using the specified public key. */
def verifying(algorithm: String, publicKey: PublicKey): SignatureFactory =
new SimpleSignatureFactoryVerifying(algorithm, publicKey) with SignatureFactory {
def verifying(algorithm: String, publicKey: PublicKey): SignerFactory =
new SimpleSignatureFactoryVerifying(algorithm, publicKey) with SignerFactory {
def newSigner = sys.error("Cannot sign with a signature factory that only supports verifying.")
}

/** Creates a signature factory that only supports signing for the specified algorithm using the specified public key. */
def verifying(algorithm: String, certificate: Certificate): SignatureFactory =
def verifying(algorithm: String, certificate: Certificate): SignerFactory =
verifying(algorithm, certificate.getPublicKey)


Expand All @@ -64,10 +84,10 @@ object SignatureFactory {

private trait SignatureFactorySigning extends WithSignature {
protected def privateKey: PrivateKey
def newSigner: Signature = {
def newSigner: Signer = {
val sig = newSignature
sig.initSign(privateKey)
sig
new SignatureSigner(sig)
}
}

Expand All @@ -78,10 +98,10 @@ object SignatureFactory {

private trait SignatureFactoryVerifying extends WithSignature {
protected def publicKey: PublicKey
def newVerifier: Signature = {
def newVerifier: Signer = {
val sig = newSignature
sig.initVerify(publicKey)
sig
new SignatureSigner(sig)
}
}

Expand All @@ -94,11 +114,12 @@ object SignatureFactory {
protected val algorithm: String,
protected val privateKey: PrivateKey,
protected val publicKey: PublicKey
) extends SignatureFactory with SignatureFactorySigning with SignatureFactoryVerifying
) extends SignerFactory with SignatureFactorySigning with SignatureFactoryVerifying

}

/** @see [[fixedSizeSignature]] and [[variableSizeSignature]] */
private[codecs] final class SignatureCodec[A](codec: Codec[A], signatureCodec: Codec[BitVector])(implicit signatureFactory: SignatureFactory) extends Codec[A] {
private[codecs] final class SignatureCodec[A](codec: Codec[A], signatureCodec: Codec[BitVector])(implicit signatureFactory: SignerFactory) extends Codec[A] {
import Codec._

override def encode(a: A) = for {
Expand Down
14 changes: 11 additions & 3 deletions src/main/scala/scodec/codecs/package.scala
Expand Up @@ -86,11 +86,16 @@ import scodec.bits.{ BitVector, ByteOrdering, ByteVector }
* Note: this design is heavily based on Scala's parser combinator library and the syntax it provides.
*
*
* === Cryptograhpy Codecs ===
* === Cryptography Codecs ===
*
* There are codecs that support working with encrypted data ([[encrypted]]) and digital signatures
* ([[fixedSizeSignature]] and [[variableSizeSignature]]). Additionally, support for `java.security.cert.Certificate`s
* is provided by [[certificate]] and [[x509Certificate]].
*
* === Checksum Codecs ===
*
* There are codecs that support working with checksums and digests, extending the
* [[fixedSizeSignature]] and [[variableSizeSignature]] functionality.
*
*
* @groupname bits Bits and Bytes Codecs
Expand All @@ -110,6 +115,9 @@ import scodec.bits.{ BitVector, ByteOrdering, ByteVector }
*
* @groupname crypto Cryptography
* @groupprio crypto 4
*
* @groupname checksum Checksums and Digests
* @groupprio checksum 5
*/
package object codecs {

Expand Down Expand Up @@ -826,7 +834,7 @@ package object codecs {
* @param signatureFactory factory to use for signing/verifying
* @group crypto
*/
def fixedSizeSignature[A](size: Int)(codec: Codec[A])(implicit signatureFactory: SignatureFactory): Codec[A] =
def fixedSizeSignature[A](size: Int)(codec: Codec[A])(implicit signatureFactory: SignerFactory): Codec[A] =
new SignatureCodec(codec, fixedSizeBytes(size, BitVectorCodec))(signatureFactory)

/**
Expand All @@ -840,7 +848,7 @@ package object codecs {
* @param signatureFactory factory to use for signing/verifying
* @group crypto
*/
def variableSizeSignature[A](size: Codec[Int])(codec: Codec[A])(implicit signatureFactory: SignatureFactory): Codec[A] =
def variableSizeSignature[A](size: Codec[Int])(codec: Codec[A])(implicit signatureFactory: SignerFactory): Codec[A] =
new SignatureCodec(codec, variableSizeBytes(size, BitVectorCodec))(signatureFactory)

/**
Expand Down
55 changes: 55 additions & 0 deletions src/test/scala/scodec/codecs/FletcherChecksumTest.scala
@@ -0,0 +1,55 @@
package scodec.codecs

import org.scalacheck._
import org.scalatest.WordSpec
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import scodec.bits.ByteVector

/**
* Test the Fletcher checksum functionality
*
*/
class FletcherChecksumTest extends WordSpec with GeneratorDrivenPropertyChecks {

"fletcher16 checksum" should {
/**
* http://en.wikipedia.org/wiki/Fletcher's_checksum
*
* "Example calculation of the Fletcher-16 checksum"
*
*/
"wikipedia" in {
val signer = ChecksumFactory.fletcher16.newSigner
signer.update(Array(0x01, 0x02))
assert(signer.verify(Array(0x04, 0x03)))
}

"0xAA * (3N+2) => Array(0x0,0x55)" in {
forAll(Gen.posNum[Int]) { (n: Int) =>
pattern(n, 2,
Array(0x0, 0x55))
}
}

"0xAA * (3N + 1) => Array(0xAA, 0xAA)" in {
forAll(Gen.posNum[Int]) { (n: Int) =>
pattern(n, 1,
Array(0xAA.asInstanceOf[Byte], 0xAA.asInstanceOf[Byte]))
}
}

"0xAA * (3N) => Array(0x0, 0x0)" in {
forAll(Gen.posNum[Int]) { (n: Int) =>
pattern(n, 0,
Array(0x0, 0x0))
}
}
}

private def pattern(n: Int, delta: Int, expected: Array[Byte]): Unit = {
val signer = ChecksumFactory.fletcher16.newSigner
signer.update(ByteVector.fill(3 * n + delta)(0xAA).toArray)
assert(signer.verify(expected))
}

}
33 changes: 29 additions & 4 deletions src/test/scala/scodec/codecs/SignatureCodecTest.scala
Expand Up @@ -2,6 +2,7 @@ package scodec
package codecs

import java.security.KeyPairGenerator
import java.util.Arrays

class SignatureCodecTest extends CodecSuite {

Expand All @@ -13,7 +14,23 @@ class SignatureCodecTest extends CodecSuite {

"fixedSizeSignature codec" should {
"roundtrip using SHA256withRSA" in {
testFixedSizeSignature(SignatureFactory("SHA256withRSA", keyPair))
testFixedSizeSignature(1024/8)(SignatureFactory("SHA256withRSA", keyPair))
}

"roundtrip using MD5" in {
testFixedSizeSignature(16)(ChecksumFactory.digest("MD5"))
}

"roundtrip using Fletcher" in {
testFixedSizeSignature(2)(ChecksumFactory.fletcher16)
}

"roundtrip using crc32" in {
testFixedSizeSignature(4)(ChecksumFactory.crc32)
}

"roundtrip using adler32" in {
testFixedSizeSignature(4)(ChecksumFactory.adler32)
}
}

Expand All @@ -22,14 +39,22 @@ class SignatureCodecTest extends CodecSuite {
testVariableSizeSignature(SignatureFactory("SHA256withRSA", keyPair))
}
}

"variableSizeSignature codec" should {
"roundtrip using MD5" in {
testVariableSizeSignature(ChecksumFactory.digest("MD5"))
}
}

protected def testFixedSizeSignature(implicit sf: SignatureFactory) {
val codec = fixedSizeSignature(1024/8) { int32 ~ variableSizeBytes(int32, utf8) }
protected def testFixedSizeSignature(size:Int)(implicit sf: SignerFactory) {
val codec = fixedSizeSignature(size) { int32 ~ variableSizeBytes(int32, utf8) }
forAll { (n: Int, s: String, x: Int) => roundtrip(codec, n ~ s) }
}

protected def testVariableSizeSignature(implicit sf: SignatureFactory) {
protected def testVariableSizeSignature(implicit sf: SignerFactory) {
val codec = variableSizeSignature(uint16) { int32 ~ variableSizeBytes(int32, utf8) } ~ int32
forAll { (n: Int, s: String, x: Int) => roundtrip(codec, n ~ s ~ x) }
}


}

0 comments on commit f2c06cf

Please sign in to comment.