Skip to content

Commit

Permalink
Merge pull request #6 from microsoft/dev/qmuntal/rsa
Browse files Browse the repository at this point in the history
Implement RSA encrypt/decrypt and sign/verify
  • Loading branch information
qmuntal authored Jun 30, 2022
2 parents 336964b + ce4cf37 commit 75b9117
Show file tree
Hide file tree
Showing 9 changed files with 798 additions and 89 deletions.
76 changes: 28 additions & 48 deletions cng/aes.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"crypto/cipher"
"errors"
"runtime"
"sync"
"unsafe"

"github.com/microsoft/go-crypto-winnative/internal/bcrypt"
Expand All @@ -19,57 +18,38 @@ import (

const aesBlockSize = 16

var aesCache sync.Map

type aesAlgorithm struct {
h bcrypt.ALG_HANDLE
allowedKeySizes []int
}

type aesCacheEntry struct {
id string
mode string
}

func loadAes(id string, mode string) (h aesAlgorithm, err error) {
if v, ok := aesCache.Load(aesCacheEntry{id, mode}); ok {
return v.(aesAlgorithm), nil
}
err = bcrypt.OpenAlgorithmProvider(&h.h, utf16PtrFromString(id), nil, bcrypt.ALG_NONE_FLAG)
if err != nil {
return
}
defer func() {
func loadAes(mode string) (aesAlgorithm, error) {
v, err := loadOrStoreAlg(bcrypt.AES_ALGORITHM, bcrypt.ALG_NONE_FLAG, mode, func(h bcrypt.ALG_HANDLE) (interface{}, error) {
// Windows 8 added support to set the CipherMode value on a key,
// but Windows 7 requires that it be set on the algorithm before key creation.
err := setString(bcrypt.HANDLE(h), bcrypt.CHAINING_MODE, mode)
if err != nil {
bcrypt.CloseAlgorithmProvider(h.h, 0)
h.h = 0
return nil, err
}
}()
// Windows 8 added support to set the CipherMode value on a key,
// but Windows 7 requires that it be set on the algorithm before key creation.
err = setString(bcrypt.HANDLE(h.h), bcrypt.CHAINING_MODE, mode)
if err != nil {
return
}
var info bcrypt.KEY_LENGTHS_STRUCT
var discard uint32
err = bcrypt.GetProperty(bcrypt.HANDLE(h.h), utf16PtrFromString(bcrypt.KEY_LENGTHS), (*[unsafe.Sizeof(info)]byte)(unsafe.Pointer(&info))[:], &discard, 0)
var info bcrypt.KEY_LENGTHS_STRUCT
var discard uint32
err = bcrypt.GetProperty(bcrypt.HANDLE(h), utf16PtrFromString(bcrypt.KEY_LENGTHS), (*[unsafe.Sizeof(info)]byte)(unsafe.Pointer(&info))[:], &discard, 0)
if err != nil {
return nil, err
}
if info.Increment == 0 || info.MinLength > info.MaxLength {
return nil, errors.New("invalid BCRYPT_KEY_LENGTHS_STRUCT")
}
var allowedKeySizes []int
for size := info.MinLength; size <= info.MaxLength; size += info.Increment {
allowedKeySizes = append(allowedKeySizes, int(size))
}
return aesAlgorithm{h, allowedKeySizes}, nil
})
if err != nil {
return
return aesAlgorithm{}, nil
}
if info.Increment == 0 || info.MinLength > info.MaxLength {
err = errors.New("invalid BCRYPT_KEY_LENGTHS_STRUCT")
return
}
for size := info.MinLength; size <= info.MaxLength; size += info.Increment {
h.allowedKeySizes = append(h.allowedKeySizes, int(size))
}
if existing, loaded := aesCache.LoadOrStore(aesCacheEntry{id, mode}, h); loaded {
// We can safely use a provider that has already been cached in another concurrent goroutine.
bcrypt.CloseAlgorithmProvider(h.h, 0)
h = existing.(aesAlgorithm)
}
return
return v.(aesAlgorithm), nil
}

type aesCipher struct {
Expand All @@ -78,7 +58,7 @@ type aesCipher struct {
}

func NewAESCipher(key []byte) (cipher.Block, error) {
h, err := loadAes(bcrypt.AES_ALGORITHM, bcrypt.CHAIN_MODE_ECB)
h, err := loadAes(bcrypt.CHAIN_MODE_ECB)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -194,7 +174,7 @@ type aesCBC struct {
}

func newCBC(encrypt bool, key, iv []byte) *aesCBC {
h, err := loadAes(bcrypt.AES_ALGORITHM, bcrypt.CHAIN_MODE_CBC)
h, err := loadAes(bcrypt.CHAIN_MODE_CBC)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -268,7 +248,7 @@ func (g *aesGCM) finalize() {
}

func newGCM(key []byte, tls bool) (*aesGCM, error) {
h, err := loadAes(bcrypt.AES_ALGORITHM, bcrypt.CHAIN_MODE_GCM)
h, err := loadAes(bcrypt.CHAIN_MODE_GCM)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -328,7 +308,7 @@ func (g *aesGCM) Seal(dst, nonce, plaintext, additionalData []byte) []byte {

info := bcrypt.NewAUTHENTICATED_CIPHER_MODE_INFO(nonce, additionalData, out[len(out)-gcmTagSize:])
var encSize uint32
err := bcrypt.Encrypt(g.kh, plaintext, info, nil, out, &encSize, 0)
err := bcrypt.Encrypt(g.kh, plaintext, unsafe.Pointer(info), nil, out, &encSize, 0)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -365,7 +345,7 @@ func (g *aesGCM) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, er

info := bcrypt.NewAUTHENTICATED_CIPHER_MODE_INFO(nonce, additionalData, tag)
var decSize uint32
err := bcrypt.Decrypt(g.kh, ciphertext, info, nil, out, &decSize, 0)
err := bcrypt.Decrypt(g.kh, ciphertext, unsafe.Pointer(info), nil, out, &decSize, 0)
if err != nil || int(decSize) != len(ciphertext) {
for i := range out {
out[i] = 0
Expand Down
31 changes: 31 additions & 0 deletions cng/bbig/big.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package bbig

import (
"math/big"

"github.com/microsoft/go-crypto-winnative/cng"
)

func Enc(b *big.Int) cng.BigInt {
if b == nil {
return nil
}
x := b.Bytes()
if len(x) == 0 {
return cng.BigInt{}
}
return x
}

func Dec(b cng.BigInt) *big.Int {
if b == nil {
return nil
}
if len(b) == 0 {
return new(big.Int)
}
return new(big.Int).SetBytes(b)
}
14 changes: 14 additions & 0 deletions cng/big.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package cng

// This file does not have build constraints to
// facilitate using BigInt in Go crypto.
// Go crypto references BigInt unconditionally,
// even if it is not finally used.

// A BigInt is the big-endian bytes from a math/big BigInt.
// Windows BCrypt accepts this specific data format.
// This definition allows us to avoid importing math/big.
// Conversion between BigInt and *big.Int is in cng/bbig.
type BigInt []byte
34 changes: 31 additions & 3 deletions cng/cng.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"math"
"reflect"
"runtime"
"sync"
"syscall"
"unsafe"

Expand All @@ -25,9 +26,36 @@ func lenU32(s []byte) int {
return len(s)
}

type algCacheEntry struct {
id string
flags uint32
var algCache sync.Map

type newAlgEntryFn func(h bcrypt.ALG_HANDLE) (interface{}, error)

func loadOrStoreAlg(id string, flags bcrypt.AlgorithmProviderFlags, mode string, fn newAlgEntryFn) (interface{}, error) {
var entryKey = struct {
id string
flags bcrypt.AlgorithmProviderFlags
mode string
}{id, flags, mode}

if v, ok := algCache.Load(entryKey); ok {
return v, nil
}
var h bcrypt.ALG_HANDLE
err := bcrypt.OpenAlgorithmProvider(&h, utf16PtrFromString(id), nil, flags)
if err != nil {
return nil, err
}
v, err := fn(h)
if err != nil {
bcrypt.CloseAlgorithmProvider(h, 0)
return nil, err
}
if existing, loaded := algCache.LoadOrStore(entryKey, v); loaded {
// We can safely use a provider that has already been cached in another concurrent goroutine.
bcrypt.CloseAlgorithmProvider(h, 0)
v = existing
}
return v, nil
}

func utf16PtrFromString(s string) *uint16 {
Expand Down
Loading

0 comments on commit 75b9117

Please sign in to comment.