Skip to content

Commit

Permalink
implement RSA public and private keys
Browse files Browse the repository at this point in the history
  • Loading branch information
qmuntal committed Mar 18, 2022
1 parent 9eae00e commit 17459e4
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 0 deletions.
173 changes: 173 additions & 0 deletions cng/rsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//go:build windows
// +build windows

package cng

import (
"math/big"
"runtime"
"sync"
"unsafe"

"github.com/microsoft/go-crypto-winnative/internal/bcrypt"
)

var rsaCache sync.Map

type rsaAlgorithm struct {
h bcrypt.ALG_HANDLE

This comment has been minimized.

Copy link
@jaredpar

jaredpar Jun 30, 2022

Member

Consider a more meaningful name like handle here.

This comment has been minimized.

Copy link
@qmuntal

qmuntal Jul 4, 2022

Author Contributor

Done!

}

func loadRsa(id string, flags bcrypt.AlgorithmProviderFlags) (h rsaAlgorithm, err error) {
if v, ok := rsaCache.Load(algCacheEntry{id, uint32(flags)}); ok {
return v.(rsaAlgorithm), nil
}
err = bcrypt.OpenAlgorithmProvider(&h.h, utf16PtrFromString(id), nil, flags)
if err != nil {
return
}
rsaCache.Store(algCacheEntry{id, uint32(flags)}, h)
return
}

const sizeOfRSABlobHeader = uint32(unsafe.Sizeof(bcrypt.RSAKEY_BLOB{}))

func GenerateKeyRSA(bits int) (N, E, D, P, Q, Dp, Dq, Qinv *big.Int, err error) {
bad := func(e error) (N, E, D, P, Q, Dp, Dq, Qinv *big.Int, err error) {
return nil, nil, nil, nil, nil, nil, nil, nil, e
}
h, err := loadRsa(bcrypt.RSA_ALGORITHM, bcrypt.ALG_NONE_FLAG)
if err != nil {
return bad(err)
}
var hkey bcrypt.KEY_HANDLE
err = bcrypt.GenerateKeyPair(h.h, &hkey, uint32(bits), 0)
if err != nil {
return bad(err)
}
defer bcrypt.DestroyKey(hkey)
// The key cannot be used until BcryptFinalizeKeyPair has been called.
err = bcrypt.FinalizeKeyPair(hkey, 0)
if err != nil {
return bad(err)
}

var size uint32
err = bcrypt.ExportKey(hkey, 0, utf16PtrFromString(bcrypt.RSAFULLPRIVATE_BLOB), nil, &size, 0)
if err != nil {
return bad(err)
}

blob := make([]byte, size)
err = bcrypt.ExportKey(hkey, 0, utf16PtrFromString(bcrypt.RSAFULLPRIVATE_BLOB), blob, &size, 0)
if err != nil {
return bad(err)
}
hdr := (*(*bcrypt.RSAKEY_BLOB)(unsafe.Pointer(&blob[0])))
if hdr.Magic != bcrypt.RSAFULLPRIVATE_MAGIC || hdr.BitLength != uint32(bits) {
panic("crypto/rsa: exported key is corrupted")
}
data := blob[sizeOfRSABlobHeader:]
newInt := func(size uint32) *big.Int {
b := new(big.Int).SetBytes(data[:size])
data = data[size:]
return b
}
E = newInt(hdr.PublicExpSize)
N = newInt(hdr.ModulusSize)
P = newInt(hdr.Prime1Size)
Q = newInt(hdr.Prime2Size)
Dp = newInt(hdr.Prime1Size)
Dq = newInt(hdr.Prime2Size)
Qinv = newInt(hdr.Prime1Size)
D = newInt(hdr.ModulusSize)
return
}

type PublicKeyRSA struct {
pkey bcrypt.KEY_HANDLE
}

func NewPublicKeyRSA(N, E *big.Int) (*PublicKeyRSA, error) {
h, err := loadRsa(bcrypt.RSA_ALGORITHM, bcrypt.ALG_NONE_FLAG)
if err != nil {
return nil, err
}
blob := encodeRSAKey(N, E, nil, nil, nil, nil, nil, nil)
k := new(PublicKeyRSA)
err = bcrypt.ImportKeyPair(h.h, 0, utf16PtrFromString(bcrypt.RSAPUBLIC_KEY_BLOB), &k.pkey, blob, 0)
if err != nil {
return nil, err
}
runtime.SetFinalizer(k, (*PublicKeyRSA).finalize)
return k, nil
}

func (k *PublicKeyRSA) finalize() {
bcrypt.DestroyKey(k.pkey)
}

type PrivateKeyRSA struct {
pkey bcrypt.KEY_HANDLE
}

func (k *PrivateKeyRSA) finalize() {
bcrypt.DestroyKey(k.pkey)
}

func NewPrivateKeyRSA(N, E, D, P, Q, Dp, Dq, Qinv *big.Int) (*PrivateKeyRSA, error) {
h, err := loadRsa(bcrypt.RSA_ALGORITHM, bcrypt.ALG_NONE_FLAG)
if err != nil {
return nil, err
}
blob := encodeRSAKey(N, E, D, P, Q, Dp, Dq, Qinv)
k := new(PrivateKeyRSA)
err = bcrypt.ImportKeyPair(h.h, 0, utf16PtrFromString(bcrypt.RSAFULLPRIVATE_BLOB), &k.pkey, blob, 0)
if err != nil {
return nil, err
}
runtime.SetFinalizer(k, (*PrivateKeyRSA).finalize)
return k, nil
}

func bigIntBytesLen(b *big.Int) uint32 {
return uint32(b.BitLen()+7) / 8
}

func encodeRSAKey(N, E, D, P, Q, Dp, Dq, Qinv *big.Int) []byte {
hdr := bcrypt.RSAKEY_BLOB{
BitLength: uint32(N.BitLen()),
PublicExpSize: bigIntBytesLen(E),
ModulusSize: bigIntBytesLen(N),
}
var blob []byte
if D == nil {
hdr.Magic = bcrypt.RSAPUBLIC_MAGIC
blob = make([]byte, sizeOfRSABlobHeader+hdr.PublicExpSize+hdr.ModulusSize)
} else {
hdr.Magic = bcrypt.RSAFULLPRIVATE_MAGIC
hdr.Prime1Size = bigIntBytesLen(P)
hdr.Prime2Size = bigIntBytesLen(Q)
blob = make([]byte, sizeOfRSABlobHeader+hdr.PublicExpSize+hdr.ModulusSize*2+hdr.Prime1Size*3+hdr.Prime2Size*2)
}
copy(blob[:sizeOfRSABlobHeader], (*(*[1<<31 - 1]byte)(unsafe.Pointer(&hdr)))[:sizeOfRSABlobHeader])
data := blob[sizeOfRSABlobHeader:]
encode := func(b *big.Int, size uint32) {
b.FillBytes(data[:size])
data = data[size:]
}
encode(E, hdr.PublicExpSize)
encode(N, hdr.ModulusSize)
if D != nil {
encode(P, hdr.Prime1Size)
encode(Q, hdr.Prime2Size)
encode(Dp, hdr.Prime1Size)
encode(Dq, hdr.Prime2Size)
encode(Qinv, hdr.Prime1Size)
encode(D, hdr.ModulusSize)
}
return blob
}
31 changes: 31 additions & 0 deletions cng/rsa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//go:build windows
// +build windows

package cng

import (
"strconv"
"testing"
)

func TestRSAKeyGeneration(t *testing.T) {
for _, size := range []int{2048, 3072} {
t.Run(strconv.Itoa(size), func(t *testing.T) {
N, E, D, P, Q, Dp, Dq, Qinv, err := GenerateKeyRSA(size)
if err != nil {
t.Fatalf("GenerateKeyRSA(%d): %v", size, err)
}
_, err = NewPrivateKeyRSA(N, E, D, P, Q, Dp, Dq, Qinv)
if err != nil {
t.Fatalf("NewPrivateKeyRSA(%d): %v", size, err)
}
_, err = NewPublicKeyRSA(N, E)
if err != nil {
t.Fatalf("NewPublicKeyRSA(%d): %v", size, err)
}
})
}
}
27 changes: 27 additions & 0 deletions internal/bcrypt/bcrypt_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
SHA384_ALGORITHM = "SHA384"
SHA512_ALGORITHM = "SHA512"
AES_ALGORITHM = "AES"
RSA_ALGORITHM = "RSA"
)

const (
Expand All @@ -28,6 +29,11 @@ const (
KEY_LENGTHS = "KeyLengths"
)

const (
RSAPUBLIC_KEY_BLOB = "RSAPUBLICBLOB"
RSAFULLPRIVATE_BLOB = "RSAFULLPRIVATEBLOB"
)

const (
USE_SYSTEM_PREFERRED_RNG = 0x00000002
)
Expand All @@ -39,6 +45,13 @@ const (
ALG_HANDLE_HMAC_FLAG AlgorithmProviderFlags = 0x00000008
)

type KeyBlobMagicNumber uint32

const (
RSAPUBLIC_MAGIC KeyBlobMagicNumber = 0x31415352
RSAFULLPRIVATE_MAGIC KeyBlobMagicNumber = 0x33415352
)

type (
HANDLE syscall.Handle
ALG_HANDLE HANDLE
Expand Down Expand Up @@ -88,6 +101,16 @@ func NewAUTHENTICATED_CIPHER_MODE_INFO(nonce, additionalData, tag []byte) *AUTHE
return &info
}

// https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_rsakey_blob
type RSAKEY_BLOB struct {
Magic KeyBlobMagicNumber
BitLength uint32
PublicExpSize uint32
ModulusSize uint32
Prime1Size uint32
Prime2Size uint32
}

//sys SetProperty(hObject HANDLE, pszProperty *uint16, pbInput []byte, dwFlags uint32) (s error) = bcrypt.BCryptSetProperty
//sys GetProperty(hObject HANDLE, pszProperty *uint16, pbOutput []byte, pcbResult *uint32, dwFlags uint32) (s error) = bcrypt.BCryptGetProperty
//sys OpenAlgorithmProvider(phAlgorithm *ALG_HANDLE, pszAlgId *uint16, pszImplementation *uint16, dwFlags AlgorithmProviderFlags) (s error) = bcrypt.BCryptOpenAlgorithmProvider
Expand All @@ -108,6 +131,10 @@ func NewAUTHENTICATED_CIPHER_MODE_INFO(nonce, additionalData, tag []byte) *AUTHE
// Keys

//sys GenerateSymmetricKey(hAlgorithm ALG_HANDLE, phKey *KEY_HANDLE, pbKeyObject []byte, pbSecret []byte, dwFlags uint32) (s error) = bcrypt.BCryptGenerateSymmetricKey
//sys GenerateKeyPair(hAlgorithm ALG_HANDLE, phKey *KEY_HANDLE, dwLength uint32, dwFlags uint32) (s error) = bcrypt.BCryptGenerateKeyPair
//sys FinalizeKeyPair(hKey KEY_HANDLE, dwFlags uint32) (s error) = bcrypt.BCryptFinalizeKeyPair
//sys ImportKeyPair (hAlgorithm ALG_HANDLE, hImportKey KEY_HANDLE, pszBlobType *uint16, phKey *KEY_HANDLE, pbInput []byte, dwFlags uint32) (s error) = bcrypt.BCryptImportKeyPair
//sys ExportKey(hKey KEY_HANDLE, hExportKey KEY_HANDLE, pszBlobType *uint16, pbOutput []byte, pcbResult *uint32, dwFlags uint32) (s error) = bcrypt.BCryptExportKey
//sys DestroyKey(hKey KEY_HANDLE) (s error) = bcrypt.BCryptDestroyKey
//sys Encrypt(hKey KEY_HANDLE, pbInput []byte, pPaddingInfo *AUTHENTICATED_CIPHER_MODE_INFO, pbIV []byte, pbOutput []byte, pcbResult *uint32, dwFlags uint32) (s error) = bcrypt.BCryptEncrypt
//sys Decrypt(hKey KEY_HANDLE, pbInput []byte, pPaddingInfo *AUTHENTICATED_CIPHER_MODE_INFO, pbIV []byte, pbOutput []byte, pcbResult *uint32, dwFlags uint32) (s error) = bcrypt.BCryptDecrypt
44 changes: 44 additions & 0 deletions internal/bcrypt/zsyscall_windows.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 17459e4

Please sign in to comment.