Security

Introduction

Although the internet was originally designed as a system to withstand atacks by hostile agents, it developed in a co-operative environment of relatively trusted entities. Alas, those days are long gone. Spam mail, denial of service attacks, phishing attempts and so on are indicative that anyone using the internet does so at their own risk.

Applications have to be built to work in hostile correctly in hostile situations. "correctly" no longer means just getting the functional aspects of the program correct, but also means ensuring privacy and integrity of data transferred, access only to legitimate users and other issues.

This of course makes your programs much more complex. There are difficult and subtle computing problems involved in making applications secure. Attempts to do it yourself (such as making up your own encryption libraries) are usually doomed to failure. Instead, you need to make use of libraries designed by security professionals

ISO security architecture

The ISO OSI (open systems interconnect) seven-layer model of distributed systems is well known and is repeated in this figure:

What is less well known is that ISO built a whole series of documents upon this architecture. For our purposes here, the most important is the ISO Security Architecture model, ISO 7498-2.

Functions and levels

The principal functions required of a security system are

These are required at the following levels of the OSI stack:

Mechanisms

Data integrity

Ensuring data integrity means supplying a means of testing that the data has not been tampered with. Usually this is done by forming a simple number oout of the bytes in the data. This process is called hashing and the resulting number is called a hash or hash value.

A naive hashing algorithm is just to sum up all the bytes in the data. However, this still allows almost any amount of changing the data around and still preserving the hash values. For example, an attacker could just swap two bytes. This preserves the hash value, but could end up with you owing someone $65,536 instead of $256.

Hashing algorithms used for security purposes have to be "strong", so that it is very difficult for an attacker to find a different sequence of bytes with the same hash value. This makes it hard to modify the data to the attacker's purposes. Security researchers are constantly testing hash algorithms to see if they can break them - that is, find a simple way of coming up with byte sequences to match a hash value. They have devised a series of cryptographic hashing algorithms which are believed to be strong.

Go has support for several hashing algorithms, including MD4, MD5, RIPEMD-160, SHA1, SHA224, SHA256, SHA384 and SHA512. They all follow the same patter as far as the Go programmer is concerned: a function New (or similar) in the appropriate package returns a Hash object from the hash package.

A Hash has an io.Writer, and you write the data to be hashed to this writer. You can query the number of bytes in the hash value by Size and the hash value by Sum.

A typical case is MD5 hashing. This uses the md5 package. The hash value is a 16 byte array. This is typically printed out in ASCII form as four hexadecimal numbers, each made of 4 bytes. A simple program is


/* MD5Hash
*/

package main

import ("crypto/md5"; "fmt")

func main() {
	hash := md5.New()
	bytes := []byte("hello\n")
	hash.Write(bytes)
	hashValue := hash.Sum()
	hashSize := hash.Size()
	for n := 0; n < hashSize; n+=4 {
		var val uint32 
		val = uint32(hashValue[n]) << 24 +
			uint32(hashValue[n+1]) << 16 +
			uint32(hashValue[n+2]) << 8 +
			uint32(hashValue[n+3])
		fmt.Printf("%x ", val)
	}
	fmt.Println()
}
which prints "b1946ac9 2492d234 7c6235b4 d2611184"

A variation on this is the HMAC (Keyed-Hash Message Authentication Code) which adds a key to the hash algorithm. There is little change in using this. To use MD5 hashing along with a key, replace the call to New by

func NewMD5(key []byte) hash.Hash
    

Symmetric key encryption

There are two major mechanisms used for encrypting data. The first uses a single key that is the same for both encryption and decryption. This key needs to be known to both the encrypting and the decrypting agents. How this key is transmitted between the agents is not discussed.

As with hashing, there are many encryption algorithms. many are now known to have weaknesses, and in general algorithms become weaker over time as computers get faster. Go has support for two symmetric key algorithms: Blowfish and

The algorithms are block algorithms. That is they work on blocks of data. If you data is not aligned to the block size, then you will have to pad it with extra blanks at the end.

Each algorith is represented by a Cipher object. This is created by NewCipher in the appropriate package, and takes the symmetric key as parameter.

Once you have a cipher, you can use it to encrypt and decrypt blocks of data. The blocks have to be 8-bit blocks for Blowfish. A program to illustrate this is


/* Blowfish
*/

package main

import ("fmt"; "crypto/blowfish"; "bytes")

func main() {
	key := []byte("my key")
     	cipher, err := blowfish.NewCipher(key)
	if err != nil {
		fmt.Println(err.String())
	}
	src := []byte("hello\n\n\n")
	var enc [512]byte

	cipher.Encrypt(enc[0:], src)
	
	var decrypt [8]byte
	cipher.Decrypt(decrypt[0:], enc[0:])
	result := bytes.NewBuffer(nil)
	result.Write(decrypt[0:8])
	fmt.Println(string(result.Bytes()))
}

CBC {TO BE DONE]

Public key encryption

A program generating RSA private and public keys is


/* GenRSAKeys
*/

package main

import ("fmt"; "crypto/rsa"; "crypto/rand"; "os"; "gob"; "encoding/pem"; "crypto/x509")

func main() {
	reader := rand.Reader
	bitSize := 512
	key, err := rsa.GenerateKey(reader, bitSize)
	checkError(err)

	fmt.Println("Private key primes", key.P.String(), key.Q.String())
	fmt.Println("Private key exponent", key.D.String())
	
	publicKey := key.PublicKey
	fmt.Println("Public key modulus", publicKey.N.String())
	fmt.Println("Public key exponent", publicKey.E)

	saveGobKey("private.key", key)
	saveGobKey("public.key", publicKey)

	savePEMKey("private.pem", key)
}

func saveGobKey(fileName string, key interface{}) {
	outFile, err := os.Open(fileName, os.O_WRONLY|os.O_CREAT, 0644)
	checkError(err)
	encoder := gob.NewEncoder(outFile)
	err = encoder.Encode(key)
	checkError(err)
	outFile.Close()
}

func savePEMKey(fileName string, key *rsa.PrivateKey) {

	outFile, err := os.Open(fileName, os.O_WRONLY|os.O_CREAT, 0644)
	checkError(err)

	var privateKey = &pem.Block{Type: "RSA PRIVATE KEY",
	Bytes: x509.MarshalPKCS1PrivateKey(key)}

	pem.Encode(outFile, privateKey)

	outFile.Close()
}

func checkError(err os.Error) {
        if err != nil {
                fmt.Println("Fatal error ", err.String())
                os.Exit(1)
        }
}

The program also saves the certificates using gob serialisation. They can be read back by this program:


/* LoadRSAKeys
*/

package main

import ("fmt"; "crypto/rsa"; "os"; "gob")

func main() {
	var key rsa.PrivateKey
	loadKey("private.key", &key)
	
	fmt.Println("Private key primes", key.P.String(), key.Q.String())
	fmt.Println("Private key exponent", key.D.String())
	
	var publicKey rsa.PublicKey
	loadKey("public.key", &publicKey)

	fmt.Println("Public key modulus", publicKey.N.String())
	fmt.Println("Public key exponent", publicKey.E)
}

func loadKey(fileName string, key interface{}) {
	inFile, err := os.Open(fileName, os.O_RDONLY, 0644)
	checkError(err)
	decoder := gob.NewDecoder(inFile)
	err = decoder.Decode(key)
	checkError(err)
	inFile.Close()
}

func checkError(err os.Error) {
        if err != nil {
                fmt.Println("Fatal error ", err.String())
                os.Exit(1)
        }
}

PEM

var pemPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0
fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu
/ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu
RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/
EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A
IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS
tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V
-----END RSA PRIVATE KEY-----
`

func bigFromString(s string) *big.Int {
        ret := new(big.Int)
        ret.SetString(s, 10)
        return ret
}

var rsaPrivateKey = &rsa.PrivateKey{
        PublicKey: rsa.PublicKey{
                N: bigFromString("9353930466774385905609975137998169297361893554
14998671685329502257853572497967725295852446635047121036783518748074826886427746
4700638583474144061408845077"),
                E: 65537,
        },
        D: bigFromString("726639843132811634405769937974922253227934392381906363
94970490393898993285385430876577337665541558398345195294398516730148002612857577
59040931985506583861"),
        P: bigFromString("989203665480846436017288690555926508355729509322669674
61790948584315647051443"),
        Q: bigFromString("945602083088470157474985238840633946716066719049446663
60068158221458669711639"),
}

        block, _ := pem.Decode([]byte(pemPrivateKey))
        priv, err := ParsePKCS1PrivateKey(block.Bytes)
    

X.509 certificates

A Public Key Infrastructure (PKI) is a framework for a collection of public keys, along with additional information such as owner name and location, and links between them giving some sort of approval mechanism.

The principal PKI in use today is based on X.509 certificates. For example, web browsers use them to verify the identity of web sites.

An example program to generate a self-signed X.509 certificate for my web site and store it in a .cer file is

TLS

A server is


/* TLSEchoServer
 */
package main

import ("fmt"; "os"; "crypto/tls"; "net"; "crypto/rand"; "time")

func main() { 

	cert, err := tls.LoadX509KeyPair("/etc/ssl/certs/ssl-cert-snakeoil.pem", "/etc/ssl/private/ssl-cert-snakeoil.key")
	//cert, err := tls.LoadX509KeyPair("jan.newmarch.name.pem", "private.pem")
	checkError(err)
	config := tls.Config {Certificates: []tls.Certificate {cert}}

        now := time.Seconds()
	config.Time = func() int64 { return now }
	config.Rand = rand.Reader

	service := "0.0.0.0:1200"

	listener, err := tls.Listen("tcp", service, &config)
	checkError(err)
	fmt.Println("Listening")
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println(err.String())
			continue
		}
		fmt.Println("Accepted")
		//tlsConn := tls.Server(conn, &config)
		go handleClient(conn)
	}
}

func handleClient(conn net.Conn) {
	defer conn.Close()

	var buf [512]byte
	for {
		fmt.Println("Trying to read")
		n, err := conn.Read(buf[0:])
		if err != nil {
			return
		}
		_, err2 := conn.Write(buf[0:n])
		if err2 != nil {
			return
		}
	}
}

func checkError(err os.Error) {
	if err != nil {
		fmt.Println("Fatal error ", err.String())
		os.Exit(1)
	}
}

The server works fine with a client openssl s_client -tls1 -connect localhost:1200. Note KeyUsage on the cert.

The following client doesn't yet as certificate still no good. Fails with snakeoil certs too.