Skip to content

Commit

Permalink
Add support for ECC asym keys
Browse files Browse the repository at this point in the history
  • Loading branch information
ammario committed Jun 25, 2016
1 parent c87823d commit 6114f86
Show file tree
Hide file tree
Showing 12 changed files with 394 additions and 34 deletions.
86 changes: 79 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ the key.
- [Symmetric Keys](#symmetric-keys)
- [Make a key service](#make-a-key-service)
- [Make key digest](#make-key-digest)
- [Validate key](#validate-key)
- [Verify key](#verify-key)
- [Using multiple secrets](#using-multiple-secrets)
- [Digest Structure](#digest-structure)
- [Asymmetric Keys](#asymmetric-keys)
- [Make a key pair](#make-a-key-pair)
- [Make key digest](#make-key-digest-1)
- [Verify key](#verify-key-1)
- [Using multiple keys](#using-multiple-keys)
- [Digest Structure](#digest-structure-1)
- [Invalidating keys](#invalidating-keys)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand Down Expand Up @@ -47,20 +53,18 @@ Always use gopkg to install, the repository may be in a broken midway state.
digest, err := ks.Digest(key)

if err != nil {
t.Errorf("Error making digest: %v", err)
return
log.Fatalf("Error making digest: %v", err)
}
fmt.Printf("Digest is %v\n", digest)
```

## Validate key
## Verify key

```go
key, err = ks.Validate(digest)
key, err = ks.Verify(digest)

if err != nil {
t.Errorf("Error reading digest: %v", err)
return
log.Fatalf("Error reading digest: %v", err)
}
//Key authenticated
fmt.Printf("Key: %+v\n", key)
Expand Down Expand Up @@ -108,6 +112,74 @@ It may seem intuitive to put the signature at the end of the digest. It's locate
at the beginning as it makes eyeballing different keys more easy due to
the avalanche effect.

# Asymmetric Keys

## Make a key pair

Make your private key
`openssl ecparam -genkey -name prime256v1 -outform DER -noout -out privatekey.der`

Make your public key
`openssl ec -in privatekey.der -inform DER -outform DER -pubout -out publickey.der`


## Make key digest
```go
privKey, _ = isokey.LoadPrivateKey("priv.key")

ks := AsymKeySigner{
PrivateKey: privKey,
}

key := &Key{
User: 1,
Expires: time.Now().Add(time.Hour)
}

digest, _ := ks.Digest(key)

fmt.Printf("Digest: %v", digest)
```

## Verify key
```go
pubKey, _ = isokey.LoadPublicKey("pub.key")

kv := AsymKeyVerifier{
PublicKey: pubKey,
}

key, err := kv.Verify(digest)
if err != nil {
log.Fatalf("Error verifying key: %v", err)
}
fmt.Printf("Key verified %v\n", key)

```

## Using multiple keys
Similar to symmetric keys, you can use multiple public
or private keys. Refer to the godoc for specifc usage.


## Digest Structure
All binary values are big endian.

| Field | Type |
|--------|------|
| R len | uint8
| R | []byte
| S Len | uint8
| S | []byte
| Made Time (Unix epoch timestamp) | uint32 |
| Expire Time (Unix epoch timestamp) | uint32 |
| Secret Version | uint32 |
| User ID | uint32 |
| Flags | uint32 |

Digests are encoded with Bitcoin's base58 alphabet.


# Invalidating keys

Custom invalidation can be useful if you'd like to support cases where the client
Expand Down
104 changes: 104 additions & 0 deletions asym_key_signer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//Package isokey allows you to make and verify API keys without a database connection via HMAC signatures.
//The keys are scalable and persistent. All information is stored in the key, and with the client.
package isokey

import (
"bytes"
"crypto/ecdsa"
"crypto/sha256"
"encoding/binary"
"io/ioutil"
"time"

"crypto/rand"

"crypto/x509"

"github.com/jbenet/go-base58"
)

//AsymKeySigner facilitates the creation ECDSA API keys
type AsymKeySigner struct {
//PrivateKey is used if GetPrivateKey and KeyMap is nil
PrivateKey *ecdsa.PrivateKey

//PrivateKeyMap maps secret versions to secrets
PrivateKeyMap map[uint32]*ecdsa.PrivateKey

//GetPrivateKey allows you to dynamically use secrets.
//Returning nil indicates that no secret was found for the version
GetPrivateKey func(key *Key) *ecdsa.PrivateKey
}

//LoadPrivateKey loads an ASN.1 ECDSA private key from a file.
func LoadPrivateKey(filename string) (privKey *ecdsa.PrivateKey, err error) {
byt, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
privKey, err = x509.ParseECPrivateKey(byt)
return
}

func (ks *AsymKeySigner) getPrivateKey(key *Key) (privKey *ecdsa.PrivateKey, err error) {
var ok bool
if ks.GetPrivateKey != nil {
privKey = ks.GetPrivateKey(key)
} else if ks.PrivateKeyMap != nil {
privKey, ok = ks.PrivateKeyMap[key.SecretVersion]
if !ok {
return privKey, ErrNoAsymKey
}
} else {
privKey = ks.PrivateKey
}

if privKey == nil {
return privKey, ErrNoAsymKey
}
return
}

//Digest signs the API key and digests it into it's base58 form.
//An error will only be returned if the corresponding key cannot be found from SecretVersion.
//if key.Made is zero it is set to the current time.
func (ks *AsymKeySigner) Digest(key *Key) (digest string, err error) {
message := &bytes.Buffer{}

if key.Made.IsZero() {
key.Made = time.Now()
}

binary.Write(message, binary.BigEndian, uint32(key.Made.Unix()))
binary.Write(message, binary.BigEndian, uint32(key.Expires.Unix()))
binary.Write(message, binary.BigEndian, key.SecretVersion)
binary.Write(message, binary.BigEndian, key.UserID)
binary.Write(message, binary.BigEndian, key.Flags)

privKey, err := ks.getPrivateKey(key)

if err != nil {
return "", err
}

checksum := sha256.Sum256(message.Bytes())
signhash := checksum[:16]

r, s, err := ecdsa.Sign(rand.Reader, privKey, signhash)
if err != nil {
return "", err
}

digestBuf := &bytes.Buffer{}

digestBuf.WriteByte(uint8(len(r.Bytes())))
digestBuf.Write(r.Bytes())
digestBuf.WriteByte(uint8(len(s.Bytes())))
digestBuf.Write(s.Bytes())

//fmt.Printf("r %x s %x buf %x pkXY %s %s\n", r.Bytes(), s.Bytes(), message.Bytes(), privKey.PublicKey.X, privKey.PublicKey.Y)

digestBuf.Write(message.Bytes())

return base58.Encode(digestBuf.Bytes()), nil
}
51 changes: 51 additions & 0 deletions asym_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package isokey

import (
"fmt"
"testing"
"time"
)

func TestASymKeyDigest(t *testing.T) {
privKey, err := LoadPrivateKey("test_assets/privatekey.der")
if err != nil {
t.Errorf("Error loading private key: %v", err)
return
}

keySigner := AsymKeySigner{
PrivateKey: privKey,
}

key := &Key{
UserID: 1,
Expires: time.Now().AddDate(0, 1, 0),
}

digest, err := keySigner.Digest(key)

if err != nil {
t.Errorf("Error making digest: %v", err)
return
}

fmt.Printf("Digest is %v\n", digest)

pubKey, err := LoadPublicKey("test_assets/publickey.der")
if err != nil {
t.Errorf("Error loading public key: %v", err)
return
}
keyVerifier := AsymKeyVerifier{
PublicKey: pubKey,
}

key, err = keyVerifier.Verify(digest)

if err != nil {
t.Errorf("Error verifying key: %v", err)
return
}

fmt.Printf("Key is %+v", key)
}
Loading

0 comments on commit 6114f86

Please sign in to comment.