-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR adds RSA key support. RSA keys are used on the browser since browsers do not yet support ed25519 keys (or at least support is patchy and not without bugs). We'll likely only need RSA key _verification_ but for completeness this PR includes signing functionality also.
- Loading branch information
Alan Shaw
authored
Aug 16, 2024
1 parent
d7e26c4
commit 6cf686f
Showing
8 changed files
with
312 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package multiformat | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
|
||
"github.com/multiformats/go-varint" | ||
) | ||
|
||
func TagWith(code uint64, bytes []byte) []byte { | ||
offset := varint.UvarintSize(code) | ||
tagged := make([]byte, len(bytes)+offset) | ||
varint.PutUvarint(tagged, code) | ||
copy(tagged[offset:], bytes) | ||
return tagged | ||
} | ||
|
||
func UntagWith(code uint64, source []byte, offset int) ([]byte, error) { | ||
b := source | ||
if offset != 0 { | ||
b = source[offset:] | ||
} | ||
|
||
tag, err := varint.ReadUvarint(bytes.NewReader(b)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if tag != code { | ||
return nil, fmt.Errorf("expected multiformat with 0x%x tag instead got 0x%x", code, tag) | ||
} | ||
|
||
size := varint.UvarintSize(code) | ||
return b[size:], nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package multiformat | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/storacha-network/go-ucanto/testing/helpers" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestTag(t *testing.T) { | ||
t.Run("round trip", func(t *testing.T) { | ||
b := []byte{1, 2, 3} | ||
tb := TagWith(1, b) | ||
utb := helpers.Must(UntagWith(1, tb, 0)) | ||
require.EqualValues(t, b, utb) | ||
}) | ||
|
||
t.Run("incorrect tag", func(t *testing.T) { | ||
b := []byte{1, 2, 3} | ||
tb := TagWith(1, b) | ||
_, err := UntagWith(2, tb, 0) | ||
require.Error(t, err) | ||
require.Equal(t, "expected multiformat with 0x2 tag instead got 0x1", err.Error()) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package signer | ||
|
||
import ( | ||
"crypto" | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/sha256" | ||
"crypto/x509" | ||
"fmt" | ||
|
||
"github.com/multiformats/go-multibase" | ||
"github.com/storacha-network/go-ucanto/did" | ||
"github.com/storacha-network/go-ucanto/principal" | ||
"github.com/storacha-network/go-ucanto/principal/multiformat" | ||
"github.com/storacha-network/go-ucanto/principal/rsa/verifier" | ||
"github.com/storacha-network/go-ucanto/ucan/crypto/signature" | ||
) | ||
|
||
const Code = 0x1300 | ||
const Name = verifier.Name | ||
|
||
const SignatureCode = verifier.SignatureCode | ||
const SignatureAlgorithm = verifier.SignatureAlgorithm | ||
|
||
const keySize = 2048 | ||
|
||
func Generate() (principal.Signer, error) { | ||
priv, err := rsa.GenerateKey(rand.Reader, keySize) | ||
if err != nil { | ||
return nil, fmt.Errorf("generating RSA key: %s", err) | ||
} | ||
|
||
// Next we need to encode public key, because `RSAVerifier` uses it to | ||
// for implementing the `DID()` method. | ||
pubbytes := multiformat.TagWith(verifier.Code, x509.MarshalPKCS1PublicKey(&priv.PublicKey)) | ||
|
||
verif, err := verifier.Decode(pubbytes) | ||
if err != nil { | ||
return nil, fmt.Errorf("decoding public bytes: %s", err) | ||
} | ||
|
||
// Export key in Private Key Cryptography Standards (PKCS) format and extract | ||
// the bytes corresponding to the private key, which we tag with RSA private | ||
// key multiformat code. With both binary and actual key representation we | ||
// create a RSASigner view. | ||
prvbytes := multiformat.TagWith(Code, x509.MarshalPKCS1PrivateKey(priv)) | ||
|
||
return rsasigner{bytes: prvbytes, privKey: priv, verifier: verif}, nil | ||
} | ||
|
||
func Parse(str string) (principal.Signer, error) { | ||
_, bytes, err := multibase.Decode(str) | ||
if err != nil { | ||
return nil, fmt.Errorf("decoding multibase string: %s", err) | ||
} | ||
return Decode(bytes) | ||
} | ||
|
||
func Format(signer principal.Signer) (string, error) { | ||
return multibase.Encode(multibase.Base64pad, signer.Encode()) | ||
} | ||
|
||
func Decode(b []byte) (principal.Signer, error) { | ||
utb, err := multiformat.UntagWith(Code, b, 0) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
priv, err := x509.ParsePKCS1PrivateKey(utb) | ||
if err != nil { | ||
return nil, fmt.Errorf("parsing private key: %s", err) | ||
} | ||
|
||
pubbytes := multiformat.TagWith(verifier.Code, x509.MarshalPKCS1PublicKey(&priv.PublicKey)) | ||
|
||
verif, err := verifier.Decode(pubbytes) | ||
if err != nil { | ||
return nil, fmt.Errorf("decoding public bytes: %s", err) | ||
} | ||
|
||
return rsasigner{bytes: b, privKey: priv, verifier: verif}, nil | ||
} | ||
|
||
type rsasigner struct { | ||
bytes []byte | ||
privKey *rsa.PrivateKey | ||
verifier principal.Verifier | ||
} | ||
|
||
func (s rsasigner) Code() uint64 { | ||
return Code | ||
} | ||
|
||
func (s rsasigner) SignatureCode() uint64 { | ||
return SignatureCode | ||
} | ||
|
||
func (s rsasigner) SignatureAlgorithm() string { | ||
return SignatureAlgorithm | ||
} | ||
|
||
func (s rsasigner) Verifier() principal.Verifier { | ||
return s.verifier | ||
} | ||
|
||
func (s rsasigner) DID() did.DID { | ||
return s.verifier.DID() | ||
} | ||
|
||
func (s rsasigner) Encode() []byte { | ||
return s.bytes | ||
} | ||
|
||
func (s rsasigner) Sign(msg []byte) signature.SignatureView { | ||
hash := sha256.New() | ||
hash.Write(msg) | ||
digest := hash.Sum(nil) | ||
sig, _ := rsa.SignPKCS1v15(nil, s.privKey, crypto.SHA256, digest) | ||
return signature.NewSignatureView(signature.NewSignature(SignatureCode, sig)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package signer | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/storacha-network/go-ucanto/testing/helpers" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestGenerateEncodeDecode(t *testing.T) { | ||
s0 := helpers.Must(Generate()) | ||
fmt.Println(s0.DID().String()) | ||
|
||
s1 := helpers.Must(Decode(s0.Encode())) | ||
fmt.Println(s1.DID().String()) | ||
|
||
require.Equal(t, s0.DID().String(), s1.DID().String()) | ||
} | ||
|
||
func TestGenerateFormatParse(t *testing.T) { | ||
s0 := helpers.Must(Generate()) | ||
fmt.Println(s0.DID().String()) | ||
|
||
str := helpers.Must(Format(s0)) | ||
fmt.Println(str) | ||
|
||
s1 := helpers.Must(Parse(str)) | ||
fmt.Println(s1.DID().String()) | ||
|
||
require.Equal(t, s0.DID().String(), s1.DID().String()) | ||
} | ||
|
||
func TestVerify(t *testing.T) { | ||
s0 := helpers.Must(Generate()) | ||
|
||
msg := []byte("testy") | ||
sig := s0.Sign(msg) | ||
|
||
res := s0.Verifier().Verify(msg, sig) | ||
require.Equal(t, true, res) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package verifier | ||
|
||
import ( | ||
"crypto" | ||
"crypto/rsa" | ||
"crypto/sha256" | ||
"crypto/x509" | ||
"fmt" | ||
|
||
"github.com/storacha-network/go-ucanto/did" | ||
"github.com/storacha-network/go-ucanto/principal" | ||
"github.com/storacha-network/go-ucanto/principal/multiformat" | ||
"github.com/storacha-network/go-ucanto/ucan/crypto/signature" | ||
) | ||
|
||
const Code = 0x1205 | ||
const Name = "RSA" | ||
|
||
const SignatureCode = signature.RS256 | ||
const SignatureAlgorithm = "RS256" | ||
|
||
func Parse(str string) (principal.Verifier, error) { | ||
did, err := did.Parse(str) | ||
if err != nil { | ||
return nil, fmt.Errorf("parsing DID: %s", err) | ||
} | ||
return Decode(did.Bytes()) | ||
} | ||
|
||
func Decode(b []byte) (principal.Verifier, error) { | ||
utb, err := multiformat.UntagWith(Code, b, 0) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
pub, err := x509.ParsePKCS1PublicKey(utb) | ||
if err != nil { | ||
return nil, fmt.Errorf("parsing public key: %s", err) | ||
} | ||
|
||
return rsaverifier{bytes: b, pubKey: pub}, nil | ||
} | ||
|
||
type rsaverifier struct { | ||
bytes []byte | ||
pubKey *rsa.PublicKey | ||
} | ||
|
||
func (v rsaverifier) Code() uint64 { | ||
return Code | ||
} | ||
|
||
func (v rsaverifier) Verify(msg []byte, sig signature.Signature) bool { | ||
if sig.Code() != signature.RS256 { | ||
return false | ||
} | ||
|
||
hash := sha256.New() | ||
hash.Write(msg) | ||
digest := hash.Sum(nil) | ||
|
||
err := rsa.VerifyPKCS1v15(v.pubKey, crypto.SHA256, digest, sig.Raw()) | ||
return err == nil | ||
} | ||
|
||
func (v rsaverifier) DID() did.DID { | ||
id, _ := did.Decode(v.bytes) | ||
return id | ||
} | ||
|
||
func (v rsaverifier) Encode() []byte { | ||
return v.bytes | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package verifier | ||
|
||
import "testing" | ||
|
||
func TestParse(t *testing.T) { | ||
str := "did:key:z4MXj1wBzi9jUstyNgxg2TNN9cNWH8BzcMa5iZ9DAUiLutvQPgBu3zE385tUsbd4oVfHwFb2afSmHpKG4x8JVzESNPSCri4fgztu9FdV3FArz2gByZ9E6zKk3snQKuRjfMJTf29b4BLwGu9j7BtJnhR7bWDWvNqo2YSAwEP8UXyV1W7Meiu96v4esmv2sBLug4vkMFDKXx8bdYZNJYGQQHYrqGXRStZZYGK9xiddMutKeopr1q9UKrczbFhWbdsHW587y4p4uVfwj8evGak6Gx7ADHyQPJc5jWmmUXTzZHJwTqEXDekFkQwkfR9ycxWKnSmPcN9mnimKmuD4LMMzZbodM8Ukgo7XGW8HbiUf3utjt6carBD4c" | ||
v, err := Parse(str) | ||
if err != nil { | ||
t.Fatalf("parsing DID: %s", err) | ||
} | ||
if v.DID().String() != str { | ||
t.Fatalf("expected %s to equal %s", v.DID().String(), str) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import ( | |
) | ||
|
||
const EdDSA = 0xd0ed | ||
const RS256 = 0xd01205 | ||
|
||
type Signature interface { | ||
Code() uint64 | ||
|