Merge branch 'master' of danga.com:camlistore

This commit is contained in:
Brett Slatkin 2010-12-01 22:15:39 -08:00
commit c323cb2ce5
14 changed files with 792 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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-----`

View File

@ -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

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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

View File

@ -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
}

View File

@ -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"

View File

@ -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-----

View File

@ -0,0 +1 @@
Hello Brad.

View File

@ -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-----