From 0bec484e6fc1553edce91eed523d9642f7870516 Mon Sep 17 00:00:00 2001
From: Adam Langley <agl@golang.org>
Date: Thu, 20 Jan 2011 07:38:34 -0500
Subject: [PATCH] crypto/openpgp: add s2k

s2k implements the string-to-key functions for OpenPGP

R=rsc
CC=golang-dev
https://golang.org/cl/3937043
---
 src/pkg/crypto/openpgp/s2k/Makefile    |  11 ++
 src/pkg/crypto/openpgp/s2k/s2k.go      | 146 +++++++++++++++++++++++++
 src/pkg/crypto/openpgp/s2k/s2k_test.go |  94 ++++++++++++++++
 3 files changed, 251 insertions(+)
 create mode 100644 src/pkg/crypto/openpgp/s2k/Makefile
 create mode 100644 src/pkg/crypto/openpgp/s2k/s2k.go
 create mode 100644 src/pkg/crypto/openpgp/s2k/s2k_test.go

diff --git a/src/pkg/crypto/openpgp/s2k/Makefile b/src/pkg/crypto/openpgp/s2k/Makefile
new file mode 100644
index 0000000000..731d53431d
--- /dev/null
+++ b/src/pkg/crypto/openpgp/s2k/Makefile
@@ -0,0 +1,11 @@
+# Copyright 2011 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+include ../../../../Make.inc
+
+TARG=crypto/openpgp/s2k
+GOFILES=\
+	s2k.go\
+
+include ../../../../Make.pkg
diff --git a/src/pkg/crypto/openpgp/s2k/s2k.go b/src/pkg/crypto/openpgp/s2k/s2k.go
new file mode 100644
index 0000000000..f369d7ed4f
--- /dev/null
+++ b/src/pkg/crypto/openpgp/s2k/s2k.go
@@ -0,0 +1,146 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This package implements the various OpenPGP string-to-key transforms as
+// specified in RFC 4800 section 3.7.1.
+package s2k
+
+import (
+	"crypto/md5"
+	"crypto/openpgp/error"
+	"crypto/ripemd160"
+	"crypto/sha1"
+	"crypto/sha256"
+	"crypto/sha512"
+	"hash"
+	"io"
+	"os"
+)
+
+// Simple writes to out the result of computing the Simple S2K function (RFC
+// 4880, section 3.7.1.1) using the given hash and input passphrase.
+func Simple(out []byte, h hash.Hash, in []byte) {
+	Salted(out, h, in, nil)
+}
+
+var zero [1]byte
+
+// Salted writes to out the result of computing the Salted S2K function (RFC
+// 4880, section 3.7.1.2) using the given hash, input passphrase and salt.
+func Salted(out []byte, h hash.Hash, in []byte, salt []byte) {
+	done := 0
+
+	for i := 0; done < len(out); i++ {
+		h.Reset()
+		for j := 0; j < i; j++ {
+			h.Write(zero[:])
+		}
+		h.Write(salt)
+		h.Write(in)
+		n := copy(out[done:], h.Sum())
+		done += n
+	}
+}
+
+// Iterated writes to out the result of computing the Iterated and Salted S2K
+// function (RFC 4880, section 3.7.1.3) using the given hash, input passphrase,
+// salt and iteration count.
+func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) {
+	combined := make([]byte, len(in)+len(salt))
+	copy(combined, salt)
+	copy(combined[len(salt):], in)
+
+	if count < len(combined) {
+		count = len(combined)
+	}
+
+	done := 0
+	for i := 0; done < len(out); i++ {
+		h.Reset()
+		for j := 0; j < i; j++ {
+			h.Write(zero[:])
+		}
+		written := 0
+		for written < count {
+			if written+len(combined) > count {
+				todo := count - written
+				h.Write(combined[:todo])
+				written = count
+			} else {
+				h.Write(combined)
+				written += len(combined)
+			}
+		}
+		n := copy(out[done:], h.Sum())
+		done += n
+	}
+}
+
+// Parse reads a binary specification for a string-to-key transformation from r
+// and returns a function which performs that transform.
+func Parse(r io.Reader) (f func(out, in []byte), err os.Error) {
+	var buf [9]byte
+
+	_, err = io.ReadFull(r, buf[:2])
+	if err != nil {
+		return
+	}
+
+	h := hashFuncFromType(buf[1])
+	if h == nil {
+		return nil, error.UnsupportedError("hash for S2K function")
+	}
+
+	switch buf[0] {
+	case 1:
+		f := func(out, in []byte) {
+			Simple(out, h, in)
+		}
+		return f, nil
+	case 2:
+		_, err := io.ReadFull(r, buf[:8])
+		if err != nil {
+			return
+		}
+		f := func(out, in []byte) {
+			Salted(out, h, in, buf[:8])
+		}
+		return f, nil
+	case 3:
+		_, err := io.ReadFull(r, buf[:9])
+		if err != nil {
+			return
+		}
+		count := (16 + int(buf[8]&15)) << (uint32(buf[8]>>4) + 6)
+		f := func(out, in []byte) {
+			Iterated(out, h, in, buf[:8], count)
+		}
+		return f, nil
+	}
+
+	return nil, error.UnsupportedError("S2K function")
+}
+
+// hashFuncFromType returns a hash.Hash which corresponds to the given hash
+// type byte. See RFC 4880, section 9.4.
+func hashFuncFromType(hashType byte) hash.Hash {
+	switch hashType {
+	case 1:
+		return md5.New()
+	case 2:
+		return sha1.New()
+	case 3:
+		return ripemd160.New()
+	case 8:
+		return sha256.New()
+	case 9:
+		return sha512.New384()
+	case 10:
+		return sha512.New()
+	case 11:
+		return sha256.New224()
+	}
+
+	return nil
+}
diff --git a/src/pkg/crypto/openpgp/s2k/s2k_test.go b/src/pkg/crypto/openpgp/s2k/s2k_test.go
new file mode 100644
index 0000000000..814b78627f
--- /dev/null
+++ b/src/pkg/crypto/openpgp/s2k/s2k_test.go
@@ -0,0 +1,94 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package s2k
+
+import (
+	"bytes"
+	"crypto/sha1"
+	"encoding/hex"
+	"testing"
+)
+
+var saltedTests = []struct {
+	in, out string
+}{
+	{"hello", "10295ac1"},
+	{"world", "ac587a5e"},
+	{"foo", "4dda8077"},
+	{"bar", "bd8aac6b9ea9cae04eae6a91c6133b58b5d9a61c14f355516ed9370456"},
+	{"x", "f1d3f289"},
+	{"xxxxxxxxxxxxxxxxxxxxxxx", "e00d7b45"},
+}
+
+func TestSalted(t *testing.T) {
+	h := sha1.New()
+	salt := [4]byte{1, 2, 3, 4}
+
+	for i, test := range saltedTests {
+		expected, _ := hex.DecodeString(test.out)
+		out := make([]byte, len(expected))
+		Salted(out, h, []byte(test.in), salt[:])
+		if !bytes.Equal(expected, out) {
+			t.Errorf("#%d, got: %x want: %x", i, out, expected)
+		}
+	}
+}
+
+
+var iteratedTests = []struct {
+	in, out string
+}{
+	{"hello", "83126105"},
+	{"world", "6fa317f9"},
+	{"foo", "8fbc35b9"},
+	{"bar", "2af5a99b54f093789fd657f19bd245af7604d0f6ae06f66602a46a08ae"},
+	{"x", "5a684dfe"},
+	{"xxxxxxxxxxxxxxxxxxxxxxx", "18955174"},
+}
+
+func TestIterated(t *testing.T) {
+	h := sha1.New()
+	salt := [4]byte{4, 3, 2, 1}
+
+	for i, test := range iteratedTests {
+		expected, _ := hex.DecodeString(test.out)
+		out := make([]byte, len(expected))
+		Iterated(out, h, []byte(test.in), salt[:], 31)
+		if !bytes.Equal(expected, out) {
+			t.Errorf("#%d, got: %x want: %x", i, out, expected)
+		}
+	}
+}
+
+
+var parseTests = []struct {
+	spec, in, out string
+}{
+	/* Simple with SHA1 */
+	{"0102", "hello", "aaf4c61d"},
+	/* Salted with SHA1 */
+	{"02020102030405060708", "hello", "f4f7d67e"},
+	/* Iterated with SHA1 */
+	{"03020102030405060708f1", "hello", "f2a57b7c"},
+}
+
+func TestParse(t *testing.T) {
+	for i, test := range parseTests {
+		spec, _ := hex.DecodeString(test.spec)
+		buf := bytes.NewBuffer(spec)
+		f, err := Parse(buf)
+		if err != nil {
+			t.Errorf("%d: Parse returned error: %s", i, err)
+			continue
+		}
+
+		expected, _ := hex.DecodeString(test.out)
+		out := make([]byte, len(expected))
+		f(out, []byte(test.in))
+		if !bytes.Equal(out, expected) {
+			t.Errorf("%d: output got: %x want: %x", i, out, expected)
+		}
+	}
+}
-- 
2.30.9