Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 63 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ latest version:
go get github.com/MyNextID/ioss-rtu-go-sdk@latest
```

Next, include the library in you project:
Next, include the library in your project:
```go
import rtu "github.com/MyNextID/ioss-rtu-go-sdk"
```
Expand All @@ -19,7 +19,14 @@ import rtu "github.com/MyNextID/ioss-rtu-go-sdk"

This library has 3 main usages: `Sign`, `SignExternally` and `Parse` an IOSSRTU token.

All the examples can be found in the [examples](internal/examples) folder
All the examples can be found in the [examples](internal/examples) folder.

Short descriptions for examples:
* **external-signer** — Demonstrates how to use `rtu.ExternalSigner` to prepare an unsigned `rtu.Payload`, export its ASN.1 DER encoded payload and digest for signing with an external private key, and reconstruct the final signed `rtu.PackedRTU` from the returned signature.

* **signer** — Shows how to sign an `rtu.Payload` directly using a `PrivateKey`, producing a fully signed `rtu.PackedRTU` object.

* **verify** — Demonstrates how to verify an `rtu.PackedRTU` (a Base64URL-encoded ASN.1 DER encoded `*rtu.RTU`) by validating its structure and verifying its signature.

---

Expand Down Expand Up @@ -72,23 +79,43 @@ SignedData ::= SEQUENCE {
```go
// Payload is the structureless data container for all versions of RTU
type Payload struct {
// unexported fields
// required info across all versions rtus
validUntil time.Time
transactionID string

// cpk not nil means, this payload was signed.
// it should be set by signers, before Version.Make is called
cpk CPK

// optional keys
delegatedUse *bool
sellerName *string
sellerAddr *string
limitDeliverArea *string
consignmentIDs []string
limitConsignments *int
}
```

`Payload` is a structure used to store all relevant data inside an `RTU.Payload`.
`rtu.Payload` is a structure used to store all relevant data inside an `RTU.Payload`. It allows setting and getting of all stored information of an `IOSS-RTU`. See the [Definitions](#definitions) section for additional rules governing these fields.


It allows setting and getting of all stored information of an `IOSS-RTU`

Each `Version` supported in this library should be able to parse its own data structure into `rtu.SchemaPayload`, which is
an interface that allows conversion to `rtu.Payload`. See the CONTRIBUTION section for more details on `SchemaPayload` and
how to add a new version
an interface that allows conversion to `rtu.Payload`.

---

### SignatureAlgorithms

This SDK has a `PublicKey` and `PrivateKey` structure to help join correct CPK and keys to its rightful `SignatureAlgorithm`
The SDK provides `PublicKey` and `PrivateKey` structs that bind cryptographic keys to their corresponding SignatureAlgorithm.

`PublicKey` wraps a compressed public key (CPK), the underlying public key implementation (such as `*ecdsa.PublicKey` or `*ed25519.PublicKey`),
and its associated `SignatureAlgorithm`.

`PrivateKey` embeds a `PublicKey` and extends it with the corresponding private key implementation (such as `*ecdsa.PrivateKey` or `*ed25519.PrivateKey`).

This design ensures that public/private key pairs are always associated with the correct signature algorithm.

List of available SignatureAlgorithms:
```go
Expand All @@ -98,20 +125,20 @@ const (
)
```

SignatureAlgorithms define a signature algorithm type. It also implements `Digest` method, which returns a digest of a payload
SignatureAlgorithms define a signature algorithm type. It also implements the `Digest` method, which returns a digest of the payload
based on the signature type. Example: `rtu.AlgorithmEcdsaP256` returns a SHA256 digest, to be signed with an ECDSA private key.

---

### CPK

CPK - Compressed Public Key is a raw byte array compressed representation of a public key.
CPK (Compressed Public Key) is a raw byte array compressed representation of a public key.

```go
type CPK []byte
```

It has a method `Parse`, that allows recovery of a publicKey, with a given `SignatureAlgorithm` and returns a `rtu.PublicKey`
It has a method `Parse`, that reconstructs the public key encoded in the CPK using the provided `SignatureAlgorithm`, and returns an `rtu.PublicKey`.


Example: For the signature algorithm `rtu.AlgorithmEcdsaP256`, the CPK value is:
Expand All @@ -124,7 +151,7 @@ var cpk CPK = elliptic.MarshalCompressed(key.Curve, key.X, key.Y)

### Keys

`PublicKey` is the combination of a SignatureAlgorithm with a publicKey `crypto.PublicKey` and a computedCPK `rtu.CPK`.
`rtu.PublicKey` is the combination of a SignatureAlgorithm with a publicKey `crypto.PublicKey` and a computedCPK `rtu.CPK`.

```go
type PublicKey struct {
Expand All @@ -135,7 +162,7 @@ type PublicKey struct {
}
```

`PrivateKey` is the same as `PublicKey` but also adds the correct privateKey into the combination.
`rtu.PrivateKey` extends `rtu.PublicKey` by additionally including the corresponding private key material.

```go
type PrivateKey struct {
Expand All @@ -152,7 +179,7 @@ For future improvements to the RTU structure and/or adding signature support, ea
which defines the signature type, signature algorithms supported, maxRTUSize, maxRTUPayload size and payload structure along with
all the validation rules for the fields inside the correct payload structure.

Currently this library support IOSS-RTU Versions:
Currently this library supports IOSS-RTU Versions:
```go
const (
Version1 Version = 1
Expand Down Expand Up @@ -240,12 +267,17 @@ NOTE: `ConsignmentIDs` and `LimitConsignments` are exclusive. If both are set, a

## Signers

### SignV1
This SDK has a `Signer` defined as a function below:

`SignV1` is a `rtu.Signer` function, that signs a `rtu.Payload` with an ECDSA-P256 private key (version 1 only supports that algorithm),
and creates a `rtu.Version1` RTU
```go
type Signer func(payload *Payload, key *PrivateKey) (*RTU, error)
```

### SignV1

`SignV1` is a `rtu.Signer` function, that signs a `rtu.Payload` as a `rtu.Version1` IOSSRTU.
It accepts an ECDSA-P256 private key (version 1 only supports that algorithm)
```go
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
Expand All @@ -264,8 +296,10 @@ signedRtu, err := rtu.SignV1(payload, privateKey)

### Sign

`Sign` is a helper function, that takes a `rtu.Version`, `rtu.Payload` and `rtu.PrivateKey` variable, and generates a `rtu.PackedRTU`
`Sign` is an ease-of-use function, that allows `rtu.Version` to be given as a parameter along with `rtu.Payload` and `rtu.PrivateKey`,
to generate a `rtu.PackedRTU`

Usage:
```go
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
Expand All @@ -280,21 +314,24 @@ if err != nil {
payload := rtu.NewPayload("TX_ID", time.Now().Add(time.Hour)).SetDelegatedUse(false)

// Sign the payload as an Version1 RTU object, with the given privateKey
packedRtu, err := rtu.Sign(rtu.Version1, paylaod, privateKey)

packedRtu, err := rtu.Sign(rtu.Version1, payload, privateKey)
```

### External Signer

`ExternalSigner` is a service, that enables issuers to sign via an external private key. It needs the `rtu.PublicKey` representation
of the external signer's public key, and the version of the RTU object it is producing.

You can then use its methods `ComputeDigest` to generate the correct digest to sign and the raw payload, that was constructed from `rtu.Payload`.
Signing the digest with the correct private key, and offering the signature along with the given raw payload into `ConstructSigned` allows the signer
to validate your signature (and validate it was signed with the correct private key) and construct the correct RTU object.
You can use its `ComputeDigest` method to generate the digest that must be signed, as well as the raw payload derived from `rtu.Payload`.

Once the digest is signed with the appropriate private key, the resulting signature (together with the raw payload) can be passed into `ConstructSigned`. This allows the signer to verify that the signature is valid (produced using the correct private key), and then construct the corresponding RTU object.

`ConstructSigned` returns a `PackedRTU` for easier handling.

If you need a `RawRTU`, use `ConstructSignedRaw`.

If you want a fully parsed `rtu.RTU` object, use `ConstructSignedObj`.

`ConstructSigned` returns a PackedRTU for easier usage, to get a `RawRTU` use `ConstructSignedRaw`,
or if you wish to get an `rtu.RTU` object, use `ConstructSignedObj`.

```go
var publicKey rtu.PublicKey // get public key for your signer, import *ecdsa.PublicKey with rtu.NewECPublicKey(publicKey)
Expand Down Expand Up @@ -335,7 +372,7 @@ payload, err := signedObj.Parse(true)
if err != nil {
// error here means the payload was malformed, signature was bad or the payload structure fields were invalid
// (validUntil field is no longer valid, transactionId field is empty or is too large, CPK malformed etc.)
// validation errors return an *rtu.ValidationError error, which has the exact fields that was bad
// validation errors return an *rtu.ValidationError error, which has the exact fields that were invalid
panic(err)
}

Expand Down
4 changes: 4 additions & 0 deletions internal/examples/external-signer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import (
rtu "github.com/MyNextID/ioss-rtu-go-sdk"
)

/*
This example generates an *ecdsa.PrivateKey, prepares an example rtu.Payload, generates a digest and raw payload from ExternalSigner,
hands it off to be signed by "a different service", and uses the raw payload and signature to create a signed rtu.PackedRTU
*/
func main() {
// generate an example ecdsa.PrivateKey (you would use your valid IOSS private key here)
externalKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Expand Down
4 changes: 4 additions & 0 deletions internal/examples/signer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import (
rtu "github.com/MyNextID/ioss-rtu-go-sdk"
)

/*
This example generates an *ecdsa.PrivateKey, prepares an example rtu.Payload, and signs it with the key, creating an
*rtu.RTU object, which is packed into rtu.PackedRTU (which is ready to be sent)
*/
func main() {
// generate an example ecdsa.PrivateKey (you would use your valid IOSS private key here)
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Expand Down
41 changes: 41 additions & 0 deletions internal/examples/verify/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"time"

rtu "github.com/MyNextID/ioss-rtu-go-sdk"
)

// A helper function to generate the PackedRTU to parse in this example
func generateAValidPackedRTU() rtu.PackedRTU {
// generate an example ecdsa.PrivateKey (you would use your valid IOSS private key here)
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
// build a rtu.PrivateKey
privKey, err := rtu.NewECPrivateKey(key)
if err != nil {
panic(err)
}
// create your RTU payload
txID := "tx-id"
validUntil := time.Now().Add(time.Hour * 24 * 30)
payload := rtu.NewPayload(txID, validUntil).
SetDelegatedUse(false).
SetSellerName("Acme Corp")
// Sign the payload and generate the signed *rtu.RTU object with Version 1
signedObj, err := rtu.SignV1(payload, privKey)
if err != nil {
panic(err)
}
// pack the signedObj to get the final base64-url encoded IOSSRTU
packedRtu, err := signedObj.Pack()
if err != nil {
panic(err)
}
return packedRtu
}
37 changes: 3 additions & 34 deletions internal/examples/verify/main.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,14 @@
package main

import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"fmt"
"time"

rtu "github.com/MyNextID/ioss-rtu-go-sdk"
)

func generateAValidPackedRTU() rtu.PackedRTU {
// generate an example ecdsa.PrivateKey (you would use your valid IOSS private key here)
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
// build a rtu.PrivateKey
privKey, err := rtu.NewECPrivateKey(key)
if err != nil {
panic(err)
}
// create your RTU payload
txID := "tx-id"
validUntil := time.Now().Add(time.Hour * 24 * 30)
payload := rtu.NewPayload(txID, validUntil).
SetDelegatedUse(false).
SetSellerName("Acme Corp")
// Sign the payload and generate the signed *rtu.RTU object with Version 1
signedObj, err := rtu.SignV1(payload, privKey)
if err != nil {
panic(err)
}
// pack the signedObj to get the final base64-url encoded IOSSRTU
packedRtu, err := signedObj.Pack()
if err != nil {
panic(err)
}
return packedRtu
}

/*
This example gets a packedRtu (rtu.PackedRTU) and parses it (verify signature and validate structure) to get rtu.Payload
*/
func main() {
// get your IOSSRTU from a source, in this example we generate a valid one
var packedRtu rtu.PackedRTU = generateAValidPackedRTU()
Expand Down