diff --git a/server/go/Makefile b/server/go/Makefile index ee6c42555..a35322c20 100644 --- a/server/go/Makefile +++ b/server/go/Makefile @@ -1,11 +1,13 @@ all: make -C auth install make -C http_util install + make -C openpgp make -C blobserver make -C sigserver clean: make -C auth clean make -C http_util clean + make -C openpgp clean make -C blobserver clean make -C sigserver clean diff --git a/server/go/openpgp/Makefile b/server/go/openpgp/Makefile new file mode 100644 index 000000000..62f6b79ef --- /dev/null +++ b/server/go/openpgp/Makefile @@ -0,0 +1,9 @@ +all: + make -C armor install + make -C error install + make -C packet install + +clean: + make -C armor clean + make -C error clean + make -C packet clean diff --git a/server/go/openpgp/armor/Makefile b/server/go/openpgp/armor/Makefile new file mode 100644 index 000000000..244d0ae70 --- /dev/null +++ b/server/go/openpgp/armor/Makefile @@ -0,0 +1,13 @@ +# Copyright 2009 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 $(GOROOT)/src/Make.inc + +TARG=crypto/openpgp/armor +GOFILES=\ + armor.go\ + +include $(GOROOT)/src/Make.pkg + + diff --git a/server/go/openpgp/armor/armor.go b/server/go/openpgp/armor/armor.go new file mode 100644 index 000000000..255a414a8 --- /dev/null +++ b/server/go/openpgp/armor/armor.go @@ -0,0 +1,205 @@ +// Copyright 2010 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 OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is +// very similar to PEM except that it has an additional CRC checksum. +package armor + +import ( + "bytes" + "encoding/base64" +) + +// A Block represents an OpenPGP armored structure. +// +// The encoded form is: +// -----BEGIN Type----- +// Headers +// base64-encoded Bytes +// '=' base64 encoded checksum +// -----END Type----- +// where Headers is a possibly empty sequence of Key: Value lines. +type Block struct { + Type string // The type, taken from the preamble (i.e. "PGP SIGNATURE"). + Headers map[string]string // Optional headers. + Bytes []byte // The decoded bytes of the contents. +} + +// getLine results the first \r\n or \n delineated line from the given byte +// array. The line does not include the \r\n or \n. The remainder of the byte +// array (also not including the new line bytes) is also returned and this will +// always be smaller than the original argument. +func getLine(data []byte) (line, rest []byte) { + i := bytes.Index(data, []byte{'\n'}) + var j int + if i < 0 { + i = len(data) + j = i + } else { + j = i + 1 + if i > 0 && data[i-1] == '\r' { + i-- + } + } + return data[0:i], data[j:] +} + +// removeWhitespace returns a copy of its input with all spaces, tab and +// newline characters removed. +func removeWhitespace(data []byte) []byte { + result := make([]byte, len(data)) + n := 0 + + for _, b := range data { + if b == ' ' || b == '\t' || b == '\r' || b == '\n' { + continue + } + result[n] = b + n++ + } + + return result[0:n] +} + +const crc24Init = 0xb704ce +const crc24Poly = 0x1864cfb + +// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1 +func crc24(d []byte) uint32 { + crc := uint64(crc24Init) + for _, b := range d { + crc ^= uint64(b) << 16 + for i := 0; i < 8; i++ { + crc <<= 1 + if crc&0x1000000 != 0 { + crc ^= crc24Poly + } + } + } + return uint32(crc & 0xffffff) +} + +var armorStart = []byte("\n-----BEGIN ") +var armorEnd = []byte("\n-----END ") +var armorEndOfLine = []byte("-----") + +// Decode will find the next armored block in the input. It returns that block +// and the remainder of the input. If no block is found, p is nil and the whole +// of the input is returned in rest. +func Decode(data []byte) (p *Block, rest []byte) { + // armorStart begins with a newline. However, at the very beginning of + // the byte array, we'll accept the start string without it. + rest = data + if bytes.HasPrefix(data, armorStart[1:]) { + rest = rest[len(armorStart)-1 : len(data)] + } else if i := bytes.Index(data, armorStart); i >= 0 { + rest = rest[i+len(armorStart) : len(data)] + } else { + return nil, data + } + + typeLine, rest := getLine(rest) + if !bytes.HasSuffix(typeLine, armorEndOfLine) { + goto Error + } + typeLine = typeLine[0 : len(typeLine)-len(armorEndOfLine)] + + p = &Block{ + Headers: make(map[string]string), + Type: string(typeLine), + } + + for { + // This loop terminates because getLine's second result is + // always smaller than it's argument. + if len(rest) == 0 { + return nil, data + } + line, next := getLine(rest) + + i := bytes.Index(line, []byte{':'}) + if i == -1 { + break + } + + // TODO(agl): need to cope with values that spread across lines. + key, val := line[0:i], line[i+1:] + key = bytes.TrimSpace(key) + val = bytes.TrimSpace(val) + p.Headers[string(key)] = string(val) + rest = next + } + + i := bytes.Index(rest, armorEnd) + if i < 4 { + print("1\n") + goto Error + } + encodedChecksumLength := 5 + if rest[i-1] == '\r' { + print("2\n") + encodedChecksumLength = 6 + } + encodedChecksum := removeWhitespace(rest[i-encodedChecksumLength : i]) + if encodedChecksum[0] != '=' { + goto Error + } + encodedChecksum = encodedChecksum[1:] + checksumBytes := make([]byte, base64.StdEncoding.DecodedLen(len(encodedChecksum))) + n, err := base64.StdEncoding.Decode(checksumBytes, encodedChecksum) + if err != nil || n != 3 { + goto Error + } + checksum := uint32(checksumBytes[0])<<16 | + uint32(checksumBytes[1])<<8 | + uint32(checksumBytes[2]) + + base64Data := removeWhitespace(rest[0 : i-encodedChecksumLength]) + + p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data))) + n, err = base64.StdEncoding.Decode(p.Bytes, base64Data) + if err != nil { + goto Error + } + p.Bytes = p.Bytes[0:n] + + calculatedChecksum := crc24(p.Bytes) + if calculatedChecksum != checksum { + print("foo ", calculatedChecksum, " ", checksum, "\n") + goto Error + } + + p.Bytes = p.Bytes[0:n] + + _, rest = getLine(rest[i+len(armorEnd):]) + + return + +Error: + // If we get here then we have rejected a likely looking, but + // ultimately invalid block. We need to start over from a new + // position. We have consumed the preamble line and will have consumed + // any lines which could be header lines. However, a valid preamble + // line is not a valid header line, therefore we cannot have consumed + // the preamble line for the any subsequent block. Thus, we will always + // find any valid block, no matter what bytes preceed it. + // + // For example, if the input is + // + // -----BEGIN MALFORMED BLOCK----- + // junk that may look like header lines + // or data lines, but no END line + // + // -----BEGIN ACTUAL BLOCK----- + // realdata + // -----END ACTUAL BLOCK----- + // + // we've failed to parse using the first BEGIN line + // and now will try again, using the second BEGIN line. + p, rest = Decode(rest) + if p == nil { + rest = data + } + return +} diff --git a/server/go/openpgp/armor/armor_test.go b/server/go/openpgp/armor/armor_test.go new file mode 100644 index 000000000..745676c26 --- /dev/null +++ b/server/go/openpgp/armor/armor_test.go @@ -0,0 +1,51 @@ +// Copyright 2010 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 armor + +import ( + "testing" + "fmt" +) + +type GetLineTest struct { + in, out1, out2 string +} + +var getLineTests = []GetLineTest{ + GetLineTest{"abc", "abc", ""}, + GetLineTest{"abc\r", "abc\r", ""}, + GetLineTest{"abc\n", "abc", ""}, + GetLineTest{"abc\r\n", "abc", ""}, + GetLineTest{"abc\nd", "abc", "d"}, + GetLineTest{"abc\r\nd", "abc", "d"}, + GetLineTest{"\nabc", "", "abc"}, + GetLineTest{"\r\nabc", "", "abc"}, +} + +func TestGetLine(t *testing.T) { + for i, test := range getLineTests { + x, y := getLine([]byte(test.in)) + if string(x) != test.out1 || string(y) != test.out2 { + t.Errorf("#%d got:%+v,%+v want:%s,%s", i, x, y, test.out1, test.out2) + } + } +} + +func TestDecode(t *testing.T) { + result, _ := Decode([]byte(armorExample1)) + fmt.Printf("%#v\n", result) +} + +const armorExample1 = `-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.10 (GNU/Linux) + +iQEcBAABAgAGBQJMtFESAAoJEKsQXJGvOPsVj40H/1WW6jaMXv4BW+1ueDSMDwM8 +kx1fLOXbVM5/Kn5LStZNt1jWWnpxdz7eq3uiqeCQjmqUoRde3YbB2EMnnwRbAhpp +cacnAvy9ZQ78OTxUdNW1mhX5bS6q1MTEJnl+DcyigD70HG/yNNQD7sOPMdYQw0TA +byQBwmLwmTsuZsrYqB68QyLHI+DUugn+kX6Hd2WDB62DKa2suoIUIHQQCd/ofwB3 +WfCYInXQKKOSxu2YOg2Eb4kLNhSMc1i9uKUWAH+sdgJh7NBgdoE4MaNtBFkHXRvv +okWuf3+xA9ksp1npSY/mDvgHijmjvtpRDe6iUeqfCn8N9u9CBg8geANgaG8+QA4= +=wfQG +-----END PGP SIGNATURE-----` diff --git a/server/go/openpgp/error/Makefile b/server/go/openpgp/error/Makefile new file mode 100644 index 000000000..590222763 --- /dev/null +++ b/server/go/openpgp/error/Makefile @@ -0,0 +1,12 @@ +# Copyright 2009 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 $(GOROOT)/src/Make.inc + +TARG=crypto/openpgp/error +GOFILES=\ + error.go\ + +include $(GOROOT)/src/Make.pkg + diff --git a/server/go/openpgp/error/error.go b/server/go/openpgp/error/error.go new file mode 100644 index 000000000..fa849926a --- /dev/null +++ b/server/go/openpgp/error/error.go @@ -0,0 +1,13 @@ +package error + +type StructuralError string + +func (s StructuralError) String() string { + return "OpenPGP data invalid: " + string(s) +} + +type Unsupported string + +func (s Unsupported) String() string { + return "OpenPGP feature unsupported: " + string(s) +} diff --git a/server/go/openpgp/example.go b/server/go/openpgp/example.go new file mode 100644 index 000000000..b40aa1053 --- /dev/null +++ b/server/go/openpgp/example.go @@ -0,0 +1,76 @@ +package main + +import ( + "bytes" + "crypto/openpgp/armor" + "crypto/openpgp/packet" + "crypto/rsa" + "crypto/sha1" + "io/ioutil" + "log" +) + +func readOpenPGPPacketFromArmoredFileOrDie(fileName string, armorType string) (p packet.Packet) { + data, err := ioutil.ReadFile(fileName) + if err != nil { + log.Exit("Cannot open '%s': %s", fileName, err) + } + + block, _ := armor.Decode(data) + if block == nil { + log.Exit("cannot parse armor") + } + if block.Type != armorType { + log.Exitf("bad type in '%s' (got: %s, want: %s)", fileName, block.Type, armorType) + } + buf := bytes.NewBuffer(block.Bytes) + p, err = packet.ReadPacket(buf) + if err != nil { + log.Exitf("failed to parse packet from '%s': %s", fileName, err) + } + return +} + +func main() { + signedData, err := ioutil.ReadFile("signed-file") + if err != nil { + log.Exitf("Cannot open 'signed-file': %s", err) + } + + p := readOpenPGPPacketFromArmoredFileOrDie("public-key", "PGP PUBLIC KEY BLOCK") + pk, ok := p.(packet.PublicKeyPacket) + if !ok { + log.Exit("didn't find a public key in the public key file") + } + + p = readOpenPGPPacketFromArmoredFileOrDie("signed-file.asc", "PGP SIGNATURE") + sig, ok := p.(packet.SignaturePacket) + if !ok { + log.Exit("didn't find a signature in the signature file") + } + + if sig.Hash != packet.HashFuncSHA1 { + log.Exit("I only do SHA1") + } + if sig.SigType != packet.SigTypeBinary { + log.Exit("I only do binary signatures") + } + + hash := sha1.New() + hash.Write(signedData) + hash.Write(sig.HashSuffix) + hashBytes := hash.Sum() + + if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] { + log.Exit("hash tag doesn't match") + } + + err = rsa.VerifyPKCS1v15(&pk.PublicKey, rsa.HashSHA1, hashBytes, sig.Signature) + if err != nil { + log.Exitf("bad signature: %s", err) + } + + log.Print("good signature") +} + + diff --git a/server/go/openpgp/packet/Makefile b/server/go/openpgp/packet/Makefile new file mode 100644 index 000000000..822cbc075 --- /dev/null +++ b/server/go/openpgp/packet/Makefile @@ -0,0 +1,12 @@ +# Copyright 2009 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 $(GOROOT)/src/Make.inc + +TARG=crypto/openpgp/packet +GOFILES=\ + packet.go\ + +include $(GOROOT)/src/Make.pkg + diff --git a/server/go/openpgp/packet/packet.go b/server/go/openpgp/packet/packet.go new file mode 100644 index 000000000..c9d8db78a --- /dev/null +++ b/server/go/openpgp/packet/packet.go @@ -0,0 +1,343 @@ +package packet + +import ( + "big" + "crypto/openpgp/error" + "crypto/rsa" + "io" + "os" +) + +func readHeader(r io.Reader) (tag uint8, length uint64, err os.Error) { + var buf [4]byte + _, err = io.ReadFull(r, buf[0:1]) + if err != nil { + return + } + if buf[0] & 0x80 == 0 { + err = error.StructuralError("tag byte does not have MSB set") + return + } + if buf[0] & 0x40 == 0 { + // Old format packet + tag = (buf[0] & 0x3f) >> 2 + lengthType := buf[0] & 3 + if lengthType == 3 { + err = error.Unsupported("indeterminate length packet") + return + } + lengthBytes := 1 << lengthType + _, err = io.ReadFull(r, buf[0:lengthBytes]) + if err != nil { + return + } + for i := 0; i < lengthBytes; i++ { + length <<= 8 + length |= uint64(buf[i]) + } + return + } + + // New format packet + tag = buf[0] & 0x3f + _, err = io.ReadFull(r, buf[0:1]) + if err != nil { + return + } + switch { + case buf[0] < 192: + length = uint64(buf[0]) + case buf[0] < 224: + length = uint64(buf[0] - 192) << 8 + _, err = io.ReadFull(r, buf[0:1]) + if err != nil { + return + } + length += uint64(buf[0]) + 192 + case buf[0] < 255: + err = error.Unsupported("chunked packet") + default: + _, err := io.ReadFull(r, buf[0:4]) + if err != nil { + return + } + length = uint64(buf[0]) << 24 | + uint64(buf[1]) << 16 | + uint64(buf[2]) << 8 | + uint64(buf[3]) + } + return +} + +type Packet interface { + Type() string +} + +func ReadPacket(r io.Reader) (p Packet, err os.Error) { + tag, length, err := readHeader(r) + limitReader := io.LimitReader(r, int64(length)) + switch tag { + case 2: + p, err = readSignaturePacket(limitReader) + case 6: + p, err = readPublicKeyPacket(limitReader) + default: + err = error.Unsupported("unknown packet type") + } + return +} + +type SignatureType uint8 +type PublicKeyAlgorithm uint8 +type HashFunction uint8 + +const ( + SigTypeBinary SignatureType = 0 + SigTypeText SignatureType = 1 + // Many other types omitted +) + +const ( + // RFC 4880, section 9.1 + PubKeyAlgoRSA PublicKeyAlgorithm = 1 + PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2 + PubKeyAlgoRSASignOnly PublicKeyAlgorithm = 3 + PubKeyAlgoElgamal PublicKeyAlgorithm = 16 + PubKeyAlgoDSA PublicKeyAlgorithm = 17 +) + +const ( + // RFC 4880, section 9.4 + HashFuncSHA1 = 2 +) + +type SignaturePacket struct { + SigType SignatureType + PubKeyAlgo PublicKeyAlgorithm + Hash HashFunction + HashSuffix []byte + HashTag [2]byte + CreationTime uint32 + Signature []byte +} + +func (s SignaturePacket) Type() string { + return "signature" +} + +func readSignaturePacket(r io.Reader) (sig SignaturePacket, err os.Error) { + // RFC 4880, section 5.2.3 + var buf [5]byte + _, err = io.ReadFull(r, buf[:1]) + if err != nil { + return + } + if buf[0] != 4 { + err = error.Unsupported("signature packet version") + return + } + + _, err = io.ReadFull(r, buf[:5]) + if err != nil { + return + } + sig.SigType = SignatureType(buf[0]) + sig.PubKeyAlgo = PublicKeyAlgorithm(buf[1]) + switch sig.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: + default: + err = error.Unsupported("public key algorithm") + return + } + sig.Hash = HashFunction(buf[2]) + hashedSubpacketsLength := int(buf[3]) << 8 | int(buf[4]) + l := 6 + hashedSubpacketsLength + sig.HashSuffix = make([]byte, l + 6) + sig.HashSuffix[0] = 4 + copy(sig.HashSuffix[1:], buf[:5]) + hashedSubpackets := sig.HashSuffix[6:l] + _, err = io.ReadFull(r, hashedSubpackets) + if err != nil { + return + } + // See RFC 4880, section 5.2.4 + trailer := sig.HashSuffix[l:] + trailer[0] = 4 + trailer[1] = 0xff + trailer[2] = uint8(l >> 24) + trailer[3] = uint8(l >> 16) + trailer[4] = uint8(l >> 8) + trailer[5] = uint8(l) + + err = parseSignatureSubpackets(&sig, hashedSubpackets, true) + if err != nil { + return + } + + _, err = io.ReadFull(r, buf[:2]) + if err != nil { + return + } + unhashedSubpacketsLength := int(buf[0]) << 8 | int(buf[1]) + unhashedSubpackets := make([]byte, unhashedSubpacketsLength) + _, err = io.ReadFull(r, unhashedSubpackets) + if err != nil { + return + } + err = parseSignatureSubpackets(&sig, unhashedSubpackets, false) + if err != nil { + return + } + + _, err = io.ReadFull(r, sig.HashTag[:2]) + if err != nil { + return + } + + // We have already checked that the public key algorithm is RSA. + sig.Signature, err = readMPI(r) + return +} + +func readMPI(r io.Reader) (mpi []byte, err os.Error) { + var buf [2]byte + _, err = io.ReadFull(r, buf[0:]) + if err != nil { + return + } + numBits := int(buf[0]) << 8 | int(buf[1]) + numBytes := (numBits + 7) / 8 + mpi = make([]byte, numBytes) + _, err = io.ReadFull(r, mpi) + return +} + +func parseSignatureSubpackets(sig *SignaturePacket, subpackets []byte, isHashed bool) (err os.Error) { + for len(subpackets) > 0 { + subpackets, err = parseSignatureSubpacket(sig, subpackets, isHashed) + if err != nil { + return + } + } + + if sig.CreationTime == 0 { + err = error.StructuralError("no creation time in signature") + } + + return +} + +func parseSignatureSubpacket(sig *SignaturePacket, subpacket []byte, isHashed bool) (rest []byte, err os.Error) { + // RFC 4880, section 5.2.3.1 + var length uint32 + switch { + case subpacket[0] < 192: + length = uint32(subpacket[0]) + subpacket = subpacket[1:] + case subpacket[0] < 255: + if len(subpacket) < 2 { + goto Truncated + } + length = uint32(subpacket[0] - 192) << 8 + uint32(subpacket[1]) + 192 + subpacket = subpacket[2:] + default: + if len(subpacket) < 5 { + goto Truncated + } + length = uint32(subpacket[1]) << 24 | + uint32(subpacket[2]) << 16 | + uint32(subpacket[3]) << 8 | + uint32(subpacket[4]) + subpacket = subpacket[5:] + } + if length < uint32(len(subpacket)) { + goto Truncated + } + rest = subpacket[length:] + subpacket = subpacket[:length] + if len(subpacket) == 0 { + err = error.StructuralError("zero length signature subpacket") + return + } + packetType := subpacket[0] & 0x7f + isCritial := subpacket[0] & 0x80 == 0x80 + subpacket = subpacket[1:] + switch packetType { + case 2: + if !isHashed { + err = error.StructuralError("signature creation time in non-hashed area") + return + } + if len(subpacket) != 4 { + err = error.StructuralError("signature creation time not four bytes") + return + } + sig.CreationTime = uint32(subpacket[0]) << 24 | + uint32(subpacket[1]) << 16 | + uint32(subpacket[2]) << 8 | + uint32(subpacket[3]) + default: + if isCritial { + err = error.Unsupported("unknown critical signature subpacket") + return + } + } + return + + Truncated: + err = error.StructuralError("signature subpacket truncated") + return +} + +type PublicKeyPacket struct { + CreationTime uint32 + PubKeyAlgo PublicKeyAlgorithm + PublicKey rsa.PublicKey +} + +func (pk PublicKeyPacket) Type() string { + return "public key" +} + +func readPublicKeyPacket(r io.Reader) (pk PublicKeyPacket, err os.Error) { + // RFC 4880, section 5.5.2 + var buf [6]byte + _, err = io.ReadFull(r, buf[0:]) + if err != nil { + return + } + if buf[0] != 4 { + err = error.Unsupported("public key version") + } + pk.CreationTime = uint32(buf[1]) << 24 | + uint32(buf[2]) << 16 | + uint32(buf[3]) << 8 | + uint32(buf[4]) + pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5]) + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + default: + err = error.Unsupported("public key type") + return + } + nBytes, err := readMPI(r) + if err != nil { + return + } + eBytes, err := readMPI(r) + if err != nil { + return + } + + if len(eBytes) > 3 { + err = error.Unsupported("large public exponent") + return + } + pk.PublicKey.E = 0 + for i := 0; i < len(eBytes); i++ { + pk.PublicKey.E <<= 8 + pk.PublicKey.E |= int(eBytes[i]) + } + pk.PublicKey.N = (new(big.Int)).SetBytes(nBytes) + return +} diff --git a/server/go/openpgp/packet/packet_test.go b/server/go/openpgp/packet/packet_test.go new file mode 100644 index 000000000..5868eb50d --- /dev/null +++ b/server/go/openpgp/packet/packet_test.go @@ -0,0 +1,26 @@ +package packet + +import ( + "bytes" + "encoding/hex" + "testing" + "fmt" +) + +func TestSignatureRead(t *testing.T) { + signatureData, _ := hex.DecodeString(signatureDataHex) + buf := bytes.NewBuffer(signatureData) + packet, err := ReadPacket(buf) + fmt.Printf("%#v, %s\n", packet, err) +} + +func TestPublicKeyRead(t *testing.T) { + pkData, _ := hex.DecodeString(pkDataHex) + buf := bytes.NewBuffer(pkData) + packet, err := ReadPacket(buf) + fmt.Printf("%#v, %s\n", packet, err) +} + +const signatureDataHex = "89011c04000102000605024cb45112000a0910ab105c91af38fb158f8d07ff5596ea368c5efe015bed6e78348c0f033c931d5f2ce5db54ce7f2a7e4b4ad64db758d65a7a71773edeab7ba2a9e0908e6a94a1175edd86c1d843279f045b021a6971a72702fcbd650efc393c5474d5b59a15f96d2eaad4c4c426797e0dcca2803ef41c6ff234d403eec38f31d610c344c06f2401c262f0993b2e66cad8a81ebc4322c723e0d4ba09fe917e8777658307ad8329adacba821420741009dfe87f007759f0982275d028a392c6ed983a0d846f890b36148c7358bdb8a516007fac760261ecd06076813831a36d0459075d1befa245ae7f7fb103d92ca759e9498fe60ef8078a39a3beda510deea251ea9f0a7f0df6ef42060f20780360686f3e400e" + +const pkDataHex = "99010d044ba02c9a010800b043c0fc9d7881a97e3cf479629acfbdf3f9c68efc838c688da245627ec7ab45aeb089fbb7b1a1a05ef541668edf919f284c96639375ac30504a850cc58e371bdae602252b9014d6f2da5a228a1da46a8e6320efd08adba4ac62be949e3976b3bc504574ebf95bbe238b7851875772beb80d21b0d1ae44a8b5530e2ab63b072c57fe6dfcb5dc575f4882492a64b60607018f7a82c4d6a50c35ef682eff83b2ae412556349a5365e5a5233b42ef003238869e383e15ed6952260e1cb9c54d1f8d884044365fea24c31607a7604aea1a713d9d7962ad46563302639954a5b13c84f0b94eb418f26c0916c211d8f5b46c527c5b9fa4d574dbff222c06d73cee5a0d0011010001b41c54657374204b6579203c66616c7365406578616d706c652e636f6d3e89013804130102002205024ba02c9a021b03060b090807030206150802090a0b0416020301021e01021780000a0910ab105c91af38fb1548c507ff79bef8990a3cbfa3553ffbf8929b52b2b6cfe5b5b8ed974ae3c199e90e4f4b77665317212891ce106100b080e65f5967ed29faa6e105f4b91eed70784653b80da51f1771deb86f133568c2da1de552fc9dbf0efa3be40fb53e8c29778f69844b0c9cd32b682fd680722b6f3502ded5ee6da4335ea83bbed1322e081fa9de500bad3d004a6c260be4a8e772fe1a4ead85493a4bc2db5b25a2992833016a83560aaec50b28dbb792ad1f9ac53f18db8c3f896bbd3c4087bd681754e2cf87f9bb2a28ca443b69119c1f6740caa780fe920670df183584f718fcfbfda7dedc0f3e329b764d0bc6dda5398f60b328875ab1ad8efb72515abde2a366dc3c7d242d949b" diff --git a/server/go/openpgp/public-key b/server/go/openpgp/public-key new file mode 100644 index 000000000..ba9656e11 --- /dev/null +++ b/server/go/openpgp/public-key @@ -0,0 +1,18 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +mQENBEugLJoBCACwQ8D8nXiBqX489Hlims+98/nGjvyDjGiNokVifserRa6wifu3 +saGgXvVBZo7fkZ8oTJZjk3WsMFBKhQzFjjcb2uYCJSuQFNby2loiih2kao5jIO/Q +itukrGK+lJ45drO8UEV06/lbviOLeFGHV3K+uA0hsNGuRKi1Uw4qtjsHLFf+bfy1 +3FdfSIJJKmS2BgcBj3qCxNalDDXvaC7/g7KuQSVWNJpTZeWlIztC7wAyOIaeOD4V +7WlSJg4cucVNH42IQEQ2X+okwxYHp2BK6hpxPZ15Yq1GVjMCY5lUpbE8hPC5TrQY +8mwJFsIR2PW0bFJ8W5+k1XTb/yIsBtc87loNABEBAAG0HFRlc3QgS2V5IDxmYWxz +ZUBleGFtcGxlLmNvbT6JATgEEwECACIFAkugLJoCGwMGCwkIBwMCBhUIAgkKCwQW +AgMBAh4BAheAAAoJEKsQXJGvOPsVSMUH/3m++JkKPL+jVT/7+JKbUrK2z+W1uO2X +SuPBmekOT0t3ZlMXISiRzhBhALCA5l9ZZ+0p+qbhBfS5Hu1weEZTuA2lHxdx3rhv +EzVowtod5VL8nb8O+jvkD7U+jCl3j2mESwyc0ytoL9aAcitvNQLe1e5tpDNeqDu+ +0TIuCB+p3lALrT0ASmwmC+So53L+Gk6thUk6S8LbWyWimSgzAWqDVgquxQso27eS +rR+axT8Y24w/iWu9PECHvWgXVOLPh/m7KijKRDtpEZwfZ0DKp4D+kgZw3xg1hPcY +/Pv9p97cDz4ym3ZNC8bdpTmPYLMoh1qxrY77clFaveKjZtw8fSQtlJs= +=K51W +-----END PGP PUBLIC KEY BLOCK----- diff --git a/server/go/openpgp/signed-file b/server/go/openpgp/signed-file new file mode 100644 index 000000000..207fe0478 --- /dev/null +++ b/server/go/openpgp/signed-file @@ -0,0 +1 @@ +Hello Brad. diff --git a/server/go/openpgp/signed-file.asc b/server/go/openpgp/signed-file.asc new file mode 100644 index 000000000..7c91833c1 --- /dev/null +++ b/server/go/openpgp/signed-file.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.10 (GNU/Linux) + +iQEcBAABAgAGBQJMtH2SAAoJEKsQXJGvOPsVJgkH/2Y93lc17iUWdPxgdQuD85tH +2uzr26Hd7XEdxGwySngDf9/Bd4iQpPTMynumpZePUiE/cXrWULgpBamznYPCoRxH +tjKHQrXJ/ENntx/m1gViETg5TbH/KS72eJIm20PmDDsvkvfIFQCnDjQmbgvj2RO2 +vOsWF3IOyO+70C+dCXbdd7Qev/00Rdr40Y4n1VZq6QoC37QsNFlpczZNrQjQCNZj +0Xnw7BnVIOjQnl1mGYcFJlOFGVwUGnj0TkWYPeXtPA39/cWklXInlgaUsFxtUSmB +sq12AY9G4wMOSPlmxtMegkhGScSvFsRtGPLsP8IK127u1EDeDohxM+2mzmtYR+Y= +=4uTw +-----END PGP SIGNATURE-----